mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-01 10:13:22 +00:00
- eye-dropper cursor to editor - JM_SETCURSOR - incremental-search to fileview widget - jgrid widget - JI_SIGNAL_SET_FONT - sprite_getpixel - colorselector widget - test_jlist test - HSV color type - grid, exchange_colors, and eyedropper_tool commands Fixed: - duplication of layers Renamed: - jwidget_add_childs to jwidget_add_children - status_bar to statusbar - tool_bar to toolbar - color_viewer to colorviewer - color_bar to colorbar - color_button to colorbutton - rectfill_exclude to jrectexclude - _graya_getk to _graya_getv - _graya_k_shift to _graya_v_shift Refactoring to color type: - now it's a color_t structure instead of a string (char *) Removed: - mapgen.[ch] - update_global_script_variables routine - JM_CHAR (now it's JM_KEYPRESSED). - README-es.txt - FAQ.txt
642 lines
15 KiB
C
642 lines
15 KiB
C
/* Jinete - a GUI library
|
|
* Copyright (C) 2003-2008 David A. Capello.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* * Neither the name of the Jinete nor the names of its contributors may
|
|
* be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <allegro.h>
|
|
#include <allegro/internal/aintern.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
|
|
#include "jinete/jclipboard.h"
|
|
#include "jinete/jfont.h"
|
|
#include "jinete/jmanager.h"
|
|
#include "jinete/jmessage.h"
|
|
#include "jinete/jrect.h"
|
|
#include "jinete/jsystem.h"
|
|
#include "jinete/jtheme.h"
|
|
#include "jinete/jwidget.h"
|
|
|
|
#define CHARACTER_LENGTH(f, c) ((f)->vtable->char_length((f), (c)))
|
|
|
|
typedef struct Entry
|
|
{
|
|
int cursor;
|
|
int scroll;
|
|
int select;
|
|
int timer_id;
|
|
bool hidden : 1;
|
|
bool state : 1; /* show or not the text cursor */
|
|
bool readonly : 1;
|
|
bool password : 1;
|
|
bool recent_focused : 1;
|
|
} Entry;
|
|
|
|
static bool entry_msg_proc(JWidget widget, JMessage msg);
|
|
static void entry_request_size(JWidget widget, int *w, int *h);
|
|
|
|
static int entry_get_cursor_from_mouse(JWidget widget, JMessage msg);
|
|
|
|
static void entry_forward_word(JWidget widget);
|
|
static void entry_backward_word(JWidget widget);
|
|
|
|
JWidget jentry_new(int maxsize, const char *format, ...)
|
|
{
|
|
JWidget widget = jwidget_new(JI_ENTRY);
|
|
Entry *entry = jnew(Entry, 1);
|
|
char buf[4096];
|
|
|
|
/* formatted string */
|
|
if (format) {
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
vsprintf(buf, format, ap);
|
|
va_end(ap);
|
|
}
|
|
/* empty string */
|
|
else {
|
|
ustrcpy(buf, empty_string);
|
|
}
|
|
|
|
jwidget_add_hook(widget, JI_ENTRY, entry_msg_proc, entry);
|
|
|
|
entry->cursor = 0;
|
|
entry->scroll = 0;
|
|
entry->select = 0;
|
|
entry->timer_id = jmanager_add_timer(widget, 500);
|
|
entry->hidden = FALSE;
|
|
entry->state = FALSE;
|
|
entry->password = FALSE;
|
|
entry->readonly = FALSE;
|
|
entry->recent_focused = FALSE;
|
|
|
|
/* TODO support for text alignment and multi-line */
|
|
/* widget->align = JI_LEFT | JI_MIDDLE; */
|
|
widget->text_size = maxsize+1;
|
|
widget->text = jmalloc(widget->text_size);
|
|
jwidget_set_text(widget, buf);
|
|
|
|
jwidget_focusrest(widget, TRUE);
|
|
jwidget_init_theme(widget);
|
|
|
|
return widget;
|
|
}
|
|
|
|
void jentry_readonly(JWidget widget, bool state)
|
|
{
|
|
Entry *entry = jwidget_get_data(widget, JI_ENTRY);
|
|
|
|
entry->readonly = state;
|
|
}
|
|
|
|
void jentry_password(JWidget widget, bool state)
|
|
{
|
|
Entry *entry = jwidget_get_data(widget, JI_ENTRY);
|
|
|
|
entry->password = state;
|
|
}
|
|
|
|
bool jentry_is_readonly(JWidget widget)
|
|
{
|
|
Entry *entry = jwidget_get_data(widget, JI_ENTRY);
|
|
|
|
return entry->readonly;
|
|
}
|
|
|
|
bool jentry_is_password(JWidget widget)
|
|
{
|
|
Entry *entry = jwidget_get_data(widget, JI_ENTRY);
|
|
|
|
return entry->password;
|
|
}
|
|
|
|
void jentry_show_cursor(JWidget widget)
|
|
{
|
|
Entry *entry = jwidget_get_data(widget, JI_ENTRY);
|
|
|
|
entry->hidden = FALSE;
|
|
|
|
jwidget_dirty(widget);
|
|
}
|
|
|
|
void jentry_hide_cursor(JWidget widget)
|
|
{
|
|
Entry *entry = jwidget_get_data(widget, JI_ENTRY);
|
|
|
|
entry->hidden = TRUE;
|
|
|
|
jwidget_dirty(widget);
|
|
}
|
|
|
|
void jentry_set_cursor_pos(JWidget widget, int pos)
|
|
{
|
|
Entry *entry = jwidget_get_data(widget, JI_ENTRY);
|
|
const char *text = widget->text;
|
|
int x, c;
|
|
|
|
entry->cursor = pos;
|
|
|
|
/* backward scroll */
|
|
if (entry->cursor < entry->scroll)
|
|
entry->scroll = entry->cursor;
|
|
|
|
/* forward scroll */
|
|
entry->scroll--;
|
|
do {
|
|
x = widget->rc->x1 + widget->border_width.l;
|
|
for (c=++entry->scroll; ; c++) {
|
|
x += CHARACTER_LENGTH(widget->text_font,
|
|
(c < ustrlen(text))? ugetat(text, c): ' ');
|
|
|
|
if (x >= widget->rc->x2-widget->border_width.r)
|
|
break;
|
|
}
|
|
} while (entry->cursor >= c);
|
|
|
|
jmanager_start_timer(entry->timer_id);
|
|
entry->state = TRUE;
|
|
|
|
jwidget_dirty(widget);
|
|
}
|
|
|
|
void jentry_select_text(JWidget widget, int from, int to)
|
|
{
|
|
Entry *entry = jwidget_get_data(widget, JI_ENTRY);
|
|
int end = ustrlen(widget->text);
|
|
|
|
entry->select = from;
|
|
jentry_set_cursor_pos(widget, from);
|
|
jentry_set_cursor_pos(widget, (to >= 0)? to: end+to+1);
|
|
|
|
jwidget_dirty(widget);
|
|
}
|
|
|
|
void jentry_deselect_text(JWidget widget)
|
|
{
|
|
Entry *entry = jwidget_get_data(widget, JI_ENTRY);
|
|
|
|
entry->select = -1;
|
|
|
|
jwidget_dirty(widget);
|
|
}
|
|
|
|
void jtheme_entry_info(JWidget widget,
|
|
int *scroll, int *cursor, int *state,
|
|
int *selbeg, int *selend)
|
|
{
|
|
Entry *entry = jwidget_get_data(widget, JI_ENTRY);
|
|
|
|
if (scroll) *scroll = entry->scroll;
|
|
if (cursor) *cursor = entry->cursor;
|
|
if (state) *state = !entry->hidden && entry->state;
|
|
|
|
if ((entry->select >= 0) &&
|
|
(entry->cursor != entry->select)) {
|
|
*selbeg = MIN(entry->cursor, entry->select);
|
|
*selend = MAX(entry->cursor, entry->select)-1;
|
|
}
|
|
else {
|
|
*selbeg = -1;
|
|
*selend = -1;
|
|
}
|
|
}
|
|
|
|
static bool entry_msg_proc(JWidget widget, JMessage msg)
|
|
{
|
|
Entry *entry = jwidget_get_data(widget, JI_ENTRY);
|
|
|
|
switch (msg->type) {
|
|
|
|
case JM_DESTROY:
|
|
jmanager_remove_timer(entry->timer_id);
|
|
jfree(entry);
|
|
break;
|
|
|
|
case JM_REQSIZE:
|
|
entry_request_size(widget, &msg->reqsize.w, &msg->reqsize.h);
|
|
return TRUE;
|
|
|
|
case JM_TIMER:
|
|
if (jwidget_has_focus(widget) &&
|
|
msg->timer.timer_id == entry->timer_id) {
|
|
entry->state = entry->state ? FALSE: TRUE;
|
|
jwidget_dirty(widget);
|
|
}
|
|
break;
|
|
|
|
case JM_FOCUSENTER:
|
|
jmanager_start_timer(entry->timer_id);
|
|
|
|
entry->state = TRUE;
|
|
jwidget_dirty(widget);
|
|
|
|
jentry_select_text(widget, 0, -1);
|
|
entry->recent_focused = TRUE;
|
|
break;
|
|
|
|
case JM_FOCUSLEAVE:
|
|
jwidget_dirty(widget);
|
|
|
|
jmanager_stop_timer(entry->timer_id);
|
|
|
|
jentry_deselect_text(widget);
|
|
entry->recent_focused = FALSE;
|
|
break;
|
|
|
|
case JM_KEYPRESSED:
|
|
if (jwidget_has_focus(widget) && !jentry_is_readonly(widget)) {
|
|
char *text = jmalloc(widget->text_size);
|
|
int c, selbeg, selend;
|
|
|
|
jtheme_entry_info(widget, NULL, NULL, NULL, &selbeg, &selend);
|
|
|
|
ustrcpy(text, widget->text);
|
|
|
|
switch (msg->key.scancode) {
|
|
|
|
case KEY_LEFT:
|
|
/* selection */
|
|
if (msg->any.shifts & KB_SHIFT_FLAG) {
|
|
if (entry->select < 0)
|
|
entry->select = entry->cursor;
|
|
}
|
|
else
|
|
entry->select = -1;
|
|
|
|
/* backward word */
|
|
if (msg->any.shifts & KB_CTRL_FLAG)
|
|
entry_backward_word(widget);
|
|
/* backward char */
|
|
else if (entry->cursor > 0)
|
|
entry->cursor--;
|
|
break;
|
|
|
|
case KEY_RIGHT:
|
|
/* selection */
|
|
if (msg->any.shifts & KB_SHIFT_FLAG) {
|
|
if (entry->select < 0)
|
|
entry->select = entry->cursor;
|
|
}
|
|
else
|
|
entry->select = -1;
|
|
|
|
/* forward word */
|
|
if (msg->any.shifts & KB_CTRL_FLAG)
|
|
entry_forward_word(widget);
|
|
/* forward char */
|
|
else if (entry->cursor < ustrlen (text))
|
|
entry->cursor++;
|
|
break;
|
|
|
|
case KEY_HOME:
|
|
/* selection */
|
|
if (msg->any.shifts & KB_SHIFT_FLAG) {
|
|
if (entry->select < 0)
|
|
entry->select = entry->cursor;
|
|
}
|
|
else
|
|
entry->select = -1;
|
|
|
|
entry->cursor = 0;
|
|
break;
|
|
|
|
case KEY_END:
|
|
/* selection */
|
|
if (msg->any.shifts & KB_SHIFT_FLAG) {
|
|
if (entry->select < 0)
|
|
entry->select = entry->cursor;
|
|
}
|
|
else
|
|
entry->select = -1;
|
|
|
|
entry->cursor = ustrlen(text);
|
|
break;
|
|
|
|
case KEY_DEL:
|
|
/* delete the entire selection */
|
|
if (selbeg >= 0) {
|
|
/* *cut* text! */
|
|
if (msg->any.shifts & KB_SHIFT_FLAG) {
|
|
char buf[1024];
|
|
ustrcpy(buf, empty_string);
|
|
for (c=selbeg; c<=selend; c++)
|
|
uinsert(buf, ustrlen(buf), ugetat(text, c));
|
|
jclipboard_set_text(buf);
|
|
}
|
|
|
|
/* remove text */
|
|
for (c=0; c<selend-selbeg+1; c++)
|
|
uremove(text, selbeg);
|
|
|
|
entry->cursor = selbeg;
|
|
}
|
|
/* delete the next character */
|
|
else {
|
|
if (entry->cursor < ustrlen (text))
|
|
uremove(text, entry->cursor);
|
|
}
|
|
|
|
entry->select = -1;
|
|
break;
|
|
|
|
case KEY_INSERT:
|
|
/* *paste* text */
|
|
if (msg->any.shifts & KB_SHIFT_FLAG) {
|
|
const char *clipboard;
|
|
|
|
if ((clipboard = jclipboard_get_text ())) {
|
|
/* delete the entire selection */
|
|
if (selbeg >= 0) {
|
|
for (c=0; c<selend-selbeg+1; c++)
|
|
uremove(text, selbeg);
|
|
|
|
entry->cursor = selbeg;
|
|
entry->select = -1;
|
|
}
|
|
|
|
/* paste text */
|
|
for (c=0; c<ustrlen (clipboard); c++)
|
|
if (ustrsizez(text) < widget->text_size)
|
|
uinsert(text, entry->cursor+c, ugetat(clipboard, c));
|
|
else
|
|
break;
|
|
|
|
jentry_set_cursor_pos(widget, entry->cursor+c);
|
|
}
|
|
}
|
|
/* *copy* text */
|
|
else if ((selbeg >= 0) && (msg->any.shifts & KB_CTRL_FLAG)) {
|
|
char buf[1024];
|
|
ustrcpy(buf, empty_string);
|
|
for (c=selbeg; c<=selend; c++)
|
|
uinsert(buf, ustrlen(buf), ugetat(text, c));
|
|
jclipboard_set_text(buf);
|
|
}
|
|
break;
|
|
|
|
case KEY_BACKSPACE:
|
|
/* delete the entire selection */
|
|
if (selbeg >= 0) {
|
|
for (c=0; c<selend-selbeg+1; c++)
|
|
uremove(text, selbeg);
|
|
|
|
entry->cursor = selbeg;
|
|
}
|
|
/* delete the previous character */
|
|
else {
|
|
if (entry->cursor > 0)
|
|
uremove(text, --entry->cursor);
|
|
}
|
|
|
|
entry->select = -1;
|
|
break;
|
|
|
|
default:
|
|
if (msg->key.ascii >= 32) {
|
|
/* delete the entire selection */
|
|
if (selbeg >= 0) {
|
|
for (c=0; c<selend-selbeg+1; c++)
|
|
uremove(text, selbeg);
|
|
|
|
entry->cursor = selbeg;
|
|
}
|
|
|
|
/* put the character */
|
|
if (ustrsizez(text) < widget->text_size)
|
|
uinsert(text, entry->cursor++, msg->key.ascii);
|
|
|
|
entry->select = -1;
|
|
break;
|
|
}
|
|
else {
|
|
jfree(text);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (ustrcmp(widget->text, text) != 0) {
|
|
jwidget_set_text(widget, text);
|
|
jwidget_emit_signal(widget, JI_SIGNAL_ENTRY_CHANGE);
|
|
}
|
|
|
|
jfree(text);
|
|
|
|
jentry_set_cursor_pos(widget, entry->cursor);
|
|
jwidget_dirty(widget);
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case JM_BUTTONPRESSED:
|
|
jwidget_capture_mouse(widget);
|
|
|
|
case JM_MOTION:
|
|
if (jwidget_has_capture(widget)) {
|
|
const char *text = widget->text;
|
|
bool move, dirty;
|
|
int c, x;
|
|
|
|
move = TRUE;
|
|
dirty = FALSE;
|
|
|
|
/* backward scroll */
|
|
if (msg->mouse.x < widget->rc->x1) {
|
|
if (entry->scroll > 0) {
|
|
entry->cursor = --entry->scroll;
|
|
move = FALSE;
|
|
dirty = TRUE;
|
|
jwidget_dirty(widget);
|
|
}
|
|
}
|
|
/* forward scroll */
|
|
else if (msg->mouse.x >= widget->rc->x2) {
|
|
if (entry->scroll < ustrlen(text)) {
|
|
entry->scroll++;
|
|
x = widget->rc->x1 + widget->border_width.l;
|
|
for (c=entry->scroll; ; c++) {
|
|
x += CHARACTER_LENGTH(widget->text_font,
|
|
(c < ustrlen(text))? ugetat(text, c): ' ');
|
|
if (x > widget->rc->x2-widget->border_width.r) {
|
|
c--;
|
|
break;
|
|
}
|
|
else if (!ugetat (text, c))
|
|
break;
|
|
}
|
|
entry->cursor = c;
|
|
move = FALSE;
|
|
dirty = TRUE;
|
|
jwidget_dirty(widget);
|
|
}
|
|
}
|
|
|
|
/* move cursor */
|
|
if (move) {
|
|
c = entry_get_cursor_from_mouse(widget, msg);
|
|
|
|
if (entry->cursor != c) {
|
|
entry->cursor = c;
|
|
dirty = TRUE;
|
|
jwidget_dirty(widget);
|
|
}
|
|
}
|
|
|
|
/* move selection */
|
|
if (entry->recent_focused) {
|
|
entry->recent_focused = FALSE;
|
|
entry->select = entry->cursor;
|
|
}
|
|
else if (msg->type == JM_BUTTONPRESSED)
|
|
entry->select = entry->cursor;
|
|
|
|
/* show the cursor */
|
|
if (dirty) {
|
|
jmanager_start_timer(entry->timer_id);
|
|
entry->state = TRUE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case JM_BUTTONRELEASED:
|
|
if (jwidget_has_capture(widget))
|
|
jwidget_release_mouse(widget);
|
|
return TRUE;
|
|
|
|
case JM_DOUBLECLICK:
|
|
entry_forward_word(widget);
|
|
entry->select = entry->cursor;
|
|
entry_backward_word(widget);
|
|
jwidget_dirty(widget);
|
|
return TRUE;
|
|
|
|
case JM_MOUSEENTER:
|
|
case JM_MOUSELEAVE:
|
|
/* TODO theme stuff */
|
|
if (jwidget_is_enabled(widget))
|
|
jwidget_dirty(widget);
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void entry_request_size(JWidget widget, int *w, int *h)
|
|
{
|
|
*w =
|
|
+ widget->border_width.l
|
|
+ ji_font_char_len(widget->text_font, 'w') * MIN(widget->text_size, 6)
|
|
+ 2 + widget->border_width.r;
|
|
|
|
*w = MIN(*w, JI_SCREEN_W/2);
|
|
|
|
*h =
|
|
+ widget->border_width.t
|
|
+ text_height(widget->text_font)
|
|
+ widget->border_width.b;
|
|
}
|
|
|
|
static int entry_get_cursor_from_mouse(JWidget widget, JMessage msg)
|
|
{
|
|
Entry *entry = jwidget_get_data(widget, JI_ENTRY);
|
|
int c, x, w, mx, cursor = entry->cursor;
|
|
|
|
mx = msg->mouse.x;
|
|
mx = MID(widget->rc->x1+widget->border_width.l,
|
|
mx,
|
|
widget->rc->x2-widget->border_width.r-1);
|
|
|
|
x = widget->rc->x1 + widget->border_width.l;
|
|
for (c=entry->scroll; ugetat (widget->text, c); c++) {
|
|
w = CHARACTER_LENGTH(widget->text_font, ugetat(widget->text, c));
|
|
if (x+w >= widget->rc->x2-widget->border_width.r)
|
|
break;
|
|
if ((mx >= x) && (mx < x+w)) {
|
|
cursor = c;
|
|
break;
|
|
}
|
|
x += w;
|
|
}
|
|
|
|
if (!ugetat (widget->text, c))
|
|
if ((mx >= x) &&
|
|
(mx <= widget->rc->x2-widget->border_width.r-1))
|
|
cursor = c;
|
|
|
|
return cursor;
|
|
}
|
|
|
|
#define IS_WORD_CHAR(ch) \
|
|
(!((!ch) || (uisspace(ch)) || \
|
|
((ch) == '/') || ((ch) == OTHER_PATH_SEPARATOR)))
|
|
|
|
static void entry_forward_word(JWidget widget)
|
|
{
|
|
Entry *entry = jwidget_get_data(widget, JI_ENTRY);
|
|
int ch;
|
|
|
|
for (; entry->cursor<ustrlen (widget->text); entry->cursor++) {
|
|
ch = ugetat(widget->text, entry->cursor);
|
|
if (IS_WORD_CHAR (ch))
|
|
break;
|
|
}
|
|
|
|
for (; entry->cursor<ustrlen(widget->text); entry->cursor++) {
|
|
ch = ugetat(widget->text, entry->cursor);
|
|
if (!IS_WORD_CHAR(ch)) {
|
|
entry->cursor++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void entry_backward_word(JWidget widget)
|
|
{
|
|
Entry *entry = jwidget_get_data(widget, JI_ENTRY);
|
|
int ch;
|
|
|
|
for (entry->cursor--; entry->cursor >= 0; entry->cursor--) {
|
|
ch = ugetat(widget->text, entry->cursor);
|
|
if (IS_WORD_CHAR(ch))
|
|
break;
|
|
}
|
|
|
|
for (; entry->cursor >= 0; entry->cursor--) {
|
|
ch = ugetat(widget->text, entry->cursor);
|
|
if (!IS_WORD_CHAR(ch)) {
|
|
entry->cursor++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (entry->cursor < 0)
|
|
entry->cursor = 0;
|
|
}
|