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