searchinputfield.C
demonstrates how to implement
a search field. This is done by creating an
input field with a callback that
executes as characters get typed into the input field.
The application implements this callback as a callable object or a lambda
that searches
for potential matches for the partial contents of the input field,
and the callback returns the list of potential matches.
This list of matches returned from the callback gets displayed a popup below (or above) the input field. Clicking on one of the popped-up results, or using Cursor-Down to move the cursor into the popup to select a match result, copies the matching result into the input field:
/* ** Copyright 2018-2021 Double Precision, Inc. ** See COPYING for distribution information. */ #include "config.h" #include "close_flag.H" #include <x/exception.H> #include <x/destroy_callback.H> #include <x/ondestroy.H> #include <x/appid.H> #include <x/w/main_window.H> #include <x/w/listlayoutmanager.H> #include <x/w/gridlayoutmanager.H> #include <x/w/gridfactory.H> #include <x/w/label.H> #include <x/w/text_param_literals.H> #include <x/w/font_literals.H> #include <x/w/input_field.H> #include <x/w/input_field_lock.H> #include <x/w/container.H> #include <x/w/button.H> #include <courier-unicode.h> #include <string> #include <iostream> std::string x::appid() noexcept { return "searchinputfield.examples.w.libcxx.com"; } static void search_function(const x::w::input_field_search_info &search_info) { static const std::u32string lorem_ipsum[]= { U"Lorem Ipsum", U"dolor sit amet", U"consectetur adipisicing elit", U"sed do eiusmod tempor", U"incididunt ut labore", U"et dolore magna aliqua", U"Ut enim ad minim veniam", U"quis nostrud exercitation", U"ullamco laboris nisi", U"ut aliquip ex ea commodo", U"consequat", U"Duis aute irure dolor", U"in reprehenderit", U"in voluptate velit", U"esse cillum dolore eu", U"fugiat nulla pariatur", U"Excepteur sint occaecat cupidatat non proident", U"sunt in culpa qui officia deserunt mollit anim", U"id est laborum", }; // A separate execution thread invokes the search callback. Each time // a character gets added to the end of the input field, this search // callback gets invoked. The search callback does not get invoked // if something gets edited in the middle of the input field, only // when more characters are added, or the last character in the input // field gets deleted. // // Simulate a slow search when more than two characters are searched // for, by sleeping for one second before returning. if (search_info.search_string.size() > 6) { sleep(1); } // // Type-ahead input continues in the input field, while we're // "searching" (sleeping). After the existing search returns, the // execution thread calls it again, this time passing in the // additional typed-in text. else if (search_info.search_string.size() > 2) { // Let's do something more sophisticated than sleeping for // a second. Let's make use of the abort mcguffin. The main // library execution thread releases its reference on the // mcguffin when it aborts the current search in progress. // This could be because the keyboard focus left the search // field, or additional text was added to, or removed from, // the search field; the current "slow running search" is // obsolete. There are no means to forcibly terminate a // different execution thread, so the main library execution // thread releases its reference on the object as the means // of indicating the aborted search, and any results returned // by the search thread get ignored. // // What we'll do is attach a destructor callback to the // abort mcguffin. // // We happen to have a convenient thread-safe bool flag // available to us, in the form of a close_flag_ref. auto abort_flag=close_flag_ref::create(); // The destructor callback captures the abort flag, and sets // it when the mcguffin gets destroyed. search_info.get_abort_mcguffin()->ondestroy ([abort_flag] { abort_flag->close(); }); // Wait for a second, or until the close flag gets set. // Using this approach makes it possible to detect when the // currently-running "slow search" can be bailed out of. // // Note that even in the case of an aborted search, any // resulted that eventually get returned from this search // callback may or may not end up in the popup, so the // search callback is not required to return without setting // any results. If the search callback detects an aborted // search after "finding" some partial results, it's fine to // save the partial results and return them. x::mpcobj<bool>::lock lock{abort_flag->flag}; lock.wait_for(std::chrono::seconds(1), [&] { return *lock; }); if (*lock) return; } for (const auto &search:lorem_ipsum) { // search_info.search_string is what's being searched. // // Case-insensitive unicode search, here. auto iter=std::search(search.begin(), search.end(), search_info.search_string.begin(), search_info.search_string.end(), [] (const auto &a, const auto &b) { return unicode_uc(a) == unicode_uc(b); }); if (iter==search.end()) continue; // Found a "search result". Each individual "search result" // gets recorded in two different ways. Firstly, the // std::u32string representing the matching search result // goes into search_info.search_string. search_info.search_results.push_back(search); auto iter_end=iter+search_info.search_string.size(); // Then each search result goes into search_info.search_items, // which is a list_item_param. At this time, the only // documented list_item_param is a text_param, which is // a unicode string with meta font and color mark-ups. x::w::text_param t; // Each search result may appear in the search popup with // custom font and color. We use this to show the portion of // each found "search result" that matches the search string // in bold font and underlined. // // First, any initial part of each "search result" is // just the default theme font: plain "sans_serif" font. if (iter != search.begin()) { t("sans_serif"_theme_font); t(std::u32string{search.begin(), iter}); } // And now the matching portion, bolded and underlined t("sans_serif;weight=bold"_theme_font); t(x::w::text_decoration::underline); t(std::u32string{iter, iter_end}); // If there's anything after the matching portion of each // "search result", go back to the plain font. if (iter_end != search.end()) { t("sans_serif"_theme_font); t(x::w::text_decoration::none); t(std::u32string{iter_end, search.end()}); } search_info.search_items.push_back(t); } // Alternatively, if no special markup is required: // // std::vector<std::u32string> results; // // search_info.results(results); // // results() is a helper function that sets both // search_info.search_results and search_info.search_items. // // NOTE: it is up to the search callback to limit the size of the // search results. All "search results" get shown by the popup. // The "search results" should be limited to some maximum number // of results. Also, if the width of each individual search results // is bigger than the popup's width, it gets cut-off. It is the // search callback's responsibility to enforce some reasonable limits. } void create_mainwindow(const x::w::main_window &main_window, const close_flag_ref &close_flag) { auto layout=main_window->gridlayout(); x::w::gridfactory factory=layout->append_row(); x::w::input_field_config search_config{30}; // Install the search callback. search_config.input_field_search.emplace( search_function, x::w::bidi_format::none ); // Add some padding, to make the window bigger. // Align the input field and the button in the middle of the row, // vertically. auto field=factory->left_padding(10) .top_padding(10) .bottom_padding(10) .valign(x::w::valign::middle) .create_input_field("", search_config); // Use the on_validate callback to "report" search results. // on_validate() normally gets invoked to validate the input field // after it gets tabbed out of. With a search callback, on_validate() // also gets invoked after picking something from the search popup. // // Note that what's manually typed in may not match anything that // the search found, if the popup does not get used. field->on_validate ([] (ONLY IN_THREAD, x::w::input_lock &lock, const x::w::callback_trigger_t &trigger) { std::cout << "Search found: " << lock.get() << std::endl; return true; }); auto ok=factory->right_padding(10) .top_padding(10) .bottom_padding(10) .create_button({"Ok"}, { x::w::default_button(), x::w::shortcut{'\n'}, }); ok->on_activate([close_flag] (ONLY IN_THREAD, const x::w::callback_trigger_t &trigger, const x::w::busy &ignore) { close_flag->close(); }); } void searchinputfield() { x::destroy_callback::base::guard guard; auto close_flag=close_flag_ref::create(); auto main_window= x::w::main_window::create([&] (const auto &main_window) { create_mainwindow(main_window, close_flag); }); main_window->on_disconnect([] { _exit(1); }); guard(main_window->connection_mcguffin()); main_window->set_window_title("QuackQuackRun!"); main_window->on_delete ([close_flag] (ONLY IN_THREAD, const x::w::busy &ignore) { close_flag->close(); }); main_window->show_all(); close_flag->wait(); } int main(int argc, char **argv) { try { searchinputfield(); } catch (const x::exception &e) { e->caught(); exit(1); } return 0; }
#include <x/w/input_field.H> #include <x/w/input_field_config.H> #include <x/w/listlayoutmanager.H> x::w::input_field_config config{30}; config.input_field_search.emplace( [] (const x::w::input_field_search_info &search_info) { // ... });
As explained in the section called “Input fields”, a factory's
create_input_field
() method
creates a new
x::w::input_field
,
using its
x::w::input_field_config
parameter to set the new input field's options.
Initializing its
input_field_search
members adds a combo-box like
popup to the search input field.
input_field_search
is a small object with two
fields: a callback
for the actual callable object,
and a search_format
. This is a
x::w::bidi_format
value that sets whether the search string received by the callback
has bi-directional markers,
and defaults to
x::w::bidi_format::none
.
Alternatively, enable_search
() also enables
the search popup, and the callback gets installed after the input
field gets created:
x::w::input_field_config config{30}; config.enable_search(); x::w::input_field f=factory->create_input_field("", config); f->on_search( { [] (const x::w::input_field_search_info &search_info) { // ... } });
[] (const x::w::input_field_search_info &search_info) { std::u32string search_string=search_info.search_string; // ... Find this string std::vector<std::u32string> results; // Either plain text results: search_info.results(results); // ... or plain text results and a text_param with custom fonts or // colors: std::u32string search_result; x::w::text_param search_item; search_info.search_results.push_back(search_result); search_info.search_items.push_back(search_item); };
The search callback receives an
x::w::input_field_search_info
.
This parameter contains a search_string
, the
current contents of the input field, as a Unicode string.
The input_field_search
's
search_format
member defaults to
x::w::bidi_format::none
.
This strips off all
bi-directional markers from the
search_string
,
irrespective of the
x::w::input_field_config
's
directional_format
setting.
This search callback is not a typical connection thread
callback, and does not receive an
IN_THREAD
parameter.
The search callback gets invoked from a separate,
independent execution thread that does not block
the connection thread.
The search callback takes the search string,
and places the search results
into its x::w::input_field_search_info
parameter, and returns. These search results then show up in the
search field's popup.
There are two ways to return the list of matches for the search string:
Pass a
std::vector<std::u32string>
to search_info
's
results
() method. This records the
list of strings that comprise the found matches, and shows them
without any special highlighting or formatting, in the popup.
Initialize search_info
's
search_results
and
search_items
individually. These are two
std::vector
s.
search_results
is a
x::std::vector<std::u32string>
and represents the found matches, as plain text.
search_items
is a
x::std::vector<x::w::list_item_param>
.
Both of these vectors must have the same size.
The corresponding value in each vector represents:
1) a single matching search result, as plain text, and
2) a formatted representation of the search result, as a
x::w::text_param
.
The second vector's
x::w::list_item_param
reveal that they
get passed to the search popup's
list layout manager; but
at this time, the
x::w::list_item_param
s
in the search_items
can only be
x::w::text_param
s.
The
results
() method is simply a shortcut
for initializing both vectors from a single vector of Unicode
text strings.
The search popup displays the
search_items
, as is.
Selecting one of the search items from the popup copies the
corresponding value from
search_results
into the input field.
searchinputfield.C
gives a basic example
of a rudimentary search function, that searches a list of canned
strings, for a substring that matches the
search_string
.
Each matching string gets copied into the
search_results
, with
search_items
formatted so that the matching
substring gets shown in bold, and underlined.
Despite it being a standalone execution thread, the search callback is owned by the input field, and the usual rules for capturing references in callbaks apply.