blob: d97f2af1bb7fc1fe516594218219aa162bf18c22 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2001 Dirk Mueller (mueller@kde.org)
* (C) 2006 Alexey Proskuryakov (ap@webkit.org)
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
* Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
* Copyright (C) Research In Motion Limited 2010. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "DocumentMarkerController.h"
#include "Node.h"
#include "Range.h"
#include "TextIterator.h"
namespace WebCore {
static IntRect placeholderRectForMarker()
{
return IntRect(-1, -1, -1, -1);
}
inline bool DocumentMarkerController::possiblyHasMarkers(DocumentMarker::MarkerTypes types)
{
return m_possiblyExistingMarkerTypes.intersects(types);
}
DocumentMarkerController::DocumentMarkerController()
: m_possiblyExistingMarkerTypes(0)
{
}
void DocumentMarkerController::detach()
{
m_possiblyExistingMarkerTypes = 0;
if (m_markers.isEmpty())
return;
deleteAllValues(m_markers);
m_markers.clear();
}
void DocumentMarkerController::addMarker(Range* range, DocumentMarker::MarkerType type, String description)
{
// Use a TextIterator to visit the potentially multiple nodes the range covers.
for (TextIterator markedText(range); !markedText.atEnd(); markedText.advance()) {
RefPtr<Range> textPiece = markedText.range();
int exception = 0;
DocumentMarker marker = {type, textPiece->startOffset(exception), textPiece->endOffset(exception), description, false};
addMarker(textPiece->startContainer(exception), marker);
}
}
void DocumentMarkerController::removeMarkers(Range* range, DocumentMarker::MarkerTypes markerTypes, RemovePartiallyOverlappingMarkerOrNot shouldRemovePartiallyOverlappingMarker)
{
for (TextIterator markedText(range); !markedText.atEnd(); markedText.advance()) {
if (!possiblyHasMarkers(markerTypes))
return;
ASSERT(!m_markers.isEmpty());
RefPtr<Range> textPiece = markedText.range();
int startOffset = textPiece->startOffset();
int endOffset = textPiece->endOffset();
removeMarkers(textPiece->startContainer(), startOffset, endOffset - startOffset, markerTypes, shouldRemovePartiallyOverlappingMarker);
}
}
// Markers are stored in order sorted by their start offset.
// Markers of the same type do not overlap each other.
void DocumentMarkerController::addMarker(Node* node, DocumentMarker newMarker)
{
ASSERT(newMarker.endOffset >= newMarker.startOffset);
if (newMarker.endOffset == newMarker.startOffset)
return;
m_possiblyExistingMarkerTypes.add(newMarker.type);
MarkerMapVectorPair* vectorPair = m_markers.get(node);
if (!vectorPair) {
vectorPair = new MarkerMapVectorPair;
vectorPair->first.append(newMarker);
vectorPair->second.append(placeholderRectForMarker());
m_markers.set(node, vectorPair);
} else {
Vector<DocumentMarker>& markers = vectorPair->first;
Vector<IntRect>& rects = vectorPair->second;
size_t numMarkers = markers.size();
ASSERT(numMarkers == rects.size());
size_t i;
// Iterate over all markers whose start offset is less than or equal to the new marker's.
// If one of them is of the same type as the new marker and touches it or intersects with it
// (there is at most one), remove it and adjust the new marker's start offset to encompass it.
for (i = 0; i < numMarkers; ++i) {
DocumentMarker marker = markers[i];
if (marker.startOffset > newMarker.startOffset)
break;
if (marker.type == newMarker.type && marker.endOffset >= newMarker.startOffset) {
newMarker.startOffset = marker.startOffset;
markers.remove(i);
rects.remove(i);
numMarkers--;
break;
}
}
size_t j = i;
// Iterate over all markers whose end offset is less than or equal to the new marker's,
// removing markers of the same type as the new marker which touch it or intersect with it,
// adjusting the new marker's end offset to cover them if necessary.
while (j < numMarkers) {
DocumentMarker marker = markers[j];
if (marker.startOffset > newMarker.endOffset)
break;
if (marker.type == newMarker.type) {
markers.remove(j);
rects.remove(j);
if (newMarker.endOffset <= marker.endOffset) {
newMarker.endOffset = marker.endOffset;
break;
}
numMarkers--;
} else
j++;
}
// At this point i points to the node before which we want to insert.
markers.insert(i, newMarker);
rects.insert(i, placeholderRectForMarker());
}
// repaint the affected node
if (node->renderer())
node->renderer()->repaint();
}
// copies markers from srcNode to dstNode, applying the specified shift delta to the copies. The shift is
// useful if, e.g., the caller has created the dstNode from a non-prefix substring of the srcNode.
void DocumentMarkerController::copyMarkers(Node* srcNode, unsigned startOffset, int length, Node* dstNode, int delta)
{
if (length <= 0)
return;
if (!possiblyHasMarkers(DocumentMarker::AllMarkers()))
return;
ASSERT(!m_markers.isEmpty());
MarkerMapVectorPair* vectorPair = m_markers.get(srcNode);
if (!vectorPair)
return;
ASSERT(vectorPair->first.size() == vectorPair->second.size());
bool docDirty = false;
unsigned endOffset = startOffset + length - 1;
Vector<DocumentMarker>& markers = vectorPair->first;
for (size_t i = 0; i != markers.size(); ++i) {
DocumentMarker marker = markers[i];
// stop if we are now past the specified range
if (marker.startOffset > endOffset)
break;
// skip marker that is before the specified range or is the wrong type
if (marker.endOffset < startOffset)
continue;
// pin the marker to the specified range and apply the shift delta
docDirty = true;
if (marker.startOffset < startOffset)
marker.startOffset = startOffset;
if (marker.endOffset > endOffset)
marker.endOffset = endOffset;
marker.startOffset += delta;
marker.endOffset += delta;
addMarker(dstNode, marker);
}
// repaint the affected node
if (docDirty && dstNode->renderer())
dstNode->renderer()->repaint();
}
void DocumentMarkerController::removeMarkers(Node* node, unsigned startOffset, int length, DocumentMarker::MarkerTypes markerTypes, RemovePartiallyOverlappingMarkerOrNot shouldRemovePartiallyOverlappingMarker)
{
if (length <= 0)
return;
if (!possiblyHasMarkers(markerTypes))
return;
ASSERT(!(m_markers.isEmpty()));
MarkerMapVectorPair* vectorPair = m_markers.get(node);
if (!vectorPair)
return;
Vector<DocumentMarker>& markers = vectorPair->first;
Vector<IntRect>& rects = vectorPair->second;
ASSERT(markers.size() == rects.size());
bool docDirty = false;
unsigned endOffset = startOffset + length;
for (size_t i = 0; i < markers.size();) {
DocumentMarker marker = markers[i];
// markers are returned in order, so stop if we are now past the specified range
if (marker.startOffset >= endOffset)
break;
// skip marker that is wrong type or before target
if (marker.endOffset <= startOffset || !markerTypes.contains(marker.type)) {
i++;
continue;
}
// at this point we know that marker and target intersect in some way
docDirty = true;
// pitch the old marker and any associated rect
markers.remove(i);
rects.remove(i);
if (shouldRemovePartiallyOverlappingMarker)
// Stop here. Don't add resulting slices back.
continue;
// add either of the resulting slices that are left after removing target
if (startOffset > marker.startOffset) {
DocumentMarker newLeft = marker;
newLeft.endOffset = startOffset;
markers.insert(i, newLeft);
rects.insert(i, placeholderRectForMarker());
// i now points to the newly-inserted node, but we want to skip that one
i++;
}
if (marker.endOffset > endOffset) {
DocumentMarker newRight = marker;
newRight.startOffset = endOffset;
markers.insert(i, newRight);
rects.insert(i, placeholderRectForMarker());
// i now points to the newly-inserted node, but we want to skip that one
i++;
}
}
if (markers.isEmpty()) {
ASSERT(rects.isEmpty());
m_markers.remove(node);
delete vectorPair;
}
if (m_markers.isEmpty())
m_possiblyExistingMarkerTypes = 0;
// repaint the affected node
if (docDirty && node->renderer())
node->renderer()->repaint();
}
DocumentMarker* DocumentMarkerController::markerContainingPoint(const IntPoint& point, DocumentMarker::MarkerType markerType)
{
if (!possiblyHasMarkers(markerType))
return 0;
ASSERT(!(m_markers.isEmpty()));
// outer loop: process each node that contains any markers
MarkerMap::iterator end = m_markers.end();
for (MarkerMap::iterator nodeIterator = m_markers.begin(); nodeIterator != end; ++nodeIterator) {
// inner loop; process each marker in this node
MarkerMapVectorPair* vectorPair = nodeIterator->second;
Vector<DocumentMarker>& markers = vectorPair->first;
Vector<IntRect>& rects = vectorPair->second;
ASSERT(markers.size() == rects.size());
unsigned markerCount = markers.size();
for (unsigned markerIndex = 0; markerIndex < markerCount; ++markerIndex) {
DocumentMarker& marker = markers[markerIndex];
// skip marker that is wrong type
if (marker.type != markerType)
continue;
IntRect& r = rects[markerIndex];
// skip placeholder rects
if (r == placeholderRectForMarker())
continue;
if (r.contains(point))
return &marker;
}
}
return 0;
}
Vector<DocumentMarker> DocumentMarkerController::markersForNode(Node* node)
{
MarkerMapVectorPair* vectorPair = m_markers.get(node);
if (vectorPair)
return vectorPair->first;
return Vector<DocumentMarker>();
}
Vector<DocumentMarker> DocumentMarkerController::markersInRange(Range* range, DocumentMarker::MarkerType markerType)
{
if (!possiblyHasMarkers(markerType))
return Vector<DocumentMarker>();
Vector<DocumentMarker> foundMarkers;
Node* startContainer = range->startContainer();
ASSERT(startContainer);
Node* endContainer = range->endContainer();
ASSERT(endContainer);
Node* pastLastNode = range->pastLastNode();
for (Node* node = range->firstNode(); node != pastLastNode; node = node->traverseNextNode()) {
Vector<DocumentMarker> markers = markersForNode(node);
Vector<DocumentMarker>::const_iterator end = markers.end();
for (Vector<DocumentMarker>::const_iterator it = markers.begin(); it != end; ++it) {
if (markerType != it->type)
continue;
if (node == startContainer && it->endOffset <= static_cast<unsigned>(range->startOffset()))
continue;
if (node == endContainer && it->startOffset >= static_cast<unsigned>(range->endOffset()))
continue;
foundMarkers.append(*it);
}
}
return foundMarkers;
}
Vector<IntRect> DocumentMarkerController::renderedRectsForMarkers(DocumentMarker::MarkerType markerType)
{
Vector<IntRect> result;
if (!possiblyHasMarkers(markerType))
return result;
ASSERT(!(m_markers.isEmpty()));
// outer loop: process each node
MarkerMap::iterator end = m_markers.end();
for (MarkerMap::iterator nodeIterator = m_markers.begin(); nodeIterator != end; ++nodeIterator) {
// inner loop; process each marker in this node
MarkerMapVectorPair* vectorPair = nodeIterator->second;
Vector<DocumentMarker>& markers = vectorPair->first;
Vector<IntRect>& rects = vectorPair->second;
ASSERT(markers.size() == rects.size());
unsigned markerCount = markers.size();
for (unsigned markerIndex = 0; markerIndex < markerCount; ++markerIndex) {
DocumentMarker marker = markers[markerIndex];
// skip marker that is wrong type
if (marker.type != markerType)
continue;
IntRect r = rects[markerIndex];
// skip placeholder rects
if (r == placeholderRectForMarker())
continue;
result.append(r);
}
}
return result;
}
void DocumentMarkerController::removeMarkers(Node* node, DocumentMarker::MarkerTypes markerTypes)
{
if (!possiblyHasMarkers(markerTypes))
return;
ASSERT(!m_markers.isEmpty());
MarkerMap::iterator iterator = m_markers.find(node);
if (iterator != m_markers.end())
removeMarkersFromMarkerMapVectorPair(node, iterator->second, markerTypes);
}
void DocumentMarkerController::removeMarkers(DocumentMarker::MarkerTypes markerTypes)
{
if (!possiblyHasMarkers(markerTypes))
return;
ASSERT(!m_markers.isEmpty());
// outer loop: process each markered node in the document
MarkerMap markerMapCopy = m_markers;
MarkerMap::iterator end = markerMapCopy.end();
for (MarkerMap::iterator i = markerMapCopy.begin(); i != end; ++i) {
Node* node = i->first.get();
MarkerMapVectorPair* vectorPair = i->second;
removeMarkersFromMarkerMapVectorPair(node, vectorPair, markerTypes);
}
m_possiblyExistingMarkerTypes.remove(markerTypes);
}
// This function may release node and vectorPair.
void DocumentMarkerController::removeMarkersFromMarkerMapVectorPair(Node* node, MarkerMapVectorPair* vectorPair, DocumentMarker::MarkerTypes markerTypes)
{
if (markerTypes == DocumentMarker::AllMarkers()) {
delete vectorPair;
m_markers.remove(node);
if (RenderObject* renderer = node->renderer())
renderer->repaint();
} else {
bool needsRepaint = false;
Vector<DocumentMarker>& markers = vectorPair->first;
Vector<IntRect>& rects = vectorPair->second;
ASSERT(markers.size() == rects.size());
for (size_t i = 0; i != markers.size();) {
DocumentMarker marker = markers[i];
// skip nodes that are not of the specified type
if (!markerTypes.contains(marker.type)) {
++i;
continue;
}
// pitch the old marker
markers.remove(i);
rects.remove(i);
needsRepaint = true;
// i now is the index of the next marker
}
// Redraw the node if it changed. Do this before the node is removed from m_markers, since
// m_markers might contain the last reference to the node.
if (needsRepaint) {
RenderObject* renderer = node->renderer();
if (renderer)
renderer->repaint();
}
// delete the node's list if it is now empty
if (markers.isEmpty()) {
ASSERT(rects.isEmpty());
m_markers.remove(node);
delete vectorPair;
}
}
if (m_markers.isEmpty())
m_possiblyExistingMarkerTypes = 0;
}
void DocumentMarkerController::repaintMarkers(DocumentMarker::MarkerTypes markerTypes)
{
if (!possiblyHasMarkers(markerTypes))
return;
ASSERT(!m_markers.isEmpty());
// outer loop: process each markered node in the document
MarkerMap::iterator end = m_markers.end();
for (MarkerMap::iterator i = m_markers.begin(); i != end; ++i) {
Node* node = i->first.get();
// inner loop: process each marker in the current node
MarkerMapVectorPair* vectorPair = i->second;
Vector<DocumentMarker>& markers = vectorPair->first;
bool nodeNeedsRepaint = false;
for (size_t i = 0; i != markers.size(); ++i) {
DocumentMarker marker = markers[i];
// skip nodes that are not of the specified type
if (markerTypes.contains(marker.type)) {
nodeNeedsRepaint = true;
break;
}
}
if (!nodeNeedsRepaint)
continue;
// cause the node to be redrawn
if (RenderObject* renderer = node->renderer())
renderer->repaint();
}
}
void DocumentMarkerController::setRenderedRectForMarker(Node* node, const DocumentMarker& marker, const IntRect& r)
{
MarkerMapVectorPair* vectorPair = m_markers.get(node);
if (!vectorPair) {
ASSERT_NOT_REACHED(); // shouldn't be trying to set the rect for a marker we don't already know about
return;
}
Vector<DocumentMarker>& markers = vectorPair->first;
ASSERT(markers.size() == vectorPair->second.size());
unsigned markerCount = markers.size();
for (unsigned markerIndex = 0; markerIndex < markerCount; ++markerIndex) {
DocumentMarker m = markers[markerIndex];
if (m == marker) {
vectorPair->second[markerIndex] = r;
return;
}
}
ASSERT_NOT_REACHED(); // shouldn't be trying to set the rect for a marker we don't already know about
}
void DocumentMarkerController::invalidateRenderedRectsForMarkersInRect(const IntRect& r)
{
// outer loop: process each markered node in the document
MarkerMap::iterator end = m_markers.end();
for (MarkerMap::iterator i = m_markers.begin(); i != end; ++i) {
// inner loop: process each rect in the current node
MarkerMapVectorPair* vectorPair = i->second;
Vector<IntRect>& rects = vectorPair->second;
unsigned rectCount = rects.size();
for (unsigned rectIndex = 0; rectIndex < rectCount; ++rectIndex)
if (rects[rectIndex].intersects(r))
rects[rectIndex] = placeholderRectForMarker();
}
}
void DocumentMarkerController::shiftMarkers(Node* node, unsigned startOffset, int delta)
{
if (!possiblyHasMarkers(DocumentMarker::AllMarkers()))
return;
ASSERT(!m_markers.isEmpty());
MarkerMapVectorPair* vectorPair = m_markers.get(node);
if (!vectorPair)
return;
Vector<DocumentMarker>& markers = vectorPair->first;
Vector<IntRect>& rects = vectorPair->second;
ASSERT(markers.size() == rects.size());
bool docDirty = false;
for (size_t i = 0; i != markers.size(); ++i) {
DocumentMarker& marker = markers[i];
if (marker.startOffset >= startOffset) {
ASSERT((int)marker.startOffset + delta >= 0);
marker.startOffset += delta;
marker.endOffset += delta;
docDirty = true;
// Marker moved, so previously-computed rendered rectangle is now invalid
rects[i] = placeholderRectForMarker();
}
}
// repaint the affected node
if (docDirty && node->renderer())
node->renderer()->repaint();
}
void DocumentMarkerController::setMarkersActive(Range* range, bool active)
{
if (!possiblyHasMarkers(DocumentMarker::AllMarkers()))
return;
ASSERT(!m_markers.isEmpty());
ExceptionCode ec = 0;
Node* startContainer = range->startContainer(ec);
Node* endContainer = range->endContainer(ec);
Node* pastLastNode = range->pastLastNode();
for (Node* node = range->firstNode(); node != pastLastNode; node = node->traverseNextNode()) {
int startOffset = node == startContainer ? range->startOffset(ec) : 0;
int endOffset = node == endContainer ? range->endOffset(ec) : INT_MAX;
setMarkersActive(node, startOffset, endOffset, active);
}
}
void DocumentMarkerController::setMarkersActive(Node* node, unsigned startOffset, unsigned endOffset, bool active)
{
MarkerMapVectorPair* vectorPair = m_markers.get(node);
if (!vectorPair)
return;
Vector<DocumentMarker>& markers = vectorPair->first;
ASSERT(markers.size() == vectorPair->second.size());
bool docDirty = false;
for (size_t i = 0; i != markers.size(); ++i) {
DocumentMarker& marker = markers[i];
// Markers are returned in order, so stop if we are now past the specified range.
if (marker.startOffset >= endOffset)
break;
// Skip marker that is wrong type or before target.
if (marker.endOffset < startOffset || marker.type != DocumentMarker::TextMatch)
continue;
marker.activeMatch = active;
docDirty = true;
}
// repaint the affected node
if (docDirty && node->renderer())
node->renderer()->repaint();
}
bool DocumentMarkerController::hasMarkers(Range* range, DocumentMarker::MarkerTypes markerTypes)
{
if (!possiblyHasMarkers(markerTypes))
return false;
ASSERT(!m_markers.isEmpty());
Node* startContainer = range->startContainer();
ASSERT(startContainer);
Node* endContainer = range->endContainer();
ASSERT(endContainer);
Node* pastLastNode = range->pastLastNode();
for (Node* node = range->firstNode(); node != pastLastNode; node = node->traverseNextNode()) {
Vector<DocumentMarker> markers = markersForNode(node);
Vector<DocumentMarker>::const_iterator end = markers.end();
for (Vector<DocumentMarker>::const_iterator it = markers.begin(); it != end; ++it) {
if (!markerTypes.contains(it->type))
continue;
if (node == startContainer && it->endOffset <= static_cast<unsigned>(range->startOffset()))
continue;
if (node == endContainer && it->startOffset >= static_cast<unsigned>(range->endOffset()))
continue;
return true;
}
}
return false;
}
void DocumentMarkerController::clearDescriptionOnMarkersIntersectingRange(Range* range, DocumentMarker::MarkerTypes markerTypes)
{
if (!possiblyHasMarkers(markerTypes))
return;
ASSERT(!m_markers.isEmpty());
Node* startContainer = range->startContainer();
Node* endContainer = range->endContainer();
Node* pastLastNode = range->pastLastNode();
for (Node* node = range->firstNode(); node != pastLastNode; node = node->traverseNextNode()) {
unsigned startOffset = node == startContainer ? range->startOffset() : 0;
unsigned endOffset = node == endContainer ? static_cast<unsigned>(range->endOffset()) : std::numeric_limits<unsigned>::max();
MarkerMapVectorPair* vectorPair = m_markers.get(node);
if (!vectorPair)
continue;
Vector<DocumentMarker>& markers = vectorPair->first;
for (size_t i = 0; i < markers.size(); ++i) {
DocumentMarker& marker = markers[i];
// markers are returned in order, so stop if we are now past the specified range
if (marker.startOffset >= endOffset)
break;
// skip marker that is wrong type or before target
if (marker.endOffset <= startOffset || !markerTypes.contains(marker.type)) {
i++;
continue;
}
marker.description = String();
}
}
}
#ifndef NDEBUG
void DocumentMarkerController::showMarkers() const
{
fprintf(stderr, "%d nodes have markers:\n", m_markers.size());
MarkerMap::const_iterator end = m_markers.end();
for (MarkerMap::const_iterator nodeIterator = m_markers.begin(); nodeIterator != end; ++nodeIterator) {
Node* node = nodeIterator->first.get();
fprintf(stderr, "%p", node);
MarkerMapVectorPair* vectorPair = nodeIterator->second;
Vector<DocumentMarker>& markers = vectorPair->first;
unsigned markerCount = markers.size();
for (unsigned markerIndex = 0; markerIndex < markerCount; ++markerIndex)
fprintf(stderr, " %d:[%d:%d](%d)", markers[markerIndex].type, markers[markerIndex].startOffset, markers[markerIndex].endOffset, markers[markerIndex].activeMatch);
fprintf(stderr, "\n");
}
}
#endif
} // namespace WebCore
#ifndef NDEBUG
void showDocumentMarkers(const WebCore::DocumentMarkerController* controller)
{
if (controller)
controller->showMarkers();
}
#endif