summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am163
-rw-r--r--src/Makefile.in968
-rw-r--r--src/bencode.c302
-rw-r--r--src/bencode.h66
-rw-r--r--src/gtk.suppression294
-rw-r--r--src/hicolor_apps_16x16_transmission-remote-gtk.pngbin0 -> 838 bytes
-rw-r--r--src/hicolor_apps_22x22_transmission-remote-gtk.pngbin0 -> 1207 bytes
-rw-r--r--src/hicolor_apps_24x24_transmission-remote-gtk.pngbin0 -> 1200 bytes
-rw-r--r--src/hicolor_apps_32x32_transmission-remote-gtk.pngbin0 -> 1881 bytes
-rw-r--r--src/hicolor_apps_48x48_transmission-remote-gtk.pngbin0 -> 2683 bytes
-rw-r--r--src/hicolor_apps_scalable_transmission-remote-gtk.svg445
-rw-r--r--src/hig.c213
-rw-r--r--src/hig.h86
-rw-r--r--src/icon-turtle.h84
-rw-r--r--src/icons.c252
-rw-r--r--src/icons.h21
-rw-r--r--src/installer-gtk2.nsi645
-rw-r--r--src/installer.nsi628
-rw-r--r--src/json.c114
-rw-r--r--src/json.h35
-rw-r--r--src/main.c301
-rw-r--r--src/protocol-constants.h198
-rw-r--r--src/remote-exec.c206
-rw-r--r--src/remote-exec.h26
-rw-r--r--src/requests.c293
-rw-r--r--src/requests.h53
-rw-r--r--src/session-get.c238
-rw-r--r--src/session-get.h115
-rw-r--r--src/torrent-cell-renderer.c1207
-rw-r--r--src/torrent-cell-renderer.h51
-rw-r--r--src/torrent.c681
-rw-r--r--src/torrent.h150
-rw-r--r--src/transmission-remote-gtk.desktop.in10
-rw-r--r--src/transmission-remote-gtk.pod57
-rw-r--r--src/transmission_large.icobin0 -> 9662 bytes
-rw-r--r--src/trg-about-window.c99
-rw-r--r--src/trg-about-window.h27
-rw-r--r--src/trg-cell-renderer-counter.c157
-rw-r--r--src/trg-cell-renderer-counter.h51
-rw-r--r--src/trg-cell-renderer-epoch.c121
-rw-r--r--src/trg-cell-renderer-epoch.h51
-rw-r--r--src/trg-cell-renderer-eta.c119
-rw-r--r--src/trg-cell-renderer-eta.h51
-rw-r--r--src/trg-cell-renderer-file-icon.c187
-rw-r--r--src/trg-cell-renderer-file-icon.h51
-rw-r--r--src/trg-cell-renderer-numgteqthan.c146
-rw-r--r--src/trg-cell-renderer-numgteqthan.h51
-rw-r--r--src/trg-cell-renderer-priority.c122
-rw-r--r--src/trg-cell-renderer-priority.h51
-rw-r--r--src/trg-cell-renderer-ratio.c115
-rw-r--r--src/trg-cell-renderer-ratio.h51
-rw-r--r--src/trg-cell-renderer-size.c118
-rw-r--r--src/trg-cell-renderer-size.h51
-rw-r--r--src/trg-cell-renderer-speed.c116
-rw-r--r--src/trg-cell-renderer-speed.h52
-rw-r--r--src/trg-cell-renderer-wanted.c115
-rw-r--r--src/trg-cell-renderer-wanted.h51
-rw-r--r--src/trg-client.c706
-rw-r--r--src/trg-client.h167
-rw-r--r--src/trg-destination-combo.c466
-rw-r--r--src/trg-destination-combo.h58
-rw-r--r--src/trg-file-parser.c201
-rw-r--r--src/trg-file-parser.h28
-rw-r--r--src/trg-files-model-common.c199
-rw-r--r--src/trg-files-model-common.h41
-rw-r--r--src/trg-files-model.c449
-rw-r--r--src/trg-files-model.h71
-rw-r--r--src/trg-files-tree-view-common.c229
-rw-r--r--src/trg-files-tree-view-common.h42
-rw-r--r--src/trg-files-tree-view.c226
-rw-r--r--src/trg-files-tree-view.h76
-rw-r--r--src/trg-files-tree.c52
-rw-r--r--src/trg-files-tree.h43
-rw-r--r--src/trg-general-panel.c342
-rw-r--r--src/trg-general-panel.h61
-rw-r--r--src/trg-gtk-app.c231
-rw-r--r--src/trg-gtk-app.h55
-rw-r--r--src/trg-icons.c63
-rw-r--r--src/trg-icons.h25
-rw-r--r--src/trg-json-widgets.c183
-rw-r--r--src/trg-json-widgets.h58
-rw-r--r--src/trg-main-window.c2907
-rw-r--r--src/trg-main-window.h87
-rw-r--r--src/trg-menu-bar.c980
-rw-r--r--src/trg-menu-bar.h68
-rw-r--r--src/trg-model.c114
-rw-r--r--src/trg-model.h32
-rw-r--r--src/trg-peers-model.c292
-rw-r--r--src/trg-peers-model.h90
-rw-r--r--src/trg-peers-tree-view.c87
-rw-r--r--src/trg-peers-tree-view.h57
-rw-r--r--src/trg-persistent-tree-view.c495
-rw-r--r--src/trg-persistent-tree-view.h76
-rw-r--r--src/trg-preferences-dialog.c979
-rw-r--r--src/trg-preferences-dialog.h65
-rw-r--r--src/trg-prefs.c578
-rw-r--r--src/trg-prefs.h166
-rw-r--r--src/trg-remote-prefs-dialog.c776
-rw-r--r--src/trg-remote-prefs-dialog.h56
-rw-r--r--src/trg-sortable-filtered-model.c202
-rw-r--r--src/trg-sortable-filtered-model.h53
-rw-r--r--src/trg-state-selector.c815
-rw-r--r--src/trg-state-selector.h81
-rw-r--r--src/trg-stats-dialog.c376
-rw-r--r--src/trg-stats-dialog.h56
-rw-r--r--src/trg-status-bar.c269
-rw-r--r--src/trg-status-bar.h68
-rw-r--r--src/trg-toolbar.c352
-rw-r--r--src/trg-toolbar.h59
-rw-r--r--src/trg-torrent-add-dialog.c964
-rw-r--r--src/trg-torrent-add-dialog.h73
-rw-r--r--src/trg-torrent-add-url-dialog.c165
-rw-r--r--src/trg-torrent-add-url-dialog.h55
-rw-r--r--src/trg-torrent-graph.c640
-rw-r--r--src/trg-torrent-graph.h57
-rw-r--r--src/trg-torrent-model.c851
-rw-r--r--src/trg-torrent-model.h154
-rw-r--r--src/trg-torrent-move-dialog.c283
-rw-r--r--src/trg-torrent-move-dialog.h57
-rw-r--r--src/trg-torrent-props-dialog.c736
-rw-r--r--src/trg-torrent-props-dialog.h58
-rw-r--r--src/trg-torrent-tree-view.c337
-rw-r--r--src/trg-torrent-tree-view.h58
-rw-r--r--src/trg-trackers-model.c196
-rw-r--r--src/trg-trackers-model.h96
-rw-r--r--src/trg-trackers-tree-view.c392
-rw-r--r--src/trg-trackers-tree-view.h61
-rw-r--r--src/trg-tree-view.c790
-rw-r--r--src/trg-tree-view.h104
-rw-r--r--src/trg-valgrind.sh2
-rw-r--r--src/util.c654
-rw-r--r--src/util.h106
-rw-r--r--src/win32-mailslot.c196
-rw-r--r--src/win32-mailslot.h32
-rw-r--r--src/win32.rc35
135 files changed, 31315 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..0c1abd6
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,163 @@
+#
+# transmission-remote-gtk - A GTK RPC client to Transmission
+# Copyright (C) 2011 Alan Fitton
+
+# 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, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+NULL =
+
+public_icons_themes = \
+ hicolor \
+ $(NULL)
+
+public_icons = \
+ hicolor_apps_scalable_transmission-remote-gtk.svg \
+ hicolor_apps_16x16_transmission-remote-gtk.png \
+ hicolor_apps_24x24_transmission-remote-gtk.png \
+ hicolor_apps_48x48_transmission-remote-gtk.png \
+ hicolor_apps_22x22_transmission-remote-gtk.png \
+ hicolor_apps_32x32_transmission-remote-gtk.png \
+ $(NULL)
+
+if !WIN32
+EXTRA_DIST = transmission-remote-gtk.desktop.in transmission-remote-gtk.pod
+CLEANFILES = transmission-remote-gtk.desktop transmission-remote-gtk.1
+
+man_MANS = transmission-remote-gtk.1
+
+desktopdir = $(datadir)/applications
+desktop_DATA = transmission-remote-gtk.desktop
+
+endif
+
+bin_PROGRAMS = transmission-remote-gtk
+INCLUDES = -std=c99 -Wall -I.. -Wno-overflow -DTRGLICENSE=\""$(trglicense)"\" $(libcurl_CFLAGS) $(jsonglib_CFLAGS) $(gthread_CFLAGS) $(gtk_CFLAGS) $(gio_CFLAGS) $(unique_CFLAGS) $(notify_CFLAGS) $(libproxy_CFLAGS) $(libappindicator_CFLAGS)
+
+transmission_remote_gtk_SOURCES = \
+ trg-cell-renderer-speed.c \
+ trg-cell-renderer-counter.c \
+ trg-cell-renderer-size.c \
+ trg-cell-renderer-ratio.c \
+ trg-cell-renderer-eta.c \
+ trg-cell-renderer-priority.c \
+ trg-cell-renderer-wanted.c \
+ trg-cell-renderer-file-icon.c \
+ trg-cell-renderer-epoch.c \
+ trg-cell-renderer-numgteqthan.c \
+ torrent-cell-renderer.c \
+ trg-remote-prefs-dialog.c \
+ trg-torrent-props-dialog.c \
+ trg-torrent-add-url-dialog.c \
+ trg-torrent-add-dialog.c \
+ trg-torrent-move-dialog.c \
+ trg-preferences-dialog.c \
+ trg-stats-dialog.c \
+ trg-about-window.c \
+ trg-destination-combo.c \
+ trg-state-selector.c \
+ trg-general-panel.c \
+ trg-torrent-graph.c \
+ trg-icons.c \
+ icons.c \
+ trg-toolbar.c \
+ trg-menu-bar.c \
+ trg-status-bar.c \
+ trg-file-parser.c \
+ trg-json-widgets.c \
+ trg-model.c \
+ trg-sortable-filtered-model.c \
+ trg-files-tree.c \
+ trg-files-model.c \
+ trg-files-tree-view-common.c \
+ trg-files-tree-view.c \
+ trg-files-model-common.c \
+ trg-trackers-model.c \
+ trg-trackers-tree-view.c \
+ trg-peers-model.c \
+ trg-peers-tree-view.c \
+ trg-torrent-model.c \
+ trg-torrent-tree-view.c \
+ trg-persistent-tree-view.c \
+ trg-tree-view.c \
+ util.c \
+ hig.c \
+ bencode.c \
+ trg-prefs.c \
+ remote-exec.c \
+ trg-gtk-app.c \
+ requests.c \
+ torrent.c \
+ session-get.c \
+ json.c \
+ trg-client.c \
+ trg-main-window.c \
+ main.c \
+ $(NULL)
+
+transmission_remote_gtk_LDFLAGS = -lm $(jsonglib_LIBS) $(gtk_LIBS) $(gthread_LIBS) $(GEOIP_LIBS) $(gio_LIBS) $(unique_LIBS) $(notify_LIBS) $(libproxy_LIBS) $(libcurl_LIBS) $(libappindicator_LIBS)
+
+if WIN32
+.rc.o:
+ windres $^ -o $@
+%.o : %.rc
+ windres $^ -o $@
+
+CFLAGS += -mms-bitfields -mwin32 -mwindows
+LDFLAGS += -Wl,--allow-multiple-definition -lws2_32 -lintl
+transmission_remote_gtk_SOURCES += win32.rc win32-mailslot.c
+INCLUDES += -O2
+else
+%.1: %.pod
+ pod2man --release="" --center="Transmission Remote GTK" $< > $@
+endif
+
+install-data-local: install-icons update-icon-cache
+
+gtk_update_icon_cache = gtk-update-icon-cache -f -t
+
+update-icon-cache:
+ @-if test -z "$(DESTDIR)"; then \
+ echo "Updating Gtk icon cache."; \
+ for theme in $(public_icons_themes); do \
+ $(gtk_update_icon_cache) $(datadir)/icons/$$theme; \
+ done; \
+ else \
+ echo "*** Icon cache not updated. After (un)install, run this:"; \
+ for theme in $(public_icons_themes); do \
+ echo "*** $(gtk_update_icon_cache) $(datadir)/icons/$$theme"; \
+ done; \
+ fi
+
+install-icons:
+ for icon in $(public_icons); do \
+ THEME=`echo $$icon | cut -d_ -f1`; \
+ CONTEXT=`echo $$icon | cut -d_ -f2`; \
+ SIZE=`echo $$icon | cut -d_ -f3`; \
+ ICONFILE=`echo $$icon | cut -d_ -f4`; \
+ mkdir -p $(DESTDIR)$(datadir)/icons/$$THEME/$$SIZE/$$CONTEXT; \
+ $(INSTALL_DATA) $(srcdir)/$$icon $(DESTDIR)$(datadir)/icons/$$THEME/$$SIZE/$$CONTEXT/$$ICONFILE; \
+ done; \
+ for icon in $(private_icons); do \
+ THEME=`echo $$icon | cut -d_ -f1`; \
+ CONTEXT=`echo $$icon | cut -d_ -f2`; \
+ SIZE=`echo $$icon | cut -d_ -f3`; \
+ ICONFILE=`echo $$icon | cut -d_ -f4`; \
+ mkdir -p $(DESTDIR)$(pkgdatadir)/icons/$$THEME/$$SIZE/$$CONTEXT; \
+ $(INSTALL_DATA) $(srcdir)/$$icon $(DESTDIR)$(pkgdatadir)/icons/$$THEME/$$SIZE/$$CONTEXT/$$ICONFILE; \
+ done
+
+transmission-remote-gtk.desktop: transmission-remote-gtk.desktop.in
+ sed -e 's,@bindir\@,$(bindir),g' <transmission-remote-gtk.desktop.in > $@
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 0000000..bc32290
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,968 @@
+# Makefile.in generated by automake 1.12.2 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2012 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+#
+# transmission-remote-gtk - A GTK RPC client to Transmission
+# Copyright (C) 2011 Alan Fitton
+
+# 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, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+
+VPATH = @srcdir@
+am__make_dryrun = \
+ { \
+ am__dry=no; \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ echo 'am--echo: ; @echo "AM" OK' | $(MAKE) -f - 2>/dev/null \
+ | grep '^AM OK$$' >/dev/null || am__dry=yes;; \
+ *) \
+ for am__flg in $$MAKEFLAGS; do \
+ case $$am__flg in \
+ *=*|--*) ;; \
+ *n*) am__dry=yes; break;; \
+ esac; \
+ done;; \
+ esac; \
+ test $$am__dry = yes; \
+ }
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+bin_PROGRAMS = transmission-remote-gtk$(EXEEXT)
+@WIN32_TRUE@am__append_1 = -mms-bitfields -mwin32 -mwindows
+@WIN32_TRUE@am__append_2 = -Wl,--allow-multiple-definition -lws2_32 -lintl
+@WIN32_TRUE@am__append_3 = win32.rc win32-mailslot.c
+@WIN32_TRUE@am__append_4 = -O2
+subdir = src
+DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in \
+ $(top_srcdir)/depcomp
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/acinclude.m4 \
+ $(top_srcdir)/m4/intltool.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(man1dir)" \
+ "$(DESTDIR)$(desktopdir)"
+PROGRAMS = $(bin_PROGRAMS)
+am__transmission_remote_gtk_SOURCES_DIST = trg-cell-renderer-speed.c \
+ trg-cell-renderer-counter.c trg-cell-renderer-size.c \
+ trg-cell-renderer-ratio.c trg-cell-renderer-eta.c \
+ trg-cell-renderer-priority.c trg-cell-renderer-wanted.c \
+ trg-cell-renderer-file-icon.c trg-cell-renderer-epoch.c \
+ trg-cell-renderer-numgteqthan.c torrent-cell-renderer.c \
+ trg-remote-prefs-dialog.c trg-torrent-props-dialog.c \
+ trg-torrent-add-url-dialog.c trg-torrent-add-dialog.c \
+ trg-torrent-move-dialog.c trg-preferences-dialog.c \
+ trg-stats-dialog.c trg-about-window.c trg-destination-combo.c \
+ trg-state-selector.c trg-general-panel.c trg-torrent-graph.c \
+ trg-icons.c icons.c trg-toolbar.c trg-menu-bar.c \
+ trg-status-bar.c trg-file-parser.c trg-json-widgets.c \
+ trg-model.c trg-sortable-filtered-model.c trg-files-tree.c \
+ trg-files-model.c trg-files-tree-view-common.c \
+ trg-files-tree-view.c trg-files-model-common.c \
+ trg-trackers-model.c trg-trackers-tree-view.c \
+ trg-peers-model.c trg-peers-tree-view.c trg-torrent-model.c \
+ trg-torrent-tree-view.c trg-persistent-tree-view.c \
+ trg-tree-view.c util.c hig.c bencode.c trg-prefs.c \
+ remote-exec.c trg-gtk-app.c requests.c torrent.c session-get.c \
+ json.c trg-client.c trg-main-window.c main.c win32.rc \
+ win32-mailslot.c
+am__objects_1 =
+@WIN32_TRUE@am__objects_2 = win32.$(OBJEXT) win32-mailslot.$(OBJEXT)
+am_transmission_remote_gtk_OBJECTS = \
+ trg-cell-renderer-speed.$(OBJEXT) \
+ trg-cell-renderer-counter.$(OBJEXT) \
+ trg-cell-renderer-size.$(OBJEXT) \
+ trg-cell-renderer-ratio.$(OBJEXT) \
+ trg-cell-renderer-eta.$(OBJEXT) \
+ trg-cell-renderer-priority.$(OBJEXT) \
+ trg-cell-renderer-wanted.$(OBJEXT) \
+ trg-cell-renderer-file-icon.$(OBJEXT) \
+ trg-cell-renderer-epoch.$(OBJEXT) \
+ trg-cell-renderer-numgteqthan.$(OBJEXT) \
+ torrent-cell-renderer.$(OBJEXT) \
+ trg-remote-prefs-dialog.$(OBJEXT) \
+ trg-torrent-props-dialog.$(OBJEXT) \
+ trg-torrent-add-url-dialog.$(OBJEXT) \
+ trg-torrent-add-dialog.$(OBJEXT) \
+ trg-torrent-move-dialog.$(OBJEXT) \
+ trg-preferences-dialog.$(OBJEXT) trg-stats-dialog.$(OBJEXT) \
+ trg-about-window.$(OBJEXT) trg-destination-combo.$(OBJEXT) \
+ trg-state-selector.$(OBJEXT) trg-general-panel.$(OBJEXT) \
+ trg-torrent-graph.$(OBJEXT) trg-icons.$(OBJEXT) \
+ icons.$(OBJEXT) trg-toolbar.$(OBJEXT) trg-menu-bar.$(OBJEXT) \
+ trg-status-bar.$(OBJEXT) trg-file-parser.$(OBJEXT) \
+ trg-json-widgets.$(OBJEXT) trg-model.$(OBJEXT) \
+ trg-sortable-filtered-model.$(OBJEXT) trg-files-tree.$(OBJEXT) \
+ trg-files-model.$(OBJEXT) trg-files-tree-view-common.$(OBJEXT) \
+ trg-files-tree-view.$(OBJEXT) trg-files-model-common.$(OBJEXT) \
+ trg-trackers-model.$(OBJEXT) trg-trackers-tree-view.$(OBJEXT) \
+ trg-peers-model.$(OBJEXT) trg-peers-tree-view.$(OBJEXT) \
+ trg-torrent-model.$(OBJEXT) trg-torrent-tree-view.$(OBJEXT) \
+ trg-persistent-tree-view.$(OBJEXT) trg-tree-view.$(OBJEXT) \
+ util.$(OBJEXT) hig.$(OBJEXT) bencode.$(OBJEXT) \
+ trg-prefs.$(OBJEXT) remote-exec.$(OBJEXT) \
+ trg-gtk-app.$(OBJEXT) requests.$(OBJEXT) torrent.$(OBJEXT) \
+ session-get.$(OBJEXT) json.$(OBJEXT) trg-client.$(OBJEXT) \
+ trg-main-window.$(OBJEXT) main.$(OBJEXT) $(am__objects_1) \
+ $(am__objects_2)
+transmission_remote_gtk_OBJECTS = \
+ $(am_transmission_remote_gtk_OBJECTS)
+transmission_remote_gtk_LDADD = $(LDADD)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+transmission_remote_gtk_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(transmission_remote_gtk_LDFLAGS) \
+ $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(transmission_remote_gtk_SOURCES)
+DIST_SOURCES = $(am__transmission_remote_gtk_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+man1dir = $(mandir)/man1
+NROFF = nroff
+MANS = $(man_MANS)
+DATA = $(desktop_DATA)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ALL_LINGUAS = @ALL_LINGUAS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@ $(am__append_1)
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DATADIRNAME = @DATADIRNAME@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GEOIP_LIBS = @GEOIP_LIBS@
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+GMSGFMT = @GMSGFMT@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@
+INTLTOOL_MERGE = @INTLTOOL_MERGE@
+INTLTOOL_PERL = @INTLTOOL_PERL@
+INTLTOOL_UPDATE = @INTLTOOL_UPDATE@
+LD = @LD@
+LDFLAGS = @LDFLAGS@ $(am__append_2)
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOCALEDIR = @LOCALEDIR@
+LTLIBOBJS = @LTLIBOBJS@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+XGETTEXT = @XGETTEXT@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+gio_CFLAGS = @gio_CFLAGS@
+gio_LIBS = @gio_LIBS@
+gthread_CFLAGS = @gthread_CFLAGS@
+gthread_LIBS = @gthread_LIBS@
+gtk_CFLAGS = @gtk_CFLAGS@
+gtk_LIBS = @gtk_LIBS@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+jsonglib_CFLAGS = @jsonglib_CFLAGS@
+jsonglib_LIBS = @jsonglib_LIBS@
+libappindicator_CFLAGS = @libappindicator_CFLAGS@
+libappindicator_LIBS = @libappindicator_LIBS@
+libcurl_CFLAGS = @libcurl_CFLAGS@
+libcurl_LIBS = @libcurl_LIBS@
+libdir = @libdir@
+libexecdir = @libexecdir@
+libproxy_CFLAGS = @libproxy_CFLAGS@
+libproxy_LIBS = @libproxy_LIBS@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+notify_CFLAGS = @notify_CFLAGS@
+notify_LIBS = @notify_LIBS@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+trglicense = @trglicense@
+unique_CFLAGS = @unique_CFLAGS@
+unique_LIBS = @unique_LIBS@
+NULL =
+public_icons_themes = \
+ hicolor \
+ $(NULL)
+
+public_icons = \
+ hicolor_apps_scalable_transmission-remote-gtk.svg \
+ hicolor_apps_16x16_transmission-remote-gtk.png \
+ hicolor_apps_24x24_transmission-remote-gtk.png \
+ hicolor_apps_48x48_transmission-remote-gtk.png \
+ hicolor_apps_22x22_transmission-remote-gtk.png \
+ hicolor_apps_32x32_transmission-remote-gtk.png \
+ $(NULL)
+
+@WIN32_FALSE@EXTRA_DIST = transmission-remote-gtk.desktop.in transmission-remote-gtk.pod
+@WIN32_FALSE@CLEANFILES = transmission-remote-gtk.desktop transmission-remote-gtk.1
+@WIN32_FALSE@man_MANS = transmission-remote-gtk.1
+@WIN32_FALSE@desktopdir = $(datadir)/applications
+@WIN32_FALSE@desktop_DATA = transmission-remote-gtk.desktop
+INCLUDES = -std=c99 -Wall -I.. -Wno-overflow \
+ -DTRGLICENSE=\""$(trglicense)"\" $(libcurl_CFLAGS) \
+ $(jsonglib_CFLAGS) $(gthread_CFLAGS) $(gtk_CFLAGS) \
+ $(gio_CFLAGS) $(unique_CFLAGS) $(notify_CFLAGS) \
+ $(libproxy_CFLAGS) $(libappindicator_CFLAGS) $(am__append_4)
+transmission_remote_gtk_SOURCES = trg-cell-renderer-speed.c \
+ trg-cell-renderer-counter.c trg-cell-renderer-size.c \
+ trg-cell-renderer-ratio.c trg-cell-renderer-eta.c \
+ trg-cell-renderer-priority.c trg-cell-renderer-wanted.c \
+ trg-cell-renderer-file-icon.c trg-cell-renderer-epoch.c \
+ trg-cell-renderer-numgteqthan.c torrent-cell-renderer.c \
+ trg-remote-prefs-dialog.c trg-torrent-props-dialog.c \
+ trg-torrent-add-url-dialog.c trg-torrent-add-dialog.c \
+ trg-torrent-move-dialog.c trg-preferences-dialog.c \
+ trg-stats-dialog.c trg-about-window.c trg-destination-combo.c \
+ trg-state-selector.c trg-general-panel.c trg-torrent-graph.c \
+ trg-icons.c icons.c trg-toolbar.c trg-menu-bar.c \
+ trg-status-bar.c trg-file-parser.c trg-json-widgets.c \
+ trg-model.c trg-sortable-filtered-model.c trg-files-tree.c \
+ trg-files-model.c trg-files-tree-view-common.c \
+ trg-files-tree-view.c trg-files-model-common.c \
+ trg-trackers-model.c trg-trackers-tree-view.c \
+ trg-peers-model.c trg-peers-tree-view.c trg-torrent-model.c \
+ trg-torrent-tree-view.c trg-persistent-tree-view.c \
+ trg-tree-view.c util.c hig.c bencode.c trg-prefs.c \
+ remote-exec.c trg-gtk-app.c requests.c torrent.c session-get.c \
+ json.c trg-client.c trg-main-window.c main.c $(NULL) \
+ $(am__append_3)
+transmission_remote_gtk_LDFLAGS = -lm $(jsonglib_LIBS) $(gtk_LIBS) $(gthread_LIBS) $(GEOIP_LIBS) $(gio_LIBS) $(unique_LIBS) $(notify_LIBS) $(libproxy_LIBS) $(libcurl_LIBS) $(libappindicator_LIBS)
+gtk_update_icon_cache = gtk-update-icon-cache -f -t
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj .rc
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p || test -f $$p1; \
+ then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+transmission-remote-gtk$(EXEEXT): $(transmission_remote_gtk_OBJECTS) $(transmission_remote_gtk_DEPENDENCIES) $(EXTRA_transmission_remote_gtk_DEPENDENCIES)
+ @rm -f transmission-remote-gtk$(EXEEXT)
+ $(AM_V_CCLD)$(transmission_remote_gtk_LINK) $(transmission_remote_gtk_OBJECTS) $(transmission_remote_gtk_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bencode.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hig.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/icons.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/json.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/remote-exec.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/requests.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session-get.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/torrent-cell-renderer.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/torrent.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-about-window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-cell-renderer-counter.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-cell-renderer-epoch.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-cell-renderer-eta.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-cell-renderer-file-icon.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-cell-renderer-numgteqthan.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-cell-renderer-priority.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-cell-renderer-ratio.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-cell-renderer-size.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-cell-renderer-speed.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-cell-renderer-wanted.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-client.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-destination-combo.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-file-parser.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-files-model-common.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-files-model.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-files-tree-view-common.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-files-tree-view.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-files-tree.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-general-panel.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-gtk-app.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-icons.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-json-widgets.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-main-window.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-menu-bar.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-model.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-peers-model.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-peers-tree-view.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-persistent-tree-view.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-preferences-dialog.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-prefs.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-remote-prefs-dialog.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-sortable-filtered-model.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-state-selector.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-stats-dialog.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-status-bar.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-toolbar.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-torrent-add-dialog.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-torrent-add-url-dialog.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-torrent-graph.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-torrent-model.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-torrent-move-dialog.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-torrent-props-dialog.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-torrent-tree-view.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-trackers-model.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-trackers-tree-view.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/trg-tree-view.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/util.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/win32-mailslot.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-man1: $(man_MANS)
+ @$(NORMAL_INSTALL)
+ @list1=''; \
+ list2='$(man_MANS)'; \
+ test -n "$(man1dir)" \
+ && test -n "`echo $$list1$$list2`" \
+ || exit 0; \
+ echo " $(MKDIR_P) '$(DESTDIR)$(man1dir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(man1dir)" || exit 1; \
+ { for i in $$list1; do echo "$$i"; done; \
+ if test -n "$$list2"; then \
+ for i in $$list2; do echo "$$i"; done \
+ | sed -n '/\.1[a-z]*$$/p'; \
+ fi; \
+ } | while read p; do \
+ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; echo "$$p"; \
+ done | \
+ sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \
+ sed 'N;N;s,\n, ,g' | { \
+ list=; while read file base inst; do \
+ if test "$$base" = "$$inst"; then list="$$list $$file"; else \
+ echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man1dir)/$$inst'"; \
+ $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man1dir)/$$inst" || exit $$?; \
+ fi; \
+ done; \
+ for i in $$list; do echo "$$i"; done | $(am__base_list) | \
+ while read files; do \
+ test -z "$$files" || { \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man1dir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(man1dir)" || exit $$?; }; \
+ done; }
+
+uninstall-man1:
+ @$(NORMAL_UNINSTALL)
+ @list=''; test -n "$(man1dir)" || exit 0; \
+ files=`{ for i in $$list; do echo "$$i"; done; \
+ l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \
+ sed -n '/\.1[a-z]*$$/p'; \
+ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \
+ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \
+ dir='$(DESTDIR)$(man1dir)'; $(am__uninstall_files_from_dir)
+install-desktopDATA: $(desktop_DATA)
+ @$(NORMAL_INSTALL)
+ @list='$(desktop_DATA)'; test -n "$(desktopdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(desktopdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(desktopdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(desktopdir)'"; \
+ $(INSTALL_DATA) $$files "$(DESTDIR)$(desktopdir)" || exit $$?; \
+ done
+
+uninstall-desktopDATA:
+ @$(NORMAL_UNINSTALL)
+ @list='$(desktop_DATA)'; test -n "$(desktopdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(desktopdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ set x; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+
+cscopelist: $(HEADERS) $(SOURCES) $(LISP)
+ list='$(SOURCES) $(HEADERS) $(LISP)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @list='$(MANS)'; if test -n "$$list"; then \
+ list=`for p in $$list; do \
+ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \
+ if test -f "$$d$$p"; then echo "$$d$$p"; else :; fi; done`; \
+ if test -n "$$list" && \
+ grep 'ab help2man is required to generate this page' $$list >/dev/null; then \
+ echo "error: found man pages containing the 'missing help2man' replacement text:" >&2; \
+ grep -l 'ab help2man is required to generate this page' $$list | sed 's/^/ /' >&2; \
+ echo " to fix them, install help2man, remove and regenerate the man pages;" >&2; \
+ echo " typically 'make maintainer-clean' will remove them" >&2; \
+ exit 1; \
+ else :; fi; \
+ else :; fi
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(MANS) $(DATA)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(man1dir)" "$(DESTDIR)$(desktopdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-data-local install-desktopDATA install-man
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man: install-man1
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS uninstall-desktopDATA \
+ uninstall-man
+
+uninstall-man: uninstall-man1
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+ clean-generic clean-libtool cscopelist ctags distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-binPROGRAMS install-data \
+ install-data-am install-data-local install-desktopDATA \
+ install-dvi install-dvi-am install-exec install-exec-am \
+ install-html install-html-am install-info install-info-am \
+ install-man install-man1 install-pdf install-pdf-am install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags uninstall \
+ uninstall-am uninstall-binPROGRAMS uninstall-desktopDATA \
+ uninstall-man uninstall-man1
+
+
+@WIN32_TRUE@.rc.o:
+@WIN32_TRUE@ windres $^ -o $@
+@WIN32_TRUE@%.o : %.rc
+@WIN32_TRUE@ windres $^ -o $@
+@WIN32_FALSE@%.1: %.pod
+@WIN32_FALSE@ pod2man --release="" --center="Transmission Remote GTK" $< > $@
+
+install-data-local: install-icons update-icon-cache
+
+update-icon-cache:
+ @-if test -z "$(DESTDIR)"; then \
+ echo "Updating Gtk icon cache."; \
+ for theme in $(public_icons_themes); do \
+ $(gtk_update_icon_cache) $(datadir)/icons/$$theme; \
+ done; \
+ else \
+ echo "*** Icon cache not updated. After (un)install, run this:"; \
+ for theme in $(public_icons_themes); do \
+ echo "*** $(gtk_update_icon_cache) $(datadir)/icons/$$theme"; \
+ done; \
+ fi
+
+install-icons:
+ for icon in $(public_icons); do \
+ THEME=`echo $$icon | cut -d_ -f1`; \
+ CONTEXT=`echo $$icon | cut -d_ -f2`; \
+ SIZE=`echo $$icon | cut -d_ -f3`; \
+ ICONFILE=`echo $$icon | cut -d_ -f4`; \
+ mkdir -p $(DESTDIR)$(datadir)/icons/$$THEME/$$SIZE/$$CONTEXT; \
+ $(INSTALL_DATA) $(srcdir)/$$icon $(DESTDIR)$(datadir)/icons/$$THEME/$$SIZE/$$CONTEXT/$$ICONFILE; \
+ done; \
+ for icon in $(private_icons); do \
+ THEME=`echo $$icon | cut -d_ -f1`; \
+ CONTEXT=`echo $$icon | cut -d_ -f2`; \
+ SIZE=`echo $$icon | cut -d_ -f3`; \
+ ICONFILE=`echo $$icon | cut -d_ -f4`; \
+ mkdir -p $(DESTDIR)$(pkgdatadir)/icons/$$THEME/$$SIZE/$$CONTEXT; \
+ $(INSTALL_DATA) $(srcdir)/$$icon $(DESTDIR)$(pkgdatadir)/icons/$$THEME/$$SIZE/$$CONTEXT/$$ICONFILE; \
+ done
+
+transmission-remote-gtk.desktop: transmission-remote-gtk.desktop.in
+ sed -e 's,@bindir\@,$(bindir),g' <transmission-remote-gtk.desktop.in > $@
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/bencode.c b/src/bencode.c
new file mode 100644
index 0000000..89f35ed
--- /dev/null
+++ b/src/bencode.c
@@ -0,0 +1,302 @@
+/*
+ * C implementation of a bencode decoder.
+ * This is the format defined by BitTorrent:
+ * http://wiki.theory.org/BitTorrentSpecification#bencoding
+ *
+ * The only external requirements are a few [standard] function calls and
+ * the gint64 type. Any sane system should provide all of these things.
+ *
+ * See the bencode.h header file for usage information.
+ *
+ * This is released into the public domain:
+ * http://en.wikipedia.org/wiki/Public_Domain
+ *
+ * Written by:
+ * Mike Frysinger <vapier@gmail.com>
+ * And improvements from:
+ * Gilles Chanteperdrix <gilles.chanteperdrix@xenomai.org>
+ */
+
+/*
+ * This implementation isn't optimized at all as I wrote it to support a bogus
+ * system. I have no real interest in this format. Feel free to send me
+ * patches (so long as you don't copyright them and you release your changes
+ * into the public domain as well).
+ */
+
+#include <stdlib.h> /* malloc() realloc() free() strtoll() */
+#include <string.h> /* memset() */
+#include <ctype.h>
+
+#include <glib.h>
+
+#include "bencode.h"
+
+static be_node *be_alloc(be_type type)
+{
+ be_node *ret = g_malloc0(sizeof(be_node));
+ if (ret)
+ ret->type = type;
+ return ret;
+}
+
+static gint64 _be_decode_int(const char **data, gint64 * data_len)
+{
+ char *endp;
+ gint64 ret = strtoll(*data, &endp, 10);
+ *data_len -= (endp - *data);
+ *data = endp;
+ return ret;
+}
+
+gint64 be_str_len(be_node * node)
+{
+ gint64 ret = 0;
+ if (node->val.s)
+ memcpy(&ret, node->val.s - sizeof(ret), sizeof(ret));
+ return ret;
+}
+
+static char *_be_decode_str(const char **data, gint64 * data_len)
+{
+ gint64 sllen = _be_decode_int(data, data_len);
+ long slen = sllen;
+ unsigned long len;
+ char *ret = NULL;
+
+ /* slen is signed, so negative values get rejected */
+ if (sllen < 0)
+ return ret;
+
+ /* reject attempts to allocate large values that overflow the
+ * size_t type which is used with malloc()
+ */
+ if (sizeof(gint64) != sizeof(long))
+ if (sllen != slen)
+ return ret;
+
+ /* make sure we have enough data left */
+ if (sllen > *data_len - 1)
+ return ret;
+
+ /* switch from signed to unsigned so we don't overflow below */
+ len = slen;
+
+ if (**data == ':') {
+ char *_ret = g_malloc(sizeof(sllen) + len + 1);
+ memcpy(_ret, &sllen, sizeof(sllen));
+ ret = _ret + sizeof(sllen);
+ memcpy(ret, *data + 1, len);
+ ret[len] = '\0';
+ *data += len + 1;
+ *data_len -= len + 1;
+ }
+ return ret;
+}
+
+static be_node *_be_decode(const char **data, gint64 * data_len)
+{
+ be_node *ret = NULL;
+ char dc;
+
+ if (!*data_len)
+ return ret;
+
+ dc = **data;
+ if (dc == 'l') {
+ unsigned int i = 0;
+
+ ret = be_alloc(BE_LIST);
+
+ --(*data_len);
+ ++(*data);
+ while (**data != 'e') {
+ ret->val.l =
+ g_realloc(ret->val.l, (i + 2) * sizeof(*ret->val.l));
+ ret->val.l[i] = _be_decode(data, data_len);
+ if (!ret->val.l[i])
+ break;
+ ++i;
+ }
+ --(*data_len);
+ ++(*data);
+
+ if (i > 0)
+ ret->val.l[i] = NULL;
+
+ return ret;
+ } else if (dc == 'd') {
+ unsigned int i = 0;
+
+ ret = be_alloc(BE_DICT);
+
+ --(*data_len);
+ ++(*data);
+ while (**data != 'e') {
+ ret->val.d =
+ g_realloc(ret->val.d, (i + 2) * sizeof(*ret->val.d));
+ ret->val.d[i].key = _be_decode_str(data, data_len);
+ ret->val.d[i].val = _be_decode(data, data_len);
+ if (!ret->val.l[i])
+ break;
+ ++i;
+ }
+ --(*data_len);
+ ++(*data);
+
+ if (i > 0)
+ ret->val.d[i].val = NULL;
+
+ return ret;
+ } else if (dc == 'i') {
+ ret = be_alloc(BE_INT);
+
+ --(*data_len);
+ ++(*data);
+ ret->val.i = _be_decode_int(data, data_len);
+ if (**data != 'e')
+ return NULL;
+ --(*data_len);
+ ++(*data);
+
+ return ret;
+ } else if (isdigit(dc)) {
+ ret = be_alloc(BE_STR);
+
+ ret->val.s = _be_decode_str(data, data_len);
+ return ret;
+ }
+
+ return ret;
+}
+
+be_node *be_decoden(const char *data, gint64 len)
+{
+ return _be_decode(&data, &len);
+}
+
+be_node *be_decode(const char *data)
+{
+ return be_decoden(data, strlen(data));
+}
+
+gboolean be_validate_node(be_node * node, gint type)
+{
+ if (!node || node->type != type)
+ return FALSE;
+ else
+ return TRUE;
+}
+
+static inline void _be_free_str(char *str)
+{
+ if (str)
+ g_free(str - sizeof(gint64));
+}
+
+void be_free(be_node * node)
+{
+ switch (node->type) {
+ case BE_STR:
+ _be_free_str(node->val.s);
+ break;
+
+ case BE_INT:
+ break;
+
+ case BE_LIST:
+ {
+ unsigned int i;
+ if (node->val.l) {
+ for (i = 0; node->val.l[i]; ++i)
+ be_free(node->val.l[i]);
+ g_free(node->val.l);
+ }
+ break;
+ }
+
+ case BE_DICT:
+ {
+ unsigned int i;
+ if (node->val.d) {
+ for (i = 0; node->val.d[i].val; ++i) {
+ _be_free_str(node->val.d[i].key);
+ be_free(node->val.d[i].val);
+ }
+ g_free(node->val.d);
+ }
+ break;
+ }
+ }
+ g_free(node);
+}
+
+be_node *be_dict_find(be_node * node, char *key, int type)
+{
+ int i;
+ for (i = 0; node->val.d[i].val; ++i) {
+ if (!strcmp(node->val.d[i].key, key)) {
+ be_node *cn = node->val.d[i].val;
+ if (type < 0 || cn->type == type)
+ return node->val.d[i].val;
+ }
+ }
+ return NULL;
+}
+
+#ifdef BE_DEBUG
+#include <stdio.h>
+#include <stdint.h>
+
+static void _be_dump_indent(ssize_t indent)
+{
+ while (indent-- > 0)
+ printf(" ");
+}
+
+static void _be_dump(be_node * node, ssize_t indent)
+{
+ size_t i;
+
+ _be_dump_indent(indent);
+ indent = abs(indent);
+
+ switch (node->type) {
+ case BE_STR:
+ printf("str = %s (len = %lli)\n", node->val.s, be_str_len(node));
+ break;
+
+ case BE_INT:
+ printf("int = %lli\n", node->val.i);
+ break;
+
+ case BE_LIST:
+ puts("list [");
+
+ for (i = 0; node->val.l[i]; ++i)
+ _be_dump(node->val.l[i], indent + 1);
+
+ _be_dump_indent(indent);
+ puts("]");
+ break;
+
+ case BE_DICT:
+ puts("dict {");
+
+ for (i = 0; node->val.d[i].val; ++i) {
+ _be_dump_indent(indent + 1);
+ printf("%s => ", node->val.d[i].key);
+ _be_dump(node->val.d[i].val, -(indent + 1));
+ }
+
+ _be_dump_indent(indent);
+ puts("}");
+ break;
+ }
+}
+
+void be_dump(be_node * node)
+{
+ _be_dump(node, 0);
+}
+#endif
diff --git a/src/bencode.h b/src/bencode.h
new file mode 100644
index 0000000..23fc51a
--- /dev/null
+++ b/src/bencode.h
@@ -0,0 +1,66 @@
+/*
+ * C implementation of a bencode decoder.
+ * This is the format defined by BitTorrent:
+ * http://wiki.theory.org/BitTorrentSpecification#bencoding
+ *
+ * The only external requirements are a few [standard] function calls and
+ * the gint64 type. Any sane system should provide all of these things.
+ *
+ * This is released into the public domain.
+ * Written by Mike Frysinger <vapier@gmail.com>.
+ */
+
+/* USAGE:
+ * - pass the string full of the bencoded data to be_decode()
+ * - parse the resulting tree however you like
+ * - call be_free() on the tree to release resources
+ */
+
+#ifndef _BENCODE_H
+#define _BENCODE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ typedef enum {
+ BE_STR,
+ BE_INT,
+ BE_LIST,
+ BE_DICT
+ } be_type;
+
+ struct be_dict;
+ struct be_node;
+
+/*
+ * XXX: the "val" field of be_dict and be_node can be confusing ...
+ */
+
+ typedef struct be_dict {
+ char *key;
+ struct be_node *val;
+ } be_dict;
+
+ typedef struct be_node {
+ be_type type;
+ union {
+ char *s;
+ gint64 i;
+ struct be_node **l;
+ struct be_dict *d;
+ } val;
+ } be_node;
+
+ gint64 be_str_len(be_node * node);
+ be_node *be_decode(const char *bencode);
+ be_node *be_decoden(const char *bencode, gint64 bencode_len);
+ void be_free(be_node * node);
+ void be_dump(be_node * node);
+ be_node *be_dict_find(be_node * node, char *key, int type);
+ gboolean be_validate_node(be_node * node, gint type);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/gtk.suppression b/src/gtk.suppression
new file mode 100644
index 0000000..2cea334
--- /dev/null
+++ b/src/gtk.suppression
@@ -0,0 +1,294 @@
+#
+# Valgrind suppression file for Gtk+ 2.12
+#
+# Format specification:
+# http://valgrind.org/docs/manual/manual-core.html#manual-core.suppress
+#
+
+#
+# glibc Ubuntu Edgy
+#
+
+{
+ libc: getpwnam_r
+ Memcheck:Addr4
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/tls/i686/cmov/libc-*.so
+ obj:/lib/ld-*.so
+ fun:__libc_dlopen_mode
+ fun:__nss_lookup_function
+ obj:/lib/tls/i686/cmov/libc-*.so
+ fun:__nss_passwd_lookup
+ fun:getpwnam_r
+ fun:g_get_any_init_do
+ fun:g_get_home_dir
+ fun:gtk_rc_add_initial_default_files
+ fun:_gtk_rc_init
+ fun:post_parse_hook
+ fun:g_option_context_parse
+ fun:gtk_parse_args
+ fun:gtk_init_check
+ fun:gtk_init
+}
+
+{
+ libc: getpwnam_r
+ Memcheck:Addr4
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/tls/i686/cmov/libc-*.so
+ obj:/lib/ld-*.so
+ fun:__libc_dlopen_mode
+ fun:__nss_lookup_function
+ obj:/lib/tls/i686/cmov/libc-*.so
+ fun:__nss_passwd_lookup
+ fun:getpwnam_r
+ fun:g_get_any_init_do
+ fun:g_get_home_dir
+ fun:gtk_rc_add_initial_default_files
+ fun:_gtk_rc_init
+ fun:post_parse_hook
+ fun:g_option_context_parse
+ fun:gtk_parse_args
+ fun:gtk_init_check
+ fun:gtk_init
+}
+
+{
+ libc: getpwnam_r
+ Memcheck:Addr4
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/tls/i686/cmov/libc-*.so
+ obj:/lib/ld-*.so
+ fun:__libc_dlopen_mode
+ fun:__nss_lookup_function
+ fun:__nss_next
+ fun:getpwnam_r
+ fun:g_get_any_init_do
+ fun:g_get_home_dir
+ fun:gtk_rc_add_initial_default_files
+ fun:_gtk_rc_init
+ fun:post_parse_hook
+ fun:g_option_context_parse
+ fun:gtk_parse_args
+ fun:gtk_init_check
+ fun:gtk_init
+}
+
+{
+ libc: getpwnam_r
+ Memcheck:Addr4
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/tls/i686/cmov/libc-*.so
+ obj:/lib/ld-*.so
+ fun:__libc_dlopen_mode
+ fun:__nss_lookup_function
+ fun:__nss_next
+ fun:getpwnam_r
+ fun:g_get_any_init_do
+ fun:g_get_home_dir
+ fun:gtk_rc_add_initial_default_files
+ fun:_gtk_rc_init
+ fun:post_parse_hook
+ fun:g_option_context_parse
+ fun:gtk_parse_args
+ fun:gtk_init_check
+ fun:gtk_init
+}
+
+#
+# glibc Ubuntu feisty
+#
+
+{
+ getpwnam_r
+ Memcheck:Leak
+ fun:malloc
+ obj:/lib/libc-2.5.so
+ fun:__nss_database_lookup
+ obj:*
+ obj:*
+ fun:getpwnam_r
+}
+
+#
+# X
+#
+
+{
+ XSupportsLocale
+ Memcheck:Addr4
+ obj:/lib/ld-*.so
+ obj:/lib/tls/i686/cmov/libdl-*.so
+ obj:/lib/ld-*.so
+ obj:/lib/tls/i686/cmov/libdl-*.so
+ fun:dlopen
+ obj:/usr/lib/libX11.so.6.2.0
+ fun:_XlcDynamicLoad
+ fun:_XOpenLC
+ fun:_XlcCurrentLC
+ fun:XSupportsLocale
+ fun:_gdk_x11_initialize_locale
+ fun:_gdk_windowing_init
+ fun:gdk_pre_parse_libgtk_only
+ fun:pre_parse_hook
+ fun:g_option_context_parse
+ fun:gtk_parse_args
+ fun:gtk_init_check
+ fun:gtk_init
+ fun:main
+}
+
+
+{
+ Xcursor
+ Memcheck:Leak
+ fun:malloc
+ obj:/usr/lib/libXcursor.so.1.0.2
+ obj:/usr/lib/libXcursor.so.1.0.2
+ fun:XcursorXcFileLoadImages
+ fun:XcursorFileLoadImages
+ fun:XcursorLibraryLoadImages
+ fun:XcursorShapeLoadImages
+ fun:XcursorTryShapeCursor
+ fun:XCreateGlyphCursor
+ fun:XCreateFontCursor
+ fun:gdk_cursor_new_for_display
+}
+
+{
+ XcursorGetTheme
+ Memcheck:Leak
+ fun:malloc
+ fun:/usr/lib/libX11.so.6.2.0
+ fun:/usr/lib/libX11.so.6.2.0
+ fun:XrmGetStringDatabase
+ fun:XGetDefault
+ fun:_XcursorGetDisplayInfo
+ fun:XcursorGetTheme
+}
+
+{
+ XOpenDisplay
+ Memcheck:Leak
+ fun:calloc
+ fun:XOpenDisplay
+}
+
+{
+ XOpenDisplay
+ Memcheck:Leak
+ fun:malloc
+ fun:XOpenDisplay
+}
+
+#
+# fontconfig
+#
+
+{
+ fontconfig
+ Memcheck:Leak
+ fun:realloc
+ fun:FcPatternObjectInsertElt
+ fun:FcPatternObjectAddWithBinding
+}
+
+{
+ pango_fc_font_map_load_fontset
+ Memcheck:Leak
+ fun:malloc
+ fun:FcLangSetCreate
+ fun:FcLangSetCopy
+ fun:FcValueSave
+ fun:FcPatternObjectAddWithBinding
+ fun:FcPatternObjectAdd
+ fun:FcFontRenderPrepare
+ fun:pango_fc_font_map_load_fontset
+ fun:pango_font_map_load_fontset
+}
+
+{
+ pango_font_map_load_fontset
+ Memcheck:Leak
+ fun:malloc
+ fun:FcPatternObjectAddWithBinding
+ fun:FcPatternObjectAdd
+ fun:FcFontRenderPrepare
+ fun:pango_fc_font_map_load_fontset
+ fun:pango_font_map_load_fontset
+}
+
+{
+ pango_fc_font_map_load_fontset
+ Memcheck:Leak
+ fun:malloc
+ fun:FcStrStaticName
+ fun:FcPatternObjectAddWithBinding
+ fun:FcPatternObjectAdd
+ fun:FcFontRenderPrepare
+ fun:pango_fc_font_map_load_fontset
+}
+
+{
+ pango_fc_font_map_list_families
+ Memcheck:Leak
+ fun:malloc
+ fun:FcStrStaticName
+ fun:FcPatternObjectAddWithBinding
+ fun:FcPatternAdd
+ fun:FcFontSetList
+ fun:FcFontList
+ fun:pango_fc_font_map_list_families
+}
+
+#
+# freetype
+#
+
+{
+ freetype FT_Init_FreeType
+ Memcheck:Leak
+ fun:malloc
+ obj:/usr/lib/libfreetype.so.6.3.10
+ fun:ft_mem_qalloc
+ fun:ft_mem_alloc
+ fun:FT_New_Library
+ fun:FT_Init_FreeType
+}
+
+#
+# glib
+#
+
+{
+ glib g_rand_new
+ Memcheck:Leak
+ fun:calloc
+ fun:g_malloc0
+ fun:g_rand_new_with_seed_array
+ fun:g_rand_new
+ fun:g_random_int
+}
diff --git a/src/hicolor_apps_16x16_transmission-remote-gtk.png b/src/hicolor_apps_16x16_transmission-remote-gtk.png
new file mode 100644
index 0000000..78b1af2
--- /dev/null
+++ b/src/hicolor_apps_16x16_transmission-remote-gtk.png
Binary files differ
diff --git a/src/hicolor_apps_22x22_transmission-remote-gtk.png b/src/hicolor_apps_22x22_transmission-remote-gtk.png
new file mode 100644
index 0000000..3283ea2
--- /dev/null
+++ b/src/hicolor_apps_22x22_transmission-remote-gtk.png
Binary files differ
diff --git a/src/hicolor_apps_24x24_transmission-remote-gtk.png b/src/hicolor_apps_24x24_transmission-remote-gtk.png
new file mode 100644
index 0000000..6200ec0
--- /dev/null
+++ b/src/hicolor_apps_24x24_transmission-remote-gtk.png
Binary files differ
diff --git a/src/hicolor_apps_32x32_transmission-remote-gtk.png b/src/hicolor_apps_32x32_transmission-remote-gtk.png
new file mode 100644
index 0000000..d3d6464
--- /dev/null
+++ b/src/hicolor_apps_32x32_transmission-remote-gtk.png
Binary files differ
diff --git a/src/hicolor_apps_48x48_transmission-remote-gtk.png b/src/hicolor_apps_48x48_transmission-remote-gtk.png
new file mode 100644
index 0000000..3444de8
--- /dev/null
+++ b/src/hicolor_apps_48x48_transmission-remote-gtk.png
Binary files differ
diff --git a/src/hicolor_apps_scalable_transmission-remote-gtk.svg b/src/hicolor_apps_scalable_transmission-remote-gtk.svg
new file mode 100644
index 0000000..e9c4cec
--- /dev/null
+++ b/src/hicolor_apps_scalable_transmission-remote-gtk.svg
@@ -0,0 +1,445 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg5186"
+ sodipodi:version="0.32"
+ inkscape:version="0.45+devel"
+ sodipodi:docname="transmission.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/home/andreas/project/application icons/48x48/transmission.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs5188">
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient9795">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop9797" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop9799" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient9783">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop9785" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop9787" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient9775">
+ <stop
+ style="stop-color:#f9f9f9;stop-opacity:1"
+ offset="0"
+ id="stop9777" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0.62037037"
+ offset="1"
+ id="stop9779" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5948">
+ <stop
+ style="stop-color:#787b76;stop-opacity:1;"
+ offset="0"
+ id="stop5950" />
+ <stop
+ id="stop5956"
+ offset="0.87125719"
+ style="stop-color:#babcb9;stop-opacity:1" />
+ <stop
+ style="stop-color:#787b76;stop-opacity:1"
+ offset="1"
+ id="stop5952" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5908">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop5910" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop5912" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5898">
+ <stop
+ style="stop-color:#cc0000;stop-opacity:1;"
+ offset="0"
+ id="stop5900" />
+ <stop
+ id="stop5906"
+ offset="0.36509839"
+ style="stop-color:#ef0000;stop-opacity:1" />
+ <stop
+ style="stop-color:#aa0000;stop-opacity:1"
+ offset="1"
+ id="stop5902" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5871">
+ <stop
+ style="stop-color:#f0f2ef;stop-opacity:1"
+ offset="0"
+ id="stop5873" />
+ <stop
+ style="stop-color:#cdd1c8;stop-opacity:1"
+ offset="1"
+ id="stop5875" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5843">
+ <stop
+ style="stop-color:#888a85;stop-opacity:1"
+ offset="0"
+ id="stop5845" />
+ <stop
+ style="stop-color:#2e3436;stop-opacity:1"
+ offset="1"
+ id="stop5847" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5835">
+ <stop
+ style="stop-color:#555753;stop-opacity:1;"
+ offset="0"
+ id="stop5837" />
+ <stop
+ style="stop-color:#2e3436;stop-opacity:1"
+ offset="1"
+ id="stop5839" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5823">
+ <stop
+ style="stop-color:#2e3436;stop-opacity:1;"
+ offset="0"
+ id="stop5825" />
+ <stop
+ style="stop-color:#2e3436;stop-opacity:0;"
+ offset="1"
+ id="stop5827" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5234">
+ <stop
+ style="stop-color:#babdb6;stop-opacity:1;"
+ offset="0"
+ id="stop5236" />
+ <stop
+ id="stop5242"
+ offset="0.13299191"
+ style="stop-color:#eeeeec;stop-opacity:1" />
+ <stop
+ style="stop-color:#babdb6;stop-opacity:1"
+ offset="1"
+ id="stop5238" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5234"
+ id="linearGradient5240"
+ x1="23.738585"
+ y1="4.156569"
+ x2="23.738585"
+ y2="19.46567"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5823"
+ id="linearGradient5829"
+ x1="23.732271"
+ y1="30.057167"
+ x2="23.688078"
+ y2="22.632544"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5835"
+ id="linearGradient5841"
+ x1="23.9375"
+ y1="30.616879"
+ x2="23.9375"
+ y2="36.357994"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5843"
+ id="linearGradient5849"
+ x1="20.771132"
+ y1="32.248005"
+ x2="20.563131"
+ y2="23.939499"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5898"
+ id="linearGradient5904"
+ x1="14.8125"
+ y1="5.6244211"
+ x2="14.8125"
+ y2="9"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5908"
+ id="linearGradient5914"
+ x1="24.040522"
+ y1="5.0690055"
+ x2="24.040522"
+ y2="10.0086"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5871"
+ id="linearGradient5928"
+ x1="13.625"
+ y1="33.125"
+ x2="14.125"
+ y2="24"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5948"
+ id="linearGradient5954"
+ x1="10.1875"
+ y1="20.25"
+ x2="10.1875"
+ y2="42.5"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ inkscape:collect="always"
+ id="filter9771"
+ x="-0.02976581"
+ width="1.0595316"
+ y="-0.13995509"
+ height="1.2799102">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.5196773"
+ id="feGaussianBlur9773" />
+ </filter>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient9775"
+ id="linearGradient9781"
+ x1="24.71875"
+ y1="35.958694"
+ x2="23.936657"
+ y2="17.070877"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient9783"
+ id="linearGradient9789"
+ x1="18.3125"
+ y1="20.743757"
+ x2="18.3125"
+ y2="21.814325"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient9795"
+ id="linearGradient9801"
+ x1="30.4375"
+ y1="31.82852"
+ x2="29.742416"
+ y2="27.45352"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.6568542"
+ inkscape:cx="30.372474"
+ inkscape:cy="21.423534"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1091"
+ inkscape:window-height="777"
+ inkscape:window-x="557"
+ inkscape:window-y="164">
+ <inkscape:grid
+ type="xygrid"
+ id="grid5195" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata5191">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <rect
+ style="opacity:0.28240741;fill:#2e3436;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter9771)"
+ id="rect9761"
+ width="41.901279"
+ height="8.9116125"
+ x="3"
+ y="39"
+ rx="2.2980971"
+ ry="2.2980971" />
+ <path
+ style="fill:url(#linearGradient5954);fill-rule:evenodd;stroke:#555753;stroke-width:1.00000011999999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
+ d="M 10,16.59375 C 8.8196081,16.548814 7.6402135,17.571722 7.53125,18.8125 C 6.643292,26.100083 5.3269606,33.403527 4.65625,40.6875 L 4.65625,43.75 C 4.6900093,45.329492 5.7271791,46.392039 6.875,46.59375 L 41.5,46.59375 C 42.479024,46.569246 43.565009,45.89005 43.53125,44.59375 L 43.53125,40.65625 L 40.40625,19.4375 C 40.152431,18.135677 39.039534,16.752716 37.5,16.59375 L 10,16.59375 z"
+ id="path5232"
+ sodipodi:nodetypes="ccccccccccc" />
+ <path
+ style="fill:url(#linearGradient5928);fill-opacity:1;fill-rule:evenodd;stroke:#555753;stroke-width:0.99999994000000003px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 10.601853,39.624614 C 9.47224,39.502143 8.6733861,38.760954 8.7014295,37.401046 L 10.601853,21.407733 C 10.893931,20.339398 11.586949,19.485349 12.680909,19.488442 L 34.605501,19.488442 C 35.691818,19.455762 36.778134,20.208796 37.062569,21.104687 L 39.478435,37.237611 C 39.535481,38.706714 38.931012,39.557098 37.913093,39.523599 L 10.601853,39.624614 z"
+ id="path5230"
+ sodipodi:nodetypes="ccccccccc" />
+ <path
+ style="fill:url(#linearGradient5841);fill-rule:evenodd;stroke:url(#linearGradient5849);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
+ d="M 20.46875,20.4375 L 18.40625,32.46875 L 15.4375,32.46875 L 23.46875,37.625 L 32.4375,32.46875 L 29.46875,32.46875 L 27.59375,20.4375 L 20.46875,20.4375 z"
+ id="path5197"
+ sodipodi:nodetypes="cccccccc" />
+ <rect
+ style="opacity:1;fill:url(#linearGradient5904);fill-opacity:1;stroke:#930000;stroke-width:1.00000011999999994;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect5224"
+ width="31.113209"
+ height="6.0609155"
+ x="8.4847708"
+ y="4.5135489"
+ rx="5.0159144"
+ ry="1.9854566" />
+ <rect
+ style="opacity:0.58333333;fill:none;fill-opacity:1;stroke:url(#linearGradient5914);stroke-width:1.00000011999999994;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect5896"
+ width="29.080278"
+ height="3.9395947"
+ x="9.5003824"
+ y="5.5690055"
+ rx="1.8339339"
+ ry="1.2783499" />
+ <path
+ style="opacity:0.24537036000000001;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient9781);stroke-width:1.00000011999999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 10.592965,17.57221 C 9.474152,17.53019 8.3562869,18.486727 8.2530054,19.647002 L 5.4687498,39.722803 C 5.4796612,39.847886 5.4997885,39.979699 5.5279893,40.102694 L 5.5279893,42.966491 C 5.559989,44.443503 6.5430497,45.407885 7.6309909,45.596509 L 40.479283,45.596509 C 41.407232,45.573597 42.406944,44.967688 42.374947,43.755497 L 42.374947,40.073472 C 42.382229,40.044972 42.398547,40.013922 42.404566,39.985805 L 42.374947,39.781247 L 42.374947,39.576691 L 42.345327,39.576691 L 39.442592,20.202228 C 39.202015,18.98487 38.147175,17.72086 36.687956,17.57221 L 10.592965,17.57221 z"
+ id="path5881" />
+ <path
+ style="fill:url(#linearGradient9789);fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1;opacity:0.20833333000000001"
+ d="M 10.210155,29.955767 L 12.048004,22 L 36.07815,22.05802 L 37.857941,31.044156 L 36.681164,21.969631 C 36.460193,20.967897 35.929863,20 34.957591,20.025088 L 13.037281,19.980893 C 11.606886,19.936699 11.32554,20.864777 11,21.969631 L 10.210155,29.955767 z"
+ id="path5926"
+ sodipodi:nodetypes="ccccccccc" />
+ <rect
+ style="opacity:1;fill:url(#linearGradient5240);fill-opacity:1;stroke:#888a85;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect5226"
+ width="7.0964494"
+ height="25.970053"
+ x="20.48369"
+ y="3.6044116"
+ rx="1.0763195"
+ ry="1.0763192" />
+ <rect
+ style="opacity:1;fill:url(#linearGradient5829);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect5244"
+ width="8.1317272"
+ height="8.0433397"
+ x="19.975765"
+ y="22.013826"
+ rx="1.0763195"
+ ry="1.0763192" />
+ <path
+ style="opacity:0.43518521;fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 11.423372,41.486321 L 39.533811,41.486321"
+ id="path5879"
+ sodipodi:nodetypes="cc" />
+ <rect
+ style="opacity:0.22685185;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect5892"
+ width="5.151906"
+ height="23.93712"
+ x="21.428234"
+ y="4.6321397"
+ rx="1.0763195"
+ ry="1.0763192" />
+ <g
+ id="g5972"
+ style="opacity:0.62037037">
+ <path
+ sodipodi:nodetypes="cc"
+ id="path5831"
+ d="M 20.4375,30.5 L 27.5,30.5"
+ style="fill:none;fill-rule:evenodd;stroke:#888a85;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;opacity:1" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path5833"
+ d="M 19.960998,32.5 L 27.976504,32.5"
+ style="fill:none;fill-rule:evenodd;stroke:#888a85;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;opacity:0.68055556" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path5958"
+ d="M 20.273498,31.5 L 27.726504,31.5"
+ style="fill:none;fill-rule:evenodd;stroke:#5d5d5c;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;opacity:1" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path5960"
+ d="M 19.869986,33.488738 L 28.141277,33.488738"
+ style="fill:none;fill-rule:evenodd;stroke:#5d5d5c;stroke-width:0.99999994000000003px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;opacity:0.68055556" />
+ </g>
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 14.381412,31.513733 L 17.519198,31.513733"
+ id="path9791"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 30.443912,31.451233 L 33.581698,31.451233"
+ id="path9803"
+ sodipodi:nodetypes="cc" />
+ <path
+ sodipodi:type="arc"
+ style="opacity:0.33500001;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path5119"
+ sodipodi:cx="9.8553009"
+ sodipodi:cy="42.188465"
+ sodipodi:rx="1.1932427"
+ sodipodi:ry="1.0827572"
+ d="M 11.048544,42.188465 A 1.1932427,1.0827572 0 1 1 8.6620582,42.188465 A 1.1932427,1.0827572 0 1 1 11.048544,42.188465 z"
+ transform="matrix(0.4216252,0,0,0.4766032,5.3634688,21.39228)" />
+ </g>
+</svg>
diff --git a/src/hig.c b/src/hig.c
new file mode 100644
index 0000000..32432fe
--- /dev/null
+++ b/src/hig.c
@@ -0,0 +1,213 @@
+/*
+ * This file Copyright (C) 2007-2010 Mnemosyne LLC
+ *
+ * This file is licensed by the GPL version 2. Works owned by the
+ * Transmission project are granted a special exemption to clause 2(b)
+ * so that the bulk of its code can remain under the MIT license.
+ * This exemption does not extend to derived works not owned by
+ * the Transmission project.
+ *
+ * $Id: hig.c 9868 2010-01-04 21:00:47Z charles $
+ */
+
+#include <gtk/gtk.h>
+#include "hig.h"
+#include "util.h"
+
+GtkWidget *hig_workarea_create(void)
+{
+ GtkWidget *t = gtk_table_new(1, 2, FALSE);
+
+ gtk_container_set_border_width(GTK_CONTAINER(t), GUI_PAD_BIG);
+ gtk_table_set_col_spacing(GTK_TABLE(t), 0, GUI_PAD_BIG);
+ gtk_table_set_row_spacings(GTK_TABLE(t), GUI_PAD);
+ return t;
+}
+
+void hig_workarea_add_section_divider(GtkWidget * t, guint * row)
+{
+ GtkWidget *w = gtk_alignment_new(0.0f, 0.0f, 0.0f, 0.0f);
+
+ gtk_widget_set_size_request(w, 0u, 6u);
+ gtk_table_attach(GTK_TABLE(t), w, 0, 2, *row, *row + 1, 0, 0, 0, 0);
+ ++*row;
+}
+
+void
+hig_workarea_add_section_title_widget(GtkWidget * t, guint * row,
+ GtkWidget * w)
+{
+ gtk_table_attach(GTK_TABLE(t), w, 0, 2, *row, *row + 1, ~0, 0, 0, 0);
+ ++*row;
+}
+
+void
+hig_workarea_add_section_title(GtkWidget * t,
+ guint * row, const char *section_title)
+{
+ char buf[512];
+ GtkWidget *l;
+
+ g_snprintf(buf, sizeof(buf), "<b>%s</b>", section_title);
+ l = gtk_label_new(buf);
+ gtk_misc_set_alignment(GTK_MISC(l), 0.0f, 0.5f);
+ gtk_label_set_use_markup(GTK_LABEL(l), TRUE);
+ hig_workarea_add_section_title_widget(t, row, l);
+}
+
+static GtkWidget *rowNew(GtkWidget * w)
+{
+ GtkWidget *a;
+ GtkWidget *h = trg_hbox_new(FALSE, 0);
+
+ /* spacer */
+ a = gtk_alignment_new(0.0f, 0.0f, 0.0f, 0.0f);
+ gtk_widget_set_size_request(a, 18u, 0u);
+ gtk_box_pack_start(GTK_BOX(h), a, FALSE, FALSE, 0);
+
+ /* lhs widget */
+ if (GTK_IS_MISC(w))
+ gtk_misc_set_alignment(GTK_MISC(w), 0.0f, 0.5f);
+ if (GTK_IS_LABEL(w))
+ gtk_label_set_use_markup(GTK_LABEL(w), TRUE);
+ gtk_box_pack_start(GTK_BOX(h), w, TRUE, TRUE, 0);
+
+ return h;
+}
+
+void hig_workarea_add_wide_control(GtkWidget * t, guint * row,
+ GtkWidget * w)
+{
+ GtkWidget *r = rowNew(w);
+
+ gtk_table_attach(GTK_TABLE(t), r, 0, 2, *row, *row + 1, GTK_FILL,
+ 0, 0, 0);
+ ++*row;
+}
+
+void
+hig_workarea_add_wide_tall_control(GtkWidget * t, guint * row,
+ GtkWidget * w)
+{
+ GtkWidget *r = rowNew(w);
+
+ gtk_table_attach(GTK_TABLE(t), r, 0, 2, *row, *row + 1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
+
+ ++*row;
+}
+
+GtkWidget *hig_workarea_add_wide_checkbutton(GtkWidget * t,
+ guint * row,
+ const char *mnemonic_string,
+ gboolean is_active)
+{
+ GtkWidget *w = gtk_check_button_new_with_mnemonic(mnemonic_string);
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), is_active);
+ hig_workarea_add_wide_control(t, row, w);
+ return w;
+}
+
+void hig_workarea_add_label_w(GtkWidget * t, guint row, GtkWidget * l)
+{
+ GtkWidget *w = rowNew(l);
+
+ gtk_table_attach(GTK_TABLE(t), w, 0, 1, row, row + 1, GTK_FILL,
+ GTK_FILL, 0, 0);
+}
+
+GtkWidget *hig_workarea_add_label(GtkWidget * t, guint row,
+ const char *mnemonic_string)
+{
+ GtkWidget *l = gtk_label_new_with_mnemonic(mnemonic_string);
+
+ hig_workarea_add_label_w(t, row, l);
+ return l;
+}
+
+static void
+hig_workarea_add_tall_control(GtkWidget * t, guint row,
+ GtkWidget * control)
+{
+ if (GTK_IS_MISC(control))
+ gtk_misc_set_alignment(GTK_MISC(control), 0.0f, 0.5f);
+
+ gtk_table_attach(GTK_TABLE(t), control,
+ 1, 2, row, row + 1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
+}
+
+void hig_workarea_add_control(GtkWidget * t, guint row,
+ GtkWidget * control)
+{
+ if (GTK_IS_MISC(control))
+ gtk_misc_set_alignment(GTK_MISC(control), 0.0f, 0.5f);
+
+ gtk_table_attach(GTK_TABLE(t), control,
+ 1, 2, row, row + 1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0, 0);
+}
+
+void
+hig_workarea_add_row_w(GtkWidget * t,
+ guint * row,
+ GtkWidget * label,
+ GtkWidget * control, GtkWidget * mnemonic)
+{
+ hig_workarea_add_label_w(t, *row, label);
+ hig_workarea_add_control(t, *row, control);
+ if (GTK_IS_LABEL(label))
+ gtk_label_set_mnemonic_widget(GTK_LABEL(label),
+ mnemonic ? mnemonic : control);
+ ++*row;
+}
+
+GtkWidget *hig_workarea_add_row(GtkWidget * t,
+ guint * row,
+ const char *mnemonic_string,
+ GtkWidget * control, GtkWidget * mnemonic)
+{
+ GtkWidget *l = gtk_label_new_with_mnemonic(mnemonic_string);
+
+ hig_workarea_add_row_w(t, row, l, control, mnemonic);
+ return l;
+}
+
+GtkWidget *hig_workarea_add_tall_row(GtkWidget * table,
+ guint * row,
+ const char *mnemonic_string,
+ GtkWidget * control,
+ GtkWidget * mnemonic)
+{
+ GtkWidget *l = gtk_label_new_with_mnemonic(mnemonic_string);
+ GtkWidget *h = trg_hbox_new(FALSE, 0);
+ GtkWidget *v = trg_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(h), l, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(v), h, FALSE, FALSE, GUI_PAD_SMALL);
+
+ hig_workarea_add_label_w(table, *row, v);
+ hig_workarea_add_tall_control(table, *row, control);
+
+ if (GTK_IS_LABEL(l))
+ gtk_label_set_mnemonic_widget(GTK_LABEL(l),
+ mnemonic ? mnemonic : control);
+
+ ++*row;
+ return l;
+}
+
+void hig_workarea_finish(GtkWidget * t, guint * row)
+{
+ gtk_table_resize(GTK_TABLE(t), *row, 2);
+}
+
+void
+hig_message_dialog_set_text(GtkMessageDialog * dialog,
+ const char *primary, const char *secondary)
+{
+ gtk_message_dialog_set_markup(dialog, primary);
+ gtk_message_dialog_format_secondary_text(dialog, "%s", secondary);
+}
diff --git a/src/hig.h b/src/hig.h
new file mode 100644
index 0000000..de4a8bc
--- /dev/null
+++ b/src/hig.h
@@ -0,0 +1,86 @@
+/*
+ * This file Copyright (C) 2007-2010 Mnemosyne LLC
+ *
+ * This file is licensed by the GPL version 2. Works owned by the
+ * Transmission project are granted a special exemption to clause 2(b)
+ * so that the bulk of its code can remain under the MIT license.
+ * This exemption does not extend to derived works not owned by
+ * the Transmission project.
+ *
+ * $Id: hig.h 9868 2010-01-04 21:00:47Z charles $
+ */
+
+#ifndef __HIG_H__
+#define __HIG_H__
+
+#include <gtk/gtk.h>
+
+/**
+*** utility code for making dialog layout that follows the Gnome HIG.
+*** see section 8.2.2, Visual Design > Window Layout > Dialogs.
+**/
+
+GtkWidget *hig_workarea_create(void);
+
+void hig_workarea_add_section_divider(GtkWidget * table, guint * row);
+
+void hig_workarea_add_section_title_widget(GtkWidget * t,
+ guint * row, GtkWidget * w);
+
+void hig_workarea_add_section_title(GtkWidget * table,
+ guint * row,
+ const char *section_title);
+
+void hig_workarea_add_wide_tall_control(GtkWidget * table,
+ guint * row, GtkWidget * w);
+
+void hig_workarea_add_wide_control(GtkWidget * table,
+ guint * row, GtkWidget * w);
+
+GtkWidget *hig_workarea_add_wide_checkbutton(GtkWidget * table,
+ guint * row,
+ const char *mnemonic_string,
+ gboolean is_active);
+
+GtkWidget *hig_workarea_add_label(GtkWidget * table,
+ guint row, const char *mnemonic_string);
+
+void hig_workarea_add_label_w(GtkWidget * table,
+ guint row, GtkWidget * label_widget);
+
+void hig_workarea_add_control(GtkWidget * table,
+ guint row, GtkWidget * control);
+
+GtkWidget *hig_workarea_add_tall_row(GtkWidget * table,
+ guint * row,
+ const char *mnemonic_string,
+ GtkWidget * control,
+ GtkWidget *
+ mnemonic_or_null_for_control);
+
+GtkWidget *hig_workarea_add_row(GtkWidget * table,
+ guint * row,
+ const char *mnemonic_string,
+ GtkWidget * control,
+ GtkWidget * mnemonic_or_null_for_control);
+
+void hig_workarea_add_row_w(GtkWidget * table,
+ guint * row,
+ GtkWidget * label,
+ GtkWidget * control,
+ GtkWidget * mnemonic_or_null_for_control);
+
+void hig_workarea_finish(GtkWidget * table, guint * row);
+
+void hig_message_dialog_set_text(GtkMessageDialog * dialog,
+ const char *primary,
+ const char *secondary);
+
+enum {
+ GUI_PAD_SMALL = 3,
+ GUI_PAD = 6,
+ GUI_PAD_BIG = 12,
+ GUI_PAD_LARGE = 12
+};
+
+#endif /* __HIG_H__ */
diff --git a/src/icon-turtle.h b/src/icon-turtle.h
new file mode 100644
index 0000000..953fb50
--- /dev/null
+++ b/src/icon-turtle.h
@@ -0,0 +1,84 @@
+/* GdkPixbuf RGBA C-Source image dump 1-byte-run-length-encoded */
+
+#ifdef __SUNPRO_C
+#pragma align 4 (blue_turtle)
+#endif
+#ifdef __GNUC__
+static const guint8 blue_turtle[] __attribute__ ((__aligned__(4))) =
+#else
+static const guint8 blue_turtle[] =
+#endif
+{ ""
+ /* Pixbuf magic (0x47646b50) */
+ "GdkP"
+ /* length: header (24) + pixel_data (315) */
+ "\0\0\1S"
+ /* pixdata_type (0x2010002) */
+ "\2\1\0\2"
+ /* rowstride (80) */
+ "\0\0\0P"
+ /* width (20) */
+ "\0\0\0\24"
+ /* height (14) */
+ "\0\0\0\16"
+ /* pixel_data: */
+ "\304\377\377\377\0\3"
+ "66\377@77\377p55\3770\205\377\377\377\0\1\0\0\0"
+ "\0\211\377\377\377\0\2"
+ "66\3770<<\377\317\202\77\77\377\377\2>>\377\377"
+ "::\377\237\204\377\377\377\0\1\0\0\0\0\210\377\377\377\0\14"
+ "66\3770"
+ "BB\377\360FF\377\377II\377\377JJ\377\377HH\377\377DD\377\377==\377\237"
+ "\377\377\377\0"
+ "99\377`\77\77\377\377;;\377\237\207\377\377\377\0\4" "5"
+ "5\377\20FF\377\360KK\377\377OO\377\377\202QQ\377\377\7PP\377\377NN\377"
+ "\377II\377\377EE\377\377FF\377\377II\377\377[[\377\267\207\377\377\377"
+ "\0\3EE\377\261NN\377\377TT\377\377\204VV\377\377\3UU\377\377ee\377\331"
+ "\252\252\377\214\202\377\377\377f\1\377\377\377@\207\377\377\377\0\3"
+ "\377\377\377F\224\224\377\240YY\377\377\204ZZ\377\377\3XX\377\377kk\377"
+ "\274\377\377\377\31\202\377\377\377\0\1\0\0\0\0\210\377\377\377\0\2m"
+ "m\377wvv\377\305\204\377\377\377f\3\230\230\377\237TT\377\377FF\377\237"
+ "\202\377\377\377\0\1\0\0\0\0\323\377\377\377\0"
+};
+
+
+/* GdkPixbuf RGBA C-Source image dump 1-byte-run-length-encoded */
+
+#ifdef __SUNPRO_C
+#pragma align 4 (grey_turtle)
+#endif
+#ifdef __GNUC__
+static const guint8 grey_turtle[] __attribute__ ((__aligned__(4))) =
+#else
+static const guint8 grey_turtle[] =
+#endif
+{ ""
+ /* Pixbuf magic (0x47646b50) */
+ "GdkP"
+ /* length: header (24) + pixel_data (315) */
+ "\0\0\1S"
+ /* pixdata_type (0x2010002) */
+ "\2\1\0\2"
+ /* rowstride (80) */
+ "\0\0\0P"
+ /* width (20) */
+ "\0\0\0\24"
+ /* height (14) */
+ "\0\0\0\16"
+ /* pixel_data: */
+ "\304\377\377\377\0\3\3\3\3@\5\5\5p\2\2\2"
+ "0\205\377\377\377\0\1\0\0\0" "\0\211\377\377\377\0\2\3\3\3"
+ "0\13\13\13\317\202\17\17\17\377\2\16\16"
+ "\16\377\11\11\11\237\204\377\377\377\0\1\0\0\0\0\210\377\377\377\0\14"
+ "\4\4\4"
+ "0\23\23\23\360\27\27\27\377\33\33\33\377\34\34\34\377\32\32\32"
+ "\377\25\25\25\377\14\14\14\237\377\377\377\0\7\7\7`\17\17\17\377\12\12"
+ "\12\237\207\377\377\377\0\4\2\2\2\20\27\27\27\360\36\36\36\377###\377"
+ "\202%%%\377\7$$$\377!!!\377\33\33\33\377\26\26\26\377\30\30\30\377\33"
+ "\33\33\377222\267\207\377\377\377\0\3\26\26\26\261\"\"\"\377)))\377\204"
+ "+++\377\3***\377\77\77\77\331\225\225\225\214\202\377\377\377f\1\377"
+ "\377\377@\207\377\377\377\0\3\377\377\377Fyyy\240///\377\204000\377\3"
+ "...\377FFF\274\377\377\377\31\202\377\377\377\0\1\0\0\0\0\210\377\377"
+ "\377\0\2HHHwTTT\305\204\377\377\377f\3~~~\237)))\377\27\27\27\237\202"
+ "\377\377\377\0\1\0\0\0\0\323\377\377\377\0"
+};
diff --git a/src/icons.c b/src/icons.c
new file mode 100644
index 0000000..bb8e33d
--- /dev/null
+++ b/src/icons.c
@@ -0,0 +1,252 @@
+/*
+ * icons.[ch] written by Paolo Bacchilega, who writes:
+ * "There is no problem for me, you can license my code
+ * under whatever licence you wish :)"
+ *
+ * $Id: icons.c 12639 2011-08-07 18:41:13Z jordan $
+ */
+
+#include <string.h> /* strcmp */
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include "icons.h"
+
+#define VOID_PIXBUF_KEY "void-pixbuf"
+
+static const char *get_static_string(const char *s)
+{
+ static GStringChunk *static_strings = NULL;
+
+ if (s == NULL)
+ return NULL;
+
+ if (static_strings == NULL)
+ static_strings = g_string_chunk_new(1024);
+
+ return g_string_chunk_insert_const(static_strings, s);
+}
+
+typedef struct {
+ GtkIconTheme *icon_theme;
+ int icon_size;
+ GHashTable *cache;
+} IconCache;
+
+
+static IconCache *icon_cache[7] =
+ { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+
+
+static GdkPixbuf *create_void_pixbuf(int width, int height)
+{
+ GdkPixbuf *p;
+
+ p = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
+ gdk_pixbuf_fill(p, 0xFFFFFF00);
+
+ return p;
+}
+
+
+static int get_size_in_pixels(GtkWidget * widget, GtkIconSize icon_size)
+{
+ int width, height;
+
+ gtk_icon_size_lookup_for_settings(gtk_widget_get_settings(widget),
+ icon_size, &width, &height);
+ return MAX(width, height);
+}
+
+
+static IconCache *icon_cache_new(GtkWidget * for_widget, int icon_size)
+{
+ IconCache *icon_cache;
+
+ g_return_val_if_fail(for_widget != NULL, NULL);
+
+ icon_cache = g_new0(IconCache, 1);
+ icon_cache->icon_theme =
+ gtk_icon_theme_get_for_screen(gtk_widget_get_screen(for_widget));
+ icon_cache->icon_size = get_size_in_pixels(for_widget, icon_size);
+ icon_cache->cache =
+ g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
+ g_object_unref);
+
+ g_hash_table_insert(icon_cache->cache, (void *) VOID_PIXBUF_KEY,
+ create_void_pixbuf(icon_cache->icon_size,
+ icon_cache->icon_size));
+
+ return icon_cache;
+}
+
+static const char *_icon_cache_get_icon_key(GIcon * icon)
+{
+ const char *key = NULL;
+
+ if (G_IS_THEMED_ICON(icon)) {
+ char **icon_names;
+ char *name;
+
+ g_object_get(icon, "names", &icon_names, NULL);
+ name = g_strjoinv(",", icon_names);
+
+ key = get_static_string(name);
+
+ g_free(name);
+ g_strfreev(icon_names);
+ } else if (G_IS_FILE_ICON(icon)) {
+ GFile *file;
+ char *filename;
+
+ file = g_file_icon_get_file(G_FILE_ICON(icon));
+ filename = g_file_get_path(file);
+
+ key = get_static_string(filename);
+
+ g_free(filename);
+ g_object_unref(file);
+ }
+
+ return key;
+}
+
+
+static GdkPixbuf *get_themed_icon_pixbuf(GThemedIcon * icon,
+ int size,
+ GtkIconTheme * icon_theme)
+{
+ char **icon_names = NULL;
+ GtkIconInfo *icon_info;
+ GdkPixbuf *pixbuf;
+ GError *error = NULL;
+
+ g_object_get(icon, "names", &icon_names, NULL);
+
+ icon_info =
+ gtk_icon_theme_choose_icon(icon_theme, (const char **) icon_names,
+ size, 0);
+ if (icon_info == NULL)
+ icon_info =
+ gtk_icon_theme_lookup_icon(icon_theme, "text-x-generic", size,
+ GTK_ICON_LOOKUP_USE_BUILTIN);
+
+ pixbuf = gtk_icon_info_load_icon(icon_info, &error);
+ if (pixbuf == NULL) {
+ if (error && error->message)
+ g_warning("could not load icon pixbuf: %s\n", error->message);
+ g_clear_error(&error);
+ }
+
+ gtk_icon_info_free(icon_info);
+ g_strfreev(icon_names);
+
+ return pixbuf;
+}
+
+
+static GdkPixbuf *get_file_icon_pixbuf(GFileIcon * icon, int size)
+{
+ GFile *file;
+ char *filename;
+ GdkPixbuf *pixbuf;
+
+ file = g_file_icon_get_file(icon);
+ filename = g_file_get_path(file);
+ pixbuf = gdk_pixbuf_new_from_file_at_size(filename, size, -1, NULL);
+ g_free(filename);
+ g_object_unref(file);
+
+ return pixbuf;
+}
+
+
+static GdkPixbuf *_get_icon_pixbuf(GIcon * icon, int size,
+ GtkIconTheme * theme)
+{
+ if (icon == NULL)
+ return NULL;
+ if (G_IS_THEMED_ICON(icon))
+ return get_themed_icon_pixbuf(G_THEMED_ICON(icon), size, theme);
+ if (G_IS_FILE_ICON(icon))
+ return get_file_icon_pixbuf(G_FILE_ICON(icon), size);
+ return NULL;
+}
+
+
+static GdkPixbuf *icon_cache_get_mime_type_icon(IconCache * icon_cache,
+ const char *mime_type)
+{
+ GIcon *icon;
+ const char *key = NULL;
+ GdkPixbuf *pixbuf;
+
+ icon = g_content_type_get_icon(mime_type);
+ key = _icon_cache_get_icon_key(icon);
+
+ if (key == NULL)
+ key = VOID_PIXBUF_KEY;
+
+ pixbuf = g_hash_table_lookup(icon_cache->cache, key);
+ if (pixbuf != NULL) {
+ g_object_ref(pixbuf);
+ g_object_unref(G_OBJECT(icon));
+ return pixbuf;
+ }
+
+ pixbuf =
+ _get_icon_pixbuf(icon, icon_cache->icon_size,
+ icon_cache->icon_theme);
+ if (pixbuf != NULL)
+ g_hash_table_insert(icon_cache->cache, (gpointer) key,
+ g_object_ref(pixbuf));
+
+ g_object_unref(G_OBJECT(icon));
+
+ return pixbuf;
+}
+
+GdkPixbuf *gtr_get_mime_type_icon(const char *mime_type,
+ GtkIconSize icon_size,
+ GtkWidget * for_widget)
+{
+ int n;
+
+ switch (icon_size) {
+ case GTK_ICON_SIZE_MENU:
+ n = 1;
+ break;
+ case GTK_ICON_SIZE_SMALL_TOOLBAR:
+ n = 2;
+ break;
+ case GTK_ICON_SIZE_LARGE_TOOLBAR:
+ n = 3;
+ break;
+ case GTK_ICON_SIZE_BUTTON:
+ n = 4;
+ break;
+ case GTK_ICON_SIZE_DND:
+ n = 5;
+ break;
+ case GTK_ICON_SIZE_DIALOG:
+ n = 6;
+ break;
+ default /*GTK_ICON_SIZE_INVALID */ :
+ n = 0;
+ break;
+ }
+
+ if (icon_cache[n] == NULL)
+ icon_cache[n] = icon_cache_new(for_widget, icon_size);
+
+ return icon_cache_get_mime_type_icon(icon_cache[n], mime_type);
+}
+
+
+const char *gtr_get_mime_type_from_filename(const char *file G_GNUC_UNUSED)
+{
+ char *tmp = g_content_type_guess(file, NULL, 0, NULL);
+ const char *ret = get_static_string(tmp);
+ g_free(tmp);
+ return ret;
+}
diff --git a/src/icons.h b/src/icons.h
new file mode 100644
index 0000000..1f9088a
--- /dev/null
+++ b/src/icons.h
@@ -0,0 +1,21 @@
+/*
+ * icons.[ch] written by Paolo Bacchilega, who writes:
+ * "There is no problem for me, you can license
+ * my code under whatever licence you wish :)"
+ *
+ * $Id: icons.h 11709 2011-01-19 13:48:47Z jordan $
+ */
+
+#ifndef GTR_ICONS_H
+#define GTR_ICONS_H
+
+#define DIRECTORY_MIME_TYPE "folder"
+#define UNKNOWN_MIME_TYPE "unknown"
+
+const char *gtr_get_mime_type_from_filename(const char *file);
+
+GdkPixbuf *gtr_get_mime_type_icon(const char *mime_type,
+ GtkIconSize icon_size,
+ GtkWidget * for_widget);
+
+#endif
diff --git a/src/installer-gtk2.nsi b/src/installer-gtk2.nsi
new file mode 100644
index 0000000..31e62f5
--- /dev/null
+++ b/src/installer-gtk2.nsi
@@ -0,0 +1,645 @@
+!include "MUI2.nsh"
+;!include "FileAssociation.nsh"
+;!include "ProtocolAssociation.nsh"
+!include "x64.nsh"
+
+;--------------------------------
+
+; The name of the installer
+Name "Transmission Remote GTK"
+
+; The file to write
+!ifndef REV
+OutFile "transmission-remote-gtk-1.1-installer.exe"
+!else
+OutFile "transmission-remote-gtk-${REV}-installer.exe"
+!endif
+
+; The default installation directory
+!define ProgramFilesDir "Transmission Remote GTK"
+
+RequestExecutionLevel admin
+
+;--------------------------------
+
+XPStyle on
+
+Var StartMenuFolder
+
+!define MUI_ICON "transmission_large.ico"
+!define MUI_UNICON "transmission_large.ico"
+!define MUI_HEADERIMAGE
+;!define MUI_HEADERIMAGE_BITMAP "logo.bmp"
+;!define MUI_WELCOMEFINISHPAGE_BITMAP "nsis_wizard.bmp"
+;!define MUI_UNWELCOMEFINISHPAGE_BITMAP "nsis_wizard.bmp"
+;!define MUI_COMPONENTSPAGE_CHECKBITMAP "${NSISDIR}\Contrib\Graphics\Checks\colorful.bmp"
+!define MUI_COMPONENTSPAGE_SMALLDESC
+!define MUI_ABORTWARNING
+
+;!define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\README.txt"
+;!define MUI_FINISHPAGE_SHOWREADME_TEXT "Show ReadMe"
+;!define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED
+
+; Pages
+!insertmacro MUI_PAGE_WELCOME
+!insertmacro MUI_PAGE_LICENSE "..\COPYING"
+!insertmacro MUI_PAGE_COMPONENTS
+!insertmacro MUI_PAGE_DIRECTORY
+!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder
+!insertmacro MUI_PAGE_INSTFILES
+!insertmacro MUI_PAGE_FINISH
+
+!insertmacro MUI_UNPAGE_WELCOME
+!insertmacro MUI_UNPAGE_CONFIRM
+!insertmacro MUI_UNPAGE_INSTFILES
+!insertmacro MUI_UNPAGE_FINISH
+
+;--------------------------------
+
+!insertmacro MUI_LANGUAGE "English"
+!insertmacro MUI_RESERVEFILE_LANGDLL
+
+; English
+LangString NAME_SecTransmissionRemoteGTK ${LANG_ENGLISH} "Transmission Remote GTK (required)"
+LangString DESC_SecTransmissionRemoteGTK ${LANG_ENGLISH} "The application."
+LangString NAME_GeoIP ${LANG_ENGLISH} "GeoIP database"
+LangString DESC_GeoIP ${LANG_ENGLISH} "Shows the country of origin for a peer"
+LangString NAME_SecGlibGtkEtc ${LANG_ENGLISH} "Glib, GTK, and other dependencies (recommended)."
+LangString DESC_SecGlibGtkEtc ${LANG_ENGLISH} "If unset, you'll need to install these yourself."
+LangString NAME_SecDesktopIcon ${LANG_ENGLISH} "Create icon on desktop"
+LangString DESC_SecDesktopIcon ${LANG_ENGLISH} "If set, a shortcut for Transmission Remote will be created on the desktop."
+;LangString NAME_SecFiletypeAssociations ${LANG_ENGLISH} "Register Filetype Associations"
+;LangString DESC_SecFiletypeAssociations ${LANG_ENGLISH} "Register Associations to Transmission Remote"
+;LangString NAME_SecRegiterTorrent ${LANG_ENGLISH} "Register .torrent"
+;LangString DESC_SecRegiterTorrent ${LANG_ENGLISH} "Register .torrent to Transmission Remote"
+;LangString NAME_SecRegiterMagnet ${LANG_ENGLISH} "Register Magnet URI"
+;LangString DESC_SecRegiterMagnet ${LANG_ENGLISH} "Register Magnet URI to Transmission Remote"
+;LangString DESC_SecGeoIPDatabase ${LANG_ENGLISH} "GeoIP database"
+;LangString NAME_SecLanguages ${LANG_ENGLISH} "Languages"
+;LangString DESC_SecLanguages ${LANG_ENGLISH} "Languages for Transmission Remote"
+
+;--------------------------------
+
+; The stuff to install
+Section $(NAME_SecTransmissionRemoteGTK) SecTransmissionRemoteGTK
+ SectionIn RO
+
+ SetOutPath $INSTDIR
+
+ File /oname=README.TXT "..\README"
+ File /oname=COPYING.TXT "..\COPYING"
+ File /oname=ChangeLog.TXT "..\ChangeLog"
+ File /oname=AUTHORS.TXT "..\AUTHORS"
+
+ ; Set output path to the installation directory.
+ SetOutPath $INSTDIR\bin
+
+ ; Put file there
+ File ".libs\transmission-remote-gtk.exe"
+
+ SetOutPath $INSTDIR\share\icons\hicolor\scalable\apps
+
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\scalable\apps\transmission-remote-gtk.svg"
+
+ SetOutPath $INSTDIR\share\icons\hicolor\48x48\apps
+
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\48x48\apps\transmission-remote-gtk.png"
+
+ SetOutPath $INSTDIR\share\icons\hicolor\32x32\apps
+
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\32x32\apps\transmission-remote-gtk.png"
+
+ SetOutPath $INSTDIR\share\icons\hicolor\24x24\apps
+
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\24x24\apps\transmission-remote-gtk.png"
+
+ SetOutPath $INSTDIR\share\icons\hicolor\22x22\apps
+
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\22x22\apps\transmission-remote-gtk.png"
+
+ SetOutPath $INSTDIR\share\icons\hicolor\16x16\apps
+
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\16x16\apps\transmission-remote-gtk.png"
+
+ !ifndef PORTABLE
+ ; Write the installation path into the registry
+ WriteRegStr HKLM "SOFTWARE\TransmissionRemoteGTK" "Install_Dir" "$INSTDIR"
+
+ ; Write the uninstall keys for Windows
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TransmissionRemoteGTK" "DisplayName" "Transmission Remote GTK"
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TransmissionRemoteGTK" "Publisher" "Alan Fitton"
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TransmissionRemoteGTK" "UninstallString" '"$INSTDIR\uninstall.exe"'
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TransmissionRemoteGTK" "DisplayIcon" "$INSTDIR\bin\transmission-remote-gtk.exe,0"
+ WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TransmissionRemoteGTK" "NoModify" 1
+ WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TransmissionRemoteGTK" "NoRepair" 1
+ WriteUninstaller "uninstall.exe"
+!endif
+ !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
+ SetShellVarContext current
+ CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
+ CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Transmission Remote GTK.lnk" "$INSTDIR\bin\transmission-remote-gtk.exe" "" "$INSTDIR\bin\transmission-remote-gtk.exe" 0
+!ifndef PORTABLE
+ SetShellVarContext all
+ CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
+ CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0
+ CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Transmission Remote GTK.lnk" "$INSTDIR\bin\transmission-remote-gtk.exe" "" "$INSTDIR\bin\transmission-remote-gtk.exe" 0
+!endif
+ !insertmacro MUI_STARTMENU_WRITE_END
+
+SectionEnd
+
+Section $(NAME_GeoIP) SecGeoIP
+ SetOutPath $INSTDIR
+
+ File "..\GeoIP.dat"
+ File "..\GeoIPv6.dat"
+SectionEnd
+
+Section $(NAME_SecGlibGtkEtc) SecGlibGtkEtc
+ SetOutPath $INSTDIR\bin
+
+ File "C:\MinGW\msys\1.0\bin\libgtk-win32-2.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libffi-5.dll"
+ File "C:\MinGW\msys\1.0\bin\libcairo-gobject-2.dll"
+ File "C:\MinGW\msys\1.0\bin\libcrypto-8.dll"
+ File "C:\MinGW\msys\1.0\bin\libssl-8.dll"
+ File "C:\MinGW\msys\1.0\bin\freetype6.dll"
+ File "C:\MinGW\msys\1.0\bin\libatk-1.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libcairo-2.dll"
+ File "C:\MinGW\msys\1.0\bin\libcurl-4.dll"
+ File "C:\MinGW\msys\1.0\bin\libexpat-1.dll"
+ File "C:\MinGW\msys\1.0\bin\libfontconfig-1.dll"
+ File "C:\MinGW\msys\1.0\bin\libgdk-win32-2.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libgdk_pixbuf-2.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libgio-2.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libglib-2.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libgmodule-2.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libgobject-2.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libgthread-2.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libjson-glib-1.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libpango-1.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libpangocairo-1.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libpangoft2-1.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libpangowin32-1.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libpng14-14.dll"
+ File "C:\MinGW\msys\1.0\bin\zlib1.dll"
+ File "C:\MinGW\msys\1.0\bin\libintl-8.dll"
+ File "C:\MinGW\msys\1.0\bin\intl.dll"
+ File "C:\MinGW\msys\1.0\bin\libiconv-2.dll"
+ File "C:\MinGW\msys\1.0\bin\gspawn-win32-helper-console.exe"
+ File "C:\MinGW\msys\1.0\bin\gspawn-win32-helper.exe"
+ File "C:\MinGW\msys\1.0\bin\libproxy.dll"
+ File "C:\MinGW\msys\1.0\bin\libmodman.dll"
+ File "C:\MinGW\msys\1.0\bin\libstdc++-6.dll"
+ File "C:\MinGW\msys\1.0\bin\libgcc_s_sjlj-1.dll"
+ File "C:\MinGW\msys\1.0\bin\libgcc_s_dw2-1.dll"
+
+ SetOutPath $INSTDIR\lib\gtk-2.0\2.10.0\engines
+
+ File "C:\MinGW\msys\1.0\lib\gtk-2.0\2.10.0\engines\libpixmap.dll"
+ File "C:\MinGW\msys\1.0\lib\gtk-2.0\2.10.0\engines\libwimp.dll"
+
+ SetOutPath $INSTDIR\lib\gtk-2.0\modules
+
+ File "C:\MinGW\msys\1.0\lib\gtk-2.0\modules\libgail.dll"
+
+ SetOutPath $INSTDIR\etc\gtk-2.0
+
+ File "C:\MinGW\msys\1.0\etc\gtk-2.0\gtkrc"
+
+ SetOutPath $INSTDIR\share\themes\MS-Windows\gtk-2.0
+
+ File "C:\MinGW\msys\1.0\share\themes\MS-Windows\gtk-2.0\gtkrc"
+
+ SetOutPath $INSTDIR\share\locale\ca\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\ca\LC_MESSAGES\*.mo"
+ File /x gcc.mo /x cpplib.mo "C:\MinGW\share\locale\ca\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\ca\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\locale\cs\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\cs\LC_MESSAGES\*.mo"
+ File /x gcc.mo /x cpplib.mo "C:\MinGW\share\locale\cs\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\cs\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\locale\de\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\de\LC_MESSAGES\*.mo"
+ File /x gcc.mo /x cpplib.mo "C:\MinGW\share\locale\de\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\de\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\locale\es\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\es\LC_MESSAGES\*.mo"
+ File /x gcc.mo /x cpplib.mo "C:\MinGW\share\locale\es\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\es\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\locale\fr\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\fr\LC_MESSAGES\*.mo"
+ File /x gcc.mo /x cpplib.mo "C:\MinGW\share\locale\fr\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\fr\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\locale\it\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\it\LC_MESSAGES\*.mo"
+ File /x gcc.mo /x cpplib.mo "C:\MinGW\share\locale\it\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\it\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\locale\ko\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\ko\LC_MESSAGES\*.mo"
+ File /x gcc.mo /x cpplib.mo "C:\MinGW\share\locale\ko\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\ko\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\locale\lt\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\lt\LC_MESSAGES\*.mo"
+ #File "C:\MinGW\share\locale\lt\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\lt\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\locale\pl\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\pl\LC_MESSAGES\*.mo"
+ File /x gcc.mo /x cpplib.mo "C:\MinGW\share\locale\pl\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\pl\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\locale\pt_BR\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\pt_BR\LC_MESSAGES\*.mo"
+ File /x gcc.mo /x cpplib.mo "C:\MinGW\share\locale\pt_BR\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\pt_BR\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\locale\ru\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\ru\LC_MESSAGES\*.mo"
+ File /x gcc.mo /x cpplib.mo "C:\MinGW\share\locale\ru\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\ru\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\locale\sv\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\sv\LC_MESSAGES\*.mo"
+ File /x gcc.mo /x cpplib.mo "C:\MinGW\share\locale\sv\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\sv\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\locale\tr\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\tr\LC_MESSAGES\*.mo"
+ File /x gcc.mo /x cpplib.mo "C:\MinGW\share\locale\tr\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\tr\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\locale\uk\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\uk\LC_MESSAGES\*.mo"
+ File /x gcc.mo /x cpplib.mo "C:\MinGW\share\locale\uk\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\uk\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\locale\zh_TW\LC_MESSAGES
+ File /x dos2unix.mo "C:\MinGW\msys\1.0\share\locale\zh_TW\LC_MESSAGES\*.mo"
+ File /x gcc.mo /x cpplib.mo "C:\MinGW\share\locale\zh_TW\LC_MESSAGES\*.mo"
+ File "C:\MinGW\msys\1.0\lib\locale\zh_TW\LC_MESSAGES\*.mo"
+
+ SetOutPath $INSTDIR\share\icons\hicolor
+
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\index.theme"
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\icon-theme.cache"
+
+SectionEnd
+
+; Optional section (can be disabled by the user)
+
+Section /o $(NAME_SecDesktopIcon) SecDesktopIcon
+ SetShellVarContext current
+ SetOutPath "$INSTDIR\bin"
+ CreateShortCut "$DESKTOP\Transmission Remote GTK.lnk" "$INSTDIR\bin\transmission-remote-gtk.exe" "" "$INSTDIR\bin\transmission-remote-gtk.exe" 0
+SectionEnd
+
+;!ifndef PORTABLE
+;SubSection $(NAME_SecFiletypeAssociations) SecFiletypeAssociations
+
+; Section $(NAME_SecRegiterTorrent) SecRegiterTorrent
+; ${registerExtension} "$INSTDIR\Transmission Remote.exe" ".torrent" "Transmission Remote Torrent"
+; SectionEnd
+
+; Section $(NAME_SecRegiterMagnet) SecRegiterMagnet
+; ${registerProtocol} "$INSTDIR\Transmission Remote.exe" "magnet" "Magnet URI"
+; SectionEnd
+
+;SubSectionEnd
+;!endif
+
+; Translation
+
+;SectionGroup $(NAME_SecLanguages) SecLanguages
+
+; Section /o "Brazilian Portuguese" SecLanguagesBrazilianPortuguese
+; CreateDirectory "$INSTDIR\pt-BR"
+; SetOutPath "$INSTDIR\pt-BR"
+; File "TransmissionClientNew\bin\Release\pt-BR\Transmission Remote.resources.dll"
+; SectionEnd
+
+ ; Section /o "Chinese" SecLanguagesChinese
+ ; CreateDirectory "$INSTDIR\zh-CN"
+ ; SetOutPath "$INSTDIR\zh-CN"
+ ; File "TransmissionClientNew\bin\Release\zh-CN\Transmission Remote.resources.dll"
+ ; SectionEnd
+
+;SectionGroupEnd
+
+;--------------------------------
+
+; Uninstaller
+
+Section "Uninstall"
+
+;!ifndef PORTABLE
+ ; Unregister File Association
+; ${unregisterExtension} ".torrent" "Transmission Remote Torrent"
+; ${unregisterProtocol} "magnet" "Magnet URI"
+
+;!endif
+
+ ; Remove files and uninstaller
+ Delete "$INSTDIR\AUTHORS.TXT"
+ Delete "$INSTDIR\bin\freetype6.dll"
+ Delete "$INSTDIR\bin\gspawn-win32-helper-console.exe"
+ Delete "$INSTDIR\bin\gspawn-win32-helper.exe"
+ Delete "$INSTDIR\bin\intl.dll"
+ Delete "$INSTDIR\bin\libatk-1.0-0.dll"
+ Delete "$INSTDIR\bin\libcairo-2.dll"
+ Delete "$INSTDIR\bin\libcairo-gobject-2.dll"
+ Delete "$INSTDIR\bin\libcrypto-8.dll"
+ Delete "$INSTDIR\bin\libcurl-4.dll"
+ Delete "$INSTDIR\bin\libexpat-1.dll"
+ Delete "$INSTDIR\bin\libffi-5.dll"
+ Delete "$INSTDIR\bin\libfontconfig-1.dll"
+ Delete "$INSTDIR\bin\libgcc_s_dw2-1.dll"
+ Delete "$INSTDIR\bin\libgcc_s_sjlj-1.dll"
+ Delete "$INSTDIR\bin\libgdk-win32-2.0-0.dll"
+ Delete "$INSTDIR\bin\libgdk_pixbuf-2.0-0.dll"
+ Delete "$INSTDIR\bin\libgio-2.0-0.dll"
+ Delete "$INSTDIR\bin\libglib-2.0-0.dll"
+ Delete "$INSTDIR\bin\libgmodule-2.0-0.dll"
+ Delete "$INSTDIR\bin\libgobject-2.0-0.dll"
+ Delete "$INSTDIR\bin\libgthread-2.0-0.dll"
+ Delete "$INSTDIR\bin\libgtk-win32-2.0-0.dll"
+ Delete "$INSTDIR\bin\libiconv-2.dll"
+ Delete "$INSTDIR\bin\libintl-8.dll"
+ Delete "$INSTDIR\bin\libjson-glib-1.0-0.dll"
+ Delete "$INSTDIR\bin\libmodman.dll"
+ Delete "$INSTDIR\bin\libpango-1.0-0.dll"
+ Delete "$INSTDIR\bin\libpangocairo-1.0-0.dll"
+ Delete "$INSTDIR\bin\libpangoft2-1.0-0.dll"
+ Delete "$INSTDIR\bin\libpangowin32-1.0-0.dll"
+ Delete "$INSTDIR\bin\libpng14-14.dll"
+ Delete "$INSTDIR\bin\libproxy.dll"
+ Delete "$INSTDIR\bin\libssl-8.dll"
+ Delete "$INSTDIR\bin\libstdc++-6.dll"
+ Delete "$INSTDIR\bin\transmission-remote-gtk.exe"
+ Delete "$INSTDIR\bin\zlib1.dll"
+ Delete "$INSTDIR\ChangeLog.TXT"
+ Delete "$INSTDIR\COPYING.TXT"
+ Delete "$INSTDIR\etc\gtk-2.0\gtkrc"
+ Delete "$INSTDIR\GeoIP.dat"
+ Delete "$INSTDIR\GeoIPv6.dat"
+ Delete "$INSTDIR\lib\gtk-2.0\2.10.0\engines\libpixmap.dll"
+ Delete "$INSTDIR\lib\gtk-2.0\2.10.0\engines\libwimp.dll"
+ Delete "$INSTDIR\lib\gtk-2.0\modules\libgail.dll"
+ Delete "$INSTDIR\README.TXT"
+ Delete "$INSTDIR\share\icons\hicolor\16x16\apps\transmission-remote-gtk.png"
+ Delete "$INSTDIR\share\icons\hicolor\22x22\apps\transmission-remote-gtk.png"
+ Delete "$INSTDIR\share\icons\hicolor\24x24\apps\transmission-remote-gtk.png"
+ Delete "$INSTDIR\share\icons\hicolor\32x32\apps\transmission-remote-gtk.png"
+ Delete "$INSTDIR\share\icons\hicolor\48x48\apps\transmission-remote-gtk.png"
+ Delete "$INSTDIR\share\icons\hicolor\icon-theme.cache"
+ Delete "$INSTDIR\share\icons\hicolor\index.theme"
+ Delete "$INSTDIR\share\icons\hicolor\scalable\apps\transmission-remote-gtk.svg"
+ Delete "$INSTDIR\share\locale\ca\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\ca\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\ca\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\ca\LC_MESSAGES\gettext-tools.mo"
+ Delete "$INSTDIR\share\locale\ca\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\ca\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\ca\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\ca\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\ca\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\locale\cs\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\cs\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\cs\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\cs\LC_MESSAGES\gettext-tools.mo"
+ Delete "$INSTDIR\share\locale\cs\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\cs\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\cs\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\cs\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\cs\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\locale\de\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\de\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\de\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\de\LC_MESSAGES\gettext-tools.mo"
+ Delete "$INSTDIR\share\locale\de\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\de\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\de\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\de\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\de\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\locale\es\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\es\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\es\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\es\LC_MESSAGES\gettext-tools.mo"
+ Delete "$INSTDIR\share\locale\es\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\es\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\es\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\es\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\es\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\gettext-tools.mo"
+ Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\locale\it\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\it\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\it\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\it\LC_MESSAGES\gettext-tools.mo"
+ Delete "$INSTDIR\share\locale\it\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\it\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\it\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\it\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\it\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\gettext-tools.mo"
+ Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\locale\lt\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\lt\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\lt\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\lt\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\lt\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\lt\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\gettext-tools.mo"
+ Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\locale\pt_BR\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\pt_BR\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\pt_BR\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\pt_BR\LC_MESSAGES\gettext-tools.mo"
+ Delete "$INSTDIR\share\locale\pt_BR\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\pt_BR\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\pt_BR\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\pt_BR\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\pt_BR\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\gettext-tools.mo"
+ Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\locale\sv\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\sv\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\sv\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\sv\LC_MESSAGES\gettext-tools.mo"
+ Delete "$INSTDIR\share\locale\sv\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\sv\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\sv\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\sv\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\sv\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\locale\tr\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\tr\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\tr\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\tr\LC_MESSAGES\gettext-tools.mo"
+ Delete "$INSTDIR\share\locale\tr\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\tr\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\tr\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\tr\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\tr\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\gettext-tools.mo"
+ Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\locale\zh_TW\LC_MESSAGES\atk10.mo"
+ Delete "$INSTDIR\share\locale\zh_TW\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\zh_TW\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\zh_TW\LC_MESSAGES\gettext-tools.mo"
+ Delete "$INSTDIR\share\locale\zh_TW\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\zh_TW\LC_MESSAGES\gtk20-properties.mo"
+ Delete "$INSTDIR\share\locale\zh_TW\LC_MESSAGES\gtk20.mo"
+ Delete "$INSTDIR\share\locale\zh_TW\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\zh_TW\LC_MESSAGES\transmission-remote-gtk.mo"
+ Delete "$INSTDIR\share\themes\MS-Windows\gtk-2.0\gtkrc"
+ Delete "$INSTDIR\uninstall.exe"
+
+ ; Remove shortcuts, if any
+ !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder
+ SetShellVarContext current
+ Delete "$SMPROGRAMS\$StartMenuFolder\*.*"
+ RMDir "$SMPROGRAMS\$StartMenuFolder"
+!ifndef PORTABLE
+ SetShellVarContext all
+ Delete "$SMPROGRAMS\$StartMenuFolder\*.*"
+ RMDir "$SMPROGRAMS\$StartMenuFolder"
+!endif
+
+ ; Remove directories used
+ RMDir "$INSTDIR\share\themes\MS-Windows\gtk-2.0"
+ RMDir "$INSTDIR\share\themes\MS-Windows"
+ RMDir "$INSTDIR\share\themes"
+ RMDir "$INSTDIR\share\locale\zh_TW\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\zh_TW"
+ RMDir "$INSTDIR\share\locale\uk\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\uk"
+ RMDir "$INSTDIR\share\locale\tr\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\tr"
+ RMDir "$INSTDIR\share\locale\sv\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\sv"
+ RMDir "$INSTDIR\share\locale\ru\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\ru"
+ RMDir "$INSTDIR\share\locale\pt_BR\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\pt_BR"
+ RMDir "$INSTDIR\share\locale\pl\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\pl"
+ RMDir "$INSTDIR\share\locale\lt\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\lt"
+ RMDir "$INSTDIR\share\locale\ko\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\ko"
+ RMDir "$INSTDIR\share\locale\it\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\it"
+ RMDir "$INSTDIR\share\locale\fr\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\fr"
+ RMDir "$INSTDIR\share\locale\es\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\es"
+ RMDir "$INSTDIR\share\locale\de\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\de"
+ RMDir "$INSTDIR\share\locale\cs\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\cs"
+ RMDir "$INSTDIR\share\locale\ca\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\ca"
+ RMDir "$INSTDIR\share\locale"
+ RMDir "$INSTDIR\share\icons\hicolor\scalable\apps"
+ RMDir "$INSTDIR\share\icons\hicolor\scalable"
+ RMDir "$INSTDIR\share\icons\hicolor\48x48\apps"
+ RMDir "$INSTDIR\share\icons\hicolor\48x48"
+ RMDir "$INSTDIR\share\icons\hicolor\32x32\apps"
+ RMDir "$INSTDIR\share\icons\hicolor\32x32"
+ RMDir "$INSTDIR\share\icons\hicolor\24x24\apps"
+ RMDir "$INSTDIR\share\icons\hicolor\24x24"
+ RMDir "$INSTDIR\share\icons\hicolor\22x22\apps"
+ RMDir "$INSTDIR\share\icons\hicolor\22x22"
+ RMDir "$INSTDIR\share\icons\hicolor\16x16\apps"
+ RMDir "$INSTDIR\share\icons\hicolor\16x16"
+ RMDir "$INSTDIR\share\icons\hicolor"
+ RMDir "$INSTDIR\share\icons"
+ RMDir "$INSTDIR\share"
+ RMDir "$INSTDIR\lib\gtk-2.0\modules"
+ RMDir "$INSTDIR\lib\gtk-2.0\2.10.0\engines"
+ RMDir "$INSTDIR\lib\gtk-2.0\2.10.0"
+ RMDir "$INSTDIR\lib\gtk-2.0"
+ RMDir "$INSTDIR\lib"
+ RMDir "$INSTDIR\etc\gtk-2.0"
+ RMDir "$INSTDIR\etc"
+ RMDir "$INSTDIR\bin"
+ RMDir "$INSTDIR"
+
+ DeleteRegKey /ifempty HKCU "SOFTWARE\TransmissionRemoteGTK"
+!ifndef PORTABLE
+ DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TransmissionRemoteGTK"
+!endif
+
+SectionEnd
+
+!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
+ !insertmacro MUI_DESCRIPTION_TEXT ${SecTransmissionRemoteGTK} $(DESC_SecTransmissionRemoteGTK)
+ !insertmacro MUI_DESCRIPTION_TEXT ${SecGeoIP} $(DESC_GeoIP)
+ !insertmacro MUI_DESCRIPTION_TEXT ${SecGlibGtkEtc} $(DESC_SecGlibGtkEtc)
+ !insertmacro MUI_DESCRIPTION_TEXT ${SecDesktopIcon} $(DESC_SecDesktopIcon)
+; !insertmacro MUI_DESCRIPTION_TEXT ${SecFiletypeAssociations} $(DESC_SecFiletypeAssociations)
+; !insertmacro MUI_DESCRIPTION_TEXT ${SecGeoIPDatabase} $(DESC_SecGeoIPDatabase)
+; !insertmacro MUI_DESCRIPTION_TEXT ${SecLanguages} $(DESC_SecLanguages)
+; !insertmacro MUI_DESCRIPTION_TEXT ${SecRegiterTorrent} $(DESC_SecRegiterTorrent)
+; !insertmacro MUI_DESCRIPTION_TEXT ${SecRegiterMagnet} $(DESC_SecRegiterMagnet)
+!insertmacro MUI_FUNCTION_DESCRIPTION_END
+
+Function .onInit
+ System::Call 'kernel32::CreateMutexA(i 0, i 0, t "Transmission Remote GTK") ?e'
+ Pop $R0
+ StrCmp $R0 0 +3
+ MessageBox MB_OK|MB_ICONEXCLAMATION "The installer is already running."
+ Abort
+
+ !insertmacro MUI_LANGDLL_DISPLAY
+!ifdef PORTABLE
+ StrCpy $INSTDIR "\${ProgramFilesDir}"
+!else
+ ${If} ${RunningX64}
+ StrCpy $INSTDIR "$PROGRAMFILES64\${ProgramFilesDir}"
+ ${Else}
+ StrCpy $INSTDIR "$PROGRAMFILES\${ProgramFilesDir}"
+ ${Endif}
+!endif
+FunctionEnd
diff --git a/src/installer.nsi b/src/installer.nsi
new file mode 100644
index 0000000..2b9367b
--- /dev/null
+++ b/src/installer.nsi
@@ -0,0 +1,628 @@
+!include "MUI2.nsh"
+;!include "FileAssociation.nsh"
+;!include "ProtocolAssociation.nsh"
+!include "x64.nsh"
+
+;--------------------------------
+
+; The name of the installer
+Name "Transmission Remote GTK"
+
+; The file to write
+!ifndef REV
+OutFile "transmission-remote-gtk-1.0.2-installer.exe"
+!else
+OutFile "transmission-remote-gtk-${REV}-installer.exe"
+!endif
+
+; The default installation directory
+!define ProgramFilesDir "Transmission Remote GTK"
+
+RequestExecutionLevel admin
+
+;--------------------------------
+
+XPStyle on
+
+Var StartMenuFolder
+
+!define MUI_ICON "transmission_large.ico"
+!define MUI_UNICON "transmission_large.ico"
+!define MUI_HEADERIMAGE
+;!define MUI_HEADERIMAGE_BITMAP "logo.bmp"
+;!define MUI_WELCOMEFINISHPAGE_BITMAP "nsis_wizard.bmp"
+;!define MUI_UNWELCOMEFINISHPAGE_BITMAP "nsis_wizard.bmp"
+;!define MUI_COMPONENTSPAGE_CHECKBITMAP "${NSISDIR}\Contrib\Graphics\Checks\colorful.bmp"
+!define MUI_COMPONENTSPAGE_SMALLDESC
+!define MUI_ABORTWARNING
+
+;!define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\README.txt"
+;!define MUI_FINISHPAGE_SHOWREADME_TEXT "Show ReadMe"
+;!define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED
+
+; Pages
+!insertmacro MUI_PAGE_WELCOME
+!insertmacro MUI_PAGE_LICENSE "..\COPYING"
+!insertmacro MUI_PAGE_COMPONENTS
+!insertmacro MUI_PAGE_DIRECTORY
+!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder
+!insertmacro MUI_PAGE_INSTFILES
+!insertmacro MUI_PAGE_FINISH
+
+!insertmacro MUI_UNPAGE_WELCOME
+!insertmacro MUI_UNPAGE_CONFIRM
+!insertmacro MUI_UNPAGE_INSTFILES
+!insertmacro MUI_UNPAGE_FINISH
+
+;--------------------------------
+
+!insertmacro MUI_LANGUAGE "English"
+!insertmacro MUI_RESERVEFILE_LANGDLL
+
+; English
+LangString NAME_SecTransmissionRemoteGTK ${LANG_ENGLISH} "Transmission Remote GTK (required)"
+LangString DESC_SecTransmissionRemoteGTK ${LANG_ENGLISH} "The application."
+LangString NAME_GeoIP ${LANG_ENGLISH} "GeoIP database"
+LangString DESC_GeoIP ${LANG_ENGLISH} "Shows the country of origin for a peer"
+LangString NAME_SecGlibGtkEtc ${LANG_ENGLISH} "Glib, GTK, and other dependencies (recommended)."
+LangString DESC_SecGlibGtkEtc ${LANG_ENGLISH} "If unset, you'll need to install these yourself."
+LangString NAME_SecDesktopIcon ${LANG_ENGLISH} "Create icon on desktop"
+LangString DESC_SecDesktopIcon ${LANG_ENGLISH} "If set, a shortcut for Transmission Remote will be created on the desktop."
+;LangString NAME_SecFiletypeAssociations ${LANG_ENGLISH} "Register Filetype Associations"
+;LangString DESC_SecFiletypeAssociations ${LANG_ENGLISH} "Register Associations to Transmission Remote"
+;LangString NAME_SecRegiterTorrent ${LANG_ENGLISH} "Register .torrent"
+;LangString DESC_SecRegiterTorrent ${LANG_ENGLISH} "Register .torrent to Transmission Remote"
+;LangString NAME_SecRegiterMagnet ${LANG_ENGLISH} "Register Magnet URI"
+;LangString DESC_SecRegiterMagnet ${LANG_ENGLISH} "Register Magnet URI to Transmission Remote"
+;LangString DESC_SecGeoIPDatabase ${LANG_ENGLISH} "GeoIP database"
+;LangString NAME_SecLanguages ${LANG_ENGLISH} "Languages"
+;LangString DESC_SecLanguages ${LANG_ENGLISH} "Languages for Transmission Remote"
+
+;--------------------------------
+
+; The stuff to install
+Section $(NAME_SecTransmissionRemoteGTK) SecTransmissionRemoteGTK
+ SectionIn RO
+
+ SetOutPath $INSTDIR
+
+ File /oname=README.TXT "..\README"
+ File /oname=COPYING.TXT "..\COPYING"
+ File /oname=ChangeLog.TXT "..\ChangeLog"
+ File /oname=AUTHORS.TXT "..\AUTHORS"
+
+ ; Set output path to the installation directory.
+ SetOutPath $INSTDIR\bin
+
+ ; Put file there
+ File ".libs\transmission-remote-gtk.exe"
+
+ #SetOutPath $INSTDIR\etc\gtk-2.0
+
+ #File "C:\MinGW\msys\1.0\etc\gtk-2.0\gtkrc"
+
+ #SetOutPath $INSTDIR\share\themes\MS-Windows\gtk-2.0
+
+ #File "C:\MinGW\msys\1.0\share\themes\MS-Windows\gtk-2.0\gtkrc"
+
+ SetOutPath $INSTDIR\share\locale\uk\LC_MESSAGES
+
+ File "C:\MinGW\msys\1.0\lib\locale\uk\LC_MESSAGES\transmission-remote-gtk.mo"
+
+ SetOutPath $INSTDIR\share\locale\fr\LC_MESSAGES
+
+ File "C:\MinGW\msys\1.0\lib\locale\fr\LC_MESSAGES\transmission-remote-gtk.mo"
+
+ SetOutPath $INSTDIR\share\locale\ru\LC_MESSAGES
+
+ File "C:\MinGW\msys\1.0\lib\locale\ru\LC_MESSAGES\transmission-remote-gtk.mo"
+
+ SetOutPath $INSTDIR\share\locale\pl\LC_MESSAGES
+
+ File "C:\MinGW\msys\1.0\lib\locale\pl\LC_MESSAGES\transmission-remote-gtk.mo"
+
+ SetOutPath $INSTDIR\share\locale\ko\LC_MESSAGES
+
+ File "C:\MinGW\msys\1.0\lib\locale\ko\LC_MESSAGES\transmission-remote-gtk.mo"
+
+ SetOutPath $INSTDIR\share\locale\es\LC_MESSAGES
+
+ File "C:\MinGW\msys\1.0\lib\locale\es\LC_MESSAGES\transmission-remote-gtk.mo"
+
+ SetOutPath $INSTDIR\share\locale\de\LC_MESSAGES
+
+ File "C:\MinGW\msys\1.0\lib\locale\de\LC_MESSAGES\transmission-remote-gtk.mo"
+
+ SetOutPath $INSTDIR\share\locale\lt\LC_MESSAGES
+
+ File "C:\MinGW\msys\1.0\lib\locale\lt\LC_MESSAGES\transmission-remote-gtk.mo"
+
+ SetOutPath $INSTDIR\share\icons\hicolor\scalable\apps
+
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\scalable\apps\transmission-remote-gtk.svg"
+
+ SetOutPath $INSTDIR\share\icons\hicolor\48x48\apps
+
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\48x48\apps\transmission-remote-gtk.png"
+
+ SetOutPath $INSTDIR\share\icons\hicolor\32x32\apps
+
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\32x32\apps\transmission-remote-gtk.png"
+
+ SetOutPath $INSTDIR\share\icons\hicolor\24x24\apps
+
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\24x24\apps\transmission-remote-gtk.png"
+
+ SetOutPath $INSTDIR\share\icons\hicolor\22x22\apps
+
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\22x22\apps\transmission-remote-gtk.png"
+
+ SetOutPath $INSTDIR\share\icons\hicolor\16x16\apps
+
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\16x16\apps\transmission-remote-gtk.png"
+
+ !ifndef PORTABLE
+ ; Write the installation path into the registry
+ WriteRegStr HKLM "SOFTWARE\TransmissionRemoteGTK" "Install_Dir" "$INSTDIR"
+
+ ; Write the uninstall keys for Windows
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TransmissionRemoteGTK" "DisplayName" "Transmission Remote GTK"
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TransmissionRemoteGTK" "Publisher" "Alan Fitton"
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TransmissionRemoteGTK" "UninstallString" '"$INSTDIR\uninstall.exe"'
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TransmissionRemoteGTK" "DisplayIcon" "$INSTDIR\bin\transmission-remote-gtk.exe,0"
+ WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TransmissionRemoteGTK" "NoModify" 1
+ WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TransmissionRemoteGTK" "NoRepair" 1
+ WriteUninstaller "uninstall.exe"
+!endif
+ !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
+ SetShellVarContext current
+ CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
+ CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Transmission Remote GTK.lnk" "$INSTDIR\bin\transmission-remote-gtk.exe" "" "$INSTDIR\bin\transmission-remote-gtk.exe" 0
+!ifndef PORTABLE
+ SetShellVarContext all
+ CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
+ CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0
+ CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Transmission Remote GTK.lnk" "$INSTDIR\bin\transmission-remote-gtk.exe" "" "$INSTDIR\bin\transmission-remote-gtk.exe" 0
+!endif
+ !insertmacro MUI_STARTMENU_WRITE_END
+
+SectionEnd
+
+Section $(NAME_GeoIP) SecGeoIP
+ SetOutPath $INSTDIR
+
+ File "..\GeoIP.dat"
+ File "..\GeoIPv6.dat"
+SectionEnd
+
+Section $(NAME_SecGlibGtkEtc) SecGlibGtkEtc
+ SetOutPath $INSTDIR\bin
+
+ File "C:\MinGW\msys\1.0\bin\libgtk-3-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libffi-5.dll"
+ File "C:\MinGW\msys\1.0\bin\libcairo-gobject-2.dll"
+ File "C:\MinGW\msys\1.0\bin\libeay32.dll"
+ File "C:\MinGW\msys\1.0\bin\libssl32.dll"
+ File "C:\MinGW\msys\1.0\bin\freetype6.dll"
+ File "C:\MinGW\msys\1.0\bin\libatk-1.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libcairo-2.dll"
+ File "C:\MinGW\msys\1.0\bin\libcurl.dll"
+ File "C:\MinGW\msys\1.0\bin\libexpat-1.dll"
+ File "C:\MinGW\msys\1.0\bin\libfontconfig-1.dll"
+ File "C:\MinGW\msys\1.0\bin\libgdk-3-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libgdk_pixbuf-2.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libgio-2.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libglib-2.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libgmodule-2.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libgobject-2.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libgthread-2.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libjson-glib-1.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libpango-1.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libpangocairo-1.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libpangoft2-1.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libpangowin32-1.0-0.dll"
+ File "C:\MinGW\msys\1.0\bin\libpng14-14.dll"
+ File "C:\MinGW\msys\1.0\bin\zlib1.dll"
+ File "C:\MinGW\msys\1.0\bin\libintl-8.dll"
+ File "C:\MinGW\msys\1.0\bin\intl.dll"
+ File "C:\MinGW\msys\1.0\bin\libiconv-2.dll"
+ File "C:\MinGW\msys\1.0\bin\gspawn-win32-helper-console.exe"
+ File "C:\MinGW\msys\1.0\bin\gspawn-win32-helper.exe"
+ File "C:\MinGW\msys\1.0\bin\libproxy.dll"
+ File "C:\MinGW\msys\1.0\bin\libmodman.dll"
+ File "C:\MinGW\msys\1.0\bin\libstdc++-6.dll"
+ File "C:\MinGW\msys\1.0\bin\libgcc_s_sjlj-1.dll"
+ File "C:\MinGW\msys\1.0\bin\libgcc_s_dw2-1.dll"
+
+ #SetOutPath $INSTDIR\lib\gtk-2.0\2.10.0\engines
+
+ #File "C:\MinGW\msys\1.0\lib\gtk-2.0\2.10.0\engines\libpixmap.dll"
+ #File "C:\MinGW\msys\1.0\lib\gtk-2.0\2.10.0\engines\libwimp.dll"
+
+ #SetOutPath $INSTDIR\lib\gtk-2.0\modules
+
+ #File "C:\MinGW\msys\1.0\lib\gtk-2.0\modules\libgail.dll"
+
+ #SetOutPath $INSTDIR\share\locale\lt\LC_MESSAGES
+
+ #File "C:\MinGW\share\locale\lt\LC_MESSAGES\gtk30.mo"
+ #File "C:\MinGW\share\locale\lt\LC_MESSAGES\gtk30-properties.mo"
+ #File "C:\MinGW\share\locale\lt\LC_MESSAGES\glib20.mo"
+ #File "C:\MinGW\share\locale\lt\LC_MESSAGES\gdk-pixbuf.mo"
+ #File "C:\MinGW\share\locale\lt\LC_MESSAGES\atk10.mo"
+
+ #SetOutPath $INSTDIR\share\locale\uk\LC_MESSAGES
+
+ #File "C:\MinGW\share\locale\uk\LC_MESSAGES\libiconv.mo"
+ #File "C:\MinGW\share\locale\uk\LC_MESSAGES\gtk30.mo"
+ #File "C:\MinGW\share\locale\uk\LC_MESSAGES\gtk30-properties.mo"
+ #File "C:\MinGW\share\locale\uk\LC_MESSAGES\glib20.mo"
+ #File "C:\MinGW\share\locale\uk\LC_MESSAGES\gettext-tools.mo"
+ #File "C:\MinGW\share\locale\uk\LC_MESSAGES\gettext-runtime.mo"
+ #File "C:\MinGW\share\locale\uk\LC_MESSAGES\gdk-pixbuf.mo"
+ #File "C:\MinGW\share\locale\uk\LC_MESSAGES\atk10.mo"
+
+ SetOutPath $INSTDIR\share\locale\fr\LC_MESSAGES
+
+ File "C:\MinGW\msys\1.0\share\locale\fr\LC_MESSAGES\gtk30.mo"
+ File "C:\MinGW\msys\1.0\share\locale\fr\LC_MESSAGES\gtk30-properties.mo"
+ #File "C:\MinGW\share\locale\fr\LC_MESSAGES\glib20.mo"
+ File "C:\MinGW\share\locale\fr\LC_MESSAGES\gettext-runtime.mo"
+ #File "C:\MinGW\share\locale\fr\LC_MESSAGES\gdk-pixbuf.mo"
+ #File "C:\MinGW\share\locale\fr\LC_MESSAGES\atk10.mo"
+
+ SetOutPath $INSTDIR\share\locale\ru\LC_MESSAGES
+
+ File "C:\MinGW\share\locale\ru\LC_MESSAGES\libiconv.mo"
+ #File "C:\MinGW\share\locale\ru\LC_MESSAGES\gtk30.mo"
+ #File "C:\MinGW\share\locale\ru\LC_MESSAGES\gtk30-properties.mo"
+ #File "C:\MinGW\share\locale\ru\LC_MESSAGES\glib20.mo"
+ File "C:\MinGW\share\locale\ru\LC_MESSAGES\gettext-tools.mo"
+ File "C:\MinGW\share\locale\ru\LC_MESSAGES\gettext-runtime.mo"
+ #File "C:\MinGW\share\locale\ru\LC_MESSAGES\gdk-pixbuf.mo"
+ #File "C:\MinGW\share\locale\ru\LC_MESSAGES\atk10.mo"
+
+ SetOutPath $INSTDIR\share\locale\pl\LC_MESSAGES
+
+ File "C:\MinGW\share\locale\pl\LC_MESSAGES\libiconv.mo"
+ #File "C:\MinGW\share\locale\pl\LC_MESSAGES\gtk30.mo"
+ #File "C:\MinGW\share\locale\pl\LC_MESSAGES\gtk30-properties.mo"
+ #File "C:\MinGW\share\locale\pl\LC_MESSAGES\glib20.mo"
+ File "C:\MinGW\share\locale\pl\LC_MESSAGES\gettext-tools.mo"
+ File "C:\MinGW\share\locale\pl\LC_MESSAGES\gettext-runtime.mo"
+ #File "C:\MinGW\share\locale\pl\LC_MESSAGES\gdk-pixbuf.mo"
+ #File "C:\MinGW\share\locale\pl\LC_MESSAGES\atk10.mo"
+
+ SetOutPath $INSTDIR\share\locale\ko\LC_MESSAGES
+
+ #File "C:\MinGW\share\locale\ko\LC_MESSAGES\gtk30.mo"
+ #File "C:\MinGW\share\locale\ko\LC_MESSAGES\gtk30-properties.mo"
+ #File "C:\MinGW\share\locale\ko\LC_MESSAGES\glib20.mo"
+ File "C:\MinGW\share\locale\ko\LC_MESSAGES\gettext-tools.mo"
+ File "C:\MinGW\share\locale\ko\LC_MESSAGES\gettext-runtime.mo"
+ #File "C:\MinGW\share\locale\ko\LC_MESSAGES\gdk-pixbuf.mo"
+ #File "C:\MinGW\share\locale\ko\LC_MESSAGES\atk10.mo"
+
+ SetOutPath $INSTDIR\share\locale\es\LC_MESSAGES
+
+ File "C:\MinGW\share\locale\es\LC_MESSAGES\libiconv.mo"
+ #File "C:\MinGW\share\locale\es\LC_MESSAGES\gtk30.mo"
+ #File "C:\MinGW\share\locale\es\LC_MESSAGES\gtk30-properties.mo"
+ #File "C:\MinGW\share\locale\es\LC_MESSAGES\glib20.mo"
+ File "C:\MinGW\share\locale\es\LC_MESSAGES\gettext-tools.mo"
+ File "C:\MinGW\share\locale\es\LC_MESSAGES\gettext-runtime.mo"
+ #File "C:\MinGW\share\locale\es\LC_MESSAGES\gdk-pixbuf.mo"
+ #File "C:\MinGW\share\locale\es\LC_MESSAGES\atk10.mo"
+
+ SetOutPath $INSTDIR\share\locale\de\LC_MESSAGES
+
+ File "C:\MinGW\share\locale\de\LC_MESSAGES\libiconv.mo"
+ #File "C:\MinGW\share\locale\de\LC_MESSAGES\gtk30.mo"
+ #File "C:\MinGW\share\locale\de\LC_MESSAGES\gtk30-properties.mo"
+ #File "C:\MinGW\share\locale\de\LC_MESSAGES\glib20.mo"
+ File "C:\MinGW\share\locale\de\LC_MESSAGES\gettext-tools.mo"
+ File "C:\MinGW\share\locale\de\LC_MESSAGES\gettext-runtime.mo"
+ #File "C:\MinGW\share\locale\de\LC_MESSAGES\gdk-pixbuf.mo"
+ #File "C:\MinGW\share\locale\de\LC_MESSAGES\atk10.mo"
+
+ SetOutPath $INSTDIR\share\icons\hicolor
+
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\index.theme"
+ File "C:\MinGW\msys\1.0\share\icons\hicolor\icon-theme.cache"
+
+SectionEnd
+
+; Optional section (can be disabled by the user)
+
+Section /o $(NAME_SecDesktopIcon) SecDesktopIcon
+ SetShellVarContext current
+ SetOutPath "$INSTDIR\bin"
+ CreateShortCut "$DESKTOP\Transmission Remote GTK.lnk" "$INSTDIR\bin\transmission-remote-gtk.exe" "" "$INSTDIR\bin\transmission-remote-gtk.exe" 0
+SectionEnd
+
+;!ifndef PORTABLE
+;SubSection $(NAME_SecFiletypeAssociations) SecFiletypeAssociations
+
+; Section $(NAME_SecRegiterTorrent) SecRegiterTorrent
+; ${registerExtension} "$INSTDIR\Transmission Remote.exe" ".torrent" "Transmission Remote Torrent"
+; SectionEnd
+
+; Section $(NAME_SecRegiterMagnet) SecRegiterMagnet
+; ${registerProtocol} "$INSTDIR\Transmission Remote.exe" "magnet" "Magnet URI"
+; SectionEnd
+
+;SubSectionEnd
+;!endif
+
+; Translation
+
+;SectionGroup $(NAME_SecLanguages) SecLanguages
+
+; Section /o "Brazilian Portuguese" SecLanguagesBrazilianPortuguese
+; CreateDirectory "$INSTDIR\pt-BR"
+; SetOutPath "$INSTDIR\pt-BR"
+; File "TransmissionClientNew\bin\Release\pt-BR\Transmission Remote.resources.dll"
+; SectionEnd
+
+ ; Section /o "Chinese" SecLanguagesChinese
+ ; CreateDirectory "$INSTDIR\zh-CN"
+ ; SetOutPath "$INSTDIR\zh-CN"
+ ; File "TransmissionClientNew\bin\Release\zh-CN\Transmission Remote.resources.dll"
+ ; SectionEnd
+
+;SectionGroupEnd
+
+;--------------------------------
+
+; Uninstaller
+
+Section "Uninstall"
+
+;!ifndef PORTABLE
+ ; Unregister File Association
+; ${unregisterExtension} ".torrent" "Transmission Remote Torrent"
+; ${unregisterProtocol} "magnet" "Magnet URI"
+
+;!endif
+
+ ; Remove files and uninstaller
+ Delete "$INSTDIR\COPYING.txt"
+ Delete "$INSTDIR\README.txt"
+ Delete "$INSTDIR\AUTHORS.txt"
+ Delete "$INSTDIR\ChangeLog.txt"
+ Delete "$INSTDIR\GeoIP.dat"
+ Delete "$INSTDIR\GeoIPv6.dat"
+ Delete "$INSTDIR\uninstall.exe"
+ Delete "$INSTDIR\bin\transmission-remote-gtk.exe"
+ Delete "$INSTDIR\bin\freetype6.dll"
+ Delete "$INSTDIR\bin\intl.dll"
+ Delete "$INSTDIR\bin\libatk-1.0-0.dll"
+ Delete "$INSTDIR\bin\libcairo-2.dll"
+ Delete "$INSTDIR\bin\libcurl.dll"
+ Delete "$INSTDIR\bin\libexpat-1.dll"
+ Delete "$INSTDIR\bin\libfontconfig-1.dll"
+ Delete "$INSTDIR\bin\libgdk-3-0.dll"
+ Delete "$INSTDIR\bin\libgdk_pixbuf-2.0-0.dll"
+ Delete "$INSTDIR\bin\libgio-2.0-0.dll"
+ Delete "$INSTDIR\bin\libglib-2.0-0.dll"
+ Delete "$INSTDIR\bin\libgmodule-2.0-0.dll"
+ Delete "$INSTDIR\bin\libgobject-2.0-0.dll"
+ Delete "$INSTDIR\bin\libgthread-2.0-0.dll"
+ Delete "$INSTDIR\bin\libjson-glib-1.0-0.dll"
+ Delete "$INSTDIR\bin\libpango-1.0-0.dll"
+ Delete "$INSTDIR\bin\libpangocairo-1.0-0.dll"
+ Delete "$INSTDIR\bin\libpangoft2-1.0-0.dll"
+ Delete "$INSTDIR\bin\libpangowin32-1.0-0.dll"
+ Delete "$INSTDIR\bin\libpng14-14.dll"
+ Delete "$INSTDIR\bin\intl.dll"
+ Delete "$INSTDIR\bin\libintl-8.dll"
+ Delete "$INSTDIR\bin\libiconv-2.dll"
+ Delete "$INSTDIR\bin\zlib1.dll"
+ Delete "$INSTDIR\bin\libgtk-3-0.dll"
+ Delete "$INSTDIR\bin\libffi-5.dll"
+ Delete "$INSTDIR\bin\libcairo-gobject-2.dll"
+ Delete "$INSTDIR\bin\libeay32.dll"
+ Delete "$INSTDIR\bin\libssl32.dll"
+
+ Delete "$INSTDIR\bin\libproxy.dll"
+ Delete "$INSTDIR\bin\libmodman.dll"
+ Delete "$INSTDIR\bin\libstdc++-6.dll"
+ Delete "$INSTDIR\bin\libgcc_s_sjlj-1.dll"
+ Delete "$INSTDIR\bin\libgcc_s_dw2-1.dll"
+
+ Delete "$INSTDIR\bin\gspawn-win32-helper-console.exe"
+ Delete "$INSTDIR\bin\gspawn-win32-helper.exe"
+ #Delete "$INSTDIR\lib\gtk-2.0\2.10.0\engines\libpixmap.dll"
+ #Delete "$INSTDIR\lib\gtk-2.0\2.10.0\engines\libwimp.dll"
+ #Delete "$INSTDIR\lib\gtk-2.0\modules\libgail.dll"
+ #Delete "$INSTDIR\etc\gtk-2.0\gtkrc"
+
+ Delete "$INSTDIR\share\icons\hicolor\16x16\apps\transmission-remote-gtk.png"
+ Delete "$INSTDIR\share\icons\hicolor\22x22\apps\transmission-remote-gtk.png"
+ Delete "$INSTDIR\share\icons\hicolor\24x24\apps\transmission-remote-gtk.png"
+ Delete "$INSTDIR\share\icons\hicolor\32x32\apps\transmission-remote-gtk.png"
+ Delete "$INSTDIR\share\icons\hicolor\48x48\apps\transmission-remote-gtk.png"
+ Delete "$INSTDIR\share\icons\hicolor\icon-theme.cache"
+ Delete "$INSTDIR\share\icons\hicolor\index.theme"
+ Delete "$INSTDIR\share\icons\hicolor\scalable\apps\transmission-remote-gtk.svg"
+ #Delete "$INSTDIR\share\locale\de\LC_MESSAGES\atk10.mo"
+ #Delete "$INSTDIR\share\locale\de\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\de\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\de\LC_MESSAGES\gettext-tools.mo"
+ #Delete "$INSTDIR\share\locale\de\LC_MESSAGES\glib20.mo"
+ #Delete "$INSTDIR\share\locale\de\LC_MESSAGES\gtk30-properties.mo"
+ #Delete "$INSTDIR\share\locale\de\LC_MESSAGES\gtk30.mo"
+ Delete "$INSTDIR\share\locale\de\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\de\LC_MESSAGES\transmission-remote-gtk.mo"
+ #Delete "$INSTDIR\share\locale\lt\LC_MESSAGES\atk10.mo"
+ #Delete "$INSTDIR\share\locale\lt\LC_MESSAGES\gdk-pixbuf.mo"
+ #Delete "$INSTDIR\share\locale\lt\LC_MESSAGES\glib20.mo"
+ #Delete "$INSTDIR\share\locale\lt\LC_MESSAGES\gtk30-properties.mo"
+ #Delete "$INSTDIR\share\locale\lt\LC_MESSAGES\gtk30.mo"
+ Delete "$INSTDIR\share\locale\lt\LC_MESSAGES\transmission-remote-gtk.mo"
+ #Delete "$INSTDIR\share\locale\es\LC_MESSAGES\atk10.mo"
+ #Delete "$INSTDIR\share\locale\es\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\es\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\es\LC_MESSAGES\gettext-tools.mo"
+ #Delete "$INSTDIR\share\locale\es\LC_MESSAGES\glib20.mo"
+ #Delete "$INSTDIR\share\locale\es\LC_MESSAGES\gtk30-properties.mo"
+ #Delete "$INSTDIR\share\locale\es\LC_MESSAGES\gtk30.mo"
+ Delete "$INSTDIR\share\locale\es\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\es\LC_MESSAGES\transmission-remote-gtk.mo"
+ #Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\atk10.mo"
+ #Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\gettext-tools.mo"
+ #Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\glib20.mo"
+ #Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\gtk30-properties.mo"
+ #Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\gtk30.mo"
+ Delete "$INSTDIR\share\locale\ko\LC_MESSAGES\transmission-remote-gtk.mo"
+ #Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\atk10.mo"
+ #Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\gettext-tools.mo"
+ #Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\glib20.mo"
+ #Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\gtk30-properties.mo"
+ #Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\gtk30.mo"
+ Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\pl\LC_MESSAGES\transmission-remote-gtk.mo"
+ #Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\atk10.mo"
+ #Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\gettext-runtime.mo"
+ Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\gettext-tools.mo"
+ #Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\glib20.mo"
+ #Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\gtk30-properties.mo"
+ #Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\gtk30.mo"
+ Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\libiconv.mo"
+ Delete "$INSTDIR\share\locale\ru\LC_MESSAGES\transmission-remote-gtk.mo"
+ #Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\atk10.mo"
+ #Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\gdk-pixbuf.mo"
+ Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\gettext-runtime.mo"
+ #Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\glib20.mo"
+ Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\gtk30-properties.mo"
+ Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\gtk30.mo"
+ Delete "$INSTDIR\share\locale\fr\LC_MESSAGES\transmission-remote-gtk.mo"
+ #Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\atk10.mo"
+ #Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\gdk-pixbuf.mo"
+ #Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\gettext-runtime.mo"
+ #Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\gettext-tools.mo"
+ #Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\glib20.mo"
+ #Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\gtk30-properties.mo"
+ #Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\gtk30.mo"
+ #Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\libiconv.mo"
+ #Delete "$INSTDIR\share\locale\uk\LC_MESSAGES\transmission-remote-gtk.mo"
+ #Delete "$INSTDIR\share\themes\MS-Windows\gtk-2.0\gtkrc"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Script.pod"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Script.pm"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Maketext\TPJ13.pod"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Maketext\GutsLoader.pm"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Maketext\Guts.pm"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Maketext.pod"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Maketext.pm"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Language.pod"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Language.pm"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Currency.pod"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Currency.pm"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Country.pod"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Country.pm"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Constants.pod"
+ Delete "$INSTDIR\share\perl5\5.8\Locale\Constants.pm"
+
+ ; Remove shortcuts, if any
+ !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder
+ SetShellVarContext current
+ Delete "$SMPROGRAMS\$StartMenuFolder\*.*"
+ RMDir "$SMPROGRAMS\$StartMenuFolder"
+!ifndef PORTABLE
+ SetShellVarContext all
+ Delete "$SMPROGRAMS\$StartMenuFolder\*.*"
+ RMDir "$SMPROGRAMS\$StartMenuFolder"
+!endif
+
+ ; Remove directories used
+ #RMDir "$INSTDIR\share\themes\MS-Windows\gtk-2.0"
+ #RMDir "$INSTDIR\share\themes\MS-Windows"
+ RMDir "$INSTDIR\share\themes"
+ #RMDir "$INSTDIR\share\locale\uk\LC_MESSAGES"
+ #RMDir "$INSTDIR\share\locale\uk"
+ RMDir "$INSTDIR\share\locale\fr\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\fr"
+ RMDir "$INSTDIR\share\locale\ru\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\ru"
+ RMDir "$INSTDIR\share\locale\pl\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\pl"
+ RMDir "$INSTDIR\share\locale\ko\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\ko"
+ RMDir "$INSTDIR\share\locale\es\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\es"
+ RMDir "$INSTDIR\share\locale\de\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\de"
+ RMDir "$INSTDIR\share\locale\lt\LC_MESSAGES"
+ RMDir "$INSTDIR\share\locale\lt"
+ RMDir "$INSTDIR\share\locale"
+ RMDir "$INSTDIR\share\icons\hicolor\scalable\apps"
+ RMDir "$INSTDIR\share\icons\hicolor\scalable"
+ RMDir "$INSTDIR\share\icons\hicolor\48x48\apps"
+ RMDir "$INSTDIR\share\icons\hicolor\48x48"
+ RMDir "$INSTDIR\share\icons\hicolor\32x32\apps"
+ RMDir "$INSTDIR\share\icons\hicolor\32x32"
+ RMDir "$INSTDIR\share\icons\hicolor\24x24\apps"
+ RMDir "$INSTDIR\share\icons\hicolor\24x24"
+ RMDir "$INSTDIR\share\icons\hicolor\22x22\apps"
+ RMDir "$INSTDIR\share\icons\hicolor\22x22"
+ RMDir "$INSTDIR\share\icons\hicolor\16x16\apps"
+ RMDir "$INSTDIR\share\icons\hicolor\16x16"
+ RMDir "$INSTDIR\share\icons\hicolor"
+ RMDir "$INSTDIR\share\icons"
+ RMDir "$INSTDIR\share\perl5\5.8\Locale\Maketext"
+ RMDir "$INSTDIR\share\perl5\5.8\Locale"
+ RMDir "$INSTDIR\share\perl5\5.8"
+ RMDir "$INSTDIR\share\perl5"
+ RMDir "$INSTDIR\share"
+ #RMDir "$INSTDIR\etc\gtk-2.0"
+ RMDir "$INSTDIR\etc"
+ #RMDir "$INSTDIR\lib\gtk-2.0\modules"
+ #RMDir "$INSTDIR\lib\gtk-2.0\2.10.0\engines"
+ #RMDir "$INSTDIR\lib\gtk-2.0\2.10.0"
+ #RMDir "$INSTDIR\lib\gtk-2.0"
+ RMDir "$INSTDIR\lib"
+ RMDir "$INSTDIR\bin"
+ RMDir "$INSTDIR"
+
+ DeleteRegKey /ifempty HKCU "SOFTWARE\TransmissionRemoteGTK"
+!ifndef PORTABLE
+ DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TransmissionRemoteGTK"
+!endif
+
+SectionEnd
+
+!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
+ !insertmacro MUI_DESCRIPTION_TEXT ${SecTransmissionRemoteGTK} $(DESC_SecTransmissionRemoteGTK)
+ !insertmacro MUI_DESCRIPTION_TEXT ${SecGeoIP} $(DESC_GeoIP)
+ !insertmacro MUI_DESCRIPTION_TEXT ${SecGlibGtkEtc} $(DESC_SecGlibGtkEtc)
+ !insertmacro MUI_DESCRIPTION_TEXT ${SecDesktopIcon} $(DESC_SecDesktopIcon)
+; !insertmacro MUI_DESCRIPTION_TEXT ${SecFiletypeAssociations} $(DESC_SecFiletypeAssociations)
+; !insertmacro MUI_DESCRIPTION_TEXT ${SecGeoIPDatabase} $(DESC_SecGeoIPDatabase)
+; !insertmacro MUI_DESCRIPTION_TEXT ${SecLanguages} $(DESC_SecLanguages)
+; !insertmacro MUI_DESCRIPTION_TEXT ${SecRegiterTorrent} $(DESC_SecRegiterTorrent)
+; !insertmacro MUI_DESCRIPTION_TEXT ${SecRegiterMagnet} $(DESC_SecRegiterMagnet)
+!insertmacro MUI_FUNCTION_DESCRIPTION_END
+
+Function .onInit
+ System::Call 'kernel32::CreateMutexA(i 0, i 0, t "Transmission Remote GTK") ?e'
+ Pop $R0
+ StrCmp $R0 0 +3
+ MessageBox MB_OK|MB_ICONEXCLAMATION "The installer is already running."
+ Abort
+
+ !insertmacro MUI_LANGDLL_DISPLAY
+!ifdef PORTABLE
+ StrCpy $INSTDIR "\${ProgramFilesDir}"
+!else
+ ${If} ${RunningX64}
+ StrCpy $INSTDIR "$PROGRAMFILES64\${ProgramFilesDir}"
+ ${Else}
+ StrCpy $INSTDIR "$PROGRAMFILES\${ProgramFilesDir}"
+ ${Endif}
+!endif
+FunctionEnd
diff --git a/src/json.c b/src/json.c
new file mode 100644
index 0000000..fdce0b6
--- /dev/null
+++ b/src/json.c
@@ -0,0 +1,114 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib-object.h>
+#include <glib/gprintf.h>
+#include <json-glib/json-glib.h>
+#include <gtk/gtk.h>
+
+#include "config.h"
+#include "protocol-constants.h"
+#include "requests.h"
+#include "json.h"
+
+/* JSON helper functions */
+
+gchar *trg_serialize(JsonNode * req)
+{
+ JsonGenerator *generator;
+ gsize length;
+ gchar *response;
+
+ generator = json_generator_new();
+ json_generator_set_root(generator, req);
+
+ response = json_generator_to_data(generator, &length);
+ g_object_unref(generator);
+
+ return response;
+}
+
+JsonObject *trg_deserialize(trg_response * response, GError ** error)
+{
+ JsonParser *parser;
+ JsonNode *root;
+ JsonObject *ret = NULL;
+
+ parser = json_parser_new();
+ json_parser_load_from_data(parser, response->raw, response->size,
+ error);
+ if (*error == NULL) {
+ root = json_parser_get_root(parser);
+#ifdef DEBUG
+ if (g_getenv("TRG_SHOW_INCOMING") != NULL) {
+ g_debug("<=(INcoming)<=: %s", response->raw);
+ } else if (g_getenv("TRG_SHOW_INCOMING_PRETTY") != NULL) {
+ JsonGenerator *pg;
+ gsize len;
+ gchar *pgdata;
+
+ pg = json_generator_new();
+ g_object_set(pg, "pretty", TRUE, NULL);
+ json_generator_set_root(pg, root);
+
+ pgdata = json_generator_to_data(pg, &len);
+ g_debug("<=(incoming)<=:\n%s\n", pgdata);
+ g_free(pgdata);
+
+ g_object_unref(pg);
+ }
+#endif
+ ret = json_node_get_object(root);
+ json_object_ref(ret);
+ }
+
+ g_object_unref(parser);
+ return ret;
+}
+
+JsonObject *node_get_arguments(JsonNode * req)
+{
+ JsonObject *rootObj = json_node_get_object(req);
+ return get_arguments(rootObj);
+}
+
+JsonObject *get_arguments(JsonObject * req)
+{
+ return json_object_get_object_member(req, PARAM_ARGUMENTS);
+}
+
+gdouble json_double_to_progress(JsonNode * n)
+{
+ return json_node_really_get_double(n) * 100.0;
+}
+
+gdouble json_node_really_get_double(JsonNode * node)
+{
+ GValue a = { 0 };
+
+ json_node_get_value(node, &a);
+ switch (G_VALUE_TYPE(&a)) {
+ case G_TYPE_INT64:
+ return (gdouble) g_value_get_int64(&a);
+ case G_TYPE_DOUBLE:
+ return g_value_get_double(&a);
+ default:
+ return 0.0;
+ }
+}
diff --git a/src/json.h b/src/json.h
new file mode 100644
index 0000000..860f73a
--- /dev/null
+++ b/src/json.h
@@ -0,0 +1,35 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef JSON_H_
+#define JSON_H_
+
+#include <glib-object.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-client.h"
+
+gchar *trg_serialize(JsonNode * req);
+JsonObject *trg_deserialize(trg_response * response, GError ** error);
+JsonObject *get_arguments(JsonObject * req);
+JsonObject *node_get_arguments(JsonNode * req);
+gdouble json_double_to_progress(JsonNode * n);
+gdouble json_node_really_get_double(JsonNode * node);
+
+#endif /* JSON_H_ */
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..f7570ce
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,301 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+#include <fontconfig/fontconfig.h>
+
+#if !GTK_CHECK_VERSION( 3, 0, 0 ) && HAVE_LIBUNIQUE
+#include <unique/unique.h>
+#elif GTK_CHECK_VERSION( 3, 0, 0 )
+#include "trg-gtk-app.h"
+#elif WIN32
+#include "win32-mailslot.h"
+#endif
+
+#include "trg-main-window.h"
+#include "trg-client.h"
+#include "util.h"
+
+/* Handle arguments and start the main window. Unfortunately, there's three
+ * different ways to achieve a unique instance and pass arguments around. :(
+ *
+ * 1) libunique - GTK2 (non-win32). deprecated in GTK3 for GtkApplication.
+ * 2) GtkApplication - replaces libunique, GTK3 only, and non-win32.
+ * 3) win32 API mailslots.
+ */
+
+/*
+ * libunique.
+ */
+
+#if !GTK_CHECK_VERSION( 3, 0, 0 ) && HAVE_LIBUNIQUE
+
+enum {
+ COMMAND_0,
+ COMMAND_ADD
+};
+
+static UniqueResponse
+message_received_cb(UniqueApp * app G_GNUC_UNUSED,
+ gint command,
+ UniqueMessageData * message,
+ guint time_, gpointer user_data)
+{
+ TrgMainWindow *win;
+ UniqueResponse res;
+ gchar **uris;
+
+ win = TRG_MAIN_WINDOW(user_data);
+
+ switch (command) {
+ case UNIQUE_ACTIVATE:
+ gtk_window_set_screen(GTK_WINDOW(user_data),
+ unique_message_data_get_screen(message));
+ gtk_window_present_with_time(GTK_WINDOW(user_data), time_);
+ res = UNIQUE_RESPONSE_OK;
+ break;
+ case COMMAND_ADD:
+ uris = unique_message_data_get_uris(message);
+ res =
+ trg_add_from_filename(win,
+ uris) ? UNIQUE_RESPONSE_OK :
+ UNIQUE_RESPONSE_FAIL;
+ break;
+ default:
+ res = UNIQUE_RESPONSE_OK;
+ break;
+ }
+
+ return res;
+}
+
+static gint
+trg_libunique_init(TrgClient * client, int argc,
+ gchar * argv[], gchar ** args)
+{
+ UniqueApp *app = unique_app_new_with_commands("uk.org.eth0.trg", NULL,
+ "add", COMMAND_ADD,
+ NULL);
+ TrgMainWindow *window;
+
+ if (unique_app_is_running(app)) {
+ UniqueCommand command;
+ UniqueResponse response;
+ UniqueMessageData *message;
+
+ if (args) {
+ command = COMMAND_ADD;
+ message = unique_message_data_new();
+ unique_message_data_set_uris(message, args);
+ g_strfreev(args);
+ } else {
+ command = UNIQUE_ACTIVATE;
+ message = NULL;
+ }
+
+ response = unique_app_send_message(app, command, message);
+ unique_message_data_free(message);
+
+ if (response != UNIQUE_RESPONSE_OK)
+ return EXIT_FAILURE;
+ } else {
+ window =
+ trg_main_window_new(client, should_be_minimised(argc, argv));
+ g_signal_connect(app, "message-received",
+ G_CALLBACK(message_received_cb), window);
+
+ trg_main_window_set_start_args(window, args);
+ auto_connect_if_required(window);
+ gtk_main();
+ }
+
+ g_object_unref(app);
+
+ return EXIT_SUCCESS;
+}
+
+#elif !WIN32 && GTK_CHECK_VERSION( 3, 0, 0 )
+
+/* GtkApplication - the replacement for libunique.
+ * This is implemented in trg-gtk-app.c
+ */
+
+static gint trg_gtkapp_init(TrgClient * client, int argc, char *argv[])
+{
+ TrgGtkApp *gtk_app = trg_gtk_app_new(client);
+
+ gint exitCode = g_application_run(G_APPLICATION(gtk_app), argc, argv);
+
+ g_object_unref(gtk_app);
+
+ return exitCode;
+}
+
+#elif WIN32
+
+static gint
+trg_win32_init(TrgClient * client, int argc, char *argv[], gchar ** args)
+{
+ gchar *moddir =
+ g_win32_get_package_installation_directory_of_module(NULL);
+ gchar *localedir =
+ g_build_path(G_DIR_SEPARATOR_S, moddir, "share", "locale",
+ NULL);
+
+ bindtextdomain(GETTEXT_PACKAGE, localedir);
+ g_free(moddir);
+ g_free(localedir);
+
+ if (!mailslot_send_message(args)) {
+ TrgMainWindow *window =
+ trg_main_window_new(client, should_be_minimised(argc, argv));
+ trg_main_window_set_start_args(window, args);
+ auto_connect_if_required(window);
+ mailslot_start_background_listener(window);
+ gtk_main();
+ }
+
+ return EXIT_SUCCESS;
+}
+
+#else
+
+static gint
+trg_simple_init(TrgClient * client, int argc, char *argv[], gchar ** args)
+{
+ TrgMainWindow *window =
+ trg_main_window_new(client, should_be_minimised(argc, argv));
+ trg_main_window_set_start_args(window, args);
+ auto_connect_if_required(window);
+ gtk_main();
+
+ return EXIT_SUCCESS;
+}
+
+#endif
+
+/* Win32 mailslots. I've implemented this in win32-mailslot.c */
+
+#if !WIN32
+static void trg_non_win32_init()
+{
+ bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
+}
+#endif
+
+static void trg_cleanup()
+{
+ curl_global_cleanup();
+}
+
+#if WIN32 || !GTK_CHECK_VERSION( 3, 0, 0 )
+
+static gchar **convert_args(int argc, char *argv[])
+{
+ gchar *cwd = g_get_current_dir();
+ gchar **files = NULL;
+
+ if (argc > 1) {
+ GSList *list = NULL;
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ if (is_minimised_arg(argv[i])) {
+ continue;
+ } else if (!is_url(argv[i]) && !is_magnet(argv[i])
+ && g_file_test(argv[i], G_FILE_TEST_IS_REGULAR)
+ && !g_path_is_absolute(argv[i])) {
+ list = g_slist_append(list,
+ g_build_path(G_DIR_SEPARATOR_S, cwd,
+ argv[i], NULL));
+ } else {
+ list = g_slist_append(list, g_strdup(argv[i]));
+ }
+ }
+
+ if (list) {
+ GSList *li;
+ files = g_new0(gchar *, g_slist_length(list) + 1);
+ i = 0;
+ for (li = list; li; li = g_slist_next(li)) {
+ files[i++] = li->data;
+ }
+ g_slist_free(list);
+ }
+ }
+
+ g_free(cwd);
+
+ return files;
+}
+
+#endif
+
+int main(int argc, char *argv[])
+{
+#if WIN32 || !GTK_CHECK_VERSION( 3, 0, 0 )
+ gchar **args;
+#endif
+ gint exitCode = EXIT_SUCCESS;
+ TrgClient *client;
+
+ g_type_init();
+ g_thread_init(NULL);
+ gtk_init(&argc, &argv);
+
+#if WIN32 || !GTK_CHECK_VERSION( 3, 0, 0 )
+ args = convert_args(argc, argv);
+#endif
+
+ curl_global_init(CURL_GLOBAL_ALL);
+ client = trg_client_new();
+
+ g_set_application_name(PACKAGE_NAME);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+ textdomain(GETTEXT_PACKAGE);
+
+#ifdef WIN32
+ exitCode = trg_win32_init(client, argc, argv, args);
+#else
+ trg_non_win32_init();
+#if !GTK_CHECK_VERSION( 3, 0, 0 ) && HAVE_LIBUNIQUE
+ exitCode = trg_libunique_init(client, argc, argv, args);
+#elif GTK_CHECK_VERSION( 3, 0, 0 )
+ exitCode = trg_gtkapp_init(client, argc, argv);
+#else
+ exitCode = trg_simple_init(client, argc, argv, args);
+#endif
+#endif
+
+ trg_cleanup();
+
+ return exitCode;
+}
diff --git a/src/protocol-constants.h b/src/protocol-constants.h
new file mode 100644
index 0000000..061a10e
--- /dev/null
+++ b/src/protocol-constants.h
@@ -0,0 +1,198 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PROTOCOL_CONSTANTS_H_
+#define PROTOCOL_CONSTANTS_H_
+
+/* generic contstants */
+
+#define PARAM_METHOD "method"
+#define FIELD_ID "id"
+
+/* top level */
+
+#define FIELD_RESULT "result"
+#define FIELD_SUCCESS "success"
+
+/* torrents */
+
+#define FIELD_RECENTLY_ACTIVE "recently-active"
+#define FIELD_TORRENTS "torrents" /* parent node */
+#define FIELD_REMOVED "removed"
+#define FIELD_ANNOUNCE_URL "announceUrl"
+#define FIELD_LEFT_UNTIL_DONE "leftUntilDone"
+#define FIELD_TOTAL_SIZE "totalSize"
+#define FIELD_DONE_DATE "doneDate"
+#define FIELD_ADDED_DATE "addedDate"
+#define FIELD_DATE_CREATED "dateCreated"
+#define FIELD_TRACKER_STATS "trackerStats"
+#define FIELD_DOWNLOAD_DIR "downloadDir"
+#define FIELD_HASH_STRING "hashString"
+//#define FIELD_SWARM_SPEED "swarmSpeed"
+#define FIELD_NAME "name"
+#define FIELD_SIZEWHENDONE "sizeWhenDone"
+#define FIELD_STATUS "status"
+#define FIELD_MOVE "move"
+#define FIELD_CREATOR "creator"
+#define FIELD_LOCATION "location"
+#define FIELD_RATEDOWNLOAD "rateDownload"
+#define FIELD_RATEUPLOAD "rateUpload"
+#define FIELD_ETA "eta"
+#define FIELD_UPLOADEDEVER "uploadedEver"
+#define FIELD_DOWNLOADEDEVER "downloadedEver"
+#define FIELD_HAVEVALID "haveValid"
+#define FIELD_HAVEUNCHECKED "haveUnchecked"
+#define FIELD_PERCENTDONE "percentDone"
+#define FIELD_PEERS "peers"
+#define FIELD_PEERSFROM "peersFrom"
+#define FIELD_FILES "files"
+#define FIELD_WANTED "wanted"
+#define FIELD_WEB_SEEDS_SENDING_TO_US "webseedsSendingToUs"
+#define FIELD_PRIORITIES "priorities"
+#define FIELD_COMMENT "comment"
+#define FIELD_LEFTUNTILDONE "leftUntilDone"
+#define FIELD_ISFINISHED "isFinished"
+#define FIELD_ISPRIVATE "isPrivate"
+#define FIELD_ERROR "error"
+#define FIELD_ERROR_STRING "errorString"
+#define FIELD_BANDWIDTH_PRIORITY "bandwidthPriority"
+#define FIELD_UPLOAD_LIMIT "uploadLimit"
+#define FIELD_UPLOAD_LIMITED "uploadLimited"
+#define FIELD_DOWNLOAD_LIMIT "downloadLimit"
+#define FIELD_DOWNLOAD_LIMITED "downloadLimited"
+#define FIELD_HONORS_SESSION_LIMITS "honorsSessionLimits"
+#define FIELD_SEED_RATIO_MODE "seedRatioMode"
+#define FIELD_SEED_RATIO_LIMIT "seedRatioLimit"
+#define FIELD_PEER_LIMIT "peer-limit"
+#define FIELD_DOWNLOAD_DIR "downloadDir"
+#define FIELD_FILE_DOWNLOAD_DIR "download-dir"
+#define FIELD_PEERS_SENDING_TO_US "peersSendingToUs"
+#define FIELD_PEERS_GETTING_FROM_US "peersGettingFromUs"
+#define FIELD_PEERS_CONNECTED "peersConnected"
+#define FIELD_QUEUE_POSITION "queuePosition"
+#define FIELD_ACTIVITY_DATE "activityDate"
+#define FIELD_ISPRIVATE "isPrivate"
+#define FIELD_METADATAPERCENTCOMPLETE "metadataPercentComplete"
+
+#define FIELD_FILES_WANTED "files-wanted"
+#define FIELD_FILES_UNWANTED "files-unwanted"
+#define FIELD_FILES_PRIORITY_HIGH "priority-high"
+#define FIELD_FILES_PRIORITY_NORMAL "priority-normal"
+#define FIELD_FILES_PRIORITY_LOW "priority-low"
+
+/* trackers */
+
+#define FIELD_TIER "tier"
+#define FIELD_ANNOUNCE "announce"
+#define FIELD_SCRAPE "scrape"
+#define FIELD_LAST_ANNOUNCE_PEER_COUNT "lastAnnouncePeerCount"
+#define FIELD_LAST_ANNOUNCE_TIME "lastAnnounceTime"
+#define FIELD_LAST_SCRAPE_TIME "lastScrapeTime"
+#define FIELD_SEEDERCOUNT "seederCount"
+#define FIELD_LEECHERCOUNT "leecherCount"
+#define FIELD_DOWNLOADCOUNT "downloadCount"
+#define FIELD_HOST "host"
+#define FIELD_LAST_ANNOUNCE_RESULT "lastAnnounceResult"
+#define FIELD_RECHECK_PROGRESS "recheckProgress"
+
+/* methods */
+
+#define METHOD_TORRENT_START "torrent-start"
+#define METHOD_SESSION_GET "session-get"
+#define METHOD_SESSION_SET "session-set"
+#define METHOD_TORRENT_GET "torrent-get"
+#define METHOD_TORRENT_SET "torrent-set"
+#define METHOD_TORRENT_SET_LOCATION "torrent-set-location"
+#define METHOD_TORRENT_STOP "torrent-stop"
+#define METHOD_TORRENT_VERIFY "torrent-verify"
+#define METHOD_TORRENT_REMOVE "torrent-remove"
+#define METHOD_TORRENT_ADD "torrent-add"
+#define METHOD_TORRENT_REANNOUNCE "torrent-reannounce"
+#define METHOD_PORT_TEST "port-test"
+#define METHOD_BLOCKLIST_UPDATE "blocklist-update"
+#define METHOD_SESSION_STATS "session-stats"
+#define METHOD_QUEUE_MOVE_TOP "queue-move-top"
+#define METHOD_QUEUE_MOVE_UP "queue-move-up"
+#define METHOD_QUEUE_MOVE_BOTTOM "queue-move-bottom"
+#define METHOD_QUEUE_MOVE_DOWN "queue-move-down"
+#define METHOD_TORRENT_START_NOW "torrent-start-now"
+
+#define PARAM_IDS "ids"
+#define PARAM_DELETE_LOCAL_DATA "delete-local-data"
+#define PARAM_ARGUMENTS "arguments"
+#define PARAM_FIELDS "fields"
+#define PARAM_METAINFO "metainfo"
+#define PARAM_FILENAME "filename"
+#define PARAM_PAUSED "paused"
+#define PARAM_TAG "tag"
+
+/* peers structure */
+
+#define TPEER_ADDRESS "address"
+#define TPEER_CLIENT_NAME "clientName"
+#define TPEER_PROGRESS "progress"
+#define TPEER_RATE_TO_CLIENT "rateToClient"
+#define TPEER_RATE_TO_PEER "rateToPeer"
+#define TPEER_IS_ENCRYPTED "isEncrypted"
+#define TPEER_IS_DOWNLOADING_FROM "isDownloadingFrom"
+#define TPEER_IS_UPLOADING_TO "isUploadingTo"
+#define TPEER_FLAGSTR "flagStr"
+
+#define TPEERFROM_FROMPEX "fromPex"
+#define TPEERFROM_FROMDHT "fromDht"
+#define TPEERFROM_FROMTRACKERS "fromTracker"
+#define TPEERFROM_FROMLTEP "fromLtep"
+#define TPEERFROM_FROMRESUME "fromCache"
+#define TPEERFROM_FROMINCOMING "fromIncoming"
+#define TPEERFROM_FROMLPD "fromLpd"
+
+/* The rpc-version >= that the status field of torrent-get changed */
+#define NEW_STATUS_RPC_VERSION 14
+
+typedef enum {
+ OLD_STATUS_WAITING_TO_CHECK = 1,
+ OLD_STATUS_CHECKING = 2,
+ OLD_STATUS_DOWNLOADING = 4,
+ OLD_STATUS_SEEDING = 8,
+ OLD_STATUS_PAUSED = 16
+} trg_old_status;
+
+typedef enum {
+ TR_STATUS_STOPPED = 0, /* Torrent is stopped */
+ TR_STATUS_CHECK_WAIT = 1, /* Queued to check files */
+ TR_STATUS_CHECK = 2, /* Checking files */
+ TR_STATUS_DOWNLOAD_WAIT = 3, /* Queued to download */
+ TR_STATUS_DOWNLOAD = 4, /* Downloading */
+ TR_STATUS_SEED_WAIT = 5, /* Queued to seed */
+ TR_STATUS_SEED = 6 /* Seeding */
+} tr_torrent_activity;
+
+enum {
+ TR_PRI_UNSET = -3, /* Not actually in the protocol. Just used in UI. */
+ TR_PRI_MIXED = -2, /* Neither is this. */
+ TR_PRI_LOW = -1,
+ TR_PRI_NORMAL = 0, /* since NORMAL is 0, memset initializes nicely */
+ TR_PRI_HIGH = 1
+};
+
+#define TFILE_LENGTH "length"
+#define TFILE_BYTES_COMPLETED "bytesCompleted"
+#define TFILE_NAME "name"
+
+#endif /* PROTOCOL_CONSTANTS_H_ */
diff --git a/src/remote-exec.c b/src/remote-exec.c
new file mode 100644
index 0000000..edf75f2
--- /dev/null
+++ b/src/remote-exec.c
@@ -0,0 +1,206 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-torrent-model.h"
+#include "trg-client.h"
+#include "trg-prefs.h"
+#include "protocol-constants.h"
+#include "torrent.h"
+
+/* A few functions used to build local commands, otherwise known as actions.
+ *
+ * The functionality from a user perspective is documented in the wiki.
+ * The code below really just uses GRegex to replace variable identifier
+ * with the values inside the connected profile, the session, or the first selected
+ * torrent (in that order of precedence). A field seperator I call a repeater
+ * can be appended to a variable in square brackets, like %{id}[,] to
+ * cause it to be repeated for each selection.
+ */
+
+static const char json_exceptions[] = { 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84,
+ 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90,
+ 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c,
+ 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8,
+ 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4,
+ 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0,
+ 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc,
+ 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8,
+ 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4,
+ 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0,
+ 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc,
+ 0xfd, 0xfe, 0xff, '\0' /* g_strescape() expects a NUL-terminated string */
+};
+
+static gchar *dump_json_value(JsonNode * node)
+{
+ GValue value = { 0, };
+ GString *buffer;
+
+ buffer = g_string_new("");
+
+ json_node_get_value(node, &value);
+
+ switch (G_VALUE_TYPE(&value)) {
+ case G_TYPE_INT64:
+ g_string_append_printf(buffer, "%" G_GINT64_FORMAT,
+ g_value_get_int64(&value));
+ break;
+ case G_TYPE_STRING:
+ {
+ gchar *tmp;
+
+ tmp = g_strescape(g_value_get_string(&value), json_exceptions);
+ g_string_append(buffer, tmp);
+
+ g_free(tmp);
+ }
+ break;
+ case G_TYPE_DOUBLE:
+ {
+ gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
+
+ g_string_append(buffer,
+ g_ascii_dtostr(buf, sizeof(buf),
+ g_value_get_double(&value)));
+ }
+ break;
+ case G_TYPE_BOOLEAN:
+ g_string_append_printf(buffer, "%s",
+ g_value_get_boolean(&value) ? "true" :
+ "false");
+ break;
+ default:
+ break;
+ }
+
+ g_value_unset(&value);
+
+ return g_string_free(buffer, FALSE);
+}
+
+gchar *build_remote_exec_cmd(TrgClient * tc, GtkTreeModel * model,
+ GList * selection, const gchar * input)
+{
+ TrgPrefs *prefs = trg_client_get_prefs(tc);
+ JsonObject *session = trg_client_get_session(tc);
+ JsonObject *profile = trg_prefs_get_connection(prefs);
+ gchar *work;
+ GRegex *regex, *replacerx;
+ GMatchInfo *match_info;
+ gchar *whole, *wholeEscaped, *id, *tmp, *valuestr, *repeater;
+ JsonNode *replacement;
+
+ if (!profile)
+ return NULL;
+
+ work = g_strdup(input);
+ regex = g_regex_new("%{([A-Za-z\\-]+)}(?:\\[(.*)\\])?", 0, 0, NULL);
+
+ g_regex_match_full(regex, input, -1, 0, 0, &match_info, NULL);
+
+ if (match_info) {
+ while (g_match_info_matches(match_info)) {
+ whole = g_match_info_fetch(match_info, 0);
+ wholeEscaped = g_regex_escape_string(whole, -1);
+ id = g_match_info_fetch(match_info, 1);
+ repeater = g_match_info_fetch(match_info, 2);
+
+ replacerx = g_regex_new(wholeEscaped, 0, 0, NULL);
+ valuestr = NULL;
+
+ if (profile && json_object_has_member(profile, id)) {
+ replacement = json_object_get_member(profile, id);
+ if (JSON_NODE_HOLDS_VALUE(replacement))
+ valuestr = dump_json_value(replacement);
+ } else if (session && json_object_has_member(session, id)) {
+ replacement = json_object_get_member(session, id);
+ if (JSON_NODE_HOLDS_VALUE(replacement))
+ valuestr = dump_json_value(replacement);
+ } else {
+ GString *gs = g_string_new("");
+ GList *li;
+ GtkTreeIter iter;
+ JsonObject *json;
+ gchar *piece;
+
+ for (li = selection; li; li = g_list_next(li)) {
+ piece = NULL;
+ gtk_tree_model_get_iter(model, &iter,
+ (GtkTreePath *) li->data);
+ gtk_tree_model_get(model, &iter, TORRENT_COLUMN_JSON,
+ &json, -1);
+ if (json_object_has_member(json, id)) {
+ replacement = json_object_get_member(json, id);
+ if (JSON_NODE_HOLDS_VALUE(replacement)) {
+ piece = dump_json_value(replacement);
+ }
+ }
+
+ if (!piece) {
+ if (!g_strcmp0(id, "full-dir")) {
+ piece = torrent_get_full_dir(json);
+ } else if (!g_strcmp0(id, "full-path")) {
+ piece = torrent_get_full_path(json);
+ }
+ }
+
+ if (piece) {
+ g_string_append(gs, piece);
+ g_free(piece);
+ }
+
+ if (!repeater)
+ break;
+
+ if (piece && li != g_list_last(selection))
+ g_string_append(gs, repeater);
+ }
+
+ if (gs->len > 0)
+ valuestr = g_string_free(gs, FALSE);
+ else
+ g_string_free(gs, TRUE);
+ }
+
+ if (valuestr) {
+ tmp = g_regex_replace(replacerx, work, -1, 0, valuestr, 0,
+ NULL);
+ g_free(work);
+ work = tmp;
+ g_free(valuestr);
+ }
+
+ g_regex_unref(replacerx);
+ g_free(whole);
+ g_free(repeater);
+ g_free(wholeEscaped);
+ g_free(id);
+ g_match_info_next(match_info, NULL);
+ }
+
+ g_match_info_free(match_info);
+ }
+
+ g_regex_unref(regex);
+ return work;
+}
diff --git a/src/remote-exec.h b/src/remote-exec.h
new file mode 100644
index 0000000..07ffa57
--- /dev/null
+++ b/src/remote-exec.h
@@ -0,0 +1,26 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef REMOTE_EXEC_H_
+#define REMOTE_EXEC_H_
+
+gchar *build_remote_exec_cmd(TrgClient * tc, GtkTreeModel * model,
+ GList * selection, const gchar * input);
+
+#endif /* REMOTE_EXEC_H_ */
diff --git a/src/requests.c b/src/requests.c
new file mode 100644
index 0000000..95be1d7
--- /dev/null
+++ b/src/requests.c
@@ -0,0 +1,293 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+
+#include <glib/gstdio.h>
+#include <glib-object.h>
+#include <json-glib/json-glib.h>
+
+#include "protocol-constants.h"
+#include "json.h"
+#include "torrent.h"
+#include "util.h"
+#include "requests.h"
+
+/* A bunch of functions for creating the various requests, in the form of a
+ * JsonNode ready for dispatch.
+ */
+
+static JsonNode *base_request(gchar * method);
+
+JsonNode *generic_request(gchar * method, JsonArray * ids)
+{
+ JsonNode *root = base_request(method);
+
+ if (ids) {
+ JsonObject *args = node_get_arguments(root);
+ json_object_set_array_member(args, PARAM_IDS, ids);
+ request_set_tag_from_ids(root, ids);
+ }
+
+ return root;
+}
+
+JsonNode *session_stats(void)
+{
+ return base_request(METHOD_SESSION_STATS);
+}
+
+JsonNode *blocklist_update(void)
+{
+ return base_request(METHOD_BLOCKLIST_UPDATE);
+}
+
+JsonNode *port_test(void)
+{
+ return base_request(METHOD_PORT_TEST);
+}
+
+JsonNode *session_get(void)
+{
+ return base_request(METHOD_SESSION_GET);
+}
+
+JsonNode *torrent_set_location(JsonArray * array, gchar * location,
+ gboolean move)
+{
+ JsonNode *req = generic_request(METHOD_TORRENT_SET_LOCATION, array);
+ JsonObject *args = node_get_arguments(req);
+ json_object_set_boolean_member(args, FIELD_MOVE, move);
+ json_object_set_string_member(args, FIELD_LOCATION, location);
+ return req;
+}
+
+JsonNode *torrent_start(JsonArray * array)
+{
+ return generic_request(METHOD_TORRENT_START, array);
+}
+
+JsonNode *torrent_pause(JsonArray * array)
+{
+ return generic_request(METHOD_TORRENT_STOP, array);
+}
+
+JsonNode *torrent_reannounce(JsonArray * array)
+{
+ return generic_request(METHOD_TORRENT_REANNOUNCE, array);
+}
+
+JsonNode *torrent_verify(JsonArray * array)
+{
+ return generic_request(METHOD_TORRENT_VERIFY, array);
+}
+
+JsonNode *torrent_queue_move_up(JsonArray * array)
+{
+ return generic_request(METHOD_QUEUE_MOVE_UP, array);
+}
+
+JsonNode *torrent_queue_move_down(JsonArray * array)
+{
+ return generic_request(METHOD_QUEUE_MOVE_DOWN, array);
+}
+
+JsonNode *torrent_start_now(JsonArray * array)
+{
+ return generic_request(METHOD_TORRENT_START_NOW, array);
+}
+
+JsonNode *torrent_queue_move_bottom(JsonArray * array)
+{
+ return generic_request(METHOD_QUEUE_MOVE_BOTTOM, array);
+}
+
+JsonNode *torrent_queue_move_top(JsonArray * array)
+{
+ return generic_request(METHOD_QUEUE_MOVE_TOP, array);
+}
+
+JsonNode *session_set(void)
+{
+ return generic_request(METHOD_SESSION_SET, NULL);
+}
+
+JsonNode *torrent_set(JsonArray * array)
+{
+ return generic_request(METHOD_TORRENT_SET, array);
+}
+
+JsonNode *torrent_remove(JsonArray * array, gboolean removeData)
+{
+ JsonNode *root = base_request(METHOD_TORRENT_REMOVE);
+ JsonObject *args = node_get_arguments(root);
+
+ json_object_set_array_member(args, PARAM_IDS, array);
+ json_object_set_boolean_member(args, PARAM_DELETE_LOCAL_DATA,
+ removeData);
+
+ request_set_tag(root, TORRENT_GET_TAG_MODE_FULL);
+
+ return root;
+}
+
+JsonNode *torrent_get(gint64 id)
+{
+ JsonNode *root = base_request(METHOD_TORRENT_GET);
+ JsonObject *args = node_get_arguments(root);
+ JsonArray *fields = json_array_new();
+
+ if (id == TORRENT_GET_TAG_MODE_UPDATE) {
+ json_object_set_string_member(args, PARAM_IDS,
+ FIELD_RECENTLY_ACTIVE);
+ } else if (id >= 0) {
+ JsonArray *ids = json_array_new();
+ json_array_add_int_element(ids, id);
+ json_object_set_array_member(args, PARAM_IDS, ids);
+ }
+
+ json_array_add_string_element(fields, FIELD_ETA);
+ json_array_add_string_element(fields, FIELD_PEERS);
+ json_array_add_string_element(fields, FIELD_PEERSFROM);
+ json_array_add_string_element(fields, FIELD_FILES);
+ json_array_add_string_element(fields, FIELD_PEERS_SENDING_TO_US);
+ json_array_add_string_element(fields, FIELD_PEERS_GETTING_FROM_US);
+ json_array_add_string_element(fields, FIELD_WEB_SEEDS_SENDING_TO_US);
+ json_array_add_string_element(fields, FIELD_PEERS_CONNECTED);
+ json_array_add_string_element(fields, FIELD_HAVEVALID);
+ json_array_add_string_element(fields, FIELD_HAVEUNCHECKED);
+ json_array_add_string_element(fields, FIELD_RATEUPLOAD);
+ json_array_add_string_element(fields, FIELD_RATEDOWNLOAD);
+ json_array_add_string_element(fields, FIELD_STATUS);
+ json_array_add_string_element(fields, FIELD_ISFINISHED);
+ json_array_add_string_element(fields, FIELD_ISPRIVATE);
+ json_array_add_string_element(fields, FIELD_ADDED_DATE);
+ json_array_add_string_element(fields, FIELD_DOWNLOADEDEVER);
+ json_array_add_string_element(fields, FIELD_UPLOADEDEVER);
+ json_array_add_string_element(fields, FIELD_SIZEWHENDONE);
+ json_array_add_string_element(fields, FIELD_QUEUE_POSITION);
+ json_array_add_string_element(fields, FIELD_ID);
+ json_array_add_string_element(fields, FIELD_NAME);
+ json_array_add_string_element(fields, FIELD_PERCENTDONE);
+ json_array_add_string_element(fields, FIELD_COMMENT);
+ json_array_add_string_element(fields, FIELD_TOTAL_SIZE);
+ json_array_add_string_element(fields, FIELD_METADATAPERCENTCOMPLETE);
+ json_array_add_string_element(fields, FIELD_LEFT_UNTIL_DONE);
+ json_array_add_string_element(fields, FIELD_ANNOUNCE_URL);
+ json_array_add_string_element(fields, FIELD_ERROR_STRING);
+ json_array_add_string_element(fields, FIELD_TRACKER_STATS);
+ json_array_add_string_element(fields, FIELD_DATE_CREATED);
+ json_array_add_string_element(fields, FIELD_DOWNLOAD_DIR);
+ json_array_add_string_element(fields, FIELD_CREATOR);
+ json_array_add_string_element(fields, FIELD_HASH_STRING);
+ json_array_add_string_element(fields, FIELD_DONE_DATE);
+ json_array_add_string_element(fields, FIELD_HONORS_SESSION_LIMITS);
+ json_array_add_string_element(fields, FIELD_UPLOAD_LIMIT);
+ json_array_add_string_element(fields, FIELD_UPLOAD_LIMITED);
+ json_array_add_string_element(fields, FIELD_DOWNLOAD_LIMIT);
+ json_array_add_string_element(fields, FIELD_DOWNLOAD_LIMITED);
+ json_array_add_string_element(fields, FIELD_BANDWIDTH_PRIORITY);
+ json_array_add_string_element(fields, FIELD_SEED_RATIO_LIMIT);
+ json_array_add_string_element(fields, FIELD_SEED_RATIO_MODE);
+ json_array_add_string_element(fields, FIELD_PEER_LIMIT);
+ json_array_add_string_element(fields, FIELD_ACTIVITY_DATE);
+ json_array_add_string_element(fields, FIELD_ERROR);
+ json_array_add_string_element(fields, FIELD_ERROR_STRING);
+ json_array_add_string_element(fields, FIELD_WANTED);
+ json_array_add_string_element(fields, FIELD_PRIORITIES);
+ json_array_add_string_element(fields, FIELD_RECHECK_PROGRESS);
+ json_object_set_array_member(args, PARAM_FIELDS, fields);
+ return root;
+}
+
+JsonNode *torrent_add_url(const gchar * url, gboolean paused)
+{
+ JsonNode *root = base_request(METHOD_TORRENT_ADD);
+ JsonObject *args = node_get_arguments(root);
+
+ json_object_set_string_member(args, PARAM_FILENAME, url);
+ json_object_set_boolean_member(args, PARAM_PAUSED, paused);
+ request_set_tag(root, TORRENT_GET_TAG_MODE_FULL);
+ return root;
+}
+
+JsonNode *torrent_add(gchar * target, gint flags)
+{
+ JsonNode *root;
+ JsonObject *args;
+ gboolean isMagnet = is_magnet(target);
+ gboolean isUri = isMagnet || is_url(target);
+ gchar *encodedFile;
+
+ if (!isUri && !g_file_test(target, G_FILE_TEST_IS_REGULAR)) {
+ g_message("file \"%s\" does not exist.", target);
+ return NULL;
+ }
+
+ root = base_request(METHOD_TORRENT_ADD);
+ args = node_get_arguments(root);
+
+ if (isUri) {
+ json_object_set_string_member(args, PARAM_FILENAME, target);
+ } else {
+ encodedFile = trg_base64encode(target);
+ if (encodedFile) {
+ json_object_set_string_member(args, PARAM_METAINFO,
+ encodedFile);
+ g_free(encodedFile);
+ } else {
+ g_error("unable to base64 encode file \"%s\".", target);
+ return NULL;
+ }
+ }
+
+ json_object_set_boolean_member(args, PARAM_PAUSED,
+ (flags & TORRENT_ADD_FLAG_PAUSED));
+
+ if ((flags & TORRENT_ADD_FLAG_DELETE))
+ g_unlink(target);
+
+ return root;
+}
+
+static JsonNode *base_request(gchar * method)
+{
+ JsonNode *root = json_node_new(JSON_NODE_OBJECT);
+ JsonObject *object = json_object_new();
+ JsonObject *args = json_object_new();
+
+ json_object_set_string_member(object, PARAM_METHOD, method);
+ json_object_set_object_member(object, PARAM_ARGUMENTS, args);
+ json_node_take_object(root, object);
+
+ return root;
+}
+
+void request_set_tag(JsonNode * req, gint64 tag)
+{
+ json_object_set_int_member(json_node_get_object(req), PARAM_TAG, tag);
+}
+
+void request_set_tag_from_ids(JsonNode * req, JsonArray * ids)
+{
+ gint64 id =
+ json_array_get_length(ids) == 1 ?
+ json_array_get_int_element(ids, 0) : TORRENT_GET_TAG_MODE_FULL;
+ request_set_tag(req, id);
+}
diff --git a/src/requests.h b/src/requests.h
new file mode 100644
index 0000000..5ea9b2a
--- /dev/null
+++ b/src/requests.h
@@ -0,0 +1,53 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef REQUESTS_H_
+#define REQUESTS_H_
+
+#include <glib-object.h>
+#include <json-glib/json-glib.h>
+
+JsonNode *generic_request(gchar * method, JsonArray * array);
+
+JsonNode *session_set(void);
+JsonNode *session_get(void);
+JsonNode *torrent_get(gint64 id);
+JsonNode *torrent_set(JsonArray * array);
+JsonNode *torrent_pause(JsonArray * array);
+JsonNode *torrent_start(JsonArray * array);
+JsonNode *torrent_verify(JsonArray * array);
+JsonNode *torrent_reannounce(JsonArray * array);
+JsonNode *torrent_remove(JsonArray * array, int removeData);
+JsonNode *torrent_add(gchar * filename, gint flags);
+JsonNode *torrent_add_url(const gchar * url, gboolean paused);
+JsonNode *torrent_set_location(JsonArray * array, gchar * location,
+ gboolean move);
+JsonNode *blocklist_update(void);
+JsonNode *port_test(void);
+JsonNode *session_stats(void);
+JsonNode *torrent_queue_move_down(JsonArray * array);
+JsonNode *torrent_queue_move_up(JsonArray * array);
+JsonNode *torrent_queue_move_bottom(JsonArray * array);
+JsonNode *torrent_queue_move_top(JsonArray * array);
+JsonNode *torrent_start_now(JsonArray * array);
+
+void request_set_tag(JsonNode * req, gint64 tag);
+void request_set_tag_from_ids(JsonNode * req, JsonArray * ids);
+
+#endif /* REQUESTS_H_ */
diff --git a/src/session-get.c b/src/session-get.c
new file mode 100644
index 0000000..98c466c
--- /dev/null
+++ b/src/session-get.c
@@ -0,0 +1,238 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+
+#include <json-glib/json-glib.h>
+
+#include "protocol-constants.h"
+#include "json.h"
+#include "session-get.h"
+
+/* Just some functions to get fields out of a session-get response. */
+
+const gchar *session_get_version_string(JsonObject * s)
+{
+ return json_object_get_string_member(s, SGET_VERSION);
+}
+
+gdouble session_get_version(JsonObject * s)
+{
+ const gchar *versionString = session_get_version_string(s);
+ gchar *spaceChar = g_strrstr(" ", versionString);
+ return g_ascii_strtod(versionString, &spaceChar);
+}
+
+gint64 session_get_download_dir_free_space(JsonObject * s)
+{
+ return json_object_get_int_member(s, SGET_DOWNLOAD_DIR_FREE_SPACE);
+}
+
+gint64 session_get_rpc_version(JsonObject * s)
+{
+ return json_object_get_int_member(s, SGET_RPC_VERSION);
+}
+
+gboolean session_get_pex_enabled(JsonObject * s)
+{
+ return json_object_get_boolean_member(s, SGET_PEX_ENABLED);
+}
+
+gboolean session_get_lpd_enabled(JsonObject * s)
+{
+ return json_object_get_boolean_member(s, SGET_LPD_ENABLED);
+}
+
+const gchar *session_get_download_dir(JsonObject * s)
+{
+ return json_object_get_string_member(s, SGET_DOWNLOAD_DIR);
+}
+
+gboolean session_get_peer_port_random(JsonObject * s)
+{
+ return json_object_get_boolean_member(s,
+ SGET_PEER_PORT_RANDOM_ON_START);
+}
+
+gint64 session_get_peer_port(JsonObject * s)
+{
+ return json_object_get_int_member(s, SGET_PEER_PORT);
+}
+
+gboolean session_get_port_forwarding_enabled(JsonObject * s)
+{
+ return json_object_get_boolean_member(s, SGET_PORT_FORWARDING_ENABLED);
+}
+
+const gchar *session_get_blocklist_url(JsonObject * s)
+{
+ if (json_object_has_member(s, SGET_BLOCKLIST_URL))
+ return json_object_get_string_member(s, SGET_BLOCKLIST_URL);
+ else
+ return NULL;
+}
+
+gint64 session_get_blocklist_size(JsonObject * s)
+{
+ return json_object_get_int_member(s, SGET_BLOCKLIST_SIZE);
+}
+
+gboolean session_get_blocklist_enabled(JsonObject * s)
+{
+ return json_object_get_boolean_member(s, SGET_BLOCKLIST_ENABLED);
+}
+
+gboolean session_get_rename_partial_files(JsonObject * s)
+{
+ return json_object_get_boolean_member(s, SGET_RENAME_PARTIAL_FILES);
+}
+
+const gchar *session_get_encryption(JsonObject * s)
+{
+ return json_object_get_string_member(s, SGET_ENCRYPTION);
+}
+
+const gchar *session_get_incomplete_dir(JsonObject * s)
+{
+ return json_object_get_string_member(s, SGET_INCOMPLETE_DIR);
+}
+
+gboolean session_get_incomplete_dir_enabled(JsonObject * s)
+{
+ return json_object_get_boolean_member(s, SGET_INCOMPLETE_DIR_ENABLED);
+}
+
+gboolean session_get_alt_speed_enabled(JsonObject * s)
+{
+ return json_object_get_boolean_member(s, SGET_ALT_SPEED_ENABLED);
+}
+
+gboolean session_get_seed_ratio_limited(JsonObject * s)
+{
+ return json_object_get_boolean_member(s, SGET_SEED_RATIO_LIMITED);
+}
+
+gboolean session_get_download_queue_enabled(JsonObject * s)
+{
+ return json_object_get_boolean_member(s, SGET_DOWNLOAD_QUEUE_ENABLED);
+}
+
+gint64 session_get_download_queue_size(JsonObject * s)
+{
+ return json_object_get_int_member(s, SGET_DOWNLOAD_QUEUE_SIZE);
+}
+
+gboolean session_get_seed_queue_enabled(JsonObject * s)
+{
+ return json_object_get_boolean_member(s, SGET_SEED_QUEUE_ENABLED);
+}
+
+gint64 session_get_seed_queue_size(JsonObject * s)
+{
+ return json_object_get_int_member(s, SGET_SEED_QUEUE_SIZE);
+}
+
+const gchar *session_get_torrent_done_filename(JsonObject * s)
+{
+ return json_object_get_string_member(s,
+ SGET_SCRIPT_TORRENT_DONE_FILENAME);
+}
+
+gboolean session_get_torrent_done_enabled(JsonObject * s)
+{
+ return json_object_get_boolean_member(s,
+ SGET_SCRIPT_TORRENT_DONE_ENABLED);
+}
+
+gint64 session_get_cache_size_mb(JsonObject * s)
+{
+ if (json_object_has_member(s, SGET_CACHE_SIZE_MB))
+ return json_object_get_int_member(s, SGET_CACHE_SIZE_MB);
+ else
+ return -1;
+}
+
+gdouble session_get_seed_ratio_limit(JsonObject * s)
+{
+ return
+ json_node_really_get_double(json_object_get_member
+ (s, SGET_SEED_RATIO_LIMIT));
+}
+
+gboolean session_get_start_added_torrents(JsonObject * s)
+{
+ return json_object_get_boolean_member(s, SGET_START_ADDED_TORRENTS);
+}
+
+gboolean session_get_trash_original_torrent_files(JsonObject * s)
+{
+ return json_object_get_boolean_member(s,
+ SGET_TRASH_ORIGINAL_TORRENT_FILES);
+}
+
+gboolean session_get_speed_limit_alt_enabled(JsonObject * s)
+{
+ return json_object_get_boolean_member(s, SGET_ALT_SPEED_ENABLED);
+}
+
+gboolean session_get_speed_limit_up_enabled(JsonObject * s)
+{
+ return json_object_get_boolean_member(s, SGET_SPEED_LIMIT_UP_ENABLED);
+}
+
+gint64 session_get_peer_limit_per_torrent(JsonObject * s)
+{
+ return json_object_get_int_member(s, SGET_PEER_LIMIT_PER_TORRENT);
+}
+
+gint64 session_get_peer_limit_global(JsonObject * s)
+{
+ return json_object_get_int_member(s, SGET_PEER_LIMIT_GLOBAL);
+}
+
+gint64 session_get_alt_speed_limit_up(JsonObject * s)
+{
+ return json_object_get_int_member(s, SGET_ALT_SPEED_UP);
+}
+
+gint64 session_get_speed_limit_up(JsonObject * s)
+{
+ return json_object_get_int_member(s, SGET_SPEED_LIMIT_UP);
+}
+
+gboolean session_get_speed_limit_down_enabled(JsonObject * s)
+{
+ return json_object_get_boolean_member(s,
+ SGET_SPEED_LIMIT_DOWN_ENABLED);
+}
+
+gint64 session_get_alt_speed_limit_down(JsonObject * s)
+{
+ return json_object_get_int_member(s, SGET_ALT_SPEED_DOWN);
+}
+
+gint64 session_get_speed_limit_down(JsonObject * s)
+{
+ return json_object_get_int_member(s, SGET_SPEED_LIMIT_DOWN);
+}
+
+gboolean session_get_dht_enabled(JsonObject * s)
+{
+ return json_object_get_boolean_member(s, SGET_DHT_ENABLED);
+}
diff --git a/src/session-get.h b/src/session-get.h
new file mode 100644
index 0000000..9d26c99
--- /dev/null
+++ b/src/session-get.h
@@ -0,0 +1,115 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef SESSION_GET_H_
+#define SESSION_GET_H_
+
+#include <glib-object.h>
+
+#define SGET_DOWNLOAD_DIR_FREE_SPACE "download-dir-free-space"
+#define SGET_BLOCKLIST_ENABLED "blocklist-enabled"
+#define SGET_BLOCKLIST_URL "blocklist-url"
+#define SGET_BLOCKLIST_SIZE "blocklist-size"
+#define SGET_DHT_ENABLED "dht-enabled"
+#define SGET_LPD_ENABLED "lpd-enabled"
+#define SGET_DOWNLOAD_DIR "download-dir"
+#define SGET_INCOMPLETE_DIR "incomplete-dir"
+#define SGET_INCOMPLETE_DIR_ENABLED "incomplete-dir-enabled"
+#define SGET_ENCRYPTION "encryption"
+#define SGET_PEER_LIMIT_GLOBAL "peer-limit-global"
+#define SGET_PEER_LIMIT_PER_TORRENT "peer-limit-per-torrent"
+#define SGET_PEER_PORT "peer-port"
+#define SGET_PEER_PORT_RANDOM_ON_START "peer-port-random-on-start"
+#define SGET_PEX_ENABLED "pex-enabled"
+#define SGET_PORT_FORWARDING_ENABLED "port-forwarding-enabled"
+#define SGET_RPC_VERSION "rpc-version"
+#define SGET_RPC_VERSION_MINIMUM "rpc-version-minimum"
+#define SGET_SEED_RATIO_LIMIT "seedRatioLimit"
+#define SGET_SEED_RATIO_LIMITED "seedRatioLimited"
+#define SGET_SPEED_LIMIT_DOWN "speed-limit-down"
+#define SGET_SPEED_LIMIT_DOWN_ENABLED "speed-limit-down-enabled"
+#define SGET_SPEED_LIMIT_UP "speed-limit-up"
+#define SGET_SPEED_LIMIT_UP_ENABLED "speed-limit-up-enabled"
+#define SGET_VERSION "version"
+#define SGET_RPC_VERSION "rpc-version"
+#define SGET_TRASH_ORIGINAL_TORRENT_FILES "trash-original-torrent-files"
+#define SGET_START_ADDED_TORRENTS "start-added-torrents"
+#define SGET_RENAME_PARTIAL_FILES "rename-partial-files"
+#define SGET_CACHE_SIZE_MB "cache-size-mb"
+#define SGET_SCRIPT_TORRENT_DONE_FILENAME "script-torrent-done-filename"
+#define SGET_SCRIPT_TORRENT_DONE_ENABLED "script-torrent-done-enabled"
+#define SGET_BLOCKLIST_URL "blocklist-url"
+#define SGET_BLOCKLIST_ENABLED "blocklist-enabled"
+#define SGET_BLOCKLIST_SIZE "blocklist-size"
+#define SGET_DOWNLOAD_QUEUE_ENABLED "download-queue-enabled"
+#define SGET_DOWNLOAD_QUEUE_SIZE "download-queue-size"
+#define SGET_SEED_QUEUE_ENABLED "seed-queue-enabled"
+#define SGET_SEED_QUEUE_SIZE "seed-queue-size"
+#define SGET_QUEUE_STALLED_ENABLED "queue-stalled-enabled"
+#define SGET_QUEUE_STALLED_MINUTES "queue-stalled-minutes"
+
+#define SGET_ALT_SPEED_DOWN "alt-speed-down"
+#define SGET_ALT_SPEED_ENABLED "alt-speed-enabled"
+#define SGET_ALT_SPEED_TIME_BEGIN "alt-speed-time-begin"
+#define SGET_ALT_SPEED_TIME_ENABLED "alt-speed-time-enabled"
+#define SGET_ALT_SPEED_TIME_END "alt-speed-time-end"
+#define SGET_ALT_SPEED_TIME_DAY "alt-speed-time-day"
+#define SGET_ALT_SPEED_UP "alt-speed-up"
+
+const gchar *session_get_torrent_done_filename(JsonObject * s);
+gboolean session_get_torrent_done_enabled(JsonObject * s);
+gint64 session_get_cache_size_mb(JsonObject * s);
+const gchar *session_get_version_string(JsonObject * s);
+gdouble session_get_version(JsonObject * s);
+gboolean session_get_pex_enabled(JsonObject * s);
+gboolean session_get_lpd_enabled(JsonObject * s);
+const gchar *session_get_download_dir(JsonObject * s);
+gboolean session_get_peer_port_random(JsonObject * s);
+gint64 session_get_peer_port(JsonObject * s);
+gint64 session_get_peer_limit_global(JsonObject * s);
+gint64 session_get_peer_limit_per_torrent(JsonObject * s);
+gboolean session_get_port_forwarding_enabled(JsonObject * s);
+const gchar *session_get_blocklist_url(JsonObject * s);
+gboolean session_get_blocklist_enabled(JsonObject * s);
+gint64 session_get_blocklist_size(JsonObject * s);
+gboolean session_get_rename_partial_files(JsonObject * s);
+const gchar *session_get_encryption(JsonObject * s);
+const gchar *session_get_incomplete_dir(JsonObject * s);
+gboolean session_get_incomplete_dir_enabled(JsonObject * s);
+gboolean session_get_seed_ratio_limited(JsonObject * s);
+gdouble session_get_seed_ratio_limit(JsonObject * s);
+gboolean session_get_start_added_torrents(JsonObject * s);
+gboolean session_get_trash_original_torrent_files(JsonObject * s);
+gboolean session_get_speed_limit_up_enabled(JsonObject * s);
+gboolean session_get_speed_limit_alt_enabled(JsonObject * s);
+gint64 session_get_speed_limit_up(JsonObject * s);
+gboolean session_get_speed_limit_down_enabled(JsonObject * s);
+gint64 session_get_speed_limit_down(JsonObject * s);
+gboolean session_get_download_queue_enabled(JsonObject * s);
+gint64 session_get_download_queue_size(JsonObject * s);
+gboolean session_get_seed_queue_enabled(JsonObject * s);
+gint64 session_get_seed_queue_size(JsonObject * s);
+gint64 session_get_rpc_version(JsonObject * s);
+gint64 session_get_download_dir_free_space(JsonObject * s);
+gboolean session_get_dht_enabled(JsonObject * s);
+gboolean session_get_alt_speed_enabled(JsonObject * s);
+gint64 session_get_alt_speed_limit_up(JsonObject * s);
+gint64 session_get_alt_speed_limit_down(JsonObject * s);
+
+#endif /* SESSION_GET_H_ */
diff --git a/src/torrent-cell-renderer.c b/src/torrent-cell-renderer.c
new file mode 100644
index 0000000..95f4d37
--- /dev/null
+++ b/src/torrent-cell-renderer.c
@@ -0,0 +1,1207 @@
+/*
+ * This file Copyright (C) Mnemosyne LLC
+ *
+ * This file is licensed by the GPL version 2. Works owned by the
+ * Transmission project are granted a special exemption to clause 2(b)
+ * so that the bulk of its code can remain under the MIT license.
+ * This exemption does not extend to derived works not owned by
+ * the Transmission project.
+ *
+ * $Id: torrent-cell-renderer.c 13388 2012-07-14 19:26:55Z jordan $
+ */
+
+/* This cell renderer has been modified heavily to work with the
+ * TrgTorrentModel instead of libtransmission. */
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <glib/gi18n.h>
+
+#include "hig.h"
+#include "icons.h"
+#include "trg-client.h"
+#include "torrent.h"
+#include "util.h"
+#include "torrent-cell-renderer.h"
+
+enum {
+ P_STATUS = 1,
+ P_CLIENT,
+ P_RATIO,
+ P_SEEDRATIOLIMIT,
+ P_SEEDRATIOMODE,
+ P_DOWNLOADED,
+ P_HAVEVALID,
+ P_HAVEUNCHECKED,
+ P_ERROR,
+ P_SIZEWHENDONE,
+ P_TOTALSIZE,
+ P_UPLOADED,
+ P_PERCENTCOMPLETE,
+ P_METADATAPERCENTCOMPLETE,
+ P_UPSPEED,
+ P_DOWNSPEED,
+ P_PEERSGETTINGFROMUS,
+ P_WEBSEEDSTOUS,
+ P_PEERSTOUS,
+ P_ETA,
+ P_JSON,
+ P_CONNECTED,
+ P_FILECOUNT,
+ P_BAR_HEIGHT,
+ P_OWNER,
+ P_COMPACT
+};
+
+#define DEFAULT_BAR_HEIGHT 12
+#define SMALL_SCALE 0.9
+#define COMPACT_ICON_SIZE GTK_ICON_SIZE_MENU
+#define FULL_ICON_SIZE GTK_ICON_SIZE_DND
+
+#if GTK_CHECK_VERSION( 3, 0, 0 )
+#define FOREGROUND_COLOR_KEY "foreground-rgba"
+typedef GdkRGBA GtrColor;
+typedef cairo_t GtrDrawable;
+typedef GtkRequisition GtrRequisition;
+#else
+#define FOREGROUND_COLOR_KEY "foreground-gdk"
+typedef GdkColor GtrColor;
+typedef GdkWindow GtrDrawable;
+typedef GdkRectangle GtrRequisition;
+#endif
+
+/***
+****
+***/
+
+static void
+render_compact(TorrentCellRenderer * cell,
+ GtrDrawable * window,
+ GtkWidget * widget,
+ const GdkRectangle * background_area,
+ const GdkRectangle * cell_area, GtkCellRendererState flags);
+
+static void
+render_full(TorrentCellRenderer * cell,
+ GtrDrawable * window,
+ GtkWidget * widget,
+ const GdkRectangle * background_area,
+ const GdkRectangle * cell_area, GtkCellRendererState flags);
+
+struct TorrentCellRendererPrivate {
+ GtkCellRenderer *text_renderer;
+ GtkCellRenderer *progress_renderer;
+ GtkCellRenderer *icon_renderer;
+ GString *gstr1;
+ GString *gstr2;
+ int bar_height;
+
+ guint flags;
+ guint fileCount;
+ gint64 uploadedEver;
+ gint64 sizeWhenDone;
+ gint64 totalSize;
+ gint64 downloaded;
+ gint64 haveValid;
+ gint64 haveUnchecked;
+ gint64 upSpeed;
+ gint64 downSpeed;
+ gint64 peersFromUs;
+ gint64 webSeedsToUs;
+ gint64 peersToUs;
+ gint64 connected;
+ gint64 eta;
+ gint64 error;
+ gint64 seedRatioMode;
+ gdouble done;
+ gdouble metadataPercentComplete;
+ gdouble ratio;
+ gdouble seedRatioLimit;
+ gpointer json;
+ TrgClient *client;
+ GtkTreeView *owner;
+ gboolean compact;
+};
+
+static gboolean getSeedRatio(TorrentCellRenderer * r, gdouble * ratio)
+{
+ struct TorrentCellRendererPrivate *p = r->priv;
+
+ if ((p->seedRatioMode == 0)
+ && (trg_client_get_seed_ratio_limited(p->client) == TRUE)) {
+ *ratio = trg_client_get_seed_ratio_limit(p->client);
+ return TRUE;
+ } else if (p->seedRatioMode == 1) {
+ *ratio = p->seedRatioLimit;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void getProgressString(GString * gstr, TorrentCellRenderer * r)
+{
+ struct TorrentCellRendererPrivate *p = r->priv;
+
+ const gint64 haveTotal = p->haveUnchecked + p->haveValid;
+ const int isSeed = p->haveValid >= p->totalSize;
+ char buf1[32], buf2[32], buf3[32], buf4[32], buf5[32], buf6[32];
+ double seedRatio;
+ const gboolean hasSeedRatio = getSeedRatio(r, &seedRatio);
+
+ if (p->flags & TORRENT_FLAG_DOWNLOADING) { /* downloading */
+ g_string_append_printf(gstr,
+ /* %1$s is how much we've got,
+ %2$s is how much we'll have when done,
+ %3$s%% is a percentage of the two */
+ _("%1$s of %2$s (%3$s)"),
+ tr_strlsize(buf1, haveTotal, sizeof(buf1)),
+ tr_strlsize(buf2, p->sizeWhenDone,
+ sizeof(buf2)),
+ tr_strlpercent(buf3, p->done,
+ sizeof(buf3)));
+ } else if (!isSeed) { /* Partial seed */
+ if (hasSeedRatio) {
+ g_string_append_printf(gstr,
+ _
+ ("%1$s of %2$s (%3$s), uploaded %4$s (Ratio: %5$s Goal: %6$s)"),
+ tr_strlsize(buf1, haveTotal,
+ sizeof(buf1)),
+ tr_strlsize(buf2, p->totalSize,
+ sizeof(buf2)),
+ tr_strlpercent(buf3, p->done,
+ sizeof(buf3)),
+ tr_strlsize(buf4, p->uploadedEver,
+ sizeof(buf4)),
+ tr_strlratio(buf5, p->ratio,
+ sizeof(buf5)),
+ tr_strlratio(buf6, seedRatio,
+ sizeof(buf6)));
+ } else {
+ g_string_append_printf(gstr,
+ _
+ ("%1$s of %2$s (%3$s), uploaded %4$s (Ratio: %5$s)"),
+ tr_strlsize(buf1, haveTotal,
+ sizeof(buf1)),
+ tr_strlsize(buf2, p->totalSize,
+ sizeof(buf2)),
+ tr_strlpercent(buf3, p->done,
+ sizeof(buf3)),
+ tr_strlsize(buf4, p->uploadedEver,
+ sizeof(buf4)),
+ tr_strlratio(buf5, p->ratio,
+ sizeof(buf5)));
+ }
+ } else { /* seeding */
+
+ if (hasSeedRatio) {
+ g_string_append_printf(gstr,
+ _
+ ("%1$s, uploaded %2$s (Ratio: %3$s Goal: %4$s)"),
+ tr_strlsize(buf1, p->totalSize,
+ sizeof(buf1)),
+ tr_strlsize(buf2, p->uploadedEver,
+ sizeof(buf2)),
+ tr_strlratio(buf3, p->ratio,
+ sizeof(buf3)),
+ tr_strlratio(buf4, seedRatio,
+ sizeof(buf4)));
+ } else {
+ g_string_append_printf(gstr,
+ /* %1$s is the torrent's total size,
+ %2$s is how much we've uploaded,
+ %3$s is our upload-to-download ratio */
+ _("%1$s, uploaded %2$s (Ratio: %3$s)"),
+ tr_strlsize(buf1, p->sizeWhenDone,
+ sizeof(buf1)),
+ tr_strlsize(buf2, p->uploadedEver,
+ sizeof(buf2)),
+ tr_strlratio(buf3, p->ratio,
+ sizeof(buf3)));
+ }
+ }
+
+ /* add time when downloading */
+ if ((p->flags & TORRENT_FLAG_DOWNLOADING)
+ || (hasSeedRatio && (p->flags & TORRENT_FLAG_SEEDING))) {
+ gint64 eta = p->eta;
+ g_string_append(gstr, " - ");
+ if (eta < 0)
+ g_string_append(gstr, _("Remaining time unknown"));
+ else {
+ char timestr[128];
+ tr_strltime_long(timestr, eta, sizeof(timestr));
+ /* time remaining */
+ g_string_append_printf(gstr, _("%s remaining"), timestr);
+ }
+ }
+}
+
+static char *getShortTransferString(TorrentCellRenderer * r,
+ char *buf, size_t buflen)
+{
+ struct TorrentCellRendererPrivate *priv = r->priv;
+
+ char downStr[32], upStr[32];
+ const gboolean haveMeta = priv->fileCount > 0;
+ const gboolean haveUp = haveMeta && priv->peersFromUs > 0;
+ const gboolean haveDown = haveMeta && priv->peersToUs > 0;
+
+ if (haveDown)
+ tr_formatter_speed_KBps(downStr, priv->downSpeed / speed_K,
+ sizeof(downStr));
+ if (haveUp)
+ tr_formatter_speed_KBps(upStr, priv->upSpeed / speed_K,
+ sizeof(upStr));
+
+ if (haveDown && haveUp)
+ /* 1==down arrow, 2==down speed, 3==up arrow, 4==down speed */
+ g_snprintf(buf, buflen, _("%1$s %2$s, %3$s %4$s"),
+ GTR_UNICODE_DOWN, downStr, GTR_UNICODE_UP, upStr);
+ else if (haveDown)
+ /* bandwidth speed + unicode arrow */
+ g_snprintf(buf, buflen, _("%1$s %2$s"), GTR_UNICODE_DOWN, downStr);
+ else if (haveUp)
+ /* bandwidth speed + unicode arrow */
+ g_snprintf(buf, buflen, _("%1$s %2$s"), GTR_UNICODE_UP, upStr);
+ /*else if( st->isStalled )
+ g_strlcpy( buf, _( "Stalled" ), buflen ); */
+ else if (haveMeta)
+ g_strlcpy(buf, _("Idle"), buflen);
+ else
+ *buf = '\0';
+
+ return buf;
+}
+
+static void getShortStatusString(GString * gstr, TorrentCellRenderer * r)
+{
+ struct TorrentCellRendererPrivate *priv = r->priv;
+ guint flags = priv->flags;
+
+ if (flags & TORRENT_FLAG_PAUSED) {
+ g_string_append(gstr,
+ (flags & TORRENT_FLAG_COMPLETE) ? _("Finished") :
+ _("Paused"));
+ } else if (flags & TORRENT_FLAG_WAITING_CHECK) {
+ g_string_append(gstr, _("Queued for verification"));
+ } else if (flags & TORRENT_FLAG_DOWNLOADING_WAIT) {
+ g_string_append(gstr, _("Queued for download"));
+ } else if (flags & TORRENT_FLAG_SEEDING_WAIT) {
+ g_string_append(gstr, _("Queued for seeding"));
+ } else if (flags & TORRENT_FLAG_CHECKING) {
+ char buf1[32];
+ g_string_append_printf(gstr, _("Verifying data (%1$s tested)"),
+ tr_strlpercent(buf1, priv->done,
+ sizeof(buf1)));
+ } else if ((flags & TORRENT_FLAG_DOWNLOADING)
+ || (flags & TORRENT_FLAG_SEEDING)) {
+ char buf[512];
+ if (flags & ~TORRENT_FLAG_DOWNLOADING) {
+ tr_strlratio(buf, priv->ratio, sizeof(buf));
+ g_string_append_printf(gstr, _("Ratio %s"), buf);
+ g_string_append(gstr, ", ");
+ }
+ getShortTransferString(r, buf, sizeof(buf));
+ g_string_append(gstr, buf);
+ }
+}
+
+static void getStatusString(GString * gstr, TorrentCellRenderer * r)
+{
+ struct TorrentCellRendererPrivate *priv = r->priv;
+ char buf[256];
+
+ if (priv->error) {
+ const char *fmt[] = { NULL, N_("Tracker gave a warning: \"%s\""),
+ N_("Tracker gave an error: \"%s\""),
+ N_("Error: %s")
+ };
+ g_string_append_printf(gstr, _(fmt[priv->error]),
+ torrent_get_errorstr(priv->json));
+ } else if ((priv->flags & TORRENT_FLAG_PAUSED)
+ || (priv->flags & TORRENT_FLAG_WAITING_CHECK)
+ || (priv->flags & TORRENT_FLAG_CHECKING)
+ || (priv->flags & TORRENT_FLAG_DOWNLOADING_WAIT)
+ || (priv->flags & TORRENT_FLAG_SEEDING_WAIT)) {
+ getShortStatusString(gstr, r);
+ } else if (priv->flags & TORRENT_FLAG_DOWNLOADING) {
+ if (priv->fileCount > 0) {
+ g_string_append_printf(gstr,
+ ngettext
+ ("Downloading from %1$li of %2$li connected peer",
+ "Downloading from %1$li of %2$li connected peers",
+ priv->webSeedsToUs + priv->peersToUs),
+ priv->webSeedsToUs + priv->peersToUs,
+ priv->webSeedsToUs + priv->connected);
+ } else {
+ g_string_append_printf(gstr,
+ ngettext
+ ("Downloading metadata from %1$li peer (%2$s done)",
+ "Downloading metadata from %1$li peers (%2$s done)",
+ priv->connected + priv->webSeedsToUs),
+ priv->connected + priv->webSeedsToUs,
+ tr_strlpercent(buf,
+ priv->metadataPercentComplete,
+ sizeof(buf)));
+ }
+ } else if (priv->flags & TORRENT_FLAG_SEEDING) {
+ g_string_append_printf(gstr,
+ ngettext
+ ("Seeding to %1$li of %2$li connected peer",
+ "Seeding to %1$li of %2$li connected peers",
+ priv->connected), priv->peersFromUs,
+ priv->connected);
+ }
+
+ if ((priv->flags & ~TORRENT_FLAG_WAITING_CHECK) &&
+ (priv->flags & ~TORRENT_FLAG_CHECKING) &&
+ (priv->flags & ~TORRENT_FLAG_DOWNLOADING_WAIT) &&
+ (priv->flags & ~TORRENT_FLAG_SEEDING_WAIT) &&
+ (priv->flags & ~TORRENT_FLAG_PAUSED)) {
+ getShortTransferString(r, buf, sizeof(buf));
+ if (*buf)
+ g_string_append_printf(gstr, " - %s", buf);
+ }
+}
+
+/***
+****
+***/
+
+static GdkPixbuf *get_icon(TorrentCellRenderer * r, GtkIconSize icon_size,
+ GtkWidget * for_widget)
+{
+ struct TorrentCellRendererPrivate *p = r->priv;
+
+ const char *mime_type;
+
+ if (p->fileCount == 0)
+ mime_type = UNKNOWN_MIME_TYPE;
+ else if (p->fileCount > 1)
+ mime_type = DIRECTORY_MIME_TYPE;
+ /*else if( strchr( info->files[0].name, '/' ) != NULL )
+ mime_type = DIRECTORY_MIME_TYPE;
+ else
+ mime_type = gtr_get_mime_type_from_filename( info->files[0].name ); */
+ else
+ mime_type = FILE_MIME_TYPE;
+
+ //return NULL;
+ return gtr_get_mime_type_icon(mime_type, icon_size, for_widget);
+}
+
+/***
+****
+***/
+
+static void
+gtr_cell_renderer_get_preferred_size(GtkCellRenderer * renderer,
+ GtkWidget * widget,
+ GtkRequisition * minimum_size,
+ GtkRequisition * natural_size)
+{
+#if GTK_CHECK_VERSION( 3, 0, 0 )
+ gtk_cell_renderer_get_preferred_size(renderer, widget, minimum_size,
+ natural_size);
+#else
+ GtkRequisition r;
+ gtk_cell_renderer_get_size(renderer, widget, NULL, NULL, NULL,
+ &r.width, &r.height);
+ if (minimum_size)
+ *minimum_size = r;
+ if (natural_size)
+ *natural_size = r;
+#endif
+}
+
+static void
+get_size_compact(TorrentCellRenderer * cell,
+ GtkWidget * widget, gint * width, gint * height)
+{
+ int xpad, ypad;
+ GtkRequisition icon_size;
+ GtkRequisition name_size;
+ GtkRequisition stat_size;
+ GdkPixbuf *icon;
+
+ struct TorrentCellRendererPrivate *p = cell->priv;
+ GString *gstr_stat = p->gstr1;
+
+ icon = get_icon(cell, COMPACT_ICON_SIZE, widget);
+ g_string_truncate(gstr_stat, 0);
+ getShortStatusString(gstr_stat, cell);
+ gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
+
+ /* get the idealized cell dimensions */
+ g_object_set(p->icon_renderer, "pixbuf", icon, NULL);
+ gtr_cell_renderer_get_preferred_size(p->icon_renderer, widget, NULL,
+ &icon_size);
+ g_object_set(p->text_renderer, "text", torrent_get_name(p->json),
+ "ellipsize", PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL);
+ gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &name_size);
+ g_object_set(p->text_renderer, "text", gstr_stat->str, "scale",
+ SMALL_SCALE, NULL);
+ gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &stat_size);
+
+ /**
+ *** LAYOUT
+ **/
+
+#define BAR_WIDTH 50
+ if (width != NULL)
+ *width =
+ xpad * 2 + icon_size.width + GUI_PAD + name_size.width +
+ GUI_PAD + BAR_WIDTH + GUI_PAD + stat_size.width;
+ if (height != NULL)
+ *height = ypad * 2 + MAX(name_size.height, p->bar_height);
+
+ /* cleanup */
+ g_object_unref(icon);
+}
+
+#define MAX3(a,b,c) MAX(a,MAX(b,c))
+
+static void
+get_size_full(TorrentCellRenderer * cell,
+ GtkWidget * widget, gint * width, gint * height)
+{
+ int xpad, ypad;
+ GtkRequisition icon_size;
+ GtkRequisition name_size;
+ GtkRequisition stat_size;
+ GtkRequisition prog_size;
+ GdkPixbuf *icon;
+
+ struct TorrentCellRendererPrivate *p = cell->priv;
+ GString *gstr_prog = p->gstr1;
+ GString *gstr_stat = p->gstr2;
+
+ icon = get_icon(cell, FULL_ICON_SIZE, widget);
+
+ g_string_truncate(gstr_stat, 0);
+ getStatusString(gstr_stat, cell);
+ g_string_truncate(gstr_prog, 0);
+ getProgressString(gstr_prog, cell);
+ gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
+
+ /* get the idealized cell dimensions */
+ g_object_set(p->icon_renderer, "pixbuf", icon, NULL);
+ gtr_cell_renderer_get_preferred_size(p->icon_renderer, widget, NULL,
+ &icon_size);
+ g_object_set(p->text_renderer, "text", torrent_get_name(p->json),
+ "weight", PANGO_WEIGHT_BOLD, "scale", 1.0, "ellipsize",
+ PANGO_ELLIPSIZE_NONE, NULL);
+ gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &name_size);
+ g_object_set(p->text_renderer, "text", gstr_prog->str, "weight",
+ PANGO_WEIGHT_NORMAL, "scale", SMALL_SCALE, NULL);
+ gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &prog_size);
+ g_object_set(p->text_renderer, "text", gstr_stat->str, NULL);
+ gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &stat_size);
+
+ /**
+ *** LAYOUT
+ **/
+
+ if (width != NULL)
+ *width =
+ xpad * 2 + icon_size.width + GUI_PAD + MAX3(name_size.width,
+ prog_size.width,
+ stat_size.width);
+ if (height != NULL)
+ *height =
+ ypad * 2 + name_size.height + prog_size.height +
+ GUI_PAD_SMALL + p->bar_height + GUI_PAD_SMALL +
+ stat_size.height;
+
+ /* cleanup */
+ g_object_unref(icon);
+}
+
+
+static void
+torrent_cell_renderer_get_size(GtkCellRenderer * cell, GtkWidget * widget,
+#if GTK_CHECK_VERSION( 3,0,0 )
+ const GdkRectangle * cell_area,
+#else
+ GdkRectangle * cell_area,
+#endif
+ gint * x_offset,
+ gint * y_offset,
+ gint * width, gint * height)
+{
+ TorrentCellRenderer *self = TORRENT_CELL_RENDERER(cell);
+
+ if (self) {
+ int w, h;
+ struct TorrentCellRendererPrivate *p = self->priv;
+
+ if (p->compact)
+ get_size_compact(TORRENT_CELL_RENDERER(cell), widget, &w, &h);
+ else
+ get_size_full(TORRENT_CELL_RENDERER(cell), widget, &w, &h);
+
+ if (width)
+ *width = w;
+
+ if (height)
+ *height = h;
+
+ if (x_offset)
+ *x_offset = cell_area ? cell_area->x : 0;
+
+ if (y_offset) {
+ int xpad, ypad;
+ gtk_cell_renderer_get_padding(cell, &xpad, &ypad);
+ *y_offset =
+ cell_area ? (int) ((cell_area->height - (ypad * 2 + h)) /
+ 2.0) : 0;
+ }
+ }
+}
+
+static void
+get_text_color(TorrentCellRenderer * r, GtkWidget * widget,
+ GtrColor * setme)
+{
+ struct TorrentCellRendererPrivate *p = r->priv;
+#if GTK_CHECK_VERSION( 3,0,0 )
+ static const GdkRGBA red = { 1.0, 0, 0, 0 };
+
+ if (p->error)
+ *setme = red;
+ else if (p->flags & TORRENT_FLAG_PAUSED)
+ gtk_style_context_get_color(gtk_widget_get_style_context(widget),
+ GTK_STATE_FLAG_INSENSITIVE, setme);
+ else
+ gtk_style_context_get_color(gtk_widget_get_style_context(widget),
+ GTK_STATE_FLAG_NORMAL, setme);
+#else
+ static const GdkColor red = { 0, 65535, 0, 0 };
+
+ if (p->error)
+ *setme = red;
+ else if (p->flags & TORRENT_FLAG_PAUSED)
+ *setme = gtk_widget_get_style(widget)->text[GTK_STATE_INSENSITIVE];
+ else
+ *setme = gtk_widget_get_style(widget)->text[GTK_STATE_NORMAL];
+#endif
+}
+
+static double get_percent_done(TorrentCellRenderer * r, gboolean * seed)
+{
+ struct TorrentCellRendererPrivate *priv = r->priv;
+ gdouble d;
+
+ if ((priv->flags & TORRENT_FLAG_SEEDING) && getSeedRatio(r, &d)) {
+ *seed = TRUE;
+ const gint64 baseline =
+ priv->downloaded ? priv->downloaded : priv->sizeWhenDone;
+ const gint64 goal = baseline * priv->seedRatioLimit;
+ const gint64 bytesLeft =
+ goal > priv->uploadedEver ? goal - priv->uploadedEver : 0;
+ float seedRatioPercentDone = (gdouble) (goal - bytesLeft) / goal;
+
+ d = MAX(0.0, seedRatioPercentDone * 100.0);
+ } else {
+ *seed = FALSE;
+ d = MAX(0.0, priv->done);
+ }
+
+ return d;
+}
+
+static void
+gtr_cell_renderer_render(GtkCellRenderer * renderer,
+ GtrDrawable * drawable,
+ GtkWidget * widget,
+ const GdkRectangle * area,
+ GtkCellRendererState flags)
+{
+#if GTK_CHECK_VERSION( 3, 0, 0 )
+ gtk_cell_renderer_render(renderer, drawable, widget, area, area,
+ flags);
+#else
+ gtk_cell_renderer_render(renderer, drawable, widget, area, area, area,
+ flags);
+#endif
+}
+
+static void
+torrent_cell_renderer_render(GtkCellRenderer * cell,
+ GtrDrawable * window, GtkWidget * widget,
+#if GTK_CHECK_VERSION( 3,0,0 )
+ const GdkRectangle * background_area,
+ const GdkRectangle * cell_area,
+#else
+ GdkRectangle * background_area,
+ GdkRectangle * cell_area,
+ GdkRectangle * expose_area,
+#endif
+ GtkCellRendererState flags)
+{
+ TorrentCellRenderer *self = TORRENT_CELL_RENDERER(cell);
+
+ if (self) {
+ struct TorrentCellRendererPrivate *p = self->priv;
+ if (p->compact)
+ render_compact(self, window, widget, background_area,
+ cell_area, flags);
+ else
+ render_full(self, window, widget, background_area, cell_area,
+ flags);
+ }
+}
+
+static void torrent_cell_renderer_set_property(GObject * object,
+ guint property_id,
+ const GValue * v,
+ GParamSpec * pspec)
+{
+ TorrentCellRenderer *self = TORRENT_CELL_RENDERER(object);
+ struct TorrentCellRendererPrivate *p = self->priv;
+
+ switch (property_id) {
+ case P_JSON:
+ p->json = g_value_get_pointer(v);
+ break;
+ case P_STATUS:
+ p->flags = g_value_get_uint(v);
+ break;
+ case P_TOTALSIZE:
+ p->totalSize = g_value_get_int64(v);
+ break;
+ case P_SIZEWHENDONE:
+ p->sizeWhenDone = g_value_get_int64(v);
+ break;
+ case P_DOWNLOADED:
+ p->downloaded = g_value_get_int64(v);
+ break;
+ case P_HAVEVALID:
+ p->haveValid = g_value_get_int64(v);
+ break;
+ case P_HAVEUNCHECKED:
+ p->haveUnchecked = g_value_get_int64(v);
+ break;
+ case P_UPLOADED:
+ p->uploadedEver = g_value_get_int64(v);
+ break;
+ case P_UPSPEED:
+ p->upSpeed = g_value_get_int64(v);
+ break;
+ case P_DOWNSPEED:
+ p->downSpeed = g_value_get_int64(v);
+ break;
+ case P_PEERSGETTINGFROMUS:
+ p->peersFromUs = g_value_get_int64(v);
+ break;
+ case P_WEBSEEDSTOUS:
+ p->webSeedsToUs = g_value_get_int64(v);
+ break;
+ case P_CONNECTED:
+ p->connected = g_value_get_int64(v);
+ break;
+ case P_FILECOUNT:
+ p->fileCount = g_value_get_uint(v);
+ break;
+ case P_ETA:
+ p->eta = g_value_get_int64(v);
+ break;
+ case P_PEERSTOUS:
+ p->peersToUs = g_value_get_int64(v);
+ break;
+ case P_ERROR:
+ p->error = g_value_get_int64(v);
+ break;
+ case P_RATIO:
+ p->ratio = g_value_get_double(v);
+ break;
+ case P_PERCENTCOMPLETE:
+ p->done = g_value_get_double(v);
+ break;
+ case P_METADATAPERCENTCOMPLETE:
+ p->metadataPercentComplete = g_value_get_double(v);
+ break;
+ case P_BAR_HEIGHT:
+ p->bar_height = g_value_get_int(v);
+ break;
+ case P_COMPACT:
+ p->compact = g_value_get_boolean(v);
+ break;
+ case P_SEEDRATIOMODE:
+ p->seedRatioMode = g_value_get_int64(v);
+ break;
+ case P_SEEDRATIOLIMIT:
+ p->seedRatioLimit = g_value_get_double(v);
+ break;
+ case P_CLIENT:
+ p->client = g_value_get_pointer(v);
+ break;
+ case P_OWNER:
+ p->owner = g_value_get_pointer(v);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+torrent_cell_renderer_get_property(GObject * object,
+ guint property_id,
+ GValue * v, GParamSpec * pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+}
+
+G_DEFINE_TYPE(TorrentCellRenderer, torrent_cell_renderer,
+ GTK_TYPE_CELL_RENDERER)
+
+static void torrent_cell_renderer_dispose(GObject * o)
+{
+ TorrentCellRenderer *r = TORRENT_CELL_RENDERER(o);
+
+ if (r && r->priv) {
+ g_string_free(r->priv->gstr1, TRUE);
+ g_string_free(r->priv->gstr2, TRUE);
+ g_object_unref(G_OBJECT(r->priv->text_renderer));
+ g_object_unref(G_OBJECT(r->priv->progress_renderer));
+ g_object_unref(G_OBJECT(r->priv->icon_renderer));
+ r->priv = NULL;
+ }
+
+ G_OBJECT_CLASS(torrent_cell_renderer_parent_class)->dispose(o);
+}
+
+static void
+torrent_cell_renderer_class_init(TorrentCellRendererClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+ GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS(klass);
+
+ g_type_class_add_private(klass,
+ sizeof(struct TorrentCellRendererPrivate));
+
+ cell_class->render = torrent_cell_renderer_render;
+ cell_class->get_size = torrent_cell_renderer_get_size;
+ gobject_class->set_property = torrent_cell_renderer_set_property;
+ gobject_class->get_property = torrent_cell_renderer_get_property;
+ gobject_class->dispose = torrent_cell_renderer_dispose;
+
+ g_object_class_install_property(gobject_class, P_JSON,
+ g_param_spec_pointer("json", NULL,
+ "json",
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_CLIENT,
+ g_param_spec_pointer("client", NULL,
+ "client",
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_OWNER,
+ g_param_spec_pointer("owner", NULL,
+ "owner",
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_RATIO,
+ g_param_spec_double("ratio", NULL,
+ "ratio",
+ 0, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_SEEDRATIOLIMIT,
+ g_param_spec_double("seedRatioLimit",
+ NULL,
+ "seedRatioLimit",
+ 0, G_MAXDOUBLE, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_SEEDRATIOMODE,
+ g_param_spec_int64("seedRatioMode",
+ NULL,
+ "seedRatioMode", 0,
+ 2, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_PERCENTCOMPLETE,
+ g_param_spec_double("percentComplete",
+ NULL,
+ "percentComplete",
+ 0, 100.00, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class,
+ P_METADATAPERCENTCOMPLETE,
+ g_param_spec_double
+ ("metadataPercentComplete", NULL,
+ "metadataPercentComplete", 0, 100.00,
+ 0, G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_TOTALSIZE,
+ g_param_spec_int64("totalSize", NULL,
+ "totalSize",
+ 0, G_MAXINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_SIZEWHENDONE,
+ g_param_spec_int64("sizeWhenDone",
+ NULL,
+ "sizeWhenDone", 0,
+ G_MAXINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_STATUS,
+ g_param_spec_uint("status", NULL,
+ "status",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_FILECOUNT,
+ g_param_spec_uint("fileCount", NULL,
+ "fileCount",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_ERROR,
+ g_param_spec_int64("error", NULL,
+ "error",
+ 0, G_MAXINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_UPSPEED,
+ g_param_spec_int64("upSpeed", NULL,
+ "upSpeed",
+ 0, G_MAXINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_DOWNSPEED,
+ g_param_spec_int64("downSpeed", NULL,
+ "downSpeed",
+ 0, G_MAXINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_DOWNLOADED,
+ g_param_spec_int64("downloaded", NULL,
+ "downloaded",
+ 0, G_MAXINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_HAVEVALID,
+ g_param_spec_int64("haveValid", NULL,
+ "haveValid",
+ 0, G_MAXINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_HAVEUNCHECKED,
+ g_param_spec_int64("haveUnchecked",
+ NULL,
+ "haveUnchecked", 0,
+ G_MAXINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_UPLOADED,
+ g_param_spec_int64("uploaded", NULL,
+ "uploaded",
+ 0, G_MAXINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_PEERSGETTINGFROMUS,
+ g_param_spec_int64
+ ("peersGettingFromUs", NULL,
+ "peersGettingFromUs", -1, G_MAXINT64,
+ 0, G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_PEERSTOUS,
+ g_param_spec_int64("peersToUs", NULL,
+ "peersToUs",
+ -1, G_MAXINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_WEBSEEDSTOUS,
+ g_param_spec_int64("webSeedsToUs",
+ NULL,
+ "webSeedsToUs", 0,
+ G_MAXINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_ETA,
+ g_param_spec_int64("eta", NULL,
+ "eta",
+ -2, G_MAXINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_CONNECTED,
+ g_param_spec_int64("connected", NULL,
+ "connected",
+ 0, G_MAXINT64, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_BAR_HEIGHT,
+ g_param_spec_int("bar-height", NULL,
+ "Bar Height",
+ 1, INT_MAX,
+ DEFAULT_BAR_HEIGHT,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(gobject_class, P_COMPACT,
+ g_param_spec_boolean("compact", NULL,
+ "Compact Mode",
+ FALSE,
+ G_PARAM_READWRITE));
+}
+
+static void torrent_cell_renderer_init(TorrentCellRenderer * self)
+{
+ struct TorrentCellRendererPrivate *p;
+
+ p = self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self,
+ TORRENT_CELL_RENDERER_TYPE,
+ struct
+ TorrentCellRendererPrivate);
+
+ p->gstr1 = g_string_new(NULL);
+ p->gstr2 = g_string_new(NULL);
+ p->text_renderer = gtk_cell_renderer_text_new();
+ g_object_set(p->text_renderer, "xpad", 0, "ypad", 0, NULL);
+ p->progress_renderer = gtk_cell_renderer_progress_new();
+ p->icon_renderer = gtk_cell_renderer_pixbuf_new();
+ g_object_ref_sink(p->text_renderer);
+ g_object_ref_sink(p->progress_renderer);
+ g_object_ref_sink(p->icon_renderer);
+
+ p->bar_height = DEFAULT_BAR_HEIGHT;
+}
+
+
+GtkCellRenderer *torrent_cell_renderer_new(void)
+{
+ return (GtkCellRenderer *) g_object_new(TORRENT_CELL_RENDERER_TYPE,
+ NULL);
+}
+
+static void
+render_compact(TorrentCellRenderer * cell,
+ GtrDrawable * window,
+ GtkWidget * widget,
+ const GdkRectangle * background_area,
+ const GdkRectangle * cell_area, GtkCellRendererState flags)
+{
+ int xpad, ypad;
+ GtkRequisition size;
+ GdkRectangle icon_area;
+ GdkRectangle name_area;
+ GdkRectangle stat_area;
+ GdkRectangle prog_area;
+ GdkRectangle fill_area;
+ GdkPixbuf *icon;
+ GtrColor text_color;
+ gboolean seed;
+
+ struct TorrentCellRendererPrivate *p = cell->priv;
+ const gboolean active = (p->flags & ~TORRENT_FLAG_PAUSED)
+ && (p->flags & ~TORRENT_FLAG_DOWNLOADING_WAIT)
+ && (p->flags & ~TORRENT_FLAG_SEEDING_WAIT);
+ const double percentDone = get_percent_done(cell, &seed);
+ const gboolean sensitive = active || p->error;
+ GString *gstr_stat = p->gstr1;
+
+ icon = get_icon(cell, COMPACT_ICON_SIZE, widget);
+
+ g_string_truncate(gstr_stat, 0);
+ getShortStatusString(gstr_stat, cell);
+ gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
+ get_text_color(cell, widget, &text_color);
+
+ fill_area = *background_area;
+ fill_area.x += xpad;
+ fill_area.y += ypad;
+ fill_area.width -= xpad * 2;
+ fill_area.height -= ypad * 2;
+ icon_area = name_area = stat_area = prog_area = fill_area;
+
+ g_object_set(p->icon_renderer, "pixbuf", icon, NULL);
+ gtr_cell_renderer_get_preferred_size(p->icon_renderer, widget, NULL,
+ &size);
+ icon_area.width = size.width;
+ g_object_set(p->text_renderer, "text", torrent_get_name(p->json),
+ "ellipsize", PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL);
+ gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &size);
+ name_area.width = size.width;
+ g_object_set(p->text_renderer, "text", gstr_stat->str, "scale",
+ SMALL_SCALE, NULL);
+ gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &size);
+ stat_area.width = size.width;
+
+ icon_area.x = fill_area.x;
+ prog_area.x = fill_area.x + fill_area.width - BAR_WIDTH;
+ prog_area.width = BAR_WIDTH;
+ stat_area.x = prog_area.x - GUI_PAD - stat_area.width;
+ name_area.x = icon_area.x + icon_area.width + GUI_PAD;
+ name_area.y = fill_area.y;
+ name_area.width = stat_area.x - GUI_PAD - name_area.x;
+
+ /**
+ *** RENDER
+ **/
+
+ g_object_set(p->icon_renderer, "pixbuf", icon, "sensitive", sensitive,
+ NULL);
+ gtr_cell_renderer_render(p->icon_renderer, window, widget, &icon_area,
+ flags);
+ g_object_set(p->progress_renderer, "value", (gint) percentDone, "text",
+ NULL, "sensitive", sensitive, NULL);
+ gtr_cell_renderer_render(p->progress_renderer, window, widget,
+ &prog_area, flags);
+ g_object_set(p->text_renderer, "text", gstr_stat->str, "scale",
+ SMALL_SCALE, "ellipsize", PANGO_ELLIPSIZE_END,
+ FOREGROUND_COLOR_KEY, &text_color, NULL);
+ gtr_cell_renderer_render(p->text_renderer, window, widget, &stat_area,
+ flags);
+ g_object_set(p->text_renderer, "text", torrent_get_name(p->json),
+ "scale", 1.0, FOREGROUND_COLOR_KEY, &text_color, NULL);
+ gtr_cell_renderer_render(p->text_renderer, window, widget, &name_area,
+ flags);
+
+ /* cleanup */
+ g_object_unref(icon);
+}
+
+static void
+render_full(TorrentCellRenderer * cell,
+ GtrDrawable * window,
+ GtkWidget * widget,
+ const GdkRectangle * background_area,
+ const GdkRectangle * cell_area, GtkCellRendererState flags)
+{
+ int xpad, ypad;
+ GtkRequisition size;
+ GdkRectangle fill_area;
+ GdkRectangle icon_area;
+ GdkRectangle name_area;
+ GdkRectangle stat_area;
+ GdkRectangle prog_area;
+ GdkRectangle prct_area;
+ GdkPixbuf *icon;
+ GtrColor text_color;
+ gboolean seed;
+
+ struct TorrentCellRendererPrivate *p = cell->priv;
+ const gboolean active = (p->flags & ~TORRENT_FLAG_PAUSED)
+ && (p->flags & ~TORRENT_FLAG_DOWNLOADING_WAIT)
+ && (p->flags & ~TORRENT_FLAG_SEEDING_WAIT);
+ const gboolean sensitive = active || p->error;
+ const double percentDone = get_percent_done(cell, &seed);
+ GString *gstr_prog = p->gstr1;
+ GString *gstr_stat = p->gstr2;
+
+ icon = get_icon(cell, FULL_ICON_SIZE, widget);
+ g_string_truncate(gstr_prog, 0);
+ getProgressString(gstr_prog, cell);
+ g_string_truncate(gstr_stat, 0);
+ getStatusString(gstr_stat, cell);
+ gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
+ get_text_color(cell, widget, &text_color);
+
+ /* get the idealized cell dimensions */
+ g_object_set(p->icon_renderer, "pixbuf", icon, NULL);
+ gtr_cell_renderer_get_preferred_size(p->icon_renderer, widget, NULL,
+ &size);
+ icon_area.width = size.width;
+ icon_area.height = size.height;
+ g_object_set(p->text_renderer, "text", torrent_get_name(p->json),
+ "weight", PANGO_WEIGHT_BOLD, "ellipsize",
+ PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL);
+ gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &size);
+ name_area.width = size.width;
+ name_area.height = size.height;
+ g_object_set(p->text_renderer, "text", gstr_prog->str, "weight",
+ PANGO_WEIGHT_NORMAL, "scale", SMALL_SCALE, NULL);
+ gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &size);
+ prog_area.width = size.width;
+ prog_area.height = size.height;
+ g_object_set(p->text_renderer, "text", gstr_stat->str, NULL);
+ gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &size);
+ stat_area.width = size.width;
+ stat_area.height = size.height;
+
+ /**
+ *** LAYOUT
+ **/
+
+ fill_area = *background_area;
+ fill_area.x += xpad;
+ fill_area.y += ypad;
+ fill_area.width -= xpad * 2;
+ fill_area.height -= ypad * 2;
+
+ /* icon */
+ icon_area.x = fill_area.x;
+ icon_area.y = fill_area.y + (fill_area.height - icon_area.height) / 2;
+
+ /* name */
+ name_area.x = icon_area.x + icon_area.width + GUI_PAD;
+ name_area.y = fill_area.y;
+ name_area.width =
+ fill_area.width - GUI_PAD - icon_area.width - GUI_PAD_SMALL;
+
+ /* prog */
+ prog_area.x = name_area.x;
+ prog_area.y = name_area.y + name_area.height;
+ prog_area.width = name_area.width;
+
+ /* progressbar */
+ prct_area.x = prog_area.x;
+ prct_area.y = prog_area.y + prog_area.height + GUI_PAD_SMALL;
+ prct_area.width = prog_area.width;
+ prct_area.height = p->bar_height;
+
+ /* status */
+ stat_area.x = prct_area.x;
+ stat_area.y = prct_area.y + prct_area.height + GUI_PAD_SMALL;
+ stat_area.width = prct_area.width;
+
+ /**
+ *** RENDER
+ **/
+
+ g_object_set(p->icon_renderer, "pixbuf", icon, "sensitive", sensitive,
+ NULL);
+ gtr_cell_renderer_render(p->icon_renderer, window, widget, &icon_area,
+ flags);
+ g_object_set(p->text_renderer, "text", torrent_get_name(p->json),
+ "scale", 1.0, FOREGROUND_COLOR_KEY, &text_color,
+ "ellipsize", PANGO_ELLIPSIZE_END, "weight",
+ PANGO_WEIGHT_BOLD, NULL);
+ gtr_cell_renderer_render(p->text_renderer, window, widget, &name_area,
+ flags);
+ g_object_set(p->text_renderer, "text", gstr_prog->str, "scale",
+ SMALL_SCALE, "weight", PANGO_WEIGHT_NORMAL, NULL);
+ gtr_cell_renderer_render(p->text_renderer, window, widget, &prog_area,
+ flags);
+ g_object_set(p->progress_renderer, "value", (gint) percentDone,
+ "text", "", "sensitive", sensitive, NULL);
+ gtr_cell_renderer_render(p->progress_renderer, window, widget,
+ &prct_area, flags);
+ g_object_set(p->text_renderer, "text", gstr_stat->str,
+ FOREGROUND_COLOR_KEY, &text_color, NULL);
+ gtr_cell_renderer_render(p->text_renderer, window, widget, &stat_area,
+ flags);
+
+ /* cleanup */
+ g_object_unref(icon);
+}
+
+GtkTreeView *torrent_cell_renderer_get_owner(TorrentCellRenderer * r)
+{
+ return r->priv->owner;
+}
diff --git a/src/torrent-cell-renderer.h b/src/torrent-cell-renderer.h
new file mode 100644
index 0000000..c6bb7ad
--- /dev/null
+++ b/src/torrent-cell-renderer.h
@@ -0,0 +1,51 @@
+/*
+ * This file Copyright (C) Mnemosyne LLC
+ *
+ * This file is licensed by the GPL version 2. Works owned by the
+ * Transmission project are granted a special exemption to clause 2(b)
+ * so that the bulk of its code can remain under the MIT license.
+ * This exemption does not extend to derived works not owned by
+ * the Transmission project.
+ *
+ * $Id: torrent-cell-renderer.h 12658 2011-08-09 05:47:24Z jordan $
+ */
+
+#ifndef GTR_TORRENT_CELL_RENDERER_H
+#define GTR_TORRENT_CELL_RENDERER_H
+
+#include <gtk/gtk.h>
+
+#define GTR_UNICODE_UP "\xE2\x86\x91"
+#define GTR_UNICODE_DOWN "\xE2\x86\x93"
+#define DIRECTORY_MIME_TYPE "folder"
+#define FILE_MIME_TYPE "file"
+#define UNKNOWN_MIME_TYPE "unknown"
+
+#define TORRENT_CELL_RENDERER_TYPE ( torrent_cell_renderer_get_type( ) )
+
+#define TORRENT_CELL_RENDERER( o ) \
+ ( G_TYPE_CHECK_INSTANCE_CAST( ( o ), \
+ TORRENT_CELL_RENDERER_TYPE, \
+ TorrentCellRenderer ) )
+
+typedef struct TorrentCellRenderer TorrentCellRenderer;
+
+typedef struct TorrentCellRendererClass TorrentCellRendererClass;
+
+struct TorrentCellRenderer {
+ GtkCellRenderer parent;
+
+ /*< private > */
+ struct TorrentCellRendererPrivate *priv;
+};
+
+struct TorrentCellRendererClass {
+ GtkCellRendererClass parent;
+};
+
+GType torrent_cell_renderer_get_type(void) G_GNUC_CONST;
+
+GtkCellRenderer *torrent_cell_renderer_new(void);
+GtkTreeView *torrent_cell_renderer_get_owner(TorrentCellRenderer * r);
+
+#endif /* GTR_TORRENT_CELL_RENDERER_H */
diff --git a/src/torrent.c b/src/torrent.c
new file mode 100644
index 0000000..ed595dd
--- /dev/null
+++ b/src/torrent.c
@@ -0,0 +1,681 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-client.h"
+#include "json.h"
+#include "torrent.h"
+#include "protocol-constants.h"
+#include "util.h"
+
+/* Just some functions to get fields out of the torrent object. */
+
+JsonArray *torrent_get_peers(JsonObject * t)
+{
+ g_assert(json_object_get_array_member(t, FIELD_PEERS));
+ return json_object_get_array_member(t, FIELD_PEERS);
+}
+
+JsonObject *torrent_get_peersfrom(JsonObject * t)
+{
+ return json_object_get_object_member(t, FIELD_PEERSFROM);
+}
+
+JsonArray *torrent_get_wanted(JsonObject * t)
+{
+ g_assert(json_object_get_array_member(t, FIELD_WANTED));
+ return json_object_get_array_member(t, FIELD_WANTED);
+}
+
+JsonArray *torrent_get_priorities(JsonObject * t)
+{
+ g_assert(json_object_get_array_member(t, FIELD_PRIORITIES));
+ return json_object_get_array_member(t, FIELD_PRIORITIES);
+}
+
+JsonArray *torrent_get_tracker_stats(JsonObject * t)
+{
+ g_assert(json_object_get_array_member(t, FIELD_TRACKER_STATS));
+ return json_object_get_array_member(t, FIELD_TRACKER_STATS);
+}
+
+gint64 torrent_get_id(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_ID);
+}
+
+const gchar *torrent_get_download_dir(JsonObject * t)
+{
+ return json_object_get_string_member(t, FIELD_DOWNLOAD_DIR);
+}
+
+const gchar *torrent_get_comment(JsonObject * t)
+{
+ return json_object_get_string_member(t, FIELD_COMMENT);
+}
+
+gdouble torrent_get_metadata_percent_complete(JsonObject * t)
+{
+ JsonNode *node =
+ json_object_get_member(t, FIELD_METADATAPERCENTCOMPLETE);
+ if (node)
+ return json_double_to_progress(node);
+ else
+ return 100.0;
+}
+
+const gchar *torrent_get_name(JsonObject * t)
+{
+ return json_object_get_string_member(t, FIELD_NAME);
+}
+
+gint64 torrent_get_added_date(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_ADDED_DATE);
+}
+
+gboolean torrent_get_honors_session_limits(JsonObject * t)
+{
+ return json_object_get_boolean_member(t, FIELD_HONORS_SESSION_LIMITS);
+}
+
+gint64 torrent_get_bandwidth_priority(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_BANDWIDTH_PRIORITY);
+}
+
+gint64 torrent_get_upload_limit(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_UPLOAD_LIMIT);
+}
+
+gint64 torrent_get_peer_limit(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_PEER_LIMIT);
+}
+
+gboolean torrent_get_upload_limited(JsonObject * t)
+{
+ return json_object_get_boolean_member(t, FIELD_UPLOAD_LIMITED);
+}
+
+gint64 torrent_get_seed_ratio_mode(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_SEED_RATIO_MODE);
+}
+
+gdouble torrent_get_seed_ratio_limit(JsonObject * t)
+{
+ return
+ json_node_really_get_double(json_object_get_member
+ (t, FIELD_SEED_RATIO_LIMIT));
+}
+
+gint64 torrent_get_download_limit(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_DOWNLOAD_LIMIT);
+}
+
+gboolean torrent_get_download_limited(JsonObject * t)
+{
+ return json_object_get_boolean_member(t, FIELD_DOWNLOAD_LIMITED);
+}
+
+gint64 torrent_get_total_size(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_TOTAL_SIZE);
+}
+
+gint64 torrent_get_size_when_done(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_SIZEWHENDONE);
+}
+
+gint64 torrent_get_rate_down(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_RATEDOWNLOAD);
+}
+
+gint64 torrent_get_rate_up(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_RATEUPLOAD);
+}
+
+gint64 torrent_get_eta(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_ETA);
+}
+
+gint64 torrent_get_downloaded(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_DOWNLOADEDEVER);
+}
+
+gint64 torrent_get_uploaded(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_UPLOADEDEVER);
+}
+
+gint64 torrent_get_have_valid(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_HAVEVALID);
+}
+
+gint64 torrent_get_have_unchecked(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_HAVEUNCHECKED);
+}
+
+gint64 torrent_get_status(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_STATUS);
+}
+
+gboolean torrent_get_is_finished(JsonObject * t)
+{
+ return torrent_get_left_until_done(t) <= 0;
+}
+
+gboolean torrent_get_is_private(JsonObject * t)
+{
+ return json_object_get_boolean_member(t, FIELD_ISPRIVATE);
+}
+
+gdouble torrent_get_percent_done(JsonObject * t)
+{
+ return
+ json_double_to_progress(json_object_get_member
+ (t, FIELD_PERCENTDONE));
+}
+
+gdouble torrent_get_recheck_progress(JsonObject * t)
+{
+ return
+ json_double_to_progress(json_object_get_member
+ (t, FIELD_RECHECK_PROGRESS));
+}
+
+gint64 torrent_get_activity_date(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_ACTIVITY_DATE);
+}
+
+guint32
+torrent_get_flags(JsonObject * t, gint64 rpcv, gint64 status,
+ gint64 fileCount, gint64 downRate, gint64 upRate)
+{
+ guint32 flags = 0;
+
+ if (fileCount > 0 && torrent_get_is_finished(t) == TRUE)
+ flags |= TORRENT_FLAG_COMPLETE;
+ else
+ flags |= TORRENT_FLAG_INCOMPLETE;
+
+ if (rpcv >= NEW_STATUS_RPC_VERSION) {
+ switch (status) {
+ case TR_STATUS_STOPPED:
+ flags |= TORRENT_FLAG_PAUSED;
+ break;
+ case TR_STATUS_CHECK_WAIT:
+ flags |= TORRENT_FLAG_WAITING_CHECK;
+ flags |= TORRENT_FLAG_CHECKING_ANY;
+ break;
+ case TR_STATUS_CHECK:
+ flags |= TORRENT_FLAG_CHECKING;
+ flags |= TORRENT_FLAG_CHECKING_ANY;
+ break;
+ case TR_STATUS_DOWNLOAD_WAIT:
+ flags |= TORRENT_FLAG_DOWNLOADING_WAIT;
+ flags |= TORRENT_FLAG_QUEUED;
+ break;
+ case TR_STATUS_DOWNLOAD:
+ if (!(flags & TORRENT_FLAG_COMPLETE))
+ flags |= TORRENT_FLAG_DOWNLOADING;
+
+ if (fileCount <= 0)
+ flags |= TORRENT_FLAG_DOWNLOADING_METADATA;
+
+ flags |= TORRENT_FLAG_ACTIVE;
+ break;
+ case TR_STATUS_SEED_WAIT:
+ flags |= TORRENT_FLAG_SEEDING_WAIT;
+ break;
+ case TR_STATUS_SEED:
+ flags |= TORRENT_FLAG_SEEDING;
+ if (torrent_get_peers_getting_from_us(t))
+ flags |= TORRENT_FLAG_ACTIVE;
+ break;
+ }
+ } else {
+ switch (status) {
+ case OLD_STATUS_DOWNLOADING:
+ flags |= TORRENT_FLAG_DOWNLOADING;
+ break;
+ case OLD_STATUS_PAUSED:
+ flags |= TORRENT_FLAG_PAUSED;
+ break;
+ case OLD_STATUS_SEEDING:
+ flags |= TORRENT_FLAG_SEEDING;
+ break;
+ case OLD_STATUS_CHECKING:
+ flags |= TORRENT_FLAG_CHECKING;
+ break;
+ case OLD_STATUS_WAITING_TO_CHECK:
+ flags |= TORRENT_FLAG_WAITING_CHECK;
+ flags |= TORRENT_FLAG_CHECKING;
+ break;
+ }
+
+ if (downRate > 0 || upRate > 0)
+ flags |= TORRENT_FLAG_ACTIVE;
+ }
+
+ if (torrent_get_error(t) > 0)
+ flags |= TORRENT_FLAG_ERROR;
+
+ return flags;
+}
+
+gchar *torrent_get_status_icon(gint64 rpcv, guint flags)
+{
+ if (flags & TORRENT_FLAG_ERROR)
+ return g_strdup(GTK_STOCK_DIALOG_WARNING);
+ else if (flags & TORRENT_FLAG_DOWNLOADING_METADATA)
+ return g_strdup(GTK_STOCK_FIND);
+ else if (flags & TORRENT_FLAG_DOWNLOADING)
+ return g_strdup(GTK_STOCK_GO_DOWN);
+ else if (flags & TORRENT_FLAG_PAUSED)
+ return g_strdup(GTK_STOCK_MEDIA_PAUSE);
+ else if (flags & TORRENT_FLAG_SEEDING)
+ return g_strdup(GTK_STOCK_GO_UP);
+ else if (flags & TORRENT_FLAG_CHECKING)
+ return g_strdup(GTK_STOCK_REFRESH);
+ else if (flags & TORRENT_FLAG_DOWNLOADING_WAIT)
+ return g_strdup(GTK_STOCK_MEDIA_REWIND);
+ else if (flags & TORRENT_FLAG_SEEDING_WAIT)
+ return g_strdup(GTK_STOCK_MEDIA_FORWARD);
+ else
+ return g_strdup(GTK_STOCK_DIALOG_QUESTION);
+}
+
+gint64 torrent_get_done_date(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_DONE_DATE);
+}
+
+const gchar *torrent_get_errorstr(JsonObject * t)
+{
+ return json_object_get_string_member(t, FIELD_ERROR_STRING);
+}
+
+gint64 torrent_get_error(JsonObject * t)
+{
+ if (!json_object_has_member(t, FIELD_ERROR))
+ return 0;
+ else
+ return json_object_get_int_member(t, FIELD_ERROR);
+}
+
+const gchar *torrent_get_creator(JsonObject * t)
+{
+ return json_object_get_string_member(t, FIELD_CREATOR);
+}
+
+gint64 torrent_get_date_created(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_DATE_CREATED);
+}
+
+const gchar *torrent_get_hash(JsonObject * t)
+{
+ return json_object_get_string_member(t, FIELD_HASH_STRING);
+}
+
+gchar *torrent_get_status_string(gint64 rpcv, gint64 value, guint flags)
+{
+ if (rpcv >= NEW_STATUS_RPC_VERSION) {
+ switch (value) {
+ case TR_STATUS_DOWNLOAD:
+ if (flags & TORRENT_FLAG_DOWNLOADING_METADATA)
+ return g_strdup(_("Metadata Downloading"));
+ else
+ return g_strdup(_("Downloading"));
+ case TR_STATUS_DOWNLOAD_WAIT:
+ return g_strdup(_("Queued download"));
+ case TR_STATUS_CHECK_WAIT:
+ return g_strdup(_("Waiting To Check"));
+ case TR_STATUS_CHECK:
+ return g_strdup(_("Checking"));
+ case TR_STATUS_SEED_WAIT:
+ return g_strdup(_("Queued seed"));
+ case TR_STATUS_SEED:
+ return g_strdup(_("Seeding"));
+ case TR_STATUS_STOPPED:
+ return g_strdup(_("Paused"));
+ }
+ } else {
+ switch (value) {
+ case OLD_STATUS_DOWNLOADING:
+ if (flags & TORRENT_FLAG_DOWNLOADING_METADATA)
+ return g_strdup(_("Metadata Downloading"));
+ else
+ return g_strdup(_("Downloading"));
+ case OLD_STATUS_PAUSED:
+ return g_strdup(_("Paused"));
+ case OLD_STATUS_SEEDING:
+ return g_strdup(_("Seeding"));
+ case OLD_STATUS_CHECKING:
+ return g_strdup(_("Checking"));
+ case OLD_STATUS_WAITING_TO_CHECK:
+ return g_strdup(_("Waiting To Check"));
+ }
+ }
+
+ return g_strdup(_("Unknown"));
+}
+
+gboolean torrent_has_tracker(JsonObject * t, GRegex * rx, gchar * search)
+{
+ GList *trackers;
+ GList *li;
+ gboolean ret = FALSE;
+
+ trackers = json_array_get_elements(torrent_get_tracker_stats(t));
+
+ for (li = trackers; li; li = g_list_next(li)) {
+ JsonObject *tracker = json_node_get_object((JsonNode *) li->data);
+ const gchar *trackerAnnounce = tracker_stats_get_announce(tracker);
+ gchar *trackerAnnounceHost =
+ trg_gregex_get_first(rx, trackerAnnounce);
+ int cmpResult = g_strcmp0(trackerAnnounceHost, search);
+ g_free(trackerAnnounceHost);
+ if (!cmpResult) {
+ ret = TRUE;
+ break;
+ }
+ }
+
+ g_list_free(trackers);
+
+ return ret;
+}
+
+gint64 torrent_get_left_until_done(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_LEFTUNTILDONE);
+}
+
+const gchar *tracker_stats_get_announce(JsonObject * t)
+{
+ return json_object_get_string_member(t, FIELD_ANNOUNCE);
+}
+
+const gchar *tracker_stats_get_scrape(JsonObject * t)
+{
+ return json_object_get_string_member(t, FIELD_SCRAPE);
+}
+
+JsonArray *get_torrents_removed(JsonObject * response)
+{
+ if (G_UNLIKELY(json_object_has_member(response, FIELD_REMOVED)))
+ return json_object_get_array_member(response, FIELD_REMOVED);
+ else
+ return NULL;
+}
+
+JsonArray *get_torrents(JsonObject * response)
+{
+ g_assert(json_object_get_array_member(response, FIELD_TORRENTS));
+ return json_object_get_array_member(response, FIELD_TORRENTS);
+}
+
+JsonArray *torrent_get_files(JsonObject * args)
+{
+ return json_object_get_array_member(args, FIELD_FILES);
+}
+
+gint64 torrent_get_peers_connected(JsonObject * args)
+{
+ return json_object_get_int_member(args, FIELD_PEERS_CONNECTED);
+}
+
+gint64 torrent_get_peers_sending_to_us(JsonObject * args)
+{
+ return json_object_get_int_member(args, FIELD_PEERS_SENDING_TO_US);
+}
+
+gint64 torrent_get_peers_getting_from_us(JsonObject * args)
+{
+ return json_object_get_int_member(args, FIELD_PEERS_GETTING_FROM_US);
+}
+
+gint64 torrent_get_web_seeds_sending_to_us(JsonObject * args)
+{
+ return json_object_get_int_member(args, FIELD_WEB_SEEDS_SENDING_TO_US);
+}
+
+gint64 torrent_get_queue_position(JsonObject * args)
+{
+ if (json_object_has_member(args, FIELD_QUEUE_POSITION))
+ return json_object_get_int_member(args, FIELD_QUEUE_POSITION);
+ else
+ return -1;
+}
+
+/* tracker stats */
+
+gint64 tracker_stats_get_id(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_ID);
+}
+
+gint64 tracker_stats_get_tier(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_TIER);
+}
+
+gint64 tracker_stats_get_last_announce_peer_count(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_LAST_ANNOUNCE_PEER_COUNT);
+}
+
+gint64 tracker_stats_get_last_announce_time(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_LAST_ANNOUNCE_TIME);
+}
+
+gint64 tracker_stats_get_last_scrape_time(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_LAST_SCRAPE_TIME);
+}
+
+gint64 tracker_stats_get_seeder_count(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_SEEDERCOUNT);
+}
+
+gint64 tracker_stats_get_leecher_count(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_LEECHERCOUNT);
+}
+
+gint64 tracker_stats_get_download_count(JsonObject * t)
+{
+ return json_object_get_int_member(t, FIELD_DOWNLOADCOUNT);
+}
+
+const gchar *tracker_stats_get_announce_result(JsonObject * t)
+{
+ return json_object_get_string_member(t, FIELD_LAST_ANNOUNCE_RESULT);
+}
+
+const gchar *tracker_stats_get_host(JsonObject * t)
+{
+ return json_object_get_string_member(t, FIELD_HOST);
+}
+
+gchar *torrent_get_full_path(JsonObject * obj)
+{
+ const gchar *location =
+ json_object_get_string_member(obj, FIELD_DOWNLOAD_DIR);
+ const gchar *name = json_object_get_string_member(obj, FIELD_NAME);
+ return g_strdup_printf("%s/%s", location, name);
+}
+
+gchar *torrent_get_full_dir(JsonObject * obj)
+{
+ gchar *containing_path, *name, *delim;
+ const gchar *location;
+ JsonArray *files = torrent_get_files(obj);
+ JsonObject *firstFile;
+
+ location = json_object_get_string_member(obj, FIELD_DOWNLOAD_DIR);
+ firstFile = json_array_get_object_element(files, 0);
+ name = g_strdup(json_object_get_string_member(firstFile, TFILE_NAME));
+
+ if ((delim = g_strstr_len(name, -1, "/"))) {
+ *delim = '\0';
+ containing_path = g_strdup_printf("%s/%s", location, name);
+ } else {
+ containing_path = g_strdup(location);
+ }
+
+ g_free(name);
+ return containing_path;
+}
+
+/* peers */
+
+const gchar *peer_get_address(JsonObject * p)
+{
+ return json_object_get_string_member(p, TPEER_ADDRESS);
+}
+
+const gchar *peer_get_flagstr(JsonObject * p)
+{
+ return json_object_get_string_member(p, TPEER_FLAGSTR);
+}
+
+const gchar *peer_get_client_name(JsonObject * p)
+{
+ return json_object_get_string_member(p, TPEER_CLIENT_NAME);
+}
+
+gboolean peer_get_is_encrypted(JsonObject * p)
+{
+ return json_object_get_boolean_member(p, TPEER_IS_ENCRYPTED);
+}
+
+gboolean peer_get_is_uploading_to(JsonObject * p)
+{
+ return json_object_get_boolean_member(p, TPEER_IS_UPLOADING_TO);
+}
+
+gboolean peer_get_is_downloading_from(JsonObject * p)
+{
+ return json_object_get_boolean_member(p, TPEER_IS_DOWNLOADING_FROM);
+}
+
+gdouble peer_get_progress(JsonObject * p)
+{
+ return
+ json_double_to_progress(json_object_get_member(p, TPEER_PROGRESS));
+}
+
+gint64 peer_get_rate_to_client(JsonObject * p)
+{
+ return json_object_get_int_member(p, TPEER_RATE_TO_CLIENT);
+}
+
+gint64 peer_get_rate_to_peer(JsonObject * p)
+{
+ return json_object_get_int_member(p, TPEER_RATE_TO_PEER);
+}
+
+gint64 peerfrom_get_pex(JsonObject * pf)
+{
+ return json_object_get_int_member(pf, TPEERFROM_FROMPEX);
+}
+
+gint64 peerfrom_get_dht(JsonObject * pf)
+{
+ return json_object_get_int_member(pf, TPEERFROM_FROMDHT);
+}
+
+gint64 peerfrom_get_trackers(JsonObject * pf)
+{
+ return json_object_get_int_member(pf, TPEERFROM_FROMTRACKERS);
+}
+
+gint64 peerfrom_get_ltep(JsonObject * pf)
+{
+ return json_object_get_int_member(pf, TPEERFROM_FROMLTEP);
+}
+
+gint64 peerfrom_get_resume(JsonObject * pf)
+{
+ return json_object_get_int_member(pf, TPEERFROM_FROMRESUME);
+}
+
+gint64 peerfrom_get_incoming(JsonObject * pf)
+{
+ return json_object_get_int_member(pf, TPEERFROM_FROMINCOMING);
+}
+
+
+gint64 peerfrom_get_lpd(JsonObject * pf)
+{
+ return json_object_has_member(pf, TPEERFROM_FROMLPD) ?
+ json_object_get_int_member(pf, TPEERFROM_FROMLPD) : -1;
+}
+
+/* files */
+
+gdouble file_get_progress(gint64 length, gint64 completed)
+{
+ if (length > 0) {
+ gdouble progress =
+ ((gdouble) completed / (gdouble) length) * 100.0;
+ if (progress > 100.0)
+ return 100.0;
+ else
+ return progress;
+ } else {
+ return 0.0;
+ }
+}
+
+gint64 file_get_length(JsonObject * f)
+{
+ return json_object_get_int_member(f, TFILE_LENGTH);
+}
+
+gint64 file_get_bytes_completed(JsonObject * f)
+{
+ return json_object_get_int_member(f, TFILE_BYTES_COMPLETED);
+}
+
+const gchar *file_get_name(JsonObject * f)
+{
+ return json_object_get_string_member(f, TFILE_NAME);
+}
diff --git a/src/torrent.h b/src/torrent.h
new file mode 100644
index 0000000..cebdd76
--- /dev/null
+++ b/src/torrent.h
@@ -0,0 +1,150 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TORRENT_H_
+#define TORRENT_H_
+
+#include <json-glib/json-glib.h>
+
+#include "trg-client.h"
+
+#define TORRENT_FLAG_ERROR (1 << 0)
+#define TORRENT_FLAG_COMPLETE (1 << 1)
+#define TORRENT_FLAG_INCOMPLETE (1 << 2)
+#define TORRENT_FLAG_SEEDING (1 << 3)
+#define TORRENT_FLAG_SEEDING_WAIT (1 << 4)
+#define TORRENT_FLAG_CHECKING (1 << 5)
+#define TORRENT_FLAG_WAITING_CHECK (1 << 6)
+#define TORRENT_FLAG_CHECKING_ANY (1 << 7)
+#define TORRENT_FLAG_DOWNLOADING (1 << 8)
+#define TORRENT_FLAG_DOWNLOADING_WAIT (1 << 9)
+#define TORRENT_FLAG_PAUSED (1 << 10)
+#define TORRENT_FLAG_QUEUED (1 << 11)
+#define TORRENT_FLAG_ACTIVE (1 << 12)
+#define TORRENT_FLAG_DOWNLOADING_METADATA (1 << 13)
+#define FILTER_FLAG_TRACKER (1 << 14)
+#define FILTER_FLAG_DIR (1 << 15)
+
+#define TORRENT_ADD_FLAG_PAUSED (1 << 0) /* 0x01 */
+#define TORRENT_ADD_FLAG_DELETE (1 << 1) /* 0x02 */
+
+gint64 torrent_get_total_size(JsonObject * t);
+gint64 torrent_get_size_when_done(JsonObject * t);
+const gchar *torrent_get_name(JsonObject * t);
+gint64 torrent_get_rate_down(JsonObject * t);
+gint64 torrent_get_rate_up(JsonObject * t);
+gint64 torrent_get_eta(JsonObject * t);
+gint64 torrent_get_uploaded(JsonObject * t);
+gint64 torrent_get_downloaded(JsonObject * t);
+const gchar *torrent_get_errorstr(JsonObject * t);
+gint64 torrent_get_error(JsonObject * t);
+const gchar *torrent_get_download_dir(JsonObject * t);
+const gchar *torrent_get_comment(JsonObject * t);
+gint64 torrent_get_have_unchecked(JsonObject * t);
+gint64 torrent_get_have_valid(JsonObject * t);
+gint64 torrent_get_status(JsonObject * t);
+const gchar *torrent_get_creator(JsonObject * t);
+gint64 torrent_get_date_created(JsonObject * t);
+const gchar *torrent_get_hash(JsonObject * t);
+gchar *torrent_get_status_string(gint64 rpcv, gint64 value, guint flags);
+gchar *torrent_get_status_icon(gint64 rpcv, guint flags);
+guint32 torrent_get_flags(JsonObject * t, gint64 rpcv, gint64 status,
+ gint64 fileCount, gint64 downRate,
+ gint64 upRate);
+JsonArray *torrent_get_peers(JsonObject * t);
+JsonObject *torrent_get_peersfrom(JsonObject * t);
+JsonArray *torrent_get_tracker_stats(JsonObject * t);
+JsonArray *torrent_get_wanted(JsonObject * t);
+JsonArray *torrent_get_priorities(JsonObject * t);
+gint64 torrent_get_id(JsonObject * t);
+JsonArray *torrent_get_files(JsonObject * args);
+gint64 torrent_get_peers_getting_from_us(JsonObject * args);
+gint64 torrent_get_peers_sending_to_us(JsonObject * args);
+gint64 torrent_get_web_seeds_sending_to_us(JsonObject * args);
+gint64 torrent_get_peers_connected(JsonObject * args);
+gdouble torrent_get_percent_done(JsonObject * t);
+gdouble torrent_get_recheck_progress(JsonObject * t);
+gint64 torrent_get_left_until_done(JsonObject * t);
+gboolean torrent_get_is_finished(JsonObject * t);
+gboolean torrent_get_is_private(JsonObject * t);
+gboolean torrent_get_honors_session_limits(JsonObject * t);
+gint64 torrent_get_bandwidth_priority(JsonObject * t);
+gint64 torrent_get_upload_limit(JsonObject * t);
+gint64 torrent_get_added_date(JsonObject * t);
+gint64 torrent_get_done_date(JsonObject * t);
+gboolean torrent_get_upload_limited(JsonObject * t);
+gint64 torrent_get_download_limit(JsonObject * t);
+gboolean torrent_get_download_limited(JsonObject * t);
+gdouble torrent_get_seed_ratio_limit(JsonObject * t);
+gint64 torrent_get_seed_ratio_mode(JsonObject * t);
+gint64 torrent_get_peer_limit(JsonObject * t);
+gboolean torrent_has_tracker(JsonObject * t, GRegex * rx, gchar * search);
+gint64 torrent_get_queue_position(JsonObject * args);
+gint64 torrent_get_activity_date(JsonObject * t);
+gchar *torrent_get_full_dir(JsonObject * obj);
+gchar *torrent_get_full_path(JsonObject * obj);
+gdouble torrent_get_metadata_percent_complete(JsonObject * t);
+
+/* outer response object */
+
+JsonArray *get_torrents(JsonObject * response);
+JsonArray *get_torrents_removed(JsonObject * response);
+
+/* tracker stats */
+
+const gchar *tracker_stats_get_announce(JsonObject * t);
+const gchar *tracker_stats_get_scrape(JsonObject * t);
+gint64 tracker_stats_get_tier(JsonObject * t);
+gint64 tracker_stats_get_id(JsonObject * t);
+gint64 tracker_stats_get_last_announce_peer_count(JsonObject * t);
+gint64 tracker_stats_get_last_announce_time(JsonObject * t);
+gint64 tracker_stats_get_seeder_count(JsonObject * t);
+gint64 tracker_stats_get_leecher_count(JsonObject * t);
+gint64 tracker_stats_get_download_count(JsonObject * t);
+const gchar *tracker_stats_get_announce_result(JsonObject * t);
+const gchar *tracker_stats_get_host(JsonObject * t);
+gint64 tracker_stats_get_last_scrape_time(JsonObject * t);
+
+/* files */
+
+gint64 file_get_length(JsonObject * f);
+gint64 file_get_bytes_completed(JsonObject * f);
+const gchar *file_get_name(JsonObject * f);
+gdouble file_get_progress(gint64 length, gint64 completed);
+
+/* peers */
+
+const gchar *peer_get_address(JsonObject * p);
+const gchar *peer_get_client_name(JsonObject * p);
+gboolean peer_get_is_encrypted(JsonObject * p);
+gdouble peer_get_progress(JsonObject * p);
+const gchar *peer_get_flagstr(JsonObject * p);
+gint64 peer_get_rate_to_client(JsonObject * p);
+gint64 peer_get_rate_to_peer(JsonObject * p);
+gboolean peer_get_is_uploading_to(JsonObject * p);
+gboolean peer_get_is_downloading_from(JsonObject * p);
+
+gint64 peerfrom_get_pex(JsonObject * pf);
+gint64 peerfrom_get_dht(JsonObject * pf);
+gint64 peerfrom_get_trackers(JsonObject * pf);
+gint64 peerfrom_get_ltep(JsonObject * pf);
+gint64 peerfrom_get_resume(JsonObject * pf);
+gint64 peerfrom_get_incoming(JsonObject * pf);
+gint64 peerfrom_get_lpd(JsonObject * pf);
+#endif /* TORRENT_H_ */
diff --git a/src/transmission-remote-gtk.desktop.in b/src/transmission-remote-gtk.desktop.in
new file mode 100644
index 0000000..30a9a5d
--- /dev/null
+++ b/src/transmission-remote-gtk.desktop.in
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Name=Transmission Remote
+Comment=Remotely manage the Transmission BitTorrent client
+Exec=@bindir@/transmission-remote-gtk %U
+Icon=transmission-remote-gtk
+Terminal=false
+TryExec=transmission-remote-gtk
+Type=Application
+MimeType=application/x-bittorrent;x-scheme-handler/magnet;
+Categories=Network;FileTransfer;P2P;GTK;
diff --git a/src/transmission-remote-gtk.pod b/src/transmission-remote-gtk.pod
new file mode 100644
index 0000000..de7c575
--- /dev/null
+++ b/src/transmission-remote-gtk.pod
@@ -0,0 +1,57 @@
+
+=head1 NAME
+
+transmission-remote-gtk - RPC client for the Transmission bittorrent client.
+
+=head1 SYNOPSIS
+
+B<transmission-remote-gtk> [OPTIONS] [torrent file|magnet link|url]
+
+=head1 DESCRIPTION
+
+B<transmission-remote-gtk> is an application for remote management of the
+Transmission BitTorrent client using its RPC interface.
+
+=head1 OPTIONS
+
+The following options are accepted when running C<transmission-remote-gtk>:
+
+=over 1
+
+=item -m, --minimized, /m
+
+Start the application minimized
+
+=back
+
+=head1 ENVIRONMENT
+
+=over 1
+
+=item TRG_NOUNIQUE
+
+Start a new instance, even if one already exists.
+
+=back
+
+=head1 AUTHOR
+
+Written by Alan Fitton.
+
+=head1 BUGS
+
+Please see L<http://code.google.com/p/transmission-remote-gtk/issues>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2011 Alan Fitton and various contributors.
+This is free software. You may redistribute copies of it under the terms of the GNU General
+Public License C<http://www.gnu.org/licenses/gpl.html>. There is NO WARRANTY, to the extent
+permitted by law.
+
+=head1 SEE ALSO
+
+C<transmission-daemon(1)>, C<transmission-gtk(1)>, the project website C<http://code.google.com/p/transmission-remote-gtk/>
+
+=cut
+
diff --git a/src/transmission_large.ico b/src/transmission_large.ico
new file mode 100644
index 0000000..39ddb81
--- /dev/null
+++ b/src/transmission_large.ico
Binary files differ
diff --git a/src/trg-about-window.c b/src/trg-about-window.c
new file mode 100644
index 0000000..8dbf7a8
--- /dev/null
+++ b/src/trg-about-window.c
@@ -0,0 +1,99 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-about-window.h"
+#include "util.h"
+
+GtkWidget *trg_about_window_new(GtkWindow * parent)
+{
+ GtkWidget *dialog;
+ GdkPixbuf *logo;
+ gchar *licenseText = NULL;
+ const gchar *trgAuthors[] = { "Alan Fitton <alan@eth0.org.uk>", NULL };
+ gchar *licenseFile;
+
+#ifdef WIN32
+ licenseFile = trg_win32_support_path("COPYING.TXT");
+#else
+ licenseFile = g_strdup(TRGLICENSE);
+#endif
+
+ dialog = gtk_about_dialog_new();
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE);
+
+ logo =
+ gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
+ PACKAGE_NAME, 48,
+ GTK_ICON_LOOKUP_USE_BUILTIN, NULL);
+
+ if (logo != NULL) {
+ gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(dialog), logo);
+ g_object_unref(logo);
+ }
+
+ if (g_file_get_contents(licenseFile, &licenseText, NULL, NULL)) {
+ gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(dialog),
+ licenseText);
+ } else {
+ gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(dialog), "GPL2");
+ }
+
+ gtk_about_dialog_set_program_name(GTK_ABOUT_DIALOG(dialog),
+ PACKAGE_NAME);
+ gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(dialog),
+ PACKAGE_VERSION);
+ gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(dialog),
+ "(C) 2011-2013 Alan Fitton");
+ gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(dialog),
+ _
+ ("A remote client to transmission-daemon."));
+
+ gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(dialog),
+ "http://code.google.com/p/transmission-remote-gtk/");
+ gtk_about_dialog_set_website_label(GTK_ABOUT_DIALOG(dialog),
+ "http://code.google.com/p/transmission-remote-gtk/");
+
+ gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(dialog), trgAuthors);
+ /*gtk_about_dialog_set_documenters(GTK_ABOUT_DIALOG(dialog), documenters); */
+ gtk_about_dialog_set_translator_credits(GTK_ABOUT_DIALOG(dialog),
+ "translations kindly contributed by\n\n"
+ "* Pierre Rudloff (French)\n"
+ "* Julian Held (German)\n"
+ "* Algimantas MargeviÄius (Lithuanian)\n"
+ "* Youn sok Choi (Korean)\n"
+ "* Piotr (Polish)\n"
+ "* Y3AVD (Russian)\n"
+ "* aspidzent (Spanish)\n"
+ "* Ã…ke Svensson (Swedish)\n"
+ "* ROR191 (Ukranian)\n");
+
+ g_free(licenseFile);
+ g_free(licenseText);
+
+ return dialog;
+}
diff --git a/src/trg-about-window.h b/src/trg-about-window.h
new file mode 100644
index 0000000..fbd1569
--- /dev/null
+++ b/src/trg-about-window.h
@@ -0,0 +1,27 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef ABOUT_WINDOW_H_
+#define ABOUT_WINDOW_H_
+
+#include <gtk/gtk.h>
+
+GtkWidget *trg_about_window_new(GtkWindow * parent);
+
+#endif /* ABOUT_WINDOW_H_ */
diff --git a/src/trg-cell-renderer-counter.c b/src/trg-cell-renderer-counter.c
new file mode 100644
index 0000000..9a4049c
--- /dev/null
+++ b/src/trg-cell-renderer-counter.c
@@ -0,0 +1,157 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdint.h>
+#include <gtk/gtk.h>
+
+#include "trg-cell-renderer-counter.h"
+#include "util.h"
+
+enum {
+ PROP_0, PROP_STATE_LABEL, PROP_STATE_COUNT
+};
+
+G_DEFINE_TYPE(TrgCellRendererCounter, trg_cell_renderer_counter,
+ GTK_TYPE_CELL_RENDERER_TEXT)
+#define TRG_CELL_RENDERER_COUNTER_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_CELL_RENDERER_COUNTER, TrgCellRendererCounterPrivate))
+typedef struct _TrgCellRendererCounterPrivate
+ TrgCellRendererCounterPrivate;
+
+struct _TrgCellRendererCounterPrivate {
+ gint count;
+ gchar *originalLabel;
+};
+
+static void trg_cell_renderer_counter_get_property(GObject * object,
+ guint property_id,
+ GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererCounterPrivate *priv =
+ TRG_CELL_RENDERER_COUNTER_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_STATE_COUNT:
+ g_value_set_int(value, priv->count);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void trg_cell_renderer_counter_refresh(TrgCellRendererCounter * cr)
+{
+ TrgCellRendererCounterPrivate *priv =
+ TRG_CELL_RENDERER_COUNTER_GET_PRIVATE(cr);
+ if (priv->originalLabel && priv->count > 0) {
+ gchar *counterLabel =
+ g_strdup_printf("%s <span size=\"small\">(%d)</span>",
+ priv->originalLabel,
+ priv->count);
+ g_object_set(cr, "markup", counterLabel, NULL);
+ g_free(counterLabel);
+ } else {
+ g_object_set(cr, "text", priv->originalLabel, NULL);
+ }
+}
+
+static void
+trg_cell_renderer_counter_set_property(GObject * object,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererCounterPrivate *priv =
+ TRG_CELL_RENDERER_COUNTER_GET_PRIVATE(object);
+
+ if (property_id == PROP_STATE_LABEL) {
+ g_free(priv->originalLabel);
+ priv->originalLabel = g_strdup(g_value_get_string(value));
+ trg_cell_renderer_counter_refresh(TRG_CELL_RENDERER_COUNTER
+ (object));
+ } else if (property_id == PROP_STATE_COUNT) {
+ gint newCount = g_value_get_int(value);
+ if (priv->count != newCount) {
+ priv->count = newCount;
+ trg_cell_renderer_counter_refresh(TRG_CELL_RENDERER_COUNTER
+ (object));
+ }
+ } else {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void trg_cell_renderer_counter_dispose(GObject * object)
+{
+ TrgCellRendererCounterPrivate *priv =
+ TRG_CELL_RENDERER_COUNTER_GET_PRIVATE(object);
+ g_free(priv->originalLabel);
+ G_OBJECT_CLASS(trg_cell_renderer_counter_parent_class)->dispose
+ (object);
+}
+
+static void
+trg_cell_renderer_counter_class_init(TrgCellRendererCounterClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->get_property = trg_cell_renderer_counter_get_property;
+ object_class->set_property = trg_cell_renderer_counter_set_property;
+ object_class->dispose = trg_cell_renderer_counter_dispose;
+
+ g_object_class_install_property(object_class,
+ PROP_STATE_COUNT,
+ g_param_spec_int("state-count",
+ "State Count",
+ "State Count",
+ -1,
+ INT_MAX,
+ -1,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_STATE_LABEL,
+ g_param_spec_string("state-label",
+ "State Label",
+ "State Label",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_type_class_add_private(klass, sizeof(TrgCellRendererCounterPrivate));
+}
+
+static void trg_cell_renderer_counter_init(TrgCellRendererCounter * self)
+{
+}
+
+GtkCellRenderer *trg_cell_renderer_counter_new(void)
+{
+ return
+ GTK_CELL_RENDERER(g_object_new
+ (TRG_TYPE_CELL_RENDERER_COUNTER, NULL));
+}
diff --git a/src/trg-cell-renderer-counter.h b/src/trg-cell-renderer-counter.h
new file mode 100644
index 0000000..7e70c4b
--- /dev/null
+++ b/src/trg-cell-renderer-counter.h
@@ -0,0 +1,51 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_CELL_RENDERER_COUNTER_H_
+#define TRG_CELL_RENDERER_COUNTER_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define TRG_TYPE_CELL_RENDERER_COUNTER trg_cell_renderer_counter_get_type()
+#define TRG_CELL_RENDERER_COUNTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_CELL_RENDERER_COUNTER, TrgCellRendererCounter))
+#define TRG_CELL_RENDERER_COUNTER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_CELL_RENDERER_COUNTER, TrgCellRendererCounterClass))
+#define TRG_IS_CELL_RENDERER_COUNTER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_CELL_RENDERER_COUNTER))
+#define TRG_IS_CELL_RENDERER_COUNTER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_CELL_RENDERER_COUNTER))
+#define TRG_CELL_RENDERER_COUNTER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_CELL_RENDERER_COUNTER, TrgCellRendererCounterClass))
+ typedef struct {
+ GtkCellRendererText parent;
+} TrgCellRendererCounter;
+
+typedef struct {
+ GtkCellRendererTextClass parent_class;
+} TrgCellRendererCounterClass;
+
+GType trg_cell_renderer_counter_get_type(void);
+
+GtkCellRenderer *trg_cell_renderer_counter_new(void);
+
+G_END_DECLS
+#endif /* TRG_CELL_RENDERER_COUNTER_H_ */
diff --git a/src/trg-cell-renderer-epoch.c b/src/trg-cell-renderer-epoch.c
new file mode 100644
index 0000000..8e38949
--- /dev/null
+++ b/src/trg-cell-renderer-epoch.c
@@ -0,0 +1,121 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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 depochils.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdint.h>
+#include <time.h>
+#include <gtk/gtk.h>
+
+#include "trg-cell-renderer-epoch.h"
+#include "util.h"
+
+enum {
+ PROP_0,
+ PROP_EPOCH_VALUE
+};
+
+G_DEFINE_TYPE(TrgCellRendererEpoch, trg_cell_renderer_epoch,
+ GTK_TYPE_CELL_RENDERER_TEXT)
+#define TRG_CELL_RENDERER_EPOCH_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_CELL_RENDERER_EPOCH, TrgCellRendererEpochPrivate))
+typedef struct _TrgCellRendererEpochPrivate TrgCellRendererEpochPrivate;
+
+struct _TrgCellRendererEpochPrivate {
+ gdouble epoch_value;
+};
+
+static void
+trg_cell_renderer_epoch_get_property(GObject * object,
+ guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ TrgCellRendererEpochPrivate *priv =
+ TRG_CELL_RENDERER_EPOCH_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_EPOCH_VALUE:
+ g_value_set_int64(value, priv->epoch_value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_cell_renderer_epoch_set_property(GObject * object, guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererEpochPrivate *priv =
+ TRG_CELL_RENDERER_EPOCH_GET_PRIVATE(object);
+
+ if (property_id == PROP_EPOCH_VALUE) {
+ gint64 new_value = g_value_get_int64(value);
+ if (priv->epoch_value != new_value) {
+ if (new_value > 0) {
+ gchar *timestring = epoch_to_string(new_value);
+ g_object_set(object, "text", timestring, NULL);
+ g_free(timestring);
+ } else {
+ g_object_set(object, "text", "", NULL);
+ }
+ priv->epoch_value = new_value;
+ }
+ } else {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+trg_cell_renderer_epoch_class_init(TrgCellRendererEpochClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->get_property = trg_cell_renderer_epoch_get_property;
+ object_class->set_property = trg_cell_renderer_epoch_set_property;
+
+ g_object_class_install_property(object_class,
+ PROP_EPOCH_VALUE,
+ g_param_spec_int64("epoch-value",
+ "Epoch Value",
+ "Epoch Value",
+ G_MININT64,
+ G_MAXINT64,
+ 0,
+ G_PARAM_READWRITE
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_type_class_add_private(klass, sizeof(TrgCellRendererEpochPrivate));
+}
+
+static void
+trg_cell_renderer_epoch_init(TrgCellRendererEpoch * self G_GNUC_UNUSED)
+{
+}
+
+GtkCellRenderer *trg_cell_renderer_epoch_new(void)
+{
+ return
+ GTK_CELL_RENDERER(g_object_new
+ (TRG_TYPE_CELL_RENDERER_EPOCH, NULL));
+}
diff --git a/src/trg-cell-renderer-epoch.h b/src/trg-cell-renderer-epoch.h
new file mode 100644
index 0000000..918fb9b
--- /dev/null
+++ b/src/trg-cell-renderer-epoch.h
@@ -0,0 +1,51 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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 depochils.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_CELL_RENDERER_EPOCH_H_
+#define TRG_CELL_RENDERER_EPOCH_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define TRG_TYPE_CELL_RENDERER_EPOCH trg_cell_renderer_epoch_get_type()
+#define TRG_CELL_RENDERER_EPOCH(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_CELL_RENDERER_EPOCH, TrgCellRendererEpoch))
+#define TRG_CELL_RENDERER_EPOCH_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_CELL_RENDERER_EPOCH, TrgCellRendererEpochClass))
+#define TRG_IS_CELL_RENDERER_EPOCH(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_CELL_RENDERER_EPOCH))
+#define TRG_IS_CELL_RENDERER_EPOCH_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_CELL_RENDERER_EPOCH))
+#define TRG_CELL_RENDERER_EPOCH_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_CELL_RENDERER_EPOCH, TrgCellRendererEpochClass))
+ typedef struct {
+ GtkCellRendererText parent;
+} TrgCellRendererEpoch;
+
+typedef struct {
+ GtkCellRendererTextClass parent_class;
+} TrgCellRendererEpochClass;
+
+GType trg_cell_renderer_epoch_get_type(void);
+
+GtkCellRenderer *trg_cell_renderer_epoch_new(void);
+
+G_END_DECLS
+#endif /* TRG_CELL_RENDERER_EPOCH_H_ */
diff --git a/src/trg-cell-renderer-eta.c b/src/trg-cell-renderer-eta.c
new file mode 100644
index 0000000..90e8886
--- /dev/null
+++ b/src/trg-cell-renderer-eta.c
@@ -0,0 +1,119 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdint.h>
+#include <gtk/gtk.h>
+
+#include "trg-cell-renderer-eta.h"
+#include "util.h"
+
+enum {
+ PROP_0,
+ PROP_ETA_VALUE
+};
+
+G_DEFINE_TYPE(TrgCellRendererEta, trg_cell_renderer_eta,
+ GTK_TYPE_CELL_RENDERER_TEXT)
+#define TRG_CELL_RENDERER_ETA_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_CELL_RENDERER_ETA, TrgCellRendererEtaPrivate))
+typedef struct _TrgCellRendererEtaPrivate TrgCellRendererEtaPrivate;
+
+struct _TrgCellRendererEtaPrivate {
+ gdouble eta_value;
+};
+
+static void
+trg_cell_renderer_eta_get_property(GObject * object,
+ guint property_id, GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererEtaPrivate *priv =
+ TRG_CELL_RENDERER_ETA_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_ETA_VALUE:
+ g_value_set_int64(value, priv->eta_value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_cell_renderer_eta_set_property(GObject * object, guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererEtaPrivate *priv =
+ TRG_CELL_RENDERER_ETA_GET_PRIVATE(object);
+
+ if (property_id == PROP_ETA_VALUE) {
+ priv->eta_value = g_value_get_int64(value);
+ if (priv->eta_value > 0) {
+ char etaString[32];
+ tr_strltime_short(etaString, priv->eta_value,
+ sizeof(etaString));
+ g_object_set(object, "text", etaString, NULL);
+ } else if (priv->eta_value == -2) {
+ g_object_set(object, "text", "∞", NULL);
+ } else {
+ g_object_set(object, "text", "", NULL);
+ }
+ } else {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+trg_cell_renderer_eta_class_init(TrgCellRendererEtaClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->get_property = trg_cell_renderer_eta_get_property;
+ object_class->set_property = trg_cell_renderer_eta_set_property;
+
+ g_object_class_install_property(object_class,
+ PROP_ETA_VALUE,
+ g_param_spec_int64("eta-value",
+ "Eta Value",
+ "Eta Value",
+ G_MININT64,
+ G_MAXINT64,
+ 0,
+ G_PARAM_READWRITE
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_type_class_add_private(klass, sizeof(TrgCellRendererEtaPrivate));
+}
+
+static void
+trg_cell_renderer_eta_init(TrgCellRendererEta * self G_GNUC_UNUSED)
+{
+}
+
+GtkCellRenderer *trg_cell_renderer_eta_new(void)
+{
+ return
+ GTK_CELL_RENDERER(g_object_new(TRG_TYPE_CELL_RENDERER_ETA, NULL));
+}
diff --git a/src/trg-cell-renderer-eta.h b/src/trg-cell-renderer-eta.h
new file mode 100644
index 0000000..90ca95b
--- /dev/null
+++ b/src/trg-cell-renderer-eta.h
@@ -0,0 +1,51 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_CELL_RENDERER_ETA_H_
+#define TRG_CELL_RENDERER_ETA_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define TRG_TYPE_CELL_RENDERER_ETA trg_cell_renderer_eta_get_type()
+#define TRG_CELL_RENDERER_ETA(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_CELL_RENDERER_ETA, TrgCellRendererEta))
+#define TRG_CELL_RENDERER_ETA_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_CELL_RENDERER_ETA, TrgCellRendererEtaClass))
+#define TRG_IS_CELL_RENDERER_ETA(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_CELL_RENDERER_ETA))
+#define TRG_IS_CELL_RENDERER_ETA_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_CELL_RENDERER_ETA))
+#define TRG_CELL_RENDERER_ETA_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_CELL_RENDERER_ETA, TrgCellRendererEtaClass))
+ typedef struct {
+ GtkCellRendererText parent;
+} TrgCellRendererEta;
+
+typedef struct {
+ GtkCellRendererTextClass parent_class;
+} TrgCellRendererEtaClass;
+
+GType trg_cell_renderer_eta_get_type(void);
+
+GtkCellRenderer *trg_cell_renderer_eta_new(void);
+
+G_END_DECLS
+#endif /* TRG_CELL_RENDERER_ETA_H_ */
diff --git a/src/trg-cell-renderer-file-icon.c b/src/trg-cell-renderer-file-icon.c
new file mode 100644
index 0000000..e116d90
--- /dev/null
+++ b/src/trg-cell-renderer-file-icon.c
@@ -0,0 +1,187 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdint.h>
+#include <gtk/gtk.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "trg-cell-renderer-file-icon.h"
+#include "util.h"
+
+enum {
+ PROP_0,
+ PROP_FILE_ID,
+ PROP_FILE_NAME
+};
+
+G_DEFINE_TYPE(TrgCellRendererFileIcon, trg_cell_renderer_file_icon,
+ GTK_TYPE_CELL_RENDERER_PIXBUF)
+#define TRG_CELL_RENDERER_FILE_ICON_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_CELL_RENDERER_FILE_ICON, TrgCellRendererFileIconPrivate))
+typedef struct _TrgCellRendererFileIconPrivate
+ TrgCellRendererFileIconPrivate;
+
+struct _TrgCellRendererFileIconPrivate {
+ gint64 file_id;
+ gchar *text;
+};
+
+static void
+trg_cell_renderer_file_icon_get_property(GObject * object,
+ guint property_id,
+ GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererFileIconPrivate *priv =
+ TRG_CELL_RENDERER_FILE_ICON_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_FILE_ID:
+ g_value_set_int64(value, priv->file_id);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_cell_renderer_file_icon_refresh(TrgCellRendererFileIcon * fi)
+{
+ TrgCellRendererFileIconPrivate *priv =
+ TRG_CELL_RENDERER_FILE_ICON_GET_PRIVATE(fi);
+
+ if (priv->file_id == -2) {
+ return;
+ } else if (priv->file_id == -1) {
+ g_object_set(fi, "stock-id", GTK_STOCK_DIRECTORY, NULL);
+ } else if (priv->text) {
+#ifndef WIN32
+ gboolean uncertain;
+ gchar *mimetype =
+ g_content_type_guess(priv->text, NULL, 0, &uncertain);
+ GIcon *icon = NULL;
+
+ if (!uncertain && mimetype)
+ icon = g_content_type_get_icon(mimetype);
+
+ g_free(mimetype);
+
+ if (icon) {
+ g_object_set(fi, "gicon", icon, NULL);
+ g_object_unref(icon);
+ } else {
+ g_object_set(fi, "stock-id", GTK_STOCK_FILE, NULL);
+ }
+#else
+ g_object_set(fi, "stock-id", GTK_STOCK_FILE, NULL);
+#endif
+ }
+}
+
+static void
+trg_cell_renderer_file_icon_set_property(GObject * object,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererFileIconPrivate *priv =
+ TRG_CELL_RENDERER_FILE_ICON_GET_PRIVATE(object);
+ if (property_id == PROP_FILE_ID) {
+ priv->file_id = g_value_get_int64(value);
+ trg_cell_renderer_file_icon_refresh(TRG_CELL_RENDERER_FILE_ICON
+ (object));
+ } else if (property_id == PROP_FILE_NAME) {
+ if (priv->file_id != -1) {
+ g_free(priv->text);
+ priv->text = g_strdup(g_value_get_string(value));
+ trg_cell_renderer_file_icon_refresh(TRG_CELL_RENDERER_FILE_ICON
+ (object));
+ }
+ } else {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void trg_cell_renderer_file_icon_dispose(GObject * object)
+{
+ TrgCellRendererFileIconPrivate *priv =
+ TRG_CELL_RENDERER_FILE_ICON_GET_PRIVATE(object);
+ g_free(priv->text);
+ G_OBJECT_CLASS(trg_cell_renderer_file_icon_parent_class)->dispose
+ (object);
+}
+
+static void
+trg_cell_renderer_file_icon_class_init(TrgCellRendererFileIconClass *
+ klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->get_property = trg_cell_renderer_file_icon_get_property;
+ object_class->set_property = trg_cell_renderer_file_icon_set_property;
+ object_class->dispose = trg_cell_renderer_file_icon_dispose;
+
+ g_object_class_install_property(object_class,
+ PROP_FILE_ID,
+ g_param_spec_int64("file-id",
+ "File ID",
+ "File ID",
+ -2,
+ G_MAXINT64,
+ -2,
+ G_PARAM_READWRITE
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_FILE_NAME,
+ g_param_spec_string("file-name",
+ "Filename",
+ "Filename",
+ NULL,
+ G_PARAM_READWRITE
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_type_class_add_private(klass,
+ sizeof(TrgCellRendererFileIconPrivate));
+}
+
+static void
+trg_cell_renderer_file_icon_init(TrgCellRendererFileIcon * self)
+{
+}
+
+GtkCellRenderer *trg_cell_renderer_file_icon_new(void)
+{
+ return
+ GTK_CELL_RENDERER(g_object_new
+ (TRG_TYPE_CELL_RENDERER_FILE_ICON, NULL));
+}
diff --git a/src/trg-cell-renderer-file-icon.h b/src/trg-cell-renderer-file-icon.h
new file mode 100644
index 0000000..504bd59
--- /dev/null
+++ b/src/trg-cell-renderer-file-icon.h
@@ -0,0 +1,51 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_CELL_RENDERER_FILE_ICON_H_
+#define TRG_CELL_RENDERER_FILE_ICON_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define TRG_TYPE_CELL_RENDERER_FILE_ICON trg_cell_renderer_file_icon_get_type()
+#define TRG_CELL_RENDERER_FILE_ICON(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_CELL_RENDERER_FILE_ICON, TrgCellRendererFileIcon))
+#define TRG_CELL_RENDERER_FILE_ICON_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_CELL_RENDERER_FILE_ICON, TrgCellRendererFileIconClass))
+#define TRG_IS_CELL_RENDERER_FILE_ICON(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_CELL_RENDERER_FILE_ICON))
+#define TRG_IS_CELL_RENDERER_FILE_ICON_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_CELL_RENDERER_FILE_ICON))
+#define TRG_CELL_RENDERER_FILE_ICON_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_CELL_RENDERER_FILE_ICON, TrgCellRendererFileIconClass))
+ typedef struct {
+ GtkCellRendererPixbuf parent;
+} TrgCellRendererFileIcon;
+
+typedef struct {
+ GtkCellRendererPixbufClass parent_class;
+} TrgCellRendererFileIconClass;
+
+GType trg_cell_renderer_file_icon_get_type(void);
+
+GtkCellRenderer *trg_cell_renderer_file_icon_new(void);
+
+G_END_DECLS
+#endif /* TRG_CELL_RENDERER_FILE_ICON_H_ */
diff --git a/src/trg-cell-renderer-numgteqthan.c b/src/trg-cell-renderer-numgteqthan.c
new file mode 100644
index 0000000..3be1cd6
--- /dev/null
+++ b/src/trg-cell-renderer-numgteqthan.c
@@ -0,0 +1,146 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdint.h>
+#include <gtk/gtk.h>
+
+#include "trg-cell-renderer-numgteqthan.h"
+#include "util.h"
+
+enum {
+ PROP_0,
+ PROP_VALUE_VALUE,
+ PROP_MINVALUE
+};
+
+G_DEFINE_TYPE(TrgCellRendererNumGtEqThan, trg_cell_renderer_numgteqthan,
+ GTK_TYPE_CELL_RENDERER_TEXT)
+#define TRG_CELL_RENDERER_NUMGTEQTHAN_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_CELL_RENDERER_NUMGTEQTHAN, TrgCellRendererNumGtEqThanPrivate))
+typedef struct _TrgCellRendererNumGtEqThanPrivate
+ TrgCellRendererNumGtEqThanPrivate;
+
+struct _TrgCellRendererNumGtEqThanPrivate {
+ gint64 value_value;
+ gint64 minvalue;
+};
+
+static void
+trg_cell_renderer_numgteqthan_get_property(GObject * object,
+ guint property_id,
+ GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererNumGtEqThanPrivate *priv =
+ TRG_CELL_RENDERER_NUMGTEQTHAN_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_VALUE_VALUE:
+ g_value_set_int64(value, priv->value_value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_cell_renderer_numgteqthan_set_property(GObject * object,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererNumGtEqThanPrivate *priv =
+ TRG_CELL_RENDERER_NUMGTEQTHAN_GET_PRIVATE(object);
+ if (property_id == PROP_VALUE_VALUE) {
+ priv->value_value = g_value_get_int64(value);
+ if (priv->value_value >= priv->minvalue) {
+ gchar size_text[32];
+ g_snprintf(size_text, sizeof(size_text), "%" G_GINT64_FORMAT,
+ priv->value_value);
+ g_object_set(object, "text", size_text, NULL);
+ } else {
+ g_object_set(object, "text", "", NULL);
+ }
+ } else if (property_id == PROP_MINVALUE) {
+ priv->minvalue = g_value_get_int64(value);
+ } else {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+trg_cell_renderer_numgteqthan_class_init(TrgCellRendererNumGtEqThanClass *
+ klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->get_property =
+ trg_cell_renderer_numgteqthan_get_property;
+ object_class->set_property =
+ trg_cell_renderer_numgteqthan_set_property;
+
+ g_object_class_install_property(object_class,
+ PROP_VALUE_VALUE,
+ g_param_spec_int64("value",
+ "Value",
+ "Value",
+ G_MININT64,
+ G_MAXINT64,
+ 0,
+ G_PARAM_READWRITE
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_MINVALUE,
+ g_param_spec_int64("minvalue",
+ "Min Value",
+ "Min Value",
+ G_MININT64,
+ G_MAXINT64,
+ 1,
+ G_PARAM_READWRITE
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_type_class_add_private(klass,
+ sizeof(TrgCellRendererNumGtEqThanPrivate));
+}
+
+static void
+trg_cell_renderer_numgteqthan_init(TrgCellRendererNumGtEqThan * self)
+{
+ g_object_set(self, "xalign", 1.0f, NULL);
+}
+
+GtkCellRenderer *trg_cell_renderer_numgteqthan_new(gint64 minvalue)
+{
+ return
+ GTK_CELL_RENDERER(g_object_new
+ (TRG_TYPE_CELL_RENDERER_NUMGTEQTHAN, "minvalue",
+ minvalue, NULL));
+}
diff --git a/src/trg-cell-renderer-numgteqthan.h b/src/trg-cell-renderer-numgteqthan.h
new file mode 100644
index 0000000..e71ed55
--- /dev/null
+++ b/src/trg-cell-renderer-numgteqthan.h
@@ -0,0 +1,51 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_CELL_RENDERER_NUMGTEQTHAN_H_
+#define TRG_CELL_RENDERER_NUMGTEQTHAN_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define TRG_TYPE_CELL_RENDERER_NUMGTEQTHAN trg_cell_renderer_numgteqthan_get_type()
+#define TRG_CELL_RENDERER_NUMGTEQTHAN(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_CELL_RENDERER_NUMGTEQTHAN, TrgCellRendererNumGtEqThan))
+#define TRG_CELL_RENDERER_NUMGTEQTHAN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_CELL_RENDERER_NUMGTEQTHAN, TrgCellRendererNumGtEqThanClass))
+#define TRG_IS_CELL_RENDERER_NUMGTEQTHAN(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_CELL_RENDERER_NUMGTEQTHAN))
+#define TRG_IS_CELL_RENDERER_NUMGTEQTHAN_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_CELL_RENDERER_NUMGTEQTHAN))
+#define TRG_CELL_RENDERER_NUMGTEQTHAN_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_CELL_RENDERER_NUMGTEQTHAN, TrgCellRendererNumGtEqThanClass))
+ typedef struct {
+ GtkCellRendererText parent;
+} TrgCellRendererNumGtEqThan;
+
+typedef struct {
+ GtkCellRendererTextClass parent_class;
+} TrgCellRendererNumGtEqThanClass;
+
+GType trg_cell_renderer_numgteqthan_get_type(void);
+
+GtkCellRenderer *trg_cell_renderer_numgteqthan_new(gint64 minvalue);
+
+G_END_DECLS
+#endif /* TRG_CELL_RENDERER_NUMGTEQTHAN_H_ */
diff --git a/src/trg-cell-renderer-priority.c b/src/trg-cell-renderer-priority.c
new file mode 100644
index 0000000..d52129a
--- /dev/null
+++ b/src/trg-cell-renderer-priority.c
@@ -0,0 +1,122 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdint.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "trg-cell-renderer-priority.h"
+#include "protocol-constants.h"
+#include "trg-files-model.h"
+#include "util.h"
+
+enum {
+ PROP_0,
+ PROP_PRIORITY_VALUE
+};
+
+G_DEFINE_TYPE(TrgCellRendererPriority, trg_cell_renderer_priority,
+ GTK_TYPE_CELL_RENDERER_TEXT)
+#define TRG_CELL_RENDERER_PRIORITY_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_CELL_RENDERER_PRIORITY, TrgCellRendererPriorityPrivate))
+typedef struct _TrgCellRendererPriorityPrivate
+ TrgCellRendererPriorityPrivate;
+
+struct _TrgCellRendererPriorityPrivate {
+ gint64 priority_value;
+};
+
+static void
+trg_cell_renderer_priority_get_property(GObject * object,
+ guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ TrgCellRendererPriorityPrivate *priv =
+ TRG_CELL_RENDERER_PRIORITY_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_PRIORITY_VALUE:
+ g_value_set_int64(value, priv->priority_value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_cell_renderer_priority_set_property(GObject * object,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererPriorityPrivate *priv =
+ TRG_CELL_RENDERER_PRIORITY_GET_PRIVATE(object);
+
+ if (property_id == PROP_PRIORITY_VALUE) {
+ priv->priority_value = g_value_get_int(value);
+ if (priv->priority_value == TR_PRI_LOW) {
+ g_object_set(object, "text", _("Low"), NULL);
+ } else if (priv->priority_value == TR_PRI_HIGH) {
+ g_object_set(object, "text", _("High"), NULL);
+ } else if (priv->priority_value == TR_PRI_NORMAL) {
+ g_object_set(object, "text", _("Normal"), NULL);
+ } else if (priv->priority_value == TR_PRI_MIXED) {
+ g_object_set(object, "text", _("Mixed"), NULL);
+ } else {
+ g_object_set(object, "text", "", NULL);
+ }
+ } else {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+trg_cell_renderer_priority_class_init(TrgCellRendererPriorityClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->get_property = trg_cell_renderer_priority_get_property;
+ object_class->set_property = trg_cell_renderer_priority_set_property;
+
+ g_object_class_install_property(object_class,
+ PROP_PRIORITY_VALUE,
+ g_param_spec_int
+ ("priority-value",
+ "Priority Value",
+ "Priority Value", TR_PRI_UNSET,
+ TR_PRI_HIGH, TR_PRI_NORMAL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_type_class_add_private(klass,
+ sizeof(TrgCellRendererPriorityPrivate));
+}
+
+static void trg_cell_renderer_priority_init(TrgCellRendererPriority * self)
+{
+}
+
+GtkCellRenderer *trg_cell_renderer_priority_new(void)
+{
+ return
+ GTK_CELL_RENDERER(g_object_new
+ (TRG_TYPE_CELL_RENDERER_PRIORITY, NULL));
+}
diff --git a/src/trg-cell-renderer-priority.h b/src/trg-cell-renderer-priority.h
new file mode 100644
index 0000000..58a07a2
--- /dev/null
+++ b/src/trg-cell-renderer-priority.h
@@ -0,0 +1,51 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_CELL_RENDERER_PRIORITY_H_
+#define TRG_CELL_RENDERER_PRIORITY_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define TRG_TYPE_CELL_RENDERER_PRIORITY trg_cell_renderer_priority_get_type()
+#define TRG_CELL_RENDERER_PRIORITY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_CELL_RENDERER_PRIORITY, TrgCellRendererPriority))
+#define TRG_CELL_RENDERER_PRIORITY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_CELL_RENDERER_PRIORITY, TrgCellRendererPriorityClass))
+#define TRG_IS_CELL_RENDERER_PRIORITY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_CELL_RENDERER_PRIORITY))
+#define TRG_IS_CELL_RENDERER_PRIORITY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_CELL_RENDERER_PRIORITY))
+#define TRG_CELL_RENDERER_PRIORITY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_CELL_RENDERER_PRIORITY, TrgCellRendererPriorityClass))
+ typedef struct {
+ GtkCellRendererText parent;
+} TrgCellRendererPriority;
+
+typedef struct {
+ GtkCellRendererTextClass parent_class;
+} TrgCellRendererPriorityClass;
+
+GType trg_cell_renderer_priority_get_type(void);
+
+GtkCellRenderer *trg_cell_renderer_priority_new(void);
+
+G_END_DECLS
+#endif /* TRG_CELL_RENDERER_PRIORITY_H_ */
diff --git a/src/trg-cell-renderer-ratio.c b/src/trg-cell-renderer-ratio.c
new file mode 100644
index 0000000..ed0a453
--- /dev/null
+++ b/src/trg-cell-renderer-ratio.c
@@ -0,0 +1,115 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <limits.h>
+#include <gtk/gtk.h>
+
+#include "trg-cell-renderer-ratio.h"
+#include "util.h"
+
+enum {
+ PROP_0,
+ PROP_RATIO_VALUE
+};
+
+G_DEFINE_TYPE(TrgCellRendererRatio, trg_cell_renderer_ratio,
+ GTK_TYPE_CELL_RENDERER_TEXT)
+#define TRG_CELL_RENDERER_RATIO_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_CELL_RENDERER_RATIO, TrgCellRendererRatioPrivate))
+typedef struct _TrgCellRendererRatioPrivate TrgCellRendererRatioPrivate;
+
+struct _TrgCellRendererRatioPrivate {
+ gdouble ratio_value;
+};
+
+static void
+trg_cell_renderer_ratio_get_property(GObject * object,
+ guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ TrgCellRendererRatioPrivate *priv =
+ TRG_CELL_RENDERER_RATIO_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_RATIO_VALUE:
+ g_value_set_double(value, priv->ratio_value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+trg_cell_renderer_ratio_set_property(GObject * object, guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererRatioPrivate *priv =
+ TRG_CELL_RENDERER_RATIO_GET_PRIVATE(object);
+ if (property_id == PROP_RATIO_VALUE) {
+ priv->ratio_value = g_value_get_double(value);
+ if (priv->ratio_value > 0) {
+ char ratioString[32];
+ trg_strlratio(ratioString, priv->ratio_value);
+ g_object_set(object, "text", ratioString, NULL);
+ } else {
+ g_object_set(object, "text", "", NULL);
+ }
+ } else {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+trg_cell_renderer_ratio_class_init(TrgCellRendererRatioClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->get_property = trg_cell_renderer_ratio_get_property;
+ object_class->set_property = trg_cell_renderer_ratio_set_property;
+
+ g_object_class_install_property(object_class,
+ PROP_RATIO_VALUE,
+ g_param_spec_double("ratio-value",
+ "Ratio Value",
+ "Ratio Value",
+ 0,
+ DBL_MAX,
+ 0,
+ G_PARAM_READWRITE
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_type_class_add_private(klass, sizeof(TrgCellRendererRatioPrivate));
+}
+
+static void trg_cell_renderer_ratio_init(TrgCellRendererRatio * self)
+{
+ g_object_set(self, "xalign", 1.0f, NULL);
+}
+
+GtkCellRenderer *trg_cell_renderer_ratio_new(void)
+{
+ return
+ GTK_CELL_RENDERER(g_object_new
+ (TRG_TYPE_CELL_RENDERER_RATIO, NULL));
+}
diff --git a/src/trg-cell-renderer-ratio.h b/src/trg-cell-renderer-ratio.h
new file mode 100644
index 0000000..6b7e95b
--- /dev/null
+++ b/src/trg-cell-renderer-ratio.h
@@ -0,0 +1,51 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_CELL_RENDERER_RATIO_H_
+#define TRG_CELL_RENDERER_RATIO_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define TRG_TYPE_CELL_RENDERER_RATIO trg_cell_renderer_ratio_get_type()
+#define TRG_CELL_RENDERER_RATIO(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_CELL_RENDERER_RATIO, TrgCellRendererRatio))
+#define TRG_CELL_RENDERER_RATIO_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_CELL_RENDERER_RATIO, TrgCellRendererRatioClass))
+#define TRG_IS_CELL_RENDERER_RATIO(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_CELL_RENDERER_RATIO))
+#define TRG_IS_CELL_RENDERER_RATIO_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_CELL_RENDERER_RATIO))
+#define TRG_CELL_RENDERER_RATIO_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_CELL_RENDERER_RATIO, TrgCellRendererRatioClass))
+ typedef struct {
+ GtkCellRendererText parent;
+} TrgCellRendererRatio;
+
+typedef struct {
+ GtkCellRendererTextClass parent_class;
+} TrgCellRendererRatioClass;
+
+GType trg_cell_renderer_ratio_get_type(void);
+
+GtkCellRenderer *trg_cell_renderer_ratio_new(void);
+
+G_END_DECLS
+#endif /* TRG_CELL_RENDERER_RATIO_H_ */
diff --git a/src/trg-cell-renderer-size.c b/src/trg-cell-renderer-size.c
new file mode 100644
index 0000000..ebe6b25
--- /dev/null
+++ b/src/trg-cell-renderer-size.c
@@ -0,0 +1,118 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdint.h>
+#include <gtk/gtk.h>
+
+#include "trg-cell-renderer-size.h"
+#include "util.h"
+
+enum {
+ PROP_0,
+ PROP_SIZE_VALUE
+};
+
+G_DEFINE_TYPE(TrgCellRendererSize, trg_cell_renderer_size,
+ GTK_TYPE_CELL_RENDERER_TEXT)
+#define TRG_CELL_RENDERER_SIZE_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_CELL_RENDERER_SIZE, TrgCellRendererSizePrivate))
+typedef struct _TrgCellRendererSizePrivate TrgCellRendererSizePrivate;
+
+struct _TrgCellRendererSizePrivate {
+ gint64 size_value;
+};
+
+static void
+trg_cell_renderer_size_get_property(GObject * object,
+ guint property_id, GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererSizePrivate *priv =
+ TRG_CELL_RENDERER_SIZE_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_SIZE_VALUE:
+ g_value_set_int64(value, priv->size_value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_cell_renderer_size_set_property(GObject * object, guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererSizePrivate *priv =
+ TRG_CELL_RENDERER_SIZE_GET_PRIVATE(object);
+ if (property_id == PROP_SIZE_VALUE) {
+ gint64 new_value = g_value_get_int64(value);
+ if (priv->size_value != new_value) {
+ if (new_value > 0) {
+ char sizeString[32];
+ trg_strlsize(sizeString, new_value);
+ g_object_set(object, "text", sizeString, NULL);
+ } else {
+ g_object_set(object, "text", "", NULL);
+ }
+ priv->size_value = new_value;
+ }
+ } else {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+trg_cell_renderer_size_class_init(TrgCellRendererSizeClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->get_property = trg_cell_renderer_size_get_property;
+ object_class->set_property = trg_cell_renderer_size_set_property;
+
+ g_object_class_install_property(object_class,
+ PROP_SIZE_VALUE,
+ g_param_spec_int64("size-value",
+ "Size Value",
+ "Size Value",
+ 0,
+ G_MAXINT64,
+ 0,
+ G_PARAM_READWRITE
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_type_class_add_private(klass, sizeof(TrgCellRendererSizePrivate));
+}
+
+static void trg_cell_renderer_size_init(TrgCellRendererSize * self)
+{
+ g_object_set(self, "xalign", 1.0f, NULL);
+}
+
+GtkCellRenderer *trg_cell_renderer_size_new(void)
+{
+ return
+ GTK_CELL_RENDERER(g_object_new(TRG_TYPE_CELL_RENDERER_SIZE, NULL));
+}
diff --git a/src/trg-cell-renderer-size.h b/src/trg-cell-renderer-size.h
new file mode 100644
index 0000000..a5d547b
--- /dev/null
+++ b/src/trg-cell-renderer-size.h
@@ -0,0 +1,51 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_CELL_RENDERER_SIZE_H_
+#define TRG_CELL_RENDERER_SIZE_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define TRG_TYPE_CELL_RENDERER_SIZE trg_cell_renderer_size_get_type()
+#define TRG_CELL_RENDERER_SIZE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_CELL_RENDERER_SIZE, TrgCellRendererSize))
+#define TRG_CELL_RENDERER_SIZE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_CELL_RENDERER_SIZE, TrgCellRendererSizeClass))
+#define TRG_IS_CELL_RENDERER_SIZE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_CELL_RENDERER_SIZE))
+#define TRG_IS_CELL_RENDERER_SIZE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_CELL_RENDERER_SIZE))
+#define TRG_CELL_RENDERER_SIZE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_CELL_RENDERER_SIZE, TrgCellRendererSizeClass))
+ typedef struct {
+ GtkCellRendererText parent;
+} TrgCellRendererSize;
+
+typedef struct {
+ GtkCellRendererTextClass parent_class;
+} TrgCellRendererSizeClass;
+
+GType trg_cell_renderer_size_get_type(void);
+
+GtkCellRenderer *trg_cell_renderer_size_new(void);
+
+G_END_DECLS
+#endif /* TRG_CELL_RENDERER_SIZE_H_ */
diff --git a/src/trg-cell-renderer-speed.c b/src/trg-cell-renderer-speed.c
new file mode 100644
index 0000000..a527b59
--- /dev/null
+++ b/src/trg-cell-renderer-speed.c
@@ -0,0 +1,116 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdint.h>
+#include <gtk/gtk.h>
+
+#include "trg-cell-renderer-speed.h"
+#include "util.h"
+
+enum {
+ PROP_0, PROP_SPEED_VALUE
+};
+
+G_DEFINE_TYPE(TrgCellRendererSpeed, trg_cell_renderer_speed,
+ GTK_TYPE_CELL_RENDERER_TEXT)
+#define TRG_CELL_RENDERER_SPEED_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_CELL_RENDERER_SPEED, TrgCellRendererSpeedPrivate))
+typedef struct _TrgCellRendererSpeedPrivate TrgCellRendererSpeedPrivate;
+
+struct _TrgCellRendererSpeedPrivate {
+ gint64 speed_value;
+};
+
+static void trg_cell_renderer_speed_get_property(GObject * object,
+ guint property_id,
+ GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererSpeedPrivate *priv =
+ TRG_CELL_RENDERER_SPEED_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_SPEED_VALUE:
+ g_value_set_int64(value, priv->speed_value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void trg_cell_renderer_speed_set_property(GObject * object,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererSpeedPrivate *priv =
+ TRG_CELL_RENDERER_SPEED_GET_PRIVATE(object);
+ if (property_id == PROP_SPEED_VALUE) {
+ gint64 new_value = g_value_get_int64(value);
+ if (new_value != priv->speed_value) {
+ if (new_value > 0) {
+ char speedString[32];
+ trg_strlspeed(speedString, new_value / disk_K);
+ g_object_set(object, "text", speedString, NULL);
+ } else {
+ g_object_set(object, "text", "", NULL);
+ }
+ priv->speed_value = new_value;
+ }
+ } else {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void trg_cell_renderer_speed_class_init(TrgCellRendererSpeedClass *
+ klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->get_property = trg_cell_renderer_speed_get_property;
+ object_class->set_property = trg_cell_renderer_speed_set_property;
+
+ g_object_class_install_property(object_class,
+ PROP_SPEED_VALUE,
+ g_param_spec_int64("speed-value",
+ "Speed Value",
+ "Speed Value",
+ 0,
+ G_MAXINT64,
+ 0,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_type_class_add_private(klass, sizeof(TrgCellRendererSpeedPrivate));
+}
+
+static void trg_cell_renderer_speed_init(TrgCellRendererSpeed * self)
+{
+ g_object_set(self, "xalign", 1.0f, NULL);
+}
+
+GtkCellRenderer *trg_cell_renderer_speed_new(void)
+{
+ return GTK_CELL_RENDERER(g_object_new
+ (TRG_TYPE_CELL_RENDERER_SPEED, NULL));
+}
diff --git a/src/trg-cell-renderer-speed.h b/src/trg-cell-renderer-speed.h
new file mode 100644
index 0000000..e74265a
--- /dev/null
+++ b/src/trg-cell-renderer-speed.h
@@ -0,0 +1,52 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#ifndef TRG_CELL_RENDERER_SPEED_H_
+#define TRG_CELL_RENDERER_SPEED_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define TRG_TYPE_CELL_RENDERER_SPEED trg_cell_renderer_speed_get_type()
+#define TRG_CELL_RENDERER_SPEED(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_CELL_RENDERER_SPEED, TrgCellRendererSpeed))
+#define TRG_CELL_RENDERER_SPEED_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_CELL_RENDERER_SPEED, TrgCellRendererSpeedClass))
+#define TRG_IS_CELL_RENDERER_SPEED(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_CELL_RENDERER_SPEED))
+#define TRG_IS_CELL_RENDERER_SPEED_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_CELL_RENDERER_SPEED))
+#define TRG_CELL_RENDERER_SPEED_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_CELL_RENDERER_SPEED, TrgCellRendererSpeedClass))
+ typedef struct {
+ GtkCellRendererText parent;
+} TrgCellRendererSpeed;
+
+typedef struct {
+ GtkCellRendererTextClass parent_class;
+} TrgCellRendererSpeedClass;
+
+GType trg_cell_renderer_speed_get_type(void);
+
+GtkCellRenderer *trg_cell_renderer_speed_new(void);
+
+G_END_DECLS
+#endif /* TRG_CELL_RENDERER_SPEED_H_ */
diff --git a/src/trg-cell-renderer-wanted.c b/src/trg-cell-renderer-wanted.c
new file mode 100644
index 0000000..c23aae1
--- /dev/null
+++ b/src/trg-cell-renderer-wanted.c
@@ -0,0 +1,115 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdint.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "trg-cell-renderer-wanted.h"
+#include "protocol-constants.h"
+#include "trg-files-model.h"
+#include "util.h"
+
+enum {
+ PROP_0,
+ PROP_WANTED_VALUE
+};
+
+G_DEFINE_TYPE(TrgCellRendererWanted, trg_cell_renderer_wanted,
+ GTK_TYPE_CELL_RENDERER_TOGGLE)
+#define TRG_CELL_RENDERER_WANTED_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_CELL_RENDERER_WANTED, TrgCellRendererWantedPrivate))
+typedef struct _TrgCellRendererWantedPrivate
+ TrgCellRendererWantedPrivate;
+
+struct _TrgCellRendererWantedPrivate {
+ gint wanted_value;
+};
+
+static void
+trg_cell_renderer_wanted_get_property(GObject * object,
+ guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ TrgCellRendererWantedPrivate *priv =
+ TRG_CELL_RENDERER_WANTED_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_WANTED_VALUE:
+ g_value_set_int(value, priv->wanted_value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_cell_renderer_wanted_set_property(GObject * object,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgCellRendererWantedPrivate *priv =
+ TRG_CELL_RENDERER_WANTED_GET_PRIVATE(object);
+
+ if (property_id == PROP_WANTED_VALUE) {
+ priv->wanted_value = g_value_get_int(value);
+ g_object_set(G_OBJECT(object), "inconsistent",
+ (priv->wanted_value == TR_PRI_MIXED), "active",
+ (priv->wanted_value == TRUE), NULL);
+ } else {
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ }
+}
+
+static void
+trg_cell_renderer_wanted_class_init(TrgCellRendererWantedClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->get_property = trg_cell_renderer_wanted_get_property;
+ object_class->set_property = trg_cell_renderer_wanted_set_property;
+
+ g_object_class_install_property(object_class,
+ PROP_WANTED_VALUE,
+ g_param_spec_int
+ ("wanted-value",
+ "Wanted Value",
+ "Wanted Value", TR_PRI_UNSET,
+ TRUE, TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_type_class_add_private(klass, sizeof(TrgCellRendererWantedPrivate));
+}
+
+static void trg_cell_renderer_wanted_init(TrgCellRendererWanted * self)
+{
+ /*g_object_set(G_OBJECT(self), "xalign", (gfloat) 0.5, "yalign", (gfloat) 0.5,
+ NULL); */
+}
+
+GtkCellRenderer *trg_cell_renderer_wanted_new(void)
+{
+ return
+ GTK_CELL_RENDERER(g_object_new
+ (TRG_TYPE_CELL_RENDERER_WANTED, NULL));
+}
diff --git a/src/trg-cell-renderer-wanted.h b/src/trg-cell-renderer-wanted.h
new file mode 100644
index 0000000..4a8720b
--- /dev/null
+++ b/src/trg-cell-renderer-wanted.h
@@ -0,0 +1,51 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_CELL_RENDERER_WANTED_H_
+#define TRG_CELL_RENDERER_WANTED_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define TRG_TYPE_CELL_RENDERER_WANTED trg_cell_renderer_wanted_get_type()
+#define TRG_CELL_RENDERER_WANTED(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_CELL_RENDERER_WANTED, TrgCellRendererWanted))
+#define TRG_CELL_RENDERER_WANTED_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_CELL_RENDERER_WANTED, TrgCellRendererWantedClass))
+#define TRG_IS_CELL_RENDERER_WANTED(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_CELL_RENDERER_WANTED))
+#define TRG_IS_CELL_RENDERER_WANTED_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_CELL_RENDERER_WANTED))
+#define TRG_CELL_RENDERER_WANTED_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_CELL_RENDERER_WANTED, TrgCellRendererWantedClass))
+ typedef struct {
+ GtkCellRendererToggle parent;
+} TrgCellRendererWanted;
+
+typedef struct {
+ GtkCellRendererToggleClass parent_class;
+} TrgCellRendererWantedClass;
+
+GType trg_cell_renderer_wanted_get_type(void);
+
+GtkCellRenderer *trg_cell_renderer_wanted_new(void);
+
+G_END_DECLS
+#endif /* TRG_CELL_RENDERER_WANTED_H_ */
diff --git a/src/trg-client.c b/src/trg-client.c
new file mode 100644
index 0000000..f20677a
--- /dev/null
+++ b/src/trg-client.c
@@ -0,0 +1,706 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <glib/gi18n.h>
+
+#ifdef HAVE_LIBPROXY
+#include <proxy.h>
+#endif
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+
+#include "json.h"
+#include "trg-prefs.h"
+#include "protocol-constants.h"
+#include "util.h"
+#include "requests.h"
+#include "trg-client.h"
+
+/* This class manages/does quite a few things, and is passed around a lot. It:
+ *
+ * 1) Holds/inits the single TrgPrefs object for managing configuration.
+ * 2) Manages a thread pool for making requests
+ * (each thread has its own CURL client in thread local storage)
+ * 3) Holds current connection details needed by CURL clients.
+ * (session ID, username, password, URL, ssl, proxy)
+ * 4) Holds a hash table for looking up a torrent by its ID.
+ * 5) Dispatches synchronous/asyncrhonous requests and tracks failures.
+ * 6) Holds connection state, an update serial, and provides signals for
+ * connect/disconnect.
+ * 7) Provides a mutex for locking updates.
+ * 8) Holds the latest session object sent in a session-get response.
+ */
+
+G_DEFINE_TYPE(TrgClient, trg_client, G_TYPE_OBJECT)
+enum {
+ TC_SESSION_UPDATED, TC_SIGNAL_COUNT
+};
+
+static guint signals[TC_SIGNAL_COUNT] = { 0 };
+
+struct _TrgClientPrivate {
+ char *session_id;
+ gint connid;
+ guint failCount;
+ guint retries;
+ guint timeout;
+ gint64 updateSerial;
+ JsonObject *session;
+ gboolean ssl;
+ gdouble version;
+ char *url;
+ char *username;
+ char *password;
+ char *proxy;
+ GHashTable *torrentTable;
+ GThreadPool *pool;
+ TrgPrefs *prefs;
+ GPrivate *tlsKey;
+ gint configSerial;
+ GMutex *configMutex;
+ gboolean seedRatioLimited;
+ gdouble seedRatioLimit;
+};
+
+static void dispatch_async_threadfunc(trg_request * reqrsp,
+ TrgClient * tc);
+
+static void
+trg_client_get_property(GObject * object, guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ switch (property_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_client_set_property(GObject * object, guint property_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ switch (property_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void trg_client_dispose(GObject * object)
+{
+ G_OBJECT_CLASS(trg_client_parent_class)->dispose(object);
+}
+
+static void trg_client_class_init(TrgClientClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgClientPrivate));
+
+ object_class->get_property = trg_client_get_property;
+ object_class->set_property = trg_client_set_property;
+ object_class->dispose = trg_client_dispose;
+
+ signals[TC_SESSION_UPDATED] = g_signal_new("session-updated",
+ G_TYPE_FROM_CLASS
+ (object_class),
+ G_SIGNAL_RUN_LAST |
+ G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET
+ (TrgClientClass,
+ session_updated), NULL,
+ NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+}
+
+static void trg_client_init(TrgClient * self)
+{
+ self->priv =
+ G_TYPE_INSTANCE_GET_PRIVATE(self, TRG_TYPE_CLIENT,
+ TrgClientPrivate);
+}
+
+TrgClient *trg_client_new(void)
+{
+ TrgClient *tc = g_object_new(TRG_TYPE_CLIENT, NULL);
+ TrgClientPrivate *priv = tc->priv;
+ TrgPrefs *prefs = priv->prefs = trg_prefs_new();
+
+ trg_prefs_load(prefs);
+
+ priv->configMutex = g_mutex_new();
+ priv->tlsKey = g_private_new(NULL);
+ priv->seedRatioLimited = FALSE;
+ priv->seedRatioLimit = 0.00;
+
+ priv->pool = g_thread_pool_new((GFunc) dispatch_async_threadfunc, tc,
+ DISPATCH_POOL_SIZE, TRUE, NULL);
+
+ tr_formatter_size_init(disk_K, _(disk_K_str), _(disk_M_str),
+ _(disk_G_str), _(disk_T_str));
+ tr_formatter_speed_init(speed_K, _(speed_K_str), _(speed_M_str),
+ _(speed_G_str), _(speed_T_str));
+
+ return tc;
+}
+
+const gchar *trg_client_get_version_string(TrgClient * tc)
+{
+ return session_get_version_string(tc->priv->session);
+}
+
+gdouble trg_client_get_version(TrgClient * tc)
+{
+ return tc->priv->version;
+}
+
+gint64 trg_client_get_rpc_version(TrgClient * tc)
+{
+ TrgClientPrivate *priv = tc->priv;
+ return session_get_rpc_version(priv->session);
+}
+
+void trg_client_inc_connid(TrgClient * tc)
+{
+ TrgClientPrivate *priv = tc->priv;
+ g_atomic_int_inc(&priv->connid);
+}
+
+void trg_client_set_session(TrgClient * tc, JsonObject * session)
+{
+ TrgClientPrivate *priv = tc->priv;
+
+ if (priv->session)
+ json_object_unref(priv->session);
+ else
+ priv->version = session_get_version(session);
+
+ priv->session = session;
+ json_object_ref(session);
+
+ priv->seedRatioLimit = session_get_seed_ratio_limit(session);
+ priv->seedRatioLimited = session_get_seed_ratio_limited(session);
+
+ g_signal_emit(tc, signals[TC_SESSION_UPDATED], 0, session);
+}
+
+TrgPrefs *trg_client_get_prefs(TrgClient * tc)
+{
+ return tc->priv->prefs;
+}
+
+int trg_client_populate_with_settings(TrgClient * tc)
+{
+ TrgClientPrivate *priv = tc->priv;
+ TrgPrefs *prefs = priv->prefs;
+
+ gint port;
+ gchar *host, *path;
+#ifdef HAVE_LIBPROXY
+ pxProxyFactory *pf = NULL;
+#endif
+
+ g_mutex_lock(priv->configMutex);
+
+ trg_prefs_set_connection(prefs, trg_prefs_get_profile(prefs));
+
+ g_free(priv->url);
+ priv->url = NULL;
+
+ g_free(priv->username);
+ priv->username = NULL;
+
+ g_free(priv->password);
+ priv->password = NULL;
+
+ port =
+ trg_prefs_get_int(prefs, TRG_PREFS_KEY_PORT, TRG_PREFS_CONNECTION);
+
+ host = trg_prefs_get_string(prefs, TRG_PREFS_KEY_HOSTNAME,
+ TRG_PREFS_CONNECTION);
+ path = trg_prefs_get_string(prefs, TRG_PREFS_KEY_RPC_URL_PATH, TRG_PREFS_CONNECTION);
+
+ if (!host || strlen(host) < 1) {
+ g_free(host);
+ g_mutex_unlock(priv->configMutex);
+ return TRG_NO_HOSTNAME_SET;
+ }
+#ifndef CURL_NO_SSL
+ priv->ssl = trg_prefs_get_bool(prefs, TRG_PREFS_KEY_SSL,
+ TRG_PREFS_CONNECTION);
+#else
+ priv->ssl = FALSE;
+#endif
+
+
+ priv->url = g_strdup_printf("%s://%s:%d%s",
+ priv->ssl ? HTTPS_URI_PREFIX :
+ HTTP_URI_PREFIX, host, port, path);
+ g_free(host);
+ g_free(path);
+
+ priv->username = trg_prefs_get_string(prefs, TRG_PREFS_KEY_USERNAME,
+ TRG_PREFS_CONNECTION);
+
+ priv->password = trg_prefs_get_string(prefs, TRG_PREFS_KEY_PASSWORD,
+ TRG_PREFS_CONNECTION);
+
+ g_free(priv->proxy);
+ priv->proxy = NULL;
+
+#ifdef HAVE_LIBPROXY
+ if ((pf = px_proxy_factory_new())) {
+ char **proxies = px_proxy_factory_get_proxies(pf, priv->url);
+ int i;
+
+ for (i = 0; proxies[i]; i++) {
+ if (g_str_has_prefix(proxies[i], HTTP_URI_PREFIX)
+ || g_str_has_prefix(proxies[i], HTTPS_URI_PREFIX)) {
+ g_free(priv->proxy);
+ priv->proxy = proxies[i];
+ } else {
+ g_free(proxies[i]);
+ }
+ }
+
+ g_free(proxies);
+ px_proxy_factory_free(pf);
+ }
+#endif
+
+ priv->configSerial++;
+ g_mutex_unlock(priv->configMutex);
+ return 0;
+}
+
+gchar *trg_client_get_password(TrgClient * tc)
+{
+ return tc->priv->password;
+}
+
+gchar *trg_client_get_username(TrgClient * tc)
+{
+ return tc->priv->username;
+}
+
+gchar *trg_client_get_url(TrgClient * tc)
+{
+ return tc->priv->url;
+}
+
+gchar *trg_client_get_session_id(TrgClient * tc)
+{
+ TrgClientPrivate *priv = tc->priv;
+ return priv->session_id ? g_strdup(priv->session_id) : NULL;
+}
+
+void trg_client_set_session_id(TrgClient * tc, gchar * session_id)
+{
+ TrgClientPrivate *priv = tc->priv;
+
+ g_mutex_lock(priv->configMutex);
+
+ if (priv->session_id)
+ g_free(priv->session_id);
+
+ priv->session_id = session_id;
+
+ g_mutex_unlock(priv->configMutex);
+}
+
+void trg_client_status_change(TrgClient * tc, gboolean connected)
+{
+ TrgClientPrivate *priv = tc->priv;
+
+ if (!connected) {
+ if (priv->session) {
+ json_object_unref(priv->session);
+ priv->session = NULL;
+ }
+ g_mutex_lock(priv->configMutex);
+ trg_prefs_set_connection(priv->prefs, NULL);
+ g_mutex_unlock(priv->configMutex);
+ }
+}
+
+JsonObject *trg_client_get_session(TrgClient * tc)
+{
+ return tc->priv->session;
+}
+
+void
+trg_client_thread_pool_push(TrgClient * tc, gpointer data, GError ** err)
+{
+ g_thread_pool_push(tc->priv->pool, data, err);
+}
+
+void trg_client_inc_serial(TrgClient * tc)
+{
+ TrgClientPrivate *priv = tc->priv;
+ priv->updateSerial++;
+}
+
+gint64 trg_client_get_serial(TrgClient * tc)
+{
+ return tc->priv->updateSerial;
+}
+
+#ifndef CURL_NO_SSL
+gboolean trg_client_get_ssl(TrgClient * tc)
+{
+ return tc->priv->ssl;
+}
+#endif
+
+gchar *trg_client_get_proxy(TrgClient * tc)
+{
+ return tc->priv->proxy;
+}
+
+void trg_client_set_torrent_table(TrgClient * tc, GHashTable * table)
+{
+ TrgClientPrivate *priv = tc->priv;
+ priv->torrentTable = table;
+}
+
+GHashTable *trg_client_get_torrent_table(TrgClient * tc)
+{
+ TrgClientPrivate *priv = tc->priv;
+ return priv->torrentTable;
+}
+
+gboolean trg_client_is_connected(TrgClient * tc)
+{
+ TrgClientPrivate *priv = tc->priv;
+ return priv->session != NULL;
+}
+
+void trg_client_configlock(TrgClient * tc)
+{
+ g_mutex_lock(tc->priv->configMutex);
+}
+
+guint trg_client_get_failcount(TrgClient * tc)
+{
+ return tc->priv->failCount;
+}
+
+guint trg_client_inc_failcount(TrgClient * tc)
+{
+ TrgClientPrivate *priv = tc->priv;
+ return ++(priv->failCount);
+}
+
+void trg_client_reset_failcount(TrgClient * tc)
+{
+ tc->priv->failCount = 0;
+}
+
+void trg_client_configunlock(TrgClient * tc)
+{
+ g_mutex_unlock(tc->priv->configMutex);
+}
+
+/* formerly http.c */
+
+void trg_response_free(trg_response * response)
+{
+ if (response->obj)
+ json_object_unref(response->obj);
+ g_free(response);
+}
+
+static size_t
+http_receive_callback(void *ptr, size_t size, size_t nmemb, void *data)
+{
+ size_t realsize = size * nmemb;
+ trg_response *mem = (trg_response *) data;
+
+ mem->raw = g_realloc(mem->raw, mem->size + realsize + 1);
+ if (mem->raw) {
+ memcpy(&(mem->raw[mem->size]), ptr, realsize);
+ mem->size += realsize;
+ mem->raw[mem->size] = 0;
+ }
+
+ return realsize;
+}
+
+static size_t
+header_callback(void *ptr, size_t size, size_t nmemb, void *data)
+{
+ char *header = (char *) (ptr);
+ TrgClient *tc = TRG_CLIENT(data);
+ gchar *session_id;
+
+ if (g_str_has_prefix(header, X_TRANSMISSION_SESSION_ID_HEADER_PREFIX)) {
+ char *nl;
+
+ session_id = g_strdup(header);
+ nl = strrchr(session_id, '\r');
+ if (nl)
+ *nl = '\0';
+
+ trg_client_set_session_id(tc, session_id);
+ }
+
+ return (nmemb * size);
+}
+
+static void trg_tls_update(TrgClient * tc, trg_tls * tls, gint serial)
+{
+ gchar *proxy;
+
+ curl_easy_setopt(tls->curl, CURLOPT_PASSWORD,
+ trg_client_get_password(tc));
+ curl_easy_setopt(tls->curl, CURLOPT_USERNAME,
+ trg_client_get_username(tc));
+ curl_easy_setopt(tls->curl, CURLOPT_URL, trg_client_get_url(tc));
+
+#ifndef CURL_NO_SSL
+ if (trg_client_get_ssl(tc))
+ curl_easy_setopt(tls->curl, CURLOPT_SSL_VERIFYPEER, 0);
+#endif
+
+ proxy = trg_client_get_proxy(tc);
+ if (proxy) {
+ curl_easy_setopt(tls->curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
+ curl_easy_setopt(tls->curl, CURLOPT_PROXY, proxy);
+ }
+
+ tls->serial = serial;
+}
+
+trg_tls *trg_tls_new(TrgClient * tc)
+{
+ trg_tls *tls = g_new0(trg_tls, 1);
+
+ tls->curl = curl_easy_init();
+ curl_easy_setopt(tls->curl, CURLOPT_USERAGENT, PACKAGE_NAME);
+ curl_easy_setopt(tls->curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
+ curl_easy_setopt(tls->curl, CURLOPT_WRITEFUNCTION,
+ &http_receive_callback);
+ curl_easy_setopt(tls->curl, CURLOPT_HEADERFUNCTION, &header_callback);
+ curl_easy_setopt(tls->curl, CURLOPT_WRITEHEADER, (void *) tc);
+
+ tls->serial = -1;
+
+ return tls;
+}
+
+static int
+trg_http_perform_inner(TrgClient * tc, gchar * reqstr,
+ trg_response * response, gboolean recurse)
+{
+ TrgClientPrivate *priv = tc->priv;
+ TrgPrefs *prefs = trg_client_get_prefs(tc);
+ gpointer threadLocalStorage = g_private_get(priv->tlsKey);
+ trg_tls *tls;
+ long httpCode = 0;
+ gchar *session_id;
+ struct curl_slist *headers = NULL;
+
+ if (!threadLocalStorage) {
+ tls = trg_tls_new(tc);
+ g_private_set(priv->tlsKey, tls);
+ } else {
+ tls = (trg_tls *) threadLocalStorage;
+ }
+
+ g_mutex_lock(priv->configMutex);
+
+ if (priv->configSerial > tls->serial)
+ trg_tls_update(tc, tls, priv->configSerial);
+
+ session_id = trg_client_get_session_id(tc);
+ if (session_id) {
+ headers = curl_slist_append(NULL, session_id);
+ curl_easy_setopt(tls->curl, CURLOPT_HTTPHEADER, headers);
+ }
+
+ curl_easy_setopt(tls->curl, CURLOPT_TIMEOUT,
+ (long) trg_prefs_get_int(prefs, TRG_PREFS_KEY_TIMEOUT,
+ TRG_PREFS_CONNECTION));
+
+ g_mutex_unlock(priv->configMutex);
+
+ response->size = 0;
+ response->raw = NULL;
+
+ curl_easy_setopt(tls->curl, CURLOPT_POSTFIELDS, reqstr);
+ curl_easy_setopt(tls->curl, CURLOPT_WRITEDATA, (void *) response);
+ response->status = curl_easy_perform(tls->curl);
+
+ if (session_id) {
+ g_free(session_id);
+ curl_slist_free_all(headers);
+ }
+
+ curl_easy_getinfo(tls->curl, CURLINFO_RESPONSE_CODE, &httpCode);
+
+ if (response->status == CURLE_OK) {
+ if (httpCode == HTTP_CONFLICT && recurse == TRUE)
+ return trg_http_perform_inner(tc, reqstr, response, FALSE);
+ else if (httpCode != HTTP_OK)
+ response->status = (-httpCode) - 100;
+ }
+
+ return response->status;
+}
+
+int trg_http_perform(TrgClient * tc, gchar * reqstr, trg_response * reqrsp)
+{
+ return trg_http_perform_inner(tc, reqstr, reqrsp, TRUE);
+}
+
+/* formerly dispatch.c */
+
+trg_response *dispatch(TrgClient * tc, JsonNode * req)
+{
+ gchar *serialized = trg_serialize(req);
+ json_node_free(req);
+#ifdef DEBUG
+ if (g_getenv("TRG_SHOW_OUTGOING"))
+ g_debug("=>(OUTgoing)=>: %s", serialized);
+#endif
+ return dispatch_str(tc, serialized);
+}
+
+trg_response *dispatch_str(TrgClient * tc, gchar * req)
+{
+ trg_response *response = g_new0(trg_response, 1);
+ GError *decode_error = NULL;
+ JsonNode *result;
+
+ trg_http_perform(tc, req, response);
+ g_free(req);
+
+ if (response->status == CURLE_OK)
+ response->obj = trg_deserialize(response, &decode_error);
+
+ g_free(response->raw);
+ response->raw = NULL;
+
+ if (response->status != CURLE_OK)
+ return response;
+
+ if (decode_error) {
+ g_error("JSON decoding error: %s", decode_error->message);
+ g_error_free(decode_error);
+ response->status = FAIL_JSON_DECODE;
+ return response;
+ }
+
+ result = json_object_get_member(response->obj, FIELD_RESULT);
+ if (!result || g_strcmp0(json_node_get_string(result), FIELD_SUCCESS))
+ response->status = FAIL_RESPONSE_UNSUCCESSFUL;
+
+ return response;
+}
+
+static void dispatch_async_threadfunc(trg_request * req, TrgClient * tc)
+{
+ TrgClientPrivate *priv = tc->priv;
+
+ trg_response *rsp;
+
+ if (req->str)
+ rsp = dispatch_str(tc, req->str);
+ else
+ rsp = dispatch(tc, req->node);
+
+ rsp->cb_data = req->cb_data;
+
+ if (req->callback && req->connid == g_atomic_int_get(&priv->connid))
+ g_idle_add(req->callback, rsp);
+ else
+ trg_response_free(rsp);
+
+ g_free(req);
+}
+
+static gboolean
+dispatch_async_common(TrgClient * tc,
+ trg_request * trg_req,
+ GSourceFunc callback, gpointer data)
+{
+ TrgClientPrivate *priv = tc->priv;
+ GError *error = NULL;
+
+ trg_req->callback = callback;
+ trg_req->cb_data = data;
+ trg_req->connid = g_atomic_int_get(&priv->connid);
+
+ trg_client_thread_pool_push(tc, trg_req, &error);
+ if (error) {
+ g_error("thread creation error: %s\n", error->message);
+ g_error_free(error);
+ g_free(trg_req);
+ return FALSE;
+ } else {
+ return TRUE;
+ }
+}
+
+gboolean
+dispatch_async(TrgClient * tc, JsonNode * req,
+ GSourceFunc callback, gpointer data)
+{
+ trg_request *trg_req = g_new0(trg_request, 1);
+ trg_req->node = req;
+
+ return dispatch_async_common(tc, trg_req, callback, data);
+}
+
+gboolean
+dispatch_async_str(TrgClient * tc, gchar * req,
+ GSourceFunc callback, gpointer data)
+{
+ trg_request *trg_req = g_new0(trg_request, 1);
+ trg_req->str = req;
+
+ return dispatch_async_common(tc, trg_req, callback, data);
+}
+
+gboolean trg_client_update_session(TrgClient * tc, GSourceFunc callback,
+ gpointer data)
+{
+ return dispatch_async(tc, session_get(), callback, data);
+}
+
+gdouble trg_client_get_seed_ratio_limit(TrgClient * tc)
+{
+ return tc->priv->seedRatioLimit;
+}
+
+gboolean trg_client_get_seed_ratio_limited(TrgClient * tc)
+{
+ return tc->priv->seedRatioLimited;
+}
diff --git a/src/trg-client.h b/src/trg-client.h
new file mode 100644
index 0000000..3b7d916
--- /dev/null
+++ b/src/trg-client.h
@@ -0,0 +1,167 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* trg-client.h */
+
+#ifndef _TRG_CLIENT_H_
+#define _TRG_CLIENT_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+
+#include <json-glib/json-glib.h>
+#include <glib-object.h>
+
+#include "trg-prefs.h"
+#include "session-get.h"
+
+#define TRANSMISSION_MIN_SUPPORTED 2.0
+#define X_TRANSMISSION_SESSION_ID_HEADER_PREFIX "X-Transmission-Session-Id: "
+
+#define TORRENT_GET_MODE_FIRST 0
+#define TORRENT_GET_MODE_ACTIVE 1
+#define TORRENT_GET_MODE_INTERACTION 2
+#define TORRENT_GET_MODE_UPDATE 3
+
+#define TORRENT_GET_TAG_MODE_FULL -1
+#define TORRENT_GET_TAG_MODE_UPDATE -2
+
+#define TRG_NO_HOSTNAME_SET -2
+
+#define HTTP_URI_PREFIX "http"
+#define HTTPS_URI_PREFIX "https"
+#define HTTP_OK 200
+#define HTTP_CONFLICT 409
+
+#define FAIL_JSON_DECODE -2
+#define FAIL_RESPONSE_UNSUCCESSFUL -3
+#define DISPATCH_POOL_SIZE 3
+
+typedef struct {
+ int status;
+ int size;
+ char *raw;
+ JsonObject *obj;
+ gpointer cb_data;
+} trg_response;
+
+typedef struct {
+ gint connid;
+ JsonNode *node;
+ gchar *str;
+ GSourceFunc callback;
+ gpointer cb_data;
+} trg_request;
+
+typedef struct _TrgClientPrivate TrgClientPrivate;
+
+G_BEGIN_DECLS
+#define TRG_TYPE_CLIENT trg_client_get_type()
+#define TRG_CLIENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_CLIENT, TrgClient))
+#define TRG_CLIENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_CLIENT, TrgClientClass))
+#define TRG_IS_CLIENT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_CLIENT))
+#define TRG_IS_CLIENT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_CLIENT))
+#define TRG_CLIENT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_CLIENT, TrgClientClass))
+ typedef struct {
+ GObject parent;
+ TrgClientPrivate *priv;
+} TrgClient;
+
+typedef struct {
+ GObjectClass parent_class;
+ void (*session_updated) (TrgClient * tc, JsonObject * session,
+ gpointer data);
+
+} TrgClientClass;
+
+/* Thread local storage (TLS).
+ * CURL clients can't be used concurrently.
+ * So create one instance for each thread in the thread pool.
+ */
+typedef struct {
+ /* Use a serial to figure out when there's been a configuration change
+ * by comparing with priv->serial.
+ * We lock updating (and checking for updates) with priv->configMutex
+ */
+ int serial;
+ CURL *curl;
+} trg_tls;
+
+/* stuff that used to be in http.h */
+void trg_response_free(trg_response * response);
+int trg_http_perform(TrgClient * client, gchar * reqstr,
+ trg_response * reqrsp);
+/* end http.h*/
+
+/* stuff that used to be in dispatch.c */
+trg_response *dispatch(TrgClient * client, JsonNode * req);
+trg_response *dispatch_str(TrgClient * client, gchar * req);
+gboolean dispatch_async(TrgClient * client, JsonNode * req,
+ GSourceFunc callback, gpointer data);
+/* end dispatch.c*/
+
+GType trg_client_get_type(void);
+
+TrgClient *trg_client_new(void);
+TrgPrefs *trg_client_get_prefs(TrgClient * tc);
+int trg_client_populate_with_settings(TrgClient * tc);
+void trg_client_set_session(TrgClient * tc, JsonObject * session);
+gdouble trg_client_get_version(TrgClient * tc);
+const gchar *trg_client_get_version_string(TrgClient * tc);
+gint64 trg_client_get_rpc_version(TrgClient * tc);
+gchar *trg_client_get_password(TrgClient * tc);
+gchar *trg_client_get_username(TrgClient * tc);
+gchar *trg_client_get_url(TrgClient * tc);
+gchar *trg_client_get_session_id(TrgClient * tc);
+void trg_client_set_session_id(TrgClient * tc, gchar * session_id);
+#ifndef CURL_NO_SSL
+gboolean trg_client_get_ssl(TrgClient * tc);
+#endif
+gchar *trg_client_get_proxy(TrgClient * tc);
+gint64 trg_client_get_serial(TrgClient * tc);
+void trg_client_thread_pool_push(TrgClient * tc, gpointer data,
+ GError ** err);
+void trg_client_set_torrent_table(TrgClient * tc, GHashTable * table);
+GHashTable *trg_client_get_torrent_table(TrgClient * tc);
+JsonObject *trg_client_get_session(TrgClient * tc);
+void trg_client_status_change(TrgClient * tc, gboolean connected);
+gboolean trg_client_is_connected(TrgClient * tc);
+void trg_client_configunlock(TrgClient * tc);
+void trg_client_configlock(TrgClient * tc);
+guint trg_client_inc_failcount(TrgClient * tc);
+guint trg_client_get_failcount(TrgClient * tc);
+void trg_client_reset_failcount(TrgClient * tc);
+void trg_client_inc_serial(TrgClient * tc);
+void trg_client_inc_connid(TrgClient * tc);
+gboolean trg_client_update_session(TrgClient * tc, GSourceFunc callback,
+ gpointer data);
+gboolean trg_client_get_seed_ratio_limited(TrgClient * tc);
+gdouble trg_client_get_seed_ratio_limit(TrgClient * tc);
+
+G_END_DECLS
+#endif /* _TRG_CLIENT_H_ */
diff --git a/src/trg-destination-combo.c b/src/trg-destination-combo.c
new file mode 100644
index 0000000..d2e3eb0
--- /dev/null
+++ b/src/trg-destination-combo.c
@@ -0,0 +1,466 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "trg-client.h"
+#include "torrent.h"
+#include "trg-torrent-model.h"
+#include "trg-destination-combo.h"
+#include "util.h"
+
+G_DEFINE_TYPE(TrgDestinationCombo, trg_destination_combo,
+ GTK_TYPE_COMBO_BOX)
+#define TRG_DESTINATION_COMBO_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_DESTINATION_COMBO, TrgDestinationComboPrivate))
+typedef struct _TrgDestinationComboPrivate TrgDestinationComboPrivate;
+
+struct _TrgDestinationComboPrivate {
+ TrgClient *client;
+ gchar *last_selection;
+ GtkWidget *entry;
+ GtkCellRenderer *text_renderer;
+};
+
+enum {
+ PROP_0, PROP_CLIENT, PROP_LAST_SELECTION
+};
+
+enum {
+ DEST_DEFAULT, DEST_LABEL, DEST_EXISTING, DEST_USERADD
+};
+
+enum {
+ DEST_COLUMN_LABEL, DEST_COLUMN_DIR, DEST_COLUMN_TYPE, N_DEST_COLUMNS
+};
+
+static void trg_destination_combo_finalize(GObject * object)
+{
+ TrgDestinationComboPrivate *priv =
+ TRG_DESTINATION_COMBO_GET_PRIVATE(object);
+ g_free(priv->last_selection);
+}
+
+static void
+trg_destination_combo_get_property(GObject * object,
+ guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ TrgDestinationComboPrivate *priv =
+ TRG_DESTINATION_COMBO_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_CLIENT:
+ g_value_set_pointer(value, priv->client);
+ break;
+ case PROP_LAST_SELECTION:
+ g_value_set_string(value, priv->last_selection);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_destination_combo_set_property(GObject * object,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgDestinationComboPrivate *priv =
+ TRG_DESTINATION_COMBO_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_CLIENT:
+ priv->client = g_value_get_pointer(value);
+ break;
+ case PROP_LAST_SELECTION:
+ priv->last_selection = g_strdup(g_value_get_string(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean g_slist_str_set_add(GSList ** list, const gchar * string)
+{
+ GSList *li;
+ for (li = *list; li; li = g_slist_next(li))
+ if (!g_strcmp0((gchar *) li->data, string))
+ return FALSE;
+
+ *list = g_slist_insert_sorted(*list, (gpointer) string,
+ (GCompareFunc) g_strcmp0);
+
+ return TRUE;
+}
+
+void trg_destination_combo_save_selection(TrgDestinationCombo * combo_box)
+{
+ TrgDestinationComboPrivate *priv =
+ TRG_DESTINATION_COMBO_GET_PRIVATE(combo_box);
+ GtkTreeIter iter;
+
+ if (priv->last_selection
+ && gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo_box), &iter))
+ {
+ GtkTreeModel *model =
+ gtk_combo_box_get_model(GTK_COMBO_BOX(combo_box));
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+ gchar *text;
+
+ gtk_tree_model_get(model, &iter, DEST_COLUMN_LABEL, &text, -1);
+ trg_prefs_set_string(prefs, priv->last_selection, text,
+ TRG_PREFS_CONNECTION);
+ g_free(text);
+ }
+}
+
+static void
+gtk_combo_box_entry_active_changed(GtkComboBox * combo_box,
+ gpointer user_data)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean editableEntry = TRUE;
+
+ if (gtk_combo_box_get_active_iter(combo_box, &iter)) {
+ GtkEntry *entry =
+ trg_destination_combo_get_entry(TRG_DESTINATION_COMBO
+ (combo_box));
+
+ if (entry) {
+ GValue value = { 0, };
+ guint type;
+
+ model = gtk_combo_box_get_model(combo_box);
+
+ gtk_tree_model_get_value(model, &iter, DEST_COLUMN_LABEL,
+ &value);
+ gtk_tree_model_get(model, &iter, DEST_COLUMN_TYPE, &type, -1);
+
+ g_object_set_property(G_OBJECT(entry), "text", &value);
+ g_value_unset(&value);
+
+ if (type == DEST_LABEL)
+ editableEntry = FALSE;
+ }
+ }
+#if GTK_CHECK_VERSION( 3, 0, 0 )
+ gtk_editable_set_editable(GTK_EDITABLE
+ (trg_destination_combo_get_entry
+ (TRG_DESTINATION_COMBO(combo_box))),
+ editableEntry);
+#else
+ gtk_entry_set_editable(trg_destination_combo_get_entry
+ (TRG_DESTINATION_COMBO(combo_box)),
+ editableEntry);
+#endif
+}
+
+gboolean trg_destination_combo_has_text(TrgDestinationCombo * combo)
+{
+ const gchar *text =
+ gtk_entry_get_text(trg_destination_combo_get_entry
+ (TRG_DESTINATION_COMBO(combo)));
+ return strlen(text) > 0;
+}
+
+GtkEntry *trg_destination_combo_get_entry(TrgDestinationCombo * combo)
+{
+ TrgDestinationComboPrivate *priv =
+ TRG_DESTINATION_COMBO_GET_PRIVATE(combo);
+ return GTK_ENTRY(priv->entry);
+}
+
+static void
+add_entry_cb(GtkEntry * entry,
+ GtkEntryIconPosition icon_pos,
+ GdkEvent * event, gpointer user_data)
+{
+ GtkComboBox *combo = GTK_COMBO_BOX(user_data);
+ GtkTreeModel *model = gtk_combo_box_get_model(combo);
+ GtkTreeIter iter;
+
+ gtk_list_store_insert_with_values(GTK_LIST_STORE(model), &iter,
+ INT_MAX, DEST_COLUMN_LABEL, "",
+ DEST_COLUMN_DIR, "",
+ DEST_COLUMN_TYPE, DEST_USERADD, -1);
+ gtk_combo_box_set_active_iter(combo, &iter);
+}
+
+struct findDupeArg {
+ const gchar *dir;
+ gboolean isDupe;
+};
+
+gboolean
+trg_destination_combo_insert_check_dupe_foreach(GtkTreeModel * model,
+ GtkTreePath *
+ path G_GNUC_UNUSED,
+ GtkTreeIter * iter,
+ struct findDupeArg *args)
+{
+ gchar *existing;
+ gtk_tree_model_get(model, iter, DEST_COLUMN_DIR, &existing, -1);
+ args->isDupe = g_strcmp0(existing, args->dir) == 0;
+ g_free(existing);
+ return args->isDupe;
+}
+
+static void
+trg_destination_combo_insert(GtkComboBox * box,
+ const gchar * label,
+ const gchar * dir, guint type,
+ const gchar * lastDestination)
+{
+ GtkTreeModel *model = gtk_combo_box_get_model(box);
+ gchar *comboLabel;
+ GtkTreeIter iter;
+
+ if (type == DEST_EXISTING) {
+ struct findDupeArg args;
+ args.isDupe = FALSE;
+ args.dir = dir;
+ gtk_tree_model_foreach(GTK_TREE_MODEL(model),
+ (GtkTreeModelForeachFunc)
+ trg_destination_combo_insert_check_dupe_foreach,
+ &args);
+ if (args.isDupe)
+ return;
+ }
+
+ comboLabel =
+ label ? g_strdup_printf("%s (%s)", label, dir) : g_strdup(dir);
+
+ gtk_list_store_insert_with_values(GTK_LIST_STORE(model), &iter,
+ INT_MAX, DEST_COLUMN_LABEL,
+ comboLabel, DEST_COLUMN_DIR, dir,
+ DEST_COLUMN_TYPE, type, -1);
+
+ if (lastDestination && !g_strcmp0(lastDestination, comboLabel))
+ gtk_combo_box_set_active_iter(box, &iter);
+
+ g_free(comboLabel);
+}
+
+static GObject *trg_destination_combo_constructor(GType type,
+ guint
+ n_construct_properties,
+ GObjectConstructParam *
+ construct_params)
+{
+ GObject *object = G_OBJECT_CLASS
+ (trg_destination_combo_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+ TrgDestinationComboPrivate *priv =
+ TRG_DESTINATION_COMBO_GET_PRIVATE(object);
+
+ TrgClient *client = priv->client;
+ TrgPrefs *prefs = trg_client_get_prefs(client);
+
+ GSList *dirs = NULL, *sli;
+ GList *li, *list;
+ GtkTreeRowReference *rr;
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkListStore *comboModel;
+ JsonArray *savedDestinations;
+ gchar *defaultDir;
+ gchar *lastDestination = NULL;
+
+ comboModel = gtk_list_store_new(N_DEST_COLUMNS, G_TYPE_STRING,
+ G_TYPE_STRING, G_TYPE_UINT);
+ gtk_combo_box_set_model(GTK_COMBO_BOX(object),
+ GTK_TREE_MODEL(comboModel));
+ g_object_unref(comboModel);
+
+ g_signal_connect(object, "changed",
+ G_CALLBACK(gtk_combo_box_entry_active_changed), NULL);
+
+ priv->entry = gtk_entry_new();
+ gtk_container_add(GTK_CONTAINER(object), priv->entry);
+
+ priv->text_renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(object),
+ priv->text_renderer, TRUE);
+
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(object),
+ priv->text_renderer, "text", 0, NULL);
+
+ g_slist_foreach(dirs, (GFunc) g_free, NULL);
+ g_slist_free(dirs);
+
+ gtk_entry_set_icon_from_stock(GTK_ENTRY(priv->entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ GTK_STOCK_CLEAR);
+
+ g_signal_connect(priv->entry, "icon-release",
+ G_CALLBACK(add_entry_cb), object);
+
+ defaultDir =
+ g_strdup(session_get_download_dir(trg_client_get_session(client)));
+ rm_trailing_slashes(defaultDir);
+
+ savedDestinations =
+ trg_prefs_get_array(prefs, TRG_PREFS_KEY_DESTINATIONS,
+ TRG_PREFS_CONNECTION);
+
+ if (priv->last_selection)
+ lastDestination = trg_prefs_get_string(prefs, priv->last_selection,
+ TRG_PREFS_CONNECTION);
+
+ trg_destination_combo_insert(GTK_COMBO_BOX(object),
+ NULL,
+ defaultDir, DEST_DEFAULT,
+ lastDestination);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(object), 0);
+
+ if (savedDestinations) {
+ list = json_array_get_elements(savedDestinations);
+ if (list) {
+ for (li = list; li; li = g_list_next(li)) {
+ JsonObject *obj =
+ json_node_get_object((JsonNode *) li->data);
+ trg_destination_combo_insert(GTK_COMBO_BOX(object),
+ json_object_get_string_member
+ (obj, TRG_PREFS_SUBKEY_LABEL),
+ json_object_get_string_member
+ (obj,
+ TRG_PREFS_KEY_DESTINATIONS_SUBKEY_DIR),
+ DEST_LABEL, lastDestination);
+ }
+ g_list_free(list);
+ }
+ }
+
+ list = g_hash_table_get_values(trg_client_get_torrent_table(client));
+ for (li = list; li; li = g_list_next(li)) {
+ rr = (GtkTreeRowReference *) li->data;
+ model = gtk_tree_row_reference_get_model(rr);
+ path = gtk_tree_row_reference_get_path(rr);
+
+ if (path) {
+ GtkTreeIter iter;
+
+ if (gtk_tree_model_get_iter(model, &iter, path)) {
+ gchar *dd;
+
+ gtk_tree_model_get(model, &iter,
+ TORRENT_COLUMN_DOWNLOADDIR, &dd, -1);
+
+ if (dd && g_strcmp0(dd, defaultDir))
+ g_slist_str_set_add(&dirs, dd);
+ else
+ g_free(dd);
+ }
+
+ gtk_tree_path_free(path);
+ }
+ }
+
+ for (sli = dirs; sli; sli = g_slist_next(sli))
+ trg_destination_combo_insert(GTK_COMBO_BOX(object),
+ NULL,
+ (gchar *) sli->data,
+ DEST_EXISTING, lastDestination);
+
+ g_list_free(list);
+ g_free(defaultDir);
+ g_free(lastDestination);
+
+ return object;
+}
+
+gchar *trg_destination_combo_get_dir(TrgDestinationCombo * combo)
+{
+ GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
+ GtkTreeIter iter;
+
+ if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) {
+ gchar *value;
+ guint type;
+
+ gtk_tree_model_get(model, &iter, DEST_COLUMN_TYPE, &type, -1);
+
+ if (type == DEST_LABEL) {
+ gtk_tree_model_get(model, &iter, DEST_COLUMN_DIR, &value, -1);
+ return value;
+ }
+ }
+
+ return
+ g_strdup(gtk_entry_get_text
+ (trg_destination_combo_get_entry(combo)));
+}
+
+static void
+trg_destination_combo_class_init(TrgDestinationComboClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgDestinationComboPrivate));
+
+ object_class->get_property = trg_destination_combo_get_property;
+ object_class->set_property = trg_destination_combo_set_property;
+ object_class->finalize = trg_destination_combo_finalize;
+ object_class->constructor = trg_destination_combo_constructor;
+
+ g_object_class_install_property(object_class,
+ PROP_CLIENT,
+ g_param_spec_pointer("trg-client",
+ "TClient",
+ "Client",
+ G_PARAM_READWRITE
+ |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_LAST_SELECTION,
+ g_param_spec_string
+ ("last-selection-key",
+ "LastSelectionKey",
+ "LastSelectionKey", NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+}
+
+static void trg_destination_combo_init(TrgDestinationCombo * self)
+{
+}
+
+GtkWidget *trg_destination_combo_new(TrgClient * client,
+ const gchar * lastSelectionKey)
+{
+ return GTK_WIDGET(g_object_new(TRG_TYPE_DESTINATION_COMBO,
+ "trg-client", client,
+ "last-selection-key", lastSelectionKey,
+ NULL));
+}
diff --git a/src/trg-destination-combo.h b/src/trg-destination-combo.h
new file mode 100644
index 0000000..9ab5076
--- /dev/null
+++ b/src/trg-destination-combo.h
@@ -0,0 +1,58 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_DESTINATION_COMBO_H_
+#define TRG_DESTINATION_COMBO_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "trg-client.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_DESTINATION_COMBO trg_destination_combo_get_type()
+#define TRG_DESTINATION_COMBO(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_DESTINATION_COMBO, TrgDestinationCombo))
+#define TRG_DESTINATION_COMBO_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_DESTINATION_COMBO, TrgDestinationComboClass))
+#define TRG_IS_DESTINATION_COMBO(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_DESTINATION_COMBO))
+#define TRG_IS_DESTINATION_COMBO_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_DESTINATION_COMBO))
+#define TRG_DESTINATION_COMBO_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_DESTINATION_COMBO, TrgDestinationComboClass))
+ typedef struct {
+ GtkComboBox parent;
+} TrgDestinationCombo;
+
+typedef struct {
+ GtkComboBoxClass parent_class;
+} TrgDestinationComboClass;
+
+GType trg_destination_combo_get_type(void);
+
+GtkWidget *trg_destination_combo_new(TrgClient * client,
+ const gchar * lastSelectionKey);
+gchar *trg_destination_combo_get_dir(TrgDestinationCombo * combo);
+gboolean trg_destination_combo_has_text(TrgDestinationCombo * combo);
+GtkEntry *trg_destination_combo_get_entry(TrgDestinationCombo * combo);
+void trg_destination_combo_save_selection(TrgDestinationCombo * combo_box);
+
+G_END_DECLS
+#endif /* TRG_DESTINATION_COMBO_H_ */
diff --git a/src/trg-file-parser.c b/src/trg-file-parser.c
new file mode 100644
index 0000000..fbfb6aa
--- /dev/null
+++ b/src/trg-file-parser.c
@@ -0,0 +1,201 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include "bencode.h"
+#include "trg-file-parser.h"
+
+static trg_files_tree_node *trg_file_parser_node_insert(trg_files_tree_node
+ * top,
+ trg_files_tree_node
+ * last,
+ be_node *
+ file_node,
+ gint index)
+{
+ be_node *file_length_node = be_dict_find(file_node, "length", BE_INT);
+ be_node *file_path_list = be_dict_find(file_node, "path", BE_LIST);
+ trg_files_tree_node *lastIter = last;
+ GList *parentList = NULL;
+ be_node *path_el_node;
+ GList *li;
+ int i;
+
+ if (!file_path_list || !file_length_node)
+ return NULL;
+
+ if (lastIter)
+ while ((lastIter = lastIter->parent))
+ parentList = g_list_prepend(parentList, lastIter);
+
+ li = parentList;
+ lastIter = top;
+
+ /* Iterate over the path list which contains each file/directory
+ * component of the path in order.
+ */
+ for (i = 0; (path_el_node = file_path_list->val.l[i]); i++) {
+ gboolean isFile = !file_path_list->val.l[i + 1];
+ trg_files_tree_node *target_node = NULL;
+
+ if (li && !isFile) {
+ trg_files_tree_node *lastPathNode = (trg_files_tree_node *) li->data;
+
+ if (!g_strcmp0(lastPathNode->name, path_el_node->val.s)) {
+ target_node = lastPathNode;
+ li = g_list_next(li);
+ } else {
+ li = NULL;
+ }
+ }
+
+ if (!target_node && lastIter && lastIter->childrenHash && !isFile)
+ target_node = g_hash_table_lookup(lastIter->childrenHash, path_el_node->val.s);
+
+ if (!target_node) {
+ target_node = g_new0(trg_files_tree_node, 1);
+ target_node->name = g_strdup(path_el_node->val.s);
+ target_node->parent = lastIter;
+ trg_files_tree_node_add_child(lastIter, target_node);
+ }
+
+ if (isFile) {
+ target_node->length = (gint64) file_length_node->val.i;
+
+ while (lastIter) {
+ lastIter->length = target_node->length;
+ lastIter = lastIter->parent;
+ }
+ }
+
+ target_node->index = isFile ? index : -1;
+ lastIter = target_node;
+ }
+
+ g_list_free(parentList);
+
+ return lastIter;
+}
+
+void trg_torrent_file_free(trg_torrent_file * t)
+{
+ trg_files_tree_node_free(t->top_node);
+ g_free(t->name);
+ g_free(t);
+}
+
+static trg_files_tree_node *trg_parse_torrent_file_nodes(be_node *
+ info_node)
+{
+ be_node *files_node = be_dict_find(info_node, "files", BE_LIST);
+ trg_files_tree_node *top_node = g_new0(trg_files_tree_node, 1);
+ trg_files_tree_node *lastNode = NULL;
+ int i;
+
+ /* Probably means single file mode. */
+ if (!files_node)
+ return NULL;
+
+ for (i = 0; files_node->val.l[i]; ++i) {
+ be_node *file_node = files_node->val.l[i];
+
+ if (!be_validate_node(file_node, BE_DICT)
+ || !(lastNode =
+ trg_file_parser_node_insert(top_node, lastNode,
+ file_node, i))) {
+ /* Unexpected format. Throw away everything, file indexes need to
+ * be correct. */
+ trg_files_tree_node_free(top_node);
+ return NULL;
+ }
+ }
+
+ return top_node;
+}
+
+trg_torrent_file *trg_parse_torrent_file(const gchar * filename)
+{
+ GError *error = NULL;
+ GMappedFile *mf;
+ be_node *top_node, *info_node, *name_node;
+ trg_torrent_file *ret = NULL;
+
+ if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
+ g_message("%s does not exist", filename);
+ return NULL;
+ }
+
+ mf = g_mapped_file_new(filename, FALSE, &error);
+
+ if (error) {
+ g_error("%s", error->message);
+ g_error_free(error);
+ g_mapped_file_unref(mf);
+ return NULL;
+ } else {
+ top_node =
+ be_decoden(g_mapped_file_get_contents(mf),
+ g_mapped_file_get_length(mf));
+ }
+
+ g_mapped_file_unref(mf);
+
+ if (!top_node) {
+ return NULL;
+ } else if (!be_validate_node(top_node, BE_DICT)) {
+ goto out;
+ }
+
+ info_node = be_dict_find(top_node, "info", BE_DICT);
+ if (!info_node)
+ goto out;
+
+ name_node = be_dict_find(info_node, "name", BE_STR);
+ if (!name_node)
+ goto out;
+
+ ret = g_new0(trg_torrent_file, 1);
+ ret->name = g_strdup(name_node->val.s);
+
+ ret->top_node = trg_parse_torrent_file_nodes(info_node);
+ if (!ret->top_node) {
+ trg_files_tree_node *file_node;
+ be_node *length_node = be_dict_find(info_node, "length", BE_INT);
+
+ if (!length_node) {
+ g_free(ret);
+ ret = NULL;
+ goto out;
+ }
+
+ file_node = g_new0(trg_files_tree_node, 1);
+ file_node->length = (gint64) (length_node->val.i);
+ file_node->name = g_strdup(ret->name);
+ ret->top_node = file_node;
+ }
+
+ out:
+ be_free(top_node);
+ return ret;
+}
diff --git a/src/trg-file-parser.h b/src/trg-file-parser.h
new file mode 100644
index 0000000..1344be2
--- /dev/null
+++ b/src/trg-file-parser.h
@@ -0,0 +1,28 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "trg-files-tree.h"
+
+typedef struct {
+ char *name;
+ trg_files_tree_node *top_node;
+} trg_torrent_file;
+
+void trg_torrent_file_free(trg_torrent_file * t);
+trg_torrent_file *trg_parse_torrent_file(const gchar * filename);
diff --git a/src/trg-files-model-common.c b/src/trg-files-model-common.c
new file mode 100644
index 0000000..b435069
--- /dev/null
+++ b/src/trg-files-model-common.c
@@ -0,0 +1,199 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "protocol-constants.h"
+#include "trg-files-model-common.h"
+
+struct SubtreeForeachData {
+ gint column;
+ gint new_value;
+ GtkTreePath *path;
+};
+
+static void
+set_wanted_foreachfunc(GtkTreeModel * model,
+ GtkTreePath * path G_GNUC_UNUSED,
+ GtkTreeIter * iter, gpointer data)
+{
+ struct SubtreeForeachData *args = (struct SubtreeForeachData *) data;
+
+ gtk_tree_store_set(GTK_TREE_STORE(model), iter, args->column,
+ args->new_value, -1);
+
+ trg_files_tree_model_set_subtree(model, path, iter, args->column,
+ args->new_value);
+}
+
+static void
+set_priority_foreachfunc(GtkTreeModel * model,
+ GtkTreePath * path,
+ GtkTreeIter * iter, gpointer data)
+{
+ struct SubtreeForeachData *args = (struct SubtreeForeachData *) data;
+ GValue value = { 0 };
+
+ g_value_init(&value, G_TYPE_INT);
+ g_value_set_int(&value, args->new_value);
+
+ gtk_tree_store_set_value(GTK_TREE_STORE(model), iter, args->column,
+ &value);
+
+ trg_files_tree_model_set_subtree(model, path, iter, args->column,
+ args->new_value);
+}
+
+void
+trg_files_model_set_wanted(GtkTreeView * tv, gint column, gint new_value)
+{
+ struct SubtreeForeachData args;
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(tv);
+
+ args.column = column;
+ args.new_value = new_value;
+
+ gtk_tree_selection_selected_foreach(selection, set_wanted_foreachfunc,
+ &args);
+}
+
+void
+trg_files_tree_model_set_priority(GtkTreeView * tv, gint column,
+ gint new_value)
+{
+ struct SubtreeForeachData args;
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(tv);
+
+ args.column = column;
+ args.new_value = new_value;
+
+ gtk_tree_selection_selected_foreach(selection,
+ set_priority_foreachfunc, &args);
+
+}
+
+static gboolean
+setSubtreeForeach(GtkTreeModel * model, GtkTreePath * path,
+ GtkTreeIter * iter, gpointer gdata)
+{
+ struct SubtreeForeachData *data = gdata;
+
+ if (!gtk_tree_path_compare(path, data->path)
+ || gtk_tree_path_is_descendant(path, data->path)) {
+ GValue value = { 0 };
+
+ g_value_init(&value, G_TYPE_INT);
+ g_value_set_int(&value, data->new_value);
+
+ gtk_tree_store_set_value(GTK_TREE_STORE(model), iter, data->column,
+ &value);
+ }
+
+ return FALSE; /* keep walking */
+}
+
+void
+trg_files_tree_model_propogate_change_up(GtkTreeModel * model,
+ GtkTreeIter * iter,
+ gint column, gint new_value)
+{
+ GtkTreeIter back_iter = *iter;
+ gint result = new_value;
+
+ while (1) {
+ GtkTreeIter tmp_iter;
+ gint n_children, i;
+
+ if (!gtk_tree_model_iter_parent(model, &tmp_iter, &back_iter))
+ break;
+
+ n_children = gtk_tree_model_iter_n_children(model, &tmp_iter);
+
+ for (i = 0; i < n_children; i++) {
+ GtkTreeIter child;
+ gint current_value;
+
+ if (!gtk_tree_model_iter_nth_child
+ (model, &child, &tmp_iter, i))
+ continue;
+
+ gtk_tree_model_get(model, &child, column, &current_value, -1);
+ if (current_value != new_value) {
+ result = TR_PRI_MIXED;
+ break;
+ }
+ }
+
+ gtk_tree_store_set(GTK_TREE_STORE(model), &tmp_iter, column,
+ result, -1);
+
+ back_iter = tmp_iter;
+ }
+}
+
+void
+trg_files_tree_model_set_subtree(GtkTreeModel * model,
+ GtkTreePath * path,
+ GtkTreeIter * iter, gint column,
+ gint new_value)
+{
+ GtkTreeIter back_iter = *iter;
+
+ if (gtk_tree_model_iter_has_child(model, iter)) {
+ struct SubtreeForeachData tmp;
+
+ tmp.column = column;
+ tmp.new_value = new_value;
+ tmp.path = path;
+
+ gtk_tree_model_foreach(model, setSubtreeForeach, &tmp);
+ } else {
+ gtk_tree_store_set(GTK_TREE_STORE(model), &back_iter, column,
+ new_value, -1);
+ }
+
+ trg_files_tree_model_propogate_change_up(model, iter, column,
+ new_value);
+}
+
+void
+trg_files_model_update_parents(GtkTreeModel * model,
+ GtkTreeIter * iter, gint size_column)
+{
+ GtkTreeIter back_iter = *iter;
+ GtkTreeIter tmp_iter;
+ gint64 size, oldSize;
+
+ if (!gtk_tree_model_iter_parent(model, &tmp_iter, &back_iter))
+ return;
+
+ gtk_tree_model_get(model, iter, size_column, &size, -1);
+
+ do {
+ gtk_tree_model_get(model, &tmp_iter, size_column, &oldSize, -1);
+ gtk_tree_store_set(GTK_TREE_STORE(model), &tmp_iter, size_column,
+ size + oldSize, -1);
+ back_iter = tmp_iter;
+ }
+ while (gtk_tree_model_iter_parent(model, &tmp_iter, &back_iter));
+}
diff --git a/src/trg-files-model-common.h b/src/trg-files-model-common.h
new file mode 100644
index 0000000..ad151e1
--- /dev/null
+++ b/src/trg-files-model-common.h
@@ -0,0 +1,41 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_FILES_TREE_MODEL_COMMON_H_
+#define TRG_FILES_TREE_MODEL_COMMON_H_
+
+void trg_files_tree_model_propogate_change_up(GtkTreeModel * model,
+ GtkTreeIter * iter,
+ gint column, gint new_value);
+void trg_files_tree_model_set_subtree(GtkTreeModel * model,
+ GtkTreePath * path,
+ GtkTreeIter * iter, gint column,
+ gint new_value);
+void trg_files_tree_model_set_priority(GtkTreeView * tv, gint column,
+ gint new_value);
+void trg_files_model_set_wanted(GtkTreeView * tv, gint column,
+ gint new_value);
+gboolean trg_files_model_update_parent_size(GtkTreeModel * model,
+ GtkTreePath * path,
+ GtkTreeIter * iter,
+ gpointer data);
+void trg_files_model_update_parents(GtkTreeModel * model,
+ GtkTreeIter * iter, gint size_column);
+
+#endif /* TRG_FILES_TREE_MODEL_COMMON_H_ */
diff --git a/src/trg-files-model.c b/src/trg-files-model.c
new file mode 100644
index 0000000..bb1b46b
--- /dev/null
+++ b/src/trg-files-model.c
@@ -0,0 +1,449 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <string.h>
+#include <limits.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+#include "protocol-constants.h"
+#include "trg-files-model-common.h"
+#include "trg-files-tree-view-common.h"
+#include "trg-files-tree.h"
+#include "trg-files-model.h"
+#include "trg-client.h"
+#include "torrent.h"
+#include "util.h"
+
+#include "trg-files-model.h"
+
+G_DEFINE_TYPE(TrgFilesModel, trg_files_model, GTK_TYPE_TREE_STORE)
+#define TRG_FILES_MODEL_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_FILES_MODEL, TrgFilesModelPrivate))
+typedef struct _TrgFilesModelPrivate TrgFilesModelPrivate;
+
+struct _TrgFilesModelPrivate {
+ gint64 torrentId;
+ guint n_items;
+ gboolean accept;
+};
+
+/* Push a given increment to a treemodel node and its parents.
+ * Used for updating bytescompleted.
+ * This is only used for user interaction, the initial population is done
+ * in a simple tree before for performance.
+ * */
+
+static void trg_files_update_parent_progress(GtkTreeModel * model,
+ GtkTreeIter * iter,
+ gint64 increment)
+{
+ GtkTreeIter back_iter = *iter;
+
+ if (increment < 1)
+ return;
+
+ while (1) {
+ GtkTreeIter tmp_iter;
+ gint64 lastCompleted, newCompleted, length;
+
+ if (!gtk_tree_model_iter_parent(model, &tmp_iter, &back_iter))
+ break;
+
+ gtk_tree_model_get(model, &tmp_iter, FILESCOL_BYTESCOMPLETED,
+ &lastCompleted, FILESCOL_SIZE, &length, -1);
+ newCompleted = lastCompleted + increment;
+
+ gtk_tree_store_set(GTK_TREE_STORE(model), &tmp_iter,
+ FILESCOL_PROGRESS, file_get_progress(length,
+ newCompleted),
+ FILESCOL_BYTESCOMPLETED, newCompleted, -1);
+
+ back_iter = tmp_iter;
+ }
+}
+
+/* Update the bytesCompleted and size for a nodes parents, and also figure out
+ * if a priority/enabled change requires updating the parents (needs to iterate
+ * over the other nodes at its level).
+ *
+ * It's faster doing it in here than when it's in the model.
+ */
+static void trg_files_tree_update_ancestors(trg_files_tree_node * node)
+{
+ trg_files_tree_node *back_iter = node;
+ gint pri_result = node->priority;
+ gint enabled_result = node->enabled;
+
+ while ((back_iter = back_iter->parent)) {
+ GList *li;
+ for (li = back_iter->children; li; li = g_list_next(li)) {
+ trg_files_tree_node *back_node =
+ (trg_files_tree_node *) li->data;
+ gint common_result = 0;
+
+ if (back_node->priority != pri_result)
+ common_result = pri_result = TR_PRI_MIXED;
+
+ if (back_node->enabled != enabled_result)
+ common_result = enabled_result = TR_PRI_MIXED;
+
+ if (common_result == TR_PRI_MIXED)
+ break;
+ }
+
+ back_iter->bytesCompleted += node->bytesCompleted;
+ back_iter->length += node->length;
+ back_iter->priority = pri_result;
+ back_iter->enabled = enabled_result;
+ }
+}
+
+static void
+store_add_node(GtkTreeStore * store, GtkTreeIter * parent,
+ trg_files_tree_node * node)
+{
+ GtkTreeIter child;
+ GList *li;
+
+ if (node->name) {
+ gdouble progress =
+ file_get_progress(node->length, node->bytesCompleted);
+ gtk_tree_store_insert_with_values(store, &child, parent, INT_MAX,
+ FILESCOL_WANTED, node->enabled,
+ FILESCOL_PROGRESS, progress,
+ FILESCOL_SIZE, node->length,
+ FILESCOL_ID, node->index,
+ FILESCOL_PRIORITY,
+ node->priority, FILESCOL_NAME,
+ node->name, -1);
+ }
+
+ for (li = node->children; li; li = g_list_next(li))
+ store_add_node(store, node->name ? &child : NULL,
+ (trg_files_tree_node *) li->data);
+}
+
+static trg_files_tree_node *trg_file_parser_node_insert(trg_files_tree_node
+ * top,
+ trg_files_tree_node
+ * last,
+ JsonObject * file,
+ gint index,
+ JsonArray *
+ enabled,
+ JsonArray *
+ priorities)
+{
+ gchar **path = g_strsplit(file_get_name(file), "/", -1);
+ trg_files_tree_node *lastIter = last;
+ GList *parentList = NULL;
+ gchar *path_el;
+ GList *li;
+ int i;
+
+ /* Build up a list of pointers to each parent trg_files_tree_node
+ * reversing the order as it iterates over its parent.
+ */
+ if (lastIter)
+ while ((lastIter = lastIter->parent))
+ parentList = g_list_prepend(parentList, lastIter);
+
+ li = parentList;
+ lastIter = top;
+
+ /* Iterate over the path list which contains each file/directory
+ * component of the path in order.
+ */
+ for (i = 0; (path_el = path[i]); i++) {
+ gboolean isFile = !path[i + 1];
+ trg_files_tree_node *target_node = NULL;
+
+ /* No point checking for files. If there is a last parents iterator
+ * check it for a shortcut. I'm assuming that these come in order of
+ * directory at least to give performance a boost.
+ */
+ if (li && !isFile) {
+ trg_files_tree_node *lastPathNode =
+ (trg_files_tree_node *) li->data;
+
+ if (!g_strcmp0(lastPathNode->name, path[i])) {
+ target_node = lastPathNode;
+ li = g_list_next(li);
+ } else {
+ /* No need to check any further. */
+ li = NULL;
+ }
+ }
+
+ if (!target_node && lastIter && lastIter->childrenHash && !isFile)
+ target_node = g_hash_table_lookup(lastIter->childrenHash, path_el);
+
+ /* Node needs creating */
+
+ if (!target_node) {
+ target_node = g_new0(trg_files_tree_node, 1);
+ target_node->name = g_strdup(path[i]);
+ target_node->parent = lastIter;
+ trg_files_tree_node_add_child(lastIter, target_node);
+ }
+
+ lastIter = target_node;
+
+ /* Files have more properties set here than for files.
+ * Directories are updated from here too, by trg_files_tree_update_ancestors
+ * working up the parents.
+ */
+ if (isFile) {
+ target_node->length = file_get_length(file);
+ target_node->bytesCompleted = file_get_bytes_completed(file);
+ target_node->index = index;
+ target_node->enabled =
+ (gint) json_array_get_int_element(enabled, index);
+ target_node->priority =
+ (gint) json_array_get_int_element(priorities, index);
+
+ trg_files_tree_update_ancestors(target_node);
+ } else {
+ target_node->index = -1;
+ }
+ }
+
+ g_list_free(parentList);
+ g_strfreev(path);
+
+ return lastIter;
+}
+
+void trg_files_model_set_accept(TrgFilesModel * model, gboolean accept)
+{
+ TrgFilesModelPrivate *priv = TRG_FILES_MODEL_GET_PRIVATE(model);
+ priv->accept = accept;
+}
+
+static void
+trg_files_model_iter_update(TrgFilesModel * model,
+ GtkTreeIter * filesIter,
+ JsonObject * file,
+ JsonArray * wantedArray,
+ JsonArray * prioritiesArray, gint id)
+{
+ TrgFilesModelPrivate *priv = TRG_FILES_MODEL_GET_PRIVATE(model);
+ gint64 fileLength = file_get_length(file);
+ gint64 fileCompleted = file_get_bytes_completed(file);
+ gint64 lastCompleted;
+
+ gint wanted = (gint) json_array_get_int_element(wantedArray, id);
+ gint priority = (gint) json_array_get_int_element(prioritiesArray, id);
+ gdouble progress = file_get_progress(fileLength, fileCompleted);
+
+ gtk_tree_model_get(GTK_TREE_MODEL(model), filesIter,
+ FILESCOL_BYTESCOMPLETED, &lastCompleted, -1);
+
+ gtk_tree_store_set(GTK_TREE_STORE(model), filesIter, FILESCOL_PROGRESS,
+ progress, FILESCOL_BYTESCOMPLETED, fileCompleted,
+ -1);
+
+ trg_files_update_parent_progress(GTK_TREE_MODEL(model), filesIter,
+ fileCompleted - lastCompleted);
+
+ if (priv->accept)
+ gtk_tree_store_set(GTK_TREE_STORE(model), filesIter,
+ FILESCOL_WANTED, wanted, FILESCOL_PRIORITY,
+ priority, -1);
+}
+
+static void trg_files_model_class_init(TrgFilesModelClass * klass)
+{
+ g_type_class_add_private(klass, sizeof(TrgFilesModelPrivate));
+}
+
+static void trg_files_model_init(TrgFilesModel * self)
+{
+ TrgFilesModelPrivate *priv = TRG_FILES_MODEL_GET_PRIVATE(self);
+ GType column_types[FILESCOL_COLUMNS];
+
+ priv->accept = TRUE;
+
+ column_types[FILESCOL_NAME] = G_TYPE_STRING;
+ column_types[FILESCOL_SIZE] = G_TYPE_INT64;
+ column_types[FILESCOL_PROGRESS] = G_TYPE_DOUBLE;
+ column_types[FILESCOL_ID] = G_TYPE_INT;
+ column_types[FILESCOL_WANTED] = G_TYPE_INT;
+ column_types[FILESCOL_PRIORITY] = G_TYPE_INT;
+ column_types[FILESCOL_BYTESCOMPLETED] = G_TYPE_INT64;
+
+ gtk_tree_store_set_column_types(GTK_TREE_STORE(self), FILESCOL_COLUMNS,
+ column_types);
+}
+
+struct MinorUpdateData {
+ GList *filesList;
+ JsonArray *priorities;
+ JsonArray *wanted;
+};
+
+gboolean
+trg_files_model_update_foreach(GtkListStore * model,
+ GtkTreePath * path G_GNUC_UNUSED,
+ GtkTreeIter * iter, gpointer data)
+{
+ struct MinorUpdateData *mud = (struct MinorUpdateData *) data;
+ JsonObject *file;
+ gint id;
+
+ gtk_tree_model_get(GTK_TREE_MODEL(model), iter, FILESCOL_ID, &id, -1);
+
+ if (id >= 0) {
+ file = json_node_get_object(g_list_nth_data(mud->filesList, id));
+ trg_files_model_iter_update(TRG_FILES_MODEL(model), iter,
+ file, mud->wanted, mud->priorities,
+ id);
+ }
+
+ return FALSE;
+}
+
+struct FirstUpdateThreadData {
+ TrgFilesModel *model;
+ GtkTreeView *tree_view;
+ JsonArray *files;
+ JsonArray *priorities;
+ JsonArray *wanted;
+ guint n_items;
+ trg_files_tree_node *top_node;
+ gint64 torrent_id;
+ GList *filesList;
+ gboolean idle_add;
+};
+
+static gboolean trg_files_model_applytree_idlefunc(gpointer data)
+{
+ struct FirstUpdateThreadData *args =
+ (struct FirstUpdateThreadData *) data;
+ TrgFilesModelPrivate *priv = TRG_FILES_MODEL_GET_PRIVATE(args->model);
+
+ if (args->torrent_id == priv->torrentId) {
+ store_add_node(GTK_TREE_STORE(args->model), NULL, args->top_node);
+ gtk_tree_view_expand_all(args->tree_view);
+ priv->n_items = args->n_items;
+ priv->accept = TRUE;
+ }
+
+ trg_files_tree_node_free(args->top_node);
+ g_free(data);
+
+ return FALSE;
+}
+
+static gpointer trg_files_model_buildtree_threadfunc(gpointer data)
+{
+ struct FirstUpdateThreadData *args =
+ (struct FirstUpdateThreadData *) data;
+ trg_files_tree_node *lastNode = NULL;
+ GList *li;
+
+ args->top_node = g_new0(trg_files_tree_node, 1);
+
+ for (li = args->filesList; li; li = g_list_next(li)) {
+ JsonObject *file = json_node_get_object((JsonNode *) li->data);
+
+ lastNode =
+ trg_file_parser_node_insert(args->top_node, lastNode,
+ file, args->n_items++,
+ args->wanted, args->priorities);
+ }
+
+ g_list_free(args->filesList);
+ json_array_unref(args->files);
+
+ if (args->idle_add)
+ g_idle_add(trg_files_model_applytree_idlefunc, data);
+
+ return NULL;
+}
+
+void
+trg_files_model_update(TrgFilesModel * model, GtkTreeView * tv,
+ gint64 updateSerial, JsonObject * t, gint mode)
+{
+ TrgFilesModelPrivate *priv = TRG_FILES_MODEL_GET_PRIVATE(model);
+ JsonArray *files = torrent_get_files(t);
+ GList *filesList = json_array_get_elements(files);
+ guint filesListLength = g_list_length(filesList);
+ JsonArray *priorities = torrent_get_priorities(t);
+ JsonArray *wanted = torrent_get_wanted(t);
+ priv->torrentId = torrent_get_id(t);
+
+ /* It's quicker to build this up with simple data structures before
+ * putting it into GTK models.
+ */
+ if (mode == TORRENT_GET_MODE_FIRST || priv->n_items != filesListLength) {
+ struct FirstUpdateThreadData *futd =
+ g_new0(struct FirstUpdateThreadData, 1);
+
+ gtk_tree_store_clear(GTK_TREE_STORE(model));
+ json_array_ref(files);
+
+ futd->tree_view = tv;
+ futd->files = files;
+ futd->priorities = priorities;
+ futd->wanted = wanted;
+ futd->filesList = filesList;
+ futd->torrent_id = priv->torrentId;
+ futd->model = model;
+ futd->idle_add =
+ filesListLength > TRG_FILES_MODEL_CREATE_THREAD_IF_GT;
+
+ /* If this update has more than a given number of files, build up the
+ * simple tree in a thread, then g_idle_add a function which
+ * adds the contents of this prebuilt tree.
+ *
+ * If less than or equal to, I don't think it's worth spawning threads
+ * for. Just do it in the main loop.
+ */
+ if (futd->idle_add) {
+ g_thread_create(trg_files_model_buildtree_threadfunc, futd,
+ FALSE, NULL);
+ } else {
+ trg_files_model_buildtree_threadfunc(futd);
+ trg_files_model_applytree_idlefunc(futd);
+ }
+ } else {
+ struct MinorUpdateData mud;
+ mud.priorities = priorities;
+ mud.wanted = wanted;
+ mud.filesList = filesList;
+ gtk_tree_model_foreach(GTK_TREE_MODEL(model),
+ (GtkTreeModelForeachFunc)
+ trg_files_model_update_foreach, &mud);
+ g_list_free(filesList);
+ }
+}
+
+gint64 trg_files_model_get_torrent_id(TrgFilesModel * model)
+{
+ TrgFilesModelPrivate *priv = TRG_FILES_MODEL_GET_PRIVATE(model);
+ return priv->torrentId;
+}
+
+TrgFilesModel *trg_files_model_new(void)
+{
+ return g_object_new(TRG_TYPE_FILES_MODEL, NULL);
+}
diff --git a/src/trg-files-model.h b/src/trg-files-model.h
new file mode 100644
index 0000000..3b23f9c
--- /dev/null
+++ b/src/trg-files-model.h
@@ -0,0 +1,71 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_FILES_MODEL_H_
+#define TRG_FILES_MODEL_H_
+
+#include <glib-object.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-model.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_FILES_MODEL trg_files_model_get_type()
+#define TRG_FILES_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_FILES_MODEL, TrgFilesModel))
+#define TRG_FILES_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_FILES_MODEL, TrgFilesModelClass))
+#define TRG_IS_FILES_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_FILES_MODEL))
+#define TRG_IS_FILES_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_FILES_MODEL))
+#define TRG_FILES_MODEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_FILES_MODEL, TrgFilesModelClass))
+ typedef struct {
+ GtkTreeStore parent;
+} TrgFilesModel;
+
+typedef struct {
+ GtkTreeStoreClass parent_class;
+} TrgFilesModelClass;
+
+GType trg_files_model_get_type(void);
+
+TrgFilesModel *trg_files_model_new(void);
+
+G_END_DECLS enum {
+ FILESCOL_NAME,
+ FILESCOL_SIZE,
+ FILESCOL_PROGRESS,
+ FILESCOL_ID,
+ FILESCOL_WANTED,
+ FILESCOL_PRIORITY,
+ FILESCOL_BYTESCOMPLETED,
+ FILESCOL_COLUMNS
+};
+
+#define TRG_FILES_MODEL_CREATE_THREAD_IF_GT 600
+
+void trg_files_model_update(TrgFilesModel * model, GtkTreeView * tv,
+ gint64 updateSerial, JsonObject * t,
+ gint mode);
+gint64 trg_files_model_get_torrent_id(TrgFilesModel * model);
+void trg_files_model_set_accept(TrgFilesModel * model, gboolean accept);
+
+#endif /* TRG_FILES_MODEL_H_ */
diff --git a/src/trg-files-tree-view-common.c b/src/trg-files-tree-view-common.c
new file mode 100644
index 0000000..4680d5f
--- /dev/null
+++ b/src/trg-files-tree-view-common.c
@@ -0,0 +1,229 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "protocol-constants.h"
+#include "trg-files-model-common.h"
+#include "trg-files-tree-view-common.h"
+
+static void expand_all_cb(GtkWidget * w, gpointer data)
+{
+ gtk_tree_view_expand_all(GTK_TREE_VIEW(data));
+}
+
+static void collapse_all_cb(GtkWidget * w, gpointer data)
+{
+ gtk_tree_view_collapse_all(GTK_TREE_VIEW(data));
+}
+
+static void
+view_popup_menu(GtkWidget * treeview, GdkEventButton * event,
+ GCallback low_cb, GCallback normal_cb,
+ GCallback high_cb, GCallback wanted_cb,
+ GCallback unwanted_cb, gpointer data G_GNUC_UNUSED)
+{
+ GtkWidget *menu, *menuitem;
+
+ menu = gtk_menu_new();
+
+ menuitem = gtk_menu_item_new_with_label(_("High Priority"));
+ g_signal_connect(menuitem, "activate", high_cb, treeview);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ menuitem = gtk_menu_item_new_with_label(_("Normal Priority"));
+ g_signal_connect(menuitem, "activate", normal_cb, treeview);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ menuitem = gtk_menu_item_new_with_label(_("Low Priority"));
+ g_signal_connect(menuitem, "activate", low_cb, treeview);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ gtk_separator_menu_item_new());
+
+ menuitem = gtk_image_menu_item_new_with_label(GTK_STOCK_APPLY);
+ gtk_image_menu_item_set_use_stock(GTK_IMAGE_MENU_ITEM(menuitem), TRUE);
+ gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM
+ (menuitem), TRUE);
+ gtk_menu_item_set_label(GTK_MENU_ITEM(menuitem), _("Download"));
+ g_signal_connect(menuitem, "activate", wanted_cb, treeview);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ menuitem = gtk_image_menu_item_new_with_label(GTK_STOCK_CANCEL);
+ gtk_image_menu_item_set_use_stock(GTK_IMAGE_MENU_ITEM(menuitem), TRUE);
+ gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM
+ (menuitem), TRUE);
+ gtk_menu_item_set_label(GTK_MENU_ITEM(menuitem), _("Skip"));
+ g_signal_connect(menuitem, "activate", unwanted_cb, treeview);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ gtk_separator_menu_item_new());
+
+ menuitem = gtk_image_menu_item_new_with_label(_("Expand All"));
+ g_signal_connect(menuitem, "activate", G_CALLBACK(expand_all_cb),
+ treeview);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ menuitem = gtk_image_menu_item_new_with_label(_("Collapse All"));
+ g_signal_connect(menuitem, "activate", G_CALLBACK(collapse_all_cb),
+ treeview);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ gtk_widget_show_all(menu);
+
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
+ (event != NULL) ? event->button : 0,
+ gdk_event_get_time((GdkEvent *) event));
+}
+
+gboolean
+trg_files_tree_view_viewOnPopupMenu(GtkWidget * treeview,
+ GCallback low_cb,
+ GCallback normal_cb,
+ GCallback high_cb,
+ GCallback wanted_cb,
+ GCallback unwanted_cb,
+ gpointer userdata)
+{
+ view_popup_menu(treeview, NULL, low_cb, normal_cb, high_cb, wanted_cb,
+ unwanted_cb, userdata);
+ return TRUE;
+}
+
+static gboolean
+onViewPathToggled(GtkTreeView * view,
+ GtkTreeViewColumn * col,
+ GtkTreePath * path,
+ gint pri_id, gint enabled_id, gpointer data)
+{
+ int cid;
+ gboolean handled = FALSE;
+
+ if (!col || !path)
+ return FALSE;
+
+ cid = gtk_tree_view_column_get_sort_column_id(col);
+ if ((cid == pri_id) || (cid == enabled_id)) {
+ GtkTreeIter iter;
+ GtkTreeModel *model = gtk_tree_view_get_model(view);
+
+ gtk_tree_model_get_iter(model, &iter, path);
+
+ if (cid == pri_id) {
+ int priority;
+ gtk_tree_model_get(model, &iter, pri_id, &priority, -1);
+ switch (priority) {
+ case TR_PRI_NORMAL:
+ priority = TR_PRI_HIGH;
+ break;
+ case TR_PRI_HIGH:
+ priority = TR_PRI_LOW;
+ break;
+ default:
+ priority = TR_PRI_NORMAL;
+ break;
+ }
+ trg_files_tree_model_set_subtree(model, path, &iter, pri_id,
+ priority);
+ } else if (cid == enabled_id) {
+ int enabled;
+ gtk_tree_model_get(model, &iter, enabled_id, &enabled, -1);
+ enabled = !enabled;
+
+ trg_files_tree_model_set_subtree(model, path, &iter,
+ enabled_id, enabled);
+ }
+
+ handled = TRUE;
+ }
+
+ return handled;
+}
+
+static gboolean
+getAndSelectEventPath(GtkTreeView * treeview,
+ GdkEventButton * event,
+ GtkTreeViewColumn ** col, GtkTreePath ** path)
+{
+ GtkTreeSelection *sel;
+
+ if (gtk_tree_view_get_path_at_pos
+ (treeview, event->x, event->y, path, col, NULL, NULL)) {
+ sel = gtk_tree_view_get_selection(treeview);
+ if (!gtk_tree_selection_path_is_selected(sel, *path)) {
+ gtk_tree_selection_unselect_all(sel);
+ gtk_tree_selection_select_path(sel, *path);
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+trg_files_tree_view_onViewButtonPressed(GtkWidget * w,
+ GdkEventButton * event,
+ gint pri_id,
+ gint enabled_id,
+ GCallback low_cb,
+ GCallback normal_cb,
+ GCallback high_cb,
+ GCallback wanted_cb,
+ GCallback unwanted_cb,
+ gpointer gdata)
+{
+ GtkTreeViewColumn *col = NULL;
+ GtkTreePath *path = NULL;
+ GtkTreeSelection *selection;
+ gboolean handled = FALSE;
+ GtkTreeView *treeview = GTK_TREE_VIEW(w);
+
+ if (event->type == GDK_BUTTON_PRESS && event->button == 1
+ && !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
+ && getAndSelectEventPath(treeview, event, &col, &path)) {
+ handled =
+ onViewPathToggled(treeview, col, path, pri_id, enabled_id,
+ NULL);
+ } else if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
+
+ if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview),
+ (gint) event->x, (gint) event->y,
+ &path, NULL, NULL, NULL)) {
+ if (!gtk_tree_selection_path_is_selected(selection, path)) {
+ gtk_tree_selection_unselect_all(selection);
+ gtk_tree_selection_select_path(selection, path);
+ }
+
+ view_popup_menu(w, event, low_cb, normal_cb, high_cb,
+ wanted_cb, unwanted_cb, gdata);
+ handled = TRUE;
+ }
+ }
+
+ gtk_tree_path_free(path);
+
+ return handled;
+}
diff --git a/src/trg-files-tree-view-common.h b/src/trg-files-tree-view-common.h
new file mode 100644
index 0000000..3826182
--- /dev/null
+++ b/src/trg-files-tree-view-common.h
@@ -0,0 +1,42 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_FILES_TREE_VIEW_COMMON_H_
+#define TRG_FILES_TREE_VIEW_COMMON_H_
+
+gboolean trg_files_tree_view_onViewButtonPressed(GtkWidget * w,
+ GdkEventButton * event,
+ gint pri_id,
+ gint enabled_id,
+ GCallback low_cb,
+ GCallback normal_cb,
+ GCallback high_cb,
+ GCallback wanted_cb,
+ GCallback unwanted_cb,
+ gpointer gdata);
+
+gboolean trg_files_tree_view_viewOnPopupMenu(GtkWidget * treeview,
+ GCallback low_cb,
+ GCallback normal_cb,
+ GCallback high_cb,
+ GCallback wanted_cb,
+ GCallback unwanted_cb,
+ gpointer userdata);
+
+#endif /* TRG_FILES_TREE_VIEW_COMMON_H_ */
diff --git a/src/trg-files-tree-view.c b/src/trg-files-tree-view.c
new file mode 100644
index 0000000..1c93ad6
--- /dev/null
+++ b/src/trg-files-tree-view.c
@@ -0,0 +1,226 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "trg-client.h"
+#include "trg-tree-view.h"
+#include "trg-files-model-common.h"
+#include "trg-files-tree-view-common.h"
+#include "trg-files-tree-view.h"
+#include "trg-files-model.h"
+#include "trg-main-window.h"
+#include "requests.h"
+#include "util.h"
+#include "json.h"
+#include "protocol-constants.h"
+
+G_DEFINE_TYPE(TrgFilesTreeView, trg_files_tree_view, TRG_TYPE_TREE_VIEW)
+#define TRG_FILES_TREE_VIEW_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_FILES_TREE_VIEW, TrgFilesTreeViewPrivate))
+typedef struct _TrgFilesTreeViewPrivate TrgFilesTreeViewPrivate;
+
+struct _TrgFilesTreeViewPrivate {
+ TrgClient *client;
+ TrgMainWindow *win;
+};
+
+static void trg_files_tree_view_class_init(TrgFilesTreeViewClass * klass)
+{
+ g_type_class_add_private(klass, sizeof(TrgFilesTreeViewPrivate));
+}
+
+static gboolean
+send_updated_file_prefs_foreachfunc(GtkTreeModel * model,
+ GtkTreePath *
+ path G_GNUC_UNUSED,
+ GtkTreeIter * iter, gpointer data)
+{
+ JsonObject *args = (JsonObject *) data;
+ gint priority;
+ gint id;
+ gint wanted;
+
+ gtk_tree_model_get(model, iter, FILESCOL_ID, &id, -1);
+
+ if (id < 0)
+ return FALSE;
+
+ gtk_tree_model_get(model, iter, FILESCOL_WANTED, &wanted,
+ FILESCOL_PRIORITY, &priority, -1);
+
+ if (wanted)
+ add_file_id_to_array(args, FIELD_FILES_WANTED, id);
+ else
+ add_file_id_to_array(args, FIELD_FILES_UNWANTED, id);
+
+ if (priority == TR_PRI_LOW)
+ add_file_id_to_array(args, FIELD_FILES_PRIORITY_LOW, id);
+ else if (priority == TR_PRI_HIGH)
+ add_file_id_to_array(args, FIELD_FILES_PRIORITY_HIGH, id);
+ else
+ add_file_id_to_array(args, FIELD_FILES_PRIORITY_NORMAL, id);
+
+ return FALSE;
+}
+
+static gboolean on_files_update(gpointer data)
+{
+ trg_response *response = (trg_response *) data;
+ TrgFilesTreeViewPrivate *priv =
+ TRG_FILES_TREE_VIEW_GET_PRIVATE(response->cb_data);
+ GtkTreeModel *model =
+ gtk_tree_view_get_model(GTK_TREE_VIEW(response->cb_data));
+
+ trg_files_model_set_accept(TRG_FILES_MODEL(model), TRUE);
+
+ response->cb_data = priv->win;
+
+ return on_generic_interactive_action(data);
+}
+
+static void send_updated_file_prefs(TrgFilesTreeView * tv)
+{
+ TrgFilesTreeViewPrivate *priv = TRG_FILES_TREE_VIEW_GET_PRIVATE(tv);
+ JsonNode *req;
+ JsonObject *args;
+ GtkTreeModel *model;
+ gint64 targetId;
+ JsonArray *targetIdArray;
+
+ model = gtk_tree_view_get_model(GTK_TREE_VIEW(tv));
+ targetId = trg_files_model_get_torrent_id(TRG_FILES_MODEL(model));
+ targetIdArray = json_array_new();
+ json_array_add_int_element(targetIdArray, targetId);
+
+ req = torrent_set(targetIdArray);
+ args = node_get_arguments(req);
+
+ gtk_tree_model_foreach(model, send_updated_file_prefs_foreachfunc,
+ args);
+
+ trg_files_model_set_accept(TRG_FILES_MODEL(model), FALSE);
+
+ dispatch_async(priv->client, req, on_files_update, tv);
+}
+
+static void set_low(GtkWidget * w G_GNUC_UNUSED, gpointer data)
+{
+ trg_files_tree_model_set_priority(GTK_TREE_VIEW(data),
+ FILESCOL_PRIORITY, TR_PRI_LOW);
+ send_updated_file_prefs(TRG_FILES_TREE_VIEW(data));
+}
+
+static void set_normal(GtkWidget * w G_GNUC_UNUSED, gpointer data)
+{
+ trg_files_tree_model_set_priority(GTK_TREE_VIEW(data),
+ FILESCOL_PRIORITY, TR_PRI_NORMAL);
+ send_updated_file_prefs(TRG_FILES_TREE_VIEW(data));
+}
+
+static void set_high(GtkWidget * w G_GNUC_UNUSED, gpointer data)
+{
+ trg_files_tree_model_set_priority(GTK_TREE_VIEW(data),
+ FILESCOL_PRIORITY, TR_PRI_HIGH);
+ send_updated_file_prefs(TRG_FILES_TREE_VIEW(data));
+}
+
+static void set_unwanted(GtkWidget * w G_GNUC_UNUSED, gpointer data)
+{
+ trg_files_model_set_wanted(GTK_TREE_VIEW(data), FILESCOL_WANTED,
+ FALSE);
+ send_updated_file_prefs(TRG_FILES_TREE_VIEW(data));
+}
+
+static void set_wanted(GtkWidget * w G_GNUC_UNUSED, gpointer data)
+{
+ trg_files_model_set_wanted(GTK_TREE_VIEW(data), FILESCOL_WANTED, TRUE);
+ send_updated_file_prefs(TRG_FILES_TREE_VIEW(data));
+}
+
+static gboolean
+view_onButtonPressed(GtkWidget * treeview,
+ GdkEventButton * event, gpointer userdata)
+{
+ gboolean handled =
+ trg_files_tree_view_onViewButtonPressed(treeview, event,
+ -1,
+ FILESCOL_WANTED,
+ G_CALLBACK(set_low),
+ G_CALLBACK(set_normal),
+ G_CALLBACK(set_high),
+ G_CALLBACK(set_wanted),
+ G_CALLBACK(set_unwanted),
+ userdata);
+
+ if (handled)
+ send_updated_file_prefs(TRG_FILES_TREE_VIEW(treeview));
+
+ return handled;
+}
+
+static void trg_files_tree_view_init(TrgFilesTreeView * self)
+{
+ TrgTreeView *ttv = TRG_TREE_VIEW(self);
+ trg_column_description *desc;
+
+ desc = trg_tree_view_reg_column(ttv, TRG_COLTYPE_FILEICONTEXT,
+ FILESCOL_NAME, _("Name"), "name", 0);
+ desc->model_column_extra = FILESCOL_ID;
+
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_SIZE, FILESCOL_SIZE,
+ _("Size"), "size", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_PROG, FILESCOL_PROGRESS,
+ _("Progress"), "progress", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_WANTED, FILESCOL_WANTED,
+ _("Download"), "wanted", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_PRIO, FILESCOL_PRIORITY,
+ _("Priority"), "priority", 0);
+
+ gtk_tree_view_set_search_column(GTK_TREE_VIEW(self), FILESCOL_NAME);
+
+ g_signal_connect(self, "button-press-event",
+ G_CALLBACK(view_onButtonPressed), NULL);
+ g_signal_connect(self, "popup-menu",
+ G_CALLBACK(trg_files_tree_view_viewOnPopupMenu),
+ NULL);
+}
+
+TrgFilesTreeView *trg_files_tree_view_new(TrgFilesModel * model,
+ TrgMainWindow * win,
+ TrgClient * client,
+ const gchar * configId)
+{
+ GObject *obj = g_object_new(TRG_TYPE_FILES_TREE_VIEW,
+ "config-id", configId,
+ "prefs", trg_client_get_prefs(client),
+ NULL);
+
+ TrgFilesTreeViewPrivate *priv = TRG_FILES_TREE_VIEW_GET_PRIVATE(obj);
+
+ gtk_tree_view_set_model(GTK_TREE_VIEW(obj), GTK_TREE_MODEL(model));
+
+ priv->client = client;
+ priv->win = win;
+
+ trg_tree_view_setup_columns(TRG_TREE_VIEW(obj));
+
+ return TRG_FILES_TREE_VIEW(obj);
+}
diff --git a/src/trg-files-tree-view.h b/src/trg-files-tree-view.h
new file mode 100644
index 0000000..dedcb3d
--- /dev/null
+++ b/src/trg-files-tree-view.h
@@ -0,0 +1,76 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_FILES_TREE_VIEW_H_
+#define TRG_FILES_TREE_VIEW_H_
+
+#include <glib-object.h>
+
+#include "trg-main-window.h"
+#include "trg-client.h"
+#include "trg-files-model.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_FILES_TREE_VIEW trg_files_tree_view_get_type()
+#define TRG_FILES_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_FILES_TREE_VIEW, TrgFilesTreeView))
+#define TRG_FILES_TREE_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_FILES_TREE_VIEW, TrgFilesTreeViewClass))
+#define TRG_IS_FILES_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_FILES_TREE_VIEW))
+#define TRG_IS_FILES_TREE_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_FILES_TREE_VIEW))
+#define TRG_FILES_TREE_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_FILES_TREE_VIEW, TrgFilesTreeViewClass))
+ typedef struct {
+ GtkTreeView parent;
+} TrgFilesTreeView;
+
+typedef struct {
+ GtkTreeViewClass parent_class;
+} TrgFilesTreeViewClass;
+
+enum {
+ NOT_SET = 1000,
+ MIXED = 1001
+};
+
+GType trg_files_tree_view_get_type(void);
+
+TrgFilesTreeView *trg_files_tree_view_new(TrgFilesModel * model,
+ TrgMainWindow * win,
+ TrgClient * client,
+ const gchar * configId);
+
+void
+trg_files_tree_view_renderPriority(GtkTreeViewColumn *
+ column G_GNUC_UNUSED,
+ GtkCellRenderer * renderer,
+ GtkTreeModel * model,
+ GtkTreeIter * iter,
+ gpointer data G_GNUC_UNUSED);
+void trg_files_tree_view_renderDownload(GtkTreeViewColumn *
+ column G_GNUC_UNUSED,
+ GtkCellRenderer * renderer,
+ GtkTreeModel * model,
+ GtkTreeIter * iter,
+ gpointer data G_GNUC_UNUSED);
+
+G_END_DECLS
+#endif /* TRG_FILES_TREE_VIEW_H_ */
diff --git a/src/trg-files-tree.c b/src/trg-files-tree.c
new file mode 100644
index 0000000..b3a3df9
--- /dev/null
+++ b/src/trg-files-tree.c
@@ -0,0 +1,52 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* This is the stuff common between both files trees, built up before
+ * populating the model.
+ */
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+
+#include "trg-files-tree.h"
+
+void trg_files_tree_node_add_child(trg_files_tree_node* node, trg_files_tree_node* child)
+{
+ if (!node->childrenHash) {
+ node->childrenHash = g_hash_table_new(g_str_hash, g_str_equal);
+ }
+ g_hash_table_insert(node->childrenHash, child->name, child);
+ node->children = g_list_append(node->children, child);
+}
+
+void trg_files_tree_node_free(trg_files_tree_node * node)
+{
+ GList *li;
+
+ for (li = node->children; li; li = g_list_next(li))
+ trg_files_tree_node_free((trg_files_tree_node *) li->data);
+
+ if (node->childrenHash)
+ g_hash_table_destroy(node->childrenHash);
+
+ g_list_free(node->children);
+ g_free(node->name);
+ g_free(node);
+}
diff --git a/src/trg-files-tree.h b/src/trg-files-tree.h
new file mode 100644
index 0000000..c5072b6
--- /dev/null
+++ b/src/trg-files-tree.h
@@ -0,0 +1,43 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_FILES_TREE_H_
+#define TRG_FILES_TREE_H_
+
+#include <glib.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-files-tree.h"
+
+typedef struct {
+ gchar *name;
+ gint64 length;
+ gint64 bytesCompleted;
+ GList *children;
+ GHashTable *childrenHash;
+ gint index;
+ gpointer parent;
+ gint priority;
+ gint enabled;
+} trg_files_tree_node;
+
+void trg_files_tree_node_add_child(trg_files_tree_node* node, trg_files_tree_node* child);
+void trg_files_tree_node_free(trg_files_tree_node * node);
+
+#endif /* TRG_FILES_MODEL_H_ */
diff --git a/src/trg-general-panel.c b/src/trg-general-panel.c
new file mode 100644
index 0000000..cec641a
--- /dev/null
+++ b/src/trg-general-panel.c
@@ -0,0 +1,342 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <string.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "trg-client.h"
+#include "torrent.h"
+#include "util.h"
+#include "trg-general-panel.h"
+#include "trg-torrent-model.h"
+
+#define TRG_GENERAL_PANEL_WIDTH_FROM_KEY 20
+#define TRG_GENERAL_PANEL_WIDTH_FROM_VALUE 60
+#define TRG_GENERAL_PANEL_SPACING_X 4
+#define TRG_GENERAL_PANEL_SPACING_Y 2
+#define TRG_GENERAL_PANEL_COLUMNS 3
+#define TRG_GENERAL_PANEL_COLUMNS_TOTAL (TRG_GENERAL_PANEL_COLUMNS*2)
+
+static void gtk_label_clear(GtkLabel * l);
+static GtkLabel *gen_panel_label_get_key_label(GtkLabel * l);
+static GtkLabel *trg_general_panel_add_label(TrgGeneralPanel * gp,
+ char *key, guint col,
+ guint row);
+
+G_DEFINE_TYPE(TrgGeneralPanel, trg_general_panel, GTK_TYPE_TABLE)
+#define TRG_GENERAL_PANEL_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_GENERAL_PANEL, TrgGeneralPanelPrivate))
+typedef struct _TrgGeneralPanelPrivate TrgGeneralPanelPrivate;
+
+struct _TrgGeneralPanelPrivate {
+ GtkLabel *gen_name_label;
+ GtkLabel *gen_size_label;
+ GtkLabel *gen_completed_label;
+ GtkLabel *gen_seeders_label;
+ GtkLabel *gen_leechers_label;
+ GtkLabel *gen_status_label;
+ GtkLabel *gen_eta_label;
+ GtkLabel *gen_downloaded_label;
+ GtkLabel *gen_uploaded_label;
+ GtkLabel *gen_down_rate_label;
+ GtkLabel *gen_up_rate_label;
+ GtkLabel *gen_ratio_label;
+ GtkLabel *gen_completedat_label;
+ GtkLabel *gen_downloaddir_label;
+ GtkLabel *gen_comment_label;
+ GtkLabel *gen_error_label;
+ GtkTreeModel *model;
+ TrgClient *tc;
+};
+
+void trg_general_panel_clear(TrgGeneralPanel * panel)
+{
+ TrgGeneralPanelPrivate *priv = TRG_GENERAL_PANEL_GET_PRIVATE(panel);
+
+ gtk_label_clear(priv->gen_name_label);
+ gtk_label_clear(priv->gen_size_label);
+ gtk_label_clear(priv->gen_completed_label);
+ gtk_label_clear(priv->gen_seeders_label);
+ gtk_label_clear(priv->gen_leechers_label);
+ gtk_label_clear(priv->gen_status_label);
+ gtk_label_clear(priv->gen_eta_label);
+ gtk_label_clear(priv->gen_downloaded_label);
+ gtk_label_clear(priv->gen_uploaded_label);
+ gtk_label_clear(priv->gen_down_rate_label);
+ gtk_label_clear(priv->gen_up_rate_label);
+ gtk_label_clear(priv->gen_ratio_label);
+ gtk_label_clear(priv->gen_completedat_label);
+ gtk_label_clear(priv->gen_downloaddir_label);
+ gtk_label_clear(priv->gen_comment_label);
+ gtk_label_clear(priv->gen_error_label);
+ gtk_label_clear(gen_panel_label_get_key_label
+ (GTK_LABEL(priv->gen_error_label)));
+}
+
+static void gtk_label_clear(GtkLabel * l)
+{
+ gtk_label_set_text(l, "");
+}
+
+static GtkLabel *gen_panel_label_get_key_label(GtkLabel * l)
+{
+ return GTK_LABEL(g_object_get_data(G_OBJECT(l), "key-label"));
+}
+
+static void trg_general_panel_class_init(TrgGeneralPanelClass * klass)
+{
+ g_type_class_add_private(klass, sizeof(TrgGeneralPanelPrivate));
+}
+
+void
+trg_general_panel_update(TrgGeneralPanel * panel, JsonObject * t,
+ GtkTreeIter * iter)
+{
+ TrgGeneralPanelPrivate *priv;
+ gchar buf[32];
+ gint sizeOfBuf;
+ gchar *statusString, *fullStatusString, *completedAtString, *comment,
+ *markup;
+ const gchar *errorStr;
+ gint64 eta, uploaded, haveValid, completedAt;
+ GtkLabel *keyLabel;
+ gint64 seeders = 0, leechers = 0;
+
+ priv = TRG_GENERAL_PANEL_GET_PRIVATE(panel);
+
+ gtk_tree_model_get(GTK_TREE_MODEL(priv->model), iter,
+ TORRENT_COLUMN_SEEDS, &seeders,
+ TORRENT_COLUMN_LEECHERS, &leechers,
+ TORRENT_COLUMN_STATUS, &statusString, -1);
+
+ sizeOfBuf = sizeof(buf);
+
+ trg_strlsize(buf, torrent_get_size_when_done(t));
+ gtk_label_set_text(GTK_LABEL(priv->gen_size_label), buf);
+
+ trg_strlspeed(buf, torrent_get_rate_down(t) / disk_K);
+ gtk_label_set_text(GTK_LABEL(priv->gen_down_rate_label), buf);
+
+ trg_strlspeed(buf, torrent_get_rate_up(t) / disk_K);
+ gtk_label_set_text(GTK_LABEL(priv->gen_up_rate_label), buf);
+
+ uploaded = torrent_get_uploaded(t);
+ trg_strlsize(buf, uploaded);
+ gtk_label_set_text(GTK_LABEL(priv->gen_uploaded_label), buf);
+
+ haveValid = torrent_get_have_valid(t);
+ trg_strlsize(buf, torrent_get_downloaded(t));
+ gtk_label_set_text(GTK_LABEL(priv->gen_downloaded_label), buf);
+
+ if (uploaded > 0 && haveValid > 0) {
+ trg_strlratio(buf, (double) uploaded / (double) haveValid);
+ gtk_label_set_text(GTK_LABEL(priv->gen_ratio_label), buf);
+ } else {
+ gtk_label_set_text(GTK_LABEL(priv->gen_ratio_label), _("N/A"));
+ }
+
+ completedAt = torrent_get_done_date(t);
+ if (completedAt > 0) {
+ completedAtString = epoch_to_string(completedAt);
+ gtk_label_set_text(GTK_LABEL(priv->gen_completedat_label),
+ completedAtString);
+ g_free(completedAtString);
+ } else {
+ gtk_label_set_text(GTK_LABEL(priv->gen_completedat_label), "");
+ }
+
+ fullStatusString = g_strdup_printf("%s %s", statusString,
+ torrent_get_is_private(t) ?
+ _("(Private)") : _("(Public)"));
+ gtk_label_set_text(GTK_LABEL(priv->gen_status_label),
+ fullStatusString);
+ g_free(fullStatusString);
+ g_free(statusString);
+
+ trg_strlpercent(buf, torrent_get_percent_done(t));
+ gtk_label_set_text(GTK_LABEL(priv->gen_completed_label), buf);
+
+ gtk_label_set_text(GTK_LABEL(priv->gen_name_label),
+ torrent_get_name(t));
+
+ gtk_label_set_text(GTK_LABEL(priv->gen_downloaddir_label),
+ torrent_get_download_dir(t));
+
+ comment = add_links_to_text(torrent_get_comment(t));
+ gtk_label_set_markup(GTK_LABEL(priv->gen_comment_label), comment);
+ g_free(comment);
+
+ errorStr = torrent_get_errorstr(t);
+ keyLabel =
+ gen_panel_label_get_key_label(GTK_LABEL(priv->gen_error_label));
+ if (strlen(errorStr) > 0) {
+ markup =
+ g_markup_printf_escaped("<span fgcolor=\"red\">%s</span>",
+ errorStr);
+ gtk_label_set_markup(GTK_LABEL(priv->gen_error_label), markup);
+ g_free(markup);
+
+ markup =
+ g_markup_printf_escaped
+ ("<span font_weight=\"bold\" fgcolor=\"red\">%s</span>",
+ _("Error"));
+ gtk_label_set_markup(keyLabel, markup);
+ g_free(markup);
+ } else {
+ gtk_label_clear(GTK_LABEL(priv->gen_error_label));
+ gtk_label_clear(keyLabel);
+ }
+
+ if ((eta = torrent_get_eta(t)) > 0) {
+ tr_strltime_long(buf, eta, sizeOfBuf);
+ gtk_label_set_text(GTK_LABEL(priv->gen_eta_label), buf);
+ } else {
+ gtk_label_set_text(GTK_LABEL(priv->gen_eta_label), _("N/A"));
+ }
+
+ snprintf(buf, sizeof(buf), "%" G_GINT64_FORMAT,
+ seeders >= 0 ? seeders : 0);
+ gtk_label_set_text(GTK_LABEL(priv->gen_seeders_label), buf);
+ snprintf(buf, sizeof(buf), "%" G_GINT64_FORMAT,
+ leechers >= 0 ? leechers : 0);
+ gtk_label_set_text(GTK_LABEL(priv->gen_leechers_label), buf);
+}
+
+static GtkLabel *trg_general_panel_add_label_with_width(TrgGeneralPanel *
+ gp, char *key,
+ guint col,
+ guint row,
+ gint width)
+{
+ GtkWidget *value, *keyLabel, *alignment;
+
+ int startCol = col * 2;
+
+ alignment = gtk_alignment_new(0, 0, 0, 0);
+ keyLabel = gtk_label_new(NULL);
+ if (strlen(key) > 0) {
+ gchar *keyMarkup =
+ g_markup_printf_escaped(strlen(key) > 0 ? "<b>%s:</b>" : "",
+ key);
+ gtk_label_set_markup(GTK_LABEL(keyLabel), keyMarkup);
+ g_free(keyMarkup);
+ }
+ gtk_container_add(GTK_CONTAINER(alignment), keyLabel);
+ gtk_table_attach(GTK_TABLE(gp), alignment, startCol, startCol + 1, row,
+ row + 1, GTK_FILL, 0, TRG_GENERAL_PANEL_SPACING_X,
+ TRG_GENERAL_PANEL_SPACING_Y);
+
+ alignment = gtk_alignment_new(0, 0, 0, 0);
+ value = gtk_label_new(NULL);
+ g_object_set_data(G_OBJECT(value), "key-label", keyLabel);
+ gtk_label_set_selectable(GTK_LABEL(value), TRUE);
+ gtk_container_add(GTK_CONTAINER(alignment), value);
+ gtk_table_attach(GTK_TABLE(gp), alignment, startCol + 1,
+ width <
+ 0 ? TRG_GENERAL_PANEL_COLUMNS_TOTAL - 1 : startCol +
+ 1 + width, row, row + 1, GTK_FILL | GTK_SHRINK, 0,
+ TRG_GENERAL_PANEL_SPACING_X,
+ TRG_GENERAL_PANEL_SPACING_Y);
+
+ return GTK_LABEL(value);
+}
+
+static GtkLabel *trg_general_panel_add_label(TrgGeneralPanel * gp,
+ char *key, guint col,
+ guint row)
+{
+ return trg_general_panel_add_label_with_width(gp, key, col, row, 1);
+}
+
+static void trg_general_panel_init(TrgGeneralPanel * self)
+{
+ TrgGeneralPanelPrivate *priv = TRG_GENERAL_PANEL_GET_PRIVATE(self);
+ int i;
+
+ g_object_set(G_OBJECT(self), "n-columns",
+ TRG_GENERAL_PANEL_COLUMNS_TOTAL, "n-rows", 7, NULL);
+
+ priv->gen_name_label =
+ trg_general_panel_add_label_with_width(self, _("Name"), 0, 0, -1);
+
+ priv->gen_size_label =
+ trg_general_panel_add_label(self, _("Size"), 0, 1);
+ priv->gen_eta_label =
+ trg_general_panel_add_label(self, _("ETA"), 1, 1);
+ priv->gen_completed_label =
+ trg_general_panel_add_label(self, _("Completed"), 2, 1);
+
+ priv->gen_seeders_label =
+ trg_general_panel_add_label(self, _("Seeders"), 0, 2);
+ priv->gen_down_rate_label =
+ trg_general_panel_add_label(self, _("Rate Down"), 1, 2);
+ priv->gen_downloaded_label =
+ trg_general_panel_add_label(self, _("Downloaded"), 2, 2);
+
+ priv->gen_leechers_label =
+ trg_general_panel_add_label(self, _("Leechers"), 0, 3);
+ priv->gen_up_rate_label =
+ trg_general_panel_add_label(self, _("Rate Up"), 1, 3);
+ priv->gen_uploaded_label =
+ trg_general_panel_add_label(self, _("Uploaded"), 2, 3);
+
+ priv->gen_status_label =
+ trg_general_panel_add_label(self, _("Status"), 0, 4);
+ priv->gen_ratio_label =
+ trg_general_panel_add_label(self, _("Ratio"), 1, 4);
+
+ priv->gen_comment_label =
+ trg_general_panel_add_label(self, _("Comment"), 2, 4);
+
+ priv->gen_completedat_label =
+ trg_general_panel_add_label_with_width(self, _("Completed At"), 0,
+ 5, -1);
+
+ priv->gen_downloaddir_label =
+ trg_general_panel_add_label_with_width(self, _("Location"), 1, 5,
+ -1);
+
+ priv->gen_error_label =
+ trg_general_panel_add_label_with_width(self, "", 0, 6, -1);
+
+ for (i = 0; i < TRG_GENERAL_PANEL_COLUMNS_TOTAL; i++)
+ gtk_table_set_col_spacing(GTK_TABLE(self), i,
+ i % 2 ==
+ 0 ? TRG_GENERAL_PANEL_WIDTH_FROM_KEY :
+ TRG_GENERAL_PANEL_WIDTH_FROM_VALUE);
+
+ gtk_widget_set_sensitive(GTK_WIDGET(self), FALSE);
+}
+
+TrgGeneralPanel *trg_general_panel_new(GtkTreeModel * model,
+ TrgClient * tc)
+{
+ GObject *obj;
+ TrgGeneralPanelPrivate *priv;
+
+ obj = g_object_new(TRG_TYPE_GENERAL_PANEL, NULL);
+
+ priv = TRG_GENERAL_PANEL_GET_PRIVATE(obj);
+ priv->model = model;
+ priv->tc = tc;
+
+ return TRG_GENERAL_PANEL(obj);
+}
diff --git a/src/trg-general-panel.h b/src/trg-general-panel.h
new file mode 100644
index 0000000..e1a75bb
--- /dev/null
+++ b/src/trg-general-panel.h
@@ -0,0 +1,61 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_GENERAL_PANEL_H_
+#define TRG_GENERAL_PANEL_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include <glib-object.h>
+
+#include "trg-client.h"
+#include "trg-torrent-model.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_GENERAL_PANEL trg_general_panel_get_type()
+#define TRG_GENERAL_PANEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_GENERAL_PANEL, TrgGeneralPanel))
+#define TRG_GENERAL_PANEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_GENERAL_PANEL, TrgGeneralPanelClass))
+#define TRG_IS_GENERAL_PANEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_GENERAL_PANEL))
+#define TRG_IS_GENERAL_PANEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_GENERAL_PANEL))
+#define TRG_GENERAL_PANEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_GENERAL_PANEL, TrgGeneralPanelClass))
+ typedef struct {
+ GtkTable parent;
+} TrgGeneralPanel;
+
+typedef struct {
+ GtkTableClass parent_class;
+} TrgGeneralPanelClass;
+
+GType trg_general_panel_get_type(void);
+
+TrgGeneralPanel *trg_general_panel_new(GtkTreeModel * model,
+ TrgClient * tc);
+
+G_END_DECLS
+ void trg_general_panel_update(TrgGeneralPanel * panel, JsonObject * t,
+ GtkTreeIter * iter);
+void trg_general_panel_clear(TrgGeneralPanel * panel);
+
+#endif /* TRG_GENERAL_PANEL_H_ */
diff --git a/src/trg-gtk-app.c b/src/trg-gtk-app.c
new file mode 100644
index 0000000..a0c9ac4
--- /dev/null
+++ b/src/trg-gtk-app.c
@@ -0,0 +1,231 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <gtk/gtk.h>
+
+#if GTK_CHECK_VERSION( 3, 0, 0 )
+
+#include "trg-main-window.h"
+#include "trg-gtk-app.h"
+#include "util.h"
+
+enum {
+ PROP_0, PROP_CLIENT, PROP_MINIMISE_ON_START
+};
+
+G_DEFINE_TYPE(TrgGtkApp, trg_gtk_app, GTK_TYPE_APPLICATION)
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_GTK_APP, TrgGtkAppPrivate))
+typedef struct _TrgGtkAppPrivate TrgGtkAppPrivate;
+
+struct _TrgGtkAppPrivate {
+ TrgClient *client;
+ gboolean min_start;
+};
+
+static void
+trg_gtk_app_get_property(GObject * object, guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ TrgGtkAppPrivate *priv = GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_CLIENT:
+ g_value_set_pointer(value, priv->client);
+ break;
+ case PROP_MINIMISE_ON_START:
+ g_value_set_boolean(value, priv->min_start);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_gtk_app_set_property(GObject * object, guint property_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ TrgGtkAppPrivate *priv = GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_CLIENT:
+ priv->client = g_value_get_pointer(value);
+ break;
+ case PROP_MINIMISE_ON_START:
+ priv->min_start = g_value_get_boolean(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void trg_gtk_app_dispose(GObject * object)
+{
+ G_OBJECT_CLASS(trg_gtk_app_parent_class)->dispose(object);
+}
+
+static void trg_gtk_app_finalize(GObject * object)
+{
+ G_OBJECT_CLASS(trg_gtk_app_parent_class)->finalize(object);
+}
+
+static void trg_gtk_app_startup(GtkApplication * app, gpointer data)
+{
+ TrgGtkAppPrivate *priv = GET_PRIVATE(app);
+ TrgMainWindow *window =
+ trg_main_window_new(priv->client, priv->min_start);
+ gtk_window_set_application(GTK_WINDOW(window), app);
+}
+
+static int
+trg_gtk_app_command_line(GApplication * application,
+ GApplicationCommandLine * cmdline)
+{
+ GList *windows =
+ gtk_application_get_windows(GTK_APPLICATION(application));
+ TrgMainWindow *window;
+ gchar **argv;
+
+ if (!windows || !windows->data)
+ return 1;
+
+ window = TRG_MAIN_WINDOW(windows->data);
+ argv = g_application_command_line_get_arguments(cmdline, NULL);
+
+ if (g_application_command_line_get_is_remote(cmdline)) {
+ if (!argv[0]) {
+ gtk_window_present(GTK_WINDOW(window));
+ g_strfreev(argv);
+ } else {
+ return trg_add_from_filename(window, argv);
+ }
+ } else {
+ trg_main_window_set_start_args(window, argv);
+ auto_connect_if_required(TRG_MAIN_WINDOW(windows->data));
+ }
+
+ return 0;
+}
+
+static void shift_args(gchar ** argv, int i)
+{
+ gint j;
+ g_free(argv[i]);
+ for (j = i; argv[j]; j++)
+ argv[j] = argv[j + 1];
+}
+
+static gboolean
+test_local_cmdline(GApplication * application,
+ gchar *** arguments, gint * exit_status)
+{
+ TrgGtkAppPrivate *priv = GET_PRIVATE(application);
+ gchar **argv;
+ gchar *cwd = g_get_current_dir();
+ gchar *tmp;
+ gint i;
+
+ argv = *arguments;
+ shift_args(argv, 0);
+
+ i = 1;
+ while (argv[i]) {
+ if (is_minimised_arg(argv[i])) {
+ shift_args(argv, i);
+ priv->min_start = TRUE;
+ } else if (!is_url(argv[i]) && !is_magnet(argv[i])
+ && g_file_test(argv[i], G_FILE_TEST_IS_REGULAR)
+ && !g_path_is_absolute(argv[i])) {
+ tmp = g_build_path(G_DIR_SEPARATOR_S, cwd, argv[i], NULL);
+ g_free(argv[i]);
+ argv[i] = tmp;
+ }
+ i++;
+ }
+
+ *exit_status = 0;
+
+ g_free(cwd);
+
+ return FALSE;
+}
+
+static void trg_gtk_app_class_init(TrgGtkAppClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ GApplicationClass *app_class = G_APPLICATION_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgGtkAppPrivate));
+
+ object_class->get_property = trg_gtk_app_get_property;
+ object_class->set_property = trg_gtk_app_set_property;
+ object_class->dispose = trg_gtk_app_dispose;
+ object_class->finalize = trg_gtk_app_finalize;
+ app_class->local_command_line = test_local_cmdline;
+
+ g_object_class_install_property(object_class,
+ PROP_CLIENT,
+ g_param_spec_pointer("trg-client",
+ "TClient",
+ "Client",
+ G_PARAM_READWRITE
+ |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_MINIMISE_ON_START,
+ g_param_spec_boolean("min-on-start",
+ "Min On Start",
+ "Min On Start",
+ FALSE,
+ G_PARAM_READWRITE
+ |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+}
+
+static void trg_gtk_app_init(TrgGtkApp * self)
+{
+ g_application_set_inactivity_timeout(G_APPLICATION(self), 10000);
+ g_signal_connect(self, "command-line",
+ G_CALLBACK(trg_gtk_app_command_line), NULL);
+ g_signal_connect(self, "startup", G_CALLBACK(trg_gtk_app_startup),
+ NULL);
+}
+
+TrgGtkApp *trg_gtk_app_new(TrgClient * client)
+{
+ return g_object_new(TRG_TYPE_GTK_APP,
+ "application-id", "uk.org.eth0.trg",
+ "flags", G_APPLICATION_HANDLES_COMMAND_LINE,
+ "trg-client", client, NULL);
+}
+
+#endif
diff --git a/src/trg-gtk-app.h b/src/trg-gtk-app.h
new file mode 100644
index 0000000..b60425a
--- /dev/null
+++ b/src/trg-gtk-app.h
@@ -0,0 +1,55 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <gtk/gtk.h>
+
+#ifndef TRG_GTKAPP_
+#define TRG_GTKAPP_
+#if GTK_CHECK_VERSION( 3, 0, 0 )
+
+#include <glib-object.h>
+
+#include "trg-client.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_GTK_APP trg_gtk_app_get_type()
+#define TRG_GTK_APP(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_GTK_APP, TrgGtkApp))
+#define TRG_GTK_APP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_GTK_APP, TrgGtkAppClass))
+#define TRG_IS_GTK_APP(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_GTK_APP))
+#define TRG_IS_GTK_APP_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_GTK_APP))
+#define TRG_GTK_APP_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_GTK_APP, TrgGtkAppClass))
+ typedef struct {
+ GtkApplication parent;
+} TrgGtkApp;
+
+typedef struct {
+ GtkApplicationClass parent_class;
+} TrgGtkAppClass;
+
+GType trg_gtk_app_get_type(void);
+
+TrgGtkApp *trg_gtk_app_new(TrgClient * client);
+
+#endif
+#endif
diff --git a/src/trg-icons.c b/src/trg-icons.c
new file mode 100644
index 0000000..efa45a7
--- /dev/null
+++ b/src/trg-icons.c
@@ -0,0 +1,63 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <gtk/gtk.h>
+
+#include "icon-turtle.h"
+
+typedef struct {
+ const guint8 *raw;
+ const char *name;
+} BuiltinIconInfo;
+
+static const BuiltinIconInfo my_fallback_icons[] = {
+ {blue_turtle, "alt-speed-on"},
+ {grey_turtle, "alt-speed-off"}
+};
+
+void register_my_icons(GtkIconTheme * theme)
+{
+ int i;
+ const int n = G_N_ELEMENTS(my_fallback_icons);
+ GtkIconFactory *factory = gtk_icon_factory_new();
+
+ gtk_icon_factory_add_default(factory);
+
+ for (i = 0; i < n; ++i) {
+ const char *name = my_fallback_icons[i].name;
+
+ if (!gtk_icon_theme_has_icon(theme, name)) {
+ int width;
+ GdkPixbuf *p;
+ GtkIconSet *icon_set;
+
+ p = gdk_pixbuf_new_from_inline(-1, my_fallback_icons[i].raw,
+ FALSE, NULL);
+ width = gdk_pixbuf_get_width(p);
+ icon_set = gtk_icon_set_new_from_pixbuf(p);
+ gtk_icon_theme_add_builtin_icon(name, width, p);
+ gtk_icon_factory_add(factory, name, icon_set);
+
+ g_object_unref(p);
+ gtk_icon_set_unref(icon_set);
+ }
+ }
+
+ g_object_unref(G_OBJECT(factory));
+}
diff --git a/src/trg-icons.h b/src/trg-icons.h
new file mode 100644
index 0000000..e3555d5
--- /dev/null
+++ b/src/trg-icons.h
@@ -0,0 +1,25 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_ICONS_H_
+#define TRG_ICONS_H_
+
+void register_my_icons(GtkIconTheme * theme);
+
+#endif /* TRG_ICONS_H_ */
diff --git a/src/trg-json-widgets.c b/src/trg-json-widgets.c
new file mode 100644
index 0000000..abca7e1
--- /dev/null
+++ b/src/trg-json-widgets.c
@@ -0,0 +1,183 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-json-widgets.h"
+#include "json.h"
+#include "util.h"
+
+/* Functions for creating widgets that load/save their state from/to a JSON
+ * object. This is used by the torrent properties and remote settings dialogs.
+ * The pattern here is farily similar to that used in local configuration,
+ * the widget creation functions take a list as an argument, which gets a
+ * trg_json_widget_desc appended to it. This contains the key, and the function
+ * pointers for load/save.
+ */
+
+void trg_json_widgets_save(GList * list, JsonObject * out)
+{
+ GList *li;
+ for (li = list; li; li = g_list_next(li)) {
+ trg_json_widget_desc *wd = (trg_json_widget_desc *) li->data;
+ wd->saveFunc(wd->widget, out, wd->key);
+ }
+}
+
+void trg_json_widget_desc_free(trg_json_widget_desc * wd)
+{
+ g_free(wd->key);
+ g_free(wd);
+}
+
+void trg_json_widget_desc_list_free(GList * list)
+{
+ GList *li;
+ for (li = list; li; li = g_list_next(li))
+ trg_json_widget_desc_free((trg_json_widget_desc *) li->data);
+
+ g_list_free(list);
+}
+
+void toggle_active_arg_is_sensitive(GtkToggleButton * b, gpointer data)
+{
+ gtk_widget_set_sensitive(GTK_WIDGET(data),
+ gtk_toggle_button_get_active(b));
+}
+
+GtkWidget *trg_json_widget_check_new(GList ** wl, JsonObject * obj,
+ const gchar * key,
+ const gchar * label,
+ GtkWidget * toggleDep)
+{
+ GtkWidget *w = gtk_check_button_new_with_mnemonic(label);
+ trg_json_widget_desc *wd = g_new0(trg_json_widget_desc, 1);
+
+ wd->saveFunc = trg_json_widget_check_save;
+ wd->key = g_strdup(key);
+ wd->widget = w;
+
+ if (toggleDep) {
+ gtk_widget_set_sensitive(w,
+ gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON(toggleDep)));
+ g_signal_connect(G_OBJECT(toggleDep), "toggled",
+ G_CALLBACK(toggle_active_arg_is_sensitive), w);
+ }
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),
+ json_object_get_boolean_member(obj, key));
+
+ *wl = g_list_append(*wl, wd);
+
+ return w;
+}
+
+GtkWidget *trg_json_widget_entry_new(GList ** wl, JsonObject * obj,
+ const gchar * key,
+ GtkWidget * toggleDep)
+{
+ GtkWidget *w = gtk_entry_new();
+ trg_json_widget_desc *wd = g_new0(trg_json_widget_desc, 1);
+
+ wd->saveFunc = trg_json_widget_entry_save;
+ wd->key = g_strdup(key);
+ wd->widget = w;
+
+ if (toggleDep) {
+ gtk_widget_set_sensitive(w,
+ gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON(toggleDep)));
+ g_signal_connect(G_OBJECT(toggleDep), "toggled",
+ G_CALLBACK(toggle_active_arg_is_sensitive), w);
+ }
+
+ gtk_entry_set_text(GTK_ENTRY(w),
+ json_object_get_string_member(obj, key));
+
+ *wl = g_list_append(*wl, wd);
+
+ return w;
+}
+
+void
+trg_json_widget_time_save(GtkWidget * widget, JsonObject * obj,
+ gchar * key)
+{
+
+ json_object_set_double_member(obj, key,
+ gtk_spin_button_get_value(GTK_SPIN_BUTTON
+ (widget)));
+}
+
+GtkWidget *trg_json_widget_spin_new(GList ** wl, JsonObject * obj,
+ const gchar * key,
+ GtkWidget * toggleDep, gdouble min,
+ gdouble max, gdouble step)
+{
+ GtkWidget *w = gtk_spin_button_new_with_range(min, max, step);
+ trg_json_widget_desc *wd = g_new0(trg_json_widget_desc, 1);
+ JsonNode *node = json_object_get_member(obj, key);
+
+ wd->saveFunc = trg_json_widget_spin_save_double;
+ wd->key = g_strdup(key);
+ wd->widget = w;
+
+ if (toggleDep) {
+ gtk_widget_set_sensitive(w,
+ gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON(toggleDep)));
+ g_signal_connect(G_OBJECT(toggleDep), "toggled",
+ G_CALLBACK(toggle_active_arg_is_sensitive), w);
+ }
+
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(w),
+ json_node_really_get_double(node));
+
+ *wl = g_list_append(*wl, wd);
+
+ return w;
+}
+
+void
+trg_json_widget_check_save(GtkWidget * widget, JsonObject * obj,
+ gchar * key)
+{
+ gboolean active =
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+ json_object_set_boolean_member(obj, key, active);
+}
+
+void
+trg_json_widget_entry_save(GtkWidget * widget, JsonObject * obj,
+ gchar * key)
+{
+ json_object_set_string_member(obj, key,
+ gtk_entry_get_text(GTK_ENTRY(widget)));
+}
+
+void
+trg_json_widget_spin_save_double(GtkWidget * widget, JsonObject * obj,
+ gchar * key)
+{
+ json_object_set_double_member(obj, key,
+ gtk_spin_button_get_value(GTK_SPIN_BUTTON
+ (widget)));
+}
diff --git a/src/trg-json-widgets.h b/src/trg-json-widgets.h
new file mode 100644
index 0000000..a7dde5b
--- /dev/null
+++ b/src/trg-json-widgets.h
@@ -0,0 +1,58 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_JSON_WIDGETS_H_
+#define TRG_JSON_WIDGETS_H_
+
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+typedef struct {
+ GtkWidget *widget;
+ gchar *key;
+ void (*saveFunc) (GtkWidget * widget, JsonObject * obj, gchar * key);
+} trg_json_widget_desc;
+
+void toggle_active_arg_is_sensitive(GtkToggleButton * b, gpointer data);
+
+GtkWidget *trg_json_widget_check_new(GList ** wl, JsonObject * obj,
+ const gchar * key,
+ const gchar * label,
+ GtkWidget * toggleDep);
+GtkWidget *trg_json_widget_entry_new(GList ** wl, JsonObject * obj,
+ const gchar * key,
+ GtkWidget * toggleDep);
+GtkWidget *trg_json_widget_spin_new(GList ** wl, JsonObject * obj,
+ const gchar * key,
+ GtkWidget * toggleDep, gdouble min,
+ gdouble max, gdouble step);
+void trg_json_widget_check_save(GtkWidget * widget, JsonObject * obj,
+ gchar * key);
+void trg_json_widget_entry_save(GtkWidget * widget, JsonObject * obj,
+ gchar * key);
+void trg_json_widget_spin_save_int(GtkWidget * widget, JsonObject * obj,
+ gchar * key);
+void trg_json_widget_spin_save_double(GtkWidget * widget, JsonObject * obj,
+ gchar * key);
+
+void trg_json_widget_desc_free(trg_json_widget_desc * wd);
+void trg_json_widget_desc_list_free(GList * list);
+void trg_json_widgets_save(GList * list, JsonObject * out);
+
+#endif /* TRG_JSON_WIDGETS_H_ */
diff --git a/src/trg-main-window.c b/src/trg-main-window.c
new file mode 100644
index 0000000..975e68f
--- /dev/null
+++ b/src/trg-main-window.c
@@ -0,0 +1,2907 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+#include <gdk/gdkkeysyms.h>
+#if GTK_CHECK_VERSION( 3, 0, 0 )
+#include <gdk/gdkkeysyms-compat.h>
+#endif
+#include <curl/curl.h>
+#ifdef HAVE_LIBNOTIFY
+#include <libnotify/notify.h>
+#endif
+#ifdef HAVE_LIBAPPINDICATOR
+#include <libappindicator/app-indicator.h>
+#endif
+
+#include "trg-client.h"
+#include "json.h"
+#include "util.h"
+#include "requests.h"
+#include "session-get.h"
+#include "torrent.h"
+#include "protocol-constants.h"
+#include "remote-exec.h"
+
+#include "trg-main-window.h"
+#include "trg-icons.h"
+#include "trg-about-window.h"
+#include "trg-tree-view.h"
+#include "trg-prefs.h"
+#include "trg-sortable-filtered-model.h"
+#include "trg-torrent-model.h"
+#include "trg-torrent-tree-view.h"
+#include "trg-peers-model.h"
+#include "trg-peers-tree-view.h"
+#include "trg-files-tree-view.h"
+#include "trg-files-model.h"
+#include "trg-trackers-tree-view.h"
+#include "trg-trackers-model.h"
+#include "trg-state-selector.h"
+#include "trg-torrent-graph.h"
+#include "trg-torrent-move-dialog.h"
+#include "trg-torrent-props-dialog.h"
+#include "trg-torrent-add-url-dialog.h"
+#include "trg-torrent-add-dialog.h"
+#include "trg-toolbar.h"
+#include "trg-menu-bar.h"
+#include "trg-status-bar.h"
+#include "trg-stats-dialog.h"
+#include "trg-remote-prefs-dialog.h"
+#include "trg-preferences-dialog.h"
+
+/* The rather large main window class, which glues everything together. */
+
+static void update_selected_torrent_notebook(TrgMainWindow * win,
+ gint mode, gint64 id);
+#ifdef HAVE_LIBNOTIFY
+static void torrent_event_notification(TrgTorrentModel * model,
+ gchar * icon, gchar * desc,
+ gint tmout, gchar * prefKey,
+ GtkTreeIter * iter, gpointer data);
+#endif
+static void connchange_whatever_statusicon(TrgMainWindow * win,
+ gboolean connected);
+static void update_whatever_statusicon(TrgMainWindow * win,
+ trg_torrent_model_update_stats *
+ stats);
+static void on_torrent_completed(TrgTorrentModel * model,
+ GtkTreeIter * iter, gpointer data);
+static void on_torrent_added(TrgTorrentModel * model, GtkTreeIter * iter,
+ gpointer data);
+static gboolean delete_event(GtkWidget * w, GdkEvent * event,
+ gpointer data);
+static void destroy_window(TrgMainWindow * win,
+ gpointer data G_GNUC_UNUSED);
+static void torrent_tv_onRowActivated(GtkTreeView * treeview,
+ GtkTreePath * path,
+ GtkTreeViewColumn * col,
+ gpointer userdata);
+static void add_url_cb(GtkWidget * w, gpointer data);
+static void add_cb(GtkWidget * w, gpointer data);
+static void disconnect_cb(GtkWidget * w, gpointer data);
+static void open_local_prefs_cb(GtkWidget * w G_GNUC_UNUSED,
+ TrgMainWindow * win);
+static void open_remote_prefs_cb(GtkWidget * w G_GNUC_UNUSED,
+ TrgMainWindow * win);
+static TrgToolbar *trg_main_window_toolbar_new(TrgMainWindow * win);
+static void verify_cb(GtkWidget * w, TrgMainWindow * win);
+static void reannounce_cb(GtkWidget * w, TrgMainWindow * win);
+static void pause_cb(GtkWidget * w, TrgMainWindow * win);
+static void resume_cb(GtkWidget * w, TrgMainWindow * win);
+static void remove_cb(GtkWidget * w, TrgMainWindow * win);
+static void resume_all_cb(GtkWidget * w, TrgMainWindow * win);
+static void pause_all_cb(GtkWidget * w, TrgMainWindow * win);
+static void move_cb(GtkWidget * w, TrgMainWindow * win);
+static void delete_cb(GtkWidget * w, TrgMainWindow * win);
+static void open_props_cb(GtkWidget * w, TrgMainWindow * win);
+static gint confirm_action_dialog(GtkWindow * gtk_win,
+ GtkTreeSelection * selection,
+ const gchar * question_single,
+ const gchar * question_multi,
+ const gchar * action_stock);
+static void view_stats_toggled_cb(GtkWidget * w, gpointer data);
+static void view_states_toggled_cb(GtkCheckMenuItem * w,
+ TrgMainWindow * win);
+static void view_notebook_toggled_cb(GtkCheckMenuItem * w,
+ TrgMainWindow * win);
+static GtkWidget *trg_main_window_notebook_new(TrgMainWindow * win);
+static gboolean on_session_get_timer(gpointer data);
+static gboolean on_session_get(gpointer data);
+static gboolean on_torrent_get(gpointer data, int mode);
+static gboolean on_torrent_get_first(gpointer data);
+static gboolean on_torrent_get_active(gpointer data);
+static gboolean on_torrent_get_update(gpointer data);
+static gboolean on_torrent_get_interactive(gpointer data);
+static gboolean trg_session_update_timerfunc(gpointer data);
+static gboolean trg_update_torrents_timerfunc(gpointer data);
+static void open_about_cb(GtkWidget * w, GtkWindow * parent);
+static gboolean trg_torrent_tree_view_visible_func(GtkTreeModel * model,
+ GtkTreeIter * iter,
+ gpointer data);
+static TrgTorrentTreeView
+ * trg_main_window_torrent_tree_view_new(TrgMainWindow * win,
+ GtkTreeModel * model);
+static gboolean trg_dialog_error_handler(TrgMainWindow * win,
+ trg_response * response);
+static gboolean torrent_selection_changed(GtkTreeSelection * selection,
+ TrgMainWindow * win);
+static void trg_main_window_torrent_scrub(TrgMainWindow * win);
+static void entry_filter_changed_cb(GtkWidget * w, TrgMainWindow * win);
+static void torrent_state_selection_changed(TrgStateSelector * selector,
+ guint flag, gpointer data);
+static void trg_main_window_conn_changed(TrgMainWindow * win,
+ gboolean connected);
+static void trg_main_window_get_property(GObject * object,
+ guint property_id, GValue * value,
+ GParamSpec * pspec);
+static void trg_main_window_set_property(GObject * object,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec);
+static void quit_cb(GtkWidget * w, gpointer data);
+static TrgMenuBar *trg_main_window_menu_bar_new(TrgMainWindow * win);
+static void status_icon_activated(GtkStatusIcon * icon,
+ TrgMainWindow * win);
+static gboolean trg_status_icon_popup_menu_cb(GtkStatusIcon * icon,
+ TrgMainWindow * win);
+static gboolean status_icon_button_press_event(GtkStatusIcon * icon,
+ GdkEventButton * event,
+ TrgMainWindow * win);
+static void clear_filter_entry_cb(GtkEntry * entry,
+ GtkEntryIconPosition icon_pos,
+ GdkEvent * event, gpointer user_data);
+static GtkWidget *trg_imagemenuitem_new(GtkMenuShell * shell,
+ const gchar * text, char *stock_id,
+ gboolean sensitive, GCallback cb,
+ gpointer cbdata);
+static void set_limit_cb(GtkWidget * w, TrgMainWindow * win);
+static GtkWidget *limit_item_new(TrgMainWindow * win, GtkWidget * menu,
+ gint64 currentLimit, gfloat limit);
+static GtkWidget *limit_menu_new(TrgMainWindow * win, gchar * title,
+ gchar * enabledKey, gchar * speedKey,
+ JsonArray * ids);
+static void trg_torrent_tv_view_menu(GtkWidget * treeview,
+ GdkEventButton * event,
+ TrgMainWindow * win);
+static GtkMenu *trg_status_icon_view_menu(TrgMainWindow * win,
+ const gchar * msg);
+static gboolean torrent_tv_button_pressed_cb(GtkWidget * treeview,
+ GdkEventButton * event,
+ gpointer userdata);
+static gboolean torrent_tv_popup_menu_cb(GtkWidget * treeview,
+ gpointer userdata);
+static void trg_main_window_set_hidden_to_tray(TrgMainWindow * win,
+ gboolean hidden);
+static gboolean is_ready_for_torrent_action(TrgMainWindow * win);
+static gboolean window_state_event(TrgMainWindow * win,
+ GdkEventWindowState * event,
+ gpointer trayIcon);
+
+
+G_DEFINE_TYPE(TrgMainWindow, trg_main_window, GTK_TYPE_WINDOW)
+struct _TrgMainWindowPrivate {
+ TrgClient *client;
+ TrgToolbar *toolBar;
+ TrgMenuBar *menuBar;
+
+ TrgStatusBar *statusBar;
+ GtkWidget *iconStatusItem, *iconDownloadingItem, *iconSeedingItem,
+ *iconSepItem;
+#ifdef HAVE_LIBAPPINDICATOR
+ AppIndicator *appIndicator;
+#endif
+ GtkMenu *iconMenu;
+ GtkStatusIcon *statusIcon;
+ TrgStateSelector *stateSelector;
+ GtkWidget *stateSelectorScroller;
+ TrgGeneralPanel *genDetails;
+ GtkWidget *notebook;
+
+ TrgTorrentModel *torrentModel;
+ TrgTorrentTreeView *torrentTreeView;
+ GtkTreeModel *filteredTorrentModel;
+ GtkTreeModel *sortedTorrentModel;
+ gint selectedTorrentId;
+
+ TrgTrackersModel *trackersModel;
+ TrgTrackersTreeView *trackersTreeView;
+
+ TrgFilesModel *filesModel;
+ TrgFilesTreeView *filesTreeView;
+
+ TrgPeersModel *peersModel;
+ TrgPeersTreeView *peersTreeView;
+
+#if TRG_WITH_GRAPH
+ TrgTorrentGraph *graph;
+#endif
+ gint graphNotebookIndex;
+
+ GtkWidget *hpaned, *vpaned;
+ GtkWidget *filterEntry;
+
+ gboolean hidden;
+ gint width, height;
+ guint timerId;
+ guint sessionTimerId;
+ gboolean min_on_start;
+ gboolean queuesEnabled;
+
+ gchar **args;
+};
+
+enum {
+ PROP_0, PROP_CLIENT, PROP_MINIMISE_ON_START
+};
+
+static void reset_connect_args(TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ if (priv->args) {
+ g_strfreev(priv->args);
+ priv->args = NULL;
+ }
+}
+
+static void trg_main_window_init(TrgMainWindow * self)
+{
+ self->priv =
+ G_TYPE_INSTANCE_GET_PRIVATE(self, TRG_TYPE_MAIN_WINDOW,
+ TrgMainWindowPrivate);
+}
+
+gint trg_mw_get_selected_torrent_id(TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ return priv->selectedTorrentId;
+}
+
+static void
+update_selected_torrent_notebook(TrgMainWindow * win, gint mode, gint64 id)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgClient *client = priv->client;
+ gint64 serial = trg_client_get_serial(client);
+ JsonObject *t;
+ GtkTreeIter iter;
+
+ if (id >= 0
+ && get_torrent_data(trg_client_get_torrent_table(client), id, &t,
+ &iter)) {
+ trg_toolbar_torrent_actions_sensitive(priv->toolBar, TRUE);
+ trg_menu_bar_torrent_actions_sensitive(priv->menuBar, TRUE);
+ trg_general_panel_update(priv->genDetails, t, &iter);
+ trg_trackers_model_update(priv->trackersModel, serial, t, mode);
+ trg_files_model_update(priv->filesModel,
+ GTK_TREE_VIEW(priv->filesTreeView),
+ serial, t, mode);
+ trg_peers_model_update(priv->peersModel,
+ TRG_TREE_VIEW(priv->peersTreeView),
+ serial, t, mode);
+ } else {
+ trg_main_window_torrent_scrub(win);
+ }
+
+ priv->selectedTorrentId = id;
+}
+
+#ifdef HAVE_LIBNOTIFY
+static void
+torrent_event_notification(TrgTorrentModel * model,
+ gchar * icon, gchar * desc,
+ gint tmout, gchar * prefKey,
+ GtkTreeIter * iter, gpointer data)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(data);
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+ gchar *name;
+ NotifyNotification *notify;
+
+ if (!trg_prefs_get_bool(prefs, prefKey, TRG_PREFS_NOFLAGS))
+ return;
+
+ gtk_tree_model_get(GTK_TREE_MODEL(model), iter, TORRENT_COLUMN_NAME,
+ &name, -1);
+
+ notify = notify_notification_new(name, desc, icon
+#if !defined(NOTIFY_VERSION_MINOR) || (NOTIFY_VERSION_MAJOR == 0 && NOTIFY_VERSION_MINOR < 7)
+ , NULL
+#endif
+ );
+
+#if !defined(NOTIFY_VERSION_MINOR) || (NOTIFY_VERSION_MAJOR == 0 && NOTIFY_VERSION_MINOR < 7)
+ if (priv->statusIcon && gtk_status_icon_is_embedded(priv->statusIcon))
+ notify_notification_attach_to_status_icon(notify,
+ priv->statusIcon);
+#endif
+
+ notify_notification_set_urgency(notify, NOTIFY_URGENCY_LOW);
+ notify_notification_set_timeout(notify, tmout);
+
+ g_free(name);
+
+ notify_notification_show(notify, NULL);
+}
+#endif
+
+static void
+on_torrent_completed(TrgTorrentModel * model,
+ GtkTreeIter * iter, gpointer data)
+{
+#ifdef HAVE_LIBNOTIFY
+ torrent_event_notification(model, GTK_STOCK_APPLY,
+ _("This torrent has completed."),
+ TORRENT_COMPLETE_NOTIFY_TMOUT,
+ TRG_PREFS_KEY_COMPLETE_NOTIFY, iter, data);
+#endif
+}
+
+static void
+on_torrent_added(TrgTorrentModel * model, GtkTreeIter * iter,
+ gpointer data)
+{
+#ifdef HAVE_LIBNOTIFY
+ torrent_event_notification(model, GTK_STOCK_ADD,
+ _("This torrent has been added."),
+ TORRENT_ADD_NOTIFY_TMOUT,
+ TRG_PREFS_KEY_ADD_NOTIFY, iter, data);
+#endif
+}
+
+static gboolean
+delete_event(GtkWidget * w, GdkEvent * event G_GNUC_UNUSED,
+ gpointer data G_GNUC_UNUSED)
+{
+ return FALSE;
+}
+
+static void
+destroy_window(TrgMainWindow * win, gpointer data G_GNUC_UNUSED)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+
+ trg_prefs_set_int(prefs, TRG_PREFS_KEY_WINDOW_HEIGHT, priv->height,
+ TRG_PREFS_GLOBAL);
+ trg_prefs_set_int(prefs, TRG_PREFS_KEY_WINDOW_WIDTH, priv->width,
+ TRG_PREFS_GLOBAL);
+ trg_prefs_set_int(prefs, TRG_PREFS_KEY_NOTEBOOK_PANED_POS,
+ gtk_paned_get_position(GTK_PANED(priv->vpaned)),
+ TRG_PREFS_GLOBAL);
+ trg_prefs_set_int(prefs, TRG_PREFS_KEY_STATES_PANED_POS,
+ gtk_paned_get_position(GTK_PANED(priv->hpaned)),
+ TRG_PREFS_GLOBAL);
+
+ trg_tree_view_persist(TRG_TREE_VIEW(priv->peersTreeView),
+ TRG_TREE_VIEW_PERSIST_SORT |
+ TRG_TREE_VIEW_PERSIST_LAYOUT);
+ trg_tree_view_persist(TRG_TREE_VIEW(priv->filesTreeView),
+ TRG_TREE_VIEW_PERSIST_SORT |
+ TRG_TREE_VIEW_PERSIST_LAYOUT);
+ trg_tree_view_persist(TRG_TREE_VIEW(priv->torrentTreeView),
+ TRG_TREE_VIEW_PERSIST_SORT |
+ TRG_TREE_VIEW_SORTABLE_PARENT |
+ (trg_prefs_get_int
+ (prefs, TRG_PREFS_KEY_STYLE,
+ TRG_PREFS_GLOBAL) ==
+ TRG_STYLE_CLASSIC ? TRG_TREE_VIEW_PERSIST_LAYOUT
+ : 0));
+ trg_tree_view_persist(TRG_TREE_VIEW(priv->trackersTreeView),
+ TRG_TREE_VIEW_PERSIST_SORT |
+ TRG_TREE_VIEW_PERSIST_LAYOUT);
+ trg_prefs_save(prefs);
+
+#if ! GTK_CHECK_VERSION( 3, 0, 0 )
+ gtk_main_quit();
+#else
+ g_application_quit (g_application_get_default ());
+#endif
+}
+
+static void open_props_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgTorrentPropsDialog *dialog;
+
+ if (priv->selectedTorrentId < 0)
+ return;
+
+ dialog = trg_torrent_props_dialog_new(GTK_WINDOW(win),
+ priv->torrentTreeView,
+ priv->torrentModel,
+ priv->client);
+
+ gtk_widget_show_all(GTK_WIDGET(dialog));
+}
+
+static void
+torrent_tv_onRowActivated(GtkTreeView * treeview,
+ GtkTreePath * path G_GNUC_UNUSED,
+ GtkTreeViewColumn *
+ col G_GNUC_UNUSED, gpointer userdata)
+{
+ open_props_cb(GTK_WIDGET(treeview), userdata);
+}
+
+static void add_url_cb(GtkWidget * w G_GNUC_UNUSED, gpointer data)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(data);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ TrgTorrentAddUrlDialog *dlg = trg_torrent_add_url_dialog_new(win,
+ priv->
+ client);
+ gtk_widget_show_all(GTK_WIDGET(dlg));
+}
+
+static void add_cb(GtkWidget * w G_GNUC_UNUSED, gpointer data)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(data);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (trg_client_is_connected(priv->client))
+ trg_torrent_add_dialog(win, priv->client);
+}
+
+static void pause_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (trg_client_is_connected(priv->client))
+ dispatch_async(priv->client,
+ torrent_pause(build_json_id_array
+ (priv->torrentTreeView)),
+ on_generic_interactive_action, win);
+}
+
+static void pause_all_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (trg_client_is_connected(priv->client))
+ dispatch_async(priv->client, torrent_pause(NULL),
+ on_generic_interactive_action, win);
+}
+
+gint trg_add_from_filename(TrgMainWindow * win, gchar ** uris)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgClient *client = priv->client;
+ TrgPrefs *prefs = trg_client_get_prefs(client);
+ GSList *filesList = NULL;
+ int i;
+
+ if (!trg_client_is_connected(client)) {
+ g_strfreev(uris);
+ return EXIT_SUCCESS;
+ }
+
+ if (uris)
+ for (i = 0; uris[i]; i++)
+ if (uris[i])
+ filesList = g_slist_append(filesList, uris[i]);
+
+ g_free(uris);
+
+ if (!filesList)
+ return EXIT_SUCCESS;
+
+ if (trg_prefs_get_bool(prefs, TRG_PREFS_KEY_ADD_OPTIONS_DIALOG,
+ TRG_PREFS_GLOBAL)) {
+ TrgTorrentAddDialog *dialog =
+ trg_torrent_add_dialog_new(win, client,
+ filesList);
+
+ gtk_widget_show_all(GTK_WIDGET(dialog));
+ } else {
+ struct add_torrent_threadfunc_args *args =
+ g_new0(struct add_torrent_threadfunc_args, 1);
+ args->list = filesList;
+ args->cb_data = win;
+ args->client = client;
+ args->extraArgs = FALSE;
+ args->flags = trg_prefs_get_add_flags(prefs);
+
+ launch_add_thread(args);
+ }
+
+ return EXIT_SUCCESS;
+}
+
+static void resume_all_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (trg_client_is_connected(priv->client))
+ dispatch_async(priv->client, torrent_start(NULL),
+ on_generic_interactive_action, win);
+}
+
+static void resume_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (trg_client_is_connected(priv->client))
+ dispatch_async(priv->client,
+ torrent_start(build_json_id_array
+ (priv->torrentTreeView)),
+ on_generic_interactive_action, win);
+}
+
+static void disconnect_cb(GtkWidget * w G_GNUC_UNUSED, gpointer data)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(data);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ trg_client_inc_connid(priv->client);
+ trg_main_window_conn_changed(TRG_MAIN_WINDOW(data), FALSE);
+ trg_status_bar_reset(priv->statusBar);
+}
+
+void connect_cb(GtkWidget * w, gpointer data)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(data);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+ JsonObject *currentProfile = trg_prefs_get_profile(prefs);
+ JsonObject *profile = NULL;
+ GtkWidget *dialog;
+ int populate_result;
+
+ if (w)
+ profile = (JsonObject *) g_object_get_data(G_OBJECT(w), "profile");
+
+ if (trg_client_is_connected(priv->client))
+ disconnect_cb(NULL, data);
+
+ if (profile && currentProfile != profile)
+ trg_prefs_set_profile(prefs, profile);
+ else
+ trg_prefs_profile_change_emit_signal(prefs);
+
+ populate_result = trg_client_populate_with_settings(priv->client);
+
+ if (populate_result < 0) {
+ gchar *msg;
+
+ switch (populate_result) {
+ case TRG_NO_HOSTNAME_SET:
+ msg = _("No hostname set");
+ break;
+ default:
+ msg = _("Unknown error getting settings");
+ break;
+ }
+
+ dialog = gtk_message_dialog_new(GTK_WINDOW(data),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ (populate_result == TRG_NO_HOSTNAME_SET) ? GTK_MESSAGE_INFO : GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "%s", msg);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ reset_connect_args(TRG_MAIN_WINDOW(data));
+
+ if (populate_result == TRG_NO_HOSTNAME_SET)
+ open_local_prefs_cb (NULL, win);
+
+ return;
+ }
+
+ trg_status_bar_push_connection_msg(priv->statusBar,
+ _("Connecting..."));
+ trg_client_inc_connid(priv->client);
+ dispatch_async(priv->client, session_get(), on_session_get, data);
+}
+
+static void
+open_local_prefs_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ GtkWidget *dlg = trg_preferences_dialog_get_instance(win,
+ priv->client);
+ gtk_widget_show_all(dlg);
+}
+
+static void
+open_remote_prefs_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (trg_client_is_connected(priv->client))
+ gtk_widget_show_all(GTK_WIDGET
+ (trg_remote_prefs_dialog_get_instance
+ (win, priv->client)));
+}
+
+static void
+main_window_toggle_filter_dirs(GtkCheckMenuItem * w, gpointer data)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(data);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (gtk_widget_is_sensitive(GTK_WIDGET(w)))
+ trg_state_selector_set_show_dirs(priv->stateSelector,
+ gtk_check_menu_item_get_active
+ (w));
+}
+
+static void
+main_window_toggle_filter_trackers(GtkCheckMenuItem * w, gpointer data)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(data);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (gtk_widget_is_sensitive(GTK_WIDGET(w)))
+ trg_state_selector_set_show_trackers(priv->stateSelector,
+ gtk_check_menu_item_get_active
+ (w));
+}
+
+static TrgToolbar *trg_main_window_toolbar_new(TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+
+ GObject *b_connect, *b_disconnect, *b_add, *b_resume, *b_pause;
+ GObject *b_remove, *b_delete, *b_props, *b_local_prefs,
+ *b_remote_prefs;
+
+ TrgToolbar *toolBar = trg_toolbar_new(win, prefs);
+
+ g_object_get(toolBar, "connect-button", &b_connect,
+ "disconnect-button", &b_disconnect, "add-button", &b_add,
+ "resume-button", &b_resume, "pause-button", &b_pause,
+ "delete-button", &b_delete, "remove-button", &b_remove,
+ "props-button", &b_props, "remote-prefs-button",
+ &b_remote_prefs, "local-prefs-button", &b_local_prefs,
+ NULL);
+
+ g_signal_connect(b_connect, "clicked", G_CALLBACK(connect_cb), win);
+ g_signal_connect(b_disconnect, "clicked", G_CALLBACK(disconnect_cb),
+ win);
+ g_signal_connect(b_add, "clicked", G_CALLBACK(add_cb), win);
+ g_signal_connect(b_resume, "clicked", G_CALLBACK(resume_cb), win);
+ g_signal_connect(b_pause, "clicked", G_CALLBACK(pause_cb), win);
+ g_signal_connect(b_delete, "clicked", G_CALLBACK(delete_cb), win);
+ g_signal_connect(b_remove, "clicked", G_CALLBACK(remove_cb), win);
+ g_signal_connect(b_props, "clicked", G_CALLBACK(open_props_cb), win);
+ g_signal_connect(b_local_prefs, "clicked",
+ G_CALLBACK(open_local_prefs_cb), win);
+ g_signal_connect(b_remote_prefs, "clicked",
+ G_CALLBACK(open_remote_prefs_cb), win);
+
+ return toolBar;
+}
+
+static void reannounce_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (trg_client_is_connected(priv->client))
+ dispatch_async(priv->client,
+ torrent_reannounce(build_json_id_array
+ (priv->torrentTreeView)),
+ on_generic_interactive_action, win);
+}
+
+static void verify_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (is_ready_for_torrent_action(win))
+ dispatch_async(priv->client,
+ torrent_verify(build_json_id_array
+ (priv->torrentTreeView)),
+ on_generic_interactive_action, win);
+}
+
+static void start_now_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (is_ready_for_torrent_action(win))
+ dispatch_async(priv->client,
+ torrent_start_now(build_json_id_array
+ (priv->torrentTreeView)),
+ on_generic_interactive_action, win);
+}
+
+static void up_queue_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (priv->queuesEnabled && is_ready_for_torrent_action(win))
+ dispatch_async(priv->client,
+ torrent_queue_move_up(build_json_id_array
+ (priv->torrentTreeView)),
+ on_generic_interactive_action, win);
+}
+
+static void top_queue_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (priv->queuesEnabled && is_ready_for_torrent_action(win))
+ dispatch_async(priv->client,
+ torrent_queue_move_top(build_json_id_array
+ (priv->torrentTreeView)),
+ on_generic_interactive_action, win);
+}
+
+static void
+bottom_queue_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (priv->queuesEnabled && is_ready_for_torrent_action(win))
+ dispatch_async(priv->client,
+ torrent_queue_move_bottom(build_json_id_array
+ (priv->torrentTreeView)),
+ on_generic_interactive_action, win);
+}
+
+static void down_queue_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (priv->queuesEnabled && is_ready_for_torrent_action(win))
+ dispatch_async(priv->client,
+ torrent_queue_move_down(build_json_id_array
+ (priv->torrentTreeView)),
+ on_generic_interactive_action, win);
+}
+
+static gint
+confirm_action_dialog(GtkWindow * gtk_win,
+ GtkTreeSelection * selection,
+ const gchar * question_single,
+ const gchar * question_multi,
+ const gchar * action_stock)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(gtk_win);
+ TrgMainWindowPrivate *priv = win->priv;
+ gint selectCount;
+ gint response;
+ GtkWidget *dialog = NULL;
+
+ selectCount = gtk_tree_selection_count_selected_rows(selection);
+
+ if (selectCount == 1) {
+ GList *list;
+ GList *firstNode;
+ GtkTreeIter firstIter;
+ gchar *name = NULL;
+
+ list = gtk_tree_selection_get_selected_rows(selection, NULL);
+ firstNode = g_list_first(list);
+
+ gtk_tree_model_get_iter(GTK_TREE_MODEL
+ (priv->filteredTorrentModel), &firstIter,
+ firstNode->data);
+ gtk_tree_model_get(GTK_TREE_MODEL(priv->filteredTorrentModel),
+ &firstIter, TORRENT_COLUMN_NAME, &name, -1);
+ g_list_foreach(list, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free(list);
+
+ dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(win),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ question_single, name);
+ g_free(name);
+ } else if (selectCount > 1) {
+ dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(win),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ question_multi,
+ selectCount);
+
+ } else {
+ return 0;
+ }
+
+ gtk_dialog_add_buttons(GTK_DIALOG(dialog), GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL, action_stock,
+ GTK_RESPONSE_ACCEPT, NULL);
+ gtk_dialog_set_default_response(GTK_DIALOG(dialog),
+ GTK_RESPONSE_CANCEL);
+ gtk_dialog_set_alternative_button_order(GTK_DIALOG(dialog),
+ GTK_RESPONSE_ACCEPT,
+ GTK_RESPONSE_CANCEL, -1);
+
+ response = gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ return response;
+}
+
+static gboolean is_ready_for_torrent_action(TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ return priv->selectedTorrentId >= 0
+ && trg_client_is_connected(priv->client);
+}
+
+static void move_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (is_ready_for_torrent_action(win))
+ gtk_widget_show_all(GTK_WIDGET
+ (trg_torrent_move_dialog_new
+ (win, priv->client, priv->torrentTreeView)));
+}
+
+static void remove_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ GtkTreeSelection *selection;
+ JsonArray *ids;
+
+ if (!is_ready_for_torrent_action(win))
+ return;
+
+ selection =
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->torrentTreeView));
+ ids = build_json_id_array(priv->torrentTreeView);
+
+ if (confirm_action_dialog(GTK_WINDOW(win), selection, _
+ ("<big><b>Remove torrent \"%s\"?</b></big>"),
+ _("<big><b>Remove %d torrents?</b></big>"),
+ GTK_STOCK_REMOVE) == GTK_RESPONSE_ACCEPT)
+ dispatch_async(priv->client, torrent_remove(ids, FALSE),
+ on_generic_interactive_action, win);
+ else
+ json_array_unref(ids);
+}
+
+static void delete_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ GtkTreeSelection *selection;
+ JsonArray *ids;
+
+ selection =
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->torrentTreeView));
+ ids = build_json_id_array(priv->torrentTreeView);
+
+ if (!is_ready_for_torrent_action(win))
+ return;
+
+ if (confirm_action_dialog(GTK_WINDOW(win), selection, _
+ ("<big><b>Remove and delete torrent \"%s\"?</b></big>"),
+ _
+ ("<big><b>Remove and delete %d torrents?</b></big>"),
+ GTK_STOCK_DELETE) == GTK_RESPONSE_ACCEPT)
+ dispatch_async(priv->client, torrent_remove(ids, TRUE),
+ on_delete_complete, win);
+ else
+ json_array_unref(ids);
+}
+
+static void view_stats_toggled_cb(GtkWidget * w, gpointer data)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(data);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (trg_client_is_connected(priv->client)) {
+ TrgStatsDialog *dlg =
+ trg_stats_dialog_get_instance(TRG_MAIN_WINDOW(data),
+ priv->client);
+
+ gtk_widget_show_all(GTK_WIDGET(dlg));
+ }
+}
+
+static void
+view_states_toggled_cb(GtkCheckMenuItem * w, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ trg_widget_set_visible(priv->stateSelectorScroller,
+ gtk_check_menu_item_get_active(w));
+}
+
+static void
+view_notebook_toggled_cb(GtkCheckMenuItem * w, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ trg_widget_set_visible(priv->notebook,
+ gtk_check_menu_item_get_active(w));
+}
+
+#if TRG_WITH_GRAPH
+static void
+trg_main_window_toggle_graph_cb(GtkCheckMenuItem * w, gpointer data)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(data);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (!gtk_widget_is_sensitive(GTK_WIDGET(w))) {
+ return;
+ } else if (gtk_check_menu_item_get_active(w)) {
+ if (priv->graphNotebookIndex < 0)
+ trg_main_window_add_graph(TRG_MAIN_WINDOW(win), TRUE);
+ } else if (priv->graphNotebookIndex >= 0) {
+ trg_main_window_remove_graph(TRG_MAIN_WINDOW(win));
+ }
+}
+#endif
+
+void
+trg_main_window_notebook_set_visible(TrgMainWindow * win, gboolean visible)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ trg_widget_set_visible(priv->notebook, visible);
+}
+
+static GtkWidget *trg_main_window_notebook_new(TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+
+ GtkWidget *notebook = priv->notebook = gtk_notebook_new();
+ GtkWidget *genScrolledWin = gtk_scrolled_window_new(NULL, NULL);
+
+ priv->genDetails =
+ trg_general_panel_new(GTK_TREE_MODEL(priv->torrentModel),
+ priv->client);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(genScrolledWin),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW
+ (genScrolledWin),
+ GTK_WIDGET(priv->genDetails));
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook), genScrolledWin,
+ gtk_label_new(_("General")));
+
+ priv->trackersModel = trg_trackers_model_new();
+ priv->trackersTreeView =
+ trg_trackers_tree_view_new(priv->trackersModel, priv->client, win,
+ NULL);
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ my_scrolledwin_new(GTK_WIDGET
+ (priv->trackersTreeView)),
+ gtk_label_new(_("Trackers")));
+
+ priv->filesModel = trg_files_model_new();
+ priv->filesTreeView = trg_files_tree_view_new(priv->filesModel, win,
+ priv->client, NULL);
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ my_scrolledwin_new(GTK_WIDGET
+ (priv->filesTreeView)),
+ gtk_label_new(_("Files")));
+
+ priv->peersModel = trg_peers_model_new();
+ priv->peersTreeView =
+ trg_peers_tree_view_new(prefs, priv->peersModel, NULL);
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ my_scrolledwin_new(GTK_WIDGET
+ (priv->peersTreeView)),
+ gtk_label_new(_("Peers")));
+
+#if TRG_WITH_GRAPH
+ if (trg_prefs_get_bool
+ (prefs, TRG_PREFS_KEY_SHOW_GRAPH, TRG_PREFS_GLOBAL))
+ trg_main_window_add_graph(win, FALSE);
+ else
+ priv->graphNotebookIndex = -1;
+#endif
+
+ return notebook;
+}
+
+gboolean on_session_set(gpointer data)
+{
+ trg_response *response = (trg_response *) data;
+ TrgMainWindow *win = TRG_MAIN_WINDOW(response->cb_data);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (response->status == CURLE_OK
+ || response->status == FAIL_RESPONSE_UNSUCCESSFUL)
+ trg_client_update_session(priv->client, on_session_get,
+ response->cb_data);
+
+ trg_dialog_error_handler(TRG_MAIN_WINDOW(response->cb_data), response);
+ trg_response_free(response);
+
+ return FALSE;
+}
+
+static gboolean
+hasEnabledChanged(JsonObject * a, JsonObject * b, const gchar * key)
+{
+ return json_object_get_boolean_member(a, key) !=
+ json_object_get_boolean_member(b, key);
+}
+
+static gboolean on_session_get_timer(gpointer data)
+{
+ trg_response *response = (trg_response *) data;
+ TrgMainWindow *win = TRG_MAIN_WINDOW(response->cb_data);
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+
+ on_session_get(data);
+
+ priv->sessionTimerId = g_timeout_add_seconds(trg_prefs_get_int(prefs,
+ TRG_PREFS_KEY_SESSION_UPDATE_INTERVAL,
+ TRG_PREFS_CONNECTION),
+ trg_session_update_timerfunc,
+ win);
+
+ return FALSE;
+}
+
+static gboolean on_session_get(gpointer data)
+{
+ trg_response *response = (trg_response *) data;
+ TrgMainWindow *win = TRG_MAIN_WINDOW(response->cb_data);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ TrgClient *client = priv->client;
+ gboolean isConnected = trg_client_is_connected(client);
+ JsonObject *lastSession = trg_client_get_session(client);
+ JsonObject *newSession = NULL;
+
+ if (response->obj)
+ newSession = get_arguments(response->obj);
+
+ if (!isConnected) {
+ gdouble version;
+
+ if (trg_dialog_error_handler(win, response)) {
+ trg_response_free(response);
+ reset_connect_args(win);
+ return FALSE;
+ }
+
+ if ((version =
+ session_get_version(newSession)) < TRANSMISSION_MIN_SUPPORTED)
+ {
+ gchar *msg =
+ g_strdup_printf(_
+ ("This application supports Transmission %g and later, you have %g."),
+TRANSMISSION_MIN_SUPPORTED, version);
+ GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(win),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "%s",
+ msg);
+ gtk_window_set_title(GTK_WINDOW(dialog), _("Error"));
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ g_free(msg);
+ trg_response_free(response);
+ reset_connect_args(win);
+ return FALSE;
+ }
+
+ trg_status_bar_connect(priv->statusBar, newSession, client);
+ }
+
+ if (newSession) {
+ gboolean reloadAliases = lastSession
+ && g_strcmp0(session_get_download_dir(lastSession),
+ session_get_download_dir(newSession));
+ gboolean refreshSpeed = lastSession
+ &&
+ (hasEnabledChanged
+ (lastSession, newSession, SGET_ALT_SPEED_ENABLED)
+ || hasEnabledChanged(lastSession, newSession,
+ SGET_SPEED_LIMIT_DOWN_ENABLED)
+ || hasEnabledChanged(lastSession, newSession,
+ SGET_SPEED_LIMIT_UP_ENABLED));
+
+ trg_client_set_session(client, newSession);
+
+ if (reloadAliases)
+ trg_main_window_reload_dir_aliases(win);
+
+ if (refreshSpeed)
+ trg_status_bar_update_speed(priv->statusBar,
+ trg_torrent_model_get_stats
+ (priv->torrentModel),
+ priv->client);
+ }
+
+ if (!isConnected) {
+ trg_main_window_conn_changed(win, TRUE);
+ trg_trackers_tree_view_new_connection(priv->trackersTreeView,
+ client);
+ dispatch_async(client, torrent_get(TORRENT_GET_TAG_MODE_FULL),
+ on_torrent_get_first, win);
+ }
+
+ trg_response_free(response);
+
+ return FALSE;
+}
+
+static void
+connchange_whatever_statusicon(TrgMainWindow * win, gboolean connected)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+ gchar *display = connected ?
+ trg_prefs_get_string(prefs, TRG_PREFS_KEY_PROFILE_NAME,
+ TRG_PREFS_CONNECTION) :
+ g_strdup(_("Disconnected"));
+
+#ifdef HAVE_LIBAPPINDICATOR
+ if (priv->appIndicator) {
+ GtkMenu *menu = trg_status_icon_view_menu(win, display);
+ app_indicator_set_menu(priv->appIndicator, menu);
+ } else {
+#else
+ if (1) {
+#endif
+ if (priv->iconMenu)
+ gtk_widget_destroy(GTK_WIDGET(priv->iconMenu));
+
+ priv->iconMenu = trg_status_icon_view_menu(win, display);
+
+ if (priv->statusIcon)
+ gtk_status_icon_set_tooltip_text(priv->statusIcon, display);
+ }
+
+ g_free(display);
+}
+
+static void
+update_whatever_statusicon(TrgMainWindow * win,
+ trg_torrent_model_update_stats * stats)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+#ifdef HAVE_LIBAPPINDICATOR
+ if (!priv->appIndicator && !priv->statusIcon)
+#else
+ if (!priv->statusIcon)
+#endif
+ return;
+
+ gtk_widget_set_visible(priv->iconSeedingItem, stats != NULL);
+ gtk_widget_set_visible(priv->iconDownloadingItem, stats != NULL);
+ gtk_widget_set_visible(priv->iconSepItem, stats != NULL);
+
+ if (stats) {
+ gchar *downloadingLabel;
+ gchar *seedingLabel;
+ gchar buf[32];
+
+ trg_strlspeed(buf, stats->downRateTotal / disk_K);
+ downloadingLabel = g_strdup_printf(_("%d Downloading @ %s"),
+ stats->down, buf);
+ gtk_menu_item_set_label(GTK_MENU_ITEM(priv->iconDownloadingItem),
+ downloadingLabel);
+ g_free(downloadingLabel);
+
+ trg_strlspeed(buf, stats->upRateTotal / disk_K);
+ seedingLabel = g_strdup_printf(_("%d Seeding @ %s"),
+ stats->seeding, buf);
+ gtk_menu_item_set_label(GTK_MENU_ITEM(priv->iconSeedingItem),
+ seedingLabel);
+ g_free(seedingLabel);
+ }
+}
+
+/*
+ * The callback for a torrent-get response.
+ */
+
+static gboolean on_torrent_get(gpointer data, int mode)
+{
+ trg_response *response = (trg_response *) data;
+ TrgMainWindow *win = TRG_MAIN_WINDOW(response->cb_data);
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgClient *client = priv->client;
+ TrgPrefs *prefs = trg_client_get_prefs(client);
+ trg_torrent_model_update_stats *stats;
+ guint interval;
+ gint old_sort_id;
+ GtkSortType old_order;
+
+ /* Disconnected between request and response callback */
+ if (!trg_client_is_connected(client)) {
+ trg_response_free(response);
+ return FALSE;
+ }
+
+ interval =
+ gtk_widget_get_visible(GTK_WIDGET(win)) ? trg_prefs_get_int(prefs,
+ TRG_PREFS_KEY_UPDATE_INTERVAL,
+ TRG_PREFS_CONNECTION)
+ : trg_prefs_get_int(prefs, TRG_PREFS_KEY_MINUPDATE_INTERVAL,
+ TRG_PREFS_CONNECTION);
+ if (interval < 1)
+ interval = TRG_INTERVAL_DEFAULT;
+
+ if (response->status != CURLE_OK) {
+ gint64 max_retries =
+ trg_prefs_get_int(prefs, TRG_PREFS_KEY_RETRIES,
+ TRG_PREFS_CONNECTION);
+
+ if (trg_client_inc_failcount(client) >= max_retries) {
+ trg_main_window_conn_changed(win, FALSE);
+ trg_dialog_error_handler(win, response);
+ } else {
+ gchar *msg =
+ make_error_message(response->obj, response->status);
+ gchar *statusBarMsg =
+ g_strdup_printf(_("Request %d/%d failed: %s"),
+ trg_client_get_failcount(client),
+ (gint) max_retries, msg);
+ trg_status_bar_push_connection_msg(priv->statusBar,
+ statusBarMsg);
+ g_free(msg);
+ g_free(statusBarMsg);
+ priv->timerId = g_timeout_add_seconds(interval,
+ trg_update_torrents_timerfunc,
+ win);
+ }
+
+ trg_response_free(response);
+
+ return FALSE;
+ }
+
+ trg_client_reset_failcount(client);
+ trg_client_inc_serial(client);
+
+ if (mode != TORRENT_GET_MODE_FIRST)
+ gtk_widget_freeze_child_notify(GTK_WIDGET(priv->torrentTreeView));
+
+ gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE
+ (priv->sortedTorrentModel),
+ &old_sort_id, &old_order);
+ gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE
+ (priv->sortedTorrentModel),
+ GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
+ GTK_SORT_ASCENDING);
+
+ stats =
+ trg_torrent_model_update(priv->torrentModel, client, response->obj,
+ mode);
+
+ gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE
+ (priv->sortedTorrentModel),
+ old_sort_id, old_order);
+
+ if (mode != TORRENT_GET_MODE_FIRST)
+ gtk_widget_thaw_child_notify(GTK_WIDGET(priv->torrentTreeView));
+
+ update_selected_torrent_notebook(win, mode, priv->selectedTorrentId);
+ trg_status_bar_update(priv->statusBar, stats, client);
+ update_whatever_statusicon(win, stats);
+
+#if TRG_WITH_GRAPH
+ if (priv->graphNotebookIndex >= 0)
+ trg_torrent_graph_set_speed(priv->graph, stats);
+#endif
+
+ if (mode != TORRENT_GET_MODE_INTERACTION)
+ priv->timerId = g_timeout_add_seconds(interval,
+ trg_update_torrents_timerfunc,
+ win);
+
+ trg_response_free(response);
+ return FALSE;
+}
+
+static gboolean on_torrent_get_active(gpointer data)
+{
+ return on_torrent_get(data, TORRENT_GET_MODE_ACTIVE);
+}
+
+static gboolean on_torrent_get_first(gpointer data)
+{
+ trg_response *response = (trg_response *) data;
+ TrgMainWindow *win = TRG_MAIN_WINDOW(response->cb_data);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ gboolean result = on_torrent_get(data, TORRENT_GET_MODE_FIRST);
+
+ if (priv->args) {
+ trg_add_from_filename(win, priv->args);
+ priv->args = NULL;
+ }
+
+ return result;
+}
+
+static gboolean on_torrent_get_interactive(gpointer data)
+{
+ return on_torrent_get(data, TORRENT_GET_MODE_INTERACTION);
+}
+
+static gboolean on_torrent_get_update(gpointer data)
+{
+ return on_torrent_get(data, TORRENT_GET_MODE_UPDATE);
+}
+
+static gboolean trg_session_update_timerfunc(gpointer data)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(data);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ trg_client_update_session(priv->client, on_session_get_timer, win);
+
+ return FALSE;
+}
+
+static gboolean trg_update_torrents_timerfunc(gpointer data)
+{
+ /* Check if the TrgMainWindow* has already been destroyed
+ * and, in that case, stop polling the server. */
+ if (!TRG_IS_MAIN_WINDOW (data))
+ return FALSE;
+
+ TrgMainWindow *win = TRG_MAIN_WINDOW(data);
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgClient *tc = priv->client;
+ TrgPrefs *prefs = trg_client_get_prefs(tc);
+
+ if (trg_client_is_connected(tc)) {
+ gboolean activeOnly = trg_prefs_get_bool(prefs,
+ TRG_PREFS_KEY_UPDATE_ACTIVE_ONLY,
+ TRG_PREFS_CONNECTION)
+ && (!trg_prefs_get_bool(prefs,
+ TRG_PREFS_ACTIVEONLY_FULLSYNC_ENABLED,
+ TRG_PREFS_CONNECTION)
+ || (trg_client_get_serial(tc) % trg_prefs_get_int(prefs,
+ TRG_PREFS_ACTIVEONLY_FULLSYNC_EVERY,
+ TRG_PREFS_CONNECTION)
+ != 0));
+ dispatch_async(tc,
+ torrent_get(activeOnly ? TORRENT_GET_TAG_MODE_UPDATE
+ : TORRENT_GET_TAG_MODE_FULL),
+ activeOnly ? on_torrent_get_active :
+ on_torrent_get_update, data);
+ }
+
+ return FALSE;
+}
+
+static void open_about_cb(GtkWidget * w G_GNUC_UNUSED, GtkWindow * parent)
+{
+ GtkWidget *aboutDialog = trg_about_window_new(parent);
+
+ gtk_dialog_run(GTK_DIALOG(aboutDialog));
+ gtk_widget_destroy(aboutDialog);
+}
+
+static gboolean
+trg_torrent_tree_view_visible_func(GtkTreeModel * model,
+ GtkTreeIter * iter, gpointer data)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(data);
+ TrgMainWindowPrivate *priv = win->priv;
+ guint flags;
+ gboolean visible;
+ const gchar *filterText;
+
+ guint32 criteria = trg_state_selector_get_flag(priv->stateSelector);
+
+ gtk_tree_model_get(model, iter, TORRENT_COLUMN_FLAGS, &flags, -1);
+
+ if (criteria != 0) {
+ if (criteria & FILTER_FLAG_TRACKER) {
+ gchar *text =
+ trg_state_selector_get_selected_text(priv->stateSelector);
+ JsonObject *json = NULL;
+ gboolean matchesTracker;
+ gtk_tree_model_get(model, iter, TORRENT_COLUMN_JSON, &json,
+ -1);
+ matchesTracker = (!json
+ || !torrent_has_tracker(json,
+ trg_state_selector_get_url_host_regex
+ (priv->
+ stateSelector),
+ text));
+ g_free(text);
+ if (matchesTracker)
+ return FALSE;
+ } else if (criteria & FILTER_FLAG_DIR) {
+ gchar *text =
+ trg_state_selector_get_selected_text(priv->stateSelector);
+ gchar *dd;
+ int cmp;
+ gtk_tree_model_get(model, iter,
+ TORRENT_COLUMN_DOWNLOADDIR_SHORT, &dd, -1);
+ cmp = g_strcmp0(text, dd);
+ g_free(dd);
+ g_free(text);
+ if (cmp)
+ return FALSE;
+ } else if (!(flags & criteria)) {
+ return FALSE;
+ }
+ }
+
+ visible = TRUE;
+
+ filterText = gtk_entry_get_text(GTK_ENTRY(priv->filterEntry));
+ if (strlen(filterText) > 0) {
+ gchar *name = NULL;
+ gtk_tree_model_get(model, iter, TORRENT_COLUMN_NAME, &name, -1);
+ if (name) {
+ gchar *filterCmp = g_utf8_casefold(filterText, -1);
+ gchar *nameCmp = g_utf8_casefold(name, -1);
+
+ if (!strstr(nameCmp, filterCmp))
+ visible = FALSE;
+
+ g_free(nameCmp);
+ g_free(filterCmp);
+ g_free(name);
+ }
+ }
+
+ return visible;
+}
+
+void trg_main_window_reload_dir_aliases(TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ trg_torrent_model_reload_dir_aliases(priv->client, GTK_TREE_MODEL
+ (priv->torrentModel));
+}
+
+static TrgTorrentTreeView
+ * trg_main_window_torrent_tree_view_new(TrgMainWindow * win,
+ GtkTreeModel * model)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgTorrentTreeView *torrentTreeView =
+ trg_torrent_tree_view_new(priv->client,
+ model);
+
+ GtkTreeSelection *selection =
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(torrentTreeView));
+
+ g_signal_connect(G_OBJECT(selection), "changed",
+ G_CALLBACK(torrent_selection_changed), win);
+
+ return torrentTreeView;
+}
+
+static gboolean
+trg_dialog_error_handler(TrgMainWindow * win, trg_response * response)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (response->status != CURLE_OK) {
+ GtkWidget *dialog;
+ const gchar *msg;
+
+ msg = make_error_message(response->obj, response->status);
+ trg_status_bar_clear_indicators(priv->statusBar);
+ trg_status_bar_push_connection_msg(priv->statusBar, msg);
+ dialog = gtk_message_dialog_new(GTK_WINDOW(win), GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ "%s", msg);
+ gtk_window_set_title(GTK_WINDOW(dialog), _("Error"));
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ g_free((gpointer) msg);
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+static gboolean
+torrent_selection_changed(GtkTreeSelection * selection,
+ TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ GList *selectionList;
+ GList *firstNode;
+ gint64 id;
+
+ if (trg_torrent_model_is_remove_in_progress(priv->torrentModel)) {
+ trg_main_window_torrent_scrub(win);
+ return TRUE;
+ }
+
+ selectionList = gtk_tree_selection_get_selected_rows(selection, NULL);
+ firstNode = g_list_first(selectionList);
+ id = -1;
+
+ if (firstNode) {
+ GtkTreeIter iter;
+ if (gtk_tree_model_get_iter(priv->filteredTorrentModel, &iter,
+ (GtkTreePath *) firstNode->data)) {
+ gtk_tree_model_get(priv->filteredTorrentModel, &iter,
+ TORRENT_COLUMN_ID, &id, -1);
+ }
+ }
+
+ g_list_foreach(selectionList, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free(selectionList);
+
+ update_selected_torrent_notebook(win, TORRENT_GET_MODE_FIRST, id);
+
+ return TRUE;
+}
+
+gboolean on_delete_complete(gpointer data)
+{
+ trg_response *response = (trg_response *) data;
+ TrgMainWindow *win = TRG_MAIN_WINDOW(response->cb_data);
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgClient *tc = priv->client;
+
+ if (trg_client_is_connected(tc) && response->status == CURLE_OK)
+ trg_client_update_session(priv->client, on_session_get,
+ response->cb_data);
+
+ return on_generic_interactive_action(data);
+}
+
+gboolean on_generic_interactive_action(gpointer data)
+{
+ trg_response *response = (trg_response *) data;
+ TrgMainWindow *win = TRG_MAIN_WINDOW(response->cb_data);
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgClient *tc = priv->client;
+
+ if (trg_client_is_connected(tc)) {
+ trg_dialog_error_handler(win, response);
+
+ if (response->status == CURLE_OK) {
+ gint64 id;
+ if (json_object_has_member(response->obj, PARAM_TAG))
+ id = json_object_get_int_member(response->obj, PARAM_TAG);
+ else
+ id = TORRENT_GET_TAG_MODE_FULL;
+
+ dispatch_async(tc, torrent_get(id), on_torrent_get_interactive,
+ win);
+ }
+ }
+
+ trg_response_free(response);
+ return FALSE;
+}
+
+static void trg_main_window_torrent_scrub(TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ gtk_tree_store_clear(GTK_TREE_STORE(priv->filesModel));
+ gtk_list_store_clear(GTK_LIST_STORE(priv->trackersModel));
+ gtk_list_store_clear(GTK_LIST_STORE(priv->peersModel));
+ trg_general_panel_clear(priv->genDetails);
+ trg_trackers_model_set_no_selection(TRG_TRACKERS_MODEL
+ (priv->trackersModel));
+
+ trg_toolbar_torrent_actions_sensitive(priv->toolBar, FALSE);
+ trg_menu_bar_torrent_actions_sensitive(priv->menuBar, FALSE);
+}
+
+static void entry_filter_changed_cb(GtkWidget * w, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ gboolean clearSensitive = gtk_entry_get_text_length(GTK_ENTRY(w)) > 0;
+
+ gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER
+ (priv->filteredTorrentModel));
+
+ g_object_set(priv->filterEntry, "secondary-icon-sensitive",
+ clearSensitive, NULL);
+}
+
+static void
+torrent_state_selection_changed(TrgStateSelector *
+ selector G_GNUC_UNUSED,
+ guint flag G_GNUC_UNUSED, gpointer data)
+{
+ gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(data));
+}
+
+static void
+trg_main_window_conn_changed(TrgMainWindow * win, gboolean connected)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgClient *tc = priv->client;
+
+ trg_toolbar_connected_change(priv->toolBar, connected);
+ trg_menu_bar_connected_change(priv->menuBar, connected);
+
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->torrentTreeView), connected);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->peersTreeView), connected);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->filesTreeView), connected);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->trackersTreeView),
+ connected);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->genDetails), connected);
+
+ if (connected) {
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+ priv->sessionTimerId =
+ g_timeout_add_seconds(trg_prefs_get_int
+ (prefs,
+ TRG_PREFS_KEY_SESSION_UPDATE_INTERVAL,
+ TRG_PREFS_CONNECTION),
+ trg_session_update_timerfunc, win);
+ } else {
+ trg_main_window_torrent_scrub(win);
+ trg_state_selector_disconnect(priv->stateSelector);
+
+#if TRG_WITH_GRAPH
+ if (priv->graphNotebookIndex >= 0)
+ trg_torrent_graph_set_nothing(priv->graph);
+#endif
+
+ trg_torrent_model_remove_all(priv->torrentModel);
+
+ g_source_remove(priv->timerId);
+ g_source_remove(priv->sessionTimerId);
+ priv->sessionTimerId = priv->timerId = 0;
+ }
+
+ trg_client_status_change(tc, connected);
+ connchange_whatever_statusicon(win, connected);
+}
+
+static void
+trg_main_window_get_property(GObject * object,
+ guint property_id, GValue * value,
+ GParamSpec * pspec)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(object);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ switch (property_id) {
+ case PROP_CLIENT:
+ g_value_set_pointer(value, priv->client);
+ break;
+ case PROP_MINIMISE_ON_START:
+ g_value_set_boolean(value, priv->min_on_start);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_main_window_set_property(GObject * object,
+ guint property_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(object);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ switch (property_id) {
+ case PROP_CLIENT:
+ priv->client = g_value_get_pointer(value);
+ break;
+ case PROP_MINIMISE_ON_START:
+ priv->min_on_start = g_value_get_boolean(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void quit_cb(GtkWidget * w G_GNUC_UNUSED, gpointer data)
+{
+ gtk_widget_destroy(GTK_WIDGET(data));
+}
+
+static TrgMenuBar *trg_main_window_menu_bar_new(TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ GObject *b_disconnect, *b_add, *b_resume, *b_pause, *b_verify,
+ *b_remove, *b_delete, *b_props, *b_local_prefs, *b_remote_prefs,
+ *b_about, *b_view_states, *b_view_notebook, *b_view_stats,
+ *b_add_url, *b_quit, *b_move, *b_reannounce, *b_pause_all,
+ *b_resume_all, *b_dir_filters, *b_tracker_filters, *b_up_queue,
+ *b_down_queue, *b_top_queue, *b_bottom_queue,
+#if TRG_WITH_GRAPH
+ *b_show_graph,
+#endif
+ *b_start_now;
+
+ TrgMenuBar *menuBar;
+ GtkAccelGroup *accel_group;
+
+ accel_group = gtk_accel_group_new();
+
+ menuBar =
+ trg_menu_bar_new(win, trg_client_get_prefs(priv->client),
+ priv->torrentTreeView, accel_group);
+
+ g_object_get(menuBar, "disconnect-button", &b_disconnect, "add-button",
+ &b_add, "add-url-button", &b_add_url, "resume-button",
+ &b_resume, "resume-all-button", &b_resume_all,
+ "pause-button", &b_pause, "pause-all-button",
+ &b_pause_all, "delete-button", &b_delete, "remove-button",
+ &b_remove, "move-button", &b_move, "verify-button",
+ &b_verify, "reannounce-button", &b_reannounce,
+ "props-button", &b_props, "remote-prefs-button",
+ &b_remote_prefs, "local-prefs-button", &b_local_prefs,
+ "view-notebook-button", &b_view_notebook,
+ "view-states-button", &b_view_states, "view-stats-button",
+ &b_view_stats, "about-button", &b_about, "quit-button",
+ &b_quit, "dir-filters", &b_dir_filters, "tracker-filters",
+ &b_tracker_filters,
+#if TRG_WITH_GRAPH
+ "show-graph", &b_show_graph,
+#endif
+ "up-queue", &b_up_queue, "down-queue", &b_down_queue,
+ "top-queue", &b_top_queue, "bottom-queue",
+ &b_bottom_queue, "start-now", &b_start_now, NULL);
+
+ g_signal_connect(b_disconnect, "activate", G_CALLBACK(disconnect_cb),
+ win);
+ g_signal_connect(b_add, "activate", G_CALLBACK(add_cb), win);
+ g_signal_connect(b_add_url, "activate", G_CALLBACK(add_url_cb), win);
+ g_signal_connect(b_resume, "activate", G_CALLBACK(resume_cb), win);
+ g_signal_connect(b_resume_all, "activate", G_CALLBACK(resume_all_cb),
+ win);
+ g_signal_connect(b_pause, "activate", G_CALLBACK(pause_cb), win);
+ g_signal_connect(b_pause_all, "activate", G_CALLBACK(pause_all_cb),
+ win);
+ g_signal_connect(b_verify, "activate", G_CALLBACK(verify_cb), win);
+ g_signal_connect(b_reannounce, "activate", G_CALLBACK(reannounce_cb),
+ win);
+ g_signal_connect(b_delete, "activate", G_CALLBACK(delete_cb), win);
+ g_signal_connect(b_remove, "activate", G_CALLBACK(remove_cb), win);
+ g_signal_connect(b_up_queue, "activate", G_CALLBACK(up_queue_cb), win);
+ g_signal_connect(b_down_queue, "activate", G_CALLBACK(down_queue_cb),
+ win);
+ g_signal_connect(b_top_queue, "activate", G_CALLBACK(top_queue_cb),
+ win);
+ g_signal_connect(b_bottom_queue, "activate",
+ G_CALLBACK(bottom_queue_cb), win);
+ g_signal_connect(b_start_now, "activate", G_CALLBACK(start_now_cb),
+ win);
+ g_signal_connect(b_move, "activate", G_CALLBACK(move_cb), win);
+ g_signal_connect(b_about, "activate", G_CALLBACK(open_about_cb), win);
+ g_signal_connect(b_local_prefs, "activate",
+ G_CALLBACK(open_local_prefs_cb), win);
+ g_signal_connect(b_remote_prefs, "activate",
+ G_CALLBACK(open_remote_prefs_cb), win);
+ g_signal_connect(b_view_notebook, "toggled",
+ G_CALLBACK(view_notebook_toggled_cb), win);
+ g_signal_connect(b_dir_filters, "toggled",
+ G_CALLBACK(main_window_toggle_filter_dirs), win);
+ g_signal_connect(b_tracker_filters, "toggled",
+ G_CALLBACK(main_window_toggle_filter_trackers), win);
+ g_signal_connect(b_view_states, "toggled",
+ G_CALLBACK(view_states_toggled_cb), win);
+ g_signal_connect(b_view_stats, "activate",
+ G_CALLBACK(view_stats_toggled_cb), win);
+#if TRG_WITH_GRAPH
+ g_signal_connect(b_show_graph, "toggled",
+ G_CALLBACK(trg_main_window_toggle_graph_cb), win);
+#endif
+ g_signal_connect(b_props, "activate", G_CALLBACK(open_props_cb), win);
+ g_signal_connect(b_quit, "activate", G_CALLBACK(quit_cb), win);
+
+ gtk_window_add_accel_group(GTK_WINDOW(win), accel_group);
+
+ return menuBar;
+}
+
+static void
+status_icon_activated(GtkStatusIcon * icon G_GNUC_UNUSED,
+ TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+
+ trg_main_window_set_hidden_to_tray(win,
+ !priv->hidden
+ && trg_prefs_get_bool(prefs,
+ TRG_PREFS_KEY_SYSTEM_TRAY_MINIMISE,
+ TRG_PREFS_GLOBAL));
+}
+
+static gboolean
+trg_status_icon_popup_menu_cb(GtkStatusIcon * icon, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ gtk_menu_popup(priv->iconMenu, NULL, NULL,
+#ifdef WIN32
+ NULL,
+#else
+ gtk_status_icon_position_menu,
+#endif
+ priv->statusIcon, 0, gtk_get_current_event_time());
+
+ return TRUE;
+}
+
+static gboolean
+status_icon_button_press_event(GtkStatusIcon * icon,
+ GdkEventButton * event, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
+
+ gtk_menu_popup(priv->iconMenu, NULL, NULL,
+#ifdef WIN32
+ NULL,
+#else
+ gtk_status_icon_position_menu,
+#endif
+ priv->statusIcon,
+ event->button,
+ gdk_event_get_time((GdkEvent *) event));
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+static void
+clear_filter_entry_cb(GtkEntry * entry,
+ GtkEntryIconPosition icon_pos,
+ GdkEvent * event, gpointer user_data)
+{
+ gtk_entry_set_text(entry, "");
+}
+
+static GtkWidget *trg_imagemenuitem_new(GtkMenuShell * shell,
+ const gchar * text, char *stock_id,
+ gboolean sensitive, GCallback cb,
+ gpointer cbdata)
+{
+ GtkWidget *item = gtk_image_menu_item_new_with_label(stock_id);
+
+ gtk_image_menu_item_set_use_stock(GTK_IMAGE_MENU_ITEM(item), TRUE);
+ gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM
+ (item), TRUE);
+ gtk_menu_item_set_label(GTK_MENU_ITEM(item), text);
+ g_signal_connect(item, "activate", cb, cbdata);
+ gtk_widget_set_sensitive(item, sensitive);
+ gtk_menu_shell_append(shell, item);
+
+ return item;
+}
+
+static void set_limit_cb(GtkWidget * w, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ GtkWidget *parent = gtk_widget_get_parent(w);
+
+ gint speed = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), "limit"));
+ gchar *speedKey = g_object_get_data(G_OBJECT(parent), "speedKey");
+ gchar *enabledKey = g_object_get_data(G_OBJECT(parent), "enabledKey");
+ gpointer limitIds = g_object_get_data(G_OBJECT(parent), "limit-ids");
+
+ JsonNode *req = NULL;
+ JsonObject *args;
+
+ if (limitIds)
+ req = torrent_set((JsonArray *) limitIds);
+ else
+ req = session_set();
+
+ args = node_get_arguments(req);
+
+ if (speed >= 0)
+ json_object_set_int_member(args, speedKey, speed);
+
+ json_object_set_boolean_member(args, enabledKey, speed >= 0);
+
+ if (limitIds)
+ dispatch_async(priv->client, req, on_generic_interactive_action,
+ win);
+ else
+ dispatch_async(priv->client, req, on_session_set, win);
+}
+
+static void set_priority_cb(GtkWidget * w, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ GtkWidget *parent = gtk_widget_get_parent(w);
+
+ gint priority =
+ GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), "priority"));
+ gpointer limitIds = g_object_get_data(G_OBJECT(parent), "pri-ids");
+
+ JsonNode *req = NULL;
+ JsonObject *args;
+
+ req = torrent_set((JsonArray *) limitIds);
+
+ args = node_get_arguments(req);
+
+ json_object_set_int_member(args, FIELD_BANDWIDTH_PRIORITY, priority);
+
+ dispatch_async(priv->client, req, on_generic_interactive_action, win);
+}
+
+static GtkWidget *limit_item_new(TrgMainWindow * win, GtkWidget * menu,
+ gint64 currentLimit, gfloat limit)
+{
+ char speed[32];
+ GtkWidget *item;
+ gboolean active = limit < 0 ? FALSE : (currentLimit == (gint64) limit);
+
+ trg_strlspeed(speed, limit);
+
+ item = gtk_check_menu_item_new_with_label(speed);
+
+ g_object_set_data(G_OBJECT(item), "limit",
+ GINT_TO_POINTER((gint) limit));
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), active);
+ g_signal_connect(item, "activate", G_CALLBACK(set_limit_cb), win);
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+ return item;
+}
+
+static GtkWidget *priority_menu_item_new(TrgMainWindow * win,
+ GtkMenuShell * menu,
+ const gchar * label, gint value,
+ gint current_value)
+{
+ GtkWidget *item = gtk_check_menu_item_new_with_label(label);
+
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),
+ value == current_value);
+ g_object_set_data(G_OBJECT(item), "priority", GINT_TO_POINTER(value));
+ g_signal_connect(item, "activate", G_CALLBACK(set_priority_cb), win);
+
+ gtk_menu_shell_append(menu, item);
+
+ return item;
+}
+
+static GtkWidget *priority_menu_new(TrgMainWindow * win, JsonArray * ids)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgClient *client = priv->client;
+ JsonObject *t = NULL;
+ gint selected_pri = TR_PRI_UNSET;
+ GtkWidget *toplevel, *menu;
+
+ if (get_torrent_data(trg_client_get_torrent_table(client),
+ priv->selectedTorrentId, &t, NULL))
+ selected_pri = torrent_get_bandwidth_priority(t);
+
+ toplevel = gtk_image_menu_item_new_with_label(GTK_STOCK_NETWORK);
+ gtk_image_menu_item_set_use_stock(GTK_IMAGE_MENU_ITEM(toplevel), TRUE);
+ gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM
+ (toplevel), TRUE);
+ gtk_menu_item_set_label(GTK_MENU_ITEM(toplevel), _("Priority"));
+
+ menu = gtk_menu_new();
+
+ g_object_set_data_full(G_OBJECT(menu), "pri-ids", ids,
+ (GDestroyNotify) json_array_unref);
+
+ priority_menu_item_new(win, GTK_MENU_SHELL(menu), _("High"),
+ TR_PRI_HIGH, selected_pri);
+ priority_menu_item_new(win, GTK_MENU_SHELL(menu), _("Normal"),
+ TR_PRI_NORMAL, selected_pri);
+ priority_menu_item_new(win, GTK_MENU_SHELL(menu), _("Low"), TR_PRI_LOW,
+ selected_pri);
+
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(toplevel), menu);
+
+ return toplevel;
+}
+
+static GtkWidget *limit_menu_new(TrgMainWindow * win, gchar * title,
+ gchar * enabledKey, gchar * speedKey,
+ JsonArray * ids)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgClient *client = priv->client;
+ JsonObject *current = NULL;
+ GtkTreeIter iter;
+ GtkWidget *toplevel, *menu, *item;
+ gint64 limit;
+
+ if (ids)
+ get_torrent_data(trg_client_get_torrent_table(client),
+ priv->selectedTorrentId, &current, &iter);
+ else
+ current = trg_client_get_session(client);
+
+ limit =
+ json_object_get_boolean_member(current,
+ enabledKey) ?
+ json_object_get_int_member(current, speedKey) : -1;
+ toplevel = gtk_image_menu_item_new_with_label(GTK_STOCK_NETWORK);
+ gtk_image_menu_item_set_use_stock(GTK_IMAGE_MENU_ITEM(toplevel), TRUE);
+ gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM
+ (toplevel), TRUE);
+ gtk_menu_item_set_label(GTK_MENU_ITEM(toplevel), title);
+
+ menu = gtk_menu_new();
+
+ g_object_set_data_full(G_OBJECT(menu), "speedKey", g_strdup(speedKey),
+ g_free);
+ g_object_set_data_full(G_OBJECT(menu), "enabledKey",
+ g_strdup(enabledKey), g_free);
+ g_object_set_data_full(G_OBJECT(menu), "limit-ids", ids,
+ (GDestroyNotify) json_array_unref);
+
+ item = gtk_check_menu_item_new_with_label(_("No Limit"));
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), limit < 0);
+ g_object_set_data(G_OBJECT(item), "limit", GINT_TO_POINTER(-1));
+ g_signal_connect(item, "activate", G_CALLBACK(set_limit_cb), win);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ gtk_separator_menu_item_new());
+
+ limit_item_new(win, menu, limit, 0);
+ limit_item_new(win, menu, limit, 5);
+ limit_item_new(win, menu, limit, 10);
+ limit_item_new(win, menu, limit, 25);
+ limit_item_new(win, menu, limit, 50);
+ limit_item_new(win, menu, limit, 75);
+ limit_item_new(win, menu, limit, 100);
+ limit_item_new(win, menu, limit, 150);
+ limit_item_new(win, menu, limit, 200);
+ limit_item_new(win, menu, limit, 300);
+ limit_item_new(win, menu, limit, 400);
+ limit_item_new(win, menu, limit, 500);
+ limit_item_new(win, menu, limit, 750);
+ limit_item_new(win, menu, limit, 1024);
+ limit_item_new(win, menu, limit, 1280);
+ limit_item_new(win, menu, limit, 1536);
+ limit_item_new(win, menu, limit, 2048);
+ limit_item_new(win, menu, limit, 2560);
+ limit_item_new(win, menu, limit, 3072);
+
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(toplevel), menu);
+
+ return toplevel;
+}
+
+static void exec_cmd_cb(GtkWidget * w, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ JsonObject *cmd_obj = (JsonObject *) g_object_get_data(G_OBJECT(w),
+ "cmd-object");
+ GtkTreeSelection *selection =
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->torrentTreeView));
+ GtkTreeModel *model;
+ GList *selectedRows = gtk_tree_selection_get_selected_rows(selection,
+ &model);
+ GError *cmd_error = NULL;
+ gchar *cmd_line = NULL;
+ gchar **argv = NULL;
+
+ cmd_line = build_remote_exec_cmd(priv->client,
+ model,
+ selectedRows,
+ json_object_get_string_member(cmd_obj,
+ TRG_PREFS_KEY_EXEC_COMMANDS_SUBKEY_CMD));
+
+ g_debug("Exec: %s", cmd_line);
+
+ if (!cmd_line)
+ return;
+
+ /* GTK has bug, won't let you pass a string here containing a quoted param, so use parse and then spawn
+ * rather than g_spawn_command_line_async(cmd_line,&cmd_error); */
+
+ g_shell_parse_argv(cmd_line, NULL, &argv, NULL);
+ g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL,
+ &cmd_error);
+
+ g_list_foreach(selectedRows, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free(selectedRows);
+
+ if (argv)
+ g_strfreev(argv);
+
+ g_free(cmd_line);
+
+ if (cmd_error) {
+ GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(win),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK, "%s",
+ cmd_error->message);
+ gtk_window_set_title(GTK_WINDOW(dialog), _("Error"));
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ g_error_free(cmd_error);
+ }
+}
+
+static void
+trg_torrent_tv_view_menu(GtkWidget * treeview,
+ GdkEventButton * event, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+ GtkWidget *menu;
+ gint n_cmds;
+ JsonArray *ids;
+ JsonArray *cmds;
+
+ menu = gtk_menu_new();
+ ids = build_json_id_array(TRG_TORRENT_TREE_VIEW(treeview));
+
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Properties"),
+ GTK_STOCK_PROPERTIES, TRUE,
+ G_CALLBACK(open_props_cb), win);
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Resume"),
+ GTK_STOCK_MEDIA_PLAY, TRUE,
+ G_CALLBACK(resume_cb), win);
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Pause"),
+ GTK_STOCK_MEDIA_PAUSE, TRUE,
+ G_CALLBACK(pause_cb), win);
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Verify"),
+ GTK_STOCK_REFRESH, TRUE, G_CALLBACK(verify_cb),
+ win);
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Re-announce"),
+ GTK_STOCK_REFRESH, TRUE,
+ G_CALLBACK(reannounce_cb), win);
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Move"),
+ GTK_STOCK_HARDDISK, TRUE, G_CALLBACK(move_cb),
+ win);
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Remove"),
+ GTK_STOCK_REMOVE, TRUE, G_CALLBACK(remove_cb),
+ win);
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Remove & Delete"),
+ GTK_STOCK_CLEAR, TRUE, G_CALLBACK(delete_cb),
+ win);
+
+ cmds = trg_prefs_get_array(prefs, TRG_PREFS_KEY_EXEC_COMMANDS,
+ TRG_PREFS_CONNECTION);
+ n_cmds = json_array_get_length(cmds);
+
+ if (n_cmds > 0) {
+ GList *cmds_list = json_array_get_elements(cmds);
+ GtkMenuShell *cmds_shell;
+ GList *cmds_li;
+
+ if (n_cmds < 3) {
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ gtk_separator_menu_item_new());
+ cmds_shell = GTK_MENU_SHELL(menu);
+ } else {
+ GtkImageMenuItem *cmds_menu =
+ GTK_IMAGE_MENU_ITEM(gtk_image_menu_item_new_with_label
+ (GTK_STOCK_EXECUTE));
+ gtk_image_menu_item_set_use_stock(cmds_menu, TRUE);
+ gtk_image_menu_item_set_always_show_image(cmds_menu, TRUE);
+ gtk_menu_item_set_label(GTK_MENU_ITEM(cmds_menu),
+ _("Actions"));
+
+ cmds_shell = GTK_MENU_SHELL(gtk_menu_new());
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(cmds_menu),
+ GTK_WIDGET(cmds_shell));
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ GTK_WIDGET(cmds_menu));
+ }
+
+ for (cmds_li = cmds_list; cmds_li; cmds_li = g_list_next(cmds_li)) {
+ JsonObject *cmd_obj = json_node_get_object((JsonNode *)
+ cmds_li->data);
+ const gchar *cmd_label = json_object_get_string_member(cmd_obj,
+ "label");
+ GtkWidget *item = trg_imagemenuitem_new(cmds_shell, cmd_label,
+ GTK_STOCK_EXECUTE,
+ TRUE,
+ G_CALLBACK
+ (exec_cmd_cb), win);
+ g_object_set_data(G_OBJECT(item), "cmd-object", cmd_obj);
+ }
+
+ g_list_free(cmds_list);
+ }
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ gtk_separator_menu_item_new());
+
+ if (priv->queuesEnabled) {
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Start Now"),
+ GTK_STOCK_MEDIA_PLAY, TRUE,
+ G_CALLBACK(start_now_cb), win);
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Move Up Queue"),
+ GTK_STOCK_GO_UP, TRUE,
+ G_CALLBACK(up_queue_cb), win);
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Move Down Queue"),
+ GTK_STOCK_GO_DOWN, TRUE,
+ G_CALLBACK(down_queue_cb), win);
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Bottom Of Queue"),
+ GTK_STOCK_GOTO_BOTTOM, TRUE,
+ G_CALLBACK(bottom_queue_cb), win);
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Top Of Queue"),
+ GTK_STOCK_GOTO_TOP, TRUE,
+ G_CALLBACK(top_queue_cb), win);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ gtk_separator_menu_item_new());
+ }
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ limit_menu_new(win,
+ _("Down Limit"),
+ FIELD_DOWNLOAD_LIMITED,
+ FIELD_DOWNLOAD_LIMIT, ids));
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ limit_menu_new(win,
+ _("Up Limit"),
+ FIELD_UPLOAD_LIMITED,
+ FIELD_UPLOAD_LIMIT, ids));
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ priority_menu_new(win, ids));
+
+ gtk_widget_show_all(menu);
+
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
+ (event != NULL) ? event->button : 0,
+ gdk_event_get_time((GdkEvent *) event));
+}
+
+static GtkMenu *trg_status_icon_view_menu(TrgMainWindow * win,
+ const gchar * msg)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+ gboolean connected = trg_client_is_connected(priv->client);
+ GtkWidget *menu, *connect;
+
+ menu = gtk_menu_new();
+
+ priv->iconStatusItem = gtk_menu_item_new_with_label(msg);
+ gtk_widget_set_sensitive(priv->iconStatusItem, FALSE);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), priv->iconStatusItem);
+
+ if (connected) {
+ priv->iconDownloadingItem =
+ gtk_menu_item_new_with_label(_("Updating..."));
+ gtk_widget_set_visible(priv->iconDownloadingItem, FALSE);
+ gtk_widget_set_sensitive(priv->iconDownloadingItem, FALSE);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ priv->iconDownloadingItem);
+
+ priv->iconSeedingItem =
+ gtk_menu_item_new_with_label(_("Updating..."));
+ gtk_widget_set_visible(priv->iconSeedingItem, FALSE);
+ gtk_widget_set_sensitive(priv->iconSeedingItem, FALSE);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), priv->iconSeedingItem);
+ }
+
+ priv->iconSepItem = gtk_separator_menu_item_new();
+ gtk_widget_set_sensitive(priv->iconSepItem, FALSE);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), priv->iconSepItem);
+
+ connect = gtk_image_menu_item_new_with_label(GTK_STOCK_CONNECT);
+ gtk_image_menu_item_set_use_stock(GTK_IMAGE_MENU_ITEM(connect), TRUE);
+ gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(connect),
+ TRUE);
+ gtk_menu_item_set_label(GTK_MENU_ITEM(connect), _("Connect"));
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(connect),
+ trg_menu_bar_file_connect_menu_new(win,
+ prefs));
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), connect);
+
+ if (connected) {
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Disconnect"),
+ GTK_STOCK_DISCONNECT, connected,
+ G_CALLBACK(disconnect_cb), win);
+
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Add"),
+ GTK_STOCK_ADD, connected, G_CALLBACK(add_cb),
+ win);
+
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Add from URL"),
+ GTK_STOCK_ADD, connected,
+ G_CALLBACK(add_url_cb), win);
+
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Resume All"),
+ GTK_STOCK_MEDIA_PLAY, connected,
+ G_CALLBACK(resume_all_cb), win);
+
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Pause All"),
+ GTK_STOCK_MEDIA_PAUSE, connected,
+ G_CALLBACK(pause_all_cb), win);
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ limit_menu_new(win, _("Down Limit"),
+ SGET_SPEED_LIMIT_DOWN_ENABLED,
+ SGET_SPEED_LIMIT_DOWN, NULL));
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ limit_menu_new(win, _("Up Limit"),
+ SGET_SPEED_LIMIT_UP_ENABLED,
+ SGET_SPEED_LIMIT_UP, NULL));
+ }
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ gtk_separator_menu_item_new());
+ trg_imagemenuitem_new(GTK_MENU_SHELL(menu), _("Quit"), GTK_STOCK_QUIT,
+ TRUE, G_CALLBACK(quit_cb), win);
+
+ gtk_widget_show_all(menu);
+
+ return GTK_MENU(menu);
+}
+
+static gboolean
+torrent_tv_button_pressed_cb(GtkWidget * treeview,
+ GdkEventButton * event, gpointer userdata)
+{
+ GtkTreeSelection *selection;
+ GtkTreePath *path;
+
+ if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
+
+ if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview),
+ (gint) event->x, (gint) event->y,
+ &path, NULL, NULL, NULL)) {
+ if (!gtk_tree_selection_path_is_selected(selection, path)) {
+ gtk_tree_selection_unselect_all(selection);
+ gtk_tree_selection_select_path(selection, path);
+ }
+
+ gtk_tree_path_free(path);
+
+ trg_torrent_tv_view_menu(treeview, event, userdata);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+torrent_tv_popup_menu_cb(GtkWidget * treeview, gpointer userdata)
+{
+ trg_torrent_tv_view_menu(treeview, NULL, userdata);
+ return TRUE;
+}
+
+static void trg_main_window_set_hidden_to_tray(TrgMainWindow * win,
+ gboolean hidden)
+{
+
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (hidden) {
+ gtk_widget_hide(GTK_WIDGET(win));
+ } else {
+ gtk_window_deiconify(GTK_WINDOW(win));
+ gtk_window_present(GTK_WINDOW(win));
+
+ if (priv->timerId > 0) {
+ g_source_remove(priv->timerId);
+ dispatch_async(priv->client,
+ torrent_get(TORRENT_GET_TAG_MODE_FULL),
+ on_torrent_get_update, win);
+ }
+ }
+
+ priv->hidden = hidden;
+}
+
+static gboolean
+window_state_event(TrgMainWindow * win,
+ GdkEventWindowState * event, gpointer trayIcon)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+
+ if (priv->statusIcon
+ && (event->changed_mask & GDK_WINDOW_STATE_ICONIFIED)
+ && (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED)
+ && trg_prefs_get_bool(prefs, TRG_PREFS_KEY_SYSTEM_TRAY_MINIMISE,
+ TRG_PREFS_GLOBAL)) {
+ trg_main_window_set_hidden_to_tray(win, TRUE);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void trg_main_window_remove_status_icon(TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+#ifdef HAVE_LIBAPPINDICATOR
+ if (priv->appIndicator) {
+ g_object_unref(G_OBJECT(priv->appIndicator));
+
+ priv->appIndicator = NULL;
+ } else {
+#else
+ if (1) {
+#endif
+ if (priv->statusIcon)
+ g_object_unref(G_OBJECT(priv->statusIcon));
+
+ priv->statusIcon = NULL;
+ }
+}
+
+#if TRG_WITH_GRAPH
+void trg_main_window_add_graph(TrgMainWindow * win, gboolean show)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ priv->graph =
+ trg_torrent_graph_new(gtk_widget_get_style(priv->notebook));
+ priv->graphNotebookIndex =
+ gtk_notebook_append_page(GTK_NOTEBOOK(priv->notebook),
+ GTK_WIDGET(priv->graph),
+ gtk_label_new(_("Graph")));
+
+ if (show)
+ gtk_widget_show_all(priv->notebook);
+
+ trg_torrent_graph_start(priv->graph);
+}
+
+void trg_main_window_remove_graph(TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (priv->graphNotebookIndex >= 0) {
+ gtk_notebook_remove_page(GTK_NOTEBOOK(priv->notebook),
+ priv->graphNotebookIndex);
+ priv->graphNotebookIndex = -1;
+ }
+}
+#endif
+
+/*static gboolean status_icon_size_changed(GtkStatusIcon *status_icon,
+ gint size,
+ gpointer user_data)
+{
+ gtk_status_icon_set_from_icon_name(status_icon, PACKAGE_NAME);
+ return TRUE;
+}*/
+
+void trg_main_window_add_status_icon(TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+#ifdef HAVE_LIBAPPINDICATOR
+ if (is_unity() && (priv->appIndicator =
+ app_indicator_new(PACKAGE_NAME, PACKAGE_NAME,
+ APP_INDICATOR_CATEGORY_APPLICATION_STATUS)))
+ {
+ app_indicator_set_status(priv->appIndicator,
+ APP_INDICATOR_STATUS_ACTIVE);
+ app_indicator_set_menu(priv->appIndicator,
+ trg_status_icon_view_menu(win, NULL));
+ } else {
+#else
+ if (!is_unity()) {
+#endif
+ priv->statusIcon =
+ gtk_status_icon_new_from_icon_name(PACKAGE_NAME);
+ gtk_status_icon_set_screen(priv->statusIcon,
+ gtk_window_get_screen(GTK_WINDOW(win)));
+ g_signal_connect(priv->statusIcon, "activate",
+ G_CALLBACK(status_icon_activated), win);
+ g_signal_connect(priv->statusIcon, "button-press-event",
+ G_CALLBACK(status_icon_button_press_event), win);
+ g_signal_connect(priv->statusIcon, "popup-menu",
+ G_CALLBACK(trg_status_icon_popup_menu_cb), win);
+
+ gtk_status_icon_set_visible(priv->statusIcon, TRUE);
+ }
+
+ connchange_whatever_statusicon(win,
+ trg_client_is_connected(priv->client));
+}
+
+TrgStateSelector *trg_main_window_get_state_selector(TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ return priv->stateSelector;
+}
+
+/* Couldn't find a way to get the width/height on exit, so save the
+ * values of this event for when that happens. */
+static gboolean
+trg_main_window_config_event(TrgMainWindow * win,
+ GdkEvent * event,
+ gpointer user_data G_GNUC_UNUSED)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ priv->width = event->configure.width;
+ priv->height = event->configure.height;
+ return FALSE;
+}
+
+static void
+trg_client_session_updated_cb(TrgClient * tc,
+ JsonObject * session, TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ gboolean queuesEnabled;
+
+ trg_status_bar_session_update(priv->statusBar, session);
+
+ if (json_object_has_member(session, SGET_DOWNLOAD_QUEUE_ENABLED)) {
+ queuesEnabled = json_object_get_boolean_member(session,
+ SGET_DOWNLOAD_QUEUE_ENABLED)
+ || json_object_get_boolean_member(session,
+ SGET_SEED_QUEUE_ENABLED);
+ } else {
+ queuesEnabled = FALSE;
+ }
+
+ if (priv->queuesEnabled != queuesEnabled) {
+ trg_menu_bar_set_supports_queues(priv->menuBar, queuesEnabled);
+ trg_state_selector_set_queues_enabled(priv->stateSelector,
+ queuesEnabled);
+ }
+
+ priv->queuesEnabled = queuesEnabled;
+}
+
+/* Drag & Drop support */
+static GtkTargetEntry target_list[] = {
+/* datatype (string), restrictions on DnD (GtkTargetFlags), datatype (int) */
+ {"text/uri-list", GTK_TARGET_OTHER_APP | GTK_TARGET_OTHER_WIDGET, 0}
+};
+
+static guint n_targets = G_N_ELEMENTS(target_list);
+
+static void on_dropped_file(GtkWidget * widget, GdkDragContext * context,
+ gint x, gint y, GtkSelectionData * data,
+ guint info, guint time, gpointer user_data)
+{
+ TrgMainWindow *win = user_data;
+
+ if ((gtk_selection_data_get_length(data) >= 0)
+ && (gtk_selection_data_get_format(data) == 8)) {
+ gchar **uri_list = gtk_selection_data_get_uris(data);
+ guint num_files = g_strv_length(uri_list);
+ gchar **file_list = g_new0(gchar *, num_files + 1);
+ int i;
+
+ for (i = 0; i < num_files; i++)
+ file_list[i] = g_filename_from_uri(uri_list[i], NULL, NULL);
+
+ g_strfreev(uri_list);
+ gtk_drag_finish(context, TRUE, FALSE, time);
+ trg_add_from_filename(win, file_list);
+ } else {
+ gtk_drag_finish(context, FALSE, FALSE, time);
+ }
+}
+
+static gboolean window_key_press_handler(GtkWidget * widget,
+ GdkEvent * event,
+ gpointer user_data)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(widget);
+
+ if ((event->key.state & GDK_CONTROL_MASK)
+ && event->key.keyval == GDK_k) {
+ gtk_widget_grab_focus(win->priv->filterEntry);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GObject *trg_main_window_constructor(GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *
+ construct_params)
+{
+ TrgMainWindow *self = TRG_MAIN_WINDOW(G_OBJECT_CLASS
+ (trg_main_window_parent_class)->
+ constructor(type,
+ n_construct_properties,
+ construct_params));
+ TrgMainWindowPrivate *priv =
+ G_TYPE_INSTANCE_GET_PRIVATE(self, TRG_TYPE_MAIN_WINDOW,
+ TrgMainWindowPrivate);
+ GtkWidget *w;
+ GtkWidget *outerVbox;
+ GtkWidget *toolbarHbox;
+ GtkWidget *outerAlignment;
+ GtkIconTheme *theme;
+ gint width, height, pos;
+ gboolean tray;
+ TrgPrefs *prefs;
+
+ priv->queuesEnabled = TRUE;
+
+ prefs = trg_client_get_prefs(priv->client);
+
+ theme = gtk_icon_theme_get_default();
+ register_my_icons(theme);
+
+#ifdef HAVE_LIBNOTIFY
+ notify_init(PACKAGE_NAME);
+#endif
+ gtk_window_set_default_icon_name(PACKAGE_NAME);
+
+ gtk_window_set_title(GTK_WINDOW(self), _("Transmission Remote"));
+ gtk_window_set_default_size(GTK_WINDOW(self), 1000, 600);
+
+ g_signal_connect(G_OBJECT(self), "delete-event",
+ G_CALLBACK(delete_event), NULL);
+ g_signal_connect(G_OBJECT(self), "destroy", G_CALLBACK(destroy_window),
+ NULL);
+ g_signal_connect(G_OBJECT(self), "window-state-event",
+ G_CALLBACK(window_state_event), NULL);
+ g_signal_connect(G_OBJECT(self), "configure-event",
+ G_CALLBACK(trg_main_window_config_event), NULL);
+ g_signal_connect(G_OBJECT(self), "key-press-event",
+ G_CALLBACK(window_key_press_handler), NULL);
+
+ priv->torrentModel = trg_torrent_model_new();
+ trg_client_set_torrent_table(priv->client,
+ get_torrent_table(priv->torrentModel));
+
+ g_signal_connect(priv->torrentModel, "torrent-completed",
+ G_CALLBACK(on_torrent_completed), self);
+ g_signal_connect(priv->torrentModel, "torrent-added",
+ G_CALLBACK(on_torrent_added), self);
+
+ priv->sortedTorrentModel =
+ gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL
+ (priv->torrentModel));
+
+ priv->filteredTorrentModel =
+ trg_sortable_filtered_model_new(GTK_TREE_SORTABLE
+ (priv->sortedTorrentModel), NULL);
+ gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER
+ (priv->filteredTorrentModel),
+ trg_torrent_tree_view_visible_func,
+ self, NULL);
+
+ priv->torrentTreeView = trg_main_window_torrent_tree_view_new(self,
+ priv->
+ filteredTorrentModel);
+ g_signal_connect(priv->torrentTreeView, "popup-menu",
+ G_CALLBACK(torrent_tv_popup_menu_cb), self);
+ g_signal_connect(priv->torrentTreeView, "button-press-event",
+ G_CALLBACK(torrent_tv_button_pressed_cb), self);
+ g_signal_connect(priv->torrentTreeView, "row-activated",
+ G_CALLBACK(torrent_tv_onRowActivated), self);
+
+ outerVbox = trg_vbox_new(FALSE, 2);
+
+ /* Create a GtkAlignment to hold the outerVbox making possible
+ * some padding. */
+ outerAlignment = gtk_alignment_new (0.5f, 0.5f, 1.0f, 1.0f);
+ gtk_alignment_set_padding (GTK_ALIGNMENT (outerAlignment), 0, 0, 6, 6);
+ gtk_container_add (GTK_CONTAINER (outerAlignment), outerVbox);
+
+ gtk_container_add(GTK_CONTAINER(self), outerAlignment);
+
+ priv->menuBar = trg_main_window_menu_bar_new(self);
+ gtk_box_pack_start(GTK_BOX(outerVbox), GTK_WIDGET(priv->menuBar),
+ FALSE, FALSE, 0);
+
+ toolbarHbox = trg_hbox_new(FALSE, 0);
+ priv->toolBar = trg_main_window_toolbar_new(self);
+ gtk_box_pack_start(GTK_BOX(toolbarHbox), GTK_WIDGET(priv->toolBar),
+ TRUE, TRUE, 0);
+
+ w = gtk_entry_new();
+ gtk_entry_set_icon_from_stock(GTK_ENTRY(w), GTK_ENTRY_ICON_SECONDARY,
+ GTK_STOCK_CLEAR);
+ g_signal_connect(w, "icon-release", G_CALLBACK(clear_filter_entry_cb),
+ NULL);
+ gtk_box_pack_start(GTK_BOX(toolbarHbox), w, FALSE, FALSE, 0);
+ g_object_set(w, "secondary-icon-sensitive", FALSE, NULL);
+ priv->filterEntry = w;
+
+ g_signal_connect(G_OBJECT(priv->filterEntry), "changed",
+ G_CALLBACK(entry_filter_changed_cb), self);
+
+ gtk_box_pack_start(GTK_BOX(outerVbox), GTK_WIDGET(toolbarHbox), FALSE,
+ FALSE, 0);
+
+#if GTK_CHECK_VERSION( 3, 0, 0 )
+ priv->hpaned = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+ priv->vpaned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
+#else
+ priv->vpaned = gtk_vpaned_new();
+ priv->hpaned = gtk_hpaned_new();
+#endif
+
+ gtk_box_pack_start(GTK_BOX(outerVbox), priv->vpaned, TRUE, TRUE, 0);
+ gtk_paned_pack1(GTK_PANED(priv->vpaned), priv->hpaned, TRUE, TRUE);
+
+ priv->stateSelector = trg_state_selector_new(priv->client,
+ priv->torrentModel);
+ priv->stateSelectorScroller =
+ my_scrolledwin_new(GTK_WIDGET(priv->stateSelector));
+ gtk_paned_pack1(GTK_PANED(priv->hpaned), priv->stateSelectorScroller,
+ FALSE, FALSE);
+
+ gtk_paned_pack2(GTK_PANED(priv->hpaned), my_scrolledwin_new(GTK_WIDGET
+ (priv->
+ torrentTreeView)),
+ TRUE, TRUE);
+
+ g_signal_connect(G_OBJECT(priv->stateSelector),
+ "torrent-state-changed",
+ G_CALLBACK(torrent_state_selection_changed),
+ priv->filteredTorrentModel);
+
+ priv->notebook = trg_main_window_notebook_new(self);
+ gtk_paned_pack2(GTK_PANED(priv->vpaned), priv->notebook, FALSE, FALSE);
+
+ tray = trg_prefs_get_bool(prefs, TRG_PREFS_KEY_SYSTEM_TRAY,
+ TRG_PREFS_GLOBAL);
+ if (tray)
+ trg_main_window_add_status_icon(self);
+ else
+ trg_main_window_remove_status_icon(self);
+
+ priv->statusBar = trg_status_bar_new(self, priv->client);
+ g_signal_connect(priv->client, "session-updated",
+ G_CALLBACK(trg_client_session_updated_cb), self);
+
+ gtk_box_pack_start(GTK_BOX(outerVbox), GTK_WIDGET(priv->statusBar),
+ FALSE, FALSE, 2);
+
+ width = trg_prefs_get_int(prefs, TRG_PREFS_KEY_WINDOW_WIDTH,
+ TRG_PREFS_GLOBAL);
+ height = trg_prefs_get_int(prefs, TRG_PREFS_KEY_WINDOW_HEIGHT,
+ TRG_PREFS_GLOBAL);
+
+ pos = trg_prefs_get_int(prefs, TRG_PREFS_KEY_NOTEBOOK_PANED_POS,
+ TRG_PREFS_GLOBAL);
+
+ if (width > 0 && height > 0)
+ gtk_window_set_default_size(GTK_WINDOW(self), width, height);
+ else if (pos < 1)
+ gtk_paned_set_position(GTK_PANED(priv->vpaned), 300);
+
+ if (pos > 0)
+ gtk_paned_set_position(GTK_PANED(priv->vpaned), pos);
+
+ gtk_widget_show_all(GTK_WIDGET(self));
+
+ trg_widget_set_visible(priv->stateSelectorScroller,
+ trg_prefs_get_bool(prefs,
+ TRG_PREFS_KEY_SHOW_STATE_SELECTOR,
+ TRG_PREFS_GLOBAL));
+ trg_widget_set_visible(priv->notebook,
+ trg_prefs_get_bool(prefs,
+ TRG_PREFS_KEY_SHOW_NOTEBOOK,
+ TRG_PREFS_GLOBAL));
+
+ pos = trg_prefs_get_int(prefs, TRG_PREFS_KEY_STATES_PANED_POS,
+ TRG_PREFS_GLOBAL);
+ if (pos > 0)
+ gtk_paned_set_position(GTK_PANED(priv->hpaned), pos);
+
+ if (tray && priv->min_on_start)
+ trg_main_window_set_hidden_to_tray(self, TRUE);
+
+ /* Drag and Drop */
+ gtk_drag_dest_set(GTK_WIDGET(self), /* widget that will accept a drop */
+ GTK_DEST_DEFAULT_ALL, /* default actions for dest on DnD */
+ target_list, /* lists of target to support */
+ n_targets, /* size of list */
+ GDK_ACTION_MOVE /* what to do with data after dropped */
+ /* | GDK_ACTION_COPY ... seems that file managers only need ACTION_MOVE, not ACTION_COPY */
+ );
+
+ g_signal_connect(self, "drag-data-received",
+ G_CALLBACK(on_dropped_file), self);
+
+ return G_OBJECT(self);
+}
+
+static void trg_main_window_class_init(TrgMainWindowClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgMainWindowPrivate));
+
+ object_class->constructor = trg_main_window_constructor;
+ object_class->get_property = trg_main_window_get_property;
+ object_class->set_property = trg_main_window_set_property;
+
+ g_object_class_install_property(object_class,
+ PROP_CLIENT,
+ g_param_spec_pointer("trg-client",
+ "TClient",
+ "Client",
+ G_PARAM_READWRITE
+ |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_MINIMISE_ON_START,
+ g_param_spec_boolean("min-on-start",
+ "Min On Start",
+ "Min On Start",
+ FALSE,
+ G_PARAM_READWRITE
+ |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+}
+
+void trg_main_window_set_start_args(TrgMainWindow * win, gchar ** args)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ priv->args = args;
+}
+
+void auto_connect_if_required(TrgMainWindow * win)
+{
+ TrgMainWindowPrivate *priv = win->priv;
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+ gchar *host = trg_prefs_get_string(prefs, TRG_PREFS_KEY_HOSTNAME,
+ TRG_PREFS_PROFILE);
+
+ if (host) {
+ gint len = strlen(host);
+ g_free(host);
+ if (len > 0
+ && trg_prefs_get_bool(prefs, TRG_PREFS_KEY_AUTO_CONNECT,
+ TRG_PREFS_PROFILE)) {
+ connect_cb(NULL, win);
+ }
+ }
+}
+
+TrgMainWindow *trg_main_window_new(TrgClient * tc, gboolean minonstart)
+{
+ return g_object_new(TRG_TYPE_MAIN_WINDOW, "trg-client", tc,
+ "min-on-start", minonstart, NULL);
+}
+
diff --git a/src/trg-main-window.h b/src/trg-main-window.h
new file mode 100644
index 0000000..3c6ace5
--- /dev/null
+++ b/src/trg-main-window.h
@@ -0,0 +1,87 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MAIN_WINDOW_H_
+#define MAIN_WINDOW_H_
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-torrent-model.h"
+#include "trg-peers-model.h"
+#include "trg-files-model.h"
+#include "trg-trackers-model.h"
+#include "trg-general-panel.h"
+#include "trg-torrent-tree-view.h"
+#include "trg-client.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_MAIN_WINDOW trg_main_window_get_type()
+#define TRG_MAIN_WINDOW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_MAIN_WINDOW, TrgMainWindow))
+#define TRG_MAIN_WINDOW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_MAIN_WINDOW, TrgMainWindowClass))
+#define TRG_IS_MAIN_WINDOW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_MAIN_WINDOW))
+#define TRG_IS_MAIN_WINDOW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_MAIN_WINDOW))
+#define TRG_MAIN_WINDOW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_MAIN_WINDOW, TrgMainWindowClass))
+typedef struct _TrgMainWindowPrivate TrgMainWindowPrivate;
+
+typedef struct {
+ GtkWindow parent;
+ TrgMainWindowPrivate *priv;
+} TrgMainWindow;
+
+typedef struct {
+ GtkWindowClass parent_class;
+} TrgMainWindowClass;
+
+#define TORRENT_COMPLETE_NOTIFY_TMOUT 8000
+#define TORRENT_ADD_NOTIFY_TMOUT 3000
+
+GType trg_main_window_get_type(void);
+gint trg_add_from_filename(TrgMainWindow * win, gchar ** uris);
+gboolean on_session_set(gpointer data);
+gboolean on_delete_complete(gpointer data);
+gboolean on_generic_interactive_action(gpointer data);
+void auto_connect_if_required(TrgMainWindow * win);
+void trg_main_window_set_start_args(TrgMainWindow * win, gchar ** args);
+TrgMainWindow *trg_main_window_new(TrgClient * tc, gboolean minonstart);
+void trg_main_window_add_status_icon(TrgMainWindow * win);
+void trg_main_window_remove_status_icon(TrgMainWindow * win);
+void trg_main_window_add_graph(TrgMainWindow * win, gboolean show);
+void trg_main_window_remove_graph(TrgMainWindow * win);
+TrgStateSelector *trg_main_window_get_state_selector(TrgMainWindow * win);
+gint trg_mw_get_selected_torrent_id(TrgMainWindow * win);
+GtkTreeModel *trg_main_window_get_torrent_model(TrgMainWindow * win);
+void trg_main_window_notebook_set_visible(TrgMainWindow * win,
+ gboolean visible);
+void connect_cb(GtkWidget * w, gpointer data);
+void trg_main_window_reload_dir_aliases(TrgMainWindow * win);
+
+#if !GTK_CHECK_VERSION(2, 21, 1)
+#define gdk_drag_context_get_actions(context) context->actions
+#endif
+
+G_END_DECLS
+#endif /* MAIN_WINDOW_H_ */
diff --git a/src/trg-menu-bar.c b/src/trg-menu-bar.c
new file mode 100644
index 0000000..4fcd06c
--- /dev/null
+++ b/src/trg-menu-bar.c
@@ -0,0 +1,980 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#if GTK_CHECK_VERSION( 3, 0, 0 )
+#include <gdk/gdkkeysyms-compat.h>
+#endif
+
+#include "trg-prefs.h"
+#include "trg-torrent-graph.h"
+#include "trg-tree-view.h"
+#include "trg-torrent-tree-view.h"
+#include "trg-main-window.h"
+#include "trg-menu-bar.h"
+
+enum {
+ PROP_0,
+ PROP_CONNECT_BUTTON,
+ PROP_DISCONNECT_BUTTON,
+ PROP_ADD_BUTTON,
+ PROP_ADD_URL_BUTTON,
+ PROP_REMOVE_BUTTON,
+ PROP_DELETE_BUTTON,
+ PROP_RESUME_BUTTON,
+ PROP_RESUME_ALL_BUTTON,
+ PROP_PAUSE_BUTTON,
+ PROP_PAUSE_ALL_BUTTON,
+ PROP_VERIFY_BUTTON,
+ PROP_REANNOUNCE_BUTTON,
+ PROP_PROPS_BUTTON,
+ PROP_MOVE_BUTTON,
+ PROP_REMOTE_PREFS_BUTTON,
+ PROP_LOCAL_PREFS_BUTTON,
+ PROP_ABOUT_BUTTON,
+ PROP_VIEW_STATS_BUTTON,
+ PROP_VIEW_STATES_BUTTON,
+ PROP_VIEW_NOTEBOOK_BUTTON,
+ PROP_QUIT,
+ PROP_PREFS,
+ PROP_MAIN_WINDOW,
+ PROP_TORRENT_TREE_VIEW,
+ PROP_ACCEL_GROUP,
+ PROP_DIR_FILTERS,
+ PROP_TRACKER_FILTERS,
+#if TRG_WITH_GRAPH
+ PROP_VIEW_SHOW_GRAPH,
+#endif
+ PROP_MOVE_DOWN_QUEUE,
+ PROP_MOVE_UP_QUEUE,
+ PROP_MOVE_BOTTOM_QUEUE,
+ PROP_MOVE_TOP_QUEUE,
+ PROP_START_NOW
+};
+
+#define G_DATAKEY_CONF_KEY "conf-key"
+#define G_DATAKEY_PREF_VALUE "pref-index"
+
+G_DEFINE_TYPE(TrgMenuBar, trg_menu_bar, GTK_TYPE_MENU_BAR)
+#define TRG_MENU_BAR_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_MENU_BAR, TrgMenuBarPrivate))
+typedef struct _TrgMenuBarPrivate TrgMenuBarPrivate;
+
+struct _TrgMenuBarPrivate {
+ GtkWidget *mb_connect;
+ GtkWidget *mb_disconnect;
+ GtkWidget *mb_add;
+ GtkWidget *mb_add_url;
+ GtkWidget *mb_move;
+ GtkWidget *mb_remove;
+ GtkWidget *mb_delete;
+ GtkWidget *mb_resume;
+ GtkWidget *mb_pause;
+ GtkWidget *mb_resume_all;
+ GtkWidget *mb_pause_all;
+ GtkWidget *mb_verify;
+ GtkWidget *mb_reannounce;
+ GtkWidget *mb_props;
+ GtkWidget *mb_local_prefs;
+ GtkWidget *mb_remote_prefs;
+ GtkWidget *mb_view_states;
+ GtkWidget *mb_view_notebook;
+ GtkWidget *mb_view_stats;
+ GtkWidget *mb_about;
+ GtkWidget *mb_quit;
+ GtkWidget *mb_directory_filters;
+ GtkWidget *mb_tracker_filters;
+#if TRG_WITH_GRAPH
+ GtkWidget *mb_view_graph;
+#endif
+ GtkWidget *mb_down_queue;
+ GtkWidget *mb_up_queue;
+ GtkWidget *mb_bottom_queue;
+ GtkWidget *mb_top_queue;
+ GtkWidget *mb_start_now;
+ GtkWidget *mb_queues_seperator;
+ GtkWidget *mb_view_classic;
+ GtkWidget *mb_view_transmission;
+ GtkWidget *mb_view_transmission_compact;
+ GtkAccelGroup *accel_group;
+ TrgPrefs *prefs;
+ TrgMainWindow *main_window;
+ TrgTorrentTreeView *torrent_tree_view;
+};
+
+void trg_menu_bar_set_supports_queues(TrgMenuBar * mb,
+ gboolean supportsQueues)
+{
+ TrgMenuBarPrivate *priv = TRG_MENU_BAR_GET_PRIVATE(mb);
+
+ gtk_widget_set_visible(priv->mb_down_queue, supportsQueues);
+ gtk_widget_set_visible(priv->mb_up_queue, supportsQueues);
+ gtk_widget_set_visible(priv->mb_top_queue, supportsQueues);
+ gtk_widget_set_visible(priv->mb_bottom_queue, supportsQueues);
+ gtk_widget_set_visible(priv->mb_queues_seperator, supportsQueues);
+ gtk_widget_set_visible(priv->mb_start_now, supportsQueues);
+}
+
+void trg_menu_bar_connected_change(TrgMenuBar * mb, gboolean connected)
+{
+ TrgMenuBarPrivate *priv = TRG_MENU_BAR_GET_PRIVATE(mb);
+
+ gtk_widget_set_sensitive(priv->mb_add, connected);
+ gtk_widget_set_sensitive(priv->mb_add_url, connected);
+ gtk_widget_set_sensitive(priv->mb_disconnect, connected);
+ gtk_widget_set_sensitive(priv->mb_remote_prefs, connected);
+ gtk_widget_set_sensitive(priv->mb_view_stats, connected);
+ gtk_widget_set_sensitive(priv->mb_resume_all, connected);
+ gtk_widget_set_sensitive(priv->mb_pause_all, connected);
+}
+
+void
+trg_menu_bar_torrent_actions_sensitive(TrgMenuBar * mb, gboolean sensitive)
+{
+ TrgMenuBarPrivate *priv = TRG_MENU_BAR_GET_PRIVATE(mb);
+
+ gtk_widget_set_sensitive(priv->mb_props, sensitive);
+ gtk_widget_set_sensitive(priv->mb_remove, sensitive);
+ gtk_widget_set_sensitive(priv->mb_delete, sensitive);
+ gtk_widget_set_sensitive(priv->mb_resume, sensitive);
+ gtk_widget_set_sensitive(priv->mb_pause, sensitive);
+ gtk_widget_set_sensitive(priv->mb_verify, sensitive);
+ gtk_widget_set_sensitive(priv->mb_reannounce, sensitive);
+ gtk_widget_set_sensitive(priv->mb_move, sensitive);
+ gtk_widget_set_sensitive(priv->mb_start_now, sensitive);
+ gtk_widget_set_sensitive(priv->mb_up_queue, sensitive);
+ gtk_widget_set_sensitive(priv->mb_down_queue, sensitive);
+ gtk_widget_set_sensitive(priv->mb_top_queue, sensitive);
+ gtk_widget_set_sensitive(priv->mb_bottom_queue, sensitive);
+}
+
+static void
+trg_menu_bar_set_property(GObject * object,
+ guint prop_id, const GValue * value,
+ GParamSpec * pspec G_GNUC_UNUSED)
+{
+ TrgMenuBarPrivate *priv = TRG_MENU_BAR_GET_PRIVATE(object);
+
+ switch (prop_id) {
+ case PROP_ACCEL_GROUP:
+ priv->accel_group = g_value_get_object(value);
+ break;
+ case PROP_PREFS:
+ priv->prefs = g_value_get_object(value);
+ break;
+ case PROP_MAIN_WINDOW:
+ priv->main_window = g_value_get_object(value);
+ break;
+ case PROP_TORRENT_TREE_VIEW:
+ priv->torrent_tree_view = g_value_get_object(value);
+ break;
+ }
+}
+
+static void
+trg_menu_bar_get_property(GObject * object, guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ TrgMenuBarPrivate *priv = TRG_MENU_BAR_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_CONNECT_BUTTON:
+ g_value_set_object(value, priv->mb_connect);
+ break;
+ case PROP_DISCONNECT_BUTTON:
+ g_value_set_object(value, priv->mb_disconnect);
+ break;
+ case PROP_ADD_BUTTON:
+ g_value_set_object(value, priv->mb_add);
+ break;
+ case PROP_ADD_URL_BUTTON:
+ g_value_set_object(value, priv->mb_add_url);
+ break;
+ case PROP_REMOVE_BUTTON:
+ g_value_set_object(value, priv->mb_remove);
+ break;
+ case PROP_DELETE_BUTTON:
+ g_value_set_object(value, priv->mb_delete);
+ break;
+ case PROP_MOVE_UP_QUEUE:
+ g_value_set_object(value, priv->mb_up_queue);
+ break;
+ case PROP_MOVE_DOWN_QUEUE:
+ g_value_set_object(value, priv->mb_down_queue);
+ break;
+ case PROP_MOVE_TOP_QUEUE:
+ g_value_set_object(value, priv->mb_top_queue);
+ break;
+ case PROP_MOVE_BOTTOM_QUEUE:
+ g_value_set_object(value, priv->mb_bottom_queue);
+ break;
+ case PROP_START_NOW:
+ g_value_set_object(value, priv->mb_start_now);
+ break;
+ case PROP_MOVE_BUTTON:
+ g_value_set_object(value, priv->mb_move);
+ break;
+ case PROP_RESUME_BUTTON:
+ g_value_set_object(value, priv->mb_resume);
+ break;
+ case PROP_RESUME_ALL_BUTTON:
+ g_value_set_object(value, priv->mb_resume_all);
+ break;
+ case PROP_PAUSE_BUTTON:
+ g_value_set_object(value, priv->mb_pause);
+ break;
+ case PROP_PAUSE_ALL_BUTTON:
+ g_value_set_object(value, priv->mb_pause_all);
+ break;
+ case PROP_VERIFY_BUTTON:
+ g_value_set_object(value, priv->mb_verify);
+ break;
+ case PROP_REANNOUNCE_BUTTON:
+ g_value_set_object(value, priv->mb_reannounce);
+ break;
+ case PROP_PROPS_BUTTON:
+ g_value_set_object(value, priv->mb_props);
+ break;
+ case PROP_REMOTE_PREFS_BUTTON:
+ g_value_set_object(value, priv->mb_remote_prefs);
+ break;
+ case PROP_LOCAL_PREFS_BUTTON:
+ g_value_set_object(value, priv->mb_local_prefs);
+ break;
+ case PROP_ABOUT_BUTTON:
+ g_value_set_object(value, priv->mb_about);
+ break;
+#if TRG_WITH_GRAPH
+ case PROP_VIEW_SHOW_GRAPH:
+ g_value_set_object(value, priv->mb_view_graph);
+ break;
+#endif
+ case PROP_VIEW_STATES_BUTTON:
+ g_value_set_object(value, priv->mb_view_states);
+ break;
+ case PROP_VIEW_NOTEBOOK_BUTTON:
+ g_value_set_object(value, priv->mb_view_notebook);
+ break;
+ case PROP_VIEW_STATS_BUTTON:
+ g_value_set_object(value, priv->mb_view_stats);
+ break;
+ case PROP_QUIT:
+ g_value_set_object(value, priv->mb_quit);
+ break;
+ case PROP_DIR_FILTERS:
+ g_value_set_object(value, priv->mb_directory_filters);
+ break;
+ case PROP_TRACKER_FILTERS:
+ g_value_set_object(value, priv->mb_tracker_filters);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_menu_bar_install_widget_prop(GObjectClass * class, guint propId,
+ const gchar * name, const gchar * nick)
+{
+ g_object_class_install_property(class,
+ propId,
+ g_param_spec_object(name,
+ nick,
+ nick,
+ GTK_TYPE_WIDGET,
+ G_PARAM_READABLE
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+}
+
+GtkWidget *trg_menu_bar_item_new(GtkMenuShell * shell, const gchar * text,
+ const gchar * stock_id,
+ gboolean sensitive)
+{
+ GtkWidget *item = gtk_image_menu_item_new_with_label(stock_id);
+ gtk_image_menu_item_set_use_stock(GTK_IMAGE_MENU_ITEM(item), TRUE);
+
+ gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM
+ (item), TRUE);
+ gtk_menu_item_set_use_underline(GTK_MENU_ITEM(item), TRUE);
+ gtk_menu_item_set_label(GTK_MENU_ITEM(item), text);
+ gtk_widget_set_sensitive(item, sensitive);
+
+ gtk_menu_shell_append(shell, item);
+
+ return item;
+}
+
+static void
+trg_menu_bar_accel_add(TrgMenuBar * menu, GtkWidget * item,
+ guint key, GdkModifierType mods)
+{
+ TrgMenuBarPrivate *priv = TRG_MENU_BAR_GET_PRIVATE(menu);
+
+ gtk_widget_add_accelerator(item, "activate", priv->accel_group,
+ key, mods, GTK_ACCEL_VISIBLE);
+
+}
+
+static void view_menu_radio_item_toggled_cb(GtkCheckMenuItem * w,
+ gpointer data)
+{
+ TrgPrefs *p = TRG_PREFS(data);
+ const gchar *key =
+ (gchar *) g_object_get_data(G_OBJECT(w), G_DATAKEY_CONF_KEY);
+
+ if (gtk_check_menu_item_get_active(w)) {
+ gint index =
+ GPOINTER_TO_INT(g_object_get_data
+ (G_OBJECT(w), G_DATAKEY_PREF_VALUE));
+ trg_prefs_set_int(p, key, index, TRG_PREFS_GLOBAL);
+ }
+}
+
+static void view_menu_item_toggled_cb(GtkCheckMenuItem * w, gpointer data)
+{
+ TrgPrefs *p = TRG_PREFS(data);
+ const gchar *key =
+ (gchar *) g_object_get_data(G_OBJECT(w), G_DATAKEY_CONF_KEY);
+ trg_prefs_set_bool(p, key, gtk_check_menu_item_get_active(w),
+ TRG_PREFS_GLOBAL);
+}
+
+static void
+view_menu_bar_toggled_dependency_cb(GtkCheckMenuItem * w, gpointer data)
+{
+ gtk_widget_set_sensitive(GTK_WIDGET(data),
+ gtk_check_menu_item_get_active
+ (GTK_CHECK_MENU_ITEM(w)));
+}
+
+static void
+trg_menu_bar_view_item_update(TrgPrefs * p, const gchar * updatedKey,
+ gpointer data)
+{
+ const gchar *key =
+ (gchar *) g_object_get_data(G_OBJECT(data), G_DATAKEY_CONF_KEY);
+ if (!g_strcmp0(updatedKey, key))
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(data),
+ trg_prefs_get_bool(p, key,
+ TRG_PREFS_GLOBAL));
+}
+
+static void
+trg_menu_bar_view_radio_item_update(TrgPrefs * p, const gchar * updatedKey,
+ gpointer data)
+{
+ const gchar *key =
+ (gchar *) g_object_get_data(G_OBJECT(data), G_DATAKEY_CONF_KEY);
+ gint myIndex =
+ GPOINTER_TO_INT(g_object_get_data
+ (G_OBJECT(data), G_DATAKEY_PREF_VALUE));
+
+ if (!g_strcmp0(updatedKey, key)) {
+ gboolean shouldBeActive =
+ trg_prefs_get_int(p, key, TRG_PREFS_GLOBAL) == myIndex;
+ if (shouldBeActive !=
+ gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(data)))
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(data),
+ shouldBeActive);
+ }
+}
+
+static GtkWidget *trg_menu_bar_view_radio_item_new(TrgPrefs * prefs,
+ GSList * group,
+ const gchar * key,
+ gint index,
+ const gchar * label)
+{
+ GtkWidget *w = gtk_radio_menu_item_new_with_label(group, label);
+ g_object_set_data_full(G_OBJECT(w), G_DATAKEY_CONF_KEY, g_strdup(key),
+ g_free);
+ g_object_set_data(G_OBJECT(w), G_DATAKEY_PREF_VALUE,
+ GINT_TO_POINTER(index));
+
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w),
+ trg_prefs_get_int(prefs, key,
+ TRG_PREFS_GLOBAL) ==
+ (gint64) index);
+
+ g_signal_connect(w, "toggled",
+ G_CALLBACK(view_menu_radio_item_toggled_cb), prefs);
+ g_signal_connect(prefs, "pref-changed",
+ G_CALLBACK(trg_menu_bar_view_radio_item_update), w);
+
+ return w;
+}
+
+static GtkWidget *trg_menu_bar_view_item_new(TrgPrefs * prefs,
+ const gchar * key,
+ const gchar * label,
+ GtkWidget * dependency)
+{
+ GtkWidget *w = gtk_check_menu_item_new_with_label(label);
+ g_object_set_data_full(G_OBJECT(w), G_DATAKEY_CONF_KEY, g_strdup(key),
+ g_free);
+
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(w),
+ trg_prefs_get_bool(prefs, key,
+ TRG_PREFS_GLOBAL));
+
+ if (dependency) {
+ gtk_widget_set_sensitive(w,
+ gtk_check_menu_item_get_active
+ (GTK_CHECK_MENU_ITEM(dependency)));
+ g_signal_connect(dependency, "toggled",
+ G_CALLBACK(view_menu_bar_toggled_dependency_cb),
+ w);
+ }
+
+ g_signal_connect(w, "toggled",
+ G_CALLBACK(view_menu_item_toggled_cb), prefs);
+ g_signal_connect(prefs, "pref-changed",
+ G_CALLBACK(trg_menu_bar_view_item_update), w);
+
+ return w;
+}
+
+static GtkWidget *trg_menu_bar_view_menu_new(TrgMenuBar * mb)
+{
+ TrgMenuBarPrivate *priv = TRG_MENU_BAR_GET_PRIVATE(mb);
+
+ GtkWidget *view = gtk_menu_item_new_with_mnemonic(_("_View"));
+ GtkWidget *viewMenu = gtk_menu_new();
+ GSList *group;
+
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(view), viewMenu);
+
+ priv->mb_view_transmission =
+ trg_menu_bar_view_radio_item_new(priv->prefs, NULL,
+ TRG_PREFS_KEY_STYLE, TRG_STYLE_TR,
+ _("Transmission Style"));
+ gtk_menu_shell_append(GTK_MENU_SHELL(viewMenu),
+ priv->mb_view_transmission);
+ group =
+ gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM
+ (priv->mb_view_transmission));
+ priv->mb_view_transmission_compact =
+ trg_menu_bar_view_radio_item_new(priv->prefs, group,
+ TRG_PREFS_KEY_STYLE,
+ TRG_STYLE_TR_COMPACT,
+ _("Transmission Compact Style"));
+ gtk_menu_shell_append(GTK_MENU_SHELL(viewMenu),
+ priv->mb_view_transmission_compact);
+ group =
+ gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM
+ (priv->mb_view_transmission_compact));
+ priv->mb_view_classic =
+ trg_menu_bar_view_radio_item_new(priv->prefs, group,
+ TRG_PREFS_KEY_STYLE,
+ TRG_STYLE_CLASSIC,
+ _("Classic Style"));
+ gtk_menu_shell_append(GTK_MENU_SHELL(viewMenu), priv->mb_view_classic);
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(viewMenu),
+ trg_tree_view_sort_menu(TRG_TREE_VIEW
+ (priv->torrent_tree_view),
+ _("Sort")));
+
+ priv->mb_view_states =
+ trg_menu_bar_view_item_new(priv->prefs,
+ TRG_PREFS_KEY_SHOW_STATE_SELECTOR,
+ _("State selector"), NULL);
+ trg_menu_bar_accel_add(mb, priv->mb_view_states, GDK_F2, 0);
+ gtk_menu_shell_append(GTK_MENU_SHELL(viewMenu), priv->mb_view_states);
+
+ priv->mb_directory_filters =
+ trg_menu_bar_view_item_new(priv->prefs, TRG_PREFS_KEY_FILTER_DIRS,
+ _("Directory filters"),
+ priv->mb_view_states);
+ trg_menu_bar_accel_add(mb, priv->mb_directory_filters, GDK_F3, 0);
+ gtk_menu_shell_append(GTK_MENU_SHELL(viewMenu),
+ priv->mb_directory_filters);
+
+ priv->mb_tracker_filters =
+ trg_menu_bar_view_item_new(priv->prefs,
+ TRG_PREFS_KEY_FILTER_TRACKERS,
+ _("Tracker filters"),
+ priv->mb_view_states);
+ trg_menu_bar_accel_add(mb, priv->mb_tracker_filters, GDK_F4, 0);
+ gtk_menu_shell_append(GTK_MENU_SHELL(viewMenu),
+ priv->mb_tracker_filters);
+
+ priv->mb_view_notebook =
+ trg_menu_bar_view_item_new(priv->prefs,
+ TRG_PREFS_KEY_SHOW_NOTEBOOK,
+ _("Torrent Details"), NULL);
+ trg_menu_bar_accel_add(mb, priv->mb_view_notebook, GDK_F5, 0);
+ gtk_menu_shell_append(GTK_MENU_SHELL(viewMenu),
+ priv->mb_view_notebook);
+
+#if TRG_WITH_GRAPH
+ priv->mb_view_graph =
+ trg_menu_bar_view_item_new(priv->prefs, TRG_PREFS_KEY_SHOW_GRAPH,
+ _("Graph"), priv->mb_view_notebook);
+ trg_menu_bar_accel_add(mb, priv->mb_view_graph, GDK_F6, 0);
+ gtk_menu_shell_append(GTK_MENU_SHELL(viewMenu), priv->mb_view_graph);
+#endif
+
+ priv->mb_view_stats =
+ gtk_menu_item_new_with_mnemonic(_("_Statistics"));
+ trg_menu_bar_accel_add(mb, priv->mb_view_stats, GDK_F7, 0);
+ gtk_widget_set_sensitive(priv->mb_view_stats, FALSE);
+ gtk_menu_shell_append(GTK_MENU_SHELL(viewMenu), priv->mb_view_stats);
+
+ return view;
+}
+
+static GtkWidget *trg_menu_bar_options_menu_new(TrgMenuBar * menu)
+{
+ TrgMenuBarPrivate *priv = TRG_MENU_BAR_GET_PRIVATE(menu);
+
+ GtkWidget *opts = gtk_menu_item_new_with_mnemonic(_("_Options"));
+ GtkWidget *optsMenu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(opts), optsMenu);
+
+ priv->mb_local_prefs =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(optsMenu),
+ _("_Local Preferences"),
+ GTK_STOCK_PREFERENCES, TRUE);
+ trg_menu_bar_accel_add(menu, priv->mb_local_prefs, GDK_s,
+ GDK_CONTROL_MASK);
+
+ priv->mb_remote_prefs =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(optsMenu),
+ _("_Remote Preferences"),
+ GTK_STOCK_NETWORK, FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_remote_prefs, GDK_s,
+ GDK_MOD1_MASK);
+
+ return opts;
+}
+
+static void
+trg_menu_bar_file_connect_item_new(TrgMainWindow * win,
+ GtkMenuShell * shell,
+ const gchar * text,
+ gboolean checked, JsonObject * profile)
+{
+ GtkWidget *item = gtk_check_menu_item_new_with_label(text);
+
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), checked);
+ g_object_set_data(G_OBJECT(item), "profile", profile);
+ gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), TRUE);
+
+ g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(connect_cb),
+ win);
+
+ gtk_menu_shell_append(shell, item);
+}
+
+GtkWidget *trg_menu_bar_file_connect_menu_new(TrgMainWindow * win,
+ TrgPrefs * p)
+{
+ GtkWidget *menu = gtk_menu_new();
+ GList *profiles = json_array_get_elements(trg_prefs_get_profiles(p));
+ JsonObject *currentProfile = trg_prefs_get_profile(p);
+ GList *li;
+
+ for (li = profiles; li; li = g_list_next(li)) {
+ JsonObject *profile = json_node_get_object((JsonNode *) li->data);
+ const gchar *name_value;
+
+ if (json_object_has_member(profile, TRG_PREFS_KEY_PROFILE_NAME)) {
+ name_value = json_object_get_string_member(profile,
+ TRG_PREFS_KEY_PROFILE_NAME);
+ } else {
+ name_value = _(TRG_PROFILE_NAME_DEFAULT);
+ }
+
+ trg_menu_bar_file_connect_item_new(win, GTK_MENU_SHELL(menu),
+ name_value,
+ profile == currentProfile,
+ profile);
+ }
+
+ g_list_free(profiles);
+
+ return menu;
+}
+
+static GtkWidget *trg_menu_bar_file_file_menu_new(TrgMenuBar * menu)
+{
+ TrgMenuBarPrivate *priv = TRG_MENU_BAR_GET_PRIVATE(menu);
+
+ GtkWidget *file = gtk_menu_item_new_with_mnemonic(_("_File"));
+ GtkWidget *fileMenu = gtk_menu_new();
+
+ GtkWidget *connectMenu =
+ trg_menu_bar_file_connect_menu_new(priv->main_window, priv->prefs);
+
+ priv->mb_connect =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(fileMenu), _("Connect"),
+ GTK_STOCK_CONNECT, TRUE);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(priv->mb_connect),
+ connectMenu);
+
+ priv->mb_disconnect =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(fileMenu), _("_Disconnect"),
+ GTK_STOCK_DISCONNECT, FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_disconnect, GDK_d,
+ GDK_CONTROL_MASK);
+
+ priv->mb_add =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(fileMenu), _("_Add"),
+ GTK_STOCK_ADD, FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_add, GDK_o, GDK_CONTROL_MASK);
+
+ priv->mb_add_url =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(fileMenu), _("Add from _URL"),
+ GTK_STOCK_ADD, FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_add_url, GDK_u,
+ GDK_CONTROL_MASK);
+
+ priv->mb_quit =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(fileMenu), _("_Quit"),
+ GTK_STOCK_QUIT, TRUE);
+
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(file), fileMenu);
+
+ return file;
+}
+
+static GtkWidget *trg_menu_bar_torrent_menu_new(TrgMenuBar * menu)
+{
+ TrgMenuBarPrivate *priv = TRG_MENU_BAR_GET_PRIVATE(menu);
+ GtkWidget *torrent = gtk_menu_item_new_with_mnemonic(_("_Torrent"));
+ GtkWidget *torrentMenu = gtk_menu_new();
+
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(torrent), torrentMenu);
+
+ priv->mb_props =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu),
+ _("Properties"), GTK_STOCK_PROPERTIES,
+ FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_props, GDK_i, GDK_CONTROL_MASK);
+
+ priv->mb_resume =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu), _("_Resume"),
+ GTK_STOCK_MEDIA_PLAY, FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_resume, GDK_r, GDK_CONTROL_MASK);
+
+ priv->mb_pause =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu), _("_Pause"),
+ GTK_STOCK_MEDIA_PAUSE, FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_pause, GDK_p, GDK_CONTROL_MASK);
+
+ priv->mb_verify =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu), _("_Verify"),
+ GTK_STOCK_REFRESH, FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_verify, GDK_h, GDK_CONTROL_MASK);
+
+ priv->mb_reannounce =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu),
+ _("Re-_announce"), GTK_STOCK_REFRESH, FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_reannounce, GDK_q,
+ GDK_CONTROL_MASK);
+
+ priv->mb_move =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu), _("_Move"),
+ GTK_STOCK_HARDDISK, FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_move, GDK_m, GDK_CONTROL_MASK);
+
+ priv->mb_remove =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu), _("Remove"),
+ GTK_STOCK_REMOVE, FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_remove, GDK_Delete, 0);
+
+ priv->mb_delete =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu),
+ _("Remove and Delete"), GTK_STOCK_CLEAR,
+ FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_delete, GDK_Delete,
+ GDK_SHIFT_MASK);
+
+ priv->mb_queues_seperator = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(torrentMenu),
+ priv->mb_queues_seperator);
+
+ priv->mb_start_now = trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu),
+ _("Start Now"),
+ GTK_STOCK_MEDIA_PLAY,
+ FALSE);
+
+ priv->mb_up_queue = trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu),
+ _("Move Up Queue"),
+ GTK_STOCK_GO_UP, FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_up_queue, GDK_Up,
+ GDK_SHIFT_MASK);
+
+ priv->mb_down_queue =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu),
+ _("Move Down Queue"), GTK_STOCK_GO_DOWN,
+ FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_down_queue, GDK_Down,
+ GDK_SHIFT_MASK);
+
+ priv->mb_bottom_queue =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu),
+ _("Bottom Of Queue"), GTK_STOCK_GOTO_BOTTOM,
+ FALSE);
+
+ priv->mb_top_queue = trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu),
+ _("Top Of Queue"),
+ GTK_STOCK_GOTO_TOP, FALSE);
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(torrentMenu),
+ gtk_separator_menu_item_new());
+
+ priv->mb_resume_all =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu),
+ _("_Resume All"), GTK_STOCK_MEDIA_PLAY,
+ FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_resume_all, GDK_r,
+ GDK_SHIFT_MASK | GDK_CONTROL_MASK);
+
+ priv->mb_pause_all =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(torrentMenu), _("_Pause All"),
+ GTK_STOCK_MEDIA_PAUSE, FALSE);
+ trg_menu_bar_accel_add(menu, priv->mb_pause_all, GDK_p,
+ GDK_SHIFT_MASK | GDK_CONTROL_MASK);
+
+ return torrent;
+}
+
+static GtkWidget *trg_menu_bar_help_menu_new(TrgMenuBar * menuBar)
+{
+ TrgMenuBarPrivate *priv = TRG_MENU_BAR_GET_PRIVATE(menuBar);
+
+ GtkWidget *help = gtk_menu_item_new_with_mnemonic(_("_Help"));
+ GtkWidget *helpMenu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(help), helpMenu);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menuBar), help);
+
+ priv->mb_about =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(helpMenu), _("_About"),
+ GTK_STOCK_ABOUT, TRUE);
+
+ return helpMenu;
+}
+
+static void menu_bar_refresh_menu(GtkWidget * w, gpointer data)
+{
+ TrgMenuBarPrivate *priv = TRG_MENU_BAR_GET_PRIVATE(data);
+ GtkWidget *old =
+ gtk_menu_item_get_submenu(GTK_MENU_ITEM(priv->mb_connect));
+ GtkWidget *new =
+ trg_menu_bar_file_connect_menu_new(priv->main_window, priv->prefs);
+
+ gtk_widget_destroy(old);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(priv->mb_connect), new);
+ gtk_widget_show_all(new);
+}
+
+static GObject *trg_menu_bar_constructor(GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *
+ construct_params)
+{
+ GObject *object;
+ TrgMenuBarPrivate *priv;
+ TrgMenuBar *menu;
+
+ object = G_OBJECT_CLASS
+ (trg_menu_bar_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+ menu = TRG_MENU_BAR(object);
+ priv = TRG_MENU_BAR_GET_PRIVATE(object);
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(object),
+ trg_menu_bar_file_file_menu_new(menu));
+ gtk_menu_shell_append(GTK_MENU_SHELL(object),
+ trg_menu_bar_torrent_menu_new(menu));
+ gtk_menu_shell_append(GTK_MENU_SHELL(object),
+ trg_menu_bar_options_menu_new(menu));
+ gtk_menu_shell_append(GTK_MENU_SHELL(object),
+ trg_menu_bar_view_menu_new(menu));
+ trg_menu_bar_help_menu_new(TRG_MENU_BAR(object));
+
+ g_signal_connect(G_OBJECT(priv->prefs), "pref-profile-changed",
+ G_CALLBACK(menu_bar_refresh_menu), object);
+
+ return object;
+}
+
+static void trg_menu_bar_class_init(TrgMenuBarClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ object_class->get_property = trg_menu_bar_get_property;
+ object_class->set_property = trg_menu_bar_set_property;
+ object_class->constructor = trg_menu_bar_constructor;
+
+ g_type_class_add_private(klass, sizeof(TrgMenuBarPrivate));
+
+ trg_menu_bar_install_widget_prop(object_class, PROP_CONNECT_BUTTON,
+ "connect-button", "Connect Button");
+ trg_menu_bar_install_widget_prop(object_class,
+ PROP_DISCONNECT_BUTTON,
+ "disconnect-button",
+ "Disconnect Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_ADD_BUTTON,
+ "add-button", "Add Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_ADD_URL_BUTTON,
+ "add-url-button", "Add URL Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_REMOVE_BUTTON,
+ "remove-button", "Remove Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_MOVE_BUTTON,
+ "move-button", "Move Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_DELETE_BUTTON,
+ "delete-button", "Delete Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_RESUME_BUTTON,
+ "resume-button", "Resume Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_RESUME_ALL_BUTTON,
+ "resume-all-button",
+ "Resume All Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_VERIFY_BUTTON,
+ "verify-button", "Verify Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_REANNOUNCE_BUTTON,
+ "reannounce-button",
+ "Re-announce Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_PAUSE_ALL_BUTTON,
+ "pause-all-button",
+ "Pause All Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_PAUSE_BUTTON,
+ "pause-button", "Pause Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_PROPS_BUTTON,
+ "props-button", "Props Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_ABOUT_BUTTON,
+ "about-button", "About Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_VIEW_STATS_BUTTON,
+ "view-stats-button",
+ "View stats button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_VIEW_STATES_BUTTON,
+ "view-states-button",
+ "View states Button");
+ trg_menu_bar_install_widget_prop(object_class,
+ PROP_VIEW_NOTEBOOK_BUTTON,
+ "view-notebook-button",
+ "View notebook Button");
+ trg_menu_bar_install_widget_prop(object_class,
+ PROP_REMOTE_PREFS_BUTTON,
+ "remote-prefs-button",
+ "Remote Prefs Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_LOCAL_PREFS_BUTTON,
+ "local-prefs-button",
+ "Local Prefs Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_QUIT,
+ "quit-button", "Quit Button");
+ trg_menu_bar_install_widget_prop(object_class, PROP_DIR_FILTERS,
+ "dir-filters", "Dir Filters");
+ trg_menu_bar_install_widget_prop(object_class, PROP_TRACKER_FILTERS,
+ "tracker-filters", "Tracker Filters");
+#if TRG_WITH_GRAPH
+ trg_menu_bar_install_widget_prop(object_class, PROP_VIEW_SHOW_GRAPH,
+ "show-graph", "Show Graph");
+#endif
+ trg_menu_bar_install_widget_prop(object_class, PROP_MOVE_DOWN_QUEUE,
+ "down-queue", "Down Queue");
+ trg_menu_bar_install_widget_prop(object_class, PROP_MOVE_UP_QUEUE,
+ "up-queue", "Up Queue");
+ trg_menu_bar_install_widget_prop(object_class, PROP_MOVE_BOTTOM_QUEUE,
+ "bottom-queue", "Bottom Queue");
+ trg_menu_bar_install_widget_prop(object_class, PROP_MOVE_TOP_QUEUE,
+ "top-queue", "Top Queue");
+ trg_menu_bar_install_widget_prop(object_class, PROP_START_NOW,
+ "start-now", "Start Now");
+
+ g_object_class_install_property(object_class,
+ PROP_PREFS,
+ g_param_spec_object("prefs",
+ "prefs",
+ "Prefs",
+ TRG_TYPE_PREFS,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_ACCEL_GROUP,
+ g_param_spec_object("accel-group",
+ "accel-group",
+ "accel-group",
+ GTK_TYPE_ACCEL_GROUP,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_MAIN_WINDOW,
+ g_param_spec_object("mainwin",
+ "mainwin",
+ "mainwin",
+ TRG_TYPE_MAIN_WINDOW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_TORRENT_TREE_VIEW,
+ g_param_spec_object
+ ("torrent-tree-view",
+ "torrent-tree-view",
+ "torrent-tree-view",
+ TRG_TYPE_TORRENT_TREE_VIEW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+}
+
+static void trg_menu_bar_init(TrgMenuBar * self)
+{
+}
+
+TrgMenuBar *trg_menu_bar_new(TrgMainWindow * win, TrgPrefs * prefs,
+ TrgTorrentTreeView * ttv,
+ GtkAccelGroup * accel_group)
+{
+ return g_object_new(TRG_TYPE_MENU_BAR,
+ "torrent-tree-view", ttv, "prefs", prefs,
+ "mainwin", win, "accel-group", accel_group, NULL);
+}
diff --git a/src/trg-menu-bar.h b/src/trg-menu-bar.h
new file mode 100644
index 0000000..601ea13
--- /dev/null
+++ b/src/trg-menu-bar.h
@@ -0,0 +1,68 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_MENU_BAR_H_
+#define TRG_MENU_BAR_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "trg-prefs.h"
+#include "trg-torrent-tree-view.h"
+#include "trg-main-window.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_MENU_BAR trg_menu_bar_get_type()
+#define TRG_MENU_BAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_MENU_BAR, TrgMenuBar))
+#define TRG_MENU_BAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_MENU_BAR, TrgMenuBarClass))
+#define TRG_IS_MENU_BAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_MENU_BAR))
+#define TRG_IS_MENU_BAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_MENU_BAR))
+#define TRG_MENU_BAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_MENU_BAR, TrgMenuBarClass))
+ typedef struct {
+ GtkMenuBar parent;
+} TrgMenuBar;
+
+typedef struct {
+ GtkMenuBarClass parent_class;
+} TrgMenuBarClass;
+
+GType trg_menu_bar_get_type(void);
+
+TrgMenuBar *trg_menu_bar_new(TrgMainWindow * win, TrgPrefs * prefs,
+ TrgTorrentTreeView * ttv,
+ GtkAccelGroup * accel_group);
+GtkWidget *trg_menu_bar_item_new(GtkMenuShell * shell, const gchar * text,
+ const gchar * stock_id,
+ gboolean sensitive);
+
+G_END_DECLS
+ void trg_menu_bar_torrent_actions_sensitive(TrgMenuBar * mb,
+ gboolean sensitive);
+void trg_menu_bar_connected_change(TrgMenuBar * mb, gboolean connected);
+void trg_menu_bar_set_supports_queues(TrgMenuBar * mb,
+ gboolean supportsQueues);
+GtkWidget *trg_menu_bar_file_connect_menu_new(TrgMainWindow * win,
+ TrgPrefs * p);
+
+#endif /* TRG_MENU_BAR_H_ */
diff --git a/src/trg-model.c b/src/trg-model.c
new file mode 100644
index 0000000..fa67d08
--- /dev/null
+++ b/src/trg-model.c
@@ -0,0 +1,114 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+/* An extension of GtkListStore which provides some functions for looking up
+ * an entry by ID. Also for removing entries which have an old update serial,
+ * which means it needs removing.
+ */
+
+struct trg_model_remove_removed_foreachfunc_args {
+ gint64 currentSerial;
+ gint serial_column;
+ GList *toRemove;
+};
+
+gboolean
+trg_model_remove_removed_foreachfunc(GtkTreeModel * model,
+ GtkTreePath * path G_GNUC_UNUSED,
+ GtkTreeIter * iter, gpointer data)
+{
+ struct trg_model_remove_removed_foreachfunc_args *args =
+ (struct trg_model_remove_removed_foreachfunc_args *) data;
+ gint64 rowSerial;
+ gtk_tree_model_get(model, iter, args->serial_column, &rowSerial, -1);
+ if (rowSerial != args->currentSerial)
+ args->toRemove =
+ g_list_append(args->toRemove, gtk_tree_iter_copy(iter));
+
+ return FALSE;
+}
+
+guint
+trg_model_remove_removed(GtkListStore * model, gint serial_column,
+ gint64 currentSerial)
+{
+ struct trg_model_remove_removed_foreachfunc_args args;
+ GList *li;
+ guint removed = 0;
+
+ args.toRemove = NULL;
+ args.currentSerial = currentSerial;
+ args.serial_column = serial_column;
+ gtk_tree_model_foreach(GTK_TREE_MODEL(model),
+ trg_model_remove_removed_foreachfunc, &args);
+ if (args.toRemove != NULL) {
+ for (li = g_list_last(args.toRemove); li != NULL;
+ li = g_list_previous(li)) {
+ gtk_list_store_remove(model, (GtkTreeIter *) li->data);
+ gtk_tree_iter_free((GtkTreeIter *) li->data);
+ removed++;
+ }
+ g_list_free(args.toRemove);
+ }
+
+ return removed;
+}
+
+struct find_existing_item_foreach_args {
+ gint64 id;
+ gint search_column;
+ GtkTreeIter *iter;
+ gboolean found;
+};
+
+static gboolean
+find_existing_item_foreachfunc(GtkTreeModel * model,
+ GtkTreePath * path G_GNUC_UNUSED,
+ GtkTreeIter * iter, gpointer data)
+{
+ struct find_existing_item_foreach_args *args =
+ (struct find_existing_item_foreach_args *) data;
+ gint64 currentId;
+
+ gtk_tree_model_get(model, iter, args->search_column, &currentId, -1);
+ if (currentId == args->id) {
+ args->iter = iter;
+ return args->found = TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+find_existing_model_item(GtkTreeModel * model, gint search_column,
+ gint64 id, GtkTreeIter * iter)
+{
+ struct find_existing_item_foreach_args args;
+ args.id = id;
+ args.found = FALSE;
+ args.search_column = search_column;
+ gtk_tree_model_foreach(model, find_existing_item_foreachfunc, &args);
+ if (args.found == TRUE)
+ *iter = *(args.iter);
+ return args.found;
+}
diff --git a/src/trg-model.h b/src/trg-model.h
new file mode 100644
index 0000000..4f5f8ae
--- /dev/null
+++ b/src/trg-model.h
@@ -0,0 +1,32 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_MODEL_H_
+#define TRG_MODEL_H_
+
+#include <gtk/gtk.h>
+
+guint trg_model_remove_removed(GtkListStore * model, gint serial_column,
+ gint64 currentSerial);
+
+gboolean
+find_existing_model_item(GtkTreeModel * model, gint search_column,
+ gint64 id, GtkTreeIter * iter);
+
+#endif /* TRG_MODEL_H_ */
diff --git a/src/trg-peers-model.c b/src/trg-peers-model.c
new file mode 100644
index 0000000..0d15753
--- /dev/null
+++ b/src/trg-peers-model.c
@@ -0,0 +1,292 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+#include <gio/gio.h>
+#include <glib/gstdio.h>
+#ifdef HAVE_GEOIP
+#include <GeoIP.h>
+#endif
+
+#include "trg-tree-view.h"
+#include "torrent.h"
+#include "trg-client.h"
+#include "trg-peers-model.h"
+#include "trg-model.h"
+#include "util.h"
+
+G_DEFINE_TYPE(TrgPeersModel, trg_peers_model, GTK_TYPE_LIST_STORE)
+#define TRG_PEERS_MODEL_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_PEERS_MODEL, TrgPeersModelPrivate))
+#ifdef HAVE_GEOIP
+typedef struct _TrgPeersModelPrivate TrgPeersModelPrivate;
+
+struct _TrgPeersModelPrivate {
+ GeoIP *geoip;
+ GeoIP *geoipv6;
+};
+#endif
+
+static void trg_peers_model_class_init(TrgPeersModelClass *
+ klass G_GNUC_UNUSED)
+{
+#ifdef HAVE_GEOIP
+ g_type_class_add_private(klass, sizeof(TrgPeersModelPrivate));
+#endif
+}
+
+gboolean
+find_existing_peer_item_foreachfunc(GtkTreeModel * model,
+ GtkTreePath *
+ path G_GNUC_UNUSED,
+ GtkTreeIter * iter, gpointer data)
+{
+ struct peerAndIter *pi = (struct peerAndIter *) data;
+ gchar *ip;
+
+ gtk_tree_model_get(model, iter, PEERSCOL_IP, &ip, -1);
+
+ if (g_strcmp0(ip, pi->ip) == 0) {
+ pi->iter = *iter;
+ pi->found = TRUE;
+ }
+
+ g_free(ip);
+
+ return pi->found;
+}
+
+gboolean
+find_existing_peer_item(TrgPeersModel * model, JsonObject * p,
+ GtkTreeIter * iter)
+{
+ struct peerAndIter pi;
+ pi.ip = peer_get_address(p);
+ pi.found = FALSE;
+
+ gtk_tree_model_foreach(GTK_TREE_MODEL(model),
+ find_existing_peer_item_foreachfunc, &pi);
+
+ if (pi.found == TRUE)
+ *iter = pi.iter;
+
+ return pi.found;
+}
+
+struct ResolvedDnsIdleData {
+ GtkTreeRowReference *rowRef;
+ gchar *rdns;
+};
+
+static gboolean resolved_dns_idle_cb(gpointer data)
+{
+ struct ResolvedDnsIdleData *idleData = data;
+ GtkTreeModel *model =
+ gtk_tree_row_reference_get_model(idleData->rowRef);
+ GtkTreePath *path = gtk_tree_row_reference_get_path(idleData->rowRef);
+
+ if (path != NULL) {
+ GtkTreeIter iter;
+ if (gtk_tree_model_get_iter(model, &iter, path) == TRUE) {
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, PEERSCOL_HOST,
+ idleData->rdns, -1);
+ }
+ gtk_tree_path_free(path);
+ }
+
+ gtk_tree_row_reference_free(idleData->rowRef);
+ g_free(idleData->rdns);
+ g_free(idleData);
+
+ return FALSE;
+}
+
+static void resolved_dns_cb(GObject * source_object, GAsyncResult * res,
+ gpointer data)
+{
+
+ gchar *rdns =
+ g_resolver_lookup_by_address_finish(G_RESOLVER(source_object),
+ res, NULL);
+ GtkTreeRowReference *rowRef = data;
+
+ if (rdns != NULL) {
+ struct ResolvedDnsIdleData *idleData =
+ g_new(struct ResolvedDnsIdleData, 1);
+ idleData->rdns = rdns;
+ idleData->rowRef = rowRef;
+ g_idle_add(resolved_dns_idle_cb, idleData);
+ } else {
+ gtk_tree_row_reference_free(rowRef);
+ }
+}
+
+void
+trg_peers_model_update(TrgPeersModel * model, TrgTreeView * tv,
+ gint64 updateSerial, JsonObject * t, gint mode)
+{
+#ifdef HAVE_GEOIP
+ TrgPeersModelPrivate *priv = TRG_PEERS_MODEL_GET_PRIVATE(model);
+ gboolean doGeoLookup =
+ trg_tree_view_is_column_showing(tv, PEERSCOL_COUNTRY);
+#endif
+
+ gboolean doHostLookup =
+ trg_tree_view_is_column_showing(tv, PEERSCOL_HOST);
+ JsonArray *peers;
+ GtkTreeIter peerIter;
+ GList *li, *peersList;
+ gboolean isNew;
+
+ peers = torrent_get_peers(t);
+
+ if (mode == TORRENT_GET_MODE_FIRST)
+ gtk_list_store_clear(GTK_LIST_STORE(model));
+
+ peersList = json_array_get_elements(peers);
+ for (li = peersList; li; li = g_list_next(li)) {
+ JsonObject *peer = json_node_get_object((JsonNode *) li->data);
+ const gchar *address = NULL, *flagStr;
+#ifdef HAVE_GEOIP
+ const gchar *country = NULL;
+#endif
+
+ if (mode == TORRENT_GET_MODE_FIRST
+ || find_existing_peer_item(model, peer, &peerIter) == FALSE) {
+ gtk_list_store_append(GTK_LIST_STORE(model), &peerIter);
+
+ address = peer_get_address(peer);
+#ifdef HAVE_GEOIP
+ if (address && doGeoLookup) { /* just in case address wasn't set */
+ if (strchr(address, ':') && priv->geoipv6)
+ country =
+ GeoIP_country_name_by_addr_v6(priv->geoipv6,
+ address);
+ else if (priv->geoip)
+ country =
+ GeoIP_country_name_by_addr(priv->geoip, address);
+ }
+#endif
+ gtk_list_store_set(GTK_LIST_STORE(model), &peerIter,
+ PEERSCOL_ICON, GTK_STOCK_NETWORK,
+ PEERSCOL_IP, address,
+#ifdef HAVE_GEOIP
+ PEERSCOL_COUNTRY, country ? country : "",
+#endif
+ PEERSCOL_CLIENT, peer_get_client_name(peer),
+ -1);
+
+ isNew = TRUE;
+ } else {
+ isNew = FALSE;
+ }
+
+ flagStr = peer_get_flagstr(peer);
+ gtk_list_store_set(GTK_LIST_STORE(model), &peerIter,
+ PEERSCOL_FLAGS, flagStr, PEERSCOL_PROGRESS,
+ peer_get_progress(peer), PEERSCOL_DOWNSPEED,
+ peer_get_rate_to_client(peer), PEERSCOL_UPSPEED,
+ peer_get_rate_to_peer(peer),
+ PEERSCOL_UPDATESERIAL, updateSerial, -1);
+
+ if (doHostLookup && isNew == TRUE) {
+ GtkTreePath *path =
+ gtk_tree_model_get_path(GTK_TREE_MODEL(model),
+ &peerIter);
+ GtkTreeRowReference *treeRef =
+ gtk_tree_row_reference_new(GTK_TREE_MODEL(model), path);
+ GInetAddress *inetAddr;
+ GResolver *resolver;
+
+ gtk_tree_path_free(path);
+
+ inetAddr = g_inet_address_new_from_string(address);
+ resolver = g_resolver_get_default();
+ g_resolver_lookup_by_address_async(resolver, inetAddr, NULL,
+ resolved_dns_cb, treeRef);
+ g_object_unref(resolver);
+ g_object_unref(inetAddr);
+ }
+ }
+
+ g_list_free(peersList);
+
+ if (mode != TORRENT_GET_MODE_FIRST)
+ trg_model_remove_removed(GTK_LIST_STORE(model),
+ PEERSCOL_UPDATESERIAL, updateSerial);
+}
+
+static void trg_peers_model_init(TrgPeersModel * self)
+{
+#ifdef HAVE_GEOIP
+ TrgPeersModelPrivate *priv = TRG_PEERS_MODEL_GET_PRIVATE(self);
+ gchar *geoip_db_path = NULL;
+ gchar *geoip_v6_db_path = NULL;
+#endif
+
+ GType column_types[PEERSCOL_COLUMNS];
+
+ column_types[PEERSCOL_ICON] = G_TYPE_STRING;
+ column_types[PEERSCOL_IP] = G_TYPE_STRING;
+#ifdef HAVE_GEOIP
+ column_types[PEERSCOL_COUNTRY] = G_TYPE_STRING;
+#endif
+ column_types[PEERSCOL_HOST] = G_TYPE_STRING;
+ column_types[PEERSCOL_FLAGS] = G_TYPE_STRING;
+ column_types[PEERSCOL_PROGRESS] = G_TYPE_DOUBLE;
+ column_types[PEERSCOL_DOWNSPEED] = G_TYPE_INT64;
+ column_types[PEERSCOL_UPSPEED] = G_TYPE_INT64;
+ column_types[PEERSCOL_CLIENT] = G_TYPE_STRING;
+ column_types[PEERSCOL_UPDATESERIAL] = G_TYPE_INT64;
+
+ gtk_list_store_set_column_types(GTK_LIST_STORE(self), PEERSCOL_COLUMNS,
+ column_types);
+
+#ifdef HAVE_GEOIP
+#ifdef WIN32
+ geoip_db_path = trg_win32_support_path("GeoIP.dat");
+ geoip_v6_db_path = trg_win32_support_path("GeoIPv6.dat");
+#else
+ geoip_db_path = g_strdup(TRG_GEOIP_DATABASE);
+ geoip_v6_db_path = g_strdup(TRG_GEOIPV6_DATABASE);
+#endif
+
+ if (g_file_test(geoip_db_path, G_FILE_TEST_EXISTS) == TRUE)
+ priv->geoip = GeoIP_open(geoip_db_path,
+ GEOIP_STANDARD | GEOIP_CHECK_CACHE);
+
+ if (g_file_test(geoip_v6_db_path, G_FILE_TEST_EXISTS) == TRUE)
+ priv->geoipv6 = GeoIP_open(geoip_v6_db_path,
+ GEOIP_STANDARD | GEOIP_CHECK_CACHE);
+
+ g_free(geoip_db_path);
+ g_free(geoip_v6_db_path);
+#endif
+}
+
+TrgPeersModel *trg_peers_model_new()
+{
+ return g_object_new(TRG_TYPE_PEERS_MODEL, NULL);
+}
diff --git a/src/trg-peers-model.h b/src/trg-peers-model.h
new file mode 100644
index 0000000..6ae941a
--- /dev/null
+++ b/src/trg-peers-model.h
@@ -0,0 +1,90 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#ifndef TRG_PEERS_MODEL_H_
+#define TRG_PEERS_MODEL_H_
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+#ifdef HAVE_GEOIP
+#include <GeoIP.h>
+#endif
+#include <glib-object.h>
+
+#include "trg-tree-view.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_PEERS_MODEL trg_peers_model_get_type()
+#define TRG_PEERS_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_PEERS_MODEL, TrgPeersModel))
+#define TRG_PEERS_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_PEERS_MODEL, TrgPeersModelClass))
+#define TRG_IS_PEERS_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_PEERS_MODEL))
+#define TRG_IS_PEERS_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_PEERS_MODEL))
+#define TRG_PEERS_MODEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_PEERS_MODEL, TrgPeersModelClass))
+ typedef struct {
+ GtkListStore parent;
+} TrgPeersModel;
+
+typedef struct {
+ GtkListStoreClass parent_class;
+} TrgPeersModelClass;
+
+GType trg_peers_model_get_type(void);
+
+TrgPeersModel *trg_peers_model_new();
+
+G_END_DECLS struct peerAndIter {
+ const gchar *ip;
+ GtkTreeIter iter;
+ gboolean found;
+};
+
+enum {
+ PEERSCOL_ICON,
+ PEERSCOL_IP,
+#if HAVE_GEOIP
+ PEERSCOL_COUNTRY,
+#endif
+ PEERSCOL_HOST,
+ PEERSCOL_FLAGS,
+ PEERSCOL_PROGRESS,
+ PEERSCOL_DOWNSPEED,
+ PEERSCOL_UPSPEED,
+ PEERSCOL_CLIENT,
+ PEERSCOL_UPDATESERIAL,
+ PEERSCOL_COLUMNS
+};
+
+void trg_peers_model_update(TrgPeersModel * model, TrgTreeView * tv,
+ gint64 updateSerial, JsonObject * t,
+ gboolean first);
+
+#endif /* TRG_PEERS_MODEL_H_ */
+
+#define TRG_GEOIP_DATABASE "/usr/share/GeoIP/GeoIP.dat"
+#define TRG_GEOIPV6_DATABASE "/usr/share/GeoIP/GeoIPv6.dat"
diff --git a/src/trg-peers-tree-view.c b/src/trg-peers-tree-view.c
new file mode 100644
index 0000000..f7fbd7c
--- /dev/null
+++ b/src/trg-peers-tree-view.c
@@ -0,0 +1,87 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_GEOIP
+#include <GeoIP.h>
+#endif
+
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "trg-prefs.h"
+#include "trg-tree-view.h"
+#include "trg-peers-model.h"
+#include "trg-peers-tree-view.h"
+
+G_DEFINE_TYPE(TrgPeersTreeView, trg_peers_tree_view, TRG_TYPE_TREE_VIEW)
+static void
+trg_peers_tree_view_class_init(TrgPeersTreeViewClass * klass G_GNUC_UNUSED)
+{
+}
+
+static void trg_peers_tree_view_init(TrgPeersTreeView * self)
+{
+ TrgTreeView *ttv = TRG_TREE_VIEW(self);
+ trg_column_description *desc;
+
+ desc =
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_STOCKICONTEXT,
+ PEERSCOL_IP, _("IP"), "ip", 0);
+ desc->model_column_extra = PEERSCOL_ICON;
+
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_TEXT, PEERSCOL_HOST,
+ _("Host"), "host", 0);
+
+#ifdef HAVE_GEOIP
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_TEXT, PEERSCOL_COUNTRY,
+ _("Country"), "country", 0);
+#endif
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_SPEED, PEERSCOL_DOWNSPEED,
+ _("Down Speed"), "down-speed", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_SPEED, PEERSCOL_UPSPEED,
+ _("Up Speed"), "up-speed", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_PROG, PEERSCOL_PROGRESS,
+ _("Progress"), "progress", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_TEXT, PEERSCOL_FLAGS,
+ _("Flags"), "flags", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_TEXT, PEERSCOL_CLIENT,
+ _("Client"), "client", 0);
+
+ gtk_tree_view_set_search_column(GTK_TREE_VIEW(self), PEERSCOL_HOST);
+}
+
+TrgPeersTreeView *trg_peers_tree_view_new(TrgPrefs * prefs,
+ TrgPeersModel * model,
+ const gchar * configId)
+{
+ GObject *obj = g_object_new(TRG_TYPE_PEERS_TREE_VIEW,
+ "config-id", configId,
+ "prefs", prefs, NULL);
+
+ gtk_tree_view_set_model(GTK_TREE_VIEW(obj), GTK_TREE_MODEL(model));
+ trg_tree_view_restore_sort(TRG_TREE_VIEW(obj), 0x00);
+ trg_tree_view_setup_columns(TRG_TREE_VIEW(obj));
+
+ return TRG_PEERS_TREE_VIEW(obj);
+}
diff --git a/src/trg-peers-tree-view.h b/src/trg-peers-tree-view.h
new file mode 100644
index 0000000..57a09c1
--- /dev/null
+++ b/src/trg-peers-tree-view.h
@@ -0,0 +1,57 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#ifndef TRG_PEERS_TREE_VIEW_H_
+#define TRG_PEERS_TREE_VIEW_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "trg-prefs.h"
+#include "trg-peers-model.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_PEERS_TREE_VIEW trg_peers_tree_view_get_type()
+#define TRG_PEERS_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_PEERS_TREE_VIEW, TrgPeersTreeView))
+#define TRG_PEERS_TREE_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_PEERS_TREE_VIEW, TrgPeersTreeViewClass))
+#define TRG_IS_PEERS_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_PEERS_TREE_VIEW))
+#define TRG_IS_PEERS_TREE_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_PEERS_TREE_VIEW))
+#define TRG_PEERS_TREE_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_PEERS_TREE_VIEW, TrgPeersTreeViewClass))
+ typedef struct {
+ GtkTreeView parent;
+} TrgPeersTreeView;
+
+typedef struct {
+ GtkTreeViewClass parent_class;
+} TrgPeersTreeViewClass;
+
+GType trg_peers_tree_view_get_type(void);
+
+TrgPeersTreeView *trg_peers_tree_view_new(TrgPrefs * prefs,
+ TrgPeersModel * model,
+ const gchar * configId);
+
+G_END_DECLS
+#endif /* TRG_PEERS_TREE_VIEW_H_ */
diff --git a/src/trg-persistent-tree-view.c b/src/trg-persistent-tree-view.c
new file mode 100644
index 0000000..e116fdf
--- /dev/null
+++ b/src/trg-persistent-tree-view.c
@@ -0,0 +1,495 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <gtk/gtk.h>
+
+#include "trg-prefs.h"
+#include "trg-persistent-tree-view.h"
+#include "trg-preferences-dialog.h"
+#include "util.h"
+
+/* A config TreeView which can save/restore simple data into the TrgConf backend.
+ * It's not actually a GtkTreeView, it's a GtKVBox which contains the buttons
+ * to add/remove entries as well as the TreeView.
+ */
+
+G_DEFINE_TYPE(TrgPersistentTreeView, trg_persistent_tree_view,
+ GTK_TYPE_VBOX)
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_PERSISTENT_TREE_VIEW, TrgPersistentTreeViewPrivate))
+typedef struct _TrgPersistentTreeViewPrivate
+ TrgPersistentTreeViewPrivate;
+
+enum {
+ PROP_0, PROP_PREFS, PROP_KEY, PROP_MODEL
+};
+
+struct _TrgPersistentTreeViewPrivate {
+ TrgPrefs *prefs;
+ gchar *key;
+ GSList *columns;
+ GtkTreeView *tv;
+ JsonArray *ja;
+ GtkWidget *delButton;
+ GtkWidget *upButton;
+ GtkWidget *downButton;
+ trg_pref_widget_desc *wd;
+ GtkTreeModel *model;
+ trg_persistent_tree_view_column *addSelect;
+};
+
+static void selection_changed(TrgPersistentTreeView * ptv,
+ GtkTreeSelection * selection)
+{
+ TrgPersistentTreeViewPrivate *priv = GET_PRIVATE(ptv);
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
+ GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
+ gtk_widget_set_sensitive(priv->upButton, gtk_tree_path_prev(path));
+ gtk_widget_set_sensitive(priv->downButton,
+ gtk_tree_model_iter_next(model, &iter));
+ gtk_tree_path_free(path);
+ gtk_widget_set_sensitive(priv->delButton, TRUE);
+ } else {
+ gtk_widget_set_sensitive(priv->delButton, FALSE);
+ gtk_widget_set_sensitive(priv->upButton, FALSE);
+ gtk_widget_set_sensitive(priv->downButton, FALSE);
+ }
+}
+
+static void selection_changed_cb(GtkTreeSelection * selection,
+ gpointer data)
+{
+ selection_changed(TRG_PERSISTENT_TREE_VIEW(data), selection);
+}
+
+static void
+trg_persistent_tree_view_edit(GtkCellRendererText * renderer,
+ gchar * path, gchar * new_text,
+ gpointer user_data)
+{
+ trg_persistent_tree_view_column *cd =
+ (trg_persistent_tree_view_column *) user_data;
+ TrgPersistentTreeViewPrivate *priv = GET_PRIVATE(cd->tv);
+ GtkTreeModel *model = gtk_tree_view_get_model(priv->tv);
+ GtkTreeIter iter;
+
+ gtk_tree_model_get_iter_from_string(model, &iter, path);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, cd->index, new_text,
+ -1);
+}
+
+static void trg_persistent_tree_view_refresh(TrgPrefs * prefs, void *wdp)
+{
+ trg_pref_widget_desc *wd = (trg_pref_widget_desc *) wdp;
+ TrgPersistentTreeViewPrivate *priv = GET_PRIVATE(wd->widget);
+ GtkListStore *model =
+ GTK_LIST_STORE(gtk_tree_view_get_model(priv->tv));
+ GtkTreeIter iter;
+ JsonArray *ja;
+ GList *ja_list, *li;
+ GSList *sli;
+
+ ja = trg_prefs_get_array(prefs, wd->key, wd->flags);
+
+ gtk_list_store_clear(model);
+
+ if (!ja)
+ return;
+
+ ja_list = json_array_get_elements(ja);
+
+ for (li = ja_list; li; li = g_list_next(li)) {
+ JsonNode *ja_node = (JsonNode *) li->data;
+ JsonObject *jobj = json_node_get_object(ja_node);
+ gtk_list_store_append(model, &iter);
+ for (sli = priv->columns; sli; sli = g_slist_next(sli)) {
+ trg_persistent_tree_view_column *cd =
+ (trg_persistent_tree_view_column *) sli->data;
+ gtk_list_store_set(model, &iter, cd->index,
+ json_object_get_string_member(jobj,
+ cd->key), -1);
+ }
+ }
+
+ g_list_free(ja_list);
+}
+
+static gboolean
+trg_persistent_tree_view_save_foreachfunc(GtkTreeModel *
+ model,
+ GtkTreePath *
+ path,
+ GtkTreeIter * iter,
+ gpointer data)
+{
+ TrgPersistentTreeViewPrivate *priv = GET_PRIVATE(data);
+ JsonObject *new = json_object_new();
+ gchar *value;
+ GSList *li;
+
+ for (li = priv->columns; li; li = g_slist_next(li)) {
+ trg_persistent_tree_view_column *cd =
+ (trg_persistent_tree_view_column *) li->data;
+ gtk_tree_model_get(model, iter, cd->index, &value, -1);
+ json_object_set_string_member(new, cd->key, value);
+ g_free(value);
+ }
+
+ json_array_add_object_element(priv->ja, new);
+
+ return FALSE;
+}
+
+static void trg_persistent_tree_view_save(TrgPrefs * prefs, void *wdp)
+{
+ trg_pref_widget_desc *wd = (trg_pref_widget_desc *) wdp;
+ TrgPersistentTreeViewPrivate *priv = GET_PRIVATE(wd->widget);
+ GtkTreeModel *model = gtk_tree_view_get_model(priv->tv);
+
+ JsonNode *node = trg_prefs_get_value(prefs, wd->key, JSON_NODE_ARRAY,
+ wd->flags |
+ TRG_PREFS_REPLACENODE);
+
+ priv->ja = json_array_new();
+
+ gtk_tree_model_foreach(model,
+ trg_persistent_tree_view_save_foreachfunc,
+ wd->widget);
+ json_node_take_array(node, priv->ja);
+
+ trg_prefs_changed_emit_signal(prefs, wd->key);
+}
+
+trg_persistent_tree_view_column
+ * trg_persistent_tree_view_add_column(TrgPersistentTreeView * ptv,
+ gint index, const gchar * key,
+ const gchar * label)
+{
+ TrgPersistentTreeViewPrivate *priv = GET_PRIVATE(ptv);
+ trg_persistent_tree_view_column *cd =
+ g_new0(trg_persistent_tree_view_column, 1);
+ GtkCellRenderer *renderer;
+
+ cd->key = g_strdup(key);
+ cd->label = g_strdup(label);
+ cd->index = index;
+ cd->tv = ptv;
+
+ renderer = gtk_cell_renderer_text_new();
+ g_object_set(G_OBJECT(renderer), "editable", TRUE, NULL);
+ g_signal_connect(renderer, "edited",
+ G_CALLBACK(trg_persistent_tree_view_edit), cd);
+ cd->column = gtk_tree_view_column_new_with_attributes(cd->label,
+ renderer, "text",
+ cd->index, NULL);
+ gtk_tree_view_column_set_resizable(cd->column, TRUE);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(priv->tv), cd->column);
+
+ priv->columns = g_slist_append(priv->columns, cd);
+
+ return cd;
+}
+
+static GtkTreeView
+ * trg_persistent_tree_view_tree_view_new(TrgPersistentTreeView * ptv,
+ GtkTreeModel * model)
+{
+ GtkTreeView *tv = GTK_TREE_VIEW(gtk_tree_view_new_with_model(model));
+ GtkTreeSelection *selection;
+
+ g_object_unref(G_OBJECT(model));
+
+ gtk_tree_view_set_rubber_banding(tv, TRUE);
+
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
+
+ g_signal_connect(G_OBJECT(selection), "changed",
+ G_CALLBACK(selection_changed_cb), ptv);
+
+ return tv;
+}
+
+static void trg_persistent_tree_view_add_cb(GtkWidget * w, gpointer data)
+{
+ TrgPersistentTreeViewPrivate *priv = GET_PRIVATE(data);
+ GtkTreeModel *model = gtk_tree_view_get_model(priv->tv);
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ path = gtk_tree_model_get_path(model, &iter);
+
+ if (priv->addSelect)
+ gtk_tree_view_set_cursor(priv->tv, path, priv->addSelect->column,
+ TRUE);
+
+ gtk_tree_path_free(path);
+}
+
+static void trg_persistent_tree_view_up_cb(GtkWidget * w, gpointer data)
+{
+ TrgPersistentTreeViewPrivate *priv = GET_PRIVATE(data);
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(priv->tv);
+ GtkTreeModel *model;
+ GtkTreeIter iter, prevIter;
+ GtkTreePath *path;
+
+ if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
+ path = gtk_tree_model_get_path(model, &iter);
+ if (gtk_tree_path_prev(path) &&
+ gtk_tree_model_get_iter(model, &prevIter, path)) {
+ gtk_list_store_move_before(GTK_LIST_STORE(model), &iter,
+ &prevIter);
+ selection_changed(TRG_PERSISTENT_TREE_VIEW(data), selection);
+ }
+ gtk_tree_path_free(path);
+ }
+}
+
+static void trg_persistent_tree_view_down_cb(GtkWidget * w, gpointer data)
+{
+ TrgPersistentTreeViewPrivate *priv = GET_PRIVATE(data);
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(priv->tv);
+ GtkTreeModel *model;
+ GtkTreeIter iter, nextIter;
+
+ if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
+ nextIter = iter;
+ if (gtk_tree_model_iter_next(model, &nextIter)) {
+ gtk_list_store_move_after(GTK_LIST_STORE(model), &iter,
+ &nextIter);
+ selection_changed(TRG_PERSISTENT_TREE_VIEW(data), selection);
+ }
+ }
+}
+
+static void trg_persistent_tree_view_del_cb(GtkWidget * w, gpointer data)
+{
+ TrgPersistentTreeViewPrivate *priv = GET_PRIVATE(data);
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(priv->tv);
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ if (gtk_tree_selection_get_selected(selection, &model, &iter))
+ gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
+}
+
+static void
+trg_persistent_tree_view_get_property(GObject * object,
+ guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ switch (property_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_persistent_tree_view_set_property(GObject * object,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgPersistentTreeViewPrivate *priv = GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_PREFS:
+ priv->prefs = g_value_get_object(value);
+ break;
+ case PROP_KEY:
+ priv->key = g_strdup(g_value_get_pointer(value));
+ break;
+ case PROP_MODEL:
+ priv->model = g_value_get_object(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void trg_persistent_tree_view_finalize(GObject * object)
+{
+ TrgPersistentTreeViewPrivate *priv = GET_PRIVATE(object);
+ GSList *li;
+ for (li = priv->columns; li; li = g_slist_next(li)) {
+ trg_persistent_tree_view_column *cd =
+ (trg_persistent_tree_view_column *) li->data;
+ g_free(cd->key);
+ g_free(cd->label);
+ g_free(cd);
+ }
+ g_slist_free(priv->columns);
+ g_free(priv->key);
+ G_OBJECT_CLASS(trg_persistent_tree_view_parent_class)->finalize
+ (object);
+}
+
+trg_pref_widget_desc
+ * trg_persistent_tree_view_get_widget_desc(TrgPersistentTreeView * ptv)
+{
+ TrgPersistentTreeViewPrivate *priv = GET_PRIVATE(ptv);
+ return priv->wd;
+}
+
+void
+trg_persistent_tree_view_set_add_select(TrgPersistentTreeView * ptv,
+ trg_persistent_tree_view_column *
+ cd)
+{
+ TrgPersistentTreeViewPrivate *priv = GET_PRIVATE(ptv);
+ priv->addSelect = cd;
+}
+
+static GObject *trg_persistent_tree_view_constructor(GType type,
+ guint
+ n_construct_properties,
+ GObjectConstructParam
+ * construct_params)
+{
+ GObject *object;
+ TrgPersistentTreeViewPrivate *priv;
+ GtkWidget *hbox, *w;
+
+ object = G_OBJECT_CLASS
+ (trg_persistent_tree_view_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+ priv = GET_PRIVATE(object);
+
+ hbox = trg_hbox_new(FALSE, 0);
+
+ w = gtk_button_new_from_stock(GTK_STOCK_ADD);
+ g_signal_connect(w, "clicked",
+ G_CALLBACK(trg_persistent_tree_view_add_cb), object);
+ gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 4);
+
+ w = priv->delButton = gtk_button_new_from_stock(GTK_STOCK_DELETE);
+ gtk_widget_set_sensitive(w, FALSE);
+ g_signal_connect(w, "clicked",
+ G_CALLBACK(trg_persistent_tree_view_del_cb), object);
+ gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 4);
+
+ w = priv->upButton = gtk_button_new_from_stock(GTK_STOCK_GO_UP);
+ gtk_widget_set_sensitive(w, FALSE);
+ g_signal_connect(w, "clicked",
+ G_CALLBACK(trg_persistent_tree_view_up_cb), object);
+ gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 4);
+
+ w = priv->downButton = gtk_button_new_from_stock(GTK_STOCK_GO_DOWN);
+ gtk_widget_set_sensitive(w, FALSE);
+ g_signal_connect(w, "clicked",
+ G_CALLBACK(trg_persistent_tree_view_down_cb), object);
+ gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 4);
+
+ priv->tv =
+ trg_persistent_tree_view_tree_view_new(TRG_PERSISTENT_TREE_VIEW
+ (object), priv->model);
+ gtk_box_pack_start(GTK_BOX(object),
+ my_scrolledwin_new(GTK_WIDGET(priv->tv)), TRUE,
+ TRUE, 4);
+ gtk_box_pack_start(GTK_BOX(object), hbox, FALSE, FALSE, 4);
+
+ priv->wd = trg_pref_widget_desc_new(GTK_WIDGET(priv->tv), priv->key,
+ TRG_PREFS_PROFILE);
+ priv->wd->widget = GTK_WIDGET(object);
+ priv->wd->saveFunc = &trg_persistent_tree_view_save;
+ priv->wd->refreshFunc = &trg_persistent_tree_view_refresh;
+
+ return object;
+}
+
+static void
+trg_persistent_tree_view_class_init(TrgPersistentTreeViewClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgPersistentTreeViewPrivate));
+
+ object_class->get_property = trg_persistent_tree_view_get_property;
+ object_class->set_property = trg_persistent_tree_view_set_property;
+ object_class->finalize = trg_persistent_tree_view_finalize;
+ object_class->constructor = trg_persistent_tree_view_constructor;
+
+ g_object_class_install_property(object_class,
+ PROP_KEY,
+ g_param_spec_pointer("conf-key",
+ "Conf Key",
+ "Conf Key",
+ G_PARAM_READWRITE
+ |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_PREFS,
+ g_param_spec_object("prefs",
+ "Prefs",
+ "Prefs",
+ TRG_TYPE_PREFS,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_MODEL,
+ g_param_spec_object("persistent-model",
+ "Persistent Model",
+ "Persistent Model",
+ GTK_TYPE_LIST_STORE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+}
+
+static void trg_persistent_tree_view_init(TrgPersistentTreeView * self)
+{
+}
+
+TrgPersistentTreeView *trg_persistent_tree_view_new(TrgPrefs * prefs,
+ GtkListStore * model,
+ const gchar * key)
+{
+ GObject *obj =
+ g_object_new(TRG_TYPE_PERSISTENT_TREE_VIEW, "prefs", prefs,
+ "conf-key", key, "persistent-model",
+ model,
+ NULL);
+
+ return TRG_PERSISTENT_TREE_VIEW(obj);
+}
diff --git a/src/trg-persistent-tree-view.h b/src/trg-persistent-tree-view.h
new file mode 100644
index 0000000..a5a7c8f
--- /dev/null
+++ b/src/trg-persistent-tree-view.h
@@ -0,0 +1,76 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _TRG_PERSISTENT_TREE_VIEW
+#define _TRG_PERSISTENT_TREE_VIEW
+
+#include <gtk/gtk.h>
+#include <glib-object.h>
+
+#include "trg-preferences-dialog.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_PERSISTENT_TREE_VIEW trg_persistent_tree_view_get_type()
+#define TRG_PERSISTENT_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_PERSISTENT_TREE_VIEW, TrgPersistentTreeView))
+#define TRG_PERSISTENT_TREE_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_PERSISTENT_TREE_VIEW, TrgPersistentTreeViewClass))
+#define TRG_IS_PERSISTENT_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_PERSISTENT_TREE_VIEW))
+#define TRG_IS_PERSISTENT_TREE_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_PERSISTENT_TREE_VIEW))
+#define TRG_PERSISTENT_TREE_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_PERSISTENT_TREE_VIEW, TrgPersistentTreeViewClass))
+ typedef struct {
+ GtkVBox parent;
+} TrgPersistentTreeView;
+
+typedef struct {
+ GtkTreeViewClass parent_class;
+} TrgPersistentTreeViewClass;
+
+GType trg_persistent_tree_view_get_type(void);
+
+typedef struct {
+ GtkTreeViewColumn *column;
+ gchar *key;
+ gchar *label;
+ TrgPersistentTreeView *tv;
+ gint index;
+} trg_persistent_tree_view_column;
+
+TrgPersistentTreeView *trg_persistent_tree_view_new(TrgPrefs * prefs,
+ GtkListStore * model,
+ const gchar * key);
+
+trg_pref_widget_desc
+ * trg_persistent_tree_view_get_widget_desc(TrgPersistentTreeView *
+ ptv);
+
+void trg_persistent_tree_view_set_add_select(TrgPersistentTreeView * ptv,
+ trg_persistent_tree_view_column
+ * cd);
+
+trg_persistent_tree_view_column
+ * trg_persistent_tree_view_add_column(TrgPersistentTreeView * ptv,
+ gint index, const gchar * key,
+ const gchar * label);
+
+G_END_DECLS
+#endif /* _TRG_PERSISTENT_TREE_VIEW */
diff --git a/src/trg-preferences-dialog.c b/src/trg-preferences-dialog.c
new file mode 100644
index 0000000..6e98f25
--- /dev/null
+++ b/src/trg-preferences-dialog.c
@@ -0,0 +1,979 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <glib/gprintf.h>
+#include <gtk/gtk.h>
+
+#include "hig.h"
+#include "trg-json-widgets.h"
+#include "trg-preferences-dialog.h"
+#include "trg-persistent-tree-view.h"
+#include "trg-main-window.h"
+#include "trg-prefs.h"
+#include "util.h"
+
+/* UI frontend to modify configurables stored in TrgConf.
+ * To avoid lots of repetitive code, use our own functions for creating widgets
+ * which also create a trg_pref_widget_desc structure and add it to a list.
+ * This contains details of the config key, config flags etc, and a function
+ * pointer for a save/display function. These are all called on save/load.
+ */
+
+#define TRG_PREFERENCES_DIALOG_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), TRG_TYPE_PREFERENCES_DIALOG, TrgPreferencesDialogPrivate))
+
+G_DEFINE_TYPE(TrgPreferencesDialog, trg_preferences_dialog,
+ GTK_TYPE_DIALOG)
+enum {
+ PROP_0, PROP_TRG_CLIENT, PROP_MAIN_WINDOW
+};
+
+struct _TrgPreferencesDialogPrivate {
+ TrgMainWindow *win;
+ TrgClient *client;
+ TrgPrefs *prefs;
+ GtkWidget *profileDelButton;
+ GtkWidget *profileComboBox;
+ GtkWidget *profileNameEntry;
+ GtkWidget *fullUpdateCheck;
+ GList *widgets;
+};
+
+static GObject *instance = NULL;
+
+static void trg_pref_widget_desc_free(trg_pref_widget_desc * wd)
+{
+ g_free(wd->key);
+ g_free(wd);
+}
+
+trg_pref_widget_desc *trg_pref_widget_desc_new(GtkWidget * w, gchar * key,
+ int flags)
+{
+ trg_pref_widget_desc *desc = g_new0(trg_pref_widget_desc, 1);
+ desc->widget = w;
+ desc->key = g_strdup(key);
+ desc->flags = flags;
+ return desc;
+}
+
+static void
+trg_pref_widget_refresh(TrgPreferencesDialog * dlg,
+ trg_pref_widget_desc * wd)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dlg);
+
+ wd->refreshFunc(priv->prefs, wd);
+}
+
+static void trg_pref_widget_refresh_all(TrgPreferencesDialog * dlg)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dlg);
+
+ GList *li;
+ for (li = priv->widgets; li; li = g_list_next(li))
+ trg_pref_widget_refresh(dlg, (trg_pref_widget_desc *) li->data);
+}
+
+static void
+trg_pref_widget_save(TrgPreferencesDialog * dlg, trg_pref_widget_desc * wd)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dlg);
+
+ wd->saveFunc(priv->prefs, wd);
+}
+
+static void trg_pref_widget_save_all(TrgPreferencesDialog * dlg)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dlg);
+ GList *li;
+
+ if (trg_prefs_get_profile(priv->prefs) == NULL)
+ return;
+
+ trg_client_configlock(priv->client);
+ for (li = priv->widgets; li; li = g_list_next(li)) {
+ trg_pref_widget_desc *wd = (trg_pref_widget_desc *) li->data;
+ trg_pref_widget_save(dlg, wd);
+ }
+ trg_client_configunlock(priv->client);
+}
+
+static void
+trg_preferences_dialog_set_property(GObject * object,
+ guint prop_id,
+ const GValue * value,
+ GParamSpec * pspec G_GNUC_UNUSED)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(object);
+
+ switch (prop_id) {
+ case PROP_MAIN_WINDOW:
+ priv->win = g_value_get_object(value);
+ break;
+ case PROP_TRG_CLIENT:
+ priv->client = g_value_get_pointer(value);
+ priv->prefs = trg_client_get_prefs(priv->client);
+ break;
+ }
+}
+
+static void
+trg_preferences_response_cb(GtkDialog * dlg, gint res_id,
+ gpointer data G_GNUC_UNUSED)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dlg);
+ GList *li;
+
+ if (res_id == GTK_RESPONSE_OK) {
+ trg_pref_widget_save_all(TRG_PREFERENCES_DIALOG(dlg));
+ trg_prefs_save(priv->prefs);
+ }
+
+ trg_main_window_reload_dir_aliases(priv->win);
+
+ for (li = priv->widgets; li; li = g_list_next(li))
+ trg_pref_widget_desc_free((trg_pref_widget_desc *) li->data);
+ g_list_free(priv->widgets);
+
+ gtk_widget_destroy(GTK_WIDGET(dlg));
+ instance = NULL;
+}
+
+static void
+trg_preferences_dialog_get_property(GObject * object,
+ guint prop_id,
+ GValue * value,
+ GParamSpec * pspec G_GNUC_UNUSED)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(object);
+
+ switch (prop_id) {
+ case PROP_MAIN_WINDOW:
+ g_value_set_object(value, priv->win);
+ break;
+ case PROP_TRG_CLIENT:
+ g_value_set_pointer(value, priv->client);
+ break;
+ }
+}
+
+static void entry_refresh(TrgPrefs * prefs, void *wdp)
+{
+ trg_pref_widget_desc *wd = (trg_pref_widget_desc *) wdp;
+ gchar *value = trg_prefs_get_string(prefs, wd->key, wd->flags);
+
+ if (value) {
+ gtk_entry_set_text(GTK_ENTRY(wd->widget), value);
+ g_free(value);
+ } else {
+ gtk_entry_set_text(GTK_ENTRY(wd->widget), "");
+ }
+}
+
+static void entry_save(TrgPrefs * prefs, void *wdp)
+{
+ trg_pref_widget_desc *wd = (trg_pref_widget_desc *) wdp;
+
+ trg_prefs_set_string(prefs, wd->key,
+ gtk_entry_get_text(GTK_ENTRY(wd->widget)),
+ wd->flags);
+}
+
+static GtkWidget *trgp_entry_new(TrgPreferencesDialog * dlg, gchar * key,
+ int flags)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dlg);
+ GtkWidget *w = gtk_entry_new();
+ trg_pref_widget_desc *wd = trg_pref_widget_desc_new(w, key, flags);
+
+ wd->saveFunc = &entry_save;
+ wd->refreshFunc = &entry_refresh;
+
+ entry_refresh(priv->prefs, wd);
+ priv->widgets = g_list_append(priv->widgets, wd);
+
+ return w;
+}
+
+static void check_refresh(TrgPrefs * prefs, void *wdp)
+{
+ trg_pref_widget_desc *wd = (trg_pref_widget_desc *) wdp;
+ gboolean value = trg_prefs_get_bool(prefs, wd->key, wd->flags);
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(wd->widget), value);
+}
+
+static void check_save(TrgPrefs * prefs, void *wdp)
+{
+ trg_pref_widget_desc *wd = (trg_pref_widget_desc *) wdp;
+ trg_prefs_set_bool(prefs, wd->key,
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
+ (wd->widget)),
+ wd->flags);
+}
+
+static void trgp_toggle_dependent(GtkToggleButton * b, gpointer data)
+{
+ gtk_widget_set_sensitive(GTK_WIDGET(data),
+ gtk_toggle_button_get_active(b));
+}
+
+static GtkWidget *trgp_check_new(TrgPreferencesDialog * dlg,
+ const char *mnemonic, gchar * key,
+ int flags, GtkToggleButton * dependency)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dlg);
+
+ GtkWidget *w = gtk_check_button_new_with_mnemonic(mnemonic);
+
+ trg_pref_widget_desc *wd = trg_pref_widget_desc_new(w, key, flags);
+ wd->saveFunc = &check_save;
+ wd->refreshFunc = &check_refresh;
+ check_refresh(priv->prefs, wd);
+
+ if (dependency) {
+ g_signal_connect(dependency, "toggled",
+ G_CALLBACK(trgp_toggle_dependent), w);
+ gtk_widget_set_sensitive(w,
+ gtk_toggle_button_get_active(dependency));
+ }
+
+ priv->widgets = g_list_append(priv->widgets, wd);
+
+ return w;
+}
+
+static void spin_refresh(TrgPrefs * prefs, void *wdp)
+{
+ trg_pref_widget_desc *wd = (trg_pref_widget_desc *) wdp;
+ GtkWidget *widget = wd->widget;
+
+ gint value = trg_prefs_get_int(prefs, wd->key, wd->flags);
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), value);
+}
+
+static void spin_save(TrgPrefs * prefs, void *wdp)
+{
+ trg_pref_widget_desc *wd = (trg_pref_widget_desc *) wdp;
+ trg_prefs_set_int(prefs, wd->key,
+ gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON
+ (wd->widget)),
+ wd->flags);
+}
+
+static GtkWidget *trgp_spin_new(TrgPreferencesDialog * dlg, gchar * key,
+ int low, int high, int step, int flags,
+ GtkToggleButton * dependency)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dlg);
+ GtkWidget *w = gtk_spin_button_new_with_range(low, high, step);
+ trg_pref_widget_desc *wd = trg_pref_widget_desc_new(w, key, flags);
+
+ gtk_spin_button_set_digits(GTK_SPIN_BUTTON(w), 0);
+
+ wd->saveFunc = &spin_save;
+ wd->refreshFunc = &spin_refresh;
+
+ if (dependency) {
+ g_signal_connect(dependency, "toggled",
+ G_CALLBACK(trgp_toggle_dependent), w);
+ gtk_widget_set_sensitive(w,
+ gtk_toggle_button_get_active(dependency));
+ }
+
+ spin_refresh(priv->prefs, wd);
+ priv->widgets = g_list_append(priv->widgets, wd);
+
+ return w;
+}
+
+static void toggle_filter_trackers(GtkToggleButton * w, gpointer win)
+{
+ TrgStateSelector *selector =
+ trg_main_window_get_state_selector(TRG_MAIN_WINDOW(win));
+ trg_state_selector_set_show_trackers(selector,
+ gtk_toggle_button_get_active(w));
+}
+
+#if TRG_WITH_GRAPH
+static void toggle_graph(GtkToggleButton * w, gpointer win)
+{
+ if (gtk_toggle_button_get_active(w))
+ trg_main_window_add_graph(TRG_MAIN_WINDOW(win), TRUE);
+ else
+ trg_main_window_remove_graph(TRG_MAIN_WINDOW(win));
+}
+#endif
+
+static void toggle_tray_icon(GtkToggleButton * w, gpointer win)
+{
+ if (gtk_toggle_button_get_active(w))
+ trg_main_window_add_status_icon(TRG_MAIN_WINDOW(win));
+ else
+ trg_main_window_remove_status_icon(TRG_MAIN_WINDOW(win));
+}
+
+static void menu_bar_toggle_filter_dirs(GtkToggleButton * w, gpointer win)
+{
+ TrgStateSelector *selector =
+ trg_main_window_get_state_selector(TRG_MAIN_WINDOW(win));
+ trg_state_selector_set_show_dirs(selector,
+ gtk_toggle_button_get_active(w));
+}
+
+static void view_states_toggled_cb(GtkToggleButton * w, gpointer data)
+{
+ GtkWidget *scroll =
+ gtk_widget_get_parent(GTK_WIDGET
+ (trg_main_window_get_state_selector
+ (TRG_MAIN_WINDOW(data))));
+ trg_widget_set_visible(scroll, gtk_toggle_button_get_active(w));
+}
+
+static void notebook_toggled_cb(GtkToggleButton * b, gpointer data)
+{
+ trg_main_window_notebook_set_visible(TRG_MAIN_WINDOW(data),
+ gtk_toggle_button_get_active(b));
+}
+
+static void
+trgp_double_special_dependent(GtkWidget * widget, gpointer data)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(gtk_widget_get_toplevel
+ (widget));
+ gtk_widget_set_sensitive(GTK_WIDGET(data),
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
+ (widget))
+ &&
+ gtk_widget_get_sensitive
+ (priv->fullUpdateCheck)
+ &&
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
+ (priv->
+ fullUpdateCheck)));
+}
+
+static GtkWidget *trg_prefs_generalPage(TrgPreferencesDialog * dlg)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dlg);
+
+ GtkWidget *w, *activeOnly, *t;
+ guint row = 0;
+
+ t = hig_workarea_create();
+
+ hig_workarea_add_section_title(t, &row, _("Updates"));
+
+ activeOnly = w = trgp_check_new(dlg, _("Update active torrents only"),
+ TRG_PREFS_KEY_UPDATE_ACTIVE_ONLY,
+ TRG_PREFS_PROFILE, NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ priv->fullUpdateCheck = trgp_check_new(dlg,
+ _
+ ("Full update every (?) updates"),
+ TRG_PREFS_ACTIVEONLY_FULLSYNC_ENABLED,
+ TRG_PREFS_PROFILE,
+ GTK_TOGGLE_BUTTON(activeOnly));
+ w = trgp_spin_new(dlg, TRG_PREFS_ACTIVEONLY_FULLSYNC_EVERY, 2, INT_MAX,
+ 1, TRG_PREFS_PROFILE,
+ GTK_TOGGLE_BUTTON(priv->fullUpdateCheck));
+ g_signal_connect(activeOnly, "toggled",
+ G_CALLBACK(trgp_double_special_dependent), w);
+
+ hig_workarea_add_row_w(t, &row, priv->fullUpdateCheck, w, NULL);
+
+ w = trgp_spin_new(dlg, TRG_PREFS_KEY_UPDATE_INTERVAL, 1, INT_MAX, 1,
+ TRG_PREFS_PROFILE, NULL);
+ hig_workarea_add_row(t, &row, _("Update interval:"), w, NULL);
+
+ w = trgp_spin_new(dlg, TRG_PREFS_KEY_MINUPDATE_INTERVAL, 1, INT_MAX, 1,
+ TRG_PREFS_PROFILE, NULL);
+ hig_workarea_add_row(t, &row, _("Minimised update interval:"), w,
+ NULL);
+
+ w = trgp_spin_new(dlg, TRG_PREFS_KEY_SESSION_UPDATE_INTERVAL, 1,
+ INT_MAX, 1, TRG_PREFS_PROFILE, NULL);
+ hig_workarea_add_row(t, &row, _("Session update interval:"), w, NULL);
+
+ hig_workarea_add_section_title(t, &row, _("Torrents"));
+
+ w = trgp_check_new(dlg, _("Start paused"), TRG_PREFS_KEY_START_PAUSED,
+ TRG_PREFS_GLOBAL, NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ w = trgp_check_new(dlg, _("Options dialog on add"),
+ TRG_PREFS_KEY_ADD_OPTIONS_DIALOG, TRG_PREFS_GLOBAL,
+ NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ w = trgp_check_new(dlg, _("Delete local .torrent file after adding"),
+ TRG_PREFS_KEY_DELETE_LOCAL_TORRENT,
+ TRG_PREFS_GLOBAL, NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+
+ return t;
+}
+
+static void profile_changed_cb(GtkWidget * w, gpointer data)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(data);
+ GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(w));
+ gint n_children = gtk_tree_model_iter_n_children(model, NULL);
+
+ GtkTreeIter iter;
+ JsonObject *profile;
+
+ trg_pref_widget_save_all(TRG_PREFERENCES_DIALOG(data));
+
+ if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(w), &iter)) {
+ gtk_tree_model_get(model, &iter, 0, &profile, -1);
+ trg_prefs_set_profile(priv->prefs, profile);
+ trg_pref_widget_refresh_all(TRG_PREFERENCES_DIALOG(data));
+ gtk_widget_set_sensitive(priv->profileDelButton, n_children > 1);
+ } else {
+ gtk_widget_set_sensitive(priv->profileDelButton, FALSE);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(w), 0);
+ }
+}
+
+static void
+trg_prefs_profile_combo_populate(TrgPreferencesDialog * dialog,
+ GtkComboBox * combo, TrgPrefs * prefs)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dialog);
+
+ gint profile_id = trg_prefs_get_int(prefs, TRG_PREFS_KEY_PROFILE_ID,
+ TRG_PREFS_GLOBAL);
+ GtkListStore *store = GTK_LIST_STORE(gtk_combo_box_get_model(combo));
+ GList *profiles =
+ json_array_get_elements(trg_prefs_get_profiles(prefs));
+ GList *li;
+
+ int i = 0;
+ for (li = profiles; li; li = g_list_next(li)) {
+ JsonObject *profile = json_node_get_object((JsonNode *) li->data);
+ const gchar *name_value;
+ GtkTreeIter iter;
+
+ if (json_object_has_member(profile, TRG_PREFS_KEY_PROFILE_NAME)) {
+ name_value = json_object_get_string_member(profile,
+ TRG_PREFS_KEY_PROFILE_NAME);
+ } else {
+ name_value = _(TRG_PROFILE_NAME_DEFAULT);
+ }
+
+ gtk_list_store_insert_with_values(store, &iter, INT_MAX, 0,
+ profile, 1, name_value, -1);
+ if (i == profile_id)
+ gtk_combo_box_set_active_iter(combo, &iter);
+
+ i++;
+ }
+
+ gtk_widget_set_sensitive(priv->profileDelButton,
+ g_list_length(profiles) > 1);
+
+ g_list_free(profiles);
+}
+
+static GtkWidget *trg_prefs_profile_combo_new(TrgClient * tc)
+{
+ GtkWidget *w;
+ GtkCellRenderer *r;
+ GtkListStore *store =
+ gtk_list_store_new(2, G_TYPE_POINTER, G_TYPE_STRING);
+
+ w = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
+ r = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), r, FALSE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(w), r, "text", 1);
+
+ return w;
+}
+
+static void name_changed_cb(GtkWidget * w, gpointer data)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(data);
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GtkComboBox *combo;
+
+ combo = GTK_COMBO_BOX(priv->profileComboBox);
+ model = gtk_combo_box_get_model(combo);
+
+ if (gtk_combo_box_get_active_iter(combo, &iter)) {
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, 1,
+ gtk_entry_get_text(GTK_ENTRY(w)), -1);
+ }
+}
+
+static void del_profile_cb(GtkWidget * w, gpointer data)
+{
+ GtkWidget *win = gtk_widget_get_toplevel(w);
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(win);
+ TrgPrefs *prefs = priv->prefs;
+ GtkComboBox *combo = GTK_COMBO_BOX(data);
+ GtkTreeModel *profileModel = gtk_combo_box_get_model(combo);
+ GtkTreeIter iter;
+ JsonObject *profile;
+
+ if (gtk_combo_box_get_active_iter(combo, &iter)) {
+ gtk_tree_model_get(profileModel, &iter, 0, &profile, -1);
+ trg_prefs_del_profile(prefs, profile);
+ trg_prefs_set_profile(prefs, NULL);
+ gtk_list_store_remove(GTK_LIST_STORE(profileModel), &iter);
+ gtk_combo_box_set_active(combo, 0);
+ }
+}
+
+static void add_profile_cb(GtkWidget * w, gpointer data)
+{
+ GtkWidget *win = gtk_widget_get_toplevel(w);
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(win);
+ GtkComboBox *combo = GTK_COMBO_BOX(data);
+ GtkTreeModel *profileModel = gtk_combo_box_get_model(combo);
+ GtkTreeIter iter;
+
+ JsonObject *profile = trg_prefs_new_profile(priv->prefs);
+ gtk_list_store_insert_with_values(GTK_LIST_STORE(profileModel), &iter,
+ INT_MAX, 0, profile, 1,
+ _(TRG_PROFILE_NAME_DEFAULT), -1);
+ gtk_combo_box_set_active_iter(combo, &iter);
+}
+
+static GtkWidget *trg_prefs_openExecPage(TrgPreferencesDialog * dlg)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dlg);
+ GtkWidget *t;
+ TrgPersistentTreeView *ptv;
+ GtkListStore *model;
+ trg_pref_widget_desc *wd;
+ guint row = 0;
+
+ t = hig_workarea_create();
+
+ hig_workarea_add_section_title(t, &row, _("Commands"));
+
+ model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
+
+ ptv = trg_persistent_tree_view_new(priv->prefs, model,
+ TRG_PREFS_KEY_EXEC_COMMANDS);
+ trg_persistent_tree_view_set_add_select(ptv,
+ trg_persistent_tree_view_add_column
+ (ptv, 0,
+ TRG_PREFS_SUBKEY_LABEL,
+ _("Label")));
+ trg_persistent_tree_view_add_column(ptv, 1,
+ TRG_PREFS_KEY_EXEC_COMMANDS_SUBKEY_CMD,
+ _("Command"));
+ wd = trg_persistent_tree_view_get_widget_desc(ptv);
+ trg_pref_widget_refresh(dlg, wd);
+ priv->widgets = g_list_append(priv->widgets, wd);
+
+ gtk_table_attach(GTK_TABLE(t), GTK_WIDGET(ptv), 1, 2, row, row + 1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 0);
+
+ return t;
+}
+
+static GtkWidget *trg_prefs_dirsPage(TrgPreferencesDialog * dlg)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dlg);
+ GtkWidget *t;
+ TrgPersistentTreeView *ptv;
+ GtkListStore *model;
+ trg_pref_widget_desc *wd;
+ guint row = 0;
+
+ t = hig_workarea_create();
+
+ hig_workarea_add_section_title(t, &row,
+ _("Remote Download Directories"));
+
+ model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
+
+ ptv = trg_persistent_tree_view_new(priv->prefs, model,
+ TRG_PREFS_KEY_DESTINATIONS);
+ trg_persistent_tree_view_set_add_select(ptv,
+ trg_persistent_tree_view_add_column
+ (ptv, 0,
+ TRG_PREFS_SUBKEY_LABEL,
+ _("Label")));
+ trg_persistent_tree_view_add_column(ptv, 1,
+ TRG_PREFS_KEY_DESTINATIONS_SUBKEY_DIR,
+ _("Directory"));
+ wd = trg_persistent_tree_view_get_widget_desc(ptv);
+ trg_pref_widget_refresh(dlg, wd);
+ priv->widgets = g_list_append(priv->widgets, wd);
+
+ gtk_table_attach(GTK_TABLE(t), GTK_WIDGET(ptv), 1, 2, row, row + 1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 0);
+
+ return t;
+}
+
+static GtkWidget *trg_prefs_viewPage(TrgPreferencesDialog * dlg)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dlg);
+
+ GtkWidget *w, *dep, *t, *tray;
+ guint row = 0;
+ gboolean _is_unity = is_unity();
+
+ t = hig_workarea_create();
+
+ hig_workarea_add_section_title(t, &row, _("View"));
+
+ dep = w = trgp_check_new(dlg, _("State selector"),
+ TRG_PREFS_KEY_SHOW_STATE_SELECTOR,
+ TRG_PREFS_GLOBAL, NULL);
+ g_signal_connect(G_OBJECT(w), "toggled",
+ G_CALLBACK(view_states_toggled_cb), priv->win);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ w = trgp_check_new(dlg, _("Directory filters"),
+ TRG_PREFS_KEY_FILTER_DIRS, TRG_PREFS_GLOBAL,
+ GTK_TOGGLE_BUTTON(dep));
+ g_signal_connect(G_OBJECT(w), "toggled",
+ G_CALLBACK(menu_bar_toggle_filter_dirs), priv->win);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ w = trgp_check_new(dlg, _("Tracker filters"),
+ TRG_PREFS_KEY_FILTER_TRACKERS, TRG_PREFS_GLOBAL,
+ GTK_TOGGLE_BUTTON(dep));
+ g_signal_connect(G_OBJECT(w), "toggled",
+ G_CALLBACK(toggle_filter_trackers), priv->win);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ w = trgp_check_new(dlg, _("Torrent Details"),
+ TRG_PREFS_KEY_SHOW_NOTEBOOK, TRG_PREFS_GLOBAL,
+ NULL);
+ g_signal_connect(G_OBJECT(w), "toggled",
+ G_CALLBACK(notebook_toggled_cb), priv->win);
+ hig_workarea_add_wide_control(t, &row, w);
+
+#if TRG_WITH_GRAPH
+ w = trgp_check_new(dlg, _("Show graph"), TRG_PREFS_KEY_SHOW_GRAPH,
+ TRG_PREFS_GLOBAL, GTK_TOGGLE_BUTTON(w));
+ g_signal_connect(G_OBJECT(w), "toggled", G_CALLBACK(toggle_graph),
+ priv->win);
+ hig_workarea_add_wide_control(t, &row, w);
+#endif
+
+#ifndef HAVE_LIBAPPINDICATOR
+ if (!_is_unity) {
+#endif
+ hig_workarea_add_section_title(t, &row, _("System Tray"));
+
+ tray = trgp_check_new(dlg, _("Show in system tray"),
+ TRG_PREFS_KEY_SYSTEM_TRAY, TRG_PREFS_GLOBAL,
+ NULL);
+ g_signal_connect(G_OBJECT(tray), "toggled",
+ G_CALLBACK(toggle_tray_icon), priv->win);
+ hig_workarea_add_wide_control(t, &row, tray);
+
+ if (!_is_unity) {
+ w = trgp_check_new(dlg, _("Minimise to system tray"),
+ TRG_PREFS_KEY_SYSTEM_TRAY_MINIMISE,
+ TRG_PREFS_GLOBAL, NULL);
+ gtk_widget_set_sensitive(w,
+ gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON(tray)));
+ g_signal_connect(G_OBJECT(tray), "toggled",
+ G_CALLBACK(toggle_active_arg_is_sensitive),
+ w);
+ hig_workarea_add_wide_control(t, &row, w);
+ }
+#ifndef HAVE_LIBAPPINDICATOR
+ }
+#endif
+
+#ifdef HAVE_LIBNOTIFY
+ hig_workarea_add_section_title(t, &row, _("Notifications"));
+
+ w = trgp_check_new(dlg, _("Torrent added notifications"),
+ TRG_PREFS_KEY_ADD_NOTIFY, TRG_PREFS_GLOBAL, NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ w = trgp_check_new(dlg, _("Torrent complete notifications"),
+ TRG_PREFS_KEY_COMPLETE_NOTIFY, TRG_PREFS_GLOBAL,
+ NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+#endif
+
+ return t;
+}
+
+static GtkWidget *trg_prefs_serverPage(TrgPreferencesDialog * dlg)
+{
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dlg);
+ TrgPrefs *prefs = priv->prefs;
+
+ GtkWidget *w, *t, *frame, *frameHbox, *profileLabel;
+ GtkWidget *profileButtonsHbox;
+ guint row = 0;
+
+ t = hig_workarea_create();
+
+ /* Profile */
+
+ priv->profileNameEntry =
+ trgp_entry_new(dlg, TRG_PREFS_KEY_PROFILE_NAME, TRG_PREFS_PROFILE);
+
+ priv->profileComboBox = trg_prefs_profile_combo_new(priv->client);
+ profileLabel = gtk_label_new(_("Profile: "));
+
+ profileButtonsHbox = trg_hbox_new(FALSE, 0);
+ w = gtk_button_new_from_stock(GTK_STOCK_NEW);
+ g_signal_connect(w, "clicked", G_CALLBACK(add_profile_cb),
+ priv->profileComboBox);
+ gtk_box_pack_start(GTK_BOX(profileButtonsHbox), w, FALSE, FALSE, 4);
+
+ priv->profileDelButton = gtk_button_new_from_stock(GTK_STOCK_DELETE);
+ g_signal_connect(priv->profileDelButton, "clicked",
+ G_CALLBACK(del_profile_cb), priv->profileComboBox);
+ gtk_widget_set_sensitive(priv->profileDelButton, FALSE);
+ gtk_box_pack_start(GTK_BOX(profileButtonsHbox), priv->profileDelButton,
+ FALSE, FALSE, 4);
+
+ trg_prefs_profile_combo_populate(dlg,
+ GTK_COMBO_BOX(priv->profileComboBox),
+ prefs);
+ g_signal_connect(G_OBJECT(priv->profileComboBox), "changed",
+ G_CALLBACK(profile_changed_cb), dlg);
+
+ /* Name */
+
+ g_signal_connect(priv->profileNameEntry, "changed",
+ G_CALLBACK(name_changed_cb), dlg);
+
+ hig_workarea_add_row(t, &row, _("Name:"), priv->profileNameEntry,
+ NULL);
+
+ gtk_table_attach(GTK_TABLE(t), profileButtonsHbox, 1, 2, row, row + 1,
+ GTK_EXPAND | GTK_SHRINK, 0, 0, 0);
+
+ row++;
+
+ hig_workarea_add_section_title(t, &row, _("Connection"));
+
+ w = trgp_entry_new(dlg, TRG_PREFS_KEY_HOSTNAME, TRG_PREFS_PROFILE);
+ hig_workarea_add_row(t, &row, _("Host:"), w, NULL);
+
+ w = trgp_spin_new(dlg, TRG_PREFS_KEY_PORT, 1, 65535, 1,
+ TRG_PREFS_PROFILE, NULL);
+ hig_workarea_add_row(t, &row, _("Port:"), w, NULL);
+ w = trgp_entry_new(dlg, TRG_PREFS_KEY_RPC_URL_PATH, TRG_PREFS_PROFILE);
+ hig_workarea_add_row(t, &row, _("RPC URL Path:"), w, NULL);
+
+ w = trgp_entry_new(dlg, TRG_PREFS_KEY_USERNAME, TRG_PREFS_PROFILE);
+ hig_workarea_add_row(t, &row, _("Username:"), w, NULL);
+
+ w = trgp_entry_new(dlg, TRG_PREFS_KEY_PASSWORD, TRG_PREFS_PROFILE);
+ gtk_entry_set_visibility(GTK_ENTRY(w), FALSE);
+ hig_workarea_add_row(t, &row, _("Password:"), w, NULL);
+
+ w = trgp_check_new(dlg, _("Automatically connect"),
+ TRG_PREFS_KEY_AUTO_CONNECT, TRG_PREFS_PROFILE,
+ NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+#ifndef CURL_NO_SSL
+ w = trgp_check_new(dlg, _("SSL"), TRG_PREFS_KEY_SSL, TRG_PREFS_PROFILE,
+ NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+#endif
+
+ w = trgp_spin_new(dlg, TRG_PREFS_KEY_TIMEOUT, 1, 3600, 1,
+ TRG_PREFS_PROFILE, NULL);
+ hig_workarea_add_row(t, &row, _("Timeout:"), w, NULL);
+
+ w = trgp_spin_new(dlg, TRG_PREFS_KEY_RETRIES, 0, 3600, 1,
+ TRG_PREFS_PROFILE, NULL);
+ hig_workarea_add_row(t, &row, _("Retries:"), w, NULL);
+
+ frame = gtk_frame_new(NULL);
+ frameHbox = trg_hbox_new(FALSE, 2);
+ gtk_box_pack_start(GTK_BOX(frameHbox), profileLabel, FALSE, FALSE, 2);
+ gtk_box_pack_start(GTK_BOX(frameHbox), priv->profileComboBox, FALSE,
+ FALSE, 4);
+ gtk_frame_set_label_widget(GTK_FRAME(frame), frameHbox);
+ gtk_container_add(GTK_CONTAINER(frame), t);
+
+ return frame;
+}
+
+static GObject *trg_preferences_dialog_constructor(GType type,
+ guint
+ n_construct_properties,
+ GObjectConstructParam *
+ construct_params)
+{
+ GObject *object;
+ TrgPreferencesDialogPrivate *priv;
+ GtkWidget *notebook, *contentvbox;
+
+ object = G_OBJECT_CLASS
+ (trg_preferences_dialog_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+ priv = TRG_PREFERENCES_DIALOG_GET_PRIVATE(object);
+
+ contentvbox = gtk_dialog_get_content_area(GTK_DIALOG(object));
+
+ gtk_window_set_transient_for(GTK_WINDOW(object),
+ GTK_WINDOW(priv->win));
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(object), TRUE);
+ gtk_dialog_add_button(GTK_DIALOG(object), GTK_STOCK_CLOSE,
+ GTK_RESPONSE_CLOSE);
+ gtk_dialog_add_button(GTK_DIALOG(object), GTK_STOCK_OK,
+ GTK_RESPONSE_OK);
+
+ gtk_dialog_set_default_response(GTK_DIALOG(object), GTK_RESPONSE_OK);
+
+ gtk_window_set_title(GTK_WINDOW(object), _("Local Preferences"));
+ gtk_container_set_border_width(GTK_CONTAINER(object), GUI_PAD);
+
+ g_signal_connect(G_OBJECT(object), "response",
+ G_CALLBACK(trg_preferences_response_cb), NULL);
+
+ notebook = gtk_notebook_new();
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ trg_prefs_serverPage(TRG_PREFERENCES_DIALOG
+ (object)),
+ gtk_label_new(_("Connection")));
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ trg_prefs_generalPage(TRG_PREFERENCES_DIALOG
+ (object)),
+ gtk_label_new(_("General")));
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ trg_prefs_viewPage(TRG_PREFERENCES_DIALOG
+ (object)),
+ gtk_label_new(_("View")));
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ trg_prefs_openExecPage(TRG_PREFERENCES_DIALOG
+ (object)),
+ gtk_label_new(_("Actions")));
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ trg_prefs_dirsPage(TRG_PREFERENCES_DIALOG
+ (object)),
+ gtk_label_new(_("Directories")));
+
+ gtk_container_set_border_width(GTK_CONTAINER(notebook), GUI_PAD);
+
+ gtk_box_pack_start(GTK_BOX(contentvbox), notebook, TRUE, TRUE, 0);
+
+ return object;
+}
+
+static void trg_preferences_dialog_init(TrgPreferencesDialog * pref_dlg)
+{
+}
+
+static void
+trg_preferences_dialog_class_init(TrgPreferencesDialogClass * class)
+{
+ GObjectClass *g_object_class = (GObjectClass *) class;
+
+ g_object_class->constructor = trg_preferences_dialog_constructor;
+ g_object_class->set_property = trg_preferences_dialog_set_property;
+ g_object_class->get_property = trg_preferences_dialog_get_property;
+
+ g_object_class_install_property(g_object_class,
+ PROP_TRG_CLIENT,
+ g_param_spec_pointer("trg-client",
+ "TClient",
+ "Client",
+ G_PARAM_READWRITE
+ |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(g_object_class,
+ PROP_MAIN_WINDOW,
+ g_param_spec_object("main-window",
+ "Main Window",
+ "Main Window",
+ TRG_TYPE_MAIN_WINDOW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_type_class_add_private(g_object_class,
+ sizeof(TrgPreferencesDialogPrivate));
+}
+
+GtkWidget *trg_preferences_dialog_get_instance(TrgMainWindow * win,
+ TrgClient * client)
+{
+ if (!instance)
+ instance =
+ g_object_new(TRG_TYPE_PREFERENCES_DIALOG, "main-window", win,
+ "trg-client", client, NULL);
+
+ return GTK_WIDGET(instance);
+}
diff --git a/src/trg-preferences-dialog.h b/src/trg-preferences-dialog.h
new file mode 100644
index 0000000..f5e9825
--- /dev/null
+++ b/src/trg-preferences-dialog.h
@@ -0,0 +1,65 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_PREFERENCES_WINDOW_H_
+#define TRG_PREFERENCES_WINDOW_H_
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "trg-main-window.h"
+
+G_BEGIN_DECLS typedef struct _TrgPreferencesDialog TrgPreferencesDialog;
+typedef struct _TrgPreferencesDialogClass TrgPreferencesDialogClass;
+typedef struct _TrgPreferencesDialogPrivate TrgPreferencesDialogPrivate;
+
+#define TRG_TYPE_PREFERENCES_DIALOG (trg_preferences_dialog_get_type ())
+#define TRG_PREFERENCES_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TRG_TYPE_PREFERENCES_DIALOG, TrgPreferencesDialog))
+#define TRG_PREFERENCES_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TRG_TYPE_PREFERENCES_DIALOG, TrgPreferencesDialogClass))
+#define TRG_IS_PREFERENCES_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TRG_TYPE_PREFERENCES_DIALOG))
+#define TRG_IS_PREFERENCES_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TRG_TYPE_PREFERENCES_DIALOG))
+#define TRG_PREFERENCES_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), TRG_TYPE_PREFERENCES_DIALOG, TrgPreferencesDialogClass))
+
+typedef struct {
+ GtkWidget *widget;
+ int flags;
+ gchar *key;
+ void (*saveFunc) (TrgPrefs *, void *);
+ void (*refreshFunc) (TrgPrefs *, void *);
+} trg_pref_widget_desc;
+
+struct _TrgPreferencesDialog {
+ GtkDialog dialog;
+ TrgPreferencesDialogPrivate *priv;
+};
+
+struct _TrgPreferencesDialogClass {
+ GtkDialogClass parent_class;
+};
+
+GType trg_preferences_dialog_get_type(void);
+
+GtkWidget *trg_preferences_dialog_get_instance(TrgMainWindow * win,
+ TrgClient * client);
+trg_pref_widget_desc *trg_pref_widget_desc_new(GtkWidget * w, gchar * key,
+ int flags);
+
+G_END_DECLS
+#endif /* TRG_PREFERENCES_WINDOW_H_ */
diff --git a/src/trg-prefs.c b/src/trg-prefs.c
new file mode 100644
index 0000000..b559479
--- /dev/null
+++ b/src/trg-prefs.c
@@ -0,0 +1,578 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <json-glib/json-glib.h>
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+
+#include "util.h"
+#include "torrent.h"
+#include "trg-client.h"
+#include "trg-prefs.h"
+
+/* I replaced GConf with this custom configuration backend for a few reasons.
+ * 1) Better windows support. No dependency on DBus.
+ * 2) We're including a JSON parser/writer anyway.
+ * 3) The GConf API is pretty verbose sometimes, working with lists is awkward.
+ * 4) Easily switch between profiles, and scope config to either profiles or global.
+ * 5) Transparently use configurables from the current connection, not current profile.
+ */
+
+G_DEFINE_TYPE(TrgPrefs, trg_prefs, G_TYPE_OBJECT)
+
+struct _TrgPrefsPrivate {
+ JsonObject *defaultsObj;
+ JsonNode *user;
+ JsonObject *userObj;
+ JsonObject *connectionObj;
+ JsonObject *profile;
+ gchar *file;
+};
+
+enum {
+ PREF_CHANGE,
+ PREF_PROFILE_CHANGE,
+ PREFS_SIGNAL_COUNT
+};
+
+static guint signals[PREFS_SIGNAL_COUNT] = { 0 };
+
+void trg_prefs_profile_change_emit_signal(TrgPrefs * p)
+{
+ g_signal_emit(p, signals[PREF_PROFILE_CHANGE], 0);
+}
+
+void trg_prefs_changed_emit_signal(TrgPrefs * p, const gchar * key)
+{
+ g_signal_emit(p, signals[PREF_CHANGE], 0, key);
+}
+
+static void
+trg_prefs_get_property(GObject * object, guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+}
+
+static void
+trg_prefs_set_property(GObject * object, guint property_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+}
+
+static void trg_prefs_dispose(GObject * object)
+{
+ G_OBJECT_CLASS(trg_prefs_parent_class)->dispose(object);
+}
+
+static void trg_prefs_create_defaults(TrgPrefs * p)
+{
+ TrgPrefsPrivate *priv = p->priv;
+ priv->defaultsObj = json_object_new();
+
+ trg_prefs_add_default_string(p, TRG_PREFS_KEY_PROFILE_NAME,
+ _(TRG_PROFILE_NAME_DEFAULT));
+ trg_prefs_add_default_string(p, TRG_PREFS_KEY_RPC_URL_PATH, "/transmission/rpc");
+ trg_prefs_add_default_int(p, TRG_PREFS_KEY_PORT, 9091);
+ trg_prefs_add_default_int(p, TRG_PREFS_KEY_UPDATE_INTERVAL,
+ TRG_INTERVAL_DEFAULT);
+ trg_prefs_add_default_int(p, TRG_PREFS_KEY_SESSION_UPDATE_INTERVAL,
+ TRG_SESSION_INTERVAL_DEFAULT);
+ trg_prefs_add_default_int(p, TRG_PREFS_KEY_MINUPDATE_INTERVAL,
+ TRG_INTERVAL_DEFAULT);
+ trg_prefs_add_default_int(p, TRG_PREFS_ACTIVEONLY_FULLSYNC_EVERY, 2);
+ trg_prefs_add_default_int(p, TRG_PREFS_KEY_STATES_PANED_POS, 120);
+ trg_prefs_add_default_int(p, TRG_PREFS_KEY_TIMEOUT, 40);
+ trg_prefs_add_default_int(p, TRG_PREFS_KEY_RETRIES, 3);
+
+ trg_prefs_add_default_bool_true(p, TRG_PREFS_KEY_FILTER_DIRS);
+ trg_prefs_add_default_bool_true(p, TRG_PREFS_KEY_FILTER_TRACKERS);
+ trg_prefs_add_default_bool_true(p, TRG_PREFS_KEY_SHOW_GRAPH);
+ trg_prefs_add_default_bool_true(p, TRG_PREFS_KEY_ADD_OPTIONS_DIALOG);
+ trg_prefs_add_default_bool_true(p, TRG_PREFS_KEY_SHOW_STATE_SELECTOR);
+ //trg_prefs_add_default_bool_true(p, TRG_PREFS_KEY_SHOW_NOTEBOOK);
+}
+
+static GObject *trg_prefs_constructor(GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *
+ construct_params)
+{
+ GObject *object = G_OBJECT_CLASS
+ (trg_prefs_parent_class)->constructor(type, n_construct_properties,
+ construct_params);
+ TrgPrefs *prefs = TRG_PREFS(object);
+ TrgPrefsPrivate *priv = prefs->priv;
+
+ trg_prefs_create_defaults(prefs);
+
+ priv->file = g_build_filename(g_get_user_config_dir(),
+ g_get_application_name(),
+ TRG_PREFS_FILENAME, NULL);
+
+ return object;
+}
+
+static void trg_prefs_class_init(TrgPrefsClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgPrefsPrivate));
+
+ object_class->get_property = trg_prefs_get_property;
+ object_class->set_property = trg_prefs_set_property;
+ object_class->dispose = trg_prefs_dispose;
+ object_class->constructor = trg_prefs_constructor;
+
+ signals[PREF_CHANGE] =
+ g_signal_new("pref-changed",
+ G_TYPE_FROM_CLASS(object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET(TrgPrefsClass,
+ pref_changed), NULL,
+ NULL, g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ signals[PREF_PROFILE_CHANGE] =
+ g_signal_new("pref-profile-changed",
+ G_TYPE_FROM_CLASS(object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET(TrgPrefsClass,
+ pref_changed), NULL,
+ NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+}
+
+static void trg_prefs_init(TrgPrefs * self)
+{
+ self->priv =
+ G_TYPE_INSTANCE_GET_PRIVATE(self, TRG_TYPE_PREFS, TrgPrefsPrivate);
+}
+
+TrgPrefs *trg_prefs_new(void)
+{
+ return g_object_new(TRG_TYPE_PREFS, NULL);
+}
+
+static JsonObject *trg_prefs_new_profile_object()
+{
+ return json_object_new();
+}
+
+void trg_prefs_add_default_int(TrgPrefs * p, const gchar * key, int value)
+{
+ TrgPrefsPrivate *priv = p->priv;
+
+ json_object_set_int_member(priv->defaultsObj, key, value);
+}
+
+void
+trg_prefs_add_default_string(TrgPrefs * p, const gchar * key,
+ gchar * value)
+{
+ TrgPrefsPrivate *priv = p->priv;
+
+ json_object_set_string_member(priv->defaultsObj, key, value);
+}
+
+void
+trg_prefs_add_default_double(TrgPrefs * p, const gchar * key, double value)
+{
+ TrgPrefsPrivate *priv = p->priv;
+
+ json_object_set_double_member(priv->defaultsObj, key, value);
+}
+
+/* Not much point adding a default of FALSE, as that's the fallback */
+void trg_prefs_add_default_bool_true(TrgPrefs * p, const gchar * key)
+{
+ TrgPrefsPrivate *priv = p->priv;
+ json_object_set_boolean_member(priv->defaultsObj, key, TRUE);
+}
+
+gint trg_prefs_get_profile_id(TrgPrefs * p)
+{
+ TrgPrefsPrivate *priv = p->priv;
+ return (gint) json_object_get_int_member(priv->userObj,
+ TRG_PREFS_KEY_PROFILE_ID);
+}
+
+static JsonNode *trg_prefs_get_value_inner(JsonObject * obj,
+ const gchar * key, int type,
+ int flags)
+{
+ if (json_object_has_member(obj, key)) {
+ if ((flags & TRG_PREFS_REPLACENODE))
+ json_object_remove_member(obj, key);
+ else
+ return json_object_get_member(obj, key);
+ }
+
+ if ((flags & TRG_PREFS_NEWNODE) || (flags & TRG_PREFS_REPLACENODE)) {
+ JsonNode *newNode = json_node_new(type);
+ json_object_set_member(obj, key, newNode);
+ return newNode;
+ }
+
+ return NULL;
+}
+
+JsonNode *trg_prefs_get_value(TrgPrefs * p, const gchar * key, int type,
+ int flags)
+{
+ TrgPrefsPrivate *priv = p->priv;
+ JsonNode *res;
+
+ if (priv->profile && (flags & TRG_PREFS_PROFILE)) {
+ if ((res =
+ trg_prefs_get_value_inner(priv->profile, key, type, flags)))
+ return res;
+ } else if (priv->connectionObj && (flags & TRG_PREFS_CONNECTION)) {
+ if ((res =
+ trg_prefs_get_value_inner(priv->connectionObj, key, type,
+ flags)))
+ return res;
+ } else {
+ if ((res =
+ trg_prefs_get_value_inner(priv->userObj, key, type, flags)))
+ return res;
+ }
+
+ if (priv->defaultsObj
+ && json_object_has_member(priv->defaultsObj, key))
+ return json_object_get_member(priv->defaultsObj, key);
+
+ return NULL;
+}
+
+void trg_prefs_set_connection(TrgPrefs * p, JsonObject * profile)
+{
+ TrgPrefsPrivate *priv = p->priv;
+
+ if (priv->connectionObj)
+ json_object_unref(priv->connectionObj);
+
+ if (profile)
+ json_object_ref(profile);
+
+ priv->connectionObj = profile;
+}
+
+gchar *trg_prefs_get_string(TrgPrefs * p, const gchar * key, int flags)
+{
+ JsonNode *node = trg_prefs_get_value(p, key, JSON_NODE_VALUE, flags);
+
+ if (node)
+ return g_strdup(json_node_get_string(node));
+ else
+ return NULL;
+}
+
+JsonArray *trg_prefs_get_array(TrgPrefs * p, const gchar * key, int flags)
+{
+ JsonNode *node = trg_prefs_get_value(p, key, JSON_NODE_ARRAY, flags);
+
+ if (node)
+ return json_node_get_array(node);
+ else
+ return NULL;
+}
+
+gint64 trg_prefs_get_int(TrgPrefs * p, const gchar * key, int flags)
+{
+ JsonNode *node = trg_prefs_get_value(p, key, JSON_NODE_VALUE, flags);
+
+ if (node)
+ return json_node_get_int(node);
+ else
+ return 0;
+}
+
+gdouble trg_prefs_get_double(TrgPrefs * p, const gchar * key, int flags)
+{
+ JsonNode *node = trg_prefs_get_value(p, key, JSON_NODE_VALUE, flags);
+ if (node)
+ return json_node_get_double(node);
+ else
+ return 0.0;
+}
+
+gboolean trg_prefs_get_bool(TrgPrefs * p, const gchar * key, int flags)
+{
+ JsonNode *node = trg_prefs_get_value(p, key, JSON_NODE_VALUE, flags);
+
+ if (node)
+ return json_node_get_boolean(node);
+ else
+ return FALSE;
+}
+
+void
+trg_prefs_set_int(TrgPrefs * p, const gchar * key, int value, int flags)
+{
+ JsonNode *node = trg_prefs_get_value(p, key, JSON_NODE_VALUE,
+ flags | TRG_PREFS_NEWNODE);
+
+ json_node_set_int(node, (gint64) value);
+ trg_prefs_changed_emit_signal(p, key);
+}
+
+void
+trg_prefs_set_string(TrgPrefs * p, const gchar * key,
+ const gchar * value, int flags)
+{
+ JsonNode *node = trg_prefs_get_value(p, key, JSON_NODE_VALUE,
+ flags | TRG_PREFS_NEWNODE);
+ json_node_set_string(node, value);
+ trg_prefs_changed_emit_signal(p, key);
+}
+
+void trg_prefs_set_profile(TrgPrefs * p, JsonObject * profile)
+{
+ TrgPrefsPrivate *priv = p->priv;
+ GList *profiles = json_array_get_elements(trg_prefs_get_profiles(p));
+ gint i = 0;
+ GList *li;
+
+ priv->profile = profile;
+
+ for (li = profiles; li; li = g_list_next(li)) {
+ if (json_node_get_object((JsonNode *) li->data) == profile) {
+ trg_prefs_set_int(p, TRG_PREFS_KEY_PROFILE_ID, i,
+ TRG_PREFS_GLOBAL);
+ break;
+ }
+ i++;
+ }
+
+ g_list_free(profiles);
+
+ trg_prefs_changed_emit_signal(p, NULL);
+ trg_prefs_profile_change_emit_signal(p);
+}
+
+JsonObject *trg_prefs_new_profile(TrgPrefs * p)
+{
+ JsonArray *profiles = trg_prefs_get_profiles(p);
+ JsonObject *newp = trg_prefs_new_profile_object();
+
+ json_array_add_object_element(profiles, newp);
+
+ return newp;
+}
+
+void trg_prefs_del_profile(TrgPrefs * p, JsonObject * profile)
+{
+ JsonArray *profiles = trg_prefs_get_profiles(p);
+ GList *profilesList = json_array_get_elements(profiles);
+
+ GList *li;
+ JsonNode *node;
+ int i = 0;
+
+ for (li = profilesList; li; li = g_list_next(li)) {
+ node = (JsonNode *) li->data;
+ if (profile == json_node_get_object(node)) {
+ json_array_remove_element(profiles, i);
+ break;
+ }
+ i++;
+ }
+
+ g_list_free(profilesList);
+
+ trg_prefs_profile_change_emit_signal(p);
+}
+
+JsonObject *trg_prefs_get_profile(TrgPrefs * p)
+{
+ TrgPrefsPrivate *priv = p->priv;
+ return priv->profile;
+}
+
+JsonObject *trg_prefs_get_connection(TrgPrefs * p)
+{
+ TrgPrefsPrivate *priv = p->priv;
+ return priv->connectionObj;
+}
+
+JsonArray *trg_prefs_get_profiles(TrgPrefs * p)
+{
+ TrgPrefsPrivate *priv = p->priv;
+ return json_object_get_array_member(priv->userObj,
+ TRG_PREFS_KEY_PROFILES);
+}
+
+void
+trg_prefs_set_double(TrgPrefs * p, const gchar * key, gdouble value,
+ int flags)
+{
+ JsonNode *node = trg_prefs_get_value(p, key, JSON_NODE_VALUE,
+ flags | TRG_PREFS_NEWNODE);
+ json_node_set_double(node, value);
+ trg_prefs_changed_emit_signal(p, key);
+}
+
+void
+trg_prefs_set_bool(TrgPrefs * p, const gchar * key, gboolean value,
+ int flags)
+{
+ JsonNode *node = trg_prefs_get_value(p, key, JSON_NODE_VALUE,
+ flags | TRG_PREFS_NEWNODE);
+ json_node_set_boolean(node, value);
+ trg_prefs_changed_emit_signal(p, key);
+}
+
+gboolean trg_prefs_save(TrgPrefs * p)
+{
+ TrgPrefsPrivate *priv = p->priv;
+ JsonGenerator *gen = json_generator_new();
+ gchar *dirName;
+ gboolean success = TRUE;
+ gboolean isNew = TRUE;
+
+ dirName = g_path_get_dirname(priv->file);
+ if (!g_file_test(dirName, G_FILE_TEST_IS_DIR)) {
+ success = g_mkdir_with_parents(dirName, TRG_PREFS_DEFAULT_DIR_MODE)
+ == 0;
+ } else if (g_file_test(priv->file, G_FILE_TEST_IS_REGULAR)) {
+ isNew = FALSE;
+ }
+ g_free(dirName);
+
+ if (!success) {
+ g_error
+ ("Problem creating parent directory (permissions?) for: %s\n",
+ priv->file);
+ return success;
+ }
+
+ g_object_set(G_OBJECT(gen), "pretty", TRUE, NULL);
+ json_generator_set_root(gen, priv->user);
+
+ success = json_generator_to_file(gen, priv->file, NULL);
+
+ if (!success)
+ g_error("Problem writing configuration file (permissions?) to: %s",
+ priv->file);
+ else if (isNew)
+ g_chmod(priv->file, 384);
+
+ g_object_unref(gen);
+
+ return success;
+}
+
+JsonObject *trg_prefs_get_root(TrgPrefs * p)
+{
+ TrgPrefsPrivate *priv = p->priv;
+ return priv->userObj;
+}
+
+void trg_prefs_empty_init(TrgPrefs * p)
+{
+ TrgPrefsPrivate *priv = p->priv;
+ JsonArray *profiles = json_array_new();
+
+ priv->user = json_node_new(JSON_NODE_OBJECT);
+ priv->userObj = json_object_new();
+ json_node_take_object(priv->user, priv->userObj);
+
+ priv->profile = trg_prefs_new_profile_object();
+
+ json_array_add_object_element(profiles, priv->profile);
+ json_object_set_array_member(priv->userObj, TRG_PREFS_KEY_PROFILES,
+ profiles);
+
+ json_object_set_int_member(priv->userObj, TRG_PREFS_KEY_PROFILE_ID, 0);
+}
+
+void trg_prefs_load(TrgPrefs * p)
+{
+ TrgPrefsPrivate *priv = p->priv;
+ JsonParser *parser = json_parser_new();
+ JsonNode *root;
+ guint n_profiles;
+ JsonArray *profiles;
+
+ gboolean parsed = json_parser_load_from_file(parser, priv->file, NULL);
+
+ if (!parsed) {
+ trg_prefs_empty_init(p);
+ g_object_unref(parser);
+ return;
+ }
+
+ root = json_parser_get_root(parser);
+ if (root) {
+ priv->user = json_node_copy(root);
+ priv->userObj = json_node_get_object(priv->user);
+ }
+
+ g_object_unref(parser);
+
+ if (!root) {
+ trg_prefs_empty_init(p);
+ return;
+ }
+
+ if (!json_object_has_member(priv->userObj, TRG_PREFS_KEY_PROFILES)) {
+ profiles = json_array_new();
+ json_object_set_array_member(priv->userObj, TRG_PREFS_KEY_PROFILES,
+ profiles);
+ } else {
+ profiles = json_object_get_array_member(priv->userObj,
+ TRG_PREFS_KEY_PROFILES);
+ }
+
+ n_profiles = json_array_get_length(profiles);
+
+ if (n_profiles < 1) {
+ priv->profile = trg_prefs_new_profile_object();
+ json_array_add_object_element(profiles, priv->profile);
+ trg_prefs_set_int(p, TRG_PREFS_KEY_PROFILE_ID, 0,
+ TRG_PREFS_GLOBAL);
+ } else {
+ gint profile_id = trg_prefs_get_int(p, TRG_PREFS_KEY_PROFILE_ID,
+ TRG_PREFS_GLOBAL);
+ if (profile_id >= n_profiles)
+ trg_prefs_set_int(p, TRG_PREFS_KEY_PROFILE_ID, profile_id = 0,
+ TRG_PREFS_GLOBAL);
+
+ priv->profile =
+ json_array_get_object_element(profiles, profile_id);
+ }
+}
+
+guint trg_prefs_get_add_flags(TrgPrefs * p)
+{
+ guint flags = 0x00;
+
+ if (trg_prefs_get_bool
+ (p, TRG_PREFS_KEY_START_PAUSED, TRG_PREFS_GLOBAL))
+ flags |= TORRENT_ADD_FLAG_PAUSED;
+
+ if (trg_prefs_get_bool
+ (p, TRG_PREFS_KEY_DELETE_LOCAL_TORRENT, TRG_PREFS_GLOBAL))
+ flags |= TORRENT_ADD_FLAG_DELETE;
+
+ return flags;
+}
diff --git a/src/trg-prefs.h b/src/trg-prefs.h
new file mode 100644
index 0000000..e7a7f48
--- /dev/null
+++ b/src/trg-prefs.h
@@ -0,0 +1,166 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _TRG_PREFS_H_
+#define _TRG_PREFS_H_
+
+#include <glib-object.h>
+#include <json-glib/json-glib.h>
+
+#define TRG_PREFS_FILENAME "config.json"
+#define TRG_PREFS_DEFAULT_DIR_MODE 448
+#define TRG_PORT_DEFAULT 9091
+#define TRG_INTERVAL_DEFAULT 3
+#define TRG_SESSION_INTERVAL_DEFAULT 60
+#define TRG_PROFILE_NAME_DEFAULT "Default"
+
+#define TRG_PREFS_KEY_RPC_URL_PATH "rpc-url-path"
+#define TRG_PREFS_KEY_PROFILE_ID "profile-id"
+#define TRG_PREFS_KEY_PROFILES "profiles"
+#define TRG_PREFS_KEY_PROFILE_NAME "profile-name"
+#define TRG_PREFS_KEY_HOSTNAME "hostname"
+#define TRG_PREFS_KEY_PORT "port"
+#define TRG_PREFS_KEY_MINUPDATE_INTERVAL "min-update-interval"
+#define TRG_PREFS_KEY_USERNAME "username"
+#define TRG_PREFS_KEY_PASSWORD "password"
+#define TRG_PREFS_KEY_AUTO_CONNECT "auto-connect"
+#define TRG_PREFS_KEY_SSL "ssl"
+#define TRG_PREFS_KEY_TIMEOUT "timeout"
+#define TRG_PREFS_KEY_RETRIES "retries"
+#define TRG_PREFS_KEY_UPDATE_INTERVAL "update-interval"
+#define TRG_PREFS_KEY_SESSION_UPDATE_INTERVAL "session-update-interval"
+#define TRG_PREFS_KEY_COMPLETE_NOTIFY "complete-notify"
+#define TRG_PREFS_KEY_ADD_NOTIFY "add-notify"
+#define TRG_PREFS_KEY_WINDOW_WIDTH "window-width"
+#define TRG_PREFS_KEY_WINDOW_HEIGHT "window-height"
+#define TRG_PREFS_KEY_GRAPH_SPAN "graph-span"
+#define TRG_PREFS_KEY_SYSTEM_TRAY "system-tray"
+#define TRG_PREFS_KEY_SHOW_GRAPH "show-graph"
+#define TRG_PREFS_KEY_SYSTEM_TRAY_MINIMISE "system-tray-minimise"
+#define TRG_PREFS_KEY_FILTER_TRACKERS "filter-trackers"
+#define TRG_PREFS_KEY_FILTER_DIRS "filter-dirs"
+#define TRG_PREFS_KEY_SHOW_STATE_SELECTOR "show-state-selector"
+#define TRG_PREFS_KEY_SHOW_NOTEBOOK "show-notebook"
+#define TRG_PREFS_KEY_LAST_TORRENT_DIR "last-torrent-dir"
+#define TRG_PREFS_KEY_ADD_OPTIONS_DIALOG "add-options-dialog"
+#define TRG_PREFS_KEY_START_PAUSED "start-paused"
+#define TRG_PREFS_KEY_UPDATE_ACTIVE_ONLY "update-active-only"
+#define TRG_PREFS_KEY_DELETE_LOCAL_TORRENT "delete-local-torrent"
+#define TRG_PREFS_STATE_SELECTOR_LAST "state-selector-last"
+#define TRG_PREFS_ACTIVEONLY_FULLSYNC_ENABLED "activeonly-fullsync-enabled"
+#define TRG_PREFS_ACTIVEONLY_FULLSYNC_EVERY "activeonly-fullsync-every"
+#define TRG_PREFS_KEY_STYLE "style"
+#define TRG_PREFS_KEY_TREE_VIEWS "tree-views"
+#define TRG_PREFS_KEY_TV_SORT_TYPE "sort-type"
+#define TRG_PREFS_KEY_TV_SORT_COL "sort-col"
+#define TRG_PREFS_KEY_TV_COLUMNS "columns"
+#define TRG_PREFS_KEY_TV_WIDTHS "widths"
+#define TRG_PREFS_KEY_NOTEBOOK_PANED_POS "notebook-paned-pos"
+#define TRG_PREFS_KEY_STATES_PANED_POS "states-paned-pos"
+#define TRG_PREFS_SUBKEY_LABEL "label"
+#define TRG_PREFS_KEY_EXEC_COMMANDS "exec-commands"
+#define TRG_PREFS_KEY_EXEC_COMMANDS_SUBKEY_CMD "cmd"
+#define TRG_PREFS_KEY_DESTINATIONS "destinations"
+#define TRG_PREFS_KEY_LAST_MOVE_DESTINATION "last-move-destination"
+#define TRG_PREFS_KEY_LAST_ADD_DESTINATION "last-add-destination"
+#define TRG_PREFS_KEY_DESTINATIONS_SUBKEY_DIR "dir"
+
+#define TRG_PREFS_NOFLAGS (1 << 0) /* 0x00 */
+#define TRG_PREFS_GLOBAL (1 << 1) /* 0x01 */
+#define TRG_PREFS_PROFILE (1 << 2) /* 0x02 */
+#define TRG_PREFS_CONNECTION (1 << 3) /* 0x04 */
+#define TRG_PREFS_NEWNODE (1 << 4) /* 0x08 */
+#define TRG_PREFS_REPLACENODE (1 << 5) /* 0x16 */
+
+enum {
+ TRG_STYLE_TR = 0,
+ TRG_STYLE_CLASSIC,
+ TRG_STYLE_TR_COMPACT
+};
+
+typedef struct _TrgPrefsPrivate TrgPrefsPrivate;
+
+G_BEGIN_DECLS
+#define TRG_TYPE_PREFS trg_prefs_get_type()
+#define TRG_PREFS(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_PREFS, TrgPrefs))
+#define TRG_PREFS_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_PREFS, TrgPrefsClass))
+#define TRG_IS_PREFS(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_PREFS))
+#define TRG_IS_PREFS_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_PREFS))
+#define TRG_PREFS_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_PREFS, TrgPrefsClass))
+ typedef struct {
+ GObject parent;
+ TrgPrefsPrivate *priv;
+} TrgPrefs;
+
+typedef struct {
+ GObjectClass parent_class;
+ void (*pref_changed) (TrgPrefs * tc, const gchar * key, gpointer data);
+} TrgPrefsClass;
+
+GType trg_prefs_get_type(void);
+
+TrgPrefs *trg_prefs_new(void);
+
+void trg_prefs_add_default_int(TrgPrefs * p, const gchar * key, int value);
+void trg_prefs_add_default_string(TrgPrefs * p, const gchar * key,
+ gchar * value);
+void trg_prefs_add_default_double(TrgPrefs * p, const gchar * key,
+ double value);
+void trg_prefs_add_default_bool_true(TrgPrefs * p, const gchar * key);
+
+JsonNode *trg_prefs_get_value(TrgPrefs * p, const gchar * key, int type,
+ int flags);
+gchar *trg_prefs_get_string(TrgPrefs * p, const gchar * key, int flags);
+gint64 trg_prefs_get_int(TrgPrefs * p, const gchar * key, int flags);
+gdouble trg_prefs_get_double(TrgPrefs * p, const gchar * key, int flags);
+gboolean trg_prefs_get_bool(TrgPrefs * p, const gchar * key, int flags);
+JsonObject *trg_prefs_get_profile(TrgPrefs * p);
+JsonObject *trg_prefs_get_connection(TrgPrefs * p);
+JsonArray *trg_prefs_get_profiles(TrgPrefs * p);
+void trg_prefs_set_connection(TrgPrefs * p, JsonObject * profile);
+gint trg_prefs_get_profile_id(TrgPrefs * p);
+void trg_prefs_del_profile(TrgPrefs * p, JsonObject * profile);
+void trg_prefs_set_profile(TrgPrefs * p, JsonObject * profile);
+JsonObject *trg_prefs_new_profile(TrgPrefs * p);
+JsonObject *trg_get_current_profile(TrgPrefs * p);
+JsonObject *trg_prefs_get_root(TrgPrefs * p);
+JsonArray *trg_prefs_get_array(TrgPrefs * p, const gchar * key, int flags);
+
+void trg_prefs_set_int(TrgPrefs * p, const gchar * key, int value,
+ int flags);
+void trg_prefs_set_string(TrgPrefs * p, const gchar * key,
+ const gchar * value, int flags);
+void trg_prefs_set_double(TrgPrefs * p, const gchar * key, double value,
+ int flags);
+void trg_prefs_set_bool(TrgPrefs * p, const gchar * key, gboolean value,
+ int flags);
+
+gboolean trg_prefs_save(TrgPrefs * p);
+void trg_prefs_load(TrgPrefs * p);
+void trg_prefs_changed_emit_signal(TrgPrefs * p, const gchar * key);
+void trg_prefs_profile_change_emit_signal(TrgPrefs * p);
+guint trg_prefs_get_add_flags(TrgPrefs * p);
+
+G_END_DECLS
+#endif /* _TRG_PREFS_H_ */
diff --git a/src/trg-remote-prefs-dialog.c b/src/trg-remote-prefs-dialog.c
new file mode 100644
index 0000000..f8bd43b
--- /dev/null
+++ b/src/trg-remote-prefs-dialog.c
@@ -0,0 +1,776 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <math.h>
+#include <stdint.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-main-window.h"
+#include "trg-remote-prefs-dialog.h"
+#include "hig.h"
+#include "util.h"
+#include "requests.h"
+#include "json.h"
+#include "trg-json-widgets.h"
+#include "session-get.h"
+
+/* Using the widget creation functions in trg-json-widgets.c, load remote
+ * preferences from the latest session object held by TrgClient.
+ * If the user clicks OK, use trg-json-widgets to build up a request object
+ * and send that in a session-set request.
+ *
+ * The on_session_set callback should cause that session object to be refreshed
+ * as soon as the set is complete.
+ */
+
+G_DEFINE_TYPE(TrgRemotePrefsDialog, trg_remote_prefs_dialog,
+ GTK_TYPE_DIALOG)
+#define TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(o) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_REMOTE_PREFS_DIALOG, TrgRemotePrefsDialogPrivate))
+enum {
+ PROP_0, PROP_PARENT, PROP_CLIENT
+};
+
+typedef struct _TrgRemotePrefsDialogPrivate TrgRemotePrefsDialogPrivate;
+
+struct _TrgRemotePrefsDialogPrivate {
+ TrgClient *client;
+ TrgMainWindow *parent;
+
+ GList *widgets;
+ GtkWidget *encryption_combo;
+ GtkWidget *port_test_label, *port_test_button;
+ GtkWidget *blocklist_update_button, *blocklist_check;
+ GtkWidget *alt_check, *alt_time_check;
+};
+
+static GObject *instance = NULL;
+
+static void update_session(GtkDialog * dlg)
+{
+ TrgRemotePrefsDialogPrivate *priv =
+ TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(dlg);
+
+ JsonNode *request = session_set();
+ JsonObject *args = node_get_arguments(request);
+ gchar *encryption;
+
+ /* Connection */
+
+ switch (gtk_combo_box_get_active
+ (GTK_COMBO_BOX(priv->encryption_combo))) {
+ case 0:
+ encryption = "required";
+ break;
+ case 2:
+ encryption = "tolerated";
+ break;
+ default:
+ encryption = "preferred";
+ break;
+ }
+
+ json_object_set_string_member(args, SGET_ENCRYPTION, encryption);
+
+ trg_json_widgets_save(priv->widgets, args);
+
+ dispatch_async(priv->client, request, on_session_set, priv->parent);
+}
+
+static void
+trg_remote_prefs_response_cb(GtkDialog * dlg, gint res_id,
+ gpointer data G_GNUC_UNUSED)
+{
+ TrgRemotePrefsDialogPrivate *priv =
+ TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(dlg);
+
+ if (res_id == GTK_RESPONSE_OK)
+ update_session(dlg);
+
+ trg_json_widget_desc_list_free(priv->widgets);
+
+ gtk_widget_destroy(GTK_WIDGET(dlg));
+ instance = NULL;
+}
+
+static void
+trg_remote_prefs_dialog_get_property(GObject * object,
+ guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ TrgRemotePrefsDialogPrivate *priv =
+ TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_PARENT:
+ g_value_set_object(value, priv->parent);
+ break;
+ case PROP_CLIENT:
+ g_value_set_pointer(value, priv->client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_remote_prefs_dialog_set_property(GObject * object,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgRemotePrefsDialogPrivate *priv =
+ TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_PARENT:
+ priv->parent = g_value_get_object(value);
+ break;
+ case PROP_CLIENT:
+ priv->client = g_value_get_pointer(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_remote_prefs_double_special_dependent(GtkWidget * widget,
+ gpointer data)
+{
+ TrgRemotePrefsDialogPrivate *priv =
+ TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(gtk_widget_get_toplevel
+ (GTK_WIDGET(widget)));
+
+ gtk_widget_set_sensitive(GTK_WIDGET(data),
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
+ (priv->
+ alt_time_check))
+ ||
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
+ (priv->
+ alt_check)));
+}
+
+static void
+trg_rprefs_time_widget_savefunc(GtkWidget * w, JsonObject * obj,
+ gchar * key)
+{
+ GtkWidget *hourSpin = g_object_get_data(G_OBJECT(w), "hours-spin");
+ GtkWidget *minutesSpin = g_object_get_data(G_OBJECT(w), "mins-spin");
+ gdouble hoursValue =
+ gtk_spin_button_get_value(GTK_SPIN_BUTTON(hourSpin));
+ gdouble minutesValue =
+ gtk_spin_button_get_value(GTK_SPIN_BUTTON(minutesSpin));
+
+ json_object_set_int_member(obj, key,
+ (gint64) ((hoursValue * 60.0) +
+ minutesValue));
+
+}
+
+static gboolean on_output(GtkSpinButton * spin, gpointer data)
+{
+ GtkAdjustment *adj;
+ gchar *text;
+ int value;
+ adj = gtk_spin_button_get_adjustment(spin);
+ value = (int) gtk_adjustment_get_value(adj);
+ text = g_strdup_printf("%02d", value);
+ gtk_entry_set_text(GTK_ENTRY(spin), text);
+ g_free(text);
+
+ return TRUE;
+}
+
+static GtkWidget *trg_rprefs_timer_widget_spin_new(gint max,
+ GtkWidget *
+ alt_time_check)
+{
+ GtkWidget *w = gtk_spin_button_new_with_range(0, max, 1);
+ gtk_widget_set_sensitive(w,
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
+ (alt_time_check)));
+ g_signal_connect(G_OBJECT(alt_time_check), "toggled",
+ G_CALLBACK(toggle_active_arg_is_sensitive), w);
+ g_signal_connect(G_OBJECT(w), "output", G_CALLBACK(on_output), NULL);
+ return w;
+}
+
+static GtkWidget *trg_rprefs_time_widget_new(GList ** wl, JsonObject * obj,
+ const gchar * key,
+ GtkWidget * alt_time_check)
+{
+ GtkWidget *hbox = trg_hbox_new(FALSE, 0);
+ GtkWidget *colonLabel = gtk_label_new(":");
+ GtkWidget *hourSpin =
+ trg_rprefs_timer_widget_spin_new(23, alt_time_check);
+ GtkWidget *minutesSpin = trg_rprefs_timer_widget_spin_new(69,
+ alt_time_check);
+ gint64 value = json_object_get_int_member(obj, key);
+
+ trg_json_widget_desc *wd = g_new0(trg_json_widget_desc, 1);
+ wd->key = g_strdup(key);
+ wd->widget = hbox;
+ wd->saveFunc = trg_rprefs_time_widget_savefunc;
+
+ g_object_set_data(G_OBJECT(hbox), "hours-spin", hourSpin);
+ g_object_set_data(G_OBJECT(hbox), "mins-spin", minutesSpin);
+
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(hourSpin),
+ floor((gdouble) value / 60.0));
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(minutesSpin),
+ (gdouble) (value % 60));
+
+ gtk_box_pack_start(GTK_BOX(hbox), hourSpin, TRUE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(hbox), colonLabel, TRUE, FALSE, 2);
+ gtk_box_pack_start(GTK_BOX(hbox), minutesSpin, TRUE, FALSE, 0);
+
+ *wl = g_list_append(*wl, wd);
+
+ return hbox;
+}
+
+static GtkWidget *trg_rprefs_time_begin_end_new(GList ** wl,
+ JsonObject * obj,
+ GtkWidget * alt_time_check)
+{
+ GtkWidget *hbox = trg_hbox_new(FALSE, 0);
+ GtkWidget *begin = trg_rprefs_time_widget_new(wl, obj,
+ SGET_ALT_SPEED_TIME_BEGIN,
+ alt_time_check);
+ GtkWidget *end = trg_rprefs_time_widget_new(wl, obj,
+ SGET_ALT_SPEED_TIME_END,
+ alt_time_check);
+
+ gtk_box_pack_start(GTK_BOX(hbox), begin, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("-"), FALSE, FALSE,
+ 10);
+ gtk_box_pack_start(GTK_BOX(hbox), end, FALSE, FALSE, 0);
+
+ return hbox;
+}
+
+static GtkWidget *trg_rprefs_alt_speed_spin_new(GList ** wl,
+ JsonObject * obj,
+ const gchar * key,
+ GtkWidget * alt_check,
+ GtkWidget * alt_time_check)
+{
+ GtkWidget *w = trg_json_widget_spin_new(wl, obj, key,
+ NULL, 0, INT_MAX, 5);
+ gtk_widget_set_sensitive(w,
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
+ (alt_check))
+ ||
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
+ (alt_time_check)));
+ g_signal_connect(G_OBJECT(alt_time_check), "toggled",
+ G_CALLBACK(trg_remote_prefs_double_special_dependent),
+ w);
+ g_signal_connect(G_OBJECT(alt_check), "toggled",
+ G_CALLBACK(trg_remote_prefs_double_special_dependent),
+ w);
+ return w;
+}
+
+static GtkWidget *trg_rprefs_bandwidthPage(TrgRemotePrefsDialog * win,
+ JsonObject * json)
+{
+ TrgRemotePrefsDialogPrivate *priv =
+ TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(win);
+ GtkWidget *w, *tb, *t;
+ guint row = 0;
+
+ t = hig_workarea_create();
+
+ hig_workarea_add_section_title(t, &row, _("Bandwidth limits"));
+
+ tb = trg_json_widget_check_new(&priv->widgets, json,
+ SGET_SPEED_LIMIT_DOWN_ENABLED,
+ _("Down Limit (KiB/s)"), NULL);
+ w = trg_json_widget_spin_new(&priv->widgets, json,
+ SGET_SPEED_LIMIT_DOWN, tb, 0, INT_MAX, 5);
+ hig_workarea_add_row_w(t, &row, tb, w, NULL);
+
+ tb = trg_json_widget_check_new(&priv->widgets, json,
+ SGET_SPEED_LIMIT_UP_ENABLED,
+ _("Up Limit (KiB/s)"), NULL);
+ w = trg_json_widget_spin_new(&priv->widgets, json, SGET_SPEED_LIMIT_UP,
+ tb, 0, INT_MAX, 5);
+ hig_workarea_add_row_w(t, &row, tb, w, NULL);
+
+ hig_workarea_add_section_title(t, &row, _("Alternate limits"));
+
+ w = priv->alt_check = trg_json_widget_check_new(&priv->widgets, json,
+ SGET_ALT_SPEED_ENABLED,
+ _
+ ("Alternate speed limits active"),
+ NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ tb = priv->alt_time_check =
+ trg_json_widget_check_new(&priv->widgets, json,
+ SGET_ALT_SPEED_TIME_ENABLED,
+ _("Alternate time range"), NULL);
+ w = trg_rprefs_time_begin_end_new(&priv->widgets, json, tb);
+ hig_workarea_add_row_w(t, &row, tb, w, NULL);
+
+ w = trg_rprefs_alt_speed_spin_new(&priv->widgets, json,
+ SGET_ALT_SPEED_DOWN, priv->alt_check,
+ tb);
+ hig_workarea_add_row(t, &row, _("Alternate down limit (KiB/s)"), w, w);
+
+ w = trg_rprefs_alt_speed_spin_new(&priv->widgets, json,
+ SGET_ALT_SPEED_UP, priv->alt_check,
+ tb);
+ hig_workarea_add_row(t, &row, _("Alternate up limit (KiB/s)"), w, w);
+
+ return t;
+}
+
+static GtkWidget *trg_rprefs_limitsPage(TrgRemotePrefsDialog * win,
+ JsonObject * json)
+{
+ TrgRemotePrefsDialogPrivate *priv =
+ TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(win);
+ GtkWidget *w, *tb, *t;
+ guint row = 0;
+
+ t = hig_workarea_create();
+
+ hig_workarea_add_section_title(t, &row, _("Seeding"));
+
+ tb = trg_json_widget_check_new(&priv->widgets, json,
+ SGET_SEED_RATIO_LIMITED,
+ _("Seed ratio limit"), NULL);
+ w = trg_json_widget_spin_new(&priv->widgets, json,
+ SGET_SEED_RATIO_LIMIT, tb, 0, INT_MAX,
+ 0.1);
+ hig_workarea_add_row_w(t, &row, tb, w, NULL);
+
+ if (json_object_has_member(json, SGET_DOWNLOAD_QUEUE_ENABLED)) {
+ hig_workarea_add_section_title(t, &row, _("Queues"));
+
+ tb = trg_json_widget_check_new(&priv->widgets, json,
+ SGET_DOWNLOAD_QUEUE_ENABLED,
+ _("Download queue size"), NULL);
+ w = trg_json_widget_spin_new(&priv->widgets, json,
+ SGET_DOWNLOAD_QUEUE_SIZE, tb, 0,
+ INT_MAX, 1);
+ hig_workarea_add_row_w(t, &row, tb, w, NULL);
+
+ tb = trg_json_widget_check_new(&priv->widgets, json,
+ SGET_SEED_QUEUE_ENABLED,
+ _("Seed queue size"), NULL);
+ w = trg_json_widget_spin_new(&priv->widgets, json,
+ SGET_SEED_QUEUE_SIZE, tb, 0, INT_MAX,
+ 1);
+ hig_workarea_add_row_w(t, &row, tb, w, NULL);
+
+ tb = trg_json_widget_check_new(&priv->widgets, json,
+ SGET_QUEUE_STALLED_ENABLED,
+ _("Ignore stalled (minutes)"),
+ NULL);
+ w = trg_json_widget_spin_new(&priv->widgets, json,
+ SGET_QUEUE_STALLED_MINUTES, tb, 0,
+ INT_MAX, 1);
+ hig_workarea_add_row_w(t, &row, tb, w, NULL);
+ }
+
+ hig_workarea_add_section_title(t, &row, _("Peers"));
+
+ w = trg_json_widget_spin_new(&priv->widgets, json,
+ SGET_PEER_LIMIT_GLOBAL, NULL, 0, INT_MAX,
+ 5);
+ hig_workarea_add_row(t, &row, _("Global peer limit"), w, w);
+
+ w = trg_json_widget_spin_new(&priv->widgets, json,
+ SGET_PEER_LIMIT_PER_TORRENT, NULL, 0,
+ INT_MAX, 5);
+ hig_workarea_add_row(t, &row, _("Per torrent peer limit"), w, w);
+
+ return t;
+}
+
+static gboolean on_port_tested(gpointer data)
+{
+ trg_response *response = (trg_response *) data;
+ if (TRG_IS_REMOTE_PREFS_DIALOG(response->cb_data)) {
+ TrgRemotePrefsDialogPrivate *priv =
+ TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(response->cb_data);
+
+ gtk_button_set_label(GTK_BUTTON(priv->port_test_button),
+ _("Retest"));
+ gtk_widget_set_sensitive(priv->port_test_button, TRUE);
+
+ if (response->status == CURLE_OK) {
+ gboolean isOpen =
+ json_object_get_boolean_member(get_arguments
+ (response->obj),
+ "port-is-open");
+ if (isOpen)
+ gtk_label_set_markup(GTK_LABEL(priv->port_test_label),
+ _
+ ("Port is <span font_weight=\"bold\" fgcolor=\"darkgreen\">open</span>"));
+ else
+ gtk_label_set_markup(GTK_LABEL(priv->port_test_label),
+ _
+ ("Port is <span font_weight=\"bold\" fgcolor=\"red\">closed</span>"));
+ } else {
+ trg_error_dialog(GTK_WINDOW(data), response);
+ }
+ }
+
+ trg_response_free(response);
+ return FALSE;
+}
+
+static void port_test_cb(GtkButton * b, gpointer data)
+{
+ TrgRemotePrefsDialogPrivate *priv =
+ TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(data);
+ JsonNode *req = port_test();
+
+ gtk_label_set_text(GTK_LABEL(priv->port_test_label), _("Port test"));
+ gtk_button_set_label(b, _("Testing..."));
+ gtk_widget_set_sensitive(GTK_WIDGET(b), FALSE);
+
+ dispatch_async(priv->client, req, on_port_tested, data);
+}
+
+static gboolean on_blocklist_updated(gpointer data)
+{
+ trg_response *response = (trg_response *) data;
+ if (TRG_IS_REMOTE_PREFS_DIALOG(response->cb_data)) {
+ TrgRemotePrefsDialogPrivate *priv =
+ TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(response->cb_data);
+
+ gtk_widget_set_sensitive(priv->blocklist_update_button, TRUE);
+ gtk_button_set_label(GTK_BUTTON(priv->blocklist_update_button),
+ _("Update"));
+
+ if (response->status == CURLE_OK) {
+ JsonObject *args = get_arguments(response->obj);
+ gchar *labelText =
+ g_strdup_printf(_("Blocklist (%ld entries)"),
+ json_object_get_int_member(args,
+ SGET_BLOCKLIST_SIZE));
+ gtk_button_set_label(GTK_BUTTON(priv->blocklist_check),
+ labelText);
+ g_free(labelText);
+ } else {
+ trg_error_dialog(GTK_WINDOW(response->cb_data), response);
+ }
+ }
+
+ trg_response_free(response);
+
+ return FALSE;
+}
+
+static gboolean update_blocklist_cb(GtkButton * b, gpointer data)
+{
+ TrgRemotePrefsDialogPrivate *priv =
+ TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(data);
+ JsonNode *req = blocklist_update();
+
+ gtk_widget_set_sensitive(GTK_WIDGET(b), FALSE);
+ gtk_button_set_label(b, _("Updating..."));
+
+ dispatch_async(priv->client, req, on_blocklist_updated, data);
+
+ return FALSE;
+}
+
+static GtkWidget *trg_rprefs_connPage(TrgRemotePrefsDialog * win,
+ JsonObject * s)
+{
+ TrgRemotePrefsDialogPrivate *priv =
+ TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(win);
+
+ GtkWidget *w, *tb, *t;
+ const gchar *stringValue;
+ guint row = 0;
+
+ t = hig_workarea_create();
+
+ hig_workarea_add_section_title(t, &row, _("Connections"));
+
+ w = trg_json_widget_spin_new(&priv->widgets, s, SGET_PEER_PORT, NULL,
+ 0, 65535, 1);
+ hig_workarea_add_row(t, &row, _("Peer port"), w, w);
+
+ priv->port_test_label = gtk_label_new(_("Port test"));
+ w = priv->port_test_button = gtk_button_new_with_label(_("Test"));
+ g_signal_connect(w, "clicked", G_CALLBACK(port_test_cb), win);
+ hig_workarea_add_row_w(t, &row, priv->port_test_label, w, NULL);
+
+ w = priv->encryption_combo = gtr_combo_box_new_enum(_("Required"), 0,
+ _("Preferred"), 1,
+ _("Tolerated"), 2,
+ NULL);
+ stringValue = session_get_encryption(s);
+ if (!g_strcmp0(stringValue, "required")) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(w), 0);
+ } else if (!g_strcmp0(stringValue, "tolerated")) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(w), 2);
+ } else {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(w), 1);
+ }
+
+ hig_workarea_add_row(t, &row, _("Encryption"), w, NULL);
+
+ w = trg_json_widget_check_new(&priv->widgets, s,
+ SGET_PEER_PORT_RANDOM_ON_START,
+ _("Random peer port on start"), NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ w = trg_json_widget_check_new(&priv->widgets, s,
+ SGET_PORT_FORWARDING_ENABLED,
+ _("Peer port forwarding"), NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ hig_workarea_add_section_title(t, &row, _("Protocol"));
+
+ w = trg_json_widget_check_new(&priv->widgets, s, SGET_PEX_ENABLED,
+ _("Peer exchange (PEX)"), NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ w = trg_json_widget_check_new(&priv->widgets, s, SGET_DHT_ENABLED,
+ _("Distributed Hash Table (DHT)"), NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ w = trg_json_widget_check_new(&priv->widgets, s, SGET_LPD_ENABLED,
+ _("Local peer discovery"), NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ hig_workarea_add_section_title(t, &row, _("Blocklist"));
+
+ stringValue = g_strdup_printf(_("Blocklist (%ld entries)"),
+ session_get_blocklist_size(s));
+ tb = priv->blocklist_check =
+ trg_json_widget_check_new(&priv->widgets, s,
+ SGET_BLOCKLIST_ENABLED, stringValue,
+ NULL);
+ g_free((gchar *) stringValue);
+
+ w = priv->blocklist_update_button =
+ gtk_button_new_with_label(_("Update"));
+ g_signal_connect(G_OBJECT(w), "clicked",
+ G_CALLBACK(update_blocklist_cb), win);
+ hig_workarea_add_row_w(t, &row, tb, w, NULL);
+
+ stringValue = session_get_blocklist_url(s);
+ if (stringValue) {
+ w = trg_json_widget_entry_new(&priv->widgets, s,
+ SGET_BLOCKLIST_URL, NULL);
+ hig_workarea_add_row(t, &row, _("Blocklist URL:"), w, NULL);
+ }
+
+ return t;
+}
+
+static GtkWidget *trg_rprefs_generalPage(TrgRemotePrefsDialog * win,
+ JsonObject * s)
+{
+ TrgRemotePrefsDialogPrivate *priv =
+ TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(win);
+
+ GtkWidget *w, *tb, *t;
+ guint row = 0;
+ gint64 cache_size_mb;
+
+ t = hig_workarea_create();
+
+ hig_workarea_add_section_title(t, &row, _("Environment"));
+
+ w = trg_json_widget_entry_new(&priv->widgets, s, SGET_DOWNLOAD_DIR,
+ NULL);
+ hig_workarea_add_row(t, &row, _("Download directory"), w, NULL);
+
+ tb = trg_json_widget_check_new(&priv->widgets, s,
+ SGET_INCOMPLETE_DIR_ENABLED,
+ _("Incomplete download dir"), NULL);
+ w = trg_json_widget_entry_new(&priv->widgets, s, SGET_INCOMPLETE_DIR,
+ tb);
+ hig_workarea_add_row_w(t, &row, tb, w, NULL);
+
+ tb = trg_json_widget_check_new(&priv->widgets, s,
+ SGET_SCRIPT_TORRENT_DONE_ENABLED,
+ _("Torrent done script"), NULL);
+ w = trg_json_widget_entry_new(&priv->widgets, s,
+ SGET_SCRIPT_TORRENT_DONE_FILENAME, tb);
+ hig_workarea_add_row_w(t, &row, tb, w, NULL);
+
+ cache_size_mb = session_get_cache_size_mb(s);
+ if (cache_size_mb >= 0) {
+ w = trg_json_widget_spin_new(&priv->widgets, s, SGET_CACHE_SIZE_MB,
+ NULL, 0, INT_MAX, 1);
+ hig_workarea_add_row(t, &row, _("Cache size (MiB)"), w, w);
+ }
+
+ hig_workarea_add_section_title(t, &row, _("Behavior"));
+
+ w = trg_json_widget_check_new(&priv->widgets, s,
+ SGET_RENAME_PARTIAL_FILES,
+ _("Rename partial files"), NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ w = trg_json_widget_check_new(&priv->widgets, s,
+ SGET_TRASH_ORIGINAL_TORRENT_FILES, _
+ ("Trash original torrent files"), NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ w = trg_json_widget_check_new(&priv->widgets, s,
+ SGET_START_ADDED_TORRENTS,
+ _("Start added torrents"), NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ return t;
+}
+
+static GObject *trg_remote_prefs_dialog_constructor(GType type,
+ guint
+ n_construct_properties,
+ GObjectConstructParam *
+ construct_params)
+{
+ GObject *object;
+ TrgRemotePrefsDialogPrivate *priv;
+ JsonObject *session;
+ GtkWidget *notebook, *contentvbox;
+
+ object = G_OBJECT_CLASS
+ (trg_remote_prefs_dialog_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+ priv = TRG_REMOTE_PREFS_DIALOG_GET_PRIVATE(object);
+ session = trg_client_get_session(priv->client);
+
+ contentvbox = gtk_dialog_get_content_area(GTK_DIALOG(object));
+
+ gtk_window_set_title(GTK_WINDOW(object), _("Remote Preferences"));
+ gtk_window_set_transient_for(GTK_WINDOW(object),
+ GTK_WINDOW(priv->parent));
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(object), TRUE);
+
+ gtk_dialog_add_button(GTK_DIALOG(object), GTK_STOCK_CLOSE,
+ GTK_RESPONSE_CLOSE);
+ gtk_dialog_add_button(GTK_DIALOG(object), GTK_STOCK_OK,
+ GTK_RESPONSE_OK);
+
+ gtk_container_set_border_width(GTK_CONTAINER(object), GUI_PAD);
+
+ gtk_dialog_set_default_response(GTK_DIALOG(object), GTK_RESPONSE_OK);
+
+ g_signal_connect(G_OBJECT(object), "response",
+ G_CALLBACK(trg_remote_prefs_response_cb), NULL);
+
+ notebook = gtk_notebook_new();
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ trg_rprefs_generalPage(TRG_REMOTE_PREFS_DIALOG
+ (object), session),
+ gtk_label_new(_("General")));
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ trg_rprefs_connPage(TRG_REMOTE_PREFS_DIALOG
+ (object), session),
+ gtk_label_new(_("Connections")));
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ trg_rprefs_bandwidthPage
+ (TRG_REMOTE_PREFS_DIALOG(object), session),
+ gtk_label_new(_("Bandwidth")));
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ trg_rprefs_limitsPage(TRG_REMOTE_PREFS_DIALOG
+ (object), session),
+ gtk_label_new(_("Limits")));
+
+ gtk_container_set_border_width(GTK_CONTAINER(notebook), GUI_PAD);
+
+ gtk_box_pack_start(GTK_BOX(contentvbox), notebook, TRUE, TRUE, 0);
+
+ return object;
+}
+
+static void
+trg_remote_prefs_dialog_class_init(TrgRemotePrefsDialogClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgRemotePrefsDialogPrivate));
+
+ object_class->constructor = trg_remote_prefs_dialog_constructor;
+ object_class->get_property = trg_remote_prefs_dialog_get_property;
+ object_class->set_property = trg_remote_prefs_dialog_set_property;
+
+ g_object_class_install_property(object_class,
+ PROP_CLIENT,
+ g_param_spec_pointer("trg-client",
+ "TClient",
+ "Client",
+ G_PARAM_READWRITE
+ |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_PARENT,
+ g_param_spec_object("parent-window",
+ "Parent window",
+ "Parent window",
+ TRG_TYPE_MAIN_WINDOW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+}
+
+static void
+trg_remote_prefs_dialog_init(TrgRemotePrefsDialog * self G_GNUC_UNUSED)
+{
+}
+
+TrgRemotePrefsDialog *trg_remote_prefs_dialog_get_instance(TrgMainWindow *
+ parent,
+ TrgClient *
+ client)
+{
+ if (instance == NULL) {
+ instance =
+ g_object_new(TRG_TYPE_REMOTE_PREFS_DIALOG, "parent-window",
+ parent, "trg-client", client, NULL);
+ }
+
+ return TRG_REMOTE_PREFS_DIALOG(instance);
+}
diff --git a/src/trg-remote-prefs-dialog.h b/src/trg-remote-prefs-dialog.h
new file mode 100644
index 0000000..45d0a97
--- /dev/null
+++ b/src/trg-remote-prefs-dialog.h
@@ -0,0 +1,56 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_REMOTE_PREFS_DIALOG_H_
+#define TRG_REMOTE_PREFS_DIALOG_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "trg-main-window.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_REMOTE_PREFS_DIALOG trg_remote_prefs_dialog_get_type()
+#define TRG_REMOTE_PREFS_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_REMOTE_PREFS_DIALOG, TrgRemotePrefsDialog))
+#define TRG_REMOTE_PREFS_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_REMOTE_PREFS_DIALOG, TrgRemotePrefsDialogClass))
+#define TRG_IS_REMOTE_PREFS_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_REMOTE_PREFS_DIALOG))
+#define TRG_IS_REMOTE_PREFS_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_REMOTE_PREFS_DIALOG))
+#define TRG_REMOTE_PREFS_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_REMOTE_PREFS_DIALOG, TrgRemotePrefsDialogClass))
+ typedef struct {
+ GtkDialog parent;
+} TrgRemotePrefsDialog;
+
+typedef struct {
+ GtkDialogClass parent_class;
+} TrgRemotePrefsDialogClass;
+
+GType trg_remote_prefs_dialog_get_type(void);
+
+TrgRemotePrefsDialog *trg_remote_prefs_dialog_get_instance(TrgMainWindow *
+ parent,
+ TrgClient *
+ client);
+
+G_END_DECLS
+#endif /* TRG_REMOTE_PREFS_DIALOG_H_ */
diff --git a/src/trg-sortable-filtered-model.c b/src/trg-sortable-filtered-model.c
new file mode 100644
index 0000000..9db1827
--- /dev/null
+++ b/src/trg-sortable-filtered-model.c
@@ -0,0 +1,202 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <gtk/gtk.h>
+
+#include "trg-sortable-filtered-model.h"
+
+/* This class extends GtkTreeModelFilter, so it can implement the
+ * GtkTreeSortable interface. All of the sortable functions are passed on to
+ * a child GtkTreeModelSort. Also proxy the sort-column-changed signal
+ * so that a TreeViewColumn can track changes for indicators.
+ */
+
+static void
+trg_sortable_filtered_model_tree_sortable_init(GtkTreeSortableIface *
+ iface);
+
+/* TreeSortable interface */
+static GtkTreeSortable
+ * trg_sortable_filtered_model_get_real_sortable(GtkTreeSortable *
+ sortable);
+static gboolean
+trg_sortable_filtered_model_sort_get_sort_column_id(GtkTreeSortable *
+ sortable,
+ gint * sort_column_id,
+ GtkSortType * order);
+static void
+trg_sortable_filtered_model_sort_set_sort_column_id(GtkTreeSortable *
+ sortable,
+ gint sort_column_id,
+ GtkSortType order);
+static void trg_sortable_filtered_model_sort_set_sort_func(GtkTreeSortable
+ * sortable,
+ gint
+ sort_column_id,
+ GtkTreeIterCompareFunc
+ func,
+ gpointer data,
+ GDestroyNotify
+ destroy);
+static void
+trg_sortable_filtered_model_sort_set_default_sort_func(GtkTreeSortable *
+ sortable,
+ GtkTreeIterCompareFunc
+ func, gpointer data,
+ GDestroyNotify
+ destroy);
+static gboolean
+trg_sortable_filtered_model_sort_has_default_sort_func(GtkTreeSortable *
+ sortable);
+static void trg_sortable_filtered_model_sort_column_changed(GtkTreeSortable
+ * realSortable,
+ GtkTreeSortable
+ *
+ fakeSortable);
+
+G_DEFINE_TYPE_WITH_CODE(TrgSortableFilteredModel,
+ trg_sortable_filtered_model,
+ GTK_TYPE_TREE_MODEL_FILTER,
+ G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_SORTABLE,
+ trg_sortable_filtered_model_tree_sortable_init))
+static void
+trg_sortable_filtered_model_class_init(TrgSortableFilteredModelClass *
+ klass)
+{
+}
+
+static void
+trg_sortable_filtered_model_init(TrgSortableFilteredModel * self)
+{
+}
+
+static void
+trg_sortable_filtered_model_tree_sortable_init(GtkTreeSortableIface *
+ iface)
+{
+ iface->get_sort_column_id =
+ trg_sortable_filtered_model_sort_get_sort_column_id;
+ iface->set_sort_column_id =
+ trg_sortable_filtered_model_sort_set_sort_column_id;
+ iface->set_sort_func = trg_sortable_filtered_model_sort_set_sort_func;
+ iface->set_default_sort_func =
+ trg_sortable_filtered_model_sort_set_default_sort_func;
+ iface->has_default_sort_func =
+ trg_sortable_filtered_model_sort_has_default_sort_func;
+}
+
+static void
+trg_sortable_filtered_model_sort_column_changed(GtkTreeSortable
+ *
+ realSortable
+ G_GNUC_UNUSED,
+ GtkTreeSortable
+ * fakeSortable)
+{
+ g_signal_emit_by_name(fakeSortable, "sort-column-changed");
+}
+
+GtkTreeModel *trg_sortable_filtered_model_new(GtkTreeSortable *
+ child_model,
+ GtkTreePath * root)
+{
+ GObject *obj = g_object_new(TRG_TYPE_SORTABLE_FILTERED_MODEL,
+ "child-model", GTK_TREE_MODEL(child_model),
+ "virtual-root", root,
+ NULL);
+
+ g_signal_connect(child_model, "sort-column-changed",
+ G_CALLBACK
+ (trg_sortable_filtered_model_sort_column_changed),
+ obj);
+
+ return GTK_TREE_MODEL(obj);
+}
+
+static GtkTreeSortable
+ * trg_sortable_filtered_model_get_real_sortable(GtkTreeSortable *
+ sortable)
+{
+ return
+ GTK_TREE_SORTABLE(gtk_tree_model_filter_get_model
+ (GTK_TREE_MODEL_FILTER(sortable)));
+}
+
+static gboolean
+trg_sortable_filtered_model_sort_get_sort_column_id(GtkTreeSortable *
+ sortable,
+ gint * sort_column_id,
+ GtkSortType * order)
+{
+ GtkTreeSortable *realSortable =
+ trg_sortable_filtered_model_get_real_sortable(sortable);
+ return gtk_tree_sortable_get_sort_column_id(realSortable,
+ sort_column_id, order);
+}
+
+static void
+trg_sortable_filtered_model_sort_set_sort_column_id(GtkTreeSortable *
+ sortable,
+ gint sort_column_id,
+ GtkSortType order)
+{
+ GtkTreeSortable *realSortable =
+ trg_sortable_filtered_model_get_real_sortable(sortable);
+ gtk_tree_sortable_set_sort_column_id(realSortable, sort_column_id,
+ order);
+}
+
+static void
+trg_sortable_filtered_model_sort_set_sort_func(GtkTreeSortable
+ * sortable,
+ gint
+ sort_column_id,
+ GtkTreeIterCompareFunc
+ func,
+ gpointer data,
+ GDestroyNotify destroy)
+{
+ GtkTreeSortable *realSortable =
+ trg_sortable_filtered_model_get_real_sortable(sortable);
+ gtk_tree_sortable_set_sort_func(realSortable, sort_column_id, func,
+ data, destroy);
+}
+
+static void
+trg_sortable_filtered_model_sort_set_default_sort_func(GtkTreeSortable *
+ sortable,
+ GtkTreeIterCompareFunc
+ func, gpointer data,
+ GDestroyNotify
+ destroy)
+{
+ GtkTreeSortable *realSortable =
+ trg_sortable_filtered_model_get_real_sortable(sortable);
+ gtk_tree_sortable_set_default_sort_func(realSortable, func, data,
+ destroy);
+}
+
+static gboolean
+trg_sortable_filtered_model_sort_has_default_sort_func(GtkTreeSortable *
+ sortable)
+{
+ GtkTreeSortable *realSortable =
+ trg_sortable_filtered_model_get_real_sortable(sortable);
+ return gtk_tree_sortable_has_default_sort_func(realSortable);
+}
diff --git a/src/trg-sortable-filtered-model.h b/src/trg-sortable-filtered-model.h
new file mode 100644
index 0000000..3523d09
--- /dev/null
+++ b/src/trg-sortable-filtered-model.h
@@ -0,0 +1,53 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _TRG_SORTABLE_FILTERED_MODEL
+#define _TRG_SORTABLE_FILTERED_MODEL
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define TRG_TYPE_SORTABLE_FILTERED_MODEL trg_sortable_filtered_model_get_type()
+#define TRG_SORTABLE_FILTERED_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_SORTABLE_FILTERED_MODEL, TrgSortableFilteredModel))
+#define TRG_SORTABLE_FILTERED_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_SORTABLE_FILTERED_MODEL, TrgSortableFilteredModelClass))
+#define TRG_IS_SORTABLE_FILTERED_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_SORTABLE_FILTERED_MODEL))
+#define TRG_IS_SORTABLE_FILTERED_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_SORTABLE_FILTERED_MODEL))
+#define TRG_SORTABLE_FILTERED_MODEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_SORTABLE_FILTERED_MODEL, TrgSortableFilteredModelClass))
+ typedef struct {
+ GtkTreeModelFilter parent;
+} TrgSortableFilteredModel;
+
+typedef struct {
+ GtkTreeModelFilterClass parent_class;
+} TrgSortableFilteredModelClass;
+
+GType trg_sortable_filtered_model_get_type(void);
+
+GtkTreeModel *trg_sortable_filtered_model_new(GtkTreeSortable *
+ child_model,
+ GtkTreePath * root);
+
+G_END_DECLS
+#endif /* _TRG_SORTABLE_FILTERED_MODEL */
diff --git a/src/trg-state-selector.c b/src/trg-state-selector.c
new file mode 100644
index 0000000..cee0c56
--- /dev/null
+++ b/src/trg-state-selector.c
@@ -0,0 +1,815 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib-object.h>
+#include <json-glib/json-glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "torrent.h"
+#include "trg-cell-renderer-counter.h"
+#include "trg-state-selector.h"
+#include "trg-torrent-model.h"
+#include "util.h"
+#include "trg-prefs.h"
+#include "trg-client.h"
+
+enum {
+ SELECTOR_STATE_CHANGED, SELECTOR_SIGNAL_COUNT
+};
+
+enum {
+ PROP_0, PROP_CLIENT
+};
+
+static guint signals[SELECTOR_SIGNAL_COUNT] = { 0 };
+
+G_DEFINE_TYPE(TrgStateSelector, trg_state_selector, GTK_TYPE_TREE_VIEW)
+#define TRG_STATE_SELECTOR_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_STATE_SELECTOR, TrgStateSelectorPrivate))
+typedef struct _TrgStateSelectorPrivate TrgStateSelectorPrivate;
+
+struct _TrgStateSelectorPrivate {
+ guint flag;
+ gboolean showDirs;
+ gboolean showTrackers;
+ TrgClient *client;
+ TrgPrefs *prefs;
+ GHashTable *trackers;
+ GHashTable *directories;
+ GRegex *urlHostRegex;
+ gint n_categories;
+ GtkListStore *store;
+ GtkTreeRowReference *error_rr;
+ GtkTreeRowReference *all_rr;
+ GtkTreeRowReference *paused_rr;
+ GtkTreeRowReference *down_rr;
+ GtkTreeRowReference *seeding_rr;
+ GtkTreeRowReference *complete_rr;
+ GtkTreeRowReference *incomplete_rr;
+ GtkTreeRowReference *checking_rr;
+ GtkTreeRowReference *active_rr;
+ GtkTreeRowReference *seed_wait_rr;
+ GtkTreeRowReference *down_wait_rr;
+};
+
+GRegex *trg_state_selector_get_url_host_regex(TrgStateSelector * s)
+{
+ TrgStateSelectorPrivate *priv = TRG_STATE_SELECTOR_GET_PRIVATE(s);
+ return priv->urlHostRegex;
+}
+
+guint32 trg_state_selector_get_flag(TrgStateSelector * s)
+{
+ TrgStateSelectorPrivate *priv = TRG_STATE_SELECTOR_GET_PRIVATE(s);
+ return priv->flag;
+}
+
+static void
+state_selection_changed(GtkTreeSelection * selection, gpointer data)
+{
+ TrgStateSelectorPrivate *priv;
+ GtkTreeIter iter;
+ GtkTreeModel *stateModel;
+ guint index = 0;
+
+ priv = TRG_STATE_SELECTOR_GET_PRIVATE(data);
+
+ if (gtk_tree_selection_get_selected(selection, &stateModel, &iter))
+ gtk_tree_model_get(stateModel, &iter, STATE_SELECTOR_BIT,
+ &priv->flag, STATE_SELECTOR_INDEX, &index, -1);
+ else
+ priv->flag = 0;
+
+ trg_prefs_set_int(priv->prefs, TRG_PREFS_STATE_SELECTOR_LAST, index,
+ TRG_PREFS_GLOBAL);
+
+ g_signal_emit(TRG_STATE_SELECTOR(data),
+ signals[SELECTOR_STATE_CHANGED], 0, priv->flag);
+}
+
+static GtkTreeRowReference *quick_tree_ref_new(GtkTreeModel * model,
+ GtkTreeIter * iter)
+{
+ GtkTreePath *path = gtk_tree_model_get_path(model, iter);
+ GtkTreeRowReference *rr = gtk_tree_row_reference_new(model, path);
+ gtk_tree_path_free(path);
+ return rr;
+}
+
+struct cruft_remove_args {
+ GHashTable *table;
+ gint64 serial;
+};
+
+static gboolean
+trg_state_selector_remove_cruft(gpointer key, gpointer value,
+ gpointer data)
+{
+ struct cruft_remove_args *args = (struct cruft_remove_args *) data;
+ GtkTreeRowReference *rr = (GtkTreeRowReference *) value;
+ GtkTreeModel *model = gtk_tree_row_reference_get_model(rr);
+ GtkTreePath *path = gtk_tree_row_reference_get_path(rr);
+ gboolean remove;
+
+ GtkTreeIter iter;
+ gint64 currentSerial;
+
+ gtk_tree_model_get_iter(model, &iter, path);
+ gtk_tree_model_get(model, &iter, STATE_SELECTOR_SERIAL, &currentSerial,
+ -1);
+
+ remove = (args->serial != currentSerial);
+
+ gtk_tree_path_free(path);
+
+ return remove;
+}
+
+gchar *trg_state_selector_get_selected_text(TrgStateSelector * s)
+{
+ GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(s));
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *name = NULL;
+
+ if (gtk_tree_selection_get_selected(sel, &model, &iter))
+ gtk_tree_model_get(model, &iter, STATE_SELECTOR_NAME, &name, -1);
+
+ return name;
+}
+
+static void
+trg_state_selector_update_dynamic_filter(GtkTreeModel * model,
+ GtkTreeRowReference *
+ rr, gint64 serial)
+{
+ GtkTreeIter iter;
+ GtkTreePath *path = gtk_tree_row_reference_get_path(rr);
+ gint64 oldSerial;
+ GValue gvalue = { 0 };
+ gint oldCount;
+
+ gtk_tree_model_get_iter(model, &iter, path);
+ gtk_tree_model_get(model, &iter, STATE_SELECTOR_SERIAL, &oldSerial,
+ STATE_SELECTOR_COUNT, &oldCount, -1);
+
+ if (oldSerial != serial) {
+ g_value_init(&gvalue, G_TYPE_INT);
+ g_value_set_int(&gvalue, 1);
+ gtk_list_store_set_value(GTK_LIST_STORE(model), &iter,
+ STATE_SELECTOR_COUNT, &gvalue);
+
+ memset(&gvalue, 0, sizeof(GValue));
+ g_value_init(&gvalue, G_TYPE_INT64);
+ g_value_set_int64(&gvalue, serial);
+ gtk_list_store_set_value(GTK_LIST_STORE(model), &iter,
+ STATE_SELECTOR_SERIAL, &gvalue);
+ } else {
+ g_value_init(&gvalue, G_TYPE_INT);
+ g_value_set_int(&gvalue, ++oldCount);
+ gtk_list_store_set_value(GTK_LIST_STORE(model), &iter,
+ STATE_SELECTOR_COUNT, &gvalue);
+ }
+
+ gtk_tree_path_free(path);
+}
+
+static void refresh_statelist_cb(GtkWidget * w, gpointer data)
+{
+ TrgStateSelectorPrivate *priv = TRG_STATE_SELECTOR_GET_PRIVATE(data);
+ trg_client_inc_serial(priv->client);
+ trg_state_selector_update(TRG_STATE_SELECTOR(data),
+ TORRENT_UPDATE_ADDREMOVE);
+}
+
+static void
+view_popup_menu(GtkWidget * treeview, GdkEventButton * event,
+ gpointer data G_GNUC_UNUSED)
+{
+ GtkWidget *menu, *item;
+
+ menu = gtk_menu_new();
+
+ item = gtk_image_menu_item_new_with_label(GTK_STOCK_REFRESH);
+ gtk_image_menu_item_set_use_stock(GTK_IMAGE_MENU_ITEM(item), TRUE);
+ gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM
+ (item), TRUE);
+ g_signal_connect(item, "activate", G_CALLBACK(refresh_statelist_cb),
+ treeview);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+ gtk_widget_show_all(menu);
+
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
+ (event != NULL) ? event->button : 0,
+ gdk_event_get_time((GdkEvent *) event));
+}
+
+static gboolean view_onPopupMenu(GtkWidget * treeview, gpointer userdata)
+{
+ view_popup_menu(treeview, NULL, userdata);
+ return TRUE;
+}
+
+static gboolean
+view_onButtonPressed(GtkWidget * treeview,
+ GdkEventButton * event, gpointer userdata)
+{
+ if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
+ view_popup_menu(treeview, event, userdata);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+struct state_find_pos {
+ int offset;
+ int range;
+ int pos;
+ const gchar *name;
+};
+
+static gboolean
+trg_state_selector_find_pos_foreach(GtkTreeModel * model,
+ GtkTreePath * path,
+ GtkTreeIter * iter, gpointer data)
+{
+ struct state_find_pos *args = (struct state_find_pos *) data;
+ gchar *name;
+ gboolean res;
+
+ if (args->pos < args->offset) {
+ args->pos++;
+ return FALSE;
+ } else if (args->range >= 0
+ && args->pos > args->offset + args->range - 1) {
+ return TRUE;
+ }
+
+ gtk_tree_model_get(model, iter, STATE_SELECTOR_NAME, &name, -1);
+ res = g_strcmp0(name, args->name) >= 0;
+ g_free(name);
+
+ if (!res)
+ args->pos++;
+
+ return res;
+}
+
+static void
+trg_state_selector_insert(TrgStateSelector * s, int offset,
+ gint range, const gchar * name,
+ GtkTreeIter * iter)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(s));
+
+ struct state_find_pos args;
+ args.offset = offset;
+ args.pos = 0;
+ args.range = range;
+ args.name = name;
+
+ gtk_tree_model_foreach(model, trg_state_selector_find_pos_foreach,
+ &args);
+ gtk_list_store_insert(GTK_LIST_STORE(model), iter, args.pos);
+}
+
+void trg_state_selector_update(TrgStateSelector * s, guint whatsChanged)
+{
+ TrgStateSelectorPrivate *priv = TRG_STATE_SELECTOR_GET_PRIVATE(s);
+ GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(s));
+ TrgClient *client = priv->client;
+ gint64 updateSerial = trg_client_get_serial(client);
+ GList *torrentItemRefs;
+ GtkTreeIter torrentIter, iter;
+ GList *trackersList, *trackerItem, *li;
+ GtkTreeRowReference *rr;
+ GtkTreePath *path;
+ GtkTreeModel *torrentModel;
+ gpointer result;
+ struct cruft_remove_args cruft;
+
+ if (!trg_client_is_connected(client))
+ return;
+
+ torrentItemRefs =
+ g_hash_table_get_values(trg_client_get_torrent_table(client));
+
+ for (li = torrentItemRefs; li; li = g_list_next(li)) {
+ JsonObject *t = NULL;
+ rr = (GtkTreeRowReference *) li->data;
+ path = gtk_tree_row_reference_get_path(rr);
+ torrentModel = gtk_tree_row_reference_get_model(rr);
+
+ if (path) {
+ if (gtk_tree_model_get_iter(torrentModel, &torrentIter, path)) {
+ gtk_tree_model_get(torrentModel, &torrentIter,
+ TORRENT_COLUMN_JSON, &t, -1);
+ }
+ gtk_tree_path_free(path);
+ }
+
+ if (!t)
+ continue;
+
+ if (priv->showTrackers
+ && (whatsChanged & TORRENT_UPDATE_ADDREMOVE)) {
+ trackersList =
+ json_array_get_elements(torrent_get_tracker_stats(t));
+ for (trackerItem = trackersList; trackerItem;
+ trackerItem = g_list_next(trackerItem)) {
+ JsonObject *tracker =
+ json_node_get_object((JsonNode *) trackerItem->data);
+ const gchar *announceUrl =
+ tracker_stats_get_announce(tracker);
+ gchar *announceHost =
+ trg_gregex_get_first(priv->urlHostRegex,
+ announceUrl);
+
+ if (!announceHost)
+ continue;
+
+ result = g_hash_table_lookup(priv->trackers, announceHost);
+
+ if (result) {
+ trg_state_selector_update_dynamic_filter(model,
+ (GtkTreeRowReference
+ *) result,
+ updateSerial);
+ g_free(announceHost);
+ } else {
+ trg_state_selector_insert(s, priv->n_categories,
+ g_hash_table_size
+ (priv->trackers),
+ announceHost, &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter,
+ STATE_SELECTOR_ICON,
+ GTK_STOCK_NETWORK,
+ STATE_SELECTOR_NAME, announceHost,
+ STATE_SELECTOR_SERIAL, updateSerial,
+ STATE_SELECTOR_COUNT, 1,
+ STATE_SELECTOR_BIT,
+ FILTER_FLAG_TRACKER,
+ STATE_SELECTOR_INDEX, 0, -1);
+ g_hash_table_insert(priv->trackers, announceHost,
+ quick_tree_ref_new(model, &iter));
+ }
+ }
+ g_list_free(trackersList);
+ }
+
+ if (priv->showDirs && ((whatsChanged & TORRENT_UPDATE_ADDREMOVE)
+ || (whatsChanged &
+ TORRENT_UPDATE_PATH_CHANGE))) {
+ gchar *dir;
+ gtk_tree_model_get(torrentModel, &torrentIter,
+ TORRENT_COLUMN_DOWNLOADDIR_SHORT, &dir, -1);
+
+ result = g_hash_table_lookup(priv->directories, dir);
+ if (result) {
+ trg_state_selector_update_dynamic_filter(model,
+ (GtkTreeRowReference
+ *) result,
+ updateSerial);
+ } else {
+ trg_state_selector_insert(s,
+ priv->n_categories +
+ g_hash_table_size
+ (priv->trackers), -1, dir,
+ &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter,
+ STATE_SELECTOR_ICON,
+ GTK_STOCK_DIRECTORY,
+ STATE_SELECTOR_NAME, dir,
+ STATE_SELECTOR_SERIAL, updateSerial,
+ STATE_SELECTOR_BIT, FILTER_FLAG_DIR,
+ STATE_SELECTOR_COUNT, 1,
+ STATE_SELECTOR_INDEX, 0, -1);
+ g_hash_table_insert(priv->directories, g_strdup(dir),
+ quick_tree_ref_new(model, &iter));
+ }
+
+ g_free(dir);
+ }
+ }
+
+ g_list_free(torrentItemRefs);
+
+ cruft.serial = trg_client_get_serial(client);
+
+ if (priv->showTrackers && ((whatsChanged & TORRENT_UPDATE_ADDREMOVE))) {
+ cruft.table = priv->trackers;
+ g_hash_table_foreach_remove(priv->trackers,
+ trg_state_selector_remove_cruft,
+ &cruft);
+ }
+
+ if (priv->showDirs && ((whatsChanged & TORRENT_UPDATE_ADDREMOVE)
+ || (whatsChanged & TORRENT_UPDATE_PATH_CHANGE))) {
+ cruft.table = priv->directories;
+ g_hash_table_foreach_remove(priv->directories,
+ trg_state_selector_remove_cruft,
+ &cruft);
+ }
+}
+
+void trg_state_selector_set_show_dirs(TrgStateSelector * s, gboolean show)
+{
+ TrgStateSelectorPrivate *priv = TRG_STATE_SELECTOR_GET_PRIVATE(s);
+ priv->showDirs = show;
+ if (!show)
+ g_hash_table_remove_all(priv->directories);
+ else
+ trg_state_selector_update(s, TORRENT_UPDATE_PATH_CHANGE);
+}
+
+static void
+on_torrents_state_change(TrgTorrentModel * model,
+ guint whatsChanged, gpointer data)
+{
+ TrgStateSelector *selector = TRG_STATE_SELECTOR(data);
+ trg_state_selector_update(selector, whatsChanged);
+
+ if ((whatsChanged & TORRENT_UPDATE_ADDREMOVE)
+ || (whatsChanged & TORRENT_UPDATE_STATE_CHANGE))
+ trg_state_selector_stats_update(selector,
+ trg_torrent_model_get_stats
+ (model));
+}
+
+void
+trg_state_selector_set_show_trackers(TrgStateSelector * s, gboolean show)
+{
+ TrgStateSelectorPrivate *priv = TRG_STATE_SELECTOR_GET_PRIVATE(s);
+ priv->showTrackers = show;
+ if (!show)
+ g_hash_table_remove_all(priv->trackers);
+ else
+ trg_state_selector_update(s, TORRENT_UPDATE_ADDREMOVE);
+}
+
+static void
+trg_state_selector_add_state(TrgStateSelector * selector,
+ GtkTreeIter * iter, gint pos,
+ gchar * icon, gchar * name,
+ guint32 flag, GtkTreeRowReference ** rr)
+{
+ TrgStateSelectorPrivate *priv =
+ TRG_STATE_SELECTOR_GET_PRIVATE(selector);
+ GtkListStore *model =
+ GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(selector)));
+
+ if (pos < 0)
+ gtk_list_store_append(priv->store, iter);
+ else
+ gtk_list_store_insert(priv->store, iter, pos);
+
+ gtk_list_store_set(model, iter, STATE_SELECTOR_ICON, icon,
+ STATE_SELECTOR_NAME, name, STATE_SELECTOR_BIT, flag,
+ STATE_SELECTOR_INDEX,
+ gtk_tree_model_iter_n_children(GTK_TREE_MODEL
+ (model), NULL) - 1,
+ -1);
+
+ if (rr)
+ *rr = quick_tree_ref_new(GTK_TREE_MODEL(model), iter);
+
+ priv->n_categories++;
+}
+
+static void remove_row_ref_and_free(GtkTreeRowReference * rr)
+{
+ GtkTreeModel *model = gtk_tree_row_reference_get_model(rr);
+ GtkTreePath *path = gtk_tree_row_reference_get_path(rr);
+ GtkTreeIter iter;
+
+ gtk_tree_model_get_iter(model, &iter, path);
+ gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
+ gtk_tree_path_free(path);
+ gtk_tree_row_reference_free(rr);
+}
+
+static void
+trg_state_selector_update_stat(GtkTreeRowReference * rr, gint count)
+{
+ if (rr) {
+ GValue gvalue = { 0 };
+ GtkTreeIter iter;
+ GtkTreePath *path = gtk_tree_row_reference_get_path(rr);
+ GtkTreeModel *model = gtk_tree_row_reference_get_model(rr);
+
+ gtk_tree_model_get_iter(model, &iter, path);
+
+ g_value_init(&gvalue, G_TYPE_INT);
+ g_value_set_int(&gvalue, count);
+ gtk_list_store_set_value(GTK_LIST_STORE(model), &iter,
+ STATE_SELECTOR_COUNT, &gvalue);
+
+ gtk_tree_path_free(path);
+ }
+}
+
+void
+trg_state_selector_stats_update(TrgStateSelector * s,
+ trg_torrent_model_update_stats * stats)
+{
+ TrgStateSelectorPrivate *priv = TRG_STATE_SELECTOR_GET_PRIVATE(s);
+ GtkTreeIter iter;
+ if (stats->error > 0 && !priv->error_rr) {
+ trg_state_selector_add_state(s, &iter, priv->n_categories - 1,
+ GTK_STOCK_DIALOG_WARNING, _("Error"),
+ TORRENT_FLAG_ERROR, &priv->error_rr);
+
+ } else if (stats->error < 1 && priv->error_rr) {
+ remove_row_ref_and_free(priv->error_rr);
+ priv->error_rr = NULL;
+ priv->n_categories--;
+ }
+
+ trg_state_selector_update_stat(priv->all_rr, stats->count);
+ trg_state_selector_update_stat(priv->down_rr, stats->down);
+ trg_state_selector_update_stat(priv->seeding_rr, stats->seeding);
+ trg_state_selector_update_stat(priv->error_rr, stats->error);
+ trg_state_selector_update_stat(priv->paused_rr, stats->paused);
+ trg_state_selector_update_stat(priv->complete_rr, stats->complete);
+ trg_state_selector_update_stat(priv->incomplete_rr, stats->incomplete);
+ trg_state_selector_update_stat(priv->active_rr, stats->active);
+ trg_state_selector_update_stat(priv->checking_rr, stats->checking);
+ trg_state_selector_update_stat(priv->down_wait_rr, stats->down_wait);
+ trg_state_selector_update_stat(priv->seed_wait_rr, stats->seed_wait);
+}
+
+void trg_state_selector_disconnect(TrgStateSelector * s)
+{
+ TrgStateSelectorPrivate *priv = TRG_STATE_SELECTOR_GET_PRIVATE(s);
+
+ if (priv->error_rr) {
+ remove_row_ref_and_free(priv->error_rr);
+ priv->error_rr = NULL;
+ priv->n_categories--;
+ }
+
+ g_hash_table_remove_all(priv->trackers);
+ g_hash_table_remove_all(priv->directories);
+
+ trg_state_selector_update_stat(priv->all_rr, -1);
+ trg_state_selector_update_stat(priv->down_rr, -1);
+ trg_state_selector_update_stat(priv->seeding_rr, -1);
+ trg_state_selector_update_stat(priv->error_rr, -1);
+ trg_state_selector_update_stat(priv->paused_rr, -1);
+ trg_state_selector_update_stat(priv->complete_rr, -1);
+ trg_state_selector_update_stat(priv->incomplete_rr, -1);
+ trg_state_selector_update_stat(priv->active_rr, -1);
+ trg_state_selector_update_stat(priv->checking_rr, -1);
+}
+
+static void trg_state_selector_init(TrgStateSelector * self)
+{
+}
+
+TrgStateSelector *trg_state_selector_new(TrgClient * client,
+ TrgTorrentModel * tmodel)
+{
+ TrgStateSelector *selector =
+ g_object_new(TRG_TYPE_STATE_SELECTOR, "client",
+ client, NULL);
+ g_signal_connect(tmodel, "torrents-state-change",
+ G_CALLBACK(on_torrents_state_change), selector);
+ return selector;
+}
+
+static GObject *trg_state_selector_constructor(GType type,
+ guint
+ n_construct_properties,
+ GObjectConstructParam *
+ construct_params)
+{
+ GObject *object;
+ TrgStateSelector *selector;
+ TrgStateSelectorPrivate *priv;
+ GtkListStore *store;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ GtkTreeIter iter;
+ gint index;
+ GtkTreeSelection *selection;
+
+ object = G_OBJECT_CLASS
+ (trg_state_selector_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+
+ selector = TRG_STATE_SELECTOR(object);
+ priv = TRG_STATE_SELECTOR_GET_PRIVATE(object);
+
+ priv->urlHostRegex = trg_uri_host_regex_new();
+ priv->trackers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify)
+ remove_row_ref_and_free);
+ priv->directories =
+ g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify) remove_row_ref_and_free);
+
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(object), FALSE);
+
+ column = gtk_tree_view_column_new();
+
+ renderer = gtk_cell_renderer_pixbuf_new();
+ gtk_tree_view_column_pack_start(column, renderer, FALSE);
+ g_object_set(renderer, "stock-size", 4, NULL);
+ gtk_tree_view_column_set_attributes(column, renderer, "stock-id",
+ STATE_SELECTOR_ICON, NULL);
+
+ renderer = trg_cell_renderer_counter_new();
+ gtk_tree_view_column_pack_start(column, renderer, TRUE);
+ gtk_tree_view_column_set_attributes(column, renderer, "state-label",
+ STATE_SELECTOR_NAME, "state-count",
+ STATE_SELECTOR_COUNT, NULL);
+
+ gtk_tree_view_append_column(GTK_TREE_VIEW(object), column);
+
+ store = priv->store = gtk_list_store_new(STATE_SELECTOR_COLUMNS,
+ G_TYPE_STRING, G_TYPE_STRING,
+ G_TYPE_INT, G_TYPE_UINT,
+ G_TYPE_INT64, G_TYPE_UINT);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(object), GTK_TREE_MODEL(store));
+
+ trg_state_selector_add_state(selector, &iter, -1, GTK_STOCK_ABOUT,
+ _("All"), 0, &priv->all_rr);
+ trg_state_selector_add_state(selector, &iter, -1, GTK_STOCK_GO_DOWN,
+ _("Downloading"),
+ TORRENT_FLAG_DOWNLOADING, &priv->down_rr);
+ trg_state_selector_add_state(selector, &iter, -1,
+ GTK_STOCK_MEDIA_REWIND, _("Queue Down"),
+ TORRENT_FLAG_DOWNLOADING_WAIT,
+ &priv->down_wait_rr);
+ trg_state_selector_add_state(selector, &iter, -1, GTK_STOCK_GO_UP,
+ _("Seeding"), TORRENT_FLAG_SEEDING,
+ &priv->seeding_rr);
+ trg_state_selector_add_state(selector, &iter, -1,
+ GTK_STOCK_MEDIA_FORWARD, _("Queue Up"),
+ TORRENT_FLAG_SEEDING_WAIT,
+ &priv->seed_wait_rr);
+ trg_state_selector_add_state(selector, &iter, -1,
+ GTK_STOCK_MEDIA_PAUSE, _("Paused"),
+ TORRENT_FLAG_PAUSED, &priv->paused_rr);
+ trg_state_selector_add_state(selector, &iter, -1, GTK_STOCK_APPLY,
+ _("Complete"), TORRENT_FLAG_COMPLETE,
+ &priv->complete_rr);
+ trg_state_selector_add_state(selector, &iter, -1, GTK_STOCK_SELECT_ALL,
+ _("Incomplete"), TORRENT_FLAG_INCOMPLETE,
+ &priv->incomplete_rr);
+ trg_state_selector_add_state(selector, &iter, -1, GTK_STOCK_NETWORK,
+ _("Active"), TORRENT_FLAG_ACTIVE,
+ &priv->active_rr);
+ trg_state_selector_add_state(selector, &iter, -1, GTK_STOCK_REFRESH,
+ _("Checking"), TORRENT_FLAG_CHECKING_ANY,
+ &priv->checking_rr);
+ trg_state_selector_add_state(selector, &iter, -1, NULL, NULL, 0, NULL);
+
+ gtk_tree_view_set_rubber_banding(GTK_TREE_VIEW(object), TRUE);
+
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(object));
+
+ g_signal_connect(G_OBJECT(selection), "changed",
+ G_CALLBACK(state_selection_changed), object);
+ g_signal_connect(object, "button-press-event",
+ G_CALLBACK(view_onButtonPressed), NULL);
+ g_signal_connect(object, "popup-menu", G_CALLBACK(view_onPopupMenu),
+ NULL);
+
+ gtk_tree_view_set_search_column(GTK_TREE_VIEW(object),
+ STATE_SELECTOR_NAME);
+
+ index = trg_prefs_get_int(priv->prefs, TRG_PREFS_STATE_SELECTOR_LAST,
+ TRG_PREFS_GLOBAL);
+ if (index > 0
+ && gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(store), &iter,
+ NULL, index)) {
+ GtkTreeSelection *selection =
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(object));
+ gtk_tree_selection_select_iter(selection, &iter);
+ }
+
+ priv->showDirs =
+ trg_prefs_get_bool(priv->prefs, TRG_PREFS_KEY_FILTER_DIRS,
+ TRG_PREFS_GLOBAL);
+ priv->showTrackers =
+ trg_prefs_get_bool(priv->prefs, TRG_PREFS_KEY_FILTER_TRACKERS,
+ TRG_PREFS_GLOBAL);
+
+ return object;
+}
+
+void
+trg_state_selector_set_queues_enabled(TrgStateSelector * s,
+ gboolean enabled)
+{
+ TrgStateSelectorPrivate *priv = TRG_STATE_SELECTOR_GET_PRIVATE(s);
+ GtkTreeIter iter;
+
+ if (enabled) {
+ trg_state_selector_add_state(s, &iter, 2, GTK_STOCK_MEDIA_REWIND,
+ _("Queue Down"),
+ TORRENT_FLAG_DOWNLOADING_WAIT,
+ &priv->down_wait_rr);
+ trg_state_selector_add_state(s, &iter, 4, GTK_STOCK_MEDIA_FORWARD,
+ _("Queue Up"),
+ TORRENT_FLAG_SEEDING_WAIT,
+ &priv->seed_wait_rr);
+ } else {
+ remove_row_ref_and_free(priv->seed_wait_rr);
+ remove_row_ref_and_free(priv->down_wait_rr);
+ priv->down_wait_rr = NULL;
+ priv->seed_wait_rr = NULL;
+ priv->n_categories -= 2;
+ }
+}
+
+static void
+trg_state_selector_get_property(GObject * object,
+ guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ TrgStateSelectorPrivate *priv = TRG_STATE_SELECTOR_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_CLIENT:
+ g_value_set_object(value, priv->client);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_state_selector_set_property(GObject * object,
+ guint prop_id,
+ const GValue * value,
+ GParamSpec * pspec G_GNUC_UNUSED)
+{
+ TrgStateSelectorPrivate *priv = TRG_STATE_SELECTOR_GET_PRIVATE(object);
+
+ switch (prop_id) {
+ case PROP_CLIENT:
+ priv->client = g_value_get_object(value);
+ priv->prefs = trg_client_get_prefs(priv->client);
+ break;
+ }
+}
+
+static void trg_state_selector_class_init(TrgStateSelectorClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ object_class->constructor = trg_state_selector_constructor;
+ object_class->set_property = trg_state_selector_set_property;
+ object_class->get_property = trg_state_selector_get_property;
+
+ signals[SELECTOR_STATE_CHANGED] = g_signal_new("torrent-state-changed",
+ G_TYPE_FROM_CLASS
+ (object_class),
+ G_SIGNAL_RUN_LAST |
+ G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET
+ (TrgStateSelectorClass,
+ torrent_state_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1,
+ G_TYPE_UINT);
+
+ g_object_class_install_property(object_class,
+ PROP_CLIENT,
+ g_param_spec_object("client",
+ "Client",
+ "Client",
+ TRG_TYPE_CLIENT,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_type_class_add_private(klass, sizeof(TrgStateSelectorPrivate));
+}
diff --git a/src/trg-state-selector.h b/src/trg-state-selector.h
new file mode 100644
index 0000000..5107505
--- /dev/null
+++ b/src/trg-state-selector.h
@@ -0,0 +1,81 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_STATE_LIST_H_
+#define TRG_STATE_LIST_H_
+
+#include <glib-object.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-torrent-model.h"
+#include "trg-client.h"
+
+enum {
+ STATE_SELECTOR_ICON,
+ STATE_SELECTOR_NAME,
+ STATE_SELECTOR_COUNT,
+ STATE_SELECTOR_BIT,
+ STATE_SELECTOR_SERIAL,
+ STATE_SELECTOR_INDEX,
+ STATE_SELECTOR_COLUMNS
+};
+
+G_BEGIN_DECLS
+#define TRG_TYPE_STATE_SELECTOR trg_state_selector_get_type()
+#define TRG_STATE_SELECTOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_STATE_SELECTOR, TrgStateSelector))
+#define TRG_STATE_SELECTOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_STATE_SELECTOR, TrgStateSelectorClass))
+#define TRG_IS_STATE_SELECTOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_STATE_SELECTOR))
+#define TRG_IS_STATE_SELECTOR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_STATE_SELECTOR))
+#define TRG_STATE_SELECTOR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_STATE_SELECTOR, TrgStateSelectorClass))
+ typedef struct {
+ GtkTreeView parent;
+} TrgStateSelector;
+
+typedef struct {
+ GtkTreeViewClass parent_class;
+
+ void (*torrent_state_changed) (TrgStateSelector * selector,
+ guint flag, gpointer data);
+
+} TrgStateSelectorClass;
+
+GType trg_state_selector_get_type(void);
+TrgStateSelector *trg_state_selector_new(TrgClient * client,
+ TrgTorrentModel * tmodel);
+
+G_END_DECLS guint32 trg_state_selector_get_flag(TrgStateSelector * s);
+void trg_state_selector_update(TrgStateSelector * s, guint whatsChanged);
+gchar *trg_state_selector_get_selected_text(TrgStateSelector * s);
+GRegex *trg_state_selector_get_url_host_regex(TrgStateSelector * s);
+void trg_state_selector_disconnect(TrgStateSelector * s);
+void trg_state_selector_set_show_trackers(TrgStateSelector * s,
+ gboolean show);
+void trg_state_selector_set_show_dirs(TrgStateSelector * s, gboolean show);
+void trg_state_selector_set_queues_enabled(TrgStateSelector * s,
+ gboolean enabled);
+void trg_state_selector_stats_update(TrgStateSelector * s,
+ trg_torrent_model_update_stats *
+ stats);
+
+#endif /* TRG_STATE_LIST_H_ */
diff --git a/src/trg-stats-dialog.c b/src/trg-stats-dialog.c
new file mode 100644
index 0000000..3ae0336
--- /dev/null
+++ b/src/trg-stats-dialog.c
@@ -0,0 +1,376 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+#include <curl/curl.h>
+
+#include "hig.h"
+#include "requests.h"
+#include "json.h"
+#include "util.h"
+#include "trg-client.h"
+#include "trg-stats-dialog.h"
+#include "trg-main-window.h"
+#include "trg-tree-view.h"
+
+enum {
+ STATCOL_STAT,
+ STATCOL_SESSION,
+ STATCOL_CUMULAT,
+ STATCOL_COLUMNS
+};
+
+enum {
+ PROP_0,
+ PROP_PARENT,
+ PROP_CLIENT
+};
+
+#define STATS_UPDATE_INTERVAL 5
+
+G_DEFINE_TYPE(TrgStatsDialog, trg_stats_dialog, GTK_TYPE_DIALOG)
+#define TRG_STATS_DIALOG_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_STATS_DIALOG, TrgStatsDialogPrivate))
+typedef struct _TrgStatsDialogPrivate TrgStatsDialogPrivate;
+
+struct _TrgStatsDialogPrivate {
+ TrgClient *client;
+ TrgMainWindow *parent;
+ GtkWidget *tv;
+ GtkListStore *model;
+ GtkTreeRowReference *rr_up, *rr_down, *rr_files_added,
+ *rr_session_count, *rr_active, *rr_version;
+};
+
+static GObject *instance = NULL;
+static gboolean trg_update_stats_timerfunc(gpointer data);
+static gboolean on_stats_reply(gpointer data);
+
+static void
+trg_stats_dialog_get_property(GObject * object, guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+}
+
+static void
+trg_stats_dialog_set_property(GObject * object, guint property_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ TrgStatsDialogPrivate *priv = TRG_STATS_DIALOG_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_CLIENT:
+ priv->client = g_value_get_pointer(value);
+ break;
+ case PROP_PARENT:
+ priv->parent = g_value_get_object(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_stats_response_cb(GtkDialog * dlg, gint res_id,
+ gpointer data G_GNUC_UNUSED)
+{
+ gtk_widget_destroy(GTK_WIDGET(dlg));
+ instance = NULL;
+}
+
+static GtkTreeRowReference *stats_dialog_add_statistic(GtkListStore *
+ model, gchar * name)
+{
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ GtkTreeRowReference *rr;
+
+ gtk_list_store_append(model, &iter);
+ gtk_list_store_set(model, &iter, STATCOL_STAT, name, -1);
+ path = gtk_tree_model_get_path(GTK_TREE_MODEL(model), &iter);
+ rr = gtk_tree_row_reference_new(GTK_TREE_MODEL(model), path);
+
+ gtk_tree_path_free(path);
+
+ return rr;
+}
+
+static void
+update_statistic(GtkTreeRowReference * rr, gchar * session,
+ gchar * cumulat)
+{
+ GtkTreePath *path = gtk_tree_row_reference_get_path(rr);
+ GtkTreeModel *model = gtk_tree_row_reference_get_model(rr);
+ GtkTreeIter iter;
+
+ gtk_tree_model_get_iter(model, &iter, path);
+
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, STATCOL_SESSION,
+ session, STATCOL_CUMULAT, cumulat, -1);
+
+ gtk_tree_path_free(path);
+}
+
+static JsonObject *get_session_arg(JsonObject * args)
+{
+ return json_object_get_object_member(args, "current-stats");
+}
+
+static JsonObject *get_cumulat_arg(JsonObject * args)
+{
+ return json_object_get_object_member(args, "cumulative-stats");
+}
+
+static void
+update_int_stat(JsonObject * args, GtkTreeRowReference * rr,
+ gchar * jsonKey)
+{
+ gchar session_val[32];
+ gchar cumulat_val[32];
+
+ g_snprintf(session_val, sizeof(session_val), "%" G_GINT64_FORMAT,
+ json_object_get_int_member(get_session_arg(args), jsonKey));
+ g_snprintf(cumulat_val, sizeof(cumulat_val), "%" G_GINT64_FORMAT,
+ json_object_get_int_member(get_cumulat_arg(args), jsonKey));
+
+ update_statistic(rr, session_val, cumulat_val);
+}
+
+static void
+update_size_stat(JsonObject * args, GtkTreeRowReference * rr,
+ gchar * jsonKey)
+{
+ gchar session_val[32];
+ gchar cumulat_val[32];
+
+ trg_strlsize(cumulat_val,
+ json_object_get_int_member(get_cumulat_arg(args),
+ jsonKey));
+ trg_strlsize(session_val,
+ json_object_get_int_member(get_session_arg(args),
+ jsonKey));
+
+ update_statistic(rr, session_val, cumulat_val);
+}
+
+static void
+update_time_stat(JsonObject * args, GtkTreeRowReference * rr,
+ gchar * jsonKey)
+{
+ gchar session_val[32];
+ gchar cumulat_val[32];
+
+ tr_strltime_long(session_val,
+ json_object_get_int_member(get_session_arg(args),
+ jsonKey),
+ sizeof(session_val));
+ tr_strltime_long(cumulat_val,
+ json_object_get_int_member(get_cumulat_arg(args),
+ jsonKey),
+ sizeof(cumulat_val));
+
+ update_statistic(rr, session_val, cumulat_val);
+}
+
+static gboolean on_stats_reply(gpointer data)
+{
+ trg_response *response = (trg_response *) data;
+ TrgStatsDialogPrivate *priv;
+ JsonObject *args;
+ char versionStr[32];
+
+ if (!TRG_IS_STATS_DIALOG(response->cb_data)) {
+ trg_response_free(response);
+ return FALSE;
+ }
+
+ priv = TRG_STATS_DIALOG_GET_PRIVATE(response->cb_data);
+
+ if (response->status == CURLE_OK) {
+ args = get_arguments(response->obj);
+
+ g_snprintf(versionStr, sizeof(versionStr), "Transmission %s",
+ trg_client_get_version_string(priv->client));
+ update_statistic(priv->rr_version, versionStr, "");
+
+ update_size_stat(args, priv->rr_up, "uploadedBytes");
+ update_size_stat(args, priv->rr_down, "downloadedBytes");
+ update_int_stat(args, priv->rr_files_added, "filesAdded");
+ update_int_stat(args, priv->rr_session_count, "sessionCount");
+ update_time_stat(args, priv->rr_active, "secondsActive");
+
+ if (trg_client_is_connected(priv->client))
+ g_timeout_add_seconds(STATS_UPDATE_INTERVAL,
+ trg_update_stats_timerfunc,
+ response->cb_data);
+ } else {
+ trg_error_dialog(GTK_WINDOW(data), response);
+ }
+
+ trg_response_free(response);
+ return FALSE;
+}
+
+static gboolean trg_update_stats_timerfunc(gpointer data)
+{
+ TrgStatsDialogPrivate *priv;
+
+ if (TRG_IS_STATS_DIALOG(data)) {
+ priv = TRG_STATS_DIALOG_GET_PRIVATE(data);
+ if (trg_client_is_connected(priv->client))
+ dispatch_async(priv->client, session_stats(), on_stats_reply,
+ data);
+ }
+
+ return FALSE;
+}
+
+static void
+trg_stats_add_column(GtkTreeView * tv, gint index, gchar * title,
+ gint width)
+{
+ GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
+ GtkTreeViewColumn *column =
+ gtk_tree_view_column_new_with_attributes(title, renderer,
+ "text", index, NULL);
+ gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
+ gtk_tree_view_column_set_fixed_width(column, width);
+
+ gtk_tree_view_append_column(tv, column);
+}
+
+static GObject *trg_stats_dialog_constructor(GType type,
+ guint
+ n_construct_properties,
+ GObjectConstructParam *
+ construct_params)
+{
+ GtkWidget *tv;
+
+ GObject *obj = G_OBJECT_CLASS
+ (trg_stats_dialog_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+ TrgStatsDialogPrivate *priv = TRG_STATS_DIALOG_GET_PRIVATE(obj);
+
+ gtk_window_set_title(GTK_WINDOW(obj), _("Statistics"));
+ gtk_window_set_transient_for(GTK_WINDOW(obj),
+ GTK_WINDOW(priv->parent));
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(obj), TRUE);
+ gtk_dialog_add_button(GTK_DIALOG(obj), GTK_STOCK_CLOSE,
+ GTK_RESPONSE_CLOSE);
+
+ gtk_container_set_border_width(GTK_CONTAINER(obj), GUI_PAD);
+
+ gtk_dialog_set_default_response(GTK_DIALOG(obj), GTK_RESPONSE_CLOSE);
+
+ g_signal_connect(G_OBJECT(obj),
+ "response", G_CALLBACK(trg_stats_response_cb), NULL);
+
+ priv->model =
+ gtk_list_store_new(STATCOL_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ priv->rr_version =
+ stats_dialog_add_statistic(priv->model, _("Version"));
+ priv->rr_down =
+ stats_dialog_add_statistic(priv->model, _("Download Total"));
+ priv->rr_up =
+ stats_dialog_add_statistic(priv->model, _("Upload Total"));
+ priv->rr_files_added =
+ stats_dialog_add_statistic(priv->model, _("Files Added"));
+ priv->rr_session_count =
+ stats_dialog_add_statistic(priv->model, _("Session Count"));
+ priv->rr_active =
+ stats_dialog_add_statistic(priv->model, _("Time Active"));
+
+ tv = priv->tv = trg_tree_view_new();
+ gtk_widget_set_sensitive(tv, TRUE);
+
+ trg_stats_add_column(GTK_TREE_VIEW(tv), STATCOL_STAT, _("Statistic"),
+ 200);
+ trg_stats_add_column(GTK_TREE_VIEW(tv), STATCOL_SESSION, _("Session"),
+ 200);
+ trg_stats_add_column(GTK_TREE_VIEW(tv), STATCOL_CUMULAT,
+ _("Cumulative"), 200);
+
+ gtk_tree_view_set_model(GTK_TREE_VIEW(tv),
+ GTK_TREE_MODEL(priv->model));
+
+ gtk_container_set_border_width(GTK_CONTAINER(tv), GUI_PAD);
+ gtk_box_pack_start(GTK_BOX(gtk_bin_get_child(GTK_BIN(obj))), tv, TRUE,
+ TRUE, 0);
+
+ dispatch_async(priv->client, session_stats(), on_stats_reply, obj);
+
+ return obj;
+}
+
+static void trg_stats_dialog_class_init(TrgStatsDialogClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgStatsDialogPrivate));
+
+ object_class->get_property = trg_stats_dialog_get_property;
+ object_class->set_property = trg_stats_dialog_set_property;
+ object_class->constructor = trg_stats_dialog_constructor;
+
+ g_object_class_install_property(object_class,
+ PROP_PARENT,
+ g_param_spec_object
+ ("parent-window", "Parent window",
+ "Parent window",
+ TRG_TYPE_MAIN_WINDOW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_CLIENT,
+ g_param_spec_pointer
+ ("trg-client", "TClient",
+ "Client",
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+}
+
+static void trg_stats_dialog_init(TrgStatsDialog * self)
+{
+}
+
+TrgStatsDialog *trg_stats_dialog_get_instance(TrgMainWindow * parent,
+ TrgClient * client)
+{
+ if (instance == NULL) {
+ instance = g_object_new(TRG_TYPE_STATS_DIALOG,
+ "trg-client", client,
+ "parent-window", parent, NULL);
+ }
+
+ return TRG_STATS_DIALOG(instance);
+}
diff --git a/src/trg-stats-dialog.h b/src/trg-stats-dialog.h
new file mode 100644
index 0000000..58a192a
--- /dev/null
+++ b/src/trg-stats-dialog.h
@@ -0,0 +1,56 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_STATS_DIALOG_H_
+#define TRG_STATS_DIALOG_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "trg-client.h"
+#include "trg-main-window.h"
+#include "trg-tree-view.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_STATS_DIALOG trg_stats_dialog_get_type()
+#define TRG_STATS_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_STATS_DIALOG, TrgStatsDialog))
+#define TRG_STATS_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_STATS_DIALOG, TrgStatsDialogClass))
+#define TRG_IS_STATS_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_STATS_DIALOG))
+#define TRG_IS_STATS_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_STATS_DIALOG))
+#define TRG_STATS_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_STATS_DIALOG, TrgStatsDialogClass))
+ typedef struct {
+ GtkDialog parent;
+} TrgStatsDialog;
+
+typedef struct {
+ GtkDialogClass parent_class;
+} TrgStatsDialogClass;
+
+GType trg_stats_dialog_get_type(void);
+
+TrgStatsDialog *trg_stats_dialog_get_instance(TrgMainWindow * parent,
+ TrgClient * client);
+
+G_END_DECLS
+#endif /* TRG_STATS_DIALOG_H_ */
diff --git a/src/trg-status-bar.c b/src/trg-status-bar.c
new file mode 100644
index 0000000..34454df
--- /dev/null
+++ b/src/trg-status-bar.c
@@ -0,0 +1,269 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+#include <gtk/gtk.h>
+
+#include "trg-prefs.h"
+#include "trg-main-window.h"
+#include "trg-status-bar.h"
+#include "trg-torrent-model.h"
+#include "session-get.h"
+#include "requests.h"
+#include "json.h"
+#include "util.h"
+
+/* A subclass of GtkHBox which contains a status label on the left.
+ * Free space indicator on left-right.
+ * Speed (including limits if in use) label on right-right.
+ *
+ * Status and speed labels should be updated on every torrent-get using
+ * trg_status_bar_update. Free space is updated with trg_status_bar_session_update.
+ *
+ * There's a signal in TrgClient for session updates, connected into the
+ * main window, which calls this. Session updates happen every 10 torrent-get updates.
+ */
+
+G_DEFINE_TYPE(TrgStatusBar, trg_status_bar, GTK_TYPE_HBOX)
+#define TRG_STATUS_BAR_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_STATUS_BAR, TrgStatusBarPrivate))
+typedef struct _TrgStatusBarPrivate TrgStatusBarPrivate;
+
+struct _TrgStatusBarPrivate {
+ GtkWidget *speed_lbl;
+ GtkWidget *turtleImage, *turtleEventBox;
+ GtkWidget *free_lbl;
+ GtkWidget *info_lbl;
+ TrgClient *client;
+ TrgMainWindow *win;
+};
+
+static void trg_status_bar_class_init(TrgStatusBarClass * klass)
+{
+ g_type_class_add_private(klass, sizeof(TrgStatusBarPrivate));
+}
+
+void trg_status_bar_clear_indicators(TrgStatusBar * sb)
+{
+ TrgStatusBarPrivate *priv = TRG_STATUS_BAR_GET_PRIVATE(sb);
+ gtk_label_set_text(GTK_LABEL(priv->free_lbl), "");
+ gtk_label_set_text(GTK_LABEL(priv->speed_lbl), "");
+}
+
+void trg_status_bar_reset(TrgStatusBar * sb)
+{
+ TrgStatusBarPrivate *priv = TRG_STATUS_BAR_GET_PRIVATE(sb);
+ trg_status_bar_clear_indicators(sb);
+ gtk_label_set_text(GTK_LABEL(priv->info_lbl), _("Disconnected"));
+ gtk_widget_set_visible(priv->turtleEventBox, FALSE);
+}
+
+static void
+turtle_toggle(GtkWidget * w, GdkEventButton * event, gpointer data)
+{
+ TrgStatusBarPrivate *priv = TRG_STATUS_BAR_GET_PRIVATE(data);
+ JsonNode *req = session_set();
+ JsonObject *args = node_get_arguments(req);
+ gchar *stockName;
+ gboolean altSpeedOn;
+
+ gtk_image_get_stock(GTK_IMAGE(priv->turtleImage), &stockName, NULL);
+ altSpeedOn = g_strcmp0(stockName, "alt-speed-on") == 0;
+
+ gtk_image_set_from_stock(GTK_IMAGE(priv->turtleImage),
+ altSpeedOn ? "alt-speed-off" : "alt-speed-on",
+ GTK_ICON_SIZE_SMALL_TOOLBAR);
+ json_object_set_boolean_member(args, SGET_ALT_SPEED_ENABLED,
+ !altSpeedOn);
+
+ dispatch_async(priv->client, req, on_session_set, priv->win);
+}
+
+static void trg_status_bar_init(TrgStatusBar * self)
+{
+ TrgStatusBarPrivate *priv = TRG_STATUS_BAR_GET_PRIVATE(self);
+ gtk_container_set_border_width(GTK_CONTAINER(self), 2);
+
+ priv->info_lbl = gtk_label_new(_("Disconnected"));
+ gtk_box_pack_start(GTK_BOX(self), priv->info_lbl, FALSE, TRUE, 0);
+
+ priv->turtleImage = gtk_image_new();
+
+ priv->turtleEventBox = gtk_event_box_new();
+ g_signal_connect(priv->turtleEventBox, "button-press-event",
+ G_CALLBACK(turtle_toggle), self);
+ gtk_widget_set_visible(priv->turtleEventBox, FALSE);
+ gtk_container_add(GTK_CONTAINER(priv->turtleEventBox),
+ priv->turtleImage);
+ gtk_box_pack_end(GTK_BOX(self), priv->turtleEventBox, FALSE, TRUE, 5);
+
+ priv->speed_lbl = gtk_label_new(NULL);
+ gtk_box_pack_end(GTK_BOX(self), priv->speed_lbl, FALSE, TRUE, 10);
+
+ priv->free_lbl = gtk_label_new(NULL);
+ gtk_box_pack_end(GTK_BOX(self), priv->free_lbl, FALSE, TRUE, 30);
+}
+
+void
+trg_status_bar_push_connection_msg(TrgStatusBar * sb, const gchar * msg)
+{
+ TrgStatusBarPrivate *priv = TRG_STATUS_BAR_GET_PRIVATE(sb);
+ gtk_label_set_text(GTK_LABEL(priv->info_lbl), msg);
+}
+
+static void
+trg_status_bar_set_connected_label(TrgStatusBar * sb, JsonObject * session,
+ TrgClient * client)
+{
+ TrgPrefs *prefs = trg_client_get_prefs(client);
+ gdouble version = session_get_version(session);
+
+ gchar *profileName = trg_prefs_get_string(prefs,
+ TRG_PREFS_KEY_PROFILE_NAME,
+ TRG_PREFS_CONNECTION);
+ gchar *statusMsg =
+ g_strdup_printf(_("Connected: %s (Transmission %g)"),
+ profileName,
+ version);
+
+ trg_status_bar_push_connection_msg(sb, statusMsg);
+
+ g_free(profileName);
+ g_free(statusMsg);
+}
+
+void
+trg_status_bar_connect(TrgStatusBar * sb, JsonObject * session,
+ TrgClient * client)
+{
+ TrgStatusBarPrivate *priv = TRG_STATUS_BAR_GET_PRIVATE(sb);
+
+ trg_status_bar_set_connected_label(sb, session, client);
+ gtk_label_set_text(GTK_LABEL(priv->speed_lbl),
+ _("Updating torrents..."));
+}
+
+void trg_status_bar_session_update(TrgStatusBar * sb, JsonObject * session)
+{
+ TrgStatusBarPrivate *priv = TRG_STATUS_BAR_GET_PRIVATE(sb);
+ gint64 free = session_get_download_dir_free_space(session);
+ gboolean altSpeedEnabled = session_get_alt_speed_enabled(session);
+ gchar freeSpace[64];
+
+ if (free >= 0) {
+ gchar *freeSpaceString;
+ trg_strlsize(freeSpace, free);
+ freeSpaceString = g_strdup_printf(_("Free space: %s"), freeSpace);
+ gtk_label_set_text(GTK_LABEL(priv->free_lbl), freeSpaceString);
+ g_free(freeSpaceString);
+ } else {
+ gtk_label_set_text(GTK_LABEL(priv->free_lbl), "");
+ }
+
+ gtk_image_set_from_stock(GTK_IMAGE(priv->turtleImage),
+ altSpeedEnabled ? "alt-speed-on" :
+ "alt-speed-off", GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gtk_widget_set_tooltip_text(priv->turtleImage,
+ altSpeedEnabled ?
+ _("Disable alternate speed limits") :
+ _("Enable alternate speed limits"));
+ gtk_widget_set_visible(priv->turtleEventBox, TRUE);
+}
+
+void
+trg_status_bar_update_speed(TrgStatusBar * sb,
+ trg_torrent_model_update_stats * stats,
+ TrgClient * client)
+{
+ TrgStatusBarPrivate *priv = TRG_STATUS_BAR_GET_PRIVATE(sb);
+ JsonObject *session = trg_client_get_session(client);
+ gboolean altLimits = session_get_speed_limit_alt_enabled(session);
+ gchar *speedText;
+ gint64 uplimitraw, downlimitraw;
+ gchar downRateTotalString[32], upRateTotalString[32];
+ gchar uplimit[64], downlimit[64];
+
+ if (session_get_speed_limit_down_enabled(session))
+ downlimitraw = session_get_speed_limit_down(session);
+ else if (altLimits)
+ downlimitraw = session_get_alt_speed_limit_down(session);
+ else
+ downlimitraw = -1;
+
+ if (session_get_speed_limit_up_enabled(session))
+ uplimitraw = session_get_speed_limit_up(session);
+ else if (altLimits)
+ uplimitraw = session_get_alt_speed_limit_up(session);
+ else
+ uplimitraw = -1;
+
+ trg_strlspeed(downRateTotalString, stats->downRateTotal / disk_K);
+ trg_strlspeed(upRateTotalString, stats->upRateTotal / disk_K);
+
+ if (uplimitraw >= 0) {
+ gchar uplimitstring[32];
+ trg_strlspeed(uplimitstring, uplimitraw);
+ g_snprintf(uplimit, sizeof(uplimit), _(" (Limit: %s)"),
+ uplimitstring);
+ }
+
+ if (downlimitraw >= 0) {
+ gchar downlimitstring[32];
+ trg_strlspeed(downlimitstring, downlimitraw);
+ g_snprintf(downlimit, sizeof(downlimit), _(" (Limit: %s)"),
+ downlimitstring);
+ }
+
+ speedText =
+ g_strdup_printf(_("Down: %s%s, Up: %s%s"), downRateTotalString,
+ downlimitraw >= 0 ? downlimit : "",
+ upRateTotalString, uplimitraw >= 0 ? uplimit : "");
+
+ gtk_label_set_text(GTK_LABEL(priv->speed_lbl), speedText);
+
+ g_free(speedText);
+}
+
+void
+trg_status_bar_update(TrgStatusBar * sb,
+ trg_torrent_model_update_stats * stats,
+ TrgClient * client)
+{
+ trg_status_bar_set_connected_label(sb, trg_client_get_session(client),
+ client);
+ trg_status_bar_update_speed(sb, stats, client);
+}
+
+const gchar *trg_status_bar_get_speed_text(TrgStatusBar * s)
+{
+ TrgStatusBarPrivate *priv = TRG_STATUS_BAR_GET_PRIVATE(s);
+ return gtk_label_get_text(GTK_LABEL(priv->speed_lbl));
+}
+
+TrgStatusBar *trg_status_bar_new(TrgMainWindow * win, TrgClient * client)
+{
+ TrgStatusBar *sb = g_object_new(TRG_TYPE_STATUS_BAR, NULL);
+ TrgStatusBarPrivate *priv = TRG_STATUS_BAR_GET_PRIVATE(sb);
+
+ priv->client = client;
+ priv->win = win;
+
+ return sb;
+}
diff --git a/src/trg-status-bar.h b/src/trg-status-bar.h
new file mode 100644
index 0000000..b4942ee
--- /dev/null
+++ b/src/trg-status-bar.h
@@ -0,0 +1,68 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_STATUS_BAR_H_
+#define TRG_STATUS_BAR_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "trg-torrent-model.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_STATUS_BAR trg_status_bar_get_type()
+#define TRG_STATUS_BAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_STATUS_BAR, TrgStatusBar))
+#define TRG_STATUS_BAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_STATUS_BAR, TrgStatusBarClass))
+#define TRG_IS_STATUS_BAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_STATUS_BAR))
+#define TRG_IS_STATUS_BAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_STATUS_BAR))
+#define TRG_STATUS_BAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_STATUS_BAR, TrgStatusBarClass))
+ typedef struct {
+ GtkHBox parent;
+} TrgStatusBar;
+
+typedef struct {
+ GtkHBoxClass parent_class;
+} TrgStatusBarClass;
+
+GType trg_status_bar_get_type(void);
+
+TrgStatusBar *trg_status_bar_new();
+
+G_END_DECLS
+ void trg_status_bar_update(TrgStatusBar * sb,
+ trg_torrent_model_update_stats * stats,
+ TrgClient * client);
+void trg_status_bar_session_update(TrgStatusBar * sb,
+ JsonObject * session);
+void trg_status_bar_connect(TrgStatusBar * sb, JsonObject * session,
+ TrgClient * client);
+void trg_status_bar_push_connection_msg(TrgStatusBar * sb,
+ const gchar * msg);
+void trg_status_bar_reset(TrgStatusBar * sb);
+void trg_status_bar_clear_indicators(TrgStatusBar * sb);
+const gchar *trg_status_bar_get_speed_text(TrgStatusBar * s);
+void trg_status_bar_update_speed(TrgStatusBar * sb,
+ trg_torrent_model_update_stats * stats,
+ TrgClient * client);
+#endif /* TRG_STATUS_BAR_H_ */
diff --git a/src/trg-toolbar.c b/src/trg-toolbar.c
new file mode 100644
index 0000000..96dd95d
--- /dev/null
+++ b/src/trg-toolbar.c
@@ -0,0 +1,352 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "trg-prefs.h"
+#include "trg-main-window.h"
+#include "trg-toolbar.h"
+#include "trg-menu-bar.h"
+
+enum {
+ PROP_0,
+ PROP_CONNECT_BUTTON,
+ PROP_DISCONNECT_BUTTON,
+ PROP_ADD_BUTTON,
+ PROP_ADD_URL_BUTTON,
+ PROP_REMOVE_BUTTON,
+ PROP_DELETE_BUTTON,
+ PROP_RESUME_BUTTON,
+ PROP_PAUSE_BUTTON,
+ /*PROP_VERIFY_BUTTON, */
+ PROP_PROPS_BUTTON,
+ PROP_REMOTE_PREFS_BUTTON,
+ PROP_LOCAL_PREFS_BUTTON,
+ PROP_PREFS,
+ PROP_MAIN_WINDOW
+};
+
+G_DEFINE_TYPE(TrgToolbar, trg_toolbar, GTK_TYPE_TOOLBAR)
+#define TRG_TOOLBAR_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_TOOLBAR, TrgToolbarPrivate))
+typedef struct _TrgToolbarPrivate TrgToolbarPrivate;
+
+struct _TrgToolbarPrivate {
+ GtkWidget *tb_connect;
+ GtkWidget *tb_disconnect;
+ GtkWidget *tb_add;
+ GtkWidget *tb_remove;
+ GtkWidget *tb_delete;
+ GtkWidget *tb_resume;
+ GtkWidget *tb_pause;
+ GtkWidget *tb_props;
+ GtkWidget *tb_remote_prefs;
+ GtkWidget *tb_local_prefs;
+ TrgPrefs *prefs;
+ TrgMainWindow *main_window;
+};
+
+static void
+trg_toolbar_set_property(GObject * object,
+ guint prop_id,
+ const GValue * value,
+ GParamSpec * pspec G_GNUC_UNUSED)
+{
+ TrgToolbarPrivate *priv = TRG_TOOLBAR_GET_PRIVATE(object);
+
+ switch (prop_id) {
+ case PROP_PREFS:
+ priv->prefs = g_value_get_pointer(value);
+ break;
+ case PROP_MAIN_WINDOW:
+ priv->main_window = g_value_get_object(value);
+ break;
+ }
+}
+
+static void
+trg_toolbar_get_property(GObject * object, guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ TrgToolbarPrivate *priv = TRG_TOOLBAR_GET_PRIVATE(object);
+
+ switch (property_id) {
+ case PROP_CONNECT_BUTTON:
+ g_value_set_object(value, priv->tb_connect);
+ break;
+ case PROP_DISCONNECT_BUTTON:
+ g_value_set_object(value, priv->tb_disconnect);
+ break;
+ case PROP_ADD_BUTTON:
+ g_value_set_object(value, priv->tb_add);
+ break;
+ case PROP_REMOVE_BUTTON:
+ g_value_set_object(value, priv->tb_remove);
+ break;
+ case PROP_DELETE_BUTTON:
+ g_value_set_object(value, priv->tb_delete);
+ break;
+ case PROP_RESUME_BUTTON:
+ g_value_set_object(value, priv->tb_resume);
+ break;
+ case PROP_PAUSE_BUTTON:
+ g_value_set_object(value, priv->tb_pause);
+ break;
+ case PROP_PROPS_BUTTON:
+ g_value_set_object(value, priv->tb_props);
+ break;
+ case PROP_REMOTE_PREFS_BUTTON:
+ g_value_set_object(value, priv->tb_remote_prefs);
+ break;
+ case PROP_LOCAL_PREFS_BUTTON:
+ g_value_set_object(value, priv->tb_local_prefs);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_toolbar_install_widget_prop(GObjectClass * class, guint propId,
+ const gchar * name, const gchar * nick)
+{
+ g_object_class_install_property(class,
+ propId,
+ g_param_spec_object(name,
+ nick,
+ nick,
+ GTK_TYPE_WIDGET,
+ G_PARAM_READABLE
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+}
+
+GtkWidget *trg_toolbar_item_new(TrgToolbar * toolbar,
+ gchar * text,
+ int *index, gchar * icon,
+ gboolean sensitive)
+{
+ GtkToolItem *w = gtk_tool_button_new_from_stock(icon);
+ gtk_widget_set_sensitive(GTK_WIDGET(w), sensitive);
+ gtk_tool_item_set_tooltip_text(w, text);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), w, (*index)++);
+ return GTK_WIDGET(w);
+}
+
+static void trg_toolbar_refresh_menu(GtkWidget * w, gpointer data)
+{
+ TrgToolbarPrivate *priv = TRG_TOOLBAR_GET_PRIVATE(data);
+ GtkWidget *old =
+ gtk_menu_tool_button_get_menu(GTK_MENU_TOOL_BUTTON
+ (priv->tb_connect));
+ GtkWidget *new =
+ trg_menu_bar_file_connect_menu_new(priv->main_window, priv->prefs);
+
+ gtk_widget_destroy(old);
+ gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(priv->tb_connect),
+ new);
+ gtk_widget_show_all(new);
+}
+
+static GObject *trg_toolbar_constructor(GType type,
+ guint
+ n_construct_properties,
+ GObjectConstructParam *
+ construct_params)
+{
+ GObject *obj =
+ G_OBJECT_CLASS(trg_toolbar_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+ TrgToolbarPrivate *priv = TRG_TOOLBAR_GET_PRIVATE(obj);
+
+ GtkToolItem *separator;
+ GtkWidget *menu;
+ int position = 0;
+
+ gtk_toolbar_set_icon_size(GTK_TOOLBAR(obj),
+ GTK_ICON_SIZE_LARGE_TOOLBAR);
+ gtk_toolbar_set_style(GTK_TOOLBAR(obj), GTK_TOOLBAR_BOTH_HORIZ);
+
+ priv->tb_connect =
+ GTK_WIDGET(gtk_menu_tool_button_new_from_stock(GTK_STOCK_CONNECT));
+ gtk_tool_item_set_tooltip_text(GTK_TOOL_ITEM(priv->tb_connect),
+ _("Connect"));
+ gtk_tool_item_set_is_important (GTK_TOOL_ITEM (priv->tb_connect), TRUE);
+ menu =
+ trg_menu_bar_file_connect_menu_new(priv->main_window, priv->prefs);
+ gtk_menu_tool_button_set_menu(GTK_MENU_TOOL_BUTTON(priv->tb_connect),
+ menu);
+ gtk_toolbar_insert(GTK_TOOLBAR(obj), GTK_TOOL_ITEM(priv->tb_connect),
+ position++);
+ gtk_widget_show_all(menu);
+
+ priv->tb_disconnect =
+ trg_toolbar_item_new(TRG_TOOLBAR(obj), _("Disconnect"), &position,
+ GTK_STOCK_DISCONNECT, FALSE);
+ priv->tb_add =
+ trg_toolbar_item_new(TRG_TOOLBAR(obj), _("Add"), &position,
+ GTK_STOCK_ADD, FALSE);
+
+ separator = gtk_separator_tool_item_new();
+ gtk_toolbar_insert(GTK_TOOLBAR(obj), separator, position++);
+
+ priv->tb_resume =
+ trg_toolbar_item_new(TRG_TOOLBAR(obj), _("Resume"), &position,
+ GTK_STOCK_MEDIA_PLAY, FALSE);
+ priv->tb_pause =
+ trg_toolbar_item_new(TRG_TOOLBAR(obj), _("Pause"), &position,
+ GTK_STOCK_MEDIA_PAUSE, FALSE);
+
+ priv->tb_props =
+ trg_toolbar_item_new(TRG_TOOLBAR(obj), _("Properties"), &position,
+ GTK_STOCK_PROPERTIES, FALSE);
+
+ priv->tb_remove =
+ trg_toolbar_item_new(TRG_TOOLBAR(obj), _("Remove"), &position,
+ GTK_STOCK_REMOVE, FALSE);
+
+ priv->tb_delete =
+ trg_toolbar_item_new(TRG_TOOLBAR(obj), _("Remove with data"),
+ &position, GTK_STOCK_CLEAR, FALSE);
+
+ separator = gtk_separator_tool_item_new();
+ gtk_toolbar_insert(GTK_TOOLBAR(obj), separator, position++);
+
+ priv->tb_local_prefs =
+ trg_toolbar_item_new(TRG_TOOLBAR(obj), _("Local Preferences"),
+ &position, GTK_STOCK_PREFERENCES, TRUE);
+
+ priv->tb_remote_prefs =
+ trg_toolbar_item_new(TRG_TOOLBAR(obj), _("Remote Preferences"),
+ &position, GTK_STOCK_NETWORK, FALSE);
+
+#if !GTK_CHECK_VERSION( 3, 0, 0 )
+ gtk_toolbar_set_tooltips(GTK_TOOLBAR(obj), TRUE);
+#endif
+
+ g_signal_connect(G_OBJECT(priv->prefs), "pref-profile-changed",
+ G_CALLBACK(trg_toolbar_refresh_menu), obj);
+
+ return obj;
+}
+
+static void trg_toolbar_class_init(TrgToolbarClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ object_class->get_property = trg_toolbar_get_property;
+ object_class->set_property = trg_toolbar_set_property;
+ object_class->constructor = trg_toolbar_constructor;
+
+ g_object_class_install_property(object_class,
+ PROP_PREFS,
+ g_param_spec_pointer("prefs",
+ "Prefs",
+ "Prefs",
+ G_PARAM_READWRITE
+ |
+ G_PARAM_CONSTRUCT
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_MAIN_WINDOW,
+ g_param_spec_object("mainwindow",
+ "mainwindow",
+ "mainwindow",
+ TRG_TYPE_MAIN_WINDOW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ trg_toolbar_install_widget_prop(object_class, PROP_CONNECT_BUTTON,
+ "connect-button", "Connect Button");
+ trg_toolbar_install_widget_prop(object_class,
+ PROP_DISCONNECT_BUTTON,
+ "disconnect-button",
+ "Disconnect Button");
+ trg_toolbar_install_widget_prop(object_class, PROP_ADD_BUTTON,
+ "add-button", "Add Button");
+ trg_toolbar_install_widget_prop(object_class, PROP_ADD_URL_BUTTON,
+ "add-url-button", "Add URL Button");
+ trg_toolbar_install_widget_prop(object_class, PROP_REMOVE_BUTTON,
+ "remove-button", "Remove Button");
+ trg_toolbar_install_widget_prop(object_class, PROP_DELETE_BUTTON,
+ "delete-button", "Delete Button");
+ trg_toolbar_install_widget_prop(object_class, PROP_RESUME_BUTTON,
+ "resume-button", "Resume Button");
+ trg_toolbar_install_widget_prop(object_class, PROP_PAUSE_BUTTON,
+ "pause-button", "Pause Button");
+ trg_toolbar_install_widget_prop(object_class, PROP_PROPS_BUTTON,
+ "props-button", "Props Button");
+ trg_toolbar_install_widget_prop(object_class, PROP_REMOTE_PREFS_BUTTON,
+ "remote-prefs-button",
+ "Remote Prefs Button");
+ trg_toolbar_install_widget_prop(object_class, PROP_LOCAL_PREFS_BUTTON,
+ "local-prefs-button",
+ "Local Prefs Button");
+
+ g_type_class_add_private(klass, sizeof(TrgToolbarPrivate));
+}
+
+void trg_toolbar_connected_change(TrgToolbar * tb, gboolean connected)
+{
+ TrgToolbarPrivate *priv = TRG_TOOLBAR_GET_PRIVATE(tb);
+
+ gtk_widget_set_sensitive(priv->tb_add, connected);
+ gtk_widget_set_sensitive(priv->tb_disconnect, connected);
+ gtk_widget_set_sensitive(priv->tb_remote_prefs, connected);
+}
+
+void
+trg_toolbar_torrent_actions_sensitive(TrgToolbar * tb, gboolean sensitive)
+{
+ TrgToolbarPrivate *priv = TRG_TOOLBAR_GET_PRIVATE(tb);
+
+ gtk_widget_set_sensitive(priv->tb_props, sensitive);
+ gtk_widget_set_sensitive(priv->tb_remove, sensitive);
+ gtk_widget_set_sensitive(priv->tb_delete, sensitive);
+ gtk_widget_set_sensitive(priv->tb_resume, sensitive);
+ gtk_widget_set_sensitive(priv->tb_pause, sensitive);
+}
+
+static void trg_toolbar_init(TrgToolbar * self)
+{
+}
+
+TrgToolbar *trg_toolbar_new(TrgMainWindow * win, TrgPrefs * prefs)
+{
+ return g_object_new(TRG_TYPE_TOOLBAR,
+ "prefs", prefs, "mainwindow", win, NULL);
+}
diff --git a/src/trg-toolbar.h b/src/trg-toolbar.h
new file mode 100644
index 0000000..7d12b2d
--- /dev/null
+++ b/src/trg-toolbar.h
@@ -0,0 +1,59 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#ifndef TRG_TOOLBAR_H_
+#define TRG_TOOLBAR_H_
+
+#include <gtk/gtk.h>
+#include <glib-object.h>
+
+#include "trg-prefs.h"
+#include "trg-main-window.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_TOOLBAR trg_toolbar_get_type()
+#define TRG_TOOLBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_TOOLBAR, TrgToolbar))
+#define TRG_TOOLBAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_TOOLBAR, TrgToolbarClass))
+#define TRG_IS_TOOLBAR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_TOOLBAR))
+#define TRG_IS_TOOLBAR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_TOOLBAR))
+#define TRG_TOOLBAR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_TOOLBAR, TrgToolbarClass))
+ typedef struct {
+ GtkToolbar parent;
+} TrgToolbar;
+
+typedef struct {
+ GtkToolbarClass parent_class;
+} TrgToolbarClass;
+
+GType trg_toolbar_get_type(void);
+
+TrgToolbar *trg_toolbar_new(TrgMainWindow * win, TrgPrefs * prefs);
+
+G_END_DECLS
+ void trg_toolbar_torrent_actions_sensitive(TrgToolbar * mb,
+ gboolean sensitive);
+void trg_toolbar_connected_change(TrgToolbar * tb, gboolean connected);
+
+#endif /* TRG_TOOLBAR_H_ */
diff --git a/src/trg-torrent-add-dialog.c b/src/trg-torrent-add-dialog.c
new file mode 100644
index 0000000..bde24eb
--- /dev/null
+++ b/src/trg-torrent-add-dialog.c
@@ -0,0 +1,964 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* Most of the UI code was taken from open-dialog.c and files-list.c
+ * in Transmission, adapted to fit in with different torrent file parser
+ * and JSON dispatch.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+#include <glib/gprintf.h>
+
+#include "hig.h"
+#include "util.h"
+#include "trg-client.h"
+#include "trg-main-window.h"
+#include "trg-file-parser.h"
+#include "trg-torrent-add-dialog.h"
+#include "trg-files-tree-view-common.h"
+#include "trg-files-model-common.h"
+#include "trg-cell-renderer-size.h"
+#include "trg-cell-renderer-priority.h"
+#include "trg-cell-renderer-file-icon.h"
+#include "trg-cell-renderer-wanted.h"
+#include "trg-destination-combo.h"
+#include "trg-prefs.h"
+#include "requests.h"
+#include "torrent.h"
+#include "json.h"
+#include "protocol-constants.h"
+
+enum {
+ PROP_0, PROP_FILENAME, PROP_PARENT, PROP_CLIENT
+};
+
+enum {
+ FC_INDEX, FC_LABEL, FC_SIZE, FC_PRIORITY, FC_ENABLED, N_FILE_COLS
+};
+
+G_DEFINE_TYPE(TrgTorrentAddDialog, trg_torrent_add_dialog, GTK_TYPE_DIALOG)
+#define TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_TORRENT_ADD_DIALOG, TrgTorrentAddDialogPrivate))
+typedef struct _TrgTorrentAddDialogPrivate TrgTorrentAddDialogPrivate;
+
+struct _TrgTorrentAddDialogPrivate {
+ TrgClient *client;
+ TrgMainWindow *parent;
+ GSList *filenames;
+ GtkWidget *source_chooser;
+ GtkWidget *dest_combo;
+ GtkWidget *priority_combo;
+ GtkWidget *file_list;
+ GtkTreeStore *store;
+ GtkWidget *paused_check;
+ GtkWidget *delete_check;
+};
+
+#define MAGNET_MAX_LINK_WIDTH 75
+
+static void trg_torrent_add_dialog_set_property(GObject * object,
+ guint prop_id,
+ const GValue * value,
+ GParamSpec *
+ pspec G_GNUC_UNUSED)
+{
+ TrgTorrentAddDialogPrivate *priv =
+ TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(object);
+
+ switch (prop_id) {
+ case PROP_FILENAME:
+ priv->filenames = g_value_get_pointer(value);
+ break;
+ case PROP_PARENT:
+ priv->parent = g_value_get_object(value);
+ break;
+ case PROP_CLIENT:
+ priv->client = g_value_get_pointer(value);
+ break;
+ }
+}
+
+static void
+trg_torrent_add_dialog_get_property(GObject * object,
+ guint prop_id,
+ GValue * value,
+ GParamSpec * pspec G_GNUC_UNUSED)
+{
+ TrgTorrentAddDialogPrivate *priv =
+ TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(object);
+
+ switch (prop_id) {
+ case PROP_FILENAME:
+ g_value_set_pointer(value, priv->filenames);
+ break;
+ case PROP_PARENT:
+ g_value_set_object(value, priv->parent);
+ break;
+ }
+}
+
+static void
+add_set_common_args(JsonObject * args, gint priority, gchar * dir)
+{
+ json_object_set_string_member(args, FIELD_FILE_DOWNLOAD_DIR, dir);
+ json_object_set_int_member(args, FIELD_BANDWIDTH_PRIORITY,
+ (gint64) priority);
+}
+
+static gpointer add_files_threadfunc(gpointer data)
+{
+ struct add_torrent_threadfunc_args *files_thread_data =
+ (struct add_torrent_threadfunc_args *) data;
+
+ GSList *li;
+
+ for (li = files_thread_data->list; li; li = g_slist_next(li)) {
+ gchar *fileName = (gchar *) li->data;
+ JsonNode *request =
+ torrent_add(fileName, files_thread_data->flags);
+ JsonObject *args;
+ trg_response *response;
+
+ if (!request)
+ continue;
+
+ args = node_get_arguments(request);
+
+ if (files_thread_data->extraArgs)
+ add_set_common_args(args, files_thread_data->priority,
+ files_thread_data->dir);
+
+ response = dispatch(files_thread_data->client, request);
+ response->cb_data = files_thread_data->cb_data;
+ g_idle_add(on_generic_interactive_action, response);
+ }
+
+ g_str_slist_free(files_thread_data->list);
+
+ if (files_thread_data->extraArgs)
+ g_free(files_thread_data->dir);
+
+ g_free(files_thread_data);
+
+ return NULL;
+}
+
+void launch_add_thread(struct add_torrent_threadfunc_args *args)
+{
+ GError *error = NULL;
+ g_thread_create(add_files_threadfunc, args, FALSE, &error);
+
+ if (error) {
+ g_error("thread creation error: %s", error->message);
+ g_error_free(error);
+ g_str_slist_free(args->list);
+ g_free(args);
+ }
+}
+
+static gboolean
+add_file_indexes_foreachfunc(GtkTreeModel * model,
+ GtkTreePath *
+ path G_GNUC_UNUSED,
+ GtkTreeIter * iter, gpointer data)
+{
+ JsonObject *args = (JsonObject *) data;
+ gint priority, index, wanted;
+
+ gtk_tree_model_get(model, iter, FC_PRIORITY, &priority, FC_ENABLED,
+ &wanted, FC_INDEX, &index, -1);
+
+ if (gtk_tree_model_iter_has_child(model, iter) || index < 0)
+ return FALSE;
+
+ if (wanted)
+ add_file_id_to_array(args, FIELD_FILES_WANTED, index);
+ else
+ add_file_id_to_array(args, FIELD_FILES_UNWANTED, index);
+
+ if (priority == TR_PRI_LOW)
+ add_file_id_to_array(args, FIELD_FILES_PRIORITY_LOW, index);
+ else if (priority == TR_PRI_HIGH)
+ add_file_id_to_array(args, FIELD_FILES_PRIORITY_HIGH, index);
+ else
+ add_file_id_to_array(args, FIELD_FILES_PRIORITY_NORMAL, index);
+
+ return FALSE;
+}
+
+static void
+trg_torrent_add_response_cb(GtkDialog * dlg, gint res_id, gpointer data)
+{
+ TrgTorrentAddDialogPrivate *priv =
+ TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(dlg);
+
+ guint flags = 0x00;
+ if (gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON(priv->paused_check)))
+ flags |= TORRENT_ADD_FLAG_PAUSED;
+ if (gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON(priv->delete_check)))
+ flags |= TORRENT_ADD_FLAG_DELETE;
+
+ if (res_id == GTK_RESPONSE_ACCEPT) {
+ gint priority =
+ gtk_combo_box_get_active(GTK_COMBO_BOX(priv->priority_combo)) -
+ 1;
+ gchar *dir =
+ trg_destination_combo_get_dir(TRG_DESTINATION_COMBO
+ (priv->dest_combo));
+
+ if (g_slist_length(priv->filenames) == 1) {
+ JsonNode *req =
+ torrent_add((gchar *) priv->filenames->data, flags);
+ if (req) {
+ JsonObject *args = node_get_arguments(req);
+ gtk_tree_model_foreach(GTK_TREE_MODEL(priv->store),
+ add_file_indexes_foreachfunc, args);
+ add_set_common_args(args, priority, dir);
+ dispatch_async(priv->client, req,
+ on_generic_interactive_action,
+ priv->parent);
+ }
+ g_str_slist_free(priv->filenames);
+ } else {
+ struct add_torrent_threadfunc_args *args =
+ g_new(struct add_torrent_threadfunc_args, 1);
+ args->list = priv->filenames;
+ args->cb_data = priv->parent;
+ args->client = priv->client;
+ args->dir = g_strdup(dir);
+ args->priority = priority;
+ args->flags = flags;
+ args->extraArgs = TRUE;
+
+ launch_add_thread(args);
+ }
+
+ trg_destination_combo_save_selection(TRG_DESTINATION_COMBO
+ (priv->dest_combo));
+
+ g_free(dir);
+ } else {
+ g_str_slist_free(priv->filenames);
+ }
+
+ gtk_widget_destroy(GTK_WIDGET(dlg));
+}
+
+static void set_low(GtkWidget * w G_GNUC_UNUSED, gpointer data)
+{
+ trg_files_tree_model_set_priority(GTK_TREE_VIEW(data), FC_PRIORITY,
+ TR_PRI_LOW);
+}
+
+static void set_normal(GtkWidget * w G_GNUC_UNUSED, gpointer data)
+{
+ trg_files_tree_model_set_priority(GTK_TREE_VIEW(data), FC_PRIORITY,
+ TR_PRI_NORMAL);
+}
+
+static void set_high(GtkWidget * w G_GNUC_UNUSED, gpointer data)
+{
+ trg_files_tree_model_set_priority(GTK_TREE_VIEW(data), FC_PRIORITY,
+ TR_PRI_HIGH);
+}
+
+static void set_unwanted(GtkWidget * w G_GNUC_UNUSED, gpointer data)
+{
+ trg_files_model_set_wanted(GTK_TREE_VIEW(data), FC_ENABLED, FALSE);
+}
+
+static void set_wanted(GtkWidget * w G_GNUC_UNUSED, gpointer data)
+{
+ trg_files_model_set_wanted(GTK_TREE_VIEW(data), FC_ENABLED, TRUE);
+}
+
+static gboolean
+onViewButtonPressed(GtkWidget * w, GdkEventButton * event, gpointer gdata)
+{
+ return trg_files_tree_view_onViewButtonPressed(w, event, FC_PRIORITY,
+ FC_ENABLED,
+ G_CALLBACK(set_low),
+ G_CALLBACK(set_normal),
+ G_CALLBACK(set_high),
+ G_CALLBACK(set_wanted),
+ G_CALLBACK
+ (set_unwanted), gdata);
+}
+
+GtkWidget *gtr_file_list_new(GtkTreeStore ** store)
+{
+ int size;
+ int width;
+ GtkWidget *view;
+ GtkWidget *scroll;
+ GtkCellRenderer *rend;
+ GtkTreeSelection *sel;
+ GtkTreeViewColumn *col;
+ GtkTreeView *tree_view;
+ const char *title;
+ PangoLayout *pango_layout;
+ PangoContext *pango_context;
+ PangoFontDescription *pango_font_description;
+
+ /* create the view */
+ view = gtk_tree_view_new();
+ tree_view = GTK_TREE_VIEW(view);
+ gtk_tree_view_set_rules_hint(tree_view, TRUE);
+ gtk_container_set_border_width(GTK_CONTAINER(view), GUI_PAD_BIG);
+ g_signal_connect(view, "button-press-event",
+ G_CALLBACK(onViewButtonPressed), view);
+
+ pango_context = gtk_widget_create_pango_context(view);
+ pango_font_description =
+ pango_font_description_copy(pango_context_get_font_description
+ (pango_context));
+ size = pango_font_description_get_size(pango_font_description);
+ pango_font_description_set_size(pango_font_description, size * 0.8);
+ g_object_unref(G_OBJECT(pango_context));
+
+ /* set up view */
+ sel = gtk_tree_view_get_selection(tree_view);
+ gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
+ gtk_tree_view_expand_all(tree_view);
+ gtk_tree_view_set_search_column(tree_view, FC_LABEL);
+
+ /* add file column */
+ col = GTK_TREE_VIEW_COLUMN(g_object_new(GTK_TYPE_TREE_VIEW_COLUMN,
+ "expand", TRUE,
+ "title", _("Name"), NULL));
+ gtk_tree_view_column_set_resizable(col, TRUE);
+ rend = trg_cell_renderer_file_icon_new();
+ gtk_tree_view_column_pack_start(col, rend, FALSE);
+ gtk_tree_view_column_set_attributes(col, rend, "file-name", FC_LABEL,
+ "file-id", FC_INDEX, NULL);
+
+ /* add text renderer */
+ rend = gtk_cell_renderer_text_new();
+ g_object_set(rend, "ellipsize", PANGO_ELLIPSIZE_END, "font-desc",
+ pango_font_description, NULL);
+ gtk_tree_view_column_pack_start(col, rend, TRUE);
+ gtk_tree_view_column_set_attributes(col, rend, "text", FC_LABEL, NULL);
+ gtk_tree_view_column_set_sort_column_id(col, FC_LABEL);
+ gtk_tree_view_append_column(tree_view, col);
+
+ /* add "size" column */
+
+ title = _("Size");
+ rend = trg_cell_renderer_size_new();
+ g_object_set(rend, "alignment", PANGO_ALIGN_RIGHT, "font-desc",
+ pango_font_description, "xpad", GUI_PAD, "xalign", 1.0f,
+ "yalign", 0.5f, NULL);
+ col = gtk_tree_view_column_new_with_attributes(title, rend, NULL);
+ gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+ gtk_tree_view_column_set_sort_column_id(col, FC_SIZE);
+ gtk_tree_view_column_set_attributes(col, rend, "size-value", FC_SIZE,
+ NULL);
+ gtk_tree_view_append_column(tree_view, col);
+
+ /* add "enabled" column */
+ title = _("Download");
+ pango_layout = gtk_widget_create_pango_layout(view, title);
+ pango_layout_get_pixel_size(pango_layout, &width, NULL);
+ width += 30; /* room for the sort indicator */
+ g_object_unref(G_OBJECT(pango_layout));
+ rend = trg_cell_renderer_wanted_new();
+ col =
+ gtk_tree_view_column_new_with_attributes(title, rend,
+ "wanted-value",
+ FC_ENABLED, NULL);
+ gtk_tree_view_column_set_fixed_width(col, width);
+ gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
+ gtk_tree_view_column_set_sort_column_id(col, FC_ENABLED);
+ gtk_tree_view_append_column(tree_view, col);
+
+ /* add priority column */
+ title = _("Priority");
+ pango_layout = gtk_widget_create_pango_layout(view, title);
+ pango_layout_get_pixel_size(pango_layout, &width, NULL);
+ width += 30; /* room for the sort indicator */
+ g_object_unref(G_OBJECT(pango_layout));
+ rend = trg_cell_renderer_priority_new();
+ col = gtk_tree_view_column_new_with_attributes(title, rend,
+ "priority-value",
+ FC_PRIORITY, NULL);
+ gtk_tree_view_column_set_fixed_width(col, width);
+ gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
+ gtk_tree_view_column_set_sort_column_id(col, FC_PRIORITY);
+ gtk_tree_view_append_column(tree_view, col);
+
+ *store = gtk_tree_store_new(N_FILE_COLS, G_TYPE_INT, /* index */
+ G_TYPE_STRING, /* label */
+ G_TYPE_INT64, /* size */
+ G_TYPE_INT, /* priority */
+ G_TYPE_INT); /* dl enabled */
+
+ gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(*store));
+ g_object_unref(G_OBJECT(*store));
+
+ /* create the scrolled window and stick the view in it */
+ scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
+ GTK_SHADOW_IN);
+ gtk_container_add(GTK_CONTAINER(scroll), view);
+ gtk_widget_set_size_request(scroll, -1, 200);
+
+ pango_font_description_free(pango_font_description);
+ return scroll;
+}
+
+static GtkWidget *gtr_dialog_get_content_area(GtkDialog * dialog)
+{
+#if GTK_CHECK_VERSION( 2,14,0 )
+ return gtk_dialog_get_content_area(dialog);
+#else
+ return dialog->vbox;
+#endif
+}
+
+static void gtr_dialog_set_content(GtkDialog * dialog, GtkWidget * content)
+{
+ GtkWidget *vbox = gtr_dialog_get_content_area(dialog);
+ gtk_box_pack_start(GTK_BOX(vbox), content, TRUE, TRUE, 0);
+ gtk_widget_show_all(content);
+}
+
+GtkWidget *gtr_priority_combo_new(void)
+{
+ return gtr_combo_box_new_enum(_("Low"), TR_PRI_LOW, _("Normal"),
+ TR_PRI_NORMAL, _("High"), TR_PRI_HIGH,
+ NULL);
+}
+
+static void addTorrentFilters(GtkFileChooser * chooser)
+{
+ GtkFileFilter *filter;
+
+ filter = gtk_file_filter_new();
+ gtk_file_filter_set_name(filter, _("Torrent files"));
+ gtk_file_filter_add_pattern(filter, "*.torrent");
+ gtk_file_chooser_add_filter(chooser, filter);
+
+ filter = gtk_file_filter_new();
+ gtk_file_filter_set_name(filter, _("All files"));
+ gtk_file_filter_add_pattern(filter, "*");
+ gtk_file_chooser_add_filter(chooser, filter);
+}
+
+static void
+store_add_node(GtkTreeStore * store, GtkTreeIter * parent,
+ trg_files_tree_node * node)
+{
+ GtkTreeIter child;
+ GList *li;
+
+ if (node->name) {
+ gtk_tree_store_append(store, &child, parent);
+ gtk_tree_store_set(store, &child, FC_LABEL, node->name, FC_ENABLED,
+ 1, FC_INDEX, node->index,
+ FC_PRIORITY, TR_PRI_NORMAL,
+ FC_SIZE, node->length, -1);
+ }
+
+ for (li = node->children; li; li = g_list_next(li))
+ store_add_node(store, node->name ? &child : NULL,
+ (trg_files_tree_node *) li->data);
+}
+
+static void torrent_not_parsed_warning(GtkWindow * parent)
+{
+ GtkWidget *dialog = gtk_message_dialog_new(parent,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_OK,
+ _
+ ("Unable to parse torrent file. File preferences unavailable, but you can still try uploading it."));
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+}
+
+static void torrent_not_found_error(GtkWindow * parent, gchar * file)
+{
+ GtkWidget *dialog = gtk_message_dialog_new(parent,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ _
+ ("Unable to open torrent file: %s"),
+ file);
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+}
+
+static void
+trg_torrent_add_dialog_set_filenames(TrgTorrentAddDialog * d,
+ GSList * filenames)
+{
+ TrgTorrentAddDialogPrivate *priv =
+ TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(d);
+ GtkButton *chooser = GTK_BUTTON(priv->source_chooser);
+ gint nfiles = filenames ? g_slist_length(filenames) : 0;
+
+ gtk_tree_store_clear(priv->store);
+
+ if (nfiles == 1) {
+ gchar *file_name = (gchar *) filenames->data;
+ if (is_url(file_name) || is_magnet(file_name)) {
+ if (strlen(file_name) > MAGNET_MAX_LINK_WIDTH) {
+ gchar *file_name_trunc =
+ g_strndup(file_name, MAGNET_MAX_LINK_WIDTH);
+ gchar *file_name_trunc_fmt =
+ g_strdup_printf("%s ...", file_name_trunc);
+ gtk_button_set_label(chooser, file_name_trunc_fmt);
+ g_free(file_name_trunc);
+ g_free(file_name_trunc_fmt);
+ } else {
+ gtk_button_set_label(chooser, file_name);
+ }
+
+ gtk_widget_set_sensitive(priv->file_list, FALSE);
+ gtk_widget_set_sensitive(priv->delete_check, FALSE);
+ } else {
+ gchar *file_name_base;
+ trg_torrent_file *tor_data = NULL;
+
+ file_name_base = g_path_get_basename(file_name);
+
+ if (file_name_base) {
+ gtk_button_set_label(chooser, file_name_base);
+ g_free(file_name_base);
+ } else {
+ gtk_button_set_label(chooser, file_name);
+ }
+
+ if (g_file_test(file_name, G_FILE_TEST_IS_REGULAR)) {
+ tor_data = trg_parse_torrent_file(file_name);
+ if (!tor_data) {
+ torrent_not_parsed_warning(GTK_WINDOW(priv->parent));
+ } else {
+ store_add_node(priv->store, NULL, tor_data->top_node);
+ trg_torrent_file_free(tor_data);
+ }
+ } else {
+ torrent_not_found_error(GTK_WINDOW(priv->parent),
+ file_name);
+ }
+
+ gtk_widget_set_sensitive(priv->file_list, tor_data != NULL);
+ }
+ } else {
+ gtk_widget_set_sensitive(priv->file_list, FALSE);
+ if (nfiles < 1) {
+ gtk_button_set_label(chooser, _("(None)"));
+ } else {
+ gtk_button_set_label(chooser, _("(Multiple)"));
+ }
+ }
+
+ priv->filenames = filenames;
+}
+
+static void
+trg_torrent_add_dialog_generic_save_dir(GtkFileChooser * c,
+ TrgPrefs * prefs)
+{
+ gchar *cwd = gtk_file_chooser_get_current_folder(c);
+
+ if (cwd) {
+ trg_prefs_set_string(prefs, TRG_PREFS_KEY_LAST_TORRENT_DIR, cwd,
+ TRG_PREFS_GLOBAL);
+ g_free(cwd);
+ }
+}
+
+static GtkWidget *trg_torrent_add_dialog_generic(GtkWindow * parent,
+ TrgPrefs * prefs)
+{
+ GtkWidget *w = gtk_file_chooser_dialog_new(_("Add a Torrent"), parent,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL,
+ GTK_STOCK_ADD,
+ GTK_RESPONSE_ACCEPT, NULL);
+ gchar *dir =
+ trg_prefs_get_string(prefs, TRG_PREFS_KEY_LAST_TORRENT_DIR,
+ TRG_PREFS_GLOBAL);
+ if (dir) {
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(w), dir);
+ g_free(dir);
+ }
+
+ addTorrentFilters(GTK_FILE_CHOOSER(w));
+ gtk_dialog_set_alternative_button_order(GTK_DIALOG(w),
+ GTK_RESPONSE_ACCEPT,
+ GTK_RESPONSE_CANCEL, -1);
+ gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(w), TRUE);
+ return w;
+}
+
+static void
+trg_torrent_add_dialog_source_click_cb(GtkWidget * w, gpointer data)
+{
+ TrgTorrentAddDialogPrivate *priv =
+ TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(data);
+ GtkWidget *d = trg_torrent_add_dialog_generic(GTK_WINDOW(data),
+ trg_client_get_prefs
+ (priv->client));
+
+ if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) {
+ if (priv->filenames)
+ g_str_slist_free(priv->filenames);
+
+ priv->filenames =
+ gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(d));
+
+ trg_torrent_add_dialog_generic_save_dir(GTK_FILE_CHOOSER(d),
+ trg_client_get_prefs
+ (priv->client));
+ trg_torrent_add_dialog_set_filenames(TRG_TORRENT_ADD_DIALOG(data),
+ priv->filenames);
+ }
+
+ gtk_widget_destroy(GTK_WIDGET(d));
+}
+
+static gboolean
+apply_all_changed_foreachfunc(GtkTreeModel * model,
+ GtkTreePath * path,
+ GtkTreeIter * iter, gpointer data)
+{
+ GtkComboBox *combo = GTK_COMBO_BOX(data);
+ GtkTreeModel *combo_model = gtk_combo_box_get_model(combo);
+ GtkTreeIter selection_iter;
+ if (gtk_combo_box_get_active_iter(combo, &selection_iter)) {
+ gint column;
+ gint value;
+ GValue gvalue = { 0 };
+ g_value_init(&gvalue, G_TYPE_INT);
+ gtk_tree_model_get(combo_model, &selection_iter, 2, &column, 3,
+ &value, -1);
+ g_value_set_int(&gvalue, value);
+ gtk_tree_store_set_value(GTK_TREE_STORE(model), iter, column,
+ &gvalue);
+ }
+ return FALSE;
+}
+
+static void
+trg_torrent_add_dialog_apply_all_changed_cb(GtkWidget * w, gpointer data)
+{
+ TrgTorrentAddDialogPrivate *priv =
+ TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(data);
+ GtkWidget *tv = gtk_bin_get_child(GTK_BIN(priv->file_list));
+ GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(tv));
+ gtk_tree_model_foreach(model, apply_all_changed_foreachfunc, w);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(w), -1);
+}
+
+static GtkWidget
+ * trg_torrent_add_dialog_apply_all_combo_new(TrgTorrentAddDialog *
+ dialog)
+{
+ GtkListStore *model =
+ gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT,
+ G_TYPE_INT);
+ GtkWidget *combo = gtk_combo_box_new();
+ GtkTreeIter iter;
+ GtkCellRenderer *renderer;
+
+ gtk_list_store_append(model, &iter);
+ gtk_list_store_set(model, &iter, 1, _("High Priority"), 2, FC_PRIORITY,
+ 3, TR_PRI_HIGH, -1);
+ gtk_list_store_append(model, &iter);
+ gtk_list_store_set(model, &iter, 1, _("Normal Priority"), 2,
+ FC_PRIORITY, 3, TR_PRI_NORMAL, -1);
+ gtk_list_store_append(model, &iter);
+ gtk_list_store_set(model, &iter, 1, _("Low Priority"), 2, FC_PRIORITY,
+ 3, TR_PRI_LOW, -1);
+ gtk_list_store_append(model, &iter);
+ gtk_list_store_set(model, &iter, 0, GTK_STOCK_APPLY, 1, _("Download"),
+ 2, FC_ENABLED, 3, TRUE, -1);
+ gtk_list_store_append(model, &iter);
+ gtk_list_store_set(model, &iter, 0, GTK_STOCK_CANCEL, 1, _("Skip"), 2,
+ FC_ENABLED, 3, FALSE, -1);
+
+ renderer = gtk_cell_renderer_pixbuf_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, FALSE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), renderer,
+ "stock-id", 0);
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, FALSE);
+ gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), renderer, "text",
+ 1);
+
+ gtk_combo_box_set_model(GTK_COMBO_BOX(combo), GTK_TREE_MODEL(model));
+ g_signal_connect(combo, "changed",
+ G_CALLBACK
+ (trg_torrent_add_dialog_apply_all_changed_cb),
+ dialog);
+
+ return combo;
+}
+
+static GObject *trg_torrent_add_dialog_constructor(GType type,
+ guint
+ n_construct_properties,
+ GObjectConstructParam *
+ construct_params)
+{
+ GObject *obj = G_OBJECT_CLASS
+ (trg_torrent_add_dialog_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+ TrgTorrentAddDialogPrivate *priv =
+ TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(obj);
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+
+ GtkWidget *t, *l, *applyall_combo;
+
+ /* window */
+ gtk_window_set_title(GTK_WINDOW(obj), _("Add Torrent"));
+ gtk_window_set_transient_for(GTK_WINDOW(obj),
+ GTK_WINDOW(priv->parent));
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(obj), TRUE);
+
+ /* buttons */
+ gtk_dialog_add_button(GTK_DIALOG(obj), GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button(GTK_DIALOG(obj), GTK_STOCK_OPEN,
+ GTK_RESPONSE_ACCEPT);
+ gtk_dialog_set_alternative_button_order(GTK_DIALOG(obj),
+ GTK_RESPONSE_ACCEPT,
+ GTK_RESPONSE_CANCEL, -1);
+ gtk_dialog_set_default_response(GTK_DIALOG(obj), GTK_RESPONSE_ACCEPT);
+
+ /* workspace */
+ t = gtk_table_new(6, 2, FALSE);
+ gtk_container_set_border_width(GTK_CONTAINER(t), GUI_PAD_BIG);
+ gtk_table_set_row_spacings(GTK_TABLE(t), GUI_PAD);
+ gtk_table_set_col_spacings(GTK_TABLE(t), GUI_PAD_BIG);
+
+ priv->file_list = gtr_file_list_new(&priv->store);
+ gtk_widget_set_sensitive(priv->file_list, FALSE);
+
+ priv->paused_check =
+ gtk_check_button_new_with_mnemonic(_("Start _paused"));
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->paused_check),
+ trg_prefs_get_bool(prefs,
+ TRG_PREFS_KEY_START_PAUSED,
+ TRG_PREFS_GLOBAL));
+
+ priv->delete_check = gtk_check_button_new_with_mnemonic(_
+ ("Delete local .torrent file after adding"));
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->delete_check),
+ trg_prefs_get_bool(prefs,
+ TRG_PREFS_KEY_DELETE_LOCAL_TORRENT,
+ TRG_PREFS_GLOBAL));
+
+ priv->priority_combo = gtr_priority_combo_new();
+ gtk_combo_box_set_active(GTK_COMBO_BOX(priv->priority_combo), 1);
+
+ l = gtk_label_new_with_mnemonic(_("_Torrent file:"));
+ gtk_misc_set_alignment(GTK_MISC(l), 0.0f, 0.5f);
+ gtk_table_attach(GTK_TABLE(t), l, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
+
+ priv->source_chooser = gtk_button_new();
+ gtk_button_set_alignment(GTK_BUTTON(priv->source_chooser), 0.0f, 0.5f);
+ trg_torrent_add_dialog_set_filenames(TRG_TORRENT_ADD_DIALOG(obj),
+ priv->filenames);
+
+ gtk_table_attach(GTK_TABLE(t), priv->source_chooser, 1, 2, 0,
+ 1, ~0, 0, 0, 0);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(l), priv->source_chooser);
+ g_signal_connect(priv->source_chooser, "clicked",
+ G_CALLBACK(trg_torrent_add_dialog_source_click_cb),
+ obj);
+
+ l = gtk_label_new_with_mnemonic(_("_Destination folder:"));
+ gtk_misc_set_alignment(GTK_MISC(l), 0.0f, 0.5f);
+ gtk_table_attach(GTK_TABLE(t), l, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+
+ priv->dest_combo =
+ trg_destination_combo_new(priv->client,
+ TRG_PREFS_KEY_LAST_ADD_DESTINATION);
+ gtk_table_attach(GTK_TABLE(t), priv->dest_combo, 1, 2, 1,
+ 2, ~0, 0, 0, 0);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(l), priv->dest_combo);
+
+ gtk_widget_set_size_request(priv->file_list, 466u, 300u);
+ gtk_table_attach_defaults(GTK_TABLE(t), priv->file_list, 0, 2, 2, 3);
+
+ l = gtk_label_new_with_mnemonic(_("Apply to all:"));
+ gtk_misc_set_alignment(GTK_MISC(l), 0.0f, 0.5f);
+ gtk_table_attach(GTK_TABLE(t), l, 0, 1, 3, 4, ~0, 0, 0, 0);
+
+ applyall_combo =
+ trg_torrent_add_dialog_apply_all_combo_new(TRG_TORRENT_ADD_DIALOG
+ (obj));
+ gtk_table_attach(GTK_TABLE(t), applyall_combo, 1, 2, 3, 4, ~0, 0, 0,
+ 0);
+
+ l = gtk_label_new_with_mnemonic(_("Torrent _priority:"));
+ gtk_misc_set_alignment(GTK_MISC(l), 0.0f, 0.5f);
+ gtk_table_attach(GTK_TABLE(t), l, 0, 1, 4, 5, ~0, 0, 0, 0);
+
+ gtk_table_attach(GTK_TABLE(t), priv->priority_combo, 1, 2, 4,
+ 5, ~0, 0, 0, 0);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(l), priv->priority_combo);
+
+ gtk_table_attach(GTK_TABLE(t), priv->paused_check, 0, 2, 5,
+ 6, GTK_FILL, 0, 0, 0);
+
+ gtk_table_attach(GTK_TABLE(t), priv->delete_check, 0, 2, 6,
+ 7, GTK_FILL, 0, 0, 0);
+
+ gtr_dialog_set_content(GTK_DIALOG(obj), t);
+
+ g_signal_connect(G_OBJECT(obj), "response",
+ G_CALLBACK(trg_torrent_add_response_cb),
+ priv->parent);
+
+ return obj;
+}
+
+static void
+trg_torrent_add_dialog_class_init(TrgTorrentAddDialogClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgTorrentAddDialogPrivate));
+
+ object_class->set_property = trg_torrent_add_dialog_set_property;
+ object_class->get_property = trg_torrent_add_dialog_get_property;
+ object_class->constructor = trg_torrent_add_dialog_constructor;
+
+ g_object_class_install_property(object_class,
+ PROP_FILENAME,
+ g_param_spec_pointer("filenames",
+ "filenames",
+ "filenames",
+ G_PARAM_READWRITE
+ |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_CLIENT,
+ g_param_spec_pointer("client",
+ "client",
+ "client",
+ G_PARAM_READWRITE
+ |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_PARENT,
+ g_param_spec_object("parent",
+ "parent",
+ "parent",
+ TRG_TYPE_MAIN_WINDOW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+}
+
+static void trg_torrent_add_dialog_init(TrgTorrentAddDialog * self)
+{
+}
+
+TrgTorrentAddDialog *trg_torrent_add_dialog_new(TrgMainWindow * parent,
+ TrgClient * client,
+ GSList * filenames)
+{
+ return g_object_new(TRG_TYPE_TORRENT_ADD_DIALOG, "filenames",
+ filenames, "parent", parent, "client", client,
+ NULL);
+}
+
+void trg_torrent_add_dialog(TrgMainWindow * win, TrgClient * client)
+{
+ GtkWidget *w;
+ GtkWidget *c;
+ TrgPrefs *prefs = trg_client_get_prefs(client);
+
+ w = trg_torrent_add_dialog_generic(GTK_WINDOW(win), prefs);
+
+ c = gtk_check_button_new_with_mnemonic(_("Show _options dialog"));
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(c),
+ trg_prefs_get_bool(prefs,
+ TRG_PREFS_KEY_ADD_OPTIONS_DIALOG,
+ TRG_PREFS_GLOBAL));
+ gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(w), c);
+
+ if (gtk_dialog_run(GTK_DIALOG(w)) == GTK_RESPONSE_ACCEPT) {
+ GtkFileChooser *chooser = GTK_FILE_CHOOSER(w);
+ GtkToggleButton *tb =
+ GTK_TOGGLE_BUTTON(gtk_file_chooser_get_extra_widget(chooser));
+ gboolean showOptions = gtk_toggle_button_get_active(tb);
+ GSList *l = gtk_file_chooser_get_filenames(chooser);
+
+ trg_torrent_add_dialog_generic_save_dir(GTK_FILE_CHOOSER(w),
+ prefs);
+
+ if (showOptions) {
+ TrgTorrentAddDialog *dialog = trg_torrent_add_dialog_new(win,
+ client,
+ l);
+
+ gtk_widget_show_all(GTK_WIDGET(dialog));
+ } else {
+ struct add_torrent_threadfunc_args *args =
+ g_new0(struct add_torrent_threadfunc_args, 1);
+
+ args->list = l;
+ args->cb_data = win;
+ args->client = client;
+ args->extraArgs = FALSE;
+ args->flags = trg_prefs_get_add_flags(prefs);
+
+ launch_add_thread(args);
+ }
+ }
+
+ gtk_widget_destroy(GTK_WIDGET(w));
+}
diff --git a/src/trg-torrent-add-dialog.h b/src/trg-torrent-add-dialog.h
new file mode 100644
index 0000000..0f45e2c
--- /dev/null
+++ b/src/trg-torrent-add-dialog.h
@@ -0,0 +1,73 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_TORRENT_ADD_DIALOG_H_
+#define TRG_TORRENT_ADD_DIALOG_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "trg-client.h"
+#include "trg-main-window.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_TORRENT_ADD_DIALOG trg_torrent_add_dialog_get_type()
+#define TRG_TORRENT_ADD_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_TORRENT_ADD_DIALOG, TrgTorrentAddDialog))
+#define TRG_TORRENT_ADD_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_TORRENT_ADD_DIALOG, TrgTorrentAddDialogClass))
+#define TRG_IS_TORRENT_ADD_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_TORRENT_ADD_DIALOG))
+#define TRG_IS_TORRENT_ADD_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_TORRENT_ADD_DIALOG))
+#define TRG_TORRENT_ADD_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_TORRENT_ADD_DIALOG, TrgTorrentAddDialogClass))
+ typedef struct {
+ GtkDialog parent;
+} TrgTorrentAddDialog;
+
+typedef struct {
+ GtkDialogClass parent_class;
+} TrgTorrentAddDialogClass;
+
+/* Use synchronous dispatch() in our dedicated thread function.
+ * This means torrents are added in sequence, instead of dispatch_async()
+ * working concurrently for each upload.
+ */
+
+struct add_torrent_threadfunc_args {
+ GSList *list;
+ TrgClient *client;
+ gpointer cb_data;
+ guint flags;
+ gchar *dir;
+ gint priority;
+ gboolean extraArgs;
+};
+
+GType trg_torrent_add_dialog_get_type(void);
+
+TrgTorrentAddDialog *trg_torrent_add_dialog_new(TrgMainWindow * win,
+ TrgClient * client,
+ GSList * filenames);
+void trg_torrent_add_dialog(TrgMainWindow * win, TrgClient * client);
+void launch_add_thread(struct add_torrent_threadfunc_args *args);
+
+G_END_DECLS
+#endif /* TRG_TORRENT_ADD_DIALOG_H_ */
diff --git a/src/trg-torrent-add-url-dialog.c b/src/trg-torrent-add-url-dialog.c
new file mode 100644
index 0000000..7f6335e
--- /dev/null
+++ b/src/trg-torrent-add-url-dialog.c
@@ -0,0 +1,165 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-client.h"
+#include "trg-main-window.h"
+#include "trg-torrent-add-url-dialog.h"
+#include "hig.h"
+#include "requests.h"
+
+G_DEFINE_TYPE(TrgTorrentAddUrlDialog, trg_torrent_add_url_dialog,
+ GTK_TYPE_DIALOG)
+#define TRG_TORRENT_ADD_URL_DIALOG_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_TORRENT_ADD_URL_DIALOG, TrgTorrentAddUrlDialogPrivate))
+typedef struct _TrgTorrentAddUrlDialogPrivate
+ TrgTorrentAddUrlDialogPrivate;
+
+struct _TrgTorrentAddUrlDialogPrivate {
+ TrgClient *client;
+ TrgMainWindow *win;
+ GtkWidget *urlEntry, *startCheck, *addButton;
+};
+
+static void
+trg_torrent_add_url_dialog_class_init(TrgTorrentAddUrlDialogClass * klass)
+{
+ g_type_class_add_private(klass, sizeof(TrgTorrentAddUrlDialogPrivate));
+}
+
+static gboolean has_dht_support(TrgTorrentAddUrlDialog * dlg)
+{
+ TrgTorrentAddUrlDialogPrivate *priv =
+ TRG_TORRENT_ADD_URL_DIALOG_GET_PRIVATE(dlg);
+ JsonObject *session = trg_client_get_session(priv->client);
+ return session_get_dht_enabled(session);
+}
+
+static void show_dht_not_enabled_warning(TrgTorrentAddUrlDialog * dlg)
+{
+ gchar *msg =
+ _
+ ("You are trying to add a magnet torrent, but DHT is disabled. Distributed Hash Table (DHT) should be enabled in remote settings.");
+ GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(dlg),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_OK,
+ "%s", msg);
+ gtk_window_set_title(GTK_WINDOW(dialog), _("Error"));
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+}
+
+static void
+trg_torrent_add_url_response_cb(TrgTorrentAddUrlDialog * dlg, gint res_id,
+ gpointer data)
+{
+ TrgTorrentAddUrlDialogPrivate *priv =
+ TRG_TORRENT_ADD_URL_DIALOG_GET_PRIVATE(dlg);
+
+ if (res_id == GTK_RESPONSE_ACCEPT) {
+ JsonNode *request;
+ const gchar *entryText =
+ gtk_entry_get_text(GTK_ENTRY(priv->urlEntry));
+
+ if (g_str_has_prefix(entryText, "magnet:")
+ && !has_dht_support(dlg))
+ show_dht_not_enabled_warning(dlg);
+
+ request =
+ torrent_add_url(entryText,
+ gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON(priv->startCheck)));
+ dispatch_async(priv->client, request,
+ on_generic_interactive_action, data);
+ }
+
+ gtk_widget_destroy(GTK_WIDGET(dlg));
+}
+
+static void url_entry_changed(GtkWidget * w, gpointer data)
+{
+ TrgTorrentAddUrlDialogPrivate *priv =
+ TRG_TORRENT_ADD_URL_DIALOG_GET_PRIVATE(data);
+ gtk_widget_set_sensitive(priv->addButton,
+ gtk_entry_get_text_length(GTK_ENTRY(w)) > 0);
+}
+
+static void trg_torrent_add_url_dialog_init(TrgTorrentAddUrlDialog * self)
+{
+ TrgTorrentAddUrlDialogPrivate *priv =
+ TRG_TORRENT_ADD_URL_DIALOG_GET_PRIVATE(self);
+ GtkWidget *w, *t, *contentvbox;
+ guint row = 0;
+
+ contentvbox = gtk_dialog_get_content_area(GTK_DIALOG(self));
+
+ t = hig_workarea_create();
+
+ w = priv->urlEntry = gtk_entry_new();
+ g_signal_connect(w, "changed", G_CALLBACK(url_entry_changed), self);
+ hig_workarea_add_row(t, &row, _("URL:"), w, NULL);
+
+ priv->startCheck =
+ hig_workarea_add_wide_checkbutton(t, &row, _("Start Paused"),
+ FALSE);
+
+ gtk_window_set_title(GTK_WINDOW(self), _("Add torrent from URL"));
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(self), TRUE);
+
+ gtk_dialog_add_button(GTK_DIALOG(self), GTK_STOCK_CLOSE,
+ GTK_RESPONSE_CANCEL);
+ priv->addButton =
+ gtk_dialog_add_button(GTK_DIALOG(self), GTK_STOCK_ADD,
+ GTK_RESPONSE_ACCEPT);
+ gtk_widget_set_sensitive(priv->addButton, FALSE);
+
+ gtk_container_set_border_width(GTK_CONTAINER(self), GUI_PAD);
+
+ gtk_dialog_set_default_response(GTK_DIALOG(self), GTK_RESPONSE_ACCEPT);
+
+ gtk_dialog_set_alternative_button_order(GTK_DIALOG(self),
+ GTK_RESPONSE_ACCEPT,
+ GTK_RESPONSE_CANCEL, -1);
+
+ gtk_container_set_border_width(GTK_CONTAINER(t), GUI_PAD);
+
+ gtk_box_pack_start(GTK_BOX(contentvbox), t, TRUE, TRUE, 0);
+}
+
+TrgTorrentAddUrlDialog *trg_torrent_add_url_dialog_new(TrgMainWindow * win,
+ TrgClient * client)
+{
+ GObject *obj = g_object_new(TRG_TYPE_TORRENT_ADD_URL_DIALOG, NULL);
+ TrgTorrentAddUrlDialogPrivate *priv =
+ TRG_TORRENT_ADD_URL_DIALOG_GET_PRIVATE(obj);
+
+ priv->client = client;
+ priv->win = win;
+
+ gtk_window_set_transient_for(GTK_WINDOW(obj), GTK_WINDOW(win));
+ g_signal_connect(G_OBJECT(obj),
+ "response",
+ G_CALLBACK(trg_torrent_add_url_response_cb), win);
+
+ return TRG_TORRENT_ADD_URL_DIALOG(obj);
+}
diff --git a/src/trg-torrent-add-url-dialog.h b/src/trg-torrent-add-url-dialog.h
new file mode 100644
index 0000000..ea446ed
--- /dev/null
+++ b/src/trg-torrent-add-url-dialog.h
@@ -0,0 +1,55 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_TORRENT_ADD_URL_DIALOG_H_
+#define TRG_TORRENT_ADD_URL_DIALOG_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "trg-client.h"
+#include "trg-main-window.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_TORRENT_ADD_URL_DIALOG trg_torrent_add_url_dialog_get_type()
+#define TRG_TORRENT_ADD_URL_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_TORRENT_ADD_URL_DIALOG, TrgTorrentAddUrlDialog))
+#define TRG_TORRENT_ADD_URL_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_TORRENT_ADD_URL_DIALOG, TrgTorrentAddUrlDialogClass))
+#define TRG_IS_TORRENT_ADD_URL_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_TORRENT_ADD_URL_DIALOG))
+#define TRG_IS_TORRENT_ADD_URL_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_TORRENT_ADD_URL_DIALOG))
+#define TRG_TORRENT_ADD_URL_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_TORRENT_ADD_URL_DIALOG, TrgTorrentAddUrlDialogClass))
+ typedef struct {
+ GtkDialog parent;
+} TrgTorrentAddUrlDialog;
+
+typedef struct {
+ GtkDialogClass parent_class;
+} TrgTorrentAddUrlDialogClass;
+
+GType trg_torrent_add_url_dialog_get_type(void);
+
+TrgTorrentAddUrlDialog *trg_torrent_add_url_dialog_new(TrgMainWindow * win,
+ TrgClient * client);
+
+G_END_DECLS
+#endif /* TRG_TORRENT_ADD_URL_DIALOG_H_ */
diff --git a/src/trg-torrent-graph.c b/src/trg-torrent-graph.c
new file mode 100644
index 0000000..40d3b4a
--- /dev/null
+++ b/src/trg-torrent-graph.c
@@ -0,0 +1,640 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * This graph drawing code was taken from gnome-system-monitor (load-graph.cpp)
+ * Converted the class from C++ to GObject, substituted out some STL (C++)
+ * functions, and removed the unecessary parts for memory/cpu.
+ *
+ * Would be nice if it supported configurable timespans. This could possibly
+ * be replaced with ubergraph, which is a graphing widget for GTK (C) also based
+ * on this widget but with some improvements I didn't do.
+ */
+
+#include "trg-torrent-graph.h"
+
+#if TRG_WITH_GRAPH
+
+#include <math.h>
+#include <glib.h>
+#include <cairo.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include "util.h"
+
+/* damn you freebsd */
+#define log2(x) (log(x)/0.69314718055994530942)
+
+#define GRAPH_NUM_POINTS 62
+#define GRAPH_MIN_HEIGHT 40
+#define GRAPH_NUM_LINES 2
+#define GRAPH_NUM_DATA_BLOCK_ELEMENTS GRAPH_NUM_POINTS * GRAPH_NUM_LINES
+#define GRAPH_OUT_COLOR "#2D7DB3"
+#define GRAPH_IN_COLOR "#844798"
+#define GRAPH_LINE_WIDTH 3
+#define GRAPH_FRAME_WIDTH 4
+
+G_DEFINE_TYPE(TrgTorrentGraph, trg_torrent_graph, GTK_TYPE_VBOX)
+#define TRG_TORRENT_GRAPH_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_TORRENT_GRAPH, TrgTorrentGraphPrivate))
+typedef struct _TrgTorrentGraphPrivate TrgTorrentGraphPrivate;
+
+struct _TrgTorrentGraphPrivate {
+ double fontsize;
+ double rmargin;
+ double indent;
+ guint speed;
+ guint draw_width, draw_height;
+ guint render_counter;
+ guint frames_per_unit;
+ guint graph_dely;
+ guint real_draw_height;
+ double graph_delx;
+ guint graph_buffer_offset;
+
+ GdkColor colors[GRAPH_NUM_LINES];
+
+ float data_block[GRAPH_NUM_POINTS * GRAPH_NUM_LINES];
+ GList *points;
+
+ GtkWidget *disp;
+ GdkGC *gc;
+ GdkDrawable *background;
+ guint timer_index;
+ gboolean draw;
+ guint64 out, in;
+ unsigned int max;
+ unsigned values[GRAPH_NUM_POINTS];
+ size_t cur;
+ GtkStyle *style;
+
+ GtkWidget *label_in;
+ GtkWidget *label_out;
+};
+
+static int trg_torrent_graph_update(gpointer user_data);
+
+static void
+trg_torrent_graph_get_property(GObject * object, guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ switch (property_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_torrent_graph_set_property(GObject * object, guint property_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ switch (property_id) {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+void trg_torrent_graph_draw_background(TrgTorrentGraph * g)
+{
+ TrgTorrentGraphPrivate *priv;
+ GtkAllocation allocation;
+
+ double dash[2] = { 1.0, 2.0 };
+ cairo_t *cr;
+ guint i;
+ unsigned num_bars;
+ char *caption;
+ cairo_text_extents_t extents;
+ unsigned rate;
+ unsigned total_seconds;
+
+ priv = TRG_TORRENT_GRAPH_GET_PRIVATE(g);
+
+ num_bars = trg_torrent_graph_get_num_bars(g);
+ priv->graph_dely = (priv->draw_height - 15) / num_bars; /* round to int to avoid AA blur */
+ priv->real_draw_height = priv->graph_dely * num_bars;
+ priv->graph_delx =
+ (priv->draw_width - 2.0 - priv->rmargin -
+ priv->indent) / (GRAPH_NUM_POINTS - 3);
+ priv->graph_buffer_offset =
+ (int) (1.5 * priv->graph_delx) + GRAPH_FRAME_WIDTH;
+
+ gtk_widget_get_allocation(priv->disp, &allocation);
+ priv->background =
+ gdk_pixmap_new(GDK_DRAWABLE(gtk_widget_get_window(priv->disp)),
+ allocation.width, allocation.height, -1);
+ cr = gdk_cairo_create(priv->background);
+
+ gdk_cairo_set_source_color(cr, &priv->style->bg[GTK_STATE_NORMAL]);
+ cairo_paint(cr);
+ cairo_translate(cr, GRAPH_FRAME_WIDTH, GRAPH_FRAME_WIDTH);
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ cairo_rectangle(cr, priv->rmargin + priv->indent, 0,
+ priv->draw_width - priv->rmargin - priv->indent,
+ priv->real_draw_height);
+
+ cairo_fill(cr);
+
+ cairo_set_line_width(cr, 1.0);
+ cairo_set_dash(cr, dash, 2, 0);
+ cairo_set_font_size(cr, priv->fontsize);
+
+ for (i = 0; i <= num_bars; ++i) {
+ double y;
+ gchar caption[32];
+
+ if (i == 0)
+ y = 0.5 + priv->fontsize / 2.0;
+ else if (i == num_bars)
+ y = i * priv->graph_dely + 0.5;
+ else
+ y = i * priv->graph_dely + priv->fontsize / 2.0;
+
+ gdk_cairo_set_source_color(cr, &priv->style->fg[GTK_STATE_NORMAL]);
+ rate = priv->max - (i * priv->max / num_bars);
+ trg_strlspeed(caption, (gint64) (rate / 1024));
+ cairo_text_extents(cr, caption, &extents);
+ cairo_move_to(cr, priv->indent - extents.width + 20, y);
+ cairo_show_text(cr, caption);
+ }
+
+ cairo_stroke(cr);
+
+ cairo_set_dash(cr, dash, 2, 1.5);
+
+ total_seconds = priv->speed * (GRAPH_NUM_POINTS - 2) / 1000;
+
+ for (i = 0; i < 7; i++) {
+ unsigned seconds;
+ const char *format;
+ double x =
+ (i) * (priv->draw_width - priv->rmargin - priv->indent) / 6;
+ cairo_set_source_rgba(cr, 0, 0, 0, 0.75);
+ cairo_move_to(cr, (ceil(x) + 0.5) + priv->rmargin + priv->indent,
+ 0.5);
+ cairo_line_to(cr, (ceil(x) + 0.5) + priv->rmargin + priv->indent,
+ priv->real_draw_height + 4.5);
+ cairo_stroke(cr);
+ seconds = total_seconds - i * total_seconds / 6;
+ if (i == 0)
+ format = "%u seconds";
+ else
+ format = "%u";
+ caption = g_strdup_printf(format, seconds);
+ cairo_text_extents(cr, caption, &extents);
+ cairo_move_to(cr,
+ ((ceil(x) + 0.5) + priv->rmargin + priv->indent) -
+ (extents.width / 2), priv->draw_height);
+ gdk_cairo_set_source_color(cr, &priv->style->fg[GTK_STATE_NORMAL]);
+ cairo_show_text(cr, caption);
+ g_free(caption);
+ }
+
+ cairo_stroke(cr);
+ cairo_destroy(cr);
+}
+
+void trg_torrent_graph_set_nothing(TrgTorrentGraph * g)
+{
+ TrgTorrentGraphPrivate *priv = TRG_TORRENT_GRAPH_GET_PRIVATE(g);
+
+ priv->in = priv->out = 0;
+}
+
+void
+trg_torrent_graph_set_speed(TrgTorrentGraph * g,
+ trg_torrent_model_update_stats * stats)
+{
+ TrgTorrentGraphPrivate *priv = TRG_TORRENT_GRAPH_GET_PRIVATE(g);
+
+ priv->in = (guint64) stats->downRateTotal;
+ priv->out = (guint64) stats->upRateTotal;
+}
+
+static gboolean
+trg_torrent_graph_configure(GtkWidget * widget,
+ GdkEventConfigure * event, gpointer data_ptr)
+{
+ GtkAllocation allocation;
+ TrgTorrentGraph *g = TRG_TORRENT_GRAPH(data_ptr);
+ TrgTorrentGraphPrivate *priv = TRG_TORRENT_GRAPH_GET_PRIVATE(data_ptr);
+
+ gtk_widget_get_allocation(widget, &allocation);
+ priv->draw_width = allocation.width - 2 * GRAPH_FRAME_WIDTH;
+ priv->draw_height = allocation.height - 2 * GRAPH_FRAME_WIDTH;
+
+ trg_torrent_graph_clear_background(g);
+
+ if (priv->gc == NULL)
+ priv->gc = gdk_gc_new(GDK_DRAWABLE(gtk_widget_get_window(widget)));
+
+ trg_torrent_graph_draw(g);
+
+ return TRUE;
+}
+
+static gboolean
+trg_torrent_graph_expose(GtkWidget * widget,
+ GdkEventExpose * event, gpointer data_ptr)
+{
+ TrgTorrentGraph *g = TRG_TORRENT_GRAPH(data_ptr);
+ TrgTorrentGraphPrivate *priv = TRG_TORRENT_GRAPH_GET_PRIVATE(data_ptr);
+
+ GtkAllocation allocation;
+ GdkWindow *window;
+ cairo_t *cr;
+
+ guint i, j;
+ float *fp;
+ gdouble sample_width, x_offset;
+
+ if (priv->background == NULL)
+ trg_torrent_graph_draw_background(g);
+
+ window = gtk_widget_get_window(priv->disp);
+ gtk_widget_get_allocation(priv->disp, &allocation);
+ gdk_draw_drawable(window,
+ priv->gc,
+ priv->background,
+ 0, 0, 0, 0, allocation.width, allocation.height);
+
+ sample_width =
+ (float) (priv->draw_width - priv->rmargin -
+ priv->indent) / (float) GRAPH_NUM_POINTS;
+ x_offset = priv->draw_width - priv->rmargin + (sample_width * 2);
+ x_offset +=
+ priv->rmargin -
+ ((sample_width / priv->frames_per_unit) * priv->render_counter);
+
+ cr = gdk_cairo_create(window);
+
+ cairo_set_line_width(cr, GRAPH_LINE_WIDTH);
+ cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
+ cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND);
+ cairo_rectangle(cr,
+ priv->rmargin + priv->indent + GRAPH_FRAME_WIDTH + 1,
+ GRAPH_FRAME_WIDTH - 1,
+ priv->draw_width - priv->rmargin - priv->indent - 1,
+ priv->real_draw_height + GRAPH_FRAME_WIDTH - 1);
+ cairo_clip(cr);
+
+ for (j = 0; j < GRAPH_NUM_LINES; ++j) {
+ GList *li = priv->points;
+ fp = (float *) li->data;
+ cairo_move_to(cr, x_offset,
+ (1.0f - fp[j]) * priv->real_draw_height);
+ gdk_cairo_set_source_color(cr, &(priv->colors[j]));
+
+ i = 0;
+ for (li = g_list_next(li); li != NULL; li = g_list_next(li)) {
+ GList *lli = g_list_previous(li);
+ float *lfp = (float *) lli->data;
+ fp = (float *) li->data;
+
+ i++;
+
+ if (fp[j] == -1.0f)
+ continue;
+
+ cairo_curve_to(cr,
+ x_offset - ((i - 0.5f) * priv->graph_delx),
+ (1.0f - lfp[j]) * priv->real_draw_height + 3.5f,
+ x_offset - ((i - 0.5f) * priv->graph_delx),
+ (1.0f - fp[j]) * priv->real_draw_height + 3.5f,
+ x_offset - (i * priv->graph_delx),
+ (1.0f - fp[j]) * priv->real_draw_height + 3.5f);
+ }
+ cairo_stroke(cr);
+ }
+
+ cairo_destroy(cr);
+
+ return TRUE;
+}
+
+void trg_torrent_graph_stop(TrgTorrentGraph * g)
+{
+ TrgTorrentGraphPrivate *priv = TRG_TORRENT_GRAPH_GET_PRIVATE(g);
+
+ priv->draw = FALSE;
+}
+
+static void trg_torrent_graph_dispose(GObject * object)
+{
+ TrgTorrentGraph *g = TRG_TORRENT_GRAPH(object);
+ TrgTorrentGraphPrivate *priv = TRG_TORRENT_GRAPH_GET_PRIVATE(g);
+
+ trg_torrent_graph_stop(g);
+
+ if (priv->timer_index)
+ g_source_remove(priv->timer_index);
+
+ trg_torrent_graph_clear_background(g);
+
+ G_OBJECT_CLASS(trg_torrent_graph_parent_class)->dispose(object);
+}
+
+void trg_torrent_graph_start(TrgTorrentGraph * g)
+{
+ TrgTorrentGraphPrivate *priv = TRG_TORRENT_GRAPH_GET_PRIVATE(g);
+
+ if (!priv->timer_index) {
+ trg_torrent_graph_update(g);
+
+ priv->timer_index =
+ g_timeout_add(priv->speed / priv->frames_per_unit,
+ trg_torrent_graph_update, g);
+ }
+
+ priv->draw = TRUE;
+}
+
+static unsigned get_max_value_element(TrgTorrentGraph * g)
+{
+ TrgTorrentGraphPrivate *priv = TRG_TORRENT_GRAPH_GET_PRIVATE(g);
+ unsigned r = 0;
+ int i;
+
+ for (i = 0; i < GRAPH_NUM_POINTS; i++)
+ if (priv->values[i] > r)
+ r = priv->values[i];
+
+ return r;
+}
+
+static void trg_torrent_graph_update_net(TrgTorrentGraph * g)
+{
+ TrgTorrentGraphPrivate *priv = TRG_TORRENT_GRAPH_GET_PRIVATE(g);
+
+ unsigned max, new_max, bak_max, pow2, base10, coef10, factor10,
+ num_bars;
+
+ float scale;
+ char speed[32];
+ gchar *labelMarkup;
+ GList *li;
+
+ float *fp = (float *) (priv->points->data);
+
+ GList *last = g_list_last(priv->points);
+ float *lastData = last->data;
+
+ priv->points = g_list_delete_link(priv->points, last);
+ priv->points = g_list_prepend(priv->points, lastData);
+
+ fp[0] = 1.0f * priv->out / priv->max;
+ fp[1] = 1.0f * priv->in / priv->max;
+
+ trg_strlspeed(speed, (gint64) (priv->out / disk_K));
+ labelMarkup =
+ g_markup_printf_escaped("<span font_size=\"small\" color=\""
+ GRAPH_OUT_COLOR "\">%s: %s</span>",
+ _("Total Uploading"), speed);
+ gtk_label_set_markup(GTK_LABEL(priv->label_out), labelMarkup);
+ g_free(labelMarkup);
+
+ trg_strlspeed(speed, (gint64) (priv->in / 1024));
+ labelMarkup =
+ g_markup_printf_escaped("<span font_size=\"small\" color=\""
+ GRAPH_IN_COLOR "\">%s: %s</span>",
+ _("Total Downloading"), speed);
+ gtk_label_set_markup(GTK_LABEL(priv->label_in), labelMarkup);
+ g_free(labelMarkup);
+
+ max = MAX(priv->in, priv->out);
+ priv->values[priv->cur] = max;
+ priv->cur = (priv->cur + 1) % GRAPH_NUM_POINTS;
+
+ if (max >= priv->max)
+ new_max = max;
+ else
+ new_max = get_max_value_element(g);
+
+ bak_max = new_max;
+ new_max = 1.1 * new_max;
+ new_max = MAX(new_max, 1024U);
+ pow2 = floor(log2(new_max));
+ base10 = pow2 / 10;
+ coef10 = ceil(new_max / (double) (1UL << (base10 * 10)));
+ factor10 = pow(10.0, floor(log10(coef10)));
+ coef10 = ceil(coef10 / (double) (factor10)) * factor10;
+
+ num_bars = trg_torrent_graph_get_num_bars(g);
+
+ if (coef10 % num_bars != 0)
+ coef10 = coef10 + (num_bars - coef10 % num_bars);
+
+ new_max = coef10 * (1UL << (base10 * 10));
+
+ if (bak_max > new_max) {
+ new_max = bak_max;
+ }
+
+ if ((0.8 * priv->max) < new_max && new_max <= priv->max)
+ return;
+
+ scale = 1.0f * priv->max / new_max;
+
+ for (li = priv->points; li != NULL; li = g_list_next(li)) {
+ float *fp = (float *) li->data;
+ if (fp[0] >= 0.0f) {
+ fp[0] *= scale;
+ fp[1] *= scale;
+ }
+ }
+
+ priv->max = new_max;
+
+ trg_torrent_graph_clear_background(g);
+}
+
+static GObject *trg_torrent_graph_constructor(GType type,
+ guint
+ n_construct_properties,
+ GObjectConstructParam *
+ construct_params)
+{
+ GObject *object;
+ TrgTorrentGraphPrivate *priv;
+ GtkWidget *hbox;
+ int i;
+
+ object =
+ G_OBJECT_CLASS
+ (trg_torrent_graph_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+ priv = TRG_TORRENT_GRAPH_GET_PRIVATE(object);
+
+ priv->draw_width = 0;
+ priv->draw_height = 0;
+ priv->render_counter = 0;
+ priv->graph_dely = 0;
+ priv->real_draw_height = 0;
+ priv->graph_delx = 0.0;
+ priv->graph_buffer_offset = 0;
+ priv->disp = NULL;
+ priv->gc = NULL;
+ priv->background = NULL;
+ priv->timer_index = 0;
+ priv->draw = FALSE;
+
+ priv->fontsize = 8.0;
+ priv->frames_per_unit = 10;
+ priv->rmargin = 3.5 * priv->fontsize;
+ priv->indent = 24.0;
+ priv->out = 0;
+ priv->in = 0;
+ priv->cur = 0;
+
+ priv->speed = 1000;
+ priv->max = 1024;
+
+ hbox = gtk_hbox_new(FALSE, 0);
+
+ priv->label_in = gtk_label_new(NULL);
+ priv->label_out = gtk_label_new(NULL);
+
+ gtk_box_pack_start(GTK_BOX(hbox), priv->label_in, FALSE, FALSE, 65);
+ gtk_box_pack_start(GTK_BOX(hbox), priv->label_out, FALSE, FALSE, 0);
+
+ gtk_box_pack_start(GTK_BOX(object), hbox, FALSE, FALSE, 2);
+
+ gdk_color_parse(GRAPH_OUT_COLOR, &priv->colors[0]);
+ gdk_color_parse(GRAPH_IN_COLOR, &priv->colors[1]);
+
+ priv->timer_index = 0;
+ priv->render_counter = (priv->frames_per_unit - 1);
+ priv->draw = FALSE;
+
+ gtk_box_set_homogeneous(GTK_BOX(object), FALSE);
+
+ priv->disp = gtk_drawing_area_new();
+ g_signal_connect(G_OBJECT(priv->disp), "expose_event",
+ G_CALLBACK(trg_torrent_graph_expose), object);
+ g_signal_connect(G_OBJECT(priv->disp), "configure_event",
+ G_CALLBACK(trg_torrent_graph_configure), object);
+
+ gtk_widget_set_events(priv->disp, GDK_EXPOSURE_MASK);
+
+ gtk_box_pack_start(GTK_BOX(object), priv->disp, TRUE, TRUE, 0);
+
+ priv->points = NULL;
+ for (i = 0; i < GRAPH_NUM_DATA_BLOCK_ELEMENTS; i++) {
+ priv->data_block[i] = -1.0f;
+ if (i % GRAPH_NUM_LINES == 0)
+ priv->points =
+ g_list_append(priv->points, &priv->data_block[i]);
+ }
+
+ return object;
+}
+
+static void trg_torrent_graph_class_init(TrgTorrentGraphClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgTorrentGraphPrivate));
+
+ object_class->get_property = trg_torrent_graph_get_property;
+ object_class->set_property = trg_torrent_graph_set_property;
+ object_class->dispose = trg_torrent_graph_dispose;
+ object_class->constructor = trg_torrent_graph_constructor;
+}
+
+static void trg_torrent_graph_init(TrgTorrentGraph * self)
+{
+}
+
+TrgTorrentGraph *trg_torrent_graph_new(GtkStyle * style)
+{
+ GObject *obj = g_object_new(TRG_TYPE_TORRENT_GRAPH, NULL);
+ TrgTorrentGraphPrivate *priv = TRG_TORRENT_GRAPH_GET_PRIVATE(obj);
+
+ priv->style = style;
+
+ return TRG_TORRENT_GRAPH(obj);
+}
+
+void trg_torrent_graph_draw(TrgTorrentGraph * g)
+{
+ TrgTorrentGraphPrivate *priv = TRG_TORRENT_GRAPH_GET_PRIVATE(g);
+
+ gtk_widget_queue_draw(priv->disp);
+}
+
+void trg_torrent_graph_clear_background(TrgTorrentGraph * g)
+{
+ TrgTorrentGraphPrivate *priv = TRG_TORRENT_GRAPH_GET_PRIVATE(g);
+
+ if (priv->background) {
+ g_object_unref(priv->background);
+ priv->background = NULL;
+ }
+}
+
+
+static gboolean trg_torrent_graph_update(gpointer user_data)
+{
+ TrgTorrentGraph *g = TRG_TORRENT_GRAPH(user_data);
+ TrgTorrentGraphPrivate *priv =
+ TRG_TORRENT_GRAPH_GET_PRIVATE(user_data);
+
+ if (priv->render_counter == priv->frames_per_unit - 1)
+ trg_torrent_graph_update_net(g);
+
+ if (priv->draw)
+ trg_torrent_graph_draw(g);
+
+ priv->render_counter++;
+
+ if (priv->render_counter >= priv->frames_per_unit)
+ priv->render_counter = 0;
+
+ return TRUE;
+}
+
+unsigned trg_torrent_graph_get_num_bars(TrgTorrentGraph * g)
+{
+ TrgTorrentGraphPrivate *priv = TRG_TORRENT_GRAPH_GET_PRIVATE(g);
+ unsigned n;
+
+ switch ((int) (priv->draw_height / (priv->fontsize + 14))) {
+ case 0:
+ case 1:
+ n = 1;
+ break;
+ case 2:
+ case 3:
+ n = 2;
+ break;
+ case 4:
+ n = 4;
+ break;
+ default:
+ n = 5;
+ break;
+ }
+
+ return n;
+}
+
+#endif
diff --git a/src/trg-torrent-graph.h b/src/trg-torrent-graph.h
new file mode 100644
index 0000000..49a9b87
--- /dev/null
+++ b/src/trg-torrent-graph.h
@@ -0,0 +1,57 @@
+/* trg-torrent-graph.h */
+
+#ifndef _TRG_TORRENT_GRAPH
+#define _TRG_TORRENT_GRAPH
+
+#include <gtk/gtk.h>
+
+#define TRG_WITH_GRAPH !GTK_CHECK_VERSION( 3, 0, 0 )
+
+#if TRG_WITH_GRAPH
+
+#include <glib-object.h>
+#include "trg-torrent-model.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_TORRENT_GRAPH trg_torrent_graph_get_type()
+#define TRG_TORRENT_GRAPH(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_TORRENT_GRAPH, TrgTorrentGraph))
+#define TRG_TORRENT_GRAPH_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_TORRENT_GRAPH, TrgTorrentGraphClass))
+#define TRG_IS_TORRENT_GRAPH(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_TORRENT_GRAPH))
+#define TRG_IS_TORRENT_GRAPH_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_TORRENT_GRAPH))
+#define TRG_TORRENT_GRAPH_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_TORRENT_GRAPH, TrgTorrentGraphClass))
+ typedef struct {
+ GtkVBox parent;
+} TrgTorrentGraph;
+
+typedef struct {
+ GtkVBoxClass parent_class;
+} TrgTorrentGraphClass;
+
+GType trg_torrent_graph_get_type(void);
+
+TrgTorrentGraph *trg_torrent_graph_new(GtkStyle * style);
+
+unsigned trg_torrent_graph_get_num_bars(TrgTorrentGraph * g);
+
+void trg_torrent_graph_clear_background();
+
+void trg_torrent_graph_draw(TrgTorrentGraph * g);
+
+void trg_torrent_graph_start(TrgTorrentGraph * g);
+
+void trg_torrent_graph_stop(TrgTorrentGraph * g);
+
+void trg_torrent_graph_change_speed(TrgTorrentGraph * g, guint new_speed);
+
+void trg_torrent_graph_set_speed(TrgTorrentGraph * g,
+ trg_torrent_model_update_stats * stats);
+void trg_torrent_graph_set_nothing(TrgTorrentGraph * g);
+
+G_END_DECLS
+#endif
+#endif /* _TRG_TORRENT_GRAPH */
diff --git a/src/trg-torrent-model.c b/src/trg-torrent-model.c
new file mode 100644
index 0000000..b1e98e5
--- /dev/null
+++ b/src/trg-torrent-model.c
@@ -0,0 +1,851 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <string.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+#include <glib/gi18n.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "torrent.h"
+#include "json.h"
+#include "trg-torrent-model.h"
+#include "protocol-constants.h"
+#include "trg-model.h"
+#include "util.h"
+
+/* An extension of TrgModel (which is an extension of GtkListStore) which
+ * updates from a JSON torrent-get response. It handles a number of different
+ * update modes.
+ * 1) The first update.
+ * 2) A full update.
+ * 3) An active-only update.
+ * 4) Individual torrent updates.
+ *
+ * Other stuff it does.
+ * 1) Populates a stats struct with speeds/state counts as it works through the
+ * response.
+ * 2) Emits signals if something is added or removed. This is used by the state
+ * selector so it doesn't have to refresh itself on every update.
+ * 3) Added or completed signals, for libnotify notifications.
+ * 4) Maintains the torrent hash table (by ID).
+ * (and provide a lookup function which outputs an iter and/or JSON object.)
+ * 5) If the download directory is new/changed, create a short version if there
+ * is one (duplicate it not) for the state selector to filter/populate against.
+ * 6) Shorten the tracker announce URL.
+ */
+
+enum {
+ TMODEL_TORRENT_COMPLETED,
+ TMODEL_UPDATE,
+ TMODEL_TORRENT_ADDED,
+ TMODEL_STATE_CHANGED,
+ TMODEL_SIGNAL_COUNT
+};
+
+#define PROP_REMOVE_IN_PROGRESS "remove-in-progress"
+
+static guint signals[TMODEL_SIGNAL_COUNT] = { 0 };
+
+G_DEFINE_TYPE(TrgTorrentModel, trg_torrent_model, GTK_TYPE_LIST_STORE)
+#define TRG_TORRENT_MODEL_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_TORRENT_MODEL, TrgTorrentModelPrivate))
+typedef struct _TrgTorrentModelPrivate TrgTorrentModelPrivate;
+
+struct _TrgTorrentModelPrivate {
+ GHashTable *ht;
+ GRegex *urlHostRegex;
+ trg_torrent_model_update_stats stats;
+};
+
+static void trg_torrent_model_dispose(GObject * object)
+{
+ TrgTorrentModelPrivate *priv = TRG_TORRENT_MODEL_GET_PRIVATE(object);
+ g_hash_table_destroy(priv->ht);
+ G_OBJECT_CLASS(trg_torrent_model_parent_class)->dispose(object);
+}
+
+static void
+update_torrent_iter(TrgTorrentModel * model, TrgClient * tc, gint64 rpcv,
+ gint64 serial, GtkTreeIter * iter, JsonObject * t,
+ trg_torrent_model_update_stats * stats,
+ guint * whatsChanged);
+
+static void trg_torrent_model_class_init(TrgTorrentModelClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgTorrentModelPrivate));
+ object_class->dispose = trg_torrent_model_dispose;
+
+ signals[TMODEL_TORRENT_COMPLETED] = g_signal_new("torrent-completed",
+ G_TYPE_FROM_CLASS
+ (object_class),
+ G_SIGNAL_RUN_LAST |
+ G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET
+ (TrgTorrentModelClass,
+ torrent_completed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ signals[TMODEL_UPDATE] = g_signal_new("update",
+ G_TYPE_FROM_CLASS
+ (object_class),
+ G_SIGNAL_RUN_LAST |
+ G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET
+ (TrgTorrentModelClass,
+ update),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[TMODEL_TORRENT_ADDED] = g_signal_new("torrent-added",
+ G_TYPE_FROM_CLASS
+ (object_class),
+ G_SIGNAL_RUN_LAST |
+ G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET
+ (TrgTorrentModelClass,
+ torrent_added), NULL,
+ NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ signals[TMODEL_STATE_CHANGED] = g_signal_new("torrents-state-change",
+ G_TYPE_FROM_CLASS
+ (object_class),
+ G_SIGNAL_RUN_LAST |
+ G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET
+ (TrgTorrentModelClass,
+ torrent_removed), NULL,
+ NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1,
+ G_TYPE_UINT);
+}
+
+trg_torrent_model_update_stats *trg_torrent_model_get_stats(TrgTorrentModel
+ * model)
+{
+ TrgTorrentModelPrivate *priv = TRG_TORRENT_MODEL_GET_PRIVATE(model);
+ return &(priv->stats);
+}
+
+static void
+trg_torrent_model_count_peers(TrgTorrentModel * model,
+ GtkTreeIter * iter, JsonObject * t)
+{
+ GList *trackersList =
+ json_array_get_elements(torrent_get_tracker_stats(t));
+ gint64 seeders = 0;
+ gint64 leechers = 0;
+ gint64 downloads = 0;
+ GList *li;
+
+ for (li = trackersList; li; li = g_list_next(li)) {
+ JsonObject *tracker = json_node_get_object((JsonNode *) li->data);
+
+ seeders += tracker_stats_get_seeder_count(tracker);
+ leechers += tracker_stats_get_leecher_count(tracker);
+ downloads += tracker_stats_get_download_count(tracker);
+ }
+
+ g_list_free(trackersList);
+
+ gtk_list_store_set(GTK_LIST_STORE(model), iter, TORRENT_COLUMN_SEEDS,
+ seeders, TORRENT_COLUMN_LEECHERS, leechers,
+ TORRENT_COLUMN_DOWNLOADS, downloads, -1);
+}
+
+static void trg_torrent_model_ref_free(gpointer data)
+{
+ GtkTreeRowReference *rr = (GtkTreeRowReference *) data;
+ GtkTreeModel *model = gtk_tree_row_reference_get_model(rr);
+ GtkTreePath *path = gtk_tree_row_reference_get_path(rr);
+ if (path) {
+ GtkTreeIter iter;
+ JsonObject *json;
+ if (gtk_tree_model_get_iter(model, &iter, path)) {
+ gtk_tree_model_get(model, &iter, TORRENT_COLUMN_JSON, &json,
+ -1);
+ json_object_unref(json);
+ g_object_set_data(G_OBJECT(model), PROP_REMOVE_IN_PROGRESS,
+ GINT_TO_POINTER(TRUE));
+ gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
+ g_object_set_data(G_OBJECT(model), PROP_REMOVE_IN_PROGRESS,
+ GINT_TO_POINTER(FALSE));
+ }
+
+ gtk_tree_path_free(path);
+ }
+
+ gtk_tree_row_reference_free(rr);
+}
+
+static void trg_torrent_model_init(TrgTorrentModel * self)
+{
+ TrgTorrentModelPrivate *priv = TRG_TORRENT_MODEL_GET_PRIVATE(self);
+
+ GType column_types[TORRENT_COLUMN_COLUMNS];
+
+ column_types[TORRENT_COLUMN_ICON] = G_TYPE_STRING;
+ column_types[TORRENT_COLUMN_NAME] = G_TYPE_STRING;
+ column_types[TORRENT_COLUMN_ERROR] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_SIZEWHENDONE] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_TOTALSIZE] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_HAVE_UNCHECKED] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_PERCENTDONE] = G_TYPE_DOUBLE;
+ column_types[TORRENT_COLUMN_METADATAPERCENTCOMPLETE] = G_TYPE_DOUBLE;
+ column_types[TORRENT_COLUMN_STATUS] = G_TYPE_STRING;
+ column_types[TORRENT_COLUMN_SEEDS] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_LEECHERS] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_DOWNLOADS] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_DOWNSPEED] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_ADDED] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_UPSPEED] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_ETA] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_UPLOADED] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_DOWNLOADED] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_HAVE_VALID] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_RATIO] = G_TYPE_DOUBLE;
+ column_types[TORRENT_COLUMN_ID] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_JSON] = G_TYPE_POINTER;
+ column_types[TORRENT_COLUMN_UPDATESERIAL] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_FLAGS] = G_TYPE_INT;
+ column_types[TORRENT_COLUMN_DOWNLOADDIR] = G_TYPE_STRING;
+ column_types[TORRENT_COLUMN_DOWNLOADDIR_SHORT] = G_TYPE_STRING;
+ column_types[TORRENT_COLUMN_BANDWIDTH_PRIORITY] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_DONE_DATE] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_FROMPEX] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_FROMDHT] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_FROMTRACKERS] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_FROMLTEP] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_FROMRESUME] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_FROMINCOMING] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_PEER_SOURCES] = G_TYPE_STRING;
+ column_types[TORRENT_COLUMN_SEED_RATIO_LIMIT] = G_TYPE_DOUBLE;
+ column_types[TORRENT_COLUMN_SEED_RATIO_MODE] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_PEERS_CONNECTED] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_PEERS_FROM_US] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_WEB_SEEDS_TO_US] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_PEERS_TO_US] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_TRACKERHOST] = G_TYPE_STRING;
+ column_types[TORRENT_COLUMN_QUEUE_POSITION] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_LASTACTIVE] = G_TYPE_INT64;
+ column_types[TORRENT_COLUMN_FILECOUNT] = G_TYPE_UINT;
+
+ gtk_list_store_set_column_types(GTK_LIST_STORE(self),
+ TORRENT_COLUMN_COLUMNS, column_types);
+
+ priv->ht = g_hash_table_new_full(g_int64_hash, g_int64_equal,
+ (GDestroyNotify) g_free,
+ trg_torrent_model_ref_free);
+
+ g_object_set_data(G_OBJECT(self), PROP_REMOVE_IN_PROGRESS,
+ GINT_TO_POINTER(FALSE));
+
+ priv->urlHostRegex = trg_uri_host_regex_new();
+}
+
+gboolean trg_torrent_model_is_remove_in_progress(TrgTorrentModel * model)
+{
+ return (gboolean) GPOINTER_TO_INT(g_object_get_data
+ (G_OBJECT(model),
+ PROP_REMOVE_IN_PROGRESS));
+}
+
+static gboolean
+trg_torrent_model_reload_dir_aliases_foreachfunc(GtkTreeModel * model,
+ GtkTreePath *
+ path G_GNUC_UNUSED,
+ GtkTreeIter * iter,
+ gpointer gdata)
+{
+ gchar *downloadDir, *shortDownloadDir;
+
+ gtk_tree_model_get(model, iter, TORRENT_COLUMN_DOWNLOADDIR,
+ &downloadDir, -1);
+
+ shortDownloadDir =
+ shorten_download_dir((TrgClient *) gdata, downloadDir);
+
+ gtk_list_store_set(GTK_LIST_STORE(model), iter,
+ TORRENT_COLUMN_DOWNLOADDIR_SHORT, shortDownloadDir,
+ -1);
+
+ g_free(downloadDir);
+ g_free(shortDownloadDir);
+
+ return FALSE;
+}
+
+void
+trg_torrent_model_reload_dir_aliases(TrgClient * tc, GtkTreeModel * model)
+{
+ gtk_tree_model_foreach(model,
+ trg_torrent_model_reload_dir_aliases_foreachfunc,
+ tc);
+ g_signal_emit(model, signals[TMODEL_STATE_CHANGED], 0,
+ TORRENT_UPDATE_PATH_CHANGE);
+}
+
+static gboolean
+trg_torrent_model_stats_scan_foreachfunc(GtkTreeModel *
+ model,
+ GtkTreePath *
+ path
+ G_GNUC_UNUSED,
+ GtkTreeIter * iter,
+ gpointer gdata)
+{
+ trg_torrent_model_update_stats *stats =
+ (trg_torrent_model_update_stats *) gdata;
+ guint flags;
+
+ gtk_tree_model_get(model, iter, TORRENT_COLUMN_FLAGS, &flags, -1);
+
+ if (flags & TORRENT_FLAG_SEEDING)
+ stats->seeding++;
+ else if (flags & TORRENT_FLAG_DOWNLOADING)
+ stats->down++;
+ else if (flags & TORRENT_FLAG_PAUSED)
+ stats->paused++;
+
+ if (flags & TORRENT_FLAG_ERROR)
+ stats->error++;
+
+ if (flags & TORRENT_FLAG_COMPLETE)
+ stats->complete++;
+ else
+ stats->incomplete++;
+
+ if (flags & TORRENT_FLAG_CHECKING)
+ stats->checking++;
+
+ if (flags & TORRENT_FLAG_ACTIVE)
+ stats->active++;
+
+ if (flags & TORRENT_FLAG_SEEDING_WAIT)
+ stats->seed_wait++;
+
+ if (flags & TORRENT_FLAG_DOWNLOADING_WAIT)
+ stats->down_wait++;
+
+ stats->count++;
+
+ return FALSE;
+}
+
+void trg_torrent_model_remove_all(TrgTorrentModel * model)
+{
+ TrgTorrentModelPrivate *priv = TRG_TORRENT_MODEL_GET_PRIVATE(model);
+ g_hash_table_remove_all(priv->ht);
+ gtk_list_store_clear(GTK_LIST_STORE(model));
+}
+
+gchar *shorten_download_dir(TrgClient * tc, const gchar * downloadDir)
+{
+ TrgPrefs *prefs = trg_client_get_prefs(tc);
+ JsonArray *labels =
+ trg_prefs_get_array(prefs, TRG_PREFS_KEY_DESTINATIONS,
+ TRG_PREFS_CONNECTION);
+ JsonObject *session = trg_client_get_session(tc);
+ const gchar *defaultDownloadDir = session_get_download_dir(session);
+ gchar *shortDownloadDir = NULL;
+
+ if (labels) {
+ GList *labelsList = json_array_get_elements(labels);
+ if (labelsList) {
+ GList *li;
+ for (li = labelsList; li; li = g_list_next(li)) {
+ JsonObject *labelObj = json_node_get_object((JsonNode *)
+ li->data);
+ const gchar *labelDir =
+ json_object_get_string_member(labelObj,
+ TRG_PREFS_KEY_DESTINATIONS_SUBKEY_DIR);
+ if (!g_strcmp0(downloadDir, labelDir)) {
+ const gchar *labelLabel =
+ json_object_get_string_member(labelObj,
+ TRG_PREFS_SUBKEY_LABEL);
+ shortDownloadDir = g_strdup(labelLabel);
+ break;
+ }
+ }
+ g_list_free(labelsList);
+ }
+ }
+
+ if (shortDownloadDir) {
+ return shortDownloadDir;
+ } else {
+ if (!g_strcmp0(defaultDownloadDir, downloadDir))
+ return g_strdup(_("Default"));
+
+ if (g_str_has_prefix(downloadDir, defaultDownloadDir)) {
+ int offset = strlen(defaultDownloadDir);
+ if (*(downloadDir + offset) == '/')
+ offset++;
+
+ if (offset < strlen(downloadDir))
+ return g_strdup(downloadDir + offset);
+ }
+ }
+
+ return g_strdup(downloadDir);
+}
+
+static inline void
+update_torrent_iter(TrgTorrentModel * model,
+ TrgClient * tc, gint64 rpcv,
+ gint64 serial, GtkTreeIter * iter,
+ JsonObject * t,
+ trg_torrent_model_update_stats *
+ stats, guint * whatsChanged)
+{
+ TrgTorrentModelPrivate *priv = TRG_TORRENT_MODEL_GET_PRIVATE(model);
+ GtkListStore *ls = GTK_LIST_STORE(model);
+ guint lastFlags, newFlags;
+ JsonObject *lastJson, *pf;
+ JsonArray *trackerStats;
+ gchar *statusString, *statusIcon, *downloadDir;
+ gint64 downRate, upRate, haveValid, uploaded, downloaded, id, status,
+ lpd;
+ guint fileCount;
+ gchar *firstTrackerHost = NULL;
+ gchar *peerSources = NULL;
+ gchar *lastDownloadDir = NULL;
+
+ downRate = torrent_get_rate_down(t);
+ stats->downRateTotal += downRate;
+
+ upRate = torrent_get_rate_up(t);
+ stats->upRateTotal += upRate;
+
+ uploaded = torrent_get_uploaded(t);
+ downloaded = torrent_get_downloaded(t);
+ haveValid = torrent_get_have_valid(t);
+
+ downloadDir = (gchar *) torrent_get_download_dir(t);
+ rm_trailing_slashes(downloadDir);
+
+ id = torrent_get_id(t);
+ status = torrent_get_status(t);
+ fileCount = json_array_get_length(torrent_get_files(t));
+ newFlags =
+ torrent_get_flags(t, rpcv, status, fileCount, downRate, upRate);
+ statusString = torrent_get_status_string(rpcv, status, newFlags);
+ statusIcon = torrent_get_status_icon(rpcv, newFlags);
+ pf = torrent_get_peersfrom(t);
+ trackerStats = torrent_get_tracker_stats(t);
+
+ gtk_tree_model_get(GTK_TREE_MODEL(model), iter, TORRENT_COLUMN_FLAGS,
+ &lastFlags, TORRENT_COLUMN_JSON, &lastJson,
+ TORRENT_COLUMN_DOWNLOADDIR, &lastDownloadDir, -1);
+
+ json_object_ref(t);
+
+ if (json_array_get_length(trackerStats) > 0) {
+ JsonObject *firstTracker =
+ json_array_get_object_element(trackerStats,
+ 0);
+ firstTrackerHost = trg_gregex_get_first(priv->urlHostRegex,
+ tracker_stats_get_host
+ (firstTracker));
+ }
+
+ lpd = peerfrom_get_lpd(pf);
+ if (newFlags & TORRENT_FLAG_ACTIVE) {
+ if (lpd >= 0) {
+ peerSources =
+ g_strdup_printf("%" G_GINT64_FORMAT " / %" G_GINT64_FORMAT
+ " / %" G_GINT64_FORMAT " / %"
+ G_GINT64_FORMAT " / %" G_GINT64_FORMAT
+ " / %" G_GINT64_FORMAT " / %"
+ G_GINT64_FORMAT, peerfrom_get_trackers(pf),
+ peerfrom_get_incoming(pf),
+ peerfrom_get_ltep(pf),
+ peerfrom_get_dht(pf), peerfrom_get_pex(pf),
+ lpd, peerfrom_get_resume(pf));
+ } else {
+ peerSources =
+ g_strdup_printf("%" G_GINT64_FORMAT " / %" G_GINT64_FORMAT
+ " / %" G_GINT64_FORMAT " / %"
+ G_GINT64_FORMAT " / %" G_GINT64_FORMAT
+ " / N/A / %" G_GINT64_FORMAT,
+ peerfrom_get_trackers(pf),
+ peerfrom_get_incoming(pf),
+ peerfrom_get_ltep(pf),
+ peerfrom_get_dht(pf), peerfrom_get_pex(pf),
+ peerfrom_get_resume(pf));
+ }
+ }
+#ifdef DEBUG
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_ICON, statusIcon, -1);
+ gtk_list_store_set(ls, iter,
+ TORRENT_COLUMN_NAME, torrent_get_name(t), -1);
+ gtk_list_store_set(ls, iter,
+ TORRENT_COLUMN_SIZEWHENDONE,
+ torrent_get_size_when_done(t), -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_PERCENTDONE,
+ (newFlags & TORRENT_FLAG_CHECKING) ?
+ torrent_get_recheck_progress(t)
+ : torrent_get_percent_done(t), -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_STATUS, statusString, -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_DOWNSPEED, downRate, -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_FLAGS, newFlags, -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_UPSPEED, upRate, -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_ETA, torrent_get_eta(t),
+ -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_UPLOADED, uploaded, -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_DOWNLOADED, downloaded,
+ -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_RATIO, uploaded > 0
+ && downloaded >
+ 0 ? (double) uploaded / (double) downloaded : 0,
+ -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_ID, id, -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_JSON, t, -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_UPDATESERIAL, serial, -1);
+ gtk_list_store_set(ls, iter,
+ TORRENT_COLUMN_ADDED, torrent_get_added_date(t),
+ -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_DOWNLOADDIR, downloadDir,
+ -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_BANDWIDTH_PRIORITY,
+ torrent_get_bandwidth_priority(t), -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_TOTALSIZE,
+ torrent_get_total_size(t), -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_HAVE_UNCHECKED,
+ torrent_get_have_unchecked(t), -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_METADATAPERCENTCOMPLETE,
+ torrent_get_metadata_percent_complete(t), -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_PEERS_TO_US,
+ torrent_get_peers_sending_to_us(t), -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_PEERS_FROM_US,
+ torrent_get_peers_getting_from_us(t), -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_WEB_SEEDS_TO_US,
+ torrent_get_web_seeds_sending_to_us(t), -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_SEED_RATIO_LIMIT,
+ torrent_get_seed_ratio_limit(t), -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_SEED_RATIO_MODE,
+ torrent_get_seed_ratio_mode(t), -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_FILECOUNT, fileCount, -1);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_HAVE_VALID, haveValid, -1);
+#else
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_ICON, statusIcon,
+ TORRENT_COLUMN_ADDED, torrent_get_added_date(t),
+ TORRENT_COLUMN_FILECOUNT,
+ fileCount,
+ TORRENT_COLUMN_DONE_DATE, torrent_get_done_date(t),
+ TORRENT_COLUMN_NAME, torrent_get_name(t),
+ TORRENT_COLUMN_ERROR, torrent_get_error(t),
+ TORRENT_COLUMN_SIZEWHENDONE,
+ torrent_get_size_when_done(t),
+ TORRENT_COLUMN_PERCENTDONE,
+ (newFlags & TORRENT_FLAG_CHECKING) ?
+ torrent_get_recheck_progress(t)
+ : torrent_get_percent_done(t),
+ TORRENT_COLUMN_METADATAPERCENTCOMPLETE,
+ torrent_get_metadata_percent_complete(t),
+ TORRENT_COLUMN_STATUS, statusString,
+ TORRENT_COLUMN_DOWNSPEED, downRate,
+ TORRENT_COLUMN_FLAGS, newFlags,
+ TORRENT_COLUMN_UPSPEED, upRate, TORRENT_COLUMN_ETA,
+ torrent_get_eta(t), TORRENT_COLUMN_UPLOADED,
+ uploaded, TORRENT_COLUMN_DOWNLOADED, downloaded,
+ TORRENT_COLUMN_TOTALSIZE, torrent_get_total_size(t),
+ TORRENT_COLUMN_HAVE_UNCHECKED,
+ torrent_get_have_unchecked(t),
+ TORRENT_COLUMN_HAVE_VALID, haveValid,
+ TORRENT_COLUMN_FROMPEX, peerfrom_get_pex(pf),
+ TORRENT_COLUMN_FROMDHT, peerfrom_get_dht(pf),
+ TORRENT_COLUMN_FROMTRACKERS,
+ peerfrom_get_trackers(pf), TORRENT_COLUMN_FROMLTEP,
+ peerfrom_get_ltep(pf), TORRENT_COLUMN_FROMRESUME,
+ peerfrom_get_resume(pf),
+ TORRENT_COLUMN_FROMINCOMING,
+ peerfrom_get_incoming(pf),
+ TORRENT_COLUMN_PEER_SOURCES, peerSources,
+ TORRENT_COLUMN_PEERS_CONNECTED,
+ torrent_get_peers_connected(t),
+ TORRENT_COLUMN_PEERS_TO_US,
+ torrent_get_peers_sending_to_us(t),
+ TORRENT_COLUMN_PEERS_FROM_US,
+ torrent_get_peers_getting_from_us(t),
+ TORRENT_COLUMN_WEB_SEEDS_TO_US,
+ torrent_get_web_seeds_sending_to_us(t),
+ TORRENT_COLUMN_QUEUE_POSITION,
+ torrent_get_queue_position(t),
+ TORRENT_COLUMN_SEED_RATIO_LIMIT,
+ torrent_get_seed_ratio_limit(t),
+ TORRENT_COLUMN_SEED_RATIO_MODE,
+ torrent_get_seed_ratio_mode(t),
+ TORRENT_COLUMN_LASTACTIVE,
+ torrent_get_activity_date(t), TORRENT_COLUMN_RATIO,
+ uploaded > 0
+ && haveValid >
+ 0 ? (double) uploaded / (double) haveValid : 0,
+ TORRENT_COLUMN_DOWNLOADDIR, downloadDir,
+ TORRENT_COLUMN_BANDWIDTH_PRIORITY,
+ torrent_get_bandwidth_priority(t),
+ TORRENT_COLUMN_ID, id, TORRENT_COLUMN_JSON, t,
+ TORRENT_COLUMN_TRACKERHOST,
+ firstTrackerHost ? firstTrackerHost : "",
+ TORRENT_COLUMN_UPDATESERIAL, serial, -1);
+#endif
+
+ if (!lastDownloadDir || g_strcmp0(downloadDir, lastDownloadDir)) {
+ gchar *shortDownloadDir = shorten_download_dir(tc, downloadDir);
+ gtk_list_store_set(ls, iter, TORRENT_COLUMN_DOWNLOADDIR_SHORT,
+ shortDownloadDir, -1);
+ g_free(shortDownloadDir);
+ *whatsChanged |= TORRENT_UPDATE_PATH_CHANGE;
+ }
+
+ if (lastJson)
+ json_object_unref(lastJson);
+
+ if ((lastFlags & TORRENT_FLAG_DOWNLOADING)
+ && (!(newFlags & TORRENT_FLAG_DOWNLOADING))
+ && (newFlags & TORRENT_FLAG_COMPLETE))
+ g_signal_emit(model, signals[TMODEL_TORRENT_COMPLETED], 0, iter);
+
+ if (lastFlags != newFlags)
+ *whatsChanged |= TORRENT_UPDATE_STATE_CHANGE;
+
+ trg_torrent_model_count_peers(model, iter, t);
+
+ if (firstTrackerHost)
+ g_free(firstTrackerHost);
+
+ if (peerSources)
+ g_free(peerSources);
+
+ g_free(lastDownloadDir);
+ g_free(statusString);
+ g_free(statusIcon);
+}
+
+TrgTorrentModel *trg_torrent_model_new(void)
+{
+ return g_object_new(TRG_TYPE_TORRENT_MODEL, NULL);
+}
+
+struct TrgModelRemoveData {
+ GList *toRemove;
+ gint64 currentSerial;
+};
+
+GHashTable *get_torrent_table(TrgTorrentModel * model)
+{
+ TrgTorrentModelPrivate *priv = TRG_TORRENT_MODEL_GET_PRIVATE(model);
+ return priv->ht;
+}
+
+gboolean
+trg_model_find_removed_foreachfunc(GtkTreeModel * model,
+ GtkTreePath *
+ path G_GNUC_UNUSED,
+ GtkTreeIter * iter, gpointer gdata)
+{
+ struct TrgModelRemoveData *args = (struct TrgModelRemoveData *) gdata;
+ gint64 rowSerial;
+
+ gtk_tree_model_get(model, iter, TORRENT_COLUMN_UPDATESERIAL,
+ &rowSerial, -1);
+
+ if (rowSerial != args->currentSerial) {
+ gint64 *id = g_new(gint64, 1);
+ gtk_tree_model_get(model, iter, TORRENT_COLUMN_ID, id, -1);
+ args->toRemove = g_list_append(args->toRemove, id);
+ }
+
+ return FALSE;
+}
+
+GList *trg_torrent_model_find_removed(GtkTreeModel * model,
+ gint64 currentSerial)
+{
+ struct TrgModelRemoveData args;
+ args.toRemove = NULL;
+ args.currentSerial = currentSerial;
+
+ gtk_tree_model_foreach(GTK_TREE_MODEL(model),
+ trg_model_find_removed_foreachfunc, &args);
+
+ return args.toRemove;
+}
+
+gboolean
+get_torrent_data(GHashTable * table, gint64 id, JsonObject ** t,
+ GtkTreeIter * out_iter)
+{
+ gpointer result = g_hash_table_lookup(table, &id);
+ gboolean found = FALSE;
+
+ if (result) {
+ GtkTreeRowReference *rr = (GtkTreeRowReference *) result;
+ GtkTreePath *path = gtk_tree_row_reference_get_path(rr);
+ GtkTreeIter iter;
+ if (path) {
+ GtkTreeModel *model = gtk_tree_row_reference_get_model(rr);
+ gtk_tree_model_get_iter(model, &iter, path);
+ if (out_iter)
+ *out_iter = iter;
+ if (t)
+ gtk_tree_model_get(model, &iter, TORRENT_COLUMN_JSON, t,
+ -1);
+ found = TRUE;
+ gtk_tree_path_free(path);
+ }
+ }
+
+ return found;
+}
+
+static void
+trg_torrent_model_stat_counts_clear(trg_torrent_model_update_stats * stats)
+{
+ stats->count = stats->down = stats->error = stats->paused =
+ stats->seeding = stats->complete = stats->incomplete =
+ stats->active = stats->checking = stats->seed_wait =
+ stats->down_wait = 0;
+}
+
+trg_torrent_model_update_stats *trg_torrent_model_update(TrgTorrentModel *
+ model,
+ TrgClient * tc,
+ JsonObject *
+ response,
+ gint mode)
+{
+ TrgTorrentModelPrivate *priv = TRG_TORRENT_MODEL_GET_PRIVATE(model);
+
+ GList *torrentList;
+ JsonObject *args, *t;
+ GList *li;
+ gint64 id;
+ gint64 serial = trg_client_get_serial(tc);
+ JsonArray *removedTorrents;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ GtkTreeRowReference *rr;
+ gpointer *result;
+ guint whatsChanged = 0;
+
+ gint64 rpcv = trg_client_get_rpc_version(tc);
+
+ args = get_arguments(response);
+ torrentList = json_array_get_elements(get_torrents(args));
+
+ priv->stats.downRateTotal = 0;
+ priv->stats.upRateTotal = 0;
+
+ for (li = torrentList; li; li = g_list_next(li)) {
+ t = json_node_get_object((JsonNode *) li->data);
+ id = torrent_get_id(t);
+
+ result =
+ mode == TORRENT_GET_MODE_FIRST ? NULL :
+ g_hash_table_lookup(priv->ht, &id);
+
+ if (!result) {
+ gint64 *idCopy;
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ whatsChanged |= TORRENT_UPDATE_ADDREMOVE;
+
+ update_torrent_iter(model, tc, rpcv, serial,
+ &iter, t, &(priv->stats), &whatsChanged);
+
+ path = gtk_tree_model_get_path(GTK_TREE_MODEL(model), &iter);
+ rr = gtk_tree_row_reference_new(GTK_TREE_MODEL(model), path);
+ idCopy = g_new(gint64, 1);
+ *idCopy = id;
+ g_hash_table_insert(priv->ht, idCopy, rr);
+ gtk_tree_path_free(path);
+
+ if (mode != TORRENT_GET_MODE_FIRST)
+ g_signal_emit(model, signals[TMODEL_TORRENT_ADDED], 0,
+ &iter);
+ } else {
+ path = gtk_tree_row_reference_get_path((GtkTreeRowReference *)
+ result);
+ if (path) {
+ if (gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter,
+ path)) {
+ update_torrent_iter(model, tc, rpcv,
+ serial, &iter,
+ t, &(priv->stats), &whatsChanged);
+ }
+ gtk_tree_path_free(path);
+ }
+ }
+ }
+
+ g_list_free(torrentList);
+
+ if (mode == TORRENT_GET_MODE_UPDATE) {
+ GList *hitlist =
+ trg_torrent_model_find_removed(GTK_TREE_MODEL(model), serial);
+ if (hitlist) {
+ for (li = hitlist; li; li = g_list_next(li)) {
+ g_hash_table_remove(priv->ht, li->data);
+ g_free(li->data);
+ }
+ whatsChanged |= TORRENT_UPDATE_ADDREMOVE;
+ g_list_free(hitlist);
+ }
+ } else if (mode > TORRENT_GET_MODE_FIRST) {
+ removedTorrents = get_torrents_removed(args);
+ if (removedTorrents) {
+ GList *hitlist = json_array_get_elements(removedTorrents);
+ for (li = hitlist; li; li = g_list_next(li)) {
+ id = json_node_get_int((JsonNode *) li->data);
+ g_hash_table_remove(priv->ht, &id);
+ whatsChanged |= TORRENT_UPDATE_ADDREMOVE;
+ }
+ g_list_free(hitlist);
+ }
+ }
+
+ if (whatsChanged != 0) {
+ if ((whatsChanged & TORRENT_UPDATE_ADDREMOVE)
+ || (whatsChanged & TORRENT_UPDATE_STATE_CHANGE)) {
+ trg_torrent_model_stat_counts_clear(&priv->stats);
+ gtk_tree_model_foreach(GTK_TREE_MODEL(model),
+ trg_torrent_model_stats_scan_foreachfunc,
+ &(priv->stats));
+ }
+ g_signal_emit(model, signals[TMODEL_STATE_CHANGED], 0,
+ whatsChanged);
+ }
+
+ g_signal_emit(model, signals[TMODEL_UPDATE], 0);
+
+ return &(priv->stats);
+}
diff --git a/src/trg-torrent-model.h b/src/trg-torrent-model.h
new file mode 100644
index 0000000..078afe8
--- /dev/null
+++ b/src/trg-torrent-model.h
@@ -0,0 +1,154 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_TORRENT_MODEL_H_
+#define TRG_TORRENT_MODEL_H_
+
+#include <glib-object.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-client.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_TORRENT_MODEL trg_torrent_model_get_type()
+#define TRG_TORRENT_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_TORRENT_MODEL, TrgTorrentModel))
+#define TRG_TORRENT_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_TORRENT_MODEL, TrgTorrentModelClass))
+#define TRG_IS_TORRENT_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_TORRENT_MODEL))
+#define TRG_IS_TORRENT_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_TORRENT_MODEL))
+#define TRG_TORRENT_MODEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_TORRENT_MODEL, TrgTorrentModelClass))
+ typedef struct {
+ GtkListStore parent;
+} TrgTorrentModel;
+
+typedef struct {
+ GtkListStoreClass parent_class;
+ void (*torrent_completed) (TrgTorrentModel * model,
+ GtkTreeIter * iter, gpointer data);
+ void (*update) (TrgTorrentModel * model, gpointer data);
+ void (*torrent_added) (TrgTorrentModel * model,
+ GtkTreeIter * iter, gpointer data);
+
+ void (*torrent_removed) (TrgTorrentModel * model, gpointer data);
+} TrgTorrentModelClass;
+
+typedef struct {
+ gint64 downRateTotal;
+ gint64 upRateTotal;
+ gint seeding;
+ gint down;
+ gint paused;
+ gint count;
+ gint error;
+ gint complete;
+ gint incomplete;
+ gint checking;
+ gint active;
+ gint seed_wait;
+ gint down_wait;
+} trg_torrent_model_update_stats;
+
+#define TORRENT_UPDATE_STATE_CHANGE (1 << 0)
+#define TORRENT_UPDATE_PATH_CHANGE (1 << 1)
+#define TORRENT_UPDATE_ADDREMOVE (1 << 2)
+
+GType trg_torrent_model_get_type(void);
+
+TrgTorrentModel *trg_torrent_model_new();
+
+G_END_DECLS
+ gboolean
+find_existing_peer_item(GtkListStore * model, JsonObject * p,
+ GtkTreeIter * iter);
+
+trg_torrent_model_update_stats *trg_torrent_model_update(TrgTorrentModel *
+ model,
+ TrgClient * tc,
+ JsonObject *
+ response,
+ gint mode);
+trg_torrent_model_update_stats *trg_torrent_model_get_stats(TrgTorrentModel
+ * model);
+
+GHashTable *get_torrent_table(TrgTorrentModel * model);
+void trg_torrent_model_remove_all(TrgTorrentModel * model);
+
+gboolean trg_torrent_model_is_remove_in_progress(TrgTorrentModel * model);
+
+gboolean get_torrent_data(GHashTable * table, gint64 id, JsonObject ** t,
+ GtkTreeIter * out_iter);
+
+gchar *shorten_download_dir(TrgClient * tc, const gchar * downloadDir);
+void trg_torrent_model_reload_dir_aliases(TrgClient * tc,
+ GtkTreeModel * model);
+
+enum {
+ TORRENT_COLUMN_ICON,
+ TORRENT_COLUMN_NAME,
+ TORRENT_COLUMN_SIZEWHENDONE,
+ TORRENT_COLUMN_PERCENTDONE,
+ TORRENT_COLUMN_METADATAPERCENTCOMPLETE,
+ TORRENT_COLUMN_STATUS,
+ TORRENT_COLUMN_SEEDS,
+ TORRENT_COLUMN_LEECHERS,
+ TORRENT_COLUMN_DOWNLOADS,
+ TORRENT_COLUMN_PEERS_CONNECTED,
+ TORRENT_COLUMN_PEERS_FROM_US,
+ TORRENT_COLUMN_WEB_SEEDS_TO_US,
+ TORRENT_COLUMN_PEERS_TO_US,
+ TORRENT_COLUMN_DOWNSPEED,
+ TORRENT_COLUMN_UPSPEED,
+ TORRENT_COLUMN_ETA,
+ TORRENT_COLUMN_UPLOADED,
+ TORRENT_COLUMN_DOWNLOADED,
+ TORRENT_COLUMN_TOTALSIZE,
+ TORRENT_COLUMN_HAVE_UNCHECKED,
+ TORRENT_COLUMN_HAVE_VALID,
+ TORRENT_COLUMN_RATIO,
+ TORRENT_COLUMN_ADDED,
+ TORRENT_COLUMN_ID,
+ TORRENT_COLUMN_JSON,
+ TORRENT_COLUMN_UPDATESERIAL,
+ TORRENT_COLUMN_FLAGS,
+ TORRENT_COLUMN_DOWNLOADDIR,
+ TORRENT_COLUMN_DOWNLOADDIR_SHORT,
+ TORRENT_COLUMN_BANDWIDTH_PRIORITY,
+ TORRENT_COLUMN_DONE_DATE,
+ TORRENT_COLUMN_FROMPEX,
+ TORRENT_COLUMN_FROMDHT,
+ TORRENT_COLUMN_FROMTRACKERS,
+ TORRENT_COLUMN_FROMLTEP,
+ TORRENT_COLUMN_FROMRESUME,
+ TORRENT_COLUMN_FROMINCOMING,
+ TORRENT_COLUMN_PEER_SOURCES,
+ TORRENT_COLUMN_TRACKERHOST,
+ TORRENT_COLUMN_QUEUE_POSITION,
+ TORRENT_COLUMN_LASTACTIVE,
+ TORRENT_COLUMN_FILECOUNT,
+ TORRENT_COLUMN_ERROR,
+ TORRENT_COLUMN_SEED_RATIO_MODE,
+ TORRENT_COLUMN_SEED_RATIO_LIMIT,
+ TORRENT_COLUMN_COLUMNS
+};
+
+#endif /* TRG_TORRENT_MODEL_H_ */
diff --git a/src/trg-torrent-move-dialog.c b/src/trg-torrent-move-dialog.c
new file mode 100644
index 0000000..7669545
--- /dev/null
+++ b/src/trg-torrent-move-dialog.c
@@ -0,0 +1,283 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-client.h"
+#include "trg-main-window.h"
+#include "trg-torrent-move-dialog.h"
+#include "trg-destination-combo.h"
+#include "hig.h"
+#include "torrent.h"
+#include "requests.h"
+
+G_DEFINE_TYPE(TrgTorrentMoveDialog, trg_torrent_move_dialog,
+ GTK_TYPE_DIALOG)
+#define TRG_TORRENT_MOVE_DIALOG_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_TORRENT_MOVE_DIALOG, TrgTorrentMoveDialogPrivate))
+typedef struct _TrgTorrentMoveDialogPrivate TrgTorrentMoveDialogPrivate;
+
+struct _TrgTorrentMoveDialogPrivate {
+ TrgClient *client;
+ TrgMainWindow *win;
+ TrgTorrentTreeView *treeview;
+ JsonArray *ids;
+ GtkWidget *location_combo, *move_check, *move_button;
+};
+
+enum {
+ PROP_0,
+ PROP_CLIENT,
+ PROP_PARENT_WINDOW,
+ PROP_TREEVIEW
+};
+
+static void
+trg_torrent_move_response_cb(GtkDialog * dlg, gint res_id, gpointer data)
+{
+ TrgTorrentMoveDialogPrivate *priv =
+ TRG_TORRENT_MOVE_DIALOG_GET_PRIVATE(dlg);
+
+ if (res_id == GTK_RESPONSE_ACCEPT) {
+ gchar *location =
+ trg_destination_combo_get_dir(TRG_DESTINATION_COMBO
+ (priv->location_combo));
+ JsonNode *request = torrent_set_location(priv->ids, location,
+ gtk_toggle_button_get_active
+ (GTK_TOGGLE_BUTTON
+ (priv->move_check)));
+ g_free(location);
+ trg_destination_combo_save_selection(TRG_DESTINATION_COMBO
+ (priv->location_combo));
+ dispatch_async(priv->client, request,
+ on_generic_interactive_action, data);
+ } else {
+ json_array_unref(priv->ids);
+ }
+ gtk_widget_destroy(GTK_WIDGET(dlg));
+}
+
+static void location_changed(GtkWidget * w, gpointer data)
+{
+ TrgTorrentMoveDialogPrivate *priv =
+ TRG_TORRENT_MOVE_DIALOG_GET_PRIVATE(data);
+ gtk_widget_set_sensitive(priv->move_button,
+ trg_destination_combo_has_text
+ (TRG_DESTINATION_COMBO
+ (priv->location_combo)));
+}
+
+static GObject *trg_torrent_move_dialog_constructor(GType type,
+ guint
+ n_construct_properties,
+ GObjectConstructParam *
+ construct_params)
+{
+ GObject *object = G_OBJECT_CLASS
+ (trg_torrent_move_dialog_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+ TrgTorrentMoveDialogPrivate *priv =
+ TRG_TORRENT_MOVE_DIALOG_GET_PRIVATE(object);
+
+ gint count;
+ gchar *msg;
+
+ GtkWidget *w, *t;
+ guint row = 0;
+
+ t = hig_workarea_create();
+
+ w = priv->location_combo =
+ trg_destination_combo_new(priv->client,
+ TRG_PREFS_KEY_LAST_MOVE_DESTINATION);
+ g_signal_connect(trg_destination_combo_get_entry
+ (TRG_DESTINATION_COMBO(w)), "changed",
+ G_CALLBACK(location_changed), object);
+ hig_workarea_add_row(t, &row, _("Location:"), w, NULL);
+
+ priv->move_check =
+ hig_workarea_add_wide_checkbutton(t, &row, _("Move"), TRUE);
+
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(object), TRUE);
+
+ gtk_dialog_add_button(GTK_DIALOG(object), GTK_STOCK_CLOSE,
+ GTK_RESPONSE_CANCEL);
+ priv->move_button =
+ gtk_dialog_add_button(GTK_DIALOG(object), _("Move"),
+ GTK_RESPONSE_ACCEPT);
+
+ gtk_widget_set_sensitive(priv->move_button,
+ trg_destination_combo_has_text
+ (TRG_DESTINATION_COMBO
+ (priv->location_combo)));
+
+ gtk_container_set_border_width(GTK_CONTAINER(object), GUI_PAD);
+
+ gtk_dialog_set_default_response(GTK_DIALOG(object),
+ GTK_RESPONSE_ACCEPT);
+
+ gtk_dialog_set_alternative_button_order(GTK_DIALOG(object),
+ GTK_RESPONSE_ACCEPT,
+ GTK_RESPONSE_CANCEL, -1);
+
+ gtk_container_set_border_width(GTK_CONTAINER(t), GUI_PAD);
+
+ gtk_box_pack_start(GTK_BOX
+ (gtk_dialog_get_content_area(GTK_DIALOG(object))),
+ t, TRUE, TRUE, 0);
+
+ count =
+ gtk_tree_selection_count_selected_rows(gtk_tree_view_get_selection
+ (GTK_TREE_VIEW
+ (priv->treeview)));
+ priv->ids = build_json_id_array(priv->treeview);
+
+ if (count == 1) {
+ JsonObject *json;
+ const gchar *name;
+
+ get_torrent_data(trg_client_get_torrent_table(priv->client),
+ trg_mw_get_selected_torrent_id(priv->win), &json,
+ NULL);
+ name = torrent_get_name(json);
+ msg = g_strdup_printf(_("Move %s"), name);
+ } else {
+ msg = g_strdup_printf(_("Move %d torrents"), count);
+ }
+
+ gtk_window_set_transient_for(GTK_WINDOW(object),
+ GTK_WINDOW(priv->win));
+ gtk_window_set_title(GTK_WINDOW(object), msg);
+
+ g_signal_connect(G_OBJECT(object),
+ "response",
+ G_CALLBACK(trg_torrent_move_response_cb), priv->win);
+
+ return object;
+}
+
+static void
+trg_torrent_move_dialog_get_property(GObject * object, guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ TrgTorrentMoveDialogPrivate *priv =
+ TRG_TORRENT_MOVE_DIALOG_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_CLIENT:
+ g_value_set_pointer(value, priv->client);
+ break;
+ case PROP_PARENT_WINDOW:
+ g_value_set_object(value, priv->win);
+ break;
+ case PROP_TREEVIEW:
+ g_value_set_object(value, priv->treeview);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_torrent_move_dialog_set_property(GObject * object, guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgTorrentMoveDialogPrivate *priv =
+ TRG_TORRENT_MOVE_DIALOG_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_CLIENT:
+ priv->client = g_value_get_pointer(value);
+ break;
+ case PROP_PARENT_WINDOW:
+ priv->win = g_value_get_object(value);
+ break;
+ case PROP_TREEVIEW:
+ priv->treeview = g_value_get_object(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_torrent_move_dialog_class_init(TrgTorrentMoveDialogClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgTorrentMoveDialogPrivate));
+
+ object_class->get_property = trg_torrent_move_dialog_get_property;
+ object_class->set_property = trg_torrent_move_dialog_set_property;
+ object_class->constructor = trg_torrent_move_dialog_constructor;
+
+ g_object_class_install_property(object_class,
+ PROP_TREEVIEW,
+ g_param_spec_object
+ ("torrent-tree-view",
+ "TrgTorrentTreeView",
+ "TrgTorrentTreeView",
+ TRG_TYPE_TORRENT_TREE_VIEW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_PARENT_WINDOW,
+ g_param_spec_object
+ ("parent-window", "Parent window",
+ "Parent window", TRG_TYPE_MAIN_WINDOW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_CLIENT,
+ g_param_spec_pointer
+ ("trg-client", "TClient",
+ "Client",
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+}
+
+static void trg_torrent_move_dialog_init(TrgTorrentMoveDialog * self)
+{
+}
+
+TrgTorrentMoveDialog *trg_torrent_move_dialog_new(TrgMainWindow * win,
+ TrgClient * client,
+ TrgTorrentTreeView * ttv)
+{
+ return g_object_new(TRG_TYPE_TORRENT_MOVE_DIALOG,
+ "trg-client", client,
+ "torrent-tree-view", ttv, "parent-window", win,
+ NULL);
+}
diff --git a/src/trg-torrent-move-dialog.h b/src/trg-torrent-move-dialog.h
new file mode 100644
index 0000000..5f58979
--- /dev/null
+++ b/src/trg-torrent-move-dialog.h
@@ -0,0 +1,57 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_TORRENT_MOVE_DIALOG_H_
+#define TRG_TORRENT_MOVE_DIALOG_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "trg-client.h"
+#include "trg-main-window.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_TORRENT_MOVE_DIALOG trg_torrent_move_dialog_get_type()
+#define TRG_TORRENT_MOVE_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_TORRENT_MOVE_DIALOG, TrgTorrentMoveDialog))
+#define TRG_TORRENT_MOVE_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_TORRENT_MOVE_DIALOG, TrgTorrentMoveDialogClass))
+#define TRG_IS_TORRENT_MOVE_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_TORRENT_MOVE_DIALOG))
+#define TRG_IS_TORRENT_MOVE_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_TORRENT_MOVE_DIALOG))
+#define TRG_TORRENT_MOVE_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_TORRENT_MOVE_DIALOG, TrgTorrentMoveDialogClass))
+ typedef struct {
+ GtkDialog parent;
+} TrgTorrentMoveDialog;
+
+typedef struct {
+ GtkDialogClass parent_class;
+} TrgTorrentMoveDialogClass;
+
+GType trg_torrent_move_dialog_get_type(void);
+
+TrgTorrentMoveDialog *trg_torrent_move_dialog_new(TrgMainWindow * win,
+ TrgClient * client,
+ TrgTorrentTreeView *
+ ttv);
+
+G_END_DECLS
+#endif /* TRG_TORRENT_MOVE_DIALOG_H_ */
diff --git a/src/trg-torrent-props-dialog.c b/src/trg-torrent-props-dialog.c
new file mode 100644
index 0000000..90ef4e3
--- /dev/null
+++ b/src/trg-torrent-props-dialog.c
@@ -0,0 +1,736 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+#include "util.h"
+#include "torrent.h"
+#include "json.h"
+#include "trg-client.h"
+#include "trg-json-widgets.h"
+#include "requests.h"
+#include "protocol-constants.h"
+#include "trg-peers-model.h"
+#include "trg-peers-tree-view.h"
+#include "trg-files-model.h"
+#include "trg-files-tree-view.h"
+#include "trg-trackers-model.h"
+#include "trg-trackers-tree-view.h"
+#include "trg-torrent-model.h"
+#include "trg-torrent-tree-view.h"
+#include "trg-torrent-props-dialog.h"
+#include "trg-main-window.h"
+#include "hig.h"
+
+/* Pretty similar to remote preferences, also using the widget creation functions
+ * in trg-json-widgets.c. The torrent tree view is passed into here, which this
+ * gets the selection from. If there are multiple selections, use the first to
+ * populate the fields.
+ *
+ * Build the JSON array of torrent IDs when the dialog is created, in case the
+ * selection changes before clicking OK.
+ *
+ * When the user clicks OK, use trg-json-widgets to populate an object with the
+ * values and then send it with the IDs.
+ */
+
+G_DEFINE_TYPE(TrgTorrentPropsDialog, trg_torrent_props_dialog,
+ GTK_TYPE_DIALOG)
+enum {
+ PROP_0, PROP_TREEVIEW, PROP_TORRENT_MODEL, PROP_PARENT_WINDOW,
+ PROP_CLIENT
+};
+
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_TORRENT_PROPS_DIALOG, TrgTorrentPropsDialogPrivate))
+typedef struct _TrgTorrentPropsDialogPrivate TrgTorrentPropsDialogPrivate;
+
+struct _TrgTorrentPropsDialogPrivate {
+ TrgTorrentTreeView *tv;
+ TrgTorrentModel *torrentModel;
+ TrgClient *client;
+ TrgMainWindow *parent;
+ JsonArray *targetIds;
+
+ GList *widgets;
+
+ GtkWidget *bandwidthPriorityCombo, *seedRatioMode;
+
+ TrgPeersTreeView *peersTv;
+ TrgPeersModel *peersModel;
+ TrgTrackersTreeView *trackersTv;
+ TrgTrackersModel *trackersModel;
+ TrgFilesTreeView *filesTv;
+ TrgFilesModel *filesModel;
+ JsonObject *lastJson;
+
+ GtkWidget *size_lb;
+ GtkWidget *have_lb;
+ GtkWidget *dl_lb;
+ GtkWidget *ul_lb;
+ GtkWidget *state_lb;
+ GtkWidget *date_started_lb;
+ GtkWidget *eta_lb;
+ GtkWidget *last_activity_lb;
+ GtkWidget *error_lb;
+ GtkWidget *destination_lb;
+ GtkWidget *hash_lb;
+ GtkWidget *privacy_lb;
+ GtkWidget *origin_lb;
+ GtkTextBuffer *comment_buffer;
+ gboolean show_details;
+};
+
+static void trg_torrent_props_dialog_set_property(GObject * object,
+ guint prop_id,
+ const GValue * value,
+ GParamSpec *
+ pspec G_GNUC_UNUSED)
+{
+ TrgTorrentPropsDialogPrivate *priv = GET_PRIVATE(object);
+
+ switch (prop_id) {
+ case PROP_PARENT_WINDOW:
+ priv->parent = g_value_get_object(value);
+ break;
+ case PROP_TREEVIEW:
+ priv->tv = g_value_get_object(value);
+ break;
+ case PROP_TORRENT_MODEL:
+ priv->torrentModel = g_value_get_object(value);
+ break;
+ case PROP_CLIENT:
+ priv->client = g_value_get_pointer(value);
+ break;
+ }
+}
+
+static void trg_torrent_props_dialog_get_property(GObject * object,
+ guint prop_id,
+ GValue * value,
+ GParamSpec *
+ pspec G_GNUC_UNUSED)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+}
+
+static void trg_torrent_props_response_cb(GtkDialog * dialog, gint res_id,
+ gpointer data G_GNUC_UNUSED)
+{
+ TrgTorrentPropsDialogPrivate *priv = GET_PRIVATE(dialog);
+
+ if (priv->show_details) {
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+ gint width, height;
+ gtk_window_get_size(GTK_WINDOW(dialog), &width, &height);
+ trg_prefs_set_int(prefs, "dialog-width", width, TRG_PREFS_GLOBAL);
+ trg_prefs_set_int(prefs, "dialog-height", height,
+ TRG_PREFS_GLOBAL);
+
+ trg_tree_view_persist(TRG_TREE_VIEW(priv->peersTv),
+ TRG_TREE_VIEW_PERSIST_SORT |
+ TRG_TREE_VIEW_PERSIST_LAYOUT);
+ trg_tree_view_persist(TRG_TREE_VIEW(priv->filesTv),
+ TRG_TREE_VIEW_PERSIST_SORT |
+ TRG_TREE_VIEW_PERSIST_LAYOUT);
+ trg_tree_view_persist(TRG_TREE_VIEW(priv->trackersTv),
+ TRG_TREE_VIEW_PERSIST_SORT |
+ TRG_TREE_VIEW_PERSIST_LAYOUT);
+ }
+
+ if (res_id != GTK_RESPONSE_OK) {
+ json_array_unref(priv->targetIds);
+ } else {
+ JsonNode *request = torrent_set(priv->targetIds);
+ JsonObject *args = node_get_arguments(request);
+
+ json_object_set_int_member(args, FIELD_SEED_RATIO_MODE,
+ gtk_combo_box_get_active(GTK_COMBO_BOX
+ (priv->seedRatioMode) ));
+ json_object_set_int_member(args, FIELD_BANDWIDTH_PRIORITY,
+ gtk_combo_box_get_active(GTK_COMBO_BOX
+ (priv->bandwidthPriorityCombo) ) - 1);
+
+ trg_json_widgets_save(priv->widgets, args);
+ trg_json_widget_desc_list_free(priv->widgets);
+
+ dispatch_async(priv->client, request, on_generic_interactive_action,
+ priv->parent);
+ }
+
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+static void seed_ratio_mode_changed_cb(GtkWidget * w, gpointer data)
+{
+ gtk_widget_set_sensitive(GTK_WIDGET(data),
+ gtk_combo_box_get_active(GTK_COMBO_BOX(w)) ==
+ 1 ? TRUE : FALSE);
+}
+
+static GtkWidget *info_page_new(TrgTorrentPropsDialog * dialog)
+{
+ TrgTorrentPropsDialogPrivate *priv = GET_PRIVATE(dialog);
+ guint row = 0;
+ GtkTextBuffer *b;
+ GtkWidget *l, *w, *fr, *sw;
+ GtkWidget *t = hig_workarea_create();
+
+ hig_workarea_add_section_title(t, &row, _("Activity"));
+
+ /* size */
+ l = priv->size_lb = gtk_label_new(NULL);
+ gtk_label_set_single_line_mode(GTK_LABEL(l), TRUE);
+ hig_workarea_add_row(t, &row, _("Torrent size:"), l, NULL);
+
+ /* have */
+ l = priv->have_lb = gtk_label_new(NULL);
+ gtk_label_set_single_line_mode(GTK_LABEL(l), TRUE);
+ hig_workarea_add_row(t, &row, _("Have:"), l, NULL);
+
+ /* downloaded */
+ l = priv->dl_lb = gtk_label_new(NULL);
+ gtk_label_set_single_line_mode(GTK_LABEL(l), TRUE);
+ hig_workarea_add_row(t, &row, _("Downloaded:"), l, NULL);
+
+ /* uploaded */
+ l = priv->ul_lb = gtk_label_new(NULL);
+ gtk_label_set_single_line_mode(GTK_LABEL(l), TRUE);
+ hig_workarea_add_row(t, &row, _("Uploaded:"), l, NULL);
+
+ /* state */
+ l = priv->state_lb = gtk_label_new(NULL);
+ gtk_label_set_single_line_mode(GTK_LABEL(l), TRUE);
+ hig_workarea_add_row(t, &row, _("State:"), l, NULL);
+
+ /* running for */
+ l = priv->date_started_lb = gtk_label_new(NULL);
+ gtk_label_set_single_line_mode(GTK_LABEL(l), TRUE);
+ hig_workarea_add_row(t, &row, _("Running time:"), l, NULL);
+
+ /* eta */
+ l = priv->eta_lb = gtk_label_new(NULL);
+ gtk_label_set_single_line_mode(GTK_LABEL(l), TRUE);
+ hig_workarea_add_row(t, &row, _("Remaining time:"), l, NULL);
+
+ /* last activity */
+ l = priv->last_activity_lb = gtk_label_new(NULL);
+ gtk_label_set_single_line_mode(GTK_LABEL(l), TRUE);
+ hig_workarea_add_row(t, &row, _("Last activity:"), l, NULL);
+
+ /* error */
+ l = g_object_new(GTK_TYPE_LABEL, "selectable", TRUE, "ellipsize",
+ PANGO_ELLIPSIZE_END, NULL);
+ hig_workarea_add_row(t, &row, _("Error:"), l, NULL);
+ priv->error_lb = l;
+
+ hig_workarea_add_section_divider(t, &row);
+ hig_workarea_add_section_title(t, &row, _("Details"));
+
+ /* destination */
+ l = g_object_new(GTK_TYPE_LABEL, "selectable", TRUE, "ellipsize",
+ PANGO_ELLIPSIZE_END, NULL);
+ hig_workarea_add_row(t, &row, _("Location:"), l, NULL);
+ priv->destination_lb = l;
+
+ /* hash */
+ l = g_object_new(GTK_TYPE_LABEL, "selectable", TRUE, "ellipsize",
+ PANGO_ELLIPSIZE_END, NULL);
+ hig_workarea_add_row(t, &row, _("Hash:"), l, NULL);
+ priv->hash_lb = l;
+
+ /* privacy */
+ l = gtk_label_new(NULL);
+ gtk_label_set_single_line_mode(GTK_LABEL(l), TRUE);
+ hig_workarea_add_row(t, &row, _("Privacy:"), l, NULL);
+ priv->privacy_lb = l;
+
+ /* origins */
+ l = g_object_new(GTK_TYPE_LABEL, "selectable", TRUE, "ellipsize",
+ PANGO_ELLIPSIZE_END, NULL);
+ hig_workarea_add_row(t, &row, _("Origin:"), l, NULL);
+ priv->origin_lb = l;
+
+ /* comment */
+ b = priv->comment_buffer = gtk_text_buffer_new(NULL);
+ w = gtk_text_view_new_with_buffer(b);
+ gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(w), GTK_WRAP_WORD);
+ gtk_text_view_set_editable(GTK_TEXT_VIEW(w), FALSE);
+ sw = gtk_scrolled_window_new(NULL, NULL);
+ gtk_widget_set_size_request(sw, 350, 36);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_container_add(GTK_CONTAINER(sw), w);
+ fr = gtk_frame_new(NULL);
+ gtk_frame_set_shadow_type(GTK_FRAME(fr), GTK_SHADOW_IN);
+ gtk_container_add(GTK_CONTAINER(fr), sw);
+ w = hig_workarea_add_tall_row(t, &row, _("Comment:"), fr, NULL);
+ gtk_misc_set_alignment(GTK_MISC(w), 0.0f, 0.0f);
+
+ hig_workarea_add_section_divider(t, &row);
+ return t;
+}
+
+static void info_page_update(TrgTorrentPropsDialog * dialog,
+ JsonObject * t,
+ TrgTorrentModel * torrentModel,
+ GtkTreeIter * iter)
+{
+ TrgTorrentPropsDialogPrivate *priv = GET_PRIVATE(dialog);
+ GtkTreeModel *model = GTK_TREE_MODEL(torrentModel);
+ gint64 sizeWhenDone, haveValid, downloaded, uploaded, percentDone, eta,
+ activityDate, error;
+ gchar *statusString;
+ guint flags;
+ const gchar *str;
+
+ char buf[512];
+
+ gtk_tree_model_get(model, iter,
+ TORRENT_COLUMN_SIZEWHENDONE, &sizeWhenDone,
+ TORRENT_COLUMN_UPLOADED, &uploaded,
+ TORRENT_COLUMN_DOWNLOADED, &downloaded,
+ TORRENT_COLUMN_HAVE_VALID, &haveValid,
+ TORRENT_COLUMN_PERCENTDONE, &percentDone,
+ TORRENT_COLUMN_ETA, &eta,
+ TORRENT_COLUMN_LASTACTIVE, &activityDate,
+ TORRENT_COLUMN_STATUS, &statusString,
+ TORRENT_COLUMN_FLAGS, &flags,
+ TORRENT_COLUMN_ERROR, &error, -1);
+
+ if (torrent_get_is_private(t))
+ str = _("Private to this tracker -- DHT and PEX disabled");
+ else
+ str = _("Public torrent");
+
+ gtk_label_set_text(GTK_LABEL(priv->privacy_lb), str);
+
+ {
+ const gchar *creator = torrent_get_creator(t);
+ gint64 dateCreated = torrent_get_date_created(t);
+ gchar *dateStr = epoch_to_string(dateCreated);
+
+ if (!creator || strlen(creator) <= 0)
+ g_snprintf(buf, sizeof(buf), _("Created on %1$s"), dateStr);
+ else
+ g_snprintf(buf, sizeof(buf), _("Created by %1$s on %2$s"),
+ creator, dateStr);
+
+ g_free(dateStr);
+ gtk_label_set_text(GTK_LABEL(priv->origin_lb), buf);
+ }
+
+ gtk_text_buffer_set_text(priv->comment_buffer, torrent_get_comment(t),
+ -1);
+ gtk_label_set_text(GTK_LABEL(priv->destination_lb),
+ torrent_get_download_dir(t));
+
+ gtk_label_set_text(GTK_LABEL(priv->state_lb), statusString);
+ g_free(statusString);
+
+ {
+ gchar *addedStr = epoch_to_string(torrent_get_added_date(t));
+ gtk_label_set_text(GTK_LABEL(priv->date_started_lb), addedStr);
+ g_free(addedStr);
+ }
+
+ /* eta */
+
+ if (eta > 0) {
+ tr_strltime_long(buf, eta, sizeof(buf));
+ gtk_label_set_text(GTK_LABEL(priv->eta_lb), buf);
+ } else {
+ gtk_label_set_text(GTK_LABEL(priv->eta_lb), "");
+ }
+
+ gtk_label_set_text(GTK_LABEL(priv->hash_lb), torrent_get_hash(t));
+ gtk_label_set_text(GTK_LABEL(priv->error_lb),
+ error ? torrent_get_errorstr(t) : _("No errors"));
+
+ if (flags & TORRENT_FLAG_ACTIVE) {
+ gtk_label_set_text(GTK_LABEL(priv->last_activity_lb),
+ _("Active now"));
+ } else {
+ gchar *activityStr = epoch_to_string(activityDate);
+ gtk_label_set_text(GTK_LABEL(priv->last_activity_lb), activityStr);
+ g_free(activityStr);
+ }
+
+ tr_strlsize(buf, sizeWhenDone, sizeof(buf));
+ gtk_label_set_text(GTK_LABEL(priv->size_lb), buf);
+
+ tr_strlsize(buf, downloaded, sizeof(buf));
+ gtk_label_set_text(GTK_LABEL(priv->dl_lb), buf);
+
+ tr_strlsize(buf, uploaded, sizeof(buf));
+ gtk_label_set_text(GTK_LABEL(priv->ul_lb), buf);
+
+ tr_strlsize(buf, haveValid, sizeof(buf));
+ gtk_label_set_text(GTK_LABEL(priv->have_lb), buf);
+}
+
+static GtkWidget *trg_props_limits_page_new(TrgTorrentPropsDialog * win,
+ JsonObject * json)
+{
+ TrgTorrentPropsDialogPrivate *priv = GET_PRIVATE(win);
+ GtkWidget *w, *tb, *t;
+ guint row = 0;
+
+ t = hig_workarea_create();
+
+ hig_workarea_add_section_title(t, &row, _("Bandwidth"));
+
+ w = trg_json_widget_check_new(&priv->widgets, json,
+ FIELD_HONORS_SESSION_LIMITS,
+ _("Honor global limits"), NULL);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ w = priv->bandwidthPriorityCombo = gtr_combo_box_new_enum(_("Low"), 0,
+ _("Normal"),
+ 1, _("High"),
+ 2, NULL);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(w),
+ torrent_get_bandwidth_priority(json) + 1);
+
+ hig_workarea_add_row(t, &row, _("Torrent priority:"), w, NULL);
+
+ if (json_object_has_member(json, FIELD_QUEUE_POSITION)) {
+ w = trg_json_widget_spin_new(&priv->widgets, json,
+ FIELD_QUEUE_POSITION, NULL, 0,
+ INT_MAX, 1);
+ hig_workarea_add_row(t, &row, _("Queue Position:"), w, w);
+ }
+
+ tb = trg_json_widget_check_new(&priv->widgets, json,
+ FIELD_DOWNLOAD_LIMITED,
+ _("Limit download speed (KiB/s)"),
+ NULL);
+ w = trg_json_widget_spin_new(&priv->widgets, json,
+ FIELD_DOWNLOAD_LIMIT, tb, 0, INT_MAX, 1);
+ hig_workarea_add_row_w(t, &row, tb, w, NULL);
+
+ tb = trg_json_widget_check_new(&priv->widgets, json,
+ FIELD_UPLOAD_LIMITED,
+ _("Limit upload speed (KiB/s)"), NULL);
+ w = trg_json_widget_spin_new(&priv->widgets, json, FIELD_UPLOAD_LIMIT,
+ tb, 0, INT_MAX, 1);
+ hig_workarea_add_row_w(t, &row, tb, w, NULL);
+
+ hig_workarea_add_section_title(t, &row, _("Seeding"));
+
+ w = priv->seedRatioMode =
+ gtr_combo_box_new_enum(_("Use global settings"), 0,
+ _("Stop seeding at ratio"), 1,
+ _("Seed regardless of ratio"), 2, NULL);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(w),
+ torrent_get_seed_ratio_mode(json));
+ hig_workarea_add_row(t, &row, _("Seed ratio mode:"), w, NULL);
+
+ w = trg_json_widget_spin_new(&priv->widgets, json,
+ FIELD_SEED_RATIO_LIMIT, NULL, 0, INT_MAX,
+ 0.2);
+ seed_ratio_mode_changed_cb(priv->seedRatioMode, w);
+ g_signal_connect(G_OBJECT(priv->seedRatioMode), "changed",
+ G_CALLBACK(seed_ratio_mode_changed_cb), w);
+ hig_workarea_add_row(t, &row, _("Seed ratio limit:"), w, w);
+
+ hig_workarea_add_section_title(t, &row, _("Peers"));
+
+ w = trg_json_widget_spin_new(&priv->widgets, json, FIELD_PEER_LIMIT,
+ NULL, 0, INT_MAX, 5);
+ hig_workarea_add_row(t, &row, _("Peer limit:"), w, w);
+
+ return t;
+}
+
+static void models_updated(TrgTorrentModel * model, gpointer data)
+{
+ TrgTorrentPropsDialogPrivate *priv = GET_PRIVATE(data);
+ GHashTable *ht = get_torrent_table(model);
+ gint64 serial = trg_client_get_serial(priv->client);
+ JsonObject *t = NULL;
+ GtkTreeIter iter;
+ gboolean exists = get_torrent_data(ht,
+ json_array_get_int_element(priv->
+ targetIds,
+ 0), &t,
+ &iter);
+
+ if (exists && priv->lastJson != t) {
+ trg_files_model_update(priv->filesModel,
+ GTK_TREE_VIEW(priv->filesTv), serial, t,
+ TORRENT_GET_MODE_UPDATE);
+ trg_peers_model_update(priv->peersModel,
+ TRG_TREE_VIEW(priv->peersTv), serial, t,
+ TORRENT_GET_MODE_UPDATE);
+ trg_trackers_model_update(priv->trackersModel, serial, t,
+ TORRENT_GET_MODE_UPDATE);
+ info_page_update(TRG_TORRENT_PROPS_DIALOG(data), t, model, &iter);
+ }
+
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->filesTv), exists);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->trackersTv), exists);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->peersTv), exists);
+
+ priv->lastJson = t;
+}
+
+static GObject *trg_torrent_props_dialog_constructor(GType type,
+ guint
+ n_construct_properties,
+ GObjectConstructParam
+ * construct_params)
+{
+ GObject *object = G_OBJECT_CLASS
+ (trg_torrent_props_dialog_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+
+ TrgTorrentPropsDialog *propsDialog = TRG_TORRENT_PROPS_DIALOG(object);
+ GtkWindow *window = GTK_WINDOW(object);
+ TrgTorrentPropsDialogPrivate *priv = GET_PRIVATE(object);
+
+ GtkTreeSelection *selection =
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->tv));
+ gint rowCount = gtk_tree_selection_count_selected_rows(selection);
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+ priv->show_details = trg_prefs_get_int(prefs, TRG_PREFS_KEY_STYLE,
+ TRG_PREFS_GLOBAL) !=
+ TRG_STYLE_CLASSIC && rowCount == 1;
+
+ gint64 width, height;
+
+ JsonObject *json;
+ GtkTreeIter iter;
+ GtkWidget *notebook, *contentvbox;
+
+ get_torrent_data(trg_client_get_torrent_table(priv->client),
+ trg_mw_get_selected_torrent_id(priv->parent), &json,
+ &iter);
+ priv->targetIds = build_json_id_array(priv->tv);
+
+ if (rowCount > 1) {
+ gchar *windowTitle =
+ g_strdup_printf(_("Multiple (%d) torrent properties"),
+ rowCount);
+ gtk_window_set_title(window, windowTitle);
+ g_free(windowTitle);
+ } else if (rowCount == 1) {
+ gtk_window_set_title(window, torrent_get_name(json));
+ }
+
+ gtk_window_set_transient_for(window, GTK_WINDOW(priv->parent));
+ gtk_window_set_destroy_with_parent(window, TRUE);
+
+ gtk_dialog_add_button(GTK_DIALOG(object), GTK_STOCK_CLOSE,
+ GTK_RESPONSE_CLOSE);
+ gtk_dialog_add_button(GTK_DIALOG(object), GTK_STOCK_OK,
+ GTK_RESPONSE_OK);
+
+ gtk_container_set_border_width(GTK_CONTAINER(object), GUI_PAD);
+
+ gtk_dialog_set_default_response(GTK_DIALOG(object), GTK_RESPONSE_OK);
+
+ g_signal_connect(G_OBJECT(object), "response",
+ G_CALLBACK(trg_torrent_props_response_cb), NULL);
+
+ notebook = gtk_notebook_new();
+
+ if (priv->show_details) {
+ gint64 serial = trg_client_get_serial(priv->client);
+
+ /* Information */
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ info_page_new(propsDialog),
+ gtk_label_new(_("Information")));
+ info_page_update(propsDialog, json, priv->torrentModel, &iter);
+
+ /* Files */
+
+ priv->filesModel = trg_files_model_new();
+ priv->filesTv =
+ trg_files_tree_view_new(priv->filesModel, priv->parent,
+ priv->client,
+ "TrgFilesTreeView-dialog");
+ trg_files_model_update(priv->filesModel,
+ GTK_TREE_VIEW(priv->filesTv), serial, json,
+ TORRENT_GET_MODE_FIRST);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->filesTv), TRUE);
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ my_scrolledwin_new(GTK_WIDGET
+ (priv->filesTv)),
+ gtk_label_new(_("Files")));
+
+ /* Peers */
+
+ priv->peersModel = trg_peers_model_new();
+ priv->peersTv = trg_peers_tree_view_new(prefs, priv->peersModel,
+ "TrgPeersTreeView-dialog");
+ trg_peers_model_update(priv->peersModel,
+ TRG_TREE_VIEW(priv->peersTv), serial, json,
+ TORRENT_GET_MODE_FIRST);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->peersTv), TRUE);
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ my_scrolledwin_new(GTK_WIDGET
+ (priv->peersTv)),
+ gtk_label_new(_("Peers")));
+
+ /* Trackers */
+
+ priv->trackersModel = trg_trackers_model_new();
+ priv->trackersTv = trg_trackers_tree_view_new(priv->trackersModel,
+ priv->client,
+ priv->parent,
+ "TrgTrackersTreeView-dialog");
+ trg_trackers_tree_view_new_connection(priv->trackersTv,
+ priv->client);
+ trg_trackers_model_update(priv->trackersModel, serial, json,
+ TORRENT_GET_MODE_FIRST);
+ gtk_widget_set_sensitive(GTK_WIDGET(priv->trackersTv), TRUE);
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ my_scrolledwin_new(GTK_WIDGET
+ (priv->trackersTv)),
+ gtk_label_new(_("Trackers")));
+
+ g_object_unref(priv->trackersModel);
+ g_object_unref(priv->filesModel);
+ g_object_unref(priv->peersModel);
+
+ g_signal_connect_object(priv->torrentModel, "update", G_CALLBACK
+ (models_updated), object, G_CONNECT_AFTER);
+
+ priv->lastJson = json;
+ }
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ trg_props_limits_page_new(propsDialog, json),
+ gtk_label_new(_("Limits")));
+
+ gtk_container_set_border_width(GTK_CONTAINER(notebook), GUI_PAD);
+
+ contentvbox = gtk_dialog_get_content_area(GTK_DIALOG(object));
+ gtk_box_pack_start(GTK_BOX(contentvbox), notebook, TRUE, TRUE, 0);
+
+ if (priv->show_details) {
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+ if ((width =
+ trg_prefs_get_int(prefs, "dialog-width",
+ TRG_PREFS_GLOBAL)) <= 0
+ || (height =
+ trg_prefs_get_int(prefs, "dialog-height",
+ TRG_PREFS_GLOBAL)) <= 0) {
+ width = 700;
+ height = 600;
+ }
+ } else {
+ width = height = 500;
+ }
+
+ gtk_window_set_default_size(window, width, height);
+
+ return object;
+}
+
+static void trg_torrent_props_dialog_class_init(TrgTorrentPropsDialogClass
+ * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->constructor = trg_torrent_props_dialog_constructor;
+ object_class->set_property = trg_torrent_props_dialog_set_property;
+ object_class->get_property = trg_torrent_props_dialog_get_property;
+
+ g_type_class_add_private(klass, sizeof(TrgTorrentPropsDialogPrivate));
+
+ g_object_class_install_property(object_class, PROP_TREEVIEW,
+ g_param_spec_object
+ ("torrent-tree-view",
+ "TrgTorrentTreeView",
+ "TrgTorrentTreeView",
+ TRG_TYPE_TORRENT_TREE_VIEW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class, PROP_TORRENT_MODEL,
+ g_param_spec_object("torrent-model",
+ "TrgTorrentModel",
+ "TrgTorrentModel",
+ TRG_TYPE_TORRENT_MODEL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class, PROP_PARENT_WINDOW,
+ g_param_spec_object("parent-window",
+ "Parent window",
+ "Parent window",
+ TRG_TYPE_MAIN_WINDOW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class, PROP_CLIENT,
+ g_param_spec_pointer("trg-client",
+ "TClient",
+ "Client",
+ G_PARAM_READWRITE
+ |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+}
+
+static void trg_torrent_props_dialog_init(TrgTorrentPropsDialog *
+ self G_GNUC_UNUSED)
+{
+}
+
+TrgTorrentPropsDialog *trg_torrent_props_dialog_new(GtkWindow * window,
+ TrgTorrentTreeView *
+ treeview,
+ TrgTorrentModel *
+ torrentModel,
+ TrgClient * client)
+{
+ return g_object_new(TRG_TYPE_TORRENT_PROPS_DIALOG, "torrent-tree-view",
+ treeview, "torrent-model", torrentModel,
+ "parent-window", window, "trg-client", client,
+ NULL);
+}
diff --git a/src/trg-torrent-props-dialog.h b/src/trg-torrent-props-dialog.h
new file mode 100644
index 0000000..9829127
--- /dev/null
+++ b/src/trg-torrent-props-dialog.h
@@ -0,0 +1,58 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_TORRENT_PROPS_DIALOG_H_
+#define TRG_TORRENT_PROPS_DIALOG_H_
+
+#include <glib-object.h>
+
+#include "trg-torrent-tree-view.h"
+#include "trg-client.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_TORRENT_PROPS_DIALOG trg_torrent_props_dialog_get_type()
+#define TRG_TORRENT_PROPS_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_TORRENT_PROPS_DIALOG, TrgTorrentPropsDialog))
+#define TRG_TORRENT_PROPS_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_TORRENT_PROPS_DIALOG, TrgTorrentPropsDialogClass))
+#define TRG_IS_TORRENT_PROPS_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_TORRENT_PROPS_DIALOG))
+#define TRG_IS_TORRENT_PROPS_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_TORRENT_PROPS_DIALOG))
+#define TRG_TORRENT_PROPS_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_TORRENT_PROPS_DIALOG, TrgTorrentPropsDialogClass))
+ typedef struct {
+ GtkDialog parent;
+} TrgTorrentPropsDialog;
+
+typedef struct {
+ GtkDialogClass parent_class;
+} TrgTorrentPropsDialogClass;
+
+GType trg_torrent_props_dialog_get_type(void);
+
+TrgTorrentPropsDialog *trg_torrent_props_dialog_new(GtkWindow * window,
+ TrgTorrentTreeView *
+ treeview,
+ TrgTorrentModel *
+ torrentModel,
+ TrgClient * client);
+
+G_END_DECLS
+#endif /* TRG_TORRENT_PROPS_DIALOG_H_ */
diff --git a/src/trg-torrent-tree-view.c b/src/trg-torrent-tree-view.c
new file mode 100644
index 0000000..207bef7
--- /dev/null
+++ b/src/trg-torrent-tree-view.c
@@ -0,0 +1,337 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "trg-prefs.h"
+#include "trg-tree-view.h"
+#include "trg-torrent-model.h"
+#include "torrent-cell-renderer.h"
+#include "trg-torrent-tree-view.h"
+
+G_DEFINE_TYPE(TrgTorrentTreeView, trg_torrent_tree_view,
+ TRG_TYPE_TREE_VIEW)
+#define GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_TORRENT_TREE_VIEW, TrgTorrentTreeViewPrivate))
+typedef struct _TrgTorrentTreeViewPrivate TrgTorrentTreeViewPrivate;
+
+struct _TrgTorrentTreeViewPrivate {
+ TrgClient *client;
+};
+
+static void trg_torrent_tree_view_class_init(TrgTorrentTreeViewClass *
+ klass G_GNUC_UNUSED)
+{
+ g_type_class_add_private(klass, sizeof(TrgTorrentTreeViewPrivate));
+}
+
+static void trg_torrent_tree_view_init(TrgTorrentTreeView * tttv)
+{
+ TrgTreeView *ttv = TRG_TREE_VIEW(tttv);
+ trg_column_description *desc;
+
+ desc =
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_STOCKICONTEXT,
+ TORRENT_COLUMN_NAME, _("Name"), "name",
+ 0);
+ desc->model_column_extra = TORRENT_COLUMN_ICON;
+
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_SIZE,
+ TORRENT_COLUMN_SIZEWHENDONE, _("Size"),
+ "size", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_PROG,
+ TORRENT_COLUMN_PERCENTDONE, _("Done"), "done",
+ 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_TEXT, TORRENT_COLUMN_STATUS,
+ _("Status"), "status", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TORRENT_COLUMN_SEEDS, _("Seeds"), "seeds", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TORRENT_COLUMN_PEERS_TO_US, _("Sending"),
+ "sending", TRG_COLUMN_EXTRA);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TORRENT_COLUMN_LEECHERS, _("Leechers"),
+ "leechers", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TORRENT_COLUMN_DOWNLOADS, _("Downloads"),
+ "downloads", TRG_COLUMN_EXTRA);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TORRENT_COLUMN_PEERS_FROM_US, _("Receiving"),
+ "connected-leechers", TRG_COLUMN_EXTRA);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TORRENT_COLUMN_PEERS_CONNECTED,
+ _("Connected"), "connected-peers", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TORRENT_COLUMN_FROMPEX, _("PEX Peers"),
+ "from-pex",
+ TRG_COLUMN_EXTRA |
+ TRG_COLUMN_HIDE_FROM_TOP_MENU);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TORRENT_COLUMN_FROMDHT, _("DHT Peers"),
+ "from-dht",
+ TRG_COLUMN_EXTRA |
+ TRG_COLUMN_HIDE_FROM_TOP_MENU);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TORRENT_COLUMN_FROMTRACKERS,
+ _("Tracker Peers"), "from-trackers",
+ TRG_COLUMN_EXTRA |
+ TRG_COLUMN_HIDE_FROM_TOP_MENU);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TORRENT_COLUMN_FROMLTEP, _("LTEP Peers"),
+ "from-ltep",
+ TRG_COLUMN_EXTRA |
+ TRG_COLUMN_HIDE_FROM_TOP_MENU);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TORRENT_COLUMN_FROMRESUME, _("Resumed Peers"),
+ "from-resume",
+ TRG_COLUMN_EXTRA |
+ TRG_COLUMN_HIDE_FROM_TOP_MENU);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TORRENT_COLUMN_FROMINCOMING,
+ _("Incoming Peers"), "from-incoming",
+ TRG_COLUMN_EXTRA |
+ TRG_COLUMN_HIDE_FROM_TOP_MENU);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_TEXT,
+ TORRENT_COLUMN_PEER_SOURCES,
+ _("Peers T/I/E/H/X/L/R"), "peer-sources",
+ TRG_COLUMN_EXTRA |
+ TRG_COLUMN_HIDE_FROM_TOP_MENU);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_SPEED,
+ TORRENT_COLUMN_DOWNSPEED, _("Down Speed"),
+ "down-speed", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_SPEED,
+ TORRENT_COLUMN_UPSPEED, _("Up Speed"),
+ "up-speed", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_ETA, TORRENT_COLUMN_ETA,
+ _("ETA"), "eta", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_SIZE,
+ TORRENT_COLUMN_UPLOADED, _("Uploaded"),
+ "uploaded", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_SIZE,
+ TORRENT_COLUMN_DOWNLOADED, _("Downloaded"),
+ "downloaded", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_RATIO, TORRENT_COLUMN_RATIO,
+ _("Ratio"), "ratio", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_EPOCH, TORRENT_COLUMN_ADDED,
+ _("Added"), "added", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_TEXT,
+ TORRENT_COLUMN_TRACKERHOST,
+ _("First Tracker"), "first-tracker",
+ TRG_COLUMN_EXTRA);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_TEXT,
+ TORRENT_COLUMN_DOWNLOADDIR, _("Location"),
+ "download-dir", TRG_COLUMN_EXTRA);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_TEXT, TORRENT_COLUMN_ID,
+ _("ID"), "id", TRG_COLUMN_EXTRA);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_PRIO,
+ TORRENT_COLUMN_BANDWIDTH_PRIORITY,
+ _("Priority"), "priority", TRG_COLUMN_EXTRA);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTEQZERO,
+ TORRENT_COLUMN_QUEUE_POSITION,
+ _("Queue Position"), "queue-position",
+ TRG_COLUMN_EXTRA);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_EPOCH,
+ TORRENT_COLUMN_DONE_DATE, _("Completed"),
+ "done-date", TRG_COLUMN_EXTRA);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_EPOCH,
+ TORRENT_COLUMN_LASTACTIVE, _("Last Active"),
+ "last-active", TRG_COLUMN_EXTRA);
+
+ gtk_tree_view_set_search_column(GTK_TREE_VIEW(tttv),
+ TORRENT_COLUMN_NAME);
+}
+
+static void
+trg_torrent_model_get_json_id_array_foreach(GtkTreeModel * model,
+ GtkTreePath *
+ path G_GNUC_UNUSED,
+ GtkTreeIter * iter,
+ gpointer data)
+{
+ JsonArray *output = (JsonArray *) data;
+ gint64 id;
+ gtk_tree_model_get(model, iter, TORRENT_COLUMN_ID, &id, -1);
+ json_array_add_int_element(output, id);
+}
+
+JsonArray *build_json_id_array(TrgTorrentTreeView * tv)
+{
+ GtkTreeSelection *selection =
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
+
+ JsonArray *ids = json_array_new();
+ gtk_tree_selection_selected_foreach(selection,
+ (GtkTreeSelectionForeachFunc)
+ trg_torrent_model_get_json_id_array_foreach,
+ ids);
+
+ return ids;
+}
+
+static void setup_classic_layout(TrgTorrentTreeView * tv)
+{
+ gtk_tree_view_set_rubber_banding(GTK_TREE_VIEW(tv), TRUE);
+ gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(tv), TRUE);
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv), TRUE);
+ trg_tree_view_setup_columns(TRG_TREE_VIEW(tv));
+}
+
+static void
+trg_torrent_tree_view_renderer_pref_changed(TrgPrefs * p,
+ const gchar * updatedKey,
+ gpointer data)
+{
+ if (!g_strcmp0(updatedKey, TRG_PREFS_KEY_STYLE)) {
+ GtkTreeView *tv =
+ torrent_cell_renderer_get_owner(TORRENT_CELL_RENDERER(data));
+ gboolean compact = trg_prefs_get_int(p, TRG_PREFS_KEY_STYLE,
+ TRG_PREFS_GLOBAL) ==
+ TRG_STYLE_TR_COMPACT;
+ g_object_set(G_OBJECT(data), "compact", GINT_TO_POINTER(compact),
+ NULL);
+#if GTK_CHECK_VERSION( 3,0,0 )
+ g_signal_emit_by_name(tv, "style-updated", NULL, NULL);
+#else
+ g_signal_emit_by_name(tv, "style-set", NULL, NULL);
+#endif
+ }
+}
+
+static void setup_transmission_layout(TrgTorrentTreeView * tv,
+ gint64 style)
+{
+ TrgTorrentTreeViewPrivate *priv = GET_PRIVATE(tv);
+ GtkCellRenderer *renderer = torrent_cell_renderer_new();
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+
+ GtkTreeViewColumn *column =
+ gtk_tree_view_column_new_with_attributes("",
+ renderer,
+ "status",
+ TORRENT_COLUMN_FLAGS,
+ "error",
+ TORRENT_COLUMN_ERROR,
+ "fileCount",
+ TORRENT_COLUMN_FILECOUNT,
+ "totalSize",
+ TORRENT_COLUMN_TOTALSIZE,
+ "ratio",
+ TORRENT_COLUMN_RATIO,
+ "downloaded",
+ TORRENT_COLUMN_DOWNLOADED,
+ "haveValid",
+ TORRENT_COLUMN_HAVE_VALID,
+ "sizeWhenDone",
+ TORRENT_COLUMN_SIZEWHENDONE,
+ "uploaded",
+ TORRENT_COLUMN_UPLOADED,
+ "percentComplete",
+ TORRENT_COLUMN_PERCENTDONE,
+ "metadataPercentComplete",
+ TORRENT_COLUMN_METADATAPERCENTCOMPLETE,
+ "upSpeed",
+ TORRENT_COLUMN_UPSPEED,
+ "downSpeed",
+ TORRENT_COLUMN_DOWNSPEED,
+ "peersToUs",
+ TORRENT_COLUMN_PEERS_TO_US,
+ "peersGettingFromUs",
+ TORRENT_COLUMN_PEERS_FROM_US,
+ "webSeedsToUs",
+ TORRENT_COLUMN_WEB_SEEDS_TO_US,
+ "eta", TORRENT_COLUMN_ETA,
+ "json",
+ TORRENT_COLUMN_JSON,
+ "seedRatioMode",
+ TORRENT_COLUMN_SEED_RATIO_MODE,
+ "seedRatioLimit",
+ TORRENT_COLUMN_SEED_RATIO_LIMIT,
+ "connected",
+ TORRENT_COLUMN_PEERS_CONNECTED,
+ NULL);
+
+ g_object_set(G_OBJECT(renderer), "client", priv->client,
+ "owner", tv,
+ "compact", style == TRG_STYLE_TR_COMPACT, NULL);
+
+ g_signal_connect_object(prefs, "pref-changed",
+ G_CALLBACK
+ (trg_torrent_tree_view_renderer_pref_changed),
+ renderer, G_CONNECT_AFTER);
+
+ gtk_tree_view_column_set_resizable(column, FALSE);
+ gtk_tree_view_column_set_reorderable(column, FALSE);
+ gtk_tree_view_set_rubber_banding(GTK_TREE_VIEW(tv), FALSE);
+ gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(tv), FALSE);
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv), FALSE);
+
+ gtk_tree_view_column_set_sort_column_id(column, TORRENT_COLUMN_NAME);
+
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tv), column);
+}
+
+static void
+trg_torrent_tree_view_pref_changed(TrgPrefs * p, const gchar * updatedKey,
+ gpointer data)
+{
+ if (!g_strcmp0(updatedKey, TRG_PREFS_KEY_STYLE)) {
+ TrgTorrentTreeViewPrivate *priv = GET_PRIVATE(data);
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+
+ trg_tree_view_remove_all_columns(TRG_TREE_VIEW(data));
+ if (trg_prefs_get_int(p, TRG_PREFS_KEY_STYLE, TRG_PREFS_GLOBAL) ==
+ TRG_STYLE_CLASSIC)
+ setup_classic_layout(TRG_TORRENT_TREE_VIEW(data));
+ else
+ setup_transmission_layout(TRG_TORRENT_TREE_VIEW(data),
+ trg_prefs_get_int(prefs,
+ TRG_PREFS_KEY_STYLE,
+ TRG_PREFS_GLOBAL));
+ }
+}
+
+TrgTorrentTreeView *trg_torrent_tree_view_new(TrgClient * tc,
+ GtkTreeModel * model)
+{
+ GObject *obj = g_object_new(TRG_TYPE_TORRENT_TREE_VIEW, NULL);
+ TrgTorrentTreeViewPrivate *priv = GET_PRIVATE(obj);
+ TrgPrefs *prefs = trg_client_get_prefs(tc);
+ gint64 style =
+ trg_prefs_get_int(prefs, TRG_PREFS_KEY_STYLE, TRG_PREFS_GLOBAL);
+
+ trg_tree_view_set_prefs(TRG_TREE_VIEW(obj), trg_client_get_prefs(tc));
+ gtk_tree_view_set_model(GTK_TREE_VIEW(obj), model);
+
+ priv->client = tc;
+
+ if (style == TRG_STYLE_CLASSIC) {
+ setup_classic_layout(TRG_TORRENT_TREE_VIEW(obj));
+ } else {
+ setup_transmission_layout(TRG_TORRENT_TREE_VIEW(obj), style);
+ }
+
+ g_signal_connect(prefs, "pref-changed",
+ G_CALLBACK(trg_torrent_tree_view_pref_changed), obj);
+
+ trg_tree_view_restore_sort(TRG_TREE_VIEW(obj),
+ TRG_TREE_VIEW_SORTABLE_PARENT);
+
+ return TRG_TORRENT_TREE_VIEW(obj);
+}
diff --git a/src/trg-torrent-tree-view.h b/src/trg-torrent-tree-view.h
new file mode 100644
index 0000000..d308de9
--- /dev/null
+++ b/src/trg-torrent-tree-view.h
@@ -0,0 +1,58 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _TRG_TORRENT_TREE_VIEW_H_
+#define _TRG_TORRENT_TREE_VIEW_H_
+
+#include <glib-object.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-prefs.h"
+#include "trg-torrent-model.h"
+#include "trg-tree-view.h"
+#include "trg-state-selector.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_TORRENT_TREE_VIEW trg_torrent_tree_view_get_type()
+#define TRG_TORRENT_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_TORRENT_TREE_VIEW, TrgTorrentTreeView))
+#define TRG_TORRENT_TREE_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_TORRENT_TREE_VIEW, TrgTorrentTreeViewClass))
+#define TRG_IS_TORRENT_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_TORRENT_TREE_VIEW))
+#define TRG_IS_TORRENT_TREE_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_TORRENT_TREE_VIEW))
+#define TRG_TORRENT_TREE_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_TORRENT_TREE_VIEW, TrgTorrentTreeViewClass))
+ typedef struct {
+ TrgTreeView parent;
+} TrgTorrentTreeView;
+
+typedef struct {
+ TrgTreeViewClass parent_class;
+} TrgTorrentTreeViewClass;
+
+GType trg_torrent_tree_view_get_type(void);
+
+TrgTorrentTreeView *trg_torrent_tree_view_new(TrgClient * tc,
+ GtkTreeModel * model);
+JsonArray *build_json_id_array(TrgTorrentTreeView * tv);
+
+G_END_DECLS
+#endif /* _TRG_TORRENT_TREE_VIEW_H_ */
diff --git a/src/trg-trackers-model.c b/src/trg-trackers-model.c
new file mode 100644
index 0000000..c998c93
--- /dev/null
+++ b/src/trg-trackers-model.c
@@ -0,0 +1,196 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+#include "config.h"
+#include "torrent.h"
+#include "trg-client.h"
+#include "trg-model.h"
+#include "trg-trackers-model.h"
+
+G_DEFINE_TYPE(TrgTrackersModel, trg_trackers_model, GTK_TYPE_LIST_STORE)
+#define TRG_TRACKERS_MODEL_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_TRACKERS_MODEL, TrgTrackersModelPrivate))
+typedef struct _TrgTrackersModelPrivate TrgTrackersModelPrivate;
+
+struct _TrgTrackersModelPrivate {
+ gint64 torrentId;
+ gint64 accept;
+};
+
+void trg_trackers_model_set_no_selection(TrgTrackersModel * model)
+{
+ TrgTrackersModelPrivate *priv = TRG_TRACKERS_MODEL_GET_PRIVATE(model);
+ priv->torrentId = -1;
+}
+
+gint64 trg_trackers_model_get_torrent_id(TrgTrackersModel * model)
+{
+ TrgTrackersModelPrivate *priv = TRG_TRACKERS_MODEL_GET_PRIVATE(model);
+ return priv->torrentId;
+}
+
+void
+trg_trackers_model_update(TrgTrackersModel * model,
+ gint64 updateSerial, JsonObject * t, gint mode)
+{
+ TrgTrackersModelPrivate *priv = TRG_TRACKERS_MODEL_GET_PRIVATE(model);
+
+ GtkTreeIter trackIter;
+ JsonObject *tracker;
+ gint64 trackerId;
+ GList *trackers, *li;
+ const gchar *announce;
+ const gchar *scrape;
+
+ if (mode == TORRENT_GET_MODE_FIRST) {
+ gtk_list_store_clear(GTK_LIST_STORE(model));
+ priv->torrentId = torrent_get_id(t);
+ priv->accept = TRUE;
+ } else if (!priv->accept) {
+ return;
+ }
+
+ trackers = json_array_get_elements(torrent_get_tracker_stats(t));
+
+ for (li = trackers; li; li = g_list_next(li)) {
+ tracker = json_node_get_object((JsonNode *) li->data);
+ trackerId = tracker_stats_get_id(tracker);
+ announce = tracker_stats_get_announce(tracker);
+ scrape = tracker_stats_get_scrape(tracker);
+
+ if (mode == TORRENT_GET_MODE_FIRST
+ || find_existing_model_item(GTK_TREE_MODEL(model),
+ TRACKERCOL_ID, trackerId,
+ &trackIter) == FALSE)
+ gtk_list_store_append(GTK_LIST_STORE(model), &trackIter);
+
+#ifdef DEBUG
+ gtk_list_store_set(GTK_LIST_STORE(model), &trackIter,
+ TRACKERCOL_ICON, GTK_STOCK_NETWORK, -1);
+ gtk_list_store_set(GTK_LIST_STORE(model), &trackIter,
+ TRACKERCOL_TIER,
+ tracker_stats_get_tier(tracker), -1);
+ gtk_list_store_set(GTK_LIST_STORE(model), &trackIter,
+ TRACKERCOL_ANNOUNCE, announce, -1);
+ gtk_list_store_set(GTK_LIST_STORE(model), &trackIter,
+ TRACKERCOL_SCRAPE, scrape, -1);
+ gtk_list_store_set(GTK_LIST_STORE(model), &trackIter,
+ TRACKERCOL_ID, trackerId, -1);
+ gtk_list_store_set(GTK_LIST_STORE(model), &trackIter,
+ TRACKERCOL_UPDATESERIAL, updateSerial, -1);
+ gtk_list_store_set(GTK_LIST_STORE(model), &trackIter,
+ TRACKERCOL_LAST_ANNOUNCE_RESULT,
+ tracker_stats_get_announce_result(tracker), -1);
+ gtk_list_store_set(GTK_LIST_STORE(model), &trackIter,
+ TRACKERCOL_LAST_ANNOUNCE_TIME,
+ tracker_stats_get_last_announce_time(tracker),
+ -1);
+ gtk_list_store_set(GTK_LIST_STORE(model), &trackIter,
+ TRACKERCOL_LAST_SCRAPE_TIME,
+ tracker_stats_get_last_scrape_time(tracker),
+ -1);
+ gtk_list_store_set(GTK_LIST_STORE(model), &trackIter,
+ TRACKERCOL_HOST,
+ tracker_stats_get_host(tracker), -1);
+ gtk_list_store_set(GTK_LIST_STORE(model), &trackIter,
+ TRACKERCOL_LAST_ANNOUNCE_PEER_COUNT,
+ tracker_stats_get_last_announce_peer_count
+ (tracker), -1);
+ gtk_list_store_set(GTK_LIST_STORE(model), &trackIter,
+ TRACKERCOL_LEECHERCOUNT,
+ tracker_stats_get_leecher_count(tracker), -1);
+ gtk_list_store_set(GTK_LIST_STORE(model), &trackIter,
+ TRACKERCOL_SEEDERCOUNT,
+ tracker_stats_get_seeder_count(tracker), -1);
+#else
+ gtk_list_store_set(GTK_LIST_STORE(model), &trackIter,
+ TRACKERCOL_ICON, GTK_STOCK_NETWORK,
+ TRACKERCOL_ID, trackerId,
+ TRACKERCOL_UPDATESERIAL, updateSerial,
+ TRACKERCOL_TIER,
+ tracker_stats_get_tier(tracker),
+ TRACKERCOL_ANNOUNCE, announce,
+ TRACKERCOL_SCRAPE, scrape, TRACKERCOL_HOST,
+ tracker_stats_get_host(tracker),
+ TRACKERCOL_LAST_ANNOUNCE_RESULT,
+ tracker_stats_get_announce_result(tracker),
+ TRACKERCOL_LAST_ANNOUNCE_TIME,
+ tracker_stats_get_last_announce_time(tracker),
+ TRACKERCOL_LAST_SCRAPE_TIME,
+ tracker_stats_get_last_scrape_time(tracker),
+ TRACKERCOL_LAST_ANNOUNCE_PEER_COUNT,
+ tracker_stats_get_last_announce_peer_count
+ (tracker), TRACKERCOL_LEECHERCOUNT,
+ tracker_stats_get_leecher_count(tracker),
+ TRACKERCOL_SEEDERCOUNT,
+ tracker_stats_get_seeder_count(tracker), -1);
+#endif
+ }
+
+ g_list_free(trackers);
+ trg_model_remove_removed(GTK_LIST_STORE(model),
+ TRACKERCOL_UPDATESERIAL, updateSerial);
+}
+
+static void trg_trackers_model_class_init(TrgTrackersModelClass * klass)
+{
+ g_type_class_add_private(klass, sizeof(TrgTrackersModelPrivate));
+}
+
+void
+trg_trackers_model_set_accept(TrgTrackersModel * model, gboolean accept)
+{
+ TrgTrackersModelPrivate *priv = TRG_TRACKERS_MODEL_GET_PRIVATE(model);
+ priv->accept = accept;
+}
+
+static void trg_trackers_model_init(TrgTrackersModel * self)
+{
+ TrgTrackersModelPrivate *priv = TRG_TRACKERS_MODEL_GET_PRIVATE(self);
+
+ GType column_types[TRACKERCOL_COLUMNS];
+
+ column_types[TRACKERCOL_ICON] = G_TYPE_STRING;
+ column_types[TRACKERCOL_TIER] = G_TYPE_INT64;
+ column_types[TRACKERCOL_ANNOUNCE] = G_TYPE_STRING;
+ column_types[TRACKERCOL_SCRAPE] = G_TYPE_STRING;
+ column_types[TRACKERCOL_ID] = G_TYPE_INT64;
+ column_types[TRACKERCOL_LAST_ANNOUNCE_PEER_COUNT] = G_TYPE_INT64;
+ column_types[TRACKERCOL_LAST_ANNOUNCE_TIME] = G_TYPE_INT64;
+ column_types[TRACKERCOL_LAST_SCRAPE_TIME] = G_TYPE_INT64;
+ column_types[TRACKERCOL_SEEDERCOUNT] = G_TYPE_INT64;
+ column_types[TRACKERCOL_LEECHERCOUNT] = G_TYPE_INT64;
+ column_types[TRACKERCOL_HOST] = G_TYPE_STRING;
+ column_types[TRACKERCOL_LAST_ANNOUNCE_RESULT] = G_TYPE_STRING;
+ column_types[TRACKERCOL_UPDATESERIAL] = G_TYPE_INT64;
+
+ priv->accept = TRUE;
+ priv->torrentId = -1;
+
+ gtk_list_store_set_column_types(GTK_LIST_STORE(self),
+ TRACKERCOL_COLUMNS, column_types);
+}
+
+TrgTrackersModel *trg_trackers_model_new(void)
+{
+ return g_object_new(TRG_TYPE_TRACKERS_MODEL, NULL);
+}
diff --git a/src/trg-trackers-model.h b/src/trg-trackers-model.h
new file mode 100644
index 0000000..3e699f7
--- /dev/null
+++ b/src/trg-trackers-model.h
@@ -0,0 +1,96 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_TRACKERS_MODEL_H_
+#define TRG_TRACKERS_MODEL_H_
+
+#include <glib-object.h>
+#include <json-glib/json-glib.h>
+
+G_BEGIN_DECLS
+#define TRG_TYPE_TRACKERS_MODEL trg_trackers_model_get_type()
+#define TRG_TRACKERS_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_TRACKERS_MODEL, TrgTrackersModel))
+#define TRG_TRACKERS_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_TRACKERS_MODEL, TrgTrackersModelClass))
+#define TRG_IS_TRACKERS_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_TRACKERS_MODEL))
+#define TRG_IS_TRACKERS_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_TRACKERS_MODEL))
+#define TRG_TRACKERS_MODEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_TRACKERS_MODEL, TrgTrackersModelClass))
+ typedef struct {
+ GtkListStore parent;
+} TrgTrackersModel;
+
+typedef struct {
+ GtkListStoreClass parent_class;
+} TrgTrackersModelClass;
+
+GType trg_trackers_model_get_type(void);
+
+TrgTrackersModel *trg_trackers_model_new(void);
+
+G_END_DECLS
+ void trg_trackers_model_update(TrgTrackersModel * model,
+ gint64 updateSerial, JsonObject * t,
+ gint mode);
+void trg_trackers_model_set_accept(TrgTrackersModel * model,
+ gboolean accept);
+gint64 trg_trackers_model_get_torrent_id(TrgTrackersModel * model);
+void trg_trackers_model_set_no_selection(TrgTrackersModel * model);
+
+typedef enum {
+ /* we won't (announce,scrape) this torrent to this tracker because
+ * the torrent is stopped, or because of an error, or whatever */
+ TR_TRACKER_INACTIVE = 0,
+
+ /* we will (announce,scrape) this torrent to this tracker, and are
+ * waiting for enough time to pass to satisfy the tracker's interval */
+ TR_TRACKER_WAITING = 1,
+
+ /* it's time to (announce,scrape) this torrent, and we're waiting on a
+ * a free slot to open up in the announce manager */
+ TR_TRACKER_QUEUED = 2,
+
+ /* we're (announcing,scraping) this torrent right now */
+ TR_TRACKER_ACTIVE = 3
+} tr_tracker_state;
+
+enum {
+ /* trackers */
+ TRACKERCOL_ICON,
+ TRACKERCOL_TIER,
+ TRACKERCOL_ANNOUNCE,
+ TRACKERCOL_SCRAPE,
+ TRACKERCOL_ID,
+ /* trackerstats */
+ TRACKERCOL_LAST_ANNOUNCE_PEER_COUNT,
+ TRACKERCOL_LAST_ANNOUNCE_TIME,
+ TRACKERCOL_LAST_SCRAPE_TIME,
+ TRACKERCOL_SEEDERCOUNT,
+ TRACKERCOL_LEECHERCOUNT,
+ TRACKERCOL_HOST,
+ TRACKERCOL_LAST_ANNOUNCE_RESULT,
+ /* other */
+ TRACKERCOL_UPDATESERIAL,
+ TRACKERCOL_COLUMNS
+};
+
+#endif /* TRG_TRACKERS_MODEL_H_ */
diff --git a/src/trg-trackers-tree-view.c b/src/trg-trackers-tree-view.c
new file mode 100644
index 0000000..0dfd4f9
--- /dev/null
+++ b/src/trg-trackers-tree-view.c
@@ -0,0 +1,392 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "trg-prefs.h"
+#include "trg-trackers-tree-view.h"
+#include "trg-tree-view.h"
+#include "trg-client.h"
+#include "trg-menu-bar.h"
+#include "requests.h"
+#include "json.h"
+#include "trg-trackers-model.h"
+#include "trg-main-window.h"
+
+G_DEFINE_TYPE(TrgTrackersTreeView, trg_trackers_tree_view,
+ TRG_TYPE_TREE_VIEW)
+#define TRG_TRACKERS_TREE_VIEW_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_TRACKERS_TREE_VIEW, TrgTrackersTreeViewPrivate))
+typedef struct _TrgTrackersTreeViewPrivate TrgTrackersTreeViewPrivate;
+
+struct _TrgTrackersTreeViewPrivate {
+ TrgClient *client;
+ GtkCellRenderer *announceRenderer;
+ GtkTreeViewColumn *announceColumn;
+ TrgMainWindow *win;
+};
+
+static void
+trg_trackers_tree_view_class_init(TrgTrackersTreeViewClass * klass)
+{
+ g_type_class_add_private(klass, sizeof(TrgTrackersTreeViewPrivate));
+}
+
+static gboolean is_tracker_edit_supported(TrgClient * tc)
+{
+ return trg_client_get_rpc_version(tc) >= 10;
+}
+
+static gboolean on_trackers_update(gpointer data)
+{
+ trg_response *response = (trg_response *) data;
+ TrgTrackersTreeViewPrivate *priv =
+ TRG_TRACKERS_TREE_VIEW_GET_PRIVATE(response->cb_data);
+ GtkTreeModel *model =
+ gtk_tree_view_get_model(GTK_TREE_VIEW(response->cb_data));
+
+ trg_trackers_model_set_accept(TRG_TRACKERS_MODEL(model), TRUE);
+
+ response->cb_data = priv->win;
+ return on_generic_interactive_action(data);
+}
+
+void
+trg_trackers_tree_view_new_connection(TrgTrackersTreeView * tv,
+ TrgClient * tc)
+{
+ TrgTrackersTreeViewPrivate *priv =
+ TRG_TRACKERS_TREE_VIEW_GET_PRIVATE(tv);
+
+ gboolean editable = is_tracker_edit_supported(tc);
+
+ g_object_set(priv->announceRenderer,
+ "editable", editable,
+ "mode", editable ? GTK_CELL_RENDERER_MODE_EDITABLE :
+ GTK_CELL_RENDERER_MODE_INERT, NULL);
+}
+
+static void
+trg_tracker_announce_edited(GtkCellRendererText * renderer,
+ gchar * path,
+ gchar * new_text, gpointer user_data)
+{
+ TrgTrackersTreeViewPrivate *priv =
+ TRG_TRACKERS_TREE_VIEW_GET_PRIVATE(user_data);
+ GtkTreeModel *model =
+ gtk_tree_view_get_model(GTK_TREE_VIEW(user_data));
+ gint64 torrentId =
+ trg_trackers_model_get_torrent_id(TRG_TRACKERS_MODEL(model));
+ JsonArray *torrentIds = json_array_new();
+ JsonArray *trackerModifiers = json_array_new();
+
+ gint64 trackerId;
+ JsonNode *req;
+ JsonObject *args;
+ GtkTreeIter iter;
+ gchar *icon;
+
+ gtk_tree_model_get_iter_from_string(model, &iter, path);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, TRACKERCOL_ANNOUNCE,
+ new_text, -1);
+ gtk_tree_model_get(model, &iter, TRACKERCOL_ID, &trackerId,
+ TRACKERCOL_ICON, &icon, -1);
+
+ json_array_add_int_element(torrentIds, torrentId);
+
+ req = torrent_set(torrentIds);
+ args = node_get_arguments(req);
+
+ if (!g_strcmp0(icon, GTK_STOCK_ADD)) {
+ json_array_add_string_element(trackerModifiers, new_text);
+ json_object_set_array_member(args, "trackerAdd", trackerModifiers);
+ } else {
+ json_array_add_int_element(trackerModifiers, trackerId);
+ json_array_add_string_element(trackerModifiers, new_text);
+ json_object_set_array_member(args, "trackerReplace",
+ trackerModifiers);
+ }
+
+ g_free(icon);
+
+ dispatch_async(priv->client, req, on_trackers_update, user_data);
+}
+
+static void
+trg_tracker_announce_editing_started(GtkCellRenderer *
+ renderer G_GNUC_UNUSED,
+ GtkCellEditable *
+ editable G_GNUC_UNUSED,
+ gchar *
+ path G_GNUC_UNUSED,
+ gpointer user_data)
+{
+ TrgTrackersModel *model =
+ TRG_TRACKERS_MODEL(gtk_tree_view_get_model
+ (GTK_TREE_VIEW(user_data)));
+
+ trg_trackers_model_set_accept(model, FALSE);
+}
+
+static void
+trg_tracker_announce_editing_canceled(GtkWidget *
+ w G_GNUC_UNUSED, gpointer data)
+{
+ TrgTrackersModel *model =
+ TRG_TRACKERS_MODEL(gtk_tree_view_get_model(GTK_TREE_VIEW(data)));
+
+ trg_trackers_model_set_accept(model, TRUE);
+}
+
+static void trg_trackers_tree_view_init(TrgTrackersTreeView * self)
+{
+ TrgTrackersTreeViewPrivate *priv =
+ TRG_TRACKERS_TREE_VIEW_GET_PRIVATE(self);
+ TrgTreeView *ttv = TRG_TREE_VIEW(self);
+ trg_column_description *desc;
+
+ desc =
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_STOCKICONTEXT,
+ TRACKERCOL_TIER, _("Tier"), "tier",
+ TRG_COLUMN_UNREMOVABLE);
+ desc->model_column_extra = TRACKERCOL_ICON;
+
+ desc =
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_TEXT,
+ TRACKERCOL_ANNOUNCE, _("Announce URL"),
+ "announce-url", TRG_COLUMN_UNREMOVABLE);
+ priv->announceRenderer = desc->customRenderer =
+ gtk_cell_renderer_text_new();
+ g_signal_connect(priv->announceRenderer, "edited",
+ G_CALLBACK(trg_tracker_announce_edited), self);
+ g_signal_connect(priv->announceRenderer, "editing-canceled",
+ G_CALLBACK(trg_tracker_announce_editing_canceled),
+ self);
+ g_signal_connect(priv->announceRenderer, "editing-started",
+ G_CALLBACK(trg_tracker_announce_editing_started),
+ self);
+ desc->out = &priv->announceColumn;
+
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TRACKERCOL_LAST_ANNOUNCE_PEER_COUNT,
+ _("Peers"), "last-announce-peer-count", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TRACKERCOL_SEEDERCOUNT, _("Seeder Count"),
+ "seeder-count", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_NUMGTZERO,
+ TRACKERCOL_LEECHERCOUNT, _("Leecher Count"),
+ "leecher-count", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_EPOCH,
+ TRACKERCOL_LAST_ANNOUNCE_TIME,
+ _("Last Announce"), "last-announce-time", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_TEXT,
+ TRACKERCOL_LAST_ANNOUNCE_RESULT,
+ _("Last Result"), "last-result", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_TEXT, TRACKERCOL_SCRAPE,
+ _("Scrape URL"), "scrape-url", 0);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_EPOCH,
+ TRACKERCOL_LAST_SCRAPE_TIME, _("Last Scrape"),
+ "last-scrape-time", TRG_COLUMN_EXTRA);
+ trg_tree_view_reg_column(ttv, TRG_COLTYPE_TEXT, TRACKERCOL_HOST,
+ _("Host"), "host", TRG_COLUMN_EXTRA);
+}
+
+static void add_tracker(GtkWidget * w, gpointer data)
+{
+ GtkTreeView *tv = GTK_TREE_VIEW(data);
+ TrgTrackersTreeViewPrivate *priv =
+ TRG_TRACKERS_TREE_VIEW_GET_PRIVATE(data);
+ GtkTreeModel *model = gtk_tree_view_get_model(tv);
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, TRACKERCOL_ICON,
+ GTK_STOCK_ADD, -1);
+
+ path = gtk_tree_model_get_path(model, &iter);
+ gtk_tree_view_set_cursor(tv, path, priv->announceColumn, TRUE);
+ gtk_tree_path_free(path);
+}
+
+static void delete_tracker(GtkWidget * w, gpointer data)
+{
+ TrgTrackersTreeViewPrivate *priv =
+ TRG_TRACKERS_TREE_VIEW_GET_PRIVATE(data);
+ GtkTreeView *tv = GTK_TREE_VIEW(data);
+ GList *selectionRefs = trg_tree_view_get_selected_refs_list(tv);
+ GtkTreeModel *model = gtk_tree_view_get_model(tv);
+ JsonArray *trackerIds = json_array_new();
+ gint64 torrentId =
+ trg_trackers_model_get_torrent_id(TRG_TRACKERS_MODEL(model));
+ JsonArray *torrentIds = json_array_new();
+
+ JsonNode *req;
+ JsonObject *args;
+ GList *li;
+
+ for (li = selectionRefs; li; li = g_list_next(li)) {
+ GtkTreeRowReference *rr = (GtkTreeRowReference *) li->data;
+ GtkTreePath *path = gtk_tree_row_reference_get_path(rr);
+ if (path) {
+ gint64 trackerId;
+ GtkTreeIter trackerIter;
+ gtk_tree_model_get_iter(model, &trackerIter, path);
+ gtk_tree_model_get(model, &trackerIter, TRACKERCOL_ID,
+ &trackerId, -1);
+ json_array_add_int_element(trackerIds, trackerId);
+ gtk_list_store_remove(GTK_LIST_STORE(model), &trackerIter);
+ gtk_tree_path_free(path);
+ }
+ gtk_tree_row_reference_free(rr);
+ }
+ g_list_free(selectionRefs);
+
+ json_array_add_int_element(torrentIds, torrentId);
+
+ req = torrent_set(torrentIds);
+ args = node_get_arguments(req);
+
+ json_object_set_array_member(args, "trackerRemove", trackerIds);
+
+ trg_trackers_model_set_accept(TRG_TRACKERS_MODEL(model), FALSE);
+
+ dispatch_async(priv->client, req, on_trackers_update, data);
+}
+
+static void
+view_popup_menu_add_only(GtkWidget * treeview, GdkEventButton * event,
+ gpointer data G_GNUC_UNUSED)
+{
+ GtkWidget *menu, *menuitem;
+
+ menu = gtk_menu_new();
+
+ menuitem =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(menu), _("Add"),
+ GTK_STOCK_ADD, TRUE);
+ g_signal_connect(menuitem, "activate", G_CALLBACK(add_tracker),
+ treeview);
+
+ gtk_widget_show_all(menu);
+
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
+ (event != NULL) ? event->button : 0,
+ gdk_event_get_time((GdkEvent *) event));
+}
+
+static void
+view_popup_menu(GtkWidget * treeview, GdkEventButton * event,
+ gpointer data G_GNUC_UNUSED)
+{
+ GtkWidget *menu, *menuitem;
+
+ menu = gtk_menu_new();
+
+ menuitem =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(menu), _("Delete"),
+ GTK_STOCK_DELETE, TRUE);
+ g_signal_connect(menuitem, "activate", G_CALLBACK(delete_tracker),
+ treeview);
+
+ menuitem =
+ trg_menu_bar_item_new(GTK_MENU_SHELL(menu), _("Add"),
+ GTK_STOCK_ADD, TRUE);
+ g_signal_connect(menuitem, "activate", G_CALLBACK(add_tracker),
+ treeview);
+
+ gtk_widget_show_all(menu);
+
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
+ (event != NULL) ? event->button : 0,
+ gdk_event_get_time((GdkEvent *) event));
+}
+
+static gboolean
+view_onButtonPressed(GtkWidget * treeview, GdkEventButton * event,
+ gpointer userdata)
+{
+ TrgTrackersTreeViewPrivate *priv =
+ TRG_TRACKERS_TREE_VIEW_GET_PRIVATE(treeview);
+ TrgTrackersModel *model =
+ TRG_TRACKERS_MODEL(gtk_tree_view_get_model
+ (GTK_TREE_VIEW(treeview)));
+ GtkTreeSelection *selection;
+ GtkTreePath *path;
+
+ if (!is_tracker_edit_supported(priv->client))
+ return FALSE;
+
+ if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
+
+ if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview),
+ (gint) event->x,
+ (gint) event->y, &path,
+ NULL, NULL, NULL)) {
+ if (!gtk_tree_selection_path_is_selected(selection, path)) {
+ gtk_tree_selection_unselect_all(selection);
+ gtk_tree_selection_select_path(selection, path);
+ }
+ gtk_tree_path_free(path);
+
+ view_popup_menu(treeview, event, userdata);
+ } else if (trg_trackers_model_get_torrent_id(model) >= 0) {
+ view_popup_menu_add_only(treeview, event, userdata);
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean view_onPopupMenu(GtkWidget * treeview, gpointer userdata)
+{
+ view_popup_menu(treeview, NULL, userdata);
+ return TRUE;
+}
+
+TrgTrackersTreeView *trg_trackers_tree_view_new(TrgTrackersModel * model,
+ TrgClient * client,
+ TrgMainWindow * win,
+ const gchar * configId)
+{
+ GObject *obj = g_object_new(TRG_TYPE_TRACKERS_TREE_VIEW,
+ "config-id", configId,
+ "prefs", trg_client_get_prefs(client),
+ NULL);
+
+ TrgTrackersTreeViewPrivate *priv =
+ TRG_TRACKERS_TREE_VIEW_GET_PRIVATE(obj);
+
+ gtk_tree_view_set_model(GTK_TREE_VIEW(obj), GTK_TREE_MODEL(model));
+ priv->client = client;
+ priv->win = win;
+
+ trg_tree_view_setup_columns(TRG_TREE_VIEW(obj));
+
+ g_signal_connect(obj, "button-press-event",
+ G_CALLBACK(view_onButtonPressed), NULL);
+ g_signal_connect(obj, "popup-menu", G_CALLBACK(view_onPopupMenu),
+ NULL);
+
+ return TRG_TRACKERS_TREE_VIEW(obj);
+}
diff --git a/src/trg-trackers-tree-view.h b/src/trg-trackers-tree-view.h
new file mode 100644
index 0000000..39c5f46
--- /dev/null
+++ b/src/trg-trackers-tree-view.h
@@ -0,0 +1,61 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_TRACKERS_TREE_VIEW_H_
+#define TRG_TRACKERS_TREE_VIEW_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "trg-trackers-model.h"
+#include "trg-tree-view.h"
+#include "trg-client.h"
+#include "trg-main-window.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_TRACKERS_TREE_VIEW trg_trackers_tree_view_get_type()
+#define TRG_TRACKERS_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_TRACKERS_TREE_VIEW, TrgTrackersTreeView))
+#define TRG_TRACKERS_TREE_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_TRACKERS_TREE_VIEW, TrgTrackersTreeViewClass))
+#define TRG_IS_TRACKERS_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_TRACKERS_TREE_VIEW))
+#define TRG_IS_TRACKERS_TREE_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_TRACKERS_TREE_VIEW))
+#define TRG_TRACKERS_TREE_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_TRACKERS_TREE_VIEW, TrgTrackersTreeViewClass))
+ typedef struct {
+ TrgTreeView parent;
+} TrgTrackersTreeView;
+
+typedef struct {
+ TrgTreeViewClass parent_class;
+} TrgTrackersTreeViewClass;
+
+GType trg_trackers_tree_view_get_type(void);
+
+TrgTrackersTreeView *trg_trackers_tree_view_new(TrgTrackersModel * model,
+ TrgClient * client,
+ TrgMainWindow * win,
+ const gchar * configId);
+void trg_trackers_tree_view_new_connection(TrgTrackersTreeView * tv,
+ TrgClient * tc);
+
+G_END_DECLS
+#endif /* TRG_TRACKERS_TREE_VIEW_H_ */
diff --git a/src/trg-tree-view.c b/src/trg-tree-view.c
new file mode 100644
index 0000000..3f7b95e
--- /dev/null
+++ b/src/trg-tree-view.c
@@ -0,0 +1,790 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+#include <glib/gi18n.h>
+
+#include "trg-prefs.h"
+#include "trg-tree-view.h"
+#include "trg-cell-renderer-speed.h"
+#include "trg-cell-renderer-size.h"
+#include "trg-cell-renderer-ratio.h"
+#include "trg-cell-renderer-wanted.h"
+#include "trg-cell-renderer-eta.h"
+#include "trg-cell-renderer-epoch.h"
+#include "trg-cell-renderer-priority.h"
+#include "trg-cell-renderer-numgteqthan.h"
+#include "trg-cell-renderer-file-icon.h"
+
+/* A subclass of GtkTreeView which allows the user to change column visibility
+ * by right clicking on any column for a menu to hide the clicked column, or
+ * insert any hidden column after.
+ *
+ * This class persists these choices to TrgPrefs, and restores them when it is
+ * initialised. Column widths are also saved/restored.
+ *
+ * All the columns must be preregistered so it knows what model column,
+ * renderers etc to use if it should be created, and what columns are available.
+ */
+
+enum {
+ PROP_0, PROP_PREFS, PROP_CONFIGID
+};
+
+G_DEFINE_TYPE(TrgTreeView, trg_tree_view, GTK_TYPE_TREE_VIEW)
+#define TRG_TREE_VIEW_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_TREE_VIEW, TrgTreeViewPrivate))
+typedef struct _TrgTreeViewPrivate TrgTreeViewPrivate;
+
+struct _TrgTreeViewPrivate {
+ GList *columns;
+ TrgPrefs *prefs;
+ gchar *configId;
+};
+
+#define GDATA_KEY_COLUMN_DESC "column-desc"
+
+gboolean trg_tree_view_is_column_showing(TrgTreeView * tv, gint index)
+{
+ TrgTreeViewPrivate *priv = TRG_TREE_VIEW_GET_PRIVATE(tv);
+
+ GList *li;
+ for (li = priv->columns; li; li = g_list_next(li)) {
+ trg_column_description *cd = (trg_column_description *) li->data;
+ if (cd->model_column == index) {
+ if (cd->flags & TRG_COLUMN_SHOWING)
+ return TRUE;
+ else
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+trg_tree_view_get_property(GObject * object, guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+}
+
+static void
+trg_tree_view_set_property(GObject * object, guint property_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ TrgTreeViewPrivate *priv = TRG_TREE_VIEW_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_PREFS:
+ priv->prefs = g_value_get_object(value);
+ break;
+ case PROP_CONFIGID:
+ g_free(priv->configId);
+ priv->configId = g_value_dup_string(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static GObject *trg_tree_view_constructor(GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *
+ construct_params)
+{
+ GObject *obj = G_OBJECT_CLASS
+ (trg_tree_view_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+
+ return obj;
+}
+
+static JsonObject *trg_prefs_get_tree_view_props(TrgTreeView * tv)
+{
+ TrgTreeViewPrivate *priv = TRG_TREE_VIEW_GET_PRIVATE(tv);
+ JsonObject *root = trg_prefs_get_root(priv->prefs);
+ const gchar *className = priv->configId
+ && strlen(priv->configId) >
+ 0 ? priv->configId : G_OBJECT_TYPE_NAME(tv);
+ JsonObject *obj;
+ JsonObject *tvProps = NULL;
+
+ if (!json_object_has_member(root, TRG_PREFS_KEY_TREE_VIEWS)) {
+ obj = json_object_new();
+ json_object_set_object_member(root, TRG_PREFS_KEY_TREE_VIEWS, obj);
+ } else {
+ obj =
+ json_object_get_object_member(root, TRG_PREFS_KEY_TREE_VIEWS);
+ }
+
+ if (!json_object_has_member(obj, className)) {
+ tvProps = json_object_new();
+ json_object_set_object_member(obj, className, tvProps);
+ } else {
+ tvProps = json_object_get_object_member(obj, className);
+ }
+
+ return tvProps;
+}
+
+static void trg_tree_view_add_column_after(TrgTreeView * tv,
+ trg_column_description * desc,
+ gint64 width,
+ GtkTreeViewColumn * after_col);
+
+trg_column_description *trg_tree_view_reg_column(TrgTreeView * tv,
+ gint type,
+ gint model_column,
+ const gchar * header,
+ const gchar * id,
+ guint flags)
+{
+ TrgTreeViewPrivate *priv = TRG_TREE_VIEW_GET_PRIVATE(tv);
+ trg_column_description *desc = g_new0(trg_column_description, 1);
+
+ desc->type = type;
+ desc->model_column = model_column;
+ desc->header = g_strdup(header);
+ desc->id = g_strdup(id);
+ desc->flags = flags;
+
+ priv->columns = g_list_append(priv->columns, desc);
+
+ return desc;
+}
+
+static trg_column_description *trg_tree_view_find_column(TrgTreeView * tv,
+ const gchar * id)
+{
+ TrgTreeViewPrivate *priv = TRG_TREE_VIEW_GET_PRIVATE(tv);
+ GList *li;
+ trg_column_description *desc;
+
+ for (li = priv->columns; li; li = g_list_next(li)) {
+ desc = (trg_column_description *) li->data;
+ if (!g_strcmp0(desc->id, id))
+ return desc;
+ }
+
+ return NULL;
+}
+
+static void
+trg_tree_view_hide_column(GtkWidget * w, GtkTreeViewColumn * col)
+{
+ trg_column_description *desc = g_object_get_data(G_OBJECT(col),
+ GDATA_KEY_COLUMN_DESC);
+ GtkWidget *tv = gtk_tree_view_column_get_tree_view(col);
+ desc->flags &= ~TRG_COLUMN_SHOWING;
+ gtk_tree_view_remove_column(GTK_TREE_VIEW(tv), col);
+}
+
+static void
+trg_tree_view_add_column(TrgTreeView * tv,
+ trg_column_description * desc, gint64 width)
+{
+ trg_tree_view_add_column_after(tv, desc, width, NULL);
+}
+
+static void
+trg_tree_view_user_add_column_cb(GtkWidget * w,
+ trg_column_description * desc)
+{
+ GtkTreeViewColumn *col = g_object_get_data(G_OBJECT(w), "parent-col");
+ TrgTreeView *tv =
+ TRG_TREE_VIEW(gtk_tree_view_column_get_tree_view(col));
+
+ trg_tree_view_add_column_after(tv, desc, -1, col);
+}
+
+static void trg_tree_view_sort_menu_item_toggled(GtkCheckMenuItem * w,
+ gpointer data)
+{
+ GtkTreeSortable *model = GTK_TREE_SORTABLE(data);
+ trg_column_description *desc =
+ (trg_column_description *) g_object_get_data(G_OBJECT(w),
+ GDATA_KEY_COLUMN_DESC);
+
+ if (gtk_check_menu_item_get_active(w)) {
+ GtkSortType sortType;
+ gtk_tree_sortable_get_sort_column_id(model, NULL, &sortType);
+ gtk_tree_sortable_set_sort_column_id(model, desc->model_column,
+ sortType);
+ }
+}
+
+static void trg_tree_view_sort_menu_type_toggled(GtkCheckMenuItem * w,
+ gpointer data)
+{
+ GtkTreeSortable *model = GTK_TREE_SORTABLE(data);
+
+ if (gtk_check_menu_item_get_active(w)) {
+ gint sortColumn;
+ gint sortType =
+ GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), "sort-type"));
+ gtk_tree_sortable_get_sort_column_id(model, &sortColumn, NULL);
+ gtk_tree_sortable_set_sort_column_id(model, sortColumn, sortType);
+ }
+}
+
+
+GtkWidget *trg_tree_view_sort_menu(TrgTreeView * tv, const gchar * label)
+{
+ TrgTreeViewPrivate *priv = TRG_TREE_VIEW_GET_PRIVATE(tv);
+ GtkWidget *item = gtk_menu_item_new_with_mnemonic(label);
+ GtkTreeModel *treeViewModel =
+ gtk_tree_view_get_model(GTK_TREE_VIEW(tv));
+ GtkTreeSortable *sortableModel =
+ GTK_TREE_SORTABLE(gtk_tree_model_filter_get_model
+ (GTK_TREE_MODEL_FILTER(treeViewModel)));
+ GtkWidget *menu = gtk_menu_new();
+ GtkWidget *b;
+ GList *li;
+ gint sort;
+ GtkSortType sortType;
+ GSList *group = NULL;
+
+ gtk_tree_sortable_get_sort_column_id(sortableModel, &sort, &sortType);
+
+ b = gtk_radio_menu_item_new_with_label(group, _("Ascending"));
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(b),
+ sortType == GTK_SORT_ASCENDING);
+ g_object_set_data(G_OBJECT(b), "sort-type",
+ GINT_TO_POINTER(GTK_SORT_ASCENDING));
+ g_signal_connect(b, "toggled",
+ G_CALLBACK(trg_tree_view_sort_menu_type_toggled),
+ sortableModel);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), b);
+ group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(b));
+ b = gtk_radio_menu_item_new_with_label(group, _("Descending"));
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(b),
+ sortType == GTK_SORT_DESCENDING);
+ g_object_set_data(G_OBJECT(b), "sort-type",
+ GINT_TO_POINTER(GTK_SORT_DESCENDING));
+ g_signal_connect(b, "toggled",
+ G_CALLBACK(trg_tree_view_sort_menu_type_toggled),
+ sortableModel);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), b);
+
+ group = NULL;
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu),
+ gtk_separator_menu_item_new());
+
+ for (li = priv->columns; li; li = g_list_next(li)) {
+ trg_column_description *desc = (trg_column_description *) li->data;
+ if (!(desc->flags & TRG_COLUMN_HIDE_FROM_TOP_MENU)) {
+ b = gtk_radio_menu_item_new_with_label(group, desc->header);
+ group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(b));
+
+ if (desc->model_column == sort)
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(b),
+ TRUE);
+
+ g_object_set_data(G_OBJECT(b), GDATA_KEY_COLUMN_DESC, desc);
+ g_signal_connect(b, "toggled",
+ G_CALLBACK
+ (trg_tree_view_sort_menu_item_toggled),
+ sortableModel);
+
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), b);
+ }
+ }
+
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu);
+
+ return item;
+}
+
+static void
+view_popup_menu(GtkButton * button, GdkEventButton * event,
+ GtkTreeViewColumn * column)
+{
+ GtkWidget *tv = gtk_tree_view_column_get_tree_view(column);
+ TrgTreeViewPrivate *priv = TRG_TREE_VIEW_GET_PRIVATE(tv);
+ GtkWidget *menu, *menuitem;
+ trg_column_description *desc;
+ GList *li;
+
+ menu = gtk_menu_new();
+
+ desc = g_object_get_data(G_OBJECT(column), GDATA_KEY_COLUMN_DESC);
+ menuitem = gtk_check_menu_item_new_with_label(desc->header);
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), TRUE);
+ g_signal_connect(menuitem, "activate",
+ G_CALLBACK(trg_tree_view_hide_column), column);
+ gtk_widget_set_sensitive(menuitem,
+ !(desc->flags & TRG_COLUMN_UNREMOVABLE));
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+ for (li = priv->columns; li; li = g_list_next(li)) {
+ trg_column_description *desc = (trg_column_description *) li->data;
+ if (!(desc->flags & TRG_COLUMN_SHOWING)) {
+ menuitem = gtk_check_menu_item_new_with_label(desc->header);
+ g_object_set_data(G_OBJECT(menuitem), "parent-col", column);
+ g_signal_connect(menuitem, "activate",
+ G_CALLBACK(trg_tree_view_user_add_column_cb),
+ desc);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+ }
+ }
+ gtk_widget_show_all(menu);
+
+ gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
+ (event != NULL) ? event->button : 0,
+ gdk_event_get_time((GdkEvent *) event));
+}
+
+static gboolean
+col_onButtonPressed(GtkButton * button,
+ GdkEventButton * event, GtkTreeViewColumn * col)
+{
+ if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
+ view_popup_menu(button, event, col);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GtkTreeViewColumn
+ * trg_tree_view_icontext_column_new(trg_column_description * desc,
+ gchar * renderer_property)
+{
+ GtkTreeViewColumn *column = gtk_tree_view_column_new();
+ GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
+
+ gtk_tree_view_column_set_title(column, desc->header);
+ gtk_tree_view_column_pack_start(column, renderer, FALSE);
+ gtk_tree_view_column_set_attributes(column, renderer,
+ renderer_property,
+ desc->model_column_extra, NULL);
+
+ renderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(column, renderer, TRUE);
+ gtk_tree_view_column_set_attributes(column, renderer, "text",
+ desc->model_column, NULL);
+
+ return column;
+}
+
+static GtkTreeViewColumn
+ * trg_tree_view_fileicontext_column_new(trg_column_description * desc)
+{
+ GtkTreeViewColumn *column = gtk_tree_view_column_new();
+ GtkCellRenderer *renderer = trg_cell_renderer_file_icon_new();
+
+ gtk_tree_view_column_set_title(column, desc->header);
+ gtk_tree_view_column_pack_start(column, renderer, FALSE);
+ gtk_tree_view_column_set_attributes(column, renderer,
+ "file-id",
+ desc->model_column_extra,
+ "file-name", desc->model_column,
+ NULL);
+
+ renderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(column, renderer, TRUE);
+ gtk_tree_view_column_set_attributes(column, renderer, "text",
+ desc->model_column, NULL);
+
+ return column;
+}
+
+static void
+trg_tree_view_add_column_after(TrgTreeView * tv,
+ trg_column_description * desc,
+ gint64 width, GtkTreeViewColumn * after_col)
+{
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column = NULL;
+
+ switch (desc->type) {
+ case TRG_COLTYPE_TEXT:
+ renderer =
+ desc->customRenderer ? desc->customRenderer :
+ gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(desc->header,
+ renderer, "text",
+ desc->
+ model_column,
+ NULL);
+
+ break;
+ case TRG_COLTYPE_SPEED:
+ renderer = trg_cell_renderer_speed_new();
+ column = gtk_tree_view_column_new_with_attributes(desc->header,
+ renderer,
+ "speed-value",
+ desc->
+ model_column,
+ NULL);
+ break;
+ case TRG_COLTYPE_EPOCH:
+ renderer = trg_cell_renderer_epoch_new();
+ column = gtk_tree_view_column_new_with_attributes(desc->header,
+ renderer,
+ "epoch-value",
+ desc->
+ model_column,
+ NULL);
+ break;
+ case TRG_COLTYPE_ETA:
+ renderer = trg_cell_renderer_eta_new();
+ column = gtk_tree_view_column_new_with_attributes(desc->header,
+ renderer,
+ "eta-value",
+ desc->
+ model_column,
+ NULL);
+ break;
+ case TRG_COLTYPE_SIZE:
+ renderer = trg_cell_renderer_size_new();
+ column = gtk_tree_view_column_new_with_attributes(desc->header,
+ renderer,
+ "size-value",
+ desc->
+ model_column,
+ NULL);
+ break;
+ case TRG_COLTYPE_PROG:
+ renderer = gtk_cell_renderer_progress_new();
+ column = gtk_tree_view_column_new_with_attributes(desc->header,
+ renderer,
+ "value",
+ desc->
+ model_column,
+ NULL);
+ break;
+ case TRG_COLTYPE_RATIO:
+ renderer = trg_cell_renderer_ratio_new();
+ column = gtk_tree_view_column_new_with_attributes(desc->header,
+ renderer,
+ "ratio-value",
+ desc->
+ model_column,
+ NULL);
+ break;
+ case TRG_COLTYPE_WANTED:
+ column = gtk_tree_view_column_new();
+ renderer = trg_cell_renderer_wanted_new();
+ /*gtk_cell_renderer_set_alignment(GTK_CELL_RENDERER(renderer), 0.5f,
+ 0.0); */
+ gtk_tree_view_column_set_title(column, desc->header);
+ gtk_tree_view_column_pack_start(column, renderer, TRUE);
+ gtk_tree_view_column_set_attributes(column, renderer,
+ "wanted-value",
+ desc->model_column, NULL);
+ break;
+ case TRG_COLTYPE_STOCKICONTEXT:
+ column = trg_tree_view_icontext_column_new(desc, "stock-id");
+ break;
+ case TRG_COLTYPE_FILEICONTEXT:
+ column = trg_tree_view_fileicontext_column_new(desc);
+ break;
+ case TRG_COLTYPE_PRIO:
+ renderer = trg_cell_renderer_priority_new();
+ column = gtk_tree_view_column_new_with_attributes(desc->header,
+ renderer,
+ "priority-value",
+ desc->
+ model_column,
+ NULL);
+ break;
+ case TRG_COLTYPE_NUMGTZERO:
+ renderer = trg_cell_renderer_numgteqthan_new(1);
+ column = gtk_tree_view_column_new_with_attributes(desc->header,
+ renderer,
+ "value",
+ desc->
+ model_column,
+ NULL);
+ break;
+ case TRG_COLTYPE_NUMGTEQZERO:
+ renderer = trg_cell_renderer_numgteqthan_new(0);
+ column = gtk_tree_view_column_new_with_attributes(desc->header,
+ renderer,
+ "value",
+ desc->
+ model_column,
+ NULL);
+ break;
+ default:
+ g_critical("unknown TrgTreeView column");
+ return;
+ }
+
+ gtk_tree_view_column_set_min_width(column, 0);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+ gtk_tree_view_column_set_reorderable(column, TRUE);
+ gtk_tree_view_column_set_sort_column_id(column, desc->model_column);
+
+ if (width > 0) {
+ gtk_tree_view_column_set_sizing(column,
+ GTK_TREE_VIEW_COLUMN_FIXED);
+ gtk_tree_view_column_set_fixed_width(column, width);
+ }
+
+ g_object_set_data(G_OBJECT(column), GDATA_KEY_COLUMN_DESC, desc);
+
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tv), column);
+
+ if (after_col)
+ gtk_tree_view_move_column_after(GTK_TREE_VIEW(tv), column,
+ after_col);
+
+#if GTK_CHECK_VERSION( 3,0,0 )
+ g_signal_connect(gtk_tree_view_column_get_button(column),
+ "button-press-event", G_CALLBACK(col_onButtonPressed),
+ column);
+#else
+ g_signal_connect(column->button, "button-press-event",
+ G_CALLBACK(col_onButtonPressed), column);
+#endif
+
+ if (desc->out)
+ *(desc->out) = column;
+
+ desc->flags |= TRG_COLUMN_SHOWING;
+}
+
+void trg_tree_view_remove_all_columns(TrgTreeView * tv)
+{
+ GtkTreeView *gtv = GTK_TREE_VIEW(tv);
+ GList *cols = gtk_tree_view_get_columns(gtv);
+ GList *li;
+ for (li = cols; li; li = g_list_next(li)) {
+ gtk_tree_view_remove_column(gtv, GTK_TREE_VIEW_COLUMN(li->data));
+ }
+ g_list_free(cols);
+}
+
+void trg_tree_view_persist(TrgTreeView * tv, guint flags)
+{
+ JsonObject *props = trg_prefs_get_tree_view_props(tv);
+ GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(tv));
+ GList *cols, *li;
+ JsonArray *widths, *columns;
+ gint sort_column_id;
+ GtkSortType sort_type;
+
+ if (flags & TRG_TREE_VIEW_PERSIST_SORT) {
+ gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE
+ ((flags &
+ TRG_TREE_VIEW_SORTABLE_PARENT)
+ ?
+ gtk_tree_model_filter_get_model
+ (GTK_TREE_MODEL_FILTER
+ (model)) : model),
+ &sort_column_id, &sort_type);
+
+ if (json_object_has_member(props, TRG_PREFS_KEY_TV_SORT_COL))
+ json_object_remove_member(props, TRG_PREFS_KEY_TV_SORT_COL);
+
+ if (json_object_has_member(props, TRG_PREFS_KEY_TV_SORT_TYPE))
+ json_object_remove_member(props, TRG_PREFS_KEY_TV_SORT_TYPE);
+
+ json_object_set_int_member(props, TRG_PREFS_KEY_TV_SORT_COL,
+ (gint64) sort_column_id);
+ json_object_set_int_member(props, TRG_PREFS_KEY_TV_SORT_TYPE,
+ (gint64) sort_type);
+ }
+
+ if (flags & TRG_TREE_VIEW_PERSIST_LAYOUT) {
+ cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(tv));
+
+ if (json_object_has_member(props, TRG_PREFS_KEY_TV_WIDTHS))
+ json_object_remove_member(props, TRG_PREFS_KEY_TV_WIDTHS);
+
+ widths = json_array_new();
+ json_object_set_array_member(props, TRG_PREFS_KEY_TV_WIDTHS,
+ widths);
+
+ if (json_object_has_member(props, TRG_PREFS_KEY_TV_COLUMNS))
+ json_object_remove_member(props, TRG_PREFS_KEY_TV_COLUMNS);
+
+ columns = json_array_new();
+ json_object_set_array_member(props, TRG_PREFS_KEY_TV_COLUMNS,
+ columns);
+
+ for (li = cols; li; li = g_list_next(li)) {
+ GtkTreeViewColumn *col = (GtkTreeViewColumn *) li->data;
+ trg_column_description *desc =
+ g_object_get_data(G_OBJECT(li->data),
+ GDATA_KEY_COLUMN_DESC);
+
+ json_array_add_string_element(columns, desc->id);
+ json_array_add_int_element(widths,
+ gtk_tree_view_column_get_width
+ (col));
+ }
+
+ g_list_free(cols);
+ }
+}
+
+void trg_tree_view_restore_sort(TrgTreeView * tv, guint flags)
+{
+ JsonObject *props = trg_prefs_get_tree_view_props(tv);
+ GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(tv));
+
+ if (json_object_has_member(props, TRG_PREFS_KEY_TV_SORT_COL)
+ && json_object_has_member(props, TRG_PREFS_KEY_TV_SORT_TYPE)) {
+ gint64 sort_col = json_object_get_int_member(props,
+ TRG_PREFS_KEY_TV_SORT_COL);
+ gint64 sort_type = json_object_get_int_member(props,
+ TRG_PREFS_KEY_TV_SORT_TYPE);
+ gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE
+ ((flags &
+ TRG_TREE_VIEW_SORTABLE_PARENT)
+ ?
+ gtk_tree_model_filter_get_model
+ (GTK_TREE_MODEL_FILTER
+ (model)) : model), sort_col,
+ (GtkSortType) sort_type);
+
+ }
+}
+
+void trg_tree_view_set_prefs(TrgTreeView * tv, TrgPrefs * prefs)
+{
+ TrgTreeViewPrivate *priv = TRG_TREE_VIEW_GET_PRIVATE(tv);
+ priv->prefs = prefs;
+}
+
+void trg_tree_view_setup_columns(TrgTreeView * tv)
+{
+ TrgTreeViewPrivate *priv = TRG_TREE_VIEW_GET_PRIVATE(tv);
+ JsonObject *props = trg_prefs_get_tree_view_props(tv);
+ GList *columns, *widths, *cli, *wli;
+
+ if (!json_object_has_member(props, TRG_PREFS_KEY_TV_COLUMNS)
+ || !json_object_has_member(props, TRG_PREFS_KEY_TV_WIDTHS)) {
+ GList *li;
+ for (li = priv->columns; li; li = g_list_next(li)) {
+ trg_column_description *desc =
+ (trg_column_description *) li->data;
+ if (desc && !(desc->flags & TRG_COLUMN_EXTRA))
+ trg_tree_view_add_column(tv, desc, -1);
+ }
+ return;
+ }
+
+ columns =
+ json_array_get_elements(json_object_get_array_member
+ (props, TRG_PREFS_KEY_TV_COLUMNS));
+ widths =
+ json_array_get_elements(json_object_get_array_member
+ (props, TRG_PREFS_KEY_TV_WIDTHS));
+
+ for (cli = columns, wli = widths; cli && wli;
+ cli = g_list_next(cli), wli = g_list_next(wli)) {
+ trg_column_description *desc = trg_tree_view_find_column(tv,
+ json_node_get_string
+ ((JsonNode
+ *)
+ cli->
+ data));
+ if (desc) {
+ gint64 width = json_node_get_int((JsonNode *) wli->data);
+ trg_tree_view_add_column(tv, desc, width);
+ }
+ }
+
+ g_list_free(columns);
+ g_list_free(widths);
+}
+
+GList *trg_tree_view_get_selected_refs_list(GtkTreeView * tv)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model(tv);
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(tv);
+ GList *li, *selectionList;
+ GList *refList = NULL;
+
+ selectionList = gtk_tree_selection_get_selected_rows(selection, NULL);
+ for (li = selectionList; li != NULL; li = g_list_next(li)) {
+ GtkTreePath *path = (GtkTreePath *) li->data;
+ GtkTreeRowReference *ref = gtk_tree_row_reference_new(model, path);
+ gtk_tree_path_free(path);
+ refList = g_list_append(refList, ref);
+ }
+ g_list_free(selectionList);
+
+ return refList;
+}
+
+static void trg_tree_view_class_init(TrgTreeViewClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgTreeViewPrivate));
+
+ object_class->get_property = trg_tree_view_get_property;
+ object_class->set_property = trg_tree_view_set_property;
+ object_class->constructor = trg_tree_view_constructor;
+
+ g_object_class_install_property(object_class,
+ PROP_PREFS,
+ g_param_spec_object("prefs",
+ "Trg Prefs",
+ "Trg Prefs",
+ TRG_TYPE_PREFS,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_CONFIGID,
+ g_param_spec_string
+ ("config-id",
+ "config-id",
+ "config-id",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+}
+
+static void trg_tree_view_init(TrgTreeView * tv)
+{
+ gtk_tree_view_set_rubber_banding(GTK_TREE_VIEW(tv), TRUE);
+ gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(tv), TRUE);
+ gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tv), TRUE);
+ gtk_tree_selection_set_mode(gtk_tree_view_get_selection
+ (GTK_TREE_VIEW(tv)),
+ GTK_SELECTION_MULTIPLE);
+
+ gtk_widget_set_sensitive(GTK_WIDGET(tv), FALSE);
+}
+
+GtkWidget *trg_tree_view_new(void)
+{
+ return GTK_WIDGET(g_object_new(TRG_TYPE_TREE_VIEW, NULL));
+}
diff --git a/src/trg-tree-view.h b/src/trg-tree-view.h
new file mode 100644
index 0000000..5a17073
--- /dev/null
+++ b/src/trg-tree-view.h
@@ -0,0 +1,104 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _TRG_TREE_VIEW_H_
+#define _TRG_TREE_VIEW_H_
+
+#include <glib-object.h>
+
+#include "trg-prefs.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_TREE_VIEW trg_tree_view_get_type()
+#define TRG_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_TREE_VIEW, TrgTreeView))
+#define TRG_TREE_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_TREE_VIEW, TrgTreeViewClass))
+#define TRG_IS_TREE_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_TREE_VIEW))
+#define TRG_IS_TREE_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_TREE_VIEW))
+#define TRG_TREE_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_TREE_VIEW, TrgTreeViewClass))
+ typedef struct {
+ GtkTreeView parent;
+} TrgTreeView;
+
+typedef struct {
+ GtkTreeViewClass parent_class;
+} TrgTreeViewClass;
+
+GType trg_tree_view_get_type(void);
+
+GtkWidget *trg_tree_view_new(void);
+
+G_END_DECLS GList *trg_tree_view_get_selected_refs_list(GtkTreeView * tv);
+
+enum {
+ TRG_COLTYPE_STOCKICONTEXT,
+ TRG_COLTYPE_FILEICONTEXT,
+ TRG_COLTYPE_WANTED,
+ TRG_COLTYPE_TEXT,
+ TRG_COLTYPE_SIZE,
+ TRG_COLTYPE_RATIO,
+ TRG_COLTYPE_EPOCH,
+ TRG_COLTYPE_SPEED,
+ TRG_COLTYPE_ETA,
+ TRG_COLTYPE_PROG,
+ TRG_COLTYPE_PRIO,
+ TRG_COLTYPE_NUMGTZERO,
+ TRG_COLTYPE_NUMGTEQZERO
+} TrgColumnType;
+
+typedef struct {
+ gint model_column;
+ gint model_column_extra;
+ gchar *header;
+ gchar *id;
+ guint flags;
+ guint type;
+ GtkCellRenderer *customRenderer;
+ GtkTreeViewColumn **out;
+} trg_column_description;
+
+#define TRG_COLUMN_DEFAULT 0x00
+#define TRG_COLUMN_SHOWING (1 << 0) /* 0x01 */
+#define TRG_COLUMN_UNREMOVABLE (1 << 1) /* 0x02 */
+#define TRG_COLUMN_EXTRA (1 << 2) /* 0x04 */
+#define TRG_COLUMN_HIDE_FROM_TOP_MENU (1 << 3) /* 0x08 */
+
+#define TRG_TREE_VIEW_PERSIST_SORT (1 << 0)
+#define TRG_TREE_VIEW_PERSIST_LAYOUT (1 << 1)
+#define TRG_TREE_VIEW_SORTABLE_PARENT (1 << 2)
+
+trg_column_description *trg_tree_view_reg_column(TrgTreeView * tv,
+ gint type,
+ gint model_column,
+ const gchar * header,
+ const gchar * id,
+ guint flags);
+void trg_tree_view_setup_columns(TrgTreeView * tv);
+void trg_tree_view_set_prefs(TrgTreeView * tv, TrgPrefs * prefs);
+void trg_tree_view_persist(TrgTreeView * tv, guint flags);
+void trg_tree_view_remove_all_columns(TrgTreeView * tv);
+void trg_tree_view_restore_sort(TrgTreeView * tv, guint flags);
+GtkWidget *trg_tree_view_sort_menu(TrgTreeView * tv, const gchar * label);
+gboolean trg_tree_view_is_column_showing(TrgTreeView * tv, gint index);
+
+#endif /* _TRG_TREE_VIEW_H_ */
diff --git a/src/trg-valgrind.sh b/src/trg-valgrind.sh
new file mode 100644
index 0000000..aeff135
--- /dev/null
+++ b/src/trg-valgrind.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+G_SLICE=always-malloc G_DEBUG=gc-friendly,resident-modules valgrind --suppressions=gtk.suppression --tool=memcheck --leak-check=full --leak-resolution=high --num-callers=20 --log-file=valgrind.log ./transmission-remote-gtk
diff --git a/src/util.c b/src/util.c
new file mode 100644
index 0000000..312c564
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,654 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* Many of these functions are taken from the Transmission Project. */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <limits.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <curl/curl.h>
+#include <json-glib/json-glib.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <gtk/gtk.h>
+
+#include "util.h"
+
+/***
+**** The code for formatting size and speeds, taken from Transmission.
+***/
+
+const int disk_K = 1024;
+const char *disk_K_str = N_("KiB");
+const char *disk_M_str = N_("MiB");
+const char *disk_G_str = N_("GiB");
+const char *disk_T_str = N_("TiB");
+
+const int speed_K = 1024;
+const char *speed_K_str = N_("KiB/s");
+const char *speed_M_str = N_("MiB/s");
+const char *speed_G_str = N_("GiB/s");
+const char *speed_T_str = N_("TiB/s");
+
+struct formatter_unit {
+ char *name;
+ gint64 value;
+};
+
+struct formatter_units {
+ struct formatter_unit units[4];
+};
+
+enum { TR_FMT_KB, TR_FMT_MB, TR_FMT_GB, TR_FMT_TB };
+
+static void
+formatter_init(struct formatter_units *units,
+ unsigned int kilo,
+ const char *kb, const char *mb,
+ const char *gb, const char *tb)
+{
+ guint64 value = kilo;
+ units->units[TR_FMT_KB].name = g_strdup(kb);
+ units->units[TR_FMT_KB].value = value;
+
+ value *= kilo;
+ units->units[TR_FMT_MB].name = g_strdup(mb);
+ units->units[TR_FMT_MB].value = value;
+
+ value *= kilo;
+ units->units[TR_FMT_GB].name = g_strdup(gb);
+ units->units[TR_FMT_GB].value = value;
+
+ value *= kilo;
+ units->units[TR_FMT_TB].name = g_strdup(tb);
+ units->units[TR_FMT_TB].value = value;
+}
+
+static char *formatter_get_size_str(const struct formatter_units *u,
+ char *buf, gint64 bytes, size_t buflen)
+{
+ int precision;
+ double value;
+ const char *units;
+ const struct formatter_unit *unit;
+
+ if (bytes < u->units[1].value)
+ unit = &u->units[0];
+ else if (bytes < u->units[2].value)
+ unit = &u->units[1];
+ else if (bytes < u->units[3].value)
+ unit = &u->units[2];
+ else
+ unit = &u->units[3];
+
+ value = (double) bytes / unit->value;
+ units = unit->name;
+ if (unit->value == 1)
+ precision = 0;
+ else if (value < 100)
+ precision = 2;
+ else
+ precision = 1;
+ tr_snprintf(buf, buflen, "%.*f %s", precision, value, units);
+ return buf;
+}
+
+static struct formatter_units size_units;
+
+void
+tr_formatter_size_init(unsigned int kilo,
+ const char *kb, const char *mb,
+ const char *gb, const char *tb)
+{
+ formatter_init(&size_units, kilo, kb, mb, gb, tb);
+}
+
+char *tr_formatter_size_B(char *buf, gint64 bytes, size_t buflen)
+{
+ return formatter_get_size_str(&size_units, buf, bytes, buflen);
+}
+
+static struct formatter_units speed_units;
+
+unsigned int tr_speed_K = 0u;
+
+void
+tr_formatter_speed_init(unsigned int kilo,
+ const char *kb, const char *mb,
+ const char *gb, const char *tb)
+{
+ tr_speed_K = kilo;
+ formatter_init(&speed_units, kilo, kb, mb, gb, tb);
+}
+
+char *tr_formatter_speed_KBps(char *buf, double KBps, size_t buflen)
+{
+ const double K = speed_units.units[TR_FMT_KB].value;
+ double speed = KBps;
+
+ if (speed <= 999.95) /* 0.0 KB to 999.9 KB */
+ tr_snprintf(buf, buflen, "%d %s", (int) speed,
+ speed_units.units[TR_FMT_KB].name);
+ else {
+ speed /= K;
+ if (speed <= 99.995) /* 0.98 MB to 99.99 MB */
+ tr_snprintf(buf, buflen, "%.2f %s", speed,
+ speed_units.units[TR_FMT_MB].name);
+ else if (speed <= 999.95) /* 100.0 MB to 999.9 MB */
+ tr_snprintf(buf, buflen, "%.1f %s", speed,
+ speed_units.units[TR_FMT_MB].name);
+ else {
+ speed /= K;
+ tr_snprintf(buf, buflen, "%.1f %s", speed,
+ speed_units.units[TR_FMT_GB].name);
+ }
+ }
+
+ return buf;
+}
+
+/* URL checkers. */
+
+gboolean is_magnet(const gchar * string)
+{
+ return g_str_has_prefix(string, "magnet:");
+}
+
+gboolean is_url(const gchar * string)
+{
+ /* return g_regex_match_simple ("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?", string, 0, 0); */
+ return g_regex_match_simple("^http[s]?://", string, 0, 0);
+}
+
+/*
+ * Glib-ish Utility functions.
+ */
+
+gchar *trg_base64encode(const gchar * filename)
+{
+ GError *error = NULL;
+ GMappedFile *mf = g_mapped_file_new(filename, FALSE, &error);
+ gchar *b64out = NULL;
+
+ if (error) {
+ g_error("%s", error->message);
+ g_error_free(error);
+ } else {
+ b64out =
+ g_base64_encode((guchar *) g_mapped_file_get_contents(mf),
+ g_mapped_file_get_length(mf));
+ }
+
+ g_mapped_file_unref(mf);
+
+ return b64out;
+}
+
+gchar *trg_gregex_get_first(GRegex * rx, const gchar * src)
+{
+ GMatchInfo *mi = NULL;
+ gchar *dst = NULL;
+ g_regex_match(rx, src, 0, &mi);
+ if (mi) {
+ dst = g_match_info_fetch(mi, 1);
+ g_match_info_free(mi);
+ }
+ return dst;
+}
+
+GRegex *trg_uri_host_regex_new(void)
+{
+ return
+ g_regex_new
+ ("^[^:/?#]+:?//(?:www\\.|torrent\\.|torrents\\.|tracker\\.|\\d+\\.)?([^/?#:]*)",
+ G_REGEX_OPTIMIZE, 0, NULL);
+}
+
+void g_str_slist_free(GSList * list)
+{
+ g_slist_foreach(list, (GFunc) g_free, NULL);
+ g_slist_free(list);
+}
+
+void rm_trailing_slashes(gchar * str)
+{
+ int i, len;
+
+ if (!str)
+ return;
+
+ if ((len = strlen(str)) < 1)
+ return;
+
+ for (i = strlen(str) - 1; str[i]; i--) {
+ if (str[i] == '/')
+ str[i] = '\0';
+ else
+ return;
+ }
+}
+
+/* Working with torrents.. */
+
+void add_file_id_to_array(JsonObject * args, const gchar * key, gint index)
+{
+ JsonArray *array;
+ if (json_object_has_member(args, key)) {
+ array = json_object_get_array_member(args, key);
+ } else {
+ array = json_array_new();
+ json_object_set_array_member(args, key, array);
+ }
+ json_array_add_int_element(array, index);
+}
+
+/* GTK utilities. */
+
+GtkWidget *gtr_combo_box_new_enum(const char *text_1, ...)
+{
+ GtkWidget *w;
+ GtkCellRenderer *r;
+ GtkListStore *store;
+ va_list vl;
+ const char *text;
+ va_start(vl, text_1);
+
+ store = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
+
+ text = text_1;
+ if (text != NULL)
+ do {
+ const int val = va_arg(vl, int);
+ gtk_list_store_insert_with_values(store, NULL, INT_MAX, 0, val,
+ 1, text, -1);
+ text = va_arg(vl, const char *);
+ }
+ while (text != NULL);
+
+ w = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
+ r = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), r, TRUE);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), r, "text", 1, NULL);
+
+ /* cleanup */
+ g_object_unref(store);
+ return w;
+}
+
+GtkWidget *my_scrolledwin_new(GtkWidget * child)
+{
+ GtkWidget *scrolled_win = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_win),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scrolled_win),
+ GTK_SHADOW_ETCHED_IN);
+ gtk_container_add(GTK_CONTAINER(scrolled_win), child);
+ return scrolled_win;
+}
+
+/* gtk_widget_set_sensitive() was introduced in 2.18, we can have a minimum of
+ * 2.16 otherwise. */
+
+void trg_widget_set_visible(GtkWidget * w, gboolean visible)
+{
+ if (visible)
+ gtk_widget_show(w);
+ else
+ gtk_widget_hide(w);
+}
+
+void trg_error_dialog(GtkWindow * parent, trg_response * response)
+{
+ gchar *msg = make_error_message(response->obj, response->status);
+ GtkWidget *dialog = gtk_message_dialog_new(parent,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK, "%s",
+ msg);
+ gtk_window_set_title(GTK_WINDOW(dialog), _("Error"));
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ g_free(msg);
+}
+
+gchar *make_error_message(JsonObject * response, int status)
+{
+ if (status == FAIL_JSON_DECODE) {
+ return g_strdup(_("JSON decoding error."));
+ } else if (status == FAIL_RESPONSE_UNSUCCESSFUL) {
+ const gchar *resultStr =
+ json_object_get_string_member(response, "result");
+ if (resultStr == NULL)
+ return g_strdup(_("Server responded, but with no result."));
+ else
+ return g_strdup(resultStr);
+ } else if (status <= -100) {
+ return g_strdup_printf(_("Request failed with HTTP code %d"),
+ -(status + 100));
+ } else {
+ return g_strdup(curl_easy_strerror(status));
+ }
+}
+
+/* Formatters and Transmission basic utility functions.. */
+
+char *tr_strlpercent(char *buf, double x, size_t buflen)
+{
+ int precision;
+ if (x < 10.0)
+ precision = 2;
+ else if (x < 100.0)
+ precision = 1;
+ else
+ precision = 0;
+
+ tr_snprintf(buf, buflen, "%.*f%%", precision, tr_truncd(x, precision));
+ return buf;
+}
+
+double tr_truncd(double x, int decimal_places)
+{
+ const int i = (int) pow(10, decimal_places);
+ double x2 = (int) (x * i);
+ return x2 / i;
+}
+
+char *tr_strratio(char *buf, size_t buflen, double ratio,
+ const char *infinity)
+{
+ if ((int) ratio == TR_RATIO_NA)
+ tr_strlcpy(buf, _("None"), buflen);
+ else if ((int) ratio == TR_RATIO_INF)
+ tr_strlcpy(buf, infinity, buflen);
+ else if (ratio < 10.0)
+ tr_snprintf(buf, buflen, "%.2f", tr_truncd(ratio, 2));
+ else if (ratio < 100.0)
+ tr_snprintf(buf, buflen, "%.1f", tr_truncd(ratio, 1));
+ else
+ tr_snprintf(buf, buflen, "%'.0f", ratio);
+ return buf;
+}
+
+char *tr_strlratio(char *buf, double ratio, size_t buflen)
+{
+ return tr_strratio(buf, buflen, ratio, "\xE2\x88\x9E");
+}
+
+char *tr_strltime_short(char *buf, long seconds, size_t buflen)
+{
+ int hours, minutes;
+
+ if (seconds < 0)
+ seconds = 0;
+
+ hours = seconds / 3600;
+ minutes = (seconds % 3600) / 60;
+ seconds = (seconds % 3600) % 60;
+
+ g_snprintf(buf, buflen, "%02d:%02d:%02ld", hours, minutes, seconds);
+
+ return buf;
+}
+
+char *tr_strltime_long(char *buf, long seconds, size_t buflen)
+{
+ int days, hours, minutes;
+ char d[128], h[128], m[128], s[128];
+
+ if (seconds < 0)
+ seconds = 0;
+
+ days = seconds / 86400;
+ hours = (seconds % 86400) / 3600;
+ minutes = (seconds % 3600) / 60;
+ seconds = (seconds % 3600) % 60;
+
+ g_snprintf(d, sizeof(d), ngettext("%d day", "%d days", days), days);
+ g_snprintf(h, sizeof(h), ngettext("%d hour", "%d hours", hours),
+ hours);
+ g_snprintf(m, sizeof(m), ngettext("%d minute", "%d minutes", minutes),
+ minutes);
+ g_snprintf(s, sizeof(s),
+ ngettext("%ld second", "%ld seconds", seconds), seconds);
+
+ if (days) {
+ if (days >= 4 || !hours) {
+ g_strlcpy(buf, d, buflen);
+ } else {
+ g_snprintf(buf, buflen, "%s, %s", d, h);
+ }
+ } else if (hours) {
+ if (hours >= 4 || !minutes) {
+ g_strlcpy(buf, h, buflen);
+ } else {
+ g_snprintf(buf, buflen, "%s, %s", h, m);
+ }
+ } else if (minutes) {
+ if (minutes >= 4 || !seconds) {
+ g_strlcpy(buf, m, buflen);
+ } else {
+ g_snprintf(buf, buflen, "%s, %s", m, s);
+ }
+ } else {
+ g_strlcpy(buf, s, buflen);
+ }
+
+ return buf;
+}
+
+char *gtr_localtime(time_t time)
+{
+ const struct tm tm = *localtime(&time);
+ char buf[256], *eoln;
+
+ g_strlcpy(buf, asctime(&tm), sizeof(buf));
+ if ((eoln = strchr(buf, '\n')))
+ *eoln = '\0';
+
+ return g_locale_to_utf8(buf, -1, NULL, NULL, NULL);
+}
+
+char *gtr_localtime2(char *buf, time_t time, size_t buflen)
+{
+ char *tmp = gtr_localtime(time);
+ g_strlcpy(buf, tmp, buflen);
+ g_free(tmp);
+ return buf;
+}
+
+int tr_snprintf(char *buf, size_t buflen, const char *fmt, ...)
+{
+ int len;
+ va_list args;
+
+ va_start(args, fmt);
+ len = evutil_vsnprintf(buf, buflen, fmt, args);
+ va_end(args);
+ return len;
+}
+
+gchar *epoch_to_string(gint64 epoch)
+{
+#if GLIB_CHECK_VERSION(2, 26, 00)
+ GDateTime *dt = g_date_time_new_from_unix_local(epoch);
+ gchar *timestring = g_date_time_format(dt, "%F %H:%M:%S");
+ g_date_time_unref(dt);
+ return timestring;
+#else
+ char buf[64];
+ time_t time_val = epoch;
+ struct tm *ts = localtime(&time_val);
+ int wrote = strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ts);
+ return g_strndup(buf, wrote);
+#endif
+}
+
+/* wrap a link in text with a hyperlink, for use in pango markup.
+ * with or without any links - a newly allocated string is returned. */
+
+gchar *add_links_to_text(const gchar * original)
+{
+ /* return if original already contains links */
+ if (g_regex_match_simple("<a\\s.*>", original, 0, 0)) {
+ return g_strdup(original);
+ }
+
+ gchar *newText, *url, *link;
+ GMatchInfo *match_info;
+ GRegex *regex =
+ g_regex_new("(https?://[a-zA-Z0-9_\\-\\./?=&]+)", 0, 0, NULL);
+
+ // extract url and build escaped link
+ g_regex_match(regex, original, 0, &match_info);
+ url = g_match_info_fetch(match_info, 1);
+
+ if(url) {
+ link = g_markup_printf_escaped("<a href='%s'>%s</a>", url, url);
+ newText = g_regex_replace(regex, original, -1, 0, link,
+ 0, NULL);
+ g_free(url);
+ g_free(link);
+ } else {
+ newText = g_strdup(original);
+ }
+
+ g_regex_unref(regex);
+ g_match_info_unref(match_info);
+ return newText;
+}
+
+size_t tr_strlcpy(char *dst, const void *src, size_t siz)
+{
+#ifdef HAVE_STRLCPY
+ return strlcpy(dst, src, siz);
+#else
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++);
+ }
+
+ return s - (char *) src - 1; /* count does not include NUL */
+#endif
+}
+
+int
+evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap)
+{
+#ifdef _MSC_VER
+ int r = _vsnprintf(buf, buflen, format, ap);
+ buf[buflen - 1] = '\0';
+ if (r >= 0)
+ return r;
+ else
+ return _vscprintf(format, ap);
+#else
+ int r = vsnprintf(buf, buflen, format, ap);
+ buf[buflen - 1] = '\0';
+ return r;
+#endif
+}
+
+char *tr_strlsize(char *buf, guint64 bytes, size_t buflen)
+{
+ if (!bytes)
+ g_strlcpy(buf, Q_("None"), buflen);
+ else
+ tr_formatter_size_B(buf, bytes, buflen);
+
+ return buf;
+}
+
+gboolean is_minimised_arg(const gchar * arg)
+{
+ return !g_strcmp0(arg, "-m")
+ || !g_strcmp0(arg, "--minimized") || !g_strcmp0(arg, "/m");
+}
+
+gboolean should_be_minimised(int argc, char *argv[])
+{
+ int i;
+ for (i = 1; i < argc; i++)
+ if (is_minimised_arg(argv[i]))
+ return TRUE;
+
+ return FALSE;
+}
+
+GtkWidget *trg_hbox_new(gboolean homogeneous, gint spacing)
+{
+ GtkWidget *box;
+#if GTK_CHECK_VERSION( 3, 0, 0 )
+ box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, spacing);
+ gtk_box_set_homogeneous(GTK_BOX(box), homogeneous);
+#else
+ box = gtk_hbox_new(homogeneous, spacing);
+#endif
+ return box;
+}
+
+GtkWidget *trg_vbox_new(gboolean homogeneous, gint spacing)
+{
+ GtkWidget *box;
+#if GTK_CHECK_VERSION( 3, 0, 0 )
+ box = gtk_box_new(GTK_ORIENTATION_VERTICAL, spacing);
+ gtk_box_set_homogeneous(GTK_BOX(box), homogeneous);
+#else
+ box = gtk_vbox_new(homogeneous, spacing);
+#endif
+ return box;
+}
+
+#ifdef WIN32
+gchar *trg_win32_support_path(gchar * file)
+{
+ gchar *moddir =
+ g_win32_get_package_installation_directory_of_module(NULL);
+ gchar *path = g_build_filename(moddir, file, NULL);
+ g_free(moddir);
+ return path;
+}
+#endif
+
+gboolean is_unity()
+{
+ return g_strcmp0(g_getenv("XDG_CURRENT_DESKTOP"), "Unity") == 0;
+}
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..351f565
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,106 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* Most of these functions are taken from the Transmission Project. */
+
+#ifndef UTIL_H_
+#define UTIL_H_
+
+#include <gtk/gtk.h>
+#include <glib-object.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-client.h"
+
+#define trg_strlspeed(a, b) tr_formatter_speed_KBps(a, b, sizeof(a))
+#define trg_strlpercent(a, b) tr_strlpercent(a, b, sizeof(a))
+#define trg_strlsize(a, b) tr_formatter_size_B(a, b, sizeof(a))
+#define trg_strlratio(a, b) tr_strlratio(a, b, sizeof(a))
+
+#define TR_RATIO_NA -1
+#define TR_RATIO_INF -2
+
+extern const int disk_K;
+extern const char *disk_K_str;
+extern const char *disk_M_str;
+extern const char *disk_G_str;
+extern const char *disk_T_str;
+
+extern const int speed_K;
+extern const char *speed_K_str;
+extern const char *speed_M_str;
+extern const char *speed_G_str;
+extern const char *speed_T_str;
+
+void add_file_id_to_array(JsonObject * args, const gchar * key,
+ gint index);
+void g_str_slist_free(GSList * list);
+GRegex *trg_uri_host_regex_new(void);
+gchar *trg_gregex_get_first(GRegex * rx, const gchar * uri);
+gchar *make_error_message(JsonObject * response, int status);
+void trg_error_dialog(GtkWindow * parent, trg_response * response);
+gchar *add_links_to_text(const gchar * original);
+
+void
+tr_formatter_size_init(unsigned int kilo,
+ const char *kb, const char *mb,
+ const char *gb, const char *tb);
+char *tr_formatter_size_B(char *buf, gint64 bytes, size_t buflen);
+
+void
+tr_formatter_speed_init(unsigned int kilo,
+ const char *kb, const char *mb,
+ const char *gb, const char *tb);
+char *tr_formatter_speed_KBps(char *buf, double KBps, size_t buflen);
+
+char *tr_strltime_long(char *buf, long seconds, size_t buflen);
+gchar *epoch_to_string(gint64 epoch);
+char *tr_strltime_short(char *buf, long seconds, size_t buflen);
+char *tr_strlpercent(char *buf, double x, size_t buflen);
+char *tr_strratio(char *buf, size_t buflen, double ratio,
+ const char *infinity);
+char *tr_strlratio(char *buf, double ratio, size_t buflen);
+char *gtr_localtime(time_t time);
+char *gtr_localtime2(char *buf, time_t time, size_t buflen);
+int tr_snprintf(char *buf, size_t buflen, const char *fmt, ...);
+int tr_snprintf(char *buf, size_t buflen, const char *fmt, ...);
+size_t tr_strlcpy(char *dst, const void *src, size_t siz);
+double tr_truncd(double x, int decimal_places);
+int evutil_vsnprintf(char *buf, size_t buflen, const char *format,
+ va_list ap);
+char *tr_strlsize(char *buf, guint64 bytes, size_t buflen);
+void rm_trailing_slashes(gchar * str);
+void trg_widget_set_visible(GtkWidget * w, gboolean visible);
+gchar *trg_base64encode(const gchar * filename);
+GtkWidget *my_scrolledwin_new(GtkWidget * child);
+gboolean is_url(const gchar * string);
+gboolean is_magnet(const gchar * string);
+GtkWidget *gtr_combo_box_new_enum(const char *text_1, ...);
+
+gboolean should_be_minimised(int argc, char *argv[]);
+gboolean is_minimised_arg(const gchar * arg);
+GtkWidget *trg_vbox_new(gboolean homogeneous, gint spacing);
+GtkWidget *trg_hbox_new(gboolean homogeneous, gint spacing);
+gboolean is_unity();
+
+#ifdef WIN32
+gchar *trg_win32_support_path(gchar * file);
+#endif
+
+#endif /* UTIL_H_ */
diff --git a/src/win32-mailslot.c b/src/win32-mailslot.c
new file mode 100644
index 0000000..0ca52a9
--- /dev/null
+++ b/src/win32-mailslot.c
@@ -0,0 +1,196 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#if WIN32
+
+#define TRG_MAILSLOT_NAME "\\\\.\\mailslot\\TransmissionRemoteGTK"
+#define MAILSLOT_BUFFER_SIZE 1024*32
+
+#include <windows.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-main-window.h"
+#include "win32-mailslot.h"
+
+struct trg_mailslot_recv_args {
+ TrgMainWindow *win;
+ gchar **uris;
+ gboolean present;
+};
+
+/* to be queued into the glib main loop with g_idle_add() */
+
+static gboolean mailslot_recv_args(gpointer data)
+{
+ struct trg_mailslot_recv_args *args =
+ (struct trg_mailslot_recv_args *) data;
+
+ if (args->present) {
+ gtk_window_deiconify(GTK_WINDOW(args->win));
+ gtk_window_present(GTK_WINDOW(args->win));
+ }
+
+ if (args->uris)
+ trg_add_from_filename(args->win, args->uris);
+
+ g_free(args);
+
+ return FALSE;
+}
+
+static gpointer mailslot_recv_thread(gpointer data)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(data);
+ JsonParser *parser;
+ char szBuffer[MAILSLOT_BUFFER_SIZE];
+ HANDLE hMailslot;
+ DWORD cbBytes;
+ BOOL bResult;
+
+ hMailslot = CreateMailslot(TRG_MAILSLOT_NAME, /* mailslot name */
+ MAILSLOT_BUFFER_SIZE, /* input buffer size */
+ MAILSLOT_WAIT_FOREVER, /* no timeout */
+ NULL); /* default security attribute */
+
+ if (INVALID_HANDLE_VALUE == hMailslot) {
+ g_error("\nError occurred while creating the mailslot: %d",
+ GetLastError());
+ return NULL; /* Error */
+ }
+
+ while (1) {
+ bResult = ReadFile(hMailslot, /* handle to mailslot */
+ szBuffer, /* buffer to receive data */
+ sizeof(szBuffer), /* size of buffer */
+ &cbBytes, /* number of bytes read */
+ NULL); /* not overlapped I/O */
+
+ if ((!bResult) || (0 == cbBytes)) {
+ g_error("Mailslot error from client: %d", GetLastError());
+ break;
+ }
+
+ parser = json_parser_new();
+
+ if (json_parser_load_from_data(parser, szBuffer, cbBytes, NULL)) {
+ JsonNode *node = json_parser_get_root(parser);
+ JsonObject *obj = json_node_get_object(node);
+ struct trg_mailslot_recv_args *args =
+ g_new0(struct trg_mailslot_recv_args, 1);
+
+ args->present = json_object_has_member(obj, "present")
+ && json_object_get_boolean_member(obj, "present");
+ args->win = win;
+
+ if (json_object_has_member(obj, "args")) {
+ JsonArray *array =
+ json_object_get_array_member(obj, "args");
+ GList *arrayList = json_array_get_elements(array);
+
+ if (arrayList) {
+ guint arrayLength = g_list_length(arrayList);
+ guint i = 0;
+ GList *li;
+
+ args->uris = g_new0(gchar *, arrayLength + 1);
+
+ for (li = arrayList; li; li = g_list_next(li)) {
+ const gchar *liStr =
+ json_node_get_string((JsonNode *) li->data);
+ args->uris[i++] = g_strdup(liStr);
+ }
+
+ g_list_free(arrayList);
+ }
+ }
+
+ json_node_free(node);
+
+ g_idle_add(mailslot_recv_args, args);
+ }
+
+ g_object_unref(parser);
+ }
+
+ CloseHandle(hMailslot);
+ return NULL; /* Success */
+}
+
+void mailslot_start_background_listener(TrgMainWindow * win)
+{
+ g_thread_create(mailslot_recv_thread, win, FALSE, NULL);
+}
+
+gboolean mailslot_send_message(gchar ** args)
+{
+ HANDLE hMailSlot = CreateFile(TRG_MAILSLOT_NAME, /* mailslot name */
+ GENERIC_WRITE, /* mailslot write only */
+ FILE_SHARE_READ, /* required for mailslots */
+ NULL, /* default security attributes */
+ OPEN_EXISTING, /* opens existing mailslot */
+ FILE_ATTRIBUTE_NORMAL, /* normal attributes */
+ NULL); /* no template file */
+
+ if (hMailSlot != INVALID_HANDLE_VALUE) {
+ DWORD cbBytes;
+ JsonNode *node = json_node_new(JSON_NODE_OBJECT);
+ JsonObject *obj = json_object_new();
+ JsonArray *array = json_array_new();
+ JsonGenerator *generator;
+ gchar *msg;
+ int i;
+
+ if (args) {
+ for (i = 0; args[i]; i++)
+ json_array_add_string_element(array, args[i]);
+
+ json_object_set_array_member(obj, "args", array);
+
+ g_strfreev(args);
+ } else {
+ json_object_set_boolean_member(obj, "present", TRUE);
+ }
+
+ json_node_take_object(node, obj);
+
+ generator = json_generator_new();
+ json_generator_set_root(generator, node);
+ msg = json_generator_to_data(generator, NULL);
+
+ json_node_free(node);
+ g_object_unref(generator);
+
+ WriteFile(hMailSlot, /* handle to mailslot */
+ msg, /* buffer to write from */
+ strlen(msg) + 1, /* number of bytes to write, include the NULL */
+ &cbBytes, /* number of bytes written */
+ NULL);
+
+ CloseHandle(hMailSlot);
+ g_free(msg);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+#endif
diff --git a/src/win32-mailslot.h b/src/win32-mailslot.h
new file mode 100644
index 0000000..1d60fef
--- /dev/null
+++ b/src/win32-mailslot.h
@@ -0,0 +1,32 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef WIN32_MAILSLOT_H_
+#define WIN32_MAILSLOT_H_
+
+#if WIN32
+
+#include <windows.h>
+#include "trg-main-window.h"
+
+void mailslot_start_background_listener(TrgMainWindow * win);
+int mailslot_send_message(gchar ** args);
+
+#endif
+#endif /* WIN32_MAILSLOT_H_ */
diff --git a/src/win32.rc b/src/win32.rc
new file mode 100644
index 0000000..9fd8d82
--- /dev/null
+++ b/src/win32.rc
@@ -0,0 +1,35 @@
+1 ICON "transmission_large.ico"
+
+// Executable version information.
+1 VERSIONINFO
+ FILEVERSION 1,0,0,0
+ PRODUCTVERSION 1,0,0,0
+ FILEFLAGSMASK 0x17L
+#ifdef DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "080904b0"
+ BEGIN
+ VALUE "CompanyName", "eth0.org.uk"
+ VALUE "FileDescription", "Transmission Remote GTK"
+ VALUE "FileVersion", "1, 0, 0, 0"
+ VALUE "InternalName", "transmission-remote-gtk"
+ VALUE "LegalCopyright", "©2011 Alan Fitton"
+ VALUE "OriginalFilename", "transmission-remote-gtk.exe"
+ VALUE "ProductName", "Transmission Remote GTK"
+ VALUE "ProductVersion", "1, 0, 0, 0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x809, 1200
+ END
+END \ No newline at end of file