diff options
Diffstat (limited to '')
| -rw-r--r-- | char-table.c | 325 |
1 files changed, 265 insertions, 60 deletions
diff --git a/char-table.c b/char-table.c index 2d877d9..ab3f73d 100644 --- a/char-table.c +++ b/char-table.c @@ -8,7 +8,12 @@ #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)) @@ -42,26 +47,30 @@ 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 int -format_character(gunichar ch, char *buf) -{ - int len = is_printable_character(ch) ? g_unichar_to_utf8(ch, buf) : 0; buf[len] = '\0'; return len; } @@ -109,6 +118,22 @@ find_codepoint_index(CharTable *this, uint32_t codepoint, uint32_t *index_out) 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) { double size, ok_size = 1.0; @@ -187,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; @@ -274,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); @@ -318,10 +343,10 @@ get_selected_cell_screen_rectangle(CharTable *this, int *x_out, int *y_out, int static int -fit_magnification(CharTable *this, const char *text, int text_len, struct glyph_layout *layout_out) +fit_magnification(CharTable *this, const char *text, int text_len, int width, int height, struct glyph_layout *layout_out) { - const int max_w = MAGNIFICATION_WIDTH - CELL_X_CLEARANCE * 2; - const int max_h = MAGNIFICATION_HEIGHT - CELL_Y_CLEARANCE * 2; + 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; @@ -377,35 +402,24 @@ fit_magnification(CharTable *this, const char *text, int text_len, struct glyph_ } -static gboolean -gcmap_char_table_magnification_expose(GtkWidget *widget, GdkEventExpose *event, void *user_data) +static void +draw_magnification(CharTable *this, cairo_t *g, int width, int height) { - CharTable *this = user_data; GtkStyle *style = gtk_widget_get_style(GTK_WIDGET(this)); - cairo_t *g; - char text[16]; + char text[TEXT_LIMIT]; int text_len; struct glyph_layout layout; - const double w = (double)MAGNIFICATION_WIDTH; - const double h = (double)MAGNIFICATION_HEIGHT; - - g = gdk_cairo_create(widget->window); - if (!g) { - weprintf("gdk_cairo_create:"); - return FALSE; - } - - text_len = format_character((gunichar)get_codepoint(this, this->selected), text); - if (fit_magnification(this, text, text_len, &layout) < 0) - return FALSE; + const double w = (double)width; + const double h = (double)height; - gdk_cairo_rectangle(g, &event->area); - cairo_clip(g); set_source_colour(g, &style->base[GTK_STATE_NORMAL]); cairo_paint(g); - if (!this->nchars) - goto out; + 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); @@ -424,13 +438,26 @@ gcmap_char_table_magnification_expose(GtkWidget *widget, GdkEventExpose *event, set_source_colour(g, &style->text[GTK_STATE_NORMAL]); cairo_save(g); - cairo_translate(g, 0.5 * ((double)MAGNIFICATION_WIDTH - layout.width) + layout.x, - 0.5 * ((double)MAGNIFICATION_HEIGHT - layout.height) + layout.y); + 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); +} -out: + +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; } @@ -593,6 +620,44 @@ 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); @@ -606,12 +671,12 @@ 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 */ @@ -620,6 +685,7 @@ select_clicked_character(GtkWidget *widget, int meta, unsigned click_count, doub } else { this->multiclick_count = 0u; } + return 1; } @@ -628,23 +694,33 @@ gcmap_char_table_button_press(GtkWidget *widget, GdkEventButton *event, void *us { 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; + /* fall through */ + case 1: + gtk_widget_grab_focus(widget); 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; } - if (event->button == 3) - this->right_button_down = 1; - select_clicked_character(widget, !!(event->state & GDK_MOD1_MASK), click_count, event->x, event->y); + 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); - } else { + 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; } - return TRUE; } @@ -653,6 +729,8 @@ gcmap_char_table_button_release(GtkWidget *widget, GdkEventButton *event, void * { 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; @@ -665,7 +743,37 @@ static gboolean gcmap_char_table_motion_notify(GtkWidget *widget, GdkEventMotion *event, void *user_data) { CharTable *this = GCMAP_CHAR_TABLE(widget); - (void) user_data; + 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)) { @@ -707,8 +815,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; @@ -819,9 +926,34 @@ gcmap_char_table_parent_set(GtkWidget *widget, GtkWidget *old_parent, void *user 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; @@ -839,10 +971,14 @@ gcmap_char_table_init(CharTable *this) 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; @@ -852,11 +988,17 @@ 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); @@ -879,6 +1021,8 @@ gcmap_char_table_dispose(GObject *object) } 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); } @@ -964,6 +1108,69 @@ gcmap_char_table_set_font_size(CharTable *this, int size) 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); @@ -1004,8 +1211,7 @@ gcmap_char_table_set_ranges(CharTable *this, const struct libcmap_range *ranges, 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); } @@ -1103,8 +1309,7 @@ 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) { |
