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: