using UnityEngine; using UnityEditor; using System; using System.Collections.Generic; using System.Linq; using System.Text; using Perforce.P4; using log4net; namespace P4Connect { #region PendingAssetAndMeta class PendingAssetAndMeta { public string AssetPath; public string MetaPath; public FileAndMetaType Type; public AssetStatus State; public string EffectiveFilePath { get { switch (Type) { case FileAndMetaType.None: return ""; case FileAndMetaType.FileOnly: return AssetPath; case FileAndMetaType.MetaOnly: return MetaPath; case FileAndMetaType.FileAndMeta: return AssetPath; default: throw new System.Exception("Unhandled case"); } } } public string TypeString { get { switch (Type) { case FileAndMetaType.None: return ""; case FileAndMetaType.FileOnly: return "Asset"; case FileAndMetaType.MetaOnly: return "Meta"; case FileAndMetaType.FileAndMeta: return "Asset+Meta"; default: throw new System.Exception("Unhandled case"); } } } public PendingAssetAndMeta() { AssetPath = ""; MetaPath = ""; Type = FileAndMetaType.None; State = new AssetStatus(); } public override string ToString() { return string.Format("type: {0} asset: {1} meta: {2} state: {3}", Type.ToString(), AssetPath, MetaPath, State.ToString()); } } #endregion public class PendingChanges : EditorWindow { [SerializeField] private static readonly ILog log = LogManager.GetLogger(typeof(PendingChanges)); private const int c_itemHeight = 18; [SerializeField] private Vector2 _scrollPosition; [SerializeField] private Rect _lastRect; [SerializeField] private int _itemCount = 0; [SerializeField] private int _itemsAbove, _itemsToRender, _itemsBelow; // Widths of controls const float headerFudge = 3.0f; const float checkWidth = 10.0f; const float spaceBeforeIcon = 8.0f; const float iconWidth = 20.0f; const float iconHeight = 20.0f; const float storageWidth = 80.0f; const float stateWidth = 80.0f; const float fileNameWidth = 250.0f; const float directoryNameWidth = 250.0f; const float scrollWidth = 30.0f; // static objects frequently used to create controls static GUILayoutOption _CheckWidth = GUILayout.Width(checkWidth); static GUILayoutOption _IconWidth = GUILayout.Width(iconWidth); static GUILayoutOption _IconHeight = GUILayout.Height(iconHeight); static GUILayoutOption _StateWidth = GUILayout.Width(stateWidth); static GUILayoutOption _StorageWidth = GUILayout.Width(storageWidth); static GUILayoutOption _FilenameWidth = GUILayout.Width(fileNameWidth); static GUILayoutOption _HeaderFudgeWidth = GUILayout.Width(headerFudge); static GUILayoutOption _IconGapWidth = GUILayout.Width(spaceBeforeIcon + iconWidth); static GUILayoutOption _IconScrollWidth = GUILayout.Width(iconWidth + scrollWidth); static GUILayoutOption _200Width = GUILayout.Width(200.0f); const double DoubleClickTime = 500; // ms [SerializeField] static PendingChanges _currentInstance; public static PendingChanges Window = null; // Add menu named "Perforce" to the Window menu [MenuItem("Window/Perforce", false, 1000)] public static void ShowWindow() { // Get existing open window or if none, make a new one: Window = EditorWindow.GetWindow(typeof(PendingChanges), false, "Perforce") as PendingChanges; Window.titleContent = new GUIContent(Icons.GetIcon("p4connect-icon.png"),"Perforce"); Window.name = "Perforce"; } public static void UpdateDisplay() { if (_currentInstance != null) { if (Config.ValidConfiguration) { _currentInstance.UpdateList(); } else { _currentInstance.Clear(); } _currentInstance.Repaint(); } } public IEnumerable SelectedAssets { get { return _CurrentSelectedChanges.Select(ch => ch.EffectiveFilePath); } } List _PendingChanges; Dictionary _CheckedFiles; Vector2 _ScrollVector; string _ChangeListDescription; Texture2D _HighlightedBackground; Texture2D _SelectedBackground; //static GUISkin _P4Skin = (GUISkin) ScriptableObject.CreateInstance(); // GUI element styles static GUIStyle HeaderStyle ; static GUIStyle HeaderButtons; static GUIStyle ToggleStyle; static GUIStyle NormalStyle; static GUIStyle HighlightedStyle; static GUIStyle SelectedStyle; static GUIStyle ChangelistStyle; // User selection PendingAssetAndMeta _LastSelectedChange; DateTime _LastSelectedChangeTime; HashSet _CurrentSelectedChanges; // Sorting algorithms System.Func _CurrentSortingFunc; bool _CurrentSortingAscending; public PendingChanges() { //Debug.Log("PendingChanges .ctor"); } public void OnEnable() { //Debug.Log("OnEnable"); if (_currentInstance == null) { _currentInstance = this; } _LastSelectedChange = null; if (_CurrentSelectedChanges == null) _CurrentSelectedChanges = new HashSet(); if (_PendingChanges == null) _PendingChanges = new List(); if (_CheckedFiles == null) _CheckedFiles = new Dictionary(); _CurrentSortingFunc = FilenameSortingFunc; _CurrentSortingAscending = true; if (Config.ValidConfiguration) { ChangeManager.PrepareChange(ChangeManager.DEFAULT_CHANGE, true); UpdateList(); } AssetStatusCache.OnAssetStatusChanged -= OnOperationPerformed; AssetStatusCache.OnAssetStatusChanged += OnOperationPerformed; } public void OnDisable() { // Debug.Log("OnDisable"); } void OnFocus() { // Debug.Log("OnFocus"); if (_currentInstance != null) _currentInstance.Repaint(); } // This should only be called from OnGUI private void OnGuiInit() { if (_HighlightedBackground == null) { //Debug.Log("initializing styles"); _HighlightedBackground = new Texture2D(1, 1); _HighlightedBackground.SetPixel(0, 0, new Color(61.0f / 255.0f, 96.0f / 255.0f, 145.0f / 255.0f)); _HighlightedBackground.Apply(); } else { //Debug.Log("existing styles"); } if (_SelectedBackground == null) { _SelectedBackground = new Texture2D(1, 1); _SelectedBackground.SetPixel(0, 0, new Color(72.0f / 255.0f, 72.0f / 255.0f, 72.0f / 255.0f)); _SelectedBackground.Apply(); } if (HeaderStyle == null) { HeaderStyle = new GUIStyle(EditorStyles.miniButtonMid); //HeaderStyle = GUI.skin.GetStyle("minibuttonmid"); HeaderStyle.normal.textColor = Color.white; HeaderStyle.padding = new RectOffset(0, 0, 0, 0); HeaderStyle.margin = new RectOffset(0, 0, 0, 0); HeaderStyle.overflow = new RectOffset(0, 0, 0, 0); } if (HeaderButtons == null) { HeaderButtons = new GUIStyle(EditorStyles.miniButtonMid); //HeaderButton = GUI.skin.GetStyle("minibuttonmid"); HeaderButtons.alignment = TextAnchor.MiddleLeft; HeaderStyle.padding = new RectOffset(0, 0, 0, 2); HeaderButtons.margin = new RectOffset(0, 0, 0, 0); HeaderButtons.overflow = new RectOffset(0, 0, 0, 2); } if (ToggleStyle == null) { ToggleStyle = new GUIStyle(EditorStyles.toggle); RectOffset margin = ToggleStyle.margin; margin.top = 0; margin.bottom = 0; ToggleStyle.margin = margin; } if (NormalStyle == null) { NormalStyle = new GUIStyle(GUIStyle.none); NormalStyle.padding = new RectOffset(0, 0, 0, 0); NormalStyle.margin = new RectOffset(0, 0, 0, 0); NormalStyle.border = new RectOffset(0, 0, 0, 0); } if (HighlightedStyle == null) { HighlightedStyle = new GUIStyle(GUIStyle.none); HighlightedStyle.normal.background = _HighlightedBackground; HighlightedStyle.padding = new RectOffset(0, 0, 0, 0); HighlightedStyle.margin = new RectOffset(0, 0, 0, 0); HighlightedStyle.border = new RectOffset(0, 0, 0, 0); } if (SelectedStyle == null) { SelectedStyle = new GUIStyle(GUIStyle.none); SelectedStyle.normal.background = _SelectedBackground; SelectedStyle.padding = new RectOffset(0, 0, 0, 0); SelectedStyle.margin = new RectOffset(0, 0, 0, 0); SelectedStyle.border = new RectOffset(0, 0, 0, 0); } if (ChangelistStyle == null) { ChangelistStyle = new GUIStyle(EditorStyles.toolbarButton); ChangelistStyle.normal.textColor = Color.grey; ChangelistStyle.alignment = TextAnchor.MiddleLeft; } } public void OnDestroy() { _currentInstance = null; Clear(); AssetStatusCache.OnAssetStatusChanged -= OnOperationPerformed; } void Clear() { if (_HighlightedBackground == null) return; _PendingChanges.Clear(); _CheckedFiles.Clear(); _ScrollVector = Vector2.zero; // User selection _LastSelectedChange = null; _CurrentSelectedChanges.Clear(); } void OnOperationPerformed(PerforceConnection con) { Debug.Log("PendingChanges::OnOperationPerformed"); UpdateList(); Repaint(); } void Update() { //Debug.Log("Update"); if (Window == null || !Config.ValidConfiguration) { this.Close(); // If the perforce connection is broken, or Window reset, close the pending changes window return; } //ChangeManager.PrepareChange(ChangeManager.DEFAULT_CHANGE); //UpdateList(); } /// /// Rebuild the Display data for PendingChanges /// Goes to the server for new FileMetaData /// /// void UpdateList() { //Profiler.BeginSample("UpdateList"); //log.Debug("UpdateList"); _PendingChanges.Clear(); if (Config.ValidConfiguration) { ChangeManager.PrepareChange(ChangeManager.DEFAULT_CHANGE); // Notice that this next line will insure that no default assetstatuses exist var openedFiles = ChangeManager.GetHashMap(ChangeManager.DEFAULT_CHANGE).InitializeAssetStatuses(); #if DEBUG // log.DebugFormat("openedFiles: {0}", Logger.StringEnumerationToString(openedFiles)); #endif PamManager pManager = new PamManager(); if (openedFiles.Any()) { // Parse the list, seeing if files have metas and such foreach (string file in openedFiles) { string filePath = Utils.UnescapeFilename(file); pManager.Add(filePath); } foreach(var entry in pManager.Values) { //log.Debug("entry: " + entry.EffectiveFilePath + " asset: " + Logger.ToStringNullSafe(entry.AssetPath) + " meta: " + Logger.ToStringNullSafe(entry.MetaPath)); // Check whether we knew about this file and wanted it selected if (!_CheckedFiles.ContainsKey(entry.EffectiveFilePath)) { // If we don't know the file, say that it's selected _CheckedFiles.Add(entry.EffectiveFilePath, true); } _PendingChanges.Add(entry); } // Fetch the state of the file List paths = new List(_PendingChanges.Select(pc => pc.EffectiveFilePath)); for (int i = 0; i < paths.Count; ++i) { _PendingChanges[i].State = AssetStatusCache.GetCachedAssetStatus(paths[i]); } // Sort the changes SortPendingChanges(); } // AssetStatusCache.PendingDirty = false; } Profiler.EndSample(); } void OnGUI() { if (Event.current == null || ! Config.IsEditing()) { GUIUtility.ExitGUI(); } if (_HighlightedBackground == null) { OnGuiInit(); } //Debug.Log("plugh1"); Event evt = Event.current; // Buttons GUILayout.BeginHorizontal(EditorStyles.toolbar); EditorGUI.BeginDisabledGroup(!Config.ValidConfiguration); GUILayout.Label("Default Changelist: " + _PendingChanges.Count + " assets", ChangelistStyle, _200Width); if (GUILayout.Button("Refresh", EditorStyles.toolbarButton)) { ChangeManager.PrepareChange(ChangeManager.DEFAULT_CHANGE, true); UpdateList(); } if (GUILayout.Button("Revert All Unchanged", EditorStyles.toolbarButton)) { RevertUnchanged(); } if (GUILayout.Button("Get Latest Assets", EditorStyles.toolbarButton)) { if (EditorUtility.DisplayDialog("Get Latest Assets?", "Are you sure you want to update all assets to the latest revision?", "Ok", "Cancel")) { if (Engine.GetLatestAsset("...", false).Count == 0) { Debug.Log("P4Connect - Get Latest Revision - No Change"); } } } EditorGUI.EndDisabledGroup(); GUILayout.FlexibleSpace(); if (GUILayout.Button("Help", EditorStyles.toolbarButton)) { System.Diagnostics.Process.Start("http://www.perforce.com/perforce/doc.current/manuals/p4connectguide/index.html"); } if (GUILayout.Button("Settings...", EditorStyles.toolbarButton)) { ConfigWindow.ShowWindow(); } GUILayout.EndHorizontal(); EditorGUI.BeginDisabledGroup(!Config.ValidConfiguration); OnGUIHeader(); GUILayout.BeginHorizontal(NormalStyle); _scrollPosition = GUILayout.BeginScrollView(_scrollPosition, false, false, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, GUI.skin.box); _itemCount = _PendingChanges.Count; if (evt.type == EventType.Layout) { CalculateWhatItemsToRender(); } GUILayout.Space(_itemsAbove * c_itemHeight); for( var i = _itemsAbove; i < Mathf.Clamp(_itemsAbove + _itemsToRender, 0, _itemCount); i++) { OnGUILineItem(_PendingChanges[i], evt); } GUILayout.Space(_itemsBelow * c_itemHeight); GUILayout.EndScrollView(); if (Event.current.type == EventType.Repaint) { _lastRect = GUILayoutUtility.GetLastRect(); } GUILayout.EndHorizontal(); EditorGUI.EndDisabledGroup(); if (!Config.PerforceEnabled) { EditorGUILayout.HelpBox("Perforce integration is disabled", MessageType.Info); } else if (!Config.ValidConfiguration) { EditorGUILayout.HelpBox("Pending Changes are unavailable because your settings are invalid.\nPlease go to Edit->Perforce Settings to update them.", MessageType.Warning); } EditorGUI.BeginDisabledGroup(!Config.ValidConfiguration); GUILayout.Label("Description", EditorStyles.boldLabel); GUI.SetNextControlName("DescriptionBox"); _ChangeListDescription = EditorGUILayout.TextArea(_ChangeListDescription, GUILayout.MinHeight(50.0f)); GUILayout.BeginHorizontal(); int unresolvedCount = _PendingChanges.Count(ch => _CheckedFiles[ch.EffectiveFilePath] && ch.State.ResolvedState == ResolvedState.NeedsResolve); int outOfDateCount = _PendingChanges.Count(ch => _CheckedFiles[ch.EffectiveFilePath] && ch.State.RevisionState == RevisionState.OutOfDate); if (unresolvedCount > 0) { Color prevColor = GUI.color; GUI.color = Color.yellow; GUILayout.Label(unresolvedCount.ToString() + " unresolved assets selected!"); GUI.color = prevColor; } else if (outOfDateCount > 0) { Color prevColor = GUI.color; GUI.color = Color.yellow; GUILayout.Label(outOfDateCount.ToString() + " out of date assets selected!"); GUI.color = prevColor; } GUILayout.FlexibleSpace(); int assetCount = 0; int fileCount = 0; GetSelectedCounts(out assetCount, out fileCount); GUILayout.Label(assetCount.ToString() + " assets selected (" + fileCount.ToString() + " files)"); EditorGUI.BeginDisabledGroup(_ChangeListDescription == null || _ChangeListDescription.Length == 0 || unresolvedCount > 0 || outOfDateCount > 0); if (GUILayout.Button("Submit Selected")) { SubmitFiles(); } EditorGUI.EndDisabledGroup(); GUILayout.EndHorizontal(); EditorGUI.EndDisabledGroup(); // Handle key events if (evt.isKey) { if (evt.type == EventType.KeyDown) { if (evt.keyCode == KeyCode.Return) { // Toggle all selected changes to the new state of the selected item bool itemChecked = _CheckedFiles[_LastSelectedChange.EffectiveFilePath]; foreach (var ch in _CurrentSelectedChanges) { _CheckedFiles[ch.EffectiveFilePath] = !itemChecked; } evt.Use(); Repaint(); } else if (evt.keyCode == KeyCode.DownArrow) { // Update highlight _LastSelectedChange = GetNextPendingChange(_LastSelectedChange); // Handle shift selection if ((evt.modifiers & EventModifiers.Shift) == 0 && (evt.modifiers & EventModifiers.Control) == 0) { _CurrentSelectedChanges.Clear(); } _CurrentSelectedChanges.Add(_LastSelectedChange); evt.Use(); Repaint(); } else if (evt.keyCode == KeyCode.UpArrow) { _LastSelectedChange = GetPrevPendingChange(_LastSelectedChange); if ((evt.modifiers & EventModifiers.Shift) == 0 && (evt.modifiers & EventModifiers.Control) == 0) { _CurrentSelectedChanges.Clear(); } _CurrentSelectedChanges.Add(_LastSelectedChange); evt.Use(); Repaint(); } else if (evt.keyCode == KeyCode.A) { _CurrentSelectedChanges.Clear(); foreach (var change in _PendingChanges) { _CurrentSelectedChanges.Add(change); } evt.Use(); Repaint(); } else if (evt.keyCode == KeyCode.D) { foreach (var change in _CurrentSelectedChanges) { Utils.LaunchDiffAgainstHaveRev(change.EffectiveFilePath); } evt.Use(); Repaint(); } else if (evt.keyCode == KeyCode.I) { HashSet newSelection = new HashSet(); foreach (var change in _PendingChanges) { if (!_CurrentSelectedChanges.Contains(change)) { newSelection.Add(change); } } _CurrentSelectedChanges = newSelection; evt.Use(); Repaint(); } } } } /// /// Display the Header of Pending Changes /// /// private void OnGUIHeader() { GUILayout.BeginHorizontal(HeaderStyle); { GUILayout.Label("", _HeaderFudgeWidth); bool allSelected = _PendingChanges.Count(pc => _CheckedFiles[pc.EffectiveFilePath]) == _PendingChanges.Count; bool newAllSelected = GUILayout.Toggle(allSelected, "", ToggleStyle, _CheckWidth); if (newAllSelected != allSelected) { foreach (var pc in _PendingChanges) { _CheckedFiles[pc.EffectiveFilePath] = newAllSelected; } } if (GUILayout.Button("", HeaderButtons, _IconGapWidth)) { if (GUI.GetNameOfFocusedControl() == "DescriptionBox") { GUI.FocusControl(""); } // Sort the files according to their extension if (_CurrentSortingFunc == SelectedSortingFunc) { // Reverse the order if the sorting was already according to whether they are selected _CurrentSortingAscending = !_CurrentSortingAscending; } else { // Make sorting according to the selection and reset the direction _CurrentSortingFunc = SelectedSortingFunc; _CurrentSortingAscending = true; } SortPendingChanges(); } if (GUILayout.Button("Filename", HeaderButtons, _FilenameWidth)) { if (GUI.GetNameOfFocusedControl() == "DescriptionBox") { GUI.FocusControl(""); } // Sort the files according to their name if (_CurrentSortingFunc == FilenameSortingFunc) { // Reverse the order if the sorting was already according to the name _CurrentSortingAscending = !_CurrentSortingAscending; } else { // Make sorting according to the name and reset the direction _CurrentSortingFunc = FilenameSortingFunc; _CurrentSortingAscending = true; } SortPendingChanges(); } if (GUILayout.Button("in Directory", HeaderButtons, GUILayout.MinWidth(directoryNameWidth))) { if (GUI.GetNameOfFocusedControl() == "DescriptionBox") { GUI.FocusControl(""); } if (_CurrentSortingFunc == DirectorySortingFunc) { _CurrentSortingAscending = !_CurrentSortingAscending; } else { _CurrentSortingFunc = DirectorySortingFunc; _CurrentSortingAscending = true; } SortPendingChanges(); } GUILayout.FlexibleSpace(); if (GUILayout.Button("Type", HeaderButtons, _StorageWidth)) { if (GUI.GetNameOfFocusedControl() == "DescriptionBox") { GUI.FocusControl(""); } if (_CurrentSortingFunc == TypeSortingFunc) { _CurrentSortingAscending = !_CurrentSortingAscending; } else { _CurrentSortingFunc = TypeSortingFunc; _CurrentSortingAscending = true; } SortPendingChanges(); } if (GUILayout.Button("State", HeaderButtons, _StateWidth )) { if (GUI.GetNameOfFocusedControl() == "DescriptionBox") { GUI.FocusControl(""); } if (_CurrentSortingFunc == StateSortingFunc) { _CurrentSortingAscending = !_CurrentSortingAscending; } else { _CurrentSortingFunc = StateSortingFunc; _CurrentSortingAscending = true; } SortPendingChanges(); } GUILayout.Label("", _IconScrollWidth); } GUILayout.EndHorizontal(); } /// /// Display one Pending Changes item on a line /// /// Which file and/or Meta /// OnGUI Event private void OnGUILineItem(PendingAssetAndMeta change, Event evt) { // Get a style for this line (if selected or not) GUIStyle lineStyle = NormalStyle; if (_LastSelectedChange == change && focusedWindow == this) { if (_CurrentSelectedChanges.Contains(_LastSelectedChange)) { lineStyle = HighlightedStyle; } else { lineStyle = NormalStyle; } } else if (_CurrentSelectedChanges.Contains(change)) { lineStyle = SelectedStyle; } GUILayout.BeginHorizontal(lineStyle); // Draw a selection box bool itemChecked = _CheckedFiles[change.EffectiveFilePath]; bool newItemChecked = GUILayout.Toggle(itemChecked, "", EditorStyles.toggle, _CheckWidth); if (newItemChecked != itemChecked) { if (_CurrentSelectedChanges.Contains(change)) { foreach (var ch in _CurrentSelectedChanges) { _CheckedFiles[ch.EffectiveFilePath] = newItemChecked; } } else { _CheckedFiles[change.EffectiveFilePath] = newItemChecked; } } GUILayout.Space(spaceBeforeIcon); // Fetch the asset's icon and draw it Texture assetTexture = Icons.GetAssetIcon(change.AssetPath); GUILayout.Label(assetTexture, _IconWidth, _IconHeight); // Then decorate it with status icons Rect iconRect = GUILayoutUtility.GetLastRect(); iconRect.yMin -= 1.0f; iconRect.xMax -= 0.5f; iconRect.yMax += 16.0f; Icons.DrawItemIcons(change.EffectiveFilePath, iconRect, false); // And the name of the asset string fullpath = Utils.AssetPathToLocalPath(change.AssetPath); GUILayout.Label(System.IO.Path.GetFileName(change.AssetPath), _FilenameWidth ); GUILayout.Label(System.IO.Path.GetDirectoryName(change.AssetPath), GUILayout.MinWidth(directoryNameWidth)); GUILayout.FlexibleSpace(); // Now indicate if we have the file and/or the meta GUILayout.Label(Utils.GetStorageTypeString(change.State.StorageType), _StorageWidth); GUILayout.Label(Utils.GetFileStateString(change.State.LocalState), _StateWidth); GUILayout.Label(Icons.GetFileAndMetaTypeIcon(change.Type), _IconWidth, _IconHeight); GUILayout.EndHorizontal(); Rect lastRect = GUILayoutUtility.GetLastRect(); if (_LastSelectedChange == change && focusedWindow == this && !_CurrentSelectedChanges.Contains(_LastSelectedChange)) { Rect topLine = lastRect; topLine.yMax = topLine.yMin + 1; Rect bottomLine = lastRect; bottomLine.yMin = bottomLine.yMax - 1; Rect leftLine = lastRect; leftLine.xMin += 1; leftLine.xMax = leftLine.xMin + 1; Rect rightLine = lastRect; rightLine.xMin = rightLine.xMax - 1; GUI.Box(topLine, GUIContent.none, HighlightedStyle); GUI.Box(bottomLine, GUIContent.none, HighlightedStyle); GUI.Box(leftLine, GUIContent.none, HighlightedStyle); GUI.Box(rightLine, GUIContent.none, HighlightedStyle); } // is the mouse here? if (lastRect.Contains(evt.mousePosition)) { // Handle events here if (evt.isMouse) { if (evt.type == EventType.MouseDown) { if (GUI.GetNameOfFocusedControl() == "DescriptionBox") { GUI.FocusControl(""); } if (evt.button == 0) { if ((evt.modifiers & EventModifiers.Control) != 0) { // Ctrl means Add/remove from selection if (_CurrentSelectedChanges.Contains(change)) { _CurrentSelectedChanges.Remove(change); } else { _CurrentSelectedChanges.Add(change); } } else if ((evt.modifiers & EventModifiers.Shift) != 0) { // Shift means select all between this and previous selected foreach (var ch in GetChangesBetweenChanges(_LastSelectedChange, change)) { _CurrentSelectedChanges.Add(ch); } } else { if (change == _LastSelectedChange) { // Is this a double click? if ((DateTime.Now - _LastSelectedChangeTime).TotalMilliseconds < DoubleClickTime) { // Yes, open the asset if (_LastSelectedChange.AssetPath != null || _LastSelectedChange.AssetPath != "") { UnityEngine.Object asset = AssetDatabase.LoadMainAssetAtPath(_LastSelectedChange.AssetPath); AssetDatabase.OpenAsset(asset); } } } else { // Clear selection and set it to new element _CurrentSelectedChanges.Clear(); _CurrentSelectedChanges.Add(change); } } // In all cases, we remember the last selected change _LastSelectedChange = change; _LastSelectedChangeTime = DateTime.Now; evt.Use(); Repaint(); } else if (evt.button == 1) { // In all cases, we remember the last selected change _LastSelectedChange = change; evt.Use(); Repaint(); } } else if (evt.type == EventType.MouseUp) { if (evt.button == 1) { if (!_CurrentSelectedChanges.Contains(change) && (evt.modifiers & EventModifiers.Control) == 0) { // Anything other than Ctrl Deselects _CurrentSelectedChanges.Clear(); _CurrentSelectedChanges.Add(change); } // Display contextual menu Rect menuRect = lastRect; menuRect.xMin = evt.mousePosition.x; menuRect.yMin = evt.mousePosition.y; EditorUtility.DisplayPopupMenu(menuRect, "CONTEXT/Perforce/", new MenuCommand(this)); evt.Use(); } } } } } private void CalculateWhatItemsToRender() { _itemsAbove = (int)(_scrollPosition.y / c_itemHeight); _itemsToRender = (int)(_lastRect.height / c_itemHeight); if (_itemsAbove + _itemsToRender > _itemCount - 2) { _itemsToRender += 1; } else { _itemsToRender += 2; } _itemsBelow = _itemCount - _itemsAbove - _itemsToRender; } void GetSelectedCounts(out int aOutAssetCount, out int aOutFileCount) { int assetCount = 0; int fileCount = 0; for (int i = 0; i < _PendingChanges.Count; i++) { var change = _PendingChanges[i]; if (_CheckedFiles[change.EffectiveFilePath]) { assetCount += 1; switch (change.Type) { case FileAndMetaType.FileOnly: fileCount += 1; break; case FileAndMetaType.MetaOnly: fileCount += 1; break; case FileAndMetaType.FileAndMeta: fileCount += 2; break; } } } aOutAssetCount = assetCount; aOutFileCount = fileCount; } /// /// Submit the files in this pending change /// void SubmitFiles() { List specsToSubmit = new List(); // Build a string of the names StringBuilder builder = new StringBuilder(); builder.AppendLine("Are you sure you want to submit the following files?"); builder.AppendLine(); int maxCount = 20; int curCount = 0; for (int i = 0; i < _PendingChanges.Count; i++) { var change = _PendingChanges[i]; if (_CheckedFiles[change.EffectiveFilePath]) { switch (change.Type) { case FileAndMetaType.FileOnly: { string filepath = Utils.AssetPathToLocalPath(change.AssetPath); specsToSubmit.Add(FileSpec.LocalSpec(filepath)); if (++curCount <= maxCount) builder.AppendLine(change.AssetPath); } break; case FileAndMetaType.MetaOnly: { string filepath = Utils.AssetPathToLocalPath(change.MetaPath); specsToSubmit.Add(FileSpec.LocalSpec(filepath)); if (++curCount <= maxCount) builder.AppendLine(change.MetaPath); } break; case FileAndMetaType.FileAndMeta: { string filepath = Utils.AssetPathToLocalPath(change.AssetPath); specsToSubmit.Add(FileSpec.LocalSpec(filepath)); if (++curCount <= maxCount) builder.AppendLine(change.AssetPath); } { string filepath = Utils.AssetPathToLocalPath(change.MetaPath); specsToSubmit.Add(FileSpec.LocalSpec(filepath)); if (++curCount <= maxCount) builder.AppendLine(change.MetaPath); } break; } } } if (curCount > maxCount) { builder.Append("..."); } if (EditorUtility.DisplayDialog("Submit changes?", builder.ToString(), "Submit", "Cancel")) { Engine.PerformConnectionOperation(con => Engine.SubmitFiles(con, _ChangeListDescription, specsToSubmit)); _ChangeListDescription = ""; //GUI.FocusControl(""); OnFocus(); } } /// /// Revert unchanged files which are in the pending change /// void RevertUnchanged() { List filesToRevert = new List(); for (int i = 0; i < _PendingChanges.Count; i++) { var change = _PendingChanges[i]; switch (change.Type) { case FileAndMetaType.FileOnly: filesToRevert.Add(change.AssetPath); break; case FileAndMetaType.MetaOnly: filesToRevert.Add(change.MetaPath); break; case FileAndMetaType.FileAndMeta: filesToRevert.Add(change.AssetPath); filesToRevert.Add(change.MetaPath); break; } } if (Engine.RevertAssets(filesToRevert.ToArray(), false).Count == 0) { Debug.Log("P4Connect - Revert if Unchanged - No Change"); } } string SelectedSortingFunc(PendingAssetAndMeta aAssetAndMeta) { return _CheckedFiles[aAssetAndMeta.EffectiveFilePath] ? "A" : "B"; } static string ExtensionSortingFunc(PendingAssetAndMeta aAssetAndMeta) { return System.IO.Path.GetExtension(aAssetAndMeta.EffectiveFilePath); } static string FilenameSortingFunc(PendingAssetAndMeta aAssetAndMeta) { return System.IO.Path.GetFileName(aAssetAndMeta.EffectiveFilePath); } static string DirectorySortingFunc(PendingAssetAndMeta aAssetAndMeta) { return System.IO.Path.GetDirectoryName(aAssetAndMeta.EffectiveFilePath); } static string StateSortingFunc(PendingAssetAndMeta aAssetAndMeta) { return Utils.GetFileStateString(aAssetAndMeta.State.LocalState); } static string TypeSortingFunc(PendingAssetAndMeta aAssetAndMeta) { return Utils.GetStorageTypeString(aAssetAndMeta.State.StorageType); } void SortPendingChanges() { if (_CurrentSortingFunc != null) { List newList = null; if (_CurrentSortingAscending) newList = new List(_PendingChanges.OrderBy(_CurrentSortingFunc)); else newList = new List(_PendingChanges.OrderByDescending(_CurrentSortingFunc)); _PendingChanges = newList; } } PendingAssetAndMeta GetPrevPendingChange(PendingAssetAndMeta aCurrent) { int index = _PendingChanges.IndexOf(aCurrent); return _PendingChanges[Mathf.Clamp(index - 1, 0, _PendingChanges.Count)]; } PendingAssetAndMeta GetNextPendingChange(PendingAssetAndMeta aCurrent) { int index = _PendingChanges.IndexOf(aCurrent); return _PendingChanges[Mathf.Clamp(index + 1, 0, _PendingChanges.Count)]; } IEnumerable GetChangesBetweenChanges(PendingAssetAndMeta aStart, PendingAssetAndMeta aEndIncluded) { int indexStart = _PendingChanges.IndexOf(aStart); int indexEnd = _PendingChanges.IndexOf(aEndIncluded); for (int i = indexStart; i <= indexEnd; ++i) { yield return _PendingChanges[i]; } } public void SelectAssets(IEnumerable aEffectiveFilePaths) { // Sometimes the screen is blank _currentInstance.Repaint(); // Deselect all List files = new List(_CheckedFiles.Keys); foreach (var file in files) { _CheckedFiles[file] = false; } // Select files passed in foreach (var file in aEffectiveFilePaths) { bool state = false; if (_CheckedFiles.TryGetValue(file, out state)) { _CheckedFiles[file] = true; } } } #region Context Menus // Menu entries associated with the Pending Changes Dialog [MenuItem("CONTEXT/Perforce/Select in Project View", false, 10)] public static void SelectInProjectView(MenuCommand aCommand) { PendingChanges changes = aCommand.context as PendingChanges; List objects = new List(changes.SelectedAssets.Select(path => AssetDatabase.LoadMainAssetAtPath(path))); Selection.objects = objects.ToArray(); EditorUtility.FocusProjectWindow(); } [MenuItem("CONTEXT/Perforce/Force Get Latest Revision", false, 10)] public static void ProjectViewForceGetLatest1(MenuCommand aCommand) { PendingChanges changes = aCommand.context as PendingChanges; var assets = changes.SelectedAssets.AddDirectoriesFromMetas(); if (! assets.Any()) return; // Build a string of the names StringBuilder builder = new StringBuilder(); builder.AppendLine("Are you sure you want to force-get the latest revision of the following assets?"); builder.AppendLine(); foreach (string path in assets) { builder.AppendLine(path); } if (EditorUtility.DisplayDialog("P4Connect - Force Get Latest", builder.ToString(), "Yes", "No")) { Engine.GetLatestAssets(assets.ToArray(), true); } } [MenuItem("CONTEXT/Perforce/Revert if Unchanged", false, 10)] public static void RevertAssetIfUnchanged(MenuCommand aCommand) { PendingChanges changes = aCommand.context as PendingChanges; Engine.RevertAssets(changes.SelectedAssets.AddDirectoriesFromMetas().ToArray(), false); } [MenuItem("CONTEXT/Perforce/Revert", false, 10)] public static void RevertAsset(MenuCommand aCommand) { // Grab the selected assets PendingChanges changes = aCommand.context as PendingChanges; var assets = changes.SelectedAssets.AddDirectoriesFromMetas(); // Build a string of the names StringBuilder builder = new StringBuilder(); builder.AppendLine("Are you sure you want to revert the following assets?"); builder.AppendLine(); foreach (string path in assets) { builder.AppendLine(path); } if (EditorUtility.DisplayDialog("P4Connect - Revert", builder.ToString(), "Yes", "No")) { Engine.RevertAssets(assets.ToArray(), true); } } [MenuItem("CONTEXT/Perforce/Diff against Have-Revision", false, 10)] public static void DiffAsset(MenuCommand aCommand) { // Grab the selected assets PendingChanges changes = aCommand.context as PendingChanges; List assets = new List(changes.SelectedAssets); foreach (string asset in assets) { Utils.LaunchDiffAgainstHaveRev(asset); } } [MenuItem("CONTEXT/Perforce/Lock", false, 10)] public static void LockAsset(MenuCommand aCommand) { // Grab the selected assets PendingChanges changes = aCommand.context as PendingChanges; var assets = changes.SelectedAssets.AddDirectoriesFromMetas(); //log.Debug("files: " + Logger.StringArrayToString(assets.ToArray())); Engine.LockAssets(assets.ToArray()); } [MenuItem("CONTEXT/Perforce/Unlock", false, 10)] public static void UnlockAsset(MenuCommand aCommand) { PendingChanges changes = aCommand.context as PendingChanges; var assets = changes.SelectedAssets.AddDirectoriesFromMetas(); //log.Debug("files: " + Logger.StringArrayToString(assets.ToArray())); Engine.UnlockAssets(assets.ToArray()); } #endregion } #region PamManager /// /// Class used by Pending Changes to collect open files into pairs (asset and meta) with appropriate types. /// Results are returned as a PendingAssetAndMeta collection /// Refactored out of the PendingChanges class to simplify things. /// class PamManager { Dictionary pamDictionary; public PamManager() { pamDictionary = new Dictionary(); } /// /// Add a path to the Pam Manager /// /// public void Add(string path) { //Debug.Log("Add: " + path); PendingAssetAndMeta assetAndMeta = new PendingAssetAndMeta(); if (path.EndsWith(".meta")) { // It's a meta file, is there an asset in the list as well? string assetpath = Utils.AssetFromMeta(path); PendingAssetAndMeta existingPam = new PendingAssetAndMeta(); if (pamDictionary.TryGetValue(assetpath, out existingPam)) { existingPam.Type = FileAndMetaType.FileAndMeta; pamDictionary[existingPam.AssetPath] = existingPam; // update existing entry } else { PendingAssetAndMeta pam = new PendingAssetAndMeta(); pam.Type = FileAndMetaType.MetaOnly; pam.MetaPath = path; pam.AssetPath = assetpath; pamDictionary[pam.AssetPath] = pam; // Add entry to pamDictionary } } else { string metapath = Utils.MetaFromAsset(path); PendingAssetAndMeta existingPam = new PendingAssetAndMeta(); // It's not a meta file, is there a meta as well? if (pamDictionary.TryGetValue(path, out existingPam)) { existingPam.Type = FileAndMetaType.FileAndMeta; pamDictionary[existingPam.AssetPath] = existingPam; // update existing entry } else { PendingAssetAndMeta pam = new PendingAssetAndMeta(); pam.Type = FileAndMetaType.FileOnly; pam.AssetPath = path; pam.MetaPath = metapath; pamDictionary[pam.AssetPath] = pam; // Add entry to pamDictionary } } } /// /// Return all pamDictionary elements /// public ICollection Values { get { return pamDictionary.Values; } } } #endregion }