476 lines
17 KiB
C#
476 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Windows.Forms;
|
|
using System.Xml;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Threading;
|
|
using AnxSampleBrowser.Resources;
|
|
|
|
namespace AnxSampleBrowser
|
|
{
|
|
/// <summary>
|
|
/// WinForm project which let you browse through the ANX samples
|
|
/// </summary>
|
|
public partial class AnxSampleBrowser : Form
|
|
{
|
|
//list of all registered samples
|
|
private List<SampleData> _sampleAtlas;
|
|
private SortedList<SampleData> _filteredSamples;
|
|
private Dictionary<SampleData, SampleDataHalfVisual> _sampleDataControlMap;
|
|
private CancellationTokenSource _searchCancellationTokenSource;
|
|
private object _filteredSamplesLock = new object();
|
|
private volatile bool _alreadyRunInvoke = false;
|
|
|
|
private int _previousPage = -1;
|
|
private int _currentPage = 1;
|
|
private int _pageCount;
|
|
//default path to the samples folder
|
|
public String SamplePath
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
DropDownElement _sortByName = new DropDownElement("name", StringResources.Name);
|
|
DropDownElement _sortByCategory = new DropDownElement("category", StringResources.Category);
|
|
DropDownElement _dontFilterAll = new DropDownElement("all", StringResources.All);
|
|
DropDownElement _selectedCategory = null;
|
|
|
|
/// <summary>
|
|
/// Constructor of the browser
|
|
/// parses the xml and set up lists
|
|
/// </summary>
|
|
public AnxSampleBrowser()
|
|
{
|
|
SamplePath = "../../Samples/";
|
|
|
|
InitializeComponent();
|
|
//make the form fixed size
|
|
this.FormBorderStyle = FormBorderStyle.FixedDialog;
|
|
// Set the MaximizeBox to false to remove the maximize box.
|
|
this.MaximizeBox = false;
|
|
// Set the MinimizeBox to false to remove the minimize box.
|
|
this.MinimizeBox = false;
|
|
|
|
_dSort.Items.Add(_sortByName);
|
|
_dSort.Items.Add(_sortByCategory);
|
|
_dSort.SelectedIndex = 0;
|
|
_dSort.DropDownStyle = ComboBoxStyle.DropDownList;
|
|
|
|
//add default categorie (everything)...
|
|
_dCategories.Items.Add(_dontFilterAll);
|
|
//...and select it
|
|
_dCategories.SelectedIndex = 0;
|
|
_dCategories.DropDownStyle = ComboBoxStyle.DropDownList;
|
|
|
|
//init sample list
|
|
_sampleAtlas = new List<SampleData>();
|
|
_filteredSamples = new SortedList<SampleData>(SamplesComparer);
|
|
_sampleDataControlMap = new Dictionary<SampleData, SampleDataHalfVisual>();
|
|
|
|
Application.Idle += Application_Idle;
|
|
|
|
//load out Samples.xml
|
|
parseXMl();
|
|
addAll();
|
|
buildPages();
|
|
|
|
Size originalSize = _pSamples.Size;
|
|
int right = 0;
|
|
int bottom = 0;
|
|
foreach (Control c in _pSamples.Controls)
|
|
{
|
|
right = Math.Max(right, c.Location.X + c.Size.Width);
|
|
bottom = Math.Max(bottom, c.Location.Y + c.Size.Height);
|
|
}
|
|
|
|
//_pSamples gets resized by changing the size of the window.
|
|
this.ClientSize = new System.Drawing.Size(ClientSize.Width + right - originalSize.Width, ClientSize.Height + bottom - originalSize.Height);
|
|
|
|
_cFilter.CheckOnClick = true;
|
|
|
|
|
|
for (int i = 0; i < _cFilter.Items.Count; ++i)
|
|
{
|
|
_cFilter.SetItemChecked(i, true);
|
|
}
|
|
this._cFilter.ItemCheck += _cFilter_ItemCheck;
|
|
}
|
|
|
|
void _cFilter_ItemCheck(object sender, ItemCheckEventArgs e)
|
|
{
|
|
search();
|
|
}
|
|
|
|
void Application_Idle(object sender, EventArgs e)
|
|
{
|
|
_alreadyRunInvoke = false;
|
|
}
|
|
|
|
private IComparer<SampleData> SamplesComparer
|
|
{
|
|
get
|
|
{
|
|
if (_dSort.SelectedItem == _sortByCategory)
|
|
{
|
|
return SampleDataCategoryComparer.Instance;
|
|
}
|
|
else if (_dSort.SelectedItem == _sortByName)
|
|
{
|
|
return SampleDataNameComparer.Instance;
|
|
}
|
|
else
|
|
throw new InvalidOperationException("No sort mode specified.");
|
|
}
|
|
}
|
|
|
|
protected override CreateParams CreateParams
|
|
{
|
|
get
|
|
{
|
|
CreateParams cp = base.CreateParams;
|
|
//Activating DoubleBuffered for form and child controls where possible.
|
|
cp.ExStyle |= 0x02000000; // WS_EX_COMPOSITED
|
|
return cp;
|
|
}
|
|
}
|
|
|
|
private void buildPages()
|
|
{
|
|
Action body = () =>
|
|
{
|
|
//Makes this async code on the main thread more performant by running it only once per update.
|
|
//It can happen that we trigger the buildPage several times before the app gets idle again, in which
|
|
//it would usually work through the calls we gave by calling invoke. When that happens, it would run this
|
|
//code multiple successive times, but in between these calls it doesn't have the chance to manipulate the
|
|
//data upon which we build these controls. Which means, it would be a wasted effort.
|
|
//But this check is also from the time were the controls weren't cached and the performance difference isn't
|
|
//as big anymore as it once was.
|
|
if (_alreadyRunInvoke)
|
|
return;
|
|
|
|
|
|
bool needsRebuild = false;
|
|
var controls = _pSamples.Controls.Cast<SampleDataHalfVisual>().ToArray();
|
|
if (_filteredSamples.Count != 0)
|
|
{
|
|
var sourceData = _filteredSamples.GetRange((_currentPage - 1) * 6, Math.Min(6, _filteredSamples.Count - (_currentPage - 1) * 6));
|
|
if (controls.Length == sourceData.Count)
|
|
{
|
|
for (int i = 0; i < controls.Length; i++)
|
|
{
|
|
var data = controls[i].SampleData;
|
|
if (data != sourceData[i])
|
|
{
|
|
needsRebuild = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
needsRebuild = true;
|
|
}
|
|
else
|
|
needsRebuild = true;
|
|
|
|
if (_previousPage != _currentPage)
|
|
needsRebuild = true;
|
|
|
|
_previousPage = _currentPage;
|
|
|
|
if (needsRebuild)
|
|
{
|
|
_pSamples.Controls.Clear();
|
|
|
|
_pageCount = (int)Math.Ceiling(_filteredSamples.Count / 6f);
|
|
|
|
_currentPage = Math.Min(_currentPage, _pageCount);
|
|
if (_currentPage < 1 && _pageCount >= 1)
|
|
_currentPage = 1;
|
|
|
|
_lCurrentPage.Text = _currentPage.ToString();
|
|
_lMaxPage.Text = _pageCount.ToString();
|
|
|
|
if (_filteredSamples.Count > 0)
|
|
{
|
|
for (int i = 6 * (_currentPage - 1); i < _currentPage * 6 && i < _filteredSamples.Count; ++i)
|
|
{
|
|
addSampleDataVisual(_filteredSamples[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
_alreadyRunInvoke = true;
|
|
};
|
|
|
|
if (this.InvokeRequired)
|
|
this.Invoke(body);
|
|
else
|
|
{
|
|
//Just making sure, that if we are running this code not on the idle event, it actually runs.
|
|
_alreadyRunInvoke = false;
|
|
body();
|
|
_alreadyRunInvoke = false;
|
|
}
|
|
}
|
|
|
|
private void parseXMl()
|
|
{
|
|
XmlDocument sampleDescription = new XmlDocument();
|
|
try
|
|
{
|
|
sampleDescription.Load(SamplePath + "SampleCatalog.xml");
|
|
}
|
|
catch (FileNotFoundException)
|
|
{
|
|
OpenFileDialog openSampleXML = new OpenFileDialog();
|
|
openSampleXML.Filter = "xml files (*.xml)|*.xml";
|
|
openSampleXML.Title = "Pick SampleCatalog.xml";
|
|
if (openSampleXML.ShowDialog() == DialogResult.OK)
|
|
{
|
|
SamplePath = Path.GetDirectoryName(openSampleXML.FileName);
|
|
sampleDescription.Load(openSampleXML.FileName);
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show("Samples.xml not found");
|
|
this.Close();
|
|
return;
|
|
}
|
|
|
|
}
|
|
XmlNode sampleRoot = sampleDescription.ChildNodes[1];
|
|
|
|
List<String> _categorieAtlas = new List<string>();
|
|
|
|
List<String> _tagAtlas = new List<string>();
|
|
|
|
foreach (XmlNode sample in sampleRoot.ChildNodes)
|
|
{
|
|
|
|
SampleData data = new SampleData()
|
|
{
|
|
Name = sample["Name"].InnerText,
|
|
Description = sample["Description"].InnerText,
|
|
ExecPath = sample["ExecPath"].InnerText,
|
|
ProjectPath = sample["ProjectPath"].InnerText,
|
|
Category = sample["Category"].InnerText,
|
|
};
|
|
XmlNode imagePath = sample["ImagePath"];
|
|
if (imagePath != null)
|
|
data.ImagePath = imagePath.InnerText;
|
|
else
|
|
data.ImagePath = "";
|
|
data.Validate();
|
|
if (!_categorieAtlas.Contains(sample["Category"].InnerText))
|
|
_categorieAtlas.Add(sample["Category"].InnerText);
|
|
XmlNode tags = sample["Tags"];
|
|
List<String> dataTags = new List<string>();
|
|
foreach (XmlNode tag in tags.ChildNodes)
|
|
{
|
|
if (!_tagAtlas.Contains(tag.InnerText))
|
|
_tagAtlas.Add(tag.InnerText);
|
|
dataTags.Add(tag.InnerText);
|
|
}
|
|
data.Tags = dataTags.ToArray();
|
|
_sampleAtlas.Add(data);
|
|
addSampleDataVisual(data);
|
|
}
|
|
|
|
_dCategories.Items.AddRange(_categorieAtlas.Select((categoryName) => new DropDownElement(categoryName, categoryName)).ToArray());
|
|
|
|
_cFilter.Items.AddRange(_tagAtlas.ToArray());
|
|
}
|
|
|
|
private void addAll()
|
|
{
|
|
lock (_filteredSamplesLock)
|
|
{
|
|
_filteredSamples.Clear();
|
|
_filteredSamples.AddRange(_sampleAtlas);
|
|
buildPages();
|
|
}
|
|
}
|
|
|
|
private void addAll(List<SampleData> value)
|
|
{
|
|
lock (_filteredSamplesLock)
|
|
{
|
|
_filteredSamples.Clear();
|
|
_filteredSamples.AddRange(value);
|
|
buildPages();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private bool isAllowed(SampleData data)
|
|
{
|
|
if (_selectedCategory != _dontFilterAll)
|
|
{
|
|
if (data.Category != _selectedCategory.Name)
|
|
return false;
|
|
}
|
|
|
|
if (data.Tags.Length == 0)
|
|
return true;
|
|
|
|
foreach (String tag in data.Tags)
|
|
{
|
|
if (_cFilter.CheckedItems.Contains(tag))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void addSampleDataVisual(SampleData data)
|
|
{
|
|
SampleDataHalfVisual dataV;
|
|
if (!_sampleDataControlMap.TryGetValue(data, out dataV))
|
|
{
|
|
dataV = new SampleDataHalfVisual(data, this);
|
|
_sampleDataControlMap.Add(data, dataV);
|
|
}
|
|
|
|
if (this._pSamples.Controls.Count % 2 == 0)
|
|
dataV.Location = new Point(0, (this._pSamples.Controls.Count / 2) * (dataV.Size.Height + 5));
|
|
else
|
|
dataV.Location = new Point(dataV.Width + 5, ((this._pSamples.Controls.Count - 1) / 2) * (dataV.Size.Height + 5));
|
|
this._pSamples.Controls.Add(dataV);
|
|
}
|
|
|
|
|
|
private Task search()
|
|
{
|
|
if (_searchCancellationTokenSource != null)
|
|
{
|
|
_searchCancellationTokenSource.Cancel();
|
|
}
|
|
|
|
_searchCancellationTokenSource = new CancellationTokenSource();
|
|
return Task.Factory.StartNew(() => search(_searchCancellationTokenSource.Token), _searchCancellationTokenSource.Token);
|
|
}
|
|
|
|
|
|
private void search(CancellationToken cancellationToken)
|
|
{
|
|
if (!this.IsHandleCreated)
|
|
return;
|
|
|
|
lock (_filteredSamplesLock)
|
|
{
|
|
if (cancellationToken.IsCancellationRequested)
|
|
return;
|
|
|
|
_filteredSamples.Clear();
|
|
//Search by any of the specified words and remove empty entries to make sure that we don't just return everything if we end with one of the split characters.
|
|
String[] phrases = _tSearch.Text.Split(new char[] { ' ', ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
|
|
//If we have nothing or just the split characters, we return everything.
|
|
if (phrases.Length == 0)
|
|
phrases = new string[] { "" };
|
|
|
|
foreach (SampleData data in _sampleAtlas)
|
|
{
|
|
if (cancellationToken.IsCancellationRequested)
|
|
return;
|
|
|
|
if (!isAllowed(data))
|
|
continue;
|
|
|
|
bool add = false;
|
|
foreach (string phrase in phrases)
|
|
{
|
|
if (data.Name.Contains(phrase, StringComparison.OrdinalIgnoreCase) ||
|
|
data.Description.Contains(phrase, StringComparison.OrdinalIgnoreCase) ||
|
|
data.Category.Contains(phrase, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
add = true;
|
|
break;
|
|
}
|
|
foreach (String tag in data.Tags)
|
|
{
|
|
if (tag.ToLower().Equals(phrase.ToLower()))
|
|
{
|
|
add = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (add)
|
|
_filteredSamples.Add(data);
|
|
}
|
|
|
|
buildPages();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void _bSearch_Click(object sender, EventArgs e)
|
|
{
|
|
search();
|
|
}
|
|
|
|
private void _tSearch_KeyDown(object sender, KeyEventArgs e)
|
|
{
|
|
if (e.KeyCode == Keys.Enter)
|
|
{
|
|
e.SuppressKeyPress = true;
|
|
e.Handled = true;
|
|
search();
|
|
}
|
|
}
|
|
|
|
private void _tSearch_TextChanged(object sender, EventArgs e)
|
|
{
|
|
search();
|
|
}
|
|
|
|
private void _bClear_Click(object sender, EventArgs e)
|
|
{
|
|
this._tSearch.Text = "";
|
|
addAll();
|
|
}
|
|
|
|
private void AnxSampleBrowser_Load(object sender, EventArgs e)
|
|
{
|
|
this.Text += " v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
|
}
|
|
|
|
private void _cSort_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
//Make sure our List has been initialized.
|
|
if (_filteredSamples == null)
|
|
return;
|
|
|
|
lock (_filteredSamplesLock)
|
|
{
|
|
_filteredSamples.Comparer = SamplesComparer;
|
|
|
|
buildPages();
|
|
}
|
|
}
|
|
|
|
private void pictureBox2_Click(object sender, EventArgs e)
|
|
{
|
|
_currentPage = _currentPage + 1 > _pageCount ? _currentPage : _currentPage + 1;
|
|
buildPages();
|
|
}
|
|
|
|
private void pictureBox1_Click(object sender, EventArgs e)
|
|
{
|
|
_currentPage = _currentPage - 1 <= 0 ? _currentPage : _currentPage - 1;
|
|
buildPages();
|
|
}
|
|
|
|
private void _dCategories_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
_selectedCategory = (DropDownElement)_dCategories.SelectedItem;
|
|
search();
|
|
}
|
|
}
|
|
}
|