From 1e4e8e97abee723c2ba79c67dff12852e4f2f63c Mon Sep 17 00:00:00 2001 From: michael Date: Sat, 6 Apr 2013 18:38:23 -0400 Subject: [PATCH] Image navigation for single tab editor, synchronization of editor with file browser and improvements to various other shortcuts (see issue 47, comment 52). Thanks Hombre & DrSlony for help! --- rtdata/images/Dark/actions/nav-next.png | Bin 0 -> 711 bytes rtdata/images/Dark/actions/nav-prev.png | Bin 0 -> 712 bytes rtdata/images/Dark/actions/nav-sync.png | Bin 0 -> 487 bytes rtdata/images/Light/actions/nav-next.png | Bin 0 -> 710 bytes rtdata/images/Light/actions/nav-prev.png | Bin 0 -> 704 bytes rtdata/images/Light/actions/nav-sync.png | Bin 0 -> 482 bytes rtdata/languages/default | 19 +- rtgui/editorpanel.cc | 87 ++- rtgui/editorpanel.h | 8 +- rtgui/filebrowser.cc | 191 ++++- rtgui/filebrowser.h | 2 + rtgui/filecatalog.cc | 165 ++++- rtgui/filecatalog.h | 8 +- rtgui/filterpanel.h | 54 +- rtgui/guiutils.h | 6 + rtgui/toolbar.cc | 5 +- tools/source_icons/scalable/nav-next.file | 1 + tools/source_icons/scalable/nav-next.svg | 649 +++++++++++++++++ tools/source_icons/scalable/nav-prev.file | 1 + tools/source_icons/scalable/nav-prev.svg | 649 +++++++++++++++++ tools/source_icons/scalable/nav-sync.file | 1 + tools/source_icons/scalable/nav-sync.svg | 833 ++++++++++++++++++++++ 22 files changed, 2571 insertions(+), 108 deletions(-) create mode 100644 rtdata/images/Dark/actions/nav-next.png create mode 100644 rtdata/images/Dark/actions/nav-prev.png create mode 100644 rtdata/images/Dark/actions/nav-sync.png create mode 100644 rtdata/images/Light/actions/nav-next.png create mode 100644 rtdata/images/Light/actions/nav-prev.png create mode 100644 rtdata/images/Light/actions/nav-sync.png create mode 100644 tools/source_icons/scalable/nav-next.file create mode 100644 tools/source_icons/scalable/nav-next.svg create mode 100644 tools/source_icons/scalable/nav-prev.file create mode 100644 tools/source_icons/scalable/nav-prev.svg create mode 100644 tools/source_icons/scalable/nav-sync.file create mode 100644 tools/source_icons/scalable/nav-sync.svg diff --git a/rtdata/images/Dark/actions/nav-next.png b/rtdata/images/Dark/actions/nav-next.png new file mode 100644 index 0000000000000000000000000000000000000000..a3e8ed0b0da82ffb014e2d6ddb7f609e99094392 GIT binary patch literal 711 zcmV;&0yzDNP)8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H10z^qfK~y-6rI5dC6hR!tzu)i94!b*NZWE0l7)Ym}U?Y-Caw$A+ zC545(e}GL0ArKHrqo62)2>$5&2Nc2TI8MFX5=c_0U?ZXd6%?Z2k-+S1cEXH>Mv^nP z3Gu}ZZ1UcfIpXZ$4Tv}S{$@6V07e+)B1i{HH%Wlul z&-eZp%vuXX1VV`8h>Ok4AOKh?<&l`<~4a0C&2ytY6 zef>ck$CG1YV;{EF+^ee9>Wg-}eKkpv#P|KfVHiGaHk&8*1p@$xqUh!7>gr>ywe~!( zuUITzUszc1_XTS-8mEiJ;sw`r-84;qN+~awN+kvG?`FAbHk-%7FudmbeovAl-_tam zD3{C2o9}y07RT{`@B6nr&+Ao6eYMtJ8Xg{A-s;(wjEDwXt=2RAEdl@}B2Y@*5JF4@ zLC~+1`mD8{8W|b+*7@OIn6(y+F+fD_pTx)*BgPm5fEmy8`lOT}H#Rn=%jNQqozkPV zwrjOkt94fhaS9PBP1E<*+L_VO(RMd~?q-Mx#u$)NzFk{eo6ZAmtGSbmF-A)HT5CN! tK0Yq)M5()w~ae@E<002ovPDHLkV1nAIIlBM= literal 0 HcmV?d00001 diff --git a/rtdata/images/Dark/actions/nav-prev.png b/rtdata/images/Dark/actions/nav-prev.png new file mode 100644 index 0000000000000000000000000000000000000000..cd621d372e9f7cd3f0d844691ab30871281aa493 GIT binary patch literal 712 zcmV;(0yq7MP)8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H10!2wgK~y-6rISr)6G0Tm|GQ+C%nasZ8$YmA4<4%E&3n2SNC=c( zw0IZvA}DyM(3>bK6)9CO-n~df5OT=}*%0VK4nlE;Ip?PVX!|ABlu|@l}hD!JRVBdSMhlKVk8ool2Yd6mclTUnx=(@VY~+L zH>_H%o=7H>Pbj5Fg%G~$y7wrhZ#tdM#$GQWttEhw`Q|>&GWpEl+tO&*pguwT-S9200CeCAW}-~ zE@tF9ZB>{cggm5_&W2&Agb?rN=H_mU@{e6~%d)zyR_kW3*V_OvI_o4b09aU9a2>~4 u5<LU0000wz3e2UA9>82}BzU zpTII8C@5;_1Bi`aW1Cc2h7i~U1Hslu5Q`9ytY%&3*u>!OhBXBA*5|wToXee|0LU!G zW}=c{v)R0?R;%+#k(6?~(P*4_p0}9-ec#_RO>@VxtV*goV_QOqeE=IV(D(f%(=?Ab z=S6rw2dD5aHNuQ&HC zqLijqui3W!K?rHp>-AM#*9jry*>Rjs*0izJL>~GZG!dM(rUC(^T>R}s#?mq^3#ip< z3qpu>0E?prYf{Q$KA(Tg1Pg`2JLmk$FpT9e3_lISI2aCxE4FQSVjd`s@1RsFy$uF~ zLp8cN0Iy1^<5WgWc%0>O`5_2`I{=8H=-hSPmsmzh=~Ebnw}g;Z67Ki=r@34Xj^o^q d_5c0@d;zcTli4_|gWdoD002ovPDHLkV1m~3%X$C+ literal 0 HcmV?d00001 diff --git a/rtdata/images/Light/actions/nav-next.png b/rtdata/images/Light/actions/nav-next.png new file mode 100644 index 0000000000000000000000000000000000000000..d68e1df60e1d1385fb05f6f0f57f2d1ae7268928 GIT binary patch literal 710 zcmV;%0y+JOP)8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H10z*keK~y-6rIbyJ6G0G$yQ?Rg50jpJ3Lch-$uNPDn1J{LynEP7 zPX2{u2@4A%dr?pnK@`-RKOlGog}c4KBD>kCEG z)XP&{MRy}11ab>iRWIiA`4e8A!vV-c0C351oLh>bP%q!y@0BEkKqL}58w>{Tq|@ox zf5ABC2xE*WrMY-KepgYHl$Re1A&{adDFC<(02uxVV~jBXTnL3iNzS?O`~5z~*xq=s z2b0O<@n5q5s1F8%cL<@YvMjGllJuZhEN<27_0O&_Ap~o;+pk?l@ERUkVP4dAnoHDbwO$&Aam_T%pW$%$WHcImR45d*W5EzYNZ0jOy#Ik!{;IWm(>& zl%|HmVP`NHlyzPIx>#@yn>`(jv2V)4004nNKq7?fgu~%@zu*5TgxJ<~{rj@hV(bjR07*qoM6N<$g0bH-d;kCd literal 0 HcmV?d00001 diff --git a/rtdata/images/Light/actions/nav-prev.png b/rtdata/images/Light/actions/nav-prev.png new file mode 100644 index 0000000000000000000000000000000000000000..ad37367ba25aff1338d93702430292d95688fef4 GIT binary patch literal 704 zcmV;x0zdtUP)8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H10zFAYK~y-6os`du(?Ar*U&3r?NHR@Lsl`%lX-Z;gd-3Sai+_NE zcR??Lf`=8niK5~nvaWxE7cYYUfPyzYh?EvmTFgN#68mG)rm3y*jfY*A#ci{7AIxEx z?|j~uH*X$r&S9NZuh$jJvM$7F2&@sdZEJ*(N1`bH0)V%xbmCESLW*mX%H>lUtgml@O61l~~|>xyCfjjIQg?G)+4*o6Q!YD2ibi_7U+T=bR%V$KBwF z__EXK{8;MDw(V1fVQj0anhV45=V&x~Z5YPoL?U4z;>9S65D_toA_Rcn0PqF?4#P^N z(&=nAyQ3(|$;o6g8xDsL1wr`G@An&rYDCCY07&oe4<44wItbHNz969mCfk|dH&r!SeNdG1&+0DwlL;q-dFYe5irx~@~hFdo>p zZ5;~+0N^-|+w1kN`@a7znM|fMO?zURW`14R(xxpIi`h&jv#qLXo-y`yI-Ne3W%({5 z!d}B7-@Qoon)YV9-Tt!lueq-KoiTQUG1jG&=44sklx0~Vgh-+&ZU}-P0l)_591tSyROX=Vn_ynSh zgHPaC5EK-3^Z~?0aPbwK3zmRWA%$*T#EuCV68!!yE!YHVE$BBM-;ew5IF6;1w2Z`3 zR2r<+YB%L_c{OdSR4RM*di~sSoSh8VXfzHBg~GmRnw>PLs_L$;>qpGIJp(qI%?-ma zP8CHFxm@nRb=~$tQ2|^EA=V~~Vj{Y>EK8r70A#^dtF^++cVQUb1CVhXKNC?$2%&xR zWmd0vo|h0&w_2^*s;Uxzx4~f0TPW&O%uLM8KS3GrH(C_Tr1;y8LS${qJixYXQ`0m@ z2(eB?#LQc+>$VNUc%2IZh)Si>t)^*K9LJyeeEv9!qD{~9dUKCtrl-O to focus the path text box.\nEnter / Ctrl-Enter (in the File Browser) to browse there;\n\nPath shortcuts:\n ~ - user's home directory\n ! - user's pictures directory +FILEBROWSER_BROWSEPATHHINT;Type a path to navigate to.\nCtrl-O to focus to the path text box.\nEnter / Ctrl-Enter to browse there;\nEsc to clear changes.\nShift-Esc to remove focus.\n\n\nPath shortcuts:\n ~ - user's home directory\n ! - user's pictures directory FILEBROWSER_CACHECLEARFROMFULL;Clear from cache - full FILEBROWSER_CACHECLEARFROMPARTIAL;Clear from cache - partial FILEBROWSER_CACHE;Cache @@ -149,7 +149,7 @@ FILEBROWSER_POPUPUNTRASH;Remove from trash FILEBROWSER_PROCESSINGSETTINGSHINT;Set the file format and output directory FILEBROWSER_PROCESSINGSETTINGS;Settings FILEBROWSER_QUERYBUTTONHINT;Clear the Find query -FILEBROWSER_QUERYHINT;Type a partial filename to search for or a comma-separated list.\nE.g. 1001,1004,1199 \n\nCtrl-F to focus to the Find text box (in the File Browser).\nEnter to commence search.\nEscape to clear. +FILEBROWSER_QUERYHINT;Type a partial filename to search for or a comma-separated list.\nE.g. 1001,1004,1199 \n\nCtrl-F to focus to the Find text box.\nEnter to commence search.\nEsc to clear.\nShift-Esc to remove focus. FILEBROWSER_QUERYLABEL; Find: FILEBROWSER_RENAMEDLGLABEL;Rename file FILEBROWSER_RENAMEDLGMSG;Rename file "%1" to: @@ -163,7 +163,7 @@ FILEBROWSER_SHOWCOLORLABEL5HINT;Show images labeled Purple.\nShortcut: Alt-5< FILEBROWSER_SHOWDIRHINT;Clear all filters.\nShortcut: D FILEBROWSER_SHOWEDITEDHINT;Show edited images.\nShortcut: 7 FILEBROWSER_SHOWEDITEDNOTHINT;Show not edited images.\nShortcut: 6 -FILEBROWSER_SHOWEXIFINFO;Show Exif info.\nShortcut: i +FILEBROWSER_SHOWEXIFINFO;Show Exif info.\nShortcut: i\n\nShortcut in Single Editor Tab: Alt-i FILEBROWSER_SHOWQUEUEHINT;Show content of the processing queue FILEBROWSER_SHOWRANK1HINT;Show images ranked as 1 star.\nShortcut: 1 FILEBROWSER_SHOWRANK2HINT;Show images ranked as 2 star.\nShortcut: 2 @@ -182,8 +182,8 @@ FILEBROWSER_STOPPROCESSING;Stop Processing FILEBROWSER_THUMBSIZE;Thumbnail size FILEBROWSER_TOOLTIP_STOPPROCESSING;Start processing automatically when a new job arrives FILEBROWSER_USETEMPLATE;Use template: -FILEBROWSER_ZOOMINHINT;Increase thumbnail size.\nShortcut: + -FILEBROWSER_ZOOMOUTHINT;Decrease thumbnail size.\nShortcut: - +FILEBROWSER_ZOOMINHINT;Increase thumbnail size.\nShortcut: +\n\nShortcut in Single Editor Tab: Alt + +FILEBROWSER_ZOOMOUTHINT;Decrease thumbnail size.\nShortcut: -\n\nShortcut in Single Editor Tab: Alt - GENERAL_ABOUT;About GENERAL_AFTER;After GENERAL_AUTO;Automatic @@ -497,6 +497,9 @@ IPTCPANEL_TRANSREFERENCEHINT;A code representing the location of the original tr IPTCPANEL_TRANSREFERENCE;Trans. reference MAIN_BUTTON_EXIT;Exit MAIN_BUTTON_FULLSCREEN;Fullscreen +MAIN_BUTTON_NAVNEXT_TOOLTIP;Navigate to the Next image relative to image open in Editor\nShortcut: Shift-F4\n\nTo navigate to the Next image relative to thumbnail selected in File Browser\nShortcut: F4 +MAIN_BUTTON_NAVPREV_TOOLTIP;Navigate to the Previous image relative to image open in Editor\nShortcut: Shift-F3 \n\nTo navigate to the Previous image relative to thumbnail selected in File Browser\nShortcut: F3 +MAIN_BUTTON_NAVSYNC_TOOLTIP;Synchronize the File Browser with the Editor to reveal the thumbnail of the currently open image, and clear filters in File Browser \nShortcut: x\n\nAs above, but without clearing filters in File Browser\nShortcut: y\n(Note that thumbnail of the open file will not be shown if filtered out). MAIN_BUTTON_PREFERENCES;Preferences MAIN_BUTTON_PUTTOQUEUE_TOOLTIP;Put current image to processing queue.\nShortcut: Ctrl+Q MAIN_BUTTON_QUEUE;Put to Queue @@ -909,8 +912,8 @@ TP_CHMIXER_LABEL;Channel Mixer TP_CHMIXER_RED;Red Channel TP_CHROMATABERR_LABEL;Chromatic Aberration TP_COARSETRAF_TOOLTIP_HFLIP;Flip horizontally -TP_COARSETRAF_TOOLTIP_ROTLEFT;Rotate left.\nShortcut: [ -TP_COARSETRAF_TOOLTIP_ROTRIGHT;Rotate right.\nShortcut: ] +TP_COARSETRAF_TOOLTIP_ROTLEFT;Rotate left.\nShortcut: [\n\nShortcut in Single Editor Tab: Alt-[ +TP_COARSETRAF_TOOLTIP_ROTRIGHT;Rotate right.\nShortcut: ]\n\nShortcut in Single Editor Tab: Alt-] TP_COARSETRAF_TOOLTIP_VFLIP;Flip vertically TP_COLORAPP_ADAPTSCENE;Adaptation scene luminosity (cd/m²) TP_COLORAPP_ADAPTSCENE_TOOLTIP;Absolute luminance of the scene environnement\n(usually 2000cd/m²) @@ -1324,7 +1327,7 @@ ZOOMBAR_SCALE;Scale ZOOMBAR_SMALL;Small ZOOMPANEL_100;(100%) ZOOMPANEL_NEWCROPWINDOW;Open (new) detail window -ZOOMPANEL_ZOOM100;Zoom to 100%\nShortcut: 1 +ZOOMPANEL_ZOOM100;Zoom to 100%\nShortcut: z ZOOMPANEL_ZOOMFITSCREEN;Fit to screen\nShortcut: f ZOOMPANEL_ZOOMIN;Zoom In\nShortcut: + ZOOMPANEL_ZOOMOUT;Zoom Out\nShortcut: - diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc index e544e2e38..614b38ce7 100644 --- a/rtgui/editorpanel.cc +++ b/rtgui/editorpanel.cc @@ -223,6 +223,35 @@ EditorPanel::EditorPanel (FilePanel* filePanel) iops->pack_end (*iareapanel->imageArea->zoomPanel, Gtk::PACK_SHRINK, 1); iops->pack_end (*vsepz3, Gtk::PACK_SHRINK, 2); + // Navigation buttons + Gtk::Image *navPrevImage = Gtk::manage (new RTImage ("nav-prev.png")); + navPrevImage->set_padding(0,0); + navPrev = Gtk::manage (new Gtk::Button ()); + navPrev->add(*navPrevImage); + navPrev->set_relief(Gtk::RELIEF_NONE); + navPrev->set_tooltip_markup(M("MAIN_BUTTON_NAVPREV_TOOLTIP")); + + Gtk::Image *navNextImage = Gtk::manage (new RTImage ("nav-next.png")); + navNextImage->set_padding(0,0); + navNext = Gtk::manage (new Gtk::Button ()); + navNext->add(*navNextImage); + navNext->set_relief(Gtk::RELIEF_NONE); + navNext->set_tooltip_markup(M("MAIN_BUTTON_NAVNEXT_TOOLTIP")); + + Gtk::Image *navSyncImage = Gtk::manage (new RTImage ("nav-sync.png")); + navSyncImage->set_padding(0,0); + navSync = Gtk::manage (new Gtk::Button ()); + navSync->add(*navSyncImage); + navSync->set_relief(Gtk::RELIEF_NONE); + navSync->set_tooltip_markup(M("MAIN_BUTTON_NAVSYNC_TOOLTIP")); + + if (!simpleEditor && !options.tabbedUI){ + iops->pack_end (*Gtk::manage(new Gtk::VSeparator()), Gtk::PACK_SHRINK, 0); + iops->pack_end (*navNext, Gtk::PACK_SHRINK, 0); + iops->pack_end (*navSync, Gtk::PACK_SHRINK, 0); + iops->pack_end (*navPrev, Gtk::PACK_SHRINK, 0); + } + editbox->pack_start (*Gtk::manage(new Gtk::HSeparator()), Gtk::PACK_SHRINK, 0); editbox->pack_start (*iops, Gtk::PACK_SHRINK, 0); editbox->show_all (); @@ -295,6 +324,10 @@ EditorPanel::EditorPanel (FilePanel* filePanel) saveimgas->signal_pressed().connect( sigc::mem_fun(*this, &EditorPanel::saveAsPressed) ); queueimg->signal_pressed().connect( sigc::mem_fun(*this, &EditorPanel::queueImgPressed) ); sendtogimp->signal_pressed().connect( sigc::mem_fun(*this, &EditorPanel::sendToGimpPressed) ); + navPrev->signal_pressed().connect( sigc::mem_fun(*this, &EditorPanel::openPreviousEditorImage) ); + navNext->signal_pressed().connect( sigc::mem_fun(*this, &EditorPanel::openNextEditorImage) ); + navSync->signal_pressed().connect( sigc::mem_fun(*this, &EditorPanel::syncFileBrowser) ); + ShowHideSidePanelsconn = tbShowHideSidePanels->signal_toggled().connect ( sigc::mem_fun(*this, &EditorPanel::toggleSidePanels), true); if (tbTopPanel_1) tbTopPanel_1->signal_toggled().connect( sigc::mem_fun(*this, &EditorPanel::tbTopPanel_1_toggled) ); @@ -866,7 +899,7 @@ bool EditorPanel::handleShortcutKey (GdkEventKey* event) { case GDK_underscore: iareapanel->imageArea->zoomPanel->zoomOutClicked(); return true; - case GDK_1: + case GDK_z://GDK_1 iareapanel->imageArea->zoomPanel->zoom11Clicked(); return true; @@ -909,6 +942,18 @@ bool EditorPanel::handleShortcutKey (GdkEventKey* event) { case GDK_F5: openThm->openDefaultViewer(event->state & GDK_SHIFT_MASK ? 2 : 1); return true; + case GDK_y: // synchronize filebrowser with image in Editor + if (!simpleEditor && fPanel && fname!=""){ + fPanel->fileCatalog->selectImage(fname, false); + return true; + } + break; // to avoid gcc complain + case GDK_x: // clear filters and synchronize filebrowser with image in Editor + if (!simpleEditor && fPanel && fname!=""){ + fPanel->fileCatalog->selectImage(fname, true); + return true; + } + break; // to avoid gcc complain } } else { @@ -949,12 +994,34 @@ bool EditorPanel::handleShortcutKey (GdkEventKey* event) { return true; } } + + if (shift){ + switch (event->keyval) { + case GDK_F3: // open Previous image from Editor's perspective + if (!simpleEditor && fPanel && fname!=""){ + EditorPanel::openPreviousEditorImage(); + return true; + } + break; // to avoid gcc complain + case GDK_F4: // open next image from Editor's perspective + if (!simpleEditor && fPanel && fname!=""){ + EditorPanel::openNextEditorImage(); + return true; + } + break; // to avoid gcc complain + } + } - if(tpc->getToolBar()->handleShortcutKey(event)) + if(tpc->getToolBar() && tpc->getToolBar()->handleShortcutKey(event)) return true; if(tpc->handleShortcutKey(event)) return true; + if (!simpleEditor && fPanel){ + if (fPanel->handleShortcutKey(event)) + return true; + } + return false; } @@ -1163,6 +1230,22 @@ void EditorPanel::sendToGimpPressed () { sendtogimp->set_sensitive(false); } + +void EditorPanel::openPreviousEditorImage() { + if (!simpleEditor && fPanel && fname!="") + fPanel->fileCatalog->openNextPreviousEditorImage(fname, true, NAV_PREVIOUS); +} + +void EditorPanel::openNextEditorImage() { + if (!simpleEditor && fPanel && fname!="") + fPanel->fileCatalog->openNextPreviousEditorImage(fname, true, NAV_NEXT); +} + +void EditorPanel::syncFileBrowser() { // synchronize filebrowser with image in Editor + if (!simpleEditor && fPanel && fname!="") + fPanel->fileCatalog->selectImage(fname, true); +} + bool EditorPanel::idle_sendToGimp( ProgressConnector *pc){ rtengine::IImage16* img = pc->returnValue(); diff --git a/rtgui/editorpanel.h b/rtgui/editorpanel.h index 2a56cbaf1..bd47e87d4 100644 --- a/rtgui/editorpanel.h +++ b/rtgui/editorpanel.h @@ -78,6 +78,9 @@ class EditorPanel : public Gtk::VBox, Gtk::Button* queueimg; Gtk::Button* saveimgas; Gtk::Button* sendtogimp; + Gtk::Button* navSync; + Gtk::Button* navNext; + Gtk::Button* navPrev; ImageAreaPanel* iareapanel; PreviewHandler* previewHandler; @@ -105,7 +108,7 @@ class EditorPanel : public Gtk::VBox, bool firstProcessingDone; Thumbnail* openThm; // may get invalid on external delete event - Glib::ustring fname; // must be safed seperately + Glib::ustring fname; // must be saved separately rtengine::InitialImage* isrc; rtengine::StagedImageProcessor* ipc; @@ -174,6 +177,9 @@ class EditorPanel : public Gtk::VBox, void saveAsPressed (); void queueImgPressed (); void sendToGimpPressed (); + void openNextEditorImage (); + void openPreviousEditorImage (); + void syncFileBrowser (); void tbTopPanel_1_visible (bool visible); bool CheckSidePanelsVisibility(); diff --git a/rtgui/filebrowser.cc b/rtgui/filebrowser.cc index f975c74bd..997c694af 100644 --- a/rtgui/filebrowser.cc +++ b/rtgui/filebrowser.cc @@ -808,38 +808,51 @@ void FileBrowser::openDefaultViewer (int destination) { bool FileBrowser::keyPressed (GdkEventKey* event) { - if ((event->keyval==GDK_C || event->keyval==GDK_c) && event->state & GDK_CONTROL_MASK) { + bool ctrl = event->state & GDK_CONTROL_MASK; + bool shift = event->state & GDK_SHIFT_MASK; + bool alt = event->state & GDK_MOD1_MASK; + + if ((event->keyval==GDK_C || event->keyval==GDK_c) && ctrl) { copyProfile (); return true; } - else if ((event->keyval==GDK_V || event->keyval==GDK_v) && event->state & GDK_CONTROL_MASK && !(event->state & GDK_SHIFT_MASK)) { + else if ((event->keyval==GDK_V || event->keyval==GDK_v) && ctrl && !shift) { pasteProfile (); return true; } - else if ((event->keyval==GDK_V || event->keyval==GDK_v) && event->state & GDK_CONTROL_MASK && event->state & GDK_SHIFT_MASK) { + else if ((event->keyval==GDK_V || event->keyval==GDK_v) && ctrl && shift) { partPasteProfile (); return true; } - else if (event->keyval==GDK_Delete && !(event->state & GDK_SHIFT_MASK)) { + else if (event->keyval==GDK_Delete && !shift) { menuItemActivated (trash); return true; } - else if (event->keyval==GDK_Delete && event->state & GDK_SHIFT_MASK) { + else if (event->keyval==GDK_Delete && shift) { menuItemActivated (untrash); return true; } - else if ((event->keyval==GDK_Q || event->keyval==GDK_q) && event->state & GDK_CONTROL_MASK) { + else if ((event->keyval==GDK_Q || event->keyval==GDK_q) && ctrl) { menuItemActivated (develop); return true; } - else if ((event->keyval==GDK_A || event->keyval==GDK_a) && event->state & GDK_CONTROL_MASK) { + else if ((event->keyval==GDK_A || event->keyval==GDK_a) && ctrl) { menuItemActivated (selall); return true; } - else if (event->keyval==GDK_F2 && !(event->state & GDK_CONTROL_MASK)) { + else if (event->keyval==GDK_F2 && !ctrl) { menuItemActivated (rename); return true; } + else if (event->keyval==GDK_F3 && !(ctrl || shift || alt)) { // open Previous image from FileBrowser perspective + FileBrowser::openPrevImage (); + return true; + } + else if (event->keyval==GDK_F4 && !(ctrl || shift || alt)) { // open Next image from FileBrowser perspective + FileBrowser::openNextImage (); + return true; + } + else if (event->keyval==GDK_F5) { int dest = 1; if (event->state & GDK_SHIFT_MASK) @@ -1103,51 +1116,151 @@ void FileBrowser::buttonPressed (LWButton* button, int actionCode, void* actionD } void FileBrowser::openNextImage () { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::ReaderLock l(entryRW); - #endif + // TODO: Check for Linux + #ifdef WIN32 + Glib::RWLock::ReaderLock l(entryRW); + #endif - if (!fd.empty()) { - for (size_t i=fd.size()-1; i>0; i--) - if (editedFiles.find (fd[i]->filename)!=editedFiles.end()) - if (i entries; - entries.push_back ((static_cast(fd[i+1]))->thumbnail); - tbl->openRequested (entries); - return; + if (!fd.empty() && selected.size()>0 && !options.tabbedUI) { + + for (size_t i=0; ithumbnail->getFileName()==fd[i]->filename) {// located 1-st image in current selection + if (ifiltered/*checkFilter (fd[k])*/){ + // clear current selection + for (size_t j=0; jselected = false; + selected.clear (); + + // set new selection + fd[k]->selected = true; + selected.push_back (fd[k]); + //queue_draw (); + notifySelectionListener (); + + // scroll to the selected position + double h1, v1; + getScrollPosition(h1,v1); + + double h2=selected[0]->getStartX(); + double v2=selected[0]->getStartY(); + + // scroll only when selected[0] is outside of the displayed bounds + if (h2+fd[k]->getMinimalWidth()-h1 > get_width()) + setScrollPosition(h2-(get_width()-fd[k]->getMinimalWidth()),v2); + if (h1>h2) + setScrollPosition(h2,v2); + + // open the selected image + std::vector entries; + entries.push_back ((static_cast(fd[k]))->thumbnail); + tbl->openRequested (entries); + return; + } + } } - if (tbl) { - std::vector entries; - entries.push_back ((static_cast(fd[0]))->thumbnail); - tbl->openRequested (entries); + } } } } void FileBrowser::openPrevImage () { - // TODO: Check for Linux - #ifdef WIN32 - Glib::RWLock::ReaderLock l(entryRW); - #endif + // TODO: Check for Linux + #ifdef WIN32 + Glib::RWLock::ReaderLock l(entryRW); + #endif - if (!fd.empty()) { - for (size_t i=0; ifilename)!=editedFiles.end()) + if (!fd.empty() && selected.size()>0 && !options.tabbedUI) { + + for (size_t i=1; ithumbnail->getFileName()==fd[i]->filename) {// located 1-st image in current selection if (i>0 && tbl) { - std::vector entries; - entries.push_back ((static_cast(fd[i-1]))->thumbnail); - tbl->openRequested (entries); - return; + // find the first not-filtered-out (previous) image + for (size_t k=i-1; k>=0; k--){ + if (!fd[k]->filtered/*checkFilter (fd[k])*/){ + // clear current selection + for (size_t j=0; jselected = false; + selected.clear (); + + // set new selection + fd[k]->selected = true; + selected.push_back (fd[k]); + //queue_draw (); + notifySelectionListener (); + + // scroll to the selected position + double h1, v1; + getScrollPosition(h1,v1); + + double h2=selected[0]->getStartX(); + double v2=selected[0]->getStartY(); + + // scroll only when selected[0] is outside of the displayed bounds + if (h2+fd[k]->getMinimalWidth()-h1 > get_width()) + setScrollPosition(h2-(get_width()-fd[k]->getMinimalWidth()),v2); + if (h1>h2) + setScrollPosition(h2,v2); + + // open the selected image + std::vector entries; + entries.push_back ((static_cast(fd[k]))->thumbnail); + tbl->openRequested (entries); + return; + } + } } - if (tbl) { - std::vector entries; - entries.push_back ((static_cast(fd[fd.size()-1]))->thumbnail); - tbl->openRequested (entries); + } } } } + +void FileBrowser::selectImage (Glib::ustring fname) { + + // need to clear the filter in filecatalog + + if (!fd.empty() && !options.tabbedUI) { + for (size_t i=0; ifilename && !fd[i]->filtered) { + // matching file found for sync + + // clear current selection + for (size_t j=0; jselected = false; + selected.clear (); + + // set new selection + fd[i]->selected = true; + selected.push_back (fd[i]); + queue_draw (); + notifySelectionListener (); + + // scroll to the selected position + double h=selected[0]->getStartX(); + double v=selected[0]->getStartY(); + setScrollPosition(h,v); + + return; + } + } + } +} + +void FileBrowser::openNextPreviousEditorImage (Glib::ustring fname, eRTNav nextPrevious) { + + // let FileBrowser acquire Editor's perspective + selectImage (fname); + + // now switch to the requested image + if (nextPrevious==NAV_NEXT) + openNextImage(); + else if (nextPrevious==NAV_PREVIOUS) + openPrevImage(); +} + int refreshThumbImagesUI (void* data) { (static_cast(data))->_thumbRearrangementNeeded (); return 0; diff --git a/rtgui/filebrowser.h b/rtgui/filebrowser.h index 529b1f4a6..5188f19f9 100644 --- a/rtgui/filebrowser.h +++ b/rtgui/filebrowser.h @@ -154,6 +154,8 @@ class FileBrowser : public ThumbBrowserBase, void copyProfile (); void pasteProfile (); void partPasteProfile (); + void selectImage (Glib::ustring fname); + void openNextPreviousEditorImage (Glib::ustring fname, eRTNav eNextPrevious); void openDefaultViewer (int destination); diff --git a/rtgui/filecatalog.cc b/rtgui/filecatalog.cc index 65a2ae45e..3b5734e9f 100644 --- a/rtgui/filecatalog.cc +++ b/rtgui/filecatalog.cc @@ -94,6 +94,7 @@ FileCatalog::FileCatalog (CoarsePanel* cp, ToolBar* tb, FilePanel* filepanel) : hbToolBar1->pack_start (*hbBrowsePath, Gtk::PACK_EXPAND_WIDGET,0); BrowsePath->signal_activate().connect (sigc::mem_fun(*this, &FileCatalog::buttonBrowsePathPressed)); //respond to the Enter key + BrowsePath->signal_key_press_event().connect(sigc::mem_fun(*this, &FileCatalog::BrowsePath_key_pressed)); //setup Query iQueryClear = new RTImage("gtk-close-small.png"); @@ -487,7 +488,7 @@ void FileCatalog::dirSelected (const Glib::ustring& dirname, const Glib::ustring previewsToLoad = 0; previewsLoaded = 0; // if openfile exists, we have to open it first (it is a command line argument) - if (openfile!="") + if (!openfile.empty()) addAndOpenFile (openfile); selectedDirectory = dir->get_parse_name(); @@ -643,6 +644,17 @@ void FileCatalog::previewsFinishedUI () { fileBrowser->applyFilter (getFilter()); // refresh total image count _refreshProgressBar(); filepanel->loadingThumbs(M("PROGRESSBAR_READY"),0); + + if (!imageToSelect_fname.empty()){ + fileBrowser->selectImage(imageToSelect_fname); + imageToSelect_fname = ""; + } + + if (!refImageForOpen_fname.empty() && actionNextPrevious!=NAV_NONE){ + fileBrowser->openNextPreviousEditorImage(refImageForOpen_fname,actionNextPrevious); + refImageForOpen_fname = ""; + actionNextPrevious = NAV_NONE; + } } void FileCatalog::previewsFinished (int dir_id) { @@ -968,7 +980,7 @@ void FileCatalog::renameRequested (std::vector tbe) { continue; // if no extension is given, concatenate the extension of the original file Glib::ustring ext = getExtension (nBaseName); - if (ext=="") + if (ext.empty()) nBaseName += "." + getExtension (baseName); Glib::ustring nfname = Glib::build_filename (dirName, nBaseName); @@ -1061,6 +1073,9 @@ void FileCatalog::categoryButtonToggled (Gtk::ToggleButton* b, bool isMouseClick //was shift key pressed bool shift_down = modifierKey & GDK_SHIFT_MASK; + // The event is process here, we can clear modifierKey now, it'll be set again on the next even + modifierKey = 0; + for (int i=0; i<18; i++) bCateg[i].block (true); @@ -1337,7 +1352,7 @@ void FileCatalog::filterChanged () { void FileCatalog::reparseDirectory () { - if (selectedDirectory=="") + if (selectedDirectory.empty()) return; if (!safe_file_test (selectedDirectory, Glib::FILE_TEST_IS_DIR)) { @@ -1528,14 +1543,19 @@ void FileCatalog::executeQuery(){ } bool FileCatalog::Query_key_pressed (GdkEventKey *event){ - switch (event->keyval) - { - case GDK_Escape: - // Clear Query if the Escape character is pressed within it - FileCatalog::buttonQueryClearPressed (); - return true; - default: return false; - } + + bool shift = event->state & GDK_SHIFT_MASK; + + switch (event->keyval) { + case GDK_Escape: + // Clear Query if the Escape character is pressed within it + if (!shift){ + FileCatalog::buttonQueryClearPressed (); + return true; + } + default: + return false; + } } void FileCatalog::updateFBQueryTB (bool singleRow) { @@ -1573,7 +1593,7 @@ void FileCatalog::buttonBrowsePathPressed () { DecodedPathPrefix = safe_get_user_picture_dir(); } - if (DecodedPathPrefix!=""){ + if (!DecodedPathPrefix.empty()){ BrowsePathValue = Glib::ustring::compose ("%1%2",DecodedPathPrefix,BrowsePathValue.substr (1,BrowsePath->get_text_length()-1)); BrowsePath->set_text(BrowsePathValue); } @@ -1588,6 +1608,24 @@ void FileCatalog::buttonBrowsePathPressed () { buttonBrowsePath->set_image (*iRefreshRed); } +bool FileCatalog::BrowsePath_key_pressed (GdkEventKey *event){ + + bool shift = event->state & GDK_SHIFT_MASK; + + switch (event->keyval) { + case GDK_Escape: + // On Escape character Reset BrowsePath to selectedDirectory + if (!shift){ + BrowsePath->set_text(selectedDirectory); + // place cursor at the end + BrowsePath->select_region(BrowsePath->get_text_length(), BrowsePath->get_text_length()); + return true; + } + default: + return false; + } +} + void FileCatalog::tbLeftPanel_1_visible (bool visible){ if (visible) tbLeftPanel_1->show(); @@ -1638,6 +1676,65 @@ void FileCatalog::toggleSidePanels(){ tbRightPanel_1->set_active (!bAllSidePanelsVisible); } +void FileCatalog::selectImage (Glib::ustring fname, bool clearFilters) { + + Glib::ustring dirname = Glib::path_get_dirname(fname); + if (!dirname.empty()){ + BrowsePath->set_text(dirname); + + + if (clearFilters){ // clear all filters + Query->set_text(""); + categoryButtonToggled(bFilterClear,false); + // disable exif filters + if (filterPanel->isEnabled()) filterPanel->setEnabled (false); + } + + if (BrowsePath->get_text()!=selectedDirectory){ + // reload or refresh thumbs and select image + buttonBrowsePathPressed (); + // the actual selection of image will be handled asynchronously at the end of FileCatalog::previewsFinishedUI + imageToSelect_fname = fname; + } + else{ + // FileCatalog::filterChanged ();//this will be replaced by queue_draw() in fileBrowser->selectImage + fileBrowser->selectImage(fname); + imageToSelect_fname = ""; + } + } +} + + +void FileCatalog::openNextPreviousEditorImage (Glib::ustring fname, bool clearFilters, eRTNav nextPrevious) { + + Glib::ustring dirname = Glib::path_get_dirname(fname); + if (!dirname.empty()){ + BrowsePath->set_text(dirname); + + + if (clearFilters){ // clear all filters + Query->set_text(""); + categoryButtonToggled(bFilterClear,false); + // disable exif filters + if (filterPanel->isEnabled()) filterPanel->setEnabled (false); + } + + if (BrowsePath->get_text()!=selectedDirectory){ + // reload or refresh thumbs and select image + buttonBrowsePathPressed (); + // the actual selection of image will be handled asynchronously at the end of FileCatalog::previewsFinishedUI + refImageForOpen_fname = fname; + actionNextPrevious = nextPrevious; + } + else{ + // FileCatalog::filterChanged ();//this was replace by queue_draw() in fileBrowser->selectImage + fileBrowser->openNextPreviousEditorImage(fname,nextPrevious); + refImageForOpen_fname = ""; + actionNextPrevious = NAV_NONE; + } + } +} + bool FileCatalog::handleShortcutKey (GdkEventKey* event) { bool ctrl = event->state & GDK_CONTROL_MASK; @@ -1661,6 +1758,17 @@ bool FileCatalog::handleShortcutKey (GdkEventKey* event) { return true; } + if (shift){ + switch(event->keyval) { + case GDK_Escape: + BrowsePath->set_text(selectedDirectory); + // set focus on something neutral, this is useful to remove focus from BrowsePath and Query + // when need to execute a shortcut, which otherwise will be typed into those fields + filepanel->grab_focus(); + return true; + } + } + if (!alt) { switch(event->keyval) { case GDK_grave: @@ -1690,8 +1798,10 @@ bool FileCatalog::handleShortcutKey (GdkEventKey* event) { case GDK_Return: case GDK_KP_Enter: - FileCatalog::buttonBrowsePathPressed (); - return true; + if (BrowsePath->is_focus()){ + FileCatalog::buttonBrowsePathPressed (); + return true; + } } } @@ -1737,7 +1847,7 @@ bool FileCatalog::handleShortcutKey (GdkEventKey* event) { } } - if (!ctrl) { + if (!ctrl || (alt && !options.tabbedUI)) { switch(event->keyval) { case GDK_bracketright: @@ -1761,22 +1871,21 @@ bool FileCatalog::handleShortcutKey (GdkEventKey* event) { return true; } } - else { // with Ctrl + if (ctrl && !alt) { switch (event->keyval) { - case GDK_o: - if (!alt){ - BrowsePath->select_region(0, BrowsePath->get_text_length()); - BrowsePath->grab_focus(); - return true; - } - case GDK_f: - if (!alt){ - Query->select_region(0, Query->get_text_length()); - Query->grab_focus(); - return true; - } + case GDK_o: + BrowsePath->select_region(0, BrowsePath->get_text_length()); + BrowsePath->grab_focus(); + return true; + case GDK_f: + Query->select_region(0, Query->get_text_length()); + Query->grab_focus(); + return true; } } + if (fileBrowser->keyPressed(event)) + return true; + return false; } diff --git a/rtgui/filecatalog.h b/rtgui/filecatalog.h index b53fb4324..83a5e2381 100644 --- a/rtgui/filecatalog.h +++ b/rtgui/filecatalog.h @@ -74,6 +74,9 @@ class FileCatalog : public Gtk::VBox, int selectedDirectoryId; bool enabled; bool inTabMode; // Tab mode has e.g. different progress bar handling + Glib::ustring imageToSelect_fname; + Glib::ustring refImageForOpen_fname; // Next/previous for Editor's perspective + eRTNav actionNextPrevious; FileSelectionListener* listener; FileSelectionChangeListener* fslistener; @@ -223,6 +226,7 @@ class FileCatalog : public Gtk::VBox, void zoomOut (); void buttonBrowsePathPressed (); + bool BrowsePath_key_pressed (GdkEventKey *event); void buttonQueryClearPressed (); void executeQuery (); bool Query_key_pressed(GdkEventKey *event); @@ -234,7 +238,9 @@ class FileCatalog : public Gtk::VBox, void tbRightPanel_1_visible (bool visible); void openNextImage () { fileBrowser->openNextImage(); } - void openPrevImage () { fileBrowser->openPrevImage(); } + void openPrevImage () { fileBrowser->openPrevImage(); } + void selectImage (Glib::ustring fname, bool clearFilters); + void openNextPreviousEditorImage (Glib::ustring fname, bool clearFilters, eRTNav nextPrevious); bool handleShortcutKey (GdkEventKey* event); diff --git a/rtgui/filterpanel.h b/rtgui/filterpanel.h index 4f3c86a78..3d2303b0c 100644 --- a/rtgui/filterpanel.h +++ b/rtgui/filterpanel.h @@ -24,8 +24,8 @@ class FilterPanelListener { - public: - virtual void exifFilterChanged () {} + public: + virtual void exifFilterChanged () {} }; class FilterPanel : public Gtk::VBox { @@ -43,33 +43,33 @@ class FilterPanel : public Gtk::VBox { Gtk::Entry* focalTo; Gtk::Entry* isoFrom; Gtk::Entry* isoTo; - Gtk::CheckButton* enabled; - Gtk::CheckButton* enaFNumber; - Gtk::CheckButton* enaShutter; - Gtk::CheckButton* enaFocalLen; - Gtk::CheckButton* enaISO; - Gtk::CheckButton* enaExpComp; - Gtk::CheckButton* enaCamera; - Gtk::CheckButton* enaLens; - Gtk::CheckButton* enaFiletype; + Gtk::CheckButton* enabled; + Gtk::CheckButton* enaFNumber; + Gtk::CheckButton* enaShutter; + Gtk::CheckButton* enaFocalLen; + Gtk::CheckButton* enaISO; + Gtk::CheckButton* enaExpComp; + Gtk::CheckButton* enaCamera; + Gtk::CheckButton* enaLens; + Gtk::CheckButton* enaFiletype; - int conns; - sigc::connection sChange[22]; - - ExifFilterSettings curefs; - FilterPanelListener* listener; - - public: - FilterPanel (); - - void setFilterPanelListener (FilterPanelListener* l) { listener = l; } - - void setFilter (ExifFilterSettings& defefs, bool updateLists); + int conns; + sigc::connection sChange[22]; + + ExifFilterSettings curefs; + FilterPanelListener* listener; + + public: + FilterPanel (); + + void setFilterPanelListener (FilterPanelListener* l) { listener = l; } + + void setFilter (ExifFilterSettings& defefs, bool updateLists); ExifFilterSettings getFilter (); - bool isEnabled (); - - - void valueChanged (); + bool isEnabled (); + + void valueChanged (); + void setEnabled(bool enabledState){enabled->set_active(enabledState);} }; #endif diff --git a/rtgui/guiutils.h b/rtgui/guiutils.h index c2f400419..45eaf99ef 100644 --- a/rtgui/guiutils.h +++ b/rtgui/guiutils.h @@ -187,6 +187,12 @@ enum TOITypes { TOI_ICON }; +typedef enum RTNav { + NAV_NONE, + NAV_NEXT, + NAV_PREVIOUS +} eRTNav; + /** * @brief Handle the switch between text and image to be displayed in the HBox (to be used in a button/toolpanel) */ diff --git a/rtgui/toolbar.cc b/rtgui/toolbar.cc index de852e63d..c90677dcd 100644 --- a/rtgui/toolbar.cc +++ b/rtgui/toolbar.cc @@ -93,8 +93,9 @@ void ToolBar::setTool (ToolMode tool) { handTool->set_active (true); handTool->grab_focus();; // switch focus to the handTool button } - else if (tool==TMSpotWB) + else if (tool==TMSpotWB) { if (wbTool) wbTool->set_active (true); + } else if (tool==TMCropSelect) cropTool->set_active (true); else if (tool==TMStraighten) @@ -199,7 +200,7 @@ void ToolBar::stra_pressed () { bool ToolBar::handleShortcutKey (GdkEventKey* event) { bool ctrl = event->state & GDK_CONTROL_MASK; - bool shift = event->state & GDK_SHIFT_MASK; + //bool shift = event->state & GDK_SHIFT_MASK; bool alt = event->state & GDK_MOD1_MASK; if (!ctrl && !alt) { diff --git a/tools/source_icons/scalable/nav-next.file b/tools/source_icons/scalable/nav-next.file new file mode 100644 index 000000000..cf444112a --- /dev/null +++ b/tools/source_icons/scalable/nav-next.file @@ -0,0 +1 @@ +nav-next.png,w19,actions \ No newline at end of file diff --git a/tools/source_icons/scalable/nav-next.svg b/tools/source_icons/scalable/nav-next.svg new file mode 100644 index 000000000..123f334d9 --- /dev/null +++ b/tools/source_icons/scalable/nav-next.svg @@ -0,0 +1,649 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/tools/source_icons/scalable/nav-prev.file b/tools/source_icons/scalable/nav-prev.file new file mode 100644 index 000000000..0c9254b3c --- /dev/null +++ b/tools/source_icons/scalable/nav-prev.file @@ -0,0 +1 @@ +nav-prev.png,w19,actions \ No newline at end of file diff --git a/tools/source_icons/scalable/nav-prev.svg b/tools/source_icons/scalable/nav-prev.svg new file mode 100644 index 000000000..4112c31fb --- /dev/null +++ b/tools/source_icons/scalable/nav-prev.svg @@ -0,0 +1,649 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/tools/source_icons/scalable/nav-sync.file b/tools/source_icons/scalable/nav-sync.file new file mode 100644 index 000000000..7a0e01b10 --- /dev/null +++ b/tools/source_icons/scalable/nav-sync.file @@ -0,0 +1 @@ +nav-sync.png,w14,actions \ No newline at end of file diff --git a/tools/source_icons/scalable/nav-sync.svg b/tools/source_icons/scalable/nav-sync.svg new file mode 100644 index 000000000..420e2c335 --- /dev/null +++ b/tools/source_icons/scalable/nav-sync.svg @@ -0,0 +1,833 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + +