more refactoring, rearanged sort-related functions and re-written image/gid distance related code...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2013-09-25 17:00:54 +04:00
parent d2a065b09a
commit 1bc2fb64f8
2 changed files with 329 additions and 288 deletions

View File

@ -172,119 +172,6 @@ function concatZip(){
}
function makeDistanceCmp(start, get){
if(get == null){
return function(a, b){
return Math.abs(start - a) - Math.abs(start - b)
}
} else {
start = get(start)
return function(a, b){
return Math.abs(start - get(a)) - Math.abs(start - get(b))
}
}
}
// Make a cmp function to compare two gids by distance from gid.
function makeImageGIDDistanceCmp(gid, get, order){
order = order == null ? DATA.order : order
return makeDistanceCmp(gid, get == null ?
function(a){
return order.indexOf(a)
}
: function(a){
return order.indexOf(get(a))
})
}
// NOTE: essentially this is a 2D distance comparison from gid...
//
// XXX make this faster...
// XXX this is fun, but do we actually need this?
function makeImageRibbonDistanceCmp(gid, get, data, images){
data = data == null ? DATA : data
images = images == null ? IMAGES : images
// make a cmp index...
var ribbons = $.map(DATA.ribbons, function(r, i){
// sort each ribbon by distance from closest gid...
//return [r.slice().sort(makeImageGIDDistanceCmp(getGIDBefore(gid, i)))]
return [r.slice().sort(makeImageGIDDistanceCmp(gid))]
})
var gids = $.map(ribbons, function(e){ return [e[0]] })
var ri = gids.indexOf(gid)
function _getRibbon(gid){
for(var i=0; i < ribbons.length; i++){
if(ribbons[i].indexOf(gid) >= 0){
return ribbons[i]
}
}
}
function _getDistance(a){
var r = _getRibbon(a)
var x = r.indexOf(a)
var y = Math.abs(gids.indexOf(r[0]) - ri)
// NOTE: this is cheating...
//return x + y
// calculate real distance...
return Math.sqrt(x*x + y*y)
}
if(get == null){
return function(a, b){
return _getDistance(a) - _getDistance(b)
}
} else {
return function(a, b){
return _getDistance(get(a)) - _getDistance(get(b))
}
}
}
function cmp(a, b, get){
if(get == null){
return a - b
}
return get(a) - get(b)
}
// NOTE: this expects gids...
function imageDateCmp(a, b, get, data){
data = data == null ? IMAGES : data
if(get != null){
a = get(a)
b = get(b)
}
return data[b].ctime - data[a].ctime
}
// NOTE: this expects gids...
function imageNameCmp(a, b, get, data){
data = data == null ? IMAGES : data
if(get != null){
a = get(a)
b = get(b)
}
a = data[a].path.split('/').pop()
b = data[b].path.split('/').pop()
if(a == b){
return 0
} else if(a < b){
return -1
} else {
return +1
}
}
// Get the first sequence of numbers in the file name...
function getImageNameSeq(gid, data){
data = data == null ? IMAGES : data
@ -293,6 +180,7 @@ function getImageNameSeq(gid, data){
return r == null ? n : parseInt(r[1])
}
// Get the first sequence of numbers in the file name but only if it is
// at the filename start...
function getImageNameLeadingSeq(gid, data){
@ -303,62 +191,69 @@ function getImageNameLeadingSeq(gid, data){
}
// Compare images by sequence number (in filename) or by filename
//
// Examples:
// "1 file name", "012-file", "file 123 name", "DSC_1234"
//
// NOTE: if there are more than one sequence numbers in a filename then
// only the first is considered.
// NOTE: images with sequence number always precede images with plain
// filenames...
function imageSeqOrNameCmp(a, b, get, data, get_seq){
data = data == null ? IMAGES : data
get_seq = get_seq == null ? getImageNameSeq : get_seq
function getGIDDistance(a, b, get, data){
data = data == null ? DATA : data
var order = data.order
if(get != null){
a = get(a)
b = get(b)
}
a = order.indexOf(a)
b = order.indexOf(b)
return Math.abs(a - b)
}
var aa = get_seq(a, data)
var bb = get_seq(b, data)
// special case: seq, name
if(typeof(aa) == typeof(123) && typeof(bb) == typeof('str')){ return -1 }
// special case: name, seq
if(typeof(aa) == typeof('str') && typeof(bb) == typeof(123)){ return +1 }
// NOTE: this is a constructor to cache the generated index as it is
// quite slow to construct, but needs to be current...
function makeGIDRibbonDistanceGetter(gid, data){
data = data == null ? DATA : data
// get the names if there are no sequence numbers...
// NOTE: at this point both a and b are either numbers or NaN's...
a = isNaN(aa) ? data[a].path.split('/').pop() : aa
b = isNaN(bb) ? data[b].path.split('/').pop() : bb
// make a cmp index...
var ribbons = $.map(DATA.ribbons, function(r, i){
// sort each ribbon by distance from closest gid...
//return [r.slice().sort(makeGIDDistanceCmp(getGIDBefore(gid, i)))]
return [r.slice().sort(makeGIDDistanceCmp(gid))]
})
var gids = $.map(ribbons, function(e){ return [e[0]] })
var ri = gids.indexOf(gid)
// do the actual comparison
if(a == b){
return 0
} else if(a < b){
return -1
} else {
return +1
// the basic calculator...
return function(gid){
var r = ribbons[getGIDRibbonIndex(gid, {ribbons: ribbons})]
var x = r.indexOf(gid)
var y = Math.abs(gids.indexOf(r[0]) - ri)
// calculate real distance...
return Math.sqrt(x*x + y*y)
}
}
// Sort images XP-style
// Get distance between two gids taking into account ribbons...
//
// This will consider sequence numbers if they are at the start of the
// filename.
//
// Examples:
// "1 file name", "012-file"
// This is essentially a 2D distance between two gids in data.
//
// NOTE: images with sequence number always precede images with plain
// filenames...
function imageXPStyleFileNameCmp(a, b, get, data){
return imageSeqOrNameCmp(a, b, get, data, getImageNameLeadingSeq)
// NOTE: to get lots of distances from a specific image use
// makeGIDDistanceCmp(...) for faster results...
function getGIDRibbonDistance(a, b, data){
return makeDistanceFromGIDGetter(a, data)(b)
}
function cmp(a, b, get){
if(get == null){
return a - b
}
return get(a) - get(b)
}
// Generic ordering via DATA.order
//
// NOTE: this expects gids...
// NOTE: this is not in sort.js because it is a generic base sort method
function imageOrderCmp(a, b, get, data){
data = data == null ? DATA : data
if(get != null){
@ -482,128 +377,22 @@ Array.prototype.binSearch = function(target, cmp, get){
}
// Orientation translation...
function orientationExif2ImageGrid(orientation){
return {
orientation: {
0: 0,
1: 0,
2: 0,
3: 180,
4: 0,
5: 90,
6: 90,
7: 90,
8: 270,
}[orientation],
flipped: {
0: null,
1: null,
2: ['horizontal'],
3: null,
4: ['vertical'],
5: ['vertical'],
6: null,
7: ['horizontal'],
8: null,
}[orientation]
}
}
// Base URL interface...
//
// NOTE: changing a base URL will trigger a baseURLChanged event...
function setBaseURL(url){
var old_url = BASE_URL
url = url.replace(/\/*$/, '/')
BASE_URL = url
$('.viewer').trigger('baseURLChanged', [old_url, url])
return url
}
function getBaseURL(){
return BASE_URL
}
// Normalize the path...
//
// This will:
// - convert windows absolute paths 'X:\...' -> 'file:///X:/...'
// - if mode is 'absolute':
// - return absolute paths as-is
// - base relative paths on base/BASE_URL, returning an absolute
// path
// - if mode is relative:
// - if absolute path is based on base/BASE_URL make a relative
// to base path out of it buy cutting the base out.
// - return absolute paths as-is
// - return relative paths as-is
//
// NOTE: mode can be either 'absolute' (default) or 'relative'...
function normalizePath(url, base, mode){
base = base == null ? getBaseURL() : base
//mode = /^\./.test(base) && mode == null ? 'relative' : null
mode = mode == null ? 'absolute' : mode
res = ''
// windows path...
// - replace all '\\' with '/'...
url = url.replace(/\\/g, '/')
// - replace 'X:/...' with 'file:///X:/...'
if(/^[A-Z]:\//.test(url)){
url = 'file:///' + url
}
// we got absolute path...
if(/^(file|http|https):\/\/.*$/.test(url)){
// check if we start with base, and remove it if so...
if(mode == 'relative' && url.substring(0, base.length) == base){
url = url.substring(base.length - 1)
res = url[0] == '/' ? url.substring(1) : url
// if it's a different path, return as-is
} else if(mode == 'absolute'){
res = url
}
// make an absolute path...
} else if(mode == 'absolute') {
// if base ends and url starts with '.' avoid making it a '..'
if(base[base.length-1] == '.' && url[0] == '.'){
res = base + url.substring(1)
// avoid creating '//'...
} else if(base[base.length-1] != '/' && url[0] != '/'){
res = base + '/' + url
} else {
res = base + url
}
}
// get the actual path...
res = res.replace('${CACHE_DIR}', CACHE_DIR)
// XXX legacy support...
res = res.replace('.ImageGridCache', CACHE_DIR)
return res
}
// like getRibbonIndex but get the index only via DATA...
function getDataRibbonIndex(gid, data){
function getGIDRibbonIndex(gid, data){
gid = gid == null ? getImageGID() : gid
data = data == null ? DATA : data
for(var i=0; i < data.ribbons.length; i++){
if(data.ribbons[i].indexOf(gid) >= 0){
var ribbons = data.ribbons
for(var i=0; i < ribbons.length; i++){
if(ribbons[i].indexOf(gid) >= 0){
return i
}
}
return -1
}
// Same as getImageBefore(...), but uses gids and searches in DATA...
//
// NOTE: this uses it's own predicate...
@ -612,7 +401,7 @@ function getGIDBefore(gid, ribbon, search, data){
data = data == null ? DATA : data
// XXX get a ribbon without getting into DOM...
// ...dependency leek...
ribbon = ribbon == null ? getDataRibbonIndex(gid, data) : ribbon
ribbon = ribbon == null ? getGIDRibbonIndex(gid, data) : ribbon
search = search == null ? binSearch : search
//search = search == null ? match2(linSearch, binSearch) : search
ribbon = data.ribbons[ribbon]
@ -698,6 +487,86 @@ function getImageGIDs(from, count, ribbon, inclusive){
}
// Base URL interface...
//
// NOTE: changing a base URL will trigger a baseURLChanged event...
function getBaseURL(){
return BASE_URL
}
function setBaseURL(url){
var old_url = BASE_URL
url = url.replace(/\/*$/, '/')
BASE_URL = url
$('.viewer').trigger('baseURLChanged', [old_url, url])
return url
}
// Normalize the path...
//
// This will:
// - convert windows absolute paths 'X:\...' -> 'file:///X:/...'
// - if mode is 'absolute':
// - return absolute paths as-is
// - base relative paths on base/BASE_URL, returning an absolute
// path
// - if mode is relative:
// - if absolute path is based on base/BASE_URL make a relative
// to base path out of it buy cutting the base out.
// - return absolute paths as-is
// - return relative paths as-is
//
// NOTE: mode can be either 'absolute' (default) or 'relative'...
function normalizePath(url, base, mode){
base = base == null ? getBaseURL() : base
//mode = /^\./.test(base) && mode == null ? 'relative' : null
mode = mode == null ? 'absolute' : mode
res = ''
// windows path...
// - replace all '\\' with '/'...
url = url.replace(/\\/g, '/')
// - replace 'X:/...' with 'file:///X:/...'
if(/^[A-Z]:\//.test(url)){
url = 'file:///' + url
}
// we got absolute path...
if(/^(file|http|https):\/\/.*$/.test(url)){
// check if we start with base, and remove it if so...
if(mode == 'relative' && url.substring(0, base.length) == base){
url = url.substring(base.length - 1)
res = url[0] == '/' ? url.substring(1) : url
// if it's a different path, return as-is
} else if(mode == 'absolute'){
res = url
}
// make an absolute path...
} else if(mode == 'absolute') {
// if base ends and url starts with '.' avoid making it a '..'
if(base[base.length-1] == '.' && url[0] == '.'){
res = base + url.substring(1)
// avoid creating '//'...
} else if(base[base.length-1] != '/' && url[0] != '/'){
res = base + '/' + url
} else {
res = base + url
}
}
// get the actual path...
res = res.replace('${CACHE_DIR}', CACHE_DIR)
// XXX legacy support...
res = res.replace('.ImageGridCache', CACHE_DIR)
return res
}
// Select best preview by size...
//
// NOTE: this will use the original if everything else is smaller...
@ -725,27 +594,32 @@ function getBestPreview(gid, size){
}
// Resort the ribbons by DATA.order and re-render...
//
// NOTE: due to how the format is structured, to sort the images one
// only needs to sort DATA.order and call this.
function updateRibbonOrder(no_reload_viewer){
for(var i=0; i < DATA.ribbons.length; i++){
DATA.ribbons[i].sort(imageOrderCmp)
// Orientation translation...
function orientationExif2ImageGrid(orientation){
return {
orientation: {
0: 0,
1: 0,
2: 0,
3: 180,
4: 0,
5: 90,
6: 90,
7: 90,
8: 270,
}[orientation],
flipped: {
0: null,
1: null,
2: ['horizontal'],
3: null,
4: ['vertical'],
5: ['vertical'],
6: null,
7: ['horizontal'],
8: null,
}[orientation]
}
if(!no_reload_viewer){
reloadViewer()
}
}
// get list of gids sorted by proximity to current gid
//
// NOTE: the distance used is the actual 2D distance...
function getClosestGIDs(gid){
gid = gid == null ? getImageGID() : gid
//return DATA.order.slice().sort(makeImageGIDDistanceCmp(gid))
return DATA.order.slice().sort(makeImageRibbonDistanceCmp(gid))
}
@ -1276,9 +1150,9 @@ function updateImages(size, cmp){
// sorted run...
if(UPDATE_SORT_ENABLED && cmp != false){
cmp = cmp == null ?
makeImageGIDDistanceCmp(getImageGID(), getImageGID)
makeGIDDistanceCmp(getImageGID(), getImageGID)
// XXX this is more correct but is slow...
//makeImageRibbonDistanceCmp(getImageGID(), getImageGID)
//makeGIDRibbonDistanceCmp(getImageGID(), getImageGID)
: cmp
deferred.resolve($('.image')
// sort images by distance from current, so as to update what
@ -1588,6 +1462,21 @@ function getPrevLocation(){
* Actions...
*/
// Sort the ribbons by DATA.order and re-render...
//
// NOTE: due to how the format is structured, to sort the images one
// only needs to sort DATA.order and call this.
function updateRibbonOrder(no_reload_viewer){
for(var i=0; i < DATA.ribbons.length; i++){
DATA.ribbons[i].sort(imageOrderCmp)
}
if(!no_reload_viewer){
reloadViewer()
}
}
/******************************************************* Extension ***/
// Open image in an external editor/viewer

View File

@ -12,7 +12,159 @@ var OVERFLOW_GAP = PROXIMITY * 5
/********************************************************* Sorting ***/
/**********************************************************************
* Helpers
*/
// create a generic distance comparison function
//
// NOTE: both distances are measured from a fixed point (start)...
function makeDistanceCmp(start, get){
if(get == null){
return function(a, b){
return Math.abs(start - a) - Math.abs(start - b)
}
} else {
start = get(start)
return function(a, b){
return Math.abs(start - get(a)) - Math.abs(start - get(b))
}
}
}
// Make a cmp function to compare two gids by distance from gid.
//
// NOTE: this calculates the distance in a flat sequence...
function makeGIDDistanceCmp(gid, get, data){
return function(a, b){
return getGIDDistance(gid, a, get, data) - getGIDDistance(gid, b, get, data)
}
}
// 2D distance from a specified gid comparison...
//
// XXX make this faster...
// XXX this is fun, but do we actually need this?
function makeGIDRibbonDistanceCmp(gid, get, data){
data = data == null ? DATA : data
var _getDistance = makeGIDRibbonDistanceGetter(gid)
if(get == null){
return function(a, b){
return _getDistance(a) - _getDistance(b)
}
} else {
return function(a, b){
return _getDistance(get(a)) - _getDistance(get(b))
}
}
}
// NOTE: this expects gids...
function imageDateCmp(a, b, get, data){
data = data == null ? IMAGES : data
if(get != null){
a = get(a)
b = get(b)
}
return data[b].ctime - data[a].ctime
}
// NOTE: this expects gids...
function imageNameCmp(a, b, get, data){
data = data == null ? IMAGES : data
if(get != null){
a = get(a)
b = get(b)
}
a = data[a].path.split('/').pop()
b = data[b].path.split('/').pop()
if(a == b){
return 0
} else if(a < b){
return -1
} else {
return +1
}
}
// Compare images by sequence number (in filename) or by filename
//
// Examples:
// "1 file name", "012-file", "file 123 name", "DSC_1234"
//
// NOTE: if there are more than one sequence numbers in a filename then
// only the first is considered.
// NOTE: images with sequence number always precede images with plain
// filenames...
function imageSeqOrNameCmp(a, b, get, data, get_seq){
data = data == null ? IMAGES : data
get_seq = get_seq == null ? getImageNameSeq : get_seq
if(get != null){
a = get(a)
b = get(b)
}
var aa = get_seq(a, data)
var bb = get_seq(b, data)
// special case: seq, name
if(typeof(aa) == typeof(123) && typeof(bb) == typeof('str')){ return -1 }
// special case: name, seq
if(typeof(aa) == typeof('str') && typeof(bb) == typeof(123)){ return +1 }
// get the names if there are no sequence numbers...
// NOTE: at this point both a and b are either numbers or NaN's...
a = isNaN(aa) ? data[a].path.split('/').pop() : aa
b = isNaN(bb) ? data[b].path.split('/').pop() : bb
// do the actual comparison
if(a == b){
return 0
} else if(a < b){
return -1
} else {
return +1
}
}
// Compate images by name XP-style
//
// This will consider sequence numbers if they are at the start of the
// filename.
//
// Examples:
// "1 file name", "012-file"
//
// NOTE: images with sequence number always precede images with plain
// filenames...
function imageXPStyleFileNameCmp(a, b, get, data){
return imageSeqOrNameCmp(a, b, get, data, getImageNameLeadingSeq)
}
// Get list of gids sorted by proximity to current gid
//
// NOTE: the distance used is the actual 2D distance...
function getClosestGIDs(gid){
gid = gid == null ? getImageGID() : gid
//return DATA.order.slice().sort(makeGIDDistanceCmp(gid))
return DATA.order.slice().sort(makeGIDRibbonDistanceCmp(gid))
}
/**********************************************************************
* Actions
*/
function reverseImageOrder(){
DATA.order.reverse()