// // Application.cs // // Author: // Michael Becker // // Copyright (c) 2020 Mike Becker's Software // // This program 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. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WAR+RANTY; 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 this program. If not, see . using System; using System.Collections.Generic; using System.Text; namespace MBS.Framework { public class Application { /// /// Implements a which notifies /// the containing of context addition and /// removal. /// public class ApplicationContextCollection : Context.ContextCollection { protected override void ClearItems() { for (int i = 0; i < this.Count; i++) { Instance.RemoveContext(this[i]); } base.ClearItems(); } protected override void InsertItem(int index, Context item) { base.InsertItem(index, item); Instance.AddContext(item); } protected override void RemoveItem(int index) { Instance.RemoveContext(this[index]); base.RemoveItem(index); } } /// /// Gets or sets the currently-running /// instance. /// /// /// The currently-running instance. /// public static Application Instance { get; set; } = null; protected virtual Command FindCommandInternal(string commandID) { return null; } /// /// Finds the command with the given , /// searching across application-global commands as well as commands /// defined in the currently-loaded s. /// /// /// The command with the given , or /// if the command was not found. /// /// Command identifier. public Command FindCommand(string commandID) { Command cmd = Commands[commandID]; if (cmd != null) return cmd; cmd = FindCommandInternal(commandID); if (cmd != null) return cmd; foreach (Context ctx in Contexts) { cmd = ctx.Commands[commandID]; if (cmd != null) return cmd; } return null; } protected virtual Context FindContextInternal(Guid contextID) { return null; } public Context FindContext(Guid contextID) { Context ctx = FindContextInternal(contextID); if (ctx != null) return ctx; ctx = Contexts[contextID]; if (ctx != null) return ctx; return null; } public Guid ID { get; set; } = Guid.Empty; protected virtual void EnableDisableCommandInternal(Command command, bool enable) { } internal void _EnableDisableCommand(Command command, bool enable) { EnableDisableCommandInternal(command, enable); } private string _UniqueName = null; public string UniqueName { get { if (_UniqueName == null) { return ShortName; } return _UniqueName; } set { _UniqueName = value; } } private string mvarBasePath = null; public string BasePath { get { if (mvarBasePath == null) { // Set up the base path for the current application. Should this be able to be // overridden with a switch (/basepath:...) ? mvarBasePath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); } return mvarBasePath; } } public string[] EnumerateDataPaths() { return new string[] { // first look in the application root directory since this will override everything else BasePath, // then look in /usr/share/universal-editor or C:\ProgramData\Mike Becker's Software\Universal Editor String.Join(System.IO.Path.DirectorySeparatorChar.ToString(), new string[] { System.Environment.GetFolderPath(System.Environment.SpecialFolder.CommonApplicationData), ShortName }), // then look in ~/.local/share/universal-editor or C:\Users\USERNAME\AppData\Local\Mike Becker's Software\Universal Editor String.Join(System.IO.Path.DirectorySeparatorChar.ToString(), new string[] { System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData), ShortName }), // then look in ~/.universal-editor or C:\Users\USERNAME\AppData\Roaming\Mike Becker's Software\Universal Editor String.Join(System.IO.Path.DirectorySeparatorChar.ToString(), new string[] { System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), ShortName }) }; } public string ShortName { get; set; } public string Title { get; set; } = String.Empty; public int ExitCode { get; protected set; } = 0; protected virtual InstallationStatus GetInstallationStatusInternal() { return InstallationStatus.Unknown; } public InstallationStatus InstallationStatus { get { return GetInstallationStatusInternal(); } } public CommandLine CommandLine { get; protected set; } = null; public Application() { CommandLine = new DefaultCommandLine(); } private Dictionary> _CommandEventHandlers = new Dictionary>(); public Command.CommandCollection Commands { get; } = new Command.CommandCollection(); /// /// Attachs the specified to handle the with the given . /// /// true, if the command was found, false otherwise. /// Command identifier. /// Handler. public bool AttachCommandEventHandler(string commandID, EventHandler handler) { Command cmd = Commands[commandID]; if (cmd != null) { cmd.Executed += handler; return true; } Console.WriteLine("attempted to attach handler for unknown command '" + commandID + "'"); // handle command event handlers attached without a Command instance if (!_CommandEventHandlers.ContainsKey(commandID)) { _CommandEventHandlers.Add(commandID, new List()); } if (!_CommandEventHandlers[commandID].Contains(handler)) { _CommandEventHandlers[commandID].Add(handler); } return false; } /// /// Executes the command with the given . /// /// Identifier. /// Named parameters. public void ExecuteCommand(string id, KeyValuePair[] namedParameters = null) { Command cmd = Commands[id]; // handle command event handlers attached without a Command instance if (_CommandEventHandlers.ContainsKey(id)) { List c = _CommandEventHandlers[id]; for (int i = 0; i < c.Count; i++) { c[i](this, new CommandEventArgs(cmd, namedParameters)); } return; } // handle command event handlers attached in a context, most recently added first for (int i = Contexts.Count - 1; i >= 0; i--) { if (Contexts[i].ExecuteCommand(id)) return; } if (cmd == null) return; cmd.Execute(); } protected virtual void InitializeInternal() { } public bool Initialized { get; private set; } = false; [System.Diagnostics.DebuggerNonUserCode()] public void Initialize() { if (Initialized) return; if (ShortName == null) throw new ArgumentException("must specify a ShortName for the application"); InitializeInternal(); Initialized = true; } protected virtual int StartInternal() { return 0; } public int Start() { if (Application.Instance == null) Application.Instance = this; Initialize(); int exitCode = StartInternal(); return exitCode; } // CONTEXTS /// /// Gets a collection of objects representing /// system, application, user, and custom contexts for settings and other /// items. /// /// /// A collection of objects representing system, /// application, user, and custom contexts for settings and other items. /// public ApplicationContextCollection Contexts { get; } = new ApplicationContextCollection(); /// /// The event raised when a is added to the /// . /// public ContextChangedEventHandler ContextAdded; /// /// Called when a is added to the /// . The default behavior is to simply fire /// the event. Implementations should handle /// adding context-specific behaviors and UI elements in this method. /// /// Event arguments. protected virtual void OnContextAdded(ContextChangedEventArgs e) { ContextAdded?.Invoke(this, e); } /// /// The event raised when a is removed from the /// . /// public ContextChangedEventHandler ContextRemoved; /// /// Called when a is removed from the /// . The default behavior is to simply fire /// the event. Implementations should handle /// removing context-specific behaviors and UI elements in this method. /// /// Event arguments. protected virtual void OnContextRemoved(ContextChangedEventArgs e) { ContextRemoved?.Invoke(this, e); } /// /// Handles updating the menus, toolbars, keyboard shortcuts, and other /// UI elements associated with the application . /// private void AddContext(Context ctx) { OnContextAdded(new ContextChangedEventArgs(ctx)); } /// /// Handles updating the menus, toolbars, keyboard shortcuts, and other /// UI elements associated with the application . /// private void RemoveContext(Context ctx) { OnContextRemoved(new ContextChangedEventArgs(ctx)); } /// /// Log the specified message. /// /// Message. public void Log(string message) { Log(null, 0, message); } /// /// Log the specified message, including information about the relevant /// object instance or static class , line number of /// the corresponding source code. /// /// Object. /// Line number. /// Message. public void Log(object obj, int lineNumber, string message) { Type type = (obj is Type ? ((Type)obj) : (obj != null ? obj.GetType() : null)); StringBuilder sb = new StringBuilder(); if (type != null) { sb.Append('['); sb.Append(type.Assembly.GetName().Name); sb.Append("] "); sb.Append(type.FullName); sb.Append('('); sb.Append(lineNumber); sb.Append(')'); sb.Append(": "); } sb.Append(message); System.Diagnostics.Debug.WriteLine(sb); } /// /// Gets a value indicating whether this is /// currently in the process of shutting down. /// /// true if stopping; otherwise, false. public bool Stopping { get; private set; } = false; protected virtual void OnStopping(System.ComponentModel.CancelEventArgs e) { } protected virtual void OnStopped(EventArgs e) { } /// /// The event raised when the method is called, before /// the application is stopped. /// public event System.ComponentModel.CancelEventHandler BeforeShutdown; /// /// Event handler for event. Called when the /// method is called, before the application is stopped. /// /// Event arguments. protected virtual void OnBeforeShutdown(System.ComponentModel.CancelEventArgs e) { BeforeShutdown?.Invoke(this, e); } public event EventHandler Shutdown; private void OnShutdown(EventArgs e) { Shutdown?.Invoke(this, e); } public void Restart() { Stop(); Start(); } /// /// Informs the underlying system backend that it is to begin the process /// of application shutdown, gracefully ending the main loop before /// returning the specified to the operating /// system. /// /// /// The exit code to return to the operating system. /// protected virtual void StopInternal(int exitCode) { } /// /// Shuts down the application gracefully, calling any event handlers /// attached to the shutdown event to give listeners the opportunity to /// cancel the shutdown and passing the specified /// to the operating system. /// /// /// The exit code to return to the operating system. /// public void Stop(int exitCode = 0) { if (Stopping) return; Stopping = true; System.ComponentModel.CancelEventArgs ce = new System.ComponentModel.CancelEventArgs(); OnStopping(ce); if (ce.Cancel) return; ce = new System.ComponentModel.CancelEventArgs(); OnBeforeShutdown(ce); if (ce.Cancel) { return; } StopInternal(exitCode); OnShutdown(EventArgs.Empty); OnStopped(EventArgs.Empty); Stopping = false; } } }