Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
template-list.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/* Authors:
3 * Martin Owens <doctormo@geek-2.com>
4 *
5 * Copyright (C) 2022 Authors
6 *
7 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
8 */
9
10#include "template-list.h"
11
12#include <cairomm/surface.h>
13#include <giomm/liststore.h>
14#include <glibmm/markup.h>
15#include <glibmm/refptr.h>
16#include <gtkmm/expression.h>
17#include <gtkmm/gridview.h>
18#include <map>
19#include <glib/gi18n.h>
20#include <gdkmm/pixbuf.h>
21#include <gtkmm/builder.h>
22#include <gtkmm/iconview.h>
23#include <gtkmm/liststore.h>
24#include <gtkmm/numericsorter.h>
25#include <gtkmm/scrolledwindow.h>
26#include <gtkmm/treemodel.h>
27#include <gtkmm/sortlistmodel.h>
28#include <gtkmm/singleselection.h>
29#include <memory>
30#include <glibmm/miscutils.h>
31#include <gtkmm/filterlistmodel.h>
32
33#include "document.h"
34#include "extension/db.h"
35#include "extension/template.h"
37#include "io/resource.h"
38#include "ui/builder-utils.h"
40#include "ui/util.h"
41#include "ui/svg-renderer.h"
42
43using namespace Inkscape::IO::Resource;
45
46namespace Inkscape::UI::Widget {
47
48struct TemplateList::TemplateItem : public Glib::Object {
49 Glib::ustring name;
50 Glib::ustring label;
51 Glib::ustring tooltip;
52 Glib::RefPtr<Gdk::Texture> icon;
53 Glib::ustring key;
54 int priority;
55 Glib::ustring category;
56
57 static Glib::RefPtr<TemplateItem> create(const Glib::ustring& name, const Glib::ustring& label, const Glib::ustring& tooltip,
58 Glib::RefPtr<Gdk::Texture> icon, Glib::ustring key, int priority, const Glib::ustring& category) {
59
60 auto item = Glib::make_refptr_for_instance<TemplateItem>(new TemplateItem());
61 item->name = name;
62 item->label = label;
63 item->tooltip = tooltip;
64 item->icon = icon;
65 item->key = key;
66 item->priority = priority;
67 item->category = category;
68 return item;
69 }
70private:
71 TemplateItem() = default;
72};
73
74
75TemplateList::TemplateList(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &refGlade)
76 : Gtk::Stack(cobject)
77{
78}
79
80static Glib::ustring all_templates = "All templates";
81
86{
87 // same width for all items
88 set_hhomogeneous();
89 // height can vary per row
90 set_vhomogeneous(false);
91 // track page switching
92 property_visible_child_name().signal_changed().connect([this]() {
93 _signal_switch_page.emit(get_visible_child_name());
94 });
95
96 std::map<std::string, Glib::RefPtr<Gio::ListStore<TemplateItem>>> stores;
97
100
101 Glib::RefPtr<Gio::ListStore<TemplateItem>> all;
102 if (add_page == All) {
103 all = generate_category(all_templates, allow_unselect);
104 }
105
106 int group = 0;
107 for (auto tmod : extensions) {
108 for (auto preset : tmod->get_presets(mode)) {
109 auto const &cat = preset->get_category();
110 if (add_page == Custom && cat != "Custom") continue;
111
112 if (auto it = stores.lower_bound(cat);
113 it == stores.end() || it->first != cat)
114 {
115 try {
116 group += 10000;
117 it = stores.emplace_hint(it, cat, generate_category(cat, allow_unselect));
118 it->second->remove_all();
119 } catch (UIBuilderError const& error) {
120 g_error("Error building templates %s\n", error.what());
121 return;
122 }
123
124 if (add_page == Custom) {
125 // add new template placeholder
126 auto const filepath = Glib::build_filename("icons", "custom.svg");
127 auto const fullpath = get_filename(TEMPLATES, filepath.c_str(), false, true);
128 auto icon = to_texture(icon_to_pixbuf(fullpath, get_scale_factor()));
129 auto templ = TemplateItem::create(
130 Glib::Markup::escape_text(_("<new template>")),
131 "", "", icon, "-new-template-", -1, cat
132 );
133 stores[cat]->append(templ);
134 }
135 }
136
137 auto& name = preset->get_name();
138 auto& desc = preset->get_description();
139 auto& label = preset->get_label();
140 auto tooltip = _(desc.empty() ? name.c_str() : desc.c_str());
141 auto trans_label = label.empty() ? "" : _(label.c_str());
142 auto icon = to_texture(icon_to_pixbuf(preset->get_icon_path(), get_scale_factor()));
143
144 auto templ = TemplateItem::create(
145 Glib::Markup::escape_text(name),
146 Glib::Markup::escape_text(trans_label),
147 Glib::Markup::escape_text(tooltip),
148 icon, preset->get_key(), group + preset->get_sort_priority(),
149 cat
150 );
151 stores[cat]->append(templ);
152 if (all) {
153 all->append(templ);
154 }
155 }
156 }
157
159
160 if (allow_unselect) {
162 }
163}
164
168Cairo::RefPtr<Cairo::ImageSurface> TemplateList::icon_to_pixbuf(std::string const &path, int scale)
169{
170 // TODO: cache to filesystem. This function is a major bottleneck for startup time (ca. 1 second)!
171 // The current memory-based caching only catches the case where multiple templates share the same icon.
172 static std::map<std::string, Cairo::RefPtr<Cairo::ImageSurface>> cache;
173 if (path.empty()) {
174 return {};
175 }
176 if (cache.contains(path)) {
177 return cache[path];
178 }
179 Inkscape::svg_renderer renderer(path.c_str());
180 auto result = renderer.render_surface(scale * 0.7); // reduced template icon size to fit more in a dialog
181 cache[path] = result;
182 return result;
183}
184
185sigc::signal<void (const Glib::ustring&)> TemplateList::signal_switch_page() {
186 return _signal_switch_page;
187}
188
192Glib::RefPtr<Gio::ListStore<TemplateList::TemplateItem>> TemplateList::generate_category(std::string const &label, bool allow_unselect)
193{
194 auto builder = create_builder("widget-new-from-template.ui");
195 auto& container = get_widget<Gtk::ScrolledWindow>(builder, "container");
196 auto& icons = get_widget<Gtk::GridView> (builder, "iconview");
197
199 auto sorter = Gtk::NumericSorter<int>::create(Gtk::ClosureExpression<int>::create([this](auto& item){
200 auto ptr = std::dynamic_pointer_cast<TemplateItem>(item);
201 return ptr ? ptr->priority : 0;
202 }));
203 auto sorted_model = Gtk::SortListModel::create(store, sorter);
204 if (!_filter) {
205 _filter = Gtk::BoolFilter::create({});
206 }
207 auto filtered_model = Gtk::FilterListModel::create(sorted_model, _filter);
208 auto selection_model = Gtk::SingleSelection::create(filtered_model);
209 if (allow_unselect) {
210 selection_model->set_can_unselect();
211 selection_model->set_autoselect(false);
212 }
213 auto factory = IconViewItemFactory::create([](auto& ptr) -> IconViewItemFactory::ItemData {
214 auto tmpl = std::dynamic_pointer_cast<TemplateItem>(ptr);
215 if (!tmpl) return {};
216
217 auto label = tmpl->label.empty() ? tmpl->name :
218 tmpl->name + "<small><span line_height='0.5'>\n\n</span><span alpha='60%'>" + tmpl->label + "</span></small>";
219 return { .label_markup = label, .image = tmpl->icon, .tooltip = tmpl->tooltip };
220 });
221 icons.set_max_columns(30);
222 icons.set_tab_behavior(Gtk::ListTabBehavior::ITEM); // don't steal the tab key
223 icons.set_factory(factory->get_factory());
224 icons.set_model(selection_model);
225
226 // This packing keeps the Gtk widget alive, beyond the builder's lifetime
227 add(container, label, g_dpgettext2(nullptr, "TemplateCategory", label.c_str()));
228 _categories.emplace_back(label);
229
230 selection_model->signal_selection_changed().connect([this](auto pos, auto count){
231 _item_selected_signal.emit(count > 0 ? static_cast<int>(pos) : -1);
232 });
233 icons.signal_activate().connect([this](auto pos){
235 });
236
237 _factory.emplace_back(std::move(factory));
238 return store;
239}
240
248
250 if (auto item = get_selected_item()) {
251 return item->key == "-new-template-";
252 }
253 return false;
254}
255
256Glib::RefPtr<TemplateList::TemplateItem> TemplateList::get_selected_item(Gtk::Widget* current_page) {
257 if (auto iconview = get_iconview(current_page ? current_page : get_visible_child())) {
258 auto sel = std::dynamic_pointer_cast<Gtk::SingleSelection>(iconview->get_model());
259 auto ptr = sel->get_selected_item();
260 if (auto item = std::dynamic_pointer_cast<TemplateList::TemplateItem>(ptr)) {
261 return item;
262 }
263 }
264 return nullptr;
265}
266
270std::shared_ptr<TemplatePreset> TemplateList::get_selected_preset(Gtk::Widget* current_page)
271{
272 if (auto item = get_selected_item(current_page)) {
274 }
275 return nullptr;
276}
277
281SPDocument *TemplateList::new_document(Gtk::Widget* current_page)
282{
284 if (auto preset = get_selected_preset(current_page)) {
285 if (auto doc = preset->new_from_template()) {
286 // TODO: Add memory to remember this preset for next time.
287 return app->document_add(std::move(doc));
288 } else {
289 // Cancel pressed in options box.
290 return nullptr;
291 }
292 }
293 // Fallback to the default template (already added)!
294 return app->document_new();
295}
296
297// Show page by its name
298void TemplateList::show_page(const Glib::ustring& name) {
299 set_visible_child(name);
301}
302
303// callback to check if template should be visible
304bool TemplateList::is_item_visible(const Glib::RefPtr<Glib::ObjectBase>& item, const Glib::ustring& search) const {
305 auto ptr = std::dynamic_pointer_cast<TemplateItem>(item);
306 if (!ptr) return false;
307
308 const auto& templ = *ptr;
309
310 if (search.empty()) return true;
311
312 // filter by name and label
313 return templ.label.lowercase().find(search) != Glib::ustring::npos ||
314 templ.name.lowercase().find(search) != Glib::ustring::npos;
315}
316
317// filter list of visible templates down to those that contain given search string in their name or label
318void TemplateList::filter(Glib::ustring search) {
319 _search_term = search;
320 refilter(search);
321}
322
323// set keyboard focus on template list
325 if (auto iconview = get_iconview(get_visible_child())) {
326 iconview->grab_focus();
327 }
328}
329
330void TemplateList::refilter(Glib::ustring search) {
331 // When a new expression is set in the BoolFilter, it emits signal_changed(),
332 // and the FilterListModel re-evaluates the filter.
333 search = search.lowercase();
334 auto expression = Gtk::ClosureExpression<bool>::create([this, search](auto& item){ return is_item_visible(item, search); });
335 // filter results
336 _filter->set_expression(expression);
337}
338
342void TemplateList::reset_selection(Gtk::Widget* current_page)
343{
344 // TODO: Add memory here for the new document default (see new_document).
345 for (auto const widget : UI::get_children(current_page ? *current_page : *this)) {
346 if (auto iconview = get_iconview(widget)) {
347 auto sel = std::dynamic_pointer_cast<Gtk::SingleSelection>(iconview->get_model());
348 sel->unselect_all();
349 }
350 }
351}
352
356Gtk::GridView *TemplateList::get_iconview(Gtk::Widget *widget)
357{
358 if (!widget) return nullptr;
359
360 for (auto const child : UI::get_children(*widget)) {
361 if (auto iconview = get_iconview(child)) {
362 return iconview;
363 }
364 }
365
366 return dynamic_cast<Gtk::GridView *>(widget);
367}
368
369} // namespace Inkscape::UI::Widget
370
371/*
372 Local Variables:
373 mode:c++
374 c-file-style:"stroustrup"
375 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
376 indent-tabs-mode:nil
377 fill-column:99
378 End:
379*/
380// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
Gtk builder utilities.
Fragment store
Definition canvas.cpp:155
static InkscapeApplication * instance()
Singleton instance.
TemplateList & get_template_list(TemplateList &ou_list)
Create a list of all the Template extensions.
Definition db.cpp:228
std::list< Template * > TemplateList
Definition db.h:57
static std::shared_ptr< TemplatePreset > get_any_preset(const std::string &key)
Return the template preset based on the key from any template class (static method).
Definition template.cpp:352
static std::unique_ptr< IconViewItemFactory > create(std::function< ItemData(Glib::RefPtr< Glib::ObjectBase > &)> get_item)
Like Gtk::Stack, but for holding a stack of Inkscape canvases.
Definition stack.h:16
sigc::signal< void(int)> _item_selected_signal
sigc::signal< void(const Glib::ustring &)> _signal_switch_page
void init(Extension::TemplateShow mode, AddPage add_page, bool allow_unselect=false)
Initialise this template list with categories and icons.
Glib::RefPtr< Gtk::BoolFilter > _filter
sigc::signal< void()> _item_activated_signal
void refilter(Glib::ustring search)
void reset_selection(Gtk::Widget *current_page=nullptr)
Reset the selection, forcing the use of the default template.
std::shared_ptr< Extension::TemplatePreset > get_selected_preset(Gtk::Widget *current_page=nullptr)
Returns the selected template preset, if one is not selected returns nullptr.
Gtk::GridView * get_iconview(Gtk::Widget *widget)
Returns the internal iconview for the given widget.
void filter(Glib::ustring search)
Cairo::RefPtr< Cairo::ImageSurface > icon_to_pixbuf(std::string const &name, int scale)
Turn the requested template icon name into a pixbuf.
void show_page(const Glib::ustring &name)
std::vector< std::unique_ptr< IconViewItemFactory > > _factory
sigc::signal< void(const Glib::ustring &)> signal_switch_page()
std::vector< Glib::ustring > _categories
Glib::RefPtr< TemplateItem > get_selected_item(Gtk::Widget *current_page=nullptr)
bool is_item_visible(const Glib::RefPtr< Glib::ObjectBase > &item, const Glib::ustring &search) const
SPDocument * new_document(Gtk::Widget *current_page=nullptr)
Create a new document based on the selected item and return.
bool has_selected_preset()
Returns true if the template list has a visible, selected preset.
Glib::RefPtr< Gio::ListStore< TemplateItem > > generate_category(std::string const &label, bool allow_unselect)
Generate a new category with the given label and return it's list store.
Cairo::RefPtr< Cairo::ImageSurface > render_surface(double scale)
Typed SVG document implementation.
Definition document.h:101
char const * label() const
Gets the author-visible label property for the object or a default if no label is defined.
Css & result
std::string cache
SPItem * item
Glib::ustring label
Definition desktop.h:50
DB db
This is the actual database object.
Definition db.cpp:32
std::string get_filename(Type type, char const *filename, bool localized, bool silent)
Definition resource.cpp:170
Custom widgets.
Definition desktop.h:126
static Glib::ustring all_templates
std::vector< Gtk::Widget * > get_children(Gtk::Widget &widget)
Get a vector of the widgetʼs children, from get_first_child() through each get_next_sibling().
Definition util.cpp:141
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
int mode
Ocnode * child[8]
Definition quantize.cpp:33
Inkscape::IO::Resource - simple resource API.
std::unique_ptr< Toolbar >(* create)()
Definition toolbars.cpp:56
Glib::ustring name
Definition toolbars.cpp:55
Glib::RefPtr< Gtk::Builder > builder
Glib::RefPtr< Gdk::Texture > to_texture(Cairo::RefPtr< Cairo::Surface > const &surface)
Convert an image surface in ARGB32 format to a texture.
Definition util.cpp:495