diff options
Diffstat (limited to 'char-table.c')
| -rw-r--r-- | char-table.c | 816 |
1 files changed, 731 insertions, 85 deletions
diff --git a/char-table.c b/char-table.c index 0b812ca..9f62b5e 100644 --- a/char-table.c +++ b/char-table.c @@ -1,11 +1,22 @@ /* See LICENSE file for copyright and license details. */ #include "common.h" -#define REFERENCE_CHARACTER "X" -#define CELL_WIDTH_FACTOR 4 -#define CELL_HEIGHT_FACTOR 2 -#define CELL_X_CLEARANCE 2 -#define CELL_Y_CLEARANCE 2 +#define REFERENCE_CHARACTER "X" +#define CELL_WIDTH_FACTOR 4 +#define CELL_HEIGHT_FACTOR 2 +#define CELL_X_CLEARANCE 2 +#define CELL_Y_CLEARANCE 2 +#define MAGNIFICATION_WIDTH 200 +#define MAGNIFICATION_HEIGHT 200 +#define DRAG_WIDTH 100 +#define DRAG_HEIGHT 100 +#define DRAG_X -16 +#define DRAG_Y -16 + +#define TEXT_LIMIT 32u +#define IS_SHIFT_KEY(KEYVAL) ((KEYVAL) == GDK_KEY_Shift_L || (KEYVAL) == GDK_KEY_Shift_R) +#define SHIFT_KEY(KEYVAL) (1u << ((KEYVAL) == GDK_KEY_Shift_R)) + struct cells { int r0, c0; /* inclusive */ @@ -13,37 +24,63 @@ struct cells { int r_off; }; + +struct glyph_layout { + double scale; + double x; + double y; + double width; + double height; + + /* XXX this should be calculated and displayed with lines */ + double baseline_y; + double descender_y; + double ascender_y; + double x_height_y; + double cap_height_y; + double preadvance_x; + double postadvance_x; +}; + + G_DEFINE_TYPE(CharTable, gcmap_char_table, GTK_TYPE_DRAWING_AREA) static int -is_noncharacter(gunichar ch) +format_character(uint32_t cp, char *buf) { - return (0xFDD0u <= ch && ch <= 0xFDEFu) || (ch & 0xFFFEu) == 0xFFFEu; -} + int len = 0; + switch (classify_codepoint(cp)) { + case CP_COMBINING_MARK: +#if MARK_BASE + len += g_unichar_to_utf8(MARK_BASE, &buf[len]); +#endif + /* fall through */ -static int -is_printable_character(gunichar ch) -{ - return g_unichar_validate(ch) && - !g_unichar_iscntrl(ch) && - !g_unichar_iszerowidth(ch) && - !is_noncharacter(ch); -} + case CP_REGULAR: + case CP_RESERVED: + len += g_unichar_to_utf8((gunichar)cp, &buf[len]); + break; + case CP_CONTROL: + case CP_SURROGATE: + case CP_NONCHARACTER: + case CP_OUT_OF_RANGE: + default: + break; + } -static void -format_character(gunichar ch, char *buf) -{ - buf[is_printable_character(ch) ? g_unichar_to_utf8(ch, buf) : 0] = '\0'; + buf[len] = '\0'; + return len; } static void -apply_font_size(CharTable *this, int size, int *text_w_out, int *text_h_out) +apply_font_size(CharTable *this, double size, int *text_w_out, int *text_h_out) { - pango_font_description_set_size(this->font, size * PANGO_SCALE); + int size_int = (int)(size * PANGO_SCALE); + pango_font_description_set_size(this->font, MAX(size_int, 1)); pango_layout_set_font_description(this->layout, this->font); pango_layout_context_changed(this->layout); pango_layout_get_pixel_size(this->layout, text_w_out, text_h_out); @@ -55,22 +92,54 @@ get_codepoint(CharTable *this, uint32_t index) { size_t i = 0u; uint32_t count; - while (index >= (count = (uint32_t)LIBCMAP_RANGE_SIZE(&this->ranges[i]))) { index -= count; if (++i == this->nranges) abort(); } - return this->ranges[i].first + index; } +static int +find_codepoint_index(CharTable *this, uint32_t codepoint, uint32_t *index_out) +{ + size_t i = 0u; + uint32_t index = 0u; + while (i < this->nranges && codepoint > this->ranges[i].last) { + index += (uint32_t)LIBCMAP_RANGE_SIZE(&this->ranges[i]); + i += 1u; + } + if (i >= this->nranges || codepoint < this->ranges[i].first) + return 0; + *index_out = index + codepoint - this->ranges[i].first; + return 1; +} + + +static void +execute_selection_callback(CharTable *this, uint32_t cell, int meta, int copy_to_text_field) +{ + uint32_t codepoint; + if (!this->selection_callback) + return; + codepoint = get_codepoint(this, cell); + if (copy_to_text_field) + (*this->selection_callback)(cell, codepoint, COPY_TO_TEXTFIELD); + else if (meta) + (*this->selection_callback)(cell, codepoint, COPY_TO_SECONDARY); + else + (*this->selection_callback)(cell, codepoint, COPY_TO_PRIMARY); +} + + static void fit_text(CharTable *this, const char *text, int *text_w_out, int *text_h_out) { - int size, ok_size = 1; - double scale_x, scale_y, scale; + double size, ok_size = 1.0; + double xscale, yscale, scale; + double next_size, steps[] = {1.0, 0.1, 0.01}; + size_t i; /* Set text to measure */ pango_layout_set_text(this->layout, text, -1); @@ -81,15 +150,14 @@ fit_text(CharTable *this, const char *text, int *text_w_out, int *text_h_out) return; /* Too large? Then estimate largest smaller size that would fit */ - scale_x = (double)this->max_w / (double)*text_w_out; - scale_y = (double)this->max_h / (double)*text_h_out; - scale = MIN(scale_x, scale_y); + xscale = (double)this->max_w / (double)*text_w_out; + yscale = (double)this->max_h / (double)*text_h_out; + scale = MIN(xscale, yscale); scale = MIN(scale, 1) * this->font_size; - size = (int)scale; - size = MAX(size, 1); + size = MAX(scale, 1.0); /* Check and decrease by one until it fits */ - for (; size > 1; size--) { + for (; size > 1.0; size -= 1.0) { apply_font_size(this, size, text_w_out, text_h_out); if (*text_w_out <= this->max_w && *text_h_out <= this->max_h) { ok_size = size; @@ -98,20 +166,19 @@ fit_text(CharTable *this, const char *text, int *text_w_out, int *text_h_out) } /* Maybe it was too small, then try larger */ - if (*text_w_out < this->max_w && *text_h_out < this->max_h) { - goto start; - do { - ok_size++; - start: - if (ok_size == this->font_size) + for (i = 0u; i < ELEMSOF(steps); i++) { + while (*text_w_out < this->max_w && *text_h_out < this->max_h) { + next_size = MIN(ok_size + steps[i], (double)this->font_size); + if (next_size <= ok_size) break; - apply_font_size(this, ok_size + 1, text_w_out, text_h_out); - } while (*text_w_out < this->max_w && *text_h_out < this->max_h); + apply_font_size(this, next_size, text_w_out, text_h_out); + if (*text_w_out < this->max_w && *text_h_out < this->max_h) + ok_size = next_size; + } } - /* Use the on that fit, 1 if none fit */ - if (ok_size != size) - apply_font_size(this, ok_size, text_w_out, text_h_out); + /* Use the one that fits, 1 if none fit */ + apply_font_size(this, ok_size, text_w_out, text_h_out); } @@ -120,8 +187,6 @@ get_exposed_cells(GtkWidget *widget, GdkEventExpose *event, struct cells *cells_ { CharTable *this = GCMAP_CHAR_TABLE(widget); GdkRectangle area = event->area; - LogicalScrollPane *scrolls = LOGICAL_SCROLL_PANE(gtk_widget_get_parent(widget)); - GtkAdjustment *vadjustment = logical_scroll_pane_get_vadjustment(scrolls); double y; cells_out->r0 = area.y * this->nrows / (this->viewport_h + 1); @@ -134,7 +199,7 @@ get_exposed_cells(GtkWidget *widget, GdkEventExpose *event, struct cells *cells_ cells_out->r1 = MIN(cells_out->r1, cells_out->r0 + this->nrows); cells_out->c1 = MIN(cells_out->c1, cells_out->c0 + this->ncols); - y = round(vadjustment->value); + y = this->vadjustment ? round(this->vadjustment->value) : 0.0; cells_out->r_off = MAX((int)y, 0); } @@ -147,7 +212,7 @@ gcmap_char_table_expose(GtkWidget *widget, GdkEventExpose *event) GdkRectangle area = event->area; GtkStyle *style = gtk_widget_get_style(widget); cairo_t *g; - char text[16]; + char text[TEXT_LIMIT]; int r, c, x0, x1, y0, y1, text_w, text_h; size_t skip, range, row_skip, max_jump, jump; uint32_t cp, index; @@ -234,7 +299,7 @@ gcmap_char_table_expose(GtkWidget *widget, GdkEventExpose *event) set_source_colour(g, &style->text[selectedState]); } - format_character((gunichar)cp, text); + format_character(cp, text); fit_text(this, text, &text_w, &text_h); cairo_move_to(g, (x0 + x1 - text_w) / 2, (y0 + y1 - text_h) / 2); pango_cairo_show_layout(g, this->layout); @@ -261,6 +326,197 @@ out: static void +get_selected_cell_screen_rectangle(CharTable *this, int *x_out, int *y_out, int *w_out, int *h_out, int *c_out, int *r_out) +{ + double yadj = this->vadjustment ? round(this->vadjustment->value) : 0.0; + int yadj_int = (int)yadj; + int row = (int)(this->selected / (uint32_t)this->ncols); + int col = (int)(this->selected % (uint32_t)this->ncols); + row -= MAX(yadj_int, 0); + *x_out = (this->viewport_w + 1) * (col + 0) / this->ncols; + *y_out = (this->viewport_h + 1) * (row + 0) / this->nrows; + *w_out = (this->viewport_w + 1) * (col + 1) / this->ncols - *x_out - 1; + *h_out = (this->viewport_h + 1) * (row + 1) / this->nrows - *y_out - 1; + *r_out = row; + *c_out = col; +} + + +static int +fit_magnification(CharTable *this, const char *text, int text_len, int width, int height, struct glyph_layout *layout_out) +{ + const int max_w = width - CELL_X_CLEARANCE * 2; + const int max_h = height - CELL_Y_CLEARANCE * 2; + int font_size = this->font_size * PANGO_SCALE; + PangoFontDescription *font; + double xscale, yscale; + PangoRectangle ink, logical; + int x1i, x2i, y1i, y2i; + int x1l, x2l, y1l, y2l; + int x1, x2, y1, y2; + + font = pango_font_description_copy(this->font); + if (!font) { + weprintf("pango_font_description_copy:"); + return -1; + } + + pango_font_description_set_size(font, MAX(font_size, 1)); + pango_layout_set_text(this->magnification_layout, text, text_len); + pango_layout_set_font_description(this->magnification_layout, font); + pango_layout_context_changed(this->magnification_layout); + pango_font_description_free(font); + pango_layout_get_pixel_extents(this->magnification_layout, &ink, &logical); + + x1i = MAX(ink.x, 0); + y1i = MAX(ink.y, 0); + x1l = MAX(logical.x, 0); + y1l = MAX(logical.y, 0); + x2i = x1i + ink.width; + y2i = y1i + ink.height; + x2l = x1l + logical.width; + y2l = y1l + logical.height; + x1 = MIN(x1i, x1l); + y1 = MIN(y1i, y1l); + x2 = MAX(x2i, x2l); + y2 = MAX(y2i, y2l); + + layout_out->x = (double)MAX(x1 - ink.x, 0); + layout_out->y = (double)MAX(y1 - ink.y, 0); + layout_out->width = (double)(x2 - x1); + layout_out->height = (double)(y2 - y1); + + if (layout_out->width > 0.0 && layout_out->height > 0.0) { + xscale = (double)max_w / (double)layout_out->width; + yscale = (double)max_h / (double)layout_out->height; + layout_out->scale = MIN(xscale, yscale); + } else { + layout_out->scale = 1.0; + } + + layout_out->x *= layout_out->scale; + layout_out->y *= layout_out->scale; + layout_out->width *= layout_out->scale; + layout_out->height *= layout_out->scale; + return 0; +} + + +static void +draw_magnification(CharTable *this, cairo_t *g, int width, int height) +{ + GtkStyle *style = gtk_widget_get_style(GTK_WIDGET(this)); + char text[TEXT_LIMIT]; + int text_len; + struct glyph_layout layout; + const double w = (double)width; + const double h = (double)height; + + set_source_colour(g, &style->base[GTK_STATE_NORMAL]); + cairo_paint(g); + if (!this->nchars) + return; + + text_len = format_character(get_codepoint(this, this->selected), text); + if (fit_magnification(this, text, text_len, width, height, &layout) < 0) + return; + + set_source_colour_blend(g, &style->base[GTK_STATE_NORMAL], 0.05, &style->text[GTK_STATE_NORMAL]); + cairo_set_line_width(g, 1.0); + cairo_move_to(g, 0.0, 0.5); + cairo_line_to(g, w, 0.5); + cairo_stroke(g); + cairo_move_to(g, 0.0, h - 0.5); + cairo_line_to(g, w, h - 0.5); + cairo_stroke(g); + cairo_move_to(g, 0.5, 0.0); + cairo_line_to(g, 0.5, h); + cairo_stroke(g); + cairo_move_to(g, w - 0.5, 0.0); + cairo_line_to(g, w - 0.5, h); + cairo_stroke(g); + + set_source_colour(g, &style->text[GTK_STATE_NORMAL]); + cairo_save(g); + cairo_translate(g, 0.5 * (w - layout.width) + layout.x, + 0.5 * (h - layout.height) + layout.y); + cairo_scale(g, layout.scale, layout.scale); + pango_cairo_show_layout(g, this->magnification_layout); + cairo_restore(g); +} + + +static gboolean +gcmap_char_table_magnification_expose(GtkWidget *widget, GdkEventExpose *event, void *user_data) +{ + CharTable *this = user_data; + cairo_t *g = gdk_cairo_create(widget->window); + if (!g) { + weprintf("gdk_cairo_create:"); + return FALSE; + } + gdk_cairo_rectangle(g, &event->area); + cairo_clip(g); + draw_magnification(this, g, MAGNIFICATION_WIDTH, MAGNIFICATION_HEIGHT); + cairo_destroy(g); + return FALSE; +} + + +static void +gcmap_char_table_update_magnification(CharTable *this) +{ + GtkWidget *widget = GTK_WIDGET(this); + GdkRectangle monitor; + GdkScreen *screen; + int monitor_num, x, y, cw, ch; + int cx1, cx2, cy1, cy2, col, row; + int mx1, mx2, my1, my2; + + if (!this->magnification_window || + !(this->shift_keys || this->right_button_down) || + !this->nchars || !GTK_WIDGET_REALIZED(widget)) { + if (this->magnification_window) + gtk_widget_hide(this->magnification_window); + return; + } + + get_selected_cell_screen_rectangle(this, &cx1, &cy1, &cw, &ch, &col, &row); + gdk_window_get_origin(widget->window, &x, &y); + cx1 += x; + cy1 += y; + cx2 = cx1 + cw; + cy2 = cy1 + ch; + + x = cx1 + cw / 2; + y = cy1 + ch / 2; + screen = gtk_widget_get_screen(widget); + gtk_window_set_screen(GTK_WINDOW(this->magnification_window), screen); + monitor_num = gdk_screen_get_monitor_at_point(screen, x, y); + gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor); + mx1 = monitor.x; + my1 = monitor.y; + mx2 = mx1 + monitor.width; + my2 = my1 + monitor.height; + + mx2 -= MAGNIFICATION_WIDTH; + my2 -= MAGNIFICATION_HEIGHT; + + cx1 -= 1; + x = cx1 > mx2 ? cx2 - MAGNIFICATION_WIDTH : cx1; + y = cy2 > my2 ? cy1 - MAGNIFICATION_HEIGHT : cy2; + x = MIN(x, mx2); + y = MIN(y, my2); + x = MAX(x, mx1); + y = MAX(y, my1); + + gtk_window_move(GTK_WINDOW(this->magnification_window), x, y); + gtk_widget_show_all(this->magnification_window); + gtk_widget_queue_draw(this->magnification); +} + + +static void update_cell_size(GtkWidget *widget, int ranges_changed) { CharTable *this = GCMAP_CHAR_TABLE(widget); @@ -310,6 +566,28 @@ update_cell_size(GtkWidget *widget, int ranges_changed) static void +gcmap_char_table_scroll_selected_cell_into_view(CharTable *this) +{ + int selected_row, viewport_row, yadj_int; + double yadj; + + if (!this->vadjustment || !this->nchars) + return; + + yadj = round(this->vadjustment->value); + yadj_int = (int)yadj; + + selected_row = (int)(this->selected / (uint32_t)this->ncols); + viewport_row = MAX(yadj_int, 0); + + if (selected_row < viewport_row) + gtk_adjustment_set_value(this->vadjustment, (double)selected_row); + else if (selected_row >= viewport_row + this->nrows) + gtk_adjustment_set_value(this->vadjustment, (double)(selected_row - this->nrows + 1)); +} + + +static void update_content_size(CharTable *this) { GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(this)); @@ -317,8 +595,17 @@ update_content_size(CharTable *this) if (parent && LOGICAL_IS_SCROLL_PANE(parent)) { nrows = (int)(this->nchars / (uint32_t)this->ncols); nrows += this->nchars % (uint32_t)this->ncols > 0u; + + if (this->vadjustment_value_changed_id) + g_signal_handler_block(this->vadjustment, this->vadjustment_value_changed_id); + logical_scroll_pane_set_content_size(LOGICAL_SCROLL_PANE(parent), this->ncols, nrows); logical_scroll_pane_set_viewport_size(LOGICAL_SCROLL_PANE(parent), this->ncols, this->nrows); + + if (this->vadjustment_value_changed_id) + g_signal_handler_unblock(this->vadjustment, this->vadjustment_value_changed_id); + gcmap_char_table_scroll_selected_cell_into_view(this); + gcmap_char_table_update_magnification(this); } } @@ -333,12 +620,48 @@ gcmap_char_table_size_request(GtkWidget *widget, GtkRequisition *requisition) static void +gcmap_char_table_drag_begin(GtkWidget *widget, GdkDragContext *context, void *user_data) +{ + CharTable *this = GCMAP_CHAR_TABLE(widget); + GdkPixmap *pixmap; + cairo_t *g; + uint32_t codepoint; + + (void) user_data; + + if (!this->nchars) + return; + + codepoint = get_codepoint(this, this->selected); + if (!is_valid_codepoint(codepoint)) + return; + + pixmap = gdk_pixmap_new(widget->window, DRAG_WIDTH, DRAG_HEIGHT, -1); + if (!pixmap) + return; + + g = gdk_cairo_create(pixmap); + if (!g) { + g_object_unref(pixmap); + return; + } + draw_magnification(this, g, DRAG_WIDTH, DRAG_HEIGHT); + cairo_destroy(g); + + gtk_drag_set_icon_pixmap(context, gtk_widget_get_colormap(widget), pixmap, NULL, DRAG_X, DRAG_Y); + g_object_unref(pixmap); + + this->shift_keys = 0u; + this->right_button_down = 0; + gcmap_char_table_update_magnification(this); +} + + +static int select_clicked_character(GtkWidget *widget, int meta, unsigned click_count, double x, double y) { CharTable *this = GCMAP_CHAR_TABLE(widget); - LogicalScrollPane *scrolls = LOGICAL_SCROLL_PANE(gtk_widget_get_parent(widget)); - GtkAdjustment *vadjustment = logical_scroll_pane_get_vadjustment(scrolls); - double yadj = round(vadjustment->value); + double yadj = this->vadjustment ? round(this->vadjustment->value) : 0.0; int r0 = (int)yadj; int r = (int)y * this->nrows / (this->viewport_h + 1); int c = (int)x * this->ncols / (this->viewport_w + 1); @@ -348,39 +671,120 @@ select_clicked_character(GtkWidget *widget, int meta, unsigned click_count, doub selected = ((uint32_t)r + (uint32_t)r0) * (uint32_t)this->ncols + (uint32_t)c; if (selected >= this->nchars) { this->multiclick_count = 0u; + return 0; } else if (selected != this->selected) { click_count = 1u; clicked: this->selected = selected; - if (this->selection_callback) - (*this->selection_callback)(selected, get_codepoint(this, selected), meta, click_count >= 2u); + execute_selection_callback(this, selected, meta, click_count >= 2u); this->multiclick_count = click_count == 1u ? 1u : 0u; + gcmap_char_table_update_magnification(this); gtk_widget_queue_draw(GTK_WIDGET(this)); /* XXX just redraw the affected cells */ } else if (click_count > this->multiclick_count) { goto clicked; } else { this->multiclick_count = 0u; } + return 1; } static gboolean gcmap_char_table_button_press(GtkWidget *widget, GdkEventButton *event, void *user_data) { + CharTable *this = GCMAP_CHAR_TABLE(widget); unsigned click_count; + int meta, is_a_cell; (void) user_data; - gtk_widget_grab_focus(widget); - if (event->button == 1 || event->button == 3) { + switch (event->button) { + case 3: + this->right_button_down = 1; + this->multiclick_count = 0u; + click_count = 0; + goto select; + case 1: switch ((unsigned)event->type) { default: case GDK_BUTTON_PRESS: click_count = 1u; break; case GDK_2BUTTON_PRESS: click_count = 2u; break; case GDK_3BUTTON_PRESS: click_count = 3u; break; } - select_clicked_character(widget, !!(event->state & GDK_MOD1_MASK), click_count, event->x, event->y); - } else { - GCMAP_CHAR_TABLE(widget)->multiclick_count = 0u; + select: + gtk_widget_grab_focus(widget); + meta = !!(event->state & GDK_MOD1_MASK); + is_a_cell = select_clicked_character(widget, meta, click_count, event->x, event->y); + gcmap_char_table_update_magnification(this); + if (is_a_cell && event->button == 1 && event->type == GDK_BUTTON_PRESS) { + this->drag_x = (int)event->x; + this->drag_y = (int)event->y; + this->drag_stage = 1; + } + return TRUE; + default: + this->multiclick_count = 0u; + return event->button != 2; } +} + + +static gboolean +gcmap_char_table_button_release(GtkWidget *widget, GdkEventButton *event, void *user_data) +{ + CharTable *this = GCMAP_CHAR_TABLE(widget); + (void) user_data; + if (event->button == 1) + this->drag_stage = 0; + if (event->button != 3) + return FALSE; + this->right_button_down = 0; + gcmap_char_table_update_magnification(this); + return TRUE; +} + + +static gboolean +gcmap_char_table_motion_notify(GtkWidget *widget, GdkEventMotion *event, void *user_data) +{ + CharTable *this = GCMAP_CHAR_TABLE(widget); + uint32_t codepoint; + GdkDragContext *drag_ctx; + + this->pointer_cell = UINT32_MAX; + + if (this->drag_stage == 1 && (event->state & GDK_BUTTON1_MASK)) { + if (!gtk_drag_check_threshold(widget, this->drag_x, this->drag_y, (int)event->x, (int)event->y)) + goto not_starting_drag; + this->drag_stage = 2; + + if (!this->nchars) + goto cannot_drag; + + codepoint = get_codepoint(this, this->selected); + if (this->selection_callback) + (*this->selection_callback)(this->selected, codepoint, DRAG_ANNOUNCEMENT); + if (!is_valid_codepoint(codepoint)) + goto cannot_drag; + + drag_ctx = gtk_drag_begin(widget, this->drag_targets, GDK_ACTION_COPY, 1, (GdkEvent *)event); + if (!drag_ctx) + goto cannot_drag; + + gcmap_char_table_drag_begin(widget, drag_ctx, user_data); + return TRUE; + + cannot_drag: + this->drag_stage = 0; + } + +not_starting_drag: + if (!this->right_button_down) + return FALSE; + if (!(event->state & GDK_BUTTON3_MASK)) { + this->right_button_down = 0; + gcmap_char_table_update_magnification(this); + return FALSE; + } + select_clicked_character(widget, !!(event->state & GDK_MOD1_MASK), 1u, event->x, event->y); return TRUE; } @@ -393,8 +797,14 @@ gcmap_char_table_key_press(GtkWidget *widget, GdkEventKey *event, void *user_dat (void) user_data; - if (event->state & GDK_CONTROL_MASK) - return FALSE; + if (IS_SHIFT_KEY(event->keyval)) { + this->shift_keys |= SHIFT_KEY(event->keyval); + gcmap_char_table_update_magnification(this); + return TRUE; + } + + if (event->state & GDK_CONTROL_MASK) + return FALSE; meta = !!(event->state & GDK_MOD1_MASK); @@ -408,8 +818,7 @@ gcmap_char_table_key_press(GtkWidget *widget, GdkEventKey *event, void *user_dat case GDK_KEY_Return: case GDK_KEY_KP_Enter: case GDK_KEY_Begin: case GDK_KEY_KP_Begin: case GDK_KEY_space: - if (this->selection_callback) - (*this->selection_callback)(this->selected, get_codepoint(this, this->selected), meta, 1); + execute_selection_callback(this, this->selected, meta, 1); break; default: return FALSE; @@ -419,10 +828,135 @@ gcmap_char_table_key_press(GtkWidget *widget, GdkEventKey *event, void *user_dat } +static gboolean +gcmap_char_table_key_release(GtkWidget *widget, GdkEventKey *event, void *user_data) +{ + CharTable *this = GCMAP_CHAR_TABLE(widget); + (void) user_data; + if (IS_SHIFT_KEY(event->keyval)) { + this->shift_keys &= ~SHIFT_KEY(event->keyval); + gcmap_char_table_update_magnification(this); + return TRUE; + } + return FALSE; +} + + +static gboolean +gcmap_char_table_focus_out(GtkWidget *widget, GdkEventFocus *event, void *user_data) +{ + CharTable *this = GCMAP_CHAR_TABLE(widget); + (void) event; + (void) user_data; + this->shift_keys = 0u; + this->right_button_down = 0; + gcmap_char_table_update_magnification(this); + return FALSE; +} + + +static gboolean +gcmap_char_table_focus_in(GtkWidget *widget, GdkEventFocus *event, void *user_data) +{ + CharTable *this = GCMAP_CHAR_TABLE(widget); + GdkModifierType modifiers; + (void) event; + (void) user_data; + gdk_window_get_pointer(widget->window, NULL, NULL, &modifiers); + this->shift_keys = modifiers & GDK_SHIFT_MASK ? 1u : 0u; + gcmap_char_table_update_magnification(this); + return FALSE; +} + + +static void +gcmap_char_table_vadjustment_value_changed(GtkAdjustment *adjustment, CharTable *this) +{ + uint32_t first, last, column, cell; + double yadj; + int row, yadj_int; + + if (!this->nchars) + return; + + yadj = round(adjustment->value); + yadj_int = (int)yadj; + + row = MAX(yadj_int, 0); + first = (uint32_t)row * (uint32_t)this->ncols; + last = first + (uint32_t)this->nrows * (uint32_t)this->ncols - 1u; + last = MIN(last, this->nchars - 1u); + column = this->selected % (uint32_t)this->ncols; + + if (this->selected < first) { + cell = first + column; + } else if (this->selected > last) { + cell = last - (last % (uint32_t)this->ncols); + cell += column; + } else { + gcmap_char_table_update_magnification(this); + gtk_widget_queue_draw(GTK_WIDGET(this)); + return; + } + + cell = MIN(cell, last); + gcmap_char_table_select_cell(this, (int32_t)cell, 1, 0); +} + + +static void +gcmap_char_table_parent_set(GtkWidget *widget, GtkWidget *old_parent, void *user_data) +{ + CharTable *this = GCMAP_CHAR_TABLE(widget); + GtkWidget *parent = gtk_widget_get_parent(widget); + + (void) old_parent; + (void) user_data; + + if (this->vadjustment_value_changed_id) { + g_signal_handler_disconnect(this->vadjustment, this->vadjustment_value_changed_id); + this->vadjustment_value_changed_id = 0u; + } + this->vadjustment = NULL; + + if (!parent || !LOGICAL_IS_SCROLL_PANE(parent)) + return; + + this->vadjustment = logical_scroll_pane_get_vadjustment(LOGICAL_SCROLL_PANE(parent)); + this->vadjustment_value_changed_id = g_signal_connect(this->vadjustment, "value-changed", + G_CALLBACK(&gcmap_char_table_vadjustment_value_changed), this); +} + + +static void +gcmap_char_table_drag_data_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection_data, + unsigned int info, unsigned int time, void *user_data) +{ + CharTable *this = GCMAP_CHAR_TABLE(widget); + char text[TEXT_LIMIT]; + uint32_t codepoint; + int text_len; + (void) context; + (void) info; + (void) time; + (void) user_data; + if (!this->nchars) + return; + codepoint = get_codepoint(this, this->selected); + if (!is_valid_codepoint(codepoint)) + return; + text_len = g_unichar_to_utf8((gunichar)codepoint, text); + gtk_selection_data_set_text(selection_data, text, text_len); +} + + + static void gcmap_char_table_init(CharTable *this) { + static char utf8_string[] = "UTF8_STRING"; GtkWidget *widget = GTK_WIDGET(this); + GtkTargetEntry drag_target = {utf8_string, 0, 0}; this->nrows = 1; this->ncols = 1; @@ -433,10 +967,21 @@ gcmap_char_table_init(CharTable *this) this->selected = 0u; this->ranges = NULL; this->nranges = 0u; + this->magnification_layout = NULL; + this->magnification_window = NULL; + this->magnification = NULL; + this->vadjustment = NULL; + this->vadjustment_value_changed_id = 0u; + this->shift_keys = 0u; + this->right_button_down = 0; + this->pointer_cell = UINT32_MAX; this->viewport_w = 1; this->viewport_h = 1; this->snap_cols_to_2pow = 0; this->snap_rows_to_2pow = 0; + this->drag_x = 0; + this->drag_y = 0; + this->drag_stage = 0; this->multiclick_count = 0u; this->selection_callback = NULL; @@ -445,11 +990,26 @@ gcmap_char_table_init(CharTable *this) gtk_widget_add_events(widget, GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON3_MOTION_MASK | GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK); + this->drag_targets = gtk_target_list_new(&drag_target, 1u); + if (!this->drag_targets) + eprintf("gtk_target_list_new:"); + + g_signal_connect(widget, "drag-data-get", G_CALLBACK(&gcmap_char_table_drag_data_get), NULL); g_signal_connect(widget, "button-press-event", G_CALLBACK(&gcmap_char_table_button_press), NULL); + g_signal_connect(widget, "button-release-event", G_CALLBACK(&gcmap_char_table_button_release), NULL); + g_signal_connect(widget, "motion-notify-event", G_CALLBACK(&gcmap_char_table_motion_notify), NULL); g_signal_connect(widget, "key-press-event", G_CALLBACK(&gcmap_char_table_key_press), NULL); + g_signal_connect(widget, "key-release-event", G_CALLBACK(&gcmap_char_table_key_release), NULL); + g_signal_connect(widget, "focus-in-event", G_CALLBACK(&gcmap_char_table_focus_in), NULL); + g_signal_connect(widget, "focus-out-event", G_CALLBACK(&gcmap_char_table_focus_out), NULL); + g_signal_connect(widget, "parent-set", G_CALLBACK(&gcmap_char_table_parent_set), NULL); } @@ -457,7 +1017,15 @@ static void gcmap_char_table_dispose(GObject *object) { CharTable *this = GCMAP_CHAR_TABLE(object); + if (this->magnification_window) { + gtk_widget_destroy(this->magnification_window); + this->magnification_window = NULL; + this->magnification = NULL; + } + g_clear_object(&this->magnification_layout); g_clear_object(&this->layout); + gtk_target_list_unref(this->drag_targets); + this->drag_targets = NULL; G_OBJECT_CLASS(gcmap_char_table_parent_class)->dispose(object); } @@ -491,6 +1059,7 @@ gcmap_char_table_set_font_family(CharTable *this, const char *family) pango_layout_context_changed(this->layout); update_cell_size(GTK_WIDGET(this), 0); update_content_size(this); + gcmap_char_table_update_magnification(this); gtk_widget_queue_draw(GTK_WIDGET(this)); } @@ -504,6 +1073,7 @@ gcmap_char_table_set_font_weight(CharTable *this, PangoWeight weight) pango_font_description_set_weight(this->font, this->font_weight); pango_layout_set_font_description(this->layout, this->font); pango_layout_context_changed(this->layout); + gcmap_char_table_update_magnification(this); gtk_widget_queue_draw(GTK_WIDGET(this)); } @@ -517,6 +1087,7 @@ gcmap_char_table_set_font_style(CharTable *this, PangoStyle style) pango_font_description_set_style(this->font, this->font_style); pango_layout_set_font_description(this->layout, this->font); pango_layout_context_changed(this->layout); + gcmap_char_table_update_magnification(this); gtk_widget_queue_draw(GTK_WIDGET(this)); } @@ -534,11 +1105,75 @@ gcmap_char_table_set_font_size(CharTable *this, int size) widget = GTK_WIDGET(this); update_cell_size(widget, 0); update_content_size(this); + gcmap_char_table_update_magnification(this); gtk_widget_queue_draw(widget); } void +gcmap_char_table_set_font_size_at_pointer(CharTable *this, int size, double x, double y) +{ + int row, col, row0, px, py, prow, crow; + double value, upper, yadj; + uint32_t cell; + + if (!this->nchars || !this->vadjustment || this->font_size == MAX(size, 1)) { + gcmap_char_table_set_font_size(this, size); + return; + } + + px = (int)x; + py = (int)y; + + if (this->pointer_cell < this->nchars) { + cell = this->pointer_cell; + } else { + yadj = round(MAX(this->vadjustment->value, 0.0)); + row0 = (int)yadj; + + row = py * this->nrows / (this->viewport_h + 1); + col = px * this->ncols / (this->viewport_w + 1); + row = MAX(row, 0); + col = MAX(col, 0); + row = MIN(row, this->nrows - 1); + col = MIN(col, this->ncols - 1); + + cell = ((uint32_t)row + (uint32_t)row0) * (uint32_t)this->ncols + (uint32_t)col; + cell = MIN(cell, this->nchars - 1u); + this->pointer_cell = cell; + } + + gcmap_char_table_set_font_size(this, size); + + yadj = round(MAX(this->vadjustment->value, 0.0)); + row0 = (int)yadj; + crow = (int)(cell / (uint32_t)this->ncols) - row0; + crow = MAX(crow, 0); + prow = py * this->nrows / (this->viewport_h + 1); + prow = MAX(prow, 0); + prow = MIN(prow, this->nrows - 1); + row0 = crow - prow + row0; + + row = (int)(this->selected / (uint32_t)this->ncols); + if (row < row0) { + this->selected = (uint32_t)row0 * (uint32_t)this->ncols; + } else if (row >= row0 + this->nrows) { + this->selected = (uint32_t)row0 + (uint32_t)this->nrows; + this->selected *= (uint32_t)this->ncols; + if (this->selected) + this->selected -= 1u; + } + this->selected = MIN(this->selected, this->nchars); + + value = (double)row0; + upper = this->vadjustment->upper - this->vadjustment->page_size; + value = MAX(value, this->vadjustment->lower); + value = MIN(value, upper); + gtk_adjustment_set_value(this->vadjustment, value); +} + + +void gcmap_char_table_set_snap_cols_to_2pow(CharTable *this, int active) { GtkWidget *widget = GTK_WIDGET(this); @@ -564,16 +1199,23 @@ void gcmap_char_table_set_ranges(CharTable *this, const struct libcmap_range *ranges, size_t nranges) { GtkWidget *widget; + uint32_t codepoint, selected; + if (this->ranges == ranges && this->nranges == nranges) return; + + codepoint = this->nchars ? get_codepoint(this, this->selected) : 0u; this->ranges = ranges; this->nranges = nranges; this->selected = 0u; + if (this->nchars && find_codepoint_index(this, codepoint, &selected)) + this->selected = selected; + widget = GTK_WIDGET(this); update_cell_size(widget, 1); update_content_size(this); - if (this->selection_callback) - (*this->selection_callback)(this->selected, get_codepoint(this, this->selected), 0, 0); + execute_selection_callback(this, this->selected, 0, 0); + gcmap_char_table_update_magnification(this); gtk_widget_queue_draw(widget); } @@ -585,6 +1227,7 @@ gcmap_char_table_viewport_updated(CharTable *this, int viewport_w, int viewport_ this->viewport_h = viewport_h; update_cell_size(GTK_WIDGET(this), 0); update_content_size(this); + gcmap_char_table_update_magnification(this); } @@ -609,6 +1252,20 @@ gcmap_char_table_new(const char *font_family, int font_size, PangoWeight font_we this->layout = gtk_widget_create_pango_layout(widget, ""); if (!this->layout) eprintf("gtk_widget_create_pango_layout:"); + this->magnification_layout = gtk_widget_create_pango_layout(widget, ""); + if (!this->magnification_layout) + eprintf("gtk_widget_create_pango_layout:"); + + this->magnification_window = gtk_window_new(GTK_WINDOW_POPUP); + gtk_window_set_wmclass(GTK_WINDOW(this->magnification_window), "gcmap", "gcmap"); + gtk_window_set_role(GTK_WINDOW(this->magnification_window), "magnification"); + gtk_window_set_decorated(GTK_WINDOW(this->magnification_window), FALSE); + gtk_window_set_resizable(GTK_WINDOW(this->magnification_window), FALSE); + gtk_window_set_type_hint(GTK_WINDOW(this->magnification_window), GDK_WINDOW_TYPE_HINT_TOOLTIP); + this->magnification = gtk_drawing_area_new(); + gtk_widget_set_size_request(this->magnification, MAGNIFICATION_WIDTH, MAGNIFICATION_HEIGHT); + gtk_container_add(GTK_CONTAINER(this->magnification_window), this->magnification); + g_signal_connect(this->magnification, "expose-event", G_CALLBACK(&gcmap_char_table_magnification_expose), this); return widget; } @@ -618,9 +1275,7 @@ void gcmap_char_table_select_cell(CharTable *this, int32_t selected, int clamp, int meta) { GtkWidget *widget = GTK_WIDGET(this); - LogicalScrollPane *scrolls = LOGICAL_SCROLL_PANE(gtk_widget_get_parent(widget)); - GtkAdjustment *vadjustment = logical_scroll_pane_get_vadjustment(scrolls); - double yadj = round(vadjustment->value); + double yadj = this->vadjustment ? round(this->vadjustment->value) : 0.0; int r0 = MAX((int)yadj, 0); int r = r0; int32_t first, last; @@ -657,14 +1312,14 @@ gcmap_char_table_select_cell(CharTable *this, int32_t selected, int clamp, int m } this->selected = (uint32_t)selected; - if (this->selection_callback) - (*this->selection_callback)(this->selected, get_codepoint(this, this->selected), meta, 0); + execute_selection_callback(this, this->selected, meta, 0); + gcmap_char_table_update_magnification(this); if (r == r0) { gtk_widget_queue_draw(widget); /* XXX just redraw the affected cells */ - } else { + } else if (this->vadjustment) { yadj = (double)r; - gtk_adjustment_set_value(vadjustment, yadj); + gtk_adjustment_set_value(this->vadjustment, yadj); } } @@ -680,19 +1335,10 @@ gcmap_char_table_adjust_cell_selection(CharTable *this, int32_t adjustment, int int gcmap_char_table_select_codepoint(CharTable *this, uint32_t codepoint) { - size_t i = 0u; - uint32_t index = 0u; - - while (i < this->nranges && codepoint > this->ranges[i].last) { - index += (uint32_t)LIBCMAP_RANGE_SIZE(&this->ranges[i]); - i += 1u; + uint32_t index; + if (find_codepoint_index(this, codepoint, &index)) { + gcmap_char_table_select_cell(this, (int32_t)index, 0, 0); + return 1; } - - if (i >= this->nranges || codepoint < this->ranges[i].first) - return 0; - - index += codepoint - this->ranges[i].first; - - gcmap_char_table_select_cell(this, (int32_t)index, 0, 0); - return 1; + return 0; } |
