diff --git a/rtgui/browserfilter.cc b/rtgui/browserfilter.cc index ab4c843a4..cd3af072e 100644 --- a/rtgui/browserfilter.cc +++ b/rtgui/browserfilter.cc @@ -19,11 +19,11 @@ #include "browserfilter.h" BrowserFilter::BrowserFilter () : - showTrash (true), - showNotTrash (true), - showOriginal (false), - multiselect (false), - exifFilterEnabled (false) + showTrash(true), + showNotTrash(true), + showOriginal(false), + exifFilterEnabled(false), + matchEqual(true) { for (int i = 0; i < 6; i++) { showRanked[i] = true; diff --git a/rtgui/browserfilter.h b/rtgui/browserfilter.h index 8880a364d..ac9818ce5 100644 --- a/rtgui/browserfilter.h +++ b/rtgui/browserfilter.h @@ -33,13 +33,11 @@ public: bool showOriginal; bool showEdited[2]; bool showRecentlySaved[2]; - bool multiselect; - - Glib::ustring queryString; - Glib::ustring queryFileName; bool exifFilterEnabled; + bool matchEqual; ExifFilterSettings exifFilter; + std::vector vFilterStrings; BrowserFilter (); }; diff --git a/rtgui/filebrowser.cc b/rtgui/filebrowser.cc index cc215e70a..7a32cd6b7 100644 --- a/rtgui/filebrowser.cc +++ b/rtgui/filebrowser.cc @@ -18,6 +18,7 @@ * You should have received a copy of the GNU General Public License * along with RawTherapee. If not, see . */ +#include #include #include @@ -622,7 +623,7 @@ void FileBrowser::addEntry_ (FileBrowserEntry* entry) initEntry(entry); } - redraw(false); + redraw(entry); } FileBrowserEntry* FileBrowser::delEntry (const Glib::ustring& fname) @@ -1461,12 +1462,12 @@ void FileBrowser::applyFilter (const BrowserFilter& filter) redraw (); } -bool FileBrowser::checkFilter (ThumbBrowserEntryBase* entryb) // true -> entry complies filter +bool FileBrowser::checkFilter (ThumbBrowserEntryBase* entryb) const // true -> entry complies filter { FileBrowserEntry* entry = static_cast(entryb); - if (filter.showOriginal && entry->getOriginal() != nullptr) { + if (filter.showOriginal && entry->getOriginal()) { return false; } @@ -1485,44 +1486,22 @@ bool FileBrowser::checkFilter (ThumbBrowserEntryBase* entryb) // true -> entry return false; } - // return false is query is not satisfied - if (!filter.queryFileName.empty()) { + // return false if query is not satisfied + if (!filter.vFilterStrings.empty()) { // check if image's FileName contains queryFileName (case insensitive) // TODO should we provide case-sensitive search option via preferences? - Glib::ustring FileName; - FileName = Glib::path_get_basename (entry->thumbnail->getFileName()); - FileName = FileName.uppercase(); - //printf("FileBrowser::checkFilter FileName = '%s'; find() result= %i \n",FileName.c_str(), FileName.find(filter.queryFileName.uppercase())); - - Glib::ustring decodedQueryFileName; - bool MatchEqual; - - // Determine the match mode - check if the first 2 characters are equal to "!=" - if (filter.queryFileName.find("!=") == 0) { - decodedQueryFileName = filter.queryFileName.substr (2, filter.queryFileName.length() - 2); - MatchEqual = false; - } else { - decodedQueryFileName = filter.queryFileName; - MatchEqual = true; - } - - // Consider that queryFileName consist of comma separated values (FilterString) - // Evaluate if ANY of these FilterString are contained in the filename - // This will construct OR filter within the filter.queryFileName + std::string FileName = Glib::path_get_basename(entry->thumbnail->getFileName()); + std::transform(FileName.begin(), FileName.end(), FileName.begin(), ::toupper); int iFilenameMatch = 0; - std::vector vFilterStrings = Glib::Regex::split_simple(",", decodedQueryFileName.uppercase()); - for(size_t i = 0; i < vFilterStrings.size(); i++) { - // ignore empty vFilterStrings. Otherwise filter will always return true if - // e.g. filter.queryFileName ends on "," and will stop being a filter - if (!vFilterStrings.at(i).empty()) { - if (FileName.find(vFilterStrings.at(i)) != Glib::ustring::npos) { - iFilenameMatch++; - } + for (const auto& entry : filter.vFilterStrings) { + if (FileName.find(entry) != std::string::npos) { + iFilenameMatch++; + break; } } - if (MatchEqual) { + if (filter.matchEqual) { if (iFilenameMatch == 0) { //none of the vFilterStrings found in FileName return false; } @@ -1531,10 +1510,10 @@ bool FileBrowser::checkFilter (ThumbBrowserEntryBase* entryb) // true -> entry return false; } } + } - /*experimental Regex support, this is unlikely to be useful to photographers*/ - //bool matchfound=Glib::Regex::match_simple(filter.queryFileName.uppercase(),FileName); - //if (!matchfound) return false; + if (!filter.exifFilterEnabled) { + return true; } // check exif filter @@ -1542,17 +1521,12 @@ bool FileBrowser::checkFilter (ThumbBrowserEntryBase* entryb) // true -> entry double tol = 0.01; double tol2 = 1e-8; - if (!filter.exifFilterEnabled) { - return true; - } - - Glib::ustring camera(cfs->getCamera()); - - if (!cfs->exifValid) - return (!filter.exifFilter.filterCamera || filter.exifFilter.cameras.count(camera) > 0) + if (!cfs->exifValid) { + return (!filter.exifFilter.filterCamera || filter.exifFilter.cameras.count(cfs->getCamera()) > 0) && (!filter.exifFilter.filterLens || filter.exifFilter.lenses.count(cfs->lens) > 0) && (!filter.exifFilter.filterFiletype || filter.exifFilter.filetypes.count(cfs->filetype) > 0) && (!filter.exifFilter.filterExpComp || filter.exifFilter.expcomp.count(cfs->expcomp) > 0); + } return (!filter.exifFilter.filterShutter || (rtengine::FramesMetaData::shutterFromString(rtengine::FramesMetaData::shutterToString(cfs->shutter)) >= filter.exifFilter.shutterFrom - tol2 && rtengine::FramesMetaData::shutterFromString(rtengine::FramesMetaData::shutterToString(cfs->shutter)) <= filter.exifFilter.shutterTo + tol2)) @@ -1560,7 +1534,7 @@ bool FileBrowser::checkFilter (ThumbBrowserEntryBase* entryb) // true -> entry && (!filter.exifFilter.filterFocalLen || (cfs->focalLen >= filter.exifFilter.focalFrom - tol && cfs->focalLen <= filter.exifFilter.focalTo + tol)) && (!filter.exifFilter.filterISO || (cfs->iso >= filter.exifFilter.isoFrom && cfs->iso <= filter.exifFilter.isoTo)) && (!filter.exifFilter.filterExpComp || filter.exifFilter.expcomp.count(cfs->expcomp) > 0) - && (!filter.exifFilter.filterCamera || filter.exifFilter.cameras.count(camera) > 0) + && (!filter.exifFilter.filterCamera || filter.exifFilter.cameras.count(cfs->getCamera()) > 0) && (!filter.exifFilter.filterLens || filter.exifFilter.lenses.count(cfs->lens) > 0) && (!filter.exifFilter.filterFiletype || filter.exifFilter.filetypes.count(cfs->filetype) > 0); } diff --git a/rtgui/filebrowser.h b/rtgui/filebrowser.h index b208a854d..cef99e08c 100644 --- a/rtgui/filebrowser.h +++ b/rtgui/filebrowser.h @@ -168,7 +168,7 @@ public: void buttonPressed (LWButton* button, int actionCode, void* actionData) override; void redrawNeeded (LWButton* button) override; - bool checkFilter (ThumbBrowserEntryBase* entry) override; + bool checkFilter (ThumbBrowserEntryBase* entry) const override; void rightClicked (ThumbBrowserEntryBase* entry) override; void doubleClicked (ThumbBrowserEntryBase* entry) override; bool keyPressed (GdkEventKey* event) override; diff --git a/rtgui/filecatalog.cc b/rtgui/filecatalog.cc index dbe540cb4..7c14873a2 100644 --- a/rtgui/filecatalog.cc +++ b/rtgui/filecatalog.cc @@ -1603,7 +1603,6 @@ BrowserFilter FileCatalog::getFilter () anyRankFilterActive || anyCLabelFilterActive || anyEditedFilterActive; } - filter.multiselect = false; /* * Step 2 @@ -1619,7 +1618,6 @@ BrowserFilter FileCatalog::getFilter () (anyEditedFilterActive && anyRecentlySavedFilterActive) || (anySupplementaryActive && (anyRankFilterActive || anyCLabelFilterActive || anyEditedFilterActive || anyRecentlySavedFilterActive))) { - filter.multiselect = true; filter.showRanked[0] = anyRankFilterActive ? bUnRanked->get_active () : true; filter.showCLabeled[0] = anyCLabelFilterActive ? bUnCLabeled->get_active () : true; @@ -1656,14 +1654,28 @@ BrowserFilter FileCatalog::getFilter () //TODO could use date:;iso: etc // default will be filename - /* // this is for safe execution if getFilter is called before Query object is instantiated - Glib::ustring tempQuery; - tempQuery=""; - if (Query) tempQuery = Query->get_text(); - */ - filter.queryString = Query->get_text(); // full query string from Query Entry - filter.queryFileName = Query->get_text(); // for now Query is only by file name + Glib::ustring decodedQueryFileName = Query->get_text(); // for now Query is only by file name + // Determine the match mode - check if the first 2 characters are equal to "!=" + if (decodedQueryFileName.find("!=") == 0) { + decodedQueryFileName = decodedQueryFileName.substr(2); + filter.matchEqual = false; + } else { + filter.matchEqual = true; + } + + // Consider that queryFileName consist of comma separated values (FilterString) + // Evaluate if ANY of these FilterString are contained in the filename + // This will construct OR filter within the queryFileName + filter.vFilterStrings.clear(); + const std::vector filterStrings = Glib::Regex::split_simple(",", decodedQueryFileName.uppercase()); + for (const auto& entry : filterStrings) { + // ignore empty filterStrings. Otherwise filter will always return true if + // e.g. queryFileName ends on "," and will stop being a filter + if (!entry.empty()) { + filter.vFilterStrings.push_back(entry); + } + } return filter; } diff --git a/rtgui/thumbbrowserbase.cc b/rtgui/thumbbrowserbase.cc index dba5390c1..a67eb7eec 100644 --- a/rtgui/thumbbrowserbase.cc +++ b/rtgui/thumbbrowserbase.cc @@ -28,7 +28,7 @@ using namespace std; ThumbBrowserBase::ThumbBrowserBase () - : location(THLOC_FILEBROWSER), inspector(nullptr), isInspectorActive(false), eventTime(0), lastClicked(nullptr), previewHeight(options.thumbSize), numOfCols(1), arrangement(TB_Horizontal) + : location(THLOC_FILEBROWSER), inspector(nullptr), isInspectorActive(false), eventTime(0), lastClicked(nullptr), previewHeight(options.thumbSize), numOfCols(1), lastRowHeight(0), arrangement(TB_Horizontal) { inW = -1; inH = -1; @@ -44,6 +44,8 @@ ThumbBrowserBase::ThumbBrowserBase () show_all (); + vscroll.get_adjustment()->set_lower(0); + hscroll.get_adjustment()->set_lower(0); vscroll.signal_value_changed().connect( sigc::mem_fun(*this, &ThumbBrowserBase::scrollChanged) ); hscroll.signal_value_changed().connect( sigc::mem_fun(*this, &ThumbBrowserBase::scrollChanged) ); @@ -543,7 +545,6 @@ void ThumbBrowserBase::configScrollBars () auto ha = hscroll.get_adjustment(); int iw = internal.get_width(); ha->set_upper(inW); - ha->set_lower(0); ha->set_step_increment(!fd.empty() ? fd[0]->getEffectiveWidth() : 0); ha->set_page_increment(iw); ha->set_page_size(iw); @@ -558,7 +559,6 @@ void ThumbBrowserBase::configScrollBars () auto va = vscroll.get_adjustment(); va->set_upper(inH); - va->set_lower(0); const auto height = !fd.empty() ? fd[0]->getEffectiveHeight() : 0; va->set_step_increment(height); va->set_page_increment(height == 0 ? ih : (ih / height) * height); @@ -572,8 +572,22 @@ void ThumbBrowserBase::configScrollBars () } } -void ThumbBrowserBase::arrangeFiles(bool checkfilter) +void ThumbBrowserBase::arrangeFiles(ThumbBrowserEntryBase* entry) { + + if (fd.empty()) { + // nothing to arrange + resizeThumbnailArea(0, 0); + return; + } + if(entry && entry->filtered) { + // a filtered entry was added, nothing to arrange, but has to be marked not drawable + MYREADERLOCK(l, entryRW); + entry->drawable = false; + MYREADERLOCK_RELEASE(l); + return; + } + MYREADERLOCK(l, entryRW); // GUI already locked by ::redraw, the only caller of this method for now. @@ -581,17 +595,22 @@ void ThumbBrowserBase::arrangeFiles(bool checkfilter) //GThreadLock lock; int rowHeight = 0; - for (const auto entry : fd) { - if (checkfilter) { - // apply filter - entry->filtered = !checkFilter(entry); - } + if (entry) { + // we got the reference to the added entry, makes calculation of rowHeight O(1) + lastRowHeight = rowHeight = std::max(lastRowHeight, entry->getMinimalHeight()); + } else { - // compute size of the items - if (!entry->filtered) { - rowHeight = std::max(entry->getMinimalHeight(), rowHeight); + lastRowHeight = 0; + for (const auto thumb : fd) { + // apply filter + thumb->filtered = !checkFilter(thumb); + // compute max rowHeight + if (!thumb->filtered) { + rowHeight = std::max(thumb->getMinimalHeight(), rowHeight); + } } } + if (arrangement == TB_Horizontal) { numOfCols = 1; @@ -622,14 +641,16 @@ void ThumbBrowserBase::arrangeFiles(bool checkfilter) const int availWidth = internal.get_width(); // initial number of columns + int oldNumOfCols = numOfCols; numOfCols = 0; int colsWidth = 0; for (unsigned int i = 0; i < fd.size(); ++i) { if (!fd[i]->filtered && colsWidth + fd[i]->getMinimalWidth() <= availWidth) { - colsWidth += fd[numOfCols]->getMinimalWidth(); + colsWidth += fd[i]->getMinimalWidth(); ++numOfCols; if(colsWidth > availWidth) { + --numOfCols; break; } } @@ -640,6 +661,7 @@ void ThumbBrowserBase::arrangeFiles(bool checkfilter) } std::vector colWidths; + std::vector oldColWidths; for (; numOfCols > 0; --numOfCols) { // compute column widths @@ -663,10 +685,77 @@ void ThumbBrowserBase::arrangeFiles(bool checkfilter) } } + if (oldNumOfCols == numOfCols) { + int oldColsWidth = 0; + for (; oldNumOfCols > 0; --oldNumOfCols) { + // compute old column widths + oldColWidths.assign(oldNumOfCols, 0); + + for (unsigned int i = 0, j = 0; i < fd.size(); ++i) { + if (fd[i] != entry && !fd[i]->filtered && fd[i]->getMinimalWidth() > oldColWidths[j % oldNumOfCols]) { + oldColWidths[j % oldNumOfCols] = fd[i]->getMinimalWidth(); + } + + if (fd[i] != entry && !fd[i]->filtered) { + ++j; + } + } + // if not wider than the space available, arrange it and we are ready + oldColsWidth = std::accumulate(oldColWidths.begin(), oldColWidths.end(), 0); + + if (oldNumOfCols == 1 || oldColsWidth < availWidth) { + break; + } + } + } + + bool arrangeAll = true; + if (entry && oldNumOfCols == numOfCols && oldColWidths.size() > 0) { + arrangeAll = false; + for (int i = 0; i < numOfCols; ++i) { + if(colWidths[i] != oldColWidths[i]) { + arrangeAll = true; + break; + } + } + } + // arrange files int curry = 0; + size_t ct = 0; + if (entry && !arrangeAll) { + int i = 0; + // Find currently added entry + for (; ct < fd.size() && fd[ct] != entry; i += !fd[ct]->filtered, ++ct) { + } + //Calculate the position of currently added entry + const int row = i / numOfCols; + const int col = i % numOfCols; + curry = row * rowHeight; + int currx = 0; + for (int c = 0; c < col; ++c) { + currx += colWidths[c]; + } + // arrange all entries in the row beginning with the currently added one + for (int i = col; ct < fd.size() && i < numOfCols; ++i, ++ct) { + for (; ct < fd.size() && fd[ct]->filtered; ++ct) { + fd[ct]->drawable = false; + } + if (ct < fd.size()) { + fd[ct]->setPosition(currx, curry, colWidths[i], rowHeight); + fd[ct]->drawable = true; + currx += colWidths[i]; + } + } + + if (currx > 0) { // there were thumbnails placed in the row + curry += rowHeight; + } + } + + // arrange remaining entries, if any, that's the most expensive part + for (; ct < fd.size();) { - for (unsigned int ct = 0; ct < fd.size();) { // arrange items in the row int currx = 0; @@ -689,7 +778,7 @@ void ThumbBrowserBase::arrangeFiles(bool checkfilter) MYREADERLOCK_RELEASE(l); // This will require a Writer access - resizeThumbnailArea (colsWidth, curry); + resizeThumbnailArea(colsWidth, curry); } } @@ -979,11 +1068,11 @@ bool ThumbBrowserBase::Internal::on_scroll_event (GdkEventScroll* event) } -void ThumbBrowserBase::redraw (bool checkfilter) +void ThumbBrowserBase::redraw (ThumbBrowserEntryBase* entry) { GThreadLock lock; - arrangeFiles(checkfilter); + arrangeFiles(entry); queue_draw(); } diff --git a/rtgui/thumbbrowserbase.h b/rtgui/thumbbrowserbase.h index ae561db43..e9e69416c 100644 --- a/rtgui/thumbbrowserbase.h +++ b/rtgui/thumbbrowserbase.h @@ -174,12 +174,13 @@ protected: int previewHeight; int numOfCols; + int lastRowHeight; Arrangement arrangement; std::set editedFiles; - void arrangeFiles (bool checkfilter = true); + void arrangeFiles (ThumbBrowserEntryBase* entry = nullptr); void zoomChanged (bool zoomIn); public: @@ -201,7 +202,7 @@ public: return fd; } void on_style_updated () override; - void redraw (bool checkfilter = true); // arrange files and draw area + void redraw (ThumbBrowserEntryBase* entry = nullptr); // arrange files and draw area void refreshThumbImages (); // refresh thumbnail sizes, re-generate thumbnail images, arrange and draw void refreshQuickThumbImages (); // refresh thumbnail sizes, re-generate thumbnail images, arrange and draw void refreshEditedState (const std::set& efiles); @@ -214,7 +215,7 @@ public: void setArrangement (Arrangement a); void enableTabMode(bool enable); // set both thumb sizes and arrangements - virtual bool checkFilter (ThumbBrowserEntryBase* entry) + virtual bool checkFilter (ThumbBrowserEntryBase* entry) const { return true; }