// // Copyright 2014 Perforce Software Inc. // using Perforce.ViewModel; using Perforce.Model; using System; using System.Collections.ObjectModel; using System.Windows; using System.Windows.Controls; using Perforce.P4; using System.Collections; using System.Linq; using System.Collections.Generic; namespace Perforce.Helper { public class ContextMenuHelper { public static ObservableCollection<FrameworkElement> GetContextMenu(ListingItem item) { Log.TraceFunction(); var items = item.ColumnContext.Listing.SelectedItems; var contextMenu = new ObservableCollection<FrameworkElement>(); if (items.Count > 1) { contextMenu.Add(new MenuItem { Header = "<multiple selection>", IsEnabled = false }); } else if (items.Count == 1) { contextMenu.Add(new MenuItem { Header = (items[0] as ListingItem).LabelText, IsEnabled = false, FontWeight = FontWeights.Bold }); } else { return null; } contextMenu.Add(new Separator()); switch (item.ColumnContext.Selector.SelectorType) { case SELECTOR_TYPE.WORKSPACE: ConfigureWorkspaceContextMenu(contextMenu, items); break; case SELECTOR_TYPE.SERVER: case SELECTOR_TYPE.SEARCH: ConfigureServerContextMenu(contextMenu, items); break; case SELECTOR_TYPE.PENDING: ConfigurePendingContextMenu(contextMenu, items); break; case SELECTOR_TYPE.RECENT: ConfigureRecentContextMenu(contextMenu, items); break; case SELECTOR_TYPE.TRASH: ConfigureTrashContextMenu(contextMenu, items); break; case SELECTOR_TYPE.FAVORITE: var fav = (item.ColumnContext.Selector.Model as FavoritesViewModel).Item; if (fav.Selector == SELECTOR_TYPE.WORKSPACE) { ConfigureWorkspaceContextMenu(contextMenu, items); } else { ConfigureServerContextMenu(contextMenu, items); } break; } return contextMenu; } private static void ConfigureTrashContextMenu(ObservableCollection<FrameworkElement> contextMenu, IList items) { Log.TraceFunction(); if (items.Count == 1) { var item = items[0] as ListingItem; if (item.Type.Equals(ListingItem.TYPE.FILE)) { var file = item as FileItem; if (MetadataHelper.CanRevert(file)) { contextMenu.Add(new MenuItem { Header = "Undo trash", InputGestureText="Ctrl+R", IsEnabled = true, Command = new DelegateCommand(RevertFile), CommandParameter = file }); } contextMenu.Add(new MenuItem { Header = "Submit file", IsEnabled = true, Command = new DelegateCommand(SubmitFiles), CommandParameter = file }); } } else { contextMenu.Add(new MenuItem { Header = "Undo trash (all selected)", InputGestureText="Ctrl+R", IsEnabled = true, Command = new DelegateCommand(RevertFile), CommandParameter = items }); contextMenu.Add(new MenuItem { Header = "Submit selected files", InputGestureText="Ctrl+S", IsEnabled = true, Command = new DelegateCommand(SubmitFiles), CommandParameter = items }); } } private static void ConfigureRecentContextMenu(ObservableCollection<FrameworkElement> contextMenu, IList items) { Log.TraceFunction(); if (items.Count == 1) { var item = items[0] as ListingItem; if (item.Type.Equals(ListingItem.TYPE.FILE)) { var file = item as FileItem; var editable = MetadataHelper.CanEdit(file); var revertable = MetadataHelper.CanRevert(file); contextMenu.Add(new MenuItem { Header = "Open", IsEnabled = editable, Command = new DelegateCommand(OpenWorkspaceFile), CommandParameter = file }); contextMenu.Add(new MenuItem { Header = "Open readonly", IsEnabled = true, Command = new DelegateCommand(ViewWorkspaceFile), CommandParameter = file }); if (editable) { contextMenu.Add(new MenuItem { Header = "Edit file", IsEnabled = true, Command = new DelegateCommand(CheckoutFile), CommandParameter = file }); } if (revertable) { contextMenu.Add(new MenuItem { Header = "Revert changes", InputGestureText="Ctrl+R", IsEnabled = true, Command = new DelegateCommand(RevertFile), CommandParameter = file }); } contextMenu.Add(new MenuItem { Header = "Mark as read", IsEnabled = true, Command = new DelegateCommand(MarkRead), CommandParameter = file }); } else { // should never see folders in the recent list } contextMenu.Add(new MenuItem { Header = "Show in Explorer", IsEnabled = true, Command = new DelegateCommand(ShowInExplorer), CommandParameter = item }); } else { contextMenu.Add(new MenuItem { Header = "Revert selected files", InputGestureText="Ctrl+R", IsEnabled = true, Command = new DelegateCommand(RevertFile), CommandParameter = items }); contextMenu.Add(new MenuItem { Header = "Mark selected read", IsEnabled = true, Command = new DelegateCommand(MarkRead), CommandParameter = items }); } } private static void ConfigurePendingContextMenu(ObservableCollection<FrameworkElement> contextMenu, IList items) { Log.TraceFunction(); if (items.Count == 1) { var item = items[0] as ListingItem; if (item.Type.Equals(ListingItem.TYPE.FILE)) { var file = item as FileItem; var editable = MetadataHelper.CanEdit(file); var revertable = MetadataHelper.CanRevert(file); contextMenu.Add(new MenuItem { Header = "Open", IsEnabled = editable, Command = new DelegateCommand(OpenWorkspaceFile), CommandParameter = file }); contextMenu.Add(new MenuItem { Header = "Open readonly", IsEnabled = true, Command = new DelegateCommand(ViewWorkspaceFile), CommandParameter = file }); if (editable) { contextMenu.Add(new MenuItem { Header = "Edit file", IsEnabled = true, Command = new DelegateCommand(CheckoutFile), CommandParameter = file }); contextMenu.Add(new MenuItem { Header = "Trash", IsEnabled = editable, Command = new DelegateCommand(DeleteFile), CommandParameter = file }); } if (revertable) { contextMenu.Add(new MenuItem { Header = "Revert changes", InputGestureText="Ctrl+R", IsEnabled = true, Command = new DelegateCommand(RevertFile), CommandParameter = file }); } contextMenu.Add(new MenuItem { Header = "Submit file", InputGestureText="Ctrl+S", IsEnabled = true, Command = new DelegateCommand(SubmitFiles), CommandParameter = file }); } else { // should never see folders in the pending list } contextMenu.Add(new MenuItem { Header = "Show in Explorer", IsEnabled = true, Command = new DelegateCommand(ShowInExplorer), CommandParameter = item }); } else { contextMenu.Add(new MenuItem { Header = "Revert selected files", InputGestureText="Ctrl+R", IsEnabled = true, Command = new DelegateCommand(RevertFile), CommandParameter = items }); contextMenu.Add(new MenuItem { Header = "Submit selected files", InputGestureText="Ctrl+S", IsEnabled = true, Command = new DelegateCommand(SubmitFiles), CommandParameter = items }); } } private static void ConfigureServerContextMenu(ObservableCollection<FrameworkElement> contextMenu, IList items) { Log.TraceFunction(); if (items.Count == 1) { var item = items[0] as ListingItem; var p4 = Utility.GetPerforceHelper(); if (item.Type.Equals(ListingItem.TYPE.FILE)) { var file = item as FileItem; var editable = MetadataHelper.CanEdit(file); contextMenu.Add(new MenuItem { Header = "Open", IsEnabled = editable, Command = new DelegateCommand(OpenWorkspaceFile), CommandParameter = file }); contextMenu.Add(new MenuItem { Header = "Open readonly", IsEnabled = editable, Command = new DelegateCommand(ViewWorkspaceFile), CommandParameter = file }); contextMenu.Add(new MenuItem { Header = "Open file from server", IsEnabled = true, Command = new DelegateCommand(OpenFromServer), CommandParameter = file }); contextMenu.Add(new Separator()); contextMenu.Add(new MenuItem { Header = "Show in Explorer", IsEnabled = item.IsMapped, Command = new DelegateCommand(ShowInExplorer), CommandParameter = file }); contextMenu.Add(new MenuItem { Header = "Show Versions", IsEnabled = true, Command = new DelegateCommand(ShowVersions), CommandParameter = file }); contextMenu.Add(new MenuItem { Header = "Copy link", IsEnabled = true, Command = new DelegateCommand(CopyLink), CommandParameter = file }); contextMenu.Add(new MenuItem { Header = "Trash", IsEnabled = editable, Command = new DelegateCommand(DeleteFile), CommandParameter = file }); } else { var folder = item as FolderItem; if (p4.ClientFolderMappingContains(item.DepotPath)) { contextMenu.Add(new MenuItem { Header = "Unsync from computer", IsEnabled = true, Command = new DelegateCommand(UnsyncFromComputer), CommandParameter = folder }); } else if (!folder.IsMapped) { contextMenu.Add(new MenuItem { Header = "Sync with computer", IsEnabled = true, Command = new DelegateCommand(SyncWithComputer), CommandParameter = folder }); } contextMenu.Add(new MenuItem { Header = "Add to Favorites", IsEnabled = true, Command = new DelegateCommand(AddToFavorites), CommandParameter = folder }); contextMenu.Add(new MenuItem { Header = "Show in Explorer", IsEnabled = folder.IsMapped, Command = new DelegateCommand(ShowInExplorer), CommandParameter = folder }); contextMenu.Add(new MenuItem { Header = "Show Folder History", IsEnabled = folder.IsMapped, Command = new DelegateCommand(ShowFolderHistory), CommandParameter = folder }); contextMenu.Add(new MenuItem { Header = "Copy link", IsEnabled = true, Command = new DelegateCommand(CopyLink), CommandParameter = folder }); contextMenu.Add(new MenuItem { Header = "Trash", IsEnabled = folder.IsMapped, Command = new DelegateCommand(DeleteFile), CommandParameter = folder }); } } else { // multiple selection } } private static void ConfigureWorkspaceContextMenu(ObservableCollection<FrameworkElement> contextMenu, IList items) { Log.TraceFunction(); if (items.Count == 1) { var item = items[0] as ListingItem; if (item.Type.Equals(ListingItem.TYPE.FILE)) { var file = item as FileItem; var editable = MetadataHelper.CanEdit(file); var revertable = MetadataHelper.CanRevert(file); contextMenu.Add(new MenuItem { Header = "Open", IsEnabled = editable, Command = new DelegateCommand(OpenWorkspaceFile), CommandParameter = file }); contextMenu.Add(new MenuItem { Header = "Open readonly", IsEnabled = true, Command = new DelegateCommand(ViewWorkspaceFile), CommandParameter = file }); if (editable) { contextMenu.Add(new MenuItem { Header = "Edit file", IsEnabled = true, Command = new DelegateCommand(CheckoutFile), CommandParameter = file }); } if (revertable) { contextMenu.Add(new MenuItem { Header = "Revert changes", InputGestureText="Ctrl+R", IsEnabled = true, Command = new DelegateCommand(RevertFile), CommandParameter = file }); contextMenu.Add(new MenuItem { Header = "Submit file", InputGestureText="Ctrl+S", IsEnabled = true, Command = new DelegateCommand(SubmitFiles), CommandParameter = file }); } contextMenu.Add(new MenuItem { Header = "Show Versions", IsEnabled = item.IsMapped, Command = new DelegateCommand(ShowVersions), CommandParameter = file }); contextMenu.Add(new MenuItem { Header = "Show in Explorer", IsEnabled = true, Command = new DelegateCommand(ShowInExplorer), CommandParameter = file }); contextMenu.Add(new MenuItem { Header = "Copy link", IsEnabled = item.IsMapped, Command = new DelegateCommand(CopyLink), CommandParameter = file }); contextMenu.Add(new MenuItem { Header = "Trash", IsEnabled = editable, Command = new DelegateCommand(DeleteFile), CommandParameter = file }); } else { var folder = item as FolderItem; contextMenu.Add(new MenuItem { Header = "Edit all files", IsEnabled = folder.IsMapped, Command = new DelegateCommand(CheckoutFile), CommandParameter = folder }); contextMenu.Add(new MenuItem { Header = "Revert all files", InputGestureText="Ctrl+R", IsEnabled = folder.IsMapped, Command = new DelegateCommand(RevertFile), CommandParameter = folder }); contextMenu.Add(new MenuItem { Header = "Submit all files", InputGestureText="Ctrl+S", IsEnabled = folder.IsMapped, Command = new DelegateCommand(SubmitFolder), CommandParameter = folder }); contextMenu.Add(new MenuItem { Header = "Add to Favorites", IsEnabled = true, Command = new DelegateCommand(AddToFavorites), CommandParameter = folder }); contextMenu.Add(new MenuItem { Header = "Show in Explorer", IsEnabled = folder.IsMapped, Command = new DelegateCommand(ShowInExplorer), CommandParameter = folder }); contextMenu.Add(new MenuItem { Header = "Reconcile Files", IsEnabled = folder.IsMapped, Command = new DelegateCommand(ReconcileFiles), CommandParameter = folder }); contextMenu.Add(new MenuItem { Header = "Show Folder History", IsEnabled = folder.IsMapped, Command = new DelegateCommand(ShowFolderHistory), CommandParameter = folder }); contextMenu.Add(new MenuItem { Header = "Copy link", IsEnabled = true, Command = new DelegateCommand(CopyLink), CommandParameter = folder }); contextMenu.Add(new MenuItem { Header = "Trash", IsEnabled = folder.IsMapped, Command = new DelegateCommand(DeleteFile), CommandParameter = folder }); } } else if (items.Count > 1) { // multiple selections if (AllFiles(items)) { contextMenu.Add(new MenuItem { Header = "Edit selected files", IsEnabled = true, Command = new DelegateCommand(CheckoutFile), CommandParameter = items }); contextMenu.Add(new MenuItem { Header = "Revert selected files", InputGestureText="Ctrl+R", IsEnabled = true, Command = new DelegateCommand(RevertFile), CommandParameter = items }); } else { contextMenu.Add(new MenuItem { Header = "Edit selected files", IsEnabled = true, Command = new DelegateCommand(CheckoutFile), CommandParameter = items }); contextMenu.Add(new MenuItem { Header = "Revert selected files", InputGestureText="Ctrl+R", IsEnabled = true, Command = new DelegateCommand(RevertFile), CommandParameter = items }); } } } private static bool AllFiles(IList items) { var allFiles = false; foreach (var item in items) { if((item as ListingItem).Type.Equals(ListingItem.TYPE.FOLDER)) { break; } allFiles = true; } return allFiles; } #region COMMANDS private static void SyncWithComputer(object parameter) { Log.TraceFunction(); ListingItem[] items = null; if (parameter is ListingItem) { items = new ListingItem[1]; items[0] = parameter as ListingItem; } else if (parameter is IList) { var list = parameter as IList; items = list.Cast<ListingItem>().ToArray(); } if(items != null && items.Length > 0) { UIHelper.SetBusyState(); Utility.StopBackgroundProcesses(); var helper = Utility.GetPerforceHelper(); var main = App.Current.MainWindow as MainWindow; var waitDialog = main.DialogManager.CreateWaitDialog("Synchronizing files", string.Empty, Technewlogic.WpfDialogManagement.DialogMode.Ok); waitDialog.VerticalDialogAlignment = VerticalAlignment.Top; waitDialog.Show(() => { foreach (var item in items) { var path = item.DepotPath; if (item is FolderItem) { path += "/..."; } helper.ClientFolderMappingAdd(path); var result = helper.RunSync(path); if (result != null && result.Count > 0) { waitDialog.ReadyMessage = string.Format("{0} files synchronized from server", result.Count); } else { waitDialog.ReadyMessage = "No files synchronized from server"; } item.Refresh(); } Utility.StartBackgroundProcesses(); UIHelper.RefreshSelectorAsync(SELECTOR_TYPE.WORKSPACE); }); } } private static void UnsyncFromComputer(object parameter) { Log.TraceFunction(); ListingItem[] items = null; if (parameter is ListingItem) { items = new ListingItem[1]; items[0] = parameter as ListingItem; } else if (parameter is IList) { var list = parameter as IList; items = list.Cast<ListingItem>().ToArray(); } if (items != null && items.Length > 0) { UIHelper.SetBusyState(); var helper = Utility.GetPerforceHelper(); var main = App.Current.MainWindow as MainWindow; foreach (var item in main.CurrentSelection) { var path = item.DepotPath; if (item is FolderItem) { path += "/..."; } helper.RunSync(path, "#0"); helper.ClientFolderMappingRemove(path); item.Refresh(); } UIHelper.RefreshSelectorAsync(SELECTOR_TYPE.WORKSPACE); } } private static void OpenWorkspaceFile(object parameter) { Log.TraceFunction(); if (parameter is FileItem) { var item = parameter as FileItem; CommandHelper.CheckoutAndOpenFile(item); item.Refresh(); UIHelper.RefreshCurrent(); UIHelper.RefreshSelector(SELECTOR_TYPE.PENDING); } } private static void ViewWorkspaceFile(object parameter) { Log.TraceFunction(); if (parameter is FileItem) { var item = parameter as FileItem; CommandHelper.OpenLocalFile(item.ClientPath); } } private static void OpenFromServer(object parameter) { Log.TraceFunction(); if (parameter is FileItem) { var item = parameter as FileItem; CommandHelper.OpenFileFromServer(item.DepotPath); } } private static void AddToFavorites(object parameter) { Log.TraceFunction(); if (parameter is FolderItem) { var item = parameter as FolderItem; CommandHelper.AddToFavorites(item); } } private static void ShowInExplorer(object parameter) { Log.TraceFunction(); if (parameter is ListingItem) { var item = parameter as ListingItem; CommandHelper.ShowFileInExplorer(item.ClientPath); } } private static void CheckoutFile(object parameter) { Log.TraceFunction(); var helper = Utility.GetPerforceHelper(); if (parameter is ListingItem) { if (parameter is FolderItem) { var folder = parameter as FolderItem; if (helper.PathHasAnyOpened(folder.ClientPath + "/...")) { UIHelper.ShowMessage(string.Format("Sorry, {0} has locked files", folder.ClientPath)); return; } } var item = parameter as ListingItem; CommandHelper.CheckoutFiles(items: item); item.Refresh(); } else if (parameter is IList) { var list = parameter as IList; var items = list.Cast<ListingItem>().ToArray(); CommandHelper.CheckoutFiles(items: items); foreach (var item in items) { item.Refresh(); } } UIHelper.RefreshSelector(SELECTOR_TYPE.PENDING); } private static void DeleteFile(object parameter) { Log.TraceFunction(); if (parameter is ListingItem) { var item = parameter as ListingItem; CommandHelper.DeleteFiles(item); item.Refresh(); } else if (parameter is IList) { var list = parameter as IList; var items = list.Cast<ListingItem>().ToArray(); CommandHelper.DeleteFiles(items); foreach (var item in items) { item.Refresh(); } } UIHelper.RefreshCurrent(); } private static void RevertFile(object parameter) { Log.TraceFunction(); if (parameter is ListingItem) { var item = parameter as ListingItem; CommandHelper.RevertFiles(item); item.Refresh(); } else if (parameter is IList) { var list = parameter as IList; var items = list.Cast<ListingItem>().ToArray(); CommandHelper.RevertFiles(items); foreach (var item in items) { item.Refresh(); } } } private static void MarkRead(object parameter) { Log.TraceFunction(); var main = App.Current.MainWindow as MainWindow; var set = Utility.GetRecentSet(); if (parameter is ListingItem) { var item = parameter as ListingItem; CommandHelper.RevertFiles(item); item.Refresh(); set.Remove(item); } else if (parameter is IList) { var list = parameter as IList; var items = list.Cast<ListingItem>().ToArray(); CommandHelper.RevertFiles(items); foreach (var item in items) { item.Refresh(); set.Remove(item); } } UIHelper.RefreshSelector(SELECTOR_TYPE.RECENT); } private static void ReconcileFiles(object parameter) { Log.TraceFunction(); ListingItem[] items = null; if (parameter is ListingItem) { items = new ListingItem[1]; items[0] = parameter as ListingItem; } else if (parameter is IList) { var list = parameter as IList; items = list.Cast<ListingItem>().ToArray(); } var main = App.Current.MainWindow as MainWindow; var waitDialog = main.DialogManager.CreateWaitDialog("Reconciling files (this may take a while)", string.Empty, Technewlogic.WpfDialogManagement.DialogMode.Ok); waitDialog.VerticalDialogAlignment = VerticalAlignment.Top; waitDialog.Show(() => { var result = CommandHelper.ReconcileFiles(items); if (result != null && result.Success) { waitDialog.ReadyMessage = "Reconcile complete"; } else { waitDialog.ReadyMessage = "Reconcile failed"; } }); main.RefreshCurrent(); } private static void SubmitFolder(object parameter) { Log.TraceFunction(); if (parameter is FolderItem) { var folder = parameter as FolderItem; CommandHelper.SubmitFolder(folder); } } private static void SubmitFiles(object parameter) { Log.TraceFunction(); var helper = Utility.GetPerforceHelper(); var items = new List<string>(); if (parameter is ListingItem) { var itm = parameter as ListingItem; items.Add(itm.DepotPath); var md = helper.RunFstat(itm.DepotPath); if (md != null) { if (md.Action == FileAction.MoveAdd) { items.Add(md.MovedFile.Path); } else if (md.Action == FileAction.MoveDelete) { items.Add(md.MovedFile.Path); } } } else if (parameter is IList) { var list = parameter as IList; foreach (var i in list) { var itm = i as ListingItem; items.Add(itm.DepotPath); var md = helper.RunFstat(itm.DepotPath); if (md != null) { if (md.Action == FileAction.MoveAdd) { if (!items.Contains(md.MovedFile.Path)) { items.Add(md.MovedFile.Path); } } else if (md.Action == FileAction.MoveDelete) { if (!items.Contains(md.MovedFile.Path)) { items.Add(md.MovedFile.Path); } } } } } if (items.Count > 0) { CommandHelper.SubmitChangelist(selected: items.ToArray()); } } private static void ShowFolderHistory(object parameter) { Log.TraceFunction(); if (parameter is FolderItem) { var folder = parameter as FolderItem; var main = App.Current.MainWindow as MainWindow; main.DisplayCurrentFolderHistory(folder); } } private static void CopyLink(object parameter) { Log.TraceFunction(); if (parameter is ListingItem) { var item = parameter as ListingItem; if (item.DepotPath != null) { Clipboard.SetText(string.Format("p4:{0}", Utility.UrlEncodePath(item.DepotPath))); UIHelper.ShowTimedMessage("URL copied to clipboard", timeout: 0.5); } } } private static void ShowVersions(object parameter) { Log.TraceFunction(); if (parameter is FileItem) { var item = parameter as FileItem; var main = App.Current.MainWindow as MainWindow; main.DisplayCurrentItemVersionInfo(item); } } #endregion } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 16507 | perforce_software | Move to main branch. | ||
//guest/perforce_software/piper/windows/R1.1/Perforce/Helper/ContextMenuHelper.cs | |||||
#3 | 16474 | Robert Cowham | Bring up-to-date with //guest/perforce_software/piper/windows/main/...@16473 | ||
#2 | 13572 | alan_petersen | updating R1.1 | ||
#1 | 11256 | alan_petersen |
Populate //guest/perforce_software/piper/windows/R1.1/... from //guest/perforce_software/piper/windows/main/.... |
||
//guest/perforce_software/piper/windows/main/Perforce/Helper/ContextMenuHelper.cs | |||||
#1 | 11255 | alan_petersen | Rename/move file(s) | ||
//guest/perforce_software/piper/windows/Perforce/Helper/ContextMenuHelper.cs | |||||
#5 | 11032 | alan_petersen |
MEGA UPDATE: - fixed bug in folder rollback was failing due to deleted files - files needed to be re-added with the downgrade (-d) flag - put some checking in to stop/start the background workers when - folder versioning - can select 'show versions' on mapped folders - currently limites the number of changelists displayed to the last 50 - rollback button is disabled/hidden unless there are no files in the path (//path/to/folder/...) that are opened (uses p4 opened -a path) - various fixes for some strange null pointer exceptions - fixed folder items (edit all/revert all) - needs some testing to ensure that it is functioning correctly (e.g. when there are locked files in the path being checked out) - general code clean-up -- primarily using the Utility method to obtain the PerforceHelper, rather than having to cast all the time. - found some stability issues (at least when communicating with a local p4d running in the same virtual machine) so some additional error checking/handling was added - reconcile files action now provides feedback via a wait dialog: while reconcile is running, the dialog is displayed along with a wait indicator, when complete a message is displayed that the reconcile is finished - submit changes - submit now perfomed within a wait dialog: dialog displays message along with a wait indicator, results of the submit are displayed when complete - currently, the 'ok' button is disabled until the submit completes. - looking into providing a progress bar (using the feedback mechanism in the API) to provide some more interesting information (and to give users the sense that something is actually happening when a large changelist is begin submitted) - added copy function to PerforceHelper along with test case - implemented copy (ctrl-c) and paste (ctrl-v) in DESI - limitations: - currently only implemented for FILES (not folders) - to paste, one must select the destination FOLDER - next steps: - get working for folders - implement cut (ctrl-x) - permit paste into a column (more intuitive) - rebuilt using 'Any CPU' target -- not sure if this will fix the error seen by the Win7-64 user -- we may need to switch to separate 32- and 64-bit builds - fixed defect #78: delete and move/delete files are no longer displayed in the server view - fixed defect #76: tags now displayed in version history - added MemoryCache for repository reference -- the repository reference was getting stale and creating strange errors, so a reference is now stored in the MemoryCache with a SlidingExpiration of 2 minutes. - fixes #80: copy checks out file -- remnant of code from the rename logic was checking out the code - fixes #14: mail now opens outlook using office API - fixes #72: submit dialog now waits for results from submit, and if there is an error the error message is displayed, otherwise the changelist number is displayed - fixes #75: folder versioning changes -- restore version button now always displayed, but not connected to anything if the folder has opened items; -- opened items now ignored items marked for Add -- this could result in the folder being re-created if/when the user submits the added files - fixed bug in submit dialog in which changelist number was not displayed correctly - fixed bug in submission of partial changelists -- files moved to new changelist but original not updated, resulting in error - #79 - add file now works correctly, relying on the client path rather than the //dummy_depot_path #41: various changes and refactoring to support 'Recent' view - background sync causes immediate refresh of the recent list - ability to mark items as 'read' -- removed from the recent list and the view is refreshed #43 - udpates to logic to deal with transient threading issues (causing the NoSuchObject exceptions) and sync/refresh of workspace files to added files. #48: submit single file now working -- when submit file is selected, the file is moved to a shiny new changelist, and that changelist is presented in the UI - refactoring of submit pane logic to allow passing in a changelist ID incremental build with partially working toolbar buttons -- still working on the forward/back buttons (they aren't quite working right yet) - toolbar items are now functional: left/right nav, add files, create folder addresses #12: favorite folders -- added favorite folder functionality to the application - favorites are now stored in a workspace-specific property (client_name.favoriteFolders) in the favorites.json file in the user's configuration directory (this file is created if it does not exist) - favorites can be edited by double-clicking on the label, right-clicking the label and selecting 'edit', or clicking on the edit icon - favorites can be deleted by right-clicking on the label and selecting 'delete' update involved various refactoring so enable the hidden 'Favorites' selector and select the appropriate sidebar selector (Workspace or Server) when the favorite is selected - favorite tags implemented, satisfying ticket #18 - favorite tags are stored in a workspace-specific property (client_name.favoriteTags) in the favorites.json file in the user's configuration directory (this file is created if it does not exist) - favorite tags can be added by clicking on the '+' that appears when hovering over the Favorite Tags header in the sidebar - a popup appears with a textfield - ESC cancels the add and closes the popup - Enter or clicking on the add button adds the tag to the property (immediately saved) and updates the Favorite Tags sidebar list and closes the popup - changing focus (ie clicking somewhere else in the application) cancels the add and closes the popup - favorite tags can be selected/deselected with a single click - selected tags have a checkmark in the rightmost column - favorite tags can be deleted by right-clicking on the label and selecting 'delete' - list items in ColumnDisplay.xaml modified - FileItem model class modified with boolean method to determine if the file has a selected tag (matching is case insensitive) - FavoritesHelper caches selected tags (to avoid unnecessary IO and parsing) - FavoriteTagItem modified to - fixing copy/paste functionality: - copy/paste now uses filesystem copy and paste (rather than p4 copy) - CopyDirectory method added to Utility class to assist in copying of entire directory structures - copy enabled from either the server tab or the workspace tab... paste only allowed on the workspace tab - wait dialog implemented for the paste process -- should happen quickly if copy is on local filesystem, but if copy is from the server then copy may take a while if the file(s) being copied are large (they have to be downloaded first) - confirmation dialog when folders selected for the copy (giving user the option of skipping large directories if they were selected accidentally) - implementation of p4 sizes code to determine size/number of files in a given path - implementation of p4 print for a depot path -- by default saves to a temp directory, but in the case of copy/paste the print is performed directly to the target directory Addresses DEFECT #91 -- Desi crashing on viewing old versions - previous refactoring had introduced the VersionItem model class, but there were still some remnants of references to FileHistory around, causing issues (ie. crashing) - changelist cleanup had used the -f (force) flag, which hadn't appeared as an issue because the test cases currently run as an admin user, but that masked the fact that only admins can do 'p4 change -d -f'. Doh #94 DEFECT: Selecting tags causes error in Server view - issue was due to tag attributes not retrieved in HEX format, so hex decoding then failed (because they weren't hex!) - this is now fixed in the PerforceHelper - general code cleanup and optimization - helper methods in the utility class to retrieve common objects from the App context - start sync now first checks the preferences to ensure that sync is actually turned on - background processes now wait for initial workspace selection before starting up #36: DEFECT: Refresh color coding - modifications for #88 fix this too #52: DEFECT: Cannot move files from "My Pending Changes" to "Trash" - modified CommandHelper logic to revert files that are in any state other than 'None' when trying to delete #88: DEFECT: icon should be different for items not on server - logic to determine file status updated - icon sizes bumped up slightly (from 18px to 24px) to make differences more visible.. #96: DEFECT: Adding files double window - this was due to a bug in the AddFilesButton_Click method of ToolBar.xaml.cs... basically ShowDialog() was called twice! DOH!! #99: DEFECT: Refresh color coding - modified logic around refreshing after a paste, seems to have fixed this issue #106: DEFECT: Paste a file in a folder where the name exists and the application crashes - added CopyFileToDirectory method to utility class to check to see if the destination file already exists... if it does, then (Copy) is appended to the filename - if there is already a file named xxx (Copy), then a counter is appended: xxx (Copy 2) - this is repeated until it comes up with a filename that doesn't conflict #104: DEFECT: right click option to open file as read only does not work - Code in ContextMenuHelper's ViewWorkspaceFile method was not wired to the code that opens a file. Now it is! - no specific bugs fixed, just trying to get refresh to be a little more responsive and context menus working a little better - reworked DelegateCommand to permit parameter - reworked various menuitem commands to include item(s) selected as parameters - reworked command methods to get parameter, cast it appropriately to the item(s), perform the operation(s), and then refresh the items as well as associated UI components - reworked some methods in MainWindow to take item(s) as arguments (rather than just trying to get the currently selected item -- sometimes when you right-click on something it isn't fully selected) #107: Defect: Unsync from computer not working - modified code that syncs/unsyncs to refresh items after change -- should make for a more responsive interface - modified PerforceHelper code that sets the client to ensure that rmdir is set as an option - fixed Exit menu item to actually shut down the application (YAY) #49: Defect: Desi throws exception when internet connection is lost - modified the GetPerforceHelper utility method to check for a live server. If the server is unreachable, the user is presented with the option to relaunch the application or shutdown #97: DEFECT: Object error when using navigation buttons - code was checking Perforce server to determine if a path was a directory or file, but when the file was newly created it doesn't exist on the server so it returned false, which later caused a null pointer exception. The PerforceHelper IsDirectory code was modified to check, as a last restort, the client path to see if the file is a directory using System.IO.Directory.exists() - modification to MainWindow.cs to re-enable the filesystem watcher #73: Defect: Checkout and delete file .. appropriate change in status not done - code in filesystem watcher was not handing deletes of opened files correctly... opened files need to be reverted first, then they can be deleted using a p4 delete. Since the file no longer exists on the filesystem, these p4 commands are performed as server-only (-k option). When the file is an Add or MoveAdd, then the server-side revert is just performed. Otherwise, if the file is in any other state (other than none), a server-side revert is performed, then the server-side delete. #92: Defect: Option for unshelving missing - added a button to the file info screen for unshelving. Since unshelving may overwrite changes the user has made, a dialog is presented and the user must confirm the unshelve operation. - application now opens with window displaying the file/folder in question when given a command-line argument in the form p4://... |
||
#4 | 10800 | alan_petersen |
UPDATE: - Various fixes - fixed bug in which double-click would not work on files locked by the user - fixed bug in helper application dialog - added 'reconcile files' to context menu in Workspace view - updated submit dialog to display error message if submit fails |
||
#3 | 10794 | alan_petersen |
Various updates.... - made main exception caught (happens if application cannot initialize, for example) more explicit in the message box (was "test" :) - CTRL-F now moves focus to search box - fixed double-click warning message when item is already locked - better thumbnail handling - general cleanup |
||
#2 | 10778 | alan_petersen |
Some updates to logic that looks for locked files... exclusively locked files do not show up as locked in fstat (doh!) so I had to do a little massaging (basically, looking at the file type and if it is +l and there is an otheraction then there must be an exclusive lock) |
||
#1 | 10761 | alan_petersen |
initial drop of Piper for Windows.... this version still has _many_ bugs (er... i mean "unintended features") but I will be updating it over the next week as more stability is added. |