diff options
Diffstat (limited to 'src')
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 Binary files differnew file mode 100644 index 0000000..78b1af2 --- /dev/null +++ b/src/hicolor_apps_16x16_transmission-remote-gtk.png diff --git a/src/hicolor_apps_22x22_transmission-remote-gtk.png b/src/hicolor_apps_22x22_transmission-remote-gtk.png Binary files differnew file mode 100644 index 0000000..3283ea2 --- /dev/null +++ b/src/hicolor_apps_22x22_transmission-remote-gtk.png diff --git a/src/hicolor_apps_24x24_transmission-remote-gtk.png b/src/hicolor_apps_24x24_transmission-remote-gtk.png Binary files differnew file mode 100644 index 0000000..6200ec0 --- /dev/null +++ b/src/hicolor_apps_24x24_transmission-remote-gtk.png diff --git a/src/hicolor_apps_32x32_transmission-remote-gtk.png b/src/hicolor_apps_32x32_transmission-remote-gtk.png Binary files differnew file mode 100644 index 0000000..d3d6464 --- /dev/null +++ b/src/hicolor_apps_32x32_transmission-remote-gtk.png diff --git a/src/hicolor_apps_48x48_transmission-remote-gtk.png b/src/hicolor_apps_48x48_transmission-remote-gtk.png Binary files differnew file mode 100644 index 0000000..3444de8 --- /dev/null +++ b/src/hicolor_apps_48x48_transmission-remote-gtk.png 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 Binary files differnew file mode 100644 index 0000000..39ddb81 --- /dev/null +++ b/src/transmission_large.ico 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, ¤t_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, ¤t, &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, ¤tId, -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, ¤tSerial, + -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 |