LoginSignup
2
1

More than 5 years have passed since last update.

Gtkでマウスカーソルに追従するツールチップの作り方

Posted at

もしかしたらGtk3でも同じ方法で実装できるかもしれませんが、サンプルコードはGtk2で書いています。

コードはmain()から読むとわかりやすいと思います。

カレンダーみたいなウィジェットを自力で実装することを考えます。カレンダーには日付やスケジュールなどが書かれているとします。
このときスケジュールが長いと、実装によっては、対象のセルに収まりきらず表示が切れてしまいます。

そんなとき、ツールチップにスケジュールを省略せずに表示できると便利です。

ポイントはいくつかあるのですが、箇条書きで示します。

  • query-tooltipイベントとカスタムツールチップでやる
    • どちらか片方だけだとツールチップの表示位置がずれたり、ツールチップのサイズが制御できなかったりで、中途半端になってしまう
  • has-tooltipTRUEにしてからquery-tooltipg_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`

参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1