comparison pidgin/gtkroomlist.c @ 21329:a04a0d3f9b4f

Add tooltips when hovering over rooms in the roomlist so the full channel topics can be seen more easily. It's mostly copy & paste from gtkblist.c, so could probably be cleaned up as these tips don't need all the complexity of the blist tips.
author Stu Tomlinson <stu@nosnilmot.com>
date Sun, 11 Nov 2007 18:08:19 +0000
parents 6bf32c9e15a7
children 2a2496044eef 29a4b8e5f4f6
comparison
equal deleted inserted replaced
21328:daf85e00658b 21329:a04a0d3f9b4f
59 PidginRoomlistDialog *dialog; 59 PidginRoomlistDialog *dialog;
60 GtkTreeStore *model; 60 GtkTreeStore *model;
61 GtkWidget *tree; 61 GtkWidget *tree;
62 GHashTable *cats; /**< Meow. */ 62 GHashTable *cats; /**< Meow. */
63 gint num_rooms, total_rooms; 63 gint num_rooms, total_rooms;
64 GtkWidget *tipwindow;
65 GdkRectangle tip_rect;
66 guint timeout;
67 PangoLayout *tip_layout;
68 PangoLayout *tip_name_layout;
69 int tip_height;
70 int tip_width;
71 int tip_name_height;
72 int tip_name_width;
64 } PidginRoomlist; 73 } PidginRoomlist;
65 74
66 enum { 75 enum {
67 NAME_COLUMN = 0, 76 NAME_COLUMN = 0,
68 ROOM_COLUMN, 77 ROOM_COLUMN,
327 336
328 if (!category->expanded_once) { 337 if (!category->expanded_once) {
329 purple_roomlist_expand_category(list, category); 338 purple_roomlist_expand_category(list, category);
330 category->expanded_once = TRUE; 339 category->expanded_once = TRUE;
331 } 340 }
341 }
342
343 static void pidgin_roomlist_tooltip_destroy(PidginRoomlist *grl)
344 {
345 if ((grl == NULL) || (grl->tipwindow == NULL))
346 return;
347
348 gtk_widget_destroy(grl->tipwindow);
349 grl->tipwindow = NULL;
350 }
351
352 static void pidgin_roomlist_tooltip_destroy_cb(GObject *object, PidginRoomlist *grl)
353 {
354 if ((grl == NULL) || (grl->tipwindow == NULL))
355 return;
356
357 if (grl->timeout)
358 g_source_remove(grl->timeout);
359 grl->timeout = 0;
360
361 pidgin_roomlist_tooltip_destroy(grl);
362 }
363
364 #define SMALL_SPACE 6
365 #define TOOLTIP_BORDER 12
366
367 static void pidgin_roomlist_paint_tip(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
368 {
369 PidginRoomlist *grl = (PidginRoomlist *)user_data;
370 GtkStyle *style;
371 int current_height, max_width;
372 int max_text_width;
373 GtkTextDirection dir = gtk_widget_get_direction(GTK_WIDGET(grl->tree));
374
375 style = grl->tipwindow->style;
376 gtk_paint_flat_box(style, grl->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
377 NULL, grl->tipwindow, "tooltip", 0, 0, -1, -1);
378
379 max_text_width = 0;
380
381 max_text_width = MAX(grl->tip_width, grl->tip_name_width);
382 max_width = TOOLTIP_BORDER + SMALL_SPACE + max_text_width + TOOLTIP_BORDER;
383
384 current_height = 12;
385
386 if (dir == GTK_TEXT_DIR_RTL) {
387 gtk_paint_layout(style, grl->tipwindow->window, GTK_STATE_NORMAL, FALSE,
388 NULL, grl->tipwindow, "tooltip",
389 max_width - (TOOLTIP_BORDER + SMALL_SPACE) - PANGO_PIXELS(600000),
390 current_height, grl->tip_name_layout);
391 } else {
392 gtk_paint_layout (style, grl->tipwindow->window, GTK_STATE_NORMAL, FALSE,
393 NULL, grl->tipwindow, "tooltip",
394 TOOLTIP_BORDER + SMALL_SPACE, current_height, grl->tip_name_layout);
395 }
396 if (dir != GTK_TEXT_DIR_RTL) {
397 gtk_paint_layout (style, grl->tipwindow->window, GTK_STATE_NORMAL, FALSE,
398 NULL, grl->tipwindow, "tooltip",
399 TOOLTIP_BORDER + SMALL_SPACE, current_height + grl->tip_name_height, grl->tip_layout);
400 } else {
401 gtk_paint_layout(style, grl->tipwindow->window, GTK_STATE_NORMAL, FALSE,
402 NULL, grl->tipwindow, "tooltip",
403 max_width - (TOOLTIP_BORDER + SMALL_SPACE) - PANGO_PIXELS(600000),
404 current_height + grl->tip_name_height,
405 grl->tip_layout);
406 }
407
408 }
409
410 static gboolean pidgin_roomlist_create_tip(PurpleRoomlist *list)
411 {
412 PidginRoomlist *grl = list->ui_data;
413 GtkWidget *tv = grl->tree;
414 PurpleRoomlistRoom *room;
415 GtkTreePath *path;
416 GtkTreeIter iter;
417 GValue val;
418 gchar *name, *tmp, *node_name;
419 GString *tooltip_text = NULL;
420 GList *l, *k;
421 gint j;
422 gboolean first = TRUE;
423
424 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), grl->tip_rect.x, grl->tip_rect.y + (grl->tip_rect.height/2),
425 &path, NULL, NULL, NULL))
426 return FALSE;
427
428 gtk_tree_model_get_iter(GTK_TREE_MODEL(grl->model), &iter, path);
429
430 val.g_type = 0;
431 gtk_tree_model_get_value(GTK_TREE_MODEL(grl->model), &iter, ROOM_COLUMN, &val);
432 room = g_value_get_pointer(&val);
433
434 if (!room || !(room->type & PURPLE_ROOMLIST_ROOMTYPE_ROOM))
435 return FALSE;
436
437 tooltip_text = g_string_new("");
438 gtk_tree_model_get(GTK_TREE_MODEL(grl->model), &iter, NAME_COLUMN, &name, -1);
439
440 for (j = NUM_OF_COLUMNS, l = room->fields, k = list->fields; l && k; j++, l = l->next, k = k->next) {
441 PurpleRoomlistField *f = k->data;
442 gchar *label;
443 if (f->hidden)
444 continue;
445 label = g_markup_escape_text(f->label, -1);
446 switch (f->type) {
447 case PURPLE_ROOMLIST_FIELD_BOOL:
448 g_string_append_printf(tooltip_text, "%s<b>%s:</b> %s", first ? "" : "\n", label, l->data ? "True" : "False");
449 break;
450 case PURPLE_ROOMLIST_FIELD_INT:
451 g_string_append_printf(tooltip_text, "%s<b>%s:</b> %d", first ? "" : "\n", label, GPOINTER_TO_INT(l->data));
452 break;
453 case PURPLE_ROOMLIST_FIELD_STRING:
454 tmp = g_markup_escape_text((char *)l->data, -1);
455 g_string_append_printf(tooltip_text, "%s<b>%s:</b> %s", first ? "" : "\n", label, tmp);
456 g_free(tmp);
457 break;
458 }
459 first = FALSE;
460 g_free(label);
461 }
462
463 gtk_tree_path_free(path);
464
465 grl->tip_layout = gtk_widget_create_pango_layout(grl->tipwindow, NULL);
466 grl->tip_name_layout = gtk_widget_create_pango_layout(grl->tipwindow, NULL);
467
468 tmp = g_markup_escape_text(name, -1);
469 g_free(name);
470 node_name = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>", tmp);
471 g_free(tmp);
472
473 pango_layout_set_markup(grl->tip_layout, tooltip_text->str, -1);
474 pango_layout_set_wrap(grl->tip_layout, PANGO_WRAP_WORD);
475 pango_layout_set_width(grl->tip_layout, 600000);
476
477 pango_layout_get_size (grl->tip_layout, &grl->tip_width, &grl->tip_height);
478 grl->tip_width = PANGO_PIXELS(grl->tip_width);
479 grl->tip_height = PANGO_PIXELS(grl->tip_height);
480
481 pango_layout_set_markup(grl->tip_name_layout, node_name, -1);
482 pango_layout_set_wrap(grl->tip_name_layout, PANGO_WRAP_WORD);
483 pango_layout_set_width(grl->tip_name_layout, 600000);
484
485 pango_layout_get_size (grl->tip_name_layout, &grl->tip_name_width, &grl->tip_name_height);
486 grl->tip_name_width = PANGO_PIXELS(grl->tip_name_width) + SMALL_SPACE;
487 grl->tip_name_height = MAX(PANGO_PIXELS(grl->tip_name_height), SMALL_SPACE);
488
489 g_free(node_name);
490 g_string_free(tooltip_text, TRUE);
491
492 return TRUE;
493 }
494
495 static void pidgin_roomlist_draw_tooltip(PurpleRoomlist *list, GtkWidget *widget)
496 {
497 PidginRoomlist *grl = list->ui_data;
498 int scr_w, scr_h, w, h, x, y;
499 #if GTK_CHECK_VERSION(2,2,0)
500 int mon_num;
501 GdkScreen *screen = NULL;
502 #endif
503 GdkRectangle mon_size;
504 int sig;
505 const char *name;
506
507 pidgin_roomlist_tooltip_destroy(grl);
508 grl->tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
509 gtk_widget_ensure_style (grl->tipwindow);
510
511 if (!pidgin_roomlist_create_tip(list)) {
512 pidgin_roomlist_tooltip_destroy(grl);
513 return;
514 }
515
516 name = gtk_window_get_title(GTK_WINDOW(gtk_widget_get_toplevel(widget)));
517 gtk_widget_set_app_paintable(grl->tipwindow, TRUE);
518 gtk_window_set_title(GTK_WINDOW(grl->tipwindow), name ? name : _("Room List"));
519 gtk_window_set_resizable(GTK_WINDOW(grl->tipwindow), FALSE);
520 gtk_widget_set_name(grl->tipwindow, "gtk-tooltips");
521 g_signal_connect(G_OBJECT(grl->tipwindow), "expose_event",
522 G_CALLBACK(pidgin_roomlist_paint_tip), grl);
523
524 w = TOOLTIP_BORDER + SMALL_SPACE +
525 MAX(grl->tip_width, grl->tip_name_width) + TOOLTIP_BORDER;
526 h = TOOLTIP_BORDER + grl->tip_height + grl->tip_name_height
527 + TOOLTIP_BORDER;
528
529 #if GTK_CHECK_VERSION(2,2,0)
530 gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL);
531 mon_num = gdk_screen_get_monitor_at_point(screen, x, y);
532 gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size);
533
534 scr_w = mon_size.width + mon_size.x;
535 scr_h = mon_size.height + mon_size.y;
536 #else
537 scr_w = gdk_screen_width();
538 scr_h = gdk_screen_height();
539 gdk_window_get_pointer(NULL, &x, &y, NULL);
540 mon_size.x = 0;
541 mon_size.y = 0;
542 #endif
543
544 #if GTK_CHECK_VERSION(2,2,0)
545 if (w > mon_size.width)
546 w = mon_size.width - 10;
547
548 if (h > mon_size.height)
549 h = mon_size.height - 10;
550 #endif
551 x -= ((w >> 1) + 4);
552
553 if ((y + h + 4) > scr_h)
554 y = y - h - 5;
555 else
556 y = y + 6;
557
558 if (y < mon_size.y)
559 y = mon_size.y;
560
561 if (y != mon_size.y) {
562 if ((x + w) > scr_w)
563 x -= (x + w + 5) - scr_w;
564 else if (x < mon_size.x)
565 x = mon_size.x;
566 } else {
567 x -= (w / 2 + 10);
568 if (x < mon_size.x)
569 x = mon_size.x;
570 }
571
572 gtk_widget_set_size_request(grl->tipwindow, w, h);
573 gtk_window_move(GTK_WINDOW(grl->tipwindow), x, y);
574 gtk_widget_show(grl->tipwindow);
575
576 /* Hide the tooltip when the widget is destroyed */
577 sig = g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(pidgin_roomlist_tooltip_destroy_cb), grl);
578 g_signal_connect_swapped(G_OBJECT(grl->tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig));
579 }
580
581 static gboolean pidgin_roomlist_tooltip_timeout(PurpleRoomlist *list)
582 {
583 PidginRoomlist *grl = list->ui_data;
584 GtkWidget *tv = grl->tree;
585 GtkTreePath *path;
586
587 pidgin_roomlist_tooltip_destroy(grl);
588
589 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), grl->tip_rect.x, grl->tip_rect.y + (grl->tip_rect.height/2),
590 &path, NULL, NULL, NULL))
591 return FALSE;
592
593 pidgin_roomlist_draw_tooltip(list, GTK_WIDGET(grl->tree));
594
595 return FALSE;
596 }
597
598 static gboolean row_motion_cb(GtkWidget *tv, GdkEventMotion *event, gpointer user_data)
599 {
600 PurpleRoomlist *list = user_data;
601 PidginRoomlist *grl = list->ui_data;
602 GtkTreePath *path;
603 int delay;
604
605 /* XXX: should this be using the blist delay pref? */
606 delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay");
607
608 if (delay == 0)
609 return FALSE;
610
611 if (grl->timeout) {
612 if ((event->y > grl->tip_rect.y) && ((event->y - grl->tip_rect.height) < grl->tip_rect.y))
613 return FALSE;
614 /* We've left the cell. Remove the timeout and create a new one below */
615 pidgin_roomlist_tooltip_destroy(grl);
616 }
617
618 gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL);
619
620 if (path == NULL) {
621 pidgin_roomlist_tooltip_destroy(grl);
622 return FALSE;
623 }
624
625 gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &grl->tip_rect);
626
627 if (path)
628 gtk_tree_path_free(path);
629 grl->timeout = g_timeout_add(delay, (GSourceFunc)pidgin_roomlist_tooltip_timeout, list);
630
631 return FALSE;
632 }
633
634 static void row_leave_cb(GtkWidget *tv, GdkEventCrossing *e, gpointer user_data)
635 {
636 PurpleRoomlist *list = user_data;
637 PidginRoomlist *grl = list->ui_data;
638
639 if (grl->timeout) {
640 g_source_remove(grl->timeout);
641 grl->timeout = 0;
642 }
643
644 pidgin_roomlist_tooltip_destroy(grl);
332 } 645 }
333 646
334 static gboolean account_filter_func(PurpleAccount *account) 647 static gboolean account_filter_func(PurpleAccount *account)
335 { 648 {
336 PurpleConnection *gc = purple_account_get_connection(account); 649 PurpleConnection *gc = purple_account_get_connection(account);
648 } 961 }
649 962
650 g_signal_connect(G_OBJECT(tree), "button-press-event", G_CALLBACK(room_click_cb), list); 963 g_signal_connect(G_OBJECT(tree), "button-press-event", G_CALLBACK(room_click_cb), list);
651 g_signal_connect(G_OBJECT(tree), "row-expanded", G_CALLBACK(row_expanded_cb), list); 964 g_signal_connect(G_OBJECT(tree), "row-expanded", G_CALLBACK(row_expanded_cb), list);
652 g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(row_activated_cb), list); 965 g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(row_activated_cb), list);
966 g_signal_connect(G_OBJECT(tree), "motion-notify-event", G_CALLBACK(row_motion_cb), list);
967 g_signal_connect(G_OBJECT(tree), "leave-notify-event", G_CALLBACK(row_leave_cb), list);
653 968
654 /* Enable CTRL+F searching */ 969 /* Enable CTRL+F searching */
655 gtk_tree_view_set_search_column(GTK_TREE_VIEW(tree), NAME_COLUMN); 970 gtk_tree_view_set_search_column(GTK_TREE_VIEW(tree), NAME_COLUMN);
656 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(tree), _search_func, NULL, NULL); 971 gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(tree), _search_func, NULL, NULL);
657 972