diff --git a/config.lib b/config.lib
index 6551e25884..fe74ae0fec 100644
--- a/config.lib
+++ b/config.lib
@@ -1498,7 +1498,7 @@ make_cflags_and_ldflags() {
LDFLAGS="$LDFLAGS -Wl,--subsystem,windows"
fi
- LIBS="$LIBS -lws2_32 -lwinmm -lgdi32 -ldxguid -lole32"
+ LIBS="$LIBS -lws2_32 -lwinmm -lgdi32 -ldxguid -lole32 -limm32"
if [ $cc_version -ge 44 ]; then
LDFLAGS_BUILD="$LDFLAGS_BUILD -static-libgcc -static-libstdc++"
diff --git a/projects/openttd_vs100.vcxproj b/projects/openttd_vs100.vcxproj
index dcef8274fc..f5d4fe9777 100644
--- a/projects/openttd_vs100.vcxproj
+++ b/projects/openttd_vs100.vcxproj
@@ -131,7 +131,7 @@
0x0809
- winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
+ winmm.lib;ws2_32.lib;imm32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
true
%(IgnoreSpecificDefaultLibraries)
true
@@ -176,7 +176,7 @@
0x0809
- winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
+ winmm.lib;ws2_32.lib;imm32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
true
LIBCMT.lib;%(IgnoreSpecificDefaultLibraries)
true
@@ -233,7 +233,7 @@
0x0809
- winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
+ winmm.lib;ws2_32.lib;imm32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
true
%(IgnoreSpecificDefaultLibraries)
true
@@ -280,7 +280,7 @@
0x0809
- winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
+ winmm.lib;ws2_32.lib;imm32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
true
LIBCMT.lib;%(IgnoreSpecificDefaultLibraries)
true
diff --git a/projects/openttd_vs100.vcxproj.in b/projects/openttd_vs100.vcxproj.in
index 46b934c98f..ce522dad8b 100644
--- a/projects/openttd_vs100.vcxproj.in
+++ b/projects/openttd_vs100.vcxproj.in
@@ -131,7 +131,7 @@
0x0809
- winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
+ winmm.lib;ws2_32.lib;imm32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
true
%(IgnoreSpecificDefaultLibraries)
true
@@ -176,7 +176,7 @@
0x0809
- winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
+ winmm.lib;ws2_32.lib;imm32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
true
LIBCMT.lib;%(IgnoreSpecificDefaultLibraries)
true
@@ -233,7 +233,7 @@
0x0809
- winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
+ winmm.lib;ws2_32.lib;imm32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
true
%(IgnoreSpecificDefaultLibraries)
true
@@ -280,7 +280,7 @@
0x0809
- winmm.lib;ws2_32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
+ winmm.lib;ws2_32.lib;imm32.lib;libpng.lib;zlibstat.lib;lzo2.lib;liblzma.lib;libfreetype2.lib;icuuc.lib;icuin.lib;icudt.lib;icule.lib;iculx.lib;%(AdditionalDependencies)
true
LIBCMT.lib;%(IgnoreSpecificDefaultLibraries)
true
diff --git a/projects/openttd_vs80.vcproj b/projects/openttd_vs80.vcproj
index 0271ee5e22..7140db3446 100644
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -87,7 +87,7 @@
/>
EditBoxLostFocus();
}
/**
@@ -215,6 +217,9 @@ struct IConsoleWindow : Window
delta = 0;
}
+ /* If we have a marked area, draw a background highlight. */
+ if (_iconsole_cmdline.marklength != 0) GfxFillRect(this->line_offset + delta + _iconsole_cmdline.markxoffs, this->height - this->line_height, this->line_offset + delta + _iconsole_cmdline.markxoffs + _iconsole_cmdline.marklength, this->height - 1, PC_DARK_RED);
+
DrawString(this->line_offset + delta, right, this->height - this->line_height, _iconsole_cmdline.buf, (TextColour)CC_COMMAND, SA_LEFT | SA_FORCE);
if (_focused_window == this && _iconsole_cmdline.caret) {
@@ -307,10 +312,70 @@ struct IConsoleWindow : Window
return ES_HANDLED;
}
+ virtual void InsertTextString(int wid, const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end)
+ {
+ if (_iconsole_cmdline.InsertString(str, marked, caret, insert_location, replacement_end)) {
+ IConsoleWindow::scroll = 0;
+ IConsoleResetHistoryPos();
+ this->SetDirty();
+ }
+ }
+
+ virtual const char *GetFocusedText() const
+ {
+ return _iconsole_cmdline.buf;
+ }
+
+ virtual const char *GetCaret() const
+ {
+ return _iconsole_cmdline.buf + _iconsole_cmdline.caretpos;
+ }
+
+ virtual const char *GetMarkedText(size_t *length) const
+ {
+ if (_iconsole_cmdline.markend == 0) return NULL;
+
+ *length = _iconsole_cmdline.markend - _iconsole_cmdline.markpos;
+ return _iconsole_cmdline.buf + _iconsole_cmdline.markpos;
+ }
+
+ virtual Point GetCaretPosition() const
+ {
+ int delta = min(this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH, 0);
+ Point pt = {this->line_offset + delta + _iconsole_cmdline.caretxoffs, this->height - this->line_height};
+
+ return pt;
+ }
+
+ virtual Rect GetTextBoundingRect(const char *from, const char *to) const
+ {
+ int delta = min(this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH, 0);
+
+ Point p1 = GetCharPosInString(_iconsole_cmdline.buf, from, FS_NORMAL);
+ Point p2 = from != to ? GetCharPosInString(_iconsole_cmdline.buf, from) : p1;
+
+ Rect r = {this->line_offset + delta + p1.x, this->height - this->line_height, this->line_offset + delta + p2.x, this->height};
+ return r;
+ }
+
+ virtual const char *GetTextCharacterAtPosition(const Point &pt) const
+ {
+ int delta = min(this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH, 0);
+
+ if (!IsInsideMM(pt.y, this->height - this->line_height, this->height)) return NULL;
+
+ return GetCharAtPosition(_iconsole_cmdline.buf, pt.x - delta);
+ }
+
virtual void OnMouseWheel(int wheel)
{
this->Scroll(-wheel);
}
+
+ virtual void OnFocusLost()
+ {
+ _video_driver->EditBoxLostFocus();
+ }
};
int IConsoleWindow::scroll = 0;
diff --git a/src/core/bitmath_func.hpp b/src/core/bitmath_func.hpp
index 5838dd11be..31e679b005 100644
--- a/src/core/bitmath_func.hpp
+++ b/src/core/bitmath_func.hpp
@@ -365,8 +365,8 @@ static inline T ROR(const T x, const uint8 n)
* (since it will use hardware swapping if available).
* Even though they should return uint16 and uint32, we get
* warnings if we don't cast those (why?) */
- #define BSWAP32(x) ((uint32)Endian32_Swap(x))
- #define BSWAP16(x) ((uint16)Endian16_Swap(x))
+ #define BSWAP32(x) ((uint32)CFSwapInt32(x))
+ #define BSWAP16(x) ((uint16)CFSwapInt16(x))
#elif defined(_MSC_VER)
/* MSVC has intrinsics for swapping, resulting in faster code */
#define BSWAP32(x) (_byteswap_ulong(x))
diff --git a/src/gfx.cpp b/src/gfx.cpp
index 114f852ef3..37b64ba1f3 100644
--- a/src/gfx.cpp
+++ b/src/gfx.cpp
@@ -653,6 +653,21 @@ Point GetCharPosInString(const char *str, const char *ch, FontSize start_fontsiz
return layout.GetCharPosition(ch);
}
+/**
+ * Get the character from a string that is drawn at a specific position.
+ * @param str String to test.
+ * @param x Position relative to the start of the string.
+ * @param start_fontsize Font size to start the text with.
+ * @return Pointer to the character at the position or NULL if there is no character at the position.
+ */
+const char *GetCharAtPosition(const char *str, int x, FontSize start_fontsize)
+{
+ if (x < 0) return NULL;
+
+ Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
+ return layout.GetCharAtPosition(x);
+}
+
/**
* Draw single character horizontally centered around (x,y)
* @param c Character (glyph) to draw
diff --git a/src/gfx_func.h b/src/gfx_func.h
index 1e121b4c30..aa18f3f707 100644
--- a/src/gfx_func.h
+++ b/src/gfx_func.h
@@ -71,6 +71,7 @@ extern Dimension _cur_resolution;
extern Palette _cur_palette; ///< Current palette
void HandleKeypress(uint keycode, WChar key);
+void HandleTextInput(const char *str, bool marked = false, const char *caret = NULL, const char *insert_location = NULL, const char *replacement_end = NULL);
void HandleCtrlChanged();
void HandleMouseEvents();
void CSleep(int milliseconds);
@@ -128,6 +129,7 @@ Dimension GetStringMultiLineBoundingBox(StringID str, const Dimension &suggestio
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);
+const char *GetCharAtPosition(const char *str, int x, FontSize start_fontsize = FS_NORMAL);
void DrawDirtyBlocks();
void SetDirtyBlocks(int left, int top, int right, int bottom);
diff --git a/src/gfx_layout.cpp b/src/gfx_layout.cpp
index a4fc6969f8..cf2fe6da28 100644
--- a/src/gfx_layout.cpp
+++ b/src/gfx_layout.cpp
@@ -735,6 +735,50 @@ Point Layouter::GetCharPosition(const char *ch) const
return p;
}
+/**
+ * Get the character that is at a position.
+ * @param x Position in the string.
+ * @return Pointer to the character at the position or NULL if no character is at the position.
+ */
+const char *Layouter::GetCharAtPosition(int x) const
+{
+ const ParagraphLayouter::Line *line = *this->Begin();;
+
+ for (int run_index = 0; run_index < line->CountRuns(); run_index++) {
+ const ParagraphLayouter::VisualRun *run = line->GetVisualRun(run_index);
+
+ for (int i = 0; i < run->GetGlyphCount(); i++) {
+ /* Not a valid glyph (empty). */
+ if (run->GetGlyphs()[i] == 0xFFFF) continue;
+
+ int begin_x = (int)run->GetPositions()[i * 2];
+ int end_x = (int)run->GetPositions()[i * 2 + 2];
+
+ if (IsInsideMM(x, begin_x, end_x)) {
+ /* Found our glyph, now convert to UTF-8 string index. */
+ size_t index = run->GetGlyphToCharMap()[i];
+
+ size_t cur_idx = 0;
+ for (const char *str = this->string; *str != '\0'; ) {
+ if (cur_idx == index) return str;
+
+ WChar c;
+ size_t len = Utf8Decode(&c, str);
+#ifdef WITH_ICU
+ /* ICU uses UTF-16 internally which means we need to account for surrogate pairs. */
+ cur_idx += len < 4 ? 1 : 2;
+#else
+ cur_idx++;
+#endif
+ str += len;
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
/**
* Get a static font instance.
*/
diff --git a/src/gfx_layout.h b/src/gfx_layout.h
index 4a30860cc7..4d85cb5a22 100644
--- a/src/gfx_layout.h
+++ b/src/gfx_layout.h
@@ -179,6 +179,7 @@ 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;
+ const char *GetCharAtPosition(int x) const;
static void ResetFontCache(FontSize size);
static void ResetLineCache();
diff --git a/src/industry_gui.cpp b/src/industry_gui.cpp
index a38bbbcb9f..0b8ae32eaa 100644
--- a/src/industry_gui.cpp
+++ b/src/industry_gui.cpp
@@ -2588,7 +2588,7 @@ struct IndustryCargoesWindow : public Window {
delete lst;
break;
}
- int selected = (this->ind_cargo >= NUM_INDUSTRYTYPES) ? this->ind_cargo - NUM_INDUSTRYTYPES : -1;
+ int selected = (this->ind_cargo >= NUM_INDUSTRYTYPES) ? (int)(this->ind_cargo - NUM_INDUSTRYTYPES) : -1;
ShowDropDownList(this, lst, selected, WID_IC_CARGO_DROPDOWN, 0, true);
break;
}
@@ -2605,7 +2605,7 @@ struct IndustryCargoesWindow : public Window {
delete lst;
break;
}
- int selected = (this->ind_cargo < NUM_INDUSTRYTYPES) ? this->ind_cargo : -1;
+ int selected = (this->ind_cargo < NUM_INDUSTRYTYPES) ? (int)this->ind_cargo : -1;
ShowDropDownList(this, lst, selected, WID_IC_IND_DROPDOWN, 0, true);
break;
}
diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp
index 5c7454a741..2e72acbc7b 100644
--- a/src/misc_gui.cpp
+++ b/src/misc_gui.cpp
@@ -764,6 +764,9 @@ void QueryString::DrawEditBox(const Window *w, int wid) const
if (tb->caretxoffs + delta < 0) delta = -tb->caretxoffs;
+ /* If we have a marked area, draw a background highlight. */
+ if (tb->marklength != 0) GfxFillRect(delta + tb->markxoffs, 0, delta + tb->markxoffs + tb->marklength - 1, bottom - top, PC_GREY);
+
DrawString(delta, tb->pixels, 0, tb->buf, TC_YELLOW);
bool focussed = w->IsWidgetGloballyFocused(wid) || IsOSKOpenedFor(w, wid);
if (focussed && tb->caret) {
@@ -774,6 +777,105 @@ void QueryString::DrawEditBox(const Window *w, int wid) const
_cur_dpi = old_dpi;
}
+/**
+ * Get the current caret position.
+ * @param w Window the edit box is in.
+ * @param wid Widget index.
+ * @return Top-left location of the caret, relative to the window.
+ */
+Point QueryString::GetCaretPosition(const Window *w, int wid) const
+{
+ const NWidgetLeaf *wi = w->GetWidget(wid);
+
+ assert((wi->type & WWT_MASK) == WWT_EDITBOX);
+
+ bool rtl = _current_text_dir == TD_RTL;
+ Dimension sprite_size = GetSpriteSize(rtl ? SPR_IMG_DELETE_RIGHT : SPR_IMG_DELETE_LEFT);
+ int clearbtn_width = sprite_size.width + WD_IMGBTN_LEFT + WD_IMGBTN_RIGHT;
+
+ int left = wi->pos_x + (rtl ? clearbtn_width : 0);
+ int right = wi->pos_x + (rtl ? wi->current_x : wi->current_x - clearbtn_width) - 1;
+
+ /* Clamp caret position to be inside out current width. */
+ const Textbuf *tb = &this->text;
+ int delta = min(0, (right - left) - tb->pixels - 10);
+ if (tb->caretxoffs + delta < 0) delta = -tb->caretxoffs;
+
+ Point pt = {left + WD_FRAMERECT_LEFT + tb->caretxoffs + delta, wi->pos_y + WD_FRAMERECT_TOP};
+ return pt;
+}
+
+/**
+ * Get the bounding rectangle for a range of the query string.
+ * @param w Window the edit box is in.
+ * @param wid Widget index.
+ * @param from Start of the string range.
+ * @param to End of the string range.
+ * @return Rectangle encompassing the string range, relative to the window.
+ */
+Rect QueryString::GetBoundingRect(const Window *w, int wid, const char *from, const char *to) const
+{
+ const NWidgetLeaf *wi = w->GetWidget(wid);
+
+ assert((wi->type & WWT_MASK) == WWT_EDITBOX);
+
+ bool rtl = _current_text_dir == TD_RTL;
+ Dimension sprite_size = GetSpriteSize(rtl ? SPR_IMG_DELETE_RIGHT : SPR_IMG_DELETE_LEFT);
+ int clearbtn_width = sprite_size.width + WD_IMGBTN_LEFT + WD_IMGBTN_RIGHT;
+
+ int left = wi->pos_x + (rtl ? clearbtn_width : 0);
+ int right = wi->pos_x + (rtl ? wi->current_x : wi->current_x - clearbtn_width) - 1;
+
+ int top = wi->pos_y + WD_FRAMERECT_TOP;
+ int bottom = wi->pos_y + wi->current_y - 1 - WD_FRAMERECT_BOTTOM;
+
+ /* Clamp caret position to be inside our current width. */
+ const Textbuf *tb = &this->text;
+ int delta = min(0, (right - left) - tb->pixels - 10);
+ if (tb->caretxoffs + delta < 0) delta = -tb->caretxoffs;
+
+ /* Get location of first and last character. */
+ Point p1 = GetCharPosInString(tb->buf, from, FS_NORMAL);
+ Point p2 = from != to ? GetCharPosInString(tb->buf, to, FS_NORMAL) : p1;
+
+ Rect r = { Clamp(left + p1.x + delta + WD_FRAMERECT_LEFT, left, right), top, Clamp(left + p2.x + delta + WD_FRAMERECT_LEFT, left, right - WD_FRAMERECT_RIGHT), bottom };
+
+ return r;
+}
+
+/**
+ * Get the character that is rendered at a position.
+ * @param w Window the edit box is in.
+ * @param wid Widget index.
+ * @param pt Position to test.
+ * @return Pointer to the character at the position or NULL if no character is at the position.
+ */
+const char *QueryString::GetCharAtPosition(const Window *w, int wid, const Point &pt) const
+{
+ const NWidgetLeaf *wi = w->GetWidget(wid);
+
+ assert((wi->type & WWT_MASK) == WWT_EDITBOX);
+
+ bool rtl = _current_text_dir == TD_RTL;
+ Dimension sprite_size = GetSpriteSize(rtl ? SPR_IMG_DELETE_RIGHT : SPR_IMG_DELETE_LEFT);
+ int clearbtn_width = sprite_size.width + WD_IMGBTN_LEFT + WD_IMGBTN_RIGHT;
+
+ int left = wi->pos_x + (rtl ? clearbtn_width : 0);
+ int right = wi->pos_x + (rtl ? wi->current_x : wi->current_x - clearbtn_width) - 1;
+
+ int top = wi->pos_y + WD_FRAMERECT_TOP;
+ int bottom = wi->pos_y + wi->current_y - 1 - WD_FRAMERECT_BOTTOM;
+
+ if (!IsInsideMM(pt.y, top, bottom)) return NULL;
+
+ /* Clamp caret position to be inside our current width. */
+ const Textbuf *tb = &this->text;
+ int delta = min(0, (right - left) - tb->pixels - 10);
+ if (tb->caretxoffs + delta < 0) delta = -tb->caretxoffs;
+
+ return ::GetCharAtPosition(tb->buf, pt.x - delta - left);
+}
+
void QueryString::ClickEditBox(Window *w, Point pt, int wid, int click_count, bool focus_changed)
{
const NWidgetLeaf *wi = w->GetWidget(wid);
diff --git a/src/os/macosx/osx_stdafx.h b/src/os/macosx/osx_stdafx.h
index 87cff83723..cd30f372ea 100644
--- a/src/os/macosx/osx_stdafx.h
+++ b/src/os/macosx/osx_stdafx.h
@@ -108,6 +108,14 @@ typedef unsigned int NSUInteger;
#endif /* __LP64__ */
#endif /* NSInteger */
+#ifndef CGFLOAT_DEFINED
+#if __LP64__
+typedef double CGFloat;
+#else
+typedef float CGFloat;
+#endif /* __LP64__ */
+#endif /* CGFLOAT_DEFINED */
+
/* OS X SDK versions >= 10.5 have a non-const iconv. */
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
# define HAVE_NON_CONST_ICONV
diff --git a/src/osk_gui.cpp b/src/osk_gui.cpp
index 015222d4dd..f32419401a 100644
--- a/src/osk_gui.cpp
+++ b/src/osk_gui.cpp
@@ -16,6 +16,7 @@
#include "window_func.h"
#include "gfx_func.h"
#include "querystring_gui.h"
+#include "video/video_driver.hpp"
#include "widgets/osk_widget.h"
@@ -205,6 +206,7 @@ struct OskWindow : public Window {
virtual void OnFocusLost()
{
+ _video_driver->EditBoxLostFocus();
delete this;
}
};
diff --git a/src/querystring_gui.h b/src/querystring_gui.h
index b2c91f3540..a1f3896dd1 100644
--- a/src/querystring_gui.h
+++ b/src/querystring_gui.h
@@ -53,6 +53,41 @@ 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);
+
+ Point GetCaretPosition(const Window *w, int wid) const;
+ Rect GetBoundingRect(const Window *w, int wid, const char *from, const char *to) const;
+ const char *GetCharAtPosition(const Window *w, int wid, const Point &pt) const;
+
+ /**
+ * Get the current text.
+ * @return Current text.
+ */
+ const char *GetText() const
+ {
+ return this->text.buf;
+ }
+
+ /**
+ * Get the position of the caret in the text buffer.
+ * @return Pointer to the caret in the text buffer.
+ */
+ const char *GetCaret() const
+ {
+ return this->text.buf + this->text.caretpos;
+ }
+
+ /**
+ * Get the currently marked text.
+ * @param[out] length Length of the marked text.
+ * @return Begining of the marked area or NULL if no text is marked.
+ */
+ const char *GetMarkedText(size_t *length) const
+ {
+ if (this->text.markend == 0) return NULL;
+
+ *length = this->text.markend - this->text.markpos;
+ return this->text.buf + this->text.markpos;
+ }
};
void ShowOnScreenKeyboard(Window *parent, int button);
diff --git a/src/sound/cocoa_s.cpp b/src/sound/cocoa_s.cpp
index 945c15da22..1dc2a25d4a 100644
--- a/src/sound/cocoa_s.cpp
+++ b/src/sound/cocoa_s.cpp
@@ -67,7 +67,7 @@ const char *SoundDriver_Cocoa::Start(const char * const *parm)
requestedDesc.mBytesPerFrame = requestedDesc.mBitsPerChannel * requestedDesc.mChannelsPerFrame / 8;
requestedDesc.mBytesPerPacket = requestedDesc.mBytesPerFrame * requestedDesc.mFramesPerPacket;
- MxInitialize(requestedDesc.mSampleRate);
+ MxInitialize((uint)requestedDesc.mSampleRate);
/* Locate the default output audio unit */
desc.componentType = kAudioUnitType_Output;
diff --git a/src/textbuf.cpp b/src/textbuf.cpp
index 58f931f8a5..766396fc66 100644
--- a/src/textbuf.cpp
+++ b/src/textbuf.cpp
@@ -103,6 +103,7 @@ bool Textbuf::DeleteChar(uint16 keycode)
this->UpdateStringIter();
this->UpdateWidth();
this->UpdateCaretPosition();
+ this->UpdateMarkedText();
return true;
}
@@ -115,6 +116,7 @@ void Textbuf::DeleteAll()
memset(this->buf, 0, this->max_bytes);
this->bytes = this->chars = 1;
this->pixels = this->caretpos = this->caretxoffs = 0;
+ this->markpos = this->markend = this->markxoffs = this->marklength = 0;
this->UpdateStringIter();
}
@@ -138,11 +140,80 @@ bool Textbuf::InsertChar(WChar key)
this->UpdateStringIter();
this->UpdateWidth();
this->UpdateCaretPosition();
+ this->UpdateMarkedText();
return true;
}
return false;
}
+/**
+ * Insert a string into the text buffer. If maxwidth of the Textbuf is zero,
+ * we don't care about the visual-length but only about the physical
+ * length of the string.
+ * @param str String to insert.
+ * @param marked Replace the currently marked text with the new text.
+ * @param caret Move the caret to this point in the insertion string.
+ * @param insert_location Position at which to insert the string.
+ * @param replacement_end Replace all characters from #insert_location up to this location with the new string.
+ * @return True on successful change of Textbuf, or false otherwise.
+ */
+bool Textbuf::InsertString(const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end)
+{
+ uint16 insertpos = (marked && this->marklength != 0) ? this->markpos : this->caretpos;
+ if (insert_location != NULL) {
+ insertpos = insert_location - this->buf;
+ if (insertpos > this->bytes) return false;
+
+ if (replacement_end != NULL) {
+ this->DeleteText(insertpos, replacement_end - this->buf, str == NULL);
+ }
+ } else {
+ if (marked) this->DiscardMarkedText(str == NULL);
+ }
+
+ if (str == NULL) return false;
+
+ uint16 bytes = 0, chars = 0;
+ WChar c;
+ for (const char *ptr = str; (c = Utf8Consume(&ptr)) != '\0';) {
+ if (!IsValidChar(c, this->afilter)) break;
+
+ byte len = Utf8CharLen(c);
+ if (this->bytes + bytes + len > this->max_bytes) break;
+ if (this->chars + chars + 1 > this->max_chars) break;
+
+ bytes += len;
+ chars++;
+
+ /* Move caret if needed. */
+ if (ptr == caret) this->caretpos = insertpos + bytes;
+ }
+
+ if (bytes == 0) return false;
+
+ if (marked) {
+ this->markpos = insertpos;
+ this->markend = insertpos + bytes;
+ }
+
+ memmove(this->buf + insertpos + bytes, this->buf + insertpos, this->bytes - insertpos);
+ memcpy(this->buf + insertpos, str, bytes);
+
+ this->bytes += bytes;
+ this->chars += chars;
+ if (!marked && caret == NULL) this->caretpos += bytes;
+ assert(this->bytes <= this->max_bytes);
+ assert(this->chars <= this->max_chars);
+ this->buf[this->bytes - 1] = '\0'; // terminating zero
+
+ this->UpdateStringIter();
+ this->UpdateWidth();
+ this->UpdateCaretPosition();
+ this->UpdateMarkedText();
+
+ return true;
+}
+
/**
* Insert a chunk of text from the clipboard onto the textbuffer. Get TEXT clipboard
* and append this up to the maximum length (either absolute or screenlength). If maxlength
@@ -155,36 +226,55 @@ bool Textbuf::InsertClipboard()
if (!GetClipboardContents(utf8_buf, lengthof(utf8_buf))) return false;
- uint16 bytes = 0, chars = 0;
- WChar c;
- for (const char *ptr = utf8_buf; (c = Utf8Consume(&ptr)) != '\0';) {
- if (!IsValidChar(c, this->afilter)) break;
+ return this->InsertString(utf8_buf, false);
+}
- byte len = Utf8CharLen(c);
- if (this->bytes + bytes + len > this->max_bytes) break;
- if (this->chars + chars + 1 > this->max_chars) break;
-
- bytes += len;
- chars++;
+/**
+ * Delete a part of the text.
+ * @param from Start of the text to delete.
+ * @param to End of the text to delete.
+ * @param update Set to true if the internal state should be updated.
+ */
+void Textbuf::DeleteText(uint16 from, uint16 to, bool update)
+{
+ uint c = 0;
+ const char *s = this->buf + from;
+ while (s < this->buf + to) {
+ Utf8Consume(&s);
+ c++;
}
- if (bytes == 0) return false;
+ /* Strip marked characters from buffer. */
+ memmove(this->buf + from, this->buf + to, this->bytes - to);
+ this->bytes -= to - from;
+ this->chars -= c;
- memmove(this->buf + this->caretpos + bytes, this->buf + this->caretpos, this->bytes - this->caretpos);
- memcpy(this->buf + this->caretpos, utf8_buf, bytes);
+ /* Fixup caret if needed. */
+ if (this->caretpos > from) {
+ if (this->caretpos <= to) {
+ this->caretpos = from;
+ } else {
+ this->caretpos -= to - from;
+ }
+ }
- this->bytes += bytes;
- this->chars += chars;
- this->caretpos += bytes;
- assert(this->bytes <= this->max_bytes);
- assert(this->chars <= this->max_chars);
- this->buf[this->bytes - 1] = '\0'; // terminating zero
+ if (update) {
+ this->UpdateStringIter();
+ this->UpdateCaretPosition();
+ this->UpdateMarkedText();
+ }
+}
- this->UpdateStringIter();
- this->UpdateWidth();
- this->UpdateCaretPosition();
+/**
+ * Discard any marked text.
+ * @param update Set to true if the internal state should be updated.
+ */
+void Textbuf::DiscardMarkedText(bool update)
+{
+ if (this->markend == 0) return;
- return true;
+ this->DeleteText(this->markpos, this->markend, update);
+ this->markpos = this->markend = this->markxoffs = this->marklength = 0;
}
/** Update the character iter after the text has changed. */
@@ -207,6 +297,17 @@ void Textbuf::UpdateCaretPosition()
this->caretxoffs = this->chars > 1 ? GetCharPosInString(this->buf, this->buf + this->caretpos, FS_NORMAL).x : 0;
}
+/** Update pixel positions of the marked text area. */
+void Textbuf::UpdateMarkedText()
+{
+ if (this->markend != 0) {
+ this->markxoffs = GetCharPosInString(this->buf, this->buf + this->markpos, FS_NORMAL).x;
+ this->marklength = GetCharPosInString(this->buf, this->buf + this->markend, FS_NORMAL).x - this->markxoffs;
+ } else {
+ this->markxoffs = this->marklength = 0;
+ }
+}
+
/**
* Handle text navigation with arrow keys left/right.
* This defines where the caret will blink and the next character interaction will occur
@@ -342,6 +443,7 @@ void Textbuf::UpdateSize()
this->caretpos = this->bytes - 1;
this->UpdateStringIter();
this->UpdateWidth();
+ this->UpdateMarkedText();
this->UpdateCaretPosition();
}
diff --git a/src/textbuf_type.h b/src/textbuf_type.h
index 5976159909..1d927b72d9 100644
--- a/src/textbuf_type.h
+++ b/src/textbuf_type.h
@@ -40,6 +40,10 @@ struct Textbuf {
bool caret; ///< is the caret ("_") visible or not
uint16 caretpos; ///< the current position of the caret in the buffer, in bytes
uint16 caretxoffs; ///< the current position of the caret in pixels
+ uint16 markpos; ///< the start position of the marked area in the buffer, in bytes
+ uint16 markend; ///< the end position of the marked area in the buffer, in bytes
+ uint16 markxoffs; ///< the start position of the marked area in pixels
+ uint16 marklength; ///< the length of the marked area in pixels
explicit Textbuf(uint16 max_bytes, uint16 max_chars = UINT16_MAX);
~Textbuf();
@@ -52,6 +56,7 @@ struct Textbuf {
bool InsertClipboard();
bool InsertChar(uint32 key);
+ bool InsertString(const char *str, bool marked, const char *caret = NULL, const char *insert_location = NULL, const char *replacement_end = NULL);
bool DeleteChar(uint16 keycode);
bool MovePos(uint16 keycode);
@@ -61,14 +66,19 @@ struct Textbuf {
bool HandleCaret();
void UpdateSize();
+ void DiscardMarkedText(bool update = true);
+
private:
StringIterator *char_iter;
bool CanDelChar(bool backspace);
+ void DeleteText(uint16 from, uint16 to, bool update);
+
void UpdateStringIter();
void UpdateWidth();
void UpdateCaretPosition();
+ void UpdateMarkedText();
};
#endif /* TEXTBUF_TYPE_H */
diff --git a/src/video/cocoa/cocoa_v.h b/src/video/cocoa/cocoa_v.h
index 06bbbfdb2a..e70a33b152 100644
--- a/src/video/cocoa/cocoa_v.h
+++ b/src/video/cocoa/cocoa_v.h
@@ -50,6 +50,11 @@ public:
*/
/* virtual */ bool AfterBlitterChange();
+ /**
+ * An edit box lost the input focus. Abort character compositing if necessary.
+ */
+ /* virtual */ void EditBoxLostFocus();
+
/** Return driver name
* @return driver name
*/
@@ -227,7 +232,17 @@ uint QZ_ListModes(OTTD_Point *modes, uint max_modes, CGDirectDisplayID display_i
@end
/** Subclass of NSView to fix Quartz rendering and mouse awareness */
-@interface OTTD_CocoaView : NSView {
+@interface OTTD_CocoaView : NSView
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+# if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
+
+# else
+
+# endif /* MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4 */
+#else
+
+#endif /* MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 */
+{
CocoaSubdriver *driver;
NSTrackingRectTag trackingtag;
}
diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm
index f338ee3ffa..4ca27f06b1 100644
--- a/src/video/cocoa/cocoa_v.mm
+++ b/src/video/cocoa/cocoa_v.mm
@@ -34,6 +34,8 @@
#include "../../blitter/factory.hpp"
#include "../../fileio_func.h"
#include "../../gfx_func.h"
+#include "../../window_func.h"
+#include "../../window_gui.h"
#import /* for MAXPATHLEN */
@@ -57,7 +59,7 @@ static bool _cocoa_video_dialog = false;
CocoaSubdriver *_cocoa_subdriver = NULL;
-static const NSString *OTTDMainLaunchGameEngine = @"ottdmain_launch_game_engine";
+static NSString *OTTDMainLaunchGameEngine = @"ottdmain_launch_game_engine";
/**
@@ -394,21 +396,12 @@ static CocoaSubdriver *QZ_CreateSubdriver(int width, int height, int bpp, bool f
/* OSX 10.7 allows to toggle fullscreen mode differently */
if (MacOSVersionIsAtLeast(10, 7, 0)) {
ret = QZ_CreateWindowSubdriver(width, height, bpp);
- }
-#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9)
- else {
+ if (ret != NULL && fullscreen) ret->ToggleFullscreen();
+ } else {
ret = fullscreen ? QZ_CreateFullscreenSubdriver(width, height, bpp) : QZ_CreateWindowSubdriver(width, height, bpp);
}
-#endif /* (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9) */
-
- if (ret != NULL) {
- /* We cannot set any fullscreen mode on OSX 10.7 when not compiled against SDK 10.7 */
-#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
- if (fullscreen) { ret->ToggleFullscreen(); }
-#endif
- return ret;
- }
+ if (ret != NULL) return ret;
if (!fallback) return NULL;
/* Try again in 640x480 windowed */
@@ -572,6 +565,22 @@ bool VideoDriver_Cocoa::AfterBlitterChange()
return this->ChangeResolution(_screen.width, _screen.height);
}
+/**
+ * An edit box lost the input focus. Abort character compositing if necessary.
+ */
+void VideoDriver_Cocoa::EditBoxLostFocus()
+{
+ if (_cocoa_subdriver != NULL) {
+ if ([ _cocoa_subdriver->cocoaview respondsToSelector:@selector(inputContext) ] && [ [ _cocoa_subdriver->cocoaview performSelector:@selector(inputContext) ] respondsToSelector:@selector(discardMarkedText) ]) {
+ [ [ _cocoa_subdriver->cocoaview performSelector:@selector(inputContext) ] performSelector:@selector(discardMarkedText) ];
+ } else {
+ [ [ NSInputManager currentInputManager ] markedTextAbandoned:_cocoa_subdriver->cocoaview ];
+ }
+ }
+ /* Clear any marked string from the current edit box. */
+ HandleTextInput(NULL, true);
+}
+
/**
* Catch asserts prior to initialization of the videodriver.
*
@@ -757,6 +766,43 @@ void cocoaReleaseAutoreleasePool()
+/**
+ * Count the number of UTF-16 code points in a range of an UTF-8 string.
+ * @param from Start of the range.
+ * @param to End of the range.
+ * @return Number of UTF-16 code points in the range.
+ */
+static NSUInteger CountUtf16Units(const char *from, const char *to)
+{
+ NSUInteger i = 0;
+
+ while (from < to) {
+ WChar c;
+ size_t len = Utf8Decode(&c, from);
+ i += len < 4 ? 1 : 2; // Watch for surrogate pairs.
+ from += len;
+ }
+
+ return i;
+}
+
+/**
+ * Advance an UTF-8 string by a number of equivalent UTF-16 code points.
+ * @param str UTF-8 string.
+ * @param count Number of UTF-16 code points to advance the string by.
+ * @return Advanced string pointer.
+ */
+static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count)
+{
+ for (NSUInteger i = 0; i < count && *str != '\0'; ) {
+ WChar c;
+ size_t len = Utf8Decode(&c, str);
+ i += len < 4 ? 1 : 2; // Watch for surrogates.
+ str += len;
+ }
+
+ return str;
+}
@implementation OTTD_CocoaView
/**
@@ -852,6 +898,200 @@ void cocoaReleaseAutoreleasePool()
if (_cocoa_subdriver != NULL) UndrawMouseCursor();
_cursor.in_window = false;
}
+
+
+/** Insert the given text at the given range. */
+- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange
+{
+ if (!EditBoxInGlobalFocus()) return;
+
+ NSString *s = [ aString isKindOfClass:[ NSAttributedString class ] ] ? [ aString string ] : (NSString *)aString;
+
+ const char *insert_point = NULL;
+ const char *replace_range = NULL;
+ if (replacementRange.location != NSNotFound) {
+ /* Calculate the part to be replaced. */
+ insert_point = Utf8AdvanceByUtf16Units(_focused_window->GetFocusedText(), replacementRange.location);
+ replace_range = Utf8AdvanceByUtf16Units(insert_point, replacementRange.length);
+ }
+
+ HandleTextInput(NULL, true);
+ HandleTextInput([ s UTF8String ], false, NULL, insert_point, replace_range);
+}
+
+/** Insert the given text at the caret. */
+- (void)insertText:(id)aString
+{
+ [ self insertText:aString replacementRange:NSMakeRange(NSNotFound, 0) ];
+}
+
+/** Set a new marked text and reposition the caret. */
+- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange replacementRange:(NSRange)replacementRange
+{
+ if (!EditBoxInGlobalFocus()) return;
+
+ NSString *s = [ aString isKindOfClass:[ NSAttributedString class ] ] ? [ aString string ] : (NSString *)aString;
+
+ const char *utf8 = [ s UTF8String ];
+ if (utf8 != NULL) {
+ const char *insert_point = NULL;
+ const char *replace_range = NULL;
+ if (replacementRange.location != NSNotFound) {
+ /* Calculate the part to be replaced. */
+ NSRange marked = [ self markedRange ];
+ insert_point = Utf8AdvanceByUtf16Units(_focused_window->GetFocusedText(), replacementRange.location + (marked.location != NSNotFound ? marked.location : 0u));
+ replace_range = Utf8AdvanceByUtf16Units(insert_point, replacementRange.length);
+ }
+
+ /* Convert caret index into a pointer in the UTF-8 string. */
+ const char *selection = Utf8AdvanceByUtf16Units(utf8, selRange.location);
+
+ HandleTextInput(utf8, true, selection, insert_point, replace_range);
+ }
+}
+
+/** Set a new marked text and reposition the caret. */
+- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange
+{
+ [ self setMarkedText:aString selectedRange:selRange replacementRange:NSMakeRange(NSNotFound, 0) ];
+}
+
+/** Unmark the current marked text. */
+- (void)unmarkText
+{
+ HandleTextInput(NULL, true);
+}
+
+/** Get the caret position. */
+- (NSRange)selectedRange
+{
+ if (!EditBoxInGlobalFocus()) return NSMakeRange(NSNotFound, 0);
+
+ NSUInteger start = CountUtf16Units(_focused_window->GetFocusedText(), _focused_window->GetCaret());
+ return NSMakeRange(start, 0);
+}
+
+/** Get the currently marked range. */
+- (NSRange)markedRange
+{
+ if (!EditBoxInGlobalFocus()) return NSMakeRange(NSNotFound, 0);
+
+ size_t mark_len;
+ const char *mark = _focused_window->GetMarkedText(&mark_len);
+ if (mark != NULL) {
+ NSUInteger start = CountUtf16Units(_focused_window->GetFocusedText(), mark);
+ NSUInteger len = CountUtf16Units(mark, mark + mark_len);
+
+ return NSMakeRange(start, len);
+ }
+
+ return NSMakeRange(NSNotFound, 0);
+}
+
+/** Is any text marked? */
+- (BOOL)hasMarkedText
+{
+ if (!EditBoxInGlobalFocus()) return NO;
+
+ size_t len;
+ return _focused_window->GetMarkedText(&len) != NULL;
+}
+
+/** Get a string corresponding to the given range. */
+- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)theRange actualRange:(NSRangePointer)actualRange
+{
+ if (!EditBoxInGlobalFocus()) return nil;
+
+ NSString *s = [ NSString stringWithUTF8String:_focused_window->GetFocusedText() ];
+ NSRange valid_range = NSIntersectionRange(NSMakeRange(0, [ s length ]), theRange);
+
+ if (actualRange != NULL) *actualRange = valid_range;
+ if (valid_range.length == 0) return nil;
+
+ return [ [ [ NSAttributedString alloc ] initWithString:[ s substringWithRange:valid_range ] ] autorelease ];
+}
+
+/** Get a string corresponding to the given range. */
+- (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange
+{
+ return [ self attributedSubstringForProposedRange:theRange actualRange:NULL ];
+}
+
+/** Get the current edit box string. */
+- (NSAttributedString *)attributedString
+{
+ if (!EditBoxInGlobalFocus()) return [ [ [ NSAttributedString alloc ] initWithString:@"" ] autorelease ];
+
+ return [ [ [ NSAttributedString alloc ] initWithString:[ NSString stringWithUTF8String:_focused_window->GetFocusedText() ] ] autorelease ];
+}
+
+/** Get the character that is rendered at the given point. */
+- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
+{
+ if (!EditBoxInGlobalFocus()) return NSNotFound;
+
+ NSPoint view_pt = [ self convertPoint:[ [ self window ] convertScreenToBase:thePoint ] fromView:nil ];
+
+ Point pt = { (int)view_pt.x, (int)[ self frame ].size.height - (int)view_pt.y };
+
+ const char *ch = _focused_window->GetTextCharacterAtPosition(pt);
+ if (ch == NULL) return NSNotFound;
+
+ return CountUtf16Units(_focused_window->GetFocusedText(), ch);
+}
+
+/** Get the bounding rect for the given range. */
+- (NSRect)firstRectForCharacterRange:(NSRange)aRange
+{
+ if (!EditBoxInGlobalFocus()) return NSMakeRect(0, 0, 0, 0);
+
+ /* Convert range to UTF-8 string pointers. */
+ const char *start = Utf8AdvanceByUtf16Units(_focused_window->GetFocusedText(), aRange.location);
+ const char *end = aRange.length != 0 ? Utf8AdvanceByUtf16Units(_focused_window->GetFocusedText(), aRange.location + aRange.length) : start;
+
+ /* Get the bounding rect for the text range.*/
+ Rect r = _focused_window->GetTextBoundingRect(start, end);
+ NSRect view_rect = NSMakeRect(_focused_window->left + r.left, [ self frame ].size.height - _focused_window->top - r.bottom, r.right - r.left, r.bottom - r.top);
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
+ if ([ [ self window ] respondsToSelector:@selector(convertRectToScreen:) ]) {
+ return [ [ self window ] convertRectToScreen:[ self convertRect:view_rect toView:nil ] ];
+ }
+#endif
+
+ NSRect window_rect = [ self convertRect:view_rect toView:nil ];
+ NSPoint origin = [ [ self window ] convertBaseToScreen:window_rect.origin ];
+ return NSMakeRect(origin.x, origin.y, window_rect.size.width, window_rect.size.height);
+}
+
+/** Get the bounding rect for the given range. */
+- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
+{
+ return [ self firstRectForCharacterRange:aRange ];
+}
+
+/** Get all string attributes that we can process for marked text. */
+- (NSArray*)validAttributesForMarkedText
+{
+ return [ NSArray array ];
+}
+
+/** Identifier for this text input instance. */
+#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5
+- (long)conversationIdentifier
+#else
+- (NSInteger)conversationIdentifier
+#endif
+{
+ return 0;
+}
+
+/** Invoke the selector if we implement it. */
+- (void)doCommandBySelector:(SEL)aSelector
+{
+ if ([ self respondsToSelector:aSelector ]) [ self performSelector:aSelector ];
+}
+
@end
diff --git a/src/video/cocoa/event.mm b/src/video/cocoa/event.mm
index 9057c6ccc8..489b385225 100644
--- a/src/video/cocoa/event.mm
+++ b/src/video/cocoa/event.mm
@@ -60,11 +60,28 @@ enum RightMouseButtonEmulationState {
static unsigned int _current_mods;
static bool _tab_is_down;
static bool _emulating_right_button;
+#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
+static float _current_magnification;
+#endif
#ifdef _DEBUG
static uint32 _tEvent;
#endif
+/* Support for touch gestures is only available starting with the
+ * 10.6 SDK, even if it says that support starts in fact with 10.5.2.
+ * Replicate the needed stuff for older SDKs. */
+#if MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_5
+static const NSUInteger NSEventTypeMagnify = 30;
+static const NSUInteger NSEventTypeEndGesture = 20;
+
+@interface NSEvent ()
+/* This message is valid for events of type NSEventTypeMagnify, on 10.5.2 or later */
+- (CGFloat)magnification WEAK_IMPORT_ATTRIBUTE;
+@end
+#endif
+
+
static uint32 GetTick()
{
struct timeval tim;
@@ -255,8 +272,10 @@ static uint32 QZ_MapKey(unsigned short sym)
return key;
}
-static void QZ_KeyEvent(unsigned short keycode, unsigned short unicode, BOOL down)
+static bool QZ_KeyEvent(unsigned short keycode, unsigned short unicode, BOOL down)
{
+ bool interpret_keys = true;
+
switch (keycode) {
case QZ_UP: SB(_dirkeys, 1, 1, down); break;
case QZ_DOWN: SB(_dirkeys, 3, 1, down); break;
@@ -275,6 +294,21 @@ static void QZ_KeyEvent(unsigned short keycode, unsigned short unicode, BOOL dow
if (down) {
uint32 pressed_key = QZ_MapKey(keycode);
+
+ static bool console = false;
+
+ if (pressed_key == WKC_BACKQUOTE && unicode == 0) {
+ if (!console) {
+ /* Backquote is a dead key, require a double press for hotkey behaviour (i.e. console). */
+ console = true;
+ return true;
+ } else {
+ /* Second backquote, don't interpret as text input. */
+ interpret_keys = false;
+ }
+ }
+ console = false;
+
/* Don't handle normal characters if an edit box has the focus. */
if (!EditBoxInGlobalFocus() || ((pressed_key & ~WKC_SPECIAL_KEYS) <= WKC_TAB) || IsInsideMM(pressed_key & ~WKC_SPECIAL_KEYS, WKC_F1, WKC_PAUSE + 1)) {
HandleKeypress(pressed_key, unicode);
@@ -283,6 +317,8 @@ static void QZ_KeyEvent(unsigned short keycode, unsigned short unicode, BOOL dow
} else {
DEBUG(driver, 2, "cocoa_v: QZ_KeyEvent: %x (%x), up", keycode, unicode);
}
+
+ return interpret_keys;
}
static void QZ_DoUnsidedModifiers(unsigned int newMods)
@@ -387,7 +423,6 @@ static bool QZ_PollEvent()
NSString *chars;
NSPoint pt;
- NSText *fieldEditor;
switch ([ event type ]) {
case NSMouseMoved:
case NSOtherMouseDragged:
@@ -504,17 +539,19 @@ static bool QZ_PollEvent()
break;
}
- fieldEditor = [[ event window ] fieldEditor:YES forObject:nil ];
- [ fieldEditor setString:@"" ];
- [ fieldEditor interpretKeyEvents: [ NSArray arrayWithObject:event ] ];
-
- chars = [ event characters ];
- if ([ chars length ] == 0) {
- QZ_KeyEvent([ event keyCode ], 0, YES);
+ if (EditBoxInGlobalFocus()) {
+ if (QZ_KeyEvent([ event keyCode ], 0, YES)) {
+ [ _cocoa_subdriver->cocoaview interpretKeyEvents:[ NSArray arrayWithObject:event ] ];
+ }
} else {
- QZ_KeyEvent([ event keyCode ], [ chars characterAtIndex:0 ], YES);
- for (uint i = 1; i < [ chars length ]; i++) {
- QZ_KeyEvent(0, [ chars characterAtIndex:i ], YES);
+ chars = [ event characters ];
+ if ([ chars length ] == 0) {
+ QZ_KeyEvent([ event keyCode ], 0, YES);
+ } else {
+ QZ_KeyEvent([ event keyCode ], [ chars characterAtIndex:0 ], YES);
+ for (uint i = 1; i < [ chars length ]; i++) {
+ QZ_KeyEvent(0, [ chars characterAtIndex:i ], YES);
+ }
}
}
break;
@@ -547,6 +584,29 @@ static bool QZ_PollEvent()
_cursor.v_wheel -= (int)([ event deltaY ] * 5 * _settings_client.gui.scrollwheel_multiplier);
break;
+#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
+ case NSEventTypeMagnify:
+ /* Pinch open or close gesture. */
+ _current_magnification += [ event magnification ] * 5.0f;
+
+ while (_current_magnification >= 1.0f) {
+ _current_magnification -= 1.0f;
+ _cursor.wheel++;
+ HandleMouseEvents();
+ }
+ while (_current_magnification <= -1.0f) {
+ _current_magnification += 1.0f;
+ _cursor.wheel--;
+ HandleMouseEvents();
+ }
+ break;
+
+ case NSEventTypeEndGesture:
+ /* Gesture ended. */
+ _current_magnification = 0.0f;
+ break;
+#endif
+
case NSCursorUpdate:
case NSMouseEntered:
case NSMouseExited:
diff --git a/src/video/cocoa/fullscreen.mm b/src/video/cocoa/fullscreen.mm
index 8de36f8525..411b6d0c03 100644
--- a/src/video/cocoa/fullscreen.mm
+++ b/src/video/cocoa/fullscreen.mm
@@ -241,7 +241,7 @@ class FullscreenSubdriver: public CocoaSubdriver {
* disable until a replacement can be found. */
if (MacOSVersionIsAtLeast(10, 7, 0)) {
this->window_buffer = NULL;
- this->window_pitch = NULL;
+ this->window_pitch = 0;
} else {
#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7)
this->window_buffer = CGDisplayBaseAddress(this->display_id);
diff --git a/src/video/cocoa/wnd_quartz.mm b/src/video/cocoa/wnd_quartz.mm
index 34be5656b0..12021631d4 100644
--- a/src/video/cocoa/wnd_quartz.mm
+++ b/src/video/cocoa/wnd_quartz.mm
@@ -34,6 +34,13 @@
#include "../../core/math_func.hpp"
#include "../../gfx_func.h"
+/* On some old versions of MAC OS this may not be defined.
+ * Those versions generally only produce code for PPC. So it should be safe to
+ * set this to 0. */
+#ifndef kCGBitmapByteOrder32Host
+#define kCGBitmapByteOrder32Host 0
+#endif
+
/**
* Important notice regarding all modifications!!!!!!!
* There are certain limitations because the file is objective C++.
@@ -144,7 +151,7 @@ static CGColorSpaceRef QZ_GetCorrectColorSpace()
/* Calculate total area we are blitting */
uint32 blitArea = 0;
for (int n = 0; n < dirtyRectCount; n++) {
- blitArea += dirtyRects[n].size.width * dirtyRects[n].size.height;
+ blitArea += (uint32)(dirtyRects[n].size.width * dirtyRects[n].size.height);
}
/*
@@ -240,12 +247,12 @@ void WindowQuartzSubdriver::GetDeviceInfo()
*/
bool WindowQuartzSubdriver::ToggleFullscreen()
{
-#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
- [this->window toggleFullScreen:this->window];
- return true;
-#else
+ if ([ this->window respondsToSelector:@selector(toggleFullScreen:) ]) {
+ [ this->window performSelector:@selector(toggleFullScreen:) withObject:this->window ];
+ return true;
+ }
+
return false;
-#endif
}
bool WindowQuartzSubdriver::SetVideoMode(int width, int height, int bpp)
@@ -280,15 +287,17 @@ bool WindowQuartzSubdriver::SetVideoMode(int width, int height, int bpp)
return false;
}
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
/* Add built in full-screen support when available (OS X 10.7 and higher)
* This code actually compiles for 10.5 and later, but only makes sense in conjunction
* with the quartz fullscreen support as found only in 10.7 and later
*/
-#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7)
if ([this->window respondsToSelector:@selector(toggleFullScreen:)]) {
- /* Constants needed to build on pre-10.7 systems. Source: NSWindow documentation. */
+#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
+ /* Constants needed to build on pre-10.7 SDKs. Source: NSWindow documentation. */
const int NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7;
const int NSWindowFullScreenButton = 7;
+#endif
NSWindowCollectionBehavior behavior = [ this->window collectionBehavior ];
behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
@@ -326,10 +335,10 @@ bool WindowQuartzSubdriver::SetVideoMode(int width, int height, int bpp)
[ this->window setContentSize:contentRect.size ];
/* Ensure frame height - title bar height >= view height */
- contentRect.size.height = Clamp(height, 0, [ this->window frame ].size.height - 22 /* 22 is the height of title bar of window*/);
+ contentRect.size.height = Clamp(height, 0, (int)[ this->window frame ].size.height - 22 /* 22 is the height of title bar of window*/);
if (this->cocoaview != nil) {
- height = contentRect.size.height;
+ height = (int)contentRect.size.height;
[ this->cocoaview setFrameSize:contentRect.size ];
}
}
@@ -514,7 +523,7 @@ NSPoint WindowQuartzSubdriver::GetMouseLocation(NSEvent *event)
{
NSPoint pt;
- if (event.window == nil) {
+ if ( [ event window ] == nil) {
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
if ([ this->cocoaview respondsToSelector:@selector(convertRectFromScreen:) ]) {
pt = [ this->cocoaview convertPoint:[ [ this->cocoaview window ] convertRectFromScreen:NSMakeRect([ event locationInWindow ].x, [ event locationInWindow ].y, 0, 0) ].origin fromView:nil ];
@@ -560,8 +569,8 @@ bool WindowQuartzSubdriver::WindowResized()
NSRect newframe = [ this->cocoaview frame ];
- this->window_width = newframe.size.width;
- this->window_height = newframe.size.height;
+ this->window_width = (int)newframe.size.width;
+ this->window_height = (int)newframe.size.height;
/* Create Core Graphics Context */
free(this->window_buffer);
diff --git a/src/video/video_driver.hpp b/src/video/video_driver.hpp
index 306fe28039..d8249b1f77 100644
--- a/src/video/video_driver.hpp
+++ b/src/video/video_driver.hpp
@@ -73,6 +73,11 @@ public:
{
return true;
}
+
+ /**
+ * An edit box lost the input focus. Abort character compositing if necessary.
+ */
+ virtual void EditBoxLostFocus() {}
};
/** Base of the factory for the video drivers. */
diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp
index d0f0acce8d..4ea7748549 100644
--- a/src/video/win32_v.cpp
+++ b/src/video/win32_v.cpp
@@ -21,9 +21,11 @@
#include "../texteff.hpp"
#include "../thread/thread.h"
#include "../progress.h"
+#include "../window_gui.h"
#include "../window_func.h"
#include "win32_v.h"
#include
+#include
/* Missing define in MinGW headers. */
#ifndef MAPVK_VK_TO_CHAR
@@ -50,6 +52,9 @@ bool _window_maximize;
uint _display_hz;
uint _fullscreen_bpp;
static Dimension _bck_resolution;
+#if !defined(WINCE) || _WIN32_WCE >= 0x400
+DWORD _imm_props;
+#endif
/** Whether the drawing is/may be done in a separate thread. */
static bool _draw_threaded;
@@ -498,6 +503,152 @@ static LRESULT HandleCharMsg(uint keycode, WChar charcode)
return 0;
}
+#if !defined(WINCE) || _WIN32_WCE >= 0x400
+/** Should we draw the composition string ourself, i.e is this a normal IME? */
+static bool DrawIMECompositionString()
+{
+ return (_imm_props & IME_PROP_AT_CARET) && !(_imm_props & IME_PROP_SPECIAL_UI);
+}
+
+/** Set position of the composition window to the caret position. */
+static void SetCompositionPos(HWND hwnd)
+{
+ HIMC hIMC = ImmGetContext(hwnd);
+ if (hIMC != NULL) {
+ COMPOSITIONFORM cf;
+ cf.dwStyle = CFS_POINT;
+
+ if (EditBoxInGlobalFocus()) {
+ /* Get caret position. */
+ Point pt = _focused_window->GetCaretPosition();
+ cf.ptCurrentPos.x = _focused_window->left + pt.x;
+ cf.ptCurrentPos.y = _focused_window->top + pt.y;
+ } else {
+ cf.ptCurrentPos.x = 0;
+ cf.ptCurrentPos.y = 0;
+ }
+ ImmSetCompositionWindow(hIMC, &cf);
+ }
+ ImmReleaseContext(hwnd, hIMC);
+}
+
+/** Set the position of the candidate window. */
+static void SetCandidatePos(HWND hwnd)
+{
+ HIMC hIMC = ImmGetContext(hwnd);
+ if (hIMC != NULL) {
+ CANDIDATEFORM cf;
+ cf.dwIndex = 0;
+ cf.dwStyle = CFS_EXCLUDE;
+
+ if (EditBoxInGlobalFocus()) {
+ Point pt = _focused_window->GetCaretPosition();
+ cf.ptCurrentPos.x = _focused_window->left + pt.x;
+ cf.ptCurrentPos.y = _focused_window->top + pt.y;
+ if (_focused_window->window_class == WC_CONSOLE) {
+ cf.rcArea.left = _focused_window->left;
+ cf.rcArea.top = _focused_window->top;
+ cf.rcArea.right = _focused_window->left + _focused_window->width;
+ cf.rcArea.bottom = _focused_window->top + _focused_window->height;
+ } else {
+ cf.rcArea.left = _focused_window->left + _focused_window->nested_focus->pos_x;
+ cf.rcArea.top = _focused_window->top + _focused_window->nested_focus->pos_y;
+ cf.rcArea.right = cf.rcArea.left + _focused_window->nested_focus->current_x;
+ cf.rcArea.bottom = cf.rcArea.top + _focused_window->nested_focus->current_y;
+ }
+ } else {
+ cf.ptCurrentPos.x = 0;
+ cf.ptCurrentPos.y = 0;
+ SetRectEmpty(&cf.rcArea);
+ }
+ ImmSetCandidateWindow(hIMC, &cf);
+ }
+ ImmReleaseContext(hwnd, hIMC);
+}
+
+/** Handle WM_IME_COMPOSITION messages. */
+static LRESULT HandleIMEComposition(HWND hwnd, WPARAM wParam, LPARAM lParam)
+{
+ HIMC hIMC = ImmGetContext(hwnd);
+
+ if (hIMC != NULL) {
+ if (lParam & GCS_RESULTSTR) {
+ /* Read result string from the IME. */
+ LONG len = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0); // Length is always in bytes, even in UNICODE build.
+ TCHAR *str = (TCHAR *)_alloca(len + sizeof(TCHAR));
+ len = ImmGetCompositionString(hIMC, GCS_RESULTSTR, str, len);
+ str[len / sizeof(TCHAR)] = '\0';
+
+ /* Transmit text to windowing system. */
+ if (len > 0) {
+ HandleTextInput(NULL, true); // Clear marked string.
+ HandleTextInput(FS2OTTD(str));
+ }
+ SetCompositionPos(hwnd);
+
+ /* Don't pass the result string on to the default window proc. */
+ lParam &= ~(GCS_RESULTSTR | GCS_RESULTCLAUSE | GCS_RESULTREADCLAUSE | GCS_RESULTREADSTR);
+ }
+
+ if ((lParam & GCS_COMPSTR) && DrawIMECompositionString()) {
+ /* Read composition string from the IME. */
+ LONG len = ImmGetCompositionString(hIMC, GCS_COMPSTR, NULL, 0); // Length is always in bytes, even in UNICODE build.
+ TCHAR *str = (TCHAR *)_alloca(len + sizeof(TCHAR));
+ len = ImmGetCompositionString(hIMC, GCS_COMPSTR, str, len);
+ str[len / sizeof(TCHAR)] = '\0';
+
+ if (len > 0) {
+ static char utf8_buf[1024];
+ convert_from_fs(str, utf8_buf, lengthof(utf8_buf));
+
+ /* Convert caret position from bytes in the input string to a position in the UTF-8 encoded string. */
+ LONG caret_bytes = ImmGetCompositionString(hIMC, GCS_CURSORPOS, NULL, 0);
+ const char *caret = utf8_buf;
+ for (const TCHAR *c = str; *c != '\0' && *caret != '\0' && caret_bytes > 0; c++, caret_bytes--) {
+ /* Skip DBCS lead bytes or leading surrogates. */
+#ifdef UNICODE
+ if (Utf16IsLeadSurrogate(*c)) {
+#else
+ if (IsDBCSLeadByte(*c)) {
+#endif
+ c++;
+ caret_bytes--;
+ }
+ Utf8Consume(&caret);
+ }
+
+ HandleTextInput(utf8_buf, true, caret);
+ } else {
+ HandleTextInput(NULL, true);
+ }
+
+ lParam &= ~(GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS | GCS_DELTASTART);
+ }
+ }
+ ImmReleaseContext(hwnd, hIMC);
+
+ return lParam != 0 ? DefWindowProc(hwnd, WM_IME_COMPOSITION, wParam, lParam) : 0;
+}
+
+/** Clear the current composition string. */
+static void CancelIMEComposition(HWND hwnd)
+{
+ HIMC hIMC = ImmGetContext(hwnd);
+ if (hIMC != NULL) ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
+ ImmReleaseContext(hwnd, hIMC);
+ /* Clear any marked string from the current edit box. */
+ HandleTextInput(NULL, true);
+}
+
+#else
+
+static bool DrawIMECompositionString() { return false; }
+static void SetCompositionPos(HWND hwnd) {}
+static void SetCandidatePos(HWND hwnd) {}
+static void CancelIMEComposition(HWND hwnd) {}
+
+#endif /* !defined(WINCE) || _WIN32_WCE >= 0x400 */
+
static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static uint32 keycode = 0;
@@ -507,6 +658,10 @@ static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lP
switch (msg) {
case WM_CREATE:
SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, (TIMERPROC)TrackMouseTimerProc);
+ SetCompositionPos(hwnd);
+#if !defined(WINCE) || _WIN32_WCE >= 0x400
+ _imm_props = ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY);
+#endif
break;
case WM_ENTERSIZEMOVE:
@@ -633,6 +788,33 @@ static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lP
}
#if !defined(WINCE) || _WIN32_WCE >= 0x400
+ case WM_INPUTLANGCHANGE:
+ _imm_props = ImmGetProperty(GetKeyboardLayout(0), IGP_PROPERTY);
+ break;
+
+ case WM_IME_SETCONTEXT:
+ /* Don't show the composition window if we draw the string ourself. */
+ if (DrawIMECompositionString()) lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
+ break;
+
+ case WM_IME_STARTCOMPOSITION:
+ SetCompositionPos(hwnd);
+ if (DrawIMECompositionString()) return 0;
+ break;
+
+ case WM_IME_COMPOSITION:
+ return HandleIMEComposition(hwnd, wParam, lParam);
+
+ case WM_IME_ENDCOMPOSITION:
+ /* Clear any pending composition string. */
+ HandleTextInput(NULL, true);
+ if (DrawIMECompositionString()) return 0;
+ break;
+
+ case WM_IME_NOTIFY:
+ if (wParam == IMN_OPENCANDIDATE) SetCandidatePos(hwnd);
+ break;
+
#if !defined(UNICODE)
case WM_IME_CHAR:
if (GB(wParam, 8, 8) != 0) {
@@ -817,6 +999,7 @@ static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lP
case WM_SETFOCUS:
_wnd.has_focus = true;
+ SetCompositionPos(hwnd);
break;
case WM_KILLFOCUS:
@@ -1172,3 +1355,10 @@ bool VideoDriver_Win32::AfterBlitterChange()
{
return AllocateDibSection(_screen.width, _screen.height, true) && this->MakeWindow(_fullscreen);
}
+
+void VideoDriver_Win32::EditBoxLostFocus()
+{
+ CancelIMEComposition(_wnd.main_wnd);
+ SetCompositionPos(_wnd.main_wnd);
+ SetCandidatePos(_wnd.main_wnd);
+}
diff --git a/src/video/win32_v.h b/src/video/win32_v.h
index 0706c0ee7a..6be60c2300 100644
--- a/src/video/win32_v.h
+++ b/src/video/win32_v.h
@@ -33,6 +33,8 @@ public:
/* virtual */ bool ClaimMousePointer();
+ /* virtual */ void EditBoxLostFocus();
+
/* virtual */ const char *GetName() const { return "win32"; }
bool MakeWindow(bool full_screen);
diff --git a/src/window.cpp b/src/window.cpp
index ad4bf525f7..949b722676 100644
--- a/src/window.cpp
+++ b/src/window.cpp
@@ -33,6 +33,7 @@
#include "statusbar_gui.h"
#include "error.h"
#include "game/game.hpp"
+#include "video/video_driver.hpp"
/** Values for _settings_client.gui.auto_scrolling */
enum ViewportAutoscrolling {
@@ -249,6 +250,89 @@ QueryString *Window::GetQueryString(uint widnum)
return query != this->querystrings.End() ? query->second : NULL;
}
+/**
+ * Get the current input text if an edit box has the focus.
+ * @return The currently focused input text or NULL if no input focused.
+ */
+/* virtual */ const char *Window::GetFocusedText() const
+{
+ if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
+ return this->GetQueryString(this->nested_focus->index)->GetText();
+ }
+
+ return NULL;
+}
+
+/**
+ * Get the string at the caret if an edit box has the focus.
+ * @return The text at the caret or NULL if no edit box is focused.
+ */
+/* virtual */ const char *Window::GetCaret() const
+{
+ if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
+ return this->GetQueryString(this->nested_focus->index)->GetCaret();
+ }
+
+ return NULL;
+}
+
+/**
+ * Get the range of the currently marked input text.
+ * @param[out] length Length of the marked text.
+ * @return Pointer to the start of the marked text or NULL if no text is marked.
+ */
+/* virtual */ const char *Window::GetMarkedText(size_t *length) const
+{
+ if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
+ return this->GetQueryString(this->nested_focus->index)->GetMarkedText(length);
+ }
+
+ return NULL;
+}
+
+/**
+ * Get the current caret position if an edit box has the focus.
+ * @return Top-left location of the caret, relative to the window.
+ */
+/* virtual */ Point Window::GetCaretPosition() const
+{
+ if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
+ return this->GetQueryString(this->nested_focus->index)->GetCaretPosition(this, this->nested_focus->index);
+ }
+
+ Point pt = {0, 0};
+ return pt;
+}
+
+/**
+ * Get the bounding rectangle for a text range if an edit box has the focus.
+ * @param from Start of the string range.
+ * @param to End of the string range.
+ * @return Rectangle encompassing the string range, relative to the window.
+ */
+/* virtual */ Rect Window::GetTextBoundingRect(const char *from, const char *to) const
+{
+ if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
+ return this->GetQueryString(this->nested_focus->index)->GetBoundingRect(this, this->nested_focus->index, from, to);
+ }
+
+ Rect r = {0, 0, 0, 0};
+ return r;
+}
+
+/**
+ * Get the character that is rendered at a position by the focused edit box.
+ * @param pt The position to test.
+ * @return Pointer to the character at the position or NULL if no character is at the position.
+ */
+/* virtual */ const char *Window::GetTextCharacterAtPosition(const Point &pt) const
+{
+ if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) {
+ return this->GetQueryString(this->nested_focus->index)->GetCharAtPosition(this, this->nested_focus->index, pt);
+ }
+
+ return NULL;
+}
/**
* Set the window that has the focus
@@ -293,6 +377,8 @@ bool EditBoxInGlobalFocus()
void Window::UnfocusFocusedWidget()
{
if (this->nested_focus != NULL) {
+ if (this->nested_focus->type == WWT_EDITBOX) _video_driver->EditBoxLostFocus();
+
/* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
this->nested_focus->SetDirty(this);
this->nested_focus = NULL;
@@ -315,11 +401,20 @@ bool Window::SetFocusedWidget(int widget_index)
/* Repaint the widget that lost focus. A focused edit box may else leave the caret on the screen. */
this->nested_focus->SetDirty(this);
+ if (this->nested_focus->type == WWT_EDITBOX) _video_driver->EditBoxLostFocus();
}
this->nested_focus = this->GetWidget(widget_index);
return true;
}
+/**
+ * Called when window looses focus
+ */
+void Window::OnFocusLost()
+{
+ if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) _video_driver->EditBoxLostFocus();
+}
+
/**
* Sets the enabled/disabled status of a list of widgets.
* By default, widgets are enabled.
@@ -823,7 +918,10 @@ Window::~Window()
if (_last_scroll_window == this) _last_scroll_window = NULL;
/* Make sure we don't try to access this window as the focused window when it doesn't exist anymore. */
- if (_focused_window == this) _focused_window = NULL;
+ if (_focused_window == this) {
+ this->OnFocusLost();
+ _focused_window = NULL;
+ }
this->DeleteChildWindows();
@@ -2299,9 +2397,14 @@ EventState Window::HandleEditBoxKey(int wid, WChar key, uint16 keycode)
break;
case QueryString::ACTION_CLEAR:
- query->text.DeleteAll();
- this->SetWidgetDirty(wid);
- this->OnEditboxChanged(wid);
+ if (query->text.bytes <= 1) {
+ /* If already empty, unfocus instead */
+ this->UnfocusFocusedWidget();
+ } else {
+ query->text.DeleteAll();
+ this->SetWidgetDirty(wid);
+ this->OnEditboxChanged(wid);
+ }
break;
default:
@@ -2372,6 +2475,35 @@ void HandleCtrlChanged()
}
}
+/**
+ * Insert a text string at the cursor position into the edit box widget.
+ * @param wid Edit box widget.
+ * @param str Text string to insert.
+ */
+/* virtual */ void Window::InsertTextString(int wid, const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end)
+{
+ QueryString *query = this->GetQueryString(wid);
+ if (query == NULL) return;
+
+ if (query->text.InsertString(str, marked, caret, insert_location, replacement_end) || marked) {
+ this->SetWidgetDirty(wid);
+ this->OnEditboxChanged(wid);
+ }
+}
+
+/**
+ * Handle text input.
+ * @param str Text string to input.
+ * @param marked Is the input a marked composition string from an IME?
+ * @param caret Move the caret to this point in the insertion string.
+ */
+void HandleTextInput(const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end)
+{
+ if (!EditBoxInGlobalFocus()) return;
+
+ _focused_window->InsertTextString(_focused_window->window_class == WC_CONSOLE ? 0 : _focused_window->nested_focus->index, str, marked, caret, insert_location, replacement_end);
+}
+
/**
* Local counter that is incremented each time an mouse input event is detected.
* The counter is used to stop auto-scrolling.
diff --git a/src/window_func.h b/src/window_func.h
index ee494592be..453b889789 100644
--- a/src/window_func.h
+++ b/src/window_func.h
@@ -14,6 +14,7 @@
#include "window_type.h"
#include "company_type.h"
+#include "core/geometry_type.hpp"
Window *FindWindowById(WindowClass cls, WindowNumber number);
Window *FindWindowByClass(WindowClass cls);
@@ -53,5 +54,6 @@ void DeleteWindowById(WindowClass cls, WindowNumber number, bool force = true);
void DeleteWindowByClass(WindowClass cls);
bool EditBoxInGlobalFocus();
+Point GetCaretPosition();
#endif /* WINDOW_FUNC_H */
diff --git a/src/window_gui.h b/src/window_gui.h
index 86fb802b1b..efe8dca876 100644
--- a/src/window_gui.h
+++ b/src/window_gui.h
@@ -323,6 +323,13 @@ public:
const QueryString *GetQueryString(uint widnum) const;
QueryString *GetQueryString(uint widnum);
+ virtual const char *GetFocusedText() const;
+ virtual const char *GetCaret() const;
+ virtual const char *GetMarkedText(size_t *length) const;
+ virtual Point GetCaretPosition() const;
+ virtual Rect GetTextBoundingRect(const char *from, const char *to) const;
+ virtual const char *GetTextCharacterAtPosition(const Point &pt) const;
+
void InitNested(const WindowDesc *desc, WindowNumber number = 0);
void CreateNestedTree(const WindowDesc *desc, bool fill_nested = true);
void FinishInitNested(const WindowDesc *desc, WindowNumber window_number = 0);
@@ -467,6 +474,7 @@ public:
bool SetFocusedWidget(int widget_index);
EventState HandleEditBoxKey(int wid, WChar key, uint16 keycode);
+ virtual void InsertTextString(int wid, const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end);
void HandleButtonClick(byte widget);
int GetRowFromWidget(int clickpos, int widget, int padding, int line_height = -1) const;
@@ -559,10 +567,7 @@ public:
*/
virtual void OnFocus() {}
- /**
- * Called when window looses focus
- */
- virtual void OnFocusLost() {}
+ virtual void OnFocusLost();
/**
* A key has been pressed.