|
1 package org.hedgewars.hedgeroid; |
|
2 |
|
3 import java.io.IOException; |
|
4 import java.util.Arrays; |
|
5 import java.util.Collections; |
|
6 import java.util.List; |
|
7 import java.util.Random; |
|
8 |
|
9 import org.hedgewars.hedgeroid.R; |
|
10 import org.hedgewars.hedgeroid.Datastructures.FrontendDataUtils; |
|
11 import org.hedgewars.hedgeroid.Datastructures.MapFile; |
|
12 import org.hedgewars.hedgeroid.Datastructures.MapRecipe; |
|
13 import org.hedgewars.hedgeroid.Datastructures.Scheme; |
|
14 import org.hedgewars.hedgeroid.Datastructures.Weaponset; |
|
15 import org.hedgewars.hedgeroid.frontlib.Frontlib; |
|
16 |
|
17 import android.content.Context; |
|
18 import android.graphics.drawable.Drawable; |
|
19 import android.os.Bundle; |
|
20 import android.os.Handler; |
|
21 import android.os.Looper; |
|
22 import android.os.Message; |
|
23 import android.support.v4.app.Fragment; |
|
24 import android.view.LayoutInflater; |
|
25 import android.view.View; |
|
26 import android.view.View.OnClickListener; |
|
27 import android.view.ViewGroup; |
|
28 import android.widget.AdapterView; |
|
29 import android.widget.AdapterView.OnItemSelectedListener; |
|
30 import android.widget.ArrayAdapter; |
|
31 import android.widget.ImageView; |
|
32 import android.widget.Spinner; |
|
33 import android.widget.TableRow; |
|
34 import android.widget.Toast; |
|
35 |
|
36 public class MapFragment extends Fragment implements RoomStateManager.Observer { |
|
37 private Spinner mapTypeSpinner, mapNameSpinner, templateSpinner, mazeSizeSpinner; |
|
38 private TableRow nameRow, templateRow, mazeSizeRow; |
|
39 private ImageView mapPreview; |
|
40 private List<MapFile> mapFiles; |
|
41 private RoomStateManager stateManager; |
|
42 private Random random = new Random(); |
|
43 private CalmDownHandler mapPreviewHandler; |
|
44 |
|
45 /* |
|
46 * Rendering the preview can take a few seconds on Android, so we want to prevent preview |
|
47 * requests from queueing up if maps are changed quickly. So if there is already a preview |
|
48 * being generated, we store our latest request in the newPreviewRequest variable instead. |
|
49 * Once the current preview is finished generating it will start on that one. |
|
50 */ |
|
51 private boolean previewGenerationInProgress; |
|
52 private MapRecipe newPreviewRequest; |
|
53 |
|
54 @Override |
|
55 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
|
56 View v = inflater.inflate(R.layout.fragment_map, container, false); |
|
57 |
|
58 final Context appContext = getActivity().getApplicationContext(); |
|
59 mapPreviewHandler = new CalmDownHandler(getActivity().getMainLooper(), new Runnable() { |
|
60 public void run() { |
|
61 if(!previewGenerationInProgress) { |
|
62 mapPreview.setImageResource(R.drawable.roomlist_preparing); |
|
63 MapPreviewGenerator.startPreviewGeneration(appContext, stateManager.getMapRecipe(), mapPreviewListener); |
|
64 previewGenerationInProgress = true; |
|
65 } else { |
|
66 newPreviewRequest = stateManager.getMapRecipe(); |
|
67 } |
|
68 } |
|
69 }, 250); |
|
70 |
|
71 nameRow = (TableRow) v.findViewById(R.id.rowMapName); |
|
72 templateRow = (TableRow) v.findViewById(R.id.rowTemplateFilter); |
|
73 mazeSizeRow = (TableRow) v.findViewById(R.id.rowMazeSize); |
|
74 mapPreview = (ImageView) v.findViewById(R.id.mapPreview); |
|
75 mapPreview.setImageDrawable(null);; |
|
76 mapPreview.setOnClickListener(mapClickListener); |
|
77 |
|
78 try { |
|
79 mapFiles = FrontendDataUtils.getMaps(getActivity()); |
|
80 } catch (IOException e) { |
|
81 Toast.makeText(getActivity().getApplicationContext(), R.string.error_missing_sdcard_or_files, Toast.LENGTH_LONG).show(); |
|
82 getActivity().finish(); |
|
83 } |
|
84 Collections.sort(mapFiles, MapFile.MISSIONS_FIRST_NAME_ORDER); |
|
85 |
|
86 List<String> mapNames = MapFile.toDisplayNameList(mapFiles, getResources()); |
|
87 mapTypeSpinner = prepareSpinner(v, R.id.spinMapType, Arrays.asList(getResources().getStringArray(R.array.map_types)), mapTypeSelectedListener); |
|
88 mapNameSpinner = prepareSpinner(v, R.id.spinMapName, mapNames, mapNameSelectedListener); |
|
89 templateSpinner = prepareSpinner(v, R.id.spinTemplateFilter, Arrays.asList(getResources().getStringArray(R.array.map_templates)), mapTemplateSelectedListener); |
|
90 mazeSizeSpinner = prepareSpinner(v, R.id.spinMazeSize, Arrays.asList(getResources().getStringArray(R.array.map_maze_sizes)), mazeSizeSelectedListener); |
|
91 |
|
92 stateManager.registerObserver(this); |
|
93 MapRecipe map = stateManager.getMapRecipe(); |
|
94 if(map != null) { |
|
95 updateDisplay(map); |
|
96 } |
|
97 setChiefState(stateManager.getChiefStatus()); |
|
98 mapPreviewHandler.activity(); |
|
99 return v; |
|
100 } |
|
101 |
|
102 private static Spinner prepareSpinner(View v, int id, List<String> items, OnItemSelectedListener itemSelectedListener) { |
|
103 Spinner spinner = (Spinner)v.findViewById(id); |
|
104 ArrayAdapter<String> adapter = new ArrayAdapter<String>(v.getContext(), R.layout.listview_item, items); |
|
105 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); |
|
106 spinner.setAdapter(adapter); |
|
107 spinner.setOnItemSelectedListener(itemSelectedListener); |
|
108 return spinner; |
|
109 } |
|
110 |
|
111 @Override |
|
112 public void onCreate(Bundle savedInstanceState) { |
|
113 super.onCreate(savedInstanceState); |
|
114 try { |
|
115 stateManager = ((RoomStateManager.Provider)getActivity()).getRoomStateManager(); |
|
116 } catch(ClassCastException e) { |
|
117 throw new RuntimeException("Hosting activity must implement RoomStateManager.Provider.", e); |
|
118 } |
|
119 } |
|
120 |
|
121 @Override |
|
122 public void onDestroy() { |
|
123 super.onDestroy(); |
|
124 mapPreviewHandler.stop(); |
|
125 stateManager.unregisterObserver(this); |
|
126 } |
|
127 |
|
128 private void setChiefState(boolean chiefState) { |
|
129 mapTypeSpinner.setEnabled(chiefState); |
|
130 mapNameSpinner.setEnabled(chiefState); |
|
131 templateSpinner.setEnabled(chiefState); |
|
132 mazeSizeSpinner.setEnabled(chiefState); |
|
133 mapPreview.setEnabled(chiefState); |
|
134 |
|
135 if(chiefState) { |
|
136 sendMapnameAndGenerator(); |
|
137 stateManager.changeMapTemplate(templateSpinner.getSelectedItemPosition()); |
|
138 stateManager.changeMazeSize(mazeSizeSpinner.getSelectedItemPosition()); |
|
139 } |
|
140 } |
|
141 |
|
142 private void updateDisplay(MapRecipe map) { |
|
143 nameRow.setVisibility(map.mapgen == Frontlib.MAPGEN_NAMED ? View.VISIBLE : View.GONE); |
|
144 templateRow.setVisibility(map.mapgen == Frontlib.MAPGEN_REGULAR ? View.VISIBLE : View.GONE); |
|
145 mazeSizeRow.setVisibility(map.mapgen == Frontlib.MAPGEN_MAZE ? View.VISIBLE : View.GONE); |
|
146 |
|
147 mapTypeSpinner.setSelection(map.mapgen); |
|
148 int mapPosition = findMapPosition(mapFiles, map.name); |
|
149 if(mapPosition >= 0) { |
|
150 mapNameSpinner.setSelection(mapPosition); |
|
151 } |
|
152 templateSpinner.setSelection(map.templateFilter); |
|
153 mazeSizeSpinner.setSelection(map.mazeSize); |
|
154 } |
|
155 |
|
156 private static int findMapPosition(List<MapFile> mapFiles, String mapName) { |
|
157 for(int i=0; i<mapFiles.size(); i++) { |
|
158 if(mapName.equals(mapFiles.get(i).name)) { |
|
159 return i; |
|
160 } |
|
161 } |
|
162 return -1; |
|
163 } |
|
164 |
|
165 private void sendMapnameAndGenerator() { |
|
166 int mapType = mapTypeSpinner.getSelectedItemPosition(); |
|
167 String mapName = mapFiles.get(mapNameSpinner.getSelectedItemPosition()).name; |
|
168 stateManager.changeMapNameAndGenerator(MapRecipe.mapnameForGenerator(mapType, mapName)); |
|
169 } |
|
170 |
|
171 private final OnItemSelectedListener mapTypeSelectedListener = new OnItemSelectedListener() { |
|
172 public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) { |
|
173 sendMapnameAndGenerator(); |
|
174 } |
|
175 public void onNothingSelected(AdapterView<?> arg0) {} |
|
176 }; |
|
177 |
|
178 private final OnItemSelectedListener mapNameSelectedListener = new OnItemSelectedListener() { |
|
179 public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) { |
|
180 sendMapnameAndGenerator(); |
|
181 } |
|
182 public void onNothingSelected(AdapterView<?> arg0) {} |
|
183 }; |
|
184 |
|
185 private final OnItemSelectedListener mapTemplateSelectedListener = new OnItemSelectedListener() { |
|
186 public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) { |
|
187 stateManager.changeMapTemplate(position); |
|
188 } |
|
189 public void onNothingSelected(AdapterView<?> arg0) {} |
|
190 }; |
|
191 |
|
192 private final OnItemSelectedListener mazeSizeSelectedListener = new OnItemSelectedListener() { |
|
193 public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) { |
|
194 stateManager.changeMazeSize(position); |
|
195 } |
|
196 public void onNothingSelected(AdapterView<?> arg0) {} |
|
197 }; |
|
198 |
|
199 private final OnClickListener mapClickListener = new OnClickListener() { |
|
200 public void onClick(View v) { |
|
201 stateManager.changeMapSeed(MapRecipe.makeRandomSeed()); |
|
202 switch(mapTypeSpinner.getSelectedItemPosition()) { |
|
203 case Frontlib.MAPGEN_NAMED: |
|
204 mapNameSpinner.setSelection(random.nextInt(mapNameSpinner.getCount())); |
|
205 break; |
|
206 case Frontlib.MAPGEN_REGULAR: |
|
207 templateSpinner.setSelection(Frontlib.TEMPLATEFILTER_ALL); |
|
208 break; |
|
209 case Frontlib.MAPGEN_MAZE: |
|
210 mazeSizeSpinner.setSelection(random.nextInt(mazeSizeSpinner.getCount())); |
|
211 break; |
|
212 } |
|
213 } |
|
214 }; |
|
215 |
|
216 public void onChiefStatusChanged(boolean isChief) { |
|
217 setChiefState(isChief); |
|
218 } |
|
219 |
|
220 public void onMapChanged(MapRecipe recipe) { |
|
221 updateDisplay(recipe); |
|
222 mapPreviewHandler.activity(); |
|
223 } |
|
224 |
|
225 public void onGameStyleChanged(String gameStyle) { } |
|
226 public void onSchemeChanged(Scheme scheme) { } |
|
227 public void onWeaponsetChanged(Weaponset weaponset) { } |
|
228 |
|
229 private MapPreviewGenerator.Listener mapPreviewListener = new MapPreviewGenerator.Listener() { |
|
230 public void onMapPreviewResult(Drawable preview) { |
|
231 if(newPreviewRequest != null) { |
|
232 MapPreviewGenerator.startPreviewGeneration(getActivity().getApplicationContext(), newPreviewRequest, mapPreviewListener); |
|
233 newPreviewRequest = null; |
|
234 } else { |
|
235 if(mapPreview != null) { |
|
236 mapPreview.setImageDrawable(preview); |
|
237 } |
|
238 previewGenerationInProgress = false; |
|
239 } |
|
240 } |
|
241 }; |
|
242 |
|
243 /** |
|
244 * This class allows you to define a runnable that is called when there has been no activity |
|
245 * for a set amount of time, where activity is determined by calls to the activity() method |
|
246 * of the handler. It is used here to update the map preview when there have been no updates |
|
247 * to the relevant map information for a time, to prevent triggering several updates at once |
|
248 * when different parts of the information change. |
|
249 */ |
|
250 private static final class CalmDownHandler extends Handler { |
|
251 int runningMessagesCounter = 0; |
|
252 final Runnable inactivityRunnable; |
|
253 final long inactivityMs; |
|
254 boolean stopped; |
|
255 |
|
256 public CalmDownHandler(Looper looper, Runnable runnable, long inactivityMs) { |
|
257 super(looper); |
|
258 this.inactivityRunnable = runnable; |
|
259 this.inactivityMs = inactivityMs; |
|
260 } |
|
261 |
|
262 public void activity() { |
|
263 runningMessagesCounter++; |
|
264 sendMessageDelayed(obtainMessage(), inactivityMs); |
|
265 } |
|
266 |
|
267 @Override |
|
268 public void handleMessage(Message msg) { |
|
269 runningMessagesCounter--; |
|
270 if(runningMessagesCounter==0 && !stopped) { |
|
271 inactivityRunnable.run(); |
|
272 } |
|
273 } |
|
274 |
|
275 public void stop() { |
|
276 stopped = true; |
|
277 } |
|
278 } |
|
279 } |