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