Skip to content

Commit 6b77f0a

Browse files
committed
Add move to folder DnD in the CE
1 parent 8cde475 commit 6b77f0a

File tree

6 files changed

+378
-94
lines changed

6 files changed

+378
-94
lines changed

Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerViewModel.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using System.Windows.Input;
1717
using Rubberduck.Parsing.UIContext;
1818
using Rubberduck.Templates;
19+
using Rubberduck.UI.CodeExplorer.Commands.DragAndDrop;
1920
using Rubberduck.UI.Command.ComCommands;
2021
using Rubberduck.UI.UnitTesting.ComCommands;
2122
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
@@ -399,7 +400,9 @@ private void ExecuteRemoveCommand(object param)
399400
public CommandBase SyncCodePaneCommand { get; }
400401
public CodeExplorerExtractInterfaceCommand CodeExplorerExtractInterfaceCommand { get; set; }
401402

402-
public ICodeExplorerNode FindVisibleNodeForDeclaration(Declaration declaration)
403+
public CodeExplorerMoveToFolderDragAndDropCommand MoveToFolderDragAndDropCommand { get; set; }
404+
405+
public ICodeExplorerNode FindVisibleNodeForDeclaration(Declaration declaration)
403406
{
404407
if (declaration == null)
405408
{

Rubberduck.Core/UI/CodeExplorer/CodeExplorerControl.xaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@
210210
<Setter Property="HorizontalAlignment" Value="Left" />
211211
<EventSetter Event="MouseDoubleClick" Handler="TreeView_OnMouseDoubleClick" />
212212
<EventSetter Event="MouseRightButtonDown" Handler="TreeView_OnMouseRightButtonDown" />
213+
<EventSetter Event="DragOver" Handler="TreeView_OnDragOver" />
214+
<EventSetter Event="Drop" Handler="TreeView_OnDrop" />
215+
<EventSetter Event="PreviewQueryContinueDrag" Handler="TreeView_PreviewContinueDrag" />
216+
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="TreeView_PreviewMouseLeftButtonDown" />
217+
<EventSetter Event="PreviewMouseMove" Handler="TreeView_PreviewMouseMove" />
218+
213219
<Style.Triggers>
214220
<Trigger Property="IsSelected" Value="True">
215221
<Setter Property="BorderBrush" Value="{StaticResource HighlightBorderActiveBrush}"/>
@@ -395,7 +401,8 @@
395401
FontSize="{Binding FontSize, Mode=OneWay}"
396402
BorderThickness="0,1"
397403
VirtualizingPanel.IsVirtualizing="False"
398-
KeyDown="ProjectTree_KeyDown">
404+
KeyDown="ProjectTree_KeyDown"
405+
AllowDrop="True">
399406
<TreeView.ContextMenu>
400407
<ContextMenu>
401408
<MenuItem Header="{Resx ResxName=Rubberduck.Resources.CodeExplorer.CodeExplorerUI, Key=CodeExplorer_Open}"

Rubberduck.Core/UI/CodeExplorer/CodeExplorerControl.xaml.cs

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.Windows.Controls;
1+
using System;
2+
using System.Windows;
3+
using System.Windows.Controls;
24
using System.Windows.Input;
35
using Rubberduck.Navigation.CodeExplorer;
46

@@ -39,5 +41,164 @@ private void ProjectTree_KeyDown(object sender, KeyEventArgs e)
3941
e.Handled = true;
4042
}
4143
}
44+
45+
private Point _lastLeftClickPosition;
46+
47+
private void TreeView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
48+
{
49+
_lastLeftClickPosition = e.GetPosition(null);
50+
}
51+
52+
private void TreeView_PreviewMouseMove(object sender, MouseEventArgs e)
53+
{
54+
EvaluateDragInitialization(sender, e);
55+
}
56+
57+
private void EvaluateDragInitialization(object sender, MouseEventArgs e)
58+
{
59+
if (e.LeftButton != MouseButtonState.Pressed)
60+
{
61+
return;
62+
}
63+
64+
var currentPosition = e.GetPosition(null);
65+
var fromLeftClick = currentPosition - _lastLeftClickPosition;
66+
67+
if (Math.Abs(fromLeftClick.X) < SystemParameters.MinimumHorizontalDragDistance
68+
&& Math.Abs(fromLeftClick.Y) < SystemParameters.MinimumVerticalDragDistance)
69+
{
70+
return;
71+
}
72+
73+
InitiateDrag(sender, e);
74+
}
75+
76+
private void InitiateDrag(object sender, MouseEventArgs e)
77+
{
78+
if (ViewModel == null)
79+
{
80+
return;
81+
}
82+
83+
EvaluateMoveToFolderDrag(sender, e);
84+
}
85+
86+
private const string DragFolderMoveItemDataName = "dragFolderMoveItem";
87+
private void EvaluateMoveToFolderDrag(object sender, MouseEventArgs e)
88+
{
89+
var selectedItem = ViewModel.SelectedItem;
90+
if (!ViewModel.MoveToFolderDragAndDropCommand.CanExecute(("targetFolder", selectedItem)))
91+
{
92+
return;
93+
}
94+
95+
var sourceTreeItem = (TreeViewItem)sender;
96+
var dragData = new DataObject(DragFolderMoveItemDataName, selectedItem);
97+
DragDrop.DoDragDrop(sourceTreeItem, dragData, DragDropEffects.Move);
98+
}
99+
100+
private void TreeView_PreviewContinueDrag(object sender, QueryContinueDragEventArgs e)
101+
{
102+
if (e.EscapePressed)
103+
{
104+
e.Action = DragAction.Cancel;
105+
}
106+
}
107+
108+
private void TreeView_OnDragOver(object sender, DragEventArgs e)
109+
{
110+
if (sender == e.OriginalSource)
111+
{
112+
return;
113+
}
114+
115+
EvaluateCanDrop(sender, e);
116+
117+
e.Handled = true;
118+
}
119+
120+
private void EvaluateCanDrop(object sender, DragEventArgs e)
121+
{
122+
if (e.Handled)
123+
{
124+
return;
125+
}
126+
127+
if (e.Data.GetDataPresent(DragFolderMoveItemDataName))
128+
{
129+
EvaluateCanDropFolderMove(sender, e);
130+
}
131+
}
132+
133+
private void EvaluateCanDropFolderMove(object sender, DragEventArgs e)
134+
{
135+
e.Effects = DragDropEffects.None;
136+
137+
if (e.Handled
138+
|| !e.Data.GetDataPresent(DragFolderMoveItemDataName)
139+
|| !TryGetFolderViewModel(sender, out var folderViewModel))
140+
{
141+
return;
142+
}
143+
144+
var targetFolder = folderViewModel.FullPath;
145+
//We have to cast here to get the correct type parameter of the value type for the command.
146+
var draggedItem = e.Data.GetData(DragFolderMoveItemDataName) as ICodeExplorerNode;
147+
148+
var moveToFolderDragCommand = ViewModel.MoveToFolderDragAndDropCommand;
149+
150+
if (moveToFolderDragCommand.CanExecute((targetFolder, draggedItem)))
151+
{
152+
e.Effects = DragDropEffects.Move;
153+
e.Handled = true;
154+
}
155+
}
156+
157+
private static bool TryGetFolderViewModel(object sender, out CodeExplorerCustomFolderViewModel folderViewModel)
158+
{
159+
if((sender as TreeViewItem)?.Header is CodeExplorerCustomFolderViewModel folder)
160+
{
161+
folderViewModel = folder;
162+
return true;
163+
}
164+
165+
folderViewModel = null;
166+
return false;
167+
}
168+
169+
private void TreeView_OnDrop(object sender, DragEventArgs e)
170+
{
171+
if (sender == e.OriginalSource)
172+
{
173+
return;
174+
}
175+
176+
EvaluateMoveToFolderDrag(sender, e);
177+
178+
e.Handled = true;
179+
}
180+
181+
private void EvaluateMoveToFolderDrag(object sender, DragEventArgs e)
182+
{
183+
if (e.Handled
184+
|| !e.Data.GetDataPresent(DragFolderMoveItemDataName)
185+
|| !TryGetFolderViewModel(sender, out var folderViewModel))
186+
{
187+
return;
188+
}
189+
190+
var targetFolder = folderViewModel.FullPath;
191+
//We have to cast here to get the correct type parameter of the value type for the command.
192+
var draggedItem = e.Data.GetData(DragFolderMoveItemDataName) as ICodeExplorerNode;
193+
194+
var moveToFolderDragCommand = ViewModel.MoveToFolderDragAndDropCommand;
195+
196+
if (moveToFolderDragCommand.CanExecute((targetFolder, draggedItem)))
197+
{
198+
moveToFolderDragCommand.Execute((targetFolder, draggedItem));
199+
e.Effects = DragDropEffects.Move;
200+
e.Handled = true;
201+
}
202+
}
42203
}
43204
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Rubberduck.JunkDrawer.Extensions;
5+
using Rubberduck.Navigation.CodeExplorer;
6+
using Rubberduck.Parsing.Symbols;
7+
using Rubberduck.Parsing.VBA;
8+
using Rubberduck.Refactorings;
9+
using Rubberduck.Refactorings.Exceptions;
10+
using Rubberduck.Refactorings.MoveFolder;
11+
using Rubberduck.Refactorings.MoveToFolder;
12+
using Rubberduck.VBEditor.Events;
13+
using Rubberduck.UI.Command.Refactorings.Notifiers;
14+
15+
namespace Rubberduck.UI.CodeExplorer.Commands.Abstract
16+
{
17+
public abstract class CodeExplorerMoveToFolderCommandBase : CodeExplorerCommandBase
18+
{
19+
protected static readonly Type[] ApplicableBaseNodes =
20+
{
21+
typeof(CodeExplorerCustomFolderViewModel),
22+
typeof(CodeExplorerComponentViewModel)
23+
};
24+
25+
private readonly IParserStatusProvider _parserStatusProvider;
26+
27+
private readonly IRefactoringAction<MoveMultipleFoldersModel> _moveFolders;
28+
private readonly IRefactoringAction<MoveMultipleToFolderModel> _moveToFolder;
29+
30+
private readonly IRefactoringFailureNotifier _failureNotifier;
31+
32+
public CodeExplorerMoveToFolderCommandBase(
33+
MoveMultipleFoldersRefactoringAction moveFolders,
34+
MoveMultipleToFolderRefactoringAction moveToFolder,
35+
MoveToFolderRefactoringFailedNotifier failureNotifier,
36+
IParserStatusProvider parserStatusProvider,
37+
IVbeEvents vbeEvents)
38+
: base(vbeEvents)
39+
{
40+
_moveFolders = moveFolders;
41+
_moveToFolder = moveToFolder;
42+
43+
_parserStatusProvider = parserStatusProvider;
44+
_failureNotifier = failureNotifier;
45+
46+
AddToCanExecuteEvaluation(SpecialEvaluateCanExecute);
47+
}
48+
49+
private bool SpecialEvaluateCanExecute(object parameter)
50+
{
51+
return _parserStatusProvider.Status == ParserState.Ready;
52+
}
53+
54+
protected abstract ICodeExplorerNode NodeFromParameter(object parameter);
55+
protected abstract MoveMultipleFoldersModel ModifiedFolderModel(MoveMultipleFoldersModel model, object parameter);
56+
protected abstract MoveMultipleToFolderModel ModifiedComponentModel(MoveMultipleToFolderModel model, object parameter);
57+
58+
protected override void OnExecute(object parameter)
59+
{
60+
if (!CanExecute(parameter))
61+
{
62+
return;
63+
}
64+
65+
var node = NodeFromParameter(parameter);
66+
67+
if (node is CodeExplorerComponentViewModel componentViewModel)
68+
{
69+
var model = ComponentModel(componentViewModel);
70+
var modifiedModel = ModifiedComponentModel(model, parameter);
71+
ExecuteRefactoringAction(modifiedModel, _moveToFolder, _failureNotifier);
72+
}
73+
74+
if (node is CodeExplorerCustomFolderViewModel folderViewModel)
75+
{
76+
var model = FolderModel(folderViewModel);
77+
var modifiedModel = ModifiedFolderModel(model, parameter);
78+
ExecuteRefactoringAction(modifiedModel, _moveFolders, _failureNotifier);
79+
}
80+
}
81+
82+
private MoveMultipleFoldersModel FolderModel(CodeExplorerCustomFolderViewModel folderModel)
83+
{
84+
var folder = folderModel.FullPath;
85+
var containedModules = ContainedModules(folderModel);
86+
var modulesBySourceFolder = new Dictionary<string, ICollection<ModuleDeclaration>>{{folder, containedModules}};
87+
var initialTargetFolder = folder.ParentFolder();
88+
return new MoveMultipleFoldersModel(modulesBySourceFolder, initialTargetFolder);
89+
}
90+
91+
private static ICollection<ModuleDeclaration> ContainedModules(ICodeExplorerNode itemModel)
92+
{
93+
if (itemModel is CodeExplorerComponentViewModel componentModel)
94+
{
95+
var component = componentModel.Declaration;
96+
return component is ModuleDeclaration moduleDeclaration
97+
? new List<ModuleDeclaration> {moduleDeclaration}
98+
: new List<ModuleDeclaration>();
99+
}
100+
101+
return itemModel.Children
102+
.SelectMany(ContainedModules)
103+
.ToList();
104+
}
105+
106+
private MoveMultipleToFolderModel ComponentModel(CodeExplorerComponentViewModel componentViewModel)
107+
{
108+
if (!(componentViewModel.Declaration is ModuleDeclaration moduleDeclaration))
109+
{
110+
return null;
111+
}
112+
113+
var targets = new List<ModuleDeclaration>{moduleDeclaration};
114+
var targetFolder = moduleDeclaration.CustomFolder;
115+
return new MoveMultipleToFolderModel(targets, targetFolder);
116+
}
117+
118+
private static void ExecuteRefactoringAction<TModel>(TModel model, IRefactoringAction<TModel> refactoringAction, IRefactoringFailureNotifier failureNotifier)
119+
where TModel : class, IRefactoringModel
120+
{
121+
try
122+
{
123+
refactoringAction.Refactor(model);
124+
}
125+
catch (RefactoringAbortedException)
126+
{}
127+
catch (RefactoringException exception)
128+
{
129+
failureNotifier.Notify(exception);
130+
}
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)