// // EditorPage.cs // // Author: // Michael Becker // // Copyright (c) 2019 // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . using System; using System.Collections.Generic; using System.Text; using UniversalEditor.Accessors; using UniversalEditor.ObjectModels.FileSystem; using UniversalEditor.UserInterface; using MBS.Framework.UserInterface.Controls; using MBS.Framework.UserInterface.Layouts; using MBS.Framework.UserInterface; using UniversalEditor.ObjectModels.Text.Plain; using UniversalEditor.ObjectModels.Binary; using MBS.Framework; namespace UniversalEditor.UserInterface.Pages { public partial class EditorPage : FilePage // , AwesomeControls.MultipleDocumentContainer.IMultipleDocumentContainerProgressBroadcaster // wtf is this??? { public event EventHandler DocumentEdited; protected virtual void OnDocumentEdited(EventArgs e) { if (DocumentEdited != null) DocumentEdited(this, e); } public EditorPage() { this.Layout = new BoxLayout (Orientation.Vertical); } /* public event AwesomeControls.MultipleDocumentContainer.MultipleDocumentContainerProgressEventHandler Progress; protected virtual void OnProgress(AwesomeControls.MultipleDocumentContainer.MultipleDocumentContainerProgressEventArgs e) { if (Progress != null) Progress(this, e); } */ /* private ObjectModel mvarObjectModel = null; public ObjectModel ObjectModel { get { return mvarObjectModel; } set { if (mvarObjectModel != value) { ObjectModelChangingEventArgs ce = new ObjectModelChangingEventArgs(mvarObjectModel, value); OnObjectModelChanging(ce); if (ce.Cancel) return; } mvarObjectModel = value; RefreshEditor(); OnObjectModelChanged(EventArgs.Empty); } } */ /// /// try to determine within a reasonable doubt whether or not is a "plain text" file (e.g. ASCII, UTF-8, UTF-16lE, UTF-16BE, UTF-32, etc.) /// /// true, if the specified file appears to be a text file, false otherwise. /// Filename. private bool isText(Accessor acc) { int len = 2048; len = (int)Math.Min(len, acc.Length); acc.Seek(0, IO.SeekOrigin.Begin); byte[] b = acc.Reader.ReadBytes(len); acc.Seek(-len, IO.SeekOrigin.Current); string utf8 = System.Text.Encoding.UTF8.GetString(b); // yes I know this isn't the best way to do this bool isUTF8 = (b.Length >= 3 && b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF); int start = isUTF8 ? 3 : 0; for (int i = start; i < utf8.Length; i++) { if (Char.IsControl(utf8[i]) && !Char.IsWhiteSpace(utf8[i])) { // control character, so bail out return false; } } return true; } private EditorReference DefaultBinaryEditor = new EditorReference(typeof(UniversalEditor.Editors.Binary.BinaryEditor)); private EditorReference DefaultTextEditor = new EditorReference(typeof(UniversalEditor.Editors.Text.Plain.PlainTextEditor)); protected override void OnDocumentChanged(EventArgs e) { base.OnDocumentChanged(e); RefreshEditor(); } public override string Title => EditorWindow.GetDocumentTitle(Document); private void RefreshEditor() { if (Document == null) return; // pnlLoading.Enabled = true; // pnlLoading.Visible = true; string title = EditorWindow.GetDocumentTitle(Document); ObjectModel om = null; EditorReference[] reditors = new EditorReference[0]; if (Document.ObjectModel != null) { om = Document.ObjectModel; reditors = UniversalEditor.UserInterface.Common.Reflection.GetAvailableEditors(om.MakeReference()); } if (reditors.Length == 0) { // errorMessage1.Enabled = true; // errorMessage1.Visible = true; // errorMessage1.Details = "Detected object model: " + om.GetType().FullName; bool requiresOpen = false; if (Document.Accessor == null) return; if (!Document.Accessor.IsOpen) { Document.Accessor.Open(); requiresOpen = true; } Editor ed = null; if (isText(Document.Accessor)) { ed = DefaultTextEditor.Create(); PlainTextObjectModel om1 = new PlainTextObjectModel(); if (Document.Accessor.Length < Math.Pow(1024, 2)) { string value = Document.Accessor.Reader.ReadStringToEnd(); om1.Text = value; } ed.ObjectModel = om1; } else { ed = DefaultBinaryEditor.Create(); BinaryObjectModel om1 = new BinaryObjectModel(); if (Document.Accessor.Length < Math.Pow(1024, 4)) { byte[] content = Document.Accessor.Reader.ReadToEnd(); om1.Data = content; } ed.ObjectModel = om1; } ed.Document = Document; if (requiresOpen) Document.Accessor.Close(); if (ed == null) return; ed.Title = title; ed.DocumentEdited += editor_DocumentEdited; Document.ObjectModel = ed.ObjectModel; Controls.Add(ed, new BoxLayout.Constraints(true, true)); } else { // this MIGHT or MIGHT NOT be a performance improvement, but it comes about as a rather ugly hack // to deal with deficiencies currently present in TreeModels/GtkTreeView and control collection management bool changed = false; if (Controls.Count - 1 == reditors.Length) { for (int i = 0; i < Controls.Count - 1; i++) { if (reditors[i].EditorType != Controls[i].GetType()) { // if one of our editors doesn't match what we currently have, we've obviously changed editors changed = true; break; } } } else { // if we have more or less editors than currently present in the control, we've obviously changed editors changed = true; } if (changed) { // only re-create the control collection if we've actually had a change // otherwise, this causes an exact replica of the original editor to be loaded, and sometimes breaks things // (e.g. FileSystemEditor does not work properly) // this is definitely an underlying bug in the UWT implementation tracking GtkTreeView and GtkTreeModel handles, but we can sweep it under the rug // for the moment by only refreshing the control collection if we REALLY need a different Editor (in which case, since it's a different Editor, the // problem no longer manifests itself) Controls.Clear(); Container tbEditorsAndViews = new Container(); tbEditorsAndViews.Layout = new GridLayout(); for (int i = 0; i < reditors.Length; i++) { EditorReference reditor = reditors[i]; Editor editor = reditor.Create(); // editor.Dock = DockStyle.Fill; editor.ObjectModel = om; editor.DocumentEdited += editor_DocumentEdited; editor.Title = title; editor.Document = Document; for (int j = 0; j < reditor.Views.Count; j++) { EditorView view = reditor.Views[j]; Button btn = new Button(); btn.BorderStyle = ButtonBorderStyle.None; btn.Text = view.Title; btn.Click += tibEditorView_Click; btn.SetExtraData("editor", editor); btn.SetExtraData("view", view); btn.HorizontalAlignment = HorizontalAlignment.Left; // btn.DisplayStyle = ToolbarItemDisplayStyle.ImageAndText; tbEditorsAndViews.Controls.Add(btn, new GridLayout.Constraints(0, i + j)); } Controls.Add(editor, new BoxLayout.Constraints(true, true)); } Controls.Add(tbEditorsAndViews, new BoxLayout.Constraints(false, false)); } else { // the Editors are all the same, so just update them with the new ObjectModel for (int i = 0; i < Controls.Count - 1; i++) { (Controls[i] as Editor).ObjectModel = om; (Controls[i] as Editor).Title = title; } } } } private void tibEditorView_Click(object sender, EventArgs e) { Button tib = (sender as Button); Editor editor = tib.GetExtraData("editor"); EditorView view = tib.GetExtraData("view"); editor.CurrentView = view; Application.Instance.Log(this, 274, String.Format("Switching to view '{0}'", view.Title)); } private void editor_DocumentEdited(object sender, EventArgs e) { OnDocumentEdited(e); } /* public event ObjectModelChangingEventHandler ObjectModelChanging;c protected virtual void OnObjectModelChanging(ObjectModelChangingEventArgs e) { if (ObjectModelChanging != null) ObjectModelChanging(this, e); } public event EventHandler ObjectModelChanged; protected virtual void OnObjectModelChanged(EventArgs e) { if (ObjectModelChanged != null) ObjectModelChanged(this, e); } */ /// /// The event that is raised when a file has loaded successfully in the editor. /// public event EventHandler FileOpened; protected virtual void OnFileOpened(object sender, EventArgs e) { if (FileOpened != null) FileOpened(this, e); } private void NotifyOnFileOpened(object sender, EventArgs e) { Action _NotifyOnFileOpened = new Action(OnFileOpened); // if (IsHandleCreated) Invoke(_NotifyOnFileOpened, sender, e); } public void OpenFile(Document document) { this.Document = document; if (System.IO.Directory.Exists(document.Accessor.GetFileName())) { // we're looking at a directory, so create a FileSystemObjectModel for it FileSystemObjectModel fsom = FileSystemObjectModel.FromDirectory(document.Accessor.GetFileName()); Document = new Document(fsom, null, null); NotifyOnFileOpened(this, EventArgs.Empty); RefreshEditor(); return; } System.Threading.Thread tOpenFile = new System.Threading.Thread(tOpenFile_ThreadStart); tOpenFile.Start(); } private void _ErrorMessageConfig(bool visible, string title) { // errorMessage1.Visible = visible; // errorMessage1.Enabled = visible; // if (title != null) errorMessage1.Title = title; } private void _ErrorMessageConfig(bool visible, string title, string message) { // errorMessage1.Visible = visible; // errorMessage1.Enabled = visible; // if (title != null) errorMessage1.Title = title; // if (message != null) errorMessage1.Details = message; } private void tOpenFile_ThreadStart() { if (Document.DataFormat == null) { // TODO: DataFormat-guessing should be implemented in the platform- // independent UserInterface library, or possibly in core DataFormatReference[] fmts = UniversalEditor.Common.Reflection.GetAvailableDataFormats(Document.Accessor); #region When there is no available DataFormat if (fmts.Length == 0) { // Invoke(new Action(_ErrorMessageConfig), true, "There are no data formats available for this file"); } #endregion #region When there is more than one DataFormat else if (fmts.Length > 1) { // attempt to guess the data format for the object model ObjectModelReference[] objms = UniversalEditor.Common.Reflection.GetAvailableObjectModels(Document.Accessor); ObjectModel om1 = null; DataFormat df1 = null; bool found1 = false; bool notimplemented = false; foreach (ObjectModelReference omr in objms) { EditorReference[] reditors = UniversalEditor.UserInterface.Common.Reflection.GetAvailableEditors(omr); if (reditors.Length < 1) continue; ObjectModel om = omr.Create(); bool found = false; DataFormat df = null; foreach (DataFormatReference dfr in fmts) { df = dfr.Create(); Document d = new UniversalEditor.Document(om, df, Document.Accessor); d.InputAccessor.Open(); try { d.Load(); found = true; break; } catch (InvalidDataFormatException ex) { continue; } catch (NotImplementedException ex) { notimplemented = true; continue; } catch (NotSupportedException ex) { continue; } catch (DataCorruptedException ex) { // MessageBox.Show("The data is corrupted.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); found = true; break; } catch (ArgumentException ex) { found = true; break; } finally { // do not close input accessor since we may need to read from it later (i.e. if // it's a FileSystemObjectModel with deferred file data loading) // d.InputAccessor.Close(); } } if (found) { notimplemented = false; om1 = om; df1 = df; found1 = true; break; } } if (!found1) { // Invoke(new Action(_ErrorMessageConfig), true, "There are no data formats available for this file"); } else if (notimplemented) { // Invoke(new Action(_ErrorMessageConfig), true, "The data format for this file has not been implemented"); } else { Document = new Document(om1, df1, Document.Accessor); // if (IsHandleCreated) Invoke(new Action(_ErrorMessageConfig), false, null); NotifyOnFileOpened(this, EventArgs.Empty); } } #endregion #region When there is exactly one DataFormat else { ObjectModelReference[] objms = UniversalEditor.Common.Reflection.GetAvailableObjectModels(fmts[0]); if (objms.Length >= 1) { /* if (objms.Length > 1) { // TODO: Attempt to sort available object models by relevance Pages.MultipleObjectModelPage page = new Pages.MultipleObjectModelPage(); // page.DataFormat = fmts[0].Create(); // page.FileName = FileName; // page.SelectionChanged += MultipleObjectModelPage_SelectionChanged; mdcc.Documents.Add(System.IO.Path.GetFileName(FileName), page); } else if (objms.Length == 1) { */ try { ObjectModel objm = objms[0].Create(); DataFormat fmt = fmts[0].Create(); if (!((EditorApplication)Application.Instance).ShowCustomOptionDialog(ref fmt, CustomOptionDialogType.Import)) return; Document document = new UniversalEditor.Document(objm, fmt, Document.Accessor); document.InputAccessor.Open(); document.Load(); Document = document; NotifyOnFileOpened(this, EventArgs.Empty); } catch (InvalidDataFormatException ex) { // Invoke(new Action(_ErrorMessageConfig), true, ex.GetType().FullName, ex.Message); } } else { } } #endregion } else { ObjectModel objm = Document.ObjectModel; DataFormat fmt = Document.DataFormat; if (!((EditorApplication)Application.Instance).ShowCustomOptionDialog(ref fmt, CustomOptionDialogType.Import)) return; Document document = new UniversalEditor.Document(objm, fmt, Document.Accessor); document.InputAccessor.Open(); document.Load(); Document = document; NotifyOnFileOpened(this, EventArgs.Empty); } RefreshEditor(); } } }