Skip to content

Commit be6993d

Browse files
authored
Merge pull request #494 from Bamboy/master
Improved FileDialog Side-Panel + OS compatibility
2 parents 527c5a9 + 860895e commit be6993d

File tree

3 files changed

+279
-69
lines changed

3 files changed

+279
-69
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using MonoGame.Utilities;
6+
7+
namespace Myra.Graphics2D.UI.File
8+
{
9+
// === Platform dependent code for FileDialog class
10+
public partial class FileDialog
11+
{
12+
/// <summary>
13+
/// Platform specific code for <see cref="FileDialog"/>
14+
/// </summary>
15+
protected static class Platform
16+
{
17+
private static IReadOnlyList<string> _userPaths;
18+
/// <summary>
19+
/// Return a predetermined list of directories under the user's HOME folder, depending on OS.
20+
/// </summary>
21+
public static IReadOnlyList<string> SystemUserPlacePaths
22+
{
23+
get
24+
{
25+
if (_userPaths == null)
26+
{
27+
switch (CurrentPlatform.OS)
28+
{
29+
case OS.Windows:
30+
_userPaths = _GetWindowsPlaces();
31+
break;
32+
case OS.MacOSX:
33+
_userPaths = _GetWindowsPlaces(); //TODO Mac/OSX specific - using old windows code for now!
34+
break;
35+
case OS.Linux:
36+
_userPaths = _GetLinuxPlaces();
37+
break;
38+
default:
39+
throw new PlatformNotSupportedException(CurrentPlatform.OS.ToString());
40+
}
41+
}
42+
return _userPaths;
43+
}
44+
}
45+
46+
private static string _homePath = string.Empty;
47+
public static string UserHomePath
48+
{
49+
get
50+
{
51+
if (string.IsNullOrEmpty(_homePath))
52+
{
53+
_homePath = (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX)
54+
? Environment.GetEnvironmentVariable("HOME")
55+
: Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%");
56+
}
57+
return _homePath;
58+
}
59+
}
60+
61+
private static string _osUser = string.Empty;
62+
/// <summary>
63+
/// Returns the name of the user logged into the system
64+
/// </summary>
65+
public static string SystemUsername
66+
{
67+
get
68+
{
69+
if (string.IsNullOrEmpty(_osUser))
70+
_osUser = UserHomePath.Split(new char[]{Path.DirectorySeparatorChar}, StringSplitOptions.RemoveEmptyEntries).Last();
71+
return _osUser;
72+
}
73+
}
74+
75+
/// <summary>
76+
/// Append <see cref="Location"/> directories under the user's HOME directory.
77+
/// </summary>
78+
/// <param name="placeList">What folders to try to add relative to the HOME directory.</param>
79+
public static void AppendUserPlacesOnSystem(List<Location> appendResult, IReadOnlyList<string> placeList)
80+
{
81+
ThrowIfNull(appendResult);
82+
83+
string homePath = UserHomePath;
84+
var places = new List<string>(placeList.Count);
85+
86+
// Special label for HOME directory
87+
if (CurrentPlatform.OS != OS.Windows)
88+
appendResult.Add(new Location("Home", SystemUsername, homePath, false ));
89+
else
90+
places.Add(homePath);
91+
92+
foreach (string folder in placeList)
93+
{
94+
places.Add(Path.Combine(homePath, folder));
95+
}
96+
97+
foreach (string path in places)
98+
{
99+
if (!Directory.Exists(path))
100+
continue;
101+
102+
//TODO check permissions for those places here
103+
//Location.TryAccess(path);
104+
105+
appendResult.Add(new Location(string.Empty, Path.GetFileName(path), path, false ));
106+
}
107+
}
108+
109+
/// <summary>
110+
/// Append a list of <see cref="Location"/> for devices we can visit, depending on platform.
111+
/// </summary>
112+
/// <exception cref="PlatformNotSupportedException"></exception>
113+
public static void AppendDrivesOnSystem(List<Location> appendResult)
114+
{
115+
ThrowIfNull(appendResult);
116+
117+
switch (CurrentPlatform.OS)
118+
{
119+
case OS.Windows:
120+
_GetWindowsDrives(appendResult);
121+
return;
122+
case OS.MacOSX:
123+
_GetWindowsDrives(appendResult); //TODO Mac/OSX specific - using old windows code for now!
124+
return;
125+
case OS.Linux:
126+
_GetLinuxDrives(appendResult);
127+
return;
128+
default:
129+
throw new PlatformNotSupportedException(CurrentPlatform.OS.ToString());
130+
}
131+
}
132+
133+
#region Windows
134+
private static void _GetWindowsDrives(List<Location> appendResult)
135+
{
136+
foreach (DriveInfo d in DriveInfo.GetDrives())
137+
{
138+
switch (d.DriveType)
139+
{
140+
case DriveType.CDRom: //Acceptable
141+
case DriveType.Fixed:
142+
case DriveType.Network:
143+
case DriveType.Removable:
144+
break;
145+
case DriveType.NoRootDirectory: //Skip These
146+
case DriveType.Unknown:
147+
case DriveType.Ram:
148+
default:
149+
continue;
150+
}
151+
152+
try
153+
{
154+
string vol = string.Empty;
155+
if (!string.IsNullOrEmpty(d.VolumeLabel) && d.VolumeLabel != d.RootDirectory.FullName)
156+
vol = d.VolumeLabel;
157+
158+
appendResult.Add(new Location(vol, d.Name, d.RootDirectory.FullName, true));
159+
}
160+
catch (Exception)
161+
{
162+
}
163+
}
164+
}
165+
private static IReadOnlyList<string> _GetWindowsPlaces()
166+
{
167+
return new[] { "Desktop", "Downloads", "Documents", "Pictures" };
168+
}
169+
#endregion Windows
170+
#region Linux
171+
private static void _GetLinuxDrives(List<Location> appendResult)
172+
{
173+
string tmpFileName = Path.GetTempFileName();
174+
string[] bashResult;
175+
try
176+
{
177+
// The all caps words after o directly corelate to output string indexes. Some strings may return empty.
178+
BashRunner.Run($"lsblk -no TYPE,NAME,LABEL,MOUNTPOINT --raw > {tmpFileName}");
179+
bashResult = System.IO.File.ReadAllLines(tmpFileName);
180+
}
181+
finally
182+
{
183+
System.IO.File.Delete(tmpFileName);
184+
}
185+
186+
const string RawSpace = @"\x20";
187+
foreach (string deviceLine in bashResult)
188+
{
189+
string[] splits = deviceLine.Split(new[] { ' ' }, StringSplitOptions.None);
190+
191+
if(splits[0] != "part") //TYPE
192+
continue; // We only want partitioned file systems.
193+
194+
splits[2] = splits[2].Replace(RawSpace, " ");
195+
if(string.Equals(splits[2], "System Reserved")) //LABEL
196+
continue;
197+
198+
if(string.IsNullOrEmpty(splits[3]) || string.Equals(splits[3], "/boot"))
199+
continue;
200+
splits[3] = splits[3].Replace(RawSpace, " ");; //MOUNTPOINT
201+
202+
appendResult.Add(new Location(splits[1], splits[2], splits[3], true));
203+
}
204+
}
205+
private static IReadOnlyList<string> _GetLinuxPlaces()
206+
{
207+
return new[] { "Desktop", "Downloads", "Documents", "Pictures" };
208+
}
209+
#endregion Linux
210+
211+
private static void ThrowIfNull(List<Location> obj)
212+
{
213+
if (obj == null)
214+
throw new NullReferenceException();
215+
}
216+
}
217+
}
218+
}

src/Myra/Graphics2D/UI/File/FileDialog.cs

Lines changed: 58 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Myra.Graphics2D.UI.File
88
{
99
public partial class FileDialog
1010
{
11-
private class PathInfo
11+
protected class PathInfo
1212
{
1313
public string Path { get; }
1414
public bool IsDrive { get; }
@@ -19,14 +19,33 @@ public PathInfo(string path, bool isDrive)
1919
IsDrive = isDrive;
2020
}
2121
}
22-
23-
private const int ImageTextSpacing = 4;
24-
25-
private static readonly string[] Folders =
22+
/// <summary>
23+
/// Container for info about a browsable file system or device.
24+
/// </summary>
25+
protected class Location
2626
{
27-
"Desktop", "Downloads"
28-
};
27+
public Location(string volume, string label, string path, bool isDrive)
28+
{
29+
VolumeLabel = volume;
30+
Label = label;
31+
Path = path;
32+
IsDrive = isDrive;
33+
}
34+
35+
public readonly string VolumeLabel;
36+
public readonly string Label;
37+
public readonly string Path;
38+
public readonly bool IsDrive;
39+
40+
public bool TryAccess() => TryAccess(this.Path);
41+
public static bool TryAccess(string path)
42+
{
43+
throw new NotImplementedException();
44+
}
45+
}
2946

47+
private const int ImageTextSpacing = 4;
48+
3049
private readonly List<string> _paths = new List<string>();
3150
private readonly List<string> _history = new List<string>();
3251
private int _historyPosition;
@@ -134,68 +153,16 @@ public FileDialog(FileDialogMode mode) : base(null)
134153
_buttonBack.Background = null;
135154
_buttonForward.Background = null;
136155
_buttonParent.Background = null;
156+
_listPlaces.Background = null;
157+
158+
PopulatePlacesListUI(_listPlaces);
137159

138-
_listPlaces.Background = null;
139-
140-
var homePath = (Environment.OSVersion.Platform == PlatformID.Unix ||
141-
Environment.OSVersion.Platform == PlatformID.MacOSX)
142-
? Environment.GetEnvironmentVariable("HOME")
143-
: Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%");
144-
145-
var places = new List<string>
146-
{
147-
homePath
148-
};
149-
150-
foreach (var f in Folders)
151-
{
152-
places.Add(Path.Combine(homePath, f));
153-
}
154-
155-
foreach (var p in places)
156-
{
157-
if (!Directory.Exists(p))
158-
{
159-
continue;
160-
}
161-
162-
var item = CreateListItem(Path.GetFileName(p), p, false);
163-
_listPlaces.Widgets.Add(item);
164-
}
165-
166-
if (_listPlaces.Widgets.Count > 0)
160+
if (_listPlaces.Widgets.Count > 0) //Set starting folder
167161
{
168162
var pathInfo = (PathInfo)_listPlaces.Widgets[0].Tag;
169163
SetFolder(pathInfo.Path, false);
170164
}
171-
172-
_listPlaces.Widgets.Add(new HorizontalSeparator());
173-
174-
var drives = DriveInfo.GetDrives();
175-
foreach (var d in drives)
176-
{
177-
if (d.DriveType == DriveType.Ram || d.DriveType == DriveType.Unknown)
178-
{
179-
continue;
180-
}
181-
182-
try
183-
{
184-
var s = d.RootDirectory.FullName;
185-
186-
if (!string.IsNullOrEmpty(d.VolumeLabel) && d.VolumeLabel != d.RootDirectory.FullName)
187-
{
188-
s += " (" + d.VolumeLabel + ")";
189-
}
190-
191-
var item = CreateListItem(s, d.RootDirectory.FullName, true);
192-
_listPlaces.Widgets.Add(item);
193-
}
194-
catch (Exception)
195-
{
196-
}
197-
}
198-
165+
199166
_listPlaces.SelectedIndexChanged += OnPlacesSelectedIndexChanged;
200167

201168
_gridFiles.SelectedIndexChanged += OnGridFilesSelectedIndexChanged;
@@ -215,20 +182,42 @@ public FileDialog(FileDialogMode mode) : base(null)
215182
SetStyle(Stylesheet.DefaultStyleName);
216183
}
217184

218-
private static Widget CreateListItem(string text, string path, bool isDrive)
185+
protected virtual void PopulatePlacesListUI(ListView listView)
186+
{
187+
List<Location> placeList = new List<Location>(8);
188+
int index = 0;
189+
190+
//Add user directories
191+
Platform.AppendUserPlacesOnSystem(placeList, Platform.SystemUserPlacePaths);
192+
for (; index < placeList.Count; index++)
193+
listView.Widgets.Add( CreateListItem(placeList[index]) );
194+
195+
if (_listPlaces.Widgets.Count > 0)
196+
listView.Widgets.Add(new HorizontalSeparator());
197+
198+
//Add file system drives
199+
Platform.AppendDrivesOnSystem(placeList);
200+
for (; index < placeList.Count; index++)
201+
listView.Widgets.Add( CreateListItem(placeList[index]) );
202+
}
203+
204+
protected virtual Widget CreateListItem(Location location)
219205
{
220206
var item = new HorizontalStackPanel
221207
{
222208
Spacing = ImageTextSpacing,
223-
Tag = new PathInfo(path, isDrive)
209+
Tag = new PathInfo(location.Path, location.IsDrive)
224210
};
225211

212+
string label = string.IsNullOrEmpty(location.VolumeLabel)
213+
? location.Label
214+
: $"[{location.VolumeLabel}] {location.Label}";
215+
226216
item.Widgets.Add(new Image());
227-
item.Widgets.Add(new Label { Text = text });
228-
217+
item.Widgets.Add(new Label { Text = label });
229218
return item;
230219
}
231-
220+
232221
private void UpdateEnabled()
233222
{
234223
var enabled = false;

0 commit comments

Comments
 (0)