]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/xywindow.cpp
gcc: appease the hardening warnings
[xonotic/netradiant.git] / radiant / xywindow.cpp
1 /*
2    Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3    For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 //
23 // XY Window
24 //
25 // Leonardo Zide (leo@lokigames.com)
26 //
27
28 #include "xywindow.h"
29
30 #include <gtk/gtk.h>
31
32 #include "debugging/debugging.h"
33
34 #include "ientity.h"
35 #include "igl.h"
36 #include "ibrush.h"
37 #include "iundo.h"
38 #include "iimage.h"
39 #include "ifilesystem.h"
40 #include "os/path.h"
41 #include "image.h"
42 #include "gtkutil/messagebox.h"
43
44 #include <uilib/uilib.h>
45 #include <gdk/gdkkeysyms.h>
46 #include <cassert>
47
48 #include "generic/callback.h"
49 #include "string/string.h"
50 #include "stream/stringstream.h"
51
52 #include "scenelib.h"
53 #include "eclasslib.h"
54 #include "renderer.h"
55 #include "moduleobserver.h"
56
57 #include "gtkutil/menu.h"
58 #include "gtkutil/container.h"
59 #include "gtkutil/widget.h"
60 #include "gtkutil/glwidget.h"
61 #include "gtkutil/filechooser.h"
62 #include "gtkmisc.h"
63 #include "select.h"
64 #include "csg.h"
65 #include "brushmanip.h"
66 #include "selection.h"
67 #include "entity.h"
68 #include "camwindow.h"
69 #include "texwindow.h"
70 #include "mainframe.h"
71 #include "preferences.h"
72 #include "commands.h"
73 #include "feedback.h"
74 #include "grid.h"
75 #include "windowobservers.h"
76
77 void LoadTextureRGBA(qtexture_t *q, unsigned char *pPixels, int nWidth, int nHeight);
78
79 // d1223m
80 extern bool g_brush_always_caulk;
81
82 //!\todo Rewrite.
83 class ClipPoint {
84 public:
85     Vector3 m_ptClip;        // the 3d point
86     bool m_bSet;
87
88     ClipPoint()
89     {
90         Reset();
91     };
92
93     void Reset()
94     {
95         m_ptClip[0] = m_ptClip[1] = m_ptClip[2] = 0.0;
96         m_bSet = false;
97     }
98
99     bool Set()
100     {
101         return m_bSet;
102     }
103
104     void Set(bool b)
105     {
106         m_bSet = b;
107     }
108
109     operator Vector3 &()
110     {
111         return m_ptClip;
112     };
113
114 /*! Draw clip/path point with rasterized number label */
115     void Draw(int num, float scale);
116
117 /*! Draw clip/path point with rasterized string label */
118     void Draw(const char *label, float scale);
119 };
120
121 VIEWTYPE g_clip_viewtype;
122 bool g_bSwitch = true;
123 bool g_clip_useCaulk = false;
124 ClipPoint g_Clip1;
125 ClipPoint g_Clip2;
126 ClipPoint g_Clip3;
127 ClipPoint *g_pMovingClip = 0;
128
129 /* Drawing clip points */
130 void ClipPoint::Draw(int num, float scale)
131 {
132     StringOutputStream label(4);
133     label << num;
134     Draw(label.c_str(), scale);
135 }
136
137 void ClipPoint::Draw(const char *label, float scale)
138 {
139     // draw point
140     glPointSize(4);
141     glColor3fv(vector3_to_array(g_xywindow_globals.color_clipper));
142     glBegin(GL_POINTS);
143     glVertex3fv(vector3_to_array(m_ptClip));
144     glEnd();
145     glPointSize(1);
146
147     float offset = 2.0f / scale;
148
149     // draw label
150     glRasterPos3f(m_ptClip[0] + offset, m_ptClip[1] + offset, m_ptClip[2] + offset);
151     glCallLists(GLsizei(strlen(label)), GL_UNSIGNED_BYTE, label);
152 }
153
154 float fDiff(float f1, float f2)
155 {
156     if (f1 > f2) {
157         return f1 - f2;
158     } else {
159         return f2 - f1;
160     }
161 }
162
163 inline double ClipPoint_Intersect(const ClipPoint &clip, const Vector3 &point, VIEWTYPE viewtype, float scale)
164 {
165     int nDim1 = (viewtype == YZ) ? 1 : 0;
166     int nDim2 = (viewtype == XY) ? 1 : 2;
167     double screenDistanceSquared(vector2_length_squared(Vector2(fDiff(clip.m_ptClip[nDim1], point[nDim1]) * scale,
168                                                                 fDiff(clip.m_ptClip[nDim2], point[nDim2]) * scale)));
169     if (screenDistanceSquared < 8 * 8) {
170         return screenDistanceSquared;
171     }
172     return FLT_MAX;
173 }
174
175 inline void
176 ClipPoint_testSelect(ClipPoint &clip, const Vector3 &point, VIEWTYPE viewtype, float scale, double &bestDistance,
177                      ClipPoint *&bestClip)
178 {
179     if (clip.Set()) {
180         double distance = ClipPoint_Intersect(clip, point, viewtype, scale);
181         if (distance < bestDistance) {
182             bestDistance = distance;
183             bestClip = &clip;
184         }
185     }
186 }
187
188 inline ClipPoint *GlobalClipPoints_Find(const Vector3 &point, VIEWTYPE viewtype, float scale)
189 {
190     double bestDistance = FLT_MAX;
191     ClipPoint *bestClip = 0;
192     ClipPoint_testSelect(g_Clip1, point, viewtype, scale, bestDistance, bestClip);
193     ClipPoint_testSelect(g_Clip2, point, viewtype, scale, bestDistance, bestClip);
194     ClipPoint_testSelect(g_Clip3, point, viewtype, scale, bestDistance, bestClip);
195     return bestClip;
196 }
197
198 inline void GlobalClipPoints_Draw(float scale)
199 {
200     // Draw clip points
201     if (g_Clip1.Set()) {
202         g_Clip1.Draw(1, scale);
203     }
204     if (g_Clip2.Set()) {
205         g_Clip2.Draw(2, scale);
206     }
207     if (g_Clip3.Set()) {
208         g_Clip3.Draw(3, scale);
209     }
210 }
211
212 inline bool GlobalClipPoints_valid()
213 {
214     return g_Clip1.Set() && g_Clip2.Set();
215 }
216
217 void PlanePointsFromClipPoints(Vector3 planepts[3], const AABB &bounds, int viewtype)
218 {
219     ASSERT_MESSAGE(GlobalClipPoints_valid(), "clipper points not initialised");
220     planepts[0] = g_Clip1.m_ptClip;
221     planepts[1] = g_Clip2.m_ptClip;
222     planepts[2] = g_Clip3.m_ptClip;
223     Vector3 maxs(vector3_added(bounds.origin, bounds.extents));
224     Vector3 mins(vector3_subtracted(bounds.origin, bounds.extents));
225     if (!g_Clip3.Set()) {
226         int n = (viewtype == XY) ? 2 : (viewtype == YZ) ? 0 : 1;
227         int x = (n == 0) ? 1 : 0;
228         int y = (n == 2) ? 1 : 2;
229
230         if (n == 1) { // on viewtype XZ, flip clip points
231             planepts[0][n] = maxs[n];
232             planepts[1][n] = maxs[n];
233             planepts[2][x] = g_Clip1.m_ptClip[x];
234             planepts[2][y] = g_Clip1.m_ptClip[y];
235             planepts[2][n] = mins[n];
236         } else {
237             planepts[0][n] = mins[n];
238             planepts[1][n] = mins[n];
239             planepts[2][x] = g_Clip1.m_ptClip[x];
240             planepts[2][y] = g_Clip1.m_ptClip[y];
241             planepts[2][n] = maxs[n];
242         }
243     }
244 }
245
246 void Clip_Update()
247 {
248     Vector3 planepts[3];
249     if (!GlobalClipPoints_valid()) {
250         planepts[0] = Vector3(0, 0, 0);
251         planepts[1] = Vector3(0, 0, 0);
252         planepts[2] = Vector3(0, 0, 0);
253         Scene_BrushSetClipPlane(GlobalSceneGraph(), Plane3(0, 0, 0, 0));
254     } else {
255         AABB bounds(Vector3(0, 0, 0), Vector3(64, 64, 64));
256         PlanePointsFromClipPoints(planepts, bounds, g_clip_viewtype);
257         if (g_bSwitch) {
258             std::swap(planepts[0], planepts[1]);
259         }
260         Scene_BrushSetClipPlane(GlobalSceneGraph(), plane3_for_points(planepts[0], planepts[1], planepts[2]));
261     }
262     ClipperChangeNotify();
263 }
264
265 const char *Clip_getShader()
266 {
267     return g_clip_useCaulk ? "textures/common/caulk" : TextureBrowser_GetSelectedShader(GlobalTextureBrowser());
268 }
269
270 void Clip()
271 {
272     if (ClipMode() && GlobalClipPoints_valid()) {
273         Vector3 planepts[3];
274         AABB bounds(Vector3(0, 0, 0), Vector3(64, 64, 64));
275         PlanePointsFromClipPoints(planepts, bounds, g_clip_viewtype);
276         Scene_BrushSplitByPlane(GlobalSceneGraph(), planepts[0], planepts[1], planepts[2], Clip_getShader(),
277                                 (!g_bSwitch) ? eFront : eBack);
278         g_Clip1.Reset();
279         g_Clip2.Reset();
280         g_Clip3.Reset();
281         Clip_Update();
282         ClipperChangeNotify();
283     }
284 }
285
286 void SplitClip()
287 {
288     if (ClipMode() && GlobalClipPoints_valid()) {
289         Vector3 planepts[3];
290         AABB bounds(Vector3(0, 0, 0), Vector3(64, 64, 64));
291         PlanePointsFromClipPoints(planepts, bounds, g_clip_viewtype);
292         Scene_BrushSplitByPlane(GlobalSceneGraph(), planepts[0], planepts[1], planepts[2], Clip_getShader(),
293                                 eFrontAndBack);
294         g_Clip1.Reset();
295         g_Clip2.Reset();
296         g_Clip3.Reset();
297         Clip_Update();
298         ClipperChangeNotify();
299     }
300 }
301
302 void FlipClip()
303 {
304     g_bSwitch = !g_bSwitch;
305     Clip_Update();
306     ClipperChangeNotify();
307 }
308
309 void OnClipMode(bool enabled)
310 {
311     g_Clip1.Reset();
312     g_Clip2.Reset();
313     g_Clip3.Reset();
314
315     if (!enabled && g_pMovingClip) {
316         g_pMovingClip = 0;
317     }
318
319     Clip_Update();
320     ClipperChangeNotify();
321 }
322
323 bool ClipMode()
324 {
325     return GlobalSelectionSystem().ManipulatorMode() == SelectionSystem::eClip;
326 }
327
328 void NewClipPoint(const Vector3 &point)
329 {
330     if (g_Clip1.Set() == false) {
331         g_Clip1.m_ptClip = point;
332         g_Clip1.Set(true);
333     } else if (g_Clip2.Set() == false) {
334         g_Clip2.m_ptClip = point;
335         g_Clip2.Set(true);
336     } else if (g_Clip3.Set() == false) {
337         g_Clip3.m_ptClip = point;
338         g_Clip3.Set(true);
339     } else {
340         g_Clip1.Reset();
341         g_Clip2.Reset();
342         g_Clip3.Reset();
343         g_Clip1.m_ptClip = point;
344         g_Clip1.Set(true);
345     }
346
347     Clip_Update();
348     ClipperChangeNotify();
349 }
350
351
352 struct xywindow_globals_private_t {
353     bool d_showgrid;
354
355     // these are in the View > Show menu with Show coordinates
356     bool show_names;
357     bool show_coordinates;
358     bool show_angles;
359     bool show_outline;
360     bool show_axis;
361
362     bool d_show_work;
363
364     bool show_blocks;
365     int blockSize;
366
367     bool m_bCamXYUpdate;
368     bool m_bChaseMouse;
369     bool m_bSizePaint;
370
371     xywindow_globals_private_t() :
372             d_showgrid(true),
373
374             show_names(false),
375             show_coordinates(true),
376             show_angles(true),
377             show_outline(false),
378             show_axis(true),
379
380             d_show_work(false),
381
382             show_blocks(false),
383
384             m_bCamXYUpdate(true),
385             m_bChaseMouse(true),
386             m_bSizePaint(true)
387     {
388     }
389
390 };
391
392 xywindow_globals_t g_xywindow_globals;
393 xywindow_globals_private_t g_xywindow_globals_private;
394
395 const unsigned int RAD_NONE = 0x00;
396 const unsigned int RAD_SHIFT = 0x01;
397 const unsigned int RAD_ALT = 0x02;
398 const unsigned int RAD_CONTROL = 0x04;
399 const unsigned int RAD_PRESS = 0x08;
400 const unsigned int RAD_LBUTTON = 0x10;
401 const unsigned int RAD_MBUTTON = 0x20;
402 const unsigned int RAD_RBUTTON = 0x40;
403
404 inline ButtonIdentifier button_for_flags(unsigned int flags)
405 {
406     if (flags & RAD_LBUTTON) {
407         return c_buttonLeft;
408     }
409     if (flags & RAD_RBUTTON) {
410         return c_buttonRight;
411     }
412     if (flags & RAD_MBUTTON) {
413         return c_buttonMiddle;
414     }
415     return c_buttonInvalid;
416 }
417
418 inline ModifierFlags modifiers_for_flags(unsigned int flags)
419 {
420     ModifierFlags modifiers = c_modifierNone;
421     if (flags & RAD_SHIFT) {
422         modifiers |= c_modifierShift;
423     }
424     if (flags & RAD_CONTROL) {
425         modifiers |= c_modifierControl;
426     }
427     if (flags & RAD_ALT) {
428         modifiers |= c_modifierAlt;
429     }
430     return modifiers;
431 }
432
433 inline unsigned int buttons_for_button_and_modifiers(ButtonIdentifier button, ModifierFlags flags)
434 {
435     unsigned int buttons = 0;
436
437     switch (button.get()) {
438         case ButtonEnumeration::INVALID:
439             break;
440         case ButtonEnumeration::LEFT:
441             buttons |= RAD_LBUTTON;
442             break;
443         case ButtonEnumeration::MIDDLE:
444             buttons |= RAD_MBUTTON;
445             break;
446         case ButtonEnumeration::RIGHT:
447             buttons |= RAD_RBUTTON;
448             break;
449     }
450
451     if (bitfield_enabled(flags, c_modifierControl)) {
452         buttons |= RAD_CONTROL;
453     }
454
455     if (bitfield_enabled(flags, c_modifierShift)) {
456         buttons |= RAD_SHIFT;
457     }
458
459     if (bitfield_enabled(flags, c_modifierAlt)) {
460         buttons |= RAD_ALT;
461     }
462
463     return buttons;
464 }
465
466 inline unsigned int buttons_for_event_button(GdkEventButton *event)
467 {
468     unsigned int flags = 0;
469
470     switch (event->button) {
471         case 1:
472             flags |= RAD_LBUTTON;
473             break;
474         case 2:
475             flags |= RAD_MBUTTON;
476             break;
477         case 3:
478             flags |= RAD_RBUTTON;
479             break;
480     }
481
482     if ((event->state & GDK_CONTROL_MASK) != 0) {
483         flags |= RAD_CONTROL;
484     }
485
486     if ((event->state & GDK_SHIFT_MASK) != 0) {
487         flags |= RAD_SHIFT;
488     }
489
490     if ((event->state & GDK_MOD1_MASK) != 0) {
491         flags |= RAD_ALT;
492     }
493
494     return flags;
495 }
496
497 inline unsigned int buttons_for_state(guint state)
498 {
499     unsigned int flags = 0;
500
501     if ((state & GDK_BUTTON1_MASK) != 0) {
502         flags |= RAD_LBUTTON;
503     }
504
505     if ((state & GDK_BUTTON2_MASK) != 0) {
506         flags |= RAD_MBUTTON;
507     }
508
509     if ((state & GDK_BUTTON3_MASK) != 0) {
510         flags |= RAD_RBUTTON;
511     }
512
513     if ((state & GDK_CONTROL_MASK) != 0) {
514         flags |= RAD_CONTROL;
515     }
516
517     if ((state & GDK_SHIFT_MASK) != 0) {
518         flags |= RAD_SHIFT;
519     }
520
521     if ((state & GDK_MOD1_MASK) != 0) {
522         flags |= RAD_ALT;
523     }
524
525     return flags;
526 }
527
528
529 void XYWnd::SetScale(float f)
530 {
531     m_fScale = f;
532     updateProjection();
533     updateModelview();
534     XYWnd_Update(*this);
535 }
536
537 void XYWnd_ZoomIn(XYWnd *xy)
538 {
539     float max_scale = 64;
540     float scale = xy->Scale() * 5.0f / 4.0f;
541     if (scale > max_scale) {
542         if (xy->Scale() != max_scale) {
543             xy->SetScale(max_scale);
544         }
545     } else {
546         xy->SetScale(scale);
547     }
548 }
549
550
551 // NOTE: the zoom out factor is 4/5, we could think about customizing it
552 //  we don't go below a zoom factor corresponding to 10% of the max world size
553 //  (this has to be computed against the window size)
554 void XYWnd_ZoomOut(XYWnd *xy)
555 {
556     float min_scale = MIN(xy->Width(), xy->Height()) / (1.1f * (g_MaxWorldCoord - g_MinWorldCoord));
557     float scale = xy->Scale() * 4.0f / 5.0f;
558     if (scale < min_scale) {
559         if (xy->Scale() != min_scale) {
560             xy->SetScale(min_scale);
561         }
562     } else {
563         xy->SetScale(scale);
564     }
565 }
566
567 VIEWTYPE GlobalXYWnd_getCurrentViewType()
568 {
569     ASSERT_NOTNULL(g_pParentWnd);
570     ASSERT_NOTNULL(g_pParentWnd->ActiveXY());
571     return g_pParentWnd->ActiveXY()->GetViewType();
572 }
573
574 // =============================================================================
575 // variables
576
577 bool g_bCrossHairs = false;
578
579 ui::Menu XYWnd::m_mnuDrop(ui::null);
580
581 // this is disabled, and broken
582 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=394
583 #if 0
584                                                                                                                         void WXY_Print(){
585         long width, height;
586         width = g_pParentWnd->ActiveXY()->Width();
587         height = g_pParentWnd->ActiveXY()->Height();
588         unsigned char* img;
589         const char* filename;
590
591         filename = ui::file_dialog( MainFrame_getWindow( ), FALSE, "Save Image", 0, FILTER_BMP );
592         if ( !filename ) {
593                 return;
594         }
595
596         g_pParentWnd->ActiveXY()->MakeCurrent();
597         img = (unsigned char*)malloc( width * height * 3 );
598         glReadPixels( 0,0,width,height,GL_RGB,GL_UNSIGNED_BYTE,img );
599
600         FILE *fp;
601         fp = fopen( filename, "wb" );
602         if ( fp ) {
603                 unsigned short bits;
604                 unsigned long cmap, bfSize;
605
606                 bits = 24;
607                 cmap = 0;
608                 bfSize = 54 + width * height * 3;
609
610                 long byteswritten = 0;
611                 long pixoff = 54 + cmap * 4;
612                 short res = 0;
613                 char m1 = 'B', m2 = 'M';
614                 fwrite( &m1, 1, 1, fp );      byteswritten++; // B
615                 fwrite( &m2, 1, 1, fp );      byteswritten++; // M
616                 fwrite( &bfSize, 4, 1, fp );  byteswritten += 4; // bfSize
617                 fwrite( &res, 2, 1, fp );     byteswritten += 2; // bfReserved1
618                 fwrite( &res, 2, 1, fp );     byteswritten += 2; // bfReserved2
619                 fwrite( &pixoff, 4, 1, fp );  byteswritten += 4; // bfOffBits
620
621                 unsigned long biSize = 40, compress = 0, size = 0;
622                 long pixels = 0;
623                 unsigned short planes = 1;
624                 fwrite( &biSize, 4, 1, fp );  byteswritten += 4; // biSize
625                 fwrite( &width, 4, 1, fp );   byteswritten += 4; // biWidth
626                 fwrite( &height, 4, 1, fp );  byteswritten += 4; // biHeight
627                 fwrite( &planes, 2, 1, fp );  byteswritten += 2; // biPlanes
628                 fwrite( &bits, 2, 1, fp );    byteswritten += 2; // biBitCount
629                 fwrite( &compress, 4, 1, fp ); byteswritten += 4; // biCompression
630                 fwrite( &size, 4, 1, fp );    byteswritten += 4; // biSizeImage
631                 fwrite( &pixels, 4, 1, fp );  byteswritten += 4; // biXPelsPerMeter
632                 fwrite( &pixels, 4, 1, fp );  byteswritten += 4; // biYPelsPerMeter
633                 fwrite( &cmap, 4, 1, fp );    byteswritten += 4; // biClrUsed
634                 fwrite( &cmap, 4, 1, fp );    byteswritten += 4; // biClrImportant
635
636                 unsigned long widthDW = ( ( ( width * 24 ) + 31 ) / 32 * 4 );
637                 long row, row_size = width * 3;
638                 for ( row = 0; row < height; row++ )
639                 {
640                         unsigned char* buf = img + row * row_size;
641
642                         // write a row
643                         int col;
644                         for ( col = 0; col < row_size; col += 3 )
645                         {
646                                 putc( buf[col + 2], fp );
647                                 putc( buf[col + 1], fp );
648                                 putc( buf[col], fp );
649                         }
650                         byteswritten += row_size;
651
652                         unsigned long count;
653                         for ( count = row_size; count < widthDW; count++ )
654                         {
655                                 putc( 0, fp ); // dummy
656                                 byteswritten++;
657                         }
658                 }
659
660                 fclose( fp );
661         }
662
663         free( img );
664 }
665 #endif
666
667
668 #include "timer.h"
669
670 Timer g_chasemouse_timer;
671
672 void XYWnd::ChaseMouse()
673 {
674     float multiplier = g_chasemouse_timer.elapsed_msec() / 10.0f;
675     Scroll(float_to_integer(multiplier * m_chasemouse_delta_x), float_to_integer(multiplier * -m_chasemouse_delta_y));
676
677     //globalOutputStream() << "chasemouse: multiplier=" << multiplier << " x=" << m_chasemouse_delta_x << " y=" << m_chasemouse_delta_y << '\n';
678
679     XY_MouseMoved(m_chasemouse_current_x, m_chasemouse_current_y, getButtonState());
680     g_chasemouse_timer.start();
681 }
682
683 gboolean xywnd_chasemouse(gpointer data)
684 {
685     reinterpret_cast<XYWnd *>( data )->ChaseMouse();
686     return TRUE;
687 }
688
689 inline const int &min_int(const int &left, const int &right)
690 {
691     return std::min(left, right);
692 }
693
694 bool XYWnd::chaseMouseMotion(int pointx, int pointy)
695 {
696     m_chasemouse_delta_x = 0;
697     m_chasemouse_delta_y = 0;
698
699     if (g_xywindow_globals_private.m_bChaseMouse && getButtonState() == RAD_LBUTTON) {
700         const int epsilon = 16;
701
702         if (pointx < epsilon) {
703             m_chasemouse_delta_x = std::max(pointx, 0) - epsilon;
704         } else if ((pointx - m_nWidth) > -epsilon) {
705             m_chasemouse_delta_x = min_int((pointx - m_nWidth), 0) + epsilon;
706         }
707
708         if (pointy < epsilon) {
709             m_chasemouse_delta_y = std::max(pointy, 0) - epsilon;
710         } else if ((pointy - m_nHeight) > -epsilon) {
711             m_chasemouse_delta_y = min_int((pointy - m_nHeight), 0) + epsilon;
712         }
713
714         if (m_chasemouse_delta_y != 0 || m_chasemouse_delta_x != 0) {
715             //globalOutputStream() << "chasemouse motion: x=" << pointx << " y=" << pointy << "... ";
716             m_chasemouse_current_x = pointx;
717             m_chasemouse_current_y = pointy;
718             if (m_chasemouse_handler == 0) {
719                 //globalOutputStream() << "chasemouse timer start... ";
720                 g_chasemouse_timer.start();
721                 m_chasemouse_handler = g_idle_add(xywnd_chasemouse, this);
722             }
723             return true;
724         } else {
725             if (m_chasemouse_handler != 0) {
726                 //globalOutputStream() << "chasemouse cancel\n";
727                 g_source_remove(m_chasemouse_handler);
728                 m_chasemouse_handler = 0;
729             }
730         }
731     } else {
732         if (m_chasemouse_handler != 0) {
733             //globalOutputStream() << "chasemouse cancel\n";
734             g_source_remove(m_chasemouse_handler);
735             m_chasemouse_handler = 0;
736         }
737     }
738     return false;
739 }
740
741 // =============================================================================
742 // XYWnd class
743 Shader *XYWnd::m_state_selected = 0;
744
745 void xy_update_xor_rectangle(XYWnd &self, rect_t area)
746 {
747     if (self.GetWidget().visible()) {
748         self.m_XORRectangle.set(rectangle_from_area(area.min, area.max, self.Width(), self.Height()));
749     }
750 }
751
752 gboolean xywnd_button_press(ui::Widget widget, GdkEventButton *event, XYWnd *xywnd)
753 {
754     if (event->type == GDK_BUTTON_PRESS) {
755         g_pParentWnd->SetActiveXY(xywnd);
756
757         xywnd->ButtonState_onMouseDown(buttons_for_event_button(event));
758
759         xywnd->onMouseDown(WindowVector(event->x, event->y), button_for_button(event->button),
760                            modifiers_for_state(event->state));
761     }
762     return FALSE;
763 }
764
765 gboolean xywnd_button_release(ui::Widget widget, GdkEventButton *event, XYWnd *xywnd)
766 {
767     if (event->type == GDK_BUTTON_RELEASE) {
768         xywnd->XY_MouseUp(static_cast<int>( event->x ), static_cast<int>( event->y ), buttons_for_event_button(event));
769
770         xywnd->ButtonState_onMouseUp(buttons_for_event_button(event));
771     }
772     return FALSE;
773 }
774
775 gboolean xywnd_focus_in(ui::Widget widget, GdkEventFocus *event, XYWnd *xywnd)
776 {
777     if (event->type == GDK_FOCUS_CHANGE) {
778         if (event->in) {
779             g_pParentWnd->SetActiveXY(xywnd);
780         }
781     }
782     return FALSE;
783 }
784
785 void xywnd_motion(gdouble x, gdouble y, guint state, void *data)
786 {
787     if (reinterpret_cast<XYWnd *>( data )->chaseMouseMotion(static_cast<int>( x ), static_cast<int>( y ))) {
788         return;
789     }
790     reinterpret_cast<XYWnd *>( data )->XY_MouseMoved(static_cast<int>( x ), static_cast<int>( y ),
791                                                      buttons_for_state(state));
792 }
793
794 gboolean xywnd_wheel_scroll(ui::Widget widget, GdkEventScroll *event, XYWnd *xywnd)
795 {
796     if (event->direction == GDK_SCROLL_UP) {
797         XYWnd_ZoomIn(xywnd);
798     } else if (event->direction == GDK_SCROLL_DOWN) {
799         XYWnd_ZoomOut(xywnd);
800     }
801     return FALSE;
802 }
803
804 gboolean xywnd_size_allocate(ui::Widget widget, GtkAllocation *allocation, XYWnd *xywnd)
805 {
806     xywnd->m_nWidth = allocation->width;
807     xywnd->m_nHeight = allocation->height;
808     xywnd->updateProjection();
809     xywnd->m_window_observer->onSizeChanged(xywnd->Width(), xywnd->Height());
810     return FALSE;
811 }
812
813 gboolean xywnd_expose(ui::Widget widget, GdkEventExpose *event, XYWnd *xywnd)
814 {
815     if (glwidget_make_current(xywnd->GetWidget()) != FALSE) {
816         if (Map_Valid(g_map) && ScreenUpdates_Enabled()) {
817             GlobalOpenGL_debugAssertNoErrors();
818             xywnd->XY_Draw();
819             GlobalOpenGL_debugAssertNoErrors();
820
821             xywnd->m_XORRectangle.set(rectangle_t());
822         }
823         glwidget_swap_buffers(xywnd->GetWidget());
824     }
825     return FALSE;
826 }
827
828
829 void XYWnd_CameraMoved(XYWnd &xywnd)
830 {
831     if (g_xywindow_globals_private.m_bCamXYUpdate) {
832         XYWnd_Update(xywnd);
833     }
834 }
835
836 XYWnd::XYWnd() :
837         m_gl_widget(glwidget_new(FALSE)),
838         m_deferredDraw(WidgetQueueDrawCaller(m_gl_widget)),
839         m_deferred_motion(xywnd_motion, this),
840         m_parent(ui::null),
841         m_window_observer(NewWindowObserver()),
842         m_XORRectangle(m_gl_widget),
843         m_chasemouse_handler(0)
844 {
845     m_bActive = false;
846     m_buttonstate = 0;
847
848     m_bNewBrushDrag = false;
849     m_move_started = false;
850     m_zoom_started = false;
851
852     m_nWidth = 0;
853     m_nHeight = 0;
854
855     m_vOrigin[0] = 0;
856     m_vOrigin[1] = 20;
857     m_vOrigin[2] = 46;
858     m_fScale = 1;
859     m_viewType = XY;
860
861     m_backgroundActivated = false;
862     m_alpha = 1.0f;
863     m_xmin = 0.0f;
864     m_ymin = 0.0f;
865     m_xmax = 0.0f;
866     m_ymax = 0.0f;
867
868     m_entityCreate = false;
869
870     m_mnuDrop = ui::Menu(ui::null);
871
872     GlobalWindowObservers_add(m_window_observer);
873     GlobalWindowObservers_connectWidget(m_gl_widget);
874
875     m_window_observer->setRectangleDrawCallback(ReferenceCaller<XYWnd, void(rect_t), xy_update_xor_rectangle>(*this));
876     m_window_observer->setView(m_view);
877
878     g_object_ref(m_gl_widget._handle);
879
880     gtk_widget_set_events(m_gl_widget,
881                           GDK_DESTROY | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
882                           GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK);
883     gtk_widget_set_can_focus(m_gl_widget, true);
884
885     m_sizeHandler = m_gl_widget.connect("size_allocate", G_CALLBACK(xywnd_size_allocate), this);
886     m_exposeHandler = m_gl_widget.on_render(G_CALLBACK(xywnd_expose), this);
887
888     m_gl_widget.connect("button_press_event", G_CALLBACK(xywnd_button_press), this);
889     m_gl_widget.connect("button_release_event", G_CALLBACK(xywnd_button_release), this);
890     m_gl_widget.connect("focus_in_event", G_CALLBACK(xywnd_focus_in), this);
891     m_gl_widget.connect("motion_notify_event", G_CALLBACK(DeferredMotion::gtk_motion), &m_deferred_motion);
892
893     m_gl_widget.connect("scroll_event", G_CALLBACK(xywnd_wheel_scroll), this);
894
895     Map_addValidCallback(g_map, DeferredDrawOnMapValidChangedCaller(m_deferredDraw));
896
897     updateProjection();
898     updateModelview();
899
900     AddSceneChangeCallback(ReferenceCaller<XYWnd, void(), &XYWnd_Update>(*this));
901     AddCameraMovedCallback(ReferenceCaller<XYWnd, void(), &XYWnd_CameraMoved>(*this));
902
903     PressedButtons_connect(g_pressedButtons, m_gl_widget);
904
905     onMouseDown.connectLast(makeSignalHandler3(MouseDownCaller(), *this));
906 }
907
908 XYWnd::~XYWnd()
909 {
910     onDestroyed();
911
912     if (m_mnuDrop) {
913         m_mnuDrop.destroy();
914         m_mnuDrop = ui::Menu(ui::null);
915     }
916
917     g_signal_handler_disconnect(G_OBJECT(m_gl_widget), m_sizeHandler);
918     g_signal_handler_disconnect(G_OBJECT(m_gl_widget), m_exposeHandler);
919
920     m_gl_widget.unref();
921
922     m_window_observer->release();
923 }
924
925 void XYWnd::captureStates()
926 {
927     m_state_selected = GlobalShaderCache().capture("$XY_OVERLAY");
928 }
929
930 void XYWnd::releaseStates()
931 {
932     GlobalShaderCache().release("$XY_OVERLAY");
933 }
934
935 const Vector3 &XYWnd::GetOrigin()
936 {
937     return m_vOrigin;
938 }
939
940 void XYWnd::SetOrigin(const Vector3 &origin)
941 {
942     m_vOrigin = origin;
943     updateModelview();
944 }
945
946 void XYWnd::Scroll(int x, int y)
947 {
948     int nDim1 = (m_viewType == YZ) ? 1 : 0;
949     int nDim2 = (m_viewType == XY) ? 1 : 2;
950     m_vOrigin[nDim1] += x / m_fScale;
951     m_vOrigin[nDim2] += y / m_fScale;
952     updateModelview();
953     queueDraw();
954 }
955
956 unsigned int Clipper_buttons()
957 {
958     return RAD_LBUTTON;
959 }
960
961 void XYWnd::DropClipPoint(int pointx, int pointy)
962 {
963     Vector3 point;
964
965     XY_ToPoint(pointx, pointy, point);
966
967     Vector3 mid;
968     Select_GetMid(mid);
969     g_clip_viewtype = static_cast<VIEWTYPE>( GetViewType());
970     const int nDim = (g_clip_viewtype == YZ) ? 0 : ((g_clip_viewtype == XZ) ? 1 : 2);
971     point[nDim] = mid[nDim];
972     vector3_snap(point, GetSnapGridSize());
973     NewClipPoint(point);
974 }
975
976 void XYWnd::Clipper_OnLButtonDown(int x, int y)
977 {
978     Vector3 mousePosition;
979     XY_ToPoint(x, y, mousePosition);
980     g_pMovingClip = GlobalClipPoints_Find(mousePosition, (VIEWTYPE) m_viewType, m_fScale);
981     if (!g_pMovingClip) {
982         DropClipPoint(x, y);
983     }
984 }
985
986 void XYWnd::Clipper_OnLButtonUp(int x, int y)
987 {
988     if (g_pMovingClip) {
989         g_pMovingClip = 0;
990     }
991 }
992
993 void XYWnd::Clipper_OnMouseMoved(int x, int y)
994 {
995     if (g_pMovingClip) {
996         XY_ToPoint(x, y, g_pMovingClip->m_ptClip);
997         XY_SnapToGrid(g_pMovingClip->m_ptClip);
998         Clip_Update();
999         ClipperChangeNotify();
1000     }
1001 }
1002
1003 void XYWnd::Clipper_Crosshair_OnMouseMoved(int x, int y)
1004 {
1005     Vector3 mousePosition;
1006     XY_ToPoint(x, y, mousePosition);
1007     if (ClipMode() && GlobalClipPoints_Find(mousePosition, (VIEWTYPE) m_viewType, m_fScale) != 0) {
1008         GdkCursor *cursor;
1009         cursor = gdk_cursor_new(GDK_CROSSHAIR);
1010         gdk_window_set_cursor(gtk_widget_get_window(m_gl_widget), cursor);
1011         gdk_cursor_unref(cursor);
1012     } else {
1013         gdk_window_set_cursor(gtk_widget_get_window(m_gl_widget), 0);
1014     }
1015 }
1016
1017 unsigned int MoveCamera_buttons()
1018 {
1019     return RAD_CONTROL | (g_glwindow_globals.m_nMouseType == ETwoButton ? RAD_RBUTTON : RAD_MBUTTON);
1020 }
1021
1022 void XYWnd_PositionCamera(XYWnd *xywnd, int x, int y, CamWnd &camwnd)
1023 {
1024     Vector3 origin(Camera_getOrigin(camwnd));
1025     xywnd->XY_ToPoint(x, y, origin);
1026     xywnd->XY_SnapToGrid(origin);
1027     Camera_setOrigin(camwnd, origin);
1028 }
1029
1030 unsigned int OrientCamera_buttons()
1031 {
1032     if (g_glwindow_globals.m_nMouseType == ETwoButton) {
1033         return RAD_RBUTTON | RAD_SHIFT | RAD_CONTROL;
1034     }
1035     return RAD_MBUTTON;
1036 }
1037
1038 void XYWnd_OrientCamera(XYWnd *xywnd, int x, int y, CamWnd &camwnd)
1039 {
1040     Vector3 point = g_vector3_identity;
1041     xywnd->XY_ToPoint(x, y, point);
1042     xywnd->XY_SnapToGrid(point);
1043     vector3_subtract(point, Camera_getOrigin(camwnd));
1044
1045     int n1 = (xywnd->GetViewType() == XY) ? 1 : 2;
1046     int n2 = (xywnd->GetViewType() == YZ) ? 1 : 0;
1047     int nAngle = (xywnd->GetViewType() == XY) ? CAMERA_YAW : CAMERA_PITCH;
1048     if (point[n1] || point[n2]) {
1049         Vector3 angles(Camera_getAngles(camwnd));
1050         angles[nAngle] = static_cast<float>( radians_to_degrees(atan2(point[n1], point[n2])));
1051         Camera_setAngles(camwnd, angles);
1052     }
1053 }
1054
1055 /*
1056    ==============
1057    NewBrushDrag
1058    ==============
1059  */
1060 unsigned int NewBrushDrag_buttons()
1061 {
1062     return RAD_LBUTTON;
1063 }
1064
1065 void XYWnd::NewBrushDrag_Begin(int x, int y)
1066 {
1067     m_NewBrushDrag = 0;
1068     m_nNewBrushPressx = x;
1069     m_nNewBrushPressy = y;
1070
1071     m_bNewBrushDrag = true;
1072     GlobalUndoSystem().start();
1073 }
1074
1075 void XYWnd::NewBrushDrag_End(int x, int y)
1076 {
1077     if (m_NewBrushDrag != 0) {
1078         GlobalUndoSystem().finish("brushDragNew");
1079     }
1080 }
1081
1082 void XYWnd::NewBrushDrag(int x, int y)
1083 {
1084     Vector3 mins, maxs;
1085     XY_ToPoint(m_nNewBrushPressx, m_nNewBrushPressy, mins);
1086     XY_SnapToGrid(mins);
1087     XY_ToPoint(x, y, maxs);
1088     XY_SnapToGrid(maxs);
1089
1090     int nDim = (m_viewType == XY) ? 2 : (m_viewType == YZ) ? 0 : 1;
1091
1092     mins[nDim] = float_snapped(Select_getWorkZone().d_work_min[nDim], GetSnapGridSize());
1093     maxs[nDim] = float_snapped(Select_getWorkZone().d_work_max[nDim], GetSnapGridSize());
1094
1095     if (maxs[nDim] <= mins[nDim]) {
1096         maxs[nDim] = mins[nDim] + GetGridSize();
1097     }
1098
1099     for (int i = 0; i < 3; i++) {
1100         if (mins[i] == maxs[i]) {
1101             return; // don't create a degenerate brush
1102         }
1103         if (mins[i] > maxs[i]) {
1104             float temp = mins[i];
1105             mins[i] = maxs[i];
1106             maxs[i] = temp;
1107         }
1108     }
1109
1110     if (m_NewBrushDrag == 0) {
1111         NodeSmartReference node(GlobalBrushCreator().createBrush());
1112         Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(node);
1113
1114         scene::Path brushpath(makeReference(GlobalSceneGraph().root()));
1115         brushpath.push(makeReference(*Map_GetWorldspawn(g_map)));
1116         brushpath.push(makeReference(node.get()));
1117         selectPath(brushpath, true);
1118
1119         m_NewBrushDrag = node.get_pointer();
1120     }
1121
1122     // d1223m
1123     //Scene_BrushResize_Selected(GlobalSceneGraph(), aabb_for_minmax(mins, maxs), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()));
1124     Scene_BrushResize_Selected(GlobalSceneGraph(), aabb_for_minmax(mins, maxs),
1125                                g_brush_always_caulk ?
1126                                "textures/common/caulk" : TextureBrowser_GetSelectedShader(GlobalTextureBrowser()));
1127 }
1128
1129 void entitycreate_activated(ui::Widget item)
1130 {
1131     scene::Node *world_node = Map_FindWorldspawn(g_map);
1132     const char *entity_name = gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(item))));
1133
1134     if (!(world_node && string_equal(entity_name, "worldspawn"))) {
1135         g_pParentWnd->ActiveXY()->OnEntityCreate(entity_name);
1136     } else {
1137         GlobalRadiant().m_pfnMessageBox(MainFrame_getWindow(), "There's already a worldspawn in your map!"
1138                                                 "",
1139                                         "Info",
1140                                         eMB_OK,
1141                                         eMB_ICONDEFAULT);
1142     }
1143 }
1144
1145 void EntityClassMenu_addItem(ui::Menu menu, const char *name)
1146 {
1147     auto item = ui::MenuItem(name);
1148     item.connect("activate", G_CALLBACK(entitycreate_activated), item);
1149     item.show();
1150     menu_add_item(menu, item);
1151 }
1152
1153 class EntityClassMenuInserter : public EntityClassVisitor {
1154     typedef std::pair<ui::Menu, CopiedString> MenuPair;
1155     typedef std::vector<MenuPair> MenuStack;
1156     MenuStack m_stack;
1157     CopiedString m_previous;
1158 public:
1159     EntityClassMenuInserter(ui::Menu menu)
1160     {
1161         m_stack.reserve(2);
1162         m_stack.push_back(MenuPair(menu, ""));
1163     }
1164
1165     ~EntityClassMenuInserter()
1166     {
1167         if (!string_empty(m_previous.c_str())) {
1168             addItem(m_previous.c_str(), "");
1169         }
1170     }
1171
1172     void visit(EntityClass *e)
1173     {
1174         ASSERT_MESSAGE(!string_empty(e->name()), "entity-class has no name");
1175         if (!string_empty(m_previous.c_str())) {
1176             addItem(m_previous.c_str(), e->name());
1177         }
1178         m_previous = e->name();
1179     }
1180
1181     void pushMenu(const CopiedString &name)
1182     {
1183         auto item = ui::MenuItem(name.c_str());
1184         item.show();
1185         m_stack.back().first.add(item);
1186
1187         auto submenu = ui::Menu(ui::New);
1188         gtk_menu_item_set_submenu(item, submenu);
1189
1190         m_stack.push_back(MenuPair(submenu, name));
1191     }
1192
1193     void popMenu()
1194     {
1195         m_stack.pop_back();
1196     }
1197
1198     void addItem(const char *name, const char *next)
1199     {
1200         const char *underscore = strchr(name, '_');
1201
1202         if (underscore != 0 && underscore != name) {
1203             bool nextEqual = string_equal_n(name, next, (underscore + 1) - name);
1204             const char *parent = m_stack.back().second.c_str();
1205
1206             if (!string_empty(parent)
1207                 && string_length(parent) == std::size_t(underscore - name)
1208                 && string_equal_n(name, parent, underscore - name)) { // this is a child
1209             } else if (nextEqual) {
1210                 if (m_stack.size() == 2) {
1211                     popMenu();
1212                 }
1213                 pushMenu(CopiedString(StringRange(name, underscore)));
1214             } else if (m_stack.size() == 2) {
1215                 popMenu();
1216             }
1217         } else if (m_stack.size() == 2) {
1218             popMenu();
1219         }
1220
1221         EntityClassMenu_addItem(m_stack.back().first, name);
1222     }
1223 };
1224
1225 void XYWnd::OnContextMenu()
1226 {
1227     if (g_xywindow_globals.m_bRightClick == false) {
1228         return;
1229     }
1230
1231     if (!m_mnuDrop) { // first time, load it up
1232         auto menu = m_mnuDrop = ui::Menu(ui::New);
1233
1234         EntityClassMenuInserter inserter(menu);
1235         GlobalEntityClassManager().forEach(inserter);
1236     }
1237
1238     gtk_menu_popup(m_mnuDrop, 0, 0, 0, 0, 1, GDK_CURRENT_TIME);
1239 }
1240
1241 FreezePointer g_xywnd_freezePointer;
1242
1243 unsigned int Move_buttons()
1244 {
1245     return RAD_RBUTTON;
1246 }
1247
1248 void XYWnd_moveDelta(int x, int y, unsigned int state, void *data)
1249 {
1250     reinterpret_cast<XYWnd *>( data )->EntityCreate_MouseMove(x, y);
1251     reinterpret_cast<XYWnd *>( data )->Scroll(-x, y);
1252 }
1253
1254 gboolean XYWnd_Move_focusOut(ui::Widget widget, GdkEventFocus *event, XYWnd *xywnd)
1255 {
1256     xywnd->Move_End();
1257     return FALSE;
1258 }
1259
1260 void XYWnd::Move_Begin()
1261 {
1262     if (m_move_started) {
1263         Move_End();
1264     }
1265     m_move_started = true;
1266     g_xywnd_freezePointer.freeze_pointer(m_parent ? m_parent : MainFrame_getWindow(), XYWnd_moveDelta, this);
1267     m_move_focusOut = m_gl_widget.connect("focus_out_event", G_CALLBACK(XYWnd_Move_focusOut), this);
1268 }
1269
1270 void XYWnd::Move_End()
1271 {
1272     m_move_started = false;
1273     g_xywnd_freezePointer.unfreeze_pointer(m_parent ? m_parent : MainFrame_getWindow());
1274     g_signal_handler_disconnect(G_OBJECT(m_gl_widget), m_move_focusOut);
1275 }
1276
1277 unsigned int Zoom_buttons()
1278 {
1279     return RAD_RBUTTON | RAD_SHIFT;
1280 }
1281
1282 int g_dragZoom = 0;
1283
1284 void XYWnd_zoomDelta(int x, int y, unsigned int state, void *data)
1285 {
1286     if (y != 0) {
1287         g_dragZoom += y;
1288
1289         while (abs(g_dragZoom) > 8) {
1290             if (g_dragZoom > 0) {
1291                 XYWnd_ZoomOut(reinterpret_cast<XYWnd *>( data ));
1292                 g_dragZoom -= 8;
1293             } else {
1294                 XYWnd_ZoomIn(reinterpret_cast<XYWnd *>( data ));
1295                 g_dragZoom += 8;
1296             }
1297         }
1298     }
1299 }
1300
1301 gboolean XYWnd_Zoom_focusOut(ui::Widget widget, GdkEventFocus *event, XYWnd *xywnd)
1302 {
1303     xywnd->Zoom_End();
1304     return FALSE;
1305 }
1306
1307 void XYWnd::Zoom_Begin()
1308 {
1309     if (m_zoom_started) {
1310         Zoom_End();
1311     }
1312     m_zoom_started = true;
1313     g_dragZoom = 0;
1314     g_xywnd_freezePointer.freeze_pointer(m_parent ? m_parent : MainFrame_getWindow(), XYWnd_zoomDelta, this);
1315     m_zoom_focusOut = m_gl_widget.connect("focus_out_event", G_CALLBACK(XYWnd_Zoom_focusOut), this);
1316 }
1317
1318 void XYWnd::Zoom_End()
1319 {
1320     m_zoom_started = false;
1321     g_xywnd_freezePointer.unfreeze_pointer(m_parent ? m_parent : MainFrame_getWindow());
1322     g_signal_handler_disconnect(G_OBJECT(m_gl_widget), m_zoom_focusOut);
1323 }
1324
1325 // makes sure the selected brush or camera is in view
1326 void XYWnd::PositionView(const Vector3 &position)
1327 {
1328     int nDim1 = (m_viewType == YZ) ? 1 : 0;
1329     int nDim2 = (m_viewType == XY) ? 1 : 2;
1330
1331     m_vOrigin[nDim1] = position[nDim1];
1332     m_vOrigin[nDim2] = position[nDim2];
1333
1334     updateModelview();
1335
1336     XYWnd_Update(*this);
1337 }
1338
1339 void XYWnd::SetViewType(VIEWTYPE viewType)
1340 {
1341     m_viewType = viewType;
1342     updateModelview();
1343
1344     if (m_parent) {
1345         gtk_window_set_title(m_parent, ViewType_getTitle(m_viewType));
1346     }
1347 }
1348
1349
1350 inline WindowVector WindowVector_forInteger(int x, int y)
1351 {
1352     return WindowVector(static_cast<float>( x ), static_cast<float>( y ));
1353 }
1354
1355 void XYWnd::mouseDown(const WindowVector &position, ButtonIdentifier button, ModifierFlags modifiers)
1356 {
1357     XY_MouseDown(static_cast<int>( position.x()), static_cast<int>( position.y()),
1358                  buttons_for_button_and_modifiers(button, modifiers));
1359 }
1360
1361 void XYWnd::XY_MouseDown(int x, int y, unsigned int buttons)
1362 {
1363     if (buttons == Move_buttons()) {
1364         Move_Begin();
1365         EntityCreate_MouseDown(x, y);
1366     } else if (buttons == Zoom_buttons()) {
1367         Zoom_Begin();
1368     } else if (ClipMode() && buttons == Clipper_buttons()) {
1369         Clipper_OnLButtonDown(x, y);
1370     } else if (buttons == NewBrushDrag_buttons() && GlobalSelectionSystem().countSelected() == 0) {
1371         NewBrushDrag_Begin(x, y);
1372     }
1373         // control mbutton = move camera
1374     else if (buttons == MoveCamera_buttons()) {
1375         XYWnd_PositionCamera(this, x, y, *g_pParentWnd->GetCamWnd());
1376     }
1377         // mbutton = angle camera
1378     else if (buttons == OrientCamera_buttons()) {
1379         XYWnd_OrientCamera(this, x, y, *g_pParentWnd->GetCamWnd());
1380     } else {
1381         m_window_observer->onMouseDown(WindowVector_forInteger(x, y), button_for_flags(buttons),
1382                                        modifiers_for_flags(buttons));
1383     }
1384 }
1385
1386 void XYWnd::XY_MouseUp(int x, int y, unsigned int buttons)
1387 {
1388     if (m_move_started) {
1389         Move_End();
1390         EntityCreate_MouseUp(x, y);
1391     } else if (m_zoom_started) {
1392         Zoom_End();
1393     } else if (ClipMode() && buttons == Clipper_buttons()) {
1394         Clipper_OnLButtonUp(x, y);
1395     } else if (m_bNewBrushDrag) {
1396         m_bNewBrushDrag = false;
1397         NewBrushDrag_End(x, y);
1398     } else {
1399         m_window_observer->onMouseUp(WindowVector_forInteger(x, y), button_for_flags(buttons),
1400                                      modifiers_for_flags(buttons));
1401     }
1402 }
1403
1404 void XYWnd::XY_MouseMoved(int x, int y, unsigned int buttons)
1405 {
1406     // rbutton = drag xy origin
1407     if (m_move_started) {
1408     }
1409         // zoom in/out
1410     else if (m_zoom_started) {
1411     } else if (ClipMode() && g_pMovingClip != 0) {
1412         Clipper_OnMouseMoved(x, y);
1413     }
1414         // lbutton without selection = drag new brush
1415     else if (m_bNewBrushDrag) {
1416         NewBrushDrag(x, y);
1417     }
1418
1419         // control mbutton = move camera
1420     else if (getButtonState() == MoveCamera_buttons()) {
1421         XYWnd_PositionCamera(this, x, y, *g_pParentWnd->GetCamWnd());
1422     }
1423
1424         // mbutton = angle camera
1425     else if (getButtonState() == OrientCamera_buttons()) {
1426         XYWnd_OrientCamera(this, x, y, *g_pParentWnd->GetCamWnd());
1427     } else {
1428         m_window_observer->onMouseMotion(WindowVector_forInteger(x, y), modifiers_for_flags(buttons));
1429
1430         m_mousePosition[0] = m_mousePosition[1] = m_mousePosition[2] = 0.0;
1431         XY_ToPoint(x, y, m_mousePosition);
1432         XY_SnapToGrid(m_mousePosition);
1433
1434         StringOutputStream status(64);
1435         status << "x:: " << FloatFormat(m_mousePosition[0], 6, 1)
1436                << "  y:: " << FloatFormat(m_mousePosition[1], 6, 1)
1437                << "  z:: " << FloatFormat(m_mousePosition[2], 6, 1);
1438         g_pParentWnd->SetStatusText(g_pParentWnd->m_position_status, status.c_str());
1439
1440         if (g_bCrossHairs) {
1441             XYWnd_Update(*this);
1442         }
1443
1444         Clipper_Crosshair_OnMouseMoved(x, y);
1445     }
1446 }
1447
1448 void XYWnd::EntityCreate_MouseDown(int x, int y)
1449 {
1450     m_entityCreate = true;
1451     m_entityCreate_x = x;
1452     m_entityCreate_y = y;
1453 }
1454
1455 void XYWnd::EntityCreate_MouseMove(int x, int y)
1456 {
1457     if (m_entityCreate && (m_entityCreate_x != x || m_entityCreate_y != y)) {
1458         m_entityCreate = false;
1459     }
1460 }
1461
1462 void XYWnd::EntityCreate_MouseUp(int x, int y)
1463 {
1464     if (m_entityCreate) {
1465         m_entityCreate = false;
1466         OnContextMenu();
1467     }
1468 }
1469
1470 inline float screen_normalised(int pos, unsigned int size)
1471 {
1472     return ((2.0f * pos) / size) - 1.0f;
1473 }
1474
1475 inline float normalised_to_world(float normalised, float world_origin, float normalised2world_scale)
1476 {
1477     return world_origin + normalised * normalised2world_scale;
1478 }
1479
1480
1481 // TTimo: watch it, this doesn't init one of the 3 coords
1482 void XYWnd::XY_ToPoint(int x, int y, Vector3 &point)
1483 {
1484     float normalised2world_scale_x = m_nWidth / 2 / m_fScale;
1485     float normalised2world_scale_y = m_nHeight / 2 / m_fScale;
1486     if (m_viewType == XY) {
1487         point[0] = normalised_to_world(screen_normalised(x, m_nWidth), m_vOrigin[0], normalised2world_scale_x);
1488         point[1] = normalised_to_world(-screen_normalised(y, m_nHeight), m_vOrigin[1], normalised2world_scale_y);
1489     } else if (m_viewType == YZ) {
1490         point[1] = normalised_to_world(screen_normalised(x, m_nWidth), m_vOrigin[1], normalised2world_scale_x);
1491         point[2] = normalised_to_world(-screen_normalised(y, m_nHeight), m_vOrigin[2], normalised2world_scale_y);
1492     } else {
1493         point[0] = normalised_to_world(screen_normalised(x, m_nWidth), m_vOrigin[0], normalised2world_scale_x);
1494         point[2] = normalised_to_world(-screen_normalised(y, m_nHeight), m_vOrigin[2], normalised2world_scale_y);
1495     }
1496 }
1497
1498 void XYWnd::XY_SnapToGrid(Vector3 &point)
1499 {
1500     if (m_viewType == XY) {
1501         point[0] = float_snapped(point[0], GetSnapGridSize());
1502         point[1] = float_snapped(point[1], GetSnapGridSize());
1503     } else if (m_viewType == YZ) {
1504         point[1] = float_snapped(point[1], GetSnapGridSize());
1505         point[2] = float_snapped(point[2], GetSnapGridSize());
1506     } else {
1507         point[0] = float_snapped(point[0], GetSnapGridSize());
1508         point[2] = float_snapped(point[2], GetSnapGridSize());
1509     }
1510 }
1511
1512 void XYWnd::XY_LoadBackgroundImage(const char *name)
1513 {
1514     const char *relative = path_make_relative(name, GlobalFileSystem().findRoot(name));
1515     if (relative == name) {
1516         globalOutputStream() << "WARNING: could not extract the relative path, using full path instead\n";
1517     }
1518
1519     char fileNameWithoutExt[512];
1520     strncpy(fileNameWithoutExt, relative, sizeof(fileNameWithoutExt) - 1);
1521     fileNameWithoutExt[512 - 1] = '\0';
1522     fileNameWithoutExt[strlen(fileNameWithoutExt) - 4] = '\0';
1523
1524     Image *image = QERApp_LoadImage(0, fileNameWithoutExt);
1525     if (!image) {
1526         globalOutputStream() << "Could not load texture " << fileNameWithoutExt << "\n";
1527         return;
1528     }
1529     g_pParentWnd->ActiveXY()->m_tex = (qtexture_t *) malloc(sizeof(qtexture_t));
1530     LoadTextureRGBA(g_pParentWnd->ActiveXY()->XYWnd::m_tex, image->getRGBAPixels(), image->getWidth(),
1531                     image->getHeight());
1532     globalOutputStream() << "Loaded background texture " << relative << "\n";
1533     g_pParentWnd->ActiveXY()->m_backgroundActivated = true;
1534
1535     int m_ix, m_iy;
1536     switch (g_pParentWnd->ActiveXY()->m_viewType) {
1537         case XY:
1538             m_ix = 0;
1539             m_iy = 1;
1540             break;
1541         case XZ:
1542             m_ix = 0;
1543             m_iy = 2;
1544             break;
1545         case YZ:
1546             m_ix = 1;
1547             m_iy = 2;
1548             break;
1549         default:
1550             assert(false);
1551     }
1552
1553     Vector3 min, max;
1554     Select_GetBounds(min, max);
1555     g_pParentWnd->ActiveXY()->m_xmin = min[m_ix];
1556     g_pParentWnd->ActiveXY()->m_ymin = min[m_iy];
1557     g_pParentWnd->ActiveXY()->m_xmax = max[m_ix];
1558     g_pParentWnd->ActiveXY()->m_ymax = max[m_iy];
1559 }
1560
1561 void XYWnd::XY_DisableBackground(void)
1562 {
1563     g_pParentWnd->ActiveXY()->m_backgroundActivated = false;
1564     if (g_pParentWnd->ActiveXY()->m_tex) {
1565         free(g_pParentWnd->ActiveXY()->m_tex);
1566     }
1567     g_pParentWnd->ActiveXY()->m_tex = NULL;
1568 }
1569
1570 void WXY_BackgroundSelect(void)
1571 {
1572     bool brushesSelected = Scene_countSelectedBrushes(GlobalSceneGraph()) != 0;
1573     if (!brushesSelected) {
1574         ui::alert(ui::root, "You have to select some brushes to get the bounding box for.\n",
1575                   "No selection", ui::alert_type::OK, ui::alert_icon::Error);
1576         return;
1577     }
1578
1579     const char *filename = MainFrame_getWindow().file_dialog(TRUE, "Background Image", NULL, NULL);
1580     g_pParentWnd->ActiveXY()->XY_DisableBackground();
1581     if (filename) {
1582         g_pParentWnd->ActiveXY()->XY_LoadBackgroundImage(filename);
1583     }
1584 }
1585
1586 /*
1587    ============================================================================
1588
1589    DRAWING
1590
1591    ============================================================================
1592  */
1593
1594 /*
1595    ==============
1596    XY_DrawGrid
1597    ==============
1598  */
1599
1600 double two_to_the_power(int power)
1601 {
1602     return pow(2.0f, power);
1603 }
1604
1605 void XYWnd::XY_DrawAxis(void)
1606 {
1607     if (g_xywindow_globals_private.show_axis) {
1608         const char g_AxisName[3] = {'X', 'Y', 'Z'};
1609         const int nDim1 = (m_viewType == YZ) ? 1 : 0;
1610         const int nDim2 = (m_viewType == XY) ? 1 : 2;
1611         const int w = (m_nWidth / 2 / m_fScale);
1612         const int h = (m_nHeight / 2 / m_fScale);
1613
1614         const Vector3 &colourX = (m_viewType == YZ) ? g_xywindow_globals.AxisColorY : g_xywindow_globals.AxisColorX;
1615         const Vector3 &colourY = (m_viewType == XY) ? g_xywindow_globals.AxisColorY : g_xywindow_globals.AxisColorZ;
1616
1617         // draw two lines with corresponding axis colors to highlight current view
1618         // horizontal line: nDim1 color
1619         glLineWidth(2);
1620         glBegin(GL_LINES);
1621         glColor3fv(vector3_to_array(colourX));
1622         glVertex2f(m_vOrigin[nDim1] - w + 40 / m_fScale, m_vOrigin[nDim2] + h - 45 / m_fScale);
1623         glVertex2f(m_vOrigin[nDim1] - w + 65 / m_fScale, m_vOrigin[nDim2] + h - 45 / m_fScale);
1624         glVertex2f(0, 0);
1625         glVertex2f(32 / m_fScale, 0);
1626         glColor3fv(vector3_to_array(colourY));
1627         glVertex2f(m_vOrigin[nDim1] - w + 40 / m_fScale, m_vOrigin[nDim2] + h - 45 / m_fScale);
1628         glVertex2f(m_vOrigin[nDim1] - w + 40 / m_fScale, m_vOrigin[nDim2] + h - 20 / m_fScale);
1629         glVertex2f(0, 0);
1630         glVertex2f(0, 32 / m_fScale);
1631         glEnd();
1632         glLineWidth(1);
1633         // now print axis symbols
1634         glColor3fv(vector3_to_array(colourX));
1635         glRasterPos2f(m_vOrigin[nDim1] - w + 55 / m_fScale, m_vOrigin[nDim2] + h - 55 / m_fScale);
1636         GlobalOpenGL().drawChar(g_AxisName[nDim1]);
1637         glRasterPos2f(28 / m_fScale, -10 / m_fScale);
1638         GlobalOpenGL().drawChar(g_AxisName[nDim1]);
1639         glColor3fv(vector3_to_array(colourY));
1640         glRasterPos2f(m_vOrigin[nDim1] - w + 25 / m_fScale, m_vOrigin[nDim2] + h - 30 / m_fScale);
1641         GlobalOpenGL().drawChar(g_AxisName[nDim2]);
1642         glRasterPos2f(-10 / m_fScale, 28 / m_fScale);
1643         GlobalOpenGL().drawChar(g_AxisName[nDim2]);
1644     }
1645 }
1646
1647 void XYWnd::XY_DrawBackground(void)
1648 {
1649     glPushAttrib(GL_ALL_ATTRIB_BITS);
1650
1651     glEnable(GL_TEXTURE_2D);
1652     glEnable(GL_BLEND);
1653     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1654     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
1655     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
1656     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
1657
1658     glPolygonMode(GL_FRONT, GL_FILL);
1659
1660     glBindTexture(GL_TEXTURE_2D, m_tex->texture_number);
1661     glBegin(GL_QUADS);
1662
1663     glColor4f(1.0, 1.0, 1.0, m_alpha);
1664     glTexCoord2f(0.0, 1.0);
1665     glVertex2f(m_xmin, m_ymin);
1666
1667     glTexCoord2f(1.0, 1.0);
1668     glVertex2f(m_xmax, m_ymin);
1669
1670     glTexCoord2f(1.0, 0.0);
1671     glVertex2f(m_xmax, m_ymax);
1672
1673     glTexCoord2f(0.0, 0.0);
1674     glVertex2f(m_xmin, m_ymax);
1675
1676     glEnd();
1677     glBindTexture(GL_TEXTURE_2D, 0);
1678
1679     glPopAttrib();
1680 }
1681
1682 void XYWnd::XY_DrawGrid(void)
1683 {
1684     float x, y, xb, xe, yb, ye;
1685     float w, h, a;
1686     char text[32];
1687     float step, minor_step, stepx, stepy;
1688     step = minor_step = stepx = stepy = GetGridSize();
1689
1690     int minor_power = Grid_getPower();
1691     int mask;
1692
1693     while ((minor_step * m_fScale) <= 4.0f) { // make sure minor grid spacing is at least 4 pixels on the screen
1694         ++minor_power;
1695         minor_step *= 2;
1696     }
1697     int power = minor_power;
1698     while ((power % 3) != 0 ||
1699            (step * m_fScale) <= 32.0f) { // make sure major grid spacing is at least 32 pixels on the screen
1700         ++power;
1701         step = float(two_to_the_power(power));
1702     }
1703     mask = (1 << (power - minor_power)) - 1;
1704     while ((stepx * m_fScale) <= 32.0f) { // text step x must be at least 32
1705         stepx *= 2;
1706     }
1707     while ((stepy * m_fScale) <= 32.0f) { // text step y must be at least 32
1708         stepy *= 2;
1709     }
1710
1711     a = ((GetSnapGridSize() > 0.0f) ? 1.0f : 0.3f);
1712
1713     glDisable(GL_TEXTURE_2D);
1714     glDisable(GL_TEXTURE_1D);
1715     glDisable(GL_DEPTH_TEST);
1716     glDisable(GL_BLEND);
1717     glLineWidth(1);
1718
1719     w = (m_nWidth / 2 / m_fScale);
1720     h = (m_nHeight / 2 / m_fScale);
1721
1722     const int nDim1 = (m_viewType == YZ) ? 1 : 0;
1723     const int nDim2 = (m_viewType == XY) ? 1 : 2;
1724
1725     xb = m_vOrigin[nDim1] - w;
1726     if (xb < region_mins[nDim1]) {
1727         xb = region_mins[nDim1];
1728     }
1729     xb = step * floor(xb / step);
1730
1731     xe = m_vOrigin[nDim1] + w;
1732     if (xe > region_maxs[nDim1]) {
1733         xe = region_maxs[nDim1];
1734     }
1735     xe = step * ceil(xe / step);
1736
1737     yb = m_vOrigin[nDim2] - h;
1738     if (yb < region_mins[nDim2]) {
1739         yb = region_mins[nDim2];
1740     }
1741     yb = step * floor(yb / step);
1742
1743     ye = m_vOrigin[nDim2] + h;
1744     if (ye > region_maxs[nDim2]) {
1745         ye = region_maxs[nDim2];
1746     }
1747     ye = step * ceil(ye / step);
1748
1749 #define COLORS_DIFFER(a, b) \
1750     ( ( a )[0] != ( b )[0] || \
1751       ( a )[1] != ( b )[1] || \
1752       ( a )[2] != ( b )[2] )
1753
1754     // djbob
1755     // draw minor blocks
1756     if (g_xywindow_globals_private.d_showgrid || a < 1.0f) {
1757         if (a < 1.0f) {
1758             glEnable(GL_BLEND);
1759         }
1760
1761         if (COLORS_DIFFER(g_xywindow_globals.color_gridminor, g_xywindow_globals.color_gridback)) {
1762             glColor4fv(vector4_to_array(Vector4(g_xywindow_globals.color_gridminor, a)));
1763
1764             glBegin(GL_LINES);
1765             int i = 0;
1766             for (x = xb; x < xe; x += minor_step, ++i) {
1767                 if ((i & mask) != 0) {
1768                     glVertex2f(x, yb);
1769                     glVertex2f(x, ye);
1770                 }
1771             }
1772             i = 0;
1773             for (y = yb; y < ye; y += minor_step, ++i) {
1774                 if ((i & mask) != 0) {
1775                     glVertex2f(xb, y);
1776                     glVertex2f(xe, y);
1777                 }
1778             }
1779             glEnd();
1780         }
1781
1782         // draw major blocks
1783         if (COLORS_DIFFER(g_xywindow_globals.color_gridmajor, g_xywindow_globals.color_gridminor)) {
1784             glColor4fv(vector4_to_array(Vector4(g_xywindow_globals.color_gridmajor, a)));
1785
1786             glBegin(GL_LINES);
1787             for (x = xb; x <= xe; x += step) {
1788                 glVertex2f(x, yb);
1789                 glVertex2f(x, ye);
1790             }
1791             for (y = yb; y <= ye; y += step) {
1792                 glVertex2f(xb, y);
1793                 glVertex2f(xe, y);
1794             }
1795             glEnd();
1796         }
1797
1798         if (a < 1.0f) {
1799             glDisable(GL_BLEND);
1800         }
1801     }
1802
1803     // draw coordinate text if needed
1804     if (g_xywindow_globals_private.show_coordinates) {
1805         glColor4fv(vector4_to_array(Vector4(g_xywindow_globals.color_gridtext, 1.0f)));
1806         float offx = m_vOrigin[nDim2] + h - (4 + GlobalOpenGL().m_font->getPixelAscent()) / m_fScale;
1807         float offy = m_vOrigin[nDim1] - w + 4 / m_fScale;
1808         for (x = xb - fmod(xb, stepx); x <= xe; x += stepx) {
1809             glRasterPos2f(x, offx);
1810             sprintf(text, "%g", x);
1811             GlobalOpenGL().drawString(text);
1812         }
1813         for (y = yb - fmod(yb, stepy); y <= ye; y += stepy) {
1814             glRasterPos2f(offy, y);
1815             sprintf(text, "%g", y);
1816             GlobalOpenGL().drawString(text);
1817         }
1818
1819         if (Active()) {
1820             glColor3fv(vector3_to_array(g_xywindow_globals.color_viewname));
1821         }
1822
1823         // we do this part (the old way) only if show_axis is disabled
1824         if (!g_xywindow_globals_private.show_axis) {
1825             glRasterPos2f(m_vOrigin[nDim1] - w + 35 / m_fScale, m_vOrigin[nDim2] + h - 20 / m_fScale);
1826
1827             GlobalOpenGL().drawString(ViewType_getTitle(m_viewType));
1828         }
1829     }
1830
1831     XYWnd::XY_DrawAxis();
1832
1833     // show current work zone?
1834     // the work zone is used to place dropped points and brushes
1835     if (g_xywindow_globals_private.d_show_work) {
1836         glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
1837         glBegin(GL_LINES);
1838         glVertex2f(xb, Select_getWorkZone().d_work_min[nDim2]);
1839         glVertex2f(xe, Select_getWorkZone().d_work_min[nDim2]);
1840         glVertex2f(xb, Select_getWorkZone().d_work_max[nDim2]);
1841         glVertex2f(xe, Select_getWorkZone().d_work_max[nDim2]);
1842         glVertex2f(Select_getWorkZone().d_work_min[nDim1], yb);
1843         glVertex2f(Select_getWorkZone().d_work_min[nDim1], ye);
1844         glVertex2f(Select_getWorkZone().d_work_max[nDim1], yb);
1845         glVertex2f(Select_getWorkZone().d_work_max[nDim1], ye);
1846         glEnd();
1847     }
1848 }
1849
1850 /*
1851    ==============
1852    XY_DrawBlockGrid
1853    ==============
1854  */
1855 void XYWnd::XY_DrawBlockGrid()
1856 {
1857     if (Map_FindWorldspawn(g_map) == 0) {
1858         return;
1859     }
1860     const char *value = Node_getEntity(*Map_GetWorldspawn(g_map))->getKeyValue("_blocksize");
1861     if (strlen(value)) {
1862         sscanf(value, "%i", &g_xywindow_globals_private.blockSize);
1863     }
1864
1865     if (!g_xywindow_globals_private.blockSize || g_xywindow_globals_private.blockSize > 65536 ||
1866         g_xywindow_globals_private.blockSize < 1024) {
1867         // don't use custom blocksize if it is less than the default, or greater than the maximum world coordinate
1868         g_xywindow_globals_private.blockSize = 1024;
1869     }
1870
1871     float x, y, xb, xe, yb, ye;
1872     float w, h;
1873     char text[32];
1874
1875     glDisable(GL_TEXTURE_2D);
1876     glDisable(GL_TEXTURE_1D);
1877     glDisable(GL_DEPTH_TEST);
1878     glDisable(GL_BLEND);
1879
1880     w = (m_nWidth / 2 / m_fScale);
1881     h = (m_nHeight / 2 / m_fScale);
1882
1883     int nDim1 = (m_viewType == YZ) ? 1 : 0;
1884     int nDim2 = (m_viewType == XY) ? 1 : 2;
1885
1886     xb = m_vOrigin[nDim1] - w;
1887     if (xb < region_mins[nDim1]) {
1888         xb = region_mins[nDim1];
1889     }
1890     xb = static_cast<float>( g_xywindow_globals_private.blockSize * floor(xb / g_xywindow_globals_private.blockSize));
1891
1892     xe = m_vOrigin[nDim1] + w;
1893     if (xe > region_maxs[nDim1]) {
1894         xe = region_maxs[nDim1];
1895     }
1896     xe = static_cast<float>( g_xywindow_globals_private.blockSize * ceil(xe / g_xywindow_globals_private.blockSize));
1897
1898     yb = m_vOrigin[nDim2] - h;
1899     if (yb < region_mins[nDim2]) {
1900         yb = region_mins[nDim2];
1901     }
1902     yb = static_cast<float>( g_xywindow_globals_private.blockSize * floor(yb / g_xywindow_globals_private.blockSize));
1903
1904     ye = m_vOrigin[nDim2] + h;
1905     if (ye > region_maxs[nDim2]) {
1906         ye = region_maxs[nDim2];
1907     }
1908     ye = static_cast<float>( g_xywindow_globals_private.blockSize * ceil(ye / g_xywindow_globals_private.blockSize));
1909
1910     // draw major blocks
1911
1912     glColor3fv(vector3_to_array(g_xywindow_globals.color_gridblock));
1913     glLineWidth(2);
1914
1915     glBegin(GL_LINES);
1916
1917     for (x = xb; x <= xe; x += g_xywindow_globals_private.blockSize) {
1918         glVertex2f(x, yb);
1919         glVertex2f(x, ye);
1920     }
1921
1922     if (m_viewType == XY) {
1923         for (y = yb; y <= ye; y += g_xywindow_globals_private.blockSize) {
1924             glVertex2f(xb, y);
1925             glVertex2f(xe, y);
1926         }
1927     }
1928
1929     glEnd();
1930     glLineWidth(1);
1931
1932     // draw coordinate text if needed
1933
1934     if (m_viewType == XY && m_fScale > .1) {
1935         for (x = xb; x < xe; x += g_xywindow_globals_private.blockSize) {
1936             for (y = yb; y < ye; y += g_xywindow_globals_private.blockSize) {
1937                 glRasterPos2f(x + (g_xywindow_globals_private.blockSize / 2),
1938                               y + (g_xywindow_globals_private.blockSize / 2));
1939                 sprintf(text, "%i,%i", (int) floor(x / g_xywindow_globals_private.blockSize),
1940                         (int) floor(y / g_xywindow_globals_private.blockSize));
1941                 GlobalOpenGL().drawString(text);
1942             }
1943         }
1944     }
1945
1946     glColor4f(0, 0, 0, 0);
1947 }
1948
1949 void XYWnd::DrawCameraIcon(const Vector3 &origin, const Vector3 &angles)
1950 {
1951     float x, y, fov, box;
1952     double a;
1953
1954     fov = 48 / m_fScale;
1955     box = 16 / m_fScale;
1956
1957     if (m_viewType == XY) {
1958         x = origin[0];
1959         y = origin[1];
1960         a = degrees_to_radians(angles[CAMERA_YAW]);
1961     } else if (m_viewType == YZ) {
1962         x = origin[1];
1963         y = origin[2];
1964         a = degrees_to_radians(angles[CAMERA_PITCH]);
1965     } else {
1966         x = origin[0];
1967         y = origin[2];
1968         a = degrees_to_radians(angles[CAMERA_PITCH]);
1969     }
1970
1971     glColor3f(0.0, 0.0, 1.0);
1972     glBegin(GL_LINE_STRIP);
1973     glVertex3f(x - box, y, 0);
1974     glVertex3f(x, y + (box / 2), 0);
1975     glVertex3f(x + box, y, 0);
1976     glVertex3f(x, y - (box / 2), 0);
1977     glVertex3f(x - box, y, 0);
1978     glVertex3f(x + box, y, 0);
1979     glEnd();
1980
1981     glBegin(GL_LINE_STRIP);
1982     glVertex3f(x + static_cast<float>( fov * cos(a + c_pi / 4)), y + static_cast<float>( fov * sin(a + c_pi / 4)), 0);
1983     glVertex3f(x, y, 0);
1984     glVertex3f(x + static_cast<float>( fov * cos(a - c_pi / 4)), y + static_cast<float>( fov * sin(a - c_pi / 4)), 0);
1985     glEnd();
1986
1987 }
1988
1989
1990 float Betwixt(float f1, float f2)
1991 {
1992     if (f1 > f2) {
1993         return f2 + ((f1 - f2) / 2);
1994     } else {
1995         return f1 + ((f2 - f1) / 2);
1996     }
1997 }
1998
1999
2000 // can be greatly simplified but per usual i am in a hurry
2001 // which is not an excuse, just a fact
2002 void XYWnd::PaintSizeInfo(int nDim1, int nDim2, Vector3 &vMinBounds, Vector3 &vMaxBounds)
2003 {
2004     if (vector3_equal(vMinBounds, vMaxBounds)) {
2005         return;
2006     }
2007     const char *g_pDimStrings[] = {"x:", "y:", "z:"};
2008     typedef const char *OrgStrings[2];
2009     const OrgStrings g_pOrgStrings[] = {{"x:", "y:",},
2010                                         {"x:", "z:",},
2011                                         {"y:", "z:",}};
2012
2013     Vector3 vSize(vector3_subtracted(vMaxBounds, vMinBounds));
2014
2015     glColor3f(g_xywindow_globals.color_selbrushes[0] * .65f,
2016               g_xywindow_globals.color_selbrushes[1] * .65f,
2017               g_xywindow_globals.color_selbrushes[2] * .65f);
2018
2019     StringOutputStream dimensions(16);
2020
2021     if (m_viewType == XY) {
2022         glBegin(GL_LINES);
2023
2024         glVertex3f(vMinBounds[nDim1], vMinBounds[nDim2] - 6.0f / m_fScale, 0.0f);
2025         glVertex3f(vMinBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale, 0.0f);
2026
2027         glVertex3f(vMinBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale, 0.0f);
2028         glVertex3f(vMaxBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale, 0.0f);
2029
2030         glVertex3f(vMaxBounds[nDim1], vMinBounds[nDim2] - 6.0f / m_fScale, 0.0f);
2031         glVertex3f(vMaxBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale, 0.0f);
2032
2033
2034         glVertex3f(vMaxBounds[nDim1] + 6.0f / m_fScale, vMinBounds[nDim2], 0.0f);
2035         glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, vMinBounds[nDim2], 0.0f);
2036
2037         glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, vMinBounds[nDim2], 0.0f);
2038         glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, vMaxBounds[nDim2], 0.0f);
2039
2040         glVertex3f(vMaxBounds[nDim1] + 6.0f / m_fScale, vMaxBounds[nDim2], 0.0f);
2041         glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, vMaxBounds[nDim2], 0.0f);
2042
2043         glEnd();
2044
2045         glRasterPos3f(Betwixt(vMinBounds[nDim1], vMaxBounds[nDim1]), vMinBounds[nDim2] - 20.0f / m_fScale, 0.0f);
2046         dimensions << g_pDimStrings[nDim1] << vSize[nDim1];
2047         GlobalOpenGL().drawString(dimensions.c_str());
2048         dimensions.clear();
2049
2050         glRasterPos3f(vMaxBounds[nDim1] + 16.0f / m_fScale, Betwixt(vMinBounds[nDim2], vMaxBounds[nDim2]), 0.0f);
2051         dimensions << g_pDimStrings[nDim2] << vSize[nDim2];
2052         GlobalOpenGL().drawString(dimensions.c_str());
2053         dimensions.clear();
2054
2055         glRasterPos3f(vMinBounds[nDim1] + 4, vMaxBounds[nDim2] + 8 / m_fScale, 0.0f);
2056         dimensions << "(" << g_pOrgStrings[0][0] << vMinBounds[nDim1] << "  " << g_pOrgStrings[0][1]
2057                    << vMaxBounds[nDim2] << ")";
2058         GlobalOpenGL().drawString(dimensions.c_str());
2059     } else if (m_viewType == XZ) {
2060         glBegin(GL_LINES);
2061
2062         glVertex3f(vMinBounds[nDim1], 0, vMinBounds[nDim2] - 6.0f / m_fScale);
2063         glVertex3f(vMinBounds[nDim1], 0, vMinBounds[nDim2] - 10.0f / m_fScale);
2064
2065         glVertex3f(vMinBounds[nDim1], 0, vMinBounds[nDim2] - 10.0f / m_fScale);
2066         glVertex3f(vMaxBounds[nDim1], 0, vMinBounds[nDim2] - 10.0f / m_fScale);
2067
2068         glVertex3f(vMaxBounds[nDim1], 0, vMinBounds[nDim2] - 6.0f / m_fScale);
2069         glVertex3f(vMaxBounds[nDim1], 0, vMinBounds[nDim2] - 10.0f / m_fScale);
2070
2071
2072         glVertex3f(vMaxBounds[nDim1] + 6.0f / m_fScale, 0, vMinBounds[nDim2]);
2073         glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, 0, vMinBounds[nDim2]);
2074
2075         glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, 0, vMinBounds[nDim2]);
2076         glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, 0, vMaxBounds[nDim2]);
2077
2078         glVertex3f(vMaxBounds[nDim1] + 6.0f / m_fScale, 0, vMaxBounds[nDim2]);
2079         glVertex3f(vMaxBounds[nDim1] + 10.0f / m_fScale, 0, vMaxBounds[nDim2]);
2080
2081         glEnd();
2082
2083         glRasterPos3f(Betwixt(vMinBounds[nDim1], vMaxBounds[nDim1]), 0, vMinBounds[nDim2] - 20.0f / m_fScale);
2084         dimensions << g_pDimStrings[nDim1] << vSize[nDim1];
2085         GlobalOpenGL().drawString(dimensions.c_str());
2086         dimensions.clear();
2087
2088         glRasterPos3f(vMaxBounds[nDim1] + 16.0f / m_fScale, 0, Betwixt(vMinBounds[nDim2], vMaxBounds[nDim2]));
2089         dimensions << g_pDimStrings[nDim2] << vSize[nDim2];
2090         GlobalOpenGL().drawString(dimensions.c_str());
2091         dimensions.clear();
2092
2093         glRasterPos3f(vMinBounds[nDim1] + 4, 0, vMaxBounds[nDim2] + 8 / m_fScale);
2094         dimensions << "(" << g_pOrgStrings[1][0] << vMinBounds[nDim1] << "  " << g_pOrgStrings[1][1]
2095                    << vMaxBounds[nDim2] << ")";
2096         GlobalOpenGL().drawString(dimensions.c_str());
2097     } else {
2098         glBegin(GL_LINES);
2099
2100         glVertex3f(0, vMinBounds[nDim1], vMinBounds[nDim2] - 6.0f / m_fScale);
2101         glVertex3f(0, vMinBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale);
2102
2103         glVertex3f(0, vMinBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale);
2104         glVertex3f(0, vMaxBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale);
2105
2106         glVertex3f(0, vMaxBounds[nDim1], vMinBounds[nDim2] - 6.0f / m_fScale);
2107         glVertex3f(0, vMaxBounds[nDim1], vMinBounds[nDim2] - 10.0f / m_fScale);
2108
2109
2110         glVertex3f(0, vMaxBounds[nDim1] + 6.0f / m_fScale, vMinBounds[nDim2]);
2111         glVertex3f(0, vMaxBounds[nDim1] + 10.0f / m_fScale, vMinBounds[nDim2]);
2112
2113         glVertex3f(0, vMaxBounds[nDim1] + 10.0f / m_fScale, vMinBounds[nDim2]);
2114         glVertex3f(0, vMaxBounds[nDim1] + 10.0f / m_fScale, vMaxBounds[nDim2]);
2115
2116         glVertex3f(0, vMaxBounds[nDim1] + 6.0f / m_fScale, vMaxBounds[nDim2]);
2117         glVertex3f(0, vMaxBounds[nDim1] + 10.0f / m_fScale, vMaxBounds[nDim2]);
2118
2119         glEnd();
2120
2121         glRasterPos3f(0, Betwixt(vMinBounds[nDim1], vMaxBounds[nDim1]), vMinBounds[nDim2] - 20.0f / m_fScale);
2122         dimensions << g_pDimStrings[nDim1] << vSize[nDim1];
2123         GlobalOpenGL().drawString(dimensions.c_str());
2124         dimensions.clear();
2125
2126         glRasterPos3f(0, vMaxBounds[nDim1] + 16.0f / m_fScale, Betwixt(vMinBounds[nDim2], vMaxBounds[nDim2]));
2127         dimensions << g_pDimStrings[nDim2] << vSize[nDim2];
2128         GlobalOpenGL().drawString(dimensions.c_str());
2129         dimensions.clear();
2130
2131         glRasterPos3f(0, vMinBounds[nDim1] + 4.0f, vMaxBounds[nDim2] + 8 / m_fScale);
2132         dimensions << "(" << g_pOrgStrings[2][0] << vMinBounds[nDim1] << "  " << g_pOrgStrings[2][1]
2133                    << vMaxBounds[nDim2] << ")";
2134         GlobalOpenGL().drawString(dimensions.c_str());
2135     }
2136 }
2137
2138 class XYRenderer : public Renderer {
2139     struct state_type {
2140         state_type() :
2141                 m_highlight(0),
2142                 m_state(0)
2143         {
2144         }
2145
2146         unsigned int m_highlight;
2147         Shader *m_state;
2148     };
2149
2150 public:
2151     XYRenderer(RenderStateFlags globalstate, Shader *selected) :
2152             m_globalstate(globalstate),
2153             m_state_selected(selected)
2154     {
2155         ASSERT_NOTNULL(selected);
2156         m_state_stack.push_back(state_type());
2157     }
2158
2159     void SetState(Shader *state, EStyle style)
2160     {
2161         ASSERT_NOTNULL(state);
2162         if (style == eWireframeOnly) {
2163             m_state_stack.back().m_state = state;
2164         }
2165     }
2166
2167     EStyle getStyle() const
2168     {
2169         return eWireframeOnly;
2170     }
2171
2172     void PushState()
2173     {
2174         m_state_stack.push_back(m_state_stack.back());
2175     }
2176
2177     void PopState()
2178     {
2179         ASSERT_MESSAGE(!m_state_stack.empty(), "popping empty stack");
2180         m_state_stack.pop_back();
2181     }
2182
2183     void Highlight(EHighlightMode mode, bool bEnable = true)
2184     {
2185         (bEnable)
2186         ? m_state_stack.back().m_highlight |= mode
2187         : m_state_stack.back().m_highlight &= ~mode;
2188     }
2189
2190     void addRenderable(const OpenGLRenderable &renderable, const Matrix4 &localToWorld)
2191     {
2192         if (m_state_stack.back().m_highlight & ePrimitive) {
2193             m_state_selected->addRenderable(renderable, localToWorld);
2194         } else {
2195             m_state_stack.back().m_state->addRenderable(renderable, localToWorld);
2196         }
2197     }
2198
2199     void render(const Matrix4 &modelview, const Matrix4 &projection)
2200     {
2201         GlobalShaderCache().render(m_globalstate, modelview, projection);
2202     }
2203
2204 private:
2205     std::vector<state_type> m_state_stack;
2206     RenderStateFlags m_globalstate;
2207     Shader *m_state_selected;
2208 };
2209
2210 void XYWnd::updateProjection()
2211 {
2212     m_projection[0] = 1.0f / static_cast<float>( m_nWidth / 2 );
2213     m_projection[5] = 1.0f / static_cast<float>( m_nHeight / 2 );
2214     m_projection[10] = 1.0f / (g_MaxWorldCoord * m_fScale);
2215
2216     m_projection[12] = 0.0f;
2217     m_projection[13] = 0.0f;
2218     m_projection[14] = -1.0f;
2219
2220     m_projection[1] =
2221     m_projection[2] =
2222     m_projection[3] =
2223
2224     m_projection[4] =
2225     m_projection[6] =
2226     m_projection[7] =
2227
2228     m_projection[8] =
2229     m_projection[9] =
2230     m_projection[11] = 0.0f;
2231
2232     m_projection[15] = 1.0f;
2233
2234     m_view.Construct(m_projection, m_modelview, m_nWidth, m_nHeight);
2235 }
2236
2237 // note: modelview matrix must have a uniform scale, otherwise strange things happen when rendering the rotation manipulator.
2238 void XYWnd::updateModelview()
2239 {
2240     int nDim1 = (m_viewType == YZ) ? 1 : 0;
2241     int nDim2 = (m_viewType == XY) ? 1 : 2;
2242
2243     // translation
2244     m_modelview[12] = -m_vOrigin[nDim1] * m_fScale;
2245     m_modelview[13] = -m_vOrigin[nDim2] * m_fScale;
2246     m_modelview[14] = g_MaxWorldCoord * m_fScale;
2247
2248     // axis base
2249     switch (m_viewType) {
2250         case XY:
2251             m_modelview[0] = m_fScale;
2252             m_modelview[1] = 0;
2253             m_modelview[2] = 0;
2254
2255             m_modelview[4] = 0;
2256             m_modelview[5] = m_fScale;
2257             m_modelview[6] = 0;
2258
2259             m_modelview[8] = 0;
2260             m_modelview[9] = 0;
2261             m_modelview[10] = -m_fScale;
2262             break;
2263         case XZ:
2264             m_modelview[0] = m_fScale;
2265             m_modelview[1] = 0;
2266             m_modelview[2] = 0;
2267
2268             m_modelview[4] = 0;
2269             m_modelview[5] = 0;
2270             m_modelview[6] = m_fScale;
2271
2272             m_modelview[8] = 0;
2273             m_modelview[9] = m_fScale;
2274             m_modelview[10] = 0;
2275             break;
2276         case YZ:
2277             m_modelview[0] = 0;
2278             m_modelview[1] = 0;
2279             m_modelview[2] = -m_fScale;
2280
2281             m_modelview[4] = m_fScale;
2282             m_modelview[5] = 0;
2283             m_modelview[6] = 0;
2284
2285             m_modelview[8] = 0;
2286             m_modelview[9] = m_fScale;
2287             m_modelview[10] = 0;
2288             break;
2289     }
2290
2291     m_modelview[3] = m_modelview[7] = m_modelview[11] = 0;
2292     m_modelview[15] = 1;
2293
2294     m_view.Construct(m_projection, m_modelview, m_nWidth, m_nHeight);
2295 }
2296
2297 /*
2298    ==============
2299    XY_Draw
2300    ==============
2301  */
2302
2303 //#define DBG_SCENEDUMP
2304
2305 void XYWnd::XY_Draw()
2306 {
2307     //
2308     // clear
2309     //
2310     glViewport(0, 0, m_nWidth, m_nHeight);
2311     glClearColor(g_xywindow_globals.color_gridback[0],
2312                  g_xywindow_globals.color_gridback[1],
2313                  g_xywindow_globals.color_gridback[2], 0);
2314
2315     glClear(GL_COLOR_BUFFER_BIT);
2316
2317     //
2318     // set up viewpoint
2319     //
2320
2321     glMatrixMode(GL_PROJECTION);
2322     glLoadMatrixf(reinterpret_cast<const float *>( &m_projection ));
2323
2324     glMatrixMode(GL_MODELVIEW);
2325     glLoadIdentity();
2326     glScalef(m_fScale, m_fScale, 1);
2327     int nDim1 = (m_viewType == YZ) ? 1 : 0;
2328     int nDim2 = (m_viewType == XY) ? 1 : 2;
2329     glTranslatef(-m_vOrigin[nDim1], -m_vOrigin[nDim2], 0);
2330
2331     glDisable(GL_LINE_STIPPLE);
2332     glLineWidth(1);
2333     glDisableClientState(GL_TEXTURE_COORD_ARRAY);
2334     glDisableClientState(GL_NORMAL_ARRAY);
2335     glDisableClientState(GL_COLOR_ARRAY);
2336     glDisable(GL_TEXTURE_2D);
2337     glDisable(GL_LIGHTING);
2338     glDisable(GL_COLOR_MATERIAL);
2339     glDisable(GL_DEPTH_TEST);
2340
2341     if (m_backgroundActivated) {
2342         XY_DrawBackground();
2343     }
2344     XY_DrawGrid();
2345
2346     if (g_xywindow_globals_private.show_blocks) {
2347         XY_DrawBlockGrid();
2348     }
2349
2350     glLoadMatrixf(reinterpret_cast<const float *>( &m_modelview ));
2351
2352     unsigned int globalstate = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_POLYGONSMOOTH | RENDER_LINESMOOTH;
2353     if (!g_xywindow_globals.m_bNoStipple) {
2354         globalstate |= RENDER_LINESTIPPLE;
2355     }
2356
2357     {
2358         XYRenderer renderer(globalstate, m_state_selected);
2359
2360         Scene_Render(renderer, m_view);
2361
2362         GlobalOpenGL_debugAssertNoErrors();
2363         renderer.render(m_modelview, m_projection);
2364         GlobalOpenGL_debugAssertNoErrors();
2365     }
2366
2367     glDepthMask(GL_FALSE);
2368
2369     GlobalOpenGL_debugAssertNoErrors();
2370
2371     glLoadMatrixf(reinterpret_cast<const float *>( &m_modelview ));
2372
2373     GlobalOpenGL_debugAssertNoErrors();
2374     glDisable(GL_LINE_STIPPLE);
2375     GlobalOpenGL_debugAssertNoErrors();
2376     glLineWidth(1);
2377     GlobalOpenGL_debugAssertNoErrors();
2378     if (GlobalOpenGL().GL_1_3()) {
2379         glActiveTexture(GL_TEXTURE0);
2380         glClientActiveTexture(GL_TEXTURE0);
2381     }
2382     glDisableClientState(GL_TEXTURE_COORD_ARRAY);
2383     GlobalOpenGL_debugAssertNoErrors();
2384     glDisableClientState(GL_NORMAL_ARRAY);
2385     GlobalOpenGL_debugAssertNoErrors();
2386     glDisableClientState(GL_COLOR_ARRAY);
2387     GlobalOpenGL_debugAssertNoErrors();
2388     glDisable(GL_TEXTURE_2D);
2389     GlobalOpenGL_debugAssertNoErrors();
2390     glDisable(GL_LIGHTING);
2391     GlobalOpenGL_debugAssertNoErrors();
2392     glDisable(GL_COLOR_MATERIAL);
2393     GlobalOpenGL_debugAssertNoErrors();
2394
2395     GlobalOpenGL_debugAssertNoErrors();
2396
2397
2398     // size info
2399     if (g_xywindow_globals_private.m_bSizePaint && GlobalSelectionSystem().countSelected() != 0) {
2400         Vector3 min, max;
2401         Select_GetBounds(min, max);
2402         PaintSizeInfo(nDim1, nDim2, min, max);
2403     }
2404
2405     if (g_bCrossHairs) {
2406         glColor4f(0.2f, 0.9f, 0.2f, 0.8f);
2407         glBegin(GL_LINES);
2408         if (m_viewType == XY) {
2409             glVertex2f(2.0f * g_MinWorldCoord, m_mousePosition[1]);
2410             glVertex2f(2.0f * g_MaxWorldCoord, m_mousePosition[1]);
2411             glVertex2f(m_mousePosition[0], 2.0f * g_MinWorldCoord);
2412             glVertex2f(m_mousePosition[0], 2.0f * g_MaxWorldCoord);
2413         } else if (m_viewType == YZ) {
2414             glVertex3f(m_mousePosition[0], 2.0f * g_MinWorldCoord, m_mousePosition[2]);
2415             glVertex3f(m_mousePosition[0], 2.0f * g_MaxWorldCoord, m_mousePosition[2]);
2416             glVertex3f(m_mousePosition[0], m_mousePosition[1], 2.0f * g_MinWorldCoord);
2417             glVertex3f(m_mousePosition[0], m_mousePosition[1], 2.0f * g_MaxWorldCoord);
2418         } else {
2419             glVertex3f(2.0f * g_MinWorldCoord, m_mousePosition[1], m_mousePosition[2]);
2420             glVertex3f(2.0f * g_MaxWorldCoord, m_mousePosition[1], m_mousePosition[2]);
2421             glVertex3f(m_mousePosition[0], m_mousePosition[1], 2.0f * g_MinWorldCoord);
2422             glVertex3f(m_mousePosition[0], m_mousePosition[1], 2.0f * g_MaxWorldCoord);
2423         }
2424         glEnd();
2425     }
2426
2427     if (ClipMode()) {
2428         GlobalClipPoints_Draw(m_fScale);
2429     }
2430
2431     GlobalOpenGL_debugAssertNoErrors();
2432
2433     // reset modelview
2434     glLoadIdentity();
2435     glScalef(m_fScale, m_fScale, 1);
2436     glTranslatef(-m_vOrigin[nDim1], -m_vOrigin[nDim2], 0);
2437
2438     DrawCameraIcon(Camera_getOrigin(*g_pParentWnd->GetCamWnd()), Camera_getAngles(*g_pParentWnd->GetCamWnd()));
2439
2440     Feedback_draw2D(m_viewType);
2441
2442     if (g_xywindow_globals_private.show_outline) {
2443         if (Active()) {
2444             glMatrixMode(GL_PROJECTION);
2445             glLoadIdentity();
2446             glOrtho(0, m_nWidth, 0, m_nHeight, 0, 1);
2447
2448             glMatrixMode(GL_MODELVIEW);
2449             glLoadIdentity();
2450
2451             // four view mode doesn't colorize
2452             if (g_pParentWnd->CurrentStyle() == MainFrame::eSplit) {
2453                 glColor3fv(vector3_to_array(g_xywindow_globals.color_viewname));
2454             } else {
2455                 switch (m_viewType) {
2456                     case YZ:
2457                         glColor3fv(vector3_to_array(g_xywindow_globals.AxisColorX));
2458                         break;
2459                     case XZ:
2460                         glColor3fv(vector3_to_array(g_xywindow_globals.AxisColorY));
2461                         break;
2462                     case XY:
2463                         glColor3fv(vector3_to_array(g_xywindow_globals.AxisColorZ));
2464                         break;
2465                 }
2466             }
2467             glBegin(GL_LINE_LOOP);
2468             glVertex2f(0.5, 0.5);
2469             glVertex2f(m_nWidth - 0.5, 1);
2470             glVertex2f(m_nWidth - 0.5, m_nHeight - 0.5);
2471             glVertex2f(0.5, m_nHeight - 0.5);
2472             glEnd();
2473         }
2474     }
2475
2476     GlobalOpenGL_debugAssertNoErrors();
2477
2478     glFinish();
2479 }
2480
2481 void XYWnd_MouseToPoint(XYWnd *xywnd, int x, int y, Vector3 &point)
2482 {
2483     xywnd->XY_ToPoint(x, y, point);
2484     xywnd->XY_SnapToGrid(point);
2485
2486     int nDim = (xywnd->GetViewType() == XY) ? 2 : (xywnd->GetViewType() == YZ) ? 0 : 1;
2487     float fWorkMid = float_mid(Select_getWorkZone().d_work_min[nDim], Select_getWorkZone().d_work_max[nDim]);
2488     point[nDim] = float_snapped(fWorkMid, GetGridSize());
2489 }
2490
2491 void XYWnd::OnEntityCreate(const char *item)
2492 {
2493     StringOutputStream command;
2494     command << "entityCreate -class " << item;
2495     UndoableCommand undo(command.c_str());
2496     Vector3 point;
2497     XYWnd_MouseToPoint(this, m_entityCreate_x, m_entityCreate_y, point);
2498     Entity_createFromSelection(item, point);
2499 }
2500
2501
2502 void GetFocusPosition(Vector3 &position)
2503 {
2504     if (GlobalSelectionSystem().countSelected() != 0) {
2505         Select_GetMid(position);
2506     } else {
2507         position = Camera_getOrigin(*g_pParentWnd->GetCamWnd());
2508     }
2509 }
2510
2511 void XYWnd_Focus(XYWnd *xywnd)
2512 {
2513     Vector3 position;
2514     GetFocusPosition(position);
2515     xywnd->PositionView(position);
2516 }
2517
2518 void XY_Split_Focus()
2519 {
2520     Vector3 position;
2521     GetFocusPosition(position);
2522     if (g_pParentWnd->GetXYWnd()) {
2523         g_pParentWnd->GetXYWnd()->PositionView(position);
2524     }
2525     if (g_pParentWnd->GetXZWnd()) {
2526         g_pParentWnd->GetXZWnd()->PositionView(position);
2527     }
2528     if (g_pParentWnd->GetYZWnd()) {
2529         g_pParentWnd->GetYZWnd()->PositionView(position);
2530     }
2531 }
2532
2533 void XY_Focus()
2534 {
2535     if (g_pParentWnd->CurrentStyle() == MainFrame::eSplit) {
2536         // cannot do this in a split window
2537         // do something else that the user may want here
2538         XY_Split_Focus();
2539         return;
2540     }
2541
2542     XYWnd *xywnd = g_pParentWnd->GetXYWnd();
2543     XYWnd_Focus(xywnd);
2544 }
2545
2546 void XY_Top()
2547 {
2548     if (g_pParentWnd->CurrentStyle() == MainFrame::eSplit || g_pParentWnd->CurrentStyle() == MainFrame::eFloating) {
2549         // cannot do this in a split window
2550         // do something else that the user may want here
2551         XY_Split_Focus();
2552         return;
2553     }
2554
2555     XYWnd *xywnd = g_pParentWnd->GetXYWnd();
2556     xywnd->SetViewType(XY);
2557     XYWnd_Focus(xywnd);
2558 }
2559
2560 void XY_Side()
2561 {
2562     if (g_pParentWnd->CurrentStyle() == MainFrame::eSplit || g_pParentWnd->CurrentStyle() == MainFrame::eFloating) {
2563         // cannot do this in a split window
2564         // do something else that the user may want here
2565         XY_Split_Focus();
2566         return;
2567     }
2568
2569     XYWnd *xywnd = g_pParentWnd->GetXYWnd();
2570     xywnd->SetViewType(XZ);
2571     XYWnd_Focus(xywnd);
2572 }
2573
2574 void XY_Front()
2575 {
2576     if (g_pParentWnd->CurrentStyle() == MainFrame::eSplit || g_pParentWnd->CurrentStyle() == MainFrame::eFloating) {
2577         // cannot do this in a split window
2578         // do something else that the user may want here
2579         XY_Split_Focus();
2580         return;
2581     }
2582
2583     XYWnd *xywnd = g_pParentWnd->GetXYWnd();
2584     xywnd->SetViewType(YZ);
2585     XYWnd_Focus(xywnd);
2586 }
2587
2588 void XY_Next()
2589 {
2590     if (g_pParentWnd->CurrentStyle() == MainFrame::eSplit || g_pParentWnd->CurrentStyle() == MainFrame::eFloating) {
2591         // cannot do this in a split window
2592         // do something else that the user may want here
2593         XY_Split_Focus();
2594         return;
2595     }
2596
2597     XYWnd *xywnd = g_pParentWnd->GetXYWnd();
2598     if (xywnd->GetViewType() == XY) {
2599         xywnd->SetViewType(XZ);
2600     } else if (xywnd->GetViewType() == XZ) {
2601         xywnd->SetViewType(YZ);
2602     } else {
2603         xywnd->SetViewType(XY);
2604     }
2605     XYWnd_Focus(xywnd);
2606 }
2607
2608 void XY_Zoom100()
2609 {
2610     if (g_pParentWnd->GetXYWnd()) {
2611         g_pParentWnd->GetXYWnd()->SetScale(1);
2612     }
2613     if (g_pParentWnd->GetXZWnd()) {
2614         g_pParentWnd->GetXZWnd()->SetScale(1);
2615     }
2616     if (g_pParentWnd->GetYZWnd()) {
2617         g_pParentWnd->GetYZWnd()->SetScale(1);
2618     }
2619 }
2620
2621 void XY_ZoomIn()
2622 {
2623     XYWnd_ZoomIn(g_pParentWnd->ActiveXY());
2624 }
2625
2626 // NOTE: the zoom out factor is 4/5, we could think about customizing it
2627 //  we don't go below a zoom factor corresponding to 10% of the max world size
2628 //  (this has to be computed against the window size)
2629 void XY_ZoomOut()
2630 {
2631     XYWnd_ZoomOut(g_pParentWnd->ActiveXY());
2632 }
2633
2634
2635 void ToggleShowCrosshair()
2636 {
2637     g_bCrossHairs ^= 1;
2638     XY_UpdateAllWindows();
2639 }
2640
2641 void ToggleShowSizeInfo()
2642 {
2643     g_xywindow_globals_private.m_bSizePaint = !g_xywindow_globals_private.m_bSizePaint;
2644     XY_UpdateAllWindows();
2645 }
2646
2647 void ToggleShowGrid()
2648 {
2649     g_xywindow_globals_private.d_showgrid = !g_xywindow_globals_private.d_showgrid;
2650     XY_UpdateAllWindows();
2651 }
2652
2653 ToggleShown g_xy_top_shown(true);
2654
2655 void XY_Top_Shown_Construct(ui::Window parent)
2656 {
2657     g_xy_top_shown.connect(parent);
2658 }
2659
2660 ToggleShown g_yz_side_shown(false);
2661
2662 void YZ_Side_Shown_Construct(ui::Window parent)
2663 {
2664     g_yz_side_shown.connect(parent);
2665 }
2666
2667 ToggleShown g_xz_front_shown(false);
2668
2669 void XZ_Front_Shown_Construct(ui::Window parent)
2670 {
2671     g_xz_front_shown.connect(parent);
2672 }
2673
2674
2675 class EntityClassMenu : public ModuleObserver {
2676     std::size_t m_unrealised;
2677 public:
2678     EntityClassMenu() : m_unrealised(1)
2679     {
2680     }
2681
2682     void realise()
2683     {
2684         if (--m_unrealised == 0) {
2685         }
2686     }
2687
2688     void unrealise()
2689     {
2690         if (++m_unrealised == 1) {
2691             if (XYWnd::m_mnuDrop) {
2692                 XYWnd::m_mnuDrop.destroy();
2693                 XYWnd::m_mnuDrop = ui::Menu(ui::null);
2694             }
2695         }
2696     }
2697 };
2698
2699 EntityClassMenu g_EntityClassMenu;
2700
2701
2702 void ShowNamesToggle()
2703 {
2704     GlobalEntityCreator().setShowNames(!GlobalEntityCreator().getShowNames());
2705     XY_UpdateAllWindows();
2706 }
2707
2708 typedef FreeCaller<void(), ShowNamesToggle> ShowNamesToggleCaller;
2709
2710 void ShowNamesExport(const Callback<void(bool)> &importer)
2711 {
2712     importer(GlobalEntityCreator().getShowNames());
2713 }
2714
2715 typedef FreeCaller<void(const Callback<void(bool)> &), ShowNamesExport> ShowNamesExportCaller;
2716
2717 void ShowAnglesToggle()
2718 {
2719     GlobalEntityCreator().setShowAngles(!GlobalEntityCreator().getShowAngles());
2720     XY_UpdateAllWindows();
2721 }
2722
2723 typedef FreeCaller<void(), ShowAnglesToggle> ShowAnglesToggleCaller;
2724
2725 void ShowAnglesExport(const Callback<void(bool)> &importer)
2726 {
2727     importer(GlobalEntityCreator().getShowAngles());
2728 }
2729
2730 typedef FreeCaller<void(const Callback<void(bool)> &), ShowAnglesExport> ShowAnglesExportCaller;
2731
2732 void ShowBlocksToggle()
2733 {
2734     g_xywindow_globals_private.show_blocks ^= 1;
2735     XY_UpdateAllWindows();
2736 }
2737
2738 typedef FreeCaller<void(), ShowBlocksToggle> ShowBlocksToggleCaller;
2739
2740 void ShowBlocksExport(const Callback<void(bool)> &importer)
2741 {
2742     importer(g_xywindow_globals_private.show_blocks);
2743 }
2744
2745 typedef FreeCaller<void(const Callback<void(bool)> &), ShowBlocksExport> ShowBlocksExportCaller;
2746
2747 void ShowCoordinatesToggle()
2748 {
2749     g_xywindow_globals_private.show_coordinates ^= 1;
2750     XY_UpdateAllWindows();
2751 }
2752
2753 typedef FreeCaller<void(), ShowCoordinatesToggle> ShowCoordinatesToggleCaller;
2754
2755 void ShowCoordinatesExport(const Callback<void(bool)> &importer)
2756 {
2757     importer(g_xywindow_globals_private.show_coordinates);
2758 }
2759
2760 typedef FreeCaller<void(const Callback<void(bool)> &), ShowCoordinatesExport> ShowCoordinatesExportCaller;
2761
2762 void ShowOutlineToggle()
2763 {
2764     g_xywindow_globals_private.show_outline ^= 1;
2765     XY_UpdateAllWindows();
2766 }
2767
2768 typedef FreeCaller<void(), ShowOutlineToggle> ShowOutlineToggleCaller;
2769
2770 void ShowOutlineExport(const Callback<void(bool)> &importer)
2771 {
2772     importer(g_xywindow_globals_private.show_outline);
2773 }
2774
2775 typedef FreeCaller<void(const Callback<void(bool)> &), ShowOutlineExport> ShowOutlineExportCaller;
2776
2777 void ShowAxesToggle()
2778 {
2779     g_xywindow_globals_private.show_axis ^= 1;
2780     XY_UpdateAllWindows();
2781 }
2782
2783 typedef FreeCaller<void(), ShowAxesToggle> ShowAxesToggleCaller;
2784
2785 void ShowAxesExport(const Callback<void(bool)> &importer)
2786 {
2787     importer(g_xywindow_globals_private.show_axis);
2788 }
2789
2790 typedef FreeCaller<void(const Callback<void(bool)> &), ShowAxesExport> ShowAxesExportCaller;
2791
2792 void ShowWorkzoneToggle()
2793 {
2794     g_xywindow_globals_private.d_show_work ^= 1;
2795     XY_UpdateAllWindows();
2796 }
2797
2798 typedef FreeCaller<void(), ShowWorkzoneToggle> ShowWorkzoneToggleCaller;
2799
2800 void ShowWorkzoneExport(const Callback<void(bool)> &importer)
2801 {
2802     importer(g_xywindow_globals_private.d_show_work);
2803 }
2804
2805 typedef FreeCaller<void(const Callback<void(bool)> &), ShowWorkzoneExport> ShowWorkzoneExportCaller;
2806
2807 ShowNamesExportCaller g_show_names_caller;
2808 Callback<void(const Callback<void(bool)> &)> g_show_names_callback(g_show_names_caller);
2809 ToggleItem g_show_names(g_show_names_callback);
2810
2811 ShowAnglesExportCaller g_show_angles_caller;
2812 Callback<void(const Callback<void(bool)> &)> g_show_angles_callback(g_show_angles_caller);
2813 ToggleItem g_show_angles(g_show_angles_callback);
2814
2815 ShowBlocksExportCaller g_show_blocks_caller;
2816 Callback<void(const Callback<void(bool)> &)> g_show_blocks_callback(g_show_blocks_caller);
2817 ToggleItem g_show_blocks(g_show_blocks_callback);
2818
2819 ShowCoordinatesExportCaller g_show_coordinates_caller;
2820 Callback<void(const Callback<void(bool)> &)> g_show_coordinates_callback(g_show_coordinates_caller);
2821 ToggleItem g_show_coordinates(g_show_coordinates_callback);
2822
2823 ShowOutlineExportCaller g_show_outline_caller;
2824 Callback<void(const Callback<void(bool)> &)> g_show_outline_callback(g_show_outline_caller);
2825 ToggleItem g_show_outline(g_show_outline_callback);
2826
2827 ShowAxesExportCaller g_show_axes_caller;
2828 Callback<void(const Callback<void(bool)> &)> g_show_axes_callback(g_show_axes_caller);
2829 ToggleItem g_show_axes(g_show_axes_callback);
2830
2831 ShowWorkzoneExportCaller g_show_workzone_caller;
2832 Callback<void(const Callback<void(bool)> &)> g_show_workzone_callback(g_show_workzone_caller);
2833 ToggleItem g_show_workzone(g_show_workzone_callback);
2834
2835 void XYShow_registerCommands()
2836 {
2837     GlobalToggles_insert("ShowAngles", ShowAnglesToggleCaller(), ToggleItem::AddCallbackCaller(g_show_angles));
2838     GlobalToggles_insert("ShowNames", ShowNamesToggleCaller(), ToggleItem::AddCallbackCaller(g_show_names));
2839     GlobalToggles_insert("ShowBlocks", ShowBlocksToggleCaller(), ToggleItem::AddCallbackCaller(g_show_blocks));
2840     GlobalToggles_insert("ShowCoordinates", ShowCoordinatesToggleCaller(),
2841                          ToggleItem::AddCallbackCaller(g_show_coordinates));
2842     GlobalToggles_insert("ShowWindowOutline", ShowOutlineToggleCaller(), ToggleItem::AddCallbackCaller(g_show_outline));
2843     GlobalToggles_insert("ShowAxes", ShowAxesToggleCaller(), ToggleItem::AddCallbackCaller(g_show_axes));
2844     GlobalToggles_insert("ShowWorkzone", ShowWorkzoneToggleCaller(), ToggleItem::AddCallbackCaller(g_show_workzone));
2845 }
2846
2847 void XYWnd_registerShortcuts()
2848 {
2849     command_connect_accelerator("ToggleCrosshairs");
2850     command_connect_accelerator("ToggleSizePaint");
2851 }
2852
2853
2854 void Orthographic_constructPreferences(PreferencesPage &page)
2855 {
2856     page.appendCheckBox("", "Solid selection boxes", g_xywindow_globals.m_bNoStipple);
2857     page.appendCheckBox("", "Display size info", g_xywindow_globals_private.m_bSizePaint);
2858     page.appendCheckBox("", "Chase mouse during drags", g_xywindow_globals_private.m_bChaseMouse);
2859     page.appendCheckBox("", "Update views on camera move", g_xywindow_globals_private.m_bCamXYUpdate);
2860 }
2861
2862 void Orthographic_constructPage(PreferenceGroup &group)
2863 {
2864     PreferencesPage page(group.createPage("Orthographic", "Orthographic View Preferences"));
2865     Orthographic_constructPreferences(page);
2866 }
2867
2868 void Orthographic_registerPreferencesPage()
2869 {
2870     PreferencesDialog_addSettingsPage(makeCallbackF(Orthographic_constructPage));
2871 }
2872
2873 void Clipper_constructPreferences(PreferencesPage &page)
2874 {
2875     page.appendCheckBox("", "Clipper tool uses caulk", g_clip_useCaulk);
2876 }
2877
2878 void Clipper_constructPage(PreferenceGroup &group)
2879 {
2880     PreferencesPage page(group.createPage("Clipper", "Clipper Tool Settings"));
2881     Clipper_constructPreferences(page);
2882 }
2883
2884 void Clipper_registerPreferencesPage()
2885 {
2886     PreferencesDialog_addSettingsPage(makeCallbackF(Clipper_constructPage));
2887 }
2888
2889
2890 #include "preferencesystem.h"
2891 #include "stringio.h"
2892
2893
2894 struct ToggleShown_Bool {
2895     static void Export(const ToggleShown &self, const Callback<void(bool)> &returnz)
2896     {
2897         returnz(self.active());
2898     }
2899
2900     static void Import(ToggleShown &self, bool value)
2901     {
2902         self.set(value);
2903     }
2904 };
2905
2906
2907 void XYWindow_Construct()
2908 {
2909     GlobalCommands_insert("ToggleCrosshairs", makeCallbackF(ToggleShowCrosshair),
2910                           Accelerator('X', (GdkModifierType) GDK_SHIFT_MASK));
2911     GlobalCommands_insert("ToggleSizePaint", makeCallbackF(ToggleShowSizeInfo), Accelerator('J'));
2912     GlobalCommands_insert("ToggleGrid", makeCallbackF(ToggleShowGrid), Accelerator('0'));
2913
2914     GlobalToggles_insert("ToggleView", ToggleShown::ToggleCaller(g_xy_top_shown),
2915                          ToggleItem::AddCallbackCaller(g_xy_top_shown.m_item),
2916                          Accelerator('V', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
2917     GlobalToggles_insert("ToggleSideView", ToggleShown::ToggleCaller(g_yz_side_shown),
2918                          ToggleItem::AddCallbackCaller(g_yz_side_shown.m_item));
2919     GlobalToggles_insert("ToggleFrontView", ToggleShown::ToggleCaller(g_xz_front_shown),
2920                          ToggleItem::AddCallbackCaller(g_xz_front_shown.m_item));
2921     GlobalCommands_insert("NextView", makeCallbackF(XY_Next), Accelerator(GDK_KEY_Tab,
2922                                                                           (GdkModifierType) GDK_CONTROL_MASK)); // fixme: doesn't show its shortcut
2923     GlobalCommands_insert("ZoomIn", makeCallbackF(XY_ZoomIn), Accelerator(GDK_KEY_Delete));
2924     GlobalCommands_insert("ZoomOut", makeCallbackF(XY_ZoomOut), Accelerator(GDK_KEY_Insert));
2925     GlobalCommands_insert("ViewTop", makeCallbackF(XY_Top), Accelerator(GDK_KEY_KP_Home));
2926     GlobalCommands_insert("ViewSide", makeCallbackF(XY_Side), Accelerator(GDK_KEY_KP_Page_Down));
2927     GlobalCommands_insert("ViewFront", makeCallbackF(XY_Front), Accelerator(GDK_KEY_KP_End));
2928     GlobalCommands_insert("Zoom100", makeCallbackF(XY_Zoom100));
2929     GlobalCommands_insert("CenterXYView", makeCallbackF(XY_Focus),
2930                           Accelerator(GDK_KEY_Tab, (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
2931
2932     GlobalPreferenceSystem().registerPreference("ClipCaulk", make_property_string(g_clip_useCaulk));
2933
2934     GlobalPreferenceSystem().registerPreference("NewRightClick",
2935                                                 make_property_string(g_xywindow_globals.m_bRightClick));
2936     GlobalPreferenceSystem().registerPreference("ChaseMouse",
2937                                                 make_property_string(g_xywindow_globals_private.m_bChaseMouse));
2938     GlobalPreferenceSystem().registerPreference("SizePainting",
2939                                                 make_property_string(g_xywindow_globals_private.m_bSizePaint));
2940     GlobalPreferenceSystem().registerPreference("NoStipple", make_property_string(g_xywindow_globals.m_bNoStipple));
2941     GlobalPreferenceSystem().registerPreference("SI_ShowCoords",
2942                                                 make_property_string(g_xywindow_globals_private.show_coordinates));
2943     GlobalPreferenceSystem().registerPreference("SI_ShowOutlines",
2944                                                 make_property_string(g_xywindow_globals_private.show_outline));
2945     GlobalPreferenceSystem().registerPreference("SI_ShowAxis",
2946                                                 make_property_string(g_xywindow_globals_private.show_axis));
2947     GlobalPreferenceSystem().registerPreference("CamXYUpdate",
2948                                                 make_property_string(g_xywindow_globals_private.m_bCamXYUpdate));
2949     GlobalPreferenceSystem().registerPreference("ShowWorkzone",
2950                                                 make_property_string(g_xywindow_globals_private.d_show_work));
2951
2952     GlobalPreferenceSystem().registerPreference("SI_AxisColors0", make_property_string(g_xywindow_globals.AxisColorX));
2953     GlobalPreferenceSystem().registerPreference("SI_AxisColors1", make_property_string(g_xywindow_globals.AxisColorY));
2954     GlobalPreferenceSystem().registerPreference("SI_AxisColors2", make_property_string(g_xywindow_globals.AxisColorZ));
2955     GlobalPreferenceSystem().registerPreference("SI_Colors1", make_property_string(g_xywindow_globals.color_gridback));
2956     GlobalPreferenceSystem().registerPreference("SI_Colors2", make_property_string(g_xywindow_globals.color_gridminor));
2957     GlobalPreferenceSystem().registerPreference("SI_Colors3", make_property_string(g_xywindow_globals.color_gridmajor));
2958     GlobalPreferenceSystem().registerPreference("SI_Colors6", make_property_string(g_xywindow_globals.color_gridblock));
2959     GlobalPreferenceSystem().registerPreference("SI_Colors7", make_property_string(g_xywindow_globals.color_gridtext));
2960     GlobalPreferenceSystem().registerPreference("SI_Colors8", make_property_string(g_xywindow_globals.color_brushes));
2961     GlobalPreferenceSystem().registerPreference("SI_Colors14",
2962                                                 make_property_string(g_xywindow_globals.color_gridmajor_alt));
2963
2964
2965     GlobalPreferenceSystem().registerPreference("XZVIS", make_property_string<ToggleShown_Bool>(g_xz_front_shown));
2966     GlobalPreferenceSystem().registerPreference("YZVIS", make_property_string<ToggleShown_Bool>(g_yz_side_shown));
2967
2968     Orthographic_registerPreferencesPage();
2969     Clipper_registerPreferencesPage();
2970
2971     XYWnd::captureStates();
2972     GlobalEntityClassManager().attach(g_EntityClassMenu);
2973 }
2974
2975 void XYWindow_Destroy()
2976 {
2977     GlobalEntityClassManager().detach(g_EntityClassMenu);
2978     XYWnd::releaseStates();
2979 }