ARB
AW_device_click.cxx
Go to the documentation of this file.
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 
16 using namespace AW;
17 
18 // ------------------------
19 // AW_device_click
20 
21 AW_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 
27 void 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 
41  return AW_DEVICE_CLICK;
42 }
43 
44 bool 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 
63 bool 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 
91 inline 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 }
98 inline 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 }
105 static 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 
114 bool 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 
153 bool 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 
239 const 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 
273 AW::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 
282  return d->line(gc, line);
283 }
285  return d->box(gc, AW::FillStyle::SOLID, textArea);
286 }
288  return d->box(gc, AW::FillStyle::SOLID, box);
289 }
290 int AW_clicked_polygon::indicate_selected(AW_device *d, int gc) const {
291  return d->polygon(gc, AW::FillStyle::SOLID, npos, pos);
292 }
293 
long AW_bitset
Definition: aw_base.hxx:44
GB_TYPES type
double kpt2(const Position &a, const Position &b, const Position &c)
bool is_empty() const
Definition: aw_position.hxx:69
Rectangle bounding_box(const Rectangle &r1, const Rectangle &r2)
Position nearest_linepoint(const Position &pos, const LineVector &line, double &factor)
const AW_bitset AW_ALL_DEVICES
Definition: aw_device.hxx:44
bool polygon(int gc, AW::FillStyle filled, int npoints, const AW_pos *points, AW_bitset filteri=AW_ALL_DEVICES_SCALED)
Definition: aw_device.hxx:500
#define AWT_NO_CATCH
#define ARRAY_ELEMS(array)
Definition: arb_defs.h:19
double AW_pos
Definition: aw_base.hxx:29
const double & ypos() const
ValueCounter< double > Distance
Definition: AP_Tree.cxx:1315
#define aw_assert(bed)
Definition: aw_position.hxx:29
bool line(int gc, const AW::LineVector &Line, AW_bitset filteri=AW_ALL_DEVICES_SCALED)
Definition: aw_device.hxx:430
static bool polygon_contains(const Position &mouse, int npos, const Position *pos)
int indicate_selected(AW_device *d, int gc) const OVERRIDE
bool is_between(const double &coord1, const double &between, const double &coord2)
Definition: aw_position.hxx:99
bool contains(const Position &pos) const
const double & xpos() const
#define NULp
Definition: cxxforward.h:116
AW_DEVICE_TYPE
Definition: aw_device.hxx:48
double KreuzProdTest(const Position &a, const Position &b, const Position &c)
bool box(int gc, AW::FillStyle filled, const AW::Rectangle &rect, AW_bitset filteri=AW_ALL_DEVICES_SCALED)
Definition: aw_device.hxx:471
int get_distance() const
const Position Origin
int indicate_selected(AW_device *d, int gc) const OVERRIDE
AW_pos x_alignment(AW_pos x_pos, AW_pos x_size, AW_pos alignment)
int indicate_selected(AW_device *d, int gc) const OVERRIDE
#define max(a, b)
Definition: f2c.h:154