From d2cff759ac70ac820444b251fd827e5d1d40d325 Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Mon, 13 Jan 2014 06:37:08 +0400 Subject: [PATCH] added a filter dialog... Signed-off-by: Alex A. Naanou --- ui/TODO.otl | 34 ++++++++---- ui/bookmarks.js | 5 +- ui/crop.js | 129 ++++++++++++++++++++++++++++++++++++++++++++++ ui/data.js | 48 ++++++++++++++++- ui/keybindings.js | 6 ++- ui/marks.js | 74 ++++++++++++++++++-------- ui/sort.js | 6 +-- ui/ui.js | 2 +- 8 files changed, 265 insertions(+), 39 deletions(-) diff --git a/ui/TODO.otl b/ui/TODO.otl index c4b95e4c..d3fd6aa8 100755 --- a/ui/TODO.otl +++ b/ui/TODO.otl @@ -112,6 +112,8 @@ Roadmap [_] 62% High priority [_] BUG: sorting breaks when at or near the end of a ribbon... | + | Race condition... + | | Procedure: | - go to end of a ribbon | - shift-s @@ -125,7 +127,28 @@ Roadmap | - check "Descending" in the sort dialog | NOTE: this breaks because current the current image is not | yet loaded/created when reloadViewer(..) tries to focus it... + | + | Temporary workaround: + | because there is nothing wrong with sorting itself, just the + | UI, the resulting state can be fixed by simply reloading the + | viewer (reloadViewer(true) or ctrl-alt-r) + [_] BUG: sorting mis-aligns ribbons in some cases... + | Example: + | oooo... --[reverse]-> ...oooo + | ...oooo[o]oooo... ...oooo[o]oooo... + | + | Should be: + | oooo... --[reverse]-> ...oooo + | ...oooo[o]oooo... ...oooo[o]oooo... + | + | The above can happen when, for example, sorting the images via data + | and then sorting them in the same way with reverse checked... + | + | XXX is this related to? + | BUG: sorting breaks when at or near the end of a ribbon... [_] BUG: panels: open/close events get triggered on panel drag/sort... + [_] crop/filter/search dialog... + | make a number of fields each accepting a filter -- string/regexp [_] buildcache: add option to control image sort... [_] ASAP: Need visual indicators for long operations... [_] 66% tags @@ -269,17 +292,6 @@ Roadmap [_] 0% metadata [_] comment [_] tags - [_] BUG: sorting mis-aligns ribbons in some cases... - | Example: - | oooo... --[reverse]-> ...oooo - | ...oooo[o]oooo... ...oooo[o]oooo... - | - | Should be: - | oooo... --[reverse]-> ...oooo - | ...oooo[o]oooo... ...oooo[o]oooo... - | - | The above can happen when, for example, sorting the images via data - | and then sorting them in the same way with reverse checked... [_] BUG: opening a dir form history sometimes loads wrong size previews | this happens in part of the view and a refresh, reload or image | update (updateImages()) fixes the issue... diff --git a/ui/bookmarks.js b/ui/bookmarks.js index ec3adc3e..83c3a729 100755 --- a/ui/bookmarks.js +++ b/ui/bookmarks.js @@ -7,7 +7,7 @@ // list of bookmarked gids... // // NOTE: this must be sorted in the same order as DATA.order -var BOOKMARKS= [] +var BOOKMARKS = [] // bookmark data // @@ -24,6 +24,9 @@ var BOOKMARKS_FILE_PATTERN = /^[0-9]*-bookmarked.json$/ * Helpers */ +var getBookmarked = makeMarkedLister(function(){ return BOOKMARKS }) +var getUnbookmarked = makeUnmarkedLister(function(){ return BOOKMARKS }) + var getBookmarkedGIDBefore = makeGIDBeforeGetterFromList( function(){ return BOOKMARKS diff --git a/ui/crop.js b/ui/crop.js index fbd0b3aa..771973a2 100755 --- a/ui/crop.js +++ b/ui/crop.js @@ -273,6 +273,135 @@ function cropImagesDialog(){ } +function filterImagesDialog(){ + updateStatus('Filter...').show() + + cfg = {} + cfg['sep0'] = '---' + cfg['Name'] = '' + cfg['Path |' + +'this applies to the non-common\n' + +'part of the relative path.'] = '' + cfg['Comment'] = '' + cfg['Tags |' + +'an image will match if at least\n' + +'one tag matches'] = '' + // XXX date... + cfg['Rotated'] = {select: [ + '', + 'no', + '90° or 270°', + '0° or 180°', + '90° only', + '180° only', + '270° only' + ]} + cfg['Flipped'] = {select: [ + '', + 'no', + 'vertical', + 'horizontal' + ]} + cfg['sep1'] = '---' + cfg['Marked'] = {select: [ + '', + 'yes', + 'no' + ]} + cfg['Bookmarked'] = {select: [ + '', + 'yes', + 'no' + ]} + cfg['sep2'] = '---' + cfg['Keep ribbons'] = false + + formDialog(null, + 'Filter images | NOTE: all filter text fields\n' + +'support regular expressions.', + cfg, + 'OK', + 'filterImagesDialog') + .done(function(res){ + var gids + + showStatusQ('Filtering...') + + // XXX date... + + var filter = {} + // build the filter... + for(var field in res){ + if(/^Name/.test(field) && res[field].trim() != ''){ + filter['name'] = res[field] + + } else if(/^Path/.test(field) && res[field].trim() != ''){ + filter['path'] = res[field] + + } else if(/^Comment/.test(field) && res[field].trim() != ''){ + filter['comment'] = res[field] + + } else if(/^Tags/.test(field) && res[field].trim() != ''){ + filter['tags'] = res[field] + + } else if(/^Rotated/.test(field) && res[field].trim() != ''){ + if(res[field] == 'no'){ + filter['orientation'] = '^0$|undefined|null' + } else if(/or/.test(res[field])){ + filter['orientation'] = res[field] + .split('or') + .map(function(e){ + e = parseInt(e) + if(e == 0){ + return '^0$|undefined|null' + } + return e + }) + .join('|') + } else { + filter['orientation'] = RegExp(parseInt(res[field])) + } + + } else if(/^Flipped/.test(field) && res[field].trim() != ''){ + if(res[field] == 'no'){ + filter['flipped'] = 'undefined|null' + } else { + filter['flipped'] = res[field] + } + + } else if(/^Bookmarked/.test(field) && res[field].trim() != ''){ + if(res[field] == 'yes'){ + gids = getBookmarked(gids) + } else { + gids = getUnbookmarked(gids) + } + + } else if(/^Marked/.test(field) && res[field].trim() != ''){ + if(res[field] == 'yes'){ + gids = getMarked(gids) + } else { + gids = getUnmarked(gids) + } + } + } + + var keep_ribbons = res['Keep ribbons'] + + gids = filterGIDs(filter, gids) + + if(gids.length > 0){ + cropDataTo(gids, keep_ribbons) + } else { + showStatusQ('Filter: nothing matched.') + } + }) + .fail(function(){ + showStatusQ('Filter: canceled.') + }) +} + + + /********************************************************************** * vim:set ts=4 sw=4 : */ diff --git a/ui/data.js b/ui/data.js index dd40c446..da909abd 100755 --- a/ui/data.js +++ b/ui/data.js @@ -748,12 +748,17 @@ function getAllGids(data){ // Get all the currently loaded gids... // // NOTE: this will return an unsorted list of gids... -function getLoadedGIDs(data){ +function getLoadedGIDs(gids, data){ data = data == null ? DATA : data var res = [] data.ribbons.forEach(function(r){ res = res.concat(r) }) + if(gids != null){ + return gids.filter(function(e){ + return res.indexOf(e) >= 0 + }) + } return res } @@ -1405,6 +1410,47 @@ function makePrevFromListAction(get_closest, get_list, restrict_to_ribbon){ } +// XXX also need a date filter -- separate function? +function filterGIDs(filter, gids, data, images){ + images = images == null ? IMAGES : images + gids = gids == null ? getLoadedGIDs(null, data) : gids + + // normalize filter... + for(var k in filter){ + if(typeof(filter[k]) == typeof('str')){ + filter[k] = RegExp(filter[k]) + } + } + + var res = gids.filter(function(gid){ + var img = images[gid] + for(var k in filter){ + // if key does not exist we have no match... + if(!(k in img)){ + return false + } + + var f = filter[k] + var val = img[k] + val = typeof(val) == typeof('str') ? val.trim() : val + + // value is a list, check items, at least one needs to match... + if(val.constructor.name == 'Array' + && val.filter(function(e){ return f.test(e) }).length < 1){ + return false + + // check the whole value... + } else if(!f.test(val)){ + return false + } + } + return true + }) + + return res +} + + /********************************************************************** * Constructors and general data manipulation diff --git a/ui/keybindings.js b/ui/keybindings.js index 48cd2dc0..b043b277 100755 --- a/ui/keybindings.js +++ b/ui/keybindings.js @@ -78,7 +78,10 @@ var KEYBOARD_CONFIG = { // NOTE: this is handled by the wrapper at this point, so we do // not have to do anything here... - F11: doc('Toggle full screen view', function(){ toggleFullscreenMode() }), + F11: doc('Toggle full screen view', function(){ + toggleFullscreenMode() + return false + }), F: { ctrl: 'F11', }, @@ -380,6 +383,7 @@ var KEYBOARD_CONFIG = { // cropping... C: doc('Show ribbon crop dialog', cropImagesDialog), + F: doc('Filter images', filterImagesDialog), // XXX add a non FXX key for macs... F2: { diff --git a/ui/marks.js b/ui/marks.js index f11b75df..885ade49 100755 --- a/ui/marks.js +++ b/ui/marks.js @@ -74,38 +74,70 @@ function invalidateMarksCache(){ } -// Get list of unmarked images... +function makeMarkedLister(get_marked){ + return function(mode){ + var marked = get_marked() + mode = mode == null ? 'all' : mode + var gids = mode == 'all' ? getLoadedGIDs(marked) + : mode.constructor.name == 'Array' ? getLoadedGIDs(mode) + : typeof(mode) == typeof(123) ? getRibbonGIDs(mode) + : getRibbonGIDs() + + if(mode == 'all'){ + return gids + } + return gids.filter(function(e){ + return marked.indexOf(e) >= 0 + }) + } +} + + +// Make lister of unmarked images... // // mode can be: // - 'ribbon' // - 'all' // - number - ribbon index +// - list - list of gids used as source // - null - same as all -function getUnmarked(mode){ - mode = mode == null ? 'all' : mode - var gids = mode == 'all' ? getLoadedGIDs() - : typeof(mode) == typeof(123) ? getRibbonGIDs(mode) - : getRibbonGIDs() - mode = mode == 'ribbon' ? getRibbonIndex() : mode +function makeUnmarkedLister(get_marked, get_cache){ + return function(mode){ + var marked = get_marked() + var cache = get_cache != null ? get_cache() : null - // get the cached set... - if(_UNMARKED_CACHE != null && mode in _UNMARKED_CACHE){ - return _UNMARKED_CACHE[mode] + mode = mode == null ? 'all' : mode + var gids = mode == 'all' ? getLoadedGIDs() + : mode.constructor.name == 'Array' ? getLoadedGIDs(mode) + : typeof(mode) == typeof(123) ? getRibbonGIDs(mode) + : getRibbonGIDs() + mode = mode == 'ribbon' ? getRibbonIndex() : mode + + // get the cached set... + if(cache != null && mode in cache){ + return cache[mode] + } + + // calculate the set... + var res = gids.filter(function(e){ + // keep only unmarked... + return marked.indexOf(e) < 0 + }) + if(cache != null && typeof(mode) == typeof(123)){ + cache[mode] = res + } + + return res } - - // calculate the set... - var res = gids.filter(function(e){ - // keep only unmarked... - return MARKED.indexOf(e) < 0 - }) - if(_UNMARKED_CACHE != null){ - _UNMARKED_CACHE[mode] = res - } - - return res } +var getMarked = makeMarkedLister(function(){ return MARKED }) +var getUnmarked = makeUnmarkedLister( + function(){ return MARKED }, + function(){ return _UNMARKED_CACHE }) + + var getMarkedGIDBefore = makeGIDBeforeGetterFromList( function(){ return MARKED diff --git a/ui/sort.js b/ui/sort.js index e20adeb1..cb3414bc 100755 --- a/ui/sort.js +++ b/ui/sort.js @@ -418,9 +418,9 @@ function sortImagesDialog(){ cfg = {} cfg[alg] = [ - 'Date |'+ - 'fall back to file sequence then\n'+ - 'file name when the earlier is equal.', + 'Date |' + +'fall back to file sequence then\n' + +'file name when the earlier is equal.', 'Sequence number', 'Sequence number with overflow', 'File name' diff --git a/ui/ui.js b/ui/ui.js index 9bf9ae13..bcf04e32 100755 --- a/ui/ui.js +++ b/ui/ui.js @@ -808,7 +808,7 @@ var FIELD_TYPES = { var select = field.find('select') for(var i=0; i < value.select.length; i++){ item - .text(value.select[i]) + .html(value.select[i]) .val(value.select[i]) item.appendTo(select)