summaryrefslogtreecommitdiff
path: root/dw/selection.hh
blob: 7f6b1a5835201f941784b3a131a586c7cb465d01 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
#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__