1 /*
2 Copyright (C) 2003-2006 Andrey Nazarov
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 
21 #include "ui_local.h"
22 
23 /*
24 =======================================================================
25 
26 DEMOS MENU
27 
28 =======================================================================
29 */
30 
31 #define MAX_MENU_DEMOS	1024
32 
33 #define FFILE_UP	1
34 #define FFILE_FOLDER	2
35 #define FFILE_DEMO	3
36 
37 #define ID_LIST			105
38 
39 typedef struct m_demos_s {
40 	menuFrameWork_t		menu;
41 	menuList_t		list;
42 	menuList_t		playerList;
43 	menuStatic_t	banner;
44 
45 	int				count;
46 	char		*names[MAX_MENU_DEMOS];
47 	int			types[MAX_MENU_DEMOS];
48 	char		*playerNames[MAX_DEMOINFO_CLIENTS+1];
49 
50 	// Demo file info
51 	demoInfo_t	demo;
52 } m_demos_t;
53 
54 static m_demos_t	m_demos;
55 static char			m_demos_browse[MAX_QPATH];
56 static int			m_demos_selection;
57 
Demos_FreeInfo(void)58 static void Demos_FreeInfo( void ) {
59 	memset( &m_demos.demo, 0, sizeof( m_demos.demo ) );
60 
61 	m_demos.playerList.generic.flags |= QMF_HIDDEN;
62 }
63 
Demos_LoadInfo(int index)64 static void Demos_LoadInfo( int index ) {
65 	char buffer[MAX_QPATH];
66 	int i, numNames, localPlayerNum;
67 
68 	if( m_demos.types[index] != FFILE_DEMO ) {
69 		m_demos.playerList.generic.flags |= QMF_HIDDEN;
70 		m_demos.menu.statusbar = NULL;
71 		return;
72 	}
73 
74 
75 	Com_sprintf( buffer, sizeof( buffer ), "%s/%s", m_demos_browse + 1, m_demos.names[index] );
76 
77 	client.GetDemoInfo( buffer, &m_demos.demo );
78 
79 	if( !m_demos.demo.mapname[0] ) {
80 	    m_demos.menu.statusbar = "Couldn't read demo info";
81 		m_demos.playerList.generic.flags |= QMF_HIDDEN;
82 		return;
83 	}
84 
85 
86 	localPlayerNum = 0;
87 
88 	numNames = 0;
89 	for( i = 0; i < MAX_DEMOINFO_CLIENTS; i++ ) {
90 		if( !m_demos.demo.clients[i][0] ) {
91 			continue;
92 		}
93 
94 		if( i == m_demos.demo.clientNum ) {
95 			localPlayerNum = numNames;
96 		}
97 
98 		// ( m_demos.demo.mvd || i == m_demos.demo.clientNum ) ? colorYellow : NULL
99 
100 		m_demos.playerNames[numNames++] = m_demos.demo.clients[i];
101 	}
102 
103 	if( numNames ) {
104 		m_demos.playerList.generic.flags &= ~QMF_HIDDEN;
105 	} else {
106 		m_demos.playerList.generic.flags |= QMF_HIDDEN;
107 	}
108 
109 	m_demos.playerNames[numNames] = NULL;
110 
111     if( m_demos.demo.mvd ) {
112     	m_demos.menu.statusbar = "Press Enter to play";
113     } else {
114     	m_demos.menu.statusbar = "Press Enter to play, "
115             "hold Shift to play on server";
116     }
117 
118 	MenuList_Init( &m_demos.playerList );
119 	MenuList_SetValue( &m_demos.playerList, localPlayerNum );
120 }
121 
Demos_BuildName(const char * base,const char * extenstion,int fileSize,int fileType)122 static char *Demos_BuildName( const char *base, const char *extenstion, int fileSize, int fileType ) {
123 	char *type, *sizeString, *s;
124 
125 	switch( fileType ) {
126 	case FFILE_UP:
127 		sizeString = "";
128 		type = " UP";
129 		break;
130 	case FFILE_FOLDER:
131 		sizeString = "";
132 		type = "DIR";
133 		break;
134 	case FFILE_DEMO:
135 		sizeString = va( "%4i", fileSize );
136 		if( !strncmp( extenstion, ".mvd2", 5 ) ) {
137 			type = "MVD";
138 		} else {
139 			type = "DM2";
140 		}
141 		break;
142 	default:
143 		type = "";
144 		sizeString = "";
145 		break;
146 	}
147 
148 	s = UI_FormatColumns( 3, base, sizeString, type );
149 
150 	return s;
151 
152 }
153 
Demos_Scan(const char * path,const char * extenstion)154 static void Demos_Scan( const char *path, const char *extenstion ) {
155 	int numFiles;
156 	char **list;
157 	int i;
158 	fsFileInfo_t *info;
159 
160 	list = fs.ListFiles( path, extenstion, FS_PATH_GAME|FS_SEARCH_EXTRAINFO, &numFiles );
161 	if( !list ) {
162 		return;
163 	}
164 
165 	for( i = 0; i < numFiles; i++ ) {
166 		if( m_demos.count == MAX_MENU_DEMOS - 1 ) {
167 			break;
168 		}
169 
170 		info = ( fsFileInfo_t * )( list[i] + strlen( list[i] ) + 1 );
171 
172 		m_demos.names[m_demos.count] = Demos_BuildName( list[i], extenstion, info->fileSize >> 10, FFILE_DEMO );
173 		m_demos.types[m_demos.count] = FFILE_DEMO;
174 		m_demos.count++;
175 	}
176 
177 	fs.FreeFileList( list );
178 
179 }
180 
Demos_BuildList(const char * path)181 static void Demos_BuildList( const char *path ) {
182 	int numFiles;
183 	char **list;
184 	int pos;
185 	int i;
186 
187 	if( *path == '/' ) {
188 		path++;
189 	}
190 
191 	if( *path ) {
192 		m_demos.names[m_demos.count] = Demos_BuildName( "..", NULL, -1, FFILE_UP );
193 		m_demos.types[m_demos.count] = FFILE_UP;
194 		m_demos.count++;
195 	}
196 
197 	// List directories first
198 	list = fs.ListFiles( path, NULL, FS_PATH_GAME|FS_SEARCHDIRS_ONLY, &numFiles );
199 	if( list ) {
200 		for( i = 0; i < numFiles; i++ ) {
201 			if( m_demos.count == MAX_MENU_DEMOS - 1 ) {
202 				break;
203 			}
204 			m_demos.names[m_demos.count] = Demos_BuildName( list[i], NULL, -1, FFILE_FOLDER );
205 			m_demos.types[m_demos.count] = FFILE_FOLDER;
206 			m_demos.count++;
207 		}
208 
209 		fs.FreeFileList( list );
210 	}
211 
212 	pos = m_demos.count;
213 
214 	Demos_Scan( path, ".dm2" );
215 	Demos_Scan( path, ".dm2.gz" );
216 	Demos_Scan( path, ".dm_35" );
217 	Demos_Scan( path, ".dm_35.gz" );
218 	Demos_Scan( path, ".dm_36" );
219 	Demos_Scan( path, ".dm_36.gz" );
220 	Demos_Scan( path, ".mvd2" );
221 	Demos_Scan( path, ".mvd2.gz" );
222 
223 	// Sort demos, not directories
224 	if( m_demos.count - pos > 1 ) {
225 		qsort( m_demos.names + pos, m_demos.count - pos, sizeof( m_demos.names[0] ), SortStrcmp );
226 	}
227 
228 	m_demos.names[m_demos.count] = NULL;
229 }
230 
Demos_Free(void)231 static void Demos_Free( void ) {
232 	int i;
233 
234 	Demos_FreeInfo();
235 
236 	for( i = 0; i < m_demos.count; i++ ) {
237 		com.Free( m_demos.names[i] );
238 		m_demos.names[i] = NULL;
239 		m_demos.types[i] = 0;
240 	}
241 
242 	m_demos.count = 0;
243 	m_demos.list.curvalue = 0;
244 }
245 
Demos_LeaveDirectory(void)246 static void Demos_LeaveDirectory( void ) {
247 	char buffer[MAX_QPATH];
248 	char *s;
249 	int i;
250 
251 	s = strrchr( m_demos_browse, '/' );
252 	if( s ) {
253 		*s = 0;
254 		strcpy( buffer, s + 1 );
255 	} else {
256 		buffer[0] = 0;
257 	}
258 
259 	// rebuild list
260 	Demos_Free();
261 	Demos_BuildList( m_demos_browse );
262 	MenuList_Init( &m_demos.list );
263 
264 	if( s == m_demos_browse ) {
265 		m_demos_browse[0] = '/';
266 		m_demos_browse[1] = 0;
267 	}
268 
269 	// move cursor to the previous directory
270 	if( buffer[0] ) {
271 		for( i = 0; i < m_demos.count; i++ ) {
272 			if( !strcmp( m_demos.names[i], buffer ) ) {
273 				MenuList_SetValue( &m_demos.list, i );
274 				return;
275 			}
276 		}
277 	}
278 
279 	MenuList_SetValue( &m_demos.list, 0 );
280 
281 }
282 
Demos_Action(void)283 static int Demos_Action( void ) {
284 	char buffer[MAX_QPATH];
285 	int length, baseLength;
286     char *c;
287 
288 	switch( m_demos.types[m_demos.list.curvalue] ) {
289 	case FFILE_UP:
290 		Demos_LeaveDirectory();
291 		return QMS_OUT;
292 
293 	case FFILE_FOLDER:
294 		baseLength = strlen( m_demos_browse );
295 		length = strlen( m_demos.names[m_demos.list.curvalue] );
296 		if( baseLength + length > sizeof( m_demos_browse ) - 2 ) {
297 			return QMS_BEEP;
298 		}
299 		if( m_demos_browse[ baseLength - 1 ] != '/' ) {
300 			m_demos_browse[ baseLength++ ] = '/';
301 		}
302 
303 		strcpy( m_demos_browse + baseLength, m_demos.names[m_demos.list.curvalue] );
304 
305 		// rebuild list
306 		Demos_Free();
307 		Demos_BuildList( m_demos_browse );
308 		MenuList_Init( &m_demos.list );
309 		return QMS_IN;
310 
311 	case FFILE_DEMO:
312 	    if( !m_demos.demo.mapname[0] ) {
313             return QMS_BEEP;
314         }
315         c = m_demos.demo.mvd || keys.IsDown( K_SHIFT ) ? "mvdplay" : "demo";
316 		Com_sprintf( buffer, sizeof( buffer ), "%s \"%s/%s\"\n", c,
317 			m_demos_browse, m_demos.names[m_demos.list.curvalue] );
318 		cmd.ExecuteText( EXEC_APPEND, buffer );
319 		//UI_ForceMenuOff();
320 		return QMS_SILENT;
321 	}
322 
323 	return QMS_NOTHANDLED;
324 }
325 
Demos_MenuCallback(int id,int msg,int param)326 static int Demos_MenuCallback( int id, int msg, int param ) {
327 	switch( msg ) {
328 	case QM_ACTIVATE:
329 		switch( id ) {
330 		case ID_LIST:
331 			return Demos_Action();
332 		}
333 		return QMS_IN;
334 
335 	case QM_CHANGE:
336 		switch( id ) {
337 		case ID_LIST:
338 			Demos_LoadInfo( m_demos.list.curvalue );
339 			break;
340 		default:
341 			break;
342 		}
343 		return QMS_MOVE;
344 
345 	case QM_KEY:
346 		switch( id ) {
347 		case ID_LIST:
348 			switch( param ) {
349 			case K_BACKSPACE:
350 				Demos_LeaveDirectory();
351 				return QMS_OUT;
352 			default:
353 				break;
354 			}
355 		default:
356 			break;
357 		}
358 		break;
359 
360 	case QM_DESTROY:
361 		m_demos_selection = m_demos.list.curvalue; // save previous position
362 		Demos_Free();
363 		break;
364 
365 	default:
366 		break;
367 	}
368 
369 	return QMS_NOTHANDLED;
370 }
371 
Demos_MenuInit(void)372 static void Demos_MenuInit( void ) {
373 	int w1, w2;
374 
375 	memset( &m_demos, 0, sizeof( m_demos ) );
376 
377 	m_demos.menu.callback = Demos_MenuCallback;
378 
379 	// Point to a nice location at startup
380 	if( !m_demos_browse[0] ) {
381 		strcpy( m_demos_browse, "/demos" );
382 	}
383 
384 	Demos_BuildList( m_demos_browse );
385 
386 	w1 = ( uis.glconfig.vidWidth - 30 ) * 0.8f;
387 	w2 = ( uis.glconfig.vidWidth - 30 ) * 0.2f;
388 
389 	m_demos.list.generic.type	= MTYPE_LIST;
390 	m_demos.list.generic.id		= ID_LIST;
391 	m_demos.list.generic.flags  = QMF_HASFOCUS;
392 	m_demos.list.generic.x		= 10;
393 	m_demos.list.generic.y		= 32;
394 	m_demos.list.generic.width	= 0;
395 	m_demos.list.generic.height	= uis.glconfig.vidHeight - 64;
396 	m_demos.list.generic.name	= NULL;
397 	m_demos.list.itemnames		= ( const char ** )m_demos.names;
398 	m_demos.list.drawNames		= qtrue;
399 	m_demos.list.numcolumns	= 3;
400 
401 	m_demos.list.columns[0].width = w1 - 90;
402 	m_demos.list.columns[0].name = m_demos_browse;
403 	m_demos.list.columns[0].uiFlags = UI_LEFT;
404 
405 	m_demos.list.columns[1].width = 40;
406 	m_demos.list.columns[1].name = "KB";
407 	m_demos.list.columns[1].uiFlags = UI_RIGHT;
408 
409 	m_demos.list.columns[2].width = 50;
410 	m_demos.list.columns[2].name = "Type";
411 	m_demos.list.columns[2].uiFlags = UI_CENTER;
412 
413 	m_demos.playerList.generic.type		= MTYPE_LIST;
414 	m_demos.playerList.generic.flags	= QMF_HIDDEN|QMF_DISABLED;
415 	m_demos.playerList.generic.x		= w1 + 20;
416 	m_demos.playerList.generic.y		= 32;
417 	m_demos.playerList.generic.height	= uis.glconfig.vidHeight - 64;
418 	m_demos.playerList.itemnames		= ( const char ** )m_demos.playerNames;
419 	m_demos.playerList.mlFlags			= MLF_HIDE_SCROLLBAR_EMPTY;
420 	m_demos.playerList.numcolumns		= 1;
421 	m_demos.playerList.drawNames		= qtrue;
422 
423 	m_demos.playerList.columns[0].width = w2;
424 	m_demos.playerList.columns[0].name = "Players";
425 	m_demos.playerList.columns[0].uiFlags = UI_CENTER;
426 
427 	UI_SetupDefaultBanner( &m_demos.banner, "Demos" );
428 
429 	Menu_AddItem( &m_demos.menu, (void *)&m_demos.list );
430 	Menu_AddItem( &m_demos.menu, (void *)&m_demos.playerList );
431 	Menu_AddItem( &m_demos.menu, (void *)&m_demos.banner );
432 
433 	// move cursor to previous position
434 	MenuList_SetValue( &m_demos.list, m_demos_selection );
435 }
436 
M_Menu_Demos_f(void)437 void M_Menu_Demos_f( void ) {
438 	Demos_MenuInit();
439 	UI_PushMenu( &m_demos.menu );
440 }
441