mirror of https://github.com/OpenTTD/OpenTTD.git
(svn r25979) [1.3] -Backport from trunk:
- Fix: [OSX] The new 10.7 fullscreen code can now also be compiled with older SDK versions [FS#4744] (r25656) - Fix: [OSX] Mouse cursor was not displayed properly after switching to fullscreen on 10.7+ (r25655) - Fix: Improve character and word deletion for CJK languages and complex scripts (r25654, r25653) - Fix: [OSX] Define version constants before they are used (r25643)
This commit is contained in:
parent
38bcb67c38
commit
6b47e4ba24
|
@ -573,6 +573,7 @@
|
|||
<ClInclude Include="..\src\statusbar_gui.h" />
|
||||
<ClInclude Include="..\src\stdafx.h" />
|
||||
<ClInclude Include="..\src\strgen\strgen.h" />
|
||||
<ClInclude Include="..\src\string_base.h" />
|
||||
<ClInclude Include="..\src\string_func.h" />
|
||||
<ClInclude Include="..\src\string_type.h" />
|
||||
<ClInclude Include="..\src\stringfilter_type.h" />
|
||||
|
|
|
@ -948,6 +948,9 @@
|
|||
<ClInclude Include="..\src\strgen\strgen.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\string_base.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\src\string_func.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
@ -1566,6 +1566,10 @@
|
|||
RelativePath=".\..\src\strgen\strgen.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\..\src\string_base.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\..\src\string_func.h"
|
||||
>
|
||||
|
|
|
@ -1563,6 +1563,10 @@
|
|||
RelativePath=".\..\src\strgen\strgen.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\..\src\string_base.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\..\src\string_func.h"
|
||||
>
|
||||
|
|
|
@ -306,6 +306,7 @@ station_type.h
|
|||
statusbar_gui.h
|
||||
stdafx.h
|
||||
strgen/strgen.h
|
||||
string_base.h
|
||||
string_func.h
|
||||
string_type.h
|
||||
stringfilter_type.h
|
||||
|
|
|
@ -290,46 +290,13 @@ struct IConsoleWindow : Window
|
|||
MarkWholeScreenDirty();
|
||||
break;
|
||||
|
||||
#ifdef WITH_COCOA
|
||||
case (WKC_META | 'V'):
|
||||
#endif
|
||||
case (WKC_CTRL | 'V'):
|
||||
if (_iconsole_cmdline.InsertClipboard()) {
|
||||
IConsoleResetHistoryPos();
|
||||
this->SetDirty();
|
||||
}
|
||||
break;
|
||||
|
||||
case (WKC_CTRL | 'L'):
|
||||
IConsoleCmdExec("clear");
|
||||
break;
|
||||
|
||||
#ifdef WITH_COCOA
|
||||
case (WKC_META | 'U'):
|
||||
#endif
|
||||
case (WKC_CTRL | 'U'):
|
||||
_iconsole_cmdline.DeleteAll();
|
||||
this->SetDirty();
|
||||
break;
|
||||
|
||||
case WKC_BACKSPACE: case WKC_DELETE:
|
||||
if (_iconsole_cmdline.DeleteChar(keycode)) {
|
||||
IConsoleResetHistoryPos();
|
||||
this->SetDirty();
|
||||
}
|
||||
break;
|
||||
|
||||
case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
|
||||
if (_iconsole_cmdline.MovePos(keycode)) {
|
||||
IConsoleResetHistoryPos();
|
||||
this->SetDirty();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (IsValidChar(key, CS_ALPHANUMERAL)) {
|
||||
if (_iconsole_cmdline.HandleKeyPress(key, keycode) != HKPR_NOT_HANDLED) {
|
||||
IConsoleWindow::scroll = 0;
|
||||
_iconsole_cmdline.InsertChar(key);
|
||||
IConsoleResetHistoryPos();
|
||||
this->SetDirty();
|
||||
} else {
|
||||
|
|
14
src/gfx.cpp
14
src/gfx.cpp
|
@ -639,6 +639,20 @@ Dimension GetStringBoundingBox(StringID strid)
|
|||
return GetStringBoundingBox(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the leading corner of a character in a single-line string relative
|
||||
* to the start of the string.
|
||||
* @param str String containing the character.
|
||||
* @param ch Pointer to the character in the string.
|
||||
* @param start_fontsize Font size to start the text with.
|
||||
* @return Upper left corner of the glyph associated with the character.
|
||||
*/
|
||||
Point GetCharPosInString(const char *str, const char *ch, FontSize start_fontsize)
|
||||
{
|
||||
Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
|
||||
return layout.GetCharPosition(ch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw single character horizontally centered around (x,y)
|
||||
* @param c Character (glyph) to draw
|
||||
|
|
|
@ -126,6 +126,7 @@ int GetStringLineCount(StringID str, int maxw);
|
|||
Dimension GetStringMultiLineBoundingBox(StringID str, const Dimension &suggestion);
|
||||
Dimension GetStringMultiLineBoundingBox(const char *str, const Dimension &suggestion);
|
||||
void LoadStringWidthTable(bool monospace = false);
|
||||
Point GetCharPosInString(const char *str, const char *ch, FontSize start_fontsize = FS_NORMAL);
|
||||
|
||||
void DrawDirtyBlocks();
|
||||
void SetDirtyBlocks(int left, int top, int right, int bottom);
|
||||
|
|
|
@ -423,7 +423,7 @@ ParagraphLayout *Layouter::GetParagraphLayout(WChar *buff, WChar *buff_end, Font
|
|||
* @param colour The colour of the font.
|
||||
* @param fontsize The size of font to use.
|
||||
*/
|
||||
Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsize)
|
||||
Layouter::Layouter(const char *str, int maxw, TextColour colour, FontSize fontsize) : string(str)
|
||||
{
|
||||
FontState state(colour, fontsize);
|
||||
WChar c = 0;
|
||||
|
@ -512,6 +512,59 @@ Dimension Layouter::GetBounds()
|
|||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the position of a character in the layout.
|
||||
* @param ch Character to get the position of.
|
||||
* @return Upper left corner of the character relative to the start of the string.
|
||||
* @note Will only work right for single-line strings.
|
||||
*/
|
||||
Point Layouter::GetCharPosition(const char *ch) const
|
||||
{
|
||||
/* Find the code point index which corresponds to the char
|
||||
* pointer into our UTF-8 source string. */
|
||||
size_t index = 0;
|
||||
const char *str = this->string;
|
||||
while (str < ch) {
|
||||
WChar c;
|
||||
size_t len = Utf8Decode(&c, str);
|
||||
if (c == '\0' || c == '\n') break;
|
||||
str += len;
|
||||
#ifdef WITH_ICU
|
||||
/* ICU uses UTF-16 internally which means we need to account for surrogate pairs. */
|
||||
index += len < 4 ? 1 : 2;
|
||||
#else
|
||||
index++;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (str == ch) {
|
||||
/* Valid character. */
|
||||
const ParagraphLayout::Line *line = *this->Begin();
|
||||
|
||||
/* Pointer to the end-of-string/line marker? Return total line width. */
|
||||
if (*ch == '\0' || *ch == '\n') {
|
||||
Point p = { line->getWidth(), 0 };
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Scan all runs until we've found our code point index. */
|
||||
for (int run_index = 0; run_index < line->countRuns(); run_index++) {
|
||||
const ParagraphLayout::VisualRun *run = line->getVisualRun(run_index);
|
||||
|
||||
for (int i = 0; i < run->getGlyphCount(); i++) {
|
||||
/* Matching glyph? Return position. */
|
||||
if ((size_t)run->getGlyphToCharMap()[i] == index) {
|
||||
Point p = { run->getPositions()[i * 2], run->getPositions()[i * 2 + 1] };
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Point p = { 0, 0 };
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a static font instance.
|
||||
*/
|
||||
|
|
|
@ -168,6 +168,8 @@ class Layouter : public AutoDeleteSmallVector<ParagraphLayout::Line *, 4> {
|
|||
typedef WChar CharType; ///< The type of character used within the layouter.
|
||||
#endif /* WITH_ICU */
|
||||
|
||||
const char *string; ///< Pointer to the original string.
|
||||
|
||||
size_t AppendToBuffer(CharType *buff, const CharType *buffer_last, WChar c);
|
||||
ParagraphLayout *GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping);
|
||||
|
||||
|
@ -209,6 +211,7 @@ class Layouter : public AutoDeleteSmallVector<ParagraphLayout::Line *, 4> {
|
|||
public:
|
||||
Layouter(const char *str, int maxw = INT32_MAX, TextColour colour = TC_FROMSTRING, FontSize fontsize = FS_NORMAL);
|
||||
Dimension GetBounds();
|
||||
Point GetCharPosition(const char *ch) const;
|
||||
|
||||
static void ResetFontCache(FontSize size);
|
||||
static void ResetLineCache();
|
||||
|
|
|
@ -715,56 +715,6 @@ void GuiShowTooltips(Window *parent, StringID str, uint paramcount, const uint64
|
|||
new TooltipsWindow(parent, str, paramcount, params, close_tooltip);
|
||||
}
|
||||
|
||||
HandleEditBoxResult QueryString::HandleEditBoxKey(Window *w, int wid, uint16 key, uint16 keycode, EventState &state)
|
||||
{
|
||||
if (!w->IsWidgetGloballyFocused(wid)) return HEBR_NOT_FOCUSED;
|
||||
|
||||
state = ES_HANDLED;
|
||||
|
||||
bool edited = false;
|
||||
|
||||
switch (keycode) {
|
||||
case WKC_ESC: return HEBR_CANCEL;
|
||||
|
||||
case WKC_RETURN: case WKC_NUM_ENTER: return HEBR_CONFIRM;
|
||||
|
||||
#ifdef WITH_COCOA
|
||||
case (WKC_META | 'V'):
|
||||
#endif
|
||||
case (WKC_CTRL | 'V'):
|
||||
edited = this->text.InsertClipboard();
|
||||
break;
|
||||
|
||||
#ifdef WITH_COCOA
|
||||
case (WKC_META | 'U'):
|
||||
#endif
|
||||
case (WKC_CTRL | 'U'):
|
||||
this->text.DeleteAll();
|
||||
edited = true;
|
||||
break;
|
||||
|
||||
case WKC_BACKSPACE: case WKC_DELETE:
|
||||
case WKC_CTRL | WKC_BACKSPACE: case WKC_CTRL | WKC_DELETE:
|
||||
edited = this->text.DeleteChar(keycode);
|
||||
break;
|
||||
|
||||
case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
|
||||
case WKC_CTRL | WKC_LEFT: case WKC_CTRL | WKC_RIGHT:
|
||||
this->text.MovePos(keycode);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (IsValidChar(key, this->text.afilter)) {
|
||||
edited = this->text.InsertChar(key);
|
||||
} else {
|
||||
state = ES_NOT_HANDLED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return edited ? HEBR_EDITING : HEBR_CURSOR;
|
||||
}
|
||||
|
||||
void QueryString::HandleEditBox(Window *w, int wid)
|
||||
{
|
||||
if (w->IsWidgetGloballyFocused(wid) && this->text.HandleCaret()) {
|
||||
|
|
|
@ -12,32 +12,6 @@
|
|||
#ifndef MACOS_H
|
||||
#define MACOS_H
|
||||
|
||||
/* It would seem that to ensure backward compability we have to ensure that we have defined MAC_OS_X_VERSION_10_x everywhere */
|
||||
#ifndef MAC_OS_X_VERSION_10_3
|
||||
#define MAC_OS_X_VERSION_10_3 1030
|
||||
#endif
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_4
|
||||
#define MAC_OS_X_VERSION_10_4 1040
|
||||
#endif
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_5
|
||||
#define MAC_OS_X_VERSION_10_5 1050
|
||||
#endif
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_6
|
||||
#define MAC_OS_X_VERSION_10_6 1060
|
||||
#endif
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_7
|
||||
#define MAC_OS_X_VERSION_10_7 1070
|
||||
#endif
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_8
|
||||
#define MAC_OS_X_VERSION_10_8 1080
|
||||
#endif
|
||||
|
||||
|
||||
/** Helper function displaying a message the best possible way. */
|
||||
void ShowMacDialog(const char *title, const char *message, const char *button_label);
|
||||
|
||||
|
|
|
@ -12,6 +12,37 @@
|
|||
#ifndef MACOS_STDAFX_H
|
||||
#define MACOS_STDAFX_H
|
||||
|
||||
|
||||
/* It would seem that to ensure backward compability we have to ensure that we have defined MAC_OS_X_VERSION_10_x everywhere */
|
||||
#ifndef MAC_OS_X_VERSION_10_3
|
||||
#define MAC_OS_X_VERSION_10_3 1030
|
||||
#endif
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_4
|
||||
#define MAC_OS_X_VERSION_10_4 1040
|
||||
#endif
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_5
|
||||
#define MAC_OS_X_VERSION_10_5 1050
|
||||
#endif
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_6
|
||||
#define MAC_OS_X_VERSION_10_6 1060
|
||||
#endif
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_7
|
||||
#define MAC_OS_X_VERSION_10_7 1070
|
||||
#endif
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_8
|
||||
#define MAC_OS_X_VERSION_10_8 1080
|
||||
#endif
|
||||
|
||||
#ifndef MAC_OS_X_VERSION_10_9
|
||||
#define MAC_OS_X_VERSION_10_9 1090
|
||||
#endif
|
||||
|
||||
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
|
||||
|
|
|
@ -16,18 +16,6 @@
|
|||
#include "textbuf_gui.h"
|
||||
#include "window_gui.h"
|
||||
|
||||
/**
|
||||
* Return values for HandleEditBoxKey
|
||||
*/
|
||||
enum HandleEditBoxResult
|
||||
{
|
||||
HEBR_EDITING, ///< Editbox content changed.
|
||||
HEBR_CURSOR, ///< Non-text change, e.g. cursor position.
|
||||
HEBR_CONFIRM, ///< Return or enter key pressed.
|
||||
HEBR_CANCEL, ///< Escape key pressed.
|
||||
HEBR_NOT_FOCUSED, ///< Edit box widget not focused.
|
||||
};
|
||||
|
||||
/**
|
||||
* Data stored about a string that can be modified in the GUI
|
||||
*/
|
||||
|
@ -65,7 +53,6 @@ public:
|
|||
void DrawEditBox(const Window *w, int wid) const;
|
||||
void ClickEditBox(Window *w, Point pt, int wid, int click_count, bool focus_changed);
|
||||
void HandleEditBox(Window *w, int wid);
|
||||
HandleEditBoxResult HandleEditBoxKey(Window *w, int wid, uint16 key, uint16 keycode, EventState &state);
|
||||
};
|
||||
|
||||
void ShowOnScreenKeyboard(Window *parent, int button);
|
||||
|
|
256
src/string.cpp
256
src/string.cpp
|
@ -14,6 +14,7 @@
|
|||
#include "core/alloc_func.hpp"
|
||||
#include "core/math_func.hpp"
|
||||
#include "string_func.h"
|
||||
#include "string_base.h"
|
||||
|
||||
#include "table/control_codes.h"
|
||||
|
||||
|
@ -650,3 +651,258 @@ int strnatcmp(const char *s1, const char *s2, bool ignore_garbage_at_front)
|
|||
/* Do a normal comparison if ICU is missing or if we cannot create a collator. */
|
||||
return strcasecmp(s1, s2);
|
||||
}
|
||||
|
||||
#ifdef WITH_ICU
|
||||
|
||||
#include <unicode/utext.h>
|
||||
#include <unicode/brkiter.h>
|
||||
|
||||
/** String iterator using ICU as a backend. */
|
||||
class IcuStringIterator : public StringIterator
|
||||
{
|
||||
icu::BreakIterator *char_itr; ///< ICU iterator for characters.
|
||||
icu::BreakIterator *word_itr; ///< ICU iterator for words.
|
||||
const char *string; ///< Iteration string in UTF-8.
|
||||
|
||||
SmallVector<UChar, 32> utf16_str; ///< UTF-16 copy of the string.
|
||||
SmallVector<size_t, 32> utf16_to_utf8; ///< Mapping from UTF-16 code point position to index in the UTF-8 source string.
|
||||
|
||||
public:
|
||||
IcuStringIterator() : char_itr(NULL), word_itr(NULL)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
this->char_itr = icu::BreakIterator::createCharacterInstance(icu::Locale(_current_language != NULL ? _current_language->isocode : "en"), status);
|
||||
this->word_itr = icu::BreakIterator::createWordInstance(icu::Locale(_current_language != NULL ? _current_language->isocode : "en"), status);
|
||||
|
||||
*this->utf16_str.Append() = '\0';
|
||||
*this->utf16_to_utf8.Append() = 0;
|
||||
}
|
||||
|
||||
virtual ~IcuStringIterator()
|
||||
{
|
||||
delete this->char_itr;
|
||||
delete this->word_itr;
|
||||
}
|
||||
|
||||
virtual void SetString(const char *s)
|
||||
{
|
||||
this->string = s;
|
||||
|
||||
/* Unfortunately current ICU versions only provide rudimentary support
|
||||
* for word break iterators (especially for CJK languages) in combination
|
||||
* with UTF-8 input. As a work around we have to convert the input to
|
||||
* UTF-16 and create a mapping back to UTF-8 character indices. */
|
||||
this->utf16_str.Clear();
|
||||
this->utf16_to_utf8.Clear();
|
||||
|
||||
while (*s != '\0') {
|
||||
size_t idx = s - this->string;
|
||||
|
||||
WChar c = Utf8Consume(&s);
|
||||
if (c < 0x10000) {
|
||||
*this->utf16_str.Append() = (UChar)c;
|
||||
} else {
|
||||
/* Make a surrogate pair. */
|
||||
*this->utf16_str.Append() = (UChar)(0xD800 + ((c - 0x10000) >> 10));
|
||||
*this->utf16_str.Append() = (UChar)(0xDC00 + ((c - 0x10000) & 0x3FF));
|
||||
*this->utf16_to_utf8.Append() = idx;
|
||||
}
|
||||
*this->utf16_to_utf8.Append() = idx;
|
||||
}
|
||||
*this->utf16_str.Append() = '\0';
|
||||
*this->utf16_to_utf8.Append() = s - this->string;
|
||||
|
||||
UText text = UTEXT_INITIALIZER;
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
utext_openUChars(&text, this->utf16_str.Begin(), this->utf16_str.Length() - 1, &status);
|
||||
this->char_itr->setText(&text, status);
|
||||
this->word_itr->setText(&text, status);
|
||||
this->char_itr->first();
|
||||
this->word_itr->first();
|
||||
}
|
||||
|
||||
virtual size_t SetCurPosition(size_t pos)
|
||||
{
|
||||
/* Convert incoming position to an UTF-16 string index. */
|
||||
uint utf16_pos = 0;
|
||||
for (uint i = 0; i < this->utf16_to_utf8.Length(); i++) {
|
||||
if (this->utf16_to_utf8[i] == pos) {
|
||||
utf16_pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* isBoundary has the documented side-effect of setting the current
|
||||
* position to the first valid boundary equal to or greater than
|
||||
* the passed value. */
|
||||
this->char_itr->isBoundary(utf16_pos);
|
||||
return this->utf16_to_utf8[this->char_itr->current()];
|
||||
}
|
||||
|
||||
virtual size_t Next(IterType what)
|
||||
{
|
||||
int32_t pos;
|
||||
switch (what) {
|
||||
case ITER_CHARACTER:
|
||||
pos = this->char_itr->next();
|
||||
break;
|
||||
|
||||
case ITER_WORD:
|
||||
pos = this->word_itr->following(this->char_itr->current());
|
||||
/* The ICU word iterator considers both the start and the end of a word a valid
|
||||
* break point, but we only want word starts. Move to the next location in
|
||||
* case the new position points to whitespace. */
|
||||
while (pos != icu::BreakIterator::DONE && IsWhitespace(Utf16DecodeChar((const uint16 *)&this->utf16_str[pos]))) pos = this->word_itr->next();
|
||||
|
||||
this->char_itr->isBoundary(pos);
|
||||
break;
|
||||
|
||||
default:
|
||||
NOT_REACHED();
|
||||
}
|
||||
|
||||
return pos == icu::BreakIterator::DONE ? END : this->utf16_to_utf8[pos];
|
||||
}
|
||||
|
||||
virtual size_t Prev(IterType what)
|
||||
{
|
||||
int32_t pos;
|
||||
switch (what) {
|
||||
case ITER_CHARACTER:
|
||||
pos = this->char_itr->previous();
|
||||
break;
|
||||
|
||||
case ITER_WORD:
|
||||
pos = this->word_itr->preceding(this->char_itr->current());
|
||||
/* The ICU word iterator considers both the start and the end of a word a valid
|
||||
* break point, but we only want word starts. Move to the previous location in
|
||||
* case the new position points to whitespace. */
|
||||
while (pos != icu::BreakIterator::DONE && IsWhitespace(Utf16DecodeChar((const uint16 *)&this->utf16_str[pos]))) pos = this->word_itr->previous();
|
||||
|
||||
this->char_itr->isBoundary(pos);
|
||||
break;
|
||||
|
||||
default:
|
||||
NOT_REACHED();
|
||||
}
|
||||
|
||||
return pos == icu::BreakIterator::DONE ? END : this->utf16_to_utf8[pos];
|
||||
}
|
||||
};
|
||||
|
||||
/* static */ StringIterator *StringIterator::Create()
|
||||
{
|
||||
return new IcuStringIterator();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/** Fallback simple string iterator. */
|
||||
class DefaultStringIterator : public StringIterator
|
||||
{
|
||||
const char *string; ///< Current string.
|
||||
size_t len; ///< String length.
|
||||
size_t cur_pos; ///< Current iteration position.
|
||||
|
||||
public:
|
||||
DefaultStringIterator() : string(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void SetString(const char *s)
|
||||
{
|
||||
this->string = s;
|
||||
this->len = strlen(s);
|
||||
this->cur_pos = 0;
|
||||
}
|
||||
|
||||
virtual size_t SetCurPosition(size_t pos)
|
||||
{
|
||||
assert(this->string != NULL && pos <= this->len);
|
||||
/* Sanitize in case we get a position inside an UTF-8 sequence. */
|
||||
while (pos > 0 && IsUtf8Part(this->string[pos])) pos--;
|
||||
return this->cur_pos = pos;
|
||||
}
|
||||
|
||||
virtual size_t Next(IterType what)
|
||||
{
|
||||
assert(this->string != NULL);
|
||||
|
||||
/* Already at the end? */
|
||||
if (this->cur_pos >= this->len) return END;
|
||||
|
||||
switch (what) {
|
||||
case ITER_CHARACTER: {
|
||||
WChar c;
|
||||
this->cur_pos += Utf8Decode(&c, this->string + this->cur_pos);
|
||||
return this->cur_pos;
|
||||
}
|
||||
|
||||
case ITER_WORD: {
|
||||
WChar c;
|
||||
/* Consume current word. */
|
||||
size_t offs = Utf8Decode(&c, this->string + this->cur_pos);
|
||||
while (this->cur_pos < this->len && !IsWhitespace(c)) {
|
||||
this->cur_pos += offs;
|
||||
offs = Utf8Decode(&c, this->string + this->cur_pos);
|
||||
}
|
||||
/* Consume whitespace to the next word. */
|
||||
while (this->cur_pos < this->len && IsWhitespace(c)) {
|
||||
this->cur_pos += offs;
|
||||
offs = Utf8Decode(&c, this->string + this->cur_pos);
|
||||
}
|
||||
|
||||
return this->cur_pos;
|
||||
}
|
||||
|
||||
default:
|
||||
NOT_REACHED();
|
||||
}
|
||||
|
||||
return END;
|
||||
}
|
||||
|
||||
virtual size_t Prev(IterType what)
|
||||
{
|
||||
assert(this->string != NULL);
|
||||
|
||||
/* Already at the beginning? */
|
||||
if (this->cur_pos == 0) return END;
|
||||
|
||||
switch (what) {
|
||||
case ITER_CHARACTER:
|
||||
return this->cur_pos = Utf8PrevChar(this->string + this->cur_pos) - this->string;
|
||||
|
||||
case ITER_WORD: {
|
||||
const char *s = this->string + this->cur_pos;
|
||||
WChar c;
|
||||
/* Consume preceding whitespace. */
|
||||
do {
|
||||
s = Utf8PrevChar(s);
|
||||
Utf8Decode(&c, s);
|
||||
} while (s > this->string && IsWhitespace(c));
|
||||
/* Consume preceding word. */
|
||||
while (s > this->string && !IsWhitespace(c)) {
|
||||
s = Utf8PrevChar(s);
|
||||
Utf8Decode(&c, s);
|
||||
}
|
||||
/* Move caret back to the beginning of the word. */
|
||||
if (IsWhitespace(c)) Utf8Consume(&s);
|
||||
|
||||
return this->cur_pos = s - this->string;
|
||||
}
|
||||
|
||||
default:
|
||||
NOT_REACHED();
|
||||
}
|
||||
|
||||
return END;
|
||||
}
|
||||
};
|
||||
|
||||
/* static */ StringIterator *StringIterator::Create()
|
||||
{
|
||||
return new DefaultStringIterator();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/* $Id$ */
|
||||
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* OpenTTD 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, version 2.
|
||||
* OpenTTD 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 OpenTTD. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef STRING_BASE_H
|
||||
#define STRING_BASE_H
|
||||
|
||||
#include "string_type.h"
|
||||
|
||||
/** Class for iterating over different kind of parts of a string. */
|
||||
class StringIterator {
|
||||
public:
|
||||
/** Type of the iterator. */
|
||||
enum IterType {
|
||||
ITER_CHARACTER, ///< Iterate over characters (or more exactly grapheme clusters).
|
||||
ITER_WORD, ///< Iterate over words.
|
||||
};
|
||||
|
||||
/** Sentinel to indicate end-of-iteration. */
|
||||
static const size_t END = SIZE_MAX;
|
||||
|
||||
/**
|
||||
* Create a new iterator instance.
|
||||
* @return New iterator instance.
|
||||
*/
|
||||
static StringIterator *Create();
|
||||
|
||||
virtual ~StringIterator() {}
|
||||
|
||||
/**
|
||||
* Set a new iteration string. Must also be called if the string contents
|
||||
* changed. The cursor is reset to the start of the string.
|
||||
* @param s New string.
|
||||
*/
|
||||
virtual void SetString(const char *s) = 0;
|
||||
|
||||
/**
|
||||
* Change the current string cursor.
|
||||
* @param p New cursor position.
|
||||
* @return Actual new cursor position at the next valid character boundary.
|
||||
* @pre p has to be inside the current string.
|
||||
*/
|
||||
virtual size_t SetCurPosition(size_t pos) = 0;
|
||||
|
||||
/**
|
||||
* Advance the cursor by one iteration unit.
|
||||
* @return New cursor position (in bytes) or #END if the cursor is already at the end of the string.
|
||||
*/
|
||||
virtual size_t Next(IterType what = ITER_CHARACTER) = 0;
|
||||
|
||||
/**
|
||||
* Move the cursor back by one iteration unit.
|
||||
* @return New cursor position (in bytes) or #END if the cursor is already at the start of the string.
|
||||
*/
|
||||
virtual size_t Prev(IterType what = ITER_CHARACTER) = 0;
|
||||
|
||||
protected:
|
||||
StringIterator() {}
|
||||
};
|
||||
|
||||
#endif /* STRING_BASE_H */
|
|
@ -90,7 +90,6 @@ static inline WChar Utf8Consume(const char **s)
|
|||
return c;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the length of a UTF-8 encoded character.
|
||||
* @param c Unicode character.
|
||||
|
@ -147,8 +146,60 @@ static inline char *Utf8PrevChar(char *s)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static inline const char *Utf8PrevChar(const char *s)
|
||||
{
|
||||
const char *ret = s;
|
||||
while (IsUtf8Part(*--ret)) {}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t Utf8StringLength(const char *s);
|
||||
|
||||
/**
|
||||
* Is the given character a lead surrogate code point?
|
||||
* @param c The character to test.
|
||||
* @return True if the character is a lead surrogate code point.
|
||||
*/
|
||||
static inline bool Utf16IsLeadSurrogate(uint c)
|
||||
{
|
||||
return c >= 0xD800 && c <= 0xDBFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given character a lead surrogate code point?
|
||||
* @param c The character to test.
|
||||
* @return True if the character is a lead surrogate code point.
|
||||
*/
|
||||
static inline bool Utf16IsTrailSurrogate(uint c)
|
||||
{
|
||||
return c >= 0xDC00 && c <= 0xDFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an UTF-16 surrogate pair to the corresponding Unicode character.
|
||||
* @param lead Lead surrogate code point.
|
||||
* @param trail Trail surrogate code point.
|
||||
* @return Decoded Unicode character.
|
||||
*/
|
||||
static inline WChar Utf16DecodeSurrogate(uint lead, uint trail)
|
||||
{
|
||||
return 0x10000 + (((lead - 0xD800) << 10) | (trail - 0xDC00));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an UTF-16 character.
|
||||
* @param c Pointer to one or two UTF-16 code points.
|
||||
* @return Decoded Unicode character.
|
||||
*/
|
||||
static inline WChar Utf16DecodeChar(const uint16 *c)
|
||||
{
|
||||
if (Utf16IsLeadSurrogate(c[0])) {
|
||||
return Utf16DecodeSurrogate(c[0], c[1]);
|
||||
} else {
|
||||
return *c;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given character a text direction character.
|
||||
* @param c The character to test.
|
||||
|
|
323
src/textbuf.cpp
323
src/textbuf.cpp
|
@ -43,101 +43,68 @@ bool Textbuf::CanDelChar(bool backspace)
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the next character that will be removed by DelChar.
|
||||
* @param backspace if set, delete the character before the caret,
|
||||
* otherwise, delete the character after it.
|
||||
* @return the next character that will be removed by DelChar.
|
||||
* @warning You should ensure Textbuf::CanDelChar returns true before calling this function.
|
||||
* Delete a character from a textbuffer, either with 'Delete' or 'Backspace'
|
||||
* The character is delete from the position the caret is at
|
||||
* @param keycode Type of deletion, either WKC_BACKSPACE or WKC_DELETE
|
||||
* @return Return true on successful change of Textbuf, or false otherwise
|
||||
*/
|
||||
WChar Textbuf::GetNextDelChar(bool backspace)
|
||||
bool Textbuf::DeleteChar(uint16 keycode)
|
||||
{
|
||||
assert(this->CanDelChar(backspace));
|
||||
bool word = (keycode & WKC_CTRL) != 0;
|
||||
|
||||
const char *s;
|
||||
if (backspace) {
|
||||
s = Utf8PrevChar(this->buf + this->caretpos);
|
||||
} else {
|
||||
s = this->buf + this->caretpos;
|
||||
}
|
||||
keycode &= ~WKC_SPECIAL_KEYS;
|
||||
if (keycode != WKC_BACKSPACE && keycode != WKC_DELETE) return false;
|
||||
|
||||
WChar c;
|
||||
Utf8Decode(&c, s);
|
||||
return c;
|
||||
}
|
||||
bool backspace = keycode == WKC_BACKSPACE;
|
||||
|
||||
/**
|
||||
* Delete a character at the caret position in a text buf.
|
||||
* @param backspace if set, delete the character before the caret,
|
||||
* else delete the character after it.
|
||||
* @warning You should ensure Textbuf::CanDelChar returns true before calling this function.
|
||||
*/
|
||||
void Textbuf::DelChar(bool backspace)
|
||||
{
|
||||
assert(this->CanDelChar(backspace));
|
||||
if (!CanDelChar(backspace)) return false;
|
||||
|
||||
WChar c;
|
||||
char *s = this->buf + this->caretpos;
|
||||
uint16 len = 0;
|
||||
|
||||
if (backspace) s = Utf8PrevChar(s);
|
||||
|
||||
uint16 len = (uint16)Utf8Decode(&c, s);
|
||||
uint width = GetCharacterWidth(FS_NORMAL, c);
|
||||
|
||||
this->pixels -= width;
|
||||
if (backspace) {
|
||||
this->caretpos -= len;
|
||||
this->caretxoffs -= width;
|
||||
if (word) {
|
||||
/* Delete a complete word. */
|
||||
if (backspace) {
|
||||
/* Delete whitespace and word in front of the caret. */
|
||||
len = this->caretpos - (uint16)this->char_iter->Prev(StringIterator::ITER_WORD);
|
||||
s -= len;
|
||||
} else {
|
||||
/* Delete word and following whitespace following the caret. */
|
||||
len = (uint16)this->char_iter->Next(StringIterator::ITER_WORD) - this->caretpos;
|
||||
}
|
||||
/* Update character count. */
|
||||
for (const char *ss = s; ss < s + len; Utf8Consume(&ss)) {
|
||||
this->chars--;
|
||||
}
|
||||
} else {
|
||||
/* Delete a single character. */
|
||||
if (backspace) {
|
||||
/* Delete the last code point in front of the caret. */
|
||||
s = Utf8PrevChar(s);
|
||||
WChar c;
|
||||
len = (uint16)Utf8Decode(&c, s);
|
||||
this->chars--;
|
||||
} else {
|
||||
/* Delete the complete character following the caret. */
|
||||
len = (uint16)this->char_iter->Next(StringIterator::ITER_CHARACTER) - this->caretpos;
|
||||
/* Update character count. */
|
||||
for (const char *ss = s; ss < s + len; Utf8Consume(&ss)) {
|
||||
this->chars--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Move the remaining characters over the marker */
|
||||
memmove(s, s + len, this->bytes - (s - this->buf) - len);
|
||||
this->bytes -= len;
|
||||
this->chars--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a character from a textbuffer, either with 'Delete' or 'Backspace'
|
||||
* The character is delete from the position the caret is at
|
||||
* @param delmode Type of deletion, either WKC_BACKSPACE or WKC_DELETE
|
||||
* @return Return true on successful change of Textbuf, or false otherwise
|
||||
*/
|
||||
bool Textbuf::DeleteChar(int delmode)
|
||||
{
|
||||
if (delmode == WKC_BACKSPACE || delmode == WKC_DELETE) {
|
||||
bool backspace = delmode == WKC_BACKSPACE;
|
||||
if (CanDelChar(backspace)) {
|
||||
this->DelChar(backspace);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (backspace) this->caretpos -= len;
|
||||
|
||||
if (delmode == (WKC_CTRL | WKC_BACKSPACE) || delmode == (WKC_CTRL | WKC_DELETE)) {
|
||||
bool backspace = delmode == (WKC_CTRL | WKC_BACKSPACE);
|
||||
this->UpdateStringIter();
|
||||
this->UpdateWidth();
|
||||
this->UpdateCaretPosition();
|
||||
|
||||
if (!CanDelChar(backspace)) return false;
|
||||
WChar c = this->GetNextDelChar(backspace);
|
||||
|
||||
/* Backspace: Delete left whitespaces.
|
||||
* Delete: Delete right word.
|
||||
*/
|
||||
while (backspace ? IsWhitespace(c) : !IsWhitespace(c)) {
|
||||
this->DelChar(backspace);
|
||||
if (!this->CanDelChar(backspace)) return true;
|
||||
c = this->GetNextDelChar(backspace);
|
||||
}
|
||||
/* Backspace: Delete left word.
|
||||
* Delete: Delete right whitespaces.
|
||||
*/
|
||||
while (backspace ? !IsWhitespace(c) : IsWhitespace(c)) {
|
||||
this->DelChar(backspace);
|
||||
if (!this->CanDelChar(backspace)) return true;
|
||||
c = this->GetNextDelChar(backspace);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,6 +115,7 @@ void Textbuf::DeleteAll()
|
|||
memset(this->buf, 0, this->max_bytes);
|
||||
this->bytes = this->chars = 1;
|
||||
this->pixels = this->caretpos = this->caretxoffs = 0;
|
||||
this->UpdateStringIter();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -159,17 +127,17 @@ void Textbuf::DeleteAll()
|
|||
*/
|
||||
bool Textbuf::InsertChar(WChar key)
|
||||
{
|
||||
const byte charwidth = GetCharacterWidth(FS_NORMAL, key);
|
||||
uint16 len = (uint16)Utf8CharLen(key);
|
||||
if (this->bytes + len <= this->max_bytes && this->chars + 1 <= this->max_chars) {
|
||||
memmove(this->buf + this->caretpos + len, this->buf + this->caretpos, this->bytes - this->caretpos);
|
||||
Utf8Encode(this->buf + this->caretpos, key);
|
||||
this->chars++;
|
||||
this->bytes += len;
|
||||
this->pixels += charwidth;
|
||||
this->bytes += len;
|
||||
this->caretpos += len;
|
||||
|
||||
this->caretpos += len;
|
||||
this->caretxoffs += charwidth;
|
||||
this->UpdateStringIter();
|
||||
this->UpdateWidth();
|
||||
this->UpdateCaretPosition();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -187,7 +155,7 @@ bool Textbuf::InsertClipboard()
|
|||
|
||||
if (!GetClipboardContents(utf8_buf, lengthof(utf8_buf))) return false;
|
||||
|
||||
uint16 pixels = 0, bytes = 0, chars = 0;
|
||||
uint16 bytes = 0, chars = 0;
|
||||
WChar c;
|
||||
for (const char *ptr = utf8_buf; (c = Utf8Consume(&ptr)) != '\0';) {
|
||||
if (!IsValidChar(c, this->afilter)) break;
|
||||
|
@ -196,9 +164,6 @@ bool Textbuf::InsertClipboard()
|
|||
if (this->bytes + bytes + len > this->max_bytes) break;
|
||||
if (this->chars + chars + 1 > this->max_chars) break;
|
||||
|
||||
byte char_pixels = GetCharacterWidth(FS_NORMAL, c);
|
||||
|
||||
pixels += char_pixels;
|
||||
bytes += len;
|
||||
chars++;
|
||||
}
|
||||
|
@ -207,8 +172,6 @@ bool Textbuf::InsertClipboard()
|
|||
|
||||
memmove(this->buf + this->caretpos + bytes, this->buf + this->caretpos, this->bytes - this->caretpos);
|
||||
memcpy(this->buf + this->caretpos, utf8_buf, bytes);
|
||||
this->pixels += pixels;
|
||||
this->caretxoffs += pixels;
|
||||
|
||||
this->bytes += bytes;
|
||||
this->chars += chars;
|
||||
|
@ -217,131 +180,76 @@ bool Textbuf::InsertClipboard()
|
|||
assert(this->chars <= this->max_chars);
|
||||
this->buf[this->bytes - 1] = '\0'; // terminating zero
|
||||
|
||||
this->UpdateStringIter();
|
||||
this->UpdateWidth();
|
||||
this->UpdateCaretPosition();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if it is possible to move caret to the left
|
||||
* @return true if the caret can be moved to the left, otherwise false.
|
||||
*/
|
||||
bool Textbuf::CanMoveCaretLeft()
|
||||
/** Update the character iter after the text has changed. */
|
||||
void Textbuf::UpdateStringIter()
|
||||
{
|
||||
return this->caretpos != 0;
|
||||
this->char_iter->SetString(this->buf);
|
||||
size_t pos = this->char_iter->SetCurPosition(this->caretpos);
|
||||
this->caretpos = pos == StringIterator::END ? 0 : (uint16)pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the caret to the left.
|
||||
* @pre Ensure that Textbuf::CanMoveCaretLeft returns true
|
||||
* @return The character under the caret.
|
||||
*/
|
||||
WChar Textbuf::MoveCaretLeft()
|
||||
/** Update pixel width of the text. */
|
||||
void Textbuf::UpdateWidth()
|
||||
{
|
||||
assert(this->CanMoveCaretLeft());
|
||||
|
||||
WChar c;
|
||||
const char *s = Utf8PrevChar(this->buf + this->caretpos);
|
||||
Utf8Decode(&c, s);
|
||||
this->caretpos = s - this->buf;
|
||||
this->caretxoffs -= GetCharacterWidth(FS_NORMAL, c);
|
||||
|
||||
return c;
|
||||
this->pixels = GetStringBoundingBox(this->buf, FS_NORMAL).width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if it is possible to move caret to the right
|
||||
* @return true if the caret can be moved to the right, otherwise false.
|
||||
*/
|
||||
bool Textbuf::CanMoveCaretRight()
|
||||
/** Update pixel position of the caret. */
|
||||
void Textbuf::UpdateCaretPosition()
|
||||
{
|
||||
return this->caretpos < this->bytes - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the caret to the right.
|
||||
* @pre Ensure that Textbuf::CanMoveCaretRight returns true
|
||||
* @return The character under the caret.
|
||||
*/
|
||||
WChar Textbuf::MoveCaretRight()
|
||||
{
|
||||
assert(this->CanMoveCaretRight());
|
||||
|
||||
WChar c;
|
||||
this->caretpos += (uint16)Utf8Decode(&c, this->buf + this->caretpos);
|
||||
this->caretxoffs += GetCharacterWidth(FS_NORMAL, c);
|
||||
|
||||
Utf8Decode(&c, this->buf + this->caretpos);
|
||||
return c;
|
||||
this->caretxoffs = this->chars > 1 ? GetCharPosInString(this->buf, this->buf + this->caretpos, FS_NORMAL).x : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle text navigation with arrow keys left/right.
|
||||
* This defines where the caret will blink and the next character interaction will occur
|
||||
* @param navmode Direction in which navigation occurs (WKC_CTRL |) WKC_LEFT, (WKC_CTRL |) WKC_RIGHT, WKC_END, WKC_HOME
|
||||
* @param keycode Direction in which navigation occurs (WKC_CTRL |) WKC_LEFT, (WKC_CTRL |) WKC_RIGHT, WKC_END, WKC_HOME
|
||||
* @return Return true on successful change of Textbuf, or false otherwise
|
||||
*/
|
||||
bool Textbuf::MovePos(int navmode)
|
||||
bool Textbuf::MovePos(uint16 keycode)
|
||||
{
|
||||
switch (navmode) {
|
||||
switch (keycode) {
|
||||
case WKC_LEFT:
|
||||
if (this->CanMoveCaretLeft()) {
|
||||
this->MoveCaretLeft();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case WKC_CTRL | WKC_LEFT: {
|
||||
if (!this->CanMoveCaretLeft()) break;
|
||||
if (this->caretpos == 0) break;
|
||||
|
||||
/* Unconditionally move one char to the left. */
|
||||
WChar c = this->MoveCaretLeft();
|
||||
/* Consume left whitespaces. */
|
||||
while (IsWhitespace(c)) {
|
||||
if (!this->CanMoveCaretLeft()) return true;
|
||||
c = this->MoveCaretLeft();
|
||||
}
|
||||
/* Consume left word. */
|
||||
while (!IsWhitespace(c)) {
|
||||
if (!this->CanMoveCaretLeft()) return true;
|
||||
c = this->MoveCaretLeft();
|
||||
}
|
||||
/* Place caret at the beginning of the left word. */
|
||||
this->MoveCaretRight();
|
||||
size_t pos = this->char_iter->Prev(keycode & WKC_CTRL ? StringIterator::ITER_WORD : StringIterator::ITER_CHARACTER);
|
||||
if (pos == StringIterator::END) return true;
|
||||
|
||||
this->caretpos = (uint16)pos;
|
||||
this->UpdateCaretPosition();
|
||||
return true;
|
||||
}
|
||||
|
||||
case WKC_RIGHT:
|
||||
if (this->CanMoveCaretRight()) {
|
||||
this->MoveCaretRight();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case WKC_CTRL | WKC_RIGHT: {
|
||||
if (!this->CanMoveCaretRight()) break;
|
||||
if (this->caretpos >= this->bytes - 1) break;
|
||||
|
||||
/* Unconditionally move one char to the right. */
|
||||
WChar c = this->MoveCaretRight();
|
||||
/* Continue to consume current word. */
|
||||
while (!IsWhitespace(c)) {
|
||||
if (!this->CanMoveCaretRight()) return true;
|
||||
c = this->MoveCaretRight();
|
||||
}
|
||||
/* Consume right whitespaces. */
|
||||
while (IsWhitespace(c)) {
|
||||
if (!this->CanMoveCaretRight()) return true;
|
||||
c = this->MoveCaretRight();
|
||||
}
|
||||
size_t pos = this->char_iter->Next(keycode & WKC_CTRL ? StringIterator::ITER_WORD : StringIterator::ITER_CHARACTER);
|
||||
if (pos == StringIterator::END) return true;
|
||||
|
||||
this->caretpos = (uint16)pos;
|
||||
this->UpdateCaretPosition();
|
||||
return true;
|
||||
}
|
||||
|
||||
case WKC_HOME:
|
||||
this->caretpos = 0;
|
||||
this->caretxoffs = 0;
|
||||
this->char_iter->SetCurPosition(this->caretpos);
|
||||
this->UpdateCaretPosition();
|
||||
return true;
|
||||
|
||||
case WKC_END:
|
||||
this->caretpos = this->bytes - 1;
|
||||
this->caretxoffs = this->pixels;
|
||||
this->char_iter->SetCurPosition(this->caretpos);
|
||||
this->UpdateCaretPosition();
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
@ -364,6 +272,8 @@ Textbuf::Textbuf(uint16 max_bytes, uint16 max_chars)
|
|||
assert(max_bytes != 0);
|
||||
assert(max_chars != 0);
|
||||
|
||||
this->char_iter = StringIterator::Create();
|
||||
|
||||
this->afilter = CS_ALPHANUMERAL;
|
||||
this->max_bytes = max_bytes;
|
||||
this->max_chars = max_chars == UINT16_MAX ? max_bytes : max_chars;
|
||||
|
@ -373,6 +283,7 @@ Textbuf::Textbuf(uint16 max_bytes, uint16 max_chars)
|
|||
|
||||
Textbuf::~Textbuf()
|
||||
{
|
||||
delete this->char_iter;
|
||||
free(this->buf);
|
||||
}
|
||||
|
||||
|
@ -418,21 +329,21 @@ void Textbuf::UpdateSize()
|
|||
{
|
||||
const char *buf = this->buf;
|
||||
|
||||
this->pixels = 0;
|
||||
this->chars = this->bytes = 1; // terminating zero
|
||||
|
||||
WChar c;
|
||||
while ((c = Utf8Consume(&buf)) != '\0') {
|
||||
this->pixels += GetCharacterWidth(FS_NORMAL, c);
|
||||
this->bytes += Utf8CharLen(c);
|
||||
this->chars++;
|
||||
}
|
||||
|
||||
assert(this->bytes <= this->max_bytes);
|
||||
assert(this->chars <= this->max_chars);
|
||||
|
||||
this->caretpos = this->bytes - 1;
|
||||
this->caretxoffs = this->pixels;
|
||||
this->UpdateStringIter();
|
||||
this->UpdateWidth();
|
||||
|
||||
this->UpdateCaretPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -450,3 +361,49 @@ bool Textbuf::HandleCaret()
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
HandleKeyPressResult Textbuf::HandleKeyPress(uint16 key, uint16 keycode)
|
||||
{
|
||||
bool edited = false;
|
||||
|
||||
switch (keycode) {
|
||||
case WKC_ESC: return HKPR_CANCEL;
|
||||
|
||||
case WKC_RETURN: case WKC_NUM_ENTER: return HKPR_CONFIRM;
|
||||
|
||||
#ifdef WITH_COCOA
|
||||
case (WKC_META | 'V'):
|
||||
#endif
|
||||
case (WKC_CTRL | 'V'):
|
||||
edited = this->InsertClipboard();
|
||||
break;
|
||||
|
||||
#ifdef WITH_COCOA
|
||||
case (WKC_META | 'U'):
|
||||
#endif
|
||||
case (WKC_CTRL | 'U'):
|
||||
this->DeleteAll();
|
||||
edited = true;
|
||||
break;
|
||||
|
||||
case WKC_BACKSPACE: case WKC_DELETE:
|
||||
case WKC_CTRL | WKC_BACKSPACE: case WKC_CTRL | WKC_DELETE:
|
||||
edited = this->DeleteChar(keycode);
|
||||
break;
|
||||
|
||||
case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME:
|
||||
case WKC_CTRL | WKC_LEFT: case WKC_CTRL | WKC_RIGHT:
|
||||
this->MovePos(keycode);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (IsValidChar(key, this->afilter)) {
|
||||
edited = this->InsertChar(key);
|
||||
} else {
|
||||
return HKPR_NOT_HANDLED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return edited ? HKPR_EDITING : HKPR_CURSOR;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,19 @@
|
|||
|
||||
#include "string_type.h"
|
||||
#include "strings_type.h"
|
||||
#include "string_base.h"
|
||||
|
||||
/**
|
||||
* Return values for Textbuf::HandleKeypress
|
||||
*/
|
||||
enum HandleKeyPressResult
|
||||
{
|
||||
HKPR_EDITING, ///< Textbuf content changed.
|
||||
HKPR_CURSOR, ///< Non-text change, e.g. cursor position.
|
||||
HKPR_CONFIRM, ///< Return or enter key pressed.
|
||||
HKPR_CANCEL, ///< Escape key pressed.
|
||||
HKPR_NOT_HANDLED, ///< Key does not affect editboxes.
|
||||
};
|
||||
|
||||
/** Helper/buffer for input fields. */
|
||||
struct Textbuf {
|
||||
|
@ -36,23 +49,26 @@ struct Textbuf {
|
|||
void CDECL Print(const char *format, ...) WARN_FORMAT(2, 3);
|
||||
|
||||
void DeleteAll();
|
||||
bool DeleteChar(int delmode);
|
||||
bool InsertChar(uint32 key);
|
||||
bool InsertClipboard();
|
||||
bool MovePos(int navmode);
|
||||
|
||||
bool InsertChar(uint32 key);
|
||||
|
||||
bool DeleteChar(uint16 keycode);
|
||||
bool MovePos(uint16 keycode);
|
||||
|
||||
HandleKeyPressResult HandleKeyPress(uint16 key, uint16 keycode);
|
||||
|
||||
bool HandleCaret();
|
||||
void UpdateSize();
|
||||
|
||||
private:
|
||||
bool CanDelChar(bool backspace);
|
||||
WChar GetNextDelChar(bool backspace);
|
||||
void DelChar(bool backspace);
|
||||
bool CanMoveCaretLeft();
|
||||
WChar MoveCaretLeft();
|
||||
bool CanMoveCaretRight();
|
||||
WChar MoveCaretRight();
|
||||
StringIterator *char_iter;
|
||||
|
||||
bool CanDelChar(bool backspace);
|
||||
|
||||
void UpdateStringIter();
|
||||
void UpdateWidth();
|
||||
void UpdateCaretPosition();
|
||||
};
|
||||
|
||||
#endif /* TEXTBUF_TYPE_H */
|
||||
|
|
|
@ -253,6 +253,7 @@ uint QZ_ListModes(OTTD_Point *modes, uint max_modes, CGDirectDisplayID display_i
|
|||
- (void)setDriver:(CocoaSubdriver*)drv;
|
||||
|
||||
- (BOOL)windowShouldClose:(id)sender;
|
||||
- (void)windowDidEnterFullScreen:(NSNotification *)aNotification;
|
||||
@end
|
||||
|
||||
|
||||
|
|
|
@ -776,6 +776,14 @@ void cocoaReleaseAutoreleasePool()
|
|||
{
|
||||
driver->active = false;
|
||||
}
|
||||
/** Window entered fullscreen mode (10.7). */
|
||||
- (void)windowDidEnterFullScreen:(NSNotification *)aNotification
|
||||
{
|
||||
NSPoint loc = [ driver->cocoaview convertPoint:[ [ aNotification object ] mouseLocationOutsideOfEventStream ] fromView:nil ];
|
||||
BOOL inside = ([ driver->cocoaview hitTest:loc ] == driver->cocoaview);
|
||||
|
||||
if (inside) [ driver->cocoaview mouseEntered:NULL ];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -290,16 +290,15 @@ bool WindowQuartzSubdriver::SetVideoMode(int width, int height, int bpp)
|
|||
const int NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7;
|
||||
const int NSWindowFullScreenButton = 7;
|
||||
|
||||
NSWindowCollectionBehavior behavior = [this->window collectionBehavior];
|
||||
NSWindowCollectionBehavior behavior = [ this->window collectionBehavior ];
|
||||
behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
|
||||
[window setCollectionBehavior:behavior];
|
||||
[ this->window setCollectionBehavior:behavior ];
|
||||
|
||||
NSButton* fullscreenButton =
|
||||
[this->window standardWindowButton:NSWindowFullScreenButton];
|
||||
[fullscreenButton setAction:@selector(toggleFullScreen:)];
|
||||
[fullscreenButton setTarget:this->window];
|
||||
NSButton* fullscreenButton = [ this->window standardWindowButton:NSWindowFullScreenButton ];
|
||||
[ fullscreenButton setAction:@selector(toggleFullScreen:) ];
|
||||
[ fullscreenButton setTarget:this->window ];
|
||||
|
||||
[this->window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary];
|
||||
[ this->window setCollectionBehavior: NSWindowCollectionBehaviorFullScreenPrimary ];
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -2250,26 +2250,24 @@ static bool MaybeBringWindowToFront(Window *w)
|
|||
*/
|
||||
EventState Window::HandleEditBoxKey(int wid, uint16 key, uint16 keycode)
|
||||
{
|
||||
EventState state = ES_NOT_HANDLED;
|
||||
|
||||
QueryString *query = this->GetQueryString(wid);
|
||||
if (query == NULL) return state;
|
||||
if (query == NULL) return ES_NOT_HANDLED;
|
||||
|
||||
int action = QueryString::ACTION_NOTHING;
|
||||
|
||||
switch (query->HandleEditBoxKey(this, wid, key, keycode, state)) {
|
||||
case HEBR_EDITING:
|
||||
switch (query->text.HandleKeyPress(key, keycode)) {
|
||||
case HKPR_EDITING:
|
||||
this->SetWidgetDirty(wid);
|
||||
this->OnEditboxChanged(wid);
|
||||
break;
|
||||
|
||||
case HEBR_CURSOR:
|
||||
case HKPR_CURSOR:
|
||||
this->SetWidgetDirty(wid);
|
||||
/* For the OSK also invalidate the parent window */
|
||||
if (this->window_class == WC_OSK) this->InvalidateData();
|
||||
break;
|
||||
|
||||
case HEBR_CONFIRM:
|
||||
case HKPR_CONFIRM:
|
||||
if (this->window_class == WC_OSK) {
|
||||
this->OnClick(Point(), WID_OSK_OK, 1);
|
||||
} else if (query->ok_button >= 0) {
|
||||
|
@ -2279,7 +2277,7 @@ EventState Window::HandleEditBoxKey(int wid, uint16 key, uint16 keycode)
|
|||
}
|
||||
break;
|
||||
|
||||
case HEBR_CANCEL:
|
||||
case HKPR_CANCEL:
|
||||
if (this->window_class == WC_OSK) {
|
||||
this->OnClick(Point(), WID_OSK_CANCEL, 1);
|
||||
} else if (query->cancel_button >= 0) {
|
||||
|
@ -2289,6 +2287,9 @@ EventState Window::HandleEditBoxKey(int wid, uint16 key, uint16 keycode)
|
|||
}
|
||||
break;
|
||||
|
||||
case HKPR_NOT_HANDLED:
|
||||
return ES_NOT_HANDLED;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
@ -2307,7 +2308,7 @@ EventState Window::HandleEditBoxKey(int wid, uint16 key, uint16 keycode)
|
|||
break;
|
||||
}
|
||||
|
||||
return state;
|
||||
return ES_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue