Monday, February 11, 2013

Selection Paste Enhancements

When pasting the selection, the cursor position should be changed to the location where the mouse is pointing when the middle mouse button is released not where the current text cursor is located.  The event received in the mouse release event handler contains the position of the mouse relative to the widget (in this case the edit box).

The mouse position is passed to the cursorForPosition() function of the QPlainTextEdit base class to obtain a text cursor for the this position.  The edit box text cursor is then set to this text cursor to move the cursor, so when the selection is pasted, the text will be inserted to where the mouse was pointing.

It is also nice to be able to paste the current selection using a key command.  The Control+Shift+Insert key sequence was chosen.  Shift+Insert is the key sequence is used to paste the contents of the clipboard.  A case was added to the key press event handler key code switch for the Insert key, which checks for the Control and Shift key modifiers.

Since middle mouse button and Control+Shift+Insert needed to do the same thing, a paste selection function was implemented from parts of the mouse release event handler, which gets the clipboard for the application, checks if the selection is supported, sets the text cursor position to the mouse location, gets the text for the selection and inserts the text.  The function returns true if the selection was pasted, and false if the selection is not supported.

Since the position is only set with the middle mouse button, the paste selection function contains a position argument, which defaults to a null point.  The text cursor is only set to this position when the position is not a null point.  Only the mouse release event handler passes the position of the mouse (from the event).

It was also noticed that the backspace key code check in the key press event handler was in the default section of the switch statement that handled the key sequences.  The backspace check was removed and a backspace key code case was added to the switch.

Next Work: Handling the pasting of text when there is current selection text in the edit box, where the inserted text will replace the selection.  This handling will need to detect how many if any lines needed to be reported as being deleted, before reporting the changed and inserted lines from the paste.

[commit addb4fd6eb] [commit 6af5c59191]

Sunday, February 10, 2013

Pasting Selection With Middle-Click

There is another type of paste operation that is supported on Linux, specifically by the X11 window system used by Linux and MAC-OS.  The selection is made using the mouse by clicking at the start of the text and dragging to the end of the text.  This selection can then be pasted into any window using the middle mouse button (the button under the wheel on most modern mice).  Windows does not support this feature.

This paste operation is intercepted by reimplementing the mouse release event handler.  If the event has the middle mouse button, the clipboard is checked to see if it supports this type of selection.  If it does, the text() function is again used to obtain the text of the selection, however, instead of using the default argument (nothing), the QClipboard::Selection argument is used to get this selection text instead of the normal clipboard paste buffer text.  Otherwise, the mouse release event is passed to the mouse release event handler of the QPlainTextEdit base class.

There is one issue with this paste operation however.  The text is pasted at the current location of the text cursor, not where the mouse cursor is actually pointing to at the time of the middle button click.  This will be the subject of next post.

[commit 9d1c0780e8]

Changed Lines From Pasting

In order to detect lines that may be modified of inserted text from pasting, the text that is about to be inserted from the clipboard needs to be intercepted and examined to see what is about to be inserted.  To catch the paste action, the paste function was reimplemented in the EditBox class.  The paste key sequence also needed to be intercepted, so the key press event handler was modified to also call this new paste function for the paste key sequence.

In the new paste function, a pointer to the clipboard is obtained from the static QApplication::clipboard() function.  The plain text contents of the clipboard is obtained by calling text() function.  Since the existing function for inserting a new line contained a subset of the code needed for inserting text which may contain new lines.  So this function was modified to handle more than just inserting a single new line.  This new function is called for the return key with a string containing a single new line character.

The insert text function handles determining whether the current line (before inserting the text) needs to be reported as changed, if any new lines need to be reported as inserted, and what to set the modified status of the cursor line once the text has been inserted.  Right now this function only handles a paste when there is no text selected.

[commit 8b46b474f1]

Saturday, February 9, 2013

Another Backspace Issue

While testing the line detection code when pasting, another backspace issue was discovered.  If the current line was modified, and the backspace is used at the beginning of the line to combine is with the previous line that is blank, it was not preserving the modified status of the line (the modified line was being reset always).

To  correct this, if the previous line is blank and the current line is modified, then the modified line variable is decremented to reflect the line number of the current line after the backspace (combine) operation is performed.  Only if the previous line is not blank is the modified line reset.

Since the backspace functionality is getting rather large, it was moved into its own function.  It was also noticed that the check for if the current line is blank (which sets the ignore change flag since the previous line won't actually be modified), was in both parts of the if statement, these lines were moved outside the if statement.

[commit cd7200da0e] [commit ee9e2ee6a4]

Thursday, February 7, 2013

Minor Backspace Issue

The backspace was not completely debugged.  When backspacing at the beginning of the line, and the line was not reported yet because it was a new line (insert modification type), the modification type was suppose to be reset back to line changed type (see the Two Issues post from a few days ago).  However, the equality operator was used instead of the assignment operator, so it was actually resetting it.

[commit 13443cd8ce]

Wednesday, February 6, 2013

Line Changes and Blank Lines

The debugging of detecting line changes when pasting continues.  There are a number of scenarios that need to be handled and tested.  First is the location of where the insertion occurs, which includes at the beginning of a line, in the middle of a line, at the end of the line, and on a blank line (at both the beginning and end of the line).  Second is the contents of the text being pasted, which could include a new line at the beginning of the text, a new line at the end of the text and a number of new lines in the text.

While testing these scenarios, a number of issues were discovered when using the delete and backspace key commands when at a blank line including when there is a blank line on the next line (delete at the end of the line) and on the previous line (backspace at the beginning of the line).

In the case of a backspace at the beginning of a line when the previous line is blank, the current line was being reported as deleted, and the previous line was being set as modified (reported as changed when the cursor leaves the line).  However, if the current line hasn't been modified, the previous (blank) line only should be reported as deleted.  If the current line is modified, again the previous line should be reported as deleted and the current line should remain as modified.

In the case of a delete at the end of the line when the next line is blank, the next line was reported as being deleted and the current line was being set as modified.  However, if the current line hasn't been modified and the next line is blank, nothing was actually modified, so it should not have been set as modified.

[commit a3fbc19266]

Sunday, February 3, 2013

Two Issues (Connection/Backspace)

While testing the detection of inserted lines when pasting, two issues were discovered.  The first was a minor problem where a "connect: no such slot" warning was reported by Qt with the slot that catches the document block (line) count change signal.  When this slot function was copied from the Code Editor Example in the documentation, the argument for the number of blocks was removed since it wasn't being used.  However, the argument type in the connect call was not changed causing the warning.  The argument type was removed from the connect call (it is acceptable to connect a signal with arguments to a slot with no arguments).

The other issue was when using backspace at the beginning of the line, which combines the current line with the previous line.  If the current line was marked as modified with a line inserted type (because it is a new line that hasn't been reported yet), then the backspace would report that this line is deleted.  However, no line was actually inserted yet, so the next line would have been incorrectly deleted.

To correct this error, backspace now checks for the line inserted modification type and clears the line modified variable and sets the modification type back to line changed instead of emitting that the line was deleted.

One additional issue with this change is that if the current line being combined is blank, then the previous line is not actually modified, so the modified line variable should not be set.  To prevent it from getting set, the ignore change flag is set, but then another issue was found.

For some reason, for the delete and backspace key commands, the document change signal is sent twice, which defeated the ignore change mechanism because the document change slot cleared this flag when set.  Therefore, instead of clearing this flag in this slot, it is now the setter of this flag's responsibility to clear the flag after the call is made that will trigger the document change signal (the undo and redo functions were already doing this).

[commit 4a2e5f51a7] [commit aa906ee7f4]

Saturday, February 2, 2013

Deleted Lines From Selections (Actions)

So far, when deleting the selected text, the proper lines are reported as being deleted when the selection is deleted be a key command (delete or backspace) or replaced by typing a character.  The cut and delete actions (from the Edit menu or tool bar) also need to be handled.

The cut() function from the QPlainTextEdit class was reimplemented in the EditBox class and a call to the QplainTextEdit::cut() function was added.  Before this call, the current selection is saved.  After the call, the function implemented for key press deletion of selections that detects deleted lines is called.

The remove() function was already implemented to delete the selected text (not to the clipboard).  Similar to the new cut() function, the selection is saved before the text is deleted and  the deleted lines are captured afterward.

[commit 5c3e16f9e8]

Deleted Lines From Selections (Keys)

There are quite a few situations to handle with detecting changed, deleted or inserted lines when selection text is involved.  When there is selected text, it needs to determined if the selection is on one line or several lines.  If text is being pastes on top of a selection (replacing it), it needs to determined if the pasted text is one line or several lines. Each of these situations will be handled one at a time to make this more manageable.

The first situation to be handled is when text has been selected and either a character is typed (the selected text is replaced with the character), or a delete command key is entered (delete or backspace).  After the key has been entered, if there was a selection before the key is processed, it needs to determine how many lines may have been deleted and report these lines.  The line the cursor is left on is set as the modified line, which will be reported when the cursor leaves the line.

To capture the selection before the key is processed (it won't be there after the key is processed), the particulars of the selection need to be saved, namely, whether there was a selection, and the starting and ending line of the selection.  Because of implicit sharing, which the QTextCursor class supports, a copy of it before processing the key can not be made because the copy will be updated when the key is processed (the selection goes away).

To simplify the process of saving the selection particulars, a new Selection class was implemented with members for the start and end positions and the start and end line numbers of the selection.  Because the document is changed when the key is processed, the start and end positions can't be used afterward except to check if there was a selection (the positions are not the same).  This class has functions to set these variables from the text cursor (called before a key is processed), check if the selection is empty, get the number of lines in the selection (ending line minus starting line plus one), and accessors for the starting and ending line numbers.

The code needs to know if something was actually deleted by the key command before checking if lines were deleted (the command may have just moved the selection).  There is a signal from the document like the general document changed signal, but includes the position of the change along with the number of characters removed and added.  This signal was connected to a new slot function to capture and store the number of characters removed and added.

A new function was implemented to capture any lines that may have been deleted when there was a selection.  This function checks if there was a selection before the operation, and there were characters removed.  If the selection was more than one line, then the lines starting with the selection's starting line for the number of lines minus one are reported as being deleted.  The number of characters removed and added are reset to zero so further operations don't erroneously report more lines being deleted.

If the selection, was on one line, no lines need to be reported.  The current line modified is already being set when the document changed signal was received, so only lines below the cursor line need to be reported as being deleted.  When the cursor leaves this line, the line will be reported as being changed.

[commit 1c0c27c744]

Edit Box – Line Numbers

One of the things making the development of detecting line changes involving selections difficult is not seeing which lines are being referred to by the line numbers being reported without continuously counting lines on the screen.  Therefore, lines numbers were added to the edit box.  How to do this was discovered in the Code Editor Example in the Qt documentation, which was linked to from the QPlainTextEdit help page.  It can also be found by search for "Code Editor" using the help page search.

This code was utilized in the edit box with several changes (only the code dealing with line numbers was used).  The example contains documentation, but this code essentially adds a widget on the left side of the edit box text area.  The background color of this widget is set to light gray and the line numbers are drawn into this widget.

The example code only made the width of the line number widget wide enough to hold the maximum line number plus three pixels on the left side of the numbers.  This made the line numbers look bunched in, so a full space was added to each side of the numbers.

The example code also started line numbers at one, but the line numbers in the document actually start at zero.  This needs to be taken account when calculating the widget of the line number widget and when drawing the line numbers to the widget.  However, for debugging purposes (the reason line numbers were added), it makes more sense to start the line numbers at zero.  Therefore, a BaseLineNumber definition was added to set the base line number.  For debugging, this will be set to a zero, but once this is no longer needed, this definition will be set to one.

[commit 0c67d5b69c]

Edit Box - Plain Text Edit

Detecting line changes when selections are involved is turning out to be a rather involved.  In doing research on a number of things, I discovered that there is a QPlainTextEdit class, which is a much simpler version of the QTextEdit class, and is more appropriate for a code editor.  Namely, QTextEdit is more complex and can handle rich text (text with formatting, which was disabled for EditBox), lists and tables.

The QPlainTextEdit class only handles plain text, and will also handle larger files more efficiently.  Fortunately, this class will still support syntax highlighting and other highlighting that will be needed for EditBox including highlighting errors.

To switch EditBox to using this class as its base class, all that was needed was changing all instances of QTextEdit to QPlainTextEdit.  The call to setAcceptRichText() with a false argument was no longer needed.  And there is no such concept of a current font (QPlainTextEdit only has one font), so in order to set the font to the desired fixed width font, the font() and setFont() access functions inherited from QWidget are used to set the font of the edit box.

[commit 497c46a9fa]

Wednesday, January 30, 2013

Catching Line Changes (Combining Lines)

There are two key commands that can cause a line to be deleted (but there is currently no key command to delete a line).  One is deleting when the cursor is at the end of the line, where the current line will be combined with the next, the next line will be deleted.  The other is backspacing when the cursor is at the beginning of the line, where the current line will be deleted, but the contents of the line will be combined with the previous line.

For the delete check, where the cursor is at the end of the line, it can't be at the end of the file (no line will be deleted) and there can't be any text selected (selections will be handled separately).  For the backspace check, where the cursor is at the beginning of the line, it can't be at the beginning of the file (nothing to backspace to) and there can't be any text selected.

Both of these will emit the lines deleted signal for one line, delete will send the current line number plus one and backspace will send the current line number.  Both of these signals are sent before the actual deletion takes place in the edit box.  When the document changed signal is received, the current modified line number will be set (when the cursor leave the line, it will be reported as changed).

[commit 8cf236c8d3]

Tuesday, January 29, 2013

Separate Program Change Signals

One of the next items to handle are selections of text in the edit box where multiple lines selected may be cut of deleted, or multiple lines may be pasted.  A single line change slot, even with a type, is not sufficient to handle a line changed, lines inserted and lines deleted without some involved code.

Therefore, there will be three signals, the current line changed signal (with a number and text of the line), a new lines inserted inserted (with a number and of list of strings of the lines), and a new lines deleted signal (with a number and count of the number of lines).

For now, there is only the line changed signal and lines inserted signal (for one line) and the equivalent slots in MainWindow.  The lines deleted signal will be implemented next when lines are combined due to deleting at the end of the line or backspacing at the beginning of the line.

[commit 1226e81ca4]

Monday, January 28, 2013

Catching Line Changes (New Lines)

So far the edit box is catching lines that are modified including newly inserted lines.  One of the ways that new lines are inserted is the Return key (at end of the line) or Control+Return key (splitting a line).  The edit box needs to indicate that line has been inserted and not modified, so the line changed signal was modified to report the type of change, modified, inserted or deleted.  A new function was implemented for inserting a new line into the program called for both Return keys.

When the cursor is at the end of the line, a new blank line will be inserted on the next line.  If the current line is not modified, this line does not need to be emitted since it hasn't changed.  However, if the line has been modified, it will be emitted as a modified line.  For the new blank line, all that needs to be done is the line number recorded and the new modified line type set to inserted.  The line will be emitted as a new line when the cursor leaves the line.

When the cursor is in the middle of the line (splitting the line), the line will be emitted since it is being truncated.  For the new line, the end part of the line that is now on the new line, the line number is recorded and the new modified line type set to inserted.  The line will be emitted as a new line when the cursor leaves the line.

[commit b1db6d29b0]

Sunday, January 27, 2013

Catching Line Changes (Undo/Redo)

Detecting modified lines properly with undo and redo required some effort.  The undo and redo functions were reimplemented in the EditBox class so that these operations could be controlled.  The key press event handler was modified to catch the undo and redo key sequences and to call these reimplemented functions.  A new slot was added to catch when a new undo command is added to the undo queue. This was necessary to catch when a line is modified due to an undo command.

To properly detect line changes, there are several things that undo and redo need to keep track of.  No changes are detected until the cursor has been moved from the line, so if all the changes are undone, then no line change gets detected.  Therefore, the number of changes made to a line are counted, where this count is decremented for an undo and incremented for a redo.  Only upon leaving the line does change get detected if this count is not zero.

Once a modified line is detect, if a further undo returns the cursor to the line, each undo is counted, so that when the cursor leaves the line, the modified line (from the undo) gets detected.  Any redo operation while the cursor is on the line adjust the line modification count accordingly.

The source code can be studied for exactly the steps required to accomplish this, which consists of various checks throughout.  A new line changed signal was added to EditBox, so modified lines are now emitted with the line number.  A slot was added to the MainWindow class that is connected to this signal.  For now, this slot function simply outputs the modified line number and text to the console.

There is still a problem when undoing entire lines where lines are actually deleted by the undo.  These changes will need to be emitted as line deletions.  The same signal can be used, but somehow needs to indicate the line is being delete.  There are similar issues when two lines are combined (the second line needs to be deleted), or when cutting, pasting or deleting multiple line selections.  There also needs to be a way to indicate when new lines are added.

[commit 6112a7ffd8] [commit f941edae59]

Saturday, January 26, 2013

Edit Box – Custom Context Menu

The Undo and Redo commands require extra work to track line changes they cause.  These commands need to be intercepted before the operation is performed, because the wrong lines are detected as being changed.  There are three ways that these commands are activated, by their actions (Edit menu and tool bar), keys (Control+Z and Control+Y) and from the default context menu of the edit box.

The actions can be caught by reimplementing the undo() and redo() functions in the EditBox class.  The keys can be caught by adding checks for the key sequences in the key press event handler.  But the context menu can not be caught (at least none that I could find).

Instead of using the default context menu, a custom context menu will be used for EditBox.   The necessary actions are already present.  A context menu can easily be added either by adding each action individually or creating a list of actions and then adding the list.  The later method was used and after adding the actions, plus the desired menu separators, the setContextMenuPolicy() function is used to tell EditBox to use these actions instead of the default context menu.  A side benefit of the custom context menu is that the items have the icons assigned to the actions (the default context menu does not display any icons).

[commit f3fe01028e]