ライブラリ更新

git-svn-id: http://www.cozmixng.org/repos/piro/treestyletab/trunk@5830 599a83e7-65a4-db11-8015-0010dcdd6dc2
This commit is contained in:
piro 2010-01-06 11:57:06 +00:00
parent 279bf5a15e
commit 3e9ccf9c00

View File

@ -1,5 +1,5 @@
/* /*
UI Operations Global Undo History Manager UI Operations History Manager
Usage: Usage:
var OH = window['piro.sakura.ne.jp'].operationHistory; var OH = window['piro.sakura.ne.jp'].operationHistory;
@ -74,7 +74,7 @@
http://www.cozmixng.org/repos/piro/fx3-compatibility-lib/trunk/operationHistory.test.js http://www.cozmixng.org/repos/piro/fx3-compatibility-lib/trunk/operationHistory.test.js
*/ */
(function() { (function() {
const currentRevision = 16; const currentRevision = 19;
const DEBUG = false; const DEBUG = false;
if (!('piro.sakura.ne.jp' in window)) window['piro.sakura.ne.jp'] = {}; if (!('piro.sakura.ne.jp' in window)) window['piro.sakura.ne.jp'] = {};
@ -93,8 +93,8 @@
window['piro.sakura.ne.jp'].operationHistory.destroy(); window['piro.sakura.ne.jp'].operationHistory.destroy();
} }
var Cc = Components.classes; const Cc = Components.classes;
var Ci = Components.interfaces; const Ci = Components.interfaces;
function log() { function log() {
if (!DEBUG) return; if (!DEBUG) return;
@ -107,10 +107,8 @@
window['piro.sakura.ne.jp'].operationHistory = { window['piro.sakura.ne.jp'].operationHistory = {
revision : currentRevision, revision : currentRevision,
MAX_ENTRIES : 999,
WINDOW_ID : 'ui-operation-global-history-window-id', WINDOW_ID : 'ui-operation-global-history-window-id',
// old name, for backward compatibility
addEntry : function() addEntry : function()
{ {
this.doUndoableTask.apply(this, arguments); this.doUndoableTask.apply(this, arguments);
@ -121,43 +119,20 @@
var options = this._getOptionsFromArguments(arguments); var options = this._getOptionsFromArguments(arguments);
log('doUndoableTask start ('+options.name+' for '+options.windowId+')'); log('doUndoableTask start ('+options.name+' for '+options.windowId+')');
var history = options.history; var history = options.history;
var entries = history.entries;
var metaData = history.metaData;
var error;
var wasInUndoableTask = history._inUndoableTask;
if (!wasInUndoableTask)
history._inUndoableTask = true;
var entry = options.entry; var entry = options.entry;
if (!this._doingUndo && entry) { if (!this._doingUndo && entry) {
log('register new entry to history\n '+entry.label);
let f = this._getAvailableFunction(entry.onRedo, entry.onredo, entry.redo); let f = this._getAvailableFunction(entry.onRedo, entry.onredo, entry.redo);
if (!f && !entry.onRedo && !entry.onredo && !entry.redo && options.task) if (!f && !entry.onRedo && !entry.onredo && !entry.redo && options.task)
entry.onRedo = options.task; entry.onRedo = options.task;
history.addEntry(entry);
if (wasInUndoableTask) {
metaData[metaData.length-1].children.push(entry);
log(' => child level ('+(metaData[metaData.length-1].children.length-1)+')');
}
else {
entries = entries.slice(0, history.index+1);
entries.push(entry);
entries = entries.slice(-this.MAX_ENTRIES);
metaData = metaData.slice(0, history.index+1);
metaData.push(new UIHistoryMetaData());
metaData = metaData.slice(-this.MAX_ENTRIES);
history.entries = entries;
history.metaData = metaData;
history.index = entries.length;
log(' => top level ('+(entries.length-1)+')');
}
} }
var firstContinuation; var currentLevel = history.addingEntryLevel;
var continuationCall = { called : false, allowed : false }; history.addingEntryLevel++;
var continuationInfo = new ContinuationInfo();
var error;
try { try {
if (options.task) if (options.task)
options.task.call( options.task.call(
@ -168,14 +143,11 @@
done : false, done : false,
manager : this, manager : this,
getContinuation : function() { getContinuation : function() {
let continuation = this.manager._getContinuation( return this.manager._createContinuation(
!firstContinuation && wasInUndoableTask ? 'null' : 'undoable', !continuationInfo.created && (currentLevel > 0) ? 'null' : 'undoable',
options, options,
continuationCall continuationInfo
); );
if (!firstContinuation)
firstContinuation = continuation;
return continuation;
} }
} }
); );
@ -185,11 +157,9 @@
error = e; error = e;
} }
continuationCall.allowed = true; continuationInfo.allowed = true;
if (!firstContinuation || continuationCall.called) { if (continuationInfo.done) {
if (!wasInUndoableTask) history.addingEntryLevel--;
delete history._inUndoableTask;
log(' => doUndoableTask finish'); log(' => doUndoableTask finish');
} }
@ -200,12 +170,7 @@
getHistory : function() getHistory : function()
{ {
var options = this._getOptionsFromArguments(arguments); var options = this._getOptionsFromArguments(arguments);
var history = options.history; return new UIHistoryProxy(options.history);
return {
entries : history.entries,
metaData : history.metaData,
index : Math.max(0, Math.min(history.entries.length-1, history.index))
};
}, },
undo : function() undo : function()
@ -213,41 +178,36 @@
var options = this._getOptionsFromArguments(arguments); var options = this._getOptionsFromArguments(arguments);
var history = options.history; var history = options.history;
log('undo start ('+history.index+' / '+history.entries.length+', '+options.name+' for '+options.windowId+', '+this._doingUndo+')'); log('undo start ('+history.index+' / '+history.entries.length+', '+options.name+' for '+options.windowId+', '+this._doingUndo+')');
if (history.index < 0 || this._doingUndo) if (!history.canUndo || this._doingUndo)
return false; return false;
this._doingUndo = true; this._doingUndo = true;
var processed = false; var processed = false;
var error; var error;
var firstContinuation; var continuationInfo = new ContinuationInfo();
var continuationCall = { called : false, allowed : false };
while (processed === false && history.index > -1) do {
{ let entries = history.currentEntries;
let entry = history.entries[history.index];
let metaData = history.metaData[history.index];
--history.index; --history.index;
if (!entry) continue; if (!entries.length) continue;
log(' '+(history.index+1)+' '+entry.label); log(' '+(history.index+1)+' '+entries[0].label);
let done = false; let done = false;
[entry].concat(metaData.children).forEach(function(aEntry, aIndex) { entries.forEach(function(aEntry, aIndex) {
log(' level '+(aIndex)+' '+aEntry.label); log(' level '+(aIndex)+' '+aEntry.label);
let f = this._getAvailableFunction(aEntry.onUndo, aEntry.onundo, aEntry.undo); let f = this._getAvailableFunction(aEntry.onUndo, aEntry.onundo, aEntry.undo);
try { try {
if (f) { if (f) {
let info = { let info = {
level : aIndex, level : aIndex,
parent : (aIndex ? entry.data : null ), parent : (aIndex ? entries[0] : null ),
done : processed && done, done : processed && done,
manager : this, manager : this,
getContinuation : function() { getContinuation : function() {
continuation = this.manager._getContinuation( return this.manager._createContinuation(
firstContinuation ? 'null' : 'undo', continuationInfo.created ? 'null' : 'undo',
options, options,
continuationCall continuationInfo
); );
if (!firstContinuation)
firstContinuation = continuation;
return continuation;
} }
}; };
let oneProcessed = f.call(aEntry, info); let oneProcessed = f.call(aEntry, info);
@ -264,10 +224,12 @@
error = e; error = e;
} }
}, this); }, this);
this._dispatchEvent('UIOperationGlobalHistoryUndo', options, entry, done); this._dispatchEvent('UIOperationGlobalHistoryUndo', options, entries[0], done);
} }
continuationCall.allowed = true; while (processed === false && history.canUndo);
if (!firstContinuation || continuationCall.called) {
continuationInfo.allowed = true;
if (continuationInfo.done) {
this._doingUndo = false; this._doingUndo = false;
log(' => undo finish'); log(' => undo finish');
} }
@ -284,23 +246,22 @@
var history = options.history; var history = options.history;
var max = history.entries.length; var max = history.entries.length;
log('redo start ('+history.index+' / '+max+', '+options.name+' for '+options.windowId+', '+this._doingUndo+')'); log('redo start ('+history.index+' / '+max+', '+options.name+' for '+options.windowId+', '+this._doingUndo+')');
if (history.index >= max || this._doingUndo) if (!history.canRedo || this._doingUndo)
return false; return false;
this._doingUndo = true; this._doingUndo = true;
var processed = false; var processed = false;
var error; var error;
var firstContinuation; var continuationInfo = new ContinuationInfo();
var continuationCall = { called : false, allowed : false };
while (processed === false && history.index < max) while (processed === false && history.canRedo)
{ {
++history.index; ++history.index;
let entry = history.entries[history.index]; let entries = history.currentEntries;
let metaData = history.metaData[history.index]; if (!entries.length) continue;
if (!entry) continue; log(' '+(history.index)+' '+entries[0].label);
log(' '+(history.index)+' '+entry.label);
let done = false; let done = false;
[entry].concat(metaData.children).forEach(function(aEntry, aIndex) { entries.forEach(function(aEntry, aIndex) {
log(' level '+(aIndex)+' '+aEntry.label); log(' level '+(aIndex)+' '+aEntry.label);
let f = this._getAvailableFunction(aEntry.onRedo, aEntry.onredo, aEntry.redo); let f = this._getAvailableFunction(aEntry.onRedo, aEntry.onredo, aEntry.redo);
let done = false; let done = false;
@ -308,18 +269,15 @@
if (f) { if (f) {
let info = { let info = {
level : aIndex, level : aIndex,
parent : (aIndex ? entry.data : null ), parent : (aIndex ? entries[0] : null ),
done : processed && done, done : processed && done,
manager : this, manager : this,
getContinuation : function() { getContinuation : function() {
continuation = this.manager._getContinuation( return this.manager._createContinuation(
firstContinuation ? 'null' : 'redo', continuationInfo.created ? 'null' : 'redo',
options, options,
continuationCall continuationInfo
); );
if (!firstContinuation)
firstContinuation = continuation;
return continuation;
} }
}; };
let oneProcessed = f.call(aEntry, info); let oneProcessed = f.call(aEntry, info);
@ -336,10 +294,11 @@
error = e; error = e;
} }
}, this); }, this);
this._dispatchEvent('UIOperationGlobalHistoryRedo', options, entry.data, done); this._dispatchEvent('UIOperationGlobalHistoryRedo', options, entries[0], done);
} }
continuationCall.allowed = true;
if (!firstContinuation || continuationCall.called) { continuationInfo.allowed = true;
if (continuationInfo.done) {
this._doingUndo = false; this._doingUndo = false;
log(' => redo finish'); log(' => redo finish');
} }
@ -395,7 +354,6 @@
/* PRIVATE METHODS */ /* PRIVATE METHODS */
_doingUndo : false,
_db : db, _db : db,
initialized : false, initialized : false,
@ -459,34 +417,34 @@
name = w ? 'window' : 'global' ; name = w ? 'window' : 'global' ;
var windowId = w ? this.getWindowId(w) : null ; var windowId = w ? this.getWindowId(w) : null ;
var table = this._getTable(name, w); var history = this._getHistoryFor(name, w);
return { return {
name : name, name : name,
window : w, window : w,
windowId : windowId, windowId : windowId,
entry : entry, entry : entry,
history : table, history : history,
task : task task : task
}; };
}, },
_getTable : function(aName, aWindow) _getHistoryFor : function(aName, aWindow)
{ {
aName = encodeURIComponent(aName); var uniqueName = encodeURIComponent(aName);
var windowId = aWindow ? this.getWindowId(aWindow) : null ; var windowId = aWindow ? this.getWindowId(aWindow) : null ;
if (windowId) if (windowId)
aName += '::'+windowId; uniqueName += '::'+windowId;
if (!(aName in this._db)) { if (!(uniqueName in this._db)) {
this._db[aName] = new UIHistory(aWindow, windowId); this._db[uniqueName] = new UIHistory(aName, aWindow, windowId);
} }
return this._db[aName]; return this._db[uniqueName];
}, },
_getContinuation : function(aType, aOptions, aCall) _createContinuation : function(aType, aOptions, aInfo)
{ {
var continuation; var continuation;
var history = aOptions.history; var history = aOptions.history;
@ -495,32 +453,35 @@
{ {
case 'undoable': case 'undoable':
continuation = function() { continuation = function() {
if (aCall.allowed) if (aInfo.allowed)
delete history._inUndoableTask; history.addingEntryLevel--;
aCall.called = true; aInfo.called = true;
log(' => doUndoableTask finish (delayed)'); log(' => doUndoableTask finish (delayed)');
}; };
self = null; self = null;
aInfo.created = true;
break; break;
case 'undo': case 'undo':
continuation = function() { continuation = function() {
if (aCall.allowed) if (aInfo.allowed)
self._doingUndo = false; self._doingUndo = false;
aCall.called = true; aInfo.called = true;
log(' => undo finish (delayed)'); log(' => undo finish (delayed)');
}; };
history = null; history = null;
aInfo.created = true;
break; break;
case 'redo': case 'redo':
continuation = function() { continuation = function() {
if (aCall.allowed) if (aInfo.allowed)
self._doingUndo = false; self._doingUndo = false;
aCall.called = true; aInfo.called = true;
log(' => redo finish (delayed)'); log(' => redo finish (delayed)');
}; };
history = null; history = null;
aInfo.created = true;
break; break;
case 'null': case 'null':
@ -528,7 +489,8 @@
}; };
history = null; history = null;
self = null; self = null;
aCall = null; aInfo.created = true;
aInfo = null;
break; break;
default: default:
@ -634,21 +596,136 @@
}; };
function UIHistory(aWindow, aId) const Prefs = Cc['@mozilla.org/preferences;1']
.getService(Ci.nsIPrefBranch);
const PREF_PREFIX = 'extensions.UIOperationsHistoryManager@piro.sakura.ne.jp.';
function UIHistory(aName, aWindow, aId)
{ {
this.name = aName;
this.window = aWindow; this.window = aWindow;
this.windowId = aId; this.windowId = aId;
try {
var max = Prefs.getIntPref(this.maxPref);
this._max = Math.max(0, Math.min(this.MAX_ENTRIES, max));
}
catch(e) {
this._max = this.MAX_ENTRIES;
}
this.clear(); this.clear();
} }
UIHistory.prototype = { UIHistory.prototype = {
MAX_ENTRIES : 999,
get maxPref()
{
return PREF_PREFIX+escape(this.name)+'.max.'+(this.window ? 'window' : 'global');
},
get max()
{
return this._max;
},
set max(aValue)
{
var max = parseInt(aValue);
if (!isNaN(max)) {
max = Math.max(0, Math.min(this.MAX_ENTRIES, max));
try {
if (max != this._max)
Prefs.setIntPref(this.maxPref, max);
}
catch(e) {
}
this._max = max;
}
return aValue;
},
clear : function() clear : function()
{ {
this.entries = []; this.entries = [];
this.metaData = []; this.metaData = [];
this.index = -1; this.index = -1;
this.addingEntryLevel = 0;
},
addEntry : function(aEntry)
{
log('UIHistory::addEntry / register new entry to history\n '+aEntry.label);
if (this.addingEntryLevel > 0) {
this.lastMetaData.children.push(aEntry);
log(' => child level ('+(this.lastMetaData.children.length-1)+')');
}
else {
this._addNewEntry(aEntry);
log(' => top level ('+(this.entries.length-1)+')');
}
},
_addNewEntry : function(aEntry)
{
this.entries = this.entries.slice(0, this.index+1);
this.entries.push(aEntry);
this.entries = this.entries.slice(-this.max);
this.metaData = this.metaData.slice(0, this.index+1);
this.metaData.push(new UIHistoryMetaData());
this.metaData = this.metaData.slice(-this.max);
this.index = this.entries.length;
},
get canUndo()
{
return this.index >= 0;
},
get canRedo()
{
return this.index < this.entries.length;
},
get currentEntry()
{
return this.entries[this.index] || null ;
},
get lastEntry()
{
return this.entries[this.entries.length-1];
},
get currentMetaData()
{
return this.metaData[this.index] || null ;
},
get lastMetaData()
{
return this.metaData[this.metaData.length-1];
},
_getEntriesAt : function(aIndex)
{
let entry = this.entries[aIndex];
if (!entry) return [];
let metaData = this.metaData[aIndex];
return [entry].concat(metaData.children);
},
get currentEntries()
{
return this._getEntriesAt(this.index);
},
get lastEntries()
{
return this._getEntriesAt(this.entries.length-1);
} }
}; };
function UIHistoryProxy(aHistory)
{
this.__proto__ = aHistory;
this.index = Math.max(0, Math.min(aHistory.entries.length-1, aHistory.index));
}
function UIHistoryMetaData() function UIHistoryMetaData()
{ {
this.clear(); this.clear();
@ -660,5 +737,24 @@
} }
}; };
function ContinuationInfo()
{
this.called = false;
this.allowed = false;
this.created = false;
}
ContinuationInfo.prototype = {
get done()
{
return !this.created || this.called;
}
};
// export
window['piro.sakura.ne.jp'].operationHistory.UIHistory = UIHistory;
window['piro.sakura.ne.jp'].operationHistory.UIHistoryProxy = UIHistoryProxy;
window['piro.sakura.ne.jp'].operationHistory.UIHistoryMetaData = UIHistoryMetaData;
window['piro.sakura.ne.jp'].operationHistory.ContinuationInfo = ContinuationInfo;
window['piro.sakura.ne.jp'].operationHistory.init(); window['piro.sakura.ne.jp'].operationHistory.init();
})(); })();