Chapter 22. Standard and editable combo-boxes

Index

Creating a standard combo-box layout manager
Retrieving the standard combo-box's current selection
Using the standard combo-box with a pointer or a keyboard
Locking the combo-box
Editable combo-boxes
A standard combo-box.

A combo-box is an alternative to a selection list. The vertical list gets replaced by a single label with a button that opens a popup with the selection list, and the combo-box label shows the selected list item.

/*
** Copyright 2017-2021 Double Precision, Inc.
** See COPYING for distribution information.
*/

#include "config.h"
#include <x/mpobj.H>
#include <x/appid.H>
#include <x/exception.H>
#include <x/destroy_callback.H>
#include <x/w/main_window.H>
#include <x/w/gridlayoutmanager.H>
#include <x/w/standard_comboboxlayoutmanager.H>
#include <x/w/editable_comboboxlayoutmanager.H>
#include <x/w/input_field_lock.H>
#include <x/w/focusable_container.H>
#include <x/w/gridfactory.H>
#include <x/w/label.H>
#include <x/w/button.H>
#include <string>
#include <iostream>
#include <sstream>
#include <algorithm>

#include "close_flag.H"
#include "combobox.H"

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

static inline auto
create_standard_combobox(const x::w::factory &factory,
			 void (*creator)(const x::w::focusable_container &))
{

	x::w::new_standard_comboboxlayoutmanager nclm{

		// Default callback that gets invoked whenever any combobox
		// list item gets selected or unselected.

		[]
		(ONLY IN_THREAD,
		 const x::w::standard_combobox_selection_changed_info_t &i)
		{
			if (!i.list_item_status_info.selected)
				return;

			std::cout << "Selected item #"
				  << i.list_item_status_info.item_number
				  << std::endl;
		}};

	return factory->create_focusable_container(creator, nclm);
}

static inline auto
create_editable_combobox(const x::w::factory &factory,
			 void (*creator)(const x::w::focusable_container &))
{
	x::w::new_editable_comboboxlayoutmanager nclm{

		// Default callback that gets invoked whenever any combobox
		// list item gets selected or unselected.

		[]
		(ONLY IN_THREAD,
		 const x::w::editable_combobox_selection_changed_info_t &i)
		{
			if (!i.list_item_status_info.selected)
				return;

			std::cout << "Selected item #"
				  << i.list_item_status_info.item_number
				  << std::endl;
		}};

	return factory->create_focusable_container(creator, nclm);
}

// append_items(), insert_items(), et. al., take a vector of list_items_params
// as a parameter. We can simply prepare the list of items in advance, like
// this, and pass it.

std::vector<x::w::list_item_param> days_of_week()
{
	return {"Sunday", "Monday", "Tuesday",	"Wednesday",
			"Thursday", "Friday", "Saturday"};
}

static inline void create_main_window(const x::w::main_window &main_window,
				      const options &opts)
{
	auto layout=main_window->gridlayout();

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

	auto factory=layout->append_row();

	factory->create_label("Days of the week (or else):")->show();

	// Note that whether it's an editable or the standard combo-box,
	// it's still the same focusable container object. The only
	// immediate difference is the layout manager.

	auto combobox=(opts.editable->value
		       ? create_editable_combobox
		       : create_standard_combobox)
		(factory,
		[]
		 (const auto &container)
		 {
			 // An editable_comboboxlayoutmanager is a subclass of
			 // standard_comboboxlayoutmanager, so with either the
			 // standard or the editable combo-box we can use a
			 // standard_comboboxlayoutmanager to initialize it.

			 x::w::standard_comboboxlayoutmanager lm=
				 container->standard_comboboxlayout();

			 lm->replace_all_items(days_of_week());
		 });

	combobox->show();

	// Combo-box layout manager inherits methods from the underlying
	// list layout manager, like append_items().
	//
	// Create buttons on the rows below, with each button demonstrating
	// each combo-box operation. The combo-box layout manager inherits
	// append_items(), insert_items(), et. al., from the underlying
	// list layout manager.
	//
	// The buttons' callbacks get invoked by the connection thread and
	// receive the IN_THREAD handle; as such the callbacks can use
	// the append_items(), insert_items(), et. al., overloads that
	// take an IN_THREAD parameter.
	//
	// Like a selection list, the combo-box's contents get updated
	// by the connection thread. Calling the IN_THREAD overloads reflects
	// the changes in the combo-box's contents immediately. A main
	// execution thread invoking the non-IN_THREAD overloads results in
	// a message getting set to the connection thread, and the overloaded
	// methods returning immediately. This means that, for example,
	// calling insert_items() or append_items() from a non-IN_THREAD
	// contents, followed by size(), will likely report the combo-box's
	// size apparently unchanged. This is because the new items will
	// actually get inserted by the connection thread when it processes
	// the message. Caveat emptor.

	auto button=layout->append_row()->colspan(2)
		.create_button("Append row");

	button->on_activate([=, counter=0]
			    (ONLY IN_THREAD,
			     const x::w::callback_trigger_t &trigger,
			     const x::w::busy &ignore)
			    mutable
			    {
				    std::ostringstream o;

				    o << "Append " << ++counter;

				    auto lm=combobox->standard_comboboxlayout();

				    lm->append_items(IN_THREAD, {o.str()});
			    });
	button->show();

	// Same deal for insert_items().

	button=layout->append_row()->colspan(2)
		.create_button("Insert row");

	button->on_activate([=, counter=0]
			    (ONLY IN_THREAD,
			     const x::w::callback_trigger_t &trigger,
			     const x::w::busy &ignore)
			    mutable
			    {
				    std::ostringstream o;

				    o << "Insert " << ++counter;

				    auto lm=combobox->standard_comboboxlayout();

				    lm->insert_items(IN_THREAD, 0, {o.str()});
			    });
	button->show();

	// Same deal for replace_items(), delete_item(), size(), and
	// replace_all_items().

	button=layout->append_row()->colspan(2)
		.create_button("Replace row");

	button->on_activate([=, counter=0]
			    (ONLY IN_THREAD,
			     const x::w::callback_trigger_t &trigger,
			     const x::w::busy &ignore)
			    mutable
			    {
				    auto lm=combobox->standard_comboboxlayout();

				    // Instantiating a standard_combo_box
				    // blocks all other execution threads from
				    // accessing the contents of the combo-box.
				    //
				    // After we check size(), below, it is
				    // theoretically possible for another thread
				    // to modify it.
				    //
				    // This is not really needed here, this
				    // is for demonstration purposes only.
				    // This example modifies the contents of
				    // the combo-box only IN_THREAD, so this
				    // doesn't really do anything.

				    x::w::standard_combobox_lock
					    lock{lm};

				    if (lm->size() == 0)
					    return;

				    std::ostringstream o;

				    o << "Replace " << ++counter << std::endl;

				    // For convenience, all modification
				    // functions are also implemented by
				    // standard_combobox_lock. The following
				    // is equivalent to:
				    //
				    // lock.replace_items( ... )

				    lm->replace_items(IN_THREAD, 0, {o.str()});
			    });
	button->show();

	button=layout->append_row()->colspan(2)
		.create_button("Delete row");

	button->on_activate([combobox]
			    (ONLY IN_THREAD,
			     const x::w::callback_trigger_t &trigger,
			     const x::w::busy &ignore)
			    {
				    auto lm=combobox->standard_comboboxlayout();

				    if (lm->size() == 0)
					    return;

				    lm->remove_item(IN_THREAD, 0);
			    });
	button->show();

	button=layout->append_row()->colspan(2)
		.create_button("Reset");

	button->on_activate([combobox]
			    (ONLY IN_THREAD,
			     const x::w::callback_trigger_t &trigger,
			     const x::w::busy &ignore)
			    {
				    auto lm=combobox->standard_comboboxlayout();

				    lm->replace_all_items(IN_THREAD,
							  days_of_week());
			    });
	button->show();

	// Use resort_items() to randomly shuffle the list items.

	button=layout->append_row()->colspan(2)
		.create_button("Shuffle");

	button->on_activate([combobox]
			    (ONLY IN_THREAD,
			     const x::w::callback_trigger_t &trigger,
			     const x::w::busy &ignore)
			    {
				    auto lm=combobox->standard_comboboxlayout();

				    std::vector<size_t> n;

				    n.resize(lm->size());

				    std::generate(n.begin(), n.end(),
						  [n=0]
						  ()
						  mutable
						  {
							  return n++;
						  });

				    std::random_shuffle(n.begin(), n.end());

				    lm->resort_items(IN_THREAD, n);
			    });
	button->show();
	// Stash the combobox in main_window's appdata.

	main_window->appdata=combobox;
}

void create_combobox(const options &opts)
{
	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_main_window(main_window, opts);
			 });

	main_window->set_window_title(opts.editable->value ?
				      "Editable combo-box"
				      : "Standard combo-box");

	guard(main_window->connection_mcguffin());

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

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

	main_window->show();

	close_flag->wait();

	x::w::focusable_container combobox=main_window->appdata;

	// The editable combo-box implements most methods that access
	// the underlying contents of the locked input field like get()
	// (via the input_lock) and set().
	// input field.

	if (opts.editable->value)
	{
		auto lm=combobox->editable_comboboxlayout();

		std::cout << "Final contents: " << lm->get() << std::endl;
	}

	auto lm=combobox->standard_comboboxlayout();

	std::optional<size_t> selection=lm->selected();

	if (selection)
	{
		std::cout << "Final selection was day #" << selection.value()
			  << std::endl;
	}
	else
	{
		std::cout << "No combo-box selection."
			  << std::endl;
	}
}

int main(int argc, char **argv)
{
	try {
		options opts;

		opts.parse(argc, argv);

		create_combobox(opts);
	} catch (const x::exception &e)
	{
		e->caught();
		exit(1);
	}
	return 0;
}

Creating a standard combo-box layout manager

#include <x/w/focusable_container.H>
#include <x/w/standard_comboboxlayoutmanager.H>

x::w::new_standard_comboboxlayoutmanager new_combobox;

x::w::focusable_container list=f->create_focusable_container(
       []
       (const x::w::focusable_container &c)
       {
            x::w::standard_comboboxlayoutmanager l=c->get_layoutmanager();
       },
       new_combobox);

x::w::standard_comboboxlayoutmanager cbmanager=list->get_layoutmanager();

Passing a x::w::new_standard_comboboxlayoutmanager for the second parameter to a factory's create_focusable_container results in a container with a x::w::standard_comboboxlayoutmanager. This is the layout manager that manages the combo-box.

A standard combo-box consists of a popup with a vertical list of text labels. The combo-box itself consists of a label that receives input focus, and a button on its right. Clicking on the button opens the combo-box's popup with the combo-box's items. Selecting one of the items sets the combo-box label to the item's text. This is now the selected item. Selecting another item replaces the previously-selected item.

x::w::standard_comboboxlayoutmanager is a subclass of the list layout manager, and inherits its append_items(), insert_items(), replace_items(), replace_all_items() and remove_item() methods. These methods modify the contents of the combo-box. combobox.C calls replace_all_items() in create_focusable_container()'s creator lambda to set the initial contents of the combo-box.