From 22edf5f0697dbdafa51744b9c03dcc673e7de957 Mon Sep 17 00:00:00 2001 From: Lawrence Lee <45837045+Lawrence37@users.noreply.github.com> Date: Sun, 15 Jan 2023 15:10:54 -0800 Subject: [PATCH] Add radio indicator to external editor selector Make it easier to see that the selector is for changing the current editor and not for immediately sending the image to the clicked-on editor. --- rtdata/themes/TooWaBlue-GTK3-20_.css | 5 ++ rtdata/themes/size - Legacy.css | 7 +- rtdata/themes/size.css | 5 ++ rtgui/editorpanel.cc | 16 ++-- rtgui/editorpanel.h | 1 + rtgui/guiutils.cc | 108 +++++++++++++++++++++------ rtgui/guiutils.h | 56 +++++++++++--- rtgui/popupcommon.cc | 35 ++++++--- rtgui/popupcommon.h | 9 ++- 9 files changed, 186 insertions(+), 56 deletions(-) diff --git a/rtdata/themes/TooWaBlue-GTK3-20_.css b/rtdata/themes/TooWaBlue-GTK3-20_.css index 4e7e192ad..d2a63dd2e 100644 --- a/rtdata/themes/TooWaBlue-GTK3-20_.css +++ b/rtdata/themes/TooWaBlue-GTK3-20_.css @@ -1278,6 +1278,11 @@ menuitem:hover > * { color: @text-hl-color; } +menu menuitem > radio + * image:not(.dummy), +#MyExpander menu menuitem > radio + * image:not(.dummy) { + margin-left: 1pt; +} + menu image:not(.dummy), #MyExpander menu image:not(.dummy) { min-height: 2em; diff --git a/rtdata/themes/size - Legacy.css b/rtdata/themes/size - Legacy.css index 08c39f973..089a909ee 100644 --- a/rtdata/themes/size - Legacy.css +++ b/rtdata/themes/size - Legacy.css @@ -383,6 +383,11 @@ menu arrow { margin: 0 -0.25em 0 0; } +menu menuitem > radio + * image:not(.dummy), +#MyExpander menu menuitem > radio + * image:not(.dummy) { + margin-left: 1pt; +} + menu image:not(.dummy), #MyExpander menu image:not(.dummy) { min-height: 2em; @@ -1029,4 +1034,4 @@ messagedialog headerbar button.titlebutton { min-height: 1.25em; margin: 0; } -/*** end ***************************************************************************************/ \ No newline at end of file +/*** end ***************************************************************************************/ diff --git a/rtdata/themes/size.css b/rtdata/themes/size.css index 2d23bf860..675ed51c2 100644 --- a/rtdata/themes/size.css +++ b/rtdata/themes/size.css @@ -351,6 +351,11 @@ menu arrow { margin: 0 -0.25em 0 0; } +menu menuitem > radio + * image:not(.dummy), +#MyExpander menu menuitem > radio + * image:not(.dummy) { + margin-left: 1pt; +} + menu image:not(.dummy), #MyExpander menu image:not(.dummy) { min-height: 2em; diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index 3a180cc58..ebcf221da 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -905,7 +905,6 @@ EditorPanel::EditorPanel (FilePanel* filePanel) send_to_external = Gtk::manage(new PopUpButton("", false)); send_to_external->set_tooltip_text(M("MAIN_BUTTON_SENDTOEDITOR_TOOLTIP")); setExpandAlignProperties(send_to_external->buttonGroup, false, false, Gtk::ALIGN_CENTER, Gtk::ALIGN_FILL); - send_to_external->addEntry("palette-brush.png", M("GENERAL_OTHER")); updateExternalEditorWidget( options.externalEditorIndex >= 0 ? options.externalEditorIndex : options.externalEditors.size(), options.externalEditors @@ -2748,10 +2747,14 @@ void EditorPanel::tbShowHideSidePanels_managestate() void EditorPanel::updateExternalEditorWidget(int selectedIndex, const std::vector &editors) { - // Remove the editors and leave the "Other" entry. - while (send_to_external->getEntryCount() > 1) { - send_to_external->removeEntry(0); + // Remove the editors. + while (send_to_external->getEntryCount()) { + send_to_external->removeEntry(send_to_external->getEntryCount() - 1); } + + // Create new radio button group because they cannot be reused: https://developer-old.gnome.org/gtkmm/3.16/classGtk_1_1RadioButtonGroup.html#details. + send_to_external_radio_group = Gtk::RadioButtonGroup(); + // Add the editors. for (unsigned i = 0; i < editors.size(); i++) { const auto & name = editors[i].name.empty() ? Glib::ustring(" ") : editors[i].name; @@ -2771,11 +2774,12 @@ void EditorPanel::updateExternalEditorWidget(int selectedIndex, const std::vecto gioIcon = Gio::Icon::deserialize(Glib::VariantBase(icon_variant)); } - send_to_external->insertEntry(i, gioIcon, name); + send_to_external->insertEntry(i, gioIcon, name, &send_to_external_radio_group); } else { - send_to_external->insertEntry(i, "palette-brush.png", name); + send_to_external->insertEntry(i, "palette-brush.png", name, &send_to_external_radio_group); } } + send_to_external->addEntry("palette-brush.png", M("GENERAL_OTHER"), &send_to_external_radio_group); send_to_external->setSelected(selectedIndex); send_to_external->show(); } diff --git a/rtgui/editorpanel.h b/rtgui/editorpanel.h index e822f1677..fa7f7943b 100644 --- a/rtgui/editorpanel.h +++ b/rtgui/editorpanel.h @@ -246,6 +246,7 @@ private: Gtk::Button* queueimg; Gtk::Button* saveimgas; PopUpButton* send_to_external; + Gtk::RadioButtonGroup send_to_external_radio_group; Gtk::Button* navSync; Gtk::Button* navNext; Gtk::Button* navPrev; diff --git a/rtgui/guiutils.cc b/rtgui/guiutils.cc index 522ec9b7f..18b82fe36 100644 --- a/rtgui/guiutils.cc +++ b/rtgui/guiutils.cc @@ -1506,48 +1506,110 @@ TextOrIcon::TextOrIcon (const Glib::ustring &fname, const Glib::ustring &labelTx } -MyImageMenuItem::MyImageMenuItem(Glib::ustring label, Glib::ustring imageFileName) +class ImageAndLabel::Impl { - RTImage* itemImage = nullptr; +public: + RTImage* image; + Gtk::Label* label; - if (!imageFileName.empty()) { - itemImage = Gtk::manage(new RTImage(imageFileName)); + Impl(RTImage* image, Gtk::Label* label) : image(image), label(label) {} + static std::unique_ptr createImage(const Glib::ustring& fileName); +}; + +std::unique_ptr ImageAndLabel::Impl::createImage(const Glib::ustring& fileName) +{ + if (fileName.empty()) { + return nullptr; } - - construct(label, itemImage); + return std::unique_ptr(new RTImage(fileName)); } -MyImageMenuItem::MyImageMenuItem(Glib::ustring label, RTImage* itemImage) { - construct(label, itemImage); +ImageAndLabel::ImageAndLabel(const Glib::ustring& label, const Glib::ustring& imageFileName) : + ImageAndLabel(label, Gtk::manage(Impl::createImage(imageFileName).release())) +{ } -void MyImageMenuItem::construct(Glib::ustring label, RTImage* itemImage) +ImageAndLabel::ImageAndLabel(const Glib::ustring& label, RTImage *image) : + pimpl(new Impl(image, Gtk::manage(new Gtk::Label(label)))) { - box = Gtk::manage (new Gtk::Grid()); - this->label = Gtk::manage( new Gtk::Label(label)); - box->set_orientation(Gtk::ORIENTATION_HORIZONTAL); + Gtk::Grid* grid = Gtk::manage(new Gtk::Grid()); + grid->set_orientation(Gtk::ORIENTATION_HORIZONTAL); - if (itemImage) { - image = itemImage; - box->attach_next_to(*image, Gtk::POS_LEFT, 1, 1); - } else { - image = nullptr; + if (image) { + grid->attach_next_to(*image, Gtk::POS_LEFT, 1, 1); } - box->attach_next_to(*this->label, Gtk::POS_RIGHT, 1, 1); - box->set_column_spacing(4); - box->set_row_spacing(0); - add(*box); + grid->attach_next_to(*(pimpl->label), Gtk::POS_RIGHT, 1, 1); + grid->set_column_spacing(4); + grid->set_row_spacing(0); + pack_start(*grid, Gtk::PACK_SHRINK, 0); +} + +const RTImage* ImageAndLabel::getImage() const +{ + return pimpl->image; +} + +const Gtk::Label* ImageAndLabel::getLabel() const +{ + return pimpl->label; +} + +class MyImageMenuItem::Impl +{ +private: + std::unique_ptr widget; + +public: + Impl(const Glib::ustring &label, const Glib::ustring &imageFileName) : + widget(new ImageAndLabel(label, imageFileName)) {} + Impl(const Glib::ustring &label, RTImage *itemImage) : + widget(new ImageAndLabel(label, itemImage)) {} + ImageAndLabel* getWidget() const { return widget.get(); } +}; + +MyImageMenuItem::MyImageMenuItem(const Glib::ustring& label, const Glib::ustring& imageFileName) : + pimpl(new Impl(label, imageFileName)) +{ + add(*(pimpl->getWidget())); +} + +MyImageMenuItem::MyImageMenuItem(const Glib::ustring& label, RTImage* itemImage) : + pimpl(new Impl(label, itemImage)) +{ + add(*(pimpl->getWidget())); } const RTImage *MyImageMenuItem::getImage () const { - return image; + return pimpl->getWidget()->getImage(); } const Gtk::Label* MyImageMenuItem::getLabel () const { - return label; + return pimpl->getWidget()->getLabel(); +} + +class MyRadioImageMenuItem::Impl +{ + std::unique_ptr widget; + +public: + Impl(const Glib::ustring &label, RTImage *image) : + widget(new ImageAndLabel(label, image)) {} + ImageAndLabel* getWidget() const { return widget.get(); } +}; + +MyRadioImageMenuItem::MyRadioImageMenuItem(const Glib::ustring& label, RTImage *image, Gtk::RadioButton::Group& group) : + Gtk::RadioMenuItem(group), + pimpl(new Impl(label, image)) +{ + add(*(pimpl->getWidget())); +} + +const Gtk::Label* MyRadioImageMenuItem::getLabel() const +{ + return pimpl->getWidget()->getLabel(); } MyProgressBar::MyProgressBar(int width) : w(rtengine::max(width, 10 * RTScalable::getScale())) {} diff --git a/rtgui/guiutils.h b/rtgui/guiutils.h index d133599f2..938c81c42 100644 --- a/rtgui/guiutils.h +++ b/rtgui/guiutils.h @@ -482,20 +482,56 @@ public: TextOrIcon (const Glib::ustring &filename, const Glib::ustring &labelTx, const Glib::ustring &tooltipTx); }; -class MyImageMenuItem final : public Gtk::MenuItem +/** + * Widget with image and label placed horizontally. + */ +class ImageAndLabel final : public Gtk::Box { -private: - Gtk::Grid *box; - RTImage *image; - Gtk::Label *label; - - void construct(Glib::ustring label, RTImage* image); + class Impl; + std::unique_ptr pimpl; public: - MyImageMenuItem (Glib::ustring label, Glib::ustring imageFileName); - MyImageMenuItem (Glib::ustring label, RTImage* image); + ImageAndLabel(const Glib::ustring& label, const Glib::ustring& imageFileName); + ImageAndLabel(const Glib::ustring& label, RTImage* image); + const RTImage* getImage() const; + const Gtk::Label* getLabel() const; +}; + +/** + * Menu item with an image and label. + */ +class MyImageMenuItemInterface +{ +public: + virtual const Gtk::Label* getLabel() const = 0; +}; + +/** + * Basic image menu item. + */ +class MyImageMenuItem final : public Gtk::MenuItem, public MyImageMenuItemInterface +{ + class Impl; + std::unique_ptr pimpl; + +public: + MyImageMenuItem (const Glib::ustring& label, const Glib::ustring& imageFileName); + MyImageMenuItem (const Glib::ustring& label, RTImage* image); const RTImage *getImage () const; - const Gtk::Label* getLabel () const; + const Gtk::Label* getLabel() const override; +}; + +/** + * Image menu item with radio selector. + */ +class MyRadioImageMenuItem final : public Gtk::RadioMenuItem, public MyImageMenuItemInterface +{ + class Impl; + std::unique_ptr pimpl; + +public: + MyRadioImageMenuItem(const Glib::ustring& label, RTImage* image, Gtk::RadioButton::Group& group); + const Gtk::Label* getLabel() const override; }; class MyProgressBar final : public Gtk::ProgressBar diff --git a/rtgui/popupcommon.cc b/rtgui/popupcommon.cc index 1dbde833e..a6d9b6046 100644 --- a/rtgui/popupcommon.cc +++ b/rtgui/popupcommon.cc @@ -73,44 +73,50 @@ PopUpCommon::~PopUpCommon () { } -bool PopUpCommon::addEntry (const Glib::ustring& fileName, const Glib::ustring& label) +bool PopUpCommon::addEntry (const Glib::ustring& fileName, const Glib::ustring& label, Gtk::RadioButtonGroup* radioGroup) { - return insertEntry(getEntryCount(), fileName, label); + return insertEntry(getEntryCount(), fileName, label, radioGroup); } -bool PopUpCommon::insertEntry(int position, const Glib::ustring& fileName, const Glib::ustring& label) +bool PopUpCommon::insertEntry(int position, const Glib::ustring& fileName, const Glib::ustring& label, Gtk::RadioButtonGroup *radioGroup) { RTImage* image = nullptr; if (!fileName.empty()) { image = Gtk::manage(new RTImage(fileName)); } - bool success = insertEntryImpl(position, fileName, Glib::RefPtr(), image, label); + bool success = insertEntryImpl(position, fileName, Glib::RefPtr(), image, label, radioGroup); if (!success && image) { delete image; } return success; } -bool PopUpCommon::insertEntry(int position, const Glib::RefPtr& gIcon, const Glib::ustring& label) +bool PopUpCommon::insertEntry(int position, const Glib::RefPtr& gIcon, const Glib::ustring& label, Gtk::RadioButtonGroup *radioGroup) { RTImage* image = Gtk::manage(new RTImage(gIcon, Gtk::ICON_SIZE_BUTTON)); - bool success = insertEntryImpl(position, "", gIcon, image, label); + bool success = insertEntryImpl(position, "", gIcon, image, label, radioGroup); if (!success) { delete image; } return success; } -bool PopUpCommon::insertEntryImpl(int position, const Glib::ustring& fileName, const Glib::RefPtr& gIcon, RTImage* image, const Glib::ustring& label) +bool PopUpCommon::insertEntryImpl(int position, const Glib::ustring& fileName, const Glib::RefPtr& gIcon, RTImage* image, const Glib::ustring& label, Gtk::RadioButtonGroup* radioGroup) { if (label.empty() || position < 0 || position > getEntryCount()) return false; // Create the menu item and image - MyImageMenuItem *newItem = Gtk::manage(new MyImageMenuItem(label, image)); + Gtk::MenuItem *newItem; + if (radioGroup) { + newItem = Gtk::manage(new MyRadioImageMenuItem(label, image, *radioGroup)); + } + else { + newItem = Gtk::manage(new MyImageMenuItem(label, image)); + } imageIcons.insert(imageIcons.begin() + position, gIcon); imageFilenames.insert(imageFilenames.begin() + position, fileName); - images.insert(images.begin() + position, newItem->getImage()); + images.insert(images.begin() + position, image); // When there is at least 1 choice, we add the arrow button if (images.size() == 1) { @@ -154,9 +160,8 @@ void PopUpCommon::removeEntry(int position) setButtonHint(); } - MyImageMenuItem *menuItem = dynamic_cast(menu->get_children()[position]); + std::unique_ptr menuItem(menu->get_children()[position]); menu->remove(*menuItem); - delete menuItem; imageIcons.erase(imageIcons.begin() + position); imageFilenames.erase(imageFilenames.begin() + position); images.erase(images.begin() + position); @@ -222,6 +227,12 @@ bool PopUpCommon::setSelected (int entryNum) changeImage(entryNum); selected = entryNum; setButtonHint(); + + auto radioMenuItem = dynamic_cast(menu->get_children()[entryNum]); + if (radioMenuItem && menu->get_active() != radioMenuItem) { + radioMenuItem->set_active(); + } + return true; } } @@ -248,7 +259,7 @@ void PopUpCommon::setButtonHint() if (selected > -1) { auto widget = menu->get_children ()[selected]; - auto item = dynamic_cast(widget); + auto item = dynamic_cast(widget); if (item) { hint += escapeHtmlChars(item->getLabel()->get_text()); diff --git a/rtgui/popupcommon.h b/rtgui/popupcommon.h index 59a5b8d0e..9ca6b2030 100644 --- a/rtgui/popupcommon.h +++ b/rtgui/popupcommon.h @@ -40,6 +40,7 @@ class Grid; class Menu; class Button; class ImageMenuItem; +class RadioButtonGroup; class Widget; } @@ -60,9 +61,9 @@ public: explicit PopUpCommon (Gtk::Button* button, const Glib::ustring& label = ""); virtual ~PopUpCommon (); - bool addEntry (const Glib::ustring& fileName, const Glib::ustring& label); - bool insertEntry(int position, const Glib::ustring& fileName, const Glib::ustring& label); - bool insertEntry(int position, const Glib::RefPtr& gIcon, const Glib::ustring& label); + bool addEntry (const Glib::ustring& fileName, const Glib::ustring& label, Gtk::RadioButtonGroup* radioGroup = nullptr); + bool insertEntry(int position, const Glib::ustring& fileName, const Glib::ustring& label, Gtk::RadioButtonGroup* radioGroup = nullptr); + bool insertEntry(int position, const Glib::RefPtr& gIcon, const Glib::ustring& label, Gtk::RadioButtonGroup* radioGroup = nullptr); int getEntryCount () const; bool setSelected (int entryNum); int getSelected () const; @@ -91,7 +92,7 @@ private: void changeImage(int position); void changeImage(const Glib::ustring& fileName, const Glib::RefPtr& gIcon); void entrySelected(Gtk::Widget* menuItem); - bool insertEntryImpl(int position, const Glib::ustring& fileName, const Glib::RefPtr& gIcon, RTImage* image, const Glib::ustring& label); + bool insertEntryImpl(int position, const Glib::ustring& fileName, const Glib::RefPtr& gIcon, RTImage* image, const Glib::ustring& label, Gtk::RadioButtonGroup* radioGroup); void showMenu(GdkEventButton* event); protected: