// SPDX-License-Identifier: GPL-2.0-or-later /* * Author: * Vaibhav Malik * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include #include #include "font-collection-selector.h" #include "libnrtype/font-lister.h" // For updating from selection #include "util/document-fonts.h" namespace Inkscape { namespace UI { namespace Widget { FontCollectionSelector::FontCollectionSelector() { // Step 1: Initialize the treeview. treeview = Gtk::manage(new Gtk::TreeView()); // Step 2: Setup the treeview. setup_tree_view(treeview); // Step 3: Intialize the model. store = Gtk::TreeStore::create(FontCollection); // Step 4: Populate the ListStore. treeview->set_model(store); // Signals. setup_signals(); show_all_children(); } // Setup the treeview of the widget. void FontCollectionSelector::setup_tree_view(Gtk::TreeView *tv) { cell_text = new Gtk::CellRendererText(); del_icon_renderer = manage(new Inkscape::UI::Widget::IconRenderer()); del_icon_renderer->add_icon("edit-delete"); text_column.pack_start (*cell_text, true); text_column.add_attribute (*cell_text, "text", TEXT_COLUMN); text_column.set_expand(true); del_icon_column.pack_start (*del_icon_renderer, false); // Attach the cell data functions. text_column.set_cell_data_func(*cell_text, sigc::mem_fun(*this, &FontCollectionSelector::text_cell_data_func)); treeview->enable_model_drag_dest (Gdk::ACTION_MOVE); treeview->set_headers_visible (false); // Target entries for Drag and Drop. target_entries.emplace_back(Gtk::TargetEntry("STRING", (Gtk::TargetFlags)0, 0)); target_entries.emplace_back(Gtk::TargetEntry("text/plain", (Gtk::TargetFlags)0, 0)); treeview->drag_dest_set(target_entries, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_COPY); // Append the columns to the treeview. treeview->append_column(text_column); treeview->append_column(del_icon_column); scroll.set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); scroll.set_overlay_scrolling(false); scroll.add (*treeview); frame.set_hexpand (true); frame.set_vexpand (true); frame.add (scroll); // Grid set_name("FontCollection"); set_row_spacing(4); set_column_spacing(1); // Add extra columns to the "frame" to change space distribution attach (frame, 0, 0, 1, 2); } void FontCollectionSelector::change_frame_name(const Glib::ustring& name) { frame.set_label(name); } void FontCollectionSelector::setup_signals() { cell_text->signal_edited().connect(sigc::mem_fun(*this, &FontCollectionSelector::on_rename_collection)); del_icon_renderer->signal_activated().connect(sigc::mem_fun(*this, &FontCollectionSelector::on_delete_icon_clicked)); treeview->signal_key_press_event().connect([=](GdkEventKey *ev){ return on_key_pressed(ev); }); treeview->set_row_separator_func(sigc::mem_fun(*this, &FontCollectionSelector::row_separator_func)); treeview->get_column(ICON_COLUMN)->set_cell_data_func(*del_icon_renderer, sigc::mem_fun(*this, &FontCollectionSelector::icon_cell_data_func)); // Signals for drag and drop. treeview->signal_drag_motion().connect(sigc::mem_fun(*this, &FontCollectionSelector::on_drag_motion), false); treeview->signal_drag_data_received().connect(sigc::mem_fun(*this, &FontCollectionSelector::on_drag_data_received), false); treeview->signal_drag_drop().connect(sigc::mem_fun(*this, &FontCollectionSelector::on_drag_drop), false); // treeview->signal_drag_failed().connect(sigc::mem_fun(*this, &FontCollectionSelector::on_drag_failed), false); treeview->signal_drag_leave().connect(sigc::mem_fun(*this, &FontCollectionSelector::on_drag_leave), false); treeview->signal_drag_end().connect(sigc::mem_fun(*this, &FontCollectionSelector::on_drag_end), false); treeview->get_selection()->signal_changed().connect([=](){ on_selection_changed(); }); Inkscape::RecentlyUsedFonts::get()->connectUpdate(sigc::mem_fun(*this, &FontCollectionSelector::populate_system_collections)); } // To distinguish the collection name and the font name. Glib::ustring FontCollectionSelector::get_text_cell_markup(Gtk::TreeIter const &iter) { Glib::ustring markup; auto parent = (*iter)->parent(); if(parent) { // It is a font. markup = ""; markup += (*iter)[FontCollection.name]; markup += ""; } else { // It is a collection. markup = ""; markup += (*iter)[FontCollection.name]; markup += ""; } return markup; } // This function will TURN OFF the visibility of the delete icon for system collections. void FontCollectionSelector::text_cell_data_func(Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter) { // Add the delete icon only if the collection is editable(user-collection). Glib::ustring markup = get_text_cell_markup(iter); renderer->set_property("markup", markup); } // This function will TURN OFF the visibility of the delete icon for system collections. void FontCollectionSelector::icon_cell_data_func(Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter) { // Add the delete icon only if the collection is editable(user-collection). Gtk::TreeModel::Row row = *iter; auto parent = (*iter)->parent(); if(parent) { // Case: It is a font. bool is_user = (*parent)[FontCollection.is_editable]; del_icon_renderer->set_visible(is_user); cell_text->property_editable() = false; } else if((*iter)[FontCollection.is_editable]) { // Case: User font collection. del_icon_renderer->set_visible(true); cell_text->property_editable() = true; } else { // Case: System font collection. del_icon_renderer->set_visible(false); cell_text->property_editable() = false; } } // This function will TURN OFF the visibility of checkbuttons for children in the TreeStore. void FontCollectionSelector::check_button_cell_data_func(Gtk::CellRenderer *renderer, Gtk::TreeIter const &iter) { renderer->set_visible(false); /* // Append the checkbutton column only if the iterator have some children. Gtk::TreeModel::Row row = *iter; auto parent = row->parent(); if(parent) { renderer->set_visible(false); } else { renderer->set_visible(true); } */ } bool FontCollectionSelector::row_separator_func(const Glib::RefPtr& model, const Gtk::TreeModel::iterator& iter) { return (*iter)[FontCollection.name] == "#"; } void FontCollectionSelector::populate_collections() { store->clear(); populate_system_collections(); populate_user_collections(); } // This function will keep the populate the system collections and their fonts. void FontCollectionSelector::populate_system_collections() { FontCollections *font_collections = Inkscape::FontCollections::get(); std::vector system_collections = font_collections->get_collections(true); // Erase the previous collections. store->freeze_notify(); Gtk::TreePath path; path.push_back(0); Gtk::TreeModel::iterator iter; bool row_0 = false, row_1 = false; for(int i = 0; i < 3; i++) { iter = store->get_iter(path); if(iter) { if(treeview->row_expanded(path)) { if(i == 0) { row_0 = true; } else if(i == 1) { row_1 = true; } } store->erase(iter); } } // Insert a separator. iter = store->prepend(); (*iter)[FontCollection.name] = "#"; (*iter)[FontCollection.is_editable] = false; iter = store->children(); for(auto const &col: system_collections) { iter = store->prepend(); (*iter)[FontCollection.name] = col; (*iter)[FontCollection.is_editable] = false; } populate_document_fonts(); populate_recently_used_fonts(); store->thaw_notify(); if(row_0) { treeview->expand_row(Gtk::TreePath("0"), true); } if(row_1) { treeview->expand_row(Gtk::TreePath("1"), true); } } void FontCollectionSelector::populate_document_fonts() { // The position of the recently used collection is hardcoded for now. Gtk::TreePath path; path.push_back(1); Gtk::TreeModel::iterator iter = store->get_iter(path); for(auto const& font: Inkscape::DocumentFonts::get()->get_fonts()) { Gtk::TreeModel::iterator child = store->append((*iter).children()); (*child)[FontCollection.name] = font; (*child)[FontCollection.is_editable] = false; } } void FontCollectionSelector::populate_recently_used_fonts() { // The position of the recently used collection is hardcoded for now. Gtk::TreePath path; path.push_back(0); Gtk::TreeModel::iterator iter = store->get_iter(path); for(auto const& font: Inkscape::RecentlyUsedFonts::get()->get_fonts()) { Gtk::TreeModel::iterator child = store->append((*iter).children()); (*child)[FontCollection.name] = font; (*child)[FontCollection.is_editable] = false; } } // This function will keep the collections_list updated after any event. void FontCollectionSelector::populate_user_collections() { // Get the list of all the user collections. auto collections = Inkscape::FontCollections::get()->get_collections(); // Now insert these collections one by one into the treeview. store->freeze_notify(); Gtk::TreeModel::iterator iter; for(const auto &col: collections) { iter = store->append(); (*iter)[FontCollection.name] = col; // User collections are editable. (*iter)[FontCollection.is_editable] = true; // Alright, now populate the fonts of this collection. populate_fonts(col); } store->thaw_notify(); } void FontCollectionSelector::populate_fonts(const Glib::ustring& collection_name) { // Get the FontLister instance to get the list of all the collections. FontCollections *font_collections = Inkscape::FontCollections::get(); std::set fonts = font_collections->get_fonts(collection_name); // First find the location of this collection_name in the map. // +1 for the separator. int index = font_collections->get_user_collection_location(collection_name) + 1; store->freeze_notify(); // Generate the iterator path. Gtk::TreePath path; path.push_back(index); Gtk::TreeModel::iterator iter = store->get_iter(path); // auto child_iter = iter->children(); auto size = iter->children().size(); // Clear the previously stored fonts at this path. while(size--) { Gtk::TreeModel::iterator child = iter->children().begin(); store->erase(child); } for(auto const &font: fonts) { Gtk::TreeModel::iterator child = store->append((*iter).children()); (*child)[FontCollection.name] = font; (*child)[FontCollection.is_editable] = false; } store->thaw_notify(); } void FontCollectionSelector::on_delete_icon_clicked(Glib::ustring const &path) { FontCollections *collections = Inkscape::FontCollections::get(); Gtk::TreeModel::iterator iter = store->get_iter(path); auto parent = (*iter)->parent(); if(!parent) { // It is a collection. // No need to confirm in case of empty collections. if (!collections->get_fonts((*iter)[FontCollection.name]).empty()) { // Warn the user and then proceed. int response = deleltion_warning_message_dialog((*iter)[FontCollection.name]); if (response != Gtk::RESPONSE_YES) { return; } } collections->remove_collection((*iter)[FontCollection.name]); } else { // It is a font. collections->remove_font((*parent)[FontCollection.name], (*iter)[FontCollection.name]); } store->erase(iter); } void FontCollectionSelector::on_create_collection() { Gtk::TreeModel::iterator iter = store->append(); (*iter)[FontCollection.is_editable] = true; Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iter; treeview->set_cursor(path, text_column, true); grab_focus(); } void FontCollectionSelector::on_rename_collection(const Glib::ustring& path, const Glib::ustring& new_text) { // Fetch the collections. FontCollections *collections = Inkscape::FontCollections::get(); // Check if the same collection is already present. bool is_system = collections->find_collection(new_text, true); bool is_user = collections->find_collection(new_text, false); // Return if the new name is empty. // Do not allow user collections to be named as system collections. if (new_text == "" || is_system || is_user) { return; } Gtk::TreeModel::iterator iter = store->get_iter(path); // Return if it is not a valid iter. if(!iter) { return; } // To check if it's a font-collection or a font. auto parent = (*iter)->parent(); if(!parent) { // Call the rename_collection function collections->rename_collection((*iter)[FontCollection.name], new_text); } else { collections->rename_font((*parent)[FontCollection.name], (*iter)[FontCollection.name], new_text); } (*iter)[FontCollection.name] = new_text; populate_collections(); } void FontCollectionSelector::on_delete_button_pressed() { // Get the current collection. Glib::RefPtr selection = treeview->get_selection(); Gtk::TreeModel::iterator iter = selection->get_selected(); Gtk::TreeModel::Row row = *iter; auto parent = row->parent(); FontCollections *collections = Inkscape::FontCollections::get(); if(!parent) { // It is a collection. // Check if it is a system collection. bool is_system = collections->find_collection((*iter)[FontCollection.name], true); if(is_system) { return; } // Warn the user and then proceed. int response = deleltion_warning_message_dialog((*iter)[FontCollection.name]); if (response != Gtk::RESPONSE_YES) { return; } collections->remove_collection((*iter)[FontCollection.name]); } else { // It is a font. // Check if it belongs to a system collection. bool is_system = collections->find_collection((*parent)[FontCollection.name], true); if(is_system) { return; } collections->remove_font((*parent)[FontCollection.name], row[FontCollection.name]); } store->erase(iter); } // Function to edit the name of the collection or font. void FontCollectionSelector::on_edit_button_pressed() { Glib::RefPtr selection = treeview->get_selection(); if(selection) { Gtk::TreeModel::iterator iter = selection->get_selected(); if(!iter) { return; } Gtk::TreeModel::Row row = *iter; auto parent = row->parent(); bool is_system = Inkscape::FontCollections::get()->find_collection((*iter)[FontCollection.name], true); if(!parent && !is_system) { // It is a collection. treeview->set_cursor(Gtk::TreePath(iter), text_column, true); } } } int FontCollectionSelector::deleltion_warning_message_dialog(const Glib::ustring &collection_name) { Glib::ustring message = Glib::ustring::compose(_("Are you sure want to delete the \"%1\" font collection?\n"), collection_name); Gtk::MessageDialog dialog(message, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true); dialog.set_transient_for(*dynamic_cast(get_toplevel())); return dialog.run(); } bool FontCollectionSelector::on_key_pressed(GdkEventKey *event) { if (event->type == GDK_KEY_PRESS && frame.get_label() == "Collections") { // std::cout << "Key pressed" << std::endl; switch (Inkscape::UI::Tools::get_latin_keyval (event)) { case GDK_KEY_Delete: on_delete_button_pressed(); break; } // We handled this event. return true; } // We did not handle this event. return false; } bool FontCollectionSelector::on_drag_motion(const Glib::RefPtr &context, int x, int y, guint time) { Gtk::TreeModel::Path path; Gtk::TreeViewDropPosition pos; treeview->get_dest_row_at_pos(x, y, path, pos); treeview->drag_unhighlight(); if (path) { context->drag_status(Gdk::ACTION_COPY, time); return false; } // remove drop highlight context->drag_refuse(time); return true; } void FontCollectionSelector::on_drag_data_received(const Glib::RefPtr context, int x, int y, const Gtk::SelectionData &selection_data, guint info, guint time) { // std::cout << "FontCollectionSelector::on_drag_data_received()" << std::endl; // 1. Get the row at which the data is dropped. Gtk::TreePath path; treeview->get_path_at_pos(x, y, path); Gtk::TreeModel::iterator iter = store->get_iter(path); bool is_expanded = false; // Case when the font is dragged in the empty space. if(!iter) { return; } Glib::ustring collection_name = (*iter)[FontCollection.name]; auto font_name = Inkscape::FontLister::get_instance()->get_dragging_family(); FontCollections *collections = Inkscape::FontCollections::get(); std::vector system_collections = collections->get_collections(true); auto parent = (*iter)->parent(); if(parent) { is_expanded = true; collection_name = (*parent)[FontCollection.name]; bool is_system = collections->find_collection(collection_name, true); if(is_system) { // The font is dropped in a system collection. return; } } else { if (treeview->row_expanded(path)) { is_expanded = true; } bool is_system = collections->find_collection(collection_name, true); if(is_system) { // The font is dropped in a system collection. return; } } // 2. Get the data that is sent by the source. // std::cout << "Received: " << selection_data.get_data() << std::endl; // std::cout << (*iter)[FontCollection.name] << std::endl; // Add the font into the collection. collections->add_font(collection_name, font_name); // Re-populate the collection. populate_fonts(collection_name); // Re-expand this row after re-population. if(is_expanded) { treeview->expand_to_path(path); } // Call gtk_drag_finish(context, success, del = false, time) gtk_drag_finish(context->gobj(), TRUE, FALSE, time); } bool FontCollectionSelector::on_drag_drop(const Glib::RefPtr &context, int x, int y, guint time) { // std::cout << "FontCollectionSelector::on_drag_drop()" << std::endl; Gtk::TreeModel::Path path; Gtk::TreeViewDropPosition pos; treeview->get_dest_row_at_pos(x, y, path, pos); if (!path) { // std::cout << "Not on target\n"; return false; } on_drag_end(context); return true; } /* bool FontCollectionSelector::on_drag_failed(const Glib::RefPtr &context, const Gtk::DragResult result) { std::cout << "Drag Failed\n"; return true; } */ void FontCollectionSelector::on_drag_leave(const Glib::RefPtr &context, guint time) { // std::cout << "Drag Leave\n"; } /* void FontCollectionSelector::on_drag_start(const Glib::RefPtr &context) { // std::cout << "FontCollectionSelector::on_drag_start()" << std::endl; } */ void FontCollectionSelector::on_drag_end(const Glib::RefPtr &context) { // std::cout << "FontCollection::on_drag_end()" << std::endl; treeview->drag_unhighlight(); } void FontCollectionSelector::on_selection_changed() { Glib::RefPtr selection = treeview->get_selection(); if(selection) { FontCollections *font_collections = Inkscape::FontCollections::get(); Gtk::TreeModel::iterator iter = selection->get_selected(); auto parent = iter->parent(); // We use 3 states to adjust the sensitivity of the edit and // delete buttons in the font collections manager dialog. int state = 0; // State -1: Selection is a system collection or a system // collection font.(Neither edit nor delete) // State 0: It's not a system collection or it's font. But it is // a user collection.(Both edit and delete). // State 1: It is a font that belongs to a user collection. // (Only delete) if(parent) { // It is a font, and thus it is not editable. // Now check if it's parent is a system collection. bool is_system = font_collections->find_collection((*parent)[FontCollection.name], true); state = (is_system) ? SYSTEM_COLLECTION: USER_COLLECTION_FONT; } else { // Check if it is a system collection. bool is_system = font_collections->find_collection((*iter)[FontCollection.name], true); state = (is_system) ? SYSTEM_COLLECTION: USER_COLLECTION; } signal_changed.emit(state); } } } // namespace Widget } // namespace UI } // namespace Inkscape /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :