1 /*
2  * This file Copyright (C) 2007-2014 Mnemosyne LLC
3  *
4  * It may be used under the GNU GPL versions 2 or 3
5  * or any future license endorsed by Mnemosyne LLC.
6  *
7  */
8 
9 #include <limits.h> /* INT_MAX */
10 #include <gtk/gtk.h>
11 #include <glib/gi18n.h>
12 #include <libtransmission/transmission.h>
13 #include <libtransmission/utils.h> /* tr_truncd() */
14 #include "hig.h"
15 #include "icons.h"
16 #include "torrent-cell-renderer.h"
17 #include "util.h"
18 
19 /* #define TEST_RTL */
20 
21 enum
22 {
23     P_TORRENT = 1,
24     P_UPLOAD_SPEED,
25     P_DOWNLOAD_SPEED,
26     P_BAR_HEIGHT,
27     P_COMPACT
28 };
29 
30 #define DEFAULT_BAR_HEIGHT 12
31 #define SMALL_SCALE 0.9
32 #define COMPACT_ICON_SIZE GTK_ICON_SIZE_MENU
33 #define FULL_ICON_SIZE GTK_ICON_SIZE_DND
34 
35 /***
36 ****
37 ***/
38 
getProgressString(GString * gstr,tr_torrent const * tor,tr_info const * info,tr_stat const * st)39 static void getProgressString(GString* gstr, tr_torrent const* tor, tr_info const* info, tr_stat const* st)
40 {
41     bool const isDone = st->leftUntilDone == 0;
42     uint64_t const haveTotal = st->haveUnchecked + st->haveValid;
43     bool const isSeed = st->haveValid >= info->totalSize;
44     char buf1[32];
45     char buf2[32];
46     char buf3[32];
47     char buf4[32];
48     char buf5[32];
49     char buf6[32];
50     double seedRatio;
51     bool const hasSeedRatio = tr_torrentGetSeedRatio(tor, &seedRatio);
52 
53     if (!isDone) /* downloading */
54     {
55         g_string_append_printf(gstr,
56             /* %1$s is how much we've got,
57                %2$s is how much we'll have when done,
58                %3$s%% is a percentage of the two */
59             _("%1$s of %2$s (%3$s%%)"),
60             tr_strlsize(buf1, haveTotal, sizeof(buf1)),
61             tr_strlsize(buf2, st->sizeWhenDone, sizeof(buf2)),
62             tr_strlpercent(buf3, st->percentDone * 100.0, sizeof(buf3)));
63     }
64     else if (!isSeed) /* partial seeds */
65     {
66         if (hasSeedRatio)
67         {
68             g_string_append_printf(gstr,
69                 /* %1$s is how much we've got,
70                    %2$s is the torrent's total size,
71                    %3$s%% is a percentage of the two,
72                    %4$s is how much we've uploaded,
73                    %5$s is our upload-to-download ratio,
74                    %6$s is the ratio we want to reach before we stop uploading */
75                 _("%1$s of %2$s (%3$s%%), uploaded %4$s (Ratio: %5$s Goal: %6$s)"),
76                 tr_strlsize(buf1, haveTotal, sizeof(buf1)),
77                 tr_strlsize(buf2, info->totalSize, sizeof(buf2)),
78                 tr_strlpercent(buf3, st->percentComplete * 100.0, sizeof(buf3)),
79                 tr_strlsize(buf4, st->uploadedEver, sizeof(buf4)),
80                 tr_strlratio(buf5, st->ratio, sizeof(buf5)),
81                 tr_strlratio(buf6, seedRatio, sizeof(buf6)));
82         }
83         else
84         {
85             g_string_append_printf(gstr,
86                 /* %1$s is how much we've got,
87                    %2$s is the torrent's total size,
88                    %3$s%% is a percentage of the two,
89                    %4$s is how much we've uploaded,
90                    %5$s is our upload-to-download ratio */
91                 _("%1$s of %2$s (%3$s%%), uploaded %4$s (Ratio: %5$s)"),
92                 tr_strlsize(buf1, haveTotal, sizeof(buf1)),
93                 tr_strlsize(buf2, info->totalSize, sizeof(buf2)),
94                 tr_strlpercent(buf3, st->percentComplete * 100.0, sizeof(buf3)),
95                 tr_strlsize(buf4, st->uploadedEver, sizeof(buf4)),
96                 tr_strlratio(buf5, st->ratio, sizeof(buf5)));
97         }
98     }
99     else /* seeding */
100     {
101         if (hasSeedRatio)
102         {
103             g_string_append_printf(gstr,
104                 /* %1$s is the torrent's total size,
105                    %2$s is how much we've uploaded,
106                    %3$s is our upload-to-download ratio,
107                    %4$s is the ratio we want to reach before we stop uploading */
108                 _("%1$s, uploaded %2$s (Ratio: %3$s Goal: %4$s)"),
109                 tr_strlsize(buf1, info->totalSize, sizeof(buf1)),
110                 tr_strlsize(buf2, st->uploadedEver, sizeof(buf2)),
111                 tr_strlratio(buf3, st->ratio, sizeof(buf3)),
112                 tr_strlratio(buf4, seedRatio, sizeof(buf4)));
113         }
114         else /* seeding w/o a ratio */
115         {
116             g_string_append_printf(gstr,
117                 /* %1$s is the torrent's total size,
118                    %2$s is how much we've uploaded,
119                    %3$s is our upload-to-download ratio */
120                 _("%1$s, uploaded %2$s (Ratio: %3$s)"),
121                 tr_strlsize(buf1, info->totalSize, sizeof(buf1)),
122                 tr_strlsize(buf2, st->uploadedEver, sizeof(buf2)),
123                 tr_strlratio(buf3, st->ratio, sizeof(buf3)));
124         }
125     }
126 
127     /* add time when downloading */
128     if (st->activity == TR_STATUS_DOWNLOAD || (hasSeedRatio && st->activity == TR_STATUS_SEED))
129     {
130         int const eta = st->eta;
131         g_string_append(gstr, " - ");
132 
133         if (eta < 0)
134         {
135             g_string_append(gstr, _("Remaining time unknown"));
136         }
137         else
138         {
139             char timestr[128];
140             tr_strltime(timestr, eta, sizeof(timestr));
141             /* time remaining */
142             g_string_append_printf(gstr, _("%s remaining"), timestr);
143         }
144     }
145 }
146 
getShortTransferString(tr_torrent const * tor,tr_stat const * st,double uploadSpeed_KBps,double downloadSpeed_KBps,char * buf,size_t buflen)147 static char* getShortTransferString(tr_torrent const* tor, tr_stat const* st, double uploadSpeed_KBps,
148     double downloadSpeed_KBps, char* buf, size_t buflen)
149 {
150     bool const haveMeta = tr_torrentHasMetadata(tor);
151     bool const haveUp = haveMeta && st->peersGettingFromUs > 0;
152     bool const haveDown = haveMeta && (st->peersSendingToUs > 0 || st->webseedsSendingToUs > 0);
153 
154     if (haveDown)
155     {
156         char dnStr[32];
157         char upStr[32];
158         tr_formatter_speed_KBps(dnStr, downloadSpeed_KBps, sizeof(dnStr));
159         tr_formatter_speed_KBps(upStr, uploadSpeed_KBps, sizeof(upStr));
160 
161         /* down speed, down symbol, up speed, up symbol */
162         g_snprintf(buf, buflen, _("%1$s %2$s  %3$s %4$s"), dnStr, gtr_get_unicode_string(GTR_UNICODE_DOWN), upStr,
163             gtr_get_unicode_string(GTR_UNICODE_UP));
164     }
165     else if (haveUp)
166     {
167         char upStr[32];
168         tr_formatter_speed_KBps(upStr, uploadSpeed_KBps, sizeof(upStr));
169 
170         /* up speed, up symbol */
171         g_snprintf(buf, buflen, _("%1$s  %2$s"), upStr, gtr_get_unicode_string(GTR_UNICODE_UP));
172     }
173     else if (st->isStalled)
174     {
175         g_strlcpy(buf, _("Stalled"), buflen);
176     }
177     else
178     {
179         *buf = '\0';
180     }
181 
182     return buf;
183 }
184 
getShortStatusString(GString * gstr,tr_torrent const * tor,tr_stat const * st,double uploadSpeed_KBps,double downloadSpeed_KBps)185 static void getShortStatusString(GString* gstr, tr_torrent const* tor, tr_stat const* st, double uploadSpeed_KBps,
186     double downloadSpeed_KBps)
187 {
188     switch (st->activity)
189     {
190     case TR_STATUS_STOPPED:
191         g_string_append(gstr, st->finished ? _("Finished") : _("Paused"));
192         break;
193 
194     case TR_STATUS_CHECK_WAIT:
195         g_string_append(gstr, _("Queued for verification"));
196         break;
197 
198     case TR_STATUS_DOWNLOAD_WAIT:
199         g_string_append(gstr, _("Queued for download"));
200         break;
201 
202     case TR_STATUS_SEED_WAIT:
203         g_string_append(gstr, _("Queued for seeding"));
204         break;
205 
206     case TR_STATUS_CHECK:
207         g_string_append_printf(gstr, _("Verifying local data (%.1f%% tested)"),
208             tr_truncd(st->recheckProgress * 100.0, 1));
209         break;
210 
211     case TR_STATUS_DOWNLOAD:
212     case TR_STATUS_SEED:
213         {
214             char speedStr[64];
215             char ratioStr[64];
216             tr_strlratio(ratioStr, st->ratio, sizeof(ratioStr));
217             getShortTransferString(tor, st, uploadSpeed_KBps, downloadSpeed_KBps, speedStr, sizeof(speedStr));
218             /* download/upload speed, ratio */
219             g_string_append_printf(gstr, "%1$s  Ratio: %2$s", speedStr, ratioStr);
220             break;
221         }
222 
223     default:
224         break;
225     }
226 }
227 
getStatusString(GString * gstr,tr_torrent const * tor,tr_stat const * st,double const uploadSpeed_KBps,double const downloadSpeed_KBps)228 static void getStatusString(GString* gstr, tr_torrent const* tor, tr_stat const* st, double const uploadSpeed_KBps,
229     double const downloadSpeed_KBps)
230 {
231     if (st->error != 0)
232     {
233         char const* fmt[] =
234         {
235             NULL,
236             N_("Tracker gave a warning: \"%s\""),
237             N_("Tracker gave an error: \"%s\""),
238             N_("Error: %s")
239         };
240 
241         g_string_append_printf(gstr, _(fmt[st->error]), st->errorString);
242     }
243     else
244     {
245         switch (st->activity)
246         {
247         case TR_STATUS_STOPPED:
248         case TR_STATUS_CHECK_WAIT:
249         case TR_STATUS_CHECK:
250         case TR_STATUS_DOWNLOAD_WAIT:
251         case TR_STATUS_SEED_WAIT:
252             {
253                 getShortStatusString(gstr, tor, st, uploadSpeed_KBps, downloadSpeed_KBps);
254                 break;
255             }
256 
257         case TR_STATUS_DOWNLOAD:
258             {
259                 if (!tr_torrentHasMetadata(tor))
260                 {
261                     /* Downloading metadata from 2 peer (s)(50% done) */
262                     g_string_append_printf(gstr, _("Downloading metadata from %1$'d %2$s (%3$d%% done)"), st->peersConnected,
263                         ngettext("peer", "peers", st->peersConnected), (int)(100.0 * st->metadataPercentComplete));
264                 }
265                 else if (st->peersSendingToUs != 0 && st->webseedsSendingToUs != 0)
266                 {
267                     /* Downloading from 2 of 3 peer (s) and 2 webseed (s) */
268                     g_string_append_printf(gstr, _("Downloading from %1$'d of %2$'d %3$s and %4$'d %5$s"), st->peersSendingToUs,
269                         st->peersConnected, ngettext("peer", "peers", st->peersConnected), st->webseedsSendingToUs,
270                         ngettext("web seed", "web seeds", st->webseedsSendingToUs));
271                 }
272                 else if (st->webseedsSendingToUs != 0)
273                 {
274                     /* Downloading from 3 web seed (s) */
275                     g_string_append_printf(gstr, _("Downloading from %1$'d %2$s"), st->webseedsSendingToUs,
276                         ngettext("web seed", "web seeds", st->webseedsSendingToUs));
277                 }
278                 else
279                 {
280                     /* Downloading from 2 of 3 peer (s) */
281                     g_string_append_printf(gstr, _("Downloading from %1$'d of %2$'d %3$s"), st->peersSendingToUs,
282                         st->peersConnected, ngettext("peer", "peers", st->peersConnected));
283                 }
284 
285                 break;
286             }
287 
288         case TR_STATUS_SEED:
289             g_string_append_printf(gstr, ngettext("Seeding to %1$'d of %2$'d connected peer",
290                 "Seeding to %1$'d of %2$'d connected peers", st->peersConnected), st->peersGettingFromUs, st->peersConnected);
291             break;
292         }
293     }
294 
295     if (st->activity != TR_STATUS_CHECK_WAIT && st->activity != TR_STATUS_CHECK && st->activity != TR_STATUS_DOWNLOAD_WAIT &&
296         st->activity != TR_STATUS_SEED_WAIT && st->activity != TR_STATUS_STOPPED)
297     {
298         char buf[256];
299         getShortTransferString(tor, st, uploadSpeed_KBps, downloadSpeed_KBps, buf, sizeof(buf));
300 
301         if (!tr_str_is_empty(buf))
302         {
303             g_string_append_printf(gstr, " - %s", buf);
304         }
305     }
306 }
307 
308 /***
309 ****
310 ***/
311 
312 typedef struct TorrentCellRendererPrivate
313 {
314     tr_torrent* tor;
315     GtkCellRenderer* text_renderer;
316     GtkCellRenderer* progress_renderer;
317     GtkCellRenderer* icon_renderer;
318     GString* gstr1;
319     GString* gstr2;
320     int bar_height;
321 
322     /* Use this instead of tr_stat.pieceUploadSpeed so that the model can
323        control when the speed displays get updated. This is done to keep
324        the individual torrents' speeds and the status bar's overall speed
325        in sync even if they refresh at slightly different times */
326     double upload_speed_KBps;
327 
328     /* @see upload_speed_Bps */
329     double download_speed_KBps;
330 
331     gboolean compact;
332 }
333 TorrentCellRendererPrivate;
334 
335 /***
336 ****
337 ***/
338 
get_icon(tr_torrent const * tor,GtkIconSize icon_size,GtkWidget * for_widget)339 static GdkPixbuf* get_icon(tr_torrent const* tor, GtkIconSize icon_size, GtkWidget* for_widget)
340 {
341     char const* mime_type;
342     tr_info const* info = tr_torrentInfo(tor);
343 
344     if (info->fileCount == 0)
345     {
346         mime_type = UNKNOWN_MIME_TYPE;
347     }
348     else if (info->fileCount > 1)
349     {
350         mime_type = DIRECTORY_MIME_TYPE;
351     }
352     else if (strchr(info->files[0].name, '/') != NULL)
353     {
354         mime_type = DIRECTORY_MIME_TYPE;
355     }
356     else
357     {
358         mime_type = gtr_get_mime_type_from_filename(info->files[0].name);
359     }
360 
361     return gtr_get_mime_type_icon(mime_type, icon_size, for_widget);
362 }
363 
364 /***
365 ****
366 ***/
367 
gtr_cell_renderer_get_preferred_size(GtkCellRenderer * renderer,GtkWidget * widget,GtkRequisition * minimum_size,GtkRequisition * natural_size)368 static void gtr_cell_renderer_get_preferred_size(GtkCellRenderer* renderer, GtkWidget* widget, GtkRequisition* minimum_size,
369     GtkRequisition* natural_size)
370 {
371     gtk_cell_renderer_get_preferred_size(renderer, widget, minimum_size, natural_size);
372 }
373 
get_size_compact(TorrentCellRenderer * cell,GtkWidget * widget,gint * width,gint * height)374 static void get_size_compact(TorrentCellRenderer* cell, GtkWidget* widget, gint* width, gint* height)
375 {
376     int xpad;
377     int ypad;
378     GtkRequisition icon_size;
379     GtkRequisition name_size;
380     GtkRequisition stat_size;
381     char const* name;
382     GdkPixbuf* icon;
383 
384     struct TorrentCellRendererPrivate* p = cell->priv;
385     tr_torrent const* tor = p->tor;
386     tr_stat const* st = tr_torrentStatCached((tr_torrent*)tor);
387     GString* gstr_stat = p->gstr1;
388 
389     icon = get_icon(tor, COMPACT_ICON_SIZE, widget);
390     name = tr_torrentName(tor);
391     g_string_truncate(gstr_stat, 0);
392     getShortStatusString(gstr_stat, tor, st, p->upload_speed_KBps, p->download_speed_KBps);
393     gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
394 
395     /* get the idealized cell dimensions */
396     g_object_set(p->icon_renderer, "pixbuf", icon, NULL);
397     gtr_cell_renderer_get_preferred_size(p->icon_renderer, widget, NULL, &icon_size);
398     g_object_set(p->text_renderer, "text", name, "ellipsize", PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL);
399     gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &name_size);
400     g_object_set(p->text_renderer, "text", gstr_stat->str, "scale", SMALL_SCALE, NULL);
401     gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &stat_size);
402 
403     /**
404     *** LAYOUT
405     **/
406 
407 #define BAR_WIDTH 50
408 
409     if (width != NULL)
410     {
411         *width = xpad * 2 + icon_size.width + GUI_PAD + BAR_WIDTH + GUI_PAD + stat_size.width;
412     }
413 
414     if (height != NULL)
415     {
416         *height = ypad * 2 + MAX(name_size.height, p->bar_height);
417     }
418 
419     /* cleanup */
420     g_object_unref(icon);
421 }
422 
get_size_full(TorrentCellRenderer * cell,GtkWidget * widget,gint * width,gint * height)423 static void get_size_full(TorrentCellRenderer* cell, GtkWidget* widget, gint* width, gint* height)
424 {
425     int xpad;
426     int ypad;
427     GtkRequisition icon_size;
428     GtkRequisition name_size;
429     GtkRequisition stat_size;
430     GtkRequisition prog_size;
431     char const* name;
432     GdkPixbuf* icon;
433 
434     struct TorrentCellRendererPrivate* p = cell->priv;
435     tr_torrent const* tor = p->tor;
436     tr_stat const* st = tr_torrentStatCached((tr_torrent*)tor);
437     tr_info const* inf = tr_torrentInfo(tor);
438     GString* gstr_prog = p->gstr1;
439     GString* gstr_stat = p->gstr2;
440 
441     icon = get_icon(tor, FULL_ICON_SIZE, widget);
442     name = tr_torrentName(tor);
443     g_string_truncate(gstr_stat, 0);
444     getStatusString(gstr_stat, tor, st, p->upload_speed_KBps, p->download_speed_KBps);
445     g_string_truncate(gstr_prog, 0);
446     getProgressString(gstr_prog, tor, inf, st);
447     gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
448 
449     /* get the idealized cell dimensions */
450     g_object_set(p->icon_renderer, "pixbuf", icon, NULL);
451     gtr_cell_renderer_get_preferred_size(p->icon_renderer, widget, NULL, &icon_size);
452     g_object_set(p->text_renderer, "text", name, "weight", PANGO_WEIGHT_BOLD, "scale", 1.0, "ellipsize", PANGO_ELLIPSIZE_NONE,
453         NULL);
454     gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &name_size);
455     g_object_set(p->text_renderer, "text", gstr_prog->str, "weight", PANGO_WEIGHT_NORMAL, "scale", SMALL_SCALE, NULL);
456     gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &prog_size);
457     g_object_set(p->text_renderer, "text", gstr_stat->str, NULL);
458     gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &stat_size);
459 
460     /**
461     *** LAYOUT
462     **/
463 
464     if (width != NULL)
465     {
466         *width = xpad * 2 + icon_size.width + GUI_PAD + MAX(prog_size.width, stat_size.width);
467     }
468 
469     if (height != NULL)
470     {
471         *height = ypad * 2 + name_size.height + prog_size.height + GUI_PAD_SMALL + p->bar_height + GUI_PAD_SMALL +
472             stat_size.height;
473     }
474 
475     /* cleanup */
476     g_object_unref(icon);
477 }
478 
torrent_cell_renderer_get_size(GtkCellRenderer * cell,GtkWidget * widget,GdkRectangle const * cell_area,gint * x_offset,gint * y_offset,gint * width,gint * height)479 static void torrent_cell_renderer_get_size(GtkCellRenderer* cell, GtkWidget* widget, GdkRectangle const* cell_area,
480     gint* x_offset, gint* y_offset, gint* width, gint* height)
481 {
482     TorrentCellRenderer* self = TORRENT_CELL_RENDERER(cell);
483 
484     if (self != NULL && self->priv->tor != NULL)
485     {
486         int w;
487         int h;
488         struct TorrentCellRendererPrivate* p = self->priv;
489 
490         if (p->compact)
491         {
492             get_size_compact(TORRENT_CELL_RENDERER(cell), widget, &w, &h);
493         }
494         else
495         {
496             get_size_full(TORRENT_CELL_RENDERER(cell), widget, &w, &h);
497         }
498 
499         if (width != NULL)
500         {
501             *width = w;
502         }
503 
504         if (height != NULL)
505         {
506             *height = h;
507         }
508 
509         if (x_offset != NULL)
510         {
511             *x_offset = cell_area ? cell_area->x : 0;
512         }
513 
514         if (y_offset != NULL)
515         {
516             int xpad;
517             int ypad;
518             gtk_cell_renderer_get_padding(cell, &xpad, &ypad);
519             *y_offset = cell_area ? (int)((cell_area->height - (ypad * 2 + h)) / 2.0) : 0;
520         }
521     }
522 }
523 
524 typedef GdkRGBA GtrColor;
525 #define FOREGROUND_COLOR_KEY "foreground-rgba"
526 
get_text_color(GtkWidget * w,tr_stat const * st,GtrColor * setme)527 static void get_text_color(GtkWidget* w, tr_stat const* st, GtrColor* setme)
528 {
529     static GdkRGBA const red = { 1.0, 0, 0, 0 };
530 
531     if (st->error != 0)
532     {
533         *setme = red;
534     }
535     else if (st->activity == TR_STATUS_STOPPED)
536     {
537         gtk_style_context_get_color(gtk_widget_get_style_context(w), GTK_STATE_FLAG_INSENSITIVE, setme);
538     }
539     else
540     {
541         gtk_style_context_get_color(gtk_widget_get_style_context(w), GTK_STATE_FLAG_NORMAL, setme);
542     }
543 }
544 
get_percent_done(tr_torrent const * tor,tr_stat const * st,bool * seed)545 static double get_percent_done(tr_torrent const* tor, tr_stat const* st, bool* seed)
546 {
547     double d;
548 
549     if (st->activity == TR_STATUS_SEED && tr_torrentGetSeedRatio(tor, &d))
550     {
551         *seed = true;
552         d = MAX(0.0, st->seedRatioPercentDone);
553     }
554     else
555     {
556         *seed = false;
557         d = MAX(0.0, st->percentDone);
558     }
559 
560     return d;
561 }
562 
563 typedef cairo_t GtrDrawable;
564 
gtr_cell_renderer_render(GtkCellRenderer * renderer,GtrDrawable * drawable,GtkWidget * widget,GdkRectangle const * area,GtkCellRendererState flags)565 static void gtr_cell_renderer_render(GtkCellRenderer* renderer, GtrDrawable* drawable, GtkWidget* widget,
566     GdkRectangle const* area, GtkCellRendererState flags)
567 {
568     gtk_cell_renderer_render(renderer, drawable, widget, area, area, flags);
569 }
570 
render_compact(TorrentCellRenderer * cell,GtrDrawable * window,GtkWidget * widget,GdkRectangle const * background_area,GdkRectangle const * cell_area UNUSED,GtkCellRendererState flags)571 static void render_compact(TorrentCellRenderer* cell, GtrDrawable* window, GtkWidget* widget,
572     GdkRectangle const* background_area, GdkRectangle const* cell_area UNUSED,
573     GtkCellRendererState flags)
574 {
575     int xpad;
576     int ypad;
577     GtkRequisition size;
578     GdkRectangle icon_area;
579     GdkRectangle name_area;
580     GdkRectangle stat_area;
581     GdkRectangle prog_area;
582     GdkRectangle fill_area;
583     char const* name;
584     GdkPixbuf* icon;
585     GtrColor text_color;
586     bool seed;
587 
588     struct TorrentCellRendererPrivate* p = cell->priv;
589     tr_torrent const* tor = p->tor;
590     tr_stat const* st = tr_torrentStatCached((tr_torrent*)tor);
591     gboolean const active = st->activity != TR_STATUS_STOPPED && st->activity != TR_STATUS_DOWNLOAD_WAIT &&
592         st->activity != TR_STATUS_SEED_WAIT;
593     double const percentDone = get_percent_done(tor, st, &seed);
594     gboolean const sensitive = active || st->error;
595     GString* gstr_stat = p->gstr1;
596 
597     icon = get_icon(tor, COMPACT_ICON_SIZE, widget);
598     name = tr_torrentName(tor);
599     g_string_truncate(gstr_stat, 0);
600     getShortStatusString(gstr_stat, tor, st, p->upload_speed_KBps, p->download_speed_KBps);
601     gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
602     get_text_color(widget, st, &text_color);
603 
604     fill_area = *background_area;
605     fill_area.x += xpad;
606     fill_area.y += ypad;
607     fill_area.width -= xpad * 2;
608     fill_area.height -= ypad * 2;
609     icon_area = name_area = stat_area = prog_area = fill_area;
610 
611     g_object_set(p->icon_renderer, "pixbuf", icon, NULL);
612     gtr_cell_renderer_get_preferred_size(p->icon_renderer, widget, NULL, &size);
613     icon_area.width = size.width;
614     g_object_set(p->text_renderer, "text", name, "ellipsize", PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL);
615     gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &size);
616     name_area.width = size.width;
617     g_object_set(p->text_renderer, "text", gstr_stat->str, "scale", SMALL_SCALE, NULL);
618     gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &size);
619     stat_area.width = size.width;
620 
621     icon_area.x = fill_area.x;
622     prog_area.x = fill_area.x + fill_area.width - BAR_WIDTH;
623     prog_area.width = BAR_WIDTH;
624     stat_area.x = prog_area.x - GUI_PAD - stat_area.width;
625     name_area.x = icon_area.x + icon_area.width + GUI_PAD;
626     name_area.y = fill_area.y;
627     name_area.width = stat_area.x - GUI_PAD - name_area.x;
628 
629     /**
630     *** RENDER
631     **/
632 
633     g_object_set(p->icon_renderer, "pixbuf", icon, "sensitive", sensitive, NULL);
634     gtr_cell_renderer_render(p->icon_renderer, window, widget, &icon_area, flags);
635     g_object_set(p->progress_renderer, "value", (int)(percentDone * 100.0), "text", NULL, "sensitive", sensitive, NULL);
636     gtr_cell_renderer_render(p->progress_renderer, window, widget, &prog_area, flags);
637     g_object_set(p->text_renderer, "text", gstr_stat->str, "scale", SMALL_SCALE, "ellipsize", PANGO_ELLIPSIZE_END,
638         FOREGROUND_COLOR_KEY, &text_color, NULL);
639     gtr_cell_renderer_render(p->text_renderer, window, widget, &stat_area, flags);
640     g_object_set(p->text_renderer, "text", name, "scale", 1.0, FOREGROUND_COLOR_KEY, &text_color, NULL);
641     gtr_cell_renderer_render(p->text_renderer, window, widget, &name_area, flags);
642 
643     /* cleanup */
644     g_object_unref(icon);
645 }
646 
render_full(TorrentCellRenderer * cell,GtrDrawable * window,GtkWidget * widget,GdkRectangle const * background_area,GdkRectangle const * cell_area UNUSED,GtkCellRendererState flags)647 static void render_full(TorrentCellRenderer* cell, GtrDrawable* window, GtkWidget* widget, GdkRectangle const* background_area,
648     GdkRectangle const* cell_area UNUSED, GtkCellRendererState flags)
649 {
650     int xpad;
651     int ypad;
652     GtkRequisition size;
653     GdkRectangle fill_area;
654     GdkRectangle icon_area;
655     GdkRectangle name_area;
656     GdkRectangle stat_area;
657     GdkRectangle prog_area;
658     GdkRectangle prct_area;
659     char const* name;
660     GdkPixbuf* icon;
661     GtrColor text_color;
662     bool seed;
663 
664     struct TorrentCellRendererPrivate* p = cell->priv;
665     tr_torrent const* tor = p->tor;
666     tr_stat const* st = tr_torrentStatCached((tr_torrent*)tor);
667     tr_info const* inf = tr_torrentInfo(tor);
668     gboolean const active = st->activity != TR_STATUS_STOPPED && st->activity != TR_STATUS_DOWNLOAD_WAIT &&
669         st->activity != TR_STATUS_SEED_WAIT;
670     double const percentDone = get_percent_done(tor, st, &seed);
671     gboolean const sensitive = active || st->error;
672     GString* gstr_prog = p->gstr1;
673     GString* gstr_stat = p->gstr2;
674 
675     icon = get_icon(tor, FULL_ICON_SIZE, widget);
676     name = tr_torrentName(tor);
677     g_string_truncate(gstr_prog, 0);
678     getProgressString(gstr_prog, tor, inf, st);
679     g_string_truncate(gstr_stat, 0);
680     getStatusString(gstr_stat, tor, st, p->upload_speed_KBps, p->download_speed_KBps);
681     gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
682     get_text_color(widget, st, &text_color);
683 
684     /* get the idealized cell dimensions */
685     g_object_set(p->icon_renderer, "pixbuf", icon, NULL);
686     gtr_cell_renderer_get_preferred_size(p->icon_renderer, widget, NULL, &size);
687     icon_area.width = size.width;
688     icon_area.height = size.height;
689     g_object_set(p->text_renderer, "text", name, "weight", PANGO_WEIGHT_BOLD, "ellipsize", PANGO_ELLIPSIZE_NONE, "scale", 1.0,
690         NULL);
691     gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &size);
692     name_area.width = size.width;
693     name_area.height = size.height;
694     g_object_set(p->text_renderer, "text", gstr_prog->str, "weight", PANGO_WEIGHT_NORMAL, "scale", SMALL_SCALE, NULL);
695     gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &size);
696     prog_area.width = size.width;
697     prog_area.height = size.height;
698     g_object_set(p->text_renderer, "text", gstr_stat->str, NULL);
699     gtr_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL, &size);
700     stat_area.width = size.width;
701     stat_area.height = size.height;
702 
703     /**
704     *** LAYOUT
705     **/
706 
707     fill_area = *background_area;
708     fill_area.x += xpad;
709     fill_area.y += ypad;
710     fill_area.width -= xpad * 2;
711     fill_area.height -= ypad * 2;
712 
713     /* icon */
714     icon_area.x = fill_area.x;
715     icon_area.y = fill_area.y + (fill_area.height - icon_area.height) / 2;
716 
717     /* name */
718     name_area.x = icon_area.x + icon_area.width + GUI_PAD;
719     name_area.y = fill_area.y;
720     name_area.width = fill_area.width - GUI_PAD - icon_area.width - GUI_PAD_SMALL;
721 
722     /* prog */
723     prog_area.x = name_area.x;
724     prog_area.y = name_area.y + name_area.height;
725     prog_area.width = name_area.width;
726 
727     /* progressbar */
728     prct_area.x = prog_area.x;
729     prct_area.y = prog_area.y + prog_area.height + GUI_PAD_SMALL;
730     prct_area.width = prog_area.width;
731     prct_area.height = p->bar_height;
732 
733     /* status */
734     stat_area.x = prct_area.x;
735     stat_area.y = prct_area.y + prct_area.height + GUI_PAD_SMALL;
736     stat_area.width = prct_area.width;
737 
738     /**
739     *** RENDER
740     **/
741 
742     g_object_set(p->icon_renderer, "pixbuf", icon, "sensitive", sensitive, NULL);
743     gtr_cell_renderer_render(p->icon_renderer, window, widget, &icon_area, flags);
744     g_object_set(p->text_renderer, "text", name, "scale", 1.0, FOREGROUND_COLOR_KEY, &text_color, "ellipsize",
745         PANGO_ELLIPSIZE_END, "weight", PANGO_WEIGHT_BOLD, NULL);
746     gtr_cell_renderer_render(p->text_renderer, window, widget, &name_area, flags);
747     g_object_set(p->text_renderer, "text", gstr_prog->str, "scale", SMALL_SCALE, "weight", PANGO_WEIGHT_NORMAL, NULL);
748     gtr_cell_renderer_render(p->text_renderer, window, widget, &prog_area, flags);
749     g_object_set(p->progress_renderer, "value", (int)(percentDone * 100.0), "text", "", "sensitive", sensitive, NULL);
750     gtr_cell_renderer_render(p->progress_renderer, window, widget, &prct_area, flags);
751     g_object_set(p->text_renderer, "text", gstr_stat->str, FOREGROUND_COLOR_KEY, &text_color, NULL);
752     gtr_cell_renderer_render(p->text_renderer, window, widget, &stat_area, flags);
753 
754     /* cleanup */
755     g_object_unref(icon);
756 }
757 
torrent_cell_renderer_render(GtkCellRenderer * cell,GtrDrawable * window,GtkWidget * widget,GdkRectangle const * background_area,GdkRectangle const * cell_area,GtkCellRendererState flags)758 static void torrent_cell_renderer_render(GtkCellRenderer* cell, GtrDrawable* window, GtkWidget* widget,
759     GdkRectangle const* background_area, GdkRectangle const* cell_area,
760     GtkCellRendererState flags)
761 {
762     TorrentCellRenderer* self = TORRENT_CELL_RENDERER(cell);
763 
764 #ifdef TEST_RTL
765     GtkTextDirection real_dir = gtk_widget_get_direction(widget);
766     gtk_widget_set_direction(widget, GTK_TEXT_DIR_RTL);
767 #endif
768 
769     if (self != NULL && self->priv->tor != NULL)
770     {
771         struct TorrentCellRendererPrivate* p = self->priv;
772 
773         if (p->compact)
774         {
775             render_compact(self, window, widget, background_area, cell_area, flags);
776         }
777         else
778         {
779             render_full(self, window, widget, background_area, cell_area, flags);
780         }
781     }
782 
783 #ifdef TEST_RTL
784     gtk_widget_set_direction(widget, real_dir);
785 #endif
786 }
787 
torrent_cell_renderer_set_property(GObject * object,guint property_id,GValue const * v,GParamSpec * pspec)788 static void torrent_cell_renderer_set_property(GObject* object, guint property_id, GValue const* v, GParamSpec* pspec)
789 {
790     TorrentCellRenderer* self = TORRENT_CELL_RENDERER(object);
791     struct TorrentCellRendererPrivate* p = self->priv;
792 
793     switch (property_id)
794     {
795     case P_TORRENT:
796         p->tor = g_value_get_pointer(v);
797         break;
798 
799     case P_UPLOAD_SPEED:
800         p->upload_speed_KBps = g_value_get_double(v);
801         break;
802 
803     case P_DOWNLOAD_SPEED:
804         p->download_speed_KBps = g_value_get_double(v);
805         break;
806 
807     case P_BAR_HEIGHT:
808         p->bar_height = g_value_get_int(v);
809         break;
810 
811     case P_COMPACT:
812         p->compact = g_value_get_boolean(v);
813         break;
814 
815     default:
816         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
817         break;
818     }
819 }
820 
torrent_cell_renderer_get_property(GObject * object,guint property_id,GValue * v,GParamSpec * pspec)821 static void torrent_cell_renderer_get_property(GObject* object, guint property_id, GValue* v, GParamSpec* pspec)
822 {
823     TorrentCellRenderer const* self = TORRENT_CELL_RENDERER(object);
824     struct TorrentCellRendererPrivate* p = self->priv;
825 
826     switch (property_id)
827     {
828     case P_TORRENT:
829         g_value_set_pointer(v, p->tor);
830         break;
831 
832     case P_UPLOAD_SPEED:
833         g_value_set_double(v, p->upload_speed_KBps);
834         break;
835 
836     case P_DOWNLOAD_SPEED:
837         g_value_set_double(v, p->download_speed_KBps);
838         break;
839 
840     case P_BAR_HEIGHT:
841         g_value_set_int(v, p->bar_height);
842         break;
843 
844     case P_COMPACT:
845         g_value_set_boolean(v, p->compact);
846         break;
847 
848     default:
849         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
850         break;
851     }
852 }
853 
G_DEFINE_TYPE_WITH_CODE(TorrentCellRenderer,torrent_cell_renderer,GTK_TYPE_CELL_RENDERER,G_ADD_PRIVATE (TorrentCellRenderer))854 G_DEFINE_TYPE_WITH_CODE(TorrentCellRenderer, torrent_cell_renderer, GTK_TYPE_CELL_RENDERER, G_ADD_PRIVATE(TorrentCellRenderer))
855 
856 static void torrent_cell_renderer_dispose(GObject* o)
857 {
858     TorrentCellRenderer* r = TORRENT_CELL_RENDERER(o);
859 
860     if (r != NULL && r->priv != NULL)
861     {
862         g_string_free(r->priv->gstr1, TRUE);
863         g_string_free(r->priv->gstr2, TRUE);
864         g_object_unref(G_OBJECT(r->priv->text_renderer));
865         g_object_unref(G_OBJECT(r->priv->progress_renderer));
866         g_object_unref(G_OBJECT(r->priv->icon_renderer));
867         r->priv = NULL;
868     }
869 
870     G_OBJECT_CLASS(torrent_cell_renderer_parent_class)->dispose(o);
871 }
872 
torrent_cell_renderer_class_init(TorrentCellRendererClass * klass)873 static void torrent_cell_renderer_class_init(TorrentCellRendererClass* klass)
874 {
875     GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
876     GtkCellRendererClass* cell_class = GTK_CELL_RENDERER_CLASS(klass);
877 
878     cell_class->render = torrent_cell_renderer_render;
879     cell_class->get_size = torrent_cell_renderer_get_size;
880     gobject_class->set_property = torrent_cell_renderer_set_property;
881     gobject_class->get_property = torrent_cell_renderer_get_property;
882     gobject_class->dispose = torrent_cell_renderer_dispose;
883 
884     g_object_class_install_property(gobject_class, P_TORRENT,
885         g_param_spec_pointer("torrent", NULL, "tr_torrent*", G_PARAM_READWRITE));
886 
887     g_object_class_install_property(gobject_class, P_UPLOAD_SPEED,
888         g_param_spec_double("piece-upload-speed", NULL, "tr_stat.pieceUploadSpeed_KBps", 0, INT_MAX, 0, G_PARAM_READWRITE));
889 
890     g_object_class_install_property(gobject_class, P_DOWNLOAD_SPEED,
891         g_param_spec_double("piece-download-speed", NULL, "tr_stat.pieceDownloadSpeed_KBps", 0, INT_MAX, 0, G_PARAM_READWRITE));
892 
893     g_object_class_install_property(gobject_class, P_BAR_HEIGHT,
894         g_param_spec_int("bar-height", NULL, "Bar Height", 1, INT_MAX, DEFAULT_BAR_HEIGHT, G_PARAM_READWRITE));
895 
896     g_object_class_install_property(gobject_class, P_COMPACT,
897         g_param_spec_boolean("compact", NULL, "Compact Mode", FALSE, G_PARAM_READWRITE));
898 }
899 
torrent_cell_renderer_init(TorrentCellRenderer * self)900 static void torrent_cell_renderer_init(TorrentCellRenderer* self)
901 {
902     struct TorrentCellRendererPrivate* p;
903 
904 #if GLIB_CHECK_VERSION(2, 58, 0)
905     p = self->priv = torrent_cell_renderer_get_instance_private(self);
906 #else
907     p = self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, TORRENT_CELL_RENDERER_TYPE, struct TorrentCellRendererPrivate);
908 #endif
909 
910     p->tor = NULL;
911     p->gstr1 = g_string_new(NULL);
912     p->gstr2 = g_string_new(NULL);
913     p->text_renderer = gtk_cell_renderer_text_new();
914     g_object_set(p->text_renderer, "xpad", 0, "ypad", 0, NULL);
915     p->progress_renderer = gtk_cell_renderer_progress_new();
916     p->icon_renderer = gtk_cell_renderer_pixbuf_new();
917     g_object_ref_sink(p->text_renderer);
918     g_object_ref_sink(p->progress_renderer);
919     g_object_ref_sink(p->icon_renderer);
920 
921     p->bar_height = DEFAULT_BAR_HEIGHT;
922 }
923 
torrent_cell_renderer_new(void)924 GtkCellRenderer* torrent_cell_renderer_new(void)
925 {
926     return (GtkCellRenderer*)g_object_new(TORRENT_CELL_RENDERER_TYPE, NULL);
927 }
928