File kanjipad-2.0.0-gtk3.patch of Package kanjipad

diff -urN kanjipad-2.0.0/kanjipad.c kanjipad-2.0.0.gtk3/kanjipad.c
--- kanjipad-2.0.0/kanjipad.c	2002-08-26 03:00:54.000000000 +0800
+++ kanjipad-2.0.0.gtk3/kanjipad.c	2026-02-15 11:04:35.868409625 +0800
@@ -1,6 +1,8 @@
 /* KanjiPad - Japanese handwriting recognition front end
  * Copyright (C) 1997 Owen Taylor
  *
+ * Ported to GTK+ 3
+ *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
@@ -23,23 +25,27 @@
 #include <stdio.h>
 #include <string.h>
 #include <unistd.h>
+#include <glib/gstdio.h>
+#include <math.h>
 
 #include "kanjipad.h"
 
+#ifndef BINDIR
+#define BINDIR "/usr/local/bin"
+#endif
+
 typedef struct {
   gchar d[2];
 } kp_wchar;
 
 #define WCHAR_EQ(a,b) (a.d[0] == b.d[0] && a.d[1] == b.d[1])
 
-/* Wait for child process? */
-
 /* user interface elements */
-static GdkPixmap *kpixmap;
+static cairo_surface_t *ksurface = NULL;
 GtkWidget *karea;
 GtkWidget *clear_button;
 GtkWidget *lookup_button;
-GtkItemFactory *factory;
+GtkWidget *main_window;
 
 #define MAX_GUESSES 10
 kp_wchar kanjiguess[MAX_GUESSES];
@@ -56,38 +62,29 @@
 static char *data_file = NULL;
 static char *progname;
 
-static void exit_callback ();
-static void copy_callback ();
-static void save_callback ();
-static void clear_callback ();
-static void look_up_callback ();
-static void annotate_callback ();
+static GActionGroup *action_group = NULL;
 
-static void update_sensitivity ();
+/* Function declarations */
+static void exit_activated (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void copy_activated (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void save_activated (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void clear_activated (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void look_up_activated (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void annotate_activated (GSimpleAction *action, GVariant *parameter, gpointer data);
+static void update_sensitivity (void);
+static void karea_draw (GtkWidget *w);
 
-static GtkItemFactoryEntry menu_items[] =
+/* Pad area changed callback - called from pad_area.c */
+void
+pad_area_changed_callback (PadArea *area)
 {
-  { "/_File", NULL, NULL, 0, "<Branch>" },
-  { "/File/_Quit", NULL, exit_callback, 0, "<StockItem>", GTK_STOCK_QUIT },
-
-  { "/_Edit", NULL, NULL, 0, "<Branch>" },
-  { "/Edit/_Copy", NULL, copy_callback, 0, "<StockItem>", GTK_STOCK_COPY },
-  
-  { "/_Character", NULL, NULL, 0, "<Branch>" },
-  { "/Character/_Lookup", "<control>L", look_up_callback },
-  { "/Character/_Clear", "<control>X", clear_callback },
-  { "/Character/_Save", "<control>S", save_callback },
-  { "/Character/sep1", NULL, NULL, 0, "<Separator>" },
-  
-  { "/Character/_Annotate", NULL, annotate_callback, 0, "<CheckItem>" },
-};
-
-static int nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]);
+  update_sensitivity ();
+}
 
 static void
 karea_get_char_size (GtkWidget *widget,
-		     int       *width,
-		     int       *height)
+                     int       *width,
+                     int       *height)
 {
   PangoLayout *layout = gtk_widget_create_pango_layout (widget, "\xe6\xb6\x88");
   pango_layout_get_pixel_size (layout, width, height);
@@ -110,7 +107,7 @@
   if (!string_utf)
     {
       g_printerr ("Cannot convert string from EUC-JP to UTF-8: %s\n",
-		  err->message);
+                  err->message);
       exit (1);
     }
 
@@ -119,88 +116,123 @@
 
 static void
 karea_draw_character (GtkWidget *w,
-		      int        index,
-		      int        selected)
+                      int        index,
+                      int        selected)
 {
+  cairo_t *cr;
   PangoLayout *layout;
   gchar *string_utf;
   gint char_width, char_height;
-  gint x;
+  gint x, y;
+  GtkStyleContext *context = gtk_widget_get_style_context (w);
+  GtkAllocation allocation;
 
+  gtk_widget_get_allocation (w, &allocation);
   karea_get_char_size (w, &char_width, &char_height);
 
+  if (!ksurface)
+    return;
+    
+  cr = cairo_create (ksurface);
+  
+  y = (char_height + 6) * index;
+  
   if (selected >= 0)
     {
-      gdk_draw_rectangle (kpixmap,
-			  selected ? w->style->bg_gc[GTK_STATE_SELECTED] :
-			  w->style->white_gc,
-			  TRUE,
-			  0, (char_height + 6) *index, w->allocation.width - 1, char_height + 5);
+      if (selected)
+        {
+          /* Selected state - use theme color */
+          gtk_style_context_save (context);
+          gtk_style_context_set_state (context, GTK_STATE_FLAG_SELECTED);
+          gtk_render_background (context, cr, 0, y, 
+                                 allocation.width - 1, 
+                                 char_height + 5);
+          gtk_style_context_restore (context);
+        }
+      else
+        {
+          /* Unselected - white background */
+          cairo_set_source_rgb (cr, 1, 1, 1);
+          cairo_rectangle (cr, 0, y, 
+                           allocation.width - 1, 
+                           char_height + 5);
+          cairo_fill (cr);
+        }
     }
 
   string_utf = utf8_for_char (kanjiguess[index]);
   layout = gtk_widget_create_pango_layout (w, string_utf);
   g_free (string_utf);
 
-  x = (w->allocation.width - char_width) / 2;
+  x = (allocation.width - char_width) / 2;
+  
+  if (selected > 0)
+    cairo_set_source_rgb (cr, 1, 1, 1);
+  else
+    cairo_set_source_rgb (cr, 0, 0, 0);
+  
+  cairo_move_to (cr, x, y + 3);
+  pango_cairo_show_layout (cr, layout);
   
-  gdk_draw_layout (kpixmap, 
-		   (selected > 0) ? w->style->white_gc :
-		                    w->style->black_gc,
-		   x, (char_height + 6) * index + 3, layout);
   g_object_unref (layout);
+  cairo_destroy (cr);
 }
 
-
 static void
 karea_draw (GtkWidget *w)
 {
-  gint width = w->allocation.width;
-  gint height = w->allocation.height;
+  GtkAllocation allocation;
   int i;
 
-  gdk_draw_rectangle (kpixmap, 
-		      w->style->white_gc, TRUE,
-		      0, 0, width, height);
+  if (!ksurface)
+    return;
+    
+  gtk_widget_get_allocation (w, &allocation);
+  
+  cairo_t *cr = cairo_create (ksurface);
   
+  /* Clear with white background */
+  cairo_set_source_rgb (cr, 1, 1, 1);
+  cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
+  cairo_fill (cr);
+  
+  cairo_destroy (cr);
 
-  for (i=0; i<num_guesses; i++)
+  /* Draw all guess characters */
+  for (i = 0; i < num_guesses; i++)
     {
       if (WCHAR_EQ (kselected, kanjiguess[i]))
-	karea_draw_character (w, i, 1);
+        karea_draw_character (w, i, 1);
       else
-	karea_draw_character (w, i, -1);
+        karea_draw_character (w, i, -1);
     }
 
   gtk_widget_queue_draw (w);
 }
 
-static int
-karea_configure_event (GtkWidget *w, GdkEventConfigure *event)
+static gboolean
+karea_draw_cb (GtkWidget *w, cairo_t *cr, gpointer data)
 {
-  if (kpixmap)
-    g_object_unref (kpixmap);
-
-  kpixmap = gdk_pixmap_new (w->window, event->width, event->height, -1);
-
-  karea_draw (w);
+  if (ksurface)
+    {
+      cairo_set_source_surface (cr, ksurface, 0, 0);
+      cairo_paint (cr);
+    }
   
-  return TRUE;
+  return FALSE;
 }
 
-static int
-karea_expose_event (GtkWidget *w, GdkEventExpose *event)
+static void
+karea_resize_cb (GtkWidget *w, GtkAllocation *allocation, gpointer data)
 {
-  if (!kpixmap)
-    return 0;
+  if (ksurface)
+    cairo_surface_destroy (ksurface);
 
-  gdk_draw_drawable (w->window,
-		     w->style->fg_gc[GTK_STATE_NORMAL], kpixmap,
-		     event->area.x, event->area.y,
-		     event->area.x, event->area.y,
-		     event->area.width, event->area.height);
-
-    return 0;
+  ksurface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 
+                                         allocation->width, 
+                                         allocation->height);
+  
+  karea_draw (w);
 }
 
 static int
@@ -209,20 +241,20 @@
   int i;
   if (kselected.d[0] || kselected.d[1])
     {
-      for (i=0; i<num_guesses; i++)
-	{
-	  if (WCHAR_EQ (kselected, kanjiguess[i]))
-	    {
-	      karea_draw_character (w, i, 0);
-	    }
-	}
+      for (i = 0; i < num_guesses; i++)
+        {
+          if (WCHAR_EQ (kselected, kanjiguess[i]))
+            {
+              karea_draw_character (w, i, 0);
+            }
+        }
     }
   return TRUE;
 }
 
 static void
 karea_primary_clear (GtkClipboard *clipboard,
-		     gpointer      owner)
+                     gpointer      owner)
 {
   GtkWidget *w = owner;
   
@@ -235,9 +267,9 @@
 
 static void
 karea_primary_get (GtkClipboard     *clipboard,
-		   GtkSelectionData *selection_data,
-		   guint             info,
-		   gpointer          owner)
+                   GtkSelectionData *selection_data,
+                   guint             info,
+                   gpointer          owner)
 {
   if (kselected.d[0] || kselected.d[1])
     {
@@ -247,12 +279,13 @@
     }
 }
 
-static int
-karea_button_press_event (GtkWidget *w, GdkEventButton *event)
+static gboolean
+karea_button_press_cb (GtkWidget *w, GdkEventButton *event, gpointer data)
 {
   int j;
   gint char_height;
   GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
+  GtkAllocation allocation;
 
   static const GtkTargetEntry targets[] = {
     { "STRING", 0, 0 },
@@ -261,6 +294,7 @@
     { "UTF8_STRING", 0, 0 }
   };
 
+  gtk_widget_get_allocation (w, &allocation);
   karea_erase_selection (w);
 
   karea_get_char_size (w, NULL, &char_height);
@@ -272,15 +306,15 @@
       karea_draw_character (w, j, 1);
       
       if (!gtk_clipboard_set_with_owner (clipboard, targets, G_N_ELEMENTS (targets),
-					 karea_primary_get, karea_primary_clear, G_OBJECT (w)))
-	karea_primary_clear (clipboard, w);
+                                         karea_primary_get, karea_primary_clear, G_OBJECT (w)))
+        karea_primary_clear (clipboard, w);
     }
   else
     {
       kselected.d[0] = 0;
       kselected.d[1] = 0;
       if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (w))
-	gtk_clipboard_clear (clipboard);
+        gtk_clipboard_clear (clipboard);
     }
 
   update_sensitivity ();
@@ -289,14 +323,16 @@
   return TRUE;
 }
 
-static void 
-exit_callback (GtkWidget *w)
+/* Action callbacks */
+
+static void
+exit_activated (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
   exit (0);
 }
 
-static void 
-copy_callback (GtkWidget *w)
+static void
+copy_activated (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
   if (kselected.d[0] || kselected.d[1])
     {
@@ -306,10 +342,9 @@
     }
 }
 
-static void 
-look_up_callback (GtkWidget *w)
+static void
+look_up_activated (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
-  /*	     kill 'HUP',$engine_pid; */
   GList *tmp_list;
   GString *message = g_string_new (NULL);
   GError *err = NULL;
@@ -317,44 +352,45 @@
   tmp_list = pad_area->strokes;
   while (tmp_list)
     {
-     GList *stroke_list = tmp_list->data;
-     while (stroke_list)
-       {
-	 gint16 x = ((GdkPoint *)stroke_list->data)->x;
-	 gint16 y = ((GdkPoint *)stroke_list->data)->y;
-	 g_string_append_printf (message, "%d %d ", x, y);
-	 stroke_list = stroke_list->next;
-       }
-     g_string_append (message, "\n");
-     tmp_list = tmp_list->next;
+      GList *stroke_list = tmp_list->data;
+      while (stroke_list)
+        {
+          gint16 x = ((GdkPoint *)stroke_list->data)->x;
+          gint16 y = ((GdkPoint *)stroke_list->data)->y;
+          g_string_append_printf (message, "%d %d ", x, y);
+          stroke_list = stroke_list->next;
+        }
+      g_string_append (message, "\n");
+      tmp_list = tmp_list->next;
     }
   g_string_append (message, "\n");
+  
   if (g_io_channel_write_chars (to_engine,
-				message->str, message->len,
-				NULL, &err) != G_IO_STATUS_NORMAL)
+                                message->str, message->len,
+                                NULL, &err) != G_IO_STATUS_NORMAL)
     {
       g_printerr ("Cannot write message to engine: %s\n",
-		  err->message);
+                  err->message);
       exit (1);
     }
   if (g_io_channel_flush (to_engine, &err) != G_IO_STATUS_NORMAL)
     {
       g_printerr ("Error flushing message to engine: %s\n",
-		  err->message);
+                  err->message);
       exit (1);
     }
 
-  g_string_free (message, FALSE);
+  g_string_free (message, TRUE);
 }
 
-static void 
-clear_callback (GtkWidget *w)
+static void
+clear_activated (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
   pad_area_clear (pad_area);
 }
 
-static void 
-save_callback (GtkWidget *w)
+static void
+save_activated (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
   static int unknownID = 0;
   static FILE *samples = NULL;
@@ -366,21 +402,21 @@
   if (!samples)
     {
       if (!(samples = fopen("samples.dat", "a")))
-	g_error ("Can't open 'samples.dat': %s", g_strerror(errno));
+        g_error ("Can't open 'samples.dat': %s", g_strerror(errno));
     }
   
   if (kselected.d[0] || kselected.d[1])
     {
-      for (i=0; i<num_guesses; i++)
-	{
-	  if (WCHAR_EQ (kselected, kanjiguess[i]))
-	    found = TRUE;
-	}
+      for (i = 0; i < num_guesses; i++)
+        {
+          if (WCHAR_EQ (kselected, kanjiguess[i]))
+            found = TRUE;
+        }
     }
   
   if (found)
-    fprintf(samples,"%2x%2x %c%c\n", kselected.d[0], kselected.d[1],
-	   0x80 | kselected.d[0], 0x80 | kselected.d[1]);
+    fprintf(samples, "%2x%2x %c%c\n", kselected.d[0], kselected.d[1],
+           0x80 | kselected.d[0], 0x80 | kselected.d[1]);
   else
     {
       fprintf (samples, "0000 ??%d\n", unknownID);
@@ -391,53 +427,58 @@
   tmp_list = pad_area->strokes;
   while (tmp_list)
     {
-     GList *stroke_list = tmp_list->data;
-     while (stroke_list)
-       {
-	 gint16 x = ((GdkPoint *)stroke_list->data)->x;
-	 gint16 y = ((GdkPoint *)stroke_list->data)->y;
-	 fprintf(samples, "%d %d ", x, y);
-	 stroke_list = stroke_list->next;
-       }
-     fprintf(samples, "\n");
-     tmp_list = tmp_list->next;
+      GList *stroke_list = tmp_list->data;
+      while (stroke_list)
+        {
+          gint16 x = ((GdkPoint *)stroke_list->data)->x;
+          gint16 y = ((GdkPoint *)stroke_list->data)->y;
+          fprintf(samples, "%d %d ", x, y);
+          stroke_list = stroke_list->next;
+        }
+      fprintf(samples, "\n");
+      tmp_list = tmp_list->next;
     }
   fprintf(samples, "\n");
   fflush(samples);
 }
 
 static void
-annotate_callback ()
-{
-  pad_area_set_annotate (pad_area, !pad_area->annotate);
-}
-
-void
-pad_area_changed_callback (PadArea *area)
-{
-  update_sensitivity ();
-}
-
-static void
-update_path_sensitive (const gchar *path,
-		       gboolean     sensitive)
+annotate_activated (GSimpleAction *action, GVariant *parameter, gpointer data)
 {
-  GtkWidget *widget = gtk_item_factory_get_widget (factory, path);
-  gtk_widget_set_sensitive (widget, sensitive);
+  gboolean state = g_variant_get_boolean (parameter);
+  g_simple_action_set_state (action, parameter);
+  pad_area_set_annotate (pad_area, state);
 }
 
 static void
-update_sensitivity ()
+update_sensitivity (void)
 {
   gboolean have_selected = (kselected.d[0] || kselected.d[1]);
   gboolean have_strokes = (pad_area->strokes != NULL);
 
-  update_path_sensitive ("/Edit/Copy", have_selected);
-  update_path_sensitive ("/Character/Lookup", have_strokes);
-  gtk_widget_set_sensitive (lookup_button, have_strokes);
-  update_path_sensitive ("/Character/Clear", have_strokes);
-  gtk_widget_set_sensitive (clear_button, have_strokes);
-  update_path_sensitive ("/Character/Save", have_strokes);
+  GAction *action;
+  
+  action = g_action_map_lookup_action (G_ACTION_MAP (main_window), "copy");
+  if (action)
+    g_simple_action_set_enabled (G_SIMPLE_ACTION (action), have_selected);
+  
+  action = g_action_map_lookup_action (G_ACTION_MAP (main_window), "lookup");
+  if (action)
+    g_simple_action_set_enabled (G_SIMPLE_ACTION (action), have_strokes);
+  
+  if (lookup_button)
+    gtk_widget_set_sensitive (lookup_button, have_strokes);
+  
+  action = g_action_map_lookup_action (G_ACTION_MAP (main_window), "clear");
+  if (action)
+    g_simple_action_set_enabled (G_SIMPLE_ACTION (action), have_strokes);
+  
+  if (clear_button)
+    gtk_widget_set_sensitive (clear_button, have_strokes);
+  
+  action = g_action_map_lookup_action (G_ACTION_MAP (main_window), "save");
+  if (action)
+    g_simple_action_set_enabled (G_SIMPLE_ACTION (action), have_strokes);
 }
 
 #define BUFLEN 256
@@ -445,8 +486,7 @@
 static gboolean
 engine_input_handler (GIOChannel *source, GIOCondition condition, gpointer data)
 {
-  static gchar *p;
-  static gchar *line;
+  gchar *line = NULL;
   GError *err = NULL;
   GIOStatus status;
   int i;
@@ -465,28 +505,27 @@
       exit (1);
       break;
     case G_IO_STATUS_AGAIN:
-      g_assert_not_reached ();
-      break;
+      return TRUE;
     }
 
-  if (line[0] == 'K')
+  if (line && line[0] == 'K')
     {
       unsigned int t1, t2;
-      p = line+1;
-      for (i=0; i<MAX_GUESSES; i++)
-	{
-	  while (*p && isspace(*p)) p++;
-	  if (!*p || sscanf(p, "%2x%2x", &t1, &t2) != 2)
-	    {
-	      i--;
-	      break;
-	    }
-	  kanjiguess[i].d[0] = t1;
-	  kanjiguess[i].d[1] = t2;
-	  while (*p && !isspace(*p)) p++;
-	}
-      num_guesses = i+1;
-      karea_draw(karea);
+      gchar *p = line + 1;
+      
+      for (i = 0; i < MAX_GUESSES; i++)
+        {
+          while (*p && isspace(*p)) p++;
+          if (!*p || sscanf(p, "%2x%2x", &t1, &t2) != 2)
+            {
+              break;
+            }
+          kanjiguess[i].d[0] = t1;
+          kanjiguess[i].d[1] = t2;
+          while (*p && !isspace(*p)) p++;
+        }
+      num_guesses = i;
+      karea_draw (karea);
     }
 
   g_free (line);
@@ -496,7 +535,7 @@
 
 /* Open the connection to the engine */
 static void 
-init_engine()
+init_engine(void)
 {
   gchar *argv[] = { BINDIR G_DIR_SEPARATOR_S "kpengine", "--data-file", NULL, NULL };
   GError *err = NULL;
@@ -513,21 +552,23 @@
     argv[1] = NULL;
 
   if (!g_spawn_async_with_pipes (NULL, /* working directory */
-				 argv, NULL,	/* argv, envp */
-				 0,
-				 NULL, NULL,	/* child_setup */
-				 &engine_pid,   /* child pid */
-				 &stdin_fd, &stdout_fd, NULL,
-				 &err))
+                                 argv, NULL,   /* argv, envp */
+                                 G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+                                 NULL, NULL,   /* child_setup */
+                                 &engine_pid,  /* child pid */
+                                 &stdin_fd, &stdout_fd, NULL,
+                                 &err))
     {
       GtkWidget *dialog;
 
-      dialog = gtk_message_dialog_new (NULL, 0,
-				       GTK_MESSAGE_ERROR,
-				       GTK_BUTTONS_OK,
-				       "Could not start engine '%s': %s",
-				       argv[0], err->message);
+      dialog = gtk_message_dialog_new (GTK_WINDOW (main_window),
+                                       GTK_DIALOG_DESTROY_WITH_PARENT,
+                                       GTK_MESSAGE_ERROR,
+                                       GTK_BUTTONS_OK,
+                                       "Could not start engine '%s': %s",
+                                       argv[0], err->message);
       gtk_dialog_run (GTK_DIALOG (dialog));
+      gtk_widget_destroy (dialog);
       g_error_free (err);
       exit (1);
     }
@@ -539,170 +580,244 @@
   if (!(from_engine = g_io_channel_unix_new (stdout_fd)))
     g_error ("Couldn't create pipe from child process: %s", g_strerror(errno));
 
+  /* Set encoding to binary to avoid conversion issues */
+  if (to_engine)
+    g_io_channel_set_encoding (to_engine, NULL, NULL);
+  if (from_engine)
+    g_io_channel_set_encoding (from_engine, NULL, NULL);
+
   g_io_add_watch (from_engine, G_IO_IN, engine_input_handler, NULL);
 }
 
+static void
+setup_actions (void)
+{
+  static const GActionEntry actions[] = {
+    { "quit", exit_activated, NULL, NULL, NULL },
+    { "copy", copy_activated, NULL, NULL, NULL },
+    { "lookup", look_up_activated, NULL, NULL, NULL },
+    { "clear", clear_activated, NULL, NULL, NULL },
+    { "save", save_activated, NULL, NULL, NULL },
+    { "annotate", NULL, NULL, "false", annotate_activated }
+  };
+  
+  g_action_map_add_action_entries (G_ACTION_MAP (main_window),
+                                   actions, G_N_ELEMENTS (actions),
+                                   NULL);
+}
+
+static void
+setup_menu (void)
+{
+  GMenu *menu, *file_menu, *edit_menu, *char_menu;
+  GMenuItem *menu_item;
+  
+  menu = g_menu_new ();
   
+  /* File menu */
+  file_menu = g_menu_new ();
+  menu_item = g_menu_item_new ("Quit", "win.quit");
+  g_menu_append_item (file_menu, menu_item);
+  g_object_unref (menu_item);
+  
+  menu_item = g_menu_item_new_submenu ("_File", G_MENU_MODEL (file_menu));
+  g_menu_append_item (menu, menu_item);
+  g_object_unref (menu_item);
+  g_object_unref (file_menu);
+  
+  /* Edit menu */
+  edit_menu = g_menu_new ();
+  menu_item = g_menu_item_new ("_Copy", "win.copy");
+  g_menu_append_item (edit_menu, menu_item);
+  g_object_unref (menu_item);
+  
+  menu_item = g_menu_item_new_submenu ("_Edit", G_MENU_MODEL (edit_menu));
+  g_menu_append_item (menu, menu_item);
+  g_object_unref (menu_item);
+  g_object_unref (edit_menu);
+  
+  /* Character menu */
+  char_menu = g_menu_new ();
+  menu_item = g_menu_item_new ("_Lookup", "win.lookup");
+  g_menu_append_item (char_menu, menu_item);
+  g_object_unref (menu_item);
+  
+  menu_item = g_menu_item_new ("_Clear", "win.clear");
+  g_menu_append_item (char_menu, menu_item);
+  g_object_unref (menu_item);
+  
+  menu_item = g_menu_item_new ("_Save", "win.save");
+  g_menu_append_item (char_menu, menu_item);
+  g_object_unref (menu_item);
+  
+  menu_item = g_menu_item_new (NULL, NULL);
+  g_menu_item_set_attribute (menu_item, "section", "s", "sep");
+  g_menu_append_item (char_menu, menu_item);
+  g_object_unref (menu_item);
+  
+  menu_item = g_menu_item_new ("_Annotate", "win.annotate");
+  g_menu_append_item (char_menu, menu_item);
+  g_object_unref (menu_item);
+  
+  menu_item = g_menu_item_new_submenu ("_Character", G_MENU_MODEL (char_menu));
+  g_menu_append_item (menu, menu_item);
+  g_object_unref (menu_item);
+  g_object_unref (char_menu);
+  
+  gtk_application_set_menubar (GTK_APPLICATION (g_application_get_default ()),
+                               G_MENU_MODEL (menu));
+  g_object_unref (menu);
+}
+
 /* Create Interface */
 
-void
-usage ()
+static void
+usage (void)
 {
   fprintf(stderr, "Usage: %s [-f/--data-file FILE]\n", progname);
   exit (1);
 }
 
-int 
-main (int argc, char **argv)
+static void
+activate_cb (GtkApplication *app, gpointer user_data)
 {
-  GtkWidget *window;
   GtkWidget *main_hbox;
   GtkWidget *vseparator;
   GtkWidget *button;
   GtkWidget *main_vbox;
-  GtkWidget *menubar;
   GtkWidget *vbox;
   GtkWidget *label;
+  GtkWidget *header;
+  GtkStyleContext *context;
   
-  GtkAccelGroup *accel_group;
-
   PangoFontDescription *font_desc;
-  int i;
-  char *p;
-
-  p = progname = argv[0];
-  while (*p)
-    {
-      if (*p == '/') progname = p+1;
-      p++;
-    }
-
-  gtk_init (&argc, &argv);
-
-  for (i=1; i<argc; i++)
-    {
-      if (!strcmp(argv[i], "--data-file") ||
-	  !strcmp(argv[i], "-f"))
-	{
-	  i++;
-	  if (i < argc)
-	    data_file = argv[i];
-	  else
-	    usage();
-	}
-      else
-	{
-	  usage();
-	}
-    }
-
-  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-  gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
-  gtk_window_set_default_size (GTK_WINDOW (window), 350, 350);
-
-  g_signal_connect (window, "destroy",
-		    G_CALLBACK (exit_callback), NULL);
-
-  gtk_window_set_title (GTK_WINDOW(window), "KanjiPad");
   
-  main_vbox = gtk_vbox_new (FALSE, 0);
-  gtk_container_add (GTK_CONTAINER (window), main_vbox);
-  gtk_widget_show (main_vbox);
-
-  /* Menu */
-
-  accel_group = gtk_accel_group_new ();
-  factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", accel_group);
-  gtk_item_factory_create_items (factory, nmenu_items, menu_items, NULL);
-
-  /* create a menubar */
-  menubar = gtk_item_factory_get_widget (factory, "<main>");
-  gtk_box_pack_start (GTK_BOX (main_vbox), menubar,
-		      FALSE, TRUE, 0);
-  gtk_widget_show (menubar);
-
-  /*  Install the accelerator table in the main window  */
-  gtk_window_add_accel_group (GTK_WINDOW (window), accel_group);
-
-  main_hbox = gtk_hbox_new (FALSE, 0);
-  gtk_box_pack_start (GTK_BOX(main_vbox), main_hbox, TRUE, TRUE, 0);
-  gtk_widget_show (main_hbox);
+  main_window = gtk_application_window_new (app);
+  gtk_window_set_title (GTK_WINDOW (main_window), "KanjiPad");
+  gtk_window_set_default_size (GTK_WINDOW (main_window), 350, 350);
+  
+  /* Set up actions */
+  setup_actions ();
+  
+  /* Create header bar */
+  header = gtk_header_bar_new ();
+  gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (header), TRUE);
+  gtk_header_bar_set_title (GTK_HEADER_BAR (header), "KanjiPad");
+  gtk_window_set_titlebar (GTK_WINDOW (main_window), header);
+  
+  main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+  gtk_container_add (GTK_CONTAINER (main_window), main_vbox);
+  
+  main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, TRUE, TRUE, 0);
 
   /* Area for user to draw characters in */
-
   pad_area = pad_area_create ();
-
   gtk_box_pack_start (GTK_BOX (main_hbox), pad_area->widget, TRUE, TRUE, 0);
-  gtk_widget_show (pad_area->widget);
 
-  vseparator = gtk_vseparator_new();
+  vseparator = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
   gtk_box_pack_start (GTK_BOX (main_hbox), vseparator, FALSE, FALSE, 0);
-  gtk_widget_show (vseparator);
   
   /* Area in which to draw guesses */
-
-  vbox = gtk_vbox_new (FALSE, 0);
+  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
   gtk_box_pack_start (GTK_BOX (main_hbox), vbox, FALSE, FALSE, 0);
-  gtk_widget_show (vbox);
 
-  karea = gtk_drawing_area_new();
+  karea = gtk_drawing_area_new ();
+  
+  g_signal_connect (karea, "draw",
+                    G_CALLBACK (karea_draw_cb), NULL);
+  g_signal_connect (karea, "size-allocate",
+                    G_CALLBACK (karea_resize_cb), NULL);
+  g_signal_connect (karea, "button-press-event",
+                    G_CALLBACK (karea_button_press_cb), NULL);
+
+  gtk_widget_set_events (karea, GDK_BUTTON_PRESS_MASK);
 
-  g_signal_connect (karea, "configure_event",
-		    G_CALLBACK (karea_configure_event), NULL);
-  g_signal_connect (karea, "expose_event",
-		    G_CALLBACK (karea_expose_event), NULL);
-  g_signal_connect (karea, "button_press_event",
-		    G_CALLBACK (karea_button_press_event), NULL);
-
-  gtk_widget_set_events (karea, GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK);
-
-#ifdef G_OS_WIN32
-  font_desc = pango_font_description_from_string ("MS Gothic 18");
-#else
   font_desc = pango_font_description_from_string ("Sans 18");
-#endif  
-  gtk_widget_modify_font (karea, font_desc);
+  gtk_widget_override_font (karea, font_desc);
+  pango_font_description_free (font_desc);
   
   gtk_box_pack_start (GTK_BOX (vbox), karea, TRUE, TRUE, 0);
-  gtk_widget_show (karea);
 
   /* Buttons */
-  label = gtk_label_new ("\xe5\xbc\x95");
-  /* We have to set the alignment here, since GTK+ will fail
-   * to get the width of the string appropriately...
-   */
-  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
-  gtk_widget_modify_font (label, font_desc);
-  gtk_widget_show (label);
+  context = gtk_widget_get_style_context (karea);
+  font_desc = pango_font_description_copy (gtk_style_context_get_font (context, GTK_STATE_FLAG_NORMAL));
+  
+  label = gtk_label_new ("\xe5\xbc\x95");  /* "引" - Lookup */
+  gtk_widget_set_halign (label, GTK_ALIGN_START);
+  gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
+  gtk_widget_override_font (label, font_desc);
   
   lookup_button = button = gtk_button_new ();
   gtk_container_add (GTK_CONTAINER (button), label);
-  g_signal_connect (button, "clicked",
-		    G_CALLBACK (look_up_callback), NULL);
+  g_signal_connect_swapped (button, "clicked",
+                            G_CALLBACK (look_up_activated), NULL);
 
   gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
-  gtk_widget_show (button);
   
-  label = gtk_label_new ("\xe6\xb6\x88");
-  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
-  gtk_widget_modify_font (label, font_desc);
-  gtk_widget_show (label);
-
+  label = gtk_label_new ("\xe6\xb6\x88");  /* "消" - Clear */
+  gtk_widget_set_halign (label, GTK_ALIGN_START);
+  gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
+  gtk_widget_override_font (label, font_desc);
+  
   clear_button = button = gtk_button_new ();
   gtk_container_add (GTK_CONTAINER (button), label);
-  g_signal_connect (button, "clicked",
-		    G_CALLBACK (clear_callback), NULL);
+  g_signal_connect_swapped (button, "clicked",
+                            G_CALLBACK (clear_activated), NULL);
 
   gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
-  gtk_widget_show (button);
-
-  gtk_widget_show(window);
 
   pango_font_description_free (font_desc);
 
+  /* Set initial sensitivity */
+  update_sensitivity ();
+  
+  /* Set up menu */
+  setup_menu ();
+  
+  gtk_widget_show_all (main_window);
+
+  /* Initialize engine after UI is created */
   init_engine();
+}
+
+int 
+main (int argc, char **argv)
+{
+  GtkApplication *app;
+  int status;
+  int i;
+  char *p;
+
+  p = progname = argv[0];
+  while (*p)
+    {
+      if (*p == '/') progname = p+1;
+      p++;
+    }
 
-  gtk_main();
+  for (i = 1; i < argc; i++)
+    {
+      if (!strcmp(argv[i], "--data-file") ||
+          !strcmp(argv[i], "-f"))
+        {
+          i++;
+          if (i < argc)
+            data_file = argv[i];
+          else
+            usage();
+        }
+      else
+        {
+          usage();
+        }
+    }
+
+  app = gtk_application_new ("org.gtk.kanjipad", G_APPLICATION_FLAGS_NONE);
+  g_signal_connect (app, "activate", G_CALLBACK (activate_cb), NULL);
+  
+  status = g_application_run (G_APPLICATION (app), argc, argv);
+  g_object_unref (app);
 
-  return 0;
+  return status;
 }
diff -urN kanjipad-2.0.0/kanjipad.h kanjipad-2.0.0.gtk3/kanjipad.h
--- kanjipad-2.0.0/kanjipad.h	2002-03-16 08:09:45.000000000 +0800
+++ kanjipad-2.0.0.gtk3/kanjipad.h	2026-02-15 10:52:40.886573429 +0800
@@ -1,21 +1,22 @@
+#ifndef KANJIPAD_H
+#define KANJIPAD_H
+
 #include <gtk/gtk.h>
 
 typedef struct _PadArea PadArea;
 
 struct _PadArea {
   GtkWidget *widget;
-
-  gint annotate;
-  GList *strokes;
-
-  /* Private */
-  GdkPixmap *pixmap;
-  GList *curstroke;
-  int instroke;
+  GList *strokes;          /* List of strokes (each stroke is a GList of GdkPoint) */
+  GList *curstroke;        /* Current stroke being drawn */
+  gboolean instroke;       /* Whether we're currently drawing a stroke */
+  gboolean annotate;       /* Whether to show stroke numbers */
+  cairo_surface_t *surface; /* Backing surface for drawing */
 };
 
-PadArea *pad_area_create ();
+PadArea *pad_area_create (void);
 void pad_area_clear (PadArea *area);
-void pad_area_set_annotate (PadArea *area, gint annotate);
-
+void pad_area_set_annotate (PadArea *area, gboolean annotate);
 void pad_area_changed_callback (PadArea *area);
+
+#endif
diff -urN kanjipad-2.0.0/Makefile kanjipad-2.0.0.gtk3/Makefile
--- kanjipad-2.0.0/Makefile	2026-02-15 11:09:55.169998892 +0800
+++ kanjipad-2.0.0.gtk3/Makefile	2026-02-15 10:50:55.828211226 +0800
@@ -1,8 +1,8 @@
 OPTIMIZE=-g -Wall
 #OPTIMIZE=-O2 
 
-GTKINC=$(shell pkg-config --cflags gtk+-2.0) -DG_DISABLE_DEPRECATED
-GTKLIBS=$(shell pkg-config --libs gtk+-2.0)
+GTKINC=$(shell pkg-config --cflags gtk+-3.0) -DG_DISABLE_DEPRECATED
+GTKLIBS=$(shell pkg-config --libs gtk+-3.0)
 GLIBLIBS=$(shell pkg-config --libs glib-2.0)
 
 PREFIX=/usr/local
diff -urN kanjipad-2.0.0/padarea.c kanjipad-2.0.0.gtk3/padarea.c
--- kanjipad-2.0.0/padarea.c	2002-03-16 08:09:45.000000000 +0800
+++ kanjipad-2.0.0.gtk3/padarea.c	2026-02-15 11:06:58.735633667 +0800
@@ -3,6 +3,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 
+extern void pad_area_changed_callback (PadArea *area);
+
 static void
 pad_area_free_stroke (GList *stroke)
 {
@@ -15,147 +17,211 @@
   g_list_free (stroke);
 }
 
-
 static void
-pad_area_annotate_stroke (PadArea *area, GList *stroke, gint index)
+pad_area_draw_stroke_number (PadArea *area, GList *stroke, gint index, cairo_t *cr)
 {
   GdkPoint *cur, *old;
+  GList *iter;
+  int num_points = g_list_length (stroke);
+  
+  /* Need at least 2 points to calculate direction */
+  if (num_points < 2)
+    return;
+    
+  /* Get first point */
+  iter = stroke;
+  old = (GdkPoint *)iter->data;
+  
+  /* Find a point that's far enough from the first to get a direction */
+  iter = iter->next;
+  while (iter)
+    {
+      cur = (GdkPoint *)iter->data;
+      if (abs(cur->x - old->x) >= 5 || abs(cur->y - old->y) >= 5)
+        break;
+      iter = iter->next;
+    }
+  
+  if (iter && cur)
+    {
+      char buffer[16];
+      PangoLayout *layout;
+      int swidth, sheight;
+      double x, y;
+      double r;
+      double dx = cur->x - old->x;
+      double dy = cur->y - old->y;
+      double dl = sqrt(dx*dx + dy*dy);
+      int sign = (dy <= dx) ? 1 : -1;
+      GtkAllocation allocation;
 
-  /* Annotate the stroke with the stroke number - the algorithm
-   * for placing the digit is pretty simple. The text is inscribed
-   * in a circle tangent to the stroke. The circle will be above
-   * and/or to the left of the line */
-  if (stroke)
-    {
-      old = (GdkPoint *)stroke->data;
-      
-      do
-	{
-	  cur = (GdkPoint *)stroke->data;
-	  stroke = stroke->next;
-	}
-      while (stroke && abs(cur->x - old->x) < 5 && abs (cur->y - old->y) < 5);
-      
-      if (stroke)
-	{
-	  char buffer[16];
-	  PangoLayout *layout;
-	  int swidth, sheight;
-	  gint16 x, y;
-	  double r;
-	  double dx = cur->x - old->x;
-	  double dy = cur->y - old->y;
-	  double dl = sqrt(dx*dx+dy*dy);
-	  int sign = (dy <= dx) ? 1 : -1;
-	  GdkRectangle update_area;
-
-	  sprintf (buffer, "%d", index);
-	  layout = gtk_widget_create_pango_layout (area->widget, buffer);
-	  pango_layout_get_pixel_size (layout, &swidth, &sheight);
-
-	  r = sqrt(swidth*swidth + sheight*sheight);
-	  
-	  x = 0.5 + old->x + 0.5*r*dx/dl + sign * 0.5*r*dy/dl;
-	  y = 0.5 + old->y + 0.5*r*dy/dl - sign * 0.5*r*dx/dl;
-	  
-	  x -= swidth/2;
-	  y -= sheight/2;
-
-	  update_area.x = x;
-	  update_area.y = y;
-	  update_area.width = swidth;
-	  update_area.height = sheight;
-	  
-	  x = CLAMP (x, 0, area->widget->allocation.width - swidth);
-	  y = CLAMP (y, 0, area->widget->allocation.height - sheight);
-
-	  gdk_draw_layout (area->pixmap, 
-			   area->widget->style->black_gc,
-			   x, y, layout);
-
-	  g_object_unref (layout);
+      gtk_widget_get_allocation (area->widget, &allocation);
+      
+      sprintf (buffer, "%d", index);
+      layout = gtk_widget_create_pango_layout (area->widget, buffer);
+      pango_layout_get_pixel_size (layout, &swidth, &sheight);
 
-	  gdk_window_invalidate_rect (area->widget->window, &update_area, FALSE);
-	}
+      r = sqrt(swidth*swidth + sheight*sheight);
+      
+      x = old->x + 0.5 * r * dx/dl + sign * 0.5 * r * dy/dl;
+      y = old->y + 0.5 * r * dy/dl - sign * 0.5 * r * dx/dl;
+      
+      x -= swidth/2;
+      y -= sheight/2;
+
+      /* Clamp to widget boundaries */
+      x = CLAMP (x, 0, allocation.width - swidth);
+      y = CLAMP (y, 0, allocation.height - sheight);
+
+      /* Draw the number */
+      cairo_move_to (cr, x, y);
+      pango_cairo_show_layout (cr, layout);
+
+      g_object_unref (layout);
     }
 }
 
-static void 
-pad_area_init (PadArea *area)
+static void
+pad_area_redraw_surface (PadArea *area)
 {
+  GtkAllocation allocation;
   GList *tmp_list;
   int index = 1;
+  cairo_t *cr;
   
-  guint16 width = area->widget->allocation.width;
-  guint16 height = area->widget->allocation.height;
-
-  gdk_draw_rectangle (area->pixmap, 
-		      area->widget->style->white_gc, TRUE,
-		      0, 0, width, height);
-
+  if (!area->surface)
+    return;
+    
+  gtk_widget_get_allocation (area->widget, &allocation);
+  
+  cr = cairo_create (area->surface);
+  
+  /* Clear with white background */
+  cairo_set_source_rgb (cr, 1, 1, 1);
+  cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
+  cairo_fill (cr);
+  
+  /* Draw grid if annotate mode */
+  if (area->annotate)
+    {
+      int i;
+      cairo_set_source_rgb (cr, 0.7, 0.7, 1.0);
+      cairo_set_line_width (cr, 0.5);
+      
+      for (i = 0; i < 20; i++)
+        {
+          cairo_move_to (cr, i * 20, 0);
+          cairo_line_to (cr, i * 20, allocation.height);
+          cairo_move_to (cr, 0, i * 20);
+          cairo_line_to (cr, allocation.width, i * 20);
+        }
+      cairo_stroke (cr);
+    }
+  
+  /* Set line properties for strokes */
+  cairo_set_source_rgb (cr, 0, 0, 0);
+  cairo_set_line_width (cr, 2.0);
+  cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
+  cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
+  
+  /* Draw all completed strokes */
   tmp_list = area->strokes;
   while (tmp_list)
     {
-      GdkPoint *cur, *old;
       GList *stroke_list = tmp_list->data;
-
-      old = NULL;
-
+      GdkPoint *old = NULL;
+      GdkPoint *cur;
+      
+      /* Draw stroke numbers if annotate mode */
       if (area->annotate)
-	pad_area_annotate_stroke (area, stroke_list, index);
-
+        {
+          cairo_save (cr);
+          cairo_set_source_rgb (cr, 0, 0, 1); /* Blue for numbers */
+          pad_area_draw_stroke_number (area, stroke_list, index, cr);
+          cairo_restore (cr);
+        }
+      
+      /* Draw the stroke lines */
       while (stroke_list)
-	{
-	  cur = (GdkPoint *)stroke_list->data;
-	  if (old)
-	    gdk_draw_line (area->pixmap, 
-			   area->widget->style->black_gc,
-			   old->x, old->y, cur->x, cur->y);
-
-	  old = cur;
-	  stroke_list = stroke_list->next;
-	}
+        {
+          cur = (GdkPoint *)stroke_list->data;
+          if (old)
+            {
+              cairo_move_to (cr, old->x, old->y);
+              cairo_line_to (cr, cur->x, cur->y);
+              cairo_stroke (cr);
+            }
+          
+          old = cur;
+          stroke_list = stroke_list->next;
+        }
       
       tmp_list = tmp_list->next;
       index++;
     }
-
-  gtk_widget_queue_draw (area->widget);
   
+  /* Draw current stroke if any */
+  if (area->curstroke)
+    {
+      GList *stroke_list = area->curstroke;
+      GdkPoint *old = NULL;
+      GdkPoint *cur;
+      
+      while (stroke_list)
+        {
+          cur = (GdkPoint *)stroke_list->data;
+          if (old)
+            {
+              cairo_move_to (cr, old->x, old->y);
+              cairo_line_to (cr, cur->x, cur->y);
+              cairo_stroke (cr);
+            }
+          
+          old = cur;
+          stroke_list = stroke_list->next;
+        }
+    }
+  
+  cairo_destroy (cr);
+  
+  /* Trigger redraw */
+  gtk_widget_queue_draw (area->widget);
 }
 
-static int
-pad_area_configure_event (GtkWidget *w, GdkEventConfigure *event,
-			  PadArea *area)
+static gboolean
+pad_area_draw_cb (GtkWidget *w, cairo_t *cr, gpointer data)
 {
-  if (area->pixmap)
-    g_object_unref (area->pixmap);
-
-  area->pixmap = gdk_pixmap_new (w->window, event->width, event->height, -1);
-
-  pad_area_init (area);
+  PadArea *area = (PadArea *)data;
   
-  return TRUE;
+  if (area->surface)
+    {
+      cairo_set_source_surface (cr, area->surface, 0, 0);
+      cairo_paint (cr);
+    }
+  
+  return FALSE;
 }
 
-static int
-pad_area_expose_event (GtkWidget *w, GdkEventExpose *event, PadArea *area)
+static void
+pad_area_resize_cb (GtkWidget *w, GtkAllocation *allocation, gpointer data)
 {
-  if (!area->pixmap)
-    return 0;
-
-  gdk_draw_drawable (w->window,
-		     w->style->fg_gc[GTK_STATE_NORMAL], area->pixmap,
-		     event->area.x, event->area.y,
-		     event->area.x, event->area.y,
-		     event->area.width, event->area.height);
-
-  return TRUE;
+  PadArea *area = (PadArea *)data;
+  
+  if (area->surface)
+    cairo_surface_destroy (area->surface);
+    
+  area->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+                                              allocation->width,
+                                              allocation->height);
+  
+  pad_area_redraw_surface (area);
 }
 
-static int
-pad_area_button_press_event (GtkWidget *w, GdkEventButton *event, PadArea *area)
+static gboolean
+pad_area_button_press_cb (GtkWidget *w, GdkEventButton *event, gpointer data)
 {
+  PadArea *area = (PadArea *)data;
+  
   if (event->button == 1)
     {
       GdkPoint *p = g_new (GdkPoint, 1);
@@ -163,110 +229,119 @@
       p->y = event->y;
       area->curstroke = g_list_append (area->curstroke, p);
       area->instroke = TRUE;
+      
+      /* Draw the point immediately */
+      pad_area_redraw_surface (area);
     }
-
+  
   return TRUE;
 }
 
-static int
-pad_area_button_release_event (GtkWidget *w, GdkEventButton *event, PadArea *area)
+static gboolean
+pad_area_button_release_cb (GtkWidget *w, GdkEventButton *event, gpointer data)
 {
-  if (area->annotate)
-    pad_area_annotate_stroke (area, area->curstroke, g_list_length (area->strokes) + 1);
-
-  area->strokes = g_list_append (area->strokes, area->curstroke);
-  area->curstroke = NULL;
-  area->instroke = FALSE;
-
-  pad_area_changed_callback (area);
-
+  PadArea *area = (PadArea *)data;
+  
+  if (area->instroke && area->curstroke)
+    {
+      /* Add the current stroke to the strokes list */
+      area->strokes = g_list_append (area->strokes, area->curstroke);
+      area->curstroke = NULL;
+      area->instroke = FALSE;
+      
+      /* Redraw with the completed stroke */
+      pad_area_redraw_surface (area);
+      
+      /* Notify that the drawing has changed */
+      pad_area_changed_callback (area);
+    }
+  
   return TRUE;
 }
 
-static int
-pad_area_motion_event (GtkWidget *w, GdkEventMotion *event, PadArea *area)
+static gboolean
+pad_area_motion_cb (GtkWidget *w, GdkEventMotion *event, gpointer data)
 {
-  gint x,y;
+  PadArea *area = (PadArea *)data;
+  gdouble x, y;
   GdkModifierType state;
-
+  
+  /* In GTK 3, we can get coordinates directly from the event */
+  x = event->x;
+  y = event->y;
+  state = event->state;
+  
+  /* If it's a hint event, we might need to get the device position,
+   * but modern GTK 3 usually provides coordinates directly */
   if (event->is_hint)
     {
-      gdk_window_get_pointer (w->window, &x, &y, &state);
-    }
-  else
-    {
-      x = event->x;
-      y = event->y;
-      state = event->state;
+      gint int_x, int_y;
+      GdkDevice *device = gdk_event_get_device ((GdkEvent*)event);
+      if (device)
+        {
+          gdk_window_get_device_position (gtk_widget_get_window (w),
+                                         device,
+                                         &int_x, &int_y, &state);
+          x = int_x;
+          y = int_y;
+        }
     }
-
-  if (area->instroke && state & GDK_BUTTON1_MASK)
+  
+  if (area->instroke && (state & GDK_BUTTON1_MASK))
     {
-      GdkRectangle rect;
-      GdkPoint *p;
-      int xmin, ymin, xmax, ymax;
-      GdkPoint *old = (GdkPoint *)g_list_last (area->curstroke)->data;
-
-      gdk_draw_line (area->pixmap, w->style->black_gc,
-		     old->x, old->y, x, y);
-
-      if (old->x < x) { xmin = old->x; xmax = x; }
-      else            { xmin = x;      xmax = old->x; }
-
-      if (old->y < y) { ymin = old->y; ymax = y; }
-      else            { ymin = y;      ymax = old->y; }
-
-      rect.x = xmin - 1; 
-      rect.y = ymin = 1;
-      rect.width  = xmax - xmin + 2;
-      rect.height = ymax - ymin + 2;
-      gdk_window_invalidate_rect (w->window, &rect, FALSE);
-
-      p = g_new (GdkPoint, 1);
+      GdkPoint *p = g_new (GdkPoint, 1);
       p->x = x;
       p->y = y;
       area->curstroke = g_list_append (area->curstroke, p);
+      
+      /* Redraw to show the new line segment */
+      pad_area_redraw_surface (area);
     }
-
+  
   return TRUE;
 }
 
-PadArea *pad_area_create ()
+PadArea *
+pad_area_create (void)
 {
   PadArea *area = g_new (PadArea, 1);
   
-  area->widget = gtk_drawing_area_new();
-  gtk_widget_set_size_request (area->widget, 100, 100);
-
-  g_signal_connect (area->widget, "configure_event",
-		    G_CALLBACK (pad_area_configure_event), area);
-  g_signal_connect (area->widget, "expose_event",
-		    G_CALLBACK (pad_area_expose_event), area);
-  g_signal_connect (area->widget, "button_press_event",
-		    G_CALLBACK (pad_area_button_press_event), area);
-  g_signal_connect (area->widget, "button_release_event",
-		    G_CALLBACK (pad_area_button_release_event), area);
-  g_signal_connect (area->widget, "motion_notify_event",
-		    G_CALLBACK (pad_area_motion_event), area);
-
-  gtk_widget_set_events (area->widget, 
-			 GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK 
-			 | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK 
-			 | GDK_POINTER_MOTION_HINT_MASK);
-
+  area->widget = gtk_drawing_area_new ();
+  gtk_widget_set_size_request (area->widget, 200, 200);
+  
+  g_signal_connect (area->widget, "draw",
+                    G_CALLBACK (pad_area_draw_cb), area);
+  g_signal_connect (area->widget, "size-allocate",
+                    G_CALLBACK (pad_area_resize_cb), area);
+  g_signal_connect (area->widget, "button-press-event",
+                    G_CALLBACK (pad_area_button_press_cb), area);
+  g_signal_connect (area->widget, "button-release-event",
+                    G_CALLBACK (pad_area_button_release_cb), area);
+  g_signal_connect (area->widget, "motion-notify-event",
+                    G_CALLBACK (pad_area_motion_cb), area);
+  
+  gtk_widget_set_events (area->widget,
+                         GDK_EXPOSURE_MASK |
+                         GDK_BUTTON_PRESS_MASK |
+                         GDK_BUTTON_RELEASE_MASK |
+                         GDK_POINTER_MOTION_MASK |
+                         GDK_POINTER_MOTION_HINT_MASK);
+  
   area->strokes = NULL;
   area->curstroke = NULL;
   area->instroke = FALSE;
   area->annotate = FALSE;
-  area->pixmap = NULL;
-
+  area->surface = NULL;
+  
   return area;
 }
 
-void pad_area_clear (PadArea *area)
+void
+pad_area_clear (PadArea *area)
 {
   GList *tmp_list;
-
+  
+  /* Free all strokes */
   tmp_list = area->strokes;
   while (tmp_list)
     {
@@ -275,34 +350,29 @@
     }
   g_list_free (area->strokes);
   area->strokes = NULL;
-
-#if 0
-  tmp_list = thinned;
-  while (tmp_list)
+  
+  /* Free current stroke if any */
+  if (area->curstroke)
     {
-      pad_area_free_stroke (tmp_list->data);
-      tmp_list = tmp_list->next;
+      pad_area_free_stroke (area->curstroke);
+      area->curstroke = NULL;
     }
-  g_list_free (thinned);
-  thinned = NULL;
-#endif
-
-  g_list_free (area->curstroke);
-  area->curstroke = NULL;
-
-  pad_area_init (area);
-
-  pad_area_changed_callback (area);  
+  
+  area->instroke = FALSE;
+  
+  /* Redraw the empty surface */
+  pad_area_redraw_surface (area);
+  
+  /* Notify that the drawing has changed */
+  pad_area_changed_callback (area);
 }
 
-void pad_area_set_annotate (PadArea *area, gint annotate)
+void
+pad_area_set_annotate (PadArea *area, gboolean annotate)
 {
   if (area->annotate != annotate)
     {
       area->annotate = annotate;
-      pad_area_init (area);
+      pad_area_redraw_surface (area);
     }
 }
-
-
-
openSUSE Build Service is sponsored by