"Fossies" - the Fresh Open Source Software Archive

Member "rawtherapee-5.7/rtgui/thumbbrowserbase.cc" (10 Sep 2019, 39765 Bytes) of package /linux/misc/rawtherapee-5.7.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "thumbbrowserbase.cc" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 5.6_vs_5.7.

    1 /*
    2  *  This file is part of RawTherapee.
    3  *
    4  *  RawTherapee is free software: you can redistribute it and/or modify
    5  *  it under the terms of the GNU General Public License as published by
    6  *  the Free Software Foundation, either version 3 of the License, or
    7  *  (at your option) any later version.
    8  *
    9  *  RawTherapee is distributed in the hope that it will be useful,
   10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   12  *  GNU General Public License for more details.
   13  *
   14  *  You should have received a copy of the GNU General Public License
   15  *  along with RawTherapee.  If not, see <https://www.gnu.org/licenses/>.
   16  */
   17 #include <numeric>
   18 
   19 #include <glibmm.h>
   20 
   21 #include "multilangmgr.h"
   22 #include "options.h"
   23 #include "thumbbrowserbase.h"
   24 
   25 #include "../rtengine/mytime.h"
   26 #include "../rtengine/rt_math.h"
   27 
   28 using namespace std;
   29 
   30 ThumbBrowserBase::ThumbBrowserBase ()
   31     : location(THLOC_FILEBROWSER), inspector(nullptr), isInspectorActive(false), eventTime(0), lastClicked(nullptr), anchor(nullptr), previewHeight(options.thumbSize), numOfCols(1), lastRowHeight(0), arrangement(TB_Horizontal)
   32 {
   33     inW = -1;
   34     inH = -1;
   35 
   36     setExpandAlignProperties(&internal, true, true, Gtk::ALIGN_FILL, Gtk::ALIGN_FILL);
   37     setExpandAlignProperties(&hscroll, true, false, Gtk::ALIGN_FILL, Gtk::ALIGN_CENTER);
   38     setExpandAlignProperties(&vscroll, false, true, Gtk::ALIGN_CENTER, Gtk::ALIGN_FILL);
   39     attach (internal, 0, 0, 1, 1);
   40     attach (vscroll, 1, 0, 1, 1);
   41     attach (hscroll, 0, 1, 1, 1);
   42 
   43     internal.setParent (this);
   44 
   45     show_all ();
   46 
   47     vscroll.get_adjustment()->set_lower(0);
   48     hscroll.get_adjustment()->set_lower(0);
   49     vscroll.signal_value_changed().connect( sigc::mem_fun(*this, &ThumbBrowserBase::scrollChanged) );
   50     hscroll.signal_value_changed().connect( sigc::mem_fun(*this, &ThumbBrowserBase::scrollChanged) );
   51 
   52     internal.signal_size_allocate().connect( sigc::mem_fun(*this, &ThumbBrowserBase::internalAreaResized) );
   53 }
   54 
   55 void ThumbBrowserBase::scrollChanged ()
   56 {
   57     {
   58         MYWRITERLOCK(l, entryRW);
   59 
   60         for (size_t i = 0; i < fd.size(); i++) {
   61             fd[i]->setOffset ((int)(hscroll.get_value()), (int)(vscroll.get_value()));
   62         }
   63     }
   64 
   65     internal.setPosition ((int)(hscroll.get_value()), (int)(vscroll.get_value()));
   66 
   67     if (!internal.isDirty()) {
   68         internal.setDirty ();
   69         internal.queue_draw ();
   70     }
   71 }
   72 
   73 void ThumbBrowserBase::scroll (int direction, double deltaX, double deltaY)
   74 {
   75     double delta = 0.0;
   76     if (abs(deltaX) > abs(deltaY)) {
   77         delta = deltaX;
   78     } else {
   79         delta = deltaY;
   80     }
   81     if (direction == GDK_SCROLL_SMOOTH && delta == 0.0) {
   82         // sometimes this case happens. To avoid scrolling the wrong direction in this case, we just do nothing
   83         // This is probably no longer necessary now that coef is no longer quantized to +/-1.0 but why waste CPU cycles?
   84         return;
   85     }
   86     //GDK_SCROLL_SMOOTH can come in as many events with small deltas, don't quantize these to +/-1.0 so trackpads work well
   87     double coef;
   88     if(direction == GDK_SCROLL_SMOOTH) {
   89         coef = delta;
   90     } else if (direction == GDK_SCROLL_DOWN) {
   91         coef = +1.0;
   92     } else {
   93         coef = -1.0;
   94     }
   95 
   96     // GUI already acquired when here
   97     if (direction == GDK_SCROLL_UP || direction == GDK_SCROLL_DOWN || direction == GDK_SCROLL_SMOOTH) {
   98         if (arrangement == TB_Vertical) {
   99             double currValue = vscroll.get_value();
  100             double newValue = rtengine::LIM<double>(currValue + coef * vscroll.get_adjustment()->get_step_increment(),
  101                                                     vscroll.get_adjustment()->get_lower (),
  102                                                     vscroll.get_adjustment()->get_upper());
  103             if (newValue != currValue) {
  104                 vscroll.set_value (newValue);
  105             }
  106         } else {
  107             double currValue = hscroll.get_value();
  108             double newValue = rtengine::LIM<double>(currValue + coef * hscroll.get_adjustment()->get_step_increment(),
  109                                                     hscroll.get_adjustment()->get_lower(),
  110                                                     hscroll.get_adjustment()->get_upper());
  111             if (newValue != currValue) {
  112                 hscroll.set_value (newValue);
  113             }
  114         }
  115     }
  116 }
  117 
  118 void ThumbBrowserBase::scrollPage (int direction)
  119 {
  120     // GUI already acquired when here
  121     // GUI already acquired when here
  122     if (direction == GDK_SCROLL_UP || direction == GDK_SCROLL_DOWN) {
  123         if (arrangement == TB_Vertical) {
  124             double currValue = vscroll.get_value();
  125             double newValue = rtengine::LIM<double>(currValue + (direction == GDK_SCROLL_DOWN ? +1 : -1) * vscroll.get_adjustment()->get_page_increment(),
  126                                                     vscroll.get_adjustment()->get_lower(),
  127                                                     vscroll.get_adjustment()->get_upper());
  128             if (newValue != currValue) {
  129                 vscroll.set_value (newValue);
  130             }
  131         } else {
  132             double currValue = hscroll.get_value();
  133             double newValue = rtengine::LIM<double>(currValue + (direction == GDK_SCROLL_DOWN ? +1 : -1) * hscroll.get_adjustment()->get_page_increment(),
  134                                                     hscroll.get_adjustment()->get_lower(),
  135                                                     hscroll.get_adjustment()->get_upper());
  136             if (newValue != currValue) {
  137                 hscroll.set_value (newValue);
  138             }
  139         }
  140     }
  141 }
  142 
  143 namespace
  144 {
  145 
  146 typedef std::vector<ThumbBrowserEntryBase*> ThumbVector;
  147 typedef ThumbVector::iterator ThumbIterator;
  148 
  149 inline void clearSelection (ThumbVector& selected)
  150 {
  151     for (ThumbIterator thumb = selected.begin (); thumb != selected.end (); ++thumb)
  152         (*thumb)->selected = false;
  153 
  154     selected.clear ();
  155 }
  156 
  157 inline void addToSelection (ThumbBrowserEntryBase* entry, ThumbVector& selected)
  158 {
  159     if (entry->selected || entry->filtered)
  160         return;
  161 
  162     entry->selected = true;
  163     selected.push_back (entry);
  164 }
  165 
  166 inline void removeFromSelection (const ThumbIterator& iterator, ThumbVector& selected)
  167 {
  168     (*iterator)->selected = false;
  169     selected.erase (iterator);
  170 }
  171 
  172 }
  173 
  174 void ThumbBrowserBase::selectSingle (ThumbBrowserEntryBase* clicked)
  175 {
  176     clearSelection(selected);
  177     anchor = clicked;
  178 
  179     if (clicked) {
  180         addToSelection(clicked, selected);
  181     }
  182 }
  183 
  184 void ThumbBrowserBase::selectRange (ThumbBrowserEntryBase* clicked, bool additional)
  185 {
  186     if (!anchor) {
  187         anchor = clicked;
  188         if (selected.empty()) {
  189             addToSelection(clicked, selected);
  190             return;
  191         }
  192     }
  193 
  194     if (!additional || !lastClicked) {
  195         // Extend the current range w.r.t to first selected entry.
  196         ThumbIterator back = std::find(fd.begin(), fd.end(), clicked);
  197         ThumbIterator front = anchor == clicked ? back : std::find(fd.begin(), fd.end(), anchor);
  198 
  199         if (front > back) {
  200             std::swap(front, back);
  201         }
  202 
  203         clearSelection(selected);
  204 
  205         for (; front <= back && front != fd.end(); ++front) {
  206             addToSelection(*front, selected);
  207         }
  208     } else {
  209         // Add an additional range w.r.t. the last clicked entry.
  210         ThumbIterator last = std::find(fd.begin(), fd.end(), lastClicked);
  211         ThumbIterator current = std::find(fd.begin(), fd.end(), clicked);
  212 
  213         if (last > current) {
  214             std::swap(last, current);
  215         }
  216 
  217         for (; last <= current && last != fd.end(); ++last) {
  218             addToSelection(*last, selected);
  219         }
  220     }
  221 }
  222 
  223 void ThumbBrowserBase::selectSet (ThumbBrowserEntryBase* clicked)
  224 {
  225     const ThumbIterator iterator = std::find(selected.begin(), selected.end(), clicked);
  226 
  227     if (iterator != selected.end()) {
  228         removeFromSelection(iterator, selected);
  229     } else {
  230         addToSelection(clicked, selected);
  231     }
  232     anchor = clicked;
  233 }
  234 
  235 static void scrollToEntry (double& h, double& v, int iw, int ih, ThumbBrowserEntryBase* entry)
  236 {
  237     const int hMin = entry->getX();
  238     const int hMax = hMin + entry->getEffectiveWidth() - iw;
  239     const int vMin = entry->getY();
  240     const int vMax = vMin + entry->getEffectiveHeight() - ih;
  241 
  242     if (hMin < 0) {
  243         h += hMin;
  244     } else if (hMax > 0) {
  245         h += hMax;
  246     }
  247 
  248     if (vMin < 0) {
  249         v += vMin;
  250     } else if (vMax > 0) {
  251         v += vMax;
  252     }
  253 }
  254 
  255 void ThumbBrowserBase::selectPrev (int distance, bool enlarge)
  256 {
  257     double h, v;
  258     getScrollPosition (h, v);
  259 
  260     {
  261         MYWRITERLOCK(l, entryRW);
  262 
  263         if (!selected.empty ()) {
  264             std::vector<ThumbBrowserEntryBase*>::iterator front = std::find (fd.begin (), fd.end (), selected.front ());
  265             std::vector<ThumbBrowserEntryBase*>::iterator back = std::find (fd.begin (), fd.end (), selected.back ());
  266             std::vector<ThumbBrowserEntryBase*>::iterator last = std::find (fd.begin (), fd.end (), lastClicked);
  267 
  268             if (front > back) {
  269                 std::swap(front, back);
  270             }
  271 
  272             std::vector<ThumbBrowserEntryBase*>::iterator& curr = last == front ? front : back;
  273 
  274             // find next thumbnail at filtered distance before current
  275             for (; curr >= fd.begin (); --curr) {
  276                 if (!(*curr)->filtered) {
  277                     if (distance-- == 0) {
  278                         // clear current selection
  279                         for (size_t i = 0; i < selected.size (); ++i) {
  280                             selected[i]->selected = false;
  281                             redrawNeeded (selected[i]);
  282                         }
  283 
  284                         selected.clear ();
  285 
  286                         // make sure the newly selected thumbnail is visible and make it current
  287                         scrollToEntry (h, v, internal.get_width (), internal.get_height (), *curr);
  288                         lastClicked = *curr;
  289 
  290                         // either enlarge current selection or set new selection
  291                         if(enlarge) {
  292                             // reverse direction if distance is too large
  293                             if(front > back) {
  294                                 std::swap(front, back);
  295                             }
  296 
  297                             for (; front <= back; ++front) {
  298                                 if (!(*front)->filtered) {
  299                                     (*front)->selected = true;
  300                                     redrawNeeded (*front);
  301                                     selected.push_back (*front);
  302                                 }
  303                             }
  304                         } else {
  305                             (*curr)->selected = true;
  306                             redrawNeeded (*curr);
  307                             selected.push_back (*curr);
  308                         }
  309 
  310                         break;
  311                     }
  312                 }
  313             }
  314         }
  315 
  316         MYWRITERLOCK_RELEASE(l);
  317         selectionChanged ();
  318     }
  319 
  320     setScrollPosition (h, v);
  321 }
  322 
  323 void ThumbBrowserBase::selectNext (int distance, bool enlarge)
  324 {
  325     double h, v;
  326     getScrollPosition (h, v);
  327 
  328     {
  329         MYWRITERLOCK(l, entryRW);
  330 
  331         if (!selected.empty ()) {
  332             std::vector<ThumbBrowserEntryBase*>::iterator front = std::find (fd.begin (), fd.end (), selected.front ());
  333             std::vector<ThumbBrowserEntryBase*>::iterator back = std::find (fd.begin (), fd.end (), selected.back ());
  334             std::vector<ThumbBrowserEntryBase*>::iterator last = std::find (fd.begin (), fd.end (), lastClicked);
  335 
  336             if (front > back) {
  337                 std::swap(front, back);
  338             }
  339 
  340             std::vector<ThumbBrowserEntryBase*>::iterator& curr = last == back ? back : front;
  341 
  342             // find next thumbnail at filtered distance after current
  343             for (; curr < fd.end (); ++curr) {
  344                 if (!(*curr)->filtered) {
  345                     if (distance-- == 0) {
  346                         // clear current selection
  347                         for (size_t i = 0; i < selected.size (); ++i) {
  348                             selected[i]->selected = false;
  349                             redrawNeeded (selected[i]);
  350                         }
  351 
  352                         selected.clear ();
  353 
  354                         // make sure the newly selected thumbnail is visible and make it current
  355                         scrollToEntry (h, v, internal.get_width (), internal.get_height (), *curr);
  356                         lastClicked = *curr;
  357 
  358                         // either enlarge current selection or set new selection
  359                         if(enlarge) {
  360                             // reverse direction if distance is too large
  361                             if(front > back) {
  362                                 std::swap(front, back);
  363                             }
  364 
  365                             for (; front <= back && front != fd.end(); ++front) {
  366                                 if (!(*front)->filtered) {
  367                                     (*front)->selected = true;
  368                                     redrawNeeded (*front);
  369                                     selected.push_back (*front);
  370                                 }
  371                             }
  372                         } else {
  373                             (*curr)->selected = true;
  374                             redrawNeeded (*curr);
  375                             selected.push_back (*curr);
  376                         }
  377 
  378                         break;
  379                     }
  380                 }
  381             }
  382         }
  383 
  384         MYWRITERLOCK_RELEASE(l);
  385         selectionChanged ();
  386     }
  387 
  388     setScrollPosition (h, v);
  389 }
  390 
  391 void ThumbBrowserBase::selectFirst (bool enlarge)
  392 {
  393     double h, v;
  394     getScrollPosition (h, v);
  395 
  396     {
  397         MYWRITERLOCK(l, entryRW);
  398 
  399         if (!fd.empty ()) {
  400             // find first unfiltered entry
  401             std::vector<ThumbBrowserEntryBase*>::iterator first = fd.begin ();
  402 
  403             for (; first < fd.end (); ++first) {
  404                 if (!(*first)->filtered) {
  405                     break;
  406                 }
  407             }
  408 
  409             scrollToEntry (h, v, internal.get_width (), internal.get_height (), *first);
  410 
  411             ThumbBrowserEntryBase* lastEntry = lastClicked;
  412             lastClicked = *first;
  413 
  414             if(selected.empty ()) {
  415                 (*first)->selected = true;
  416                 redrawNeeded (*first);
  417                 selected.push_back (*first);
  418             } else {
  419                 std::vector<ThumbBrowserEntryBase*>::iterator back = std::find (fd.begin (), fd.end (), lastEntry ? lastEntry : selected.back ());
  420 
  421                 if (first > back) {
  422                     std::swap(first, back);
  423                 }
  424 
  425                 // clear current selection
  426                 for (size_t i = 0; i < selected.size (); ++i) {
  427                     selected[i]->selected = false;
  428                     redrawNeeded (selected[i]);
  429                 }
  430 
  431                 selected.clear ();
  432 
  433                 // either enlarge current selection or set new selection
  434                 for (; first <= back; ++first) {
  435                     if (!(*first)->filtered) {
  436                         (*first)->selected = true;
  437                         redrawNeeded (*first);
  438                         selected.push_back (*first);
  439                     }
  440 
  441                     if (!enlarge) {
  442                         break;
  443                     }
  444                 }
  445             }
  446         }
  447 
  448         MYWRITERLOCK_RELEASE(l);
  449         selectionChanged ();
  450     }
  451 
  452     setScrollPosition (h, v);
  453 }
  454 
  455 void ThumbBrowserBase::selectLast (bool enlarge)
  456 {
  457     double h, v;
  458     getScrollPosition (h, v);
  459 
  460     {
  461         MYWRITERLOCK(l, entryRW);
  462 
  463         if (!fd.empty ()) {
  464             // find last unfiltered entry
  465             std::vector<ThumbBrowserEntryBase*>::iterator last = fd.end () - 1;
  466 
  467             for (; last >= fd.begin (); --last) {
  468                 if (!(*last)->filtered) {
  469                     break;
  470                 }
  471             }
  472 
  473             scrollToEntry (h, v, internal.get_width (), internal.get_height (), *last);
  474 
  475             ThumbBrowserEntryBase* lastEntry = lastClicked;
  476             lastClicked = *last;
  477 
  478             if(selected.empty()) {
  479                 (*last)->selected = true;
  480                 redrawNeeded (*last);
  481                 selected.push_back (*last);
  482             } else {
  483                 std::vector<ThumbBrowserEntryBase*>::iterator front = std::find (fd.begin (), fd.end (), lastEntry ? lastEntry : selected.front ());
  484 
  485                 if (last < front) {
  486                     std::swap(last, front);
  487                 }
  488 
  489                 // clear current selection
  490                 for (size_t i = 0; i < selected.size (); ++i) {
  491                     selected[i]->selected = false;
  492                     redrawNeeded (selected[i]);
  493                 }
  494 
  495                 selected.clear ();
  496 
  497                 // either enlarge current selection or set new selection
  498                 for (; front <= last; --last) {
  499                     if (!(*last)->filtered) {
  500                         (*last)->selected = true;
  501                         redrawNeeded (*last);
  502                         selected.push_back (*last);
  503                     }
  504 
  505                     if (!enlarge) {
  506                         break;
  507                     }
  508                 }
  509 
  510                 std::reverse(selected.begin (), selected.end ());
  511             }
  512         }
  513 
  514         MYWRITERLOCK_RELEASE(l);
  515         selectionChanged ();
  516     }
  517 
  518     setScrollPosition (h, v);
  519 }
  520 
  521 void ThumbBrowserBase::resizeThumbnailArea (int w, int h)
  522 {
  523 
  524     inW = w;
  525     inH = h;
  526 
  527     if (hscroll.get_value() + internal.get_width() > inW) {
  528         hscroll.set_value (inW - internal.get_width());
  529     }
  530 
  531     if (vscroll.get_value() + internal.get_height() > inH) {
  532         vscroll.set_value (inH - internal.get_height());
  533     }
  534 
  535     configScrollBars ();
  536 }
  537 
  538 void ThumbBrowserBase::internalAreaResized (Gtk::Allocation& req)
  539 {
  540 
  541     if (inW > 0 && inH > 0) {
  542         configScrollBars ();
  543         redraw ();
  544     }
  545 }
  546 
  547 void ThumbBrowserBase::configScrollBars ()
  548 {
  549 
  550     // HOMBRE:DELETE ME?
  551     GThreadLock tLock; // Acquire the GUI
  552 
  553     if (inW > 0 && inH > 0) {
  554         int ih = internal.get_height();
  555         if (arrangement == TB_Horizontal) {
  556             auto ha = hscroll.get_adjustment();
  557             int iw = internal.get_width();
  558             ha->set_upper(inW);
  559             ha->set_step_increment(!fd.empty() ? fd[0]->getEffectiveWidth() : 0);
  560             ha->set_page_increment(iw);
  561             ha->set_page_size(iw);
  562             if (iw >= inW) {
  563                 hscroll.hide();
  564             } else {
  565                 hscroll.show();
  566             }
  567         } else {
  568             hscroll.hide();
  569         }
  570 
  571         auto va = vscroll.get_adjustment();
  572         va->set_upper(inH);
  573         const auto height = !fd.empty() ? fd[0]->getEffectiveHeight() : 0;
  574         va->set_step_increment(height);
  575         va->set_page_increment(height == 0 ? ih : (ih / height) * height);
  576         va->set_page_size(ih);
  577 
  578         if (ih >= inH) {
  579             vscroll.hide();
  580         } else {
  581             vscroll.show();
  582         }
  583     }
  584 }
  585 
  586 void ThumbBrowserBase::arrangeFiles(ThumbBrowserEntryBase* entry)
  587 {
  588 
  589     if (fd.empty()) {
  590         // nothing to arrange
  591         resizeThumbnailArea(0, 0);
  592         return;
  593     }
  594     if(entry && entry->filtered) {
  595         // a filtered entry was added, nothing to arrange, but has to be marked not drawable
  596         MYREADERLOCK(l, entryRW);
  597         entry->drawable = false;
  598         MYREADERLOCK_RELEASE(l);
  599         return;
  600     }
  601 
  602     MYREADERLOCK(l, entryRW);
  603 
  604     // GUI already locked by ::redraw, the only caller of this method for now.
  605     // We could lock it one more time, there's no harm excepted (negligible) speed penalty
  606     //GThreadLock lock;
  607 
  608     int rowHeight = 0;
  609     if (entry) {
  610         // we got the reference to the added entry, makes calculation of rowHeight O(1)
  611         lastRowHeight = rowHeight = std::max(lastRowHeight, entry->getMinimalHeight());
  612     } else {
  613 
  614         lastRowHeight = 0;
  615         for (const auto thumb : fd) {
  616             // apply filter
  617             thumb->filtered = !checkFilter(thumb);
  618             // compute max rowHeight
  619             if (!thumb->filtered) {
  620                 rowHeight = std::max(thumb->getMinimalHeight(), rowHeight);
  621             }
  622         }
  623     }
  624 
  625     if (arrangement == TB_Horizontal) {
  626         numOfCols = 1;
  627 
  628         int currx = 0;
  629 
  630         for (unsigned int ct = 0; ct < fd.size(); ++ct) {
  631             // arrange items in the column
  632 
  633             for (; ct < fd.size() && fd[ct]->filtered; ++ct) {
  634                 fd[ct]->drawable = false;
  635             }
  636 
  637             if (ct < fd.size()) {
  638                 const int maxw = fd[ct]->getMinimalWidth();
  639 
  640                 fd[ct]->setPosition(currx, 0, maxw, rowHeight);
  641                 fd[ct]->drawable = true;
  642                 currx += maxw;
  643             }
  644         }
  645 
  646         MYREADERLOCK_RELEASE(l);
  647         // This will require a Writer access
  648         resizeThumbnailArea(currx, !fd.empty() ? fd[0]->getEffectiveHeight() : rowHeight);
  649     } else {
  650         const int availWidth = internal.get_width();
  651 
  652         // initial number of columns
  653         int oldNumOfCols = numOfCols;
  654         numOfCols = 0;
  655         int colsWidth = 0;
  656 
  657         for (unsigned int i = 0; i < fd.size(); ++i) {
  658             if (!fd[i]->filtered && colsWidth + fd[i]->getMinimalWidth() <= availWidth) {
  659                 colsWidth += fd[i]->getMinimalWidth();
  660                 ++numOfCols;
  661                 if(colsWidth > availWidth) {
  662                     --numOfCols;
  663                     break;
  664                 }
  665             }
  666         }
  667 
  668         if (numOfCols < 1) {
  669             numOfCols = 1;
  670         }
  671 
  672         std::vector<int> colWidths;
  673 
  674         for (; numOfCols > 0; --numOfCols) {
  675             // compute column widths
  676             colWidths.assign(numOfCols, 0);
  677 
  678             for (unsigned int i = 0, j = 0; i < fd.size(); ++i) {
  679                 if (!fd[i]->filtered && fd[i]->getMinimalWidth() > colWidths[j % numOfCols]) {
  680                     colWidths[j % numOfCols] = fd[i]->getMinimalWidth();
  681                 }
  682 
  683                 if (!fd[i]->filtered) {
  684                     ++j;
  685                 }
  686             }
  687 
  688             // if not wider than the space available, arrange it and we are ready
  689             colsWidth = std::accumulate(colWidths.begin(), colWidths.end(), 0);
  690 
  691             if (numOfCols == 1 || colsWidth < availWidth) {
  692                 break;
  693             }
  694         }
  695 
  696         // arrange files
  697         int curry = 0;
  698         size_t ct = 0;
  699         if (entry) {
  700             std::vector<int> oldColWidths;
  701             if (oldNumOfCols == numOfCols) {
  702                 for (; oldNumOfCols > 0; --oldNumOfCols) {
  703                     // compute old column widths
  704                     oldColWidths.assign(oldNumOfCols, 0);
  705 
  706                     for (unsigned int i = 0, j = 0; i < fd.size(); ++i) {
  707                         if (fd[i] != entry && !fd[i]->filtered && fd[i]->getMinimalWidth() > oldColWidths[j % oldNumOfCols]) {
  708                             oldColWidths[j % oldNumOfCols] = fd[i]->getMinimalWidth();
  709                         }
  710 
  711                         if (fd[i] != entry && !fd[i]->filtered) {
  712                             ++j;
  713                         }
  714                     }
  715                     if (oldNumOfCols == 1 || std::accumulate(oldColWidths.begin(), oldColWidths.end(), 0) < availWidth) {
  716                         break;
  717                     }
  718                 }
  719             }
  720 
  721             bool arrangeAll = true;
  722             if (oldNumOfCols == numOfCols) {
  723                 arrangeAll = false;
  724                 for (int i = 0; i < numOfCols; ++i) {
  725                     if(colWidths[i] != oldColWidths[i]) {
  726                         arrangeAll = true;
  727                         break;
  728                     }
  729                 }
  730             }
  731             if (!arrangeAll) {
  732                 int j = 0;
  733                 // Find currently added entry
  734                 for (; ct < fd.size() && fd[ct] != entry; j += !fd[ct]->filtered, ++ct) {
  735                 }
  736                 //Calculate the position of currently added entry
  737                 const int row = j / numOfCols;
  738                 const int col = j % numOfCols;
  739                 curry = row * rowHeight;
  740                 int currx = 0;
  741                 for (int c = 0; c < col; ++c) {
  742                     currx += colWidths[c];
  743                 }
  744                 // arrange all entries in the row beginning with the currently added one
  745                 for (int i = col; ct < fd.size() && i < numOfCols; ++i, ++ct) {
  746                     for (; ct < fd.size() && fd[ct]->filtered; ++ct) {
  747                         fd[ct]->drawable = false;
  748                     }
  749                     if (ct < fd.size()) {
  750                         fd[ct]->setPosition(currx, curry, colWidths[i], rowHeight);
  751                         fd[ct]->drawable = true;
  752                         currx += colWidths[i];
  753                     }
  754                 }
  755 
  756                 if (currx > 0) { // there were thumbnails placed in the row
  757                     curry += rowHeight;
  758                 }
  759             }
  760         }
  761 
  762         // arrange remaining entries, if any, that's the most expensive part
  763         for (; ct < fd.size();) {
  764 
  765             // arrange items in the row
  766             int currx = 0;
  767 
  768             for (int i = 0; ct < fd.size() && i < numOfCols; ++i, ++ct) {
  769                 for (; ct < fd.size() && fd[ct]->filtered; ++ct) {
  770                     fd[ct]->drawable = false;
  771                 }
  772 
  773                 if (ct < fd.size()) {
  774                     fd[ct]->setPosition(currx, curry, colWidths[i], rowHeight);
  775                     fd[ct]->drawable = true;
  776                     currx += colWidths[i];
  777                 }
  778             }
  779 
  780             if (currx > 0) { // there were thumbnails placed in the row
  781                 curry += rowHeight;
  782             }
  783         }
  784 
  785         MYREADERLOCK_RELEASE(l);
  786         // This will require a Writer access
  787         resizeThumbnailArea(colsWidth, curry);
  788     }
  789 }
  790 
  791 void ThumbBrowserBase::disableInspector()
  792 {
  793     if (inspector) {
  794         inspector->setActive(false);
  795     }
  796 }
  797 
  798 void ThumbBrowserBase::enableInspector()
  799 {
  800     if (inspector) {
  801         inspector->setActive(true);
  802     }
  803 }
  804 
  805 bool ThumbBrowserBase::Internal::on_configure_event(GdkEventConfigure *configure_event)
  806 {
  807     return true;
  808 }
  809 
  810 void ThumbBrowserBase::Internal::on_style_updated()
  811 {
  812     style = get_style_context ();
  813     textn = style->get_color(Gtk::STATE_FLAG_NORMAL);
  814     texts = style->get_color(Gtk::STATE_FLAG_SELECTED);
  815     bgn = style->get_background_color(Gtk::STATE_FLAG_NORMAL);
  816     bgs = style->get_background_color(Gtk::STATE_FLAG_SELECTED);
  817 }
  818 
  819 void ThumbBrowserBase::Internal::on_realize()
  820 {
  821     // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave)
  822     Cairo::FontOptions cfo;
  823     cfo.set_antialias (Cairo::ANTIALIAS_SUBPIXEL);
  824     get_pango_context()->set_cairo_font_options (cfo);
  825 
  826     Gtk::DrawingArea::on_realize();
  827 
  828     style = get_style_context ();
  829     textn = style->get_color(Gtk::STATE_FLAG_NORMAL);
  830     texts = style->get_color(Gtk::STATE_FLAG_SELECTED);
  831     bgn = style->get_background_color(Gtk::STATE_FLAG_NORMAL);
  832     bgs = style->get_background_color(Gtk::STATE_FLAG_SELECTED);
  833 
  834     set_can_focus(true);
  835     add_events(Gdk::EXPOSURE_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK | Gdk::KEY_PRESS_MASK);
  836     set_has_tooltip (true);
  837     signal_query_tooltip().connect( sigc::mem_fun(*this, &ThumbBrowserBase::Internal::on_query_tooltip) );
  838 }
  839 
  840 bool ThumbBrowserBase::Internal::on_query_tooltip (int x, int y, bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip>& tooltip)
  841 {
  842     // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave)
  843     Glib::ustring ttip;
  844     bool useMarkup = false;
  845     {
  846         MYREADERLOCK(l, parent->entryRW);
  847 
  848         for (size_t i = 0; i < parent->fd.size(); i++)
  849             if (parent->fd[i]->drawable && parent->fd[i]->inside (x, y)) {
  850                 std::tie(ttip, useMarkup) = parent->fd[i]->getToolTip (x, y);
  851                 break;
  852             }
  853     }
  854 
  855     if (!ttip.empty()) {
  856         if (useMarkup) {
  857             tooltip->set_markup(ttip);
  858         } else {
  859             tooltip->set_text(ttip);
  860         }
  861         return true;
  862     } else {
  863         return false;
  864     }
  865 }
  866 
  867 void ThumbBrowserBase::on_style_updated ()
  868 {
  869     // GUI will be acquired by refreshThumbImages
  870     refreshThumbImages ();
  871 }
  872 
  873 ThumbBrowserBase::Internal::Internal () : ofsX(0), ofsY(0), parent(nullptr), dirty(true)
  874 {
  875     set_name("FileCatalog");
  876 }
  877 
  878 void ThumbBrowserBase::Internal::setParent (ThumbBrowserBase* p)
  879 {
  880     parent = p;
  881 }
  882 
  883 void ThumbBrowserBase::Internal::setPosition (int x, int y)
  884 {
  885     ofsX = x;
  886     ofsY = y;
  887 }
  888 
  889 bool ThumbBrowserBase::Internal::on_key_press_event (GdkEventKey* event)
  890 {
  891     // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave)
  892     return parent->keyPressed (event);
  893 }
  894 
  895 bool ThumbBrowserBase::Internal::on_button_press_event (GdkEventButton* event)
  896 {
  897     // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave)
  898     grab_focus ();
  899 
  900     parent->eventTime = event->time;
  901 
  902     parent->buttonPressed ((int)event->x, (int)event->y, event->button, event->type, event->state, 0, 0, get_width(), get_height());
  903     Glib::RefPtr<Gdk::Window> window = get_window();
  904 
  905     GdkRectangle rect;
  906     rect.x = 0;
  907     rect.y = 0;
  908     rect.width = window->get_width();
  909     rect.height = window->get_height();
  910 
  911     gdk_window_invalidate_rect (window->gobj(), &rect, true);
  912     gdk_window_process_updates (window->gobj(), true);
  913 
  914     return true;
  915 }
  916 
  917 void ThumbBrowserBase::buttonPressed (int x, int y, int button, GdkEventType type, int state, int clx, int cly, int clw, int clh)
  918 {
  919     // GUI already acquired
  920 
  921     ThumbBrowserEntryBase* fileDescr = nullptr;
  922     bool handled = false;
  923 
  924     {
  925         MYREADERLOCK(l, entryRW);
  926 
  927         for (size_t i = 0; i < fd.size(); i++)
  928             if (fd[i]->drawable) {
  929                 if (fd[i]->inside (x, y) && fd[i]->insideWindow (clx, cly, clw, clh)) {
  930                     fileDescr = fd[i];
  931                 }
  932 
  933                 bool b = fd[i]->pressNotify (button, type, state, x, y);
  934                 handled = handled || b;
  935             }
  936     }
  937 
  938     if (handled || (fileDescr && fileDescr->processing)) {
  939         return;
  940     }
  941 
  942     {
  943         MYWRITERLOCK(l, entryRW);
  944 
  945         if (selected.size() == 1 && type == GDK_2BUTTON_PRESS && button == 1) {
  946             doubleClicked (selected[0]);
  947         } else if (button == 1 && type == GDK_BUTTON_PRESS) {
  948             if (fileDescr && (state & GDK_SHIFT_MASK))
  949                 selectRange (fileDescr, state & GDK_CONTROL_MASK);
  950             else if (fileDescr && (state & GDK_CONTROL_MASK))
  951                 selectSet (fileDescr);
  952             else
  953                 selectSingle (fileDescr);
  954 
  955             lastClicked = fileDescr;
  956             MYWRITERLOCK_RELEASE(l);
  957             selectionChanged ();
  958         } else if (fileDescr && button == 3 && type == GDK_BUTTON_PRESS) {
  959             if (!fileDescr->selected) {
  960                 selectSingle (fileDescr);
  961 
  962                 lastClicked = fileDescr;
  963                 MYWRITERLOCK_RELEASE(l);
  964                 selectionChanged ();
  965             }
  966 
  967             MYWRITERLOCK_RELEASE(l);
  968             rightClicked (fileDescr);
  969         }
  970     } // end of MYWRITERLOCK(l, entryRW);
  971 
  972 }
  973 
  974 bool ThumbBrowserBase::Internal::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr)
  975 {
  976     // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave)
  977 
  978     dirty = false;
  979 
  980     int w = get_width();
  981     int h = get_height();
  982 
  983     // draw thumbnails
  984 
  985     cr->set_antialias(Cairo::ANTIALIAS_NONE);
  986     cr->set_line_join(Cairo::LINE_JOIN_MITER);
  987     style->render_background(cr, 0., 0., w, h);
  988     Glib::RefPtr<Pango::Context> context = get_pango_context ();
  989     context->set_font_description (style->get_font());
  990 
  991     {
  992         MYWRITERLOCK(l, parent->entryRW);
  993 
  994         for (size_t i = 0; i < parent->fd.size() && !dirty; i++) { // if dirty meanwhile, cancel and wait for next redraw
  995             if (!parent->fd[i]->drawable || !parent->fd[i]->insideWindow (0, 0, w, h)) {
  996                 parent->fd[i]->updatepriority = false;
  997             } else {
  998                 parent->fd[i]->updatepriority = true;
  999                 parent->fd[i]->draw (cr);
 1000             }
 1001         }
 1002     }
 1003     style->render_frame(cr, 0., 0., w, h);
 1004 
 1005     return true;
 1006 }
 1007 
 1008 Gtk::SizeRequestMode ThumbBrowserBase::Internal::get_request_mode_vfunc () const
 1009 {
 1010     return Gtk::SIZE_REQUEST_CONSTANT_SIZE;
 1011 }
 1012 
 1013 void ThumbBrowserBase::Internal::get_preferred_height_vfunc (int &minimum_height, int &natural_height) const
 1014 {
 1015     minimum_height = 20 * RTScalable::getScale();
 1016     natural_height = 80 * RTScalable::getScale();
 1017 }
 1018 
 1019 void ThumbBrowserBase::Internal::get_preferred_width_vfunc (int &minimum_width, int &natural_width) const
 1020 {
 1021     minimum_width = 200 * RTScalable::getScale();
 1022     natural_width = 1000 * RTScalable::getScale();
 1023 }
 1024 
 1025 void ThumbBrowserBase::Internal::get_preferred_height_for_width_vfunc (int width, int &minimum_height, int &natural_height) const
 1026 {
 1027     get_preferred_height_vfunc(minimum_height, natural_height);
 1028 }
 1029 
 1030 void ThumbBrowserBase::Internal::get_preferred_width_for_height_vfunc (int height, int &minimum_width, int &natural_width) const
 1031 {
 1032     get_preferred_width_vfunc (minimum_width, natural_width);
 1033 }
 1034 
 1035 
 1036 bool ThumbBrowserBase::Internal::on_button_release_event (GdkEventButton* event)
 1037 {
 1038     // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave)
 1039     int w = get_width();
 1040     int h = get_height();
 1041 
 1042     MYREADERLOCK(l, parent->entryRW);
 1043 
 1044     for (size_t i = 0; i < parent->fd.size(); i++)
 1045         if (parent->fd[i]->drawable && parent->fd[i]->insideWindow (0, 0, w, h)) {
 1046             ThumbBrowserEntryBase* tbe = parent->fd[i];
 1047             MYREADERLOCK_RELEASE(l);
 1048             // This will require a Writer access...
 1049             tbe->releaseNotify (event->button, event->type, event->state, (int)event->x, (int)event->y);
 1050             MYREADERLOCK_ACQUIRE(l);
 1051         }
 1052 
 1053     return true;
 1054 }
 1055 
 1056 bool ThumbBrowserBase::Internal::on_motion_notify_event (GdkEventMotion* event)
 1057 {
 1058     // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave)
 1059     int w = get_width();
 1060     int h = get_height();
 1061 
 1062     MYREADERLOCK(l, parent->entryRW);
 1063 
 1064     for (size_t i = 0; i < parent->fd.size(); i++)
 1065         if (parent->fd[i]->drawable && parent->fd[i]->insideWindow (0, 0, w, h)) {
 1066             parent->fd[i]->motionNotify ((int)event->x, (int)event->y);
 1067         }
 1068 
 1069     return true;
 1070 }
 1071 
 1072 bool ThumbBrowserBase::Internal::on_scroll_event (GdkEventScroll* event)
 1073 {
 1074     // Gtk signals automatically acquire the GUI (i.e. this method is enclosed by gdk_thread_enter and gdk_thread_leave)
 1075     parent->scroll (event->direction, event->delta_x, event->delta_y);
 1076     return true;
 1077 }
 1078 
 1079 
 1080 void ThumbBrowserBase::redraw (ThumbBrowserEntryBase* entry)
 1081 {
 1082 
 1083     GThreadLock lock;
 1084     arrangeFiles(entry);
 1085     queue_draw();
 1086 }
 1087 
 1088 void ThumbBrowserBase::zoomChanged (bool zoomIn)
 1089 {
 1090 
 1091     int newHeight = 0;
 1092     int optThumbSize = getThumbnailHeight();
 1093 
 1094     if (zoomIn)
 1095         for (size_t i = 0; i < options.thumbnailZoomRatios.size(); i++) {
 1096             newHeight = (int)(options.thumbnailZoomRatios[i] * getMaxThumbnailHeight());
 1097 
 1098             if (newHeight > optThumbSize) {
 1099                 break;
 1100             }
 1101         }
 1102     else
 1103         for (size_t i = options.thumbnailZoomRatios.size() - 1; i > 0; i--) {
 1104             newHeight = (int)(options.thumbnailZoomRatios[i] * getMaxThumbnailHeight());
 1105 
 1106             if (newHeight < optThumbSize) {
 1107                 break;
 1108             }
 1109         }
 1110 
 1111     previewHeight = newHeight;
 1112 
 1113     saveThumbnailHeight(newHeight);
 1114 
 1115     {
 1116         MYWRITERLOCK(l, entryRW);
 1117 
 1118         for (size_t i = 0; i < fd.size(); i++) {
 1119             fd[i]->resize (previewHeight);
 1120         }
 1121     }
 1122 
 1123     redraw ();
 1124 }
 1125 
 1126 void ThumbBrowserBase::refreshThumbImages ()
 1127 {
 1128 
 1129     int previewHeight = getThumbnailHeight();
 1130     {
 1131         MYWRITERLOCK(l, entryRW);
 1132 
 1133         for (size_t i = 0; i < fd.size(); i++) {
 1134             fd[i]->resize (previewHeight);
 1135         }
 1136     }
 1137 
 1138     redraw ();
 1139 }
 1140 
 1141 void ThumbBrowserBase::refreshQuickThumbImages ()
 1142 {
 1143     MYWRITERLOCK(l, entryRW);
 1144 
 1145     for (size_t i = 0; i < fd.size(); ++i) {
 1146         fd[i]->refreshQuickThumbnailImage ();
 1147     }
 1148 }
 1149 
 1150 void ThumbBrowserBase::refreshEditedState (const std::set<Glib::ustring>& efiles)
 1151 {
 1152 
 1153     editedFiles = efiles;
 1154     {
 1155         MYREADERLOCK(l, entryRW);
 1156 
 1157         for (size_t i = 0; i < fd.size(); i++) {
 1158             fd[i]->framed = editedFiles.find (fd[i]->filename) != editedFiles.end();
 1159         }
 1160     }
 1161 
 1162     queue_draw ();
 1163 }
 1164 
 1165 void ThumbBrowserBase::setArrangement (Arrangement a)
 1166 {
 1167 
 1168     arrangement = a;
 1169     redraw ();
 1170 }
 1171 
 1172 void ThumbBrowserBase::enableTabMode(bool enable)
 1173 {
 1174     location = enable ? THLOC_EDITOR : THLOC_FILEBROWSER;
 1175     arrangement = enable ? ThumbBrowserBase::TB_Horizontal : ThumbBrowserBase::TB_Vertical;
 1176 
 1177     if ((!options.sameThumbSize && (options.thumbSizeTab != options.thumbSize)) || (options.showFileNames || options.filmStripShowFileNames)) {
 1178 
 1179         MYWRITERLOCK(l, entryRW);
 1180 
 1181         for (size_t i = 0; i < fd.size(); i++) {
 1182             fd[i]->resize (getThumbnailHeight());
 1183         }
 1184     }
 1185 
 1186     redraw ();
 1187 
 1188     // Scroll to selected position if going into ribbon mode or back
 1189     // Tab mode is horizontal, file browser is vertical
 1190     {
 1191         MYREADERLOCK(l, entryRW);
 1192 
 1193         if (!selected.empty()) {
 1194             if (enable) {
 1195                 double h = selected[0]->getStartX();
 1196                 MYREADERLOCK_RELEASE(l);
 1197                 hscroll.set_value (min(h, hscroll.get_adjustment()->get_upper()));
 1198             } else {
 1199                 double v = selected[0]->getStartY();
 1200                 MYREADERLOCK_RELEASE(l);
 1201                 vscroll.set_value (min(v, vscroll.get_adjustment()->get_upper()));
 1202             }
 1203         }
 1204     }
 1205 }
 1206 
 1207 void ThumbBrowserBase::initEntry (ThumbBrowserEntryBase* entry)
 1208 {
 1209     entry->setOffset ((int)(hscroll.get_value()), (int)(vscroll.get_value()));
 1210 }
 1211 
 1212 void ThumbBrowserBase::getScrollPosition (double& h, double& v)
 1213 {
 1214     h = hscroll.get_value ();
 1215     v = vscroll.get_value ();
 1216 }
 1217 
 1218 void ThumbBrowserBase::setScrollPosition (double h, double v)
 1219 {
 1220     hscroll.set_value (h > hscroll.get_adjustment()->get_upper() ? hscroll.get_adjustment()->get_upper() : h);
 1221     vscroll.set_value (v > vscroll.get_adjustment()->get_upper() ? vscroll.get_adjustment()->get_upper() : v);
 1222 }
 1223 
 1224 // needed for auto-height in single tab
 1225 int ThumbBrowserBase::getEffectiveHeight()
 1226 {
 1227     int h = hscroll.get_height() + 2; // have 2 pixels rounding error for scroll bars to appear
 1228 
 1229     MYREADERLOCK(l, entryRW);
 1230 
 1231     // Filtered items do not change in size, so take a non-filtered
 1232     for (size_t i = 0; i < fd.size(); i++)
 1233         if (!fd[i]->filtered) {
 1234             h += fd[i]->getEffectiveHeight();
 1235             break;
 1236         }
 1237 
 1238     return h;
 1239 }
 1240 
 1241 void ThumbBrowserBase::redrawNeeded (ThumbBrowserEntryBase* entry)
 1242 {
 1243 
 1244     // HOMBRE:DELETE ME?
 1245     GThreadLock tLock; // Acquire the GUI
 1246 
 1247     if (entry->insideWindow (0, 0, internal.get_width(), internal.get_height())) {
 1248         if (!internal.isDirty ()) {
 1249             internal.setDirty ();
 1250             internal.queue_draw ();
 1251         }
 1252     }
 1253 }
 1254 
 1255