/* This file is part of the KDE project Copyright (C) 2003-2005 Hamish Rodda Copyright (C) 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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. */ #ifndef KDELIBS_KTEXTEDITOR_SMARTRANGE_H #define KDELIBS_KTEXTEDITOR_SMARTRANGE_H #include #include #include #include #include #ifndef DOXYGEN_SHOULD_SKIP_THIS #include #endif class KAction; namespace KTextEditor { class SmartRangeNotifier; class SmartRangeWatcher; /** * \short A Range which is bound to a specific Document, and maintains its position. * * \ingroup kte_group_smart_classes * * A SmartRange is an extension of the basic Range class. It maintains its * position in the document and provides extra functionality, * including: * \li convenience functions for accessing and manipulating the content * of the associated document, * \li adjusting behavior in response to text edits, * \li forming a tree structure out of multiple SmartRange%s, * \li providing attribute information for the arbitrary highlighting extension, * \li allowing KAction%s to be bound to the range (note: not currently implemented), and * \li providing notification of changes to 3rd party software. * * As a result of a smart range's close association with a document, and the processing * that occurrs as a result, smart ranges may not be copied. * * For simplicity of code, ranges always maintain their start position to * be before or equal to their end position. Attempting to set either the * start or end of the range beyond the respective end or start will result in * both values being set to the specified position. * * Hierarchical range-trees maintain specific relationships: * - When a child-range is changed, all parent-ranges are resized so the child-range fits in * - When a parent-range is changed, all child-ranges are resized so they fit in * * This means that parent-ranges always completely contain all their child-ranges. However * it may lead to unexpected range-changes from the perspective of your application, so keep * this in mind. * * The child-ranges of one smart-range are allowed to overlap each other. However overlaps * should be omitted where possible, for performance-reasons, and because each range can * be overlapped by max. 63 sibling-ranges while still rendering correctly. * * Create a new SmartRange like this: * \code * // Retrieve the SmartInterface * KTextEditor::SmartInterface* smart = * qobject_cast( yourDocument ); * * if ( smart ) { * KTextEditor::SmartRange* range = smart->newSmartRange(); * } * \endcode * * When finished with a SmartRange, simply delete it. * * \sa Range, SmartRangeNotifier, SmartRangeWatcher, and SmartInterface * * \author Hamish Rodda \ */ class KTEXTEDITOR_EXPORT SmartRange : public Range { friend class SmartCursor; public: /// Determine how the range reacts to characters inserted immediately outside the range. enum InsertBehavior { /// Don't expand to encapsulate new characters in either direction. This is the default. DoNotExpand = 0, /// Expand to encapsulate new characters to the left of the range. ExpandLeft = 0x1, /// Expand to encapsulate new characters to the right of the range. ExpandRight = 0x2 }; Q_DECLARE_FLAGS(InsertBehaviors, InsertBehavior) virtual ~SmartRange(); /** * Returns that this range is a SmartRange. */ virtual bool isSmartRange() const; /** * Returns this range as a SmartRange, if it is one. */ virtual SmartRange* toSmartRange() const; /** * \name Position * * The following functions provide access and manipulation of the range's position. * \{ */ /** * \copydoc Range::setRange(const Range&) * * This function also provides any required adjustment of parent and child ranges, * and notification of the change if required. */ virtual void setRange(const Range& range); /** * Get the start point of this range. This version returns a casted * version of start(), as SmartRange%s always use SmartCursor%s as * the start() and end(). * * \returns a reference to the start of this range. */ inline SmartCursor& smartStart() { return *static_cast(m_start); } /** * Get the start point of this range. This version returns a casted * version of start(), as SmartRange%s always use SmartCursor%s as * the start() and end(). * * \returns a const reference to the start of this range. */ inline const SmartCursor& smartStart() const { return *static_cast(m_start); } /** * Get the end point of this range. This version returns a casted * version of end(), as SmartRange%s always use SmartCursor%s as * the start() and end(). * * \returns a reference to the end of this range. */ inline SmartCursor& smartEnd() { return *static_cast(m_end); } /** * Get the end point of this range. This version returns a casted * version of end(), as SmartRange%s always use SmartCursor%s as * the start() and end(). * * \returns a const reference to the end of this range. */ inline const SmartCursor& smartEnd() const { return *static_cast(m_end); } /** * \overload confineToRange(const Range&) * Overloaded version which confines child ranges as well. */ virtual bool confineToRange(const Range& range); /** * \overload expandToRange(const Range&) * Overloaded version which expands child ranges as well. */ virtual bool expandToRange(const Range& range); //BEGIN Functionality present from having this range associated with a Document /** * \} * * \name Document-related functions * * The following functions are provided for convenient access to the * associated Document. * \{ */ /** * Retrieve the document associated with this SmartRange. * * \return a pointer to the associated document */ Document* document() const; /** * Retrieve the text which is contained within this range. * * \param block specify whether the text should be returned from the range's visual block, rather * than all the text within the range. */ virtual QStringList text(bool block = false) const; /** * Replace text in this range with \p text * * \param text text to use as a replacement * \param block insert this text as a visual block of text rather than a linear sequence * \return \e true on success, otherwise \e false */ virtual bool replaceText(const QStringList &text, bool block = false); /** * Remove text contained within this range. * The range itself will not be deleted. * * \param block specify whether the text should be deleted from the range's visual block, rather * than all the text within the range. */ virtual bool removeText(bool block = false); //END //BEGIN Behavior /** * \} * * \name Behavior * * The following functions relate to the behavior of this SmartRange. * \{ */ /** * Returns how this range reacts to characters inserted immediately outside the range. * * \return the current insert behavior. */ InsertBehaviors insertBehavior() const; /** * Determine how the range should react to characters inserted immediately outside the range. * * \todo does this need a custom function to enable determining of the behavior based on the * text that is inserted / deleted? * * \param behavior the insertion behavior to use for future edits * * \sa InsertBehavior */ void setInsertBehavior(InsertBehaviors behavior); //END //BEGIN Relationships to other ranges /** * \} * * \name Tree structure * * The following functions relate to the tree structure functionality. * \{ */ /** * Returns this range's parent range, if one exists. * * At all times, this range will be contained within parentRange(). * * \return a pointer to the current parent range */ inline SmartRange* parentRange() const { return m_parentRange; } /** * Set this range's parent range. * * At all times, this range will be contained within parentRange(). So, if it is outside of the * new parent to begin with, it will be expanded automatically. * * When being inserted into the parent range, the parent range will be fit in between any other * pre-existing child ranges, and may resize them so as not to overlap. However, once insertion * has occurred, changing this range directly will only resize the others, it will \e not change * the order of the ranges. To change the order, unset the parent range, change the range, and * re-set the parent range. * * \param r range to become the new parent of this range */ virtual void setParentRange(SmartRange* r); /** * Determine whether \a parent is a parent of this range. * * \param parent range to check to see if it is a parent of this range. * * \return \e true if \a parent is in the parent heirachy, otherwise \e false. */ bool hasParent(SmartRange* parent) const; /** * Calculate the current depth of this range. * * \return the depth of this range, where 0 is no parent, 1 is one parent, etc. */ inline int depth() const { return m_parentRange ? m_parentRange->depth() + 1 : 0; } /** * Returns the range's top parent range, or this range if there are no parents. * * \return a pointer to the top parent range */ inline SmartRange* topParentRange() const { return parentRange() ? parentRange()->topParentRange() : const_cast(this); } /** * Get the ordered list of child ranges. * * To insert a child range, simply set its parent to this range using setParentRange(). * * \returns a list of child ranges. */ const QList& childRanges() const; /** * Clears child ranges - i.e., removes the text that is covered by the ranges. * The ranges themselves are not deleted. * * \sa removeText() */ void clearChildRanges(); /** * Deletes child ranges - i.e., deletes the SmartRange objects only. * The underlying text is not affected. */ void deleteChildRanges(); /** * Clears child ranges - i.e., clears the text that is covered by the ranges, * and deletes the SmartRange objects. */ void clearAndDeleteChildRanges(); /** * Find the child before \p range, if any. * The order is determined by the range end-cursors. * * \param range to seach backwards from * * \return the range before \p range if one exists, otherwise null. */ SmartRange* childBefore( const SmartRange * range ) const; /** * Find the child after \p range, if any. * The order is determined by the range end-cursors. * * \param range to seach forwards from * * \return the range after \p range if one exists, otherwise null. */ SmartRange* childAfter( const SmartRange * range ) const; /** * Finds the most specific range in a heirachy for the given input range * (ie. the smallest range which wholly contains the input range) * * In case of overlaps, the smallest containing range is chosen, * if there are multiple of the same size, then the first one. * * \param input the range to use in searching * * \return the deepest range which contains \p input */ SmartRange* mostSpecificRange(const Range& input) const; /** * Finds the first child range which contains position \p pos. * * \param pos the cursor position to use in searching * * \return the most shallow range (from and including this range) which * contains \p pos */ SmartRange* firstRangeContaining(const Cursor& pos) const; /** * Finds the deepest range in the heirachy which contains position \p pos. * Allows the caller to determine which ranges were entered and exited * by providing pointers to QStack. * * If child-ranges overlap in the given position, * the first smallest one is returned. * * \param pos the cursor position to use in searching * \param rangesEntered provide a QStack here to find out * which ranges were entered during the traversal. * The top item was the first descended. * \param rangesExited provide a QStack here to find out * which ranges were exited during the traversal. * The top item was the first exited. * * \return the deepest range (from and including this range) which * contains \p pos, or null if no ranges contain this position. */ SmartRange* deepestRangeContaining(const Cursor& pos, QStack* rangesEntered = 0L, QStack* rangesExited = 0L) const; QList deepestRangesContaining(const Cursor& pos) const; /** * Returns the count of ranges within the parent-range * that end behind this range, and that overlap this range. */ int overlapCount() const; //END //BEGIN Arbitrary highlighting /** * \} * * \name Arbitrary highlighting * * The following functions relate to arbitrary highlighting capabilities. * \{ */ /** * Gets the active Attribute for this range. * * \return a pointer to the active attribute */ Attribute::Ptr attribute() const; /** * Sets the currently active attribute for this range. * * \param attribute Attribute to assign to this range. If null, simply * removes the previous Attribute. * * \note \ref SmartInterface::addHighlightToDocument must be called with the top-range before * the highlighting can work. */ void setAttribute(Attribute::Ptr attribute); //END //BEGIN Action binding /** * \} * * \name Action binding * * The following functions relate to action binding capabilities. * * \note This feature is currently not implemented (ETA KDE 4.1). * \{ */ /** * Associate an action with this range. The associated action(s) will be * enabled when the caret enters the range, and disabled them on exit. * The action is also added to the context menu when the mouse/caret is within * an associated range. * * \param action KAction to associate with this range */ void associateAction(KAction* action); /** * Remove the association with an action from this range; it will no * longer be managed. * * \param action KAction to dissociate from this range */ void dissociateAction(KAction* action); /** * Access the list of currently associated KAction%s. * * \return the list of associated actions */ const QList& associatedActions() const { return m_associatedActions; } /** * Clears all associations between KAction%s and this range. */ void clearAssociatedActions(); //END //BEGIN Notification methods /** * \} * * \name Notification * * The following functions allow for changes related to this range to be * notified to 3rd party programs. * \{ */ /** * Connect to a notifier to receive signals indicating change of state of this range. * This function creates a notifier if none is already bound to this range; if one has * already been assigned this will return the first notifier. * * If you have finished with notifications for a reasonable period of time you can * save memory by calling deleteNotifier(). */ SmartRangeNotifier* primaryNotifier(); /** * Returns a list of notifiers which are receiving signals indicating change of state * of this range. These notifiers may be receiving signals from other ranges as well. */ const QList notifiers() const; /** * Register a notifier to receive signals indicating change of state of this range. * * NOTE: Make sure you call @c removeNotifier() when deleting the notifier before the range. * * \param notifier notifier to register. Ownership is not transferred. */ void addNotifier(SmartRangeNotifier* notifier); /** * Deregister a notifier and no longer deliver signals indicating change of state of this range. * * \param notifier notifier to deregister. */ void removeNotifier(SmartRangeNotifier* notifier); /** * When finished with the primaryNotifier(), call this method to save memory by * having the SmartRangeNotifier deleted. * * \note If a notifier was first registered via addNotifier() rather than created inside * primaryNotifier(), this method will delete that notifier. Text editor implementations * should not use notifiers for internal purposes, instead use watchers (faster and * has documentation to this effect) */ void deletePrimaryNotifier(); /** * Returns a list of registered SmartRangeWatchers. * * \note this function may return watchers internal to the text editor's implementation, * eg. in the case of arbitrary highlighting and kate part. Removing these watchers * with removeWatcher() will result in malfunction. */ const QList& watchers() const; /** * Register a SmartRangeWatcher to receive calls indicating change of state * of this range. To finish receiving notifications, call removeWatcher(). * * NOTE: Make sure you call @c removeWachter() when deleting the notifier before the range. * * \param watcher the instance of a class which is to receive * notifications about changes to this range. */ void addWatcher(SmartRangeWatcher* watcher); /** * Stop delivery of notifications to a SmartRangeWatcher. * * \param watcher the watcher that no longer wants notifications. */ void removeWatcher(SmartRangeWatcher* watcher); //!\} //END /** * Assignment operator. Assigns the current position of the provided range, \p rhs, only; * does not assign watchers, notifiers, behavior etc. * * \note The assignment will be performed even if the provided range belongs to * another Document. * * \param rhs range to assign to this range. * * \return a reference to this range, after assignment has occurred. * * \see setRange() */ inline SmartRange& operator=(const SmartRange& rhs) { setRange(rhs); return *this; } /** * Assignment operator. Assigns the current position of the provided range, \p rhs. * * \param rhs range to assign to this range. * * \return a reference to this range, after assignment has occurred. * * \see setRange() */ inline SmartRange& operator=(const Range& rhs) { setRange(rhs); return *this; } protected: /** * Constructor for subclasses to utilise. Protected to prevent direct * instantiation. * * \note 3rd party developers: you do not (and should not) need to subclass * the Smart* classes; instead, use the SmartInterface to create instances. * * \internal * * \param start the start cursor to use - ownership is taken * \param end the end cursor to use - ownership is taken * \param parent the parent range if this is a subrange of another range * \param insertBehavior the behavior of this range when an insert happens * immediately outside the range. */ SmartRange(SmartCursor* start, SmartCursor* end, SmartRange* parent = 0L, InsertBehaviors insertBehavior = DoNotExpand); /** * \internal * * Notify this range that one or both of the cursors' position has changed directly. * * \param cursor the cursor that changed. If null, both cursors have changed. * \param from the previous position of this range */ virtual void rangeChanged(Cursor* cursor, const Range& from); /** * \internal * * This routine is called when the range changes how much feedback it may need, eg. if it adds an action. */ virtual void checkFeedback(); /** * \internal * * Called to request creation of a new SmartRangeNotifier for this object. */ virtual SmartRangeNotifier* createNotifier() = 0; /** * Is called after child-ranges have changed internally without the rangeChanged() notification, for example * after translations. It rebuilds the child-structure, so it is consistent again. */ void rebuildChildStructure(); private: /** * \internal * Copy constructor: Disable copying of this class. */ SmartRange(const SmartRange&); /** * \internal * Implementation of deepestRangeContaining(). */ SmartRange* deepestRangeContainingInternal(const Cursor& pos, QStack* rangesEntered, QStack* rangesExited, bool first = false) const; /** * \internal * * New child classes call this to register themselves. */ void insertChildRange(SmartRange* newChild); /** * \internal * * Disassociating child classes call this to de-register themselves. */ void removeChildRange(SmartRange* newChild); /** * \internal * * This range's current attribute. */ Attribute::Ptr m_attribute; SmartRange* m_parentRange; /** * \internal * * The list of this range's child ranges, sorted by end-cursor. */ QList m_childRanges; /** * \internal * * The list of this range's associated KAction%s. */ QList m_associatedActions; /** * \internal * * The list of registered SmartRangeNotifiers. */ QList m_notifiers; /** * \internal * * The list of registered SmartRangeWatchers. */ QList m_watchers; /** * \internal * * Whether this range owns the currently assigned attribute or not. */ bool m_ownsAttribute :1; /** * \internal * * How many ranges that end behind this one at least partially overlap it. * Currently max. 64 overlaps are allowed. */ uchar m_overlapCount:6; }; Q_DECLARE_OPERATORS_FOR_FLAGS(SmartRange::InsertBehaviors) } #endif // kate: space-indent on; indent-width 2; replace-tabs on;