xwords/xwords4/linux/curgamlistwin.c

296 lines
8.3 KiB
C
Raw Normal View History

/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
/*
* Copyright 2020 by Eric House (xwords@eehouse.org). All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <ncurses.h>
#include <glib.h>
#include <sys/types.h>
#include <unistd.h>
#include "curgamlistwin.h"
2020-01-31 22:29:36 +01:00
#include "linuxmain.h"
#include "device.h"
#include "strutils.h"
#include "linuxutl.h"
struct CursGameList {
WINDOW* window;
2020-01-31 22:29:36 +01:00
LaunchParams* params;
int width, height;
int curSel;
int yOffset;
GSList* games;
int pid;
};
static void adjustCurSel( CursGameList* cgl );
CursGameList*
2020-01-31 22:29:36 +01:00
cgl_init( LaunchParams* params, int width, int height )
{
CursGameList* cgl = g_malloc0( sizeof( *cgl ) );
2020-01-31 22:29:36 +01:00
cgl->params = params;
cgl->window = newwin( height, width, 0, 0 );
XP_LOGF( "%s(): made window with height=%d, width=%d", __func__, height, width );
cgl->width = width;
cgl->height = height;
cgl->pid = getpid();
return cgl;
}
void
cgl_destroy( CursGameList* cgl )
{
if ( !!cgl->window ) {
g_slist_free_full( cgl->games, g_free );
delwin( cgl->window );
} else {
XP_LOGFF( "no window??" );
}
g_free( cgl );
}
void
cgl_resized( CursGameList* cgl, int width, int height )
{
wresize( cgl->window, height, width );
cgl->width = width;
cgl->height = height;
cgl_draw( cgl );
}
static void
addOne( CursGameList* cgl, sqlite3_int64 rowid )
{
GameInfo gib;
if ( gdb_getGameInfoForRow( cgl->params->pDb, rowid, &gib ) ) {
GameInfo* gibp = g_malloc( sizeof(*gibp) );
*gibp = gib;
cgl->games = g_slist_append( cgl->games, gibp );
}
}
/* Load from the DB */
void
cgl_refresh( CursGameList* cgl )
{
g_slist_free_full( cgl->games, g_free );
cgl->games = NULL;
2020-01-31 22:29:36 +01:00
sqlite3* pDb = cgl->params->pDb;
GSList* games = gdb_listGames( pDb );
for ( GSList* iter = games; !!iter; iter = iter->next ) {
sqlite3_int64* rowid = (sqlite3_int64*)iter->data;
addOne( cgl, *rowid );
}
cgl_draw( cgl );
}
static GSList*
findFor( CursGameList* cgl, sqlite3_int64 rowid )
{
GSList* result = NULL;
for ( GSList* iter = cgl->games; !!iter && !result; iter = iter->next ) {
GameInfo* gib = (GameInfo*)iter->data;
if ( gib->rowid == rowid ) {
result = iter;
}
}
return result;
}
void
cgl_refreshOne( CursGameList* cgl, sqlite3_int64 rowid, bool select )
{
// Update the info. In place if it exists, otherwise creating a new list
// elem
GameInfo gib;
if ( gdb_getGameInfoForRow( cgl->params->pDb, rowid, &gib ) ) {
GameInfo* found;
GSList* elem = findFor( cgl, rowid );
if ( !!elem ) {
found = (GameInfo*)elem->data;
*found = gib;
} else {
found = g_malloc( sizeof(*found) );
*found = gib;
cgl->games = g_slist_append( cgl->games, found );
}
if ( select ) {
cgl->curSel = g_slist_index( cgl->games, found );
}
adjustCurSel( cgl );
}
}
void
cgl_remove( CursGameList* cgl, sqlite3_int64 rowid )
{
GSList* elem = findFor( cgl, rowid );
if ( !!elem ) {
g_free( elem->data );
cgl->games = g_slist_delete_link( cgl->games, elem );
}
adjustCurSel( cgl );
}
void
cgl_moveSel( CursGameList* cgl, bool down )
{
int nGames = g_slist_length( cgl->games );
cgl->curSel += nGames + (down ? 1 : -1);
cgl->curSel %= nGames;
adjustCurSel( cgl );
}
static void
adjustCurSel( CursGameList* cgl )
{
int nGames = g_slist_length( cgl->games );
2020-01-29 19:59:57 +01:00
XP_LOGF( "%s() start: curSel: %d; yOffset: %d; nGames: %d", __func__,
cgl->curSel, cgl->yOffset, nGames );
if ( cgl->curSel >= nGames ) {
cgl->curSel = nGames - 1;
}
/* Now adjust yOffset */
int nVisRows = cgl->height - 2; /* 1 for the title and header rows */
2020-01-29 19:59:57 +01:00
if ( nGames < nVisRows ) {
cgl->yOffset = 0;
} else if ( cgl->curSel - cgl->yOffset >= nVisRows ) {
cgl->yOffset = cgl->curSel - nVisRows + 1;
} else {
while ( cgl->curSel < cgl->yOffset ) {
--cgl->yOffset;
}
}
XP_LOGF( "%s() end: curSel: %d; yOffset: %d", __func__, cgl->curSel, cgl->yOffset );
cgl_draw( cgl );
}
void
cgl_draw( CursGameList* cgl )
{
WINDOW* win = cgl->window;
werase( win );
const int nGames = g_slist_length( cgl->games );
/* Draw '+' at far right if scrollable */
int nBelow = nGames - (cgl->height-2) - cgl->yOffset;
XP_LOGF( "%s(): yOffset: %d; nBelow: %d", __func__, cgl->yOffset, nBelow );
if ( 0 < nBelow ) {
mvwaddstr( win, cgl->height-2, cgl->width - 1, "+" );
}
if ( 0 < cgl->yOffset ) {
mvwaddstr( win, 0, cgl->width-1, "+" );
}
const char* cols[] = {"#", "RowID", "Lang", "Scores", "GameID", "Role",
"nTot", "nMiss", "Seed", "#Mv", "Turn", "nPend", "DupTimer" };
int nShown = nGames <= cgl->height - 2 ? nGames : cgl->height - 2;
char* data[nShown + 1][VSIZE(cols)];
for ( int ii = 0; ii < VSIZE(cols); ++ii ) {
data[0][ii] = g_strdup(cols[ii]);
}
int line = 1;
for ( int ii = 0; ii < nShown; ++ii ) {
const GameInfo* gi = g_slist_nth_data( cgl->games, ii + cgl->yOffset );
int col = 0;
data[line][col++] = g_strdup_printf( "%d", ii + cgl->yOffset + 1 ); /* 1-based */
data[line][col++] = g_strdup_printf( "%05lld", gi->rowid );
data[line][col++] = g_strdup( gi->isoCode );
data[line][col++] = g_strdup( gi->scores );
data[line][col++] = g_strdup_printf( "%d", gi->gameID );
data[line][col++] = g_strdup_printf( "%d", gi->role );
data[line][col++] = g_strdup_printf( "%d", gi->nTotal );
data[line][col++] = g_strdup_printf( "%d", countBits(gi->nMissing) );
data[line][col++] = g_strdup_printf( "%d", gi->seed );
data[line][col++] = g_strdup_printf( "%d", gi->nMoves );
data[line][col++] = g_strdup_printf( "%d", gi->turn );
data[line][col++] = g_strdup_printf( "%d", gi->nPending );
gchar buf[64];
formatSeconds( gi->dupTimerExpires, buf, VSIZE(buf) );
data[line][col++] = g_strdup( buf );
XP_ASSERT( col == VSIZE(data[line]) );
++line;
}
int maxlen = 0;
int offset = 0;
for ( int col = 0; col < VSIZE(data[0]); ++col ) {
for ( int line = 0; line < VSIZE(data); ++line ) {
char* str = data[line][col];
int len = strlen(str);
if ( maxlen < len ) {
maxlen = len;
}
bool highlight = cgl->yOffset + line - 1 == cgl->curSel;
if ( highlight ) {
wstandout( win );
}
mvwaddstr( win, line + 1, offset, str );
if ( highlight ) {
wstandend( win );
}
g_free( str );
}
offset += maxlen + 2;
maxlen = 0;
}
2020-01-31 22:29:36 +01:00
XP_U32 relayID = linux_getDevIDRelay( cgl->params );
char buf[cgl->width + 1];
MQTTDevID devID;
dvc_getMQTTDevID( cgl->params->dutil, NULL_XWE, &devID );
XP_UCHAR didBuf[32];
snprintf( buf, VSIZE(buf), "pid: %d; nGames: %d, relayid: %d, mqttid: %s",
cgl->pid, nGames, relayID, formatMQTTDevID( &devID, didBuf, VSIZE(didBuf) ) );
mvwaddstr( win, 0, 0, buf );
wrefresh( win );
}
const GameInfo*
cgl_getSel( CursGameList* cgl )
{
return g_slist_nth_data( cgl->games, cgl->curSel );
}
void
cgl_setSel( CursGameList* cgl, int sel )
{
if ( sel < 0 ) {
sel = XP_RANDOM() % g_slist_length( cgl->games );
}
cgl->curSel = sel;
adjustCurSel( cgl );
}
int
cgl_getNGames( CursGameList* cgl )
{
return g_slist_length( cgl->games );
}