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.