-
Notifications
You must be signed in to change notification settings - Fork 622
Add uiEntryOnFinished() event to uiEntry #290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
4e5a02e
8252984
fb2c716
a6b58c6
11b3a02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,6 +59,8 @@ - (NSSize)intrinsicContentSize | |
NSTextField *textfield; | ||
void (*onChanged)(uiEntry *, void *); | ||
void *onChangedData; | ||
void (*onFinished)(uiEntry *, void *); | ||
void *onFinishedData; | ||
}; | ||
|
||
static BOOL isSearchField(NSTextField *tf) | ||
|
@@ -69,6 +71,7 @@ static BOOL isSearchField(NSTextField *tf) | |
@interface entryDelegateClass : NSObject<NSTextFieldDelegate> { | ||
struct mapTable *entries; | ||
} | ||
- (void)controlTextDidEndEditing:(NSNotification *)note; | ||
- (void)controlTextDidChange:(NSNotification *)note; | ||
- (IBAction)onSearch:(id)sender; | ||
- (void)registerEntry:(uiEntry *)e; | ||
|
@@ -91,17 +94,30 @@ - (void)dealloc | |
[super dealloc]; | ||
} | ||
|
||
- (void)controlTextDidEndEditing:(NSNotification *)note | ||
{ | ||
uiEntry *e; | ||
e = (uiEntry *) mapGet(self->entries, [note object]); | ||
(*(e->onFinished))(e, e->onFinishedData); | ||
} | ||
|
||
|
||
- (void)controlTextDidChange:(NSNotification *)note | ||
{ | ||
[self onSearch:[note object]]; | ||
uiEntry *e; | ||
e = (uiEntry *) mapGet(self->entries, [note object]); | ||
(*(e->onChanged))(e, e->onChangedData); | ||
} | ||
|
||
- (IBAction)onSearch:(id)sender | ||
{ | ||
uiEntry *e; | ||
|
||
e = (uiEntry *) mapGet(self->entries, sender); | ||
|
||
NSSearchField *s; | ||
s = (NSSearchField *) (e->textfield); | ||
(*(e->onChanged))(e, e->onChangedData); | ||
(*(e->onFinished))(e, e->onFinishedData); | ||
} | ||
|
||
- (void)registerEntry:(uiEntry *)e | ||
|
@@ -155,6 +171,31 @@ void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data) | |
e->onChangedData = data; | ||
} | ||
|
||
// NOTE: for search widgets on OSX, setting OnFinished() alters the behaviour | ||
// (by setting sendsWholeSearchString). | ||
// Standard text and password entry widgets are not affected. | ||
// background: | ||
// On OSX, there doesn't seem to be any simple way to catch | ||
// both 'changed' events and 'enter' (finished editing) events on | ||
// search widgets. Instead, there just a single 'search' event, and flags | ||
// to determine when it triggers. By default, it triggers after each keypress | ||
// (with a little delay in case the user is still typing). | ||
// There's also an option to change the behaviour to trigger only when the | ||
// enter key is hit, or the search icon is pressed. This is the | ||
// sendsWholeSearchString flag, and we'll set it if (and only if) OnFinished() | ||
// is used. | ||
void uiEntryOnFinished(uiEntry *e, void (*f)(uiEntry *, void *), void *data) | ||
{ | ||
e->onFinished = f; | ||
e->onFinishedData = data; | ||
if (isSearchField(e->textfield)) { | ||
NSSearchField *s; | ||
s = (NSSearchField *) (e->textfield); | ||
// TODO requires OSX >= 10.10 (is that an issue?) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Calling it directly is, but this kind of thing is what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually the equivalent property on NSSearchFieldCell is 10.3 and newer, so just replace |
||
[s setSendsWholeSearchString:YES]; | ||
} | ||
} | ||
|
||
int uiEntryReadOnly(uiEntry *e) | ||
{ | ||
return [e->textfield isEditable] == NO; | ||
|
@@ -175,6 +216,11 @@ static void defaultOnChanged(uiEntry *e, void *data) | |
// do nothing | ||
} | ||
|
||
static void defaultOnFinished(uiEntry *e, void *data) | ||
{ | ||
// do nothing | ||
} | ||
|
||
// these are based on interface builder defaults; my comments in the old code weren't very good so I don't really know what talked about what, sorry :/ | ||
void finishNewTextField(NSTextField *t, BOOL isEntry) | ||
{ | ||
|
@@ -219,8 +265,13 @@ void finishNewTextField(NSTextField *t, BOOL isEntry) | |
[delegates addObject:entryDelegate]; | ||
} | ||
[entryDelegate registerEntry:e]; | ||
uiEntryOnChanged(e, defaultOnChanged, NULL); | ||
|
||
// set the callbacks directly, so as to not trigger the | ||
// sendsWholeSearchString flag set in OnFinished() for search widgets. | ||
e->onFinished = defaultOnFinished; | ||
e->onFinishedData = NULL; | ||
e->onChanged = defaultOnChanged; | ||
e->onChangedData = NULL; | ||
return e; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,14 +9,15 @@ struct uiEntry { | |
void (*onChanged)(uiEntry *, void *); | ||
void *onChangedData; | ||
gulong onChangedSignal; | ||
void (*onFinished)(uiEntry *, void *); | ||
void *onFinishedData; | ||
}; | ||
|
||
uiUnixControlAllDefaults(uiEntry) | ||
|
||
static void onChanged(GtkEditable *editable, gpointer data) | ||
{ | ||
uiEntry *e = uiEntry(data); | ||
|
||
(*(e->onChanged))(e, e->onChangedData); | ||
} | ||
|
||
|
@@ -25,6 +26,17 @@ static void defaultOnChanged(uiEntry *e, void *data) | |
// do nothing | ||
} | ||
|
||
static void onFinished(GtkEditable *editable, gpointer data) | ||
{ | ||
uiEntry *e = uiEntry(data); | ||
(*(e->onFinished))(e, e->onFinishedData); | ||
} | ||
|
||
static void defaultOnFinished(uiEntry *e, void *data) | ||
{ | ||
// do nothing | ||
} | ||
|
||
char *uiEntryText(uiEntry *e) | ||
{ | ||
return uiUnixStrdupText(gtk_entry_get_text(e->entry)); | ||
|
@@ -45,6 +57,13 @@ void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data) | |
e->onChangedData = data; | ||
} | ||
|
||
void uiEntryOnFinished(uiEntry *e, void (*f)(uiEntry *, void *), void *data) | ||
{ | ||
e->onFinished = f; | ||
e->onFinishedData = data; | ||
} | ||
|
||
|
||
int uiEntryReadOnly(uiEntry *e) | ||
{ | ||
return gtk_editable_get_editable(e->editable) == FALSE; | ||
|
@@ -73,6 +92,9 @@ static uiEntry *finishNewEntry(GtkWidget *w, const gchar *signal) | |
e->onChangedSignal = g_signal_connect(e->widget, signal, G_CALLBACK(onChanged), e); | ||
uiEntryOnChanged(e, defaultOnChanged, NULL); | ||
|
||
g_signal_connect(e->widget, "activate", G_CALLBACK(onFinished), e); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would Note to self: TODO investigate the phrasing "it is also commonly used by applications to intercept activation of entries" in the docs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd discounted I see the "it is also commonly used by applications to intercept activation of entries" line as acknowledging that the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh - I've just been poking through the Gtk+ source code. So I think we're better off sticking with |
||
uiEntryOnFinished(e, defaultOnFinished, NULL); | ||
|
||
return e; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,19 +7,27 @@ struct uiEntry { | |
void (*onChanged)(uiEntry *, void *); | ||
void *onChangedData; | ||
BOOL inhibitChanged; | ||
void (*onFinished)(uiEntry *, void *); | ||
void *onFinishedData; | ||
}; | ||
|
||
static BOOL onWM_COMMAND(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult) | ||
{ | ||
uiEntry *e = uiEntry(c); | ||
|
||
if (code != EN_CHANGE) | ||
return FALSE; | ||
if (e->inhibitChanged) | ||
return FALSE; | ||
(*(e->onChanged))(e, e->onChangedData); | ||
*lResult = 0; | ||
return TRUE; | ||
if (code == EN_CHANGE) { | ||
if (e->inhibitChanged) | ||
return FALSE; | ||
(*(e->onChanged))(e, e->onChangedData); | ||
*lResult = 0; | ||
return TRUE; | ||
} | ||
if (code == EN_KILLFOCUS) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Windows was the thing I was the least sure about what constitutes "finished editing", because the traditional wisdom was (as far as I can gather) to validate everything when dismissing a dialog box. My original idea for validating correctness of user entries was to provide a function That being said, I'd suggest keeping these in mind: https://blogs.msdn.microsoft.com/oldnewthing/20040419-00/?p=39753 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't dialog validation a slightly moot point anyway, without dialog box support? Caveat: I have no experience or knowledge whatsoever about dialog boxes on any of the target platforms :-) The use case I'm interested in myself is less to do with validation, and more to do with reacting to a "completed" change, rather than the higher-frequency OnChanged() callbacks. In particular, I have a search box where the searches take a few seconds to perform, so I only want to perform them when the user has completed entering their search query. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And yes, my reading of those Raymond Chen posts also seems to suggest that |
||
(*(e->onFinished))(e, e->onFinishedData); | ||
*lResult = 0; | ||
return TRUE; | ||
} | ||
return FALSE; | ||
} | ||
|
||
static void uiEntryDestroy(uiControl *c) | ||
|
@@ -56,6 +64,11 @@ static void defaultOnChanged(uiEntry *e, void *data) | |
// do nothing | ||
} | ||
|
||
static void defaultOnFinished(uiEntry *e, void *data) | ||
{ | ||
// do nothing | ||
} | ||
|
||
char *uiEntryText(uiEntry *e) | ||
{ | ||
return uiWindowsWindowText(e->hwnd); | ||
|
@@ -76,6 +89,12 @@ void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data) | |
e->onChangedData = data; | ||
} | ||
|
||
void uiEntryOnFinished(uiEntry *e, void (*f)(uiEntry *, void *), void *data) | ||
{ | ||
e->onFinished = f; | ||
e->onFinishedData = data; | ||
} | ||
|
||
int uiEntryReadOnly(uiEntry *e) | ||
{ | ||
return (getStyle(e->hwnd) & ES_READONLY) != 0; | ||
|
@@ -106,6 +125,7 @@ static uiEntry *finishNewEntry(DWORD style) | |
|
||
uiWindowsRegisterWM_COMMANDHandler(e->hwnd, onWM_COMMAND, uiControl(e)); | ||
uiEntryOnChanged(e, defaultOnChanged, NULL); | ||
uiEntryOnFinished(e, defaultOnFinished, NULL); | ||
|
||
return e; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good comment here! As for the text itself, yes, that is an issue, and I'm not sure what workarounds there are. The control editing system in OS X is something I don't fully understand yet, especially since NSSearchField can also use target-action. I'll have to look into it more...