From 453fb961f203af59e0cd3790bb16fab15f41bd32 Mon Sep 17 00:00:00 2001 From: Lawrence Lee Date: Sun, 28 Mar 2021 18:12:47 -0700 Subject: [PATCH] Create a preferences widget for external editors The widget is intended to be embedded in the preferences dialog. It displays a list of external editors using an icon, name, and command for each editor. The name and command can be edited. Editors can be added, removed, and rearranged. A button allows one to set an editor's information by selecting from a list of installed applications. --- rtgui/externaleditorpreferences.cc | 261 +++++++++++++++++++++++++++++ rtgui/externaleditorpreferences.h | 147 ++++++++++++++++ 2 files changed, 408 insertions(+) create mode 100644 rtgui/externaleditorpreferences.cc create mode 100644 rtgui/externaleditorpreferences.h diff --git a/rtgui/externaleditorpreferences.cc b/rtgui/externaleditorpreferences.cc new file mode 100644 index 000000000..df6b6efc2 --- /dev/null +++ b/rtgui/externaleditorpreferences.cc @@ -0,0 +1,261 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2021 Lawrence Lee + * + * RawTherapee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RawTherapee 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ +#include "externaleditorpreferences.h" +#include "multilangmgr.h" +#include "rtimage.h" + + +ExternalEditorPreferences::ExternalEditorPreferences(): + Box(Gtk::Orientation::ORIENTATION_VERTICAL), + list_model(Gtk::ListStore::create(model_columns)), + toolbar(Gtk::Orientation::ORIENTATION_HORIZONTAL) +{ + // List view. + list_view = Gtk::make_managed(); + list_view->set_model(list_model); + list_view->append_column(*Gtk::manage(makeAppColumn())); + list_view->append_column(*Gtk::manage(makeCommandColumn())); + + for (auto &&column : list_view->get_columns()) { + column->set_sizing(Gtk::TreeViewColumnSizing::TREE_VIEW_COLUMN_FIXED); + } + + list_view->set_grid_lines(Gtk::TREE_VIEW_GRID_LINES_VERTICAL); + list_view->set_reorderable(); + + // List scroll area. + list_scroll_area.set_hexpand(); + list_scroll_area.set_vexpand(); + list_scroll_area.add(*list_view); + + // Toolbar buttons. + auto add_image = Gtk::make_managed("add-small.png"); + auto remove_image = Gtk::make_managed("remove-small.png"); + button_add = Gtk::make_managed(); + button_remove = Gtk::make_managed(); + button_add->set_image(*add_image); + button_remove->set_image(*remove_image); + button_app_chooser = Gtk::make_managed(M("PREFERENCES_EXTERNALEDITOR_CHANGE")); + + button_app_chooser->signal_pressed().connect(sigc::mem_fun( + *this, &ExternalEditorPreferences::openAppChooserDialog)); + button_add->signal_pressed().connect(sigc::mem_fun( + *this, &ExternalEditorPreferences::addEditor)); + button_remove->signal_pressed().connect(sigc::mem_fun( + *this, &ExternalEditorPreferences::removeSelectedEditors)); + + list_view->get_selection()->signal_changed().connect(sigc::mem_fun( + *this, &ExternalEditorPreferences::updateToolbarSensitivity)); + updateToolbarSensitivity(); + + // Toolbar. + toolbar.set_halign(Gtk::Align::ALIGN_END); + toolbar.add(*button_app_chooser); + toolbar.add(*button_add); + toolbar.add(*button_remove); + + // This widget's children. + add(list_scroll_area); + add(toolbar); + show_all(); +} + +std::vector +ExternalEditorPreferences::getEditors() const +{ + std::vector editors; + + auto children = list_model->children(); + + for (auto rowIter = children.begin(); rowIter != children.end(); rowIter++) { + const Gio::Icon *const icon = rowIter->get_value(model_columns.icon).get(); + const auto &icon_name = icon == nullptr ? "" : icon->to_string(); + editors.push_back(ExternalEditorPreferences::EditorInfo( + rowIter->get_value(model_columns.name), + rowIter->get_value(model_columns.command), + icon_name, + rowIter->get_value(model_columns.other_data) + )); + } + + return editors; +} + +void ExternalEditorPreferences::setEditors( + const std::vector &editors) +{ + list_model->clear(); + + for (const ExternalEditorPreferences::EditorInfo & editor : editors) { + auto row = *list_model->append(); + row[model_columns.name] = editor.name; + row[model_columns.icon] = editor.icon_name.empty() ? Glib::RefPtr() : Gio::Icon::create(editor.icon_name); + row[model_columns.command] = editor.command; + row[model_columns.other_data] = editor.other_data; + } +} + +void ExternalEditorPreferences::addEditor() +{ + Gtk::TreeModel::Row row; + auto selected = list_view->get_selection()->get_selected_rows(); + + if (selected.size()) { + row = *list_model->insert_after(list_model->get_iter(selected.back())); + } else { + row = *list_model->append(); + } + + row[model_columns.name] = "-"; + list_view->get_selection()->select(row); +} + +Gtk::TreeViewColumn *ExternalEditorPreferences::makeAppColumn() +{ + auto name_renderer = Gtk::make_managed(); + auto icon_renderer = Gtk::make_managed(); + auto col = Gtk::make_managed(); + + col->set_title(M("PREFERENCES_EXTERNALEDITOR_COLUMN_NAME")); + col->set_resizable(); + col->pack_start(*icon_renderer, false); + col->pack_start(*name_renderer); + col->add_attribute(*icon_renderer, "gicon", model_columns.icon); + col->add_attribute(*name_renderer, "text", model_columns.name); + col->set_min_width(20); + + name_renderer->property_editable() = true; + name_renderer->signal_edited().connect( + sigc::mem_fun(*this, &ExternalEditorPreferences::setAppName)); + + return col; +} + +Gtk::TreeViewColumn *ExternalEditorPreferences::makeCommandColumn() +{ + auto command_renderer = Gtk::make_managed(); + auto col = Gtk::make_managed(); + + col->set_title(M("PREFERENCES_EXTERNALEDITOR_COLUMN_COMMAND")); + col->pack_start(*command_renderer); + col->add_attribute(*command_renderer, "text", model_columns.command); + + command_renderer->property_editable() = true; + command_renderer->signal_edited().connect( + sigc::mem_fun(*this, &ExternalEditorPreferences::setAppCommand)); + + return col; +} + +void ExternalEditorPreferences::onAppChooserDialogResponse( + int response_id, Gtk::AppChooserDialog *dialog) +{ + switch (response_id) { + case Gtk::RESPONSE_OK: + dialog->close(); + setApp(dialog->get_app_info()); + break; + + case Gtk::RESPONSE_CANCEL: + case Gtk::RESPONSE_CLOSE: + dialog->close(); + break; + + default: + break; + } +} + +void ExternalEditorPreferences::openAppChooserDialog() +{ + if (app_chooser_dialog.get()) { + app_chooser_dialog->refresh(); + app_chooser_dialog->show(); + return; + } + + app_chooser_dialog.reset(new Gtk::AppChooserDialog("image/tiff")); + app_chooser_dialog->signal_response().connect(sigc::bind( + sigc::mem_fun(*this, &ExternalEditorPreferences::onAppChooserDialogResponse), + app_chooser_dialog.get() + )); + app_chooser_dialog->set_modal(); + app_chooser_dialog->show(); +} + +void ExternalEditorPreferences::removeSelectedEditors() +{ + auto selection = list_view->get_selection()->get_selected_rows(); + + for (const auto &selected : selection) { + list_model->erase(list_model->get_iter(selected)); + } +} + +void ExternalEditorPreferences::setApp(const Glib::RefPtr app_info) +{ + auto selection = list_view->get_selection()->get_selected_rows(); + + for (const auto &selected : selection) { + auto row = *list_model->get_iter(selected); + row[model_columns.icon] = app_info->get_icon(); + row[model_columns.name] = app_info->get_name(); + row[model_columns.command] = app_info->get_commandline(); + } +} + +void ExternalEditorPreferences::setAppCommand( + const Glib::ustring & path, const Glib::ustring & new_text) +{ + auto row_iter = list_model->get_iter(path); + + if (!row_iter->get_value(model_columns.command).compare(new_text)) { + return; + } + + row_iter->set_value(model_columns.command, new_text); + row_iter->set_value(model_columns.icon, Glib::RefPtr(nullptr)); +} + +void ExternalEditorPreferences::setAppName( + const Glib::ustring & path, const Glib::ustring & new_text) +{ + list_model->get_iter(path)->set_value(model_columns.name, new_text); +} + +void ExternalEditorPreferences::updateToolbarSensitivity() +{ + bool selected = list_view->get_selection()->count_selected_rows(); + button_app_chooser->set_sensitive(selected); + button_remove->set_sensitive(selected); +} + +ExternalEditorPreferences::EditorInfo::EditorInfo( + Glib::ustring name, Glib::ustring command, Glib::ustring icon_name, void *other_data +) : name(name), icon_name(icon_name), command(command), other_data(other_data) +{ +} + +ExternalEditorPreferences::ModelColumns::ModelColumns() +{ + add(name); + add(icon); + add(command); + add(other_data); +} diff --git a/rtgui/externaleditorpreferences.h b/rtgui/externaleditorpreferences.h new file mode 100644 index 000000000..be988e901 --- /dev/null +++ b/rtgui/externaleditorpreferences.h @@ -0,0 +1,147 @@ +/* + * This file is part of RawTherapee. + * + * Copyright (c) 2021 Lawrence Lee + * + * RawTherapee is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * RawTherapee 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with RawTherapee. If not, see . + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + +/** + * Widget for editing the external editors options. + */ +class ExternalEditorPreferences : public Gtk::Box +{ +public: + /** + * Data struct containing information about an external editor. + */ + struct EditorInfo { + explicit EditorInfo( + Glib::ustring name = Glib::ustring(), + Glib::ustring command = Glib::ustring(), + Glib::ustring icon_name = Glib::ustring(), + void *other_data = nullptr + ); + /** + * Name of the external editor. + */ + Glib::ustring name; + /** + * The string representation of the icon. See Gio::Icon::to_string(). + */ + Glib::ustring icon_name; + /** + * The commandline for running the program. See + * Gio::AppInfo::get_commandline() + */ + Glib::ustring command; + /** + * Holds any other data associated with the editor. For example, it can + * be used as a tag to uniquely identify the editor. + */ + void *other_data; + }; + + ExternalEditorPreferences(); + + /** + * Creates and returns a vector representing the external editors shown in + * this widget. + */ + std::vector getEditors() const; + /** + * Populates this widget with the external editors described in the + * argument. + */ + void setEditors(const std::vector &editors); + +private: + /** + * Model representing the data fields each external editor entry has. + */ + class ModelColumns : public Gtk::TreeModelColumnRecord + { + public: + ModelColumns(); + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn> icon; + Gtk::TreeModelColumn command; + Gtk::TreeModelColumn other_data; + }; + + ModelColumns model_columns; + Glib::RefPtr list_model; // The list of editors. + Gtk::ScrolledWindow list_scroll_area; // Allows the list to be scrolled. + Gtk::TreeView *list_view; // Widget for displaying the list. + Gtk::Box toolbar; // Contains buttons for editing the list. + Gtk::Button *button_app_chooser; + Gtk::Button *button_add; + Gtk::Button *button_remove; + std::unique_ptr app_chooser_dialog; + + /** + * Inserts a new editor entry after the current selection, or at the end if + * no editor is selected. + */ + void addEditor(); + /** + * Constructs the column for displaying the external editor name (and icon). + */ + Gtk::TreeViewColumn *makeAppColumn(); + /** + * Constructs the column for displaying an editable commandline. + */ + Gtk::TreeViewColumn *makeCommandColumn(); + /** + * Called when the user is done interacting with the app chooser dialog. + * Closes the dialog and updates the selected entry if an app was chosen. + */ + void onAppChooserDialogResponse(int responseId, Gtk::AppChooserDialog *dialog); + /** + * Shows the app chooser dialog. + */ + void openAppChooserDialog(); + /** + * Removes all selected editors. + */ + void removeSelectedEditors(); + /** + * Sets the selected entries with the provided information. + */ + void setApp(const Glib::RefPtr app_info); + /** + * Updates the application command and removes the icon for the given row. + */ + void setAppCommand(const Glib::ustring & path, const Glib::ustring & new_text); + /** + * Updates the application name for the given row. + */ + void setAppName(const Glib::ustring & path, const Glib::ustring & new_text); + /** + * Sets the sensitivity of the widgets in the toolbar to reflect the current + * state of the list. For example, makes the remove button insensitive if no + * entries are selected. + */ + void updateToolbarSensitivity(); +};