P4Connect.PendingChanges.cs #1

  • //
  • guest/
  • anis_sg/
  • perforce_software/
  • p4connect/
  • src/
  • P4Connect/
  • P4Connect/
  • P4Connect.PendingChanges.cs
  • View
  • Commits
  • Open Download .zip Download (31 KB)
using UnityEngine;
using UnityEditor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Perforce.P4;
using log4net;

namespace P4Connect
{
	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 class PendingChanges
		: EditorWindow
	{

        private static readonly ILog log = LogManager.GetLogger(typeof(PendingChanges));

		const float iconSize = 20.0f;
		const float spaceBeforeIcon = 8.0f;
		const double DoubleClickTime = 500; // ms

		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;

		// User selection
		PendingAssetAndMeta _LastSelectedChange;
		DateTime _LastSelectedChangeTime;
		HashSet<PendingAssetAndMeta> _CurrentSelectedChanges;

		// Sorting algorithms
		System.Func<PendingAssetAndMeta, string> _CurrentSortingFunc;
		bool _CurrentSortingAscending;

		public PendingChanges()
		{
                Init();
		}

		void Init()
		{
			_LastSelectedChange = null;
			_CurrentSelectedChanges = new HashSet<PendingAssetAndMeta>();

			_PendingChanges = new List<PendingAssetAndMeta>();
			_CheckedFiles = new Dictionary<string, bool>();

            if (!Config.ValidConfiguration)
                return;

            ChangeManager.Refresh(ChangeManager.DEFAULT_CHANGE);
            using (PerforceConnection con = new PerforceConnection())
            {
                UpdateList(con);
            }
			AssetStatusCache.OnAssetStatusChanged -= OnOperationPerformed;
			AssetStatusCache.OnAssetStatusChanged += OnOperationPerformed;

			_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();

			_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();

			_CurrentSortingFunc = FilenameSortingFunc;
			_CurrentSortingAscending = true;

			_CurrentInstance = this;
		}

		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 = new List<string>(ChangeManager.LocalOpenedFiles);
                //log.DebugFormat("openedFiles: {0}", Logger.StringArrayToString(openedFiles.ToArray()));

				if (openedFiles != null)
				{
					// Parse the list, seeing if files have metas and such
					while (openedFiles.Count > 0)
					{
						Profiler.BeginSample("UpdateList - count" + openedFiles.Count);
                        //log.DebugFormat("Main.Rootpath: {0} ", Main.RootPath);

						//if (openedFiles[0].StartsWith(Main.RootPath, !Utils.IsCaseSensitive(), null))
						//{
							string filePath = Utils.UnescapeFilename(openedFiles[0]);
                            // log.DebugFormat("filePath: {0}", filePath);

							PendingAssetAndMeta assetAndMeta = new PendingAssetAndMeta();
							if (filePath.EndsWith(".meta"))
							{
								// It's a meta file, is there an asset in the list as well?
								assetAndMeta.MetaPath = filePath;
								assetAndMeta.AssetPath = Utils.AssetFromMeta(filePath);
								int assetIndex = openedFiles.IndexOf(assetAndMeta.AssetPath);
								if (assetIndex != -1)
								{
									// Yes, there is an asset file
									assetAndMeta.Type = FileAndMetaType.FileAndMeta;

									// Remove both entries
									openedFiles.RemoveAt(assetIndex);
								}
								else
								{
									// No, this is a meta only
									assetAndMeta.Type = FileAndMetaType.MetaOnly;
								}
							}
							else
							{
								// It's not a meta file, is there a meta as well?
								assetAndMeta.AssetPath = filePath;
								assetAndMeta.MetaPath = Utils.MetaFromAsset(filePath);
								int metaIndex = openedFiles.IndexOf(Utils.EscapeFilename(assetAndMeta.MetaPath));
								if (metaIndex != -1)
								{
									// Yes, there is a meta file
									assetAndMeta.Type = FileAndMetaType.FileAndMeta;

									// Remove both entries
									openedFiles.RemoveAt(metaIndex);
								}
								else
								{
									// No, this is a file only
									assetAndMeta.Type = FileAndMetaType.FileOnly;
								}
							}

							// Check whether we knew about this file and wanted it selected
							if (!_CheckedFiles.ContainsKey(assetAndMeta.EffectiveFilePath))
							{
								// If we don't know the file, say that it's selected
								_CheckedFiles.Add(assetAndMeta.EffectiveFilePath, true);
							}

							// Finally, add the asset and meta to the list
                            //log.DebugFormat(" Adding to Pending Changes: {0}", assetAndMeta.EffectiveFilePath);
							_PendingChanges.Add(assetAndMeta);
						//}

						// No matter what, remove the file
						openedFiles.RemoveAt(0);

						Profiler.EndSample();
					}
					
					// 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.GetAssetData(con, paths, statuses);
					for (int i = 0; i < paths.Count; ++i)
					{
						_PendingChanges[i].State = statuses[i];
					}

					// Sort the changes
					SortPendingChanges();
				}
			}
			Profiler.EndSample();
		}

		void OnGUI()
		{
			CheckInit();
			Event evt = Event.current;

			// Header
			GUIStyle changelistStyle = new GUIStyle(EditorStyles.toolbarButton);
			changelistStyle.normal.textColor = Color.grey;
			changelistStyle.alignment = TextAnchor.MiddleLeft;

			// Buttons
			GUILayout.BeginHorizontal(EditorStyles.toolbar);
			EditorGUI.BeginDisabledGroup(!Config.ValidConfiguration);
			GUILayout.Label("Default Changelist: " + _PendingChanges.Count + " assets", changelistStyle, GUILayout.Width(200.0f));
			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("", 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();

			EditorGUI.BeginDisabledGroup(!Config.ValidConfiguration);
			GUILayout.BeginHorizontal();
			_ScrollVector = GUILayout.BeginScrollView(_ScrollVector);

			// Header
			GUIStyle HeaderStyle = new GUIStyle(EditorStyles.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);

			GUIStyle HeaderButtons = new GUIStyle(EditorStyles.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);

			GUIStyle ToggleStyle = new GUIStyle(EditorStyles.toggle);
			RectOffset margin = ToggleStyle.margin;
			margin.top = 0; margin.bottom = 0;
			ToggleStyle.margin = margin;

			GUIStyle 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);

			GUIStyle 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);

			GUIStyle 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);
			

			GUILayout.BeginHorizontal(HeaderStyle);
			{
				bool allSelected = _PendingChanges.Count(pc => _CheckedFiles[pc.EffectiveFilePath]) == _PendingChanges.Count;
				bool newAllSelected = GUILayout.Toggle(allSelected, "", ToggleStyle, GUILayout.Width(10.0f));
				if (newAllSelected != allSelected)
				{
					foreach (var pc in _PendingChanges)
					{
						_CheckedFiles[pc.EffectiveFilePath] = newAllSelected;
					}
				}
				if (GUILayout.Button("", HeaderButtons, GUILayout.Width(spaceBeforeIcon)))
				{
					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("", HeaderButtons, GUILayout.Width(iconSize)))
				{
					if (GUI.GetNameOfFocusedControl() == "DescriptionBox")
					{
						GUI.FocusControl("");
					}
					// Sort the files according to their extension
					if (_CurrentSortingFunc == ExtensionSortingFunc)
					{
						// Reverse the order if the sorting was already according to the extension
						_CurrentSortingAscending = !_CurrentSortingAscending;
					}
					else
					{
						// Make sorting according to the extension and reset the direction
						_CurrentSortingFunc = ExtensionSortingFunc;
						_CurrentSortingAscending = true;
					}
					SortPendingChanges();
				}
				if (GUILayout.Button("Filename", HeaderButtons, GUILayout.Width(250.0f)))
				{
					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(250.0f)))
				{
					if (GUI.GetNameOfFocusedControl() == "DescriptionBox")
					{
						GUI.FocusControl("");
					}
					if (_CurrentSortingFunc == DirectorySortingFunc)
					{
						_CurrentSortingAscending = !_CurrentSortingAscending;
					}
					else
					{
						_CurrentSortingFunc = DirectorySortingFunc;
						_CurrentSortingAscending = true;
					}
					SortPendingChanges();
				}
				//GUILayout.FlexibleSpace();
				if (GUILayout.Button("Type", HeaderButtons, GUILayout.Width(80.0f)))
				{
					if (GUI.GetNameOfFocusedControl() == "DescriptionBox")
					{
						GUI.FocusControl("");
					}
					if (_CurrentSortingFunc == TypeSortingFunc)
					{
						_CurrentSortingAscending = !_CurrentSortingAscending;
					}
					else
					{
						_CurrentSortingFunc = TypeSortingFunc;
						_CurrentSortingAscending = true;
					}
					SortPendingChanges();
				}
				if (GUILayout.Button("Change", HeaderButtons, GUILayout.Width(80.0f)))
				{
					if (GUI.GetNameOfFocusedControl() == "DescriptionBox")
					{
						GUI.FocusControl("");
					}
					if (_CurrentSortingFunc == StateSortingFunc)
					{
						_CurrentSortingAscending = !_CurrentSortingAscending;
					}
					else
					{
						_CurrentSortingFunc = StateSortingFunc;
						_CurrentSortingAscending = true;
					}
					SortPendingChanges();
				}
				GUILayout.Space(32.0f);
			}
			GUILayout.EndHorizontal();

			// Add all the items
			foreach (var change in _PendingChanges)
			{
				if (_LastSelectedChange == change && focusedWindow == this)
				{
					if (_CurrentSelectedChanges.Contains(_LastSelectedChange))
					{
						GUILayout.BeginHorizontal(highlightedStyle);
					}
					else
					{
						GUILayout.BeginHorizontal(normalStyle);
					}
				}
				else if (_CurrentSelectedChanges.Contains(change))
				{
					GUILayout.BeginHorizontal(selectedStyle);
				}
				else
				{
					GUILayout.BeginHorizontal(normalStyle);
				}

				// Draw a selection box
				bool itemChecked = _CheckedFiles[change.EffectiveFilePath];
				bool newItemChecked = GUILayout.Toggle(itemChecked, "", EditorStyles.toggle, GUILayout.Width(10.0f));
				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, GUILayout.Width(iconSize), GUILayout.Height(iconSize));

				// 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
				GUILayout.Label(System.IO.Path.GetFileName(change.AssetPath), GUILayout.Width(250.0f));
				GUILayout.Label(System.IO.Path.GetDirectoryName(change.AssetPath), GUILayout.MinWidth(250.0f));

				GUILayout.FlexibleSpace();

				// Now indicate if we have the file and/or the meta
				GUILayout.Label(Utils.GetStorageTypeString(change.State.StorageType), GUILayout.Width(80.0f));
				GUILayout.Label(Utils.GetFileStateString(change.State.LocalState), GUILayout.Width(80.0f));
				GUILayout.Label(Icons.GetFileAndMetaTypeIcon(change.Type), GUILayout.Width(iconSize), GUILayout.Height(iconSize));

				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);
				}

				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();
							}
						}
					}
				}
			}

			GUILayout.EndScrollView();
			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();
					}
				}
			}
		}

		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;
		}

		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("");
			}
		}

		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)
		{
			// 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;
				}
			}
		}
	}
}
# Change User Description Committed
#1 12954 anis_sg Populate -o //guest/perforce_software/p4connect/...
//guest/anis_sg/perforce_software/p4connect/....
//guest/perforce_software/p4connect/src/P4Connect/P4Connect/P4Connect.PendingChanges.cs
#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