weston/clients/editor.c
Ander Conselvan de Oliveira 8e37d96bf7 editor: Fix cursor positioning with pointer and touch
The calculation off the vertical offset between the widget coordinates
and where the text was rendered was wrong. It was using the constant for
horizontal offset for that too.

Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=78411
2014-05-09 15:32:20 -07:00

1388 lines
34 KiB
C

/*
* Copyright © 2012 Openismus GmbH
* Copyright © 2012 Intel Corporation
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
* that the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of the copyright holders not be used in
* advertising or publicity pertaining to distribution of the software
* without specific, written prior permission. The copyright holders make
* no representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied warranty.
*
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
* SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <linux/input.h>
#include <cairo.h>
#include <pango/pangocairo.h>
#include "window.h"
#include "text-client-protocol.h"
struct text_entry {
struct widget *widget;
struct window *window;
char *text;
int active;
uint32_t cursor;
uint32_t anchor;
struct {
char *text;
int32_t cursor;
char *commit;
PangoAttrList *attr_list;
} preedit;
struct {
PangoAttrList *attr_list;
int32_t cursor;
} preedit_info;
struct {
int32_t cursor;
int32_t anchor;
uint32_t delete_index;
uint32_t delete_length;
bool invalid_delete;
} pending_commit;
struct wl_text_input *text_input;
PangoLayout *layout;
struct {
xkb_mod_mask_t shift_mask;
} keysym;
uint32_t serial;
uint32_t reset_serial;
uint32_t content_purpose;
uint32_t click_to_show;
char *preferred_language;
bool button_pressed;
};
struct editor {
struct wl_text_input_manager *text_input_manager;
struct display *display;
struct window *window;
struct widget *widget;
struct text_entry *entry;
struct text_entry *editor;
struct text_entry *active_entry;
};
static const char *
utf8_end_char(const char *p)
{
while ((*p & 0xc0) == 0x80)
p++;
return p;
}
static const char *
utf8_prev_char(const char *s, const char *p)
{
for (--p; p >= s; --p) {
if ((*p & 0xc0) != 0x80)
return p;
}
return NULL;
}
static const char *
utf8_next_char(const char *p)
{
if (*p != 0)
return utf8_end_char(++p);
return NULL;
}
static void
move_up(const char *p, uint32_t *cursor)
{
const char *posr, *posr_i;
char text[16];
xkb_keysym_to_utf8(XKB_KEY_Return, text, sizeof(text));
posr = strstr(p, text);
while (posr) {
if (*cursor > (unsigned)(posr-p)) {
posr_i = strstr(posr+1, text);
if (!posr_i || !(*cursor > (unsigned)(posr_i-p))) {
*cursor = posr-p;
break;
}
posr = posr_i;
} else {
break;
}
}
}
static void
move_down(const char *p, uint32_t *cursor)
{
const char *posr;
char text[16];
xkb_keysym_to_utf8(XKB_KEY_Return, text, sizeof(text));
posr = strstr(p, text);
while (posr) {
if (*cursor <= (unsigned)(posr-p)) {
*cursor = posr-p + 1;
break;
}
posr = strstr(posr+1, text);
}
}
static void text_entry_redraw_handler(struct widget *widget, void *data);
static void text_entry_button_handler(struct widget *widget,
struct input *input, uint32_t time,
uint32_t button,
enum wl_pointer_button_state state, void *data);
static void text_entry_touch_handler(struct widget *widget, struct input *input,
uint32_t serial, uint32_t time, int32_t id,
float tx, float ty, void *data);
static int text_entry_motion_handler(struct widget *widget,
struct input *input, uint32_t time,
float x, float y, void *data);
static void text_entry_insert_at_cursor(struct text_entry *entry, const char *text,
int32_t cursor, int32_t anchor);
static void text_entry_set_preedit(struct text_entry *entry,
const char *preedit_text,
int preedit_cursor);
static void text_entry_delete_text(struct text_entry *entry,
uint32_t index, uint32_t length);
static void text_entry_delete_selected_text(struct text_entry *entry);
static void text_entry_reset_preedit(struct text_entry *entry);
static void text_entry_commit_and_reset(struct text_entry *entry);
static void text_entry_get_cursor_rectangle(struct text_entry *entry, struct rectangle *rectangle);
static void text_entry_update(struct text_entry *entry);
static void
text_input_commit_string(void *data,
struct wl_text_input *text_input,
uint32_t serial,
const char *text)
{
struct text_entry *entry = data;
if ((entry->serial - serial) > (entry->serial - entry->reset_serial)) {
fprintf(stderr, "Ignore commit. Serial: %u, Current: %u, Reset: %u\n",
serial, entry->serial, entry->reset_serial);
return;
}
if (entry->pending_commit.invalid_delete) {
fprintf(stderr, "Ignore commit. Invalid previous delete_surrounding event.\n");
memset(&entry->pending_commit, 0, sizeof entry->pending_commit);
return;
}
text_entry_reset_preedit(entry);
if (entry->pending_commit.delete_length) {
text_entry_delete_text(entry,
entry->pending_commit.delete_index,
entry->pending_commit.delete_length);
} else {
text_entry_delete_selected_text(entry);
}
text_entry_insert_at_cursor(entry, text,
entry->pending_commit.cursor,
entry->pending_commit.anchor);
memset(&entry->pending_commit, 0, sizeof entry->pending_commit);
widget_schedule_redraw(entry->widget);
}
static void
clear_pending_preedit(struct text_entry *entry)
{
memset(&entry->pending_commit, 0, sizeof entry->pending_commit);
pango_attr_list_unref(entry->preedit_info.attr_list);
entry->preedit_info.cursor = 0;
entry->preedit_info.attr_list = NULL;
memset(&entry->preedit_info, 0, sizeof entry->preedit_info);
}
static void
text_input_preedit_string(void *data,
struct wl_text_input *text_input,
uint32_t serial,
const char *text,
const char *commit)
{
struct text_entry *entry = data;
if ((entry->serial - serial) > (entry->serial - entry->reset_serial)) {
fprintf(stderr, "Ignore preedit_string. Serial: %u, Current: %u, Reset: %u\n",
serial, entry->serial, entry->reset_serial);
clear_pending_preedit(entry);
return;
}
if (entry->pending_commit.invalid_delete) {
fprintf(stderr, "Ignore preedit_string. Invalid previous delete_surrounding event.\n");
clear_pending_preedit(entry);
return;
}
if (entry->pending_commit.delete_length) {
text_entry_delete_text(entry,
entry->pending_commit.delete_index,
entry->pending_commit.delete_length);
} else {
text_entry_delete_selected_text(entry);
}
text_entry_set_preedit(entry, text, entry->preedit_info.cursor);
entry->preedit.commit = strdup(commit);
entry->preedit.attr_list = pango_attr_list_ref(entry->preedit_info.attr_list);
clear_pending_preedit(entry);
text_entry_update(entry);
widget_schedule_redraw(entry->widget);
}
static void
text_input_delete_surrounding_text(void *data,
struct wl_text_input *text_input,
int32_t index,
uint32_t length)
{
struct text_entry *entry = data;
uint32_t text_length;
entry->pending_commit.delete_index = entry->cursor + index;
entry->pending_commit.delete_length = length;
entry->pending_commit.invalid_delete = false;
text_length = strlen(entry->text);
if (entry->pending_commit.delete_index > text_length ||
length > text_length ||
entry->pending_commit.delete_index + length > text_length) {
fprintf(stderr, "delete_surrounding_text: Invalid index: %d," \
"length %u'; cursor: %u text length: %u\n", index, length, entry->cursor, text_length);
entry->pending_commit.invalid_delete = true;
return;
}
}
static void
text_input_cursor_position(void *data,
struct wl_text_input *text_input,
int32_t index,
int32_t anchor)
{
struct text_entry *entry = data;
entry->pending_commit.cursor = index;
entry->pending_commit.anchor = anchor;
}
static void
text_input_preedit_styling(void *data,
struct wl_text_input *text_input,
uint32_t index,
uint32_t length,
uint32_t style)
{
struct text_entry *entry = data;
PangoAttribute *attr1 = NULL;
PangoAttribute *attr2 = NULL;
if (!entry->preedit_info.attr_list)
entry->preedit_info.attr_list = pango_attr_list_new();
switch (style) {
case WL_TEXT_INPUT_PREEDIT_STYLE_DEFAULT:
case WL_TEXT_INPUT_PREEDIT_STYLE_UNDERLINE:
attr1 = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
break;
case WL_TEXT_INPUT_PREEDIT_STYLE_INCORRECT:
attr1 = pango_attr_underline_new(PANGO_UNDERLINE_ERROR);
attr2 = pango_attr_underline_color_new(65535, 0, 0);
break;
case WL_TEXT_INPUT_PREEDIT_STYLE_SELECTION:
attr1 = pango_attr_background_new(0.3 * 65535, 0.3 * 65535, 65535);
attr2 = pango_attr_foreground_new(65535, 65535, 65535);
break;
case WL_TEXT_INPUT_PREEDIT_STYLE_HIGHLIGHT:
case WL_TEXT_INPUT_PREEDIT_STYLE_ACTIVE:
attr1 = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
attr2 = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
break;
case WL_TEXT_INPUT_PREEDIT_STYLE_INACTIVE:
attr1 = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
attr2 = pango_attr_foreground_new(0.3 * 65535, 0.3 * 65535, 0.3 * 65535);
break;
}
if (attr1) {
attr1->start_index = entry->cursor + index;
attr1->end_index = entry->cursor + index + length;
pango_attr_list_insert(entry->preedit_info.attr_list, attr1);
}
if (attr2) {
attr2->start_index = entry->cursor + index;
attr2->end_index = entry->cursor + index + length;
pango_attr_list_insert(entry->preedit_info.attr_list, attr2);
}
}
static void
text_input_preedit_cursor(void *data,
struct wl_text_input *text_input,
int32_t index)
{
struct text_entry *entry = data;
entry->preedit_info.cursor = index;
}
static void
text_input_modifiers_map(void *data,
struct wl_text_input *text_input,
struct wl_array *map)
{
struct text_entry *entry = data;
entry->keysym.shift_mask = keysym_modifiers_get_mask(map, "Shift");
}
static void
text_input_keysym(void *data,
struct wl_text_input *text_input,
uint32_t serial,
uint32_t time,
uint32_t key,
uint32_t state,
uint32_t modifiers)
{
struct text_entry *entry = data;
const char *new_char;
if (key == XKB_KEY_Left ||
key == XKB_KEY_Right) {
if (state != WL_KEYBOARD_KEY_STATE_RELEASED)
return;
if (key == XKB_KEY_Left)
new_char = utf8_prev_char(entry->text, entry->text + entry->cursor);
else
new_char = utf8_next_char(entry->text + entry->cursor);
if (new_char != NULL) {
entry->cursor = new_char - entry->text;
}
if (!(modifiers & entry->keysym.shift_mask))
entry->anchor = entry->cursor;
widget_schedule_redraw(entry->widget);
return;
}
if (key == XKB_KEY_Up ||
key == XKB_KEY_Down) {
if (state != WL_KEYBOARD_KEY_STATE_RELEASED)
return;
if (key == XKB_KEY_Up)
move_up(entry->text, &entry->cursor);
else
move_down(entry->text, &entry->cursor);
if (!(modifiers & entry->keysym.shift_mask))
entry->anchor = entry->cursor;
widget_schedule_redraw(entry->widget);
return;
}
if (key == XKB_KEY_BackSpace) {
const char *start, *end;
if (state != WL_KEYBOARD_KEY_STATE_RELEASED)
return;
text_entry_commit_and_reset(entry);
start = utf8_prev_char(entry->text, entry->text + entry->cursor);
if (start == NULL)
return;
end = utf8_next_char(start);
text_entry_delete_text(entry,
start - entry->text,
end - start);
return;
}
if (key == XKB_KEY_Tab ||
key == XKB_KEY_KP_Enter ||
key == XKB_KEY_Return) {
char text[16];
if (state != WL_KEYBOARD_KEY_STATE_RELEASED)
return;
xkb_keysym_to_utf8(key, text, sizeof(text));
text_entry_insert_at_cursor(entry, text, 0, 0);
return;
}
}
static void
text_input_enter(void *data,
struct wl_text_input *text_input,
struct wl_surface *surface)
{
struct text_entry *entry = data;
if (surface != window_get_wl_surface(entry->window))
return;
entry->active = 1;
text_entry_update(entry);
entry->reset_serial = entry->serial;
widget_schedule_redraw(entry->widget);
}
static void
text_input_leave(void *data,
struct wl_text_input *text_input)
{
struct text_entry *entry = data;
text_entry_commit_and_reset(entry);
entry->active = 0;
wl_text_input_hide_input_panel(text_input);
widget_schedule_redraw(entry->widget);
}
static void
text_input_input_panel_state(void *data,
struct wl_text_input *text_input,
uint32_t state)
{
}
static void
text_input_language(void *data,
struct wl_text_input *text_input,
uint32_t serial,
const char *language)
{
fprintf(stderr, "input language is %s \n", language);
}
static void
text_input_text_direction(void *data,
struct wl_text_input *text_input,
uint32_t serial,
uint32_t direction)
{
struct text_entry *entry = data;
PangoContext *context = pango_layout_get_context(entry->layout);
PangoDirection pango_direction;
switch (direction) {
case WL_TEXT_INPUT_TEXT_DIRECTION_LTR:
pango_direction = PANGO_DIRECTION_LTR;
break;
case WL_TEXT_INPUT_TEXT_DIRECTION_RTL:
pango_direction = PANGO_DIRECTION_RTL;
break;
case WL_TEXT_INPUT_TEXT_DIRECTION_AUTO:
default:
pango_direction = PANGO_DIRECTION_NEUTRAL;
}
pango_context_set_base_dir(context, pango_direction);
}
static const struct wl_text_input_listener text_input_listener = {
text_input_enter,
text_input_leave,
text_input_modifiers_map,
text_input_input_panel_state,
text_input_preedit_string,
text_input_preedit_styling,
text_input_preedit_cursor,
text_input_commit_string,
text_input_cursor_position,
text_input_delete_surrounding_text,
text_input_keysym,
text_input_language,
text_input_text_direction
};
static struct text_entry*
text_entry_create(struct editor *editor, const char *text)
{
struct text_entry *entry;
entry = xmalloc(sizeof *entry);
memset(entry, 0, sizeof *entry);
entry->widget = widget_add_widget(editor->widget, entry);
entry->window = editor->window;
entry->text = strdup(text);
entry->active = 0;
entry->cursor = strlen(text);
entry->anchor = entry->cursor;
entry->text_input = wl_text_input_manager_create_text_input(editor->text_input_manager);
wl_text_input_add_listener(entry->text_input, &text_input_listener, entry);
widget_set_redraw_handler(entry->widget, text_entry_redraw_handler);
widget_set_button_handler(entry->widget, text_entry_button_handler);
widget_set_motion_handler(entry->widget, text_entry_motion_handler);
widget_set_touch_down_handler(entry->widget, text_entry_touch_handler);
return entry;
}
static void
text_entry_destroy(struct text_entry *entry)
{
widget_destroy(entry->widget);
wl_text_input_destroy(entry->text_input);
g_clear_object(&entry->layout);
free(entry->text);
free(entry);
}
static void
redraw_handler(struct widget *widget, void *data)
{
struct editor *editor = data;
cairo_surface_t *surface;
struct rectangle allocation;
cairo_t *cr;
surface = window_get_surface(editor->window);
widget_get_allocation(editor->widget, &allocation);
cr = cairo_create(surface);
cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height);
cairo_clip(cr);
cairo_translate(cr, allocation.x, allocation.y);
/* Draw background */
cairo_push_group(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_rgba(cr, 1, 1, 1, 1);
cairo_rectangle(cr, 0, 0, allocation.width, allocation.height);
cairo_fill(cr);
cairo_pop_group_to_source(cr);
cairo_paint(cr);
cairo_destroy(cr);
cairo_surface_destroy(surface);
}
static void
text_entry_allocate(struct text_entry *entry, int32_t x, int32_t y,
int32_t width, int32_t height)
{
widget_set_allocation(entry->widget, x, y, width, height);
}
static void
resize_handler(struct widget *widget,
int32_t width, int32_t height, void *data)
{
struct editor *editor = data;
struct rectangle allocation;
widget_get_allocation(editor->widget, &allocation);
text_entry_allocate(editor->entry,
allocation.x + 20, allocation.y + 20,
width - 40, height / 2 - 40);
text_entry_allocate(editor->editor,
allocation.x + 20, allocation.y + height / 2 + 20,
width - 40, height / 2 - 40);
}
static void
text_entry_activate(struct text_entry *entry,
struct wl_seat *seat)
{
struct wl_surface *surface = window_get_wl_surface(entry->window);
if (entry->click_to_show && entry->active) {
wl_text_input_show_input_panel(entry->text_input);
return;
}
if (!entry->click_to_show)
wl_text_input_show_input_panel(entry->text_input);
wl_text_input_activate(entry->text_input,
seat,
surface);
}
static void
text_entry_deactivate(struct text_entry *entry,
struct wl_seat *seat)
{
wl_text_input_deactivate(entry->text_input,
seat);
}
static void
text_entry_update_layout(struct text_entry *entry)
{
char *text;
PangoAttrList *attr_list;
assert(entry->cursor <= (strlen(entry->text) +
(entry->preedit.text ? strlen(entry->preedit.text) : 0)));
if (entry->preedit.text) {
text = malloc(strlen(entry->text) + strlen(entry->preedit.text) + 1);
strncpy(text, entry->text, entry->cursor);
strcpy(text + entry->cursor, entry->preedit.text);
strcpy(text + entry->cursor + strlen(entry->preedit.text),
entry->text + entry->cursor);
} else {
text = strdup(entry->text);
}
if (entry->cursor != entry->anchor) {
int start_index = MIN(entry->cursor, entry->anchor);
int end_index = MAX(entry->cursor, entry->anchor);
PangoAttribute *attr;
attr_list = pango_attr_list_copy(entry->preedit.attr_list);
if (!attr_list)
attr_list = pango_attr_list_new();
attr = pango_attr_background_new(0.3 * 65535, 0.3 * 65535, 65535);
attr->start_index = start_index;
attr->end_index = end_index;
pango_attr_list_insert(attr_list, attr);
attr = pango_attr_foreground_new(65535, 65535, 65535);
attr->start_index = start_index;
attr->end_index = end_index;
pango_attr_list_insert(attr_list, attr);
} else {
attr_list = pango_attr_list_ref(entry->preedit.attr_list);
}
if (entry->preedit.text && !entry->preedit.attr_list) {
PangoAttribute *attr;
if (!attr_list)
attr_list = pango_attr_list_new();
attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
attr->start_index = entry->cursor;
attr->end_index = entry->cursor + strlen(entry->preedit.text);
pango_attr_list_insert(attr_list, attr);
}
if (entry->layout) {
pango_layout_set_text(entry->layout, text, -1);
pango_layout_set_attributes(entry->layout, attr_list);
}
free(text);
pango_attr_list_unref(attr_list);
}
static void
text_entry_update(struct text_entry *entry)
{
struct rectangle cursor_rectangle;
wl_text_input_set_content_type(entry->text_input,
WL_TEXT_INPUT_CONTENT_HINT_NONE,
entry->content_purpose);
wl_text_input_set_surrounding_text(entry->text_input,
entry->text,
entry->cursor,
entry->anchor);
if (entry->preferred_language)
wl_text_input_set_preferred_language(entry->text_input,
entry->preferred_language);
text_entry_get_cursor_rectangle(entry, &cursor_rectangle);
wl_text_input_set_cursor_rectangle(entry->text_input, cursor_rectangle.x, cursor_rectangle.y,
cursor_rectangle.width, cursor_rectangle.height);
wl_text_input_commit_state(entry->text_input, ++entry->serial);
}
static void
text_entry_insert_at_cursor(struct text_entry *entry, const char *text,
int32_t cursor, int32_t anchor)
{
char *new_text = malloc(strlen(entry->text) + strlen(text) + 1);
strncpy(new_text, entry->text, entry->cursor);
strcpy(new_text + entry->cursor, text);
strcpy(new_text + entry->cursor + strlen(text),
entry->text + entry->cursor);
free(entry->text);
entry->text = new_text;
if (anchor >= 0)
entry->anchor = entry->cursor + strlen(text) + anchor;
else
entry->anchor = entry->cursor + 1 + anchor;
if (cursor >= 0)
entry->cursor += strlen(text) + cursor;
else
entry->cursor += 1 + cursor;
text_entry_update_layout(entry);
widget_schedule_redraw(entry->widget);
text_entry_update(entry);
}
static void
text_entry_reset_preedit(struct text_entry *entry)
{
entry->preedit.cursor = 0;
free(entry->preedit.text);
entry->preedit.text = NULL;
free(entry->preedit.commit);
entry->preedit.commit = NULL;
pango_attr_list_unref(entry->preedit.attr_list);
entry->preedit.attr_list = NULL;
}
static void
text_entry_commit_and_reset(struct text_entry *entry)
{
char *commit = NULL;
if (entry->preedit.commit)
commit = strdup(entry->preedit.commit);
text_entry_reset_preedit(entry);
if (commit) {
text_entry_insert_at_cursor(entry, commit, 0, 0);
free(commit);
}
wl_text_input_reset(entry->text_input);
text_entry_update(entry);
entry->reset_serial = entry->serial;
}
static void
text_entry_set_preedit(struct text_entry *entry,
const char *preedit_text,
int preedit_cursor)
{
text_entry_reset_preedit(entry);
if (!preedit_text)
return;
entry->preedit.text = strdup(preedit_text);
entry->preedit.cursor = preedit_cursor;
text_entry_update_layout(entry);
widget_schedule_redraw(entry->widget);
}
static uint32_t
text_entry_try_invoke_preedit_action(struct text_entry *entry,
int32_t x, int32_t y,
uint32_t button,
enum wl_pointer_button_state state)
{
int index, trailing;
uint32_t cursor;
const char *text;
if (!entry->preedit.text)
return 0;
pango_layout_xy_to_index(entry->layout,
x * PANGO_SCALE, y * PANGO_SCALE,
&index, &trailing);
text = pango_layout_get_text(entry->layout);
cursor = g_utf8_offset_to_pointer(text + index, trailing) - text;
if (cursor < entry->cursor ||
cursor > entry->cursor + strlen(entry->preedit.text)) {
return 0;
}
if (state == WL_POINTER_BUTTON_STATE_RELEASED)
wl_text_input_invoke_action(entry->text_input,
button,
cursor - entry->cursor);
return 1;
}
static bool
text_entry_has_preedit(struct text_entry *entry)
{
return entry->preedit.text && (strlen(entry->preedit.text) > 0);
}
static void
text_entry_set_cursor_position(struct text_entry *entry,
int32_t x, int32_t y,
bool move_anchor)
{
int index, trailing;
const char *text;
uint32_t cursor;
pango_layout_xy_to_index(entry->layout,
x * PANGO_SCALE, y * PANGO_SCALE,
&index, &trailing);
text = pango_layout_get_text(entry->layout);
cursor = g_utf8_offset_to_pointer(text + index, trailing) - text;
if (move_anchor)
entry->anchor = cursor;
if (text_entry_has_preedit(entry)) {
text_entry_commit_and_reset(entry);
assert(!text_entry_has_preedit(entry));
}
if (entry->cursor == cursor)
return;
entry->cursor = cursor;
text_entry_update_layout(entry);
widget_schedule_redraw(entry->widget);
text_entry_update(entry);
}
static void
text_entry_delete_text(struct text_entry *entry,
uint32_t index, uint32_t length)
{
uint32_t l;
assert(index <= strlen(entry->text));
assert(index + length <= strlen(entry->text));
assert(index + length >= length);
l = strlen(entry->text + index + length);
memmove(entry->text + index,
entry->text + index + length,
l + 1);
if (entry->cursor > (index + length))
entry->cursor -= length;
else if (entry->cursor > index)
entry->cursor = index;
entry->anchor = entry->cursor;
text_entry_update_layout(entry);
widget_schedule_redraw(entry->widget);
text_entry_update(entry);
}
static void
text_entry_delete_selected_text(struct text_entry *entry)
{
uint32_t start_index = entry->anchor < entry->cursor ? entry->anchor : entry->cursor;
uint32_t end_index = entry->anchor < entry->cursor ? entry->cursor : entry->anchor;
if (entry->anchor == entry->cursor)
return;
text_entry_delete_text(entry, start_index, end_index - start_index);
entry->anchor = entry->cursor;
}
static void
text_entry_get_cursor_rectangle(struct text_entry *entry, struct rectangle *rectangle)
{
struct rectangle allocation;
PangoRectangle extents;
PangoRectangle cursor_pos;
widget_get_allocation(entry->widget, &allocation);
if (entry->preedit.text && entry->preedit.cursor < 0) {
rectangle->x = 0;
rectangle->y = 0;
rectangle->width = 0;
rectangle->height = 0;
return;
}
pango_layout_get_extents(entry->layout, &extents, NULL);
pango_layout_get_cursor_pos(entry->layout,
entry->cursor + entry->preedit.cursor,
&cursor_pos, NULL);
rectangle->x = allocation.x + (allocation.height / 2) + PANGO_PIXELS(cursor_pos.x);
rectangle->y = allocation.y + 10 + PANGO_PIXELS(cursor_pos.y);
rectangle->width = PANGO_PIXELS(cursor_pos.width);
rectangle->height = PANGO_PIXELS(cursor_pos.height);
}
static void
text_entry_draw_cursor(struct text_entry *entry, cairo_t *cr)
{
PangoRectangle extents;
PangoRectangle cursor_pos;
if (entry->preedit.text && entry->preedit.cursor < 0)
return;
pango_layout_get_extents(entry->layout, &extents, NULL);
pango_layout_get_cursor_pos(entry->layout,
entry->cursor + entry->preedit.cursor,
&cursor_pos, NULL);
cairo_set_line_width(cr, 1.0);
cairo_move_to(cr, PANGO_PIXELS(cursor_pos.x), PANGO_PIXELS(cursor_pos.y));
cairo_line_to(cr, PANGO_PIXELS(cursor_pos.x), PANGO_PIXELS(cursor_pos.y) + PANGO_PIXELS(cursor_pos.height));
cairo_stroke(cr);
}
static int
text_offset_left(struct rectangle *allocation)
{
return 10;
}
static int
text_offset_top(struct rectangle *allocation)
{
return allocation->height / 2;
}
static void
text_entry_redraw_handler(struct widget *widget, void *data)
{
struct text_entry *entry = data;
cairo_surface_t *surface;
struct rectangle allocation;
cairo_t *cr;
surface = window_get_surface(entry->window);
widget_get_allocation(entry->widget, &allocation);
cr = cairo_create(surface);
cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height);
cairo_clip(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_push_group(cr);
cairo_translate(cr, allocation.x, allocation.y);
cairo_set_source_rgba(cr, 1, 1, 1, 1);
cairo_rectangle(cr, 0, 0, allocation.width, allocation.height);
cairo_fill(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
if (entry->active) {
cairo_rectangle(cr, 0, 0, allocation.width, allocation.height);
cairo_set_line_width (cr, 3);
cairo_set_source_rgba(cr, 0, 0, 1, 1.0);
cairo_stroke(cr);
}
cairo_set_source_rgba(cr, 0, 0, 0, 1);
cairo_translate(cr,
text_offset_left(&allocation),
text_offset_top(&allocation));
if (!entry->layout)
entry->layout = pango_cairo_create_layout(cr);
else
pango_cairo_update_layout(cr, entry->layout);
text_entry_update_layout(entry);
pango_cairo_show_layout(cr, entry->layout);
text_entry_draw_cursor(entry, cr);
cairo_pop_group_to_source(cr);
cairo_paint(cr);
cairo_destroy(cr);
cairo_surface_destroy(surface);
}
static int
text_entry_motion_handler(struct widget *widget,
struct input *input, uint32_t time,
float x, float y, void *data)
{
struct text_entry *entry = data;
struct rectangle allocation;
int tx, ty;
if (!entry->button_pressed) {
return CURSOR_IBEAM;
}
widget_get_allocation(entry->widget, &allocation);
tx = x - allocation.x - text_offset_left(&allocation);
ty = y - allocation.y - text_offset_top(&allocation);
text_entry_set_cursor_position(entry, tx, ty, false);
return CURSOR_IBEAM;
}
static void
text_entry_button_handler(struct widget *widget,
struct input *input, uint32_t time,
uint32_t button,
enum wl_pointer_button_state state, void *data)
{
struct text_entry *entry = data;
struct rectangle allocation;
struct editor *editor;
int32_t x, y;
uint32_t result;
widget_get_allocation(entry->widget, &allocation);
input_get_position(input, &x, &y);
x -= allocation.x + text_offset_left(&allocation);
y -= allocation.y + text_offset_top(&allocation);
editor = window_get_user_data(entry->window);
if (button == BTN_LEFT) {
entry->button_pressed = (state == WL_POINTER_BUTTON_STATE_PRESSED);
if (state == WL_POINTER_BUTTON_STATE_PRESSED)
input_grab(input, entry->widget, button);
else
input_ungrab(input);
}
if (text_entry_has_preedit(entry)) {
result = text_entry_try_invoke_preedit_action(entry, x, y, button, state);
if (result)
return;
}
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
struct wl_seat *seat = input_get_seat(input);
text_entry_activate(entry, seat);
editor->active_entry = entry;
text_entry_set_cursor_position(entry, x, y, true);
}
}
static void
text_entry_touch_handler(struct widget *widget, struct input *input,
uint32_t serial, uint32_t time, int32_t id,
float tx, float ty, void *data)
{
struct text_entry *entry = data;
struct wl_seat *seat = input_get_seat(input);
struct rectangle allocation;
struct editor *editor;
int32_t x, y;
widget_get_allocation(entry->widget, &allocation);
x = tx - (allocation.x + text_offset_left(&allocation));
y = ty - (allocation.y + text_offset_top(&allocation));
editor = window_get_user_data(entry->window);
text_entry_activate(entry, seat);
editor->active_entry = entry;
text_entry_set_cursor_position(entry, x, y, true);
}
static void
editor_button_handler(struct widget *widget,
struct input *input, uint32_t time,
uint32_t button,
enum wl_pointer_button_state state, void *data)
{
struct editor *editor = data;
if (button != BTN_LEFT) {
return;
}
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
struct wl_seat *seat = input_get_seat(input);
text_entry_deactivate(editor->entry, seat);
text_entry_deactivate(editor->editor, seat);
editor->active_entry = NULL;
}
}
static void
editor_touch_handler(struct widget *widget, struct input *input,
uint32_t serial, uint32_t time, int32_t id,
float tx, float ty, void *data)
{
struct editor *editor = data;
struct wl_seat *seat = input_get_seat(input);
text_entry_deactivate(editor->entry, seat);
text_entry_deactivate(editor->editor, seat);
editor->active_entry = NULL;
}
static void
keyboard_focus_handler(struct window *window,
struct input *device, void *data)
{
struct editor *editor = data;
window_schedule_redraw(editor->window);
}
static void
key_handler(struct window *window,
struct input *input, uint32_t time,
uint32_t key, uint32_t sym, enum wl_keyboard_key_state state,
void *data)
{
struct editor *editor = data;
struct text_entry *entry;
const char *new_char;
char text[16];
if (!editor->active_entry)
return;
entry = editor->active_entry;
if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
return;
switch (sym) {
case XKB_KEY_BackSpace:
text_entry_commit_and_reset(entry);
new_char = utf8_prev_char(entry->text, entry->text + entry->cursor);
if (new_char != NULL)
text_entry_delete_text(entry,
new_char - entry->text,
(entry->text + entry->cursor) - new_char);
break;
case XKB_KEY_Delete:
text_entry_commit_and_reset(entry);
new_char = utf8_next_char(entry->text + entry->cursor);
if (new_char != NULL)
text_entry_delete_text(entry,
entry->cursor,
new_char - (entry->text + entry->cursor));
break;
case XKB_KEY_Left:
text_entry_commit_and_reset(entry);
new_char = utf8_prev_char(entry->text, entry->text + entry->cursor);
if (new_char != NULL) {
entry->cursor = new_char - entry->text;
if (!(input_get_modifiers(input) & MOD_SHIFT_MASK))
entry->anchor = entry->cursor;
widget_schedule_redraw(entry->widget);
}
break;
case XKB_KEY_Right:
text_entry_commit_and_reset(entry);
new_char = utf8_next_char(entry->text + entry->cursor);
if (new_char != NULL) {
entry->cursor = new_char - entry->text;
if (!(input_get_modifiers(input) & MOD_SHIFT_MASK))
entry->anchor = entry->cursor;
widget_schedule_redraw(entry->widget);
}
break;
case XKB_KEY_Up:
text_entry_commit_and_reset(entry);
move_up(entry->text, &entry->cursor);
if (!(input_get_modifiers(input) & MOD_SHIFT_MASK))
entry->anchor = entry->cursor;
widget_schedule_redraw(entry->widget);
break;
case XKB_KEY_Down:
text_entry_commit_and_reset(entry);
move_down(entry->text, &entry->cursor);
if (!(input_get_modifiers(input) & MOD_SHIFT_MASK))
entry->anchor = entry->cursor;
widget_schedule_redraw(entry->widget);
break;
case XKB_KEY_Escape:
break;
default:
if (xkb_keysym_to_utf8(sym, text, sizeof(text)) <= 0)
break;
text_entry_commit_and_reset(entry);
text_entry_insert_at_cursor(entry, text, 0, 0);
break;
}
widget_schedule_redraw(entry->widget);
}
static void
global_handler(struct display *display, uint32_t name,
const char *interface, uint32_t version, void *data)
{
struct editor *editor = data;
if (!strcmp(interface, "wl_text_input_manager")) {
editor->text_input_manager =
display_bind(display, name,
&wl_text_input_manager_interface, 1);
}
}
int
main(int argc, char *argv[])
{
struct editor editor;
int i;
uint32_t click_to_show = 0;
const char *preferred_language = NULL;
for (i = 1; i < argc; i++) {
if (strcmp("--click-to-show", argv[i]) == 0)
click_to_show = 1;
else if (strcmp("--preferred-language", argv[i]) == 0) {
if (i + 1 < argc) {
preferred_language = argv[i + 1];
i++;
}
}
}
memset(&editor, 0, sizeof editor);
#ifdef HAVE_PANGO
g_type_init();
#endif
editor.display = display_create(&argc, argv);
if (editor.display == NULL) {
fprintf(stderr, "failed to create display: %m\n");
return -1;
}
display_set_user_data(editor.display, &editor);
display_set_global_handler(editor.display, global_handler);
editor.window = window_create(editor.display);
editor.widget = window_frame_create(editor.window, &editor);
editor.entry = text_entry_create(&editor, "Entry");
editor.entry->click_to_show = click_to_show;
if (preferred_language)
editor.entry->preferred_language = strdup(preferred_language);
editor.editor = text_entry_create(&editor, "Numeric");
editor.editor->content_purpose = WL_TEXT_INPUT_CONTENT_PURPOSE_NUMBER;
editor.editor->click_to_show = click_to_show;
window_set_title(editor.window, "Text Editor");
window_set_key_handler(editor.window, key_handler);
window_set_keyboard_focus_handler(editor.window,
keyboard_focus_handler);
window_set_user_data(editor.window, &editor);
widget_set_redraw_handler(editor.widget, redraw_handler);
widget_set_resize_handler(editor.widget, resize_handler);
widget_set_button_handler(editor.widget, editor_button_handler);
widget_set_touch_down_handler(editor.widget, editor_touch_handler);
window_schedule_resize(editor.window, 500, 400);
display_run(editor.display);
text_entry_destroy(editor.entry);
text_entry_destroy(editor.editor);
return 0;
}