Using on_default_filter()

Filter and validation callbacks serve different purposes. Filter callbacks get invoked as part of the input field's editing process, and validation callbacks get invoked at the conclusion of the editing process. The input filter example in Chapter 8, Filtered input fields accepts only digits as input, but it has no means of blocking tabbing out of the input field after typing in fewer than the requisite ten digits.

filteredinput2.C shows an example of using a validation callback together with an input filter. This is also an example of using on_default_filter() to install a basic input filter with basic, default, semantics that require fewer supporting code.

/*
** Copyright 2019-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/appid.H>

#include <x/w/main_window.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/input_field.H>
#include <x/w/canvas.H>
#include <x/w/container.H>
#include <x/w/button.H>

#include <string>
#include <iostream>
#include <sstream>
#include <optional>
#include <algorithm>

std::string x::appid() noexcept
{
	return "filteredinput2.examples.w.libcxx.com";
}

x::w::validated_input_field<std::string>
create_mainwindow(const x::w::main_window &main_window,
		  const close_flag_ref &close_flag)
{
	auto layout=main_window->gridlayout();

	layout->row_alignment(0, x::w::valign::middle);

	x::w::gridfactory factory=layout->append_row();

	factory->create_label("Phone #:");

	x::w::input_field_config config{15};

	config.maximum_size=14;
	config.autoselect=true;
	config.autodeselect=true;
	config.direction=x::w::bidi::left_to_right;

	auto field=factory->create_input_field("(###) ###-####", config);

	field->on_default_filter(// Valid input is digits 0-9.
				 []
				 (char32_t c)
				 {
					 return c >= '0' && c <= '9';
				 },

				 // Immutable positions in the input field.
				 {0, 4, 5, 9},

				 // Character that indicates no input.
				 '#');

	factory=layout->append_row();

	factory->create_canvas();

	factory->create_button("Ok", x::w::default_button() )
		->on_activate([close_flag]
			      (ONLY IN_THREAD,
			       const auto &trigger,
			       const auto &mcguffin)
			      {
				      close_flag->close();

			      });

	// An existing input field's set_validator() is equivalent to
	// using create_validated_input_field_contents() for
	// a new input field. set_validator() has the effect of converting
	// a regular input field into a validated input field.
	//
	// This is less efficient, but sometimes it's not convenient to
	// create the validator closures until after all widgets in the window
	// get created.
	//
	// set_validator() takes the same parameters as
	// create_validated_input_field_contents() and returns a
	// x::w::validated_input_field<type>.
	//
	// There's also set_string_validator() that's comparable to using
	// create_string_validated_input_field_contents().

	return field->set_validator
		([]
		 (ONLY IN_THREAD,
		  const std::string &value,
		  x::w::input_lock &lock,
		  const x::w::callback_trigger_t &trigger)
		 -> std::optional<std::string>
		 {
			 std::string s;

			 s.reserve(10);

			 for (auto c:value)
			 {
				 if (c >= '0' && c <= '9')
					 s.push_back(c);
			 }

			 if (s.size() > 0 && s.size() < 10)
			 {
				 lock.stop_message("Invalid phone number");
				 return std::nullopt;
			 }

			 return s;
		 },
		 []
		 (const std::optional<std::string> &v) -> std::string
		 {
			 std::string s="(###) ###-####";

			 auto i=s.begin();

			 if (v && v->size() == 10)
			 {
				 for (char c:*v)
				 {
					 while (*i != '#')
						 ++i;
					 *i++ = c;
				 }
			 }
			 return s;
		 });
}

void filteredinputfield()
{
	x::destroy_callback::base::guard guard;

	auto close_flag=close_flag_ref::create();

	x::w::validated_input_fieldptr<char> char_input;

	x::w::validated_input_fieldptr<int> int_input;

	x::w::validated_input_fieldptr<std::string> validated_phone_number;
	auto main_window=x::w::main_window::create
		([&]
		 (const auto &main_window)
		 {
			 validated_phone_number=
				 create_mainwindow(main_window, close_flag);

			 validated_phone_number->set("2125551212");
		 });

	main_window->on_disconnect([]
				   {
					   _exit(1);
				   });

	guard(main_window->connection_mcguffin());

	main_window->set_window_title("Prompt");
	main_window->on_delete
		([close_flag]
		 (ONLY IN_THREAD,
		  const x::w::busy &ignore)
		 {
			 close_flag->close();
		 });

	main_window->show_all();

	close_flag->wait();

	auto phone_number=validated_phone_number->value();

	if (phone_number)
		std::cout << "Phone number: " << *phone_number << std::endl;
}

int main(int argc, char **argv)
{
	try {
		filteredinputfield();
	} catch (const x::exception &e)
	{
		e->caught();
		exit(1);
	}
	return 0;
}

on_default_filter() uses on_filter() to install an input filter with behavior similar to what filteredinput.C implements. filteredinput2.C implements an input field for entering a phone number in the US telephone number format, with hashes serving as placeholders for the ten digits. A validation callback checks that all ten digits of the phone number got typed in, and provides a validated phone number string containing just the ten digits, with the punctuation stripped off.

on_default_filter() requirements

on_default_filter() takes the following parameters:

  • A closure or a lambda that determines whether a single Unicode character is acceptable input.

  • A std::vector<size_t> that specifies the indexes of the immutable characters in the input field. These immutable characters get skipped in the input field and cannot be modified. filteredinput2.C passes in a vector that lists the parenthesis, the space, and the dash characters' indexes.

  • The placeholder character for an empty character position. This defaults to a space character.

on_default_filter() imposes additional requirements on the input field:

  • The initial contents of the input field should be set to a text string with the same number of Unicode characters as the input field's defined maximum size.

  • All characters except the immutable ones should be set to the empty placeholder value.

An input field with an on_default_filter() is effectively a fixed-sized field. New text gets added to the input field replacing the empty placeholder characters, and removed text gets replaced with placeholder characters. filteredinput2.C uses # as empty placeholders where phone number digits go. They get replaced by the typed-in digits, and deleting the phone number's digits replaces them with #s.