1 //
2 // "$Id$"
3 //
4 // Tile widget for the Fast Light Tool Kit (FLTK).
5 //
6 // Copyright 1998-2016 by Bill Spitzak and others.
7 //
8 // This library is free software. Distribution and use rights are outlined in
9 // the file "COPYING" which should have been included with this file. If this
10 // file is missing or damaged, see the license at:
11 //
12 //     http://www.fltk.org/COPYING.php
13 //
14 // Please report all bugs and problems on the following page:
15 //
16 //     http://www.fltk.org/str.php
17 //
18 
19 /**
20   \class Fl_Tile
21 
22   The Fl_Tile class lets you resize its children by dragging
23   the border between them.
24 
25   \image html Fl_Tile.png
26   \image latex Fl_Tile.png "Fl_Tile" width=5cm
27 
28   For the tiling to work correctly, the children of an Fl_Tile must
29   cover the entire area of the widget, but not overlap.
30   This means that all children must touch each other at their edges,
31   and no gaps can be left inside the Fl_Tile.
32 
33   Fl_Tile does not normally draw any graphics of its own.
34   The "borders" which can be seen in the snapshot above are actually
35   part of the children. Their boxtypes have been set to FL_DOWN_BOX
36   creating the impression of "ridges" where the boxes touch. What
37   you see are actually two adjacent FL_DOWN_BOX's drawn next to each
38   other. All neighboring widgets share the same edge - the widget's
39   thick borders make it appear as though the widgets aren't actually
40   touching, but they are. If the edges of adjacent widgets do not
41   touch, then it will be impossible to drag the corresponding edges.
42 
43   Fl_Tile allows objects to be resized to zero dimensions.
44   To prevent this you can use the resizable() to limit where
45   corners can be dragged to. For more information see note below.
46 
47   Even though objects can be resized to zero sizes, they must initially
48   have non-zero sizes so the Fl_Tile can figure out their layout.
49   If desired, call position() after creating the children but before
50   displaying the window to set the borders where you want.
51 
52   <b>Note on resizable(Fl_Widget &w):</b>
53   The "resizable" child widget (which should be invisible) limits where
54   the borders can be dragged to. All dragging will be limited inside the
55   resizable widget's borders. If you don't set it, it will be possible
56   to drag the borders right to the edges of the Fl_Tile widget, and thus
57   resize objects on the edges to zero width or height. When the entire
58   Fl_Tile widget is resized, the resizable() widget will keep its border
59   distance to all borders the same (this is normal resize behavior), so
60   that you can effectively set a border width that will never change.
61   To ensure correct event delivery to all child widgets the resizable()
62   widget must be the first child of the Fl_Tile widget group. Otherwise
63   some events (e.g. FL_MOVE and FL_ENTER) might be consumed by the resizable()
64   widget so that they are lost for widgets covered (overlapped) by the
65   resizable() widget.
66 
67   \note
68   You can still resize widgets \b inside the resizable() to zero width and/or
69   height, i.e. box \b 2b above to zero width and box \b 3a to zero height.
70 
71   \see void Fl_Group::resizable(Fl_Widget &w)
72 
73   Example for resizable with 20 pixel border distance:
74   \code
75     int dx = 20, dy = dx;
76     Fl_Tile tile(50,50,300,300);
77     // create resizable() box first
78     Fl_Box r(tile.x()+dx,tile.y()+dy,tile.w()-2*dx,tile.h()-2*dy);
79     tile.resizable(r);
80     // ... create widgets inside tile (see test/tile.cxx) ...
81     tile.end();
82   \endcode
83 
84   See also the complete example program in test/tile.cxx.
85 */
86 
87 #include <FL/Fl.H>
88 #include <FL/Fl_Tile.H>
89 #include <FL/Fl_Window.H>
90 #include <stdlib.h>
91 
92 /**
93   Drags the intersection at (\p oldx,\p oldy) to (\p newx,\p newy).
94   This redraws all the necessary children.
95 
96   Pass zero as \p oldx or \p oldy to disable drag in that direction.
97 */
position(int oldx,int oldy,int newx,int newy)98 void Fl_Tile::position(int oldx, int oldy, int newx, int newy) {
99   Fl_Widget*const* a = array();
100   int *p = sizes();
101   p += 8; // skip group & resizable's saved size
102   for (int i=children(); i--; p += 4) {
103     Fl_Widget* o = *a++;
104     if (o == resizable()) continue;
105     int X = o->x();
106     int R = X+o->w();
107     if (oldx) {
108       int t = p[0];
109       if (t == oldx || (t>oldx && X<newx) || (t<oldx && X>newx) ) X = newx;
110       t = p[1];
111       if (t == oldx || (t>oldx && R<newx) || (t<oldx && R>newx) ) R = newx;
112     }
113     int Y = o->y();
114     int B = Y+o->h();
115     if (oldy) {
116       int t = p[2];
117       if (t == oldy || (t>oldy && Y<newy) || (t<oldy && Y>newy) ) Y = newy;
118       t = p[3];
119       if (t == oldy || (t>oldy && B<newy) || (t<oldy && B>newy) ) B = newy;
120     }
121     o->damage_resize(X,Y,R-X,B-Y);
122   }
123 }
124 
125 /**
126   Resizes the Fl_Tile widget and its children.
127 
128   Fl_Tile implements its own resize() method. It does not use
129   Fl_Group::resize() to resize itself and its children.
130 
131   Enlarging works by just moving the lower-right corner and resizing
132   the bottom and right border widgets accordingly.
133 
134   Shrinking the Fl_Tile works in the opposite way by shrinking
135   the bottom and right border widgets, unless they are reduced to zero
136   width or height, resp. or to their minimal sizes defined by the
137   resizable() widget. In this case other widgets will be shrunk as well.
138 
139   See the Fl_Tile class documentation about how the resizable() works.
140 */
141 
resize(int X,int Y,int W,int H)142 void Fl_Tile::resize(int X,int Y,int W,int H) {
143 
144   // remember how much to move the child widgets:
145   int dx = X-x();
146   int dy = Y-y();
147   int dw = W-w();
148   int dh = H-h();
149   int *p = sizes();
150   // resize this (skip the Fl_Group resize):
151   Fl_Widget::resize(X,Y,W,H);
152 
153   // find bottom-right corner of resizable:
154   int OR = p[5];		// old right border
155   int NR = X+W-(p[1]-OR);	// new right border
156   int OB = p[7];		// old bottom border
157   int NB = Y+H-(p[3]-OB);	// new bottom border
158 
159   // move everything to be on correct side of new resizable:
160   Fl_Widget*const* a = array();
161   p += 8;
162   for (int i=children(); i--;) {
163     Fl_Widget* o = *a++;
164     int xx = o->x()+dx;
165     int R = xx+o->w();
166     if (*p++ >= OR) xx += dw; else if (xx > NR) xx = NR;
167     if (*p++ >= OR) R += dw; else if (R > NR) R = NR;
168     int yy = o->y()+dy;
169     int B = yy+o->h();
170     if (*p++ >= OB) yy += dh; else if (yy > NB) yy = NB;
171     if (*p++ >= OB) B += dh; else if (B > NB) B = NB;
172     o->resize(xx,yy,R-xx,B-yy);
173     // do *not* call o->redraw() here! If you do, and the tile is inside a
174     // scroll, it'll set the damage areas wrong for all children!
175   }
176 }
177 
set_cursor(Fl_Tile * t,Fl_Cursor c)178 static void set_cursor(Fl_Tile*t, Fl_Cursor c) {
179   static Fl_Cursor cursor;
180   if (cursor == c || !t->window()) return;
181   cursor = c;
182 #ifdef __sgi
183   t->window()->cursor(c,FL_RED,FL_WHITE);
184 #else
185   t->window()->cursor(c);
186 #endif
187 }
188 
189 static Fl_Cursor cursors[4] = {
190   FL_CURSOR_DEFAULT,
191   FL_CURSOR_WE,
192   FL_CURSOR_NS,
193   FL_CURSOR_MOVE};
194 
handle(int event)195 int Fl_Tile::handle(int event) {
196   static int sdrag;
197   static int sdx, sdy;
198   static int sx, sy;
199 #define DRAGH 1
200 #define DRAGV 2
201 #define GRABAREA 4
202 
203   int mx = Fl::event_x();
204   int my = Fl::event_y();
205 
206   switch (event) {
207 
208   case FL_MOVE:
209   case FL_ENTER:
210   case FL_PUSH:
211     // don't potentially change the mouse cursor if inactive:
212     if (!active()) break; // will cascade inherited handle()
213     {
214     int mindx = 100;
215     int mindy = 100;
216     int oldx = 0;
217     int oldy = 0;
218     Fl_Widget*const* a = array();
219     int *q = sizes();
220     int *p = q+8;
221     for (int i=children(); i--; p += 4) {
222       Fl_Widget* o = *a++;
223       if (o == resizable()) continue;
224       if (p[1]<q[1] && o->y()<=my+GRABAREA && o->y()+o->h()>=my-GRABAREA) {
225 	int t = mx - (o->x()+o->w());
226 	if (abs(t) < mindx) {
227 	  sdx = t;
228 	  mindx = abs(t);
229 	  oldx = p[1];
230 	}
231       }
232       if (p[3]<q[3] && o->x()<=mx+GRABAREA && o->x()+o->w()>=mx-GRABAREA) {
233 	int t = my - (o->y()+o->h());
234 	if (abs(t) < mindy) {
235 	  sdy = t;
236 	  mindy = abs(t);
237 	  oldy = p[3];
238 	}
239       }
240     }
241     sdrag = 0; sx = sy = 0;
242     if (mindx <= GRABAREA) {sdrag = DRAGH; sx = oldx;}
243     if (mindy <= GRABAREA) {sdrag |= DRAGV; sy = oldy;}
244     set_cursor(this, cursors[sdrag]);
245     if (sdrag) return 1;
246     return Fl_Group::handle(event);
247   }
248 
249   case FL_LEAVE:
250     set_cursor(this, FL_CURSOR_DEFAULT);
251     break;
252 
253   case FL_DRAG:
254     // This is necessary if CONSOLIDATE_MOTION in Fl_x.cxx is turned off:
255     // if (damage()) return 1; // don't fall behind
256   case FL_RELEASE: {
257     if (!sdrag) return 0; // should not happen
258     Fl_Widget* r = resizable(); if (!r) r = this;
259     int newx;
260     if (sdrag&DRAGH) {
261       newx = Fl::event_x()-sdx;
262       if (newx < r->x()) newx = r->x();
263       else if (newx > r->x()+r->w()) newx = r->x()+r->w();
264     } else
265       newx = sx;
266     int newy;
267     if (sdrag&DRAGV) {
268       newy = Fl::event_y()-sdy;
269       if (newy < r->y()) newy = r->y();
270       else if (newy > r->y()+r->h()) newy = r->y()+r->h();
271     } else
272       newy = sy;
273     position(sx,sy,newx,newy);
274     if (event == FL_DRAG) set_changed();
275     do_callback();
276     return 1;}
277 
278   }
279 
280   return Fl_Group::handle(event);
281 }
282 
283 /**
284   Creates a new Fl_Tile widget using the given position, size,
285   and label string. The default boxtype is FL_NO_BOX.
286 
287   The destructor <I>also deletes all the children</I>. This allows a
288   whole tree to be deleted at once, without having to keep a pointer to
289   all the children in the user code. A kludge has been done so the
290   Fl_Tile and all of its children can be automatic (local)
291   variables, but you must declare the Fl_Tile <I>first</I>, so
292   that it is destroyed last.
293 
294   \see class Fl_Group
295 */
296 
Fl_Tile(int X,int Y,int W,int H,const char * L)297 Fl_Tile::Fl_Tile(int X,int Y,int W,int H,const char*L)
298 : Fl_Group(X,Y,W,H,L)
299 {
300 }
301 
302 
303 //
304 // End of "$Id$".
305 //
306