Monday, February 25, 2013

Detecting Line Changes – New Idea

After some additional research, I discovered what may be a better and easier way to detect line changes (including deleted and inserted line).  The contents change signal that is emitted containing the number of characters removed and added also contains the position in the document where the change occurred.

Since all document changes cause this signal, catching line changes from this signal should be sufficient for all of the commands that change the document including the undo and redo commands.  And it may not be necessary to consider the what text was selected before the change or what the text being inserted contains.

This position of this signal can be used to determine where the cursor was before the change occurred and the line number for this position can easily be obtained and compared to the current cursor line number to determine the number of lines affected by the change.  Along with this position and the current cursor position, the net change in the number of lines in document can be used to determine how many lines may have been deleted or inserted by the command.

While doing initial testing this new scheme, it was noticed that when the file is loaded upon startup (or from loading a file), that two of these contents change signals are emitted along with the general document changed signal (used to catch when a line is modified).  These signals should be ignored.

To accomplish this, the setPlainText() was reimplemented in the EditBox class, which simply sets the ignore change flag, calls the base QPlainTextEdit::setPlainText() function and then clears the flag.  It was also necessary to add a check of this flag in the document changed slot for receiving the contents change signal.

[commit 01d1156c64]

Sunday, February 24, 2013

Pasting Over A Selection (Status)

Getting the modified lines reported correctly when pasting text over a selection is proving to be very difficult.  There are several conditions to check including whether the start of the selection is at the beginning of the line, end of the line, both (on a blank line), or neither (middle of the line); the same for the end of the selection; the number of new lines in the text being inserted, and if the text being inserted starts with or ends with a new line.

These conditions are using to determine the starting line and number of lines (if any) to report as being deleted, if the line that start position is at should be reported as modified, not modified, be included with the deleted lines or the inserted lines, the lines that have been inserted, and if the line the cursor ends up on is modified, not modified or should be marked as a new line to be inserted.

Some of the if statements for these checks are five to eight lines long.  I already know how to simplify if statements for up to four conditions using Karnaugh Maps, but I had to learn how to do ones with five and six conditions (not easy) and gets worst with more conditions.  So I'm going to take a step back and see if there is an easier what handle all this...

Saturday, February 16, 2013

Another Deleted Line Detection Issue

Another issue was found in the capture deleted lines routine when the selection is replaced by a single character (typing a character when text is selected).  There were two conditions that reset the modified line variable when a selection was deleted - when both the selection start and end positions were either at the beginning of a line or at the end of the line.

However, if the selection is replaced with a single character, the modified line variable should not be reset since the line has been modified by the additional of the character.  So a check was added that the number of characters added from the document change must be zero.  This value was already available because it was stored when the document change signal was received (and up to now, was not being used).

[commit 5abae096ce]

Deleted Line Detection Issues

While testing the changes for detecting changed lines when a pasting text over a selection, some problems were found with the proper reporting of lines that were deleted.  The problem was in the existing capture deleted lines routine, not in the new changes.

This routine was simply reporting lines being deleted from the line of the selection start position, for the number of lines in the selection minus one (the remaining line was set as modified).  This did not take into account if the selection start or end position was at the begin or end of their respective lines.  There are situations where the first line of the selection should not be reported as deleted (for instance, if the selection start is not at the beginning of the line, then the line will not be deleted, only modified).

Two checks needed to be made - the first line reported as deleted, and if the line the cursor is on after the deletion will be modified by the delete operation.  For instance, if the selection starts at the end of the line, and the selection end is on a blank line, then the final cursor line after the deletion will not have been modified.

The first line to report as deleted will be the line the selection start position is at unless the selection start position is not at the begin of the line or at the end of the line and the selection end position is not at the begin of the line, in which case, the first line to report is the next line after the start position line.

The delete operation will cause the line the cursor is at afterward to be marked as modified via the document changed signal.  The modified line variable needs to be reset if the line is not actually modified by the delete operation.  This condition is if the selection start and end positions are at the begin of their lines, or if both are at the end of their lines.

As before, if the selection start and end are on the same line, no lines are reported as being deleted.  The Selection class was modified to also be able to report if the selection start and end positions are at the begin or end of their lines.

[commit c753fd1662]

Wednesday, February 13, 2013

Insert Issue And Overwrite Mode

While testing the changes for detecting changed (deleted and inserted) lines when pasting text over a current selection, I noticed that the Shift+Insert sequence, while inserted text from the clipboard, was not being intercepted by the reimplemented paste function.  This was because the key press event handler was only looking for the standard paste key sequence, which is only the Control+V sequence.  So the Insert key code case was modified to look for Shift+Insert sequence in addition to the Control+Shift+Insert sequence.

It was also noticed that just the Insert key was not changing the edit box to overwrite mode from insert mode, which turned out simply to be that the QPlainTextEdit class does not support this by default.  It does support an overwrite mode, so this feature was implemented by using the setOverwrite() function with the argument of the inverse of the current overwrite mode, which causes Insert key (with no key modifiers) to toggle between overwrite and insert modes.

[commit ed503cb558] [commit 0742f7e78d]

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]