Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
3107 lines (2544 sloc)
112 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #region | |
| /* | |
| ************************************************************** | |
| * Author: Rick Strahl | |
| * © West Wind Technologies, 2016 | |
| * http://www.west-wind.com/ | |
| * | |
| * Created: 04/28/2016 | |
| * | |
| * The above copyright notice and this permission notice shall be | |
| * included in all copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | |
| * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
| * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | |
| * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
| * OTHER DEALINGS IN THE SOFTWARE. | |
| ************************************************************** | |
| */ | |
| #endregion | |
| using System; | |
| using System.Collections.Generic; | |
| using System.ComponentModel; | |
| using System.Diagnostics; | |
| using System.Drawing; | |
| using System.IO; | |
| using System.Linq; | |
| using System.Net; | |
| using System.Text; | |
| using System.Threading.Tasks; | |
| using System.Windows; | |
| using System.Windows.Controls; | |
| using System.Windows.Data; | |
| using System.Windows.Input; | |
| using System.Windows.Interop; | |
| using System.Windows.Media; | |
| using System.Windows.Threading; | |
| using Dragablz; | |
| using FontAwesome.WPF; | |
| using MahApps.Metro.Controls; | |
| using MahApps.Metro.Controls.Dialogs; | |
| using MarkdownMonster.AddIns; | |
| using MarkdownMonster.Annotations; | |
| using MarkdownMonster.Controls.ContextMenus; | |
| using MarkdownMonster.Services; | |
| using MarkdownMonster.Utilities; | |
| using MarkdownMonster.Windows; | |
| using MarkdownMonster.Windows.PreviewBrowser; | |
| using Westwind.Utilities; | |
| using Binding = System.Windows.Data.Binding; | |
| using Brushes = System.Windows.Media.Brushes; | |
| using Button = System.Windows.Controls.Button; | |
| using Color = System.Windows.Media.Color; | |
| using ContextMenu = System.Windows.Controls.ContextMenu; | |
| using DataFormats = System.Windows.DataFormats; | |
| using DataObject = System.Windows.DataObject; | |
| using DragDropEffects = System.Windows.DragDropEffects; | |
| using DragEventArgs = System.Windows.DragEventArgs; | |
| using HorizontalAlignment = System.Windows.HorizontalAlignment; | |
| using Image = System.Windows.Controls.Image; | |
| using MenuItem = System.Windows.Controls.MenuItem; | |
| using MessageBox = System.Windows.MessageBox; | |
| using MouseEventArgs = System.Windows.Input.MouseEventArgs; | |
| using OpenFileDialog = Microsoft.Win32.OpenFileDialog; | |
| using Orientation = System.Windows.Controls.Orientation; | |
| using ToolBar = System.Windows.Controls.ToolBar; | |
| namespace MarkdownMonster | |
| { | |
| /// <summary> | |
| /// Interaction logic for MainWindow.xaml | |
| /// </summary> | |
| public partial class MainWindow : MetroWindow | |
| //, IPreviewBrowser | |
| { | |
| public AppModel Model { get; set; } | |
| public NamedPipeManager PipeManager { get; set; } | |
| public IntPtr Hwnd | |
| { | |
| get | |
| { | |
| if (_hwnd == IntPtr.Zero) | |
| _hwnd = new WindowInteropHelper(this).EnsureHandle(); | |
| return _hwnd; | |
| } | |
| } | |
| private IntPtr _hwnd = IntPtr.Zero; | |
| private DateTime _invoked = DateTime.MinValue; | |
| /// <summary> | |
| /// Manages the Preview Rendering in a WebBrowser Control | |
| /// </summary> | |
| public IPreviewBrowser PreviewBrowser { get; set; } | |
| public PreviewBrowserWindow PreviewBrowserWindow | |
| { | |
| set { _previewBrowserWindow = value; } | |
| get | |
| { | |
| if (_previewBrowserWindow == null || _previewBrowserWindow.IsClosed) | |
| { | |
| _previewBrowserWindow = new PreviewBrowserWindow(); | |
| if (Model.Configuration.WindowPosition.PreviewDisplayMode == | |
| PreviewWindowDisplayModes.ActivatedByMainWindow) | |
| _previewBrowserWindow.Owner = this; | |
| } | |
| return _previewBrowserWindow; | |
| } | |
| } | |
| private PreviewBrowserWindow _previewBrowserWindow; | |
| /// <summary> | |
| /// The Preview Browser Container Grid that contains the | |
| /// Web Browser control that handles the Document tied | |
| /// preview. | |
| /// </summary> | |
| public Grid PreviewBrowserContainer { get; set; } | |
| /// <summary> | |
| /// The Preview Browser Tab if active that is used | |
| /// for image and URL previews (ie. the Preview | |
| /// without an associated editor) | |
| /// </summary> | |
| public TabItem PreviewTab { get; set; } | |
| public TabItem FavoritesTab { get; set; } | |
| public TabItem SearchTab { get; set; } | |
| public TabItem LintingErrorTab { get; set; } | |
| private IEWebBrowserControl previewBrowser; | |
| StatusBarHelper StatusBarHelper { get; } | |
| public KeyBindingsManager KeyBindings { get; set; } | |
| public MainWindow() | |
| { | |
| InitializeComponent(); | |
| Model = new AppModel(this); | |
| AddinManager.Current.RaiseOnModelLoaded(Model); | |
| // This doesn't fire when first started, but fires when | |
| // addins are added at from the Addin Manager at runtie | |
| AddinManager.Current.AddinsLoaded = OnAddinsLoaded; | |
| Model.WindowLayout = new MainWindowLayoutModel(this); | |
| DataContext = Model; | |
| TabControl.ClosingItemCallback = TabControlDragablz_TabItemClosing; | |
| Loaded += OnLoaded; | |
| Drop += MainWindow_Drop; | |
| AllowDrop = true; | |
| Activated += OnActivated; | |
| StateChanged += MainWindow_StateChanged; | |
| // Singleton App startup - server code that listens for other instances | |
| if (mmApp.Configuration.UseSingleWindow && !App.ForceNewWindow) | |
| { | |
| // Listen for other instances launching and pick up | |
| // forwarded command line arguments | |
| PipeManager = new NamedPipeManager("MarkdownMonster"); | |
| PipeManager.StartServer(); | |
| PipeManager.ReceiveString += HandleNamedPipe_OpenRequest; | |
| } | |
| // Override some of the theme defaults (dark header specifically) | |
| mmApp.SetThemeWindowOverride(this); | |
| StatusBarHelper = new StatusBarHelper(StatusText, StatusIcon); | |
| } | |
| #region Opening and Closing | |
| private void OnLoaded(object sender, RoutedEventArgs e) | |
| { | |
| // Load either default preview browser or addin-overridden browser | |
| LoadPreviewBrowser(); | |
| RestoreSettings(); | |
| OpenFilesFromCommandLine(); | |
| CheckForFirstRun(); | |
| BindTabHeaders(); | |
| SetWindowTitle(); | |
| var left = Left; | |
| Left = 300000; | |
| Model.IsPresentationMode = App.StartInPresentationMode; | |
| if (!Model.IsPresentationMode) | |
| Model.IsPresentationMode = mmApp.Configuration.OpenInPresentationMode; | |
| // run out of band | |
| Dispatcher.InvokeAsync(() => | |
| { | |
| Left = left; | |
| FixMonitorPosition(); | |
| if (Model.IsPresentationMode) | |
| { | |
| Dispatcher.InvokeAsync(() => Model.WindowLayout.SetPresentationMode(), | |
| DispatcherPriority.ApplicationIdle); | |
| } | |
| OpenFavorites(noActivate: true); | |
| //OpenSearchPane(noActivate: true); | |
| }, DispatcherPriority.Background); | |
| // run when app is loaded | |
| Dispatcher.InvokeAsync(() => | |
| { | |
| try | |
| { | |
| AddinManager.Current.InitializeAddinsUi(this); | |
| } | |
| catch (Exception exception) | |
| { | |
| mmApp.Log("Addin UI Loading failed.", exception); | |
| } | |
| AddinManager.Current.RaiseOnWindowLoaded(); | |
| // Tab Header double click to open new tab | |
| var tabHeaderContainer = TabControl.FindChild<Grid>("HeaderContainerGrid"); | |
| tabHeaderContainer.Background = Brushes.Transparent; // REQUIRED OR CLICK NOT FIRING! | |
| tabHeaderContainer.MouseLeftButtonDown += TabHeader_DoubleClick; | |
| var config = Model.Configuration; | |
| // Start the built-in localhost Web Server if marked for AutoStart | |
| if (config.WebServer.AutoStart) | |
| WebServerLauncher.StartMarkdownMonsterWebServer(); | |
| }, DispatcherPriority.ApplicationIdle); | |
| // TODO: Check to see why this fails in async block above ^^^ | |
| // this fails to load asynchronously so do it here | |
| KeyBindings = new MarkdownMonsterKeybindings(this); | |
| if (!File.Exists(KeyBindings.KeyBindingsFilename)) | |
| KeyBindings.SaveKeyBindings(); | |
| else | |
| { | |
| KeyBindings.LoadKeyBindings(); | |
| // always write back out | |
| Task.Run(() => KeyBindings.SaveKeyBindings()); | |
| } | |
| KeyBindings.SetKeyBindings(); | |
| } | |
| /// <summary> | |
| /// Capture WindowState and 'old' size for maximized windows | |
| /// </summary> | |
| /// <param name="sender"></param> | |
| /// <param name="e"></param> | |
| private void MainWindow_StateChanged(object sender, EventArgs e) | |
| { | |
| if (WindowState == WindowState.Maximized) | |
| { | |
| // capture window size before maximizing | |
| Model.Configuration.WindowPosition.WindowState = WindowState.Maximized; | |
| Model.Configuration.WindowPosition.Top = (int) Top; | |
| Model.Configuration.WindowPosition.Left = (int) Left; | |
| Model.Configuration.WindowPosition.Width = (int) Width; | |
| Model.Configuration.WindowPosition.Height = (int) Height; | |
| } | |
| } | |
| void CheckForFirstRun() | |
| { | |
| if (mmApp.Configuration.ApplicationUpdates.FirstRun) | |
| { | |
| var rect = WindowUtilities.GetScreenDimensions(this); | |
| var ratio = WindowUtilities.GetDpiRatio(this); | |
| rect.Width = Convert.ToInt32(Convert.ToDecimal(rect.Width) / ratio); | |
| rect.Height = Convert.ToInt32(Convert.ToDecimal(rect.Height) / ratio); | |
| Width = rect.Width - 60; | |
| if (Width > 1600) | |
| Width = 1600; | |
| Left = 30; | |
| Model.Configuration.WindowPosition.InternalPreviewWidth = (int) (Convert.ToInt32(Width) * 0.45); | |
| Height = rect.Height - 75; // account for statusbar | |
| if (Height > 1100) | |
| Height = 1100; | |
| Top = 10; | |
| if (TabControl.Items.Count == 0) | |
| { | |
| try | |
| { | |
| string tempFile = Path.Combine(Path.GetTempPath(), "SampleMarkdown.md"); | |
| File.Copy(Path.Combine(App.InitialStartDirectory, "SampleMarkdown.md"), tempFile, true); | |
| OpenTab(tempFile); | |
| } | |
| catch (Exception ex) | |
| { | |
| mmApp.Log("Handled: Unable to copy file to temp folder.", ex); | |
| } | |
| } | |
| mmApp.Configuration.ApplicationUpdates.FirstRun = false; | |
| } | |
| } | |
| /// <summary> | |
| /// This is called only if addin loading takes very long | |
| /// Potentially fired off | |
| /// </summary> | |
| public void OnAddinsLoaded() | |
| { | |
| // Check to see if we are using another preview browser and load | |
| // that instead | |
| Dispatcher.InvokeAsync(() => { LoadPreviewBrowser(); }, DispatcherPriority.ApplicationIdle); | |
| } | |
| protected override void OnContentRendered(EventArgs e) | |
| { | |
| base.OnContentRendered(e); | |
| } | |
| /// <summary> | |
| /// Keep track whether the editor is focused on deactivation | |
| /// </summary> | |
| bool _saveIsEditorFocused = true; | |
| protected override void OnDeactivated(EventArgs e) | |
| { | |
| var editor = Model.ActiveEditor; | |
| if (editor == null) return; | |
| var doc = Model.ActiveDocument; | |
| if (doc == null) return; | |
| doc.IsActive = true; | |
| _saveIsEditorFocused = Model.IsEditorFocused; | |
| doc.LastEditorLineNumber = editor.GetLineNumber(); | |
| if (doc.LastEditorLineNumber == -1) | |
| doc.LastEditorLineNumber = 0; | |
| base.OnDeactivated(e); | |
| mmApp.SetWorkingSet(10000000, 5000000); | |
| } | |
| protected void OnActivated(object sender, EventArgs e) | |
| { | |
| DocumentFileWatcher.CheckFileChangeInOpenDocuments(); | |
| // Active Menu Item deactivation don't refocus | |
| if (MainMenu.Items.OfType<MenuItem>().Any(item => item.IsHighlighted)) | |
| return; | |
| if (_saveIsEditorFocused && Model.ActiveEditor != null) | |
| { | |
| try | |
| { | |
| Model.ActiveEditor.SetEditorFocus(); | |
| Model.ActiveEditor.RestyleEditor(); | |
| } | |
| catch | |
| { | |
| } | |
| } | |
| } | |
| public bool ForceClose = false; | |
| protected override void OnClosing(CancelEventArgs e) | |
| { | |
| // force method to abort - we'll force a close explicitly | |
| e.Cancel = true; | |
| if (ForceClose) | |
| { | |
| // cleanup code already ran | |
| e.Cancel = false; | |
| return; | |
| } | |
| // execute shutdown logic - set ForceClose and call Close() to | |
| // actually close the window | |
| Dispatcher.InvokeAsync(ShutdownApplication, DispatcherPriority.Normal); | |
| } | |
| private void ShutdownApplication() | |
| { | |
| try | |
| { | |
| // have to do this here to capture open windows etc. in SaveSettings() | |
| mmApp.Configuration.ApplicationUpdates.AccessCount++; | |
| _previewBrowserWindow?.Close(); | |
| _previewBrowserWindow = null; | |
| PreviewBrowser = null; | |
| PreviewBrowserContainer = null; | |
| SaveSettings(); | |
| if (!CloseAllTabs()) | |
| { | |
| // tab closing was cancelled | |
| mmApp.Configuration.ApplicationUpdates.AccessCount--; | |
| return; | |
| } | |
| // hide the window quickly | |
| Top -= 10000; | |
| FolderBrowser?.ReleaseFileWatcher(); | |
| bool isNewVersion = ApplicationUpdater.CheckForNewVersion(false, false); | |
| var displayCount = 6; | |
| if (mmApp.Configuration.ApplicationUpdates.AccessCount > 250) | |
| displayCount = 1; | |
| else if (mmApp.Configuration.ApplicationUpdates.AccessCount > 100) | |
| displayCount = 2; | |
| else if (mmApp.Configuration.ApplicationUpdates.AccessCount > 50) | |
| displayCount = 5; | |
| if (!isNewVersion && mmApp.Configuration.ApplicationUpdates.AccessCount % displayCount == 0 && | |
| !UnlockKey.IsAppRegistered()) | |
| { | |
| // bring the window back | |
| Top += 10000; | |
| Opacity = 0; | |
| var rd = new RegisterDialog(true); | |
| rd.Owner = this; | |
| rd.ShowDialog(); | |
| } | |
| PipeManager?.StopServer(); | |
| WebServer?.StopServer(); | |
| AddinManager.Current.RaiseOnApplicationShutdown(); | |
| AddinManager.Current.UnloadAddins(); | |
| App.Mutex?.Dispose(); | |
| PipeManager?.WaitForThreadShutDown(5000); | |
| mmApp.Shutdown(); | |
| ForceClose = true; | |
| Close(); | |
| } | |
| catch (Exception ex) | |
| { | |
| mmApp.Log("Shutdown: " + ex.Message, ex, logLevel: LogLevels.Error); | |
| ForceClose = true; | |
| Close(); | |
| } | |
| } | |
| public void AddRecentFile(string file, bool noConfigWrite = false) | |
| { | |
| Dispatcher.InvokeAsync(() => | |
| { | |
| mmApp.Configuration.AddRecentFile(file); | |
| if (!noConfigWrite) | |
| mmApp.Configuration.Write(); | |
| try | |
| { | |
| MostRecentlyUsedList.AddToRecentlyUsedDocs(Path.GetFullPath(file)); | |
| } | |
| catch | |
| { | |
| } | |
| }, DispatcherPriority.ApplicationIdle); | |
| } | |
| /// <summary> | |
| /// Adds a fontawesome icon to the editor toolbar (or any toolbar you specify explicitly). | |
| /// Specify the FontAwesome Icon image name (FontAwesome.Wpf proper Case Syntax) | |
| /// </summary> | |
| /// <param name="iconName">FontAwesome.WPF Icon name (proper case name)</param> | |
| /// <param name="markdownActionCommand">string action from MarkupMarkdow() implementation or wrap with `html|tag`</param> | |
| /// <param name="toolbar"></param> | |
| /// <param name="command"></param> | |
| public void AddEditToolbarIcon(string iconName, string markdownActionCommand, ToolBar toolbar = null, | |
| ICommand command = null) | |
| { | |
| ImageSource icon = null; | |
| if (FontAwesomeIcon.TryParse(iconName, out FontAwesomeIcon iconId)) | |
| { | |
| icon = ImageAwesome.CreateImageSource(iconId, ToolbarEdit.Foreground); | |
| } | |
| AddEditToolbarIcon(icon, markdownActionCommand, toolbar, command); | |
| } | |
| /// <summary> | |
| /// Adds an image icon to the editor toolbar | |
| /// </summary> | |
| /// <param name="icon">An image source icon - should size well for 16px high</param> | |
| /// <param name="markdownActionCommand">string action from MarkupMarkdow() implementation or wrap with `html|tag`</param> | |
| /// <param name="toolbar"></param> | |
| /// <param name="command"></param> | |
| public void AddEditToolbarIcon(ImageSource icon, string markdownActionCommand, ToolBar toolbar = null, | |
| ICommand command = null) | |
| { | |
| if (toolbar == null) toolbar = ToolbarEdit; | |
| if (command == null) command = Model.Commands.ToolbarInsertMarkdownCommand; | |
| var tb = new Button() { Command = command, CommandParameter = markdownActionCommand }; | |
| if (icon == null) | |
| icon = ImageAwesome.CreateImageSource(FontAwesomeIcon.QuestionCircle, ToolbarEdit.Foreground); | |
| tb.Content = new Image() | |
| { | |
| Source = icon, | |
| Height = 16, | |
| Margin = new Thickness(5, 0, 5, 0), | |
| ToolTip = markdownActionCommand | |
| }; | |
| var idx = toolbar.Items.IndexOf(ButtonEmoji); | |
| if (idx > -1) | |
| toolbar.Items.Insert(idx + 1, tb); | |
| else | |
| toolbar.Items.Add(tb); | |
| } | |
| private TabItem OpenRecentDocuments() | |
| { | |
| ApplicationConfiguration conf = Model.Configuration; | |
| TabItem selectedTab = null; | |
| // prevent TabSelectionChanged to fire | |
| batchTabAction = true; | |
| // since docs are inserted at the beginning we need to go in reverse | |
| foreach (var doc in conf.OpenDocuments.Take(mmApp.Configuration.RememberLastDocumentsLength)) //.Reverse(); | |
| { | |
| if (doc.Filename == null) | |
| continue; | |
| if (File.Exists(doc.Filename)) | |
| { | |
| var tab = OpenTab(doc.Filename, selectTab: false, | |
| batchOpen: true, | |
| initialLineNumber: doc.LastEditorLineNumber); | |
| if (tab == null) | |
| continue; | |
| var editor = tab.Tag as MarkdownDocumentEditor; | |
| if (editor == null) | |
| continue; | |
| if (doc.IsActive) | |
| { | |
| selectedTab = tab; | |
| // have to explicitly notify initial activation | |
| // since we surpress it on all tabs during startup | |
| AddinManager.Current.RaiseOnDocumentActivated(editor.MarkdownDocument); | |
| } | |
| } | |
| } | |
| batchTabAction = false; | |
| return selectedTab; | |
| } | |
| void RestoreSettings() | |
| { | |
| var conf = mmApp.Configuration; | |
| if (conf.WindowPosition.Width != 0) | |
| { | |
| Left = conf.WindowPosition.Left; | |
| Top = conf.WindowPosition.Top; | |
| Width = conf.WindowPosition.Width; | |
| Height = conf.WindowPosition.Height; | |
| WindowUtilities.EnsureWindowIsVisible(this); | |
| } | |
| if (conf.WindowPosition.WindowState == WindowState.Maximized) | |
| Dispatcher.InvokeAsync(() => WindowState = WindowState.Maximized, DispatcherPriority.ApplicationIdle); | |
| if (mmApp.Configuration.RememberLastDocumentsLength > 0 && mmApp.Configuration.UseSingleWindow) | |
| { | |
| //var selectedDoc = conf.RecentDocuments.FirstOrDefault(); | |
| TabItem selectedTab = null; | |
| string firstDoc = conf.RecentDocuments.FirstOrDefault(); | |
| if (!App.ForceNewWindow) | |
| selectedTab = OpenRecentDocuments(); | |
| TabControl.SelectedIndex = -1; | |
| TabControl.SelectedItem = null; | |
| if (selectedTab == null) | |
| TabControl.SelectedIndex = 0; | |
| else | |
| TabControl.SelectedItem = selectedTab; | |
| BindTabHeaders(); | |
| } | |
| Model.IsPreviewBrowserVisible = mmApp.Configuration.IsPreviewVisible; | |
| ShowFolderBrowser(!mmApp.Configuration.FolderBrowser.Visible); | |
| // force background so we have a little more contrast | |
| if (mmApp.Configuration.ApplicationTheme == Themes.Light) | |
| { | |
| ContentGrid.Background = (SolidColorBrush) new BrushConverter().ConvertFromString("#eee"); | |
| ToolbarPanelMain.Background = (SolidColorBrush) new BrushConverter().ConvertFromString("#D5DAE8"); | |
| } | |
| else | |
| ContentGrid.Background = (SolidColorBrush) new BrushConverter().ConvertFromString("#333"); | |
| //Button Name = "ButtonLink" Margin = "7,0" ToolTip = "Insert link (Ctrl+K)" | |
| //Command = "{Binding Commands.ToolbarInsertMarkdownCommand }" | |
| //CommandParameter = "href" | |
| //fa: Awesome.Content = "ExternalLink" | |
| //TextElement.FontFamily = "pack://application:,,,/FontAwesome.WPF;component/#FontAwesome" | |
| // /> | |
| foreach (var buttonItem in Model.Configuration.Editor.AdditionalToolbarIcons) | |
| { | |
| AddEditToolbarIcon(buttonItem.Key, buttonItem.Value); | |
| } | |
| } | |
| /// <summary> | |
| /// Save active settings of the UI that are persisted in the configuration | |
| /// </summary> | |
| public void SaveSettings() | |
| { | |
| var config = mmApp.Configuration; | |
| if (Model != null) | |
| config.IsPreviewVisible = Model.IsPreviewBrowserVisible; | |
| config.WindowPosition.IsTabHeaderPanelVisible = true; | |
| if (WindowState == WindowState.Normal) | |
| { | |
| config.WindowPosition.Left = mmFileUtils.TryConvertToInt32(Left); | |
| config.WindowPosition.Top = mmFileUtils.TryConvertToInt32(Top); | |
| config.WindowPosition.Width = mmFileUtils.TryConvertToInt32(Width, 900); | |
| config.WindowPosition.Height = mmFileUtils.TryConvertToInt32(Height, 700); | |
| } | |
| if (WindowState != WindowState.Minimized) | |
| config.WindowPosition.WindowState = WindowState; | |
| if (LeftSidebarColumn.Width.Value > 20) | |
| { | |
| if (LeftSidebarColumn.Width.IsAbsolute) | |
| config.FolderBrowser.WindowWidth = | |
| mmFileUtils.TryConvertToInt32(LeftSidebarColumn.Width.Value, 220); | |
| config.FolderBrowser.Visible = true; | |
| } | |
| else | |
| config.FolderBrowser.Visible = false; | |
| config.FolderBrowser.FolderPath = FolderBrowser.FolderPath; | |
| if (!App.ForceNewWindow) | |
| SaveOpenDocuments(); | |
| else | |
| { | |
| // only save if no other instances are open | |
| if (!Process.GetProcesses() | |
| .Any(p => p.ProcessName.Equals("markdownmonster", StringComparison.InvariantCultureIgnoreCase))) | |
| SaveOpenDocuments(); | |
| } | |
| config.Write(); | |
| } | |
| /// <summary> | |
| /// Keeps track of the open documents based on the tabs | |
| /// that are open along with the tab order. | |
| /// </summary> | |
| void SaveOpenDocuments() | |
| { | |
| var config = Model.Configuration; | |
| config.OpenDocuments.Clear(); | |
| if (mmApp.Configuration.RememberLastDocumentsLength > 0) | |
| { | |
| IEnumerable<DragablzItem> headers = null; | |
| try | |
| { | |
| var ditems = GetDragablzItems(); | |
| headers = TabControl.HeaderItemsOrganiser.Sort(ditems); | |
| } | |
| catch (Exception ex) | |
| { | |
| mmApp.Log("TabControl.GetOrderedHeaders() failed. Saving unordered.", ex, | |
| logLevel: LogLevels.Warning); | |
| // This works, but doesn't keep tab order intact | |
| headers = new List<DragablzItem>(); | |
| foreach (var recent in config.RecentDocuments.Take(config.RememberLastDocumentsLength)) | |
| { | |
| var tab = GetTabFromFilename(recent); | |
| if (tab != null) | |
| ((List<DragablzItem>) headers).Add(new DragablzItem() {Content = tab}); | |
| } | |
| } | |
| if (headers != null) | |
| { | |
| // Important: collect all open tabs in the **original tab order** | |
| foreach (var dragablzItem in headers) | |
| { | |
| if (dragablzItem == null) | |
| continue; | |
| var tab = dragablzItem.Content as TabItem; | |
| var editor = tab.Tag as MarkdownDocumentEditor; | |
| var doc = editor?.MarkdownDocument; | |
| if (doc == null) | |
| continue; | |
| doc.LastEditorLineNumber = editor.GetLineNumber(); | |
| if (doc.LastEditorLineNumber < 1) | |
| doc.LastEditorLineNumber = editor.InitialLineNumber; // if document wasn't accessed line is never set | |
| if (doc.LastEditorLineNumber < 0) | |
| doc.LastEditorLineNumber = 0; | |
| config.OpenDocuments.Add(new OpenFileDocument(doc)); | |
| } | |
| } | |
| // now figure out which were recent | |
| var recents = mmApp.Configuration.RecentDocuments.Take(mmApp.Configuration.RememberLastDocumentsLength) | |
| .ToList(); | |
| // remove all those that aren't in the recent list | |
| var removeList = new List<OpenFileDocument>(); | |
| foreach (var doc in config.OpenDocuments) | |
| { | |
| if (!recents.Any(r => r.Equals(doc.Filename, StringComparison.InvariantCultureIgnoreCase))) | |
| removeList.Add(doc); | |
| } | |
| foreach (var remove in removeList) | |
| config.OpenDocuments.Remove(remove); | |
| } | |
| } | |
| public bool SaveFile(bool secureSave = false) | |
| { | |
| var tab = TabControl.SelectedItem as TabItem; | |
| if (tab == null) | |
| return false; | |
| var editor = tab.Tag as MarkdownDocumentEditor; | |
| var doc = editor?.MarkdownDocument; | |
| if (doc == null) | |
| return false; | |
| // prompt for password on a secure save | |
| if (secureSave && editor.MarkdownDocument.Password == null) | |
| { | |
| var pwdDialog = new FilePasswordDialog(editor.MarkdownDocument, false); | |
| pwdDialog.ShowDialog(); | |
| } | |
| if (!editor.SaveDocument()) | |
| { | |
| //var res = await this.ShowMessageOverlayAsync("Unable to save Document", | |
| // "Unable to save document most likely due to missing permissions."); | |
| MessageBox.Show("Unable to save document most likely due to missing permissions.", | |
| mmApp.ApplicationName); | |
| return false; | |
| } | |
| return true; | |
| } | |
| #endregion | |
| #region Open From Command Line and Singleton Pipe | |
| public WebServer WebServer = null; | |
| /// <summary> | |
| /// Opens files from the command line or from an array of strings | |
| /// </summary> | |
| /// <param name="args">Array of file names. If null Command Line Args are used.</param> | |
| public void OpenFilesFromCommandLine(string[] args = null) | |
| { | |
| if (args == null) | |
| { | |
| // read fixed up command line args | |
| args = App.CommandArgs; | |
| if (args == null || args.Length == 0) // no args, only command line | |
| return; | |
| } | |
| var autoSave = App.CommandArgs.Any(a => a.Equals("-autosave", StringComparison.InvariantCultureIgnoreCase)); | |
| bool closeNextFile = false; | |
| foreach (var fileArgs in args) | |
| { | |
| var file = fileArgs; | |
| if (string.IsNullOrEmpty(file)) | |
| continue; | |
| // handle file closing | |
| if (file == "-close") | |
| { | |
| closeNextFile = true; | |
| continue; | |
| } | |
| if (closeNextFile) | |
| { | |
| closeNextFile = false; | |
| var tab = GetTabFromFilename(file); | |
| if (tab != null) | |
| { | |
| if (tab.Tag is MarkdownDocumentEditor editor) | |
| { | |
| if (editor.IsDirty()) | |
| editor.SaveDocument(); | |
| CloseTab(tab, dontPromptForSave: true); | |
| if (TabControl.Items.Count < 1) | |
| { | |
| WindowUtilities.DoEvents(); | |
| Close(); | |
| return; | |
| } | |
| } | |
| } | |
| continue; | |
| } | |
| string ext = string.Empty; | |
| file = file.TrimEnd('\\'); | |
| // file monikers - just strip first | |
| if (file.StartsWith("markdownmonster:webserver")) | |
| { | |
| if (WebServer == null) | |
| WebServerLauncher.StartMarkdownMonsterWebServer(); | |
| else | |
| WebServerLauncher.StopMarkdownMonsterWebServer(); | |
| continue; | |
| } | |
| if (file.StartsWith("markdownmonster:")) | |
| file = WebUtility.UrlDecode(file.Replace("markdownmonster:",String.Empty)); | |
| else if (file.StartsWith("markdown:")) | |
| file = WebUtility.UrlDecode(file.Replace("markdown:",String.Empty)); | |
| bool isUntitled = file.Equals("untitled", StringComparison.OrdinalIgnoreCase); | |
| if (file.StartsWith("untitled.")) | |
| isUntitled = true; | |
| if (!isUntitled) | |
| { | |
| try | |
| { | |
| // FAIL: This fails at runtime not in debugger when value is .\ trimmed to . VERY WEIRD | |
| file = Path.GetFullPath(file); | |
| ext = Path.GetExtension(file); | |
| } | |
| catch(Exception ex) | |
| { | |
| mmApp.Log("CommandLine file path resolution failed: " + file, ex); | |
| } | |
| } | |
| Topmost = true; | |
| WindowUtilities.DoEvents(); | |
| // open an empty doc or new doc with preset text from base64 (untitled.base64text) | |
| if (isUntitled) | |
| { | |
| string docText = null; | |
| // untitled.base64text will decode the base64 text | |
| if (file.StartsWith("untitled.")) | |
| { | |
| docText = CommandLineTextEncoder.ParseUntitledString(file); | |
| } | |
| // open empty document, or fill with App.StartupText is set | |
| Model.Commands.NewDocumentCommand.Execute(docText); | |
| } | |
| else if (File.Exists(file)) | |
| { | |
| // open file which may or may not open a tab (like a project) | |
| var tab = OpenFile(filename: file, batchOpen: true); | |
| //var tab = OpenTab(mdFile: file, batchOpen: true); | |
| if (tab?.Tag is MarkdownDocumentEditor editor) | |
| { | |
| editor.MarkdownDocument.AutoSaveBackup = Model.Configuration.AutoSaveBackups; | |
| editor.MarkdownDocument.AutoSaveDocument = autoSave || Model.Configuration.AutoSaveDocuments; | |
| } | |
| } | |
| else if (Directory.Exists(file)) | |
| { | |
| ShowFolderBrowser(false, file); | |
| } | |
| // file is an .md file but doesn't exist but folder exists - create it | |
| else if ((ext.Equals(".md", StringComparison.InvariantCultureIgnoreCase) || | |
| ext.Equals(".mkdown", StringComparison.InvariantCultureIgnoreCase) || | |
| ext.Equals(".markdown", StringComparison.InvariantCultureIgnoreCase) || | |
| ext.Equals(".mdcrypt", StringComparison.InvariantCultureIgnoreCase) | |
| ) && | |
| Directory.Exists(Path.GetDirectoryName(file))) | |
| { | |
| File.WriteAllText(file, ""); | |
| var tab = OpenTab(mdFile: file, batchOpen: true); | |
| if (tab?.Tag is MarkdownDocumentEditor editor) | |
| { | |
| editor.MarkdownDocument.AutoSaveBackup = Model.Configuration.AutoSaveBackups; | |
| editor.MarkdownDocument.AutoSaveDocument = autoSave || Model.Configuration.AutoSaveDocuments; | |
| } | |
| // delete the file if we abort | |
| Dispatcher.Delay(1000, p => File.Delete(file), null); | |
| } | |
| else | |
| { | |
| file = Path.Combine(App.InitialStartDirectory, file); | |
| file = Path.GetFullPath(file); | |
| if (File.Exists(file)) | |
| { | |
| var tab = OpenTab(mdFile: file, batchOpen: true); | |
| if (tab.Tag is MarkdownDocumentEditor editor) | |
| { | |
| editor.MarkdownDocument.AutoSaveBackup = Model.Configuration.AutoSaveBackups; | |
| editor.MarkdownDocument.AutoSaveDocument = | |
| autoSave || Model.Configuration.AutoSaveDocuments; | |
| } | |
| } | |
| else if (Directory.Exists(file)) | |
| ShowFolderBrowser(false, file); | |
| } | |
| Dispatcher.Delay(800, s => { Topmost = false; }); | |
| } | |
| } | |
| private void HandleNamedPipe_OpenRequest(string filesToOpen) | |
| { | |
| Dispatcher.Invoke(() => | |
| { | |
| if (!string.IsNullOrEmpty(filesToOpen)) | |
| { | |
| var parms = StringUtils.GetLines(filesToOpen.Trim()); | |
| OpenFilesFromCommandLine(parms); | |
| BindTabHeaders(); | |
| } | |
| Topmost = true; | |
| if (WindowState == WindowState.Minimized) | |
| WindowState = WindowState.Normal; | |
| Activate(); | |
| Dispatcher.BeginInvoke(new Action(() => { Topmost = false; }), DispatcherPriority.ApplicationIdle); | |
| }); | |
| } | |
| #endregion | |
| #region Tab Handling | |
| /// <summary> | |
| /// High level wrapper around OpenTab() that checks for | |
| /// different file types like images and projects that | |
| /// have non-tab behavior. | |
| /// | |
| /// Use this function for generically opening files | |
| /// by filename . | |
| /// </summary> | |
| /// <param name="filename"></param> | |
| /// <param name="showPreviewIfActive"></param> | |
| /// <param name="syntax"></param> | |
| /// <param name="selectTab"></param> | |
| /// <param name="rebindTabHeaders"></param> | |
| /// <param name="batchOpen"></param> | |
| /// <param name="initialLineNumber"></param> | |
| /// <param name="readOnly"></param> | |
| /// <param name="noFocus"></param> | |
| /// <param name="isPreview"></param> | |
| /// <returns>Tab or Null</returns> | |
| public TabItem OpenFile(string filename, | |
| MarkdownDocumentEditor editor = null, | |
| bool showPreviewIfActive = false, | |
| string syntax = "markdown", | |
| bool selectTab = true, | |
| bool rebindTabHeaders = false, | |
| bool batchOpen = false, | |
| int initialLineNumber = 0, | |
| bool readOnly = false, | |
| bool noFocus = false, | |
| bool isPreview = false) | |
| { | |
| var ext = Path.GetExtension(filename).ToLowerInvariant(); | |
| if (ext == ".jpg" || ext == ".png" || ext == ".gif" || ext == ".jpeg") | |
| { | |
| return OpenBrowserTab(filename, isImageFile: true); | |
| } | |
| if (ext == ".mdproj") | |
| { | |
| Model.Commands.LoadProjectCommand.Execute(filename); | |
| return null; | |
| } | |
| // Executables - don't execute but open in Explorer | |
| if (StringUtils.Inlist(ext, ".exe", ".ps1", ".bat", ".cmd", ".vbs", ".sh", ".com", ".reg")) | |
| { | |
| ShellUtils.OpenFileInExplorer(filename); | |
| return null; | |
| } | |
| string format = mmFileUtils.GetEditorSyntaxFromFileType(filename); | |
| // Open a Tab for editable formats | |
| if (!string.IsNullOrEmpty(format)) | |
| { | |
| if (!noFocus && PreviewTab != null) | |
| CloseTab(PreviewTab); | |
| var tab = ActivateTab(filename, false, false, !showPreviewIfActive, | |
| !selectTab, noFocus, readOnly, isPreview); | |
| if (tab != null) | |
| return tab; | |
| return OpenTab(filename, editor, showPreviewIfActive, syntax, | |
| selectTab, rebindTabHeaders, batchOpen, | |
| initialLineNumber, readOnly, noFocus, isPreview); | |
| } | |
| try | |
| { | |
| if (File.Exists(filename)) | |
| ShellUtils.GoUrl(filename); | |
| else | |
| ShowStatusError($"Can't open file {filename}. File doesn't exist."); | |
| } | |
| catch | |
| { | |
| ShowStatusError($"Unable to open file {filename}"); | |
| } | |
| return null; | |
| } | |
| /// <summary> | |
| /// Opens a tab by a filename | |
| /// </summary> | |
| /// <param name="mdFile"></param> | |
| /// <param name="editor"></param> | |
| /// <param name="showPreviewIfActive"></param> | |
| /// <param name="syntax"></param> | |
| /// <param name="selectTab"></param> | |
| /// <param name="rebindTabHeaders"> | |
| /// Rebinds the headers which should be done whenever a new Tab is | |
| /// manually opened and added but not when opening in batch. | |
| /// | |
| /// Checks to see if multiple tabs have the same filename open and | |
| /// if so displays partial path. | |
| /// | |
| /// New Tabs are opened at the front of the tab list at index 0 | |
| /// </param> | |
| /// <param name="batchOpen"></param> | |
| /// <param name="initialLineNumber"></param> | |
| /// <param name="readOnly"></param> | |
| /// <param name="noFocus"></param> | |
| /// <param name="isPreview"></param> | |
| /// <returns></returns> | |
| public TabItem OpenTab(string mdFile = null, | |
| MarkdownDocumentEditor editor = null, | |
| bool showPreviewIfActive = false, | |
| string syntax = "markdown", | |
| bool selectTab = true, | |
| bool rebindTabHeaders = false, | |
| bool batchOpen = false, | |
| int initialLineNumber = 0, | |
| bool readOnly = false, | |
| bool noFocus = false, | |
| bool isPreview = false) | |
| { | |
| if (mdFile != null && mdFile != "untitled" && | |
| (!File.Exists(mdFile) || | |
| !AddinManager.Current.RaiseOnBeforeOpenDocument(mdFile))) | |
| return null; | |
| var tab = new TabItem(); | |
| tab.Background = Background; | |
| if (mdFile != null) | |
| { | |
| var ext = Path.GetExtension(mdFile).ToLowerInvariant(); | |
| if (ext == ".jpg" || ext == ".png" || ext == ".gif" || ext == ".jpeg") | |
| { | |
| return OpenBrowserTab(mdFile, isImageFile: true); | |
| } | |
| } | |
| ControlsHelper.SetHeaderFontSize(tab, 13F); | |
| if (editor == null) | |
| { | |
| editor = new MarkdownDocumentEditor | |
| { | |
| Window = this, | |
| EditorSyntax = syntax, | |
| InitialLineNumber = initialLineNumber, | |
| IsReadOnly = readOnly, | |
| NoInitialFocus = noFocus, | |
| IsPreview = isPreview | |
| }; | |
| tab.Content = editor.EditorPreviewPane; | |
| tab.Tag = editor; | |
| // tab is temporary until edited | |
| if (isPreview) | |
| { | |
| if (PreviewTab != null && PreviewTab != tab) | |
| TabControl.Items.Remove(PreviewTab); | |
| PreviewTab = tab; | |
| } | |
| var doc = new MarkdownDocument() {Filename = mdFile ?? "untitled", Dispatcher = Dispatcher}; | |
| if (doc.Filename != "untitled") | |
| { | |
| doc.Filename = FileUtils.GetPhysicalPath(doc.Filename); | |
| if (doc.HasBackupFile()) | |
| { | |
| try | |
| { | |
| ShowStatusError("Auto-save recovery files have been found and opened in the editor."); | |
| { | |
| File.Copy(doc.BackupFilename, doc.BackupFilename + ".md"); | |
| OpenTab(doc.BackupFilename + ".md"); | |
| File.Delete(doc.BackupFilename + ".md"); | |
| } | |
| } | |
| catch (Exception ex) | |
| { | |
| string msg = "Unable to open backup file: " + doc.BackupFilename + ".md"; | |
| mmApp.Log(msg, ex); | |
| MessageBox.Show( | |
| "A backup file was previously saved, but we're unable to open it.\r\n" + msg, | |
| "Cannot open backup file", | |
| MessageBoxButton.OK, | |
| MessageBoxImage.Warning); | |
| } | |
| } | |
| if (doc.Password == null && doc.IsFileEncrypted()) | |
| { | |
| var pwdDialog = new FilePasswordDialog(doc, true) {Owner = this}; | |
| bool? pwdResult = pwdDialog.ShowDialog(); | |
| if (pwdResult == false) | |
| { | |
| ShowStatus("Encrypted document not opened, due to missing password.", | |
| mmApp.Configuration.StatusMessageTimeout); | |
| return null; | |
| } | |
| } | |
| if (!doc.Load()) | |
| { | |
| if (!batchOpen) | |
| { | |
| var msg = "Most likely you don't have access to the file"; | |
| if (doc.Password != null && doc.IsFileEncrypted()) | |
| msg = "Invalid password for opening this file"; | |
| var file = Path.GetFileName(doc.Filename); | |
| MessageBox.Show( | |
| $"{msg}.\r\n\r\n{file}", | |
| "Can't open File", MessageBoxButton.OK, | |
| MessageBoxImage.Warning); | |
| } | |
| return null; | |
| } | |
| } | |
| editor.MarkdownDocument = doc; | |
| SetTabHeaderBinding(tab, doc, "FilenameWithIndicator"); | |
| } | |
| else | |
| tab.Tag = editor; | |
| var filename = Path.GetFileName(editor.MarkdownDocument.Filename); | |
| editor.LoadDocument(); | |
| // is the tab already open? | |
| TabItem existingTab = null; | |
| if (filename != "untitled") | |
| { | |
| foreach (TabItem tb in TabControl.Items) | |
| { | |
| var lEditor = tb.Tag as MarkdownDocumentEditor; | |
| if (lEditor == null) | |
| continue; | |
| if (lEditor.MarkdownDocument.Filename == editor.MarkdownDocument.Filename) | |
| { | |
| existingTab = tb; | |
| break; | |
| } | |
| } | |
| } | |
| if (existingTab != null) | |
| TabControl.Items.Remove(existingTab); | |
| tab.IsSelected = false; | |
| if (TabControl.Items.Count > 0) | |
| TabablzControl.AddItem(tab, TabControl.Items[0], AddLocationHint.First); | |
| else | |
| TabControl.Items.Insert(0, tab); | |
| // Make the tab draggable for moving into bookmarks or anything else that can accept filenames | |
| // have to drag down - sideways drag re-orders. | |
| try | |
| { | |
| var dragablzItem = GetDragablzItemFromTabItem(tab); | |
| if (dragablzItem != null) | |
| { | |
| dragablzItem.PreviewMouseMove += DragablzItem_PreviewMouseMove; | |
| dragablzItem.PreviewMouseLeftButtonDown += DragablzItem_PreviewMouseLeftButtonDown; | |
| //dragablzItem.PreviewGiveFeedback += (object sender, GiveFeedbackEventArgs e) => | |
| //{ | |
| // //e.Effects = DragDropEffects.Copy; | |
| // e.UseDefaultCursors = true; | |
| // if (Cursor != Cursors.Cross) | |
| // Mouse.SetCursor(Cursors.Cross); | |
| //}; | |
| } | |
| } | |
| catch (Exception ex) | |
| { | |
| // this failure can be caused if there's a modal dialog popped up when the app starts | |
| mmApp.Log("Failed to load DragablzItem for tab drag and drop", ex, false, LogLevels.Warning); | |
| } | |
| if (selectTab) | |
| { | |
| TabControl.SelectedItem = tab; | |
| SetWindowTitle(); | |
| } | |
| if (!isPreview) | |
| { | |
| Model.OpenDocuments.Add(editor.MarkdownDocument); | |
| if (!string.IsNullOrEmpty(editor.MarkdownDocument.Filename) && | |
| !editor.MarkdownDocument.Filename.Equals("untitled", StringComparison.InvariantCultureIgnoreCase)) | |
| Model.Configuration.LastFolder = Path.GetDirectoryName(editor.MarkdownDocument.Filename); | |
| } | |
| AddinManager.Current.RaiseOnAfterOpenDocument(editor.MarkdownDocument); | |
| if (rebindTabHeaders) | |
| BindTabHeaders(); | |
| // force tabstate bindings to update | |
| Model.OnPropertyChanged(nameof(AppModel.IsTabOpen)); | |
| Model.OnPropertyChanged(nameof(AppModel.IsNoTabOpen)); | |
| return tab; | |
| } | |
| private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e) | |
| { | |
| if (batchTabAction) | |
| return; | |
| var editor = GetActiveMarkdownEditor(); | |
| if (editor == null) | |
| return; | |
| var tab = TabControl.SelectedItem as TabItem; | |
| SetWindowTitle(); | |
| foreach (var doc in Model.OpenDocuments) | |
| doc.IsActive = false; | |
| Model.ActiveDocument = editor.MarkdownDocument; | |
| Model.ActiveDocument.IsActive = true; | |
| AddRecentFile(Model.ActiveDocument?.Filename, noConfigWrite: true); | |
| AddinManager.Current.RaiseOnDocumentActivated(Model.ActiveDocument); | |
| ((Grid) PreviewBrowserContainer.Parent)?.Children.Remove(PreviewBrowserContainer); | |
| editor.EditorPreviewPane.PreviewBrowserContainer.Children.Add(PreviewBrowserContainer); | |
| if (tab.Content is Grid grid) | |
| grid.Children.Add(PreviewBrowserContainer); | |
| // handle preview tab closing | |
| if (PreviewTab != null && tab != PreviewTab) | |
| { | |
| if (PreviewTab.Tag == null) | |
| CloseTab(PreviewTab); // browser preview | |
| else | |
| { | |
| // preview Markdown Tab | |
| var changedDoc = PreviewTab.Tag as MarkdownDocumentEditor; | |
| if (changedDoc != null) | |
| { | |
| if (changedDoc.IsDirty()) | |
| { | |
| // keep the document open | |
| changedDoc.IsPreview = false; | |
| PreviewTab = null; | |
| } | |
| else | |
| CloseTab(PreviewTab); | |
| } | |
| } | |
| } | |
| Model.WindowLayout.IsPreviewVisible = mmApp.Configuration.IsPreviewVisible; | |
| if (mmApp.Configuration.IsPreviewVisible) | |
| PreviewBrowser?.PreviewMarkdown(); | |
| editor.RestyleEditor(); | |
| // Don't automatically set focus - we need to do this explicitly | |
| //editor.SetEditorFocus(); | |
| Dispatcher.InvokeAsync(() => { UpdateDocumentOutline(); }, DispatcherPriority.ApplicationIdle); | |
| } | |
| /// <summary> | |
| /// Refreshes an already loaded tab with contents of a new (or the same file) file | |
| /// by just replacing the document's text. | |
| /// | |
| /// If the tab is not found a new tab is opened. | |
| /// | |
| /// Note: File must already be open for this to work | |
| /// </summary> | |
| /// <param name="editorFile">File name to display int the tab</param> | |
| /// <param name="maintainScrollPosition">If possible preserve scroll position if refreshing</param> | |
| /// <param name="noPreview">If true don't refresh the preview after updating the file</param> | |
| /// <param name="noSelectTab"></param> | |
| /// <param name="noFocus">if true don't focus the editor</param> | |
| /// <param name="readOnly">if true document can't be edited</param> | |
| /// <param name="isPreview">Determines whether this tab is treated like a preview tab</param> | |
| /// <returns>selected tab item or null</returns> | |
| public TabItem RefreshTabFromFile(string editorFile, | |
| bool maintainScrollPosition = false, | |
| bool noPreview = false, | |
| bool noSelectTab = false, | |
| bool noFocus = false, | |
| bool readOnly = false, | |
| bool isPreview = false) | |
| { | |
| var tab = GetTabFromFilename(editorFile); | |
| if (tab == null) | |
| return OpenTab(editorFile, rebindTabHeaders: true, readOnly: readOnly, noFocus: noFocus, | |
| selectTab: !noSelectTab, isPreview: isPreview); | |
| // load the underlying document | |
| var editor = tab.Tag as MarkdownDocumentEditor; | |
| if (editor == null) | |
| return null; | |
| editor.IsPreview = isPreview; | |
| if (isPreview) | |
| PreviewTab = tab; | |
| else | |
| PreviewTab = null; | |
| editor.MarkdownDocument.Load(editorFile); | |
| if (!maintainScrollPosition) | |
| editor.SetCursorPosition(0, 0); | |
| editor.SetMarkdown(editor.MarkdownDocument.CurrentText); | |
| if (!noSelectTab) | |
| TabControl.SelectedItem = tab; | |
| if (!noFocus) | |
| editor.SetEditorFocus(); | |
| editor.IsPreview = isPreview; | |
| if (!noPreview) | |
| PreviewMarkdownAsync(); | |
| return tab; | |
| } | |
| /// <summary> | |
| /// Activates a tab from an active tab instance | |
| /// </summary> | |
| /// <param name="tab"></param> | |
| /// <returns></returns> | |
| public TabItem ActivateTab(TabItem tab, bool setFocus = false) | |
| { | |
| TabControl.SelectedItem = tab; | |
| if (setFocus) | |
| Dispatcher.Invoke(() => Model.ActiveEditor.SetEditorFocus(), DispatcherPriority.ApplicationIdle); | |
| return tab; | |
| } | |
| /// <summary> | |
| /// Activates a tab by checking from a filename and activating | |
| /// or optionally open the a new tab. | |
| /// </summary> | |
| /// <param name="filename"></param> | |
| /// <returns></returns> | |
| public TabItem ActivateTab(string filename, bool openIfNotFound = false, | |
| bool maintainScrollPosition = false, | |
| bool noPreview = false, | |
| bool noSelectTab = false, | |
| bool noFocus = false, | |
| bool readOnly = false, | |
| bool isPreview = false) | |
| { | |
| var tab = GetTabFromFilename(filename); | |
| if (tab == null) | |
| return OpenTab(filename, rebindTabHeaders: true, readOnly: readOnly, noFocus: noFocus, | |
| selectTab: !noSelectTab, isPreview: isPreview); | |
| // load the underlying document | |
| var editor = tab.Tag as MarkdownDocumentEditor; | |
| if (editor == null) | |
| return null; | |
| editor.IsPreview = isPreview; | |
| if (isPreview) | |
| PreviewTab = tab; | |
| else | |
| PreviewTab = null; | |
| if (!maintainScrollPosition) | |
| editor.SetCursorPosition(0, 0); | |
| if (!noSelectTab) | |
| TabControl.SelectedItem = tab; | |
| if (!noFocus) | |
| editor.SetEditorFocus(); | |
| if (!noPreview) | |
| PreviewMarkdownAsync(); | |
| ActivateTab(tab); | |
| return tab; | |
| } | |
| /// <summary> | |
| /// Opens a preview tab | |
| /// </summary> | |
| /// <param name="url"></param> | |
| /// <param name="selectTab"></param> | |
| /// <returns></returns> | |
| public TabItem OpenBrowserTab(string url, | |
| bool selectTab = true, | |
| bool isImageFile = false, | |
| ImageSource icon = null, | |
| string tabHeaderText = "Preview") | |
| { | |
| // if a document preview tab is open close it | |
| if (PreviewTab?.Tag is MarkdownDocumentEditor) | |
| { | |
| var tab = PreviewTab; | |
| PreviewTab = null; | |
| CloseTab(tab); | |
| } | |
| if (PreviewTab == null) | |
| { | |
| PreviewTab = new TabItem(); | |
| var grid = new Grid(); | |
| PreviewTab.Header = grid; | |
| var col1 = new ColumnDefinition {Width = new GridLength(20)}; | |
| var col2 = new ColumnDefinition {Width = GridLength.Auto}; | |
| grid.ColumnDefinitions.Add(col1); | |
| grid.ColumnDefinitions.Add(col2); | |
| if (icon == null) | |
| { | |
| if (isImageFile) | |
| icon = FolderStructure.IconList.GetIconFromType("image"); | |
| else | |
| icon = FolderStructure.IconList.GetIconFromType("preview"); | |
| } | |
| var img = new Image() | |
| { | |
| Source = icon, Height = 16, Margin = new Thickness(0, 1, 5, 0), Name = "IconImage" | |
| }; | |
| img.SetValue(Grid.ColumnProperty, 0); | |
| grid.Children.Add(img); | |
| var textBlock = new TextBlock | |
| { | |
| Name = "HeaderText", | |
| Text = tabHeaderText, | |
| FontWeight = FontWeights.SemiBold, | |
| FontStyle = FontStyles.Italic | |
| }; | |
| textBlock.SetValue(Grid.ColumnProperty, 1); | |
| grid.Children.Add(textBlock); | |
| ControlsHelper.SetHeaderFontSize(PreviewTab, 13F); | |
| previewBrowser = new IEWebBrowserControl(); | |
| PreviewTab.Content = previewBrowser; | |
| TabControl.Items.Add(PreviewTab); | |
| PreviewTab.HorizontalAlignment = HorizontalAlignment.Right; | |
| PreviewTab.HorizontalContentAlignment = HorizontalAlignment.Right; | |
| } | |
| else | |
| { | |
| if (icon == null) | |
| { | |
| if (isImageFile) | |
| icon = FolderStructure.IconList.GetIconFromType("image"); | |
| else | |
| icon = FolderStructure.IconList.GetIconFromType("preview"); | |
| } | |
| var grid = PreviewTab.Header as Grid; | |
| var imgCtrl = grid.Children[0] as Image; //.FindChild<Image>("IconImage"); | |
| imgCtrl.Source = icon; | |
| var header = grid.Children[1] as TextBlock; // FindChild<TextBlock>("HeaderText"); | |
| header.Text = tabHeaderText; | |
| } | |
| PreviewTab.ToolTip = url; | |
| try | |
| { | |
| if (isImageFile) | |
| { | |
| // Image files get a special preview tab that displays an HTML page with image link | |
| var file = Path.Combine(App.InitialStartDirectory, "PreviewThemes", "ImagePreview.html"); | |
| string fileInfo = null; | |
| try | |
| { | |
| string filename = Path.GetFileName(url); | |
| string fileDimension; | |
| using (var bmp = new Bitmap(url)) | |
| { | |
| fileDimension = $"{bmp.Width}x{bmp.Height}"; | |
| } | |
| var fileSize = ((decimal) (new FileInfo(url).Length) / 1000).ToString("N1"); | |
| fileInfo = $"<b>{filename}</b> - {fileDimension} {fileSize}kb"; | |
| } | |
| catch | |
| { | |
| } | |
| var content = File.ReadAllText(file).Replace("{{imageUrl}}", url).Replace("{{fileInfo}}", fileInfo); | |
| File.WriteAllText(file.Replace("ImagePreview.html", "_ImagePreview.html"), content); | |
| url = Path.Combine(App.InitialStartDirectory, "PreviewThemes", "_ImagePreview.html"); | |
| } | |
| previewBrowser.Navigate(url); | |
| } | |
| catch | |
| { | |
| previewBrowser.Navigate("about: blank"); | |
| } | |
| if (PreviewTab != null && selectTab) | |
| TabControl.SelectedItem = PreviewTab; | |
| // HACK: force refresh of display model | |
| Model.OnPropertyChanged(nameof(AppModel.IsTabOpen)); | |
| Model.OnPropertyChanged(nameof(AppModel.IsNoTabOpen)); | |
| return PreviewTab; | |
| } | |
| /// <summary> | |
| /// Closes a tab and ask for confirmation if the tab doc | |
| /// is dirty. | |
| /// </summary> | |
| /// <param name="tab"></param> | |
| /// <param name="rebindTabHeaders"> | |
| /// When true tab headers are rebound to handle duplicate filenames | |
| /// with path additions. | |
| /// </param> | |
| /// <param name="dontPromptForSave"></param> | |
| /// <returns>true if tab can close, false if it should stay open</returns> | |
| public bool CloseTab(TabItem tab, bool rebindTabHeaders = true, bool dontPromptForSave = false) | |
| { | |
| if (tab == null) | |
| return false; | |
| if (tab == PreviewTab) | |
| { | |
| tab.Content = null; | |
| PreviewTab = null; | |
| TabControl.Items.Remove(tab); | |
| tab.Tag = null; | |
| tab = null; | |
| return true; | |
| } | |
| var editor = tab?.Tag as MarkdownDocumentEditor; | |
| if (editor == null) | |
| return false; | |
| bool returnValue = true; | |
| tab.Padding = new Thickness(200); | |
| var doc = editor.MarkdownDocument; | |
| doc.CleanupBackupFile(); | |
| if (doc.IsDirty && !dontPromptForSave) | |
| { | |
| var res = MessageBox.Show(this, Path.GetFileName(doc.Filename) + "\r\n\r\nhas been modified.\r\n" + | |
| "Do you want to save changes?", | |
| "Save Document", | |
| MessageBoxButton.YesNoCancel, MessageBoxImage.Question, MessageBoxResult.Cancel); | |
| if (res == MessageBoxResult.Cancel) | |
| { | |
| return false; // don't close | |
| } | |
| if (res == MessageBoxResult.No) | |
| { | |
| // close but don't save | |
| } | |
| else | |
| { | |
| if (doc.Filename == "untitled") | |
| Model.Commands.SaveAsCommand.Execute(ButtonSaveAsFile); | |
| else if (!SaveFile()) | |
| returnValue = false; | |
| } | |
| } | |
| doc.LastEditorLineNumber = editor.GetLineNumber(); | |
| if (doc.LastEditorLineNumber == -1) | |
| doc.LastEditorLineNumber = 0; | |
| // *** IMPORTANT: Clean up Tab controls | |
| editor.ReleaseEditor(); | |
| tab.Tag = null; | |
| editor = null; | |
| TabControl.Items.Remove(tab); | |
| tab = null; | |
| if (TabControl.Items.Count == 0) | |
| { | |
| Model.ActiveDocument = null; | |
| StatusStats.Text = null; | |
| TabDocumentOutline.Visibility = Visibility.Collapsed; | |
| if (SidebarContainer.SelectedItem == TabDocumentOutline) | |
| SidebarContainer.SelectedItem = TabFolderBrowser; | |
| Title = "Markdown Monster" + | |
| (UnlockKey.IsUnlocked ? "" : " (unregistered)"); | |
| Model.Window.Focus(); | |
| } | |
| if (rebindTabHeaders) | |
| BindTabHeaders(); | |
| Model.OnPropertyChanged(nameof(AppModel.IsTabOpen)); | |
| Model.OnPropertyChanged(nameof(AppModel.IsNoTabOpen)); | |
| return returnValue; // close | |
| } | |
| /// <summary> | |
| /// Closes a tab and ask for confirmation if the tab doc | |
| /// is dirty. | |
| /// </summary> | |
| /// <param name="filename"> | |
| /// The absolute path to the file opened in the tab that | |
| /// is going to be closed | |
| /// </param> | |
| /// <returns>true if tab can close, false if it should stay open or | |
| /// filename not opened in any tab</returns> | |
| public bool CloseTab(string filename) | |
| { | |
| var tab = GetTabFromFilename(filename); | |
| if (tab != null) | |
| return CloseTab(tab); | |
| return false; | |
| } | |
| public bool CloseAllTabs(TabItem allExcept = null) | |
| { | |
| batchTabAction = true; | |
| for (int i = TabControl.Items.Count - 1; i > -1; i--) | |
| { | |
| var tab = TabControl.Items[i] as TabItem; | |
| if (tab != null) | |
| { | |
| if (allExcept != null && tab == allExcept) | |
| continue; | |
| if (!CloseTab(tab, rebindTabHeaders: false)) | |
| return false; | |
| } | |
| } | |
| batchTabAction = false; | |
| return true; | |
| } | |
| /// <summary> | |
| /// Retrieves an open tab based on its filename. | |
| /// </summary> | |
| /// <param name="filename"></param> | |
| /// <returns></returns> | |
| public TabItem GetTabFromFilename([CanBeNull] string filename) | |
| { | |
| if (string.IsNullOrEmpty(filename)) | |
| return null; | |
| if (filename == "Preview") | |
| return PreviewTab; | |
| TabItem tab = null; | |
| foreach (TabItem tabItem in TabControl.Items.Cast<TabItem>()) | |
| { | |
| string tabFile = | |
| (tabItem.Tag as MarkdownDocumentEditor)?.MarkdownDocument?.Filename ?? | |
| (tabItem.ToolTip as string); | |
| if (tabFile == null) | |
| continue; | |
| if (tabFile.Equals(filename, StringComparison.InvariantCultureIgnoreCase)) | |
| { | |
| tab = tabItem; | |
| break; | |
| } | |
| } | |
| return tab; | |
| } | |
| private void TabControl_OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) | |
| { | |
| // Explicitly force focus into the editor | |
| // Programmatic tab selection does not automatically set focus | |
| // unless explicitly specified. Click on a tab explicitly sets focus | |
| // via this operation. | |
| Model.ActiveEditor?.SetEditorFocus(); | |
| } | |
| /// <summary> | |
| /// Flag used to let us know we don't want to perform tab selection operations | |
| /// </summary> | |
| internal bool batchTabAction = false; | |
| /// <summary> | |
| /// Binds all Tab Headers | |
| /// </summary> | |
| public void BindTabHeaders() | |
| { | |
| var tabList = new List<TabItem>(); | |
| foreach (TabItem tb in TabControl.Items) | |
| tabList.Add(tb); | |
| var tabItems = tabList | |
| .Where(tb => tb.Tag is MarkdownDocumentEditor) | |
| .Select(tb => Path.GetFileName(((MarkdownDocumentEditor) tb.Tag).MarkdownDocument.Filename.ToLower())) | |
| .GroupBy(fn => fn) | |
| .Select(tbCol => new {Filename = tbCol.Key, Count = tbCol.Count()}); | |
| foreach (TabItem tb in TabControl.Items) | |
| { | |
| var doc = ((MarkdownDocumentEditor) tb.Tag)?.MarkdownDocument; | |
| if (doc == null) | |
| continue; | |
| if (tabItems.Any(ti => ti.Filename == Path.GetFileName(doc.Filename.ToLower()) && | |
| ti.Count > 1)) | |
| SetTabHeaderBinding(tb, doc, "FilenamePathWithIndicator"); | |
| else | |
| SetTabHeaderBinding(tb, doc, "FilenameWithIndicator"); | |
| } | |
| } | |
| /// <summary> | |
| /// Returns a list of DragablzItems | |
| /// </summary> | |
| /// <returns></returns> | |
| public List<DragablzItem> GetDragablzItems() | |
| { | |
| // UGLY UGLY Hack but only way to get access to the internal controls of Dragablz | |
| var control = ReflectionUtils.GetField(TabControl, "_dragablzItemsControl") as DragablzItemsControl; | |
| if (control == null) | |
| { | |
| throw new InvalidOperationException("_dragablzItemsControl is null"); | |
| } | |
| var ditems = ReflectionUtils.CallMethod(control, "DragablzItems") as List<DragablzItem>; | |
| return ditems; | |
| } | |
| /// <summary> | |
| /// Returns a DragablzItem from a TabItem | |
| /// </summary> | |
| /// <param name="tab"></param> | |
| /// <returns></returns> | |
| public DragablzItem GetDragablzItemFromTabItem(TabItem tab) | |
| { | |
| var items = GetDragablzItems(); | |
| return items.FirstOrDefault(it => it.Content as TabItem == tab); | |
| } | |
| /// <summary> | |
| /// Binds the tab header to our custom controls/container that | |
| /// shows a customized tab header | |
| /// </summary> | |
| /// <param name="tab"></param> | |
| /// <param name="document"></param> | |
| /// <param name="propertyPath"></param> | |
| private void SetTabHeaderBinding(TabItem tab, MarkdownDocument document, | |
| string propertyPath = "FilenameWithIndicator", | |
| ImageSource icon = null) | |
| { | |
| if (document == null || tab == null) | |
| return; | |
| var editor = tab.Tag as MarkdownDocumentEditor; | |
| try | |
| { | |
| var grid = new Grid(); | |
| tab.Header = grid; | |
| var col1 = new ColumnDefinition {Width = new GridLength(20)}; | |
| var col2 = new ColumnDefinition {Width = GridLength.Auto}; | |
| grid.ColumnDefinitions.Add(col1); | |
| grid.ColumnDefinitions.Add(col2); | |
| if (icon == null) | |
| { | |
| icon = FolderStructure.IconList.GetIconFromFile(document.Filename); | |
| if (icon == AssociatedIcons.DefaultIcon) | |
| icon = FolderStructure.IconList.GetIconFromType(Model.ActiveEditor.EditorSyntax); | |
| } | |
| var img = new Image() {Source = icon, Height = 16, Margin = new Thickness(0, 1, 5, 0)}; | |
| img.SetValue(Grid.ColumnProperty, 0); | |
| grid.Children.Add(img); | |
| var textBlock = new TextBlock(); | |
| textBlock.SetValue(Grid.ColumnProperty, 1); | |
| var headerBinding = new Binding | |
| { | |
| Source = document, Path = new PropertyPath(propertyPath), Mode = BindingMode.OneWay | |
| }; | |
| BindingOperations.SetBinding(textBlock, TextBlock.TextProperty, headerBinding); | |
| var tooltipBinding = new Binding | |
| { | |
| Source = document, Path = new PropertyPath("Filename"), Mode = BindingMode.OneWay | |
| }; | |
| BindingOperations.SetBinding(tab,TabItem.ToolTipProperty, tooltipBinding); | |
| var fontStyleBinding = new Binding | |
| { | |
| Source = editor, | |
| Path = new PropertyPath("IsPreview"), | |
| Mode = BindingMode.OneWay, | |
| Converter = new FontStyleFromBoolConverter() | |
| }; | |
| BindingOperations.SetBinding(textBlock, TextBlock.FontStyleProperty, fontStyleBinding); | |
| var fontWeightBinding = new Binding | |
| { | |
| Source = tab, | |
| Path = new PropertyPath("IsSelected"), | |
| Mode = BindingMode.OneWay, | |
| Converter = new FontWeightFromBoolConverter() | |
| }; | |
| BindingOperations.SetBinding(textBlock, TextBlock.FontWeightProperty, fontWeightBinding); | |
| grid.Children.Add(textBlock); | |
| } | |
| catch | |
| { | |
| // mmApp.Log("SetTabHeaderBinding Failed. Assigning explicit path", ex); | |
| tab.Header = document.FilenameWithIndicator; | |
| } | |
| } | |
| private void TabControlDragablz_TabItemClosing(ItemActionCallbackArgs<TabablzControl> e) | |
| { | |
| var tab = e.DragablzItem.DataContext as TabItem; | |
| if (tab == null) | |
| return; | |
| if (!CloseTab(tab)) | |
| e.Cancel(); // don't do default tab removal | |
| } | |
| /// <summary> | |
| /// Adds a new panel to the sidebar, and adds header text and icon explicitly. | |
| /// This overload provides a simpler way to add icon and header | |
| /// </summary> | |
| /// <param name="tabItem">Adds the TabItem. If null the tabs are refreshed and tabs removed if down to single tab</param> | |
| /// <param name="tabHeaderText">Optional - header text to set on the tab either just text or in combination with icon</param> | |
| /// <param name="tabHeaderIcon">Optional - Icon for the tab as an Image Source</param> | |
| /// <param name="selectItem"></param> | |
| public void AddLeftSidebarPanelTabItem(TabItem tabItem, | |
| string tabHeaderText = null, | |
| ImageSource tabHeaderIcon = null, | |
| bool selectItem = true) | |
| { | |
| if (tabItem != null) | |
| { | |
| // Create the header as Icon and Text | |
| var panel = new StackPanel(); | |
| panel.Margin = new Thickness(0, 5, 0, 5); | |
| Image img = null; | |
| if (tabHeaderIcon != null) | |
| { | |
| img = new Image {Source = tabHeaderIcon, Height = 22, ToolTip = tabHeaderText}; | |
| //panel.Children.Add(new TextBlock { Text = tabHeaderText }); | |
| } | |
| else if (!string.IsNullOrEmpty(tabHeaderText)) | |
| { | |
| img = new Image | |
| { | |
| Source = ImageAwesome.CreateImageSource(FontAwesomeIcon.QuestionCircle, Brushes.SteelBlue, | |
| 22), | |
| ToolTip = tabHeaderText | |
| }; | |
| } | |
| panel.Children.Add(img); | |
| tabItem.Header = panel; | |
| //ControlsHelper.SetHeaderFontSize(tabItem, 14); | |
| SidebarContainer.Items.Add(tabItem); | |
| if (selectItem) | |
| SidebarContainer.SelectedItem = tabItem; | |
| } | |
| } | |
| /// <summary> | |
| /// Adds a new panel to the right sidebar | |
| /// </summary> | |
| /// <param name="tabItem">Adds the TabItem. If null the tabs are refreshed and tabs removed if down to single tab</param> | |
| /// <param name="tabHeaderText"></param> | |
| /// <param name="tabHeaderIcon"></param> | |
| /// <param name="selectItem"></param> | |
| public void AddRightSidebarPanelTabItem(TabItem tabItem = null, | |
| string tabHeaderText = null, | |
| ImageSource tabHeaderIcon = null, | |
| bool selectItem = true) | |
| { | |
| if (tabItem != null) | |
| { | |
| if (tabHeaderIcon != null) | |
| { | |
| // Create the header as Icon and Text | |
| var panel = new StackPanel {Orientation = Orientation.Horizontal}; | |
| panel.Children.Add(new Image | |
| { | |
| Source = tabHeaderIcon, Height = 16, Margin = new Thickness(4, 0, 4, 0) | |
| }); | |
| panel.Children.Add(new TextBlock {Text = tabHeaderText}); | |
| tabItem.Header = panel; | |
| } | |
| else if (!string.IsNullOrEmpty(tabHeaderText)) | |
| tabItem.Header = tabHeaderText; | |
| ControlsHelper.SetHeaderFontSize(tabItem, 14); | |
| RightSidebarContainer.Items.Add(tabItem); | |
| if (selectItem) | |
| RightSidebarContainer.SelectedItem = tabItem; | |
| } | |
| ShowRightSidebar(); | |
| } | |
| /// <summary> | |
| /// Sets the Window Title followed by Markdown Monster (registration status) | |
| /// by default the filename is used and it's updated whenever tabs are changed. | |
| /// | |
| /// Note: ActiveTab change causes the title to be automatically updates and | |
| /// generally you **don't want to set a custom title** because it'll get | |
| /// overwritten. | |
| /// | |
| /// Just call this when you need to have the title updated due to | |
| /// file name change that doesn't change the active tab. | |
| /// </summary> | |
| /// <param name="title"></param> | |
| public void SetWindowTitle(string title = null) | |
| { | |
| if (title == null) | |
| { | |
| var editor = GetActiveMarkdownEditor(); | |
| if (editor == null) | |
| return; | |
| if (Model.Configuration.ShowFullDocPathInTitlebar) | |
| title = editor.MarkdownDocument.Filename; | |
| else | |
| title = editor.MarkdownDocument.FilenameWithIndicator.Replace("*", ""); | |
| if (!Model.ActiveProject.IsEmpty) | |
| title = title + " • " + Path.GetFileName(Model.ActiveProject.Filename); | |
| } | |
| Title = title + | |
| " - Markdown Monster " + | |
| (Model.Configuration.ShowVersionNumberInTitle | |
| ? mmApp.GetVersionForDisplay() + " " | |
| : "") + | |
| (UnlockKey.IsUnlocked ? "" : " (unregistered)"); | |
| } | |
| /// <summary> | |
| /// Helper method that sets editor focus | |
| /// </summary> | |
| public void SetEditorFocus() | |
| { | |
| Dispatcher.Invoke(() => Model.ActiveEditor?.SetEditorFocus()); | |
| } | |
| private void TabControl_ContextMenuOpening(object sender, ContextMenuEventArgs e) | |
| { | |
| var context = new TabContextMenu(); | |
| context.ShowContextMenu(); | |
| e.Handled = true; | |
| } | |
| private void TabHeader_DoubleClick(object sender, MouseButtonEventArgs ev) | |
| { | |
| if (ev.ClickCount == 2) | |
| OpenTab("untitled"); | |
| } | |
| #endregion | |
| #region Tab Drag and Drop | |
| private System.Windows.Point _dragablzStartPoint; | |
| private void DragablzItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) | |
| { | |
| _dragablzStartPoint = e.GetPosition(null); | |
| } | |
| /// <summary> | |
| /// Drag and Drop into the BookMarks dialog | |
| /// </summary> | |
| /// <param name="sender"></param> | |
| /// <param name="e"></param> | |
| private void DragablzItem_PreviewMouseMove(object sender, MouseEventArgs e) | |
| { | |
| if (e.LeftButton == MouseButtonState.Pressed) | |
| { | |
| var curPoint = e.GetPosition(null); | |
| var diff = curPoint - _dragablzStartPoint; | |
| if (_dragablzStartPoint.Y > 0 && (diff.Y > 30 || diff.Y < -30)) | |
| { | |
| var drag = sender as DragablzItem; | |
| if (drag == null) | |
| return; | |
| var tab = drag.Content as TabItem; | |
| var editor = tab.Tag as MarkdownDocumentEditor; | |
| if (editor == null) | |
| return; | |
| //var dragData = new DataObject(DataFormats.UnicodeText, editor.MarkdownDocument.Filename); | |
| var files = new[] {editor.MarkdownDocument.Filename}; | |
| var dragData = new DataObject(DataFormats.FileDrop, files); | |
| var fav = FavoritesTab.Content as FavoritesControl; | |
| if (fav != null) | |
| fav.IsDragging = true; // notify that we're ready to drop on favorites potentially | |
| DragDrop.DoDragDrop(drag, dragData, DragDropEffects.Copy); | |
| _dragablzStartPoint.Y = 0; | |
| _dragablzStartPoint.X = 0; | |
| } | |
| } | |
| } | |
| #endregion | |
| #region Document Outline | |
| private void SidebarContainer_SelectionChanged(object sender, SelectionChangedEventArgs e) | |
| { | |
| var selected = SidebarContainer.SelectedItem as TabItem; | |
| if (selected == null) | |
| return; | |
| if (selected.Content is DocumentOutlineSidebarControl) | |
| { | |
| Dispatcher.InvokeAsync(() => | |
| { | |
| if (DocumentOutline.Model?.DocumentOutline == null) | |
| UpdateDocumentOutline(); | |
| }, DispatcherPriority.ApplicationIdle); | |
| } | |
| else if (selected.Content is FavoritesControl) | |
| OpenFavorites(); | |
| } | |
| public void UpdateDocumentOutline(int editorLineNumber = -1) | |
| { | |
| DocumentOutline?.RefreshOutline(editorLineNumber); | |
| } | |
| public void OpenFavorites(bool noActivate = false) | |
| { | |
| if (FavoritesTab == null) | |
| { | |
| FavoritesTab = new MetroTabItem(); | |
| FavoritesTab.Content = new FavoritesControl(); | |
| AddLeftSidebarPanelTabItem(FavoritesTab, "Favorite Files and Folders", | |
| ImageAwesome.CreateImageSource(FontAwesomeIcon.Star, Brushes.Goldenrod, 11), | |
| selectItem: !noActivate); | |
| } | |
| else if (!noActivate) | |
| { | |
| SidebarContainer.SelectedItem = FavoritesTab; | |
| Dispatcher.InvokeAsync(() => | |
| { | |
| var control = FavoritesTab.Content as FavoritesControl; | |
| control?.TextSearch.Focus(); | |
| }); | |
| } | |
| } | |
| public FileSearchControl OpenSearchPane(bool noActivate = false) | |
| { | |
| if (SearchTab == null) | |
| { | |
| SearchTab = new MetroTabItem() {Content = new FileSearchControl()}; | |
| AddLeftSidebarPanelTabItem(SearchTab, "File Search", | |
| ImageAwesome.CreateImageSource(FontAwesomeIcon.Search, Brushes.SteelBlue, 11), !noActivate); | |
| } | |
| else | |
| { | |
| SidebarContainer.SelectedItem = SearchTab; | |
| Dispatcher.InvokeAsync(() => | |
| { | |
| var control = SearchTab.Content as FileSearchControl; | |
| control?.SearchPhrase.Focus(); | |
| }); | |
| } | |
| return SearchTab.Content as FileSearchControl; | |
| } | |
| #endregion | |
| #region Preview and UI Visibility Helpers | |
| public void PreviewMarkdown(MarkdownDocumentEditor editor = null, bool keepScrollPosition = false, | |
| bool showInBrowser = false, string renderedHtml = null) | |
| { | |
| PreviewBrowser?.PreviewMarkdown(editor, keepScrollPosition, showInBrowser, renderedHtml); | |
| } | |
| public void PreviewMarkdownAsync(MarkdownDocumentEditor editor = null, bool keepScrollPosition = false, | |
| string renderedHtml = null) | |
| { | |
| PreviewBrowser?.PreviewMarkdownAsync(editor, keepScrollPosition, renderedHtml); | |
| } | |
| public void Navigate(string url) | |
| { | |
| PreviewBrowser?.Navigate(url); | |
| } | |
| /// <summary> | |
| /// Shows or hides the preview browser | |
| /// </summary> | |
| /// <param name="hide"></param> | |
| public void ShowPreviewBrowser(bool hide = false, bool refresh = false) | |
| { | |
| if (!hide && Model.Configuration.PreviewMode != PreviewModes.None) | |
| { | |
| if (Model.Configuration.PreviewMode == PreviewModes.InternalPreview) | |
| { | |
| if (Model.Configuration.IsPreviewVisible) | |
| Model.WindowLayout.IsPreviewVisible = true; | |
| else | |
| { | |
| Model.WindowLayout.IsPreviewVisible = false; | |
| return; | |
| } | |
| // check if we're already active - if not assign and preview immediately | |
| if (!(PreviewBrowser is IPreviewBrowser)) | |
| { | |
| LoadPreviewBrowser(); | |
| return; | |
| } | |
| // close external window if it's open | |
| if (_previewBrowserWindow != null && PreviewBrowserWindow.Visibility == Visibility.Visible) | |
| { | |
| PreviewBrowserWindow.Close(); | |
| _previewBrowserWindow = null; | |
| LoadPreviewBrowser(); | |
| return; | |
| } | |
| //MainWindowSeparatorColumn.Width = new GridLength(12); | |
| if (!refresh) | |
| { | |
| if (mmApp.Configuration.WindowPosition.SplitterPosition < 100) | |
| mmApp.Configuration.WindowPosition.SplitterPosition = 600; | |
| //if (!Model.IsPresentationMode) | |
| // MainWindowPreviewColumn.Width = | |
| // new GridLength(mmApp.Configuration.WindowPosition.SplitterPosition); | |
| } | |
| } | |
| else if (Model.Configuration.PreviewMode == PreviewModes.ExternalPreviewWindow) | |
| { | |
| // make sure it's visible | |
| //bool visible = PreviewBrowserWindow.Visibility == Visibility.Visible; | |
| PreviewBrowserWindow.Show(); | |
| // check if we're already active - if not assign and preview immediately | |
| if (!(PreviewBrowser is PreviewBrowserWindow)) | |
| { | |
| PreviewBrowser = PreviewBrowserWindow; | |
| PreviewBrowser.PreviewMarkdownAsync(); | |
| } | |
| Model.WindowLayout.IsPreviewVisible = false; | |
| // clear the preview | |
| ((IPreviewBrowser) PreviewBrowserContainer.Children[0]).Navigate("about:blank"); | |
| } | |
| } | |
| else | |
| { | |
| if (Model.Configuration.PreviewMode == PreviewModes.InternalPreview) | |
| { | |
| Model.WindowLayout.IsPreviewVisible = false; | |
| // clear the preview - have to delay otherwise the browser loses the doc | |
| //((IPreviewBrowser) PreviewBrowserContainer.Children[0]).Navigate("about:blank"); | |
| } | |
| else if (Model.Configuration.PreviewMode == PreviewModes.ExternalPreviewWindow) | |
| { | |
| if (_previewBrowserWindow != null) | |
| { | |
| PreviewBrowserWindow.Close(); | |
| _previewBrowserWindow = null; | |
| PreviewBrowser = null; | |
| // reset preview browser to internal so it's not null | |
| //LoadPreviewBrowser(); | |
| } | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// Shows or hides the File Browser | |
| /// </summary> | |
| /// <param name="hide"></param> | |
| /// <param name="folder">Folder or File. If File the file will be selected in the folder</param> | |
| public void ShowFolderBrowser(bool hide = false, string folder = null) | |
| { | |
| var layoutModel = Model.WindowLayout; | |
| if (hide) | |
| { | |
| layoutModel.IsLeftSidebarVisible = false; | |
| mmApp.Configuration.FolderBrowser.Visible = false; | |
| } | |
| else | |
| { | |
| if (folder == null) | |
| folder = FolderBrowser.FolderPath; | |
| if (folder == null) | |
| folder = mmApp.Configuration.FolderBrowser.FolderPath; | |
| Dispatcher.InvokeAsync(() => | |
| { | |
| if (string.IsNullOrEmpty(folder) && Model.ActiveDocument != null) | |
| folder = Path.GetDirectoryName(Model.ActiveDocument.Filename); | |
| FolderBrowser.FolderPath = folder; | |
| }, DispatcherPriority.ApplicationIdle); | |
| layoutModel.IsLeftSidebarVisible = true; | |
| mmApp.Configuration.FolderBrowser.Visible = true; | |
| SidebarContainer.SelectedIndex = 0; // folder browser tab | |
| Model.Configuration.LastFolder = folder; | |
| } | |
| } | |
| public void ShowLeftSidebar(bool hide = false) | |
| { | |
| if (!hide && SidebarContainer.Items.Count == 1) | |
| { | |
| ShowFolderBrowser(); | |
| return; | |
| } | |
| Model.WindowLayout.IsLeftSidebarVisible = !hide; | |
| } | |
| public void ShowRightSidebar(bool hide = false) | |
| { | |
| Model.WindowLayout.IsRightSidebarVisible = !hide; | |
| } | |
| /// <summary> | |
| /// Create an instance of the Preview Browser either using the | |
| /// default IE based preview browser, or if an addin has registered | |
| /// a custom preview browser. | |
| /// </summary> | |
| public void LoadPreviewBrowser() | |
| { | |
| var previewBrowser = AddinManager.Current.RaiseGetPreviewBrowserControl(); | |
| if (previewBrowser == null || PreviewBrowser != previewBrowser) | |
| { | |
| if (previewBrowser == null) | |
| PreviewBrowser = new IEWebBrowserControl() {Name = "PreviewBrowser"}; | |
| else | |
| PreviewBrowser = previewBrowser; | |
| if (PreviewBrowserContainer == null) | |
| PreviewBrowserContainer = new Grid(); | |
| PreviewBrowserContainer.Children.Clear(); | |
| PreviewBrowserContainer.Children.Add(PreviewBrowser as UIElement); | |
| ShowPreviewBrowser(); | |
| } | |
| // show or hide | |
| PreviewMarkdownAsync(); | |
| } | |
| #endregion | |
| #region Worker Functions | |
| public MarkdownDocumentEditor GetActiveMarkdownEditor() | |
| { | |
| var tab = TabControl?.SelectedItem as TabItem; | |
| return tab?.Tag as MarkdownDocumentEditor; | |
| } | |
| /// <summary> | |
| /// Check to see if the window is visible in the bounds of the | |
| /// virtual screen space. If not adjust to main monitor off 0 position. | |
| /// </summary> | |
| /// <returns></returns> | |
| void FixMonitorPosition() | |
| { | |
| var virtualScreenHeight = SystemParameters.VirtualScreenHeight; | |
| var virtualScreenWidth = SystemParameters.VirtualScreenWidth; | |
| if (Left > virtualScreenWidth - 150) | |
| Left = 20; | |
| if (Top > virtualScreenHeight - 150) | |
| Top = 20; | |
| if (Left < SystemParameters.VirtualScreenLeft) | |
| Left = SystemParameters.VirtualScreenLeft; | |
| if (Top < SystemParameters.VirtualScreenTop) | |
| Top = SystemParameters.VirtualScreenTop; | |
| if (Width > virtualScreenWidth) | |
| Width = virtualScreenWidth - 40; | |
| if (Height > virtualScreenHeight) | |
| Height = virtualScreenHeight - 40; | |
| } | |
| #endregion | |
| #region Button Handlers | |
| /// <summary> | |
| /// Generic button handler that handles a number of simple | |
| /// tasks in a single method to minimize class noise. | |
| /// </summary> | |
| /// <param name="sender"></param> | |
| /// <param name="e"></param> | |
| public void Button_Handler(object sender, RoutedEventArgs e) | |
| { | |
| var button = sender; | |
| if (button == null) | |
| return; | |
| if (button == ButtonOpenFromHtml) | |
| { | |
| var fd = new OpenFileDialog | |
| { | |
| DefaultExt = ".htm", | |
| Filter = "Html files (*.htm,*.html)|*.htm;*.html|" + | |
| "All files (*.*)|*.*", | |
| CheckFileExists = true, | |
| RestoreDirectory = true, | |
| Multiselect = true, | |
| Title = "Open Html as Markdown" | |
| }; | |
| if (!string.IsNullOrEmpty(mmApp.Configuration.LastFolder)) | |
| fd.InitialDirectory = mmApp.Configuration.LastFolder; | |
| var res = fd.ShowDialog(); | |
| if (res == null || !res.Value) | |
| return; | |
| string html = mmFileUtils.OpenTextFile(fd.FileName); | |
| if (html == null) | |
| return; | |
| var markdown = MarkdownUtilities.HtmlToMarkdown(html); | |
| OpenTab("untitled"); | |
| var editor = GetActiveMarkdownEditor(); | |
| editor.MarkdownDocument.CurrentText = markdown; | |
| PreviewBrowser.PreviewMarkdown(); | |
| } | |
| else if (button == ToolbarButtonRecentFiles) | |
| { | |
| var mi = button as Button; | |
| var contextMenu = new RecentDocumentsContextMenu(this); | |
| contextMenu.UpdateRecentDocumentsContextMenu(RecentFileDropdownModes.ToolbarDropdown); | |
| if (mi.ContextMenu != null) | |
| mi.ContextMenu.IsOpen = true; | |
| e.Handled = true; | |
| } | |
| else if (button == ButtonExit) | |
| { | |
| Close(); | |
| } | |
| else if (button == MenuOpenConfigFolder) | |
| { | |
| ShellUtils.GoUrl(mmApp.Configuration.CommonFolder); | |
| } | |
| else if (button == MenuOpenPreviewFolder) | |
| { | |
| ShellUtils.GoUrl(Path.Combine(App.InitialStartDirectory, "PreviewThemes", | |
| mmApp.Configuration.PreviewTheme)); | |
| } | |
| else if (button == MenuMarkdownMonsterSite) | |
| { | |
| ShellUtils.GoUrl(mmApp.Urls.WebSiteUrl); | |
| } | |
| else if (button == MenuBugReport) | |
| { | |
| ShellUtils.GoUrl(mmApp.Urls.SupportUrl); | |
| } | |
| else if (button == MenuCheckNewVersion) | |
| { | |
| ShowStatus("Checking for new version..."); | |
| if (!ApplicationUpdater.CheckForNewVersion(true, failTimeout: 5000)) | |
| ShowStatusSuccess("Your version of Markdown Monster is up to date."); | |
| } | |
| else if (button == MenuRegister) | |
| { | |
| Window rf = new RegistrationForm(); | |
| rf.Owner = this; | |
| rf.ShowDialog(); | |
| } | |
| else if (button == ButtonAbout) | |
| { | |
| Window about = new About(); | |
| about.Owner = this; | |
| about.Show(); | |
| } | |
| else if (button == Button_Find) | |
| { | |
| var editor = GetActiveMarkdownEditor(); | |
| if (editor == null) | |
| return; | |
| editor.ExecEditorCommand("find"); | |
| } | |
| else if (button == Button_FindNext) | |
| { | |
| var editor = GetActiveMarkdownEditor(); | |
| if (editor == null) | |
| return; | |
| editor.ExecEditorCommand("findnext"); | |
| } | |
| else if (button == Button_Replace) | |
| { | |
| var editor = GetActiveMarkdownEditor(); | |
| if (editor == null) | |
| return; | |
| editor.ExecEditorCommand("replace"); | |
| } | |
| else if (button == ButtonWordWrap || | |
| button == ButtonLineNumbers || | |
| button == ButtonShowInvisibles || | |
| button == ButtonCenteredView) | |
| { | |
| Model.ActiveEditor?.RestyleEditor(); | |
| } | |
| else if (button == ButtonStatusEncrypted) | |
| { | |
| if (Model.ActiveDocument == null) | |
| return; | |
| var dialog = new FilePasswordDialog(Model.ActiveDocument, false) {Owner = this}; | |
| dialog.ShowDialog(); | |
| } | |
| else if (button == MenuDocumentation) | |
| ShellUtils.GoUrl(mmApp.Urls.DocumentationBaseUrl); | |
| else if (button == MenuMarkdownBasics) | |
| ShellUtils.GoUrl(mmApp.Urls.DocumentationBaseUrl + "_4ne1eu2cq.htm"); | |
| else if (button == MenuCreateAddinDocumentation) | |
| ShellUtils.GoUrl(mmApp.Urls.DocumentationBaseUrl + "_4ne0s0qoi.htm"); | |
| else if (button == MenuShowSampleDocument) | |
| OpenTab(Path.Combine(App.InitialStartDirectory, "SampleMarkdown.md")); | |
| else if (button == MenuShowErrorLog) | |
| { | |
| string logFile = Path.Combine(mmApp.Configuration.CommonFolder, "MarkdownMonsterErrors.txt"); | |
| if (File.Exists(logFile)) | |
| ShellUtils.GoUrl(logFile); | |
| else | |
| MessageBox.Show("There are no errors in your log file.", | |
| mmApp.ApplicationName, | |
| MessageBoxButton.OK, | |
| MessageBoxImage.Information); | |
| } | |
| else if (button == MenuResetConfiguration) | |
| { | |
| if (MessageBox.Show( | |
| "This operation will reset all of your configuration settings and shut down Markdown Monster.\r\n\r\nAre you sure?", | |
| "Reset Configuration Settings", | |
| MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No) == MessageBoxResult.Yes) | |
| { | |
| mmApp.Configuration.Backup(); | |
| mmApp.Configuration.Reset(); | |
| } | |
| } | |
| else if (button == MenuBackupConfiguration) | |
| { | |
| string filename = mmApp.Configuration.Backup(); | |
| ShowStatus($"Configuration backed up to: {Path.GetFileName(filename)}", | |
| mmApp.Configuration.StatusMessageTimeout); | |
| ShellUtils.OpenFileInExplorer(filename); | |
| } | |
| else if (button == ButtonAllowScriptTags) | |
| { | |
| if (!Model.Configuration.MarkdownOptions.AllowRenderScriptTags && | |
| (Model.Configuration.MarkdownOptions.UseMathematics || Model.Configuration.MarkdownOptions.MermaidDiagrams) ) | |
| { | |
| if (MessageBox.Show(@"Disabling this option also disables: | |
| • Mathematics | |
| • Mermaid Diagrams | |
| as these options require JavaScript scripts in order to work. | |
| Do you want to continue anyway?", "Disable Markdown Script Rendering", | |
| MessageBoxButton.YesNo, | |
| MessageBoxImage.Question, | |
| MessageBoxResult.Yes) == MessageBoxResult.No) | |
| Model.Configuration.MarkdownOptions.AllowRenderScriptTags = true; | |
| else | |
| { | |
| Model.Configuration.MarkdownOptions.UseMathematics = false; | |
| Model.Configuration.MarkdownOptions.MermaidDiagrams = false; | |
| } | |
| } | |
| // force preview to refresh | |
| Model.Commands.RefreshPreviewCommand.Execute(null); | |
| } | |
| } | |
| #endregion | |
| #region Miscelleaneous Events | |
| /// <summary> | |
| /// Handle drag and drop of file. Note only works when dropped on the | |
| /// window - doesn't not work when dropped on the editor. | |
| /// </summary> | |
| /// <param name="sender"></param> | |
| /// <param name="e"></param> | |
| private void MainWindow_Drop(object sender, DragEventArgs e) | |
| { | |
| if (e.Data.GetDataPresent(DataFormats.FileDrop)) | |
| { | |
| string[] files = (string[]) e.Data.GetData(DataFormats.FileDrop); | |
| foreach (var file in files) | |
| { | |
| var ext = Path.GetExtension(file.ToLower()); | |
| if (File.Exists(file) && mmApp.AllowedFileExtensions.Contains($",{ext},")) | |
| { | |
| OpenTab(file, rebindTabHeaders: true); | |
| } | |
| } | |
| } | |
| } | |
| private void PreviewBrowser_SizeChanged(object sender, SizeChangedEventArgs e) | |
| { | |
| //if (e.NewSize.Width > 100) | |
| //{ | |
| // int width = Convert.ToInt32(MainWindowPreviewColumn.Width.Value); | |
| // if (width > 100) | |
| // mmApp.Configuration.WindowPosition.SplitterPosition = width; | |
| //} | |
| } | |
| private void EditorTheme_SelectionChanged(object sender, SelectionChangedEventArgs e) | |
| { | |
| foreach (TabItem tab in TabControl.Items) | |
| { | |
| var editor = tab.Tag as MarkdownDocumentEditor; | |
| editor?.RestyleEditor(); | |
| } | |
| PreviewBrowser?.PreviewMarkdownAsync(); | |
| } | |
| private void PreviewTheme_SelectionChanged(object sender, SelectionChangedEventArgs e) | |
| { | |
| PreviewBrowser?.PreviewMarkdownAsync(); | |
| } | |
| //public void AppTheme_MenuButtonClick(object sender, RoutedEventArgs e) | |
| //{ | |
| // var button = sender as MenuItem; | |
| // var text = button.Header as string; | |
| // var selected = (Themes) Enum.Parse(typeof(Themes), text); | |
| // var oldVal = mmApp.Configuration.ApplicationTheme; | |
| // if (oldVal != selected && | |
| // MessageBox.Show("Application theme changes require that you restart.\r\n\r\nDo you want to restart Markdown Monster?", | |
| // "Theme Change", MessageBoxButton.YesNo, | |
| // MessageBoxImage.Question, MessageBoxResult.Yes) == MessageBoxResult.Yes) | |
| // { | |
| // mmApp.Configuration.ApplicationTheme = selected; | |
| // if (mmApp.Configuration.ApplicationTheme == Themes.Light) | |
| // { | |
| // mmApp.Configuration.EditorTheme = "vscodelight"; | |
| // mmApp.Configuration.PreviewTheme = "Github"; | |
| // } | |
| // else | |
| // mmApp.Configuration.EditorTheme = "vscodedark"; | |
| // mmApp.Configuration.Write(); | |
| // PipeManager.StopServer(); | |
| // ForceClose = true; | |
| // Close(); | |
| // // execute with delay | |
| // ShellUtils.ExecuteProcess(Path.Combine(App.InitialStartDirectory, "MarkdownMonster.exe"), "-delay"); | |
| // Environment.Exit(0); | |
| // } | |
| //} | |
| private void DocumentType_SelectionChanged(object sender, SelectionChangedEventArgs e) | |
| { | |
| if (Model.ActiveEditor == null) | |
| return; | |
| Model.ActiveEditor.SetEditorSyntax(Model.ActiveEditor.EditorSyntax); | |
| SetTabHeaderBinding(TabControl.SelectedItem as TabItem, Model.ActiveEditor.MarkdownDocument); | |
| // Refresh the Preview to show preview for html and markdown and hide it for others | |
| Model.Window.PreviewMarkdownAsync(); | |
| } | |
| private void ButtonRecentFiles_SubmenuOpened(object sender, RoutedEventArgs e) | |
| { | |
| var menu = new RecentDocumentsContextMenu(this); | |
| menu.UpdateRecentDocumentsContextMenu(RecentFileDropdownModes.MenuDropDown); | |
| } | |
| private void LeftSidebarExpand_MouseDown(object sender, MouseButtonEventArgs e) | |
| { | |
| Model.Commands.OpenLeftSidebarPanelCommand.Execute(null); | |
| } | |
| private void RightSidebarExpand_MouseDown(object sender, MouseButtonEventArgs e) | |
| { | |
| Model.WindowLayout.IsRightSidebarVisible = true; | |
| Model.WindowLayout.RightSidebarWidth = GridLengthHelper.FromInt(300); | |
| } | |
| private void MarkdownParserName_SelectionChanged(object sender, SelectionChangedEventArgs e) | |
| { | |
| if (mmApp.Configuration != null && | |
| !string.IsNullOrEmpty(mmApp.Configuration.MarkdownOptions.MarkdownParserName)) | |
| { | |
| MarkdownParserFactory.GetParser(parserAddinId: mmApp.Configuration.MarkdownOptions.MarkdownParserName, | |
| forceLoad: true); | |
| PreviewBrowser?.PreviewMarkdownAsync(); | |
| } | |
| } | |
| private void ButtonLangugeDropDown_Click(object sender, RoutedEventArgs e) | |
| { | |
| var context = new LanguagesContextMenu(this); | |
| context.OpenContextMenu(); | |
| } | |
| private void ButtonWindowSizesDropdown_Click(object sender, RoutedEventArgs e) | |
| { | |
| var menu = new WindowSizesContextMenu(this); | |
| menu.OpenContextMenu(); | |
| } | |
| private void Refresh_Styling(object sender, RoutedEventArgs e) | |
| { | |
| Model.ActiveEditor?.RestyleEditor(); | |
| } | |
| #endregion | |
| #region Window Menu Items | |
| public List<MenuItem> GenerateContextMenuItemsFromOpenTabs(ContextMenu ctx = null) | |
| { | |
| var menuItems = new List<MenuItem>(); | |
| var icons = new AssociatedIcons(); | |
| var selectedTab = TabControl.SelectedItem as TabItem; | |
| var headers = TabControl.GetOrderedHeaders(); | |
| foreach (var hd in headers) | |
| { | |
| var tab = hd.Content as TabItem; | |
| StackPanel sp; | |
| string commandParameter; | |
| var doc = tab.Tag as MarkdownDocumentEditor; | |
| if (tab == PreviewTab && doc == null) | |
| { | |
| var icon = (tab.Header as Grid).FindChild<Image>("IconImage")?.Source; | |
| var txt = (tab.Header as Grid).FindChild<TextBlock>("HeaderText")?.Text; | |
| sp = new StackPanel { Orientation = Orientation.Horizontal }; | |
| sp.Children.Add(new Image | |
| { | |
| Source = icon, | |
| Width = 16, | |
| Height = 16, | |
| Margin = new Thickness(0, 0, 20, 0) | |
| }); | |
| sp.Children.Add(new TextBlock { Text = txt }); | |
| commandParameter = "Preview"; | |
| sp = new StackPanel { Orientation = Orientation.Horizontal }; | |
| sp.Children.Add(new Image | |
| { | |
| Source = icon, | |
| Width = 16, | |
| Height = 16, | |
| Margin = new Thickness(0, 0, 20, 0) | |
| }); | |
| sp.Children.Add(new TextBlock { Text = txt }); | |
| commandParameter = "Preview"; | |
| } | |
| else | |
| { | |
| if (doc == null) continue; | |
| var filename = doc.MarkdownDocument.FilenamePathWithIndicator; | |
| var icon = icons.GetIconFromFile(doc.MarkdownDocument.Filename); | |
| sp = new StackPanel { Orientation = Orientation.Horizontal }; | |
| sp.Children.Add(new Image | |
| { | |
| Source = icon, | |
| Width = 16, | |
| Height = 16, | |
| Margin = new Thickness(0, 0, 20, 0) | |
| }); | |
| sp.Children.Add(new TextBlock { Text = filename }); | |
| commandParameter = doc.MarkdownDocument.Filename; | |
| } | |
| var mi = new MenuItem(); | |
| mi.Header = sp; | |
| mi.Command = Model.Commands.TabControlFileListCommand; | |
| mi.CommandParameter = commandParameter; | |
| if (tab == selectedTab) | |
| { | |
| mi.FontWeight = FontWeights.Bold; | |
| mi.Foreground = Brushes.SteelBlue; | |
| } | |
| menuItems.Add(mi); | |
| } | |
| return menuItems; | |
| } | |
| private void MainMenuWindow_SubmenuOpened(object sender, RoutedEventArgs e) | |
| { | |
| Model.Commands.WindowMenuCommand.Execute(null); | |
| } | |
| #endregion | |
| #region StatusBar Display | |
| public void ShowStatus(string message = null, int milliSeconds = 0, | |
| FontAwesomeIcon icon = FontAwesomeIcon.None, | |
| Color color = default(Color), | |
| bool spin = false) => StatusBarHelper.ShowStatus(message, milliSeconds, icon, color, spin); | |
| /// <summary> | |
| /// Displays an error message using common defaults for a timeout milliseconds | |
| /// </summary> | |
| /// <param name="message">Message to display</param> | |
| /// <param name="timeout">optional timeout</param> | |
| /// <param name="icon">optional icon (warning)</param> | |
| /// <param name="color">optional color (firebrick)</param> | |
| public void ShowStatusError(string message, int timeout = -1, | |
| FontAwesomeIcon icon = FontAwesomeIcon.Warning, | |
| Color color = default(Color)) => StatusBarHelper.ShowStatusError(message, timeout, icon, color); | |
| /// <summary> | |
| /// Shows a success message with a green check icon for the timeout | |
| /// </summary> | |
| /// <param name="message">Message to display</param> | |
| /// <param name="timeout">optional timeout</param> | |
| /// <param name="icon">optional icon (warning)</param> | |
| /// <param name="color">optional color (firebrick)</param> | |
| public void ShowStatusSuccess(string message, int timeout = -1, | |
| FontAwesomeIcon icon = FontAwesomeIcon.CheckCircle, | |
| Color color = default(Color)) => StatusBarHelper.ShowStatusSuccess(message, timeout, icon, color); | |
| /// <summary> | |
| /// Displays an Progress message using common defaults including a spinning icon | |
| /// </summary> | |
| /// <param name="message">Message to display</param> | |
| /// <param name="timeout">optional timeout</param> | |
| /// <param name="icon">optional icon (warning)</param> | |
| /// <param name="color">optional color (firebrick)</param> | |
| /// <param name="spin"></param> | |
| public void ShowStatusProgress(string message, int timeout = -1, | |
| FontAwesomeIcon icon = FontAwesomeIcon.CircleOutlineNotch, | |
| Color color = default(Color), | |
| bool spin = true) => StatusBarHelper.ShowStatusProgress(message, timeout, icon, color); | |
| /// <summary> | |
| /// Status the statusbar icon on the left bottom to some indicator | |
| /// </summary> | |
| /// <param name="icon"></param> | |
| /// <param name="color"></param> | |
| /// <param name="spin"></param> | |
| public void SetStatusIcon(FontAwesomeIcon icon, Color color, bool spin = false) => StatusBarHelper.SetStatusIcon(icon, color, spin); | |
| /// <summary> | |
| /// Resets the Status bar icon on the left to its default green circle | |
| /// </summary> | |
| public void SetStatusIcon() => StatusBarHelper.SetStatusIcon(); | |
| /// <summary> | |
| /// Helper routine to show a Metro Dialog. Note this dialog popup is fully async! | |
| /// </summary> | |
| /// <param name="title"></param> | |
| /// <param name="message"></param> | |
| /// <param name="style"></param> | |
| /// <param name="settings"></param> | |
| /// <returns></returns> | |
| public async Task<MessageDialogResult> ShowMessageOverlayAsync(string title, string message, | |
| MessageDialogStyle style = MessageDialogStyle.Affirmative, | |
| MetroDialogSettings settings = null) | |
| { | |
| return await this.ShowMessageAsync(title, message, style, settings); | |
| } | |
| private void StatusZoomLevel_MouseDoubleClick(object sender, MouseButtonEventArgs e) | |
| { | |
| mmApp.Configuration.Editor.ZoomLevel = 100; | |
| Model.ActiveEditor?.RestyleEditor(); | |
| } | |
| private void StatusZoomLevel_PreviewTextInput(object sender, TextCompositionEventArgs e) | |
| { | |
| var text = StatusZoomLevel.Text; | |
| text = text.Replace("%", ""); | |
| if (int.TryParse(text, out int num)) | |
| { | |
| Model.Configuration.Editor.ZoomLevel = num; | |
| Model.ActiveEditor?.RestyleEditor(); | |
| } | |
| } | |
| private void StatusZoomLevel_SelectionChanged(object sender, SelectionChangedEventArgs e) | |
| { | |
| var text = StatusZoomLevel.Text; | |
| text = text.Replace("%", ""); | |
| if (int.TryParse(text, out int num)) | |
| Model.Configuration.Editor.ZoomLevel = num; | |
| Model.ActiveEditor?.RestyleEditor(); | |
| } | |
| private void StatusEncoding_SelectionChanged(object sender, SelectionChangedEventArgs e) | |
| { | |
| if (Model.ActiveDocument == null) | |
| return; | |
| string enc = StatusEncoding.SelectedValue as string; | |
| if (enc == null) | |
| return; | |
| if (enc == "UTF-16 LE") | |
| Model.ActiveDocument.Encoding = Encoding.Unicode; | |
| else if (enc == "UTF-16 BE") | |
| Model.ActiveDocument.Encoding = Encoding.BigEndianUnicode; | |
| else if (enc.Contains("with BOM")) | |
| Model.ActiveDocument.Encoding = Encoding.UTF8; | |
| else | |
| Model.ActiveDocument.Encoding = new UTF8Encoding(false); | |
| } | |
| #endregion | |
| private void TextLinefeedMode_MouseUp(object sender, MouseButtonEventArgs e) | |
| { | |
| Model.Commands.SettingsVisualCommand?.Execute("Linefeed"); | |
| } | |
| } | |
| public class RecentDocumentListItem | |
| { | |
| public string Filename { get; set; } | |
| public string DisplayFilename { get; set; } | |
| } | |
| public enum RecentFileDropdownModes | |
| { | |
| ToolbarDropdown, | |
| MenuDropDown | |
| } | |
| } |