Chapter 10. The item layout manager


Using singleton objects
The item layout manager

The item layout manager implements a common UI design pattern in combination with an input field: the input field provides a free-form text entry for typing in a list of objects or entities. The typed-in text gets parsed, validated, and converted into one or more individual items. The parsed items get displayed above or below the input field. Each item gets drawn as a small button with a label inside it, and an X that removes the item from the list.

Passing an x::w::new_itemlayoutmanager to a factory's create_focusable_container() creates a container that uses the x::w::itemlayoutmanager. itemlayoutmanager.C gives an example of creating a focusable container managed by the item layout manager, and installing an on_validate() callback for a text input field above it. The callback takes the typed-in contents, splits them into a comma-separated text strings, trims off each string's leading and trailing whitespace, and creates an x::w::label from each string.

The item layout manager draws a button around each widget created by its factory. Its container adjusts to any width, so it's typically positioned above or below the text input field, in a parent container that uses the grid layout manager. This sizes the item layout manager's container to its column's full width; the item layout manager lays out all items in its container up to its horizontal width, and creates as many rows as needed to show all items:

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

#include <x/w/main_window.H>
#include <x/w/gridlayoutmanager.H>
#include <x/w/gridfactory.H>
#include <x/w/itemlayoutmanager.H>
#include <x/w/label.H>
#include <x/w/input_field.H>
#include <x/w/input_field_lock.H>
#include <x/w/container.H>
#include <x/w/focusable_container.H>
#include <x/w/canvas.H>

#include <x/singletonptr.H>
#include <x/strtok.H>
#include <x/join.H>
#include <x/mutex.H>
#include <courier-unicode.h>
#include <unordered_set>
#include <vector>

std::string x::appid() noexcept
	return "";

// The application object.

class appObj : virtual public x::obj {


	// The main window
	const x::w::main_window main_window;

	// The input text field where toppings get typed in, as text
	const x::w::input_field toppings_text_field;

	// The container with the list layout manager.
	const x::w::focusable_container toppings_list;

	// The list of items in the container
	// This object gets accessed by both the library execution thread and
	// the main execution thread, hence it's wrapped inside a mutex.
	// This vector parallels the items in the toppings_list container.
	// Those item are display elements, and this vector is the corresponding
	// topping names.
	typedef x::mpobj<std::vector<std::string>> toppings_t;

	toppings_t toppings;

	// Constructor
	appObj(const x::w::main_window &main_window,
	       const x::w::input_field &toppings_text_field,
	       const x::w::focusable_container &toppings_list)
		: main_window{main_window},

	// Item layout manager callback that gets invoked in response to
	// the click on the closing "X". We remove the item label from the
	// container, and also update our toppings list.
	void remove_toppings(const x::w::itemlayout_lock &lock,
			     size_t i)
		toppings_t::lock t_lock{toppings};

		if (i >= t_lock->size())
			throw EXCEPTION("Shouldn't happen");

		// Remove the item label, in the container, and update our
		// vector.

	void add_toppings()
		// Something was entered into the toppings text input field.
		// Retrieve the string, split it by commas, then clear the
		// contents.
		std::vector<std::string> words;

			x::w::input_lock lock{toppings_text_field};

			x::strtok_str(lock.get(), ",", words);



	void add_toppings(const std::vector<std::string> &words)
		std::unordered_set<std::string> dupes;

		// We must obtain the item layout manager lock first, then
		// the lock on the toppings vector. When the internal
		// execution thread invokes the remove_toppings() callback,
		// the internal execution thread acquires the itemlayout_lock
		// first, then passes it to the callback, which them obtains
		// the lock on the toppings vector.
		// We must use the same locking order here, to avoid the
		// deadlock.
		x::w::itemlayout_lock i_lock{toppings_list->itemlayout()};

		toppings_t::lock t_lock{toppings};

		// Go through each word that was split out of the comma-
		// separated string.
		for (auto &w:words)
			auto b=w.begin();
			auto e=w.end();

			// Trim off the leading and trailing whitespace.
			x::trim(b, e);

			// Convert each trimmed word to lowercase, and
			// capitalize the first letter.
			auto word=unicode::iconvert::convert_tocase
				({b, e},

			if (word.empty())

			// The toppings vector is sorted, find where this
			// topping name belongs, in the vector.
			auto iter=std::lower_bound(t_lock->begin(),
						   t_lock->end(), word);

			// Is this topping already entered?
			if (iter != t_lock->end() && *iter == word)

			// Insert this topic at this position both in the
			// toppings vector and at the same corresponding
			// position in the container.

			size_t pos=iter-t_lock->begin();

			t_lock->insert(iter, word);

			// The item layout manager's insert_item() adds a new
			// item to the container positioned before an existing
			// item. There's also append_item() that adds a new
			// item to the end of the container after all existing
			// items.
			// insert_item() and append_item() take a lambda, or
			// a callable object as a parameter. The lambda
			// gets called with an x::w::factory parameter. The
			// lambda must use this factory to create exactly
			// one display element, that represents the item's
			// label. Typically the lambda calls create_label(),
			// but any display element is permissible. In all
			// cases the lambda is responsible for show()ing the
			// newly-created label.
				 (const x::w::factory &f)

		// See if there were any dupes, and show an error message

		if (dupes.empty())

			sorted_dupes{dupes.begin(), dupes.end()};


		auto list=x::join(sorted_dupes.begin(),
				  ", ");
		main_window->alert_message(list + " already ordered!");

typedef x::singletonptr<appObj> app;

auto create_mainwindow(const x::w::main_window &main_window)
	// Create the main application window and its important display
	// elements.
	auto layout=main_window->gridlayout();

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

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

	// A label and an input field on the first row in the main window.
	factory->create_label("Pizza toppings:");

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

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

	// Install a manual on_validate() callback that gets invoked by
	// "Enter" in the input field, or when tabbing out of it.

			    x::w::input_lock &lock,
			    const x::w::callback_trigger_t &triggering_event)
				   app my_app;

				   if (my_app)
				   return true;


	// The grid has two columns. Put an empty canvas place holder in
	// the first column on the 2nd row, below the "Pizza toppings:" label.

	// And create a focusable container that uses the item layout manager.
	// The callback that gets invoked by each item's "X", that's
	// intuitively indicates to click there to remove the item.
	x::w::new_itemlayoutmanager nilm
		  size_t i,
		  const x::w::itemlayout_lock &lock,
		  const x::w::callback_trigger_t &trigger,
		  const x::w::busy &mcguffin)
			 app my_app;

			 if (my_app)
				 my_app->remove_toppings(lock, i);

	auto container=factory->create_focusable_container
		 (const auto &c)
			 // Initially nothing here

	return std::tuple{field, container};

void itemlayoutmanager()
	x::destroy_callback::base::guard guard;

	auto close_flag=close_flag_ref::create();

	x::w::input_fieldptr toppings_field;
	x::w::focusable_containerptr toppings_list;

	auto main_window=x::w::main_window::create
		 (const auto &main_window)
			 std::tie(toppings_field, toppings_list)=



	// Create the application object, with the fields.

	app my_app{x::ref<appObj>::create(main_window,

	main_window->set_window_title("Sam's pizzeria");
	// Put an initial item into the list.
		  const x::w::busy &ignore)



	// Show what's been ordered:

	auto toppings=my_app->toppings.get();

	std::cout << "Ordered: " << x::join(toppings.begin(),
					    ", ") << std::endl;

int main(int argc, char **argv)
	try {
	} catch (const x::exception &e)
	return 0;

itemlayoutmanager.C creates a text input field. Free-form text gets typed into the text input field. The text gets parsed as a comma-separated list of pizza toppings. Anything goes, anything that gets typed gets parsed as a pizza topping.


append_item() and insert_item() add one item to the container. A lambda, or some other suitable callable object, gets passed as a parameter. append_item() and insert_item() invoke the callback with a factory. The callback must use the factory to create and show() exactly one widget, which becomes the new item. Each item has an X next to it, which invokes a callback that uses remove_item() to remove itself from the container.