author | unc0rr |
Mon, 19 Nov 2018 22:26:47 +0100 | |
changeset 14256 | fa2e3f123a09 |
parent 10017 | de822cd3df3a |
permissions | -rw-r--r-- |
7584
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
1 |
/* |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
2 |
* Hedgewars for Android. An Android port of Hedgewars, a free turn based strategy game |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
3 |
* Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com> |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
4 |
* |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
5 |
* This program is free software; you can redistribute it and/or |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
6 |
* modify it under the terms of the GNU General Public License |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
7 |
* as published by the Free Software Foundation; either version 2 |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
8 |
* of the License, or (at your option) any later version. |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
9 |
* |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
10 |
* This program is distributed in the hope that it will be useful, |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
11 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
12 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
13 |
* GNU General Public License for more details. |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
14 |
* |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
15 |
* You should have received a copy of the GNU General Public License |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
16 |
* along with this program; if not, write to the Free Software |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
17 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
18 |
*/ |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
19 |
|
7508
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
20 |
package org.hedgewars.hedgeroid; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
21 |
|
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
22 |
import java.io.IOException; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
23 |
import java.util.Arrays; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
24 |
import java.util.Collections; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
25 |
import java.util.List; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
26 |
import java.util.Random; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
27 |
|
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
28 |
import org.hedgewars.hedgeroid.R; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
29 |
import org.hedgewars.hedgeroid.Datastructures.FrontendDataUtils; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
30 |
import org.hedgewars.hedgeroid.Datastructures.MapFile; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
31 |
import org.hedgewars.hedgeroid.Datastructures.MapRecipe; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
32 |
import org.hedgewars.hedgeroid.frontlib.Frontlib; |
7584
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
33 |
import org.hedgewars.hedgeroid.util.CalmDownHandler; |
7508
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
34 |
|
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
35 |
import android.content.Context; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
36 |
import android.graphics.drawable.Drawable; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
37 |
import android.os.Bundle; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
38 |
import android.support.v4.app.Fragment; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
39 |
import android.view.LayoutInflater; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
40 |
import android.view.View; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
41 |
import android.view.View.OnClickListener; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
42 |
import android.view.ViewGroup; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
43 |
import android.widget.AdapterView; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
44 |
import android.widget.AdapterView.OnItemSelectedListener; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
45 |
import android.widget.ArrayAdapter; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
46 |
import android.widget.ImageView; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
47 |
import android.widget.Spinner; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
48 |
import android.widget.TableRow; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
49 |
import android.widget.Toast; |
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
50 |
|
7584
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
51 |
/** |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
52 |
* Display a map preview, and configuration options for the map. |
10017 | 53 |
* |
7584
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
54 |
* Mostly for layout reasons, this does not include the theme setting, which |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
55 |
* (arguably) is more a map setting than a general game setting. |
7831c84cc644
License change: With the agreement of Xeli, I changed the Hedgeroid license to
Medo <smaxein@googlemail.com>
parents:
7582
diff
changeset
|
56 |
*/ |
7582
714310efad8f
Hedgeroid: Final sprint to the deadline
Medo <smaxein@googlemail.com>
parents:
7572
diff
changeset
|
57 |
public class MapFragment extends Fragment { |
10017 | 58 |
private Spinner mapTypeSpinner, mapNameSpinner, templateSpinner, mazeSizeSpinner; |
59 |
private TableRow nameRow, templateRow, mazeSizeRow; |
|
60 |
private ImageView mapPreview; |
|
61 |
||
62 |
private List<MapFile> mapFiles; |
|
63 |
private RoomStateManager stateManager; |
|
64 |
private Random random = new Random(); |
|
65 |
private CalmDownHandler mapPreviewHandler; |
|
66 |
||
67 |
/* |
|
68 |
* Rendering the preview can take a few seconds on Android, so we want to prevent preview |
|
69 |
* requests from queueing up if maps are changed quickly. So if there is already a preview |
|
70 |
* being generated, we store our latest request in the newPreviewRequest variable instead. |
|
71 |
* Once the current preview is finished generating it will start on that one. |
|
72 |
*/ |
|
73 |
private boolean previewGenerationInProgress; |
|
74 |
private MapRecipe newPreviewRequest; |
|
75 |
private MapRecipe currentMap; // kept for reference on every change to find out what changed |
|
76 |
||
77 |
@Override |
|
78 |
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
|
79 |
View v = inflater.inflate(R.layout.fragment_map, container, false); |
|
80 |
final Context appContext = getActivity().getApplicationContext(); |
|
81 |
||
82 |
/* |
|
83 |
* This handler will start the map preview after none of the map settings |
|
84 |
* have been updated for a short time. |
|
85 |
*/ |
|
86 |
mapPreviewHandler = new CalmDownHandler(getActivity().getMainLooper(), new Runnable() { |
|
87 |
public void run() { |
|
88 |
if(!previewGenerationInProgress) { |
|
89 |
mapPreview.setImageResource(R.drawable.roomlist_preparing); |
|
90 |
MapPreviewGenerator.startPreviewGeneration(appContext, stateManager.getMapRecipe(), mapPreviewListener); |
|
91 |
previewGenerationInProgress = true; |
|
92 |
} else { |
|
93 |
newPreviewRequest = stateManager.getMapRecipe(); |
|
94 |
} |
|
95 |
} |
|
96 |
}, 250); |
|
97 |
||
98 |
nameRow = (TableRow) v.findViewById(R.id.rowMapName); |
|
99 |
templateRow = (TableRow) v.findViewById(R.id.rowTemplateFilter); |
|
100 |
mazeSizeRow = (TableRow) v.findViewById(R.id.rowMazeSize); |
|
101 |
mapPreview = (ImageView) v.findViewById(R.id.mapPreview); |
|
102 |
mapPreview.setImageDrawable(null);; |
|
103 |
mapPreview.setOnClickListener(mapClickListener); |
|
7508
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
104 |
|
10017 | 105 |
try { |
106 |
mapFiles = FrontendDataUtils.getMaps(getActivity()); |
|
107 |
} catch (IOException e) { |
|
108 |
Toast.makeText(getActivity().getApplicationContext(), R.string.error_missing_sdcard_or_files, Toast.LENGTH_LONG).show(); |
|
109 |
getActivity().finish(); |
|
110 |
return null; |
|
111 |
} |
|
112 |
Collections.sort(mapFiles, MapFile.MISSIONS_FIRST_NAME_ORDER); |
|
113 |
||
114 |
List<String> mapNames = MapFile.toDisplayNameList(mapFiles, getResources()); |
|
115 |
mapTypeSpinner = prepareSpinner(v, R.id.spinMapType, Arrays.asList(getResources().getStringArray(R.array.map_types)), mapTypeSelectedListener); |
|
116 |
mapNameSpinner = prepareSpinner(v, R.id.spinMapName, mapNames, mapNameSelectedListener); |
|
117 |
templateSpinner = prepareSpinner(v, R.id.spinTemplateFilter, Arrays.asList(getResources().getStringArray(R.array.map_templates)), mapTemplateSelectedListener); |
|
118 |
mazeSizeSpinner = prepareSpinner(v, R.id.spinMazeSize, Arrays.asList(getResources().getStringArray(R.array.map_maze_sizes)), mazeSizeSelectedListener); |
|
119 |
||
120 |
stateManager.addListener(roomStateChangeListener); |
|
121 |
currentMap = stateManager.getMapRecipe(); |
|
122 |
if(currentMap != null) { |
|
123 |
updateDisplay(currentMap); |
|
124 |
} |
|
125 |
setChiefState(stateManager.getChiefStatus()); |
|
126 |
mapPreviewHandler.activity(); |
|
127 |
return v; |
|
128 |
} |
|
129 |
||
130 |
private static Spinner prepareSpinner(View v, int id, List<String> items, OnItemSelectedListener itemSelectedListener) { |
|
131 |
Spinner spinner = (Spinner)v.findViewById(id); |
|
132 |
ArrayAdapter<String> adapter = new ArrayAdapter<String>(v.getContext(), R.layout.listview_item, items); |
|
133 |
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); |
|
134 |
spinner.setAdapter(adapter); |
|
135 |
spinner.setOnItemSelectedListener(itemSelectedListener); |
|
136 |
return spinner; |
|
137 |
} |
|
138 |
||
139 |
@Override |
|
140 |
public void onCreate(Bundle savedInstanceState) { |
|
141 |
super.onCreate(savedInstanceState); |
|
142 |
try { |
|
143 |
stateManager = ((RoomStateManager.Provider)getActivity()).getRoomStateManager(); |
|
144 |
} catch(ClassCastException e) { |
|
145 |
throw new RuntimeException("Hosting activity must implement RoomStateManager.Provider.", e); |
|
146 |
} |
|
147 |
} |
|
148 |
||
149 |
@Override |
|
150 |
public void onDestroy() { |
|
151 |
super.onDestroy(); |
|
152 |
mapPreviewHandler.stop(); |
|
153 |
newPreviewRequest = null; |
|
154 |
||
155 |
stateManager.removeListener(roomStateChangeListener); |
|
156 |
} |
|
157 |
||
158 |
private void setChiefState(boolean chiefState) { |
|
159 |
mapTypeSpinner.setEnabled(chiefState); |
|
160 |
mapNameSpinner.setEnabled(chiefState); |
|
161 |
templateSpinner.setEnabled(chiefState); |
|
162 |
mazeSizeSpinner.setEnabled(chiefState); |
|
163 |
mapPreview.setEnabled(chiefState); |
|
7508
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
164 |
|
10017 | 165 |
if(chiefState) { |
166 |
sendMapnameAndGenerator(); |
|
167 |
stateManager.changeMapTemplate(templateSpinner.getSelectedItemPosition()); |
|
168 |
stateManager.changeMazeSize(mazeSizeSpinner.getSelectedItemPosition()); |
|
169 |
} |
|
170 |
} |
|
171 |
||
172 |
private void updateDisplay(MapRecipe map) { |
|
173 |
nameRow.setVisibility(map.mapgen == Frontlib.MAPGEN_NAMED ? View.VISIBLE : View.GONE); |
|
174 |
templateRow.setVisibility(map.mapgen == Frontlib.MAPGEN_REGULAR ? View.VISIBLE : View.GONE); |
|
175 |
mazeSizeRow.setVisibility(map.mapgen == Frontlib.MAPGEN_MAZE ? View.VISIBLE : View.GONE); |
|
176 |
||
177 |
mapTypeSpinner.setSelection(map.mapgen); |
|
178 |
int mapPosition = findMapPosition(mapFiles, map.name); |
|
179 |
if(mapPosition >= 0) { |
|
180 |
mapNameSpinner.setSelection(mapPosition); |
|
181 |
} |
|
182 |
templateSpinner.setSelection(map.templateFilter); |
|
183 |
mazeSizeSpinner.setSelection(map.mazeSize); |
|
184 |
} |
|
185 |
||
186 |
private static int findMapPosition(List<MapFile> mapFiles, String mapName) { |
|
187 |
for(int i=0; i<mapFiles.size(); i++) { |
|
188 |
if(mapName.equals(mapFiles.get(i).name)) { |
|
189 |
return i; |
|
190 |
} |
|
191 |
} |
|
192 |
return -1; |
|
193 |
} |
|
194 |
||
195 |
private void sendMapnameAndGenerator() { |
|
196 |
int mapType = mapTypeSpinner.getSelectedItemPosition(); |
|
197 |
String mapName = mapFiles.get(mapNameSpinner.getSelectedItemPosition()).name; |
|
198 |
stateManager.changeMapNameAndGenerator(MapRecipe.mapnameForGenerator(mapType, mapName)); |
|
199 |
} |
|
200 |
||
201 |
private final OnItemSelectedListener mapTypeSelectedListener = new OnItemSelectedListener() { |
|
202 |
public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) { |
|
203 |
sendMapnameAndGenerator(); |
|
204 |
} |
|
205 |
public void onNothingSelected(AdapterView<?> arg0) {} |
|
206 |
}; |
|
207 |
||
208 |
private final OnItemSelectedListener mapNameSelectedListener = new OnItemSelectedListener() { |
|
209 |
public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) { |
|
210 |
sendMapnameAndGenerator(); |
|
211 |
} |
|
212 |
public void onNothingSelected(AdapterView<?> arg0) {} |
|
213 |
}; |
|
214 |
||
215 |
private final OnItemSelectedListener mapTemplateSelectedListener = new OnItemSelectedListener() { |
|
216 |
public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) { |
|
217 |
stateManager.changeMapTemplate(position); |
|
218 |
} |
|
219 |
public void onNothingSelected(AdapterView<?> arg0) {} |
|
220 |
}; |
|
221 |
||
222 |
private final OnItemSelectedListener mazeSizeSelectedListener = new OnItemSelectedListener() { |
|
223 |
public void onItemSelected(AdapterView<?> adapter, View v, int position, long arg3) { |
|
224 |
stateManager.changeMazeSize(position); |
|
225 |
} |
|
226 |
public void onNothingSelected(AdapterView<?> arg0) {} |
|
227 |
}; |
|
228 |
||
229 |
private final OnClickListener mapClickListener = new OnClickListener() { |
|
230 |
public void onClick(View v) { |
|
231 |
stateManager.changeMapSeed(MapRecipe.makeRandomSeed()); |
|
232 |
if(mapTypeSpinner.getSelectedItemPosition() == Frontlib.MAPGEN_NAMED) { |
|
233 |
mapNameSpinner.setSelection(random.nextInt(mapNameSpinner.getCount())); |
|
234 |
} |
|
235 |
} |
|
236 |
}; |
|
237 |
||
238 |
private final RoomStateManager.Listener roomStateChangeListener = new RoomStateManager.ListenerAdapter() { |
|
239 |
@Override |
|
240 |
public void onChiefStatusChanged(boolean isChief) { |
|
241 |
setChiefState(isChief); |
|
242 |
}; |
|
243 |
||
244 |
@Override |
|
245 |
public void onMapChanged(MapRecipe recipe) { |
|
246 |
// Only trigger a preview update if a relevant field changed (not theme) |
|
247 |
if(currentMap==null |
|
248 |
|| currentMap.mapgen != recipe.mapgen |
|
249 |
|| currentMap.mazeSize != recipe.mazeSize |
|
250 |
|| !currentMap.name.equals(recipe.name) |
|
251 |
|| !currentMap.seed.equals(recipe.seed) |
|
252 |
|| currentMap.templateFilter != recipe.templateFilter |
|
253 |
|| !Arrays.equals(currentMap.getDrawData(), recipe.getDrawData())) { |
|
254 |
mapPreviewHandler.activity(); |
|
255 |
} |
|
256 |
updateDisplay(recipe); |
|
257 |
currentMap = recipe; |
|
258 |
}; |
|
259 |
}; |
|
260 |
||
261 |
private MapPreviewGenerator.Listener mapPreviewListener = new MapPreviewGenerator.Listener() { |
|
262 |
public void onMapPreviewResult(Drawable preview) { |
|
263 |
if(newPreviewRequest != null) { |
|
264 |
MapPreviewGenerator.startPreviewGeneration(getActivity().getApplicationContext(), newPreviewRequest, mapPreviewListener); |
|
265 |
newPreviewRequest = null; |
|
266 |
} else { |
|
267 |
if(mapPreview != null) { |
|
268 |
mapPreview.setImageDrawable(preview); |
|
269 |
} |
|
270 |
previewGenerationInProgress = false; |
|
271 |
} |
|
272 |
} |
|
273 |
}; |
|
7508
763d3961400b
Hedgeroid: Frantic scrabbling toward the deadline
Medo <smaxein@googlemail.com>
parents:
diff
changeset
|
274 |
} |