/* Drop Downs * * The Gtk::DropDown widget is a modern alternative to Gtk::ComboBox. * It uses list models instead of tree models, and the content is * displayed using widgets instead of cell renderers. * * The examples here demonstrate how to use different kinds of * list models with Gtk::DropDown, how to use search and how to * display the selected item differently from the presentation * in the popup. */ #include #include class StringHolder : public Glib::Object { public: Glib::ustring m_title; std::optional m_icon; std::optional m_description; protected: StringHolder(const Glib::ustring& title, const std::optional& icon, const std::optional& description); public: static Glib::RefPtr create( const Glib::ustring& title, const std::optional& icon, const std::optional& description); }; StringHolder::StringHolder(const Glib::ustring& title, const std::optional& icon, const std::optional& description) : m_title(title), m_icon(icon), m_description(description) { } Glib::RefPtr StringHolder::create( const Glib::ustring& title, const std::optional& icon, const std::optional& description) { return Glib::make_refptr_for_instance( new StringHolder(title, icon, description)); } class Example_DropDown : public Gtk::Window { public: Example_DropDown(); ~Example_DropDown() override; protected: void strings_setup_item_single_line(const Glib::RefPtr& item); void strings_setup_item_full(const Glib::RefPtr& item); void selected_item_changed(const Gtk::DropDown* dropdown, const Glib::RefPtr& item); void strings_bind_item(const Glib::RefPtr& item, Gtk::DropDown* dropdown); void strings_unbind_item(const Glib::RefPtr& item); Glib::RefPtr strings_factory_new(Gtk::DropDown* dropdown, bool full); Glib::RefPtr strings_model_new(const std::vector& titles, const std::vector& icons, const std::vector& descriptions); Gtk::DropDown* drop_down_new_from_strings(const std::vector& titles, const std::vector& icons, const std::vector& descriptions); Gtk::Box m_box; Gtk::CheckButton m_check; }; Gtk::Window* do_dropdown() { return new Example_DropDown(); } void Example_DropDown::strings_setup_item_single_line(const Glib::RefPtr& item) { auto box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 10); auto image = Gtk::make_managed(); auto title = Gtk::make_managed(); title->set_xalign(0.0); auto checkmark = Gtk::make_managed(); checkmark->set_from_icon_name("object-select-symbolic"); box->append(*image); box->append(*title); box->append(*checkmark); item->set_data("title", title); item->set_data("image", image); item->set_data("checkmark", checkmark); item->set_child(*box); } void Example_DropDown::strings_setup_item_full(const Glib::RefPtr& item) { auto image = Gtk::make_managed(); auto title = Gtk::make_managed(); title->set_xalign(0.0); auto description = Gtk::make_managed(); description->set_xalign(0.0); description->add_css_class("dim-label"); auto checkmark = Gtk::make_managed(); checkmark->set_from_icon_name("object-select-symbolic"); auto box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 10); auto box2 = Gtk::make_managed(Gtk::Orientation::VERTICAL, 2); box->append(*image); box->append(*box2); box2->append(*title); box2->append(*description); box->append(*checkmark); item->set_data("title", title); item->set_data("image", image); item->set_data("description", description); item->set_data("checkmark", checkmark); item->set_child(*box); } void Example_DropDown::selected_item_changed(const Gtk::DropDown* dropdown, const Glib::RefPtr& item) { auto checkmark = static_cast(item->get_data("checkmark")); if (dropdown->get_selected_item() == item->get_item()) checkmark->set_opacity(1.0); else checkmark->set_opacity(0.0); } void Example_DropDown::strings_bind_item(const Glib::RefPtr& item, Gtk::DropDown* dropdown) { auto holder = std::dynamic_pointer_cast(item->get_item()); auto title = static_cast(item->get_data("title")); auto image = static_cast(item->get_data("image")); auto description = static_cast(item->get_data("description")); auto checkmark = static_cast(item->get_data("checkmark")); title->set_label(holder->m_title); if (image) { if (holder->m_icon) image->set_from_icon_name(*holder->m_icon); else image->clear(); image->set_visible(holder->m_icon.has_value()); } if (description) { description->set_label(*holder->m_description); description->set_visible(holder->m_description.has_value()); } auto popup = title->get_ancestor(GTK_TYPE_POPOVER); if (popup && popup->is_ancestor(*dropdown)) { checkmark->set_visible(true); auto connection = dropdown->property_selected_item().signal_changed().connect( sigc::bind(sigc::mem_fun(*this, &Example_DropDown::selected_item_changed), dropdown, item)); item->set_data("connection", new sigc::connection(connection), Glib::destroy_notify_delete); selected_item_changed(dropdown, item); } else { checkmark->set_visible(false); } } void Example_DropDown::strings_unbind_item(const Glib::RefPtr& item) { if (auto connection = static_cast(item->get_data("connection"))) { connection->disconnect(); item->set_data("connection", nullptr); } } Glib::RefPtr Example_DropDown::strings_factory_new(Gtk::DropDown* dropdown, bool full) { auto factory = Gtk::SignalListItemFactory::create(); if (full) factory->signal_setup().connect( sigc::mem_fun(*this, &Example_DropDown::strings_setup_item_full)); else factory->signal_setup().connect( sigc::mem_fun(*this, &Example_DropDown::strings_setup_item_single_line)); factory->signal_bind().connect( sigc::bind(sigc::mem_fun(*this, &Example_DropDown::strings_bind_item), dropdown)); factory->signal_unbind().connect( sigc::mem_fun(*this, &Example_DropDown::strings_unbind_item)); return factory; } Glib::RefPtr Example_DropDown::strings_model_new( const std::vector& titles, const std::vector& icons, const std::vector& descriptions) { auto store = Gio::ListStore::create(); for (guint i = 0; i < titles.size(); i++) { auto icon = icons.empty() ? std::nullopt : std::make_optional(icons[i]); auto description = descriptions.empty() ? std::nullopt : std::make_optional(descriptions[i]); auto holder = StringHolder::create(titles[i], icon, description); store->append(holder); } return store; } Gtk::DropDown* Example_DropDown::drop_down_new_from_strings( const std::vector& titles, const std::vector& icons, const std::vector& descriptions) { g_return_val_if_fail(!titles.empty(), nullptr); g_return_val_if_fail(icons.empty() || icons.size() == titles.size(), nullptr); g_return_val_if_fail(descriptions.empty() || icons.size() == descriptions.size(), nullptr); auto model = strings_model_new(titles, icons, descriptions); auto dropdown = Gtk::make_managed(model); auto factory = strings_factory_new(dropdown, false); Glib::RefPtr list_factory; if (!icons.empty() || !descriptions.empty()) list_factory = strings_factory_new(dropdown, true); dropdown->set_factory(factory); dropdown->set_list_factory(list_factory); return dropdown; } Example_DropDown::Example_DropDown() : m_box(Gtk::Orientation::VERTICAL, 10), m_check("Enable search") { const std::vector times{ "1 minute", "2 minutes", "5 minutes", "20 minutes" }; const std::vector many_times{ "1 minute", "2 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes", "25 minutes", "30 minutes", "35 minutes", "40 minutes", "45 minutes", "50 minutes", "55 minutes", "1 hour", "2 hours", "3 hours", "5 hours", "6 hours", "7 hours", "8 hours", "9 hours", "10 hours", "11 hours", "12 hours" }; const std::vector device_titles{ "Digital Output", "Headphones", "Digital Output", "Analog Output" }; const std::vector device_icons{ "audio-card-symbolic", "audio-headphones-symbolic", "audio-card-symbolic", "audio-card-symbolic" }; const std::vector device_descriptions{ "Built-in Audio", "Built-in audio", "Thinkpad Tunderbolt 3 Dock USB Audio", "Thinkpad Tunderbolt 3 Dock USB Audio" }; set_title("Drop Downs"); set_resizable(false); m_box.set_margin(10); set_child(m_box); auto model = Pango::CairoFontMap::get_default(); auto dropdown = Gtk::make_managed(model); dropdown->set_selected(0); auto expression = Gtk::ClosureExpression::create( [](const Glib::RefPtr& item)->Glib::ustring { auto family = std::dynamic_pointer_cast(item); return family ? family->get_name() : ""; }); dropdown->set_expression(expression); m_box.append(*dropdown); auto adj = Gtk::Adjustment::create(-1, -1, model->get_n_items()); auto spin = Gtk::make_managed(adj); spin->set_halign(Gtk::Align::START); Glib::Binding::bind_property(dropdown->property_selected(), spin->property_value(), Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::BIDIRECTIONAL); m_box.append(*spin); Glib::Binding::bind_property(dropdown->property_enable_search(), m_check.property_active(), Glib::Binding::Flags::SYNC_CREATE | Glib::Binding::Flags::BIDIRECTIONAL); m_box.append(m_check); dropdown = drop_down_new_from_strings(times, {}, {}); m_box.append(*dropdown); dropdown = drop_down_new_from_strings(many_times, {}, {}); dropdown->set_enable_search(); expression = Gtk::ClosureExpression::create( [](const Glib::RefPtr& item)->Glib::ustring { return std::dynamic_pointer_cast(item)->m_title; }); dropdown->set_expression(expression); m_box.append(*dropdown); dropdown = drop_down_new_from_strings(device_titles, device_icons, device_descriptions); m_box.append(*dropdown); } Example_DropDown::~Example_DropDown() { }