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 AssetStatusCache.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 AssetStatusCache.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; // 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: PendingChanges window = EditorWindow.GetWindow(typeof(PendingChanges), false, "Perforce") as PendingChanges; window.title = "Perforce"; window.name = "Perforce"; } public static void UpdateDisplay() { if (_CurrentInstance != null) { if (Config.ValidConfiguration) { using (PerforceConnection con = new PerforceConnection()) { _CurrentInstance.UpdateList(con); } } else { _CurrentInstance.Clear(); } _CurrentInstance.Repaint(); } } public IEnumerable<string> SelectedAssets { get { return _CurrentSelectedChanges.Select(ch => ch.EffectiveFilePath); } } List<PendingAssetAndMeta> _PendingChanges; Dictionary<string, bool> _CheckedFiles; Vector2 _ScrollVector; string _ChangeListDescription; Texture2D _HighlightedBackground; Texture2D _SelectedBackground; //static GUISkin _P4Skin = (GUISkin) ScriptableObject.CreateInstance(typeof(GUISkin)); // 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<PendingAssetAndMeta> _CurrentSelectedChanges; // Sorting algorithms System.Func<PendingAssetAndMeta, string> _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<PendingAssetAndMeta>(); if (_PendingChanges == null) _PendingChanges = new List<PendingAssetAndMeta>(); if (_CheckedFiles == null) _CheckedFiles = new Dictionary<string, bool>(); _CurrentSortingFunc = FilenameSortingFunc; _CurrentSortingAscending = true; } public void OnDisable() { //Debug.Log("OnDisable"); } void OnFocus() { // Debug.Log("OnFocus"); ChangeManager.Refresh(ChangeManager.DEFAULT_CHANGE); using (PerforceConnection con = new PerforceConnection()) { //log.Debug("Focus P4Client: " + con.P4Client.Name); //log.Debug("Focus P4Server: " + con.P4Server.Address.ToString()); UpdateList(con); } if (_CurrentInstance != null) _CurrentInstance.Repaint(); } // This should only be called from OnGUI void Init() { 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); //HeaderButtons = 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; } if (!Config.ValidConfiguration) return; ChangeManager.Refresh(ChangeManager.DEFAULT_CHANGE); using (PerforceConnection con = new PerforceConnection()) { UpdateList(con); } AssetStatusCache.OnAssetStatusChanged -= OnOperationPerformed; AssetStatusCache.OnAssetStatusChanged += OnOperationPerformed; } // Only Called from within OnGUI void CheckInit() { if (_HighlightedBackground == null) { Init(); } } 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) { UpdateList(con); Repaint(); } void Update() { if (!Config.ValidConfiguration) { this.Close(); // If the perforce connection is broken, close the pending changes window Debug.Log("Closed PendingChanges window because Connection is invalid"); } } void UpdateList(PerforceConnection con) { //Profiler.BeginSample("UpdateList"); //log.Debug("UpdateList"); _PendingChanges.Clear(); if (Config.ValidConfiguration) { List<string> openedFiles = ChangeManager.LocalOpenedFiles.ToList(); //log.DebugFormat("openedFiles: {0}", Logger.StringArrayToString(openedFiles.ToArray())); PamManager pManager = new PamManager(); if (openedFiles != null) { // 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<string> paths = new List<string>(_PendingChanges.Select(pc => pc.EffectiveFilePath)); List<AssetStatusCache.AssetStatus> statuses = new List<AssetStatusCache.AssetStatus>(); AssetStatusCache.GetAssetStatusesFromPaths(con, paths, statuses); for (int i = 0; i < paths.Count; ++i) { _PendingChanges[i].State = statuses[i]; } // Sort the changes SortPendingChanges(); } AssetStatusCache.PendingDirty = false; } Profiler.EndSample(); } void OnGUI() { if (Event.current == null || Application.isPlaying) { GUIUtility.ExitGUI(); } CheckInit(); //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)) { using (PerforceConnection con = new PerforceConnection()) { ChangeManager.Refresh(-1); UpdateList(con); } } 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(Main.RootPath, 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)) { Config.ShowWindow(); } GUILayout.EndHorizontal(); //Debug.Log("plugh2"); EditorGUI.BeginDisabledGroup(!Config.ValidConfiguration); OnGUIHeader(); //Debug.Log("plugh3"); 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<PendingAssetAndMeta> newSelection = new HashSet<PendingAssetAndMeta>(); foreach (var change in _PendingChanges) { if (!_CurrentSelectedChanges.Contains(change)) { newSelection.Add(change); } } _CurrentSelectedChanges = newSelection; evt.Use(); Repaint(); } } } } /// <summary> /// Display the Header of Pending Changes /// </summary> /// <param name="evt"></param> 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(); } /// <summary> /// Display one Pending Changes item on a line /// </summary> /// <param name="change">Which file and/or Meta</param> /// <param name="evt">OnGUI Event</param> 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; } /// <summary> /// Submit the files in this pending change /// </summary> void SubmitFiles() { List<FileSpec> specsToSubmit = new List<FileSpec>(); // 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(); } } /// <summary> /// Revert unchanged files which are in the pending change /// </summary> void RevertUnchanged() { List<string> filesToRevert = new List<string>(); 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<PendingAssetAndMeta> newList = null; if (_CurrentSortingAscending) newList = new List<PendingAssetAndMeta>(_PendingChanges.OrderBy(_CurrentSortingFunc)); else newList = new List<PendingAssetAndMeta>(_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<PendingAssetAndMeta> 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<string> aEffectiveFilePaths) { // Sometimes the screen is blank _CurrentInstance.Repaint(); // Deselect all List<string> files = new List<string>(_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 PamManager /// <summary> /// 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. /// </summary> class PamManager { Dictionary<string,PendingAssetAndMeta> pamDictionary; public PamManager() { pamDictionary = new Dictionary<string,PendingAssetAndMeta>(); } /// <summary> /// Add a path to the Pam Manager /// </summary> /// <param name="path"></param> 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 existing_pam = new PendingAssetAndMeta(); if (pamDictionary.TryGetValue(assetpath, out existing_pam)) { existing_pam.Type = FileAndMetaType.FileAndMeta; pamDictionary[existing_pam.AssetPath] = existing_pam; // 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 existing_pam = new PendingAssetAndMeta(); // It's not a meta file, is there a meta as well? if (pamDictionary.TryGetValue(path, out existing_pam)) { existing_pam.Type = FileAndMetaType.FileAndMeta; pamDictionary[existing_pam.AssetPath] = existing_pam; // 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 } } } /// <summary> /// Return all pamDictionary elements /// </summary> public ICollection<PendingAssetAndMeta> Values { get { //Debug.Log("pamDictionary:"); //foreach(var entry in pamDictionary.Values) //{ // Debug.Log("entry: " + entry.ToString()); //} return pamDictionary.Values; } } } #endregion }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 21852 | cswiedler | Branch //guest/cswiedler/p4connect | ||
//guest/perforce_software/p4connect/dev/shelves/src/P4Connect/P4Connect/P4Connect.PendingChanges.cs | |||||
#1 | 17331 | Norman Morse | Dev branch for Shelves | ||
//guest/perforce_software/p4connect/main/src/P4Connect/P4Connect/P4Connect.PendingChanges.cs | |||||
#2 | 16490 | Norman Morse | Changed refresh after submit | ||
#1 | 16209 | Norman Morse | Move entire source tree into "main" branch so workshop code will act correctly. | ||
//guest/perforce_software/p4connect/src/P4Connect/P4Connect/P4Connect.PendingChanges.cs | |||||
#14 | 16117 | Norman Morse |
Fix for EditorPrefs related Disconnection. Cleaned up some code. Removed Spurious Comments Initialize Config from within Main |
||
#13 | 15424 | Norman Morse |
Fixed exceptions in Dialogs. Updated release Notes. Added checks to menus for PerforceEnabled |
||
#12 | 15383 | Norman Morse |
Improved Diagnostics, cleaned up unnecessary log output Moved some Dialog Initialization to OnEnable() Fixed Unity 5.1.1 incompatibilities Added Operation support for In Depot Deleted files |
||
#11 | 15244 | Norman Morse |
Better Directory support in "add" "get latest" "refresh" and other commands. Improved Project Root detection Various Bug Fixes and Clean up |
||
#10 | 15079 | Norman Morse |
Rewrote AssetStatusCache to Cache AssetStatuses and FileMetaData Fixed Edge conditions on Engine Operations Change Debug output defaults. Will now Checkout files which request to be "added" but which already exist in perforce. Output P4Connect version to log on initialization. |
||
#9 | 14801 | Norman Morse |
GA.9 changes. Fixed debug message exceptions Improved Pending Changes dialog for large changesets Changed configuration to allow saving configuration with Perforce disabled. Improved restart after recompile, automatically attempts connection now unless disabled. |
||
#8 | 14193 | Norman Morse |
GA.7 release Refactor Pending Changes Resolve Submit issues. Fixed Menu entries. Handle mismatched file and meta states. |
||
#7 | 12862 | Norman Morse |
Fixed problem with an empty default change list not refresshing. Fixed crash in is_ignored Removed a lot of log output |
||
#6 | 12565 | Norman Morse |
Integrated from Dev Branch Made ChangeManager into Static Class. Improved close window behavior for when connection is invalid Fixed localOpenFiles not updating on submit |
||
#5 | 12553 | Norman Morse |
integrate from internal main Build fixes for EC. Major changes to Configuration and re-initialization code. Bug fixes |
||
#4 | 12512 | Norman Morse | Integrate from Dev branch, preparing for Beta3 release | ||
#3 | 12362 | Norman Morse |
Added Debug Logging for p4log Fixed some path comparison issues. Created a CaseSensitivity test |
||
#2 | 11223 | Norman Morse | Fixed typo in comment | ||
#1 | 10940 | Norman Morse |
Inital Workshop release of P4Connect. Released under BSD-2 license |