source: trunk/gtk/util.c @ 3353

Last change on this file since 3353 was 3353, checked in by charles, 15 years ago

minor tweaks: (1) list None' instead of 0 b' for zero bytes, such as in UL speed or the "corrupted" field. (2) the properties icon is too horrible to keep -- use `torrent details' instead. (3) list active maintainers first in credits. (4) tweak the "about" dialog's client description a bit to better match the style of other gtk+ apps.

  • Property svn:keywords set to Date Rev Author Id
File size: 11.1 KB
Line 
1/******************************************************************************
2 * $Id: util.c 3353 2007-10-10 18:52:08Z charles $
3 *
4 * Copyright (c) 2005-2007 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <sys/types.h>
26#include <sys/stat.h>
27#include <errno.h>
28#include <signal.h>
29#include <stdarg.h>
30#include <string.h>
31#include <unistd.h>
32
33#include <gtk/gtk.h>
34#include <glib/gi18n.h>
35
36#include "tr_prefs.h"
37#include "tr_torrent.h"
38#include "conf.h"
39#include "util.h"
40
41#define BESTDECIMAL(d)          (10.0 > (d) ? 2 : (100.0 > (d) ? 1 : 0))
42
43static void
44errcb(GtkWidget *wind, int resp, gpointer data);
45
46int
47tr_strcmp( const char * a, const char * b )
48{
49    if( a && b ) return strcmp( a, b );
50    if( a ) return 1;
51    if( b ) return -1;
52    return 0;
53}
54
55static const char *sizestrs[] = {
56  N_("B"), N_("KiB"), N_("MiB"), N_("GiB"), N_("TiB"), N_("PiB"), N_("EiB"),
57};
58
59char *
60readablesize(guint64 size) {
61  int ii;
62  double small = size;
63
64  if( !size )
65    return g_strdup_printf( _("None") );
66
67  for(ii = 0; ii + 1 < ALEN(sizestrs) && 1024.0 <= small / 1024.0; ii++)
68    small /= 1024.0;
69
70  if(1024.0 <= small) {
71    small /= 1024.0;
72    ii++;
73  }
74
75  return g_strdup_printf("%.*f %s", BESTDECIMAL(small), small,
76                         gettext(sizestrs[ii]));
77}
78
79char *
80readablespeed (double KiBps)
81{
82  const guint64 bps = KiBps * 1024;
83  char * str = readablesize (bps);
84  char * ret = bps ? g_strdup_printf ("%s/s", str) : g_strdup( str );
85  g_free (str);
86  return ret;
87}
88 
89
90#define SECONDS(s)              ((s) % 60)
91#define MINUTES(s)              ((s) / 60 % 60)
92#define HOURS(s)                ((s) / 60 / 60 % 24)
93#define DAYS(s)                 ((s) / 60 / 60 / 24 % 7)
94
95char *
96readabletime(int secs) {
97  if(60 > secs)
98    return g_strdup_printf(_("%i %s"),
99      SECONDS(secs), ngettext("sec", "secs", SECONDS(secs)));
100  else if(60 * 60 > secs)
101    return g_strdup_printf(_("%i %s %i %s"),
102      MINUTES(secs), ngettext("min", "mins", MINUTES(secs)),
103      SECONDS(secs), ngettext("sec", "secs", SECONDS(secs)));
104  else if(60 * 60 * 24 > secs)
105    return g_strdup_printf(_("%i %s %i %s"),
106      HOURS(secs),   ngettext("hr", "hrs", HOURS(secs)),
107      MINUTES(secs), ngettext("min", "mins", MINUTES(secs)));
108  else
109    return g_strdup_printf(_("%i %s %i %s"),
110      DAYS(secs),    ngettext("day", "days", DAYS(secs)),
111      HOURS(secs),   ngettext("hr", "hrs", HOURS(secs)));
112}
113
114char *
115rfc822date (guint64 epoch_msec)
116{
117  const time_t secs = epoch_msec / 1000;
118  const struct tm tm = *localtime (&secs);
119  char buf[128];
120  strftime (buf, sizeof(buf), "%a, %d %b %Y %T %Z", &tm);
121  return g_strdup (buf);
122}
123
124char *
125ratiostr(guint64 down, guint64 up) {
126  double ratio;
127
128  if(0 == up && 0 == down)
129    return g_strdup(_("N/A"));
130
131  if(0 == down)
132    /* this is a UTF-8 infinity symbol */
133    return g_strdup("\xE2\x88\x9E");
134
135  ratio = (double)up / (double)down;
136
137  return g_strdup_printf("%.*f", BESTDECIMAL(ratio), ratio);
138}
139
140gboolean
141mkdir_p(const char *name, mode_t mode)
142{
143#if GLIB_CHECK_VERSION(2,8,0)
144    return !g_mkdir_with_parents( name, mode );
145#else
146    struct stat sb;
147    char *parent;
148    gboolean ret;
149    int oerrno;
150
151    if(0 != stat(name, &sb)) {
152      if(ENOENT != errno)
153        return FALSE;
154      parent = g_path_get_dirname(name);
155      ret = mkdir_p(parent, mode);
156      oerrno = errno;
157      g_free(parent);
158      errno = oerrno;
159      return (ret ? (0 == mkdir(name, mode)) : FALSE);
160    }
161
162    if(!S_ISDIR(sb.st_mode)) {
163      errno = ENOTDIR;
164      return FALSE;
165    }
166
167    return TRUE;
168#endif
169}
170
171GList *
172dupstrlist( GList * list )
173{
174    GList * ii, * ret;
175
176    ret = NULL;
177    for( ii = g_list_first( list ); NULL != ii; ii = ii->next )
178    {
179        ret = g_list_append( ret, g_strdup( ii->data ) );
180    }
181
182    return ret;
183}
184
185char *
186joinstrlist(GList *list, char *sep)
187{
188  GList *l;
189  GString *gstr = g_string_new (NULL);
190  for (l=list; l!=NULL; l=l->next) {
191    g_string_append (gstr, (char*)l->data);
192    if (l->next != NULL)
193      g_string_append (gstr, (sep));
194  }
195  return g_string_free (gstr, FALSE);
196}
197
198void
199freestrlist(GList *list)
200{
201  g_list_foreach (list, (GFunc)g_free, NULL);
202  g_list_free (list);
203}
204
205char *
206urldecode(const char *str, int len) {
207  int ii, jj;
208  char *ret;
209  char buf[3];
210
211  if(0 >= len)
212    len = strlen(str);
213
214  for(ii = jj = 0; ii < len; ii++, jj++)
215    if('%' == str[ii])
216      ii += 2;
217
218  ret = g_new(char, jj + 1);
219
220  buf[2] = '\0';
221  for(ii = jj = 0; ii < len; ii++, jj++) {
222    switch(str[ii]) {
223      case '%':
224        if(ii + 2 < len) {
225          buf[0] = str[ii+1];
226          buf[1] = str[ii+2];
227          ret[jj] = g_ascii_strtoull(buf, NULL, 16);
228        }
229        ii += 2;
230        break;
231      case '+':
232        ret[jj] = ' ';
233      default:
234        ret[jj] = str[ii];
235    }
236  }
237  ret[jj] = '\0';
238
239  return ret;
240}
241
242GList *
243checkfilenames(int argc, char **argv) {
244  char *pwd = g_get_current_dir();
245  int ii, cd;
246  char *dirstr, *filestr;
247  GList *ret = NULL;
248
249  for(ii = 0; ii < argc; ii++) {
250    dirstr = g_path_get_dirname(argv[ii]);
251    if(!g_path_is_absolute(argv[ii])) {
252      filestr = g_build_filename(pwd, dirstr, NULL);
253      g_free(dirstr);
254      dirstr = filestr;
255    }
256    cd = chdir(dirstr);
257    g_free(dirstr);
258    if(0 > cd)
259      continue;
260    dirstr = g_get_current_dir();
261    filestr = g_path_get_basename(argv[ii]);
262    ret = g_list_append(ret, g_build_filename(dirstr, filestr, NULL));
263    g_free(dirstr);
264    g_free(filestr);
265  }
266
267  chdir(pwd);
268  g_free(pwd);
269
270  return ret;
271}
272
273enum tr_torrent_action
274toraddaction( const char * action )
275{
276    if( NULL == action || 0 == strcmp( "copy", action ) )
277    {
278        return TR_TOR_COPY;
279    }
280    else if( 0 == strcmp( "move", action ) )
281    {
282        return TR_TOR_MOVE;
283    }
284    else
285    {
286        return TR_TOR_LEAVE;
287    }
288}
289
290const char *
291toractionname( enum tr_torrent_action action )
292{
293    switch( action )
294    {
295        case TR_TOR_COPY:
296            return "copy";
297        case TR_TOR_MOVE:
298            return "move";
299        default:
300            return "leave";
301    }
302}
303
304char *
305getdownloaddir( void )
306{
307    static char * wd = NULL;
308    char * dir = pref_string_get( PREF_KEY_DIR_DEFAULT );
309    if ( dir == NULL ) {
310        if( wd == NULL )
311            wd = g_get_current_dir();
312        dir = g_strdup( wd );
313    }
314    return dir;
315}
316
317/**
318 * don't use more than 50% the height of the screen, nor 80% the width.
319 * but don't be too small, either -- set the minimums to 500 x 300
320 */
321void
322sizingmagic( GtkWindow         * wind,
323             GtkScrolledWindow * scroll,
324             GtkPolicyType       hscroll,
325             GtkPolicyType       vscroll )
326{
327    int width;
328    int height;
329    GtkRequisition req;
330
331    GdkScreen * screen = gtk_widget_get_screen( GTK_WIDGET( wind ) );
332
333    gtk_scrolled_window_set_policy( scroll, GTK_POLICY_NEVER,
334                                            GTK_POLICY_NEVER );
335
336    gtk_widget_size_request( GTK_WIDGET( wind ), &req );
337    req.height = MAX( req.height, 300 );
338    height = MIN( req.height, gdk_screen_get_height( screen ) / 5 * 4 );
339
340    gtk_scrolled_window_set_policy( scroll, GTK_POLICY_NEVER, vscroll );
341    gtk_widget_size_request( GTK_WIDGET( wind ), &req );
342    req.width = MAX( req.width, 500 );
343    width = MIN( req.width, gdk_screen_get_width( screen ) / 2 );
344
345    gtk_window_set_default_size( wind, width, height );
346    gtk_scrolled_window_set_policy( scroll, hscroll, vscroll );
347}
348
349void
350errmsg( GtkWindow * wind, const char * format, ... )
351{
352    GtkWidget * dialog;
353    va_list     ap;
354
355    va_start( ap, format );
356    dialog = verrmsg_full( wind, NULL, NULL, format, ap );
357    va_end( ap );
358
359    if( NULL != wind && !GTK_WIDGET_MAPPED( GTK_WIDGET( wind ) ) )
360    {
361        g_signal_connect_swapped( wind, "map",
362                                  G_CALLBACK( gtk_widget_show ), dialog );
363    }
364    else
365    {
366        gtk_widget_show( dialog );
367    }
368}
369
370GtkWidget *
371errmsg_full( GtkWindow * wind, callbackfunc_t func, void * data,
372             const char * format, ... )
373{
374    GtkWidget * dialog;
375    va_list     ap;
376
377    va_start( ap, format );
378    dialog = verrmsg_full( wind, func, data, format, ap );
379    va_end( ap );
380
381    return dialog;
382}
383
384GtkWidget *
385verrmsg_full( GtkWindow * wind, callbackfunc_t func, void * data,
386              const char * format, va_list ap )
387{
388  GtkWidget *dialog;
389  char *msg;
390  GList *funcdata;
391
392  msg = g_strdup_vprintf(format, ap);
393
394  if(NULL == wind)
395    dialog = gtk_message_dialog_new(
396      NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", msg);
397  else
398    dialog = gtk_message_dialog_new(wind,
399      GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
400      GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", msg);
401
402  if(NULL == func)
403    funcdata = NULL;
404  else
405      funcdata = g_list_append(g_list_append(NULL, (void *) func), data);
406  g_signal_connect(dialog, "response", G_CALLBACK(errcb), funcdata);
407  g_free(msg);
408
409  return dialog;
410}
411
412static void
413errcb(GtkWidget *widget, int resp SHUTUP, gpointer data) {
414  GList *funcdata;
415  callbackfunc_t func;
416
417  if(NULL != data) {
418    funcdata = g_list_first(data);
419    func = (callbackfunc_t) funcdata->data;
420    data = funcdata->next->data;
421    func(data);
422    g_list_free(funcdata);
423  }
424
425  gtk_widget_destroy(widget);
426}
427
428typedef void (PopupFunc)(GtkWidget*, GdkEventButton*); 
429
430/* pop up the context menu if a user right-clicks.
431   if the row they right-click on isn't selected, select it. */
432
433gboolean
434on_tree_view_button_pressed (GtkWidget       * view,
435                             GdkEventButton  * event,
436                             gpointer          func)
437{
438  GtkTreeView * tv = GTK_TREE_VIEW( view );
439
440  if (event->type == GDK_BUTTON_PRESS  &&  event->button == 3)
441  {
442    GtkTreeSelection * selection = gtk_tree_view_get_selection(tv);
443    GtkTreePath *path;
444    if (gtk_tree_view_get_path_at_pos (tv,
445                                       (gint) event->x,
446                                       (gint) event->y,
447                                       &path, NULL, NULL, NULL))
448    {
449      if (!gtk_tree_selection_path_is_selected (selection, path))
450      {
451        gtk_tree_selection_unselect_all (selection);
452        gtk_tree_selection_select_path (selection, path);
453      }
454      gtk_tree_path_free(path);
455    }
456   
457    ((PopupFunc*)func)(view, event);
458
459    return TRUE;
460  }
461
462  return FALSE;
463}
464
Note: See TracBrowser for help on using the repository browser.