Chapter 13. Using focusable widgets

Input fields and buttons are examples of focusable widgets, or focusables. Focusables process keyboard events when they have keyboard input focus. There are two general ways for a focusable to gain input focus: by clicking on it with the primary pointer button, or by using the Tab and Shift-Tab keys, which cycle through the focusable fields in the window.

A dashed border gets typically drawn around the widget that has keyboard input focus. Tab moves the keyboard input focus to the next widget, and Shift-Tab moves the keyboard input focus to the previous widget.

The tabbing order does not get determined by the widgets' position, but rather their creation order. Focusables inherit from x::w::focusable objects, which implement several methods:

/*
** Copyright 2017-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/weakcapture.H>
#include <x/appid.H>

#include <x/w/main_window.H>
#include <x/w/gridlayoutmanager.H>
#include <x/w/gridfactory.H>
#include <x/w/button.H>
#include <x/w/focusable.H>

#include <x/visitor.H>
#include <string>
#include <iostream>

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

void create_mainwindow(const x::w::main_window &main_window)
{
	auto layout=main_window->gridlayout();

	layout->col_alignment(0, x::w::halign::center);

	auto button1=layout->append_row()
		->create_button("Button 1");

	auto button2=layout->append_row()
		->create_button("Button 2: disable button 1");

	auto button3=layout->append_row()
		->create_button("Button 3: enable button 1");

	auto button4=layout->append_row()
		->create_button("Button 4: button 2 gets focus before button 1");

	auto button5=layout->append_row()
		->create_button("Button 5: button 3 gets focus after button 2");

	auto button6=layout->append_row()
		->create_button("Button 6: button 1 gets focus first");

	auto button7=layout->append_row()
		->create_button("Button 7: move focus to button 1");

	// Note - normally a callback cannot capture reference to its parent
	// (or children) display elements, because this would create an
	// internal circular reference.
	//
	// In all of the following cases, a callback for a given button captures
	// a reference to another button, which is neither its parent or child.

	button2->on_activate([=]
			     (ONLY IN_THREAD,
			      const x::w::callback_trigger_t &trigger,
			      const x::w::busy &mcguffin)
			     {
				     button1->set_enabled(false);
			     });

	button3->on_activate([=]
			     (ONLY IN_THREAD,
			      const x::w::callback_trigger_t &trigger,
			      const x::w::busy &mcguffin)
			     {
				     button1->set_enabled(true);
			     });

	button4->on_activate([=]
			     (ONLY IN_THREAD,
			      const x::w::callback_trigger_t &trigger,
			      const x::w::busy &mcguffin)
			     {
				     button2->get_focus_before(button1);
			     });

	button5->on_activate([=]
			     (ONLY IN_THREAD,
			      const x::w::callback_trigger_t &trigger,
			      const x::w::busy &mcguffin)
			     {
				     button3->get_focus_after(button2);
			     });

	button6->on_activate([=]
			     (ONLY IN_THREAD,
			      const x::w::callback_trigger_t &trigger,
			      const x::w::busy &mcguffin)
			     {
				     button1->get_focus_first();
			     });

	button6->on_keyboard_focus([]
				   (ONLY IN_THREAD,
				    x::w::focus_change f,
				    const x::w::callback_trigger_t &t)
				   {
					   std::cout << "Button 6 in focus: "
						     << x::w::in_focus(f)
						     << " due to "
						     << t.index()
						     << std::endl;
				   });
	button6->on_key_event
		([]
		 (ONLY IN_THREAD,
		  const x::w::all_key_events_t &ke,
		  bool activated,
		  const x::w::busy &mcguffin)
		 {
			 // We get both key press and release events. The
			 // "activated" flag indicates whether the library
			 // would normally act on this key event, based on
			 // whether it is a press or a release.
			 //
			 // When a display element responds to a particular
			 // key, it should do so only when "activated" is set.
			 //
			 // If the display element responds to a particular
			 // key, the callback should return true whether or not
			 // activated is set (but take no action if activated
			 // is not set). If the display element does not
			 // recognize the key combination, the display element
			 // should return false.
			 //
			 // The activated flag always gets set for pasted
			 // unicode text from the X input method manager.

			 if (activated)
				 std::cout << "activated: ";

			 std::visit(x::visitor{
			     [](const x::w::key_event *keptr)
                             {
				 std::cout << (keptr->keypress
					       ? "press ":"release ");
				 if (keptr->unicode)
					 std::cout << "U+"
						   << (uint32_t)keptr->unicode;
				 else
					 std::cout << "keysym "
						   << keptr->keysym;
				 std::cout << std::endl;
			     },
			     [](const std::u32string_view *keptr)
			     {
				 // Buttons don't receive pasted unicode text
				 // from the X input method manager.
				 // this is for demo purposes.
			     },
			     [](const x::w::all_key_events_is_not_copyable &)
			     {
				     // Stub to enforce all_key_events_t
				     // untouchability.
			     }}, ke);

			 // This callback takes no action on anything, it just
			 // dumps its parameters, so we always return false.

			 return false;
		 });

	button7->on_activate([=]
			     (ONLY IN_THREAD,
			      const x::w::callback_trigger_t &trigger,
			      const x::w::busy &mcguffin)
			     {
				     button1->request_focus();
			     });
}

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

	auto close_flag=close_flag_ref::create();

	auto main_window=x::w::main_window
		::create(create_mainwindow,
			 x::w::new_gridlayoutmanager{});


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

	guard(main_window->connection_mcguffin());

	main_window->set_window_title("Focusable fields");

	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 {
		focusables();
	} catch (const x::exception &e)
	{
		e->caught();
		exit(1);
	}
	return 0;
}

get_focus_after(), get_focus_before(), and get_focus_first() moves the focusable widget's tabbing order to be after another focusable widget, before another focusable widget, or the first focusable widget in the window after the window's menu, if it has one; or the first focusable widget in windows without menu bars.

set_enabled() enables or disables a focusable widget. Disabled focusable widgets do not respond to pointer clicks, and tabbing the input focus skips them. Disabled focusable widgets get drawn with a dithered mask that blends them with the background color, making them appear faint compared to enabled widgets.

request_focus() explicitly moves the keyboard input focus to the given focusable widget.

Note

A widgets that are disabled or not visible cannot receive keyboard focus. In that case the keyboard focus gets moved whenever the widget can receive keyboard focus, unless a different widget requests keyboard focus first.

request_focus() takes an optional parameter. A true value results in the keyboard focus moving to the focusable widget if it's eligible to receive keyboard focus at this time, otherwise this gets ignored and no delayed keyboard focus movement takes place.

The default behavior makes it possible to create a new widget, make it visible and immediately set the keyboard focus to the new widget. It takes some time for the connection thread to prepare the new widget and draw it; and the default behavior produces the expected results.

on_keyboard_focus() and on_key_event() install callbacks that provide keyboard focus event feedback.

The on_keyboard_focus() callback gets invoked whenever the focusable widget gains or loses keyboard input focus. Additionally, the callback gets invoked upon installation to report the focusable widget's current focus (which is typically no input focus for newly-created widgets). The on_key_event() callback gets invoked when a key gets pressed or released, or when a unicode string gets composed using the X Input Method server, while the focusable widget receives input focus.

The on_key_event() callback must return true if the callback recognized and processed the key event. Returning false results in LibCXX Widget Toolkit's default action for the key event; but LibCXX Widget Toolkit calls the on_key_event() callback only when the key event results in no specific action by the focusable widget.

focusable.C installs an on_key_event() callback into one of the input buttons. This callback does not get called for Enter and Space keys, but for all other keys (that the X display server does not handle itself). This is because Enter and Space keys have the same effect as clicking the button with the pointer, and this specific action takes precedence.