Hedgeroid: Reworked player and room lists, added menus, added playername query
authorMedo <smaxein@googlemail.com>
Mon, 23 Jul 2012 00:17:06 +0200
changeset 7352 641f11cdd319
parent 7349 12fdfd2038d4
child 7355 5673e95ef647
Hedgeroid: Reworked player and room lists, added menus, added playername query Still work in progress...
project_files/Android-build/SDL-android-project/AndroidManifest.xml
project_files/Android-build/SDL-android-project/res/layout-large/listview_room.xml
project_files/Android-build/SDL-android-project/res/layout/listview_room.xml
project_files/Android-build/SDL-android-project/res/menu/lobby_options.xml
project_files/Android-build/SDL-android-project/res/menu/lobby_playerlist_context.xml
project_files/Android-build/SDL-android-project/res/menu/lobby_roomlist_options.xml
project_files/Android-build/SDL-android-project/res/values/strings.xml
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyChatFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/MessageLog.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Netconn.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/ObservableLinkedHashMap.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Player.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerList.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerListAdapter.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerlistFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Room.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomList.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomListAdapter.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomlistFragment.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Signal.java
--- a/project_files/Android-build/SDL-android-project/AndroidManifest.xml	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/AndroidManifest.xml	Mon Jul 23 00:17:06 2012 +0200
@@ -69,7 +69,10 @@
         </activity>
         <activity
             android:name=".netplay.LobbyActivity"
-            android:label="@string/title_activity_lobby" >
+            android:label="@string/title_activity_lobby"
+            android:screenOrientation="landscape"
+
+            android:windowSoftInputMode="adjustResize" >
         </activity>
     </application>
 </manifest>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/layout-large/listview_room.xml	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/layout-large/listview_room.xml	Mon Jul 23 00:17:06 2012 +0200
@@ -3,7 +3,8 @@
     xmlns:tools="http://schemas.android.com/tools"
 	android:layout_width="fill_parent"
 	android:layout_height="wrap_content" 
-	android:minHeight="20dp" >
+	android:paddingTop="4dp"
+	android:paddingBottom="4dp">
 	
 <TextView
     android:id="@+id/roomname"
--- a/project_files/Android-build/SDL-android-project/res/layout/listview_room.xml	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/layout/listview_room.xml	Mon Jul 23 00:17:06 2012 +0200
@@ -2,7 +2,9 @@
 <TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="fill_parent"
     android:layout_height="wrap_content"
-    android:mode="twoLine">
+    android:mode="twoLine"
+    android:paddingTop="4dp"
+	android:paddingBottom="4dp">
 
     <TextView
         android:id="@android:id/text1"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/menu/lobby_options.xml	Mon Jul 23 00:17:06 2012 +0200
@@ -0,0 +1,6 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:id="@+id/room_create"
+        android:title="@string/lobby_roomlistmenu_create"
+        android:showAsAction="ifRoom" />
+</menu>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/menu/lobby_playerlist_context.xml	Mon Jul 23 00:17:06 2012 +0200
@@ -0,0 +1,10 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:id="@+id/player_info"
+        android:title="@string/lobby_playerlist_contextmenu_info">
+    </item>
+    <item
+        android:id="@+id/player_follow"
+        android:title="@string/lobby_playerlist_contextmenu_follow">
+    </item>
+</menu>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/res/menu/lobby_roomlist_options.xml	Mon Jul 23 00:17:06 2012 +0200
@@ -0,0 +1,6 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:id="@+id/roomlist_refresh"
+        android:title="@string/lobby_roomlistmenu_refresh"
+        android:showAsAction="ifRoom" />
+</menu>
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/res/values/strings.xml	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/res/values/strings.xml	Mon Jul 23 00:17:06 2012 +0200
@@ -73,7 +73,7 @@
     <string name="roomlist_header_owner">Owner</string>
     <string name="roomlist_header_map">Map</string>
     <string name="roomlist_header_scheme">Rules</string>
-    <string name="roomlist_header_weapons">Ammo</string>
+    <string name="roomlist_header_weapons">Weapons</string>
     
     <string name="roomlist_owner">by %1$s</string>
     <string name="roomlist_map">Map: %1$s</string>
@@ -86,4 +86,16 @@
     <string name="log_player_join">%1$s has joined.</string>
     <string name="log_player_leave">%1$s has left.</string>
     <string name="log_player_leave_with_msg">%1$s has left (%2$s).</string>
+    
+    <!-- Start netgame dialog -->
+    <string name="start_netgame_dialog_title">Connect</string>
+    <string name="start_netgame_dialog_message">Please select a username.</string>
+    <string name="start_netgame_dialog_playername_hint">Enter a username here</string>
+    
+    <string name="lobby_playerlist_contextmenu_info">Info (shown in chat)</string>
+    <string name="lobby_playerlist_contextmenu_follow">Follow</string>
+    <string name="lobby_roomlistmenu_create">Create room</string>
+    <string name="lobby_roomlistmenu_refresh">Refresh</string>
+    
+    <string name="not_implemented_yet">Sorry, not implemented yet. :(</string>
 </resources>
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java	Mon Jul 23 00:17:06 2012 +0200
@@ -31,14 +31,23 @@
 import android.app.ProgressDialog;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
 import android.os.Bundle;
 import android.support.v4.app.FragmentActivity;
+import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
+import android.widget.EditText;
 import android.widget.Toast;
 
 public class MainActivity extends FragmentActivity {
+	private static final int DIALOG_NO_SDCARD = 0;
+	private static final int DIALOG_START_NETGAME = 1;
+	
+	private static final String PREF_PLAYERNAME = "playerName";
+	
 	private Button downloader, startGame;
 	private ProgressDialog assetsDialog;
 
@@ -54,12 +63,12 @@
 		startGame.setOnClickListener(startGameClicker);
 		joinLobby.setOnClickListener(new OnClickListener() {
 			public void onClick(View v) {
-				startActivity(new Intent(getApplicationContext(), LobbyActivity.class));
+				showDialog(DIALOG_START_NETGAME);
 			}
 		});
 
 		if(!Utils.isDataPathAvailable()){
-			showDialog(0);
+			showDialog(DIALOG_NO_SDCARD);
 		} else {
 			String existingVersion = "";
 			try {
@@ -83,6 +92,17 @@
 	}
 
 	public Dialog onCreateDialog(int id, Bundle args){
+		switch(id) {
+		case DIALOG_NO_SDCARD:
+			return createNoSdcardDialog();
+		case DIALOG_START_NETGAME:
+			return createStartNetgameDialog();
+		default:
+			throw new IndexOutOfBoundsException();
+		}
+	}
+
+	private Dialog createNoSdcardDialog() {
 		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 		builder.setTitle(R.string.sdcard_not_mounted_title);
 		builder.setMessage(R.string.sdcard_not_mounted);
@@ -95,6 +115,43 @@
 		return builder.create();
 	}
 
+	private Dialog createStartNetgameDialog() {
+		final SharedPreferences prefs = getPreferences(MODE_PRIVATE);
+		final String playerName = prefs.getString(PREF_PLAYERNAME, "Player");
+		final EditText editText = new EditText(this);
+		final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+		
+		editText.setText(playerName);
+		editText.setHint(R.string.start_netgame_dialog_playername_hint);
+		editText.setId(android.R.id.text1);
+
+		builder.setTitle(R.string.start_netgame_dialog_title);
+		builder.setMessage(R.string.start_netgame_dialog_message);
+		builder.setView(editText);
+		builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+			public void onClick(DialogInterface dialog, int which) {
+				editText.setText(playerName);
+			}
+		});
+		builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+			public void onClick(DialogInterface dialog, int which) {
+				String playerName = editText.getText().toString();
+				if(playerName.length() > 0) {
+					Editor edit = prefs.edit();
+					edit.putString(PREF_PLAYERNAME, playerName);
+					edit.commit();
+					
+					// TODO actually use that name
+					Intent netplayIntent = new Intent(getApplicationContext(), LobbyActivity.class);
+					netplayIntent.putExtra("playerName", playerName);
+					startActivity(netplayIntent);
+				}
+			}
+		});
+
+		return builder.create();
+	}
+	
 	public void onAssetsDownloaded(boolean result){
 		if(!result){
 			Toast.makeText(this, R.string.download_failed, Toast.LENGTH_LONG).show();
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyChatFragment.java	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/LobbyChatFragment.java	Mon Jul 23 00:17:06 2012 +0200
@@ -85,13 +85,13 @@
         public void onServiceConnected(ComponentName className, IBinder binder) {
         	Log.d("LobbyChatFragment", "netconn received");
         	netconn = ((NetplayBinder) binder).getNetconn();
-        	adapter.setLog(netconn.lobbyLog.getLog());
-        	netconn.lobbyLog.registerObserver(adapter);
+        	adapter.setLog(netconn.lobbyChatlog.getLog());
+        	netconn.lobbyChatlog.registerObserver(adapter);
         }
 
         public void onServiceDisconnected(ComponentName className) {
         	// TODO navigate away
-        	netconn.lobbyLog.unregisterObserver(adapter);
+        	netconn.lobbyChatlog.unregisterObserver(adapter);
         	netconn = null;
         }
     };
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/MessageLog.java	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/MessageLog.java	Mon Jul 23 00:17:06 2012 +0200
@@ -27,6 +27,7 @@
 	private static final int BACKLOG_LINES = 200;
 	
 	private static final int INFO_COLOR = Color.GRAY;
+	private static final int PLAYERINFO_COLOR = Color.GREEN;
 	private static final int CHAT_COLOR = Color.GREEN;
 	private static final int MECHAT_COLOR = Color.CYAN;
 	private static final int WARN_COLOR = Color.RED;
@@ -111,8 +112,8 @@
 			append(withColor("***"+msg, WARN_COLOR));
 			break;
 		case JnaFrontlib.NETCONN_MSG_TYPE_PLAYERINFO:
-			// TODO better formatting or different way to display
-			append(msg);
+			// TODO Display in popup?
+			append(withColor(msg.replace("\n", " "), PLAYERINFO_COLOR));
 			break;
 		case JnaFrontlib.NETCONN_MSG_TYPE_SERVERMESSAGE:
 			appendRaw(span(TextUtils.concat("\n", Html.fromHtml(msg), "\n"), new RelativeSizeSpan(1.5f)));
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Netconn.java	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Netconn.java	Mon Jul 23 00:17:06 2012 +0200
@@ -33,23 +33,24 @@
 	
 	private NetconnPtr conn;
 	private String playerName;
+	private boolean joined; // True once we have been admitted to the lobby
 	
 	public final PlayerList playerList = new PlayerList();
 	public final RoomList roomList = new RoomList();
-	public final MessageLog lobbyLog;
-	public final MessageLog roomLog;
+	public final MessageLog lobbyChatlog;
+	public final MessageLog roomChatlog;
 	
 	private StrCallback lobbyJoinCb = new StrCallback() {
 		public void callback(Pointer context, String arg1) {
 			playerList.addPlayerWithNewId(arg1);
-			lobbyLog.appendPlayerJoin(arg1);
+			lobbyChatlog.appendPlayerJoin(arg1);
 		}
 	};
 	
 	private StrStrCallback lobbyLeaveCb = new StrStrCallback() {
 		public void callback(Pointer context, String name, String msg) {
-			playerList.remove(name);
-			lobbyLog.appendPlayerLeave(name, msg);
+			playerList.removePlayer(name);
+			lobbyChatlog.appendPlayerLeave(name, msg);
 		}
 	};
 	
@@ -79,23 +80,21 @@
 	
 	private StrCallback roomDeleteCb = new StrCallback() {
 		public void callback(Pointer context, String name) {
-			roomList.remove(name);
+			roomList.removeRoom(name);
 		}
 	};
 	
 	private VoidCallback connectedCb = new VoidCallback() {
 		public void callback(Pointer context) {
 			// TODO I guess more needs to happen here...
+			joined = true;
 			FLIB.flib_netconn_send_request_roomlist(conn);
 		}
 	};
 	
 	private RoomListCallback roomlistCb = new RoomListCallback() {
 		public void callback(Pointer context, RoomArrayPtr arg1, int count) {
-			roomList.clear();
-			for(RoomPtr roomPtr : arg1.getRooms(count)) {
-				roomList.addRoomWithNewId(roomPtr);
-			}
+			roomList.updateList(arg1.getRooms(count));
 		}
 	};
 	
@@ -125,8 +124,8 @@
 			playerName = "Player";
 		}
 		this.playerName = playerName;
-		this.lobbyLog = new MessageLog(context);
-		this.roomLog = new MessageLog(context);
+		this.lobbyChatlog = new MessageLog(context);
+		this.roomChatlog = new MessageLog(context);
 		
 		MetaschemePtr meta = null;
 		File dataPath = Utils.getDataPathFile(context);
@@ -170,24 +169,25 @@
 	public void sendChat(String s) {
 		FLIB.flib_netconn_send_chat(conn, s);
 		if(FLIB.flib_netconn_is_in_room_context(conn)) {
-			roomLog.appendChat(playerName, s);
+			roomChatlog.appendChat(playerName, s);
 		} else {
-			lobbyLog.appendChat(playerName, s);
+			lobbyChatlog.appendChat(playerName, s);
 		}
 	}
 	
 	private MessageLog getCurrentLog() {
 		if(FLIB.flib_netconn_is_in_room_context(conn)) {
-			return roomLog;
+			return roomChatlog;
 		} else {
-			return lobbyLog;
+			return lobbyChatlog;
 		}
 	}
 	
 	public void sendNick(String nick) { FLIB.flib_netconn_send_nick(conn, nick); }
 	public void sendPassword(String password) { FLIB.flib_netconn_send_password(conn, password); }
 	public void sendQuit(String message) { FLIB.flib_netconn_send_quit(conn, message); }
-	public void sendRoomlistRequest() { FLIB.flib_netconn_send_request_roomlist(conn); }
+	public void sendRoomlistRequest() { if(joined) FLIB.flib_netconn_send_request_roomlist(conn); }
+	public void sendPlayerInfoQuery(String name) { FLIB.flib_netconn_send_playerInfo(conn, name); }
 	
 	public boolean isConnected() {
 		return conn != null;
@@ -200,4 +200,5 @@
 			Log.e("Netconn", "Leaked Netconn object");
 		}
 	}
+
 }
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/ObservableLinkedHashMap.java	Sat Jul 21 14:56:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A map of items, sorted by time of insertion (earliest first). 
- * Observers can be notified about insertions, deletions and changes (which don't change the order).
- * This is useful for e.g. the lists of current rooms and players, because it allows easy addition
- * and removal of entries on the one side, as well as reaction to these events by UI elements.
- */
-public class ObservableLinkedHashMap<K,V> {
-	private LinkedHashMap<K,V> map = new LinkedHashMap<K,V>();
-	private List<Observer<K,V>> observers = new LinkedList<Observer<K,V>>();
-	
-	public Collection<V> getValues() {
-		return Collections.unmodifiableCollection(map.values());
-	}
-	
-	public Map<K,V> getMap() {
-		return Collections.unmodifiableMap(map);
-	}
-	
-	public void observe(Observer<K,V> observer) {
-		observers.add(observer);
-	}
-	
-	public void unobserve(Observer<K,V> observer) {
-		observers.remove(observer);
-	}
-	
-	// TODO ugh
-	public void clear() {
-		while(!map.isEmpty()) {
-			remove(map.keySet().iterator().next());
-		}
-	}
-	
-	public void put(K key, V value) {
-		V oldValue = map.put(key, value);
-		Map<K,V> unmodifiableMap = Collections.unmodifiableMap(map);
-		if(oldValue != null) {
-			for(Observer<K,V> o : observers) {
-				o.itemReplaced(unmodifiableMap, key, oldValue, value);
-			}
-		} else {
-			for(Observer<K,V> o : observers) {
-				o.itemAdded(unmodifiableMap, key, value);
-			}
-		}
-	}
-	
-	public void remove(K key) {
-		V oldValue = map.remove(key);
-		if(oldValue != null) {
-			Map<K,V> unmodifiableMap = Collections.unmodifiableMap(map);
-			for(Observer<K,V> o : observers) {
-				o.itemRemoved(unmodifiableMap, key, oldValue);
-			}
-		}
-	}
-
-	public static interface Observer<K,V> {
-		void itemAdded(Map<K,V> map, K key, V value);
-		void itemRemoved(Map<K,V> map, K key, V oldValue);
-		void itemReplaced(Map<K,V> map, K key, V oldValue, V newValue);
-	}
-}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Player.java	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Player.java	Mon Jul 23 00:17:06 2012 +0200
@@ -3,7 +3,7 @@
 import java.util.Comparator;
 
 public class Player {
-	public static final ByNameComparator nameComparator = new ByNameComparator(); 
+	public static final ByNameComparator NAME_COMPARATOR = new ByNameComparator(); 
 
 	public final String name;
 	public final long id; // for ListView
@@ -13,7 +13,7 @@
 		this.id = id;
 	}
 	
-	private static class ByNameComparator implements Comparator<Player> {
+	private static final class ByNameComparator implements Comparator<Player> {
 		public int compare(Player lhs, Player rhs) {
 			return lhs.name.compareToIgnoreCase(rhs.name);
 		}
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerList.java	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerList.java	Mon Jul 23 00:17:06 2012 +0200
@@ -1,12 +1,28 @@
 package org.hedgewars.hedgeroid.netplay;
 
-public class PlayerList extends ObservableLinkedHashMap<String, Player> {
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import android.database.DataSetObservable;
+
+public class PlayerList extends DataSetObservable {
 	private long nextId = 1;
+	private Map<String, Player> players = new TreeMap<String, Player>();
 	
 	public void addPlayerWithNewId(String name) {
 		Player p = new Player(name, nextId++);
-		put(name, p);
+		players.put(name, p);
+		notifyChanged();
 	}
 	
-	public interface Observer extends ObservableLinkedHashMap.Observer<String, Player> {}
+	public void removePlayer(String name) {
+		if(players.remove(name) != null) {
+			notifyChanged();
+		}
+	}
+
+	public Map<String, Player> getMap() {
+		return Collections.unmodifiableMap(players);
+	}
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerListAdapter.java	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerListAdapter.java	Mon Jul 23 00:17:06 2012 +0200
@@ -1,24 +1,35 @@
 package org.hedgewars.hedgeroid.netplay;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.netplay.PlayerList.Observer;
 
 import android.content.Context;
+import android.database.DataSetObserver;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 import android.widget.TextView;
 
-public class PlayerListAdapter extends BaseAdapter implements Observer {
+public class PlayerListAdapter extends BaseAdapter {
 	private List<Player> players = new ArrayList<Player>();
 	private Context context;
+	private PlayerList playerList;
+	
+	private DataSetObserver observer = new DataSetObserver() {
+		@Override
+		public void onChanged() {
+			reloadFromList(playerList);
+		}
+		
+		@Override
+		public void onInvalidated() {
+			invalidate();
+		}
+	};
 	
 	public PlayerListAdapter(Context context) {
 		this.context = context;
@@ -28,7 +39,7 @@
 		return players.size();
 	}
 
-	public Object getItem(int position) {
+	public Player getItem(int position) {
 		return players.get(position);
 	}
 
@@ -39,23 +50,28 @@
 	public boolean hasStableIds() {
 		return true;
 	}
-
-	public void itemAdded(Map<String, Player> map, String key, Player value) {
-		setPlayerList(map.values());
-	}
-
-	public void itemRemoved(Map<String, Player> map, String key, Player oldValue) {
-		setPlayerList(map.values());
-	}
-
-	public void itemReplaced(Map<String, Player> map, String key,
-			Player oldValue, Player newValue) {
-		setPlayerList(map.values());
+	
+	public void setList(PlayerList playerList) {
+		if(this.playerList != null) {
+			this.playerList.unregisterObserver(observer);
+		}
+		this.playerList = playerList;
+		this.playerList.registerObserver(observer);
+		reloadFromList(playerList);
 	}
 	
-	public void setPlayerList(Collection<Player> players) {
-		this.players = new ArrayList<Player>(players);
-		Collections.sort(this.players, Player.nameComparator);
+	public void invalidate() {
+		players = new ArrayList<Player>();
+		if(playerList != null) {
+			playerList.unregisterObserver(observer);
+		}
+		playerList = null;
+		notifyDataSetInvalidated();
+	}
+	
+	private void reloadFromList(PlayerList list) {
+		players = new ArrayList<Player>(list.getMap().values());
+		Collections.sort(players, Player.NAME_COMPARATOR);
 		notifyDataSetChanged();
 	}
 	
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerlistFragment.java	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/PlayerlistFragment.java	Mon Jul 23 00:17:06 2012 +0200
@@ -10,9 +10,15 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.support.v4.app.ListFragment;
+import android.view.ContextMenu;
 import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.Toast;
+import android.widget.AdapterView.AdapterContextMenuInfo;
 
 public class PlayerlistFragment extends ListFragment {
 	private Netconn netconn;
@@ -28,6 +34,38 @@
 	}
 
 	@Override
+	public void onActivityCreated(Bundle savedInstanceState) {
+		super.onActivityCreated(savedInstanceState);
+		registerForContextMenu(getListView());
+	}
+
+	@Override
+	public void onCreateContextMenu(ContextMenu menu, View v,
+			ContextMenuInfo menuInfo) {
+		super.onCreateContextMenu(menu, v, menuInfo);
+		MenuInflater inflater = getActivity().getMenuInflater();
+		inflater.inflate(R.menu.lobby_playerlist_context, menu);
+	}
+	
+	@Override
+	public boolean onContextItemSelected(MenuItem item) {
+		AdapterContextMenuInfo info = (AdapterContextMenuInfo)item.getMenuInfo();
+		switch(item.getItemId()) {
+		case R.id.player_info:
+			Player p = playerListAdapter.getItem(info.position);
+			if(netconn != null) {
+				netconn.sendPlayerInfoQuery(p.name);
+			}
+			return true;
+		case R.id.player_follow:
+			Toast.makeText(getActivity(), R.string.not_implemented_yet, Toast.LENGTH_SHORT).show();
+			return true;
+		default:
+			return super.onContextItemSelected(item);
+		}
+	}
+	
+	@Override
 	public void onDestroy() {
 		super.onDestroy();
 		getActivity().unbindService(serviceConnection);
@@ -42,13 +80,12 @@
     private ServiceConnection serviceConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder binder) {
         	netconn = ((NetplayBinder) binder).getNetconn();
-        	playerListAdapter.setPlayerList(netconn.playerList.getValues());
-        	netconn.playerList.observe(playerListAdapter);
+        	playerListAdapter.setList(netconn.playerList);
         }
 
         public void onServiceDisconnected(ComponentName className) {
         	// TODO navigate away
-        	netconn.playerList.unobserve(playerListAdapter);
+        	playerListAdapter.invalidate();
         	netconn = null;
         }
     };
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Room.java	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Room.java	Mon Jul 23 00:17:06 2012 +0200
@@ -1,5 +1,7 @@
 package org.hedgewars.hedgeroid.netplay;
 
+import java.util.Comparator;
+
 import org.hedgewars.hedgeroid.R;
 
 import android.content.res.Resources;
@@ -8,6 +10,7 @@
 	public static final String MAP_REGULAR = "+rnd+";
 	public static final String MAP_MAZE = "+maze+";
 	public static final String MAP_DRAWN = "+drawn+";
+	public static final Comparator<Room> ID_COMPARATOR = new ByIdComparator();
 	
 	public final String name, map, scheme, weapons, owner;
 	public final int playerCount, teamCount;
@@ -39,4 +42,10 @@
 		}
 		return map;
 	}
+	
+	private static final class ByIdComparator implements Comparator<Room> {
+		public int compare(Room lhs, Room rhs) {
+			return lhs.id<rhs.id ? -1 : lhs.id>rhs.id ? 1 : 0;
+		}
+	}
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomList.java	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomList.java	Mon Jul 23 00:17:06 2012 +0200
@@ -1,34 +1,68 @@
 package org.hedgewars.hedgeroid.netplay;
 
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
 import org.hedgewars.hedgeroid.netplay.JnaFrontlib.RoomPtr;
 
+import android.database.DataSetObservable;
 import android.util.Log;
 
-public class RoomList extends ObservableLinkedHashMap<String, Room> {
+public class RoomList extends DataSetObservable {
 	private long nextId = 1;
+	private Map<String, Room> rooms = new TreeMap<String, Room>();
+	
+	public void updateList(RoomPtr[] roomPtrs) {
+		Map<String, Room> newMap = new TreeMap<String, Room>();
+		for(RoomPtr roomPtr : roomPtrs) {
+			JnaFrontlib.Room room = roomPtr.deref();
+			Room oldEntry = rooms.get(room.name);
+			if(oldEntry == null) {
+				newMap.put(room.name, buildRoom(room, nextId++));
+			} else {
+				newMap.put(room.name, buildRoom(room, oldEntry.id));
+			}
+		}
+		rooms = newMap;
+		notifyChanged();
+	}
 	
 	public void addRoomWithNewId(RoomPtr roomPtr) {
-		JnaFrontlib.Room r = roomPtr.deref();
-		long id = nextId++;
-		put(r.name, new Room(r.name, r.map, r.scheme, r.weapons, r.owner, r.playerCount, r.teamCount, r.inProgress, id));
+		putRoom(roomPtr.deref(), nextId++);
+		notifyChanged();
 	}
 	
 	public void updateRoom(String name, RoomPtr roomPtr) {
-		Room oldEntry = getMap().get(name);
+		JnaFrontlib.Room room = roomPtr.deref();
+		Room oldEntry = rooms.get(name);
 		if(oldEntry == null) {
 			Log.e("RoomList", "Received update for unknown room: "+name);
+			putRoom(room, nextId++);
 		} else {
-			JnaFrontlib.Room r = roomPtr.deref();
-			/*
-			 *  TODO Room renames are handled as re-insertions which push the room
-			 *  up to the top of the list again. Should maybe be revisited (sorting by ID is an option)
-			 */
-			if(!r.name.equals(oldEntry.name)) {
-				remove(oldEntry.name);
+			if(!name.equals(room.name)) {
+				rooms.remove(name);
 			}
-			put(r.name, new Room(r.name, r.map, r.scheme, r.weapons, r.owner, r.playerCount, r.teamCount, r.inProgress, oldEntry.id));
+			putRoom(room, oldEntry.id);
+		}
+		notifyChanged();
+	}
+	
+	public void removeRoom(String name) {
+		if(rooms.remove(name) != null) {
+			notifyChanged();
 		}
 	}
-
-	public static interface Observer extends ObservableLinkedHashMap.Observer<String, Room> { }
+	
+	public Map<String, Room> getMap() {
+		return Collections.unmodifiableMap(rooms);
+	}
+	
+	private void putRoom(JnaFrontlib.Room r, long id) {
+		rooms.put(r.name, buildRoom(r, id));
+	}
+	
+	private Room buildRoom(JnaFrontlib.Room r, long id) {
+		return new Room(r.name, r.map, r.scheme, r.weapons, r.owner, r.playerCount, r.teamCount, r.inProgress, id);
+	}
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomListAdapter.java	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomListAdapter.java	Mon Jul 23 00:17:06 2012 +0200
@@ -1,25 +1,36 @@
 package org.hedgewars.hedgeroid.netplay;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 import org.hedgewars.hedgeroid.R;
-import org.hedgewars.hedgeroid.netplay.RoomList.Observer;
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.database.DataSetObserver;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 import android.widget.TextView;
 
-public class RoomListAdapter extends BaseAdapter implements Observer {
+public class RoomListAdapter extends BaseAdapter {
 	private List<Room> rooms = new ArrayList<Room>();
 	private Context context;
+	private RoomList roomList;
+	
+	private DataSetObserver observer = new DataSetObserver() {
+		@Override
+		public void onChanged() {
+			reloadFromList(roomList);
+		}
+		
+		@Override
+		public void onInvalidated() {
+			invalidate();
+		}
+	};
 	
 	public RoomListAdapter(Context context) {
 		this.context = context;
@@ -29,7 +40,7 @@
 		return rooms.size();
 	}
 
-	public Object getItem(int position) {
+	public Room getItem(int position) {
 		return rooms.get(position);
 	}
 
@@ -41,9 +52,27 @@
 		return true;
 	}
 
-	public void setList(Collection<Room> rooms) {
-		this.rooms = new ArrayList<Room>(rooms);
-		Collections.reverse(this.rooms); // We want to show the newest rooms first
+	public void setList(RoomList roomList) {
+		if(this.roomList != null) {
+			this.roomList.unregisterObserver(observer);
+		}
+		this.roomList = roomList;
+		this.roomList.registerObserver(observer);
+		reloadFromList(roomList);
+	}
+	
+	public void invalidate() {
+		rooms = new ArrayList<Room>();
+		if(roomList != null) {
+			roomList.unregisterObserver(observer);
+		}
+		roomList = null;
+		notifyDataSetInvalidated();
+	}
+	
+	private void reloadFromList(RoomList list) {
+		rooms = new ArrayList<Room>(roomList.getMap().values());
+		Collections.sort(rooms, Collections.reverseOrder(Room.ID_COMPARATOR));
 		notifyDataSetChanged();
 	}
 	
@@ -99,17 +128,4 @@
 		
 		return v;
 	}
-
-	public void itemAdded(Map<String, Room> map, String key, Room value) {
-		setList(map.values());
-	}
-
-	public void itemRemoved(Map<String, Room> map, String key, Room oldValue) {
-		setList(map.values());
-	}
-
-	public void itemReplaced(Map<String, Room> map, String key, Room oldValue,
-			Room newValue) {
-		setList(map.values());
-	}
 }
\ No newline at end of file
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomlistFragment.java	Sat Jul 21 14:56:52 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/RoomlistFragment.java	Mon Jul 23 00:17:06 2012 +0200
@@ -12,12 +12,17 @@
 import android.os.IBinder;
 import android.support.v4.app.ListFragment;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ListView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView;
+import android.widget.Toast;
 
-public class RoomlistFragment extends ListFragment {
-	private static final int AUTO_REFRESH_INTERVAL_MS = 10000;
+public class RoomlistFragment extends ListFragment implements OnItemClickListener {
+	private static final int AUTO_REFRESH_INTERVAL_MS = 15000;
 	
 	private Netconn netconn;
 	private RoomListAdapter adapter;
@@ -40,6 +45,7 @@
 	            Context.BIND_AUTO_CREATE);
 		adapter = new RoomListAdapter(getActivity());
 		setListAdapter(adapter);
+		setHasOptionsMenu(true);
 	}
 
 	@Override
@@ -50,6 +56,12 @@
 	}
 	
 	@Override
+	public void onActivityCreated(Bundle savedInstanceState) {
+		super.onActivityCreated(savedInstanceState);
+		getListView().setOnItemClickListener(this);
+	}
+	
+	@Override
 	public void onResume() {
 		super.onResume();
 		if(netconn != null) {
@@ -70,16 +82,39 @@
 		getActivity().unbindService(serviceConnection);
 	}
 	
+	@Override
+	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+		super.onCreateOptionsMenu(menu, inflater);
+		inflater.inflate(R.menu.lobby_roomlist_options, menu);
+	}
+	
+	@Override
+	public boolean onOptionsItemSelected(MenuItem item) {
+		switch(item.getItemId()) {
+		case R.id.roomlist_refresh:
+			if(netconn != null) {
+				netconn.sendRoomlistRequest();
+			}
+			return true;
+		default:
+			return super.onOptionsItemSelected(item);
+		}
+	}
+	
+	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+		Toast.makeText(getActivity(), R.string.not_implemented_yet, Toast.LENGTH_SHORT).show();
+	}
+	
     private ServiceConnection serviceConnection = new ServiceConnection() {
         public void onServiceConnected(ComponentName className, IBinder binder) {
         	netconn = ((NetplayBinder) binder).getNetconn();
-        	adapter.setList(netconn.roomList.getValues());
-        	netconn.roomList.observe(adapter);
+        	adapter.setList(netconn.roomList);
+        	autoRefreshTimer.start();
         }
 
         public void onServiceDisconnected(ComponentName className) {
         	// TODO navigate away
-        	netconn.roomList.unobserve(adapter);
+        	adapter.invalidate();
         	netconn = null;
         }
     };
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/netplay/Signal.java	Sat Jul 21 14:56:52 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-package org.hedgewars.hedgeroid.netplay;
-
-import java.util.List;
-
-public class Signal<CallbackType> {
-	private List<CallbackType> observers; 
-	
-	public void addListener(CallbackType cb) {
-		observers.add(cb);
-	}
-	
-	public void removeListener(CallbackType cb) {
-		observers.remove(cb);
-	}
-}