/// /// 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.Collections.Generic; using System.Diagnostics; using System.Text; using System.Windows.Forms; using JetBrains.Omea.Base; using JetBrains.Omea.Containers; using JetBrains.Omea.OpenAPI; using JetBrains.Omea.ResourceTools; namespace JetBrains.Omea.Categories { internal class CategoryUIHandler : IResourceUIHandler, IResourceDragDropHandler { private IResource _lastCategory; private IResourceList _lastCategoryList; private bool _lastCategoryListRecursive; private CategoryManager _categoryManager; public CategoryUIHandler( CategoryManager categoryManager ) { _categoryManager = categoryManager; } public void ResourceNodeSelected( IResource res ) { bool categoryListRecursive = res.HasProp( _categoryManager.PropShowContentsRecursively ); if( res != _lastCategory || categoryListRecursive != _lastCategoryListRecursive ) { _lastCategory = res; _lastCategoryListRecursive = categoryListRecursive; if( _lastCategoryListRecursive ) { _lastCategoryList = BuildRecursiveContentsList( res ); } else { _lastCategoryList = res.GetLinksOfTypeLive( null, _categoryManager.PropCategory ); } _lastCategoryList = ResourceTypeHelper.ExcludeUnloadedPluginResources( _lastCategoryList ); _lastCategoryList.Sort( new SortSettings( Core.Props.Date, true ) ); } ResourceListDisplayOptions options = new ResourceListDisplayOptions(); options.Caption = "Resources in category " + res.DisplayName; if( _lastCategoryListRecursive ) { options.Caption += " and subcategories"; } options.SeeAlsoBar = true; Core.ResourceBrowser.DisplayConfigurableResourceList( res, _lastCategoryList, options ); } private IResourceList BuildRecursiveContentsList( IResource res ) { IResourceList result = res.GetLinksOfTypeLive( null, _categoryManager.PropCategory ); foreach( IResource child in res.GetLinksTo( "Category", Core.Props.Parent ) ) { result = result.Union( BuildRecursiveContentsList( child ), true ); } return result; } public bool CanRenameResource( IResource res ) { return true; } public bool ResourceRenamed( IResource res, string newName ) { return Core.CategoryManager.CheckRenameCategory( Core.MainWindow, res, newName ); } public bool CanDropResources( IResource targetResource, IResourceList dragResources ) { throw new NotImplementedException( "Obsolete." ); } public void ResourcesDropped( IResource targetResource, IResourceList droppedResources ) { throw new NotImplementedException( "Obsolete." ); } /// /// Drops a list of categories (resources of other types may be present in the list, but they are ignored) over a category, or a cat-tree-root, or a cat-folder. /// Respects the parent constraints over the content-type, changes the content-type if needed and possible. /// /// Whether anything could be dropped. internal bool HandleDropCategoriesOnCategory( IResourceList resDroppedRaw, IResource targetResource ) { // C-type restriction of the droptarget (only categories are restricted) string sParentContentType = null; if( targetResource.Type == "Category" ) sParentContentType = targetResource.GetStringProp( Core.Props.ContentType ); else if( targetResource.Type == "ResourceTreeRoot" ) { // If it's a resource tree root, check for its c-type constraint string sCType = targetResource.GetStringProp( "RootResourceType" ); if( (sCType != null) && (sCType != "Category") && (sCType.StartsWith( "Category" )) ) sParentContentType = sCType.Substring( "Category".Length + 1 ); // Cut the leading “Category:” and leave the c-type } // Here we process categories only IResourceList resDropped = resDroppedRaw.Intersect( Core.ResourceStore.GetAllResources( "Category" ) ); ////////////////////// // Check Constraints // Ensure that the categories can really be dropped in here ArrayList arChangeCTypeFor = new ArrayList(); // A list of those dropped cats whose c-type does not fit the parent's one and must thus be changed foreach( IResource res in resDropped ) { // Don't allow dropping onto children if( IsParentCategory( res, targetResource ) ) { MessageBox.Show( Core.MainWindow, "A category cannot be made a child of itself.", Core.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation ); return false; } // Check if the dropped-categories (subcategory-wannabe) content-type is broader than the parent's content-type constraint string childContent = res.GetStringProp( Core.Props.ContentType ); if( (sParentContentType != null) && (childContent != sParentContentType) ) { // The child has another limitation over the content, or has gotten none at all arChangeCTypeFor.Add( res ); // The change will fail if there're unfit resources if( _categoryManager.GetUnmatchingResources( res, sParentContentType ) > 0 ) { MessageBox.Show( Core.MainWindow, String.Format( "You can only drop those categories that contain resources of type “{0}” here.", Core.ResourceStore.ResourceTypes[ sParentContentType ].DisplayName ), Core.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Stop ); return false; } } } ////////////////////// // Apply Constraints // Narrow the content-type restriction wherever needed if( arChangeCTypeFor.Count != 0 ) { if( sParentContentType == null ) throw new InvalidOperationException( "Parent resource type is undefined." ); // Prompt of the c-type change if( MessageBox.Show( Core.MainWindow, String.Format( "The category you're dropping onto restricts its contents to “{0}” resources only.\nThe same restriction will be applied to the dropped categories.", sParentContentType ), Core.ProductName, MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation ) != DialogResult.OK ) return false; // Narrow the c-type foreach( IResource res in arChangeCTypeFor ) _categoryManager.SetContentTypeRecursive( res, sParentContentType ); } ////////// // Drop! foreach( IResource res in resDropped ) new ResourceProxy( res, JobPriority.Immediate ).SetPropAsync( Core.Props.Parent, targetResource ); return true; // Dropped } internal bool HandleDropResourceOnCategory( IResourceList resDroppedRaw, IResource category ) { // Categories have already been processed separately IResourceList resDropped = resDroppedRaw.Minus( Core.ResourceStore.GetAllResources( "Category" ) ); // Content-type of the target category string contentType = category.GetStringProp( "ContentType" ); // Constrain foreach( IResource res in resDropped ) { if( Core.ResourceStore.ResourceTypes[ res.Type ].HasFlag( ResourceTypeFlags.Internal ) ) continue; if( contentType != null && contentType != res.Type ) { DialogResult dr = MessageBox.Show( Core.MainWindow, String.Format( "The category “{0}” is configured to contain only “{1}” resources. Do you wish to change it to a general category?", category.DisplayName, contentType ), "Add Resource to Category", MessageBoxButtons.YesNo, MessageBoxIcon.Question ); if( dr == DialogResult.No ) return false; Debug.WriteLine( "Changing parent for category " + category.Id ); _categoryManager.SetContentTypeRecursive( category, null ); new ResourceProxy( category, JobPriority.Immediate ).SetPropAsync( "Parent", Core.CategoryManager.RootCategory ); contentType = null; break; } } // Presence of Alt modifier means "movement" of a resource to a category, // not an addition of another one. bool isMove = (Control.ModifierKeys & Keys.Alt) > 0; foreach( IResource res in resDropped ) { if( !Core.ResourceStore.ResourceTypes[ res.Type ].HasFlag( ResourceTypeFlags.Internal ) ) { if( !isMove ) Core.CategoryManager.AddResourceCategory( res, category ); else Core.CategoryManager.SetResourceCategory( res, category ); } } return true; } private static bool IsParentCategory( IResource parent, IResource child ) { while( child != null ) { if( child == parent ) return true; child = (IResource)child.GetProp( "Parent" ); } return false; } #region IResourceDragDropHandler Members public void Drop( IResource targetResource, IDataObject data, DragDropEffects allowedEffect, int keyState ) { if( targetResource == null ) return; if( data.GetDataPresent( typeof(IResourceList) ) ) { IResourceList droppedResources = (IResourceList)data.GetData( typeof(IResourceList) ); // Drop the categories as subcategories, and attach generic resources to the category bool addedChildren = false; addedChildren = HandleDropCategoriesOnCategory( droppedResources, targetResource ) || addedChildren; addedChildren = HandleDropResourceOnCategory( droppedResources, targetResource ) || addedChildren; if( addedChildren ) new ResourceProxy( targetResource, JobPriority.Immediate ).SetPropAsync( _categoryManager.PropCategoryExpanded, 1 ); } } public DragDropEffects DragOver( IResource targetResource, IDataObject data, DragDropEffects allowedEffect, int keyState ) { if( data.GetDataPresent( typeof(IResourceList) ) ) { // The resources we're dragging IResourceList dragResources = (IResourceList)data.GetData( typeof(IResourceList) ); // Check the droptarget resource type (either a cat, or a root resource type for the cat or one of its filtrants) if( !((targetResource.Type == "Category") || ((targetResource.Type == "ResourceTreeRoot") && (targetResource.HasProp( "RootResourceType" )) && (targetResource.GetStringProp( "RootResourceType" ).StartsWith( "Category" )))) ) return DragDropEffects.None; // Collect all the direct and indirect parents of the droptarget; then we'll check to avoid dropping parent on its children List parentList = new List(); IResource parent = targetResource; while( parent != null ) { parentList.Add( parent.Id ); parent = parent.GetLinkProp( Core.Props.Parent ); } foreach( IResource res in dragResources ) { // Dropping parent over its child? if( parentList.IndexOf( res.Id ) >= 0 ) return DragDropEffects.None; } return DragDropEffects.Move; } return DragDropEffects.None; } public void AddResourceDragData( IResourceList dragResources, IDataObject dataObject ) { if( !dataObject.GetDataPresent( typeof(string) ) ) { StringBuilder sb = StringBuilderPool.Alloc(); try { foreach( IResource resource in dragResources ) { if( sb.Length != 0 ) sb.Append( ", " ); string text = resource.DisplayName; if( text.IndexOf( ' ' ) > 0 ) sb.Append( "“" + text + "”" ); else sb.Append( text ); } dataObject.SetData( sb.ToString() ); } finally { StringBuilderPool.Dispose( sb ); } } } #endregion } internal class CategoryRootUIHandler : IResourceUIHandler { public void ResourceNodeSelected( IResource res ) { Core.ResourceBrowser.DisplayResourceList( res, Core.ResourceStore.EmptyResourceList, res.DisplayName, null ); } public bool CanDropResources( IResource targetResource, IResourceList dragResources ) { throw new NotImplementedException( "Obsolete." ); } public void ResourcesDropped( IResource targetResource, IResourceList droppedResources ) { throw new NotImplementedException( "Obsolete." ); } public bool CanRenameResource( IResource res ) { return false; } public bool ResourceRenamed( IResource res, string newName ) { return false; } } }