Mercurial > pidgin.yaz
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 |