This commit is contained in:
jlewin 2019-06-17 18:12:34 -07:00
parent 19a7fba3a6
commit 95ae04b790
9 changed files with 397 additions and 51 deletions

View file

@ -220,6 +220,17 @@ namespace MatterHackers.MatterControl.CustomWidgets
{
OnBeforeSelect(null);
if (_selectedNode != null)
{
// Collapse the old tree
foreach (var ancestor in _selectedNode.Parents<TreeNode>())
{
ancestor.Expanded = false;
}
_selectedNode.Expanded = false;
}
// if the current selection (before change) is !null than clear its background color
if (_selectedNode != null)
{
@ -229,11 +240,22 @@ namespace MatterHackers.MatterControl.CustomWidgets
// change the selection
_selectedNode = value;
if (_selectedNode != null)
{
// Expand the new tree
foreach (var ancestor in _selectedNode.Parents<TreeNode>())
{
ancestor.Expanded = true;
}
}
if (_selectedNode != null)
{
_selectedNode.HighlightRegion.BackgroundColor = theme.AccentMimimalOverlay;
}
OnAfterSelect(null);
}
}

View file

@ -0,0 +1,136 @@

using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers.Classic;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;
using MatterHackers.Agg.Platform;
using MatterHackers.MatterControl;
using MatterHackers.MatterControl.DataStorage;
using MatterHackers.MatterControl.PartPreviewWindow;
using Newtonsoft.Json;
namespace MatterControlLib
{
public class LuceneHelpSearch
{
private static IndexWriter writer;
private static StandardAnalyzer analyzer;
static LuceneHelpSearch()
{
// Ensures index backwards compatibility
var AppLuceneVersion = LuceneVersion.LUCENE_48;
var indexLocation = System.IO.Path.Combine(ApplicationDataStorage.Instance.ApplicationTempDataPath, "LuceneIndex");
System.IO.Directory.CreateDirectory(indexLocation);
var dir = FSDirectory.Open(indexLocation);
// create an analyzer to process the text
analyzer = new StandardAnalyzer(AppLuceneVersion);
// create an index writer
var indexConfig = new IndexWriterConfig(AppLuceneVersion, analyzer);
writer = new IndexWriter(dir, indexConfig);
//IndexDocuments();
}
public LuceneHelpSearch()
{
}
private static void ProcessHelpTree(HelpArticle context, Dictionary<string, HelpArticle> helpArticles)
{
helpArticles[context.Path] = context;
foreach (var child in context.Children)
{
ProcessHelpTree(child, helpArticles);
}
}
private static void IndexDocuments()
{
Dictionary<string, HelpArticle> helpArticles;
// Clear existing
writer.DeleteAll();
// Build index from help-docs.zip
using (var file = AggContext.StaticData.OpenStream(System.IO.Path.Combine("OemSettings", "help-docs.zip")))
using (var zip = new ZipArchive(file, ZipArchiveMode.Read))
{
var tocEntry = zip.Entries.FirstOrDefault(e => e.FullName == "toc.json");
using (var docStream = tocEntry.Open())
{
var reader = new System.IO.StreamReader(docStream);
var tocText = reader.ReadToEnd();
var rootHelpArticle = JsonConvert.DeserializeObject<HelpArticle>(tocText);
helpArticles = new Dictionary<string, HelpArticle>();
// Walk the documents tree building up a dictionary of article paths to articles
ProcessHelpTree(rootHelpArticle, helpArticles);
}
foreach (var entry in zip.Entries)
{
if (entry.FullName.ToLower().EndsWith(".md"))
{
using (var docStream = entry.Open())
{
var reader = new System.IO.StreamReader(docStream);
string text = reader.ReadToEnd();
var doc = new Document();
// StringField indexes but doesn't tokenise
doc.Add(new StringField("name", helpArticles[entry.FullName].Name, Field.Store.YES));
doc.Add(new StringField("path", entry.FullName, Field.Store.YES));
doc.Add(new TextField("body", text, Field.Store.NO));
writer.AddDocument(doc);
writer.Flush(triggerMerge: false, applyAllDeletes: false);
}
}
}
}
writer.Commit();
}
public IEnumerable<HelpSearchResult> Search(string text)
{
var parser = new QueryParser(LuceneVersion.LUCENE_48, "body", analyzer);
var query = parser.Parse(text);
// re-use the writer to get real-time updates
var searcher = new IndexSearcher(writer.GetReader(applyAllDeletes: true));
var hits = searcher.Search(query, 20 /* top 20 */).ScoreDocs;
return hits.Select(hit =>
{
var foundDoc = searcher.Doc(hit.Doc);
return new HelpSearchResult()
{
Name = foundDoc.Get("name"),
Path = foundDoc.Get("path")
};
});
}
}
}

View file

@ -85,6 +85,9 @@
<ItemGroup>
<PackageReference Include="AngleSharp" Version="0.9.11" />
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00005" />
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00005" />
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00005" />
<PackageReference Include="Markdig" Version="0.15.2" />
<PackageReference Include="MIConvexHull" Version="1.1.18.1016" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.1.1" />

View file

@ -45,6 +45,16 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
if (gradientBackground != null)
{
graphics2D.Render(gradientBackground, this.LocalBounds.Left, 0);
var bounds = this.LocalBounds;
if (bounds.Width > gradientBackground.Width)
{
// Fill anything outside of the gradient region with the opaque background
graphics2D.FillRectangle(
new RectangleDouble(gradientBackground.Width, bounds.Bottom, bounds.Right, bounds.Top),
this.BackgroundColor);
}
}
else
{

View file

@ -32,11 +32,13 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using MatterControlLib;
using MatterHackers.Agg;
using MatterHackers.Agg.Platform;
using MatterHackers.Agg.UI;
using MatterHackers.DataConverters3D;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.CustomWidgets;
using MatterHackers.MatterControl.Library;
using MatterHackers.MatterControl.PartPreviewWindow.PlusTab;
using MatterHackers.MatterControl.PrintLibrary;
@ -45,6 +47,24 @@ using MatterHackers.VectorMath;
namespace MatterHackers.MatterControl.PartPreviewWindow
{
public class HelpSearchResult
{
public string Name { get; set; }
public string Path { get; set; }
}
public class HelpSearchResultRow : SettingsRow
{
public HelpSearchResultRow(HelpSearchResult searchResult, ThemeConfig theme)
: base(searchResult.Name, null, theme)
{
this.SearchResult = searchResult;
}
public HelpSearchResult SearchResult { get; }
}
public class MainViewWidget : FlowLayoutWidget
{
private EventHandler unregisterEvents;
@ -75,6 +95,83 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
Padding = new BorderDouble(left: 8)
};
GuiWidget searchButton = null;
var searchPanel = new SearchInputBox(theme)
{
Width = 200,
HAnchor = HAnchor.Absolute,
Visible = false,
Padding = new BorderDouble(10, 3)
};
searchPanel.searchInput.ActualTextEditWidget.EnterPressed += (s, e) =>
{
searchPanel.BackgroundColor = theme.SectionBackgroundColor;
// TODO:
//System.Diagnostics.Debugger.Break();
var rightPanel = new VerticalResizeContainer(theme, GrabBarSide.Left)
{
HAnchor = HAnchor.Absolute,
VAnchor = VAnchor.Absolute,
Width = 500,
Height = 200,
BackgroundColor = theme.SectionBackgroundColor
};
rightPanel.BoundsChanged += (s2, e2) =>
{
if (rightPanel.Parent != null)
{
rightPanel.Position = new Vector2(rightPanel.Parent.Width - rightPanel.Width, rightPanel.Position.Y);
}
};
var searcher = new LuceneHelpSearch();
foreach (var searchResult in searcher.Search(searchPanel.searchInput.Text))
{
var resultsRow = new HelpSearchResultRow(searchResult, theme);
resultsRow.Click += this.ResultsRow_Click;
rightPanel.AddChild(resultsRow);
}
this.Parents<SystemWindow>().FirstOrDefault().ShowRightSplitPopup(
new MatePoint(searchPanel),
new MatePoint(rightPanel));
};
searchPanel.ResetButton.Click += (s, e) =>
{
searchPanel.BackgroundColor = Color.Transparent;
searchPanel.Visible = false;
searchButton.Visible = true;
searchPanel.searchInput.Text = "";
// TODO:
//this.ClearSearch();
};
extensionArea.AddChild(searchPanel);
searchButton = theme.CreateSearchButton();
searchButton.Name = "App Search Button";
searchButton.Click += (s, e) =>
{
if (searchPanel.Visible)
{
// TODO
var a = 2;
}
else
{
searchPanel.Visible = true;
searchButton.Visible = false;
searchPanel.searchInput.Focus();
}
};
extensionArea.AddChild(searchButton);
tabControl = new ChromeTabs(extensionArea, theme)
{
VAnchor = VAnchor.Stretch,
@ -265,6 +362,37 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
ApplicationController.Instance.MainView = this;
}
private void ResultsRow_Click(object sender, MouseEventArgs e)
{
var helpDocsTab = this.tabControl.AllTabs.FirstOrDefault(t => t.Key == "HelpDocs") as ChromeTab;
if (helpDocsTab == null)
{
var helpTreePanel = new HelpTreePanel(theme)
{
HAnchor = HAnchor.Stretch,
VAnchor = VAnchor.Stretch
};
helpDocsTab = new ChromeTab("HelpDocs", "Help".Localize(), tabControl, helpTreePanel, theme, hasClose: false)
{
MinimumSize = new Vector2(0, theme.TabButtonHeight),
Name = "Library Tab",
Padding = new BorderDouble(15, 0),
};
this.TabControl.AddTab(helpDocsTab);
}
this.TabControl.ActiveTab = helpDocsTab;
if (helpDocsTab.TabContent is HelpTreePanel treePanel)
{
treePanel.ActiveNodePath = (sender as HelpSearchResultRow).SearchResult.Path;
}
}
private void SetInitialTab()
{
// Initial tab selection - workspace load will reset if applicable

View file

@ -197,7 +197,29 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
}
}
private static void RightHorizontalSplitPopup(SystemWindow systemWindow, MatePoint anchor, MatePoint popup, RectangleDouble altBounds)
{
// Calculate left for right aligned split
Vector2 popupPosition = new Vector2(systemWindow.Width - popup.Widget.Width, 0);
Vector2 anchorLeft = anchor.Widget.Parent.TransformToScreenSpace(anchor.Widget.Position);
popup.Widget.Height = anchorLeft.Y;
popup.Widget.Position = popupPosition;
}
public static void ShowPopup(this SystemWindow systemWindow, MatePoint anchor, MatePoint popup, RectangleDouble altBounds = default(RectangleDouble), int borderWidth = 1)
{
ShowPopup(systemWindow, anchor, popup, altBounds, borderWidth, BestPopupPosition);
}
public static void ShowRightSplitPopup(this SystemWindow systemWindow, MatePoint anchor, MatePoint popup, RectangleDouble altBounds = default(RectangleDouble), int borderWidth = 1)
{
ShowPopup(systemWindow, anchor, popup, altBounds, borderWidth, RightHorizontalSplitPopup);
}
public static void ShowPopup(this SystemWindow systemWindow, MatePoint anchor, MatePoint popup, RectangleDouble altBounds, int borderWidth, Action<SystemWindow, MatePoint, MatePoint, RectangleDouble> layoutHelper)
{
var hookedParents = new HashSet<GuiWidget>();
@ -216,44 +238,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
{
if (anchor.Widget?.Parent != null)
{
// Calculate left aligned screen space position (using widgetRelativeTo.parent)
Vector2 anchorLeft = anchor.Widget.Parent.TransformToScreenSpace(anchor.Widget.Position);
anchorLeft += new Vector2(altBounds.Left, altBounds.Bottom);
Vector2 popupPosition = anchorLeft;
var bounds = altBounds == default(RectangleDouble) ? anchor.Widget.LocalBounds : altBounds;
Vector2 xPosition = GetXAnchor(anchor.Mate, popup.Mate, popup.Widget, bounds);
Vector2 screenPosition;
screenPosition = anchorLeft + xPosition;
// Constrain
if (screenPosition.X + popup.Widget.Width > systemWindow.Width
|| screenPosition.X < 0)
{
xPosition = GetXAnchor(anchor.AltMate, popup.AltMate, popup.Widget, bounds);
}
popupPosition += xPosition;
Vector2 yPosition = GetYAnchor(anchor.Mate, popup.Mate, popup.Widget, bounds);
screenPosition = anchorLeft + yPosition;
// Constrain
if (anchor.AltMate != null
&& (screenPosition.Y + popup.Widget.Height > systemWindow.Height
|| screenPosition.Y < 0))
{
yPosition = GetYAnchor(anchor.AltMate, popup.AltMate, popup.Widget, bounds);
}
popupPosition += yPosition;
popup.Widget.Position = popupPosition;
layoutHelper.Invoke(systemWindow, anchor, popup, altBounds);
}
}
@ -336,6 +321,48 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
popup.Widget.Invalidate();
}
private static void BestPopupPosition(SystemWindow systemWindow, MatePoint anchor, MatePoint popup, RectangleDouble altBounds)
{
// Calculate left aligned screen space position (using widgetRelativeTo.parent)
Vector2 anchorLeft = anchor.Widget.Parent.TransformToScreenSpace(anchor.Widget.Position);
anchorLeft += new Vector2(altBounds.Left, altBounds.Bottom);
Vector2 popupPosition = anchorLeft;
var bounds = altBounds == default(RectangleDouble) ? anchor.Widget.LocalBounds : altBounds;
Vector2 xPosition = GetXAnchor(anchor.Mate, popup.Mate, popup.Widget, bounds);
Vector2 screenPosition;
screenPosition = anchorLeft + xPosition;
// Constrain
if (screenPosition.X + popup.Widget.Width > systemWindow.Width
|| screenPosition.X < 0)
{
xPosition = GetXAnchor(anchor.AltMate, popup.AltMate, popup.Widget, bounds);
}
popupPosition += xPosition;
Vector2 yPosition = GetYAnchor(anchor.Mate, popup.Mate, popup.Widget, bounds);
screenPosition = anchorLeft + yPosition;
// Constrain
if (anchor.AltMate != null
&& (screenPosition.Y + popup.Widget.Height > systemWindow.Height
|| screenPosition.Y < 0))
{
yPosition = GetYAnchor(anchor.AltMate, popup.AltMate, popup.Widget, bounds);
}
popupPosition += yPosition;
popup.Widget.Position = popupPosition;
}
private static Vector2 GetYAnchor(MateOptions anchor, MateOptions popup, GuiWidget popupWidget, RectangleDouble bounds)
{
if (anchor.Top && popup.Bottom)

View file

@ -114,6 +114,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
break;
}
}
// make sure the buttons state is set correctly
printer.Connection.CommunicationStateChanged += SetPrintButtonStyle;
startPrintButton.Closed += (s, e) => printer.Connection.CommunicationStateChanged -= SetPrintButtonStyle;

View file

@ -33,9 +33,6 @@ using MatterHackers.Agg.UI;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.CustomWidgets;
using MatterHackers.MatterControl.PrinterCommunication;
using MatterHackers.MatterControl.PrinterControls.PrinterConnections;
using MatterHackers.MatterControl.SlicerConfiguration;
using MatterHackers.SerialPortCommunication.FrostedSerial;
namespace MatterHackers.MatterControl.ActionBar
{

View file

@ -40,6 +40,19 @@ using MatterHackers.MatterControl.PrintLibrary;
namespace MatterHackers.MatterControl
{
public class HelpArticleTreeNode : TreeNode
{
public HelpArticleTreeNode(HelpArticle helpArticle, ThemeConfig theme)
: base (theme, useIcon: false)
{
this.HelpArticle = helpArticle;
this.Text = helpArticle.Name;
this.Tag = helpArticle;
}
public HelpArticle HelpArticle { get; }
}
public class HelpTreePanel : SearchableTreePanel
{
private string guideKey = null;
@ -318,13 +331,13 @@ namespace MatterHackers.MatterControl
private TreeNode initialSelection = null;
private TreeNode rootNode;
private Dictionary<string, HelpArticleTreeNode> nodesByPath = new Dictionary<string, HelpArticleTreeNode>();
private TreeNode ProcessTree(HelpArticle container)
{
var treeNode = new TreeNode(theme, false)
{
Text = container.Name,
Tag = container
};
var treeNode = new HelpArticleTreeNode(container, theme);
nodesByPath[container.Path] = treeNode;
foreach (var item in container.Children.OrderBy(i => i.Children.Count == 0).ThenBy(i => i.Name))
{
@ -334,12 +347,9 @@ namespace MatterHackers.MatterControl
}
else
{
var newNode = new TreeNode(theme, false)
{
Text = item.Name,
Tag = item
var newNode = new HelpArticleTreeNode(item, theme);
};
nodesByPath[item.Path] = newNode;
if (item.Name == guideKey
|| (guideKey != null
@ -356,6 +366,18 @@ namespace MatterHackers.MatterControl
return treeNode;
}
public string ActiveNodePath
{
get => treeView.SelectedNode?.Tag as string;
set
{
if (nodesByPath.TryGetValue(value, out HelpArticleTreeNode treeNode))
{
treeView.SelectedNode = treeNode;
}
}
}
public Color ChildBorderColor { get; private set; }
private void AddContent(GuiWidget column, string text, bool left, bool bold)