gnucash master: Multiple changes pushed
Geert Janssens
gjanssens at code.gnucash.org
Tue Sep 12 08:20:56 EDT 2017
Updated via https://github.com/Gnucash/gnucash/commit/0cdd7769 (commit)
via https://github.com/Gnucash/gnucash/commit/37ecf56f (commit)
via https://github.com/Gnucash/gnucash/commit/6ec9aa36 (commit)
via https://github.com/Gnucash/gnucash/commit/65f18589 (commit)
via https://github.com/Gnucash/gnucash/commit/2275603e (commit)
via https://github.com/Gnucash/gnucash/commit/240bc32e (commit)
via https://github.com/Gnucash/gnucash/commit/6c8275a7 (commit)
from https://github.com/Gnucash/gnucash/commit/ac86ca94 (commit)
commit 0cdd7769415d5af042a7d9026cb97e06a96637c6
Author: lmat <dartme18 at gmail.com>
Date: Sun Sep 10 15:42:54 2017 -0400
Adding python tests to cmake
And removing some debugging comments
diff --git a/util/ci/arch-docker b/util/ci/arch-docker
index 4d9bcc6..a5d3120 100644
--- a/util/ci/arch-docker
+++ b/util/ci/arch-docker
@@ -1,6 +1,4 @@
from base/archlinux
-#run pacman -Syu --quiet --noconfirm > /dev/null
-#run pacman -S --noconfirm archlinux-keyring
run pacman -Syu --quiet --noconfirm gcc cmake make autoconf automake boost python2 pkg-config guile2.0 guile git ninja gtest gmock sqlite3 webkit2gtk swig gwenhywfar aqbanking intltool libxslt postgresql-libs libmariadbclient libdbi libdbi-drivers > /dev/null
run echo en_US.UTF-8 UTF-8 >> /etc/locale.gen
run echo en_GB.UTF-8 UTF-8 >> /etc/locale.gen
diff --git a/util/ci/commonbuild b/util/ci/commonbuild
index f94f9ff..3df33a5 100644
--- a/util/ci/commonbuild
+++ b/util/ci/commonbuild
@@ -11,7 +11,7 @@ if [[ "$BUILDTYPE" == "cmake-make" ]]; then
make -j 4;
make check || ../afterfailure;
elif [[ "$BUILDTYPE" == "cmake-ninja" ]]; then
- cmake ../gnucash -DCMAKE_BUILD_TYPE=debug -DENABLE_DEBUG=on -G Ninja
+ cmake ../gnucash -DWITH_PYTHON=ON -DCMAKE_BUILD_TYPE=debug -DENABLE_DEBUG=on -G Ninja
ninja
ninja check || ../afterfailure;
elif [[ "$BUILDTYPE" == "autotools" ]]; then
diff --git a/util/ci/ubuntu-14.04-docker b/util/ci/ubuntu-14.04-docker
index 5609be4..9a92e69 100644
--- a/util/ci/ubuntu-14.04-docker
+++ b/util/ci/ubuntu-14.04-docker
@@ -1,6 +1,4 @@
from ubuntu:14.04
-#sudo apt-get install -qq software-properties-common
-#sudo add-apt-repository -qq ppa:george-edison55/cmake-3.x
run apt-get update -qq
run apt-get build-dep -qq gnucash > /dev/null
run apt-get install -qq git bash-completion cmake3 make swig xsltproc libdbd-sqlite3 texinfo ninja-build libboost-all-dev libgtk-3-dev libwebkit2gtk-3.0-dev > /dev/null
commit 37ecf56fbbd08746f1807750aa1a7ec9513abb53
Merge: ac86ca9 6ec9aa3
Author: Geert Janssens <geert at kobaltwit.be>
Date: Tue Sep 12 13:48:01 2017 +0200
Merge branch 'maint'
Resolved conflicts:
bindings/python/tests/CMakeLists.txt
diff --cc bindings/python/CMakeLists.txt
index e13b295,0000000..341c298
mode 100644,000000..100644
--- a/bindings/python/CMakeLists.txt
+++ b/bindings/python/CMakeLists.txt
@@@ -1,120 -1,0 +1,109 @@@
+ADD_SUBDIRECTORY(example_scripts)
+ADD_SUBDIRECTORY(tests)
+
+IF (BUILDING_FROM_VCS)
+ SET(SWIG_FILES ${CMAKE_CURRENT_SOURCE_DIR}/gnucash_core.i ${CMAKE_CURRENT_SOURCE_DIR}/timespec.i)
+ SET(GNUCASH_CORE_C_INCLUDES
+ ${CONFIG_H}
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/qofsession.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/qofbook.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/qofbackend.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/qoflog.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/qofutil.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/qofid.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/guid.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/gnc-module/gnc-module.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gnc-engine.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/Transaction.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/Split.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/Account.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gnc-commodity.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gnc-lot.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gnc-numeric.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gncCustomer.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gncEmployee.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gncVendor.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gncAddress.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gncBillTerm.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gncOwner.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gncInvoice.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gncJob.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gncEntry.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gncTaxTable.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gncIDSearch.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/gnc-pricedb.h
+ ${CMAKE_SOURCE_DIR}/libgnucash/app-utils/gnc-prefs-utils.h
+ )
+
+ SET (SWIG_GNUCASH_CORE_C ${CMAKE_CURRENT_BINARY_DIR}/gnucash_core.c)
+
+ GNC_ADD_SWIG_PYTHON_COMMAND (swig-gnucash-core ${SWIG_GNUCASH_CORE_C}
+ ${SWIG_FILES}
+ ${CMAKE_SOURCE_DIR}/common/base-typemaps.i
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine/engine-common.i
+ ${GNUCASH_CORE_C_INCLUDES}
+ )
+ELSE()
+ SET (SWIG_GNUCASH_CORE_C gnucash_core.c)
+ENDIF()
+
+IF(WITH_PYTHON)
+
+ SET(gnucash_core_c_INCLUDE_DIRS
+ ${CMAKE_SOURCE_DIR}/libgnucash
+ ${CMAKE_SOURCE_DIR}/libgnucash/engine
+ ${CMAKE_SOURCE_DIR}/gnucash/gnome-utils
+ ${CMAKE_SOURCE_DIR}/libgnucash/app-utils
+ ${CMAKE_SOURCE_DIR}/libgnucash/gnc-module
+ ${CMAKE_SOURCE_DIR}/gnucash/gnome
+ ${CMAKE_SOURCE_DIR}/libgnucash/core-utils
+ ${CMAKE_SOURCE_DIR}/libgnucash/gnc-module
+ ${GLIB_INCLUDE_DIRS}
+ ${PYTHON_INCLUDE_DIRS}
+ )
+
+ SET(PYEXEC_FILES __init__.py function_class.py gnucash_business.py gnucash_core.py)
+
+ ADD_LIBRARY(gnucash_core_c MODULE ${SWIG_GNUCASH_CORE_C})
+ TARGET_INCLUDE_DIRECTORIES(gnucash_core_c PRIVATE ${gnucash_core_c_INCLUDE_DIRS})
+
+ TARGET_LINK_LIBRARIES(gnucash_core_c gncmod-app-utils gncmod-engine gnc-module ${GLIB_LIBS} ${PYTHON_LIBRARIES})
+ SET_TARGET_PROPERTIES(gnucash_core_c PROPERTIES PREFIX "_")
+ TARGET_COMPILE_OPTIONS(gnucash_core_c PRIVATE -Wno-implicit -Wno-missing-prototypes -Wno-declaration-after-statement -Wno-missing-declarations)
+
+ ADD_EXECUTABLE(sqlite3test EXCLUDE_FROM_ALL sqlite3test.c ${SWIG_GNUCASH_CORE_C})
+ TARGET_LINK_LIBRARIES(sqlite3test gncmod-app-utils gncmod-engine gnc-module ${GLIB_LIBS} ${PYTHON_LIBRARIES})
+ TARGET_INCLUDE_DIRECTORIES(sqlite3test PRIVATE ${gnucash_core_c_INCLUDE_DIRS})
+ TARGET_COMPILE_OPTIONS(sqlite3test PRIVATE -Wno-implicit -Wno-missing-prototypes -Wno-declaration-after-statement -Wno-missing-declarations)
+
+ ADD_TEST(NAME sqlite3test COMMAND sqlite3test)
+ ADD_DEPENDENCIES(check sqlite3test)
+
-
- # Determine where to install the python libraries.
- EXECUTE_PROCESS(
- COMMAND ${PYTHON_EXECUTABLE} -c "import sysconfig; print sysconfig.get_path('platlib', vars = { 'platbase' : '${CMAKE_INSTALL_PREFIX}' } )"
- RESULT_VARIABLE PYTHON_SYSCONFIG_RESULT
- OUTPUT_VARIABLE PYTHON_SYSCONFIG_OUTPUT
- ERROR_VARIABLE PYTHON_SYSCONFIG_ERROR
- OUTPUT_STRIP_TRAILING_WHITESPACE
- ERROR_STRIP_TRAILING_WHITESPACE
- )
- IF (PYTHON_SYSCONFIG_RESULT)
- MESSAGE(SEND_ERROR "Could not determine Python site-package directory:\n${PYTHON_SYSCONFIG_ERROR}")
- ENDIF()
-
+ INSTALL(TARGETS gnucash_core_c
+ LIBRARY DESTINATION ${PYTHON_SYSCONFIG_OUTPUT}/gnucash
+ ARCHIVE DESTINATION ${PYTHON_SYSCONFIG_OUTPUT}/gnucash
+ )
- INSTALL(FILES __init__.py function_class.py gnucash_business.py gnucash_core.py
- ${CMAKE_CURRENT_BINARY_DIR}/gnucash_core_c.py
++ INSTALL(FILES ${PYEXEC_FILES} ${CMAKE_CURRENT_BINARY_DIR}/gnucash_core_c.py
+ DESTINATION ${PYTHON_SYSCONFIG_OUTPUT}/gnucash
+ )
+
- FILE(COPY ${PYEXEC_FILES} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/gnucash)
++ FILE(COPY ${PYEXEC_FILES} DESTINATION ${PYTHON_SYSCONFIG_BUILD}/gnucash)
+
+ ADD_CUSTOM_TARGET(gnucash-core-c-py ALL
- COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/gnucash_core_c.py ${CMAKE_CURRENT_BINARY_DIR}/gnucash
++ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/gnucash_core_c.py ${PYTHON_SYSCONFIG_BUILD}/gnucash
+ DEPENDS ${SWIG_GNUCASH_CORE_C})
+
++ ADD_CUSTOM_TARGET(gnucash-core-c-build ALL
++ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/lib/gnucash/_gnucash_core_c${CMAKE_SHARED_LIBRARY_SUFFIX} ${PYTHON_SYSCONFIG_BUILD}/gnucash
++ DEPENDS gnucash_core_c)
++
+ENDIF()
+
+SET(python_bindings_DATA ${PYEXEC_FILES}
+ gnucash_core.i
+ sqlite3test.c
+ timespec.i)
+
+SET_LOCAL_DIST(python_bindings_DIST_local CMakeLists.txt Makefile.am ${python_bindings_DATA})
+
- SET(python_bindings_DIST ${python_bindings_DIST_local} ${test_python_bindings_DIST} ${example_scripts_DIST} PARENT_SCOPE)
++SET(python_bindings_DIST ${python_bindings_DIST_local} ${test_python_bindings_DIST} ${example_scripts_DIST} PARENT_SCOPE)
diff --cc bindings/python/tests/CMakeLists.txt
index 76058e4,0000000..d0a857a
mode 100644,000000..100644
--- a/bindings/python/tests/CMakeLists.txt
+++ b/bindings/python/tests/CMakeLists.txt
@@@ -1,18 -1,0 +1,18 @@@
+
+IF (WITH_PYTHON)
+ ADD_TEST(python-bindings ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/runTests.py.in)
+ SET_PROPERTY(TEST python-bindings PROPERTY ENVIRONMENT
+ GNC_BUILDDIR=${CMAKE_BINARY_DIR}
- PYTHONPATH=${CMAKE_BINARY_DIR}/bindings/python:${CMAKE_BINARY_DIR}/lib/gnucash:${CMAKE_BINARY_DIR}/common/test-core
++ PYTHONPATH=${PYTHON_SYSCONFIG_BUILD}:${CMAKE_BINARY_DIR}/lib/gnucash:${CMAKE_BINARY_DIR}/common/test-core
+ )
+ENDIF()
+
+SET(test_python_bindings_DATA
+ runTests.py.in
+ test_account.py
+ test_book.py
+ test_business.py
+ test_split.py
+ test_transaction.py)
+
+SET_DIST_LIST(test_python_bindings_DIST CMakeLists.txt Makefile.am ${test_python_bindings_DATA})
diff --cc gnucash/CMakeLists.txt-bin
index 33332c2,0000000..ac8705c
mode 100644,000000..100644
--- a/gnucash/CMakeLists.txt-bin
+++ b/gnucash/CMakeLists.txt-bin
@@@ -1,178 -1,0 +1,191 @@@
+ADD_SUBDIRECTORY(overrides)
+ADD_SUBDIRECTORY(test)
+# Some settings are platform dependent. Let's define them per platform.
+IF (WIN32)
+ # Windows specific settings go here:
+ SET (GNUCASH_RESOURCE_FILE gnucash.rc)
+
+ELSE (WIN32)
+ # All other platforms use these settings:
+ SET (PLATFORM_FILES gnucash-valgrind)
+
+ENDIF (WIN32)
+
+SET (gnucash_SOURCES
+ gnucash-bin.c
+ ${GNUCASH_RESOURCE_FILE}
+)
+
+ADD_EXECUTABLE (gnucash
+ ${gnucash_SOURCES}
+)
+
+TARGET_COMPILE_DEFINITIONS(gnucash PRIVATE -DG_LOG_DOMAIN=\"gnc.bin\")
+
+TARGET_LINK_LIBRARIES (gnucash
+ gncmod-ledger-core gncmod-report-gnome gnc-gnome gncmod-gnome-utils gncmod-app-utils
+ gncmod-engine gnc-module gnc-core-utils gncmod-report-system
+ ${GUILE_LDFLAGS} ${GLIB2_LDFLAGS} ${GTK3_LDFLAGS} ${GTK_MAC_LDFLAGS}
+)
+
+
+IF (MAC_INTEGRATION)
+ TARGET_COMPILE_OPTIONS(gnucash PRIVATE ${OSX_EXTRA_COMPILE_FLAGS})
+ TARGET_LINK_LIBRARIES(gnucash ${OSX_EXTRA_LIBRARIES})
+ENDIF(MAC_INTEGRATION)
+
+INSTALL(TARGETS gnucash DESTINATION bin)
+# No headers to install.
+
+# Generate the gnucash-env script
+SET(SCRIPT_LIST "")
+SET(SCRIPT_OUTPUT_DIR ${BINDIR_BUILD})
+
+FOREACH (script gnucash-env gnucash-make-guids)
+ SET (GNUCASH_ENV_SCRIPT ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${script})
+ LIST(APPEND SCRIPT_LIST ${SCRIPT_OUTPUT_DIR}/${script})
+ SET (GNC_OVERRIDES_DIR ${CMAKE_INSTALL_PREFIX}/libexec/gnucash/overrides)
+ FILE(WRITE ${GNUCASH_ENV_SCRIPT} "#!/bin/sh\n")
+ FILE(APPEND ${GNUCASH_ENV_SCRIPT} "PATH=\"${GNC_OVERRIDES_DIR}:\${PATH}\"\n")
+ FILE(APPEND ${GNUCASH_ENV_SCRIPT} "export PATH\n")
+ FILE(APPEND ${GNUCASH_ENV_SCRIPT} "\nGUILE_WARN_DEPRECATED=\"no\"\n")
+ FILE(APPEND ${GNUCASH_ENV_SCRIPT} "export GUILE_WARN_DEPRECATED\n")
+ FILE(APPEND ${GNUCASH_ENV_SCRIPT} "\nexec \"${script}\" \"\$@\"\n")
+ FILE(COPY ${GNUCASH_ENV_SCRIPT}
+ DESTINATION ${SCRIPT_OUTPUT_DIR}
+ FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
+ )
+ENDFOREACH(script)
+
+SET(TOP_SRC_DIR ${CMAKE_SOURCE_DIR})
+SET(GNUCASH_BIN_INSTALL_NAME "gnucash")
+
+SET(VALGRIND_OUTDIR ${BINDIR_BUILD})
+
+CONFIGURE_FILE(gnucash.rc.in gnucash.rc @ONLY NEWLINE_STYLE WIN32)
+GNC_CONFIGURE(gnucash-valgrind.in ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/gnucash-valgrind)
+
+FILE(COPY ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/gnucash-valgrind
+ DESTINATION ${VALGRIND_OUTDIR}
+ FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
+)
+
+## Create the environment file
+
+FILE(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/environment.in ENV_STRINGS_IN)
+
+SET(ENV_STRINGS_LIST "")
+
+FOREACH(line ${ENV_STRINGS_IN})
+ STRING(REGEX REPLACE "@-|-@" "@" line2 "${line}")
+ STRING(REPLACE ";" "\;" line3 "${line2}")
+ IF(NOT "${line3}" MATCHES "@NOTE")
+ LIST(APPEND ENV_STRINGS_LIST "${line3}\n")
+ ENDIF()
+ENDFOREACH()
+
+STRING(CONCAT ENV_STRINGS ${ENV_STRINGS_LIST})
+STRING(CONFIGURE "${ENV_STRINGS}" ENV_STRINGS_CONF @ONLY)
+
+SET(ENV_FILE_OUT ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/environment)
+SET(BUILD_ENV_FILE_OUT ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/environment.build)
+
+FILE(WRITE ${ENV_FILE_OUT} "${ENV_STRINGS_CONF}")
+FILE(WRITE ${BUILD_ENV_FILE_OUT} "${ENV_STRINGS_CONF}")
+
+SET(XDG_TEXT "
+# GnuCash was not installed in the default location.
+# XDG_DATA_DIRS will be set so that our documentation
+# and gsettings schema are found.\n"
+)
+
+IF (NOT(${GNC_DBD_DIR} STREQUAL "${CMAKE_PREFIX_PATH}/lib/dbd"))
+ FILE(APPEND ${ENV_FILE_OUT} "GNC_DBD_DIR=${GNC_DBD_DIR}")
+ENDIF()
+
+IF (NOT(${DATADIR} STREQUAL "/usr/share") AND NOT(${DATADIR} STREQUAL "/usr/local/share"))
+ FILE(APPEND ${ENV_FILE_OUT} ${XDG_TEXT})
- FILE(APPEND ${ENV_FILE_OUT} "XDG_DATA_DIRS=${DATADIR};{XDG_DATA_DIRS}" "${GNC_SYSTEM_XDG_DATA_DIRS}\n")
++ FILE(APPEND ${ENV_FILE_OUT} "XDG_DATA_DIRS=${DATADIR};{XDG_DATA_DIRS};" "${GNC_SYSTEM_XDG_DATA_DIRS}\n")
+ENDIF()
+
+FILE(APPEND ${BUILD_ENV_FILE_OUT} "GNC_DBD_DIR=${LIBDBI_DRIVERS_DIR}/dbd")
+
+FILE(APPEND ${BUILD_ENV_FILE_OUT} ${XDG_TEXT})
+FILE(APPEND ${BUILD_ENV_FILE_OUT} "XDG_DATA_DIRS=${DATADIR_BUILD};{XDG_DATA_DIRS};" "${GNC_SYSTEM_XDG_DATA_DIRS}\n")
+
++SET(PYTHON_TEXT "
++# Define PYTHONPATH for non default installation path.\n"
++ )
++IF (NOT(${CMAKE_INSTALL_PREFIX} STREQUAL "/usr") AND NOT(${CMAKE_INSTALL_PREFIX} STREQUAL "/usr/local"))
++
++ FILE(APPEND ${ENV_FILE_OUT} ${PYTHON_TEXT})
++ FILE(APPEND ${ENV_FILE_OUT} "PYTHONPATH=${PYTHON_SYSCONFIG_OUTPUT};{PYTHONPATH}")
++ENDIF()
++
++FILE(APPEND ${BUILD_ENV_FILE_OUT} ${PYTHON_TEXT})
++FILE(APPEND ${BUILD_ENV_FILE_OUT} "PYTHONPATH=${PYTHON_SYSCONFIG_BUILD};{PYTHONPATH}")
++
++
+FILE(COPY ${BUILD_ENV_FILE_OUT}
+ DESTINATION ${SYSCONFDIR_BUILD}/gnucash
+ FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
+)
+FILE(RENAME
+ ${SYSCONFDIR_BUILD}/gnucash/environment.build
+ ${SYSCONFDIR_BUILD}/gnucash/environment
+)
+
+SET(ENVIRONMENT_FILE_DIR ${CMAKE_CURRENT_BINARY_DIR})
+FILE(COPY ${ENV_FILE_OUT}
+ DESTINATION ${ENVIRONMENT_FILE_DIR}
+ FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
+)
+
+INSTALL(FILES ${SCRIPT_LIST} ${VALGRIND_OUTDIR}/gnucash-valgrind DESTINATION bin)
+INSTALL(FILES ${ENVIRONMENT_FILE_DIR}/environment DESTINATION etc/gnucash)
+
+SET_LOCAL_DIST(bin_DIST_local CMakeLists.txt environment.in generate-gnc-script gnucash-bin.c gnucash.rc.in gnucash-valgrind.in
+ Makefile.am)
+SET(bin_DIST ${bin_DIST_local} ${overrides_DIST} ${test_bin_DIST} PARENT_SCOPE)
+
+IF (WIN32)
+ # Write out a command script for windows
+ SET(lib_directories boost enchant libsoup mysql pgsql libxslt)
+ SET(bin_directories mingw gnutls goffice libgsf pcre gnome guile webkit regex aqbanking gwenhywfar libofx opensp
+ libdbi sqlite3 mysql pgsql enchant libsoup libxslt)
+
+ SET(CMD_LINES "")
+ SET(BUILD_CMD_LINES "")
+ FOREACH(dir bin lib lib/gnucash)
+ FILE(TO_NATIVE_PATH ${CMAKE_INSTALL_PREFIX}/${dir} INSTALL_PATH_ITEM)
+ FILE(TO_NATIVE_PATH ${CMAKE_BINARY_DIR}/${dir} BUILD_PATH_ITEM)
+ LIST(APPEND CMD_LINES "set PATH=${INSTALL_PATH_ITEM}\;%PATH%\n")
+ LIST(APPEND BUILD_CMD_LINES "set PATH=${BUILD_PATH_ITEM}\;%PATH%\n")
+ ENDFOREACH(dir)
+ IF (NOT ${MINGW64})
+ FOREACH(dir ${lib_directories})
+ FILE(TO_NATIVE_PATH ${CMAKE_PREFIX_PATH}/${dir}/lib PATH_ITEM)
+ LIST(APPEND CMD_LINES "set PATH=${PATH_ITEM}\;%PATH%\n")
+ ENDFOREACH(dir)
+
+ FOREACH(dir ${bin_directories})
+ FILE(TO_NATIVE_PATH ${CMAKE_PREFIX_PATH}/${dir}/bin PATH_ITEM)
+ LIST(APPEND CMD_LINES "set PATH=${PATH_ITEM}\;%PATH%\n")
+ ENDFOREACH(dir)
+ ENDIF (NOT ${MINGW64})
+ SET(CMD_FILE ${CMAKE_CURRENT_BINARY_DIR}/gnucash-launcher.cmd)
+ FILE(WRITE ${CMD_FILE} "@echo off\nsetlocal\n\n")
+ FOREACH(line ${CMD_LINES})
+ FILE(APPEND ${CMD_FILE} "${line}")
+ ENDFOREACH(line)
+ FILE(APPEND ${CMD_FILE} "\nstart gnucash %*\n")
+
+ SET(BUILD_CMD_FILE ${CMAKE_BINARY_DIR}/bin/gnucash-launcher.cmd)
+ FILE(WRITE ${BUILD_CMD_FILE} "@echo off\nsetlocal\n\n")
+ FOREACH(line ${CMD_LINES})
+ FILE(APPEND ${BUILD_CMD_FILE} "${line}")
+ ENDFOREACH(line)
+ FILE(APPEND ${BUILD_CMD_FILE} "\nstart gnucash %*\n")
+
+ INSTALL(PROGRAMS ${CMD_FILE} DESTINATION bin)
+ENDIF(WIN32)
diff --cc gnucash/gnome-utils/gnc-main-window.c
index b3861bb,0000000..2538e21
mode 100644,000000..100644
--- a/gnucash/gnome-utils/gnc-main-window.c
+++ b/gnucash/gnome-utils/gnc-main-window.c
@@@ -1,4839 -1,0 +1,4841 @@@
+/*
+ * gnc-main-window.c -- GtkWindow which represents the
+ * GnuCash main window.
+ *
+ * Copyright (C) 2003 Jan Arne Petersen <jpetersen at uni-bonn.de>
+ * Copyright (C) 2003,2005,2006 David Hampton <hampton at employees.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact:
+ *
+ * Free Software Foundation Voice: +1-617-542-5942
+ * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
+ * Boston, MA 02110-1301, USA gnu at gnu.org
+ */
+
+/** @addtogroup Windows
+ @{ */
+/** @addtogroup GncMainWindow Main Window functions.
+ @{ */
+/** @file gnc-main-window.c
+ @brief Functions for adding content to a window.
+ @author Copyright (C) 2003 Jan Arne Petersen <jpetersen at uni-bonn.de>
+ @author Copyright (C) 2003,2005,2006 David Hampton <hampton at employees.org>
+*/
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "gnc-plugin.h"
+#include "gnc-plugin-manager.h"
+#include "gnc-main-window.h"
+
+#include "dialog-options.h"
+#include "dialog-preferences.h"
+#include "dialog-reset-warnings.h"
+#include "dialog-transfer.h"
+#include "dialog-utils.h"
+#include "file-utils.h"
+#include "gnc-component-manager.h"
+#include "gnc-engine.h"
+#include "gnc-file.h"
+#include "gnc-filepath-utils.h"
+#include "gnc-gkeyfile-utils.h"
+#include "gnc-gnome-utils.h"
+#include "gnc-gobject-utils.h"
+#include "gnc-gui-query.h"
+#include "gnc-hooks.h"
+#include "gnc-icons.h"
+#include "gnc-session.h"
+#include "gnc-state.h"
+#include "gnc-ui.h"
+#include "gnc-ui-util.h"
+#include "gnc-uri-utils.h"
+#include "gnc-version.h"
+#include "gnc-window.h"
+#include "gnc-prefs.h"
+#include "option-util.h"
+// +JSLED
+//#include "gnc-html.h"
+#include "gnc-autosave.h"
+#include "print-session.h"
+#ifdef MAC_INTEGRATION
+#include <gtkmacintegration/gtkosxapplication.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# define __need_system_sys_stat_h //To block Guile-2.0's evil substitute
+# include <sys/types.h>
+# include <sys/stat.h> // for stat(2)
+#endif
+
+/** Names of signals generated by the main window. */
+enum
+{
+ PAGE_ADDED,
+ PAGE_CHANGED,
+ LAST_SIGNAL
+};
+
+/** This label is used to provide a mapping from a visible page widget
+ * back to the corresponding GncPluginPage object. */
+#define PLUGIN_PAGE_LABEL "plugin-page"
+
+#define PLUGIN_PAGE_CLOSE_BUTTON "close-button"
+#define PLUGIN_PAGE_TAB_LABEL "label"
+
+#define GNC_PREF_SHOW_CLOSE_BUTTON "tab-close-buttons"
+#define GNC_PREF_TAB_NEXT_RECENT "tab-next-recent"
+#define GNC_PREF_TAB_POSITION_TOP "tab-position-top"
+#define GNC_PREF_TAB_POSITION_BOTTOM "tab-position-bottom"
+#define GNC_PREF_TAB_POSITION_LEFT "tab-position-left"
+#define GNC_PREF_TAB_POSITION_RIGHT "tab-position-right"
+#define GNC_PREF_TAB_WIDTH "tab-width"
+#define GNC_PREF_TAB_COLOR "show-account-color-tabs"
+#define GNC_PREF_SAVE_CLOSE_EXPIRES "save-on-close-expires"
+#define GNC_PREF_SAVE_CLOSE_WAIT_TIME "save-on-close-wait-time"
+
+#define GNC_MAIN_WINDOW_NAME "GncMainWindow"
+
+#define DIALOG_BOOK_OPTIONS_CM_CLASS "dialog-book-options"
+
+/* Static Globals *******************************************************/
+
+/** The debugging module that this .o belongs to. */
+static QofLogModule log_module = GNC_MOD_GUI;
+/** A pointer to the parent class of an embedded window. */
+static GObjectClass *parent_class = NULL;
+/** An identifier that indicates a "main" window. */
+static GQuark window_type = 0;
+/** A list of all extant main windows. This is for convenience as the
+ * same information can be obtained from the object tracking code. */
+static GList *active_windows = NULL;
+/** Count down timer for the save changes dialog. If the timer reaches zero
+ * any changes will be saved and the save dialog closed automatically */
+static guint secs_to_save = 0;
+#define MSG_AUTO_SAVE _("Changes will be saved automatically in %u seconds")
+
+/* Declarations *********************************************************/
+static void gnc_main_window_class_init (GncMainWindowClass *klass);
+static void gnc_main_window_init (GncMainWindow *window, GncMainWindowClass *klass);
+static void gnc_main_window_finalize (GObject *object);
+static void gnc_main_window_destroy (GtkWidget *widget);
+
+static void gnc_main_window_setup_window (GncMainWindow *window);
+static void gnc_window_main_window_init (GncWindowIface *iface);
+#ifndef MAC_INTEGRATION
+static void gnc_main_window_update_all_menu_items (void);
+#endif
+
+/* Callbacks */
+static void gnc_main_window_add_widget (GtkUIManager *merge, GtkWidget *widget, GncMainWindow *window);
+static void gnc_main_window_switch_page (GtkNotebook *notebook, gpointer *notebook_page, gint pos, GncMainWindow *window);
+static void gnc_main_window_page_reordered (GtkNotebook *notebook, GtkWidget *child, guint pos, GncMainWindow *window);
+static void gnc_main_window_plugin_added (GncPlugin *manager, GncPlugin *plugin, GncMainWindow *window);
+static void gnc_main_window_plugin_removed (GncPlugin *manager, GncPlugin *plugin, GncMainWindow *window);
+static void gnc_main_window_engine_commit_error_callback( gpointer data, QofBackendError errcode );
+
+/* Command callbacks */
+static void gnc_main_window_cmd_page_setup (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_file_properties (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_file_close (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_file_quit (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_edit_cut (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_edit_copy (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_edit_paste (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_edit_preferences (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_view_refresh (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_view_toolbar (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_view_summary (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_view_statusbar (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_actions_reset_warnings (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_actions_rename_page (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_window_new (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_window_move_page (GtkAction *action, GncMainWindow *window);
+#ifndef MAC_INTEGRATION
+static void gnc_main_window_cmd_window_raise (GtkAction *action, GtkRadioAction *current, GncMainWindow *window);
+#endif
+static void gnc_main_window_cmd_help_tutorial (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_help_contents (GtkAction *action, GncMainWindow *window);
+static void gnc_main_window_cmd_help_about (GtkAction *action, GncMainWindow *window);
+
+static void do_popup_menu(GncPluginPage *page, GdkEventButton *event);
+static gboolean gnc_main_window_popup_menu_cb (GtkWidget *widget, GncPluginPage *page);
+static GtkWidget *gnc_main_window_get_statusbar (GncWindow *window_in);
+static void statusbar_notification_lastmodified(void);
+
+#ifdef MAC_INTEGRATION
+static void gnc_quartz_shutdown(GtkosxApplication *theApp, gpointer data);
+static gboolean gnc_quartz_should_quit(GtkosxApplication *theApp, GncMainWindow *window);
+static void gnc_quartz_set_menu(GncMainWindow* window);
+#endif
+
+/** The instance private data structure for an embedded window
+ * object. */
+typedef struct GncMainWindowPrivate
+{
+ /** The dock (vbox) at the top of the window containing the
+ * menubar and toolbar. These items are generated by the UI
+ * manager and stored here when the UI manager provides them
+ * to the main window. */
+ GtkWidget *menu_dock;
+ /** The toolbar created by the UI manager. This pointer
+ * provides easy access for showing/hiding the toolbar. */
+ GtkWidget *toolbar;
+ /** The notebook containing all the pages in this window. */
+ GtkWidget *notebook;
+ /** Show account color as background on tabs */
+ gboolean show_color_tabs;
+ /** A pointer to the status bar at the bottom edge of the
+ * window. This pointer provides easy access for
+ * updating/showing/hiding the status bar. */
+ GtkWidget *statusbar;
+ /** A pointer to the progress bar at the bottom right of the
+ * window that is contained in the status bar. This pointer
+ * provides easy access for updating the progressbar. */
+ GtkWidget *progressbar;
+ /** Pointer to the about dialog. We need this so that we create
+ * only one, can attach to its activate-link signal, and can
+ * destroy it with the main window.
+ */
+ GtkWidget *about_dialog;
+
+ /** The group of all actions provided by the main window
+ * itself. This does not include any action provided by menu
+ * or content plugins. */
+ GtkActionGroup *action_group;
+
+ /** A list of all pages that are installed in this window. */
+ GList *installed_pages;
+ /** A list of pages in order of use (most recent -> least recent) */
+ GList *usage_order;
+ /** The currently selected page. */
+ GncPluginPage *current_page;
+ /** The identifier for this window's engine event handler. */
+ gint event_handler_id;
+
+ /** A hash table of all action groups that have been installed
+ * into this window. The keys are the name of an action
+ * group, the values are structures of type
+ * MergedActionEntry. */
+ GHashTable *merged_actions_table;
+} GncMainWindowPrivate;
+
+#define GNC_MAIN_WINDOW_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), GNC_TYPE_MAIN_WINDOW, GncMainWindowPrivate))
+
+/** This data structure maintains information about one action groups
+ * that has been installed in this window. */
+typedef struct
+{
+ /** The merge identifier for this action group. This number
+ * is provided by the UI manager. */
+ guint merge_id;
+ /** The action group itself. This contains all actions added
+ * by a single menu or content plugin. */
+ GtkActionGroup *action_group;
+} MergedActionEntry;
+
+/** A holding place for all the signals generated by the main window
+ * code. */
+static guint main_window_signals[LAST_SIGNAL] = { 0 };
+
+
+/** An array of all of the actions provided by the main window code.
+ * This includes some placeholder actions for the menus that are
+ * visible in the menu bar but have no action associated with
+ * them. */
+static GtkActionEntry gnc_menu_actions [] =
+{
+ /* Toplevel */
+
+ { "FileAction", NULL, N_("_File"), NULL, NULL, NULL, },
+ { "EditAction", NULL, N_("_Edit"), NULL, NULL, NULL },
+ { "ViewAction", NULL, N_("_View"), NULL, NULL, NULL },
+ { "ActionsAction", NULL, N_("_Actions"), NULL, NULL, NULL },
+ { "TransactionAction", NULL, N_("Tra_nsaction"), NULL, NULL, NULL },
+ { "ReportsAction", NULL, N_("_Reports"), NULL, NULL, NULL },
+ { "ToolsAction", NULL, N_("_Tools"), NULL, NULL, NULL },
+ { "ExtensionsAction", NULL, N_("E_xtensions"), NULL, NULL, NULL },
+ { "WindowsAction", NULL, N_("_Windows"), NULL, NULL, NULL },
+ { "HelpAction", NULL, N_("_Help"), NULL, NULL, NULL },
+
+ /* File menu */
+
+ { "FileImportAction", NULL, N_("_Import"), NULL, NULL, NULL },
+ { "FileExportAction", NULL, N_("_Export"), NULL, NULL, NULL },
+ {
+ "FilePrintAction", "document-print", N_("_Print..."), "<primary>p",
+ N_("Print the currently active page"), NULL
+ },
+#ifndef GTK_STOCK_PAGE_SETUP
+# define GTK_STOCK_PAGE_SETUP NULL
+#endif
+ {
+ "FilePageSetupAction", "document-page-setup", N_("Pa_ge Setup..."), "<primary><shift>p",
+ N_("Specify the page size and orientation for printing"),
+ G_CALLBACK (gnc_main_window_cmd_page_setup)
+ },
+ {
+ "FilePropertiesAction", "document-properties", N_("Proper_ties"), "<Alt>Return",
+ N_("Edit the properties of the current file"),
+ G_CALLBACK (gnc_main_window_cmd_file_properties)
+ },
+ {
+ "FileCloseAction", "window-close", N_("_Close"), "<primary>W",
+ N_("Close the currently active page"),
+ G_CALLBACK (gnc_main_window_cmd_file_close)
+ },
+ {
+ "FileQuitAction", "application-exit", N_("_Quit"), "<primary>Q",
+ N_("Quit this application"),
+ G_CALLBACK (gnc_main_window_cmd_file_quit)
+ },
+
+ /* Edit menu */
+
+ {
+ "EditCutAction", "edit-cut", N_("Cu_t"), "<primary>X",
+ N_("Cut the current selection and copy it to clipboard"),
+ G_CALLBACK (gnc_main_window_cmd_edit_cut)
+ },
+ {
+ "EditCopyAction", "edit-copy", N_("_Copy"), "<primary>C",
+ N_("Copy the current selection to clipboard"),
+ G_CALLBACK (gnc_main_window_cmd_edit_copy)
+ },
+ {
+ "EditPasteAction", "edit-paste", N_("_Paste"), "<primary>V",
+ N_("Paste the clipboard content at the cursor position"),
+ G_CALLBACK (gnc_main_window_cmd_edit_paste)
+ },
+ {
+ "EditPreferencesAction", "preferences-system", N_("Pr_eferences"), NULL,
+ N_("Edit the global preferences of GnuCash"),
+ G_CALLBACK (gnc_main_window_cmd_edit_preferences)
+ },
+
+ /* View menu */
+
+ {
+ "ViewSortByAction", NULL, N_("_Sort By..."), NULL,
+ N_("Select sorting criteria for this page view"), NULL
+ },
+ {
+ "ViewFilterByAction", NULL, N_("_Filter By..."), NULL,
+ N_("Select the account types that should be displayed."), NULL
+ },
+ {
+ "ViewRefreshAction", "view-refresh", N_("_Refresh"), "<primary>r",
+ N_("Refresh this window"),
+ G_CALLBACK (gnc_main_window_cmd_view_refresh)
+ },
+
+ /* Actions menu */
+
+ { "ScrubMenuAction", NULL, N_("_Check & Repair"), NULL, NULL, NULL },
+ {
+ "ActionsForgetWarningsAction", NULL, N_("Reset _Warnings..."), NULL,
+ N_("Reset the state of all warning messages so they will be shown again."),
+ G_CALLBACK (gnc_main_window_cmd_actions_reset_warnings)
+ },
+ {
+ "ActionsRenamePageAction", NULL, N_("Re_name Page"), NULL,
+ N_("Rename this page."),
+ G_CALLBACK (gnc_main_window_cmd_actions_rename_page)
+ },
+
+ /* Windows menu */
+
+ {
+ "WindowNewAction", NULL, N_("_New Window"), NULL,
+ N_("Open a new top-level GnuCash window."),
+ G_CALLBACK (gnc_main_window_cmd_window_new)
+ },
+ {
+ "WindowMovePageAction", NULL, N_("New Window with _Page"), NULL,
+ N_("Move the current page to a new top-level GnuCash window."),
+ G_CALLBACK (gnc_main_window_cmd_window_move_page)
+ },
+
+ /* Help menu */
+
+ {
+ "HelpTutorialAction", "help-browser", N_("Tutorial and Concepts _Guide"), "<primary>H",
+ N_("Open the GnuCash Tutorial"),
+ G_CALLBACK (gnc_main_window_cmd_help_tutorial)
+ },
+ {
+ "HelpContentsAction", "help-browser", N_("_Contents"), "F1",
+ N_("Open the GnuCash Help"),
+ G_CALLBACK (gnc_main_window_cmd_help_contents)
+ },
+ {
+ "HelpAboutAction", "help-about", N_("_About"), NULL,
+ N_("About GnuCash"),
+ G_CALLBACK (gnc_main_window_cmd_help_about)
+ },
+};
+/** The number of actions provided by the main window. */
+static guint gnc_menu_n_actions = G_N_ELEMENTS (gnc_menu_actions);
+
+/** An array of all of the toggle action provided by the main window
+ * code. */
+static GtkToggleActionEntry toggle_actions [] =
+{
+ {
+ "ViewToolbarAction", NULL, N_("_Toolbar"), NULL,
+ N_("Show/hide the toolbar on this window"),
+ G_CALLBACK (gnc_main_window_cmd_view_toolbar), TRUE
+ },
+ {
+ "ViewSummaryAction", NULL, N_("Su_mmary Bar"), NULL,
+ N_("Show/hide the summary bar on this window"),
+ G_CALLBACK (gnc_main_window_cmd_view_summary), TRUE
+ },
+ {
+ "ViewStatusbarAction", NULL, N_("Stat_us Bar"), NULL,
+ N_("Show/hide the status bar on this window"),
+ G_CALLBACK (gnc_main_window_cmd_view_statusbar), TRUE
+ },
+};
+/** The number of toggle actions provided by the main window. */
+static guint n_toggle_actions = G_N_ELEMENTS (toggle_actions);
+
+#ifndef MAC_INTEGRATION
+/** An array of all of the radio action provided by the main window
+ * code. */
+static GtkRadioActionEntry radio_entries [] =
+{
+ { "Window0Action", NULL, N_("Window _1"), NULL, NULL, 0 },
+ { "Window1Action", NULL, N_("Window _2"), NULL, NULL, 1 },
+ { "Window2Action", NULL, N_("Window _3"), NULL, NULL, 2 },
+ { "Window3Action", NULL, N_("Window _4"), NULL, NULL, 3 },
+ { "Window4Action", NULL, N_("Window _5"), NULL, NULL, 4 },
+ { "Window5Action", NULL, N_("Window _6"), NULL, NULL, 5 },
+ { "Window6Action", NULL, N_("Window _7"), NULL, NULL, 6 },
+ { "Window7Action", NULL, N_("Window _8"), NULL, NULL, 7 },
+ { "Window8Action", NULL, N_("Window _9"), NULL, NULL, 8 },
+ { "Window9Action", NULL, N_("Window _0"), NULL, NULL, 9 },
+};
+
+/** The number of radio actions provided by the main window. */
+static guint n_radio_entries = G_N_ELEMENTS (radio_entries);
+#endif
+
+/** These are the "important" actions provided by the main window.
+ * Their labels will appear when the toolbar is set to "Icons and
+ * important text" (e.g. GTK_TOOLBAR_BOTH_HORIZ) mode. */
+static const gchar *gnc_menu_important_actions[] =
+{
+ "FileCloseAction",
+ NULL,
+};
+
+
+/** The following are in the main window so they will always be
+ * present in the menu structure, but they are never sensitive.
+ * These actions should be overridden in child windows where they
+ * have meaning. */
+static const gchar *always_insensitive_actions[] =
+{
+ "FilePrintAction",
+ NULL
+};
+
+
+/** The following items in the main window should be made insensitive
+ * at startup time. The sensitivity will be changed by some later
+ * event. */
+static const gchar *initially_insensitive_actions[] =
+{
+ "FileCloseAction",
+ NULL
+};
+
+
+/** The following are in the main window so they will always be
+ * present in the menu structure, but they are always hidden.
+ * These actions should be overridden in child windows where they
+ * have meaning. */
+static const gchar *always_hidden_actions[] =
+{
+ "ViewSortByAction",
+ "ViewFilterByAction",
+ NULL
+};
+
+
+/** If a page is flagged as immutable, then the following actions
+ * cannot be performed on that page. */
+static const gchar *immutable_page_actions[] =
+{
+ "FileCloseAction",
+ NULL
+};
+
+
+/** The following actions can only be performed if there are multiple
+ * pages in a window. */
+static const gchar *multiple_page_actions[] =
+{
+ "WindowMovePageAction",
+ NULL
+};
+
+
+/************************************************************
+ * *
+ ************************************************************/
+#define WINDOW_COUNT "WindowCount"
+#define WINDOW_STRING "Window %d"
+#define WINDOW_GEOMETRY "WindowGeometry"
+#define WINDOW_POSITION "WindowPosition"
+#define WINDOW_MAXIMIZED "WindowMaximized"
+#define TOOLBAR_VISIBLE "ToolbarVisible"
+#define STATUSBAR_VISIBLE "StatusbarVisible"
+#define SUMMARYBAR_VISIBLE "SummarybarVisible"
+#define WINDOW_FIRSTPAGE "FirstPage"
+#define WINDOW_PAGECOUNT "PageCount"
+#define WINDOW_PAGEORDER "PageOrder"
+#define PAGE_TYPE "PageType"
+#define PAGE_NAME "PageName"
+#define PAGE_STRING "Page %d"
+
+typedef struct
+{
+ GKeyFile *key_file;
+ const gchar *group_name;
+ gint window_num;
+ gint page_num;
+ gint page_offset;
+} GncMainWindowSaveData;
+
+
+/* Iterator function to walk all pages in all windows, calling the
+ * specified function for each page. */
+void
+gnc_main_window_foreach_page (GncMainWindowPageFunc fn, gpointer user_data)
+{
+ GncMainWindowPrivate *priv;
+ GncMainWindow *window;
+ GncPluginPage *page;
+ GList *w, *p;
+
+ ENTER(" ");
+ for (w = active_windows; w; w = g_list_next(w))
+ {
+ window = w->data;
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ for (p = priv->installed_pages; p; p = g_list_next(p))
+ {
+ page = p->data;
+ fn(page, user_data);
+ }
+ }
+ LEAVE(" ");
+}
+
+
+/** Restore a single page to a window. This function calls a page
+ * specific function to create the actual page. It then handles all
+ * the common tasks such as insuring the page is installed into a
+ * window, updating the page name, and anything else that might be
+ * common to all pages.
+ *
+ * @param window The GncMainWindow where the new page will be
+ * installed.
+ *
+ * @param data A data structure containing state about the
+ * window/page restoration process. */
+static void
+gnc_main_window_restore_page (GncMainWindow *window,
+ GncMainWindowSaveData *data)
+{
+ GncMainWindowPrivate *priv;
+ GncPluginPage *page;
+ gchar *page_group, *page_type = NULL, *name = NULL;
+ const gchar *class_type;
+ GError *error = NULL;
+
+ ENTER("window %p, data %p (key file %p, window %d, page start %d, page num %d)",
+ window, data, data->key_file, data->window_num, data->page_offset,
+ data->page_num);
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ page_group = g_strdup_printf(PAGE_STRING,
+ data->page_offset + data->page_num);
+ page_type = g_key_file_get_string(data->key_file, page_group,
+ PAGE_TYPE, &error);
+ if (error)
+ {
+ g_warning("error reading group %s key %s: %s",
+ page_group, PAGE_TYPE, error->message);
+ goto cleanup;
+ }
+
+ /* See if the page already exists. */
+ page = g_list_nth_data(priv->installed_pages, data->page_num);
+ if (page)
+ {
+ class_type = GNC_PLUGIN_PAGE_GET_CLASS(page)->plugin_name;
+ if (strcmp(page_type, class_type) != 0)
+ {
+ g_warning("error: page types don't match: state %s, existing page %s",
+ page_type, class_type);
+ goto cleanup;
+ }
+ }
+ else
+ {
+ /* create and install the page */
+ page = gnc_plugin_page_recreate_page(GTK_WIDGET(window), page_type,
+ data->key_file, page_group);
+ if (page)
+ {
+ /* Does the page still need to be installed into the window? */
+ if (page->window == NULL)
+ {
+ gnc_plugin_page_set_use_new_window(page, FALSE);
+ gnc_main_window_open_page(window, page);
+ }
+
+ /* Restore the page name */
+ name = g_key_file_get_string(data->key_file, page_group,
+ PAGE_NAME, &error);
+ if (error)
+ {
+ g_warning("error reading group %s key %s: %s",
+ page_group, PAGE_NAME, error->message);
+ /* Fall through and still show the page. */
+ }
+ else
+ {
+ DEBUG("updating page name for %p to %s.", page, name);
+ main_window_update_page_name(page, name);
+ g_free(name);
+ }
+ }
+ }
+
+ LEAVE("ok");
+cleanup:
+ if (error)
+ g_error_free(error);
+ if (page_type)
+ g_free(page_type);
+ g_free(page_group);
+}
+
+
+/** Restore all the pages in a given window. This function restores
+ * all the window specific attributes, then calls a helper function
+ * to restore all the pages that are contained in the window.
+ *
+ * @param window The GncMainWindow whose pages should be restored.
+ *
+ * @param data A data structure containing state about the
+ * window/page restoration process. */
+static void
+gnc_main_window_restore_window (GncMainWindow *window, GncMainWindowSaveData *data)
+{
+ GncMainWindowPrivate *priv;
+ GtkAction *action;
+ gint *pos, *geom, *order;
+ gsize length;
+ gboolean max, visible, desired_visibility;
+ gchar *window_group;
+ gint page_start, page_count, i;
+ GError *error = NULL;
+
+ /* Setup */
+ ENTER("window %p, data %p (key file %p, window %d)",
+ window, data, data->key_file, data->window_num);
+ window_group = g_strdup_printf(WINDOW_STRING, data->window_num + 1);
+
+ /* Deal with the uncommon case that the state file defines a window
+ * but no pages. An example to get in such a situation can be found
+ * here: https://bugzilla.gnome.org/show_bug.cgi?id=436479#c3
+ * If this happens on the first window, we will open an account hierarchy
+ * to avoid confusing the user by presenting a completely empty window.
+ * If it happens on a later window, we'll just skip restoring that window.
+ */
+ if (!g_key_file_has_group (data->key_file, window_group) ||
+ !g_key_file_has_key (data->key_file, window_group, WINDOW_PAGECOUNT, &error))
+ {
+ if (window)
+ {
+ gnc_main_window_restore_default_state (window);
+ PINFO ("saved state had an empty first main window\n"
+ "an account hierarchy page was added automatically to avoid confusion");
+ }
+ else
+ PINFO ("saved state had an empty main window, skipping restore");
+
+ goto cleanup;
+ }
+
+
+ /* Get this window's notebook info */
+ page_count = g_key_file_get_integer(data->key_file,
+ window_group, WINDOW_PAGECOUNT, &error);
+ if (error)
+ {
+ g_warning("error reading group %s key %s: %s",
+ window_group, WINDOW_PAGECOUNT, error->message);
+ goto cleanup;
+ }
+ if (page_count == 0)
+ {
+ /* Should never happen, but has during alpha testing. Having this
+ * check doesn't hurt anything. */
+ goto cleanup;
+ }
+ page_start = g_key_file_get_integer(data->key_file,
+ window_group, WINDOW_FIRSTPAGE, &error);
+ if (error)
+ {
+ g_warning("error reading group %s key %s: %s",
+ window_group, WINDOW_FIRSTPAGE, error->message);
+ goto cleanup;
+ }
+
+ /* Build a window if we don't already have one */
+ if (window == NULL)
+ {
+ DEBUG("Window %d doesn't exist. Creating new window.", data->window_num);
+ DEBUG("active_windows %p.", active_windows);
+ if (active_windows)
+ DEBUG("first window %p.", active_windows->data);
+ window = gnc_main_window_new();
+ gtk_widget_show(GTK_WIDGET(window));
+ }
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+
+ /* Get the window coordinates, etc. */
+ geom = g_key_file_get_integer_list(data->key_file, window_group,
+ WINDOW_GEOMETRY, &length, &error);
+ if (error)
+ {
+ g_warning("error reading group %s key %s: %s",
+ window_group, WINDOW_GEOMETRY, error->message);
+ g_error_free(error);
+ error = NULL;
+ }
+ else if (length != 2)
+ {
+ g_warning("invalid number of values for group %s key %s",
+ window_group, WINDOW_GEOMETRY);
+ }
+ else
+ {
+ gtk_window_resize(GTK_WINDOW(window), geom[0], geom[1]);
+ DEBUG("window (%p) size %dx%d", window, geom[0], geom[1]);
+ }
+ /* keep the geometry for a test whether the windows position
+ is offscreen */
+
+ pos = g_key_file_get_integer_list(data->key_file, window_group,
+ WINDOW_POSITION, &length, &error);
+ if (error)
+ {
+ g_warning("error reading group %s key %s: %s",
+ window_group, WINDOW_POSITION, error->message);
+ g_error_free(error);
+ error = NULL;
+ }
+ else if (length != 2)
+ {
+ g_warning("invalid number of values for group %s key %s",
+ window_group, WINDOW_POSITION);
+ }
+// This does not do any thing ?
+// else if ((pos[0] + (geom ? geom[0] : 0) < 0) ||
+// (pos[0] > gdk_screen_width()) ||
+// (pos[1] + (geom ? geom[1] : 0) < 0) ||
+// (pos[1] > gdk_screen_height()))
+// {
+// g_debug("position %dx%d, size%dx%d is offscreen; will not move",
+// pos[0], pos[1], geom[0], geom[1]);
+// }
+ else
+ {
+ gtk_window_move(GTK_WINDOW(window), pos[0], pos[1]);
+ DEBUG("window (%p) position %dx%d", window, pos[0], pos[1]);
+ }
+ if (geom)
+ {
+ g_free(geom);
+ }
+ if (pos)
+ {
+ g_free(pos);
+ }
+
+ max = g_key_file_get_boolean(data->key_file, window_group,
+ WINDOW_MAXIMIZED, &error);
+ if (error)
+ {
+ g_warning("error reading group %s key %s: %s",
+ window_group, WINDOW_MAXIMIZED, error->message);
+ g_error_free(error);
+ error = NULL;
+ }
+ else if (max)
+ {
+ gtk_window_maximize(GTK_WINDOW(window));
+ }
+
+ /* Common view menu items */
+ action = gnc_main_window_find_action(window, "ViewToolbarAction");
+ visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+ desired_visibility = g_key_file_get_boolean(data->key_file, window_group,
+ TOOLBAR_VISIBLE, &error);
+ if (error)
+ {
+ g_warning("error reading group %s key %s: %s",
+ window_group, TOOLBAR_VISIBLE, error->message);
+ g_error_free(error);
+ error = NULL;
+ }
+ else if (visible != desired_visibility)
+ {
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), desired_visibility);
+ }
+
+ action = gnc_main_window_find_action(window, "ViewSummaryAction");
+ visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+ desired_visibility = g_key_file_get_boolean(data->key_file, window_group,
+ SUMMARYBAR_VISIBLE, &error);
+ if (error)
+ {
+ g_warning("error reading group %s key %s: %s",
+ window_group, TOOLBAR_VISIBLE, error->message);
+ g_error_free(error);
+ error = NULL;
+ }
+ else if (visible != desired_visibility)
+ {
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), desired_visibility);
+ }
+
+ action = gnc_main_window_find_action(window, "ViewStatusbarAction");
+ visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+ desired_visibility = g_key_file_get_boolean(data->key_file, window_group,
+ STATUSBAR_VISIBLE, &error);
+ if (error)
+ {
+ g_warning("error reading group %s key %s: %s",
+ window_group, TOOLBAR_VISIBLE, error->message);
+ g_error_free(error);
+ error = NULL;
+ }
+ else if (visible != desired_visibility)
+ {
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), desired_visibility);
+ }
+
+ /* Now populate the window with pages. */
+ for (i = 0; i < page_count; i++)
+ {
+ data->page_offset = page_start;
+ data->page_num = i;
+ gnc_main_window_restore_page(window, data);
+
+ /* give the page a chance to display */
+ while (gtk_events_pending ())
+ gtk_main_iteration ();
+ }
+
+ /* Restore page ordering within the notebook. Use +1 notation so the
+ * numbers in the page order match the page sections, at least for
+ * the one window case. */
+ order = g_key_file_get_integer_list(data->key_file, window_group,
+ WINDOW_PAGEORDER, &length, &error);
+ if (error)
+ {
+ g_warning("error reading group %s key %s: %s",
+ window_group, WINDOW_PAGEORDER, error->message);
+ g_error_free(error);
+ error = NULL;
+ }
+ else if (length != page_count)
+ {
+ g_warning("%s key %s length %" G_GSIZE_FORMAT " differs from window page count %d",
+ window_group, WINDOW_PAGEORDER, length, page_count);
+ }
+ else
+ {
+ /* Dump any list that might exist */
+ g_list_free(priv->usage_order);
+ priv->usage_order = NULL;
+ /* Now rebuild the list from the key file. */
+ for (i = 0; i < length; i++)
+ {
+ gpointer page = g_list_nth_data(priv->installed_pages, order[i] - 1);
+ if (page)
+ {
+ priv->usage_order = g_list_append(priv->usage_order, page);
+ }
+ }
+ gtk_notebook_set_current_page (GTK_NOTEBOOK(priv->notebook),
+ order[0] - 1);
+ }
+ if (order)
+ {
+ g_free(order);
+ }
+
+ LEAVE("window %p", window);
+cleanup:
+ if (error)
+ g_error_free(error);
+ g_free(window_group);
+}
+
+void
+gnc_main_window_restore_all_windows(const GKeyFile *keyfile)
+{
+ gint i, window_count;
+ GError *error = NULL;
+ GncMainWindowSaveData data;
+ GncMainWindow *window;
+
+ /* We use the same struct for reading and for writing, so we cast
+ away the const. */
+ data.key_file = (GKeyFile *) keyfile;
+ window_count = g_key_file_get_integer(data.key_file, STATE_FILE_TOP,
+ WINDOW_COUNT, &error);
+ if (error)
+ {
+ g_warning("error reading group %s key %s: %s",
+ STATE_FILE_TOP, WINDOW_COUNT, error->message);
+ g_error_free(error);
+ LEAVE("can't read count");
+ return;
+ }
+
+ /* Restore all state information on the open windows. Window
+ numbers in state file are 1-based. GList indices are 0-based. */
+ gnc_set_busy_cursor (NULL, TRUE);
+ for (i = 0; i < window_count; i++)
+ {
+ data.window_num = i;
+ window = g_list_nth_data(active_windows, i);
+ gnc_main_window_restore_window(window, &data);
+ }
+ gnc_unset_busy_cursor (NULL);
+
+ statusbar_notification_lastmodified();
+}
+
+void
+gnc_main_window_restore_default_state(GncMainWindow *window)
+{
+ GtkAction *action;
+
+ /* The default state should be to have an Account Tree page open
+ * in the window. */
+ DEBUG("no saved state file");
+ if (!window)
+ window = g_list_nth_data(active_windows, 0);
+ action = gnc_main_window_find_action(window, "ViewAccountTreeAction");
+ gtk_action_activate(action);
+}
+
+/** Save the state of a single page to a disk. This function handles
+ * all the common tasks such as saving the page type and name, and
+ * anything else that might be common to all pages. It then calls a
+ * page specific function to save the actual page.
+ *
+ * @param page The GncPluginPage whose state should be saved.
+ *
+ * @param data A data structure containing state about the
+ * window/page saving process. */
+static void
+gnc_main_window_save_page (GncPluginPage *page, GncMainWindowSaveData *data)
+{
+ gchar *page_group;
+ const gchar *plugin_name, *page_name;
+
+ ENTER("page %p, data %p (key file %p, window %d, page %d)",
+ page, data, data->key_file, data->window_num, data->page_num);
+ plugin_name = gnc_plugin_page_get_plugin_name(page);
+ page_name = gnc_plugin_page_get_page_name(page);
+ if (!plugin_name || !page_name)
+ {
+ LEAVE("not saving invalid page");
+ return;
+ }
+ page_group = g_strdup_printf(PAGE_STRING, data->page_num++);
+ g_key_file_set_string(data->key_file, page_group, PAGE_TYPE, plugin_name);
+ g_key_file_set_string(data->key_file, page_group, PAGE_NAME, page_name);
+
+ gnc_plugin_page_save_page(page, data->key_file, page_group);
+ g_free(page_group);
+ LEAVE(" ");
+}
+
+
+/** Saves all the pages in a single window to a disk. This function
+ * saves all the window specific attributes, then calls a helper
+ * function to save all the pages that are contained in the window.
+ *
+ * @param window The GncMainWindow whose pages should be saved.
+ *
+ * @param data A data structure containing state about the
+ * window/page saving process. */
+static void
+gnc_main_window_save_window (GncMainWindow *window, GncMainWindowSaveData *data)
+{
+ GncMainWindowPrivate *priv;
+ GtkAction *action;
+ gint i, num_pages, coords[4], *order;
+ gboolean maximized, visible;
+ gchar *window_group;
+
+ /* Setup */
+ ENTER("window %p, data %p (key file %p, window %d)",
+ window, data, data->key_file, data->window_num);
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+
+ /* Check for bogus window structures. */
+ num_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook));
+ if (0 == num_pages)
+ {
+ LEAVE("empty window %p", window);
+ return;
+ }
+
+ /* Save this window's notebook info */
+ window_group = g_strdup_printf(WINDOW_STRING, data->window_num++);
+ g_key_file_set_integer(data->key_file, window_group,
+ WINDOW_PAGECOUNT, num_pages);
+ g_key_file_set_integer(data->key_file, window_group,
+ WINDOW_FIRSTPAGE, data->page_num);
+
+ /* Save page ordering within the notebook. Use +1 notation so the
+ * numbers in the page order match the page sections, at least for
+ * the one window case. */
+ order = g_malloc(sizeof(gint) * num_pages);
+ for (i = 0; i < num_pages; i++)
+ {
+ gpointer page = g_list_nth_data(priv->usage_order, i);
+ order[i] = g_list_index(priv->installed_pages, page) + 1;
+ }
+ g_key_file_set_integer_list(data->key_file, window_group,
+ WINDOW_PAGEORDER, order, num_pages);
+ g_free(order);
+
+ /* Save the window coordinates, etc. */
+ gtk_window_get_position(GTK_WINDOW(window), &coords[0], &coords[1]);
+ gtk_window_get_size(GTK_WINDOW(window), &coords[2], &coords[3]);
+ maximized = (gdk_window_get_state(gtk_widget_get_window ((GTK_WIDGET(window))))
+ & GDK_WINDOW_STATE_MAXIMIZED) != 0;
+ g_key_file_set_integer_list(data->key_file, window_group,
+ WINDOW_POSITION, &coords[0], 2);
+ g_key_file_set_integer_list(data->key_file, window_group,
+ WINDOW_GEOMETRY, &coords[2], 2);
+ g_key_file_set_boolean(data->key_file, window_group,
+ WINDOW_MAXIMIZED, maximized);
+ DEBUG("window (%p) position %dx%d, size %dx%d, %s", window, coords[0], coords[1],
+ coords[2], coords[3],
+ maximized ? "maximized" : "not maximized");
+
+ /* Common view menu items */
+ action = gnc_main_window_find_action(window, "ViewToolbarAction");
+ visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+ g_key_file_set_boolean(data->key_file, window_group,
+ TOOLBAR_VISIBLE, visible);
+ action = gnc_main_window_find_action(window, "ViewSummaryAction");
+ visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+ g_key_file_set_boolean(data->key_file, window_group,
+ SUMMARYBAR_VISIBLE, visible);
+ action = gnc_main_window_find_action(window, "ViewStatusbarAction");
+ visible = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+ g_key_file_set_boolean(data->key_file, window_group,
+ STATUSBAR_VISIBLE, visible);
+
+ /* Save individual pages in this window */
+ g_list_foreach(priv->installed_pages, (GFunc)gnc_main_window_save_page, data);
+
+ g_free(window_group);
+ LEAVE("window %p", window);
+}
+
+void
+gnc_main_window_save_all_windows(GKeyFile *keyfile)
+{
+ GncMainWindowSaveData data;
+
+ /* Set up the iterator data structures */
+ data.key_file = keyfile;
+ data.window_num = 1;
+ data.page_num = 1;
+
+ g_key_file_set_integer(data.key_file,
+ STATE_FILE_TOP, WINDOW_COUNT,
+ g_list_length(active_windows));
+ /* Dump all state information on the open windows */
+ g_list_foreach(active_windows, (GFunc)gnc_main_window_save_window, &data);
+}
+
+
+gboolean
+gnc_main_window_finish_pending (GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+ GList *item;
+
+ g_return_val_if_fail(GNC_IS_MAIN_WINDOW(window), TRUE);
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ for (item = priv->installed_pages; item; item = g_list_next(item))
+ {
+ if (!gnc_plugin_page_finish_pending(item->data))
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+
+gboolean
+gnc_main_window_all_finish_pending (void)
+{
+ const GList *windows, *item;
+
+ windows = gnc_gobject_tracking_get_list(GNC_MAIN_WINDOW_NAME);
+ for (item = windows; item; item = g_list_next(item))
+ {
+ if (!gnc_main_window_finish_pending(item->data))
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+
+/** See if the page already exists. For each open window, look
+ * through the list of pages installed in that window and see if the
+ * specified page is there.
+ *
+ * @internal
+ *
+ * @param page The page to search for.
+ *
+ * @return TRUE if the page is present in the window, FALSE otherwise.
+ */
+static gboolean
+gnc_main_window_page_exists (GncPluginPage *page)
+{
+ GncMainWindow *window;
+ GncMainWindowPrivate *priv;
+ GList *walker;
+
+ for (walker = active_windows; walker; walker = g_list_next(walker))
+ {
+ window = walker->data;
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ if (g_list_find(priv->installed_pages, page))
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static gboolean auto_save_countdown (GtkWidget *dialog)
+{
+ GtkWidget *label;
+ gchar *timeoutstr = NULL;
+
+ /* Stop count down if user closed the dialog since the last time we were called */
+ if (!GTK_IS_DIALOG (dialog))
+ return FALSE; /* remove timer */
+
+ /* Stop count down if count down text can't be updated */
+ label = GTK_WIDGET (g_object_get_data (G_OBJECT (dialog), "count-down-label"));
+ if (!GTK_IS_LABEL (label))
+ return FALSE; /* remove timer */
+
+ secs_to_save--;
+ DEBUG ("Counting down: %d seconds", secs_to_save);
+
+ timeoutstr = g_strdup_printf (MSG_AUTO_SAVE, secs_to_save);
+ gtk_label_set_text (GTK_LABEL (label), timeoutstr);
+ g_free (timeoutstr);
+
+ /* Count down reached 0. Save and close dialog */
+ if (!secs_to_save)
+ {
+ gtk_dialog_response (GTK_DIALOG(dialog), GTK_RESPONSE_APPLY);
+ return FALSE; /* remove timer */
+ }
+
+ /* Run another cycle */
+ return TRUE;
+}
+
+
+/** This function prompts the user to save the file with a dialog that
+ * follows the HIG guidelines.
+ *
+ * @internal
+ *
+ * @returns This function returns TRUE if the user clicked the Cancel
+ * button. It returns FALSE if the closing of the window should
+ * continue.
+ */
+static gboolean
+gnc_main_window_prompt_for_save (GtkWidget *window)
+{
+ QofSession *session;
+ QofBook *book;
+ GtkWidget *dialog, *msg_area, *label;
+ gint response;
+ const gchar *filename, *tmp;
+ const gchar *title = _("Save changes to file %s before closing?");
+ /* This should be the same message as in gnc-file.c */
+ const gchar *message_hours =
+ _("If you don't save, changes from the past %d hours and %d minutes will be discarded.");
+ const gchar *message_days =
+ _("If you don't save, changes from the past %d days and %d hours will be discarded.");
+ time64 oldest_change;
+ gint minutes, hours, days;
+
+ session = gnc_get_current_session();
+ book = qof_session_get_book(session);
+ filename = qof_session_get_url(session);
+ if (!strlen (filename))
+ filename = _("<unknown>");
+ if ((tmp = strrchr(filename, '/')) != NULL)
+ filename = tmp + 1;
+
+ /* Remove any pending auto-save timeouts */
+ gnc_autosave_remove_timer(book);
+
+ dialog = gtk_message_dialog_new(GTK_WINDOW(window),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_NONE,
+ title,
+ filename);
+ oldest_change = qof_book_get_session_dirty_time(book);
+ minutes = (gnc_time (NULL) - oldest_change) / 60 + 1;
+ hours = minutes / 60;
+ minutes = minutes % 60;
+ days = hours / 24;
+ hours = hours % 24;
+ if (days > 0)
+ {
+ gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+ message_days, days, hours);
+ }
+ else if (hours > 0)
+ {
+ gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+ message_hours, hours, minutes);
+ }
+ else
+ {
+ gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
+ ngettext("If you don't save, changes from the past %d minute will be discarded.",
+ "If you don't save, changes from the past %d minutes will be discarded.",
+ minutes), minutes);
+ }
+ gtk_dialog_add_buttons(GTK_DIALOG(dialog),
+ _("Close _Without Saving"), GTK_RESPONSE_CLOSE,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_Save"), GTK_RESPONSE_APPLY,
+ NULL);
+ gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_APPLY);
+
+ /* If requested by the user, add a timeout to the question to save automatically
+ * if the user doesn't answer after a chosen number of seconds.
+ */
+ if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SAVE_CLOSE_EXPIRES))
+ {
+ gchar *timeoutstr = NULL;
+
+ secs_to_save = gnc_prefs_get_int (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SAVE_CLOSE_WAIT_TIME);
+ timeoutstr = g_strdup_printf (MSG_AUTO_SAVE, secs_to_save);
+ label = GTK_WIDGET(gtk_label_new (timeoutstr));
+ g_free (timeoutstr);
+ gtk_widget_show (label);
+
+ msg_area = gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG(dialog));
+ gtk_box_pack_end (GTK_BOX(msg_area), label, TRUE, TRUE, 0);
+ g_object_set (G_OBJECT (label), "xalign", 0.0, NULL);
+
+ g_object_set_data (G_OBJECT (dialog), "count-down-label", label);
+ g_timeout_add_seconds (1, (GSourceFunc)auto_save_countdown, dialog);
+ }
+
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy(dialog);
+
+ switch (response)
+ {
+ case GTK_RESPONSE_APPLY:
+ gnc_file_save();
+ return FALSE;
+
+ case GTK_RESPONSE_CLOSE:
+ qof_book_mark_session_saved(book);
+ return FALSE;
+
+ default:
+ return TRUE;
+ }
+}
+
+
+static void
+gnc_main_window_add_plugin (gpointer plugin,
+ gpointer window)
+{
+ g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
+ g_return_if_fail (GNC_IS_PLUGIN (plugin));
+
+ ENTER(" ");
+ gnc_plugin_add_to_window (GNC_PLUGIN (plugin),
+ GNC_MAIN_WINDOW (window),
+ window_type);
+ LEAVE(" ");
+}
+
+static void
+gnc_main_window_remove_plugin (gpointer plugin,
+ gpointer window)
+{
+ g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
+ g_return_if_fail (GNC_IS_PLUGIN (plugin));
+
+ ENTER(" ");
+ gnc_plugin_remove_from_window (GNC_PLUGIN (plugin),
+ GNC_MAIN_WINDOW (window),
+ window_type);
+ LEAVE(" ");
+}
+
+
+static gboolean
+gnc_main_window_timed_quit (gpointer dummy)
+{
+ if (gnc_file_save_in_progress())
+ return TRUE;
+
+ gnc_shutdown (0);
+ return FALSE;
+}
+
+static gboolean
+gnc_main_window_quit(GncMainWindow *window)
+{
+ QofSession *session;
+ gboolean needs_save, do_shutdown;
+
+ session = gnc_get_current_session();
+ needs_save = qof_book_session_not_saved(qof_session_get_book(session)) &&
+ !gnc_file_save_in_progress();
+ do_shutdown = !needs_save ||
+ (needs_save && !gnc_main_window_prompt_for_save(GTK_WIDGET(window)));
+
+ if (do_shutdown)
+ {
+ g_timeout_add(250, gnc_main_window_timed_quit, NULL);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static gboolean
+gnc_main_window_delete_event (GtkWidget *window,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ static gboolean already_dead = FALSE;
+
+ if (already_dead)
+ return TRUE;
+
+ if (!gnc_main_window_finish_pending(GNC_MAIN_WINDOW(window)))
+ {
+ /* Don't close the window. */
+ return TRUE;
+ }
+
+ if (g_list_length(active_windows) > 1)
+ return FALSE;
+
+ already_dead = gnc_main_window_quit(GNC_MAIN_WINDOW(window));
+ return TRUE;
+}
+
+
+/** This function handles any event notifications from the engine.
+ * The only event it currently cares about is the deletion of a book.
+ * When a book is deleted, it runs through all installed pages
+ * looking for pages that reference the just (about to be?) deleted
+ * book. It closes any page it finds so there are no dangling
+ * references to the book.
+ *
+ * @internal
+ *
+ * @param entity The guid the item being added, deleted, etc.
+ *
+ * @param type The type of the item being added, deleted, etc. This
+ * function only cares about a type of GNC_ID_BOOK.
+ *
+ * @param event_type The type of the event. This function only cares
+ * about an event type of QOF_EVENT_DESTROY.
+ *
+ * @param user_data A pointer to the window data structure.
+ */
+static void
+gnc_main_window_event_handler (QofInstance *entity, QofEventId event_type,
+ gpointer user_data, gpointer event_data)
+{
+ GncMainWindow *window;
+ GncMainWindowPrivate *priv;
+ GncPluginPage *page;
+ GList *item, *next;
+
+ /* hard failures */
+ g_return_if_fail(GNC_IS_MAIN_WINDOW(user_data));
+
+ /* soft failures */
+ if (!QOF_CHECK_TYPE(entity, QOF_ID_BOOK))
+ return;
+ if (event_type != QOF_EVENT_DESTROY)
+ return;
+
+ ENTER("entity %p, event %d, window %p, event data %p",
+ entity, event_type, user_data, event_data);
+ window = GNC_MAIN_WINDOW(user_data);
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+
+ /* This is not a typical list iteration. We're removing while
+ * we iterate, so we have to cache the 'next' pointer before
+ * executing any code in the loop. */
+ for (item = priv->installed_pages; item; item = next)
+ {
+ next = g_list_next(item);
+ page = GNC_PLUGIN_PAGE(item->data);
+ if (gnc_plugin_page_has_book (page, (QofBook *)entity))
+ gnc_main_window_close_page (page);
+ }
+ LEAVE(" ");
+}
+
+
+/** Generate a title for this window based upon the Gnome Human
+ * Interface Guidelines, v2.0. This title will be used as both the
+ * window title and the title of the "Window" menu item associated
+ * with the window.
+ *
+ * As a side-effect, the save action is set sensitive iff the book
+ * is dirty, and the immutable_page_actions are set sensitive iff the page is
+ * mutable.
+ *
+ * @param window The window whose title should be generated.
+ *
+ * @return The title for the window. It is the callers
+ * responsibility to free this string.
+ *
+ * @internal
+ */
+static gchar *
+gnc_main_window_generate_title (GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+ GncPluginPage *page;
+ QofBook *book;
+ gboolean immutable;
+ gchar *filename = NULL;
+ const gchar *book_id = NULL;
+ const gchar *dirty = "";
+ const gchar *readonly_text = NULL;
+ gchar *readonly;
+ gchar *title;
+
+ if (gnc_current_session_exist())
+ {
+ book_id = qof_session_get_url (gnc_get_current_session ());
+ book = gnc_get_current_book();
+ if (qof_book_session_not_saved (book))
+ dirty = "*";
+ if (qof_book_is_readonly(book))
+ {
+ /* Translators: This string is shown in the window title if this
+ document is, well, read-only. */
+ readonly_text = _("(read-only)");
+ }
+ }
+ readonly = (readonly_text != NULL)
+ ? g_strdup_printf(" %s", readonly_text)
+ : g_strdup("");
+
+ if (!book_id || g_strcmp0 (book_id, "") == 0)
+ filename = g_strdup(_("Unsaved Book"));
+ else
+ {
+ if ( gnc_uri_is_file_uri ( book_id ) )
+ {
+ /* The filename is a true file.
+ * The Gnome HIG 2.0 recommends only the file name (no path) be used. (p15) */
+ gchar *path = gnc_uri_get_path ( book_id );
+ filename = g_path_get_basename ( path );
+ g_free ( path );
+ }
+ else
+ {
+ /* The filename is composed of database connection parameters.
+ * For this we will show access_method://username@database[:port] */
+ filename = gnc_uri_normalize_uri (book_id, FALSE);
+ }
+ }
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ page = priv->current_page;
+ if (page)
+ {
+ /* The Gnome HIG 2.0 recommends the application name not be used. (p16)
+ * but several developers prefer to use it anyway. */
+ title = g_strdup_printf("%s%s%s - %s - GnuCash", dirty, filename, readonly,
+ gnc_plugin_page_get_page_name(page));
+ }
+ else
+ {
+ title = g_strdup_printf("%s%s%s - GnuCash", dirty, filename, readonly);
+ }
+ /* Update the menus based upon whether this is an "immutable" page. */
+ immutable = page &&
+ g_object_get_data (G_OBJECT (page), PLUGIN_PAGE_IMMUTABLE);
+ gnc_plugin_update_actions(priv->action_group,
+ immutable_page_actions,
+ "sensitive", !immutable);
+ /* Trigger sensitivity updtates of other actions such as Save/Revert */
+ g_signal_emit_by_name (window, "page_changed", page);
+ g_free( filename );
+ g_free(readonly);
+
+ return title;
+}
+
+
+/** Update the title bar on the specified window. This routine uses
+ * the gnc_main_window_generate_title() function to create the title.
+ * It is called whenever the user switched pages in a window, as the
+ * title includes the name of the current page.
+ *
+ * @param window The window whose title should be updated.
+ *
+ * @internal
+ */
+static void
+gnc_main_window_update_title (GncMainWindow *window)
+{
+ gchar *title;
+
+ title = gnc_main_window_generate_title(window);
+ gtk_window_set_title(GTK_WINDOW(window), title);
+ g_free(title);
+}
+
+static void
+gnc_main_window_update_all_titles (void)
+{
+ g_list_foreach(active_windows,
+ (GFunc)gnc_main_window_update_title,
+ NULL);
+}
+
+static void
+gnc_main_window_book_dirty_cb (QofBook *book,
+ gboolean dirty,
+ gpointer user_data)
+{
+ gnc_main_window_update_all_titles();
+
+ /* Auto-save feature */
+ gnc_autosave_dirty_handler(book, dirty);
+}
+
+static void
+gnc_main_window_attach_to_book (QofSession *session)
+{
+ QofBook *book;
+
+ g_return_if_fail(session);
+
+ book = qof_session_get_book(session);
+ qof_book_set_dirty_cb(book, gnc_main_window_book_dirty_cb, NULL);
+ gnc_main_window_update_all_titles();
+#ifndef MAC_INTEGRATION
+ gnc_main_window_update_all_menu_items();
+#endif
+}
+
+static guint gnc_statusbar_notification_messageid = 0;
+//#define STATUSBAR_NOTIFICATION_AUTOREMOVAL
+#ifdef STATUSBAR_NOTIFICATION_AUTOREMOVAL
+/* Removes the statusbar notification again that has been pushed to the
+ * statusbar by generate_statusbar_lastmodified_message. */
+static gboolean statusbar_notification_off(gpointer user_data_unused)
+{
+ GtkWidget *widget = gnc_ui_get_toplevel();
+ //g_warning("statusbar_notification_off\n");
+ if (gnc_statusbar_notification_messageid == 0)
+ return FALSE;
+
+ if (widget && GNC_IS_MAIN_WINDOW(widget))
+ {
+ GncMainWindow *mainwindow = GNC_MAIN_WINDOW(widget);
+ GtkWidget *statusbar = gnc_main_window_get_statusbar(GNC_WINDOW(mainwindow));
+ gtk_statusbar_remove(GTK_STATUSBAR(statusbar), 0, gnc_statusbar_notification_messageid);
+ gnc_statusbar_notification_messageid = 0;
+ }
+ else
+ {
+ g_warning("oops, no GncMainWindow obtained\n");
+ }
+ return FALSE; // should not be called again
+}
+#endif // STATUSBAR_NOTIFICATION_AUTOREMOVAL
+
+/* Creates a statusbar message stating the last modification time of the opened
+ * data file. */
+static gchar *generate_statusbar_lastmodified_message()
+{
+ gchar *message = NULL;
+ const gchar *book_id = NULL;
+
+ if (gnc_current_session_exist())
+ {
+ book_id = qof_session_get_url (gnc_get_current_session ());
+ }
+
+ if (!strlen (book_id))
+ return NULL;
+ else
+ {
+ if ( gnc_uri_is_file_uri ( book_id ) )
+ {
+#ifdef HAVE_SYS_STAT_H
+ /* The filename is a true file. */
+ gchar *filepath = gnc_uri_get_path ( book_id );
+ gchar *filename = g_path_get_basename ( filepath );
+ {
+ // Access the mtime information through stat(2)
+ struct stat statbuf;
+ int r = stat(filepath, &statbuf);
+ if (r == 0)
+ {
+ /* Translators: This is the date and time that is shown in
+ the status bar after opening a file: The date and time of
+ last modification. The string is a format string using
+ boost::date_time's format flags, see the boost docs for an
+ explanation of the modifiers. */
+ char *time_string =
+ gnc_print_time64(statbuf.st_mtime,
+ _("Last modified on %a, %b %d, %Y at %I:%M %p"));
+ //g_warning("got time %ld, str=%s\n", mtime, time_string);
+ /* Translators: This message appears in the status bar after opening the file. */
+ message = g_strdup_printf(_("File %s opened. %s"),
+ filename, time_string);
+ free(time_string);
+ }
+ else
+ {
+ g_warning("Unable to read mtime for file %s\n", filepath);
+ // message is still NULL
+ }
+ }
+ g_free(filename);
+ g_free(filepath);
+#else
+ return NULL;
+#endif
+ }
+ // If the URI is not a file but a database, we can maybe also show
+ // something useful, but I have no idea how to obtain this information.
+ }
+ return message;
+}
+
+static void
+statusbar_notification_lastmodified()
+{
+ // First look up the first GncMainWindow to set the statusbar there
+ GList *iter;
+ GtkWidget *widget = NULL;
+ for (iter = active_windows; iter && !(widget && GNC_IS_MAIN_WINDOW(widget));
+ iter = g_list_next(iter))
+ {
+ widget = iter->data;
+ }
+ if (widget && GNC_IS_MAIN_WINDOW(widget))
+ {
+ // Ok, we found a mainwindow where we can set a statusbar message
+ GncMainWindow *mainwindow = GNC_MAIN_WINDOW(widget);
+ GtkWidget *statusbar = gnc_main_window_get_statusbar(GNC_WINDOW(mainwindow));
+
+ gchar *msg = generate_statusbar_lastmodified_message();
+ if (msg)
+ {
+ gnc_statusbar_notification_messageid = gtk_statusbar_push(GTK_STATUSBAR(statusbar), 0, msg);
+ }
+ g_free(msg);
+
+#ifdef STATUSBAR_NOTIFICATION_AUTOREMOVAL
+ // Also register a timeout callback to remove that statusbar
+ // notification again after 10 seconds
+ g_timeout_add(10 * 1000, statusbar_notification_off, NULL); // maybe not needed anyway?
+#endif
+ }
+ else
+ {
+ g_warning("uh oh, no GNC_IS_MAIN_WINDOW\n");
+ }
+}
+
+
+/** This data structure is used to describe the requested state of a
+ * GtkRadioAction, and us used to pass data among several
+ * functions. */
+struct menu_update
+{
+ /** The name of the GtkRadioAction to be updated. */
+ gchar *action_name;
+
+ /** The new label for this GtkRadioAction. */
+ gchar *label;
+
+ /** Whether or not the GtkRadioAction should be visible. */
+ gboolean visible;
+};
+
+#ifndef MAC_INTEGRATION
+/** Update the label on the specified GtkRadioAction in the specified
+ * window. This action is displayed as a menu item in the "Windows"
+ * menu. This function will end up being called whenever the front
+ * page is changed in any window, or whenever a window is added or
+ * deleted.
+ *
+ * @param window The window whose menu item should be updated.
+ *
+ * @param data A data structure containing the name of the
+ * GtkRadioAction, and describing the new state for this action.
+ *
+ * @internal
+ */
+static void
+gnc_main_window_update_one_menu_action (GncMainWindow *window,
+ struct menu_update *data)
+{
+ GncMainWindowPrivate *priv;
+ GtkAction* action;
+
+ ENTER("window %p, action %s, label %s, visible %d", window,
+ data->action_name, data->label, data->visible);
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ action = gtk_action_group_get_action(priv->action_group, data->action_name);
+ if (action)
+ g_object_set(G_OBJECT(action),
+ "label", data->label,
+ "visible", data->visible,
+ (char *)NULL);
+ LEAVE(" ");
+}
+
+/** Update the window selection GtkRadioAction for a specific window.
+ * This is fairly simple since the windows are listed in the same
+ * order that they appear in the active_windows list, so the index
+ * from the window list is used to generate the name of the action.
+ * If the code is ever changed to allow more than ten open windows in
+ * the menu, then the actions in the menu will need to be dynamically
+ * generated/deleted and it gets harder.
+ *
+ * @param window The window whose menu item should be updated.
+ *
+ * @internal
+ */
+static void
+gnc_main_window_update_radio_button (GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+ GtkAction *action, *first_action;
+ GSList *action_list;
+ gchar *action_name;
+ gint index;
+
+ ENTER("window %p", window);
+
+ /* Show the new entry in all windows. */
+ index = g_list_index(active_windows, window);
+ if (index >= n_radio_entries)
+ {
+ LEAVE("window %d, only %d actions", index, n_radio_entries);
+ return;
+ }
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ action_name = g_strdup_printf("Window%dAction", index);
+ action = gtk_action_group_get_action(priv->action_group, action_name);
+
+ /* Block the signal so as not to affect window ordering (top to
+ * bottom) on the screen */
+ action_list = gtk_radio_action_get_group(GTK_RADIO_ACTION(action));
+ if (action_list)
+ {
+ first_action = g_slist_last(action_list)->data;
+ g_signal_handlers_block_by_func(G_OBJECT(first_action),
+ G_CALLBACK(gnc_main_window_cmd_window_raise),
+ window);
+ DEBUG("blocked signal on %p, set %p active, window %p", first_action,
+ action, window);
+ gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(action), TRUE);
+ g_signal_handlers_unblock_by_func(G_OBJECT(first_action),
+ G_CALLBACK(gnc_main_window_cmd_window_raise),
+ window);
+ }
+ g_free(action_name);
+ LEAVE(" ");
+}
+
+/** In every window that the user has open, update the "Window" menu
+ * item that points to the specified window. This keeps the "Window"
+ * menu items consistent across all open windows. (These items
+ * cannot be shared because of the way the GtkUIManager code works.)
+ *
+ * This function is called whenever the user switches pages in a
+ * window, or whenever a window is added or deleted.
+ *
+ * @param window The window whose menu item should be updated in all
+ * open windows.
+ *
+ * @internal
+ */
+static void
+gnc_main_window_update_menu_item (GncMainWindow *window)
+{
+ struct menu_update data;
+ gchar **strings, *title, *expanded;
+ gint index;
+
+ ENTER("window %p", window);
+ index = g_list_index(active_windows, window);
+ if (index > n_radio_entries)
+ {
+ LEAVE("skip window %d (only %d entries)", index, n_radio_entries);
+ return;
+ }
+
+ /* Figure out the label name. Add the accelerator if possible. */
+ title = gnc_main_window_generate_title(window);
+ strings = g_strsplit(title, "_", 0);
+ g_free(title);
+ expanded = g_strjoinv("__", strings);
+ if (index < 10)
+ {
+ data.label = g_strdup_printf("_%d %s", (index + 1) % 10, expanded);
+ g_free(expanded);
+ }
+ else
+ {
+ data.label = expanded;
+ }
+ g_strfreev(strings);
+
+ data.visible = TRUE;
+ data.action_name = g_strdup_printf("Window%dAction", index);
+ g_list_foreach(active_windows,
+ (GFunc)gnc_main_window_update_one_menu_action,
+ &data);
+ g_free(data.action_name);
+ g_free(data.label);
+
+ LEAVE(" ");
+}
+#endif /* !MAC_INTEGRATION */
+
+/** Update all menu entries for all window menu items in all windows.
+ * This function is called whenever a window is added or deleted.
+ * The worst case scenario is where the user has deleted the first
+ * window, so every single visible item needs to be updated.
+ *
+ * @internal
+ */
+
+#ifndef MAC_INTEGRATION
+static void
+gnc_main_window_update_all_menu_items (void)
+{
+ struct menu_update data;
+ gchar *label;
+ gint i;
+
+ ENTER("");
+ /* First update the entries for all existing windows */
+ g_list_foreach(active_windows,
+ (GFunc)gnc_main_window_update_menu_item,
+ NULL);
+ g_list_foreach(active_windows,
+ (GFunc)gnc_main_window_update_radio_button,
+ NULL);
+
+ /* Now hide any entries that aren't being used. */
+ data.visible = FALSE;
+ for (i = g_list_length(active_windows); i < n_radio_entries; i++)
+ {
+ data.action_name = g_strdup_printf("Window%dAction", i);
+ label = g_strdup_printf("Window _%d", (i - 1) % 10);
+ data.label = gettext(label);
+
+ g_list_foreach(active_windows,
+ (GFunc)gnc_main_window_update_one_menu_action,
+ &data);
+
+ g_free(data.action_name);
+ g_free(label);
+ }
+ LEAVE(" ");
+}
+#endif /* !MAC_INTEGRATION */
+
+/** Show/hide the close box on the tab of a notebook page. This
+ * function first checks to see if the specified page has a close
+ * box, and if so, sets its visibility to the requested state.
+ *
+ * @internal
+ *
+ * @param page The GncPluginPage whose notebook tab should be updated.
+ *
+ * @param new_value A pointer to the boolean that indicates whether
+ * or not the close button should be visible.
+ */
+static void
+gnc_main_window_update_tab_close_one_page (GncPluginPage *page,
+ gpointer user_data)
+{
+ gboolean *new_value = user_data;
+ GtkWidget * close_button;
+
+ ENTER("page %p, visible %d", page, *new_value);
+ close_button = g_object_get_data(G_OBJECT (page), PLUGIN_PAGE_CLOSE_BUTTON);
+ if (!close_button)
+ {
+ LEAVE("no close button");
+ return;
+ }
+
+ if (*new_value)
+ gtk_widget_show (close_button);
+ else
+ gtk_widget_hide (close_button);
+ LEAVE(" ");
+}
+
+
+/** Show/hide the close box on all pages in all windows. This function
+ * calls gnc_main_window_update_tab_close() for each plugin page in the
+ * application.
+ *
+ * @internal
+ *
+ * @param prefs Unused.
+ *
+ * @param pref Unused.
+ *
+ * @param user_data Unused.
+ */
+static void
+gnc_main_window_update_tab_close (gpointer prefs, gchar *pref, gpointer user_data)
+{
+ gboolean new_value;
+
+ ENTER(" ");
+ new_value = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_SHOW_CLOSE_BUTTON);
+ gnc_main_window_foreach_page(
+ gnc_main_window_update_tab_close_one_page,
+ &new_value);
+ LEAVE(" ");
+}
+
+
+/** Show/hide the account color on the tab of a notebook page.
+ *
+ * @internal
+ *
+ * @param page The GncPluginPage whose notebook tab should be updated.
+ *
+ * @param user_data GncMainWindow.
+ */
+static void
+gnc_main_window_update_tab_color_one_page (GncPluginPage *page,
+ gpointer user_data)
+{
+ const gchar *color_string;
+
+ ENTER("page %p", page);
+ color_string = gnc_plugin_page_get_page_color(page);
+ main_window_update_page_color (page, color_string);
+ LEAVE(" ");
+}
+
+
+/** Show/hide the account color on tabs.
+ *
+ * @internal
+ *
+ * @param prefs Unused.
+ *
+ * @param pref Name of the preference that was changed.
+ *
+ * @param user_data GncMainWindow.
+ */
+static void
+gnc_main_window_update_tab_color (gpointer gsettings, gchar *pref, gpointer user_data)
+{
+ GncMainWindowPrivate *priv;
+ GncMainWindow *window;
+
+ ENTER(" ");
+ g_return_if_fail(GNC_IS_MAIN_WINDOW(user_data));
+ window = user_data;
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ if (g_strcmp0 (GNC_PREF_TAB_COLOR, pref) == 0)
+ priv->show_color_tabs = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_COLOR);
+ gnc_main_window_foreach_page (gnc_main_window_update_tab_color_one_page, window);
+ LEAVE(" ");
+}
+
+
+/** Update the width of the label in the tab of a notebook page. This
+ * function adjusts both the width and the ellipsize mode so that the tab
+ * label looks correct. The special check for a zero value handles the
+ * case where a user hasn't set a tab width and the preference default isn't
+ * detected.
+ *
+ * @internal
+ *
+ * @param page The GncPluginPage whose notebook tab should be updated.
+ *
+ * @param new_value The new width of the label in the tab.
+ */
+static void
+gnc_main_window_update_tab_width_one_page (GncPluginPage *page,
+ gpointer user_data)
+{
+ gint *new_value = user_data;
+ GtkWidget *label;
+ const gchar *lab_text;
+
+ ENTER("page %p, visible %d", page, *new_value);
+ label = g_object_get_data(G_OBJECT (page), PLUGIN_PAGE_TAB_LABEL);
+ if (!label)
+ {
+ LEAVE("no label");
+ return;
+ }
+
+ lab_text = gtk_label_get_text (GTK_LABEL(label));
+
+ if (*new_value != 0)
+ {
+ if (g_utf8_strlen (lab_text, -1) < *new_value)
+ gtk_label_set_width_chars (GTK_LABEL(label), strlen (lab_text));
+ else
+ gtk_label_set_width_chars (GTK_LABEL(label), *new_value);
+
+ gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
+ }
+ else
+ {
+ gtk_label_set_width_chars (GTK_LABEL(label), 15);
+ gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_NONE);
+ }
+ LEAVE(" ");
+}
+
+
+/** Update the tab label width in all pages in all windows. This function
+ * calls gnc_main_window_update_tab_width() for each plugin page in the
+ * application.
+ *
+ * @internal
+ *
+ * @param prefs Unused.
+ *
+ * @param pref Unused.
+ *
+ * @param user_data Unused.
+ */
+static void
+gnc_main_window_update_tab_width (gpointer prefs, gchar *pref, gpointer user_data)
+{
+ gint new_value;
+
+ ENTER(" ");
+ new_value = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_WIDTH);
+ gnc_main_window_foreach_page(
+ gnc_main_window_update_tab_width_one_page,
+ &new_value);
+ LEAVE(" ");
+}
+
+
+/************************************************************
+ * Tab Label Implementation *
+ ************************************************************/
+static gboolean
+main_window_find_tab_items (GncMainWindow *window,
+ GncPluginPage *page,
+ GtkWidget **label_p,
+ GtkWidget **entry_p)
+{
+ GncMainWindowPrivate *priv;
+ GtkWidget *tab_hbox, *widget, *tab_widget;
+ GList *children, *tmp;
+
+ ENTER("window %p, page %p, label_p %p, entry_p %p",
+ window, page, label_p, entry_p);
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ *label_p = *entry_p = NULL;
+
+ if (!page->notebook_page)
+ {
+ LEAVE("invalid notebook_page");
+ return FALSE;
+ }
+
+ tab_widget = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
+ page->notebook_page);
+ if (GTK_IS_EVENT_BOX (tab_widget))
+ tab_hbox = gtk_bin_get_child(GTK_BIN(tab_widget));
+ else if (GTK_IS_BOX (tab_widget))
+ tab_hbox = tab_widget;
+ else
+ {
+ PWARN ("Unknown widget for tab label %p", tab_widget);
+ return FALSE;
+ }
+
+ children = gtk_container_get_children(GTK_CONTAINER(tab_hbox));
+ for (tmp = children; tmp; tmp = g_list_next(tmp))
+ {
+ widget = tmp->data;
+ if (GTK_IS_LABEL(widget))
+ {
+ *label_p = widget;
+ }
+ else if (GTK_IS_ENTRY(widget))
+ {
+ *entry_p = widget;
+ }
+ }
+ g_list_free(children);
+
+ LEAVE("label %p, entry %p", *label_p, *entry_p);
+ return (*label_p && *entry_p);
+}
+
+static gboolean
+main_window_find_tab_widget (GncMainWindow *window,
+ GncPluginPage *page,
+ GtkWidget **widget_p)
+{
+ GncMainWindowPrivate *priv;
+
+ ENTER("window %p, page %p, widget %p",
+ window, page, widget_p);
+ *widget_p = NULL;
+
+ if (!page->notebook_page)
+ {
+ LEAVE("invalid notebook_page");
+ return FALSE;
+ }
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ *widget_p = gtk_notebook_get_tab_label(GTK_NOTEBOOK(priv->notebook),
+ page->notebook_page);
+
+ LEAVE("widget %p", *widget_p);
+ return TRUE;
+}
+
+void
+main_window_update_page_name (GncPluginPage *page,
+ const gchar *name_in)
+{
+ GncMainWindow *window;
+ GncMainWindowPrivate *priv;
+ GtkWidget *label, *entry;
+ gchar *name, *old_page_name, *old_page_long_name;
+ gint lab_width;
+
+ ENTER(" ");
+
+ if ((name_in == NULL) || (*name_in == '\0'))
+ {
+ LEAVE("no string");
+ return;
+ }
+ name = g_strstrip(g_strdup(name_in));
+
+ /* Optimization, if the name hasn't changed, don't update X. */
+ if (*name == '\0' || 0 == strcmp(name, gnc_plugin_page_get_page_name(page)))
+ {
+ g_free(name);
+ LEAVE("empty string or name unchanged");
+ return;
+ }
+
+ old_page_name = g_strdup( gnc_plugin_page_get_page_name(page));
+ old_page_long_name = g_strdup( gnc_plugin_page_get_page_long_name(page));
+
+ /* Update the plugin */
+ gnc_plugin_page_set_page_name(page, name);
+
+ /* Update the notebook tab */
+ window = GNC_MAIN_WINDOW(page->window);
+ if (!window)
+ {
+ g_free(old_page_name);
+ g_free(old_page_long_name);
+ g_free(name);
+ LEAVE("no window widget available");
+ return;
+ }
+
+ if (main_window_find_tab_items(window, page, &label, &entry))
+ gtk_label_set_text(GTK_LABEL(label), name);
+
+ /* Adjust the label width for new text */
+ lab_width = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_WIDTH);
+ gnc_main_window_update_tab_width_one_page (page, &lab_width);
+
+ /* Update Tooltip on notebook Tab */
+ if (old_page_long_name && old_page_name
+ && g_strrstr(old_page_long_name, old_page_name) != NULL)
+ {
+ gchar *new_page_long_name;
+ gint string_position;
+ GtkWidget *tab_widget;
+
+ string_position = strlen(old_page_long_name) - strlen(old_page_name);
+ new_page_long_name = g_strconcat(g_strndup(old_page_long_name, string_position), name, NULL);
+
+ gnc_plugin_page_set_page_long_name(page, new_page_long_name);
+
+ if (main_window_find_tab_widget(window, page, &tab_widget))
+ gtk_widget_set_tooltip_text(tab_widget, new_page_long_name);
+
+ g_free(new_page_long_name);
+ }
+
+ /* Update the notebook menu */
+ if (page->notebook_page)
+ {
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ label = gtk_notebook_get_menu_label (GTK_NOTEBOOK(priv->notebook),
+ page->notebook_page);
+ gtk_label_set_text(GTK_LABEL(label), name);
+ }
+
+ /* Force an update of the window title */
+ gnc_main_window_update_title(window);
+ g_free(old_page_long_name);
+ g_free(old_page_name);
+ g_free(name);
+ LEAVE("done");
+}
+
+
+void
+main_window_update_page_color (GncPluginPage *page,
+ const gchar *color_in)
+{
+ GncMainWindow *window;
+ GncMainWindowPrivate *priv;
+ GtkWidget *tab_widget;
+ GdkRGBA tab_color;
+ gchar *color_string = NULL;
+ gboolean want_color = FALSE;
+
+ ENTER(" ");
+ if (color_in)
+ color_string = g_strstrip(g_strdup(color_in));
+
+ if (color_string && *color_string != '\0')
+ want_color = TRUE;
+
+ /* Update the plugin */
+ window = GNC_MAIN_WINDOW(page->window);
+ if (want_color)
+ gnc_plugin_page_set_page_color(page, color_string);
+ else
+ gnc_plugin_page_set_page_color(page, NULL);
+
+ /* Update the notebook tab */
+ main_window_find_tab_widget (window, page, &tab_widget);
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+
+ if (want_color && gdk_rgba_parse(&tab_color, color_string) && priv->show_color_tabs)
+ {
+ GtkCssProvider *provider = gtk_css_provider_new();
+ GtkStyleContext *stylectxt;
+ gchar *col_str, *widget_css;
+
+ if (!GTK_IS_EVENT_BOX (tab_widget))
+ {
+ GtkWidget *event_box = gtk_event_box_new ();
+ g_object_ref (tab_widget);
+ gtk_notebook_set_tab_label (GTK_NOTEBOOK(priv->notebook),
+ page->notebook_page, event_box);
+ gtk_container_add (GTK_CONTAINER(event_box), tab_widget);
+ g_object_unref (tab_widget);
+ tab_widget = event_box;
+ }
+
+ stylectxt = gtk_widget_get_style_context (GTK_WIDGET (tab_widget));
+ col_str = gdk_rgba_to_string (&tab_color);
+ widget_css = g_strconcat ("*{\n background-color:", col_str, ";\n}\n", NULL);
+
+ gtk_css_provider_load_from_data (provider, widget_css, -1, NULL);
+ gtk_style_context_add_provider (stylectxt, GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ g_object_unref (provider);
+ g_free (col_str);
+ g_free (widget_css);
+ }
+ else
+ {
+ if (GTK_IS_EVENT_BOX (tab_widget))
+ {
+ GtkWidget *tab_hbox = gtk_bin_get_child(GTK_BIN(tab_widget));
+ g_object_ref (tab_hbox);
+ gtk_container_remove (GTK_CONTAINER(tab_widget), tab_hbox);
+ gtk_notebook_set_tab_label (GTK_NOTEBOOK(priv->notebook),
+ page->notebook_page, tab_hbox);
+ g_object_unref (tab_hbox);
+ }
+ }
+ g_free(color_string);
+ LEAVE("done");
+}
+
+
+static void
+gnc_main_window_tab_entry_activate (GtkWidget *entry,
+ GncPluginPage *page)
+{
+ GtkWidget *label, *entry2;
+
+ g_return_if_fail(GTK_IS_ENTRY(entry));
+ g_return_if_fail(GNC_IS_PLUGIN_PAGE(page));
+
+ ENTER("");
+ if (!main_window_find_tab_items(GNC_MAIN_WINDOW(page->window),
+ page, &label, &entry2))
+ {
+ LEAVE("can't find required widgets");
+ return;
+ }
+
+ main_window_update_page_name(page, gtk_entry_get_text(GTK_ENTRY(entry)));
+
+ gtk_widget_hide(entry);
+ gtk_widget_show(label);
+ LEAVE("");
+}
+
+
+static gboolean
+gnc_main_window_tab_entry_editing_done (GtkWidget *entry,
+ GncPluginPage *page)
+{
+ ENTER("");
+ gnc_main_window_tab_entry_activate(entry, page);
+ LEAVE("");
+ return FALSE;
+}
+
+static gboolean
+gnc_main_window_tab_entry_focus_out_event (GtkWidget *entry,
+ GdkEvent *event,
+ GncPluginPage *page)
+{
+ ENTER("");
+ gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry));
+ LEAVE("");
+ return FALSE;
+}
+
+static gboolean
+gnc_main_window_tab_entry_key_press_event (GtkWidget *entry,
+ GdkEventKey *event,
+ GncPluginPage *page)
+{
+ if (event->keyval == GDK_KEY_Escape)
+ {
+ GtkWidget *label, *entry2;
+
+ g_return_val_if_fail(GTK_IS_ENTRY(entry), FALSE);
+ g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);
+
+ ENTER("");
+ if (!main_window_find_tab_items(GNC_MAIN_WINDOW(page->window),
+ page, &label, &entry2))
+ {
+ LEAVE("can't find required widgets");
+ return FALSE;
+ }
+
+ gtk_entry_set_text(GTK_ENTRY(entry), gtk_label_get_text(GTK_LABEL(label)));
+ gtk_widget_hide(entry);
+ gtk_widget_show(label);
+ LEAVE("");
+ }
+ return FALSE;
+}
+
+/************************************************************
+ * Widget Implementation *
+ ************************************************************/
+
+/* Get the type of a gnc main window.
+ */
+GType
+gnc_main_window_get_type (void)
+{
+ static GType gnc_main_window_type = 0;
+
+ if (gnc_main_window_type == 0)
+ {
+ static const GTypeInfo our_info =
+ {
+ sizeof (GncMainWindowClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gnc_main_window_class_init,
+ NULL,
+ NULL,
+ sizeof (GncMainWindow),
+ 0,
+ (GInstanceInitFunc) gnc_main_window_init
+ };
+
+ static const GInterfaceInfo plugin_info =
+ {
+ (GInterfaceInitFunc) gnc_window_main_window_init,
+ NULL,
+ NULL
+ };
+
+ gnc_main_window_type = g_type_register_static (GTK_TYPE_WINDOW,
+ GNC_MAIN_WINDOW_NAME,
+ &our_info, 0);
+ g_type_add_interface_static (gnc_main_window_type,
+ GNC_TYPE_WINDOW,
+ &plugin_info);
+ }
+
+ return gnc_main_window_type;
+}
+
+
+/** Initialize the class for a new gnucash main window. This will set
+ * up any function pointers that override functions in the parent
+ * class, and also initialize the signals that this class of widget
+ * can generate.
+ *
+ * @param klass The new class structure created by the object system.
+ */
+static void
+gnc_main_window_class_init (GncMainWindowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *gtkwidget_class = GTK_WIDGET_CLASS(klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ window_type = g_quark_from_static_string ("gnc-main-window");
+
+ object_class->finalize = gnc_main_window_finalize;
+
+ /* GtkWidget signals */
+ gtkwidget_class->destroy = gnc_main_window_destroy;
+
+ g_type_class_add_private(klass, sizeof(GncMainWindowPrivate));
+
+ /**
+ * GncMainWindow::page_added:
+ * @param window: the #GncMainWindow
+ * @param page: the #GncPluginPage
+ *
+ * The "page_added" signal is emitted when a new page is added
+ * to the notebook of a GncMainWindow. This can be used to
+ * attach a signal from the page so that menu actions can be
+ * adjusted based upon events that occur within the page
+ * (e.g. an account is selected.)
+ */
+ main_window_signals[PAGE_ADDED] =
+ g_signal_new ("page_added",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GncMainWindowClass, page_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ G_TYPE_OBJECT);
+
+ /**
+ * GncMainWindow::page_changed:
+ * @param window: the #GncMainWindow
+ * @param page: the #GncPluginPage
+ *
+ * The "page_changed" signal is emitted when a new page is
+ * selected in the notebook of a GncMainWindow. This can be
+ * used to to adjust menu actions based upon which page is
+ * currently displayed in a window.
+ */
+ main_window_signals[PAGE_CHANGED] =
+ g_signal_new ("page_changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GncMainWindowClass, page_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ G_TYPE_OBJECT);
+
+ gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
+ GNC_PREF_SHOW_CLOSE_BUTTON,
+ gnc_main_window_update_tab_close,
+ NULL);
+ gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
+ GNC_PREF_TAB_WIDTH,
+ gnc_main_window_update_tab_width,
+ NULL);
+
+ gnc_hook_add_dangler(HOOK_BOOK_SAVED,
+ (GFunc)gnc_main_window_update_all_titles, NULL);
+ gnc_hook_add_dangler(HOOK_BOOK_OPENED,
+ (GFunc)gnc_main_window_attach_to_book, NULL);
+
+}
+
+
+/** Initialize a new instance of a gnucash main window. This function
+ * initializes the object private storage space. It also adds the
+ * new object to a list (for memory tracking purposes).
+ *
+ * @param window The new object instance created by the object system.
+ *
+ * @param klass A pointer to the class data structure for this
+ * object. */
+static void
+gnc_main_window_init (GncMainWindow *window,
+ GncMainWindowClass *klass)
+{
+ GncMainWindowPrivate *priv;
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ priv->merged_actions_table =
+ g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ // Set the style context for this widget so it can be easily manipulated with css
+ gnc_widget_set_style_context (GTK_WIDGET(window), "GncMainWindow");
+
+ priv->event_handler_id =
+ qof_event_register_handler(gnc_main_window_event_handler, window);
+
+ /* Get the show_color_tabs value preference */
+ priv->show_color_tabs = gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_COLOR);
+ priv->about_dialog = NULL;
+
+ gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
+ GNC_PREF_TAB_COLOR,
+ gnc_main_window_update_tab_color,
+ window);
+
+ gnc_main_window_setup_window (window);
+ gnc_gobject_tracking_remember(G_OBJECT(window),
+ G_OBJECT_CLASS(klass));
+}
+
+
+/** Finalize the GncMainWindow object. This function is called from
+ * the G_Object level to complete the destruction of the object. It
+ * should release any memory not previously released by the destroy
+ * function (i.e. the private data structure), then chain up to the
+ * parent's destroy function.
+ *
+ * @param object The object being destroyed.
+ *
+ * @internal
+ */
+static void
+gnc_main_window_finalize (GObject *object)
+{
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GNC_IS_MAIN_WINDOW (object));
+
+ if (active_windows == NULL)
+ {
+ /* Oops. User killed last window and we didn't catch it. */
+ g_idle_add((GSourceFunc)gnc_shutdown, 0);
+ }
+
+ gnc_gobject_tracking_forget(object);
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gnc_main_window_destroy (GtkWidget *widget)
+{
+ GncMainWindow *window;
+ GncMainWindowPrivate *priv;
+ GncPluginManager *manager;
+ GList *plugins;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GNC_IS_MAIN_WINDOW (widget));
+
+ window = GNC_MAIN_WINDOW (widget);
+
+ active_windows = g_list_remove (active_windows, window);
+
+ /* Do these things once */
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ if (priv->merged_actions_table)
+ {
+
+ /* Close any pages in this window */
+ while (priv->current_page)
+ gnc_main_window_close_page(priv->current_page);
+
+ if (gnc_window_get_progressbar_window() == GNC_WINDOW(window))
+ gnc_window_set_progressbar_window(NULL);
+#ifndef MAC_INTEGRATION
+ /* Update the "Windows" menu in all other windows */
+ gnc_main_window_update_all_menu_items();
+#endif
+ gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
+ GNC_PREF_TAB_COLOR,
+ gnc_main_window_update_tab_color,
+ window);
+
+ qof_event_unregister_handler(priv->event_handler_id);
+ priv->event_handler_id = 0;
+
+ g_hash_table_destroy (priv->merged_actions_table);
+ priv->merged_actions_table = NULL;
+
+ /* GncPluginManager stuff */
+ manager = gnc_plugin_manager_get ();
+ plugins = gnc_plugin_manager_get_plugins (manager);
+ g_list_foreach (plugins, gnc_main_window_remove_plugin, window);
+ g_list_free (plugins);
+ }
+ if (priv->about_dialog)
+ g_object_unref (priv->about_dialog);
+ GTK_WIDGET_CLASS (parent_class)->destroy (widget);
+}
+
+
+/* Create a new gnc main window plugin.
+ */
+GncMainWindow *
+gnc_main_window_new (void)
+{
+ GncMainWindow *window;
+ GtkWidget *old_window;
+
+ window = g_object_new (GNC_TYPE_MAIN_WINDOW, NULL);
+ gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
+
+ old_window = gnc_ui_get_toplevel();
+ if (old_window)
+ {
+ gint width, height;
+ gtk_window_get_size (GTK_WINDOW (old_window), &width, &height);
+ gtk_window_resize (GTK_WINDOW (window), width, height);
+ if ((gdk_window_get_state((gtk_widget_get_window (GTK_WIDGET(old_window))))
+ & GDK_WINDOW_STATE_MAXIMIZED) != 0)
+ {
+ gtk_window_maximize (GTK_WINDOW (window));
+ }
+ }
+ active_windows = g_list_append (active_windows, window);
+ gnc_main_window_update_title(window);
+#ifdef MAC_INTEGRATION
+ gnc_quartz_set_menu(window);
+#else
+ gnc_main_window_update_all_menu_items();
+#endif
+ gnc_engine_add_commit_error_callback( gnc_main_window_engine_commit_error_callback, window );
+
+ return window;
+}
+
+/************************************************************
+ * Utility Functions *
+ ************************************************************/
+
+static void
+gnc_main_window_engine_commit_error_callback( gpointer data,
+ QofBackendError errcode )
+{
+ GncMainWindow* window = GNC_MAIN_WINDOW(data);
+ GtkWidget* dialog;
+ const gchar *reason = _("Unable to save to database.");
+ if ( errcode == ERR_BACKEND_READONLY )
+ reason = _("Unable to save to database: Book is marked read-only.");
+ dialog = gtk_message_dialog_new( GTK_WINDOW(window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "%s",
+ reason );
+ gtk_dialog_run(GTK_DIALOG (dialog));
+ gtk_widget_destroy(dialog);
+
+}
+
+/** Connect a GncPluginPage to the window. This function will insert
+ * the page in to the window's notebook and its list of active pages.
+ * It will also emit the "inserted" signal on the page, and the
+ * "add_page" signal on the window.
+ *
+ * @param window The window where the new page should be added.
+ *
+ * @param page The GncPluginPage that should be added to the window.
+ * The visible widget for this plugin must have already been created.
+ *
+ * @param tab_hbox The widget that should be added into the notebook
+ * tab for this page. Generally this is a GtkLabel, but could also
+ * be a GtkHBox containing an icon and a label.
+ *
+ * @param menu_label The widget that should be added into the
+ * notebook popup menu for this page. This should be a GtkLabel.
+ */
+static void
+gnc_main_window_connect (GncMainWindow *window,
+ GncPluginPage *page,
+ GtkWidget *tab_hbox,
+ GtkWidget *menu_label)
+{
+ GncMainWindowPrivate *priv;
+ GtkNotebook *notebook;
+
+ page->window = GTK_WIDGET(window);
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ notebook = GTK_NOTEBOOK (priv->notebook);
+ priv->installed_pages = g_list_append (priv->installed_pages, page);
+ priv->usage_order = g_list_prepend (priv->usage_order, page);
+ gtk_notebook_append_page_menu (notebook, page->notebook_page,
+ tab_hbox, menu_label);
+ gtk_notebook_set_tab_reorderable (notebook, page->notebook_page, TRUE);
+ gnc_plugin_page_inserted (page);
+ gtk_notebook_set_current_page (notebook, -1);
+ if (GNC_PLUGIN_PAGE_GET_CLASS(page)->window_changed)
+ (GNC_PLUGIN_PAGE_GET_CLASS(page)->window_changed)(page, GTK_WIDGET(window));
+ g_signal_emit (window, main_window_signals[PAGE_ADDED], 0, page);
+
+ g_signal_connect(G_OBJECT(page->notebook_page), "popup-menu",
+ G_CALLBACK(gnc_main_window_popup_menu_cb), page);
+ g_signal_connect_after(G_OBJECT(page->notebook_page), "button-press-event",
+ G_CALLBACK(gnc_main_window_button_press_cb), page);
+}
+
+
+/** Disconnect a GncPluginPage page from the window. If this page is
+ * currently foremost in the window's notebook, its user interface
+ * actions will be disconnected and the page's summarybar widget (if
+ * any) will be removed. The page is then removed from the window's
+ * notebook and its list of active pages.
+ *
+ * @param window The window the page should be removed from.
+ *
+ * @param page The GncPluginPage that should be removed from the
+ * window.
+ *
+ * @internal
+ */
+static void
+gnc_main_window_disconnect (GncMainWindow *window,
+ GncPluginPage *page)
+{
+ GncMainWindowPrivate *priv;
+ GtkNotebook *notebook;
+ GncPluginPage *new_page;
+ gint page_num;
+
+ /* Disconnect the callbacks */
+ g_signal_handlers_disconnect_by_func(G_OBJECT(page->notebook_page),
+ G_CALLBACK(gnc_main_window_popup_menu_cb), page);
+ g_signal_handlers_disconnect_by_func(G_OBJECT(page->notebook_page),
+ G_CALLBACK(gnc_main_window_button_press_cb), page);
+
+ /* Disconnect the page and summarybar from the window */
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ if (priv->current_page == page)
+ {
+ gnc_plugin_page_unmerge_actions (page, window->ui_merge);
+ gnc_plugin_page_unselected (page);
+ priv->current_page = NULL;
+ }
+
+ /* Remove it from the list of pages in the window */
+ priv->installed_pages = g_list_remove (priv->installed_pages, page);
+ priv->usage_order = g_list_remove (priv->usage_order, page);
+
+ /* Switch to the last recently used page */
+ notebook = GTK_NOTEBOOK (priv->notebook);
+ if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_NEXT_RECENT))
+ {
+ new_page = g_list_nth_data (priv->usage_order, 0);
+ if (new_page)
+ {
+ page_num = gtk_notebook_page_num(notebook, new_page->notebook_page);
+ gtk_notebook_set_current_page(notebook, page_num);
+ /* This may have caused WebKit to schedule a timer interrupt which it
+ sometimes forgets to cancel before deleting the object. See
+ <https://bugs.webkit.org/show_bug.cgi?id=119003>. Get around this
+ by flushing all events to get rid of the timer interrupt. */
+ while (gtk_events_pending())
+ gtk_main_iteration();
+ }
+ }
+
+ /* Remove the page from the notebook */
+ page_num = gtk_notebook_page_num(notebook, page->notebook_page);
+ gtk_notebook_remove_page (notebook, page_num);
+
+ if ( gtk_notebook_get_current_page(notebook) == -1)
+ {
+ /* Need to synthesize a page changed signal when the last
+ * page is removed. The notebook doesn't generate a signal
+ * for this, therefore the switch_page code in this file
+ * never gets called to generate this signal. */
+ gnc_main_window_switch_page(notebook, NULL, -1, window);
+ //g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, NULL);
+ }
+
+ gnc_plugin_page_removed (page);
+
+ gtk_ui_manager_ensure_update (window->ui_merge);
+ gnc_window_set_status (GNC_WINDOW(window), page, NULL);
+}
+
+
+/************************************************************
+ * *
+ ************************************************************/
+
+
+void
+gnc_main_window_display_page (GncPluginPage *page)
+{
+ GncMainWindow *window;
+ GncMainWindowPrivate *priv;
+ GtkNotebook *notebook;
+ gint page_num;
+
+ window = GNC_MAIN_WINDOW (page->window);
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ notebook = GTK_NOTEBOOK (priv->notebook);
+ page_num = gtk_notebook_page_num(notebook, page->notebook_page);
+ gtk_notebook_set_current_page (notebook, page_num);
+ gtk_window_present(GTK_WINDOW(window));
+}
+
+
+/* Display a data plugin page in a window. If the page already
+ * exists in any window, then that window will be brought to the
+ * front and the notebook switch to display the specified page. If
+ * the page is new then it will be added to the specified window. If
+ * the window is NULL, the new page will be added to the first
+ * window.
+ */
+void
+gnc_main_window_open_page (GncMainWindow *window,
+ GncPluginPage *page)
+{
+ GncMainWindowPrivate *priv;
+ GtkWidget *tab_hbox;
+ GtkWidget *label, *entry;
+ const gchar *icon, *text, *color_string, *lab_text;
+ GtkWidget *image;
+ GList *tmp;
+ gint width;
+
+ ENTER("window %p, page %p", window, page);
+ if (window)
+ g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
+ g_return_if_fail (GNC_IS_PLUGIN_PAGE (page));
+ g_return_if_fail (gnc_plugin_page_has_books(page));
+
+ if (gnc_main_window_page_exists(page))
+ {
+ gnc_main_window_display_page(page);
+ return;
+ }
+
+ /* Does the page want to be in a new window? */
+ if (gnc_plugin_page_get_use_new_window(page))
+ {
+ /* See if there's a blank window. If so, use that. */
+ for (tmp = active_windows; tmp; tmp = g_list_next(tmp))
+ {
+ window = GNC_MAIN_WINDOW(tmp->data);
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ if (priv->installed_pages == NULL)
+ {
+ break;
+ }
+ }
+ if (tmp == NULL)
+ window = gnc_main_window_new ();
+ gtk_widget_show(GTK_WIDGET(window));
+ }
+ else if ((window == NULL) && active_windows)
+ {
+ window = active_windows->data;
+ }
+
+ page->window = GTK_WIDGET(window);
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ page->notebook_page = gnc_plugin_page_create_widget (page);
+ g_object_set_data (G_OBJECT (page->notebook_page),
+ PLUGIN_PAGE_LABEL, page);
+
+ /*
+ * The page tab.
+ */
+ width = gnc_prefs_get_float(GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_WIDTH);
+ icon = GNC_PLUGIN_PAGE_GET_CLASS(page)->tab_icon;
+ lab_text = gnc_plugin_page_get_page_name(page);
+ label = gtk_label_new (lab_text);
+ g_object_set_data (G_OBJECT (page), PLUGIN_PAGE_TAB_LABEL, label);
+
+ if (width != 0)
+ {
+ if (g_utf8_strlen (lab_text, -1) < width)
+ gtk_label_set_width_chars (GTK_LABEL(label), strlen (lab_text));
+ else
+ gtk_label_set_width_chars (GTK_LABEL(label), width);
+
+ gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_MIDDLE);
+ }
+ gtk_widget_show (label);
+
+ tab_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
+ gtk_box_set_homogeneous (GTK_BOX (tab_hbox), FALSE);
+ gtk_widget_show (tab_hbox);
+
+ if (icon != NULL)
+ {
+ image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU);
+ gtk_widget_show (image);
+ gtk_box_pack_start (GTK_BOX (tab_hbox), image, FALSE, FALSE, 0);
+#if GTK_CHECK_VERSION(3,12,0)
+ gtk_widget_set_margin_start (GTK_WIDGET(image), 5);
+#else
+ gtk_widget_set_margin_left (GTK_WIDGET(image), 5);
+#endif
+ gtk_box_pack_start (GTK_BOX (tab_hbox), label, TRUE, TRUE, 0);
+ }
+ else
+ gtk_box_pack_start (GTK_BOX (tab_hbox), label, TRUE, TRUE, 0);
+
+ text = gnc_plugin_page_get_page_long_name(page);
+ if (text)
+ {
+ gtk_widget_set_tooltip_text(tab_hbox, text);
+ }
+
+ entry = gtk_entry_new();
+ gtk_widget_hide (entry);
+ gtk_box_pack_start (GTK_BOX (tab_hbox), entry, TRUE, TRUE, 0);
+ g_signal_connect(G_OBJECT(entry), "activate",
+ G_CALLBACK(gnc_main_window_tab_entry_activate), page);
+ g_signal_connect(G_OBJECT(entry), "focus-out-event",
+ G_CALLBACK(gnc_main_window_tab_entry_focus_out_event),
+ page);
+ g_signal_connect(G_OBJECT(entry), "key-press-event",
+ G_CALLBACK(gnc_main_window_tab_entry_key_press_event),
+ page);
+ g_signal_connect(G_OBJECT(entry), "editing-done",
+ G_CALLBACK(gnc_main_window_tab_entry_editing_done),
+ page);
+
+ /* Add close button - Not for immutable pages */
+ if (!g_object_get_data (G_OBJECT (page), PLUGIN_PAGE_IMMUTABLE))
+ {
+ GtkWidget *close_image, *close_button;
+ GtkRequisition requisition;
+
+ close_button = gtk_button_new();
+ gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE);
+ close_image = gtk_image_new_from_icon_name ("window-close", GTK_ICON_SIZE_MENU);
+ gtk_widget_show(close_image);
+ gtk_widget_get_preferred_size (close_image, &requisition, NULL);
+ gtk_widget_set_size_request(close_button, requisition.width + 4,
+ requisition.height + 2);
+ gtk_container_add(GTK_CONTAINER(close_button), close_image);
+ if (gnc_prefs_get_bool(GNC_PREFS_GROUP_GENERAL, GNC_PREF_SHOW_CLOSE_BUTTON))
+ gtk_widget_show (close_button);
+ else
+ gtk_widget_hide (close_button);
+
+ g_signal_connect_swapped (G_OBJECT (close_button), "clicked",
+ G_CALLBACK(gnc_main_window_close_page), page);
+
+ gtk_box_pack_start (GTK_BOX (tab_hbox), close_button, FALSE, FALSE, 0);
+#if GTK_CHECK_VERSION(3,12,0)
+ gtk_widget_set_margin_end (GTK_WIDGET(close_button), 5);
+#else
+ gtk_widget_set_margin_right (GTK_WIDGET(close_button), 5);
+#endif
+ g_object_set_data (G_OBJECT (page), PLUGIN_PAGE_CLOSE_BUTTON, close_button);
+ }
+
+ /*
+ * The popup menu
+ */
+ label = gtk_label_new (gnc_plugin_page_get_page_name(page));
+
+ /*
+ * Now install it all in the window.
+ */
+ gnc_main_window_connect(window, page, tab_hbox, label);
+
+ color_string = gnc_plugin_page_get_page_color(page);
+ main_window_update_page_color (page, color_string);
+ LEAVE("");
+}
+
+
+/* Remove a data plugin page from a window and display the previous
+ * page. If the page removed was the last page in the window, and
+ * there is more than one window open, then the entire window will be
+ * destroyed.
+ */
+void
+gnc_main_window_close_page (GncPluginPage *page)
+{
+ GncMainWindow *window;
+ GncMainWindowPrivate *priv;
+
+ if (!page || !page->notebook_page)
+ return;
+
+ if (!gnc_plugin_page_finish_pending(page))
+ return;
+
+ if (!GNC_IS_MAIN_WINDOW (page->window))
+ return;
+
+ window = GNC_MAIN_WINDOW (page->window);
+ if (!window)
+ {
+ g_warning("Page is not in a window.");
+ return;
+ }
+
+ gnc_main_window_disconnect(window, page);
+ gnc_plugin_page_destroy_widget (page);
+ g_object_unref(page);
+
+ /* If this isn't the last window, go ahead and destroy the window. */
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ if (priv->installed_pages == NULL)
+ {
+ if (g_list_length(active_windows) > 1)
+ {
+ gtk_widget_destroy(GTK_WIDGET(window));
+ }
+ }
+}
+
+
+/* Retrieve a pointer to the page that is currently at the front of
+ * the specified window. Any plugin that needs to manipulate its
+ * menus based upon the currently selected menu page should connect
+ * to the "page_changed" signal on a window. The callback function
+ * from that signal can then call this function to obtain a pointer
+ * to the current page.
+ */
+GncPluginPage *
+gnc_main_window_get_current_page (GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ return priv->current_page;
+}
+
+
+/* Manually add a set of actions to the specified window. Plugins
+ * whose user interface is not hard coded (e.g. the menu-additions
+ * plugin) must create their actions at run time, then use this
+ * function to install them into the window.
+ */
+void
+gnc_main_window_manual_merge_actions (GncMainWindow *window,
+ const gchar *group_name,
+ GtkActionGroup *group,
+ guint merge_id)
+{
+ GncMainWindowPrivate *priv;
+ MergedActionEntry *entry;
+
+ g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
+ g_return_if_fail (group_name != NULL);
+ g_return_if_fail (GTK_IS_ACTION_GROUP(group));
+ g_return_if_fail (merge_id > 0);
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ entry = g_new0 (MergedActionEntry, 1);
+ entry->action_group = group;
+ entry->merge_id = merge_id;
+ gtk_ui_manager_ensure_update (window->ui_merge);
+ g_hash_table_insert (priv->merged_actions_table, g_strdup (group_name), entry);
+}
+
+
+/* Add a set of actions to the specified window. This function
+ * should not need to be called directly by plugin implementors.
+ * Correctly assigning values to the GncPluginClass fields during
+ * plugin initialization will cause this routine to be automatically
+ * called.
+ */
+void
+gnc_main_window_merge_actions (GncMainWindow *window,
+ const gchar *group_name,
+ GtkActionEntry *actions,
+ guint n_actions,
+ GtkToggleActionEntry *toggle_actions,
+ guint n_toggle_actions,
+ const gchar *filename,
+ gpointer user_data)
+{
+ GncMainWindowPrivate *priv;
+ GncMainWindowActionData *data;
+ MergedActionEntry *entry;
+ GError *error = NULL;
+ gchar *pathname;
+
+ g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
+ g_return_if_fail (group_name != NULL);
+ g_return_if_fail (actions != NULL);
+ g_return_if_fail (n_actions > 0);
+ g_return_if_fail (filename != NULL);
+
+ pathname = gnc_filepath_locate_ui_file (filename);
+ if (pathname == NULL)
+ return;
+
+ data = g_new0 (GncMainWindowActionData, 1);
+ data->window = window;
+ data->data = user_data;
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ entry = g_new0 (MergedActionEntry, 1);
+ entry->action_group = gtk_action_group_new (group_name);
+ gnc_gtk_action_group_set_translation_domain (entry->action_group, GETTEXT_PACKAGE);
+ gtk_action_group_add_actions (entry->action_group, actions, n_actions, data);
+ if (toggle_actions != NULL && n_toggle_actions > 0)
+ {
+ gtk_action_group_add_toggle_actions (entry->action_group,
+ toggle_actions, n_toggle_actions,
+ data);
+ }
+ gtk_ui_manager_insert_action_group (window->ui_merge, entry->action_group, 0);
+ entry->merge_id = gtk_ui_manager_add_ui_from_file (window->ui_merge, pathname, &error);
+ g_assert(entry->merge_id || error);
+ if (entry->merge_id)
+ {
+ gtk_ui_manager_ensure_update (window->ui_merge);
+ g_hash_table_insert (priv->merged_actions_table, g_strdup (group_name), entry);
+ }
+ else
+ {
+ g_critical("Failed to load ui file.\n Filename %s\n Error %s",
+ filename, error->message);
+ g_error_free(error);
+ g_free(entry);
+ }
+ g_free(pathname);
+}
+
+
+/* Remove a set of actions from the specified window. This function
+ * should not need to be called directly by plugin implementors. It
+ * will automatically be called when a plugin is removed from a
+ * window.
+ */
+void
+gnc_main_window_unmerge_actions (GncMainWindow *window,
+ const gchar *group_name)
+{
+ GncMainWindowPrivate *priv;
+ MergedActionEntry *entry;
+
+ g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
+ g_return_if_fail (group_name != NULL);
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ if (priv->merged_actions_table == NULL)
+ return;
+ entry = g_hash_table_lookup (priv->merged_actions_table, group_name);
+
+ if (entry == NULL)
+ return;
+
+ gtk_ui_manager_remove_action_group (window->ui_merge, entry->action_group);
+ gtk_ui_manager_remove_ui (window->ui_merge, entry->merge_id);
+ gtk_ui_manager_ensure_update (window->ui_merge);
+
+ g_hash_table_remove (priv->merged_actions_table, group_name);
+}
+
+
+/* Force a full update of the user interface for the specified
+ * window. This can be an expensive function, but is needed because
+ * the gtk ui manager doesn't always seem to update properly when
+ * actions are changed.
+ */
+void
+gnc_main_window_actions_updated (GncMainWindow *window)
+{
+ GtkActionGroup *force;
+
+ g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
+
+ /* Unfortunately gtk_ui_manager_ensure_update doesn't work
+ * here. Force a full update by adding and removing an empty
+ * action group.
+ */
+ force = gtk_action_group_new("force_update");
+ gtk_ui_manager_insert_action_group (window->ui_merge, force, 0);
+ gtk_ui_manager_ensure_update (window->ui_merge);
+ gtk_ui_manager_remove_action_group (window->ui_merge, force);
+ g_object_unref(force);
+}
+
+
+GtkAction *
+gnc_main_window_find_action (GncMainWindow *window, const gchar *name)
+{
+ GtkAction *action = NULL;
+ const GList *groups, *tmp;
+
+ groups = gtk_ui_manager_get_action_groups(window->ui_merge);
+ for (tmp = groups; tmp; tmp = g_list_next(tmp))
+ {
+ action = gtk_action_group_get_action(GTK_ACTION_GROUP(tmp->data), name);
+ if (action)
+ break;
+ }
+ return action;
+}
+
+
+/* Retrieve a specific set of user interface actions from a window.
+ * This function can be used to get an group of action to be
+ * manipulated when the front page of a window has changed.
+ */
+GtkActionGroup *
+gnc_main_window_get_action_group (GncMainWindow *window,
+ const gchar *group_name)
+{
+ GncMainWindowPrivate *priv;
+ MergedActionEntry *entry;
+
+ g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), NULL);
+ g_return_val_if_fail (group_name != NULL, NULL);
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ if (priv->merged_actions_table == NULL)
+ return NULL;
+ entry = g_hash_table_lookup (priv->merged_actions_table, group_name);
+
+ if (entry == NULL)
+ return NULL;
+
+ return entry->action_group;
+}
+
+static void
+gnc_main_window_update_tab_position (gpointer prefs, gchar *pref, gpointer user_data)
+{
+ GncMainWindow *window;
+ GtkPositionType position = GTK_POS_TOP;
+ GncMainWindowPrivate *priv;
+
+ window = GNC_MAIN_WINDOW(user_data);
+
+ ENTER ("window %p", window);
+ if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_BOTTOM))
+ position = GTK_POS_BOTTOM;
+ else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_LEFT))
+ position = GTK_POS_LEFT;
+ else if (gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL, GNC_PREF_TAB_POSITION_RIGHT))
+ position = GTK_POS_RIGHT;
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE (window);
+ gtk_notebook_set_tab_pos (GTK_NOTEBOOK (priv->notebook), position);
+
+ LEAVE ("");
+}
+
+/*
+ * Based on code from Epiphany (src/ephy-window.c)
+ */
+static void
+gnc_main_window_update_edit_actions_sensitivity (GncMainWindow *window, gboolean hide)
+{
+ GncMainWindowPrivate *priv;
+ GncPluginPage *page;
+ GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (window));
+ GtkAction *action;
+ gboolean can_copy = FALSE, can_cut = FALSE, can_paste = FALSE;
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ page = priv->current_page;
+ if (page && GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)
+ {
+ (GNC_PLUGIN_PAGE_GET_CLASS(page)->update_edit_menu_actions)(page, hide);
+ return;
+ }
+
+ if (GTK_IS_EDITABLE (widget))
+ {
+ gboolean has_selection;
+
+ has_selection = gtk_editable_get_selection_bounds
+ (GTK_EDITABLE (widget), NULL, NULL);
+
+ can_copy = has_selection;
+ can_cut = has_selection;
+ can_paste = TRUE;
+ }
+ else if (GTK_IS_TEXT_VIEW (widget))
+ {
+ gboolean has_selection;
+ GtkTextBuffer *text_buffer;
+
+ text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
+ has_selection = gtk_text_buffer_get_selection_bounds
+ (text_buffer, NULL, NULL);
+
+ can_copy = has_selection;
+ can_cut = has_selection;
+ can_paste = TRUE;
+ }
+ else
+ {
+#ifdef ORIGINAL_EPIPHANY_CODE
+ /* For now we assume all actions are possible */
+ can_copy = can_cut = can_paste = TRUE;
+#else
+ /* If its not a GtkEditable, we don't know what to do
+ * with it. */
+ can_copy = can_cut = can_paste = FALSE;
+#endif
+ }
+
+ action = gnc_main_window_find_action (window, "EditCopyAction");
+ gtk_action_set_sensitive (action, can_copy);
+ gtk_action_set_visible (action, !hide || can_copy);
+ action = gnc_main_window_find_action (window, "EditCutAction");
+ gtk_action_set_sensitive (action, can_cut);
+ gtk_action_set_visible (action, !hide || can_cut);
+ action = gnc_main_window_find_action (window, "EditPasteAction");
+ gtk_action_set_sensitive (action, can_paste);
+ gtk_action_set_visible (action, !hide || can_paste);
+}
+
+static void
+gnc_main_window_enable_edit_actions_sensitivity (GncMainWindow *window)
+{
+ GtkAction *action;
+
+ action = gnc_main_window_find_action (window, "EditCopyAction");
+ gtk_action_set_sensitive (action, TRUE);
+ gtk_action_set_visible (action, TRUE);
+ action = gnc_main_window_find_action (window, "EditCutAction");
+ gtk_action_set_sensitive (action, TRUE);
+ gtk_action_set_visible (action, TRUE);
+ action = gnc_main_window_find_action (window, "EditPasteAction");
+ gtk_action_set_sensitive (action, TRUE);
+ gtk_action_set_visible (action, TRUE);
+}
+
+static void
+gnc_main_window_edit_menu_show_cb (GtkWidget *menu,
+ GncMainWindow *window)
+{
+ gnc_main_window_update_edit_actions_sensitivity (window, FALSE);
+}
+
+static void
+gnc_main_window_edit_menu_hide_cb (GtkWidget *menu,
+ GncMainWindow *window)
+{
+ gnc_main_window_enable_edit_actions_sensitivity (window);
+}
+
+static void
+gnc_main_window_init_menu_updaters (GncMainWindow *window)
+{
+ GtkWidget *edit_menu_item, *edit_menu;
+
+ edit_menu_item = gtk_ui_manager_get_widget
+ (window->ui_merge, "/menubar/Edit");
+ edit_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (edit_menu_item));
+
+ g_signal_connect (edit_menu, "show",
+ G_CALLBACK (gnc_main_window_edit_menu_show_cb), window);
+ g_signal_connect (edit_menu, "hide",
+ G_CALLBACK (gnc_main_window_edit_menu_hide_cb), window);
+}
+
+/* CS: This callback functions will set the statusbar text to the
+ * "tooltip" property of the currently selected GtkAction.
+ *
+ * This code is directly copied from gtk+/test/testmerge.c.
+ * Thanks to (L)GPL! */
+typedef struct _ActionStatus ActionStatus;
+struct _ActionStatus
+{
+ GtkAction *action;
+ GtkWidget *statusbar;
+};
+
+static void
+action_status_destroy (gpointer data)
+{
+ ActionStatus *action_status = data;
+
+ g_object_unref (action_status->action);
+ g_object_unref (action_status->statusbar);
+
+ g_free (action_status);
+}
+
+static void
+set_tip (GtkWidget *widget)
+{
+ ActionStatus *data;
+ gchar *tooltip;
+
+ data = g_object_get_data (G_OBJECT (widget), "action-status");
+
+ if (data)
+ {
+ g_object_get (data->action, "tooltip", &tooltip, NULL);
+
+ gtk_statusbar_push (GTK_STATUSBAR (data->statusbar), 0,
+ tooltip ? tooltip : "");
+
+ g_free (tooltip);
+ }
+}
+
+static void
+unset_tip (GtkWidget *widget)
+{
+ ActionStatus *data;
+
+ data = g_object_get_data (G_OBJECT (widget), "action-status");
+
+ if (data)
+ gtk_statusbar_pop (GTK_STATUSBAR (data->statusbar), 0);
+}
+
+static void
+connect_proxy (GtkUIManager *merge,
+ GtkAction *action,
+ GtkWidget *proxy,
+ GtkWidget *statusbar)
+{
+ if (GTK_IS_MENU_ITEM (proxy))
+ {
+ ActionStatus *data;
+
+ data = g_object_get_data (G_OBJECT (proxy), "action-status");
+ if (data)
+ {
+ g_object_unref (data->action);
+ g_object_unref (data->statusbar);
+
+ data->action = g_object_ref (action);
+ data->statusbar = g_object_ref (statusbar);
+ }
+ else
+ {
+ data = g_new0 (ActionStatus, 1);
+
+ data->action = g_object_ref (action);
+ data->statusbar = g_object_ref (statusbar);
+
+ g_object_set_data_full (G_OBJECT (proxy), "action-status",
+ data, action_status_destroy);
+
+ g_signal_connect (proxy, "select", G_CALLBACK (set_tip), NULL);
+ g_signal_connect (proxy, "deselect", G_CALLBACK (unset_tip), NULL);
+ }
+ }
+}
+/* CS: end copied code from gtk+/test/testmerge.c */
+
+static void
+gnc_main_window_window_menu (GncMainWindow *window)
+{
+ guint merge_id;
+#ifdef MAC_INTEGRATION
+ gchar *filename = gnc_filepath_locate_ui_file("gnc-windows-menu-ui-quartz.xml");
+#else
+ gchar *filename = gnc_filepath_locate_ui_file("gnc-windows-menu-ui.xml");
+ GncMainWindowPrivate *priv;
+#endif
+ GError *error = NULL;
+ g_assert(filename);
+ merge_id = gtk_ui_manager_add_ui_from_file(window->ui_merge, filename,
+ &error);
+ g_free(filename);
+ g_assert(merge_id);
+#ifndef MAC_INTEGRATION
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ gtk_action_group_add_radio_actions (priv->action_group,
+ radio_entries, n_radio_entries,
+ 0,
+ G_CALLBACK(gnc_main_window_cmd_window_raise),
+ window);
+#endif
+};
+
+static void
+gnc_main_window_setup_window (GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+ GtkWidget *main_vbox;
+ guint merge_id;
+ GncPluginManager *manager;
+ GList *plugins;
+ GError *error = NULL;
+ gchar *filename;
+
+ ENTER(" ");
+
+ /* Catch window manager delete signal */
+ g_signal_connect (G_OBJECT (window), "delete-event",
+ G_CALLBACK (gnc_main_window_delete_event), window);
+
+ /* Create widgets and add them to the window */
+ main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_box_set_homogeneous (GTK_BOX (main_vbox), FALSE);
+ gtk_widget_show (main_vbox);
+ gtk_container_add (GTK_CONTAINER (window), main_vbox);
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ priv->menu_dock = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_box_set_homogeneous (GTK_BOX (priv->menu_dock), FALSE);
+ gtk_widget_show (priv->menu_dock);
+ gtk_box_pack_start (GTK_BOX (main_vbox), priv->menu_dock,
+ FALSE, TRUE, 0);
+
+ priv->notebook = gtk_notebook_new ();
+ g_object_set(G_OBJECT(priv->notebook),
+ "scrollable", TRUE,
+ "enable-popup", TRUE,
+ (char *)NULL);
+ gtk_widget_show (priv->notebook);
+ g_signal_connect (G_OBJECT (priv->notebook), "switch-page",
+ G_CALLBACK (gnc_main_window_switch_page), window);
+ g_signal_connect (G_OBJECT (priv->notebook), "page-reordered",
+ G_CALLBACK (gnc_main_window_page_reordered), window);
+ gtk_box_pack_start (GTK_BOX (main_vbox), priv->notebook,
+ TRUE, TRUE, 0);
+
+ priv->statusbar = gtk_statusbar_new ();
+ gtk_widget_show (priv->statusbar);
+ gtk_box_pack_start (GTK_BOX (main_vbox), priv->statusbar,
+ FALSE, TRUE, 0);
+
+ priv->progressbar = gtk_progress_bar_new ();
+ gtk_progress_bar_set_text(GTK_PROGRESS_BAR(priv->progressbar), " ");
+ gtk_widget_show (priv->progressbar);
+ gtk_box_pack_start (GTK_BOX (priv->statusbar), priv->progressbar,
+ FALSE, TRUE, 0);
+ gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(priv->progressbar),
+ 0.01);
+
+ window->ui_merge = gtk_ui_manager_new ();
+
+ /* Create menu and toolbar information */
+ priv->action_group = gtk_action_group_new ("MainWindowActions");
+ gnc_gtk_action_group_set_translation_domain (priv->action_group, GETTEXT_PACKAGE);
+ gtk_action_group_add_actions (priv->action_group, gnc_menu_actions,
+ gnc_menu_n_actions, window);
+ gtk_action_group_add_toggle_actions (priv->action_group,
+ toggle_actions, n_toggle_actions,
+ window);
+ gnc_plugin_update_actions(priv->action_group,
+ initially_insensitive_actions,
+ "sensitive", FALSE);
+ gnc_plugin_update_actions(priv->action_group,
+ always_insensitive_actions,
+ "sensitive", FALSE);
+ gnc_plugin_update_actions(priv->action_group,
+ always_hidden_actions,
+ "visible", FALSE);
+ gnc_plugin_set_important_actions (priv->action_group,
+ gnc_menu_important_actions);
+ gtk_ui_manager_insert_action_group (window->ui_merge, priv->action_group, 0);
+
+ g_signal_connect (G_OBJECT (window->ui_merge), "add_widget",
+ G_CALLBACK (gnc_main_window_add_widget), window);
+ /* Use the "connect-proxy" signal for tooltip display in the
+ status bar */
+ g_signal_connect (G_OBJECT (window->ui_merge), "connect-proxy",
+ G_CALLBACK (connect_proxy), priv->statusbar);
+
+ filename = gnc_filepath_locate_ui_file("gnc-main-window-ui.xml");
+
+ /* Can't do much without a ui. */
+ g_assert (filename);
+
+ merge_id = gtk_ui_manager_add_ui_from_file (window->ui_merge,
+ filename, &error);
+ g_assert(merge_id || error);
+ if (merge_id)
+ {
+ gtk_window_add_accel_group (GTK_WINDOW (window),
+ gtk_ui_manager_get_accel_group(window->ui_merge));
+ gtk_ui_manager_ensure_update (window->ui_merge);
+ }
+ else
+ {
+ g_critical("Failed to load ui file.\n Filename %s\n Error %s",
+ filename, error->message);
+ g_error_free(error);
+ g_assert(merge_id != 0);
+ }
+ g_free(filename);
+ gnc_main_window_window_menu(window);
+ gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
+ GNC_PREF_TAB_POSITION_TOP,
+ gnc_main_window_update_tab_position,
+ window);
+ gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
+ GNC_PREF_TAB_POSITION_BOTTOM,
+ gnc_main_window_update_tab_position,
+ window);
+ gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
+ GNC_PREF_TAB_POSITION_LEFT,
+ gnc_main_window_update_tab_position,
+ window);
+ gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
+ GNC_PREF_TAB_POSITION_RIGHT,
+ gnc_main_window_update_tab_position,
+ window);
+ gnc_main_window_update_tab_position(NULL, NULL, window);
+
+ gnc_main_window_init_menu_updaters(window);
+
+ /* Testing */
+ /* Now update the "eXtensions" menu */
+ if (!gnc_prefs_is_extra_enabled())
+ {
+ GtkAction* action;
+
+ action = gtk_action_group_get_action(priv->action_group,
+ "ExtensionsAction");
+ gtk_action_set_visible(action, FALSE);
+ }
+
+ /* GncPluginManager stuff */
+ manager = gnc_plugin_manager_get ();
+ plugins = gnc_plugin_manager_get_plugins (manager);
+ g_list_foreach (plugins, gnc_main_window_add_plugin, window);
+ g_list_free (plugins);
+
+ g_signal_connect (G_OBJECT (manager), "plugin-added",
+ G_CALLBACK (gnc_main_window_plugin_added), window);
+ g_signal_connect (G_OBJECT (manager), "plugin-removed",
+ G_CALLBACK (gnc_main_window_plugin_removed), window);
+
+ LEAVE(" ");
+}
+
+#ifdef MAC_INTEGRATION
+/* Event handlers for the shutdown process. Gnc_quartz_shutdown is
+ * connected to NSApplicationWillTerminate, the last chance to do
+ * anything before quitting. The problem is that it's launched from a
+ * CFRunLoop, not a g_main_loop, and if we call anything that would
+ * affect the main_loop we get an assert that we're in a subidiary
+ * loop.
+ */
+static void
+gnc_quartz_shutdown (GtkosxApplication *theApp, gpointer data)
+{
+ /* Do Nothing. It's too late. */
+}
+/* Should quit responds to NSApplicationBlockTermination; returning
+ * TRUE means "don't terminate", FALSE means "do terminate". If we
+ * decide that it's OK to terminate, then we queue a gnc_shutdown for
+ * the next idle time (because we're not running in the main loop) and
+ * then tell the OS not to terminate. That gives the gnc_shutdown an
+ * opportunity to shut down.
+ */
+static gboolean
+gnc_quartz_should_quit (GtkosxApplication *theApp, GncMainWindow *window)
+{
+ QofSession *session;
+ gboolean needs_save;
+
+ if (!gnc_main_window_all_finish_pending() ||
+ gnc_file_save_in_progress())
+ {
+ return TRUE;
+ }
+ session = gnc_get_current_session();
+ needs_save = qof_book_session_not_saved(qof_session_get_book(session)) &&
+ !gnc_file_save_in_progress();
+ if (needs_save && gnc_main_window_prompt_for_save(GTK_WIDGET(window)))
+ return TRUE;
+
+ g_timeout_add(250, gnc_main_window_timed_quit, NULL);
+ return TRUE;
+}
+
+static void
+gnc_quartz_set_menu(GncMainWindow* window)
+{
+ GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
+ GtkWidget *menu;
+ GtkWidget *item;
+
+ menu = gtk_ui_manager_get_widget (window->ui_merge, "/menubar");
+ if (GTK_IS_MENU_ITEM (menu))
+ menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (menu));
+ gtk_widget_hide(menu);
+ gtkosx_application_set_menu_bar (theApp, GTK_MENU_SHELL (menu));
+
+ item = gtk_ui_manager_get_widget (window->ui_merge,
+ "/menubar/File/FileQuit");
+ if (GTK_IS_MENU_ITEM (item))
+ gtk_widget_hide (GTK_WIDGET (item));
+
+ item = gtk_ui_manager_get_widget (window->ui_merge,
+ "/menubar/Help/HelpAbout");
+ if (GTK_IS_MENU_ITEM (item))
+ {
+ gtkosx_application_insert_app_menu_item (theApp, GTK_WIDGET (item), 0);
+ }
+
+ item = gtk_ui_manager_get_widget (window->ui_merge,
+ "/menubar/Edit/EditPreferences");
+ if (GTK_IS_MENU_ITEM (item))
+ {
+ gtkosx_application_insert_app_menu_item (theApp,
+ gtk_separator_menu_item_new (), 1);
+ gtkosx_application_insert_app_menu_item (theApp, GTK_WIDGET (item), 2);
+ }
+
+ item = gtk_ui_manager_get_widget (window->ui_merge,
+ "/menubar/Help");
+ gtkosx_application_set_help_menu(theApp, GTK_MENU_ITEM(item));
+ item = gtk_ui_manager_get_widget (window->ui_merge,
+ "/menubar/Windows");
+ gtkosx_application_set_window_menu(theApp, GTK_MENU_ITEM(item));
+ g_signal_connect(theApp, "NSApplicationBlockTermination",
+ G_CALLBACK(gnc_quartz_should_quit), window);
+ gtkosx_application_set_use_quartz_accelerators (theApp, FALSE);
+ g_object_unref (theApp);
+
+}
+#endif //MAC_INTEGRATION
+
+/* Callbacks */
+static void
+gnc_main_window_add_widget (GtkUIManager *merge,
+ GtkWidget *widget,
+ GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ if (GTK_IS_TOOLBAR (widget))
+ {
+ priv->toolbar = widget;
+ }
+
+ gtk_box_pack_start (GTK_BOX (priv->menu_dock), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+}
+
+/** Should a summary bar be visible in this window? In order to
+ * prevent synchronization issues, the "ViewSummaryBar"
+ * GtkToggleAction is the sole source of information for whether or
+ * not any summary bar should be visible in a window.
+ *
+ * @param window A pointer to the window in question.
+ *
+ * @param action If known, a pointer to the "ViewSummaryBar"
+ * GtkToggleAction. If NULL, the function will look up this action.
+ *
+ * @return TRUE if the summarybar should be visible.
+ */
+static gboolean
+gnc_main_window_show_summarybar (GncMainWindow *window, GtkAction *action)
+{
+ GncMainWindowPrivate *priv;
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ if (action == NULL)
+ action = gtk_action_group_get_action(priv->action_group,
+ "ViewSummaryAction");
+ if (action == NULL)
+ return TRUE;
+ return gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action));
+}
+
+/** This function is invoked when the GtkNotebook switches pages. It
+ * is responsible for updating the rest of the window contents
+ * outside of the notebook. I.E. Updating the user interface, the
+ * summary bar, etc. This function also emits the "page_changed"
+ * signal from the window so that any plugin can also learn about the
+ * fact that the page has changed.
+ *
+ * @internal
+ */
+static void
+gnc_main_window_switch_page (GtkNotebook *notebook,
+ gpointer *notebook_page,
+ gint pos,
+ GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+ GtkWidget *child;
+ GncPluginPage *page;
+ gboolean visible;
+
+ ENTER("Notebook %p, page, %p, index %d, window %p",
+ notebook, notebook_page, pos, window);
+ g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ if (priv->current_page != NULL)
+ {
+ page = priv->current_page;
+ gnc_plugin_page_unmerge_actions (page, window->ui_merge);
+ gnc_plugin_page_unselected (page);
+ }
+
+ child = gtk_notebook_get_nth_page (notebook, pos);
+ if (child)
+ {
+ page = g_object_get_data (G_OBJECT (child), PLUGIN_PAGE_LABEL);
+ }
+ else
+ {
+ page = NULL;
+ }
+
+ priv->current_page = page;
+
+ if (page != NULL)
+ {
+ /* Update the user interface (e.g. menus and toolbars */
+ gnc_plugin_page_merge_actions (page, window->ui_merge);
+ visible = gnc_main_window_show_summarybar(window, NULL);
+ gnc_plugin_page_show_summarybar (page, visible);
+
+ /* Allow page specific actions */
+ gnc_plugin_page_selected (page);
+ gnc_window_update_status (GNC_WINDOW(window), page);
+
+ /* Update the page reference info */
+ priv->usage_order = g_list_remove (priv->usage_order, page);
+ priv->usage_order = g_list_prepend (priv->usage_order, page);
+ }
+
+ gnc_plugin_update_actions(priv->action_group,
+ multiple_page_actions,
+ "sensitive",
+ g_list_length(priv->installed_pages) > 1);
+
+ gnc_main_window_update_title(window);
+#ifndef MAC_INTEGRATION
+ gnc_main_window_update_menu_item(window);
+#endif
+ g_signal_emit (window, main_window_signals[PAGE_CHANGED], 0, page);
+ LEAVE(" ");
+}
+
+/** This function is invoked when a GtkNotebook tab gets reordered by
+ * drag and drop. It adjusts the list installed_pages to reflect the new
+ * ordering so that GnuCash saves and restores the tabs correctly.
+ *
+ * @internal
+ */
+static void
+gnc_main_window_page_reordered (GtkNotebook *notebook,
+ GtkWidget *child,
+ guint pos,
+ GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+ GncPluginPage *page;
+ GList *old_link;
+
+ ENTER("Notebook %p, child %p, index %d, window %p",
+ notebook, child, pos, window);
+ g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
+
+ if (!child) return;
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+
+ page = g_object_get_data (G_OBJECT (child), PLUGIN_PAGE_LABEL);
+ if (!page) return;
+
+ old_link = g_list_find (priv->installed_pages, page);
+ if (!old_link) return;
+
+ priv->installed_pages = g_list_delete_link (priv->installed_pages,
+ old_link);
+ priv->installed_pages = g_list_insert (priv->installed_pages,
+ page, pos);
+
+ LEAVE(" ");
+}
+
+static void
+gnc_main_window_plugin_added (GncPlugin *manager,
+ GncPlugin *plugin,
+ GncMainWindow *window)
+{
+ g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
+ g_return_if_fail (GNC_IS_PLUGIN (plugin));
+
+ gnc_plugin_add_to_window (plugin, window, window_type);
+}
+
+static void
+gnc_main_window_plugin_removed (GncPlugin *manager,
+ GncPlugin *plugin,
+ GncMainWindow *window)
+{
+ g_return_if_fail (GNC_IS_MAIN_WINDOW (window));
+ g_return_if_fail (GNC_IS_PLUGIN (plugin));
+
+ gnc_plugin_remove_from_window (plugin, window, window_type);
+}
+
+
+/* Command callbacks */
+static void
+gnc_main_window_cmd_page_setup (GtkAction *action,
+ GncMainWindow *window)
+{
+ GtkWindow *gtk_window;
+
+ g_return_if_fail(GNC_IS_MAIN_WINDOW(window));
+
+ gtk_window = gnc_window_get_gtk_window(GNC_WINDOW(window));
+ gnc_ui_page_setup(gtk_window);
+}
+
+gboolean
+gnc_book_options_dialog_apply_helper(GNCOptionDB * options)
+{
+ QofBook *book = gnc_get_current_book ();
+ gboolean use_split_action_for_num_before =
+ qof_book_use_split_action_for_num_field (book);
+ gboolean use_book_currency_before =
+ gnc_book_use_book_currency (book);
+ gboolean use_split_action_for_num_after;
+ gboolean use_book_currency_after;
+ gboolean return_val = FALSE;
+ GList *results = NULL, *iter;
+
+ if (!options) return return_val;
+
+ results = gnc_option_db_commit (options);
+ for (iter = results; iter; iter = iter->next)
+ {
+ GtkWidget *dialog = gtk_message_dialog_new(NULL,
+ 0,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "%s",
+ (char*)iter->data);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ g_free (iter->data);
+ }
+ g_list_free (results);
+ qof_book_begin_edit (book);
+ qof_book_save_options (book, gnc_option_db_save, options, TRUE);
+ use_split_action_for_num_after =
+ qof_book_use_split_action_for_num_field (book);
+ use_book_currency_after = gnc_book_use_book_currency (book);
+ if (use_split_action_for_num_before != use_split_action_for_num_after)
+ {
+ gnc_book_option_num_field_source_change_cb (
+ use_split_action_for_num_after);
+ return_val = TRUE;
+ }
+ if (use_book_currency_before != use_book_currency_after)
+ {
+ gnc_book_option_book_currency_selected_cb (use_book_currency_after);
+ return_val = TRUE;
+ }
+ qof_book_commit_edit (book);
+ return return_val;
+}
+
+static void
+gnc_book_options_dialog_apply_cb(GNCOptionWin * optionwin,
+ gpointer user_data)
+{
+ GNCOptionDB * options = user_data;
+
+ if (!options) return;
+
+ if (gnc_book_options_dialog_apply_helper (options))
+ gnc_gui_refresh_all ();
+}
+
+static void
+gnc_book_options_dialog_close_cb(GNCOptionWin * optionwin,
+ gpointer user_data)
+{
+ GNCOptionDB * options = user_data;
+
+ gnc_options_dialog_destroy(optionwin);
+ gnc_option_db_destroy(options);
+}
+
+static gboolean
+show_handler (const char *class_name, gint component_id,
+ gpointer user_data, gpointer iter_data)
+{
+ GtkWidget *dialog;
+
+ dialog = GTK_WIDGET(user_data);
+ gtk_window_present(GTK_WINDOW(dialog));
+ return(TRUE);
+}
+
+GtkWidget *
+gnc_book_options_dialog_cb (gboolean modal, gchar *title)
+{
+ QofBook *book = gnc_get_current_book ();
+ GNCOptionDB *options;
+ GNCOptionWin *optionwin;
+
+ options = gnc_option_db_new_for_type (QOF_ID_BOOK);
+ qof_book_load_options (book, gnc_option_db_load, options);
+ gnc_option_db_clean (options);
+
+ /* Only allow one Book Options dialog if called from file->properties
+ menu */
+ if (gnc_forall_gui_components(DIALOG_BOOK_OPTIONS_CM_CLASS,
+ show_handler, NULL))
+ {
+ return NULL;
+ }
+ optionwin = gnc_options_dialog_new_modal (modal,
+ (title ? title : _( "Book Options")),
+ DIALOG_BOOK_OPTIONS_CM_CLASS);
+ gnc_options_dialog_build_contents (optionwin, options);
+
+ gnc_options_dialog_set_book_options_help_cb (optionwin);
+
+ gnc_options_dialog_set_apply_cb (optionwin,
+ gnc_book_options_dialog_apply_cb,
+ (gpointer)options);
+ gnc_options_dialog_set_close_cb (optionwin,
+ gnc_book_options_dialog_close_cb,
+ (gpointer)options);
+ if (modal)
+ gnc_options_dialog_set_new_book_option_values (options);
+ return gnc_options_dialog_widget (optionwin);
+}
+
+static void
+gnc_main_window_cmd_file_properties (GtkAction *action, GncMainWindow *window)
+{
+ gnc_book_options_dialog_cb (FALSE, NULL);
+}
+
+static void
+gnc_main_window_cmd_file_close (GtkAction *action, GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+ GncPluginPage *page;
+
+ g_return_if_fail(GNC_IS_MAIN_WINDOW(window));
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ page = priv->current_page;
+ gnc_main_window_close_page(page);
+}
+
+static void
+gnc_main_window_cmd_file_quit (GtkAction *action, GncMainWindow *window)
+{
+ if (!gnc_main_window_all_finish_pending())
+ return;
+
+ gnc_main_window_quit(window);
+}
+
+static void
+gnc_main_window_cmd_edit_cut (GtkAction *action, GncMainWindow *window)
+{
+ GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (window));
+ GtkTextBuffer *text_buffer;
+ GtkClipboard *clipboard;
+ gboolean editable;
+
+ if (GTK_IS_EDITABLE (widget))
+ {
+ gtk_editable_cut_clipboard (GTK_EDITABLE (widget));
+ }
+ else if (GTK_IS_TEXT_VIEW (widget))
+ {
+ text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET(text_buffer),
+ GDK_SELECTION_CLIPBOARD);
+ editable = gtk_text_view_get_editable (GTK_TEXT_VIEW (widget));
+ gtk_text_buffer_cut_clipboard (text_buffer, clipboard, editable);
+ }
+}
+
+static void
+gnc_main_window_cmd_edit_copy (GtkAction *action, GncMainWindow *window)
+{
+ GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (window));
+ GtkTextBuffer *text_buffer;
+ GtkClipboard *clipboard;
+
+ if (GTK_IS_EDITABLE (widget))
+ {
+ gtk_editable_copy_clipboard (GTK_EDITABLE (widget));
+ }
+ else if (GTK_IS_TEXT_VIEW (widget))
+ {
+ text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET(text_buffer),
+ GDK_SELECTION_CLIPBOARD);
+ gtk_text_buffer_copy_clipboard (text_buffer, clipboard);
+ }
+}
+
+static void
+gnc_main_window_cmd_edit_paste (GtkAction *action, GncMainWindow *window)
+{
+ GtkWidget *widget = gtk_window_get_focus (GTK_WINDOW (window));
+ GtkTextBuffer *text_buffer;
+ GtkClipboard *clipboard;
+
+ if (GTK_IS_EDITABLE (widget))
+ {
+ gtk_editable_paste_clipboard (GTK_EDITABLE (widget));
+ }
+ else if (GTK_IS_TEXT_VIEW (widget))
+ {
+ text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW(widget));
+ clipboard = gtk_widget_get_clipboard (GTK_WIDGET(text_buffer),
+ GDK_SELECTION_CLIPBOARD);
+ gtk_text_buffer_paste_clipboard (text_buffer, clipboard, NULL, FALSE);
+ }
+}
+
+static void
+gnc_main_window_cmd_edit_preferences (GtkAction *action, GncMainWindow *window)
+{
+ gnc_preferences_dialog ();
+}
+
+static void
+gnc_main_window_cmd_view_refresh (GtkAction *action, GncMainWindow *window)
+{
+}
+
+static void
+gnc_main_window_cmd_actions_reset_warnings (GtkAction *action, GncMainWindow *window)
+{
+ gnc_reset_warnings_dialog(GTK_WINDOW(window));
+}
+
+static void
+gnc_main_window_cmd_actions_rename_page (GtkAction *action, GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+ GncPluginPage *page;
+ GtkWidget *label, *entry;
+
+ ENTER(" ");
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ page = priv->current_page;
+ if (!page)
+ {
+ LEAVE("No current page");
+ return;
+ }
+
+ if (!main_window_find_tab_items(window, page, &label, &entry))
+ {
+ LEAVE("can't find required widgets");
+ return;
+ }
+
+ gtk_entry_set_text(GTK_ENTRY(entry), gtk_label_get_text(GTK_LABEL(label)));
+ gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
+ gtk_widget_hide(label);
+ gtk_widget_show(entry);
+ gtk_widget_grab_focus(entry);
+ LEAVE("opened for editing");
+}
+
+static void
+gnc_main_window_cmd_view_toolbar (GtkAction *action, GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ if (gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)))
+ {
+ gtk_widget_show (priv->toolbar);
+ }
+ else
+ {
+ gtk_widget_hide (priv->toolbar);
+ }
+}
+
+static void
+gnc_main_window_cmd_view_summary (GtkAction *action, GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+ GList *item;
+ gboolean visible;
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ visible = gnc_main_window_show_summarybar(window, action);
+ for (item = priv->installed_pages; item; item = g_list_next(item))
+ {
+ gnc_plugin_page_show_summarybar(item->data, visible);
+ }
+}
+
+static void
+gnc_main_window_cmd_view_statusbar (GtkAction *action, GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ if (gtk_toggle_action_get_active(GTK_TOGGLE_ACTION(action)))
+ {
+ gtk_widget_show (priv->statusbar);
+ }
+ else
+ {
+ gtk_widget_hide (priv->statusbar);
+ }
+}
+
+static void
+gnc_main_window_cmd_window_new (GtkAction *action, GncMainWindow *window)
+{
+ GncMainWindow *new_window;
+
+ /* Create the new window */
+ ENTER(" ");
+ new_window = gnc_main_window_new ();
+ gtk_widget_show(GTK_WIDGET(new_window));
+ LEAVE(" ");
+}
+
+static void
+gnc_main_window_cmd_window_move_page (GtkAction *action, GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+ GncMainWindow *new_window;
+ GncPluginPage *page;
+ GtkNotebook *notebook;
+ GtkWidget *tab_widget, *menu_widget;
+
+ ENTER("action %p,window %p", action, window);
+
+ /* Setup */
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ page = priv->current_page;
+ if (!page)
+ {
+ LEAVE("invalid page");
+ return;
+ }
+ if (!page->notebook_page)
+ {
+ LEAVE("invalid notebook_page");
+ return;
+ }
+
+ notebook = GTK_NOTEBOOK (priv->notebook);
+ tab_widget = gtk_notebook_get_tab_label (notebook, page->notebook_page);
+ menu_widget = gtk_notebook_get_menu_label (notebook, page->notebook_page);
+
+ /* Ref the page components, then remove it from its old window */
+ g_object_ref(page);
+ g_object_ref(tab_widget);
+ g_object_ref(menu_widget);
+ g_object_ref(page->notebook_page);
+ gnc_main_window_disconnect(window, page);
+
+ /* Create the new window */
+ new_window = gnc_main_window_new ();
+ gtk_widget_show(GTK_WIDGET(new_window));
+
+ /* Now add the page to the new window */
+ gnc_main_window_connect (new_window, page, tab_widget, menu_widget);
+
+ /* Unref the page components now that we're done */
+ g_object_unref(page->notebook_page);
+ g_object_unref(menu_widget);
+ g_object_unref(tab_widget);
+ g_object_unref(page);
+
+ /* just a little debugging. :-) */
+ DEBUG("Moved page %p from window %p to new window %p",
+ page, window, new_window);
+ DEBUG("Old window current is %p, new window current is %p",
+ priv->current_page, priv->current_page);
+
+ LEAVE("page moved");
+}
+
+#ifndef MAC_INTEGRATION
+static void
+gnc_main_window_cmd_window_raise (GtkAction *action,
+ GtkRadioAction *current,
+ GncMainWindow *old_window)
+{
+ GncMainWindow *new_window;
+ gint value;
+
+ g_return_if_fail(GTK_IS_ACTION(action));
+ g_return_if_fail(GTK_IS_RADIO_ACTION(current));
+ g_return_if_fail(GNC_IS_MAIN_WINDOW(old_window));
+
+ ENTER("action %p, current %p, window %p", action, current, old_window);
+ value = gtk_radio_action_get_current_value(current);
+ new_window = g_list_nth_data(active_windows, value);
+ gtk_window_present(GTK_WINDOW(new_window));
+ /* revert the change in the radio group
+ * impossible while handling "changed" (G_SIGNAL_NO_RECURSE) */
+ g_idle_add((GSourceFunc)gnc_main_window_update_radio_button, old_window);
+ LEAVE(" ");
+}
+#endif /* !MAC_INTEGRATION */
+
+static void
+gnc_main_window_cmd_help_tutorial (GtkAction *action, GncMainWindow *window)
+{
+ gnc_gnome_help (HF_GUIDE, NULL);
+}
+
+static void
+gnc_main_window_cmd_help_contents (GtkAction *action, GncMainWindow *window)
+{
+ gnc_gnome_help (HF_HELP, NULL);
+}
+
+/** This is a helper function to find a data file and suck it into
+ * memory.
+ *
+ * @param partial The name of the file relative to the gnucash
+ * specific shared data directory.
+ *
+ * @return The text of the file or NULL. The caller is responsible
+ * for freeing this string.
+ */
+static gchar *
+get_file (const gchar *partial)
+{
+ gchar *filename, *text = NULL;
+ gsize length;
+
+ filename = gnc_filepath_locate_doc_file(partial);
+ if (filename && g_file_get_contents(filename, &text, &length, NULL))
+ {
+ if (length)
+ {
+ g_free(filename);
+ return text;
+ }
+ g_free(text);
+ }
+ g_free (filename);
+ return NULL;
+}
+
+
+/** This is a helper function to find a data file, suck it into
+ * memory, and split it into an array of strings.
+ *
+ * @param partial The name of the file relative to the gnucash
+ * specific shared data directory.
+ *
+ * @return The text of the file as an array of strings, or NULL. The
+ * caller is responsible for freeing all the strings and the array.
+ */
+static gchar **
+get_file_strsplit (const gchar *partial)
+{
+ gchar *text, **lines;
+
+ text = get_file(partial);
+ if (!text)
+ return NULL;
+
+ lines = g_strsplit_set(text, "\r\n", -1);
+ g_free(text);
+ return lines;
+}
+/** URL activation callback.
+ * Use our own function to activate the URL in the users browser
+ * instead of gtk_show_uri(), which requires gvfs.
+ * Signature described in gtk docs at GtkAboutDialog activate-link signal.
+ */
+
+static gboolean
+url_signal_cb (GtkAboutDialog *dialog, gchar *uri, gpointer data)
+{
+ gnc_launch_assoc (uri);
+ return TRUE;
+}
+
+/** Create and display the "about" dialog for gnucash.
+ *
+ * @param action The GtkAction for the "about" menu item.
+ *
+ * @param window The main window whose menu item was activated.
+ */
+static void
+gnc_main_window_cmd_help_about (GtkAction *action, GncMainWindow *window)
+{
+ GncMainWindowPrivate *priv;
+
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+
+ if (priv->about_dialog == NULL)
+ {
+ const gchar *fixed_message = _("The GnuCash personal finance manager. "
+ "The GNU way to manage your money!");
- const gchar *copyright = _("© 1997-2017 Contributors");
++ gchar *copyright = g_strdup_printf(_("© 1997-%s Contributors"),
++ GNUCASH_BUILD_YEAR);
+ gchar **authors = get_file_strsplit("AUTHORS");
+ gchar **documenters = get_file_strsplit("DOCUMENTERS");
+ gchar *license = get_file("LICENSE");
+ gchar *message;
+ GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
+ GdkPixbuf *logo = gtk_icon_theme_load_icon (icon_theme,
+ GNC_ICON_APP,
+ 48,
+ GTK_ICON_LOOKUP_USE_BUILTIN,
+ NULL);
+
+
+#ifdef GNUCASH_SCM
+ /* Development version */
+ /* Translators: 1st %s is a fixed message, which is translated independently;
+ 2nd %s is the scm type (svn/svk/git/bzr);
+ 3rd %s is the scm revision number;
+ 4th %s is the build date */
+ message = g_strdup_printf(_("%s\nThis copy was built from %s rev %s on %s."),
+ fixed_message, GNUCASH_SCM, GNUCASH_SCM_REV,
+ GNUCASH_BUILD_DATE);
+#else
+ /* Translators: 1st %s is a fixed message, which is translated independently;
+ 2nd %s is the scm (svn/svk/git/bzr) revision number;
+ 3rd %s is the build date */
+ message = g_strdup_printf(_("%s\nThis copy was built from rev %s on %s."),
+ fixed_message, GNUCASH_SCM_REV,
+ GNUCASH_BUILD_DATE);
+#endif
+ priv->about_dialog = gtk_about_dialog_new ();
+ g_object_set (priv->about_dialog,
+ "authors", authors,
+ "documenters", documenters,
+ "comments", message,
+ "copyright", copyright,
+ "license", license,
+ "logo", logo,
+ "name", "GnuCash",
+ /* Translators: the following string will be shown in Help->About->Credits
+ * Enter your name or that of your team and an email contact for feedback.
+ * The string can have multiple rows, so you can also add a list of
+ * contributors. */
+ "translator-credits", _("translator_credits"),
+ "version", VERSION,
+ "website", "http://www.gnucash.org",
+ NULL);
+
+ g_free(message);
++ g_free(copyright);
+ if (license) g_free(license);
+ if (documenters) g_strfreev(documenters);
+ if (authors) g_strfreev(authors);
+ g_object_unref (logo);
+ g_signal_connect (priv->about_dialog, "activate-link",
+ G_CALLBACK (url_signal_cb), NULL);
+ g_signal_connect (priv->about_dialog, "response",
+ G_CALLBACK (gtk_widget_hide), NULL);
+
+ /* Set dialog to resize. */
+ gtk_window_set_resizable(GTK_WINDOW(priv->about_dialog), TRUE);
+
+ gtk_window_set_transient_for (GTK_WINDOW (priv->about_dialog),
+ GTK_WINDOW (window));
+ }
+ gtk_dialog_run (GTK_DIALOG (priv->about_dialog));
+}
+
+
+/************************************************************
+ * *
+ ************************************************************/
+
+void
+gnc_main_window_show_all_windows(void)
+{
+ GList *window_iter;
+#ifdef MAC_INTEGRATION
+ GtkosxApplication *theApp = g_object_new(GTKOSX_TYPE_APPLICATION, NULL);
+#endif
+ for (window_iter = active_windows; window_iter != NULL; window_iter = window_iter->next)
+ {
+ gtk_widget_show(GTK_WIDGET(window_iter->data));
+ }
+#ifdef MAC_INTEGRATION
+ g_signal_connect(theApp, "NSApplicationWillTerminate",
+ G_CALLBACK(gnc_quartz_shutdown), NULL);
+ gtkosx_application_ready(theApp);
+ g_object_unref (theApp);
+#endif
+}
+
+/** Get a pointer to the first active top level window or NULL
+ * if there is none.
+ *
+ * @return A pointer to a GtkWindow object. */
+GtkWidget *
+gnc_ui_get_toplevel (void)
+{
+ GList *window;
+
+ for (window = active_windows; window; window = window->next)
+ if (gtk_window_is_active (GTK_WINDOW (window->data)))
+ return window->data;
+
+ return NULL;
+}
+
+
+/** Retrieve the gtk window associated with a main window object.
+ * This function is called via a vector off a generic window
+ * interface.
+ *
+ * @param window A pointer to a generic window. */
+static GtkWindow *
+gnc_main_window_get_gtk_window (GncWindow *window)
+{
+ g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window), NULL);
+ return GTK_WINDOW(window);
+}
+
+
+/** Retrieve the status bar associated with a main window object.
+ * This function is called via a vector off a generic window
+ * interface.
+ *
+ * @param window_in A pointer to a generic window. */
+static GtkWidget *
+gnc_main_window_get_statusbar (GncWindow *window_in)
+{
+ GncMainWindowPrivate *priv;
+ GncMainWindow *window;
+
+ g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), NULL);
+
+ window = GNC_MAIN_WINDOW(window_in);
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ return priv->statusbar;
+}
+
+
+/** Retrieve the progress bar associated with a main window object.
+ * This function is called via a vector off a generic window
+ * interface.
+ *
+ * @param window_in A pointer to a generic window. */
+static GtkWidget *
+gnc_main_window_get_progressbar (GncWindow *window_in)
+{
+ GncMainWindowPrivate *priv;
+ GncMainWindow *window;
+
+ g_return_val_if_fail (GNC_IS_MAIN_WINDOW (window_in), NULL);
+
+ window = GNC_MAIN_WINDOW(window_in);
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+ return priv->progressbar;
+}
+
+
+static void
+gnc_main_window_all_ui_set_sensitive (GncWindow *unused, gboolean sensitive)
+{
+ GncMainWindow *window;
+ GncMainWindowPrivate *priv;
+ GList *groupp, *groups, *winp, *tmp;
+ GtkWidget *close_button;
+
+ for (winp = active_windows; winp; winp = g_list_next(winp))
+ {
+ window = winp->data;
+ priv = GNC_MAIN_WINDOW_GET_PRIVATE(window);
+
+ groups = gtk_ui_manager_get_action_groups(window->ui_merge);
+ for (groupp = groups; groupp; groupp = g_list_next(groupp))
+ {
+ gtk_action_group_set_sensitive(GTK_ACTION_GROUP(groupp->data), sensitive);
+ }
+
+ for (tmp = priv->installed_pages; tmp; tmp = g_list_next(tmp))
+ {
+ close_button = g_object_get_data(tmp->data, PLUGIN_PAGE_CLOSE_BUTTON);
+ if (!close_button)
+ continue;
+ gtk_widget_set_sensitive (close_button, sensitive);
+ }
+ }
+}
+
+
+/** Initialize the generic window interface for a main window.
+ *
+ * @param iface A pointer to the interface data structure to
+ * populate. */
+static void
+gnc_window_main_window_init (GncWindowIface *iface)
+{
+ iface->get_gtk_window = gnc_main_window_get_gtk_window;
+ iface->get_statusbar = gnc_main_window_get_statusbar;
+ iface->get_progressbar = gnc_main_window_get_progressbar;
+ iface->ui_set_sensitive = gnc_main_window_all_ui_set_sensitive;
+}
+
+
+/* Set the window where all progressbar updates should occur. This
+ * is a wrapper around the gnc_window_set_progressbar_window()
+ * function.
+ */
+void
+gnc_main_window_set_progressbar_window (GncMainWindow *window)
+{
+ GncWindow *gncwin;
+ gncwin = GNC_WINDOW(window);
+ gnc_window_set_progressbar_window(gncwin);
+}
+
+
+/** Popup a contextual menu. This function ends up being called when
+ * the user right-clicks in the context of a window, or uses the
+ * keyboard context-menu request key combination (Shift-F10 by
+ * default).
+ *
+ * @param page This is the GncPluginPage corresponding to the visible
+ * page.
+ *
+ * @param event The event parameter passed to the "button-press"
+ * callback. May be null if there was no event (aka keyboard
+ * request).
+ */
+static void
+do_popup_menu(GncPluginPage *page, GdkEventButton *event)
+{
+ GtkUIManager *ui_merge;
+ GtkWidget *menu;
+ int button, event_time;
+
+ g_return_if_fail(GNC_IS_PLUGIN_PAGE(page));
+
+ ENTER("page %p, event %p", page, event);
+ ui_merge = gnc_plugin_page_get_ui_merge(page);
+ if (ui_merge == NULL)
+ {
+ LEAVE("no ui merge");
+ return;
+ }
+
+ menu = gtk_ui_manager_get_widget(ui_merge, "/MainPopup");
+ if (!menu)
+ {
+ LEAVE("no menu");
+ return;
+ }
+
+ if (event)
+ {
+ button = event->button;
+ event_time = event->time;
+ }
+ else
+ {
+ button = 0;
+ event_time = gtk_get_current_event_time ();
+ }
+#if GTK_CHECK_VERSION(3,22,0)
+ gtk_menu_popup_at_pointer (GTK_MENU(menu), (GdkEvent *) event);
+#else
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, button, event_time);
+#endif
+ LEAVE(" ");
+}
+
+
+/** Callback function invoked when the user requests that Gnucash
+ * popup the contextual menu via the keyboard context-menu request
+ * key combination (Shift-F10 by default).
+ *
+ * @param page This is the GncPluginPage corresponding to the visible
+ * page.
+ *
+ * @param widget Whatever widget had focus when the user issued the
+ * keyboard context-menu request.
+ *
+ * @return Always returns TRUE to indicate that the menu request was
+ * handled.
+ */
+static gboolean
+gnc_main_window_popup_menu_cb (GtkWidget *widget,
+ GncPluginPage *page)
+{
+ ENTER("widget %p, page %p", widget, page);
+ do_popup_menu(page, NULL);
+ LEAVE(" ");
+ return TRUE;
+}
+
+
+/* Callback function invoked when the user clicks in the content of
+ * any Gnucash window. If this was a "right-click" then Gnucash will
+ * popup the contextual menu.
+ */
+gboolean
+gnc_main_window_button_press_cb (GtkWidget *whatever,
+ GdkEventButton *event,
+ GncPluginPage *page)
+{
+ g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);
+
+ ENTER("widget %p, event %p, page %p", whatever, event, page);
+ /* Ignore double-clicks and triple-clicks */
+ if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
+ {
+ do_popup_menu(page, event);
+ LEAVE("menu shown");
+ return TRUE;
+ }
+
+ LEAVE("other click");
+ return FALSE;
+}
+
+
+/* CS: Code copied from gtk/gtkactiongroup.c */
+static gchar *
+dgettext_swapped (const gchar *msgid,
+ const gchar *domainname)
+{
+ /* CS: Pass this through dgettext if and only if msgid is
+ nonempty. */
+ return (msgid && *msgid) ? dgettext (domainname, msgid) : (gchar*) msgid;
+}
+
+/*
+ * This is copied into GnuCash from Gtk in order to fix problems when
+ * empty msgids were passed through gettext().
+ *
+ * See http://bugzilla.gnome.org/show_bug.cgi?id=326200 . If that bug
+ * is fixed in the gtk that we can rely open, then
+ * gnc_gtk_action_group_set_translation_domain can be replaced by
+ * gtk_action_group_set_translation_domain again.
+ */
+void
+gnc_gtk_action_group_set_translation_domain (GtkActionGroup *action_group,
+ const gchar *domain)
+{
+ g_return_if_fail (GTK_IS_ACTION_GROUP (action_group));
+
+ gtk_action_group_set_translate_func (action_group,
+ (GtkTranslateFunc)dgettext_swapped,
+ g_strdup (domain),
+ g_free);
+}
+/* CS: End of code copied from gtk/gtkactiongroup.c */
+
+void
+gnc_main_window_all_action_set_sensitive (const gchar *action_name,
+ gboolean sensitive)
+{
+ GList *tmp;
+ GtkAction *action;
+
+ for (tmp = active_windows; tmp; tmp = g_list_next(tmp))
+ {
+ action = gnc_main_window_find_action (tmp->data, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+ }
+}
+
+GtkUIManager *gnc_main_window_get_uimanager (GncMainWindow *window)
+{
+ g_assert(window);
+ return window->ui_merge;
+}
+
+/** @} */
+/** @} */
diff --cc libgnucash/core-utils/CMakeLists.txt
index 710d39a,0000000..37b72d9
mode 100644,000000..100644
--- a/libgnucash/core-utils/CMakeLists.txt
+++ b/libgnucash/core-utils/CMakeLists.txt
@@@ -1,202 -1,0 +1,204 @@@
+# CMakeLists.txt for libgnucash/core-utils
+
+ADD_SUBDIRECTORY(test)
+
+IF (BUILDING_FROM_VCS)
+ SET (SWIG_CORE_UTILS_GUILE_C ${CMAKE_CURRENT_BINARY_DIR}/swig-core-utils-guile.c)
+ GNC_ADD_SWIG_COMMAND (swig-core-utils-guile-c ${SWIG_CORE_UTILS_GUILE_C} ${CMAKE_CURRENT_SOURCE_DIR}/core-utils.i)
+ SET (SWIG_CORE_UTILS_PYTHON_C ${CMAKE_CURRENT_BINARY_DIR}/swig-core-utils-python.c)
+ GNC_ADD_SWIG_PYTHON_COMMAND (swig-core-utils-python ${SWIG_CORE_UTILS_PYTHON_C} ${CMAKE_CURRENT_SOURCE_DIR}/core-utils.i)
+ELSE()
+ SET(SWIG_CORE_UTILS_GUILE_C swig-core-utils-guile.c)
+ SET(SWIG_CORE_UTILS_PYTHON_C swig-core-utils-python.c)
+ENDIF()
+
+SET (core_utils_SOURCES
+ binreloc.c
+ gnc-prefs.c
+ gnc-environment.c
+ gnc-filepath-utils.cpp
+ gnc-gkeyfile-utils.c
+ gnc-glib-utils.c
+ gnc-guile-utils.c
+ gnc-jalali.c
+ gnc-locale-utils.c
+ gnc-path.c
+)
+
+# Add dependency on config.h
+SET_SOURCE_FILES_PROPERTIES (${core_utils_SOURCES} PROPERTIES OBJECT_DEPENDS ${CONFIG_H})
+
+IF (BUILDING_FROM_VCS)
+ # Command to generate the swig-runtime.h header
+ ADD_CUSTOM_COMMAND (
+ OUTPUT ${SWIG_RUNTIME_H}
+ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
+ COMMAND ${SWIG_EXECUTABLE} -guile -external-runtime ${SWIG_RUNTIME_H}
+ )
+ENDIF()
+
+ADD_CUSTOM_TARGET(swig-runtime-h DEPENDS ${SWIG_RUNTIME_H})
+
+# Add dependency on swig-runtime.h
+SET_SOURCE_FILES_PROPERTIES (gnc-guile-utils.c PROPERTIES OBJECT_DEPENDS ${SWIG_RUNTIME_H})
+
+SET(prefix ${CMAKE_INSTALL_PREFIX})
+SET(datadir ${DATADIR})
+SET(bindir ${BINDIR})
+SET(libdir ${LIBDIR})
+SET(sysconfdir ${SYSCONFDIR})
+GNC_CONFIGURE(gncla-dir.h.in gncla-dir.h)
+
+### Create gnc-version.h ###
+
+SET (GNC_VERSION_H_IN
+"/* Autogenerated. Do not change. */
+#ifndef GNC_VERSION_H
+#define GNC_VERSION_H
+
+#define GNUCASH_SCM \"git\"
+#define GNUCASH_BUILD_DATE \"@GNUCASH_BUILD_DATE@\"
++#define GNUCASH_BUILD_YEAR \"@GNUCASH_BUILD_YEAR@\"
+#include \"gnc-vcs-info.h\"
+#endif
+")
+
+STRING(TIMESTAMP GNUCASH_BUILD_DATE "%Y-%m-%d")
++STRING(TIMESTAMP GNUCASH_BUILD_YEAR "%Y")
+
+STRING(CONFIGURE ${GNC_VERSION_H_IN} GNC_VERSION_H_CONTENT)
+
+FILE (WRITE ${CMAKE_CURRENT_BINARY_DIR}/gnc-version.h ${GNC_VERSION_H_CONTENT})
+
+### Create gnc-vcs-info.h
+# The meta-cmake gymnastics here come from https://cmake.org/pipermail/cmake/2010-July/038015.html
+
+FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/gnc-vcs-info.h.in
+ "/* Autogenerated. Do not change. */\n#define GNUCASH_SCM_REV \"@GNUCASH_SCM_REV@\"\n"
+)
+SET(SCM_REV_COMMAND "${CMAKE_SOURCE_DIR}/util/gnc-vcs-info -r")
+FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/version.cmake
+ "EXECUTE_PROCESS(
+ COMMAND ${SHELL} ${SCM_REV_COMMAND} ${CMAKE_SOURCE_DIR}
+ OUTPUT_VARIABLE GNUCASH_SCM_REV
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+ CONFIGURE_FILE(\${SRC} \${DST} @ONLY)
+")
+
+IF (${CMAKE_VERSION} VERSION_GREATER "3.1")
+ ADD_CUSTOM_TARGET(gnc-vcs-info ALL
+ BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/gnc-vcs-info.h
+ COMMAND ${CMAKE_COMMAND} -D SRC=${CMAKE_CURRENT_BINARY_DIR}/gnc-vcs-info.h.in
+ -D DST=${CMAKE_CURRENT_BINARY_DIR}/gnc-vcs-info.h
+ -P ${CMAKE_CURRENT_BINARY_DIR}/version.cmake
+ )
+ELSE()
+ # BYPRODUCTS keyword not supported below 3.2
+ ADD_CUSTOM_TARGET(gnc-vcs-info ALL
+ COMMAND ${CMAKE_COMMAND} -D SRC=${CMAKE_CURRENT_BINARY_DIR}/gnc-vcs-info.h.in
+ -D DST=${CMAKE_CURRENT_BINARY_DIR}/gnc-vcs-info.h
+ -P ${CMAKE_CURRENT_BINARY_DIR}/version.cmake
+ )
+ENDIF()
+
+### Compile library
+
+SET(core_utils_noinst_HEADERS
+ binreloc.h
+ gnc-prefs.h
+ gnc-prefs-p.h
+ gnc-environment.h
+ gnc-filepath-utils.h
+ gnc-gkeyfile-utils.h
+ gnc-glib-utils.h
+ gnc-guile-utils.h
+ gnc-jalali.h
+ gnc-locale-utils.h
+ gnc-path.h
+)
+
+SET(core_utils_ALL_SOURCES ${core_utils_SOURCES} ${core_utils_noinst_HEADERS})
+SET(core_utils_ALL_LIBRARIES ${Boost_LIBRARIES} ${GUILE_LDFLAGS} ${GLIB2_LDFLAGS} ${GOBJECT_LDFLAGS} ${GTK_MAC_LDFLAGS})
+SET(core_utils_ALL_INCLUDES
+ ${CMAKE_SOURCE_DIR}/common
+ ${CMAKE_BINARY_DIR}/common
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${GLIB2_INCLUDE_DIRS}
+ ${GUILE_INCLUDE_DIRS}
+ ${GTK_MAC_INCLUDE_DIRS})
+
+SET_LOCAL_DIST(core_utils_DIST_local ${core_utils_ALL_SOURCES} CMakeLists.txt Makefile.am core-utils.i core-utils.scm gncla-dir.h.in)
+SET(core_utils_DIST ${core_utils_DIST_local} ${test_core_utils_DIST} PARENT_SCOPE)
+
+IF (MAC_INTEGRATION)
+ LIST(APPEND core_utils_ALL_LIBRARIES ${OSX_EXTRA_LIBRARIES})
+ENDIF()
+
+ADD_LIBRARY (gnc-core-utils ${core_utils_ALL_SOURCES} ${SWIG_CORE_UTILS_GUILE_C})
+ADD_DEPENDENCIES(gnc-core-utils gnc-vcs-info)
+
+TARGET_LINK_LIBRARIES(gnc-core-utils ${core_utils_ALL_LIBRARIES})
+
+TARGET_COMPILE_DEFINITIONS(gnc-core-utils
+ PRIVATE -DG_LOG_DOMAIN=\"gnc.core-utils\" ${GTK_MAC_CFLAGS_OTHER})
+
+TARGET_INCLUDE_DIRECTORIES(gnc-core-utils PUBLIC ${core_utils_ALL_INCLUDES})
+
+IF (MAC_INTEGRATION)
+ TARGET_COMPILE_OPTIONS(gnc-core-utils PRIVATE ${OSX_EXTRA_COMPILE_FLAGS})
+ENDIF(MAC_INTEGRATION)
+
+INSTALL(TARGETS gnc-core-utils
+ LIBRARY DESTINATION lib
+ ARCHIVE DESTINATION lib
+ RUNTIME DESTINATION bin
+)
+
+IF (WITH_PYTHON)
+ ADD_LIBRARY (gnc-core-utils-python ${core_utils_ALL_SOURCES} ${SWIG_CORE_UTILS_PYTHON_C})
+ ADD_DEPENDENCIES(gnc-core-utils-python gnc-vcs-info)
+
+ TARGET_LINK_LIBRARIES(gnc-core-utils-python ${core_utils_ALL_LIBRARIES} ${PYTHON_LIBRARIES})
+
+ TARGET_COMPILE_DEFINITIONS(gnc-core-utils-python
+ PRIVATE -DG_LOG_DOMAIN=\"gnc.core-utils\" ${GTK_MAC_CFLAGS_OTHER}
+ )
+
+ TARGET_INCLUDE_DIRECTORIES(gnc-core-utils-python PUBLIC ${core_utils_ALL_INCLUDES} ${PYTHON_INCLUDE_DIRS})
+
+ IF (MAC_INTEGRATION)
+ TARGET_COMPILE_OPTIONS(gnc-core-utils-python PRIVATE ${OSX_EXTRA_COMPILE_FLAGS})
+ ENDIF(MAC_INTEGRATION)
+
+ INSTALL(TARGETS gnc-core-utils-python
+ LIBRARY DESTINATION lib
+ ARCHIVE DESTINATION lib
+ RUNTIME DESTINATION bin
+ )
+
+ENDIF()
+
+# No headers to install
+
+# Scheme
+
+SET (core_utils_SCHEME core-utils.scm)
+
+SET(GUILE_OUTPUT_DIR gnucash)
+SET(GUILE_MODULES "")
+SET(GUILE_LOAD_DIRS libgnucash/core-utils)
+SET(GUILE_LIBRARY_DIRS libgnucash/core-utils)
+SET(GUILE_DEPENDS gnc-core-utils)
+
+GNC_ADD_SCHEME_TARGETS(scm-core-utils
+ "${core_utils_SCHEME}"
+ ${GUILE_OUTPUT_DIR}
+ "${GUILE_MODULES}"
+ "${GUILE_LOAD_DIRS}"
+ "${GUILE_LIBRARY_DIRS}"
+ "${GUILE_DEPENDS}"
+ FALSE
+)
+
diff --cc libgnucash/core-utils/Makefile.am
index c9830f6,0000000..b54f2dd
mode 100644,000000..100644
--- a/libgnucash/core-utils/Makefile.am
+++ b/libgnucash/core-utils/Makefile.am
@@@ -1,196 -1,0 +1,197 @@@
+# No SUBDIR variable here...
+# The 'test' subdir is added via src/Makefile to handle
+# it's dependency on src/engine
+
+lib_LTLIBRARIES = libgnc-core-utils.la
+
+libgnc_core_utils_la_SOURCES = \
+ binreloc.c \
+ gnc-prefs.c \
+ gnc-environment.c \
+ gnc-filepath-utils.cpp \
+ gnc-gkeyfile-utils.c \
+ gnc-glib-utils.c \
+ gnc-guile-utils.c \
+ gnc-jalali.c \
+ gnc-locale-utils.c \
+ gnc-path.c \
+ swig-core-utils-guile.c
+
+libgnc_core_utils_la_LIBADD = \
+ ${GUILE_LIBS} \
+ ${GLIB_LIBS} \
+ ${BINRELOC_LIBS} \
+ ${GTK_MAC_LIBS} \
+ ${BOOST_LDFLAGS} -lboost_filesystem
+
+
+noinst_HEADERS = \
+ binreloc.h \
+ gnc-prefs.h \
+ gnc-prefs-p.h \
+ gnc-environment.h \
+ gnc-filepath-utils.h \
+ gnc-gkeyfile-utils.h \
+ gnc-glib-utils.h \
+ gnc-guile-utils.h \
+ gnc-jalali.h \
+ gnc-locale-utils.h \
+ gnc-path.h
+
+if BUILDING_FROM_VCS
+swig-core-utils-guile.c: core-utils.i ${top_srcdir}/common/base-typemaps.i
+ $(SWIG) -guile $(SWIG_ARGS) -Linkage module \
+ -I${top_srcdir}/common -o $@ $<
+if ! OS_WIN32
+if ! SWIG_DIST_FAIL
+ if ! `grep "define scm_from_utf8_string" $@ > /dev/null 2>&1`; then \
+ patch $@ $(top_srcdir)/common/swig-utf8.patch; \
+ fi
+endif
+endif
+swig-core-utils-python.c: core-utils.i ${top_srcdir}/common/base-typemaps.i
+ $(SWIG) -python -Wall -Werror $(SWIG_ARGS) \
+ -I${top_srcdir}/common -o $@ $<
+endif
+
+AM_CPPFLAGS = \
+ ${GUILE_CFLAGS} \
+ ${GLIB_CFLAGS} \
+ ${GTK_MAC_CFLAGS} \
+ -I${top_builddir}/common \
+ -I${top_srcdir}/common \
+ $(BOOST_CPPFLAGS)
+
+gncscmmoddir = ${GNC_SCM_INSTALL_DIR}/gnucash
+gncscmmod_DATA = core-utils.scm
+
+if GNUCASH_SEPARATE_BUILDDIR
+#Only needed when srcdir and builddir are different
+#for running
+SCM_FILE_LINKS = ${gncscmmod_DATA}
+endif
+
+if WITH_PYTHON
+
+lib_LTLIBRARIES += libgnc-core-utils-python.la
+
+libgnc_core_utils_python_la_SOURCES = \
+ swig-core-utils-python.c
+
+libgnc_core_utils_python_la_LIBADD = \
+ ${PYTHON_LIBS} \
+ libgnc-core-utils.la
+
+libgnc_core_utils_python_la_CPPFLAGS = \
+ ${GLIB_CFLAGS} \
+ ${GTK_MAC_CFLAGS} \
+ ${PYTHON_CPPFLAGS} \
+ -I${top_builddir}/common \
+ -I${top_srcdir}/common
+
+endif
+
+.scm-links:
+ $(RM) -rf gnucash
+ mkdir -p gnucash
+if GNUCASH_SEPARATE_BUILDDIR
+ for X in ${SCM_FILE_LINKS} ; do \
+ $(LN_S) -f ${srcdir}/$$X . ; \
+ done
+endif
+ ( cd gnucash; for A in $(gncscmmod_DATA) ; do $(LN_S) -f ../$$A . ; done )
+if ! OS_WIN32
+# Windows knows no "ln -s" but uses "cp": must copy every time (see bug #566567).
+ touch .scm-links
+endif
+
+if GNC_HAVE_GUILE_2
+GUILE_COMPILE_ENV = \
+ --library-dir ${top_builddir}/libgnucash/core-utils
+
+%.go : %.scm .scm-links $(lib_LTLIBRARIES)
+ GNC_UNINSTALLED=yes \
+ GNC_BUILDDIR=${top_builddir} \
+ $(shell ${abs_top_srcdir}/common/gnc-test-env.pl --noexports ${GUILE_COMPILE_ENV}) \
+ $(GUILD) compile -o $@ $<
+
+gncscmmodcachedir = ${pkglibdir}/scm/ccache/@GUILE_EFFECTIVE_VERSION@/gnucash
+gncscmmodcache_DATA = $(gncscmmod_DATA:.scm=.go)
+endif
+
+noinst_DATA = .scm-links
+BUILT_SOURCES = gncla-dir.h gnc-version.h
+EXTRA_DIST = \
+ $(gncscmmod_DATA) \
+ core-utils.i \
+ gncla-dir.h.in \
+ gnc-vcs-info.h \
+ CMakeLists.txt
+
+clean-local:
+ rm -rf gnucash
+
+CLEANFILES = $(BUILT_SOURCES) .scm-links ${gncscmmodcache_DATA}
+
+MAINTAINERCLEANFILES = swig-core-utils-guile.c swig-core-utils-python.c
+
+AM_CPPFLAGS += -DG_LOG_DOMAIN=\"gnc.core-utils\" -DGNC_SCM_INSTALL_DIR="\"${GNC_SCM_INSTALL_DIR}\""
+
+gncla-dir.h: gncla-dir.h.in ${top_builddir}/config.status Makefile
+ rm -f $@.tmp
+ sed < $< > $@.tmp \
+ -e 's#@-DATADIRNAME-@#${DATADIRNAME}#g' \
+ -e 's#@-libdir-@#${libdir}#g' \
+ -e 's#@-bindir-@#${bindir}#g' \
+ -e 's#@-sysconfdir-@#${sysconfdir}#g' \
+ -e 's#@-datadir-@#${datadir}#g' \
+ -e 's#@-prefix-@#${prefix}#g'
+ mv $@.tmp $@
+
+## We borrow guile's convention and use @-...-@ as the substitution
+## brackets here, instead of the usual @... at . This prevents autoconf
+## from substituting the values directly into the left-hand sides of
+## the sed substitutions.
+gnc-version.h: _gnc-version.h
+ -if [ ! -f gnc-version.h ]; then cp _gnc-version.h gnc-version.h; fi
+ -cmp -s _gnc-version.h gnc-version.h || cp _gnc-version.h gnc-version.h
+ -rm -f _gnc-version.h
+
+_gnc-version.h: gnc-vcs-info.h Makefile
+ @echo "/* Autogenerated. Do not change. */" > _gnc-version.h
+ @echo "#ifndef GNC_VERSION_H" >> _gnc-version.h
+ @echo "#define GNC_VERSION_H" >> _gnc-version.h
+ @echo "" >> _gnc-version.h
+ @scm_info=`${top_srcdir}/util/gnc-vcs-info -t ${top_srcdir}` ; \
+ if [ $$? = 0 ] ; then \
+ echo "#define GNUCASH_SCM \"$$scm_info\"" >> _gnc-version.h ; \
+ fi
+ @echo "#define GNUCASH_BUILD_DATE \"`date +%Y-%m-%d`\"" >> _gnc-version.h
++ @echo "#define GNUCASH_BUILD_YEAR \"`date +%Y`\"" >> _gnc-version.h
+ @echo "#include \"gnc-vcs-info.h\"" >> _gnc-version.h
+ @echo "#endif" >> _gnc-version.h
+
+gnc-vcs-info.h: _gnc-vcs-info.h
+ -if [ ! -f gnc-vcs-info.h ]; then cp _gnc-vcs-info.h gnc-vcs-info.h; fi
+ -cmp -s _gnc-vcs-info.h gnc-vcs-info.h || cp _gnc-vcs-info.h gnc-vcs-info.h
+ -rm -f _gnc-vcs-info.h
+
+_gnc-vcs-info.h: Makefile
+ @scm_info=`${top_srcdir}/util/gnc-vcs-info -r ${top_srcdir}` ; \
+ if [ $$? = 0 ] ; then \
+ if [ -z "$$scm_info" ] ; then \
+ echo "gnc-vcs-info failed. figure out why." ; \
+ echo "can't determine svn/svk/git/bzr revision from ${top_srcdir}." ; \
+ exit 1 ; \
+ fi ; \
+ echo "/* Autogenerated. Do not change. */" > _gnc-vcs-info.h ; \
+ echo "#define GNUCASH_SCM_REV \"$$scm_info\"" >> _gnc-vcs-info.h ; \
+ else \
+ if [ -r $(srcdir)/gnc-vcs-info.h ] ; then \
+ cp $(srcdir)/gnc-vcs-info.h _gnc-vcs-info.h ; \
+ else \
+ echo "You're building from svn/svk/git/bzr... But your build system is broken" ; \
+ echo "Don't do that. Complain to your build-system creator." ; \
+ exit 1 ; \
+ fi ; \
+ fi
commit 6ec9aa362b647f6d24fd3836e3fee7117f6613e7
Author: Rob Gowin <robgowin at gmail.com>
Date: Mon Sep 11 15:44:41 2017 -0500
Fix issues with loading and running init.py
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 70d3380..94f74c1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -383,6 +383,20 @@ IF (WITH_PYTHON)
IF (NOT PYTHONLIBS_FOUND)
MESSAGE(SEND_ERROR "Python support enabled, but Python libraries not found.")
ENDIF()
+
+ # Determine where to install the python libraries.
+ EXECUTE_PROCESS(
+ COMMAND ${PYTHON_EXECUTABLE} -c "import sysconfig; print sysconfig.get_path('platlib', vars = { 'platbase' : '${CMAKE_INSTALL_PREFIX}' } )"
+ RESULT_VARIABLE PYTHON_SYSCONFIG_RESULT
+ OUTPUT_VARIABLE PYTHON_SYSCONFIG_OUTPUT
+ ERROR_VARIABLE PYTHON_SYSCONFIG_ERROR
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_STRIP_TRAILING_WHITESPACE
+ )
+ IF (PYTHON_SYSCONFIG_RESULT)
+ MESSAGE(SEND_ERROR "Could not determine Python site-package directory:\n${PYTHON_SYSCONFIG_ERROR}")
+ ENDIF()
+ STRING(REPLACE ${CMAKE_INSTALL_PREFIX} ${CMAKE_BINARY_DIR} PYTHON_SYSCONFIG_BUILD ${PYTHON_SYSCONFIG_OUTPUT})
ENDIF()
diff --git a/src/bin/CMakeLists.txt b/src/bin/CMakeLists.txt
index 9599994..c552e96 100644
--- a/src/bin/CMakeLists.txt
+++ b/src/bin/CMakeLists.txt
@@ -105,7 +105,7 @@ ENDIF()
IF (NOT(${DATADIR} STREQUAL "/usr/share") AND NOT(${DATADIR} STREQUAL "/usr/local/share"))
FILE(APPEND ${ENV_FILE_OUT} ${XDG_TEXT})
- FILE(APPEND ${ENV_FILE_OUT} "XDG_DATA_DIRS=${DATADIR};{XDG_DATA_DIRS}" "${GNC_SYSTEM_XDG_DATA_DIRS}\n")
+ FILE(APPEND ${ENV_FILE_OUT} "XDG_DATA_DIRS=${DATADIR};{XDG_DATA_DIRS};" "${GNC_SYSTEM_XDG_DATA_DIRS}\n")
ENDIF()
FILE(APPEND ${BUILD_ENV_FILE_OUT} "GNC_DBD_DIR=${LIBDBI_DRIVERS_DIR}/dbd")
@@ -113,6 +113,19 @@ FILE(APPEND ${BUILD_ENV_FILE_OUT} "GNC_DBD_DIR=${LIBDBI_DRIVERS_DIR}/dbd")
FILE(APPEND ${BUILD_ENV_FILE_OUT} ${XDG_TEXT})
FILE(APPEND ${BUILD_ENV_FILE_OUT} "XDG_DATA_DIRS=${DATADIR_BUILD};{XDG_DATA_DIRS};" "${GNC_SYSTEM_XDG_DATA_DIRS}\n")
+SET(PYTHON_TEXT "
+# Define PYTHONPATH for non default installation path.\n"
+ )
+IF (NOT(${CMAKE_INSTALL_PREFIX} STREQUAL "/usr") AND NOT(${CMAKE_INSTALL_PREFIX} STREQUAL "/usr/local"))
+
+ FILE(APPEND ${ENV_FILE_OUT} ${PYTHON_TEXT})
+ FILE(APPEND ${ENV_FILE_OUT} "PYTHONPATH=${PYTHON_SYSCONFIG_OUTPUT};{PYTHONPATH}")
+ENDIF()
+
+FILE(APPEND ${BUILD_ENV_FILE_OUT} ${PYTHON_TEXT})
+FILE(APPEND ${BUILD_ENV_FILE_OUT} "PYTHONPATH=${PYTHON_SYSCONFIG_BUILD};{PYTHONPATH}")
+
+
FILE(COPY ${BUILD_ENV_FILE_OUT}
DESTINATION ${SYSCONFDIR_BUILD}/gnucash
FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
diff --git a/src/optional/python-bindings/CMakeLists.txt b/src/optional/python-bindings/CMakeLists.txt
index eb1e6e9..92ea1d8 100644
--- a/src/optional/python-bindings/CMakeLists.txt
+++ b/src/optional/python-bindings/CMakeLists.txt
@@ -80,35 +80,24 @@ IF(WITH_PYTHON)
ADD_TEST(NAME sqlite3test COMMAND sqlite3test)
ADD_DEPENDENCIES(check sqlite3test)
-
- # Determine where to install the python libraries.
- EXECUTE_PROCESS(
- COMMAND ${PYTHON_EXECUTABLE} -c "import sysconfig; print sysconfig.get_path('platlib', vars = { 'platbase' : '${CMAKE_INSTALL_PREFIX}' } )"
- RESULT_VARIABLE PYTHON_SYSCONFIG_RESULT
- OUTPUT_VARIABLE PYTHON_SYSCONFIG_OUTPUT
- ERROR_VARIABLE PYTHON_SYSCONFIG_ERROR
- OUTPUT_STRIP_TRAILING_WHITESPACE
- ERROR_STRIP_TRAILING_WHITESPACE
- )
- IF (PYTHON_SYSCONFIG_RESULT)
- MESSAGE(SEND_ERROR "Could not determine Python site-package directory:\n${PYTHON_SYSCONFIG_ERROR}")
- ENDIF()
-
INSTALL(TARGETS gnucash_core_c
LIBRARY DESTINATION ${PYTHON_SYSCONFIG_OUTPUT}/gnucash
ARCHIVE DESTINATION ${PYTHON_SYSCONFIG_OUTPUT}/gnucash
)
- INSTALL(FILES __init__.py function_class.py gnucash_business.py gnucash_core.py
- ${CMAKE_CURRENT_BINARY_DIR}/gnucash_core_c.py
+ INSTALL(FILES ${PYEXEC_FILES} ${CMAKE_CURRENT_BINARY_DIR}/gnucash_core_c.py
DESTINATION ${PYTHON_SYSCONFIG_OUTPUT}/gnucash
)
- FILE(COPY ${PYEXEC_FILES} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/gnucash)
+ FILE(COPY ${PYEXEC_FILES} DESTINATION ${PYTHON_SYSCONFIG_BUILD}/gnucash)
ADD_CUSTOM_TARGET(gnucash-core-c-py ALL
- COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/gnucash_core_c.py ${CMAKE_CURRENT_BINARY_DIR}/gnucash
+ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/gnucash_core_c.py ${PYTHON_SYSCONFIG_BUILD}/gnucash
DEPENDS ${SWIG_GNUCASH_CORE_C})
+ ADD_CUSTOM_TARGET(gnucash-core-c-build ALL
+ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/lib/gnucash/_gnucash_core_c${CMAKE_SHARED_LIBRARY_SUFFIX} ${PYTHON_SYSCONFIG_BUILD}/gnucash
+ DEPENDS gnucash_core_c)
+
ENDIF()
SET(python_bindings_DATA ${PYEXEC_FILES}
@@ -118,4 +107,4 @@ SET(python_bindings_DATA ${PYEXEC_FILES}
SET_LOCAL_DIST(python_bindings_DIST_local CMakeLists.txt Makefile.am ${python_bindings_DATA})
-SET(python_bindings_DIST ${python_bindings_DIST_local} ${test_python_bindings_DIST} ${example_scripts_DIST} PARENT_SCOPE)
\ No newline at end of file
+SET(python_bindings_DIST ${python_bindings_DIST_local} ${test_python_bindings_DIST} ${example_scripts_DIST} PARENT_SCOPE)
diff --git a/src/optional/python-bindings/tests/CMakeLists.txt b/src/optional/python-bindings/tests/CMakeLists.txt
index f900fa1..78b244d 100644
--- a/src/optional/python-bindings/tests/CMakeLists.txt
+++ b/src/optional/python-bindings/tests/CMakeLists.txt
@@ -3,7 +3,7 @@ IF (WITH_PYTHON)
ADD_TEST(python-bindings ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/runTests.py.in)
SET_PROPERTY(TEST python-bindings PROPERTY ENVIRONMENT
GNC_BUILDDIR=${CMAKE_BINARY_DIR}
- PYTHONPATH=${CMAKE_BINARY_DIR}/src/optional/python-bindings:${CMAKE_BINARY_DIR}/lib/gnucash:${CMAKE_BINARY_DIR}/src/test-core
+ PYTHONPATH=${PYTHON_SYSCONFIG_BUILD}:${CMAKE_BINARY_DIR}/lib/gnucash:${CMAKE_BINARY_DIR}/src/test-core
)
ENDIF()
@@ -15,4 +15,4 @@ SET(test_python_bindings_DATA
test_split.py
test_transaction.py)
-SET_DIST_LIST(test_python_bindings_DIST CMakeLists.txt Makefile.am ${test_python_bindings_DATA})
\ No newline at end of file
+SET_DIST_LIST(test_python_bindings_DIST CMakeLists.txt Makefile.am ${test_python_bindings_DATA})
diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt
index 6698f3f..79d27ba 100644
--- a/src/python/CMakeLists.txt
+++ b/src/python/CMakeLists.txt
@@ -16,7 +16,11 @@ IF (WITH_PYTHON)
RUNTIME DESTINATION bin
)
- INSTALL(FILES init.py DESTINATION share/python)
+ INSTALL(DIRECTORY pycons DESTINATION share/gnucash/python)
+ INSTALL(FILES init.py DESTINATION share/gnucash/python)
+
+ FILE(COPY init.py DESTINATION ${CMAKE_BINARY_DIR}/share/gnucash/python)
+ FILE(COPY pycons DESTINATION ${CMAKE_BINARY_DIR}/share/gnucash/python)
ENDIF(WITH_PYTHON)
SET_LOCAL_DIST(python_DIST_local CMakeLists.txt Makefile.am gncmod-python.c init.py)
commit 65f18589da0f9be5749469ab73895fdf6917e7be
Author: fell <frank.h.ellenberger at gmail.com>
Date: Mon Sep 11 04:40:22 2017 +0200
Update changed string in de.po
diff --git a/po/de.po b/po/de.po
index 85cf949..a5d6b6f 100644
--- a/po/de.po
+++ b/po/de.po
@@ -15,8 +15,8 @@ msgid ""
msgstr ""
"Project-Id-Version: GnuCash 2.6.15\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-09-10 20:53+0200\n"
-"PO-Revision-Date: 2017-09-09 21:33+0200\n"
+"POT-Creation-Date: 2017-09-11 02:57+0200\n"
+"PO-Revision-Date: 2017-09-11 04:33+0200\n"
"Last-Translator: Mechtilde <ooo at mechtilde.de>\n"
"Language-Team: GnuCash-de <gnucash-de at gnucash.org>\n"
"Language: de\n"
@@ -1205,7 +1205,7 @@ msgstr "GnuCash Entwicklungsversion %s"
#. 2nd %s is the scm type (svn/svk/git/bzr);
#. 3rd %s is the scm revision number;
#. 4th %s is the build date
-#: ../src/bin/gnucash-bin.c:452 ../src/gnome-utils/gnc-main-window.c:4385
+#: ../src/bin/gnucash-bin.c:452 ../src/gnome-utils/gnc-main-window.c:4386
#, c-format
msgid ""
"%s\n"
@@ -1222,7 +1222,7 @@ msgstr "GnuCash %s"
#. Translators: 1st %s is a fixed message, which is translated independently;
#. 2nd %s is the scm (svn/svk/git/bzr) revision number;
#. 3rd %s is the build date
-#: ../src/bin/gnucash-bin.c:463 ../src/gnome-utils/gnc-main-window.c:4392
+#: ../src/bin/gnucash-bin.c:463 ../src/gnome-utils/gnc-main-window.c:4393
#, c-format
msgid ""
"%s\n"
@@ -7145,9 +7145,9 @@ msgid ""
"manual page of strftime by \"man 3 strftime\"."
msgstr ""
"Wenn das Datumsformat auf Benutzerdefiniert gesetzt ist, kann in dieser "
-"Ausdruck als Argument an das strftime-Format übergegeben werden. Es kann eine "
-"beliebige gültige strftime Zeichenkette sein. Für mehr Informationen lesen "
-"Sie die Ausgabe von »man 3 strftime«."
+"Ausdruck als Argument an das strftime-Format übergegeben werden. Es kann "
+"eine beliebige gültige strftime Zeichenkette sein. Für mehr Informationen "
+"lesen Sie die Ausgabe von »man 3 strftime«."
#: ../src/gnome/gschemas/org.gnucash.dialogs.checkprinting.gschema.xml.in.in.h:10
msgid "Units in which the custom coordinates are expressed"
@@ -12133,14 +12133,15 @@ msgstr ""
"GnuCash: Ihr privater Finanzmanager. Die freie Lösung zur Finanzverwaltung."
#: ../src/gnome-utils/gnc-main-window.c:4372
-msgid "© 1997-2017 Contributors"
-msgstr "© 1997-2017 Mitwirkende"
+#, c-format
+msgid "© 1997-%s Contributors"
+msgstr "© 1997-%s Mitwirkende"
#. Translators: the following string will be shown in Help->About->Credits
#. * Enter your name or that of your team and an email contact for feedback.
#. * The string can have multiple rows, so you can also add a list of
#. * contributors.
-#: ../src/gnome-utils/gnc-main-window.c:4409
+#: ../src/gnome-utils/gnc-main-window.c:4410
msgid "translator_credits"
msgstr "Christian Stimming <christian at cstimming.de> et. al."
commit 2275603eb4cb3725cac79e2955a9c68b0617f318
Author: fell <frank.h.ellenberger at gmail.com>
Date: Mon Sep 11 04:34:02 2017 +0200
Fix error in recent commit
diff --git a/src/core-utils/CMakeLists.txt b/src/core-utils/CMakeLists.txt
index b379fe3..26089f0 100644
--- a/src/core-utils/CMakeLists.txt
+++ b/src/core-utils/CMakeLists.txt
@@ -67,7 +67,7 @@ SET (GNC_VERSION_H_IN
")
STRING(TIMESTAMP GNUCASH_BUILD_DATE "%Y-%m-%d")
-STRING(TIMESTAMP GNUCASH_BUILD_DATE "%Y")
+STRING(TIMESTAMP GNUCASH_BUILD_YEAR "%Y")
STRING(CONFIGURE ${GNC_VERSION_H_IN} GNC_VERSION_H_CONTENT)
commit 240bc32e57098344a979791b38c4517e337a0fb0
Author: fell <frank.h.ellenberger at gmail.com>
Date: Mon Sep 11 02:40:03 2017 +0200
Remove build_year from translatable copyright string
diff --git a/src/core-utils/CMakeLists.txt b/src/core-utils/CMakeLists.txt
index c48f7ee..b379fe3 100644
--- a/src/core-utils/CMakeLists.txt
+++ b/src/core-utils/CMakeLists.txt
@@ -61,11 +61,13 @@ SET (GNC_VERSION_H_IN
#define GNUCASH_SCM \"git\"
#define GNUCASH_BUILD_DATE \"@GNUCASH_BUILD_DATE@\"
+#define GNUCASH_BUILD_YEAR \"@GNUCASH_BUILD_YEAR@\"
#include \"gnc-vcs-info.h\"
#endif
")
STRING(TIMESTAMP GNUCASH_BUILD_DATE "%Y-%m-%d")
+STRING(TIMESTAMP GNUCASH_BUILD_DATE "%Y")
STRING(CONFIGURE ${GNC_VERSION_H_IN} GNC_VERSION_H_CONTENT)
diff --git a/src/core-utils/Makefile.am b/src/core-utils/Makefile.am
index 514eb9d..8f2f70b 100644
--- a/src/core-utils/Makefile.am
+++ b/src/core-utils/Makefile.am
@@ -176,6 +176,7 @@ _gnc-version.h: gnc-vcs-info.h Makefile
echo "#define GNUCASH_SCM \"$$scm_info\"" >> _gnc-version.h ; \
fi
@echo "#define GNUCASH_BUILD_DATE \"`date +%Y-%m-%d`\"" >> _gnc-version.h
+ @echo "#define GNUCASH_BUILD_YEAR \"`date +%Y`\"" >> _gnc-version.h
@echo "#include \"gnc-vcs-info.h\"" >> _gnc-version.h
@echo "#endif" >> _gnc-version.h
diff --git a/src/gnome-utils/gnc-main-window.c b/src/gnome-utils/gnc-main-window.c
index ee37483..347805a 100644
--- a/src/gnome-utils/gnc-main-window.c
+++ b/src/gnome-utils/gnc-main-window.c
@@ -4369,7 +4369,8 @@ gnc_main_window_cmd_help_about (GtkAction *action, GncMainWindow *window)
{
const gchar *fixed_message = _("The GnuCash personal finance manager. "
"The GNU way to manage your money!");
- const gchar *copyright = _("© 1997-2017 Contributors");
+ gchar *copyright = g_strdup_printf(_("© 1997-%s Contributors"),
+ GNUCASH_BUILD_YEAR);
gchar **authors = get_file_strsplit("AUTHORS");
gchar **documenters = get_file_strsplit("DOCUMENTERS");
gchar *license = get_file("LICENSE");
@@ -4412,6 +4413,7 @@ gnc_main_window_cmd_help_about (GtkAction *action, GncMainWindow *window)
NULL);
g_free(message);
+ g_free(copyright);
if (license) g_free(license);
if (documenters) g_strfreev(documenters);
if (authors) g_strfreev(authors);
commit 6c8275a7ffcd9d9a7e9c4daac155a45a418ced0a
Author: fell <frank.h.ellenberger at gmail.com>
Date: Sun Sep 10 21:49:26 2017 +0200
Review of PR #183
4612 translated messages, 25 fuzzy translations, 48 untranslated
messages.
diff --git a/po/de.po b/po/de.po
index e8a618b..85cf949 100644
--- a/po/de.po
+++ b/po/de.po
@@ -15,7 +15,7 @@ msgid ""
msgstr ""
"Project-Id-Version: GnuCash 2.6.15\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-09-04 00:02+0200\n"
+"POT-Creation-Date: 2017-09-10 20:53+0200\n"
"PO-Revision-Date: 2017-09-09 21:33+0200\n"
"Last-Translator: Mechtilde <ooo at mechtilde.de>\n"
"Language-Team: GnuCash-de <gnucash-de at gnucash.org>\n"
@@ -7145,7 +7145,7 @@ msgid ""
"manual page of strftime by \"man 3 strftime\"."
msgstr ""
"Wenn das Datumsformat auf Benutzerdefiniert gesetzt ist, kann in dieser "
-"Ausdruckals Argument an das strftime-Format übergegeben werden. Es kann eine "
+"Ausdruck als Argument an das strftime-Format übergegeben werden. Es kann eine "
"beliebige gültige strftime Zeichenkette sein. Für mehr Informationen lesen "
"Sie die Ausgabe von »man 3 strftime«."
@@ -8322,7 +8322,8 @@ msgid ""
msgstr ""
"Auf hochauflösenden Bildschirmen können Berichte schwer zu lesen sein.\n"
"Diese Option erlaubt das Skalieren der Berichte um den gewählten Faktor.\n"
-"Z.B. wird ein Wert von 2 die Berichte mit dem Doppelten ihrer ursprünglichen GröÃe darstellen."
+"Z.B. wird ein Wert von 2 die Berichte mit dem Doppelten ihrer ursprünglichen "
+"GröÃe darstellen."
#: ../src/gnome/gschemas/org.gnucash.gschema.xml.in.in.h:113
msgid "PDF export file name format"
@@ -13417,7 +13418,7 @@ msgstr "Be_merkung:"
msgid "Ta_x related"
msgstr "Steuer_relevant"
-#. Translators: use the same words here as in
+#. Translators: use the same words here as in 'Ta_x Report Options'.
#: ../src/gnome-utils/gtkbuilder/dialog-account.glade.h:21
msgid ""
"Use Edit->Tax Report Options to set the tax-related flag and assign a tax "
@@ -14809,6 +14810,26 @@ msgstr "Nicht vorgemerkt"
msgid "Select occurrence date above."
msgstr "Wählen Sie oben ein Datum des Auftretens."
+#: ../src/gnome-utils/gtkbuilder/gnc-frequency.glade.h:7
+msgctxt "Daily"
+msgid "Every"
+msgstr "Alle"
+
+#: ../src/gnome-utils/gtkbuilder/gnc-frequency.glade.h:8
+msgctxt "Daily"
+msgid "days."
+msgstr "Tage."
+
+#: ../src/gnome-utils/gtkbuilder/gnc-frequency.glade.h:10
+msgctxt "Weekly"
+msgid "Every"
+msgstr "Alle"
+
+#: ../src/gnome-utils/gtkbuilder/gnc-frequency.glade.h:11
+msgctxt "Weekly"
+msgid "weeks."
+msgstr "Wochen."
+
#: ../src/gnome-utils/gtkbuilder/gnc-frequency.glade.h:12
#: ../src/report/standard-reports/daily-reports.scm:355
msgid "Saturday"
@@ -14850,6 +14871,16 @@ msgstr "Montag"
msgid "Tuesday"
msgstr "Dienstag"
+#: ../src/gnome-utils/gtkbuilder/gnc-frequency.glade.h:21
+msgctxt "Semimonthly"
+msgid "Every"
+msgstr "Alle"
+
+#: ../src/gnome-utils/gtkbuilder/gnc-frequency.glade.h:22
+msgctxt "Semimonthly"
+msgid "months."
+msgstr "Monate."
+
#: ../src/gnome-utils/gtkbuilder/gnc-frequency.glade.h:23
msgid "First on the:"
msgstr "Zuerst am:"
@@ -14866,6 +14897,16 @@ msgstr "dann am:"
msgid "Semi-Monthly"
msgstr "Halbmonatlich"
+#: ../src/gnome-utils/gtkbuilder/gnc-frequency.glade.h:27
+msgctxt "Monthly"
+msgid "Every"
+msgstr "Alle"
+
+#: ../src/gnome-utils/gtkbuilder/gnc-frequency.glade.h:28
+msgctxt "Monthly"
+msgid "months."
+msgstr "Monate."
+
#: ../src/gnome-utils/gtkbuilder/gnc-frequency.glade.h:29
msgid "On the"
msgstr "Am"
@@ -26180,38 +26221,6 @@ msgstr ""
#~ "Sie müssen eine Devise/Wertpapier auswählen. Wenn Sie eine neue anlegen "
#~ "möchten, klicken Sie auf »Neu«."
-#~ msgctxt "Daily"
-#~ msgid "Every"
-#~ msgstr "Alle"
-
-#~ msgctxt "Daily"
-#~ msgid "days."
-#~ msgstr "Tage."
-
-#~ msgctxt "Weekly"
-#~ msgid "Every"
-#~ msgstr "Alle"
-
-#~ msgctxt "Weekly"
-#~ msgid "weeks."
-#~ msgstr "Wochen."
-
-#~ msgctxt "Semimonthly"
-#~ msgid "Every"
-#~ msgstr "Alle"
-
-#~ msgctxt "Semimonthly"
-#~ msgid "months."
-#~ msgstr "Monate."
-
-#~ msgctxt "Monthly"
-#~ msgid "Every"
-#~ msgstr "Alle"
-
-#~ msgctxt "Monthly"
-#~ msgid "months."
-#~ msgstr "Monate."
-
#~ msgid "set true"
#~ msgstr "auf wahr setzen"
Summary of changes:
CMakeLists.txt | 14 +++++
bindings/python/CMakeLists.txt | 27 +++-------
bindings/python/tests/CMakeLists.txt | 2 +-
gnucash/CMakeLists.txt-bin | 15 +++++-
gnucash/gnome-utils/gnc-main-window.c | 4 +-
gnucash/python/CMakeLists.txt | 6 ++-
libgnucash/core-utils/CMakeLists.txt | 2 +
libgnucash/core-utils/Makefile.am | 1 +
po/de.po | 98 +++++++++++++++++++----------------
util/ci/arch-docker | 2 -
util/ci/commonbuild | 2 +-
util/ci/ubuntu-14.04-docker | 2 -
12 files changed, 103 insertions(+), 72 deletions(-)
More information about the gnucash-changes
mailing list