/// /// 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.Generic; using System.Drawing; using JetBrains.Omea.Conversations; using JetBrains.Omea.GUIControls; using JetBrains.Omea.OpenAPI; using JetBrains.UI.RichText; namespace JetBrains.Omea.Nntp { internal class NewsNodeDecorator : IResourceNodeDecorator { private readonly TextStyle _unreadTextStyle = new TextStyle( FontStyle.Regular, Color.Blue, SystemColors.Window ); private readonly TextStyle _draftsTextStyle = new TextStyle( FontStyle.Regular, Color.Green, SystemColors.Window ); private readonly List _updatedResources; private readonly IResourceList _allUnread; private readonly IResourceList _unreadArticles; public event ResourceEventHandler DecorationChanged; public NewsNodeDecorator() { _updatedResources = new List(); IResourceStore store = Core.ResourceStore; _allUnread = Core.ResourceStore.FindResourcesWithPropLive( null, Core.Props.IsUnread ).Minus( Core.ResourceStore.FindResourcesWithPropLive( null, Core.Props.IsDeleted ) ); _unreadArticles = store.FindResourcesWithPropLive( NntpPlugin._newsArticle, Core.Props.IsUnread ); _unreadArticles = _unreadArticles.Union( store.GetAllResourcesLive( NntpPlugin._newsLocalArticle ) ); _unreadArticles.ResourceAdded += _unreadArticles_Updated; _unreadArticles.ResourceChanged += _unreadArticles_ResourceChanged; _unreadArticles.ResourceDeleting += _unreadArticles_Updated; Core.ResourceAP.JobFinished += ResourceAP_JobFinished; Core.WorkspaceManager.WorkspaceChanged += WorkspaceManager_WorkspaceChanged; } public bool DecorateNode( IResource res, RichText nodeText ) { if( res.Type == NntpPlugin._newsFolder || res.Type == NntpPlugin._newsServer ) { int count; if( NewsFolders.IsDefaultFolder( res ) ) { IResourceList folderItems = NntpPlugin.CollectArticles( res, false ); IResource wsp = Core.WorkspaceManager.ActiveWorkspace; if ( wsp != null ) { folderItems = folderItems.Intersect( wsp.GetLinksOfType( null, "WorkspaceVisible" ), true ); } if( NewsFolders.IsSentItems( res ) ) { folderItems = folderItems.Intersect( _allUnread, true ); } else { folderItems = folderItems.Minus( Core.ResourceStore.FindResourcesWithProp( null, Core.Props.IsDeleted ) ); } count = folderItems.Count; } else { IResourceList groups = new NewsTreeNode( res ).Groups; IResource wsp = Core.WorkspaceManager.ActiveWorkspace; if ( wsp != null ) { groups = groups.Intersect( wsp.GetLinksOfType( null, "WorkspaceVisible" ), true ); } IResourceList articles = Core.ResourceStore.EmptyResourceList; foreach( IResource group in groups.ValidResources ) { articles = articles.Union( group.GetLinksTo( null, NntpPlugin._propTo ).Intersect( _allUnread ), true ); } count = articles.Count; } if( count != 0 ) { nodeText.Append( " " ); nodeText.SetStyle( FontStyle.Bold, 0, res.DisplayName.Length ); ///////////////////////////////////////////////////////////////////// /// NewsFolders.IsDefaultFolder doesn't synchronously create folders, /// so the check is necessary to avoid RunJobs to resource thread ///////////////////////////////////////////////////////////////////// if( NewsFolders.IsDrafts( res ) ) { nodeText.Append( "[" + count + "]", _draftsTextStyle ); } else { nodeText.Append( "(" + count + ")", _unreadTextStyle ); } } return true; } return false; } public string DecorationKey { get { return UnreadNodeDecorator.Key; } } private void _unreadArticles_Updated( object sender, ResourceIndexEventArgs e ) { _updatedResources.Add( e.Resource ); } private void _unreadArticles_ResourceChanged( object sender, ResourcePropIndexEventArgs e ) { if( e.ChangeSet.IsPropertyChanged( Core.Props.IsDeleted ) ) { _updatedResources.Add( e.Resource ); } } private void ResourceAP_JobFinished( object sender, EventArgs e ) { if( DecorationChanged != null ) { foreach( IResource res in _updatedResources ) { DecorateArticle( res ); } } _updatedResources.Clear(); } private void DecorateArticle( IResource article ) { IResourceList groups = article.GetLinksFrom( null, NntpPlugin._propTo ); foreach( IResource group in groups ) { IResource res = group; do { if( res.Type != NntpPlugin._newsGroup ) { Core.UserInterfaceAP.QueueJob( new ResourceDelegate( DecorateResource ), res ); } res = new NewsTreeNode( res ).Parent; } while( res != null && res != NewsFolders.Root ); } } private void WorkspaceManager_WorkspaceChanged( object sender, EventArgs e ) { ResourceDelegate job = DecorateResource; Core.UserInterfaceAP.QueueJob( job, NewsFolders.Drafts ); Core.UserInterfaceAP.QueueJob( job, NewsFolders.Outbox ); } private void DecorateResource( IResource res ) { if( DecorationChanged != null ) { DecorationChanged( this, new ResourceEventArgs( res ) ); } } } internal class WatchedArticlesDecorator : IResourceNodeDecorator { private const string _cKey = "WatchedUnreadArticlesDecorator"; private readonly TextStyle _watchedTextStyle = new TextStyle( FontStyle.Bold, Color.Red, SystemColors.Window ); private readonly IResourceList _unreadArticles; private IResourceList _heads; private readonly IResourceList _formattingRules; private readonly Dictionary> _groups2watchedHeads = new Dictionary>(); public event ResourceEventHandler DecorationChanged; public WatchedArticlesDecorator() { IResourceStore store = Core.ResourceStore; _unreadArticles = store.FindResourcesWithPropLive( NntpPlugin._newsArticle, Core.Props.IsUnread ); _unreadArticles = _unreadArticles.Minus( store.FindResourcesWithPropLive( null, Core.Props.IsDeleted )); _unreadArticles.ResourceAdded += _unreadArticles_Updated; _unreadArticles.ResourceDeleting += _unreadArticles_Updated; _formattingRules = Core.FilterRegistry.GetFormattingRules( false ); _formattingRules.ResourceAdded += RuleConditionsListChanged; _formattingRules.ResourceDeleting += RuleConditionsListChanged; _formattingRules.ResourceChanged += RuleConditionsChanged; HeadsChanged(); } public bool DecorateNode( IResource res, RichText nodeText ) { if( res.Type == NntpPlugin._newsGroup ) { int count = 0; if( _groups2watchedHeads.ContainsKey( res )) { IResourceList groupItems = NntpPlugin.CollectArticles( res, false ); groupItems = groupItems.Intersect( _unreadArticles ); List heads = _groups2watchedHeads[ res ]; foreach( IResource head in heads ) { IResourceList thread = ConversationBuilder.UnrollConversationFromCurrent( head ); count += thread.Intersect( groupItems ).Count; } } if( count != 0 ) { nodeText.Append( " !", _watchedTextStyle ); } return count != 0; } return false; } public string DecorationKey { get { return _cKey; } } private void _unreadArticles_Updated( object sender, ResourceIndexEventArgs e ) { if( DecorationChanged != null ) { foreach( IResource head in _heads ) { if( ConversationBuilder.AreLinked( head, e.Resource ) ) { DecorationChanged( this, new ResourceEventArgs( e.Resource ) ); } } } } private void RuleConditionsChanged(object sender, ResourcePropIndexEventArgs e) { HeadsChanged(); NotifyGroups(); } private void RuleConditionsListChanged( object sender, ResourceIndexEventArgs e ) { HeadsChanged(); NotifyGroups(); } private void HeadsChanged() { _groups2watchedHeads.Clear(); _heads = RecollectThreadHeads(); foreach( IResource head in _heads ) { IResourceList groups = head.GetLinksOfType( NntpPlugin._newsGroup, NntpPlugin._propTo ); foreach( IResource group in groups ) { List heads; if( !_groups2watchedHeads.TryGetValue( group, out heads ) ) { heads = new List(); _groups2watchedHeads.Add( group, heads ); } heads.Add( head ); } } } private IResourceList RecollectThreadHeads() { IResource template = Core.FilterRegistry.Std.MessageIsInThreadOfX; IResourceList heads = Core.ResourceStore.EmptyResourceList; IResourceList conditions = Core.ResourceStore.EmptyResourceList; foreach( IResource rule in _formattingRules ) { conditions = conditions.Union( Core.FilterRegistry.GetConditionsPlain( rule ) ); } conditions = conditions.Intersect( Core.FilterRegistry.GetLinkedConditions( template ) ); foreach( IResource condition in conditions ) { IResourceList conditionHeads = condition.GetLinksOfType( NntpPlugin._newsArticle, Core.FilterRegistry.Props.SetValueLink ); heads = heads.Union( conditionHeads ); } return heads; } private void NotifyGroups() { if( DecorationChanged != null ) { foreach( IResource group in _groups2watchedHeads.Keys ) DecorationChanged( this, new ResourceEventArgs( group ) ); } } } }