diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 7134163067f..cd898b668cd 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1073,6 +1073,11 @@ pref("devtools.ruleview.enabled", true); // Enable the Scratchpad tool. pref("devtools.scratchpad.enabled", true); +// The maximum number of recently-opened files stored. +// Setting this preference to 0 will not clear any recent files, but rather hide +// the 'Open Recent'-menu. +pref("devtools.scratchpad.recentFilesMax", 10); + // Enable the Style Editor. pref("devtools.styleeditor.enabled", true); pref("devtools.styleeditor.transitions", true); diff --git a/browser/devtools/scratchpad/scratchpad-manager.jsm b/browser/devtools/scratchpad/scratchpad-manager.jsm index 2749119c86a..2fdbf962c74 100644 --- a/browser/devtools/scratchpad/scratchpad-manager.jsm +++ b/browser/devtools/scratchpad/scratchpad-manager.jsm @@ -101,7 +101,7 @@ var ScratchpadManager = { } let win = Services.ww.openWindow(null, SCRATCHPAD_WINDOW_URL, "_blank", SCRATCHPAD_WINDOW_FEATURES, params); - // Only add shutdown observer if we've opened a scratchpad window + // Only add the shutdown observer if we've opened a scratchpad window. ShutdownObserver.init(); return win; diff --git a/browser/devtools/scratchpad/scratchpad.js b/browser/devtools/scratchpad/scratchpad.js index 599ee25c855..4c47d2fc972 100644 --- a/browser/devtools/scratchpad/scratchpad.js +++ b/browser/devtools/scratchpad/scratchpad.js @@ -31,6 +31,7 @@ const SCRATCHPAD_CONTEXT_CONTENT = 1; const SCRATCHPAD_CONTEXT_BROWSER = 2; const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties"; const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled"; +const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax"; const BUTTON_POSITION_SAVE = 0; const BUTTON_POSITION_CANCEL = 1; const BUTTON_POSITION_DONT_SAVE = 2; @@ -160,7 +161,7 @@ var Scratchpad = { * @param object aState * An object with filename and executionContext properties. */ - setState: function SP_getState(aState) + setState: function SP_setState(aState) { if (aState.filename) { this.setFilename(aState.filename); @@ -615,19 +616,203 @@ var Scratchpad = { /** * Open a file to edit in the Scratchpad. + * + * @param integer aIndex + * Optional integer: clicked menuitem in the 'Open Recent'-menu. */ - openFile: function SP_openFile() + openFile: function SP_openFile(aIndex) { - let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); - fp.init(window, this.strings.GetStringFromName("openFile.title"), - Ci.nsIFilePicker.modeOpen); - fp.defaultString = ""; - if (fp.show() != Ci.nsIFilePicker.returnCancel) { - this.setFilename(fp.file.path); - this.importFromFile(fp.file, false); + let fp; + if (!aIndex && aIndex !== 0) { + fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); + fp.init(window, this.strings.GetStringFromName("openFile.title"), + Ci.nsIFilePicker.modeOpen); + fp.defaultString = ""; + } + + if (aIndex > -1 || fp.show() != Ci.nsIFilePicker.returnCancel) { + this.promptSave(function(aCloseFile, aSaved, aStatus) { + let shouldOpen = aCloseFile; + if (aSaved && !Components.isSuccessCode(aStatus)) { + shouldOpen = false; + } + + if (shouldOpen) { + this._skipClosePrompt = true; + + let file; + if (fp) { + file = fp.file; + } else { + file = Components.classes["@mozilla.org/file/local;1"]. + createInstance(Components.interfaces.nsILocalFile); + let filePath = this.getRecentFiles()[aIndex]; + file.initWithPath(filePath); + } + + this.setFilename(file.path); + this.importFromFile(file, false); + this.setRecentFile(file); + } + }.bind(this)); } }, + /** + * Get recent files. + * + * @return Array + * File paths. + */ + getRecentFiles: function SP_getRecentFiles() + { + let maxRecent = Services.prefs.getIntPref(PREF_RECENT_FILES_MAX); + let branch = Services.prefs. + getBranch("devtools.scratchpad."); + + let filePaths = []; + if (branch.prefHasUserValue("recentFilePaths")) { + filePaths = JSON.parse(branch.getCharPref("recentFilePaths")); + } + + return filePaths; + }, + + /** + * Save a recent file in a JSON parsable string. + * + * @param nsILocalFile aFile + * The nsILocalFile we want to save as a recent file. + */ + setRecentFile: function SP_setRecentFile(aFile) + { + let maxRecent = Services.prefs.getIntPref(PREF_RECENT_FILES_MAX); + if (maxRecent < 1) { + return; + } + + let filePaths = this.getRecentFiles(); + let filesCount = filePaths.length; + let pathIndex = filePaths.indexOf(aFile.path); + + // We are already storing this file in the list of recent files. + if (pathIndex > -1) { + // If it's already the most recent file, we don't have to do anything. + if (pathIndex === (filesCount - 1)) { + // Updating the menu to clear the disabled state from the wrong menuitem + // in rare cases when two or more Scratchpad windows are open and the + // same file has been opened in two or more windows. + this.populateRecentFilesMenu(); + return; + } + + // It is not the most recent file. Remove it from the list, we add it as + // the most recent farther down. + filePaths.splice(pathIndex, 1); + } + // If we are not storing the file and the 'recent files'-list is full, + // remove the oldest file from the list. + else if (filesCount === maxRecent) { + filePaths.shift(); + } + + filePaths.push(aFile.path); + + let branch = Services.prefs. + getBranch("devtools.scratchpad."); + branch.setCharPref("recentFilePaths", JSON.stringify(filePaths)); + return; + }, + + /** + * Populates the 'Open Recent'-menu. + */ + populateRecentFilesMenu: function SP_populateRecentFilesMenu() + { + let maxRecent = Services.prefs.getIntPref(PREF_RECENT_FILES_MAX); + let recentFilesMenu = document.getElementById("sp-open_recent-menu"); + + if (maxRecent < 1) { + recentFilesMenu.setAttribute("hidden", true); + return; + } + + let recentFilesPopup = recentFilesMenu.firstChild; + let filePaths = this.getRecentFiles(); + let filename = this.getState().filename; + + recentFilesMenu.setAttribute("disabled", true); + while (recentFilesPopup.hasChildNodes()) { + recentFilesPopup.removeChild(recentFilesPopup.firstChild); + } + + if (filePaths.length > 0) { + recentFilesMenu.removeAttribute("disabled"); + + // Print out menuitems with the most recent file first. + for (let i = filePaths.length - 1; i >= 0; --i) { + let menuitem = document.createElement("menuitem"); + menuitem.setAttribute("type", "radio"); + menuitem.setAttribute("label", filePaths[i]); + + if (filePaths[i] === filename) { + menuitem.setAttribute("checked", true); + menuitem.setAttribute("disabled", true); + } + + menuitem.setAttribute("oncommand", "Scratchpad.openFile(" + i + ");"); + recentFilesPopup.appendChild(menuitem); + } + + recentFilesPopup.appendChild(document.createElement("menuseparator")); + let clearItems = document.createElement("menuitem"); + clearItems.setAttribute("id", "sp-menu-clear_recent"); + clearItems.setAttribute("label", + this.strings. + GetStringFromName("clearRecentMenuItems.label")); + clearItems.setAttribute("command", "sp-cmd-clearRecentFiles"); + recentFilesPopup.appendChild(clearItems); + } + }, + + /** + * Clear all recent files. + */ + clearRecentFiles: function SP_clearRecentFiles() + { + Services.prefs.clearUserPref("devtools.scratchpad.recentFilePaths"); + }, + + /** + * Handle changes to the 'PREF_RECENT_FILES_MAX'-preference. + */ + handleRecentFileMaxChange: function SP_handleRecentFileMaxChange() + { + let maxRecent = Services.prefs.getIntPref(PREF_RECENT_FILES_MAX); + let menu = document.getElementById("sp-open_recent-menu"); + + // Hide the menu if the 'PREF_RECENT_FILES_MAX'-pref is set to zero or less. + if (maxRecent < 1) { + menu.setAttribute("hidden", true); + } else { + if (menu.hasAttribute("hidden")) { + if (!menu.firstChild.hasChildNodes()) { + this.populateRecentFilesMenu(); + } + + menu.removeAttribute("hidden"); + } + + let filePaths = this.getRecentFiles(); + if (maxRecent < filePaths.length) { + let branch = Services.prefs. + getBranch("devtools.scratchpad."); + let diff = filePaths.length - maxRecent; + filePaths.splice(0, diff); + branch.setCharPref("recentFilePaths", JSON.stringify(filePaths)); + } + } + }, /** * Save the textbox content to the currently open file. * @@ -646,6 +831,7 @@ var Scratchpad = { this.exportToFile(file, true, false, function(aStatus) { if (Components.isSuccessCode(aStatus)) { this.editor.dirty = false; + this.setRecentFile(file); } if (aCallback) { aCallback(aStatus); @@ -671,6 +857,7 @@ var Scratchpad = { this.exportToFile(fp.file, true, false, function(aStatus) { if (Components.isSuccessCode(aStatus)) { this.editor.dirty = false; + this.setRecentFile(fp.file); } if (aCallback) { aCallback(aStatus); @@ -827,6 +1014,9 @@ var Scratchpad = { this.initialized = true; this._triggerObservers("Ready"); + + this.populateRecentFilesMenu(); + PreferenceObserver.init(); }, /** @@ -887,6 +1077,8 @@ var Scratchpad = { this.resetContext(); this.editor.removeEventListener(SourceEditor.EVENTS.DIRTY_CHANGED, this._onDirtyChanged); + PreferenceObserver.uninit(); + this.editor.destroy(); this.editor = null; this.initialized = false; @@ -1063,6 +1255,48 @@ var Scratchpad = { }, }; +/** + * The PreferenceObserver listens for preference changes while Scratchpad is + * running. + */ +var PreferenceObserver = { + _initialized: false, + + init: function PO_init() + { + if (this._initialized) { + return; + } + + this.branch = Services.prefs.getBranch("devtools.scratchpad."); + this.branch.addObserver("", this, false); + this._initialized = true; + }, + + observe: function PO_observe(aMessage, aTopic, aData) + { + if (aTopic != "nsPref:changed") { + return; + } + + if (aData == "recentFilesMax") { + Scratchpad.handleRecentFileMaxChange(); + } + else if (aData == "recentFilePaths") { + Scratchpad.populateRecentFilesMenu(); + } + }, + + uninit: function PO_uninit () { + if (!this.branch) { + return; + } + + this.branch.removeObserver("", this); + this.branch = null; + } +}; + XPCOMUtils.defineLazyGetter(Scratchpad, "strings", function () { return Services.strings.createBundle(SCRATCHPAD_L10N); }); diff --git a/browser/devtools/scratchpad/scratchpad.xul b/browser/devtools/scratchpad/scratchpad.xul index 02230ce9edc..66b2b447560 100644 --- a/browser/devtools/scratchpad/scratchpad.xul +++ b/browser/devtools/scratchpad/scratchpad.xul @@ -30,6 +30,7 @@ + @@ -117,6 +118,11 @@ command="sp-cmd-openFile" key="sp-key-open" accesskey="&openFileCmd.accesskey;"/> + + +