Initial import of Dillo
This commit is contained in:
502
dw/selection.cc
Normal file
502
dw/selection.cc
Normal file
@ -0,0 +1,502 @@
|
||||
/*
|
||||
* 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, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#include "core.hh"
|
||||
#include "../lout/debug.hh"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
using namespace lout;
|
||||
|
||||
/*
|
||||
* 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 ()
|
||||
{
|
||||
DBG_OBJ_CREATE ("dw::core::SelectionState");
|
||||
|
||||
layout = NULL;
|
||||
|
||||
selectionState = NONE;
|
||||
from = NULL;
|
||||
to = NULL;
|
||||
|
||||
linkState = LINK_NONE;
|
||||
link = NULL;
|
||||
}
|
||||
|
||||
SelectionState::~SelectionState ()
|
||||
{
|
||||
reset ();
|
||||
DBG_OBJ_DELETE ();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
DBG_IF_RTFL {
|
||||
misc::StringBuffer sb;
|
||||
it->intoStringBuffer (&sb);
|
||||
DBG_OBJ_ENTER ("events", 0, "buttonPress", "[%s], %d, %d, ...",
|
||||
sb.getChars (), charPos, linkNo);
|
||||
}
|
||||
|
||||
Widget *itWidget = it->getWidget ();
|
||||
bool ret = false;
|
||||
|
||||
if (event) {
|
||||
if (event->button == 3) {
|
||||
// menu popup
|
||||
layout->emitLinkPress (itWidget, linkNo, -1, -1, -1, event);
|
||||
ret = true;
|
||||
} else if (linkNo != -1) {
|
||||
// link handling
|
||||
(void) layout->emitLinkPress (itWidget, 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 if (event->button == 1) {
|
||||
// normal selection handling
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
DBG_OBJ_MSGF ("events", 1, "=> %s", ret ? "true" : "false");
|
||||
DBG_OBJ_LEAVE ();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool SelectionState::buttonRelease (Iterator *it, int charPos, int linkNo,
|
||||
EventButton *event)
|
||||
{
|
||||
DBG_IF_RTFL {
|
||||
misc::StringBuffer sb;
|
||||
it->intoStringBuffer (&sb);
|
||||
DBG_OBJ_ENTER ("events", 0, "buttonRelease", "[%s], %d, %d, ...",
|
||||
sb.getChars (), charPos, linkNo);
|
||||
}
|
||||
|
||||
Widget *itWidget = it->getWidget ();
|
||||
bool ret = false;
|
||||
|
||||
if (linkState == LINK_PRESSED && event && event->button == linkButton) {
|
||||
// link handling
|
||||
ret = true;
|
||||
if (linkNo != -1)
|
||||
(void) layout->emitLinkRelease (itWidget, linkNo, -1, -1, -1, event);
|
||||
|
||||
// The link where the user clicked the mouse button?
|
||||
if (linkNo == linkNumber) {
|
||||
resetLink ();
|
||||
(void) layout->emitLinkClick (itWidget, 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;
|
||||
}
|
||||
}
|
||||
|
||||
DBG_OBJ_MSGF ("events", 1, "=> %s", ret ? "true" : "false");
|
||||
DBG_OBJ_LEAVE ();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool SelectionState::buttonMotion (Iterator *it, int charPos, int linkNo,
|
||||
EventMotion *event)
|
||||
{
|
||||
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)
|
||||
{
|
||||
switch (eventType) {
|
||||
case BUTTON_PRESS:
|
||||
return buttonPress (it, charPos, linkNo, (EventButton*)event);
|
||||
|
||||
case BUTTON_RELEASE:
|
||||
return buttonRelease (it, charPos, linkNo, (EventButton*)event);
|
||||
|
||||
case BUTTON_MOTION:
|
||||
return buttonMotion (it, charPos, linkNo, (EventMotion*)event);
|
||||
|
||||
|
||||
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. Unhighlight 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 core
|
||||
} // namespace dw
|
||||
Reference in New Issue
Block a user