aseprite/src/ui/entry.cpp

664 lines
14 KiB
C++
Raw Normal View History

// ASEPRITE gui library
2013-01-27 23:13:13 +08:00
// Copyright (C) 2001-2013 David Capello
//
// This source file is distributed under a BSD-like license, please
// read LICENSE.txt for more information.
2007-09-19 07:57:02 +08:00
#ifdef HAVE_CONFIG_H
2009-07-13 04:29:16 +08:00
#include "config.h"
#endif
2009-07-13 04:29:16 +08:00
2013-03-31 00:11:49 +08:00
#include "ui/entry.h"
2007-09-19 07:57:02 +08:00
2012-06-18 09:49:58 +08:00
#include "ui/clipboard.h"
#include "ui/font.h"
#include "ui/manager.h"
#include "ui/message.h"
#include "ui/preferred_size_event.h"
#include "ui/rect.h"
#include "ui/system.h"
#include "ui/theme.h"
#include "ui/widget.h"
2007-09-19 07:57:02 +08:00
2013-03-31 00:11:49 +08:00
#include <allegro.h>
#include <allegro/internal/aintern.h>
#include <stdarg.h>
#include <stdio.h>
#define CHARACTER_LENGTH(f, c) ((f)->vtable->char_length((f), (c)))
2007-09-19 07:57:02 +08:00
namespace ui {
2010-12-09 01:28:13 +08:00
Entry::Entry(size_t maxsize, const char *format, ...)
: Widget(kEntryWidget)
, m_timer(500, this)
2007-09-19 07:57:02 +08:00
{
char buf[4096];
// formatted string
2007-09-19 07:57:02 +08:00
if (format) {
va_list ap;
va_start(ap, format);
vsprintf(buf, format, ap);
va_end(ap);
2007-09-19 07:57:02 +08:00
}
// empty string
2007-09-19 07:57:02 +08:00
else {
ustrcpy(buf, empty_string);
2007-09-19 07:57:02 +08:00
}
2010-12-09 01:28:13 +08:00
m_maxsize = maxsize;
m_caret = 0;
m_scroll = 0;
m_select = 0;
m_hidden = false;
m_state = false;
m_password = false;
m_readonly = false;
m_recent_focused = false;
2007-09-19 07:57:02 +08:00
2007-12-05 05:50:31 +08:00
/* TODO support for text alignment and multi-line */
2007-09-19 07:57:02 +08:00
/* widget->align = JI_LEFT | JI_MIDDLE; */
2010-12-09 01:28:13 +08:00
setText(buf);
2007-09-19 07:57:02 +08:00
this->setFocusStop(true);
initTheme();
2007-09-19 07:57:02 +08:00
}
2010-12-09 01:28:13 +08:00
Entry::~Entry()
2007-09-19 07:57:02 +08:00
{
}
2010-12-09 01:28:13 +08:00
bool Entry::isReadOnly() const
2007-09-19 07:57:02 +08:00
{
2010-12-09 01:28:13 +08:00
return m_readonly;
2007-09-19 07:57:02 +08:00
}
2010-12-09 01:28:13 +08:00
bool Entry::isPassword() const
2007-09-19 07:57:02 +08:00
{
2010-12-09 01:28:13 +08:00
return m_password;
2007-09-19 07:57:02 +08:00
}
2010-12-09 01:28:13 +08:00
void Entry::setReadOnly(bool state)
2007-09-19 07:57:02 +08:00
{
2010-12-09 01:28:13 +08:00
m_readonly = state;
2007-09-19 07:57:02 +08:00
}
2010-12-09 01:28:13 +08:00
void Entry::setPassword(bool state)
2007-09-19 07:57:02 +08:00
{
2010-12-09 01:28:13 +08:00
m_password = state;
2007-09-19 07:57:02 +08:00
}
2010-12-09 01:28:13 +08:00
void Entry::showCaret()
2007-09-19 07:57:02 +08:00
{
2010-12-09 01:28:13 +08:00
m_hidden = false;
invalidate();
2010-12-09 01:28:13 +08:00
}
2007-09-19 07:57:02 +08:00
2010-12-09 01:28:13 +08:00
void Entry::hideCaret()
{
m_hidden = true;
invalidate();
2007-09-19 07:57:02 +08:00
}
2010-12-09 01:28:13 +08:00
void Entry::setCaretPos(int pos)
2007-09-19 07:57:02 +08:00
{
2010-12-09 01:28:13 +08:00
const char *text = this->getText();
2007-09-19 07:57:02 +08:00
int x, c;
2010-12-09 01:28:13 +08:00
m_caret = pos;
2007-09-19 07:57:02 +08:00
/* backward scroll */
2010-12-09 01:28:13 +08:00
if (m_caret < m_scroll)
m_scroll = m_caret;
2007-09-19 07:57:02 +08:00
/* forward scroll */
2010-12-09 01:28:13 +08:00
m_scroll--;
2007-09-19 07:57:02 +08:00
do {
2010-12-09 01:28:13 +08:00
x = this->rc->x1 + this->border_width.l;
for (c=++m_scroll; ; c++) {
x += CHARACTER_LENGTH(this->getFont(),
(c < ustrlen(text))? ugetat(text, c): ' ');
2007-09-19 07:57:02 +08:00
2010-12-09 01:28:13 +08:00
if (x >= this->rc->x2-this->border_width.r)
2007-09-19 07:57:02 +08:00
break;
}
2010-12-09 01:28:13 +08:00
} while (m_caret >= c);
2007-09-19 07:57:02 +08:00
m_timer.start();
2010-12-09 01:28:13 +08:00
m_state = true;
2007-09-19 07:57:02 +08:00
invalidate();
2007-09-19 07:57:02 +08:00
}
2010-12-09 01:28:13 +08:00
void Entry::selectText(int from, int to)
2007-09-19 07:57:02 +08:00
{
2010-12-09 01:28:13 +08:00
int end = ustrlen(this->getText());
2007-09-19 07:57:02 +08:00
2010-12-09 01:28:13 +08:00
m_select = from;
setCaretPos(from); // to move scroll
setCaretPos((to >= 0)? to: end+to+1);
2007-09-19 07:57:02 +08:00
invalidate();
2007-09-19 07:57:02 +08:00
}
2010-12-09 01:28:13 +08:00
void Entry::deselectText()
2007-09-19 07:57:02 +08:00
{
2010-12-09 01:28:13 +08:00
m_select = -1;
invalidate();
2007-09-19 07:57:02 +08:00
}
2013-03-30 03:20:32 +08:00
void Entry::setSuffix(const std::string& suffix)
{
m_suffix = suffix;
invalidate();
}
2010-12-09 01:28:13 +08:00
void Entry::getEntryThemeInfo(int* scroll, int* caret, int* state,
int* selbeg, int* selend)
2007-09-19 07:57:02 +08:00
{
2010-12-09 01:28:13 +08:00
if (scroll) *scroll = m_scroll;
if (caret) *caret = m_caret;
if (state) *state = !m_hidden && m_state;
if ((m_select >= 0) &&
(m_caret != m_select)) {
*selbeg = MIN(m_caret, m_select);
*selend = MAX(m_caret, m_select)-1;
2007-09-19 07:57:02 +08:00
}
else {
*selbeg = -1;
*selend = -1;
}
}
bool Entry::onProcessMessage(Message* msg)
2007-09-19 07:57:02 +08:00
{
switch (msg->type()) {
2007-09-19 07:57:02 +08:00
case kTimerMessage:
if (hasFocus() && static_cast<TimerMessage*>(msg)->timer() == &m_timer) {
// Blinking caret
m_state = m_state ? false: true;
invalidate();
2007-09-19 07:57:02 +08:00
}
break;
case kFocusEnterMessage:
m_timer.start();
2010-12-09 01:28:13 +08:00
m_state = true;
invalidate();
2007-09-19 07:57:02 +08:00
2010-12-09 01:28:13 +08:00
selectText(0, -1);
m_recent_focused = true;
2007-09-19 07:57:02 +08:00
break;
case kFocusLeaveMessage:
invalidate();
2007-09-19 07:57:02 +08:00
m_timer.stop();
2010-12-09 01:28:13 +08:00
deselectText();
m_recent_focused = false;
2007-09-19 07:57:02 +08:00
break;
case kKeyDownMessage:
if (hasFocus() && !isReadOnly()) {
// Command to execute
EntryCmd::Type cmd = EntryCmd::NoOp;
KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
KeyScancode scancode = keymsg->scancode();
switch (scancode) {
case KEY_LEFT:
if (msg->ctrlPressed())
cmd = EntryCmd::BackwardWord;
else
cmd = EntryCmd::BackwardChar;
break;
case KEY_RIGHT:
if (msg->ctrlPressed())
cmd = EntryCmd::ForwardWord;
else
cmd = EntryCmd::ForwardChar;
break;
case KEY_HOME:
cmd = EntryCmd::BeginningOfLine;
break;
case KEY_END:
cmd = EntryCmd::EndOfLine;
break;
case KEY_DEL:
if (msg->shiftPressed())
cmd = EntryCmd::Cut;
else
cmd = EntryCmd::DeleteForward;
break;
case KEY_INSERT:
if (msg->shiftPressed())
cmd = EntryCmd::Paste;
else if (msg->ctrlPressed())
cmd = EntryCmd::Copy;
break;
case KEY_BACKSPACE:
cmd = EntryCmd::DeleteBackward;
break;
default:
if (keymsg->ascii() >= 32) {
// Ctrl and Alt must be unpressed to insert a character
// in the text-field.
if ((msg->keyModifiers() & (kKeyCtrlModifier | kKeyAltModifier)) == 0) {
cmd = EntryCmd::InsertChar;
}
}
else {
// Map common Windows shortcuts for Cut/Copy/Paste
if (msg->onlyCtrlPressed()) {
switch (scancode) {
case kKeyX: cmd = EntryCmd::Cut; break;
case kKeyC: cmd = EntryCmd::Copy; break;
case kKeyV: cmd = EntryCmd::Paste; break;
}
}
}
break;
}
if (cmd == EntryCmd::NoOp)
break;
executeCmd(cmd, keymsg->ascii(),
(msg->shiftPressed()) ? true: false);
return true;
2007-09-19 07:57:02 +08:00
}
break;
case kMouseDownMessage:
captureMouse();
2007-09-19 07:57:02 +08:00
case kMouseMoveMessage:
if (hasCapture()) {
gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position();
const char *text = this->getText();
int c, x;
bool move = true;
bool is_dirty = false;
// Backward scroll
if (mousePos.x < this->rc->x1) {
if (m_scroll > 0) {
m_caret = --m_scroll;
move = false;
is_dirty = true;
invalidate();
}
}
// Forward scroll
else if (mousePos.x >= this->rc->x2) {
if (m_scroll < ustrlen(text)) {
m_scroll++;
x = this->rc->x1 + this->border_width.l;
for (c=m_scroll; ; c++) {
x += CHARACTER_LENGTH(this->getFont(),
(c < ustrlen(text))? ugetat(text, c): ' ');
if (x > this->rc->x2-this->border_width.r) {
c--;
break;
}
else if (!ugetat (text, c))
break;
}
m_caret = c;
move = false;
is_dirty = true;
invalidate();
}
}
// Move caret
if (move) {
c = getCaretFromMouse(static_cast<MouseMessage*>(msg));
if (m_caret != c) {
m_caret = c;
is_dirty = true;
invalidate();
}
}
// Move selection
if (m_recent_focused) {
m_recent_focused = false;
m_select = m_caret;
}
else if (msg->type() == kMouseDownMessage)
m_select = m_caret;
// Show the caret
if (is_dirty) {
m_timer.start();
m_state = true;
}
return true;
2007-09-19 07:57:02 +08:00
}
break;
case kMouseUpMessage:
if (hasCapture())
releaseMouse();
return true;
2007-09-19 07:57:02 +08:00
case kDoubleClickMessage:
2010-12-09 01:28:13 +08:00
forwardWord();
m_select = m_caret;
backwardWord();
invalidate();
return true;
2007-09-19 07:57:02 +08:00
case kMouseEnterMessage:
case kMouseLeaveMessage:
2007-12-05 05:50:31 +08:00
/* TODO theme stuff */
if (isEnabled())
invalidate();
2007-09-19 07:57:02 +08:00
break;
}
2010-12-09 01:28:13 +08:00
return Widget::onProcessMessage(msg);
2007-09-19 07:57:02 +08:00
}
2010-12-09 01:28:13 +08:00
void Entry::onPreferredSize(PreferredSizeEvent& ev)
2007-09-19 07:57:02 +08:00
{
2010-12-09 01:28:13 +08:00
int w =
+ border_width.l
+ ji_font_char_len(getFont(), 'w') * MIN(m_maxsize, 6)
+ 2 + border_width.r;
2010-12-09 01:28:13 +08:00
w = MIN(w, JI_SCREEN_W/2);
2007-09-19 07:57:02 +08:00
int h =
2010-12-09 01:28:13 +08:00
+ border_width.t
+ text_height(getFont())
+ border_width.b;
2007-09-19 07:57:02 +08:00
2010-12-09 01:28:13 +08:00
ev.setPreferredSize(w, h);
}
void Entry::onPaint(PaintEvent& ev)
{
getTheme()->paintEntry(ev);
}
void Entry::onSetText()
{
Widget::onSetText();
if (m_caret >= 0 && (size_t)m_caret > getTextSize())
m_caret = (int)getTextSize();
}
2010-12-09 01:28:13 +08:00
void Entry::onEntryChange()
{
EntryChange();
2007-09-19 07:57:02 +08:00
}
int Entry::getCaretFromMouse(MouseMessage* mousemsg)
2007-09-19 07:57:02 +08:00
{
2010-12-09 01:28:13 +08:00
int c, x, w, mx, caret = m_caret;
2007-09-19 07:57:02 +08:00
mx = mousemsg->position().x;
2010-12-09 01:28:13 +08:00
mx = MID(this->rc->x1+this->border_width.l,
mx,
this->rc->x2-this->border_width.r-1);
2007-09-19 07:57:02 +08:00
2010-12-09 01:28:13 +08:00
x = this->rc->x1 + this->border_width.l;
for (c=m_scroll; ugetat(this->getText(), c); c++) {
w = CHARACTER_LENGTH(this->getFont(), ugetat(this->getText(), c));
if (x+w >= this->rc->x2-this->border_width.r)
2007-09-19 07:57:02 +08:00
break;
if ((mx >= x) && (mx < x+w)) {
2010-12-09 01:28:13 +08:00
caret = c;
2007-09-19 07:57:02 +08:00
break;
}
x += w;
}
2010-12-09 01:28:13 +08:00
if (!ugetat(this->getText(), c))
2007-09-19 07:57:02 +08:00
if ((mx >= x) &&
(mx <= this->rc->x2-this->border_width.r-1))
2010-12-09 01:28:13 +08:00
caret = c;
2007-09-19 07:57:02 +08:00
2010-12-09 01:28:13 +08:00
return caret;
2007-09-19 07:57:02 +08:00
}
2010-12-09 01:28:13 +08:00
void Entry::executeCmd(EntryCmd::Type cmd, int ascii, bool shift_pressed)
{
std::string text = getText();
int c, selbeg, selend;
2010-12-09 01:28:13 +08:00
getEntryThemeInfo(NULL, NULL, NULL, &selbeg, &selend);
switch (cmd) {
case EntryCmd::NoOp:
break;
case EntryCmd::InsertChar:
// delete the entire selection
if (selbeg >= 0) {
text.erase(selbeg, selend-selbeg+1);
m_caret = selbeg;
}
// put the character
if (text.size() < m_maxsize) {
ASSERT((size_t)m_caret <= text.size());
text.insert(m_caret++, 1, ascii);
}
2010-12-09 01:28:13 +08:00
m_select = -1;
break;
case EntryCmd::BackwardChar:
case EntryCmd::BackwardWord:
// selection
if (shift_pressed) {
if (m_select < 0)
m_select = m_caret;
}
else
m_select = -1;
// backward word
if (cmd == EntryCmd::BackwardWord) {
backwardWord();
}
// backward char
2010-12-09 01:28:13 +08:00
else if (m_caret > 0) {
m_caret--;
}
break;
case EntryCmd::ForwardChar:
case EntryCmd::ForwardWord:
// selection
if (shift_pressed) {
if (m_select < 0)
m_select = m_caret;
}
else
m_select = -1;
// forward word
if (cmd == EntryCmd::ForwardWord) {
forwardWord();
}
// forward char
2010-12-09 01:28:13 +08:00
else if (m_caret < (int)text.size()) {
m_caret++;
}
break;
case EntryCmd::BeginningOfLine:
// selection
if (shift_pressed) {
if (m_select < 0)
m_select = m_caret;
}
else
m_select = -1;
2010-12-09 01:28:13 +08:00
m_caret = 0;
break;
case EntryCmd::EndOfLine:
// selection
if (shift_pressed) {
if (m_select < 0)
m_select = m_caret;
}
else
m_select = -1;
2010-12-09 01:28:13 +08:00
m_caret = text.size();
break;
case EntryCmd::DeleteForward:
case EntryCmd::Cut:
// delete the entire selection
if (selbeg >= 0) {
// *cut* text!
if (cmd == EntryCmd::Cut) {
base::string buf = text.substr(selbeg, selend - selbeg + 1);
clipboard::set_text(buf.c_str());
}
// remove text
text.erase(selbeg, selend-selbeg+1);
m_caret = selbeg;
}
// delete the next character
else {
if (m_caret < (int)text.size())
text.erase(m_caret, 1);
}
2010-12-09 01:28:13 +08:00
m_select = -1;
break;
case EntryCmd::Paste: {
const char *clipboard;
if ((clipboard = clipboard::get_text())) {
// delete the entire selection
if (selbeg >= 0) {
text.erase(selbeg, selend-selbeg+1);
m_caret = selbeg;
m_select = -1;
}
// paste text
for (c=0; c<ustrlen(clipboard); c++)
if (text.size() < m_maxsize)
text.insert(m_caret+c, 1, ugetat(clipboard, c));
else
break;
setCaretPos(m_caret+c);
}
break;
}
case EntryCmd::Copy:
if (selbeg >= 0) {
base::string buf = text.substr(selbeg, selend - selbeg + 1);
clipboard::set_text(buf.c_str());
}
break;
case EntryCmd::DeleteBackward:
// delete the entire selection
if (selbeg >= 0) {
text.erase(selbeg, selend-selbeg+1);
m_caret = selbeg;
}
// delete the previous character
else {
if (m_caret > 0)
text.erase(--m_caret, 1);
}
2010-12-09 01:28:13 +08:00
m_select = -1;
break;
}
2010-12-09 01:28:13 +08:00
if (text != this->getText()) {
this->setText(text.c_str());
onEntryChange();
}
2010-12-09 01:28:13 +08:00
setCaretPos(m_caret);
invalidate();
}
#define IS_WORD_CHAR(ch) \
(!((!ch) || (uisspace(ch)) || \
2007-09-19 07:57:02 +08:00
((ch) == '/') || ((ch) == OTHER_PATH_SEPARATOR)))
2010-12-09 01:28:13 +08:00
void Entry::forwardWord()
2007-09-19 07:57:02 +08:00
{
int ch;
2010-12-09 01:28:13 +08:00
for (; m_caret<ustrlen(this->getText()); m_caret++) {
ch = ugetat(this->getText(), m_caret);
2007-09-19 07:57:02 +08:00
if (IS_WORD_CHAR (ch))
break;
}
2010-12-09 01:28:13 +08:00
for (; m_caret<ustrlen(this->getText()); m_caret++) {
ch = ugetat(this->getText(), m_caret);
if (!IS_WORD_CHAR(ch)) {
2010-12-09 01:28:13 +08:00
m_caret++;
2007-09-19 07:57:02 +08:00
break;
}
}
}
2010-12-09 01:28:13 +08:00
void Entry::backwardWord()
2007-09-19 07:57:02 +08:00
{
int ch;
2010-12-09 01:28:13 +08:00
for (m_caret--; m_caret >= 0; m_caret--) {
ch = ugetat(this->getText(), m_caret);
if (IS_WORD_CHAR(ch))
2007-09-19 07:57:02 +08:00
break;
}
2010-12-09 01:28:13 +08:00
for (; m_caret >= 0; m_caret--) {
ch = ugetat(this->getText(), m_caret);
if (!IS_WORD_CHAR(ch)) {
2010-12-09 01:28:13 +08:00
m_caret++;
2007-09-19 07:57:02 +08:00
break;
}
}
2010-12-09 01:28:13 +08:00
if (m_caret < 0)
m_caret = 0;
2007-09-19 07:57:02 +08:00
}
} // namespace ui