#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "text.h"
#include "container.h"
static void selection_handler(
GtkTextBuffer *buf,
GtkTextIter *location,
GtkTextMark *mark,
UiTextArea *textview)
{
const char *mname = gtk_text_mark_get_name(mark);
if(mname) {
GtkTextIter begin;
GtkTextIter end;
int sel = gtk_text_buffer_get_selection_bounds (buf, &begin, &end);
if(sel != textview->last_selection_state) {
if(sel) {
ui_set_group(textview->ctx,
UI_GROUP_SELECTION);
}
else {
ui_unset_group(textview->ctx,
UI_GROUP_SELECTION);
}
}
textview->last_selection_state = sel;
}
}
UIWIDGET ui_textarea_var(UiObject *obj, UiVar *var) {
GtkWidget *text_area = gtk_text_view_new();
gtk_text_view_set_wrap_mode(
GTK_TEXT_VIEW(text_area),
GTK_WRAP_WORD_CHAR);
g_signal_connect(
text_area,
"realize",
G_CALLBACK(ui_textarea_realize_event),
NULL);
UiTextArea *uitext = malloc(
sizeof(UiTextArea));
uitext->ctx = obj->ctx;
uitext->var = var;
uitext->last_selection_state =
0;
g_signal_connect(
text_area,
"destroy",
G_CALLBACK(ui_textarea_destroy),
uitext);
GtkWidget *scroll_area = gtk_scrolled_window_new (
NULL,
NULL);
gtk_scrolled_window_set_policy(
GTK_SCROLLED_WINDOW(scroll_area),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
gtk_container_add(
GTK_CONTAINER(scroll_area), text_area);
PangoFontDescription *font;
font = pango_font_description_from_string(
"Monospace");
gtk_widget_modify_font(text_area, font);
pango_font_description_free(font);
gtk_text_view_set_left_margin(
GTK_TEXT_VIEW(text_area),
2);
gtk_text_view_set_right_margin(
GTK_TEXT_VIEW(text_area),
2);
UiContainer *ct = uic_get_current_container(obj);
ct->add(ct, scroll_area,
TRUE);
UiText *value = var->value;
if(value) {
GtkTextBuffer *buf = gtk_text_view_get_buffer(
GTK_TEXT_VIEW(text_area));
if(value->value.ptr) {
gtk_text_buffer_set_text(buf, value->value.ptr, -
1);
value->value.free(value->value.ptr);
}
value->get = ui_textarea_get;
value->set = ui_textarea_set;
value->getsubstr = ui_textarea_getsubstr;
value->insert = ui_textarea_insert;
value->setposition = ui_textarea_setposition;
value->position = ui_textarea_position;
value->selection = ui_textarea_selection;
value->length = ui_textarea_length;
value->remove = ui_textarea_remove;
value->value.ptr =
NULL;
value->value.free =
NULL;
value->obj = buf;
if(!value->undomgr) {
value->undomgr = ui_create_undomgr();
}
g_signal_connect(
buf,
"changed",
G_CALLBACK(ui_textbuf_changed),
uitext);
g_signal_connect(
buf,
"insert-text",
G_CALLBACK(ui_textbuf_insert),
var);
g_signal_connect(
buf,
"delete-range",
G_CALLBACK(ui_textbuf_delete),
var);
g_signal_connect(
buf,
"mark-set",
G_CALLBACK(selection_handler),
uitext);
}
return scroll_area;
}
void ui_textarea_destroy(GtkWidget *object, UiTextArea *textarea) {
ui_destroy_boundvar(textarea->ctx, textarea->var);
free(textarea);
}
UIWIDGET ui_textarea(UiObject *obj, UiText *value) {
UiVar *var = malloc(
sizeof(UiVar));
var->value = value;
var->type =
UI_VAR_SPECIAL;
var->from =
NULL;
var->from_ctx =
NULL;
return ui_textarea_var(obj, var);
}
UIWIDGET ui_textarea_nv(UiObject *obj,
char *varname) {
UiVar *var = uic_create_var(obj->ctx, varname,
UI_VAR_TEXT);
if(var) {
return ui_textarea_var(obj, var);
}
else {
}
return NULL;
}
UIWIDGET ui_textarea_gettextwidget(
UIWIDGET textarea) {
return gtk_bin_get_child(
GTK_BIN(textarea));
}
char* ui_textarea_get(UiText *text) {
if(text->value.ptr) {
text->value.free(text->value.ptr);
}
GtkTextBuffer *buf = text->obj;
GtkTextIter start;
GtkTextIter end;
gtk_text_buffer_get_bounds(buf, &start, &end);
char *str = gtk_text_buffer_get_text(buf, &start, &end,
FALSE);
text->value.ptr = g_strdup(str);
text->value.free = (ui_freefunc)g_free;
return str;
}
void ui_textarea_set(UiText *text,
char *str) {
gtk_text_buffer_set_text((GtkTextBuffer*)text->obj, str, -
1);
if(text->value.ptr) {
text->value.free(text->value.ptr);
}
text->value.ptr =
NULL;
text->value.free =
NULL;
}
char* ui_textarea_getsubstr(UiText *text,
int begin,
int end) {
if(text->value.ptr) {
text->value.free(text->value.ptr);
}
GtkTextBuffer *buf = text->obj;
GtkTextIter ib;
GtkTextIter ie;
gtk_text_buffer_get_iter_at_offset(text->obj, &ib, begin);
gtk_text_buffer_get_iter_at_offset(text->obj, &ie, end);
char *str = gtk_text_buffer_get_text(buf, &ib, &ie,
FALSE);
text->value.ptr = g_strdup(str);
text->value.free = (ui_freefunc)g_free;
return str;
}
void ui_textarea_insert(UiText *text,
int pos,
char *str) {
GtkTextIter offset;
gtk_text_buffer_get_iter_at_offset(text->obj, &offset, pos);
gtk_text_buffer_insert(text->obj, &offset, str, -
1);
if(text->value.ptr) {
text->value.free(text->value.ptr);
}
text->value.ptr =
NULL;
text->value.free =
NULL;
}
void ui_textarea_setposition(UiText *text,
int pos) {
GtkTextIter iter;
gtk_text_buffer_get_iter_at_offset(text->obj, &iter, pos);
gtk_text_buffer_place_cursor(text->obj, &iter);
}
int ui_textarea_position(UiText *text) {
GtkTextIter begin;
GtkTextIter end;
gtk_text_buffer_get_selection_bounds(text->obj, &begin, &end);
text->pos = gtk_text_iter_get_offset(&begin);
return text->pos;
}
void ui_textarea_selection(UiText *text,
int *begin,
int *end) {
GtkTextIter b;
GtkTextIter e;
gtk_text_buffer_get_selection_bounds(text->obj, &b, &e);
*begin = gtk_text_iter_get_offset(&b);
*end = gtk_text_iter_get_offset(&e);
}
int ui_textarea_length(UiText *text) {
GtkTextBuffer *buf = text->obj;
GtkTextIter start;
GtkTextIter end;
gtk_text_buffer_get_bounds(buf, &start, &end);
return gtk_text_iter_get_offset(&end);
}
void ui_textarea_remove(UiText *text,
int begin,
int end) {
GtkTextBuffer *buf = text->obj;
GtkTextIter ib;
GtkTextIter ie;
gtk_text_buffer_get_iter_at_offset(buf, &ib, begin);
gtk_text_buffer_get_iter_at_offset(buf, &ie, end);
gtk_text_buffer_delete(buf, &ib, &ie);
}
void ui_textarea_realize_event(GtkWidget *widget, gpointer data) {
gtk_widget_grab_focus(widget);
}
void ui_textbuf_changed(GtkTextBuffer *textbuffer, UiTextArea *textarea) {
UiText *value = textarea->var->value;
if(value->observers) {
UiEvent e;
e.obj = textarea->ctx->obj;
e.window = e.obj->window;
e.document = textarea->ctx->document;
e.eventdata = value;
e.intval =
0;
ui_notify_evt(value->observers, &e);
}
}
void ui_textbuf_insert(
GtkTextBuffer *textbuffer,
GtkTextIter *location,
char *text,
int length,
void *data)
{
UiVar *var = data;
UiText *value = var->value;
if(!value->undomgr) {
value->undomgr = ui_create_undomgr();
}
UiUndoMgr *mgr = value->undomgr;
if(!mgr->event) {
return;
}
if(mgr->cur) {
UcxList *elm = mgr->cur->next;
if(elm) {
mgr->cur->next =
NULL;
while(elm) {
elm->prev =
NULL;
UcxList *next = elm->next;
ui_free_textbuf_op(elm->data);
free(elm);
elm = next;
}
}
UiTextBufOp *last_op = mgr->cur->data;
if(
last_op->type ==
UI_TEXTBUF_INSERT &&
ui_check_insertstr(last_op->text, last_op->len, text, length) ==
0)
{
int ln = last_op->len;
char *newtext = malloc(ln + length +
1);
memcpy(newtext, last_op->text, ln);
memcpy(newtext+ln, text, length);
newtext[ln+length] =
'\0';
last_op->text = newtext;
last_op->len = ln + length;
last_op->end += length;
return;
}
}
char *dpstr = malloc(length +
1);
memcpy(dpstr, text, length);
dpstr[length] =
0;
UiTextBufOp *op = malloc(
sizeof(UiTextBufOp));
op->type =
UI_TEXTBUF_INSERT;
op->start = gtk_text_iter_get_offset(location);
op->end = op->start+length;
op->len = length;
op->text = dpstr;
UcxList *elm = ucx_list_append(
NULL, op);
mgr->cur = elm;
mgr->begin = ucx_list_concat(mgr->begin, elm);
}
void ui_textbuf_delete(
GtkTextBuffer *textbuffer,
GtkTextIter *start,
GtkTextIter *end,
void *data)
{
UiVar *var = data;
UiText *value = var->value;
if(!value->undomgr) {
value->undomgr = ui_create_undomgr();
}
UiUndoMgr *mgr = value->undomgr;
if(!mgr->event) {
return;
}
if(mgr->cur) {
UcxList *elm = mgr->cur->next;
if(elm) {
mgr->cur->next =
NULL;
while(elm) {
elm->prev =
NULL;
UcxList *next = elm->next;
ui_free_textbuf_op(elm->data);
free(elm);
elm = next;
}
}
}
char *text = gtk_text_buffer_get_text(value->obj, start, end,
FALSE);
UiTextBufOp *op = malloc(
sizeof(UiTextBufOp));
op->type =
UI_TEXTBUF_DELETE;
op->start = gtk_text_iter_get_offset(start);
op->end = gtk_text_iter_get_offset(end);
op->len = op->end - op->start;
char *dpstr = malloc(op->len +
1);
memcpy(dpstr, text, op->len);
dpstr[op->len] =
0;
op->text = dpstr;
UcxList *elm = ucx_list_append(
NULL, op);
mgr->cur = elm;
mgr->begin = ucx_list_concat(mgr->begin, elm);
}
UiUndoMgr* ui_create_undomgr() {
UiUndoMgr *mgr = malloc(
sizeof(UiUndoMgr));
mgr->begin =
NULL;
mgr->cur =
NULL;
mgr->length =
0;
mgr->event =
1;
return mgr;
}
void ui_free_textbuf_op(UiTextBufOp *op) {
if(op->text) {
free(op->text);
}
free(op);
}
int ui_check_insertstr(
char *oldstr,
int oldlen,
char *newstr,
int newlen) {
int has_space =
0;
for(
int i=
0;i<oldlen;i++) {
if(oldstr[i] <
33) {
has_space =
1;
break;
}
}
for(
int i=
0;i<newlen;i++) {
if(has_space && newstr[i] >
32) {
return 1;
}
}
return 0;
}
void ui_text_undo(UiText *value) {
UiUndoMgr *mgr = value->undomgr;
if(mgr->cur) {
UiTextBufOp *op = mgr->cur->data;
mgr->event =
0;
switch(op->type) {
case UI_TEXTBUF_INSERT: {
GtkTextIter begin;
GtkTextIter end;
gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
gtk_text_buffer_delete(value->obj, &begin, &end);
break;
}
case UI_TEXTBUF_DELETE: {
GtkTextIter begin;
GtkTextIter end;
gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
gtk_text_buffer_insert(value->obj, &begin, op->text, op->len);
break;
}
}
mgr->event =
1;
mgr->cur = mgr->cur->prev;
}
}
void ui_text_redo(UiText *value) {
UiUndoMgr *mgr = value->undomgr;
UcxList *elm =
NULL;
if(mgr->cur) {
if(mgr->cur->next) {
elm = mgr->cur->next;
}
}
else if(mgr->begin) {
elm = mgr->begin;
}
if(elm) {
UiTextBufOp *op = elm->data;
mgr->event =
0;
switch(op->type) {
case UI_TEXTBUF_INSERT: {
GtkTextIter begin;
GtkTextIter end;
gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
gtk_text_buffer_insert(value->obj, &begin, op->text, op->len);
break;
}
case UI_TEXTBUF_DELETE: {
GtkTextIter begin;
GtkTextIter end;
gtk_text_buffer_get_iter_at_offset(value->obj, &begin, op->start);
gtk_text_buffer_get_iter_at_offset(value->obj, &end, op->end);
gtk_text_buffer_delete(value->obj, &begin, &end);
break;
}
}
mgr->event =
1;
mgr->cur = elm;
}
}
static UIWIDGET create_textfield_var(UiObject *obj,
int width, UiBool frameless, UiBool password, UiVar *var) {
GtkWidget *textfield = gtk_entry_new();
UiTextField *uitext = malloc(
sizeof(UiTextField));
uitext->ctx = obj->ctx;
uitext->var = var;
g_signal_connect(
textfield,
"destroy",
G_CALLBACK(ui_textfield_destroy),
uitext);
if(width >
0) {
gtk_entry_set_width_chars(
GTK_ENTRY(textfield), width);
}
if(frameless) {
gtk_entry_set_has_frame(
GTK_ENTRY(textfield),
FALSE);
}
if(password) {
gtk_entry_set_visibility(
GTK_ENTRY(textfield),
FALSE);
}
UiContainer *ct = uic_get_current_container(obj);
ct->add(ct, textfield,
FALSE);
if(var) {
UiString *value = var->value;
if(value->value.ptr) {
gtk_entry_set_text(
GTK_ENTRY(textfield), value->value.ptr);
value->value.free(value->value.ptr);
value->value.ptr =
NULL;
value->value.free =
NULL;
}
value->get = ui_textfield_get;
value->set = ui_textfield_set;
value->value.ptr =
NULL;
value->value.free =
NULL;
value->obj =
GTK_ENTRY(textfield);
g_signal_connect(
textfield,
"changed",
G_CALLBACK(ui_textfield_changed),
uitext);
}
return textfield;
}
static UIWIDGET create_textfield_nv(UiObject *obj,
int width, UiBool frameless, UiBool password,
char *varname) {
UiVar *var = uic_create_var(obj->ctx, varname,
UI_VAR_STRING);
if(var) {
return create_textfield_var(obj, width, frameless, password, var);
}
else {
}
return NULL;
}
static UIWIDGET create_textfield(UiObject *obj,
int width, UiBool frameless, UiBool password, UiString *value) {
UiVar *var =
NULL;
if(value) {
var = malloc(
sizeof(UiVar));
var->value = value;
var->type =
UI_VAR_SPECIAL;
var->from =
NULL;
var->from_ctx =
NULL;
}
return create_textfield_var(obj, width, frameless, password, var);
}
void ui_textfield_destroy(GtkWidget *object, UiTextField *textfield) {
if(textfield->var) {
ui_destroy_boundvar(textfield->ctx, textfield->var);
}
free(textfield);
}
void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) {
UiString *value = textfield->var->value;
if(value->observers) {
UiEvent e;
e.obj = textfield->ctx->obj;
e.window = e.obj->window;
e.document = textfield->ctx->document;
e.eventdata = value;
e.intval =
0;
ui_notify_evt(value->observers, &e);
}
}
UIWIDGET ui_textfield(UiObject *obj, UiString *value) {
return create_textfield(obj,
0,
FALSE,
FALSE, value);
}
UIWIDGET ui_textfield_nv(UiObject *obj,
char *varname) {
return create_textfield_nv(obj,
0,
FALSE,
FALSE, varname);
}
UIWIDGET ui_textfield_w(UiObject *obj,
int width, UiString *value) {
return create_textfield(obj, width,
FALSE,
FALSE, value);
}
UIWIDGET ui_textfield_wnv(UiObject *obj,
int width,
char *varname) {
return create_textfield_nv(obj, width,
FALSE,
FALSE, varname);
}
UIWIDGET ui_frameless_textfield(UiObject *obj, UiString *value) {
return create_textfield(obj,
0,
TRUE,
FALSE, value);
}
UIWIDGET ui_frameless_textfield_nv(UiObject *obj,
char *varname) {
return create_textfield_nv(obj,
0,
TRUE,
FALSE, varname);
}
UIWIDGET ui_passwordfield(UiObject *obj, UiString *value) {
return create_textfield(obj,
0,
FALSE,
TRUE, value);
}
UIWIDGET ui_passwordfield_nv(UiObject *obj,
char *varname) {
return create_textfield_nv(obj,
0,
FALSE,
TRUE, varname);
}
UIWIDGET ui_passwordfield_w(UiObject *obj,
int width, UiString *value) {
return create_textfield(obj, width,
FALSE,
TRUE, value);
}
UIWIDGET ui_passwordfield_wnv(UiObject *obj,
int width,
char *varname) {
return create_textfield_nv(obj, width,
FALSE,
TRUE, varname);
}
char* ui_textfield_get(UiString *str) {
if(str->value.ptr) {
str->value.free(str->value.ptr);
}
str->value.ptr = g_strdup(gtk_entry_get_text(str->obj));
str->value.free = (ui_freefunc)g_free;
return str->value.ptr;
}
void ui_textfield_set(UiString *str,
char *value) {
gtk_entry_set_text(str->obj, value);
if(str->value.ptr) {
str->value.free(str->value.ptr);
str->value.ptr =
NULL;
str->value.free =
NULL;
}
}