/[projects]/android/TrainInfo/src/dk/thoerup/traininfo/DepartureList.java
ViewVC logotype

Annotation of /android/TrainInfo/src/dk/thoerup/traininfo/DepartureList.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1442 - (hide annotations) (download)
Tue May 3 20:36:16 2011 UTC (13 years ago) by torben
File size: 18925 byte(s)
only enable notifications optionsmenu item if this view actually has notifs
1 torben 237 package dk.thoerup.traininfo;
2    
3 torben 1053 import static dk.thoerup.traininfo.R.string.departurelist_fetcharrivals;
4 torben 835 import static dk.thoerup.traininfo.R.string.departurelist_fetchdepartures;
5     import static dk.thoerup.traininfo.R.string.generic_cancel;
6     import static dk.thoerup.traininfo.R.string.generic_retry;
7    
8 torben 237 import java.text.NumberFormat;
9    
10 torben 336 import android.app.AlertDialog;
11 torben 237 import android.app.Dialog;
12     import android.app.ListActivity;
13     import android.app.ProgressDialog;
14 torben 1433 import android.content.ActivityNotFoundException;
15 torben 336 import android.content.DialogInterface;
16 torben 237 import android.content.Intent;
17 torben 1053 import android.graphics.Typeface;
18 torben 239 import android.net.Uri;
19 torben 237 import android.os.AsyncTask;
20     import android.os.Bundle;
21 torben 630 import android.util.Log;
22 torben 982 import android.view.Menu;
23     import android.view.MenuItem;
24 torben 237 import android.view.View;
25 torben 835 import android.view.View.OnClickListener;
26     import android.widget.Button;
27 torben 237 import android.widget.ListView;
28 torben 1053 import android.widget.TableLayout;
29     import android.widget.TableRow;
30 torben 237 import android.widget.TextView;
31 torben 1433 import android.widget.Toast;
32 torben 1066 import dk.thoerup.android.traininfo.common.DepartureBean;
33     import dk.thoerup.android.traininfo.common.DepartureEntry;
34     import dk.thoerup.android.traininfo.common.MetroBean;
35     import dk.thoerup.android.traininfo.common.MetroBean.MetroEntry;
36 torben 1408 import dk.thoerup.android.traininfo.common.StationEntry;
37 torben 255 import dk.thoerup.traininfo.provider.DepartureProvider;
38 torben 1053 import dk.thoerup.traininfo.provider.MetroProvider;
39 torben 253 import dk.thoerup.traininfo.provider.ProviderFactory;
40 torben 245 import dk.thoerup.traininfo.util.MessageBox;
41 torben 237
42     public class DepartureList extends ListActivity {
43    
44     public static final int DLG_PROGRESS = 1;
45 torben 982 static final int MENU_MAP = 100;
46     static final int MENU_NOTIFICATIONS = 101;
47 torben 1434 static final int MENU_METROMAP = 102;
48     static final int MENU_TOGGLEDETAILS= 103;
49 torben 237
50 torben 362
51 torben 237 DepartureListAdapter adapter;
52     DepartureProvider provider;
53 torben 981 DepartureBean departures;
54 torben 237
55 torben 1053 MetroBean metroBean;
56     MetroProvider metro;
57    
58 torben 257 int selectedItemId;
59     //DepartureBean currentDeparture;
60 torben 237
61     ProgressDialog pgDialog;
62 torben 362
63 torben 238 DepartureFetcher fetcher;
64 torben 1053 MetroFetcher metroFetcher;
65 torben 557
66 torben 1066 StationEntry station;
67 torben 238
68 torben 1250 String trainType = "REGIONAL";
69    
70 torben 1434 boolean arrival = false;
71 torben 981
72 torben 1017 int commFailCounter = 0;
73    
74 torben 237 @Override
75     protected void onCreate(Bundle savedInstanceState) {
76     super.onCreate(savedInstanceState);
77     setContentView(R.layout.departurelist);
78    
79     adapter = new DepartureListAdapter(this);
80 torben 1434 setListAdapter(adapter);
81 torben 237
82     Intent launchedBy = getIntent();
83 torben 557
84 torben 1066 station = (StationEntry) launchedBy.getSerializableExtra("stationbean");
85 torben 239
86 torben 557 ((TextView) findViewById(R.id.stationName)).setText( station.getName() );
87 torben 317
88 torben 557
89     ((TextView) findViewById(R.id.stationAddr)).setText( station.getAddress() );
90 torben 237
91 torben 835 final Button departureBtn = (Button) findViewById(R.id.departurebtn);
92     final Button arrivalBtn = (Button) findViewById(R.id.arrivalbtn);
93 torben 1044 final Button metroBtn = (Button) findViewById(R.id.metrobtn);
94 torben 1250 final Button regionalBtn = (Button) findViewById(R.id.regionalbtn);
95     final Button stogBtn = (Button) findViewById(R.id.stogbtn);
96 torben 294
97 torben 1053 final View metroView = findViewById(R.id.metroonly);
98    
99 torben 835 departureBtn.setOnClickListener( new OnClickListener() {
100     @Override
101     public void onClick(View arg0) {
102     arrivalBtn.setBackgroundResource(R.drawable.custom_button);
103     departureBtn.setBackgroundResource(R.drawable.custom_button_hilight);
104 torben 1044 metroBtn.setBackgroundResource(R.drawable.custom_button);
105 torben 1053
106     getListView().setVisibility( View.VISIBLE );
107     metroView.setVisibility( View.GONE );
108 torben 835 arrival = false;
109     startDepartureFetcher();
110     }
111     });
112     arrivalBtn.setOnClickListener( new OnClickListener() {
113     @Override
114     public void onClick(View arg0) {
115     arrivalBtn.setBackgroundResource(R.drawable.custom_button_hilight);
116     departureBtn.setBackgroundResource(R.drawable.custom_button);
117 torben 1044 metroBtn.setBackgroundResource(R.drawable.custom_button);
118 torben 1053
119     getListView().setVisibility( View.VISIBLE );
120     metroView.setVisibility( View.GONE );
121 torben 835 arrival = true;
122     startDepartureFetcher();
123     }
124     });
125    
126 torben 1250 regionalBtn.setOnClickListener( new OnClickListener() {
127     @Override
128     public void onClick(View arg0) {
129     regionalBtn.setBackgroundResource(R.drawable.custom_button_hilight);
130     stogBtn.setBackgroundResource(R.drawable.custom_button);
131     metroBtn.setBackgroundResource(R.drawable.custom_button);
132    
133     departureBtn.setVisibility( View.VISIBLE );
134     arrivalBtn.setVisibility( View.VISIBLE );
135    
136     getListView().setVisibility( View.VISIBLE );
137     metroView.setVisibility( View.GONE );
138     trainType = "REGIONAL";
139     startDepartureFetcher();
140     }
141     });
142     stogBtn.setOnClickListener( new OnClickListener() {
143     @Override
144     public void onClick(View arg0) {
145     regionalBtn.setBackgroundResource(R.drawable.custom_button);
146     stogBtn.setBackgroundResource(R.drawable.custom_button_hilight);
147     metroBtn.setBackgroundResource(R.drawable.custom_button);
148    
149    
150     departureBtn.setVisibility( View.VISIBLE );
151     arrivalBtn.setVisibility( View.VISIBLE );
152    
153     getListView().setVisibility( View.VISIBLE );
154     metroView.setVisibility( View.GONE );
155     trainType = "STOG";
156     startDepartureFetcher();
157     }
158     });
159    
160    
161    
162 torben 1044 metroBtn.setOnClickListener( new OnClickListener() {
163     @Override
164     public void onClick(View v) {
165 torben 1250 regionalBtn.setBackgroundResource(R.drawable.custom_button);
166     stogBtn.setBackgroundResource(R.drawable.custom_button);
167 torben 1044 metroBtn.setBackgroundResource(R.drawable.custom_button_hilight);
168 torben 1053
169 torben 1250 departureBtn.setVisibility( View.GONE );
170     arrivalBtn.setVisibility( View.GONE );
171    
172 torben 1053 getListView().setVisibility( View.GONE );
173     metroView.setVisibility( View.VISIBLE );
174     startMetroFetcher();
175 torben 1044 }
176     });
177 torben 835
178    
179    
180 torben 1044
181 torben 982 // findViewById(R.id.header).setOnClickListener( mapLauncher );
182 torben 237
183 torben 1066 int distance = station.getCalcdist();
184 torben 742 if (distance != 0) {
185     NumberFormat format = NumberFormat.getNumberInstance();
186     format.setMaximumFractionDigits(1);
187     format.setMinimumFractionDigits(1);
188 torben 575
189 torben 742 ((TextView) findViewById(R.id.stationDistance)).setText( format.format((double)distance/1000.0) + " km." );
190     } else {
191     ((TextView) findViewById(R.id.stationDistance)).setVisibility(View.GONE);
192     }
193 torben 552
194 torben 1028 ProviderFactory.purgeOldEntries(); //cleanup before fetching more data
195 torben 1044
196     Log.e("Station", station.toCSV() );
197    
198     if (station.isMetro() == false) {
199     metroBtn.setVisibility( View.GONE );
200     }
201 torben 1028
202 torben 1053 metro = ProviderFactory.getMetroProvider();
203    
204 torben 1250 if (station.isRegional() == false ) {
205     regionalBtn.setVisibility(View.GONE);
206     }
207    
208     if (station.isStrain() == false ) {
209     stogBtn.setVisibility(View.GONE);
210     }
211    
212     if (station.isRegional() == true && station.isStrain() == false ) {
213     if ( station.isMetro() == false )
214     regionalBtn.setVisibility(View.GONE);
215     trainType = "REGIONAL";
216     }
217    
218     if (station.isRegional() == false && station.isStrain() == true) {
219     if (station.isMetro() == false)
220     stogBtn.setVisibility(View.GONE);
221    
222     stogBtn.setBackgroundResource(R.drawable.custom_button_hilight);
223     trainType = "STOG";
224    
225     }
226    
227    
228 torben 1066 if (station.isRegional() == false && station.isStrain() == false) {
229 torben 552 getListView().setVisibility( View.GONE );
230 torben 1053 metroView.setVisibility( View.VISIBLE );
231    
232 torben 835 departureBtn.setVisibility( View.GONE );
233     arrivalBtn.setVisibility(View.GONE);
234 torben 1250 metroBtn.setVisibility( View.GONE );
235 torben 552
236 torben 1250
237    
238 torben 1053 if (savedInstanceState == null) {
239     startMetroFetcher();
240     } else {
241     metroBean = (MetroBean) savedInstanceState.getSerializable("metro");
242     loadMetroData();
243     }
244    
245 torben 257 } else {
246 torben 552 provider = ProviderFactory.getDepartureProvider();
247    
248     if (savedInstanceState == null) {
249     startDepartureFetcher();
250     } else {
251 torben 981 departures = (DepartureBean) savedInstanceState.getSerializable("departures");
252 torben 990
253     if ( (departures != null) && (departures.entries != null) ) {
254     adapter.setDepartures(departures.entries);
255     }
256 torben 982 selectedItemId = savedInstanceState.getInt("selectedItemId");
257    
258     if ( hasNotifications() ) {
259     findViewById(R.id.notifIcon).setVisibility(View.VISIBLE);
260     }
261    
262 torben 552 }
263 torben 257 }
264 torben 237 }
265    
266 torben 982 boolean hasNotifications() {
267     return (departures != null && departures.notifications.size() > 0);
268     }
269    
270 torben 243 @Override
271     public void onSaveInstanceState(Bundle outState)
272     {
273 torben 257 if (pgDialog != null && pgDialog.isShowing())
274     dismissDialog(DLG_PROGRESS);
275 torben 362
276 torben 257 outState.putInt("selectedItemId", selectedItemId);
277    
278 torben 981 outState.putSerializable("departures", departures);
279 torben 1053 outState.putSerializable("metro", metroBean);
280 torben 243 }
281 torben 918
282    
283 torben 243
284 torben 237 @Override
285 torben 918 protected void onDestroy() {
286     super.onDestroy();
287    
288     if (fetcher != null) {
289     fetcher.cancel(true);
290     }
291 torben 1053
292     if (metroFetcher != null) {
293     metroFetcher.cancel(true);
294     }
295 torben 918 }
296    
297     @Override
298 torben 237 protected void onListItemClick(ListView l, View v, int position, long id) {
299     super.onListItemClick(l, v, position, id);
300 torben 362
301     selectedItemId = position;
302 torben 364
303 torben 981 DepartureEntry dep = departures.entries.get(selectedItemId);
304 torben 237
305 torben 362 Intent intent = new Intent(this, TimetableList.class);
306 torben 364 intent.putExtra("departure", dep);
307 torben 362
308     startActivity(intent);
309    
310 torben 237 }
311    
312    
313     @Override
314     protected void onPrepareDialog(int id, Dialog dialog) {
315     super.onPrepareDialog(id, dialog);
316 torben 257
317 torben 237 switch (id) {
318     case DLG_PROGRESS:
319     pgDialog = (ProgressDialog) dialog;
320 torben 835 int messageId = arrival == false ? departurelist_fetchdepartures : departurelist_fetcharrivals;
321     pgDialog.setMessage( getString(messageId) );
322 torben 237 break;
323     }
324     }
325    
326     @Override
327     protected Dialog onCreateDialog(int id) {
328     switch (id) {
329     case DLG_PROGRESS:
330 torben 835
331 torben 237 ProgressDialog dlg = new ProgressDialog(this);
332     dlg.setCancelable(true);
333 torben 362 return dlg;
334 torben 237 default:
335     return super.onCreateDialog(id);
336     }
337     }
338 torben 982
339    
340 torben 336
341 torben 982 @Override
342 torben 1442 public boolean onPrepareOptionsMenu(Menu menu) {
343     super.onPrepareOptionsMenu(menu);
344    
345     MenuItem item = menu.findItem( MENU_NOTIFICATIONS );
346     boolean notifEnabled = hasNotifications();
347     item.setEnabled(notifEnabled);
348    
349     return true;
350     }
351    
352     @Override
353 torben 982 public boolean onCreateOptionsMenu(Menu menu) {
354     MenuItem item;
355 torben 985
356 torben 1434
357     item = menu.add(0, MENU_TOGGLEDETAILS, 0, getString(R.string.departurelist_toggledetails));
358     item.setIcon(android.R.drawable.ic_menu_view);
359    
360 torben 985 item = menu.add(0, MENU_MAP, 0, getString(R.string.departurelist_showonmap) );
361 torben 982 item.setIcon(android.R.drawable.ic_menu_mapmode);
362    
363 torben 985 item = menu.add(0, MENU_NOTIFICATIONS, 0, getString(R.string.departurelist_notifications) );
364 torben 1057 item.setIcon(android.R.drawable.ic_menu_info_details);
365 torben 982
366     boolean notifEnabled = hasNotifications();
367     item.setEnabled(notifEnabled);
368    
369 torben 1057 if (station.isMetro()) {
370     item = menu.add(0, MENU_METROMAP, 0, "Metro" ); //TODO:translate!?!
371     item.setIcon(android.R.drawable.ic_menu_mapmode);
372 torben 1434 }
373 torben 982
374     return true;
375     }
376    
377     @Override
378     public boolean onOptionsItemSelected(MenuItem item) {
379 torben 1434 boolean res = true;
380 torben 982 switch(item.getItemId()) {
381     case MENU_MAP:
382 torben 1434 Uri uri = Uri.parse("geo:" + station.getLatitude() + "," + station.getLongitude() + "?z=16");
383    
384     try {
385 torben 1432 startActivity( new Intent(Intent.ACTION_VIEW, uri));
386     } catch (ActivityNotFoundException anfe) {
387     Toast.makeText(this, "Could not launch google maps", Toast.LENGTH_LONG).show();
388     }
389 torben 1434
390 torben 982 break;
391     case MENU_NOTIFICATIONS:
392     Intent i = new Intent(this,dk.thoerup.traininfo.NotificationList.class);
393     i.putExtra(NotificationList.EXTRA_NOTIFICATIONS, departures.notifications);
394 torben 1434 startActivity(i);
395 torben 982 break;
396 torben 1057 case MENU_METROMAP:
397     Intent metroMap = new Intent(this,dk.thoerup.traininfo.MetroMap.class);
398 torben 1434 startActivity(metroMap);
399 torben 1057 break;
400 torben 1434 case MENU_TOGGLEDETAILS:
401     adapter.toggleShowDetails();
402     break;
403 torben 982 default:
404     res = super.onOptionsItemSelected(item);
405     }
406     return res;
407     }
408    
409 torben 336 void startDepartureFetcher() {
410     showDialog(DLG_PROGRESS);
411     fetcher = new DepartureFetcher();
412 torben 557 fetcher.execute(station.getId());
413 torben 336 }
414 torben 316
415 torben 1053 void startMetroFetcher() {
416     showDialog(DLG_PROGRESS);
417     metroFetcher = new MetroFetcher();
418     metroFetcher.execute(station.getId());
419     }
420    
421 torben 237 class DialogDismisser implements View.OnClickListener {
422    
423     Dialog dlg;
424     public DialogDismisser(Dialog d) {
425     dlg = d;
426     }
427    
428     @Override
429     public void onClick(View v) {
430     if (dlg.isShowing())
431     dlg.dismiss();
432 torben 365 }
433     }
434 torben 238
435 torben 982 /*View.OnClickListener mapLauncher = new View.OnClickListener() {
436 torben 239 @Override
437 torben 557 public void onClick(View v) {
438     Uri uri = Uri.parse("geo:" + station.getLatitude() + "," + station.getLongitude());
439 torben 239 startActivity( new Intent(Intent.ACTION_VIEW, uri));
440     }
441 torben 982 };*/
442 torben 336
443    
444 torben 238
445 torben 310 class DepartureFetcher extends AsyncTask<Integer, Void, Void> {
446 torben 238
447     @Override
448     protected void onPostExecute(Void result) {
449     super.onPostExecute(result);
450    
451 torben 336
452 torben 238 pgDialog.dismiss();
453 torben 1423
454 torben 1373 if (departures != null && departures.errorCode == null) {
455 torben 1017 commFailCounter = 0;
456 torben 917 DepartureList.this.getListView().setVisibility(View.GONE); //Experimental, inspired by http://osdir.com/ml/Android-Developers/2010-04/msg01198.html
457 torben 981 adapter.setDepartures(departures.entries);
458 torben 917 DepartureList.this.getListView().setVisibility(View.VISIBLE);
459    
460 torben 982
461 torben 1441 // handle notification icon.
462     View notifIcon = findViewById(R.id.notifIcon);
463     if ( hasNotifications() ) {
464 torben 1261 notifIcon.setVisibility(View.VISIBLE);
465     notifIcon.setClickable(true);
466     notifIcon.setOnClickListener( new View.OnClickListener() {
467     @Override
468     public void onClick(View v) {
469     Intent i = new Intent(DepartureList.this, dk.thoerup.traininfo.NotificationList.class);
470     i.putExtra(NotificationList.EXTRA_NOTIFICATIONS, departures.notifications);
471     startActivity(i);
472     }
473     });
474 torben 1441 } else {
475     notifIcon.setVisibility(View.INVISIBLE);
476     }
477 torben 982
478 torben 1264 if (departures.entries.size() == 0) {
479 torben 1262 int msgId = (arrival==false) ? R.string.departurelist_nodepartures : R.string.departurelist_noarrivals;
480     MessageBox.showMessage(DepartureList.this, getString(msgId), false);
481 torben 319 }
482     } else { // communication or parse error
483 torben 1017 commFailCounter++;
484 torben 1373 AlertDialog.Builder builder = new AlertDialog.Builder(DepartureList.this);
485    
486     if (departures != null && departures.errorCode != null ) { //got an error xml back
487     commFailCounter = 10;
488     builder.setMessage( getString(R.string.no_backend) );
489     } else {
490     builder.setMessage( getString(R.string.departurelist_fetcherror) );
491     }
492 torben 336 builder.setCancelable(true);
493 torben 1373
494 torben 1017 if (commFailCounter < 3) {
495     builder.setPositiveButton(getString(generic_retry), new DialogInterface.OnClickListener() {
496     public void onClick(DialogInterface dialog, int id) {
497     dialog.dismiss();
498     startDepartureFetcher();
499    
500     }
501     });
502     }
503 torben 561 builder.setNegativeButton(getString(generic_cancel), new DialogInterface.OnClickListener() {
504 torben 336 public void onClick(DialogInterface dialog, int id) {
505     dialog.dismiss();
506 torben 843 DepartureList.this.finish();
507 torben 336 }
508 torben 630 });
509    
510 torben 1263 try { //TODO: is this still necessary after the 0.9.4.1 fix ?
511 torben 630 builder.show();
512     } catch (android.view.WindowManager.BadTokenException e) {
513     Log.i("DepartureList", "BadTokenException"); // this can happen if the user switched away from this activity, while doInBackground was running
514     }
515 torben 319 }
516 torben 238 }
517    
518     @Override
519 torben 310 protected Void doInBackground(Integer... params) {
520 torben 1250 departures = provider.lookupDepartures(params[0], DepartureList.this.arrival, trainType);
521 torben 238 return null;
522     }
523    
524     }
525 torben 1053
526     public void loadMetroData() {
527     ((TextView) findViewById(R.id.operations)).setText( metroBean.operationInfo );
528     ((TextView) findViewById(R.id.plan)).setText( metroBean.plan );
529    
530    
531     TableLayout table = (TableLayout) findViewById(R.id.metrotable);
532     table.removeAllViews();
533    
534     TableRow head = new TableRow(this);
535    
536     TextView h1 = new TextView(this);
537     h1.setText("Metro");
538 torben 1072 h1.setTextSize(16);
539 torben 1053 h1.setTypeface( Typeface.defaultFromStyle(Typeface.BOLD));
540    
541    
542     TableRow.LayoutParams params = new TableRow.LayoutParams();
543     params.span = 2;
544     head.addView(h1, params);
545    
546 torben 1055
547    
548 torben 1053 TextView h2 = new TextView(this);
549 torben 1072 h2.setTextSize(16);
550 torben 1053 h2.setTypeface( Typeface.defaultFromStyle(Typeface.BOLD));
551 torben 1055 h2.setText("Om minutter");
552    
553 torben 1072 params = new TableRow.LayoutParams();
554     params.weight = 2;
555 torben 1055 head.addView(h2,params);
556 torben 1053
557    
558    
559     table.addView(head);
560    
561     for (MetroEntry entry : metroBean.entries) {
562     TableRow row = new TableRow(this);
563    
564     Log.e("Test", "" + entry.destination);
565    
566     TextView v1 = new TextView(this);
567 torben 1072 v1.setTextSize(16);
568     v1.setText( entry.metro );
569 torben 1053 row.addView(v1);
570    
571     TextView v2 = new TextView(this);
572 torben 1072 v2.setTextSize(16);
573 torben 1053 v2.setText( entry.destination );
574     row.addView(v2);
575 torben 1055
576 torben 1053 TextView v3 = new TextView(this);
577 torben 1072 v3.setTextSize(16);
578 torben 1053 v3.setText( entry.minutes );
579     row.addView(v3);
580    
581     table.addView(row);
582    
583     }
584     findViewById(R.id.rootView).requestLayout();
585     }
586    
587     class MetroFetcher extends AsyncTask<Integer, Void, Void> {
588    
589     @Override
590     protected void onPostExecute(Void result) {
591     super.onPostExecute(result);
592    
593    
594    
595     pgDialog.dismiss();
596    
597     if (metroBean != null) {
598     loadMetroData();
599     } else { // communication or parse error
600     commFailCounter++;
601     AlertDialog.Builder builder = new AlertDialog.Builder(DepartureList.this);
602     builder.setMessage("Error finding metro data");
603     builder.setCancelable(true);
604     if (commFailCounter < 3) {
605     builder.setPositiveButton(getString(generic_retry), new DialogInterface.OnClickListener() {
606     public void onClick(DialogInterface dialog, int id) {
607     dialog.dismiss();
608     startMetroFetcher();
609    
610     }
611     });
612     }
613     builder.setNegativeButton(getString(generic_cancel), new DialogInterface.OnClickListener() {
614     public void onClick(DialogInterface dialog, int id) {
615     dialog.dismiss();
616 torben 1263 DepartureList.this.finish(); //TODO: should we really close the activity ??
617 torben 1053 }
618     });
619    
620 torben 1263 try { //TODO: is this still necessary after the 0.9.4.1 fix ?
621 torben 1053 builder.show();
622     } catch (android.view.WindowManager.BadTokenException e) {
623     Log.i("DepartureList", "BadTokenException"); // this can happen if the user switched away from this activity, while doInBackground was running
624     }
625     }
626     }
627    
628     @Override
629     protected Void doInBackground(Integer... params) {
630     metroBean = metro.lookupMetroInfo(params[0]);
631     return null;
632     }
633    
634     }
635    
636 torben 237 }

  ViewVC Help
Powered by ViewVC 1.1.20