Skip to content

Commit b1812e3

Browse files
committed
Implement pipe pinning, as suggested by yaufol@
Pinned pipes are always displayed on top of the list, and their names are preserved across program starts.
1 parent 81488c1 commit b1812e3

8 files changed

Lines changed: 349 additions & 7 deletions

File tree

App.xaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -->
1515
<Application x:Class="PipeExplorer.App"
1616
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
1717
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
18+
xmlns:sys="clr-namespace:System;assembly=mscorlib"
19+
xmlns:sc="clr-namespace:System.Collections;assembly=mscorlib"
1820
xmlns:local="clr-namespace:PipeExplorer"
1921
xmlns:vm="clr-namespace:PipeExplorer.ViewModels"
2022
StartupUri="PipeExplorerMainWindow.xaml">
@@ -30,6 +32,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -->
3032

3133
<vm:AdvancedBooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
3234
<vm:AdvancedBooleanToVisibilityConverter x:Key="InvertedBooleanToVisibilityConverter" InvertedLogic="True" />
35+
<vm:ChooseConverter x:Key="ChooseConverter" />
3336

3437
<DrawingImage x:Key="AppIcon">
3538
<DrawingImage.Drawing>
@@ -60,6 +63,32 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -->
6063
</GeometryDrawing>
6164
</DrawingImage.Drawing>
6265
</DrawingImage>
66+
67+
<!-- Taken from BoxIcons -->
68+
<sc:ArrayList x:Key="PinnedIcons">
69+
<DrawingImage>
70+
<DrawingImage.Drawing>
71+
<GeometryDrawing Brush="Black">
72+
<GeometryDrawing.Geometry>
73+
<PathGeometry>
74+
M15,11.586V6h2V4c0-1.104-0.896-2-2-2H9C7.896,2,7,2.896,7,4v2h2v5.586l-2.707,1.707C6.105,13.48,6,13.734,6,14v2 c0,0.553,0.448,1,1,1h2h2v3l1,2l1-2v-3h4c0.553,0,1-0.447,1-1v-2c0-0.266-0.105-0.52-0.293-0.707L15,11.586z
75+
</PathGeometry>
76+
</GeometryDrawing.Geometry>
77+
</GeometryDrawing>
78+
</DrawingImage.Drawing>
79+
</DrawingImage>
80+
<DrawingImage>
81+
<DrawingImage.Drawing>
82+
<GeometryDrawing Brush="Black">
83+
<GeometryDrawing.Geometry>
84+
<PathGeometry>
85+
M12,22l1-2v-3h5c0.553,0,1-0.447,1-1v-1.586c0-0.526-0.214-1.042-0.586-1.414L17,11.586V8c0.553,0,1-0.447,1-1V4 c0-1.103-0.897-2-2-2H8C6.897,2,6,2.897,6,4v3c0,0.553,0.448,1,1,1v3.586L5.586,13C5.213,13.372,5,13.888,5,14.414V16 c0,0.553,0.448,1,1,1h5v3L12,22z M8,4h8v2H8V4z M7,14.414l1.707-1.707C8.895,12.52,9,12.266,9,12V8h6v4 c0,0.266,0.105,0.52,0.293,0.707L17,14.414V15H7V14.414z
86+
</PathGeometry>
87+
</GeometryDrawing.Geometry>
88+
</GeometryDrawing>
89+
</DrawingImage.Drawing>
90+
</DrawingImage>
91+
</sc:ArrayList>
6392
</ResourceDictionary>
6493
</Application.Resources>
6594
</Application>

PipeExplorer.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,12 @@
166166
<Compile Include="Models\AclModel.cs" />
167167
<Compile Include="Native.cs" />
168168
<Compile Include="Models\PipeModel.cs" />
169+
<Compile Include="Services\ObservableSet.cs" />
169170
<Compile Include="Services\PipeWatcher.cs" />
170171
<Compile Include="Services\Settings.cs" />
171172
<Compile Include="ViewModels\AclViewModel.cs" />
172173
<Compile Include="ViewModels\AdvancedBooleanToVisibilityConverter.cs" />
174+
<Compile Include="ViewModels\ChooseConverter.cs" />
173175
<Compile Include="ViewModels\PipeExplorerViewModel.cs" />
174176
<Compile Include="ViewModels\PipeViewModel.cs" />
175177
<Page Include="PipeExplorerMainWindow.xaml">

PipeExplorerMainWindow.xaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -->
124124
<ListView Grid.Row="1" ItemsSource="{Binding Pipes}">
125125
<ListView.View>
126126
<GridView AllowsColumnReorder="True">
127+
<GridViewColumn x:Name="ColumnPin" Width="24">
128+
<GridViewColumn.CellTemplate>
129+
<DataTemplate>
130+
<CheckBox IsChecked="{Binding Pinned}">
131+
<CheckBox.Template>
132+
<ControlTemplate TargetType="CheckBox">
133+
<Image Source="{Binding Pinned, Converter={StaticResource ChooseConverter}, ConverterParameter={StaticResource PinnedIcons}}" Margin="1" />
134+
</ControlTemplate>
135+
</CheckBox.Template>
136+
</CheckBox>
137+
</DataTemplate>
138+
</GridViewColumn.CellTemplate>
139+
</GridViewColumn>
127140
<GridViewColumn x:Name="ColumnName" DisplayMemberBinding="{Binding Name}" Header="{x:Static res:Resources.PipeListHeaderName}" />
128141
<GridViewColumn x:Name="ColumnConnections" DisplayMemberBinding="{Binding Connections}" Header="{x:Static res:Resources.PipeListHeaderConnections}" />
129142
<GridViewColumn x:Name="ColumnCreated" DisplayMemberBinding="{Binding Created}" Header="{x:Static res:Resources.PipeListHeaderCreated}" />

Services/ObservableSet.cs

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
#region Licensing information
2+
/*
3+
* Copyright(c) 2020 Vadim Zhukov<zhuk@openbsd.org>
4+
*
5+
* Permission to use, copy, modify, and distribute this software for any
6+
* purpose with or without fee is hereby granted, provided that the above
7+
* copyright notice and this permission notice appear in all copies.
8+
*
9+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10+
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11+
* MERCHANTABILITY AND FITNESS.IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12+
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13+
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14+
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15+
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16+
*/
17+
#endregion
18+
19+
using System;
20+
using System.Collections;
21+
using System.Collections.Generic;
22+
using System.Collections.Specialized;
23+
using System.Linq;
24+
using System.Reactive;
25+
using System.Reactive.Linq;
26+
using IX.Observable;
27+
using MahApps.Metro.Controls;
28+
29+
namespace PipeExplorer.Services
30+
{
31+
class ObservableSet<T> : ISet<T>, INotifyCollectionChanged
32+
{
33+
private readonly ObservableDictionary<T, Unit> impl;
34+
35+
public event NotifyCollectionChangedEventHandler CollectionChanged;
36+
37+
public ObservableSet()
38+
{
39+
impl = new ObservableDictionary<T, Unit>();
40+
impl.CollectionChanged += Impl_CollectionChanged;
41+
}
42+
43+
public ObservableSet(int capacity)
44+
{
45+
impl = new ObservableDictionary<T, Unit>(capacity);
46+
}
47+
48+
private void Impl_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
49+
{
50+
var oldItems = (e.OldItems as IList<KeyValuePair<T, Unit>>)?.Select(kv => kv.Key).ToList();
51+
var newItems = (e.NewItems as IList<KeyValuePair<T, Unit>>)?.Select(kv => kv.Key).ToList();
52+
NotifyCollectionChangedEventArgs newArgs;
53+
switch (e.Action)
54+
{
55+
case NotifyCollectionChangedAction.Add:
56+
newArgs = new NotifyCollectionChangedEventArgs(e.Action, newItems, e.NewStartingIndex);
57+
break;
58+
case NotifyCollectionChangedAction.Remove:
59+
newArgs = new NotifyCollectionChangedEventArgs(e.Action, oldItems, e.OldStartingIndex);
60+
break;
61+
case NotifyCollectionChangedAction.Replace:
62+
newArgs = new NotifyCollectionChangedEventArgs(e.Action, oldItems, newItems, e.OldStartingIndex);
63+
break;
64+
case NotifyCollectionChangedAction.Move:
65+
newArgs = new NotifyCollectionChangedEventArgs(e.Action, oldItems, e.NewStartingIndex, e.OldStartingIndex);
66+
break;
67+
case NotifyCollectionChangedAction.Reset:
68+
newArgs = new NotifyCollectionChangedEventArgs(e.Action);
69+
break;
70+
default:
71+
throw new ArgumentOutOfRangeException(nameof(e), e, "unsupported action");
72+
}
73+
CollectionChanged?.Invoke(this, newArgs);
74+
}
75+
76+
// Trivial
77+
78+
public void Clear() => impl.Clear();
79+
public bool Contains(T item) => impl.ContainsKey(item);
80+
public void CopyTo(T[] array, int arrayIndex) => impl.Keys.CopyTo(array, arrayIndex);
81+
public int Count => impl.Count;
82+
public IEnumerator<T> GetEnumerator() => impl.Keys.GetEnumerator();
83+
public bool IsReadOnly => impl.IsReadOnly;
84+
public bool Remove(T item) => impl.Remove(item);
85+
86+
void ICollection<T>.Add(T item) => Add(item);
87+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
88+
89+
// Non-trivial
90+
91+
public bool Add(T item)
92+
{
93+
if (!impl.TryGetValue(item, out _))
94+
{
95+
impl.Add(item, Unit.Default);
96+
return true;
97+
}
98+
else
99+
{
100+
return false;
101+
}
102+
}
103+
104+
public int AddRange(IEnumerable<T> items)
105+
{
106+
int cnt = 0;
107+
foreach (var item in items)
108+
if (Add(item))
109+
cnt++;
110+
return cnt;
111+
}
112+
113+
public void UnionWith(IEnumerable<T> other)
114+
{
115+
foreach (var item in other)
116+
Add(item);
117+
}
118+
119+
public void IntersectWith(IEnumerable<T> other)
120+
{
121+
foreach (var item in other)
122+
if (!Contains(item))
123+
Remove(item);
124+
}
125+
126+
public void ExceptWith(IEnumerable<T> other)
127+
{
128+
throw new NotImplementedException();
129+
}
130+
131+
public void SymmetricExceptWith(IEnumerable<T> other)
132+
{
133+
throw new NotImplementedException();
134+
}
135+
136+
public bool IsSubsetOf(IEnumerable<T> other)
137+
{
138+
foreach (var item in this)
139+
if (!other.Contains(item))
140+
return false;
141+
return true;
142+
}
143+
144+
public bool IsSupersetOf(IEnumerable<T> other)
145+
{
146+
foreach (var item in other)
147+
if (!Contains(item))
148+
return false;
149+
return true;
150+
}
151+
152+
public bool IsProperSupersetOf(IEnumerable<T> other)
153+
{
154+
if (Count <= other.Count())
155+
return false;
156+
return IsSupersetOf(other);
157+
}
158+
159+
public bool IsProperSubsetOf(IEnumerable<T> other)
160+
{
161+
if (other.Count() <= Count)
162+
return false;
163+
return IsSubsetOf(other);
164+
}
165+
166+
public bool Overlaps(IEnumerable<T> other)
167+
{
168+
foreach (var item in other)
169+
if (Contains(item))
170+
return true;
171+
return false;
172+
}
173+
174+
public bool SetEquals(IEnumerable<T> other)
175+
{
176+
if (Count != other.Count())
177+
return false;
178+
foreach (var item in other)
179+
if (!Contains(item))
180+
return false;
181+
return true;
182+
}
183+
}
184+
}

Services/Settings.cs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@
1717
#endregion
1818

1919
using System;
20-
using System.Collections;
2120
using System.Collections.Generic;
22-
using System.Collections.Specialized;
2321
using System.IO;
2422
using System.Linq;
23+
using System.Reactive;
2524
using System.Reactive.Linq;
2625
using System.Security;
2726
using System.Text.RegularExpressions;
@@ -47,6 +46,7 @@ interface ISettings : IReactiveObject
4746
WindowState WindowState { get; set; }
4847
Rect WindowPosition { get; set; }
4948
IDictionary<string, int> ColumnWidths { get; }
49+
ISet<string> PinnedNames { get; }
5050

5151
void Save();
5252
}
@@ -61,8 +61,10 @@ class Settings : ReactiveValidationObject<Settings>, ISettings
6161
[Reactive] public WindowState WindowState { get; set; } = WindowState.Normal;
6262
[Reactive] public Rect WindowPosition { get; set; }
6363
public ObservableDictionary<string, int> ColumnWidths { get; } = new ObservableDictionary<string, int>();
64+
public ObservableSet<string> PinnedNames { get; } = new ObservableSet<string>();
6465

6566
IDictionary<string, int> ISettings.ColumnWidths => ColumnWidths;
67+
ISet<string> ISettings.PinnedNames => PinnedNames;
6668

6769
public Settings()
6870
{
@@ -131,6 +133,14 @@ private void Load()
131133
ColumnWidths.Add(match.Groups[1].Value, width);
132134
}
133135
}
136+
137+
var pinnedKey = regKey.OpenSubKey("Pinned");
138+
if (pinnedKey is null)
139+
return;
140+
using (pinnedKey)
141+
{
142+
PinnedNames.AddRange(pinnedKey.GetValueNames());
143+
}
134144
}
135145
}
136146
catch (SecurityException) { }
@@ -143,7 +153,7 @@ public async void Save()
143153
try
144154
{
145155
var regKey = Registry.CurrentUser.CreateSubKey(regPath);
146-
if (regKey == null)
156+
if (regKey is null)
147157
{
148158
await ((MetroWindow)App.Current.MainWindow).ShowMessageAsync(Properties.Resources.ErrorAccessingRegistry, Properties.Resources.ErrorCouldNotSaveSettings);
149159
return;
@@ -164,6 +174,27 @@ public async void Save()
164174
regKey.SetValue("Columns", string.Join(" ", ColumnWidths.Select(kv => $"{kv.Key}={kv.Value}")), RegistryValueKind.String);
165175
else
166176
regKey.DeleteValue("Columns", false);
177+
178+
using (var pinnedKey = regKey.CreateSubKey("Pinned"))
179+
{
180+
if (pinnedKey is null)
181+
{
182+
await ((MetroWindow)App.Current.MainWindow).ShowMessageAsync(Properties.Resources.ErrorAccessingRegistry, Properties.Resources.ErrorCouldNotSaveSettings);
183+
return;
184+
}
185+
var existingValues = pinnedKey.GetValueNames();
186+
187+
var extra = existingValues.ToHashSet();
188+
extra.ExceptWith(PinnedNames);
189+
foreach (var name in extra)
190+
pinnedKey.DeleteValue(name, false);
191+
192+
var newNames = PinnedNames.ToHashSet();
193+
newNames.ExceptWith(existingValues);
194+
foreach (var name in newNames)
195+
pinnedKey.SetValue(name, 1, RegistryValueKind.DWord);
196+
197+
}
167198
}
168199
}
169200
catch (SecurityException sex)

0 commit comments

Comments
 (0)