source: branches/help/NTREE/ScrollSynchronizer.cxx

Last change on this file was 18730, checked in by westram, 3 years ago
  • remove trailing whitespace from c source.
File size: 10.8 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : ScrollSynchronizer.cxx                            //
4//   Purpose   : synchronize TREE_canvas scrolling                 //
5//                                                                 //
6//   Coded by Ralf Westram (coder@reallysoft.de) in October 2016   //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "ScrollSynchronizer.h"
12
13#include <TreeDisplay.hxx>
14#include <awt_canvas.hxx>
15#include <map>
16
17#if defined(DUMP_SYNC)
18# define DUMP_ADD
19# define DUMP_SCROLL_DETECT
20#endif
21
22
23using namespace AW;
24using namespace std;
25
26inline GBDATA *trackable_species(const AW_click_cd *clickable) {
27    if (!clickable) return NULp;
28
29    ClickedType clicked = (ClickedType)clickable->get_cd2();
30    if (clicked == CL_SPECIES) return (GBDATA*)clickable->get_cd1(); // NDS list
31
32    nt_assert(clicked == CL_NODE || clicked == CL_BRANCH); // unexpected clickable tracked!
33    TreeNode *node = (TreeNode*)clickable->get_cd1();
34    return (node && node->is_leaf()) ? node->gb_node :  NULp;
35}
36
37class AW_trackSpecies_device: public AW_simple_device {
38    SpeciesSet& species;
39
40    void track() {
41        GBDATA *gb_species = trackable_species(get_click_cd());
42
43        if (gb_species) {
44#if defined(DUMP_ADD)
45            bool do_insert = species.find(gb_species) == species.end();
46#else // NDEBUG
47            bool do_insert = true;
48#endif
49            if (do_insert) {
50#if defined(DUMP_ADD)
51                fprintf(stderr, " - adding species #%zu '%s'\n", species.size(), GBT_get_name_or_description(gb_species));
52#endif
53                species.insert(gb_species);
54            }
55        }
56    }
57
58public:
59    AW_trackSpecies_device(AW_common *common_, SpeciesSet& species_) :
60        AW_simple_device(common_),
61        species(species_)
62    {}
63
64    AW_DEVICE_TYPE type() OVERRIDE { return AW_DEVICE_CLICK; }
65    void specific_reset() OVERRIDE {}
66    bool invisible_impl(const Position&, AW_bitset) OVERRIDE { return false; }
67
68    bool line_impl(int, const LineVector& Line, AW_bitset filteri) OVERRIDE {
69        bool drawflag = false;
70        if (filteri & filter) {
71            LineVector transLine = transform(Line);
72            LineVector clippedLine;
73            drawflag = clip(transLine, clippedLine);
74            if (drawflag) track();
75        }
76        return drawflag;
77    }
78    bool text_impl(int, const SizedCstr&, const AW::Position& pos, AW_pos, AW_bitset filteri) OVERRIDE {
79        bool drawflag = false;
80        if (filteri & filter) {
81            if (!is_outside_clip(transform(pos))) track();
82        }
83        return drawflag;
84    }
85};
86
87
88
89SpeciesSetPtr MasterCanvas::track_displayed_species() {
90    // clip_expose
91
92    TREE_canvas *ntw    = get_canvas();
93    AW_window   *aww    = ntw->aww;
94    AW_common   *common = aww->get_common(AW_MIDDLE_AREA);
95
96    SpeciesSetPtr tracked = new SpeciesSet;
97
98    AW_trackSpecies_device device(common, *tracked);
99
100    device.set_filter(AW_TRACK);
101    device.reset();
102
103    const AW_screen_area& rect = ntw->rect;
104
105    device.set_top_clip_border(rect.t);
106    device.set_bottom_clip_border(rect.b);
107    device.set_left_clip_border(rect.l);
108    device.set_right_clip_border(rect.r);
109
110    {
111        GB_transaction ta(ntw->gb_main);
112
113        ntw->init_device(&device);
114        ntw->gfx->show(&device);
115    }
116
117    return tracked;
118}
119
120typedef map< RefPtr<GBDATA>, Rectangle> SpeciesPositions; // world-coordinates
121
122class AW_trackPositions_device: public AW_simple_device {
123    SpeciesSetPtr    species; // @@@ elim (instead "mark" contained species to improve speed?)
124    SpeciesPositions spos;
125
126    void trackPosition(GBDATA *gb_species, const Rectangle& spec_area) {
127        SpeciesPositions::iterator tracked = spos.find(gb_species);
128        if (tracked == spos.end()) { // first track
129            spos[gb_species] = spec_area;
130        }
131        else {
132            tracked->second = bounding_box(tracked->second, spec_area); // combine areas
133        }
134    }
135    void trackPosition(GBDATA *gb_species, const LineVector& spec_vec) {
136        trackPosition(gb_species, Rectangle(spec_vec));
137    }
138    void trackPosition(GBDATA *gb_species, const Position& spec_pos) {
139        trackPosition(gb_species, Rectangle(spec_pos, ZeroVector));
140    }
141
142public:
143    AW_trackPositions_device(AW_common *common_) :
144        AW_simple_device(common_)
145    {}
146
147    void set_species(SpeciesSetPtr species_) { species = species_; }
148    void forget_positions() { spos.clear(); }
149
150    AW_DEVICE_TYPE type() OVERRIDE { return AW_DEVICE_SIZE; }
151    void specific_reset() OVERRIDE {}
152    bool invisible_impl(const Position&, AW_bitset) OVERRIDE { return false; }
153
154    bool line_impl(int, const LineVector& Line, AW_bitset filteri) OVERRIDE {
155        bool drawflag = false;
156        if (filteri & filter) {
157            GBDATA *gb_species = trackable_species(get_click_cd());
158            if (gb_species) {
159                if (species->find(gb_species) != species->end()) {
160                    trackPosition(gb_species, Line);
161                }
162            }
163            drawflag = true;
164        }
165        return drawflag;
166    }
167    bool text_impl(int, const SizedCstr&, const AW::Position& pos, AW_pos, AW_bitset filteri) OVERRIDE {
168        bool drawflag = false;
169        if (filteri & filter) {
170            GBDATA *gb_species = trackable_species(get_click_cd());
171            if (gb_species) {
172                if (species->find(gb_species) != species->end()) {
173                    trackPosition(gb_species, pos);
174                }
175            }
176            drawflag = true;
177        }
178        return drawflag;
179    }
180
181    const SpeciesPositions& get_tracked_positions() const {
182        return spos;
183    }
184};
185
186struct cmp_Rectangles {
187    bool operator()(const Rectangle &r1, const Rectangle &r2) const {
188    double cmp = r1.top()-r2.top(); // upper first
189    if (!cmp) {
190        cmp = r1.bottom()-r2.bottom(); // smaller first
191        if (!cmp) {
192            cmp = r1.left()-r2.left(); // leftmost first
193            if (!cmp) {
194                cmp = r1.right()-r2.right(); // smaller first
195            }
196        }
197    }
198    return cmp<0.0;
199  }
200};
201
202typedef set<Rectangle, cmp_Rectangles> SortedPositions; // world-coordinates
203
204class SlaveCanvas_internal {
205    SortedPositions pos;
206    Vector          viewport_size;
207    Rectangle       best_area; // wanted display area (world coordinates)
208
209public:
210
211    void store_positions_sorted(const SpeciesPositions& spos) {
212        pos.clear();
213        for (SpeciesPositions::const_iterator s = spos.begin(); s != spos.end(); ++s) {
214            pos.insert(s->second);
215        }
216    }
217
218    void announce_viewport_size(const Vector& viewport_size_) {
219        viewport_size = viewport_size_;
220        best_area     = Rectangle();
221    }
222    void calc_best_area();
223    Vector calc_best_scroll_delta(const Rectangle& viewport);
224};
225
226void SlaveCanvas_internal::calc_best_area() {
227    int       best_count  = -1;
228    const int max_species = int(pos.size());
229    int       rest        = max_species;
230
231    SortedPositions::const_iterator end = pos.end();
232    for (SortedPositions::const_iterator s1 = pos.begin(); rest>best_count && s1 != end; ++s1) {
233        const Rectangle& r1 = *s1;
234
235        Rectangle testedViewport(r1.upper_left_corner(), viewport_size);
236        int       count = 1;
237
238        Rectangle contained_area = r1; // bounding box of all species displayable inside testedViewport
239
240        SortedPositions::const_iterator s2 = s1;
241        ++s2;
242        for (; s2 != end; ++s2) {
243            const Rectangle& r2 = *s2;
244            if (r2.overlaps_with(testedViewport)) {
245                ++count;
246                contained_area = contained_area.bounding_box(r2);
247            }
248        }
249
250        nt_assert(count>0);
251
252        if (count>best_count) {
253            best_count = count;
254            best_area  = contained_area;
255
256#if defined(DUMP_SCROLL_DETECT)
257            fprintf(stderr, "Found %i species fitting into area ", count);
258            AW_DUMP(contained_area);
259#endif
260        }
261
262        rest--;
263    }
264}
265
266Vector SlaveCanvas_internal::calc_best_scroll_delta(const Rectangle& viewport) {
267    // in and out are world-coordinates!
268    if (best_area.valid()) {
269        Vector    shift(viewport.width()*-0.1, (best_area.height()-viewport.height())/2);
270        Rectangle wanted_viewport = Rectangle(best_area.upper_left_corner() + shift, viewport.diagonal());
271        return wanted_viewport.upper_left_corner() - viewport.upper_left_corner();
272    }
273    return ZeroVector;
274}
275
276void SlaveCanvas::track_display_positions() {
277    TREE_canvas *ntw    = get_canvas();
278    AW_common   *common = ntw->aww->get_common(AW_MIDDLE_AREA);
279
280    // @@@ use different algo (device) for radial and for other treeviews
281    // below code fits non-radial slave-views:
282
283    AW_trackPositions_device device(common);
284
285    device.set_species(species); // @@@ move to device-ctor?
286    device.forget_positions(); // @@@ not necessary if device is recreated for each tracking
287
288    device.set_filter(AW_TRACK);
289    device.reset(); // @@@ really needed?
290
291    {
292        GB_transaction ta(ntw->gb_main);
293
294        ntw->init_device(&device);
295        ntw->gfx->show(&device);
296    }
297
298    internal->store_positions_sorted(device.get_tracked_positions());
299}
300
301void SlaveCanvas::calc_scroll_zoom() {
302    TREE_canvas *ntw      = get_canvas();
303    AW_device   *device   = ntw->aww->get_device(AW_MIDDLE_AREA);
304    Rectangle    viewport = device->rtransform(Rectangle(ntw->rect, INCLUSIVE_OUTLINE));
305
306    internal->announce_viewport_size(viewport.diagonal());
307    internal->calc_best_area();
308}
309
310void SlaveCanvas::refresh_scroll_zoom() {
311#if defined(DUMP_SYNC)
312    fprintf(stderr, "DEBUG: SlaveCanvas does refresh_scroll_zoom (idx=%i)\n", get_index());
313#endif
314
315    TREE_canvas *ntw      = get_canvas();
316    AW_device   *device   = ntw->aww->get_device(AW_MIDDLE_AREA);
317    Rectangle    viewport = device->rtransform(Rectangle(ntw->rect, INCLUSIVE_OUTLINE));
318    Vector       world_scroll(internal->calc_best_scroll_delta(viewport));
319
320#if defined(DUMP_SCROLL_DETECT)
321    AW_DUMP(viewport);
322    AW_DUMP(world_scroll);
323#endif
324
325    if (world_scroll.has_length()) { // skip scroll if (nearly) nothing happens
326        Vector screen_scroll = device->transform(world_scroll);
327#if defined(DUMP_SCROLL_DETECT)
328        AW_DUMP(screen_scroll);
329#endif
330        ntw->scroll(screen_scroll); // @@@ scroll the canvas (should be done by caller, to avoid recalculation on slave-canvas-resize)
331    }
332
333    // get_canvas()->refresh();
334}
335
336SlaveCanvas::SlaveCanvas() :
337    last_master(NULp),
338    last_master_change(0),
339    need_SetUpdate(true),
340    need_PositionTrack(true),
341    need_ScrollZoom(true),
342    need_Refresh(true)
343{
344    internal = new SlaveCanvas_internal;
345}
346
347SlaveCanvas::~SlaveCanvas() {
348    delete internal;
349}
350
351
Note: See TracBrowser for help on using the repository browser.