2005-04-29 Tor Lillqvist <tml@novell.com> Port to Windows, initial commit: * configure.in: Check for Win32, define Automake conditional OS_WIN32. Check for regexec() perhaps in separate -lregex, define REGEX_LIBS if so. Require glib-2.0 >= 2.6 (and thus also gtk+-2.0 >= 2.6) so that we can use the gstdio wrappers for full support of non-ASCII filenames on Win32. Don't use -D_REENTRANT on Win32, has ne special meaning. * gal.pc.in: Require gtk+-2.0 >= 2.6 also here for consistency. * gal-zip.in: New file, used to build zipfile format distribution of gal for Win32. * configure.in * Makefile.am: Add gal-zip(.in). * */Makefile.am * */*.c: Harmonize -I and #include conventions. (Of course, this hasn't anything to do with Windows porting as such, I just got carried away...) Use only -I$(top_srcdir). Use paths to gal headers staring with "gal", except for headers from the same directory as the .c file, which are included as such. Include all gal headers using doublequotes. Sort #includes and remove duplicates and obvious redundancies. Include config.h first without any HAVE_CONFIG_H, then system headers, then other GNOME lib headers, than gal's own headers. Just include gtk.h instead of separate gtk*.h headers. Don't include gi18n.h, include e-i18n.h to use e_gettext() consistently. * gal/Makefile.am: Use -no-undefined on Win32 so that libtool agrees to build a shared library. Because of the bidirectional dependency between libgal and libgal-a11y we can build libgal-a11y sanely as a shared library on Win32, so we don't install any separate libgal-a11y at all. So, on Win32, link the stuff that goes into libgal-a11y also into libgal. Link with REGEX_LIBS. * gal/a11y/Makefile.am: See above. Just build a dummy static libgal-a11y on Win32 (can't convince Automake not to build the library at all on one platform using Automake ifdef, apparently). Then (this is a gross hack) explicitly remove the library after installation in the install-data-local rule. * gal/e-table/Makefile.am * gal/e-table/e-table-config.c: Rename ETABLE_GLADEDIR to GAL_GLADEDIR for consistency. * gal/e-table/e-cell-date.c: No localtime_r() in Microsoft's C library, but its localtime() *is* thread-safe. * gal/e-table/e-cell-text.c * gal/e-table/e-cell-tree.c * gal/e-table/e-cell-vbox.c * gal/e-text/e-text.c * gal/widgets/e-unicode.c: Remove unnecessary inclusion of gdkx.h. * gal/e-table/e-cell-tree.c (ect_realize): Instead of the Xlib macro None (whose value is zero), use the corresponding zero-valued enums from the appropriate GDK type. * gal/e-table/e-table-config.c * gal/e-table/e-table-field-chooser.c * gal/menus/gal-define-views-dialog.c * gal/menus/gal-view-instance-save-as-dialog.c * gal/menus/gal-view-new-dialog.c * gal/widgets/e-categories-master-list-array.c * gal/widgets/e-categories-master-list-dialog.c * gal/widgets/e-categories.c: Use g_build_filename() to construct pathnames at run-time instead of compile-time. On Windows the macros GAL_GLADEDIR and GAL_IMAGESDIR expand to function calls, in order to support installing in a freely chosen location. * gal/e-table/e-table-item.c * gal/e-table/e-cell-vbox.c: Instrad of the Xlib GrabSuccess, use GDK_GRAB_SUCCESS (which has the same value). * gal/e-table/e-table-specification.c (e_table_specification_load_from_file) * gal/e-table/e-table.c (e_table_load_specification) * gal/e-table/e-tree-table-adapter.c (open_file) * gal/menus/gal-view-instance.c (load_current_view) * gal/menus/gal-view-instance.c (load_current_view): On Win32, convert filename to the locale character set before passing to xmlParseFile() which doesn't use UTF-8 filenames. Use gstdio wrappers. * gal/util/Makefile.am: Define GAL_PREFIX as $prefix. Define GAL_LOCALEDIR, GAL_GLADEDIR and GAL_IMAGESDIR also here for e-win32-reloc.c. Include e-win32-reloc.c on Win32. * gal/util/e-iconv.c (e_iconv_init): Use g_win32_getlocale() on Windows. * gal/util/e-util.c * gal/util/e-xml-utils.c: Use g_mkstemp() instead of non-portable mkstemp(). Use GLib pathname manipulation functions. Use gstdio wrappers. * gal/util/e-util-private.h: New file. Contains just Win32 bits for now that redefine the directory names from the Makefile as functions calls. * gal/util/e-win32-reloc.c: New file. Contains a minimal DllMain() and functions to support freely chosen installation location on Windows. * gal/util/e-xml-utils.c: No fsync() in the Microsoft C library. * gal/windgets/Makefile.am: Add -I$(top_srcdir)/gal for consistency with the sibling Makefile.am files. * gal/widgets/e-canvas.c: Instead of the Xlib AlreadyGrabbed, use GDK_GRAB_ALREADY_GRABBED. svn path=/trunk/; revision=29249
862 lines
22 KiB
C
862 lines
22 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
||
/*
|
||
* e-completion-view.c - A text completion selection widget
|
||
* Copyright 2000, 2001, Ximian, Inc.
|
||
*
|
||
* Authors:
|
||
* Miguel de Icaza <miguel@ximian.com>
|
||
* Adapted by Jon Trowbridge <trow@ximian.com>
|
||
*
|
||
* This library is free software; you can redistribute it and/or
|
||
* modify it under the terms of the GNU Library General Public
|
||
* License, version 2, as published by the Free Software Foundation.
|
||
*
|
||
* This library 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
|
||
* Library General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU Library General Public
|
||
* License along with this library; if not, write to the Free Software
|
||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||
* 02111-1307, USA.
|
||
*/
|
||
|
||
#include <config.h>
|
||
|
||
#include <math.h>
|
||
|
||
#include <gdk/gdkkeysyms.h>
|
||
|
||
#include "gal/e-table/e-table-scrolled.h"
|
||
#include "gal/e-table/e-table-simple.h"
|
||
#include "gal/util/e-i18n.h"
|
||
#include "gal/util/e-marshal.h"
|
||
|
||
#include "e-completion-view.h"
|
||
|
||
enum {
|
||
E_COMPLETION_VIEW_NONEMPTY,
|
||
E_COMPLETION_VIEW_ADDED,
|
||
E_COMPLETION_VIEW_FULL,
|
||
E_COMPLETION_VIEW_BROWSE,
|
||
E_COMPLETION_VIEW_UNBROWSE,
|
||
E_COMPLETION_VIEW_ACTIVATE,
|
||
E_COMPLETION_VIEW_LAST_SIGNAL
|
||
};
|
||
|
||
static guint e_completion_view_signals[E_COMPLETION_VIEW_LAST_SIGNAL] = { 0 };
|
||
|
||
static void e_completion_view_disconnect (ECompletionView *cv);
|
||
static ETable *e_completion_view_table (ECompletionView *cv);
|
||
static void e_completion_view_clear_choices (ECompletionView *cv);
|
||
static void e_completion_view_set_cursor_row (ECompletionView *cv, gint r);
|
||
static void e_completion_view_select (ECompletionView *cv, gint r);
|
||
|
||
static gint e_completion_view_key_press_handler (GtkWidget *w, GdkEventKey *key_event, gpointer user_data);
|
||
|
||
static void e_completion_view_class_init (ECompletionViewClass *klass);
|
||
static void e_completion_view_init (ECompletionView *completion);
|
||
static void e_completion_view_dispose (GObject *object);
|
||
|
||
#define PARENT_TYPE GTK_TYPE_EVENT_BOX
|
||
static GtkObjectClass *parent_class;
|
||
|
||
|
||
|
||
static gint
|
||
e_completion_view_local_key_press_handler (GtkWidget *w, GdkEventKey *ev)
|
||
{
|
||
return e_completion_view_key_press_handler (w, ev, w);
|
||
}
|
||
|
||
static void
|
||
e_completion_view_paint (GtkWidget *widget, GdkRectangle *area)
|
||
{
|
||
gint i;
|
||
|
||
g_return_if_fail (widget != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION_VIEW (widget));
|
||
g_return_if_fail (area != NULL);
|
||
|
||
if (!GTK_WIDGET_DRAWABLE (widget))
|
||
return;
|
||
|
||
for (i = 0; i < E_COMPLETION_VIEW (widget)->border_width; ++i) {
|
||
|
||
gdk_draw_rectangle (widget->window,
|
||
widget->style->black_gc,
|
||
FALSE, i, i,
|
||
widget->allocation.width-1-2*i,
|
||
widget->allocation.height-1-2*i);
|
||
|
||
}
|
||
|
||
}
|
||
|
||
#if 0
|
||
static void
|
||
e_completion_view_draw (GtkWidget *widget, GdkRectangle *area)
|
||
{
|
||
GtkBin *bin;
|
||
GdkRectangle child_area;
|
||
|
||
g_return_if_fail (widget != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION_VIEW (widget));
|
||
g_return_if_fail (area != NULL);
|
||
|
||
if (GTK_WIDGET_DRAWABLE (widget)) {
|
||
bin = GTK_BIN (widget);
|
||
|
||
e_completion_view_paint (widget, area);
|
||
|
||
if (bin->child && gtk_widget_intersect (bin->child, area, &child_area))
|
||
gtk_widget_draw (bin->child, &child_area);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
static gint
|
||
e_completion_view_expose_event (GtkWidget *widget, GdkEventExpose *event)
|
||
{
|
||
GtkBin *bin;
|
||
GdkEventExpose child_event;
|
||
|
||
g_return_val_if_fail (widget != NULL, FALSE);
|
||
g_return_val_if_fail (E_IS_COMPLETION_VIEW (widget), FALSE);
|
||
g_return_val_if_fail (event != NULL, FALSE);
|
||
|
||
if (GTK_WIDGET_DRAWABLE (widget)) {
|
||
bin = GTK_BIN (widget);
|
||
|
||
e_completion_view_paint (widget, &event->area);
|
||
|
||
child_event = *event;
|
||
if (bin->child &&
|
||
GTK_WIDGET_NO_WINDOW (bin->child) &&
|
||
gtk_widget_intersect (bin->child, &event->area, &child_event.area))
|
||
gtk_widget_send_expose (bin->child, (GdkEvent*) &child_event);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static void
|
||
e_completion_view_size_request (GtkWidget *widget, GtkRequisition *requisition)
|
||
{
|
||
GtkBin *bin;
|
||
|
||
g_return_if_fail (widget != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION_VIEW (widget));
|
||
g_return_if_fail (requisition != NULL);
|
||
|
||
bin = GTK_BIN (widget);
|
||
|
||
requisition->width = 2 * E_COMPLETION_VIEW (widget)->border_width;
|
||
requisition->height = 2 * E_COMPLETION_VIEW (widget)->border_width;
|
||
|
||
if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) {
|
||
GtkRequisition child_requisition;
|
||
|
||
gtk_widget_size_request (bin->child, &child_requisition);
|
||
|
||
requisition->width += child_requisition.width;
|
||
requisition->height += child_requisition.height;
|
||
}
|
||
|
||
requisition->height = MAX (100, requisition->height);
|
||
}
|
||
|
||
static void
|
||
e_completion_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
|
||
{
|
||
GtkBin *bin;
|
||
GtkAllocation child_allocation;
|
||
|
||
g_return_if_fail (widget != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION_VIEW (widget));
|
||
g_return_if_fail (allocation != NULL);
|
||
|
||
bin = GTK_BIN (widget);
|
||
widget->allocation = *allocation;
|
||
|
||
child_allocation.x = E_COMPLETION_VIEW (widget)->border_width;
|
||
child_allocation.width = MAX(0, (gint)allocation->width - child_allocation.x * 2);
|
||
|
||
child_allocation.y = E_COMPLETION_VIEW (widget)->border_width;
|
||
child_allocation.height = MAX (0, (gint)allocation->height - child_allocation.y * 2);
|
||
|
||
if (GTK_WIDGET_REALIZED (widget)) {
|
||
gdk_window_move_resize (widget->window,
|
||
allocation->x,
|
||
allocation->y,
|
||
allocation->width,
|
||
allocation->height);
|
||
}
|
||
|
||
if (bin->child) {
|
||
gtk_widget_size_allocate (bin->child, &child_allocation);
|
||
}
|
||
}
|
||
|
||
E_MAKE_TYPE (e_completion_view,
|
||
"ECompletionView",
|
||
ECompletionView,
|
||
e_completion_view_class_init,
|
||
e_completion_view_init,
|
||
PARENT_TYPE)
|
||
|
||
static void
|
||
e_completion_view_class_init (ECompletionViewClass *klass)
|
||
{
|
||
GObjectClass *object_class = (GObjectClass *) klass;
|
||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||
|
||
parent_class = g_type_class_ref (PARENT_TYPE);
|
||
|
||
e_completion_view_signals[E_COMPLETION_VIEW_NONEMPTY] =
|
||
g_signal_new ("nonempty",
|
||
G_OBJECT_CLASS_TYPE (object_class),
|
||
G_SIGNAL_RUN_LAST,
|
||
G_STRUCT_OFFSET (ECompletionViewClass, nonempty),
|
||
NULL, NULL,
|
||
e_marshal_NONE__NONE,
|
||
G_TYPE_NONE, 0);
|
||
|
||
e_completion_view_signals[E_COMPLETION_VIEW_ADDED] =
|
||
g_signal_new ("added",
|
||
G_OBJECT_CLASS_TYPE (object_class),
|
||
G_SIGNAL_RUN_LAST,
|
||
G_STRUCT_OFFSET (ECompletionViewClass, added),
|
||
NULL, NULL,
|
||
e_marshal_NONE__NONE,
|
||
G_TYPE_NONE, 0);
|
||
|
||
e_completion_view_signals[E_COMPLETION_VIEW_FULL] =
|
||
g_signal_new ("full",
|
||
G_OBJECT_CLASS_TYPE (object_class),
|
||
G_SIGNAL_RUN_LAST,
|
||
G_STRUCT_OFFSET (ECompletionViewClass, full),
|
||
NULL, NULL,
|
||
e_marshal_NONE__NONE,
|
||
G_TYPE_NONE, 0);
|
||
|
||
e_completion_view_signals[E_COMPLETION_VIEW_BROWSE] =
|
||
g_signal_new ("browse",
|
||
G_OBJECT_CLASS_TYPE (object_class),
|
||
G_SIGNAL_RUN_LAST,
|
||
G_STRUCT_OFFSET (ECompletionViewClass, browse),
|
||
NULL, NULL,
|
||
e_marshal_NONE__POINTER,
|
||
G_TYPE_NONE, 1,
|
||
G_TYPE_POINTER);
|
||
|
||
e_completion_view_signals[E_COMPLETION_VIEW_UNBROWSE] =
|
||
g_signal_new ("unbrowse",
|
||
G_OBJECT_CLASS_TYPE (object_class),
|
||
G_SIGNAL_RUN_LAST,
|
||
G_STRUCT_OFFSET (ECompletionViewClass, unbrowse),
|
||
NULL, NULL,
|
||
e_marshal_NONE__NONE,
|
||
G_TYPE_NONE, 0);
|
||
|
||
e_completion_view_signals[E_COMPLETION_VIEW_ACTIVATE] =
|
||
g_signal_new ("activate",
|
||
G_OBJECT_CLASS_TYPE (object_class),
|
||
G_SIGNAL_RUN_LAST,
|
||
G_STRUCT_OFFSET (ECompletionViewClass, activate),
|
||
NULL, NULL,
|
||
e_marshal_NONE__POINTER,
|
||
G_TYPE_NONE, 1,
|
||
G_TYPE_POINTER);
|
||
|
||
object_class->dispose = e_completion_view_dispose;
|
||
|
||
widget_class->key_press_event = e_completion_view_local_key_press_handler;
|
||
widget_class->expose_event = e_completion_view_expose_event;
|
||
widget_class->size_request = e_completion_view_size_request;
|
||
widget_class->size_allocate = e_completion_view_size_allocate;
|
||
}
|
||
|
||
static void
|
||
e_completion_view_init (ECompletionView *completion)
|
||
{
|
||
completion->border_width = 2;
|
||
completion->choices = g_ptr_array_new ();
|
||
}
|
||
|
||
static void
|
||
e_completion_view_dispose (GObject *object)
|
||
{
|
||
ECompletionView *cv = E_COMPLETION_VIEW (object);
|
||
|
||
e_completion_view_disconnect (cv);
|
||
|
||
if (cv->choices) {
|
||
e_completion_view_clear_choices (cv);
|
||
|
||
g_ptr_array_free (cv->choices, TRUE);
|
||
cv->choices = NULL;
|
||
}
|
||
|
||
if (cv->key_widget) {
|
||
g_signal_handler_disconnect (cv->key_widget, cv->key_signal_id);
|
||
g_object_unref (cv->key_widget);
|
||
cv->key_widget = NULL;
|
||
}
|
||
|
||
if (cv->completion)
|
||
g_object_unref (cv->completion);
|
||
cv->completion = NULL;
|
||
|
||
if (G_OBJECT_CLASS (parent_class)->dispose)
|
||
(G_OBJECT_CLASS (parent_class)->dispose) (object);
|
||
}
|
||
|
||
static void
|
||
e_completion_view_disconnect (ECompletionView *cv)
|
||
{
|
||
g_return_if_fail (cv != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION_VIEW (cv));
|
||
|
||
if (cv->begin_signal_id)
|
||
g_signal_handler_disconnect (cv->completion, cv->begin_signal_id);
|
||
if (cv->comp_signal_id)
|
||
g_signal_handler_disconnect (cv->completion, cv->comp_signal_id);
|
||
if (cv->end_signal_id)
|
||
g_signal_handler_disconnect (cv->completion, cv->end_signal_id);
|
||
|
||
cv->begin_signal_id = 0;
|
||
cv->comp_signal_id = 0;
|
||
cv->end_signal_id = 0;
|
||
}
|
||
|
||
static ETable *
|
||
e_completion_view_table (ECompletionView *cv)
|
||
{
|
||
return e_table_scrolled_get_table (E_TABLE_SCROLLED (cv->table));
|
||
}
|
||
|
||
static void
|
||
e_completion_view_clear_choices (ECompletionView *cv)
|
||
{
|
||
ECompletionMatch *match;
|
||
GPtrArray *m;
|
||
int i;
|
||
|
||
g_return_if_fail (E_IS_COMPLETION_VIEW (cv));
|
||
|
||
m = cv->choices;
|
||
for (i = 0; i < m->len; i++) {
|
||
match = g_ptr_array_index (m, i);
|
||
e_completion_match_unref (match);
|
||
}
|
||
g_ptr_array_set_size (m, 0);
|
||
}
|
||
|
||
static void
|
||
e_completion_view_set_cursor_row (ECompletionView *cv, gint r)
|
||
{
|
||
ETable *table;
|
||
GtkAdjustment *adj;
|
||
gint x, y1, y2, r1, r2, c;
|
||
double fracline;
|
||
gint iteration_count=0;
|
||
|
||
g_return_if_fail (cv != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION_VIEW (cv));
|
||
#ifndef G_DISABLE_CHECKS
|
||
/* choices->len is unsigned, but it is reasonable for r to be
|
||
* < 0 */
|
||
if (r > 0) {
|
||
g_return_if_fail (r < cv->choices->len);
|
||
}
|
||
#endif
|
||
|
||
adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (cv->table));
|
||
|
||
table = e_completion_view_table (cv);
|
||
|
||
if (r < 0) {
|
||
e_selection_model_clear (E_SELECTION_MODEL(table->selection));
|
||
|
||
/* Move back to the top when we clear the selection */
|
||
gtk_adjustment_set_value (adj, adj->lower);
|
||
return;
|
||
}
|
||
|
||
e_table_set_cursor_row (table, r);
|
||
|
||
/* OK, now the tricky bit. We try to insure that this row is
|
||
visible. */
|
||
|
||
/* If we are selecting the first or last row, then it is easy. We just
|
||
cram the vadjustment all the way up/down. */
|
||
if (r == 0) {
|
||
gtk_adjustment_set_value (adj, adj->lower);
|
||
return;
|
||
} else if (r == cv->choices->len - 1) {
|
||
gtk_adjustment_set_value (adj, adj->upper - adj->page_size);
|
||
return;
|
||
}
|
||
|
||
fracline = ((adj->upper - adj->lower - adj->page_size) / (gint)cv->choices->len) / 4;
|
||
|
||
while (iteration_count < 100) {
|
||
x = GTK_LAYOUT(table->table_canvas)->hadjustment->value;
|
||
y1 = GTK_LAYOUT(table->table_canvas)->vadjustment->value;
|
||
|
||
y2 = y1 + cv->table->allocation.height;
|
||
|
||
e_table_group_compute_location (e_completion_view_table (cv)->group, &x, &y1, &r1, &c);
|
||
e_table_group_compute_location (e_completion_view_table (cv)->group, &x, &y2, &r2, &c);
|
||
|
||
if (r <= r1) {
|
||
gtk_adjustment_set_value (adj, adj->value - fracline);
|
||
} else if (r >= r2) {
|
||
gtk_adjustment_set_value (adj, adj->value + fracline);
|
||
} else
|
||
return;
|
||
|
||
++iteration_count;
|
||
}
|
||
|
||
g_assert_not_reached ();
|
||
}
|
||
|
||
static void
|
||
e_completion_view_select (ECompletionView *cv, gint r)
|
||
{
|
||
ECompletionMatch *match;
|
||
|
||
match = g_ptr_array_index (cv->choices, r);
|
||
|
||
cv->selection = r;
|
||
e_completion_view_set_cursor_row (cv, r);
|
||
g_signal_emit (cv, e_completion_view_signals[E_COMPLETION_VIEW_ACTIVATE], 0, match);
|
||
}
|
||
|
||
static gint
|
||
e_completion_view_key_press_handler (GtkWidget *w, GdkEventKey *key_event, gpointer user_data)
|
||
{
|
||
ECompletionView *cv = E_COMPLETION_VIEW (user_data);
|
||
gint dir = 0;
|
||
gboolean key_handled = TRUE, complete_key = FALSE, uncomplete_key = FALSE, is_space = FALSE;
|
||
|
||
/* FIXME: This is totally lame.
|
||
The ECompletionView should be able to specify multiple completion/uncompletion keys, or just
|
||
have sensible defaults. */
|
||
|
||
if ((cv->complete_key && key_event->keyval == cv->complete_key)
|
||
|| ((key_event->keyval == GDK_n || key_event->keyval == GDK_N) && (key_event->state & GDK_CONTROL_MASK)))
|
||
complete_key = TRUE;
|
||
|
||
if ((cv->uncomplete_key && key_event->keyval == cv->uncomplete_key)
|
||
|| ((key_event->keyval == GDK_p || key_event->keyval == GDK_P) && (key_event->state & GDK_CONTROL_MASK)))
|
||
uncomplete_key = TRUE;
|
||
|
||
/* Start up a completion.*/
|
||
if (complete_key && !cv->editable) {
|
||
g_signal_emit (cv, e_completion_view_signals[E_COMPLETION_VIEW_BROWSE], 0, NULL);
|
||
goto stop_emission;
|
||
}
|
||
|
||
/* Stop our completion. */
|
||
if (uncomplete_key && cv->editable && cv->selection < 0) {
|
||
e_completion_view_set_cursor_row (cv, -1);
|
||
g_signal_emit (cv, e_completion_view_signals[E_COMPLETION_VIEW_UNBROWSE], 0);
|
||
goto stop_emission;
|
||
}
|
||
|
||
if (!cv->editable)
|
||
return FALSE;
|
||
|
||
switch (key_event->keyval) {
|
||
|
||
case GDK_n:
|
||
case GDK_N:
|
||
/* We (heart) emacs: treat ctrl-n as down */
|
||
if (! (key_event->state & GDK_CONTROL_MASK))
|
||
return FALSE;
|
||
|
||
case GDK_Down:
|
||
case GDK_KP_Down:
|
||
dir = 1;
|
||
break;
|
||
|
||
case GDK_p:
|
||
case GDK_P:
|
||
/* Treat ctrl-p as up */
|
||
if (! (key_event->state & GDK_CONTROL_MASK))
|
||
return FALSE;
|
||
|
||
case GDK_Up:
|
||
case GDK_KP_Up:
|
||
dir = -1;
|
||
break;
|
||
|
||
case GDK_Tab:
|
||
/* If our cursor is still up in the entry, move down into
|
||
the popup. Otherwise unbrowse. */
|
||
if (cv->choices->len > 0) {
|
||
if (cv->selection < 0) {
|
||
cv->selection = 0;
|
||
dir = 0;
|
||
} else {
|
||
cv->selection = -1;
|
||
dir = 0;
|
||
key_handled = FALSE;
|
||
}
|
||
}
|
||
break;
|
||
|
||
case GDK_space:
|
||
case GDK_KP_Space:
|
||
is_space = TRUE;
|
||
|
||
case GDK_Return:
|
||
case GDK_KP_Enter:
|
||
if (cv->selection < 0) {
|
||
/* We don't have a selection yet, move to the first selection if there is
|
||
more than one option. If there is only one option, select it automatically. */
|
||
|
||
/* Let space pass through. */
|
||
if (is_space)
|
||
return FALSE;
|
||
|
||
if (cv->choices->len == 1) {
|
||
e_completion_view_select (cv, 0);
|
||
goto stop_emission;
|
||
} else {
|
||
cv->selection = 0;
|
||
dir = 0;
|
||
}
|
||
|
||
} else {
|
||
/* Our cursor is down in the pop-up, so we make our selection. */
|
||
e_completion_view_select (cv, cv->selection);
|
||
goto stop_emission;
|
||
}
|
||
break;
|
||
|
||
case GDK_Escape:
|
||
/* Unbrowse hack */
|
||
cv->selection = -1;
|
||
dir = 0;
|
||
break;
|
||
|
||
default:
|
||
return FALSE;
|
||
}
|
||
|
||
cv->selection += dir;
|
||
|
||
if (cv->selection >= (int)cv->choices->len) {
|
||
cv->selection = cv->choices->len - 1;
|
||
/* Don't re-emit the browse signal */
|
||
goto stop_emission;
|
||
}
|
||
|
||
e_completion_view_set_cursor_row (cv, cv->selection);
|
||
|
||
if (cv->selection >= 0)
|
||
g_signal_emit (cv, e_completion_view_signals[E_COMPLETION_VIEW_BROWSE], 0,
|
||
g_ptr_array_index (cv->choices, cv->selection));
|
||
else
|
||
g_signal_emit (cv, e_completion_view_signals[E_COMPLETION_VIEW_UNBROWSE], 0);
|
||
|
||
stop_emission:
|
||
|
||
if (key_handled)
|
||
g_signal_stop_emission_by_name (w, "key_press_event");
|
||
|
||
return key_handled;
|
||
}
|
||
|
||
static void
|
||
begin_completion_cb (ECompletion *completion, const gchar *txt, gint pos, gint limit, gpointer user_data)
|
||
{
|
||
ECompletionView *cv = E_COMPLETION_VIEW (user_data);
|
||
|
||
e_table_model_pre_change (cv->model);
|
||
e_completion_view_clear_choices (cv);
|
||
cv->have_all_choices = FALSE;
|
||
|
||
e_table_model_changed (cv->model);
|
||
}
|
||
|
||
static void
|
||
completion_cb (ECompletion *completion, ECompletionMatch *match, gpointer user_data)
|
||
{
|
||
ECompletionView *cv = E_COMPLETION_VIEW (user_data);
|
||
gint r = cv->choices->len;
|
||
gboolean first = (cv->choices->len == 0);
|
||
|
||
e_table_model_pre_change (cv->model);
|
||
|
||
e_completion_match_ref (match);
|
||
g_ptr_array_add (cv->choices, match);
|
||
|
||
e_table_model_row_inserted (cv->model, r);
|
||
|
||
if (first)
|
||
g_signal_emit (cv, e_completion_view_signals[E_COMPLETION_VIEW_NONEMPTY], 0);
|
||
|
||
g_signal_emit (cv, e_completion_view_signals[E_COMPLETION_VIEW_ADDED], 0);
|
||
}
|
||
|
||
static void
|
||
end_completion_cb (ECompletion *completion, gpointer user_data)
|
||
{
|
||
ECompletionView *cv = E_COMPLETION_VIEW (user_data);
|
||
|
||
/* Do a final refresh of the table. */
|
||
e_table_model_pre_change (cv->model);
|
||
e_table_model_changed (cv->model);
|
||
|
||
cv->have_all_choices = TRUE;
|
||
g_signal_emit (cv, e_completion_view_signals[E_COMPLETION_VIEW_FULL], 0);
|
||
}
|
||
|
||
/*** Table Callbacks ***/
|
||
|
||
/* XXX toshok - we need to add sorting to this etable, through the use
|
||
of undisplayed fields of all the sort keys we want to use */
|
||
static char *simple_spec =
|
||
"<ETableSpecification no-headers=\"true\" draw-grid=\"false\" cursor-mode=\"line\" alternating-row-colors=\"false\" gettext-domain=\"" E_I18N_DOMAIN "\">"
|
||
" <ETableColumn model_col=\"0\" _title=\"Node\" expansion=\"1.0\" "
|
||
" minimum_width=\"16\" resizable=\"true\" cell=\"string\" "
|
||
" compare=\"string\"/> "
|
||
" <ETableState> "
|
||
" <column source=\"0\"/> "
|
||
" <grouping></grouping> "
|
||
" </ETableState> "
|
||
"</ETableSpecification>";
|
||
|
||
static gint
|
||
table_col_count (ETableModel *etm, gpointer data)
|
||
{
|
||
return 1;
|
||
}
|
||
|
||
static gint
|
||
table_row_count (ETableModel *etm, gpointer data)
|
||
{
|
||
ECompletionView *cv = E_COMPLETION_VIEW (data);
|
||
return cv->choices->len;
|
||
}
|
||
|
||
static gboolean
|
||
table_is_cell_editable (ETableModel *etm, gint c, gint r, gpointer data)
|
||
{
|
||
return FALSE;
|
||
}
|
||
|
||
static gpointer
|
||
table_value_at (ETableModel *etm, gint c, gint r, gpointer data)
|
||
{
|
||
ECompletionView *cv = E_COMPLETION_VIEW (data);
|
||
ECompletionMatch *match;
|
||
|
||
match = g_ptr_array_index (cv->choices, r);
|
||
|
||
return (gpointer) e_completion_match_get_menu_text (match);
|
||
}
|
||
|
||
static gchar *
|
||
table_value_to_string (ETableModel *em, gint col, gconstpointer val, gpointer data)
|
||
{
|
||
return (gchar *) val;
|
||
}
|
||
|
||
static void
|
||
table_click_cb (ETable *et, gint r, gint c, GdkEvent *ev, gpointer data)
|
||
{
|
||
ECompletionView *cv = E_COMPLETION_VIEW (data);
|
||
|
||
e_completion_view_select (cv, r);
|
||
}
|
||
|
||
void
|
||
e_completion_view_construct (ECompletionView *cv, ECompletion *completion)
|
||
{
|
||
g_return_if_fail (cv != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION_VIEW (cv));
|
||
g_return_if_fail (completion != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION (completion));
|
||
|
||
/* Make sure we don't call construct twice. */
|
||
g_return_if_fail (cv->completion == NULL);
|
||
|
||
GTK_WIDGET_SET_FLAGS (GTK_WIDGET (cv), GTK_CAN_FOCUS);
|
||
|
||
cv->completion = completion;
|
||
g_object_ref (completion);
|
||
|
||
cv->begin_signal_id = g_signal_connect (completion,
|
||
"completion_started",
|
||
G_CALLBACK (begin_completion_cb),
|
||
cv);
|
||
cv->comp_signal_id = g_signal_connect (completion,
|
||
"completion_found",
|
||
G_CALLBACK (completion_cb),
|
||
cv);
|
||
cv->end_signal_id = g_signal_connect (completion,
|
||
"completion_finished",
|
||
G_CALLBACK (end_completion_cb),
|
||
cv);
|
||
|
||
cv->model = e_table_simple_new (table_col_count,
|
||
table_row_count,
|
||
NULL,
|
||
|
||
table_value_at,
|
||
NULL,
|
||
table_is_cell_editable,
|
||
|
||
NULL, NULL,
|
||
|
||
NULL, NULL, NULL, NULL,
|
||
table_value_to_string,
|
||
cv);
|
||
|
||
cv->table = e_table_scrolled_new (cv->model, NULL, simple_spec, NULL);
|
||
g_object_unref (cv->model);
|
||
|
||
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (cv->table), GTK_SHADOW_NONE);
|
||
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (cv->table), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||
|
||
gtk_container_add (GTK_CONTAINER (cv), cv->table);
|
||
gtk_widget_show_all (cv->table);
|
||
|
||
g_signal_connect (e_completion_view_table (cv),
|
||
"click",
|
||
G_CALLBACK (table_click_cb),
|
||
cv);
|
||
|
||
cv->selection = -1;
|
||
}
|
||
|
||
GtkWidget *
|
||
e_completion_view_new (ECompletion *completion)
|
||
{
|
||
gpointer p;
|
||
|
||
g_return_val_if_fail (completion != NULL, NULL);
|
||
g_return_val_if_fail (E_IS_COMPLETION (completion), NULL);
|
||
|
||
p = g_object_new (E_COMPLETION_VIEW_TYPE, NULL);
|
||
|
||
e_completion_view_construct (E_COMPLETION_VIEW (p), completion);
|
||
|
||
return GTK_WIDGET (p);
|
||
}
|
||
|
||
void
|
||
e_completion_view_connect_keys (ECompletionView *cv, GtkWidget *w)
|
||
{
|
||
g_return_if_fail (cv != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION_VIEW (cv));
|
||
g_return_if_fail (w == NULL || GTK_IS_WIDGET (w));
|
||
|
||
if (cv->key_widget) {
|
||
g_signal_handler_disconnect (cv->key_widget, cv->key_signal_id);
|
||
g_object_unref (cv->key_widget);
|
||
}
|
||
|
||
if (w) {
|
||
cv->key_widget = w;
|
||
g_object_ref (w);
|
||
|
||
cv->key_signal_id = g_signal_connect (w,
|
||
"key_press_event",
|
||
G_CALLBACK (e_completion_view_key_press_handler),
|
||
cv);
|
||
} else {
|
||
cv->key_widget = NULL;
|
||
cv->key_signal_id = 0;
|
||
}
|
||
}
|
||
|
||
void
|
||
e_completion_view_set_complete_key (ECompletionView *cv, gint keyval)
|
||
{
|
||
g_return_if_fail (cv != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION_VIEW (cv));
|
||
|
||
cv->complete_key = keyval;
|
||
}
|
||
|
||
void
|
||
e_completion_view_set_uncomplete_key (ECompletionView *cv, gint keyval)
|
||
{
|
||
g_return_if_fail (cv != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION_VIEW (cv));
|
||
|
||
cv->uncomplete_key = keyval;
|
||
}
|
||
|
||
void
|
||
e_completion_view_set_width (ECompletionView *cv, gint width)
|
||
{
|
||
GtkWidget *w;
|
||
gint y, r, dummy, line_height, final_height;
|
||
double drop_room, lines;
|
||
|
||
g_return_if_fail (cv != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION_VIEW (cv));
|
||
g_return_if_fail (width > 0);
|
||
|
||
w = GTK_WIDGET (cv);
|
||
|
||
if (! GTK_WIDGET_REALIZED (w)) {
|
||
gtk_widget_set_usize (w, width, -1);
|
||
return;
|
||
}
|
||
|
||
/* A Horrible Hack(tm) to figure out the height of a single table row */
|
||
|
||
for (line_height=5, r=0; r == 0 && line_height < 1000; line_height += 2) {
|
||
dummy = 0;
|
||
e_table_group_compute_location (e_completion_view_table (cv)->group,
|
||
&dummy, &line_height, &r, &dummy);
|
||
}
|
||
|
||
if (line_height >= 1000) {
|
||
/* Something went wrong, so we make a (possibly very lame) guess */
|
||
line_height = 30;
|
||
}
|
||
|
||
|
||
gdk_window_get_origin (w->window, NULL, &y);
|
||
y += w->allocation.y;
|
||
|
||
lines = 5; /* default maximum */
|
||
lines = MIN (lines, cv->choices->len);
|
||
|
||
drop_room = (gdk_screen_height () - y) / (double)line_height;
|
||
drop_room = MAX (drop_room, 1);
|
||
|
||
lines = MIN (lines, drop_room);
|
||
|
||
/* We reduce the total height by a bit; in practice, this seems to work out well. */
|
||
final_height = (gint) floor (line_height * (0.5 + (float)lines) * 0.97);
|
||
gtk_widget_set_usize (w, width, final_height);
|
||
}
|
||
|
||
void
|
||
e_completion_view_set_editable (ECompletionView *cv, gboolean x)
|
||
{
|
||
g_return_if_fail (cv != NULL);
|
||
g_return_if_fail (E_IS_COMPLETION_VIEW (cv));
|
||
|
||
if (x == cv->editable)
|
||
return;
|
||
|
||
cv->editable = x;
|
||
cv->selection = -1;
|
||
e_completion_view_set_cursor_row (cv, -1);
|
||
}
|
||
|
||
|