/// <summary> /// Returns whether the message being edited matches the specified CIXMessage. /// </summary> public bool Matches(CIXMessage message) { if (_message == null || message == null) { return false; } return _message.CommentID == message.CommentID && _message.TopicID == message.TopicID; }
/// <summary> /// Find an existing message editor that matches the requested message. /// </summary> public static CIXMessageEditor Get(CIXMessage message) { if (_modelessList == null) { _modelessList = new List<CIXMessageEditor>(); return null; } return _modelessList.FirstOrDefault(editor => editor.Matches(message)); }
/// <summary> /// Mark this message as unread and update the corresponding folder /// unread counts. /// </summary> private void InnerMarkRead() { bool stateChanged = Unread; if (stateChanged) { Unread = false; ReadPending = true; } lock (CIX.DBLock) { CIX.DB.Update(this); } CIX.FolderCollection.NotifyMessageChanged(this); if (stateChanged) { Folder topic = Topic; topic.Unread -= 1; if (Priority && topic.UnreadPriority > 0) { topic.UnreadPriority -= 1; } if (!IsRoot) { CIXMessage root = topic.Messages.MessageByID(RootID); if (root != null) { lock (CIX.DBLock) { CIX.DB.Update(root); } //CIX.FolderCollection.NotifyMessageChanged(root); } } lock (CIX.DBLock) { CIX.DB.Update(topic); } CIX.FolderCollection.NotifyFolderUpdated(topic); } }
/// <summary> /// Add or remove a star from the specified message. /// </summary> private static void ToggleStar(CIXMessage item) { if (item != null) { if (item.Starred) { item.RemoveStar(); } else { item.AddStar(); } } }
/// <summary> /// Return the appropriate read icon for this message based on its unread and priority state /// </summary> private static Image ReadImageForMessage(CIXMessage message) { return (message.ReadLocked) ? Resources.ReadLock : (!message.Unread) ? Resources.ReadMessage : (message.Priority) ? Resources.UnreadPriorityMessage : Resources.UnreadMessage; }
/// <summary> /// Toggle read-lock state of a message. Also force the message unread /// if not already. /// </summary> private static void ToggleReadLock(CIXMessage item) { if (item != null) { if (item.ReadLocked) { item.ClearReadLock(); } else { item.MarkReadLock(); } } }
/// <summary> /// Select the original message for this if there is one. Download it /// if it is missing. /// </summary> private void SelectOriginal(CIXMessage message) { if (message != null && message.CommentID > 0) { Folder topic = message.Topic; Folder forum = topic.ParentFolder; FoldersTree.MainForm.Address = String.Format("cix:{0}/{1}:{2}", forum.Name, topic.Name, message.CommentID); } }
/// <summary> /// Print the specified message. /// </summary> /// <param name="message">Message to print</param> private void Print(CIXMessage message) { if (message != null) { PrintDocument printDoc = new PrintDocument { PrinterSettings = FoldersTree.MainForm.PrintDocument.PrinterSettings, DefaultPageSettings = FoldersTree.MainForm.PrintDocument.DefaultPageSettings }; Font printFont = new Font("Arial", 10); PrintDialog pdi = new PrintDialog { Document = printDoc, UseEXDialog = true }; if (pdi.ShowDialog() == DialogResult.OK) { string[] lines = message.Body.Split(new[] { '\n' }); int lineIndex = 0; try { printDoc.PrintPage += (sender, ev) => { float leftMargin = ev.MarginBounds.Left; float topMargin = ev.MarginBounds.Top; // Print each line of the file. float yPos = topMargin; while (lineIndex < lines.Count()) { string line = lines[lineIndex]; SizeF sf = ev.Graphics.MeasureString(line, printFont, ev.MarginBounds.Width); if (yPos + sf.Height > ev.MarginBounds.Bottom) { break; } using (Brush textBrush = new SolidBrush(SystemColors.ControlText)) { ev.Graphics.DrawString(line, printFont, textBrush, new RectangleF(new PointF(leftMargin, yPos), sf), new StringFormat()); } yPos += (sf.Height > 0) ? sf.Height : printFont.GetHeight(ev.Graphics); ++lineIndex; } // If more lines exist, print another page. ev.HasMorePages = lineIndex < lines.Count(); }; printDoc.Print(); } catch (Exception e) { MessageBox.Show(string.Format(Resources.PrintError, e.Message), Resources.Error, MessageBoxButtons.OK); } } } }
/// <summary> /// Return whether or not the specified message can be displayed. /// </summary> private bool CanShowMessage(CIXMessage cixMessage) { if (_currentFolder == null || !_currentFolder.CanContain(cixMessage)) { return false; } return !cixMessage.Ignored || _showIgnored; }
/// <summary> /// Topic folders can contain all messages. /// </summary> public override bool CanContain(CIXMessage message) { return message.TopicID == ID; }
/// <summary> /// Delete the specified message from the database /// </summary> /// <param name="message">The message to be withdrawn</param> private static void DeleteMessage(CIXMessage message) { if (message != null && message.IsPseudo) { Folder topic = message.Topic; topic.Messages.Delete(message); } }
/// <summary> /// Invoke the message editor on the specified message /// </summary> /// <param name="message">The message to edit</param> /// <param name="addSignature">True if signature is to be added</param> private static void EditMessage(CIXMessage message, bool addSignature) { CIXMessageEditor editor = MessageEditorCollection.Get(message) ?? new CIXMessageEditor(message, addSignature); editor.Show(); editor.BringToFront(); }
/// <summary> /// Return the CIX address of the specified message. /// </summary> private static string AddressFromMessage(CIXMessage message) { Folder topic = CIX.FolderCollection[message.TopicID]; Folder forum = topic.ParentFolder; return string.Format(@"cix:{0}/{1}:{2}", forum.Name, topic.Name, message.RemoteID); }
/// <summary> /// Return the appropriate body colour for the given message. /// </summary> private static Color BodyColourForItem(CIXMessage message) { return message.Ignored ? UI.Forums.IgnoredColour : UI.Forums.BodyColour; }
/// <summary> /// Toggle the read or unread state of the message referenced by the specified thread item. /// If the item is a root message and is collapsed then mark the whole thread instead. /// </summary> private void ToggleRead(CIXMessage message) { if (message != null && !message.ReadLocked) { if (IsCollapsed(message)) { if (message.Unread) { message.MarkThreadRead(); } else { message.MarkThreadUnread(); } } else { if (message.Unread) { message.MarkRead(); } else { message.MarkUnread(); } } } }
/// <summary> /// Show the currently selected message. /// </summary> private void ShowMessage(CIXMessage cixMessage) { tsvMessagePane.BeginUpdate(); tsvMessagePane.Items.Clear(); if (cixMessage != null && (!cixMessage.Ignored || _showIgnored)) { MessageItem messageItem = MessageItemFromMessage(cixMessage, false); messageItem.Level = 0; messageItem.Full = true; messageItem.IsExpandable = true; messageItem.ShowFolder = !_isTopicFolder || _isFiltering; messageItem.Image = Mugshot.MugshotForUser(cixMessage.Author, true).RealImage; messageItem.Font = UI.Forums.MessageFont; messageItem.ShowTooltips = true; messageItem.ItemColour = tsvMessagePane.BackColor; messageItem.ForeColor = BodyColourForItem(cixMessage); if (tsvMessagePane.Items.Count == 1) { return; } tsvMessagePane.Items.Insert(0, messageItem); } tsvMessagePane.AutoScrollPosition = new Point(0, 0); tsvMessagePane.EndUpdate(null); if (cixMessage != null) { _currentFolder.RecentMessage = cixMessage.RemoteID; } }
/// <summary> /// Select the current root message or the next root message. /// </summary> private void SelectRoot(CIXMessage message, RootToSelect value) { if (message != null) { switch (value) { case RootToSelect.PreviousRoot: case RootToSelect.NextRoot: { int direction = (value == RootToSelect.NextRoot) ? 1 : -1; int indexOfMessage = _messages.IndexOf(message) + direction; while (indexOfMessage >= 0 && indexOfMessage < _messages.Count) { message = _messages[indexOfMessage]; if (message.IsRoot) { SelectedRow = indexOfMessage; return; } indexOfMessage += direction; } break; } } } }
/// <summary> /// Withdraw the selected message on the server and replace it with a withdrawn message. /// </summary> /// <param name="message">The message to be withdrawn</param> private static void WithdrawMessage(CIXMessage message) { if (message != null) { if (message.IsPseudo) { DeleteMessage(message); return; } if (MessageBox.Show(Resources.ConfirmWithdraw, Resources.Confirm, MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk) == DialogResult.Yes) { message.Withdraw(); } } }
/// <summary> /// Remove the specified message from the list. /// </summary> /// <param name="message">The message to remove</param> private void RemoveMessage(CIXMessage message) { int selectedRow = SelectedRow; bool deletingCurrent = (selectedRow != -1) && message == _messages[selectedRow]; _messages.Remove(message); InitialiseList(); if (selectedRow == _messages.Count()) { --selectedRow; } RedrawAllItems(); if (deletingCurrent) { if (selectedRow < 0) { ShowEmptyMessage(); } else { SelectedRow = selectedRow; ShowMessage(SelectedMessage); } } }
/// <summary> /// Action the specified ID with the given message item. /// </summary> /// <param name="id">An action ID</param> /// <param name="message">A selected message, or null</param> private void Action(ActionID id, CIXMessage message) { switch (id) { case ActionID.Chat: Chat(message); break; case ActionID.Block: if (message != null) { string titleString = string.Format(Resources.BlockTitle, message.Author); string promptString = string.Format(Resources.BlockPrompt, message.Author); if (MessageBox.Show(promptString, titleString, MessageBoxButtons.YesNo) == DialogResult.Yes) { CIX.RuleCollection.Block(message.Author); OnMessageChanged(message); } } break; case ActionID.Participants: if (_currentFolder.ID > 0) { TopicFolder topic = (TopicFolder) _currentFolder; Folder forum = topic.Folder.ParentFolder; Participants parDialog = new Participants(FoldersTree.MainForm, forum.Name); parDialog.ShowDialog(); } break; case ActionID.ManageForum: if (_currentFolder.ID > 0) { TopicFolder topicFolder = (TopicFolder) _currentFolder; Folder forumFolder = topicFolder.Folder.ParentFolder; DirForum forum = CIX.DirectoryCollection.ForumByName(forumFolder.Name); FoldersTree.ManageForum(forum); } break; case ActionID.Email: Email(message); break; case ActionID.Profile: case ActionID.AuthorImage: FoldersTree.MainForm.Address = string.Format("cixuser:{0}", message.Author); break; case ActionID.Priority: PriorityThread(message); break; case ActionID.Ignore: IgnoreThread(message); break; case ActionID.ReadLock: ToggleReadLock(message); break; case ActionID.Quote: if (message != null) { Comment(message, message.Body.Quoted()); } break; case ActionID.Edit: case ActionID.Reply: if (message != null) { if (message.Topic.Flags.HasFlag(FolderFlags.OwnerCommentsOnly) && !message.IsMine) { Action(ActionID.ReplyByMail); } else { Comment(message, null); } } break; case ActionID.ReplyByMail: { InboxMessageEditor newMessageWnd = new InboxMessageEditor(message); newMessageWnd.Show(); } break; case ActionID.Print: Print(message); break; case ActionID.Read: ToggleRead(message); break; case ActionID.Star: ToggleStar(message); break; case ActionID.Withdraw: WithdrawMessage(message); break; case ActionID.Delete: DeleteMessage(message); break; case ActionID.NextUnread: GoToNextUnread(message); break; case ActionID.NextPriorityUnread: GoToNextPriorityUnread(message); break; case ActionID.GoToSource: GoToSourceThread(message); break; case ActionID.Link: CopyLinkToClipboard(message); break; case ActionID.PageMessage: if (tsvMessagePane.Items.Count > 0) { MessageItem messageItem = tsvMessagePane.Items[0] as MessageItem; if (messageItem != null) { if (ScrollMessageUp()) { break; } if (messageItem.Message.Unread) { MarkAsRead(messageItem.Message); } FoldersTree.NextUnread(FolderOptions.NextUnread); // Put focus back on thread ActiveControl = tsvMessages; } } break; case ActionID.GoTo: if (CanAction(ActionID.GoTo)) { GoToMessageDialog(); } break; case ActionID.MarkThreadRead: MarkThreadRead(message); FoldersTree.NextUnread(FolderOptions.NextUnread); break; case ActionID.MarkThreadReadThenRoot: MarkThreadRead(message); FoldersTree.NextUnread(FolderOptions.NextUnread | FolderOptions.Root); break; case ActionID.MarkTopicRead: MarkTopicRead(); FoldersTree.NextUnread(FolderOptions.NextUnread); break; case ActionID.NextRoot: SelectRoot(message, RootToSelect.NextRoot); break; case ActionID.Root: SelectRoot(message, RootToSelect.PreviousRoot); break; case ActionID.Original: SelectOriginal(message); break; case ActionID.NewMessage: NewMessage(string.Empty); break; case ActionID.SelectAll: SelectAll(); break; case ActionID.Expand: ExpandCollapseThread(message); break; case ActionID.Copy: CopySelection(); break; } }
/// <summary> /// Returns whether the folder can contain the specified message. /// </summary> public virtual bool CanContain(CIXMessage message) { return true; }
/// <summary> /// Return true if the message is a draft. /// </summary> public override bool CanContain(CIXMessage message) { return Comparator(message); }
/// <summary> /// Initialises a new instance of the <see cref="InboxMessageEditor"/> class and /// populates it with data from the specified CIX message. /// </summary> public InboxMessageEditor(CIXMessage message) { InitializeComponent(); _messageToUse = message; }
/// <summary> /// Toggle ignore the current message and all subsequent replies. /// </summary> /// <param name="item">The root of the thread to ignore</param> private static void IgnoreThread(CIXMessage item) { if (item != null) { if (item.Ignored) { item.RemoveIgnore(); } else { item.SetIgnore(); } } }
/// <summary> /// Put the selection back on the specified message. /// </summary> /// <param name="message">The message to select</param> private void RestoreSelection(CIXMessage message) { if (_messages.Count == 0) { ShowEmptyMessage(); } else if (message != null) { int row = _messages.IndexOf(message); if (row == -1 && IsCollapsed(message.Root)) { ExpandThread(message.Root); row = _messages.IndexOf(message); } _hasMouseDown = true; SelectedRow = row; } }
/// <summary> /// Handle a drop on the node. Two types of drops are handled depending on /// the type of the data in e.Data: /// /// If the item is a ListViewItem then it is assumed to be a drag from the /// thread list to another topic. In this case, create a new message in the /// target topic which indicates that it is a copy (the user can edit the /// "***COPIED FROM" header out anyway. /// /// If the item is a TreeNode then it is assumed to be a drag and drop /// re-arrangement of the nodes in the tree. /// </summary> private void frmList_DragDrop(object sender, DragEventArgs e) { Point pt = ((TreeView)sender).PointToClient(new Point(e.X, e.Y)); TreeNode targetNode = ((TreeView)sender).GetNodeAt(pt); if (targetNode != null) { if (e.Data.GetDataPresent(typeof (ListViewItem))) { ListViewItem item = e.Data.GetData(typeof (ListViewItem)) as ListViewItem; if (item != null) { if (targetNode == _nodeAtDragPoint) { _nodeAtDragPoint = null; frmList.InvalidateNode(targetNode); } FolderBase folderBase = (FolderBase)targetNode.Tag; if (folderBase.ID > 0) { string body = BodyFromDropSource(item.Tag); TopicFolder topicFolder = (TopicFolder)folderBase; CIXMessage message = new CIXMessage { Author = CIX.Username, RemoteID = 0, Priority = true, Date = DateTime.UtcNow.UTCToGMTBST(), Body = body, TopicID = topicFolder.ID, RootID = 0, CommentID = 0 }; CIXMessageEditor editor = MessageEditorCollection.Get(message) ?? new CIXMessageEditor(message, false); editor.Show(); editor.BringToFront(); } } return; } if (e.Data.GetDataPresent("System.Windows.Forms.TreeNode", false)) { TreeNode newNode = (TreeNode)e.Data.GetData("System.Windows.Forms.TreeNode"); _nodeAtDragPoint = null; if (targetNode == _nodeBeingDragged) { frmList.InvalidateNode(targetNode); } else { TreeNodeCollection parentNodes; int insertIndex; if (targetNode.Parent == _nodeBeingDragged.Parent) { parentNodes = (targetNode.Parent != null) ? targetNode.Parent.Nodes : _forumsTree.Nodes; insertIndex = parentNodes.IndexOf(targetNode) + 1; } else { parentNodes = targetNode.Nodes; insertIndex = 0; } TreeNode insertedNode = (TreeNode) newNode.Clone(); parentNodes.Insert(insertIndex, insertedNode); SelectFolder(insertedNode, FolderOptions.None); newNode.Remove(); // Fix up the indexes for all subsequent items FixupNodeIndexes(_forumsTree.Nodes); } } } }
/// <summary> /// Mark the message referenced by the specified thread item as read. /// </summary> private static void MarkAsRead(CIXMessage message) { if (message != null && message.Unread && !message.ReadLocked) { message.MarkRead(); } }
public void TestForumsFastSync() { string databasePath = Path.GetTempFileName(); CIX.Init(databasePath); Folder forumFolder = new Folder { ParentID = -1, Name = "cix.beta", Unread = 0, UnreadPriority = 0 }; CIX.FolderCollection.Add(forumFolder); Folder topicFolder = new Folder { Name = "cixreader", ParentID = forumFolder.ID, Unread = 0, UnreadPriority = 0 }; CIX.FolderCollection.Add(topicFolder); // Seed message as fast sync doesn't work on empty topics CIXMessage seedMessage = new CIXMessage { RemoteID = 1, Author = "CIX", Body = "Seed message", CommentID = 0 }; topicFolder.Messages.Add(seedMessage); string userSyncData = Resource1.UserSyncData; DateTime sinceDate = default(DateTime); FolderCollection.AddMessages(Utilities.GenerateStreamFromString(userSyncData), ref sinceDate, true, false); // On completion, verify that there are no duplicates in the topic List<CIXMessage> messages = topicFolder.Messages.OrderBy(fld => fld.RemoteID).ToList(); int lastMessageID = -1; foreach (CIXMessage cixMessage in messages) { Assert.AreNotEqual(cixMessage.RemoteID, 0); Assert.AreNotEqual(cixMessage.RemoteID, lastMessageID); Assert.AreNotEqual(cixMessage.ID, 0); lastMessageID = cixMessage.RemoteID; } // Verify that specific messages are marked read CIXMessage message = topicFolder.Messages.MessageByID(2323); Assert.IsNotNull(message); Assert.IsFalse(message.Unread); // Verify that specific messages are marked unread message = topicFolder.Messages.MessageByID(2333); Assert.IsNotNull(message); Assert.IsTrue(message.Unread); // Verify the total unread on the folder and globally Assert.AreEqual(topicFolder.Unread, 1); Assert.AreEqual(topicFolder.UnreadPriority, 6); Assert.AreEqual(CIX.FolderCollection.TotalUnread, 1); Assert.AreEqual(CIX.FolderCollection.TotalUnreadPriority, 6); }
/// <summary> /// Mark the entire thread, of which the specified message is part, as /// read. /// </summary> private static void MarkThreadRead(CIXMessage message) { if (message != null) { message.MarkThreadRead(); } }
/// <summary> /// Expands recognised tags in theString based on the object values. /// </summary> private string ExpandTags(string theString, CIXMessage message, bool cond) { bool hasOneTag = false; int tagStartIndex = 0; while ((tagStartIndex = theString.IndexOf('$', tagStartIndex)) >= 0) { int tagEndIndex = theString.IndexOf('$', tagStartIndex + 1); if (tagEndIndex < 0) { break; } int tagLength = (tagEndIndex - tagStartIndex) + 1; string tagName = theString.Substring(tagStartIndex + 1, tagLength - 2); string tagSelName = string.Format("tag{0}", tagName); Type messageType = typeof(TaggedMessage); MethodInfo method = messageType.GetMethod(tagSelName); string replacementString = (string) method.Invoke(message, new object[]{ message, this }); if (replacementString == null) { theString = theString.Substring(0, tagStartIndex) + theString.Substring(tagEndIndex + 1); } else { theString = theString.Substring(0, tagStartIndex) + replacementString + theString.Substring(tagEndIndex + 1); hasOneTag = true; if (!string.IsNullOrEmpty(replacementString)) { cond = false; } tagStartIndex += replacementString.Length; } } return (cond && hasOneTag) ? string.Empty : theString; }
/// <summary> /// Toggle as priority the current message and all subsequent replies. /// </summary> /// <param name="item">The root of the thread to toggle priority</param> private static void PriorityThread(CIXMessage item) { if (item != null) { if (item.Priority) { item.ClearPriority(); } else { item.SetPriority(); } } }