// copyright 2010 Adobe Systems, Inc. All rights reserved. // Written by Tai Luxon / edited by Barkin Aygun /* @@@BUILDINFO@@@ WorkspaceImportExport.jsx 1.0.5 */ /* A few notes about this script: Exporting: - Allows exporting custom workspaces, but not presets - Can choose to export either the original state that the workspace was created in or the current/last saved state (will save the current workspace before export if you're exporting it) Importing: - Prevents importing workspaces with the same names as preset workspaces (filename or display name) - Allows you to overwrite custom workspaces after asking you - Handled importing over the current workspace - Reloads the workspace list after importing - Does not verify that files are valid or compatible workspace files when importing Localization - Should work in localized builds, but UI of the script is not localized itself - In localized builds, we can't prevent importing a workspace that conflicts with the localized name of a "deleted" preset workspace Version Compatibility - Written for CS5. Should mostly work in CS4 (maybe earlier), but has some shortcomings (e.g. won't reload workspace list after import - tells you to relaunch instead) */ // enable double clicking from the Macintosh Finder or the Windows Explorer var gAppMajorVersion = 0; // Prompts the user for files to import as workspaces, then imports them function ImportWorkspaces(files) { // user may have canceled the open dialog if (files == null) return; var userConflicts = new Array(); var presetConflicts = new Array(); for (var i = files.length - 1; i >= 0 ; --i) { /* var file = files[i]; var msg = "absoluteURI: " + file.absoluteURI + "\ndisplayName: " + file.displayName + "\nfsName: " + file.fsName + "\nfullName: " + file.fullName + "\nlocalizedName: " + file.localizedName + "\nname: " + file.name + "\npath: " + file.path + "\nrelativeURI: " + file.relativeURI; alert (msg);*/ // If the workspace conflicts with a preset or user workspace, push it onto the beginning // of the relevant array, and remove it from 'files'. // Pass non-URI formatted names if (PresetWorkspaceWithNameExists (decodeURI(files[i].name))) { presetConflicts.unshift(files[i]); files.splice(i, 1); } else if (UserWorkspaceWithNameExists(decodeURI(files[i].name))) { userConflicts.unshift(files[i]); files.splice(i, 1); } } // If any of the files conflict with preset workspaces, we notify the user that they can't be // imported. If any of the files conflict with user workspaces, we also notify the user but give // them the option to cancel the import. Detailed in the truth table below. // Files User C Preset C Behavior // T T T Prompt to replace, Conditional Import, notify can't be imported // T T F Prompt to replace, Conditional Import, // T F T Import, notify can't be imported // T F F Import // F T T Prompt to replace, Conditional Import, notify can't be imported // F T F Prompt to replace, Conditional Import // F F T notify can't be imported // F F F Shouldn't happen if (presetConflicts.length > 0) { var haveOthers = (files.length > 0) || (userConflicts.length > 0); if (haveOthers) alert ("File Conflict\n" + "The following workspaces cannot be imported because they conflict with " + "existing workspaces:\n\n" + ConcatFileNamesFromFileArray(presetConflicts)); else alert ("None of the selected file(s) can be imported because they all conflict with " + "existing workspaces."); } var importingUserConflicts = false; if (userConflicts.length > 0) { var confirmMsg = "File Conflict\n" + "The following workspaces conflict with " + "existing custom workspaces:\n\n" + ConcatFileNamesFromFileArray(userConflicts) + "\n\n" + "If you continue, the existing workspaces will be replaced " + "with the workspaces you are importing.\n\n" + "Continue?"; // If the user wants to import the user conflicts, add them back to the 'files' array if (confirm(confirmMsg)) files = files.concat(userConflicts); } // All files canceled or conflicting if (files.length == 0) return; // Ok, let's import! ImportScreenedWorkspaceFiles(files); } // Import the provided files as workspaces. All files in 'files' should already have been // verified as not conflicting with preset workspaces and either not conflicting with user // workspaces or ok'd to overwrite user workspaces. function ImportScreenedWorkspaceFiles(files) { var userFolder = GetUserWorkspacesFolder(); var modifiedFolder = (gAppMajorVersion >= 12) ? GetModifiedWorkspacesFolder() : null; var failures = new Array(); for (var i = files.length - 1; i >= 0 ; --i) { var decodedFilename = decodeURI(files[i].name); var targetFile = new File(userFolder.fsName + "/" + decodedFilename); alert ("Copying to " + targetFile.fsName); var copySucceeded = files[i].copy(targetFile); if (copySucceeded) { // The copy succeeded. If we are copying over an existing workspace, reset it. // This does two things: // 1. Make sure we delete any "modified" file that may be around so we load the newly // imported version of a workspace next time it is selected. // 2. If we imported over the current workspace, reload it now. // If it is not an existing workspace, just make sure we delete any modified version // that may be lying around. // Older versions don't have "modified" workspace files and we can't get the workspace // list, so we just tell the user they need to relaunch Photoshop. if (gAppMajorVersion >= 12) { // The filename is the displayname for user workspaces, which is what we are creating, // so it works for resetting the workspace. if (IsLoadedWorkspace(decodedFilename)) ResetWorkspace (decodedFilename); else { var modifiedFile = new File (modifiedFolder.fsName + "/" + decodedFilename); if (modifiedFile.exists) modifiedFile.remove(); } } } else { // The copy failed, push it onto the beginning of the failures array // and remove it from 'files' failures.unshift(files[i]); files.splice(i, 1); } } // Report the results var msg = files.length + " file(s) imported successfully."; if (failures.length > 0) msg += " " + failures.length + " file(s) could not be imported."; msg += "\n"; // 1st paragraph is bold on Mac // Make PS reload the workspaces so they show up, or warn if we can't if (files.length > 0) { if (gAppMajorVersion >= 12) ReloadWorkspaces(); else msg += "You must relaunch Photoshop to see the new workspaces which you've imported.\n\n"; } // Summarize what was imported and what failed to import msg += "The following file(s) were imported:\n\n"; if (files.length > 0) msg += ConcatFileNamesFromFileArray(files); else msg += "\t"; if (failures.length > 0) msg += "\n\n" + "The following file(s) failed to import:\n\n" + ConcatFileNamesFromFileArray(failures); //alert (msg); } // Concatenates all the filenames in an array so that they can be output for the user. function ConcatFileNamesFromFileArray(files) { var rv = ''; for (var i = 0; i < files.length; ++i) { if (i > 0) rv += "\n"; // decode URI to make spaces, etc user friendly rv += "\t" + decodeURI(files[i].name); } return rv; } // Determines if there is a preset workspace with the given filename function PresetWorkspaceWithNameExists(filename) { // special-case Essentials if (filename == "Essentials") return true; // Test against preset workspace files var presetFolder = GetPresetWorkspacesFolder(); if (FolderContainsFile(presetFolder, filename)) return true; // If we are in a localized build, check against the display names of loaded presets // which will be different than the filenames, but which we'd also like to avoid // conflicts with. We can't check for conflicts with display names of "deleted" presets. if ((app.locale != 'en_US') && (gAppMajorVersion >= 12)) { // Get loaded preset workspaces var wsList = GetWorkspaceList(false, true); for (var i = 0; i < wsList.length; ++i) { if (wsList[i].displayName.toLowerCase() == filename.toLowerCase()) return true; } } // We couldn't find one! return false; } // Determines if there is a user workspace with the given filename function UserWorkspaceWithNameExists(filename) { var userFolder = GetUserWorkspacesFolder(); return FolderContainsFile(userFolder, filename); } // Returns true if Photoshop has a workspace in its list of loaded workspaces with the given filename function IsLoadedWorkspace(filename) { if (gAppMajorVersion < 12) { // Warn that I need to do something differently in old version. alert ("Only supported in CS5 and newer."); return; } var wsList = GetWorkspaceList(true, true); for (var i = 0; i < wsList.length; ++i) { if (decodeURI(wsList[i].filename).toLowerCase() == filename.toLowerCase()) return true; } return false; } // filename should NOT be in URI format function FolderContainsFile(folder, filename) { var matchingFiles = folder.getFiles(filename); // Base Case. // We got back a match in this folder, confirm we have a matching File, not just a Folder or // a file that contains the filename but isn't the same if (matchingFiles.length > 0) { for (var i = 0; i < matchingFiles.length; ++i) { if ((matchingFiles[i] instanceof File) && (decodeURI(matchingFiles[i].name).toLowerCase() == filename.toLowerCase())) return true; } } // Recursive Case // Search subfolders. var subfolders = folder.getFiles(IsFolder); for (var i = 0; i < subfolders.length; ++i) { if (FolderContainsFile(subfolders[i], filename)) return true; } return false; } // Gets all the files in a folder and it's subfolders. function GetFilesInFolderRecursive(folder) { // Base Case. // Get the files from this folder var files = folder.getFiles(IsFile); // Recursive Case // Search subfolders. var subfolders = folder.getFiles(IsFolder); for (var i = 0; i < subfolders.length; ++i) { // add in all the files from the subfolder. files = files.concat (GetFilesInFolderRecursive(subfolders[i])); } return files; } // Returns true if the provided object is a Folder object function IsFolder(object) { return object instanceof Folder; } // Returns true if the provided object is a File object function IsFile(object) { return object instanceof File; } // Gets the folder where preset workspaces live function GetPresetWorkspacesFolder() { var platform = $.os.charAt(0) == 'M' ? 'Mac' : 'Win'; var folder = new Folder (app.path + "/Locales/" + app.locale + "/Additional Presets/" + platform + "/Workspaces"); return folder; } // Gets the folder where the 'original' versions of user workspace files live. function GetUserWorkspacesFolder() { var settingsFolder = new Folder (app.preferencesFolder); if (!settingsFolder.exists) settingsFolder.create (); var folder = new Folder (settingsFolder.fsName + "/WorkSpaces"); if (!folder.exists) folder.create(); return folder; } // Gets the folder where the 'modified' versions of workspace files live. function GetModifiedWorkspacesFolder() { if (gAppMajorVersion < 12) { // Warn that I need to do something differently in old version. alert ("Only supported in CS5 and newer."); return; } var settingsFolder = new Folder (app.preferencesFolder); if (!settingsFolder.exists) settingsFolder.create (); var folder = new Folder (settingsFolder.fsName + "/WorkSpaces (Modified)"); if (!folder.exists) folder.create(); return folder; } // Gets the list of Photoshop workspaces function GetWorkspaceList(includeUser, includePreset) { var wsList = new Array(); if (gAppMajorVersion >= 12) { // Request the workspace list from Photoshop var workspaceList = stringIDToTypeID( "workspaceList" ); var typeOrdinal = charIDToTypeID( "Ordn" ); var enumTarget = charIDToTypeID( "Trgt" ); var classProperty = charIDToTypeID( "Prpr" ); var classApplication = charIDToTypeID( "capp" ); var ref = new ActionReference(); ref.putProperty( classProperty, workspaceList ); ref.putEnumerated( classApplication, typeOrdinal, enumTarget ); var desc = executeActionGet( ref ); var descList = desc.getList( workspaceList ); // Convert to a friendly list var displayName = stringIDToTypeID( "displayName" ); var filename = stringIDToTypeID( "name" ); var userWorkspace = stringIDToTypeID( "user" ); for (var i = 0; i < descList.count; ++i) { var wsDesc = descList.getObjectValue( i ); var workspace = new Object; workspace.displayName = wsDesc.getString( displayName ); workspace.filename = wsDesc.getString( filename ); workspace.user = wsDesc.getBoolean( userWorkspace ); if ((includeUser && workspace.user) || (includePreset && !workspace.user)) wsList.push (workspace); } } else { // We have to fall back to seeing what files are in the workspace // folders, which is less reliable but allows us to reasonably support // older versions. if (includeUser) { var wsFiles = GetFilesInFolderRecursive(GetUserWorkspacesFolder()); for (var i = 0; i < wsFiles.length; ++i) { var workspace = new Object; workspace.displayName = decodeURI(wsFiles[i].name); workspace.filename = decodeURI(wsFiles[i].name); workspace.user = true; wsList.push (workspace); } } if (includePreset) { var wsFiles = GetFilesInFolderRecursive(GetPresetWorkspacesFolder()); for (var i = 0; i < wsFiles.length; ++i) { var workspace = new Object; workspace.displayName = decodeURI(wsFiles[i].name); workspace.filename = decodeURI(wsFiles[i].name); workspace.user = false; wsList.push (workspace); } } } return wsList; } // Resets the named workspace. function ResetWorkspace(displayName) { if (gAppMajorVersion < 12) { // Warn that I need to do something differently in old version. alert ("Only supported in CS5 and newer."); return; } var idRset = charIDToTypeID( "Rset" ); var desc4 = new ActionDescriptor(); var idnull = charIDToTypeID( "null" ); var ref3 = new ActionReference(); var idworkspace = stringIDToTypeID( "workspace" ); ref3.putName( idworkspace, displayName ); desc4.putReference( idnull, ref3 ); executeAction( idRset, desc4, DialogModes.NO ); } // Makes PS reload the workspaces so they show up in the switcher and menus. function ReloadWorkspaces() { if (gAppMajorVersion < 12) { // Warn that I need to do something differently in old version. alert ("Only supported in CS5 and newer."); return; } var idDlt = stringIDToTypeID( "load" ); var desc3 = new ActionDescriptor(); var idnull = charIDToTypeID( "null" ); var ref2 = new ActionReference(); var idworkspace = stringIDToTypeID( "workspace" ); ref2.putClass( idworkspace ); desc3.putReference( idnull, ref2 ); executeAction( idDlt, desc3, DialogModes.NO ); } // Calls 'callee' from within a try-catch block and reports any exceptions. function TCWrap(callee, arg1, arg2, arg3) { try { callee(arg1, arg2, arg3); } catch(e) { alert (e + ":" + e.line); } }