Chapter 34. Toolbox Dialogs

Index

Creating a toolbox layout manager
Initial dialog position
The on_stabilized() callback
Custom images for checkbox and radio buttons
Toolbox dialogs, using the toolbox layout manager.

A toolbox gets implemented as a custom dialog that uses the toolbox layout manager.

All widgets in a container that uses the toolbox layout manager should have the same size. This gets typically done by creating icon buttons for image files that all have the same size. The toolbox layout manager automatically arranges the icons in a grid; and its container's width gets resizable in even incremental steps. The toolbox layout manager automatically repositions its icon according to the new width, automatically adjusting the container's height.

A top level window cannot have two or more containers with the toolbox layout manager. This is because the toolbox layout manager computes its elements' width and height and sets its size so that its' adjustable only in even increments, and it's only possible to specify a single sizing increment for a window. The toolbox layout manager normally gets used by itself, in a dialog, with no other containers. This is mostly a formal restriction.

toolboxlayoutmanager.C creates a toolbox dialog that gets placed, by default, to the left of its main window. The toolbox dialog contains eight small image icons, a set of default icons from LibCXXW's default theme. The toolbox icons don't do anything, this is for demonstration purposes.

Adjusting the toolbox dialog's width rearranges the icons automatically. Closing and rerunning toolboxlayoutmanager.C preserves the main window's and the dialog's position. The View menu's Toolbox item has an Alt-T keyboard shortcut. This closes and reopens the toolbox dialog. This reopens the toolbox dialog in its default position and size.

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

#include "config.h"
#include <x/mpobj.H>
#include <x/exception.H>
#include <x/destroy_callback.H>
#include <x/weakcapture.H>
#include <x/ref.H>
#include <x/obj.H>
#include <x/singletonptr.H>
#include <x/config.H>
#include <x/appid.H>

#include <x/w/main_window.H>
#include <x/w/dialog.H>
#include <x/w/gridlayoutmanager.H>
#include <x/w/gridfactory.H>
#include <x/w/button.H>
#include <x/w/canvas.H>
#include <x/w/toolboxlayoutmanager.H>
#include <x/w/toolboxfactory.H>
#include <x/w/image_button.H>
#include <x/w/menubarlayoutmanager.H>
#include <x/w/menubarfactory.H>
#include <x/w/listlayoutmanager.H>
#include <x/w/shortcut.H>
#include <x/w/focus_border_appearance.H>
#include <string>
#include <iostream>
#include <sstream>

#include "close_flag.H"

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

// Singleton application object.

class appObj : virtual public x::obj {

public:

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

	// The dialog that uses the toolbox layout manager.
	const x::w::dialog toolbox_dialog;

	// Close application flag.
	const close_flag_ref close_flag;

	appObj(const x::w::main_window &main_window,
	       const x::w::dialog &toolbox_dialog)
		: main_window{main_window},
		  toolbox_dialog{toolbox_dialog},
		  close_flag{close_flag_ref::create()}
	{
	}

	~appObj()=default;

	// View/Toolbox menu item selection callback.
	void view_toolbox(const x::w::list_item_status_info_t &s)
	{
		// The callback gets invoked for several reasons, including
		// selection status, but we are interested only in reasons
		// caused by the menu item getting selected, by keyboard or
		// button.
		switch (s.trigger.index()) {
		case x::w::callback_trigger_key_event:
		case x::w::callback_trigger_button_event:
			break;
		default:
			return;
		}

		auto toolbox_dialog_window=toolbox_dialog->dialog_window;

		// We'll assume that this item's selection status indicates
		// whether the toolbox is visible or not, so we'll show and
		// hide it accordingly. We don't update the selection status
		// here, this is toolbox_visible()'s job.
		//
		// There are other ways the toolbox dialog can get closed,
		// such as the dialog's close button. Our job here is to
		// open or close the dialog itself, and toolbox_visible()
		// keeps track of this menu item's selection status, no
		// matter how the toolbox got opened or closed.

		if (s.layout_manager->selected(s.item_number))
		{
			toolbox_dialog_window->hide();
			return;
		}

		// If the dialog is not currently visible, reset its position
		// to the default position on the left of the main window,
		// before showing it.
		//
		// set_dialog_position() only works when the dialog is not
		// visible.
		toolbox_dialog->set_dialog_position
			(x::w::dialog_position::on_the_left);
		toolbox_dialog_window->show_all();
	}

	void toolbox_visible(bool flag)
	{
		// The toolbox dialog is now hidden or shown. "File" menu is
		// menu #0, "View" menu is menu #1, and its item #0 is the
		// "Toolbox" item. Update its selection status accordingly.

		auto view_menu=main_window->get_menubarlayoutmanager()
			->get_menu(1)->listlayout();

		view_menu->selected(0, flag);
	}
};

// Newly created application
typedef x::ref<appObj> new_app;

// The application singleton.

typedef x::singletonptr<appObj> app;

// The creator for the toolbox dialog.
static void create_toolbox_contents(const x::w::toolboxlayoutmanager &tlm)
{
	// Initialize the contents of the toolbox container.

	auto f=tlm->append_tools();

	// The toolbox icons are really radio buttons. We'll create 8 of them.

	for (size_t i=0; i<8; ++i)
	{
		// Borrow icon images from the default theme. They're all
		// the same size.
		//
		// Each icon is two images: clicked and unclicked.

		static const char *icons[][2]=
			{
			 {"scroll-left1", "scroll-left2"},
			 {"scroll-right1", "scroll-right2"},
			 {"scroll-up1", "scroll-up2"},
			 {"scroll-down1", "scroll-down2"},
			};

		auto icon_set=icons[i % (sizeof(icons)/sizeof(icons[0]))];

		// modify() the default radio button theme.
		//
		// radio_theme() returns a constant, cached object of the
		// appearance scheme for radio buttons. Use modify() to make
		// a copy of the theme object.

		x::w::const_image_button_appearance custom=
			x::w::image_button_appearance::base::radio_theme()
			->modify
			([&]
			 (const x::w::image_button_appearance &custom)
			 {
				 // Bonus for image_button_appearance-s only:
				 // replace the invisible border when there's
				 // no focus with one that's visually visible.

				 custom->focus_border=
					 x::w::focus_border_appearance
					 ::base::visible_thin_theme();

				 // And replace the images with our custom ones:
				 custom->images={icon_set[0], icon_set[1]};
			 });

		// And pass the custom appearance as an additional parameter
		// to create_radio().

		auto b=f->create_radio("toolboxradiogroup@toolboxlayoutmanager.examples.w.libcxx.com",
				       [](const auto &f) {},
				       custom);

		// Install a callback that just prints a message on the
		// console, when the radio button gets selected.
		b->on_activate
			([i]
			 (THREAD_CALLBACK,
			  size_t n,
			  const auto &trigger,
			  const auto &mcguffin)
			 {
				 // Ignore the initial callback invocation.
				 if (std::holds_alternative<x::w::initial>
				     (trigger))
					 return;

				 if (n == 0)
					 return; // Deselected

				 std::cout << "Tool "
					   << (i+1)
					   << std::endl;
			 });
	}
}

// Creator for the main window.

static void create_main_window(const x::w::main_window &mw,
			       x::w::dialogptr &toolbox_dialog)
{
	auto glm=mw->gridlayout();

	// Create a canvas element, to give the window some size.
	x::w::canvas_config config;

	config.width={50, 100, 150};
	config.height={50, 100, 150};

	glm->append_row()->create_canvas(config);

	// The layout manager for the new toolbox.
	x::w::new_toolboxlayoutmanager dialog_lm;

	// Default number of columns is 2. This is the default value.
	dialog_lm.default_width=2;

	// Creating a dialog with this unique identifier.
	x::w::create_dialog_args
		args{"toolbox_dialog1.toolboxlayoutmanager.examples.w.libcxx.com"};

	// Initial position of this dialog is to the left of the main window.
	args.restore(x::w::dialog_position::on_the_left);

	// The new dialog's layout manager is th etoolbox layout manager.
	args.dialog_layout=dialog_lm;

	// The toolbox dialog will not grab input focus when shown. Some
	// window managers may not need this when specifying a "toolbar" for
	// set_window_type(), below.
	args.grab_input_focus=false;

	auto d=mw->create_dialog
		(args,
		 []
		 (const x::w::dialog &d)
		 {
			 create_toolbox_contents(d->dialog_window
						 ->toolboxlayout());
		 });

	// Set the X window type. Most window manager
	// probably ignore this, but we'll go ahead and do this.

	d->dialog_window->set_window_type("toolbar,normal");

	// If the window manager gives the dialog a close button, have its
	// action be to close the dialog.
	d->dialog_window->on_delete
		([]
		 (THREAD_CALLBACK,
		  const auto &ignore)
		 {
			 app my_app;

			 if (!my_app)
				 return;

			 my_app->toolbox_dialog->dialog_window->hide();
		 });

	// Attach a state update callback to the dialog, to report when it's
	// shown or hidden to toolbox_visible(). This updates the selection
	// status of View/Toolbox.

	d->dialog_window->on_state_update
		([]
		 (ONLY IN_THREAD,
		  const x::w::element_state &s,
		  const x::w::busy &mcguffin)
		 {
			 app my_app;

			 if (!my_app)
				 return;

			 if (s.state_update == s.after_hiding)
				 my_app->toolbox_visible(false);

			 if (s.state_update == s.after_showing)
				 my_app->toolbox_visible(true);
		 });

	// Application menu.
	auto new_menubar=mw->get_menubarlayoutmanager()->append_menus();

	// "File" menu, with just a "Quit" option.

	new_menubar->add_text
		("File",
		 []
		 (const x::w::listlayoutmanager &lm)
		 {
			 lm->append_items({
					   x::w::shortcut{"Alt",'Q'},
					   []
					   (THREAD_CALLBACK,
					    const auto &ignore)
					   {
						   app my_app;

						   if (!my_app)
							   return;
						   my_app->close_flag->close();
					   },
					   "Quit"});

		 });

	// The "View" menu with the "Toolbox" option.

	new_menubar->add_text
		("View",
		 []
		 (const x::w::listlayoutmanager &lm)
		 {
			 lm->append_items
				 ({
				   x::w::shortcut{"Alt",'T'},
				   []
				   (THREAD_CALLBACK,
				    const auto &status)
				   {
					   app my_app;

					   if (!my_app)
						   return;

					   my_app->view_toolbox(status);
				   },
				   "Toolbox"});

		 });

	mw->get_menubar()->show();

	// When the main window becomes visible and its position and size
	// are stable, show the dialog immediately afterwards.
	mw->on_stabilized
		([]
		 (THREAD_CALLBACK,
		  const x::w::busy &mcguffin)
		 {
			 app my_app;

			 if (!my_app)
				 return;

			 my_app->toolbox_dialog->dialog_window->show_all();
		 });

	toolbox_dialog=d;
}

// Create the new application object, optionally restoring previously-
// used position on the screen.

new_app create_app()
{
	x::w::dialogptr toolbox_dialog;

	auto main_window=x::w::main_window
		::create([&]
			 (const auto &main_window)
			 {
				 create_main_window(main_window,
						    toolbox_dialog);
			 });

	main_window->set_window_title("Toolbox");

	return new_app::create(main_window, toolbox_dialog);
}

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

	new_app my_app=create_app();

	guard(my_app->main_window->connection_mcguffin());

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

	my_app->main_window->on_delete
		([]
		 (THREAD_CALLBACK,
		  const auto &ignore)
		 {
			 app my_app;

			 if (my_app)
				 my_app->close_flag->close();
		 });

	app created_app{my_app};

	my_app->main_window->show_all();

	x::mpcobj<bool>::lock
		lock{my_app->close_flag->flag};

	lock.wait([&] { return *lock; });
}

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

Creating a toolbox layout manager

Passing a x::w::new_toolboxlayoutmanager as a 2nd parameter to create_container() creates a new container that uses the x::w::toolboxlayoutmanager; but the toolbox layout manager usually gets used as a layout manager for a dialog. This is done by creating a custom dialog with create_dialog(), and setting dialog_layout in the x::w::create_dialog_args parameter. This creates a new dialog that uses the toolbox layout manager instead of the default grid layout manager.