#define NCOLOURS (lenof(((Config *)0)->colours))
struct gui_data {
- GtkWidget *area;
+ GtkWidget *area, *sbar;
+ GtkBox *hbox;
+ GtkAdjustment *sbar_adjust;
GdkPixmap *pixmap;
GdkFont *fonts[2]; /* normal and bold (for now!) */
GdkCursor *rawcursor, *textcursor;
GdkColor cols[NCOLOURS];
GdkColormap *colmap;
- GdkGC *black_gc, *white_gc;
+ wchar_t *pastein_data;
+ int pastein_data_len;
+ char *pasteout_data;
+ int pasteout_data_len;
+ int font_width, font_height;
};
static struct gui_data the_inst;
if (inst->pixmap)
gdk_pixmap_unref(inst->pixmap);
- inst->pixmap = gdk_pixmap_new(widget->window, 9*80, 15*24, -1);
+ inst->pixmap = gdk_pixmap_new(widget->window,
+ (cfg.width * inst->font_width +
+ 2*cfg.window_border),
+ (cfg.height * inst->font_height +
+ 2*cfg.window_border), -1);
- inst->fonts[0] = gdk_font_load("9x15t"); /* XXCONFIG */
- inst->fonts[1] = NULL; /* XXCONFIG */
- inst->black_gc = widget->style->black_gc;
- inst->white_gc = widget->style->white_gc;
+ {
+ GdkGC *gc;
+ gc = gdk_gc_new(inst->area->window);
+ gdk_gc_set_foreground(gc, &inst->cols[18]); /* default background */
+ gdk_draw_rectangle(inst->pixmap, gc, 1, 0, 0,
+ cfg.width * inst->font_width + 2*cfg.window_border,
+ cfg.height * inst->font_height + 2*cfg.window_border);
+ gdk_gc_unref(gc);
+ }
/*
* Set up the colour map.
* Pass the exposed rectangle to terminal.c, which will call us
* back to do the actual painting.
*/
- term_paint(NULL,
- event->area.x / 9, event->area.y / 15,
- (event->area.x + event->area.width - 1) / 9,
- (event->area.y + event->area.height - 1) / 15);
+ if (inst->pixmap) {
+ gdk_draw_pixmap(widget->window,
+ widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
+ inst->pixmap,
+ event->area.x, event->area.y,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height);
+ }
return TRUE;
}
/*
* NYI:
- * - Shift-PgUp/PgDn for scrollbar
* - nethack mode
* - alt+numpad
* - Compose key (!!! requires Unicode faff before even trying)
- * - Shift-Ins for paste (need to deal with pasting first)
*/
+ /*
+ * Shift-PgUp and Shift-PgDn don't even generate keystrokes
+ * at all.
+ */
+ if (event->keyval == GDK_Page_Up && (event->state & GDK_SHIFT_MASK)) {
+ term_scroll(0, -cfg.height/2);
+ return TRUE;
+ }
+ if (event->keyval == GDK_Page_Down && (event->state & GDK_SHIFT_MASK)) {
+ term_scroll(0, +cfg.height/2);
+ return TRUE;
+ }
+
+ /*
+ * Neither does Shift-Ins.
+ */
+ if (event->keyval == GDK_Insert && (event->state & GDK_SHIFT_MASK)) {
+ request_paste();
+ return TRUE;
+ }
+
/* ALT+things gives leading Escape. */
output[0] = '\033';
strncpy(output+1, event->string, 31);
return TRUE;
}
+gint button_event(GtkWidget *widget, GdkEventButton *event, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ int shift, ctrl, alt, x, y, button, act;
+
+ shift = event->state & GDK_SHIFT_MASK;
+ ctrl = event->state & GDK_CONTROL_MASK;
+ alt = event->state & GDK_MOD1_MASK;
+ if (event->button == 1)
+ button = MBT_LEFT;
+ else if (event->button == 2)
+ button = MBT_MIDDLE;
+ else if (event->button == 3)
+ button = MBT_RIGHT;
+ else
+ return FALSE; /* don't even know what button! */
+
+ switch (event->type) {
+ case GDK_BUTTON_PRESS: act = MA_CLICK; break;
+ case GDK_BUTTON_RELEASE: act = MA_RELEASE; break;
+ case GDK_2BUTTON_PRESS: act = MA_2CLK; break;
+ case GDK_3BUTTON_PRESS: act = MA_3CLK; break;
+ default: return FALSE; /* don't know this event type */
+ }
+
+ if (send_raw_mouse && !(cfg.mouse_override && shift) &&
+ act != MA_CLICK && act != MA_RELEASE)
+ return TRUE; /* we ignore these in raw mouse mode */
+
+ x = (event->x - cfg.window_border) / inst->font_width;
+ y = (event->y - cfg.window_border) / inst->font_height;
+
+ term_mouse(button, act, x, y, shift, ctrl, alt);
+
+ return TRUE;
+}
+
+gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data)
+{
+ struct gui_data *inst = (struct gui_data *)data;
+ int shift, ctrl, alt, x, y, button;
+
+ shift = event->state & GDK_SHIFT_MASK;
+ ctrl = event->state & GDK_CONTROL_MASK;
+ alt = event->state & GDK_MOD1_MASK;
+ if (event->state & GDK_BUTTON1_MASK)
+ button = MBT_LEFT;
+ else if (event->state & GDK_BUTTON2_MASK)
+ button = MBT_MIDDLE;
+ else if (event->state & GDK_BUTTON3_MASK)
+ button = MBT_RIGHT;
+ else
+ return FALSE; /* don't even know what button! */
+
+ x = (event->x - cfg.window_border) / inst->font_width;
+ y = (event->y - cfg.window_border) / inst->font_height;
+
+ term_mouse(button, MA_DRAG, x, y, shift, ctrl, alt);
+
+ return TRUE;
+}
+
gint timer_func(gpointer data)
{
/* struct gui_data *inst = (struct gui_data *)data; */
void write_clip(wchar_t * data, int len, int must_deselect)
{
- /* FIXME: currently ignored */
+ if (inst->pasteout_data)
+ sfree(inst->pasteout_data);
+ inst->pasteout_data = smalloc(len);
+ inst->pasteout_data_len = len;
+ wc_to_mb(0, 0, data, len, inst->pasteout_data, inst->pasteout_data_len,
+ NULL, NULL);
+
+ if (gtk_selection_owner_set(inst->area, GDK_SELECTION_PRIMARY,
+ GDK_CURRENT_TIME)) {
+ gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY,
+ GDK_SELECTION_TYPE_STRING, 1);
+ }
+}
+
+void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
+ guint info, guint time_stamp, gpointer data)
+{
+ gtk_selection_data_set(seldata, GDK_SELECTION_TYPE_STRING, 8,
+ inst->pasteout_data, inst->pasteout_data_len);
+}
+
+gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
+ gpointer data)
+{
+ term_deselect();
+ if (inst->pasteout_data)
+ sfree(inst->pasteout_data);
+ inst->pasteout_data = NULL;
+ inst->pasteout_data_len = 0;
+ return TRUE;
+}
+
+void request_paste(void)
+{
+ /*
+ * In Unix, pasting is asynchronous: all we can do at the
+ * moment is to call gtk_selection_convert(), and when the data
+ * comes back _then_ we can call term_do_paste().
+ */
+ gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY,
+ GDK_SELECTION_TYPE_STRING, GDK_CURRENT_TIME);
+}
+
+void selection_received(GtkWidget *widget, GtkSelectionData *seldata,
+ gpointer data)
+{
+ if (seldata->length <= 0 ||
+ seldata->type != GDK_SELECTION_TYPE_STRING)
+ return; /* Nothing happens. */
+
+ if (inst->pastein_data)
+ sfree(inst->pastein_data);
+
+ inst->pastein_data = smalloc(seldata->length * sizeof(wchar_t));
+ inst->pastein_data_len = seldata->length;
+ mb_to_wc(0, 0, seldata->data, seldata->length,
+ inst->pastein_data, inst->pastein_data_len);
+
+ term_do_paste();
}
void get_clip(wchar_t ** p, int *len)
{
if (p) {
- /* FIXME: currently nonfunctional */
- *p = NULL;
- *len = 0;
+ *p = inst->pastein_data;
+ *len = inst->pastein_data_len;
}
}
void set_sbar(int total, int start, int page)
{
- /* FIXME: currently ignored */
+ inst->sbar_adjust->lower = 0;
+ inst->sbar_adjust->upper = total;
+ inst->sbar_adjust->value = start;
+ inst->sbar_adjust->page_size = page;
+ inst->sbar_adjust->step_increment = 1;
+ inst->sbar_adjust->page_increment = page/2;
+ gtk_adjustment_changed(inst->sbar_adjust);
+}
+
+void scrollbar_moved(GtkAdjustment *adj, gpointer data)
+{
+ term_scroll(1, (int)adj->value);
}
void sys_cursor(int x, int y)
* - cursor shapes other than block
* - VT100 line drawing stuff; code pages in general!
* - shadow bolding
- * - underline
*/
nfg = 2 * ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
}
gdk_gc_set_foreground(gc, &inst->cols[nbg]);
- gdk_draw_rectangle(inst->pixmap, gc, 1, x*9, y*15, len*9, 15);
-
+ gdk_draw_rectangle(inst->pixmap, gc, 1,
+ x*inst->font_width+cfg.window_border,
+ y*inst->font_height+cfg.window_border,
+ len*inst->font_width, inst->font_height);
+
gdk_gc_set_foreground(gc, &inst->cols[nfg]);
gdk_draw_text(inst->pixmap, inst->fonts[0], gc,
- x*9, y*15 + inst->fonts[0]->ascent, text, len);
+ x*inst->font_width+cfg.window_border,
+ y*inst->font_height+cfg.window_border+inst->fonts[0]->ascent,
+ text, len);
if (attr & ATTR_UNDER) {
int uheight = inst->fonts[0]->ascent + 1;
- if (uheight >= 15)
- uheight = 14;
- gdk_draw_line(inst->pixmap, gc, x*9, y*15 + uheight,
- (x+len)*9-1, y*15+uheight);
+ if (uheight >= inst->font_height)
+ uheight = inst->font_height - 1;
+ gdk_draw_line(inst->pixmap, gc, x*inst->font_width+cfg.window_border,
+ y*inst->font_height + uheight + cfg.window_border,
+ (x+len)*inst->font_width-1+cfg.window_border,
+ y*inst->font_height + uheight + cfg.window_border);
}
gdk_draw_pixmap(inst->area->window, gc, inst->pixmap,
- x*9, y*15, x*9, y*15, len*9, 15);
+ x*inst->font_width+cfg.window_border,
+ y*inst->font_height+cfg.window_border,
+ x*inst->font_width+cfg.window_border,
+ y*inst->font_height+cfg.window_border,
+ len*inst->font_width, inst->font_height);
}
void do_cursor(Context ctx, int x, int y, char *text, int len,
do_text(ctx, x, y, text, len, attr, lattr);
if (passive) {
gdk_gc_set_foreground(gc, &inst->cols[NCOLOURS-1]);
- gdk_draw_rectangle(inst->pixmap, gc, 0, x*9, y*15, len*9-1, 15-1);
+ gdk_draw_rectangle(inst->pixmap, gc, 0,
+ x*inst->font_width+cfg.window_border,
+ y*inst->font_height+cfg.window_border,
+ len*inst->font_width-1, inst->font_height-1);
gdk_draw_pixmap(inst->area->window, gc, inst->pixmap,
- x*9, y*15, x*9, y*15, len*9, 15);
+ x*inst->font_width+cfg.window_border,
+ y*inst->font_height+cfg.window_border,
+ x*inst->font_width+cfg.window_border,
+ y*inst->font_height+cfg.window_border,
+ len*inst->font_width, inst->font_height);
}
}
+GdkCursor *make_mouse_ptr(int cursor_val)
+{
+ /*
+ * Truly hideous hack: GTK doesn't allow us to set the mouse
+ * cursor foreground and background colours unless we've _also_
+ * created our own cursor from bitmaps. Therefore, I need to
+ * load the `cursor' font and draw glyphs from it on to
+ * pixmaps, in order to construct my cursors with the fg and bg
+ * I want. This is a gross hack, but it's more self-contained
+ * than linking in Xlib to find the X window handle to
+ * inst->area and calling XRecolorCursor, and it's more
+ * futureproof than hard-coding the shapes as bitmap arrays.
+ */
+ static GdkFont *cursor_font = NULL;
+ GdkPixmap *source, *mask;
+ GdkGC *gc;
+ GdkColor cfg = { 0, 65535, 65535, 65535 };
+ GdkColor cbg = { 0, 0, 0, 0 };
+ GdkColor dfg = { 1, 65535, 65535, 65535 };
+ GdkColor dbg = { 0, 0, 0, 0 };
+ GdkCursor *ret;
+ gchar text[2];
+ gint lb, rb, wid, asc, desc, w, h, x, y;
+
+ if (cursor_val < 0) {
+ gdk_font_unref(cursor_font);
+ return NULL;
+ }
+
+ if (!cursor_font)
+ cursor_font = gdk_font_load("cursor");
+
+ /*
+ * Get the text extent of the cursor in question. We use the
+ * mask character for this, because it's typically slightly
+ * bigger than the main character.
+ */
+ text[1] = '\0';
+ text[0] = (char)cursor_val + 1;
+ gdk_string_extents(cursor_font, text, &lb, &rb, &wid, &asc, &desc);
+ w = rb-lb; h = asc+desc; x = -lb; y = asc;
+
+ source = gdk_pixmap_new(NULL, w, h, 1);
+ mask = gdk_pixmap_new(NULL, w, h, 1);
+
+ /*
+ * Draw the mask character on the mask pixmap.
+ */
+ text[1] = '\0';
+ text[0] = (char)cursor_val + 1;
+ gc = gdk_gc_new(mask);
+ gdk_gc_set_foreground(gc, &dbg);
+ gdk_draw_rectangle(mask, gc, 1, 0, 0, w, h);
+ gdk_gc_set_foreground(gc, &dfg);
+ gdk_draw_text(mask, cursor_font, gc, x, y, text, 1);
+ gdk_gc_unref(gc);
+
+ /*
+ * Draw the main character on the source pixmap.
+ */
+ text[1] = '\0';
+ text[0] = (char)cursor_val;
+ gc = gdk_gc_new(source);
+ gdk_gc_set_foreground(gc, &dbg);
+ gdk_draw_rectangle(source, gc, 1, 0, 0, w, h);
+ gdk_gc_set_foreground(gc, &dfg);
+ gdk_draw_text(source, cursor_font, gc, x, y, text, 1);
+ gdk_gc_unref(gc);
+
+ /*
+ * Create the cursor.
+ */
+ ret = gdk_cursor_new_from_pixmap(source, mask, &cfg, &cbg, x, y);
+
+ /*
+ * Clean up.
+ */
+ gdk_pixmap_unref(source);
+ gdk_pixmap_unref(mask);
+
+ return ret;
+}
+
void modalfatalbox(char *p, ...)
{
va_list ap;
do_defaults(NULL, &cfg);
+ inst->fonts[0] = gdk_font_load(cfg.font);
+ inst->fonts[1] = NULL; /* FIXME: what about bold font? */
+ inst->font_width = gdk_char_width(inst->fonts[0], ' ');
+ inst->font_height = inst->fonts[0]->ascent + inst->fonts[0]->descent;
+
init_ucs();
back = &pty_backend;
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
inst->area = gtk_drawing_area_new();
gtk_drawing_area_size(GTK_DRAWING_AREA(inst->area),
- 9*80, 15*24);/* FIXME: proper resizing stuff */
+ inst->font_width * cfg.width + 2*cfg.window_border,
+ inst->font_height * cfg.height + 2*cfg.window_border);
+ inst->sbar_adjust = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 0, 0, 0, 0));
+ inst->sbar = gtk_vscrollbar_new(inst->sbar_adjust);
+ inst->hbox = GTK_BOX(gtk_hbox_new(FALSE, 0));
+ gtk_box_pack_start(inst->hbox, inst->area, FALSE, FALSE, 0);
+ gtk_box_pack_start(inst->hbox, inst->sbar, FALSE, FALSE, 0);
+
+ gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(inst->hbox));
- gtk_container_add(GTK_CONTAINER(window), inst->area);
+ {
+ GdkGeometry geom;
+ geom.min_width = inst->font_width + 2*cfg.window_border;
+ geom.min_height = inst->font_height + 2*cfg.window_border;
+ geom.max_width = geom.max_height = -1;
+ geom.base_width = 2*cfg.window_border;
+ geom.base_height = 2*cfg.window_border;
+ geom.width_inc = inst->font_width;
+ geom.height_inc = inst->font_height;
+ geom.min_aspect = geom.max_aspect = 0;
+ gtk_window_set_geometry_hints(GTK_WINDOW(window), inst->area, &geom,
+ GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE |
+ GDK_HINT_RESIZE_INC);
+ }
gtk_signal_connect(GTK_OBJECT(window), "destroy",
GTK_SIGNAL_FUNC(destroy), inst);
GTK_SIGNAL_FUNC(configure_area), inst);
gtk_signal_connect(GTK_OBJECT(inst->area), "expose_event",
GTK_SIGNAL_FUNC(expose_area), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->area), "button_press_event",
+ GTK_SIGNAL_FUNC(button_event), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->area), "button_release_event",
+ GTK_SIGNAL_FUNC(button_event), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->area), "motion_notify_event",
+ GTK_SIGNAL_FUNC(motion_event), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->area), "selection_received",
+ GTK_SIGNAL_FUNC(selection_received), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->area), "selection_get",
+ GTK_SIGNAL_FUNC(selection_get), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->area), "selection_clear_event",
+ GTK_SIGNAL_FUNC(selection_clear), inst);
+ gtk_signal_connect(GTK_OBJECT(inst->sbar_adjust), "value_changed",
+ GTK_SIGNAL_FUNC(scrollbar_moved), inst);
gtk_timeout_add(20, timer_func, inst);
gdk_input_add(pty_master_fd, GDK_INPUT_READ, pty_input_func, inst);
gtk_widget_add_events(GTK_WIDGET(inst->area),
- GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_BUTTON_MOTION_MASK);
gtk_widget_show(inst->area);
+ gtk_widget_show(inst->sbar);
+ gtk_widget_show(GTK_WIDGET(inst->hbox));
gtk_widget_show(window);
- inst->textcursor = gdk_cursor_new(GDK_XTERM);
- inst->rawcursor = gdk_cursor_new(GDK_ARROW);
+ inst->textcursor = make_mouse_ptr(GDK_XTERM);
+ inst->rawcursor = make_mouse_ptr(GDK_LEFT_PTR);
+ make_mouse_ptr(-1); /* clean up cursor font */
gdk_window_set_cursor(inst->area->window, inst->textcursor);
term_init();