Fix multi-leg promotions
[xboard.git] / winboard / wsnap.c
1 /*\r
2  * Smart "snapping" for window moving and sizing\r
3  *\r
4  * Author: Alessandro Scotti (Dec 2005)\r
5  *\r
6  * Copyright 2005 Alessandro Scotti\r
7  *\r
8  * ------------------------------------------------------------------------\r
9  *\r
10  * GNU XBoard is free software: you can redistribute it and/or modify\r
11  * it under the terms of the GNU General Public License as published by\r
12  * the Free Software Foundation, either version 3 of the License, or (at\r
13  * your option) any later version.\r
14  *\r
15  * GNU XBoard is distributed in the hope that it will be useful, but\r
16  * WITHOUT ANY WARRANTY; without even the implied warranty of\r
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
18  * General Public License for more details.\r
19  *\r
20  * You should have received a copy of the GNU General Public License\r
21  * along with this program. If not, see http://www.gnu.org/licenses/.  *\r
22  *\r
23  *------------------------------------------------------------------------\r
24  ** See the file ChangeLog for a revision history.  */\r
25 \r
26 #include "wsnap.h"\r
27 \r
28 /* Imports from winboard.c */\r
29 extern HINSTANCE hInst;\r
30 \r
31 extern HWND hwndMain;\r
32 extern HWND moveHistoryDialog;\r
33 extern HWND evalGraphDialog;\r
34 extern HWND engineOutputDialog;\r
35 extern HWND gameListDialog;\r
36 \r
37 static BOOL SnappingEnabled = TRUE;\r
38 \r
39 static void AddSnapPoint( int * grid, int * grid_len, int value )\r
40 {\r
41     int len = *grid_len;\r
42 \r
43     if( len < MAX_SNAP_POINTS ) {\r
44         int i;\r
45 \r
46         for( i=0; i<len; i++ ) {\r
47             if( grid[i] == value ) {\r
48                 return;\r
49             }\r
50         }\r
51 \r
52         grid[ len++ ] = value;\r
53 \r
54         *grid_len = len;\r
55     }\r
56 }\r
57 \r
58 static void AddSnapRectangle( SnapData * sd, RECT * rc )\r
59 {\r
60     AddSnapPoint( sd->x_grid, &sd->x_grid_len, rc->left );\r
61     AddSnapPoint( sd->x_grid, &sd->x_grid_len, rc->right );\r
62 \r
63     AddSnapPoint( sd->y_grid, &sd->y_grid_len, rc->top );\r
64     AddSnapPoint( sd->y_grid, &sd->y_grid_len, rc->bottom );\r
65 }\r
66 \r
67 static RECT activeRect, mainRect;\r
68 static int side, loc; // code for edge we were dragging, and its latest coordinate\r
69 \r
70 static void AddSnapWindow( HWND hWndCaller, SnapData * sd, HWND hWndSnapWindow )\r
71 {\r
72     if( hWndSnapWindow != NULL && IsWindowVisible(hWndSnapWindow) ) {\r
73         RECT rc;\r
74 \r
75         GetWindowRect( hWndSnapWindow, &rc );\r
76         if(hWndSnapWindow == hwndMain) mainRect = rc;\r
77 \r
78         if(hWndCaller != hWndSnapWindow) {\r
79             AddSnapRectangle( sd, &rc );\r
80         } else {\r
81             activeRect = rc; // [HGM] glue: remember original geometry of dragged window\r
82         }\r
83     }\r
84 }\r
85 \r
86 static BOOL AdjustToSnapPoint( int * grid, int grid_len, int value, int * snap_size, int * delta )\r
87 {\r
88     BOOL result = FALSE;\r
89     int i;\r
90 \r
91     for( i=0; i<grid_len; i++ ) {\r
92         int distance = value - grid[i];\r
93 \r
94         if( distance < 0 ) distance = -distance;\r
95 \r
96         if( distance < *snap_size ) {\r
97             result = TRUE;\r
98             *snap_size = distance;\r
99             *delta = grid[i] - value;\r
100         }\r
101     }\r
102 \r
103     return result;\r
104 }\r
105 \r
106 LRESULT OnEnterSizeMove( SnapData * snapData, HWND hWnd, WPARAM wParam, LPARAM lParam )\r
107 {\r
108     RECT rc;\r
109 \r
110     snapData->x_grid_len = 0;\r
111     snapData->y_grid_len = 0;\r
112     side = 0;\r
113 \r
114     /* Add desktop area */\r
115     if( SystemParametersInfo( SPI_GETWORKAREA, 0, &rc, 0 ) ) {\r
116         AddSnapRectangle( snapData, &rc );\r
117     }\r
118 \r
119     if( hWnd != hwndMain ) {\r
120         /* Add other windows */\r
121         AddSnapWindow( hWnd, snapData, hwndMain );\r
122         AddSnapWindow( hWnd, snapData, moveHistoryDialog );\r
123         AddSnapWindow( hWnd, snapData, evalGraphDialog );\r
124         AddSnapWindow( hWnd, snapData, engineOutputDialog );\r
125         AddSnapWindow( hWnd, snapData, gameListDialog );\r
126     }\r
127 \r
128     return 0;\r
129 }\r
130 \r
131 LRESULT OnMoving( SnapData * snapData, HWND hWnd, WPARAM wParam, LPARAM lParam )\r
132 {\r
133     LPRECT lprc = (LPRECT) lParam;\r
134     int delta_x = 0;\r
135     int delta_y = 0;\r
136     int snap_size_x = SNAP_DISTANCE;\r
137     int snap_size_y = SNAP_DISTANCE;\r
138 \r
139     if( ! SnappingEnabled ) {\r
140         return FALSE;\r
141     }\r
142 \r
143     AdjustToSnapPoint( snapData->x_grid, snapData->x_grid_len, lprc->left, &snap_size_x, &delta_x );\r
144     AdjustToSnapPoint( snapData->x_grid, snapData->x_grid_len, lprc->right, &snap_size_x, &delta_x );\r
145 \r
146     AdjustToSnapPoint( snapData->y_grid, snapData->y_grid_len, lprc->top, &snap_size_y, &delta_y );\r
147     AdjustToSnapPoint( snapData->y_grid, snapData->y_grid_len, lprc->bottom, &snap_size_y, &delta_y );\r
148 \r
149     OffsetRect( lprc, delta_x, delta_y );\r
150 \r
151     return TRUE;\r
152 }\r
153 \r
154 LRESULT OnSizing( SnapData * snapData, HWND hWnd, WPARAM wParam, LPARAM lParam )\r
155 {\r
156     LPRECT lprc = (LPRECT) lParam;\r
157     int delta_x = 0;\r
158     int delta_y = 0;\r
159     int snap_size_x = SNAP_DISTANCE;\r
160     int snap_size_y = SNAP_DISTANCE;\r
161 \r
162     if( ! SnappingEnabled ) {\r
163         return FALSE;\r
164     }\r
165 \r
166     switch( wParam ) {\r
167     case WMSZ_BOTTOM:\r
168         AdjustToSnapPoint( snapData->y_grid, snapData->y_grid_len, lprc->bottom, &snap_size_y, &delta_y );\r
169         lprc->bottom += delta_y; side = 4; loc = lprc->bottom;\r
170         break;\r
171     case WMSZ_BOTTOMLEFT:\r
172         AdjustToSnapPoint( snapData->y_grid, snapData->y_grid_len, lprc->bottom, &snap_size_y, &delta_y );\r
173         lprc->bottom += delta_y;\r
174         AdjustToSnapPoint( snapData->x_grid, snapData->x_grid_len, lprc->left, &snap_size_x, &delta_x );\r
175         lprc->left += delta_x;\r
176         break;\r
177     case WMSZ_BOTTOMRIGHT:\r
178         AdjustToSnapPoint( snapData->y_grid, snapData->y_grid_len, lprc->bottom, &snap_size_y, &delta_y );\r
179         lprc->bottom += delta_y;\r
180         AdjustToSnapPoint( snapData->x_grid, snapData->x_grid_len, lprc->right, &snap_size_x, &delta_x );\r
181         lprc->right += delta_x;\r
182         break;\r
183     case WMSZ_LEFT:\r
184         AdjustToSnapPoint( snapData->x_grid, snapData->x_grid_len, lprc->left, &snap_size_x, &delta_x );\r
185         lprc->left += delta_x; side = 1; loc = lprc->left;\r
186         break;\r
187     case WMSZ_RIGHT:\r
188         AdjustToSnapPoint( snapData->x_grid, snapData->x_grid_len, lprc->right, &snap_size_x, &delta_x );\r
189         lprc->right += delta_x; side = 2; loc = lprc->right;\r
190         break;\r
191     case WMSZ_TOP:\r
192         AdjustToSnapPoint( snapData->y_grid, snapData->y_grid_len, lprc->top, &snap_size_y, &delta_y );\r
193         lprc->top += delta_y; side = 3; loc = lprc->top;\r
194         break;\r
195     case WMSZ_TOPLEFT:\r
196         AdjustToSnapPoint( snapData->y_grid, snapData->y_grid_len, lprc->top, &snap_size_y, &delta_y );\r
197         lprc->top += delta_y;\r
198         AdjustToSnapPoint( snapData->x_grid, snapData->x_grid_len, lprc->left, &snap_size_x, &delta_x );\r
199         lprc->left += delta_x;\r
200         break;\r
201     case WMSZ_TOPRIGHT:\r
202         AdjustToSnapPoint( snapData->y_grid, snapData->y_grid_len, lprc->top, &snap_size_y, &delta_y );\r
203         lprc->top += delta_y;\r
204         AdjustToSnapPoint( snapData->x_grid, snapData->x_grid_len, lprc->right, &snap_size_x, &delta_x );\r
205         lprc->right += delta_x;\r
206         break;\r
207     default:\r
208         return FALSE;\r
209     }\r
210 \r
211     return TRUE;\r
212 }\r
213 \r
214 static int Adjust( LONG *data, int new, int old , int vertical)\r
215 {\r
216     // protect edges that also touch main window\r
217     if(!vertical && (old == mainRect.left || old == mainRect.right))  return 0;\r
218     if( vertical && (old == mainRect.top  || old == mainRect.bottom)) return 0;\r
219     // if the coordinate was the same as the old, now make it the same as the new edge position\r
220     if(*data == old) { *data = new; return 1; }\r
221     return 0;\r
222 }\r
223 \r
224 static void KeepTouching( int side, int new, int old, HWND hWnd )\r
225 {   // if the mentioned window was touching on the moved edge, move its touching edge too\r
226     if( IsWindowVisible(hWnd) ) {\r
227         RECT rc;\r
228         int i = 0;\r
229 \r
230         GetWindowRect( hWnd, &rc );\r
231 \r
232         switch(side) { // figure out which edge we might need to drag along (if any)\r
233           case 1: i = Adjust(&rc.right,  new, old, 0); break;\r
234           case 2: i = Adjust(&rc.left,   new, old, 0); break;\r
235           case 3: i = Adjust(&rc.bottom, new, old, 1); break;\r
236           case 4: i = Adjust(&rc.top,    new, old, 1); break;\r
237         }\r
238 \r
239         if(i) { // the correct edge was touching, and is adjusted\r
240             SetWindowPos(hWnd, HWND_TOP, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_NOZORDER );\r
241         }\r
242     }\r
243 }\r
244 \r
245 LRESULT OnExitSizeMove( SnapData * snapData, HWND hWnd, WPARAM wParam, LPARAM lParam )\r
246 {\r
247     if(side && hWnd != hwndMain) { // [HGM] glue: we have been sizing, by dragging an edge\r
248         int *grid = (side > 2 ? snapData->y_grid : snapData->x_grid);\r
249         int i, pos = -1, len = (side > 2 ? snapData->y_grid_len : snapData->x_grid_len);\r
250 \r
251         switch(side) {\r
252           case 1: pos = activeRect.left; break;\r
253           case 2: pos = activeRect.right; break;\r
254           case 3: pos = activeRect.top; break;\r
255           case 4: pos = activeRect.bottom; break;\r
256         }\r
257 \r
258         for(i=0; i<len; i++) {\r
259             if(grid[i] == pos) break; // the dragged side originally touched another auxiliary window\r
260         }\r
261 \r
262         if(i < len) { // we were touching another sticky window: figure out how, and adapt it if needed\r
263                 KeepTouching(side, loc, pos, moveHistoryDialog);\r
264                 KeepTouching(side, loc, pos, evalGraphDialog);\r
265                 KeepTouching(side, loc, pos, engineOutputDialog);\r
266                 KeepTouching(side, loc, pos, gameListDialog);\r
267         }\r
268     }\r
269     return 0;\r
270 }\r