diff options
Diffstat (limited to 'dw/selection.hh')
-rw-r--r-- | dw/selection.hh | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/dw/selection.hh b/dw/selection.hh new file mode 100644 index 00000000..9cc8d25f --- /dev/null +++ b/dw/selection.hh @@ -0,0 +1,275 @@ +#ifndef __DW_SELECTION_H__ +#define __DW_SELECTION_H__ + +#ifndef __INCLUDED_FROM_DW_CORE_HH__ +# error Do not include this file directly, use "core.hh" instead. +#endif + +namespace dw { +namespace core { + +using namespace lout; + +/** + * \brief This class handles selections, as well as activation of links, + * which is closely related. + * + * <h3>General Overview</h3> + * + * dw::core::SelectionState is associated with dw::core::Layout. The selection + * state is controlled by "abstract events", which are sent by single + * widgets by calling one of the following methods: + * + * <ul> + * <li> dw::core::SelectionState::buttonPress for button press events, + * <li> dw::core::SelectionState::buttonRelease for button release events, and + * <li> dw::core::SelectionState::buttonMotion for motion events (with pressed + * mouse button). + * </ul> + * + * The widget must construct simple iterators (dw::core::Iterator), which will + * be transferred to deep iterators (dw::core::DeepIterator), see below for + * more details. All event handling methods have the same signature, the + * arguments in detail are: + * + * <table> + * <tr><td>dw::core::Iterator *it <td>the iterator pointing on the item + * under the mouse pointer; this + * iterator \em must be created with + * dw::core::Content::SELECTION_CONTENT + * as mask + * <tr><td>int charPos <td>the exact (character) position + * within the iterator, + * <tr><td>int linkNo <td>if this item is associated with a + * link, its number (see + * dw::core::Widget::LinkReceiver), + * otherwise -1 + * <tr><td>dw::core::EventButton *event <td>the event itself; only the button + * is used + * <tr><td>bool withinContent <td>true, if there is some selectable + * content unter the mouse cursor; if + * set to false, the "full screen" + * feature is used on double click. + * </table> + * + * Look also at dw::core::SelectionState::handleEvent, which may be useful + * in some circumstances. + * + * In some cases, \em charPos would be difficult to determine. E.g., when + * the dw::Textblock widget decides that the user is pointing on a position + * <i>at the end</i> of an image (DwImage), it constructs a simple iterator + * pointing on this image widget. In a simple iterator, that fact that + * the pointer is at the end, would be represented by \em charPos == 1. But + * when transferring this simple iterator into an deep iterator, this + * simple iterator is discarded and instead the stack has an iterator + * pointing to text at the top. As a result, only the first letter of the + * ALT text would be copied. + * + * To avoid this problem, widgets should in this case pass + * dw::core::SelectionState::END_OF_WORD as \em charPos, which is then + * automatically reduced to the actual length of the deep(!) iterator. + * + * The return value is the same as in DwWidget event handling methods. + * I.e., in most cases, they should simply return it. The events + * dw::core::Widget::LinkReceiver::press, + * dw::core::Widget::LinkReceiver::release and + * dw::core::Widget::LinkReceiver::click (but not + * dw::core::Widget::LinkReceiver::enter) are emitted by these methods, so + * that widgets which let dw::core::SelectionState handle links, should only + * emit dw::core::Widget::LinkReceiver::enter for themselves. + * + * <h3>Selection State</h3> + * + * Selection interferes with handling the activation of links, so the + * latter is also handled by the dw::core::SelectionState. Details are based on + * following guidelines: + * + * <ol> + * <li> It should be simple to select links and to start selection in + * links. The rule to distinguish between link activation and + * selection is that the selection starts as soon as the user leaves + * the link. (This is, IMO, a useful feature. Even after drag and + * drop has been implemented in dillo, this should be somehow + * preserved.) + * + * <li> The selection should stay as long as possible, i.e., the old + * selection is only cleared when a new selection is started. + * </ol> + * + * The latter leads to a model with two states: the selection state and + * the link handling state. + * + * The general selection works, for events not pointing on links, like + * this (numbers in parantheses after the event denote the button, "n" + * means arbitrary button): + * + * \dot + * digraph G { + * node [shape=ellipse, fontname=Helvetica, fontsize=10]; + * edge [arrowhead="open", labelfontname=Helvetica, labelfontsize=10, + * color="#404040", labelfontcolor="#000080", + * fontname=Helvetica, fontsize=10, fontcolor="#000080"]; + * fontname=Helvetica; fontsize=10; + * + * NONE; + * SELECTING; + * q [label="Anything selected?", shape=plaintext]; + * SELECTED; + * + * NONE -> SELECTING [label="press(1)\non non-link"]; + * SELECTING -> SELECTING [label="motion(1)"]; + * SELECTING -> q [label="release(1)"]; + * q -> SELECTED [label="yes"]; + * q -> NONE [label="no"]; + * SELECTED -> SELECTING [label="press(1)"]; + * + * } + * \enddot + * + * The selected region is represented by two instances of + * dw::core::DeepIterator. + * + * Links are handled by a different state machine: + * + * \dot + * digraph G { + * node [shape=ellipse, fontname=Helvetica, fontsize=10]; + * edge [arrowhead="open", labelfontname=Helvetica, labelfontsize=10, + * color="#404040", labelfontcolor="#000080", + * fontname=Helvetica, fontsize=10, fontcolor="#000080"]; + * fontname=Helvetica; fontsize=10; + * + * LINK_NONE; + * LINK_PRESSED; + * click [label="Emit \"click\" signal.", shape=record]; + * q11 [label="Still the same link?", shape=plaintext]; + * q21 [label="Still the same link?", shape=plaintext]; + * q22 [label="n == 1?", shape=plaintext]; + * SELECTED [label="Switch selection\nto SELECTED", shape=record]; + * q12 [label="n == 1?", shape=plaintext]; + * SELECTING [label="Switch selection\nto SELECTING", shape=record]; + * + * LINK_NONE -> LINK_PRESSED [label="press(n)\non link"]; + * LINK_PRESSED -> q11 [label="motion(n)"]; + * q11 -> LINK_PRESSED [label="yes"]; + * q11 -> q12 [label="no"]; + * q12 -> SELECTING [label="yes"]; + * SELECTING -> LINK_NONE; + * q12 -> LINK_NONE [label="no"]; + * LINK_PRESSED -> q21 [label="release(n)"]; + * q21 -> click [label="yes"]; + * click -> LINK_NONE; + * q21 -> q22 [label="no"]; + * q22 -> SELECTED [label="yes"]; + * SELECTED -> LINK_NONE; + * q22 -> LINK_NONE [label="no"]; + * } + * \enddot + * + * Switching selection simply means that the selection state will + * eventually be SELECTED/SELECTING, with the original and the current + * position making up the selection region. This happens for button 1, + * events with buttons other than 1 do not affect selection at all. + * + * + * \todo dw::core::SelectionState::buttonMotion currently always assumes + * that button 1 has been pressed (since otherwise it would not do + * anything). This should be made a bit cleaner. + * + * \todo The selection should be cleared, when the user selects something + * somewhere else (perhaps switched into "non-active" mode, as e.g. Gtk+ + * does). + * + */ +class SelectionState +{ +public: + enum { END_OF_WORD = 1 << 30 }; + + class DoubleClickReceiver: public lout::signal::Receiver + { + public: + virtual void doubleClick () = 0; + }; + +private: + class DoubleClickEmitter: public lout::signal::Emitter + { + private: + enum { DOUBLE_CLICK }; + + protected: + bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo, + int argc, Object **argv); + + public: + inline void connectDoubleClick (DoubleClickReceiver *receiver) + { connect (receiver); } + + inline void emitDoubleClick () { emitVoid (DOUBLE_CLICK, 0, NULL); } + }; + + DoubleClickEmitter doubleClickEmitter; + + Layout *layout; + + // selection + enum { + NONE, + SELECTING, + SELECTED + } selectionState; + + DeepIterator *from, *to; + int fromChar, toChar; + + // link handling + enum { + LINK_NONE, + LINK_PRESSED + } linkState; + + int linkButton; + DeepIterator *link; + int linkChar, linkNumber; + + void resetSelection (); + void resetLink (); + void switchLinkToSelection (Iterator *it, int charPos); + void adjustSelection (Iterator *it, int charPos); + static int correctCharPos (DeepIterator *it, int charPos); + + void highlight (bool fl, int dir) + { highlight0 (fl, from, fromChar, to, toChar, dir); } + + void highlight0 (bool fl, DeepIterator *from, int fromChar, + DeepIterator *to, int toChar, int dir); + void copy (); + +public: + enum EventType { BUTTON_PRESS, BUTTON_RELEASE, BUTTON_MOTION }; + + SelectionState (); + ~SelectionState (); + + inline void setLayout (Layout *layout) { this->layout = layout; } + void reset (); + inline void connectDoubleClick (DoubleClickReceiver *receiver) + { doubleClickEmitter.connectDoubleClick (receiver); } + + bool buttonPress (Iterator *it, int charPos, int linkNo, + EventButton *event, bool withinContent); + bool buttonRelease (Iterator *it, int charPos, int linkNo, + EventButton *event, bool withinContent); + bool buttonMotion (Iterator *it, int charPos, int linkNo, + EventMotion *event, bool withinContent); + + bool handleEvent (EventType eventType, Iterator *it, int charPos, + int linkNo, MousePositionEvent *event, + bool withinContent); +}; + +} // namespace dw +} // namespace core + +#endif // __DW_SELECTION_H__ |