changeset 20716:42bd8405a50c draft

-Add: [OSX] Support for mouse selection in the IME composition string.
author Michael Lutz <michi@icosahedron.de>
date Fri, 26 Jul 2013 03:42:08 +0200
parents db681959a377
children 19b7ec9e442d
files src/console_gui.cpp src/gfx.cpp src/gfx_func.h src/gfx_layout.cpp src/gfx_layout.h src/misc_gui.cpp src/querystring_gui.h src/video/cocoa/cocoa_v.mm src/window.cpp src/window_gui.h
diffstat 10 files changed, 129 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/src/console_gui.cpp
+++ b/src/console_gui.cpp
@@ -351,12 +351,21 @@
 		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, FS_NORMAL) : p1;
+		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);
--- a/src/gfx.cpp
+++ b/src/gfx.cpp
@@ -722,6 +722,21 @@
 }
 
 /**
+ * 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
  * @param x           X position to draw character
--- a/src/gfx_func.h
+++ b/src/gfx_func.h
@@ -129,6 +129,7 @@
 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);
--- a/src/gfx_layout.cpp
+++ b/src/gfx_layout.cpp
@@ -567,6 +567,50 @@
 }
 
 /**
+ * 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 ParagraphLayout::Line *line = *this->Begin();;
+
+	for (int run_index = 0; run_index < line->countRuns(); run_index++) {
+		const ParagraphLayout::VisualRun *run = line->getVisualRun(run_index);
+
+		for (int i = 0; i < run->getGlyphCount(); i++) {
+			/* Not a valid glyph (empty). */
+			if (run->getGlyphs()[i] == 0xFFFF) continue;
+
+			int begin_x = run->getPositions()[i * 2];
+			int end_x   = 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.
  */
 Font *Layouter::GetFont(FontSize size, TextColour colour)
--- a/src/gfx_layout.h
+++ b/src/gfx_layout.h
@@ -212,6 +212,7 @@
 	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();
--- a/src/misc_gui.cpp
+++ b/src/misc_gui.cpp
@@ -854,6 +854,39 @@
 	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<NWidgetLeaf>(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<NWidgetLeaf>(wid);
--- a/src/querystring_gui.h
+++ b/src/querystring_gui.h
@@ -56,6 +56,7 @@
 
 	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.
--- a/src/video/cocoa/cocoa_v.mm
+++ b/src/video/cocoa/cocoa_v.mm
@@ -909,7 +909,16 @@
 /** Get the character that is rendered at the given point. */
 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
 {
-	return NSNotFound;
+	if (!EditBoxInGlobalFocus()) return NSNotFound;
+
+	NSPoint view_pt = [ self convertPoint:[ [ self window ] convertScreenToBase:thePoint ] fromView:nil ];
+
+	Point pt = { view_pt.x, [ self frame ].size.height - 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. */
--- a/src/window.cpp
+++ b/src/window.cpp
@@ -393,6 +393,19 @@
 	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
--- a/src/window_gui.h
+++ b/src/window_gui.h
@@ -350,6 +350,7 @@
 	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(WindowNumber number = 0);
 	void CreateNestedTree(bool fill_nested = true);