Skip to content

Commit

Permalink
Sort tool windows, closes #650
Browse files Browse the repository at this point in the history
  • Loading branch information
wtfsck committed Jan 10, 2019
1 parent 3b99a06 commit 871c41c
Show file tree
Hide file tree
Showing 29 changed files with 1,550 additions and 223 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,57 +66,58 @@
mvvm:InitDataTemplateAP.Initialize="True"
mvvm:AutomationPeerMemoryLeakWorkaround.Initialize="True"
ui:ListBoxSelectedItemsAP.SelectedItemsVM="{Binding SelectedItems}"
mvvm:GridViewColumnSorter.ColumnProvider="{Binding}"
SelectionMode="Extended"
ItemsSource="{Binding AllItems}">
<ListView.Resources>
<Style x:Key="{x:Static GridView.GridViewScrollViewerStyleKey}" BasedOn="{StaticResource DbgTableGridViewScrollViewerStyle}" TargetType="{x:Type ScrollViewer}" />
<local:ProgramColumnConverter x:Key="programColumnConverter" />
</ListView.Resources>
<ListView.View>
<GridView AllowsColumnReorder="False">
<GridViewColumn Header="{x:Static p:dnSpy_Debugger_Resources.Column_Process}">
<GridView AllowsColumnReorder="True">
<GridViewColumn mvvm:GridViewColumnSorter.Id="{x:Static local:AttachToProcessWindowColumnIds.Process}" Header="{x:Static p:dnSpy_Debugger_Resources.Column_Process}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding ProcessObject, Mode=OneWay, Converter={StaticResource programColumnConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="{x:Static p:dnSpy_Debugger_Resources.Column_ProcessID}">
<GridViewColumn mvvm:GridViewColumnSorter.Id="{x:Static local:AttachToProcessWindowColumnIds.ProcessID}" Header="{x:Static p:dnSpy_Debugger_Resources.Column_ProcessID}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding IdObject, Mode=OneWay, Converter={StaticResource programColumnConverter}}" HorizontalAlignment="Right" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="{x:Static p:dnSpy_Debugger_Resources.Column_ProcessTitle}">
<GridViewColumn mvvm:GridViewColumnSorter.Id="{x:Static local:AttachToProcessWindowColumnIds.ProcessTitle}" Header="{x:Static p:dnSpy_Debugger_Resources.Column_ProcessTitle}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding TitleObject, Mode=OneWay, Converter={StaticResource programColumnConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="{x:Static p:dnSpy_Debugger_Resources.Column_ProcessType}">
<GridViewColumn mvvm:GridViewColumnSorter.Id="{x:Static local:AttachToProcessWindowColumnIds.ProcessType}" Header="{x:Static p:dnSpy_Debugger_Resources.Column_ProcessType}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding RuntimeNameObject, Mode=OneWay, Converter={StaticResource programColumnConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="{x:Static p:dnSpy_Debugger_Resources.Column_ProcessArchitecture}">
<GridViewColumn mvvm:GridViewColumnSorter.Id="{x:Static local:AttachToProcessWindowColumnIds.ProcessArchitecture}" Header="{x:Static p:dnSpy_Debugger_Resources.Column_ProcessArchitecture}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding ArchitectureObject, Mode=OneWay, Converter={StaticResource programColumnConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="{x:Static p:dnSpy_Debugger_Resources.Column_ProcessFilename}">
<GridViewColumn mvvm:GridViewColumnSorter.Id="{x:Static local:AttachToProcessWindowColumnIds.ProcessFilename}" Header="{x:Static p:dnSpy_Debugger_Resources.Column_ProcessFilename}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding PathObject, Mode=OneWay, Converter={StaticResource programColumnConverter}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="{x:Static p:dnSpy_Debugger_Resources.Column_ProcessCommandLine}">
<GridViewColumn mvvm:GridViewColumnSorter.Id="{x:Static local:AttachToProcessWindowColumnIds.ProcessCommandLine}" Header="{x:Static p:dnSpy_Debugger_Resources.Column_ProcessCommandLine}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding CommandLineObject, Mode=OneWay, Converter={StaticResource programColumnConverter}}" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public AttachToProcessDlg() {
vm.PropertyChanged += AttachToProcessVM_PropertyChanged;
vm.AllItems.CollectionChanged += AllItems_CollectionChanged;
CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy,
(s2, e2) => vm.Copy(listView.SelectedItems.OfType<ProgramVM>().ToArray()),
(s2, e2) => vm.Copy(vm.Sort(listView.SelectedItems.OfType<ProgramVM>()).ToArray()),
(s2, e2) => e2.CanExecute = listView.SelectedItems.Count != 0));
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ You should have received a copy of the GNU General Public License
using Microsoft.VisualStudio.Text.Classification;

namespace dnSpy.Debugger.Dialogs.AttachToProcess {
sealed class AttachToProcessVM : ViewModelBase {
sealed class AttachToProcessVM : ViewModelBase, IGridViewColumnDescsProvider, IComparer<ProgramVM> {
readonly ObservableCollection<ProgramVM> realAllItems;
public BulkObservableCollection<ProgramVM> AllItems { get; }
public ObservableCollection<ProgramVM> SelectedItems { get; }
public GridViewColumnDescs Descs { get; }

public string SearchToolTip => ToolTipHelper.AddKeyboardShortcut(dnSpy_Debugger_Resources.AttachToProcess_Search_ToolTip, dnSpy_Debugger_Resources.ShortCutKeyCtrlF);
public ICommand SearchHelpCommand => new RelayCommand(a => searchHelp());
Expand Down Expand Up @@ -113,8 +114,22 @@ public AttachToProcessVM(ShowAttachToProcessDialogOptions options, UIDispatcher
attachToProcessContext.Formatter = programFormatterProvider.Create();
attachToProcessContext.SyntaxHighlight = debuggerSettings.SyntaxHighlight;

Descs = new GridViewColumnDescs {
Columns = new GridViewColumnDesc[] {
new GridViewColumnDesc(AttachToProcessWindowColumnIds.Process, dnSpy_Debugger_Resources.Column_Process),
new GridViewColumnDesc(AttachToProcessWindowColumnIds.ProcessID, dnSpy_Debugger_Resources.Column_ProcessID),
new GridViewColumnDesc(AttachToProcessWindowColumnIds.ProcessTitle, dnSpy_Debugger_Resources.Column_ProcessTitle),
new GridViewColumnDesc(AttachToProcessWindowColumnIds.ProcessType, dnSpy_Debugger_Resources.Column_ProcessType),
new GridViewColumnDesc(AttachToProcessWindowColumnIds.ProcessArchitecture, dnSpy_Debugger_Resources.Column_ProcessArchitecture),
new GridViewColumnDesc(AttachToProcessWindowColumnIds.ProcessFilename, dnSpy_Debugger_Resources.Column_ProcessFilename),
new GridViewColumnDesc(AttachToProcessWindowColumnIds.ProcessCommandLine, dnSpy_Debugger_Resources.Column_ProcessCommandLine),
},
};
Descs.SortedColumnChanged += (a, b) => SortList();

RefreshCore();
}

// Don't change the order of these instances without also updating input passed to SearchMatcher.IsMatchAll()
static readonly SearchColumnDefinition[] searchColumnDefinitions = new SearchColumnDefinition[] {
new SearchColumnDefinition(PredefinedTextClassifierTags.AttachToProcessWindowProcess, "p", dnSpy_Debugger_Resources.Column_Process),
Expand Down Expand Up @@ -194,12 +209,11 @@ void AttachProgramOptionsAggregator_AttachProgramOptionsAdded(object sender, Att
}

int GetInsertionIndex(ProgramVM vm, IList<ProgramVM> list) {
var comparer = ProgramVMComparer.Instance;
int lo = 0, hi = list.Count - 1;
while (lo <= hi) {
int index = (lo + hi) / 2;

int c = comparer.Compare(vm, list[index]);
int c = Compare(vm, list[index]);
if (c < 0)
hi = index - 1;
else if (c > 0)
Expand All @@ -210,20 +224,6 @@ int GetInsertionIndex(ProgramVM vm, IList<ProgramVM> list) {
return hi + 1;
}

sealed class ProgramVMComparer : IComparer<ProgramVM> {
public static readonly ProgramVMComparer Instance = new ProgramVMComparer();
ProgramVMComparer() { }
public int Compare(ProgramVM x, ProgramVM y) {
var c = StringComparer.CurrentCultureIgnoreCase.Compare(x.Name, y.Name);
if (c != 0)
return c;
c = x.Id.CompareTo(y.Id);
if (c != 0)
return c;
return StringComparer.CurrentCultureIgnoreCase.Compare(x.RuntimeName, y.RuntimeName);
}
}

void AttachProgramOptionsAggregator_Completed(object sender, EventArgs e) {
uiDispatcher.VerifyAccess();
if (attachProgramOptionsAggregator != sender)
Expand All @@ -242,12 +242,96 @@ void FilterList(string filterText) {
if (string.IsNullOrWhiteSpace(filterText))
filterText = string.Empty;
attachToProcessContext.SearchMatcher.SetSearchText(filterText);
SortList(filterText);
}

void SortList() {
uiDispatcher.VerifyAccess();
SortList(filterText);
}

void SortList(string filterText) {
uiDispatcher.VerifyAccess();
var newList = new List<ProgramVM>(GetFilteredItems(filterText));
newList.Sort(ProgramVMComparer.Instance);
newList.Sort(this);
AllItems.Reset(newList);
}

public IEnumerable<ProgramVM> Sort(IEnumerable<ProgramVM> programs) {
uiDispatcher.VerifyAccess();
var list = new List<ProgramVM>(programs);
list.Sort(this);
return list;
}

public int Compare(ProgramVM x, ProgramVM y) {
Debug.Assert(uiDispatcher.CheckAccess());
var (desc, dir) = Descs.SortedColumn;

int id;
if (desc == null || dir == GridViewSortDirection.Default) {
id = AttachToProcessWindowColumnIds.Default_Order;
dir = GridViewSortDirection.Ascending;
}
else
id = desc.Id;

int diff;
switch (id) {
case AttachToProcessWindowColumnIds.Default_Order:
diff = GetDefaultOrder(x, y);
break;

case AttachToProcessWindowColumnIds.Process:
diff = StringComparer.OrdinalIgnoreCase.Compare(x.Name, y.Name);
break;

case AttachToProcessWindowColumnIds.ProcessID:
diff = x.Id - y.Id;
break;

case AttachToProcessWindowColumnIds.ProcessTitle:
diff = StringComparer.OrdinalIgnoreCase.Compare(x.Title, y.Title);
break;

case AttachToProcessWindowColumnIds.ProcessType:
diff = StringComparer.OrdinalIgnoreCase.Compare(x.RuntimeName, y.RuntimeName);
break;

case AttachToProcessWindowColumnIds.ProcessArchitecture:
diff = StringComparer.OrdinalIgnoreCase.Compare(x.Architecture, y.Architecture);
break;

case AttachToProcessWindowColumnIds.ProcessFilename:
diff = StringComparer.OrdinalIgnoreCase.Compare(x.Filename, y.Filename);
break;

case AttachToProcessWindowColumnIds.ProcessCommandLine:
diff = StringComparer.OrdinalIgnoreCase.Compare(x.CommandLine, y.CommandLine);
break;

default:
throw new InvalidOperationException();
}

if (diff == 0 && id != AttachToProcessWindowColumnIds.Default_Order)
diff = GetDefaultOrder(x, y);
Debug.Assert(dir == GridViewSortDirection.Ascending || dir == GridViewSortDirection.Descending);
if (dir == GridViewSortDirection.Descending)
diff = -diff;
return diff;
}

static int GetDefaultOrder(ProgramVM x, ProgramVM y) {
int c = StringComparer.CurrentCultureIgnoreCase.Compare(x.Name, y.Name);
if (c != 0)
return c;
c = x.Id - y.Id;
if (c != 0)
return c;
return c = StringComparer.CurrentCultureIgnoreCase.Compare(x.RuntimeName, y.RuntimeName);
}

IEnumerable<ProgramVM> GetFilteredItems(string filterText) {
uiDispatcher.VerifyAccess();
foreach (var vm in realAllItems) {
Expand Down Expand Up @@ -329,19 +413,50 @@ public void Copy(ProgramVM[] programs) {
var sb = new DbgStringBuilderTextWriter();
var formatter = attachToProcessContext.Formatter;
foreach (var vm in programs) {
formatter.WriteProcess(sb, vm);
sb.Write(DbgTextColor.Text, "\t");
formatter.WritePid(sb, vm);
sb.Write(DbgTextColor.Text, "\t");
formatter.WriteTitle(sb, vm);
sb.Write(DbgTextColor.Text, "\t");
formatter.WriteType(sb, vm);
sb.Write(DbgTextColor.Text, "\t");
formatter.WriteMachine(sb, vm);
sb.Write(DbgTextColor.Text, "\t");
formatter.WritePath(sb, vm);
sb.Write(DbgTextColor.Text, "\t");
formatter.WriteCommandLine(sb, vm);
bool needTab = false;
foreach (var column in Descs.Columns) {
if (!column.IsVisible)
continue;
if (column.Name == string.Empty)
continue;

if (needTab)
sb.Write(DbgTextColor.Text, "\t");
switch (column.Id) {
case AttachToProcessWindowColumnIds.Process:
formatter.WriteProcess(sb, vm);
break;

case AttachToProcessWindowColumnIds.ProcessID:
formatter.WritePid(sb, vm);
break;

case AttachToProcessWindowColumnIds.ProcessTitle:
formatter.WriteTitle(sb, vm);
break;

case AttachToProcessWindowColumnIds.ProcessType:
formatter.WriteType(sb, vm);
break;

case AttachToProcessWindowColumnIds.ProcessArchitecture:
formatter.WriteMachine(sb, vm);
break;

case AttachToProcessWindowColumnIds.ProcessFilename:
formatter.WritePath(sb, vm);
break;

case AttachToProcessWindowColumnIds.ProcessCommandLine:
formatter.WriteCommandLine(sb, vm);
break;

default:
throw new InvalidOperationException();
}

needTab = true;
}
sb.Write(DbgTextColor.Text, Environment.NewLine);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
Copyright (C) 2014-2019 [email protected]
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
*/
namespace dnSpy.Debugger.Dialogs.AttachToProcess {
static class AttachToProcessWindowColumnIds {
public const int Default_Order = -1;
public const int Process = 0;
public const int ProcessID = 1;
public const int ProcessTitle = 2;
public const int ProcessType = 3;
public const int ProcessArchitecture = 4;
public const int ProcessFilename = 5;
public const int ProcessCommandLine = 6;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright (C) 2014-2019 [email protected]
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
*/

namespace dnSpy.Debugger.ToolWindows.CodeBreakpoints {
static class CodeBreakpointsColumnIds {
public const int Default_Order = -1;
public const int Name = 0;
public const int Labels = 1;
public const int Condition = 2;
public const int HitCount = 3;
public const int Filter = 4;
public const int WhenHit = 5;
public const int Module = 6;
}
}
Loading

0 comments on commit 871c41c

Please sign in to comment.