]> git.mxchange.org Git - flightgear.git/blob - src/GUI/gui_funcs.cxx
Merge branch 'next' into comm-subsystem
[flightgear.git] / src / GUI / gui_funcs.cxx
1 /**************************************************************************
2  * gui_funcs.cxx
3  *
4  * Based on gui.cxx and renamed on 2002/08/13 by Erik Hofman.
5  *
6  * Written 1998 by Durk Talsma, started Juni, 1998.  For the flight gear
7  * project.
8  *
9  * Additional mouse supported added by David Megginson, 1999.
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License as
13  * published by the Free Software Foundation; either version 2 of the
14  * License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful, but
17  * WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
24  *
25  * $Id$
26  **************************************************************************/
27
28
29 #ifdef HAVE_CONFIG_H
30 #  include <config.h>
31 #endif
32
33 #include <simgear/compiler.h>
34
35 #include <fstream>
36 #include <string>
37 #include <cstring>
38 #include <sstream>
39
40 #include <stdlib.h>
41
42 #include <simgear/debug/logstream.hxx>
43 #include <simgear/misc/sg_path.hxx>
44 #include <simgear/screen/screen-dump.hxx>
45 #include <simgear/structure/event_mgr.hxx>
46 #include <simgear/props/props_io.hxx>
47
48 #include <Cockpit/panel.hxx>
49 #include <Main/globals.hxx>
50 #include <Main/fg_props.hxx>
51 #include <Main/fg_os.hxx>
52 #include <Main/renderer.hxx>
53 #include <Main/viewmgr.hxx>
54 #include <Main/WindowSystemAdapter.hxx>
55 #include <Main/CameraGroup.hxx>
56 #include <GUI/new_gui.hxx>
57
58
59 #ifdef _WIN32
60 #  include <shellapi.h>
61 #endif
62
63 #include "gui.h"
64
65 using std::string;
66
67
68 #if defined( TR_HIRES_SNAP)
69 #include <simgear/screen/tr.h>
70 extern void fgUpdateHUD( GLfloat x_start, GLfloat y_start,
71                          GLfloat x_end, GLfloat y_end );
72 #endif
73
74
75 const __fg_gui_fn_t __fg_gui_fn[] = {
76 #ifdef TR_HIRES_SNAP
77         {"dumpHiResSnapShot", fgHiResDumpWrapper},
78 #endif
79         {"dumpSnapShot", fgDumpSnapShotWrapper},
80         // Help
81         {"helpCb", helpCb},
82
83         // Structure termination
84         {"", NULL}
85 };
86
87
88 /* ================ General Purpose Functions ================ */
89
90 // General Purpose Message Box. Makes sure no more than 5 different
91 // messages are displayed at the same time, and none of them are
92 // duplicates. (5 is a *lot*, but this will hardly ever be reached
93 // and we don't want to miss any, either.)
94 void mkDialog (const char *txt)
95 {
96     NewGUI *gui = (NewGUI *)globals->get_subsystem("gui");
97     if (!gui)
98         return;
99     SGPropertyNode *master = gui->getDialogProperties("message");
100     if (!master)
101         return;
102
103     const int maxdialogs = 5;
104     string name;
105     SGPropertyNode *msg = fgGetNode("/sim/gui/dialogs", true);
106     int i;
107     for (i = 0; i < maxdialogs; i++) {
108         std::ostringstream s;
109         s << "message-" << i;
110         name = s.str();
111
112         if (!msg->getNode(name.c_str(), false))
113             break;
114
115         if (!strcmp(txt, msg->getNode(name.c_str())->getStringValue("message"))) {
116             SG_LOG(SG_GENERAL, SG_WARN, "mkDialog(): duplicate of message " << txt);
117             return;
118         }
119     }
120     if (i == maxdialogs)
121         return;
122     msg = msg->getNode(name.c_str(), true);
123     msg->setStringValue("message", txt);
124     msg = msg->getNode("dialog", true);
125     copyProperties(master, msg);
126     msg->setStringValue("name", name.c_str());
127     gui->newDialog(msg);
128     gui->showDialog(name.c_str());
129 }
130
131 // Message Box to report an error.
132 void guiErrorMessage (const char *txt)
133 {
134     SG_LOG(SG_GENERAL, SG_ALERT, txt);
135     mkDialog(txt);
136 }
137
138 // Message Box to report a throwable (usually an exception).
139 void guiErrorMessage (const char *txt, const sg_throwable &throwable)
140 {
141     string msg = txt;
142     msg += '\n';
143     msg += throwable.getFormattedMessage();
144     if (!std::strlen(throwable.getOrigin()) != 0) {
145         msg += "\n (reported by ";
146         msg += throwable.getOrigin();
147         msg += ')';
148     }
149     SG_LOG(SG_GENERAL, SG_ALERT, msg);
150     mkDialog(msg.c_str());
151 }
152
153
154
155 /* -----------------------------------------------------------------------
156 the Gui callback functions 
157 ____________________________________________________________________*/
158
159 void helpCb()
160 {
161     openBrowser( "Docs/index.html" );
162 }
163
164 bool openBrowser(string address)
165 {
166     bool ok = true;
167
168     // do not resolve addresses with given protocol, i.e. "http://...", "ftp://..."
169     if (address.find("://")==string::npos)
170     {
171         // resolve local file path
172         SGPath path(address);
173         path = globals->resolve_maybe_aircraft_path(address);
174         if (!path.isNull())
175             address = path.str();
176         else
177         {
178             mkDialog ("Sorry, file not found!");
179             SG_LOG(SG_GENERAL, SG_ALERT, "openBrowser: Cannot find requested file '"  
180                     << address << "'.");
181             return false;
182         }
183     }
184
185 #ifndef _WIN32
186
187     string command = globals->get_browser();
188     string::size_type pos;
189     if ((pos = command.find("%u", 0)) != string::npos)
190         command.replace(pos, 2, address);
191     else
192         command += " " + address;
193
194     command += " &";
195     ok = (system( command.c_str() ) == 0);
196
197 #else // _WIN32
198
199     // Look for favorite browser
200     char win32_name[1024];
201 # ifdef __CYGWIN__
202     cygwin32_conv_to_full_win32_path(address.c_str(),win32_name);
203 # else
204     strncpy(win32_name,address.c_str(), 1024);
205 # endif
206     ShellExecute ( NULL, "open", win32_name, NULL, NULL,
207                    SW_SHOWNORMAL ) ;
208
209 #endif
210
211     mkDialog("The file is shown in your web browser window.");
212     return ok;
213 }
214
215 #if defined( TR_HIRES_SNAP)
216 void fgHiResDump()
217 {
218     FILE *f;
219     string message;
220     bool menu_status = fgGetBool("/sim/menubar/visibility");
221     char *filename = new char [24];
222     static int count = 1;
223
224     static const SGPropertyNode *master_freeze
225         = fgGetNode("/sim/freeze/master");
226
227     bool freeze = master_freeze->getBoolValue();
228     if ( !freeze ) {
229         fgSetBool("/sim/freeze/master", true);
230     }
231
232     fgSetBool("/sim/menubar/visibility", false);
233     int mouse = fgGetMouseCursor();
234     fgSetMouseCursor(MOUSE_CURSOR_NONE);
235
236     FGRenderer *renderer = globals->get_renderer();
237 //     renderer->init();
238     renderer->resize( fgGetInt("/sim/startup/xsize"),
239                       fgGetInt("/sim/startup/ysize") );
240
241     // we need two render frames here to clear the menu and cursor
242     // ... not sure why but doing an extra fgRenderFrame() shouldn't
243     // hurt anything
244     //renderer->update( true );
245     //renderer->update( true );
246
247     // This ImageSize stuff is a temporary hack
248     // should probably use 128x128 tile size and
249     // support any image size
250
251     // This should be a requester to get multiplier from user
252     int multiplier = fgGetInt("/sim/startup/hires-multiplier", 3);
253     int width = fgGetInt("/sim/startup/xsize");
254     int height = fgGetInt("/sim/startup/ysize");
255         
256     /* allocate buffer large enough to store one tile */
257     GLubyte *tile = (GLubyte *)malloc(width * height * 3 * sizeof(GLubyte));
258     if (!tile) {
259         delete [] filename;
260         printf("Malloc of tile buffer failed!\n");
261         return;
262     }
263
264     int imageWidth  = multiplier*width;
265     int imageHeight = multiplier*height;
266
267     /* allocate buffer to hold a row of tiles */
268     GLubyte *buffer
269         = (GLubyte *)malloc(imageWidth * height * 3 * sizeof(GLubyte));
270     if (!buffer) {
271         delete [] filename;
272         free(tile);
273         printf("Malloc of tile row buffer failed!\n");
274         return;
275     }
276     TRcontext *tr = trNew();
277     trTileSize(tr, width, height, 0);
278     trTileBuffer(tr, GL_RGB, GL_UNSIGNED_BYTE, tile);
279     trImageSize(tr, imageWidth, imageHeight);
280     trRowOrder(tr, TR_TOP_TO_BOTTOM);
281     // OSGFIXME
282 //     sgFrustum *frustum = ssgGetFrustum();
283 //     trFrustum(tr,
284 //               frustum->getLeft(), frustum->getRight(),
285 //               frustum->getBot(),  frustum->getTop(), 
286 //               frustum->getNear(), frustum->getFar());
287         
288     /* Prepare ppm output file */
289     while (count < 1000) {
290         snprintf(filename, 24, "fgfs-screen-%03d.ppm", count++);
291         if ( (f = fopen(filename, "r")) == NULL )
292             break;
293         fclose(f);
294     }
295     f = fopen(filename, "wb");
296     if (!f) {
297         printf("Couldn't open image file: %s\n", filename);
298         delete [] filename;
299         free(buffer);
300         free(tile);
301         return;
302     }
303     fprintf(f,"P6\n");
304     fprintf(f,"# ppm-file created by %s\n", "trdemo2");
305     fprintf(f,"%i %i\n", imageWidth, imageHeight);
306     fprintf(f,"255\n");
307
308     /* just to be safe... */
309     glPixelStorei(GL_PACK_ALIGNMENT, 1);
310
311     // OSGFIXME
312 #if 0
313     /* Because the HUD and Panel change the ViewPort we will
314      * need to handle some lowlevel stuff ourselves */
315     int ncols = trGet(tr, TR_COLUMNS);
316     int nrows = trGet(tr, TR_ROWS);
317
318     bool do_hud = fgGetBool("/sim/hud/visibility");
319     GLfloat hud_col_step = 640.0 / ncols;
320     GLfloat hud_row_step = 480.0 / nrows;
321         
322     bool do_panel = fgPanelVisible();
323     GLfloat panel_col_step = globals->get_current_panel()->getWidth() / ncols;
324     GLfloat panel_row_step = globals->get_current_panel()->getHeight() / nrows;
325 #endif
326     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
327     glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
328     glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
329     glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
330     glHint(GL_FOG_HINT, GL_NICEST);
331         
332     /* Draw tiles */
333     int more = 1;
334     while (more) {
335         trBeginTile(tr);
336         int curColumn = trGet(tr, TR_CURRENT_COLUMN);
337         // int curRow =  trGet(tr, TR_CURRENT_ROW);
338
339         renderer->update();
340         // OSGFIXME
341 //         if ( do_hud )
342 //             fgUpdateHUD( curColumn*hud_col_step,      curRow*hud_row_step,
343 //                          (curColumn+1)*hud_col_step, (curRow+1)*hud_row_step );
344         // OSGFIXME
345 //         if (do_panel)
346 //             globals->get_current_panel()->update(
347 //                                    curColumn*panel_col_step, panel_col_step,
348 //                                    curRow*panel_row_step,    panel_row_step );
349         more = trEndTile(tr);
350
351         /* save tile into tile row buffer*/
352         int curTileWidth = trGet(tr, TR_CURRENT_TILE_WIDTH);
353         int bytesPerImageRow = imageWidth * 3*sizeof(GLubyte);
354         int bytesPerTileRow = (width) * 3*sizeof(GLubyte);
355         int xOffset = curColumn * bytesPerTileRow;
356         int bytesPerCurrentTileRow = (curTileWidth) * 3*sizeof(GLubyte);
357         int i;
358         for (i=0;i<height;i++) {
359             memcpy(buffer + i*bytesPerImageRow + xOffset, /* Dest */
360                    tile + i*bytesPerTileRow,              /* Src */
361                    bytesPerCurrentTileRow);               /* Byte count*/
362         }
363
364         if (curColumn == trGet(tr, TR_COLUMNS)-1) {
365             /* write this buffered row of tiles to the file */
366             int curTileHeight = trGet(tr, TR_CURRENT_TILE_HEIGHT);
367             int bytesPerImageRow = imageWidth * 3*sizeof(GLubyte);
368             int i;
369             for (i=0;i<curTileHeight;i++) {
370                 /* Remember, OpenGL images are bottom to top.  Have to reverse. */
371                 GLubyte *rowPtr = buffer + (curTileHeight-1-i) * bytesPerImageRow;
372                 fwrite(rowPtr, 1, imageWidth*3, f);
373             }
374         }
375
376     }
377
378     renderer->resize( width, height );
379
380     trDelete(tr);
381
382     glHint(GL_POLYGON_SMOOTH_HINT, GL_DONT_CARE);
383     glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
384     glHint(GL_POINT_SMOOTH_HINT, GL_DONT_CARE);
385     glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_DONT_CARE);
386     if ( (!strcmp(fgGetString("/sim/rendering/fog"), "disabled")) ||
387          (!fgGetBool("/sim/rendering/shading"))) {
388         // if fastest fog requested, or if flat shading force fastest
389         glHint ( GL_FOG_HINT, GL_FASTEST );
390     } else if ( !strcmp(fgGetString("/sim/rendering/fog"), "nicest") ) {
391         glHint ( GL_FOG_HINT, GL_DONT_CARE );
392     }
393
394     fclose(f);
395
396     message = "Snapshot saved to \"";
397     message += filename;
398     message += "\".";
399     mkDialog (message.c_str());
400
401     free(tile);
402     free(buffer);
403
404     delete [] filename;
405
406     fgSetMouseCursor(mouse);
407     fgSetBool("/sim/menubar/visibility", menu_status);
408
409     if ( !freeze ) {
410         fgSetBool("/sim/freeze/master", false);
411     }
412 }
413 #endif // #if defined( TR_HIRES_SNAP)
414
415 void fgDumpSnapShotWrapper () {
416     fgDumpSnapShot();
417 }
418
419
420 void fgHiResDumpWrapper () {
421     fgHiResDump();
422 }
423
424 namespace
425 {
426     using namespace flightgear;
427
428     class GUISnapShotOperation : 
429         public GraphicsContextOperation
430     {
431     public:
432
433         // start new snap shot
434         static bool start()
435         {
436             // allow only one snapshot at a time
437             if (_snapShotOp.valid())
438                 return false;
439             _snapShotOp = new GUISnapShotOperation();
440             /* register with graphics context so actual snap shot is done
441              * in the graphics context (thread) */
442             osg::Camera* guiCamera = getGUICamera(CameraGroup::getDefault());
443             WindowSystemAdapter* wsa = WindowSystemAdapter::getWSA();
444             osg::GraphicsContext* gc = 0;
445             if (guiCamera)
446                 gc = guiCamera->getGraphicsContext();
447             if (gc) {
448                 gc->add(_snapShotOp.get());
449             } else {
450                 wsa->windows[0]->gc->add(_snapShotOp.get());
451             }
452             return true;
453         }
454
455     private:
456         // constructor to be executed in main loop's thread
457         GUISnapShotOperation() :
458             flightgear::GraphicsContextOperation(std::string("GUI snap shot")),
459             _master_freeze(fgGetNode("/sim/freeze/master", true)),
460             _freeze(_master_freeze->getBoolValue()),
461             _result(false),
462             _mouse(fgGetMouseCursor())
463         {
464             if (!_freeze)
465                 _master_freeze->setBoolValue(true);
466
467             fgSetMouseCursor(MOUSE_CURSOR_NONE);
468
469             string dir = fgGetString("/sim/paths/screenshot-dir");
470             if (dir.empty())
471                 dir = fgGetString("/sim/fg-current");
472
473             _path.set(dir + '/');
474             if (_path.create_dir( 0755 )) {
475                 SG_LOG(SG_GENERAL, SG_ALERT, "Cannot create screenshot directory '"
476                         << dir << "'. Trying home directory.");
477                 dir = fgGetString("/sim/fg-home");
478             }
479
480             char filename[24];
481             static int count = 1;
482             while (count < 1000) {
483                 snprintf(filename, 24, "fgfs-screen-%03d.png", count++);
484
485                 SGPath p(dir);
486                 p.append(filename);
487                 if (!p.exists()) {
488                     _path.set(p.str());
489                     break;
490                 }
491             }
492
493             _xsize = fgGetInt("/sim/startup/xsize");
494             _ysize = fgGetInt("/sim/startup/ysize");
495
496             FGRenderer *renderer = globals->get_renderer();
497             renderer->resize(_xsize, _ysize);
498             globals->get_event_mgr()->addTask("SnapShotTimer",
499                     this, &GUISnapShotOperation::timerExpired,
500                     0.1, false);
501         }
502
503         // to be executed in graphics context (maybe separate thread)
504         void run(osg::GraphicsContext* gc)
505         {
506             _result = sg_glDumpWindow(_path.c_str(),
507                                      _xsize,
508                                      _ysize);
509         }
510
511         // timer method, to be executed in main loop's thread
512         virtual void timerExpired()
513         {
514             if (isFinished())
515             {
516                 globals->get_event_mgr()->removeTask("SnapShotTimer");
517
518                 fgSetString("/sim/paths/screenshot-last", _path.c_str());
519                 fgSetBool("/sim/signals/screenshot", _result);
520
521                 fgSetMouseCursor(_mouse);
522
523                 if ( !_freeze )
524                     _master_freeze->setBoolValue(false);
525
526                 _snapShotOp = 0;
527             }
528         }
529     
530         static osg::ref_ptr<GUISnapShotOperation> _snapShotOp;
531         SGPropertyNode_ptr _master_freeze;
532         bool _freeze;
533         bool _result;
534         int _mouse;
535         int _xsize, _ysize;
536         SGPath _path;
537     };
538
539 }
540
541 osg::ref_ptr<GUISnapShotOperation> GUISnapShotOperation::_snapShotOp;
542
543 // do a screen snap shot
544 bool fgDumpSnapShot ()
545 {
546 #if 1
547     // start snap shot operation, while needs to be executed in
548     // graphics context
549     return GUISnapShotOperation::start();
550 #else
551     // obsolete code => remove when new code is stable
552     static SGConstPropertyNode_ptr master_freeze = fgGetNode("/sim/freeze/master");
553
554     bool freeze = master_freeze->getBoolValue();
555     if ( !freeze ) {
556         fgSetBool("/sim/freeze/master", true);
557     }
558
559     int mouse = fgGetMouseCursor();
560     fgSetMouseCursor(MOUSE_CURSOR_NONE);
561
562     fgSetBool("/sim/signals/screenshot", true);
563
564     FGRenderer *renderer = globals->get_renderer();
565     renderer->resize( fgGetInt("/sim/startup/xsize"),
566                       fgGetInt("/sim/startup/ysize") );
567
568     // we need two render frames here to clear the menu and cursor
569     // ... not sure why but doing an extra fgRenderFrame() shouldn't
570     // hurt anything
571     renderer->update( true );
572     renderer->update( true );
573
574     string dir = fgGetString("/sim/paths/screenshot-dir");
575     if (dir.empty())
576         dir = fgGetString("/sim/fg-current");
577
578     SGPath path(dir + '/');
579     if (path.create_dir( 0755 )) {
580         SG_LOG(SG_GENERAL, SG_ALERT, "Cannot create screenshot directory '"
581                 << dir << "'. Trying home directory.");
582         dir = fgGetString("/sim/fg-home");
583     }
584
585     char filename[24];
586     static int count = 1;
587     while (count < 1000) {
588         snprintf(filename, 24, "fgfs-screen-%03d.png", count++);
589
590         SGPath p(dir);
591         p.append(filename);
592         if (!p.exists()) {
593             path.set(p.str());
594             break;
595         }
596     }
597
598     bool result = sg_glDumpWindow(path.c_str(),
599                                  fgGetInt("/sim/startup/xsize"),
600                                  fgGetInt("/sim/startup/ysize"));
601
602     fgSetString("/sim/paths/screenshot-last", path.c_str());
603     fgSetBool("/sim/signals/screenshot", false);
604
605     fgSetMouseCursor(mouse);
606
607     if ( !freeze ) {
608         fgSetBool("/sim/freeze/master", false);
609     }
610     return result;
611 #endif
612 }
613
614 // do an entire scenegraph dump
615 void fgDumpSceneGraph()
616 {
617     char *filename = new char [24];
618     string message;
619     static int count = 1;
620
621     static const SGPropertyNode *master_freeze
622         = fgGetNode("/sim/freeze/master");
623
624     bool freeze = master_freeze->getBoolValue();
625     if ( !freeze ) {
626         fgSetBool("/sim/freeze/master", true);
627     }
628
629     while (count < 1000) {
630         FILE *fp;
631         snprintf(filename, 24, "fgfs-graph-%03d.osg", count++);
632         if ( (fp = fopen(filename, "r")) == NULL )
633             break;
634         fclose(fp);
635     }
636
637     if ( fgDumpSceneGraphToFile(filename)) {
638         message = "Entire scene graph saved to \"";
639         message += filename;
640         message += "\".";
641     } else {
642         message = "Failed to save to \"";
643         message += filename;
644         message += "\".";
645     }
646
647     mkDialog (message.c_str());
648
649     delete [] filename;
650
651     if ( !freeze ) {
652         fgSetBool("/sim/freeze/master", false);
653     }
654 }
655
656     
657 // do an terrain branch dump
658 void fgDumpTerrainBranch()
659 {
660     char *filename = new char [24];
661     string message;
662     static int count = 1;
663
664     static const SGPropertyNode *master_freeze
665         = fgGetNode("/sim/freeze/master");
666
667     bool freeze = master_freeze->getBoolValue();
668     if ( !freeze ) {
669         fgSetBool("/sim/freeze/master", true);
670     }
671
672     while (count < 1000) {
673         FILE *fp;
674         snprintf(filename, 24, "fgfs-graph-%03d.osg", count++);
675         if ( (fp = fopen(filename, "r")) == NULL )
676             break;
677         fclose(fp);
678     }
679
680     if ( fgDumpTerrainBranchToFile(filename)) {
681         message = "Terrain graph saved to \"";
682         message += filename;
683         message += "\".";
684     } else {
685         message = "Failed to save to \"";
686         message += filename;
687         message += "\".";
688     }
689
690     mkDialog (message.c_str());
691
692     delete [] filename;
693
694     if ( !freeze ) {
695         fgSetBool("/sim/freeze/master", false);
696     }
697 }
698
699 void fgPrintVisibleSceneInfoCommand()
700 {
701     static const SGPropertyNode *master_freeze
702         = fgGetNode("/sim/freeze/master");
703
704     bool freeze = master_freeze->getBoolValue();
705     if ( !freeze ) {
706         fgSetBool("/sim/freeze/master", true);
707     }
708
709     flightgear::printVisibleSceneInfo(globals->get_renderer());
710
711     if ( !freeze ) {
712         fgSetBool("/sim/freeze/master", false);
713     }
714 }
715
716     
717