1
0
mirror of https://github.com/irssi/irssi.git synced 2024-09-22 04:35:58 -04:00
irssi/src/fe-text/gui-entry.c
dequis 0d8632943d Add a wrapper of wcwidth() that picks the best implementation
This adds a i_wcwidth() function that replaces mk_wcwidth(), and a
'wcwidth_implementation' setting to pick which one it wraps.

Values:

- old: uses our local mk_wcwidth() which implements unicode 5.0
- system: uses the libc-provided wcwidth(), which may be better or worse
  than ours depending on how up to date the system is.
- auto: tests the system one against two characters that became
  fullwidth in unicode 5.2 and 9.0 respectively. If either of them pass,
  pick the system implementation, otherwise pick ours.

It defaults to auto.

mk_wcwidth() is still preferable in some cases, since the way it uses
ranges for fullwidth characters means most CJK blocks are covered even
if their characters didn't exist back then.

The "system" implementation is also wrapped to never return -1, but to
assume those unknown characters use one cell. Quoting the code:

    /* Treat all unknown characters as taking one cell. This is
     * the reason mk_wcwidth and other outdated implementations
     * mostly worked with newer unicode, while glibc's wcwidth
     * needs updating to recognize new characters.
     *
     * Instead of relying on that, we keep the behavior of assuming
     * one cell even for glibc's implementation, which is still
     * highly accurate and less of a headache overall.
     */
2018-08-23 02:30:26 -03:00

1499 lines
35 KiB
C

/*
gui-entry.c : irssi
Copyright (C) 1999 Timo Sirainen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "module.h"
#include "misc.h"
#include "utf8.h"
#include "formats.h"
#include "gui-entry.h"
#include "gui-printtext.h"
#include "term.h"
#include "recode.h"
#undef i_toupper
#undef i_tolower
#undef i_isalnum
#define KILL_RING_MAX 10
static unichar i_toupper(unichar c)
{
if (term_type == TERM_TYPE_UTF8)
return g_unichar_toupper(c);
return c <= 255 ? toupper(c) : c;
}
static unichar i_tolower(unichar c)
{
if (term_type == TERM_TYPE_UTF8)
return g_unichar_tolower(c);
return c <= 255 ? tolower(c) : c;
}
static int i_isalnum(unichar c)
{
if (term_type == TERM_TYPE_UTF8)
return (g_unichar_isalnum(c) || i_wcwidth(c) == 0);
return c <= 255 ? isalnum(c) : 0;
}
GUI_ENTRY_REC *active_entry;
static void entry_text_grow(GUI_ENTRY_REC *entry, int grow_size)
{
if (entry->text_len+grow_size < entry->text_alloc)
return;
entry->text_alloc = nearest_power(entry->text_alloc+grow_size);
entry->text = g_realloc(entry->text,
sizeof(unichar) * entry->text_alloc);
if (entry->uses_extents)
entry->extents = g_realloc(entry->extents,
sizeof(char *) * entry->text_alloc);
}
GUI_ENTRY_REC *gui_entry_create(int xpos, int ypos, int width, int utf8)
{
GUI_ENTRY_REC *rec;
rec = g_new0(GUI_ENTRY_REC, 1);
rec->xpos = xpos;
rec->ypos = ypos;
rec->width = width;
rec->text_alloc = 1024;
rec->text = g_new(unichar, rec->text_alloc);
rec->extents = NULL;
rec->text[0] = '\0';
rec->utf8 = utf8;
return rec;
}
static void destroy_extents(GUI_ENTRY_REC *entry)
{
if (entry->uses_extents) {
int i;
for (i = 0; i < entry->text_alloc; i++) {
if (entry->extents[i] != NULL) {
g_free(entry->extents[i]);
}
}
}
g_free(entry->extents);
entry->extents = NULL;
entry->uses_extents = FALSE;
}
void gui_entry_destroy(GUI_ENTRY_REC *entry)
{
GSList *tmp;
g_return_if_fail(entry != NULL);
if (active_entry == entry)
gui_entry_set_active(NULL);
for (tmp = entry->kill_ring; tmp != NULL; tmp = tmp->next) {
GUI_ENTRY_CUTBUFFER_REC *rec = tmp->data;
if (rec != NULL) {
g_free(rec->cutbuffer);
g_free(rec);
}
}
g_slist_free(entry->kill_ring);
destroy_extents(entry);
g_free(entry->text);
g_free(entry->prompt);
g_free(entry);
}
/* big5 functions */
#define big5_width(ch) ((ch)>0xff ? 2:1)
void unichars_to_big5(const unichar *str, char *out)
{
for (; *str != '\0'; str++) {
if (*str > 0xff)
*out++ = (*str >> 8) & 0xff;
*out++ = *str & 0xff;
}
*out = '\0';
}
int strlen_big5(const unsigned char *str)
{
int len=0;
while (*str != '\0') {
if (is_big5(str[0], str[1]))
str++;
len++;
str++;
}
return len;
}
void unichars_to_big5_with_pos(const unichar *str, int spos, char *out, int *opos)
{
const unichar *sstart = str;
char *ostart = out;
*opos = 0;
while(*str != '\0')
{
if(*str > 0xff)
*out ++ = (*str >> 8) & 0xff;
*out ++ = *str & 0xff;
str ++;
if(str - sstart == spos)
*opos = out - ostart;
}
*out = '\0';
}
void big5_to_unichars(const char *str, unichar *out)
{
const unsigned char *p = (const unsigned char *) str;
while (*p != '\0') {
if (is_big5(p[0], p[1])) {
*out++ = p[0] << 8 | p[1];
p += 2;
} else {
*out++ = *p++;
}
}
*out = '\0';
}
/* Return screen length of plain string */
static int scrlen_str(const char *str, int utf8)
{
int len = 0;
char *stripped;
g_return_val_if_fail(str != NULL, 0);
stripped = strip_codes(str);
len = string_width(stripped, utf8 ? TREAT_STRING_AS_UTF8 : TREAT_STRING_AS_BYTES);
g_free(stripped);
return len;
}
/* ----------------------------- */
static int pos2scrpos(GUI_ENTRY_REC *entry, int pos, int cursor)
{
int i;
int xpos = 0;
if (!cursor && pos <= 0)
return 0;
if (entry->uses_extents && entry->extents[0] != NULL) {
xpos += scrlen_str(entry->extents[0], entry->utf8);
}
for (i = 0; i < pos; i++) {
unichar c = entry->text[i];
const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL;
if (term_type == TERM_TYPE_BIG5)
xpos += big5_width(c);
else if (entry->utf8)
xpos += unichar_isprint(c) ? i_wcwidth(c) : 1;
else
xpos++;
if (extent != NULL) {
xpos += scrlen_str(extent, entry->utf8);
}
}
return xpos;
}
static int scrpos2pos(GUI_ENTRY_REC *entry, int pos)
{
int i, width, xpos = 0;
if (entry->uses_extents && entry->extents[0] != NULL) {
xpos += scrlen_str(entry->extents[0], entry->utf8);
}
for (i = 0; i < entry->text_len && xpos < pos; i++) {
unichar c = entry->text[i];
const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL;
if (term_type == TERM_TYPE_BIG5)
width = big5_width(c);
else if (entry->utf8)
width = unichar_isprint(c) ? i_wcwidth(c) : 1;
else
width = 1;
xpos += width;
if (extent != NULL) {
xpos += scrlen_str(extent, entry->utf8);
}
}
return i;
}
/* Fixes the cursor position in screen */
static void gui_entry_fix_cursor(GUI_ENTRY_REC *entry)
{
int old_scrstart;
/* assume prompt len == prompt scrlen */
int start = pos2scrpos(entry, entry->scrstart, FALSE);
int now = pos2scrpos(entry, entry->pos, TRUE);
old_scrstart = entry->scrstart;
if (now-start < entry->width - 2 - entry->promptlen && now-start > 0) {
entry->scrpos = now-start;
} else if (now < entry->width - 1 - entry->promptlen) {
entry->scrstart = 0;
entry->scrpos = now;
} else {
entry->scrstart = scrpos2pos(entry, now-(entry->width -
entry->promptlen)*2/3);
start = pos2scrpos(entry, entry->scrstart, FALSE);
entry->scrpos = now - start;
}
if (old_scrstart != entry->scrstart)
entry->redraw_needed_from = 0;
}
static char *text_effects_only(const char *p)
{
GString *str;
str = g_string_sized_new(strlen(p));
for (; *p != '\0'; p++) {
if (*p == 4 && p[1] != '\0') {
if (p[1] >= FORMAT_STYLE_SPECIAL) {
g_string_append_len(str, p, 2);
p++;
continue;
}
/* irssi color */
if (p[2] != '\0') {
#ifdef TERM_TRUECOLOR
if (p[1] == FORMAT_COLOR_24) {
if (p[3] == '\0') p += 2;
else if (p[4] == '\0') p += 3;
else if (p[5] == '\0') p += 4;
else {
g_string_append_len(str, p, 6);
p += 5;
}
} else {
#endif /* TERM_TRUECOLOR */
g_string_append_len(str, p, 3);
p += 2;
#ifdef TERM_TRUECOLOR
}
#endif /* TERM_TRUECOLOR */
continue;
}
}
}
return g_string_free(str, FALSE);
}
static void gui_entry_draw_from(GUI_ENTRY_REC *entry, int pos)
{
int i, start;
int start_xpos, xpos, new_xpos, end_xpos;
char *tmp;
GString *str;
start = entry->scrstart + pos;
start_xpos = xpos = entry->xpos + entry->promptlen +
pos2scrpos(entry, start, FALSE) -
pos2scrpos(entry, entry->scrstart, FALSE);
end_xpos = entry->xpos + entry->width;
if (xpos > end_xpos)
return;
str = g_string_sized_new(entry->text_alloc);
term_set_color(root_window, ATTR_RESET);
/* term_move(root_window, xpos, entry->ypos); */
if (entry->uses_extents && entry->extents[0] != NULL) {
g_string_append(str, entry->extents[0]);
}
for (i = 0; i < start && i < entry->text_len; i++) {
const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL;
if (extent != NULL) {
g_string_append(str, extent);
}
}
if (i == 0) {
xpos += scrlen_str(str->str, entry->utf8);
} else {
tmp = text_effects_only(str->str);
g_string_assign(str, tmp);
g_free(tmp);
}
for (; i < entry->text_len; i++) {
unichar c = entry->text[i];
const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL;
new_xpos = xpos;
if (entry->hidden)
new_xpos++;
else if (term_type == TERM_TYPE_BIG5)
new_xpos += big5_width(c);
else if (entry->utf8)
new_xpos += unichar_isprint(c) ? i_wcwidth(c) : 1;
else
new_xpos++;
if (new_xpos > end_xpos)
break;
if (entry->hidden)
g_string_append_c(str, ' ');
else if (unichar_isprint(c))
g_string_append_unichar(str, c);
else {
g_string_append_c(str, 4);
g_string_append_c(str, FORMAT_STYLE_REVERSE);
g_string_append_c(str, (c & 127)+'A'-1);
g_string_append_c(str, 4);
g_string_append_c(str, FORMAT_STYLE_REVERSE);
}
xpos = new_xpos;
if (extent != NULL) {
new_xpos += scrlen_str(extent, entry->utf8);
if (new_xpos > end_xpos)
break;
g_string_append(str, extent);
xpos = new_xpos;
}
}
/* clear the rest of the input line */
if (xpos < end_xpos) {
if (end_xpos == term_width) {
g_string_append_c(str, 4);
g_string_append_c(str, FORMAT_STYLE_CLRTOEOL);
} else {
while (xpos < end_xpos) {
g_string_append_c(str, ' ');
xpos++;
}
}
}
gui_printtext_internal(start_xpos, entry->ypos, str->str);
g_string_free(str, TRUE);
}
static void gui_entry_draw(GUI_ENTRY_REC *entry)
{
if (entry->redraw_needed_from >= 0) {
gui_entry_draw_from(entry, entry->redraw_needed_from);
entry->redraw_needed_from = -1;
}
term_move_cursor(entry->xpos + entry->scrpos + entry->promptlen,
entry->ypos);
term_refresh(NULL);
}
static void gui_entry_redraw_from(GUI_ENTRY_REC *entry, int pos)
{
pos -= entry->scrstart;
if (pos < 0) pos = 0;
if (entry->redraw_needed_from == -1 ||
entry->redraw_needed_from > pos)
entry->redraw_needed_from = pos;
}
void gui_entry_move(GUI_ENTRY_REC *entry, int xpos, int ypos, int width)
{
int old_width;
g_return_if_fail(entry != NULL);
if (entry->xpos != xpos || entry->ypos != ypos) {
/* position in screen changed - needs a full redraw */
entry->xpos = xpos;
entry->ypos = ypos;
entry->width = width;
gui_entry_redraw(entry);
return;
}
if (entry->width == width)
return; /* no changes */
if (width > entry->width) {
/* input line grew - need to draw text at the end */
old_width = width;
entry->width = width;
gui_entry_redraw_from(entry, old_width);
} else {
/* input line shrinked - make sure the cursor
is inside the input line */
entry->width = width;
if (entry->pos - entry->scrstart >
entry->width-2 - entry->promptlen) {
gui_entry_fix_cursor(entry);
}
}
gui_entry_draw(entry);
}
void gui_entry_set_active(GUI_ENTRY_REC *entry)
{
active_entry = entry;
if (entry != NULL) {
term_move_cursor(entry->xpos + entry->scrpos +
entry->promptlen, entry->ypos);
term_refresh(NULL);
}
}
void gui_entry_set_prompt(GUI_ENTRY_REC *entry, const char *str)
{
int oldlen;
g_return_if_fail(entry != NULL);
oldlen = entry->promptlen;
if (str != NULL) {
g_free_not_null(entry->prompt);
entry->prompt = g_strdup(str);
entry->promptlen = scrlen_str(str, entry->utf8);
}
if (entry->prompt != NULL)
gui_printtext_internal(entry->xpos, entry->ypos, entry->prompt);
if (entry->promptlen != oldlen) {
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
}
void gui_entry_set_hidden(GUI_ENTRY_REC *entry, int hidden)
{
g_return_if_fail(entry != NULL);
entry->hidden = hidden;
}
void gui_entry_set_utf8(GUI_ENTRY_REC *entry, int utf8)
{
g_return_if_fail(entry != NULL);
entry->utf8 = utf8;
}
void gui_entry_set_text(GUI_ENTRY_REC *entry, const char *str)
{
g_return_if_fail(entry != NULL);
g_return_if_fail(str != NULL);
entry->text_len = 0;
entry->pos = 0;
entry->text[0] = '\0';
destroy_extents(entry);
gui_entry_insert_text(entry, str);
}
char *gui_entry_get_text(GUI_ENTRY_REC *entry)
{
char *buf;
int i;
g_return_val_if_fail(entry != NULL, NULL);
if (entry->utf8)
buf = g_ucs4_to_utf8(entry->text, -1, NULL, NULL, NULL);
else {
buf = g_malloc(entry->text_len*6 + 1);
if (term_type == TERM_TYPE_BIG5)
unichars_to_big5(entry->text, buf);
else
for (i = 0; i <= entry->text_len; i++)
buf[i] = entry->text[i];
}
return buf;
}
char *gui_entry_get_text_and_pos(GUI_ENTRY_REC *entry, int *pos)
{
char *buf;
int i;
g_return_val_if_fail(entry != NULL, NULL);
if (entry->utf8) {
buf = g_ucs4_to_utf8(entry->text, -1, NULL, NULL, NULL);
*pos = g_utf8_offset_to_pointer(buf, entry->pos) - buf;
} else {
buf = g_malloc(entry->text_len*6 + 1);
if(term_type==TERM_TYPE_BIG5)
unichars_to_big5_with_pos(entry->text, entry->pos, buf, pos);
else
{
for (i = 0; i <= entry->text_len; i++)
buf[i] = entry->text[i];
*pos = entry->pos;
}
}
return buf;
}
void gui_entry_insert_text(GUI_ENTRY_REC *entry, const char *str)
{
unichar chr;
int i, len;
const char *ptr;
g_return_if_fail(entry != NULL);
g_return_if_fail(str != NULL);
gui_entry_redraw_from(entry, entry->pos);
if (entry->utf8) {
g_utf8_validate(str, -1, &ptr);
len = g_utf8_pointer_to_offset(str, ptr);
} else if (term_type == TERM_TYPE_BIG5)
len = strlen_big5((const unsigned char *)str);
else
len = strlen(str);
entry_text_grow(entry, len);
/* make space for the string */
g_memmove(entry->text + entry->pos + len, entry->text + entry->pos,
(entry->text_len-entry->pos + 1) * sizeof(unichar));
/* make space for the color */
if (entry->uses_extents) {
g_memmove(entry->extents + entry->pos + len + 1, entry->extents + entry->pos + 1,
(entry->text_len-entry->pos) * sizeof(char *));
for (i = 0; i < len; i++) {
entry->extents[entry->pos + i + 1] = NULL;
}
}
if (!entry->utf8) {
if (term_type == TERM_TYPE_BIG5) {
chr = entry->text[entry->pos + len];
big5_to_unichars(str, entry->text + entry->pos);
entry->text[entry->pos + len] = chr;
} else {
for (i = 0; i < len; i++)
entry->text[entry->pos + i] = str[i];
}
} else {
ptr = str;
for (i = 0; i < len; i++) {
entry->text[entry->pos + i] = g_utf8_get_char(ptr);
ptr = g_utf8_next_char(ptr);
}
}
entry->text_len += len;
entry->pos += len;
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
void gui_entry_insert_char(GUI_ENTRY_REC *entry, unichar chr)
{
g_return_if_fail(entry != NULL);
if (chr == 0 || chr == 13 || chr == 10)
return; /* never insert NUL, CR or LF characters */
if (entry->utf8 && entry->pos == 0 && i_wcwidth(chr) == 0)
return;
gui_entry_redraw_from(entry, entry->pos);
entry_text_grow(entry, 1);
/* make space for the string */
g_memmove(entry->text + entry->pos + 1, entry->text + entry->pos,
(entry->text_len-entry->pos + 1) * sizeof(unichar));
if (entry->uses_extents) {
g_memmove(entry->extents + entry->pos + 1 + 1, entry->extents + entry->pos + 1,
(entry->text_len-entry->pos) * sizeof(char *));
entry->extents[entry->pos + 1] = NULL;
}
entry->text[entry->pos] = chr;
entry->text_len++;
entry->pos++;
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
char *gui_entry_get_cutbuffer(GUI_ENTRY_REC *entry)
{
GUI_ENTRY_CUTBUFFER_REC *tmp;
char *buf;
int i;
g_return_val_if_fail(entry != NULL, NULL);
if (entry->kill_ring == NULL || entry->kill_ring->data == NULL)
return NULL;
tmp = entry->kill_ring->data;
if (tmp->cutbuffer == NULL)
return NULL;
if (entry->utf8)
buf = g_ucs4_to_utf8(tmp->cutbuffer, -1, NULL, NULL, NULL);
else {
buf = g_malloc(tmp->cutbuffer_len*6 + 1);
if (term_type == TERM_TYPE_BIG5)
unichars_to_big5(tmp->cutbuffer, buf);
else
for (i = 0; i <= tmp->cutbuffer_len; i++)
buf[i] = tmp->cutbuffer[i];
}
return buf;
}
char *gui_entry_get_next_cutbuffer(GUI_ENTRY_REC *entry)
{
GUI_ENTRY_CUTBUFFER_REC *tmp;
g_return_val_if_fail(entry != NULL, NULL);
if (entry->kill_ring == NULL)
return NULL;
tmp = entry->kill_ring->data;
entry->kill_ring = g_slist_remove(entry->kill_ring, tmp);
entry->kill_ring = g_slist_append(entry->kill_ring, tmp);
return gui_entry_get_cutbuffer(entry);
}
void gui_entry_erase_to(GUI_ENTRY_REC *entry, int pos, CUTBUFFER_UPDATE_OP update_cutbuffer)
{
int newpos, size = 0;
g_return_if_fail(entry != NULL);
for (newpos = gui_entry_get_pos(entry); newpos > pos; size++)
newpos = newpos - 1;
gui_entry_erase(entry, size, update_cutbuffer);
}
static GUI_ENTRY_CUTBUFFER_REC *get_cutbuffer_rec(GUI_ENTRY_REC *entry, CUTBUFFER_UPDATE_OP update_cutbuffer)
{
GUI_ENTRY_CUTBUFFER_REC *tmp;
g_return_val_if_fail(entry != NULL, NULL);
if (entry->kill_ring == NULL) {
/* no kill ring exists */
entry->kill_ring = g_slist_prepend(entry->kill_ring, (void *)NULL);
} else {
tmp = entry->kill_ring->data;
if (tmp != NULL && tmp->cutbuffer_len > 0
&& (!entry->previous_append_next_kill
|| update_cutbuffer == CUTBUFFER_UPDATE_REPLACE)) {
/* a cutbuffer exists and should be replaced */
entry->kill_ring = g_slist_prepend(entry->kill_ring, (void *)NULL);
}
}
if (g_slist_length(entry->kill_ring) > KILL_RING_MAX) {
GUI_ENTRY_CUTBUFFER_REC *rec = g_slist_last(entry->kill_ring)->data;
entry->kill_ring = g_slist_remove(entry->kill_ring, rec);
if (rec != NULL) g_free(rec->cutbuffer);
g_free(rec);
}
if (entry->kill_ring->data == NULL) {
entry->kill_ring->data = g_new0(GUI_ENTRY_CUTBUFFER_REC, 1);
}
return entry->kill_ring->data;
}
void gui_entry_erase(GUI_ENTRY_REC *entry, int size, CUTBUFFER_UPDATE_OP update_cutbuffer)
{
size_t i, w = 0;
g_return_if_fail(entry != NULL);
if (size == 0 || entry->pos < size)
return;
if (update_cutbuffer != CUTBUFFER_UPDATE_NOOP) {
int cutbuffer_new_size;
unichar *tmpcutbuffer;
GUI_ENTRY_CUTBUFFER_REC *tmp = get_cutbuffer_rec(entry, update_cutbuffer);
if (tmp->cutbuffer_len == 0) {
update_cutbuffer = CUTBUFFER_UPDATE_REPLACE;
}
cutbuffer_new_size = tmp->cutbuffer_len + size;
tmpcutbuffer = tmp->cutbuffer;
entry->append_next_kill = TRUE;
switch (update_cutbuffer) {
case CUTBUFFER_UPDATE_APPEND:
tmp->cutbuffer = g_new(unichar, cutbuffer_new_size+1);
memcpy(tmp->cutbuffer, tmpcutbuffer,
tmp->cutbuffer_len * sizeof(unichar));
memcpy(tmp->cutbuffer + tmp->cutbuffer_len,
entry->text + entry->pos - size, size * sizeof(unichar));
tmp->cutbuffer_len = cutbuffer_new_size;
tmp->cutbuffer[cutbuffer_new_size] = '\0';
g_free(tmpcutbuffer);
break;
case CUTBUFFER_UPDATE_PREPEND:
tmp->cutbuffer = g_new(unichar, cutbuffer_new_size+1);
memcpy(tmp->cutbuffer, entry->text + entry->pos - size,
size * sizeof(unichar));
memcpy(tmp->cutbuffer + size, tmpcutbuffer,
tmp->cutbuffer_len * sizeof(unichar));
tmp->cutbuffer_len = cutbuffer_new_size;
tmp->cutbuffer[cutbuffer_new_size] = '\0';
g_free(tmpcutbuffer);
break;
case CUTBUFFER_UPDATE_REPLACE:
/* put erased text to cutbuffer */
if (tmp->cutbuffer_len < size) {
g_free(tmp->cutbuffer);
tmp->cutbuffer = g_new(unichar, size+1);
}
tmp->cutbuffer_len = size;
tmp->cutbuffer[size] = '\0';
memcpy(tmp->cutbuffer, entry->text + entry->pos - size, size * sizeof(unichar));
break;
case CUTBUFFER_UPDATE_NOOP:
/* cannot happen, handled in "if" */
break;
}
}
if (entry->utf8)
while (entry->pos-size-w > 0 &&
i_wcwidth(entry->text[entry->pos-size-w]) == 0) w++;
g_memmove(entry->text + entry->pos - size, entry->text + entry->pos,
(entry->text_len-entry->pos+1) * sizeof(unichar));
if (entry->uses_extents) {
for (i = entry->pos - size; i < entry->pos; i++) {
if (entry->extents[i+1] != NULL) {
g_free(entry->extents[i+1]);
}
}
g_memmove(entry->extents + entry->pos - size + 1, entry->extents + entry->pos + 1,
(entry->text_len - entry->pos) * sizeof(void *)); /* no null terminator here */
for (i = 0; i < size; i++) {
entry->extents[entry->text_len - i] = NULL;
}
if (entry->text_len == size && entry->extents[0] != NULL) {
g_free(entry->extents[0]);
entry->extents[0] = NULL;
}
}
entry->pos -= size;
entry->text_len -= size;
gui_entry_redraw_from(entry, entry->pos-w);
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
void gui_entry_erase_cell(GUI_ENTRY_REC *entry)
{
int size = 1;
g_return_if_fail(entry != NULL);
if (entry->utf8)
while (entry->pos+size < entry->text_len &&
i_wcwidth(entry->text[entry->pos+size]) == 0) size++;
g_memmove(entry->text + entry->pos, entry->text + entry->pos + size,
(entry->text_len-entry->pos-size+1) * sizeof(unichar));
if (entry->uses_extents) {
int i;
for (i = 0; i < size; i++) {
g_free(entry->extents[entry->pos + i + 1]);
}
g_memmove(entry->extents + entry->pos + 1, entry->extents + entry->pos + size + 1,
(entry->text_len-entry->pos-size) * sizeof(char *));
for (i = 0; i < size; i++) {
entry->extents[entry->text_len - i] = NULL;
}
if (entry->text_len == size && entry->extents[0] != NULL) {
g_free(entry->extents[0]);
entry->extents[0] = NULL;
}
}
entry->text_len -= size;
gui_entry_redraw_from(entry, entry->pos);
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
void gui_entry_erase_word(GUI_ENTRY_REC *entry, int to_space, CUTBUFFER_UPDATE_OP cutbuffer_op)
{
int to;
g_return_if_fail(entry != NULL);
if (entry->pos == 0)
return;
to = entry->pos - 1;
if (to_space) {
while (entry->text[to] == ' ' && to > 0)
to--;
while (entry->text[to] != ' ' && to > 0)
to--;
} else {
while (!i_isalnum(entry->text[to]) && to > 0)
to--;
while (i_isalnum(entry->text[to]) && to > 0)
to--;
}
if (to > 0) to++;
gui_entry_erase(entry, entry->pos-to, cutbuffer_op);
}
void gui_entry_erase_next_word(GUI_ENTRY_REC *entry, int to_space, CUTBUFFER_UPDATE_OP cutbuffer_op)
{
int to, size;
g_return_if_fail(entry != NULL);
if (entry->pos == entry->text_len)
return;
to = entry->pos;
if (to_space) {
while (entry->text[to] == ' ' && to < entry->text_len)
to++;
while (entry->text[to] != ' ' && to < entry->text_len)
to++;
} else {
while (!i_isalnum(entry->text[to]) && to < entry->text_len)
to++;
while (i_isalnum(entry->text[to]) && to < entry->text_len)
to++;
}
size = to-entry->pos;
entry->pos = to;
gui_entry_erase(entry, size, cutbuffer_op);
}
void gui_entry_transpose_chars(GUI_ENTRY_REC *entry)
{
unichar chr;
char *extent;
if (entry->pos == 0 || entry->text_len < 2)
return;
if (entry->pos == entry->text_len)
entry->pos--;
/* swap chars */
chr = entry->text[entry->pos];
entry->text[entry->pos] = entry->text[entry->pos-1];
entry->text[entry->pos-1] = chr;
if (entry->uses_extents) {
extent = entry->extents[entry->pos+1];
entry->extents[entry->pos+1] = entry->extents[entry->pos];
entry->extents[entry->pos] = extent;
}
entry->pos++;
gui_entry_redraw_from(entry, entry->pos-2);
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
void gui_entry_transpose_words(GUI_ENTRY_REC *entry)
{
int spos1, epos1, spos2, epos2;
/* find last position */
epos2 = entry->pos;
while (epos2 < entry->text_len && !i_isalnum(entry->text[epos2]))
epos2++;
while (epos2 < entry->text_len && i_isalnum(entry->text[epos2]))
epos2++;
/* find other position */
spos2 = epos2;
while (spos2 > 0 && !i_isalnum(entry->text[spos2-1]))
spos2--;
while (spos2 > 0 && i_isalnum(entry->text[spos2-1]))
spos2--;
epos1 = spos2;
while (epos1 > 0 && !i_isalnum(entry->text[epos1-1]))
epos1--;
spos1 = epos1;
while (spos1 > 0 && i_isalnum(entry->text[spos1-1]))
spos1--;
/* do wordswap if any found */
if (spos1 < epos1 && epos1 < spos2 && spos2 < epos2) {
unichar *first, *sep, *second;
char **first_extent, **sep_extent, **second_extent;
int i;
first = (unichar *) g_malloc( (epos1 - spos1) * sizeof(unichar) );
sep = (unichar *) g_malloc( (spos2 - epos1) * sizeof(unichar) );
second = (unichar *) g_malloc( (epos2 - spos2) * sizeof(unichar) );
first_extent = (char **) g_malloc( (epos1 - spos1) * sizeof(char *) );
sep_extent = (char **) g_malloc( (spos2 - epos1) * sizeof(char *) );
second_extent = (char **) g_malloc( (epos2 - spos2) * sizeof(char *) );
for (i = spos1; i < epos1; i++) {
first[i-spos1] = entry->text[i];
if (entry->uses_extents)
first_extent[i-spos1] = entry->extents[i+1];
}
for (i = epos1; i < spos2; i++) {
sep[i-epos1] = entry->text[i];
if (entry->uses_extents)
sep_extent[i-epos1] = entry->extents[i+1];
}
for (i = spos2; i < epos2; i++) {
second[i-spos2] = entry->text[i];
if (entry->uses_extents)
second_extent[i-spos2] = entry->extents[i+1];
}
entry->pos = spos1;
for (i = 0; i < epos2-spos2; i++) {
entry->text[entry->pos] = second[i];
if (entry->uses_extents)
entry->extents[entry->pos+1] = second_extent[i];
entry->pos++;
}
for (i = 0; i < spos2-epos1; i++) {
entry->text[entry->pos] = sep[i];
if (entry->uses_extents)
entry->extents[entry->pos+1] = sep_extent[i];
entry->pos++;
}
for (i = 0; i < epos1-spos1; i++) {
entry->text[entry->pos] = first[i];
if (entry->uses_extents)
entry->extents[entry->pos+1] = first_extent[i];
entry->pos++;
}
g_free(first);
g_free(sep);
g_free(second);
g_free(first_extent);
g_free(sep_extent);
g_free(second_extent);
}
gui_entry_redraw_from(entry, spos1);
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
void gui_entry_capitalize_word(GUI_ENTRY_REC *entry)
{
int pos = entry->pos;
while (pos < entry->text_len && !i_isalnum(entry->text[pos]))
pos++;
if (pos < entry->text_len) {
entry->text[pos] = i_toupper(entry->text[pos]);
pos++;
}
while (pos < entry->text_len && i_isalnum(entry->text[pos])) {
entry->text[pos] = i_tolower(entry->text[pos]);
pos++;
}
gui_entry_redraw_from(entry, entry->pos);
entry->pos = pos;
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
void gui_entry_downcase_word(GUI_ENTRY_REC *entry)
{
int pos = entry->pos;
while (pos < entry->text_len && !i_isalnum(entry->text[pos]))
pos++;
while (pos < entry->text_len && i_isalnum(entry->text[pos])) {
entry->text[pos] = i_tolower(entry->text[pos]);
pos++;
}
gui_entry_redraw_from(entry, entry->pos);
entry->pos = pos;
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
void gui_entry_upcase_word(GUI_ENTRY_REC *entry)
{
int pos = entry->pos;
while (pos < entry->text_len && !i_isalnum(entry->text[pos]))
pos++;
while (pos < entry->text_len && i_isalnum(entry->text[pos])) {
entry->text[pos] = i_toupper(entry->text[pos]);
pos++;
}
gui_entry_redraw_from(entry, entry->pos);
entry->pos = pos;
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
int gui_entry_get_pos(GUI_ENTRY_REC *entry)
{
g_return_val_if_fail(entry != NULL, 0);
return entry->pos;
}
void gui_entry_set_pos(GUI_ENTRY_REC *entry, int pos)
{
g_return_if_fail(entry != NULL);
if (pos >= 0 && pos <= entry->text_len)
entry->pos = pos;
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
void gui_entry_set_text_and_pos_bytes(GUI_ENTRY_REC *entry, const char *str, int pos_bytes)
{
int pos, extents_alloc;
char **extents;
const char *ptr;
g_return_if_fail(entry != NULL);
extents = entry->extents;
extents_alloc = entry->text_alloc;
entry->extents = NULL;
entry->uses_extents = FALSE;
gui_entry_set_text(entry, str);
if (entry->utf8) {
g_utf8_validate(str, pos_bytes, &ptr);
pos = g_utf8_pointer_to_offset(str, ptr);
} else if (term_type == TERM_TYPE_BIG5)
pos = strlen_big5((const unsigned char *)str) - strlen_big5((const unsigned char *)(str + pos_bytes));
else
pos = pos_bytes;
if (extents != NULL) {
entry->uses_extents = TRUE;
entry->extents = extents;
if (extents_alloc < entry->text_alloc) {
int i;
entry->extents = g_realloc(entry->extents,
sizeof(char *) * entry->text_alloc);
for (i = extents_alloc; i < entry->text_alloc; i++) {
entry->extents[i] = NULL;
}
}
}
gui_entry_redraw_from(entry, 0);
gui_entry_set_pos(entry, pos);
}
void gui_entry_move_pos(GUI_ENTRY_REC *entry, int pos)
{
g_return_if_fail(entry != NULL);
if (entry->pos + pos >= 0 && entry->pos + pos <= entry->text_len)
entry->pos += pos;
if (entry->utf8) {
int step = pos < 0 ? -1 : 1;
while(i_wcwidth(entry->text[entry->pos]) == 0 &&
entry->pos + step >= 0 && entry->pos + step <= entry->text_len)
entry->pos += step;
}
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
static void gui_entry_move_words_left(GUI_ENTRY_REC *entry, int count, int to_space)
{
int pos;
pos = entry->pos;
while (count > 0 && pos > 0) {
if (to_space) {
while (pos > 0 && entry->text[pos-1] == ' ')
pos--;
while (pos > 0 && entry->text[pos-1] != ' ')
pos--;
} else {
while (pos > 0 && !i_isalnum(entry->text[pos-1]))
pos--;
while (pos > 0 && i_isalnum(entry->text[pos-1]))
pos--;
}
count--;
}
entry->pos = pos;
}
static void gui_entry_move_words_right(GUI_ENTRY_REC *entry, int count, int to_space)
{
int pos;
pos = entry->pos;
while (count > 0 && pos < entry->text_len) {
if (to_space) {
while (pos < entry->text_len && entry->text[pos] == ' ')
pos++;
while (pos < entry->text_len && entry->text[pos] != ' ')
pos++;
} else {
while (pos < entry->text_len && !i_isalnum(entry->text[pos]))
pos++;
while (pos < entry->text_len && i_isalnum(entry->text[pos]))
pos++;
}
count--;
}
entry->pos = pos;
}
void gui_entry_move_words(GUI_ENTRY_REC *entry, int count, int to_space)
{
g_return_if_fail(entry != NULL);
if (count < 0)
gui_entry_move_words_left(entry, -count, to_space);
else if (count > 0)
gui_entry_move_words_right(entry, count, to_space);
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
void gui_entry_redraw(GUI_ENTRY_REC *entry)
{
g_return_if_fail(entry != NULL);
gui_entry_set_prompt(entry, NULL);
gui_entry_redraw_from(entry, 0);
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
static void gui_entry_alloc_extents(GUI_ENTRY_REC *entry)
{
entry->uses_extents = TRUE;
entry->extents = g_new0(char *, entry->text_alloc);
}
void gui_entry_set_extent(GUI_ENTRY_REC *entry, int pos, const char *text)
{
int update = FALSE;
g_return_if_fail(entry != NULL);
if (pos < 0 || pos > entry->text_len)
return;
if (text == NULL)
return;
if (!entry->uses_extents) {
gui_entry_alloc_extents(entry);
}
if (g_strcmp0(entry->extents[pos], text) != 0) {
g_free(entry->extents[pos]);
if (*text == '\0') {
entry->extents[pos] = NULL;
} else {
entry->extents[pos] = g_strdup(text);
}
update = TRUE;
}
if (update) {
gui_entry_redraw_from(entry, pos - 1);
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
}
void gui_entry_set_extents(GUI_ENTRY_REC *entry, int pos, int len, const char *left, const char *right)
{
int end, update = FALSE;
g_return_if_fail(entry != NULL);
if (pos < 0 || len < 0 || pos > entry->text_len)
return;
end = pos + len;
if (end > entry->text_len)
end = entry->text_len;
if (!entry->uses_extents) {
gui_entry_alloc_extents(entry);
}
if (g_strcmp0(entry->extents[pos], left) != 0) {
g_free(entry->extents[pos]);
if (*left == '\0') {
entry->extents[pos] = NULL;
} else {
entry->extents[pos] = g_strdup(left);
}
update = TRUE;
}
if (pos != end && g_strcmp0(entry->extents[end], right) != 0) {
g_free(entry->extents[end]);
if (*right == '\0') {
entry->extents[end] = NULL;
} else {
entry->extents[end] = g_strdup(right);
}
update = TRUE;
}
if (update) {
gui_entry_redraw_from(entry, pos - 1);
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
}
void gui_entry_clear_extents(GUI_ENTRY_REC *entry, int pos, int len)
{
int i, end, update = FALSE;
g_return_if_fail(entry != NULL);
if (pos < 0 || len < 0 || pos > entry->text_len)
return;
end = pos + len;
if (end > entry->text_len)
end = entry->text_len;
if (!entry->uses_extents) {
return;
}
for (i = pos; i <= end; i++) {
if (entry->extents[i] != NULL) {
g_free(entry->extents[i]);
entry->extents[i] = NULL;
update = TRUE;
}
}
if (update) {
gui_entry_redraw_from(entry, pos);
gui_entry_fix_cursor(entry);
gui_entry_draw(entry);
}
}
char *gui_entry_get_extent(GUI_ENTRY_REC *entry, int pos)
{
g_return_val_if_fail(entry != NULL, NULL);
if (!entry->uses_extents)
return NULL;
if (pos < 0 || pos >= entry->text_len)
return NULL;
return entry->extents[pos];
}
#define POS_FLAG "%|"
GSList *gui_entry_get_text_and_extents(GUI_ENTRY_REC *entry)
{
GSList *list = NULL;
GString *str;
int i;
g_return_val_if_fail(entry != NULL, NULL);
if (entry->uses_extents && entry->extents[0] != NULL) {
if (entry->pos == 0) {
list = g_slist_prepend(list, g_strconcat(entry->extents[0], POS_FLAG, NULL));
} else {
list = g_slist_prepend(list, g_strdup(entry->extents[0]));
}
} else {
if (entry->pos == 0) {
list = g_slist_prepend(list, g_strdup(POS_FLAG));
} else {
list = g_slist_prepend(list, NULL);
}
}
str = g_string_sized_new(entry->text_alloc);
for (i = 0; i < entry->text_len; i++) {
if (entry->utf8) {
g_string_append_unichar(str, entry->text[i]);
} else if (term_type == TERM_TYPE_BIG5) {
if(entry->text[i] > 0xff)
g_string_append_c(str, (entry->text[i] >> 8) & 0xff);
g_string_append_c(str, entry->text[i] & 0xff);
} else {
g_string_append_c(str, entry->text[i]);
}
if (entry->pos == i+1 || (entry->uses_extents && entry->extents[i+1] != NULL)) {
list = g_slist_prepend(list, g_strdup(str->str));
g_string_truncate(str, 0);
if (entry->uses_extents && entry->extents[i+1] != NULL) {
if (entry->pos == i+1) {
list = g_slist_prepend(list, g_strconcat(entry->extents[i+1], POS_FLAG, NULL));
} else {
list = g_slist_prepend(list, g_strdup(entry->extents[i+1]));
}
} else if (entry->pos == i+1) {
list = g_slist_prepend(list, g_strdup(POS_FLAG));
}
}
}
if (str->len > 0) {
list = g_slist_prepend(list, g_strdup(str->str));
}
list = g_slist_reverse(list);
g_string_free(str, TRUE);
return list;
}
void gui_entry_set_text_and_extents(GUI_ENTRY_REC *entry, GSList *list)
{
GSList *tmp;
int pos = -1;
int is_extent = 1;
gui_entry_set_text(entry, "");
for (tmp = list, is_extent = TRUE; tmp != NULL; tmp = tmp->next, is_extent ^= 1) {
if (is_extent) {
char *extent;
int len;
if (tmp->data == NULL)
continue;
extent = g_strdup(tmp->data);
len = strlen(extent);
if (len >= strlen(POS_FLAG) && g_strcmp0(&extent[len-strlen(POS_FLAG)], POS_FLAG) == 0) {
char *tmp;
tmp = extent;
extent = g_strndup(tmp, len - strlen(POS_FLAG));
g_free(tmp);
pos = entry->pos;
}
if (strlen(extent) > 0) {
gui_entry_set_extent(entry, entry->pos, extent);
}
g_free(extent);
} else {
gui_entry_insert_text(entry, tmp->data);
}
}
gui_entry_set_pos(entry, pos);
}
void gui_entry_init(void)
{
}
void gui_entry_deinit(void)
{
}