/* ** Copyright 2017-2021 Double Precision, Inc. ** See COPYING for distribution information. */ #include "config.h" #include "close_flag.H" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include std::string x::appid() noexcept { return "inputfieldsandbuttons.examples.w.libcxx.com"; } // inputfields.inc.H is autogenerated from inputfields.xml #include #include "inputfields.inc.H" // inputfields gets installed in main_window->appdata, below. class appdataObj : virtual public x::obj, public inputfields { public: using inputfields::inputfields; }; typedef x::ref appdata_t; // This is the creator lambda, that gets passed to create_mainwindow() below, // factored out for readability. void create_mainwindow(const x::w::main_window &main_window, const close_flag_ref &close_flag) { // Each display element contains an optional 'appdata', a // generic LibCXX object, an x::ptr, that the library ignores, // and is for application to use. // // This can be a convenient place to store all display elements that // get created in the main application window. // // As discussed in the library's documentation, parent display // elements own references to their child display element, and // this main_window is the topmost display elements, so it's a // logical place to store all display elements for the application // to quickly reference. // // Using some innermost display element to store references to parent // display elements creates a circular reference, which will interfere // with LibCXX's object model anyway. // // We're going to use some framework templates and classes from the // LibCXX base class library. // // inputfields.inc.H gets generated from inputfields.xml by the // Makefile, and included above, and declares an inputfieldsptr class // containing ptrs to our input_fields, which we can install at our // leisure. inputfieldsptr new_inputfields; auto layout=main_window->gridlayout(); // The main window's grid will have two columns. The first column // in each row will have a label, the second column will have a // text input field. // The labels will be aligned against the right margin of the // first column, to make them flush to the input field. // First row in the main window contains a label and an input field. x::w::gridfactory factory=layout->append_row(); // Besides the right alignment, set the vertical alignment of the // label to middle, it looks better. factory->halign(x::w::halign::right) .valign(x::w::valign::middle) .create_label({"Subject:"}); // And just in case we have some weird theme where the input field // is shorter in height than the label, align it vertically also. // The input fields initial contents are empty, and it is size to // be 30 columns. auto subject_field=factory->valign(x::w::valign::middle) .create_input_field("", {30}); // Give the subject field a tooltip. subject_field->create_tooltip("A brief title"); // Save it in new_inputfields new_inputfields.subject=subject_field; // Second row in the main window factory=layout->append_row(); // It doesn't look like we need to vertically align either the label // or the column. factory->halign(x::w::halign::right).create_label({"Text:"}); // That's because the second row's input field will have 30 columns // and four rows. // // Also, let's make the input field use a non-default, proportional // font, and set its initial contents to "Hello". // // The first parameter to create_input_field() is an x::w::text_param // that can specify the input field's font. auto text_field=factory->create_input_field({"arial"_font, "Hello,\n\n"}, {30, 4}); // The tooltip for the text field. Slightly longer tooltip, word // wrap it to 30 mm width. x::w::label_config tooltip_label_config; tooltip_label_config.widthmm=30; text_field->create_tooltip("A brief message, a few lines long.", tooltip_label_config); // Save it in new_inputfields new_inputfields.text=text_field; // The third row of the main window will have all the buttons. // // First, create a nested container, which will span both columns, // and also use the grid layout manager. factory=layout->append_row(); // Give the container some extra padding on the top. factory->top_padding(3.0) // Span it across both columns .colspan(2) .create_container ([&] (const auto &container) { // Get the grid layout manager for the container. auto layout=container->gridlayout(); auto factory=layout->append_row(); // Cancel button, on the beginning of the row. auto cancel=factory->create_button ({"Cancel"}, // Button options: // // Esc key for a shortcut x::w::shortcut{'\e'}); // Next to it is a "Reset" button, with an // underlined "R", with an "Alt"-R shortcut. // "R" can be specified in upper or lowercase. auto reset=factory->create_button ({"underline"_decoration, "R", "no"_decoration, "eset"}, // Button options, keyboard shortcut. x::w::shortcut{"Alt",'R'}); // Add empty space here, between the buttons. // By default, create_canvas() creates a canvas // that specifies 0 for its minimum and preferred // size, with the maximum size unspecified (effectively // infinite. // // This allows the grid layout manager to // size the canvas spacer to fill the entire width // of the edit button row. // // There's also a create_canvas() that takes a // creator lambda, and explicit metrics specifications // as parameters, for implementing more nuanced // spacing. factory->create_canvas(); // The "Ok" button at the end of the row. // An alternative overload of create_button() // takes a generic callable object as the first // parameter instead of a label. auto ok=factory->create_button ([] (const x::w::factory &f) { // The callable object uses the // factory parameter that it // receives as its own parameter // (and not the factory that's // creating the button itself). // // The callable object uses this // factory to create the display // element that becomes the button's // contents. This can be a container // with many display elements. // // Using create_label() is equivalent // to using create_button() directly // with a text string: auto l=f->create_label("Ok"); // As with any other display element, // it must be show()n to be visible. l->show(); }, // Two options for this button. The second // parameter to create_button() is an // variadic template for all needed options, // that must be passed in as a single // parameter, in the listed order, see the // documentation. // // Uniform initialization syntax is the // easiest way to go: { // Thicker border, visual indication // of a default button. x::w::default_button(), // Enter key for a shortcut x::w::shortcut{'\n'} }); // Specify what happens when the buttons get // activated. cancel->on_activate([close_flag] (ONLY IN_THREAD, const x::w::callback_trigger_t &trigger, const x::w::busy &ignore) { std::cout << "Cancel" << std::endl; close_flag->close(); }); // Note that the Reset button's callback captures // the input field object by value. This is ok, // the input fields are not parent display elements // of the Reset button. When the main window gets // destroyed, the main window object drops its // references to its display elements, including // the Reset button, which then drops its reference // on the input field elements, which allows them // to be destroyed. reset->on_activate([text_field, subject_field] (ONLY IN_THREAD, const x::w::callback_trigger_t &trigger, const x::w::busy &ignore) { text_field->set(""); subject_field->set(""); }); ok->on_activate([close_flag] (ONLY IN_THREAD, const x::w::callback_trigger_t &trigger, const x::w::busy &ignore) { std::cout << "Ok" << std::endl; close_flag->close(); }); }, x::w::new_gridlayoutmanager()); // Now that our fields have been saved in new_inputfields, we can // construct an ref out of them, and attach them to main_window's // appdata. // // This create_mainwindow() is, after all, main_window's creator, so // this is a part of our mission statement. main_window->appdata=appdata_t::create(new_inputfields); } void inputfieldsandbuttons() { 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_mainwindow(main_window, close_flag); }); main_window->on_disconnect([] { _exit(1); }); guard(main_window->connection_mcguffin()); main_window->set_window_title("Enter a message"); main_window->on_delete ([close_flag] (ONLY IN_THREAD, const x::w::busy &ignore) { close_flag->close(); }); main_window->show_all(); close_flag->wait(); // Retrieve the appdata_t, with our fields, that the creator attached // to the main_window. appdata_t appdata=main_window->appdata; // Retrieve each input_field, and get() its contents. // Each input_field must be locked, first: x::w::input_lock subject_lock{appdata->subject}; x::w::input_lock text_lock{appdata->text}; std::cout << "Subject (" << subject_lock.size() << " character): " << subject_lock.get() << std::endl << std::endl << text_lock.get(); // Normally it's possible that a character gets typed after // size() returns, and before get() gets called, hence the // get() would return a shorter or a smaller string (let's ignore // for the moment that size() returns unicode character count, // and get() returns UTF-8). // // However, the input_lock blocks the internal library execution // thread from accessing the contents of the field, so that can't // happen. } int main(int argc, char **argv) { try { inputfieldsandbuttons(); } catch (const x::exception &e) { e->caught(); exit(1); } return 0; }