using System;
using System.Collection.Generic;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Forms;
using System.Windows.Forms.Integration;
using System.Windows.Input;
using System.Windows.Media;
using GUIControls.RichText;
using JetBrains.Annotations;
using JetBrains.Omea.Base;
using JetBrains.Omea.OpenAPI;
using JetBrains.UI.Avalon;
using OmniaMea;
using Binding=System.Windows.Data.Binding;
using Button=System.Windows.Controls.Button;
using CheckBox=System.Windows.Controls.CheckBox;
using HorizontalAlignment=System.Windows.HorizontalAlignment;
using ListView=System.Windows.Controls.ListView;
using Orientation=System.Windows.Controls.Orientation;
using ToolBar=System.Windows.Controls.ToolBar;
namespace JetBrains.Omea.Plugins
{
///
/// An options page for listing the available plugins and turning them on/off.
///
public class OmeaPluginsPage : AbstractOptionsPane
{
#region Data
public static readonly RoutedCommand CommandAboutOmeaPlugins = new RoutedCommand("AboutOmeaPlugins", typeof(OmeaPluginsPage));
public static readonly RoutedCommand CommandDownloadMorePlugins = new RoutedCommand("DownloadMorePlugins", typeof(OmeaPluginsPage));
public static readonly RoutedCommand CommandRefresh = new RoutedCommand("Refresh", typeof(OmeaPluginsPage)); // TODO: reuse the generic command
internal static bool _showDebugInfo;
private static readonly ImageSource _iconPluginPrimaryGlyph = Utils.LoadResourceImage("Icons/PluginPrimaryGlyph.png");
private static readonly ImageSource _iconPluginThirdPartyGlyph = Utils.LoadResourceImage("Icons/PluginThirdPartyGlyph.png");
private static readonly string UriDownloadMorePlugins = "http://www.jetbrains.net/confluence/display/OMEA/Third-party+Plugins";
private static readonly string UriOmeaTechnicalReference = "http://www.jetbrains.net/confluence/display/OMEA/Third-party+Plugins";
///
/// Items displayed in the list, which includes both plugins and (in debug mode) non-plugins.
///
[NotNull]
private readonly List _arItems = new List();
private ICollectionView _arItemsView;
private bool _isVisible;
///
/// Not-yet-loaded entries from that are plugins.
///
[NotNull]
private readonly Queue _queueItemsToLoad = new Queue();
#endregion
#region Init
public OmeaPluginsPage()
{
FillPluginsList();
Grid root = InitView();
InitCommands(root);
}
#endregion
#region Attributes
///
/// Gets or sets whether the page displays additional information on why the plugins were loaded or not, useful for debug.
/// Note: the field static so that to retain the value between Options dialog runs (but not Omea runs), the property is nonstatic for binding.
///
public bool ShowDebugInfo
{
get
{
return _showDebugInfo;
}
set
{
_showDebugInfo = value;
ReloadPluginsList();
}
}
#endregion
#region Implementation
///
/// Concats two collections.
///
[NotNull]
private static IEnumerable Concat([NotNull] IEnumerable ca, [NotNull] IEnumerable cb)
{
foreach(TA item in ca)
yield return item;
foreach(TB item in cb)
yield return item;
}
[NotNull]
private static DataTemplate InitView_PluginsList_IsEnabledTemplate()
{
return new DataTemplateDelegate(() => AvalonEx.Bind(new CheckBox(), ToggleButton.IsCheckedProperty, (BindingBase)new Binding("IsEnabled")).Bind(UIElement.VisibilityProperty, new Binding("SupportsIsEnabled") {Converter = new ValueConverter(b => b ? Visibility.Visible : Visibility.Collapsed)}).Bind(FrameworkElement.ToolTipProperty, new Binding("IsEnabled") {Converter = ValueConverter.Create((bool b) => b ? Stringtable.PluginWillBeLoaded : Stringtable.PluginWillNotBeLoaded)}));
}
private static DataTemplate InitView_PluginsList_NameTemplate()
{
var stack = new FrameworkElementFactory(typeof(StackPanel));
stack.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
FrameworkElementFactory image;
// IsPrimary Icon
stack.AppendChild(image = new FrameworkElementFactory(typeof(Image)));
image.SetValue(FrameworkElement.WidthProperty, 12.0);
image.SetValue(FrameworkElement.HeightProperty, 12.0);
image.SetValue(Image.StretchProperty, Stretch.Uniform);
image.SetBinding(Image.SourceProperty, new Binding("IsPrimary") {Converter = new ValueConverter(b => b != null ? ((bool)b ? _iconPluginPrimaryGlyph : _iconPluginThirdPartyGlyph) : null)});
//image.SetValue(FrameworkElement.MarginProperty, new Thickness(0));
image.SetBinding(FrameworkElement.ToolTipProperty, new Binding("IsPrimary") {Converter = new ValueConverter(b => b != null ? ((bool)b ? Stringtable.PluginIsPrimary : Stringtable.PluginIsNotPrimary) : "")});
image.SetValue(UIElement.OpacityProperty, .5);
// Plugin Icon
stack.AppendChild(image = new FrameworkElementFactory(typeof(Image)));
image.SetValue(FrameworkElement.WidthProperty, 16.0);
image.SetValue(FrameworkElement.HeightProperty, 16.0);
image.SetValue(Image.StretchProperty, Stretch.Uniform);
image.SetBinding(Image.SourceProperty, new Binding("Icon"));
image.SetValue(FrameworkElement.MarginProperty, new Thickness(1));
image.SetValue(FrameworkElement.ToolTipProperty, Stringtable.PluginIcon);
FrameworkElementFactory text;
stack.AppendChild(text = new FrameworkElementFactory(typeof(TextBlock)));
text.SetValue(FrameworkElement.VerticalAlignmentProperty, VerticalAlignment.Center);
text.SetBinding(TextBlock.TextProperty, new Binding("Title"));
return new DataTemplate {DataType = typeof(OmeaPluginsPageListEntry), VisualTree = stack};
}
///
/// Harvest the plugins, both loaded and not.
///
private void FillPluginsList()
{
_arItems.Clear();
_queueItemsToLoad.Clear();
var disabledplugins = new PluginLoader.DisabledPlugins();
var hashDisoveredPluginAssemblies = new HashSet();
var pluginloader = (PluginLoader)Core.PluginLoader;
// Stage 1: loaded plugins
foreach(IPlugin plugin in pluginloader.GetLoadedPlugins())
{
try
{
string asmname = plugin.GetType().Assembly.GetName().Name;
if(!hashDisoveredPluginAssemblies.Add(asmname))
continue;
// Submit
IPlugin pluginConst = plugin;
PluginLoader.PossiblyPluginFileInfo pluginfile = pluginloader.GetPluginFileInfo(plugin);
_arItems.Add(new OmeaPluginsPageListEntryPlugin(asmname, () => pluginConst.GetType().Assembly, pluginfile, pluginloader.GetPluginLoadRuntimeError(pluginfile.File), disabledplugins));
}
catch(Exception ex)
{
Core.ReportBackgroundException(ex);
}
}
// Stage 2: not loaded plugins (and non-plugins in Debug mode)
foreach(PluginLoader.PossiblyPluginFileInfo file in pluginloader.GetAllPluginFiles())
{
try
{
if(file.IsPlugin)
{
string asmname = Path.GetFileNameWithoutExtension(file.File.FullName);
if(!hashDisoveredPluginAssemblies.Add(asmname))
continue;
// Submit
FileInfo fileConst = file.File;
_arItems.Add(new OmeaPluginsPageListEntryPlugin(asmname, () => PluginLoader.LoadPluginAssembly(fileConst), file, pluginloader.GetPluginLoadRuntimeError(file.File), disabledplugins));
}
else if(ShowDebugInfo)
_arItems.Add(new OmeaPluginsPageListEntryNonPlugin(file));
}
catch(Exception ex)
{
Core.ReportBackgroundException(ex);
}
}
// Create the view for this collection that supports sorting, grouping and sharing the selection
if(_arItemsView == null) // Reuse afterwards, as it takes part in databinding
{
_arItemsView = CollectionViewSource.GetDefaultView(_arItems);
_arItemsView.SortDescriptions.Add(new SortDescription("IsEnabledInitially", ListSortDirection.Descending));
_arItemsView.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
_arItemsView.GroupDescriptions.Add(new PropertyGroupDescription("IsEnabledInitially", ValueConverter.Create((bool b) => b ? Stringtable.Enabled : Stringtable.Disabled)));
}
else
_arItemsView.Refresh();
// Shedulle loading of the items
foreach(OmeaPluginsPageListEntry item in _arItems)
{
if(item is OmeaPluginsPageListEntryPlugin)
_queueItemsToLoad.Enqueue((OmeaPluginsPageListEntryPlugin)item);
}
if(_queueItemsToLoad.Count > 0)
Core.UserInterfaceAP.QueueJob(Stringtable.JobLoadPluginAssemblies, PumpLoadQueue);
}
private void InitCommands(Grid root)
{
root.CommandBindings.Add(new CommandBinding(CommandDownloadMorePlugins, delegate { Core.UIManager.OpenInNewBrowserWindow(UriDownloadMorePlugins); }));
root.CommandBindings.Add(new CommandBinding(CommandRefresh, delegate { ReloadPluginsList(); }));
root.CommandBindings.Add(new CommandBinding(CommandAboutOmeaPlugins, delegate { ShowAboutOmeaPlugins(); }));
}
///
/// Creates the UI. Returns the root.
///
private Grid InitView()
{
Grid grid;
Controls.Add(new ElementHost {Dock = DockStyle.Fill, Child = grid = new Grid()});
KeyboardNavigation.SetDirectionalNavigation(grid, KeyboardNavigationMode.None);
grid.AddRowChild("Auto", new TextBlock(new Run(Stringtable.ChangesAfterRestart)) {Margin = new Thickness(5), HorizontalAlignment = HorizontalAlignment.Center}); // TODO: show only if there are changes
// TODO(H): icons on the toolbar
ToolBar toolbar;
grid.AddRowChild("Auto", new ToolBarTray {ToolBars = {(toolbar = new ToolBar())}});
KeyboardNavigation.SetTabNavigation(toolbar, KeyboardNavigationMode.Continue);
KeyboardNavigation.SetTabIndex(toolbar, 10);
toolbar.Items.Add(new Button {Content = new StackPanel {Orientation = Orientation.Horizontal, Children = {new Image {Width = 16, Height = 16, Source = Utils.LoadResourceImage("Icons/DownloadMorePlugins.png")}, new TextBlock {VerticalAlignment = VerticalAlignment.Center}.Append(Stringtable.DownloadCommandText)}}, ToolTip = Stringtable.DownloadCommandTooltip, Command = CommandDownloadMorePlugins});
toolbar.Items.Add(new Button {Content = new StackPanel {Orientation = Orientation.Horizontal, Children = {new Image {Width = 16, Height = 16, Source = Utils.LoadResourceImage("Icons/RefreshPlugins.png")}, new TextBlock {VerticalAlignment = VerticalAlignment.Center}.Append(Stringtable.RefreshCommandText)}}, ToolTip = Stringtable.RefreshCommandTooltip, Command = CommandRefresh});
toolbar.Items.Add(new Separator());
toolbar.Items.Add(new Button {Content = Stringtable.AboutPluginsCommandText, ToolTip = Stringtable.AboutPluginsCommandTooltip, Command = CommandAboutOmeaPlugins});
grid.AddRowChild("*", InitView_PluginsList());
return grid;
}
private UIElement InitView_PluginsList()
{
var grid = new Grid();
////////////////
// Plugins list
ListView list;
grid.AddRowChild("3*", list = new ListView {ItemsSource = _arItemsView, IsSynchronizedWithCurrentItem = true});
list.SelectionChanged += (sender, e) => // Lazy-load the items on selection
{
foreach(object item in e.AddedItems)
{
if(item is OmeaPluginsPageListEntryPlugin)
((OmeaPluginsPageListEntryPlugin)item).Load(() => { });
}
};
GridView gridview;
list.View = gridview = new GridView {AllowsColumnReorder = true};
gridview.Columns.Add(new GridViewColumn {Header = Stringtable.PluginColumnHeader, CellTemplate = InitView_PluginsList_NameTemplate(), Width = 300});
gridview.Columns.Add(new GridViewColumn {Header = Stringtable.EnabledColumnHeader, CellTemplate = InitView_PluginsList_IsEnabledTemplate()});
/////////////
// Splitter
grid.AddRowChild("3px", new GridSplitter {HorizontalAlignment = HorizontalAlignment.Stretch, ResizeBehavior = GridResizeBehavior.PreviousAndNext, ResizeDirection = GridResizeDirection.Rows});
/////////////////
// Preview Area
grid.AddRowChild("1*", new FlowDocumentScrollViewer {VerticalScrollBarVisibility = ScrollBarVisibility.Auto}.Bind(FlowDocumentScrollViewer.DocumentProperty, new Binding("Description") {Source = _arItemsView}));
return grid;
}
///
/// Loads next plugin from the .
///
private void PumpLoadQueue()
{
if(!_isVisible)
return;
if(_queueItemsToLoad.Count == 0)
return;
_queueItemsToLoad.Dequeue().Load(() => Core.UserInterfaceAP.QueueJob(Stringtable.JobLoadPluginAssemblies, PumpLoadQueue));
}
///
/// Refreshes the plugins list to see whether new plugins have appeared, or to update the presentation (eg after the flag).
///
private void ReloadPluginsList()
{
FillPluginsList();
}
private void ShowAboutOmeaPlugins()
{
var window = new Window();
Grid grid;
window.Content = grid = new Grid();
FlowDocument document = RichContentConverter.DocumentFromResource("Resources/AboutOmeaPlugins.xaml", Assembly.GetExecutingAssembly()).SetSystemFont();
grid.AddRowChild("*", new FlowDocumentScrollViewer {Margin = new Thickness(10), VerticalScrollBarVisibility = ScrollBarVisibility.Auto, Document = document});
grid.AddRowChild("Auto", new CheckBox {Content = Stringtable.ShowDeveloperInfo, Margin = new Thickness(10, 5, 10, 0)}.Bind(ToggleButton.IsCheckedProperty, new Binding("ShowDebugInfo") {Source = this}));
grid.AddRowChild("Auto", new Button {Content = Stringtable.Close, IsCancel = true, HorizontalAlignment = HorizontalAlignment.Right, MinWidth = 75, Margin = new Thickness(10)});
window.ShowDialog();
}
#endregion
#region Overrides
public override void EnterPane()
{
_isVisible = true;
if(_queueItemsToLoad.Count > 0)
Core.UserInterfaceAP.QueueJob(Stringtable.JobLoadPluginAssemblies, PumpLoadQueue);
}
public override void LeavePane()
{
_isVisible = false;
}
public override void OK()
{
base.OK();
// Apply the enabled state
var disabledplugins = new PluginLoader.DisabledPlugins();
bool bChanged = false;
foreach(OmeaPluginsPageListEntry item in _arItems)
{
if(item is OmeaPluginsPageListEntryPlugin)
bChanged |= ((OmeaPluginsPageListEntryPlugin)item).Commit(disabledplugins);
}
// Require restart to apply changes
// (Note: even if loading a new plugin, should better restart, as others might depend on its registration)
if(bChanged)
NeedRestart = true;
}
#endregion
}
}