Mac OS: added support for the text input feature introduced in OS 10.7 "Lion" where pressing and holding

some key opens a window with possible accented characters. This feature is used by the Fl_Input_ and
Fl_Text_Editor widgets. User-defined text input widgets can optionally use this feature, but the default
behavior is to not use it. Fl_Secret_Input turns it off, for example.

git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3@9792 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
This commit is contained in:
Manolo Gouy 2013-01-13 15:25:37 +00:00
parent e042966ae7
commit 482c4a5e0a
9 changed files with 93 additions and 35 deletions

View File

@ -147,7 +147,7 @@ public: // should be private!
#ifdef __APPLE__ #ifdef __APPLE__
static int marked_text_length(void); // returns length of marked text static int marked_text_length(void); // returns length of marked text
static void reset_marked_text(); // resets marked text static void reset_marked_text(); // resets marked text
static void insertion_point_location(int x, int y); // sets window coordinates of insertion point static void insertion_point_location(int x, int y, int height); // sets window coordinates & height of insertion point
#endif #endif
#endif #endif
/** /**

View File

@ -171,6 +171,7 @@ protected:
GROUP_RELATIVE = 1<<16, ///< position this widget relative to the parent group, not to the window GROUP_RELATIVE = 1<<16, ///< position this widget relative to the parent group, not to the window
COPIED_TOOLTIP = 1<<17, ///< the widget tooltip is internally copied, its destruction is handled by the widget COPIED_TOOLTIP = 1<<17, ///< the widget tooltip is internally copied, its destruction is handled by the widget
FULLSCREEN = 1<<18, ///< a fullscreen window (Fl_Window) FULLSCREEN = 1<<18, ///< a fullscreen window (Fl_Window)
MAC_USE_ACCENTS_MENU = 1<<19, ///< On the Mac OS platform, pressing and holding a key on the keyboard opens an accented-character menu window (Fl_Input_, Fl_Text_Editor)
// (space for more flags) // (space for more flags)
USERFLAG3 = 1<<29, ///< reserved for 3rd party extensions USERFLAG3 = 1<<29, ///< reserved for 3rd party extensions
USERFLAG2 = 1<<30, ///< reserved for 3rd party extensions USERFLAG2 = 1<<30, ///< reserved for 3rd party extensions
@ -976,6 +977,10 @@ public:
*/ */
virtual class Fl_Gl_Window* as_gl_window() {return 0;} virtual class Fl_Gl_Window* as_gl_window() {return 0;}
/** Returns non zero if MAC_USE_ACCENTS_MENU flag is set, 0 otherwise.
*/
int use_accents_menu() { return flags() & MAC_USE_ACCENTS_MENU; }
/** For back compatibility only. /** For back compatibility only.
\deprecated Use selection_color() instead. \deprecated Use selection_color() instead.
*/ */

View File

@ -132,7 +132,7 @@ public:
static void *get_carbon_function(const char *name); static void *get_carbon_function(const char *name);
static void screen_work_area(int &X, int &Y, int &W, int &H, int n); // compute work area of a given screen static void screen_work_area(int &X, int &Y, int &W, int &H, int n); // compute work area of a given screen
static int next_marked_length; // next length of marked text after current marked text will have been replaced static int next_marked_length; // next length of marked text after current marked text will have been replaced
static int insertion_point_location(int *px, int *py); // computes window coordinates of insertion point static int insertion_point_location(int *px, int *py, int *pheight); // computes window coordinates & height of insertion point
private: private:
static void relink(Fl_Window*, Fl_Window*); static void relink(Fl_Window*, Fl_Window*);
bool subwindow; bool subwindow;

View File

@ -780,12 +780,14 @@ Fl_Float_Input::Fl_Float_Input(int X,int Y,int W,int H,const char *l)
: Fl_Input(X,Y,W,H,l) : Fl_Input(X,Y,W,H,l)
{ {
type(FL_FLOAT_INPUT); type(FL_FLOAT_INPUT);
clear_flag(MAC_USE_ACCENTS_MENU);
} }
Fl_Int_Input::Fl_Int_Input(int X,int Y,int W,int H,const char *l) Fl_Int_Input::Fl_Int_Input(int X,int Y,int W,int H,const char *l)
: Fl_Input(X,Y,W,H,l) { : Fl_Input(X,Y,W,H,l) {
type(FL_INT_INPUT); type(FL_INT_INPUT);
clear_flag(MAC_USE_ACCENTS_MENU);
} }
@ -810,6 +812,7 @@ Fl_Multiline_Output::Fl_Multiline_Output(int X,int Y,int W,int H,const char *l)
Fl_Secret_Input::Fl_Secret_Input(int X,int Y,int W,int H,const char *l) Fl_Secret_Input::Fl_Secret_Input(int X,int Y,int W,int H,const char *l)
: Fl_Input(X,Y,W,H,l) { : Fl_Input(X,Y,W,H,l) {
type(FL_SECRET_INPUT); type(FL_SECRET_INPUT);
clear_flag(MAC_USE_ACCENTS_MENU);
} }
int Fl_Secret_Input::handle(int event) { int Fl_Secret_Input::handle(int event) {

View File

@ -388,6 +388,9 @@ void Fl_Input_::drawtext(int X, int Y, int W, int H) {
} else { } else {
fl_rectf((int)(xpos+curx+0.5), Y+ypos, 2, height); fl_rectf((int)(xpos+curx+0.5), Y+ypos, 2, height);
} }
#ifdef __APPLE__
Fl::insertion_point_location(xpos+curx, Y+ypos+height, height);
#endif
} }
CONTINUE: CONTINUE:
@ -1119,6 +1122,7 @@ Fl_Input_::Fl_Input_(int X, int Y, int W, int H, const char* l)
maximum_size_ = 32767; maximum_size_ = 32767;
shortcut_ = 0; shortcut_ = 0;
set_flag(SHORTCUT_LABEL); set_flag(SHORTCUT_LABEL);
set_flag(MAC_USE_ACCENTS_MENU);
tab_nav(1); tab_nav(1);
} }

View File

@ -2067,6 +2067,9 @@ void Fl_Text_Display::draw_cursor( int X, int Y ) {
if ( X < text_area.x - 1 || X > text_area.x + text_area.w ) if ( X < text_area.x - 1 || X > text_area.x + text_area.w )
return; return;
#ifdef __APPLE__
Fl::insertion_point_location(X, bot, fontHeight);
#endif
/* For cursors other than the block, make them around 2/3 of a character /* For cursors other than the block, make them around 2/3 of a character
width, rounded to an even number of pixels so that X will draw an width, rounded to an even number of pixels so that X will draw an
odd number centered on the stem at x. */ odd number centered on the stem at x. */

View File

@ -73,6 +73,7 @@ Fl_Text_Editor::Fl_Text_Editor(int X, int Y, int W, int H, const char* l)
mCursorOn = 1; mCursorOn = 1;
insert_mode_ = 1; insert_mode_ = 1;
key_bindings = 0; key_bindings = 0;
set_flag(MAC_USE_ACCENTS_MENU);
// handle the default key bindings // handle the default key bindings
add_default_key_bindings(&key_bindings); add_default_key_bindings(&key_bindings);
@ -526,12 +527,8 @@ int Fl_Text_Editor::handle_key() {
} }
#ifdef __APPLE__ #ifdef __APPLE__
if (Fl::marked_text_length()) { if (Fl::marked_text_length()) {
int x, y;
int pos = this->insert_position(); int pos = this->insert_position();
this->buffer()->select(pos - Fl::marked_text_length(), pos); this->buffer()->select(pos - Fl::marked_text_length(), pos);
this->position_to_xy( this->insert_position(), &x, &y);
y += this->textsize();
Fl::insertion_point_location(x, y);
} }
#endif #endif
show_insert_position(); show_insert_position();

View File

@ -1668,9 +1668,14 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi
} }
@interface FLView : NSView <NSTextInput> { @interface FLView : NSView <NSTextInput
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
, NSTextInputClient
#endif
> {
BOOL in_key_event; BOOL in_key_event;
NSInteger identifier; NSInteger identifier;
NSRange selectedRange;
} }
+ (void)prepareEtext:(NSString*)aString; + (void)prepareEtext:(NSString*)aString;
- (id)init; - (id)init;
@ -1949,27 +1954,34 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi
} }
// These functions implement text input. // These functions implement text input.
// On the way to fully support CJK text input, this is the way to go.
- (void)doCommandBySelector:(SEL)aSelector { - (void)doCommandBySelector:(SEL)aSelector {
} }
- (void)insertText:(id)aString { - (void)insertText:(id)aString {
[self insertText:aString replacementRange:NSMakeRange(NSNotFound, 0)];
}
- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange {
NSString *received; NSString *received;
if ([aString isKindOfClass:[NSAttributedString class]]) { if ([aString isKindOfClass:[NSAttributedString class]]) {
received = [(NSAttributedString*)aString string]; received = [(NSAttributedString*)aString string];
} else { } else {
received = (NSString*)aString; received = (NSString*)aString;
} }
//NSLog(@"insertText: received=%@ Fl::marked_text_length()%d",received,Fl::marked_text_length()); /*NSLog(@"insertText=%@ l=%d Fl::marked_text_length()=%d range=%d,%d",
received,strlen([received UTF8String]),Fl::marked_text_length(),replacementRange.location,replacementRange.length);*/
fl_lock_function(); fl_lock_function();
Fl_Window *target = [(FLWindow*)[self window] getFl_Window];
while (replacementRange.length--) { // delete replacementRange.length characters before insertion point
int saved_keysym = Fl::e_keysym;
Fl::e_keysym = FL_BackSpace;
Fl::handle(FL_KEYBOARD, target);
Fl::e_keysym = saved_keysym;
}
[FLView prepareEtext:received]; [FLView prepareEtext:received];
// We can get called outside of key events (e.g., from the character palette, from CJK text input). // We can get called outside of key events (e.g., from the character palette, from CJK text input).
// Transform character palette actions to FL_PASTE events. // Transform character palette actions to FL_PASTE events.
Fl_Window *target = [(FLWindow*)[self window] getFl_Window];
Fl_X::next_marked_length = 0; Fl_X::next_marked_length = 0;
Fl::handle( (in_key_event || Fl::marked_text_length()) ? FL_KEYBOARD : FL_PASTE, target); Fl::handle( (in_key_event || Fl::marked_text_length()) ? FL_KEYBOARD : FL_PASTE, target);
selectedRange = NSMakeRange(100, 0); // 100 is an arbitrary value
// for some reason, with the palette, the window does not redraw until the next mouse move or button push // for some reason, with the palette, the window does not redraw until the next mouse move or button push
// sending a 'redraw()' or 'awake()' does not solve the issue! // sending a 'redraw()' or 'awake()' does not solve the issue!
Fl::flush(); Fl::flush();
@ -1977,6 +1989,10 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi
} }
- (void)setMarkedText:(id)aString selectedRange:(NSRange)newSelection { - (void)setMarkedText:(id)aString selectedRange:(NSRange)newSelection {
[self setMarkedText:aString selectedRange:newSelection replacementRange:NSMakeRange(NSNotFound, 0)];
}
- (void)setMarkedText:(id)aString selectedRange:(NSRange)newSelection replacementRange:(NSRange)replacementRange {
NSString *received; NSString *received;
if ([aString isKindOfClass:[NSAttributedString class]]) { if ([aString isKindOfClass:[NSAttributedString class]]) {
received = [(NSAttributedString*)aString string]; received = [(NSAttributedString*)aString string];
@ -1984,15 +2000,21 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi
received = (NSString*)aString; received = (NSString*)aString;
} }
fl_lock_function(); fl_lock_function();
// This code creates the OS X behaviour of seeing dead keys as things /*NSLog(@"setMarkedText:%@ l=%d newSelection=%d,%d Fl::marked_text_length()=%d replacement=%d,%d",
// are being composed. received, strlen([received UTF8String]), newSelection.location, newSelection.length, Fl::marked_text_length(),
[FLView prepareEtext:received]; replacementRange.location, replacementRange.length);*/
/*NSLog(@"setMarkedText:%@ %d %d Fl::marked_text_length()=%d Fl::e_length=%d",
received, newSelection.location, newSelection.length, Fl::marked_text_length(), Fl::e_length);*/
Fl_Window *target = [(FLWindow*)[self window] getFl_Window]; Fl_Window *target = [(FLWindow*)[self window] getFl_Window];
while (replacementRange.length--) { // delete replacementRange.length characters before insertion point
Fl::e_keysym = FL_BackSpace;
Fl::compose_state = 0;
Fl_X::next_marked_length = 0;
Fl::handle(FL_KEYBOARD, target);
Fl::e_keysym = 'a'; // pretend a letter key was hit
}
[FLView prepareEtext:received];
Fl_X::next_marked_length = Fl::e_length; Fl_X::next_marked_length = Fl::e_length;
Fl::handle(FL_KEYBOARD, target); Fl::handle(FL_KEYBOARD, target);
selectedRange = NSMakeRange(100, newSelection.length);
fl_unlock_function(); fl_unlock_function();
} }
@ -2004,6 +2026,8 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi
} }
- (NSRange)selectedRange { - (NSRange)selectedRange {
Fl_Widget *w = Fl::focus();
if (w && w->use_accents_menu()) return selectedRange;
return NSMakeRange(NSNotFound, 0); return NSMakeRange(NSNotFound, 0);
} }
@ -2018,6 +2042,9 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi
} }
- (NSAttributedString *)attributedSubstringFromRange:(NSRange)aRange { - (NSAttributedString *)attributedSubstringFromRange:(NSRange)aRange {
return [self attributedSubstringForProposedRange:aRange actualRange:NULL];
}
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange {
//NSLog(@"attributedSubstringFromRange: %d %d",aRange.location,aRange.length); //NSLog(@"attributedSubstringFromRange: %d %d",aRange.location,aRange.length);
return nil; return nil;
} }
@ -2027,7 +2054,10 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi
} }
- (NSRect)firstRectForCharacterRange:(NSRange)aRange { - (NSRect)firstRectForCharacterRange:(NSRange)aRange {
//NSLog(@"firstRectForCharacterRange %d %d",aRange.location, aRange.length); return [self firstRectForCharacterRange:aRange actualRange:NULL];
}
- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange {
//NSLog(@"firstRectForCharacterRange %d %d actualRange=%p",aRange.location, aRange.length,actualRange);
NSRect glyphRect; NSRect glyphRect;
fl_lock_function(); fl_lock_function();
Fl_Widget *focus = Fl::focus(); Fl_Widget *focus = Fl::focus();
@ -2035,8 +2065,8 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi
if (!focus) focus = wfocus; if (!focus) focus = wfocus;
glyphRect.size.width = 0; glyphRect.size.width = 0;
int x, y; int x, y, height;
if (Fl_X::insertion_point_location(&x, &y)) { if (Fl_X::insertion_point_location(&x, &y, &height)) {
glyphRect.origin.x = (CGFloat)x; glyphRect.origin.x = (CGFloat)x;
glyphRect.origin.y = (CGFloat)y; glyphRect.origin.y = (CGFloat)y;
} else { } else {
@ -2048,8 +2078,9 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi
glyphRect.origin.x = focus->x(); glyphRect.origin.x = focus->x();
glyphRect.origin.y = focus->y() + focus->h(); glyphRect.origin.y = focus->y() + focus->h();
} }
height = 12;
} }
glyphRect.size.height = 12; glyphRect.size.height = height;
Fl_Window *win = focus->as_window(); Fl_Window *win = focus->as_window();
if (!win) win = focus->window(); if (!win) win = focus->window();
while (win != NULL && win != wfocus) { while (win != NULL && win != wfocus) {
@ -2060,6 +2091,7 @@ static void q_set_window_title(NSWindow *nsw, const char * name, const char *mi
// Convert the rect to screen coordinates // Convert the rect to screen coordinates
glyphRect.origin.y = wfocus->h() - glyphRect.origin.y; glyphRect.origin.y = wfocus->h() - glyphRect.origin.y;
glyphRect.origin = [[self window] convertBaseToScreen:glyphRect.origin]; glyphRect.origin = [[self window] convertBaseToScreen:glyphRect.origin];
if (actualRange) *actualRange = aRange;
fl_unlock_function(); fl_unlock_function();
return glyphRect; return glyphRect;
} }

View File

@ -16,6 +16,12 @@
// http://www.fltk.org/str.php // http://www.fltk.org/str.php
// //
/**
\file Fl_compose.cxx
Utility functions to support text input.
*/
#include <FL/Fl.H> #include <FL/Fl.H>
#include <FL/x.H> #include <FL/x.H>
@ -44,22 +50,27 @@ extern XIC fl_xim_ic;
keys, and del is set to zero. You could insert the text anyways, if keys, and del is set to zero. You could insert the text anyways, if
you don't know what else to do. you don't know what else to do.
<p>On the Mac OS platform, text editing widgets should preferentially signal <p>On the Mac OS platform, text input can involve marked text, that is,
marked text, that is, temporary text replaced by other text during the text temporary text replaced by other text during the input process. This occurs,
input process. Such signaling is usually done underlining marked text. Widgets can call e.g., when using dead keys or when entering CJK characters.
Text editing widgets should preferentially signal
marked text, usually underlining it. Widgets can call
<tt>int Fl::marked_text_length()</tt> <i>after</i> having called Fl::compose(int&) <tt>int Fl::marked_text_length()</tt> <i>after</i> having called Fl::compose(int&)
to obtain the length in bytes of marked text that always finishes at the to obtain the length in bytes of marked text that always finishes at the
current insertion point. It's the widget's task to underline marked text. current insertion point. It's the widget's task to underline marked text.
Widgets should also call <tt>void Fl::reset_marked_text()</tt> when processing FL_UNFOCUS events. Widgets should also call <tt>void Fl::reset_marked_text()</tt> when processing FL_UNFOCUS
Optionally, widgets can also call events. Optionally, widgets can also call
<tt>void Fl::insertion_point_location(int x, int y)</tt> to indicate the window <tt>void Fl::insertion_point_location(int x, int y, int height)</tt> to indicate the window
coordinates of the bottom of the current insertion point. coordinates of the bottom of the current insertion point and the line height.
This way, auxiliary windows that help choosing among alternative characters This way, auxiliary windows that help choosing among alternative characters
appear just below the insertion point. If widgets don't do that, appear just below the insertion point. If widgets don't do that,
auxiliary windows appear at the widget's bottom. The auxiliary windows appear at the widget's bottom. The
Fl_Input and Fl_Text_Editor widgets signal marked text underlining it. Fl_Input and Fl_Text_Editor widgets underline marked text.
If none of this is done by a user-defined text editing widget, complex If none of this is done by a user-defined text editing widget,
(e.g., CJK) text input will work, but will not signal to the user what text is marked. text input will work, but will not signal to the user what text is marked.
Finally, text editing widgets should call <tt>set_flag(MAC_USE_ACCENTS_MENU);</tt>
in their constructor if they want to use the feature introduced with Mac OS 10.7 "Lion"
where pressing and holding a key on the keyboard opens an accented-character menu window.
<p>Though the current implementation returns immediately, future <p>Though the current implementation returns immediately, future
versions may take quite awhile, as they may pop up a window or do versions may take quite awhile, as they may pop up a window or do
@ -100,6 +111,7 @@ int Fl::marked_text_length() {
static int insertion_point_x = 0; static int insertion_point_x = 0;
static int insertion_point_y = 0; static int insertion_point_y = 0;
static int insertion_point_height = 0;
static bool insertion_point_location_is_valid = false; static bool insertion_point_location_is_valid = false;
void Fl::reset_marked_text() { void Fl::reset_marked_text() {
@ -107,18 +119,20 @@ void Fl::reset_marked_text() {
Fl_X::next_marked_length = 0; Fl_X::next_marked_length = 0;
insertion_point_location_is_valid = false; insertion_point_location_is_valid = false;
} }
int Fl_X::insertion_point_location(int *px, int *py) int Fl_X::insertion_point_location(int *px, int *py, int *pheight)
// return true if the current coordinates of the insertion point are available // return true if the current coordinates of the insertion point are available
{ {
if ( ! insertion_point_location_is_valid ) return false; if ( ! insertion_point_location_is_valid ) return false;
*px = insertion_point_x; *px = insertion_point_x;
*py = insertion_point_y; *py = insertion_point_y;
*pheight = insertion_point_height;
return true; return true;
} }
void Fl::insertion_point_location(int x, int y) { void Fl::insertion_point_location(int x, int y, int height) {
insertion_point_location_is_valid = true; insertion_point_location_is_valid = true;
insertion_point_x = x; insertion_point_x = x;
insertion_point_y = y; insertion_point_y = y;
insertion_point_height = height;
} }
#endif // __APPLE__ #endif // __APPLE__