aboutsummaryrefslogtreecommitdiff
path: root/dw/selection.cc
diff options
context:
space:
mode:
Diffstat (limited to 'dw/selection.cc')
-rw-r--r--dw/selection.cc514
1 files changed, 514 insertions, 0 deletions
diff --git a/dw/selection.cc b/dw/selection.cc
new file mode 100644
index 00000000..3153576f
--- /dev/null
+++ b/dw/selection.cc
@@ -0,0 +1,514 @@
+/*
+ * Dillo Widget
+ *
+ * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+
+#include "core.hh"
+
+#include <string.h>
+
+/*
+ * strndup() is a GNU extension.
+ */
+extern "C" char *strndup(const char *s, size_t size)
+{
+ char *r = (char *) malloc (size + 1);
+
+ if (r) {
+ strncpy (r, s, size);
+ r[size] = 0;
+ }
+
+ return r;
+}
+
+namespace dw {
+namespace core {
+
+SelectionState::SelectionState ()
+{
+ layout = NULL;
+
+ selectionState = NONE;
+ from = NULL;
+ to = NULL;
+
+ linkState = LINK_NONE;
+ link = NULL;
+}
+
+SelectionState::~SelectionState ()
+{
+ reset ();
+}
+
+
+bool SelectionState::DoubleClickEmitter::emitToReceiver (lout::signal::Receiver
+ *receiver,
+ int signalNo,
+ int argc,
+ Object **argv)
+{
+ ((DoubleClickReceiver*)receiver)->doubleClick ();
+ return false;
+}
+
+void SelectionState::reset ()
+{
+ resetSelection ();
+ resetLink ();
+}
+
+void SelectionState::resetSelection ()
+{
+ if (from)
+ delete from;
+ from = NULL;
+ if (to)
+ delete to;
+ to = NULL;
+ selectionState = NONE;
+}
+
+
+void SelectionState::resetLink ()
+{
+ if (link)
+ delete link;
+ link = NULL;
+ linkState = LINK_NONE;
+}
+
+bool SelectionState::buttonPress (Iterator *it, int charPos, int linkNo,
+ EventButton *event, bool withinContent)
+{
+ Widget *itWidget = it->getWidget ();
+ bool ret = false;
+
+ if (event && event->button == 1 &&
+ !withinContent && event->numPressed == 2) {
+ // When the user double-clicks on empty parts, emit the double click
+ // signal instead of normal processing. Used for full screen
+ // mode.
+ doubleClickEmitter.emitDoubleClick ();
+ // Reset everything, so that dw::core::Selection::buttonRelease will
+ // ignore the "release" event following soon.
+ highlight (false, 0);
+ reset ();
+ ret = true;
+ } else {
+ if (linkNo != -1) {
+ // link handling
+ if (event) {
+ // return value is ignored
+ itWidget->emitLinkPress (linkNo, -1, -1, -1, event);
+ resetLink ();
+ linkState = LINK_PRESSED;
+ linkButton = event->button;
+ DeepIterator *newLink = new DeepIterator (it);
+ if (newLink->isEmpty ()) {
+ delete newLink;
+ resetLink ();
+ } else {
+ link = newLink;
+ // It may be that the user has pressed on something activatable
+ // (linkNo != -1), but there is no contents, e.g. with images
+ // without ALTernative text.
+ if (link) {
+ linkChar = correctCharPos (link, charPos);
+ linkNumber = linkNo;
+ }
+ }
+ // We do not return the value of the signal method,
+ // but we do actually process this event.
+ ret = true;
+ }
+ } else {
+ // normal selection handling
+ if (event && event->button == 1) {
+ highlight (false, 0);
+ resetSelection ();
+
+ selectionState = SELECTING;
+ DeepIterator *newFrom = new DeepIterator (it);
+ if (newFrom->isEmpty ()) {
+ delete newFrom;
+ resetSelection ();
+ } else {
+ from = newFrom;
+ fromChar = correctCharPos (from, charPos);
+ to = from->cloneDeepIterator ();
+ toChar = correctCharPos (to, charPos);
+ }
+ ret = true;
+ } else {
+ if (event && event->button == 3) {
+ // menu popup
+ itWidget->emitLinkPress (-1, -1, -1, -1, event);
+ ret = true;
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+bool SelectionState::buttonRelease (Iterator *it, int charPos, int linkNo,
+ EventButton *event, bool withinContent)
+{
+ Widget *itWidget = it->getWidget ();
+ bool ret = false;
+
+ if (linkState == LINK_PRESSED && event && event->button == linkButton) {
+ // link handling
+ ret = true;
+ if (linkNo != -1)
+ // return value is ignored
+ itWidget->emitLinkRelease (linkNo, -1, -1, -1, event);
+
+ // The link where the user clicked the mouse button?
+ if (linkNo == linkNumber) {
+ resetLink ();
+ // return value is ignored
+ itWidget->emitLinkClick (linkNo, -1, -1, -1, event);
+ } else {
+ if (event->button == 1)
+ // Reset links and switch to selection mode. The selection
+ // state will be set to SELECTING, which is handled some lines
+ // below.
+ switchLinkToSelection (it, charPos);
+ }
+ }
+
+ if (selectionState == SELECTING && event && event->button == 1) {
+ // normal selection
+ ret = true;
+ adjustSelection (it, charPos);
+
+ if (from->compareTo (to) == 0 && fromChar == toChar)
+ // nothing selected
+ resetSelection ();
+ else {
+ copy ();
+ selectionState = SELECTED;
+ }
+ }
+
+ return ret;
+}
+
+bool SelectionState::buttonMotion (Iterator *it, int charPos, int linkNo,
+ EventMotion *event, bool withinContent)
+{
+ if (linkState == LINK_PRESSED) {
+ //link handling
+ if (linkNo != linkNumber)
+ // No longer the link where the user clicked the mouse button.
+ // Reset links and switch to selection mode.
+ switchLinkToSelection (it, charPos);
+ // Still in link: do nothing.
+ } else if (selectionState == SELECTING) {
+ // selection
+ adjustSelection (it, charPos);
+ }
+
+ return true;
+}
+
+/**
+ * \brief General form of dw::core::SelectionState::buttonPress,
+ * dw::core::SelectionState::buttonRelease and
+ * dw::core::SelectionState::buttonMotion.
+ */
+bool SelectionState::handleEvent (EventType eventType, Iterator *it,
+ int charPos, int linkNo,
+ MousePositionEvent *event,
+ bool withinContent)
+{
+ switch (eventType) {
+ case BUTTON_PRESS:
+ return buttonPress (it, charPos, linkNo, (EventButton*)event,
+ withinContent);
+
+ case BUTTON_RELEASE:
+ return buttonRelease (it, charPos, linkNo, (EventButton*)event,
+ withinContent);
+
+ case BUTTON_MOTION:
+ return buttonMotion (it, charPos, linkNo, (EventMotion*)event,
+ withinContent);
+
+
+ default:
+ misc::assertNotReached ();
+ }
+
+ return false;
+}
+
+
+/**
+ * \brief This method is called when the user decides not to activate a link,
+ * but instead select text.
+ */
+void SelectionState::switchLinkToSelection (Iterator *it, int charPos)
+{
+ // It may be that selection->link is NULL, see a_Selection_button_press.
+ if (link) {
+ // Reset old selection.
+ highlight (false, 0);
+ resetSelection ();
+
+ // Transfer link state into selection state.
+ from = link->cloneDeepIterator ();
+ fromChar = linkChar;
+ to = from->createVariant (it);
+ toChar = correctCharPos (to, charPos);
+ selectionState = SELECTING;
+
+ // Reset link status.
+ resetLink ();
+
+ highlight (true, 0);
+
+ } else {
+ // A link was pressed on, but there is nothing to select. Reset
+ // everything.
+ resetSelection ();
+ resetLink ();
+ }
+}
+
+/**
+ * \brief This method is used by core::dw::SelectionState::buttonMotion and
+ * core::dw::SelectionState::buttonRelease, and changes the second limit of
+ * the already existing selection region.
+ */
+void SelectionState::adjustSelection (Iterator *it, int charPos)
+{
+ DeepIterator *newTo;
+ int newToChar, cmpOld, cmpNew, cmpDiff, len;
+ bool bruteHighlighting = false;
+
+ newTo = to->createVariant (it);
+ newToChar = correctCharPos (newTo, charPos);
+
+ cmpOld = to->compareTo (from);
+ cmpNew = newTo->compareTo (from);
+
+ if (cmpOld == 0 || cmpNew == 0) {
+ // Either before, or now, the limits differ only by the character
+ // position.
+ bruteHighlighting = true;
+ } else if (cmpOld * cmpNew < 0) {
+ // The selection order has changed, i.e. the user moved the selection
+ // end again beyond the position he started.
+ bruteHighlighting = true;
+ } else {
+ // Here, cmpOld and cmpNew are equivalent and != 0.
+ cmpDiff = newTo->compareTo (to);
+
+ if (cmpOld * cmpDiff > 0) {
+ // The user has enlarged the selection. Highlight the difference.
+ if (cmpDiff < 0) {
+ len = correctCharPos (to, END_OF_WORD);
+ highlight0 (true, newTo, newToChar, to, len + 1, 1);
+ } else {
+ highlight0 (true, to, 0, newTo, newToChar, -1);
+ }
+ } else {
+ if (cmpOld * cmpDiff < 0) {
+ // The user has reduced the selection. Unighlight the difference.
+ highlight0 (false, to, 0, newTo, 0, cmpDiff);
+ }
+
+ // Otherwise, the user has changed the position only slightly.
+ // In both cases, re-highlight the new position.
+ if (cmpOld < 0) {
+ len = correctCharPos (newTo, END_OF_WORD);
+ newTo->highlight (newToChar, len + 1, HIGHLIGHT_SELECTION);
+ } else
+ newTo->highlight (0, newToChar, HIGHLIGHT_SELECTION);
+ }
+ }
+
+ if (bruteHighlighting)
+ highlight (false, 0);
+
+ delete to;
+ to = newTo;
+ toChar = newToChar;
+
+ if (bruteHighlighting)
+ highlight (true, 0);
+}
+
+/**
+ * \brief This method deals especially with the case that a widget passes
+ * dw::core::SelectionState::END_OF_WORD.
+ */
+int SelectionState::correctCharPos (DeepIterator *it, int charPos)
+{
+ Iterator *top = it->getTopIterator ();
+ int len;
+
+ if (top->getContent()->type == Content::TEXT)
+ len = strlen(top->getContent()->text);
+ else
+ len = 1;
+
+ return misc::min(charPos, len);
+}
+
+void SelectionState::highlight0 (bool fl, DeepIterator *from, int fromChar,
+ DeepIterator *to, int toChar, int dir)
+{
+ DeepIterator *a, *b, *i;
+ int cmp, aChar, bChar;
+ bool start;
+
+ if (from && to) {
+ cmp = from->compareTo (to);
+ if (cmp == 0) {
+ if (fl) {
+ if (fromChar < toChar)
+ from->highlight (fromChar, toChar, HIGHLIGHT_SELECTION);
+ else
+ from->highlight (toChar, fromChar, HIGHLIGHT_SELECTION);
+ } else
+ from->unhighlight (0, HIGHLIGHT_SELECTION);
+ return;
+ }
+
+ if (cmp < 0) {
+ a = from;
+ aChar = fromChar;
+ b = to;
+ bChar = toChar;
+ } else {
+ a = to;
+ aChar = toChar;
+ b = from;
+ bChar = fromChar;
+ }
+
+ for (i = a->cloneDeepIterator (), start = true;
+ (cmp = i->compareTo (b)) <= 0;
+ i->next (), start = false) {
+ if (i->getContent()->type == Content::TEXT) {
+ if (fl) {
+ if (start) {
+ i->highlight (aChar, strlen (i->getContent()->text) + 1,
+ HIGHLIGHT_SELECTION);
+ } else if (cmp == 0) {
+ // the end
+ i->highlight (0, bChar, HIGHLIGHT_SELECTION);
+ } else {
+ i->highlight (0, strlen (i->getContent()->text) + 1,
+ HIGHLIGHT_SELECTION);
+ }
+ } else {
+ i->unhighlight (dir, HIGHLIGHT_SELECTION);
+ }
+ }
+ }
+ delete i;
+ }
+}
+
+void SelectionState::copy()
+{
+ if (from && to) {
+ Iterator *si;
+ DeepIterator *a, *b, *i;
+ int cmp, aChar, bChar;
+ bool start;
+ char *tmp;
+ misc::StringBuffer strbuf;
+
+ cmp = from->compareTo (to);
+ if (cmp == 0) {
+ if (from->getContent()->type == Content::TEXT) {
+ si = from->getTopIterator ();
+ if (fromChar < toChar)
+ tmp = strndup (si->getContent()->text + fromChar,
+ toChar - fromChar);
+ else
+ tmp = strndup (si->getContent()->text + toChar,
+ fromChar - toChar);
+ strbuf.appendNoCopy (tmp);
+ }
+ } else {
+ if (cmp < 0) {
+ a = from;
+ aChar = fromChar;
+ b = to;
+ bChar = toChar;
+ } else {
+ a = to;
+ aChar = toChar;
+ b = from;
+ bChar = fromChar;
+ }
+
+ for (i = a->cloneDeepIterator (), start = true;
+ (cmp = i->compareTo (b)) <= 0;
+ i->next (), start = false) {
+ si = i->getTopIterator ();
+ switch (si->getContent()->type) {
+ case Content::TEXT:
+ if (start) {
+ tmp = strndup (si->getContent()->text + aChar,
+ strlen (i->getContent()->text) - aChar);
+ strbuf.appendNoCopy (tmp);
+ } else if (cmp == 0) {
+ // the end
+ tmp = strndup (si->getContent()->text, bChar);
+ strbuf.appendNoCopy (tmp);
+ } else
+ strbuf.append (si->getContent()->text);
+
+ if (si->getContent()->space && cmp != 0)
+ strbuf.append (" ");
+
+ break;
+
+ case Content::BREAK:
+ if (si->getContent()->breakSpace > 0)
+ strbuf.append ("\n\n");
+ else
+ strbuf.append ("\n");
+ break;
+ default:
+ // Make pedantic compilers happy. Especially
+ // DW_CONTENT_WIDGET is never returned by a DwDeepIterator.
+ break;
+ }
+ }
+ delete i;
+ }
+
+ layout->copySelection(strbuf.getChars());
+ }
+}
+
+} // namespace dw
+} // namespace core