1
0
Fork 0

Merge remote-tracking branch 'upstream/next' into next

This commit is contained in:
Jakub Kákona 2020-04-01 15:51:23 +02:00
commit b4746123bd
179 changed files with 5969 additions and 1476 deletions

1
.gitignore vendored
View file

@ -20,3 +20,4 @@ build*/
*.qmlc *.qmlc
CMakeLists.txt.user CMakeLists.txt.user
*.pro.user *.pro.user
nbproject

View file

@ -28,6 +28,8 @@ if (ENABLE_HID_INPUT)
add_subdirectory(hidapi) add_subdirectory(hidapi)
endif() endif()
add_subdirectory(fonts)
if (ENABLE_PLIB_JOYSTICK) if (ENABLE_PLIB_JOYSTICK)
add_subdirectory(joystick) add_subdirectory(joystick)
endif() endif()

14
3rdparty/fonts/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,14 @@
set (FNT_SOURCES
fnt.cxx
fntTXF.cxx
fntBitmap.cxx
)
add_library(PLIBFont STATIC ${FNT_SOURCES})
target_link_libraries(PLIBFont SimGearCore)
target_include_directories(PLIBFont PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

2
3rdparty/fonts/README vendored Normal file
View file

@ -0,0 +1,2 @@
This is PLIB's 'fnt' library, extracted and modified
- added UTF8 path support, especially for Windows

257
3rdparty/fonts/fnt.cxx vendored Normal file
View file

@ -0,0 +1,257 @@
/*
PLIB - A Suite of Portable Game Libraries
Copyright (C) 1998,2002 Steve Baker
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
For further information visit http://plib.sourceforge.net
$Id: fnt.cxx 1939 2004-08-05 00:41:53Z puggles $
*/
#include "fntLocal.h"
#include <simgear/misc/sg_path.hxx>
#include <simgear/debug/logstream.hxx>
fntFont:: fntFont () {}
fntFont::~fntFont () {}
int fntTexFont::load ( const SGPath& path, GLenum mag, GLenum min )
{
if (path.extension() == "txf") {
return loadTXF ( path, mag, min ) ;
} else {
const auto ps = path.utf8Str();
fntSetError ( SG_WARN,
"fnt::load: Error - Unrecognised file format for '%s'", ps.c_str() ) ;
return FNT_FALSE ;
}
}
float fntTexFont::low_putch ( sgVec3 curpos, float pointsize,
float italic, char c )
{
unsigned int cc = (unsigned char) c ;
/* Auto case-convert if character is absent from font. */
if ( ! exists [ cc ] )
{
if ( cc >= 'A' && cc <= 'Z' )
cc = cc - 'A' + 'a' ;
else
if ( cc >= 'a' && cc <= 'z' )
cc = cc - 'a' + 'A' ;
if ( cc == ' ' )
{
curpos [ 0 ] += pointsize / 2.0f ;
return pointsize / 2.0f ;
}
}
/*
We might want to consider making some absent characters from
others (if they exist): lowercase 'l' could be made into digit '1'
or letter 'O' into digit '0'...or vice versa. We could also
make 'b', 'd', 'p' and 'q' by mirror-imaging - this would
save a little more texture memory in some fonts.
*/
if ( ! exists [ cc ] )
return 0.0f ;
glBegin ( GL_TRIANGLE_STRIP ) ;
glTexCoord2f ( t_left [cc], t_bot[cc] ) ;
glVertex3f ( curpos[0] + v_left [cc] * pointsize,
curpos[1] + v_bot [cc] * pointsize,
curpos[2] ) ;
glTexCoord2f ( t_left [cc], t_top[cc] ) ;
glVertex3f ( curpos[0] + (italic + v_left [cc]) * pointsize,
curpos[1] + v_top [cc] * pointsize,
curpos[2] ) ;
glTexCoord2f ( t_right[cc], t_bot[cc] ) ;
glVertex3f ( curpos[0] + v_right[cc] * pointsize,
curpos[1] + v_bot [cc] * pointsize,
curpos[2] ) ;
glTexCoord2f ( t_right[cc], t_top[cc] ) ;
glVertex3f ( curpos[0] + (italic + v_right[cc]) * pointsize,
curpos[1] + v_top [cc] * pointsize,
curpos[2] ) ;
glEnd () ;
float ww = ( gap + ( fixed_pitch ? width : widths[cc] ) ) * pointsize ;
curpos[0] += ww ;
return ww ;
}
void fntTexFont::setGlyph ( char c, float wid,
float tex_left, float tex_right,
float tex_bot , float tex_top ,
float vtx_left, float vtx_right,
float vtx_bot , float vtx_top )
{
unsigned int cc = (unsigned char) c ;
exists[cc] = FNT_TRUE ;
widths[cc] = wid;
t_left[cc] = tex_left ; t_right[cc] = tex_right ;
t_bot [cc] = tex_bot ; t_top [cc] = tex_top ;
v_left[cc] = vtx_left ; v_right[cc] = vtx_right ;
v_bot [cc] = vtx_bot ; v_top [cc] = vtx_top ;
}
int fntTexFont::getGlyph ( char c, float* wid,
float *tex_left, float *tex_right,
float *tex_bot , float *tex_top ,
float *vtx_left, float *vtx_right,
float *vtx_bot , float *vtx_top )
{
unsigned int cc = (unsigned char) c ;
if ( ! exists[cc] ) return FNT_FALSE ;
if ( wid != NULL ) *wid = widths [cc] ;
if ( tex_left != NULL ) *tex_left = t_left [cc] ;
if ( tex_right != NULL ) *tex_right = t_right[cc] ;
if ( tex_bot != NULL ) *tex_bot = t_bot [cc] ;
if ( tex_top != NULL ) *tex_top = t_top [cc] ;
if ( vtx_left != NULL ) *vtx_left = v_left [cc] ;
if ( vtx_right != NULL ) *vtx_right = v_right[cc] ;
if ( vtx_bot != NULL ) *vtx_bot = v_bot [cc] ;
if ( vtx_top != NULL ) *vtx_top = v_top [cc] ;
return FNT_TRUE ;
}
void fntTexFont::getBBox ( const char *s,
float pointsize, float italic,
float *left, float *right,
float *bot , float *top )
{
float h_pos = 0.0f ;
float v_pos = 0.0f ;
float l, r, b, t ;
l = r = b = t = 0.0f ;
while ( *s != '\0' )
{
if ( *s == '\n' )
{
h_pos = 0.0f ;
v_pos -= 1.333f ;
s++ ;
continue ;
}
unsigned int cc = (unsigned char) *(s++) ;
if ( ! exists [ cc ] )
{
if ( cc >= 'A' && cc <= 'Z' )
cc = cc - 'A' + 'a' ;
else
if ( cc >= 'a' && cc <= 'z' )
cc = cc - 'a' + 'A' ;
if ( cc == ' ' )
{
r += 0.5f ;
h_pos += 0.5f ;
continue ;
}
}
if ( ! exists [ cc ] )
continue ;
if ( italic >= 0 )
{
if ( l > h_pos + v_left [cc] ) l = h_pos + v_left [cc] ;
if ( r < gap + h_pos + v_right[cc]+italic ) r = gap + h_pos + v_right[cc] + italic ;
}
else
{
if ( l > h_pos + v_left [cc]+italic ) l = h_pos + v_left [cc] + italic ;
if ( r < gap + h_pos + v_right[cc] ) r = gap + h_pos + v_right[cc] ;
}
if ( b > v_pos + v_bot [cc] ) b = v_pos + v_bot [cc] ;
if ( t < v_pos + v_top [cc] ) t = v_pos + v_top [cc] ;
h_pos += gap + ( fixed_pitch ? width : widths[cc] ) ;
}
if ( left != NULL ) *left = l * pointsize ;
if ( right != NULL ) *right = r * pointsize ;
if ( top != NULL ) *top = t * pointsize ;
if ( bot != NULL ) *bot = b * pointsize ;
}
void fntTexFont::puts ( sgVec3 curpos, float pointsize, float italic, const char *s )
{
SGfloat origx = curpos[0] ;
if ( ! bound )
bind_texture () ;
while ( *s != '\0' )
{
if (*s == '\n')
{
curpos[0] = origx ;
curpos[1] -= pointsize ;
}
else
low_putch ( curpos, pointsize, italic, *s ) ;
s++ ;
}
}
void fntInit ()
{
/* Empty right now */
}
void fntSetError(int level, const char* format, ...)
{
const int bufferLength = 2048;
char buffer[2048];
va_list args;
va_start (args, format);
vsnprintf(buffer,bufferLength,format, args);
va_end (args);
SG_LOG(SG_GUI, static_cast<sgDebugPriority>(level), buffer);
}

357
3rdparty/fonts/fnt.h vendored Normal file
View file

@ -0,0 +1,357 @@
/*
PLIB - A Suite of Portable Game Libraries
Copyright (C) 1998,2002 Steve Baker
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
For further information visit http://plib.sourceforge.net
$Id: fnt.h 1923 2004-04-06 12:53:17Z sjbaker $
*/
#ifndef _FNT_H_
#define _FNT_H_ 1
#include <stdio.h>
#include <plib/sg.h>
#include <osg/GL>
#define FNTMAX_CHAR 256
#define FNT_TRUE 1
#define FNT_FALSE 0
class SGPath;
class fntFont
{
public:
fntFont () ;
virtual ~fntFont () ;
virtual void getBBox ( const char *s, float pointsize, float italic,
float *left, float *right,
float *bot , float *top ) = 0 ;
virtual void putch ( sgVec3 curpos, float pointsize, float italic, char c ) = 0 ;
virtual void puts ( sgVec3 curpos, float pointsize, float italic, const char *s ) = 0 ;
virtual void begin () = 0 ;
virtual void end () = 0 ;
virtual int load ( const SGPath& fname, GLenum mag = GL_NEAREST,
GLenum min = GL_LINEAR_MIPMAP_LINEAR ) = 0 ;
virtual void setFixedPitch ( int fix ) = 0 ;
virtual int isFixedPitch () const = 0 ;
virtual void setWidth ( float w ) = 0 ;
virtual void setGap ( float g ) = 0 ;
virtual float getWidth () const = 0 ;
virtual float getGap () const = 0 ;
virtual int hasGlyph ( char c ) const = 0 ;
} ;
class fntTexFont : public fntFont
{
private:
GLuint texture ;
int bound ;
int fixed_pitch ;
float width ; /* The width of a character in fixed-width mode */
float gap ; /* Set the gap between characters */
int exists [ FNTMAX_CHAR ] ; /* TRUE if character exists in tex-map */
/*
The quadrilaterals that describe the characters
in the font are drawn with the following texture
and spatial coordinates. The texture coordinates
are in (S,T) space, with (0,0) at the bottom left
of the image and (1,1) at the top-right.
The spatial coordinates are relative to the current
'cursor' position. They should be scaled such that
1.0 represent the height of a capital letter. Hence,
characters like 'y' which have a descender will be
given a negative v_bot. Most capitals will have
v_bot==0.0 and v_top==1.0.
*/
/* Nominal baseline widths */
float widths [ FNTMAX_CHAR ] ;
/* Texture coordinates */
float t_top [ FNTMAX_CHAR ] ; /* Top edge of each character [0..1] */
float t_bot [ FNTMAX_CHAR ] ; /* Bottom edge of each character [0..1] */
float t_left [ FNTMAX_CHAR ] ; /* Left edge of each character [0..1] */
float t_right [ FNTMAX_CHAR ] ; /* Right edge of each character [0..1] */
/* Vertex coordinates. */
float v_top [ FNTMAX_CHAR ] ;
float v_bot [ FNTMAX_CHAR ] ;
float v_left [ FNTMAX_CHAR ] ;
float v_right [ FNTMAX_CHAR ] ;
void bind_texture ()
{
glEnable ( GL_TEXTURE_2D ) ;
#ifdef GL_VERSION_1_1
glBindTexture ( GL_TEXTURE_2D, texture ) ;
#else
/* For ancient SGI machines */
glBindTextureEXT ( GL_TEXTURE_2D, texture ) ;
#endif
}
float low_putch ( sgVec3 curpos, float pointsize, float italic, char c ) ;
int loadTXF ( const SGPath& path, GLenum mag = GL_NEAREST,
GLenum min = GL_LINEAR_MIPMAP_LINEAR ) ;
public:
fntTexFont ()
{
bound = FNT_FALSE ;
fixed_pitch = FNT_TRUE ;
texture = 0 ;
width = 1.0f ;
gap = 0.1f ;
memset ( exists, FNT_FALSE, FNTMAX_CHAR * sizeof(int) ) ;
}
fntTexFont ( const SGPath& path, GLenum mag = GL_NEAREST,
GLenum min = GL_LINEAR_MIPMAP_LINEAR ) : fntFont ()
{
bound = FNT_FALSE ;
fixed_pitch = FNT_TRUE ;
texture = 0 ;
width = 1.0f ;
gap = 0.1f ;
memset ( exists, FNT_FALSE, FNTMAX_CHAR * sizeof(int) ) ;
load ( path, mag, min ) ;
}
~fntTexFont ()
{
if ( texture != 0 )
{
#ifdef GL_VERSION_1_1
glDeleteTextures ( 1, &texture ) ;
#else
/* For ancient SGI machines */
glDeleteTexturesEXT ( 1, &texture ) ;
#endif
}
}
int load ( const SGPath& path, GLenum mag = GL_NEAREST,
GLenum min = GL_LINEAR_MIPMAP_LINEAR ) override ;
void setFixedPitch ( int fix ) { fixed_pitch = fix ; }
int isFixedPitch () const { return fixed_pitch ; }
void setWidth ( float w ) { width = w ; }
void setGap ( float g ) { gap = g ; }
float getWidth () const { return width ; }
float getGap () const { return gap ; }
void setGlyph ( char c, float wid,
float tex_left, float tex_right,
float tex_bot , float tex_top ,
float vtx_left, float vtx_right,
float vtx_bot , float vtx_top ) ;
void setGlyph ( char c,
float tex_left, float tex_right,
float tex_bot , float tex_top ,
float vtx_left, float vtx_right,
float vtx_bot , float vtx_top ) /* deprecated */
{
setGlyph ( c, vtx_right,
tex_left, tex_right, tex_bot, tex_top,
vtx_left, vtx_right, vtx_bot, vtx_top ) ;
}
int getGlyph ( char c, float* wid,
float *tex_left = NULL, float *tex_right = NULL,
float *tex_bot = NULL, float *tex_top = NULL,
float *vtx_left = NULL, float *vtx_right = NULL,
float *vtx_bot = NULL, float *vtx_top = NULL) ;
int getGlyph ( char c,
float *tex_left = NULL, float *tex_right = NULL,
float *tex_bot = NULL, float *tex_top = NULL,
float *vtx_left = NULL, float *vtx_right = NULL,
float *vtx_bot = NULL, float *vtx_top = NULL) /* deprecated */
{
return getGlyph ( c, NULL,
tex_left, tex_right, tex_bot, tex_top,
vtx_left, vtx_right, vtx_bot, vtx_top ) ;
}
int hasGlyph ( char c ) const { return exists[ (GLubyte) c ] ; }
void getBBox ( const char *s, float pointsize, float italic,
float *left, float *right,
float *bot , float *top ) ;
void begin ()
{
bind_texture () ;
bound = FNT_TRUE ;
}
void end ()
{
bound = FNT_FALSE ;
}
void puts ( sgVec3 curpos, float pointsize, float italic, const char *s ) ;
void putch ( sgVec3 curpos, float pointsize, float italic, char c )
{
if ( ! bound )
bind_texture () ;
low_putch ( curpos, pointsize, italic, c ) ;
}
} ;
class fntBitmapFont : public fntFont
{
protected:
const GLubyte **data;
int first;
int count;
int height;
float xorig, yorig;
int fix;
float wid, gap;
public:
// data is a null-terminated list of glyph images:
// data[i][0] - image width
// data[i][1..n] - packed bitmap
fntBitmapFont ( const GLubyte **data, int first, int height,
float xorig, float yorig ) ;
virtual ~fntBitmapFont () ;
virtual void getBBox ( const char *s, float pointsize, float italic,
float *left, float *right,
float *bot , float *top ) ;
virtual void putch ( sgVec3 curpos, float pointsize, float italic, char c ) ;
virtual void puts ( sgVec3 curpos, float pointsize, float italic, const char *s ) ;
virtual void begin () ;
virtual void end () ;
virtual int load ( const SGPath& fname, GLenum mag, GLenum min ) override { return -1; }
virtual void setFixedPitch ( int f ) { fix = f; }
virtual int isFixedPitch () const { return fix; }
virtual void setWidth ( float w ) { wid = w; }
virtual void setGap ( float g ) { gap = g; }
virtual float getWidth () const { return wid; }
virtual float getGap () const { return gap; }
virtual int hasGlyph ( char c ) const ;
};
/* Builtin Bitmap Fonts */
#define FNT_BITMAP_8_BY_13 0
#define FNT_BITMAP_9_BY_15 1
#define FNT_BITMAP_HELVETICA_10 2
#define FNT_BITMAP_HELVETICA_12 3
#define FNT_BITMAP_HELVETICA_18 4
#define FNT_BITMAP_TIMES_ROMAN_10 5
#define FNT_BITMAP_TIMES_ROMAN_24 6
fntBitmapFont *fntGetBitmapFont(int id);
class fntRenderer
{
fntFont *font ;
sgVec3 curpos ;
float pointsize ;
float italic ;
public:
fntRenderer ()
{
start2f ( 0.0f, 0.0f ) ;
font = NULL ;
pointsize = 10 ;
italic = 0 ;
}
void start3fv ( sgVec3 pos ) { sgCopyVec3 ( curpos, pos ) ; }
void start2fv ( sgVec2 pos ) { sgCopyVec2 ( curpos, pos ) ; curpos[2]=0.0f ; }
void start2f ( float x, float y ) { sgSetVec3 ( curpos, x, y, 0.0f ) ; }
void start3f ( float x, float y, float z ) { sgSetVec3 ( curpos, x, y, z ) ; }
void getCursor ( float *x, float *y, float *z ) const
{
if ( x != NULL ) *x = curpos [ 0 ] ;
if ( y != NULL ) *y = curpos [ 1 ] ;
if ( z != NULL ) *z = curpos [ 2 ] ;
}
void setFont ( fntFont *f ) { font = f ; }
fntFont *getFont () const { return font ; }
void setSlant ( float i ) { italic = i ; }
void setPointSize ( float p ) { pointsize = p ; }
float getSlant () const { return italic ; }
float getPointSize () const { return pointsize ; }
void begin () { font->begin () ; }
void end () { font->end () ; }
void putch ( char c ) { font->putch ( curpos, pointsize, italic, c ) ; }
void puts ( const char *s ) { font->puts ( curpos, pointsize, italic, s ) ; }
} ;
void fntInit () ;
#endif

1017
3rdparty/fonts/fntBitmap.cxx vendored Normal file

File diff suppressed because it is too large Load diff

112
3rdparty/fonts/fntLocal.h vendored Normal file
View file

@ -0,0 +1,112 @@
/*
PLIB - A Suite of Portable Game Libraries
Copyright (C) 1998,2002 Steve Baker
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
For further information visit http://plib.sourceforge.net
$Id: fntLocal.h 1727 2002-11-09 21:27:21Z ude $
*/
#include "fnt.h"
extern int _fntIsSwapped ;
extern FILE *_fntCurrImageFd ;
inline void _fnt_swab_short ( unsigned short *x )
{
if ( _fntIsSwapped )
*x = (( *x >> 8 ) & 0x00FF ) |
(( *x << 8 ) & 0xFF00 ) ;
}
inline void _fnt_swab_int ( unsigned int *x )
{
if ( _fntIsSwapped )
*x = (( *x >> 24 ) & 0x000000FF ) |
(( *x >> 8 ) & 0x0000FF00 ) |
(( *x << 8 ) & 0x00FF0000 ) |
(( *x << 24 ) & 0xFF000000 ) ;
}
inline void _fnt_swab_int_array ( int *x, int leng )
{
if ( ! _fntIsSwapped )
return ;
for ( int i = 0 ; i < leng ; i++ )
{
*x = (( *x >> 24 ) & 0x000000FF ) |
(( *x >> 8 ) & 0x0000FF00 ) |
(( *x << 8 ) & 0x00FF0000 ) |
(( *x << 24 ) & 0xFF000000 ) ;
x++ ;
}
}
inline unsigned char _fnt_readByte ()
{
unsigned char x ;
fread ( & x, sizeof(unsigned char), 1, _fntCurrImageFd ) ;
return x ;
}
inline unsigned short _fnt_readShort ()
{
unsigned short x ;
fread ( & x, sizeof(unsigned short), 1, _fntCurrImageFd ) ;
_fnt_swab_short ( & x ) ;
return x ;
}
inline unsigned int _fnt_readInt ()
{
unsigned int x ;
fread ( & x, sizeof(unsigned int), 1, _fntCurrImageFd ) ;
_fnt_swab_int ( & x ) ;
return x ;
}
#define FNT_BYTE_FORMAT 0
#define FNT_BITMAP_FORMAT 1
struct TXF_Glyph
{
unsigned short ch ;
unsigned char w ;
unsigned char h ;
signed char x_off ;
signed char y_off ;
signed char step ;
signed char unknown ;
short x ;
short y ;
sgVec2 tx0 ; sgVec2 vx0 ;
sgVec2 tx1 ; sgVec2 vx1 ;
sgVec2 tx2 ; sgVec2 vx2 ;
sgVec2 tx3 ; sgVec2 vx3 ;
} ;
void fntSetError(int, const char* format, ...);

341
3rdparty/fonts/fntTXF.cxx vendored Executable file
View file

@ -0,0 +1,341 @@
/*
PLIB - A Suite of Portable Game Libraries
Copyright (C) 1998,2002 Steve Baker
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
For further information visit http://plib.sourceforge.net
$Id: fntTXF.cxx 1735 2002-12-01 18:21:48Z sjbaker $
*/
#include "fntLocal.h"
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/sg_path.hxx>
FILE *_fntCurrImageFd ;
int _fntIsSwapped = FALSE ;
static void tex_make_mip_maps ( GLubyte *image, int xsize,
int ysize, int zsize )
{
GLubyte *texels [ 20 ] ; /* One element per level of MIPmap */
for ( int l = 0 ; l < 20 ; l++ )
texels [ l ] = NULL ;
texels [ 0 ] = image ;
int lev ;
for ( lev = 0 ; (( xsize >> (lev+1) ) != 0 ||
( ysize >> (lev+1) ) != 0 ) ; lev++ )
{
/* Suffix '1' is the higher level map, suffix '2' is the lower level. */
int l1 = lev ;
int l2 = lev+1 ;
int w1 = xsize >> l1 ;
int h1 = ysize >> l1 ;
int w2 = xsize >> l2 ;
int h2 = ysize >> l2 ;
if ( w1 <= 0 ) w1 = 1 ;
if ( h1 <= 0 ) h1 = 1 ;
if ( w2 <= 0 ) w2 = 1 ;
if ( h2 <= 0 ) h2 = 1 ;
texels [ l2 ] = new GLubyte [ w2 * h2 * zsize ] ;
for ( int x2 = 0 ; x2 < w2 ; x2++ )
for ( int y2 = 0 ; y2 < h2 ; y2++ )
for ( int c = 0 ; c < zsize ; c++ )
{
int x1 = x2 + x2 ;
int x1_1 = ( x1 + 1 ) % w1 ;
int y1 = y2 + y2 ;
int y1_1 = ( y1 + 1 ) % h1 ;
int t1 = texels [ l1 ] [ (y1 * w1 + x1 ) * zsize + c ] ;
int t2 = texels [ l1 ] [ (y1_1 * w1 + x1 ) * zsize + c ] ;
int t3 = texels [ l1 ] [ (y1 * w1 + x1_1) * zsize + c ] ;
int t4 = texels [ l1 ] [ (y1_1 * w1 + x1_1) * zsize + c ] ;
texels [ l2 ] [ (y2 * w2 + x2) * zsize + c ] =
( t1 + t2 + t3 + t4 ) / 4 ;
}
}
texels [ lev+1 ] = NULL ;
if ( ! ((xsize & (xsize-1))==0) ||
! ((ysize & (ysize-1))==0) )
{
fntSetError ( SG_ALERT,
"TXFloader: TXF Map is not a power-of-two in size!" ) ;
}
glPixelStorei ( GL_UNPACK_ALIGNMENT, 1 ) ;
int map_level = 0 ;
#ifdef PROXY_TEXTURES_ARE_NOT_BROKEN
int ww ;
do
{
glTexImage2D ( GL_PROXY_TEXTURE_2D,
map_level, zsize, xsize, ysize, FALSE /* Border */,
(zsize==1)?GL_LUMINANCE:
(zsize==2)?GL_LUMINANCE_ALPHA:
(zsize==3)?GL_RGB:
GL_RGBA,
GL_UNSIGNED_BYTE, NULL ) ;
glGetTexLevelParameteriv ( GL_PROXY_TEXTURE_2D, 0,GL_TEXTURE_WIDTH, &ww ) ;
if ( ww == 0 )
{
delete [] texels [ 0 ] ;
xsize >>= 1 ;
ysize >>= 1 ;
for ( int l = 0 ; texels [ l ] != NULL ; l++ )
texels [ l ] = texels [ l+1 ] ;
if ( xsize < 64 && ysize < 64 )
{
fntSetError ( SG_ALERT,
"FNT: OpenGL will not accept a font texture?!?" ) ;
}
}
} while ( ww == 0 ) ;
#endif
for ( int i = 0 ; texels [ i ] != NULL ; i++ )
{
int w = xsize>>i ;
int h = ysize>>i ;
if ( w <= 0 ) w = 1 ;
if ( h <= 0 ) h = 1 ;
glTexImage2D ( GL_TEXTURE_2D,
map_level, zsize, w, h, FALSE /* Border */,
(zsize==1)?GL_LUMINANCE:
(zsize==2)?GL_LUMINANCE_ALPHA:
(zsize==3)?GL_RGB:
GL_RGBA,
GL_UNSIGNED_BYTE, (GLvoid *) texels[i] ) ;
map_level++ ;
delete [] texels [ i ] ;
}
}
int fntTexFont::loadTXF ( const SGPath& path, GLenum mag, GLenum min )
{
FILE *fd ;
const auto ps = path.utf8Str();
#if defined(SG_WINDOWS)
std::wstring fp = path.wstr();
fd = _wfopen(fp.c_str(), L"rb");
#else
fd = fopen(ps.c_str(), "rb");
#endif
if ( fd == nullptr )
{
fntSetError ( SG_WARN,
"fntLoadTXF: Failed to open '%s' for reading.", ps.c_str() ) ;
return FNT_FALSE ;
}
_fntCurrImageFd = fd ;
unsigned char magic [ 4 ] ;
if ( (int)fread ( &magic, sizeof (unsigned int), 1, fd ) != 1 )
{
fntSetError ( SG_WARN,
"fntLoadTXF: '%s' an empty file!", ps.c_str() ) ;
return FNT_FALSE ;
}
if ( magic [ 0 ] != 0xFF || magic [ 1 ] != 't' ||
magic [ 2 ] != 'x' || magic [ 3 ] != 'f' )
{
fntSetError ( SG_WARN,
"fntLoadTXF: '%s' is not a 'txf' font file.", ps.c_str() ) ;
return FNT_FALSE ;
}
_fntIsSwapped = FALSE ;
int endianness = _fnt_readInt () ;
_fntIsSwapped = ( endianness != 0x12345678 ) ;
int format = _fnt_readInt () ;
int tex_width = _fnt_readInt () ;
int tex_height = _fnt_readInt () ;
int max_height = _fnt_readInt () ;
_fnt_readInt () ;
int num_glyphs = _fnt_readInt () ;
int w = tex_width ;
int h = tex_height ;
float xstep = 0.5f / (float) w ;
float ystep = 0.5f / (float) h ;
int i, j ;
/*
Load the TXF_Glyph array
*/
TXF_Glyph glyph ;
for ( i = 0 ; i < num_glyphs ; i++ )
{
glyph . ch = _fnt_readShort () ;
glyph . w = _fnt_readByte () ;
glyph . h = _fnt_readByte () ;
glyph . x_off = _fnt_readByte () ;
glyph . y_off = _fnt_readByte () ;
glyph . step = _fnt_readByte () ;
glyph . unknown = _fnt_readByte () ;
glyph . x = _fnt_readShort () ;
glyph . y = _fnt_readShort () ;
setGlyph ( (char) glyph.ch,
(float) glyph.step / (float) max_height,
(float) glyph.x / (float) w + xstep,
(float)( glyph.x + glyph.w ) / (float) w + xstep,
(float) glyph.y / (float) h + ystep,
(float)( glyph.y + glyph.h ) / (float) h + ystep,
(float) glyph.x_off / (float) max_height,
(float)( glyph.x_off + glyph.w ) / (float) max_height,
(float) glyph.y_off / (float) max_height,
(float)( glyph.y_off + glyph.h ) / (float) max_height ) ;
}
exists [ static_cast<int>(' ') ] = FALSE ;
/*
Load the image part of the file
*/
int ntexels = w * h ;
unsigned char *teximage ;
unsigned char *texbitmap ;
switch ( format )
{
case FNT_BYTE_FORMAT:
{
unsigned char *orig = new unsigned char [ ntexels ] ;
if ( (int)fread ( orig, 1, ntexels, fd ) != ntexels )
{
fntSetError ( SG_WARN,
"fntLoadTXF: Premature EOF in '%s'.", ps.c_str() ) ;
return FNT_FALSE ;
}
teximage = new unsigned char [ 2 * ntexels ] ;
for ( i = 0 ; i < ntexels ; i++ )
{
teximage [ i*2 ] = orig [ i ] ;
teximage [ i*2 + 1 ] = orig [ i ] ;
}
delete [] orig ;
}
break ;
case FNT_BITMAP_FORMAT:
{
int stride = (w + 7) >> 3;
texbitmap = new unsigned char [ stride * h ] ;
if ( (int)fread ( texbitmap, 1, stride * h, fd ) != stride * h )
{
delete [] texbitmap ;
fntSetError ( SG_WARN,
"fntLoadTXF: Premature EOF in '%s'.", ps.c_str() ) ;
return FNT_FALSE ;
}
teximage = new unsigned char [ 2 * ntexels ] ;
for ( i = 0 ; i < 2 * ntexels ; i++ )
teximage [ i ] = 0 ;
for (i = 0; i < h; i++)
for (j = 0; j < w; j++)
if (texbitmap[i * stride + (j >> 3)] & (1 << (j & 7)))
{
teximage[(i * w + j) * 2 ] = 255;
teximage[(i * w + j) * 2 + 1] = 255;
}
delete [] texbitmap ;
}
break ;
default:
fntSetError ( SG_WARN,
"fntLoadTXF: Unrecognised format type in '%s'.", ps.c_str() ) ;
return FNT_FALSE ;
}
fclose ( fd ) ;
fixed_pitch = FALSE ;
#ifdef GL_VERSION_1_1
glGenTextures ( 1, & texture ) ;
glBindTexture ( GL_TEXTURE_2D, texture ) ;
#else
/* This is only useful on some ancient SGI hardware */
glGenTexturesEXT ( 1, & texture ) ;
glBindTextureEXT ( GL_TEXTURE_2D, texture ) ;
#endif
tex_make_mip_maps ( teximage, w, h, 2 ) ;
glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ) ;
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag ) ;
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min ) ;
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ) ;
glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ) ;
#ifdef GL_VERSION_1_1
glBindTexture ( GL_TEXTURE_2D, 0 ) ;
#else
glBindTextureEXT ( GL_TEXTURE_2D, 0 ) ;
#endif
return FNT_TRUE ;
}

39
3rdparty/hts_engine_API/lib/HTS_misc.c vendored Normal file → Executable file
View file

@ -75,14 +75,51 @@ typedef struct _HTS_Data {
size_t index; size_t index;
} HTS_Data; } HTS_Data;
#if defined(_WIN32)
#include <windows.h>
#define MAX_PATH_SIZE 8192
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
// Encode 'path' which is assumed UTF-8 string, into UNICODE string.
// wbuf and wbuf_len is a target buffer and its length.
static void to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) {
char buf[MAX_PATH_SIZE * 2], buf2[MAX_PATH_SIZE * 2], *p;
strncpy(buf, path, sizeof(buf));
buf[sizeof(buf) - 1] = '\0';
// Trim trailing slashes. Leave backslash for paths like "X:\"
p = buf + strlen(buf) - 1;
while (p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0';
// Convert to Unicode and back. If doubly-converted string does not
// match the original, something is fishy, reject.
memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
NULL, NULL);
if (strcmp(buf, buf2) != 0) {
wbuf[0] = L'\0';
}
}
#endif
/* HTS_fopen_from_fn: wrapper for fopen */ /* HTS_fopen_from_fn: wrapper for fopen */
HTS_File *HTS_fopen_from_fn(const char *name, const char *opt) HTS_File *HTS_fopen_from_fn(const char *name, const char *opt)
{ {
HTS_File *fp = (HTS_File *) HTS_calloc(1, sizeof(HTS_File)); HTS_File *fp = (HTS_File *) HTS_calloc(1, sizeof(HTS_File));
fp->type = HTS_FILE; fp->type = HTS_FILE;
#if defined(_WIN32)
wchar_t wpath[MAX_PATH_SIZE], wmode[10];
to_wchar(name, wpath, ARRAY_SIZE(wpath));
to_wchar(opt, wmode, ARRAY_SIZE(wmode));
fp->pointer = (void*) _wfopen(wpath, wmode);
#else
fp->pointer = (void *) fopen(name, opt); fp->pointer = (void *) fopen(name, opt);
#endif
if (fp->pointer == NULL) { if (fp->pointer == NULL) {
HTS_error(0, "HTS_fopen: Cannot open %s.\n", name); HTS_error(0, "HTS_fopen: Cannot open %s.\n", name);
HTS_free(fp); HTS_free(fp);

View file

@ -370,10 +370,7 @@ if (ENABLE_QT)
find_package(Qt5 5.4 COMPONENTS Widgets Network Qml Quick Svg) find_package(Qt5 5.4 COMPONENTS Widgets Network Qml Quick Svg)
if (Qt5Widgets_FOUND) if (Qt5Widgets_FOUND)
message(STATUS "Will enable Qt launcher GUI") message(STATUS "Will enable Qt launcher GUI")
message(STATUS " Qt5Widgets version: ${Qt5Widgets_VERSION_STRING}")
message(STATUS " Qt5Widgets include dir: ${Qt5Widgets_INCLUDE_DIRS}")
set(HAVE_QT 1) set(HAVE_QT 1)
include (Translations) include (Translations)
else() else()
# don't try to build FGQCanvas if Qt wasn't found correctly # don't try to build FGQCanvas if Qt wasn't found correctly
@ -385,7 +382,7 @@ else()
endif (ENABLE_QT) endif (ENABLE_QT)
############################################################################## ##############################################################################
find_package(PLIB REQUIRED puaux pu fnt) find_package(PLIB REQUIRED puaux pu)
# FlightGear and SimGear versions need to match major + minor # FlightGear and SimGear versions need to match major + minor
# split version string into components, note CMAKE_MATCH_0 is the entire regexp match # split version string into components, note CMAKE_MATCH_0 is the entire regexp match

View file

@ -109,9 +109,9 @@ if(${PLIB_LIBRARIES} STREQUAL "PLIB_LIBRARIES-NOTFOUND")
# handle MSVC confusion over pu/pui naming, by removing # handle MSVC confusion over pu/pui naming, by removing
# 'pu' and then adding it back # 'pu' and then adding it back
list(REMOVE_ITEM outDeps "pu" "fnt" "sg") list(REMOVE_ITEM outDeps "pu" "fnt" "sg")
list(APPEND outDeps ${PUNAME} "fnt" "sg") list(APPEND outDeps ${PUNAME} "sg")
elseif (${c} STREQUAL "puaux") elseif (${c} STREQUAL "puaux")
list(APPEND outDeps ${PUNAME} "fnt" "sg") list(APPEND outDeps ${PUNAME} "sg")
elseif (${c} STREQUAL "ssg") elseif (${c} STREQUAL "ssg")
list(APPEND outDeps "sg") list(APPEND outDeps "sg")
endif() endif()

View file

@ -66,6 +66,8 @@ function(setup_fgfs_libraries target)
target_link_libraries(${target} PLIBJoystick) target_link_libraries(${target} PLIBJoystick)
endif() endif()
target_link_libraries(${target} PLIBFont)
if(SYSTEM_HTS_ENGINE) if(SYSTEM_HTS_ENGINE)
target_link_libraries(${target} flite_hts ${HTS_ENGINE_LIBRARIES}) target_link_libraries(${target} flite_hts ${HTS_ENGINE_LIBRARIES})
else() else()

View file

@ -19,9 +19,10 @@ if (${do_translate} AND NOT TARGET Qt5::lrelease)
"\n(on Linux You may need to install an additional package containing the Qt5 translation tools)") "\n(on Linux You may need to install an additional package containing the Qt5 translation tools)")
endif() endif()
# FIXME - determine this based on subdirs of TRANSLATIONS_SRC_DIR
set(LANGUAGES en_US de es nl fr it pl pt ru zh_CN)
if (${do_translate}) if (${do_translate})
# FIXME - determine this based on subdirs of TRANSLATIONS_SRC_DIR
set(LANGUAGES en_US de es nl fr it pl pt ru zh_CN)
set(translation_res "${PROJECT_BINARY_DIR}/translations.qrc") set(translation_res "${PROJECT_BINARY_DIR}/translations.qrc")
add_custom_target(fgfs_qm_files ALL) add_custom_target(fgfs_qm_files ALL)
@ -55,3 +56,14 @@ if (${do_translate})
# set this so config.h can detect it # set this so config.h can detect it
set(HAVE_QRC_TRANSLATIONS TRUE) set(HAVE_QRC_TRANSLATIONS TRUE)
endif() # of do translate endif() # of do translate
add_custom_target(ts)
foreach(lang ${LANGUAGES})
add_custom_target(
ts_${lang}
COMMAND Qt5::lupdate ${CMAKE_SOURCE_DIR}/src/GUI
-locations relative -no-ui-lines -ts ${TRANSLATIONS_SRC_DIR}/${lang}/FlightGear-Qt.xlf
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_dependencies(ts ts_${lang})
endforeach()

View file

@ -184,6 +184,7 @@ class DirIndex:
def __init__(self, dirIndexFile): def __init__(self, dirIndexFile):
self.d = [] self.d = []
self.f = [] self.f = []
self.t = []
self.version = 0 self.version = 0
self.path = None # will be a VirtualPath instance when set self.path = None # will be a VirtualPath instance when set
@ -221,6 +222,9 @@ class DirIndex:
elif tokens[0] == "f": elif tokens[0] == "f":
self.f.append({ 'name': tokens[1], 'hash': tokens[2], 'size': tokens[3] }) self.f.append({ 'name': tokens[1], 'hash': tokens[2], 'size': tokens[3] })
elif tokens[0] == "t":
self.t.append({ 'name': tokens[1], 'hash': tokens[2], 'size': tokens[3] })
def _sanityCheck(self): def _sanityCheck(self):
if self.path is None: if self.path is None:
assert self._rawContents is not None assert self._rawContents is not None
@ -239,6 +243,9 @@ class DirIndex:
def getDirectories(self): def getDirectories(self):
return self.d return self.d
def getTarballs(self):
return self.t
def getFiles(self): def getFiles(self):
return self.f return self.f
@ -639,6 +646,14 @@ class TerraSync:
subdir['hash']) subdir['hash'])
serverDirs.append(d) serverDirs.append(d)
for tarball in dirIndex.getTarballs():
# Tarballs are handled the same as normal files.
f = tarball['name']
self.processFileEntry(virtualBase / f,
join(relativeBase, f),
tarball['hash'])
serverFiles.append(f)
localFullPath = join(self.target, relativeBase) localFullPath = join(self.target, relativeBase)
localFiles = [ f for f in listdir(localFullPath) localFiles = [ f for f in listdir(localFullPath)
if isfile(join(localFullPath, f)) ] if isfile(join(localFullPath, f)) ]

View file

@ -516,7 +516,7 @@ void FGAIBase::Transform() {
*/ */
std::vector<std::string> FGAIBase::resolveModelPath(ModelSearchOrder searchOrder) std::vector<std::string> FGAIBase::resolveModelPath(ModelSearchOrder searchOrder)
{ {
std::vector<std::string> path_list; string_list path_list;
if (searchOrder == DATA_ONLY) { if (searchOrder == DATA_ONLY) {
SG_LOG(SG_AI, SG_DEBUG, "Resolving model path: DATA only"); SG_LOG(SG_AI, SG_DEBUG, "Resolving model path: DATA only");
@ -536,7 +536,8 @@ std::vector<std::string> FGAIBase::resolveModelPath(ModelSearchOrder searchOrder
} }
} else { } else {
// No model, so fall back to the default // No model, so fall back to the default
path_list.push_back(fgGetString("/sim/multiplay/default-model", default_model)); const SGPath defaultModelPath = SGPath::fromUtf8(fgGetString("/sim/multiplay/default-model", default_model));
path_list.push_back(defaultModelPath.utf8Str());
} }
} else { } else {
SG_LOG(SG_AI, SG_DEBUG, "Resolving model path: PREFER_AI/PREFER_DATA"); SG_LOG(SG_AI, SG_DEBUG, "Resolving model path: PREFER_AI/PREFER_DATA");
@ -544,8 +545,8 @@ std::vector<std::string> FGAIBase::resolveModelPath(ModelSearchOrder searchOrder
for (SGPath p : globals->get_data_paths("AI")) { for (SGPath p : globals->get_data_paths("AI")) {
p.append(model_path); p.append(model_path);
if (p.exists()) { if (p.exists()) {
SG_LOG(SG_AI, SG_DEBUG, "Found AI model: " << p.local8BitStr()); SG_LOG(SG_AI, SG_DEBUG, "Found AI model: " << p);
path_list.push_back(p.local8BitStr()); path_list.push_back(p.utf8Str());
break; break;
} }
} }
@ -566,8 +567,8 @@ std::vector<std::string> FGAIBase::resolveModelPath(ModelSearchOrder searchOrder
for (SGPath p : globals->get_data_paths()) { for (SGPath p : globals->get_data_paths()) {
p.append(fallback_path); p.append(fallback_path);
if (p.exists()) { if (p.exists()) {
SG_LOG(SG_AI, SG_DEBUG, "Found fallback model path for index " << _fallback_model_index << ": " << p.local8BitStr()); SG_LOG(SG_AI, SG_DEBUG, "Found fallback model path for index " << _fallback_model_index << ": " << p);
path_list.push_back(p.local8BitStr()); path_list.push_back(p.utf8Str());
break; break;
} }
} }

View file

@ -17,9 +17,7 @@
// along with this program; if not, write to the Free Software // along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H #include <config.h>
# include <config.h>
#endif
#include <algorithm> #include <algorithm>
#include <string> #include <string>
@ -35,11 +33,13 @@
#include "AICarrier.hxx" #include "AICarrier.hxx"
FGAICarrier::FGAICarrier() : FGAIShip(otCarrier), deck_altitude(65.0065) { FGAICarrier::FGAICarrier() :
FGAIShip(otCarrier),
deck_altitude(65.0065)
{
} }
FGAICarrier::~FGAICarrier() { FGAICarrier::~FGAICarrier() = default;
}
void FGAICarrier::readFromScenario(SGPropertyNode* scFileNode) { void FGAICarrier::readFromScenario(SGPropertyNode* scFileNode) {
if (!scFileNode) if (!scFileNode)
@ -67,11 +67,14 @@ void FGAICarrier::readFromScenario(SGPropertyNode* scFileNode) {
// Transform to the right coordinate frame, configuration is done in // Transform to the right coordinate frame, configuration is done in
// the usual x-back, y-right, z-up coordinates, computations // the usual x-back, y-right, z-up coordinates, computations
// in the simulation usual body x-forward, y-right, z-down coordinates // in the simulation usual body x-forward, y-right, z-down coordinates
flols_off(0) = - flols->getDoubleValue("x-offset-m", 0); _flolsPosOffset(0) = - flols->getDoubleValue("x-offset-m", 0);
flols_off(1) = flols->getDoubleValue("y-offset-m", 0); _flolsPosOffset(1) = flols->getDoubleValue("y-offset-m", 0);
flols_off(2) = - flols->getDoubleValue("z-offset-m", 0); _flolsPosOffset(2) = - flols->getDoubleValue("z-offset-m", 0);
_flolsHeadingOffsetDeg = flols->getDoubleValue("heading-offset-deg", 0.0);
_flolsApproachAngle = flols->getDoubleValue("glidepath-angle-deg", 3.0);
} else } else
flols_off = SGVec3d::zeros(); _flolsPosOffset = SGVec3d::zeros();
std::vector<SGPropertyNode_ptr> props = scFileNode->getChildren("parking-pos"); std::vector<SGPropertyNode_ptr> props = scFileNode->getChildren("parking-pos");
std::vector<SGPropertyNode_ptr>::const_iterator it; std::vector<SGPropertyNode_ptr>::const_iterator it;
@ -180,13 +183,13 @@ void FGAICarrier::update(double dt) {
// rotate the eyepoint wrt carrier vector into the carriers frame // rotate the eyepoint wrt carrier vector into the carriers frame
eyeWrtCarrier = ec2body.transform(eyeWrtCarrier); eyeWrtCarrier = ec2body.transform(eyeWrtCarrier);
// the eyepoints vector wrt the flols position // the eyepoints vector wrt the flols position
SGVec3d eyeWrtFlols = eyeWrtCarrier - flols_off; SGVec3d eyeWrtFlols = eyeWrtCarrier - _flolsPosOffset;
// the distance from the eyepoint to the flols // the distance from the eyepoint to the flols
dist = norm(eyeWrtFlols); dist = norm(eyeWrtFlols);
// now the angle, positive angles are upwards // now the angle, positive angles are upwards
if (fabs(dist) < SGLimits<float>::min()) { if (fabs(dist) < SGLimits<double>::min()) {
angle = 0; angle = 0;
} else { } else {
double sAngle = -eyeWrtFlols(2)/dist; double sAngle = -eyeWrtFlols(2)/dist;
@ -327,11 +330,9 @@ bool FGAICarrier::getParkPosition(const string& id, SGGeod& geodPos,
{ {
// FIXME: does not yet cover rotation speeds. // FIXME: does not yet cover rotation speeds.
list<ParkPosition>::iterator it = ppositions.begin(); for (const auto& ppos : ppositions) {
while (it != ppositions.end()) {
// Take either the specified one or the first one ... // Take either the specified one or the first one ...
if ((*it).name == id || id.empty()) { if (ppos.name == id || id.empty()) {
ParkPosition ppos = *it;
SGVec3d cartPos = getCartPosAt(ppos.offset); SGVec3d cartPos = getCartPosAt(ppos.offset);
geodPos = SGGeod::fromCart(cartPos); geodPos = SGGeod::fromCart(cartPos);
hdng = hdg + ppos.heading_deg; hdng = hdg + ppos.heading_deg;
@ -341,12 +342,28 @@ bool FGAICarrier::getParkPosition(const string& id, SGGeod& geodPos,
uvw = SGVec3d(chdng*speed_fps, shdng*speed_fps, 0); uvw = SGVec3d(chdng*speed_fps, shdng*speed_fps, 0);
return true; return true;
} }
++it;
} }
return false; return false;
} }
bool FGAICarrier::getFLOLSPositionHeading(SGGeod& geodPos, double& heading) const
{
SGVec3d cartPos = getCartPosAt(_flolsPosOffset);
geodPos = SGGeod::fromCart(cartPos);
// at present we don't support a heading offset for the FLOLS, so
// heading is just the carrier heading
heading = hdg + _flolsHeadingOffsetDeg;
return true;
}
double FGAICarrier::getFLOLFSGlidepathAngleDeg() const
{
return _flolsApproachAngle;
}
// find relative wind // find relative wind
void FGAICarrier::UpdateWind( double dt) { void FGAICarrier::UpdateWind( double dt) {
@ -371,8 +388,7 @@ void FGAICarrier::UpdateWind( double dt) {
+ (rel_wind_speed_from_north_kts * rel_wind_speed_from_north_kts)); + (rel_wind_speed_from_north_kts * rel_wind_speed_from_north_kts));
//calculate the relative wind direction //calculate the relative wind direction
rel_wind_from_deg = atan2(rel_wind_speed_from_east_kts, rel_wind_speed_from_north_kts) rel_wind_from_deg = SGMiscd::rad2deg(atan2(rel_wind_speed_from_east_kts, rel_wind_speed_from_north_kts));
* SG_RADIANS_TO_DEGREES;
//calculate rel wind //calculate rel wind
rel_wind = rel_wind_from_deg - hdg; rel_wind = rel_wind_from_deg - hdg;
@ -640,7 +656,7 @@ SGSharedPtr<FGAICarrier> FGAICarrier::findCarrierByNameOrPennant(const std::stri
return {}; return {};
} }
for (const auto aiObject : aiManager->get_ai_list()) { for (const auto& aiObject : aiManager->get_ai_list()) {
if (aiObject->isa(FGAIBase::otCarrier)) { if (aiObject->isa(FGAIBase::otCarrier)) {
SGSharedPtr<FGAICarrier> c = static_cast<FGAICarrier*>(aiObject.get()); SGSharedPtr<FGAICarrier> c = static_cast<FGAICarrier*>(aiObject.get());
if ((c->sign == namePennant) || (c->_getName() == namePennant)) { if ((c->sign == namePennant) || (c->_getName() == namePennant)) {
@ -652,7 +668,7 @@ SGSharedPtr<FGAICarrier> FGAICarrier::findCarrierByNameOrPennant(const std::stri
return {}; return {};
} }
void FGAICarrier::extractNamesPennantsFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario) void FGAICarrier::extractCarriersFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario)
{ {
for (auto c : xmlNode->getChildren("entry")) { for (auto c : xmlNode->getChildren("entry")) {
if (c->getStringValue("type") != std::string("carrier")) if (c->getStringValue("type") != std::string("carrier"))
@ -673,6 +689,14 @@ void FGAICarrier::extractNamesPennantsFromScenario(SGPropertyNode_ptr xmlNode, S
// the find code above just looks for anything called a name (so alias // the find code above just looks for anything called a name (so alias
// are possible, for example) // are possible, for example)
if (!name.empty()) carrierNode->addChild("name")->setStringValue(name); if (!name.empty()) carrierNode->addChild("name")->setStringValue(name);
if (!pennant.empty()) carrierNode->addChild("name")->setStringValue(pennant); if (!pennant.empty()) {
carrierNode->addChild("name")->setStringValue(pennant);
carrierNode->addChild("pennant-number")->setStringValue(pennant);
}
// extact parkings
for (auto p : c->getChildren("parking-pos")) {
carrierNode->addChild("parking-pos")->setStringValue(p->getStringValue("name"));
}
} }
} }

View file

@ -86,8 +86,11 @@ public:
* This is used to support 'start on a carrier', since we can quickly find * This is used to support 'start on a carrier', since we can quickly find
* the corresponding scenario file to be loaded. * the corresponding scenario file to be loaded.
*/ */
static void extractNamesPennantsFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario); static void extractCarriersFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario);
bool getFLOLSPositionHeading(SGGeod &pos, double &heading) const;
double getFLOLFSGlidepathAngleDeg() const;
private: private:
/// Is sufficient to be private, stores a possible position to place an /// Is sufficient to be private, stores a possible position to place an
/// aircraft on start /// aircraft on start
@ -115,8 +118,10 @@ private:
list<ParkPosition> ppositions; // List of positions where an aircraft can start. list<ParkPosition> ppositions; // List of positions where an aircraft can start.
string sign; // The sign of this carrier. string sign; // The sign of this carrier.
// these describe the flols // these describe the flols
SGVec3d flols_off; SGVec3d _flolsPosOffset;
double _flolsHeadingOffsetDeg = 0.0; ///< angle in degrees offset from the carrier centerline
double _flolsApproachAngle = 3.0; ///< glidepath angle for the FLOLS
double dist; // the distance of the eyepoint from the flols double dist; // the distance of the eyepoint from the flols
double angle; double angle;

View file

@ -71,11 +71,11 @@ public:
_unloadScript = nasalScripts->getStringValue("unload"); _unloadScript = nasalScripts->getStringValue("unload");
std::string loadScript = nasalScripts->getStringValue("load"); std::string loadScript = nasalScripts->getStringValue("load");
if (!loadScript.empty()) { if (!loadScript.empty()) {
FGNasalSys* nasalSys = (FGNasalSys*) globals->get_subsystem("nasal"); FGNasalSys* nasalSys = globals->get_subsystem<FGNasalSys>();
std::string moduleName = "scenario_" + _internalName; std::string moduleName = "scenario_" + _internalName;
nasalSys->createModule(moduleName.c_str(), moduleName.c_str(), nasalSys->createModule(moduleName.c_str(), moduleName.c_str(),
loadScript.c_str(), loadScript.size(), loadScript.c_str(), loadScript.size(),
0); nullptr);
} }
} }
@ -85,7 +85,7 @@ public:
[](FGAIBasePtr ai) { ai->setDie(true); }); [](FGAIBasePtr ai) { ai->setDie(true); });
FGNasalSys* nasalSys = (FGNasalSys*) globals->get_subsystem("nasal"); FGNasalSys* nasalSys = globals->get_subsystem<FGNasalSys>();
if (!nasalSys) if (!nasalSys)
return; return;
@ -93,7 +93,7 @@ public:
if (!_unloadScript.empty()) { if (!_unloadScript.empty()) {
nasalSys->createModule(moduleName.c_str(), moduleName.c_str(), nasalSys->createModule(moduleName.c_str(), moduleName.c_str(),
_unloadScript.c_str(), _unloadScript.size(), _unloadScript.c_str(), _unloadScript.size(),
0); nullptr);
} }
nasalSys->deleteModule(moduleName.c_str()); nasalSys->deleteModule(moduleName.c_str());
@ -165,16 +165,19 @@ FGAIManager::init() {
registerScenarios(); registerScenarios();
} }
void FGAIManager::registerScenarios() void FGAIManager::registerScenarios(SGPropertyNode_ptr root)
{ {
// depending on if we're using a carrier startup, this function may get if (!root) {
// called early or during normal FGAIManager init, so guard against double // depending on if we're using a carrier startup, this function may get
// invocation. // called early or during normal FGAIManager init, so guard against double
// we clear this flag on shudtdown so reset works as expected // invocation.
if (static_haveRegisteredScenarios) // we clear this flag on shudtdown so reset works as expected
return; if (static_haveRegisteredScenarios)
return;
static_haveRegisteredScenarios = true;
static_haveRegisteredScenarios = true;
root = globals->get_props();
}
// find all scenarios at standard locations (for driving the GUI) // find all scenarios at standard locations (for driving the GUI)
std::vector<SGPath> scenarioSearchPaths; std::vector<SGPath> scenarioSearchPaths;
@ -184,27 +187,29 @@ void FGAIManager::registerScenarios()
// add-on scenario directories // add-on scenario directories
const auto& addonsManager = flightgear::addons::AddonManager::instance(); const auto& addonsManager = flightgear::addons::AddonManager::instance();
for (auto a : addonsManager->registeredAddons()) { if (addonsManager) {
scenarioSearchPaths.push_back(a->getBasePath() / "Scenarios"); for (auto a : addonsManager->registeredAddons()) {
scenarioSearchPaths.push_back(a->getBasePath() / "Scenarios");
}
} }
SGPropertyNode_ptr scenariosNode = fgGetNode("/sim/ai/scenarios", true); SGPropertyNode_ptr scenariosNode = root->getNode("/sim/ai/scenarios", true);
for (auto p : scenarioSearchPaths) { for (auto p : scenarioSearchPaths) {
if (!p.exists()) if (!p.exists())
continue; continue;
simgear::Dir dir(p); simgear::Dir dir(p);
for (auto xmlPath : dir.children(simgear::Dir::TYPE_FILE, ".xml")) { for (auto xmlPath : dir.children(simgear::Dir::TYPE_FILE, ".xml")) {
registerScenarioFile(xmlPath); registerScenarioFile(root, xmlPath);
} // of xml files in the scenario dir iteration } // of xml files in the scenario dir iteration
} // of scenario dirs iteration } // of scenario dirs iteration
} }
SGPropertyNode_ptr FGAIManager::registerScenarioFile(const SGPath& xmlPath) SGPropertyNode_ptr FGAIManager::registerScenarioFile(SGPropertyNode_ptr root, const SGPath& xmlPath)
{ {
if (!xmlPath.exists()) return {}; if (!xmlPath.exists()) return {};
auto scenariosNode = fgGetNode("/sim/ai/scenarios", true); auto scenariosNode = root->getNode("/sim/ai/scenarios", true);
SGPropertyNode_ptr sNode; SGPropertyNode_ptr sNode;
try { try {
@ -235,7 +240,7 @@ SGPropertyNode_ptr FGAIManager::registerScenarioFile(const SGPath& xmlPath)
sNode->setStringValue("description", xs->getStringValue("description")); sNode->setStringValue("description", xs->getStringValue("description"));
} }
FGAICarrier::extractNamesPennantsFromScenario(xs, sNode); FGAICarrier::extractCarriersFromScenario(xs, sNode);
} // of scenarios in the XML file } // of scenarios in the XML file
} catch (std::exception&) { } catch (std::exception&) {
SG_LOG(SG_AI, SG_WARN, "Skipping malformed scenario file:" << xmlPath); SG_LOG(SG_AI, SG_WARN, "Skipping malformed scenario file:" << xmlPath);
@ -367,7 +372,7 @@ FGAIManager::update(double dt)
for (FGAIBase* base : ai_list) { for (FGAIBase* base : ai_list) {
try { try {
if (base->isa(FGAIBase::otThermal)) { if (base->isa(FGAIBase::otThermal)) {
processThermal(dt, (FGAIThermal*)base); processThermal(dt, static_cast<FGAIThermal*>(base));
} else { } else {
base->update(dt); base->update(dt);
} }
@ -428,7 +433,7 @@ bool FGAIManager::isVisible(const SGGeod& pos) const
int int
FGAIManager::getNumAiObjects() const FGAIManager::getNumAiObjects() const
{ {
return ai_list.size(); return static_cast<int>(ai_list.size());
} }
void void
@ -489,12 +494,14 @@ bool FGAIManager::loadScenarioCommand(const SGPropertyNode* args, SGPropertyNode
bool FGAIManager::unloadScenarioCommand(const SGPropertyNode * arg, SGPropertyNode * root) bool FGAIManager::unloadScenarioCommand(const SGPropertyNode * arg, SGPropertyNode * root)
{ {
SG_UNUSED(root);
std::string name = arg->getStringValue("name"); std::string name = arg->getStringValue("name");
return unloadScenario(name); return unloadScenario(name);
} }
bool FGAIManager::addObjectCommand(const SGPropertyNode* arg, const SGPropertyNode* root) bool FGAIManager::addObjectCommand(const SGPropertyNode* arg, const SGPropertyNode* root)
{ {
SG_UNUSED(root);
if (!arg){ if (!arg){
return false; return false;
} }
@ -506,7 +513,7 @@ FGAIBasePtr FGAIManager::addObject(const SGPropertyNode* definition)
{ {
const std::string& type = definition->getStringValue("type", "aircraft"); const std::string& type = definition->getStringValue("type", "aircraft");
FGAIBase* ai = NULL; FGAIBase* ai = nullptr;
if (type == "tanker") { // refueling scenarios if (type == "tanker") { // refueling scenarios
ai = new FGAITanker; ai = new FGAITanker;
} else if (type == "wingman") { } else if (type == "wingman") {
@ -545,6 +552,7 @@ FGAIBasePtr FGAIManager::addObject(const SGPropertyNode* definition)
bool FGAIManager::removeObjectCommand(const SGPropertyNode* arg, const SGPropertyNode* root) bool FGAIManager::removeObjectCommand(const SGPropertyNode* arg, const SGPropertyNode* root)
{ {
SG_UNUSED(root);
if (!arg) { if (!arg) {
return false; return false;
} }
@ -703,7 +711,7 @@ FGAIManager::calcCollision(double alt, double lat, double lon, double fuse_range
} }
++ai_list_itr; ++ai_list_itr;
} }
return 0; return nullptr;
} }
double double

View file

@ -73,8 +73,8 @@ public:
* we need carrier scenarios to start the position-init process for a * we need carrier scenarios to start the position-init process for a
* carrier start. * carrier start.
*/ */
static void registerScenarios(); static void registerScenarios(SGPropertyNode_ptr root = {});
static SGPropertyNode_ptr registerScenarioFile(const SGPath& p); static SGPropertyNode_ptr registerScenarioFile(SGPropertyNode_ptr root, const SGPath& p);
static SGPropertyNode_ptr loadScenarioFile(const std::string& id); static SGPropertyNode_ptr loadScenarioFile(const std::string& id);
FGAIBasePtr addObject(const SGPropertyNode* definition); FGAIBasePtr addObject(const SGPropertyNode* definition);

View file

@ -50,6 +50,12 @@ FGAIMultiplayer::FGAIMultiplayer() :
lastUpdateTime = 0; lastUpdateTime = 0;
playerLag = 0.03; playerLag = 0.03;
compensateLag = 1; compensateLag = 1;
realTime = false;
lastTime=0.0;
lagPpsAveraged = 1.0;
rawLag = 0.0;
rawLagMod = 0.0;
lagModAveraged = 0.0;
_searchOrder = PREFER_DATA; _searchOrder = PREFER_DATA;
} }
@ -152,73 +158,96 @@ void FGAIMultiplayer::update(double dt)
// requested time to the most recent available packet. This is the // requested time to the most recent available packet. This is the
// target we want to reach in average. // target we want to reach in average.
double lag = it->second.lag; double lag = it->second.lag;
rawLag = curentPkgTime - curtime;
realTime = false; //default behaviour
if (!mTimeOffsetSet) { if (!mTimeOffsetSet) {
mTimeOffsetSet = true; mTimeOffsetSet = true;
mTimeOffset = curentPkgTime - curtime - lag; mTimeOffset = curentPkgTime - curtime - lag;
lastTime = curentPkgTime;
lagModAveraged = remainder((curentPkgTime - curtime), 3600.0);
props->setDoubleValue("lag/pps-averaged", lagPpsAveraged);
props->setDoubleValue("lag/lag-mod-averaged", lagModAveraged);
} else { } else {
double offset = 0.0; if ((curentPkgTime - lastTime) != 0) {
lagPpsAveraged = 0.99 * lagPpsAveraged + 0.01 * fabs( 1 / (lastTime - curentPkgTime));
lastTime = curentPkgTime;
rawLagMod = remainder(rawLag, 3600.0);
lagModAveraged = lagModAveraged *0.99 + 0.01 * rawLagMod;
props->setDoubleValue("lag/pps-averaged", lagPpsAveraged);
props->setDoubleValue("lag/lag-mod-averaged", lagModAveraged);
}
//spectator mode, more late to be in the interpolation zone double offset = 0.0;
//spectator mode, more late to be in the interpolation zone
if (compensateLag == 3) { offset = curentPkgTime -curtime -lag + playerLag; if (compensateLag == 3) { offset = curentPkgTime -curtime -lag + playerLag;
// old behaviour // old behaviour
} else if (compensateLag == 1) { offset = curentPkgTime - curtime - lag; } else if (compensateLag == 1) { offset = curentPkgTime - curtime - lag;
// using the prediction mode to display the mpaircraft in the futur/past with given playerlag value // using the prediction mode to display the mpaircraft in the futur/past with given playerlag value
//currently compensatelag = 2 //currently compensatelag = 2
} else if (fabs(lagModAveraged) < 0.3) {
mTimeOffset = (round(rawLag/3600))*3600; //real time mode if close enough
realTime = true;
} else { offset = curentPkgTime - curtime + 0.48*lag + playerLag; } else { offset = curentPkgTime - curtime + 0.48*lag + playerLag;
} }
if ((!mAllowExtrapolation && offset + lag < mTimeOffset)
|| (offset - 10 > mTimeOffset)) {
mTimeOffset = offset;
SG_LOG(SG_AI, SG_DEBUG, "Resetting time offset adjust system to "
"avoid extrapolation: time offset = " << mTimeOffset);
} else {
// the error of the offset, respectively the negative error to avoid
// a minus later ...
double err = offset - mTimeOffset;
// limit errors leading to shorter lag values somehow, that is late
// arriving packets will pessimize the overall lag much more than
// early packets will shorten the overall lag
double sysSpeed;
//trying to slow the rudderlag phenomenon thus using more the prediction system if (!realTime) {
//if we are off by less than 1.5s, do a little correction, and bigger step above 1.5s
if (fabs(err) < 1.5) {
if (err < 0) {
sysSpeed = mLagAdjustSystemSpeed*err*0.01;
} else {
sysSpeed = SGMiscd::min(0.5*err*err, 0.05);
}
} else {
if (err < 0) {
// Ok, we have some very late packets and nothing newer increase the if ((!mAllowExtrapolation && offset + lag < mTimeOffset)
// lag by the given speedadjust || (offset - 10 > mTimeOffset)) {
sysSpeed = mLagAdjustSystemSpeed*err; mTimeOffset = offset;
} else { SG_LOG(SG_AI, SG_DEBUG, "Resetting time offset adjust system to "
// We have a too pessimistic display delay shorten that a small bit "avoid extrapolation: time offset = " << mTimeOffset);
sysSpeed = SGMiscd::min(0.1*err*err, 0.5); } else {
} // the error of the offset, respectively the negative error to avoid
} // a minus later ...
double err = offset - mTimeOffset;
// limit errors leading to shorter lag values somehow, that is late
// arriving packets will pessimize the overall lag much more than
// early packets will shorten the overall lag
double sysSpeed;
//trying to slow the rudderlag phenomenon thus using more the prediction system
//if we are off by less than 1.5s, do a little correction, and bigger step above 1.5s
if (fabs(err) < 1.5) {
if (err < 0) {
sysSpeed = mLagAdjustSystemSpeed*err*0.01;
} else {
sysSpeed = SGMiscd::min(0.5*err*err, 0.05);
}
} else {
if (err < 0) {
// Ok, we have some very late packets and nothing newer increase the
// lag by the given speedadjust
sysSpeed = mLagAdjustSystemSpeed*err;
} else {
// We have a too pessimistic display delay shorten that a small bit
sysSpeed = SGMiscd::min(0.1*err*err, 0.5);
}
}
// simple euler integration for that first order system including some // simple euler integration for that first order system including some
// overshooting guard to prevent to aggressive system speeds // overshooting guard to prevent to aggressive system speeds
// (stiff systems) to explode the systems state // (stiff systems) to explode the systems state
double systemIncrement = dt*sysSpeed; double systemIncrement = dt*sysSpeed;
if (fabs(err) < fabs(systemIncrement)) if (fabs(err) < fabs(systemIncrement))
systemIncrement = err; systemIncrement = err;
mTimeOffset += systemIncrement; mTimeOffset += systemIncrement;
SG_LOG(SG_AI, SG_DEBUG, "Offset adjust system: time offset = " SG_LOG(SG_AI, SG_DEBUG, "Offset adjust system: time offset = "
<< mTimeOffset << ", expected longitudinal position error due to " << mTimeOffset << ", expected longitudinal position error due to "
" current adjustment of the offset: " " current adjustment of the offset: "
<< fabs(norm(it->second.linearVel)*systemIncrement)); << fabs(norm(it->second.linearVel)*systemIncrement));
}
} }
} }
// Compute the time in the feeders time scale which fits the current time // Compute the time in the feeders time scale which fits the current time
// we need to // we need to
double tInterp = curtime + mTimeOffset; double tInterp = curtime + mTimeOffset;
@ -227,9 +256,10 @@ void FGAIMultiplayer::update(double dt)
SGQuatf ecOrient; SGQuatf ecOrient;
SGVec3f ecLinearVel; SGVec3f ecLinearVel;
if (tInterp <= curentPkgTime) { if (tInterp < curentPkgTime) {
// Ok, we need a time prevous to the last available packet, // Ok, we need a time prevous to the last available packet,
// that is good ... // that is good ...
// the case tInterp = curentPkgTime need to be in the interpolation, to avoid a bug zeroing the position
// Find the first packet before the target time // Find the first packet before the target time
MotionInfo::iterator nextIt = mMotionInfo.upper_bound(tInterp); MotionInfo::iterator nextIt = mMotionInfo.upper_bound(tInterp);

View file

@ -86,11 +86,15 @@ private:
typedef std::map<unsigned, SGSharedPtr<SGPropertyNode> > PropertyMap; typedef std::map<unsigned, SGSharedPtr<SGPropertyNode> > PropertyMap;
PropertyMap mPropertyMap; PropertyMap mPropertyMap;
double mTimeOffset;
bool mTimeOffsetSet; bool mTimeOffsetSet;
double playerLag; bool realTime;
int compensateLag; int compensateLag;
double playerLag;
double mTimeOffset;
double lastUpdateTime; double lastUpdateTime;
double lastTime;
double lagPpsAveraged;
double rawLag, rawLagMod, lagModAveraged;
/// Properties which are for now exposed for testing /// Properties which are for now exposed for testing
bool mAllowExtrapolation; bool mAllowExtrapolation;

View file

@ -287,6 +287,8 @@ void FGAIShip::Run(double dt) {
if (speed_diff < 0.0) if (speed_diff < 0.0)
speed -= _speed_constant * dt; speed -= _speed_constant * dt;
} else {
speed = tgt_speed;
} }
// do not allow unreasonable speeds // do not allow unreasonable speeds

View file

@ -162,6 +162,8 @@ void FGATCDialogNew::frequencyDisplay(const std::string& ident)
buf[7] = '\0'; buf[7] = '\0';
entry->setStringValue("text[1]/label", buf); entry->setStringValue("text[1]/label", buf);
entry->setStringValue("button[0]/binding/value", buf);
entry->setStringValue("button[1]/binding/value", buf);
} }
_gui->showDialog(dialog_name); _gui->showDialog(dialog_name);

View file

@ -139,12 +139,13 @@ FGReplay::clear()
void void
FGReplay::init() FGReplay::init()
{ {
disable_replay = fgGetNode("/sim/replay/disable", true); disable_replay = fgGetNode("/sim/replay/disable", true);
replay_master = fgGetNode("/sim/replay/replay-state", true); replay_master = fgGetNode("/sim/replay/replay-state", true);
replay_time = fgGetNode("/sim/replay/time", true); replay_time = fgGetNode("/sim/replay/time", true);
replay_time_str = fgGetNode("/sim/replay/time-str", true); replay_time_str = fgGetNode("/sim/replay/time-str", true);
replay_looped = fgGetNode("/sim/replay/looped", true); replay_looped = fgGetNode("/sim/replay/looped", true);
speed_up = fgGetNode("/sim/speed-up", true); replay_duration_act = fgGetNode("/sim/replay/duration-act", true);
speed_up = fgGetNode("/sim/speed-up", true);
// alias to keep backward compatibility // alias to keep backward compatibility
fgGetNode("/sim/freeze/replay-state", true)->alias(replay_master); fgGetNode("/sim/freeze/replay-state", true)->alias(replay_master);
@ -431,8 +432,8 @@ FGReplay::update( double dt )
fgSetDouble( "/sim/replay/start-time", startTime ); fgSetDouble( "/sim/replay/start-time", startTime );
fgSetDouble( "/sim/replay/end-time", endTime ); fgSetDouble( "/sim/replay/end-time", endTime );
double duration = 0; double duration = 0;
if (replay_looped->getBoolValue()) if (replay_duration_act->getBoolValue())
fgGetDouble("/sim/replay/duration"); duration = fgGetDouble("/sim/replay/duration");
if( duration && (duration < (endTime - startTime)) ) { if( duration && (duration < (endTime - startTime)) ) {
current_time = endTime - duration; current_time = endTime - duration;
} else { } else {
@ -822,7 +823,7 @@ FGReplay::saveTape(const SGPath& Filename, SGPropertyNode* MetaDataProps)
bool ok = true; bool ok = true;
/* open output stream *******************************************/ /* open output stream *******************************************/
gzContainerWriter output(Filename.local8BitStr(), FlightRecorderFileMagic); gzContainerWriter output(Filename, FlightRecorderFileMagic);
if (!output.good()) if (!output.good())
{ {
SG_LOG(SG_SYSTEMS, SG_ALERT, "Cannot open file" << Filename); SG_LOG(SG_SYSTEMS, SG_ALERT, "Cannot open file" << Filename);
@ -940,7 +941,7 @@ FGReplay::loadTape(const SGPath& Filename, bool Preview, SGPropertyNode* UserDat
bool ok = true; bool ok = true;
/* open input stream ********************************************/ /* open input stream ********************************************/
gzContainerReader input(Filename.local8BitStr(), FlightRecorderFileMagic); gzContainerReader input(Filename, FlightRecorderFileMagic);
if (input.eof() || !input.good()) if (input.eof() || !input.good())
{ {
SG_LOG(SG_SYSTEMS, SG_ALERT, "Cannot open file " << Filename); SG_LOG(SG_SYSTEMS, SG_ALERT, "Cannot open file " << Filename);

View file

@ -118,6 +118,7 @@ private:
SGPropertyNode_ptr replay_time; SGPropertyNode_ptr replay_time;
SGPropertyNode_ptr replay_time_str; SGPropertyNode_ptr replay_time_str;
SGPropertyNode_ptr replay_looped; SGPropertyNode_ptr replay_looped;
SGPropertyNode_ptr replay_duration_act;
SGPropertyNode_ptr speed_up; SGPropertyNode_ptr speed_up;
double m_high_res_time; // default: 60 secs of high res data double m_high_res_time; // default: 60 secs of high res data

View file

@ -148,15 +148,10 @@ void RunwayList::set(const std::string & tp, const std::string & lst)
// timeOffsetInDays = weekday - currTimeDate->getGmt()->tm_wday; // timeOffsetInDays = weekday - currTimeDate->getGmt()->tm_wday;
// timeCopy = timeCopy.substr(2,timeCopy.length()); // timeCopy = timeCopy.substr(2,timeCopy.length());
type = tp; type = tp;
for (const auto s : strutils::split(lst, ",")) {
auto ident = strutils::strip(s);
BOOST_FOREACH(std::string s, strutils::split(lst, ",")) {
std::string ident = strutils::strip(s);
// http://code.google.com/p/flightgear-bugs/issues/detail?id=1137 // http://code.google.com/p/flightgear-bugs/issues/detail?id=1137
if ((ident.size() < 2) || !isdigit(ident[1])) { if ((ident.size() < 2) || !isdigit(ident[1])) {
SG_LOG(SG_GENERAL, SG_INFO, "RunwayList::set: padding runway ident '" << ident << "'");
ident = "0" + ident; ident = "0" + ident;
} }

View file

@ -52,7 +52,7 @@ namespace canvas
"canvas::Text: using font file " << path "canvas::Text: using font file " << path
); );
simgear::canvas::FontPtr font = osgText::readFontFile(path.local8BitStr()); simgear::canvas::FontPtr font = osgText::readFontFile(path.utf8Str());
if( !font ) if( !font )
SG_LOG SG_LOG
( (
@ -86,9 +86,9 @@ namespace canvas
SGPath valid_path = fgValidatePath(p, false); SGPath valid_path = fgValidatePath(p, false);
if( !valid_path.isNull() ) if( !valid_path.isNull() )
#if OSG_VERSION_LESS_THAN(3,4,0) #if OSG_VERSION_LESS_THAN(3,4,0)
return osgDB::readRefImageFile(valid_path.local8BitStr()); return osgDB::readRefImageFile(valid_path.utf8Str());
#else #else
return osgDB::readRefImageFile(valid_path.local8BitStr()); return osgDB::readRefImageFile(valid_path.utf8Str());
#endif #endif
SG_LOG(SG_IO, SG_ALERT, "canvas::Image: reading '" << path << "' denied"); SG_LOG(SG_IO, SG_ALERT, "canvas::Image: reading '" << path << "' denied");
@ -98,9 +98,9 @@ namespace canvas
SGPath tpath = globals->resolve_resource_path(path); SGPath tpath = globals->resolve_resource_path(path);
if( !tpath.isNull() ) if( !tpath.isNull() )
#if OSG_VERSION_LESS_THAN(3,4,0) #if OSG_VERSION_LESS_THAN(3,4,0)
return osgDB::readImageFile(tpath.local8BitStr()); return osgDB::readImageFile(tpath.utf8Str());
#else #else
return osgDB::readRefImageFile(tpath.local8BitStr()); return osgDB::readRefImageFile(tpath.utf8Str());
#endif #endif
SG_LOG(SG_IO, SG_ALERT, "canvas::Image: No such image: '" << path << "'"); SG_LOG(SG_IO, SG_ALERT, "canvas::Image: No such image: '" << path << "'");
@ -127,7 +127,7 @@ namespace canvas
SG_LOG( SG_IO, SG_LOG( SG_IO,
SG_ALERT, SG_ALERT,
"FGCanvasSystemAdapter: Failed to get HTTP subsystem" ); "FGCanvasSystemAdapter: Failed to get HTTP subsystem" );
return 0; return nullptr;
} }
} }

View file

@ -71,21 +71,23 @@ CanvasMgr::CanvasMgr():
void CanvasMgr::init() void CanvasMgr::init()
{ {
_gui_camera = flightgear::getGUICamera(flightgear::CameraGroup::getDefault()); _gui_camera = flightgear::getGUICamera(flightgear::CameraGroup::getDefault());
assert(_gui_camera.valid()); if (_gui_camera.valid()) {
// add our two placement factories
sc::Canvas::addPlacementFactory sc::Canvas::addPlacementFactory
( (
"object", "object",
boost::bind boost::bind
( (
&FGODGauge::set_aircraft_texture, &FGODGauge::set_aircraft_texture,
_1, _1,
boost::bind(&sc::Canvas::getTexture, _2), boost::bind(&sc::Canvas::getTexture, _2),
boost::bind(&sc::Canvas::getCullCallback, _2), boost::bind(&sc::Canvas::getCullCallback, _2),
_2 _2
) )
); );
sc::Canvas::addPlacementFactory("scenery-object", &addSceneObjectPlacement);
sc::Canvas::addPlacementFactory("scenery-object", &addSceneObjectPlacement);
}
simgear::canvas::CanvasMgr::init(); simgear::canvas::CanvasMgr::init();
} }
@ -98,7 +100,7 @@ void CanvasMgr::shutdown()
sc::Canvas::removePlacementFactory("object"); sc::Canvas::removePlacementFactory("object");
sc::Canvas::removePlacementFactory("scenery-object"); sc::Canvas::removePlacementFactory("scenery-object");
_gui_camera = 0; _gui_camera = 0;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------

View file

@ -777,7 +777,7 @@ NavDisplay::updateFont()
} }
osg::ref_ptr<osgDB::ReaderWriter::Options> fontOptions = new osgDB::ReaderWriter::Options("monochrome"); osg::ref_ptr<osgDB::ReaderWriter::Options> fontOptions = new osgDB::ReaderWriter::Options("monochrome");
osg::ref_ptr<osgText::Font> font = osgText::readFontFile(tpath.local8BitStr(), fontOptions.get()); osg::ref_ptr<osgText::Font> font = osgText::readFontFile(tpath.utf8Str(), fontOptions.get());
if (font != 0) { if (font != 0) {
_font = font; _font = font;

View file

@ -47,7 +47,7 @@
#include <simgear/compiler.h> #include <simgear/compiler.h>
#include <plib/fnt.h> #include "fnt.h"
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <simgear/debug/logstream.hxx> #include <simgear/debug/logstream.hxx>

View file

@ -1084,7 +1084,7 @@ wxRadarBg::updateFont()
} }
osg::ref_ptr<osgDB::ReaderWriter::Options> fontOptions = new osgDB::ReaderWriter::Options("monochrome"); osg::ref_ptr<osgDB::ReaderWriter::Options> fontOptions = new osgDB::ReaderWriter::Options("monochrome");
osg::ref_ptr<osgText::Font> font = osgText::readFontFile(tpath.local8BitStr(), fontOptions.get()); osg::ref_ptr<osgText::Font> font = osgText::readFontFile(tpath.utf8Str(), fontOptions.get());
if (font != 0) { if (font != 0) {
_font = font; _font = font;

View file

@ -1,6 +1,17 @@
add_executable(fgrcc fgrcc.cxx fgrcc.hxx) add_executable(fgrcc fgrcc.cxx fgrcc.hxx)
target_link_libraries(fgrcc SimGearCore ${PLATFORM_LIBS}) target_link_libraries(fgrcc SimGearCore ${PLATFORM_LIBS})
# On Windows, make sure fgrcc can be run (it needs third-party libraries) in add_custom_target
if(MSVC)
set_target_properties(fgrcc PROPERTIES DEBUG_POSTFIX d)
if(MSVC_3RDPARTY_ROOT AND MSVC_3RDPARTY_DIR)
set(CMAKE_MSVCIDE_RUN_PATH ${MSVC_3RDPARTY_ROOT}/${MSVC_3RDPARTY_DIR}/bin)
else()
message(FATAL_ERROR
"Either MSVC_3RDPARTY_ROOT or MSVC_3RDPARTY_DIR is empty or unset")
endif()
endif()
add_custom_target( add_custom_target(
embeddedresources embeddedresources
COMMAND COMMAND

View file

@ -54,7 +54,7 @@ void Ephemeris::init()
{ {
SGPath ephem_data_path(globals->get_fg_root()); SGPath ephem_data_path(globals->get_fg_root());
ephem_data_path.append("Astro"); ephem_data_path.append("Astro");
_impl.reset(new SGEphemeris(ephem_data_path.local8BitStr())); _impl.reset(new SGEphemeris(ephem_data_path));
tieStar("/ephemeris/sun/xs", _impl->get_sun(), &Star::getxs); tieStar("/ephemeris/sun/xs", _impl->get_sun(), &Star::getxs);
tieStar("/ephemeris/sun/ys", _impl->get_sun(), &Star::getys); tieStar("/ephemeris/sun/ys", _impl->get_sun(), &Star::getys);

View file

@ -198,6 +198,7 @@ FGJSBsim::FGJSBsim( double dt )
PropertyManager = new FGPropertyManager( (FGPropertyNode*)globals->get_props() ); PropertyManager = new FGPropertyManager( (FGPropertyNode*)globals->get_props() );
fdmex = new FGFDMExec( PropertyManager ); fdmex = new FGFDMExec( PropertyManager );
fdmex->Hold();
// Register ground callback. // Register ground callback.
fdmex->SetGroundCallback( new FGFSGroundCallback(this) ); fdmex->SetGroundCallback( new FGFSGroundCallback(this) );
@ -383,6 +384,15 @@ FGJSBsim::FGJSBsim( double dt )
mesh = new AircraftMesh(fgGetDouble("/fdm/jsbsim/metrics/bw-ft"), mesh = new AircraftMesh(fgGetDouble("/fdm/jsbsim/metrics/bw-ft"),
fgGetDouble("/fdm/jsbsim/metrics/cbarw-ft")); fgGetDouble("/fdm/jsbsim/metrics/cbarw-ft"));
// Trim once to initialize all output parameters
FGTrim *fgtrim = new FGTrim(fdmex,tFull);
fgtrim->DoTrim();
delete fgtrim;
string directive_file = fgGetString("/sim/jsbsim/output-directive-file");
if (!directive_file.empty())
fdmex->SetOutputDirectives(directive_file);
} }
/******************************************************************************/ /******************************************************************************/
@ -427,6 +437,7 @@ void FGJSBsim::init()
common_init(); common_init();
copy_to_JSBsim(); copy_to_JSBsim();
fdmex->Resume();
fdmex->RunIC(); //loop JSBSim once w/o integrating fdmex->RunIC(); //loop JSBSim once w/o integrating
if (fgGetBool("/sim/presets/running")) { if (fgGetBool("/sim/presets/running")) {
Propulsion->InitRunning(-1); Propulsion->InitRunning(-1);

View file

@ -57,7 +57,7 @@ Element_ptr FGModelLoader::Open(Element *el)
if (!fname.empty()) { if (!fname.empty()) {
FGXMLFileRead XMLFileRead; FGXMLFileRead XMLFileRead;
SGPath path(SGPath::fromLocal8Bit(fname.c_str())); SGPath path(SGPath::fromUtf8(fname));
if (path.isRelative()) if (path.isRelative())
path = model->FindFullPathName(path); path = model->FindFullPathName(path);

View file

@ -236,10 +236,10 @@ void FGFCSComponent::Clip(void)
double range = vmax - vmin; double range = vmax - vmin;
if (range < 0.0) { if (range < 0.0) {
cerr << "Trying to clip with a max value " << ClipMax->GetName() cerr << "Trying to clip with a max value (" << vmax << ") from "
<< " lower than the min value " << ClipMin->GetName() << ClipMax->GetName() << " lower than the min value (" << vmin
<< endl; << ") from " << ClipMin->GetName() << "." << endl
throw("JSBSim aborts"); << "Clipping is ignored." << endl;
return; return;
} }

View file

@ -189,6 +189,11 @@ void FGUFO::update( double dt ) {
set_V_north(cos(heading) * velocity * SG_METER_TO_FEET); set_V_north(cos(heading) * velocity * SG_METER_TO_FEET);
set_V_east(sin(heading) * velocity * SG_METER_TO_FEET); set_V_east(sin(heading) * velocity * SG_METER_TO_FEET);
set_V_down(-real_climb_rate); set_V_down(-real_climb_rate);
//we assume the ufo only fly along the roll axis, providing a velocity to allow lag compensation
_set_Velocities_Body( velocity * SG_METER_TO_FEET,
0 ,
0 );
} }

View file

@ -18,26 +18,26 @@ public:
FGGround(FGInterface *iface); FGGround(FGInterface *iface);
virtual ~FGGround(); virtual ~FGGround();
virtual void getGroundPlane(const double pos[3], void getGroundPlane(const double pos[3],
double plane[4], float vel[3], double plane[4], float vel[3],
unsigned int &body); unsigned int &body) override;
virtual void getGroundPlane(const double pos[3], void getGroundPlane(const double pos[3],
double plane[4], float vel[3], double plane[4], float vel[3],
const simgear::BVHMaterial **material, const simgear::BVHMaterial **material,
unsigned int &body); unsigned int &body) override;
virtual bool getBody(double t, double bodyToWorld[16], double linearVel[3], bool getBody(double t, double bodyToWorld[16], double linearVel[3],
double angularVel[3], unsigned int &id); double angularVel[3], unsigned int &id) override;
virtual bool caughtWire(const double pos[4][3]); bool caughtWire(const double pos[4][3]) override;
virtual bool getWire(double end[2][3], float vel[2][3]); bool getWire(double end[2][3], float vel[2][3]) override;
virtual void releaseWire(void); void releaseWire(void) override;
virtual float getCatapult(const double pos[3], float getCatapult(const double pos[3],
double end[2][3], float vel[2][3]); double end[2][3], float vel[2][3]) override;
void setTimeOffset(double toff); void setTimeOffset(double toff);

View file

@ -8,14 +8,6 @@
#include "Ground.hpp" #include "Ground.hpp"
namespace yasim { namespace yasim {
Ground::Ground()
{
}
Ground::~Ground()
{
}
void Ground::getGroundPlane(const double pos[3], void Ground::getGroundPlane(const double pos[3],
double plane[4], float vel[3], double plane[4], float vel[3],
unsigned int &body) unsigned int &body)
@ -47,8 +39,7 @@ void Ground::getGroundPlane(const double pos[3],
bool Ground::getBody(double t, double bodyToWorld[16], double linearVel[3], bool Ground::getBody(double t, double bodyToWorld[16], double linearVel[3],
double angularVel[3], unsigned int &body) double angularVel[3], unsigned int &body)
{ {
return getBody(t, bodyToWorld, linearVel, return false;
angularVel, body);
} }
bool Ground::caughtWire(const double pos[4][3]) bool Ground::caughtWire(const double pos[4][3])

View file

@ -8,8 +8,7 @@ namespace yasim {
class Ground { class Ground {
public: public:
Ground(); virtual ~Ground() = default;
virtual ~Ground();
virtual void getGroundPlane(const double pos[3], virtual void getGroundPlane(const double pos[3],
double plane[4], float vel[3], double plane[4], float vel[3],
@ -20,8 +19,8 @@ public:
const simgear::BVHMaterial **material, const simgear::BVHMaterial **material,
unsigned int &body); unsigned int &body);
virtual bool getBody(double t, double bodyToWorld[16], double linearVel[3], virtual bool getBody(double t, double bodyToWorld[16], double linearVel[3],
double angularVel[3], unsigned int &id); double angularVel[3], unsigned int &id);
virtual bool caughtWire(const double pos[4][3]); virtual bool caughtWire(const double pos[4][3]);

View file

@ -190,6 +190,12 @@ void FDMShell::update(double dt)
fgSetBool("/sim/fdm-initialized", true); fgSetBool("/sim/fdm-initialized", true);
fgSetBool("/sim/signals/fdm-initialized", true); fgSetBool("/sim/signals/fdm-initialized", true);
if (!copyProperties(_props->getNode("fdm", true),
_initialFdmProperties))
{
SG_LOG(SG_FLIGHT, SG_ALERT, "Failed to save initial FDM property state");
}
} }
} }

View file

@ -22,12 +22,15 @@
#include "AddonsModel.hxx" #include "AddonsModel.hxx"
#include "InstallSceneryDialog.hxx" #include "InstallSceneryDialog.hxx"
#include "QtLauncher.hxx" #include "QtLauncher.hxx"
#include "PathListModel.hxx"
#include "LaunchConfig.hxx"
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
AddOnsController::AddOnsController(LauncherMainWindow *parent) : AddOnsController::AddOnsController(LauncherMainWindow *parent, LaunchConfig* config) :
QObject(parent), QObject(parent),
m_launcher(parent) m_launcher(parent),
m_config(config)
{ {
m_catalogs = new CatalogListModel(this, m_catalogs = new CatalogListModel(this,
simgear::pkg::RootRef(globals->packageRoot())); simgear::pkg::RootRef(globals->packageRoot()));
@ -38,10 +41,32 @@ AddOnsController::AddOnsController(LauncherMainWindow *parent) :
connect(m_addonsModuleModel, &AddonsModel::modulesChanged, this, &AddOnsController::onAddonsChanged); connect(m_addonsModuleModel, &AddonsModel::modulesChanged, this, &AddOnsController::onAddonsChanged);
using namespace flightgear::addons; using namespace flightgear::addons;
QSettings settings;
m_sceneryPaths = settings.value("scenery-paths").toStringList();
m_aircraftPaths = settings.value("aircraft-paths").toStringList();
m_sceneryPaths = new PathListModel(this);
connect(m_sceneryPaths, &PathListModel::enabledPathsChanged, [this] () {
m_sceneryPaths->saveToSettings("scenery-paths-v2");
flightgear::launcherSetSceneryPaths();
});
m_sceneryPaths->loadFromSettings("scenery-paths-v2");
m_aircraftPaths = new PathListModel(this);
m_aircraftPaths->loadFromSettings("aircraft-paths-v2");
// sync up the aircraft cache now
auto aircraftCache = LocalAircraftCache::instance();
aircraftCache->setPaths(m_aircraftPaths->enabledPaths());
aircraftCache->scanDirs();
// watch for future changes
connect(m_aircraftPaths, &PathListModel::enabledPathsChanged, [this] () {
m_aircraftPaths->saveToSettings("aircraft-paths-v2");
auto aircraftCache = LocalAircraftCache::instance();
aircraftCache->setPaths(m_aircraftPaths->enabledPaths());
aircraftCache->scanDirs();
});
QSettings settings;
int size = settings.beginReadArray("addon-modules"); int size = settings.beginReadArray("addon-modules");
for (int i = 0; i < size; ++i) { for (int i = 0; i < size; ++i) {
settings.setArrayIndex(i); settings.setArrayIndex(i);
@ -66,14 +91,18 @@ AddOnsController::AddOnsController(LauncherMainWindow *parent) :
qmlRegisterUncreatableType<AddOnsController>("FlightGear.Launcher", 1, 0, "AddOnsControllers", "no"); qmlRegisterUncreatableType<AddOnsController>("FlightGear.Launcher", 1, 0, "AddOnsControllers", "no");
qmlRegisterUncreatableType<CatalogListModel>("FlightGear.Launcher", 1, 0, "CatalogListModel", "no"); qmlRegisterUncreatableType<CatalogListModel>("FlightGear.Launcher", 1, 0, "CatalogListModel", "no");
qmlRegisterUncreatableType<AddonsModel>("FlightGear.Launcher", 1, 0, "AddonsModel", "no"); qmlRegisterUncreatableType<AddonsModel>("FlightGear.Launcher", 1, 0, "AddonsModel", "no");
qmlRegisterUncreatableType<PathListModel>("FlightGear.Launcher", 1, 0, "PathListMode", "no");
connect(m_config, &LaunchConfig::collect,
this, &AddOnsController::collectArgs);
} }
QStringList AddOnsController::aircraftPaths() const PathListModel* AddOnsController::aircraftPaths() const
{ {
return m_aircraftPaths; return m_aircraftPaths;
} }
QStringList AddOnsController::sceneryPaths() const PathListModel* AddOnsController::sceneryPaths() const
{ {
return m_sceneryPaths; return m_sceneryPaths;
} }
@ -119,6 +148,7 @@ QString AddOnsController::addAircraftPath() const
} }
} }
m_aircraftPaths->appendPath(path);
return path; return path;
} }
@ -206,6 +236,7 @@ QString AddOnsController::addSceneryPath() const
} }
} }
m_sceneryPaths->appendPath(path);
return path; return path;
} }
@ -227,37 +258,6 @@ void AddOnsController::openDirectory(QString path)
QDesktopServices::openUrl(u); QDesktopServices::openUrl(u);
} }
void AddOnsController::setAircraftPaths(QStringList aircraftPaths)
{
if (m_aircraftPaths == aircraftPaths)
return;
m_aircraftPaths = aircraftPaths;
emit aircraftPathsChanged(m_aircraftPaths);
QSettings settings;
settings.setValue("aircraft-paths", m_aircraftPaths);
auto aircraftCache = LocalAircraftCache::instance();
aircraftCache->setPaths(m_aircraftPaths);
aircraftCache->scanDirs();
}
void AddOnsController::setSceneryPaths(QStringList sceneryPaths)
{
if (m_sceneryPaths == sceneryPaths)
return;
m_sceneryPaths = sceneryPaths;
QSettings settings;
settings.setValue("scenery-paths", m_sceneryPaths);
flightgear::launcherSetSceneryPaths();
emit sceneryPathsChanged(m_sceneryPaths);
}
void AddOnsController::setAddons(AddonsModel* addons) void AddOnsController::setAddons(AddonsModel* addons)
{ {
@ -342,3 +342,31 @@ void AddOnsController::onAddonsChanged()
} }
settings.endArray(); settings.endArray();
} }
void AddOnsController::collectArgs()
{
// scenery paths
Q_FOREACH(QString path, m_sceneryPaths->enabledPaths()) {
m_config->setArg("fg-scenery", path);
}
// aircraft paths
Q_FOREACH(QString path, m_aircraftPaths->enabledPaths()) {
m_config->setArg("fg-aircraft", path);
}
// add-on module paths
// we could query this directly from AddonsModel, but this is simpler right now
QSettings settings;
int size = settings.beginReadArray("addon-modules");
for (int i = 0; i < size; ++i) {
settings.setArrayIndex(i);
QString path = settings.value("path").toString();
bool enable = settings.value("enable").toBool();
if (enable) {
m_config->setArg("addon", path);
}
}
settings.endArray();
}

View file

@ -7,13 +7,15 @@
class CatalogListModel; class CatalogListModel;
class AddonsModel; class AddonsModel;
class LauncherMainWindow; class LauncherMainWindow;
class PathListModel;
class LaunchConfig;
class AddOnsController : public QObject class AddOnsController : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QStringList aircraftPaths READ aircraftPaths WRITE setAircraftPaths NOTIFY aircraftPathsChanged) Q_PROPERTY(PathListModel* aircraftPaths READ aircraftPaths CONSTANT)
Q_PROPERTY(QStringList sceneryPaths READ sceneryPaths WRITE setSceneryPaths NOTIFY sceneryPathsChanged) Q_PROPERTY(PathListModel* sceneryPaths READ sceneryPaths CONSTANT)
Q_PROPERTY(QStringList modulePaths READ modulePaths WRITE setModulePaths NOTIFY modulePathsChanged) Q_PROPERTY(QStringList modulePaths READ modulePaths WRITE setModulePaths NOTIFY modulePathsChanged)
Q_PROPERTY(CatalogListModel* catalogs READ catalogs CONSTANT) Q_PROPERTY(CatalogListModel* catalogs READ catalogs CONSTANT)
@ -22,10 +24,10 @@ class AddOnsController : public QObject
Q_PROPERTY(bool showNoOfficialHangar READ showNoOfficialHangar NOTIFY showNoOfficialHangarChanged) Q_PROPERTY(bool showNoOfficialHangar READ showNoOfficialHangar NOTIFY showNoOfficialHangarChanged)
public: public:
explicit AddOnsController(LauncherMainWindow *parent = nullptr); explicit AddOnsController(LauncherMainWindow *parent, LaunchConfig* config);
QStringList aircraftPaths() const; PathListModel* aircraftPaths() const;
QStringList sceneryPaths() const; PathListModel* sceneryPaths() const;
QStringList modulePaths() const; QStringList modulePaths() const;
CatalogListModel* catalogs() const CatalogListModel* catalogs() const
@ -50,8 +52,6 @@ public:
Q_INVOKABLE void officialCatalogAction(QString s); Q_INVOKABLE void officialCatalogAction(QString s);
signals: signals:
void aircraftPathsChanged(QStringList aircraftPaths);
void sceneryPathsChanged(QStringList sceneryPaths);
void modulePathsChanged(QStringList modulePaths); void modulePathsChanged(QStringList modulePaths);
void modulesChanged(); void modulesChanged();
@ -59,12 +59,12 @@ signals:
void showNoOfficialHangarChanged(); void showNoOfficialHangarChanged();
public slots: public slots:
void setAircraftPaths(QStringList aircraftPaths);
void setSceneryPaths(QStringList sceneryPaths);
void setModulePaths(QStringList modulePaths); void setModulePaths(QStringList modulePaths);
void setAddons(AddonsModel* addons); void setAddons(AddonsModel* addons);
void onAddonsChanged(void); void onAddonsChanged(void);
void collectArgs();
private: private:
bool shouldShowOfficialCatalogMessage() const; bool shouldShowOfficialCatalogMessage() const;
void onCatalogsChanged(); void onCatalogsChanged();
@ -72,9 +72,10 @@ private:
LauncherMainWindow* m_launcher; LauncherMainWindow* m_launcher;
CatalogListModel* m_catalogs = nullptr; CatalogListModel* m_catalogs = nullptr;
AddonsModel* m_addonsModuleModel = nullptr; AddonsModel* m_addonsModuleModel = nullptr;
LaunchConfig* m_config = nullptr;
QStringList m_aircraftPaths; PathListModel* m_aircraftPaths = nullptr;
QStringList m_sceneryPaths; PathListModel* m_sceneryPaths = nullptr;
QStringList m_addonModulePaths; QStringList m_addonModulePaths;
}; };

View file

@ -37,7 +37,7 @@ AddonsModel::AddonsModel(QObject* pr) :
int roleValue = IdRole; int roleValue = IdRole;
for (auto it = m_roles.begin(); it != m_roles.end(); ++it) { for (auto it = m_roles.begin(); it != m_roles.end(); ++it) {
QByteArray name = (*it).toLocal8Bit(); QByteArray name = it->toUtf8();
m_roleToName[roleValue] = name; m_roleToName[roleValue] = name;
m_nameToRole[*it] = roleValue++; m_nameToRole[*it] = roleValue++;
} }

View file

@ -23,6 +23,7 @@
#include <QSettings> #include <QSettings>
#include <QDebug> #include <QDebug>
#include <QSharedPointer> #include <QSharedPointer>
#include <QSettings>
// Simgear // Simgear
#include <simgear/props/props_io.hxx> #include <simgear/props/props_io.hxx>
@ -38,10 +39,21 @@
#include "QmlAircraftInfo.hxx" #include "QmlAircraftInfo.hxx"
const int STANDARD_THUMBNAIL_HEIGHT = 128;
using namespace simgear::pkg; using namespace simgear::pkg;
bool isPackageFailure(Delegate::StatusCode status)
{
switch (status) {
case Delegate::STATUS_SUCCESS:
case Delegate::STATUS_REFRESHED:
case Delegate::STATUS_IN_PROGRESS:
return false;
default:
return true;
}
}
class PackageDelegate : public simgear::pkg::Delegate class PackageDelegate : public simgear::pkg::Delegate
{ {
public: public:
@ -57,18 +69,7 @@ public:
} }
protected: protected:
void catalogRefreshed(CatalogRef aCatalog, StatusCode aReason) override void catalogRefreshed(CatalogRef aCatalog, StatusCode aReason) override;
{
if (aReason == STATUS_IN_PROGRESS) {
// nothing to do
} else if ((aReason == STATUS_REFRESHED) || (aReason == STATUS_SUCCESS)) {
m_model->refreshPackages();
} else {
qWarning() << "failed refresh of"
<< QString::fromStdString(aCatalog->url()) << ":" << aReason << endl;
}
}
void startInstall(InstallRef aInstall) override void startInstall(InstallRef aInstall) override
{ {
QModelIndex mi(indexForPackage(aInstall->package())); QModelIndex mi(indexForPackage(aInstall->package()));
@ -77,8 +78,8 @@ protected:
void installProgress(InstallRef aInstall, unsigned int bytes, unsigned int total) override void installProgress(InstallRef aInstall, unsigned int bytes, unsigned int total) override
{ {
Q_UNUSED(bytes); Q_UNUSED(bytes)
Q_UNUSED(total); Q_UNUSED(total)
QModelIndex mi(indexForPackage(aInstall->package())); QModelIndex mi(indexForPackage(aInstall->package()));
m_model->dataChanged(mi, mi); m_model->dataChanged(mi, mi);
} }
@ -104,7 +105,7 @@ protected:
void installStatusChanged(InstallRef aInstall, StatusCode aReason) override void installStatusChanged(InstallRef aInstall, StatusCode aReason) override
{ {
Q_UNUSED(aReason); Q_UNUSED(aReason)
QModelIndex mi(indexForPackage(aInstall->package())); QModelIndex mi(indexForPackage(aInstall->package()));
m_model->dataChanged(mi, mi); m_model->dataChanged(mi, mi);
} }
@ -115,44 +116,6 @@ protected:
m_model->dataChanged(mi, mi); m_model->dataChanged(mi, mi);
} }
virtual void dataForThumbnail(const std::string& aThumbnailUrl,
size_t length, const uint8_t* bytes) override
{
QImage img = QImage::fromData(QByteArray::fromRawData(reinterpret_cast<const char*>(bytes), length));
if (img.isNull()) {
qWarning() << "failed to load image data for URL:" <<
QString::fromStdString(aThumbnailUrl);
return;
}
QPixmap pix = QPixmap::fromImage(img);
if (pix.height() > STANDARD_THUMBNAIL_HEIGHT) {
pix = pix.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT, Qt::SmoothTransformation);
}
QString url = QString::fromStdString(aThumbnailUrl);
m_model->m_downloadedPixmapCache.insert(url, pix);
// notify any affected items. Linear scan here avoids another map/dict structure.
for (auto pkg : m_model->m_packages) {
const size_t variantCount = pkg->variants().size();
bool notifyChanged = false;
for (size_t v=0; v < variantCount; ++v) {
const Package::Thumbnail& thumb(pkg->thumbnailForVariant(v));
if (thumb.url == aThumbnailUrl) {
notifyChanged = true;
}
}
if (notifyChanged) {
QModelIndex mi = indexForPackage(pkg);
m_model->dataChanged(mi, mi);
}
} // of packages iteration
}
private: private:
QModelIndex indexForPackage(const PackageRef& ref) const QModelIndex indexForPackage(const PackageRef& ref) const
{ {
@ -169,6 +132,22 @@ private:
AircraftItemModel* m_model; AircraftItemModel* m_model;
}; };
void PackageDelegate::catalogRefreshed(CatalogRef aCatalog, StatusCode aReason)
{
if (aReason == STATUS_IN_PROGRESS) {
// nothing to do
} else if ((aReason == STATUS_REFRESHED) || (aReason == STATUS_SUCCESS)) {
m_model->refreshPackages();
} else {
qWarning() << "failed refresh of"
<< QString::fromStdString(aCatalog->url()) << ":" << aReason << endl;
}
}
//////////////////////////////////////////////////////////////////////////
/// \brief AircraftItemModel::AircraftItemModel
/// \param pr
///
AircraftItemModel::AircraftItemModel(QObject* pr) : AircraftItemModel::AircraftItemModel(QObject* pr) :
QAbstractListModel(pr) QAbstractListModel(pr)
{ {
@ -179,6 +158,8 @@ AircraftItemModel::AircraftItemModel(QObject* pr) :
this, &AircraftItemModel::onScanAddedItems); this, &AircraftItemModel::onScanAddedItems);
connect(cache, &LocalAircraftCache::cleared, connect(cache, &LocalAircraftCache::cleared,
this, &AircraftItemModel::onLocalCacheCleared); this, &AircraftItemModel::onLocalCacheCleared);
loadFavourites();
} }
AircraftItemModel::~AircraftItemModel() AircraftItemModel::~AircraftItemModel()
@ -260,8 +241,14 @@ QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
return m_delegateStates.at(row).variant; return m_delegateStates.at(row).variant;
} }
if (role == AircraftIsFavouriteRole) {
// recursive call here, hope that's okay
const auto uri = data(index, AircraftURIRole).toUrl();
return m_favourites.contains(uri);
}
if (row >= m_cachedLocalAircraftCount) { if (row >= m_cachedLocalAircraftCount) {
quint32 packageIndex = row - m_cachedLocalAircraftCount; quint32 packageIndex = static_cast<quint32>(row - m_cachedLocalAircraftCount);
const PackageRef& pkg(m_packages[packageIndex]); const PackageRef& pkg(m_packages[packageIndex]);
InstallRef ex = pkg->existingInstall(); InstallRef ex = pkg->existingInstall();
@ -309,16 +296,12 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateSta
} }
return item->description; return item->description;
} else if (role == Qt::DecorationRole) {
return item->thumbnail();
} else if (role == AircraftPathRole) { } else if (role == AircraftPathRole) {
return item->path; return item->path;
} else if (role == AircraftAuthorsRole) { } else if (role == AircraftAuthorsRole) {
return item->authors; return item->authors;
} else if ((role >= AircraftRatingRole) && (role < AircraftVariantDescriptionRole)) { } else if ((role >= AircraftRatingRole) && (role < AircraftVariantDescriptionRole)) {
return item->ratings[role - AircraftRatingRole]; return item->ratings[role - AircraftRatingRole];
} else if (role == AircraftThumbnailRole) {
return item->thumbnail();
} else if (role == AircraftPackageIdRole) { } else if (role == AircraftPackageIdRole) {
// can we fake an ID? otherwise fall through to a null variant // can we fake an ID? otherwise fall through to a null variant
} else if (role == AircraftPackageStatusRole) { } else if (role == AircraftPackageStatusRole) {
@ -354,10 +337,6 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateSta
QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, const DelegateState& state, int role) const QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, const DelegateState& state, int role) const
{ {
if (role == Qt::DecorationRole) {
role = AircraftThumbnailRole;
}
if (role >= AircraftVariantDescriptionRole) { if (role >= AircraftVariantDescriptionRole) {
int variantIndex = role - AircraftVariantDescriptionRole; int variantIndex = role - AircraftVariantDescriptionRole;
QString desc = QString::fromStdString(item->nameForVariant(variantIndex)); QString desc = QString::fromStdString(item->nameForVariant(variantIndex));
@ -393,6 +372,10 @@ QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, const Delega
return LocalAircraftCache::PackageUpdateAvailable; return LocalAircraftCache::PackageUpdateAvailable;
} }
const auto status = i->status();
if (isPackageFailure(status))
return LocalAircraftCache::PackageInstallFailed;
return LocalAircraftCache::PackageInstalled; return LocalAircraftCache::PackageInstalled;
} else { } else {
return LocalAircraftCache::PackageNotInstalled; return LocalAircraftCache::PackageNotInstalled;
@ -401,8 +384,6 @@ QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, const Delega
// this value wants the number of aditional variants, i.e not // this value wants the number of aditional variants, i.e not
// including the primary. Hence the -1 term. // including the primary. Hence the -1 term.
return static_cast<quint32>(item->variants().size() - 1); return static_cast<quint32>(item->variants().size() - 1);
} else if (role == AircraftThumbnailRole) {
return packageThumbnail(item, state);
} else if (role == AircraftAuthorsRole) { } else if (role == AircraftAuthorsRole) {
std::string authors = item->getLocalisedProp("author", state.variant); std::string authors = item->getLocalisedProp("author", state.variant);
if (!authors.empty()) { if (!authors.empty()) {
@ -436,42 +417,6 @@ QVariant AircraftItemModel::packageRating(const PackageRef& p, int ratingIndex)
return LocalAircraftCache::ratingFromProperties(p->properties()->getChild("rating"), ratingIndex); return LocalAircraftCache::ratingFromProperties(p->properties()->getChild("rating"), ratingIndex);
} }
QVariant AircraftItemModel::packageThumbnail(PackageRef p, const DelegateState& ds, bool download) const
{
const Package::Thumbnail& thumb(p->thumbnailForVariant(ds.variant));
if (thumb.url.empty()) {
return QVariant();
}
QString urlQString(QString::fromStdString(thumb.url));
if (m_downloadedPixmapCache.contains(urlQString)) {
// cache hit, easy
return m_downloadedPixmapCache.value(urlQString);
}
// check the on-disk store.
InstallRef ex = p->existingInstall();
if (ex.valid()) {
SGPath thumbPath = ex->path() / thumb.path;
if (thumbPath.exists()) {
QPixmap pix;
pix.load(QString::fromStdString(thumbPath.utf8Str()));
// resize to the standard size
if (pix.height() > STANDARD_THUMBNAIL_HEIGHT) {
pix = pix.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT);
}
m_downloadedPixmapCache[urlQString] = pix;
return pix;
}
} // of have existing install
if (download) {
m_packageRoot->requestThumbnailData(thumb.url);
}
return QVariant();
}
bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value, int role) bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value, int role)
{ {
int row = index.row(); int row = index.row();
@ -485,6 +430,18 @@ bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value,
m_delegateStates[row].variant = newValue; m_delegateStates[row].variant = newValue;
emit dataChanged(index, index); emit dataChanged(index, index);
return true; return true;
} else if (role == AircraftIsFavouriteRole) {
bool f = value.toBool();
const auto uri = data(index, AircraftURIRole).toUrl();
const auto cur = m_favourites.contains(uri);
if (f && !cur) {
m_favourites.append(uri);
} else if (!f && cur) {
m_favourites.removeOne(uri);
}
saveFavourites();
emit dataChanged(index, index);
} }
return false; return false;
@ -500,12 +457,12 @@ QHash<int, QByteArray> AircraftItemModel::roleNames() const
result[AircraftAuthorsRole] = "authors"; result[AircraftAuthorsRole] = "authors";
result[AircraftVariantCountRole] = "variantCount"; result[AircraftVariantCountRole] = "variantCount";
result[AircraftLongDescriptionRole] = "description"; result[AircraftLongDescriptionRole] = "description";
result[AircraftThumbnailRole] = "thumbnail";
result[AircraftPackageSizeRole] = "packageSizeBytes"; result[AircraftPackageSizeRole] = "packageSizeBytes";
result[AircraftPackageStatusRole] = "packageStatus"; result[AircraftPackageStatusRole] = "packageStatus";
result[AircraftInstallDownloadedSizeRole] = "downloadedBytes"; result[AircraftInstallDownloadedSizeRole] = "downloadedBytes";
result[AircraftVariantRole] = "activeVariant"; result[AircraftVariantRole] = "activeVariant";
result[AircraftIsFavouriteRole] = "favourite";
result[AircraftStatusRole] = "aircraftStatus"; result[AircraftStatusRole] = "aircraftStatus";
result[AircraftMinVersionRole] = "requiredFGVersion"; result[AircraftMinVersionRole] = "requiredFGVersion";
@ -625,7 +582,7 @@ QString AircraftItemModel::nameForAircraftURI(QUrl uri) const
QString ident = uri.path(); QString ident = uri.path();
PackageRef pkg = m_packageRoot->getPackageById(ident.toStdString()); PackageRef pkg = m_packageRoot->getPackageById(ident.toStdString());
if (pkg) { if (pkg) {
int variantIndex = pkg->indexOfVariant(ident.toStdString()); const auto variantIndex = pkg->indexOfVariant(ident.toStdString());
return QString::fromStdString(pkg->nameForVariant(variantIndex)); return QString::fromStdString(pkg->nameForVariant(variantIndex));
} }
} else { } else {
@ -637,7 +594,7 @@ QString AircraftItemModel::nameForAircraftURI(QUrl uri) const
void AircraftItemModel::onScanAddedItems(int addedCount) void AircraftItemModel::onScanAddedItems(int addedCount)
{ {
Q_UNUSED(addedCount); Q_UNUSED(addedCount)
const auto items = LocalAircraftCache::instance()->allItems(); const auto items = LocalAircraftCache::instance()->allItems();
const int newItemCount = items.size() - m_cachedLocalAircraftCount; const int newItemCount = items.size() - m_cachedLocalAircraftCount;
const int firstRow = m_cachedLocalAircraftCount; const int firstRow = m_cachedLocalAircraftCount;
@ -696,7 +653,7 @@ bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const
return true; // local file, always runnable return true; // local file, always runnable
} }
quint32 packageIndex = index.row() - m_cachedLocalAircraftCount; quint32 packageIndex = static_cast<quint32>(index.row() - m_cachedLocalAircraftCount);
const PackageRef& pkg(m_packages[packageIndex]); const PackageRef& pkg(m_packages[packageIndex]);
InstallRef ex = pkg->existingInstall(); InstallRef ex = pkg->existingInstall();
if (!ex.valid()) { if (!ex.valid()) {
@ -706,4 +663,21 @@ bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const
return !ex->isDownloading(); return !ex->isDownloading();
} }
void AircraftItemModel::loadFavourites()
{
m_favourites.clear();
QSettings settings;
Q_FOREACH(auto v, settings.value("favourite-aircraft").toList()) {
m_favourites.append(v.toUrl());
}
}
void AircraftItemModel::saveFavourites()
{
QVariantList favs;
Q_FOREACH(auto u, m_favourites) {
favs.append(u);
}
QSettings settings;
settings.setValue("favourite-aircraft", favs);
}

View file

@ -50,7 +50,7 @@ const int AircraftURIRole = Qt::UserRole + 14;
const int AircraftIsHelicopterRole = Qt::UserRole + 16; const int AircraftIsHelicopterRole = Qt::UserRole + 16;
const int AircraftIsSeaplaneRole = Qt::UserRole + 17; const int AircraftIsSeaplaneRole = Qt::UserRole + 17;
const int AircraftPackageRefRole = Qt::UserRole + 19; const int AircraftPackageRefRole = Qt::UserRole + 19;
const int AircraftThumbnailRole = Qt::UserRole + 20; const int AircraftIsFavouriteRole = Qt::UserRole + 20;
const int AircraftStatusRole = Qt::UserRole + 22; const int AircraftStatusRole = Qt::UserRole + 22;
const int AircraftMinVersionRole = Qt::UserRole + 23; const int AircraftMinVersionRole = Qt::UserRole + 23;
@ -146,15 +146,20 @@ private:
void installSucceeded(QModelIndex index); void installSucceeded(QModelIndex index);
void installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason); void installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason);
void loadFavourites();
void saveFavourites();
private:
PackageDelegate* m_delegate = nullptr; PackageDelegate* m_delegate = nullptr;
QVector<DelegateState> m_delegateStates; QVector<DelegateState> m_delegateStates;
simgear::pkg::RootRef m_packageRoot; simgear::pkg::RootRef m_packageRoot;
simgear::pkg::PackageList m_packages; simgear::pkg::PackageList m_packages;
mutable QHash<QString, QPixmap> m_downloadedPixmapCache; QVector<QUrl> m_favourites;
int m_cachedLocalAircraftCount = 0; int m_cachedLocalAircraftCount = 0;
}; };
#endif // of FG_GUI_AIRCRAFT_MODEL #endif // of FG_GUI_AIRCRAFT_MODEL

View file

@ -1,5 +1,8 @@
#include "AircraftSearchFilterModel.hxx" #include "AircraftSearchFilterModel.hxx"
#include <QSettings>
#include <QDebug>
#include "AircraftModel.hxx" #include "AircraftModel.hxx"
#include <simgear/package/Package.hxx> #include <simgear/package/Package.hxx>
@ -11,6 +14,8 @@ AircraftProxyModel::AircraftProxyModel(QObject *pr, QAbstractItemModel * source)
setSortCaseSensitivity(Qt::CaseInsensitive); setSortCaseSensitivity(Qt::CaseInsensitive);
setFilterCaseSensitivity(Qt::CaseInsensitive); setFilterCaseSensitivity(Qt::CaseInsensitive);
// important we sort on the primary name role and not Qt::DisplayRole // important we sort on the primary name role and not Qt::DisplayRole
// otherwise the aircraft jump when switching variant // otherwise the aircraft jump when switching variant
setSortRole(AircraftVariantDescriptionRole); setSortRole(AircraftVariantDescriptionRole);
@ -110,6 +115,15 @@ void AircraftProxyModel::setHaveUpdateFilterEnabled(bool e)
invalidate(); invalidate();
} }
void AircraftProxyModel::setShowFavourites(bool e)
{
if (e == m_onlyShowFavourites)
return;
m_onlyShowFavourites = e;
invalidate();
}
bool AircraftProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const bool AircraftProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{ {
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
@ -142,6 +156,11 @@ bool AircraftProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sour
} }
} }
if (m_onlyShowFavourites) {
if (!index.data(AircraftIsFavouriteRole).toBool())
return false;
}
// if there is no search active, i.e we are browsing, we might apply the // if there is no search active, i.e we are browsing, we might apply the
// ratings filter. // ratings filter.
if (m_filterString.isEmpty() && !m_onlyShowInstalled && m_ratingsFilter) { if (m_filterString.isEmpty() && !m_onlyShowInstalled && m_ratingsFilter) {
@ -187,3 +206,28 @@ bool AircraftProxyModel::filterAircraft(const QModelIndex &sourceIndex) const
return false; return false;
} }
void AircraftProxyModel::loadRatingsSettings()
{
QSettings settings;
m_ratingsFilter = settings.value("enable-ratings-filter", true).toBool();
QVariantList vRatings = settings.value("ratings-filter").toList();
if (vRatings.size() == 4) {
for (int i=0; i < 4; ++i) {
m_ratings[i] = vRatings.at(i).toInt();
}
}
invalidate();
}
void AircraftProxyModel::saveRatingsSettings()
{
QSettings settings;
settings.setValue("enable-ratings-filter", m_ratingsFilter);
QVariantList vRatings;
for (int i=0; i < 4; ++i) {
vRatings.append(m_ratings.at(i));
}
settings.setValue("ratings-filter", vRatings);
}

View file

@ -29,6 +29,10 @@ public:
Q_INVOKABLE void selectVariantForAircraftURI(QUrl uri); Q_INVOKABLE void selectVariantForAircraftURI(QUrl uri);
Q_INVOKABLE void loadRatingsSettings();
Q_INVOKABLE void saveRatingsSettings();
QList<int> ratings() const QList<int> ratings() const
{ {
return m_ratings; return m_ratings;
@ -56,6 +60,8 @@ public slots:
void setInstalledFilterEnabled(bool e); void setInstalledFilterEnabled(bool e);
void setHaveUpdateFilterEnabled(bool e); void setHaveUpdateFilterEnabled(bool e);
void setShowFavourites(bool e);
protected: protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
@ -65,6 +71,7 @@ private:
bool m_ratingsFilter = true; bool m_ratingsFilter = true;
bool m_onlyShowInstalled = false; bool m_onlyShowInstalled = false;
bool m_onlyShowWithUpdate = false; bool m_onlyShowWithUpdate = false;
bool m_onlyShowFavourites = false;
QList<int> m_ratings; QList<int> m_ratings;
QString m_filterString; QString m_filterString;

View file

@ -124,6 +124,10 @@ if (HAVE_QT)
AddonsModel.hxx AddonsModel.hxx
PixmapImageItem.cxx PixmapImageItem.cxx
PixmapImageItem.hxx PixmapImageItem.hxx
PathListModel.cxx
PathListModel.hxx
CarriersLocationModel.cxx
CarriersLocationModel.hxx
${uic_sources} ${uic_sources}
${qrc_sources} ${qrc_sources}
${qml_sources}) ${qml_sources})
@ -168,6 +172,8 @@ if (HAVE_QT)
RouteDiagram.hxx RouteDiagram.hxx
ModelDataExtractor.cxx ModelDataExtractor.cxx
ModelDataExtractor.hxx ModelDataExtractor.hxx
HoverArea.cxx
HoverArea.hxx
) )
set_property(TARGET fgqmlui PROPERTY AUTOMOC ON) set_property(TARGET fgqmlui PROPERTY AUTOMOC ON)

View file

@ -0,0 +1,132 @@
#include "CarriersLocationModel.hxx"
#include <algorithm>
#include <QPixmap>
#include "AIModel/AIManager.hxx"
CarriersLocationModel::CarriersLocationModel(QObject *parent)
: QAbstractListModel(parent)
{
SGPropertyNode_ptr localRoot(new SGPropertyNode);
FGAIManager::registerScenarios(localRoot);
// this code encodes some scenario structre, sorry
for (auto s : localRoot->getNode("sim/ai/scenarios")->getChildren("scenario")) {
const std::string scenarioId = s->getStringValue("id");
for (auto c : s->getChildren("carrier")) {
processCarrier(scenarioId, c);
}
}
}
void CarriersLocationModel::processCarrier(const string &scenario, SGPropertyNode_ptr carrierNode)
{
const auto name = QString::fromStdString(carrierNode->getStringValue("name"));
const auto pennant = QString::fromStdString(carrierNode->getStringValue("pennant-number"));
const auto tacan = QString::fromStdString(carrierNode->getStringValue("TACAN-channel-ID"));
SGGeod geod = SGGeod::fromDeg(carrierNode->getDoubleValue("longitude"),
carrierNode->getDoubleValue("latitude"));
QStringList parkings;
for (auto c : carrierNode->getChildren("parking-pos")) {
parkings.append(QString::fromStdString(c->getStringValue()));
}
mCarriers.push_back(Carrier{
QString::fromStdString(scenario),
pennant,
name,
geod,
tacan,
parkings
});
}
int CarriersLocationModel::rowCount(const QModelIndex &parent) const
{
// For list models only the root node (an invalid parent) should return the list's size. For all
// other (valid) parents, rowCount() should return 0 so that it does not become a tree model.
if (parent.isValid())
return 0;
return static_cast<int>(mCarriers.size());
}
QVariant CarriersLocationModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
const auto& c = mCarriers.at(static_cast<size_t>(index.row()));
switch (role) {
case Qt::DisplayRole:
case NameRole: return c.mName;
// case GeodRole: return QVariant::fromValue(c.mInitialLocation);
case IdentRole: return c.mCallsign;
case IconRole: return QPixmap(":/svg/aircraft-carrier");
default:
break;
}
return {};
}
QHash<int, QByteArray> CarriersLocationModel::roleNames() const
{
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
result[GeodRole] = "geod";
result[GuidRole] = "guid";
result[IdentRole] = "ident";
result[NameRole] = "name";
result[IconRole] = "icon";
result[TypeRole] = "type";
result[NavFrequencyRole] = "frequency";
return result;
}
int CarriersLocationModel::indexOf(const QString name) const
{
auto it = std::find_if(mCarriers.begin(), mCarriers.end(), [name]
(const Carrier& carrier)
{ return name == carrier.mName || name == carrier.mCallsign; });
if (it == mCarriers.end())
return -1;
return static_cast<int>(std::distance(mCarriers.begin(), it));
}
SGGeod CarriersLocationModel::geodForIndex(int index) const
{
const auto uIndex = static_cast<size_t>(index);
if ((index < 0) || (uIndex >= mCarriers.size())) {
return {};
}
const auto& c = mCarriers.at(uIndex);
return c.mInitialLocation;
}
QString CarriersLocationModel::pennantForIndex(int index) const
{
const auto uIndex = static_cast<size_t>(index);
if ((index < 0) || (uIndex >= mCarriers.size())) {
return {};
}
const auto& c = mCarriers.at(uIndex);
return c.mCallsign;
}
QStringList CarriersLocationModel::parkingsForIndex(int index) const
{
const auto uIndex = static_cast<size_t>(index);
if ((index < 0) || (uIndex >= mCarriers.size())) {
return {};
}
const auto& c = mCarriers.at(uIndex);
return c.mParkings;
}

View file

@ -0,0 +1,61 @@
#ifndef CARRIERSMODEL_H
#define CARRIERSMODEL_H
#include <vector>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/props/props.hxx>
#include <QAbstractListModel>
class CarriersLocationModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit CarriersLocationModel(QObject *parent = nullptr);
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
// copied from NavaidSearchModel
enum Roles {
GeodRole = Qt::UserRole + 1,
GuidRole = Qt::UserRole + 2,
IdentRole = Qt::UserRole + 3,
NameRole = Qt::UserRole + 4,
IconRole = Qt::UserRole + 5,
TypeRole = Qt::UserRole + 6,
NavFrequencyRole = Qt::UserRole + 7
};
int indexOf(const QString name) const;
SGGeod geodForIndex(int index) const;
QString pennantForIndex(int index) const;
QStringList parkingsForIndex(int index) const;
private:
struct Carrier
{
QString mScenario; // scenario ID for loading
QString mCallsign; // pennant-number
QString mName;
SGGeod mInitialLocation;
// icon?
QString mTACAN;
QStringList mParkings;
};
using CarrierVec = std::vector<Carrier>;
CarrierVec mCarriers;
void processCarrier(const std::string& scenario, SGPropertyNode_ptr carrierNode);
};
#endif // CARRIERSMODEL_H

View file

@ -93,13 +93,13 @@ void CatalogListModel::resetData()
int CatalogListModel::rowCount(const QModelIndex& parent) const int CatalogListModel::rowCount(const QModelIndex& parent) const
{ {
return m_catalogs.size(); Q_UNUSED(parent)
return static_cast<int>(m_catalogs.size());
} }
QVariant CatalogListModel::data(const QModelIndex& index, int role) const QVariant CatalogListModel::data(const QModelIndex& index, int role) const
{ {
simgear::pkg::CatalogRef cat = m_catalogs.at(index.row()); const auto cat = m_catalogs.at(static_cast<size_t>(index.row()));
if (role == Qt::DisplayRole) { if (role == Qt::DisplayRole) {
QString name = QString::fromStdString(cat->name()); QString name = QString::fromStdString(cat->name());
QString desc; QString desc;
@ -139,6 +139,8 @@ QVariant CatalogListModel::data(const QModelIndex& index, int role) const
return translateStatusForCatalog(cat); return translateStatusForCatalog(cat);
} else if (role == CatalogIsNewlyAdded) { } else if (role == CatalogIsNewlyAdded) {
return (cat == m_newlyAddedCatalog); return (cat == m_newlyAddedCatalog);
} else if (role == CatalogEnabled) {
return cat->isUserEnabled();
} }
return QVariant(); return QVariant();
@ -146,13 +148,18 @@ QVariant CatalogListModel::data(const QModelIndex& index, int role) const
bool CatalogListModel::setData(const QModelIndex &index, const QVariant &value, int role) bool CatalogListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{ {
auto cat = m_catalogs.at(static_cast<size_t>(index.row()));
if (role == CatalogEnabled) {
cat->setUserEnabled(value.toBool());
return true;
}
return false; return false;
} }
Qt::ItemFlags CatalogListModel::flags(const QModelIndex &index) const Qt::ItemFlags CatalogListModel::flags(const QModelIndex &index) const
{ {
Qt::ItemFlags r = Qt::ItemIsSelectable; Qt::ItemFlags r = Qt::ItemIsSelectable;
const auto cat = m_catalogs.at(index.row()); const auto cat = m_catalogs.at(static_cast<size_t>(index.row()));
if (cat->isEnabled()) { if (cat->isEnabled()) {
r |= Qt::ItemIsEnabled; r |= Qt::ItemIsEnabled;
} }
@ -168,28 +175,27 @@ QHash<int, QByteArray> CatalogListModel::roleNames() const
result[CatalogNameRole] = "name"; result[CatalogNameRole] = "name";
result[CatalogStatusRole] = "status"; result[CatalogStatusRole] = "status";
result[CatalogIsNewlyAdded] = "isNewlyAdded"; result[CatalogIsNewlyAdded] = "isNewlyAdded";
result[CatalogEnabled] = "enabled";
return result; return result;
} }
void CatalogListModel::removeCatalog(int index) void CatalogListModel::removeCatalog(int index)
{ {
if ((index < 0) || (index >= m_catalogs.size())) { if ((index < 0) || (index >= static_cast<int>(m_catalogs.size()))) {
return; return;
} }
const std::string removeId = m_catalogs.at(index)->id(); const std::string removeId = m_catalogs.at(static_cast<size_t>(index))->id();
m_packageRoot->removeCatalogById(removeId); m_packageRoot->removeCatalogById(removeId);
resetData(); resetData();
} }
void CatalogListModel::refreshCatalog(int index) void CatalogListModel::refreshCatalog(int index)
{ {
if ((index < 0) || (index >= m_catalogs.size())) { if ((index < 0) || (index >= static_cast<int>(m_catalogs.size()))) {
return; return;
} }
m_catalogs.at(static_cast<size_t>(index))->refresh();
m_catalogs.at(index)->refresh();
} }
void CatalogListModel::installDefaultCatalog() void CatalogListModel::installDefaultCatalog()
@ -221,7 +227,7 @@ int CatalogListModel::indexOf(QUrl url)
if (it == m_catalogs.end()) if (it == m_catalogs.end())
return -1; return -1;
return std::distance(m_catalogs.begin(), it); return static_cast<int>(std::distance(m_catalogs.begin(), it));
} }
void CatalogListModel::finalizeAddCatalog() void CatalogListModel::finalizeAddCatalog()
@ -237,7 +243,7 @@ void CatalogListModel::finalizeAddCatalog()
return; return;
} }
const int row = std::distance(m_catalogs.begin(), it); const int row = static_cast<int>(std::distance(m_catalogs.begin(), it));
m_newlyAddedCatalog.clear(); m_newlyAddedCatalog.clear();
emit isAddingCatalogChanged(); emit isAddingCatalogChanged();
emit statusOfAddingCatalogChanged(); emit statusOfAddingCatalogChanged();

View file

@ -38,6 +38,7 @@ const int CatalogStatusRole = Qt::UserRole + 5;
const int CatalogDescriptionRole = Qt::UserRole + 6; const int CatalogDescriptionRole = Qt::UserRole + 6;
const int CatalogNameRole = Qt::UserRole + 7; const int CatalogNameRole = Qt::UserRole + 7;
const int CatalogIsNewlyAdded = Qt::UserRole + 8; const int CatalogIsNewlyAdded = Qt::UserRole + 8;
const int CatalogEnabled = Qt::UserRole + 9;
class CatalogDelegate; class CatalogDelegate;

41
src/GUI/FGFontCache.cxx Normal file → Executable file
View file

@ -13,20 +13,17 @@
// along with this program; if not, write to the Free Software // along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
// //
#ifdef HAVE_CONFIG_H #include <config.h>
# include <config.h>
#endif
#ifdef HAVE_WINDOWS_H
#include <windows.h>
#endif
#include "FGFontCache.hxx" #include "FGFontCache.hxx"
#include <plib/fnt.h> // this is our one in 3rdparty
#include "fnt.h"
#include <plib/pu.h> #include <plib/pu.h>
#include <simgear/props/props.hxx> #include <simgear/props/props.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <Main/globals.hxx> #include <Main/globals.hxx>
@ -211,26 +208,18 @@ FGFontCache::getfntpath(const std::string& name)
bool FGFontCache::initializeFonts() bool FGFontCache::initializeFonts()
{ {
static std::string fontext("txf"); static std::string fontext(".txf");
init(); init();
std::string fontPath = _path.local8BitStr();
ulDir* fontdir = ulOpenDir(fontPath.c_str()); auto dir = simgear::Dir(_path);
if (!fontdir) for (auto p : dir.children(simgear::Dir::TYPE_FILE, fontext)) {
return false; fntTexFont* f = new fntTexFont;
const ulDirEnt *dirEntry; if (f->load(p))
while ((dirEntry = ulReadDir(fontdir)) != 0) { _texFonts[p.file()] = f;
SGPath path(_path); else
path.append(dirEntry->d_name); delete f;
if (path.extension() == fontext) {
fntTexFont* f = new fntTexFont;
std::string ps = path.local8BitStr();
if (f->load(ps.c_str()))
_texFonts[std::string(dirEntry->d_name)] = f;
else
delete f;
}
} }
ulCloseDir(fontdir);
return true; return true;
} }

33
src/GUI/HoverArea.cxx Normal file
View file

@ -0,0 +1,33 @@
#include "HoverArea.hxx"
#include <QDebug>
#include <QQuickWindow>
HoverArea::HoverArea()
{
connect(this, &QQuickItem::windowChanged, [this](QQuickWindow* win) {
if (win) {
win->installEventFilter(this);
}
});
}
bool HoverArea::eventFilter(QObject *sender, QEvent *event)
{
Q_UNUSED(sender)
if (event->type() == QEvent::MouseMove) {
QMouseEvent* me = static_cast<QMouseEvent*>(event);
const auto local = mapFromScene(me->pos());
const bool con = contains(local);
if (con != m_containsMouse) {
m_containsMouse = con;
emit containsMouseChanged(con);
}
}
return false;
}

31
src/GUI/HoverArea.hxx Normal file
View file

@ -0,0 +1,31 @@
#ifndef HOVERAREA_HXX
#define HOVERAREA_HXX
#include <QQuickItem>
class HoverArea : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(bool containsMouse READ containsMouse NOTIFY containsMouseChanged);
public:
HoverArea();
bool containsMouse() const
{
return m_containsMouse;
}
signals:
void containsMouseChanged(bool containsMouse);
protected:
bool eventFilter(QObject* sender, QEvent* event) override;
private:
bool m_containsMouse = false;
};
#endif // HOVERAREA_HXX

View file

@ -52,6 +52,9 @@
#include "NavaidSearchModel.hxx" #include "NavaidSearchModel.hxx"
#include "FlightPlanController.hxx" #include "FlightPlanController.hxx"
#include "ModelDataExtractor.hxx" #include "ModelDataExtractor.hxx"
#include "CarriersLocationModel.hxx"
#include "SetupRootDialog.hxx"
#include "HoverArea.hxx"
using namespace simgear::pkg; using namespace simgear::pkg;
@ -90,6 +93,9 @@ LauncherController::LauncherController(QObject *parent, QWindow* window) :
m_aircraftSearchModel = new AircraftProxyModel(this, m_aircraftModel); m_aircraftSearchModel = new AircraftProxyModel(this, m_aircraftModel);
m_favouriteAircraftModel = new AircraftProxyModel(this, m_aircraftModel);
m_favouriteAircraftModel->setShowFavourites(true);
m_aircraftHistory = new RecentAircraftModel(m_aircraftModel, this); m_aircraftHistory = new RecentAircraftModel(m_aircraftModel, this);
connect(m_aircraftModel, &AircraftItemModel::aircraftInstallCompleted, connect(m_aircraftModel, &AircraftItemModel::aircraftInstallCompleted,
@ -102,14 +108,12 @@ LauncherController::LauncherController(QObject *parent, QWindow* window) :
this, &LauncherController::updateSelectedAircraft); this, &LauncherController::updateSelectedAircraft);
QSettings settings; QSettings settings;
LocalAircraftCache::instance()->setPaths(settings.value("aircraft-paths").toStringList());
LocalAircraftCache::instance()->scanDirs();
m_aircraftModel->setPackageRoot(globals->packageRoot()); m_aircraftModel->setPackageRoot(globals->packageRoot());
m_aircraftGridMode = settings.value("aircraftGridMode").toBool(); m_aircraftGridMode = settings.value("aircraftGridMode").toBool();
m_subsystemIdleTimer = new QTimer(this); m_subsystemIdleTimer = new QTimer(this);
m_subsystemIdleTimer->setInterval(10); m_subsystemIdleTimer->setInterval(5);
connect(m_subsystemIdleTimer, &QTimer::timeout, []() connect(m_subsystemIdleTimer, &QTimer::timeout, []()
{globals->get_subsystem_mgr()->update(0.0);}); {globals->get_subsystem_mgr()->update(0.0);});
m_subsystemIdleTimer->start(); m_subsystemIdleTimer->start();
@ -144,6 +148,7 @@ void LauncherController::initQML()
qmlRegisterUncreatableType<MPServersModel>("FlightGear.Launcher", 1, 0, "MPServers", "Singleton API"); qmlRegisterUncreatableType<MPServersModel>("FlightGear.Launcher", 1, 0, "MPServers", "Singleton API");
qmlRegisterType<NavaidSearchModel>("FlightGear", 1, 0, "NavaidSearch"); qmlRegisterType<NavaidSearchModel>("FlightGear", 1, 0, "NavaidSearch");
qmlRegisterType<CarriersLocationModel>("FlightGear", 1, 0, "CarriersModel");
qmlRegisterUncreatableType<Units>("FlightGear", 1, 0, "Units", "Only for enum"); qmlRegisterUncreatableType<Units>("FlightGear", 1, 0, "Units", "Only for enum");
qmlRegisterType<UnitsModel>("FlightGear", 1, 0, "UnitsModel"); qmlRegisterType<UnitsModel>("FlightGear", 1, 0, "UnitsModel");
@ -166,6 +171,7 @@ void LauncherController::initQML()
qmlRegisterType<NavaidDiagram>("FlightGear", 1, 0, "NavaidDiagram"); qmlRegisterType<NavaidDiagram>("FlightGear", 1, 0, "NavaidDiagram");
qmlRegisterType<RouteDiagram>("FlightGear", 1, 0, "RouteDiagram"); qmlRegisterType<RouteDiagram>("FlightGear", 1, 0, "RouteDiagram");
qmlRegisterType<QmlRadioButtonGroup>("FlightGear", 1, 0, "RadioButtonGroup"); qmlRegisterType<QmlRadioButtonGroup>("FlightGear", 1, 0, "RadioButtonGroup");
qmlRegisterType<HoverArea>("FlightGear", 1, 0, "HoverArea");
qmlRegisterType<ModelDataExtractor>("FlightGear", 1, 0, "ModelDataExtractor"); qmlRegisterType<ModelDataExtractor>("FlightGear", 1, 0, "ModelDataExtractor");
@ -185,6 +191,7 @@ void LauncherController::setInAppMode()
m_inAppMode = true; m_inAppMode = true;
m_keepRunningInAppMode = true; m_keepRunningInAppMode = true;
m_appModeResult = true; m_appModeResult = true;
emit inAppChanged();
} }
bool LauncherController::keepRunningInAppMode() const bool LauncherController::keepRunningInAppMode() const
@ -280,31 +287,6 @@ void LauncherController::collectAircraftArgs()
m_config->setArg("state", state); m_config->setArg("state", state);
} }
} }
// scenery paths
QSettings settings;
Q_FOREACH(QString path, settings.value("scenery-paths").toStringList()) {
m_config->setArg("fg-scenery", path);
}
// aircraft paths
Q_FOREACH(QString path, settings.value("aircraft-paths").toStringList()) {
m_config->setArg("fg-aircraft", path);
}
// add-on module paths
int size = settings.beginReadArray("addon-modules");
for (int i = 0; i < size; ++i) {
settings.setArrayIndex(i);
QString path = settings.value("path").toString();
bool enable = settings.value("enable").toBool();
if (enable) {
m_config->setArg("addon", path);
}
}
settings.endArray();
} }
void LauncherController::saveAircraft() void LauncherController::saveAircraft()
@ -722,6 +704,20 @@ QVariantList LauncherController::defaultSplashUrls() const
return urls; return urls;
} }
QVariant LauncherController::loadUISetting(QString name, QVariant defaultValue) const
{
QSettings settings;
if (!settings.contains(name))
return defaultValue;
return settings.value(name);
}
void LauncherController::saveUISetting(QString name, QVariant value) const
{
QSettings settings;
settings.setValue(name, value);
}
void LauncherController::onAircraftInstalledCompleted(QModelIndex index) void LauncherController::onAircraftInstalledCompleted(QModelIndex index)
{ {
maybeUpdateSelectedAircraft(index); maybeUpdateSelectedAircraft(index);
@ -777,7 +773,7 @@ void LauncherController::requestChangeDataPath()
{ {
QString currentLocText; QString currentLocText;
QSettings settings; QSettings settings;
QString root = settings.value("fg-root").toString(); QString root = settings.value(SetupRootDialog::rootPathKey()).toString();
if (root.isNull()) { if (root.isNull()) {
currentLocText = tr("Currently the built-in data files are being used"); currentLocText = tr("Currently the built-in data files are being used");
} }
@ -801,12 +797,7 @@ void LauncherController::requestChangeDataPath()
return; return;
} }
{ SetupRootDialog::askRootOnNextLaunch();
QSettings settings;
// set the option to the magic marker value
settings.setValue("fg-root", "!ask");
} // scope the ensure settings are written nicely
flightgear::restartTheApp(); flightgear::restartTheApp();
} }

View file

@ -51,6 +51,7 @@ class LauncherController : public QObject
Q_PROPERTY(AircraftProxyModel* aircraftWithUpdatesModel MEMBER m_aircraftWithUpdatesModel CONSTANT) Q_PROPERTY(AircraftProxyModel* aircraftWithUpdatesModel MEMBER m_aircraftWithUpdatesModel CONSTANT)
Q_PROPERTY(AircraftProxyModel* browseAircraftModel MEMBER m_browseAircraftModel CONSTANT) Q_PROPERTY(AircraftProxyModel* browseAircraftModel MEMBER m_browseAircraftModel CONSTANT)
Q_PROPERTY(AircraftProxyModel* searchAircraftModel MEMBER m_aircraftSearchModel CONSTANT) Q_PROPERTY(AircraftProxyModel* searchAircraftModel MEMBER m_aircraftSearchModel CONSTANT)
Q_PROPERTY(AircraftProxyModel* favouriteAircraftModel MEMBER m_favouriteAircraftModel CONSTANT)
Q_PROPERTY(AircraftItemModel* baseAircraftModel MEMBER m_aircraftModel CONSTANT) Q_PROPERTY(AircraftItemModel* baseAircraftModel MEMBER m_aircraftModel CONSTANT)
@ -85,6 +86,8 @@ class LauncherController : public QObject
Q_PROPERTY(QSize minimumWindowSize READ minWindowSize WRITE setMinWindowSize NOTIFY minWindowSizeChanged) Q_PROPERTY(QSize minimumWindowSize READ minWindowSize WRITE setMinWindowSize NOTIFY minWindowSizeChanged)
Q_PROPERTY(QUrl flyIconUrl READ flyIconUrl NOTIFY selectedAircraftChanged) Q_PROPERTY(QUrl flyIconUrl READ flyIconUrl NOTIFY selectedAircraftChanged)
Q_PROPERTY(bool inAppMode READ inApp NOTIFY inAppChanged)
Q_PROPERTY(bool aircraftGridMode READ aircraftGridMode WRITE setAircraftGridMode NOTIFY aircraftGridModeChanged) Q_PROPERTY(bool aircraftGridMode READ aircraftGridMode WRITE setAircraftGridMode NOTIFY aircraftGridModeChanged)
public: public:
@ -142,7 +145,8 @@ public:
// used on the summary screen // used on the summary screen
Q_INVOKABLE QVariantList defaultSplashUrls() const; Q_INVOKABLE QVariantList defaultSplashUrls() const;
Q_INVOKABLE QVariant loadUISetting(QString name, QVariant defaultValue) const;
Q_INVOKABLE void saveUISetting(QString name, QVariant value) const;
LaunchConfig* config() const LaunchConfig* config() const
{ return m_config; } { return m_config; }
@ -193,6 +197,10 @@ public:
return m_aircraftGridMode; return m_aircraftGridMode;
} }
bool inApp() const
{
return m_inAppMode;
}
signals: signals:
void selectedAircraftChanged(QUrl selectedAircraft); void selectedAircraftChanged(QUrl selectedAircraft);
@ -207,6 +215,7 @@ signals:
void aircraftGridModeChanged(bool aircraftGridMode); void aircraftGridModeChanged(bool aircraftGridMode);
void inAppChanged();
public slots: public slots:
void setSelectedAircraft(QUrl selectedAircraft); void setSelectedAircraft(QUrl selectedAircraft);
@ -261,6 +270,8 @@ private:
AircraftProxyModel* m_aircraftSearchModel; AircraftProxyModel* m_aircraftSearchModel;
AircraftProxyModel* m_browseAircraftModel; AircraftProxyModel* m_browseAircraftModel;
AircraftProxyModel* m_aircraftWithUpdatesModel; AircraftProxyModel* m_aircraftWithUpdatesModel;
AircraftProxyModel* m_favouriteAircraftModel;
MPServersModel* m_serversModel = nullptr; MPServersModel* m_serversModel = nullptr;
LocationController* m_location = nullptr; LocationController* m_location = nullptr;
FlightPlanController* m_flightPlan = nullptr; FlightPlanController* m_flightPlan = nullptr;

View file

@ -68,9 +68,8 @@ LauncherMainWindow::LauncherMainWindow() :
connect(qa, &QAction::triggered, m_controller, &LauncherController::quit); connect(qa, &QAction::triggered, m_controller, &LauncherController::quit);
m_controller->initialRestoreSettings(); m_controller->initialRestoreSettings();
flightgear::launcherSetSceneryPaths();
auto addOnsCtl = new AddOnsController(this); auto addOnsCtl = new AddOnsController(this, m_controller->config());
//////////// ////////////
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)

View file

@ -177,23 +177,6 @@ void AircraftItem::toDataStream(QDataStream& ds) const
ds << tags; ds << tags;
} }
QPixmap AircraftItem::thumbnail(bool loadIfRequired) const
{
if (m_thumbnail.isNull() && loadIfRequired) {
QFileInfo info(path);
QDir dir = info.dir();
if (dir.exists(thumbnailPath)) {
m_thumbnail.load(dir.filePath(thumbnailPath));
// resize to the standard size
if (m_thumbnail.height() > STANDARD_THUMBNAIL_HEIGHT) {
m_thumbnail = m_thumbnail.scaledToHeight(STANDARD_THUMBNAIL_HEIGHT, Qt::SmoothTransformation);
}
}
}
return m_thumbnail;
}
int AircraftItem::indexOfVariant(QUrl uri) const int AircraftItem::indexOfVariant(QUrl uri) const
{ {
const QString path = uri.toLocalFile(); const QString path = uri.toLocalFile();
@ -253,6 +236,7 @@ public:
{ {
m_done = true; m_done = true;
} }
Q_SIGNALS: Q_SIGNALS:
void addedItems(); void addedItems();
@ -437,12 +421,11 @@ void LocalAircraftCache::scanDirs()
QStringList dirs = m_paths; QStringList dirs = m_paths;
Q_FOREACH(SGPath ap, globals->get_aircraft_paths()) { for (SGPath ap : globals->get_aircraft_paths()) {
dirs << QString::fromStdString(ap.utf8Str()); dirs << QString::fromStdString(ap.utf8Str());
} }
SGPath rootAircraft(globals->get_fg_root()); SGPath rootAircraft = globals->get_fg_root() / "Aircraft";
rootAircraft.append("Aircraft");
dirs << QString::fromStdString(rootAircraft.utf8Str()); dirs << QString::fromStdString(rootAircraft.utf8Str());
m_scanThread.reset(new AircraftScanThread(dirs)); m_scanThread.reset(new AircraftScanThread(dirs));
@ -522,10 +505,15 @@ AircraftItemPtr LocalAircraftCache::findItemWithUri(QUrl aircraftUri) const
void LocalAircraftCache::abandonCurrentScan() void LocalAircraftCache::abandonCurrentScan()
{ {
if (m_scanThread) { if (m_scanThread) {
// don't fire onScanFinished when the thread ends
disconnect(m_scanThread.get(), &AircraftScanThread::finished, this,
&LocalAircraftCache::onScanFinished);
m_scanThread->setDone(); m_scanThread->setDone();
m_scanThread->wait(1000); if (!m_scanThread->wait(2000)) {
qWarning() << Q_FUNC_INFO << "scan thread failed to terminate";
}
m_scanThread.reset(); m_scanThread.reset();
qWarning() << Q_FUNC_INFO << "current scan abandonded";
} }
} }

View file

@ -49,8 +49,6 @@ struct AircraftItem
void toDataStream(QDataStream& ds) const; void toDataStream(QDataStream& ds) const;
QPixmap thumbnail(bool loadIfRequired = true) const;
int indexOfVariant(QUrl uri) const; int indexOfVariant(QUrl uri) const;
bool excluded = false; bool excluded = false;
@ -75,7 +73,6 @@ struct AircraftItem
QUrl supportUrl; QUrl supportUrl;
QVariant status(int variant); QVariant status(int variant);
private: private:
mutable QPixmap m_thumbnail;
}; };
class LocalAircraftCache : public QObject class LocalAircraftCache : public QObject
@ -126,7 +123,8 @@ public:
PackageUpdateAvailable, PackageUpdateAvailable,
PackageQueued, PackageQueued,
PackageDownloading, PackageDownloading,
NotPackaged NotPackaged,
PackageInstallFailed
}; };
Q_ENUMS(PackageStatus) Q_ENUMS(PackageStatus)

View file

@ -35,6 +35,7 @@
#include "LaunchConfig.hxx" #include "LaunchConfig.hxx"
#include "DefaultAircraftLocator.hxx" #include "DefaultAircraftLocator.hxx"
#include "NavaidSearchModel.hxx" #include "NavaidSearchModel.hxx"
#include "CarriersLocationModel.hxx"
#include <Airports/airport.hxx> #include <Airports/airport.hxx>
#include <Airports/groundnetwork.hxx> #include <Airports/groundnetwork.hxx>
@ -53,10 +54,8 @@ const unsigned int MAX_RECENT_LOCATIONS = 64;
QVariant savePositionList(const FGPositionedList& posList) QVariant savePositionList(const FGPositionedList& posList)
{ {
QVariantList vl; QVariantList vl;
FGPositionedList::const_iterator it; for (const auto& pos : posList) {
for (it = posList.begin(); it != posList.end(); ++it) {
QVariantMap vm; QVariantMap vm;
FGPositionedRef pos = *it;
vm.insert("ident", QString::fromStdString(pos->ident())); vm.insert("ident", QString::fromStdString(pos->ident()));
vm.insert("type", pos->type()); vm.insert("type", pos->type());
vm.insert("lat", pos->geod().getLatitudeDeg()); vm.insert("lat", pos->geod().getLatitudeDeg());
@ -70,7 +69,7 @@ FGPositionedList loadPositionedList(QVariant v)
{ {
QVariantList vl = v.toList(); QVariantList vl = v.toList();
FGPositionedList result; FGPositionedList result;
result.reserve(vl.size()); result.reserve(static_cast<size_t>(vl.size()));
NavDataCache* cache = NavDataCache::instance(); NavDataCache* cache = NavDataCache::instance();
Q_FOREACH(QVariant v, vl) { Q_FOREACH(QVariant v, vl) {
@ -93,9 +92,10 @@ FGPositionedList loadPositionedList(QVariant v)
LocationController::LocationController(QObject *parent) : LocationController::LocationController(QObject *parent) :
QObject(parent) QObject(parent)
{ {
m_searchModel = new NavaidSearchModel; m_searchModel = new NavaidSearchModel(this);
m_detailQml = new QmlPositioned(this); m_detailQml = new QmlPositioned(this);
m_baseQml = new QmlPositioned(this); m_baseQml = new QmlPositioned(this);
m_carriersModel = new CarriersLocationModel(this);
m_defaultAltitude = QuantityValue{Units::FeetMSL, 6000}; m_defaultAltitude = QuantityValue{Units::FeetMSL, 6000};
m_defaultAirspeed = QuantityValue{Units::Knots, 120}; m_defaultAirspeed = QuantityValue{Units::Knots, 120};
@ -112,9 +112,7 @@ LocationController::LocationController(QObject *parent) :
this, &LocationController::descriptionChanged); this, &LocationController::descriptionChanged);
} }
LocationController::~LocationController() LocationController::~LocationController() = default;
{
}
void LocationController::setLaunchConfig(LaunchConfig *config) void LocationController::setLaunchConfig(LaunchConfig *config)
{ {
@ -196,35 +194,62 @@ void LocationController::setBaseGeod(QmlGeod geod)
if (m_locationIsLatLon && (m_geodLocation == geod.geod())) if (m_locationIsLatLon && (m_geodLocation == geod.geod()))
return; return;
clearLocation();
m_locationIsLatLon = true; m_locationIsLatLon = true;
m_geodLocation = geod.geod(); m_geodLocation = geod.geod();
emit baseLocationChanged();
}
QString LocationController::carrierName() const
{
return m_carrierName;
}
void LocationController::setCarrierLocation(QString name)
{
const auto cIndex = m_carriersModel->indexOf(name);
clearLocation();
if (cIndex < 0) {
qWarning() << "invalid carrier name:" << name;
return;
}
m_locationIsCarrier = true;
m_carrierName = name;
m_geodLocation = m_carriersModel->geodForIndex(cIndex);
m_carrierParkings = m_carriersModel->parkingsForIndex(cIndex);
emit baseLocationChanged();
}
void LocationController::clearLocation()
{
m_locationIsLatLon = false;
m_locationIsCarrier = false;
m_location.clear(); m_location.clear();
m_carrierName.clear();
m_airportLocation.clear(); m_airportLocation.clear();
m_detailLocation.clear(); m_detailLocation.clear();
m_detailQml->setGuid(0);
m_baseQml->setGuid(0);
m_carrierParkings.clear();
m_carrierParking.clear();
emit baseLocationChanged(); emit baseLocationChanged();
} }
void LocationController::setBaseLocation(QmlPositioned* pos) void LocationController::setBaseLocation(QmlPositioned* pos)
{ {
if (!pos) { if (!pos) {
m_location.clear(); clearLocation();
m_detailLocation.clear();
m_detailQml->setGuid(0);
m_baseQml->setGuid(0);
m_airportLocation.clear();
m_locationIsLatLon = false;
emit baseLocationChanged();
return; return;
} }
if (pos->inner() == m_location) if (pos->inner() == m_location)
return; return;
m_locationIsLatLon = false; clearLocation();
m_location = pos->inner(); m_location = pos->inner();
m_baseQml->setGuid(pos->guid()); m_baseQml->setGuid(pos->guid());
m_detailLocation.clear();
m_detailQml->setGuid(0);
if (FGPositioned::isAirportType(m_location.ptr())) { if (FGPositioned::isAirportType(m_location.ptr())) {
m_airportLocation = static_cast<FGAirport*>(m_location.ptr()); m_airportLocation = static_cast<FGAirport*>(m_location.ptr());
@ -249,7 +274,6 @@ void LocationController::setDetailLocation(QmlPositioned* pos)
m_detailLocation.clear(); m_detailLocation.clear();
m_detailQml->setInner({}); m_detailQml->setInner({});
} else { } else {
qInfo() << Q_FUNC_INFO << "pos:" << pos->ident();
m_detailLocation = pos->inner(); m_detailLocation = pos->inner();
m_useActiveRunway = false; m_useActiveRunway = false;
m_useAvailableParking = false; m_useAvailableParking = false;
@ -261,7 +285,7 @@ void LocationController::setDetailLocation(QmlPositioned* pos)
QmlGeod LocationController::baseGeod() const QmlGeod LocationController::baseGeod() const
{ {
if (m_locationIsLatLon) if (m_locationIsLatLon || m_locationIsCarrier)
return m_geodLocation; return m_geodLocation;
if (m_location) if (m_location)
@ -368,6 +392,33 @@ QmlGeod LocationController::parseStringAsGeod(QString string) const
return QmlGeod(g); return QmlGeod(g);
} }
QString LocationController::carrierParking() const
{
if (!m_locationIsCarrier)
return {};
return m_carrierParking;
}
void LocationController::setCarrierParking(QString name)
{
if (!m_locationIsCarrier) {
qWarning() << "active location is not a carrier";
return;
}
if (m_carrierParking == name)
return;
if (!m_carrierParkings.contains(name)) {
qWarning() << "parking '" << name << "' not found in carrier parking list";
return;
}
m_carrierParking = name;
m_useCarrierFLOLS = false;
emit configChanged();
}
QmlPositioned *LocationController::detail() const QmlPositioned *LocationController::detail() const
{ {
return m_detailQml; return m_detailQml;
@ -378,6 +429,11 @@ QmlPositioned *LocationController::baseLocation() const
return m_baseQml; return m_baseQml;
} }
QStringList LocationController::carrierParkings() const
{
return m_carrierParkings;
}
void LocationController::setOffsetRadial(QuantityValue offsetRadial) void LocationController::setOffsetRadial(QuantityValue offsetRadial)
{ {
if (m_offsetRadial == offsetRadial) if (m_offsetRadial == offsetRadial)
@ -436,27 +492,26 @@ void LocationController::setUseAvailableParking(bool useAvailableParking)
emit configChanged(); emit configChanged();
} }
void LocationController::setUseCarrierFLOLS(bool useCarrierFLOLS)
{
if (!m_locationIsCarrier) {
qWarning() << "location is not a carrier";
return;
}
if (m_useCarrierFLOLS == useCarrierFLOLS)
return;
m_useCarrierFLOLS = useCarrierFLOLS;
m_carrierParking.clear();
emit configChanged();
}
void LocationController::restoreLocation(QVariantMap l) void LocationController::restoreLocation(QVariantMap l)
{ {
try { clearLocation();
if (l.contains("location-lat")) {
m_locationIsLatLon = true;
m_geodLocation = SGGeod::fromDeg(l.value("location-lon").toDouble(),
l.value("location-lat").toDouble());
m_location.clear();
m_airportLocation.clear();
m_baseQml->setInner(nullptr);
} else if (l.contains("location-id")) {
m_location = NavDataCache::instance()->loadById(l.value("location-id").toULongLong());
m_locationIsLatLon = false;
if (FGPositioned::isAirportType(m_location.ptr())) {
m_airportLocation = static_cast<FGAirport*>(m_location.ptr());
} else {
m_airportLocation.clear();
}
m_baseQml->setInner(m_location);
}
try {
m_altitudeEnabled = l.contains("altitude"); m_altitudeEnabled = l.contains("altitude");
m_speedEnabled = l.contains("speed"); m_speedEnabled = l.contains("speed");
m_headingEnabled = l.contains("heading"); m_headingEnabled = l.contains("heading");
@ -470,10 +525,33 @@ void LocationController::restoreLocation(QVariantMap l)
m_offsetDistance = l.value("offset-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>(); m_offsetDistance = l.value("offset-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
m_tuneNAV1 = l.value("tune-nav1-radio").toBool(); m_tuneNAV1 = l.value("tune-nav1-radio").toBool();
if (l.contains("location-lat")) {
m_locationIsLatLon = true;
m_geodLocation = SGGeod::fromDeg(l.value("location-lon").toDouble(),
l.value("location-lat").toDouble());
} else if (l.contains("carrier")) {
setCarrierLocation(l.value("carrier").toString());
if (l.contains("carrier-flols")) {
setUseCarrierFLOLS(l.value("carrier-flols").toBool());
// overwrite value form above, intentionally
m_offsetDistance = l.value("location-carrier-flols-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
} else if (l.contains("carrier-parking")) {
setCarrierParking(l.value("carrier-parking").toString());
}
} else if (l.contains("location-id")) {
m_location = NavDataCache::instance()->loadById(l.value("location-id").toLongLong());
m_locationIsLatLon = false;
if (FGPositioned::isAirportType(m_location.ptr())) {
m_airportLocation = static_cast<FGAirport*>(m_location.ptr());
} else {
m_airportLocation.clear();
}
m_baseQml->setInner(m_location);
}
if (m_airportLocation) { if (m_airportLocation) {
m_useActiveRunway = false; m_useActiveRunway = false;
m_useAvailableParking = false; m_useAvailableParking = false;
m_detailLocation.clear();
if (l.contains("location-apt-runway")) { if (l.contains("location-apt-runway")) {
QString runway = l.value("location-apt-runway").toString().toUpper(); QString runway = l.value("location-apt-runway").toString().toUpper();
@ -502,10 +580,7 @@ void LocationController::restoreLocation(QVariantMap l)
} // of location is an airport } // of location is an airport
} catch (const sg_exception&) { } catch (const sg_exception&) {
qWarning() << "Errors restoring saved location, clearing"; qWarning() << "Errors restoring saved location, clearing";
m_location.clear(); clearLocation();
m_airportLocation.clear();
m_baseQml->setInner(nullptr);
m_offsetEnabled = false;
} }
baseLocationChanged(); baseLocationChanged();
@ -515,6 +590,10 @@ void LocationController::restoreLocation(QVariantMap l)
bool LocationController::shouldStartPaused() const bool LocationController::shouldStartPaused() const
{ {
if (m_useCarrierFLOLS) {
return true;
}
if (!m_location) { if (!m_location) {
return false; // defaults to on-ground at the default airport return false; // defaults to on-ground at the default airport
} }
@ -535,6 +614,14 @@ QVariantMap LocationController::saveLocation() const
if (m_locationIsLatLon) { if (m_locationIsLatLon) {
locationSet.insert("location-lat", m_geodLocation.getLatitudeDeg()); locationSet.insert("location-lat", m_geodLocation.getLatitudeDeg());
locationSet.insert("location-lon", m_geodLocation.getLongitudeDeg()); locationSet.insert("location-lon", m_geodLocation.getLongitudeDeg());
} else if (m_locationIsCarrier) {
locationSet.insert("carrier", m_carrierName);
if (m_useCarrierFLOLS) {
locationSet.insert("carrier-flols", true);
locationSet.insert("location-carrier-flols-distance", QVariant::fromValue(m_offsetDistance));
} else if (!m_carrierParking.isEmpty()) {
locationSet.insert("carrier-parking", m_carrierParking);
}
} else if (m_location) { } else if (m_location) {
locationSet.insert("location-id", static_cast<qlonglong>(m_location->guid())); locationSet.insert("location-id", static_cast<qlonglong>(m_location->guid()));
@ -588,7 +675,7 @@ void LocationController::setLocationProperties()
"runway-requested" << "navaid-id" << "offset-azimuth-deg" << "runway-requested" << "navaid-id" << "offset-azimuth-deg" <<
"offset-distance-nm" << "glideslope-deg" << "offset-distance-nm" << "glideslope-deg" <<
"speed-set" << "on-ground" << "airspeed-kt" << "speed-set" << "on-ground" << "airspeed-kt" <<
"airport-id" << "runway" << "parkpos"; "airport-id" << "runway" << "parkpos" << "carrier";
Q_FOREACH(QString s, props) { Q_FOREACH(QString s, props) {
SGPropertyNode* c = presets->getChild(s.toStdString()); SGPropertyNode* c = presets->getChild(s.toStdString());
@ -604,6 +691,8 @@ void LocationController::setLocationProperties()
fgSetDouble("/position/longitude-deg", m_geodLocation.getLongitudeDeg()); fgSetDouble("/position/longitude-deg", m_geodLocation.getLongitudeDeg());
applyPositionOffset(); applyPositionOffset();
applyAltitude();
applyAirspeed();
return; return;
} }
@ -612,6 +701,27 @@ void LocationController::setLocationProperties()
fgSetDouble("/sim/presets/altitude-ft", -9999.0); fgSetDouble("/sim/presets/altitude-ft", -9999.0);
fgSetDouble("/sim/presets/heading-deg", 9999.0); fgSetDouble("/sim/presets/heading-deg", 9999.0);
if (m_locationIsCarrier) {
fgSetString("/sim/presets/carrier", m_carrierName.toStdString());
if (m_useCarrierFLOLS) {
fgSetBool("/sim/presets/runway-requested", true );
// treat the FLOLS as a runway, for the purposes of communication with position-init
fgSetString("/sim/presets/runway", "FLOLS");
fgSetDouble("/sim/presets/offset-distance-nm", m_offsetDistance.convertToUnit(Units::NauticalMiles).value);
applyAirspeed();
} else if (!m_carrierParking.isEmpty()) {
fgSetString("/sim/presets/parkpos", m_carrierParking.toStdString());
}
if (m_tuneNAV1) {
// tune TACAN to the carrier
qInfo() << "Implement TACAN tuning";
}
return;
}
if (!m_location) { if (!m_location) {
return; return;
} }
@ -681,7 +791,7 @@ void LocationController::setLocationProperties()
break; break;
default: default:
break; break;
}; }
// set disambiguation property // set disambiguation property
globals->get_props()->setIntValue("/sim/presets/navaid-id", globals->get_props()->setIntValue("/sim/presets/navaid-id",
@ -796,6 +906,21 @@ void LocationController::onCollectConfig()
return; return;
} }
if (m_locationIsCarrier) {
m_config->setArg("carrier", m_carrierName);
if (!m_carrierParking.isEmpty()) {
m_config->setArg("parkpos", m_carrierParking);
} else if (m_useCarrierFLOLS) {
m_config->setArg("runway", QStringLiteral("FLOLS"));
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
m_config->setArg("offset-distance", QString::number(offsetNm));
applyAirspeed();
}
return;
}
if (!m_location) { if (!m_location) {
return; return;
} }
@ -852,7 +977,7 @@ void LocationController::onCollectConfig()
break; break;
default: default:
break; break;
}; }
// set disambiguation property // set disambiguation property
m_config->setProperty("/sim/presets/navaid-id", QString::number(m_location->guid())); m_config->setProperty("/sim/presets/navaid-id", QString::number(m_location->guid()));
@ -912,6 +1037,11 @@ QString LocationController::description() const
return tr("at position %1").arg(QString::fromStdString(s)); return tr("at position %1").arg(QString::fromStdString(s));
} }
if (m_locationIsCarrier) {
QString pennant = m_carriersModel->pennantForIndex(m_carriersModel->indexOf(m_carrierName));
return tr("on carrier %1 (%2)").arg(m_carrierName).arg(pennant);
}
return tr("No location selected"); return tr("No location selected");
} }
@ -952,7 +1082,7 @@ QString LocationController::description() const
if (m_offsetEnabled) { if (m_offsetEnabled) {
offsetDesc = tr("%1nm %2 of"). offsetDesc = tr("%1nm %2 of").
arg(offsetNm, 0, 'f', 1). arg(offsetNm, 0, 'f', 1).
arg(compassPointFromHeading(m_offsetRadial.value)); arg(compassPointFromHeading(static_cast<int>(m_offsetRadial.value)));
} }
QString navaidType; QString navaidType;

View file

@ -32,6 +32,7 @@
#include "UnitsModel.hxx" #include "UnitsModel.hxx"
class NavaidSearchModel; class NavaidSearchModel;
class CarriersLocationModel;
class LocationController : public QObject class LocationController : public QObject
{ {
@ -40,6 +41,7 @@ class LocationController : public QObject
Q_PROPERTY(QString description READ description NOTIFY descriptionChanged) Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
Q_PROPERTY(NavaidSearchModel* searchModel MEMBER m_searchModel CONSTANT) Q_PROPERTY(NavaidSearchModel* searchModel MEMBER m_searchModel CONSTANT)
Q_PROPERTY(CarriersLocationModel* carriersModel MEMBER m_carriersModel CONSTANT)
Q_PROPERTY(QList<QObject*> airportRunways READ airportRunways NOTIFY baseLocationChanged) Q_PROPERTY(QList<QObject*> airportRunways READ airportRunways NOTIFY baseLocationChanged)
Q_PROPERTY(QList<QObject*> airportParkings READ airportParkings NOTIFY baseLocationChanged) Q_PROPERTY(QList<QObject*> airportParkings READ airportParkings NOTIFY baseLocationChanged)
@ -68,6 +70,13 @@ class LocationController : public QObject
Q_PROPERTY(QmlPositioned* detail READ detail CONSTANT) Q_PROPERTY(QmlPositioned* detail READ detail CONSTANT)
Q_PROPERTY(bool isBaseLatLon READ isBaseLatLon NOTIFY baseLocationChanged) Q_PROPERTY(bool isBaseLatLon READ isBaseLatLon NOTIFY baseLocationChanged)
Q_PROPERTY(bool isCarrier READ isCarrier NOTIFY baseLocationChanged)
Q_PROPERTY(QString carrier READ carrierName WRITE setCarrierLocation NOTIFY baseLocationChanged)
Q_PROPERTY(QStringList carrierParkings READ carrierParkings NOTIFY baseLocationChanged)
Q_PROPERTY(bool useCarrierFLOLS READ useCarrierFLOLS WRITE setUseCarrierFLOLS NOTIFY configChanged)
Q_PROPERTY(QString carrierParking READ carrierParking WRITE setCarrierParking NOTIFY configChanged)
// allow collecting the location properties to be disabled, if the // allow collecting the location properties to be disabled, if the
// user is setting conflicting ones // user is setting conflicting ones
Q_PROPERTY(bool skipFromArgs MEMBER m_skipFromArgs NOTIFY skipFromArgsChanged) Q_PROPERTY(bool skipFromArgs MEMBER m_skipFromArgs NOTIFY skipFromArgsChanged)
@ -110,6 +119,9 @@ public:
QmlGeod baseGeod() const; QmlGeod baseGeod() const;
void setBaseGeod(QmlGeod geod); void setBaseGeod(QmlGeod geod);
QString carrierName() const;
void setCarrierLocation(QString name);
bool isAirportLocation() const; bool isAirportLocation() const;
bool offsetEnabled() const bool offsetEnabled() const
@ -138,6 +150,9 @@ public:
Q_INVOKABLE QmlGeod parseStringAsGeod(QString string) const; Q_INVOKABLE QmlGeod parseStringAsGeod(QString string) const;
QString carrierParking() const;
void setCarrierParking(QString name);
bool tuneNAV1() const bool tuneNAV1() const
{ {
return m_tuneNAV1; return m_tuneNAV1;
@ -161,6 +176,19 @@ public:
{ {
return m_altitude; return m_altitude;
} }
bool isCarrier() const
{
return m_locationIsCarrier;
}
QStringList carrierParkings() const;
bool useCarrierFLOLS() const
{
return m_useCarrierFLOLS;
}
public slots: public slots:
void setOffsetRadial(QuantityValue offsetRadial); void setOffsetRadial(QuantityValue offsetRadial);
@ -174,6 +202,8 @@ public slots:
void setUseAvailableParking(bool useAvailableParking); void setUseAvailableParking(bool useAvailableParking);
void setUseCarrierFLOLS(bool useCarrierFLOLS);
Q_SIGNALS: Q_SIGNALS:
void descriptionChanged(); void descriptionChanged();
void offsetChanged(); void offsetChanged();
@ -186,6 +216,7 @@ private Q_SLOTS:
void onRestoreCurrentLocation(); void onRestoreCurrentLocation();
void onSaveCurrentLocation(); void onSaveCurrentLocation();
private: private:
void clearLocation();
void onSearchComplete(); void onSearchComplete();
void addToRecent(FGPositionedRef pos); void addToRecent(FGPositionedRef pos);
@ -197,12 +228,15 @@ private:
void applyOnFinal(); void applyOnFinal();
NavaidSearchModel* m_searchModel = nullptr; NavaidSearchModel* m_searchModel = nullptr;
CarriersLocationModel* m_carriersModel = nullptr;
FGPositionedRef m_location; FGPositionedRef m_location;
FGAirportRef m_airportLocation; // valid if m_location is an FGAirport FGAirportRef m_airportLocation; // valid if m_location is an FGAirport
FGPositionedRef m_detailLocation; // parking stand or runway detail FGPositionedRef m_detailLocation; // parking stand or runway detail
bool m_locationIsLatLon = false; bool m_locationIsLatLon = false;
SGGeod m_geodLocation; SGGeod m_geodLocation;
bool m_locationIsCarrier = false;
QString m_carrierName;
FGPositionedList m_recentLocations; FGPositionedList m_recentLocations;
LaunchConfig* m_config = nullptr; LaunchConfig* m_config = nullptr;
@ -227,6 +261,10 @@ private:
bool m_speedEnabled = false; bool m_speedEnabled = false;
bool m_altitudeEnabled = false; bool m_altitudeEnabled = false;
bool m_skipFromArgs = false; bool m_skipFromArgs = false;
bool m_useCarrierFLOLS = false;
QString m_carrierParking;
QStringList m_carrierParkings;
}; };
#endif // LOCATION_CONTROLLER_HXX #endif // LOCATION_CONTROLLER_HXX

View file

@ -2002,7 +2002,7 @@ MapWidget::DrawAIObject::DrawAIObject(SGPropertyNode* m, const SGGeod& g) :
heading = model->getDoubleValue("orientation/true-heading-deg"); heading = model->getDoubleValue("orientation/true-heading-deg");
if ((name == "aircraft") || (name == "multiplayer") || if ((name == "aircraft") || (name == "multiplayer") ||
(name == "wingman") || (name == "tanker")) (name == "wingman") || (name == "tanker") || (name == "swift"))
{ {
speedKts = static_cast<int>(model->getDoubleValue("velocities/true-airspeed-kt")); speedKts = static_cast<int>(model->getDoubleValue("velocities/true-airspeed-kt"));
label = model->getStringValue("callsign", "<>"); label = model->getStringValue("callsign", "<>");

View file

@ -24,35 +24,49 @@ QVariant ModelDataExtractor::data() const
return m_model->data(m, role); return m_model->data(m, role);
} }
if (m_value.isArray()) { if (!m_stringsModel.empty()) {
if ((m_index < 0) || (m_index >= m_stringsModel.size()))
return {};
return m_stringsModel.at(m_index);
}
if (m_rawModel.isArray()) {
quint32 uIndex = static_cast<quint32>(m_index); quint32 uIndex = static_cast<quint32>(m_index);
auto v = m_value.property(uIndex); auto v = m_rawModel.property(uIndex);
if (v.isQObject()) { if (v.isQObject()) {
// handle the QList<QObject*> case // handle the QList<QObject*> case
auto obj = v.toQObject(); auto obj = v.toQObject();
return obj->property(m_role.toUtf8().constData()); return obj->property(m_role.toUtf8().constData());
} }
return m_value.property(uIndex).toVariant(); return m_rawModel.property(uIndex).toVariant();
} }
qWarning() << "Unable to convert model data:" << m_rawModel.toString();
return {}; return {};
} }
void ModelDataExtractor::setModel(QJSValue model) void ModelDataExtractor::clear()
{ {
if (m_value.equals(model))
return;
if (m_model) { if (m_model) {
// disconnect from everything // disconnect from everything
disconnect(m_model, nullptr, this, nullptr); disconnect(m_model, nullptr, this, nullptr);
m_model = nullptr; m_model = nullptr;
} }
m_value = model; }
if (m_value.isQObject()) {
m_model = qobject_cast<QAbstractItemModel*>(m_value.toQObject()); void ModelDataExtractor::setModel(QJSValue raw)
{
if (m_rawModel.strictlyEquals(raw))
return;
clear();
m_rawModel = raw;
if (raw.isQObject()) {
m_model = qobject_cast<QAbstractItemModel*>(raw.toQObject());
if (m_model) { if (m_model) {
connect(m_model, &QAbstractItemModel::modelReset, connect(m_model, &QAbstractItemModel::modelReset,
this, &ModelDataExtractor::dataChanged); this, &ModelDataExtractor::dataChanged);
@ -61,10 +75,24 @@ void ModelDataExtractor::setModel(QJSValue model)
// ToDo: handle rows added / removed // ToDo: handle rows added / removed
} else { } else {
qWarning() << "object but not a QAIM" << m_value.toQObject(); qWarning() << "object but not a QAIM" << raw.toQObject();
}
} else if (raw.isArray()) {
} else if (raw.isVariant() || raw.isObject()) {
// special case the QStringList case
// for reasons I don't understand yet, QStringList returned as a
// property value to JS, does not show up as a variant-in-JS-Value above
// (so ::isVariant returns false), but conversion to a variant
// works. Hence the 'raw.isObject' above
const auto var = raw.toVariant();
if (var.type() == QVariant::StringList) {
m_stringsModel = var.toStringList();
} else {
qWarning() << Q_FUNC_INFO << "variant but not a QStringList" << var;
} }
} else {
// might be null, or an array
} }
emit modelChanged(); emit modelChanged();

View file

@ -20,7 +20,7 @@ public:
QJSValue model() const QJSValue model() const
{ {
return m_value; return m_rawModel;
} }
int index() const int index() const
@ -52,8 +52,12 @@ private slots:
void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
private: private:
void clear();
QAbstractItemModel* m_model = nullptr; QAbstractItemModel* m_model = nullptr;
QJSValue m_value; QJSValue m_rawModel;
QStringList m_stringsModel;
int m_index = 0; int m_index = 0;
QString m_role; QString m_role;
}; };

View file

@ -34,6 +34,9 @@ QString fixNavaidName(QString s)
QStringList words = s.split(QChar(' ')); QStringList words = s.split(QChar(' '));
QStringList changedWords; QStringList changedWords;
Q_FOREACH(QString w, words) { Q_FOREACH(QString w, words) {
if (w.isEmpty())
continue;
QString up = w.toUpper(); QString up = w.toUpper();
// expand common abbreviations // expand common abbreviations
@ -150,10 +153,17 @@ void NavaidSearchModel::clear()
qlonglong NavaidSearchModel::guidAtIndex(int index) const qlonglong NavaidSearchModel::guidAtIndex(int index) const
{ {
if ((index < 0) || (index >= m_ids.size())) const size_t uIndex = static_cast<size_t>(index);
if ((index < 0) || (uIndex >= m_ids.size()))
return 0; return 0;
return m_ids.at(index); return m_ids.at(uIndex);
}
NavaidSearchModel::NavaidSearchModel(QObject *parent) :
QAbstractListModel(parent)
{
} }
void NavaidSearchModel::setSearch(QString t, NavaidSearchModel::AircraftType aircraft) void NavaidSearchModel::setSearch(QString t, NavaidSearchModel::AircraftType aircraft)

View file

@ -51,7 +51,7 @@ class NavaidSearchModel : public QAbstractListModel
}; };
public: public:
NavaidSearchModel() { } NavaidSearchModel(QObject* parent = nullptr);
enum AircraftType enum AircraftType
{ {

188
src/GUI/PathListModel.cxx Normal file
View file

@ -0,0 +1,188 @@
#include "PathListModel.hxx"
#include <QSettings>
#include <QDebug>
PathListModel::PathListModel(QObject *pr) :
QAbstractListModel(pr)
{
}
PathListModel::~PathListModel()
{
}
void PathListModel::loadFromSettings(QString key)
{
QSettings settings;
if (!settings.contains(key))
return;
QVariantList vl = settings.value(key).toList();
mPaths.clear();
mPaths.reserve(static_cast<size_t>(vl.size()));
beginResetModel();
Q_FOREACH(QVariant v, vl) {
QVariantMap m = v.toMap();
PathEntry entry;
entry.path = m.value("path").toString();
if (entry.path.isEmpty()) {
continue;
}
entry.enabled = m.value("enabled", QVariant{true}).toBool();
mPaths.push_back(entry);
}
endResetModel();
emit enabledPathsChanged();
emit countChanged();
}
void PathListModel::saveToSettings(QString key) const
{
QVariantList vl;
for (const auto &e : mPaths) {
QVariantMap v;
v["path"] = e.path;
v["enabled"] = e.enabled;
vl.append(v);
}
QSettings settings;
settings.setValue(key, vl);
}
QStringList PathListModel::readEnabledPaths(QString settingsKey)
{
QSettings settings;
if (!settings.contains(settingsKey))
return {};
QStringList result;
QVariantList vl = settings.value(settingsKey).toList();
Q_FOREACH(QVariant v, vl) {
QVariantMap m = v.toMap();
if (!m.value("enabled").toBool())
continue;
result.append(m.value("path").toString());
}
return result;
}
QStringList PathListModel::enabledPaths() const
{
QStringList result;
for (const auto& e : mPaths) {
if (e.enabled) {
result.append(e.path);
}
}
return result;
}
int PathListModel::count()
{
return static_cast<int>(mPaths.size());
}
int PathListModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return static_cast<int>(mPaths.size());
}
QVariant PathListModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
const auto& entry = mPaths.at(static_cast<size_t>(row));
switch (role) {
case Qt::DisplayRole:
case PathRole:
return entry.path;
case PathEnabledRole:
return entry.enabled;
default:
break;
}
return {};
}
bool PathListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
int row = index.row();
auto& entry = mPaths.at(static_cast<size_t>(row));
if (role == PathEnabledRole) {
entry.enabled = value.toBool();
emit dataChanged(index, index, {PathEnabledRole});
emit enabledPathsChanged();
return true;
}
return false;
}
QHash<int, QByteArray> PathListModel::roleNames() const
{
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
result[Qt::DisplayRole] = "path";
result[PathEnabledRole] = "enabled";
return result;
}
void PathListModel::removePath(int index)
{
if ((index < 0) || (index >= static_cast<int>(mPaths.size()))) {
qWarning() << Q_FUNC_INFO << "index invalid:" << index;
return;
}
beginRemoveRows({}, index, index);
auto it = mPaths.begin() + index;
mPaths.erase(it);
endRemoveRows();
emit enabledPathsChanged();
emit countChanged();
}
void PathListModel::appendPath(QString path)
{
PathEntry entry;
entry.path = path;
entry.enabled = true; // enable by default
const int newRow = static_cast<int>(mPaths.size());
beginInsertRows({}, newRow, newRow);
mPaths.push_back(entry);
endInsertRows();
emit enabledPathsChanged();
emit countChanged();
}
void PathListModel::swapIndices(int indexA, int indexB)
{
if ((indexA < 0) || (indexA >= static_cast<int>(mPaths.size()))) {
qWarning() << Q_FUNC_INFO << "index invalid:" << indexA;
return;
}
if ((indexB < 0) || (indexB >= static_cast<int>(mPaths.size()))) {
qWarning() << Q_FUNC_INFO << "index invalid:" << indexB;
return;
}
std::swap(mPaths[static_cast<size_t>(indexA)],
mPaths[static_cast<size_t>(indexB)]);
emit dataChanged(index(indexA), index(indexA));
emit dataChanged(index(indexB), index(indexB));
emit enabledPathsChanged();
}

56
src/GUI/PathListModel.hxx Normal file
View file

@ -0,0 +1,56 @@
#ifndef PATHLISTMODEL_HXX
#define PATHLISTMODEL_HXX
#include <vector>
#include <QAbstractListModel>
const int PathRole = Qt::UserRole + 1;
const int PathEnabledRole = Qt::UserRole + 2;
class PathListModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
PathListModel(QObject* pr);
~PathListModel() override;
void loadFromSettings(QString key);
void saveToSettings(QString key) const;
int rowCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex& index, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
QHash<int, QByteArray> roleNames() const override;
static QStringList readEnabledPaths(QString settingsKey);
QStringList enabledPaths() const;
int count();
signals:
void enabledPathsChanged();
void countChanged();
public slots:
void removePath(int index);
void appendPath(QString path);
void swapIndices(int indexA, int indexB);
private:
struct PathEntry {
QString path;
bool enabled = true;
};
std::vector<PathEntry> mPaths;
};
#endif // PATHLISTMODEL_HXX

View file

@ -38,16 +38,6 @@ QtFileDialog::~QtFileDialog() {}
void QtFileDialog::exec() void QtFileDialog::exec()
{ {
int fakeargc = 1;
static char fakeargv0[] = "fgfs";
static char * fakeargv[2] = {fakeargv0, 0};
// This does nothing if it has already been run, so the fake argc/argv are
// only used if run without launcher. Don't attempt to initialize the
// QSettings, because this would require FGGlobals to be initialized (for
// globals->get_fg_home()), which would prevent using this function at
// early startup.
flightgear::initApp(fakeargc, fakeargv, false /* doInitQSettings */);
// concatenate filter patterns, as Qt uses a single string // concatenate filter patterns, as Qt uses a single string
std::string filter=""; std::string filter="";
for( string_list::const_iterator it = _filterPatterns.begin(); it != _filterPatterns.end();++it ) { for( string_list::const_iterator it = _filterPatterns.begin(); it != _filterPatterns.end();++it ) {

View file

@ -52,6 +52,7 @@
#include <simgear/structure/exception.hxx> #include <simgear/structure/exception.hxx>
#include <simgear/structure/subsystem_mgr.hxx> #include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/misc/sg_path.hxx> #include <simgear/misc/sg_path.hxx>
#include <simgear/package/Root.hxx>
#include <simgear/package/Catalog.hxx> #include <simgear/package/Catalog.hxx>
#include <simgear/package/Package.hxx> #include <simgear/package/Package.hxx>
#include <simgear/package/Install.hxx> #include <simgear/package/Install.hxx>
@ -74,6 +75,7 @@
#include "LauncherMainWindow.hxx" #include "LauncherMainWindow.hxx"
#include "LaunchConfig.hxx" #include "LaunchConfig.hxx"
#include "UnitsModel.hxx" #include "UnitsModel.hxx"
#include "PathListModel.hxx"
using namespace flightgear; using namespace flightgear;
using namespace simgear::pkg; using namespace simgear::pkg;
@ -354,19 +356,22 @@ void initQSettings()
bool checkKeyboardModifiersForSettingFGRoot() bool checkKeyboardModifiersForSettingFGRoot()
{ {
initQSettings(); initQSettings();
#if defined(Q_OS_WIN)
const auto altState = GetAsyncKeyState(VK_MENU);
const auto shiftState = GetAsyncKeyState(VK_SHIFT);
if ((altState < 0) || (shiftState < 0))
#else
Qt::KeyboardModifiers mods = qApp->queryKeyboardModifiers(); Qt::KeyboardModifiers mods = qApp->queryKeyboardModifiers();
if (mods & (Qt::AltModifier | Qt::ShiftModifier)) { if (mods & (Qt::AltModifier | Qt::ShiftModifier))
#endif
{
qWarning() << "Alt/shift pressed during launch"; qWarning() << "Alt/shift pressed during launch";
QSettings settings;
settings.setValue("fg-root", "!ask");
return true; return true;
} }
return false; return false;
} }
void restartTheApp() void restartTheApp()
{ {
QStringList fgArgs; QStringList fgArgs;
@ -403,7 +408,7 @@ void launcherSetSceneryPaths()
// positions // positions
QSettings settings; QSettings settings;
// append explicit scenery paths // append explicit scenery paths
Q_FOREACH(QString path, settings.value("scenery-paths").toStringList()) { Q_FOREACH(QString path, PathListModel::readEnabledPaths("scenery-paths-v2")) {
globals->append_fg_scenery(path.toStdString()); globals->append_fg_scenery(path.toStdString());
} }
@ -451,6 +456,15 @@ bool runLauncherDialog()
fgInitPackageRoot(); fgInitPackageRoot();
// setup package language
auto lang = options->valueForOption("language");
if (!lang.empty()) {
globals->packageRoot()->setLocale(lang);
} else {
const auto langName = QLocale::languageToString(QLocale{}.language());
globals->packageRoot()->setLocale(langName.toStdString());
}
// startup the HTTP system now since packages needs it // startup the HTTP system now since packages needs it
FGHTTPClient* http = globals->add_new_subsystem<FGHTTPClient>(); FGHTTPClient* http = globals->add_new_subsystem<FGHTTPClient>();

20
src/GUI/SetupRootDialog.cxx Normal file → Executable file
View file

@ -40,7 +40,9 @@
#include <Include/version.h> #include <Include/version.h>
#include <Viewer/WindowBuilder.hxx> #include <Viewer/WindowBuilder.hxx>
static QString rootPathKey() #include "QtLauncher.hxx"
QString SetupRootDialog::rootPathKey()
{ {
// return a settings key like fg-root-2018-3-0 // return a settings key like fg-root-2018-3-0
return QString("fg-root-") + QString(FLIGHTGEAR_VERSION).replace('.', '-'); return QString("fg-root-") + QString(FLIGHTGEAR_VERSION).replace('.', '-');
@ -99,7 +101,8 @@ SGPath SetupRootDialog::restoreUserSelectedRoot()
{ {
QSettings settings; QSettings settings;
QString path = settings.value(rootPathKey()).toString(); QString path = settings.value(rootPathKey()).toString();
if (path == "!ask") { bool ask = flightgear::checkKeyboardModifiersForSettingFGRoot();
if (ask || (path == QStringLiteral("!ask"))) {
bool ok = runDialog(ManualChoiceRequested); bool ok = runDialog(ManualChoiceRequested);
Q_ASSERT(ok); Q_ASSERT(ok);
// run dialog either exit()s or sets fg_root, so this // run dialog either exit()s or sets fg_root, so this
@ -108,7 +111,7 @@ SGPath SetupRootDialog::restoreUserSelectedRoot()
} }
if (path.isEmpty()) { if (path.isEmpty()) {
return std::string(); // use the default path return SGPath{}; // use the default path
} }
if (validatePath(path) && validateVersion(path)) { if (validatePath(path) && validateVersion(path)) {
@ -118,7 +121,7 @@ SGPath SetupRootDialog::restoreUserSelectedRoot()
// let's see if the default root is acceptable, in which case we will // let's see if the default root is acceptable, in which case we will
// switch to it. (This gives a more friendly upgrade experience). // switch to it. (This gives a more friendly upgrade experience).
if (defaultRootAcceptable()) { if (defaultRootAcceptable()) {
return std::string(); // use the default path return SGPath{}; // use the default path
} }
// okay, we don't have an acceptable FG_DATA anywhere we can find, we // okay, we don't have an acceptable FG_DATA anywhere we can find, we
@ -131,6 +134,13 @@ SGPath SetupRootDialog::restoreUserSelectedRoot()
} }
} }
void SetupRootDialog::askRootOnNextLaunch()
{
QSettings settings;
// set the option to the magic marker value
settings.setValue(rootPathKey(), "!ask");
}
bool SetupRootDialog::validatePath(QString path) bool SetupRootDialog::validatePath(QString path)
{ {
// check assorted files exist in the root location, to avoid any chance of // check assorted files exist in the root location, to avoid any chance of
@ -216,7 +226,7 @@ void SetupRootDialog::onUseDefaults()
m_browsedPath = QString::fromStdString(r.utf8Str()); m_browsedPath = QString::fromStdString(r.utf8Str());
globals->set_fg_root(r); globals->set_fg_root(r);
QSettings settings; QSettings settings;
settings.remove("fg-root"); // remove any setting settings.remove(rootPathKey()); // remove any setting
accept(); accept();
} }

View file

@ -41,6 +41,10 @@ public:
static bool runDialog(bool usingDefaultRoot); static bool runDialog(bool usingDefaultRoot);
static SGPath restoreUserSelectedRoot(); static SGPath restoreUserSelectedRoot();
static void askRootOnNextLaunch();
static QString rootPathKey();
private slots: private slots:
void onBrowse(); void onBrowse();

View file

@ -27,30 +27,33 @@ public:
void startInstall(pkg::InstallRef) override {} void startInstall(pkg::InstallRef) override {}
void installProgress(pkg::InstallRef, unsigned int, unsigned int) override {} void installProgress(pkg::InstallRef, unsigned int, unsigned int) override {}
void finishInstall(pkg::InstallRef, StatusCode ) override {} void finishInstall(pkg::InstallRef, StatusCode ) override {}
void dataForThumbnail(const std::string& aThumbnailUrl, void dataForThumbnail(const std::string& aThumbnailUrl,
size_t length, const uint8_t* bytes) override size_t length, const uint8_t* bytes) override;
{
if (aThumbnailUrl != owner->url().toString().toStdString()) {
return;
}
QImage img = QImage::fromData(QByteArray::fromRawData(reinterpret_cast<const char*>(bytes), length));
if (img.isNull()) {
if (length > 0) {
// warn if we had valid bytes but couldn't load it, i.e corrupted data or similar
qWarning() << "failed to load image data for URL:" << QString::fromStdString(aThumbnailUrl);
owner->clearImage();
}
return;
}
owner->setImage(img);
}
ThumbnailImageItem* owner; ThumbnailImageItem* owner;
}; };
void ThumbnailImageItem::ThumbnailPackageDelegate::dataForThumbnail(const std::string& aThumbnailUrl,
size_t length, const uint8_t* bytes)
{
if (aThumbnailUrl != owner->url().toString().toStdString()) {
return;
}
const auto iLength = static_cast<int>(length);
QImage img = QImage::fromData(QByteArray::fromRawData(reinterpret_cast<const char*>(bytes), iLength));
if (img.isNull()) {
if (length > 0) {
// warn if we had valid bytes but couldn't load it, i.e corrupted data or similar
qWarning() << "failed to load image data for URL:" << QString::fromStdString(aThumbnailUrl);
owner->clearImage();
}
return;
}
owner->setImage(img);
}
ThumbnailImageItem::ThumbnailImageItem(QQuickItem* parent) : ThumbnailImageItem::ThumbnailImageItem(QQuickItem* parent) :
QQuickItem(parent), QQuickItem(parent),
m_delegate(new ThumbnailPackageDelegate(this)), m_delegate(new ThumbnailPackageDelegate(this)),
@ -81,7 +84,7 @@ QSGNode *ThumbnailImageItem::updatePaintNode(QSGNode* oldNode, QQuickItem::Updat
textureNode->setOwnsTexture(true); textureNode->setOwnsTexture(true);
} }
QSGTexture* tex = window()->createTextureFromImage(m_image); QSGTexture* tex = window()->createTextureFromImage(m_image, QQuickWindow::TextureIsOpaque);
textureNode->setTexture(tex); textureNode->setTexture(tex);
textureNode->markDirty(QSGBasicGeometryNode::DirtyMaterial); textureNode->markDirty(QSGBasicGeometryNode::DirtyMaterial);
m_imageDirty = false; m_imageDirty = false;
@ -123,7 +126,7 @@ void ThumbnailImageItem::setAircraftUri(QString uri)
pkg::Root* root = globals->packageRoot(); pkg::Root* root = globals->packageRoot();
pkg::PackageRef package = root->getPackageById(packageId); pkg::PackageRef package = root->getPackageById(packageId);
if (package) { if (package) {
int variant = package->indexOfVariant(packageId); auto variant = package->indexOfVariant(packageId);
const auto thumbnail = package->thumbnailForVariant(variant); const auto thumbnail = package->thumbnailForVariant(variant);
m_imageUrl = QUrl(QString::fromStdString(thumbnail.url)); m_imageUrl = QUrl(QString::fromStdString(thumbnail.url));
if (m_imageUrl.isValid()) { if (m_imageUrl.isValid()) {

View file

@ -47,9 +47,8 @@ static int CALLBACK BrowseFolderCallback(
if (uMsg == BFFM_INITIALIZED) { if (uMsg == BFFM_INITIALIZED) {
// set the initial directory now // set the initial directory now
WindowsFileDialog* dlg = reinterpret_cast<WindowsFileDialog*>(lpData); WindowsFileDialog* dlg = reinterpret_cast<WindowsFileDialog*>(lpData);
std::string s = dlg->getDirectory().local8BitStr(); const auto w = dlg->getDirectory().wstr();
LPCTSTR path = s.c_str(); ::SendMessageW(hwnd, BFFM_SETSELECTIONW, true, (LPARAM) w.c_str());
::SendMessage(hwnd, BFFM_SETSELECTION, true, (LPARAM) path);
} }
return 0; return 0;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -3,7 +3,9 @@
#endif #endif
#include <plib/pu.h> #include <plib/pu.h>
#include <plib/fnt.h>
#include "fnt.h"
/** /**
* fonts.cxx generated by the genfonts utility by Pawel W. Olszta. * fonts.cxx generated by the genfonts utility by Pawel W. Olszta.

View file

@ -102,12 +102,7 @@ Item {
description: qsTr("To use aircraft you download yourself, FlightGear needs to " + description: qsTr("To use aircraft you download yourself, FlightGear needs to " +
"know the folder(s) containing the aircraft data.") "know the folder(s) containing the aircraft data.")
showAddButton: true showAddButton: true
onAdd: { onAdd: _addOns.addAircraftPath();
var newPath =_addOns.addAircraftPath();
if (newPath !== "") {
_addOns.aircraftPaths.push(newPath)
}
}
} }
Rectangle { Rectangle {
@ -127,21 +122,10 @@ Item {
model: _addOns.aircraftPaths model: _addOns.aircraftPaths
delegate: PathListDelegate { delegate: PathListDelegate {
width: aircraftPathsColumn.width width: aircraftPathsColumn.width
deletePromptText: qsTr("Remove the aircraft folder: '%1' from the list? (The folder contents will not be changed)").arg(modelData); deletePromptText: qsTr("Remove the aircraft folder: '%1' from the list? (The folder contents will not be changed)").arg(model.path);
modelCount: _addOns.aircraftPaths.length modelCount: _addOns.aircraftPaths.count
onPerformDelete: _addOns.aircraftPaths.removePath(model.index)
onPerformDelete: { onPerformMove: _addOns.aircraftPaths.swapIndices(model.index, newIndex);
var modifiedPaths = _addOns.aircraftPaths.slice()
modifiedPaths.splice(model.index, 1);
_addOns.aircraftPaths = modifiedPaths;
}
onPerformMove: {
var modifiedPaths = _addOns.aircraftPaths.slice()
modifiedPaths.splice(model.index, 1);
modifiedPaths.splice(newIndex, 0, modelData)
_addOns.aircraftPaths = modifiedPaths;
}
} }
} }
@ -241,12 +225,7 @@ Item {
"to know the folders containing the scenery data. " + "to know the folders containing the scenery data. " +
"Adjust the order of the list to control which scenery is used in a region."); "Adjust the order of the list to control which scenery is used in a region.");
showAddButton: true showAddButton: true
onAdd: { onAdd: _addOns.addSceneryPath();
var newPath =_addOns.addSceneryPath();
if (newPath !== "") {
_addOns.sceneryPaths.push(newPath)
}
}
} }
Rectangle { Rectangle {
@ -267,21 +246,10 @@ Item {
delegate: PathListDelegate { delegate: PathListDelegate {
width: sceneryPathsColumn.width width: sceneryPathsColumn.width
deletePromptText: qsTr("Remove the scenery folder: '%1' from the list? (The folder contents will not be changed)").arg(modelData); deletePromptText: qsTr("Remove the scenery folder: '%1' from the list? (The folder contents will not be changed)").arg(model.path);
modelCount: _addOns.sceneryPaths.length modelCount: _addOns.sceneryPaths.count
onPerformDelete: _addOns.sceneryPaths.removePath(model.index)
onPerformDelete: { onPerformMove: _addOns.sceneryPaths.swapIndices(model.index, newIndex);
var modifiedPaths = _addOns.sceneryPaths.slice()
modifiedPaths.splice(model.index, 1);
_addOns.sceneryPaths = modifiedPaths;
}
onPerformMove: {
var modifiedPaths = _addOns.sceneryPaths.slice()
modifiedPaths.splice(model.index, 1);
modifiedPaths.splice(newIndex, 0, modelData)
_addOns.sceneryPaths = modifiedPaths;
}
} }
} }
@ -304,14 +272,10 @@ Item {
var path = _addOns.installCustomScenery(); var path = _addOns.installCustomScenery();
if (path !== "") { if (path !== "") {
// insert into scenery paths if not already present // insert into scenery paths if not already present
var sceneryPaths = _addOns.sceneryPaths
for (var i = 0; i < sceneryPaths.length; i++) {
if (sceneryPaths[i] === path)
return; // found, we are are done
}
// not found, add it // not found, add it
_addOns.sceneryPaths.push(path); _addOns.sceneryPaths.appendPath(path);
} }
} }
} }

View file

@ -41,12 +41,9 @@ Item {
width: delegateRoot.width width: delegateRoot.width
Checkbox { Checkbox {
id: chkbox id: enableCheckbox
checked: model.enable checked: model.enable
width: 30
anchors.left: parent.left anchors.left: parent.left
anchors.right: addonsDelegateHover.left
anchors.rightMargin: Style.margin
height: contentRect.height height: contentRect.height
onCheckedChanged: { onCheckedChanged: {
_addOns.modules.enable(model.index, checked) _addOns.modules.enable(model.index, checked)
@ -59,7 +56,7 @@ Item {
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.left: chkbox.right anchors.left: enableCheckbox.right
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton

View file

@ -1,5 +1,7 @@
import QtQuick 2.4 import QtQuick 2.4
import FlightGear.Launcher 1.0 import FlightGear.Launcher 1.0
import FlightGear 1.0
import "." import "."
Item { Item {
@ -77,19 +79,39 @@ Item {
spacing: Style.margin spacing: Style.margin
AircraftVariantChoice { Item {
id: titleBox height: titleBox.height
width: parent.width width: parent.width
aircraft: model.uri; FavouriteToggleButton {
currentIndex: model.activeVariant id: favourite
onSelected: { checked: model.favourite
model.activeVariant = index anchors.verticalCenter: parent.verticalCenter
root.select(model.uri) onToggle: {
model.favourite = on;
}
}
AircraftVariantChoice {
id: titleBox
anchors {
left: favourite.right
leftMargin: Style.margin
right: parent.right
}
aircraft: model.uri;
currentIndex: model.activeVariant
onSelected: {
model.activeVariant = index
root.select(model.uri)
}
} }
} }
StyledText { StyledText {
id: description id: description
width: parent.width width: parent.width

View file

@ -1,5 +1,6 @@
import QtQuick 2.4 import QtQuick 2.4
import FlightGear.Launcher 1.0 import FlightGear.Launcher 1.0
import FlightGear 1.0
import "." import "."
Item { Item {
@ -87,10 +88,21 @@ Item {
} }
} }
MouseArea { FavouriteToggleButton {
id: favourite
anchors {
left: parent.left
top: parent.top
margins: Style.margin
}
visible: hover.containsMouse || model.favourite
checked: model.favourite
onToggle: { model.favourite = on; }
}
HoverArea {
id: hover id: hover
anchors.fill: parent anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
} }
} // of root item } // of root item

View file

@ -11,6 +11,9 @@ Item {
function updateSelectionFromLauncher() function updateSelectionFromLauncher()
{ {
if (!model)
return;
var row = model.indexForURI(_launcher.selectedAircraft); var row = model.indexForURI(_launcher.selectedAircraft);
if (row >= 0) { if (row >= 0) {
view.currentIndex = row; view.currentIndex = row;

View file

@ -17,6 +17,10 @@ FocusScope
} }
} }
Component.onCompleted: {
_launcher.browseAircraftModel.loadRatingsSettings();
}
Rectangle Rectangle
{ {
id: tabBar id: tabBar
@ -24,6 +28,7 @@ FocusScope
width: parent.width width: parent.width
GridToggleButton { GridToggleButton {
id: gridModeToggle
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Style.margin anchors.leftMargin: Style.margin
@ -45,6 +50,16 @@ FocusScope
active: root.state == "installed" active: root.state == "installed"
} }
TabButton {
id: favouritesButton
text: qsTr("Favourites")
onClicked: {
root.state = "favourites"
root.updateSelectionFromLauncher();
}
active: root.state == "favourites"
}
TabButton { TabButton {
id: browseButton id: browseButton
text: qsTr("Browse") text: qsTr("Browse")
@ -128,7 +143,8 @@ FocusScope
Loader { Loader {
id: aircraftContent id: aircraftContent
source: _launcher.aircraftGridMode ? "qrc:///qml/AircraftGridView.qml" // we use gridModeToggle vis to mean enabled, effectively
source: (gridModeToggle.visible && _launcher.aircraftGridMode) ? "qrc:///qml/AircraftGridView.qml"
: "qrc:///qml/AircraftListView.qml" : "qrc:///qml/AircraftListView.qml"
anchors { anchors {
@ -182,6 +198,10 @@ FocusScope
__model: _launcher.installedAircraftModel __model: _launcher.installedAircraftModel
__header: emptyHeader __header: emptyHeader
} }
PropertyChanges {
target: gridModeToggle; visible: true
}
}, },
State { State {
@ -191,6 +211,10 @@ FocusScope
__model: _launcher.searchAircraftModel __model: _launcher.searchAircraftModel
__header: emptyHeader __header: emptyHeader
} }
PropertyChanges {
target: gridModeToggle; visible: true
}
}, },
State { State {
@ -200,6 +224,10 @@ FocusScope
__model: _launcher.browseAircraftModel __model: _launcher.browseAircraftModel
__header: _addOns.showNoOfficialHangar ? noDefaultCatalogHeader : ratingsHeader __header: _addOns.showNoOfficialHangar ? noDefaultCatalogHeader : ratingsHeader
} }
PropertyChanges {
target: gridModeToggle; visible: true
}
}, },
State { State {
@ -209,7 +237,26 @@ FocusScope
__model: _launcher.aircraftWithUpdatesModel __model: _launcher.aircraftWithUpdatesModel
__header: (_launcher.aircraftWithUpdatesModel.count > 0) ? updateAllHeader : emptyHeader __header: (_launcher.aircraftWithUpdatesModel.count > 0) ? updateAllHeader : emptyHeader
} }
PropertyChanges {
target: gridModeToggle; visible: false
}
},
State {
name: "favourites"
PropertyChanges {
target: root
__model: _launcher.favouriteAircraftModel
__header: emptyHeader
}
PropertyChanges {
target: gridModeToggle; visible: true
}
} }
] ]
function showDetails(uri) function showDetails(uri)

View file

@ -12,6 +12,9 @@ Item {
function updateSelectionFromLauncher() function updateSelectionFromLauncher()
{ {
if (!model)
return;
model.selectVariantForAircraftURI(_launcher.selectedAircraft); model.selectVariantForAircraftURI(_launcher.selectedAircraft);
var row = model.indexForURI(_launcher.selectedAircraft); var row = model.indexForURI(_launcher.selectedAircraft);
if (row >= 0) { if (row >= 0) {

View file

@ -15,6 +15,7 @@ ListHeaderBox
onCheckedChanged: { onCheckedChanged: {
_launcher.browseAircraftModel.ratingsFilterEnabled = checked _launcher.browseAircraftModel.ratingsFilterEnabled = checked
_launcher.saveUISetting("enable-ratings-filter", checked);
} }
label: qsTr("Filter using ratings") label: qsTr("Filter using ratings")

View file

@ -92,10 +92,9 @@ Rectangle {
screenPos = _launcher.mapToGlobal(title, Qt.point(0, 0)) screenPos = _launcher.mapToGlobal(title, Qt.point(0, 0))
} }
popupFrame.x = screenPos.x; var pop = popup.createObject(root, {x:screenPos.x, y:screenPos.y })
popupFrame.y = screenPos.y; tracker.window = pop;
popupFrame.show() pop.show();
tracker.window = popupFrame
} }
} }
@ -108,59 +107,63 @@ Rectangle {
id: tracker id: tracker
} }
Window { Component {
id: popupFrame id: popup
width: root.width Window {
flags: Qt.Popup id: popupFrame
height: choicesColumn.childrenRect.height
color: "white"
Rectangle { width: root.width
border.width: 1 flags: Qt.Popup
border.color: Style.minorFrameColor height: choicesColumn.childrenRect.height
anchors.fill: parent color: "white"
}
Column { Rectangle {
id: choicesColumn border.width: 1
border.color: Style.minorFrameColor
anchors.fill: parent
}
Repeater { Column {
// would prefer the model to be conditional on visiblity, id: choicesColumn
// but this trips up the Window sizing on Linux (Ubuntu at
// least) and we get a mis-aligned origin
model: aircraftInfo.variantNames
delegate: Item { Repeater {
width: popupFrame.width // would prefer the model to be conditional on visiblity,
height: choiceText.implicitHeight // but this trips up the Window sizing on Linux (Ubuntu at
// least) and we get a mis-aligned origin
model: aircraftInfo.variantNames
Text { delegate: Item {
id: choiceText width: popupFrame.width
text: modelData height: choiceText.implicitHeight
// allow override the size in case the title size is enormous Text {
font.pixelSize: (popupFontPixelSize > 0) ? popupFontPixelSize : title.font.pixelSize id: choiceText
text: modelData
color: choiceArea.containsMouse ? Style.themeColor : Style.baseTextColor // allow override the size in case the title size is enormous
anchors { font.pixelSize: (root.popupFontPixelSize > 0) ? root.popupFontPixelSize : title.font.pixelSize
left: parent.left
right: parent.right color: choiceArea.containsMouse ? Style.themeColor : Style.baseTextColor
margins: 4 anchors {
left: parent.left
right: parent.right
margins: 4
}
} }
}
MouseArea { MouseArea {
id: choiceArea id: choiceArea
hoverEnabled: true hoverEnabled: true
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
popupFrame.hide() popupFrame.close()
root.selected(model.index) root.selected(model.index)
}
} }
} } // of delegate Item
} // of delegate Item } // of Repeater
} // of Repeater }
} } // of popup frame
} // of popup frame } // of popup component
} }

View file

@ -26,12 +26,19 @@ Item {
} }
} }
function isDisabled()
{
return (model.status !== CatalogListModel.Ok) ||
(enableCheckbox.checked === false);
}
Item Item
{ {
anchors.top: divider.bottom anchors.top: divider.bottom
height: catalogTextColumn.childrenRect.height + Style.margin * 2 height: catalogTextColumn.childrenRect.height + Style.margin * 2
width: parent.width width: parent.width
Column { Column {
id: catalogTextColumn id: catalogTextColumn
@ -42,11 +49,28 @@ Item {
anchors.rightMargin: Style.margin anchors.rightMargin: Style.margin
spacing: Style.margin spacing: Style.margin
StyledText { Row {
font.pixelSize: Style.subHeadingFontPixelSize spacing: Style.margin
font.bold: true height: headingText.height
width: parent.width
text: model.name Checkbox {
id: enableCheckbox
checked: model.enabled
height: parent.height
onCheckedChanged: model.enable = checked;
// only allow the user to toggle enable/disable if
// the catalog is valid
visible: (model.status === CatalogListModel.Ok)
}
StyledText {
id: headingText
font.pixelSize: Style.subHeadingFontPixelSize
font.bold: true
width: catalogTextColumn.width - enableCheckbox.width
text: model.name
font.strikeout: delegateRoot.isDisabled();
}
} }
StyledText { StyledText {

View file

@ -1,4 +1,5 @@
import QtQuick 2.4 import QtQuick 2.4
import "."
Item { Item {
property bool checked: false property bool checked: false
@ -11,7 +12,7 @@ Item {
id: checkBox id: checkBox
width: 18 width: 18
height: 18 height: 18
border.color: mouseArea.containsMouse ? "#68A6E1" : "#9f9f9f" border.color: mouseArea.containsMouse ? Style.frameColor : Style.inactiveThemeColor
border.width: 1 border.width: 1
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 8 anchors.leftMargin: 8
@ -22,7 +23,7 @@ Item {
height: 12 height: 12
anchors.centerIn: parent anchors.centerIn: parent
id: checkMark id: checkMark
color: "#9f9f9f" color: Style.themeColor
visible: checked visible: checked
} }
} }
@ -41,5 +42,6 @@ Item {
onClicked: { onClicked: {
checked = !checked checked = !checked
} }
cursorShape: Qt.PointingHandCursor
} }
} }

View file

@ -0,0 +1,36 @@
import QtQuick 2.0
import "."
Item {
id: root
width: height
height: icon.implicitHeight + 1
property bool enable: true
signal clicked();
Image {
id: icon
source: "qrc:///svg/icon-hide"
}
MouseArea {
id: mouse
// hoverEnabled: true
onClicked: root.clicked();
anchors.fill: parent
}
// Text {
// anchors.right: root.left
// anchors.rightMargin: Style.margin
// anchors.verticalCenter: root.verticalCenter
// visible: mouse.containsMouse
// color: Style.baseTextColor
// font.pixelSize: Style.baseFontPixelSize
// text: root.enable ? qsTr("Click_to_disable")
// : qsTr("Click_to_enable")
// }
}

View file

@ -0,0 +1,32 @@
import QtQuick 2.4
import "."
Item {
id: root
property bool checked: false
implicitWidth: icon.width + Style.margin
implicitHeight: icon.height + Style.margin
signal toggle(var on);
Image {
id: icon
source: {
var b = mouse.containsMouse ? !root.checked : root.checked;
return b ? "qrc:///favourite-icon-filled" : "qrc:///favourite-icon-outline";
}
anchors.centerIn: parent
}
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: true
onClicked: root.toggle(!root.checked);
cursorShape: Qt.PointingHandCursor
}
}

View file

@ -37,11 +37,12 @@ Item {
hoverEnabled: root.enabled hoverEnabled: root.enabled
enabled: root.enabled enabled: root.enabled
onClicked: { onClicked: {
var screenPos = _launcher.mapToGlobal(button, Qt.point(-popupFrame.width, 0)) var pop = popup.createObject(root)
popupFrame.x = screenPos.x; var screenPos = _launcher.mapToGlobal(button, Qt.point(-pop.width, 0))
popupFrame.y = screenPos.y; pop.y = screenPos.y;
popupFrame.visible = true pop.x = screenPos.x;
tracker.window = popupFrame tracker.window = pop;
pop.show();
} }
} }
} }
@ -50,51 +51,54 @@ Item {
id: tracker id: tracker
} }
Window { Component {
id: popupFrame id: popup
flags: Qt.Popup Window {
height: choicesColumn.childrenRect.height + Style.margin * 2 id: popupFrame
width: choicesColumn.childrenRect.width + Style.margin * 2
visible: false
color: "white"
Rectangle { flags: Qt.Popup
border.width: 1 height: choicesColumn.childrenRect.height + Style.margin * 2
border.color: Style.minorFrameColor width: choicesColumn.childrenRect.width + Style.margin * 2
anchors.fill: parent color: "white"
}
// text repeater Rectangle {
Column { border.width: 1
id: choicesColumn border.color: Style.minorFrameColor
spacing: Style.margin anchors.fill: parent
x: Style.margin }
y: Style.margin
Repeater { // text repeater
id: choicesRepeater Column {
model: root.model id: choicesColumn
delegate: spacing: Style.margin
StyledText { x: Style.margin
id: choiceText y: Style.margin
// Taken from TableViewItemDelegateLoader.qml to follow QML role conventions Repeater {
text: model && model.hasOwnProperty(displayRole) ? model[displayRole] // Qml ListModel and QAbstractItemModel id: choicesRepeater
: modelData && modelData.hasOwnProperty(displayRole) ? modelData[displayRole] // QObjectList / QObject model: root.model
: modelData != undefined ? modelData : "" // Models without role delegate:
height: implicitHeight + Style.margin StyledText {
id: choiceText
MouseArea { // Taken from TableViewItemDelegateLoader.qml to follow QML role conventions
width: popupFrame.width // full width of the popup text: model && model.hasOwnProperty(displayRole) ? model[displayRole] // Qml ListModel and QAbstractItemModel
height: parent.height : modelData && modelData.hasOwnProperty(displayRole) ? modelData[displayRole] // QObjectList / QObject
onClicked: { : modelData != undefined ? modelData : "" // Models without role
popupFrame.visible = false height: implicitHeight + Style.margin
root.selected(model.index);
MouseArea {
width: popupFrame.width // full width of the popup
height: parent.height
onClicked: {
root.selected(model.index);
popupFrame.close()
}
} }
} } // of Text delegate
} // of Text delegate } // text repeater
} // text repeater } // text column
} // text column } // of popup Window
} // of popup Window }
} }

View file

@ -0,0 +1,30 @@
import QtQuick 2.0
import "."
Rectangle {
id: root
radius: Style.roundRadius
border.width: 1
border.color: Style.themeColor
width: height
height: Style.baseFontPixelSize + Style.margin * 2
color: mouse.containsMouse ? Style.minorFrameColor : "white"
property alias icon: icon.source
signal clicked();
Image {
id: icon
width: parent.width - Style.margin
height: parent.height - Style.margin
anchors.centerIn: parent
}
MouseArea {
id: mouse
hoverEnabled: true
onClicked: root.clicked();
anchors.fill: parent
}
}

View file

@ -6,7 +6,7 @@ Item {
id: root id: root
// order of this model sets the order of buttons in the sidebar // order of this model sets the order of buttons in the sidebar
ListModel { ListModel {
id: pagesModel id: startupPagesModel
ListElement { title: qsTr("Summary"); pageSource: "qrc:///qml/Summary.qml"; iconPath: "qrc:///svg/toolbox-summary"; state:"loader" } ListElement { title: qsTr("Summary"); pageSource: "qrc:///qml/Summary.qml"; iconPath: "qrc:///svg/toolbox-summary"; state:"loader" }
ListElement { title: qsTr("Aircraft"); pageSource: "qrc:///qml/AircraftList.qml"; iconPath: "qrc:///svg/toolbox-aircraft"; state:"loader" } ListElement { title: qsTr("Aircraft"); pageSource: "qrc:///qml/AircraftList.qml"; iconPath: "qrc:///svg/toolbox-aircraft"; state:"loader" }
@ -28,6 +28,18 @@ Item {
} }
ListModel {
id: inAppPagesModel
ListElement { title: qsTr("Summary"); pageSource: "qrc:///qml/Summary.qml"; iconPath: "qrc:///svg/toolbox-summary"; state:"loader" }
ListElement { title: qsTr("Aircraft"); pageSource: "qrc:///qml/AircraftList.qml"; iconPath: "qrc:///svg/toolbox-aircraft"; state:"loader" }
ListElement {
title: qsTr("Location"); pageSource: "qrc:///qml/Location.qml";
iconPath: "qrc:///toolbox-location"; state:"loader"
}
}
Component.onCompleted: Component.onCompleted:
{ {
_launcher.minimumWindowSize = Qt.size(Style.strutSize * 12, sidebar.minimumHeight); _launcher.minimumWindowSize = Qt.size(Style.strutSize * 12, sidebar.minimumHeight);
@ -35,7 +47,7 @@ Item {
Connections { Connections {
target: _location target: _location
onSkipFromArgsChanged: pagesModel.setProperty(2, "buttonDisabled", _location.skipFromArgs) onSkipFromArgsChanged: startupPagesModel.setProperty(2, "buttonDisabled", _location.skipFromArgs)
} }
state: "loader" state: "loader"
@ -82,7 +94,7 @@ Item {
id: sidebar id: sidebar
height: parent.height height: parent.height
z: 1 z: 1
pagesModel: pagesModel pagesModel: _launcher.inAppMode ? inAppPagesModel : startupPagesModel
selectedPage: 0 // open on the summary page selectedPage: 0 // open on the summary page
onShowMenu: menu.show(); onShowMenu: menu.show();
@ -127,7 +139,7 @@ Item {
function selectPage(index) function selectPage(index)
{ {
sidebar.setSelectedPage(index); sidebar.setSelectedPage(index);
var page = pagesModel.get(index); var page = sidebar.pagesModel.get(index);
pageLoader.source = page.pageSource pageLoader.source = page.pageSource
root.state = page.state root.state = page.state
} }

View file

@ -9,6 +9,9 @@ Item {
property bool __searchActive: false property bool __searchActive: false
property string lastSearch property string lastSearch
property bool showCarriers: false
readonly property var locationModel: showCarriers ? _location.carriersModel : _location.searchModel
function backToSearch() function backToSearch()
{ {
detailLoader.sourceComponent = null detailLoader.sourceComponent = null
@ -33,6 +36,13 @@ Item {
} }
} }
function selectCarrier(name)
{
selectedLocation.guid = 0;
_location.carrier = name;
detailLoader.sourceComponent = carrierDetails
}
Component.onCompleted: { Component.onCompleted: {
// important so we can leave the location page and return to it, // important so we can leave the location page and return to it,
// preserving the state // preserving the state
@ -46,6 +56,8 @@ Item {
} }
} else if (_location.isBaseLatLon) { } else if (_location.isBaseLatLon) {
detailLoader.sourceComponent = navaidDetails detailLoader.sourceComponent = navaidDetails
} else if (_location.isCarrier) {
detailLoader.sourceComponent = carrierDetails;
} else { } else {
_location.showHistoryInSearchModel(); _location.showHistoryInSearchModel();
} }
@ -69,6 +81,13 @@ Item {
} }
} }
Component {
id: carrierDetails
LocationCarrierView {
id: carrierView
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: "white" color: "white"
@ -129,7 +148,11 @@ Item {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onClicked: { onClicked: {
root.selectLocation(model.guid, model.type); if (root.showCarriers) {
root.selectCarrier(model.name);
} else {
root.selectLocation(model.guid, model.type);
}
} }
} }
} }
@ -160,10 +183,12 @@ Item {
anchors.topMargin: Style.margin anchors.topMargin: Style.margin
} }
SearchButton { SearchButton {
id: searchButton id: searchButton
anchors.right: parent.right anchors.right: carriersButton.left
anchors.top: headerText.bottom anchors.top: headerText.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.margins: Style.margin anchors.margins: Style.margin
@ -172,7 +197,8 @@ Item {
placeholder: qsTr("Search for an airport or navaid"); placeholder: qsTr("Search for an airport or navaid");
onSearch: { onSearch: {
// when th search term is cleared, show the history root.showCarriers = false;
// when the search term is cleared, show the history
if (term == "") { if (term == "") {
_location.showHistoryInSearchModel(); _location.showHistoryInSearchModel();
return; return;
@ -191,6 +217,18 @@ Item {
} }
} }
IconButton {
id: carriersButton
anchors.top: headerText.bottom
anchors.right: parent.right
anchors.margins: Style.margin
icon: "qrc:///svg/icon-carrier"
onClicked: {
root.showCarriers = true;
}
}
StyledText { StyledText {
id: searchHelpText id: searchHelpText
anchors.right: parent.right anchors.right: parent.right
@ -220,12 +258,12 @@ Item {
anchors.topMargin: Style.margin anchors.topMargin: Style.margin
width: parent.width width: parent.width
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
model: _location.searchModel model: root.locationModel
delegate: locationSearchDelegate delegate: locationSearchDelegate
clip: true clip: true
header: Item { header: Item {
visible: _location.searchModel.isSearchActive visible: !root.showCarriers && _location.searchModel.isSearchActive
width: parent.width width: parent.width
height: visible ? 50 : 0 height: visible ? 50 : 0
@ -246,7 +284,7 @@ Item {
footer: Item { footer: Item {
width: parent.width width: parent.width
height: noResultsText.height height: noResultsText.height
visible: (parent.count === 0) && !_location.searchModel.isSearchActive visible: !root.showCarriers && (parent.count === 0) && !_location.searchModel.isSearchActive
Text { Text {
id: noResultsText id: noResultsText
width: parent.width width: parent.width

Some files were not shown because too many files have changed in this diff Show more