Sic08/Yalv/YALV/Common/RecentFileList.cs

738 lines
24 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Xml;
using Microsoft.Win32;
namespace YALV.Common
{
/// <summary>
/// Refs: http://www.codeproject.com/Articles/23731/RecentFileList-a-WPF-MRU
/// </summary>
public class RecentFileList : Separator
{
public interface IPersist
{
List<string> RecentFiles(int max);
void InsertFile(string filepath, int max);
void RemoveFile(string filepath, int max);
}
public IPersist Persister { get; set; }
public void UseRegistryPersister() { Persister = new RegistryPersister(); }
public void UseRegistryPersister(string key) { Persister = new RegistryPersister(key); }
public void UseXmlPersister() { Persister = new XmlPersister(); }
public void UseXmlPersister(string filepath) { Persister = new XmlPersister(filepath); }
public void UseXmlPersister(Stream stream) { Persister = new XmlPersister(stream); }
public int MaxNumberOfFiles { get; set; }
public int MaxPathLength { get; set; }
public MenuItem FileMenu { get; private set; }
/// <summary>
/// Used in: String.Format( MenuItemFormat, index, filepath, displayPath );
/// Default = "_{0}: {2}"
/// </summary>
public string MenuItemFormatOneToNine { get; set; }
/// <summary>
/// Used in: String.Format( MenuItemFormat, index, filepath, displayPath );
/// Default = "{0}: {2}"
/// </summary>
public string MenuItemFormatTenPlus { get; set; }
public delegate string GetMenuItemTextDelegate(int index, string filepath);
public GetMenuItemTextDelegate GetMenuItemTextHandler { get; set; }
public event EventHandler<MenuClickEventArgs> MenuClick;
Separator _Separator = null;
List<RecentFile> _RecentFiles = null;
public RecentFileList()
{
Persister = new RegistryPersister();
MaxNumberOfFiles = 10;
MaxPathLength = 150;
MenuItemFormatOneToNine = "_{0}: {2}";
MenuItemFormatTenPlus = "{0}: {2}";
this.Loaded += (s, e) => HookFileMenu();
}
void HookFileMenu()
{
MenuItem parent = Parent as MenuItem;
if (parent == null) throw new ApplicationException("Parent must be a MenuItem");
if (FileMenu == parent) return;
if (FileMenu != null) FileMenu.SubmenuOpened -= _FileMenu_SubmenuOpened;
FileMenu = parent;
FileMenu.SubmenuOpened += _FileMenu_SubmenuOpened;
}
public List<string> RecentFiles { get { return Persister.RecentFiles(MaxNumberOfFiles); } }
public void RemoveFile(string filepath) { Persister.RemoveFile(filepath, MaxNumberOfFiles); }
public void InsertFile(string filepath) { Persister.InsertFile(filepath, MaxNumberOfFiles); }
void _FileMenu_SubmenuOpened(object sender, RoutedEventArgs e)
{
SetMenuItems();
}
void SetMenuItems()
{
RemoveMenuItems();
LoadRecentFiles();
InsertMenuItems();
}
void RemoveMenuItems()
{
if (_Separator != null) FileMenu.Items.Remove(_Separator);
if (_RecentFiles != null)
foreach (RecentFile r in _RecentFiles)
if (r.MenuItem != null)
FileMenu.Items.Remove(r.MenuItem);
_Separator = null;
_RecentFiles = null;
}
void InsertMenuItems()
{
if (_RecentFiles == null) return;
if (_RecentFiles.Count == 0) return;
int iMenuItem = FileMenu.Items.IndexOf(this);
foreach (RecentFile r in _RecentFiles)
{
string header = GetMenuItemText(r.Number + 1, r.Filepath, r.DisplayPath);
r.MenuItem = new MenuItem { Header = header };
r.MenuItem.Click += MenuItem_Click;
FileMenu.Items.Insert(++iMenuItem, r.MenuItem);
}
_Separator = new Separator();
FileMenu.Items.Insert(++iMenuItem, _Separator);
}
string GetMenuItemText(int index, string filepath, string displaypath)
{
GetMenuItemTextDelegate delegateGetMenuItemText = GetMenuItemTextHandler;
if (delegateGetMenuItemText != null) return delegateGetMenuItemText(index, filepath);
string format = (index < 10 ? MenuItemFormatOneToNine : MenuItemFormatTenPlus);
string shortPath = ShortenPathname(displaypath, MaxPathLength);
return String.Format(format, index, filepath, shortPath);
}
// This method is taken from Joe Woodbury's article at: http://www.codeproject.com/KB/cs/mrutoolstripmenu.aspx
/// <summary>
/// Shortens a pathname for display purposes.
/// </summary>
/// <param labelName="pathname">The pathname to shorten.</param>
/// <param labelName="maxLength">The maximum number of characters to be displayed.</param>
/// <remarks>Shortens a pathname by either removing consecutive components of a path
/// and/or by removing characters from the end of the filename and replacing
/// then with three elipses (...)
/// <para>In all cases, the root of the passed path will be preserved in it's entirety.</para>
/// <para>If a UNC path is used or the pathname and maxLength are particularly short,
/// the resulting path may be longer than maxLength.</para>
/// <para>This method expects fully resolved pathnames to be passed to it.
/// (Use Path.GetFullPath() to obtain this.)</para>
/// </remarks>
/// <returns></returns>
static public string ShortenPathname(string pathname, int maxLength)
{
if (pathname.Length <= maxLength)
return pathname;
string root = Path.GetPathRoot(pathname);
if (root.Length > 3)
root += Path.DirectorySeparatorChar;
string[] elements = pathname.Substring(root.Length).Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
int filenameIndex = elements.GetLength(0) - 1;
if (elements.GetLength(0) == 1) // pathname is just a root and filename
{
if (elements[0].Length > 5) // long enough to shorten
{
// if path is a UNC path, root may be rather long
if (root.Length + 6 >= maxLength)
{
return root + elements[0].Substring(0, 3) + "...";
}
else
{
return pathname.Substring(0, maxLength - 3) + "...";
}
}
}
else if ((root.Length + 4 + elements[filenameIndex].Length) > maxLength) // pathname is just a root and filename
{
root += "...\\";
int len = elements[filenameIndex].Length;
if (len < 6)
return root + elements[filenameIndex];
if ((root.Length + 6) >= maxLength)
{
len = 3;
}
else
{
len = maxLength - root.Length - 3;
}
return root + elements[filenameIndex].Substring(0, len) + "...";
}
else if (elements.GetLength(0) == 2)
{
return root + "...\\" + elements[1];
}
else
{
int len = 0;
int begin = 0;
for (int i = 0; i < filenameIndex; i++)
{
if (elements[i].Length > len)
{
begin = i;
len = elements[i].Length;
}
}
int totalLength = pathname.Length - len + 3;
int end = begin + 1;
while (totalLength > maxLength)
{
if (begin > 0)
totalLength -= elements[--begin].Length - 1;
if (totalLength <= maxLength)
break;
if (end < filenameIndex)
totalLength -= elements[++end].Length - 1;
if (begin == 0 && end == filenameIndex)
break;
}
// assemble final string
for (int i = 0; i < begin; i++)
{
root += elements[i] + '\\';
}
root += "...\\";
for (int i = end; i < filenameIndex; i++)
{
root += elements[i] + '\\';
}
return root + elements[filenameIndex];
}
return pathname;
}
void LoadRecentFiles()
{
_RecentFiles = LoadRecentFilesCore();
}
List<RecentFile> LoadRecentFilesCore()
{
List<string> list = RecentFiles;
List<RecentFile> files = new List<RecentFile>(list.Count);
int i = 0;
foreach (string filepath in list)
files.Add(new RecentFile(i++, filepath));
return files;
}
private class RecentFile
{
public int Number = 0;
public string Filepath = "";
public MenuItem MenuItem = null;
public string DisplayPath
{
get
{
return Path.Combine(
Path.GetDirectoryName(Filepath),
Path.GetFileNameWithoutExtension(Filepath));
}
}
public RecentFile(int number, string filepath)
{
this.Number = number;
this.Filepath = filepath;
}
}
public class MenuClickEventArgs : EventArgs
{
public string Filepath { get; private set; }
public MenuClickEventArgs(string filepath)
{
this.Filepath = filepath;
}
}
void MenuItem_Click(object sender, EventArgs e)
{
MenuItem menuItem = sender as MenuItem;
OnMenuClick(menuItem);
}
protected virtual void OnMenuClick(MenuItem menuItem)
{
string filepath = GetFilepath(menuItem);
if (String.IsNullOrEmpty(filepath)) return;
EventHandler<MenuClickEventArgs> dMenuClick = MenuClick;
if (dMenuClick != null) dMenuClick(menuItem, new MenuClickEventArgs(filepath));
}
string GetFilepath(MenuItem menuItem)
{
foreach (RecentFile r in _RecentFiles)
if (r.MenuItem == menuItem)
return r.Filepath;
return String.Empty;
}
//-----------------------------------------------------------------------------------------
static class ApplicationAttributes
{
static readonly Assembly _Assembly = null;
static readonly AssemblyTitleAttribute _Title = null;
static readonly AssemblyCompanyAttribute _Company = null;
static readonly AssemblyCopyrightAttribute _Copyright = null;
static readonly AssemblyProductAttribute _Product = null;
public static string Title { get; private set; }
public static string CompanyName { get; private set; }
public static string Copyright { get; private set; }
public static string ProductName { get; private set; }
static Version _Version = null;
public static string Version { get; private set; }
static ApplicationAttributes()
{
try
{
Title = String.Empty;
CompanyName = String.Empty;
Copyright = String.Empty;
ProductName = String.Empty;
Version = String.Empty;
_Assembly = Assembly.GetEntryAssembly();
if (_Assembly != null)
{
object[] attributes = _Assembly.GetCustomAttributes(false);
foreach (object attribute in attributes)
{
Type type = attribute.GetType();
if (type == typeof(AssemblyTitleAttribute)) _Title = (AssemblyTitleAttribute)attribute;
if (type == typeof(AssemblyCompanyAttribute)) _Company = (AssemblyCompanyAttribute)attribute;
if (type == typeof(AssemblyCopyrightAttribute)) _Copyright = (AssemblyCopyrightAttribute)attribute;
if (type == typeof(AssemblyProductAttribute)) _Product = (AssemblyProductAttribute)attribute;
}
_Version = _Assembly.GetName().Version;
}
if (_Title != null) Title = _Title.Title;
if (_Company != null) CompanyName = _Company.Company;
if (_Copyright != null) Copyright = _Copyright.Copyright;
if (_Product != null) ProductName = _Product.Product;
if (_Version != null) Version = _Version.ToString();
}
catch { }
}
}
//-----------------------------------------------------------------------------------------
private class RegistryPersister : IPersist
{
public string RegistryKey { get; set; }
public RegistryPersister()
{
RegistryKey =
"Software\\" +
ApplicationAttributes.CompanyName + "\\" +
ApplicationAttributes.ProductName + "\\" +
"RecentFileList";
}
public RegistryPersister(string key)
{
RegistryKey = key;
}
string Key(int i) { return i.ToString("00"); }
public List<string> RecentFiles(int max)
{
RegistryKey k = Registry.CurrentUser.OpenSubKey(RegistryKey);
if (k == null) k = Registry.CurrentUser.CreateSubKey(RegistryKey);
List<string> list = new List<string>(max);
for (int i = 0; i < max; i++)
{
string filename = (string)k.GetValue(Key(i));
if (String.IsNullOrEmpty(filename)) break;
list.Add(filename);
}
return list;
}
public void InsertFile(string filepath, int max)
{
RegistryKey k = Registry.CurrentUser.OpenSubKey(RegistryKey);
if (k == null) Registry.CurrentUser.CreateSubKey(RegistryKey);
k = Registry.CurrentUser.OpenSubKey(RegistryKey, true);
RemoveFile(filepath, max);
for (int i = max - 2; i >= 0; i--)
{
string sThis = Key(i);
string sNext = Key(i + 1);
object oThis = k.GetValue(sThis);
if (oThis == null) continue;
k.SetValue(sNext, oThis);
}
k.SetValue(Key(0), filepath);
}
public void RemoveFile(string filepath, int max)
{
RegistryKey k = Registry.CurrentUser.OpenSubKey(RegistryKey);
if (k == null) return;
for (int i = 0; i < max; i++)
{
again:
string s = (string)k.GetValue(Key(i));
if (s != null && s.Equals(filepath, StringComparison.CurrentCultureIgnoreCase))
{
RemoveFile(i, max);
goto again;
}
}
}
void RemoveFile(int index, int max)
{
RegistryKey k = Registry.CurrentUser.OpenSubKey(RegistryKey, true);
if (k == null) return;
k.DeleteValue(Key(index), false);
for (int i = index; i < max - 1; i++)
{
string sThis = Key(i);
string sNext = Key(i + 1);
object oNext = k.GetValue(sNext);
if (oNext == null) break;
k.SetValue(sThis, oNext);
k.DeleteValue(sNext);
}
}
}
//-----------------------------------------------------------------------------------------
private class XmlPersister : IPersist
{
public string Filepath { get; set; }
public Stream Stream { get; set; }
public XmlPersister()
{
Filepath =
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
ApplicationAttributes.CompanyName + "\\" +
ApplicationAttributes.ProductName + "\\" +
"RecentFileList.xml");
}
public XmlPersister(string filepath)
{
Filepath = filepath;
}
public XmlPersister(Stream stream)
{
Stream = stream;
}
public List<string> RecentFiles(int max)
{
return Load(max);
}
public void InsertFile(string filepath, int max)
{
Update(filepath, true, max);
}
public void RemoveFile(string filepath, int max)
{
Update(filepath, false, max);
}
void Update(string filepath, bool insert, int max)
{
List<string> old = Load(max);
List<string> list = new List<string>(old.Count + 1);
if (insert) list.Add(filepath);
CopyExcluding(old, filepath, list, max);
Save(list, max);
}
void CopyExcluding(List<string> source, string exclude, List<string> target, int max)
{
foreach (string s in source)
if (!String.IsNullOrEmpty(s))
if (!s.Equals(exclude, StringComparison.OrdinalIgnoreCase))
if (target.Count < max)
target.Add(s);
}
class SmartStream : IDisposable
{
bool _IsStreamOwned = true;
Stream _Stream = null;
public Stream Stream { get { return _Stream; } }
public static implicit operator Stream(SmartStream me) { return me.Stream; }
public SmartStream(string filepath, FileMode mode)
{
_IsStreamOwned = true;
Directory.CreateDirectory(Path.GetDirectoryName(filepath));
_Stream = File.Open(filepath, mode);
}
public SmartStream(Stream stream)
{
_IsStreamOwned = false;
_Stream = stream;
}
public void Dispose()
{
if (_IsStreamOwned && _Stream != null) _Stream.Dispose();
_Stream = null;
}
}
SmartStream OpenStream(FileMode mode)
{
if (!String.IsNullOrEmpty(Filepath))
{
return new SmartStream(Filepath, mode);
}
else
{
return new SmartStream(Stream);
}
}
List<string> Load(int max)
{
List<string> list = new List<string>(max);
using (MemoryStream ms = new MemoryStream())
{
using (SmartStream ss = OpenStream(FileMode.OpenOrCreate))
{
if (ss.Stream.Length == 0) return list;
ss.Stream.Position = 0;
byte[] buffer = new byte[1 << 20];
for (; ; )
{
int bytes = ss.Stream.Read(buffer, 0, buffer.Length);
if (bytes == 0) break;
ms.Write(buffer, 0, bytes);
}
ms.Position = 0;
}
XmlTextReader x = null;
try
{
x = new XmlTextReader(ms);
while (x.Read())
{
switch (x.NodeType)
{
case XmlNodeType.XmlDeclaration:
case XmlNodeType.Whitespace:
break;
case XmlNodeType.Element:
switch (x.Name)
{
case "RecentFiles": break;
case "RecentFile":
if (list.Count < max) list.Add(x.GetAttribute(0));
break;
default: Debug.Assert(false); break;
}
break;
case XmlNodeType.EndElement:
switch (x.Name)
{
case "RecentFiles": return list;
default: Debug.Assert(false); break;
}
break;
default:
Debug.Assert(false);
break;
}
}
}
finally
{
if (x != null) x.Close();
}
}
return list;
}
void Save(List<string> list, int max)
{
using (MemoryStream ms = new MemoryStream())
{
XmlTextWriter x = null;
try
{
x = new XmlTextWriter(ms, Encoding.UTF8);
if (x == null) { Debug.Assert(false); return; }
x.Formatting = Formatting.Indented;
x.WriteStartDocument();
x.WriteStartElement("RecentFiles");
foreach (string filepath in list)
{
x.WriteStartElement("RecentFile");
x.WriteAttributeString("Filepath", filepath);
x.WriteEndElement();
}
x.WriteEndElement();
x.WriteEndDocument();
x.Flush();
using (SmartStream ss = OpenStream(FileMode.Create))
{
ss.Stream.SetLength(0);
ms.Position = 0;
byte[] buffer = new byte[1 << 20];
for (; ; )
{
int bytes = ms.Read(buffer, 0, buffer.Length);
if (bytes == 0) break;
ss.Stream.Write(buffer, 0, bytes);
}
}
}
finally
{
if (x != null) x.Close();
}
}
}
}
//-----------------------------------------------------------------------------------------
}
}