Skip to content

Commit 91cc2c5

Browse files
committed
Ask for confirmation in MoveToFolder if folder will be merged
Also makes the dialog use TextBoxErrorStyle
1 parent 1f398de commit 91cc2c5

File tree

10 files changed

+621
-18
lines changed

10 files changed

+621
-18
lines changed

Rubberduck.Core/UI/CodeExplorer/Commands/DragAndDrop/CodeExplorerMoveToFolderDragAndDropCommand.cs

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
4+
using Rubberduck.Interaction;
5+
using Rubberduck.JunkDrawer.Extensions;
36
using Rubberduck.Navigation.CodeExplorer;
47
using Rubberduck.Parsing.Symbols;
58
using Rubberduck.Parsing.VBA;
69
using Rubberduck.Refactorings.MoveFolder;
710
using Rubberduck.Refactorings.MoveToFolder;
11+
using Rubberduck.Resources;
812
using Rubberduck.UI.CodeExplorer.Commands.Abstract;
913
using Rubberduck.VBEditor.Events;
1014
using Rubberduck.UI.Command.Refactorings.Notifiers;
@@ -13,14 +17,22 @@ namespace Rubberduck.UI.CodeExplorer.Commands.DragAndDrop
1317
{
1418
public sealed class CodeExplorerMoveToFolderDragAndDropCommand : CodeExplorerMoveToFolderCommandBase
1519
{
20+
private readonly IDeclarationFinderProvider _declarationFinderProvider;
21+
private readonly IMessageBox _messageBox;
22+
1623
public CodeExplorerMoveToFolderDragAndDropCommand(
1724
MoveMultipleFoldersRefactoringAction moveFolders,
1825
MoveMultipleToFolderRefactoringAction moveToFolder,
1926
MoveToFolderRefactoringFailedNotifier failureNotifier,
2027
IParserStatusProvider parserStatusProvider,
21-
IVbeEvents vbeEvents)
28+
IVbeEvents vbeEvents,
29+
IMessageBox messageBox,
30+
IDeclarationFinderProvider declarationFinderProvider)
2231
: base(moveFolders, moveToFolder, failureNotifier, parserStatusProvider, vbeEvents)
2332
{
33+
_declarationFinderProvider = declarationFinderProvider;
34+
_messageBox = messageBox;
35+
2436
AddToCanExecuteEvaluation(SpecialEvaluateCanExecute);
2537
}
2638

@@ -34,7 +46,7 @@ private bool SpecialEvaluateCanExecute(object parameter)
3446
&& (node is CodeExplorerCustomFolderViewModel folderViewModel
3547
&& folderViewModel.FullPath != targetFolder
3648
|| node is CodeExplorerComponentViewModel componentViewModel
37-
&& componentViewModel.Declaration is ModuleDeclaration);
49+
&& componentViewModel.Declaration is ModuleDeclaration);
3850
}
3951

4052
protected override ICodeExplorerNode NodeFromParameter(object parameter)
@@ -43,18 +55,71 @@ protected override ICodeExplorerNode NodeFromParameter(object parameter)
4355
return node;
4456
}
4557

46-
protected override MoveMultipleFoldersModel ModifiedFolderModel(MoveMultipleFoldersModel model, object parameter)
58+
protected override MoveMultipleToFolderModel ModifiedComponentModel(MoveMultipleToFolderModel model, object parameter)
4759
{
4860
var (targetFolder, node) = (ValueTuple<string, ICodeExplorerNode>)parameter;
4961
model.TargetFolder = targetFolder;
5062
return model;
5163
}
5264

53-
protected override MoveMultipleToFolderModel ModifiedComponentModel(MoveMultipleToFolderModel model, object parameter)
65+
protected override MoveMultipleFoldersModel ModifiedFolderModel(MoveMultipleFoldersModel model, object parameter)
5466
{
5567
var (targetFolder, node) = (ValueTuple<string, ICodeExplorerNode>)parameter;
56-
model.TargetFolder = targetFolder;
68+
if (OkToMoveFolders(model, targetFolder))
69+
{
70+
model.TargetFolder = targetFolder;
71+
}
5772
return model;
5873
}
74+
75+
private bool OkToMoveFolders(MoveMultipleFoldersModel model, string targetFolder)
76+
{
77+
var foldersMergedWithTargetFolders = FoldersMergedWithTargetFolders(model, targetFolder);
78+
return !foldersMergedWithTargetFolders.Any()
79+
|| UserConfirmsToProceedWithFolderMerge(targetFolder, foldersMergedWithTargetFolders);
80+
}
81+
82+
private List<string> FoldersMergedWithTargetFolders(MoveMultipleFoldersModel model, string targetFolder)
83+
{
84+
var movingFolders = model.ModulesBySourceFolder
85+
.Select(kvp => kvp.Key)
86+
.Where(folder => !folder.ParentFolder().Equals(targetFolder))
87+
.Select(folder => folder.SubFolderName());
88+
89+
var targetFolderSubfolders = _declarationFinderProvider.DeclarationFinder
90+
.UserDeclarations(DeclarationType.Module)
91+
.OfType<ModuleDeclaration>()
92+
.Select(module => module.CustomFolder)
93+
.Where(folder => folder.IsSubFolderOf(targetFolder))
94+
.Select(folder => folder.SubFolderPathRelativeTo(targetFolder).RootFolder())
95+
.ToHashSet();
96+
97+
return movingFolders
98+
.Where(folder => targetFolderSubfolders.Contains(folder))
99+
.ToList();
100+
}
101+
102+
private bool UserConfirmsToProceedWithFolderMerge(string targetFolder, List<string> mergedTargetFolders)
103+
{
104+
var message = FolderMergeUserConfirmationMessage(targetFolder, mergedTargetFolders);
105+
return _messageBox?.ConfirmYesNo(message, RubberduckUI.MoveFoldersDialog_Caption) ?? false;
106+
}
107+
108+
private string FolderMergeUserConfirmationMessage(string targetFolder, List<string> mergedTargetFolders)
109+
{
110+
if (mergedTargetFolders.Count == 1)
111+
{
112+
return string.Format(
113+
RubberduckUI.MoveFolders_SameNameSubfolder,
114+
targetFolder,
115+
mergedTargetFolders.Single());
116+
}
117+
118+
var subfolders = $"'{string.Join("', '", mergedTargetFolders)}'";
119+
return string.Format(
120+
RubberduckUI.MoveFolders_SameNameSubfolders,
121+
targetFolder,
122+
subfolders);
123+
}
59124
}
60125
}

Rubberduck.Core/UI/Refactorings/MoveFolder/MoveMultipleFoldersView.xaml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,12 @@
3333
Margin="0,0,5,0" />
3434
<TextBox Name="MoveToFolderTextBox"
3535
Grid.Column="1"
36+
Style="{StaticResource TextBoxErrorStyle}"
3637
Text="{Binding NewFolder, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
3738
Height="22"
3839
VerticalAlignment="Top"
3940
VerticalContentAlignment="Center"
4041
HorizontalAlignment="Stretch" />
41-
<Image Grid.Column="1"
42-
Source="{StaticResource InvalidTextImage}"
43-
Height="16"
44-
Margin="0,-8,-8,0"
45-
HorizontalAlignment="Right"
46-
VerticalAlignment="Top"
47-
Visibility="{Binding IsValidFolder, Converter={StaticResource BoolToHiddenVisibility}}"/>
4842
</Grid>
4943
<Grid Grid.Row="2" Background="{x:Static SystemColors.ControlDarkBrush}" Grid.IsSharedSizeScope="True">
5044
<Grid HorizontalAlignment="Right"

Rubberduck.Core/UI/Refactorings/MoveFolder/MoveMultipleFoldersViewModel.cs

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
using System.Collections.Generic;
22
using System.Linq;
3+
using Rubberduck.Interaction;
34
using Rubberduck.Parsing.Symbols;
45
using Rubberduck.Refactorings.MoveFolder;
56
using Rubberduck.Resources;
67
using Rubberduck.JunkDrawer.Extensions;
8+
using Rubberduck.Parsing.VBA;
79

810
namespace Rubberduck.UI.Refactorings.MoveFolder
911
{
1012
public class MoveMultipleFoldersViewModel : RefactoringViewModelBase<MoveMultipleFoldersModel>
1113
{
12-
public MoveMultipleFoldersViewModel(MoveMultipleFoldersModel model)
14+
private readonly IDeclarationFinderProvider _declarationFinderProvider;
15+
private readonly IMessageBox _messageBox;
16+
17+
public MoveMultipleFoldersViewModel(MoveMultipleFoldersModel model, IMessageBox messageBox, IDeclarationFinderProvider declarationFinderProvider)
1318
: base(model)
14-
{}
19+
{
20+
_declarationFinderProvider = declarationFinderProvider;
21+
_messageBox = messageBox;
22+
}
1523

1624
private IDictionary<string, ICollection<ModuleDeclaration>> ModulesBySourceFolder => Model.ModulesBySourceFolder;
1725

@@ -48,26 +56,117 @@ public string NewFolder
4856
set
4957
{
5058
Model.TargetFolder = value;
59+
ValidateFolder();
5160
OnPropertyChanged();
5261
OnPropertyChanged(nameof(IsValidFolder));
5362
}
5463
}
64+
65+
private void ValidateFolder()
66+
{
67+
var errors = new List<string>();
68+
69+
if (string.IsNullOrEmpty(NewFolder))
70+
{
71+
errors.Add(RubberduckUI.MoveFolders_EmptyName);
72+
}
73+
else if (NewFolder.Any(char.IsControl))
74+
{
75+
errors.Add(RubberduckUI.MoveFolders_ControlCharacter);
76+
}
77+
78+
if (errors.Any())
79+
{
80+
SetErrors(nameof(NewFolder), errors);
81+
}
82+
else
83+
{
84+
ClearErrors();
85+
}
86+
}
5587

5688
public bool IsValidFolder => ModulesBySourceFolder != null
5789
&& ModulesBySourceFolder.Any()
58-
&& NewFolder != null
59-
&& !NewFolder.Any(char.IsControl);
90+
&& !HasErrors;
6091

6192
protected override void DialogOk()
6293
{
63-
if (ModulesBySourceFolder == null || !ModulesBySourceFolder.Any())
94+
if (ModulesBySourceFolder == null
95+
|| !ModulesBySourceFolder.Any()
96+
|| MergesSourceFolders() && !UserConfirmsToProceedWithSourceFolderMerge())
6497
{
6598
base.DialogCancel();
6699
}
67100
else
68101
{
69-
base.DialogOk();
102+
var foldersMergedWithTargetFolders = FoldersMergedWithTargetFolders();
103+
if (foldersMergedWithTargetFolders.Any()
104+
&& !UserConfirmsToProceedWithFolderMerge(NewFolder, foldersMergedWithTargetFolders))
105+
{
106+
base.DialogCancel();
107+
}
108+
else
109+
{
110+
base.DialogOk();
111+
}
70112
}
71113
}
114+
115+
private bool MergesSourceFolders()
116+
{
117+
return ModulesBySourceFolder
118+
.Select(kvp => kvp.Key.SubFolderName())
119+
.GroupBy(item => item)
120+
.Any(group => group.Count() > 1);
121+
}
122+
123+
private bool UserConfirmsToProceedWithSourceFolderMerge()
124+
{
125+
var message = RubberduckUI.MoveFolders_SameNameSourceFolders;
126+
return _messageBox?.ConfirmYesNo(message, RubberduckUI.MoveFoldersDialog_Caption) ?? false;
127+
}
128+
129+
private List<string> FoldersMergedWithTargetFolders()
130+
{
131+
var movingFolders = ModulesBySourceFolder
132+
.Select(kvp => kvp.Key)
133+
.Where(folder => !folder.ParentFolder().Equals(NewFolder))
134+
.Select(folder => folder.SubFolderName());
135+
136+
var targetFolderSubfolders = _declarationFinderProvider.DeclarationFinder
137+
.UserDeclarations(DeclarationType.Module)
138+
.OfType<ModuleDeclaration>()
139+
.Select(module => module.CustomFolder)
140+
.Where(folder => folder.IsSubFolderOf(NewFolder))
141+
.Select(folder => folder.SubFolderPathRelativeTo(NewFolder).RootFolder())
142+
.ToHashSet();
143+
144+
return movingFolders
145+
.Where(folder => targetFolderSubfolders.Contains(folder))
146+
.ToList();
147+
}
148+
149+
private bool UserConfirmsToProceedWithFolderMerge(string targetFolder, List<string> mergedTargetFolders)
150+
{
151+
var message = FolderMergeUserConfirmationMessage(targetFolder, mergedTargetFolders);
152+
return _messageBox?.ConfirmYesNo(message, RubberduckUI.MoveFoldersDialog_Caption) ?? false;
153+
}
154+
155+
private string FolderMergeUserConfirmationMessage(string targetFolder, List<string> mergedTargetFolders)
156+
{
157+
if (mergedTargetFolders.Count == 1)
158+
{
159+
return string.Format(
160+
RubberduckUI.MoveFolders_SameNameSubfolder,
161+
targetFolder,
162+
mergedTargetFolders.Single());
163+
}
164+
165+
var subfolders = $"'{string.Join("', '", mergedTargetFolders)}'";
166+
return string.Format(
167+
RubberduckUI.MoveFolders_SameNameSubfolders,
168+
targetFolder,
169+
subfolders);
170+
}
72171
}
73172
}

Rubberduck.JunkDrawer/Extensions/FolderExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ public static string RootFolder(this string folder)
1111
return (folder ?? string.Empty).Split(FolderExtensions.FolderDelimiter).FirstOrDefault();
1212
}
1313

14+
public static string SubFolderName(this string folder)
15+
{
16+
return (folder ?? string.Empty).Split(FolderExtensions.FolderDelimiter).LastOrDefault();
17+
}
18+
1419
public static string SubFolderPathRelativeTo(this string subFolder, string folder)
1520
{
1621
if (subFolder is null || folder is null)

Rubberduck.Resources/RubberduckUI.Designer.cs

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rubberduck.Resources/RubberduckUI.de.resx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1685,4 +1685,22 @@ Import abgebrochen.</value>
16851685
<data name="RenameDialog_OnlyCasingDifferent" xml:space="preserve">
16861686
<value>Es ist in VBA nicht möglich nur die Groß- und Kleinschreibung eines Namens zu ändern.</value>
16871687
</data>
1688+
<data name="MoveFolders_SameNameSubfolder" xml:space="preserve">
1689+
<value>Der Zielordner '{0}' enthält bereits einen Unterordner '{1}'. Die Ordner werden verschmolzen, wenn der Ordner verschoben wird.
1690+
Wollen Sie fortfahren?</value>
1691+
</data>
1692+
<data name="MoveFolders_SameNameSubfolders" xml:space="preserve">
1693+
<value>Der Zielordner '{0}' enthält bereits die Unterordner {1}. Gleichnamige Ordner werden verschmolzen, wenn die Ordner verschoben werden.
1694+
Wollen Sie fortfahren?</value>
1695+
</data>
1696+
<data name="MoveFolders_SameNameSourceFolders" xml:space="preserve">
1697+
<value>Mehrere Ordner mit demselben Namenfolders werden verschoben. Dies verschmilzt sie.
1698+
Wollen Sie fortfahren?</value>
1699+
</data>
1700+
<data name="MoveFolders_EmptyName" xml:space="preserve">
1701+
<value>Ordnernamen dürfen nicht leer sein.</value>
1702+
</data>
1703+
<data name="MoveFolders_ControlCharacter" xml:space="preserve">
1704+
<value>Ordnernamen dürfen keine Kontrollzeichen wie etwa Zeilenumbrüche enthalten.</value>
1705+
</data>
16881706
</root>

0 commit comments

Comments
 (0)