source: tags/ms_r16q3/NTREE/ScrollSynchronizer.cxx

Last change on this file was 15340, checked in by westram, 8 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 NULL;
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 :  NULL;
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 char*, const Position& pos, AW_pos, AW_bitset filteri, long int) 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
149    void set_species(SpeciesSetPtr species_) { species = species_; }
150    void forget_positions() { spos.clear(); }
151
152    AW_DEVICE_TYPE type() OVERRIDE { return AW_DEVICE_SIZE; }
153    void specific_reset() OVERRIDE {}
154    bool invisible_impl(const Position&, AW_bitset) OVERRIDE { return false; }
155
156    bool line_impl(int, const LineVector& Line, AW_bitset filteri) OVERRIDE {
157        bool drawflag = false;
158        if (filteri & filter) {
159            GBDATA *gb_species = trackable_species(get_click_cd());
160            if (gb_species) {
161                if (species->find(gb_species) != species->end()) {
162                    trackPosition(gb_species, Line);
163                }
164            }
165            drawflag = true;
166        }
167        return drawflag;
168    }
169    bool text_impl(int, const char*, const Position& pos, AW_pos, AW_bitset filteri, long int) OVERRIDE {
170        bool drawflag = false;
171        if (filteri & filter) {
172            GBDATA *gb_species = trackable_species(get_click_cd());
173            if (gb_species) {
174                if (species->find(gb_species) != species->end()) {
175                    trackPosition(gb_species, pos);
176                }
177            }
178            drawflag = true;
179        }
180        return drawflag;
181    }
182
183    const SpeciesPositions& get_tracked_positions() const {
184        return spos;
185    }
186};
187
188struct cmp_Rectangles {
189    bool operator()(const Rectangle &r1, const Rectangle &r2) const {
190    double cmp = r1.top()-r2.top(); // upper first
191    if (!cmp) {
192        cmp = r1.bottom()-r2.bottom(); // smaller first
193        if (!cmp) {
194            cmp = r1.left()-r2.left(); // leftmost first
195            if (!cmp) {
196                cmp = r1.right()-r2.right(); // smaller first
197            }
198        }
199    }
200    return cmp<0.0;
201  }
202};
203
204typedef set<Rectangle, cmp_Rectangles> SortedPositions; // world-coordinates
205
206class SlaveCanvas_internal {
207    SortedPositions pos;
208    Vector          viewport_size;
209    Rectangle       best_area; // wanted display area (world coordinates)
210
211public:
212
213    void store_positions_sorted(const SpeciesPositions& spos) {
214        pos.clear();
215        for (SpeciesPositions::const_iterator s = spos.begin(); s != spos.end(); ++s) {
216            pos.insert(s->second);
217        }
218    }
219
220    void announce_viewport_size(const Vector& viewport_size_) {
221        viewport_size = viewport_size_;
222        best_area     = Rectangle();
223    }
224    void calc_best_area();
225    Vector calc_best_scroll_delta(const Rectangle& viewport);
226};
227
228void SlaveCanvas_internal::calc_best_area() {
229    int       best_count  = -1;
230    const int max_species = int(pos.size());
231    int       rest        = max_species;
232
233    SortedPositions::const_iterator end = pos.end();
234    for (SortedPositions::const_iterator s1 = pos.begin(); rest>best_count && s1 != end; ++s1) {
235        const Rectangle& r1 = *s1;
236
237        Rectangle testedViewport(r1.upper_left_corner(), viewport_size);
238        int       count = 1;
239
240        Rectangle contained_area = r1; // bounding box of all species displayable inside testedViewport
241
242        SortedPositions::const_iterator s2 = s1;
243        ++s2;
244        for (; s2 != end; ++s2) {
245            const Rectangle& r2 = *s2;
246            if (r2.overlaps_with(testedViewport)) {
247                ++count;
248                contained_area = contained_area.bounding_box(r2);
249            }
250        }
251
252        nt_assert(count>0);
253
254        if (count>best_count) {
255            best_count = count;
256            best_area  = contained_area;
257
258#if defined(DUMP_SCROLL_DETECT)
259            fprintf(stderr, "Found %i species fitting into area ", count);
260            AW_DUMP(contained_area);
261#endif
262        }
263
264        rest--;
265    }
266}
267
268Vector SlaveCanvas_internal::calc_best_scroll_delta(const Rectangle& viewport) {
269    // in and out are world-coordinates!
270    if (best_area.valid()) {
271        Vector    shift(viewport.width()*-0.1, (best_area.height()-viewport.height())/2);
272        Rectangle wanted_viewport = Rectangle(best_area.upper_left_corner() + shift, viewport.diagonal());
273        return wanted_viewport.upper_left_corner() - viewport.upper_left_corner();
274    }
275    return ZeroVector;
276}
277
278void SlaveCanvas::track_display_positions() {
279    TREE_canvas *ntw    = get_canvas();
280    AW_common   *common = ntw->aww->get_common(AW_MIDDLE_AREA);
281
282    // @@@ use different algo (device) for radial and for other treeviews
283    // below code fits non-radial slave-views:
284
285    AW_trackPositions_device device(common);
286
287    device.set_species(species); // @@@ move to device-ctor?
288    device.forget_positions(); // @@@ not necessary if device is recreated for each tracking
289
290    device.set_filter(AW_TRACK);
291    device.reset(); // @@@ really needed?
292
293    {
294        GB_transaction ta(ntw->gb_main);
295
296        ntw->init_device(&device);
297        ntw->gfx->show(&device);
298    }
299
300    internal->store_positions_sorted(device.get_tracked_positions());
301}
302
303void SlaveCanvas::calc_scroll_zoom() {
304    TREE_canvas *ntw      = get_canvas();
305    AW_device   *device   = ntw->aww->get_device(AW_MIDDLE_AREA);
306    Rectangle    viewport = device->rtransform(Rectangle(ntw->rect, INCLUSIVE_OUTLINE));
307
308    internal->announce_viewport_size(viewport.diagonal());
309    internal->calc_best_area();
310}
311
312void SlaveCanvas::refresh_scroll_zoom() {
313#if defined(DUMP_SYNC)
314    fprintf(stderr, "DEBUG: SlaveCanvas does refresh_scroll_zoom (idx=%i)\n", get_index());
315#endif
316
317    TREE_canvas *ntw      = get_canvas();
318    AW_device   *device   = ntw->aww->get_device(AW_MIDDLE_AREA);
319    Rectangle    viewport = device->rtransform(Rectangle(ntw->rect, INCLUSIVE_OUTLINE));
320    Vector       world_scroll(internal->calc_best_scroll_delta(viewport));
321
322#if defined(DUMP_SCROLL_DETECT)
323    AW_DUMP(viewport);
324    AW_DUMP(world_scroll);
325#endif
326
327    if (world_scroll.has_length()) { // skip scroll if (nearly) nothing happens
328        Vector screen_scroll = device->transform(world_scroll);
329#if defined(DUMP_SCROLL_DETECT)
330        AW_DUMP(screen_scroll);
331#endif
332        ntw->scroll(screen_scroll); // @@@ scroll the canvas (should be done by caller, to avoid recalculation on slave-canvas-resize)
333    }
334   
335    // get_canvas()->refresh();
336}
337
338SlaveCanvas::SlaveCanvas() :
339    last_master(NULL),
340    last_master_change(0),
341    need_SetUpdate(true),
342    need_PositionTrack(true),
343    need_ScrollZoom(true),
344    need_Refresh(true)
345{
346    internal = new SlaveCanvas_internal;
347}
348
349SlaveCanvas::~SlaveCanvas() {
350    delete internal;
351}
352
353
Note: See TracBrowser for help on using the repository browser.