]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkentrycompletion.c
Don't call gtk_tree_view_scroll_to_cell() on an empty tree view. It
[~andy/gtk] / gtk / gtkentrycompletion.c
index 73f642189f9d2f157e7ad4a665a0b2ebb787dafc..680ab815b123633921113b3ae47f635c36889058 100644 (file)
  * Boston, MA 02111-1307, USA.
  */
 
-#include <gtk/gtkentrycompletion.h>
-#include <gtk/gtkentryprivate.h>
-#include <gtk/gtkcelllayout.h>
-
-#include <gtk/gtkintl.h>
-#include <gtk/gtkcellrenderertext.h>
-#include <gtk/gtktreeselection.h>
-#include <gtk/gtktreeview.h>
-#include <gtk/gtkscrolledwindow.h>
-#include <gtk/gtkvbox.h>
-#include <gtk/gtkwindow.h>
-#include <gtk/gtkentry.h>
-#include <gtk/gtkmain.h>
-#include <gtk/gtksignal.h>
-#include <gtk/gtkmarshalers.h>
+#include <config.h>
+#include "gtkentrycompletion.h"
+#include "gtkentryprivate.h"
+#include "gtkcelllayout.h"
+
+#include "gtkintl.h"
+#include "gtkcellrenderertext.h"
+#include "gtkframe.h"
+#include "gtktreeselection.h"
+#include "gtktreeview.h"
+#include "gtkscrolledwindow.h"
+#include "gtkvbox.h"
+#include "gtkwindow.h"
+#include "gtkentry.h"
+#include "gtkmain.h"
+#include "gtksignal.h"
+#include "gtkmarshalers.h"
 
 #include <string.h>
 
@@ -52,6 +54,7 @@ enum
   PROP_MINIMUM_KEY_LENGTH
 };
 
+#define GTK_ENTRY_COMPLETION_GET_PRIVATE(obj)(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_ENTRY_COMPLETION, GtkEntryCompletionPrivate))
 
 static void     gtk_entry_completion_class_init          (GtkEntryCompletionClass *klass);
 static void     gtk_entry_completion_cell_layout_init    (GtkCellLayoutIface      *iface);
@@ -84,6 +87,9 @@ static void     gtk_entry_completion_set_cell_data_func  (GtkCellLayout
                                                           GDestroyNotify           destroy);
 static void     gtk_entry_completion_clear_attributes    (GtkCellLayout           *cell_layout,
                                                           GtkCellRenderer         *cell);
+static void     gtk_entry_completion_reorder             (GtkCellLayout           *cell_layout,
+                                                          GtkCellRenderer         *cell,
+                                                          gint                     position);
 
 static gboolean gtk_entry_completion_visible_func        (GtkTreeModel            *model,
                                                           GtkTreeIter             *iter,
@@ -114,6 +120,7 @@ static void     gtk_entry_completion_action_data_func    (GtkTreeViewColumn
                                                           gpointer                 data);
 
 
+static GObjectClass *parent_class = NULL;
 static guint entry_completion_signals[LAST_SIGNAL] = { 0 };
 
 
@@ -161,12 +168,26 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass)
 {
   GObjectClass *object_class;
 
+  parent_class = g_type_class_peek_parent (klass);
   object_class = (GObjectClass *)klass;
 
   object_class->set_property = gtk_entry_completion_set_property;
   object_class->get_property = gtk_entry_completion_get_property;
   object_class->finalize = gtk_entry_completion_finalize;
 
+  /**
+   * GtkEntryCompletion::match-selected:
+   * @widget: the object which received the signal
+   * @model: the #GtkTreeModel containing the matches
+   * @iter: a #GtkTreeIter positioned at the selected match
+   * 
+   * The ::match-selected signal is emitted when a match from the list
+   * is selected. The default behaviour is to replace the contents of the
+   * entry with the contents of the text column in the row pointed to by
+   * @iter.
+   *
+   * Return value: %TRUE if the signal has been handled
+   */ 
   entry_completion_signals[MATCH_SELECTED] =
     g_signal_new ("match_selected",
                   G_TYPE_FROM_CLASS (klass),
@@ -177,6 +198,15 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass)
                   G_TYPE_BOOLEAN, 2,
                   GTK_TYPE_TREE_MODEL,
                   GTK_TYPE_TREE_ITER);
+                 
+  /**
+   * GtkEntryCompletion::action-activated:
+   * @widget: the object which received the signal
+   * @index: the index of the activated action
+   *
+   * The ::action-activated signal is emitted when an action
+   * is activated.
+   */
   entry_completion_signals[ACTION_ACTIVATED] =
     g_signal_new ("action_activated",
                   G_TYPE_FROM_CLASS (klass),
@@ -190,15 +220,15 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass)
   g_object_class_install_property (object_class,
                                    PROP_MODEL,
                                    g_param_spec_object ("model",
-                                                        _("Completion Model"),
-                                                        _("The model to find matches in"),
+                                                        P_("Completion Model"),
+                                                        P_("The model to find matches in"),
                                                         GTK_TYPE_TREE_MODEL,
                                                         G_PARAM_READWRITE));
   g_object_class_install_property (object_class,
                                    PROP_MINIMUM_KEY_LENGTH,
                                    g_param_spec_int ("minimum_key_length",
-                                                     _("Minimum Key Length"),
-                                                     _("Minimum length of the search key in order to look up matches"),
+                                                     P_("Minimum Key Length"),
+                                                     P_("Minimum length of the search key in order to look up matches"),
                                                      -1,
                                                      G_MAXINT,
                                                      1,
@@ -216,6 +246,7 @@ gtk_entry_completion_cell_layout_init (GtkCellLayoutIface *iface)
   iface->add_attribute = gtk_entry_completion_add_attribute;
   iface->set_cell_data_func = gtk_entry_completion_set_cell_data_func;
   iface->clear_attributes = gtk_entry_completion_clear_attributes;
+  iface->reorder = gtk_entry_completion_reorder;
 }
 
 static void
@@ -224,6 +255,7 @@ gtk_entry_completion_init (GtkEntryCompletion *completion)
   GtkCellRenderer *cell;
   GtkTreeSelection *sel;
   GtkEntryCompletionPrivate *priv;
+  GtkWidget *popup_frame;
 
   /* yes, also priv, need to keep the code readable */
   priv = completion->priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (completion);
@@ -256,8 +288,10 @@ gtk_entry_completion_init (GtkEntryCompletion *completion)
                                   GTK_POLICY_NEVER,
                                   GTK_POLICY_AUTOMATIC);
   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->scrolled_window),
-                                       GTK_SHADOW_ETCHED_IN);
+                                       GTK_SHADOW_NONE);
 
+  /* a nasty hack to get the completions treeview to size nicely */
+  gtk_widget_set_size_request (GTK_SCROLLED_WINDOW (priv->scrolled_window)->vscrollbar, -1, 0);
 
   /* actions */
   priv->actions = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN);
@@ -274,9 +308,6 @@ gtk_entry_completion_init (GtkEntryCompletion *completion)
   gtk_tree_selection_unselect_all (sel);
 
   cell = gtk_cell_renderer_text_new ();
-  g_object_set (cell, "cell_background_gdk",
-                &priv->tree_view->style->bg[GTK_STATE_NORMAL],
-                NULL);
   gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (priv->action_view),
                                               0, "",
                                               cell,
@@ -294,8 +325,14 @@ gtk_entry_completion_init (GtkEntryCompletion *completion)
                     G_CALLBACK (gtk_entry_completion_popup_button_press),
                     completion);
 
+  popup_frame = gtk_frame_new (NULL);
+  gtk_frame_set_shadow_type (GTK_FRAME (popup_frame),
+                            GTK_SHADOW_ETCHED_IN);
+  gtk_widget_show (popup_frame);
+  gtk_container_add (GTK_CONTAINER (priv->popup_window), popup_frame);
+  
   priv->vbox = gtk_vbox_new (FALSE, 0);
-  gtk_container_add (GTK_CONTAINER (priv->popup_window), priv->vbox);
+  gtk_container_add (GTK_CONTAINER (popup_frame), priv->vbox);
 
   gtk_container_add (GTK_CONTAINER (priv->scrolled_window), priv->tree_view);
   gtk_box_pack_start (GTK_BOX (priv->vbox), priv->scrolled_window,
@@ -377,6 +414,8 @@ gtk_entry_completion_finalize (GObject *object)
 
   if (completion->priv->popup_window)
     gtk_widget_destroy (completion->priv->popup_window);
+
+  G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
 /* implement cell layout interface */
@@ -465,6 +504,20 @@ gtk_entry_completion_clear_attributes (GtkCellLayout   *cell_layout,
   gtk_tree_view_column_clear_attributes (priv->column, cell);
 }
 
+static void
+gtk_entry_completion_reorder (GtkCellLayout   *cell_layout,
+                              GtkCellRenderer *cell,
+                              gint             position)
+{
+  GtkEntryCompletionPrivate *priv;
+
+  g_return_if_fail (GTK_IS_ENTRY_COMPLETION (cell_layout));
+
+  priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (cell_layout);
+
+  gtk_cell_layout_reorder (GTK_CELL_LAYOUT (priv->column), cell, position);
+}
+
 /* all those callbacks */
 static gboolean
 gtk_entry_completion_default_completion_func (GtkEntryCompletion *completion,
@@ -486,15 +539,18 @@ gtk_entry_completion_default_completion_func (GtkEntryCompletion *completion,
                       completion->priv->text_column, &item,
                       -1);
 
-  normalized_string = g_utf8_normalize (item, -1, G_NORMALIZE_ALL);
-  case_normalized_string = g_utf8_casefold (normalized_string, -1);
-
-  if (!strncmp (key, case_normalized_string, strlen (key)))
-      ret = TRUE;
-
-  g_free (item);
-  g_free (normalized_string);
-  g_free (case_normalized_string);
+  if (item != NULL)
+    {
+      normalized_string = g_utf8_normalize (item, -1, G_NORMALIZE_ALL);
+      case_normalized_string = g_utf8_casefold (normalized_string, -1);
+      
+      if (!strncmp (key, case_normalized_string, strlen (key)))
+       ret = TRUE;
+      
+      g_free (item);
+      g_free (normalized_string);
+      g_free (case_normalized_string);
+    }
 
   return ret;
 }
@@ -511,17 +567,16 @@ gtk_entry_completion_visible_func (GtkTreeModel *model,
   if (!completion->priv->case_normalized_key)
     return ret;
 
-  if (completion->priv->text_column >= 0)
-    ret = gtk_entry_completion_default_completion_func (completion,
-                                                        completion->priv->case_normalized_key,
-                                                        iter,
-                                                        NULL);
-
-  else if (completion->priv->match_func)
+  if (completion->priv->match_func)
     ret = (* completion->priv->match_func) (completion,
                                             completion->priv->case_normalized_key,
                                             iter,
                                             completion->priv->match_data);
+  else if (completion->priv->text_column >= 0)
+    ret = gtk_entry_completion_default_completion_func (completion,
+                                                        completion->priv->case_normalized_key,
+                                                        iter,
+                                                        NULL);
 
   return ret;
 }
@@ -580,9 +635,13 @@ gtk_entry_completion_list_button_press (GtkWidget      *widget,
                                &iter, path);
       gtk_tree_path_free (path);
 
+      g_signal_handler_block (completion->priv->entry,
+                             completion->priv->changed_id);
       g_signal_emit (completion, entry_completion_signals[MATCH_SELECTED],
                      0, GTK_TREE_MODEL (completion->priv->filter_model),
                      &iter, &entry_set);
+      g_signal_handler_unblock (completion->priv->entry,
+                               completion->priv->changed_id);
 
       if (!entry_set)
         {
@@ -667,6 +726,8 @@ gtk_entry_completion_action_data_func (GtkTreeViewColumn *tree_column,
                   "markup", NULL,
                   "text", string,
                   NULL);
+
+  g_free (string);
 }
 
 static void
@@ -678,7 +739,8 @@ gtk_entry_completion_selection_changed (GtkTreeSelection *selection,
   if (completion->priv->first_sel_changed)
     {
       completion->priv->first_sel_changed = FALSE;
-      gtk_tree_selection_unselect_all (selection);
+      if (gtk_widget_is_focus (completion->priv->tree_view))
+        gtk_tree_selection_unselect_all (selection);
     }
 }
 
@@ -968,11 +1030,13 @@ gtk_entry_completion_delete_action (GtkEntryCompletion *completion,
  * @completion: A #GtkEntryCompletion.
  * @column: The column in the model of @completion to get strings from.
  *
- * Conviencefunction for setting up the most used case of this code: a
+ * Convenience function for setting up the most used case of this code: a
  * completion list with just strings. This function will set up @completion
  * to have a list displaying all (and just) strings in the completion list,
  * and to get those strings from @column in the model of @completion.
  *
+ * This functions creates and adds a GtkCellRendererText for the selected column.
+
  * Since: 2.4
  */
 void
@@ -1029,38 +1093,41 @@ get_borders (GtkEntry *entry,
     }
 }
 
-/* this function is a bit nasty */
-void
-_gtk_entry_completion_popup (GtkEntryCompletion *completion)
+/* some nasty size requisition */
+gboolean
+_gtk_entry_completion_resize_popup (GtkEntryCompletion *completion)
 {
-  gint x, y, x_border, y_border;
-  gint items;
-  gint height;
-
-  if (GTK_WIDGET_MAPPED (completion->priv->popup_window))
-    return;
-
-  completion->priv->first_sel_changed = TRUE;
-
-  gtk_widget_show_all (completion->priv->vbox);
+  gint x, y;
+  gint matches, items, height, x_border, y_border;
+  GdkScreen *screen;
+  gint monitor_num;
+  GdkRectangle monitor;
+  GtkRequisition popup_req;
+  GtkTreePath *path;
+  gboolean above;
 
   gdk_window_get_origin (completion->priv->entry->window, &x, &y);
   get_borders (GTK_ENTRY (completion->priv->entry), &x_border, &y_border);
 
-  items = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->filter_model), NULL);
+  x += x_border;
+  y += 2 * y_border;
+
+  matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->filter_model), NULL);
 
-  items = MIN (items, 15);
+  items = MIN (matches, 15);
 
   gtk_tree_view_column_cell_get_size (completion->priv->column, NULL,
                                       NULL, NULL, NULL, &height);
 
+  if (items <= 0)
+    gtk_widget_hide (completion->priv->scrolled_window);
+  else
+    gtk_widget_show (completion->priv->scrolled_window);
+
   gtk_widget_set_size_request (completion->priv->tree_view,
                                completion->priv->entry->allocation.width - 2 * x_border,
                                items * height);
 
-  if (items <= 0)
-    gtk_widget_hide (completion->priv->scrolled_window);
-
   /* default on no match */
   completion->priv->current_selected = -1;
 
@@ -1068,6 +1135,8 @@ _gtk_entry_completion_popup (GtkEntryCompletion *completion)
 
   if (items)
     {
+      gtk_widget_show (completion->priv->action_view);
+
       gtk_tree_view_column_cell_get_size (gtk_tree_view_get_column (GTK_TREE_VIEW (completion->priv->action_view), 0),
                                           NULL, NULL, NULL, NULL,
                                           &height);
@@ -1076,11 +1145,67 @@ _gtk_entry_completion_popup (GtkEntryCompletion *completion)
                                    completion->priv->entry->allocation.width - 2 * x_border,
                                    items * height);
     }
+  else
+    gtk_widget_hide (completion->priv->action_view);
+
+  gtk_widget_size_request (completion->priv->popup_window, &popup_req);
+
+  screen = gtk_widget_get_screen (GTK_WIDGET (completion->priv->entry));
+  monitor_num = gdk_screen_get_monitor_at_window (screen, 
+                                                 GTK_WIDGET (completion->priv->entry)->window);
+  gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+  
+  if (x < monitor.x)
+    x = monitor.x;
+  else if (x + popup_req.width > monitor.x + monitor.width)
+    x = monitor.x + monitor.width - popup_req.width;
+  
+  if (y + height + popup_req.height <= monitor.y + monitor.height)
+    {
+      y += height;
+      above = FALSE;
+    }
+  else
+    {
+      y -= popup_req.height;
+      above = TRUE;
+    }
+  
+  if (matches > 0) 
+    {
+      path = gtk_tree_path_new_from_indices (above ? matches - 1 : 0, -1);
+      gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (completion->priv->tree_view), path, 
+                                   NULL, FALSE, 0.0, 0.0);
+      gtk_tree_path_free (path);
+    }
 
-  x += x_border;
-  y += 2 * y_border;
+  gtk_window_move (GTK_WINDOW (completion->priv->popup_window), x, y);
+
+  return above;
+}
+
+void
+_gtk_entry_completion_popup (GtkEntryCompletion *completion)
+{
+  GtkTreeViewColumn *column;
+  GList *renderers;
+
+  if (GTK_WIDGET_MAPPED (completion->priv->popup_window))
+    return;
+
+  completion->priv->may_wrap = TRUE;
+
+  column = gtk_tree_view_get_column (GTK_TREE_VIEW (completion->priv->action_view), 0);
+  renderers = gtk_tree_view_column_get_cell_renderers (column);
+  gtk_widget_ensure_style (completion->priv->tree_view);
+  g_object_set (GTK_CELL_RENDERER (renderers->data), "cell_background_gdk",
+                &completion->priv->tree_view->style->bg[GTK_STATE_NORMAL],
+                NULL);
+  g_list_free (renderers);
+
+  gtk_widget_show_all (completion->priv->vbox);
 
-  gtk_window_move (GTK_WINDOW (completion->priv->popup_window), x, y + height);
+  _gtk_entry_completion_resize_popup (completion);
 
   gtk_widget_show (completion->priv->popup_window);