source: branches/stable/WINDOW/AW_device_click.cxx

Last change on this file was 16961, checked in by westram, 6 years ago
  • partial merge from 'fix' into 'trunk'
    • refactored AW_device text output
      • reduces calls to strlen (using SizedCstr)
      • eliminated/modernized several parameters/functions (esp. in TextOverlayCallbacks)
  • adds: log:branches/fix@16939:16960
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 10.0 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : AW_device_click.cxx                               //
4//   Purpose   : Detect which graphical element is "nearby"        //
5//               a given mouse position                            //
6//                                                                 //
7//   Institute of Microbiology (Technical University Munich)       //
8//   http://www.arb-home.de/                                       //
9//                                                                 //
10// =============================================================== //
11
12#include "aw_common.hxx"
13#include "aw_device_click.hxx"
14#include <algorithm>
15
16using namespace AW;
17
18// ------------------------
19//      AW_device_click
20
21AW_device_click::AW_device_click(AW_common *common_)
22        : AW_simple_device(common_)
23{
24    init_click(Origin, AWT_NO_CATCH, AW_ALL_DEVICES);
25}
26
27void AW_device_click::init_click(const AW::Position& click, int max_distance, AW_bitset filteri) {
28    mouse  = click;
29    filter = filteri;
30
31    max_distance_line = max_distance;
32    max_distance_text = max_distance;
33
34    opt_line    = AW_clicked_line();
35    opt_text    = AW_clicked_text();
36    opt_box     = AW_clicked_box();
37    opt_polygon = AW_clicked_polygon();
38}
39
40AW_DEVICE_TYPE AW_device_click::type() {
41    return AW_DEVICE_CLICK;
42}
43
44bool AW_device_click::line_impl(int /*gc*/, const AW::LineVector& Line, AW_bitset filteri) {
45    if (!(filteri & filter)) return false; // needed for motif only?
46
47    LineVector transLine = transform(Line);
48    LineVector clippedLine;
49    bool       drawflag  = clip(transLine, clippedLine);
50    if (drawflag) {
51        double   nearest_rel_pos;
52        Position nearest  = nearest_linepoint(mouse, clippedLine, nearest_rel_pos);
53        double   distance = Distance(mouse, nearest);
54
55        if (distance < max_distance_line) {
56            max_distance_line = distance;
57            opt_line.assign(Line, distance, nearest_rel_pos, click_cd);
58        }
59    }
60    return drawflag;
61}
62
63bool AW_device_click::box_impl(int gc, AW::FillStyle filled, const AW::Rectangle& rect, AW_bitset filteri) {
64    if (!(filteri & filter)) return false; // needed for motif only?
65
66    int dist = -1;
67    if (filled.is_empty()) {
68        LocallyModify<AW_clicked_line> saveLine(opt_line, AW_clicked_line());
69        LocallyModify<int>             saveDist(max_distance_line);
70
71        if (!generic_box(gc, rect, filteri)) return false;
72        if (!opt_line.does_exist()) return true; // no click near any borderline detected
73
74        dist = opt_line.get_distance();
75    }
76    else {
77        Rectangle transRect = transform(rect);
78        if (!transRect.contains(mouse)) {
79            return box_impl(gc, FillStyle::EMPTY, rect, filteri); // otherwise use min. dist to box frame
80        }
81        dist = 0; // if inside rect -> use zero distance
82    }
83
84    aw_assert(dist != -1);
85    if (!opt_box.does_exist() || dist<opt_box.get_distance()) {
86        opt_box.assign(rect, dist, 0.0, click_cd);
87    }
88    return true;
89}
90
91inline double kpt2(const Position& a, const Position& b, const Position& c) {
92    if (a.xpos() == b.xpos() && a.ypos() == b.ypos()) return 0;
93    if (a.ypos() <= b.ypos() || a.ypos() >  c.ypos()) return 1;
94
95    double d = (b.xpos()-a.xpos()) * (c.ypos()-a.ypos()) - (b.ypos()-a.ypos()) * (c.xpos()-a.xpos());
96    return d>0 ? -1 : (d<0 ? 1 : 0);
97}
98inline double KreuzProdTest(const Position& a, const Position& b, const Position& c) {
99    if (a.ypos() == b.ypos() && b.ypos() == c.ypos()) {
100        if (is_between(b.xpos(), a.xpos(), c.xpos())) return 0;
101        return 1;
102    }
103    return (b.ypos()>c.ypos()) ? kpt2(a, c, b) : kpt2(a, b, c);
104}
105static bool polygon_contains(const Position& mouse, int npos, const Position *pos) {
106    // Jordan test for "Position inside polygon?" (see https://de.wikipedia.org/wiki/Punkt-in-Polygon-Test_nach_Jordan)
107    double t = -1 * KreuzProdTest(mouse, pos[npos-1], pos[0]);
108    for (int i = 1; i<npos; ++i) {
109        t = t*KreuzProdTest(mouse, pos[i-1], pos[i]);
110    }
111    return t>=0;
112}
113
114bool AW_device_click::polygon_impl(int gc, AW::FillStyle filled, int npos, const AW::Position *pos, AW_bitset filteri) {
115    if (!(filteri & filter)) return false; // needed for motif only?
116
117    int dist = -1;
118    aw_assert(npos>2);
119
120    if (filled.is_empty()) {
121        LocallyModify<AW_clicked_line> saveLine(opt_line, AW_clicked_line());
122        LocallyModify<int>             saveDist(max_distance_line);
123
124        if (!generic_polygon(gc, npos, pos, filteri)) return false;
125        if (!opt_line.does_exist()) return true; // no click near any borderline detected
126
127        dist = opt_line.get_distance();
128    }
129    else {
130        bool inside;
131        {
132            AW::Position *tpos = new AW::Position[npos];
133            for (int i = 0; i<npos; ++i) {
134                tpos[i] = transform(pos[i]);
135            }
136            inside = polygon_contains(mouse, npos, tpos);
137            delete [] tpos;
138        }
139
140        if (!inside) {
141            return polygon_impl(gc, FillStyle::EMPTY, npos, pos, filteri);
142        }
143        dist = 0; // if inside polygon -> use zero distance
144    }
145
146    aw_assert(dist != -1);
147    if (!opt_polygon.does_exist() || dist<opt_polygon.get_distance()) {
148        opt_polygon.assign(npos, pos, dist, 0.0, click_cd);
149    }
150    return true;
151}
152
153bool AW_device_click::text_impl(int gc, const SizedCstr& cstr, const AW::Position& pos, AW_pos alignment, AW_bitset filteri) {
154    if (!(filteri & filter)) return false;
155
156    AW_pos X0, Y0;          // Transformed pos
157    this->transform(pos.xpos(), pos.ypos(), X0, Y0);
158
159    const AW_GC           *gcm  = get_common()->map_gc(gc);
160    const AW_font_limits&  font = gcm->get_font_limits();
161
162    AW_pos Y1 = Y0+font.descent;
163    Y0        = Y0-font.ascent;
164
165    // Fast check text against top/bottom clip
166    const AW_screen_area& clipRect = get_cliprect();
167    if (clipRect.t == 0) {
168        if (Y1 < clipRect.t) return false;
169    }
170    else {
171        if (Y0 < clipRect.t) return false;
172    }
173
174    if (clipRect.b == get_common()->get_screen().b) {
175        if (Y0 > clipRect.b) return false;
176    }
177    else {
178        if (Y1 > clipRect.b) return false;
179    }
180
181    // vertical check mouse against textsurrounding
182    int  dist2text = 0; // exact hit -> distance == 0
183
184    // vertical check against textborders
185    if (mouse.ypos() > Y1) { // above text
186        int ydist = mouse.ypos()-Y1;
187        if (ydist > max_distance_text) return false; // too far above
188        dist2text = ydist;
189    }
190    else if (mouse.ypos() < Y0) { // below text
191        int ydist = Y0-mouse.ypos();
192        if (ydist > max_distance_text) return false; // too far below
193        dist2text = ydist;
194    }
195
196    // align text
197    int text_width = gcm->get_string_size(cstr);
198
199    X0        = x_alignment(X0, text_width, alignment);
200    AW_pos X1 = X0+text_width;
201
202    // check against left/right clipping areas
203    if (X1 < clipRect.l) return false;
204    if (X0 > clipRect.r) return false;
205
206    // horizontal check against textborders
207    if (mouse.xpos() > X1) { // right of text
208        int xdist = mouse.xpos()-X1;
209        if (xdist > max_distance_text) return false; // too far right
210        dist2text = std::max(xdist, dist2text);
211    }
212    else if (mouse.xpos() < X0) { // left of text
213        int xdist = X0-mouse.xpos();
214        if (xdist > max_distance_text) return false; // too far left
215        dist2text = std::max(xdist, dist2text);
216    }
217
218    max_distance_text = dist2text; // exact hit -> distance = 0
219
220    if (!opt_text.does_exist() ||            // first candidate
221        (opt_text.get_distance()>dist2text)) // previous candidate had greater distance to click
222    {
223        Rectangle textArea(LineVector(X0, Y0, X1, Y1));
224
225        LineVector orientation = textArea.bigger_extent();
226        LineVector clippedOrientation;
227
228        bool visible = clip(orientation, clippedOrientation);
229        if (visible) {
230            double   nearest_rel_pos;
231            Position nearest = nearest_linepoint(mouse, clippedOrientation, nearest_rel_pos);
232
233            opt_text.assign(rtransform(textArea), max_distance_text, nearest_rel_pos, click_cd);
234        }
235    }
236    return true;
237}
238
239const AW_clicked_element *AW_device_click::best_click(ClickPreference prefer) {
240    // returns the element with lower distance (to mouse-click- or key-"click"-position).
241    // or NULp (if no element was found inside catch-distance)
242    //
243    // Note: during drag/drop the target element at mouse position
244    //       is only updated if requested using AWT_graphic::drag_target_detection
245    // see ../AWT/AWT_canvas.cxx@motion_event
246
247    const AW_clicked_element *bestClick = NULp;
248
249    if (prefer == PREFER_LINE && opt_line.does_exist()) bestClick = &opt_line;
250    if (prefer == PREFER_TEXT && opt_text.does_exist()) bestClick = &opt_text;
251
252    if (!bestClick) {
253        const AW_clicked_element *maybeClicked[] = {
254            // earlier elements are preferred over later elements
255            &opt_polygon,
256            &opt_box,
257            &opt_line,
258            &opt_text,
259        };
260
261        for (size_t i = 0; i<ARRAY_ELEMS(maybeClicked); ++i) {
262            if (maybeClicked[i]->does_exist()) {
263                if (!bestClick || maybeClicked[i]->get_distance()<bestClick->get_distance()) {
264                    bestClick = maybeClicked[i];
265                }
266            }
267        }
268    }
269
270    return bestClick;
271}
272
273AW::Rectangle AW_clicked_polygon::get_bounding_box() const {
274    Rectangle box = bounding_box(pos[0], pos[1]);
275    for (int i = 2; i<npos; ++i) {
276        box = bounding_box(box, pos[i]);
277    }
278    return box;
279}
280
281int AW_clicked_line::indicate_selected(AW_device *d, int gc) const {
282    return d->line(gc, line);
283}
284int AW_clicked_text::indicate_selected(AW_device *d, int gc) const {
285    return d->box(gc, AW::FillStyle::SOLID, textArea);
286}
287int AW_clicked_box::indicate_selected(AW_device *d, int gc) const {
288    return d->box(gc, AW::FillStyle::SOLID, box);
289}
290int AW_clicked_polygon::indicate_selected(AW_device *d, int gc) const {
291    return d->polygon(gc, AW::FillStyle::SOLID, npos, pos);
292}
293
Note: See TracBrowser for help on using the repository browser.