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
CMakeLists.txt.user
*.pro.user
nbproject

View file

@ -28,6 +28,8 @@ if (ENABLE_HID_INPUT)
add_subdirectory(hidapi)
endif()
add_subdirectory(fonts)
if (ENABLE_PLIB_JOYSTICK)
add_subdirectory(joystick)
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;
} 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_File *HTS_fopen_from_fn(const char *name, const char *opt)
{
HTS_File *fp = (HTS_File *) HTS_calloc(1, sizeof(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);
#endif
if (fp->pointer == NULL) {
HTS_error(0, "HTS_fopen: Cannot open %s.\n", name);
HTS_free(fp);

View file

@ -370,10 +370,7 @@ if (ENABLE_QT)
find_package(Qt5 5.4 COMPONENTS Widgets Network Qml Quick Svg)
if (Qt5Widgets_FOUND)
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)
include (Translations)
else()
# don't try to build FGQCanvas if Qt wasn't found correctly
@ -385,7 +382,7 @@ else()
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
# 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
# 'pu' and then adding it back
list(REMOVE_ITEM outDeps "pu" "fnt" "sg")
list(APPEND outDeps ${PUNAME} "fnt" "sg")
list(APPEND outDeps ${PUNAME} "sg")
elseif (${c} STREQUAL "puaux")
list(APPEND outDeps ${PUNAME} "fnt" "sg")
list(APPEND outDeps ${PUNAME} "sg")
elseif (${c} STREQUAL "ssg")
list(APPEND outDeps "sg")
endif()

View file

@ -66,6 +66,8 @@ function(setup_fgfs_libraries target)
target_link_libraries(${target} PLIBJoystick)
endif()
target_link_libraries(${target} PLIBFont)
if(SYSTEM_HTS_ENGINE)
target_link_libraries(${target} flite_hts ${HTS_ENGINE_LIBRARIES})
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)")
endif()
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)
if (${do_translate})
set(translation_res "${PROJECT_BINARY_DIR}/translations.qrc")
add_custom_target(fgfs_qm_files ALL)
@ -55,3 +56,14 @@ if (${do_translate})
# set this so config.h can detect it
set(HAVE_QRC_TRANSLATIONS TRUE)
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):
self.d = []
self.f = []
self.t = []
self.version = 0
self.path = None # will be a VirtualPath instance when set
@ -221,6 +222,9 @@ class DirIndex:
elif tokens[0] == "f":
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):
if self.path is None:
assert self._rawContents is not None
@ -239,6 +243,9 @@ class DirIndex:
def getDirectories(self):
return self.d
def getTarballs(self):
return self.t
def getFiles(self):
return self.f
@ -639,6 +646,14 @@ class TerraSync:
subdir['hash'])
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)
localFiles = [ f for f in listdir(localFullPath)
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> path_list;
string_list path_list;
if (searchOrder == 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 {
// 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 {
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")) {
p.append(model_path);
if (p.exists()) {
SG_LOG(SG_AI, SG_DEBUG, "Found AI model: " << p.local8BitStr());
path_list.push_back(p.local8BitStr());
SG_LOG(SG_AI, SG_DEBUG, "Found AI model: " << p);
path_list.push_back(p.utf8Str());
break;
}
}
@ -566,8 +567,8 @@ std::vector<std::string> FGAIBase::resolveModelPath(ModelSearchOrder searchOrder
for (SGPath p : globals->get_data_paths()) {
p.append(fallback_path);
if (p.exists()) {
SG_LOG(SG_AI, SG_DEBUG, "Found fallback model path for index " << _fallback_model_index << ": " << p.local8BitStr());
path_list.push_back(p.local8BitStr());
SG_LOG(SG_AI, SG_DEBUG, "Found fallback model path for index " << _fallback_model_index << ": " << p);
path_list.push_back(p.utf8Str());
break;
}
}

View file

@ -17,9 +17,7 @@
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <algorithm>
#include <string>
@ -35,11 +33,13 @@
#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) {
if (!scFileNode)
@ -67,11 +67,14 @@ void FGAICarrier::readFromScenario(SGPropertyNode* scFileNode) {
// Transform to the right coordinate frame, configuration is done in
// the usual x-back, y-right, z-up coordinates, computations
// in the simulation usual body x-forward, y-right, z-down coordinates
flols_off(0) = - flols->getDoubleValue("x-offset-m", 0);
flols_off(1) = flols->getDoubleValue("y-offset-m", 0);
flols_off(2) = - flols->getDoubleValue("z-offset-m", 0);
_flolsPosOffset(0) = - flols->getDoubleValue("x-offset-m", 0);
_flolsPosOffset(1) = flols->getDoubleValue("y-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
flols_off = SGVec3d::zeros();
_flolsPosOffset = SGVec3d::zeros();
std::vector<SGPropertyNode_ptr> props = scFileNode->getChildren("parking-pos");
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
eyeWrtCarrier = ec2body.transform(eyeWrtCarrier);
// the eyepoints vector wrt the flols position
SGVec3d eyeWrtFlols = eyeWrtCarrier - flols_off;
SGVec3d eyeWrtFlols = eyeWrtCarrier - _flolsPosOffset;
// the distance from the eyepoint to the flols
dist = norm(eyeWrtFlols);
// now the angle, positive angles are upwards
if (fabs(dist) < SGLimits<float>::min()) {
if (fabs(dist) < SGLimits<double>::min()) {
angle = 0;
} else {
double sAngle = -eyeWrtFlols(2)/dist;
@ -327,11 +330,9 @@ bool FGAICarrier::getParkPosition(const string& id, SGGeod& geodPos,
{
// FIXME: does not yet cover rotation speeds.
list<ParkPosition>::iterator it = ppositions.begin();
while (it != ppositions.end()) {
for (const auto& ppos : ppositions) {
// Take either the specified one or the first one ...
if ((*it).name == id || id.empty()) {
ParkPosition ppos = *it;
if (ppos.name == id || id.empty()) {
SGVec3d cartPos = getCartPosAt(ppos.offset);
geodPos = SGGeod::fromCart(cartPos);
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);
return true;
}
++it;
}
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
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));
//calculate the relative wind direction
rel_wind_from_deg = atan2(rel_wind_speed_from_east_kts, rel_wind_speed_from_north_kts)
* SG_RADIANS_TO_DEGREES;
rel_wind_from_deg = SGMiscd::rad2deg(atan2(rel_wind_speed_from_east_kts, rel_wind_speed_from_north_kts));
//calculate rel wind
rel_wind = rel_wind_from_deg - hdg;
@ -640,7 +656,7 @@ SGSharedPtr<FGAICarrier> FGAICarrier::findCarrierByNameOrPennant(const std::stri
return {};
}
for (const auto aiObject : aiManager->get_ai_list()) {
for (const auto& aiObject : aiManager->get_ai_list()) {
if (aiObject->isa(FGAIBase::otCarrier)) {
SGSharedPtr<FGAICarrier> c = static_cast<FGAICarrier*>(aiObject.get());
if ((c->sign == namePennant) || (c->_getName() == namePennant)) {
@ -652,7 +668,7 @@ SGSharedPtr<FGAICarrier> FGAICarrier::findCarrierByNameOrPennant(const std::stri
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")) {
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
// are possible, for example)
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
* 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:
/// Is sufficient to be private, stores a possible position to place an
/// aircraft on start
@ -116,7 +119,9 @@ private:
string sign; // The sign of this carrier.
// 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 angle;

View file

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

View file

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

View file

@ -50,6 +50,12 @@ FGAIMultiplayer::FGAIMultiplayer() :
lastUpdateTime = 0;
playerLag = 0.03;
compensateLag = 1;
realTime = false;
lastTime=0.0;
lagPpsAveraged = 1.0;
rawLag = 0.0;
rawLagMod = 0.0;
lagModAveraged = 0.0;
_searchOrder = PREFER_DATA;
}
@ -152,10 +158,27 @@ void FGAIMultiplayer::update(double dt)
// requested time to the most recent available packet. This is the
// target we want to reach in average.
double lag = it->second.lag;
rawLag = curentPkgTime - curtime;
realTime = false; //default behaviour
if (!mTimeOffsetSet) {
mTimeOffsetSet = true;
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 {
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);
}
double offset = 0.0;
//spectator mode, more late to be in the interpolation zone
@ -166,8 +189,15 @@ void FGAIMultiplayer::update(double dt)
// using the prediction mode to display the mpaircraft in the futur/past with given playerlag value
//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;
}
if (!realTime) {
if ((!mAllowExtrapolation && offset + lag < mTimeOffset)
|| (offset - 10 > mTimeOffset)) {
mTimeOffset = offset;
@ -181,7 +211,6 @@ void FGAIMultiplayer::update(double dt)
// 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) {
@ -217,7 +246,7 @@ void FGAIMultiplayer::update(double dt)
<< fabs(norm(it->second.linearVel)*systemIncrement));
}
}
}
// Compute the time in the feeders time scale which fits the current time
// we need to
@ -227,9 +256,10 @@ void FGAIMultiplayer::update(double dt)
SGQuatf ecOrient;
SGVec3f ecLinearVel;
if (tInterp <= curentPkgTime) {
if (tInterp < curentPkgTime) {
// Ok, we need a time prevous to the last available packet,
// 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
MotionInfo::iterator nextIt = mMotionInfo.upper_bound(tInterp);

View file

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

View file

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

View file

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

View file

@ -144,6 +144,7 @@ FGReplay::init()
replay_time = fgGetNode("/sim/replay/time", true);
replay_time_str = fgGetNode("/sim/replay/time-str", true);
replay_looped = fgGetNode("/sim/replay/looped", true);
replay_duration_act = fgGetNode("/sim/replay/duration-act", true);
speed_up = fgGetNode("/sim/speed-up", true);
// alias to keep backward compatibility
@ -431,8 +432,8 @@ FGReplay::update( double dt )
fgSetDouble( "/sim/replay/start-time", startTime );
fgSetDouble( "/sim/replay/end-time", endTime );
double duration = 0;
if (replay_looped->getBoolValue())
fgGetDouble("/sim/replay/duration");
if (replay_duration_act->getBoolValue())
duration = fgGetDouble("/sim/replay/duration");
if( duration && (duration < (endTime - startTime)) ) {
current_time = endTime - duration;
} else {
@ -822,7 +823,7 @@ FGReplay::saveTape(const SGPath& Filename, SGPropertyNode* MetaDataProps)
bool ok = true;
/* open output stream *******************************************/
gzContainerWriter output(Filename.local8BitStr(), FlightRecorderFileMagic);
gzContainerWriter output(Filename, FlightRecorderFileMagic);
if (!output.good())
{
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;
/* open input stream ********************************************/
gzContainerReader input(Filename.local8BitStr(), FlightRecorderFileMagic);
gzContainerReader input(Filename, FlightRecorderFileMagic);
if (input.eof() || !input.good())
{
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_str;
SGPropertyNode_ptr replay_looped;
SGPropertyNode_ptr replay_duration_act;
SGPropertyNode_ptr speed_up;
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;
// timeCopy = timeCopy.substr(2,timeCopy.length());
type = tp;
BOOST_FOREACH(std::string s, strutils::split(lst, ",")) {
std::string ident = strutils::strip(s);
for (const auto s : strutils::split(lst, ",")) {
auto ident = strutils::strip(s);
// http://code.google.com/p/flightgear-bugs/issues/detail?id=1137
if ((ident.size() < 2) || !isdigit(ident[1])) {
SG_LOG(SG_GENERAL, SG_INFO, "RunwayList::set: padding runway ident '" << ident << "'");
ident = "0" + ident;
}

View file

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

View file

@ -71,8 +71,8 @@ CanvasMgr::CanvasMgr():
void CanvasMgr::init()
{
_gui_camera = flightgear::getGUICamera(flightgear::CameraGroup::getDefault());
assert(_gui_camera.valid());
if (_gui_camera.valid()) {
// add our two placement factories
sc::Canvas::addPlacementFactory
(
"object",
@ -85,7 +85,9 @@ void CanvasMgr::init()
_2
)
);
sc::Canvas::addPlacementFactory("scenery-object", &addSceneObjectPlacement);
}
simgear::canvas::CanvasMgr::init();
}

View file

@ -777,7 +777,7 @@ NavDisplay::updateFont()
}
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) {
_font = font;

View file

@ -47,7 +47,7 @@
#include <simgear/compiler.h>
#include <plib/fnt.h>
#include "fnt.h"
#include <boost/foreach.hpp>
#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<osgText::Font> font = osgText::readFontFile(tpath.local8BitStr(), fontOptions.get());
osg::ref_ptr<osgText::Font> font = osgText::readFontFile(tpath.utf8Str(), fontOptions.get());
if (font != 0) {
_font = font;

View file

@ -1,6 +1,17 @@
add_executable(fgrcc fgrcc.cxx fgrcc.hxx)
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(
embeddedresources
COMMAND

View file

@ -54,7 +54,7 @@ void Ephemeris::init()
{
SGPath ephem_data_path(globals->get_fg_root());
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/ys", _impl->get_sun(), &Star::getys);

View file

@ -198,6 +198,7 @@ FGJSBsim::FGJSBsim( double dt )
PropertyManager = new FGPropertyManager( (FGPropertyNode*)globals->get_props() );
fdmex = new FGFDMExec( PropertyManager );
fdmex->Hold();
// Register ground callback.
fdmex->SetGroundCallback( new FGFSGroundCallback(this) );
@ -383,6 +384,15 @@ FGJSBsim::FGJSBsim( double dt )
mesh = new AircraftMesh(fgGetDouble("/fdm/jsbsim/metrics/bw-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();
copy_to_JSBsim();
fdmex->Resume();
fdmex->RunIC(); //loop JSBSim once w/o integrating
if (fgGetBool("/sim/presets/running")) {
Propulsion->InitRunning(-1);

View file

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

View file

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

View file

@ -189,6 +189,11 @@ void FGUFO::update( double dt ) {
set_V_north(cos(heading) * velocity * SG_METER_TO_FEET);
set_V_east(sin(heading) * velocity * SG_METER_TO_FEET);
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);
virtual ~FGGround();
virtual void getGroundPlane(const double pos[3],
void getGroundPlane(const double pos[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],
const simgear::BVHMaterial **material,
unsigned int &body);
unsigned int &body) override;
virtual bool getBody(double t, double bodyToWorld[16], double linearVel[3],
double angularVel[3], unsigned int &id);
bool getBody(double t, double bodyToWorld[16], double linearVel[3],
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],
double end[2][3], float vel[2][3]);
float getCatapult(const double pos[3],
double end[2][3], float vel[2][3]) override;
void setTimeOffset(double toff);

View file

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

View file

@ -8,8 +8,7 @@ namespace yasim {
class Ground {
public:
Ground();
virtual ~Ground();
virtual ~Ground() = default;
virtual void getGroundPlane(const double pos[3],
double plane[4], float vel[3],

View file

@ -190,6 +190,12 @@ void FDMShell::update(double dt)
fgSetBool("/sim/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 "InstallSceneryDialog.hxx"
#include "QtLauncher.hxx"
#include "PathListModel.hxx"
#include "LaunchConfig.hxx"
//////////////////////////////////////////////////////////////////////////
AddOnsController::AddOnsController(LauncherMainWindow *parent) :
AddOnsController::AddOnsController(LauncherMainWindow *parent, LaunchConfig* config) :
QObject(parent),
m_launcher(parent)
m_launcher(parent),
m_config(config)
{
m_catalogs = new CatalogListModel(this,
simgear::pkg::RootRef(globals->packageRoot()));
@ -38,10 +41,32 @@ AddOnsController::AddOnsController(LauncherMainWindow *parent) :
connect(m_addonsModuleModel, &AddonsModel::modulesChanged, this, &AddOnsController::onAddonsChanged);
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");
for (int i = 0; i < size; ++i) {
settings.setArrayIndex(i);
@ -66,14 +91,18 @@ AddOnsController::AddOnsController(LauncherMainWindow *parent) :
qmlRegisterUncreatableType<AddOnsController>("FlightGear.Launcher", 1, 0, "AddOnsControllers", "no");
qmlRegisterUncreatableType<CatalogListModel>("FlightGear.Launcher", 1, 0, "CatalogListModel", "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;
}
QStringList AddOnsController::sceneryPaths() const
PathListModel* AddOnsController::sceneryPaths() const
{
return m_sceneryPaths;
}
@ -119,6 +148,7 @@ QString AddOnsController::addAircraftPath() const
}
}
m_aircraftPaths->appendPath(path);
return path;
}
@ -206,6 +236,7 @@ QString AddOnsController::addSceneryPath() const
}
}
m_sceneryPaths->appendPath(path);
return path;
}
@ -227,37 +258,6 @@ void AddOnsController::openDirectory(QString path)
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)
{
@ -342,3 +342,31 @@ void AddOnsController::onAddonsChanged()
}
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 AddonsModel;
class LauncherMainWindow;
class PathListModel;
class LaunchConfig;
class AddOnsController : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList aircraftPaths READ aircraftPaths WRITE setAircraftPaths NOTIFY aircraftPathsChanged)
Q_PROPERTY(QStringList sceneryPaths READ sceneryPaths WRITE setSceneryPaths NOTIFY sceneryPathsChanged)
Q_PROPERTY(PathListModel* aircraftPaths READ aircraftPaths CONSTANT)
Q_PROPERTY(PathListModel* sceneryPaths READ sceneryPaths CONSTANT)
Q_PROPERTY(QStringList modulePaths READ modulePaths WRITE setModulePaths NOTIFY modulePathsChanged)
Q_PROPERTY(CatalogListModel* catalogs READ catalogs CONSTANT)
@ -22,10 +24,10 @@ class AddOnsController : public QObject
Q_PROPERTY(bool showNoOfficialHangar READ showNoOfficialHangar NOTIFY showNoOfficialHangarChanged)
public:
explicit AddOnsController(LauncherMainWindow *parent = nullptr);
explicit AddOnsController(LauncherMainWindow *parent, LaunchConfig* config);
QStringList aircraftPaths() const;
QStringList sceneryPaths() const;
PathListModel* aircraftPaths() const;
PathListModel* sceneryPaths() const;
QStringList modulePaths() const;
CatalogListModel* catalogs() const
@ -50,8 +52,6 @@ public:
Q_INVOKABLE void officialCatalogAction(QString s);
signals:
void aircraftPathsChanged(QStringList aircraftPaths);
void sceneryPathsChanged(QStringList sceneryPaths);
void modulePathsChanged(QStringList modulePaths);
void modulesChanged();
@ -59,12 +59,12 @@ signals:
void showNoOfficialHangarChanged();
public slots:
void setAircraftPaths(QStringList aircraftPaths);
void setSceneryPaths(QStringList sceneryPaths);
void setModulePaths(QStringList modulePaths);
void setAddons(AddonsModel* addons);
void onAddonsChanged(void);
void collectArgs();
private:
bool shouldShowOfficialCatalogMessage() const;
void onCatalogsChanged();
@ -72,9 +72,10 @@ private:
LauncherMainWindow* m_launcher;
CatalogListModel* m_catalogs = nullptr;
AddonsModel* m_addonsModuleModel = nullptr;
LaunchConfig* m_config = nullptr;
QStringList m_aircraftPaths;
QStringList m_sceneryPaths;
PathListModel* m_aircraftPaths = nullptr;
PathListModel* m_sceneryPaths = nullptr;
QStringList m_addonModulePaths;
};

View file

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

View file

@ -23,6 +23,7 @@
#include <QSettings>
#include <QDebug>
#include <QSharedPointer>
#include <QSettings>
// Simgear
#include <simgear/props/props_io.hxx>
@ -38,10 +39,21 @@
#include "QmlAircraftInfo.hxx"
const int STANDARD_THUMBNAIL_HEIGHT = 128;
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
{
public:
@ -57,18 +69,7 @@ public:
}
protected:
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 catalogRefreshed(CatalogRef aCatalog, StatusCode aReason) override;
void startInstall(InstallRef aInstall) override
{
QModelIndex mi(indexForPackage(aInstall->package()));
@ -77,8 +78,8 @@ protected:
void installProgress(InstallRef aInstall, unsigned int bytes, unsigned int total) override
{
Q_UNUSED(bytes);
Q_UNUSED(total);
Q_UNUSED(bytes)
Q_UNUSED(total)
QModelIndex mi(indexForPackage(aInstall->package()));
m_model->dataChanged(mi, mi);
}
@ -104,7 +105,7 @@ protected:
void installStatusChanged(InstallRef aInstall, StatusCode aReason) override
{
Q_UNUSED(aReason);
Q_UNUSED(aReason)
QModelIndex mi(indexForPackage(aInstall->package()));
m_model->dataChanged(mi, mi);
}
@ -115,44 +116,6 @@ protected:
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:
QModelIndex indexForPackage(const PackageRef& ref) const
{
@ -169,6 +132,22 @@ private:
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) :
QAbstractListModel(pr)
{
@ -179,6 +158,8 @@ AircraftItemModel::AircraftItemModel(QObject* pr) :
this, &AircraftItemModel::onScanAddedItems);
connect(cache, &LocalAircraftCache::cleared,
this, &AircraftItemModel::onLocalCacheCleared);
loadFavourites();
}
AircraftItemModel::~AircraftItemModel()
@ -260,8 +241,14 @@ QVariant AircraftItemModel::data(const QModelIndex& index, int role) const
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) {
quint32 packageIndex = row - m_cachedLocalAircraftCount;
quint32 packageIndex = static_cast<quint32>(row - m_cachedLocalAircraftCount);
const PackageRef& pkg(m_packages[packageIndex]);
InstallRef ex = pkg->existingInstall();
@ -309,16 +296,12 @@ QVariant AircraftItemModel::dataFromItem(AircraftItemPtr item, const DelegateSta
}
return item->description;
} else if (role == Qt::DecorationRole) {
return item->thumbnail();
} else if (role == AircraftPathRole) {
return item->path;
} else if (role == AircraftAuthorsRole) {
return item->authors;
} else if ((role >= AircraftRatingRole) && (role < AircraftVariantDescriptionRole)) {
return item->ratings[role - AircraftRatingRole];
} else if (role == AircraftThumbnailRole) {
return item->thumbnail();
} else if (role == AircraftPackageIdRole) {
// can we fake an ID? otherwise fall through to a null variant
} 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
{
if (role == Qt::DecorationRole) {
role = AircraftThumbnailRole;
}
if (role >= AircraftVariantDescriptionRole) {
int variantIndex = role - AircraftVariantDescriptionRole;
QString desc = QString::fromStdString(item->nameForVariant(variantIndex));
@ -393,6 +372,10 @@ QVariant AircraftItemModel::dataFromPackage(const PackageRef& item, const Delega
return LocalAircraftCache::PackageUpdateAvailable;
}
const auto status = i->status();
if (isPackageFailure(status))
return LocalAircraftCache::PackageInstallFailed;
return LocalAircraftCache::PackageInstalled;
} else {
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
// including the primary. Hence the -1 term.
return static_cast<quint32>(item->variants().size() - 1);
} else if (role == AircraftThumbnailRole) {
return packageThumbnail(item, state);
} else if (role == AircraftAuthorsRole) {
std::string authors = item->getLocalisedProp("author", state.variant);
if (!authors.empty()) {
@ -436,42 +417,6 @@ QVariant AircraftItemModel::packageRating(const PackageRef& p, int 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)
{
int row = index.row();
@ -485,6 +430,18 @@ bool AircraftItemModel::setData(const QModelIndex &index, const QVariant &value,
m_delegateStates[row].variant = newValue;
emit dataChanged(index, index);
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;
@ -500,12 +457,12 @@ QHash<int, QByteArray> AircraftItemModel::roleNames() const
result[AircraftAuthorsRole] = "authors";
result[AircraftVariantCountRole] = "variantCount";
result[AircraftLongDescriptionRole] = "description";
result[AircraftThumbnailRole] = "thumbnail";
result[AircraftPackageSizeRole] = "packageSizeBytes";
result[AircraftPackageStatusRole] = "packageStatus";
result[AircraftInstallDownloadedSizeRole] = "downloadedBytes";
result[AircraftVariantRole] = "activeVariant";
result[AircraftIsFavouriteRole] = "favourite";
result[AircraftStatusRole] = "aircraftStatus";
result[AircraftMinVersionRole] = "requiredFGVersion";
@ -625,7 +582,7 @@ QString AircraftItemModel::nameForAircraftURI(QUrl uri) const
QString ident = uri.path();
PackageRef pkg = m_packageRoot->getPackageById(ident.toStdString());
if (pkg) {
int variantIndex = pkg->indexOfVariant(ident.toStdString());
const auto variantIndex = pkg->indexOfVariant(ident.toStdString());
return QString::fromStdString(pkg->nameForVariant(variantIndex));
}
} else {
@ -637,7 +594,7 @@ QString AircraftItemModel::nameForAircraftURI(QUrl uri) const
void AircraftItemModel::onScanAddedItems(int addedCount)
{
Q_UNUSED(addedCount);
Q_UNUSED(addedCount)
const auto items = LocalAircraftCache::instance()->allItems();
const int newItemCount = items.size() - m_cachedLocalAircraftCount;
const int firstRow = m_cachedLocalAircraftCount;
@ -696,7 +653,7 @@ bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const
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]);
InstallRef ex = pkg->existingInstall();
if (!ex.valid()) {
@ -706,4 +663,21 @@ bool AircraftItemModel::isIndexRunnable(const QModelIndex& index) const
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 AircraftIsSeaplaneRole = Qt::UserRole + 17;
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 AircraftMinVersionRole = Qt::UserRole + 23;
@ -146,6 +146,10 @@ private:
void installSucceeded(QModelIndex index);
void installFailed(QModelIndex index, simgear::pkg::Delegate::StatusCode reason);
void loadFavourites();
void saveFavourites();
private:
PackageDelegate* m_delegate = nullptr;
QVector<DelegateState> m_delegateStates;
@ -153,8 +157,9 @@ private:
simgear::pkg::RootRef m_packageRoot;
simgear::pkg::PackageList m_packages;
mutable QHash<QString, QPixmap> m_downloadedPixmapCache;
QVector<QUrl> m_favourites;
int m_cachedLocalAircraftCount = 0;
};
#endif // of FG_GUI_AIRCRAFT_MODEL

View file

@ -1,5 +1,8 @@
#include "AircraftSearchFilterModel.hxx"
#include <QSettings>
#include <QDebug>
#include "AircraftModel.hxx"
#include <simgear/package/Package.hxx>
@ -11,6 +14,8 @@ AircraftProxyModel::AircraftProxyModel(QObject *pr, QAbstractItemModel * source)
setSortCaseSensitivity(Qt::CaseInsensitive);
setFilterCaseSensitivity(Qt::CaseInsensitive);
// important we sort on the primary name role and not Qt::DisplayRole
// otherwise the aircraft jump when switching variant
setSortRole(AircraftVariantDescriptionRole);
@ -110,6 +115,15 @@ void AircraftProxyModel::setHaveUpdateFilterEnabled(bool e)
invalidate();
}
void AircraftProxyModel::setShowFavourites(bool e)
{
if (e == m_onlyShowFavourites)
return;
m_onlyShowFavourites = e;
invalidate();
}
bool AircraftProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
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
// ratings filter.
if (m_filterString.isEmpty() && !m_onlyShowInstalled && m_ratingsFilter) {
@ -187,3 +206,28 @@ bool AircraftProxyModel::filterAircraft(const QModelIndex &sourceIndex) const
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 loadRatingsSettings();
Q_INVOKABLE void saveRatingsSettings();
QList<int> ratings() const
{
return m_ratings;
@ -56,6 +60,8 @@ public slots:
void setInstalledFilterEnabled(bool e);
void setHaveUpdateFilterEnabled(bool e);
void setShowFavourites(bool e);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
@ -65,6 +71,7 @@ private:
bool m_ratingsFilter = true;
bool m_onlyShowInstalled = false;
bool m_onlyShowWithUpdate = false;
bool m_onlyShowFavourites = false;
QList<int> m_ratings;
QString m_filterString;

View file

@ -124,6 +124,10 @@ if (HAVE_QT)
AddonsModel.hxx
PixmapImageItem.cxx
PixmapImageItem.hxx
PathListModel.cxx
PathListModel.hxx
CarriersLocationModel.cxx
CarriersLocationModel.hxx
${uic_sources}
${qrc_sources}
${qml_sources})
@ -168,6 +172,8 @@ if (HAVE_QT)
RouteDiagram.hxx
ModelDataExtractor.cxx
ModelDataExtractor.hxx
HoverArea.cxx
HoverArea.hxx
)
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
{
return m_catalogs.size();
Q_UNUSED(parent)
return static_cast<int>(m_catalogs.size());
}
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) {
QString name = QString::fromStdString(cat->name());
QString desc;
@ -139,6 +139,8 @@ QVariant CatalogListModel::data(const QModelIndex& index, int role) const
return translateStatusForCatalog(cat);
} else if (role == CatalogIsNewlyAdded) {
return (cat == m_newlyAddedCatalog);
} else if (role == CatalogEnabled) {
return cat->isUserEnabled();
}
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)
{
auto cat = m_catalogs.at(static_cast<size_t>(index.row()));
if (role == CatalogEnabled) {
cat->setUserEnabled(value.toBool());
return true;
}
return false;
}
Qt::ItemFlags CatalogListModel::flags(const QModelIndex &index) const
{
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()) {
r |= Qt::ItemIsEnabled;
}
@ -168,28 +175,27 @@ QHash<int, QByteArray> CatalogListModel::roleNames() const
result[CatalogNameRole] = "name";
result[CatalogStatusRole] = "status";
result[CatalogIsNewlyAdded] = "isNewlyAdded";
result[CatalogEnabled] = "enabled";
return result;
}
void CatalogListModel::removeCatalog(int index)
{
if ((index < 0) || (index >= m_catalogs.size())) {
if ((index < 0) || (index >= static_cast<int>(m_catalogs.size()))) {
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);
resetData();
}
void CatalogListModel::refreshCatalog(int index)
{
if ((index < 0) || (index >= m_catalogs.size())) {
if ((index < 0) || (index >= static_cast<int>(m_catalogs.size()))) {
return;
}
m_catalogs.at(index)->refresh();
m_catalogs.at(static_cast<size_t>(index))->refresh();
}
void CatalogListModel::installDefaultCatalog()
@ -221,7 +227,7 @@ int CatalogListModel::indexOf(QUrl url)
if (it == m_catalogs.end())
return -1;
return std::distance(m_catalogs.begin(), it);
return static_cast<int>(std::distance(m_catalogs.begin(), it));
}
void CatalogListModel::finalizeAddCatalog()
@ -237,7 +243,7 @@ void CatalogListModel::finalizeAddCatalog()
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();
emit isAddingCatalogChanged();
emit statusOfAddingCatalogChanged();

View file

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

33
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
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_WINDOWS_H
#include <windows.h>
#endif
#include "FGFontCache.hxx"
#include <plib/fnt.h>
// this is our one in 3rdparty
#include "fnt.h"
#include <plib/pu.h>
#include <simgear/props/props.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <Main/globals.hxx>
@ -211,26 +208,18 @@ FGFontCache::getfntpath(const std::string& name)
bool FGFontCache::initializeFonts()
{
static std::string fontext("txf");
static std::string fontext(".txf");
init();
std::string fontPath = _path.local8BitStr();
ulDir* fontdir = ulOpenDir(fontPath.c_str());
if (!fontdir)
return false;
const ulDirEnt *dirEntry;
while ((dirEntry = ulReadDir(fontdir)) != 0) {
SGPath path(_path);
path.append(dirEntry->d_name);
if (path.extension() == fontext) {
auto dir = simgear::Dir(_path);
for (auto p : dir.children(simgear::Dir::TYPE_FILE, fontext)) {
fntTexFont* f = new fntTexFont;
std::string ps = path.local8BitStr();
if (f->load(ps.c_str()))
_texFonts[std::string(dirEntry->d_name)] = f;
if (f->load(p))
_texFonts[p.file()] = f;
else
delete f;
}
}
ulCloseDir(fontdir);
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 "FlightPlanController.hxx"
#include "ModelDataExtractor.hxx"
#include "CarriersLocationModel.hxx"
#include "SetupRootDialog.hxx"
#include "HoverArea.hxx"
using namespace simgear::pkg;
@ -90,6 +93,9 @@ LauncherController::LauncherController(QObject *parent, QWindow* window) :
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);
connect(m_aircraftModel, &AircraftItemModel::aircraftInstallCompleted,
@ -102,14 +108,12 @@ LauncherController::LauncherController(QObject *parent, QWindow* window) :
this, &LauncherController::updateSelectedAircraft);
QSettings settings;
LocalAircraftCache::instance()->setPaths(settings.value("aircraft-paths").toStringList());
LocalAircraftCache::instance()->scanDirs();
m_aircraftModel->setPackageRoot(globals->packageRoot());
m_aircraftGridMode = settings.value("aircraftGridMode").toBool();
m_subsystemIdleTimer = new QTimer(this);
m_subsystemIdleTimer->setInterval(10);
m_subsystemIdleTimer->setInterval(5);
connect(m_subsystemIdleTimer, &QTimer::timeout, []()
{globals->get_subsystem_mgr()->update(0.0);});
m_subsystemIdleTimer->start();
@ -144,6 +148,7 @@ void LauncherController::initQML()
qmlRegisterUncreatableType<MPServersModel>("FlightGear.Launcher", 1, 0, "MPServers", "Singleton API");
qmlRegisterType<NavaidSearchModel>("FlightGear", 1, 0, "NavaidSearch");
qmlRegisterType<CarriersLocationModel>("FlightGear", 1, 0, "CarriersModel");
qmlRegisterUncreatableType<Units>("FlightGear", 1, 0, "Units", "Only for enum");
qmlRegisterType<UnitsModel>("FlightGear", 1, 0, "UnitsModel");
@ -166,6 +171,7 @@ void LauncherController::initQML()
qmlRegisterType<NavaidDiagram>("FlightGear", 1, 0, "NavaidDiagram");
qmlRegisterType<RouteDiagram>("FlightGear", 1, 0, "RouteDiagram");
qmlRegisterType<QmlRadioButtonGroup>("FlightGear", 1, 0, "RadioButtonGroup");
qmlRegisterType<HoverArea>("FlightGear", 1, 0, "HoverArea");
qmlRegisterType<ModelDataExtractor>("FlightGear", 1, 0, "ModelDataExtractor");
@ -185,6 +191,7 @@ void LauncherController::setInAppMode()
m_inAppMode = true;
m_keepRunningInAppMode = true;
m_appModeResult = true;
emit inAppChanged();
}
bool LauncherController::keepRunningInAppMode() const
@ -280,31 +287,6 @@ void LauncherController::collectAircraftArgs()
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()
@ -722,6 +704,20 @@ QVariantList LauncherController::defaultSplashUrls() const
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)
{
maybeUpdateSelectedAircraft(index);
@ -777,7 +773,7 @@ void LauncherController::requestChangeDataPath()
{
QString currentLocText;
QSettings settings;
QString root = settings.value("fg-root").toString();
QString root = settings.value(SetupRootDialog::rootPathKey()).toString();
if (root.isNull()) {
currentLocText = tr("Currently the built-in data files are being used");
}
@ -801,12 +797,7 @@ void LauncherController::requestChangeDataPath()
return;
}
{
QSettings settings;
// set the option to the magic marker value
settings.setValue("fg-root", "!ask");
} // scope the ensure settings are written nicely
SetupRootDialog::askRootOnNextLaunch();
flightgear::restartTheApp();
}

View file

@ -51,6 +51,7 @@ class LauncherController : public QObject
Q_PROPERTY(AircraftProxyModel* aircraftWithUpdatesModel MEMBER m_aircraftWithUpdatesModel CONSTANT)
Q_PROPERTY(AircraftProxyModel* browseAircraftModel MEMBER m_browseAircraftModel 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)
@ -86,6 +87,8 @@ class LauncherController : public QObject
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)
public:
explicit LauncherController(QObject *parent, QWindow* win);
@ -142,7 +145,8 @@ public:
// used on the summary screen
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
{ return m_config; }
@ -193,6 +197,10 @@ public:
return m_aircraftGridMode;
}
bool inApp() const
{
return m_inAppMode;
}
signals:
void selectedAircraftChanged(QUrl selectedAircraft);
@ -207,6 +215,7 @@ signals:
void aircraftGridModeChanged(bool aircraftGridMode);
void inAppChanged();
public slots:
void setSelectedAircraft(QUrl selectedAircraft);
@ -261,6 +270,8 @@ private:
AircraftProxyModel* m_aircraftSearchModel;
AircraftProxyModel* m_browseAircraftModel;
AircraftProxyModel* m_aircraftWithUpdatesModel;
AircraftProxyModel* m_favouriteAircraftModel;
MPServersModel* m_serversModel = nullptr;
LocationController* m_location = nullptr;
FlightPlanController* m_flightPlan = nullptr;

View file

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

View file

@ -177,23 +177,6 @@ void AircraftItem::toDataStream(QDataStream& ds) const
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
{
const QString path = uri.toLocalFile();
@ -253,6 +236,7 @@ public:
{
m_done = true;
}
Q_SIGNALS:
void addedItems();
@ -437,12 +421,11 @@ void LocalAircraftCache::scanDirs()
QStringList dirs = m_paths;
Q_FOREACH(SGPath ap, globals->get_aircraft_paths()) {
for (SGPath ap : globals->get_aircraft_paths()) {
dirs << QString::fromStdString(ap.utf8Str());
}
SGPath rootAircraft(globals->get_fg_root());
rootAircraft.append("Aircraft");
SGPath rootAircraft = globals->get_fg_root() / "Aircraft";
dirs << QString::fromStdString(rootAircraft.utf8Str());
m_scanThread.reset(new AircraftScanThread(dirs));
@ -522,10 +505,15 @@ AircraftItemPtr LocalAircraftCache::findItemWithUri(QUrl aircraftUri) const
void LocalAircraftCache::abandonCurrentScan()
{
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->wait(1000);
if (!m_scanThread->wait(2000)) {
qWarning() << Q_FUNC_INFO << "scan thread failed to terminate";
}
m_scanThread.reset();
qWarning() << Q_FUNC_INFO << "current scan abandonded";
}
}

View file

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

View file

@ -35,6 +35,7 @@
#include "LaunchConfig.hxx"
#include "DefaultAircraftLocator.hxx"
#include "NavaidSearchModel.hxx"
#include "CarriersLocationModel.hxx"
#include <Airports/airport.hxx>
#include <Airports/groundnetwork.hxx>
@ -53,10 +54,8 @@ const unsigned int MAX_RECENT_LOCATIONS = 64;
QVariant savePositionList(const FGPositionedList& posList)
{
QVariantList vl;
FGPositionedList::const_iterator it;
for (it = posList.begin(); it != posList.end(); ++it) {
for (const auto& pos : posList) {
QVariantMap vm;
FGPositionedRef pos = *it;
vm.insert("ident", QString::fromStdString(pos->ident()));
vm.insert("type", pos->type());
vm.insert("lat", pos->geod().getLatitudeDeg());
@ -70,7 +69,7 @@ FGPositionedList loadPositionedList(QVariant v)
{
QVariantList vl = v.toList();
FGPositionedList result;
result.reserve(vl.size());
result.reserve(static_cast<size_t>(vl.size()));
NavDataCache* cache = NavDataCache::instance();
Q_FOREACH(QVariant v, vl) {
@ -93,9 +92,10 @@ FGPositionedList loadPositionedList(QVariant v)
LocationController::LocationController(QObject *parent) :
QObject(parent)
{
m_searchModel = new NavaidSearchModel;
m_searchModel = new NavaidSearchModel(this);
m_detailQml = new QmlPositioned(this);
m_baseQml = new QmlPositioned(this);
m_carriersModel = new CarriersLocationModel(this);
m_defaultAltitude = QuantityValue{Units::FeetMSL, 6000};
m_defaultAirspeed = QuantityValue{Units::Knots, 120};
@ -112,9 +112,7 @@ LocationController::LocationController(QObject *parent) :
this, &LocationController::descriptionChanged);
}
LocationController::~LocationController()
{
}
LocationController::~LocationController() = default;
void LocationController::setLaunchConfig(LaunchConfig *config)
{
@ -196,35 +194,62 @@ void LocationController::setBaseGeod(QmlGeod geod)
if (m_locationIsLatLon && (m_geodLocation == geod.geod()))
return;
clearLocation();
m_locationIsLatLon = true;
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_carrierName.clear();
m_airportLocation.clear();
m_detailLocation.clear();
m_detailQml->setGuid(0);
m_baseQml->setGuid(0);
m_carrierParkings.clear();
m_carrierParking.clear();
emit baseLocationChanged();
}
void LocationController::setBaseLocation(QmlPositioned* pos)
{
if (!pos) {
m_location.clear();
m_detailLocation.clear();
m_detailQml->setGuid(0);
m_baseQml->setGuid(0);
m_airportLocation.clear();
m_locationIsLatLon = false;
emit baseLocationChanged();
clearLocation();
return;
}
if (pos->inner() == m_location)
return;
m_locationIsLatLon = false;
clearLocation();
m_location = pos->inner();
m_baseQml->setGuid(pos->guid());
m_detailLocation.clear();
m_detailQml->setGuid(0);
if (FGPositioned::isAirportType(m_location.ptr())) {
m_airportLocation = static_cast<FGAirport*>(m_location.ptr());
@ -249,7 +274,6 @@ void LocationController::setDetailLocation(QmlPositioned* pos)
m_detailLocation.clear();
m_detailQml->setInner({});
} else {
qInfo() << Q_FUNC_INFO << "pos:" << pos->ident();
m_detailLocation = pos->inner();
m_useActiveRunway = false;
m_useAvailableParking = false;
@ -261,7 +285,7 @@ void LocationController::setDetailLocation(QmlPositioned* pos)
QmlGeod LocationController::baseGeod() const
{
if (m_locationIsLatLon)
if (m_locationIsLatLon || m_locationIsCarrier)
return m_geodLocation;
if (m_location)
@ -368,6 +392,33 @@ QmlGeod LocationController::parseStringAsGeod(QString string) const
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
{
return m_detailQml;
@ -378,6 +429,11 @@ QmlPositioned *LocationController::baseLocation() const
return m_baseQml;
}
QStringList LocationController::carrierParkings() const
{
return m_carrierParkings;
}
void LocationController::setOffsetRadial(QuantityValue offsetRadial)
{
if (m_offsetRadial == offsetRadial)
@ -436,27 +492,26 @@ void LocationController::setUseAvailableParking(bool useAvailableParking)
emit configChanged();
}
void LocationController::restoreLocation(QVariantMap l)
void LocationController::setUseCarrierFLOLS(bool useCarrierFLOLS)
{
try {
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);
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)
{
clearLocation();
try {
m_altitudeEnabled = l.contains("altitude");
m_speedEnabled = l.contains("speed");
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_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) {
m_useActiveRunway = false;
m_useAvailableParking = false;
m_detailLocation.clear();
if (l.contains("location-apt-runway")) {
QString runway = l.value("location-apt-runway").toString().toUpper();
@ -502,10 +580,7 @@ void LocationController::restoreLocation(QVariantMap l)
} // of location is an airport
} catch (const sg_exception&) {
qWarning() << "Errors restoring saved location, clearing";
m_location.clear();
m_airportLocation.clear();
m_baseQml->setInner(nullptr);
m_offsetEnabled = false;
clearLocation();
}
baseLocationChanged();
@ -515,6 +590,10 @@ void LocationController::restoreLocation(QVariantMap l)
bool LocationController::shouldStartPaused() const
{
if (m_useCarrierFLOLS) {
return true;
}
if (!m_location) {
return false; // defaults to on-ground at the default airport
}
@ -535,6 +614,14 @@ QVariantMap LocationController::saveLocation() const
if (m_locationIsLatLon) {
locationSet.insert("location-lat", m_geodLocation.getLatitudeDeg());
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) {
locationSet.insert("location-id", static_cast<qlonglong>(m_location->guid()));
@ -588,7 +675,7 @@ void LocationController::setLocationProperties()
"runway-requested" << "navaid-id" << "offset-azimuth-deg" <<
"offset-distance-nm" << "glideslope-deg" <<
"speed-set" << "on-ground" << "airspeed-kt" <<
"airport-id" << "runway" << "parkpos";
"airport-id" << "runway" << "parkpos" << "carrier";
Q_FOREACH(QString s, props) {
SGPropertyNode* c = presets->getChild(s.toStdString());
@ -604,6 +691,8 @@ void LocationController::setLocationProperties()
fgSetDouble("/position/longitude-deg", m_geodLocation.getLongitudeDeg());
applyPositionOffset();
applyAltitude();
applyAirspeed();
return;
}
@ -612,6 +701,27 @@ void LocationController::setLocationProperties()
fgSetDouble("/sim/presets/altitude-ft", -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) {
return;
}
@ -681,7 +791,7 @@ void LocationController::setLocationProperties()
break;
default:
break;
};
}
// set disambiguation property
globals->get_props()->setIntValue("/sim/presets/navaid-id",
@ -796,6 +906,21 @@ void LocationController::onCollectConfig()
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) {
return;
}
@ -852,7 +977,7 @@ void LocationController::onCollectConfig()
break;
default:
break;
};
}
// set disambiguation property
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));
}
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");
}
@ -952,7 +1082,7 @@ QString LocationController::description() const
if (m_offsetEnabled) {
offsetDesc = tr("%1nm %2 of").
arg(offsetNm, 0, 'f', 1).
arg(compassPointFromHeading(m_offsetRadial.value));
arg(compassPointFromHeading(static_cast<int>(m_offsetRadial.value)));
}
QString navaidType;

View file

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

View file

@ -24,35 +24,49 @@ QVariant ModelDataExtractor::data() const
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);
auto v = m_value.property(uIndex);
auto v = m_rawModel.property(uIndex);
if (v.isQObject()) {
// handle the QList<QObject*> case
auto obj = v.toQObject();
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 {};
}
void ModelDataExtractor::setModel(QJSValue model)
void ModelDataExtractor::clear()
{
if (m_value.equals(model))
return;
if (m_model) {
// disconnect from everything
disconnect(m_model, nullptr, this, 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) {
connect(m_model, &QAbstractItemModel::modelReset,
this, &ModelDataExtractor::dataChanged);
@ -61,10 +75,24 @@ void ModelDataExtractor::setModel(QJSValue model)
// ToDo: handle rows added / removed
} 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 {
// might be null, or an array
qWarning() << Q_FUNC_INFO << "variant but not a QStringList" << var;
}
}
emit modelChanged();

View file

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

View file

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

View file

@ -51,7 +51,7 @@ class NavaidSearchModel : public QAbstractListModel
};
public:
NavaidSearchModel() { }
NavaidSearchModel(QObject* parent = nullptr);
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()
{
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
std::string filter="";
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/subsystem_mgr.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/package/Root.hxx>
#include <simgear/package/Catalog.hxx>
#include <simgear/package/Package.hxx>
#include <simgear/package/Install.hxx>
@ -74,6 +75,7 @@
#include "LauncherMainWindow.hxx"
#include "LaunchConfig.hxx"
#include "UnitsModel.hxx"
#include "PathListModel.hxx"
using namespace flightgear;
using namespace simgear::pkg;
@ -354,19 +356,22 @@ void initQSettings()
bool checkKeyboardModifiersForSettingFGRoot()
{
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();
if (mods & (Qt::AltModifier | Qt::ShiftModifier)) {
if (mods & (Qt::AltModifier | Qt::ShiftModifier))
#endif
{
qWarning() << "Alt/shift pressed during launch";
QSettings settings;
settings.setValue("fg-root", "!ask");
return true;
}
return false;
}
void restartTheApp()
{
QStringList fgArgs;
@ -403,7 +408,7 @@ void launcherSetSceneryPaths()
// positions
QSettings settings;
// 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());
}
@ -451,6 +456,15 @@ bool runLauncherDialog()
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
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 <Viewer/WindowBuilder.hxx>
static QString rootPathKey()
#include "QtLauncher.hxx"
QString SetupRootDialog::rootPathKey()
{
// return a settings key like fg-root-2018-3-0
return QString("fg-root-") + QString(FLIGHTGEAR_VERSION).replace('.', '-');
@ -99,7 +101,8 @@ SGPath SetupRootDialog::restoreUserSelectedRoot()
{
QSettings settings;
QString path = settings.value(rootPathKey()).toString();
if (path == "!ask") {
bool ask = flightgear::checkKeyboardModifiersForSettingFGRoot();
if (ask || (path == QStringLiteral("!ask"))) {
bool ok = runDialog(ManualChoiceRequested);
Q_ASSERT(ok);
// run dialog either exit()s or sets fg_root, so this
@ -108,7 +111,7 @@ SGPath SetupRootDialog::restoreUserSelectedRoot()
}
if (path.isEmpty()) {
return std::string(); // use the default path
return SGPath{}; // use the default 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
// switch to it. (This gives a more friendly upgrade experience).
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
@ -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)
{
// 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());
globals->set_fg_root(r);
QSettings settings;
settings.remove("fg-root"); // remove any setting
settings.remove(rootPathKey()); // remove any setting
accept();
}

View file

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

View file

@ -27,15 +27,21 @@ public:
void startInstall(pkg::InstallRef) override {}
void installProgress(pkg::InstallRef, unsigned int, unsigned int) override {}
void finishInstall(pkg::InstallRef, StatusCode ) override {}
void dataForThumbnail(const std::string& aThumbnailUrl,
size_t length, const uint8_t* bytes) override
size_t length, const uint8_t* bytes) override;
ThumbnailImageItem* owner;
};
void ThumbnailImageItem::ThumbnailPackageDelegate::dataForThumbnail(const std::string& aThumbnailUrl,
size_t length, const uint8_t* bytes)
{
if (aThumbnailUrl != owner->url().toString().toStdString()) {
return;
}
QImage img = QImage::fromData(QByteArray::fromRawData(reinterpret_cast<const char*>(bytes), length));
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
@ -48,9 +54,6 @@ public:
owner->setImage(img);
}
ThumbnailImageItem* owner;
};
ThumbnailImageItem::ThumbnailImageItem(QQuickItem* parent) :
QQuickItem(parent),
m_delegate(new ThumbnailPackageDelegate(this)),
@ -81,7 +84,7 @@ QSGNode *ThumbnailImageItem::updatePaintNode(QSGNode* oldNode, QQuickItem::Updat
textureNode->setOwnsTexture(true);
}
QSGTexture* tex = window()->createTextureFromImage(m_image);
QSGTexture* tex = window()->createTextureFromImage(m_image, QQuickWindow::TextureIsOpaque);
textureNode->setTexture(tex);
textureNode->markDirty(QSGBasicGeometryNode::DirtyMaterial);
m_imageDirty = false;
@ -123,7 +126,7 @@ void ThumbnailImageItem::setAircraftUri(QString uri)
pkg::Root* root = globals->packageRoot();
pkg::PackageRef package = root->getPackageById(packageId);
if (package) {
int variant = package->indexOfVariant(packageId);
auto variant = package->indexOfVariant(packageId);
const auto thumbnail = package->thumbnailForVariant(variant);
m_imageUrl = QUrl(QString::fromStdString(thumbnail.url));
if (m_imageUrl.isValid()) {

View file

@ -47,9 +47,8 @@ static int CALLBACK BrowseFolderCallback(
if (uMsg == BFFM_INITIALIZED) {
// set the initial directory now
WindowsFileDialog* dlg = reinterpret_cast<WindowsFileDialog*>(lpData);
std::string s = dlg->getDirectory().local8BitStr();
LPCTSTR path = s.c_str();
::SendMessage(hwnd, BFFM_SETSELECTION, true, (LPARAM) path);
const auto w = dlg->getDirectory().wstr();
::SendMessageW(hwnd, BFFM_SETSELECTIONW, true, (LPARAM) w.c_str());
}
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
#include <plib/pu.h>
#include <plib/fnt.h>
#include "fnt.h"
/**
* 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 " +
"know the folder(s) containing the aircraft data.")
showAddButton: true
onAdd: {
var newPath =_addOns.addAircraftPath();
if (newPath !== "") {
_addOns.aircraftPaths.push(newPath)
}
}
onAdd: _addOns.addAircraftPath();
}
Rectangle {
@ -127,21 +122,10 @@ Item {
model: _addOns.aircraftPaths
delegate: PathListDelegate {
width: aircraftPathsColumn.width
deletePromptText: qsTr("Remove the aircraft folder: '%1' from the list? (The folder contents will not be changed)").arg(modelData);
modelCount: _addOns.aircraftPaths.length
onPerformDelete: {
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;
}
deletePromptText: qsTr("Remove the aircraft folder: '%1' from the list? (The folder contents will not be changed)").arg(model.path);
modelCount: _addOns.aircraftPaths.count
onPerformDelete: _addOns.aircraftPaths.removePath(model.index)
onPerformMove: _addOns.aircraftPaths.swapIndices(model.index, newIndex);
}
}
@ -241,12 +225,7 @@ Item {
"to know the folders containing the scenery data. " +
"Adjust the order of the list to control which scenery is used in a region.");
showAddButton: true
onAdd: {
var newPath =_addOns.addSceneryPath();
if (newPath !== "") {
_addOns.sceneryPaths.push(newPath)
}
}
onAdd: _addOns.addSceneryPath();
}
Rectangle {
@ -267,21 +246,10 @@ Item {
delegate: PathListDelegate {
width: sceneryPathsColumn.width
deletePromptText: qsTr("Remove the scenery folder: '%1' from the list? (The folder contents will not be changed)").arg(modelData);
modelCount: _addOns.sceneryPaths.length
onPerformDelete: {
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;
}
deletePromptText: qsTr("Remove the scenery folder: '%1' from the list? (The folder contents will not be changed)").arg(model.path);
modelCount: _addOns.sceneryPaths.count
onPerformDelete: _addOns.sceneryPaths.removePath(model.index)
onPerformMove: _addOns.sceneryPaths.swapIndices(model.index, newIndex);
}
}
@ -304,14 +272,10 @@ Item {
var path = _addOns.installCustomScenery();
if (path !== "") {
// 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
_addOns.sceneryPaths.push(path);
_addOns.sceneryPaths.appendPath(path);
}
}
}

View file

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

View file

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

View file

@ -1,5 +1,6 @@
import QtQuick 2.4
import FlightGear.Launcher 1.0
import FlightGear 1.0
import "."
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
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
}
} // of root item

View file

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

View file

@ -17,6 +17,10 @@ FocusScope
}
}
Component.onCompleted: {
_launcher.browseAircraftModel.loadRatingsSettings();
}
Rectangle
{
id: tabBar
@ -24,6 +28,7 @@ FocusScope
width: parent.width
GridToggleButton {
id: gridModeToggle
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: Style.margin
@ -45,6 +50,16 @@ FocusScope
active: root.state == "installed"
}
TabButton {
id: favouritesButton
text: qsTr("Favourites")
onClicked: {
root.state = "favourites"
root.updateSelectionFromLauncher();
}
active: root.state == "favourites"
}
TabButton {
id: browseButton
text: qsTr("Browse")
@ -128,7 +143,8 @@ FocusScope
Loader {
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"
anchors {
@ -182,6 +198,10 @@ FocusScope
__model: _launcher.installedAircraftModel
__header: emptyHeader
}
PropertyChanges {
target: gridModeToggle; visible: true
}
},
State {
@ -191,6 +211,10 @@ FocusScope
__model: _launcher.searchAircraftModel
__header: emptyHeader
}
PropertyChanges {
target: gridModeToggle; visible: true
}
},
State {
@ -200,6 +224,10 @@ FocusScope
__model: _launcher.browseAircraftModel
__header: _addOns.showNoOfficialHangar ? noDefaultCatalogHeader : ratingsHeader
}
PropertyChanges {
target: gridModeToggle; visible: true
}
},
State {
@ -209,7 +237,26 @@ FocusScope
__model: _launcher.aircraftWithUpdatesModel
__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)

View file

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

View file

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

View file

@ -92,10 +92,9 @@ Rectangle {
screenPos = _launcher.mapToGlobal(title, Qt.point(0, 0))
}
popupFrame.x = screenPos.x;
popupFrame.y = screenPos.y;
popupFrame.show()
tracker.window = popupFrame
var pop = popup.createObject(root, {x:screenPos.x, y:screenPos.y })
tracker.window = pop;
pop.show();
}
}
@ -108,6 +107,9 @@ Rectangle {
id: tracker
}
Component {
id: popup
Window {
id: popupFrame
@ -140,7 +142,7 @@ Rectangle {
text: modelData
// allow override the size in case the title size is enormous
font.pixelSize: (popupFontPixelSize > 0) ? popupFontPixelSize : title.font.pixelSize
font.pixelSize: (root.popupFontPixelSize > 0) ? root.popupFontPixelSize : title.font.pixelSize
color: choiceArea.containsMouse ? Style.themeColor : Style.baseTextColor
anchors {
@ -155,7 +157,7 @@ Rectangle {
hoverEnabled: true
anchors.fill: parent
onClicked: {
popupFrame.hide()
popupFrame.close()
root.selected(model.index)
}
}
@ -163,4 +165,5 @@ Rectangle {
} // of Repeater
}
} // of popup frame
} // of popup component
}

View file

@ -26,12 +26,19 @@ Item {
}
}
function isDisabled()
{
return (model.status !== CatalogListModel.Ok) ||
(enableCheckbox.checked === false);
}
Item
{
anchors.top: divider.bottom
height: catalogTextColumn.childrenRect.height + Style.margin * 2
width: parent.width
Column {
id: catalogTextColumn
@ -42,11 +49,28 @@ Item {
anchors.rightMargin: Style.margin
spacing: Style.margin
Row {
spacing: Style.margin
height: headingText.height
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: parent.width
width: catalogTextColumn.width - enableCheckbox.width
text: model.name
font.strikeout: delegateRoot.isDisabled();
}
}
StyledText {

View file

@ -1,4 +1,5 @@
import QtQuick 2.4
import "."
Item {
property bool checked: false
@ -11,7 +12,7 @@ Item {
id: checkBox
width: 18
height: 18
border.color: mouseArea.containsMouse ? "#68A6E1" : "#9f9f9f"
border.color: mouseArea.containsMouse ? Style.frameColor : Style.inactiveThemeColor
border.width: 1
anchors.left: parent.left
anchors.leftMargin: 8
@ -22,7 +23,7 @@ Item {
height: 12
anchors.centerIn: parent
id: checkMark
color: "#9f9f9f"
color: Style.themeColor
visible: checked
}
}
@ -41,5 +42,6 @@ Item {
onClicked: {
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
enabled: root.enabled
onClicked: {
var screenPos = _launcher.mapToGlobal(button, Qt.point(-popupFrame.width, 0))
popupFrame.x = screenPos.x;
popupFrame.y = screenPos.y;
popupFrame.visible = true
tracker.window = popupFrame
var pop = popup.createObject(root)
var screenPos = _launcher.mapToGlobal(button, Qt.point(-pop.width, 0))
pop.y = screenPos.y;
pop.x = screenPos.x;
tracker.window = pop;
pop.show();
}
}
}
@ -50,13 +51,15 @@ Item {
id: tracker
}
Component {
id: popup
Window {
id: popupFrame
flags: Qt.Popup
height: choicesColumn.childrenRect.height + Style.margin * 2
width: choicesColumn.childrenRect.width + Style.margin * 2
visible: false
color: "white"
Rectangle {
@ -89,8 +92,8 @@ Item {
width: popupFrame.width // full width of the popup
height: parent.height
onClicked: {
popupFrame.visible = false
root.selected(model.index);
popupFrame.close()
}
}
} // of Text delegate
@ -98,3 +101,4 @@ Item {
} // text column
} // 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
// order of this model sets the order of buttons in the sidebar
ListModel {
id: pagesModel
id: startupPagesModel
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" }
@ -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:
{
_launcher.minimumWindowSize = Qt.size(Style.strutSize * 12, sidebar.minimumHeight);
@ -35,7 +47,7 @@ Item {
Connections {
target: _location
onSkipFromArgsChanged: pagesModel.setProperty(2, "buttonDisabled", _location.skipFromArgs)
onSkipFromArgsChanged: startupPagesModel.setProperty(2, "buttonDisabled", _location.skipFromArgs)
}
state: "loader"
@ -82,7 +94,7 @@ Item {
id: sidebar
height: parent.height
z: 1
pagesModel: pagesModel
pagesModel: _launcher.inAppMode ? inAppPagesModel : startupPagesModel
selectedPage: 0 // open on the summary page
onShowMenu: menu.show();
@ -127,7 +139,7 @@ Item {
function selectPage(index)
{
sidebar.setSelectedPage(index);
var page = pagesModel.get(index);
var page = sidebar.pagesModel.get(index);
pageLoader.source = page.pageSource
root.state = page.state
}

View file

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

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