(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:
rubidium 2013-11-13 21:28:21 +00:00
parent 38bcb67c38
commit 6b47e4ba24
23 changed files with 682 additions and 334 deletions

View File

@ -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" />

View File

@ -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>

View File

@ -1566,6 +1566,10 @@
RelativePath=".\..\src\strgen\strgen.h"
>
</File>
<File
RelativePath=".\..\src\string_base.h"
>
</File>
<File
RelativePath=".\..\src\string_func.h"
>

View File

@ -1563,6 +1563,10 @@
RelativePath=".\..\src\strgen\strgen.h"
>
</File>
<File
RelativePath=".\..\src\string_base.h"
>
</File>
<File
RelativePath=".\..\src\string_func.h"
>

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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);

View File

@ -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.
*/

View File

@ -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();

View File

@ -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()) {

View File

@ -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);

View File

@ -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>

View File

@ -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);

View File

@ -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

66
src/string_base.h Normal file
View File

@ -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 */

View File

@ -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.

View File

@ -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;
}

View File

@ -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 */

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}
/**