private void setDiffContextText(Control diffContextControl) { double fontSizePx = WinFormsHelpers.GetFontSizeInPixels(diffContextControl); DiscussionNote note = getNoteFromControl(diffContextControl); Debug.Assert(note.Type == "DiffNote"); DiffPosition position = PositionConverter.Convert(note.Position); Debug.Assert(diffContextControl is HtmlPanel); HtmlPanel htmlPanel = diffContextControl as HtmlPanel; // We need to zero the control size before SetText call to allow HtmlPanel to compute the size int prevWidth = htmlPanel.Width; htmlPanel.Width = 0; htmlPanel.Height = 0; string html = getContext(_panelContextMaker, position, _diffContextDepth, fontSizePx, out string css); htmlPanel.BaseStylesheet = css; htmlPanel.Text = html; resizeLimitedWidthHtmlPanel(htmlPanel, prevWidth); string tooltipHtml = getContext(_tooltipContextMaker, position, _tooltipContextDepth, fontSizePx, out string tooltipCSS); _htmlDiffContextToolTip.BaseStylesheet = String.Format("{0} .htmltooltip {{ padding: 1px; }}", tooltipCSS); _htmlDiffContextToolTip.SetToolTip(htmlPanel, tooltipHtml); }
private void setServiceDiscussionNoteText(Control noteControl, DiscussionNote note) { if (note == null) { Debug.Assert(false); return; } // We need to zero the control size before SetText call to allow HtmlPanel to compute the size noteControl.Width = 0; noteControl.Height = 0; Debug.Assert(noteControl is HtmlPanel); HtmlPanel htmlPanel = noteControl as HtmlPanel; htmlPanel.BaseStylesheet = String.Format("{0} body div {{ font-size: {1}px; }}", Properties.Resources.Common_CSS, WinFormsHelpers.GetFontSizeInPixels(noteControl)); string body = MarkDownUtils.ConvertToHtml(note.Body, _imagePath, _specialDiscussionNoteMarkdownPipeline); noteControl.Text = String.Format(MarkDownUtils.HtmlPageTemplate, body); resizeFullSizeHtmlPanel(noteControl as HtmlPanel); }
internal void setDiscussionNoteText(Control noteControl, DiscussionNote note) { if (note == null) { // this is possible when noteControl detaches from parent return; } Debug.Assert(noteControl is HtmlPanel); HtmlPanel htmlPanel = noteControl as HtmlPanel; htmlPanel.BaseStylesheet = String.Format( "{0} body div {{ font-size: {1}px; padding-left: 4px; padding-right: {2}px; }}", Properties.Resources.Common_CSS, WinFormsHelpers.GetFontSizeInPixels(noteControl), SystemInformation.VerticalScrollBarWidth * 2); // this is really weird // We need to zero the control size before SetText call to allow HtmlPanel to compute the size int prevWidth = noteControl.Width; noteControl.Width = 0; noteControl.Height = 0; string body = MarkDownUtils.ConvertToHtml(note.Body, _imagePath, _specialDiscussionNoteMarkdownPipeline); noteControl.Text = String.Format(MarkDownUtils.HtmlPageTemplate, addPrefix(body, note, _firstNoteAuthor)); resizeLimitedWidthHtmlPanel(htmlPanel, prevWidth); _htmlDiscussionNoteToolTip.BaseStylesheet = String.Format("{0} body div {{ font-size: {1}px; }}", Properties.Resources.Common_CSS, WinFormsHelpers.GetFontSizeInPixels(noteControl)); _htmlDiscussionNoteToolTip.SetToolTip(noteControl, getNoteTooltipHtml(note)); }
private Color getNoteColor(DiscussionNote note) { Color defaultColor = Color.White; if (isServiceDiscussionNote(note)) { return(_colorScheme.GetColorOrDefault("Discussions_ServiceMessages", _colorScheme.GetColorOrDefault("Discussions_Comments", defaultColor))); } if (note.Resolvable) { if (note.Author.Id == _mergeRequestAuthor.Id) { return(note.Resolved ? _colorScheme.GetColorOrDefault("Discussions_Author_Notes_Resolved", defaultColor) : _colorScheme.GetColorOrDefault("Discussions_Author_Notes_Unresolved", defaultColor)); } else { return(note.Resolved ? _colorScheme.GetColorOrDefault("Discussions_NonAuthor_Notes_Resolved", defaultColor) : _colorScheme.GetColorOrDefault("Discussions_NonAuthor_Notes_Unresolved", defaultColor)); } } else { return(_colorScheme.GetColorOrDefault("Discussions_Comments", defaultColor)); } }
async private Task onEditDiscussionNoteAsync(Control noteControl) { DiscussionNote note = getNoteFromControl(noteControl); if (note == null || !canBeModified(note)) { return; } string currentBody = StringUtils.ConvertNewlineUnixToWindows(note.Body); DiscussionNoteEditPanel actions = new DiscussionNoteEditPanel(); using (TextEditForm form = new TextEditForm("Edit Discussion Note", currentBody, true, true, actions)) { Point locationAtScreen = noteControl.PointToScreen(new Point(0, 0)); form.StartPosition = FormStartPosition.Manual; form.Location = locationAtScreen; actions.SetTextbox(form.TextBox); if (form.ShowDialog() == DialogResult.OK) { if (form.Body.Length == 0) { MessageBox.Show("Note text cannot be empty", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); return; } string proposedBody = StringUtils.ConvertNewlineWindowsToUnix(form.Body); await submitNewBodyAsync(noteControl, proposedBody); } } }
async private Task onDeleteNoteAsync(DiscussionNote note) { if (note == null || !canBeModified(note)) { return; } disableAllNoteControls(); Discussion discussion = null; try { await _editor.DeleteNoteAsync(note.Id); } catch (DiscussionEditorException ex) { string message = "Cannot delete a note"; ExceptionHandlers.Handle(message, ex); MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); discussion = Discussion; } await refreshDiscussion(discussion); }
private HtmlPanel createDiffContext(DiscussionNote firstNote) { if (firstNote.Type != "DiffNote") { return(null); } int fontSizePx = 12; int rowsVPaddingPx = 2; int rowHeight = (fontSizePx + rowsVPaddingPx * 2 + 1 /* border of control */ + 2); // we're adding 2 extra pixels for each row because HtmlRenderer does not support CSS line-height property // this value was found experimentally int panelHeight = (_diffContextDepth.Size + 1) * rowHeight; HtmlPanel htmlPanel = new HtmlPanel { BorderStyle = BorderStyle.FixedSingle, Height = panelHeight, MinimumSize = new Size(600, 0), TabStop = false }; DiffPosition position = convertToDiffPosition(firstNote.Position); htmlPanel.Text = getContext(_panelContextMaker, position, _diffContextDepth, fontSizePx, rowsVPaddingPx); _htmlToolTip.SetToolTip(htmlPanel, getContext(_tooltipContextMaker, position, _tooltipContextDepth, fontSizePx, rowsVPaddingPx)); return(htmlPanel); }
async private Task onToggleResolveNoteAsync(DiscussionNote note) { if (note == null) { return; } disableAllNoteControls(); Discussion discussion = null; try { bool wasResolved = note.Resolved; await _editor.ResolveNoteAsync(note.Id, !wasResolved); } catch (DiscussionEditorException ex) { string message = "Cannot toggle 'Resolved' state of a note"; ExceptionHandlers.Handle(message, ex); MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); discussion = Discussion; } await refreshDiscussion(discussion); }
async private Task deleteMostRecentNote(NewDiscussionParameters parameters) { Debug.Assert(parameters.Position != null); Trace.TraceInformation("[DicsussionCreator] Looking up for a note with bad position..."); IEnumerable <Discussion> discussions; try { discussions = await _discussionOperator.GetDiscussionsAsync(_mergeRequestKey, null); } catch (OperatorException ex) { ExceptionHandlers.Handle("Cannot obtain discussions", ex); return; } int?deletedNoteId = new int?(); foreach (Discussion discussion in discussions.Reverse()) { if (discussion.Notes.Count() == 1) { DiscussionNote note = discussion.Notes.First(); if (_currentUser != null && note != null && note.Type == "DiffNote" && note.Author != null && note.Author.Id == _currentUser.Id && note.Body == parameters.Body) { Trace.TraceInformation( "[DicsussionCreator] Deleting discussion note." + " Id: {0}, Author.Username: {1}, Created_At: {2}, Body:\n{3}", note.Id.ToString(), note.Author.Username, note.Created_At, note.Body); try { await _discussionOperator.DeleteNoteAsync(_mergeRequestKey, note.Id); deletedNoteId = note.Id; } catch (OperatorException ex) { ExceptionHandlers.Handle("Cannot delete discussion note", ex); } break; } } } string message = deletedNoteId.HasValue ? String.Format("[DicsussionCreator] Deleted note with Id {0}", deletedNoteId.Value) : "Could not find a note to delete (or could not delete it)"; Trace.TraceInformation(message); }
private void onCancelEditNote(TextBox textBox) { textBox.ReadOnly = true; DiscussionNote note = (DiscussionNote)(textBox.Tag); textBox.Text = note.Body.Replace("\n", "\r\n"); }
private bool canBeModified(DiscussionNote note) { if (note == null) { return(false); } return(note.Author.Id == _currentUser.Id && (!note.Resolvable || !note.Resolved)); }
// Collect discussions started for lines within DiffContextDepth range near `position` private IEnumerable <ReportedDiscussionNote> getRelatedDiscussions( MergeRequestKey mrk, ReportedDiscussionNoteKey?keyOpt, DiffPosition position) { // Obtain a context for a passed position. DiffContext ctx = getDiffContext <CombinedContextMaker>(position, UnchangedLinePolicy.TakeFromRight); if (!ctx.IsValid()) { return(Array.Empty <ReportedDiscussionNote>()); } string leftFileName = position.LeftPath; string rightFileName = position.RightPath; Core.Matching.DiffRefs refs = position.Refs; List <DiffPosition> neighborPositions = new List <DiffPosition>(); // CombinedContextMaker provides a context where line numbers are matched so no need to // match them manually here. foreach (DiffContext.Line line in ctx.Lines) { Debug.Assert(line.Left.HasValue || line.Right.HasValue); string leftLineNumber = null; string rightLineNumber = null; if (line.Left.HasValue) { leftLineNumber = line.Left.Value.Number.ToString(); } if (line.Right.HasValue) { rightLineNumber = line.Right.Value.Number.ToString(); } neighborPositions.Add(new DiffPosition(leftFileName, rightFileName, leftLineNumber, rightLineNumber, refs)); } // Find discussions that reported on each line from the diff context. List <ReportedDiscussionNote> relatedNotes = new List <ReportedDiscussionNote>(); foreach (Discussion discussion in _getDiscussions(mrk)) { DiscussionNote firstNote = discussion.Notes.First(); DiffPosition firstNotePosition = PositionConverter.Convert(firstNote.Position); if (firstNotePosition != null) { foreach (DiffPosition neighbor in neighborPositions) { if (doPositionsReferenceTheSameLine(neighbor, firstNotePosition) && (!keyOpt.HasValue || keyOpt.Value.Id != firstNote.Id)) { ReportedDiscussionNote note = new ReportedDiscussionNote(firstNote.Id, discussion.Id, firstNotePosition, firstNote.Body, firstNote.Author.Name, firstNote.Created_At); relatedNotes.Add(note); } } } } return(relatedNotes.GroupBy(note => note.Key.Id).Select(c => c.First())); }
private ContextMenu createContextMenuForDiscussionNote(DiscussionNote note, bool discussionResolved, TextBox textBox) { var contextMenu = new ContextMenu(); MenuItem menuItemToggleDiscussionResolve = new MenuItem { Tag = textBox, Text = (discussionResolved ? "Unresolve" : "Resolve") + " Discussion", Enabled = note.Resolvable }; menuItemToggleDiscussionResolve.Click += MenuItemToggleResolveDiscussion_Click; contextMenu.MenuItems.Add(menuItemToggleDiscussionResolve); MenuItem menuItemToggleResolve = new MenuItem { Tag = textBox, Text = (note.Resolvable && note.Resolved ? "Unresolve" : "Resolve") + " Note", Enabled = note.Resolvable }; menuItemToggleResolve.Click += MenuItemToggleResolveNote_Click; contextMenu.MenuItems.Add(menuItemToggleResolve); MenuItem menuItemDeleteNote = new MenuItem { Tag = textBox, Enabled = canBeModified(note), Text = "Delete Note" }; menuItemDeleteNote.Click += MenuItemDeleteNote_Click; contextMenu.MenuItems.Add(menuItemDeleteNote); MenuItem menuItemEditNote = new MenuItem { Tag = textBox, Enabled = canBeModified(note), Text = "Edit Note (F2)" }; menuItemEditNote.Click += MenuItemEditNote_Click; contextMenu.MenuItems.Add(menuItemEditNote); MenuItem menuItemReply = new MenuItem { Tag = textBox, Enabled = !Discussion.Individual_Note, Text = "Reply" }; menuItemReply.Click += MenuItemReply_Click; contextMenu.MenuItems.Add(menuItemReply); return(contextMenu); }
private string addPrefix(string body, DiscussionNote note, User firstNoteAuthor) { bool appendNoteAuthor = note.Author.Id != _currentUser.Id && note.Author.Id != firstNoteAuthor.Id; Debug.Assert(!appendNoteAuthor || !canBeModified(note)); string prefix = appendNoteAuthor ? String.Format("({0}) ", note.Author.Name) : String.Empty; return(prefix + body); }
private bool isServiceDiscussionNote(DiscussionNote note) { if (note == null) { Debug.Assert(false); return(false); } return(note.Author.Username == Program.ServiceManager.GetServiceMessageUsername()); }
private void enableNoteControl(Control noteControl, Color backColor, ContextMenu contextMenu, DiscussionNote note) { if (noteControl != null) { noteControl.BackColor = backColor; noteControl.ContextMenu = contextMenu; noteControl.Tag = note; } }
// Create a label that shows discussion author private Control createLabelAuthor(DiscussionNote firstNote) { Label labelAuthor = new Label { Text = firstNote.Author.Name, AutoEllipsis = true }; return(labelAuthor); }
private static string wrapTextFragmentInSpan(int startPosition, int length, DiscussionNote note) { string discussionText = note.Body; string prefix = "<span class=\"highlight\">"; string suffix = "</span>"; string newText = discussionText .Insert(startPosition, prefix) .Insert(startPosition + length + prefix.Length, suffix); return(newText); }
private string getNoteTooltipText(DiscussionNote note) { string result = string.Empty; if (note.Resolvable) { result += note.Resolved ? "Resolved." : "Not resolved."; } result += " Created by " + note.Author.Name + " at " + note.Created_At.ToLocalTime().ToString("g"); return(result); }
private DiscussionNote removeCodeBlocks(DiscussionNote note) { if (note == null) { return(null); } string oldBody = note.Body; string newBody = oldBody.Replace("`", "").Replace("~", ""); return(cloneNoteWithNewText(note, newBody)); }
public void ClearHighlight() { DiscussionNote note = getOriginalNote(); if (note == null) { return; } (Parent as DiscussionBox).setDiscussionNoteText(this, note); HighlightState = null; }
public void ClearHighlight() { DiscussionNote note = getOriginalNote(); if (note == null || HighlightState == null) { return; } // Unwrap a wrapped span (i.e. undo HihghlightFragment). // Don't reset HighlightState to remember a place where highlighting was located in order to continue search. (Parent as DiscussionBox).setDiscussionNoteText(this, note); }
private string getNoteTooltipHtml(DiscussionNote note) { System.Text.StringBuilder result = new System.Text.StringBuilder(); if (note.Resolvable) { string text = note.Resolved ? "Resolved." : "Not resolved."; string color = note.Resolved ? "green" : "red"; result.AppendFormat("<i style=\"color: {0}\">{1} </i>", color, text); } result.AppendFormat("Created by <b> {0} </b> at <span style=\"color: blue\">{1}</span>", note.Author.Name, note.Created_At.ToLocalTime().ToString(Constants.TimeStampFormat)); result.AppendFormat("<br><br>Use context menu to view note as <b>plain text</b>."); return(result.ToString()); }
private bool isDiscussionResolved() { bool result = true; foreach (Control textBox in _textboxesNotes) { DiscussionNote note = (DiscussionNote)(textBox.Tag); if (note.Resolvable && !note.Resolved) { result = false; } } return(result); }
private ContextMenu createContextMenuForFilename(DiscussionNote firstNote, TextBox textBox) { ContextMenu contextMenu = new ContextMenu(); MenuItem menuItemToggleDiscussionResolve = new MenuItem { Tag = textBox, Text = "Resolve/Unresolve Thread", Enabled = isDiscussionResolvable() }; menuItemToggleDiscussionResolve.Click += MenuItemToggleResolveDiscussion_Click; contextMenu.MenuItems.Add(menuItemToggleDiscussionResolve); contextMenu.MenuItems.Add("-"); MenuItem menuItemReply = new MenuItem { Tag = textBox, Enabled = true, Text = "Reply" }; menuItemReply.Click += MenuItemReply_Click; contextMenu.MenuItems.Add(menuItemReply); MenuItem menuItemReplyAndResolve = new MenuItem { Tag = textBox, Enabled = true, Text = "Reply and Resolve/Unresolve Thread", Shortcut = Shortcut.F4 }; menuItemReplyAndResolve.Click += MenuItemReplyAndResolve_Click; contextMenu.MenuItems.Add(menuItemReplyAndResolve); MenuItem menuItemReplyDone = new MenuItem { Tag = textBox, Enabled = isDiscussionResolvable(), Text = "Reply \"Done\" and Resolve/Unresolve Thread", Shortcut = Shortcut.ShiftF4 }; menuItemReplyDone.Click += MenuItemReplyDone_Click; contextMenu.MenuItems.Add(menuItemReplyDone); return(contextMenu); }
public void HighlightFragment(int startPosition, int length) { DiscussionNote note = removeCodeBlocks(getOriginalNote()); if (note == null) { return; } string span = wrapTextFragmentInSpan(startPosition, length, note); DiscussionNote updatedNote = cloneNoteWithNewText(getOriginalNote(), span); (Parent as DiscussionBox).setDiscussionNoteText(this, updatedNote); HighlightState = new HighlightState(startPosition, length); }
private IEnumerable <ReportedDiscussionNote> getReportedDiscussions(MergeRequestKey mrk) { DiscussionFilter discussionFilter = new DiscussionFilter( _currentUser, null, DiscussionFilterState.CurrentUserOnly); return(_getDiscussions(mrk) .Where(discussion => discussionFilter.DoesMatchFilter(discussion)) .Where(discussion => !discussion.Notes.First().System) .Select(discussion => { DiscussionNote firstNote = discussion.Notes.First(); Core.Matching.DiffPosition firstNotePosition = PositionConverter.Convert(firstNote.Position); return new ReportedDiscussionNote(firstNote.Id, discussion.Id, firstNotePosition, firstNote.Body, firstNote.Author.Name, firstNote.Created_At); })); }
async private Task onDeleteNoteAsync(TextBox textBox) { DiscussionNote note = (DiscussionNote)(textBox.Tag); try { await _editor.DeleteNoteAsync(note.Id); } catch (DiscussionEditorException) { MessageBox.Show("Cannot delete a note", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } await refreshDiscussion(); }
async private void MenuItemToggleResolveNote_Click(object sender, EventArgs e) { MenuItem menuItem = (MenuItem)(sender); HtmlPanel htmlPanel = (HtmlPanel)(menuItem.Tag); if (htmlPanel?.Parent?.Parent == null) { return; } DiscussionNote note = getNoteFromControl(htmlPanel); Debug.Assert(note == null || note.Resolvable); await onToggleResolveNoteAsync(note); }
private void resizeBoxContent(int width) { if (_textboxesNotes != null) { foreach (Control noteControl in _textboxesNotes) { HtmlPanel htmlPanel = noteControl as HtmlPanel; DiscussionNote note = getNoteFromControl(noteControl); if (note != null && !isServiceDiscussionNote(note)) { resizeLimitedWidthHtmlPanel(htmlPanel, width * NotesWidth / 100); } else { resizeFullSizeHtmlPanel(htmlPanel); } } } int realLabelAuthorPercents = Convert.ToInt32( LabelAuthorWidth * ((Parent as CustomFontForm).CurrentFontMultiplier * LabelAuthorWidthMultiplier)); if (_labelAuthor != null) { _labelAuthor.Width = width * realLabelAuthorPercents / 100; } if (_textboxFilename != null) { _textboxFilename.Width = width * LabelFilenameWidth / 100; _textboxFilename.Height = (_textboxFilename as TextBoxEx).FullPreferredHeight; } int remainingPercents = 100 - HorzMarginWidth - realLabelAuthorPercents - HorzMarginWidth - NotesWidth - HorzMarginWidth - HorzMarginWidth - HorzMarginWidth; if (_panelContext != null) { resizeLimitedWidthHtmlPanel(_panelContext as HtmlPanel, width * remainingPercents / 100); _htmlDiffContextToolTip.MaximumSize = new Size(_panelContext.Width, 0 /* auto-height */); } }