Bug 407927 - [Bidi] New API Combo#addSegmentListener(SegmentListener) -
code fix and JUnit test
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Combo.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Combo.java
index 6e7c812..4a7d140 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Combo.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Combo.java
@@ -222,6 +222,46 @@
}
/**
+ * Adds a segment listener.
+ * <p>
+ * A <code>SegmentEvent</code> is sent whenever text content is being modified or
+ * a segment listener is added or removed. You can
+ * customize the appearance of text by indicating certain characters to be inserted
+ * at certain text offsets. This may be used for bidi purposes, e.g. when
+ * adjacent segments of right-to-left text should not be reordered relative to
+ * each other.
+ * E.g., multiple Java string literals in a right-to-left language
+ * should generally remain in logical order to each other, that is, the
+ * way they are stored.
+ * </p>
+ * <p>
+ * <b>Warning</b>: This API is currently only implemented on Windows.
+ * <code>SegmentEvent</code>s won't be sent on GTK and Cocoa.
+ * </p>
+ *
+ * @param listener the listener which should be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see SegmentEvent
+ * @see SegmentListener
+ * @see #removeSegmentListener
+ *
+ * @since 3.103
+ */
+public void addSegmentListener (SegmentListener listener) {
+ checkWidget ();
+ if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
+ addListener (SWT.Segments, new TypedListener (listener));
+}
+
+/**
* Adds the listener to the collection of listeners who will
* be notified when the user changes the receiver's selection, by sending
* it one of the messages defined in the <code>SelectionListener</code>
@@ -1259,6 +1299,32 @@
/**
* Removes the listener from the collection of listeners who will
+ * be notified when the receiver's text is modified.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see SegmentEvent
+ * @see SegmentListener
+ * @see #addSegmentListener
+ *
+ * @since 3.103
+ */
+public void removeSegmentListener (SegmentListener listener) {
+ checkWidget ();
+ if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
+ eventTable.unhook (SWT.Segments, listener);
+}
+
+/**
+ * Removes the listener from the collection of listeners who will
* be notified when the user changes the receiver's selection.
*
* @param listener the listener which should no longer be notified
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java
index 7606943..6db2aee 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Combo.java
@@ -207,6 +207,46 @@
}
/**
+ * Adds a segment listener.
+ * <p>
+ * A <code>SegmentEvent</code> is sent whenever text content is being modified or
+ * a segment listener is added or removed. You can
+ * customize the appearance of text by indicating certain characters to be inserted
+ * at certain text offsets. This may be used for bidi purposes, e.g. when
+ * adjacent segments of right-to-left text should not be reordered relative to
+ * each other.
+ * E.g., multiple Java string literals in a right-to-left language
+ * should generally remain in logical order to each other, that is, the
+ * way they are stored.
+ * </p>
+ * <p>
+ * <b>Warning</b>: This API is currently only implemented on Windows.
+ * <code>SegmentEvent</code>s won't be sent on GTK and Cocoa.
+ * </p>
+ *
+ * @param listener the listener which should be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see SegmentEvent
+ * @see SegmentListener
+ * @see #removeSegmentListener
+ *
+ * @since 3.103
+ */
+public void addSegmentListener (SegmentListener listener) {
+ checkWidget ();
+ if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
+ addListener (SWT.Segments, new TypedListener (listener));
+}
+
+/**
* Adds the listener to the collection of listeners who will
* be notified when the user changes the receiver's selection, by sending
* it one of the messages defined in the <code>SelectionListener</code>
@@ -1693,6 +1733,32 @@
/**
* Removes the listener from the collection of listeners who will
+ * be notified when the receiver's text is modified.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see SegmentEvent
+ * @see SegmentListener
+ * @see #addSegmentListener
+ *
+ * @since 3.103
+ */
+public void removeSegmentListener (SegmentListener listener) {
+ checkWidget ();
+ if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
+ eventTable.unhook (SWT.Segments, listener);
+}
+
+/**
+ * Removes the listener from the collection of listeners who will
* be notified when the user changes the receiver's selection.
*
* @param listener the listener which should no longer be notified
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Combo.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Combo.java
index 517a985..3ef98e9 100644
--- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Combo.java
+++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Combo.java
@@ -60,7 +60,12 @@
boolean noSelection, ignoreDefaultSelection, ignoreCharacter, ignoreModify, ignoreResize, lockText;
int scrollWidth, visibleCount;
long /*int*/ cbtHook;
+ String [] items = new String [0];
+ int[] segments;
+ int clearSegmentsCount = 0;
+ static final char LTR_MARK = '\u200e';
+ static final char RTL_MARK = '\u200f';
static final int VISIBLE_COUNT = 5;
/**
@@ -223,6 +228,56 @@
}
/**
+ * Adds a segment listener.
+ * <p>
+ * A <code>SegmentEvent</code> is sent whenever text content is being modified or
+ * a segment listener is added or removed. You can
+ * customize the appearance of text by indicating certain characters to be inserted
+ * at certain text offsets. This may be used for bidi purposes, e.g. when
+ * adjacent segments of right-to-left text should not be reordered relative to
+ * each other.
+ * E.g., multiple Java string literals in a right-to-left language
+ * should generally remain in logical order to each other, that is, the
+ * way they are stored.
+ * </p>
+ * <p>
+ * <b>Warning</b>: This API is currently only implemented on Windows.
+ * <code>SegmentEvent</code>s won't be sent on GTK and Cocoa.
+ * </p>
+ *
+ * @param listener the listener which should be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see SegmentEvent
+ * @see SegmentListener
+ * @see #removeSegmentListener
+ *
+ * @since 3.103
+ */
+public void addSegmentListener (SegmentListener listener) {
+ checkWidget ();
+ if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
+ addListener (SWT.Segments, new TypedListener (listener));
+ int selection = OS.CB_ERR;
+ if (!noSelection) {
+ selection = (int)/*64*/OS.SendMessage (handle, OS.CB_GETCURSEL, 0, 0);
+ }
+ clearSegments (true);
+ applyEditSegments ();
+ applyListSegments ();
+ if (selection != OS.CB_ERR) {
+ OS.SendMessage (handle, OS.CB_SETCURSEL, selection, 0);
+ }
+}
+
+/**
* Adds the listener to the collection of listeners who will
* be notified when the user changes the receiver's selection, by sending
* it one of the messages defined in the <code>SelectionListener</code>
@@ -282,6 +337,118 @@
addListener (SWT.Verify, typedListener);
}
+void applyEditSegments () {
+ if (--clearSegmentsCount != 0) return;
+ if (!hooks (SWT.Segments) && !filters (SWT.Segments)) return;
+ long /*int*/ hwndText = OS.GetDlgItem (handle, CBID_EDIT);
+ int length = OS.GetWindowTextLength (hwndText);
+ int cp = getCodePage ();
+ TCHAR buffer = new TCHAR (cp, length + 1);
+ if (length > 0) OS.GetWindowText (hwndText, buffer, length + 1);
+ String string = buffer.toString (0, length);
+
+ /* Get segments text */
+ Event event = new Event ();
+ event.text = string;
+ event.segments = segments;
+ sendEvent (SWT.Segments, event);
+ segments = event.segments;
+ if (segments == null) return;
+ int nSegments = segments.length;
+ if (nSegments == 0) return;
+ length = string == null ? 0 : string.length ();
+
+ for (int i = 1; i < nSegments; i++) {
+ if (event.segments [i] < event.segments [i - 1] || event.segments [i] > length) {
+ error (SWT.ERROR_INVALID_ARGUMENT);
+ }
+ }
+ char [] segmentsChars = event.segmentsChars;
+
+ int/*64*/ limit = (int/*64*/)OS.SendMessage (hwndText, OS.EM_GETLIMITTEXT, 0, 0) & 0x7fffffff;
+ OS.SendMessage (hwndText, OS.EM_SETLIMITTEXT, limit + Math.min (nSegments, LIMIT - limit), 0);
+ length += nSegments;
+ char [] newChars = new char [length + 1];
+ int charCount = 0, segmentCount = 0;
+ char defaultSeparator = getOrientation () == SWT.RIGHT_TO_LEFT ? RTL_MARK : LTR_MARK;
+ while (charCount < length) {
+ if (segmentCount < nSegments && charCount - segmentCount == segments [segmentCount]) {
+ char separator = segmentsChars != null && segmentsChars.length > segmentCount ? segmentsChars [segmentCount] : defaultSeparator;
+ newChars [charCount++] = separator;
+ segmentCount++;
+ } else if (string != null) {
+ newChars [charCount] = string.charAt (charCount++ - segmentCount);
+ }
+ }
+ while (segmentCount < nSegments) {
+ segments [segmentCount] = charCount - segmentCount;
+ char separator = segmentsChars != null && segmentsChars.length > segmentCount ? segmentsChars [segmentCount] : defaultSeparator;
+ newChars [charCount++] = separator;
+ segmentCount++;
+ }
+ /* Get the current selection */
+ int [] start = new int [1], end = new int [1];
+ OS.SendMessage (hwndText, OS.EM_GETSEL, start, end);
+ if (!OS.IsUnicode && OS.IsDBLocale) {
+ start [0] = mbcsToWcsPos (start [0]);
+ end [0] = mbcsToWcsPos (end [0]);
+ }
+ boolean oldIgnoreCharacter = ignoreCharacter, oldIgnoreModify = ignoreModify;
+ ignoreCharacter = ignoreModify = true;
+ /*
+ * SetWindowText empties the undo buffer and disables undo menu item.
+ * Sending OS.EM_REPLACESEL message instead.
+ */
+ newChars [length] = 0;
+ buffer = new TCHAR (cp, newChars, false);
+ OS.SendMessage (hwndText, OS.EM_SETSEL, 0, -1);
+ long /*int*/ undo = OS.SendMessage (hwndText, OS.EM_CANUNDO, 0, 0);
+ OS.SendMessage (hwndText, OS.EM_REPLACESEL, undo, buffer);
+ /* Restore selection */
+ start [0] = translateOffset (start [0]);
+ end [0] = translateOffset (end [0]);
+ if (!OS.IsUnicode && OS.IsDBLocale) {
+ start [0] = wcsToMbcsPos (start [0]);
+ end [0] = wcsToMbcsPos (end [0]);
+ }
+ OS.SendMessage (hwndText, OS.EM_SETSEL, start [0], end [0]);
+ ignoreCharacter = oldIgnoreCharacter;
+ ignoreModify = oldIgnoreModify;
+}
+
+void applyListSegments () {
+ int count = (int)/*64*/OS.SendMessage (handle, OS.CB_GETCOUNT, 0, 0);
+ if (count == OS.CB_ERR) return;
+ boolean add = items.length != count;
+ if (add) items = new String [count];
+ int index = items.length;
+ int selection = OS.CB_ERR;
+ int cp = getCodePage ();
+ String string;
+ TCHAR buffer;
+ if (!noSelection) {
+ selection = (int)/*64*/OS.SendMessage (handle, OS.CB_GETCURSEL, 0, 0);
+ }
+ while (index-- > 0) {
+ buffer = null;
+ if (add) {
+ int length = (int)/*64*/OS.SendMessage (handle, OS.CB_GETLBTEXTLEN, index, 0);
+ if (length == OS.CB_ERR) error (SWT.ERROR);
+ buffer = new TCHAR (cp, length + 1);
+ if (OS.SendMessage (handle, OS.CB_GETLBTEXT, index, buffer) == OS.CB_ERR) return;
+ items [index] = string = buffer.toString (0, length);
+ } else {
+ string = items [index];
+ }
+ if (OS.SendMessage (handle, OS.CB_DELETESTRING, index, 0) == OS.CB_ERR) return;
+ if (buffer == null) buffer = new TCHAR (cp, string, true);
+ if (OS.SendMessage (handle, OS.CB_INSERTSTRING, index, buffer) == OS.CB_ERR) return;
+ }
+ if (selection != OS.CB_ERR) {
+ OS.SendMessage (handle, OS.CB_SETCURSEL, selection, 0);
+ }
+}
+
long /*int*/ callWindowProc (long /*int*/ hwnd, int msg, long /*int*/ wParam, long /*int*/ lParam) {
if (handle == 0) return 0;
if (hwnd == handle) {
@@ -370,6 +537,55 @@
return style;
}
+void clearSegments (boolean applyText) {
+ if (clearSegmentsCount++ != 0) return;
+ if (segments == null) return;
+ int nSegments = segments.length;
+ if (nSegments == 0) return;
+ long /*int*/ hwndText = OS.GetDlgItem (handle, CBID_EDIT);
+ int/*64*/ limit = (int/*64*/)OS.SendMessage (hwndText, OS.EM_GETLIMITTEXT, 0, 0) & 0x7fffffff;
+ if (limit < LIMIT) {
+ OS.SendMessage (hwndText, OS.EM_SETLIMITTEXT, Math.max (1, limit - nSegments), 0);
+ }
+ if (!applyText) {
+ segments = null;
+ return;
+ }
+ boolean oldIgnoreCharacter = ignoreCharacter, oldIgnoreModify = ignoreModify;
+ ignoreCharacter = ignoreModify = true;
+ int length = OS.GetWindowTextLength (hwndText);
+ int cp = getCodePage ();
+ TCHAR buffer = new TCHAR (cp, length + 1);
+ if (length > 0) OS.GetWindowText (hwndText, buffer, length + 1);
+ buffer = deprocessText (buffer, 0, -1, true);
+ /* Get the current selection */
+ int [] start = new int [1], end = new int [1];
+ OS.SendMessage (hwndText, OS.EM_GETSEL, start, end);
+ if (!OS.IsUnicode && OS.IsDBLocale) {
+ start [0] = mbcsToWcsPos (start[0]);
+ end [0]= mbcsToWcsPos (end [0]);
+ }
+ start [0] = untranslateOffset (start [0]);
+ end [0] = untranslateOffset (end[0]);
+
+ segments = null;
+ /*
+ * SetWindowText empties the undo buffer and disables undo in the context
+ * menu. Sending OS.EM_REPLACESEL message instead.
+ */
+ OS.SendMessage (hwndText, OS.EM_SETSEL, 0, -1);
+ long /*int*/ undo = OS.SendMessage (hwndText, OS.EM_CANUNDO, 0, 0);
+ OS.SendMessage (hwndText, OS.EM_REPLACESEL, undo, buffer);
+ /* Restore selection */
+ if (!OS.IsUnicode && OS.IsDBLocale) {
+ start [0] = wcsToMbcsPos (start [0]);
+ end [0] = wcsToMbcsPos (end [0]);
+ }
+ OS.SendMessage (hwndText, OS.EM_SETSEL, start [0], end [0]);
+ ignoreCharacter = oldIgnoreCharacter;
+ ignoreModify = oldIgnoreModify;
+}
+
/**
* Sets the selection in the receiver's text field to an empty
* selection starting just before the first character. If the
@@ -584,6 +800,43 @@
return OS.GetSysColor (OS.COLOR_WINDOW);
}
+TCHAR deprocessText (TCHAR text, int start, int end, boolean terminate) {
+ if (text == null || segments == null) return text;
+ int length = text.length();
+ if (length == 0) return text;
+ int nSegments = segments.length;
+ if (nSegments == 0) return text;
+ char [] chars;
+ if (start < 0) start = 0;
+ if (OS.IsUnicode) {
+ chars = text.chars;
+ if (text.chars [length - 1] == 0) length--;
+ } else {
+ chars = new char [length];
+ length = OS.MultiByteToWideChar (getCodePage (), OS.MB_PRECOMPOSED, text.bytes, length, chars, length);
+ }
+ if (end == -1) end = length;
+ if (end > segments [0] && start <= segments [nSegments - 1]) {
+ int nLeadSegments = 0;
+ while (start - nLeadSegments > segments [nLeadSegments]) nLeadSegments++;
+ int segmentCount = nLeadSegments;
+ for (int i = start; i < end; i++) {
+ if (segmentCount < nSegments && i - segmentCount == segments [segmentCount]) {
+ ++segmentCount;
+ } else {
+ chars [i - segmentCount + nLeadSegments] = chars [i];
+ }
+ }
+ length = end - start - segmentCount + nLeadSegments;
+ }
+ if (start != 0 || end != length) {
+ char [] newChars = new char [length];
+ System.arraycopy(chars, start, newChars, 0, length);
+ return new TCHAR (getCodePage (), newChars, terminate);
+ }
+ return text;
+}
+
void deregister () {
super.deregister ();
long /*int*/ hwndText = OS.GetDlgItem (handle, CBID_EDIT);
@@ -611,6 +864,8 @@
OS.SendMessage (handle, OS.CB_SETCURSEL, -1, 0);
sendEvent (SWT.Modify);
// widget could be disposed at this point
+ clearSegments (false);
+ clearSegmentsCount--;
}
/**
@@ -632,6 +887,8 @@
OS.SendMessage (handle, OS.CB_SETCURSEL, -1, 0);
sendEvent (SWT.Modify);
// widget could be disposed at this point
+ clearSegments (false);
+ clearSegmentsCount--;
}
boolean dragDetect (long /*int*/ hwnd, int x, int y, boolean filter, boolean [] detect, boolean [] consume) {
@@ -682,7 +939,7 @@
* If EM_POSFROMCHAR fails for any other reason, return
* pixel coordinates (0,0).
*/
- int position = getCaretPosition ();
+ int position = translateOffset (getCaretPosition ());
long /*int*/ hwndText = OS.GetDlgItem (handle, CBID_EDIT);
long /*int*/ caretPos = OS.SendMessage (hwndText, OS.EM_POSFROMCHAR, position, 0);
if (caretPos == -1) {
@@ -777,7 +1034,7 @@
if (!OS.IsUnicode && OS.IsDBLocale) {
caret = mbcsToWcsPos (caret);
}
- return caret;
+ return untranslateOffset (caret);
}
/**
@@ -800,6 +1057,7 @@
checkWidget ();
int length = (int)/*64*/OS.SendMessage (handle, OS.CB_GETLBTEXTLEN, index, 0);
if (length != OS.CB_ERR) {
+ if (hooks (SWT.Segments) || filters (SWT.Segments)) return items [index];
TCHAR buffer = new TCHAR (getCodePage (), length + 1);
int result = (int)/*64*/OS.SendMessage (handle, OS.CB_GETLBTEXT, index, buffer);
if (result != OS.CB_ERR) return buffer.toString (0, length);
@@ -863,9 +1121,15 @@
*/
public String [] getItems () {
checkWidget ();
- int count = getItemCount ();
- String [] result = new String [count];
- for (int i=0; i<count; i++) result [i] = getItem (i);
+ String [] result;
+ if (hooks (SWT.Segments) || filters (SWT.Segments)) {
+ result = new String [items.length];
+ System.arraycopy (items, 0, result, 0, items.length);
+ } else {
+ int count = getItemCount ();
+ result = new String [count];
+ for (int i=0; i<count; i++) result [i] = getItem (i);
+ }
return result;
}
@@ -939,6 +1203,50 @@
return super.getOrientation ();
}
+Event getSegments (String string) {
+ if (!hooks (SWT.Segments) && !filters (SWT.Segments)) return null;
+ Event event = new Event ();
+ event.text = string;
+ sendEvent (SWT.Segments, event);
+ if (event.segments != null) {
+ for (int i = 1, segmentCount = event.segments.length, lineLength = string == null ? 0 : string.length(); i < segmentCount; i++) {
+ if (event.segments[i] < event.segments[i - 1] || event.segments[i] > lineLength) {
+ SWT.error (SWT.ERROR_INVALID_ARGUMENT);
+ }
+ }
+ }
+ return event;
+}
+
+String getSegmentsText (String text, Event event) {
+ if (text == null || event == null) return text;
+ int[] segments = event.segments;
+ if (segments == null) return text;
+ int nSegments = segments.length;
+ if (nSegments == 0) return text;
+ char[] segmentsChars = /*event == null ? this.segmentsChars : */event.segmentsChars;
+ int length = text.length();
+ char[] oldChars = new char[length];
+ text.getChars (0, length, oldChars, 0);
+ char[] newChars = new char[length + nSegments];
+ int charCount = 0, segmentCount = 0;
+ char defaultSeparator = getOrientation () == SWT.RIGHT_TO_LEFT ? RTL_MARK : LTR_MARK;
+ while (charCount < length) {
+ if (segmentCount < nSegments && charCount == segments[segmentCount]) {
+ char separator = segmentsChars != null && segmentsChars.length > segmentCount ? segmentsChars[segmentCount] : defaultSeparator;
+ newChars[charCount + segmentCount++] = separator;
+ } else {
+ newChars[charCount + segmentCount] = oldChars[charCount++];
+ }
+ }
+ while (segmentCount < nSegments) {
+ segments[segmentCount] = charCount;
+ char separator = segmentsChars != null && segmentsChars.length > segmentCount ? segmentsChars[segmentCount] : defaultSeparator;
+ newChars[charCount + segmentCount++] = separator;
+ }
+ return new String(newChars, 0, newChars.length);
+}
+
/**
* Returns a <code>Point</code> whose x coordinate is the
* character position representing the start of the selection
@@ -969,7 +1277,7 @@
start [0] = mbcsToWcsPos (start [0]);
end [0] = mbcsToWcsPos (end [0]);
}
- return new Point (start [0], end [0]);
+ return new Point (untranslateOffset (start [0]), untranslateOffset (end [0]));
}
/**
@@ -1007,6 +1315,11 @@
if (length == 0) return "";
TCHAR buffer = new TCHAR (getCodePage (), length + 1);
OS.GetWindowText (handle, buffer, length + 1);
+ if (segments != null) {
+ buffer = deprocessText (buffer, 0, -1, false);
+ return buffer.toString ();
+ }
+
return buffer.toString (0, length);
}
@@ -1051,7 +1364,9 @@
checkWidget ();
long /*int*/ hwndText = OS.GetDlgItem (handle, CBID_EDIT);
if (hwndText == 0) return LIMIT;
- return (int)/*64*/OS.SendMessage (hwndText, OS.EM_GETLIMITTEXT, 0, 0) & 0x7FFFFFFF;
+ int/*64*/ limit = (int)/*64*/OS.SendMessage (hwndText, OS.EM_GETLIMITTEXT, 0, 0) & 0x7FFFFFFF;
+ if (segments != null && limit < LIMIT) limit = Math.max (1, limit - segments.length);
+ return limit;
}
/**
@@ -1399,6 +1714,41 @@
/**
* Removes the listener from the collection of listeners who will
+ * be notified when the receiver's text is modified.
+ *
+ * @param listener the listener which should no longer be notified
+ *
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ *
+ * @see SegmentEvent
+ * @see SegmentListener
+ * @see #addSegmentListener
+ *
+ * @since 3.103
+ */
+public void removeSegmentListener (SegmentListener listener) {
+ checkWidget ();
+ if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
+ eventTable.unhook (SWT.Segments, listener);
+ int selection = OS.CB_ERR;
+ if (!noSelection) {
+ selection = (int)/*64*/OS.SendMessage (handle, OS.CB_GETCURSEL, 0, 0);
+ }
+ clearSegments (true);
+ applyEditSegments ();
+ applyListSegments ();
+ if (selection != OS.CB_ERR) {
+ OS.SendMessage (handle, OS.CB_SETCURSEL, selection, 0);
+ }
+}
+/**
+ * Removes the listener from the collection of listeners who will
* be notified when the user changes the receiver's selection.
*
* @param listener the listener which should no longer be notified
@@ -1862,7 +2212,7 @@
public void setSelection (Point selection) {
checkWidget ();
if (selection == null) error (SWT.ERROR_NULL_ARGUMENT);
- int start = selection.x, end = selection.y;
+ int start = translateOffset (selection.x), end = translateOffset (selection.y);
if (!OS.IsUnicode && OS.IsDBLocale) {
start = wcsToMbcsPos (start);
end = wcsToMbcsPos (end);
@@ -1904,6 +2254,7 @@
if (index != -1) select (index);
return;
}
+ clearSegments (false);
int limit = LIMIT;
long /*int*/ hwndText = OS.GetDlgItem (handle, CBID_EDIT);
if (hwndText != 0) {
@@ -1912,6 +2263,7 @@
if (string.length () > limit) string = string.substring (0, limit);
TCHAR buffer = new TCHAR (getCodePage (), string, true);
if (OS.SetWindowText (handle, buffer)) {
+ applyEditSegments ();
sendEvent (SWT.Modify);
// widget could be disposed at this point
}
@@ -1940,7 +2292,11 @@
public void setTextLimit (int limit) {
checkWidget ();
if (limit == 0) error (SWT.ERROR_CANNOT_BE_ZERO);
- OS.SendMessage (handle, OS.CB_LIMITTEXT, limit, 0);
+ if (segments != null && limit > 0) {
+ OS.SendMessage (handle, OS.CB_LIMITTEXT, limit + Math.min (segments.length, LIMIT - limit), 0);
+ } else {
+ OS.SendMessage (handle, OS.CB_LIMITTEXT, limit, 0);
+ }
}
void setToolTipText (Shell shell, String string) {
@@ -1988,6 +2344,14 @@
}
}
+int translateOffset (int offset) {
+ if (segments == null) return offset;
+ for (int i = 0, nSegments = segments.length; i < nSegments && offset - i >= segments[i]; i++) {
+ offset++;
+ }
+ return offset;
+}
+
boolean translateTraversal (MSG msg) {
/*
* When the combo box is dropped down, allow return
@@ -2038,6 +2402,14 @@
}
}
+int untranslateOffset (int offset) {
+ if (segments == null) return offset;
+ for (int i = 0, nSegments = segments.length; i < nSegments && offset > segments[i]; i++) {
+ offset--;
+ }
+ return offset;
+}
+
void updateDropDownHeight () {
/*
* Feature in Windows. If the combo box has the CBS_DROPDOWN
@@ -2131,6 +2503,8 @@
event.start = mbcsToWcsPos (start);
event.end = mbcsToWcsPos (end);
}
+ event.start = untranslateOffset (event.start);
+ event.end = untranslateOffset (event.end);
/*
* It is possible (but unlikely), that application
* code could have disposed the widget in the verify
@@ -2186,11 +2560,18 @@
long /*int*/ hwndList = OS.GetDlgItem (handle, CBID_LIST);
if ((hwndText != 0 && hwnd == hwndText) || (hwndList != 0 && hwnd == hwndList)) {
LRESULT result = null;
+ boolean processSegments = false, redraw = false;
switch (msg) {
/* Keyboard messages */
- case OS.WM_CHAR: result = wmChar (hwnd, wParam, lParam); break;
+ case OS.WM_CHAR:
+ processSegments = (hooks (SWT.Segments) || filters (SWT.Segments)) && !ignoreCharacter && OS.GetKeyState (OS.VK_CONTROL) >= 0 && OS.GetKeyState (OS.VK_MENU) >= 0;
+ result = wmChar (hwnd, wParam, lParam);
+ break;
case OS.WM_IME_CHAR: result = wmIMEChar (hwnd, wParam, lParam); break;
- case OS.WM_KEYDOWN: result = wmKeyDown (hwnd, wParam, lParam); break;
+ case OS.WM_KEYDOWN:
+ processSegments = wParam == OS.VK_DELETE && (hooks (SWT.Segments) || filters (SWT.Segments));
+ result = wmKeyDown (hwnd, wParam, lParam);
+ break;
case OS.WM_KEYUP: result = wmKeyUp (hwnd, wParam, lParam); break;
case OS.WM_SYSCHAR: result = wmSysChar (hwnd, wParam, lParam); break;
case OS.WM_SYSKEYDOWN: result = wmSysKeyDown (hwnd, wParam, lParam); break;
@@ -2220,13 +2601,19 @@
/* Menu messages */
case OS.WM_CONTEXTMENU: result = wmContextMenu (hwnd, wParam, lParam); break;
-
+
/* Clipboard messages */
+ case OS.EM_CANUNDO:
+ if (hooks (SWT.Segments) || filters (SWT.Segments)) return 0;
+ break;
+ case OS.WM_UNDO:
+ case OS.EM_UNDO:
+ if (hooks (SWT.Segments) || filters (SWT.Segments)) return 0;
+ case OS.WM_COPY:
case OS.WM_CLEAR:
case OS.WM_CUT:
case OS.WM_PASTE:
- case OS.WM_UNDO:
- case OS.EM_UNDO:
+ processSegments = hooks (SWT.Segments) || filters (SWT.Segments);
case OS.WM_SETTEXT:
if (hwnd == hwndText) {
result = wmClipboard (hwnd, msg, wParam, lParam);
@@ -2234,33 +2621,120 @@
break;
}
if (result != null) return result.value;
+
+ if (processSegments) {
+ if (getDrawing () && OS.IsWindowVisible (hwndText)) {
+ redraw = true;
+ OS.DefWindowProc (hwndText, OS.WM_SETREDRAW, 0, 0);
+ }
+ clearSegments (true);
+ long /*int*/ code = callWindowProc (hwnd, msg, wParam, lParam);
+ applyEditSegments ();
+ if (redraw) {
+ OS.DefWindowProc (hwndText, OS.WM_SETREDRAW, 1, 0);
+ OS.InvalidateRect (hwndText, null, true);
+ }
+ return code;
+ }
return callWindowProc (hwnd, msg, wParam, lParam);
}
}
- if (msg == OS.CB_SETCURSEL) {
- if ((style & SWT.READ_ONLY) != 0) {
- if (hooks (SWT.Verify) || filters (SWT.Verify)) {
- String oldText = getText (), newText = null;
- if (wParam == -1) {
- newText = "";
- } else {
- if (0 <= wParam && wParam < getItemCount ()) {
- newText = getItem ((int)/*64*/wParam);
+ switch (msg) {
+ case OS.CB_SETCURSEL: {
+ long /*int*/ code = OS.CB_ERR;
+ int index = (int)/*64*/ wParam;
+ if ((style & SWT.READ_ONLY) != 0) {
+ if (hooks (SWT.Verify) || filters (SWT.Verify)) {
+ String oldText = getText (), newText = null;
+ if (wParam == -1) {
+ newText = "";
+ } else {
+ if (0 <= wParam && wParam < getItemCount ()) {
+ newText = getItem ((int)/*64*/wParam);
+ }
}
- }
- if (newText != null && !newText.equals (oldText)) {
- int length = OS.GetWindowTextLength (handle);
- oldText = newText;
- newText = verifyText (newText, 0, length, null);
- if (newText == null) return 0;
- if (!newText.equals (oldText)) {
- int index = indexOf (newText);
- if (index != -1 && index != wParam) {
- return callWindowProc (handle, OS.CB_SETCURSEL, index, lParam);
+ if (newText != null && !newText.equals (oldText)) {
+ int length = OS.GetWindowTextLength (handle);
+ oldText = newText;
+ newText = verifyText (newText, 0, length, null);
+ if (newText == null) return 0;
+ if (!newText.equals (oldText)) {
+ index = indexOf (newText);
+ if (index != -1 && index != wParam) {
+ return callWindowProc (handle, OS.CB_SETCURSEL, index, lParam);
+ }
}
}
}
}
+ if (hooks (SWT.Segments) || filters (SWT.Segments)) {
+ code = super.windowProc (hwnd, msg, wParam, lParam);
+ if (!(code == OS.CB_ERR || code == OS.CB_ERRSPACE)) {
+ segments = null;
+ Event event = getSegments (items [index]);
+ if (event != null) segments = event.segments;
+ return code;
+ }
+ }
+ }
+ case OS.CB_ADDSTRING:
+ case OS.CB_INSERTSTRING:
+ case OS.CB_FINDSTRINGEXACT:
+ if (lParam != 0 && (hooks (SWT.Segments) || filters (SWT.Segments))) {
+ long /*int*/ code = OS.CB_ERR;
+ int length = OS.IsUnicode ? OS.wcslen (lParam) : OS.strlen (lParam);
+ TCHAR buffer = new TCHAR (getCodePage (), length);
+ OS.MoveMemory (buffer, lParam, buffer.length () * TCHAR.sizeof);
+ String string = buffer.toString (0, length);
+ Event event = getSegments (string);
+ if (event != null && event.segments != null) {
+ buffer = new TCHAR (getCodePage (), getSegmentsText (string, event), true);
+ long /*int*/ hHeap = OS.GetProcessHeap ();
+ length = buffer.length() * TCHAR.sizeof;
+ long /*int*/ pszText = OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, length);
+ OS.MoveMemory (pszText, buffer, length);
+ code = super.windowProc (hwnd, msg, wParam, pszText);
+ OS.HeapFree (hHeap, 0, pszText);
+ }
+ if (msg == OS.CB_ADDSTRING || msg == OS.CB_INSERTSTRING) {
+ int index = msg == OS.CB_ADDSTRING ? items.length : (int)/*64*/ wParam;
+ String [] newItems = new String [items.length + 1];
+ System.arraycopy (items, 0, newItems, 0, index);
+ newItems [index] = string;
+ System.arraycopy (items, index, newItems, index + 1, items.length - index);
+ items = newItems;
+ }
+ if (code != OS.CB_ERR && code != OS.CB_ERRSPACE) return code;
+ }
+ break;
+ case OS.CB_DELETESTRING: {
+ if (hooks (SWT.Segments) || filters (SWT.Segments)) {
+ long /*int*/ code = super.windowProc (hwnd, msg, wParam, lParam);
+ if (code != OS.CB_ERR && code != OS.CB_ERRSPACE) {
+ int index = (int)/*64*/ wParam;
+ String [] newItems = new String [items.length - 1];
+ System.arraycopy (items, 0, newItems, 0, index);
+ System.arraycopy (items, index + 1, newItems, index, items.length - index - 1);
+ items = newItems;
+ if (!noSelection) {
+ index = (int)/*64*/OS.SendMessage (handle, OS.CB_GETCURSEL, 0, 0);
+ if (index == wParam) {
+ clearSegments (false);
+ applyEditSegments ();
+ }
+ }
+ }
+ return code;
+ }
+ break;
+ }
+ case OS.CB_RESETCONTENT: {
+ if (hooks (SWT.Segments) || filters (SWT.Segments)) {
+ if (items.length > 0) items = new String [0];
+ clearSegments (false);
+ applyEditSegments ();
+ }
+ break;
}
}
return super.windowProc (hwnd, msg, wParam, lParam);
@@ -2489,7 +2963,7 @@
case OS.WM_CLEAR:
case OS.WM_CUT:
OS.SendMessage (hwndText, OS.EM_GETSEL, start, end);
- if (start [0] != end [0]) {
+ if (untranslateOffset (start [0]) != untranslateOffset (end [0])) {
newText = "";
call = true;
}
@@ -2637,6 +3111,9 @@
}
OS.SetWindowLong (hwnd, OS.GWL_EXSTYLE, bits1);
OS.SetWindowLong (hwnd, OS.GWL_STYLE, bits2);
+ } else if (hooks (SWT.Segments) || filters (SWT.Segments)) {
+ clearSegments (true);
+ applyEditSegments ();
}
break;
}
@@ -2682,12 +3159,38 @@
LRESULT result = super.wmKeyDown (hwnd, wParam, lParam);
if (result != null) return result;
ignoreDefaultSelection = false;
- if (wParam == OS.VK_RETURN) {
- if ((style & SWT.DROP_DOWN) != 0) {
- if (OS.SendMessage (handle, OS.CB_GETDROPPEDSTATE, 0, 0) != 0) {
- ignoreDefaultSelection = true;
+ switch ((int)/*64*/wParam) {
+ case OS.VK_LEFT:
+ case OS.VK_UP:
+ case OS.VK_RIGHT:
+ case OS.VK_DOWN:
+ if (segments != null) {
+ long /*int*/ code = 0;
+ int [] start = new int [1], end = new int [1], newStart = new int [1], newEnd = new int [1];
+ OS.SendMessage (handle, OS.CB_GETEDITSEL, start, end);
+ while (true) {
+ code = callWindowProc (hwnd, OS.WM_KEYDOWN, wParam, lParam);
+ OS.SendMessage (handle, OS.CB_GETEDITSEL, newStart, newEnd);
+ if (newStart [0] != start [0]) {
+ if (untranslateOffset (newStart [0]) != untranslateOffset (start [0])) break;
+ } else if (newEnd [0] != end [0]) {
+ if (untranslateOffset (newEnd [0]) != untranslateOffset (end [0])) break;
+ } else {
+ break;
+ }
+ start [0] = newStart [0];
+ end [0] = newEnd [0];
+ }
+ result = code == 0 ? LRESULT.ZERO : new LRESULT (code);
}
- }
+ break;
+ case OS.VK_RETURN:
+ if ((style & SWT.DROP_DOWN) != 0) {
+ if (OS.SendMessage (handle, OS.CB_GETDROPPEDSTATE, 0, 0) != 0) {
+ ignoreDefaultSelection = true;
+ }
+ }
+ break;
}
return result;
}
diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Combo.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Combo.java
index 667180e..6900bca 100644
--- a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Combo.java
+++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Combo.java
@@ -16,6 +16,8 @@
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SegmentEvent;
+import org.eclipse.swt.events.SegmentListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
@@ -883,6 +885,180 @@
consistencyEvent(10, 5, 20, 10, ConsistencyUtility.MOUSE_DRAG);
}
+public void test_consistency_Segments () {
+ final SegmentListener sl1 = new SegmentListener() {
+ public void getSegments(SegmentEvent event) {
+ if ((event.lineText.length() & 1) == 1) {
+ event.segments = new int [] {1, event.lineText.length()};
+ event.segmentsChars = null;
+ } else {
+ event.segments = new int [] {0, 0, event.lineText.length()};
+ event.segmentsChars = new char [] {':', '<', '>'};
+ }
+ listenerCalled = true;
+ }
+ };
+ try {
+ combo.addSegmentListener(null);
+ fail("No exception thrown for addSegmentListener(null)");
+ }
+ catch (IllegalArgumentException e) {
+ }
+ combo.addSegmentListener(sl1);
+ doSegmentsTest(true);
+
+ combo.addSegmentListener(sl1);
+ doSegmentsTest(true);
+
+ combo.removeSegmentListener(sl1);
+ doSegmentsTest(true);
+
+ combo.removeSegmentListener(sl1);
+ combo.setText(combo.getText());
+ doSegmentsTest(false);
+}
+
+private void doSegmentsTest (boolean isListening) {
+ String[] items = { "first", "second", "third" };
+ String string = "1234";
+
+ // Test setItems
+ combo.setItems(items);
+ assertEquals(isListening, listenerCalled);
+ listenerCalled = false;
+ assertArrayEquals(items, combo.getItems());
+
+ // Test setText
+ combo.setText(string);
+ assertEquals(isListening, listenerCalled);
+ listenerCalled = false;
+ assertEquals(string, combo.getText());
+
+ // Test limit, getItem, indexOf, select
+ int limit = string.length() - 1;
+ combo.setTextLimit(limit);
+ assertEquals(limit, combo.getTextLimit());
+ combo.setText(string);
+ assertEquals(string.substring(0, limit), combo.getText());
+
+ combo.setTextLimit(Combo.LIMIT);
+ combo.setText(string);
+ assertEquals(string, combo.getText());
+
+ int count = items.length;
+ for (int i = 0; i < count; i++) {
+ assertEquals(items[i], combo.getItem(i));
+ assertEquals(i, combo.indexOf(items[i]));
+
+ combo.select(i);
+ listenerCalled = false;
+ assertEquals(i, combo.getSelectionIndex());
+ assertEquals(items[i], combo.getText());
+ assertFalse(listenerCalled);
+
+ String currentText = combo.getText();
+ combo.deselect(i ^ 1);
+ assertEquals(currentText, combo.getText());
+
+ combo.deselect(i);
+ assertEquals("", combo.getText());
+ }
+ for (int i = 0; i < count; i++) {
+ combo.setText(combo.getItem(i));
+ assertEquals(items[i], combo.getText());
+ }
+ listenerCalled = false;
+
+ limit = 2;
+ combo.setTextLimit(limit);
+ assertEquals(limit, combo.getTextLimit());
+ combo.setText(string);
+ assertEquals(string.substring(0, limit), combo.getText());
+
+ combo.select(1);
+ assertEquals(limit, combo.getTextLimit());
+
+ combo.remove(1);
+ assertEquals(limit, combo.getTextLimit());
+ assertTrue(combo.getItemCount() == --count);
+
+ combo.add(items[1], 1);
+ assertEquals(limit, combo.getTextLimit());
+ assertTrue(combo.getItemCount() == ++count);
+
+ combo.deselectAll();
+ assertEquals(limit, combo.getTextLimit());
+
+ combo.remove(1, 2);
+ count -=2;
+ assertEquals(limit, combo.getTextLimit());
+ assertTrue(combo.getItemCount() == count);
+
+ combo.removeAll();
+ assertEquals(limit, combo.getTextLimit());
+ assertTrue(combo.getItemCount() == 0);
+
+ combo.setItems(items);
+ count = items.length;
+ assertEquals(limit, combo.getTextLimit());
+
+ combo.setTextLimit(Combo.LIMIT);
+ combo.setText(string);
+ assertEquals(string, combo.getText());
+
+ // Test add item
+ String item = "forth";
+ combo.add(item);
+ assertEquals(isListening, listenerCalled);
+ listenerCalled = false;
+ assertEquals(item, combo.getItem(count++));
+ assertTrue(combo.getItemCount() == count);
+
+ combo.select(1);
+
+ // Test remove item by name
+ combo.remove(items[1]);
+ assertEquals(--count, combo.getItemCount());
+ assertEquals(1, combo.indexOf(items[2]));
+
+ // Test set item item by name
+ combo.setItem(1, "second");
+ assertEquals(count, combo.getItemCount());
+ assertEquals(1, combo.indexOf(items[1]));
+
+ combo.setText(string);
+ listenerCalled = false;
+
+ // Test selection, copy and paste
+ Point pt = new Point(1, 3);
+ combo.setSelection(pt);
+ assertEquals(pt, combo.getSelection());
+ assertFalse(listenerCalled);
+ combo.copy();
+ assertEquals(isListening, listenerCalled);
+ listenerCalled = false;
+
+ String substr = string.substring(pt.x, pt.y);
+ pt.x = pt.y = 1;
+ combo.setSelection(pt);
+ combo.paste();
+ assertEquals(isListening, listenerCalled);
+ listenerCalled = false;
+
+ assertEquals(string.substring(0, pt.x) + substr + string.substring(pt.y), combo.getText());
+ pt.x = pt.y = pt.x + substr.length();
+ assertEquals(pt, combo.getSelection());
+
+ // Test cut
+ pt.x -= 2;
+ combo.setSelection(pt);
+ assertEquals(substr, combo.getText().substring(pt.x, pt.y));
+ combo.cut();
+ assertEquals(isListening, listenerCalled);
+ listenerCalled = false;
+ assertEquals(string, combo.getText());
+}
+
}