/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// using System; using System.Collections; using System.Drawing; using System.IO; using System.Reflection; using System.Windows.Forms; using System.Xml; using JetBrains.Omea.GUIControls; using JetBrains.DataStructures; using JetBrains.Omea.Base; using JetBrains.Omea.OpenAPI; namespace JetBrains.Omea { internal class ActionManager: IActionManager { internal class FilteredAction { private readonly IAction _action; private readonly IActionStateFilter[] _filters; public FilteredAction( IAction action, IActionStateFilter[] filters ) { _action = action; _filters = filters; } public bool Execute( IActionContext context ) { if ( !CanExecute( context ) ) return false; _action.Execute( context ); return true; } public bool CanExecute( IActionContext context ) { ActionPresentation presentation = new ActionPresentation(); presentation.Reset(); if ( _filters != null ) { foreach( IActionStateFilter filter in _filters ) { filter.Update( context, ref presentation ); if ( !presentation.Visible ) break; } } if ( presentation.Visible ) { _action.Update( context, ref presentation ); } if ( !presentation.Visible || !presentation.Enabled ) return false; return true; } public IAction Action { get { return _action; } } } internal class KeyboardAction: FilteredAction { private readonly string _resourceType; private readonly Keys _shortcutKey; public KeyboardAction( IAction action, Keys shortcutKey, string resourceType, IActionStateFilter[] filters ) : base( action, filters ) { _resourceType = resourceType; _shortcutKey = shortcutKey; } internal string ResourceType { get { return _resourceType; } } internal Keys ShortcutKey { get { return _shortcutKey; } } } private readonly ContextMenuStrip _contextMenu; private readonly ContextMenuActionManager _contextMenuActionManager; private readonly MenuStrip _mainMenu; private readonly Hashtable _mainMenuActionManagers = new Hashtable(); // menu name -> MenuActionManager private readonly ToolbarActionManager _toolbarManager; private readonly ResourceBrowser _resourceBrowser; private readonly Hashtable _doubleClickActions = new Hashtable(); private readonly Hashtable _linkClickActions = new Hashtable(); private readonly Hashtable _keyToAction = new Hashtable(); private readonly Hashtable _actionToKey = new Hashtable(); private readonly ArrayList _genericDoubleClickActions = new ArrayList(); private readonly KeysConverter _keysConverter = new KeysConverter(); private readonly HashMap _xmlActionCache = new HashMap(); // class name -> IAction private readonly HashMap _actionDefNodes = new HashMap(); // action def ID -> XmlNode private readonly HashMap _compositeActions = new HashMap(); // id -> CompositeAction private readonly HashSet _excludedActionAssemblies = new HashSet(); private readonly HashMap _assemblyNameCache = new HashMap(); public ActionManager( MenuStrip mainMenu, ContextMenuStrip contextMenu, ResourceBrowser resourceBrowser ) { _mainMenu = mainMenu; _contextMenu = contextMenu; _resourceBrowser = resourceBrowser; if ( _resourceBrowser != null ) _toolbarManager = resourceBrowser.ToolBarActionManager; _contextMenuActionManager = new ContextMenuActionManager( _contextMenu ); Core.StateChanged += Core_StateChanged; } /// /// Activate menu initialization right after the Omea has initialized all its /// plugins and they have inserted their corresponding menus into the common /// menu framework. /// private void Core_StateChanged(object sender, EventArgs e) { if( Core.State == CoreState.Running ) { Core.StateChanged -= Core_StateChanged; MenuInitializationFinished(); } } public void EndUpdateActions() { _toolbarManager.Dispose(); } /// /// When after the Omea initialization the event queue becomes empty /// (for the first time) we can prefill main menu submenus. /// public void MenuInitializationFinished() { foreach( DictionaryEntry de in _mainMenuActionManagers ) { MenuActionManager manager = (MenuActionManager) de.Value; manager.FillMenuIfNecessary( GetMainMenuActionContext() ); } } internal void RegisterCoreActions( bool noTextIndex ) { ListAnchor anchorLast = new ListAnchor( AnchorType.Last ); RegisterContextMenuActionGroup( ActionGroups.ITEM_OPEN_ACTIONS, anchorLast ); RegisterContextMenuActionGroup( ActionGroups.ITEM_FIND_ACTIONS, anchorLast ); RegisterContextMenuActionGroup( ActionGroups.ITEM_MODIFY_ACTIONS, anchorLast ); RegisterContextMenuActionGroup( "", anchorLast ); RegisterMainMenuActionGroup( ActionGroups.VIEW_VIEWPANE_ACTIONS, "View", "Panes", anchorLast ); RegisterMainMenuActionGroup( ActionGroups.GO_TAB_ACTIONS, "Go", anchorLast ); RegisterMainMenuActionGroup( ActionGroups.ACTION_STANDARD_ACTIONS, "Actions", anchorLast ); RegisterMainMenuActionGroup( ActionGroups.TOOLS_OPTIONS_ACTIONS, "Tools", anchorLast ); } private static string StripMenuName( string name ) { return name.Replace( "&", "" ); } public void RegisterMainMenu( string menuName, ListAnchor anchor ) { // check if the menu is already registered foreach( ToolStripItem item in _mainMenu.Items ) { if ( StripMenuName( item.Text ) == StripMenuName( menuName ) ) return; } int index = FindMainMenuInsertIndex( anchor ); ArrayList itemStack = new ArrayList(); while( _mainMenu.Items.Count > index ) { int lastIndex = _mainMenu.Items.Count - 1; itemStack.Insert( 0, _mainMenu.Items[ lastIndex ] ); _mainMenu.Items.RemoveAt( lastIndex ); } ToolStripMenuItem newSubMenu = new ToolStripMenuItem( menuName ); _mainMenu.Items.Add( newSubMenu ); _mainMenu.Items.AddRange( (ToolStripItem[]) itemStack.ToArray( typeof( ToolStripItem ) ) ); } private int FindMainMenuInsertIndex( ListAnchor anchor ) { if ( anchor.AnchorType == AnchorType.First ) return 0; if ( anchor.AnchorType == AnchorType.Last ) return _mainMenu.Items.Count; for( int i = 0; i < _mainMenu.Items.Count; i++ ) { if ( StripMenuName( _mainMenu.Items [ i ].Text ) == StripMenuName( anchor.RefId ) ) { return anchor.AnchorType == AnchorType.After ? i + 1 : i; } } throw new ActionException( "Referended menu " + anchor.RefId + " not found" ); } public void RegisterMainMenuActionGroup( string groupId, string menuName, ListAnchor anchor ) { RegisterMainMenuActionGroup( groupId, menuName, null, anchor ); } public void RegisterMainMenuActionGroup( string groupId, string menuName, string submenuName, ListAnchor anchor ) { menuName = StripMenuName( menuName ); ToolStripMenuItem mainMenuItem = FindMainMenuItem( menuName ); if ( mainMenuItem == null ) { throw new ActionException( "Top-level menu " + menuName + " not found" ); } MenuActionManager manager = (MenuActionManager) _mainMenuActionManagers [menuName]; if ( manager == null ) { manager = new MainMenuActionManager( mainMenuItem ); _mainMenuActionManagers[ menuName ] = manager; } manager.RegisterGroup( groupId, submenuName, anchor ); } public void SuppressMainMenuGroupSeparator( string groupId1, string groupId2 ) { foreach( DictionaryEntry de in _mainMenuActionManagers ) { MenuActionManager manager = (MenuActionManager) de.Value; if ( manager.ContainsGroup( groupId1 ) && manager.ContainsGroup( groupId2 ) ) { manager.SuppressGroupSeparator( groupId1, groupId2 ); return; } } throw new ArgumentException( "Groups '" + groupId1 + "' and '" + groupId2 + " ' not found or found in different menus" ); } public void RegisterMainMenuAction( IAction action, string groupId, ListAnchor anchor, string text, Image icon, string resourceType, IActionStateFilter[] filters ) { foreach( DictionaryEntry de in _mainMenuActionManagers ) { MenuActionManager manager = (MenuActionManager) de.Value; if ( manager.ContainsGroup( groupId ) ) { manager.RegisterAction( action, groupId, anchor, text, icon, resourceType, filters ); return; } } throw new ArgumentException( "Invalid action group name " + groupId, "groupId" ); } public void UnregisterMainMenuAction( IAction action ) { foreach( DictionaryEntry de in _mainMenuActionManagers ) { MenuActionManager manager = (MenuActionManager) de.Value; if ( manager.UnregisterAction( action ) ) { break; } } } private ToolStripMenuItem FindMainMenuItem( string name ) { foreach( ToolStripMenuItem item in _mainMenu.Items ) { if ( item.Text.Replace( "&", "" ) == name.Replace( "&", "") ) { return item; } } return null; } public IActionContext GetMainMenuActionContext() { Form frm = (Form) Core.MainWindow; Control ctl = frm.ActiveControl; if ( ctl != null ) { bool foundFocusedChild; do { foundFocusedChild = false; foreach( Control child in ctl.Controls ) { if ( child.ContainsFocus ) { ctl = child; foundFocusedChild = true; break; } } } while( foundFocusedChild ); } while( ctl != null ) { IContextProvider provider = ctl as IContextProvider; if ( provider != null ) { IActionContext context = provider.GetContext( ActionContextKind.MainMenu ); if ( context != null ) { return context; } } ctl = ctl.Parent; } return new ActionContext( ActionContextKind.MainMenu, null, null ); } /** * Registers a group for toolbar buttons. */ public void RegisterToolbarActionGroup( string groupId, ListAnchor anchor ) { _toolbarManager.RegisterActionGroup( groupId, anchor ); } /** * Adds an action button with an Icon image to the toolbar. */ public void RegisterToolbarAction( IAction action, string groupId, ListAnchor anchor, Icon icon, string text, string tooltip, string resourceType, IActionStateFilter[] filters ) { _toolbarManager.RegisterAction( action, groupId, anchor, icon, text, tooltip, resourceType, filters ); } /** * Adds an action button to the toolbar. */ public void RegisterToolbarAction( IAction action, string groupId, ListAnchor anchor, Image icon, string text, string tooltip, string resourceType, IActionStateFilter[] filters ) { _toolbarManager.RegisterAction( action, groupId, anchor, icon, text, tooltip, resourceType, filters ); } public void RegisterToolbarActionFilter( string actionId, IActionStateFilter filter ) { _toolbarManager.RegisterActionFilter( actionId, filter ); } public void UnregisterToolbarAction( IAction action ) { _toolbarManager.UnregisterAction( action ); } /** * Registers an action group for the URL bar. */ public void RegisterUrlBarActionGroup( string groupId, ListAnchor anchor ) { _resourceBrowser.RegisterUrlBarActionGroup( groupId, anchor ); } /** * Adds an action button with an Icon image to the URL bar. */ public void RegisterUrlBarAction( IAction action, string groupId, ListAnchor anchor, Icon icon, string text, string tooltip, IActionStateFilter[] filters ) { _resourceBrowser.RegisterUrlBarAction( action, groupId, anchor, icon, text, tooltip, filters ); } public void RegisterUrlBarAction( IAction action, string groupId, ListAnchor anchor, Image icon, string text, string tooltip, IActionStateFilter[] filters ) { _resourceBrowser.RegisterUrlBarAction( action, groupId, anchor, icon, text, tooltip, filters ); } public void UnregisterUrlBarAction( IAction action ) { _resourceBrowser.UnregisterUrlBarAction( action ); } /** * Registers a group of actions used in the popup menu. */ public void RegisterContextMenuActionGroup( string name, ListAnchor anchor ) { RegisterContextMenuActionGroup( name, null, anchor ); } public void RegisterContextMenuActionGroup( string name, string submenuName, ListAnchor anchor ) { _contextMenuActionManager.RegisterGroup( name, submenuName, anchor ); } public void SuppressContextMenuGroupSeparator( string groupId1, string groupId2 ) { _contextMenuActionManager.SuppressGroupSeparator( groupId1, groupId2 ); } /** * Registers an action in a specified group for the specified resource types. */ public void RegisterContextMenuAction( IAction action, string groupId, ListAnchor anchor, string text, Image icon, string resourceType, IActionStateFilter[] filters ) { _contextMenuActionManager.RegisterAction( action, groupId, anchor, text, icon, resourceType, filters ); } public void UnregisterContextMenuAction( IAction action ) { _contextMenuActionManager.UnregisterAction( action ); } internal static void ExecuteAction( IAction action, ActionContext context ) { if ( action != null ) { try { action.Execute( context ); } catch( Exception e ) { Core.ReportException( e, false ); } } } /** * Registers an action that is executed when a resource is double-clicked. */ public void RegisterDoubleClickAction( IAction action, string resourceType, IActionStateFilter[] filters ) { if ( resourceType == null ) _genericDoubleClickActions.Add( new FilteredAction( action, filters ) ); else _doubleClickActions [resourceType] = new FilteredAction( action, filters ); } public void ExecuteDoubleClickAction( IResource res ) { if ( res.IsDeleted ) { return; } IAction action = GetDoubleClickAction( res ); if ( action != null ) { ActionContext context = new ActionContext( ActionContextKind.Other, null, res.ToResourceList() ); action.Execute( context ); } } public void ExecuteDoubleClickAction( IResourceList resList ) { for( int i = 0; i < resList.Count; i++ ) { IResource res; try { res = resList[ i ]; } catch( StorageException ) { continue; } ExecuteDoubleClickAction( res ); } } public IAction GetDoubleClickAction( IResource res ) { ActionContext context = new ActionContext( ActionContextKind.Other, null, res.ToResourceList() ); FilteredAction action = (FilteredAction) _doubleClickActions [res.Type]; if ( action != null && action.CanExecute( context ) ) { return action.Action; } foreach( FilteredAction genericAction in _genericDoubleClickActions ) { if ( genericAction.CanExecute( context ) ) { return genericAction.Action; } } return null; } public void UnregisterDoubleClickAction( IAction action ) { foreach( FilteredAction fAction in _genericDoubleClickActions ) { if ( fAction.Action == action ) { _genericDoubleClickActions.Remove( fAction ); return; } } foreach( DictionaryEntry de in _doubleClickActions ) { FilteredAction fAction = (FilteredAction) de.Value; if ( fAction.Action == action ) { _doubleClickActions.Remove( de.Key ); break; } } } /** * Registers an action which is executed when a link is clicked in the * links pane. */ public void RegisterLinkClickAction( IAction action, string resourceType, IActionStateFilter[] filters ) { #region Preconditions if ( resourceType == null ) throw new ArgumentNullException( "resourceType" ); #endregion Preconditions _linkClickActions [resourceType] = new FilteredAction( action, filters ); } public void UnregisterLinkClickAction( IAction action ) { foreach( DictionaryEntry de in _linkClickActions ) { FilteredAction fAction = (FilteredAction) de.Value; if ( fAction.Action == action ) { _linkClickActions.Remove( de.Key ); break; } } } public bool ExecuteLinkClickAction( IActionContext context ) { #region Preconditions if ( context == null ) throw new ArgumentNullException( "context" ); #endregion Preconditions if ( context.SelectedResources.Count > 0 ) { FilteredAction action = (FilteredAction) _linkClickActions [context.SelectedResources [0].Type]; if ( action == null ) return false; return action.Execute( context ); } return false; } /** * Shows the context menu for the specified resources. */ void IActionManager.ShowResourceContextMenu( IActionContext context, Control ownerControl, int x, int y ) { #region Preconditions if ( context == null ) throw new ArgumentNullException( "context" ); #endregion Preconditions _contextMenuActionManager.ActionContext = context; _contextMenu.Show( ownerControl, new Point( x, y ) ); } #region Keyboard Actions /** * Registers a keyboard shortcut for the action. */ public void RegisterKeyboardAction( IAction action, Keys shortcutKey, string resourceType, IActionStateFilter[] filters ) { KeyboardAction kbdAction = new KeyboardAction( action, shortcutKey, resourceType, filters ); ArrayList keyActions = (ArrayList) _keyToAction [shortcutKey]; if ( keyActions == null ) { keyActions = new ArrayList(); _keyToAction [shortcutKey] = keyActions; } keyActions.Add( kbdAction ); if ( !_actionToKey.ContainsKey( action ) ) { _actionToKey[ action ] = shortcutKey; } } public void UnregisterKeyboardAction( IAction action ) { // one action may be registered for multiple shortcuts, so we can't use the information // in _actionToKey to determine from what lists the action should be removed _actionToKey.Remove( action ); foreach( DictionaryEntry de in _keyToAction ) { ArrayList keyActions = (ArrayList) de.Value; foreach( KeyboardAction kbdAction in keyActions ) { if ( kbdAction.Action == action ) { keyActions.Remove( kbdAction ); break; } } } } /** * Returns the keyboard shortcut assigned to the specified action (if there * are many, returns a random one). */ public string GetKeyboardShortcut( IAction action ) { if ( _actionToKey.ContainsKey( action ) ) { Keys key = (Keys) _actionToKey [action]; string result = (string) _keysConverter.ConvertTo( key, typeof(string) ); // KeysConverter converts Ctrl+digit combinations to Ctrl+D#, // instead of the correct Ctrl+# if ( result.Length >= 2 && Char.IsDigit( result, result.Length-1 ) && result [result.Length-2] == 'D' ) { result = result.Substring( 0, result.Length-2 ) + result.Substring( result.Length-1 ); } return result; } return ""; } public string GetKeyboardShortcut( IAction action, IActionContext context ) { if ( _actionToKey.ContainsKey( action ) ) { Keys key = (Keys) _actionToKey [action]; ArrayList kbdActions = (ArrayList) _keyToAction [key]; foreach( KeyboardAction kbdAction in kbdActions ) { if ( kbdAction.Action.Equals( action ) ) { return GetKeyboardShortcut( action ); } } } return ""; } public Keys? GetKeyboardShortcutEx( IAction action, IActionContext context ) { if ( _actionToKey.ContainsKey( action ) ) { Keys key = (Keys) _actionToKey [action]; ArrayList kbdActions = (ArrayList) _keyToAction [key]; foreach( KeyboardAction kbdAction in kbdActions ) { if ( kbdAction.Action.Equals( action ) ) { return key; } } } return null; } /** * Tries to find a keyboard action registered for the specified shortcut * key and resources. Returns true if the action has been successfully * executed and false otherwise. */ bool IActionManager.ExecuteKeyboardAction( IActionContext context, Keys shortcutKey ) { if ( context == null ) context = new ActionContext( ActionContextKind.Keyboard, null, null ); ArrayList keyActions = (ArrayList) _keyToAction [shortcutKey]; if ( keyActions != null ) { string[] resTypes = context.SelectedResources.GetAllTypes(); foreach( KeyboardAction keyAction in keyActions ) { if ( keyAction.ResourceType != null && (resTypes.Length != 1 || resTypes [0] != keyAction.ResourceType ) ) { continue; } if ( keyAction.Execute( context ) ) { return true; } } } return false; } #endregion Keyboard Actions #region LinksPane Actions /** * Registers an action for the Links pane. */ public void RegisterLinksPaneAction( IAction action, string text, string resourceType, IActionStateFilter[] filters ) { LinksPaneActionManager.GetManager().RegisterAction( action, text, resourceType, filters ); } public void UnregisterLinksPaneAction( IAction action ) { LinksPaneActionManager.GetManager().UnregisterAction( action ); } #endregion LinksPane Actions internal void RegisterCompositeAction( string id, CompositeAction action ) { _compositeActions [id] = action; } /** * Registers a component that is contributed by a plugin to a composite action. */ public void RegisterActionComponent( IAction action, string compositeId, string resourceType, IActionStateFilter[] filters ) { CompositeAction composite = (CompositeAction) _compositeActions [compositeId]; if ( composite == null ) { throw new ArgumentException( "Invalid composite ID " + compositeId, "compositeId" ); } composite.AddComponent( resourceType, action, filters ); } #region XML configuration public void DisableXmlActionConfiguration( Assembly pluginAssembly ) { _excludedActionAssemblies.Add( pluginAssembly ); } /** * Loads the XML configuration for actions. */ internal void LoadXmlConfiguration( Assembly pluginAssembly, XmlNode node ) { if ( _excludedActionAssemblies.Contains( pluginAssembly ) ) { return; } XmlAttribute attr = node.Attributes ["namespace"]; string ns = (attr == null) ? "" : attr.Value; foreach( XmlNode childNode in node.ChildNodes ) { try { switch( childNode.Name ) { case "actiondef": LoadActionDef( childNode ); break; case "main-menu": LoadMainMenuActions( pluginAssembly, childNode, ns ); break; case "popup-menu": LoadContextMenuActions( pluginAssembly, childNode, ns ); break; case "context-menu": LoadContextMenuActions( pluginAssembly, childNode, ns ); break; case "keyboard": LoadKeyboardActions( pluginAssembly, childNode, ns ); break; case "double-click": LoadDoubleClickActions( pluginAssembly, childNode, ns ); break; case "toolbar": LoadToolbarActions( pluginAssembly, childNode, ns, false ); break; case "urlbar": LoadToolbarActions( pluginAssembly, childNode, ns, true ); break; case "links-pane": LoadLinksPaneActions( pluginAssembly, childNode, ns ); break; case "link-click": LoadLinkClickActions( pluginAssembly, childNode, ns ); break; case "composite": LoadCompositeActions( pluginAssembly, childNode, ns ); break; } } catch( XmlToolsException e ) { throw new ActionException( "Error loading <" + childNode.Name + "> for " + pluginAssembly.GetName().Name + ": " + e.Message ); } catch( ActionException e ) { throw new ActionException( "Error loading <" + childNode.Name + "> for " + pluginAssembly.GetName().Name + ": " + e.Message ); } } _actionDefNodes.Clear(); } /** * Loads an action with a set of parameters and filters which can be reused later. */ private void LoadActionDef( XmlNode node ) { string id = XmlTools.GetRequiredAttribute( node, "id" ); _actionDefNodes [id] = node; } /** * If the specified node contains an actiondef reference, returns the actiondef * node. Otherwise, returns the node itself. */ private XmlNode GetActionDefNode( XmlNode node ) { XmlAttribute attr = node.Attributes ["ref"]; if ( attr != null ) { XmlNode actionDefNode = (XmlNode) _actionDefNodes [attr.Value]; if ( actionDefNode == null ) { throw new ActionException( "Unknown action reference " + attr.Value ); } return actionDefNode; } return node; } /** * Loads the main menu actions from the XML file. */ private void LoadMainMenuActions( Assembly pluginAssembly, XmlNode node, string ns ) { foreach( XmlNode menuNode in node.SelectNodes( "menu" ) ) { string menuName = XmlTools.GetRequiredAttribute( menuNode, "name" ); ListAnchor anchor = LoadListAnchor( menuNode ); RegisterMainMenu( menuName, anchor ); } foreach( XmlNode groupNode in node.SelectNodes( "group" ) ) { string groupId = XmlTools.GetRequiredAttribute( groupNode, "id" ); XmlAttribute attrMenu = groupNode.Attributes ["menu"]; if ( attrMenu != null ) { XmlAttribute attrSubmenu = groupNode.Attributes ["submenu"]; ListAnchor anchor = LoadListAnchor( groupNode ); RegisterMainMenuActionGroup( groupId, attrMenu.Value, ( attrSubmenu == null ) ? null : attrSubmenu.Value, anchor ); } XmlAttribute keepWith = groupNode.Attributes ["keepwith"]; if ( keepWith != null ) { SuppressMainMenuGroupSeparator( groupId, keepWith.Value ); } foreach( XmlNode actionNode in groupNode.SelectNodes( "action" ) ) { ListAnchor anchor = LoadListAnchor( actionNode ); string text = XmlTools.GetRequiredAttribute( actionNode, "name" ); string resType = XmlTools.GetOptionalAttribute( actionNode, "type" ); string iconPath = XmlTools.GetOptionalAttribute( actionNode, "icon" ); // Image icon = ( iconPath == null ) ? null : Utils.TryGetEmbeddedResourceImageFromAssembly( pluginAssembly.GetName().Name, iconPath ); Image icon = ( iconPath == null ) ? null : Utils.TryGetEmbeddedResourceImageFromAssembly( pluginAssembly, iconPath ); XmlNode actionDefNode = GetActionDefNode( actionNode ); try { IAction action = CreateActionFromXml( pluginAssembly, actionDefNode, ns ); IActionStateFilter[] filters = LoadFiltersFromXml( pluginAssembly, actionDefNode, ns, TabFilterMode.None ); RegisterMainMenuAction( action, groupId, anchor, text, icon, resType, filters ); } catch( Exception ex ) { Core.ReportException( ex, false ); } } } } /** * Loads the context menu actions from the XML file. */ private void LoadContextMenuActions( Assembly pluginAssembly, XmlNode node, string ns ) { foreach( XmlNode groupNode in node.SelectNodes( "group" ) ) { string groupId = XmlTools.GetRequiredAttribute( groupNode, "id" ); ListAnchor anchor = LoadListAnchor( groupNode ); XmlAttribute attrSubmenu = groupNode.Attributes ["submenu"]; RegisterContextMenuActionGroup( groupId, (attrSubmenu == null) ? null : attrSubmenu.Value, anchor ); XmlAttribute keepWith = groupNode.Attributes ["keepwith"]; if ( keepWith != null ) { SuppressContextMenuGroupSeparator( groupId, keepWith.Value ); } foreach( XmlNode actionNode in groupNode.SelectNodes( "action" ) ) { string attrName = XmlTools.GetRequiredAttribute( actionNode, "name" ); string resType = XmlTools.GetOptionalAttribute( actionNode, "type" ); string iconPath = XmlTools.GetOptionalAttribute( actionNode, "icon" ); // Image icon = ( iconPath == null ) ? null : Utils.TryGetEmbeddedResourceImageFromAssembly( pluginAssembly.GetName().Name, iconPath ); Image icon = ( iconPath == null ) ? null : Utils.TryGetEmbeddedResourceImageFromAssembly( pluginAssembly, iconPath ); XmlNode actionDefNode = GetActionDefNode( actionNode ); try { IAction action = CreateActionFromXml( pluginAssembly, actionDefNode, ns ); anchor = LoadListAnchor( actionNode ); IActionStateFilter[] filters = LoadFiltersFromXml( pluginAssembly, actionDefNode, ns, TabFilterMode.None ); RegisterContextMenuAction( action, groupId, anchor, attrName, icon, resType, filters ); } catch( Exception ex ) { Core.ReportException( ex, false ); } } } } /** * Loads the keyboard actions from the XML file. */ private void LoadKeyboardActions( Assembly pluginAssembly, XmlNode node, string ns ) { foreach( XmlNode actionNode in node.SelectNodes( "action" ) ) { string key = XmlTools.GetRequiredAttribute( actionNode, "key" ); XmlAttribute attrResType = actionNode.Attributes ["type"]; Keys shortcut = (Keys) _keysConverter.ConvertFromInvariantString( key ); XmlNode actionDefNode = GetActionDefNode( actionNode ); try { IAction action = CreateActionFromXml( pluginAssembly, actionDefNode, ns ); IActionStateFilter[] filters = LoadFiltersFromXml( pluginAssembly, actionDefNode, ns, TabFilterMode.TabOnly ); RegisterKeyboardAction( action, shortcut, attrResType == null ? null : attrResType.Value, filters ); } catch( Exception ex ) { Core.ReportException( ex, false ); } } } /** * Loads the double-click actions from the XML file. */ private void LoadDoubleClickActions( Assembly pluginAssembly, XmlNode node, string ns ) { foreach( XmlNode actionNode in node.SelectNodes( "action" ) ) { XmlAttribute attrResType = actionNode.Attributes ["type"]; XmlNode actionDefNode = GetActionDefNode( actionNode ); try { IAction action = CreateActionFromXml( pluginAssembly, actionDefNode, ns ); IActionStateFilter[] filters = LoadFiltersFromXml( pluginAssembly, actionDefNode, ns, TabFilterMode.None ); RegisterDoubleClickAction( action, (attrResType == null ? null : attrResType.Value), filters ); } catch( Exception ex ) { Core.ReportException( ex, false ); } } } /** * Loads the toolbar actions from the XML file. */ private void LoadToolbarActions( Assembly pluginAssembly, XmlNode node, string ns, bool urlbar ) { XmlAttribute attrIconPrefix = node.Attributes ["iconprefix"]; string iconPrefix = (attrIconPrefix == null) ? "" : attrIconPrefix.Value + "."; foreach( XmlNode groupNode in node.SelectNodes( "group" ) ) { string groupId = XmlTools.GetRequiredAttribute( groupNode, "id" ); ListAnchor anchor = LoadListAnchor( groupNode ); if ( urlbar ) { RegisterUrlBarActionGroup( groupId, anchor ); } else { RegisterToolbarActionGroup( groupId, anchor ); } foreach( XmlNode actionNode in groupNode.SelectNodes( "action" ) ) { anchor = LoadListAnchor( actionNode ); string tooltip = XmlTools.GetRequiredAttribute( actionNode, "tooltip" ); string icon = XmlTools.GetRequiredAttribute( actionNode, "icon" ); Stream stream = pluginAssembly.GetManifestResourceStream( iconPrefix + icon ); if ( stream == null ) throw new ActionException( "Icon " + icon + "not found" ); XmlAttribute attrType = actionNode.Attributes ["type"]; XmlAttribute attrText = actionNode.Attributes ["text"]; string text = (attrText == null) ? null : attrText.Value; XmlNode actionDefNode = GetActionDefNode( actionNode ); try { IActionStateFilter[] filters = LoadFiltersFromXml( pluginAssembly, actionDefNode, ns, TabFilterMode.TabOnly ); IAction action = CreateActionFromXml( pluginAssembly, actionDefNode, ns ); if ( urlbar ) { RegisterUrlBarAction( action, groupId, anchor, new Icon( stream ), text, tooltip, filters ); } else { string type = ( attrType == null ) ? null : attrType.Value; RegisterToolbarAction( action, groupId, anchor, new Icon( stream ), text, tooltip, type, filters ); } } catch( Exception ex ) { Core.ReportException( ex, false ); } } } } /** * Loads the links pane actions from the XML file. */ private void LoadLinksPaneActions( Assembly pluginAssembly, XmlNode node, string ns ) { foreach( XmlNode actionNode in node.SelectNodes( "action" ) ) { string name = XmlTools.GetRequiredAttribute( actionNode, "name" ); XmlAttribute attrType = actionNode.Attributes ["type"]; XmlNode actionDefNode = GetActionDefNode( actionNode ); try { IAction action = CreateActionFromXml( pluginAssembly, actionDefNode, ns ); IActionStateFilter[] filters = LoadFiltersFromXml( pluginAssembly, actionDefNode, ns, TabFilterMode.None ); RegisterLinksPaneAction( action, name, (attrType == null ) ? null : attrType.Value, filters ); } catch( Exception ex ) { Core.ReportException( ex, false ); } } } /** * Loads the link click actions from the XML file. */ private void LoadLinkClickActions( Assembly pluginAssembly, XmlNode node, string ns ) { foreach( XmlNode actionNode in node.SelectNodes( "action" ) ) { string resType = XmlTools.GetRequiredAttribute( actionNode, "type" ); XmlNode actionDefNode = GetActionDefNode( actionNode ); try { IAction action = CreateActionFromXml( pluginAssembly, actionDefNode, ns ); IActionStateFilter[] filters = LoadFiltersFromXml( pluginAssembly, actionDefNode, ns, TabFilterMode.None ); RegisterLinkClickAction( action, resType, filters ); } catch( Exception ex ) { Core.ReportException( ex, false ); } } } /** * Loads the composite actions from the XML file. */ private void LoadCompositeActions( Assembly pluginAssembly, XmlNode node, string ns ) { foreach( XmlNode actionNode in node.SelectNodes( "component" ) ) { string id = XmlTools.GetRequiredAttribute( actionNode, "id" ); XmlAttribute attrType = actionNode.Attributes ["type"]; XmlNode actionDefNode = GetActionDefNode( actionNode ); try { IAction action = CreateActionFromXml( pluginAssembly, actionDefNode, ns ); IActionStateFilter[] filters = LoadFiltersFromXml( pluginAssembly, actionNode, ns, TabFilterMode.TabOrAll ); RegisterActionComponent( action, id, ( attrType == null ) ? null : attrType.Value, filters ); } catch( Exception ex ) { Core.ReportException( ex, false ); } } } /** * Creates an IAction instance from the specified XML node. */ private IAction CreateActionFromXml( Assembly pluginAssembly, XmlNode actionNode, string ns ) { Type actionType = FindActionType( pluginAssembly, actionNode, ns ); string cacheKey = GetActionClass( actionNode, ns ); object[] actionParams = null; XmlNodeList paramNodes = actionNode.SelectNodes( "param" ); if ( paramNodes.Count > 0 ) { actionParams = ParseActionParameters( paramNodes, ref cacheKey ); } if ( _xmlActionCache.Contains( cacheKey ) ) { return (IAction) _xmlActionCache [cacheKey]; } IAction action = ( actionParams == null ) ? (IAction) Activator.CreateInstance( actionType ) : (IAction) Activator.CreateInstance( actionType, actionParams ); _xmlActionCache.Add( cacheKey, action ); return action; } /** * Finds an instance of the action class for the specified default assembly, * class name and default namespace. */ private Type FindActionType( Assembly pluginAssembly, XmlNode actionNode, string ns ) { string actionClass = GetActionClass( actionNode, ns ); Assembly actionAssembly = pluginAssembly; XmlAttribute attrAsm = actionNode.Attributes ["assembly"]; if ( attrAsm != null ) { actionAssembly = (Assembly) _assemblyNameCache [attrAsm.Value]; if ( actionAssembly == null ) { actionAssembly = Utils.FindAssembly( attrAsm.Value ); _assemblyNameCache [attrAsm.Value] = actionAssembly; } } return actionAssembly.GetType( actionClass, true ); } private static string GetActionClass( XmlNode actionNode, string ns ) { string actionClass = XmlTools.GetRequiredAttribute( actionNode, "class" ); if ( ns != "" && actionClass.IndexOf( '.') < 0 ) { actionClass = ns + "." + actionClass; } return actionClass; } /** * Parses the action constructor parameters from the specified collection of nodes. */ private static object[] ParseActionParameters( XmlNodeList paramNodes, ref string cacheKey ) { ArrayList paramList = new ArrayList(); foreach( XmlNode node in paramNodes ) { string paramType = XmlTools.GetRequiredAttribute( node, "type" ); string paramValue = XmlTools.GetRequiredAttribute( node, "value" ); switch( paramType ) { case "string": paramList.Add( paramValue ); break; case "int": paramList.Add( Int32.Parse( paramValue ) ); break; case "bool": paramList.Add( Boolean.Parse( paramValue ) ); break; default: throw new ActionException( "Invalid or unsupported action parameter type " + paramType ); } cacheKey += "," + paramValue; } return paramList.ToArray(); } private static ListAnchor LoadListAnchor( XmlNode node ) { XmlAttribute attr = node.Attributes ["anchor"]; if ( attr == null ) { return new ListAnchor( AnchorType.Last ); } string anchor = attr.Value; string anchorRef = null; int pos = anchor.IndexOf( ':' ); if ( pos >= 0 ) { anchorRef = anchor.Substring( pos+1 ); anchor = anchor.Substring( 0, pos ); } AnchorType type; switch( anchor ) { case "first": type = AnchorType.First; break; case "last": type = AnchorType.Last; break; case "before": type = AnchorType.Before; break; case "after": type = AnchorType.After; break; default: throw new ActionException( "Invalid anchor type " + anchor ); } return new ListAnchor( type, anchorRef ); } private enum TabFilterMode { None, TabOnly, TabOrAll }; /** * Loads an array of action state filters from the specified XML node. */ private IActionStateFilter[] LoadFiltersFromXml( Assembly pluginAssembly, XmlNode node, string ns, TabFilterMode tabFilterMode ) { ArrayList filters = null; if ( tabFilterMode != TabFilterMode.None ) { XmlAttribute attrTab = node.Attributes ["tab"]; if ( attrTab != null ) { filters = new ArrayList(); filters.Add( new ActiveTabFilter( attrTab.Value, (tabFilterMode == TabFilterMode.TabOrAll) ) ); } } foreach( XmlNode filterNode in node.SelectNodes( "filter" ) ) { Type filterType = FindActionType( pluginAssembly, filterNode, ns ); object[] filterParams = null; XmlNodeList paramNodes = filterNode.SelectNodes( "param" ); if ( paramNodes.Count > 0 ) { string cacheKey = ""; filterParams = ParseActionParameters( paramNodes, ref cacheKey ); } IActionStateFilter filter; if ( filterParams != null ) { filter = (IActionStateFilter) Activator.CreateInstance( filterType, filterParams ); } else { filter = (IActionStateFilter) Activator.CreateInstance( filterType ); } if ( filters == null ) { filters = new ArrayList(); } filters.Add( filter ); } if ( filters != null ) { return (IActionStateFilter[]) filters.ToArray( typeof(IActionStateFilter) ); } return null; } #endregion } }