From 29aa7d229a5294eab8693573467e6a4e6bf6f974 Mon Sep 17 00:00:00 2001 From: Maciej Aniserowicz Date: Tue, 18 Dec 2012 13:18:05 +0100 Subject: [PATCH] initial src code import --- src/UI.WPF/App.config | 12 + src/UI.WPF/App.xaml | 8 + src/UI.WPF/App.xaml.cs | 11 + .../Details/DetailsValueCellTemplateSelector.cs | 28 ++ src/UI.WPF/Details/FlattenedAttribute.cs | 45 +++ src/UI.WPF/Details/ObjectDetailsModel.cs | 42 +++ src/UI.WPF/Details/ObjectDetailsWindow.xaml | 38 +++ src/UI.WPF/Details/ObjectDetailsWindow.xaml.cs | 40 +++ src/UI.WPF/FimClientFactory.cs | 18 ++ src/UI.WPF/HyperlinkTextExtractor.cs | 33 ++ src/UI.WPF/Import/ImportedObjectsModel.cs | 122 ++++++++ src/UI.WPF/Import/ImportedObjectsWindow.xaml | 12 + src/UI.WPF/Import/ImportedObjectsWindow.xaml.cs | 37 +++ src/UI.WPF/Main/MainModel.cs | 333 +++++++++++++++++++++ src/UI.WPF/Main/MainWindow.xaml | 65 ++++ src/UI.WPF/Main/MainWindow.xaml.cs | 150 ++++++++++ src/UI.WPF/Main/SelectableAttribute.cs | 15 + src/UI.WPF/Main/WindowExtensions.cs | 50 ++++ src/UI.WPF/Properties/AssemblyInfo.cs | 23 ++ src/UI.WPF/Properties/Resources.Designer.cs | 63 ++++ src/UI.WPF/Properties/Resources.resx | 117 ++++++++ src/UI.WPF/Properties/Settings.Designer.cs | 26 ++ src/UI.WPF/Properties/Settings.settings | 7 + src/UI.WPF/ReferenceColumnDetector.cs | 56 ++++ src/UI.WPF/UI.WPF.csproj | 151 ++++++++++ src/UI.WPF/WindowsManager.cs | 61 ++++ src/UI.WPF/_Utils/EnumerableExtensions.cs | 32 ++ src/UI.WPF/_Utils/TypeExtensions.cs | 17 ++ 28 files changed, 1612 insertions(+) create mode 100644 src/UI.WPF/App.config create mode 100644 src/UI.WPF/App.xaml create mode 100644 src/UI.WPF/App.xaml.cs create mode 100644 src/UI.WPF/Details/DetailsValueCellTemplateSelector.cs create mode 100644 src/UI.WPF/Details/FlattenedAttribute.cs create mode 100644 src/UI.WPF/Details/ObjectDetailsModel.cs create mode 100644 src/UI.WPF/Details/ObjectDetailsWindow.xaml create mode 100644 src/UI.WPF/Details/ObjectDetailsWindow.xaml.cs create mode 100644 src/UI.WPF/FimClientFactory.cs create mode 100644 src/UI.WPF/HyperlinkTextExtractor.cs create mode 100644 src/UI.WPF/Import/ImportedObjectsModel.cs create mode 100644 src/UI.WPF/Import/ImportedObjectsWindow.xaml create mode 100644 src/UI.WPF/Import/ImportedObjectsWindow.xaml.cs create mode 100644 src/UI.WPF/Main/MainModel.cs create mode 100644 src/UI.WPF/Main/MainWindow.xaml create mode 100644 src/UI.WPF/Main/MainWindow.xaml.cs create mode 100644 src/UI.WPF/Main/SelectableAttribute.cs create mode 100644 src/UI.WPF/Main/WindowExtensions.cs create mode 100644 src/UI.WPF/Properties/AssemblyInfo.cs create mode 100644 src/UI.WPF/Properties/Resources.Designer.cs create mode 100644 src/UI.WPF/Properties/Resources.resx create mode 100644 src/UI.WPF/Properties/Settings.Designer.cs create mode 100644 src/UI.WPF/Properties/Settings.settings create mode 100644 src/UI.WPF/ReferenceColumnDetector.cs create mode 100644 src/UI.WPF/UI.WPF.csproj create mode 100644 src/UI.WPF/WindowsManager.cs create mode 100644 src/UI.WPF/_Utils/EnumerableExtensions.cs create mode 100644 src/UI.WPF/_Utils/TypeExtensions.cs diff --git a/src/UI.WPF/App.config b/src/UI.WPF/App.config new file mode 100644 index 0000000..c5e2970 --- /dev/null +++ b/src/UI.WPF/App.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/UI.WPF/App.xaml b/src/UI.WPF/App.xaml new file mode 100644 index 0000000..9f0aba0 --- /dev/null +++ b/src/UI.WPF/App.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/src/UI.WPF/App.xaml.cs b/src/UI.WPF/App.xaml.cs new file mode 100644 index 0000000..c11ab54 --- /dev/null +++ b/src/UI.WPF/App.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows; + +namespace Predica.FimExplorer.UI.WPF +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/src/UI.WPF/Details/DetailsValueCellTemplateSelector.cs b/src/UI.WPF/Details/DetailsValueCellTemplateSelector.cs new file mode 100644 index 0000000..2f1fe0b --- /dev/null +++ b/src/UI.WPF/Details/DetailsValueCellTemplateSelector.cs @@ -0,0 +1,28 @@ +using System.Windows; +using System.Windows.Controls; +using Microsoft.ResourceManagement.ObjectModel; +using System; + +namespace Predica.FimExplorer.UI.WPF.Details +{ + /// + /// Selects hyperlink for references and normal text for other values + /// + public class DetailsValueCellTemplateSelector : DataTemplateSelector + { + public override DataTemplate SelectTemplate(object item, DependencyObject container) + { + var contentPresenter = (ContentPresenter)container; + var attribute = (FlattenedAttribute)item; + + if (attribute == null || attribute.ValueType.IsNot()) + { + return (DataTemplate)contentPresenter.FindResource("normalCell"); + } + else + { + return (DataTemplate)contentPresenter.FindResource("referenceCell"); + } + } + } +} \ No newline at end of file diff --git a/src/UI.WPF/Details/FlattenedAttribute.cs b/src/UI.WPF/Details/FlattenedAttribute.cs new file mode 100644 index 0000000..5b23bc3 --- /dev/null +++ b/src/UI.WPF/Details/FlattenedAttribute.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using Microsoft.ResourceManagement.ObjectModel; + +namespace Predica.FimExplorer.UI.WPF.Details +{ + public class FlattenedAttribute + { + private readonly KeyValuePair _attributeDescrpition; + + public string AttributeName { get { return _attributeDescrpition.Key.Name; } } + public string Value + { + get + { + return _attributeDescrpition.Value.Value == null + ? string.Empty + : _attributeDescrpition.Value.Value.ToString(); + } + } + public Type ValueType + { + get + { + return _attributeDescrpition.Value.Value == null + ? null + : _attributeDescrpition.Value.Value.GetType(); + } + } + public string ValueTypeName + { + get + { + return _attributeDescrpition.Value.Value == null + ? string.Empty + : _attributeDescrpition.Value.Value.GetType().Name; + } + } + + public FlattenedAttribute(KeyValuePair attributeDescrpition) + { + _attributeDescrpition = attributeDescrpition; + } + } +} \ No newline at end of file diff --git a/src/UI.WPF/Details/ObjectDetailsModel.cs b/src/UI.WPF/Details/ObjectDetailsModel.cs new file mode 100644 index 0000000..4bd9a88 --- /dev/null +++ b/src/UI.WPF/Details/ObjectDetailsModel.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.ResourceManagement.ObjectModel; +using Predica.FimCommunication; + +namespace Predica.FimExplorer.UI.WPF.Details +{ + public class ObjectDetailsModel + { + private readonly IFimClient _fimClient; + private readonly WindowsManager _windowsManager; + + public RmResource Resource { get; set; } + public IEnumerable Attributes { get; set; } + + public string ResourceForWindowTitle + { + get + { + return "[{0}] {1} ({2})".FormatWith(Resource.ObjectType, Resource.DisplayName, Resource.ObjectID); + } + } + + public ObjectDetailsModel(RmResource resource, IFimClient fimClient, WindowsManager windowsManager) + { + _fimClient = fimClient; + _windowsManager = windowsManager; + + Resource = resource; + + Attributes = resource.Attributes + .Select(x => new FlattenedAttribute(x)) + .ToList(); + } + + public void ShowDetailsById(string id) + { + RmResource result = _fimClient.FindById(id); + _windowsManager.ObjectDetailsDialog(result); + } + } +} \ No newline at end of file diff --git a/src/UI.WPF/Details/ObjectDetailsWindow.xaml b/src/UI.WPF/Details/ObjectDetailsWindow.xaml new file mode 100644 index 0000000..944e120 --- /dev/null +++ b/src/UI.WPF/Details/ObjectDetailsWindow.xaml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/UI.WPF/Details/ObjectDetailsWindow.xaml.cs b/src/UI.WPF/Details/ObjectDetailsWindow.xaml.cs new file mode 100644 index 0000000..76bf48e --- /dev/null +++ b/src/UI.WPF/Details/ObjectDetailsWindow.xaml.cs @@ -0,0 +1,40 @@ +using System.Windows; +using System.Windows.Documents; + +namespace Predica.FimExplorer.UI.WPF.Details +{ + public partial class ObjectDetailsWindow : Window + { + private ObjectDetailsModel _model; + + public ObjectDetailsWindow() + { + InitializeComponent(); + } + + public void Initialize(ObjectDetailsModel model) + { + _model = model; + this.DataContext = model; + } + + private void Hyperlink_Click(object sender, RoutedEventArgs e) + { + var textExtractor = new HyperlinkTextExtractor(); + string id = textExtractor.ExtractText((Hyperlink) e.Source); + _model.ShowDetailsById(id); + } + + private void ValueColumn_CopyingCellClipboardContent(object sender, System.Windows.Controls.DataGridCellClipboardEventArgs e) + { + var attribute = e.Item as FlattenedAttribute; + + if (attribute == null) + { + return; + } + + e.Content = attribute.Value; + } + } +} diff --git a/src/UI.WPF/FimClientFactory.cs b/src/UI.WPF/FimClientFactory.cs new file mode 100644 index 0000000..009d2bb --- /dev/null +++ b/src/UI.WPF/FimClientFactory.cs @@ -0,0 +1,18 @@ +using System.Configuration; +using System.Net; +using Predica.FimCommunication; + +namespace Predica.FimExplorer.UI.WPF +{ + public class FimClientFactory + { + public static IFimClient CreateClient() + { + var url = ConfigurationManager.AppSettings["fimAddress"]; + var username = ConfigurationManager.AppSettings["fimUser"]; + var password = ConfigurationManager.AppSettings["fimPassword"]; + + return new FimClient(url, new NetworkCredential(username, password)); + } + } +} \ No newline at end of file diff --git a/src/UI.WPF/HyperlinkTextExtractor.cs b/src/UI.WPF/HyperlinkTextExtractor.cs new file mode 100644 index 0000000..f335ae1 --- /dev/null +++ b/src/UI.WPF/HyperlinkTextExtractor.cs @@ -0,0 +1,33 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; +using System; + +namespace Predica.FimExplorer.UI.WPF +{ + /// + /// Extracts text contained in Hyperlink contaning TextBlock, wrapped in another TextBlock + /// + public class HyperlinkTextExtractor + { + public string ExtractText(Hyperlink hyperlink) + { + TextBlock textBlock = (TextBlock)hyperlink.Parent; + + DependencyObject containerVisual = VisualTreeHelper.GetChild(textBlock, 0); + DependencyObject contentPresenter = VisualTreeHelper.GetChild(containerVisual, 0); + + var innerTextBlock = contentPresenter as TextBlock; + + // true for link cells in main grid + // false for link cells in details grid + if (contentPresenter.GetType().IsNot()) + { + innerTextBlock = (TextBlock)VisualTreeHelper.GetChild(contentPresenter, 0); + } + + return innerTextBlock.Text; + } + } +} \ No newline at end of file diff --git a/src/UI.WPF/Import/ImportedObjectsModel.cs b/src/UI.WPF/Import/ImportedObjectsModel.cs new file mode 100644 index 0000000..4d9661f --- /dev/null +++ b/src/UI.WPF/Import/ImportedObjectsModel.cs @@ -0,0 +1,122 @@ +using System.ComponentModel; +using System.Data; +using System.IO; +using Microsoft.ResourceManagement.ObjectModel; +using System.Linq; +using NLog; +using Predica.FimCommunication; +using Predica.FimCommunication.Import; + +namespace Predica.FimExplorer.UI.WPF.Import +{ + public class ImportedObjectsModel : INotifyPropertyChanged + { + private readonly Stream _inputStream; + private readonly IXmlImporter _xmlImporter; + private readonly WindowsManager _windowsManager; + + private ImportResult _importResult; + + #region ImportedValues + + private DataTable _importedValues; + public DataTable ImportedValues + { + get { return _importedValues; } + set + { + _importedValues = value; + NotifyChanged("ImportedValues"); + } + } + + #endregion + + public ImportedObjectsModel(Stream inputStream, IXmlImporter xmlImporter, WindowsManager windowsManager) + { + _inputStream = inputStream; + _xmlImporter = xmlImporter; + _windowsManager = windowsManager; + } + + public void Initialize() + { + _importResult = _xmlImporter.Import(_inputStream); + + if (_importResult.PrimaryImportObjects.IsEmpty()) + { + return; + } + + var table = new DataTable(); + + var resourceWithMostAttributes = _importResult.PrimaryImportObjects.OrderByDescending(x => x.Attributes.Count).First(); + + foreach (var attr in resourceWithMostAttributes.Attributes) + { + var column = table.Columns.Add(attr.Key.Name); + + // window can detect references by their 'object' type as opposed to 'string' defined for all other fields + if (resourceWithMostAttributes[attr.Key.Name].Value is RmReference) + { + column.DataType = typeof(object); + } + else + { + column.DataType = typeof(string); + } + + } + + foreach (var importTarget in _importResult.PrimaryImportObjects) + { + var newRowValues = table.Columns.Cast().Select(x => + { + if (importTarget.Attributes.Keys.Any(y => y.Name == x.ColumnName)) + { + return importTarget.Attributes.First(y => y.Key.Name == x.ColumnName).Value.Value; + } + else + { + return null; + } + }) + .ToArray(); + + table.Rows.Add(newRowValues); + } + + _log.Debug("Created {0}-column table with {1} imported rows", table.Columns.Count, table.Rows.Count); + + ImportedValues = table; + } + + public void DetailsById(string id) + { + var obj = _importResult.AllImportedObjects.FirstOrDefault(x => x.ObjectID.Value == id); + + if (obj == null) + { + _windowsManager.Error("Object with id {0} was not imported".FormatWith(id)); + } + else + { + _log.Debug("showing details of imported object {0}", id); + _windowsManager.ObjectDetailsDialog(obj); + } + } + + #region INotifyPropertyChanged + + public event PropertyChangedEventHandler PropertyChanged = delegate { }; + + private void NotifyChanged(string propertyName) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + + #endregion + + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + } +} \ No newline at end of file diff --git a/src/UI.WPF/Import/ImportedObjectsWindow.xaml b/src/UI.WPF/Import/ImportedObjectsWindow.xaml new file mode 100644 index 0000000..dc73ee4 --- /dev/null +++ b/src/UI.WPF/Import/ImportedObjectsWindow.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/src/UI.WPF/Import/ImportedObjectsWindow.xaml.cs b/src/UI.WPF/Import/ImportedObjectsWindow.xaml.cs new file mode 100644 index 0000000..381be86 --- /dev/null +++ b/src/UI.WPF/Import/ImportedObjectsWindow.xaml.cs @@ -0,0 +1,37 @@ +using System.Windows; +using System.Windows.Controls; + +namespace Predica.FimExplorer.UI.WPF.Import +{ + /// + /// Interaction logic for ImportedObjectsWindow.xaml + /// + public partial class ImportedObjectsWindow : Window + { + ImportedObjectsModel _model; + ReferenceColumnDetector _referenceColumnDetector; + + public ImportedObjectsWindow() + { + InitializeComponent(); + + _referenceColumnDetector = new ReferenceColumnDetector(); + } + + public void Initialize(ImportedObjectsModel model) + { + _model = model; + this.DataContext = model; + } + + private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e) + { + // only references are marked as objects, all other are strings + _referenceColumnDetector.ProcessAutogeneratedColumn(e + , id => + { + _model.DetailsById(id); + }); + } + } +} diff --git a/src/UI.WPF/Main/MainModel.cs b/src/UI.WPF/Main/MainModel.cs new file mode 100644 index 0000000..cfd2533 --- /dev/null +++ b/src/UI.WPF/Main/MainModel.cs @@ -0,0 +1,333 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Data; +using System.IO; +using System.Linq; +using Microsoft.ResourceManagement.Client; +using Microsoft.ResourceManagement.ObjectModel; +using Microsoft.ResourceManagement.ObjectModel.ResourceTypes; +using Predica.FimCommunication; +using Predica.FimCommunication.Export; +using Predica.FimCommunication.Querying; + +namespace Predica.FimExplorer.UI.WPF.Main +{ + public class MainModel : INotifyPropertyChanged + { + private readonly MainWindow _parent; + private readonly IFimClient _fimClient; + private readonly WindowsManager _windowsManager; + private readonly IXmlExporter _xmlExporter; + + #region Bound properties + + #region XPath + + private string _xPath; + + public string XPath + { + get { return _xPath; } + set + { + _xPath = value; + NotifyChanged("XPath"); + } + } + + #endregion + + #region IdForSearch + + private string _idForSearch; + + public string IdForSearch + { + get { return _idForSearch; } + set + { + _idForSearch = value; + NotifyChanged("IdForSearch"); + } + } + + #endregion + + #region QueriedValues + + private DataTable _queriedValues; + + public DataTable QueriedValues + { + get { return _queriedValues; } + set + { + _queriedValues = value; + NotifyChanged("QueriedValues"); + } + } + + #endregion + + #region SelectedObjectType + + private RmObjectTypeDescription _selectedObjectType; + + public RmObjectTypeDescription SelectedObjectType + { + get { return _selectedObjectType; } + set + { + _selectedObjectType = value; + XPath = "/" + value.Name; + FetchAttributesForSelectedObjectType(); + } + } + + #endregion + + private DataRowView _selectedRow; + public DataRowView SelectedRow + { + get { return _selectedRow; } + set + { + _selectedRow = value; + IdForSearch = _selectedRow["Resource ID"].ToString(); + } + } + + public ObservableCollection ObjectTypes { get; private set; } + public ObservableCollection CurrentAttributes { get; private set; } + + #endregion + + public MainModel(MainWindow parent, IFimClient fimClient, WindowsManager windowsManager, IXmlExporter xmlExporter) + { + _parent = parent; + _fimClient = fimClient; + _windowsManager = windowsManager; + _xmlExporter = xmlExporter; + + ObjectTypes = new ObservableCollection(); + CurrentAttributes = new ObservableCollection(); + } + + public void Initialize() + { + FetchObjectTypes(); + + // cannot use ResourceTypeExtractor here - it is unable to extract type from RmResource + SelectedObjectType = ObjectTypes.Single(x => x.Name == "Resource"); + } + + #region Fetching object types + + private void FetchObjectTypes() + { + _parent.LongRunningOperation(_parent.loadingIndicator, () => + { + var attributesToFetch = new AttributesToFetch(RmResource.AttributeNames.DisplayName.Name, RmAttributeTypeDescription.AttributeNames.Name.Name); + + var list = _fimClient.EnumerateAll("/ObjectTypeDescription", attributesToFetch) + .ToList(); + ObjectTypes.Clear(); + list.ForEach(x => ObjectTypes.Add(x)); + }); + } + + #endregion + + #region Fetching attributes + + private readonly Dictionary> _attributesCache = + new Dictionary>(); + + private void FetchAttributesForSelectedObjectType() + { + string typeName = SelectedObjectType.Name; + + FetchAttributesForObjectType(typeName); + + FillCurrentAttributesCollection(_attributesCache[typeName]); + } + + private void FetchAttributesForObjectType(string typeName) + { + if (_attributesCache.ContainsKey(typeName)) + { + return; + } + + _parent.LongRunningOperation(_parent.loadingIndicator, () => + { + var attributesToFetch = new AttributesToFetch( + + RmResource.AttributeNames.DisplayName.Name + , RmAttributeTypeDescription.AttributeNames.Name.Name + , RmAttributeTypeDescription.AttributeNames.DataType.Name + ); + + string query = "/BindingDescription[BoundObjectType = " + + "/ObjectTypeDescription[Name = '{0}']]".FormatWith(typeName) + + "/BoundAttributeType"; + + var attributes = _fimClient.EnumerateAll(query, attributesToFetch) + .ToList(); + + _attributesCache.Add(typeName, attributes.Select(x => new SelectableAttribute(x)).ToList()); + }); + } + + private void FillCurrentAttributesCollection(IEnumerable list) + { + CurrentAttributes.Clear(); + list.ForEach(x => CurrentAttributes.Add(x)); + } + + #endregion + + #region Executing query + + private bool querying_selected_type() + { + return XPath.StartsWith("/" + SelectedObjectType.Name); + } + + public void ExecuteQuery() + { + var requiredAttributes = new List(); + + // attributes selection is possible only when directly querying for type selected in the list + // otherwise there is no smart/easy way to tell if queried object contains any of currently displayed attributes + if (querying_selected_type()) + { + if (CurrentAttributes.Any(x => x.IsSelected)) + { + requiredAttributes = CurrentAttributes.Where(x => x.IsSelected).ToList(); + } + else + { + requiredAttributes = CurrentAttributes.ToList(); + } + + if (requiredAttributes.None(x => x.Attribute.Name == RmResource.AttributeNames.ObjectID.Name)) + { + requiredAttributes.Add(CurrentAttributes.Single(x => x.Attribute.Name == RmResource.AttributeNames.ObjectID.Name)); + } + } + + // empty array if querying for other type than selected => all attributes will be fetched + var attributesToFetch = new AttributesToFetch(requiredAttributes.Select(x => x.Attribute.Name).ToArray()); + + string query = XPath.Replace(Environment.NewLine, string.Empty); + + RmResource[] results = _parent.LongRunningOperation(_parent.loadingIndicator, + () => _fimClient.EnumerateAll(query, attributesToFetch) + .ToArray() + ); + + DataTable table = new DataTable(); + + if (results.IsNotEmpty()) + { + // assuming that all results are of the same type + var firstResult = results.First(); + var resultType = firstResult.GetResourceType(); + var resultTypeAttributes = firstResult.Attributes; + + SelectableAttribute[] fetchedAttributes; + if (requiredAttributes.IsNotEmpty()) + { + fetchedAttributes = requiredAttributes.ToArray(); + } + else + { + FetchAttributesForObjectType(resultType); + fetchedAttributes = _attributesCache[resultType].ToArray(); + } + + var resultTableColumnNames = new List(); + + foreach (var a in resultTypeAttributes.OrderBy(x => x.Key.Name)) + { + var selectedAttribute = fetchedAttributes.SingleOrDefault(x => x.Attribute.Name == a.Key.Name); + if (selectedAttribute == null) + { + continue; + } + + var column = table.Columns.Add(selectedAttribute.Attribute.DisplayName); + resultTableColumnNames.Add(selectedAttribute.Attribute.Name); + + // window can detect references by their 'object' type as opposed to 'string' defined for all other fields + if (selectedAttribute.Attribute.DataType == RmFactory.RmAttributeType.Reference.ToString()) + { + column.DataType = typeof(object); + } + else + { + column.DataType = typeof(string); + } + } + + foreach (var resource in results) + { + var newRowValues = resultTableColumnNames + .Select(x => + { + if (resource.Attributes.Keys.Any(y => y.Name == x)) + { + return resource.Attributes.First(y => y.Key.Name == x) + .Value.Value; + } + else + { + return null; + } + }) + .ToArray(); + + table.Rows.Add(newRowValues); + } + } + + QueriedValues = table; + } + + #endregion + + #region Showing details by object id + + public void ShowDetailsById() + { + RmResource result = _parent.LongRunningOperation(_parent.loadingIndicator, + () => _fimClient.FindById(IdForSearch) + ); + _windowsManager.ObjectDetailsDialog(result); + } + + #endregion + + public void ExportToXml(string filePath) + { + using (var fileStream = File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Write)) + { + _xmlExporter.WriteXml(fileStream, XPath); + } + } + + #region INotifyPropertyChanged + + public event PropertyChangedEventHandler PropertyChanged = delegate { }; + + private void NotifyChanged(string propertyName) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/UI.WPF/Main/MainWindow.xaml b/src/UI.WPF/Main/MainWindow.xaml new file mode 100644 index 0000000..2107955 --- /dev/null +++ b/src/UI.WPF/Main/MainWindow.xaml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/UI.WPF/Main/MainWindow.xaml.cs b/src/UI.WPF/Main/MainWindow.xaml.cs new file mode 100644 index 0000000..9545c73 --- /dev/null +++ b/src/UI.WPF/Main/MainWindow.xaml.cs @@ -0,0 +1,150 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using Microsoft.Win32; +using Predica.FimCommunication.Export; + +namespace Predica.FimExplorer.UI.WPF.Main +{ + public partial class MainWindow : Window + { + MainModel _model; + ReferenceColumnDetector _referenceColumnDetector; + WindowsManager _windowsManager; + + public MainWindow() + { + try + { + _windowsManager = new WindowsManager(); + _model = new MainModel(this, FimClientFactory.CreateClient(), _windowsManager, new XmlExporter()); + _referenceColumnDetector = new ReferenceColumnDetector(); + + InitializeComponent(); + + this.DataContext = _model; + _model.Initialize(); + } + catch (Exception exc) + { + MessageBox.Show(@"There was an error during application startup. Verify that your configuration file points to a valid FIM service instance. +Application will now close. + +Exception details: + +" + exc, "Predica.FimExplorer Initialization error", MessageBoxButton.OK, MessageBoxImage.Error); + + Application.Current.Shutdown(); + } + } + + private void btnRunQuery_Click(object sender, RoutedEventArgs e) + { + if (_model.CurrentAttributes.Count == 0) + { + _windowsManager.Error("Object type must be selected first. Only currently selected object type can be searched by xpath."); + return; + } + + try + { + _model.ExecuteQuery(); + } + catch (Exception exc) + { + _windowsManager.Error(exc.ToString()); + } + } + + private void btnFindById_Click(object sender, RoutedEventArgs e) + { + _model.ShowDetailsById(); + } + + private void DataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e) + { + // only references are marked as objects, all other are strings + _referenceColumnDetector.ProcessAutogeneratedColumn(e + , id => + { + _model.IdForSearch = id; + _model.ShowDetailsById(); + }); + + DecideColumnOrder(e); + } + + // field used to correctly set column display indexes (ID and DisplayName); reset when all columns are generated + private bool resourceID_column_already_configured = false; + + private void DecideColumnOrder(DataGridAutoGeneratingColumnEventArgs e) + { + // ObjectID always as 1st column + if (e.PropertyName == "Resource ID") + { + e.Column.DisplayIndex = 0; + resourceID_column_already_configured = true; + } + // DisplayName is 2nd + if (e.PropertyName == "Display Name") + { + // set to 0 if ResourceID was not already configured - it will be configured later, pushing this column further + e.Column.DisplayIndex = resourceID_column_already_configured ? 1 : 0; + } + } + + private void DataGrid_AutoGeneratedColumns(object sender, EventArgs e) + { + resourceID_column_already_configured = false; + } + + private void DataGrid_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e) + { + _model.ShowDetailsById(); + } + + private void btnExportXml_Click(object sender, RoutedEventArgs e) + { + try + { + var saveFileDialog = new SaveFileDialog(); + saveFileDialog.DefaultExt = ".xml"; + saveFileDialog.Filter = "XML documents|*.xml|All files|*.*"; + + if (saveFileDialog.ShowDialog() != true) + { + return; + } + + _model.ExportToXml(saveFileDialog.FileName); + + _windowsManager.Info("Export finished"); + } + catch (Exception exc) + { + _windowsManager.Error(exc.ToString()); + } + } + + private void btnImportXml_Click(object sender, RoutedEventArgs e) + { + try + { + var openFileDialog = new OpenFileDialog(); + openFileDialog.DefaultExt = ".xml"; + openFileDialog.Filter = "XML documents|*.xml|All files|*.*"; + + if (openFileDialog.ShowDialog() != true) + { + return; + } + + _windowsManager.ImportObjectsDialog(openFileDialog.FileName); + } + catch (Exception exc) + { + _windowsManager.Error(exc.ToString()); + } + } + } +} diff --git a/src/UI.WPF/Main/SelectableAttribute.cs b/src/UI.WPF/Main/SelectableAttribute.cs new file mode 100644 index 0000000..dcb2a49 --- /dev/null +++ b/src/UI.WPF/Main/SelectableAttribute.cs @@ -0,0 +1,15 @@ +using Microsoft.ResourceManagement.ObjectModel.ResourceTypes; + +namespace Predica.FimExplorer.UI.WPF.Main +{ + public class SelectableAttribute + { + public RmAttributeTypeDescription Attribute { get; set; } + public bool IsSelected { get; set; } + + public SelectableAttribute(RmAttributeTypeDescription attribute) + { + Attribute = attribute; + } + } +} \ No newline at end of file diff --git a/src/UI.WPF/Main/WindowExtensions.cs b/src/UI.WPF/Main/WindowExtensions.cs new file mode 100644 index 0000000..514fbb0 --- /dev/null +++ b/src/UI.WPF/Main/WindowExtensions.cs @@ -0,0 +1,50 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Threading; + +namespace Predica.FimExplorer.UI.WPF.Main +{ + public static class WindowExtensions + { + public static TResult LongRunningOperation(this Window @this, Panel loadingIndicator, Func action) + { + TResult result = default(TResult); + + @this.LongRunningOperation(loadingIndicator, new Action(() => result = action())); + + return result; + } + + public static void LongRunningOperation(this Window @this, Panel loadingIndicator, Action action) + { + var originalCursor = @this.Cursor; + @this.Cursor = Cursors.Wait; + loadingIndicator.Visibility = Visibility.Visible; + + DispatcherFrame frame = new DispatcherFrame(); + DispatcherOperation dispatcherOperation = Dispatcher.CurrentDispatcher + .BeginInvoke(DispatcherPriority.ContextIdle, + new Action(() => + { + try + { + action(); + } + finally + { + loadingIndicator.Visibility = Visibility.Collapsed; + @this.Cursor = originalCursor; + } + }) + ); + dispatcherOperation.Completed += delegate + { + frame.Continue = false; + }; + + Dispatcher.PushFrame(frame); + } + } +} \ No newline at end of file diff --git a/src/UI.WPF/Properties/AssemblyInfo.cs b/src/UI.WPF/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f28648d --- /dev/null +++ b/src/UI.WPF/Properties/AssemblyInfo.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows; + +[assembly: AssemblyTitle("Predica.FimExplorer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Predica Business Solutions")] +[assembly: AssemblyProduct("Predica.FimExplorer")] +[assembly: AssemblyCopyright("Copyright © Predica Business Solutions")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located +)] + + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/src/UI.WPF/Properties/Resources.Designer.cs b/src/UI.WPF/Properties/Resources.Designer.cs new file mode 100644 index 0000000..afd907b --- /dev/null +++ b/src/UI.WPF/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.225 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Predica.FimExplorer.UI.WPF.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Predica.FimExplorer.UI.WPF.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/src/UI.WPF/Properties/Resources.resx b/src/UI.WPF/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/src/UI.WPF/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/UI.WPF/Properties/Settings.Designer.cs b/src/UI.WPF/Properties/Settings.Designer.cs new file mode 100644 index 0000000..7ff2e28 --- /dev/null +++ b/src/UI.WPF/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.225 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Predica.FimExplorer.UI.WPF.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/src/UI.WPF/Properties/Settings.settings b/src/UI.WPF/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/src/UI.WPF/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/UI.WPF/ReferenceColumnDetector.cs b/src/UI.WPF/ReferenceColumnDetector.cs new file mode 100644 index 0000000..40dba09 --- /dev/null +++ b/src/UI.WPF/ReferenceColumnDetector.cs @@ -0,0 +1,56 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; + +namespace Predica.FimExplorer.UI.WPF +{ + /// + /// Detects (based on type) if given autogenerated column is a reference + /// and replaces it with hyperlink column with 'click' event handled + /// + public class ReferenceColumnDetector + { + public void ProcessAutogeneratedColumn(DataGridAutoGeneratingColumnEventArgs e, Action handleReferenceClick) + { + var originalColumn = (DataGridBoundColumn)e.Column; + Type propertyType = e.PropertyType; + + if (propertyType.Is()) + { + var linkStyle = CreateLinkStyle(handleReferenceClick); + + e.Column = CreateHyperlinkColumn(originalColumn, linkStyle); + } + } + + private Style CreateLinkStyle(Action handleReferenceClick) + { + var linkStyle = new Style(typeof (TextBlock)); + + RoutedEventHandler clickHandler = (o, e) => + { + var textExtractor = new HyperlinkTextExtractor(); + string id = textExtractor.ExtractText((Hyperlink) e.Source); + handleReferenceClick(id); + }; + + linkStyle.Setters.Add(new EventSetter(Hyperlink.ClickEvent, clickHandler)); + + return linkStyle; + } + + private static DataGridHyperlinkColumn CreateHyperlinkColumn(DataGridBoundColumn originalColumn, Style linkStyle) + { + var newColumn = new DataGridHyperlinkColumn + { + ElementStyle = linkStyle, + Binding = originalColumn.Binding, + Header = originalColumn.Header, + HeaderStringFormat = "[ref] {0}", + }; + + return newColumn; + } + } +} \ No newline at end of file diff --git a/src/UI.WPF/UI.WPF.csproj b/src/UI.WPF/UI.WPF.csproj new file mode 100644 index 0000000..265b408 --- /dev/null +++ b/src/UI.WPF/UI.WPF.csproj @@ -0,0 +1,151 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {72AF0280-1398-4DB7-A2FA-24DAA9B4CDAB} + WinExe + Properties + Predica.FimExplorer.UI.WPF + Predica.FimExplorer + v4.0 + + + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + ..\..\FimClient\lib\NLog2.netfx40\NLog.dll + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + + + + + ImportedObjectsWindow.xaml + + + + + + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + + + + MainWindow.xaml + Code + + + Designer + MSBuild:Compile + + + + + + ObjectDetailsWindow.xaml + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + Designer + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + {b296ee14-b117-4b23-8e36-3e15932d8e8e} + FimCommunication + + + {6c1064e6-72b3-41aa-b543-4164ec056f68} + Fim2010Client.Client + + + {cecb3a49-a780-4558-a402-1f3ff42b0b18} + Fim2010Client.ObjectModel + + + + + \ No newline at end of file diff --git a/src/UI.WPF/WindowsManager.cs b/src/UI.WPF/WindowsManager.cs new file mode 100644 index 0000000..672f3d8 --- /dev/null +++ b/src/UI.WPF/WindowsManager.cs @@ -0,0 +1,61 @@ +using System.IO; +using System.Windows; +using Microsoft.ResourceManagement.ObjectModel; +using NLog; +using Predica.FimCommunication.Import; +using Predica.FimExplorer.UI.WPF.Details; +using Predica.FimExplorer.UI.WPF.Import; + +namespace Predica.FimExplorer.UI.WPF +{ + public class WindowsManager + { + public void ObjectDetailsDialog(RmResource target) + { + if (target == null) + { + _log.Debug("Cannot show details of null object"); + return; + } + + var model = new ObjectDetailsModel(target, FimClientFactory.CreateClient(), this); + + var detailsWindow = new ObjectDetailsWindow(); + + detailsWindow.Initialize(model); + + _log.Debug("Showing details of [{0}:{1}]", target.ObjectType, target.ObjectID); + + detailsWindow.Show(); + } + + public void ImportObjectsDialog(string fileName) + { + _log.Debug("Showing import window for file {0}", fileName); + + var importWindow = new ImportedObjectsWindow(); + + ImportedObjectsModel model; + using (var stream = File.OpenRead(fileName)) + { + model = new ImportedObjectsModel(stream, new XmlImporter(), this); + model.Initialize(); + importWindow.Initialize(model); + } + + importWindow.ShowDialog(); + } + + public void Info(string message) + { + MessageBox.Show(message, "Information", MessageBoxButton.OK, MessageBoxImage.Information); + } + + public void Error(string message) + { + MessageBox.Show(message, "Error", MessageBoxButton.OK, MessageBoxImage.Error); + } + + private static readonly Logger _log = LogManager.GetCurrentClassLogger(); + } +} \ No newline at end of file diff --git a/src/UI.WPF/_Utils/EnumerableExtensions.cs b/src/UI.WPF/_Utils/EnumerableExtensions.cs new file mode 100644 index 0000000..726f6b9 --- /dev/null +++ b/src/UI.WPF/_Utils/EnumerableExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Predica.FimExplorer.UI.WPF +{ + public static class EnumerableExtensions + { + public static bool IsEmpty(this IEnumerable @this) + { + return !@this.Any(); + } + + public static bool IsNotEmpty(this IEnumerable @this) + { + return !@this.IsEmpty(); + } + + public static void ForEach(this IEnumerable @this, Action operation) + { + foreach (var element in @this) + { + operation(element); + } + } + + public static bool None(this IEnumerable @this, Func predicate) + { + return !@this.Any(predicate); + } + } +} \ No newline at end of file diff --git a/src/UI.WPF/_Utils/TypeExtensions.cs b/src/UI.WPF/_Utils/TypeExtensions.cs new file mode 100644 index 0000000..556e95b --- /dev/null +++ b/src/UI.WPF/_Utils/TypeExtensions.cs @@ -0,0 +1,17 @@ +using System; + +namespace Predica.FimExplorer.UI.WPF +{ + public static class TypeExtensions + { + public static bool Is(this Type @this) + { + return @this == (typeof(TOther)); + } + + public static bool IsNot(this Type @this) + { + return !@this.Is(); + } + } +} \ No newline at end of file