]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkfilechooserbutton.c
filechooserbutton: Emit 'selection-changed' when changing the selection programmatically
[~andy/gtk] / gtk / gtkfilechooserbutton.c
index 1bea89bca77683977774ba5eb82fe7a79419a382..0c887e5968d90366090f488ae8c168c59ba36625 100644 (file)
@@ -154,6 +154,7 @@ typedef enum
   ROW_TYPE_CURRENT_FOLDER,
   ROW_TYPE_OTHER_SEPARATOR,
   ROW_TYPE_OTHER,
+  ROW_TYPE_EMPTY_SELECTION,
 
   ROW_TYPE_INVALID = -1
 }
@@ -207,6 +208,9 @@ struct _GtkFileChooserButtonPrivate
   guint  active                       : 1;
 
   guint  focus_on_click               : 1;
+
+  /* Whether the next async callback from GIO should emit the "selection-changed" signal */
+  guint  is_changing_selection        : 1;
 };
 
 
@@ -288,8 +292,9 @@ static gint          model_get_type_position      (GtkFileChooserButton *button,
                                                   RowType               row_type);
 static void          model_free_row_data          (GtkFileChooserButton *button,
                                                   GtkTreeIter          *iter);
-static inline void   model_add_special            (GtkFileChooserButton *button);
-static inline void   model_add_other              (GtkFileChooserButton *button);
+static void          model_add_special            (GtkFileChooserButton *button);
+static void          model_add_other              (GtkFileChooserButton *button);
+static void          model_add_empty_selection    (GtkFileChooserButton *button);
 static void          model_add_volumes            (GtkFileChooserButton *button,
                                                   GSList               *volumes);
 static void          model_add_bookmarks          (GtkFileChooserButton *button,
@@ -324,6 +329,9 @@ static void     fs_bookmarks_changed_cb          (GtkFileSystem  *fs,
 
 static void     combo_box_changed_cb             (GtkComboBox    *combo_box,
                                                  gpointer        user_data);
+static void     combo_box_notify_popup_shown_cb  (GObject        *object,
+                                                 GParamSpec     *pspec,
+                                                 gpointer        user_data);
 
 static void     button_clicked_cb                (GtkButton      *real_button,
                                                  gpointer        user_data);
@@ -521,17 +529,20 @@ gtk_file_chooser_button_init (GtkFileChooserButton *button)
   /* Keep in sync with columns enum, line 88 */
   priv->model =
     GTK_TREE_MODEL (gtk_list_store_new (NUM_COLUMNS,
-                                       GDK_TYPE_PIXBUF, /* Icon */
-                                       G_TYPE_STRING,   /* Display Name */
-                                       G_TYPE_CHAR,     /* Row Type */
-                                       G_TYPE_POINTER   /* Volume || Path */,
-                                       G_TYPE_BOOLEAN   /* Is Folder? */,
-                                       G_TYPE_POINTER   /* cancellable */));
+                                       GDK_TYPE_PIXBUF, /* ICON_COLUMN */
+                                       G_TYPE_STRING,   /* DISPLAY_NAME_COLUMN */
+                                       G_TYPE_CHAR,     /* TYPE_COLUMN */
+                                       G_TYPE_POINTER   /* DATA_COLUMN (Volume || Path) */,
+                                       G_TYPE_BOOLEAN   /* IS_FOLDER_COLUMN */,
+                                       G_TYPE_POINTER   /* CANCELLABLE_COLUMN */));
 
   priv->combo_box = gtk_combo_box_new ();
-  priv->combo_box_changed_id =
-    g_signal_connect (priv->combo_box, "changed",
-                     G_CALLBACK (combo_box_changed_cb), button);
+  priv->combo_box_changed_id = g_signal_connect (priv->combo_box, "changed",
+                                                G_CALLBACK (combo_box_changed_cb), button);
+
+  g_signal_connect (priv->combo_box, "notify::popup-shown",
+                   G_CALLBACK (combo_box_notify_popup_shown_cb), button);
+
   gtk_box_pack_start (GTK_BOX (button), priv->combo_box, TRUE, TRUE, 0);
   gtk_widget_set_halign (priv->combo_box, GTK_ALIGN_FILL);
 
@@ -583,6 +594,18 @@ gtk_file_chooser_button_file_chooser_iface_init (GtkFileChooserIface *iface)
   iface->remove_shortcut_folder = gtk_file_chooser_button_remove_shortcut_folder;
 }
 
+static void
+emit_selection_changed_if_changing_selection (GtkFileChooserButton *button)
+{
+  GtkFileChooserButtonPrivate *priv = button->priv;
+
+  if (priv->is_changing_selection)
+    {
+      priv->is_changing_selection = FALSE;
+      g_signal_emit_by_name (button, "selection-changed");
+    }
+}
+
 static gboolean
 gtk_file_chooser_button_set_current_folder (GtkFileChooser    *chooser,
                                            GFile             *file,
@@ -654,6 +677,8 @@ gtk_file_chooser_button_select_file (GtkFileChooser *chooser,
 
       priv->selection_while_inactive = g_object_ref (file);
 
+      priv->is_changing_selection = TRUE;
+
       update_label_and_image (button);
       update_combo_box (button);
 
@@ -684,6 +709,8 @@ gtk_file_chooser_button_unselect_file (GtkFileChooser *chooser,
              priv->selection_while_inactive = NULL;
            }
 
+         priv->is_changing_selection = TRUE;
+
          update_label_and_image (button);
          update_combo_box (button);
        }
@@ -708,10 +735,10 @@ gtk_file_chooser_button_unselect_all (GtkFileChooser *chooser)
        {
          g_object_unref (priv->selection_while_inactive);
          priv->selection_while_inactive = NULL;
-
-         update_label_and_image (button);
-         update_combo_box (button);
        }
+
+      update_label_and_image (button);
+      update_combo_box (button);
     }
 }
 
@@ -922,6 +949,8 @@ gtk_file_chooser_button_constructor (GType                  type,
 
   model_add_other (button);
 
+  model_add_empty_selection (button);
+
   priv->filter_model = gtk_tree_model_filter_new (priv->model, NULL);
   gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter_model),
                                          filter_model_visible_func,
@@ -946,6 +975,9 @@ gtk_file_chooser_button_constructor (GType                  type,
     g_signal_connect (priv->fs, "bookmarks-changed",
                      G_CALLBACK (fs_bookmarks_changed_cb), object);
 
+  update_label_and_image (button);
+  update_combo_box (button);
+
   return object;
 }
 
@@ -1736,6 +1768,11 @@ model_get_type_position (GtkFileChooserButton *button,
   if (row_type == ROW_TYPE_OTHER)
     return retval;
 
+  retval++;
+
+  if (row_type == ROW_TYPE_EMPTY_SELECTION)
+    return retval;
+
   g_assert_not_reached ();
   return -1;
 }
@@ -1843,7 +1880,7 @@ out:
     g_object_unref (model_cancellable);
 }
 
-static inline void
+static void
 model_add_special (GtkFileChooserButton *button)
 {
   const gchar *homedir;
@@ -2179,7 +2216,7 @@ model_update_current_folder (GtkFileChooserButton *button,
     }
 }
 
-static inline void
+static void
 model_add_other (GtkFileChooserButton *button)
 {
   GtkListStore *store;
@@ -2210,6 +2247,26 @@ model_add_other (GtkFileChooserButton *button)
                      -1);
 }
 
+static void
+model_add_empty_selection (GtkFileChooserButton *button)
+{
+  GtkListStore *store;
+  GtkTreeIter iter;
+  gint pos;
+  
+  store = GTK_LIST_STORE (button->priv->model);
+  pos = model_get_type_position (button, ROW_TYPE_EMPTY_SELECTION);
+
+  gtk_list_store_insert (store, &iter, pos);
+  gtk_list_store_set (store, &iter,
+                     ICON_COLUMN, NULL,
+                     DISPLAY_NAME_COLUMN, _(FALLBACK_DISPLAY_NAME),
+                     TYPE_COLUMN, ROW_TYPE_EMPTY_SELECTION,
+                     DATA_COLUMN, NULL,
+                     IS_FOLDER_COLUMN, FALSE,
+                     -1);
+}
+
 static void
 model_remove_rows (GtkFileChooserButton *button,
                   gint                  pos,
@@ -2237,7 +2294,7 @@ model_remove_rows (GtkFileChooserButton *button,
 }
 
 /* Filter Model */
-static inline gboolean
+static gboolean
 test_if_file_is_visible (GtkFileSystem *fs,
                         GFile         *file,
                         gboolean       local_only,
@@ -2309,6 +2366,34 @@ filter_model_visible_func (GtkTreeModel *model,
          }
       }
       break;
+    case ROW_TYPE_EMPTY_SELECTION:
+      {
+       gboolean popup_shown;
+
+       g_object_get (priv->combo_box,
+                     "popup-shown", &popup_shown,
+                     NULL);
+
+       if (popup_shown)
+         retval = FALSE;
+       else
+         {
+           GFile *selected;
+
+           /* When the combo box is not popped up... */
+
+           selected = get_selected_file (button);
+           if (selected)
+             retval = FALSE; /* ... nonempty selection means the ROW_TYPE_EMPTY_SELECTION is *not* visible... */
+           else
+             retval = TRUE;  /* ... and empty selection means the ROW_TYPE_EMPTY_SELECTION *is* visible */
+
+           if (selected)
+             g_object_unref (selected);
+         }
+
+       break;
+      }
     default:
       retval = TRUE;
       break;
@@ -2350,7 +2435,22 @@ combo_box_row_separator_func (GtkTreeModel *model,
   return (type == ROW_TYPE_BOOKMARK_SEPARATOR ||
          type == ROW_TYPE_CURRENT_FOLDER_SEPARATOR ||
          type == ROW_TYPE_OTHER_SEPARATOR);
-}                        
+}
+
+static void
+select_combo_box_row_no_notify (GtkFileChooserButton *button, int pos)
+{
+  GtkFileChooserButtonPrivate *priv = button->priv;
+  GtkTreeIter iter, filter_iter;
+  
+  gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos);
+  gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter_model),
+                                                   &filter_iter, &iter);
+
+  g_signal_handler_block (priv->combo_box, priv->combo_box_changed_id);
+  gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->combo_box), &filter_iter);
+  g_signal_handler_unblock (priv->combo_box, priv->combo_box_changed_id);
+}
 
 static void
 update_combo_box (GtkFileChooserButton *button)
@@ -2415,24 +2515,26 @@ update_combo_box (GtkFileChooserButton *button)
     }
   while (!row_found && gtk_tree_model_iter_next (priv->filter_model, &iter));
 
-  /* If it hasn't been found already, update & select the current-folder row. */
-  if (!row_found && file)
+  if (!row_found)
     {
-      GtkTreeIter filter_iter;
       gint pos;
-    
-      model_update_current_folder (button, file);
-      gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
 
-      pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER);
-      gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, pos);
+      /* If it hasn't been found already, update & select the current-folder row. */
+      if (file)
+       {
+         model_update_current_folder (button, file);
+         pos = model_get_type_position (button, ROW_TYPE_CURRENT_FOLDER);
+       }
+      else
+       {
+         /* No selection; switch to that row */
 
-      gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter_model),
-                                                       &filter_iter, &iter);
+         pos = model_get_type_position (button, ROW_TYPE_EMPTY_SELECTION);
+       }
 
-      g_signal_handler_block (priv->combo_box, priv->combo_box_changed_id);
-      gtk_combo_box_set_active_iter (GTK_COMBO_BOX (priv->combo_box), &filter_iter);
-      g_signal_handler_unblock (priv->combo_box, priv->combo_box_changed_id);
+      gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
+
+      select_combo_box_row_no_notify (button, pos);
     }
 
   if (file)
@@ -2473,6 +2575,8 @@ update_label_get_info_cb (GCancellable *cancellable,
     g_object_unref (pixbuf);
 
 out:
+  emit_selection_changed_if_changing_selection (button);
+
   g_object_unref (button);
   g_object_unref (cancellable);
 }
@@ -2483,10 +2587,12 @@ update_label_and_image (GtkFileChooserButton *button)
   GtkFileChooserButtonPrivate *priv = button->priv;
   gchar *label_text;
   GFile *file;
+  gboolean done_changing_selection;
 
   file = get_selected_file (button);
 
   label_text = NULL;
+  done_changing_selection = FALSE;
 
   if (priv->update_button_cancellable)
     {
@@ -2524,7 +2630,10 @@ update_label_and_image (GtkFileChooserButton *button)
           _gtk_file_system_volume_unref (volume);
 
           if (label_text)
+           {
+             done_changing_selection = TRUE;
              goto out;
+           }
         }
 
       if (g_file_is_native (file))
@@ -2546,8 +2655,16 @@ update_label_and_image (GtkFileChooserButton *button)
           gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), pixbuf);
           if (pixbuf)
             g_object_unref (pixbuf);
+
+         done_changing_selection = TRUE;
         }
     }
+  else
+    {
+      /* We know the selection is empty */
+      done_changing_selection = TRUE;
+    }
+
 out:
 
   if (file)
@@ -2563,6 +2680,9 @@ out:
       gtk_label_set_text (GTK_LABEL (priv->label), _(FALLBACK_DISPLAY_NAME));
       gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), NULL);
     }
+
+  if (done_changing_selection)
+    emit_selection_changed_if_changing_selection (button);
 }
 
 
@@ -2720,21 +2840,17 @@ combo_box_changed_cb (GtkComboBox *combo_box,
        case ROW_TYPE_SHORTCUT:
        case ROW_TYPE_BOOKMARK:
        case ROW_TYPE_CURRENT_FOLDER:
-         gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
          if (data)
-           gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (priv->dialog),
-                                                     data, NULL);
+           gtk_file_chooser_button_select_file (GTK_FILE_CHOOSER (button), data, NULL);
          break;
        case ROW_TYPE_VOLUME:
          {
            GFile *base_file;
 
-           gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->dialog));
            base_file = _gtk_file_system_volume_get_root (data);
            if (base_file)
              {
-               gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (priv->dialog),
-                                                         base_file, NULL);
+               gtk_file_chooser_button_select_file (GTK_FILE_CHOOSER (button), base_file, NULL);
                g_object_unref (base_file);
              }
          }
@@ -2748,6 +2864,50 @@ combo_box_changed_cb (GtkComboBox *combo_box,
     }
 }
 
+/* Calback for the "notify::popup-shown" signal on the combo box.
+ * When the combo is popped up, we don't want the ROW_TYPE_EMPTY_SELECTION to be visible
+ * at all; otherwise we would be showing a "(None)" item in the combo box's popup.
+ *
+ * However, when the combo box is *not* popped up, we want the empty-selection row
+ * to be visible depending on the selection.
+ *
+ * Since all that is done through the filter_model_visible_func(), this means
+ * that we need to refilter the model when the combo box pops up - hence the
+ * present signal handler.
+ */
+static void
+combo_box_notify_popup_shown_cb (GObject    *object,
+                                GParamSpec *pspec,
+                                gpointer    user_data)
+{
+  GtkFileChooserButton *button = GTK_FILE_CHOOSER_BUTTON (user_data);
+  GtkFileChooserButtonPrivate *priv = button->priv;
+  gboolean popup_shown;
+
+  g_object_get (priv->combo_box,
+               "popup-shown", &popup_shown,
+               NULL);
+
+  /* Indicate that the ROW_TYPE_EMPTY_SELECTION will change visibility... */
+  gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter_model));
+
+  /* If the combo box popup got dismissed, go back to showing the ROW_TYPE_EMPTY_SELECTION if needed */
+  if (!popup_shown)
+    {
+      GFile *selected = get_selected_file (button);
+
+      if (!selected)
+       {
+         int pos;
+
+         pos = model_get_type_position (button, ROW_TYPE_EMPTY_SELECTION);
+         select_combo_box_row_no_notify (button, pos);
+       }
+      else
+       g_object_unref (selected);
+    }
+}
+
 /* Button */
 static void
 button_clicked_cb (GtkButton *real_button,
@@ -2859,9 +3019,6 @@ dialog_response_cb (GtkDialog *dialog,
 
       g_signal_emit_by_name (button, "current-folder-changed");
       g_signal_emit_by_name (button, "selection-changed");
-
-      update_label_and_image (button);
-      update_combo_box (button);
     }
   else
     {
@@ -2879,6 +3036,9 @@ dialog_response_cb (GtkDialog *dialog,
       priv->active = FALSE;
     }
 
+  update_label_and_image (button);
+  update_combo_box (button);
+
   gtk_widget_set_sensitive (priv->combo_box, TRUE);
   gtk_widget_hide (priv->dialog);