source: branches/alilink/NTREE/ScrollSynchronizer.cxx

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