From d0f149f2aac0e0820cfec8c03c625ffb3dababb0 Mon Sep 17 00:00:00 2001 From: Bryan Tan Date: Mon, 27 May 2019 14:58:58 -0700 Subject: [PATCH 1/6] Refactor double click event handling --- src/gui/PageView.cpp | 60 ++++++++++++++++++++++++ src/gui/PageView.h | 1 + src/gui/inputdevices/PenInputHandler.cpp | 50 ++------------------ 3 files changed, 64 insertions(+), 47 deletions(-) diff --git a/src/gui/PageView.cpp b/src/gui/PageView.cpp index 288ceee4..12515a16 100644 --- a/src/gui/PageView.cpp +++ b/src/gui/PageView.cpp @@ -45,6 +45,7 @@ #include #include +#include XojPageView::XojPageView(XournalView* xournal, PageRef page) { @@ -443,6 +444,65 @@ bool XojPageView::onButtonPressEvent(const PositionInputData& pos) return true; } +bool XojPageView::onButtonDoublePressEvent(const PositionInputData& pos) +{ + // This method assumes that it is called after onButtonPressEvent but before + // onButtonReleaseEvent + double x = pos.x; + double y = pos.y; + if (x < 0 || y < 0) + { + return false; + } + + double zoom = this->xournal->getZoom(); + x /= zoom; + y /= zoom; + ToolHandler* toolHandler = this->xournal->getControl()->getToolHandler(); + ToolType toolType = toolHandler->getToolType(); + bool isSelectTool = toolType == TOOL_SELECT_OBJECT || TOOL_SELECT_RECT || TOOL_SELECT_REGION; + + EditSelection* selection = xournal->getSelection(); + bool hasNoModifiers = !pos.isShiftDown() && !pos.isControlDown(); + + if (hasNoModifiers && isSelectTool && selection != nullptr) + { + // Find a selected object under the cursor, if possible. The selection doesn't change the + // element coordinates until it is finalized, so we need to use position relative to the + // original coordinates of the selection. + double origx = x - (selection->getXOnView() - selection->getOriginalXOnView()) / zoom; + double origy = y - (selection->getYOnView() - selection->getOriginalYOnView()) / zoom; + std::vector* elems = selection->getElements(); + auto it = std::find_if(elems->begin(), elems->end(), [&](Element*& elem) { + return elem->intersectsArea(origx - 5, origy - 5, 5, 5); + }); + if (it != elems->end()) + { + // Enter editing mode on the selected object + Element* object = *it; + ElementType elemType = object->getType(); + if (elemType == ELEMENT_TEXT) + { + this->xournal->clearSelection(); + toolHandler->selectTool(TOOL_TEXT); + // Simulate a button press; there's too many things that we + // could forget to do if we manually call startText + this->onButtonPressEvent(pos); + } + else if (elemType == ELEMENT_TEXIMAGE) + { + Control* control = this->xournal->getControl(); + this->xournal->clearSelection(); + EditSelection* sel = new EditSelection(control->getUndoRedoHandler(), object, this, this->getPage()); + this->xournal->setSelection(sel); + control->runLatex(); + } + } + } + + return true; +} + void XojPageView::resetShapeRecognizer() { XOJ_CHECK_TYPE(XojPageView); diff --git a/src/gui/PageView.h b/src/gui/PageView.h index 4c56cbc8..960868e4 100644 --- a/src/gui/PageView.h +++ b/src/gui/PageView.h @@ -149,6 +149,7 @@ public: public: // event handler bool onButtonPressEvent(const PositionInputData& pos); bool onButtonReleaseEvent(const PositionInputData& pos); + bool onButtonDoublePressEvent(const PositionInputData& pos); bool onMotionNotifyEvent(const PositionInputData& pos); /** diff --git a/src/gui/inputdevices/PenInputHandler.cpp b/src/gui/inputdevices/PenInputHandler.cpp index 5260e84a..b7411aca 100644 --- a/src/gui/inputdevices/PenInputHandler.cpp +++ b/src/gui/inputdevices/PenInputHandler.cpp @@ -13,7 +13,6 @@ #include "gui/XournalView.h" #include -#include #include #define WIDGET_SCROLL_BORDER 25 @@ -385,58 +384,15 @@ bool PenInputHandler::actionEnd(GdkEvent* event) void PenInputHandler::actionPerform(GdkEvent* event) { XOJ_CHECK_TYPE(PenInputHandler); - GtkXournal* xournal = this->inputContext->getXournal(); - EditSelection* selection = xournal->selection; - - ToolHandler* toolHandler = this->inputContext->getToolHandler(); - ToolType toolType = toolHandler->getToolType(); - bool isSelectTool = toolType == TOOL_SELECT_OBJECT || TOOL_SELECT_RECT || TOOL_SELECT_REGION; #ifdef DEBUG_INPUT g_message("Discrete input action; modifier1=%s, modifier2=%2", this->modifier2 ? "true" : "false", this->modifier3 ? "true" : "false"); #endif - // Double click selection to edit; - // Only applies to double left clicks / taps - if (!this->modifier2 && !this->modifier3 && isSelectTool && selection != nullptr) - { - XojPageView* currentPage = this->getPageAtCurrentPosition(event); - PositionInputData pos = getInputDataRelativeToCurrentPage(currentPage, event); - - // Find a selected object under the cursor, if possible. The selection doesn't change the - // element coordinates until it is finalized, so we need to use position relative to the - // original coordinates of the selection. - double zoom = xournal->view->getZoom(); - double x = (pos.x - selection->getXOnView() + selection->getOriginalXOnView()) / zoom; - double y = (pos.y - selection->getYOnView() + selection->getOriginalYOnView()) / zoom; - std::vector* elems = selection->getElements(); - auto it = std::find_if(elems->begin(), elems->end(), [&](Element*& elem) { - return elem->intersectsArea(x - 5, y - 5, 5, 5); - }); - if (it != elems->end()) - { - // Enter editing mode on the selected object - Element* object = *it; - ElementType elemType = object->getType(); - if (elemType == ELEMENT_TEXT) - { - xournal->view->clearSelection(); - toolHandler->selectTool(TOOL_TEXT); - // Simulate a button press; there's too many things that we - // could forget to do if we manually call XojPageView::startText - currentPage->onButtonPressEvent(pos); - } - else if (elemType == ELEMENT_TEXIMAGE) - { - Control* control = xournal->view->getControl(); - xournal->view->clearSelection(); - EditSelection* sel = new EditSelection(control->getUndoRedoHandler(), object, currentPage, currentPage->getPage()); - xournal->view->setSelection(sel); - control->runLatex(); - } - } - } + XojPageView* currentPage = this->getPageAtCurrentPosition(event); + PositionInputData pos = this->getInputDataRelativeToCurrentPage(currentPage, event); + currentPage->onButtonDoublePressEvent(pos); } void PenInputHandler::actionLeaveWindow(GdkEvent* event) From 4990585fa57b12978159db2378224f141a84dba8 Mon Sep 17 00:00:00 2001 From: Bryan Tan Date: Mon, 27 May 2019 15:01:40 -0700 Subject: [PATCH 2/6] Make double clicks on text editors select the current word --- src/gui/PageView.cpp | 5 +++++ src/gui/TextEditor.cpp | 30 ++++++++++++++++++++++++++++++ src/gui/TextEditor.h | 1 + 3 files changed, 36 insertions(+) diff --git a/src/gui/PageView.cpp b/src/gui/PageView.cpp index 12515a16..5d02f241 100644 --- a/src/gui/PageView.cpp +++ b/src/gui/PageView.cpp @@ -499,6 +499,11 @@ bool XojPageView::onButtonDoublePressEvent(const PositionInputData& pos) } } } + else if (toolHandler->getToolType() == TOOL_TEXT) + { + this->startText(x, y); + this->textEditor->selectWord(); + } return true; } diff --git a/src/gui/TextEditor.cpp b/src/gui/TextEditor.cpp index 07b7e763..b8d11e21 100644 --- a/src/gui/TextEditor.cpp +++ b/src/gui/TextEditor.cpp @@ -449,6 +449,36 @@ void TextEditor::toggleBold() //this->repaintEditor(); } +void TextEditor::selectWord() +{ + XOJ_CHECK_TYPE(TextEditor); + + GtkTextMark* mark = gtk_text_buffer_get_insert(this->buffer); + GtkTextIter currentPos; + gtk_text_buffer_get_iter_at_mark(this->buffer, ¤tPos, mark); + + // Only process double click + if (!gtk_text_iter_inside_word(¤tPos)) + { + return; + } + + GtkTextIter startPos = currentPos; + GtkTextIter endPos = currentPos; + if (!gtk_text_iter_starts_word(¤tPos)) + { + gtk_text_iter_backward_word_start(&startPos); + } + if (!gtk_text_iter_ends_word(¤tPos)) + { + gtk_text_iter_forward_word_end(&endPos); + } + + gtk_text_buffer_select_range(this->buffer, &startPos, &endPos); + + this->repaintEditor(); +} + void TextEditor::selectAll() { XOJ_CHECK_TYPE(TextEditor); diff --git a/src/gui/TextEditor.h b/src/gui/TextEditor.h index 714929b7..6381b2c2 100644 --- a/src/gui/TextEditor.h +++ b/src/gui/TextEditor.h @@ -32,6 +32,7 @@ public: bool onKeyReleaseEvent(GdkEventKey* event); void toggleOverwrite(); + void selectWord(); void selectAll(); void toggleBold(); void incSize(); From ff44bf888a76893bee36b1d3c578b57612d34fc8 Mon Sep 17 00:00:00 2001 From: Bryan Tan Date: Mon, 27 May 2019 15:05:28 -0700 Subject: [PATCH 3/6] Fix double-click selection edit not working after moving the selection --- src/gui/PageView.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/PageView.cpp b/src/gui/PageView.cpp index 5d02f241..6ec88d85 100644 --- a/src/gui/PageView.cpp +++ b/src/gui/PageView.cpp @@ -470,8 +470,8 @@ bool XojPageView::onButtonDoublePressEvent(const PositionInputData& pos) // Find a selected object under the cursor, if possible. The selection doesn't change the // element coordinates until it is finalized, so we need to use position relative to the // original coordinates of the selection. - double origx = x - (selection->getXOnView() - selection->getOriginalXOnView()) / zoom; - double origy = y - (selection->getYOnView() - selection->getOriginalYOnView()) / zoom; + double origx = x - (selection->getXOnView() - selection->getOriginalXOnView()); + double origy = y - (selection->getYOnView() - selection->getOriginalYOnView()); std::vector* elems = selection->getElements(); auto it = std::find_if(elems->begin(), elems->end(), [&](Element*& elem) { return elem->intersectsArea(origx - 5, origy - 5, 5, 5); From 41f91e39951ecfd0c1285ea2feb4af1b2824cd82 Mon Sep 17 00:00:00 2001 From: Bryan Tan Date: Tue, 28 May 2019 15:05:06 -0700 Subject: [PATCH 4/6] Refactor TextEditor text selection into a single function --- src/gui/PageView.cpp | 2 +- src/gui/TextEditor.cpp | 46 +++++++++++++++++++++----------------- src/gui/TextEditor.h | 6 +++-- src/gui/TextEditorWidget.h | 2 +- 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/gui/PageView.cpp b/src/gui/PageView.cpp index 6ec88d85..0a43aa09 100644 --- a/src/gui/PageView.cpp +++ b/src/gui/PageView.cpp @@ -502,7 +502,7 @@ bool XojPageView::onButtonDoublePressEvent(const PositionInputData& pos) else if (toolHandler->getToolType() == TOOL_TEXT) { this->startText(x, y); - this->textEditor->selectWord(); + this->textEditor->selectAtCursor(TextEditor::SelectType::word); } return true; diff --git a/src/gui/TextEditor.cpp b/src/gui/TextEditor.cpp index b8d11e21..c1187f6a 100644 --- a/src/gui/TextEditor.cpp +++ b/src/gui/TextEditor.cpp @@ -449,7 +449,7 @@ void TextEditor::toggleBold() //this->repaintEditor(); } -void TextEditor::selectWord() +void TextEditor::selectAtCursor(TextEditor::SelectType ty) { XOJ_CHECK_TYPE(TextEditor); @@ -465,13 +465,31 @@ void TextEditor::selectWord() GtkTextIter startPos = currentPos; GtkTextIter endPos = currentPos; - if (!gtk_text_iter_starts_word(¤tPos)) - { - gtk_text_iter_backward_word_start(&startPos); - } - if (!gtk_text_iter_ends_word(¤tPos)) - { - gtk_text_iter_forward_word_end(&endPos); + + switch(ty) { + case TextEditor::SelectType::word: + if (!gtk_text_iter_starts_word(¤tPos)) + { + gtk_text_iter_backward_word_start(&startPos); + } + if (!gtk_text_iter_ends_word(¤tPos)) + { + gtk_text_iter_forward_word_end(&endPos); + } + break; + case TextEditor::SelectType::paragraph: + if (!gtk_text_iter_starts_line(¤tPos)) + { + gtk_text_iter_backward_line(&startPos); + } + if (!gtk_text_iter_ends_line(¤tPos)) + { + gtk_text_iter_forward_to_line_end(&endPos); + } + break; + case TextEditor::SelectType::all: + gtk_text_buffer_get_bounds(this->buffer, &startPos, &endPos); + break; } gtk_text_buffer_select_range(this->buffer, &startPos, &endPos); @@ -479,18 +497,6 @@ void TextEditor::selectWord() this->repaintEditor(); } -void TextEditor::selectAll() -{ - XOJ_CHECK_TYPE(TextEditor); - - GtkTextIter start_iter, end_iter; - - gtk_text_buffer_get_bounds(buffer, &start_iter, &end_iter); - gtk_text_buffer_select_range(buffer, &start_iter, &end_iter); - - this->repaintEditor(); -} - void TextEditor::moveCursor(GtkMovementStep step, int count, bool extendSelection) { XOJ_CHECK_TYPE(TextEditor); diff --git a/src/gui/TextEditor.h b/src/gui/TextEditor.h index 6381b2c2..a09106f8 100644 --- a/src/gui/TextEditor.h +++ b/src/gui/TextEditor.h @@ -26,14 +26,16 @@ public: TextEditor(XojPageView* gui, GtkWidget* widget, Text* text, bool ownText); virtual ~TextEditor(); + /** Represents the different kinds of text selection */ + enum class SelectType { word, paragraph, all }; + void paint(cairo_t* cr, GdkRectangle* rect, double zoom); bool onKeyPressEvent(GdkEventKey* event); bool onKeyReleaseEvent(GdkEventKey* event); void toggleOverwrite(); - void selectWord(); - void selectAll(); + void selectAtCursor(TextEditor::SelectType ty); void toggleBold(); void incSize(); void decSize(); diff --git a/src/gui/TextEditorWidget.h b/src/gui/TextEditorWidget.h index 66df9e27..e4de18d1 100644 --- a/src/gui/TextEditorWidget.h +++ b/src/gui/TextEditorWidget.h @@ -65,7 +65,7 @@ static guint signals[LAST_SIGNAL] = {0}; static void gtk_xoj_int_txt_select_all(GtkWidget* widget) { GtkXojIntTxt* txt = GTK_XOJ_INT_TXT(widget); - txt->te->selectAll(); + txt->te->selectAtCursor(TextEditor::SelectType::all); } static void gtk_xoj_int_txt_move_cursor(GtkWidget* widget, gint step, gint bitmask) From e86a2d504481e2c1499f50065abc3687e073c057 Mon Sep 17 00:00:00 2001 From: Bryan Tan Date: Tue, 28 May 2019 15:05:41 -0700 Subject: [PATCH 5/6] Handle triple clicks Note that there is an issue in that GTK considers a "paragraph" to be a section of text between two line breaks (xournalpp does not wrap lines) --- src/gui/PageView.cpp | 32 +++++++++++++++++---- src/gui/PageView.h | 1 + src/gui/inputdevices/MouseInputHandler.cpp | 2 +- src/gui/inputdevices/PenInputHandler.cpp | 9 +++++- src/gui/inputdevices/StylusInputHandler.cpp | 2 +- 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/gui/PageView.cpp b/src/gui/PageView.cpp index 0a43aa09..b2c30020 100644 --- a/src/gui/PageView.cpp +++ b/src/gui/PageView.cpp @@ -448,16 +448,14 @@ bool XojPageView::onButtonDoublePressEvent(const PositionInputData& pos) { // This method assumes that it is called after onButtonPressEvent but before // onButtonReleaseEvent - double x = pos.x; - double y = pos.y; + double zoom = this->xournal->getZoom(); + double x = pos.x / zoom; + double y = pos.y / zoom; if (x < 0 || y < 0) { return false; } - double zoom = this->xournal->getZoom(); - x /= zoom; - y /= zoom; ToolHandler* toolHandler = this->xournal->getControl()->getToolHandler(); ToolType toolType = toolHandler->getToolType(); bool isSelectTool = toolType == TOOL_SELECT_OBJECT || TOOL_SELECT_RECT || TOOL_SELECT_REGION; @@ -499,7 +497,7 @@ bool XojPageView::onButtonDoublePressEvent(const PositionInputData& pos) } } } - else if (toolHandler->getToolType() == TOOL_TEXT) + else if (toolType == TOOL_TEXT) { this->startText(x, y); this->textEditor->selectAtCursor(TextEditor::SelectType::word); @@ -508,6 +506,28 @@ bool XojPageView::onButtonDoublePressEvent(const PositionInputData& pos) return true; } +bool XojPageView::onButtonTriplePressEvent(const PositionInputData& pos) +{ + // This method assumes that it is called after onButtonDoubleEvent but before + // onButtonReleaseEvent + double zoom = this->xournal->getZoom(); + double x = pos.x / zoom; + double y = pos.y / zoom; + if (x < 0 || y < 0) + { + return false; + } + + ToolHandler* toolHandler = this->xournal->getControl()->getToolHandler(); + + if (toolHandler->getToolType() == TOOL_TEXT) + { + this->startText(x, y); + this->textEditor->selectAtCursor(TextEditor::SelectType::paragraph); + } + return true; +} + void XojPageView::resetShapeRecognizer() { XOJ_CHECK_TYPE(XojPageView); diff --git a/src/gui/PageView.h b/src/gui/PageView.h index 960868e4..a4d7efa0 100644 --- a/src/gui/PageView.h +++ b/src/gui/PageView.h @@ -150,6 +150,7 @@ public: // event handler bool onButtonPressEvent(const PositionInputData& pos); bool onButtonReleaseEvent(const PositionInputData& pos); bool onButtonDoublePressEvent(const PositionInputData& pos); + bool onButtonTriplePressEvent(const PositionInputData& pos); bool onMotionNotifyEvent(const PositionInputData& pos); /** diff --git a/src/gui/inputdevices/MouseInputHandler.cpp b/src/gui/inputdevices/MouseInputHandler.cpp index e6f56ed7..eaeaa451 100644 --- a/src/gui/inputdevices/MouseInputHandler.cpp +++ b/src/gui/inputdevices/MouseInputHandler.cpp @@ -47,7 +47,7 @@ bool MouseInputHandler::handleImpl(GdkEvent* event) return true; } - if (event->type == GDK_DOUBLE_BUTTON_PRESS) + if (event->type == GDK_DOUBLE_BUTTON_PRESS || event->type == GDK_TRIPLE_BUTTON_PRESS) { this->actionPerform(event); return true; diff --git a/src/gui/inputdevices/PenInputHandler.cpp b/src/gui/inputdevices/PenInputHandler.cpp index b7411aca..fd405ad0 100644 --- a/src/gui/inputdevices/PenInputHandler.cpp +++ b/src/gui/inputdevices/PenInputHandler.cpp @@ -392,7 +392,14 @@ void PenInputHandler::actionPerform(GdkEvent* event) XojPageView* currentPage = this->getPageAtCurrentPosition(event); PositionInputData pos = this->getInputDataRelativeToCurrentPage(currentPage, event); - currentPage->onButtonDoublePressEvent(pos); + if (event->type == GDK_DOUBLE_BUTTON_PRESS) + { + currentPage->onButtonDoublePressEvent(pos); + } + else if (event->type == GDK_TRIPLE_BUTTON_PRESS) + { + currentPage->onButtonTriplePressEvent(pos); + } } void PenInputHandler::actionLeaveWindow(GdkEvent* event) diff --git a/src/gui/inputdevices/StylusInputHandler.cpp b/src/gui/inputdevices/StylusInputHandler.cpp index 8eaad254..16e90b2e 100644 --- a/src/gui/inputdevices/StylusInputHandler.cpp +++ b/src/gui/inputdevices/StylusInputHandler.cpp @@ -55,7 +55,7 @@ bool StylusInputHandler::handleImpl(GdkEvent* event) } // Trigger discrete action on double tap - if (event->type == GDK_DOUBLE_BUTTON_PRESS) + if (event->type == GDK_DOUBLE_BUTTON_PRESS || event->type == GDK_TRIPLE_BUTTON_PRESS) { this->actionPerform(event); return true; From efb071e3f74cefeaf1b085a4db6bbdb2d014ab31 Mon Sep 17 00:00:00 2001 From: Bryan Tan Date: Wed, 29 May 2019 23:09:50 -0700 Subject: [PATCH 6/6] Properly implement text editor paragraph-on-cursor selection --- src/gui/TextEditor.cpp | 52 +++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/src/gui/TextEditor.cpp b/src/gui/TextEditor.cpp index c1187f6a..2ff3a989 100644 --- a/src/gui/TextEditor.cpp +++ b/src/gui/TextEditor.cpp @@ -454,20 +454,21 @@ void TextEditor::selectAtCursor(TextEditor::SelectType ty) XOJ_CHECK_TYPE(TextEditor); GtkTextMark* mark = gtk_text_buffer_get_insert(this->buffer); - GtkTextIter currentPos; - gtk_text_buffer_get_iter_at_mark(this->buffer, ¤tPos, mark); - - // Only process double click - if (!gtk_text_iter_inside_word(¤tPos)) - { - return; - } - - GtkTextIter startPos = currentPos; - GtkTextIter endPos = currentPos; + GtkTextIter startPos; + GtkTextIter endPos; + gtk_text_buffer_get_selection_bounds(this->buffer, &startPos, &endPos); + const auto searchFlag = GTK_TEXT_SEARCH_TEXT_ONLY; // To be used to find double newlines switch(ty) { case TextEditor::SelectType::word: + // Do nothing if cursor is over whitespace + GtkTextIter currentPos; + gtk_text_buffer_get_iter_at_mark(this->buffer, ¤tPos, mark); + if (!gtk_text_iter_inside_word(¤tPos)) + { + return; + } + if (!gtk_text_iter_starts_word(¤tPos)) { gtk_text_iter_backward_word_start(&startPos); @@ -478,13 +479,38 @@ void TextEditor::selectAtCursor(TextEditor::SelectType ty) } break; case TextEditor::SelectType::paragraph: - if (!gtk_text_iter_starts_line(¤tPos)) + // Note that a GTK "paragraph" is a line, so there's no nice one-liner. + // We define a paragraph as text separated by double newlines. + while (!gtk_text_iter_is_start(&startPos)) { + // There's no GTK function to go to line start, so do it manually. + while (!gtk_text_iter_starts_line(&startPos)) + { + if (!gtk_text_iter_backward_word_start(&startPos)) + { + break; + } + } + // Check for paragraph start + GtkTextIter searchPos = startPos; + gtk_text_iter_backward_chars(&searchPos, 2); + if (gtk_text_iter_backward_search(&startPos, "\n\n", searchFlag, nullptr, nullptr, &searchPos)) + { + break; + } gtk_text_iter_backward_line(&startPos); } - if (!gtk_text_iter_ends_line(¤tPos)) + while (!gtk_text_iter_ends_line(&endPos)) { gtk_text_iter_forward_to_line_end(&endPos); + // Check for paragraph end + GtkTextIter searchPos = endPos; + gtk_text_iter_forward_chars(&searchPos, 2); + if (gtk_text_iter_forward_search(&endPos, "\n\n", searchFlag, nullptr, nullptr, &searchPos)) + { + break; + } + gtk_text_iter_forward_line(&endPos); } break; case TextEditor::SelectType::all: