using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEditor.IMGUI.Controls; using UnityEngine; using Codice.Client.Common; using Codice.CM.Common; using Codice.Utils; using PlasticGui; using PlasticGui.CloudDrive.Workspaces; using PlasticGui.WorkspaceWindow.Items; using Unity.PlasticSCM.Editor.CloudDrive.ShareWorkspace; using Unity.PlasticSCM.Editor.CloudDrive.Workspaces.DirectoryContent; using Unity.PlasticSCM.Editor.UI; using Unity.PlasticSCM.Editor.UI.Progress; using Unity.PlasticSCM.Editor.UI.Tree; using Unity.PlasticSCM.Editor.Views; #if UNITY_6000_2_OR_NEWER using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem; #endif namespace Unity.PlasticSCM.Editor.CloudDrive.Workspaces.Tree { internal class CloudWorkspacesTreeView : PlasticTreeView, FillCloudWorkspaces.ICloudWorkspacesTree, DirectoryContentPanel.ICloudWorkspacesTreeView, ICloudWorkspacesTreeMenuOperations { internal float Width { get { return mLastValidWidth; } } internal WorkspaceInfo WorkspaceToSelect { set { mWorkspaceToSelect = value; } } internal CloudWorkspacesTreeView( Action delayedSelectionChangedAction, IProgressControls progressControls, EditorWindow parentWindow) : base(showCustomBackground: false) { mDelayedSelectionChangedAction = delayedSelectionChangedAction; mProgressControls = progressControls; mParentWindow = parentWindow; mDelayedSelectionRunner = new DelayedActionBySecondsRunner( DelayedSelectionChanged, UnityConstants.SELECTION_DELAYED_INPUT_ACTION_INTERVAL); mMenu = new CloudWorkspacesTreeViewMenu(this); rowHeight = 16; } protected override bool CanChangeExpandedState(TreeViewItem item) { if (item is CloudWorkspacesLabelTreeViewItem) return false; return ((CloudWorkspacesTreeViewItem)item).IsExpandable; } protected override IList BuildRows(TreeViewItem rootItem) { RegenerateRows( mMyDrivesByWorkspace.Values.ToList(), mDrivesSharedWithMeByWorkspace.Values.ToList(), mTreeViewItemIds, this, rootItem, mRows); SelectWorkspaceIfNeeded(); if (GetSelectedNode() != null) DelayedSelectionChanged(); return mRows; } protected override float GetCustomRowHeight(int row, TreeViewItem item) { if (item is CloudWorkspacesLabelTreeViewItem) return 30f; return base.GetCustomRowHeight(row, item); } public override void OnGUI(Rect rect) { if (Event.current.type == EventType.Repaint && mLastValidWidth != rect.width) mLastValidWidth = rect.width; base.OnGUI(rect); if (!HasFocus()) return; Event e = Event.current; if (e.type != EventType.KeyDown) return; bool isProcessed = mMenu.ProcessKeyActionIfNeeded(e); if (isProcessed) e.Use(); } protected override void ContextClickedItem(int id) { mMenu.Popup(); Repaint(); } protected override void RowGUI(RowGUIArgs args) { if (args.item is CloudWorkspacesLabelTreeViewItem) { EditorGUI.LabelField( args.rowRect, ((CloudWorkspacesLabelTreeViewItem)args.item).Label, UnityStyles.Tree.BoldLabelWithMargin); return; } if (args.item is CloudWorkspacesTreeViewItem) { CloudWorkspacesTreeViewItem item = (CloudWorkspacesTreeViewItem)args.item; args.rowRect.width = mLastValidWidth; WorkspaceContentTreeViewItemGUI( args.rowRect, this, item, GetProgressControlsDataForNode( item.ExpandedTreeNode, mProgressControlsByWorkspace), args.selected, args.focused); return; } base.RowGUI(args); } protected override void SelectionChanged(IList selectedIds) { List selectedIdsToSet = new List(); foreach (int selectedId in selectedIds) { ExpandedTreeNode selectedNode; if (mTreeViewItemIds.TryGetItemById(selectedId, out selectedNode)) selectedIdsToSet.Add(selectedId); } SetSelection(selectedIdsToSet); mDelayedSelectionRunner.Run(); } protected override void ExpandedStateChanged() { IList expandedIds = GetExpanded(); ReloadExpandedTreeNodes( expandedIds.Except(mLastExpandedIds), mTreeViewItemIds, mMyDrivesByWorkspace, mDrivesSharedWithMeByWorkspace); mLastExpandedIds = expandedIds; } internal bool IsOperationRunning() { foreach (ProgressControlsForViews progressControls in mProgressControlsByWorkspace.Values) { if (progressControls.IsOperationRunning()) return true; } return false; } internal ProgressControlsForViews GetWorkspaceProgressControls(WorkspaceInfo wkInfo) { ProgressControlsForViews result; if (mProgressControlsByWorkspace.TryGetValue(wkInfo, out result)) return result; mProgressControlsByWorkspace[wkInfo] = new ProgressControlsForViews(); return mProgressControlsByWorkspace[wkInfo]; } internal WorkspaceInfo GetSelectedWorkspaceInfo() { ExpandedTreeNode selectedNode = GetSelectedNode(); if (selectedNode == null) return null; return selectedNode.WkInfo; } internal ExpandedTreeNode GetSelectedNode() { IList selectedIds = GetSelection(); if (selectedIds.Count != 1) return null; int selectedId = selectedIds[0]; ExpandedTreeNode result; if (!mTreeViewItemIds.TryGetItemById(selectedId, out result)) return null; return result; } internal List GetSelectedNodes() { List result = new List(); IList selectedIds = GetSelection(); foreach (int selectedId in selectedIds) { ExpandedTreeNode selectedNode; if (!mTreeViewItemIds.TryGetItemById(selectedId, out selectedNode)) continue; result.Add(selectedNode); } return result; } internal void Update(EditorWindow parentWindow) { foreach (ProgressControlsForViews progressControls in mProgressControlsByWorkspace.Values) { progressControls.UpdateProgress(parentWindow); } } void FillCloudWorkspaces.ICloudWorkspacesTree.ExpandedTreesRetrieved( List myDrives, List drivesSharedWithMe) { mMyDrivesByWorkspace = BuildExpandedTrees(myDrives); mDrivesSharedWithMeByWorkspace = BuildExpandedTrees(drivesSharedWithMe); BuildProgressControls( myDrives.Concat(drivesSharedWithMe), mProgressControlsByWorkspace); Reload(); } void DirectoryContentPanel.ICloudWorkspacesTreeView.SelectNode( string wkPath, string fullPath) { SelectNode(wkPath, fullPath); } int ICloudWorkspacesTreeMenuOperations.GetSelectedItemsCount() { return GetSelection().Count; } bool ICloudWorkspacesTreeMenuOperations.IsAnyNonRootItemSelected() { List selectedNodes = GetSelectedNodes(); foreach (ExpandedTreeNode node in selectedNodes) { if (!ExpandedTreeNode.IsRootNode(node)) return true; } return false; } bool ICloudWorkspacesTreeMenuOperations.IsAnySharedDriveSelected() { List selectedNodes = GetSelectedNodes(); foreach (ExpandedTreeNode node in selectedNodes) { if (mDrivesSharedWithMeByWorkspace.ContainsKey(node.WkInfo)) return true; } return false; } void ICloudWorkspacesTreeMenuOperations.OpenInExplorer() { List selectedNodes = GetSelectedNodes(); if (selectedNodes.Count != 1) return; FileSystemOperation.OpenInExplorer(selectedNodes[0].GetFullPath()); } void ICloudWorkspacesTreeMenuOperations.OpenUnityCloud() { List selectedNodes = GetSelectedNodes(); if (selectedNodes.Count != 1) return; WorkspaceInfo workspaceInfo = selectedNodes[0].WkInfo; RepositorySpec repSpec = PlasticGui.Plastic.API.GetRepositorySpec(workspaceInfo); string cloudServer = CloudServer.GetOrganizationName(repSpec.Server); RepositoryInfo repInfo = PlasticGui.Plastic.API.GetRepositoryInfo(repSpec); OpenBrowser.TryOpen(UnityUrl.UnityDashboard. CloudDrive.GetWorkspace(cloudServer, repInfo.GUID.ToString())); } void ICloudWorkspacesTreeMenuOperations.Delete() { List selectedNodes = GetSelectedNodes(); if (selectedNodes.Count != 1) return; WorkspaceInfo workspaceInfo = selectedNodes[0].WkInfo; ProgressControlsForViews workspaceProgressControls = GetWorkspaceProgressControls(workspaceInfo); if (workspaceProgressControls.IsOperationRunning()) { GuiMessage.ShowInformation( PlasticLocalization.Name.OperationInProgress.GetString()); return; } string workspaceName = Path.GetFileName(workspaceInfo.ClientPath); if (!DeleteWorkspaceDialog.UserConfirmsDelete(workspaceName, mParentWindow)) return; DeleteWorkspaceOperation.DeleteWorkspace( workspaceInfo, mProgressControls, Reload); } void ICloudWorkspacesTreeMenuOperations.Share() { List selectedNodes = GetSelectedNodes(); if (selectedNodes.Count != 1) return; WorkspaceInfo workspaceInfo = selectedNodes[0].WkInfo; ProgressControlsForViews workspaceProgressControls = GetWorkspaceProgressControls(workspaceInfo); if (workspaceProgressControls.IsOperationRunning()) { GuiMessage.ShowInformation( PlasticLocalization.Name.OperationInProgress.GetString()); return; } List collaboratorsToAdd; List collaboratorsToRemove; if (!ShareWorkspaceDialog.ShareWorkspace( workspaceInfo, mParentWindow, out collaboratorsToAdd, out collaboratorsToRemove)) return; WorkspaceShareOperations.ShareWorkspace( workspaceInfo, collaboratorsToAdd, mProgressControls); WorkspaceShareOperations.UnshareWorkspace( workspaceInfo, collaboratorsToRemove, mProgressControls); } void ICloudWorkspacesTreeMenuOperations.UnshareWithMe() { List selectedNodes = GetSelectedNodes(); if (selectedNodes.Count != 1) return; WorkspaceInfo workspaceInfo = selectedNodes[0].WkInfo; ProgressControlsForViews workspaceProgressControls = GetWorkspaceProgressControls(workspaceInfo); if (workspaceProgressControls.IsOperationRunning()) { GuiMessage.ShowInformation( PlasticLocalization.Name.OperationInProgress.GetString()); return; } if (!GuiMessage.ShowQuestion( PlasticLocalization.Name.UnshareWithMeMenuItem.GetString(), PlasticLocalization.Name.UnshareConfirmationMessage.GetString(), PlasticLocalization.Name.UnshareButton.GetString())) { return; } WorkspaceShareOperations.UnshareWorkspaceWithMe( workspaceInfo, mProgressControls, Reload); } void SelectWorkspaceIfNeeded() { if (mWorkspaceToSelect == null) return; int idToSelect; if (!mTreeViewItemIds.TryGetItemIdByKey(mWorkspaceToSelect.ClientPath, out idToSelect)) return; SetSelection(new List { idToSelect }); mWorkspaceToSelect = null; } void DelayedSelectionChanged() { if (mIsSelectionChangedEventDisabled) return; IList selectedIds = GetSelection(); if (selectedIds.Count == 1) ReloadExpandedTreeNodeIfChildrenNotLoaded( selectedIds[0], mTreeViewItemIds, mMyDrivesByWorkspace, mDrivesSharedWithMeByWorkspace); mDelayedSelectionChangedAction(); } void SelectNode(string wkPath, string fullPath) { if (ExpandAncestors(wkPath, fullPath, mTreeViewItemIds, this)) Reload(); int idToSelect; if (!mTreeViewItemIds.TryGetItemIdByKey(fullPath, out idToSelect)) return; mIsSelectionChangedEventDisabled = true; try { TableViewOperations.SetSelectionAndScroll( this, new List { idToSelect }); } finally { mIsSelectionChangedEventDisabled = false; } DelayedSelectionChanged(); } static void WorkspaceContentTreeViewItemGUI( Rect rowRect, CloudWorkspacesTreeView tree, CloudWorkspacesTreeViewItem item, ProgressControlsForViews.Data progressControlsData, bool isSelected, bool isFocused) { DrawTreeViewItem.ForIndentedItemWithIcon( rowRect, tree.rowHeight, item.depth, Path.GetFileName(item.ExpandedTreeNode.GetFullPath()), string.Empty, GetItemIcon(tree, item), isSelected, isFocused); if (progressControlsData == null || !progressControlsData.IsOperationRunning) return; DoProgress(rowRect, progressControlsData); } static void DoProgress( Rect rowRect, ProgressControlsForViews.Data progressControlsData) { GUILayout.BeginArea(rowRect); GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); LoadingSpinner.OnGUI( progressControlsData.ProgressPercent, progressControlsData.ProgressMessage); GUILayout.Space(2); GUILayout.EndHorizontal(); GUILayout.EndArea(); } static void ReloadExpandedTreeNodes( IEnumerable idsToReload, TreeViewItemIds treeViewItemIds, Dictionary myDrivesByWorkspace, Dictionary drivesSharedWithMeByWorkspace) { foreach (int id in idsToReload) { ExpandedTreeNode node; if (!treeViewItemIds.TryGetItemById(id, out node)) continue; ExpandedTree expandedTree; if (!myDrivesByWorkspace.TryGetValue(node.WkInfo, out expandedTree) && !drivesSharedWithMeByWorkspace.TryGetValue(node.WkInfo, out expandedTree)) continue; expandedTree.ReloadNode(node); } } static void ReloadExpandedTreeNodeIfChildrenNotLoaded( int idToReload, TreeViewItemIds treeViewItemIds, Dictionary myDrivesByWorkspace, Dictionary drivesSharedWithMeByWorkspace) { ExpandedTreeNode node; if (!treeViewItemIds.TryGetItemById(idToReload, out node)) return; if (node.GetChildren() != null) return; ExpandedTree expandedTree; if (!myDrivesByWorkspace.TryGetValue(node.WkInfo, out expandedTree) && !drivesSharedWithMeByWorkspace.TryGetValue(node.WkInfo, out expandedTree)) return; expandedTree.ReloadNode(node); } static bool ExpandAncestors( string wkPath, string nodePath, TreeViewItemIds treeViewItemIds, CloudWorkspacesTreeView treeView) { List ancestors = GetAncestorsPaths(wkPath, nodePath); bool result = false; foreach (string ancestor in ancestors) { int ancestorId; if (!treeViewItemIds.TryGetItemIdByKey(ancestor, out ancestorId)) continue; if (treeView.IsExpanded(ancestorId)) continue; treeView.SetExpanded(ancestorId, true); result = true; } return result; } static void RegenerateRows( List myDrives, List drivesSharedWithMe, TreeViewItemIds treeViewItemIds, CloudWorkspacesTreeView treeView, TreeViewItem rootItem, List rows) { ClearRows(rootItem, rows); treeViewItemIds.ClearItems(); AddLabelRow(PlasticLocalization.Name.MyDrivesLabel.GetString(), treeViewItemIds, rows); AddExpandedTrees(myDrives, treeViewItemIds, treeView, rootItem, rows); AddLabelRow( PlasticLocalization.Name.DrivesSharedWithMeLabel.GetString(), treeViewItemIds, rows); AddExpandedTrees(drivesSharedWithMe, treeViewItemIds, treeView, rootItem, rows); } static void AddExpandedTrees( List expandedTrees, TreeViewItemIds treeViewItemIds, CloudWorkspacesTreeView treeView, TreeViewItem rootItem, List rows) { foreach (ExpandedTree expandedTree in expandedTrees) { ExpandedTreeNode rootNode = expandedTree.GetRootNode(); if (rootNode == null) continue; AddNode( rootNode, expandedTree, treeViewItemIds, treeView, rootItem, rows); } } static void AddLabelRow( string label, TreeViewItemIds treeViewItemIds, List rows) { int nodeId; if (!treeViewItemIds.TryGetItemIdByKey(label, out nodeId)) nodeId = treeViewItemIds.AddItemIdByKey(label); rows.Add(new CloudWorkspacesLabelTreeViewItem(nodeId, label)); } static void AddNode( ExpandedTreeNode node, ExpandedTree expandedTree, TreeViewItemIds treeViewItemIds, CloudWorkspacesTreeView treeView, TreeViewItem parentItem, List rows) { if (!ExpandedTreeNode.IsOnDisk(node)) return; int nodeId; if (!treeViewItemIds.TryGetItemIdByKey(node.GetFullPath(), out nodeId)) nodeId = treeViewItemIds.AddItemIdByKey(node.GetFullPath()); treeViewItemIds.AddItemById(nodeId, node); CloudWorkspacesTreeViewItem viewItem = new CloudWorkspacesTreeViewItem( nodeId, node, Directory.GetDirectories(node.GetFullPath()).Length > 0, parentItem.depth + 1); rows.Add(viewItem); parentItem.AddChild(viewItem); if (!treeView.IsExpanded(nodeId)) return; if (node.GetChildren() == null) expandedTree.ReloadNode(node); foreach (ExpandedTreeNode childNode in node.GetChildren()) { if (!ExpandedTreeNode.IsDirectory(childNode)) continue; AddNode( childNode, expandedTree, treeViewItemIds, treeView, viewItem, rows); } } static void ClearRows( TreeViewItem rootItem, List rows) { if (rootItem.hasChildren) rootItem.children.Clear(); rows.Clear(); } static Texture GetItemIcon( CloudWorkspacesTreeView tree, CloudWorkspacesTreeViewItem item) { if (item.parent.id == 0) return Images.GetCloudWorkspaceIcon(); if (tree.IsExpanded(item.id)) return Images.GetFolderOpenedIcon(); return Images.GetFolderIcon(); } static Dictionary BuildExpandedTrees( List expandedTrees) { Dictionary result = new Dictionary(); foreach (ExpandedTree expandedTree in expandedTrees) result[expandedTree.WkInfo] = expandedTree; return result; } static void BuildProgressControls( IEnumerable expandedTrees, Dictionary progressControlsByWorkspace) { List workspaceInfos = new List(); foreach (ExpandedTree expandedTree in expandedTrees) workspaceInfos.Add(expandedTree.GetRootNode().WkInfo); for (int i = progressControlsByWorkspace.Keys.Count - 1; i >= 0; i--) { WorkspaceInfo wkInfo = progressControlsByWorkspace.Keys.ElementAt(i); if (!workspaceInfos.Contains(wkInfo)) progressControlsByWorkspace.Remove(wkInfo); } foreach (WorkspaceInfo wkInfo in workspaceInfos) { if (progressControlsByWorkspace.ContainsKey(wkInfo)) continue; progressControlsByWorkspace[wkInfo] = new ProgressControlsForViews(); } } static List GetAncestorsPaths( string wkPath, string nodePath) { List ancestors = new List(); while (!PathHelper.IsSamePath(wkPath, nodePath)) { nodePath = Directory.GetParent(nodePath).FullName; ancestors.Add(nodePath); } ancestors.Reverse(); return ancestors; } static ProgressControlsForViews.Data GetProgressControlsDataForNode( ExpandedTreeNode node, Dictionary progressControlsByWorkspace) { if (!ExpandedTreeNode.IsRootNode(node)) return null; ProgressControlsForViews progressControls; if (!progressControlsByWorkspace.TryGetValue( node.WkInfo, out progressControls)) return null; return progressControls.ProgressData; } float mLastValidWidth; bool mIsSelectionChangedEventDisabled = false; IEnumerable mLastExpandedIds = new List(); Dictionary mMyDrivesByWorkspace = new Dictionary(); Dictionary mDrivesSharedWithMeByWorkspace = new Dictionary(); WorkspaceInfo mWorkspaceToSelect; readonly CloudWorkspacesTreeViewMenu mMenu; readonly DelayedActionBySecondsRunner mDelayedSelectionRunner; readonly Action mDelayedSelectionChangedAction; readonly IProgressControls mProgressControls; readonly EditorWindow mParentWindow; readonly TreeViewItemIds mTreeViewItemIds = new TreeViewItemIds(); readonly Dictionary mProgressControlsByWorkspace = new Dictionary(); } }