summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog-2000041433
-rw-r--r--libnautilus-extensions/nautilus-list.c528
-rw-r--r--libnautilus-private/nautilus-list.c528
-rw-r--r--libnautilus/nautilus-list.c528
4 files changed, 1281 insertions, 336 deletions
diff --git a/ChangeLog-20000414 b/ChangeLog-20000414
index 6bd43da8d..ae9c975be 100644
--- a/ChangeLog-20000414
+++ b/ChangeLog-20000414
@@ -1,3 +1,36 @@
+2000-04-09 John Sullivan <sullivan@eazel.com>
+
+ Finished task 307 (keyboard navigation in list view should
+ use same keys/behavior as in icon view). There are a few
+ minor bugs remaining that I'll write up separately.
+
+ * libnautilus/nautilus-list.c:
+ NautilusListDetails struct: added fields for
+ keyboard_row_to_reveal and keyboard_row_reveal_timer_id;
+ (nautilus_list_initialize_class): Removed key binding set for
+ this class (uses key_press_event function instead); used
+ gtk_binding_entry_clear to get rid of all the unwanted GtkCList
+ key bindings; wire up key_press_event function; wire up
+ destroy function
+ (nautilus_list_destroy): New function, destroy details struct
+ (this was storage leak) and clean up pending timer.
+ (nautilus_list_clear_keyboard_focus), (nautilus_list_set_keyboard_focus),
+ (nautilus_list_keyboard_move_to), (keyboard_row_reveal_timeout_callback),
+ (unschedule_keyboard_row_reveal), (schedule_keyboard_row_reveal),
+ (reveal_row), (nautilus_list_keyboard_navigation_key_press),
+ (nautilus_list_keyboard_home), (nautilus_list_keyboard_end),
+ (nautilus_list_keyboard_up), (nautilus_list_keyboard_down),
+ (nautilus_list_keyboard_page_up), (nautilus_list_keyboard_page_down),
+ (nautilus_list_keyboard_space), (nautilus_list_activate_selected_items),
+ (nautilus_list_get_first_selected_row),
+ (nautilus_list_get_last_selected_row),
+ (nautilus_list_key_press): New functions, all the mechanism for
+ handling key presses similarly to NautilusIconContainer. Bonus
+ key handling for Page Up and Page Down.
+ (nautilus_list_move_focus_row), (scroll_vertical): Removed these
+ functions that were used by the GtkCList key bindings in favor of
+ nautilus_list_key_press and friends.
+
2000-04-07 Andy Hertzfeld <andy@eazel.com>
* libnautilus/nautilus-icon-canvas-item.c:
diff --git a/libnautilus-extensions/nautilus-list.c b/libnautilus-extensions/nautilus-list.c
index 3165c1083..4187270c2 100644
--- a/libnautilus-extensions/nautilus-list.c
+++ b/libnautilus-extensions/nautilus-list.c
@@ -32,6 +32,8 @@
#include <gdk/gdkkeysyms.h>
#include <gtk/gtkbindings.h>
#include <gtk/gtkdnd.h>
+#include <gtk/gtkenums.h>
+#include <gtk/gtkmain.h>
#include <glib.h>
#include "nautilus-gdk-extensions.h"
@@ -42,6 +44,12 @@
#include "nautilus-background.h"
#include "nautilus-list-column-title.h"
+/* Timeout for making the row currently selected for keyboard operation
+ * visible. FIXME: This *must* be higher than the double-click time in GDK,
+ * but there is no way to access its value from outside.
+ */
+#define KEYBOARD_ROW_REVEAL_TIMEOUT 300
+
struct NautilusListDetails
{
/* Preferences */
@@ -56,6 +64,13 @@ struct NautilusListDetails
int button_down_row;
guint32 button_down_time;
+ /* Timeout used to make a selected row fully visible after a short
+ * period of time. (The timeout is needed to make sure
+ * double-clicking still works, and to optimize holding down arrow key.)
+ */
+ guint keyboard_row_reveal_timer_id;
+ int keyboard_row_to_reveal;
+
/* Delayed selection information */
int dnd_select_pending;
guint dnd_select_pending_state;
@@ -134,6 +149,7 @@ static void get_cell_style (GtkCList *clist, GtkCListRow *clist_row,
static void nautilus_list_initialize_class (NautilusListClass *class);
static void nautilus_list_initialize (NautilusList *list);
+static void nautilus_list_destroy (GtkObject *object);
static gint nautilus_list_button_press (GtkWidget *widget, GdkEventButton *event);
static gint nautilus_list_button_release (GtkWidget *widget, GdkEventButton *event);
@@ -150,7 +166,16 @@ static gboolean nautilus_list_drag_drop (GtkWidget *widget, GdkDragContext *cont
static void nautilus_list_drag_data_received (GtkWidget *widget, GdkDragContext *context,
gint x, gint y, GtkSelectionData *data,
guint info, guint time);
+static void nautilus_list_clear_keyboard_focus (NautilusList *list);
static void nautilus_list_draw_focus (GtkWidget *widget);
+static int nautilus_list_get_first_selected_row (NautilusList *list);
+static int nautilus_list_get_last_selected_row (NautilusList *list);
+static gint nautilus_list_key_press (GtkWidget *widget,
+ GdkEventKey *event);
+
+static void reveal_row (NautilusList *list, int row);
+static void schedule_keyboard_row_reveal (NautilusList *list, int row);
+static void unschedule_keyboard_row_reveal (NautilusList *list);
static void select_or_unselect_row_callback (GtkCList *clist, gint row, gint column,
GdkEvent *event);
@@ -158,9 +183,6 @@ static void nautilus_list_clear (GtkCList *clist);
static void draw_row (GtkCList *list, GdkRectangle *area, gint row, GtkCListRow *clist_row);
static void nautilus_list_realize (GtkWidget *widget);
-static void nautilus_list_scroll_vertical (GtkCList *clist,
- GtkScrollType scroll_type,
- gfloat position);
static void nautilus_list_set_cell_contents (GtkCList *clist,
GtkCListRow *clist_row,
gint column,
@@ -191,7 +213,6 @@ nautilus_list_initialize_class (NautilusListClass *klass)
NautilusListClass *list_class;
GtkBindingSet *clist_binding_set;
- GtkBindingSet *list_binding_set;
object_class = (GtkObjectClass *) klass;
widget_class = (GtkWidgetClass *) klass;
@@ -239,14 +260,39 @@ nautilus_list_initialize_class (NautilusListClass *klass)
gtk_object_class_add_signals (object_class, list_signals, LAST_SIGNAL);
- /* Add new key bindings. Some of these clobber GtkCList ones that we don't like. */
- list_binding_set = gtk_binding_set_by_class (list_class);
- gtk_binding_entry_add_signal (list_binding_set, GDK_space, GDK_CONTROL_MASK,
- "toggle_focus_row", 0);
-
- /* Turn off the GtkCList key bindings that we want entirely unbound. */
+ /* Turn off the GtkCList key bindings that we want unbound.
+ * We only need to do this for the keys that we don't handle
+ * in nautilus_list_key_press. These extra ones are turned off
+ * to avoid inappropriate GtkCList code and to standardize the
+ * keyboard behavior in Nautilus.
+ */
clist_binding_set = gtk_binding_set_by_class (clist_class);
- gtk_binding_entry_clear (clist_binding_set, GDK_space, 0);
+
+ /* Use Control-A for Select All, not Control-/ */
+ gtk_binding_entry_clear (clist_binding_set,
+ '/',
+ GDK_CONTROL_MASK);
+ /* Don't use Control-\ for Unselect All (maybe invent Nautilus
+ * standard for this?) */
+ gtk_binding_entry_clear (clist_binding_set,
+ '\\',
+ GDK_CONTROL_MASK);
+ /* Hide GtkCList's weird extend-selection-from-keyboard stuff.
+ * Users can use control-navigation and control-space to create
+ * extended selections.
+ */
+ gtk_binding_entry_clear (clist_binding_set,
+ GDK_Shift_L,
+ GDK_RELEASE_MASK | GDK_SHIFT_MASK);
+ gtk_binding_entry_clear (clist_binding_set,
+ GDK_Shift_R,
+ GDK_RELEASE_MASK | GDK_SHIFT_MASK);
+ gtk_binding_entry_clear (clist_binding_set,
+ GDK_Shift_L,
+ GDK_RELEASE_MASK | GDK_SHIFT_MASK | GDK_CONTROL_MASK);
+ gtk_binding_entry_clear (clist_binding_set,
+ GDK_Shift_R,
+ GDK_RELEASE_MASK | GDK_SHIFT_MASK | GDK_CONTROL_MASK);
list_class->column_resize_track_start = nautilus_list_column_resize_track_start;
list_class->column_resize_track = nautilus_list_column_resize_track;
@@ -255,7 +301,6 @@ nautilus_list_initialize_class (NautilusListClass *klass)
clist_class->clear = nautilus_list_clear;
clist_class->draw_row = draw_row;
clist_class->resize_column = nautilus_list_resize_column;
- clist_class->scroll_vertical = nautilus_list_scroll_vertical;
clist_class->set_cell_contents = nautilus_list_set_cell_contents;
widget_class->button_press_event = nautilus_list_button_press;
@@ -269,8 +314,11 @@ nautilus_list_initialize_class (NautilusListClass *klass)
widget_class->drag_drop = nautilus_list_drag_drop;
widget_class->drag_data_received = nautilus_list_drag_data_received;
widget_class->draw_focus = nautilus_list_draw_focus;
+ widget_class->key_press_event = nautilus_list_key_press;
widget_class->realize = nautilus_list_realize;
widget_class->size_request = nautilus_list_size_request;
+
+ object_class->destroy = nautilus_list_destroy;
}
/* Standard object initialization function */
@@ -306,6 +354,20 @@ nautilus_list_initialize (NautilusList *list)
list->details->title = GTK_WIDGET (nautilus_list_column_title_new());
}
+static void
+nautilus_list_destroy (GtkObject *object)
+{
+ NautilusList *list;
+
+ list = NAUTILUS_LIST (object);
+
+ unschedule_keyboard_row_reveal (list);
+
+ g_free (list->details);
+
+ NAUTILUS_CALL_PARENT_CLASS (GTK_OBJECT_CLASS, destroy, (object));
+}
+
static void
select_or_unselect_row_callback (GtkCList *clist, gint row, gint column, GdkEvent *event)
@@ -564,6 +626,303 @@ nautilus_list_button_release (GtkWidget *widget, GdkEventButton *event)
}
static void
+nautilus_list_clear_keyboard_focus (NautilusList *list)
+{
+ if (GTK_CLIST (list)->focus_row >= 0) {
+ gtk_widget_draw_focus (GTK_WIDGET (list));
+ }
+
+ GTK_CLIST (list)->focus_row = -1;
+}
+
+static void
+nautilus_list_set_keyboard_focus (NautilusList *list, int row)
+{
+ g_assert (row >= 0 && row < GTK_CLIST (list)->rows);
+
+ if (row == GTK_CLIST (list)->focus_row) {
+ return;
+ }
+
+ nautilus_list_clear_keyboard_focus (list);
+
+ GTK_CLIST (list)->focus_row = row;
+
+ gtk_widget_draw_focus (GTK_WIDGET (list));
+}
+
+static void
+nautilus_list_keyboard_move_to (NautilusList *list, int row, GdkEventKey *event)
+{
+ GtkCList *clist;
+
+ g_assert (NAUTILUS_IS_LIST (list));
+ g_assert (row >= 0 || row < GTK_CLIST (list)->rows);
+
+ clist = GTK_CLIST (list);
+
+ if ((event->state & GDK_CONTROL_MASK) != 0) {
+ /* Move the keyboard focus. */
+ nautilus_list_set_keyboard_focus (list, row);
+ } else {
+ /* Select row and get rid of special keyboard focus. */
+ nautilus_list_clear_keyboard_focus (list);
+ /* FIXME: This sends numerous selection_changed messages
+ * instead of only one. It also flashes unpleasantly when
+ * selection is pinned up at first row.
+ */
+ gtk_clist_unselect_all (clist);
+ gtk_clist_select_row (clist, row, -1);
+ }
+
+ schedule_keyboard_row_reveal (list, row);
+}
+
+static gboolean
+keyboard_row_reveal_timeout_callback (gpointer data)
+{
+ NautilusList *list;
+ int row;
+
+ GDK_THREADS_ENTER ();
+
+ list = NAUTILUS_LIST (data);
+ row = list->details->keyboard_row_to_reveal;
+
+ if (row >= 0 && row < GTK_CLIST (list)->rows) {
+ /* Only reveal the row if it's still the keyboard focus
+ * or if it's still selected.
+ * FIXME: Need to unschedule this if the user scrolls explicitly.
+ */
+ if (row == GTK_CLIST (list)->focus_row
+ || nautilus_list_is_row_selected (list, row)) {
+ reveal_row (list, row);
+ }
+ list->details->keyboard_row_reveal_timer_id = 0;
+ }
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+static void
+unschedule_keyboard_row_reveal (NautilusList *list)
+{
+ if (list->details->keyboard_row_reveal_timer_id != 0) {
+ gtk_timeout_remove (list->details->keyboard_row_reveal_timer_id);
+ }
+}
+
+static void
+schedule_keyboard_row_reveal (NautilusList *list, int row)
+{
+ unschedule_keyboard_row_reveal (list);
+
+ list->details->keyboard_row_to_reveal = row;
+ list->details->keyboard_row_reveal_timer_id
+ = gtk_timeout_add (KEYBOARD_ROW_REVEAL_TIMEOUT,
+ keyboard_row_reveal_timeout_callback,
+ list);
+}
+
+static void
+reveal_row (NautilusList *list, int row)
+{
+ GtkCList *clist;
+
+ g_assert (NAUTILUS_IS_LIST (list));
+
+ clist = GTK_CLIST (list);
+
+ if (ROW_TOP_YPIXEL (clist, row) + clist->row_height >
+ clist->clist_window_height) {
+ gtk_clist_moveto (clist, row, -1, 1, 0);
+ } else if (ROW_TOP_YPIXEL (clist, row) < 0) {
+ gtk_clist_moveto (clist, row, -1, 0, 0);
+ }
+}
+
+static void
+nautilus_list_keyboard_navigation_key_press (NautilusList *list, GdkEventKey *event,
+ GtkScrollType scroll_type, gboolean jump_to_end)
+{
+ GtkCList *clist;
+ int start_row;
+ int destination_row;
+ int rows_per_page;
+
+ g_assert (NAUTILUS_IS_LIST (list));
+
+ clist = GTK_CLIST (list);
+
+ if (scroll_type == GTK_SCROLL_JUMP) {
+ destination_row = (jump_to_end ?
+ clist->rows - 1 :
+ 0);
+ } else {
+ /* Choose the row to start with.
+ * If we have a keyboard focus, start with it.
+ * If there's a selection, use the selected row farthest toward the end.
+ */
+
+ if (GTK_CLIST (list)->focus_row >= 0) {
+ start_row = clist->focus_row;
+ } else {
+ start_row = (scroll_type == GTK_SCROLL_STEP_FORWARD || scroll_type == GTK_SCROLL_PAGE_FORWARD ?
+ nautilus_list_get_last_selected_row (list) :
+ nautilus_list_get_first_selected_row (list));
+ }
+
+ /* If there's no row to start with, select the row farthest toward the end.
+ * If there is a row to start with, select the next row in the arrow direction.
+ */
+ if (start_row < 0) {
+ destination_row = (scroll_type == GTK_SCROLL_STEP_FORWARD || scroll_type == GTK_SCROLL_PAGE_FORWARD ?
+ clist->rows - 1 :
+ 0);
+ } else if (scroll_type == GTK_SCROLL_STEP_FORWARD) {
+ destination_row = MIN (clist->rows - 1, start_row + 1);
+ } else if (scroll_type == GTK_SCROLL_STEP_BACKWARD) {
+ destination_row = MAX (0, start_row - 1);
+ } else {
+ g_assert (scroll_type == GTK_SCROLL_PAGE_FORWARD || GTK_SCROLL_PAGE_BACKWARD);
+ rows_per_page = (2 * clist->clist_window_height -
+ clist->row_height - CELL_SPACING) /
+ (2 * (clist->row_height + CELL_SPACING));
+
+ if (scroll_type == GTK_SCROLL_PAGE_FORWARD) {
+ destination_row = MIN (clist->rows - 1,
+ start_row + rows_per_page);
+ } else {
+ destination_row = MAX (0,
+ start_row - rows_per_page);
+ }
+ }
+ }
+
+ nautilus_list_keyboard_move_to (list, destination_row, event);
+}
+
+static void
+nautilus_list_keyboard_home (NautilusList *list, GdkEventKey *event)
+{
+ /* Home selects the first row.
+ * Control-Home sets the keyboard focus to the first row.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_JUMP, FALSE);
+}
+
+static void
+nautilus_list_keyboard_end (NautilusList *list, GdkEventKey *event)
+{
+ /* End selects the last row.
+ * Control-End sets the keyboard focus to the last row.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_JUMP, TRUE);
+}
+
+static void
+nautilus_list_keyboard_up (NautilusList *list, GdkEventKey *event)
+{
+ /* Up selects the next higher row.
+ * Control-Up sets the keyboard focus to the next higher icon.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_STEP_BACKWARD, FALSE);
+}
+
+static void
+nautilus_list_keyboard_down (NautilusList *list, GdkEventKey *event)
+{
+ /* Down selects the next lower row.
+ * Control-Down sets the keyboard focus to the next lower icon.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_STEP_FORWARD, FALSE);
+}
+
+static void
+nautilus_list_keyboard_page_up (NautilusList *list, GdkEventKey *event)
+{
+ /* Page Up selects a row one screenful higher.
+ * Control-Page Up sets the keyboard focus to the row one screenful higher.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_PAGE_BACKWARD, FALSE);
+}
+
+static void
+nautilus_list_keyboard_page_down (NautilusList *list, GdkEventKey *event)
+{
+ /* Page Down selects a row one screenful lower.
+ * Control-Page Down sets the keyboard focus to the row one screenful lower.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_PAGE_FORWARD, FALSE);
+}
+
+static void
+nautilus_list_keyboard_space (NautilusList *list, GdkEventKey *event)
+{
+ if (event->state & GDK_CONTROL_MASK) {
+ gtk_signal_emit_by_name (GTK_OBJECT (list), "toggle_focus_row");
+ }
+}
+
+static void
+nautilus_list_activate_selected_items (NautilusList *list)
+{
+ int row;
+
+ for (row = 0; row < GTK_CLIST (list)->rows; ++row) {
+ if (nautilus_list_is_row_selected (list, row)) {
+ activate_row (list, row);
+ }
+ }
+}
+
+static int
+nautilus_list_key_press (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ NautilusList *list;
+
+ list = NAUTILUS_LIST (widget);
+
+ switch (event->keyval) {
+ case GDK_Home:
+ nautilus_list_keyboard_home (list, event);
+ break;
+ case GDK_End:
+ nautilus_list_keyboard_end (list, event);
+ break;
+ case GDK_Page_Up:
+ nautilus_list_keyboard_page_up (list, event);
+ break;
+ case GDK_Page_Down:
+ nautilus_list_keyboard_page_down (list, event);
+ break;
+ case GDK_Up:
+ nautilus_list_keyboard_up (list, event);
+ break;
+ case GDK_Down:
+ nautilus_list_keyboard_down (list, event);
+ break;
+ case GDK_space:
+ nautilus_list_keyboard_space (list, event);
+ break;
+ case GDK_Return:
+ nautilus_list_activate_selected_items (list);
+ break;
+ default:
+ if (NAUTILUS_CALL_PARENT_CLASS (GTK_WIDGET_CLASS, key_press_event, (widget, event))) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
nautilus_list_realize (GtkWidget *widget)
{
NautilusList *list;
@@ -803,7 +1162,7 @@ nautilus_list_draw_focus (GtkWidget *widget)
if (clist->focus_row < 0) {
return;
}
-
+
gdk_gc_get_values (clist->xor_gc, &saved_values);
gdk_gc_set_stipple (clist->xor_gc, nautilus_stipple_bitmap ());
@@ -1591,105 +1950,6 @@ nautilus_list_column_resize_track_end (GtkWidget *widget, int column)
clist->drag_pos = -1;
}
-static void
-nautilus_list_move_focus_row (NautilusList *list,
- GtkScrollType scroll_type,
- gfloat position)
-{
- /* FIXME: Moved over from gtkclist.c. Should
- * be cleaned up stylistically and possibly
- * eliminated as a concept. Instead, determine
- * destination position and then determine whether
- * it should be selected or just focused. I'll do
- * this in the next few days (John Sullivan, 4/7/00)
- */
- GtkCList *clist;
- GtkWidget *widget;
-
- g_return_if_fail (NAUTILUS_IS_LIST (list));
-
- clist = GTK_CLIST (list);
- widget = GTK_WIDGET (list);
-
- switch (scroll_type)
- {
- case GTK_SCROLL_STEP_BACKWARD:
- if (clist->focus_row <= 0)
- return;
- gtk_widget_draw_focus (widget);
- clist->focus_row--;
- gtk_widget_draw_focus (widget);
- break;
- case GTK_SCROLL_STEP_FORWARD:
- if (clist->focus_row >= clist->rows - 1)
- return;
- gtk_widget_draw_focus (widget);
- clist->focus_row++;
- gtk_widget_draw_focus (widget);
- break;
- case GTK_SCROLL_PAGE_BACKWARD:
- if (clist->focus_row <= 0)
- return;
- gtk_widget_draw_focus (widget);
- clist->focus_row = MAX (0, clist->focus_row -
- (2 * clist->clist_window_height -
- clist->row_height - CELL_SPACING) /
- (2 * (clist->row_height + CELL_SPACING)));
- gtk_widget_draw_focus (widget);
- break;
- case GTK_SCROLL_PAGE_FORWARD:
- if (clist->focus_row >= clist->rows - 1)
- return;
- gtk_widget_draw_focus (widget);
- clist->focus_row = MIN (clist->rows - 1, clist->focus_row +
- (2 * clist->clist_window_height -
- clist->row_height - CELL_SPACING) /
- (2 * (clist->row_height + CELL_SPACING)));
- gtk_widget_draw_focus (widget);
- break;
- case GTK_SCROLL_JUMP:
- if (position >= 0 && position <= 1)
- {
- gtk_widget_draw_focus (widget);
- clist->focus_row = position * (clist->rows - 1);
- gtk_widget_draw_focus (widget);
- }
- break;
- default:
- break;
- }
-}
-
-static void
-nautilus_list_scroll_vertical (GtkCList *clist,
- GtkScrollType scroll_type,
- gfloat position)
-{
- /* Pulled over from GtkCList and simplified for our purposes. May
- * eliminate this entirely by replacing all the key bindings.
- */
- g_return_if_fail (clist != NULL);
- g_return_if_fail (NAUTILUS_IS_LIST (clist));
- g_return_if_fail (clist->selection_mode == GTK_SELECTION_MULTIPLE);
-
- /* Stolen from scroll_vertical in gtkclist.c. I don't know
- * exactly what this is for.
- */
- if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
- return;
-
-
- nautilus_list_move_focus_row (NAUTILUS_LIST (clist), scroll_type, position);
-
- if (ROW_TOP_YPIXEL (clist, clist->focus_row) + clist->row_height >
- clist->clist_window_height) {
- gtk_clist_moveto (clist, clist->focus_row, -1, 1, 0);
- }
- else if (ROW_TOP_YPIXEL (clist, clist->focus_row) < 0) {
- gtk_clist_moveto (clist, clist->focus_row, -1, 0, 0);
- }
-}
-
/* We override the drag_begin signal to do nothing */
static void
nautilus_list_drag_begin (GtkWidget *widget, GdkDragContext *context)
@@ -1800,6 +2060,50 @@ nautilus_list_new_with_titles (int columns, const char * const *titles)
return GTK_WIDGET (list);
}
+static int
+nautilus_list_get_first_selected_row (NautilusList *list)
+{
+ GtkCListRow *row;
+ GList *p;
+ int row_number;
+
+ g_return_val_if_fail (NAUTILUS_IS_LIST (list), -1);
+
+ row_number = 0;
+ for (p = GTK_CLIST (list)->row_list; p != NULL; p = p->next) {
+ row = p->data;
+ if (row->state == GTK_STATE_SELECTED) {
+ return row_number;
+ }
+
+ ++row_number;
+ }
+
+ return -1;
+}
+
+static int
+nautilus_list_get_last_selected_row (NautilusList *list)
+{
+ GtkCListRow *row;
+ GList *p;
+ int row_number;
+
+ g_return_val_if_fail (NAUTILUS_IS_LIST (list), -1);
+
+ row_number = GTK_CLIST (list)->rows - 1;
+ for (p = GTK_CLIST (list)->row_list_end; p != NULL; p = p->prev) {
+ row = p->data;
+ if (row->state == GTK_STATE_SELECTED) {
+ return row_number;
+ }
+
+ --row_number;
+ }
+
+ return -1;
+}
+
GList *
nautilus_list_get_selection (NautilusList *list)
{
diff --git a/libnautilus-private/nautilus-list.c b/libnautilus-private/nautilus-list.c
index 3165c1083..4187270c2 100644
--- a/libnautilus-private/nautilus-list.c
+++ b/libnautilus-private/nautilus-list.c
@@ -32,6 +32,8 @@
#include <gdk/gdkkeysyms.h>
#include <gtk/gtkbindings.h>
#include <gtk/gtkdnd.h>
+#include <gtk/gtkenums.h>
+#include <gtk/gtkmain.h>
#include <glib.h>
#include "nautilus-gdk-extensions.h"
@@ -42,6 +44,12 @@
#include "nautilus-background.h"
#include "nautilus-list-column-title.h"
+/* Timeout for making the row currently selected for keyboard operation
+ * visible. FIXME: This *must* be higher than the double-click time in GDK,
+ * but there is no way to access its value from outside.
+ */
+#define KEYBOARD_ROW_REVEAL_TIMEOUT 300
+
struct NautilusListDetails
{
/* Preferences */
@@ -56,6 +64,13 @@ struct NautilusListDetails
int button_down_row;
guint32 button_down_time;
+ /* Timeout used to make a selected row fully visible after a short
+ * period of time. (The timeout is needed to make sure
+ * double-clicking still works, and to optimize holding down arrow key.)
+ */
+ guint keyboard_row_reveal_timer_id;
+ int keyboard_row_to_reveal;
+
/* Delayed selection information */
int dnd_select_pending;
guint dnd_select_pending_state;
@@ -134,6 +149,7 @@ static void get_cell_style (GtkCList *clist, GtkCListRow *clist_row,
static void nautilus_list_initialize_class (NautilusListClass *class);
static void nautilus_list_initialize (NautilusList *list);
+static void nautilus_list_destroy (GtkObject *object);
static gint nautilus_list_button_press (GtkWidget *widget, GdkEventButton *event);
static gint nautilus_list_button_release (GtkWidget *widget, GdkEventButton *event);
@@ -150,7 +166,16 @@ static gboolean nautilus_list_drag_drop (GtkWidget *widget, GdkDragContext *cont
static void nautilus_list_drag_data_received (GtkWidget *widget, GdkDragContext *context,
gint x, gint y, GtkSelectionData *data,
guint info, guint time);
+static void nautilus_list_clear_keyboard_focus (NautilusList *list);
static void nautilus_list_draw_focus (GtkWidget *widget);
+static int nautilus_list_get_first_selected_row (NautilusList *list);
+static int nautilus_list_get_last_selected_row (NautilusList *list);
+static gint nautilus_list_key_press (GtkWidget *widget,
+ GdkEventKey *event);
+
+static void reveal_row (NautilusList *list, int row);
+static void schedule_keyboard_row_reveal (NautilusList *list, int row);
+static void unschedule_keyboard_row_reveal (NautilusList *list);
static void select_or_unselect_row_callback (GtkCList *clist, gint row, gint column,
GdkEvent *event);
@@ -158,9 +183,6 @@ static void nautilus_list_clear (GtkCList *clist);
static void draw_row (GtkCList *list, GdkRectangle *area, gint row, GtkCListRow *clist_row);
static void nautilus_list_realize (GtkWidget *widget);
-static void nautilus_list_scroll_vertical (GtkCList *clist,
- GtkScrollType scroll_type,
- gfloat position);
static void nautilus_list_set_cell_contents (GtkCList *clist,
GtkCListRow *clist_row,
gint column,
@@ -191,7 +213,6 @@ nautilus_list_initialize_class (NautilusListClass *klass)
NautilusListClass *list_class;
GtkBindingSet *clist_binding_set;
- GtkBindingSet *list_binding_set;
object_class = (GtkObjectClass *) klass;
widget_class = (GtkWidgetClass *) klass;
@@ -239,14 +260,39 @@ nautilus_list_initialize_class (NautilusListClass *klass)
gtk_object_class_add_signals (object_class, list_signals, LAST_SIGNAL);
- /* Add new key bindings. Some of these clobber GtkCList ones that we don't like. */
- list_binding_set = gtk_binding_set_by_class (list_class);
- gtk_binding_entry_add_signal (list_binding_set, GDK_space, GDK_CONTROL_MASK,
- "toggle_focus_row", 0);
-
- /* Turn off the GtkCList key bindings that we want entirely unbound. */
+ /* Turn off the GtkCList key bindings that we want unbound.
+ * We only need to do this for the keys that we don't handle
+ * in nautilus_list_key_press. These extra ones are turned off
+ * to avoid inappropriate GtkCList code and to standardize the
+ * keyboard behavior in Nautilus.
+ */
clist_binding_set = gtk_binding_set_by_class (clist_class);
- gtk_binding_entry_clear (clist_binding_set, GDK_space, 0);
+
+ /* Use Control-A for Select All, not Control-/ */
+ gtk_binding_entry_clear (clist_binding_set,
+ '/',
+ GDK_CONTROL_MASK);
+ /* Don't use Control-\ for Unselect All (maybe invent Nautilus
+ * standard for this?) */
+ gtk_binding_entry_clear (clist_binding_set,
+ '\\',
+ GDK_CONTROL_MASK);
+ /* Hide GtkCList's weird extend-selection-from-keyboard stuff.
+ * Users can use control-navigation and control-space to create
+ * extended selections.
+ */
+ gtk_binding_entry_clear (clist_binding_set,
+ GDK_Shift_L,
+ GDK_RELEASE_MASK | GDK_SHIFT_MASK);
+ gtk_binding_entry_clear (clist_binding_set,
+ GDK_Shift_R,
+ GDK_RELEASE_MASK | GDK_SHIFT_MASK);
+ gtk_binding_entry_clear (clist_binding_set,
+ GDK_Shift_L,
+ GDK_RELEASE_MASK | GDK_SHIFT_MASK | GDK_CONTROL_MASK);
+ gtk_binding_entry_clear (clist_binding_set,
+ GDK_Shift_R,
+ GDK_RELEASE_MASK | GDK_SHIFT_MASK | GDK_CONTROL_MASK);
list_class->column_resize_track_start = nautilus_list_column_resize_track_start;
list_class->column_resize_track = nautilus_list_column_resize_track;
@@ -255,7 +301,6 @@ nautilus_list_initialize_class (NautilusListClass *klass)
clist_class->clear = nautilus_list_clear;
clist_class->draw_row = draw_row;
clist_class->resize_column = nautilus_list_resize_column;
- clist_class->scroll_vertical = nautilus_list_scroll_vertical;
clist_class->set_cell_contents = nautilus_list_set_cell_contents;
widget_class->button_press_event = nautilus_list_button_press;
@@ -269,8 +314,11 @@ nautilus_list_initialize_class (NautilusListClass *klass)
widget_class->drag_drop = nautilus_list_drag_drop;
widget_class->drag_data_received = nautilus_list_drag_data_received;
widget_class->draw_focus = nautilus_list_draw_focus;
+ widget_class->key_press_event = nautilus_list_key_press;
widget_class->realize = nautilus_list_realize;
widget_class->size_request = nautilus_list_size_request;
+
+ object_class->destroy = nautilus_list_destroy;
}
/* Standard object initialization function */
@@ -306,6 +354,20 @@ nautilus_list_initialize (NautilusList *list)
list->details->title = GTK_WIDGET (nautilus_list_column_title_new());
}
+static void
+nautilus_list_destroy (GtkObject *object)
+{
+ NautilusList *list;
+
+ list = NAUTILUS_LIST (object);
+
+ unschedule_keyboard_row_reveal (list);
+
+ g_free (list->details);
+
+ NAUTILUS_CALL_PARENT_CLASS (GTK_OBJECT_CLASS, destroy, (object));
+}
+
static void
select_or_unselect_row_callback (GtkCList *clist, gint row, gint column, GdkEvent *event)
@@ -564,6 +626,303 @@ nautilus_list_button_release (GtkWidget *widget, GdkEventButton *event)
}
static void
+nautilus_list_clear_keyboard_focus (NautilusList *list)
+{
+ if (GTK_CLIST (list)->focus_row >= 0) {
+ gtk_widget_draw_focus (GTK_WIDGET (list));
+ }
+
+ GTK_CLIST (list)->focus_row = -1;
+}
+
+static void
+nautilus_list_set_keyboard_focus (NautilusList *list, int row)
+{
+ g_assert (row >= 0 && row < GTK_CLIST (list)->rows);
+
+ if (row == GTK_CLIST (list)->focus_row) {
+ return;
+ }
+
+ nautilus_list_clear_keyboard_focus (list);
+
+ GTK_CLIST (list)->focus_row = row;
+
+ gtk_widget_draw_focus (GTK_WIDGET (list));
+}
+
+static void
+nautilus_list_keyboard_move_to (NautilusList *list, int row, GdkEventKey *event)
+{
+ GtkCList *clist;
+
+ g_assert (NAUTILUS_IS_LIST (list));
+ g_assert (row >= 0 || row < GTK_CLIST (list)->rows);
+
+ clist = GTK_CLIST (list);
+
+ if ((event->state & GDK_CONTROL_MASK) != 0) {
+ /* Move the keyboard focus. */
+ nautilus_list_set_keyboard_focus (list, row);
+ } else {
+ /* Select row and get rid of special keyboard focus. */
+ nautilus_list_clear_keyboard_focus (list);
+ /* FIXME: This sends numerous selection_changed messages
+ * instead of only one. It also flashes unpleasantly when
+ * selection is pinned up at first row.
+ */
+ gtk_clist_unselect_all (clist);
+ gtk_clist_select_row (clist, row, -1);
+ }
+
+ schedule_keyboard_row_reveal (list, row);
+}
+
+static gboolean
+keyboard_row_reveal_timeout_callback (gpointer data)
+{
+ NautilusList *list;
+ int row;
+
+ GDK_THREADS_ENTER ();
+
+ list = NAUTILUS_LIST (data);
+ row = list->details->keyboard_row_to_reveal;
+
+ if (row >= 0 && row < GTK_CLIST (list)->rows) {
+ /* Only reveal the row if it's still the keyboard focus
+ * or if it's still selected.
+ * FIXME: Need to unschedule this if the user scrolls explicitly.
+ */
+ if (row == GTK_CLIST (list)->focus_row
+ || nautilus_list_is_row_selected (list, row)) {
+ reveal_row (list, row);
+ }
+ list->details->keyboard_row_reveal_timer_id = 0;
+ }
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+static void
+unschedule_keyboard_row_reveal (NautilusList *list)
+{
+ if (list->details->keyboard_row_reveal_timer_id != 0) {
+ gtk_timeout_remove (list->details->keyboard_row_reveal_timer_id);
+ }
+}
+
+static void
+schedule_keyboard_row_reveal (NautilusList *list, int row)
+{
+ unschedule_keyboard_row_reveal (list);
+
+ list->details->keyboard_row_to_reveal = row;
+ list->details->keyboard_row_reveal_timer_id
+ = gtk_timeout_add (KEYBOARD_ROW_REVEAL_TIMEOUT,
+ keyboard_row_reveal_timeout_callback,
+ list);
+}
+
+static void
+reveal_row (NautilusList *list, int row)
+{
+ GtkCList *clist;
+
+ g_assert (NAUTILUS_IS_LIST (list));
+
+ clist = GTK_CLIST (list);
+
+ if (ROW_TOP_YPIXEL (clist, row) + clist->row_height >
+ clist->clist_window_height) {
+ gtk_clist_moveto (clist, row, -1, 1, 0);
+ } else if (ROW_TOP_YPIXEL (clist, row) < 0) {
+ gtk_clist_moveto (clist, row, -1, 0, 0);
+ }
+}
+
+static void
+nautilus_list_keyboard_navigation_key_press (NautilusList *list, GdkEventKey *event,
+ GtkScrollType scroll_type, gboolean jump_to_end)
+{
+ GtkCList *clist;
+ int start_row;
+ int destination_row;
+ int rows_per_page;
+
+ g_assert (NAUTILUS_IS_LIST (list));
+
+ clist = GTK_CLIST (list);
+
+ if (scroll_type == GTK_SCROLL_JUMP) {
+ destination_row = (jump_to_end ?
+ clist->rows - 1 :
+ 0);
+ } else {
+ /* Choose the row to start with.
+ * If we have a keyboard focus, start with it.
+ * If there's a selection, use the selected row farthest toward the end.
+ */
+
+ if (GTK_CLIST (list)->focus_row >= 0) {
+ start_row = clist->focus_row;
+ } else {
+ start_row = (scroll_type == GTK_SCROLL_STEP_FORWARD || scroll_type == GTK_SCROLL_PAGE_FORWARD ?
+ nautilus_list_get_last_selected_row (list) :
+ nautilus_list_get_first_selected_row (list));
+ }
+
+ /* If there's no row to start with, select the row farthest toward the end.
+ * If there is a row to start with, select the next row in the arrow direction.
+ */
+ if (start_row < 0) {
+ destination_row = (scroll_type == GTK_SCROLL_STEP_FORWARD || scroll_type == GTK_SCROLL_PAGE_FORWARD ?
+ clist->rows - 1 :
+ 0);
+ } else if (scroll_type == GTK_SCROLL_STEP_FORWARD) {
+ destination_row = MIN (clist->rows - 1, start_row + 1);
+ } else if (scroll_type == GTK_SCROLL_STEP_BACKWARD) {
+ destination_row = MAX (0, start_row - 1);
+ } else {
+ g_assert (scroll_type == GTK_SCROLL_PAGE_FORWARD || GTK_SCROLL_PAGE_BACKWARD);
+ rows_per_page = (2 * clist->clist_window_height -
+ clist->row_height - CELL_SPACING) /
+ (2 * (clist->row_height + CELL_SPACING));
+
+ if (scroll_type == GTK_SCROLL_PAGE_FORWARD) {
+ destination_row = MIN (clist->rows - 1,
+ start_row + rows_per_page);
+ } else {
+ destination_row = MAX (0,
+ start_row - rows_per_page);
+ }
+ }
+ }
+
+ nautilus_list_keyboard_move_to (list, destination_row, event);
+}
+
+static void
+nautilus_list_keyboard_home (NautilusList *list, GdkEventKey *event)
+{
+ /* Home selects the first row.
+ * Control-Home sets the keyboard focus to the first row.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_JUMP, FALSE);
+}
+
+static void
+nautilus_list_keyboard_end (NautilusList *list, GdkEventKey *event)
+{
+ /* End selects the last row.
+ * Control-End sets the keyboard focus to the last row.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_JUMP, TRUE);
+}
+
+static void
+nautilus_list_keyboard_up (NautilusList *list, GdkEventKey *event)
+{
+ /* Up selects the next higher row.
+ * Control-Up sets the keyboard focus to the next higher icon.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_STEP_BACKWARD, FALSE);
+}
+
+static void
+nautilus_list_keyboard_down (NautilusList *list, GdkEventKey *event)
+{
+ /* Down selects the next lower row.
+ * Control-Down sets the keyboard focus to the next lower icon.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_STEP_FORWARD, FALSE);
+}
+
+static void
+nautilus_list_keyboard_page_up (NautilusList *list, GdkEventKey *event)
+{
+ /* Page Up selects a row one screenful higher.
+ * Control-Page Up sets the keyboard focus to the row one screenful higher.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_PAGE_BACKWARD, FALSE);
+}
+
+static void
+nautilus_list_keyboard_page_down (NautilusList *list, GdkEventKey *event)
+{
+ /* Page Down selects a row one screenful lower.
+ * Control-Page Down sets the keyboard focus to the row one screenful lower.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_PAGE_FORWARD, FALSE);
+}
+
+static void
+nautilus_list_keyboard_space (NautilusList *list, GdkEventKey *event)
+{
+ if (event->state & GDK_CONTROL_MASK) {
+ gtk_signal_emit_by_name (GTK_OBJECT (list), "toggle_focus_row");
+ }
+}
+
+static void
+nautilus_list_activate_selected_items (NautilusList *list)
+{
+ int row;
+
+ for (row = 0; row < GTK_CLIST (list)->rows; ++row) {
+ if (nautilus_list_is_row_selected (list, row)) {
+ activate_row (list, row);
+ }
+ }
+}
+
+static int
+nautilus_list_key_press (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ NautilusList *list;
+
+ list = NAUTILUS_LIST (widget);
+
+ switch (event->keyval) {
+ case GDK_Home:
+ nautilus_list_keyboard_home (list, event);
+ break;
+ case GDK_End:
+ nautilus_list_keyboard_end (list, event);
+ break;
+ case GDK_Page_Up:
+ nautilus_list_keyboard_page_up (list, event);
+ break;
+ case GDK_Page_Down:
+ nautilus_list_keyboard_page_down (list, event);
+ break;
+ case GDK_Up:
+ nautilus_list_keyboard_up (list, event);
+ break;
+ case GDK_Down:
+ nautilus_list_keyboard_down (list, event);
+ break;
+ case GDK_space:
+ nautilus_list_keyboard_space (list, event);
+ break;
+ case GDK_Return:
+ nautilus_list_activate_selected_items (list);
+ break;
+ default:
+ if (NAUTILUS_CALL_PARENT_CLASS (GTK_WIDGET_CLASS, key_press_event, (widget, event))) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
nautilus_list_realize (GtkWidget *widget)
{
NautilusList *list;
@@ -803,7 +1162,7 @@ nautilus_list_draw_focus (GtkWidget *widget)
if (clist->focus_row < 0) {
return;
}
-
+
gdk_gc_get_values (clist->xor_gc, &saved_values);
gdk_gc_set_stipple (clist->xor_gc, nautilus_stipple_bitmap ());
@@ -1591,105 +1950,6 @@ nautilus_list_column_resize_track_end (GtkWidget *widget, int column)
clist->drag_pos = -1;
}
-static void
-nautilus_list_move_focus_row (NautilusList *list,
- GtkScrollType scroll_type,
- gfloat position)
-{
- /* FIXME: Moved over from gtkclist.c. Should
- * be cleaned up stylistically and possibly
- * eliminated as a concept. Instead, determine
- * destination position and then determine whether
- * it should be selected or just focused. I'll do
- * this in the next few days (John Sullivan, 4/7/00)
- */
- GtkCList *clist;
- GtkWidget *widget;
-
- g_return_if_fail (NAUTILUS_IS_LIST (list));
-
- clist = GTK_CLIST (list);
- widget = GTK_WIDGET (list);
-
- switch (scroll_type)
- {
- case GTK_SCROLL_STEP_BACKWARD:
- if (clist->focus_row <= 0)
- return;
- gtk_widget_draw_focus (widget);
- clist->focus_row--;
- gtk_widget_draw_focus (widget);
- break;
- case GTK_SCROLL_STEP_FORWARD:
- if (clist->focus_row >= clist->rows - 1)
- return;
- gtk_widget_draw_focus (widget);
- clist->focus_row++;
- gtk_widget_draw_focus (widget);
- break;
- case GTK_SCROLL_PAGE_BACKWARD:
- if (clist->focus_row <= 0)
- return;
- gtk_widget_draw_focus (widget);
- clist->focus_row = MAX (0, clist->focus_row -
- (2 * clist->clist_window_height -
- clist->row_height - CELL_SPACING) /
- (2 * (clist->row_height + CELL_SPACING)));
- gtk_widget_draw_focus (widget);
- break;
- case GTK_SCROLL_PAGE_FORWARD:
- if (clist->focus_row >= clist->rows - 1)
- return;
- gtk_widget_draw_focus (widget);
- clist->focus_row = MIN (clist->rows - 1, clist->focus_row +
- (2 * clist->clist_window_height -
- clist->row_height - CELL_SPACING) /
- (2 * (clist->row_height + CELL_SPACING)));
- gtk_widget_draw_focus (widget);
- break;
- case GTK_SCROLL_JUMP:
- if (position >= 0 && position <= 1)
- {
- gtk_widget_draw_focus (widget);
- clist->focus_row = position * (clist->rows - 1);
- gtk_widget_draw_focus (widget);
- }
- break;
- default:
- break;
- }
-}
-
-static void
-nautilus_list_scroll_vertical (GtkCList *clist,
- GtkScrollType scroll_type,
- gfloat position)
-{
- /* Pulled over from GtkCList and simplified for our purposes. May
- * eliminate this entirely by replacing all the key bindings.
- */
- g_return_if_fail (clist != NULL);
- g_return_if_fail (NAUTILUS_IS_LIST (clist));
- g_return_if_fail (clist->selection_mode == GTK_SELECTION_MULTIPLE);
-
- /* Stolen from scroll_vertical in gtkclist.c. I don't know
- * exactly what this is for.
- */
- if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
- return;
-
-
- nautilus_list_move_focus_row (NAUTILUS_LIST (clist), scroll_type, position);
-
- if (ROW_TOP_YPIXEL (clist, clist->focus_row) + clist->row_height >
- clist->clist_window_height) {
- gtk_clist_moveto (clist, clist->focus_row, -1, 1, 0);
- }
- else if (ROW_TOP_YPIXEL (clist, clist->focus_row) < 0) {
- gtk_clist_moveto (clist, clist->focus_row, -1, 0, 0);
- }
-}
-
/* We override the drag_begin signal to do nothing */
static void
nautilus_list_drag_begin (GtkWidget *widget, GdkDragContext *context)
@@ -1800,6 +2060,50 @@ nautilus_list_new_with_titles (int columns, const char * const *titles)
return GTK_WIDGET (list);
}
+static int
+nautilus_list_get_first_selected_row (NautilusList *list)
+{
+ GtkCListRow *row;
+ GList *p;
+ int row_number;
+
+ g_return_val_if_fail (NAUTILUS_IS_LIST (list), -1);
+
+ row_number = 0;
+ for (p = GTK_CLIST (list)->row_list; p != NULL; p = p->next) {
+ row = p->data;
+ if (row->state == GTK_STATE_SELECTED) {
+ return row_number;
+ }
+
+ ++row_number;
+ }
+
+ return -1;
+}
+
+static int
+nautilus_list_get_last_selected_row (NautilusList *list)
+{
+ GtkCListRow *row;
+ GList *p;
+ int row_number;
+
+ g_return_val_if_fail (NAUTILUS_IS_LIST (list), -1);
+
+ row_number = GTK_CLIST (list)->rows - 1;
+ for (p = GTK_CLIST (list)->row_list_end; p != NULL; p = p->prev) {
+ row = p->data;
+ if (row->state == GTK_STATE_SELECTED) {
+ return row_number;
+ }
+
+ --row_number;
+ }
+
+ return -1;
+}
+
GList *
nautilus_list_get_selection (NautilusList *list)
{
diff --git a/libnautilus/nautilus-list.c b/libnautilus/nautilus-list.c
index 3165c1083..4187270c2 100644
--- a/libnautilus/nautilus-list.c
+++ b/libnautilus/nautilus-list.c
@@ -32,6 +32,8 @@
#include <gdk/gdkkeysyms.h>
#include <gtk/gtkbindings.h>
#include <gtk/gtkdnd.h>
+#include <gtk/gtkenums.h>
+#include <gtk/gtkmain.h>
#include <glib.h>
#include "nautilus-gdk-extensions.h"
@@ -42,6 +44,12 @@
#include "nautilus-background.h"
#include "nautilus-list-column-title.h"
+/* Timeout for making the row currently selected for keyboard operation
+ * visible. FIXME: This *must* be higher than the double-click time in GDK,
+ * but there is no way to access its value from outside.
+ */
+#define KEYBOARD_ROW_REVEAL_TIMEOUT 300
+
struct NautilusListDetails
{
/* Preferences */
@@ -56,6 +64,13 @@ struct NautilusListDetails
int button_down_row;
guint32 button_down_time;
+ /* Timeout used to make a selected row fully visible after a short
+ * period of time. (The timeout is needed to make sure
+ * double-clicking still works, and to optimize holding down arrow key.)
+ */
+ guint keyboard_row_reveal_timer_id;
+ int keyboard_row_to_reveal;
+
/* Delayed selection information */
int dnd_select_pending;
guint dnd_select_pending_state;
@@ -134,6 +149,7 @@ static void get_cell_style (GtkCList *clist, GtkCListRow *clist_row,
static void nautilus_list_initialize_class (NautilusListClass *class);
static void nautilus_list_initialize (NautilusList *list);
+static void nautilus_list_destroy (GtkObject *object);
static gint nautilus_list_button_press (GtkWidget *widget, GdkEventButton *event);
static gint nautilus_list_button_release (GtkWidget *widget, GdkEventButton *event);
@@ -150,7 +166,16 @@ static gboolean nautilus_list_drag_drop (GtkWidget *widget, GdkDragContext *cont
static void nautilus_list_drag_data_received (GtkWidget *widget, GdkDragContext *context,
gint x, gint y, GtkSelectionData *data,
guint info, guint time);
+static void nautilus_list_clear_keyboard_focus (NautilusList *list);
static void nautilus_list_draw_focus (GtkWidget *widget);
+static int nautilus_list_get_first_selected_row (NautilusList *list);
+static int nautilus_list_get_last_selected_row (NautilusList *list);
+static gint nautilus_list_key_press (GtkWidget *widget,
+ GdkEventKey *event);
+
+static void reveal_row (NautilusList *list, int row);
+static void schedule_keyboard_row_reveal (NautilusList *list, int row);
+static void unschedule_keyboard_row_reveal (NautilusList *list);
static void select_or_unselect_row_callback (GtkCList *clist, gint row, gint column,
GdkEvent *event);
@@ -158,9 +183,6 @@ static void nautilus_list_clear (GtkCList *clist);
static void draw_row (GtkCList *list, GdkRectangle *area, gint row, GtkCListRow *clist_row);
static void nautilus_list_realize (GtkWidget *widget);
-static void nautilus_list_scroll_vertical (GtkCList *clist,
- GtkScrollType scroll_type,
- gfloat position);
static void nautilus_list_set_cell_contents (GtkCList *clist,
GtkCListRow *clist_row,
gint column,
@@ -191,7 +213,6 @@ nautilus_list_initialize_class (NautilusListClass *klass)
NautilusListClass *list_class;
GtkBindingSet *clist_binding_set;
- GtkBindingSet *list_binding_set;
object_class = (GtkObjectClass *) klass;
widget_class = (GtkWidgetClass *) klass;
@@ -239,14 +260,39 @@ nautilus_list_initialize_class (NautilusListClass *klass)
gtk_object_class_add_signals (object_class, list_signals, LAST_SIGNAL);
- /* Add new key bindings. Some of these clobber GtkCList ones that we don't like. */
- list_binding_set = gtk_binding_set_by_class (list_class);
- gtk_binding_entry_add_signal (list_binding_set, GDK_space, GDK_CONTROL_MASK,
- "toggle_focus_row", 0);
-
- /* Turn off the GtkCList key bindings that we want entirely unbound. */
+ /* Turn off the GtkCList key bindings that we want unbound.
+ * We only need to do this for the keys that we don't handle
+ * in nautilus_list_key_press. These extra ones are turned off
+ * to avoid inappropriate GtkCList code and to standardize the
+ * keyboard behavior in Nautilus.
+ */
clist_binding_set = gtk_binding_set_by_class (clist_class);
- gtk_binding_entry_clear (clist_binding_set, GDK_space, 0);
+
+ /* Use Control-A for Select All, not Control-/ */
+ gtk_binding_entry_clear (clist_binding_set,
+ '/',
+ GDK_CONTROL_MASK);
+ /* Don't use Control-\ for Unselect All (maybe invent Nautilus
+ * standard for this?) */
+ gtk_binding_entry_clear (clist_binding_set,
+ '\\',
+ GDK_CONTROL_MASK);
+ /* Hide GtkCList's weird extend-selection-from-keyboard stuff.
+ * Users can use control-navigation and control-space to create
+ * extended selections.
+ */
+ gtk_binding_entry_clear (clist_binding_set,
+ GDK_Shift_L,
+ GDK_RELEASE_MASK | GDK_SHIFT_MASK);
+ gtk_binding_entry_clear (clist_binding_set,
+ GDK_Shift_R,
+ GDK_RELEASE_MASK | GDK_SHIFT_MASK);
+ gtk_binding_entry_clear (clist_binding_set,
+ GDK_Shift_L,
+ GDK_RELEASE_MASK | GDK_SHIFT_MASK | GDK_CONTROL_MASK);
+ gtk_binding_entry_clear (clist_binding_set,
+ GDK_Shift_R,
+ GDK_RELEASE_MASK | GDK_SHIFT_MASK | GDK_CONTROL_MASK);
list_class->column_resize_track_start = nautilus_list_column_resize_track_start;
list_class->column_resize_track = nautilus_list_column_resize_track;
@@ -255,7 +301,6 @@ nautilus_list_initialize_class (NautilusListClass *klass)
clist_class->clear = nautilus_list_clear;
clist_class->draw_row = draw_row;
clist_class->resize_column = nautilus_list_resize_column;
- clist_class->scroll_vertical = nautilus_list_scroll_vertical;
clist_class->set_cell_contents = nautilus_list_set_cell_contents;
widget_class->button_press_event = nautilus_list_button_press;
@@ -269,8 +314,11 @@ nautilus_list_initialize_class (NautilusListClass *klass)
widget_class->drag_drop = nautilus_list_drag_drop;
widget_class->drag_data_received = nautilus_list_drag_data_received;
widget_class->draw_focus = nautilus_list_draw_focus;
+ widget_class->key_press_event = nautilus_list_key_press;
widget_class->realize = nautilus_list_realize;
widget_class->size_request = nautilus_list_size_request;
+
+ object_class->destroy = nautilus_list_destroy;
}
/* Standard object initialization function */
@@ -306,6 +354,20 @@ nautilus_list_initialize (NautilusList *list)
list->details->title = GTK_WIDGET (nautilus_list_column_title_new());
}
+static void
+nautilus_list_destroy (GtkObject *object)
+{
+ NautilusList *list;
+
+ list = NAUTILUS_LIST (object);
+
+ unschedule_keyboard_row_reveal (list);
+
+ g_free (list->details);
+
+ NAUTILUS_CALL_PARENT_CLASS (GTK_OBJECT_CLASS, destroy, (object));
+}
+
static void
select_or_unselect_row_callback (GtkCList *clist, gint row, gint column, GdkEvent *event)
@@ -564,6 +626,303 @@ nautilus_list_button_release (GtkWidget *widget, GdkEventButton *event)
}
static void
+nautilus_list_clear_keyboard_focus (NautilusList *list)
+{
+ if (GTK_CLIST (list)->focus_row >= 0) {
+ gtk_widget_draw_focus (GTK_WIDGET (list));
+ }
+
+ GTK_CLIST (list)->focus_row = -1;
+}
+
+static void
+nautilus_list_set_keyboard_focus (NautilusList *list, int row)
+{
+ g_assert (row >= 0 && row < GTK_CLIST (list)->rows);
+
+ if (row == GTK_CLIST (list)->focus_row) {
+ return;
+ }
+
+ nautilus_list_clear_keyboard_focus (list);
+
+ GTK_CLIST (list)->focus_row = row;
+
+ gtk_widget_draw_focus (GTK_WIDGET (list));
+}
+
+static void
+nautilus_list_keyboard_move_to (NautilusList *list, int row, GdkEventKey *event)
+{
+ GtkCList *clist;
+
+ g_assert (NAUTILUS_IS_LIST (list));
+ g_assert (row >= 0 || row < GTK_CLIST (list)->rows);
+
+ clist = GTK_CLIST (list);
+
+ if ((event->state & GDK_CONTROL_MASK) != 0) {
+ /* Move the keyboard focus. */
+ nautilus_list_set_keyboard_focus (list, row);
+ } else {
+ /* Select row and get rid of special keyboard focus. */
+ nautilus_list_clear_keyboard_focus (list);
+ /* FIXME: This sends numerous selection_changed messages
+ * instead of only one. It also flashes unpleasantly when
+ * selection is pinned up at first row.
+ */
+ gtk_clist_unselect_all (clist);
+ gtk_clist_select_row (clist, row, -1);
+ }
+
+ schedule_keyboard_row_reveal (list, row);
+}
+
+static gboolean
+keyboard_row_reveal_timeout_callback (gpointer data)
+{
+ NautilusList *list;
+ int row;
+
+ GDK_THREADS_ENTER ();
+
+ list = NAUTILUS_LIST (data);
+ row = list->details->keyboard_row_to_reveal;
+
+ if (row >= 0 && row < GTK_CLIST (list)->rows) {
+ /* Only reveal the row if it's still the keyboard focus
+ * or if it's still selected.
+ * FIXME: Need to unschedule this if the user scrolls explicitly.
+ */
+ if (row == GTK_CLIST (list)->focus_row
+ || nautilus_list_is_row_selected (list, row)) {
+ reveal_row (list, row);
+ }
+ list->details->keyboard_row_reveal_timer_id = 0;
+ }
+
+ GDK_THREADS_LEAVE ();
+
+ return FALSE;
+}
+
+static void
+unschedule_keyboard_row_reveal (NautilusList *list)
+{
+ if (list->details->keyboard_row_reveal_timer_id != 0) {
+ gtk_timeout_remove (list->details->keyboard_row_reveal_timer_id);
+ }
+}
+
+static void
+schedule_keyboard_row_reveal (NautilusList *list, int row)
+{
+ unschedule_keyboard_row_reveal (list);
+
+ list->details->keyboard_row_to_reveal = row;
+ list->details->keyboard_row_reveal_timer_id
+ = gtk_timeout_add (KEYBOARD_ROW_REVEAL_TIMEOUT,
+ keyboard_row_reveal_timeout_callback,
+ list);
+}
+
+static void
+reveal_row (NautilusList *list, int row)
+{
+ GtkCList *clist;
+
+ g_assert (NAUTILUS_IS_LIST (list));
+
+ clist = GTK_CLIST (list);
+
+ if (ROW_TOP_YPIXEL (clist, row) + clist->row_height >
+ clist->clist_window_height) {
+ gtk_clist_moveto (clist, row, -1, 1, 0);
+ } else if (ROW_TOP_YPIXEL (clist, row) < 0) {
+ gtk_clist_moveto (clist, row, -1, 0, 0);
+ }
+}
+
+static void
+nautilus_list_keyboard_navigation_key_press (NautilusList *list, GdkEventKey *event,
+ GtkScrollType scroll_type, gboolean jump_to_end)
+{
+ GtkCList *clist;
+ int start_row;
+ int destination_row;
+ int rows_per_page;
+
+ g_assert (NAUTILUS_IS_LIST (list));
+
+ clist = GTK_CLIST (list);
+
+ if (scroll_type == GTK_SCROLL_JUMP) {
+ destination_row = (jump_to_end ?
+ clist->rows - 1 :
+ 0);
+ } else {
+ /* Choose the row to start with.
+ * If we have a keyboard focus, start with it.
+ * If there's a selection, use the selected row farthest toward the end.
+ */
+
+ if (GTK_CLIST (list)->focus_row >= 0) {
+ start_row = clist->focus_row;
+ } else {
+ start_row = (scroll_type == GTK_SCROLL_STEP_FORWARD || scroll_type == GTK_SCROLL_PAGE_FORWARD ?
+ nautilus_list_get_last_selected_row (list) :
+ nautilus_list_get_first_selected_row (list));
+ }
+
+ /* If there's no row to start with, select the row farthest toward the end.
+ * If there is a row to start with, select the next row in the arrow direction.
+ */
+ if (start_row < 0) {
+ destination_row = (scroll_type == GTK_SCROLL_STEP_FORWARD || scroll_type == GTK_SCROLL_PAGE_FORWARD ?
+ clist->rows - 1 :
+ 0);
+ } else if (scroll_type == GTK_SCROLL_STEP_FORWARD) {
+ destination_row = MIN (clist->rows - 1, start_row + 1);
+ } else if (scroll_type == GTK_SCROLL_STEP_BACKWARD) {
+ destination_row = MAX (0, start_row - 1);
+ } else {
+ g_assert (scroll_type == GTK_SCROLL_PAGE_FORWARD || GTK_SCROLL_PAGE_BACKWARD);
+ rows_per_page = (2 * clist->clist_window_height -
+ clist->row_height - CELL_SPACING) /
+ (2 * (clist->row_height + CELL_SPACING));
+
+ if (scroll_type == GTK_SCROLL_PAGE_FORWARD) {
+ destination_row = MIN (clist->rows - 1,
+ start_row + rows_per_page);
+ } else {
+ destination_row = MAX (0,
+ start_row - rows_per_page);
+ }
+ }
+ }
+
+ nautilus_list_keyboard_move_to (list, destination_row, event);
+}
+
+static void
+nautilus_list_keyboard_home (NautilusList *list, GdkEventKey *event)
+{
+ /* Home selects the first row.
+ * Control-Home sets the keyboard focus to the first row.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_JUMP, FALSE);
+}
+
+static void
+nautilus_list_keyboard_end (NautilusList *list, GdkEventKey *event)
+{
+ /* End selects the last row.
+ * Control-End sets the keyboard focus to the last row.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_JUMP, TRUE);
+}
+
+static void
+nautilus_list_keyboard_up (NautilusList *list, GdkEventKey *event)
+{
+ /* Up selects the next higher row.
+ * Control-Up sets the keyboard focus to the next higher icon.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_STEP_BACKWARD, FALSE);
+}
+
+static void
+nautilus_list_keyboard_down (NautilusList *list, GdkEventKey *event)
+{
+ /* Down selects the next lower row.
+ * Control-Down sets the keyboard focus to the next lower icon.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_STEP_FORWARD, FALSE);
+}
+
+static void
+nautilus_list_keyboard_page_up (NautilusList *list, GdkEventKey *event)
+{
+ /* Page Up selects a row one screenful higher.
+ * Control-Page Up sets the keyboard focus to the row one screenful higher.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_PAGE_BACKWARD, FALSE);
+}
+
+static void
+nautilus_list_keyboard_page_down (NautilusList *list, GdkEventKey *event)
+{
+ /* Page Down selects a row one screenful lower.
+ * Control-Page Down sets the keyboard focus to the row one screenful lower.
+ */
+ nautilus_list_keyboard_navigation_key_press (list, event, GTK_SCROLL_PAGE_FORWARD, FALSE);
+}
+
+static void
+nautilus_list_keyboard_space (NautilusList *list, GdkEventKey *event)
+{
+ if (event->state & GDK_CONTROL_MASK) {
+ gtk_signal_emit_by_name (GTK_OBJECT (list), "toggle_focus_row");
+ }
+}
+
+static void
+nautilus_list_activate_selected_items (NautilusList *list)
+{
+ int row;
+
+ for (row = 0; row < GTK_CLIST (list)->rows; ++row) {
+ if (nautilus_list_is_row_selected (list, row)) {
+ activate_row (list, row);
+ }
+ }
+}
+
+static int
+nautilus_list_key_press (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ NautilusList *list;
+
+ list = NAUTILUS_LIST (widget);
+
+ switch (event->keyval) {
+ case GDK_Home:
+ nautilus_list_keyboard_home (list, event);
+ break;
+ case GDK_End:
+ nautilus_list_keyboard_end (list, event);
+ break;
+ case GDK_Page_Up:
+ nautilus_list_keyboard_page_up (list, event);
+ break;
+ case GDK_Page_Down:
+ nautilus_list_keyboard_page_down (list, event);
+ break;
+ case GDK_Up:
+ nautilus_list_keyboard_up (list, event);
+ break;
+ case GDK_Down:
+ nautilus_list_keyboard_down (list, event);
+ break;
+ case GDK_space:
+ nautilus_list_keyboard_space (list, event);
+ break;
+ case GDK_Return:
+ nautilus_list_activate_selected_items (list);
+ break;
+ default:
+ if (NAUTILUS_CALL_PARENT_CLASS (GTK_WIDGET_CLASS, key_press_event, (widget, event))) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
nautilus_list_realize (GtkWidget *widget)
{
NautilusList *list;
@@ -803,7 +1162,7 @@ nautilus_list_draw_focus (GtkWidget *widget)
if (clist->focus_row < 0) {
return;
}
-
+
gdk_gc_get_values (clist->xor_gc, &saved_values);
gdk_gc_set_stipple (clist->xor_gc, nautilus_stipple_bitmap ());
@@ -1591,105 +1950,6 @@ nautilus_list_column_resize_track_end (GtkWidget *widget, int column)
clist->drag_pos = -1;
}
-static void
-nautilus_list_move_focus_row (NautilusList *list,
- GtkScrollType scroll_type,
- gfloat position)
-{
- /* FIXME: Moved over from gtkclist.c. Should
- * be cleaned up stylistically and possibly
- * eliminated as a concept. Instead, determine
- * destination position and then determine whether
- * it should be selected or just focused. I'll do
- * this in the next few days (John Sullivan, 4/7/00)
- */
- GtkCList *clist;
- GtkWidget *widget;
-
- g_return_if_fail (NAUTILUS_IS_LIST (list));
-
- clist = GTK_CLIST (list);
- widget = GTK_WIDGET (list);
-
- switch (scroll_type)
- {
- case GTK_SCROLL_STEP_BACKWARD:
- if (clist->focus_row <= 0)
- return;
- gtk_widget_draw_focus (widget);
- clist->focus_row--;
- gtk_widget_draw_focus (widget);
- break;
- case GTK_SCROLL_STEP_FORWARD:
- if (clist->focus_row >= clist->rows - 1)
- return;
- gtk_widget_draw_focus (widget);
- clist->focus_row++;
- gtk_widget_draw_focus (widget);
- break;
- case GTK_SCROLL_PAGE_BACKWARD:
- if (clist->focus_row <= 0)
- return;
- gtk_widget_draw_focus (widget);
- clist->focus_row = MAX (0, clist->focus_row -
- (2 * clist->clist_window_height -
- clist->row_height - CELL_SPACING) /
- (2 * (clist->row_height + CELL_SPACING)));
- gtk_widget_draw_focus (widget);
- break;
- case GTK_SCROLL_PAGE_FORWARD:
- if (clist->focus_row >= clist->rows - 1)
- return;
- gtk_widget_draw_focus (widget);
- clist->focus_row = MIN (clist->rows - 1, clist->focus_row +
- (2 * clist->clist_window_height -
- clist->row_height - CELL_SPACING) /
- (2 * (clist->row_height + CELL_SPACING)));
- gtk_widget_draw_focus (widget);
- break;
- case GTK_SCROLL_JUMP:
- if (position >= 0 && position <= 1)
- {
- gtk_widget_draw_focus (widget);
- clist->focus_row = position * (clist->rows - 1);
- gtk_widget_draw_focus (widget);
- }
- break;
- default:
- break;
- }
-}
-
-static void
-nautilus_list_scroll_vertical (GtkCList *clist,
- GtkScrollType scroll_type,
- gfloat position)
-{
- /* Pulled over from GtkCList and simplified for our purposes. May
- * eliminate this entirely by replacing all the key bindings.
- */
- g_return_if_fail (clist != NULL);
- g_return_if_fail (NAUTILUS_IS_LIST (clist));
- g_return_if_fail (clist->selection_mode == GTK_SELECTION_MULTIPLE);
-
- /* Stolen from scroll_vertical in gtkclist.c. I don't know
- * exactly what this is for.
- */
- if (gdk_pointer_is_grabbed () && GTK_WIDGET_HAS_GRAB (clist))
- return;
-
-
- nautilus_list_move_focus_row (NAUTILUS_LIST (clist), scroll_type, position);
-
- if (ROW_TOP_YPIXEL (clist, clist->focus_row) + clist->row_height >
- clist->clist_window_height) {
- gtk_clist_moveto (clist, clist->focus_row, -1, 1, 0);
- }
- else if (ROW_TOP_YPIXEL (clist, clist->focus_row) < 0) {
- gtk_clist_moveto (clist, clist->focus_row, -1, 0, 0);
- }
-}
-
/* We override the drag_begin signal to do nothing */
static void
nautilus_list_drag_begin (GtkWidget *widget, GdkDragContext *context)
@@ -1800,6 +2060,50 @@ nautilus_list_new_with_titles (int columns, const char * const *titles)
return GTK_WIDGET (list);
}
+static int
+nautilus_list_get_first_selected_row (NautilusList *list)
+{
+ GtkCListRow *row;
+ GList *p;
+ int row_number;
+
+ g_return_val_if_fail (NAUTILUS_IS_LIST (list), -1);
+
+ row_number = 0;
+ for (p = GTK_CLIST (list)->row_list; p != NULL; p = p->next) {
+ row = p->data;
+ if (row->state == GTK_STATE_SELECTED) {
+ return row_number;
+ }
+
+ ++row_number;
+ }
+
+ return -1;
+}
+
+static int
+nautilus_list_get_last_selected_row (NautilusList *list)
+{
+ GtkCListRow *row;
+ GList *p;
+ int row_number;
+
+ g_return_val_if_fail (NAUTILUS_IS_LIST (list), -1);
+
+ row_number = GTK_CLIST (list)->rows - 1;
+ for (p = GTK_CLIST (list)->row_list_end; p != NULL; p = p->prev) {
+ row = p->data;
+ if (row->state == GTK_STATE_SELECTED) {
+ return row_number;
+ }
+
+ --row_number;
+ }
+
+ return -1;
+}
+
GList *
nautilus_list_get_selection (NautilusList *list)
{