/po/POTFILES
/po/stamp-it
/src/.deps
+/src/ext-perl.c
/src/homebank
+/src/perlxsi.c
/stamp-h1
Makefile
Makefile.in
# HomeBank Makefile.am
+ACLOCAL_AMFLAGS = -I m4
+
#SUBDIRS = src
-SUBDIRS = src data images mime po doc
+SUBDIRS = src data images mime po doc plugins
# don't forget to do a 'make check'
intltool-merge \
intltool-update \
po/.intltool-merge-cache
+
+run: all
+ PERL5LIB=src src/homebank
+
+debug: all
+ PERL5LIB=src gdb src/homebank
#!/bin/sh
-aclocal \
+libtoolize \
+&& aclocal \
&& autoheader \
&& automake --gnu --add-missing \
&& autoconf
AM_INIT_AUTOMAKE([1.9 foreign])
+LT_PREREQ([2.2])
+LT_INIT([dlopen])
+AC_CONFIG_MACRO_DIR([m4])
+
# If the source code has changed at all, increment REVISION
# If any interfaces have been added, removed, or changed, increment CURRENT, and set REVISION to 0.
# If any interfaces have been added since the last public release, then increment AGE.
AC_PROG_INTLTOOL
# Checks for libraries.
-PKG_CHECK_MODULES(DEPS, gtk+-2.0 >= 2.24 glib-2.0 >= 2.28)
+PKG_CHECK_MODULES(DEPS, gtk+-2.0 >= 2.24 glib-2.0 >= 2.28 gmodule-2.0 >= 2.28)
AC_SUBST(DEPS_CFLAGS)
AC_SUBST(DEPS_LIBS)
AC_CHECK_LIB(m, pow)
then
AC_CHECK_LIB(ofx, ofx_set_status_cb, OFX_0_7="-DOFX_ENABLE")
DEPS_LIBS="-lofx ${DEPS_LIBS}"
- CFLAGS="${CFLAGS} $OFX_0_7"
+ CPPFLAGS="${CPPFLAGS} $OFX_0_7"
else
noofx=true
AC_MSG_RESULT([Libofx header missing. Check your libofx installation])
fi
AM_CONDITIONAL(NOOFX, test x$noofx = xtrue)
+AC_ARG_WITH(perl,
+ [ --with-perl build with perl plug-in support [default=without]],
+ [build_perl=$withval],
+ [build_perl=no]
+)
+if test x$build_perl != xno
+then
+ test x$build_perl != xyes -a -x "$build_perl" && PERL=$build_perl
+ AC_PATH_PROG(PERL, perl, perl)
+ AC_MSG_CHECKING(if perl can be embedded)
+ if $PERL -MExtUtils::Embed -e "use v5.8" >/dev/null 2>&1
+ then
+ AC_MSG_RESULT(yes)
+ CPPFLAGS="${CPPFLAGS} -DPERL_ENABLE"
+ PERL_CPPFLAGS="`$PERL -MExtUtils::Embed -e ccopts`"
+ PERL_OBJS="ext-perl.o perlxsi.o"
+ PERL_PRIVLIBEXP="`$PERL -MConfig -e 'print $Config{privlibexp}'`"
+ PERL_SITELIBEXP="`$PERL -MConfig -e 'print $Config{sitelibexp}'`"
+ DEPS_LIBS="`$PERL -MExtUtils::Embed -e ldopts` ${DEPS_LIBS}"
+ if test -e "$PERL_SITELIBEXP/ExtUtils/xsubpp"
+ then
+ XSUBPP="$PERL $PERL_SITELIBEXP/ExtUtils/xsubpp"
+ else
+ XSUBPP="$PERL $PERL_PRIVLIBEXP/ExtUtils/xsubpp"
+ fi
+ else
+ AC_MSG_ERROR([no working perl found, or perl not version >= 5.8])
+ fi
+fi
+AC_SUBST(PERL_CPPFLAGS)
+AC_SUBST(PERL_OBJS)
+AC_SUBST(PERL_PRIVLIBEXP)
+AC_SUBST(PERL_SITELIBEXP)
+AC_SUBST(XSUBPP)
+
# Checks for header files.
AC_HEADER_STDC
AC_CHECK_HEADERS([libintl.h locale.h stdlib.h string.h])
po/Makefile.in
doc/Makefile
doc/images/Makefile
+plugins/Makefile
])
AC_OUTPUT
echo
echo Compiler................ : $CC
echo Build with OFX support.. : $build_ofx
+echo Build with perl support. : $build_perl
if test "x$noofx" = "xtrue" ; then
echo ........................ : **error** libofx header is missing, ofx feature will be disabled. Check your libofx installation
fi
hicolor_status_48x48_prf-report.png \
hicolor_status_22x22_prf-import.png \
hicolor_status_48x48_prf-import.png \
+ hicolor_status_22x22_prf-plugins.png \
+ hicolor_status_48x48_prf-plugins.png \
$(NULL)
EXTRA_DIST = \
--- /dev/null
+
+plugindir = $(pkglibdir)/plugins
+
+native_la_LDFLAGS = -module -avoid-version -shared -export-dynamic
+
+native_la_SOURCES = native.c
+
+native_la_CPPFLAGS = $(DEPS_CFLAGS) -I$(top_srcdir)/src
+native_la_LIBADD = $(DEPS_LIBS)
+
+plugin_LTLIBRARIES = native.la
+
--- /dev/null
+
+# NAME: Hello World
+# VERSION: 0.01
+# ABSTRACT: This is the "hello world" of HomeBank plugins.
+# AUTHOR: Charles McGarvey <chazmcgarvey@brokenzipper.com>
+# WEBSITE: http://acme.tld/
+# (These comments are read, before the plugin is executed, to provide some
+# information to HomeBank and the user about what this plugin is.)
+
+eval { HomeBank->version } or die "Cannot run outside of HomeBank";
+
+use warnings;
+use strict;
+
+use Scalar::Util qw/weaken/;
+
+#use Moose;
+
+#has "cool_beans",
+ #is => 'rw',
+ #isa => 'Str',
+ #lazy => 1,
+ #default => "Booya!!!";
+
+
+our $counter = 0;
+our $temp;
+
+my $ACC;
+
+sub new {
+ my $class = shift;
+ my $self = $class->SUPER::new(@_);
+
+ $self->on(account_inserted => sub {
+ my $acc = shift;
+ print "account inserted: ", Dumper($acc);
+ print "account name is ", $acc->name, " and balance is ", $acc->bank_balance, "\n";
+ #$acc->name("FOOOOBAR!");
+ if ($acc->name eq 'Vacation') {
+ $acc->remove;
+ $ACC = $acc;
+ }
+ print Dumper($acc->is_inserted);
+ if ($acc->is_inserted) {
+ print "IT IS INSERTED\n";
+ } else {
+ print "not inserted\n";
+ }
+ print Dumper($acc->transactions);
+ });
+
+ #print $self->cool_beans, "\n";
+ #$self->cool_beans(123);
+ #print $self->cool_beans, "\n";
+
+ $self->create_menuitem;
+
+ $self;
+}
+
+sub on_create_main_window {
+ my $self = shift;
+ my $window = shift;
+
+ if (!$window) {
+ require Gtk2;
+ $window = HomeBank->main_window;
+ }
+
+ Dump($window);
+ print Dumper($window);
+ $window->set_title("foo bar baz");
+ print $window->get_title, "\n";
+
+ HomeBank->hook("my_hook", $window);
+
+ $self->create_menuitem;
+}
+
+my $test_win;
+
+sub on_test {
+ my $self = shift;
+ require Gtk2;
+
+ my $window = Gtk2::Window->new('toplevel');
+ use Devel::Peek;
+ Dump($window);
+ print Dumper($window);
+ $window->set_title("Hello World");
+ #$window->signal_connect(delete_event => sub { Gtk2->main_quit });
+ $window->signal_connect(delete_event => sub { undef $test_win });
+
+ my $button = Gtk2::Button->new('Click Me!');
+ Dump($button);
+ print Dumper($button);
+ $button->signal_connect(clicked => sub {
+ print "Hello Gtk2-Perl: $counter (perl plugin: $self)\n";
+ $counter++;
+ #if ($temp->is_inserted) {
+ #print "$temp is inserted\n";
+ #} else {
+ #print "$temp is NOT inserted\n";
+ #}
+ #if ($counter == 5) {
+ #$temp = undef;
+ #}
+ my $acc = HomeBank::Account->get(rand(10));
+ print "Changin account named ", $acc->name, " to ", $acc->name($acc), "\n";
+ HomeBank->main_window->queue_draw;
+
+ });
+ $window->add($button);
+
+ $window->show_all;
+ $test_win = $window;
+
+ weaken $self;
+}
+
+sub on_enter_main_loop {
+ my $self = shift;
+
+ use Data::Dumper;
+ print Dumper(\@_);
+ my $t = HomeBank::Transaction->new;
+ print "Transaction:::::::: $t: ", $t->amount, "\n";
+
+ $temp = HomeBank::Account->get(7);
+ print "retained account: ", $temp->name, "\n";
+
+ #require Gtk2;
+ #
+ my $txn = HomeBank::Transaction->new;
+ $txn->amount(12.3456);
+ print Dumper($txn), $txn->amount, "\n";
+ #$txn->open;
+
+ my @ret = HomeBank->hook("my_hook", @_, $temp, [qw/foo bar baz/, $txn], { asf => 42, quux => \1, meh => HomeBank->main_window });
+ #my @ret = HomeBank->hook("my_hook", @_, HomeBank->main_window, {
+ #foo => 'bar', baz => 42
+ #});
+ print Dumper(\@ret);
+
+ print "adding back account...\n";
+ $ACC->name("vacation with a different name");
+ $ACC->insert;
+ HomeBank::Account->compute_balances;
+ print "account name is ", $ACC->name, " and balance is ", $ACC->balance, "\n";
+ print Dumper($ACC->transactions);
+
+ my $cloned = $ACC->clone;
+ $cloned->name("vacation copy");
+ $cloned->insert;
+ #my $asdf = $cloned->open;
+ #$asdf->set_title("this is a new friggin account");
+
+ #my $z = HomeBank::Account->get_by_name('Checking');
+ for my $xc (HomeBank::File->transactions) {
+ use DateTime;
+ my $num = $xc->date;
+ my $date = DateTime->new($xc->date)->datetime;
+ print "transaction of amount: ", $xc->amount, "\t", $xc->wording, ", ", $xc->info, ", $num, $date\n";
+ }
+
+ HomeBank::File->owner('Billy Murphy');
+ #HomeBank::File->anonymize;
+ print HomeBank::File->owner, "\n";
+
+ HomeBank::File->baz($ACC);
+}
+
+sub on_deep_hook_recursion {
+ my $self = shift;
+ my $level = shift;
+ print STDERR "recursion is too deep ($level)\n";
+ exit -2;
+}
+
+sub on_my_hook {
+ my $self = shift;
+ print "This is MY HOOK!!!!!!\n";
+ print Dumper(\@_);
+
+ print Dumper($_[2]);
+ Dump($_[2]);
+ if ($_[2]) {
+ print "meh\n";
+ }
+ if ($_[2]->isa('HomeBank::Boolean')) {
+ print "it is a home;;boolean\n";
+ }
+ if ($_[2]->isa('Types::Serialiser::Boolean')) {
+ print "it is a types serialiser thingy\n";
+ }
+ if ($_[2]->isa('HomeBank::BooleanBase')) {
+ print "it is a base bool\n";
+ }
+
+ my $win = $_[6];
+ if ($win && ref($win) eq 'HASH') {
+ my $w = $win->{meh};
+ if ($w) {
+ $w->set_title("this is MY HOOK setting a window title");
+ }
+ }
+ #print Dumper($acc);
+ #print "transferred account: ", $acc->name, "\n";
+
+ #my $fff = HomeBank::File->foo({foo => 'asdf', bar => 123456789});
+ my $fff = HomeBank::File->meh([qw/hello this is a test 82/, \1, {foo => 'bar'}, 48]);
+ print Dumper($fff);
+
+ print "my hook done\n";
+}
+
+sub on_unhandled {
+ my ($self, $hook) = @_;
+ warn "Unhandled hook '$hook'\n";
+ #HomeBank->warn($hook, 'Hook not handled.');
+}
+
+sub on_main_window_disposal {
+ my $self = shift;
+ print "main window disposed so forgetting about merge id et al.\n";
+ delete $self->{merge_id};
+ delete $self->{action_group};
+}
+
+sub DESTROY {
+ my $self = shift;
+ print "DESTROYING HELLO WORLD!!!!!!\n";
+ if ($test_win) {
+ print "there is a test_win...\n";
+ }
+ $test_win->destroy if $test_win;
+
+ $self->destroy_menuitem;
+}
+
+sub destroy_menuitem {
+ my $self = shift;
+
+ return unless $self->{merge_id};
+
+ my $ui_manager = HomeBank->main_ui_manager;
+ $ui_manager->remove_action_group($self->{action_group});
+ $ui_manager->remove_ui($self->{merge_id});
+}
+
+sub create_menuitem {
+ my $self = shift;
+
+ return if $self->{merge_id};
+
+ require Gtk2;
+
+ my $ui_manager = HomeBank->main_ui_manager;
+ print Dumper($ui_manager);
+ return unless $ui_manager;
+
+ $self->{merge_id} = $ui_manager->new_merge_id;
+ $self->{action_group} = Gtk2::ActionGroup->new('HelloActionGroup');
+
+ my $action = Gtk2::Action->new(name => 'HelloPlugin', label => 'Booyah!', stock_id => 'prf-plugins', tooltip => 'blaaaargh');
+ $action->signal_connect(activate => sub { print "hello!!!!!!!!\n" });
+ $self->{action_group}->add_action($action);
+
+ $ui_manager->insert_action_group($self->{action_group}, -1);
+ $ui_manager->add_ui($self->{merge_id}, 'ui/MenuBar/PluginMenu', 'HelloPlugin', 'HelloPlugin', 'auto', '');
+ #$self->{merge_id} = $ui_manager->new_merge_id;
+ $ui_manager->add_ui($self->{merge_id}, 'ui/ToolBar', 'HelloPluginTool', 'HelloPlugin', 'auto', '');
+}
+
+sub EXECUTE {
+ print "the perl plugin is being configured.....\n";
+ HomeBank->info("Hello Prefs", "YEEEEEARGGH!!!!!");
+}
+
+#__PACKAGE__->meta->make_immutable;
--- /dev/null
+
+#include <gmodule.h>
+
+#include "ext.h"
+
+
+const gchar* metadata[] = {
+"NAME: Some Native Plugin",
+"VERSION: 0.0105",
+"ABSTRACT: Native plugins are also possible.",
+"AUTHOR: Charles McGarvey <chazmcgarvey@brokenzipper.com>",
+"WEBSITE: http://acme.tld/",
+};
+
+
+G_MODULE_EXPORT void load(void);
+G_MODULE_EXPORT void unload(void);
+G_MODULE_EXPORT void execute(void);
+
+G_MODULE_EXPORT void on_create_main_window(GList* args);
+G_MODULE_EXPORT void on_enter_main_loop(GList* args);
+
+
+G_MODULE_EXPORT void load()
+{
+ g_print("loading native plugin....... %p\n", load);
+}
+
+G_MODULE_EXPORT void unload()
+{
+ g_print("destroy native plugin....... %p\n", unload);
+}
+
+G_MODULE_EXPORT void execute()
+{
+ g_print("Configuring that native plugin!!!\n");
+}
+
+static GtkWidget* win = NULL;
+
+G_MODULE_EXPORT void on_create_main_window(GList* args)
+{
+ GList* it = g_list_first(args);
+ win = g_value_get_object(it->data);
+ /*gtk_window_set_title(GTK_WINDOW(GLOBALS->mainwindow), "This is the native hello-world plugin!");*/
+}
+
+G_MODULE_EXPORT void on_enter_main_loop(GList* args)
+{
+ g_print("setting main window title.....\n");
+ if (win) {
+ gtk_window_set_title(GTK_WINDOW(win), "This is the native hello-world plugin!");
+ } else {
+ g_printerr("the main window is not set :(\n");
+ }
+}
+
--- /dev/null
+
+# NAME: Transfer Matcher
+# VERSION: 0.01
+# ABSTRACT: Automatically find and pair together internal transfers.
+# AUTHOR: Charles McGarvey <chazmcgarvey@brokenzipper.com>
+# WEBSITE: http://www.homebank.free.fr/
+
+eval { HomeBank->version } or die "Cannot run outside of HomeBank";
+
+use warnings FATAL => 'all';
+use strict;
+
+my $days = 3;
+
+sub on_transaction_inserted {
+ my ($self, $txn) = @_;
+
+ my @match = grep {
+ $txn->account_num != $_->account_num &&
+ $txn->amount == -$_->amount &&
+ abs($txn->date - $_->date) <= $days
+ } HomeBank::File->transactions;
+
+ return unless @match;
+
+ $txn->pair_with(@match);
+}
+
--- /dev/null
+package HomeBank;
+
+use warnings FATAL => 'all';
+use strict;
+
+use Symbol qw/delete_package/;
+
+=head1 NAME
+
+HomeBank - Perl plugin bindings for C<homebank>
+
+=head1 SYNOPSIS
+
+ # NAME: Example Plugin
+
+ sub new {
+ my $class = shift;
+ my $self = $class->SUPER::new(@_);
+
+ $self->on(
+ terminate => sub {
+ print "Terminating...\n";
+ },
+ );
+
+ $self;
+ }
+
+ sub on_unhandled {
+ my ($self, $hook_id) = @_;
+ print "An unhandled hook named '$hook_id' was called.\n";
+ }
+
+=head1 DESCRIPTION
+
+The C<HomeBank> class provides the infrastructure for loading plugins and handling the registration and calling of
+hooks.
+
+=head1 VARIABLES
+
+=head2 %plugins
+
+Contains all of the information about each loaded perl plugin. Plugins probably shouldn't mess around with this.
+
+=cut
+
+our %plugins;
+
+=head1 METHODS
+
+=head2 load_plugin $filepath
+
+Load a plugin with the given name. Dies if a plugin with the given name cannot be found or if the plugin couldn't
+successfully be eval'd. L<homebank> calls this to load enabled plugins; plugins themselves probably shouldn't ever use
+this.
+
+=cut
+
+sub load_plugin {
+ my $filepath = shift;
+
+ my $package = _valid_package_name($filepath);
+ $plugins{$package} ||= {};
+
+ my $mtime = -M $filepath;
+ if (defined $plugins{$package}->{mtime} && $plugins{$package}->{mtime} <= $mtime) {
+ warn "Already loaded $filepath";
+ } else {
+ delete_package $package if exists $plugins{$package}->{mtime};
+
+ open my $fh, $filepath or die "Open '$filepath' failed ($!)";
+ binmode $fh, 'utf8';
+ local $/ = undef;
+ my $code = <$fh>;
+ close $fh;
+
+ my $eval = qq/# line 1 "$filepath"\npackage $package; use base 'HomeBank::Plugin'; $code/;
+ {
+ my (%plugins, $mtime, $package);
+ eval "$eval; 1" or die $@;
+ }
+
+ $plugins{$package}->{mtime} = $mtime;
+ }
+ if (!exists $plugins{$package}->{instance}) {
+ $plugins{$package}->{instance} = $package->new or die "Plugin instantiation failed";
+ }
+}
+
+=head2 unload_plugin $filepath
+
+The opposite of L<load_plugin>.
+
+=cut
+
+sub unload_plugin {
+ my $filepath = shift;
+ my $package = _valid_package_name($filepath);
+
+ return unless exists $plugins{$package};
+
+ if ($package->can('delete_package_on_unload') && $package->delete_package_on_unload) {
+ delete $plugins{$package};
+ delete_package $package;
+ } else {
+ delete $plugins{$package}->{instance};
+ delete $plugins{$package}->{hooks};
+ }
+}
+
+=head2 execute_action $filepath
+
+Allow the plugin specified by C<$filepath> to perform an action. This is called when the plugin is "activated" by the
+user. Most plugins should run a modal dialog to allow the user to see and edit plugin preferences.
+
+=cut
+
+sub execute_action {
+ my $filepath = shift;
+ my $package = _valid_package_name($filepath);
+
+ return unless exists $plugins{$package};
+
+ my $instance = $plugins{$package}->{instance};
+ $instance->EXECUTE if $instance && $instance->can('EXECUTE');
+}
+
+=head2 read_metadata $filepath
+
+Get the metadata for a plugin without evaluating it. Plugin metadata should be in the first 100 lines of the plugin file
+and should look something like this:
+
+ # NAME: Foobar
+ # VERSION: 0.01
+ # ABSTRACT: This plugin does something.
+ # AUTHOR: John Doe <jdoe@acme.tld>
+ # WEBSITE: http://acme.tld/
+
+=cut
+
+sub read_metadata {
+ my $filepath = shift;
+
+ my $package = _valid_package_name($filepath);
+ $plugins{$package} ||= {};
+
+ return $plugins{$package}->{metadata} if exists $plugins{$package}->{metadata};
+
+ my @keywords = qw/name version abstract author website/;
+ my $keywords = join('|', @keywords);
+
+ my $metadata = {};
+ open my $fh, $filepath or die "Open '$filepath' failed ($!)";
+ my $count = 0;
+ for my $line (<$fh>) {
+ last if 100 < ++$count;
+ my ($key, $val) = $line =~ /^#[ \t]*($keywords)[ \t]*[=:](.*)/i;
+ if ($key && $val) {
+ $val =~ s/^\s*//;
+ $val =~ s/\s*$//;
+ $metadata->{lc $key} = $val;
+ }
+ }
+ close $fh;
+
+ $plugins{$package}->{metadata} = $metadata;
+}
+
+=head2 call_hook $hook_id, ...
+
+Invoke each perl plugins' hook handlers for the given hook. Additional arguments are passed through to each handler.
+Plugins shouldn't use this.
+
+=cut
+
+sub call_hook {
+ my $hook = shift;
+
+ $hook =~ s/[.-]/_/g;
+
+ for my $package (keys %plugins) {
+ my $hooks = ($plugins{$package} ||= {})->{hooks} ||= {};
+ my $count = 0;
+ for my $cb (@{$hooks->{$hook} ||= []}) {
+ eval { $cb->(@_); 1 } or warn $@;
+ $count++;
+ }
+ if ($count == 0) {
+ for my $cb (@{$hooks->{unhandled} ||= []}) {
+ eval { $cb->($hook, @_); 1 } or warn $@;
+ }
+ }
+ }
+}
+
+=head2 register_method_hooks $plugin
+
+Register hooks defined as methods that begin with `on_'.
+
+=cut
+
+sub register_method_hooks {
+ my $plugin = shift;
+ my $package = ref $plugin;
+
+ no strict 'refs';
+ my %subs = map { $_ =~ /^on_(.+)/ ? ($1 => $_) : () } keys %{"${package}::"};
+ use strict 'refs';
+
+ register_hooks($plugin, %subs);
+}
+
+=head2 register_hooks $plugin, %hooks
+
+Register hooks for a plugin.
+
+=cut
+
+sub register_hooks {
+ my ($plugin, %hooks) = @_;
+ my $package = ref $plugin;
+
+ my $hooks = ($plugins{$package} ||= {})->{hooks} ||= {};
+ for my $hook (keys %hooks) {
+ if (!ref($hooks{$hook}) && defined &{"${package}::$hooks{$hook}"}) {
+ push @{$hooks->{$hook} ||= []}, sub { unshift @_, $plugin; goto &{"${package}::$hooks{$hook}"} };
+ } elsif (ref($hooks{$hook}) eq 'CODE') {
+ push @{$hooks->{$hook} ||= []}, $hooks{$hook};
+ } else {
+ warn "Hook callback is unusable";
+ }
+ }
+}
+
+=head2 unregister_hooks $package, [@hooks]
+
+Unregister hooks for a package. If no hooks are specified, B<all> hooks will be unregistered.
+
+=cut
+
+sub unregister_hooks {
+ my ($package, @hooks) = @_;
+
+ if (@hooks) {
+ for my $hook (@hooks) {
+ (($plugins{$package} ||= {})->{hooks} ||= {})->{$hook} = [];
+ }
+ } else {
+ ($plugins{$package} ||= {})->{hooks} = {};
+ }
+}
+
+=head2 _valid_package_name $string
+
+Turn a string into a valid name of a package.
+
+=cut
+
+sub _valid_package_name {
+ my $str = shift;
+ $str =~ s|.*?([^/\\]+)\.pl$|$1|;
+ $str =~ s|([^A-Za-z0-9\/_])|sprintf("_%2x",unpack("C",$1))|eg;
+ $str =~ s|/(\d)|sprintf("/_%2x",unpack("C",$1))|eg;
+ $str =~ s|[/_]|::|g;
+ "HomeBank::Plugin::$str";
+}
+
+
+package HomeBank::Boolean;
+
+use overload
+ '0+' => sub { ${$_[0]} },
+ '++' => sub { $_[0] = ${$_[0]} + 1 },
+ '--' => sub { $_[0] = ${$_[0]} - 1 },
+ fallback => 1;
+
+package Types::Serialiser::Boolean;
+@HomeBank::Boolean::ISA = Types::Serialiser::Boolean::;
+
+
+package HomeBank::Plugin;
+
+sub new {
+ my ($class, $self) = (shift, shift || {});
+ bless $self, $class;
+ HomeBank::register_method_hooks($self);
+ $self;
+}
+
+sub on {
+ goto &HomeBank::register_hooks;
+}
+
+sub off {
+ goto &HomeBank::unregister_hooks;
+}
+
+
+package HomeBank::Transaction;
+
+sub datetime {
+ require DateTime;
+ require DateTime::Format::Strptime;
+ my $dt = DateTime->new(shift->date);
+ $dt->set_formatter(DateTime::Format::Strptime->new(pattern => '%Y-%m-%d'));
+ $dt;
+}
+
+
+=head1 AUTHOR
+
+Charles McGarvey <chazmcgarvey@brokenzipper.com>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2013 Charles McGarvey.
+
+This file is part of HomeBank.
+
+HomeBank 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.
+
+HomeBank 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, see <http://www.gnu.org/licenses/>.
+
+=cut
+
+1;
common_defines = \
-DSHARE_DIR=\""$(pkgdatadir)"\" \
- -DDATA_DIR=\""$(datadir)"\"
+ -DDATA_DIR=\""$(datadir)"\" \
+ -DPKGLIB_DIR=\""$(pkglibdir)"\"
bin_PROGRAMS = homebank
ui-widgets.c \
ui-widgets.h \
gtk-chart-colors.c \
- gtk-chart-colors.h
+ gtk-chart-colors.h \
+ refcount.h \
+ ext.c \
+ ext.h \
+ ext-value.c \
+ ext-value.h \
+ ext-native.c \
+ ext-perl.xs
-homebank_LDADD = $(DEPS_LIBS)
+EXTRA_homebank_DEPENDENCIES = $(PERL_OBJS)
+
+homebank_LDADD = $(PERL_OBJS) $(DEPS_LIBS)
AM_CPPFLAGS = \
$(DEPS_CFLAGS) \
$(common_defines)
+$(PERL_OBJS): CPPFLAGS += $(PERL_CPPFLAGS)
+
+ext-perl.c: ext-perl.xs typemap
+ $(XSUBPP) -typemap $(PERL_PRIVLIBEXP)/ExtUtils/typemap -typemap typemap $< >$@
+
+perlxsi.c: Makefile
+ $(PERL) -MExtUtils::Embed -e xsinit -- -std HomeBank
+
+CLEANFILES = ext-perl.c perlxsi.c
+
+pluginsupportdir = $(pkglibdir)
+pluginsupport_DATA = HomeBank.pm
+
#include "dsp_mainwindow.h"
+#include "ext.h"
+
#include "list_account.h"
#include "list_upcoming.h"
#include "list_topspending.h"
static void ui_mainwindow_action_balance(void);
static void ui_mainwindow_action_vehiclecost(void);
+static void ui_mainwindow_action_pluginprefs(void);
+
static void ui_mainwindow_action_import(void);
static void ui_mainwindow_action_export(void);
static void ui_mainwindow_action_anonymize(void);
static void ui_mainwindow_scheduled_populate(GtkWidget *widget, gpointer user_data);
void ui_mainwindow_scheduled_postall(GtkWidget *widget, gpointer user_data);
+static void ui_mainwindow_showprefs(gint page);
+
extern gchar *CYA_ACC_TYPE[];
{ "ManageMenu" , NULL, N_("_Manage"), NULL, NULL, NULL },
{ "TransactionMenu", NULL, N_("_Transactions"), NULL, NULL, NULL },
{ "ReportMenu" , NULL, N_("_Reports"), NULL, NULL, NULL },
+ { "PluginMenu" , NULL, N_("_Plugins"), NULL, NULL, NULL },
{ "HelpMenu" , NULL, N_("_Help"), NULL, NULL, NULL },
// { "Import" , NULL, N_("Import") },
{ "RBalance" , HB_STOCK_REP_BALANCE, N_("Balance...") , NULL, N_("Open the Balance report"), G_CALLBACK (ui_mainwindow_action_balance) },
{ "RVehiculeCost" , HB_STOCK_REP_CAR , N_("_Vehicle cost...") , NULL, N_("Open the Vehicle cost report"), G_CALLBACK (ui_mainwindow_action_vehiclecost) },
+ { "PluginPreferences", "prf-plugins", N_("_Plugins..."), "<control>U", N_("Configure plugin preferences"), G_CALLBACK(ui_mainwindow_action_pluginprefs) },
+
/* HelpMenu */
{ "Contents" , GTK_STOCK_HELP , N_("_Contents") , "F1", N_("Documentation about HomeBank"), G_CALLBACK (ui_mainwindow_action_help) },
{ "Welcome" , NULL , N_("Show welcome dialog...") , NULL, NULL , G_CALLBACK (ui_mainwindow_action_help_welcome) },
" <menuitem action='RBudget'/>"
" <menuitem action='RVehiculeCost'/>"
" </menu>"
+" <menu action='PluginMenu'>"
+" <separator/>"
+" <menuitem action='PluginPreferences'/>"
+" <separator/>"
+" </menu>"
" <menu action='HelpMenu'>"
" <menuitem action='Contents'/>"
" <separator/>"
" <toolitem action='RBalance'/>"
" <toolitem action='RBudget'/>"
" <toolitem action='RVehiculeCost'/>"
+" <separator/>"
" </toolbar>"
"</ui>";
static const gchar *authors[] = {
"Lead developer:\n" \
"Maxime DOYEN",
- "\nContributor:\n" \
+ "\nContributors:\n" \
+ "Charles MCGARVEY (Plugin system, Perl support)\n" \
"Ga\xc3\xabtan LORIDANT (Maths formulas for charts)\n",
NULL
};
static void ui_mainwindow_action_preferences(void)
+{
+ ui_mainwindow_showprefs(PREF_GENERAL);
+}
+
+static void ui_mainwindow_showprefs(gint page)
{
struct hbfile_data *data = g_object_get_data(G_OBJECT(GLOBALS->mainwindow), "inst_data");
- defpref_dialog_new();
+ defpref_dialog_new(page);
if(!PREFS->euro_active)
{
GtkToggleAction *action = (GtkToggleAction *)gtk_ui_manager_get_action(data->manager, "/MenuBar/ViewMenu/AsMinor");
repcost_window_new();
}
+static void ui_mainwindow_action_pluginprefs(void)
+{
+ ui_mainwindow_showprefs(PREF_PLUGINS);
+}
+
static void ui_mainwindow_action_import(void)
{
ui_import_window_new();
struct WinGeometry *wg;
gboolean retval = FALSE;
+ GValue widget_value = G_VALUE_INIT;
+ ext_hook("main_window_disposal", EXT_OBJECT(&widget_value, widget), NULL);
+
DB( g_print("\n[ui-mainwindow] dispose\n") );
//store position and size
--- /dev/null
+
+#include <glib/gstdio.h>
+#include <gmodule.h>
+
+#include "ext.h"
+
+
+static gint ext_native_init(int* argc, char** argv[], char** env[]);
+static void ext_native_term(void);
+static gboolean ext_native_check_file(const gchar* plugin_filename);
+static GHashTable* ext_native_read_plugin_metadata(const gchar* plugin_filepath);
+static gint ext_native_load_plugin(const gchar* plugin_filepath);
+static void ext_native_unload_plugin(const gchar* plugin_filepath);
+static void ext_native_execute_action(const gchar* plugin_filepath);
+static void ext_native_call_hook(const gchar* hook_id, GList* args);
+
+static gchar* _read_data_for_keyword(const gchar* keyword, const gchar* bytes, gsize len);
+
+
+static GHashTable* _loaded_plugins = NULL;
+
+
+static gint ext_native_init(int* argc, char** argv[], char** env[])
+{
+ if (!_loaded_plugins) {
+ _loaded_plugins = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_module_close);
+ }
+ return 0;
+}
+
+static void ext_native_term(void)
+{
+ if (_loaded_plugins) {
+ ext_native_call_hook("unload", NULL);
+ g_hash_table_unref(_loaded_plugins);
+ _loaded_plugins = NULL;
+ }
+}
+
+static gboolean ext_native_check_file(const gchar* plugin_filename)
+{
+ if (g_str_has_suffix(plugin_filename, "."G_MODULE_SUFFIX)) {
+ return TRUE;
+ }
+ if (g_str_has_suffix(plugin_filename, ".la")) {
+ // allow a .la file only if no actual native plugin is found
+ gboolean check = FALSE;
+ gchar* copy = g_strdup(plugin_filename);
+ gchar* ext = g_strrstr(copy, ".la");
+ if (ext) {
+ *ext = '\0';
+ gchar* native_filename = g_strconcat(copy, "."G_MODULE_SUFFIX, NULL);
+ gchar* native_filepath = ext_find_plugin(native_filename);
+ check = !native_filepath;
+ g_free(native_filepath);
+ g_free(native_filename);
+ }
+ g_free(copy);
+ return check;
+ }
+ return FALSE;
+}
+
+static GHashTable* ext_native_read_plugin_metadata(const gchar* plugin_filepath)
+{
+ GMappedFile* file = g_mapped_file_new(plugin_filepath, FALSE, NULL);
+ if (!file) {
+ g_printerr("mapping plugin file at %s failed\n", plugin_filepath);
+ return NULL;
+ }
+
+ gchar* bytes = g_mapped_file_get_contents(file);
+ gsize len = g_mapped_file_get_length(file);
+ if (len == 0 || !bytes) {
+ g_mapped_file_unref(file);
+ g_printerr("no data in plugin file at %s failed\n", plugin_filepath);
+ return NULL;
+ }
+
+ GHashTable* table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+ const gchar* keywords[] = { "name", "version", "abstract", "author", "website", NULL };
+ const gchar** it;
+ for (it = keywords; *it; ++it) {
+ gchar* value = _read_data_for_keyword(*it, bytes, len);
+ g_hash_table_insert(table, g_strdup(*it), value);
+ }
+
+ g_mapped_file_unref(file);
+
+ return table;
+}
+
+static gint ext_native_load_plugin(const gchar* plugin_filepath)
+{
+ if (g_hash_table_contains(_loaded_plugins, plugin_filepath)) {
+ return 0;
+ }
+
+ GModule* module = g_module_open(plugin_filepath, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
+ if (!module) {
+ g_printerr("Could not load native plugin: %s\n", g_module_error());
+ return -1;
+ }
+
+ g_hash_table_insert(_loaded_plugins, g_strdup(plugin_filepath), module);
+
+ void (*symbol)();
+ if (g_module_symbol(module, "load", (gpointer)&symbol)) {
+ symbol();
+ }
+
+ return 0;
+}
+
+static void ext_native_unload_plugin(const gchar* plugin_filepath)
+{
+ GModule* module = g_hash_table_lookup(_loaded_plugins, plugin_filepath);
+ if (module) {
+ void (*symbol)();
+ if (g_module_symbol(module, "unload", (gpointer)&symbol)) {
+ symbol();
+ }
+ }
+
+ g_hash_table_remove(_loaded_plugins, plugin_filepath);
+}
+
+static void ext_native_execute_action(const gchar* plugin_filepath)
+{
+ GModule* module = g_hash_table_lookup(_loaded_plugins, plugin_filepath);
+ if (module) {
+ void (*symbol)();
+ if (g_module_symbol(module, "execute", (gpointer)&symbol)) {
+ symbol();
+ }
+ }
+}
+
+static void ext_native_call_hook(const gchar* hook_id, GList* args)
+{
+ gchar* symbol_name = g_strconcat("on_", hook_id, NULL);
+ void (*symbol)(GList*);
+
+ GHashTableIter it;
+ g_hash_table_iter_init(&it, _loaded_plugins);
+ GModule* module;
+
+ while (g_hash_table_iter_next(&it, NULL, (gpointer*)&module)) {
+ if (g_module_symbol(module, symbol_name, (gpointer)&symbol)) {
+ symbol(args);
+ }
+ }
+
+ g_free(symbol_name);
+}
+
+
+static gchar* _read_data_for_keyword(const gchar* keyword, const gchar* bytes, gsize len)
+{
+ gchar* value = NULL;
+
+ gchar* pattern = g_strdup_printf("[\\x00\\t\\n ]%s\\s*[=:]\\s*([^\\x00]+)", keyword);
+ GRegex* r = g_regex_new(pattern, G_REGEX_CASELESS, 0, NULL);
+ g_free(pattern);
+
+ GMatchInfo* match = NULL;
+ if (g_regex_match_full(r, bytes, len, 0, 0, &match, NULL)) {
+ value = g_match_info_fetch(match, 1);
+ }
+
+ g_match_info_free(match);
+ g_regex_unref(r);
+
+ return value;
+}
+
+
+static void _register(void) __attribute__((constructor));
+static void _register()
+{
+ ext_register("native",
+ ext_native_init,
+ ext_native_term,
+ ext_native_check_file,
+ ext_native_read_plugin_metadata,
+ ext_native_load_plugin,
+ ext_native_unload_plugin,
+ ext_native_execute_action,
+ ext_native_call_hook);
+}
+
--- /dev/null
+
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+
+#include <string.h>
+
+#undef _
+#include "homebank.h"
+#include "ext.h"
+#include "refcount.h"
+
+extern struct HomeBank *GLOBALS;
+#include "dsp_mainwindow.h"
+#include "dsp_account.h"
+#include "ui-transaction.h"
+
+
+static gint ext_perl_init(int* argc, char** argv[], char** env[]);
+static void ext_perl_term(void);
+static gboolean ext_perl_check_file(const gchar* plugin_filepath);
+static GHashTable* ext_perl_read_plugin_metadata(const gchar* plugin_filepath);
+static gint ext_perl_load_plugin(const gchar* plugin_filepath);
+static void ext_perl_unload_plugin(const gchar* plugin_filepath);
+static void ext_perl_execute_action(const gchar* plugin_filepath);
+static void ext_perl_call_hook(const gchar* hook_id, GList* args);
+
+static SV* val_to_sv(GValue* val);
+static GValue* sv_to_val(SV* sv);
+
+static gboolean gperl_value_from_sv(GValue* value, SV* sv);
+static SV* gperl_sv_from_value(const GValue* value, gboolean copy_boxed);
+
+
+static inline GValue* EXT_SV(GValue* v, SV* sv, GType type)
+{
+ g_value_init(v, type);
+ gperl_value_from_sv(v, sv);
+ return v;
+}
+
+
+#define EXT_P2C_OBJECT(PKG, ARG, VAR, TYP) \
+if (sv_derived_from(ARG, PKG)) { \
+ IV iv = SvIV((SV*)SvRV(ARG)); \
+ VAR = INT2PTR(TYP, iv); \
+} else { \
+ croak(#VAR" is not of type "PKG); \
+}
+
+#define EXT_C2P_OBJECT(PKG, ARG, VAR) \
+sv_setref_pv(ARG, PKG, (void*)VAR)
+
+
+static inline GPtrArray* SvGptrarray(const SV* sv)
+{
+ if (SvROK(sv)) {
+ sv = MUTABLE_SV(SvRV(sv));
+ }
+ if (SvTYPE(sv) == SVt_PVAV) {
+ AV* av = (AV*)sv;
+ int i;
+ int top = av_len(av);
+ GPtrArray* array = g_ptr_array_new();
+ for (i = 0; i <= top; ++i) {
+ SV** item = av_fetch(av, i, 0);
+ if (!item) continue;
+ g_ptr_array_add(array, sv_to_val(*item));
+ }
+ return array;
+ // TODO- leaking
+ } else {
+ croak("var is not an array");
+ }
+}
+
+static inline SV* newSVgptrarray(const GPtrArray* a)
+{
+ if (a) {
+ AV* av = newAV();
+ int i;
+ for (i = 0; i < a->len; ++i) {
+ GValue* item = g_ptr_array_index(a, i);
+ av_push(av, val_to_sv(item));
+ }
+ return newRV((SV*)av);
+ }
+ return &PL_sv_undef;
+}
+
+
+static inline GHashTable* SvGhashtable(const SV* sv)
+{
+ if (SvROK(sv)) {
+ sv = MUTABLE_SV(SvRV(sv));
+ }
+ if (SvTYPE(sv) == SVt_PVHV) {
+ HV* hv = (HV*)sv;
+ hv_iterinit(hv);
+ gchar* key;
+ I32 len;
+ SV* item;
+ GHashTable* hash = g_hash_table_new(g_str_hash, g_str_equal);
+ while ((item = hv_iternextsv(hv, &key, &len))) {
+ g_hash_table_insert(hash, key, sv_to_val(item));
+ }
+ return hash;
+ // TODO- leaking
+ } else {
+ croak("var is not a hash");
+ }
+}
+
+static inline SV* newSVghashtable(GHashTable* h)
+{
+ if (h) {
+ HV* hv = newHV();
+ GHashTableIter it;
+ g_hash_table_iter_init(&it, h);
+ gchar* key = NULL;
+ GValue* item = NULL;
+ while (g_hash_table_iter_next(&it, (gpointer*)&key, (gpointer*)&item)) {
+ hv_store(hv, key, -g_utf8_strlen(key, -1), val_to_sv(item), 0);
+ }
+ return newRV((SV*)hv);
+ }
+ return &PL_sv_undef;
+}
+
+
+static inline gboolean SvGboolean(SV* sv)
+{
+ if (!sv) {
+ return FALSE;
+ }
+ if (SvROK(sv)) {
+ return !!SvIV(SvRV(sv));
+ } else {
+ return SvTRUE(sv);
+ }
+}
+
+static inline SV* newSVgboolean(gboolean b)
+{
+ return sv_setref_iv(newSV(0), "HomeBank::Boolean", !!b);
+}
+
+
+static inline gchar* SvGchar_ptr(SV* sv)
+{
+ return SvPVutf8_nolen(sv);
+}
+
+static inline SV* newSVgchar_ptr(const gchar* str)
+{
+ if (!str) return &PL_sv_undef;
+
+ SV* sv = newSVpv(str, 0);
+ SvUTF8_on(sv);
+ return sv;
+}
+
+
+static inline GObject* SvGobject(const SV* sv)
+{
+ GObject* (*func)(const SV*) = ext_symbol_lookup("gperl_get_object");
+ if (func) {
+ return func(sv);
+ }
+ return NULL;
+}
+
+static inline SV* newSVgobject(const GObject* o)
+{
+ SV* (*func)(const GObject*, gboolean) = ext_symbol_lookup("gperl_new_object");
+ if (func) {
+ return func(o, FALSE);
+ }
+ return &PL_sv_undef;
+}
+
+
+static PerlInterpreter* context = NULL;
+
+
+static gint ext_perl_init(int* argc, char** argv[], char** env[])
+{
+ int ret = 0;
+
+ PERL_SYS_INIT3(argc, argv, env);
+ context = perl_alloc();
+ perl_construct(context);
+
+ PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
+ PL_origalen = 1;
+ PL_perl_destruct_level = 1;
+
+ gchar* bootstrap = g_strdup_printf("-e"
+ "use lib '%s';"
+ "use HomeBank;"
+ "HomeBank->bootstrap;",
+ homebank_app_get_pkglib_dir());
+ char *args[] = { "", bootstrap };
+
+ EXTERN_C void xs_init(pTHX);
+ if (perl_parse(context, xs_init, 2, args, NULL) || perl_run(context)) {
+ ext_perl_term();
+ ret = -1;
+ }
+
+ g_free(bootstrap);
+ return ret;
+}
+
+static void ext_perl_term(void)
+{
+ if (context) {
+ perl_destruct(context);
+ perl_free(context);
+ context = NULL;
+ }
+ PERL_SYS_TERM();
+}
+
+static gboolean ext_perl_check_file(const gchar* plugin_filepath)
+{
+ if (g_str_has_suffix(plugin_filepath, ".pl")) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static GHashTable* ext_perl_read_plugin_metadata(const gchar* plugin_filepath)
+{
+ GHashTable* table = NULL;
+
+ if (!context) return NULL;
+ PERL_SET_CONTEXT(context);
+
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ mXPUSHs(newSVgchar_ptr(plugin_filepath));
+ PUTBACK;
+
+ int ret = call_pv("HomeBank::read_metadata", G_SCALAR | G_EVAL);
+
+ SPAGAIN;
+
+ if (ret == 1) {
+ table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ SV* sv = POPs;
+ if (SvROK(sv)) {
+ sv = MUTABLE_SV(SvRV(sv));
+ }
+ if (SvTYPE(sv) == SVt_PVHV) {
+ HV* hv = (HV*)sv;
+ hv_iterinit(hv);
+ gchar* key;
+ I32 len;
+ SV* item;
+ while ((item = hv_iternextsv(hv, &key, &len))) {
+ if (SvPOK(item)) {
+ gchar* val = SvPVutf8_nolen(item);
+ g_hash_table_insert(table, g_strdup(key), g_strdup(val));
+ }
+ }
+ }
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return table;
+}
+
+static gint ext_perl_load_plugin(const gchar* plugin_filepath)
+{
+ if (!context) return -1;
+ PERL_SET_CONTEXT(context);
+
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ mXPUSHs(newSVgchar_ptr(plugin_filepath));
+ PUTBACK;
+ call_pv("HomeBank::load_plugin", G_DISCARD | G_EVAL);
+ SPAGAIN;
+
+ gint ret = 0;
+ if (SvTRUE(ERRSV)) {
+ g_printerr("%s", SvPV_nolen(ERRSV));
+ ret = -1;
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return ret;
+}
+
+static void ext_perl_unload_plugin(const gchar* plugin_filepath)
+{
+ if (!context) return;
+ PERL_SET_CONTEXT(context);
+
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ mXPUSHs(newSVgchar_ptr(plugin_filepath));
+ PUTBACK;
+ call_pv("HomeBank::unload_plugin", G_DISCARD | G_EVAL);
+ SPAGAIN;
+
+ if (SvTRUE(ERRSV)) {
+ g_printerr("%s", SvPV_nolen(ERRSV));
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+static void ext_perl_execute_action(const gchar* plugin_filepath)
+{
+ if (!context) return;
+ PERL_SET_CONTEXT(context);
+
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ mXPUSHs(newSVgchar_ptr(plugin_filepath));
+ PUTBACK;
+ call_pv("HomeBank::execute_action", G_DISCARD | G_EVAL);
+ SPAGAIN;
+
+ if (SvTRUE(ERRSV)) {
+ g_printerr("%s", SvPV_nolen(ERRSV));
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+static void ext_perl_call_hook(const gchar* hook_id, GList* args)
+{
+ if (!context) return;
+ PERL_SET_CONTEXT(context);
+
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ mXPUSHs(newSVgchar_ptr(hook_id));
+
+ GList *list = g_list_first(args);
+ while (list) {
+ GValue* val = list->data;
+ XPUSHs(sv_2mortal(val_to_sv(val)));
+ list = g_list_next(list);
+ }
+
+ PUTBACK;
+ call_pv("HomeBank::call_hook", G_ARRAY);
+ SPAGAIN;
+ POPi;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+
+static SV* val_to_sv(GValue* val)
+{
+ if (!val || !G_IS_VALUE(val) || G_VALUE_TYPE(val) == G_TYPE_NONE) {
+ return &PL_sv_undef;
+ }
+ if (G_VALUE_TYPE(val) == G_TYPE_BOOLEAN) {
+ return newSVgboolean(g_value_get_boolean(val));
+ }
+ if (G_VALUE_TYPE(val) == G_TYPE_PTR_ARRAY) {
+ return newSVgptrarray((GPtrArray*)g_value_get_boxed(val));
+ }
+ if (G_VALUE_TYPE(val) == G_TYPE_HASH_TABLE) {
+ return newSVghashtable((GHashTable*)g_value_get_boxed(val));
+ }
+#define obj(CTYPE, _2, PART, GTYPE, _5) \
+ if (G_VALUE_TYPE(val) == GTYPE) { \
+ SV* sv = newSV(0); \
+ CTYPE* ptr = (CTYPE*)g_value_get_##PART(val); \
+ EXT_C2P_OBJECT("HomeBank::"#CTYPE, sv, rc_ref(ptr)); \
+ return sv; \
+ }
+#include "ext-value.h"
+#undef obj
+ return gperl_sv_from_value(val, FALSE);
+}
+
+static GValue* sv_to_val(SV* sv)
+{
+ GValue* val = g_new0(GValue, 1);
+
+ if (SvUOK(sv)) return EXT_SV(val, sv, G_TYPE_UINT);
+ if (SvIOK(sv)) return EXT_SV(val, sv, G_TYPE_INT);
+ if (SvNOK(sv)) return EXT_SV(val, sv, G_TYPE_DOUBLE);
+ if (SvPOK(sv)) return EXT_SV(val, sv, G_TYPE_STRING);
+ if (sv_isobject(sv)) {
+ if (sv_derived_from(sv, "HomeBank::Boolean")) {
+ return EXT_BOOLEAN(val, SvGboolean(sv));
+ }
+#define obj(CTYPE, NAME, _3, _4, _5) \
+ if (sv_derived_from(sv, "HomeBank::"#CTYPE)) { \
+ CTYPE* ptr; \
+ EXT_P2C_OBJECT("HomeBank::"#CTYPE, sv, ptr, CTYPE*); \
+ return EXT_##NAME(val, ptr); \
+ }
+#include "ext-value.h"
+#undef obj
+ return EXT_SV(val, sv, G_TYPE_OBJECT);
+ }
+ if (SvROK(sv)) {
+ sv = SvRV(sv);
+ switch (SvTYPE(sv)) {
+ case SVt_IV:
+ return EXT_BOOLEAN(val, SvGboolean(sv));
+ case SVt_PVAV:
+ return EXT_ARRAY(val, SvGptrarray(sv));
+ case SVt_PVHV:
+ return EXT_HASH_TABLE(val, SvGhashtable(sv));
+ default:
+ break;
+ }
+ }
+ switch (SvTYPE(sv)) {
+ case SVt_PVAV:
+ return EXT_ARRAY(val, SvGptrarray(sv));
+ case SVt_PVHV:
+ return EXT_HASH_TABLE(val, SvGhashtable(sv));
+ default:
+ break;
+ }
+
+ g_free(val);
+ return NULL;
+}
+
+
+static gboolean gperl_value_from_sv(GValue* value, SV* sv)
+{
+ gboolean (*func)(GValue*, SV*) = ext_symbol_lookup("gperl_value_from_sv");
+ if (func) return func(value, sv);
+
+ GType type = G_TYPE_FUNDAMENTAL(G_VALUE_TYPE(value));
+ if (!SvOK(sv)) return TRUE;
+ switch (type) {
+ case G_TYPE_CHAR:
+ {
+ gchar *tmp = SvGchar_ptr(sv);
+ g_value_set_schar(value, (gint8)(tmp ? tmp[0] : 0));
+ break;
+ }
+ case G_TYPE_UCHAR:
+ {
+ char *tmp = SvPV_nolen(sv);
+ g_value_set_uchar(value, (guchar)(tmp ? tmp[0] : 0));
+ break;
+ }
+ case G_TYPE_BOOLEAN:
+ g_value_set_boolean(value, SvTRUE(sv));
+ break;
+ case G_TYPE_INT:
+ g_value_set_int(value, SvIV(sv));
+ break;
+ case G_TYPE_UINT:
+ g_value_set_uint(value, SvIV(sv));
+ break;
+ case G_TYPE_LONG:
+ g_value_set_long(value, SvIV(sv));
+ break;
+ case G_TYPE_ULONG:
+ g_value_set_ulong(value, SvIV(sv));
+ break;
+ case G_TYPE_FLOAT:
+ g_value_set_float(value, (gfloat)SvNV(sv));
+ break;
+ case G_TYPE_DOUBLE:
+ g_value_set_double(value, SvNV(sv));
+ break;
+ case G_TYPE_STRING:
+ g_value_set_string(value, SvGchar_ptr(sv));
+ break;
+ }
+ return TRUE;
+}
+
+static SV* gperl_sv_from_value(const GValue* value, gboolean copy_boxed)
+{
+ SV* (*func)(const GValue*, gboolean) = ext_symbol_lookup("gperl_sv_from_value");
+ if (func) return func(value, copy_boxed);
+
+ GType type = G_TYPE_FUNDAMENTAL(G_VALUE_TYPE(value));
+ switch (type) {
+ case G_TYPE_CHAR:
+ return newSViv(g_value_get_schar(value));
+ case G_TYPE_UCHAR:
+ return newSVuv(g_value_get_uchar(value));
+ case G_TYPE_BOOLEAN:
+ return newSViv(g_value_get_boolean(value));
+ case G_TYPE_INT:
+ return newSViv(g_value_get_int(value));
+ case G_TYPE_UINT:
+ return newSVuv(g_value_get_uint(value));
+ case G_TYPE_LONG:
+ return newSViv(g_value_get_long(value));
+ case G_TYPE_ULONG:
+ return newSVuv(g_value_get_ulong(value));
+ case G_TYPE_FLOAT:
+ return newSVnv(g_value_get_float(value));
+ case G_TYPE_DOUBLE:
+ return newSVnv(g_value_get_double(value));
+ case G_TYPE_STRING:
+ return newSVgchar_ptr(g_value_get_string(value));
+ }
+ return &PL_sv_undef;
+}
+
+
+static void _register(void) __attribute__((constructor));
+static void _register()
+{
+ ext_register("perl",
+ ext_perl_init,
+ ext_perl_term,
+ ext_perl_check_file,
+ ext_perl_read_plugin_metadata,
+ ext_perl_load_plugin,
+ ext_perl_unload_plugin,
+ ext_perl_execute_action,
+ ext_perl_call_hook);
+}
+
+
+MODULE = HomeBank PACKAGE = HomeBank
+
+PROTOTYPES: ENABLE
+
+const gchar*
+version(void)
+ CODE:
+ RETVAL = VERSION;
+ OUTPUT:
+ RETVAL
+
+const gchar*
+config_dir(void)
+ CODE:
+ RETVAL = homebank_app_get_config_dir();
+ OUTPUT:
+ RETVAL
+
+gboolean
+has(const gchar* CLASS, ...)
+ PREINIT:
+ int i;
+ CODE:
+ PERL_UNUSED_ARG(CLASS);
+ RETVAL = TRUE;
+ for (i = 1; i < items; ++i) {
+ gchar* feature = SvGchar_ptr(ST(i));
+ if (!feature || !ext_has(feature)) {
+ RETVAL = FALSE;
+ break;
+ }
+ }
+ OUTPUT:
+ RETVAL
+
+GObject*
+main_window(void)
+ CODE:
+ RETVAL = G_OBJECT(GLOBALS->mainwindow);
+ OUTPUT:
+ RETVAL
+
+GObject*
+main_ui_manager(void)
+ PREINIT:
+ struct hbfile_data *data;
+ CODE:
+ RETVAL = NULL;
+ if (GLOBALS->mainwindow) {
+ data = g_object_get_data(G_OBJECT(gtk_widget_get_ancestor(GLOBALS->mainwindow, GTK_TYPE_WINDOW)), "inst_data");
+ if (data) {
+ RETVAL = G_OBJECT(data->manager);
+ }
+ }
+ OUTPUT:
+ RETVAL
+
+void
+info(const gchar* CLASS, const gchar* title, const gchar* text)
+ CODE:
+ PERL_UNUSED_ARG(CLASS);
+ ext_run_modal(title, text, "info");
+
+void
+warn(const gchar* CLASS, const gchar* title, const gchar* text)
+ CODE:
+ PERL_UNUSED_ARG(CLASS);
+ ext_run_modal(title, text, "warn");
+
+void
+error(const gchar* CLASS, const gchar* title, const gchar* text)
+ CODE:
+ PERL_UNUSED_ARG(CLASS);
+ ext_run_modal(title, text, "error");
+
+void
+hook(const gchar* CLASS, const gchar* hook_name, ...)
+ PREINIT:
+ int i;
+ GList* list = NULL;
+ CODE:
+ PERL_UNUSED_ARG(CLASS);
+ for (i = 2; i < items; ++i) {
+ SV* sv = ST(i);
+ GValue *val = sv_to_val(sv);
+ list = g_list_append(list, val);
+ }
+ CLEANUP:
+ ext_vhook(hook_name, list);
+ g_list_free(list);
+ // TODO free all the things
+
+GObject*
+open_prefs(const gchar* CLASS)
+ CODE:
+ PERL_UNUSED_ARG(CLASS);
+ RETVAL = G_OBJECT(defpref_dialog_new(PREF_GENERAL));
+ OUTPUT:
+ RETVAL
+
+
+MODULE = HomeBank PACKAGE = HomeBank::File
+
+const gchar*
+owner(const gchar* CLASS, ...)
+ CODE:
+ PERL_UNUSED_ARG(CLASS);
+ if (1 < items) {
+ hbfile_change_owner(g_strdup(SvGchar_ptr(ST(1))));
+ }
+ RETVAL = GLOBALS->owner;
+ OUTPUT:
+ RETVAL
+
+void
+transactions(const gchar* CLASS)
+ PPCODE:
+ PERL_UNUSED_ARG(CLASS);
+ GList* list = g_list_first(GLOBALS->ope_list);
+ for (; list; list = g_list_next(list)) {
+ GValue val = G_VALUE_INIT;
+ SV* sv = val_to_sv(EXT_TRANSACTION(&val, list->data));
+ mXPUSHs(sv);
+ }
+
+void
+anonymize(void)
+ CODE:
+ hbfile_anonymize();
+
+void
+baz(const gchar* CLASS, Account* account)
+ CODE:
+ PERL_UNUSED_ARG(CLASS);
+ g_print("hello: %s\n", account->name);
+
+GPtrArray*
+meh(const gchar* CLASS, GPtrArray* asdf)
+ CODE:
+ PERL_UNUSED_ARG(CLASS);
+ g_print("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW\n");
+ if (asdf) {
+ ;
+ } else {
+ g_print("the array is nil\n");
+ }
+ RETVAL = asdf;
+ OUTPUT:
+ RETVAL
+ CLEANUP:
+ g_ptr_array_unref(asdf);
+
+GHashTable*
+foo(const gchar* CLASS, GHashTable* asdf)
+ CODE:
+ PERL_UNUSED_ARG(CLASS);
+ g_print("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW\n");
+ if (asdf) {
+ GHashTableIter it;
+ g_hash_table_iter_init(&it, asdf);
+ gchar* key = NULL;
+ GValue* item = NULL;
+ while (g_hash_table_iter_next(&it, (gpointer*)&key, (gpointer*)&item)) {
+ g_print("hash with key: %s\n", key);
+ }
+ } else {
+ g_print("the hash is nil\n");
+ }
+ RETVAL = asdf;
+ OUTPUT:
+ RETVAL
+ CLEANUP:
+ g_hash_table_unref(asdf);
+
+
+MODULE = HomeBank PACKAGE = HomeBank::Account
+
+void
+compute_balances(const gchar* CLASS)
+ CODE:
+ PERL_UNUSED_ARG(CLASS);
+ account_compute_balances();
+
+Account*
+new(void)
+ CODE:
+ RETVAL = da_acc_malloc();
+ OUTPUT:
+ RETVAL
+
+Account*
+clone(Account* SELF)
+ CODE:
+ RETVAL = da_acc_clone(SELF);
+ RETVAL->key = 0;
+ OUTPUT:
+ RETVAL
+
+void
+DESTROY(Account* SELF)
+ CODE:
+ da_acc_free(SELF);
+
+Account*
+get(const gchar* CLASS, guint key)
+ CODE:
+ PERL_UNUSED_ARG(CLASS);
+ RETVAL = rc_ref(da_acc_get(key));
+ OUTPUT:
+ RETVAL
+
+Account*
+get_by_name(const gchar* CLASS, const gchar* name)
+ CODE:
+ PERL_UNUSED_ARG(CLASS);
+ RETVAL = rc_ref(da_acc_get_by_name((gchar*)name));
+ OUTPUT:
+ RETVAL
+
+const gchar*
+name(Account* SELF, ...)
+ CODE:
+ if (1 < items) {
+ account_rename(SELF, SvGchar_ptr(ST(1)));
+ }
+ RETVAL = SELF->name;
+ OUTPUT:
+ RETVAL
+
+const gchar*
+number(Account* SELF, ...)
+ CODE:
+ if (1 < items) {
+ g_free(SELF->number);
+ SELF->number = g_strdup(SvGchar_ptr(ST(1)));
+ }
+ RETVAL = SELF->number;
+ OUTPUT:
+ RETVAL
+
+const gchar*
+bankname(Account* SELF, ...)
+ CODE:
+ if (1 < items) {
+ g_free(SELF->bankname);
+ SELF->bankname = g_strdup(SvGchar_ptr(ST(1)));
+ }
+ RETVAL = SELF->bankname;
+ OUTPUT:
+ RETVAL
+
+gdouble
+initial(Account* SELF, ...)
+ CODE:
+ if (1 < items) {
+ SELF->initial = SvNV(ST(1));
+ }
+ RETVAL = SELF->initial;
+ OUTPUT:
+ RETVAL
+
+gdouble
+minimum(Account* SELF, ...)
+ CODE:
+ if (1 < items) {
+ SELF->minimum = SvNV(ST(1));
+ }
+ RETVAL = SELF->minimum;
+ OUTPUT:
+ RETVAL
+
+guint
+cheque1(Account* SELF, ...)
+ ALIAS:
+ check1 = 1
+ CODE:
+ PERL_UNUSED_VAR(ix);
+ if (1 < items) {
+ SELF->cheque1 = SvUV(ST(1));
+ }
+ RETVAL = SELF->cheque1;
+ OUTPUT:
+ RETVAL
+
+guint
+cheque2(Account* SELF, ...)
+ ALIAS:
+ check2 = 1
+ CODE:
+ PERL_UNUSED_VAR(ix);
+ if (1 < items) {
+ SELF->cheque2 = SvUV(ST(1));
+ }
+ RETVAL = SELF->cheque2;
+ OUTPUT:
+ RETVAL
+
+gdouble
+balance(Account* SELF)
+ ALIAS:
+ bank_balance = 1
+ future_balance = 2
+ CODE:
+ switch (ix) {
+ case 1:
+ RETVAL = SELF->bal_bank;
+ break;
+ case 2:
+ RETVAL = SELF->bal_future;
+ break;
+ default:
+ RETVAL = SELF->bal_today;
+ break;
+ }
+ OUTPUT:
+ RETVAL
+
+gboolean
+is_inserted(Account* SELF)
+ CODE:
+ RETVAL = da_acc_get(SELF->key) == SELF;
+ OUTPUT:
+ RETVAL
+
+gboolean
+is_used(Account* SELF)
+ CODE:
+ RETVAL = account_is_used(SELF->key);
+ OUTPUT:
+ RETVAL
+
+gboolean
+insert(Account* SELF)
+ CODE:
+ if (SELF->key == 0 || account_is_used(SELF->key))
+ RETVAL = da_acc_append(rc_ref(SELF));
+ else
+ RETVAL = da_acc_insert(rc_ref(SELF));
+ OUTPUT:
+ RETVAL
+
+void
+remove(Account* SELF)
+ CODE:
+ da_acc_remove(SELF->key);
+
+void
+transactions(Account* SELF)
+ PPCODE:
+ GList* list = g_list_first(GLOBALS->ope_list);
+ for (; list; list = g_list_next(list)) {
+ Transaction* txn = list->data;
+ if (txn->kacc == SELF->key) {
+ GValue val = G_VALUE_INIT;
+ SV* sv = val_to_sv(EXT_TRANSACTION(&val, txn));
+ mXPUSHs(sv);
+ }
+ }
+
+GObject*
+open(Account* SELF)
+ CODE:
+ RETVAL = G_OBJECT(register_panel_window_new(SELF->key, SELF));
+ OUTPUT:
+ RETVAL
+
+
+MODULE = HomeBank PACKAGE = HomeBank::Transaction
+
+Transaction*
+new(void)
+ CODE:
+ RETVAL = da_transaction_malloc();
+ OUTPUT:
+ RETVAL
+
+void
+DESTROY(Transaction* SELF)
+ CODE:
+ da_transaction_free(SELF);
+
+gdouble
+amount(Transaction* SELF, ...)
+ CODE:
+ if (1 < items) {
+ SELF->amount = SvNV(ST(1));
+ }
+ RETVAL = SELF->amount;
+ OUTPUT:
+ RETVAL
+
+guint
+account_num(Transaction* SELF, ...)
+ CODE:
+ if (1 < items) {
+ SELF->kacc = SvIV(ST(1));
+ }
+ RETVAL = SELF->kacc;
+ OUTPUT:
+ RETVAL
+
+guint
+paired_account_num(Transaction* SELF, ...)
+ CODE:
+ if (1 < items) {
+ SELF->kxferacc = SvIV(ST(1));
+ }
+ RETVAL = SELF->kxferacc;
+ OUTPUT:
+ RETVAL
+
+void
+date(Transaction* SELF, ...)
+ PPCODE:
+ if (1 < items) {
+ SELF->date = SvIV(ST(1));
+ }
+ if (GIMME_V == G_ARRAY) {
+ GDate* d = g_date_new_julian(SELF->date);
+ mXPUSHp("day", 3);
+ mXPUSHi(g_date_get_day(d));
+ mXPUSHp("month", 5);
+ mXPUSHi(g_date_get_month(d));
+ mXPUSHp("year", 4);
+ mXPUSHi(g_date_get_year(d));
+ g_date_free(d);
+ XSRETURN(6);
+ } else {
+ XSRETURN_IV(SELF->date);
+ }
+
+const gchar*
+wording(Transaction* SELF, ...)
+ CODE:
+ if (1 < items) {
+ if (SELF->wording) g_free(SELF->wording);
+ SELF->wording = g_strdup(SvGchar_ptr(ST(1)));
+ }
+ RETVAL = SELF->wording ? SELF->wording : "";
+ OUTPUT:
+ RETVAL
+
+const gchar*
+info(Transaction* SELF, ...)
+ CODE:
+ if (1 < items) {
+ if (SELF->info) g_free(SELF->info);
+ SELF->info = g_strdup(SvGchar_ptr(ST(1)));
+ }
+ RETVAL = SELF->info ? SELF->info : "";
+ OUTPUT:
+ RETVAL
+
+GObject*
+open(Transaction* SELF)
+ CODE:
+ RETVAL = G_OBJECT(create_deftransaction_window(NULL, TRANSACTION_EDIT_MODIFY));
+ deftransaction_set_transaction(GTK_WIDGET(RETVAL), SELF);
+ OUTPUT:
+ RETVAL
+
+Transaction*
+pair_with(Transaction* SELF, Transaction* other, ...)
+ PREINIT:
+ int i;
+ GList* list = NULL;
+ CODE:
+ if (2 < items) {
+ list = g_list_append(list, other);
+ for (i = 2; i < items; ++i) {
+ Transaction* ptr = NULL;
+ SV* sv = ST(i);
+ EXT_P2C_OBJECT("HomeBank::Transaction", sv, ptr, Transaction*);
+ list = g_list_append(list, ptr);
+ }
+ other = ui_dialog_transaction_xfer_select_child(list);
+ }
+ if (other) {
+ transaction_xfer_change_to_child(SELF, other);
+ SELF->paymode = PAYMODE_INTXFER;
+ }
+ RETVAL = other;
+ OUTPUT:
+ RETVAL
+ CLEANUP:
+ g_list_free(list);
+
+void
+dump(Transaction* SELF)
+ CODE:
+ g_print("txn: %p (%s) at %u (%d/%d) flags:%d, paymode:%d, kpay:%d, kcat:%d", SELF,
+ SELF->wording, SELF->date, SELF->kacc, SELF->kxferacc, SELF->flags, SELF->paymode, SELF->kpay, SELF->kcat);
+
--- /dev/null
+
+#include <stdarg.h>
+
+#include "ext-value.h"
+
+
+const GValue* ext_value_undef()
+{
+ static GValue v = G_VALUE_INIT;
+ return &v;
+}
+
+const GValue* ext_value_true()
+{
+ static GValue v = G_VALUE_INIT;
+ if (!G_VALUE_HOLDS_BOOLEAN(&v)) EXT_BOOLEAN(&v, TRUE);
+ return &v;
+}
+
+const GValue* ext_value_false()
+{
+ static GValue v = G_VALUE_INIT;
+ if (!G_VALUE_HOLDS_BOOLEAN(&v)) EXT_BOOLEAN(&v, FALSE);
+ return &v;
+}
+
+
+GValue* EXT_LIST(GValue* v, ...)
+{
+ GPtrArray* a = g_ptr_array_new();
+
+ va_list ap;
+ va_start(ap, v);
+
+ for (;;) {
+ GValue* item = (GValue*)va_arg(ap, GValue*);
+ if (!item) break;
+ g_ptr_array_add(a, item);
+ }
+
+ va_end(ap);
+
+ return EXT_ARRAY(v, a);
+}
+
+GValue* EXT_HASH(GValue* v, ...)
+{
+ GHashTable* h = g_hash_table_new(g_str_hash, g_str_equal);
+
+ va_list ap;
+ va_start(ap, v);
+
+ for (;;) {
+ gchar* key = (gchar*)va_arg(ap, gchar*);
+ if (!key) break;
+ GValue* val = (GValue*)va_arg(ap, GValue*);
+ g_hash_table_insert(h, key, val);
+ }
+
+ va_end(ap);
+
+ return EXT_HASH_TABLE(v, h);
+}
+
+GValue* EXT_JULIAN(GValue* v, guint32 d)
+{
+ GDate* date = g_date_new_julian(d);
+ return EXT_DATE(v, date);
+}
+
+
+#define obj(CTYPE, _2, _3, _4, PREFIX) \
+GType PREFIX##get_type() \
+{ \
+ static GType type = 0; \
+ if (type == 0) \
+ type = g_pointer_type_register_static(#CTYPE); \
+ return type; \
+}
+#include "ext-value.h"
+#undef obj
+
--- /dev/null
+
+#ifndef __EXT_VALUE_H__
+#define __EXT_VALUE_H__
+
+#include "homebank.h"
+
+
+#define DA_TYPE_ACC (da_acc_get_type())
+#define DA_TYPE_TRANSACTION (da_transaction_get_type())
+
+#define obj(_1, _2, _3, _4, PREFIX) GType PREFIX##get_type(void);
+#include "ext-value.h"
+#undef obj
+
+
+#define val(CTYPE, NAME, PART, GTYPE) \
+static inline GValue* EXT_##NAME(GValue* v, CTYPE c) { \
+ g_value_init(v, GTYPE); \
+ g_value_set_##PART(v, c); \
+ return v; \
+}
+#define obj(CTYPE, NAME, PART, GTYPE, _5) val(CTYPE*, NAME, PART, GTYPE)
+#include "ext-value.h"
+#undef val
+#undef obj
+
+
+const GValue* ext_value_undef(void);
+const GValue* ext_value_true(void);
+const GValue* ext_value_false(void);
+
+#define EXT_UNDEF (ext_value_undef())
+#define EXT_TRUE (ext_value_true())
+#define EXT_FALSE (ext_value_false())
+
+GValue* EXT_LIST(GValue* v, ...);
+GValue* EXT_HASH(GValue* v, ...);
+GValue* EXT_JULIAN(GValue* v, guint32 d);
+
+
+#else
+
+#ifdef val
+// C type, name, fundamental, GType
+val(gboolean, BOOLEAN, boolean, G_TYPE_BOOLEAN)
+val(gint, INT, int, G_TYPE_INT)
+val(guint, UINT, uint, G_TYPE_UINT)
+val(gdouble, DOUBLE, double, G_TYPE_DOUBLE)
+val(gchar, CHAR, schar, G_TYPE_CHAR)
+val(gchar*, STRING, string, G_TYPE_STRING)
+val(GPtrArray*, ARRAY, boxed, G_TYPE_PTR_ARRAY)
+val(GHashTable*, HASH_TABLE, boxed, G_TYPE_HASH_TABLE)
+val(GDate*, DATE, boxed, G_TYPE_DATE)
+val(void*, OBJECT, object, G_TYPE_OBJECT)
+#endif
+
+#ifdef obj
+// C type, name, fundamental, GType, prefix
+obj(Account, ACCOUNT, pointer, DA_TYPE_ACC, da_acc_)
+obj(Transaction, TRANSACTION, pointer, DA_TYPE_TRANSACTION, da_transaction_)
+#endif
+
+#endif
+
--- /dev/null
+
+#include <stdarg.h>
+#include <string.h>
+#include <gmodule.h>
+#include <gtk/gtk.h>
+
+#include "ext.h"
+
+extern struct Preferences *PREFS;
+
+
+const int _hook_recursion_soft_limit = 50;
+const int _hook_recursion_hard_limit = 99;
+
+
+struct PluginEngine
+{
+ const gchar* type;
+ PluginEngineInitializer init;
+ PluginEngineTerminator term;
+ PluginEngineFileChecker check_file;
+ PluginMetadataReader read_metadata;
+ PluginLoader load_plugin;
+ PluginUnloader unload_plugin;
+ PluginExecutor execute;
+ PluginHookCaller call_hook;
+};
+
+
+static GList* _engine_list = NULL;
+static GHashTable* _loaded_plugins = NULL;
+
+
+void ext_init(int* argc, char** argv[], char** env[])
+{
+ GList *list = g_list_first(_engine_list);
+ while (list)
+ {
+ struct PluginEngine* engine = list->data;
+ engine->init(argc, argv, env);
+ list = g_list_next(list);
+ }
+ if (!_loaded_plugins) {
+ _loaded_plugins = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+ }
+}
+
+void ext_term(void)
+{
+ GList *list = g_list_first(_engine_list);
+ while (list)
+ {
+ struct PluginEngine* engine = list->data;
+ engine->term();
+ list = g_list_next(list);
+ }
+ g_list_free(_engine_list);
+ _engine_list = NULL;
+
+ if (_loaded_plugins) {
+ g_hash_table_unref(_loaded_plugins);
+ _loaded_plugins = NULL;
+ }
+}
+
+void ext_register(const gchar* type,
+ PluginEngineInitializer init,
+ PluginEngineTerminator term,
+ PluginEngineFileChecker check_file,
+ PluginMetadataReader read_metadata,
+ PluginLoader load_plugin,
+ PluginUnloader unload_plugin,
+ PluginExecutor execute,
+ PluginHookCaller call_hook)
+{
+ struct PluginEngine* engine = g_malloc0(sizeof(struct PluginEngine));
+ engine->type = type;
+ engine->init = init;
+ engine->term = term;
+ engine->check_file = check_file;
+ engine->read_metadata = read_metadata;
+ engine->load_plugin = load_plugin;
+ engine->unload_plugin = unload_plugin;
+ engine->execute = execute;
+ engine->call_hook = call_hook;
+ _engine_list = g_list_append(_engine_list, engine);
+}
+
+
+static struct PluginEngine* _get_engine_for_plugin(const gchar* plugin_filename)
+{
+ if (!plugin_filename) {
+ return NULL;
+ }
+
+ GList *list = g_list_first(_engine_list);
+ while (list) {
+ struct PluginEngine* engine = list->data;
+ if (engine->check_file(plugin_filename)) {
+ return engine;
+ }
+ list = g_list_next(list);
+ }
+ return NULL;
+}
+
+static void _read_directory(const gchar* directory, GHashTable* hash)
+{
+ GDir* dir = g_dir_open(directory, 0, NULL);
+ if (!dir) return;
+
+ const gchar* filename;
+ while ((filename = g_dir_read_name(dir))) {
+ gchar* full = g_build_filename(directory, filename, NULL);
+ if (g_file_test(full, G_FILE_TEST_IS_REGULAR) && _get_engine_for_plugin(filename)) {
+ g_hash_table_insert(hash, g_strdup(filename), NULL);
+ }
+ g_free(full);
+ }
+ g_dir_close(dir);
+}
+
+gchar** ext_list_plugins()
+{
+ GHashTable* hash = g_hash_table_new(g_str_hash, g_str_equal);
+
+ gchar** it;
+ for (it = PREFS->ext_path; it && *it; ++it) {
+ _read_directory(*it, hash);
+ }
+
+ GList* list = g_list_sort(g_hash_table_get_keys(hash), (GCompareFunc)g_utf8_collate);
+ g_hash_table_unref(hash);
+
+ guint len = g_list_length(list);
+ gchar** strv = g_new0(gchar**, len + 1);
+ int i;
+ for (i = 0; i < len; ++i) {
+ strv[i] = g_list_nth_data(list, i);
+ }
+ g_list_free(list);
+
+ return strv;
+}
+
+gchar* ext_find_plugin(const gchar* plugin_filename)
+{
+ if (!plugin_filename) return NULL;
+
+ gchar** it;
+ for (it = PREFS->ext_path; *it; ++it) {
+ if (!g_path_is_absolute(*it)) continue;
+
+ gchar* full = g_build_filename(*it, plugin_filename, NULL);
+ if (g_file_test(full, G_FILE_TEST_IS_REGULAR)) {
+ return full;
+ }
+ g_free(full);
+ }
+
+ return NULL;
+}
+
+GHashTable* ext_read_plugin_metadata(const gchar* plugin_filename)
+{
+ gchar* full = ext_find_plugin(plugin_filename);
+ if (!full) return NULL;
+
+ GHashTable* ret = NULL;
+
+ struct PluginEngine* engine = _get_engine_for_plugin(plugin_filename);
+ if (engine && engine->read_metadata) {
+ ret = engine->read_metadata(full);
+ }
+
+ g_free(full);
+ return ret;
+}
+
+gint ext_load_plugin(const gchar* plugin_filename)
+{
+ gchar* full = ext_find_plugin(plugin_filename);
+ if (!full) return -1;
+
+ gint ret = -1;
+
+ struct PluginEngine* engine = _get_engine_for_plugin(plugin_filename);
+ if (engine && engine->load_plugin && engine->load_plugin(full) == 0) {
+ g_hash_table_insert(_loaded_plugins, g_strdup(plugin_filename), NULL);
+ ret = 0;
+ }
+
+ g_free(full);
+ return ret;
+}
+
+void ext_unload_plugin(const gchar* plugin_filename)
+{
+ gchar* full = ext_find_plugin(plugin_filename);
+ if (!full) return;
+
+ struct PluginEngine* engine = _get_engine_for_plugin(plugin_filename);
+ if (engine && engine->unload_plugin) {
+ engine->unload_plugin(full);
+ }
+
+ g_free(full);
+ g_hash_table_remove(_loaded_plugins, plugin_filename);
+}
+
+gboolean ext_is_plugin_loaded(const gchar* plugin_filename)
+{
+ return g_hash_table_contains(_loaded_plugins, plugin_filename);
+}
+
+void ext_execute_action(const gchar* plugin_filename)
+{
+ gchar* full = ext_find_plugin(plugin_filename);
+ if (!full) return;
+
+ struct PluginEngine* engine = _get_engine_for_plugin(plugin_filename);
+ if (engine && engine->execute) {
+ engine->execute(full);
+ }
+
+ g_free(full);
+}
+
+void ext_hook(const gchar* hook_id, ...)
+{
+ GList *list = NULL;
+
+ va_list ap;
+ va_start(ap, hook_id);
+ for (;;) {
+ GValue* val = (GValue*)va_arg(ap, GValue*);
+ if (!val) break;
+ list = g_list_append(list, val);
+ }
+ va_end(ap);
+
+ ext_vhook(hook_id, list);
+ g_list_free(list);
+}
+
+void ext_vhook(const gchar* hook_id, GList* args)
+{
+ static int recursion_level = 0;
+
+ if (_hook_recursion_hard_limit <= recursion_level) {
+ return;
+ } else if (_hook_recursion_soft_limit <= recursion_level) {
+ int level = recursion_level;
+ recursion_level = -1;
+ GValue val_level = G_VALUE_INIT;
+ ext_hook("deep_hook_recursion", EXT_INT(&val_level, level), NULL);
+ recursion_level = level;
+ }
+
+ ++recursion_level;
+
+ g_print("ext_hook: %s (level %d)\n", hook_id, recursion_level);
+ GList *list = g_list_first(_engine_list);
+ while (list)
+ {
+ struct PluginEngine* engine = list->data;
+ engine->call_hook(hook_id, args);
+ list = g_list_next(list);
+ }
+
+ --recursion_level;
+}
+
+gboolean ext_has(const gchar* feature)
+{
+#ifdef OFX_ENABLE
+ if (0 == g_utf8_collate(feature, "libofx")) {
+ return TRUE;
+ }
+#endif
+#ifdef PERL_ENABLE
+ if (0 == g_utf8_collate(feature, "perl")) {
+ return TRUE;
+ }
+#endif
+ return FALSE;
+}
+
+
+void* ext_symbol_lookup(const gchar* symbol)
+{
+ static GModule* module = NULL;
+ if (!module) module = g_module_open(NULL, 0);
+
+ void* ptr;
+ if (module && g_module_symbol(module, symbol, &ptr)) {
+ return ptr;
+ }
+
+ return NULL;
+}
+
+
+void ext_run_modal(const gchar* title, const gchar* text, const gchar* type)
+{
+ GtkMessageType t = GTK_MESSAGE_INFO;
+ if (0 == g_utf8_collate(type, "error")) {
+ t = GTK_MESSAGE_ERROR;
+ }
+ if (0 == g_utf8_collate(type, "warn")) {
+ t = GTK_MESSAGE_WARNING;
+ }
+ if (0 == g_utf8_collate(type, "question")) {
+ t = GTK_MESSAGE_QUESTION;
+ }
+
+ GtkWidget* dialog = gtk_message_dialog_new(NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT, t,
+ GTK_BUTTONS_CLOSE, "%s", text);
+ if (title) {
+ gtk_window_set_title(GTK_WINDOW(dialog), title);
+ }
+
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+}
+
--- /dev/null
+
+#ifndef __EXT_H__
+#define __EXT_H__
+
+#include <glib.h>
+
+#include "ext-value.h"
+
+
+typedef gint (*PluginEngineInitializer)(int* argc, char** argv[], char** env[]);
+typedef void (*PluginEngineTerminator)();
+typedef gboolean (*PluginEngineFileChecker)(const gchar* plugin_filepath);
+typedef GHashTable* (*PluginMetadataReader)(const gchar* plugin_filepath);
+typedef gint (*PluginLoader)(const gchar* plugin_filepath);
+typedef void (*PluginUnloader)(const gchar* plugin_filepath);
+typedef void (*PluginExecutor)(const gchar* plugin_filepath);
+typedef void (*PluginHookCaller)(const gchar* hook_id, GList* args);
+
+void ext_init(int* argc, char** argv[], char** env[]);
+void ext_term(void);
+void ext_register(const gchar* type,
+ PluginEngineInitializer,
+ PluginEngineTerminator,
+ PluginEngineFileChecker,
+ PluginMetadataReader,
+ PluginLoader,
+ PluginUnloader,
+ PluginExecutor,
+ PluginHookCaller);
+
+
+gchar** ext_list_plugins(void);
+gchar* ext_find_plugin(const gchar* plugin_filename);
+GHashTable* ext_read_plugin_metadata(const gchar* plugin_filename);
+gint ext_load_plugin(const gchar* plugin_filename);
+void ext_unload_plugin(const gchar* plugin_filename);
+gboolean ext_is_plugin_loaded(const gchar* plugin_filename);
+void ext_execute_action(const gchar* plugin_filename);
+void ext_hook(const gchar* hook_id, ...);
+void ext_vhook(const gchar* hook_id, GList* args);
+gboolean ext_has(const gchar* feature);
+void* ext_symbol_lookup(const gchar* symbol);
+void ext_run_modal(const gchar* title, const gchar* text, const gchar* type);
+
+#endif
+
#include "homebank.h"
#include "hb-account.h"
+#include "ext.h"
+#include "refcount.h"
+
/****************************************************************************/
/* Debug macros */
/****************************************************************************/
Account *
da_acc_clone(Account *src_item)
{
-Account *new_item = g_memdup(src_item, sizeof(Account));
+Account *new_item = rc_dup(src_item, sizeof(Account));
DB( g_print("da_acc_clone\n") );
if(new_item)
da_acc_free(Account *item)
{
DB( g_print("da_acc_free\n") );
- if(item != NULL)
+ if(rc_unref(item))
{
DB( g_print(" => %d, %s\n", item->key, item->name) );
g_free(item->name);
g_free(item->number);
g_free(item->bankname);
- g_free(item);
+ rc_free(item);
}
}
da_acc_malloc(void)
{
DB( g_print("da_acc_malloc\n") );
- return g_malloc0(sizeof(Account));
+ return rc_alloc(sizeof(Account));
}
*new_key = item->key;
g_hash_table_insert(GLOBALS->h_acc, new_key, item);
+ GValue item_val = G_VALUE_INIT;
+ ext_hook("account_inserted", EXT_ACCOUNT(&item_val, item), NULL);
+
return TRUE;
}
DB( g_print(" -> insert id: %d\n", *new_key) );
g_hash_table_insert(GLOBALS->h_acc, new_key, item);
+
+ GValue item_val = G_VALUE_INIT;
+ ext_hook("account_inserted", EXT_ACCOUNT(&item_val, item), NULL);
+
return TRUE;
}
}
#include "homebank.h"
#include "hb-archive.h"
+#include "ext.h"
+#include "refcount.h"
+
/****************************************************************************/
/* Debug macros */
/****************************************************************************/
Archive *da_archive_malloc(void)
{
- return g_malloc0(sizeof(Archive));
+ return rc_alloc(sizeof(Archive));
}
Archive *da_archive_clone(Archive *src_item)
{
-Archive *new_item = g_memdup(src_item, sizeof(Archive));
+Archive *new_item = rc_dup(src_item, sizeof(Archive));
if(new_item)
{
void da_archive_free(Archive *item)
{
- if(item != NULL)
+ if(rc_unref(item))
{
if(item->wording != NULL)
g_free(item->wording);
- g_free(item);
+ rc_free(item);
}
}
#include "homebank.h"
#include "hb-assign.h"
+#include "ext.h"
+#include "refcount.h"
+
#define MYDEBUG 0
#if MYDEBUG
da_asg_free(Assign *item)
{
DB( g_print("da_asg_free\n") );
- if(item != NULL)
+ if(rc_unref(item))
{
DB( g_print(" => %d, %s\n", item->key, item->name) );
g_free(item->name);
- g_free(item);
+ rc_free(item);
}
}
da_asg_malloc(void)
{
DB( g_print("da_asg_malloc\n") );
- return g_malloc0(sizeof(Assign));
+ return rc_alloc(sizeof(Assign));
}
#include "homebank.h"
#include "hb-category.h"
+#include "ext.h"
+#include "refcount.h"
+
/****************************************************************************/
/* Debug macros */
Category *
da_cat_clone(Category *src_item)
{
-Category *new_item = g_memdup(src_item, sizeof(Category));
+Category *new_item = rc_dup(src_item, sizeof(Category));
DB( g_print("da_cat_clone\n") );
if(new_item)
da_cat_free(Category *item)
{
DB( g_print("da_cat_free\n") );
- if(item != NULL)
+ if(rc_unref(item))
{
DB( g_print(" => %d, %s\n", item->key, item->name) );
g_free(item->name);
- g_free(item);
+ rc_free(item);
}
}
da_cat_malloc(void)
{
DB( g_print("da_cat_malloc\n") );
- return g_malloc0(sizeof(Category));
+ return rc_alloc(sizeof(Category));
}
#include "homebank.h"
#include "hb-payee.h"
+#include "ext.h"
+#include "refcount.h"
+
/****************************************************************************/
/* Debug macros */
da_pay_free(Payee *item)
{
DB( g_print("da_pay_free\n") );
- if(item != NULL)
+ if(rc_unref(item))
{
DB( g_print(" => %d, %s\n", item->key, item->name) );
g_free(item->name);
- g_free(item);
+ rc_free(item);
}
}
da_pay_malloc(void)
{
DB( g_print("da_pay_malloc\n") );
- return g_malloc0(sizeof(Payee));
+ return rc_alloc(sizeof(Payee));
}
g_free(PREFS->minor_cur.decimal_char);
g_free(PREFS->minor_cur.grouping_char);
+ g_strfreev(PREFS->ext_path);
+ g_list_free_full(PREFS->ext_whitelist, g_free);
+
memset(PREFS, 0, sizeof(struct Preferences));
}
PREFS->vehicle_unit_ismile = FALSE;
PREFS->vehicle_unit_isgal = FALSE;
+ gchar** plugin_path = g_new0(gchar**, 4);
+ i = 0;
+ const gchar* env = g_getenv("HOMEBANK_PLUGINS");
+ if (env) {
+ if (g_path_is_absolute(env)) {
+ plugin_path[i++] = g_strdup(env);
+ } else {
+ gchar* cur = g_get_current_dir();
+ plugin_path[i++] = g_build_filename(cur, env, NULL);
+ g_free(cur);
+ }
+ }
+ plugin_path[i++] = g_build_filename(homebank_app_get_config_dir(), "plugins", NULL);
+ plugin_path[i++] = g_build_filename(homebank_app_get_pkglib_dir(), "plugins", NULL);
+ PREFS->ext_path = plugin_path;
+ PREFS->ext_whitelist = NULL;
+
_homebank_pref_createformat();
_homebank_pref_init_measurement_units();
}
}
-
-
-
static void homebank_pref_get_boolean(
GKeyFile *key_file,
const gchar *group_name,
//PREFS->chart_legend = g_key_file_get_boolean (keyfile, group, "Legend", NULL);
+ group = "Plugins";
+ {
+ DB( g_print(" -> ** Plugins\n") );
+
+ gchar** strv = g_key_file_get_string_list(keyfile, group, "Path", NULL, NULL);
+ if (strv) {
+ g_strfreev(PREFS->ext_path);
+ PREFS->ext_path = strv;
+ }
+
+ strv = g_key_file_get_string_list(keyfile, group, "Whitelist", NULL, NULL);
+ if (strv) {
+ gchar** it;
+ for (it = strv; it && *it; ++it) {
+ PREFS->ext_whitelist = g_list_append(PREFS->ext_whitelist, g_strdup(*it));
+ }
+ g_strfreev(strv);
+ }
+ }
+
+
/*
#if MYDEBUG == 1
gsize length;
//group = "Chart";
//g_key_file_set_boolean (keyfile, group, "Legend", PREFS->chart_legend);
+ group = "Plugins";
+ {
+ g_key_file_set_string_list(keyfile, group, "Path", (const gchar* const*)PREFS->ext_path, g_strv_length(PREFS->ext_path));
+
+ gsize len = g_list_length(PREFS->ext_whitelist);
+ gchar** strv = g_new0(gchar*, len + 1);
+ guint i;
+
+ for (i = 0; i < len; ++i) {
+ strv[i] = g_list_nth_data(PREFS->ext_whitelist, i);
+ }
+ g_key_file_set_string_list(keyfile, group, "Whitelist", (const gchar* const*)strv, len);
+ g_free(strv);
+ }
+
//g_key_file_set_string (keyfile, group, "", PREFS->);
//g_key_file_set_boolean (keyfile, group, "", PREFS->);
//g_key_file_set_integer (keyfile, group, "", PREFS->);
gchar *vehicle_unit_100;
gchar *vehicle_unit_distbyvol;
+ // plugins
+ gchar** ext_path;
+ GList* ext_whitelist;
+
};
#include "homebank.h"
#include "hb-tag.h"
+#include "ext.h"
+#include "refcount.h"
+
#define MYDEBUG 0
#if MYDEBUG
void da_tag_free(Tag *item)
{
DB( g_print("da_tag_free\n") );
- if(item != NULL)
+ if(rc_unref(item))
{
DB( g_print(" => %d, %s\n", item->key, item->name) );
g_free(item->name);
- g_free(item);
+ rc_free(item);
}
}
Tag *da_tag_malloc(void)
{
DB( g_print("da_tag_malloc\n") );
- return g_malloc0(sizeof(Tag));
+ return rc_alloc(sizeof(Tag));
}
#include "hb-transaction.h"
#include "hb-tag.h"
+#include "ext.h"
+#include "refcount.h"
+
/****************************************************************************/
/* Debug macros */
/****************************************************************************/
void
da_transaction_free(Transaction *item)
{
- if(item != NULL)
+ if(rc_unref(item))
{
da_transaction_clean(item);
- g_free(item);
+ rc_free(item);
}
}
Transaction *
da_transaction_malloc(void)
{
- return g_malloc0(sizeof(Transaction));
+ return rc_alloc(sizeof(Transaction));
}
Transaction *da_transaction_clone(Transaction *src_item)
{
-Transaction *new_item = g_memdup(src_item, sizeof(Transaction));
+Transaction *new_item = rc_dup(src_item, sizeof(Transaction));
guint count;
DB( g_print("da_transaction_clone\n") );
if(treeview != NULL)
transaction_add_treeview(child, treeview, ope->kacc);
+
+ GValue txn_value = G_VALUE_INIT;
+ ext_hook("transaction_inserted", EXT_TRANSACTION(&txn_value, child), NULL);
}
}
{
transaction_xfer_search_or_add_child(newope, treeview);
}
+
+ GValue txn_value = G_VALUE_INIT;
+ ext_hook("transaction_inserted", EXT_TRANSACTION(&txn_value, newope), NULL);
}
}
#include "hb-transaction.h"
#include "hb-xml.h"
+#include "ext.h"
+
/****************************************************************************/
/* Debug macros */
/****************************************************************************/
DB( g_print("\n[hb-xml] homebank_load_xml\n") );
+ GValue filename_val = G_VALUE_INIT;
+ ext_hook("load_file", EXT_STRING(&filename_val, filename), NULL);
+
retval = XML_OK;
if (!g_file_get_contents (filename, &buffer, &length, &error))
{
gchar *outstr;
gint retval = XML_OK;
+ GValue filename_val = G_VALUE_INIT;
+ ext_hook("save_file", EXT_STRING(&filename_val, filename), NULL);
+
io = g_io_channel_new_file(filename, "w", NULL);
if(io == NULL)
{
*/
#include "homebank.h"
+#include "ext.h"
#include "dsp_mainwindow.h"
#include "hb-preferences.h"
static gchar *locale_dir = NULL;
static gchar *help_dir = NULL;
static gchar *datas_dir = NULL;
+static gchar *pkglib_dir = NULL;
//#define MARKUP_STRING "<span size='small'>%s</span>"
"prf-display",
"prf-euro",
"prf-report",
- "prf-import"
+ "prf-import",
+ "prf-plugins"
};
factory = gtk_icon_factory_new ();
return datas_dir;
}
+const gchar *
+homebank_app_get_pkglib_dir (void)
+{
+ return pkglib_dir;
+}
+
/* build package paths at runtime */
static void
pixmaps_dir = g_build_filename (prefix, "share", PACKAGE, "icons", NULL);
help_dir = g_build_filename (prefix, "share", PACKAGE, "help", NULL);
datas_dir = g_build_filename (prefix, "share", PACKAGE, "datas", NULL);
+ pkglib_dir = g_build_filename (prefix, "lib", PACKAGE, NULL);
#ifdef PORTABLE_APP
DB( g_print("- app is portable under windows\n") );
config_dir = g_build_filename(prefix, "config", NULL);
pixmaps_dir = g_build_filename (DATA_DIR, PACKAGE, "icons", NULL);
help_dir = g_build_filename (DATA_DIR, PACKAGE, "help", NULL);
datas_dir = g_build_filename (DATA_DIR, PACKAGE, "datas", NULL);
- config_dir = g_build_filename(g_get_user_config_dir(), HB_DATA_PATH, NULL);
-
+ config_dir = g_build_filename (g_get_user_config_dir(), HB_DATA_PATH, NULL);
+ pkglib_dir = g_build_filename (PKGLIB_DIR, NULL);
+
//#870023 Ubuntu packages the help files in "/usr/share/doc/homebank-data/help/" for some strange reason
if(! g_file_test(help_dir, (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
{
DB( g_print("- locale_dir : %s\n", locale_dir) );
DB( g_print("- help_dir : %s\n", help_dir) );
DB( g_print("- datas_dir : %s\n", datas_dir) );
+ DB( g_print("- pkglib_dir : %s\n", pkglib_dir) );
}
g_free (pixmaps_dir);
g_free (locale_dir);
g_free (help_dir);
+ g_free (pkglib_dir);
}
int
-main (int argc, char *argv[])
+main (int argc, char *argv[], char *env[])
{
GOptionContext *option_context;
GOptionGroup *option_group;
/* change the locale if a language is specified */
language_init (PREFS->language);
+ DB( g_print(" -> loading plugins\n") );
+ ext_init(&argc, &argv, &env);
+
+ GList* it;
+ for (it = PREFS->ext_whitelist; it; it = g_list_next(it)) {
+ ext_load_plugin(it->data);
+ }
+
+ gchar** plugins = ext_list_plugins();
+ gchar** plugins_it;
+ for (plugins_it = plugins; *plugins_it; ++plugins_it) {
+ gboolean loaded = ext_is_plugin_loaded(*plugins_it);
+ g_print("found plugin: %s, loaded: %d\n", *plugins_it, loaded);
+ }
+ g_strfreev(plugins);
+
if( PREFS->showsplash == TRUE )
{
splash = homebank_construct_splash();
mainwin = (GtkWidget *)create_hbfile_window (NULL);
+ GValue mainwin_val = G_VALUE_INIT;
+ ext_hook("create_main_window", EXT_OBJECT(&mainwin_val, mainwin), NULL);
+
if(mainwin)
{
/* update the mainwin display */
ui_mainwindow_update(mainwin, GINT_TO_POINTER(UF_TITLE+UF_SENSITIVE+UF_BALANCE+UF_VISUAL));
- DB( g_print(" -> gtk_main()\n" ) );
+ ext_hook("enter_main_loop", NULL);
+ DB( g_print(" -> gtk_main()\n" ) );
gtk_main ();
+
+ ext_hook("exit_main_loop", NULL);
}
+ DB( g_print(" -> unloading plugins\n") );
+ ext_term();
+
}
const gchar *homebank_app_get_locale_dir (void);
const gchar *homebank_app_get_help_dir (void);
const gchar *homebank_app_get_datas_dir (void);
+const gchar *homebank_app_get_pkglib_dir (void);
guint32 homebank_app_date_get_julian(void);
/* - - - - obsolete things - - - - */
--- /dev/null
+
+#ifndef __REFCOUNT_H__
+#define __REFCOUNT_H__
+
+#include <glib.h>
+
+
+static inline gpointer rc_alloc(size_t size)
+{
+ gpointer chunk = g_malloc0(size + sizeof(long));
+ (*(long*)chunk) = 1;
+ //g_print("ALLOC: %p (ref %ld)\n", (long*)chunk + 1, *(long*)chunk);
+ return (long*)chunk + 1;
+}
+
+static inline gpointer rc_ref(gpointer p)
+{
+ //g_print(" REF: %p (ref %ld)\n", p, *((long*)p - 1));
+ if (p) {
+ ++(*((long*)p - 1));
+ }
+ return p;
+}
+
+static inline gboolean rc_unref(gpointer p)
+{
+ //g_print("UNREF: %p (ref %ld)\n", p, *((long*)p - 1));
+ if (p && --(*((long*)p - 1)) <= 0) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static inline void rc_free(gpointer p)
+{
+ //g_print(" FREE: %p (ref %ld)\n", p, *((long*)p - 1));
+ g_free((long*)p - 1);
+}
+
+static inline gpointer rc_dup(gpointer p, size_t size)
+{
+ if (p) {
+ gpointer chunk = (long*)p - 1;
+ gpointer new_chunk = g_memdup(chunk, size + sizeof(long));
+ *(long*)new_chunk = 1;
+ //g_print(" DUP: %p (ref %ld) -> %p (ref %ld)\n", p, *((long*)p - 1), (long*)new_chunk + 1, *(long*)new_chunk);
+ return (long*)new_chunk + 1;
+ }
+ //g_print(" DUP: NULL\n");
+ return NULL;
+}
+
+
+#endif
--- /dev/null
+
+TYPEMAP
+
+Account* T_HB_OBJECT
+Transaction* T_HB_OBJECT
+GObject* T_GOBJECT
+
+gint T_IV
+guint T_UV
+gdouble T_NV
+gboolean T_GBOOLEAN
+gchar T_CHAR
+gchar* T_GCHAR_PTR
+const gchar* T_GCHAR_PTR
+
+GPtrArray* T_GPTRARRAY
+GHashTable* T_GHASHTABLE
+
+
+INPUT
+
+T_HB_OBJECT
+ EXT_P2C_OBJECT(\"HomeBank::${ my ($t) = $ntype =~ /(.+)Ptr$/; \$t }\", $arg, $var, $type);
+
+T_GOBJECT
+ $var = SvGobject($arg);
+
+T_GCHAR_PTR
+ $var = SvGchar_ptr($arg);
+
+T_GBOOLEAN
+ $var = SvGboolean($arg);
+
+T_GPTRARRAY
+ $var = SvGptrarray($arg);
+
+T_GHASHTABLE
+ $var = SvGhashtable($arg);
+
+
+OUTPUT
+
+T_HB_OBJECT
+ EXT_C2P_OBJECT(\"HomeBank::${ my ($t) = $ntype =~ /(.+)Ptr$/; \$t }\", $arg, rc_ref($var));
+
+T_GOBJECT
+ $arg = newSVgobject($var);
+
+T_GCHAR_PTR
+ $arg = newSVgchar_ptr($var);
+
+T_GBOOLEAN
+ $arg = newSVgboolean($var);
+
+T_GPTRARRAY
+ $arg = newSVgptrarray($var);
+
+T_GHASHTABLE
+ $arg = newSVghashtable($var);
+
#include "dsp_mainwindow.h"
#include "gtk-chart-colors.h"
+#include "ext.h"
/****************************************************************************/
/* Debug macros */
enum
{
- PREF_GENERAL,
- PREF_INTERFACE,
- PREF_COLUMNS,
- PREF_DISPLAY,
- PREF_IMPORT,
- PREF_REPORT,
- PREF_EURO,
- PREF_MAX
+ EXT_COLUMN_ENABLED = 0,
+ EXT_COLUMN_LABEL,
+ EXT_COLUMN_TOOLTIP,
+ EXT_COLUMN_PLUGIN_NAME,
+ EXT_NUM_COLUMNS
};
GdkPixbuf *pref_pixbuf[PREF_MAX];
"prf-import",
"prf-report",
"prf-euro", // to be renamed
+"prf-plugins",
//"prf_charts.svg"
};
N_("Display format"),
N_("Import/Export"),
N_("Report"),
-N_("Euro minor")
+N_("Euro minor"),
+N_("Plugins")
//
};
static void list_txn_colpref_get(GtkTreeView *treeview, gboolean *columns);
+static void list_ext_colpref_get(GtkTreeView *treeview, GList **columns);
//PREFS->chart_legend = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->CM_chartlegend));
+ list_ext_colpref_get(GTK_TREE_VIEW(data->PI_plugin_columns), &(PREFS->ext_whitelist));
}
/*
return(container);
}
+
+void plugin_execute_action(GtkTreeView* treeview, GtkTreePath* path, GtkTreeViewColumn* col, gpointer userdata);
+
+static void
+toggle_plugin(GtkCellRendererToggle *cell, gchar* path_str, gpointer data)
+{
+ GtkTreeModel *model = (GtkTreeModel*)data;
+ GtkTreeIter iter;
+ GtkTreePath *path = gtk_tree_path_new_from_string(path_str);
+
+ const gchar* plugin;
+
+ gtk_tree_model_get_iter(model, &iter, path);
+ gtk_tree_model_get(model, &iter, EXT_COLUMN_PLUGIN_NAME, &plugin, -1);
+
+ gboolean enabled = ext_is_plugin_loaded(plugin);
+ if (enabled) {
+ ext_unload_plugin(plugin);
+ enabled = FALSE;
+ } else {
+ enabled = (ext_load_plugin(plugin) == 0);
+ if (!enabled) {
+ ext_run_modal(_("Plugin Error"), _("The plugin failed to load properly."), "error");
+ }
+ }
+
+ /* set new value */
+ gtk_list_store_set(GTK_LIST_STORE (model), &iter, EXT_COLUMN_ENABLED, enabled, -1);
+
+ /* clean up */
+ gtk_tree_path_free(path);
+}
+
+
+void plugin_execute_action(GtkTreeView* treeview, GtkTreePath* path, GtkTreeViewColumn* col, gpointer userdata)
+{
+ GtkTreeModel* model = gtk_tree_view_get_model(treeview);
+ GtkTreeIter iter;
+
+ if (gtk_tree_model_get_iter(model, &iter, path)) {
+ gchar* plugin_filename;
+ gtk_tree_model_get(model, &iter, EXT_COLUMN_PLUGIN_NAME, &plugin_filename, -1);
+ ext_execute_action(plugin_filename);
+ g_free(plugin_filename);
+ }
+}
+
+static GtkWidget *defpref_page_plugins (struct defpref_data *data)
+{
+ GtkWidget *container;
+ GtkListStore *store;
+ GtkTreeIter it;
+ GtkWidget* view;
+
+ container = gtk_vbox_new(FALSE, 0);
+
+ store = gtk_list_store_new(EXT_NUM_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
+
+ gchar** plugins = ext_list_plugins();
+ gchar** plugins_it;
+ for (plugins_it = plugins; *plugins_it; ++plugins_it) {
+
+ gboolean enabled = ext_is_plugin_loaded(*plugins_it);
+ GHashTable* metadata = ext_read_plugin_metadata(*plugins_it);
+ if (!metadata) {
+ metadata = g_hash_table_new(g_str_hash, g_str_equal);
+ }
+
+ gchar* tmp = NULL;
+
+ // NAME
+ gchar* name = g_hash_table_lookup(metadata, "name");
+ if (!name || *name == '\0') {
+ name = *plugins_it;
+ }
+ name = g_markup_escape_text(name, -1);
+ gchar* label = g_strdup_printf("<b>%s</b>", name);
+ gchar* tooltip = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>", name);
+ g_free(name);
+
+ // VERSION
+ gchar* version = g_hash_table_lookup(metadata, "version");
+ if (version) {
+ version = g_markup_escape_text(version, -1);
+ tmp = label;
+ label = g_strdup_printf("%s %s", tmp, version);
+ g_free(tmp);
+ tmp = tooltip;
+ tooltip = g_strdup_printf("%s %s", tmp, version);
+ g_free(tmp);
+ g_free(version);
+ }
+
+ // ABSTRACT
+ gchar* abstract = g_hash_table_lookup(metadata, "abstract");
+ if (abstract) {
+ abstract = g_markup_escape_text(abstract, -1);
+ tmp = label;
+ label = g_strdup_printf("%s\n%s", tmp, abstract);
+ g_free(tmp);
+ g_free(abstract);
+ }
+
+ // AUTHOR
+ gchar* author = g_hash_table_lookup(metadata, "author");
+ if (author) {
+ author = g_markup_escape_text(author, -1);
+ tmp = tooltip;
+ tooltip = g_strdup_printf("%s\n%s", tmp, author);
+ g_free(tmp);
+ g_free(author);
+ }
+
+ // WEBSITE
+ gchar* website = g_hash_table_lookup(metadata, "website");
+ if (website) {
+ website = g_markup_escape_text(website, -1);
+ tmp = tooltip;
+ tooltip = g_strdup_printf("%s\n<b>%s:</b> %s", tmp, _("Website"), website);
+ g_free(tmp);
+ g_free(website);
+ }
+
+ // FILEPATH
+ tmp = ext_find_plugin(*plugins_it);
+ gchar* full = g_markup_escape_text(tmp, -1);
+ g_free(tmp);
+ tmp = tooltip;
+ tooltip = g_strdup_printf("%s\n<b>%s:</b> %s", tmp, _("File"), full);
+ g_free(tmp);
+ g_free(full);
+
+ g_hash_table_unref(metadata);
+
+ gtk_list_store_append(store, &it);
+ gtk_list_store_set(store, &it,
+ EXT_COLUMN_ENABLED, enabled,
+ EXT_COLUMN_LABEL, label,
+ EXT_COLUMN_TOOLTIP, tooltip,
+ EXT_COLUMN_PLUGIN_NAME, *plugins_it,
+ -1);
+
+ g_free(label);
+ g_free(tooltip);
+ }
+ g_strfreev(plugins);
+
+ view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
+ g_object_unref(store);
+
+ g_signal_connect(view, "row-activated", (GCallback)plugin_execute_action, NULL);
+
+ gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), TRUE);
+ gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(view), EXT_COLUMN_TOOLTIP);
+
+
+ GtkTreeViewColumn *col;
+ GtkCellRenderer *renderer;
+
+
+ col = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(col, _("Enabled"));
+ gtk_tree_view_column_set_sort_column_id(col, EXT_COLUMN_ENABLED);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
+
+ renderer = gtk_cell_renderer_toggle_new();
+ gtk_tree_view_column_pack_start(col, renderer, TRUE);
+ gtk_tree_view_column_add_attribute(col, renderer, "active", 0);
+ g_signal_connect(renderer, "toggled", G_CALLBACK(toggle_plugin), store);
+
+ col = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(col, _("Plugin"));
+ gtk_tree_view_column_set_sort_column_id(col, EXT_COLUMN_LABEL);
+ gtk_tree_view_column_set_expand(col, TRUE);
+ /*gtk_tree_view_column_set_sort_order(col, GTK_SORT_ASCENDING);*/
+ gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
+
+ renderer = gtk_cell_renderer_text_new();
+ g_object_set(renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+ gtk_tree_view_column_pack_start(col, renderer, TRUE);
+ gtk_tree_view_column_add_attribute(col, renderer, "markup", EXT_COLUMN_LABEL);
+
+ data->PI_plugin_columns = view;
+
+ GtkWidget* sw = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ gtk_container_add(GTK_CONTAINER(sw), view);
+
+ gtk_box_pack_start(GTK_BOX(container), sw, TRUE, TRUE, 0);
+
+ return(container);
+}
+
+
static void defpref_selection(GtkTreeSelection *treeselection, gpointer user_data)
{
struct defpref_data *data;
// the window creation
-GtkWidget *defpref_dialog_new (void)
+GtkWidget *defpref_dialog_new (gint initial_selection)
{
struct defpref_data data;
GtkWidget *window, *content, *mainvbox;
page = defpref_page_euro(&data);
gtk_notebook_append_page (GTK_NOTEBOOK (notebook), page, NULL);
+ //plugins
+ page = defpref_page_plugins(&data);
+ gtk_notebook_append_page (GTK_NOTEBOOK (notebook), page, NULL);
+
//todo:should move this
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data.CM_euro_enable), PREFS->euro_active);
//select first row
- GtkTreePath *path = gtk_tree_path_new_first ();
+ GtkTreePath *path = gtk_tree_path_new_from_indices(initial_selection, -1);
+
gtk_tree_selection_select_path (gtk_tree_view_get_selection(GTK_TREE_VIEW(data.LV_page)), path);
gtk_tree_path_free(path);
gtk_widget_show_all (window);
+ gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), initial_selection);
gint result;
gchar *old_lang;
return(view);
}
+
+static void list_ext_colpref_get(GtkTreeView *treeview, GList **columns)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ g_list_free_full(*columns, g_free);
+ *columns = NULL;
+
+ model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
+
+ gboolean valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter);
+ while (valid) {
+ gboolean enabled = FALSE;
+ const gchar* name;
+
+ gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
+ EXT_COLUMN_ENABLED, &enabled,
+ EXT_COLUMN_PLUGIN_NAME, &name,
+ -1);
+
+ if (enabled) {
+ *columns = g_list_append(*columns, g_strdup(name));
+ }
+
+ valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter);
+ }
+}
+
GtkWidget *CY_dtex_datefmt;
GtkWidget *CY_dtex_ofxmemo;
+ GtkWidget *PI_plugin_columns;
+};
+
+enum
+{
+ PREF_GENERAL,
+ PREF_INTERFACE,
+ PREF_COLUMNS,
+ PREF_DISPLAY,
+ PREF_IMPORT,
+ PREF_REPORT,
+ PREF_EURO,
+ PREF_PLUGINS,
+ PREF_MAX
};
void free_pref_icons(void);
void load_pref_icons(void);
-GtkWidget *defpref_dialog_new (void);
+GtkWidget *defpref_dialog_new (gint initial_selection);
#endif