First pass on making list column resizing live.

2000-03-16  Pavel Cisler  <pavel@eazel.com>

	First pass on making list column resizing live.

	* libnautilus/Makefile.am
	* libnautilus/nautilus-list-column-title.c
	* libnautilus/nautilus-list-column-title.h
	* libnautilus/gtkflist.h
	* libnautilus/gtkflist.c
	* manager/fm-directory-view-list.c
	* libnautilus/nautilus-gdk-extensions.c
	* libnautilus/nautilus-gdk-extensions.h
This commit is contained in:
Pavel Cisler 2000-03-17 04:46:16 +00:00 committed by Pavel Cisler
parent 6d921f15ff
commit 53aa5a6308
23 changed files with 5169 additions and 191 deletions

View file

@ -1,3 +1,53 @@
2000-03-16 Pavel Cisler <pavel@eazel.com>
First pass on making list column resizing live.
* libnautilus/Makefile.am
* libnautilus/nautilus-list-column-title.c
* libnautilus/nautilus-list-column-title.h
New list column title widget that makes it easier to hook into
the mouse tracking and allows using flicker free redraw. Currently
it doesn't use an offscreen buffer yet, comming soon.
* libnautilus/gtkflist.c
(gtk_flist_initialize_class), (gtk_flist_initialize),
(gtk_flist_realize), (list_requisition_width),
(gtk_flist_size_request), (new_column_width), (size_allocate_columns),
(size_allocate_title_buttons), (get_cell_style), (draw_cell_pixmap),
(draw_row), (draw_rows),
Made it possible to replace the existing column list view titles
from GtkCList view to be replaced with the new widget. In order
to do that I had to pull in a number of calls form GtkCList and
work around dependencies on column titles being buttons.
Prepared code some for smart column truncation.
* libnautilus/gtkflist.h
* libnautilus/gtkflist.c
(gtk_flist_track_new_column_width), (gtk_flist_column_resize_track_start),
(gtk_flist_column_resize_track), (gtk_flist_column_resize_track_end):
Added new calls that hook up with the mouse tracking code that
resize the tracker column.
* manager/fm-directory-view-list.c
(get_sort_indicator), (hide_sort_indicator), (show_sort_indicator),
(create_flist):
Removed code that insert the sort order indicator into the list
column - this is now done in the new column title widget.
* manager/fm-directory-view-list.c
(create_flist):
Added min and max column width values.
* libnautilus/gtkflist.c
(gtk_flist_initialize_class)
Fixe a bug where the "selection_changed" was mistakenly connected
to the start_drag callback.
* libnautilus/nautilus-gdk-extensions.c
* libnautilus/nautilus-gdk-extensions.h
(nautilus_rectangle_contains), (nautilus_rectangle_inset):
Added GdkRectangle utility calls.
2000-03-16 John Sullivan <sullivan@eazel.com>
* src/nautilus-index-title.c:

View file

@ -47,6 +47,7 @@ libnautilusinclude_HEADERS= \
nautilus-gtk-extensions.h \
nautilus-icon-factory.h \
nautilus-icons-view-icon-item.h \
nautilus-list-column-title.h \
nautilus-metadata.h \
nautilus-mime-type.h \
nautilus-self-checks.h \
@ -85,6 +86,7 @@ libnautilus_la_SOURCES=$(nautilus_idl_sources) \
nautilus-icon-factory.c \
nautilus-icons-view-icon-item.c \
nautilus-lib-self-check-functions.c \
nautilus-list-column-title.c \
nautilus-mime-type.c \
nautilus-self-checks.c \
nautilus-string-list.c \

View file

@ -19,6 +19,7 @@
#include "nautilus-glib-extensions.h"
#include "nautilus-gtk-macros.h"
#include "nautilus-background.h"
#include "nautilus-list-column-title.h"
struct _GtkFListDetails
{
@ -37,7 +38,8 @@ struct _GtkFListDetails
/* Delayed selection information */
int dnd_select_pending;
guint dnd_select_pending_state;
GtkWidget *title;
};
/* maximum amount of milliseconds the mouse button is allowed to stay down and still be considered a click */
@ -81,8 +83,18 @@ static gboolean gtk_flist_drag_drop (GtkWidget *widget, GdkDragContext *context,
static void gtk_flist_drag_data_received (GtkWidget *widget, GdkDragContext *context,
gint x, gint y, GtkSelectionData *data,
guint info, guint time);
static void select_or_unselect_row_cb (GtkCList *clist, gint row, gint column,
GdkEvent *event);
static void gtk_flist_clear (GtkCList *clist);
static void draw_row (GtkCList *flist, GdkRectangle *area, gint row, GtkCListRow *clist_row);
static void gtk_flist_realize (GtkWidget *widget);
static void gtk_flist_size_request (GtkWidget *widget, GtkRequisition *requisition);
static void gtk_flist_column_resize_track_start (GtkWidget *widget, int column);
static void gtk_flist_column_resize_track (GtkWidget *widget, int column);
static void gtk_flist_column_resize_track_end (GtkWidget *widget, int column);
NAUTILUS_DEFINE_CLASS_BOILERPLATE (GtkFList, gtk_flist, GTK_TYPE_CLIST)
@ -90,15 +102,17 @@ static guint flist_signals[LAST_SIGNAL];
/* Standard class initialization function */
static void
gtk_flist_initialize_class (GtkFListClass *class)
gtk_flist_initialize_class (GtkFListClass *klass)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
GtkCListClass *clist_class;
GtkFListClass *flist_class;
object_class = (GtkObjectClass *) class;
widget_class = (GtkWidgetClass *) class;
clist_class = (GtkCListClass *) class;
object_class = (GtkObjectClass *) klass;
widget_class = (GtkWidgetClass *) klass;
clist_class = (GtkCListClass *) klass;
flist_class = (GtkFListClass *) klass;
flist_signals[CONTEXT_CLICK_SELECTION] =
gtk_signal_new ("context_click_selection",
@ -135,13 +149,18 @@ gtk_flist_initialize_class (GtkFListClass *class)
gtk_signal_new ("selection_changed",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (GtkFListClass, start_drag),
GTK_SIGNAL_OFFSET (GtkFListClass, selection_changed),
gtk_marshal_NONE__NONE,
GTK_TYPE_NONE, 0);
gtk_object_class_add_signals (object_class, flist_signals, LAST_SIGNAL);
flist_class->column_resize_track_start = gtk_flist_column_resize_track_start;
flist_class->column_resize_track = gtk_flist_column_resize_track;
flist_class->column_resize_track_end = gtk_flist_column_resize_track_end;
clist_class->clear = gtk_flist_clear;
clist_class->draw_row = draw_row;
widget_class->button_press_event = gtk_flist_button_press;
widget_class->button_release_event = gtk_flist_button_release;
@ -155,20 +174,14 @@ gtk_flist_initialize_class (GtkFListClass *class)
widget_class->drag_motion = gtk_flist_drag_motion;
widget_class->drag_drop = gtk_flist_drag_drop;
widget_class->drag_data_received = gtk_flist_drag_data_received;
}
static void
select_or_unselect_row_cb (GtkCList *clist, gint row, gint column, GdkEvent *event)
{
/* This is the one bottleneck for all selection changes */
gtk_signal_emit (GTK_OBJECT (clist), flist_signals[SELECTION_CHANGED]);
widget_class->realize = gtk_flist_realize;
widget_class->size_request = gtk_flist_size_request;
}
/* Standard object initialization function */
static void
gtk_flist_initialize (GtkFList *flist)
{
{
flist->details = g_new0 (GtkFListDetails, 1);
flist->details->anchor_row = -1;
@ -194,6 +207,16 @@ gtk_flist_initialize (GtkFList *flist)
"unselect_row",
select_or_unselect_row_cb,
flist);
flist->details->title = GTK_WIDGET (nautilus_list_column_title_new());
}
static void
select_or_unselect_row_cb (GtkCList *clist, gint row, gint column, GdkEvent *event)
{
/* This is the one bottleneck for all selection changes */
gtk_signal_emit (GTK_OBJECT (clist), flist_signals[SELECTION_CHANGED]);
}
static void
@ -429,6 +452,717 @@ gtk_flist_button_release (GtkWidget *widget, GdkEventButton *event)
return retval;
}
/* stolen from gtkclist.c for now */
/* minimum allowed width of a column */
#define COLUMN_MIN_WIDTH 5
/* this defines the base grid spacing */
#define CELL_SPACING 1
/* added the horizontal space at the beginning and end of a row */
#define COLUMN_INSET 3
/* the width of the column resize windows */
#define DRAG_WIDTH 6
/* gives the left pixel of the given column in context of
* the clist's hoffset */
#define COLUMN_LEFT_XPIXEL(clist, colnum) ((clist)->column[(colnum)].area.x + \
(clist)->hoffset)
/* gives the top pixel of the given row in context of
* the clist's voffset */
#define ROW_TOP_YPIXEL(clist, row) (((clist)->row_height * (row)) + \
(((row) + 1) * CELL_SPACING) + \
(clist)->voffset)
/* returns the row index from a y pixel location in the
* context of the clist's voffset */
#define ROW_FROM_YPIXEL(clist, y) (((y) - (clist)->voffset) / \
((clist)->row_height + CELL_SPACING))
/* returns the GList item for the nth row */
#define ROW_ELEMENT(clist, row) (((row) == (clist)->rows - 1) ? \
(clist)->row_list_end : \
g_list_nth ((clist)->row_list, (row)))
/* returns the total height of the list */
#define LIST_HEIGHT(clist) (((clist)->row_height * ((clist)->rows)) + \
(CELL_SPACING * ((clist)->rows + 1)))
static void
gtk_flist_realize (GtkWidget *widget)
{
GtkFList *flist;
GtkCList *clist;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
flist = GTK_FLIST (widget);
clist = GTK_CLIST (widget);
clist->column[0].button = flist->details->title;
NAUTILUS_CALL_PARENT_CLASS (GTK_WIDGET_CLASS, realize, (widget));
if (clist->title_window) {
gtk_widget_set_parent_window (flist->details->title, clist->title_window);
}
gtk_widget_set_parent (flist->details->title, GTK_WIDGET (clist));
gtk_widget_show (flist->details->title);
GTK_CLIST_SET_FLAG (clist, CLIST_SHOW_TITLES);
}
/* this is here just temporarily */
static gint
list_requisition_width (GtkCList *clist)
{
gint width = CELL_SPACING;
gint i;
for (i = clist->columns - 1; i >= 0; i--) {
if (!clist->column[i].visible)
continue;
if (clist->column[i].width_set)
width += clist->column[i].width + CELL_SPACING + (2 * COLUMN_INSET);
else if (GTK_CLIST_SHOW_TITLES(clist) && clist->column[i].button)
width += clist->column[i].button->requisition.width;
}
return width;
}
static void
gtk_flist_size_request (GtkWidget *widget, GtkRequisition *requisition)
{
/* stolen from gtk_clist
* make sure the proper title ammount is allocated for the column
* title view -- this would not otherwise be done because
* GtkFList depends the buttons being there when doing a size calculation
*/
GtkFList *flist;
GtkCList *clist;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
g_return_if_fail (requisition != NULL);
clist = GTK_CLIST (widget);
flist = GTK_FLIST (widget);
requisition->width = 0;
requisition->height = 0;
/* compute the size of the column title (title) area */
clist->column_title_area.height = 0;
if (GTK_CLIST_SHOW_TITLES(clist) && flist->details->title) {
GtkRequisition child_requisition;
gtk_widget_size_request (flist->details->title,
&child_requisition);
child_requisition.height = 20;
/* for now */
clist->column_title_area.height =
MAX (clist->column_title_area.height,
child_requisition.height);
}
requisition->width += (widget->style->klass->xthickness +
GTK_CONTAINER (widget)->border_width) * 2;
requisition->height += (clist->column_title_area.height +
(widget->style->klass->ythickness +
GTK_CONTAINER (widget)->border_width) * 2);
requisition->width += list_requisition_width (clist);
requisition->height += LIST_HEIGHT (clist);
}
static gint
new_column_width (GtkCList *clist, gint column, gint *x)
{
gint xthickness = GTK_WIDGET (clist)->style->klass->xthickness;
gint width;
gint cx;
gint dx;
gint last_column;
/* first translate the x position from widget->window
* to clist->clist_window */
cx = *x - xthickness;
for (last_column = clist->columns - 1;
last_column >= 0 && !clist->column[last_column].visible; last_column--);
/* calculate new column width making sure it doesn't end up
* less than the minimum width */
dx = (COLUMN_LEFT_XPIXEL (clist, column) + COLUMN_INSET +
(column < last_column) * CELL_SPACING);
width = cx - dx;
if (width < MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width)) {
width = MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width);
cx = dx + width;
*x = cx + xthickness;
} else if (clist->column[column].max_width >= COLUMN_MIN_WIDTH &&
width > clist->column[column].max_width) {
width = clist->column[column].max_width;
cx = dx + clist->column[column].max_width;
*x = cx + xthickness;
}
if (cx < 0 || cx > clist->clist_window_width)
*x = -1;
return width;
}
static void
size_allocate_columns (GtkCList *clist, gboolean block_resize)
{
int xoffset = CELL_SPACING + COLUMN_INSET;
int last_column;
int i;
/* find last visible column and calculate correct column width */
for (last_column = clist->columns - 1;
last_column >= 0 && !clist->column[last_column].visible; last_column--)
;
if (last_column < 0)
return;
for (i = 0; i <= last_column; i++) {
if (!clist->column[i].visible)
continue;
clist->column[i].area.x = xoffset;
if (clist->column[i].width_set) {
if (!block_resize && GTK_CLIST_SHOW_TITLES(clist) &&
clist->column[i].auto_resize && clist->column[i].button) {
gint width;
width = (clist->column[i].button->requisition.width -
(CELL_SPACING + (2 * COLUMN_INSET)));
if (width > clist->column[i].width)
gtk_clist_set_column_width (clist, i, width);
}
clist->column[i].area.width = clist->column[i].width;
xoffset += clist->column[i].width + CELL_SPACING + (2 * COLUMN_INSET);
} else if (GTK_CLIST_SHOW_TITLES(clist) && clist->column[i].button) {
clist->column[i].area.width =
clist->column[i].button->requisition.width -
(CELL_SPACING + (2 * COLUMN_INSET));
xoffset += clist->column[i].button->requisition.width;
}
}
clist->column[last_column].area.width += MAX (0, clist->clist_window_width + COLUMN_INSET - xoffset);
}
static void
size_allocate_title_buttons (GtkCList *clist)
{
GtkAllocation button_allocation;
int last_column;
int last_button = 0;
int i;
button_allocation.x = clist->hoffset;
button_allocation.y = 0;
button_allocation.width = 0;
button_allocation.height = clist->column_title_area.height;
/* find last visible column */
for (last_column = clist->columns - 1; last_column >= 0; last_column--)
if (clist->column[last_column].visible)
break;
for (i = 0; i < last_column; i++) {
if (!clist->column[i].visible) {
last_button = i + 1;
gdk_window_hide (clist->column[i].window);
continue;
}
button_allocation.width += (clist->column[i].area.width +
CELL_SPACING + 2 * COLUMN_INSET);
if (!clist->column[i + 1].button) {
gdk_window_hide (clist->column[i].window);
continue;
}
gtk_widget_size_allocate (clist->column[last_button].button,
&button_allocation);
button_allocation.x += button_allocation.width;
button_allocation.width = 0;
last_button = i + 1;
}
button_allocation.width += (clist->column[last_column].area.width +
2 * (CELL_SPACING + COLUMN_INSET));
gtk_widget_size_allocate (clist->column[last_button].button,
&button_allocation);
}
static void
get_cell_style (GtkCList *clist, GtkCListRow *clist_row,
gint state, gint column, GtkStyle **style,
GdkGC **fg_gc, GdkGC **bg_gc)
{
gint fg_state;
if ((state == GTK_STATE_NORMAL) &&
(GTK_WIDGET (clist)->state == GTK_STATE_INSENSITIVE))
fg_state = GTK_STATE_INSENSITIVE;
else
fg_state = state;
if (clist_row->cell[column].style) {
if (style)
*style = clist_row->cell[column].style;
if (fg_gc)
*fg_gc = clist_row->cell[column].style->fg_gc[fg_state];
if (bg_gc) {
if (state == GTK_STATE_SELECTED)
*bg_gc = clist_row->cell[column].style->bg_gc[state];
else
*bg_gc = clist_row->cell[column].style->base_gc[state];
}
} else if (clist_row->style) {
if (style)
*style = clist_row->style;
if (fg_gc)
*fg_gc = clist_row->style->fg_gc[fg_state];
if (bg_gc) {
if (state == GTK_STATE_SELECTED)
*bg_gc = clist_row->style->bg_gc[state];
else
*bg_gc = clist_row->style->base_gc[state];
}
} else {
if (style)
*style = GTK_WIDGET (clist)->style;
if (fg_gc)
*fg_gc = GTK_WIDGET (clist)->style->fg_gc[fg_state];
if (bg_gc) {
if (state == GTK_STATE_SELECTED)
*bg_gc = GTK_WIDGET (clist)->style->bg_gc[state];
else
*bg_gc = GTK_WIDGET (clist)->style->base_gc[state];
}
if (state != GTK_STATE_SELECTED) {
if (fg_gc && clist_row->fg_set)
*fg_gc = clist->fg_gc;
if (bg_gc && clist_row->bg_set)
*bg_gc = clist->bg_gc;
}
}
}
static gint
draw_cell_pixmap (GdkWindow *window, GdkRectangle *clip_rectangle, GdkGC *fg_gc,
GdkPixmap *pixmap, GdkBitmap *mask,
gint x, gint y, gint width, gint height)
{
gint xsrc = 0;
gint ysrc = 0;
if (mask) {
gdk_gc_set_clip_mask (fg_gc, mask);
gdk_gc_set_clip_origin (fg_gc, x, y);
}
if (x < clip_rectangle->x) {
xsrc = clip_rectangle->x - x;
width -= xsrc;
x = clip_rectangle->x;
}
if (x + width > clip_rectangle->x + clip_rectangle->width)
width = clip_rectangle->x + clip_rectangle->width - x;
if (y < clip_rectangle->y) {
ysrc = clip_rectangle->y - y;
height -= ysrc;
y = clip_rectangle->y;
}
if (y + height > clip_rectangle->y + clip_rectangle->height)
height = clip_rectangle->y + clip_rectangle->height - y;
gdk_draw_pixmap (window, fg_gc, pixmap, xsrc, ysrc, x, y, width, height);
gdk_gc_set_clip_origin (fg_gc, 0, 0);
if (mask)
gdk_gc_set_clip_mask (fg_gc, NULL);
return x + MAX (width, 0);
}
static void
draw_row (GtkCList *clist,
GdkRectangle *area,
gint row,
GtkCListRow *clist_row)
{
GtkWidget *widget;
GdkRectangle *rect;
GdkRectangle row_rectangle;
GdkRectangle cell_rectangle;
GdkRectangle clip_rectangle;
GdkRectangle intersect_rectangle;
gint last_column;
gint state;
gint i;
g_return_if_fail (clist != NULL);
/* bail now if we arn't drawable yet */
if (!GTK_WIDGET_DRAWABLE (clist) || row < 0 || row >= clist->rows)
return;
widget = GTK_WIDGET (clist);
/* if the function is passed the pointer to the row instead of null,
* it avoids this expensive lookup */
if (!clist_row)
clist_row = ROW_ELEMENT (clist, row)->data;
/* rectangle of the entire row */
row_rectangle.x = 0;
row_rectangle.y = ROW_TOP_YPIXEL (clist, row);
row_rectangle.width = clist->clist_window_width;
row_rectangle.height = clist->row_height;
/* rectangle of the cell spacing above the row */
cell_rectangle.x = 0;
cell_rectangle.y = row_rectangle.y - CELL_SPACING;
cell_rectangle.width = row_rectangle.width;
cell_rectangle.height = CELL_SPACING;
/* rectangle used to clip drawing operations, its y and height
* positions only need to be set once, so we set them once here.
* the x and width are set withing the drawing loop below once per
* column */
clip_rectangle.y = row_rectangle.y;
clip_rectangle.height = row_rectangle.height;
if (clist_row->state == GTK_STATE_NORMAL)
{
if (clist_row->fg_set)
gdk_gc_set_foreground (clist->fg_gc, &clist_row->foreground);
if (clist_row->bg_set)
gdk_gc_set_foreground (clist->bg_gc, &clist_row->background);
}
state = clist_row->state;
/* draw the cell borders and background */
if (area)
{
rect = &intersect_rectangle;
if (gdk_rectangle_intersect (area, &cell_rectangle,
&intersect_rectangle))
gdk_draw_rectangle (clist->clist_window,
widget->style->base_gc[GTK_STATE_ACTIVE],
TRUE,
intersect_rectangle.x,
intersect_rectangle.y,
intersect_rectangle.width,
intersect_rectangle.height);
/* the last row has to clear its bottom cell spacing too */
if (clist_row == clist->row_list_end->data)
{
cell_rectangle.y += clist->row_height + CELL_SPACING;
if (gdk_rectangle_intersect (area, &cell_rectangle,
&intersect_rectangle))
gdk_draw_rectangle (clist->clist_window,
widget->style->base_gc[GTK_STATE_ACTIVE],
TRUE,
intersect_rectangle.x,
intersect_rectangle.y,
intersect_rectangle.width,
intersect_rectangle.height);
}
if (!gdk_rectangle_intersect (area, &row_rectangle,&intersect_rectangle))
return;
}
else
{
rect = &clip_rectangle;
gdk_draw_rectangle (clist->clist_window,
widget->style->base_gc[GTK_STATE_ACTIVE],
TRUE,
cell_rectangle.x,
cell_rectangle.y,
cell_rectangle.width,
cell_rectangle.height);
/* the last row has to clear its bottom cell spacing too */
if (clist_row == clist->row_list_end->data)
{
cell_rectangle.y += clist->row_height + CELL_SPACING;
gdk_draw_rectangle (clist->clist_window,
widget->style->base_gc[GTK_STATE_ACTIVE],
TRUE,
cell_rectangle.x,
cell_rectangle.y,
cell_rectangle.width,
cell_rectangle.height);
}
}
for (last_column = clist->columns - 1;
last_column >= 0 && !clist->column[last_column].visible; last_column--)
;
/* iterate and draw all the columns (row cells) and draw their contents */
for (i = 0; i < clist->columns; i++)
{
GtkStyle *style;
GdkGC *fg_gc;
GdkGC *bg_gc;
gint width;
gint height;
gint pixmap_width;
gint offset = 0;
gint row_center_offset;
if (!clist->column[i].visible)
continue;
get_cell_style (clist, clist_row, state, i, &style, &fg_gc, &bg_gc);
clip_rectangle.x = clist->column[i].area.x + clist->hoffset;
clip_rectangle.width = clist->column[i].area.width;
/* calculate clipping region clipping region */
clip_rectangle.x -= COLUMN_INSET + CELL_SPACING;
clip_rectangle.width += (2 * COLUMN_INSET + CELL_SPACING +
(i == last_column) * CELL_SPACING);
if (area && !gdk_rectangle_intersect (area, &clip_rectangle,
&intersect_rectangle))
continue;
gdk_draw_rectangle (clist->clist_window, bg_gc, TRUE,
rect->x, rect->y, rect->width, rect->height);
clip_rectangle.x += COLUMN_INSET + CELL_SPACING;
clip_rectangle.width -= (2 * COLUMN_INSET + CELL_SPACING +
(i == last_column) * CELL_SPACING);
/* calculate real width for column justification */
pixmap_width = 0;
offset = 0;
switch (clist_row->cell[i].type)
{
case GTK_CELL_TEXT:
width = gdk_string_width (style->font,
GTK_CELL_TEXT (clist_row->cell[i])->text);
break;
case GTK_CELL_PIXMAP:
gdk_window_get_size (GTK_CELL_PIXMAP (clist_row->cell[i])->pixmap,
&pixmap_width, &height);
width = pixmap_width;
break;
case GTK_CELL_PIXTEXT:
gdk_window_get_size (GTK_CELL_PIXTEXT (clist_row->cell[i])->pixmap,
&pixmap_width, &height);
width = (pixmap_width +
GTK_CELL_PIXTEXT (clist_row->cell[i])->spacing +
gdk_string_width (style->font,
GTK_CELL_PIXTEXT
(clist_row->cell[i])->text));
break;
default:
continue;
break;
}
switch (clist->column[i].justification)
{
case GTK_JUSTIFY_LEFT:
offset = clip_rectangle.x + clist_row->cell[i].horizontal;
break;
case GTK_JUSTIFY_RIGHT:
offset = (clip_rectangle.x + clist_row->cell[i].horizontal +
clip_rectangle.width - width);
break;
case GTK_JUSTIFY_CENTER:
case GTK_JUSTIFY_FILL:
offset = (clip_rectangle.x + clist_row->cell[i].horizontal +
(clip_rectangle.width / 2) - (width / 2));
break;
};
/* Draw Text and/or Pixmap */
switch (clist_row->cell[i].type)
{
case GTK_CELL_PIXMAP:
draw_cell_pixmap (clist->clist_window, &clip_rectangle, fg_gc,
GTK_CELL_PIXMAP (clist_row->cell[i])->pixmap,
GTK_CELL_PIXMAP (clist_row->cell[i])->mask,
offset,
clip_rectangle.y + clist_row->cell[i].vertical +
(clip_rectangle.height - height) / 2,
pixmap_width, height);
break;
case GTK_CELL_PIXTEXT:
offset =
draw_cell_pixmap (clist->clist_window, &clip_rectangle, fg_gc,
GTK_CELL_PIXTEXT (clist_row->cell[i])->pixmap,
GTK_CELL_PIXTEXT (clist_row->cell[i])->mask,
offset,
clip_rectangle.y + clist_row->cell[i].vertical+
(clip_rectangle.height - height) / 2,
pixmap_width, height);
offset += GTK_CELL_PIXTEXT (clist_row->cell[i])->spacing;
case GTK_CELL_TEXT:
if (style != GTK_WIDGET (clist)->style)
row_center_offset = (((clist->row_height - style->font->ascent -
style->font->descent - 1) / 2) + 1.5 +
style->font->ascent);
else
row_center_offset = clist->row_center_offset;
gdk_gc_set_clip_rectangle (fg_gc, &clip_rectangle);
gdk_draw_string (clist->clist_window, style->font, fg_gc,
offset,
row_rectangle.y + row_center_offset +
clist_row->cell[i].vertical,
(clist_row->cell[i].type == GTK_CELL_PIXTEXT) ?
GTK_CELL_PIXTEXT (clist_row->cell[i])->text :
GTK_CELL_TEXT (clist_row->cell[i])->text);
gdk_gc_set_clip_rectangle (fg_gc, NULL);
break;
default:
break;
}
}
/* draw focus rectangle */
if (clist->focus_row == row &&
GTK_WIDGET_CAN_FOCUS (widget) && GTK_WIDGET_HAS_FOCUS(widget))
{
if (!area)
gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE,
row_rectangle.x, row_rectangle.y,
row_rectangle.width - 1, row_rectangle.height - 1);
else if (gdk_rectangle_intersect (area, &row_rectangle,
&intersect_rectangle))
{
gdk_gc_set_clip_rectangle (clist->xor_gc, &intersect_rectangle);
gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE,
row_rectangle.x, row_rectangle.y,
row_rectangle.width - 1,
row_rectangle.height - 1);
gdk_gc_set_clip_rectangle (clist->xor_gc, NULL);
}
}
}
static void
draw_rows (GtkCList *clist, GdkRectangle *area)
{
GList *list;
gint i;
gint first_row;
gint last_row;
if (clist->row_height == 0 || !GTK_WIDGET_DRAWABLE (clist))
return;
first_row = ROW_FROM_YPIXEL (clist, area->y);
last_row = ROW_FROM_YPIXEL (clist, area->y + area->height);
/* this is a small special case which exposes the bottom cell line
* on the last row -- it might go away if I change the wall the cell
* spacings are drawn
*/
if (clist->rows == first_row)
first_row--;
list = ROW_ELEMENT (clist, first_row);
for (i = first_row; i <= last_row ; i++) {
if (list == NULL)
break;
GTK_CLIST_CLASS ((GTK_OBJECT (clist))->klass)->draw_row (clist, area, i,
list->data);
list = list->next;
}
}
static void
gtk_flist_track_new_column_width (GtkCList *clist, int column, int new_width)
{
GtkFList *flist;
GdkRectangle title_redraw_area;
flist = GTK_FLIST (clist);
/* pin new_width to min and max values */
if (new_width < MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width))
new_width = MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width);
if (clist->column[column].max_width >= 0 &&
new_width > clist->column[column].max_width)
new_width = clist->column[column].max_width;
/* check to see if the pinned value is still different */
if (clist->column[column].width == new_width)
return;
/* set the new width */
clist->column[column].width = new_width;
clist->column[column].width_set = TRUE;
size_allocate_columns (clist, TRUE);
size_allocate_title_buttons (clist);
/* redraw the invalid columns */
if (clist->freeze_count == 0) {
GdkRectangle area;
area = clist->column_title_area;
area.x = clist->column[column].area.x;
area.height += clist->clist_window_height;
draw_rows (clist, &area);
}
/* draw the column title
* ToDo:
* fix this up
*/
title_redraw_area.x = GTK_WIDGET (flist->details->title)->allocation.x;
title_redraw_area.y = GTK_WIDGET (flist->details->title)->allocation.y;
title_redraw_area.width = GTK_WIDGET (flist->details->title)->allocation.width;
title_redraw_area.height = GTK_WIDGET (flist->details->title)->allocation.height;
(GTK_WIDGET_CLASS (NAUTILUS_KLASS (flist->details->title)))->
draw (flist->details->title, &title_redraw_area);
}
/* Our handler for motion_notify events. We override all of GtkCList's broken
* behavior.
*/
@ -476,6 +1210,45 @@ gtk_flist_motion (GtkWidget *widget, GdkEventMotion *event)
return TRUE;
}
void
gtk_flist_column_resize_track_start (GtkWidget *widget, int column)
{
GtkCList *clist;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
clist->drag_pos = column;
}
void
gtk_flist_column_resize_track (GtkWidget *widget, int column)
{
GtkCList *clist;
int x;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
clist = GTK_CLIST (widget);
gtk_widget_get_pointer (widget, &x, NULL);
gtk_flist_track_new_column_width (clist, column,
new_column_width (clist, column, &x));
}
void
gtk_flist_column_resize_track_end (GtkWidget *widget, int column)
{
GtkCList *clist;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
clist->drag_pos = -1;
}
/* Our handler for key_press and key_release events. We do nothing, and we do
* this to avoid GtkCList's broken behavior.
*/
@ -578,7 +1351,17 @@ gtk_flist_new_with_titles (int columns, char **titles)
GtkFList *flist;
flist = gtk_type_new (gtk_flist_get_type ());
gtk_clist_construct (GTK_CLIST (flist), columns, titles);
gtk_clist_construct (GTK_CLIST (flist), columns, NULL);
if (titles) {
GtkCList *clist;
int index;
clist = GTK_CLIST(flist);
for (index = 0; index < columns; index++) {
clist->column[index].title = g_strdup (titles[index]);
}
}
gtk_clist_set_selection_mode (GTK_CLIST (flist),
GTK_SELECTION_MULTIPLE);

View file

@ -36,7 +36,7 @@ struct _GtkFListClass {
GtkCListClass parent_class;
/* Signal: invoke the popup menu for selected items */
void (* context_click_selection) (GtkFList *flist, gint row);
void (* context_click_selection) (GtkFList *flist, int row);
/* Signal: invoke the popup menu for empty areas */
void (* context_click_background) (GtkFList *flist);
@ -45,10 +45,15 @@ struct _GtkFListClass {
void (* activate) (GtkFList *flist, gpointer data);
/* Signal: initiate a drag and drop operation */
void (* start_drag) (GtkFList *flist, gint button, GdkEvent *event);
void (* start_drag) (GtkFList *flist, int button, GdkEvent *event);
/* Signal: selection has changed */
void (* selection_changed) (GtkFList *flist);
/* column resize tracking calls */
void (* column_resize_track_start) (GtkWidget *widget, int column);
void (* column_resize_track) (GtkWidget *widget, int column);
void (* column_resize_track_end) (GtkWidget *widget, int column);
};

View file

@ -148,6 +148,47 @@ nautilus_fill_rectangle_with_gradient (GdkDrawable *drawable,
}
}
/**
* nautilus_rectangle_contains:
* @rectangle: Rectangle possibly containing a point.
* @x: X coordinate of a point.
* @y: Y coordinate of a point.
*
* Retun TRUE if point is contained inside a rectangle
*/
gboolean
nautilus_rectangle_contains (const GdkRectangle *rectangle,
int x,
int y)
{
g_return_val_if_fail (rectangle != NULL, FALSE);
return rectangle->x <= x && rectangle->x + rectangle->width >= x
&& rectangle->y <= y && rectangle->y + rectangle->height >= y;
}
/**
* nautilus_rectangle_inset:
* @rectangle: Rectangle we are insetting.
* @x: Horizontal ammount to inset by.
* @y: Vertical ammount to inset by.
*
* Inset a rectangle by a given horizontal and vertical ammount.
* Pass in negative inset values to grow the rectangle, positive to
* shrink it.
*/
void
nautilus_rectangle_inset (GdkRectangle *rectangle,
int x,
int y)
{
g_return_if_fail (rectangle != NULL);
rectangle->x += x;
rectangle->width -= 2 * x;
rectangle->y += y;
rectangle->height -= 2 * y;
}
/**
* nautilus_interpolate_color:
* @ratio: Place on line between colors to interpolate.

View file

@ -75,6 +75,15 @@ void nautilus_fill_rectangle_with_gradient (GdkDrawable *drawab
const GdkColor *end_color,
gboolean horizontal_gradient);
/* Misc GdkRectangle helper functions */
gboolean nautilus_rectangle_contains (const GdkRectangle *rectangle,
int x,
int y);
void nautilus_rectangle_inset (GdkRectangle *rectangle,
int x,
int y);
/* A basic operation we use for drawing gradients is interpolating two colors.*/
void nautilus_interpolate_color (gdouble ratio,
const GdkColor *start_color,

View file

@ -0,0 +1,763 @@
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
nautilus-list-column-title.c: List column title widget for interacting with list columns
Copyright (C) 2000 Eazel, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
Authors: Pavel Cisler <pavel@eazel.com>
*/
#include "nautilus-list-column-title.h"
#include "nautilus-gtk-macros.h"
#include "nautilus-gdk-extensions.h"
/* for now we need to know about GtkFList */
#include "gtkflist.h"
#include <gdk/gdk.h>
#include <gtk/gtkclist.h>
#include <gtk/gtkmain.h>
#include <libgnomeui/gnome-pixmap.h>
/* these are from GtkCList, for now we need to copy them here
* eventually the target list should be able to describe the values
*/
enum {
/* this defines the base grid spacing */
CELL_SPACING = 1,
/* added the horizontal space at the beginning and end of a row */
COLUMN_INSET = 3,
/* from GtkButton */
CHILD_SPACING = 1,
/* the width of the column resize windows */
DRAG_WIDTH = 6
};
static char * down_xpm[] = {
"6 5 2 1",
" c None",
". c #000000",
"......",
" ",
" .... ",
" ",
" .. "
};
static char * up_xpm[] = {
"6 5 2 1",
" c None",
". c #000000",
" .. ",
" ",
" .... ",
" ",
"......"
};
struct NautilusListColumnTitleDetails
{
/* gc for blitting sort order pixmaps, lazily allocated */
GdkGC *copy_area_gc;
/* sort order indicator pixmaps, lazily allocated */
GnomePixmap *up_indicator;
GnomePixmap *down_indicator;
int tracking_column_resize;
/* index of the column we are currently tracking or -1 */
int tracking_column_prelight;
/* index of the column we are currently rolling over or -1 */
int tracking_column_press;
/* index of the column we are currently pressing or -1 */
int last_tracking_x;
/* last horizontal track point so we can only resize when needed */
gboolean resize_cursor_on;
};
static void nautilus_list_column_title_initialize_class (gpointer klass);
static void nautilus_list_column_title_initialize (gpointer object, gpointer klass);
static void nautilus_list_column_title_paint (GtkWidget *widget, GdkRectangle *area);
static void nautilus_list_column_title_draw (GtkWidget *widget, GdkRectangle *box);
static void nautilus_list_column_title_buffered_draw (GtkWidget *widget);
static gboolean nautilus_list_column_title_expose (GtkWidget *widget, GdkEventExpose *event);
static void nautilus_list_column_title_realize (GtkWidget *widget);
static void nautilus_list_column_title_finalize (GtkObject *object);
static void nautilus_list_column_title_request (GtkWidget *widget, GtkRequisition *requisition);
static gboolean nautilus_list_column_title_motion (GtkWidget *widget, GdkEventMotion *event);
static gboolean nautilus_list_column_title_leave (GtkWidget *widget, GdkEventCrossing *event);
static gboolean nautilus_list_column_title_button_press (GtkWidget *widget, GdkEventButton *event);
static gboolean nautilus_list_column_title_button_release (GtkWidget *widget, GdkEventButton *event);
NAUTILUS_DEFINE_CLASS_BOILERPLATE (NautilusListColumnTitle, nautilus_list_column_title, GTK_TYPE_BIN)
/* generates nautilus_list_column_title_get_type */
static void
nautilus_list_column_title_initialize_class (gpointer klass)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
object_class = GTK_OBJECT_CLASS (klass);
widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = nautilus_list_column_title_finalize;
widget_class->draw = nautilus_list_column_title_draw;
widget_class->expose_event = nautilus_list_column_title_expose;
widget_class->realize = nautilus_list_column_title_realize;
widget_class->size_request = nautilus_list_column_title_request;
widget_class->motion_notify_event = nautilus_list_column_title_motion;
widget_class->leave_notify_event = nautilus_list_column_title_leave;
widget_class->button_press_event = nautilus_list_column_title_button_press;
widget_class->button_release_event = nautilus_list_column_title_button_release;
}
NautilusListColumnTitle *
nautilus_list_column_title_new (void)
{
return gtk_type_new (nautilus_list_column_title_get_type ());
}
static void
nautilus_list_column_title_initialize (gpointer object, gpointer klass)
{
NautilusListColumnTitle *column_title;
column_title = NAUTILUS_LIST_COLUMN_TITLE(object);
column_title->details = g_new0 (NautilusListColumnTitleDetails, 1);
/* copy_gc, up/down indicators get allocated lazily when needed */
column_title->details->copy_area_gc = NULL;
column_title->details->up_indicator = NULL;
column_title->details->down_indicator = NULL;
column_title->details->resize_cursor_on = FALSE;
column_title->details->tracking_column_resize = -1;
column_title->details->tracking_column_prelight = -1;
column_title->details->tracking_column_press = -1;
column_title->details->last_tracking_x = -1;
GTK_WIDGET_UNSET_FLAGS (object, GTK_NO_WINDOW);
}
static void
nautilus_list_column_title_realize (GtkWidget *widget)
{
GdkWindowAttr attributes;
int attributes_mask;
g_assert (widget != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
/* ask for expose events */
attributes.window_type = GDK_WINDOW_CHILD;
attributes.x = widget->allocation.x + GTK_CONTAINER (widget)->border_width;
attributes.y = widget->allocation.y + GTK_CONTAINER (widget)->border_width;
attributes.width = widget->allocation.width - GTK_CONTAINER (widget)->border_width * 2;
attributes.height = widget->allocation.height - GTK_CONTAINER (widget)->border_width * 2;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual (widget);
attributes.colormap = gtk_widget_get_colormap (widget);
attributes.event_mask = gtk_widget_get_events (widget);
attributes.event_mask |= GDK_EXPOSURE_MASK
| GDK_ENTER_NOTIFY_MASK
| GDK_LEAVE_NOTIFY_MASK
| GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
| GDK_POINTER_MOTION_MASK
| GDK_POINTER_MOTION_HINT_MASK
| GDK_KEY_PRESS_MASK;
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
/* give ourselves a background window */
widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
&attributes, attributes_mask);
gdk_window_set_user_data (widget->window, widget);
widget->style = gtk_style_attach (widget->style, widget->window);
gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
}
static void
nautilus_list_column_title_finalize (GtkObject *object)
{
NautilusListColumnTitle *column_title;
column_title = NAUTILUS_LIST_COLUMN_TITLE(object);
if (column_title->details->up_indicator != NULL)
gtk_widget_destroy (GTK_WIDGET (column_title->details->up_indicator));
if (column_title->details->down_indicator != NULL)
gtk_widget_destroy (GTK_WIDGET (column_title->details->down_indicator));
/* FIXME:
* figure out if we need to delete the copy_area_gc here
*/
g_free (column_title->details);
NAUTILUS_CALL_PARENT_CLASS (GTK_OBJECT_CLASS, finalize, (object));
}
static void
nautilus_list_column_title_request (GtkWidget *widget, GtkRequisition *requisition)
{
/* size requisition: make sure we have at least a minimal height */
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
g_assert (requisition != NULL);
requisition->width = (GTK_CONTAINER (widget)->border_width + CHILD_SPACING +
widget->style->klass->xthickness) * 2;
requisition->height = (GTK_CONTAINER (widget)->border_width + CHILD_SPACING +
widget->style->klass->ythickness) * 2;
if (GTK_BIN (widget)->child && GTK_WIDGET_VISIBLE (GTK_BIN (widget)->child)) {
GtkRequisition child_requisition;
gtk_widget_size_request (GTK_BIN (widget)->child, &child_requisition);
requisition->width += child_requisition.width;
requisition->height += child_requisition.height;
requisition->height = MIN (requisition->height, 10);
}
}
static const char *
get_column_label_at (GtkWidget *column_title, int index)
{
GtkCList *parent_clist;
parent_clist = GTK_CLIST (column_title->parent);
return parent_clist->column[index].title;
}
static void
get_column_frame_at(GtkWidget *column_title, int index, GdkRectangle *result)
{
GtkCList *parent_clist;
parent_clist = GTK_CLIST (column_title->parent);
*result = parent_clist->column_title_area;
result->x = parent_clist->hoffset + parent_clist->column[index].area.x - COLUMN_INSET;
result->y = 0;
result->width = parent_clist->column[index].area.width
+ CELL_SPACING + 2 * COLUMN_INSET - 1;
}
static GnomePixmap *
get_sort_indicator (GtkWidget *widget, gboolean ascending)
{
/* return the sort order pixmap for a given sort direction
* allocate the pixmap first time around
*/
NautilusListColumnTitle *column_title;
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
if (ascending) {
if (column_title->details->up_indicator == NULL) {
column_title->details->up_indicator =
GNOME_PIXMAP (gnome_pixmap_new_from_xpm_d (up_xpm));
}
return column_title->details->up_indicator;
} else {
if (column_title->details->down_indicator == NULL) {
column_title->details->down_indicator =
GNOME_PIXMAP (gnome_pixmap_new_from_xpm_d (down_xpm));
}
return column_title->details->down_indicator;
}
}
/* FIXME:
* Some of these magic numbers could be replaced with some more dynamic values
*/
enum {
CELL_TITLE_INSET = 6,
TITLE_BASELINE_OFFSET = 6,
SORT_ORDER_INDICATOR_WIDTH = 10
};
static void
nautilus_list_column_title_paint (GtkWidget *widget, GdkRectangle *area)
{
NautilusListColumnTitle *column_title;
GtkCList *parent_clist;
int index;
g_assert (GTK_CLIST (widget->parent) != NULL);
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
parent_clist = GTK_CLIST (widget->parent);
for (index = 0; index < parent_clist->columns; index++) {
GdkRectangle cell_rectangle;
GdkRectangle cell_redraw_area;
const char *cell_label;
int x_offset;
GnomePixmap *sort_indicator;
gboolean right_justified;
sort_indicator = NULL;
right_justified = (parent_clist->column[index].justification == GTK_JUSTIFY_RIGHT);
/* pick the ascending/descending sort indicator if needed */
if (index == parent_clist->sort_column) {
sort_indicator = get_sort_indicator (widget,
parent_clist->sort_type == GTK_SORT_ASCENDING);
}
get_column_frame_at (widget, index, &cell_rectangle);
gdk_rectangle_intersect (&cell_rectangle, area, &cell_redraw_area);
if (cell_redraw_area.width == 0 || cell_redraw_area.height == 0) {
/* no work, go on to the next */
continue;
}
cell_label = get_column_label_at (widget, index);
/* FIXME:
* add support for center justification
*/
if (right_justified) {
x_offset = cell_rectangle.x + cell_rectangle.width - CELL_TITLE_INSET;
} else {
x_offset = cell_rectangle.x + CELL_TITLE_INSET;
}
/* Paint the column title tiles as rectangles using "menu" style
* buttons as used by GtkCList produce round corners in some themes.
* Eventually we might consider having a separate style for column titles
*/
gtk_paint_box (widget->style, widget->window,
column_title->details->tracking_column_prelight == index ?
GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
column_title->details->tracking_column_press == index ?
GTK_SHADOW_IN : GTK_SHADOW_OUT,
area, widget, "menu",
cell_rectangle.x, cell_rectangle.y,
cell_rectangle.width, cell_rectangle.height);
/* Draw the sort indicator if needed */
if (sort_indicator != NULL) {
int y_offset = TITLE_BASELINE_OFFSET + 2;
if (right_justified) {
x_offset -= SORT_ORDER_INDICATOR_WIDTH;
}
/* allocate the sort indicator copy gc first time around */
if (column_title->details->copy_area_gc == NULL) {
column_title->details->copy_area_gc = gdk_gc_new (widget->window);
gdk_gc_set_function (column_title->details->copy_area_gc, GDK_COPY);
}
/* move the pixmap clip mask and origin to the right spot in the gc */
gdk_gc_set_clip_mask (column_title->details->copy_area_gc,
sort_indicator->mask);
gdk_gc_set_clip_origin (column_title->details->copy_area_gc, x_offset, y_offset);
gdk_draw_pixmap (widget->window, column_title->details->copy_area_gc,
sort_indicator->pixmap, 0, 0, x_offset, y_offset,
-1, -1);
if (!right_justified) {
x_offset += SORT_ORDER_INDICATOR_WIDTH;
}
}
if (cell_label) {
/* clip a little more than the cell rectangle to
* not have the text draw over the cell broder
* (this might no longer be needed when the
* title gets truncated properly, as opposed to
* getting chopped of
*/
nautilus_rectangle_inset (&cell_redraw_area, 2, 2);
if (right_justified) {
x_offset -= gdk_string_width (widget->style->font, cell_label) + 4;
}
gtk_paint_string (widget->style, widget->window, GTK_STATE_NORMAL,
&cell_redraw_area, widget, "label",
x_offset,
cell_rectangle.y + cell_rectangle.height - TITLE_BASELINE_OFFSET,
cell_label);
}
}
}
static void
nautilus_list_column_title_draw (GtkWidget *widget, GdkRectangle *area)
{
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
g_assert (area != NULL);
if (!GTK_WIDGET_DRAWABLE (widget)) {
return;
}
nautilus_list_column_title_paint (widget, area);
}
static void
nautilus_list_column_title_buffered_draw (GtkWidget *widget)
{
/* draw using an offscreen bitmap
* for now just draw directly
* add code here to lazily allocate an offscreen and pass it to the paint call
* without the offscreen you can now see a funny artifact when part of the
* old column border does not get erased properly when shrinking a column
*/
GdkRectangle redraw_area;
redraw_area.x = widget->allocation.x;
redraw_area.y = widget->allocation.y;
redraw_area.width = widget->allocation.width;
redraw_area.height = widget->allocation.height;
nautilus_list_column_title_draw (widget, &redraw_area);
}
static gboolean
nautilus_list_column_title_expose (GtkWidget *widget, GdkEventExpose *event)
{
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
g_assert (event != NULL);
if (!GTK_WIDGET_DRAWABLE (widget)) {
return FALSE;
}
nautilus_list_column_title_paint (widget, &event->area);
return FALSE;
}
static int
in_column_rect (GtkWidget *widget, int x, int y)
{
/* return the index of the column we hit or -1 */
GtkCList *parent_clist;
int index;
parent_clist = GTK_CLIST (widget->parent);
for (index = 0; index < parent_clist->columns; index++) {
/* hit testing for column resizing */
GdkRectangle cell_rectangle;
get_column_frame_at (widget, index, &cell_rectangle);
/* inset by a pixel so that you have to move past the border
* to be considered inside the rect
* nautilus_list_column_title_leave depends on this
*/
nautilus_rectangle_inset (&cell_rectangle, 1, 0);
if (nautilus_rectangle_contains (&cell_rectangle, x, y))
return index;
}
return -1;
}
static int
in_resize_rect (GtkWidget *widget, int x, int y)
{
/* return the index of the resize rect of a column we hit or -1 */
GtkCList *parent_clist;
int index;
parent_clist = GTK_CLIST (widget->parent);
for (index = 0; index < parent_clist->columns; index++) {
/* hit testing for column resizing */
GdkRectangle resize_rectangle;
get_column_frame_at (widget, index, &resize_rectangle);
nautilus_rectangle_inset (&resize_rectangle, 1, 0);
resize_rectangle.x = resize_rectangle.x + resize_rectangle.width - DRAG_WIDTH / 2;
resize_rectangle.width = DRAG_WIDTH;
if (nautilus_rectangle_contains (&resize_rectangle, x, y))
return index;
}
return -1;
}
static void
show_hide_resize_cursor_if_needed (GtkWidget *widget, gboolean on)
{
NautilusListColumnTitle *column_title;
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
if (on == column_title->details->resize_cursor_on)
/* already set right */
return;
if (on) {
/* switch to a resize cursor */
GdkCursor *cursor;
cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
gdk_window_set_cursor (widget->window, cursor);
gdk_cursor_destroy (cursor);
} else
/* restore to old cursor */
gdk_window_set_cursor (widget->window, NULL);
column_title->details->resize_cursor_on = on;
}
static gboolean
track_prelight (GtkWidget *widget, int mouse_x, int mouse_y)
{
NautilusListColumnTitle *column_title;
int over_column;
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
/* see if we need to update the prelight state of a column */
over_column = in_column_rect (widget, mouse_x, mouse_y);
if (column_title->details->tracking_column_resize != -1) {
/* resizing a column, don't prelight */
over_column = -1;
}
if (column_title->details->tracking_column_press != -1) {
/* pressing a column, don't prelight */
over_column = -1;
}
if (column_title->details->tracking_column_prelight == over_column) {
/* no change */
return FALSE;
}
/* update state and tell callers to redraw */
column_title->details->tracking_column_prelight = over_column;
return TRUE;
}
static gboolean
nautilus_list_column_title_motion (GtkWidget *widget, GdkEventMotion *event)
{
NautilusListColumnTitle *column_title;
GtkWidget *parent_list;
int mouse_x, mouse_y;
g_assert (GTK_FLIST (widget->parent) != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
parent_list = GTK_WIDGET (widget->parent);
gdk_window_get_pointer (widget->window, &mouse_x, &mouse_y, NULL);
if (column_title->details->tracking_column_resize != -1) {
/* we are currently tracking a column */
if (column_title->details->last_tracking_x != mouse_x) {
/* mouse did move horizontally since last time */
column_title->details->last_tracking_x = mouse_x;
(GTK_FLIST_CLASS (NAUTILUS_KLASS (parent_list)))->
column_resize_track (parent_list,
column_title->details->tracking_column_resize);
}
} else {
/* make sure we are showing the right cursor */
show_hide_resize_cursor_if_needed (widget,
in_resize_rect (widget, mouse_x, mouse_y) != -1);
}
/* see if we need to update the prelight state of a column */
if (track_prelight (widget, mouse_x, mouse_y)) {
nautilus_list_column_title_buffered_draw (widget);
}
return TRUE;
}
static gboolean
nautilus_list_column_title_leave (GtkWidget *widget, GdkEventCrossing *event)
{
g_assert (GTK_FLIST (widget->parent) != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
/* see if we need to update the prelight state of a column */
if (track_prelight (widget, (int)event->x, (int)event->y)) {
gtk_widget_set_state (widget, GTK_STATE_NORMAL);
nautilus_list_column_title_buffered_draw (widget);
}
return TRUE;
}
static gboolean
nautilus_list_column_title_button_press (GtkWidget *widget, GdkEventButton *event)
{
NautilusListColumnTitle *column_title;
GtkWidget *parent_list;
g_assert (event != NULL);
g_assert (GTK_FLIST (widget->parent) != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
g_assert (NAUTILUS_LIST_COLUMN_TITLE(widget)->details->tracking_column_resize == -1);
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
parent_list = GTK_WIDGET (widget->parent);
if (event->type == GDK_BUTTON_PRESS) {
int resized_column;
int clicked_column;
resized_column = in_resize_rect (widget, (int)event->x, (int)event->y);
clicked_column = in_column_rect (widget, (int)event->x, (int)event->y);
if (resized_column != -1) {
GdkCursor *cursor;
int grab_result;
/* during the drag, use the resize cursor */
cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
/* grab the pointer events so that we get them even when
* the mouse tracks out of the widget window
*/
grab_result = gdk_pointer_grab (widget->window, FALSE,
GDK_POINTER_MOTION_HINT_MASK
| GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK,
NULL, cursor, event->time);
gdk_cursor_destroy (cursor);
if (grab_result != 0)
/* failed to grab the pointer, give up
*
* The grab results are not very well documented
* looks like they may be Success, GrabSuccess, AlreadyGrabbed
* or anything else the low level X calls in gdk_pointer_grab
* decide to return.
*/
return FALSE;
/* set up new state */
column_title->details->tracking_column_resize = resized_column;
column_title->details->tracking_column_prelight = -1;
/* FIXME:
* use a "resized" state here ?
*/
gtk_widget_set_state (widget, GTK_STATE_NORMAL);
/* start column resize tracking */
(GTK_FLIST_CLASS (NAUTILUS_KLASS (parent_list)))->
column_resize_track_start (parent_list, resized_column);
return FALSE;
}
if (clicked_column != -1) {
/* clicked a column, draw the pressed column title */
column_title->details->tracking_column_prelight = -1;
column_title->details->tracking_column_press = clicked_column;
gtk_widget_set_state (widget, GTK_STATE_ACTIVE);
/* FIXME:
* buffered draw may be better here
*/
gtk_widget_queue_draw (widget);
}
}
return FALSE;
}
static gboolean
nautilus_list_column_title_button_release (GtkWidget *widget, GdkEventButton *event)
{
NautilusListColumnTitle *column_title;
GtkWidget *parent_list;
g_assert (event != NULL);
g_assert (GTK_FLIST (widget->parent) != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
parent_list = GTK_WIDGET (widget->parent);
if (column_title->details->tracking_column_resize != -1) {
/* let go of all the pointer events */
if (gdk_pointer_is_grabbed ())
gdk_pointer_ungrab (event->time);
/* end column resize tracking */
(GTK_FLIST_CLASS (NAUTILUS_KLASS (parent_list)))->
column_resize_track_end (parent_list,
column_title->details->tracking_column_resize);
column_title->details->tracking_column_resize = -1;
} else if (column_title->details->tracking_column_press != -1) {
/* column title press -- change the sort order */
gtk_signal_emit_by_name (GTK_OBJECT (parent_list), "click_column",
column_title->details->tracking_column_press);
/* end press tracking */
column_title->details->tracking_column_press = -1;
}
track_prelight (widget, (int)event->x, (int)event->y);
gtk_widget_set_state (widget,
column_title->details->tracking_column_prelight != -1 ?
GTK_STATE_PRELIGHT : GTK_STATE_NORMAL);
/* FIXME:
* buffered draw may be better here
*/
gtk_widget_queue_draw (widget);
return FALSE;
}

View file

@ -0,0 +1,73 @@
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
nautilus-list-column-title.h: List column title widget for interacting with list columns
Copyright (C) 2000 Eazel, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
Authors: Pavel Cisler <pavel@eazel.com>
*/
#ifndef __NAUTILUS_LIST_COLUMN_TITLE__
#define __NAUTILUS_LIST_COLUMN_TITLE__
#include <gdk/gdktypes.h>
#include <gtk/gtkwidget.h>
#include <gtk/gtkbin.h>
#include <gtk/gtkenums.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define NAUTILUS_TYPE_LIST_COLUMN_TITLE \
(nautilus_list_column_title_get_type ())
#define NAUTILUS_LIST_COLUMN_TITLE(obj) \
(GTK_CHECK_CAST ((obj), NAUTILUS_TYPE_LIST_COLUMN_TITLE, NautilusListColumnTitle))
#define NAUTILUS_LIST_COLUMN_TITLE_CLASS(klass) \
(GTK_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_LIST_COLUMN_TITLE, NautilusListColumnTitleClass))
#define NAUTILUS_IS_LIST_COLUMN_TITLE(obj) \
(GTK_CHECK_TYPE ((obj), NAUTILUS_TYPE_LIST_COLUMN_TITLE))
#define NAUTILUS_IS_LIST_COLUMN_TITLE_CLASS(klass) \
(GTK_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_LIST_COLUMN_TITLE))
typedef struct NautilusListColumnTitle NautilusListColumnTitle;
typedef struct NautilusListColumnTitleClass NautilusListColumnTitleClass;
typedef struct NautilusListColumnTitleDetails NautilusListColumnTitleDetails;
struct NautilusListColumnTitle
{
GtkBin bin;
NautilusListColumnTitleDetails *details;
};
struct NautilusListColumnTitleClass
{
GtkBinClass parent_class;
};
GtkType nautilus_list_column_title_get_type (void);
NautilusListColumnTitle *nautilus_list_column_title_new (void);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __NAUTILUS_LIST_COLUMN_TITLE__ */

View file

@ -47,6 +47,7 @@ libnautilusinclude_HEADERS= \
nautilus-gtk-extensions.h \
nautilus-icon-factory.h \
nautilus-icons-view-icon-item.h \
nautilus-list-column-title.h \
nautilus-metadata.h \
nautilus-mime-type.h \
nautilus-self-checks.h \
@ -85,6 +86,7 @@ libnautilus_la_SOURCES=$(nautilus_idl_sources) \
nautilus-icon-factory.c \
nautilus-icons-view-icon-item.c \
nautilus-lib-self-check-functions.c \
nautilus-list-column-title.c \
nautilus-mime-type.c \
nautilus-self-checks.c \
nautilus-string-list.c \

View file

@ -19,6 +19,7 @@
#include "nautilus-glib-extensions.h"
#include "nautilus-gtk-macros.h"
#include "nautilus-background.h"
#include "nautilus-list-column-title.h"
struct _GtkFListDetails
{
@ -37,7 +38,8 @@ struct _GtkFListDetails
/* Delayed selection information */
int dnd_select_pending;
guint dnd_select_pending_state;
GtkWidget *title;
};
/* maximum amount of milliseconds the mouse button is allowed to stay down and still be considered a click */
@ -81,8 +83,18 @@ static gboolean gtk_flist_drag_drop (GtkWidget *widget, GdkDragContext *context,
static void gtk_flist_drag_data_received (GtkWidget *widget, GdkDragContext *context,
gint x, gint y, GtkSelectionData *data,
guint info, guint time);
static void select_or_unselect_row_cb (GtkCList *clist, gint row, gint column,
GdkEvent *event);
static void gtk_flist_clear (GtkCList *clist);
static void draw_row (GtkCList *flist, GdkRectangle *area, gint row, GtkCListRow *clist_row);
static void gtk_flist_realize (GtkWidget *widget);
static void gtk_flist_size_request (GtkWidget *widget, GtkRequisition *requisition);
static void gtk_flist_column_resize_track_start (GtkWidget *widget, int column);
static void gtk_flist_column_resize_track (GtkWidget *widget, int column);
static void gtk_flist_column_resize_track_end (GtkWidget *widget, int column);
NAUTILUS_DEFINE_CLASS_BOILERPLATE (GtkFList, gtk_flist, GTK_TYPE_CLIST)
@ -90,15 +102,17 @@ static guint flist_signals[LAST_SIGNAL];
/* Standard class initialization function */
static void
gtk_flist_initialize_class (GtkFListClass *class)
gtk_flist_initialize_class (GtkFListClass *klass)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
GtkCListClass *clist_class;
GtkFListClass *flist_class;
object_class = (GtkObjectClass *) class;
widget_class = (GtkWidgetClass *) class;
clist_class = (GtkCListClass *) class;
object_class = (GtkObjectClass *) klass;
widget_class = (GtkWidgetClass *) klass;
clist_class = (GtkCListClass *) klass;
flist_class = (GtkFListClass *) klass;
flist_signals[CONTEXT_CLICK_SELECTION] =
gtk_signal_new ("context_click_selection",
@ -135,13 +149,18 @@ gtk_flist_initialize_class (GtkFListClass *class)
gtk_signal_new ("selection_changed",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (GtkFListClass, start_drag),
GTK_SIGNAL_OFFSET (GtkFListClass, selection_changed),
gtk_marshal_NONE__NONE,
GTK_TYPE_NONE, 0);
gtk_object_class_add_signals (object_class, flist_signals, LAST_SIGNAL);
flist_class->column_resize_track_start = gtk_flist_column_resize_track_start;
flist_class->column_resize_track = gtk_flist_column_resize_track;
flist_class->column_resize_track_end = gtk_flist_column_resize_track_end;
clist_class->clear = gtk_flist_clear;
clist_class->draw_row = draw_row;
widget_class->button_press_event = gtk_flist_button_press;
widget_class->button_release_event = gtk_flist_button_release;
@ -155,20 +174,14 @@ gtk_flist_initialize_class (GtkFListClass *class)
widget_class->drag_motion = gtk_flist_drag_motion;
widget_class->drag_drop = gtk_flist_drag_drop;
widget_class->drag_data_received = gtk_flist_drag_data_received;
}
static void
select_or_unselect_row_cb (GtkCList *clist, gint row, gint column, GdkEvent *event)
{
/* This is the one bottleneck for all selection changes */
gtk_signal_emit (GTK_OBJECT (clist), flist_signals[SELECTION_CHANGED]);
widget_class->realize = gtk_flist_realize;
widget_class->size_request = gtk_flist_size_request;
}
/* Standard object initialization function */
static void
gtk_flist_initialize (GtkFList *flist)
{
{
flist->details = g_new0 (GtkFListDetails, 1);
flist->details->anchor_row = -1;
@ -194,6 +207,16 @@ gtk_flist_initialize (GtkFList *flist)
"unselect_row",
select_or_unselect_row_cb,
flist);
flist->details->title = GTK_WIDGET (nautilus_list_column_title_new());
}
static void
select_or_unselect_row_cb (GtkCList *clist, gint row, gint column, GdkEvent *event)
{
/* This is the one bottleneck for all selection changes */
gtk_signal_emit (GTK_OBJECT (clist), flist_signals[SELECTION_CHANGED]);
}
static void
@ -429,6 +452,717 @@ gtk_flist_button_release (GtkWidget *widget, GdkEventButton *event)
return retval;
}
/* stolen from gtkclist.c for now */
/* minimum allowed width of a column */
#define COLUMN_MIN_WIDTH 5
/* this defines the base grid spacing */
#define CELL_SPACING 1
/* added the horizontal space at the beginning and end of a row */
#define COLUMN_INSET 3
/* the width of the column resize windows */
#define DRAG_WIDTH 6
/* gives the left pixel of the given column in context of
* the clist's hoffset */
#define COLUMN_LEFT_XPIXEL(clist, colnum) ((clist)->column[(colnum)].area.x + \
(clist)->hoffset)
/* gives the top pixel of the given row in context of
* the clist's voffset */
#define ROW_TOP_YPIXEL(clist, row) (((clist)->row_height * (row)) + \
(((row) + 1) * CELL_SPACING) + \
(clist)->voffset)
/* returns the row index from a y pixel location in the
* context of the clist's voffset */
#define ROW_FROM_YPIXEL(clist, y) (((y) - (clist)->voffset) / \
((clist)->row_height + CELL_SPACING))
/* returns the GList item for the nth row */
#define ROW_ELEMENT(clist, row) (((row) == (clist)->rows - 1) ? \
(clist)->row_list_end : \
g_list_nth ((clist)->row_list, (row)))
/* returns the total height of the list */
#define LIST_HEIGHT(clist) (((clist)->row_height * ((clist)->rows)) + \
(CELL_SPACING * ((clist)->rows + 1)))
static void
gtk_flist_realize (GtkWidget *widget)
{
GtkFList *flist;
GtkCList *clist;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
flist = GTK_FLIST (widget);
clist = GTK_CLIST (widget);
clist->column[0].button = flist->details->title;
NAUTILUS_CALL_PARENT_CLASS (GTK_WIDGET_CLASS, realize, (widget));
if (clist->title_window) {
gtk_widget_set_parent_window (flist->details->title, clist->title_window);
}
gtk_widget_set_parent (flist->details->title, GTK_WIDGET (clist));
gtk_widget_show (flist->details->title);
GTK_CLIST_SET_FLAG (clist, CLIST_SHOW_TITLES);
}
/* this is here just temporarily */
static gint
list_requisition_width (GtkCList *clist)
{
gint width = CELL_SPACING;
gint i;
for (i = clist->columns - 1; i >= 0; i--) {
if (!clist->column[i].visible)
continue;
if (clist->column[i].width_set)
width += clist->column[i].width + CELL_SPACING + (2 * COLUMN_INSET);
else if (GTK_CLIST_SHOW_TITLES(clist) && clist->column[i].button)
width += clist->column[i].button->requisition.width;
}
return width;
}
static void
gtk_flist_size_request (GtkWidget *widget, GtkRequisition *requisition)
{
/* stolen from gtk_clist
* make sure the proper title ammount is allocated for the column
* title view -- this would not otherwise be done because
* GtkFList depends the buttons being there when doing a size calculation
*/
GtkFList *flist;
GtkCList *clist;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
g_return_if_fail (requisition != NULL);
clist = GTK_CLIST (widget);
flist = GTK_FLIST (widget);
requisition->width = 0;
requisition->height = 0;
/* compute the size of the column title (title) area */
clist->column_title_area.height = 0;
if (GTK_CLIST_SHOW_TITLES(clist) && flist->details->title) {
GtkRequisition child_requisition;
gtk_widget_size_request (flist->details->title,
&child_requisition);
child_requisition.height = 20;
/* for now */
clist->column_title_area.height =
MAX (clist->column_title_area.height,
child_requisition.height);
}
requisition->width += (widget->style->klass->xthickness +
GTK_CONTAINER (widget)->border_width) * 2;
requisition->height += (clist->column_title_area.height +
(widget->style->klass->ythickness +
GTK_CONTAINER (widget)->border_width) * 2);
requisition->width += list_requisition_width (clist);
requisition->height += LIST_HEIGHT (clist);
}
static gint
new_column_width (GtkCList *clist, gint column, gint *x)
{
gint xthickness = GTK_WIDGET (clist)->style->klass->xthickness;
gint width;
gint cx;
gint dx;
gint last_column;
/* first translate the x position from widget->window
* to clist->clist_window */
cx = *x - xthickness;
for (last_column = clist->columns - 1;
last_column >= 0 && !clist->column[last_column].visible; last_column--);
/* calculate new column width making sure it doesn't end up
* less than the minimum width */
dx = (COLUMN_LEFT_XPIXEL (clist, column) + COLUMN_INSET +
(column < last_column) * CELL_SPACING);
width = cx - dx;
if (width < MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width)) {
width = MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width);
cx = dx + width;
*x = cx + xthickness;
} else if (clist->column[column].max_width >= COLUMN_MIN_WIDTH &&
width > clist->column[column].max_width) {
width = clist->column[column].max_width;
cx = dx + clist->column[column].max_width;
*x = cx + xthickness;
}
if (cx < 0 || cx > clist->clist_window_width)
*x = -1;
return width;
}
static void
size_allocate_columns (GtkCList *clist, gboolean block_resize)
{
int xoffset = CELL_SPACING + COLUMN_INSET;
int last_column;
int i;
/* find last visible column and calculate correct column width */
for (last_column = clist->columns - 1;
last_column >= 0 && !clist->column[last_column].visible; last_column--)
;
if (last_column < 0)
return;
for (i = 0; i <= last_column; i++) {
if (!clist->column[i].visible)
continue;
clist->column[i].area.x = xoffset;
if (clist->column[i].width_set) {
if (!block_resize && GTK_CLIST_SHOW_TITLES(clist) &&
clist->column[i].auto_resize && clist->column[i].button) {
gint width;
width = (clist->column[i].button->requisition.width -
(CELL_SPACING + (2 * COLUMN_INSET)));
if (width > clist->column[i].width)
gtk_clist_set_column_width (clist, i, width);
}
clist->column[i].area.width = clist->column[i].width;
xoffset += clist->column[i].width + CELL_SPACING + (2 * COLUMN_INSET);
} else if (GTK_CLIST_SHOW_TITLES(clist) && clist->column[i].button) {
clist->column[i].area.width =
clist->column[i].button->requisition.width -
(CELL_SPACING + (2 * COLUMN_INSET));
xoffset += clist->column[i].button->requisition.width;
}
}
clist->column[last_column].area.width += MAX (0, clist->clist_window_width + COLUMN_INSET - xoffset);
}
static void
size_allocate_title_buttons (GtkCList *clist)
{
GtkAllocation button_allocation;
int last_column;
int last_button = 0;
int i;
button_allocation.x = clist->hoffset;
button_allocation.y = 0;
button_allocation.width = 0;
button_allocation.height = clist->column_title_area.height;
/* find last visible column */
for (last_column = clist->columns - 1; last_column >= 0; last_column--)
if (clist->column[last_column].visible)
break;
for (i = 0; i < last_column; i++) {
if (!clist->column[i].visible) {
last_button = i + 1;
gdk_window_hide (clist->column[i].window);
continue;
}
button_allocation.width += (clist->column[i].area.width +
CELL_SPACING + 2 * COLUMN_INSET);
if (!clist->column[i + 1].button) {
gdk_window_hide (clist->column[i].window);
continue;
}
gtk_widget_size_allocate (clist->column[last_button].button,
&button_allocation);
button_allocation.x += button_allocation.width;
button_allocation.width = 0;
last_button = i + 1;
}
button_allocation.width += (clist->column[last_column].area.width +
2 * (CELL_SPACING + COLUMN_INSET));
gtk_widget_size_allocate (clist->column[last_button].button,
&button_allocation);
}
static void
get_cell_style (GtkCList *clist, GtkCListRow *clist_row,
gint state, gint column, GtkStyle **style,
GdkGC **fg_gc, GdkGC **bg_gc)
{
gint fg_state;
if ((state == GTK_STATE_NORMAL) &&
(GTK_WIDGET (clist)->state == GTK_STATE_INSENSITIVE))
fg_state = GTK_STATE_INSENSITIVE;
else
fg_state = state;
if (clist_row->cell[column].style) {
if (style)
*style = clist_row->cell[column].style;
if (fg_gc)
*fg_gc = clist_row->cell[column].style->fg_gc[fg_state];
if (bg_gc) {
if (state == GTK_STATE_SELECTED)
*bg_gc = clist_row->cell[column].style->bg_gc[state];
else
*bg_gc = clist_row->cell[column].style->base_gc[state];
}
} else if (clist_row->style) {
if (style)
*style = clist_row->style;
if (fg_gc)
*fg_gc = clist_row->style->fg_gc[fg_state];
if (bg_gc) {
if (state == GTK_STATE_SELECTED)
*bg_gc = clist_row->style->bg_gc[state];
else
*bg_gc = clist_row->style->base_gc[state];
}
} else {
if (style)
*style = GTK_WIDGET (clist)->style;
if (fg_gc)
*fg_gc = GTK_WIDGET (clist)->style->fg_gc[fg_state];
if (bg_gc) {
if (state == GTK_STATE_SELECTED)
*bg_gc = GTK_WIDGET (clist)->style->bg_gc[state];
else
*bg_gc = GTK_WIDGET (clist)->style->base_gc[state];
}
if (state != GTK_STATE_SELECTED) {
if (fg_gc && clist_row->fg_set)
*fg_gc = clist->fg_gc;
if (bg_gc && clist_row->bg_set)
*bg_gc = clist->bg_gc;
}
}
}
static gint
draw_cell_pixmap (GdkWindow *window, GdkRectangle *clip_rectangle, GdkGC *fg_gc,
GdkPixmap *pixmap, GdkBitmap *mask,
gint x, gint y, gint width, gint height)
{
gint xsrc = 0;
gint ysrc = 0;
if (mask) {
gdk_gc_set_clip_mask (fg_gc, mask);
gdk_gc_set_clip_origin (fg_gc, x, y);
}
if (x < clip_rectangle->x) {
xsrc = clip_rectangle->x - x;
width -= xsrc;
x = clip_rectangle->x;
}
if (x + width > clip_rectangle->x + clip_rectangle->width)
width = clip_rectangle->x + clip_rectangle->width - x;
if (y < clip_rectangle->y) {
ysrc = clip_rectangle->y - y;
height -= ysrc;
y = clip_rectangle->y;
}
if (y + height > clip_rectangle->y + clip_rectangle->height)
height = clip_rectangle->y + clip_rectangle->height - y;
gdk_draw_pixmap (window, fg_gc, pixmap, xsrc, ysrc, x, y, width, height);
gdk_gc_set_clip_origin (fg_gc, 0, 0);
if (mask)
gdk_gc_set_clip_mask (fg_gc, NULL);
return x + MAX (width, 0);
}
static void
draw_row (GtkCList *clist,
GdkRectangle *area,
gint row,
GtkCListRow *clist_row)
{
GtkWidget *widget;
GdkRectangle *rect;
GdkRectangle row_rectangle;
GdkRectangle cell_rectangle;
GdkRectangle clip_rectangle;
GdkRectangle intersect_rectangle;
gint last_column;
gint state;
gint i;
g_return_if_fail (clist != NULL);
/* bail now if we arn't drawable yet */
if (!GTK_WIDGET_DRAWABLE (clist) || row < 0 || row >= clist->rows)
return;
widget = GTK_WIDGET (clist);
/* if the function is passed the pointer to the row instead of null,
* it avoids this expensive lookup */
if (!clist_row)
clist_row = ROW_ELEMENT (clist, row)->data;
/* rectangle of the entire row */
row_rectangle.x = 0;
row_rectangle.y = ROW_TOP_YPIXEL (clist, row);
row_rectangle.width = clist->clist_window_width;
row_rectangle.height = clist->row_height;
/* rectangle of the cell spacing above the row */
cell_rectangle.x = 0;
cell_rectangle.y = row_rectangle.y - CELL_SPACING;
cell_rectangle.width = row_rectangle.width;
cell_rectangle.height = CELL_SPACING;
/* rectangle used to clip drawing operations, its y and height
* positions only need to be set once, so we set them once here.
* the x and width are set withing the drawing loop below once per
* column */
clip_rectangle.y = row_rectangle.y;
clip_rectangle.height = row_rectangle.height;
if (clist_row->state == GTK_STATE_NORMAL)
{
if (clist_row->fg_set)
gdk_gc_set_foreground (clist->fg_gc, &clist_row->foreground);
if (clist_row->bg_set)
gdk_gc_set_foreground (clist->bg_gc, &clist_row->background);
}
state = clist_row->state;
/* draw the cell borders and background */
if (area)
{
rect = &intersect_rectangle;
if (gdk_rectangle_intersect (area, &cell_rectangle,
&intersect_rectangle))
gdk_draw_rectangle (clist->clist_window,
widget->style->base_gc[GTK_STATE_ACTIVE],
TRUE,
intersect_rectangle.x,
intersect_rectangle.y,
intersect_rectangle.width,
intersect_rectangle.height);
/* the last row has to clear its bottom cell spacing too */
if (clist_row == clist->row_list_end->data)
{
cell_rectangle.y += clist->row_height + CELL_SPACING;
if (gdk_rectangle_intersect (area, &cell_rectangle,
&intersect_rectangle))
gdk_draw_rectangle (clist->clist_window,
widget->style->base_gc[GTK_STATE_ACTIVE],
TRUE,
intersect_rectangle.x,
intersect_rectangle.y,
intersect_rectangle.width,
intersect_rectangle.height);
}
if (!gdk_rectangle_intersect (area, &row_rectangle,&intersect_rectangle))
return;
}
else
{
rect = &clip_rectangle;
gdk_draw_rectangle (clist->clist_window,
widget->style->base_gc[GTK_STATE_ACTIVE],
TRUE,
cell_rectangle.x,
cell_rectangle.y,
cell_rectangle.width,
cell_rectangle.height);
/* the last row has to clear its bottom cell spacing too */
if (clist_row == clist->row_list_end->data)
{
cell_rectangle.y += clist->row_height + CELL_SPACING;
gdk_draw_rectangle (clist->clist_window,
widget->style->base_gc[GTK_STATE_ACTIVE],
TRUE,
cell_rectangle.x,
cell_rectangle.y,
cell_rectangle.width,
cell_rectangle.height);
}
}
for (last_column = clist->columns - 1;
last_column >= 0 && !clist->column[last_column].visible; last_column--)
;
/* iterate and draw all the columns (row cells) and draw their contents */
for (i = 0; i < clist->columns; i++)
{
GtkStyle *style;
GdkGC *fg_gc;
GdkGC *bg_gc;
gint width;
gint height;
gint pixmap_width;
gint offset = 0;
gint row_center_offset;
if (!clist->column[i].visible)
continue;
get_cell_style (clist, clist_row, state, i, &style, &fg_gc, &bg_gc);
clip_rectangle.x = clist->column[i].area.x + clist->hoffset;
clip_rectangle.width = clist->column[i].area.width;
/* calculate clipping region clipping region */
clip_rectangle.x -= COLUMN_INSET + CELL_SPACING;
clip_rectangle.width += (2 * COLUMN_INSET + CELL_SPACING +
(i == last_column) * CELL_SPACING);
if (area && !gdk_rectangle_intersect (area, &clip_rectangle,
&intersect_rectangle))
continue;
gdk_draw_rectangle (clist->clist_window, bg_gc, TRUE,
rect->x, rect->y, rect->width, rect->height);
clip_rectangle.x += COLUMN_INSET + CELL_SPACING;
clip_rectangle.width -= (2 * COLUMN_INSET + CELL_SPACING +
(i == last_column) * CELL_SPACING);
/* calculate real width for column justification */
pixmap_width = 0;
offset = 0;
switch (clist_row->cell[i].type)
{
case GTK_CELL_TEXT:
width = gdk_string_width (style->font,
GTK_CELL_TEXT (clist_row->cell[i])->text);
break;
case GTK_CELL_PIXMAP:
gdk_window_get_size (GTK_CELL_PIXMAP (clist_row->cell[i])->pixmap,
&pixmap_width, &height);
width = pixmap_width;
break;
case GTK_CELL_PIXTEXT:
gdk_window_get_size (GTK_CELL_PIXTEXT (clist_row->cell[i])->pixmap,
&pixmap_width, &height);
width = (pixmap_width +
GTK_CELL_PIXTEXT (clist_row->cell[i])->spacing +
gdk_string_width (style->font,
GTK_CELL_PIXTEXT
(clist_row->cell[i])->text));
break;
default:
continue;
break;
}
switch (clist->column[i].justification)
{
case GTK_JUSTIFY_LEFT:
offset = clip_rectangle.x + clist_row->cell[i].horizontal;
break;
case GTK_JUSTIFY_RIGHT:
offset = (clip_rectangle.x + clist_row->cell[i].horizontal +
clip_rectangle.width - width);
break;
case GTK_JUSTIFY_CENTER:
case GTK_JUSTIFY_FILL:
offset = (clip_rectangle.x + clist_row->cell[i].horizontal +
(clip_rectangle.width / 2) - (width / 2));
break;
};
/* Draw Text and/or Pixmap */
switch (clist_row->cell[i].type)
{
case GTK_CELL_PIXMAP:
draw_cell_pixmap (clist->clist_window, &clip_rectangle, fg_gc,
GTK_CELL_PIXMAP (clist_row->cell[i])->pixmap,
GTK_CELL_PIXMAP (clist_row->cell[i])->mask,
offset,
clip_rectangle.y + clist_row->cell[i].vertical +
(clip_rectangle.height - height) / 2,
pixmap_width, height);
break;
case GTK_CELL_PIXTEXT:
offset =
draw_cell_pixmap (clist->clist_window, &clip_rectangle, fg_gc,
GTK_CELL_PIXTEXT (clist_row->cell[i])->pixmap,
GTK_CELL_PIXTEXT (clist_row->cell[i])->mask,
offset,
clip_rectangle.y + clist_row->cell[i].vertical+
(clip_rectangle.height - height) / 2,
pixmap_width, height);
offset += GTK_CELL_PIXTEXT (clist_row->cell[i])->spacing;
case GTK_CELL_TEXT:
if (style != GTK_WIDGET (clist)->style)
row_center_offset = (((clist->row_height - style->font->ascent -
style->font->descent - 1) / 2) + 1.5 +
style->font->ascent);
else
row_center_offset = clist->row_center_offset;
gdk_gc_set_clip_rectangle (fg_gc, &clip_rectangle);
gdk_draw_string (clist->clist_window, style->font, fg_gc,
offset,
row_rectangle.y + row_center_offset +
clist_row->cell[i].vertical,
(clist_row->cell[i].type == GTK_CELL_PIXTEXT) ?
GTK_CELL_PIXTEXT (clist_row->cell[i])->text :
GTK_CELL_TEXT (clist_row->cell[i])->text);
gdk_gc_set_clip_rectangle (fg_gc, NULL);
break;
default:
break;
}
}
/* draw focus rectangle */
if (clist->focus_row == row &&
GTK_WIDGET_CAN_FOCUS (widget) && GTK_WIDGET_HAS_FOCUS(widget))
{
if (!area)
gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE,
row_rectangle.x, row_rectangle.y,
row_rectangle.width - 1, row_rectangle.height - 1);
else if (gdk_rectangle_intersect (area, &row_rectangle,
&intersect_rectangle))
{
gdk_gc_set_clip_rectangle (clist->xor_gc, &intersect_rectangle);
gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE,
row_rectangle.x, row_rectangle.y,
row_rectangle.width - 1,
row_rectangle.height - 1);
gdk_gc_set_clip_rectangle (clist->xor_gc, NULL);
}
}
}
static void
draw_rows (GtkCList *clist, GdkRectangle *area)
{
GList *list;
gint i;
gint first_row;
gint last_row;
if (clist->row_height == 0 || !GTK_WIDGET_DRAWABLE (clist))
return;
first_row = ROW_FROM_YPIXEL (clist, area->y);
last_row = ROW_FROM_YPIXEL (clist, area->y + area->height);
/* this is a small special case which exposes the bottom cell line
* on the last row -- it might go away if I change the wall the cell
* spacings are drawn
*/
if (clist->rows == first_row)
first_row--;
list = ROW_ELEMENT (clist, first_row);
for (i = first_row; i <= last_row ; i++) {
if (list == NULL)
break;
GTK_CLIST_CLASS ((GTK_OBJECT (clist))->klass)->draw_row (clist, area, i,
list->data);
list = list->next;
}
}
static void
gtk_flist_track_new_column_width (GtkCList *clist, int column, int new_width)
{
GtkFList *flist;
GdkRectangle title_redraw_area;
flist = GTK_FLIST (clist);
/* pin new_width to min and max values */
if (new_width < MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width))
new_width = MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width);
if (clist->column[column].max_width >= 0 &&
new_width > clist->column[column].max_width)
new_width = clist->column[column].max_width;
/* check to see if the pinned value is still different */
if (clist->column[column].width == new_width)
return;
/* set the new width */
clist->column[column].width = new_width;
clist->column[column].width_set = TRUE;
size_allocate_columns (clist, TRUE);
size_allocate_title_buttons (clist);
/* redraw the invalid columns */
if (clist->freeze_count == 0) {
GdkRectangle area;
area = clist->column_title_area;
area.x = clist->column[column].area.x;
area.height += clist->clist_window_height;
draw_rows (clist, &area);
}
/* draw the column title
* ToDo:
* fix this up
*/
title_redraw_area.x = GTK_WIDGET (flist->details->title)->allocation.x;
title_redraw_area.y = GTK_WIDGET (flist->details->title)->allocation.y;
title_redraw_area.width = GTK_WIDGET (flist->details->title)->allocation.width;
title_redraw_area.height = GTK_WIDGET (flist->details->title)->allocation.height;
(GTK_WIDGET_CLASS (NAUTILUS_KLASS (flist->details->title)))->
draw (flist->details->title, &title_redraw_area);
}
/* Our handler for motion_notify events. We override all of GtkCList's broken
* behavior.
*/
@ -476,6 +1210,45 @@ gtk_flist_motion (GtkWidget *widget, GdkEventMotion *event)
return TRUE;
}
void
gtk_flist_column_resize_track_start (GtkWidget *widget, int column)
{
GtkCList *clist;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
clist->drag_pos = column;
}
void
gtk_flist_column_resize_track (GtkWidget *widget, int column)
{
GtkCList *clist;
int x;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
clist = GTK_CLIST (widget);
gtk_widget_get_pointer (widget, &x, NULL);
gtk_flist_track_new_column_width (clist, column,
new_column_width (clist, column, &x));
}
void
gtk_flist_column_resize_track_end (GtkWidget *widget, int column)
{
GtkCList *clist;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
clist->drag_pos = -1;
}
/* Our handler for key_press and key_release events. We do nothing, and we do
* this to avoid GtkCList's broken behavior.
*/
@ -578,7 +1351,17 @@ gtk_flist_new_with_titles (int columns, char **titles)
GtkFList *flist;
flist = gtk_type_new (gtk_flist_get_type ());
gtk_clist_construct (GTK_CLIST (flist), columns, titles);
gtk_clist_construct (GTK_CLIST (flist), columns, NULL);
if (titles) {
GtkCList *clist;
int index;
clist = GTK_CLIST(flist);
for (index = 0; index < columns; index++) {
clist->column[index].title = g_strdup (titles[index]);
}
}
gtk_clist_set_selection_mode (GTK_CLIST (flist),
GTK_SELECTION_MULTIPLE);

View file

@ -36,7 +36,7 @@ struct _GtkFListClass {
GtkCListClass parent_class;
/* Signal: invoke the popup menu for selected items */
void (* context_click_selection) (GtkFList *flist, gint row);
void (* context_click_selection) (GtkFList *flist, int row);
/* Signal: invoke the popup menu for empty areas */
void (* context_click_background) (GtkFList *flist);
@ -45,10 +45,15 @@ struct _GtkFListClass {
void (* activate) (GtkFList *flist, gpointer data);
/* Signal: initiate a drag and drop operation */
void (* start_drag) (GtkFList *flist, gint button, GdkEvent *event);
void (* start_drag) (GtkFList *flist, int button, GdkEvent *event);
/* Signal: selection has changed */
void (* selection_changed) (GtkFList *flist);
/* column resize tracking calls */
void (* column_resize_track_start) (GtkWidget *widget, int column);
void (* column_resize_track) (GtkWidget *widget, int column);
void (* column_resize_track_end) (GtkWidget *widget, int column);
};

View file

@ -148,6 +148,47 @@ nautilus_fill_rectangle_with_gradient (GdkDrawable *drawable,
}
}
/**
* nautilus_rectangle_contains:
* @rectangle: Rectangle possibly containing a point.
* @x: X coordinate of a point.
* @y: Y coordinate of a point.
*
* Retun TRUE if point is contained inside a rectangle
*/
gboolean
nautilus_rectangle_contains (const GdkRectangle *rectangle,
int x,
int y)
{
g_return_val_if_fail (rectangle != NULL, FALSE);
return rectangle->x <= x && rectangle->x + rectangle->width >= x
&& rectangle->y <= y && rectangle->y + rectangle->height >= y;
}
/**
* nautilus_rectangle_inset:
* @rectangle: Rectangle we are insetting.
* @x: Horizontal ammount to inset by.
* @y: Vertical ammount to inset by.
*
* Inset a rectangle by a given horizontal and vertical ammount.
* Pass in negative inset values to grow the rectangle, positive to
* shrink it.
*/
void
nautilus_rectangle_inset (GdkRectangle *rectangle,
int x,
int y)
{
g_return_if_fail (rectangle != NULL);
rectangle->x += x;
rectangle->width -= 2 * x;
rectangle->y += y;
rectangle->height -= 2 * y;
}
/**
* nautilus_interpolate_color:
* @ratio: Place on line between colors to interpolate.

View file

@ -75,6 +75,15 @@ void nautilus_fill_rectangle_with_gradient (GdkDrawable *drawab
const GdkColor *end_color,
gboolean horizontal_gradient);
/* Misc GdkRectangle helper functions */
gboolean nautilus_rectangle_contains (const GdkRectangle *rectangle,
int x,
int y);
void nautilus_rectangle_inset (GdkRectangle *rectangle,
int x,
int y);
/* A basic operation we use for drawing gradients is interpolating two colors.*/
void nautilus_interpolate_color (gdouble ratio,
const GdkColor *start_color,

View file

@ -0,0 +1,763 @@
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
nautilus-list-column-title.c: List column title widget for interacting with list columns
Copyright (C) 2000 Eazel, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
Authors: Pavel Cisler <pavel@eazel.com>
*/
#include "nautilus-list-column-title.h"
#include "nautilus-gtk-macros.h"
#include "nautilus-gdk-extensions.h"
/* for now we need to know about GtkFList */
#include "gtkflist.h"
#include <gdk/gdk.h>
#include <gtk/gtkclist.h>
#include <gtk/gtkmain.h>
#include <libgnomeui/gnome-pixmap.h>
/* these are from GtkCList, for now we need to copy them here
* eventually the target list should be able to describe the values
*/
enum {
/* this defines the base grid spacing */
CELL_SPACING = 1,
/* added the horizontal space at the beginning and end of a row */
COLUMN_INSET = 3,
/* from GtkButton */
CHILD_SPACING = 1,
/* the width of the column resize windows */
DRAG_WIDTH = 6
};
static char * down_xpm[] = {
"6 5 2 1",
" c None",
". c #000000",
"......",
" ",
" .... ",
" ",
" .. "
};
static char * up_xpm[] = {
"6 5 2 1",
" c None",
". c #000000",
" .. ",
" ",
" .... ",
" ",
"......"
};
struct NautilusListColumnTitleDetails
{
/* gc for blitting sort order pixmaps, lazily allocated */
GdkGC *copy_area_gc;
/* sort order indicator pixmaps, lazily allocated */
GnomePixmap *up_indicator;
GnomePixmap *down_indicator;
int tracking_column_resize;
/* index of the column we are currently tracking or -1 */
int tracking_column_prelight;
/* index of the column we are currently rolling over or -1 */
int tracking_column_press;
/* index of the column we are currently pressing or -1 */
int last_tracking_x;
/* last horizontal track point so we can only resize when needed */
gboolean resize_cursor_on;
};
static void nautilus_list_column_title_initialize_class (gpointer klass);
static void nautilus_list_column_title_initialize (gpointer object, gpointer klass);
static void nautilus_list_column_title_paint (GtkWidget *widget, GdkRectangle *area);
static void nautilus_list_column_title_draw (GtkWidget *widget, GdkRectangle *box);
static void nautilus_list_column_title_buffered_draw (GtkWidget *widget);
static gboolean nautilus_list_column_title_expose (GtkWidget *widget, GdkEventExpose *event);
static void nautilus_list_column_title_realize (GtkWidget *widget);
static void nautilus_list_column_title_finalize (GtkObject *object);
static void nautilus_list_column_title_request (GtkWidget *widget, GtkRequisition *requisition);
static gboolean nautilus_list_column_title_motion (GtkWidget *widget, GdkEventMotion *event);
static gboolean nautilus_list_column_title_leave (GtkWidget *widget, GdkEventCrossing *event);
static gboolean nautilus_list_column_title_button_press (GtkWidget *widget, GdkEventButton *event);
static gboolean nautilus_list_column_title_button_release (GtkWidget *widget, GdkEventButton *event);
NAUTILUS_DEFINE_CLASS_BOILERPLATE (NautilusListColumnTitle, nautilus_list_column_title, GTK_TYPE_BIN)
/* generates nautilus_list_column_title_get_type */
static void
nautilus_list_column_title_initialize_class (gpointer klass)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
object_class = GTK_OBJECT_CLASS (klass);
widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = nautilus_list_column_title_finalize;
widget_class->draw = nautilus_list_column_title_draw;
widget_class->expose_event = nautilus_list_column_title_expose;
widget_class->realize = nautilus_list_column_title_realize;
widget_class->size_request = nautilus_list_column_title_request;
widget_class->motion_notify_event = nautilus_list_column_title_motion;
widget_class->leave_notify_event = nautilus_list_column_title_leave;
widget_class->button_press_event = nautilus_list_column_title_button_press;
widget_class->button_release_event = nautilus_list_column_title_button_release;
}
NautilusListColumnTitle *
nautilus_list_column_title_new (void)
{
return gtk_type_new (nautilus_list_column_title_get_type ());
}
static void
nautilus_list_column_title_initialize (gpointer object, gpointer klass)
{
NautilusListColumnTitle *column_title;
column_title = NAUTILUS_LIST_COLUMN_TITLE(object);
column_title->details = g_new0 (NautilusListColumnTitleDetails, 1);
/* copy_gc, up/down indicators get allocated lazily when needed */
column_title->details->copy_area_gc = NULL;
column_title->details->up_indicator = NULL;
column_title->details->down_indicator = NULL;
column_title->details->resize_cursor_on = FALSE;
column_title->details->tracking_column_resize = -1;
column_title->details->tracking_column_prelight = -1;
column_title->details->tracking_column_press = -1;
column_title->details->last_tracking_x = -1;
GTK_WIDGET_UNSET_FLAGS (object, GTK_NO_WINDOW);
}
static void
nautilus_list_column_title_realize (GtkWidget *widget)
{
GdkWindowAttr attributes;
int attributes_mask;
g_assert (widget != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
/* ask for expose events */
attributes.window_type = GDK_WINDOW_CHILD;
attributes.x = widget->allocation.x + GTK_CONTAINER (widget)->border_width;
attributes.y = widget->allocation.y + GTK_CONTAINER (widget)->border_width;
attributes.width = widget->allocation.width - GTK_CONTAINER (widget)->border_width * 2;
attributes.height = widget->allocation.height - GTK_CONTAINER (widget)->border_width * 2;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual (widget);
attributes.colormap = gtk_widget_get_colormap (widget);
attributes.event_mask = gtk_widget_get_events (widget);
attributes.event_mask |= GDK_EXPOSURE_MASK
| GDK_ENTER_NOTIFY_MASK
| GDK_LEAVE_NOTIFY_MASK
| GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
| GDK_POINTER_MOTION_MASK
| GDK_POINTER_MOTION_HINT_MASK
| GDK_KEY_PRESS_MASK;
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
/* give ourselves a background window */
widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
&attributes, attributes_mask);
gdk_window_set_user_data (widget->window, widget);
widget->style = gtk_style_attach (widget->style, widget->window);
gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
}
static void
nautilus_list_column_title_finalize (GtkObject *object)
{
NautilusListColumnTitle *column_title;
column_title = NAUTILUS_LIST_COLUMN_TITLE(object);
if (column_title->details->up_indicator != NULL)
gtk_widget_destroy (GTK_WIDGET (column_title->details->up_indicator));
if (column_title->details->down_indicator != NULL)
gtk_widget_destroy (GTK_WIDGET (column_title->details->down_indicator));
/* FIXME:
* figure out if we need to delete the copy_area_gc here
*/
g_free (column_title->details);
NAUTILUS_CALL_PARENT_CLASS (GTK_OBJECT_CLASS, finalize, (object));
}
static void
nautilus_list_column_title_request (GtkWidget *widget, GtkRequisition *requisition)
{
/* size requisition: make sure we have at least a minimal height */
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
g_assert (requisition != NULL);
requisition->width = (GTK_CONTAINER (widget)->border_width + CHILD_SPACING +
widget->style->klass->xthickness) * 2;
requisition->height = (GTK_CONTAINER (widget)->border_width + CHILD_SPACING +
widget->style->klass->ythickness) * 2;
if (GTK_BIN (widget)->child && GTK_WIDGET_VISIBLE (GTK_BIN (widget)->child)) {
GtkRequisition child_requisition;
gtk_widget_size_request (GTK_BIN (widget)->child, &child_requisition);
requisition->width += child_requisition.width;
requisition->height += child_requisition.height;
requisition->height = MIN (requisition->height, 10);
}
}
static const char *
get_column_label_at (GtkWidget *column_title, int index)
{
GtkCList *parent_clist;
parent_clist = GTK_CLIST (column_title->parent);
return parent_clist->column[index].title;
}
static void
get_column_frame_at(GtkWidget *column_title, int index, GdkRectangle *result)
{
GtkCList *parent_clist;
parent_clist = GTK_CLIST (column_title->parent);
*result = parent_clist->column_title_area;
result->x = parent_clist->hoffset + parent_clist->column[index].area.x - COLUMN_INSET;
result->y = 0;
result->width = parent_clist->column[index].area.width
+ CELL_SPACING + 2 * COLUMN_INSET - 1;
}
static GnomePixmap *
get_sort_indicator (GtkWidget *widget, gboolean ascending)
{
/* return the sort order pixmap for a given sort direction
* allocate the pixmap first time around
*/
NautilusListColumnTitle *column_title;
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
if (ascending) {
if (column_title->details->up_indicator == NULL) {
column_title->details->up_indicator =
GNOME_PIXMAP (gnome_pixmap_new_from_xpm_d (up_xpm));
}
return column_title->details->up_indicator;
} else {
if (column_title->details->down_indicator == NULL) {
column_title->details->down_indicator =
GNOME_PIXMAP (gnome_pixmap_new_from_xpm_d (down_xpm));
}
return column_title->details->down_indicator;
}
}
/* FIXME:
* Some of these magic numbers could be replaced with some more dynamic values
*/
enum {
CELL_TITLE_INSET = 6,
TITLE_BASELINE_OFFSET = 6,
SORT_ORDER_INDICATOR_WIDTH = 10
};
static void
nautilus_list_column_title_paint (GtkWidget *widget, GdkRectangle *area)
{
NautilusListColumnTitle *column_title;
GtkCList *parent_clist;
int index;
g_assert (GTK_CLIST (widget->parent) != NULL);
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
parent_clist = GTK_CLIST (widget->parent);
for (index = 0; index < parent_clist->columns; index++) {
GdkRectangle cell_rectangle;
GdkRectangle cell_redraw_area;
const char *cell_label;
int x_offset;
GnomePixmap *sort_indicator;
gboolean right_justified;
sort_indicator = NULL;
right_justified = (parent_clist->column[index].justification == GTK_JUSTIFY_RIGHT);
/* pick the ascending/descending sort indicator if needed */
if (index == parent_clist->sort_column) {
sort_indicator = get_sort_indicator (widget,
parent_clist->sort_type == GTK_SORT_ASCENDING);
}
get_column_frame_at (widget, index, &cell_rectangle);
gdk_rectangle_intersect (&cell_rectangle, area, &cell_redraw_area);
if (cell_redraw_area.width == 0 || cell_redraw_area.height == 0) {
/* no work, go on to the next */
continue;
}
cell_label = get_column_label_at (widget, index);
/* FIXME:
* add support for center justification
*/
if (right_justified) {
x_offset = cell_rectangle.x + cell_rectangle.width - CELL_TITLE_INSET;
} else {
x_offset = cell_rectangle.x + CELL_TITLE_INSET;
}
/* Paint the column title tiles as rectangles using "menu" style
* buttons as used by GtkCList produce round corners in some themes.
* Eventually we might consider having a separate style for column titles
*/
gtk_paint_box (widget->style, widget->window,
column_title->details->tracking_column_prelight == index ?
GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
column_title->details->tracking_column_press == index ?
GTK_SHADOW_IN : GTK_SHADOW_OUT,
area, widget, "menu",
cell_rectangle.x, cell_rectangle.y,
cell_rectangle.width, cell_rectangle.height);
/* Draw the sort indicator if needed */
if (sort_indicator != NULL) {
int y_offset = TITLE_BASELINE_OFFSET + 2;
if (right_justified) {
x_offset -= SORT_ORDER_INDICATOR_WIDTH;
}
/* allocate the sort indicator copy gc first time around */
if (column_title->details->copy_area_gc == NULL) {
column_title->details->copy_area_gc = gdk_gc_new (widget->window);
gdk_gc_set_function (column_title->details->copy_area_gc, GDK_COPY);
}
/* move the pixmap clip mask and origin to the right spot in the gc */
gdk_gc_set_clip_mask (column_title->details->copy_area_gc,
sort_indicator->mask);
gdk_gc_set_clip_origin (column_title->details->copy_area_gc, x_offset, y_offset);
gdk_draw_pixmap (widget->window, column_title->details->copy_area_gc,
sort_indicator->pixmap, 0, 0, x_offset, y_offset,
-1, -1);
if (!right_justified) {
x_offset += SORT_ORDER_INDICATOR_WIDTH;
}
}
if (cell_label) {
/* clip a little more than the cell rectangle to
* not have the text draw over the cell broder
* (this might no longer be needed when the
* title gets truncated properly, as opposed to
* getting chopped of
*/
nautilus_rectangle_inset (&cell_redraw_area, 2, 2);
if (right_justified) {
x_offset -= gdk_string_width (widget->style->font, cell_label) + 4;
}
gtk_paint_string (widget->style, widget->window, GTK_STATE_NORMAL,
&cell_redraw_area, widget, "label",
x_offset,
cell_rectangle.y + cell_rectangle.height - TITLE_BASELINE_OFFSET,
cell_label);
}
}
}
static void
nautilus_list_column_title_draw (GtkWidget *widget, GdkRectangle *area)
{
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
g_assert (area != NULL);
if (!GTK_WIDGET_DRAWABLE (widget)) {
return;
}
nautilus_list_column_title_paint (widget, area);
}
static void
nautilus_list_column_title_buffered_draw (GtkWidget *widget)
{
/* draw using an offscreen bitmap
* for now just draw directly
* add code here to lazily allocate an offscreen and pass it to the paint call
* without the offscreen you can now see a funny artifact when part of the
* old column border does not get erased properly when shrinking a column
*/
GdkRectangle redraw_area;
redraw_area.x = widget->allocation.x;
redraw_area.y = widget->allocation.y;
redraw_area.width = widget->allocation.width;
redraw_area.height = widget->allocation.height;
nautilus_list_column_title_draw (widget, &redraw_area);
}
static gboolean
nautilus_list_column_title_expose (GtkWidget *widget, GdkEventExpose *event)
{
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
g_assert (event != NULL);
if (!GTK_WIDGET_DRAWABLE (widget)) {
return FALSE;
}
nautilus_list_column_title_paint (widget, &event->area);
return FALSE;
}
static int
in_column_rect (GtkWidget *widget, int x, int y)
{
/* return the index of the column we hit or -1 */
GtkCList *parent_clist;
int index;
parent_clist = GTK_CLIST (widget->parent);
for (index = 0; index < parent_clist->columns; index++) {
/* hit testing for column resizing */
GdkRectangle cell_rectangle;
get_column_frame_at (widget, index, &cell_rectangle);
/* inset by a pixel so that you have to move past the border
* to be considered inside the rect
* nautilus_list_column_title_leave depends on this
*/
nautilus_rectangle_inset (&cell_rectangle, 1, 0);
if (nautilus_rectangle_contains (&cell_rectangle, x, y))
return index;
}
return -1;
}
static int
in_resize_rect (GtkWidget *widget, int x, int y)
{
/* return the index of the resize rect of a column we hit or -1 */
GtkCList *parent_clist;
int index;
parent_clist = GTK_CLIST (widget->parent);
for (index = 0; index < parent_clist->columns; index++) {
/* hit testing for column resizing */
GdkRectangle resize_rectangle;
get_column_frame_at (widget, index, &resize_rectangle);
nautilus_rectangle_inset (&resize_rectangle, 1, 0);
resize_rectangle.x = resize_rectangle.x + resize_rectangle.width - DRAG_WIDTH / 2;
resize_rectangle.width = DRAG_WIDTH;
if (nautilus_rectangle_contains (&resize_rectangle, x, y))
return index;
}
return -1;
}
static void
show_hide_resize_cursor_if_needed (GtkWidget *widget, gboolean on)
{
NautilusListColumnTitle *column_title;
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
if (on == column_title->details->resize_cursor_on)
/* already set right */
return;
if (on) {
/* switch to a resize cursor */
GdkCursor *cursor;
cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
gdk_window_set_cursor (widget->window, cursor);
gdk_cursor_destroy (cursor);
} else
/* restore to old cursor */
gdk_window_set_cursor (widget->window, NULL);
column_title->details->resize_cursor_on = on;
}
static gboolean
track_prelight (GtkWidget *widget, int mouse_x, int mouse_y)
{
NautilusListColumnTitle *column_title;
int over_column;
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
/* see if we need to update the prelight state of a column */
over_column = in_column_rect (widget, mouse_x, mouse_y);
if (column_title->details->tracking_column_resize != -1) {
/* resizing a column, don't prelight */
over_column = -1;
}
if (column_title->details->tracking_column_press != -1) {
/* pressing a column, don't prelight */
over_column = -1;
}
if (column_title->details->tracking_column_prelight == over_column) {
/* no change */
return FALSE;
}
/* update state and tell callers to redraw */
column_title->details->tracking_column_prelight = over_column;
return TRUE;
}
static gboolean
nautilus_list_column_title_motion (GtkWidget *widget, GdkEventMotion *event)
{
NautilusListColumnTitle *column_title;
GtkWidget *parent_list;
int mouse_x, mouse_y;
g_assert (GTK_FLIST (widget->parent) != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
parent_list = GTK_WIDGET (widget->parent);
gdk_window_get_pointer (widget->window, &mouse_x, &mouse_y, NULL);
if (column_title->details->tracking_column_resize != -1) {
/* we are currently tracking a column */
if (column_title->details->last_tracking_x != mouse_x) {
/* mouse did move horizontally since last time */
column_title->details->last_tracking_x = mouse_x;
(GTK_FLIST_CLASS (NAUTILUS_KLASS (parent_list)))->
column_resize_track (parent_list,
column_title->details->tracking_column_resize);
}
} else {
/* make sure we are showing the right cursor */
show_hide_resize_cursor_if_needed (widget,
in_resize_rect (widget, mouse_x, mouse_y) != -1);
}
/* see if we need to update the prelight state of a column */
if (track_prelight (widget, mouse_x, mouse_y)) {
nautilus_list_column_title_buffered_draw (widget);
}
return TRUE;
}
static gboolean
nautilus_list_column_title_leave (GtkWidget *widget, GdkEventCrossing *event)
{
g_assert (GTK_FLIST (widget->parent) != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
/* see if we need to update the prelight state of a column */
if (track_prelight (widget, (int)event->x, (int)event->y)) {
gtk_widget_set_state (widget, GTK_STATE_NORMAL);
nautilus_list_column_title_buffered_draw (widget);
}
return TRUE;
}
static gboolean
nautilus_list_column_title_button_press (GtkWidget *widget, GdkEventButton *event)
{
NautilusListColumnTitle *column_title;
GtkWidget *parent_list;
g_assert (event != NULL);
g_assert (GTK_FLIST (widget->parent) != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
g_assert (NAUTILUS_LIST_COLUMN_TITLE(widget)->details->tracking_column_resize == -1);
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
parent_list = GTK_WIDGET (widget->parent);
if (event->type == GDK_BUTTON_PRESS) {
int resized_column;
int clicked_column;
resized_column = in_resize_rect (widget, (int)event->x, (int)event->y);
clicked_column = in_column_rect (widget, (int)event->x, (int)event->y);
if (resized_column != -1) {
GdkCursor *cursor;
int grab_result;
/* during the drag, use the resize cursor */
cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
/* grab the pointer events so that we get them even when
* the mouse tracks out of the widget window
*/
grab_result = gdk_pointer_grab (widget->window, FALSE,
GDK_POINTER_MOTION_HINT_MASK
| GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK,
NULL, cursor, event->time);
gdk_cursor_destroy (cursor);
if (grab_result != 0)
/* failed to grab the pointer, give up
*
* The grab results are not very well documented
* looks like they may be Success, GrabSuccess, AlreadyGrabbed
* or anything else the low level X calls in gdk_pointer_grab
* decide to return.
*/
return FALSE;
/* set up new state */
column_title->details->tracking_column_resize = resized_column;
column_title->details->tracking_column_prelight = -1;
/* FIXME:
* use a "resized" state here ?
*/
gtk_widget_set_state (widget, GTK_STATE_NORMAL);
/* start column resize tracking */
(GTK_FLIST_CLASS (NAUTILUS_KLASS (parent_list)))->
column_resize_track_start (parent_list, resized_column);
return FALSE;
}
if (clicked_column != -1) {
/* clicked a column, draw the pressed column title */
column_title->details->tracking_column_prelight = -1;
column_title->details->tracking_column_press = clicked_column;
gtk_widget_set_state (widget, GTK_STATE_ACTIVE);
/* FIXME:
* buffered draw may be better here
*/
gtk_widget_queue_draw (widget);
}
}
return FALSE;
}
static gboolean
nautilus_list_column_title_button_release (GtkWidget *widget, GdkEventButton *event)
{
NautilusListColumnTitle *column_title;
GtkWidget *parent_list;
g_assert (event != NULL);
g_assert (GTK_FLIST (widget->parent) != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
parent_list = GTK_WIDGET (widget->parent);
if (column_title->details->tracking_column_resize != -1) {
/* let go of all the pointer events */
if (gdk_pointer_is_grabbed ())
gdk_pointer_ungrab (event->time);
/* end column resize tracking */
(GTK_FLIST_CLASS (NAUTILUS_KLASS (parent_list)))->
column_resize_track_end (parent_list,
column_title->details->tracking_column_resize);
column_title->details->tracking_column_resize = -1;
} else if (column_title->details->tracking_column_press != -1) {
/* column title press -- change the sort order */
gtk_signal_emit_by_name (GTK_OBJECT (parent_list), "click_column",
column_title->details->tracking_column_press);
/* end press tracking */
column_title->details->tracking_column_press = -1;
}
track_prelight (widget, (int)event->x, (int)event->y);
gtk_widget_set_state (widget,
column_title->details->tracking_column_prelight != -1 ?
GTK_STATE_PRELIGHT : GTK_STATE_NORMAL);
/* FIXME:
* buffered draw may be better here
*/
gtk_widget_queue_draw (widget);
return FALSE;
}

View file

@ -0,0 +1,73 @@
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
nautilus-list-column-title.h: List column title widget for interacting with list columns
Copyright (C) 2000 Eazel, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
Authors: Pavel Cisler <pavel@eazel.com>
*/
#ifndef __NAUTILUS_LIST_COLUMN_TITLE__
#define __NAUTILUS_LIST_COLUMN_TITLE__
#include <gdk/gdktypes.h>
#include <gtk/gtkwidget.h>
#include <gtk/gtkbin.h>
#include <gtk/gtkenums.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define NAUTILUS_TYPE_LIST_COLUMN_TITLE \
(nautilus_list_column_title_get_type ())
#define NAUTILUS_LIST_COLUMN_TITLE(obj) \
(GTK_CHECK_CAST ((obj), NAUTILUS_TYPE_LIST_COLUMN_TITLE, NautilusListColumnTitle))
#define NAUTILUS_LIST_COLUMN_TITLE_CLASS(klass) \
(GTK_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_LIST_COLUMN_TITLE, NautilusListColumnTitleClass))
#define NAUTILUS_IS_LIST_COLUMN_TITLE(obj) \
(GTK_CHECK_TYPE ((obj), NAUTILUS_TYPE_LIST_COLUMN_TITLE))
#define NAUTILUS_IS_LIST_COLUMN_TITLE_CLASS(klass) \
(GTK_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_LIST_COLUMN_TITLE))
typedef struct NautilusListColumnTitle NautilusListColumnTitle;
typedef struct NautilusListColumnTitleClass NautilusListColumnTitleClass;
typedef struct NautilusListColumnTitleDetails NautilusListColumnTitleDetails;
struct NautilusListColumnTitle
{
GtkBin bin;
NautilusListColumnTitleDetails *details;
};
struct NautilusListColumnTitleClass
{
GtkBinClass parent_class;
};
GtkType nautilus_list_column_title_get_type (void);
NautilusListColumnTitle *nautilus_list_column_title_new (void);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __NAUTILUS_LIST_COLUMN_TITLE__ */

View file

@ -47,6 +47,7 @@ libnautilusinclude_HEADERS= \
nautilus-gtk-extensions.h \
nautilus-icon-factory.h \
nautilus-icons-view-icon-item.h \
nautilus-list-column-title.h \
nautilus-metadata.h \
nautilus-mime-type.h \
nautilus-self-checks.h \
@ -85,6 +86,7 @@ libnautilus_la_SOURCES=$(nautilus_idl_sources) \
nautilus-icon-factory.c \
nautilus-icons-view-icon-item.c \
nautilus-lib-self-check-functions.c \
nautilus-list-column-title.c \
nautilus-mime-type.c \
nautilus-self-checks.c \
nautilus-string-list.c \

View file

@ -19,6 +19,7 @@
#include "nautilus-glib-extensions.h"
#include "nautilus-gtk-macros.h"
#include "nautilus-background.h"
#include "nautilus-list-column-title.h"
struct _GtkFListDetails
{
@ -37,7 +38,8 @@ struct _GtkFListDetails
/* Delayed selection information */
int dnd_select_pending;
guint dnd_select_pending_state;
GtkWidget *title;
};
/* maximum amount of milliseconds the mouse button is allowed to stay down and still be considered a click */
@ -81,8 +83,18 @@ static gboolean gtk_flist_drag_drop (GtkWidget *widget, GdkDragContext *context,
static void gtk_flist_drag_data_received (GtkWidget *widget, GdkDragContext *context,
gint x, gint y, GtkSelectionData *data,
guint info, guint time);
static void select_or_unselect_row_cb (GtkCList *clist, gint row, gint column,
GdkEvent *event);
static void gtk_flist_clear (GtkCList *clist);
static void draw_row (GtkCList *flist, GdkRectangle *area, gint row, GtkCListRow *clist_row);
static void gtk_flist_realize (GtkWidget *widget);
static void gtk_flist_size_request (GtkWidget *widget, GtkRequisition *requisition);
static void gtk_flist_column_resize_track_start (GtkWidget *widget, int column);
static void gtk_flist_column_resize_track (GtkWidget *widget, int column);
static void gtk_flist_column_resize_track_end (GtkWidget *widget, int column);
NAUTILUS_DEFINE_CLASS_BOILERPLATE (GtkFList, gtk_flist, GTK_TYPE_CLIST)
@ -90,15 +102,17 @@ static guint flist_signals[LAST_SIGNAL];
/* Standard class initialization function */
static void
gtk_flist_initialize_class (GtkFListClass *class)
gtk_flist_initialize_class (GtkFListClass *klass)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
GtkCListClass *clist_class;
GtkFListClass *flist_class;
object_class = (GtkObjectClass *) class;
widget_class = (GtkWidgetClass *) class;
clist_class = (GtkCListClass *) class;
object_class = (GtkObjectClass *) klass;
widget_class = (GtkWidgetClass *) klass;
clist_class = (GtkCListClass *) klass;
flist_class = (GtkFListClass *) klass;
flist_signals[CONTEXT_CLICK_SELECTION] =
gtk_signal_new ("context_click_selection",
@ -135,13 +149,18 @@ gtk_flist_initialize_class (GtkFListClass *class)
gtk_signal_new ("selection_changed",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (GtkFListClass, start_drag),
GTK_SIGNAL_OFFSET (GtkFListClass, selection_changed),
gtk_marshal_NONE__NONE,
GTK_TYPE_NONE, 0);
gtk_object_class_add_signals (object_class, flist_signals, LAST_SIGNAL);
flist_class->column_resize_track_start = gtk_flist_column_resize_track_start;
flist_class->column_resize_track = gtk_flist_column_resize_track;
flist_class->column_resize_track_end = gtk_flist_column_resize_track_end;
clist_class->clear = gtk_flist_clear;
clist_class->draw_row = draw_row;
widget_class->button_press_event = gtk_flist_button_press;
widget_class->button_release_event = gtk_flist_button_release;
@ -155,20 +174,14 @@ gtk_flist_initialize_class (GtkFListClass *class)
widget_class->drag_motion = gtk_flist_drag_motion;
widget_class->drag_drop = gtk_flist_drag_drop;
widget_class->drag_data_received = gtk_flist_drag_data_received;
}
static void
select_or_unselect_row_cb (GtkCList *clist, gint row, gint column, GdkEvent *event)
{
/* This is the one bottleneck for all selection changes */
gtk_signal_emit (GTK_OBJECT (clist), flist_signals[SELECTION_CHANGED]);
widget_class->realize = gtk_flist_realize;
widget_class->size_request = gtk_flist_size_request;
}
/* Standard object initialization function */
static void
gtk_flist_initialize (GtkFList *flist)
{
{
flist->details = g_new0 (GtkFListDetails, 1);
flist->details->anchor_row = -1;
@ -194,6 +207,16 @@ gtk_flist_initialize (GtkFList *flist)
"unselect_row",
select_or_unselect_row_cb,
flist);
flist->details->title = GTK_WIDGET (nautilus_list_column_title_new());
}
static void
select_or_unselect_row_cb (GtkCList *clist, gint row, gint column, GdkEvent *event)
{
/* This is the one bottleneck for all selection changes */
gtk_signal_emit (GTK_OBJECT (clist), flist_signals[SELECTION_CHANGED]);
}
static void
@ -429,6 +452,717 @@ gtk_flist_button_release (GtkWidget *widget, GdkEventButton *event)
return retval;
}
/* stolen from gtkclist.c for now */
/* minimum allowed width of a column */
#define COLUMN_MIN_WIDTH 5
/* this defines the base grid spacing */
#define CELL_SPACING 1
/* added the horizontal space at the beginning and end of a row */
#define COLUMN_INSET 3
/* the width of the column resize windows */
#define DRAG_WIDTH 6
/* gives the left pixel of the given column in context of
* the clist's hoffset */
#define COLUMN_LEFT_XPIXEL(clist, colnum) ((clist)->column[(colnum)].area.x + \
(clist)->hoffset)
/* gives the top pixel of the given row in context of
* the clist's voffset */
#define ROW_TOP_YPIXEL(clist, row) (((clist)->row_height * (row)) + \
(((row) + 1) * CELL_SPACING) + \
(clist)->voffset)
/* returns the row index from a y pixel location in the
* context of the clist's voffset */
#define ROW_FROM_YPIXEL(clist, y) (((y) - (clist)->voffset) / \
((clist)->row_height + CELL_SPACING))
/* returns the GList item for the nth row */
#define ROW_ELEMENT(clist, row) (((row) == (clist)->rows - 1) ? \
(clist)->row_list_end : \
g_list_nth ((clist)->row_list, (row)))
/* returns the total height of the list */
#define LIST_HEIGHT(clist) (((clist)->row_height * ((clist)->rows)) + \
(CELL_SPACING * ((clist)->rows + 1)))
static void
gtk_flist_realize (GtkWidget *widget)
{
GtkFList *flist;
GtkCList *clist;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
flist = GTK_FLIST (widget);
clist = GTK_CLIST (widget);
clist->column[0].button = flist->details->title;
NAUTILUS_CALL_PARENT_CLASS (GTK_WIDGET_CLASS, realize, (widget));
if (clist->title_window) {
gtk_widget_set_parent_window (flist->details->title, clist->title_window);
}
gtk_widget_set_parent (flist->details->title, GTK_WIDGET (clist));
gtk_widget_show (flist->details->title);
GTK_CLIST_SET_FLAG (clist, CLIST_SHOW_TITLES);
}
/* this is here just temporarily */
static gint
list_requisition_width (GtkCList *clist)
{
gint width = CELL_SPACING;
gint i;
for (i = clist->columns - 1; i >= 0; i--) {
if (!clist->column[i].visible)
continue;
if (clist->column[i].width_set)
width += clist->column[i].width + CELL_SPACING + (2 * COLUMN_INSET);
else if (GTK_CLIST_SHOW_TITLES(clist) && clist->column[i].button)
width += clist->column[i].button->requisition.width;
}
return width;
}
static void
gtk_flist_size_request (GtkWidget *widget, GtkRequisition *requisition)
{
/* stolen from gtk_clist
* make sure the proper title ammount is allocated for the column
* title view -- this would not otherwise be done because
* GtkFList depends the buttons being there when doing a size calculation
*/
GtkFList *flist;
GtkCList *clist;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
g_return_if_fail (requisition != NULL);
clist = GTK_CLIST (widget);
flist = GTK_FLIST (widget);
requisition->width = 0;
requisition->height = 0;
/* compute the size of the column title (title) area */
clist->column_title_area.height = 0;
if (GTK_CLIST_SHOW_TITLES(clist) && flist->details->title) {
GtkRequisition child_requisition;
gtk_widget_size_request (flist->details->title,
&child_requisition);
child_requisition.height = 20;
/* for now */
clist->column_title_area.height =
MAX (clist->column_title_area.height,
child_requisition.height);
}
requisition->width += (widget->style->klass->xthickness +
GTK_CONTAINER (widget)->border_width) * 2;
requisition->height += (clist->column_title_area.height +
(widget->style->klass->ythickness +
GTK_CONTAINER (widget)->border_width) * 2);
requisition->width += list_requisition_width (clist);
requisition->height += LIST_HEIGHT (clist);
}
static gint
new_column_width (GtkCList *clist, gint column, gint *x)
{
gint xthickness = GTK_WIDGET (clist)->style->klass->xthickness;
gint width;
gint cx;
gint dx;
gint last_column;
/* first translate the x position from widget->window
* to clist->clist_window */
cx = *x - xthickness;
for (last_column = clist->columns - 1;
last_column >= 0 && !clist->column[last_column].visible; last_column--);
/* calculate new column width making sure it doesn't end up
* less than the minimum width */
dx = (COLUMN_LEFT_XPIXEL (clist, column) + COLUMN_INSET +
(column < last_column) * CELL_SPACING);
width = cx - dx;
if (width < MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width)) {
width = MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width);
cx = dx + width;
*x = cx + xthickness;
} else if (clist->column[column].max_width >= COLUMN_MIN_WIDTH &&
width > clist->column[column].max_width) {
width = clist->column[column].max_width;
cx = dx + clist->column[column].max_width;
*x = cx + xthickness;
}
if (cx < 0 || cx > clist->clist_window_width)
*x = -1;
return width;
}
static void
size_allocate_columns (GtkCList *clist, gboolean block_resize)
{
int xoffset = CELL_SPACING + COLUMN_INSET;
int last_column;
int i;
/* find last visible column and calculate correct column width */
for (last_column = clist->columns - 1;
last_column >= 0 && !clist->column[last_column].visible; last_column--)
;
if (last_column < 0)
return;
for (i = 0; i <= last_column; i++) {
if (!clist->column[i].visible)
continue;
clist->column[i].area.x = xoffset;
if (clist->column[i].width_set) {
if (!block_resize && GTK_CLIST_SHOW_TITLES(clist) &&
clist->column[i].auto_resize && clist->column[i].button) {
gint width;
width = (clist->column[i].button->requisition.width -
(CELL_SPACING + (2 * COLUMN_INSET)));
if (width > clist->column[i].width)
gtk_clist_set_column_width (clist, i, width);
}
clist->column[i].area.width = clist->column[i].width;
xoffset += clist->column[i].width + CELL_SPACING + (2 * COLUMN_INSET);
} else if (GTK_CLIST_SHOW_TITLES(clist) && clist->column[i].button) {
clist->column[i].area.width =
clist->column[i].button->requisition.width -
(CELL_SPACING + (2 * COLUMN_INSET));
xoffset += clist->column[i].button->requisition.width;
}
}
clist->column[last_column].area.width += MAX (0, clist->clist_window_width + COLUMN_INSET - xoffset);
}
static void
size_allocate_title_buttons (GtkCList *clist)
{
GtkAllocation button_allocation;
int last_column;
int last_button = 0;
int i;
button_allocation.x = clist->hoffset;
button_allocation.y = 0;
button_allocation.width = 0;
button_allocation.height = clist->column_title_area.height;
/* find last visible column */
for (last_column = clist->columns - 1; last_column >= 0; last_column--)
if (clist->column[last_column].visible)
break;
for (i = 0; i < last_column; i++) {
if (!clist->column[i].visible) {
last_button = i + 1;
gdk_window_hide (clist->column[i].window);
continue;
}
button_allocation.width += (clist->column[i].area.width +
CELL_SPACING + 2 * COLUMN_INSET);
if (!clist->column[i + 1].button) {
gdk_window_hide (clist->column[i].window);
continue;
}
gtk_widget_size_allocate (clist->column[last_button].button,
&button_allocation);
button_allocation.x += button_allocation.width;
button_allocation.width = 0;
last_button = i + 1;
}
button_allocation.width += (clist->column[last_column].area.width +
2 * (CELL_SPACING + COLUMN_INSET));
gtk_widget_size_allocate (clist->column[last_button].button,
&button_allocation);
}
static void
get_cell_style (GtkCList *clist, GtkCListRow *clist_row,
gint state, gint column, GtkStyle **style,
GdkGC **fg_gc, GdkGC **bg_gc)
{
gint fg_state;
if ((state == GTK_STATE_NORMAL) &&
(GTK_WIDGET (clist)->state == GTK_STATE_INSENSITIVE))
fg_state = GTK_STATE_INSENSITIVE;
else
fg_state = state;
if (clist_row->cell[column].style) {
if (style)
*style = clist_row->cell[column].style;
if (fg_gc)
*fg_gc = clist_row->cell[column].style->fg_gc[fg_state];
if (bg_gc) {
if (state == GTK_STATE_SELECTED)
*bg_gc = clist_row->cell[column].style->bg_gc[state];
else
*bg_gc = clist_row->cell[column].style->base_gc[state];
}
} else if (clist_row->style) {
if (style)
*style = clist_row->style;
if (fg_gc)
*fg_gc = clist_row->style->fg_gc[fg_state];
if (bg_gc) {
if (state == GTK_STATE_SELECTED)
*bg_gc = clist_row->style->bg_gc[state];
else
*bg_gc = clist_row->style->base_gc[state];
}
} else {
if (style)
*style = GTK_WIDGET (clist)->style;
if (fg_gc)
*fg_gc = GTK_WIDGET (clist)->style->fg_gc[fg_state];
if (bg_gc) {
if (state == GTK_STATE_SELECTED)
*bg_gc = GTK_WIDGET (clist)->style->bg_gc[state];
else
*bg_gc = GTK_WIDGET (clist)->style->base_gc[state];
}
if (state != GTK_STATE_SELECTED) {
if (fg_gc && clist_row->fg_set)
*fg_gc = clist->fg_gc;
if (bg_gc && clist_row->bg_set)
*bg_gc = clist->bg_gc;
}
}
}
static gint
draw_cell_pixmap (GdkWindow *window, GdkRectangle *clip_rectangle, GdkGC *fg_gc,
GdkPixmap *pixmap, GdkBitmap *mask,
gint x, gint y, gint width, gint height)
{
gint xsrc = 0;
gint ysrc = 0;
if (mask) {
gdk_gc_set_clip_mask (fg_gc, mask);
gdk_gc_set_clip_origin (fg_gc, x, y);
}
if (x < clip_rectangle->x) {
xsrc = clip_rectangle->x - x;
width -= xsrc;
x = clip_rectangle->x;
}
if (x + width > clip_rectangle->x + clip_rectangle->width)
width = clip_rectangle->x + clip_rectangle->width - x;
if (y < clip_rectangle->y) {
ysrc = clip_rectangle->y - y;
height -= ysrc;
y = clip_rectangle->y;
}
if (y + height > clip_rectangle->y + clip_rectangle->height)
height = clip_rectangle->y + clip_rectangle->height - y;
gdk_draw_pixmap (window, fg_gc, pixmap, xsrc, ysrc, x, y, width, height);
gdk_gc_set_clip_origin (fg_gc, 0, 0);
if (mask)
gdk_gc_set_clip_mask (fg_gc, NULL);
return x + MAX (width, 0);
}
static void
draw_row (GtkCList *clist,
GdkRectangle *area,
gint row,
GtkCListRow *clist_row)
{
GtkWidget *widget;
GdkRectangle *rect;
GdkRectangle row_rectangle;
GdkRectangle cell_rectangle;
GdkRectangle clip_rectangle;
GdkRectangle intersect_rectangle;
gint last_column;
gint state;
gint i;
g_return_if_fail (clist != NULL);
/* bail now if we arn't drawable yet */
if (!GTK_WIDGET_DRAWABLE (clist) || row < 0 || row >= clist->rows)
return;
widget = GTK_WIDGET (clist);
/* if the function is passed the pointer to the row instead of null,
* it avoids this expensive lookup */
if (!clist_row)
clist_row = ROW_ELEMENT (clist, row)->data;
/* rectangle of the entire row */
row_rectangle.x = 0;
row_rectangle.y = ROW_TOP_YPIXEL (clist, row);
row_rectangle.width = clist->clist_window_width;
row_rectangle.height = clist->row_height;
/* rectangle of the cell spacing above the row */
cell_rectangle.x = 0;
cell_rectangle.y = row_rectangle.y - CELL_SPACING;
cell_rectangle.width = row_rectangle.width;
cell_rectangle.height = CELL_SPACING;
/* rectangle used to clip drawing operations, its y and height
* positions only need to be set once, so we set them once here.
* the x and width are set withing the drawing loop below once per
* column */
clip_rectangle.y = row_rectangle.y;
clip_rectangle.height = row_rectangle.height;
if (clist_row->state == GTK_STATE_NORMAL)
{
if (clist_row->fg_set)
gdk_gc_set_foreground (clist->fg_gc, &clist_row->foreground);
if (clist_row->bg_set)
gdk_gc_set_foreground (clist->bg_gc, &clist_row->background);
}
state = clist_row->state;
/* draw the cell borders and background */
if (area)
{
rect = &intersect_rectangle;
if (gdk_rectangle_intersect (area, &cell_rectangle,
&intersect_rectangle))
gdk_draw_rectangle (clist->clist_window,
widget->style->base_gc[GTK_STATE_ACTIVE],
TRUE,
intersect_rectangle.x,
intersect_rectangle.y,
intersect_rectangle.width,
intersect_rectangle.height);
/* the last row has to clear its bottom cell spacing too */
if (clist_row == clist->row_list_end->data)
{
cell_rectangle.y += clist->row_height + CELL_SPACING;
if (gdk_rectangle_intersect (area, &cell_rectangle,
&intersect_rectangle))
gdk_draw_rectangle (clist->clist_window,
widget->style->base_gc[GTK_STATE_ACTIVE],
TRUE,
intersect_rectangle.x,
intersect_rectangle.y,
intersect_rectangle.width,
intersect_rectangle.height);
}
if (!gdk_rectangle_intersect (area, &row_rectangle,&intersect_rectangle))
return;
}
else
{
rect = &clip_rectangle;
gdk_draw_rectangle (clist->clist_window,
widget->style->base_gc[GTK_STATE_ACTIVE],
TRUE,
cell_rectangle.x,
cell_rectangle.y,
cell_rectangle.width,
cell_rectangle.height);
/* the last row has to clear its bottom cell spacing too */
if (clist_row == clist->row_list_end->data)
{
cell_rectangle.y += clist->row_height + CELL_SPACING;
gdk_draw_rectangle (clist->clist_window,
widget->style->base_gc[GTK_STATE_ACTIVE],
TRUE,
cell_rectangle.x,
cell_rectangle.y,
cell_rectangle.width,
cell_rectangle.height);
}
}
for (last_column = clist->columns - 1;
last_column >= 0 && !clist->column[last_column].visible; last_column--)
;
/* iterate and draw all the columns (row cells) and draw their contents */
for (i = 0; i < clist->columns; i++)
{
GtkStyle *style;
GdkGC *fg_gc;
GdkGC *bg_gc;
gint width;
gint height;
gint pixmap_width;
gint offset = 0;
gint row_center_offset;
if (!clist->column[i].visible)
continue;
get_cell_style (clist, clist_row, state, i, &style, &fg_gc, &bg_gc);
clip_rectangle.x = clist->column[i].area.x + clist->hoffset;
clip_rectangle.width = clist->column[i].area.width;
/* calculate clipping region clipping region */
clip_rectangle.x -= COLUMN_INSET + CELL_SPACING;
clip_rectangle.width += (2 * COLUMN_INSET + CELL_SPACING +
(i == last_column) * CELL_SPACING);
if (area && !gdk_rectangle_intersect (area, &clip_rectangle,
&intersect_rectangle))
continue;
gdk_draw_rectangle (clist->clist_window, bg_gc, TRUE,
rect->x, rect->y, rect->width, rect->height);
clip_rectangle.x += COLUMN_INSET + CELL_SPACING;
clip_rectangle.width -= (2 * COLUMN_INSET + CELL_SPACING +
(i == last_column) * CELL_SPACING);
/* calculate real width for column justification */
pixmap_width = 0;
offset = 0;
switch (clist_row->cell[i].type)
{
case GTK_CELL_TEXT:
width = gdk_string_width (style->font,
GTK_CELL_TEXT (clist_row->cell[i])->text);
break;
case GTK_CELL_PIXMAP:
gdk_window_get_size (GTK_CELL_PIXMAP (clist_row->cell[i])->pixmap,
&pixmap_width, &height);
width = pixmap_width;
break;
case GTK_CELL_PIXTEXT:
gdk_window_get_size (GTK_CELL_PIXTEXT (clist_row->cell[i])->pixmap,
&pixmap_width, &height);
width = (pixmap_width +
GTK_CELL_PIXTEXT (clist_row->cell[i])->spacing +
gdk_string_width (style->font,
GTK_CELL_PIXTEXT
(clist_row->cell[i])->text));
break;
default:
continue;
break;
}
switch (clist->column[i].justification)
{
case GTK_JUSTIFY_LEFT:
offset = clip_rectangle.x + clist_row->cell[i].horizontal;
break;
case GTK_JUSTIFY_RIGHT:
offset = (clip_rectangle.x + clist_row->cell[i].horizontal +
clip_rectangle.width - width);
break;
case GTK_JUSTIFY_CENTER:
case GTK_JUSTIFY_FILL:
offset = (clip_rectangle.x + clist_row->cell[i].horizontal +
(clip_rectangle.width / 2) - (width / 2));
break;
};
/* Draw Text and/or Pixmap */
switch (clist_row->cell[i].type)
{
case GTK_CELL_PIXMAP:
draw_cell_pixmap (clist->clist_window, &clip_rectangle, fg_gc,
GTK_CELL_PIXMAP (clist_row->cell[i])->pixmap,
GTK_CELL_PIXMAP (clist_row->cell[i])->mask,
offset,
clip_rectangle.y + clist_row->cell[i].vertical +
(clip_rectangle.height - height) / 2,
pixmap_width, height);
break;
case GTK_CELL_PIXTEXT:
offset =
draw_cell_pixmap (clist->clist_window, &clip_rectangle, fg_gc,
GTK_CELL_PIXTEXT (clist_row->cell[i])->pixmap,
GTK_CELL_PIXTEXT (clist_row->cell[i])->mask,
offset,
clip_rectangle.y + clist_row->cell[i].vertical+
(clip_rectangle.height - height) / 2,
pixmap_width, height);
offset += GTK_CELL_PIXTEXT (clist_row->cell[i])->spacing;
case GTK_CELL_TEXT:
if (style != GTK_WIDGET (clist)->style)
row_center_offset = (((clist->row_height - style->font->ascent -
style->font->descent - 1) / 2) + 1.5 +
style->font->ascent);
else
row_center_offset = clist->row_center_offset;
gdk_gc_set_clip_rectangle (fg_gc, &clip_rectangle);
gdk_draw_string (clist->clist_window, style->font, fg_gc,
offset,
row_rectangle.y + row_center_offset +
clist_row->cell[i].vertical,
(clist_row->cell[i].type == GTK_CELL_PIXTEXT) ?
GTK_CELL_PIXTEXT (clist_row->cell[i])->text :
GTK_CELL_TEXT (clist_row->cell[i])->text);
gdk_gc_set_clip_rectangle (fg_gc, NULL);
break;
default:
break;
}
}
/* draw focus rectangle */
if (clist->focus_row == row &&
GTK_WIDGET_CAN_FOCUS (widget) && GTK_WIDGET_HAS_FOCUS(widget))
{
if (!area)
gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE,
row_rectangle.x, row_rectangle.y,
row_rectangle.width - 1, row_rectangle.height - 1);
else if (gdk_rectangle_intersect (area, &row_rectangle,
&intersect_rectangle))
{
gdk_gc_set_clip_rectangle (clist->xor_gc, &intersect_rectangle);
gdk_draw_rectangle (clist->clist_window, clist->xor_gc, FALSE,
row_rectangle.x, row_rectangle.y,
row_rectangle.width - 1,
row_rectangle.height - 1);
gdk_gc_set_clip_rectangle (clist->xor_gc, NULL);
}
}
}
static void
draw_rows (GtkCList *clist, GdkRectangle *area)
{
GList *list;
gint i;
gint first_row;
gint last_row;
if (clist->row_height == 0 || !GTK_WIDGET_DRAWABLE (clist))
return;
first_row = ROW_FROM_YPIXEL (clist, area->y);
last_row = ROW_FROM_YPIXEL (clist, area->y + area->height);
/* this is a small special case which exposes the bottom cell line
* on the last row -- it might go away if I change the wall the cell
* spacings are drawn
*/
if (clist->rows == first_row)
first_row--;
list = ROW_ELEMENT (clist, first_row);
for (i = first_row; i <= last_row ; i++) {
if (list == NULL)
break;
GTK_CLIST_CLASS ((GTK_OBJECT (clist))->klass)->draw_row (clist, area, i,
list->data);
list = list->next;
}
}
static void
gtk_flist_track_new_column_width (GtkCList *clist, int column, int new_width)
{
GtkFList *flist;
GdkRectangle title_redraw_area;
flist = GTK_FLIST (clist);
/* pin new_width to min and max values */
if (new_width < MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width))
new_width = MAX (COLUMN_MIN_WIDTH, clist->column[column].min_width);
if (clist->column[column].max_width >= 0 &&
new_width > clist->column[column].max_width)
new_width = clist->column[column].max_width;
/* check to see if the pinned value is still different */
if (clist->column[column].width == new_width)
return;
/* set the new width */
clist->column[column].width = new_width;
clist->column[column].width_set = TRUE;
size_allocate_columns (clist, TRUE);
size_allocate_title_buttons (clist);
/* redraw the invalid columns */
if (clist->freeze_count == 0) {
GdkRectangle area;
area = clist->column_title_area;
area.x = clist->column[column].area.x;
area.height += clist->clist_window_height;
draw_rows (clist, &area);
}
/* draw the column title
* ToDo:
* fix this up
*/
title_redraw_area.x = GTK_WIDGET (flist->details->title)->allocation.x;
title_redraw_area.y = GTK_WIDGET (flist->details->title)->allocation.y;
title_redraw_area.width = GTK_WIDGET (flist->details->title)->allocation.width;
title_redraw_area.height = GTK_WIDGET (flist->details->title)->allocation.height;
(GTK_WIDGET_CLASS (NAUTILUS_KLASS (flist->details->title)))->
draw (flist->details->title, &title_redraw_area);
}
/* Our handler for motion_notify events. We override all of GtkCList's broken
* behavior.
*/
@ -476,6 +1210,45 @@ gtk_flist_motion (GtkWidget *widget, GdkEventMotion *event)
return TRUE;
}
void
gtk_flist_column_resize_track_start (GtkWidget *widget, int column)
{
GtkCList *clist;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
clist->drag_pos = column;
}
void
gtk_flist_column_resize_track (GtkWidget *widget, int column)
{
GtkCList *clist;
int x;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
clist = GTK_CLIST (widget);
gtk_widget_get_pointer (widget, &x, NULL);
gtk_flist_track_new_column_width (clist, column,
new_column_width (clist, column, &x));
}
void
gtk_flist_column_resize_track_end (GtkWidget *widget, int column)
{
GtkCList *clist;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_FLIST (widget));
clist->drag_pos = -1;
}
/* Our handler for key_press and key_release events. We do nothing, and we do
* this to avoid GtkCList's broken behavior.
*/
@ -578,7 +1351,17 @@ gtk_flist_new_with_titles (int columns, char **titles)
GtkFList *flist;
flist = gtk_type_new (gtk_flist_get_type ());
gtk_clist_construct (GTK_CLIST (flist), columns, titles);
gtk_clist_construct (GTK_CLIST (flist), columns, NULL);
if (titles) {
GtkCList *clist;
int index;
clist = GTK_CLIST(flist);
for (index = 0; index < columns; index++) {
clist->column[index].title = g_strdup (titles[index]);
}
}
gtk_clist_set_selection_mode (GTK_CLIST (flist),
GTK_SELECTION_MULTIPLE);

View file

@ -36,7 +36,7 @@ struct _GtkFListClass {
GtkCListClass parent_class;
/* Signal: invoke the popup menu for selected items */
void (* context_click_selection) (GtkFList *flist, gint row);
void (* context_click_selection) (GtkFList *flist, int row);
/* Signal: invoke the popup menu for empty areas */
void (* context_click_background) (GtkFList *flist);
@ -45,10 +45,15 @@ struct _GtkFListClass {
void (* activate) (GtkFList *flist, gpointer data);
/* Signal: initiate a drag and drop operation */
void (* start_drag) (GtkFList *flist, gint button, GdkEvent *event);
void (* start_drag) (GtkFList *flist, int button, GdkEvent *event);
/* Signal: selection has changed */
void (* selection_changed) (GtkFList *flist);
/* column resize tracking calls */
void (* column_resize_track_start) (GtkWidget *widget, int column);
void (* column_resize_track) (GtkWidget *widget, int column);
void (* column_resize_track_end) (GtkWidget *widget, int column);
};

View file

@ -148,6 +148,47 @@ nautilus_fill_rectangle_with_gradient (GdkDrawable *drawable,
}
}
/**
* nautilus_rectangle_contains:
* @rectangle: Rectangle possibly containing a point.
* @x: X coordinate of a point.
* @y: Y coordinate of a point.
*
* Retun TRUE if point is contained inside a rectangle
*/
gboolean
nautilus_rectangle_contains (const GdkRectangle *rectangle,
int x,
int y)
{
g_return_val_if_fail (rectangle != NULL, FALSE);
return rectangle->x <= x && rectangle->x + rectangle->width >= x
&& rectangle->y <= y && rectangle->y + rectangle->height >= y;
}
/**
* nautilus_rectangle_inset:
* @rectangle: Rectangle we are insetting.
* @x: Horizontal ammount to inset by.
* @y: Vertical ammount to inset by.
*
* Inset a rectangle by a given horizontal and vertical ammount.
* Pass in negative inset values to grow the rectangle, positive to
* shrink it.
*/
void
nautilus_rectangle_inset (GdkRectangle *rectangle,
int x,
int y)
{
g_return_if_fail (rectangle != NULL);
rectangle->x += x;
rectangle->width -= 2 * x;
rectangle->y += y;
rectangle->height -= 2 * y;
}
/**
* nautilus_interpolate_color:
* @ratio: Place on line between colors to interpolate.

View file

@ -75,6 +75,15 @@ void nautilus_fill_rectangle_with_gradient (GdkDrawable *drawab
const GdkColor *end_color,
gboolean horizontal_gradient);
/* Misc GdkRectangle helper functions */
gboolean nautilus_rectangle_contains (const GdkRectangle *rectangle,
int x,
int y);
void nautilus_rectangle_inset (GdkRectangle *rectangle,
int x,
int y);
/* A basic operation we use for drawing gradients is interpolating two colors.*/
void nautilus_interpolate_color (gdouble ratio,
const GdkColor *start_color,

View file

@ -0,0 +1,763 @@
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
nautilus-list-column-title.c: List column title widget for interacting with list columns
Copyright (C) 2000 Eazel, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
Authors: Pavel Cisler <pavel@eazel.com>
*/
#include "nautilus-list-column-title.h"
#include "nautilus-gtk-macros.h"
#include "nautilus-gdk-extensions.h"
/* for now we need to know about GtkFList */
#include "gtkflist.h"
#include <gdk/gdk.h>
#include <gtk/gtkclist.h>
#include <gtk/gtkmain.h>
#include <libgnomeui/gnome-pixmap.h>
/* these are from GtkCList, for now we need to copy them here
* eventually the target list should be able to describe the values
*/
enum {
/* this defines the base grid spacing */
CELL_SPACING = 1,
/* added the horizontal space at the beginning and end of a row */
COLUMN_INSET = 3,
/* from GtkButton */
CHILD_SPACING = 1,
/* the width of the column resize windows */
DRAG_WIDTH = 6
};
static char * down_xpm[] = {
"6 5 2 1",
" c None",
". c #000000",
"......",
" ",
" .... ",
" ",
" .. "
};
static char * up_xpm[] = {
"6 5 2 1",
" c None",
". c #000000",
" .. ",
" ",
" .... ",
" ",
"......"
};
struct NautilusListColumnTitleDetails
{
/* gc for blitting sort order pixmaps, lazily allocated */
GdkGC *copy_area_gc;
/* sort order indicator pixmaps, lazily allocated */
GnomePixmap *up_indicator;
GnomePixmap *down_indicator;
int tracking_column_resize;
/* index of the column we are currently tracking or -1 */
int tracking_column_prelight;
/* index of the column we are currently rolling over or -1 */
int tracking_column_press;
/* index of the column we are currently pressing or -1 */
int last_tracking_x;
/* last horizontal track point so we can only resize when needed */
gboolean resize_cursor_on;
};
static void nautilus_list_column_title_initialize_class (gpointer klass);
static void nautilus_list_column_title_initialize (gpointer object, gpointer klass);
static void nautilus_list_column_title_paint (GtkWidget *widget, GdkRectangle *area);
static void nautilus_list_column_title_draw (GtkWidget *widget, GdkRectangle *box);
static void nautilus_list_column_title_buffered_draw (GtkWidget *widget);
static gboolean nautilus_list_column_title_expose (GtkWidget *widget, GdkEventExpose *event);
static void nautilus_list_column_title_realize (GtkWidget *widget);
static void nautilus_list_column_title_finalize (GtkObject *object);
static void nautilus_list_column_title_request (GtkWidget *widget, GtkRequisition *requisition);
static gboolean nautilus_list_column_title_motion (GtkWidget *widget, GdkEventMotion *event);
static gboolean nautilus_list_column_title_leave (GtkWidget *widget, GdkEventCrossing *event);
static gboolean nautilus_list_column_title_button_press (GtkWidget *widget, GdkEventButton *event);
static gboolean nautilus_list_column_title_button_release (GtkWidget *widget, GdkEventButton *event);
NAUTILUS_DEFINE_CLASS_BOILERPLATE (NautilusListColumnTitle, nautilus_list_column_title, GTK_TYPE_BIN)
/* generates nautilus_list_column_title_get_type */
static void
nautilus_list_column_title_initialize_class (gpointer klass)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
object_class = GTK_OBJECT_CLASS (klass);
widget_class = GTK_WIDGET_CLASS (klass);
object_class->finalize = nautilus_list_column_title_finalize;
widget_class->draw = nautilus_list_column_title_draw;
widget_class->expose_event = nautilus_list_column_title_expose;
widget_class->realize = nautilus_list_column_title_realize;
widget_class->size_request = nautilus_list_column_title_request;
widget_class->motion_notify_event = nautilus_list_column_title_motion;
widget_class->leave_notify_event = nautilus_list_column_title_leave;
widget_class->button_press_event = nautilus_list_column_title_button_press;
widget_class->button_release_event = nautilus_list_column_title_button_release;
}
NautilusListColumnTitle *
nautilus_list_column_title_new (void)
{
return gtk_type_new (nautilus_list_column_title_get_type ());
}
static void
nautilus_list_column_title_initialize (gpointer object, gpointer klass)
{
NautilusListColumnTitle *column_title;
column_title = NAUTILUS_LIST_COLUMN_TITLE(object);
column_title->details = g_new0 (NautilusListColumnTitleDetails, 1);
/* copy_gc, up/down indicators get allocated lazily when needed */
column_title->details->copy_area_gc = NULL;
column_title->details->up_indicator = NULL;
column_title->details->down_indicator = NULL;
column_title->details->resize_cursor_on = FALSE;
column_title->details->tracking_column_resize = -1;
column_title->details->tracking_column_prelight = -1;
column_title->details->tracking_column_press = -1;
column_title->details->last_tracking_x = -1;
GTK_WIDGET_UNSET_FLAGS (object, GTK_NO_WINDOW);
}
static void
nautilus_list_column_title_realize (GtkWidget *widget)
{
GdkWindowAttr attributes;
int attributes_mask;
g_assert (widget != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
/* ask for expose events */
attributes.window_type = GDK_WINDOW_CHILD;
attributes.x = widget->allocation.x + GTK_CONTAINER (widget)->border_width;
attributes.y = widget->allocation.y + GTK_CONTAINER (widget)->border_width;
attributes.width = widget->allocation.width - GTK_CONTAINER (widget)->border_width * 2;
attributes.height = widget->allocation.height - GTK_CONTAINER (widget)->border_width * 2;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual (widget);
attributes.colormap = gtk_widget_get_colormap (widget);
attributes.event_mask = gtk_widget_get_events (widget);
attributes.event_mask |= GDK_EXPOSURE_MASK
| GDK_ENTER_NOTIFY_MASK
| GDK_LEAVE_NOTIFY_MASK
| GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
| GDK_POINTER_MOTION_MASK
| GDK_POINTER_MOTION_HINT_MASK
| GDK_KEY_PRESS_MASK;
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
/* give ourselves a background window */
widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
&attributes, attributes_mask);
gdk_window_set_user_data (widget->window, widget);
widget->style = gtk_style_attach (widget->style, widget->window);
gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
}
static void
nautilus_list_column_title_finalize (GtkObject *object)
{
NautilusListColumnTitle *column_title;
column_title = NAUTILUS_LIST_COLUMN_TITLE(object);
if (column_title->details->up_indicator != NULL)
gtk_widget_destroy (GTK_WIDGET (column_title->details->up_indicator));
if (column_title->details->down_indicator != NULL)
gtk_widget_destroy (GTK_WIDGET (column_title->details->down_indicator));
/* FIXME:
* figure out if we need to delete the copy_area_gc here
*/
g_free (column_title->details);
NAUTILUS_CALL_PARENT_CLASS (GTK_OBJECT_CLASS, finalize, (object));
}
static void
nautilus_list_column_title_request (GtkWidget *widget, GtkRequisition *requisition)
{
/* size requisition: make sure we have at least a minimal height */
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
g_assert (requisition != NULL);
requisition->width = (GTK_CONTAINER (widget)->border_width + CHILD_SPACING +
widget->style->klass->xthickness) * 2;
requisition->height = (GTK_CONTAINER (widget)->border_width + CHILD_SPACING +
widget->style->klass->ythickness) * 2;
if (GTK_BIN (widget)->child && GTK_WIDGET_VISIBLE (GTK_BIN (widget)->child)) {
GtkRequisition child_requisition;
gtk_widget_size_request (GTK_BIN (widget)->child, &child_requisition);
requisition->width += child_requisition.width;
requisition->height += child_requisition.height;
requisition->height = MIN (requisition->height, 10);
}
}
static const char *
get_column_label_at (GtkWidget *column_title, int index)
{
GtkCList *parent_clist;
parent_clist = GTK_CLIST (column_title->parent);
return parent_clist->column[index].title;
}
static void
get_column_frame_at(GtkWidget *column_title, int index, GdkRectangle *result)
{
GtkCList *parent_clist;
parent_clist = GTK_CLIST (column_title->parent);
*result = parent_clist->column_title_area;
result->x = parent_clist->hoffset + parent_clist->column[index].area.x - COLUMN_INSET;
result->y = 0;
result->width = parent_clist->column[index].area.width
+ CELL_SPACING + 2 * COLUMN_INSET - 1;
}
static GnomePixmap *
get_sort_indicator (GtkWidget *widget, gboolean ascending)
{
/* return the sort order pixmap for a given sort direction
* allocate the pixmap first time around
*/
NautilusListColumnTitle *column_title;
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
if (ascending) {
if (column_title->details->up_indicator == NULL) {
column_title->details->up_indicator =
GNOME_PIXMAP (gnome_pixmap_new_from_xpm_d (up_xpm));
}
return column_title->details->up_indicator;
} else {
if (column_title->details->down_indicator == NULL) {
column_title->details->down_indicator =
GNOME_PIXMAP (gnome_pixmap_new_from_xpm_d (down_xpm));
}
return column_title->details->down_indicator;
}
}
/* FIXME:
* Some of these magic numbers could be replaced with some more dynamic values
*/
enum {
CELL_TITLE_INSET = 6,
TITLE_BASELINE_OFFSET = 6,
SORT_ORDER_INDICATOR_WIDTH = 10
};
static void
nautilus_list_column_title_paint (GtkWidget *widget, GdkRectangle *area)
{
NautilusListColumnTitle *column_title;
GtkCList *parent_clist;
int index;
g_assert (GTK_CLIST (widget->parent) != NULL);
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
parent_clist = GTK_CLIST (widget->parent);
for (index = 0; index < parent_clist->columns; index++) {
GdkRectangle cell_rectangle;
GdkRectangle cell_redraw_area;
const char *cell_label;
int x_offset;
GnomePixmap *sort_indicator;
gboolean right_justified;
sort_indicator = NULL;
right_justified = (parent_clist->column[index].justification == GTK_JUSTIFY_RIGHT);
/* pick the ascending/descending sort indicator if needed */
if (index == parent_clist->sort_column) {
sort_indicator = get_sort_indicator (widget,
parent_clist->sort_type == GTK_SORT_ASCENDING);
}
get_column_frame_at (widget, index, &cell_rectangle);
gdk_rectangle_intersect (&cell_rectangle, area, &cell_redraw_area);
if (cell_redraw_area.width == 0 || cell_redraw_area.height == 0) {
/* no work, go on to the next */
continue;
}
cell_label = get_column_label_at (widget, index);
/* FIXME:
* add support for center justification
*/
if (right_justified) {
x_offset = cell_rectangle.x + cell_rectangle.width - CELL_TITLE_INSET;
} else {
x_offset = cell_rectangle.x + CELL_TITLE_INSET;
}
/* Paint the column title tiles as rectangles using "menu" style
* buttons as used by GtkCList produce round corners in some themes.
* Eventually we might consider having a separate style for column titles
*/
gtk_paint_box (widget->style, widget->window,
column_title->details->tracking_column_prelight == index ?
GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
column_title->details->tracking_column_press == index ?
GTK_SHADOW_IN : GTK_SHADOW_OUT,
area, widget, "menu",
cell_rectangle.x, cell_rectangle.y,
cell_rectangle.width, cell_rectangle.height);
/* Draw the sort indicator if needed */
if (sort_indicator != NULL) {
int y_offset = TITLE_BASELINE_OFFSET + 2;
if (right_justified) {
x_offset -= SORT_ORDER_INDICATOR_WIDTH;
}
/* allocate the sort indicator copy gc first time around */
if (column_title->details->copy_area_gc == NULL) {
column_title->details->copy_area_gc = gdk_gc_new (widget->window);
gdk_gc_set_function (column_title->details->copy_area_gc, GDK_COPY);
}
/* move the pixmap clip mask and origin to the right spot in the gc */
gdk_gc_set_clip_mask (column_title->details->copy_area_gc,
sort_indicator->mask);
gdk_gc_set_clip_origin (column_title->details->copy_area_gc, x_offset, y_offset);
gdk_draw_pixmap (widget->window, column_title->details->copy_area_gc,
sort_indicator->pixmap, 0, 0, x_offset, y_offset,
-1, -1);
if (!right_justified) {
x_offset += SORT_ORDER_INDICATOR_WIDTH;
}
}
if (cell_label) {
/* clip a little more than the cell rectangle to
* not have the text draw over the cell broder
* (this might no longer be needed when the
* title gets truncated properly, as opposed to
* getting chopped of
*/
nautilus_rectangle_inset (&cell_redraw_area, 2, 2);
if (right_justified) {
x_offset -= gdk_string_width (widget->style->font, cell_label) + 4;
}
gtk_paint_string (widget->style, widget->window, GTK_STATE_NORMAL,
&cell_redraw_area, widget, "label",
x_offset,
cell_rectangle.y + cell_rectangle.height - TITLE_BASELINE_OFFSET,
cell_label);
}
}
}
static void
nautilus_list_column_title_draw (GtkWidget *widget, GdkRectangle *area)
{
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
g_assert (area != NULL);
if (!GTK_WIDGET_DRAWABLE (widget)) {
return;
}
nautilus_list_column_title_paint (widget, area);
}
static void
nautilus_list_column_title_buffered_draw (GtkWidget *widget)
{
/* draw using an offscreen bitmap
* for now just draw directly
* add code here to lazily allocate an offscreen and pass it to the paint call
* without the offscreen you can now see a funny artifact when part of the
* old column border does not get erased properly when shrinking a column
*/
GdkRectangle redraw_area;
redraw_area.x = widget->allocation.x;
redraw_area.y = widget->allocation.y;
redraw_area.width = widget->allocation.width;
redraw_area.height = widget->allocation.height;
nautilus_list_column_title_draw (widget, &redraw_area);
}
static gboolean
nautilus_list_column_title_expose (GtkWidget *widget, GdkEventExpose *event)
{
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
g_assert (event != NULL);
if (!GTK_WIDGET_DRAWABLE (widget)) {
return FALSE;
}
nautilus_list_column_title_paint (widget, &event->area);
return FALSE;
}
static int
in_column_rect (GtkWidget *widget, int x, int y)
{
/* return the index of the column we hit or -1 */
GtkCList *parent_clist;
int index;
parent_clist = GTK_CLIST (widget->parent);
for (index = 0; index < parent_clist->columns; index++) {
/* hit testing for column resizing */
GdkRectangle cell_rectangle;
get_column_frame_at (widget, index, &cell_rectangle);
/* inset by a pixel so that you have to move past the border
* to be considered inside the rect
* nautilus_list_column_title_leave depends on this
*/
nautilus_rectangle_inset (&cell_rectangle, 1, 0);
if (nautilus_rectangle_contains (&cell_rectangle, x, y))
return index;
}
return -1;
}
static int
in_resize_rect (GtkWidget *widget, int x, int y)
{
/* return the index of the resize rect of a column we hit or -1 */
GtkCList *parent_clist;
int index;
parent_clist = GTK_CLIST (widget->parent);
for (index = 0; index < parent_clist->columns; index++) {
/* hit testing for column resizing */
GdkRectangle resize_rectangle;
get_column_frame_at (widget, index, &resize_rectangle);
nautilus_rectangle_inset (&resize_rectangle, 1, 0);
resize_rectangle.x = resize_rectangle.x + resize_rectangle.width - DRAG_WIDTH / 2;
resize_rectangle.width = DRAG_WIDTH;
if (nautilus_rectangle_contains (&resize_rectangle, x, y))
return index;
}
return -1;
}
static void
show_hide_resize_cursor_if_needed (GtkWidget *widget, gboolean on)
{
NautilusListColumnTitle *column_title;
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
if (on == column_title->details->resize_cursor_on)
/* already set right */
return;
if (on) {
/* switch to a resize cursor */
GdkCursor *cursor;
cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
gdk_window_set_cursor (widget->window, cursor);
gdk_cursor_destroy (cursor);
} else
/* restore to old cursor */
gdk_window_set_cursor (widget->window, NULL);
column_title->details->resize_cursor_on = on;
}
static gboolean
track_prelight (GtkWidget *widget, int mouse_x, int mouse_y)
{
NautilusListColumnTitle *column_title;
int over_column;
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
/* see if we need to update the prelight state of a column */
over_column = in_column_rect (widget, mouse_x, mouse_y);
if (column_title->details->tracking_column_resize != -1) {
/* resizing a column, don't prelight */
over_column = -1;
}
if (column_title->details->tracking_column_press != -1) {
/* pressing a column, don't prelight */
over_column = -1;
}
if (column_title->details->tracking_column_prelight == over_column) {
/* no change */
return FALSE;
}
/* update state and tell callers to redraw */
column_title->details->tracking_column_prelight = over_column;
return TRUE;
}
static gboolean
nautilus_list_column_title_motion (GtkWidget *widget, GdkEventMotion *event)
{
NautilusListColumnTitle *column_title;
GtkWidget *parent_list;
int mouse_x, mouse_y;
g_assert (GTK_FLIST (widget->parent) != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
parent_list = GTK_WIDGET (widget->parent);
gdk_window_get_pointer (widget->window, &mouse_x, &mouse_y, NULL);
if (column_title->details->tracking_column_resize != -1) {
/* we are currently tracking a column */
if (column_title->details->last_tracking_x != mouse_x) {
/* mouse did move horizontally since last time */
column_title->details->last_tracking_x = mouse_x;
(GTK_FLIST_CLASS (NAUTILUS_KLASS (parent_list)))->
column_resize_track (parent_list,
column_title->details->tracking_column_resize);
}
} else {
/* make sure we are showing the right cursor */
show_hide_resize_cursor_if_needed (widget,
in_resize_rect (widget, mouse_x, mouse_y) != -1);
}
/* see if we need to update the prelight state of a column */
if (track_prelight (widget, mouse_x, mouse_y)) {
nautilus_list_column_title_buffered_draw (widget);
}
return TRUE;
}
static gboolean
nautilus_list_column_title_leave (GtkWidget *widget, GdkEventCrossing *event)
{
g_assert (GTK_FLIST (widget->parent) != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
/* see if we need to update the prelight state of a column */
if (track_prelight (widget, (int)event->x, (int)event->y)) {
gtk_widget_set_state (widget, GTK_STATE_NORMAL);
nautilus_list_column_title_buffered_draw (widget);
}
return TRUE;
}
static gboolean
nautilus_list_column_title_button_press (GtkWidget *widget, GdkEventButton *event)
{
NautilusListColumnTitle *column_title;
GtkWidget *parent_list;
g_assert (event != NULL);
g_assert (GTK_FLIST (widget->parent) != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
g_assert (NAUTILUS_LIST_COLUMN_TITLE(widget)->details->tracking_column_resize == -1);
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
parent_list = GTK_WIDGET (widget->parent);
if (event->type == GDK_BUTTON_PRESS) {
int resized_column;
int clicked_column;
resized_column = in_resize_rect (widget, (int)event->x, (int)event->y);
clicked_column = in_column_rect (widget, (int)event->x, (int)event->y);
if (resized_column != -1) {
GdkCursor *cursor;
int grab_result;
/* during the drag, use the resize cursor */
cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
/* grab the pointer events so that we get them even when
* the mouse tracks out of the widget window
*/
grab_result = gdk_pointer_grab (widget->window, FALSE,
GDK_POINTER_MOTION_HINT_MASK
| GDK_BUTTON1_MOTION_MASK
| GDK_BUTTON_RELEASE_MASK,
NULL, cursor, event->time);
gdk_cursor_destroy (cursor);
if (grab_result != 0)
/* failed to grab the pointer, give up
*
* The grab results are not very well documented
* looks like they may be Success, GrabSuccess, AlreadyGrabbed
* or anything else the low level X calls in gdk_pointer_grab
* decide to return.
*/
return FALSE;
/* set up new state */
column_title->details->tracking_column_resize = resized_column;
column_title->details->tracking_column_prelight = -1;
/* FIXME:
* use a "resized" state here ?
*/
gtk_widget_set_state (widget, GTK_STATE_NORMAL);
/* start column resize tracking */
(GTK_FLIST_CLASS (NAUTILUS_KLASS (parent_list)))->
column_resize_track_start (parent_list, resized_column);
return FALSE;
}
if (clicked_column != -1) {
/* clicked a column, draw the pressed column title */
column_title->details->tracking_column_prelight = -1;
column_title->details->tracking_column_press = clicked_column;
gtk_widget_set_state (widget, GTK_STATE_ACTIVE);
/* FIXME:
* buffered draw may be better here
*/
gtk_widget_queue_draw (widget);
}
}
return FALSE;
}
static gboolean
nautilus_list_column_title_button_release (GtkWidget *widget, GdkEventButton *event)
{
NautilusListColumnTitle *column_title;
GtkWidget *parent_list;
g_assert (event != NULL);
g_assert (GTK_FLIST (widget->parent) != NULL);
g_assert (NAUTILUS_IS_LIST_COLUMN_TITLE (widget));
column_title = NAUTILUS_LIST_COLUMN_TITLE(widget);
parent_list = GTK_WIDGET (widget->parent);
if (column_title->details->tracking_column_resize != -1) {
/* let go of all the pointer events */
if (gdk_pointer_is_grabbed ())
gdk_pointer_ungrab (event->time);
/* end column resize tracking */
(GTK_FLIST_CLASS (NAUTILUS_KLASS (parent_list)))->
column_resize_track_end (parent_list,
column_title->details->tracking_column_resize);
column_title->details->tracking_column_resize = -1;
} else if (column_title->details->tracking_column_press != -1) {
/* column title press -- change the sort order */
gtk_signal_emit_by_name (GTK_OBJECT (parent_list), "click_column",
column_title->details->tracking_column_press);
/* end press tracking */
column_title->details->tracking_column_press = -1;
}
track_prelight (widget, (int)event->x, (int)event->y);
gtk_widget_set_state (widget,
column_title->details->tracking_column_prelight != -1 ?
GTK_STATE_PRELIGHT : GTK_STATE_NORMAL);
/* FIXME:
* buffered draw may be better here
*/
gtk_widget_queue_draw (widget);
return FALSE;
}

View file

@ -0,0 +1,73 @@
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
nautilus-list-column-title.h: List column title widget for interacting with list columns
Copyright (C) 2000 Eazel, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
Authors: Pavel Cisler <pavel@eazel.com>
*/
#ifndef __NAUTILUS_LIST_COLUMN_TITLE__
#define __NAUTILUS_LIST_COLUMN_TITLE__
#include <gdk/gdktypes.h>
#include <gtk/gtkwidget.h>
#include <gtk/gtkbin.h>
#include <gtk/gtkenums.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define NAUTILUS_TYPE_LIST_COLUMN_TITLE \
(nautilus_list_column_title_get_type ())
#define NAUTILUS_LIST_COLUMN_TITLE(obj) \
(GTK_CHECK_CAST ((obj), NAUTILUS_TYPE_LIST_COLUMN_TITLE, NautilusListColumnTitle))
#define NAUTILUS_LIST_COLUMN_TITLE_CLASS(klass) \
(GTK_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_LIST_COLUMN_TITLE, NautilusListColumnTitleClass))
#define NAUTILUS_IS_LIST_COLUMN_TITLE(obj) \
(GTK_CHECK_TYPE ((obj), NAUTILUS_TYPE_LIST_COLUMN_TITLE))
#define NAUTILUS_IS_LIST_COLUMN_TITLE_CLASS(klass) \
(GTK_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_LIST_COLUMN_TITLE))
typedef struct NautilusListColumnTitle NautilusListColumnTitle;
typedef struct NautilusListColumnTitleClass NautilusListColumnTitleClass;
typedef struct NautilusListColumnTitleDetails NautilusListColumnTitleDetails;
struct NautilusListColumnTitle
{
GtkBin bin;
NautilusListColumnTitleDetails *details;
};
struct NautilusListColumnTitleClass
{
GtkBinClass parent_class;
};
GtkType nautilus_list_column_title_get_type (void);
NautilusListColumnTitle *nautilus_list_column_title_new (void);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __NAUTILUS_LIST_COLUMN_TITLE__ */

View file

@ -126,41 +126,11 @@ const char * get_attribute_from_column (int
int get_column_from_attribute (const char *value);
int get_sort_column_from_attribute (const char *value);
static GtkFList * get_flist (FMDirectoryViewList *list_view);
static GtkWidget * get_sort_indicator (GtkFList *flist,
int column,
gboolean reverse);
static void hide_sort_indicator (GtkFList *flist,
int column);
static void install_icon (FMDirectoryViewList *list_view,
guint row);
static void show_sort_indicator (GtkFList *flist,
int column,
gboolean sort_reversed);
static int sort_criterion_from_column (int column);
static void update_icons (FMDirectoryViewList *list_view);
static char * down_xpm[] = {
"6 5 2 1",
" c None",
". c #000000",
"......",
" ",
" .... ",
" ",
" .. "
};
static char * up_xpm[] = {
"6 5 2 1",
" c None",
". c #000000",
" .. ",
" ",
" .... ",
" ",
"......"
};
NAUTILUS_DEFINE_CLASS_BOILERPLATE (FMDirectoryViewList, fm_directory_view_list, FM_TYPE_DIRECTORY_VIEW);
@ -315,7 +285,12 @@ create_flist (FMDirectoryViewList *list_view)
{
GtkFList *flist;
GtkCList *clist;
/* title setup should allow for columns not being resizable at all,
* justification, editable or not, type/format,
* not being usable as a sort order criteria, etc.
* for now just set up name, min, max and current width
*/
char *titles[] = {
NULL,
_("Name"),
@ -323,6 +298,7 @@ create_flist (FMDirectoryViewList *list_view)
_("Type"),
_("Date Modified"),
};
guint widths[] = {
fm_directory_view_list_get_icon_size (list_view), /* Icon */
130, /* Name */
@ -330,6 +306,23 @@ create_flist (FMDirectoryViewList *list_view)
95, /* Type */
100, /* Modified */
};
guint min_widths[] = {
fm_directory_view_list_get_icon_size (list_view), /* Icon */
30, /* Name */
20, /* Size */
20, /* Type */
30, /* Modified */
};
guint max_widths[] = {
fm_directory_view_list_get_icon_size (list_view), /* Icon */
300, /* Name */
80, /* Size */
200, /* Type */
200, /* Modified */
};
int i;
g_return_val_if_fail (FM_IS_DIRECTORY_VIEW_LIST (list_view), NULL);
@ -339,54 +332,28 @@ create_flist (FMDirectoryViewList *list_view)
for (i = 0; i < LIST_VIEW_COLUMN_COUNT; ++i)
{
GtkWidget *hbox;
GtkWidget *label;
GtkWidget *sort_up_indicator;
GtkWidget *sort_down_indicator;
gboolean right_justified;
right_justified = (i == LIST_VIEW_COLUMN_SIZE);
gtk_clist_set_column_width (clist, i, widths[i]);
gtk_clist_set_column_min_width (clist, i, min_widths[i]);
gtk_clist_set_column_max_width (clist, i, max_widths[i]);
/* Column header button contains three views, a title,
* a "sort downward" indicator, and a "sort upward" indicator.
* Only one sort indicator (for all columns) is shown at once.
*/
hbox = gtk_hbox_new (FALSE, GNOME_PAD_SMALL);
gtk_widget_show (GTK_WIDGET (hbox));
label = gtk_label_new (titles[i]);
gtk_widget_show (GTK_WIDGET (label));
/* Sort indicators are initially hidden. They're marked with
* special data so they can be located later in each column.
*/
sort_up_indicator = gnome_pixmap_new_from_xpm_d (up_xpm);
gtk_object_set_data(GTK_OBJECT(sort_up_indicator),
SORT_INDICATOR_KEY,
GINT_TO_POINTER (UP_INDICATOR_VALUE));
sort_down_indicator = gnome_pixmap_new_from_xpm_d (down_xpm);
gtk_object_set_data(GTK_OBJECT(sort_down_indicator),
SORT_INDICATOR_KEY,
GINT_TO_POINTER (DOWN_INDICATOR_VALUE));
if (right_justified)
{
gtk_box_pack_start (GTK_BOX (hbox), sort_up_indicator, FALSE, FALSE, GNOME_PAD);
gtk_box_pack_start (GTK_BOX (hbox), sort_down_indicator, FALSE, FALSE, GNOME_PAD);
gtk_box_pack_end (GTK_BOX (hbox), label, FALSE, FALSE, 0);
if (right_justified) {
/* hack around a problem where gtk_clist_set_column_justification
* crashes if there is a column title but now
* column button (it should really be checking if it has a button instead)
* this is an easy, dirty fix for now, will get straightened out
* with a replacement list view (alternatively, we'd fix this in GtkCList)
*/
char *tmp_title = clist->column[i].title;
clist->column[i].title = NULL;
gtk_clist_set_column_justification (clist, i, GTK_JUSTIFY_RIGHT);
}
else
{
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
gtk_box_pack_end (GTK_BOX (hbox), sort_up_indicator, FALSE, FALSE, GNOME_PAD);
gtk_box_pack_end (GTK_BOX (hbox), sort_down_indicator, FALSE, FALSE, GNOME_PAD);
clist->column[i].title = tmp_title;
}
gtk_clist_set_column_widget (clist, i, hbox);
}
gtk_clist_set_auto_sort (clist, TRUE);
@ -771,9 +738,7 @@ fm_directory_view_list_sort_items (FMDirectoryViewList *list_view,
flist = get_flist (list_view);
clist = GTK_CLIST (flist);
hide_sort_indicator (flist, list_view->details->sort_column);
show_sort_indicator (flist, column, reversed);
list_view->details->sort_column = column;
if (reversed != list_view->details->sort_reversed)
@ -892,60 +857,6 @@ get_sort_column_from_attribute (const char *value)
return result;
}
static GtkWidget *
get_sort_indicator (GtkFList *flist, int column, gboolean reverse)
{
GtkWidget *column_widget;
GtkWidget *result;
GList *children;
GList *iter;
g_return_val_if_fail (GTK_IS_FLIST (flist), NULL);
g_return_val_if_fail (column >= 0, NULL);
column_widget = gtk_clist_get_column_widget (GTK_CLIST (flist), column);
g_assert (GTK_IS_HBOX (column_widget));
children = gtk_container_children (GTK_CONTAINER (column_widget));
iter = children;
result = NULL;
while (iter != NULL)
{
int indicator_int = GPOINTER_TO_INT (
gtk_object_get_data (GTK_OBJECT (iter->data), SORT_INDICATOR_KEY));
if ((reverse && indicator_int == DOWN_INDICATOR_VALUE) ||
(!reverse && indicator_int == UP_INDICATOR_VALUE))
{
result = GTK_WIDGET (iter->data);
break;
}
iter = g_list_next(iter);
}
g_assert (result != NULL);
g_list_free (children);
return result;
}
static void
hide_sort_indicator (GtkFList *flist, int column)
{
g_return_if_fail (GTK_IS_FLIST (flist));
if (column == LIST_VIEW_COLUMN_NONE)
return;
gtk_widget_hide (get_sort_indicator (flist, column, FALSE));
gtk_widget_hide (get_sort_indicator (flist, column, TRUE));
}
/**
* install_icon:
*
@ -976,17 +887,6 @@ install_icon (FMDirectoryViewList *list_view, guint row)
gtk_clist_set_pixmap (clist, row, LIST_VIEW_COLUMN_ICON, pixmap, bitmap);
}
static void
show_sort_indicator (GtkFList *flist, int column, gboolean sort_reversed)
{
g_return_if_fail (GTK_IS_FLIST (flist));
if (column == LIST_VIEW_COLUMN_NONE)
return;
gtk_widget_show (get_sort_indicator (flist, column, sort_reversed));
}
static int
sort_criterion_from_column (int column)
{