[tasque] Move some stuff over to NativeApplication
- From: Antonius Riha <antoniusri src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [tasque] Move some stuff over to NativeApplication
- Date: Sat, 1 Dec 2012 19:58:42 +0000 (UTC)
commit 2bd8c02dada7bb70efff4f70a879975364fe59c0
Author: Antonius Riha <antoniusriha gmail com>
Date: Sat Dec 1 14:29:03 2012 +0100
Move some stuff over to NativeApplication
src/Gtk.Tasque/Application.cs | 259 ++------------------------
src/Gtk.Tasque/Gtk.Tasque.csproj | 1 -
src/Gtk.Tasque/GtkApplicationBase.cs | 80 ++++++++-
src/libtasque/INativeApplication.cs | 13 +-
src/libtasque/NativeApplication.cs | 199 ++++++++++++++++++++-
src/{Gtk.Tasque => libtasque}/Preferences.cs | 0
src/libtasque/libtasque.csproj | 2 +
7 files changed, 302 insertions(+), 252 deletions(-)
---
diff --git a/src/Gtk.Tasque/Application.cs b/src/Gtk.Tasque/Application.cs
index 2199b22..c1c734d 100644
--- a/src/Gtk.Tasque/Application.cs
+++ b/src/Gtk.Tasque/Application.cs
@@ -56,33 +56,22 @@ namespace Tasque
private Gdk.Pixbuf normalPixBuf;
private Gtk.Image trayImage;
- private GtkTray trayIcon;
- private Preferences preferences;
private EventBox eb;
- private IBackend backend;
private PreferencesDialog preferencesDialog;
private bool quietStart = false;
private DateTime currentDay = DateTime.Today;
-
- /// <value>
- /// Keep track of the available backends. The key is the Type name of
- /// the backend.
- /// </value>
- private Dictionary<string, IBackend> availableBackends;
-
- private IBackend customBackend;
public static IBackend Backend
{
- get { return Application.Instance.backend; }
- set { Application.Instance.SetBackend (value); }
+ get { return Application.Instance.nativeApp.Backend; }
+ set { Application.Instance.nativeApp.Backend = value; }
}
- public static List<IBackend> AvailableBackends
+ public static IList<IBackend> AvailableBackends
{
get {
- return new List<IBackend> (Application.Instance.availableBackends.Values);
+ return Application.Instance.nativeApp.AvailableBackends;
}
// get { return Application.Instance.availableBackends; }
}
@@ -120,7 +109,7 @@ namespace Tasque
public static Preferences Preferences
{
- get { return Application.Instance.preferences; }
+ get { return Application.Instance.nativeApp.Preferences; }
}
public Application (INativeApplication nativeApp)
@@ -140,222 +129,10 @@ namespace Tasque
}
nativeApp.Initialize (args);
-
- preferences = new Preferences (nativeApp.ConfDir);
-
- string potentialBackendClassName = null;
-
- // See if a specific backend is specified
- if (potentialBackendClassName != null) {
- Logger.Debug ("Backend specified: " +
- potentialBackendClassName);
-
- customBackend = null;
- Assembly asm = Assembly.GetCallingAssembly ();
- try {
- customBackend = (IBackend)
- asm.CreateInstance (potentialBackendClassName);
- } catch (Exception e) {
- Logger.Warn ("Backend specified on args not found: {0}\n\t{1}",
- potentialBackendClassName, e.Message);
- }
- }
-
- // Discover all available backends
- LoadAvailableBackends ();
- GLib.Idle.Add(InitializeIdle);
GLib.Timeout.Add (60000, CheckForDaySwitch);
}
- /// <summary>
- /// Load all the available backends that Tasque can find. First look in
- /// Tasque.exe and then for other DLLs in the same directory Tasque.ex
- /// resides.
- /// </summary>
- private void LoadAvailableBackends ()
- {
- availableBackends = new Dictionary<string,IBackend> ();
-
- List<IBackend> backends = new List<IBackend> ();
-
- Assembly tasqueAssembly = Assembly.GetCallingAssembly ();
-
- // Look for other backends in Tasque.exe
- backends.AddRange (GetBackendsFromAssembly (tasqueAssembly));
-
- // Look through the assemblies located in the same directory as
- // Tasque.exe.
- Logger.Debug ("Tasque.exe location: {0}", tasqueAssembly.Location);
-
- DirectoryInfo loadPathInfo =
- Directory.GetParent (tasqueAssembly.Location);
- Logger.Info ("Searching for Backend DLLs in: {0}", loadPathInfo.FullName);
-
- foreach (FileInfo fileInfo in loadPathInfo.GetFiles ("*.dll")) {
- Logger.Info ("\tReading {0}", fileInfo.FullName);
- Assembly asm = null;
- try {
- asm = Assembly.LoadFile (fileInfo.FullName);
- } catch (Exception e) {
- Logger.Debug ("Exception loading {0}: {1}",
- fileInfo.FullName,
- e.Message);
- continue;
- }
-
- backends.AddRange (GetBackendsFromAssembly (asm));
- }
-
- foreach (IBackend backend in backends) {
- string typeId = backend.GetType ().ToString ();
- if (availableBackends.ContainsKey (typeId))
- continue;
-
- Logger.Debug ("Storing '{0}' = '{1}'", typeId, backend.Name);
- availableBackends [typeId] = backend;
- }
- }
-
- private List<IBackend> GetBackendsFromAssembly (Assembly asm)
- {
- List<IBackend> backends = new List<IBackend> ();
-
- Type[] types = null;
-
- try {
- types = asm.GetTypes ();
- } catch (Exception e) {
- Logger.Warn ("Exception reading types from assembly '{0}': {1}",
- asm.ToString (), e.Message);
- return backends;
- }
- foreach (Type type in types) {
- if (!type.IsClass) {
- continue; // Skip non-class types
- }
- if (type.GetInterface ("Tasque.Backends.IBackend") == null) {
- continue;
- }
- Logger.Debug ("Found Available Backend: {0}", type.ToString ());
-
- IBackend availableBackend = null;
- try {
- availableBackend = (IBackend)
- asm.CreateInstance (type.ToString ());
- } catch (Exception e) {
- Logger.Warn ("Could not instantiate {0}: {1}",
- type.ToString (),
- e.Message);
- continue;
- }
-
- if (availableBackend != null) {
- backends.Add (availableBackend);
- }
- }
-
- return backends;
- }
-
- private void SetBackend (IBackend value)
- {
- bool changingBackend = false;
- if (this.backend != null) {
- UnhookFromTooltipTaskGroupModels ();
- changingBackend = true;
- // Cleanup the old backend
- try {
- Logger.Debug ("Cleaning up backend: {0}",
- this.backend.Name);
- this.backend.Cleanup ();
- } catch (Exception e) {
- Logger.Warn ("Exception cleaning up '{0}': {1}",
- this.backend.Name,
- e);
- }
- }
-
- // Initialize the new backend
- var oldBackend = backend;
- this.backend = value;
- if (this.backend == null) {
- if (trayIcon != null)
- trayIcon.RefreshTrayIconTooltip ();
- return;
- }
-
- Logger.Info ("Using backend: {0} ({1})",
- this.backend.Name,
- this.backend.GetType ().ToString ());
- this.backend.Initialize();
-
- if (!changingBackend) {
- TaskWindow.Reinitialize (!this.quietStart);
- } else {
- TaskWindow.Reinitialize (true);
- }
-
- RebuildTooltipTaskGroupModels ();
- if (trayIcon != null)
- trayIcon.RefreshTrayIconTooltip ();
-
- Logger.Debug("Configuration status: {0}",
- this.backend.Configured.ToString());
-
- if (backend != oldBackend)
- OnBackendChanged ();
- }
-
- private bool InitializeIdle()
- {
- if (customBackend != null) {
- Application.Backend = customBackend;
- } else {
- // Check to see if the user has a preference of which backend
- // to use. If so, use it, otherwise, pop open the preferences
- // dialog so they can choose one.
- string backendTypeString = Preferences.Get (Preferences.CurrentBackend);
- Logger.Debug ("CurrentBackend specified in Preferences: {0}", backendTypeString);
- if (backendTypeString != null
- && availableBackends.ContainsKey (backendTypeString)) {
- Application.Backend = availableBackends [backendTypeString];
- }
- }
-
- trayIcon = GtkTray.CreateTray ();
-
- if (backend == null) {
- // Pop open the preferences dialog so the user can choose a
- // backend service to use.
- Application.ShowPreferences ();
- } else if (!quietStart) {
- TaskWindow.ShowWindow ();
- }
- if (backend == null || !backend.Configured){
- GLib.Timeout.Add(1000, new GLib.TimeoutHandler(RetryBackend));
- }
-
- nativeApp.InitializeIdle ();
-
- return false;
- }
- private bool RetryBackend(){
- try {
- if (backend != null && !backend.Configured) {
- backend.Cleanup();
- backend.Initialize();
- }
- } catch (Exception e) {
- Logger.Error("{0}", e.Message);
- }
- if (backend == null || !backend.Configured) {
- return true;
- } else {
- return false;
- }
- }
-
private bool CheckForDaySwitch ()
{
if (DateTime.Today != currentDay) {
@@ -367,14 +144,15 @@ namespace Tasque
UnhookFromTooltipTaskGroupModels ();
RebuildTooltipTaskGroupModels ();
- if (trayIcon != null)
- trayIcon.RefreshTrayIconTooltip ();
+ var gtkApp = (GtkApplicationBase)nativeApp;
+ if (gtkApp.TrayIcon != null)
+ gtkApp.TrayIcon.RefreshTrayIconTooltip ();
}
return true;
}
- private void UnhookFromTooltipTaskGroupModels ()
+ public void UnhookFromTooltipTaskGroupModels ()
{
foreach (TaskGroupModel model in new TaskGroupModel[] { OverdueTasks, TodayTasks, TomorrowTasks })
{
@@ -390,13 +168,14 @@ namespace Tasque
private void OnTooltipModelChanged (object o, EventArgs args)
{
- if (trayIcon != null)
- trayIcon.RefreshTrayIconTooltip ();
+ var gtkApp = (GtkApplicationBase)nativeApp;
+ if (gtkApp.TrayIcon != null)
+ gtkApp.TrayIcon.RefreshTrayIconTooltip ();
}
- private void RebuildTooltipTaskGroupModels ()
+ public void RebuildTooltipTaskGroupModels ()
{
- if (backend == null || backend.Tasks == null) {
+ if (nativeApp.Backend == null || nativeApp.Backend.Tasks == null) {
OverdueTasks = null;
TodayTasks = null;
TomorrowTasks = null;
@@ -404,9 +183,9 @@ namespace Tasque
return;
}
- OverdueTasks = TaskGroupModelFactory.CreateOverdueModel (backend.Tasks);
- TodayTasks = TaskGroupModelFactory.CreateTodayModel (backend.Tasks);
- TomorrowTasks = TaskGroupModelFactory.CreateTomorrowModel (backend.Tasks);
+ OverdueTasks = TaskGroupModelFactory.CreateOverdueModel (nativeApp.Backend.Tasks);
+ TodayTasks = TaskGroupModelFactory.CreateTodayModel (nativeApp.Backend.Tasks);
+ TomorrowTasks = TaskGroupModelFactory.CreateTomorrowModel (nativeApp.Backend.Tasks);
foreach (TaskGroupModel model in new TaskGroupModel[] { OverdueTasks, TodayTasks, TomorrowTasks })
{
@@ -456,9 +235,9 @@ namespace Tasque
public void Quit ()
{
Logger.Info ("Quit called - terminating application");
- if (backend != null) {
+ if (nativeApp.Backend != null) {
UnhookFromTooltipTaskGroupModels ();
- backend.Cleanup ();
+ nativeApp.Backend.Cleanup ();
}
TaskWindow.SavePosition ();
diff --git a/src/Gtk.Tasque/Gtk.Tasque.csproj b/src/Gtk.Tasque/Gtk.Tasque.csproj
index 9e66469..39ed652 100644
--- a/src/Gtk.Tasque/Gtk.Tasque.csproj
+++ b/src/Gtk.Tasque/Gtk.Tasque.csproj
@@ -94,7 +94,6 @@
<Compile Include="NoteDialog.cs" />
<Compile Include="NoteWidget.cs" />
<None Include="OSXApplication.cs" />
- <Compile Include="Preferences.cs" />
<Compile Include="PreferencesDialog.cs" />
<Compile Include="TaskCalendar.cs" />
<Compile Include="TaskGroup.cs" />
diff --git a/src/Gtk.Tasque/GtkApplicationBase.cs b/src/Gtk.Tasque/GtkApplicationBase.cs
index ba79628..6702c65 100644
--- a/src/Gtk.Tasque/GtkApplicationBase.cs
+++ b/src/Gtk.Tasque/GtkApplicationBase.cs
@@ -44,7 +44,7 @@ namespace Tasque
public override string ConfDir { get { return confDir; } }
- public override void Initialize (string[] args)
+ protected override void OnInitialize ()
{
Catalog.Init ("tasque", Defines.LocaleDir);
Gtk.Application.Init ();
@@ -52,7 +52,34 @@ namespace Tasque
// add package icon path to default icon theme search paths
IconTheme.Default.PrependSearchPath (Defines.IconsDir);
- base.Initialize (args);
+ Gtk.Application.Init ();
+ GLib.Idle.Add (delegate {
+ InitializeIdle ();
+ return false;
+ });
+
+ base.OnInitialize ();
+ }
+
+ protected override void OnInitializeIdle ()
+ {
+ trayIcon = GtkTray.CreateTray ();
+
+ if (Backend == null) {
+ // Pop open the preferences dialog so the user can choose a
+ // backend service to use.
+ ShowPreferences ();
+ } else if (!QuietStart)
+ TaskWindow.ShowWindow ();
+
+ if (Backend == null || !Backend.Configured) {
+ GLib.Timeout.Add (1000, new GLib.TimeoutHandler (delegate {
+ RetryBackend ();
+ return Backend == null || !Backend.Configured;
+ }));
+ }
+
+ base.OnInitializeIdle ();
}
public override void StartMainLoop ()
@@ -65,6 +92,50 @@ namespace Tasque
Gtk.Application.Quit ();
}
+ public void ShowPreferences ()
+ {
+ Logger.Info ("OnPreferences called");
+ if (preferencesDialog == null) {
+ preferencesDialog = new PreferencesDialog ();
+ preferencesDialog.Hidden += OnPreferencesDialogHidden;
+ }
+
+ preferencesDialog.Present ();
+ }
+
+ void OnPreferencesDialogHidden (object sender, EventArgs args)
+ {
+ preferencesDialog.Destroy ();
+ preferencesDialog.Hidden -= OnPreferencesDialogHidden;
+ preferencesDialog = null;
+ }
+
+ protected override void OnBackendChanged ()
+ {
+ if (backendWasNullBeforeChange)
+ TaskWindow.Reinitialize (!QuietStart);
+ else
+ TaskWindow.Reinitialize (true);
+
+ Debug.WriteLine ("Configuration status: {0}", Backend.Configured.ToString ());
+
+ Application.Instance.RebuildTooltipTaskGroupModels ();
+ if (trayIcon != null)
+ trayIcon.RefreshTrayIconTooltip ();
+
+ base.OnBackendChanged ();
+ }
+
+ protected override void OnBackendChanging ()
+ {
+ if (Backend != null)
+ Application.Instance.UnhookFromTooltipTaskGroupModels ();
+
+ backendWasNullBeforeChange = Backend == null;
+
+ base.OnBackendChanging ();
+ }
+
public override void OpenUrl (string url)
{
try {
@@ -87,6 +158,11 @@ namespace Tasque
RemoteInstanceKnocked (this, EventArgs.Empty);
}
+ internal GtkTray TrayIcon { get { return trayIcon; } }
+
+ bool backendWasNullBeforeChange;
string confDir;
+ PreferencesDialog preferencesDialog;
+ GtkTray trayIcon;
}
}
diff --git a/src/libtasque/INativeApplication.cs b/src/libtasque/INativeApplication.cs
index 4ec1f1e..5166e0f 100644
--- a/src/libtasque/INativeApplication.cs
+++ b/src/libtasque/INativeApplication.cs
@@ -1,23 +1,20 @@
using System;
+using System.Collections.Generic;
+using Tasque.Backends;
namespace Tasque
{
public interface INativeApplication : IDisposable
{
+ IList<IBackend> AvailableBackends { get; }
+ IBackend Backend { get; set; }
string ConfDir { get; }
-
+ Preferences Preferences { get; }
void Exit (int exitcode);
-
void Initialize (string [] args);
-
- void InitializeIdle ();
-
void OpenUrl (string url);
-
void QuitMainLoop ();
-
void StartMainLoop ();
-
event EventHandler Exiting;
}
}
diff --git a/src/libtasque/NativeApplication.cs b/src/libtasque/NativeApplication.cs
index 3228c37..9062861 100644
--- a/src/libtasque/NativeApplication.cs
+++ b/src/libtasque/NativeApplication.cs
@@ -26,12 +26,18 @@
using System;
using System.Diagnostics;
using Mono.Options;
+using System.Reflection;
+using System.Collections.Generic;
+using Tasque.Backends;
+using System.IO;
namespace Tasque
{
public abstract class NativeApplication : INativeApplication
{
public abstract string ConfDir { get; }
+
+ public Preferences Preferences { get { return preferences; } }
public void Exit (int exitcode)
{
@@ -52,10 +58,38 @@ namespace Tasque
RemoteInstanceKnocked += delegate { ShowMainWindow (); };
+ preferences = new Preferences (ConfDir);
+
ParseArgs (args);
+
+ SetCustomBackend ();
+
+ // Discover all available backends
+ LoadAvailableBackends ();
+
+ OnInitialize ();
}
- public virtual void InitializeIdle () {}
+ protected virtual void OnInitialize () {}
+
+ protected void InitializeIdle ()
+ {
+ if (customBackend != null) {
+ Backend = customBackend;
+ } else {
+ // Check to see if the user has a preference of which backend
+ // to use. If so, use it, otherwise, pop open the preferences
+ // dialog so they can choose one.
+ var backendTypeString = preferences.Get (Preferences.CurrentBackend);
+ Logger.Debug ("CurrentBackend specified in Preferences: {0}", backendTypeString);
+ if (backendTypeString != null && availableBackends.ContainsKey (backendTypeString))
+ Backend = availableBackends [backendTypeString];
+ }
+
+ OnInitializeIdle ();
+ }
+
+ protected virtual void OnInitializeIdle () {}
protected virtual void OnExit (int exitCode) {}
@@ -63,6 +97,52 @@ namespace Tasque
{
Process.Start (url);
}
+
+ #region Backend
+ public IList<IBackend> AvailableBackends {
+ get { return new List<IBackend> (availableBackends.Values); }
+ }
+
+ public IBackend Backend { get { return backend; } set { SetBackend (value); } }
+
+ public event EventHandler BackendChanged;
+
+ protected virtual void OnBackendChanged ()
+ {
+ if (BackendChanged != null)
+ BackendChanged (this, EventArgs.Empty);
+ }
+
+ protected virtual void OnBackendChanging () {}
+
+ void SetBackend (IBackend value)
+ {
+ if (value == backend)
+ return;
+
+ OnBackendChanging ();
+
+ if (backend != null) {
+ // Cleanup the old backend
+ try {
+ Logger.Debug ("Cleaning up backend: {0}", backend.Name);
+ backend.Cleanup ();
+ } catch (Exception e) {
+ Logger.Warn ("Exception cleaning up '{0}': {1}", backend.Name, e);
+ }
+ }
+
+ // Initialize the new backend
+ backend = value;
+ if (backend == null)
+ return;
+
+ Logger.Info ("Using backend: {0} ({1})", backend.Name, backend.GetType ().ToString ());
+ backend.Initialize ();
+
+ OnBackendChanged ();
+ }
+ #endregion
public abstract void QuitMainLoop ();
@@ -97,6 +177,115 @@ namespace Tasque
}
#endregion
+ #region Backend loading and helpers
+ protected void RetryBackend ()
+ {
+ try {
+ if (backend != null && !backend.Configured) {
+ backend.Cleanup ();
+ backend.Initialize();
+ }
+ } catch (Exception e) {
+ Logger.Error ("{0}", e.Message);
+ }
+ }
+
+ /// <summary>
+ /// Load all the available backends that Tasque can find. First look in
+ /// Tasque.exe and then for other DLLs in the same directory Tasque.ex
+ /// resides.
+ /// </summary>
+ void LoadAvailableBackends ()
+ {
+ availableBackends = new Dictionary<string, IBackend> ();
+ var backends = new List<IBackend> ();
+ var tasqueAssembly = Assembly.GetCallingAssembly ();
+
+ // Look for other backends in Tasque.exe
+ backends.AddRange (GetBackendsFromAssembly (tasqueAssembly));
+
+ // Look through the assemblies located in the same directory as Tasque.exe.
+ Logger.Debug("Tasque.exe location: " + tasqueAssembly.Location);
+
+ var loadPathInfo = Directory.GetParent (tasqueAssembly.Location);
+ Logger.Info ("Searching for Backend DLLs in: " + loadPathInfo.FullName);
+
+ foreach (var fileInfo in loadPathInfo.GetFiles ("*.dll")) {
+ Logger.Info ("\tReading " + fileInfo.FullName);
+ Assembly asm = null;
+ try {
+ asm = Assembly.LoadFile (fileInfo.FullName);
+ } catch (Exception e) {
+ Logger.Debug("Exception loading {0}: {1}", fileInfo.FullName, e.Message);
+ continue;
+ }
+
+ backends.AddRange (GetBackendsFromAssembly (asm));
+ }
+
+ foreach (var backend in backends) {
+ string typeId = backend.GetType ().ToString ();
+ if (availableBackends.ContainsKey (typeId))
+ continue;
+
+ Logger.Debug("Storing '{0}' = '{1}'", typeId, backend.Name);
+ availableBackends [typeId] = backend;
+ }
+ }
+
+ List<IBackend> GetBackendsFromAssembly (Assembly asm)
+ {
+ var backends = new List<IBackend> ();
+ Type[] types = null;
+
+ try {
+ types = asm.GetTypes ();
+ } catch (Exception e) {
+ Logger.Warn ("Exception reading types from assembly '{0}': {1}", asm.ToString (), e.Message);
+ return backends;
+ }
+
+ foreach (var type in types) {
+ if (!type.IsClass)
+ continue; // Skip non-class types
+
+ if (!typeof(IBackend).IsAssignableFrom (type))
+ continue;
+
+ Logger.Debug("Found Available Backend: {0}", type);
+
+ IBackend availableBackend = null;
+ try {
+ availableBackend = (IBackend)asm.CreateInstance (type.ToString ());
+ } catch (Exception e) {
+ Logger.Warn ("Could not instantiate {0}: {1}", type.ToString (), e.Message);
+ continue;
+ }
+
+ if (availableBackend != null)
+ backends.Add (availableBackend);
+ }
+
+ return backends;
+ }
+
+ void SetCustomBackend ()
+ {
+ // See if a specific backend is specified
+ if (potentialBackendClassName != null) {
+ Logger.Debug("Backend specified: " + potentialBackendClassName);
+
+ Assembly asm = Assembly.GetCallingAssembly ();
+ try {
+ customBackend = (IBackend)asm.CreateInstance (potentialBackendClassName);
+ } catch (Exception e) {
+ Logger.Warn ("Backend specified on args not found: {0}\n\t{1}",
+ potentialBackendClassName, e.Message);
+ }
+ }
+ }
+ #endregion
+
protected bool QuietStart { get; private set; }
void ParseArgs (string[] args)
@@ -126,6 +315,14 @@ namespace Tasque
}
}
+ /// <value>
+ /// Keep track of the available backends. The key is the Type name of
+ /// the backend.
+ /// </value>
+ Dictionary<string, IBackend> availableBackends;
+ IBackend backend;
+ IBackend customBackend;
string potentialBackendClassName;
+ Preferences preferences;
}
}
diff --git a/src/Gtk.Tasque/Preferences.cs b/src/libtasque/Preferences.cs
similarity index 100%
rename from src/Gtk.Tasque/Preferences.cs
rename to src/libtasque/Preferences.cs
diff --git a/src/libtasque/libtasque.csproj b/src/libtasque/libtasque.csproj
index c501e6c..1f4649d 100644
--- a/src/libtasque/libtasque.csproj
+++ b/src/libtasque/libtasque.csproj
@@ -40,6 +40,7 @@
<Reference Include="glib-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
<Private>False</Private>
</Reference>
+ <Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="IBackend.cs" />
@@ -76,6 +77,7 @@
<Compile Include="..\Options.cs">
<Link>Options.cs</Link>
</Compile>
+ <Compile Include="Preferences.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="DateFormatters\" />
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]