もしかしたらGtk3でも同じ方法で実装できるかもしれませんが、サンプルコードはGtk2で書いています。
コードはmain()
から読むとわかりやすいと思います。
カレンダーみたいなウィジェットを自力で実装することを考えます。カレンダーには日付やスケジュールなどが書かれているとします。
このときスケジュールが長いと、実装によっては、対象のセルに収まりきらず表示が切れてしまいます。
そんなとき、ツールチップにスケジュールを省略せずに表示できると便利です。
ポイントはいくつかあるのですが、箇条書きで示します。
-
query-tooltip
イベントとカスタムツールチップでやる- どちらか片方だけだとツールチップの表示位置がずれたり、ツールチップのサイズが制御できなかったりで、中途半端になってしまう
-
has-tooltip
をTRUE
にしてからquery-tooltip
にg_signal_connect()
する -
gtk_widget_set_tooltip_window()
でカスタムツールチップを使うようにする -
gtk_widget_set_tooltip_window()
の第2引数のGtkWindowはサイズ変更不可にしておく- これでツールチップのサイズがツールチップに表示するテキストの長さによって自動的に調整される
コード
main.c
#include <gtk/gtk.h>
#include <cairo/cairo.h>
#include <stdlib.h>
static void destroy(GtkWidget *widget, gpointer data)
{
gtk_main_quit();
}
// 格子状の模様を書く
static gboolean expose_event(GtkWidget *widget,
GdkEventExpose *event,
gpointer data)
{
cairo_t *cr = gdk_cairo_create(widget->window);
cairo_rectangle(cr,
event->area.x, event->area.y,
event->area.width, event->area.height);
cairo_clip(cr);
cairo_set_source_rgba (cr, 1, 0.2, 0.2, 0.6);
cairo_set_line_width (cr, 2.0);
for (int x = 0; x <= 500; x += 25) {
cairo_move_to(cr, x, 0);
cairo_line_to(cr, x, 500);
}
for (int y = 0; y <= 500; y += 25) {
cairo_move_to(cr, 0, y);
cairo_line_to(cr, 500, y);
}
cairo_stroke(cr);
cairo_destroy(cr);
return TRUE;
}
static gchar *prev_text = NULL;
static gboolean query_tooltip(GtkWidget *widget,
gint x,
gint y,
gboolean keyboard_mode,
GtkTooltip *tooltip,
gpointer user_data)
{
gint x_cell = x / 25;
gint y_cell = y / 25;
gint root_x = 0;
gint root_y = 0;
GtkWidget *label = GTK_WIDGET(user_data);
gdk_window_get_root_coords(widget->window,
x, y,
&root_x, &root_y);
gchar *text;
// ツールチップに表示するテキストの長さを場所によって変える
if (x % 5 == 0 || x % 3 == 0) {
text = g_strdup_printf("(%05d,%05d)", x_cell, y_cell);
} else {
text = g_strdup_printf("(%d,%d)", x_cell, y_cell);
}
// 前の query-tooltip イベントと同じマスにいるなら何もしない
if (!g_strcmp0(prev_text, text)) {
g_message("%s, %s", prev_text, text);
g_free(text);
return FALSE;
}
// gtk_widget_set_tooltip_window() 経由で
// カスタムツールチップを使うときはこれだとダメ
// gtk_tooltip_set_text(tooltip, text);
// ↓は両方呼ばないとツールチップの内容が変更できない
gtk_widget_set_tooltip_text(widget, text);
gtk_label_set_text(GTK_LABEL(label), text);
GtkWindow *tooltip_window = gtk_widget_get_tooltip_window(widget);
if (tooltip_window) {
gtk_window_move(tooltip_window, root_x, root_y);
g_debug("move");
}
g_free(prev_text);
prev_text = text;
return FALSE;
}
int main(int argc, char* argv[])
{
GtkWidget *window;
GtkWidget *drawing_area;
GtkWidget *tooltip_window;
GtkWidget *tooltip;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
drawing_area = gtk_drawing_area_new();
gtk_container_add(GTK_CONTAINER(window), drawing_area);
gtk_widget_set_size_request(window, 500, 500);
// カスタムツールチップ用のGtkWidgetを構築する
tooltip_window = gtk_window_new(GTK_WINDOW_POPUP);
tooltip = gtk_label_new("");
gtk_label_set_line_wrap(GTK_LABEL(tooltip), TRUE);
// サイズ変更不可にしないと、ツールチップのテキストを変更したとき
// ツールチップのサイズが追従しない
gtk_window_set_resizable(GTK_WINDOW(tooltip_window), FALSE);
gtk_widget_set_name(tooltip_window, "gtk-tooltip");
gtk_container_add(GTK_CONTAINER(tooltip_window), tooltip);
gtk_widget_show(tooltip);
// has-tooltip を TRUE にしてから query-tooltip に g_signal_connect() する
gtk_widget_set_has_tooltip(drawing_area, TRUE);
g_signal_connect(window,
"destroy",
G_CALLBACK(destroy),
NULL);
g_signal_connect(drawing_area,
"expose-event",
G_CALLBACK(expose_event),
NULL);
g_signal_connect(drawing_area,
"query-tooltip",
G_CALLBACK(query_tooltip),
tooltip);
gtk_widget_set_tooltip_window(drawing_area,
GTK_WINDOW(tooltip_window));
gtk_widget_show(window);
gtk_widget_show(drawing_area);
gtk_main();
return EXIT_SUCCESS;
}
Makefile
build: main.c
gcc $< -Wall -o main `pkg-config --cflags --libs gtk+-2.0 cairo`