/* Copyright (C) 2001 The gtkmm Development Team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "demos.h" #include "demowindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using std::isspace; using std::strlen; namespace { class DemoColumns : public Glib::Object { public: Glib::ustring m_title; std::string m_filename; type_slotDo m_slot; // The method to call. const Demo* m_children{nullptr}; static Glib::RefPtr create(const Demo& demo) { return Glib::make_refptr_for_instance(new DemoColumns(demo)); } protected: DemoColumns(const Demo& demo) : m_title(demo.title), m_filename(demo.filename), m_slot(demo.slot), m_children(demo.children) { } }; } // anonymous namespace //static DemoWindow* DemoWindow::m_pDemoWindow = nullptr; //static DemoWindow* DemoWindow::get_demo_window() { return m_pDemoWindow; } DemoWindow::DemoWindow() : m_RunButton("Run"), m_HBox(Gtk::Orientation::HORIZONTAL), m_TextWidget_Info(false), m_TextWidget_Source(true) { m_pDemoWindow = this; configure_header_bar(); set_child(m_HBox); //Tree: auto root = create_demo_model(); auto tree_model = Gtk::TreeListModel::create(root, sigc::mem_fun(*this, &DemoWindow::create_demo_model), false, true); m_refSingleSelection = Gtk::SingleSelection::create(tree_model); m_refSingleSelection->signal_selection_changed().connect( sigc::mem_fun(*this, &DemoWindow::on_selection_changed)); auto factory = Gtk::SignalListItemFactory::create(); factory->signal_setup().connect( sigc::mem_fun(*this, &DemoWindow::on_setup_listitem)); factory->signal_bind().connect( sigc::mem_fun(*this, &DemoWindow::on_bind_listitem)); m_ListView.set_model(m_refSingleSelection); m_ListView.set_factory(factory); m_ListView.signal_activate().connect( sigc::mem_fun(*this, &DemoWindow::on_listview_row_activated)); m_ListView.set_size_request(200, -1); //SideBar m_SideBar.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC); m_SideBar.add_css_class("sidebar"); m_SideBar.set_hexpand(false); m_SideBar.set_child(m_ListView); m_HBox.append(m_SideBar); //Notebook: m_Notebook.popup_enable(); m_Notebook.set_scrollable(); m_Notebook.append_page(m_TextWidget_Info, "_Info", true); //true = use mnemonic. m_Notebook.append_page(m_TextWidget_Source, "_Source", true); //true = use mnemonic. m_Notebook.get_page(m_TextWidget_Info)->property_tab_expand() = true; m_Notebook.get_page(m_TextWidget_Source)->property_tab_expand() = true; m_Notebook.set_expand(true); m_HBox.append(m_Notebook); m_HBox.set_vexpand(true); set_default_size(800, 600); load_file(testgtk_demos[0].filename); m_refSingleSelection->set_selected(0); m_ListView.grab_focus(); } DemoWindow::~DemoWindow() { on_example_window_hide(); //delete the example window if there is one. if (m_pDemoWindow == this) m_pDemoWindow = nullptr; } void DemoWindow::configure_header_bar() { m_HeaderBar.set_show_title_buttons(); m_HeaderBar.pack_start(m_RunButton); m_RunButton.add_css_class("suggested-action"); m_RunButton.signal_clicked().connect(sigc::mem_fun(*this, &DemoWindow::on_run_button_clicked)); set_titlebar(m_HeaderBar); } Glib::RefPtr DemoWindow::create_demo_model( const Glib::RefPtr& item) { auto col = std::dynamic_pointer_cast(item); if (col && !col->m_children) // An item without children, i.e. a leaf in the tree. return {}; auto result = Gio::ListStore::create(); const Demo* demo = col ? col->m_children : testgtk_demos; for (; demo->title; ++demo) result->append(DemoColumns::create(*demo)); return result; } void DemoWindow::on_setup_listitem(const Glib::RefPtr& list_item) { // Each ListItem contains a TreeExpander, which contains a Label. // The Label shows the DemoColumns::m_title. That's done in on_bind_listitem(). auto expander = Gtk::make_managed(); auto label = Gtk::make_managed(); expander->set_child(*label); list_item->set_child(*expander); } void DemoWindow::on_bind_listitem(const Glib::RefPtr& list_item) { auto row = std::dynamic_pointer_cast(list_item->get_item()); if (!row) return; // Only leaves in the tree can be selected. list_item->set_selectable(!row->is_expandable()); auto col = std::dynamic_pointer_cast(row->get_item()); if (!col) return; auto expander = dynamic_cast(list_item->get_child()); if (!expander) return; expander->set_list_row(row); auto label = dynamic_cast(expander->get_child()); if (!label) return; label->set_text(col->m_title); } void DemoWindow::on_selection_changed(unsigned int /*position*/, unsigned int /*n_items*/) { auto item = m_refSingleSelection->get_selected_item(); auto row = std::dynamic_pointer_cast(item); if (!row) return; auto col = std::dynamic_pointer_cast(row->get_item()); if (!col) return; set_title(col->m_title); if (!col->m_filename.empty()) load_file(col->m_filename); } void DemoWindow::on_listview_row_activated(unsigned int position) { auto item = std::dynamic_pointer_cast(m_ListView.get_model())->get_object(position); run_example(item); } void DemoWindow::on_run_button_clicked() { auto item = m_refSingleSelection->get_selected_item(); run_example(item); } void DemoWindow::run_example(const Glib::RefPtr& item) { auto row = std::dynamic_pointer_cast(item); if (!row) return; auto col = std::dynamic_pointer_cast(row->get_item()); if (!col) return; const type_slotDo& slot = col->m_slot; if (slot && (m_pWindow_Example = slot())) { m_pWindow_Example->set_transient_for(*this); m_pWindow_Example->set_modal(true); m_pWindow_Example->set_hide_on_close(true); m_pWindow_Example->signal_hide().connect(sigc::mem_fun(*this, &DemoWindow::on_example_window_hide)); m_pWindow_Example->set_visible(true); } } void DemoWindow::on_example_window_hide() { delete m_pWindow_Example; m_pWindow_Example = nullptr; } void DemoWindow::load_file(const std::string& filename) { if ( m_current_filename == filename ) { return; } else { // Show extra data files for this demo, if any. remove_data_tabs(); add_data_tabs(filename); m_current_filename = filename; m_TextWidget_Info.wipe(); m_TextWidget_Source.wipe(); auto refBufferInfo = m_TextWidget_Info.get_buffer(); auto refBufferSource = m_TextWidget_Source.get_buffer(); Glib::RefPtr bytes; try { bytes = Gio::Resource::lookup_data_global("/sources/" + filename); } catch (const Gio::ResourceError& ex) { std::cerr << "Cannot open source for " << filename << ": " << ex.what() << std::endl; return; } gsize data_size = 0; gchar** lines = g_strsplit(static_cast(bytes->get_data(data_size)), "\n", -1); bytes.reset(); int state = 0; bool in_para = false; auto start = refBufferInfo->get_iter_at_offset(0); for (std::size_t i = 0; lines[i] != NULL; i++) { /* Make sure \r is stripped at the end for the poor Windows people */ lines[i] = g_strchomp(lines[i]); gchar *p = lines[i]; gchar *q = nullptr; gchar *r = nullptr; switch (state) { case 0: /* Reading title */ while (*p == '/' || *p == '*' || isspace (*p)) p++; r = p; while (*r != '/' && strlen (r)) r++; if (strlen (r) > 0) p = r + 1; q = p + strlen (p); while (q > p && isspace (*(q - 1))) q--; if (q > p) { auto end = start; const Glib::ustring strTemp (p, q); end = refBufferInfo->insert(end, strTemp); start = end; start.backward_chars(strTemp.length()); refBufferInfo->apply_tag_by_name("title", start, end); start = end; state++; } break; case 1: /* Reading body of info section */ while (isspace (*p)) p++; if (*p == '*' && *(p + 1) == '/') { start = refBufferSource->get_iter_at_offset(0); state++; } else { int len; while (*p == '*' || isspace (*p)) p++; len = strlen (p); while (isspace (*(p + len - 1))) len--; if (len > 0) { if (in_para) { start = refBufferInfo->insert(start, " "); } start = refBufferInfo->insert(start, Glib::ustring(p, p + len)); in_para = 1; } else { start = refBufferInfo->insert(start, "\n"); in_para = 0; } } break; case 2: /* Skipping blank lines */ while (isspace (*p)) p++; if (!*p) break; p = lines[i]; state++; [[fallthrough]]; case 3: /* Reading program body */ start = refBufferSource->insert(start, p); start = refBufferSource->insert(start, "\n"); break; } // end switch state } // end for i g_strfreev (lines); m_TextWidget_Source.fontify(); } } void DemoWindow::add_data_tabs(const std::string& filename) { // We can get the resource_dir from the filename by removing "example_" and ".cc". const auto resource_dir = "/" + filename.substr(8, filename.size() - 11); std::vector resources; try { resources = Gio::Resource::enumerate_children_global(resource_dir); } catch (const Gio::ResourceError& ex) { // Ignore this exception. It's no error, if resource_dir does not exist. } for (std::size_t i = 0; i < resources.size(); ++i) { const auto resource_name = resource_dir + "/" + resources[i]; std::string suffix; auto suffix_pos = resources[i].find_last_of('.'); if (suffix_pos != std::string::npos) suffix = resources[i].substr(suffix_pos); Gtk::Widget* widget = nullptr; if (suffix == ".gif" || suffix == ".jpg" || suffix == ".png") { // It's an image. auto image = new Gtk::Image(); image->set_from_resource(resource_name); if (image->get_paintable()) widget = image; else delete image; } else if (suffix == ".webm") { // It's a video. auto video = new Gtk::Video(); video->set_resource(resource_name); if (video->get_file()) widget = video; else delete video; } if (!widget) { // So we've used the best API available to figure out it's // not an image or a video. Let's try something else then. Glib::RefPtr bytes; try { bytes = Gio::Resource::lookup_data_global(resource_name); } catch (const Gio::ResourceError& ex) { std::cerr << "Can't get data from resource '" << resource_name << "': " << ex.what() << std::endl; continue; } gsize data_size = 0; auto data = static_cast(bytes->get_data(data_size)); if (g_utf8_validate(data, data_size, nullptr)) { // Looks like it parses as text. Dump it into a TextWidget then! auto textwidget = new TextWidget(false); auto refBuffer = textwidget->get_buffer(); refBuffer->set_text(data, data + data_size); widget = textwidget; } else { std::cerr << "Don't know how to display resource '" << resource_name << "'" << std::endl; continue; } } m_Notebook.append_page(*Gtk::manage(widget), Glib::ustring(resources[i])); m_Notebook.get_page(*widget)->property_tab_expand() = true; } } void DemoWindow::remove_data_tabs() { // Remove all tabs except Info and Source. for (auto i = m_Notebook.get_n_pages(); i-- > 2;) { m_Notebook.remove_page(i); } }