Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
font-collection-selector.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
8/*
9 * Author:
10 * Vaibhav Malik <vaibhavmalik2018@gmail.com>
11 *
12 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
13 */
14
16
17#include <glibmm/i18n.h>
18#include <glibmm/markup.h>
19#include <gtkmm/cellrenderertext.h>
20#include <gtkmm/droptarget.h>
21#include <gtkmm/eventcontrollerkey.h>
22#include <gtkmm/messagedialog.h>
23#include <gtkmm/treestore.h>
24#include <set>
25#include <sigc++/functors/mem_fun.h>
26
28#include "ui/controller.h"
29#include "ui/dialog-run.h"
30#include "ui/tools/tool-base.h"
33
34namespace Inkscape::UI::Widget {
35
37{
38 treeview = Gtk::make_managed<Gtk::TreeView>();
40 store = Gtk::TreeStore::create(FontCollection);
41 treeview->set_model(store);
42
44}
45
47
48// Setup the treeview of the widget.
50{
51 cell_text = Gtk::make_managed<Gtk::CellRendererText>();
52 cell_font_count = Gtk::make_managed<Gtk::CellRendererText>();
53 del_icon_renderer = Gtk::make_managed<IconRenderer>();
54 del_icon_renderer->add_icon("edit-delete");
55
56 text_column.pack_start (*cell_text, true);
57 text_column.add_attribute (*cell_text, "text", TEXT_COLUMN);
58 text_column.set_expand(true);
59
60 font_count_column.pack_start(*cell_font_count, true);
62
63 del_icon_column.pack_start(*del_icon_renderer, false);
64
65 treeview->set_headers_visible (false);
66 treeview->enable_model_drag_dest (Gdk::DragAction::MOVE);
67
68 // Append the columns to the treeview.
69 treeview->append_column(text_column);
70 treeview->append_column(font_count_column);
71 treeview->append_column(del_icon_column);
72
73 scroll.set_policy (Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
74 scroll.set_overlay_scrolling(false);
75 scroll.set_child(*treeview);
76
77 frame.set_hexpand (true);
78 frame.set_vexpand (true);
79 frame.set_child(scroll);
80
81 // Grid
82 set_name("FontCollection");
83 set_row_spacing(4);
84 set_column_spacing(1);
85
86 // Add extra columns to the "frame" to change space distribution
87 attach (frame, 0, 0, 1, 2);
88}
89
91{
92 frame.set_label(name);
93}
94
96{
97 cell_text->signal_edited().connect([this](const Glib::ustring &current_path, const Glib::ustring &new_text) {
98 // Store the expanded collections in a map.
99 std::set<Glib::ustring> expanded_collections;
100
101 store->foreach ([&](Gtk::TreeModel::Path const &path, Gtk::TreeModel::const_iterator const &it) {
102 auto collection = it->get_value(FontCollection.name);
103 if (treeview->row_expanded(path)) {
104 expanded_collections.insert(collection);
105 }
106 return false;
107 });
108
109 bool current_collection_expanded = false;
110 if (auto iter = store->get_iter(current_path)) {
111 auto path = store->get_path(iter);
112 if (treeview->row_expanded(path)) {
113 current_collection_expanded = true;
114 }
115 }
116
117 bool updated = on_rename_collection(current_path, new_text);
118 Gtk::TreeModel::Path updated_path;
119 if (updated && current_collection_expanded) {
120 expanded_collections.insert(new_text);
121 } else if (!updated && new_entry) {
122 // Delete this row if the new collection couldn't be created.
123 if (auto iter = store->get_iter(current_path)) {
124 store->erase(iter);
125 }
126 }
127
128 // Now start expanding the collections to restore the state as before.
129 store->foreach ([&](Gtk::TreeModel::Path const &path, Gtk::TreeModel::const_iterator const &it) {
130 auto collection = it->get_value(FontCollection.name);
131
132 if (expanded_collections.contains(collection)) {
133 treeview->expand_row(path, false);
134 }
135
136 if (updated && (collection == new_text)) {
137 updated_path = path;
138 }
139
140 return false;
141 });
142
143 auto tree_sel = treeview->get_selection();
144 if (updated && updated_path) {
145 // tree_sel->select_path(updated_path);
146 tree_sel->select(updated_path);
147 // treeview->scroll_to_row(updated_path);
148 }
149
150 new_entry = false;
151 });
152
153 treeview->set_row_separator_func(sigc::mem_fun(*this, &FontCollectionSelector::row_separator_func));
154 text_column.set_cell_data_func(*cell_text, sigc::mem_fun(*this, &FontCollectionSelector::text_cell_data_func));
155 font_count_column.set_cell_data_func(*cell_font_count,
157 del_icon_column.set_cell_data_func(*del_icon_renderer,
158 sigc::mem_fun(*this, &FontCollectionSelector::icon_cell_data_func));
159
161
162 auto const key = Gtk::EventControllerKey::create();
163 key->signal_key_pressed().connect([this, &key = *key](auto &&...args) { return on_key_pressed(key, args...); }, true);
164 treeview->add_controller(key);
165
166 // Signals for drag and drop.
167 auto const drop = Gtk::DropTarget::create(G_TYPE_STRING, Gdk::DragAction::COPY);
168 drop->signal_motion().connect(sigc::mem_fun(*this, &FontCollectionSelector::on_drop_motion), false); // before
169 drop->signal_drop().connect(sigc::mem_fun(*this, &FontCollectionSelector::on_drop_drop), false); // before
170 drop->signal_leave().connect(sigc::mem_fun(*this, &FontCollectionSelector::on_drop_leave));
171 treeview->add_controller(drop);
172
173 treeview->get_selection()->signal_changed().connect([this]{ on_selection_changed(); });
174}
175
176// This function manages the visibility of the font count column.
177void FontCollectionSelector::text_cell_data_func(Gtk::CellRenderer *const renderer,
178 Gtk::TreeModel::const_iterator const &iter)
179{
180 // Only font collections should have a font count next to their names.
181 bool const is_collection = !iter->parent();
182 cell_text->property_editable() = is_collection ? true : false;
183}
184
185// This function manages the visibility of the font count column.
186void FontCollectionSelector::font_count_cell_data_func(Gtk::CellRenderer *const renderer,
187 Gtk::TreeModel::const_iterator const &iter)
188{
189 // Only font collections should have a font count next to their names.
190 bool const is_collection = !iter->parent();
191 cell_font_count->set_visible(is_collection);
192
193 Glib::ustring const markup = "<span alpha='50%'>" + std::to_string((*iter)[FontCollection.font_count]) + "</span>";
194 renderer->set_property("markup", markup);
195}
196
197// This function will TURN OFF the visibility of the delete icon for system collections.
198void FontCollectionSelector::icon_cell_data_func(Gtk::CellRenderer * const renderer,
199 Gtk::TreeModel::const_iterator const &iter)
200{
201 // Add the delete icon only if the collection is editable(user-collection).
202 if (auto const parent = iter->parent()) {
203 // Case: It is a font.
204 bool is_user = (*parent)[FontCollection.is_editable];
205 del_icon_renderer->set_visible(is_user);
206 } else if((*iter)[FontCollection.is_editable]) {
207 // Case: User font collection.
208 del_icon_renderer->set_visible(true);
209 } else {
210 // Case: System font collection.
211 del_icon_renderer->set_visible(false);
212 }
213}
214
215// This function will TURN OFF the visibility of checkbuttons for children in the TreeStore.
216void FontCollectionSelector::check_button_cell_data_func(Gtk::CellRenderer * const renderer,
217 Gtk::TreeModel::const_iterator const &iter)
218{
219 renderer->set_visible(false);
220 /*
221 // Append the checkbutton column only if the iterator have some children.
222 Gtk::TreeModel::Row row = *iter;
223 auto parent = row->parent();
224
225 if(parent) {
226 renderer->set_visible(false);
227 }
228 else {
229 renderer->set_visible(true);
230 }
231 */
232}
233
234bool FontCollectionSelector::row_separator_func(Glib::RefPtr<Gtk::TreeModel> const &/*model*/,
235 Gtk::TreeModel::const_iterator const &iter)
236{
237 return iter->get_value(FontCollection.name) == "#";
238}
239
245
246// This function will keep the collections_list updated after any event.
248{
249 // Get the list of all the user collections.
251 auto collections = font_collections->get_collections();
252
253 // Now insert these collections one by one into the treeview.
254 store->freeze_notify();
255 Gtk::TreeModel::iterator iter;
256
257 for(const auto &col: collections) {
258 iter = store->append();
259 (*iter)[FontCollection.name] = col;
260
261 // User collections are editable.
262 (*iter)[FontCollection.is_editable] = true;
263
264 // Alright, now populate the fonts of this collection.
265 populate_fonts(col);
266 }
267 store->thaw_notify();
268}
269
270void FontCollectionSelector::populate_fonts(const Glib::ustring& collection_name)
271{
272 // Get the FontLister instance to get the list of all the collections.
274 std::set <Glib::ustring> fonts = font_collections->get_fonts(collection_name);
275
276 // First find the location of this collection_name in the map.
277 int index = font_collections->get_user_collection_location(collection_name);
278
279 store->freeze_notify();
280
281 // Generate the iterator path.
282 Gtk::TreePath path;
283 path.push_back(index);
284 Gtk::TreeModel::iterator iter = store->get_iter(path);
285
286 if (!iter) {
287 store->thaw_notify();
288 return;
289 }
290
291 // Update the font count.
292 (*iter)[FontCollection.font_count] = fonts.size();
293
294 // auto child_iter = iter->children();
295 auto size = iter->children().size();
296
297 // Clear the previously stored fonts at this path.
298 while(size--) {
299 Gtk::TreeModel::iterator child = iter->children().begin();
300 store->erase(child);
301 }
302
303 for(auto const &font: fonts) {
304 Gtk::TreeModel::iterator child = store->append((*iter).children());
305 (*child)[FontCollection.name] = font;
306 (*child)[FontCollection.is_editable] = false;
307 }
308
309 store->thaw_notify();
310}
311
313{
314 auto collections = Inkscape::FontCollections::get();
315 auto iter = store->get_iter(path);
316 if (auto const parent = iter->parent()) {
317 // It is a font.
318 collections->remove_font((*parent)[FontCollection.name], (*iter)[FontCollection.name]);
319
320 // Update the font count of the parent iter.
321 (*parent)[FontCollection.font_count] = (*parent)[FontCollection.font_count] - 1;
322
323 store->erase(iter);
324 } else {
325 // It is a collection.
326 // No need to confirm in case of empty collections.
327 if (collections->get_fonts((*iter)[FontCollection.name]).empty()) {
328 collections->remove_collection((*iter)[FontCollection.name]);
329 store->erase(iter);
330 return;
331 }
332
333 // Warn the user and then proceed.
334 deletion_warning_message_dialog((*iter)[FontCollection.name], [this, iter] (int response) {
335 if (response == Gtk::ResponseType::YES) {
336 auto collections = Inkscape::FontCollections::get();
337 collections->remove_collection((*iter)[FontCollection.name]);
338 store->erase(iter);
339 }
340 });
341 }
342}
343
344void FontCollectionSelector::on_create_collection()
345{
346 new_entry = true;
347 Gtk::TreeModel::iterator iter = store->append();
348 (*iter)[FontCollection.is_editable] = true;
349 (*iter)[FontCollection.font_count] = 0;
350
351 Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iter;
352 treeview->set_cursor(path, text_column, true);
353 grab_focus();
354}
355
356bool FontCollectionSelector::on_rename_collection(const Glib::ustring &path, const Glib::ustring &new_text)
357{
358 // Fetch the collections.
360
361 // Check if the same collection is already present.
362 bool is_system = collections->find_collection(new_text, true);
363 bool is_user = collections->find_collection(new_text, false);
364
365 // Return if the new name is empty.
366 // Do not allow user collections to be named as system collections.
367 if (new_text == "" || is_system || is_user) {
368 return false;
369 }
370
371 Gtk::TreeModel::iterator iter = store->get_iter(path);
372
373 // Return if it is not a valid iter.
374 if(!iter) {
375 return false;
376 }
377
378 // To check if it's a font-collection or a font.
379 if (auto const parent = iter->parent(); !parent) {
380 // Call the rename_collection function
381 collections->rename_collection((*iter)[FontCollection.name], new_text);
382 } else {
383 collections->rename_font((*parent)[FontCollection.name], (*iter)[FontCollection.name], new_text);
384 }
385
386 (*iter)[FontCollection.name] = new_text;
387 populate_collections();
388
389 return true;
390}
391
392void FontCollectionSelector::on_delete_button_pressed()
393{
394 // Get the current collection.
395 Glib::RefPtr<Gtk::TreeSelection> selection = treeview->get_selection();
396 Gtk::TreeModel::iterator iter = selection->get_selected();
397 Gtk::TreeModel::Row row = *iter;
398 auto const parent = iter->parent();
399
400 auto collections = Inkscape::FontCollections::get();
401
402 if (!parent) {
403 // It is a collection.
404 // Check if it is a system collection.
405 bool is_system = collections->find_collection((*iter)[FontCollection.name], true);
406 if (is_system) {
407 return;
408 }
409
410 // Warn the user and then proceed.
411 deletion_warning_message_dialog((*iter)[FontCollection.name], [this, iter] (int response) {
412 if (response == Gtk::ResponseType::YES) {
413 auto collections = Inkscape::FontCollections::get();
414 collections->remove_collection((*iter)[FontCollection.name]);
415 store->erase(iter);
416 }
417 });
418 } else {
419 // It is a font.
420 // Check if it belongs to a system collection.
421 bool is_system = collections->find_collection((*parent)[FontCollection.name], true);
422 if (is_system) {
423 return;
424 }
425
426 collections->remove_font((*parent)[FontCollection.name], row[FontCollection.name]);
427
428 store->erase(iter);
429 }
430}
431
432// Function to edit the name of the collection or font.
433void FontCollectionSelector::on_edit_button_pressed()
434{
435 Glib::RefPtr<Gtk::TreeSelection> selection = treeview->get_selection();
436
437 if(selection) {
438 Gtk::TreeModel::iterator iter = selection->get_selected();
439 if(!iter) {
440 return;
441 }
442
443 auto const parent = iter->parent();
444 bool is_system = Inkscape::FontCollections::get()->find_collection((*iter)[FontCollection.name], true);
445
446 if (!parent && !is_system) {
447 // It is a collection.
448 treeview->set_cursor(Gtk::TreePath(iter), text_column, true);
449 }
450 }
451}
452
453void FontCollectionSelector::deletion_warning_message_dialog(Glib::ustring const &collection_name, sigc::slot<void(int)> onresponse)
454{
455 auto message = Glib::ustring::compose(_("Are you sure want to delete the \"%1\" font collection?\n"), collection_name);
456 auto dialog = std::make_unique<Gtk::MessageDialog>(message, false, Gtk::MessageType::WARNING, Gtk::ButtonsType::YES_NO, true);
457 dialog->signal_response().connect(onresponse);
458 dialog_show_modal_and_selfdestruct(std::move(dialog), get_root());
459}
460
461bool FontCollectionSelector::on_key_pressed(Gtk::EventControllerKey const &controller,
462 unsigned keyval, unsigned keycode, Gdk::ModifierType state)
463{
464 switch (Inkscape::UI::Tools::get_latin_keyval(controller, keyval, keycode, state)) {
465 case GDK_KEY_Delete:
466 on_delete_button_pressed();
467 return true;
468 }
469
470 return false;
471}
472
473Gdk::DragAction FontCollectionSelector::on_drop_motion(double x, double y)
474{
475 Gtk::TreeModel::Path path;
476 Gtk::TreeView::DropPosition pos;
477 treeview->get_dest_row_at_pos(x, y, path, pos);
478 treeview->unset_state_flags(Gtk::StateFlags::DROP_ACTIVE);
479
480 auto tree_sel = treeview->get_selection();
481 if (path) {
482 if (auto iter = store->get_iter(path)) {
483 if (auto parent = iter->parent()) {
484 tree_sel->select(parent);
485 } else {
486 tree_sel->select(iter);
487 }
488 return Gdk::DragAction::COPY;
489 }
490 }
491
492 tree_sel->unselect_all();
493 return {};
494}
495
496void FontCollectionSelector::on_drop_leave()
497{
498 treeview->get_selection()->unselect_all();
499}
500
501bool FontCollectionSelector::on_drop_drop(Glib::ValueBase const &, double x, double y)
502{
503 // 1. Get the row at which the data is dropped.
504 Gtk::TreePath path;
505 int bx{}, by{};
506 treeview->convert_widget_to_bin_window_coords(x, y, bx, by);
507 if (!treeview->get_path_at_pos(bx, by, path)) {
508 return false;
509 }
510 Gtk::TreeModel::iterator iter = store->get_iter(path);
511 // Case when the font is dragged in the empty space.
512 if(!iter) {
513 return false;
514 }
515
516 Glib::ustring collection_name = (*iter)[FontCollection.name];
517
518 bool is_expanded = false;
519 if (auto const parent = iter->parent()) {
520 is_expanded = true;
521 collection_name = (*parent)[FontCollection.name];
522 } else {
523 is_expanded = treeview->row_expanded(path);
524 }
525
526 auto const collections = Inkscape::FontCollections::get();
527
528 bool const is_system = collections->find_collection(collection_name, true);
529 if (is_system) {
530 // The font is dropped in a system collection.
531 return false;
532 }
533
534 // 2. Get the data that is sent by the source.
535 // std::cout << "Received: " << selection_data.get_data() << std::endl;
536 // std::cout << (*iter)[FontCollection.name] << std::endl;
537 // Add the font into the collection.
539 collections->add_font(collection_name, font_name);
540
541 // Re-populate the collection.
542 populate_fonts(collection_name);
543
544 // Re-expand this row after re-population.
545 if(is_expanded) {
546 treeview->expand_to_path(path);
547 }
548
549 return true;
550}
551
552void FontCollectionSelector::on_selection_changed()
553{
554 Glib::RefPtr <Gtk::TreeSelection> selection = treeview->get_selection();
555 if (!selection) return;
556
558 Gtk::TreeModel::iterator iter = selection->get_selected();
559 if (!iter) return;
560
561 auto parent = iter->parent();
562
563 // We use 3 states to adjust the sensitivity of the edit and
564 // delete buttons in the font collections manager dialog.
565 int state = 0;
566
567 // State -1: Selection is a system collection or a system
568 // collection font.(Neither edit nor delete)
569
570 // State 0: It's not a system collection or it's font. But it is
571 // a user collection.(Both edit and delete).
572
573 // State 1: It is a font that belongs to a user collection.
574 // (Only delete)
575
576 if(parent) {
577 // It is a font, and thus it is not editable.
578 // Now check if it's parent is a system collection.
579 bool is_system = font_collections->find_collection((*parent)[FontCollection.name], true);
580 state = (is_system) ? SYSTEM_COLLECTION: USER_COLLECTION_FONT;
581 } else {
582 // Check if it is a system collection.
583 bool is_system = font_collections->find_collection((*iter)[FontCollection.name], true);
584 state = (is_system) ? SYSTEM_COLLECTION: USER_COLLECTION;
585 }
586
587 signal_changed.emit(state);
588}
589
590} // namespace Inkscape::UI::Widget
591
592/*
593 Local Variables:
594 mode:c++
595 c-file-style:"stroustrup"
596 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
597 indent-tabs-mode:nil
598 fill-column:99
599 End:
600*/
601// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
Fragment store
Definition canvas.cpp:155
static FontCollections * get()
int get_user_collection_location(const Glib::ustring &collection_name)
void rename_font(const Glib::ustring &collection_name, const Glib::ustring &old_name, const Glib::ustring &new_name)
std::set< Glib::ustring > const & get_fonts(Glib::ustring const &name, bool is_system=false) const
void rename_collection(const Glib::ustring &old_name, const Glib::ustring &new_name)
bool find_collection(Glib::ustring const &collection_name, bool is_system=false) const
std::vector< Glib::ustring > get_collections(bool is_system=false) const
Glib::ustring const & get_dragging_family() const
static Inkscape::FontLister * get_instance()
void check_button_cell_data_func(Gtk::CellRenderer *renderer, Gtk::TreeModel::const_iterator const &iter)
void deletion_warning_message_dialog(Glib::ustring const &collection_name, sigc::slot< void(int)> onresponse)
void icon_cell_data_func(Gtk::CellRenderer *renderer, Gtk::TreeModel::const_iterator const &iter)
void font_count_cell_data_func(Gtk::CellRenderer *renderer, Gtk::TreeModel::const_iterator const &iter)
bool on_key_pressed(Gtk::EventControllerKey const &controller, unsigned keyval, unsigned keycode, Gdk::ModifierType state)
bool on_rename_collection(const Glib::ustring &, const Glib::ustring &)
bool on_drop_drop(Glib::ValueBase const &value, double x, double y)
void text_cell_data_func(Gtk::CellRenderer *renderer, Gtk::TreeModel::const_iterator const &iter)
bool row_separator_func(Glib::RefPtr< Gtk::TreeModel > const &model, Gtk::TreeModel::const_iterator const &iter)
type_signal_activated signal_activated()
void add_icon(Glib::ustring name)
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
static char const *const parent
Definition dir-util.cpp:70
This file contains the definition of the FontCollectionSelector class.
Font selection widgets.
unsigned get_latin_keyval(GtkEventControllerKey const *const controller, unsigned const keyval, unsigned const keycode, GdkModifierType const state, unsigned *consumed_modifiers)
Return the keyval corresponding to the event controller key in Latin group.
Custom widgets.
Definition desktop.h:126
void dialog_show_modal_and_selfdestruct(std::unique_ptr< Gtk::Dialog > dialog, Gtk::Root *root)
Show a dialog modally, destroying it when the user dismisses it.
static cairo_user_data_key_t key
Ocnode * child[8]
Definition quantize.cpp:33
int index
Glib::ustring name
Definition toolbars.cpp:55