Codebase list dillo / debian/3_hg20110824-1 dw / selection.hh
debian/3_hg20110824-1

Tree @debian/3_hg20110824-1 (Download .tar.gz)

selection.hh @debian/3_hg20110824-1raw · history · blame

#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 {

/**
 * \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::Layout::LinkReceiver),
 *                                          otherwise -1
 * <tr><td>dw::core::EventButton *event <td>the event itself; only the button
 *                                          is used
 * </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::Layout::LinkReceiver::press,
 * dw::core::Layout::LinkReceiver::release and
 * dw::core::Layout::LinkReceiver::click (but not
 * dw::core::Layout::LinkReceiver::enter) are emitted by these methods, so
 * that widgets which let dw::core::SelectionState handle links, should only
 * emit dw::core::Layout::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 };

private:
   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 ();
   bool buttonPress (Iterator *it, int charPos, int linkNo,
                     EventButton *event);
   bool buttonRelease (Iterator *it, int charPos, int linkNo,
                       EventButton *event);
   bool buttonMotion (Iterator *it, int charPos, int linkNo,
                      EventMotion *event);

   bool handleEvent (EventType eventType, Iterator *it, int charPos,
                     int linkNo, MousePositionEvent *event);
};

} // namespace dw
} // namespace core

#endif // __DW_SELECTION_H__