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.
This commit is contained in:
John Sullivan 2000-04-10 00:04:14 +00:00
parent b39c3637ef
commit f27fcab0c8
4 changed files with 1281 additions and 336 deletions

View file

@ -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:

View file

@ -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)
@ -563,6 +625,303 @@ nautilus_list_button_release (GtkWidget *widget, GdkEventButton *event)
return retval;
}
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)
{
@ -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)
{

View file

@ -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)
@ -563,6 +625,303 @@ nautilus_list_button_release (GtkWidget *widget, GdkEventButton *event)
return retval;
}
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)
{
@ -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)
{

View file

@ -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)
@ -563,6 +625,303 @@ nautilus_list_button_release (GtkWidget *widget, GdkEventButton *event)
return retval;
}
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)
{
@ -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)
{