Chapter 18. The book layout manager

Index

The book page factory
The on_opened callback
An appdata example
The book layout manager

The x::w::booklayoutmanager extends the page layout manager by creating a horizontal strip of "tabs" above the paged container, containing one tab for each page. Clicking on a page's tab automatically opens the tab's page.

The following example is a modified version of pagelayoutmanager that uses the book layout manager. The code that creates a button to make each page visible gets removed. The book layout manager does that:

/*
** 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/ref.H>
#include <x/obj.H>

#include <x/w/main_window.H>
#include <x/w/gridlayoutmanager.H>
#include <x/w/gridfactory.H>
#include <x/w/booklayoutmanager.H>
#include <x/w/bookpagefactory.H>
#include <x/w/label.H>
#include <x/w/text_param_literals.H>
#include <x/w/button.H>
#include <x/w/input_field.H>
#include <x/w/input_field_config.H>
#include <x/w/canvas.H>
#include <x/w/callback_trigger.H>

#include <iostream>
#include "close_flag.H"

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

/*
** Create:
**
**    First name: _____________________________
**     Last name: _____________________________
**
** Returns the "first name" x::w::input_field
*/

static void name_tab(const x::w::container &container)
{
	auto glm=container->gridlayout();

	auto f=glm->append_row();

	f->halign(x::w::halign::right).create_label("First name:");

	x::w::input_field_config config;

	config.autoselect=true;

	// The first focusable field on this page. Store it in the container's
	// appdata, for convenient access later.
	//
	// The new input field is a child element of the container. Only
	// callbacks that get attached to display elements are restricted
	// from capturing references to any parent or child elements, because
	// this creates a circular reference.
	//
	// Container own references to the elements in the container. Here,
	// we store a reference to the input field, in its container. This is
	// fine. When the container goes out of scope and gets destroyed, this
	// reference goes away as well.
	//
	// Every display element has an appdata x::ptr, that the library does
	// not use otherwise, and is available for applications to attach
	// x::ptrs or x::refs to reference-counted objects that are derived
	// from x::obj, and constructed with create().

	container->appdata=f->create_input_field("", config);

	f=glm->append_row();

	f->halign(x::w::halign::right).create_label("Last name:");
	f->create_input_field("", config);
}

/*
** Create:
**
**    Address: _____________________________
**             _____________________________
**       City: ______ State: ___ Zip: ________

** Returns the first "Address" x::w::input_field
*/

static void address_tab(const x::w::container &container)
{
	auto glm=container->gridlayout();

	auto f=glm->append_row();

	f->halign(x::w::halign::right).create_label("Address:");

	x::w::input_field_config config;

	config.autoselect=true;

	// There are six elements on the last row of the grid, so we
	// need to make the address input fields span 5 columns (the
	// label takes the first column)

	container->appdata=f->colspan(5).create_input_field("", config);

	f=glm->append_row();

	// No label for the 2nd address field, put an empty canvas in there.
	f->create_canvas();

	f->colspan(5).create_input_field("", config); // address2

	f=glm->append_row();

	f->halign(x::w::halign::right).create_label("City:");

	config.columns=20;

	f->create_input_field("", config);

	// No alignment is typically needed for State: and Zip: labels,
	// but just in case a theme makes the two big input fields wider
	// than the four elements, and the grid stretches the elements in
	// last row to align everything up, this will make things look
	// better.
	f->halign(x::w::halign::right).create_label("State:");

	config.columns=3;

	f->create_input_field("", config);

	f->halign(x::w::halign::right).create_label("Zip:");

	config.columns=11;

	f->create_input_field("", config);
}

/*
**    Phone: _______________________
**
** Returns the phone x::w::input_field
*/

static void phone_tab(const x::w::container &container)
{
	auto glm=container->gridlayout();

	auto f=glm->append_row();

	f->create_label("Phone:");

	x::w::input_field_config config;

	config.autoselect=true;

	container->appdata=f->create_input_field("", config);
}

/*
** Creator lambda for the book layout manager, factored out of
** create_mainwindow() for readability.
**
** Returns a tuple of three x::w::input_field-s, the first x::w::input_field
** on each of the three containers that get added to the book layout
** manager.
*/
static void create_book(const x::w::booklayoutmanager &pl)
{
	/*
	** append() returns a factory that appends new pages to the book.
	*/
	x::w::bookpagefactory new_page=pl->append();

	/*
	** The book layout manager is an extended version of the page
	** layout manager, and halign() and valign() serves the same
	** purpose as they do with the page layout manager.
	**
	** The major difference between page layout manager's pagefactory
	** and book layout manager's bookpagefactory is that
	** new pages get created by add().
	*/

	new_page->halign(x::w::halign::left).valign(x::w::valign::top)
		.add(// The tab's label, an x::w::text_param
		     {"underline"_decoration,
				     "A",
				     "no"_decoration,
				     "ddress"},

		     // The second parameter constructors the page element.

		     [&]
		     (const x::w::factory &page_factory)
		     {
			     auto container=page_factory->create_container
				     ([]
				      (const auto &container)
				      {
					      address_tab(container);
				      },
				      x::w::new_gridlayoutmanager{});

			     container->show_all();
		     },

		     // The third parameter to add() is its options. We
		     // specify x::w::shortcut option for this page.

		     {
		      x::w::shortcut{"Alt", 'A'}
		     });


	/*
	** Like the pagefactory, the same bookpagefactory can be used to
	** add multiple pages.
	*/

	new_page->add({
			"underline"_decoration,
				"P",
				"no"_decoration,
				"hone"},
		[&]
		(const x::w::factory &page_factory)
		{
			page_factory->create_container
				([&]
				 (const auto &container)
				 {
					 phone_tab(container);
				 },
				 x::w::new_gridlayoutmanager{})
				->show_all();
		},
		{
		 x::w::shortcut{"Alt", 'P'}
		});

	/*
	** And, similar to the pagefactory, the insert() method adds new
	** pages before an existing page.
	*/
	new_page=pl->insert(0);

	/*
	** The page's tab doesn't have to be a plain text label, it can
	** be an arbitrary display element. This is done by passing another
	** callable object that takes a factory parameter, instead of the
	** first text_param argument to add().
	*/
	new_page->halign(x::w::halign::left).valign(x::w::valign::top)
		.add([&]
		     (const x::w::factory &tab_factory)
		     {
			     /*
			     ** Providing the text label to add() directly
			     ** is equivalent to calling create_label() on the
			     ** first factory, and show()ing the label.
			     */
			     tab_factory->create_label
				     ({
					     "underline"_decoration,
					     "N",
					     "no"_decoration,
					     "ame"})->show();

		     },
		     [&]
		     (const x::w::factory &page_factory)
		     {

			     /*
			     ** And the second parameter is the same page
			     ** factory, that creates the page element normally.
			     */
			     page_factory->create_container
				     ([&]
				      (const auto &container)
				      {
					      name_tab(container);
				      },
				      x::w::new_gridlayoutmanager{})
				     ->show_all();
		     },
		     {
		      x::w::shortcut{"Alt", 'N'}
		     });

	/*
	** The initial state of the paged container: make element #0
	** visible.
	*/
	pl->open(0);

	/*
	** The on_opened callback gets invoked whenever a new page gets
	** opened. We pointedly install it after the call to open(0), and
	** not before. At this point the entire window is not visible yet,
	** so attempting to move the input focus won't accomplish anything
	** useful.
	*/

	pl->on_opened
		([]
		 (ONLY IN_THREAD,
		  const x::w::book_status_info_t &info)
		 {
			 // If there's a page that's already open
			 // (usually the case), the callback gets an
			 // initial invocation, upon installation, reporting
			 // which page is already open. Check the invocation
			 // trigger for this possibility:

			 if (info.trigger.index() ==
			     x::w::callback_trigger_initial)
				 return;

			 // Page number that was opened.

			 size_t n=info.opened;

			 auto page=info.lock.layout_manager->get_page(n);

			 // In name_tab(), address_tab(), and phone_tab(),
			 // we stashed away the first input field on each
			 // page here.

			 x::w::input_field f=page->appdata;

			 // So, after each page gets opened, we automatically
			 // move keyboard input focus here.

			 f->request_focus();
		 });
}

/*
** The main_window creator lambda, factored out for readability.
*/
static void create_mainwindow(const x::w::main_window &mw)
{
	auto glm=mw->gridlayout();

	auto gf=glm->append_row();

	/*
	** On the first row we create a focusable container using a
	** new_booklayoutmanager.
	**
	** Book layout manager's containers must be created with
	** create_focusable_container(), because the book layout manager
	** takes care of some focusable elements.
	*/

	auto book=gf->create_focusable_container
		([&]
		 (const auto &s)
		 {
			 create_book(s->booklayout());
		 },
		 x::w::new_booklayoutmanager{});

	book->show();
}

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

	auto close_flag=close_flag_ref::create();

	auto mw=x::w::main_window::create([]
					  (const auto &mw)
					  {
						  create_mainwindow(mw);
					  });

	mw->set_window_title("Book!");

	guard(mw->connection_mcguffin());

	mw->on_disconnect([]
			  {
				  exit(1);
			  });

	mw->on_delete([close_flag]
		      (ONLY IN_THREAD,
		       const auto &ignore)
		      {
			      close_flag->close();
		      });

	mw->show();

	close_flag->wait();
}

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

The book layout manager has a slightly different process for creating new pages. Both the page layout manager and the book layout manager have append() and insert() methods. x::w::pagelayoutmanager's append() and insert() methods return a factory that directly creates widgets, with each widget becoming a new page. x::w::booklayoutmanager's append() and insert() methods return a meta-factory object.

The book page factory

x::w::bookpagefactory new_page=layout_manager->append();

new_page->add("Tab",
              []
	      (const x::w::factory &page_factory)
              {

              },
              // Optional shortcut
              {x::w::shortcut{"Alt", 'X'}});

x::w::booklayoutmanager's append and insert methods return an x::w::bookpagefactory which is an intermediate object, and not a factory itself. Each call to x::w::bookpagefactory's add() method creates a new page.

There are two overloaded versions of add(). The first version creates a plain text label for the tab from an x::w::text_param parameter, and the the second parameter is a callable object or a closure that creates the new page. The second version takes another callable object, or a closure, that takes a factory object instead of the x::w::text_param first parameter. This factory gets use used to create the widget representing the new page's tab. Instead of a text string, the tab can be any non-focusable widget. The second version of add()'s parameters are:

  • A factory that creates the widget for the new page's tab.

  • A factory that creates the new page widget (same parameter as the first version of add()).

For either version of add(), the closure must create exactly one widget using each one of its factory parmeters. For the second version of add() the order in which the factories get called is not specified.

add() also takes an optional argument parameter.

  • An overloaded add() with a a factory callback that creates a tab has one optional argument, the tab's keyboard x::w::shortcut.

  • An overloaded add() with a x::w::text_param tab label has two optional parameters: the label's x::w::label_config and the tab's keyboard x::w::shortcut:

    new_page->add({
                    "underline"_decoration,
                    U"H",
                    "no"_decoration,
                    U"elp",
                  },
                  []
    	      (const x::w::factory &page_factory)
                  {
    
                  },
                  // Optional shortcut
                  {x::w::label_config{x::w::center},
                   x::w::shortcut{"Alt", 'X'}});

    This is a somewhat contrived example. Label alignment only makes a difference with multi-line labels.