diff options
author | Alan Fitton <ajf@eth0.org.uk> | 2011-04-08 14:24:25 +0000 |
---|---|---|
committer | Alan Fitton <ajf@eth0.org.uk> | 2011-04-08 14:24:25 +0000 |
commit | 070688006ac7cea1639f38c0bb270c191f7dc38f (patch) | |
tree | 258fbf88a6f28a86bef83553e2430e950d972793 /src | |
parent | f74c398577f66488cf5c420f5df318ab801117cc (diff) |
a torrent add dialog based off Transmission GTK (different bencode+action implementation) which allows you to set file priorities, torrent priority, directory in advance. probably needs a little more work.
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/bencode.c | 294 | ||||
-rw-r--r-- | src/bencode.h | 66 | ||||
-rw-r--r-- | src/protocol-constants.h | 9 | ||||
-rw-r--r-- | src/requests.c | 3 | ||||
-rw-r--r-- | src/transmission-remote-gtk.schemas | 65 | ||||
-rw-r--r-- | src/trg-cell-renderer-priority.c | 9 | ||||
-rw-r--r-- | src/trg-file-parser.c | 186 | ||||
-rw-r--r-- | src/trg-file-parser.h | 15 | ||||
-rw-r--r-- | src/trg-files-model.c | 8 | ||||
-rw-r--r-- | src/trg-files-model.h | 4 | ||||
-rw-r--r-- | src/trg-files-tree-view.c | 78 | ||||
-rw-r--r-- | src/trg-general-panel.c | 10 | ||||
-rw-r--r-- | src/trg-main-window.c | 107 | ||||
-rw-r--r-- | src/trg-preferences-dialog.c | 24 | ||||
-rw-r--r-- | src/trg-preferences.c | 27 | ||||
-rw-r--r-- | src/trg-preferences.h | 5 | ||||
-rw-r--r-- | src/trg-state-selector.c | 6 | ||||
-rw-r--r-- | src/trg-torrent-add-dialog.c | 1057 | ||||
-rw-r--r-- | src/trg-torrent-add-dialog.h | 58 | ||||
-rw-r--r-- | src/trg-torrent-graph.c | 5 | ||||
-rw-r--r-- | src/trg-torrent-tree-view.c | 5 | ||||
-rw-r--r-- | src/util.c | 18 | ||||
-rw-r--r-- | src/util.h | 2 |
24 files changed, 1901 insertions, 164 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index a022cd6..18efe16 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -69,6 +69,10 @@ transmission_remote_gtk_SOURCES = main.c \ trg-torrent-move-dialog.c \ trg-stats-dialog.c \ trg-torrent-graph.c \ + trg-torrent-add-dialog.c \ + trg-file-parser.c \ + bencode.c \ + trg-preferences.c \ $(NULL) transmission_remote_gtk_LDFLAGS = -lm -lcurl $(jsonglib_LIBS) $(gtk_LIBS) $(gthread_LIBS) $(GEOIP_LIBS) $(gconf_LIBS) $(gio_LIBS) $(unique_LIBS) $(notify_LIBS) $(libproxy_LIBS) diff --git a/src/bencode.c b/src/bencode.c new file mode 100644 index 0000000..0a66cdb --- /dev/null +++ b/src/bencode.c @@ -0,0 +1,294 @@ +/* + * 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 = calloc(1, 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 = 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 = + 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); + + 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 = + 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); + + 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)); +} + +static inline void _be_free_str(char *str) +{ + if (str) + free(str - sizeof(gint64)); +} + +int be_validate_node(be_node * node, int type) +{ + if (!node || node->type != type) + return 1; + else + return 0; +} + +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; + for (i = 0; node->val.l[i]; ++i) + be_free(node->val.l[i]); + free(node->val.l); + break; + } + + case BE_DICT:{ + unsigned int i; + for (i = 0; node->val.d[i].val; ++i) { + _be_free_str(node->val.d[i].key); + be_free(node->val.d[i].val); + } + free(node->val.d); + break; + } + } + 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..71760e0 --- /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; + + extern gint64 be_str_len(be_node * node); + extern be_node *be_decode(const char *bencode); + extern be_node *be_decoden(const char *bencode, gint64 bencode_len); + extern void be_free(be_node * node); + extern void be_dump(be_node * node); + extern be_node *be_dict_find(be_node * node, char *key, int type); + extern int be_validate_node(be_node * node, int type); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/protocol-constants.h b/src/protocol-constants.h index 98d1d72..5299f3b 100644 --- a/src/protocol-constants.h +++ b/src/protocol-constants.h @@ -70,6 +70,7 @@ #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_FILES_WANTED "files-wanted" #define FIELD_FILES_UNWANTED "files-unwanted" @@ -115,7 +116,13 @@ enum { STATUS_DOWNLOADING = 4, STATUS_SEEDING = 8, STATUS_PAUSED = 16 -} TorrentState; +}; + +enum { + 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" diff --git a/src/requests.c b/src/requests.c index 99bdd75..9c252c7 100644 --- a/src/requests.c +++ b/src/requests.c @@ -164,12 +164,13 @@ JsonNode *torrent_add_url(const gchar * url, gboolean paused) return root; } -JsonNode *torrent_add(gchar * filename, gboolean paused G_GNUC_UNUSED) +JsonNode *torrent_add(gchar * filename, gboolean paused) { JsonNode *root = base_request(METHOD_TORRENT_ADD); JsonObject *args = node_get_arguments(root); gchar *encodedFile = base64encode(filename); json_object_set_string_member(args, PARAM_METAINFO, encodedFile); + json_object_set_boolean_member(args, PARAM_PAUSED, paused); g_free(encodedFile); return root; } diff --git a/src/transmission-remote-gtk.schemas b/src/transmission-remote-gtk.schemas index 4849f83..11415a8 100644 --- a/src/transmission-remote-gtk.schemas +++ b/src/transmission-remote-gtk.schemas @@ -2,6 +2,19 @@ <schemalist> <schema> + <key>/schemas/apps/transmission-remote-gtk/add-dialog</key> + <applyto>/apps/transmission-remote-gtk/add-dialog</applyto> + <owner>transmission-remote-gtk</owner> + <type>bool</type> + <default>1</default> + + <locale name="C"> + <short>Show add dialog</short> + <long>Show add dialog</long> + </locale> + </schema> + + <schema> <key>/schemas/apps/transmission-remote-gtk/auto-connect</key> <applyto>/apps/transmission-remote-gtk/auto-connect</applyto> <owner>transmission-remote-gtk</owner> @@ -106,6 +119,45 @@ </schema> <schema> + <key>/schemas/apps/transmission-remote-gtk/add-options-dialog</key> + <applyto>/apps/transmission-remote-gtk/add-options-dialog</applyto> + <owner>transmission-remote-gtk</owner> + <type>bool</type> + <default>1</default> + + <locale name="C"> + <short>Options dialog</short> + <long>Options dialog</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/transmission-remote-gtk/start-paused</key> + <applyto>/apps/transmission-remote-gtk/start-paused</applyto> + <owner>transmission-remote-gtk</owner> + <type>bool</type> + <default>0</default> + + <locale name="C"> + <short>Start paused</short> + <long>Start paused</long> + </locale> + </schema> + + <schema> + <key>/schemas/apps/transmission-remote-gtk/show-graph</key> + <applyto>/apps/transmission-remote-gtk/show-graph</applyto> + <owner>transmission-remote-gtk</owner> + <type>bool</type> + <default>0</default> + + <locale name="C"> + <short>Show graph</short> + <long>Show graph</long> + </locale> + </schema> + + <schema> <key>/schemas/apps/transmission-remote-gtk/update-interval</key> <applyto>/apps/transmission-remote-gtk/update-interval</applyto> <owner>transmission-remote-gtk</owner> @@ -171,6 +223,19 @@ </schema> <schema> + <key>/schemas/apps/transmission-remote-gtk/last-torrent-dir</key> + <applyto>/apps/transmission-remote-gtk/last-torrent-dir</applyto> + <owner>transmission-remote-gtk</owner> + <type>string</type> + <default></default> + + <locale name="C"> + <short>Last torrent directory</short> + <long>Last torrent directory</long> + </locale> + </schema> + + <schema> <key>/schemas/apps/transmission-remote-gtk/username</key> <applyto>/apps/transmission-remote-gtk/username</applyto> <owner>transmission-remote-gtk</owner> diff --git a/src/trg-cell-renderer-priority.c b/src/trg-cell-renderer-priority.c index eb16580..14d4763 100644 --- a/src/trg-cell-renderer-priority.c +++ b/src/trg-cell-renderer-priority.c @@ -22,6 +22,7 @@ #include <gtk/gtk.h> #include "trg-cell-renderer-priority.h" +#include "protocol-constants.h" #include "trg-files-model.h" #include "util.h" @@ -68,9 +69,9 @@ trg_cell_renderer_priority_set_property(GObject * object, if (property_id == PROP_PRIORITY_VALUE) { priv->priority_value = g_value_get_int64(value); - if (priv->priority_value == T_PRIORITY_LOW) { + if (priv->priority_value == TR_PRI_LOW) { g_object_set(object, "text", _("Low"), NULL); - } else if (priv->priority_value == T_PRIORITY_HIGH) { + } else if (priv->priority_value == TR_PRI_HIGH) { g_object_set(object, "text", _("High"), NULL); } else { g_object_set(object, "text", _("Normal"), NULL); @@ -93,8 +94,8 @@ trg_cell_renderer_priority_class_init(TrgCellRendererPriorityClass * klass) g_param_spec_int64 ("priority-value", "Priority Value", - "Priority Value", T_PRIORITY_LOW, - T_PRIORITY_HIGH, T_PRIORITY_NORMAL, + "Priority Value", TR_PRI_LOW, + TR_PRI_HIGH, TR_PRI_NORMAL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | diff --git a/src/trg-file-parser.c b/src/trg-file-parser.c new file mode 100644 index 0000000..ec22d1d --- /dev/null +++ b/src/trg-file-parser.c @@ -0,0 +1,186 @@ +#include <sys/mman.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include <glib.h> + +#include "bencode.h" +#include "trg-file-parser.h" + +#define my_print_errno(x) printf("%s: error (%d) %s\n", __func__, errno, x); + +static trg_torrent_file_node + *trg_torrent_file_node_insert(trg_torrent_file_node * top, + be_node * file_node, guint index, + gint64 * total_length) +{ + int i; + trg_torrent_file_node *path_el_parent = top; + be_node *file_length_node = be_dict_find(file_node, "length", BE_INT); + be_node *file_path_node = be_dict_find(file_node, "path", BE_LIST); + + if (!file_path_node || !file_length_node) + return NULL; + + /* Iterate over the path list which contains each file/directory + * component of the path in order. + */ + for (i = 0;;) { + be_node *path_el_node = file_path_node->val.l[i]; + + trg_torrent_file_node *target_node = NULL; + GList *li; + + /* Does this element exist already? */ + for (li = path_el_parent->children; li != NULL; + li = g_list_next(li)) { + trg_torrent_file_node *x = (trg_torrent_file_node *) li->data; + if (!g_strcmp0(x->name, path_el_node->val.s)) { + target_node = x; + break; + } + } + + if (!target_node) { + /* Create a new node and add it as a child of the parent from the + * last iteration. */ + target_node = g_new0(trg_torrent_file_node, 1); + target_node->name = g_strdup(path_el_node->val.s); + path_el_parent->children = + g_list_append(path_el_parent->children, target_node); + } + + path_el_parent = target_node; + + /* Is this the last component of the path (the file)? */ + if (!file_path_node->val.l[++i]) { + *total_length += (target_node->length = + (gint64) (file_length_node->val.i)); + target_node->index = index; + return target_node; + } + } +} + +static void trg_torrent_file_node_free(trg_torrent_file_node * node) +{ + GList *li; + for (li = node->children; li != NULL; li = g_list_next(li)) + trg_torrent_file_node_free((trg_torrent_file_node *) li->data); + g_list_free(node->children); + g_free(node->name); + g_free(node); +} + +void trg_torrent_file_free(trg_torrent_file * t) +{ + trg_torrent_file_node_free(t->top_node); + g_free(t->name); + g_free(t); +} + +static trg_torrent_file_node *trg_parse_torrent_file_nodes(be_node * + info_node, + gint64 * + total_length) +{ + be_node *files_node = be_dict_find(info_node, "files", BE_LIST); + trg_torrent_file_node *top_node = g_new0(trg_torrent_file_node, 1); + 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) || + !trg_torrent_file_node_insert(top_node, file_node, i, + total_length)) { + /* Unexpected format. Throw away everything, file indexes need to + * be correct. */ + trg_torrent_file_node_free(top_node); + return NULL; + } + } + + return top_node; +} + +trg_torrent_file *trg_parse_torrent_file(char *filename) +{ + int fd; + struct stat sb; + void *addr; + be_node *top_node, *info_node, *name_node; + trg_torrent_file *ret = NULL; + + fd = open(filename, O_RDONLY); + if (fd < 0) { + my_print_errno("opening file"); + return NULL; + } + + if (fstat(fd, &sb) == -1) { + my_print_errno("on fstat"); + close(fd); + return NULL; + } + + addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) { + my_print_errno("on mmap"); + close(fd); + return NULL; + } + + top_node = be_decoden((char *) addr, sb.st_size); + munmap(addr, sb.st_size); + close(fd); + + 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, &(ret->total_length)); + if (!ret->top_node) { + trg_torrent_file_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_torrent_file_node, 1); + file_node->length = ret->total_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..0958b9d --- /dev/null +++ b/src/trg-file-parser.h @@ -0,0 +1,15 @@ +typedef struct { + char *name; + gint64 length; + GList *children; + guint index; +} trg_torrent_file_node; + +typedef struct { + char *name; + trg_torrent_file_node *top_node; + gint64 total_length; +} trg_torrent_file; + +void trg_torrent_file_free(trg_torrent_file * t); +trg_torrent_file *trg_parse_torrent_file(char *filename); diff --git a/src/trg-files-model.c b/src/trg-files-model.c index 3051f55..084997d 100644 --- a/src/trg-files-model.c +++ b/src/trg-files-model.c @@ -44,14 +44,12 @@ static void trg_files_model_iter_new(TrgFilesModel * model, GtkTreeIter * iter, JsonObject * file, int id) { - char size[32]; - gtk_list_store_append(GTK_LIST_STORE(model), iter); - trg_strlsize(size, file_get_length(file)); gtk_list_store_set(GTK_LIST_STORE(model), iter, FILESCOL_NAME, file_get_name(file), - FILESCOL_SIZE, size, FILESCOL_ID, id, -1); + FILESCOL_SIZE, file_get_length(file), + FILESCOL_ID, id, -1); } void trg_files_model_set_accept(TrgFilesModel * model, gboolean accept) @@ -100,7 +98,7 @@ static void trg_files_model_init(TrgFilesModel * self) column_types[FILESCOL_ICON] = G_TYPE_STRING; column_types[FILESCOL_NAME] = G_TYPE_STRING; - column_types[FILESCOL_SIZE] = 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_BOOLEAN; diff --git a/src/trg-files-model.h b/src/trg-files-model.h index c92ddd0..76dccf7 100644 --- a/src/trg-files-model.h +++ b/src/trg-files-model.h @@ -60,10 +60,6 @@ G_END_DECLS enum { FILESCOL_COLUMNS }; -#define T_PRIORITY_LOW -1 -#define T_PRIORITY_NORMAL 0 -#define T_PRIORITY_HIGH 1 - void trg_files_model_update(TrgFilesModel * model, gint64 updateSerial, JsonObject * t, gboolean first); diff --git a/src/trg-files-tree-view.c b/src/trg-files-tree-view.c index dab7611..2b5c812 100644 --- a/src/trg-files-tree-view.c +++ b/src/trg-files-tree-view.c @@ -28,6 +28,7 @@ #include "trg-cell-renderer-priority.h" #include "trg-main-window.h" #include "requests.h" +#include "util.h" #include "json.h" #include "dispatch.h" #include "protocol-constants.h" @@ -65,44 +66,18 @@ static void set_wanted_foreachfunc(GtkTreeModel * model, TRUE, FILESCOL_ICON, GTK_STOCK_FILE, -1); } -static void set_low_foreachfunc(GtkTreeModel * model, - GtkTreePath * path G_GNUC_UNUSED, - GtkTreeIter * iter, - gpointer data G_GNUC_UNUSED) +static void set_priority_foreachfunc(GtkTreeModel * model, + GtkTreePath * path G_GNUC_UNUSED, + GtkTreeIter * iter, gpointer data) { GValue value = { 0 }; g_value_init(&value, G_TYPE_INT64); - g_value_set_int64(&value, T_PRIORITY_LOW); + g_value_set_int64(&value, (gint64) GPOINTER_TO_INT(data)); gtk_list_store_set_value(GTK_LIST_STORE(model), iter, FILESCOL_PRIORITY, &value); } -static void set_normal_foreachfunc(GtkTreeModel * model, - GtkTreePath * path G_GNUC_UNUSED, - GtkTreeIter * iter, - gpointer data G_GNUC_UNUSED) -{ - gtk_list_store_set(GTK_LIST_STORE(model), iter, FILESCOL_PRIORITY, - T_PRIORITY_NORMAL, -1); -} - -static void set_high_foreachfunc(GtkTreeModel * model, - GtkTreePath * path G_GNUC_UNUSED, - GtkTreeIter * iter, - gpointer data G_GNUC_UNUSED) -{ - gtk_list_store_set(GTK_LIST_STORE(model), iter, FILESCOL_PRIORITY, - T_PRIORITY_HIGH, -1); -} - -static void add_file_id_to_array(JsonObject * args, gchar * key, - gint index) -{ - JsonArray *array = json_object_get_array_member(args, key); - json_array_add_int_element(array, index); -} - static void send_updated_file_prefs_foreachfunc(GtkTreeModel * model, GtkTreePath * path G_GNUC_UNUSED, @@ -121,21 +96,14 @@ static void send_updated_file_prefs_foreachfunc(GtkTreeModel * model, else add_file_id_to_array(args, FIELD_FILES_WANTED, id); - if (priority == T_PRIORITY_LOW) + if (priority == TR_PRI_LOW) add_file_id_to_array(args, FIELD_FILES_PRIORITY_LOW, id); - else if (priority == T_PRIORITY_HIGH) + 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); } -static void remove_array_if_empty(JsonObject * args, gchar * key) -{ - JsonArray *array = json_object_get_array_member(args, key); - if (json_array_get_length(array) == 0) - json_object_remove_member(args, key); -} - static void on_files_update(JsonObject * response, int status, gpointer data) { @@ -166,27 +134,10 @@ static void send_updated_file_prefs(TrgFilesTreeView * tv) req = torrent_set(targetIdArray); args = node_get_arguments(req); - json_object_set_array_member(args, FIELD_FILES_WANTED, - json_array_new()); - json_object_set_array_member(args, FIELD_FILES_UNWANTED, - json_array_new()); - json_object_set_array_member(args, FIELD_FILES_PRIORITY_HIGH, - json_array_new()); - json_object_set_array_member(args, FIELD_FILES_PRIORITY_NORMAL, - json_array_new()); - json_object_set_array_member(args, FIELD_FILES_PRIORITY_LOW, - json_array_new()); - gtk_tree_selection_selected_foreach(selection, send_updated_file_prefs_foreachfunc, args); - remove_array_if_empty(args, FIELD_FILES_WANTED); - remove_array_if_empty(args, FIELD_FILES_UNWANTED); - remove_array_if_empty(args, FIELD_FILES_PRIORITY_HIGH); - remove_array_if_empty(args, FIELD_FILES_PRIORITY_NORMAL); - remove_array_if_empty(args, FIELD_FILES_PRIORITY_LOW); - trg_files_model_set_accept(TRG_FILES_MODEL(model), FALSE); dispatch_async(priv->client, req, on_files_update, tv); @@ -197,8 +148,9 @@ static void set_low(GtkWidget * w G_GNUC_UNUSED, gpointer data) TrgFilesTreeView *tv = TRG_FILES_TREE_VIEW(data); GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data)); - gtk_tree_selection_selected_foreach(selection, set_low_foreachfunc, - NULL); + gtk_tree_selection_selected_foreach(selection, + set_priority_foreachfunc, + GINT_TO_POINTER(TR_PRI_LOW)); send_updated_file_prefs(tv); } @@ -208,7 +160,8 @@ static void set_normal(GtkWidget * w G_GNUC_UNUSED, gpointer data) GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data)); gtk_tree_selection_selected_foreach(selection, - set_normal_foreachfunc, NULL); + set_priority_foreachfunc, + GINT_TO_POINTER(TR_PRI_NORMAL)); send_updated_file_prefs(tv); } @@ -218,7 +171,8 @@ static void set_high(GtkWidget * w G_GNUC_UNUSED, gpointer data) GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(data)); gtk_tree_selection_selected_foreach(selection, - set_high_foreachfunc, NULL); + set_priority_foreachfunc, + GINT_TO_POINTER(TR_PRI_HIGH)); send_updated_file_prefs(tv); } @@ -362,8 +316,8 @@ static void trg_files_tree_view_init(TrgFilesTreeView * self) trg_tree_view_add_pixbuf_text_column(TRG_TREE_VIEW(self), FILESCOL_ICON, FILESCOL_NAME, _("Name"), -1); - trg_tree_view_add_column(TRG_TREE_VIEW(self), _("Size"), - FILESCOL_SIZE); + trg_tree_view_add_size_column(TRG_TREE_VIEW(self), _("Size"), + FILESCOL_SIZE, -1); trg_tree_view_add_prog_column(TRG_TREE_VIEW(self), _("Progress"), FILESCOL_PROGRESS, -1); trg_files_tree_view_add_wanted_column(TRG_TREE_VIEW(self), _("Wanted"), diff --git a/src/trg-general-panel.c b/src/trg-general-panel.c index 91274bf..ec60cf9 100644 --- a/src/trg-general-panel.c +++ b/src/trg-general-panel.c @@ -134,9 +134,7 @@ void trg_general_panel_update(TrgGeneralPanel * panel, JsonObject * t, gtk_label_set_text(GTK_LABEL(priv->gen_downloaded_label), buf); if (uploaded > 0 && downloaded > 0) { - trg_strlratio(buf, - (double) uploaded / - (double) downloaded); + trg_strlratio(buf, (double) uploaded / (double) downloaded); gtk_label_set_text(GTK_LABEL(priv->gen_ratio_label), buf); } else { gtk_label_set_text(GTK_LABEL(priv->gen_ratio_label), _("N/A")); @@ -165,8 +163,10 @@ void trg_general_panel_update(TrgGeneralPanel * panel, JsonObject * t, 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")); + markup = + g_markup_printf_escaped + ("<span font_weight=\"bold\" fgcolor=\"red\">%s</span>", + _("Error")); gtk_label_set_markup(keyLabel, markup); g_free(markup); } else { diff --git a/src/trg-main-window.c b/src/trg-main-window.c index 4e21cf7..8080038 100644 --- a/src/trg-main-window.c +++ b/src/trg-main-window.c @@ -60,6 +60,7 @@ #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" @@ -84,7 +85,6 @@ static void torrent_tv_onRowActivated(GtkTreeView * treeview, GtkTreePath * path, GtkTreeViewColumn * col, gpointer userdata); -static gpointer add_files_threadfunc(gpointer data); 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); @@ -370,40 +370,6 @@ torrent_tv_onRowActivated(GtkTreeView * treeview, open_props_cb(GTK_WIDGET(treeview), userdata); } -/* 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; - trg_client *client; - gpointer cb_data; -}; - -static gpointer add_files_threadfunc(gpointer data) -{ - JsonObject *response; - JsonNode *request; - struct add_torrent_threadfunc_args *args; - gint status; - GSList *li; - - args = (struct add_torrent_threadfunc_args *) data; - - for (li = args->list; li != NULL; li = g_slist_next(li)) { - request = torrent_add((gchar *) li->data, FALSE); - g_free(li->data); - response = dispatch(args->client, request, &status); - on_generic_interactive_action(response, status, args->cb_data); - } - - g_slist_free(args->list); - g_free(args); - - return NULL; -} - static void add_url_cb(GtkWidget * w G_GNUC_UNUSED, gpointer data) { TrgMainWindow *win = TRG_MAIN_WINDOW(data); @@ -416,47 +382,33 @@ static void add_url_cb(GtkWidget * w G_GNUC_UNUSED, gpointer data) static void add_cb(GtkWidget * w G_GNUC_UNUSED, gpointer data) { - TrgMainWindowPrivate *priv; - GtkWidget *dialog; - GtkFileFilter *filter; + TrgMainWindowPrivate *priv = TRG_MAIN_WINDOW_GET_PRIVATE(data); + /*TrgTorrentAddDialog *dialog; + GtkFileFilter *filter; */ - priv = TRG_MAIN_WINDOW_GET_PRIVATE(data); + trg_torrent_add_dialog(TRG_MAIN_WINDOW(data), priv->client); - dialog = gtk_file_chooser_dialog_new("Open File", - GTK_WINDOW(data), - GTK_FILE_CHOOSER_ACTION_OPEN, - GTK_STOCK_CANCEL, - GTK_RESPONSE_CANCEL, - GTK_STOCK_OPEN, - GTK_RESPONSE_ACCEPT, NULL); - gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); - - filter = gtk_file_filter_new(); - gtk_file_filter_add_pattern(filter, "*.torrent"); - gtk_file_filter_set_name(filter, _("BitTorrent Metadata")); - gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); - - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { - GThread *thread; - GError *error = NULL; - struct add_torrent_threadfunc_args *args; - - args = g_new(struct add_torrent_threadfunc_args, 1); - args->list = - gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog)); - args->cb_data = data; - args->client = priv->client; - - thread = - g_thread_create(add_files_threadfunc, args, FALSE, &error); - if (error != NULL) { - g_printf("thread creation error: %s\n", error->message); - g_error_free(error); - g_free(args); - } - } + /*dialog = trg_torrent_add_dialog_new(TRG_MAIN_WINDOW(data), priv->client, NULL, TRUE); + gtk_dialog_run(GTK_DIALOG(dialog)); */ - gtk_widget_destroy(dialog); + /*dialog = gtk_file_chooser_dialog_new("Open File", + GTK_WINDOW(data), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, + GTK_RESPONSE_ACCEPT, NULL); + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE); + + filter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(filter, "*.torrent"); + gtk_file_filter_set_name(filter, _("BitTorrent Metadata")); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) { + } + + gtk_widget_destroy(dialog); */ } static void pause_cb(GtkWidget * w G_GNUC_UNUSED, gpointer data) @@ -474,7 +426,7 @@ gboolean trg_add_from_filename(TrgMainWindow * win, gchar * fileName) TrgMainWindowPrivate *priv = TRG_MAIN_WINDOW_GET_PRIVATE(win); if (g_file_test(fileName, G_FILE_TEST_EXISTS) == TRUE) { - JsonNode *torrentAddReq = torrent_add(fileName, FALSE); + JsonNode *torrentAddReq = torrent_add(fileName, pref_get_start_paused(priv->client->gconf)); dispatch_async(priv->client, torrentAddReq, on_generic_interactive_action, win); return TRUE; @@ -1779,6 +1731,7 @@ static GObject *trg_main_window_constructor(GType type, gtk_icon_theme_load_icon(theme, PACKAGE_NAME, 48, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); + notify_init(PACKAGE_NAME); if (priv->icon) gtk_window_set_default_icon(priv->icon); @@ -1874,9 +1827,9 @@ static GObject *trg_main_window_constructor(GType type, gtk_paned_pack1(GTK_PANED(priv->vpaned), priv->hpaned, TRUE, TRUE); priv->stateSelector = trg_state_selector_new(priv->client); - priv->stateSelectorScroller = my_scrolledwin_new(GTK_WIDGET(priv->stateSelector)); - gtk_paned_pack1(GTK_PANED(priv->hpaned), - priv->stateSelectorScroller, + 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), diff --git a/src/trg-preferences-dialog.c b/src/trg-preferences-dialog.c index c20c2cf..fb71881 100644 --- a/src/trg-preferences-dialog.c +++ b/src/trg-preferences-dialog.c @@ -305,6 +305,26 @@ static GtkWidget *trg_prefs_desktopPage(GConfClient * gconf, return t; } +static GtkWidget *trg_prefs_behaviorPage(GConfClient *gconf) +{ + GtkWidget *w, *t; + gint row = 0; + + t = hig_workarea_create(); + + hig_workarea_add_section_title(t, &row, _("Torrents")); + + w = new_check_button(gconf, _("Start paused"), + TRG_GCONF_KEY_START_PAUSED); + hig_workarea_add_wide_control(t, &row, w); + + w = new_check_button(gconf, _("Options dialog on add"), + TRG_GCONF_KEY_ADD_OPTIONS_DIALOG); + hig_workarea_add_wide_control(t, &row, w); + + return t; +} + static GtkWidget *trg_prefs_serverPage(GConfClient * gconf, trg_client * client) { @@ -386,6 +406,10 @@ static GObject *trg_preferences_dialog_constructor(GType type, trg_prefs_desktopPage(priv->gconf, priv->win), gtk_label_new(_("Desktop"))); + gtk_notebook_append_page(GTK_NOTEBOOK(notebook), + trg_prefs_behaviorPage(priv->gconf), + gtk_label_new(_("Behavior"))); + gtk_container_set_border_width(GTK_CONTAINER(notebook), GUI_PAD); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(object)->vbox), notebook, diff --git a/src/trg-preferences.c b/src/trg-preferences.c new file mode 100644 index 0000000..ca52be4 --- /dev/null +++ b/src/trg-preferences.c @@ -0,0 +1,27 @@ +/* + * transmission-remote-gtk - Transmission RPC client for GTK + * Copyright (C) 2010 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 <gconf/gconf-client.h> + +#include "trg-preferences.h" + +gboolean pref_get_start_paused(GConfClient *gcc) { + return gconf_client_get_bool(gcc, TRG_GCONF_KEY_START_PAUSED, NULL); +} diff --git a/src/trg-preferences.h b/src/trg-preferences.h index e4a047d..e781dc6 100644 --- a/src/trg-preferences.h +++ b/src/trg-preferences.h @@ -37,5 +37,10 @@ #define TRG_GCONF_KEY_SYSTEM_TRAY_MINIMISE "/apps/transmission-remote-gtk/system-tray-minimise" #define TRG_GCONF_KEY_FILTER_TRACKERS "/apps/transmission-remote-gtk/filter-trackers" #define TRG_GCONF_KEY_FILTER_DIRS "/apps/transmission-remote-gtk/filter-dirs" +#define TRG_GCONF_KEY_LAST_TORRENT_DIR "/apps/transmission-remote-gtk/last-torrent-dir" +#define TRG_GCONF_KEY_ADD_OPTIONS_DIALOG "/apps/transmission-remote-gtk/add-options-dialog" +#define TRG_GCONF_KEY_START_PAUSED "/apps/transmission-remote-gtk/start-paused" + +gboolean pref_get_start_paused(GConfClient *gcc); #endif /* TRG_PREFERENCES_H_ */ diff --git a/src/trg-state-selector.c b/src/trg-state-selector.c index b6f3462..80fadde 100644 --- a/src/trg-state-selector.c +++ b/src/trg-state-selector.c @@ -250,8 +250,8 @@ void trg_state_selector_update(TrgStateSelector * s) } else { gtk_list_store_insert(GTK_LIST_STORE(model), &iter, 9 + - g_hash_table_size(priv-> - trackers)); + g_hash_table_size + (priv->trackers)); gtk_list_store_set(GTK_LIST_STORE(model), &iter, STATE_SELECTOR_ICON, GTK_STOCK_NETWORK, @@ -441,7 +441,7 @@ TrgStateSelector *trg_state_selector_new(trg_client * client) priv->client = client; priv->showDirs = gconf_client_get_bool(client->gconf, - TRG_GCONF_KEY_FILTER_DIRS, NULL); + TRG_GCONF_KEY_FILTER_DIRS, NULL); priv->showTrackers = gconf_client_get_bool_or_true(client->gconf, TRG_GCONF_KEY_FILTER_TRACKERS); diff --git a/src/trg-torrent-add-dialog.c b/src/trg-torrent-add-dialog.c new file mode 100644 index 0000000..dd1fac0 --- /dev/null +++ b/src/trg-torrent-add-dialog.c @@ -0,0 +1,1057 @@ +/* + * transmission-remote-gtk - Transmission RPC client for GTK + * 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 <gconf/gconf-client.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-cell-renderer-size.h" +#include "trg-preferences.h" +#include "requests.h" +#include "json.h" +#include "dispatch.h" +#include "protocol-constants.h" + +enum { + PROP_0, + PROP_FILENAME, + PROP_PARENT, + PROP_SHOW_OPTIONS, + PROP_CLIENT +}; + +enum { + FC_ICON, + FC_INDEX, + FC_LABEL, + FC_SIZE, + FC_PRIORITY, + FC_ENABLED, + N_FILE_COLS +}; + +enum { + NOT_SET = 1000, + MIXED = 1001 +}; + +#define TR_COLUMN_ID_KEY "tr-id-key" + +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 { + trg_client *client; + TrgMainWindow *parent; + gint show_options; + GSList *filenames; + GtkWidget *source_chooser; + GtkWidget *dest_combo; + GtkWidget *priority_combo; + GtkWidget *file_list; + GtkTreeStore *store; + GtkWidget *paused_check; +}; + +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_SHOW_OPTIONS: + priv->show_options = g_value_get_int(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_SHOW_OPTIONS: + g_value_set_int(value, priv->show_options); + break; + case PROP_PARENT: + g_value_set_object(value, priv->parent); + break; + } +} + +/* 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; + trg_client *client; + gpointer cb_data; + gboolean paused; + gchar *dir; + gint priority; + gboolean extraArgs; +}; + +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 != NULL; li = g_slist_next(li)) { + JsonNode *request = + torrent_add((gchar *) li->data, files_thread_data->paused); + JsonObject *args = node_get_arguments(request); + JsonObject *response; + gint status; + + 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, &status); + on_generic_interactive_action(response, status, + files_thread_data->cb_data); + } + + 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; +} + +static gchar *trg_destination_folder_get(GtkComboBox * box) +{ + return gtk_combo_box_get_active_text(box); +} + +static GtkWidget *trg_destination_folder_new(trg_client * client) +{ + GtkWidget *combo = gtk_combo_box_entry_new_text(); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), + json_object_get_string_member + (client->session, SGET_DOWNLOAD_DIR)); + gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0); + return combo; +} + +static void launch_add_thread(struct add_torrent_threadfunc_args *args) +{ + GError *error = NULL; + g_thread_create(add_files_threadfunc, args, FALSE, &error); + + if (error != NULL) { + g_printf("thread creation error: %s\n", 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)) + 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); + + if (res_id == GTK_RESPONSE_ACCEPT) { + gboolean paused = + gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON + (priv->paused_check)); + gint priority = + gtk_combo_box_get_active(GTK_COMBO_BOX(priv->priority_combo)) - + 1; + gchar *dir = + trg_destination_folder_get(GTK_COMBO_BOX(priv->dest_combo)); + + if (g_slist_length(priv->filenames) == 1) { + JsonNode *req = + torrent_add((gchar *) priv->filenames->data, paused); + 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 = data; + args->client = priv->client; + args->dir = g_strdup(dir); + args->priority = priority; + args->paused = paused; + args->extraArgs = TRUE; + + launch_add_thread(args); + } + } else { + g_str_slist_free(priv->filenames); + } + + gtk_widget_destroy(GTK_WIDGET(dlg)); +} + +static void +renderPriority(GtkTreeViewColumn * column G_GNUC_UNUSED, + GtkCellRenderer * renderer, + GtkTreeModel * model, + GtkTreeIter * iter, gpointer data G_GNUC_UNUSED) +{ + int priority; + const char *text; + gtk_tree_model_get(model, iter, FC_PRIORITY, &priority, -1); + switch (priority) { + case TR_PRI_HIGH: + text = _("High"); + break; + case TR_PRI_NORMAL: + text = _("Normal"); + break; + case TR_PRI_LOW: + text = _("Low"); + break; + default: + text = _("Mixed"); + break; + } + g_object_set(renderer, "text", text, NULL); +} + +static void +renderDownload(GtkTreeViewColumn * column G_GNUC_UNUSED, + GtkCellRenderer * renderer, + GtkTreeModel * model, + GtkTreeIter * iter, gpointer data G_GNUC_UNUSED) +{ + gint enabled; + gtk_tree_model_get(model, iter, FC_ENABLED, &enabled, -1); + g_object_set(renderer, "inconsistent", (enabled == MIXED), + "active", (enabled == TRUE), NULL); +} + +struct SubtreeForeachData { + gint column; + gint new_value; + GtkTreePath *path; +}; + +static gboolean +setSubtreeForeach(GtkTreeModel * model, + GtkTreePath * path, GtkTreeIter * iter, gpointer gdata) +{ + /*const gboolean is_file = !gtk_tree_model_iter_has_child(model, iter); */ + + struct SubtreeForeachData *data = gdata; + + if (!gtk_tree_path_compare(path, data->path) + || gtk_tree_path_is_descendant(path, data->path)) { + gtk_tree_store_set(GTK_TREE_STORE(model), iter, data->column, + data->new_value, -1); + } + + return FALSE; /* keep walking */ +} + +static void +setSubtree(GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter, + gint column, gint new_value) +{ + gint result = new_value; + GtkTreeIter back_iter = *iter; + + if (gtk_tree_model_iter_has_child(model, iter)) { + struct SubtreeForeachData tmp; + GtkTreeIter parent; + + tmp.column = column; + tmp.new_value = new_value; + tmp.path = path; + gtk_tree_model_foreach(model, setSubtreeForeach, &tmp); + + gtk_tree_model_iter_parent(model, &parent, iter); + } else { + gtk_tree_store_set(GTK_TREE_STORE(model), &back_iter, column, new_value, + -1); + } + + 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 = MIXED; + break; + } + } + + gtk_tree_store_set(GTK_TREE_STORE(model), &tmp_iter, column, + result, -1); + + back_iter = tmp_iter; + } +} + +static gboolean +onViewPathToggled(GtkTreeView * view, + GtkTreeViewColumn * col, + GtkTreePath * path, gpointer data) +{ + int cid; + gboolean handled = FALSE; + + if (!col || !path) + return FALSE; + + cid = + GPOINTER_TO_INT(g_object_get_data + (G_OBJECT(col), TR_COLUMN_ID_KEY)); + if ((cid == FC_PRIORITY) || (cid == FC_ENABLED)) { + GtkTreeIter iter; + /*GArray *indices = getActiveFilesForPath(view, path); */ + GtkTreeModel *model = gtk_tree_view_get_model(view); + + gtk_tree_model_get_iter(model, &iter, path); + + if (cid == FC_PRIORITY) { + int priority; + gtk_tree_model_get(model, &iter, FC_PRIORITY, &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; + } + setSubtree(model, path, &iter, FC_PRIORITY, priority); + } else { + int enabled; + gtk_tree_model_get(model, &iter, FC_ENABLED, &enabled, -1); + enabled = !enabled; + + setSubtree(model, path, &iter, FC_ENABLED, 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; +} + +static gboolean +onViewButtonPressed(GtkWidget * w, GdkEventButton * event, gpointer gdata) +{ + GtkTreeViewColumn *col = NULL; + GtkTreePath *path = NULL; + 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, NULL); + } + + gtk_tree_path_free(path); + return handled; +} + +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 = gtk_cell_renderer_pixbuf_new(); + gtk_tree_view_column_pack_start(col, rend, FALSE); + gtk_tree_view_column_add_attribute(col, rend, "stock-id", FC_ICON); + + /* 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 = gtk_cell_renderer_toggle_new(); + col = gtk_tree_view_column_new_with_attributes(title, rend, NULL); + g_object_set_data(G_OBJECT(col), TR_COLUMN_ID_KEY, + GINT_TO_POINTER(FC_ENABLED)); + 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_cell_data_func(col, rend, renderDownload, + NULL, NULL); + 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 = gtk_cell_renderer_text_new(); + g_object_set(rend, "xalign", (gfloat) 0.5, "yalign", (gfloat) 0.5, + NULL); + col = gtk_tree_view_column_new_with_attributes(title, rend, NULL); + g_object_set_data(G_OBJECT(col), TR_COLUMN_ID_KEY, + GINT_TO_POINTER(FC_PRIORITY)); + 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_column_set_cell_data_func(col, rend, renderPriority, + NULL, NULL); + gtk_tree_view_append_column(tree_view, col); + + *store = gtk_tree_store_new(N_FILE_COLS, G_TYPE_STRING, /* icon */ + G_TYPE_UINT, /* 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)); + + /* 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_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 *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_torrent_file_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, -1); + gtk_tree_store_set(store, &child, FC_ICON, + node->children ? GTK_STOCK_DIRECTORY : + GTK_STOCK_FILE, -1); + if (!node->children) { + gtk_tree_store_set(store, &child, FC_INDEX, node->index, -1); + gtk_tree_store_set(store, &child, FC_SIZE, node->length, -1); + gtk_tree_store_set(store, &child, FC_PRIORITY, 0, -1); + gtk_tree_store_set(store, &child, FC_ENABLED, 1, -1); + } + } + + for (li = node->children; li != NULL; li = g_list_next(li)) + store_add_node(store, node->name ? &child : NULL, + (trg_torrent_file_node *) li->data); +} + +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; + gchar *file_name_base = g_path_get_basename(file_name); + trg_torrent_file *tor_data = trg_parse_torrent_file(file_name); + store_add_node(priv->store, NULL, tor_data->top_node); + trg_torrent_file_free(tor_data); + gtk_button_set_label(chooser, file_name_base); + g_free(file_name_base); + gtk_widget_set_sensitive(priv->file_list, TRUE); + } 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, + GConfClient * gcc) +{ + gchar *cwd = gtk_file_chooser_get_current_folder(c); + + if (cwd) { + gconf_client_set_string(gcc, TRG_GCONF_KEY_LAST_TORRENT_DIR, cwd, + NULL); + g_free(cwd); + } +} + +static GtkWidget *trg_torrent_add_dialog_generic(GtkWindow * parent, + GConfClient * gcc) +{ + 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 = + gconf_client_get_string(gcc, TRG_GCONF_KEY_LAST_TORRENT_DIR, NULL); + 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), + priv->client->gconf); + + 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), + priv->client->gconf); + trg_torrent_add_dialog_set_filenames(TRG_TORRENT_ADD_DIALOG(data), + priv->filenames); + } + + gtk_widget_destroy(GTK_WIDGET(d)); +} + +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); + + GtkWidget *t, *l; + gint row = 0; + gint col = 0; + + /* 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), + pref_get_start_paused(priv->client-> + gconf)); + + 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, col, col + 1, row, row + 1, GTK_FILL, + 0, 0, 0); + ++col; + + 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, col, col + 1, row, + row + 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); + + ++row; + col = 0; + 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, col, col + 1, row, row + 1, GTK_FILL, + 0, 0, 0); + ++col; + priv->dest_combo = trg_destination_folder_new(priv->client); + + /*if( !gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( w ), + data->downloadDir ) ) + g_warning( "couldn't select '%s'", data->downloadDir ); + list = get_recent_destinations( ); + for( walk = list; walk; walk = walk->next ) + gtk_file_chooser_add_shortcut_folder( GTK_FILE_CHOOSER( w ), walk->data, NULL ); + g_slist_free( list ); */ + gtk_table_attach(GTK_TABLE(t), priv->dest_combo, col, col + 1, row, + row + 1, ~0, 0, 0, 0); + gtk_label_set_mnemonic_widget(GTK_LABEL(l), priv->dest_combo); + + ++row; + col = 0; + gtk_widget_set_size_request(priv->file_list, 466u, 300u); + gtk_table_attach_defaults(GTK_TABLE(t), priv->file_list, col, col + 2, + row, row + 1); + + ++row; + col = 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, col, col + 1, row, row + 1, ~0, 0, 0, + 0); + ++col; + gtk_table_attach(GTK_TABLE(t), priv->priority_combo, col, col + 1, row, + row + 1, ~0, 0, 0, 0); + gtk_label_set_mnemonic_widget(GTK_LABEL(l), priv->priority_combo); + + ++row; + col = 0; + gtk_table_attach(GTK_TABLE(t), priv->paused_check, col, col + 2, row, + row + 1, 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_SHOW_OPTIONS, + g_param_spec_int("show-options", + "show-options", + "show-options", 0, 1, + 1, + 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, + trg_client * client, + GSList * filenames, + gboolean showOptions) +{ + return g_object_new(TRG_TYPE_TORRENT_ADD_DIALOG, + "filenames", filenames, + "show-options", showOptions ? 1 : 0, + "parent", parent, "client", client, NULL); +} + +void trg_torrent_add_dialog(TrgMainWindow * win, trg_client * client) +{ + GtkWidget *w; + GtkWidget *c; + + w = trg_torrent_add_dialog_generic(GTK_WINDOW(win), client->gconf); + + c = gtk_check_button_new_with_mnemonic(_("Show _options dialog")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(c), + gconf_client_get_bool_or_true(client-> + gconf, + TRG_GCONF_KEY_ADD_OPTIONS_DIALOG)); + 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), + client->gconf); + + if (showOptions) { + TrgTorrentAddDialog *dialog = + trg_torrent_add_dialog_new(win, client, l, showOptions); + + gtk_widget_show_all(GTK_WIDGET(dialog)); + } else { + struct add_torrent_threadfunc_args *args = + g_new(struct add_torrent_threadfunc_args, 1); + args->list = l; + args->cb_data = win; + args->client = client; + args->paused = pref_get_start_paused(client->gconf); + args->extraArgs = FALSE; + + 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..ab3d6cb --- /dev/null +++ b/src/trg-torrent-add-dialog.h @@ -0,0 +1,58 @@ +/* + * transmission-remote-gtk - Transmission RPC client for GTK + * 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. + */ + +#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; + +GType trg_torrent_add_dialog_get_type(void); + +TrgTorrentAddDialog *trg_torrent_add_dialog_new(TrgMainWindow * win, + trg_client * client, + GSList * filenames, + gint showOptions); +void trg_torrent_add_dialog(TrgMainWindow * win, trg_client * client); + +G_END_DECLS +#endif /* TRG_TORRENT_ADD_DIALOG_H_ */ diff --git a/src/trg-torrent-graph.c b/src/trg-torrent-graph.c index b5ce339..c5e4d4b 100644 --- a/src/trg-torrent-graph.c +++ b/src/trg-torrent-graph.c @@ -17,7 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -/* This graph drawing code was taken from gnome-system-monitor (load-graph.cpp) +/* + * 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. */ @@ -61,7 +62,7 @@ struct _TrgTorrentGraphPrivate { double graph_delx; guint graph_buffer_offset; - GdkColor colors[2]; + GdkColor colors[GRAPH_NUM_LINES]; float data_block[GRAPH_NUM_POINTS * GRAPH_NUM_LINES]; GList *points; diff --git a/src/trg-torrent-tree-view.c b/src/trg-torrent-tree-view.c index 7571adb..f6a99c6 100644 --- a/src/trg-torrent-tree-view.c +++ b/src/trg-torrent-tree-view.c @@ -61,6 +61,9 @@ static void trg_torrent_tree_view_init(TrgTorrentTreeView * tv) TORRENT_COLUMN_DOWNLOADED, -1); trg_tree_view_add_ratio_column(TRG_TREE_VIEW(tv), _("Ratio"), TORRENT_COLUMN_RATIO, -1); + gtk_tree_view_set_search_column(GTK_TREE_VIEW(tv), + TORRENT_COLUMN_NAME); + } gint get_first_selected(trg_client * client, TrgTorrentTreeView * view, @@ -86,6 +89,8 @@ gint get_first_selected(trg_client * client, TrgTorrentTreeView * view, TORRENT_COLUMN_UPDATESERIAL, &updateSerial, -1); + /* This is about to be removed and won't have valid JSON pointed + * to by the model. */ if (updateSerial < client->updateSerial) id = -1; } @@ -35,6 +35,24 @@ #include "util.h" #include "dispatch.h" +void add_file_id_to_array(JsonObject * args, 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); +} + +void g_str_slist_free(GSList * list) +{ + g_slist_foreach( list, (GFunc)g_free, NULL ); + g_slist_free(list); +} + GRegex *trg_uri_host_regex_new(void) { return g_regex_new("^[^:/?#]+:?//([^/?#]*)", G_REGEX_OPTIMIZE, 0, @@ -37,6 +37,8 @@ #define MEGABYTE_FACTOR ( 1024.0 * 1024.0 ) #define GIGABYTE_FACTOR ( 1024.0 * 1024.0 * 1024.0 ) +void add_file_id_to_array(JsonObject * args, 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); gboolean gconf_client_get_bool_or_true(GConfClient * gconf, gchar * key); |