001package armyc2.c5isr.renderer.symbolpicker; 002 003import android.app.Activity; 004import android.app.AlertDialog; 005import android.app.Dialog; 006import android.content.Context; 007import android.content.Intent; 008import android.graphics.Bitmap; 009import android.graphics.Color; 010import android.os.Bundle; 011import androidx.core.widget.TextViewCompat; 012import android.text.Editable; 013import android.text.InputType; 014import android.text.TextWatcher; 015import android.util.SparseArray; 016import android.util.TypedValue; 017import android.view.LayoutInflater; 018import android.view.View; 019import android.view.ViewGroup; 020import android.view.Window; 021import android.view.WindowManager; 022import android.widget.AdapterView; 023import android.widget.ArrayAdapter; 024import android.widget.Button; 025import android.widget.CheckBox; 026import android.widget.CompoundButton; 027import android.widget.DatePicker; 028import android.widget.EditText; 029import android.widget.GridView; 030import android.widget.ImageView; 031import android.widget.LinearLayout; 032import android.widget.ListView; 033import android.widget.RadioGroup; 034import android.widget.SearchView; 035import android.widget.Spinner; 036import android.widget.TextView; 037import android.widget.TimePicker; 038import android.widget.Toast; 039import android.widget.ToggleButton; 040 041import androidx.core.widget.TextViewCompat; 042 043import java.io.BufferedReader; 044import java.io.IOException; 045import java.io.InputStream; 046import java.io.InputStreamReader; 047import java.text.DecimalFormat; 048import java.util.ArrayList; 049import java.util.Calendar; 050import java.util.Collections; 051import java.util.Comparator; 052import java.util.Date; 053import java.util.GregorianCalendar; 054import java.util.HashMap; 055import java.util.List; 056import java.util.Locale; 057import java.util.Map; 058import java.util.Objects; 059import java.util.Stack; 060import java.util.TreeMap; 061import java.util.stream.Collectors; 062 063import armyc2.c5isr.renderer.MilStdIconRenderer; 064import armyc2.c5isr.renderer.R; 065import armyc2.c5isr.renderer.utilities.DrawRules; 066import armyc2.c5isr.renderer.utilities.ImageInfo; 067import armyc2.c5isr.renderer.utilities.MSInfo; 068import armyc2.c5isr.renderer.utilities.MSLookup; 069import armyc2.c5isr.renderer.utilities.MilStdAttributes; 070import armyc2.c5isr.renderer.utilities.Modifiers; 071import armyc2.c5isr.renderer.utilities.RendererSettings; 072import armyc2.c5isr.renderer.utilities.SVGInfo; 073import armyc2.c5isr.renderer.utilities.SVGLookup; 074import armyc2.c5isr.renderer.utilities.SymbolID; 075import armyc2.c5isr.renderer.utilities.SymbolUtilities; 076import armyc2.c5isr.web.render.MultiPointHandler; 077 078/** 079 * Symbol picker activity. Sends selected symbol ID back to activity initialized from 080 */ 081public class SymbolPickerActivity extends Activity { 082 public static final String selectedSymbolIdKey = "selectedSymbolIdKey"; 083 public static final String modifiersKey = "modifiersKey"; 084 public static final String attributesKey = "attributesKey"; 085 public static final String supportedVersionsKey = "supportedVersionsKey"; 086 private static final String searchNodeName = "searchResults"; 087 private boolean activeSearch = false; 088 private final int cellSize = 85 * RendererSettings.getInstance().getDeviceDPI() / 96; // in px 089 private Stack<Node> pageTrail; // top of stack is current page 090 private SymbolGVAdapter symbolTableAdapter; 091 private Button configureButton; 092 private ToggleButton flattenTreeToggle; 093 private Node selectedSymbolNode; 094 private MilStdIconRenderer mir = null; 095 private final Bitmap emptyBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 096 097 // Main Modifiers Dialog 098 private Dialog mainModifiersDialog; 099 private RadioGroup contextRadioGroup; 100 private Spinner stdIdSpinner; 101 private Spinner statusSpinner; 102 private Spinner hqSpinner; 103 private Spinner ampCategorySpinner; 104 private Spinner ampTypeSpinner; 105 private LinearLayout ampTypeLayout; 106 private boolean hasSectorModifiers; 107 private Spinner sector1Spinner; 108 private final int SECTOR_1_MINE_INDEX = 13; // position of Mine in ss_ControlMeasure_sector1_array 109 private final int ANTIPERSONNEL_MINE = 0b000001; 110 private final int ANTIPERSONNEL_MINE_DIRECTIONAL = 0b000010; 111 private final int ANTITANK_MINE = 0b000100; 112 private final int ANTITANK_MINE_ANTIHANDLING = 0b001000; 113 private final int WIDE_AREA_ANTITANK_MINE = 0b010000; 114 private final int MINE_CLUSTER = 0b100000; 115 private SparseArray<String> mineSectorLookup; 116 private ArrayList<Integer> invalidMineTrios; 117 private CheckBox AntipersonnelMineBox; 118 private CheckBox AntipersonnelDirectionalMineBox; 119 private CheckBox AntitankMineBox; 120 private CheckBox AntitankAntihandlingMineBox; 121 private CheckBox WideAreaAntitankMineBox; 122 private CheckBox MineClusterBox; 123 private ArrayList<CheckBox> mineCheckBoxList; 124 private int numMinesChecked; 125 private Spinner sector2Spinner; 126 private boolean hasCountryModifier; 127 private TextView countryTextView; 128 private TreeMap<String, String> countryMap; 129 private ArrayList<String> countryNames; 130 private Dialog countrySearchDialog; 131 132 // Attributes + Extra Modifiers Dialog 133 private Dialog extraModifiersDialog; 134 private static final int M = 0; 135 private static final int KM = 1; 136 private static final int FT = 2; 137 private static final int SM = 3; 138 private static final int FL = 4; 139 enum AltitudeUnits { 140 M(SymbolPickerActivity.M, "(meters)"), 141 KM(SymbolPickerActivity.KM, "(kilometers)"), 142 FT(SymbolPickerActivity.FT, "(feet)"), 143 SM(SymbolPickerActivity.SM, "(statute miles)"), 144 FL(SymbolPickerActivity.FL, "(flight level)"); 145 146 private final int index; 147 private final String desc; 148 149 AltitudeUnits(int index, String description) { 150 this.index = index; 151 this.desc = description; 152 } 153 } 154 enum AltitudeModes { 155 AMSL(0, "(above mean sea level)"), 156 BMSL(1, "(below mean sea level)"), 157 HAE(2, "(height above ellipsoid)"), 158 AGL(3, "(above ground level)"); 159 160 private final int index; 161 private final String desc; 162 163 AltitudeModes(int index, String description) { 164 this.index = index; 165 this.desc = description; 166 } 167 } 168 169 Spinner altitudeUnitSpinner; 170 Spinner altitudeModeSpinner; 171 private ArrayList<String> modifiersToGet; 172 private HashMap<String, String> modifiersToSend; 173 private HashMap<String, String> attributesToSend; 174 private TreeManager treeManager; 175 176 @Override 177 protected void onCreate(Bundle savedInstanceState) { 178 super.onCreate(savedInstanceState); 179 setContentView(R.layout.activity_symbol_picker); 180 181 pageTrail = new Stack<>(); 182 183 Button backButton = findViewById(R.id.symbol_picker_back_button); 184 backButton.setOnClickListener(view -> onBackPressed()); 185 186 configureButton = findViewById(R.id.symbol_picker_configure_button); 187 configureButton.setOnClickListener(view -> onConfigureSymbol()); 188 189 symbolTableAdapter = new SymbolGVAdapter(this, new ArrayList<>()); 190 GridView symbolTable = findViewById(R.id.symbol_picker_table); 191 symbolTable.setAdapter(symbolTableAdapter); 192 symbolTable.setColumnWidth(cellSize); 193 194 flattenTreeToggle = findViewById(R.id.symbol_picker_flatten_toggle); 195 flattenTreeToggle.setOnClickListener(view -> updateSymbolTable()); 196 197 // Do not reinitialize render or change settings in child activity 198 mir = MilStdIconRenderer.getInstance(); 199 200 treeManager = new TreeManager(); 201 try { 202 int[] versions = getIntent().getIntArrayExtra(supportedVersionsKey); 203 if (versions == null) 204 versions = new int[]{SymbolID.Version_2525Dch1}; 205 treeManager.buildTree(getApplicationContext(), versions); 206 } catch (IOException e) { 207 throw new RuntimeException(e); 208 } 209 210 updateSelectedSymbol(treeManager.mil2525Tree); 211 212 // read in country names and codes 213 String line; 214 String[] segments; 215 countryMap = new TreeMap<>(); 216 try (InputStream in = this.getResources().openRawResource(R.raw.genc); 217 BufferedReader reader = new BufferedReader(new InputStreamReader(in))) { 218 while ((line = reader.readLine()) != null) { 219 segments = line.split("\t+"); 220 StringBuilder countryCode = new StringBuilder(segments[1]); 221 while (countryCode.length() < 3) { 222 countryCode.insert(0, "0"); 223 } 224 countryMap.put(segments[2], countryCode.toString()); 225 } 226 } catch (IOException e) { 227 throw new RuntimeException(e); 228 } 229 countryNames = new ArrayList<>(countryMap.keySet()); 230 Collections.sort(countryNames); 231 232 SearchView symbolSearchView = findViewById(R.id.symbol_picker_search); 233 symbolSearchView.setOnQueryTextListener(new SearchBoxListener()); 234 235 // these four combinations of 3 mines do not have a code in 2525D Change 1 236 invalidMineTrios = new ArrayList<>(); 237 invalidMineTrios.add(ANTITANK_MINE | ANTITANK_MINE_ANTIHANDLING | WIDE_AREA_ANTITANK_MINE); 238 invalidMineTrios.add(ANTITANK_MINE | ANTITANK_MINE_ANTIHANDLING | MINE_CLUSTER); 239 invalidMineTrios.add(ANTITANK_MINE | WIDE_AREA_ANTITANK_MINE | MINE_CLUSTER); 240 invalidMineTrios.add(ANTITANK_MINE_ANTIHANDLING | WIDE_AREA_ANTITANK_MINE | MINE_CLUSTER); 241 } 242 243 @Override 244 public void onBackPressed() { 245 if (pageTrail.peek().getName().equals(searchNodeName)) { 246 // Clearing the query will remove the search page 247 SearchView symbolSearchView = findViewById(R.id.symbol_picker_search); 248 symbolSearchView.setQuery("", false); 249 symbolSearchView.clearFocus(); 250 } else if (pageTrail.size() > 1) { 251 // Go to next higher level 252 pageTrail.pop(); 253 updateSelectedSymbol(pageTrail.pop()); 254 } else { 255 // Ask if the user wishes to exit 256 new AlertDialog.Builder(this) 257 .setTitle("Confirm Exit") 258 .setMessage("Do you want to exit the symbol picker?") 259 .setPositiveButton(android.R.string.cancel, null) 260 .setNegativeButton(android.R.string.ok, (dialog, whichButton) -> { 261 // Exit symbol picker return blank symbol code 262 Intent resultIntent = new Intent(); 263 resultIntent.putExtra(selectedSymbolIdKey, ""); 264 setResult(Activity.RESULT_OK, resultIntent); 265 finish(); 266 }) 267 .show(); 268 } 269 } 270 271 // Updates symbol preview on modifier change 272 private class ModifierSpinnerListener implements AdapterView.OnItemSelectedListener { 273 @Override 274 public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) { 275 updateSymbolPreview(); 276 277 // Disables altitudeModeSpinner if user selects "FL" (flight level) because it doesn't use an altitude mode 278 // (though the selected Mode would be ignored anyway) 279 if (altitudeUnitSpinner != null && altitudeModeSpinner != null) { 280 altitudeModeSpinner.setEnabled(altitudeUnitSpinner.getSelectedItemPosition() != AltitudeUnits.valueOf("FL").index); 281 } 282 } 283 284 @Override 285 public void onNothingSelected(AdapterView<?> adapterView) { 286 updateSymbolPreview(); 287 } 288 } 289 290 // Called when "configure symbol" is clicked 291 private void onConfigureSymbol() { 292 // There's no good way to send a SparseArray<String> in an Intent, so use HashMaps here and 293 // the calling application can build SparseArrays from them. 294 modifiersToSend = new HashMap<>(); 295 attributesToSend = new HashMap<>(); 296 297 final int selectedSymbolVersion = Integer.parseInt(selectedSymbolNode.getVersion()); 298 final String selectedSymbolSet = selectedSymbolNode.getSymbolSetCode(); 299 final String selectedSymbolEntityCode = selectedSymbolNode.getCode(); 300 final String selectedSymbolName = selectedSymbolNode.getName(); 301 302 // TODO temporarily bypasses weather all modifiers because the extra ones that are applicable are not implemented yet (and thus sends empty modifiers HashMap back up) 303 switch (Integer.parseInt(selectedSymbolSet)) { 304 case SymbolID.SymbolSet_Atmospheric: 305 case SymbolID.SymbolSet_Oceanographic: 306 case SymbolID.SymbolSet_MeteorologicalSpace: 307 // for debugging modifiers: 308 /*MSInfo msi = MSLookup.getInstance().getMSLInfo(symbolSetCode + selectedSymbolEntityCode,0); 309 ArrayList<Integer> initialModifiers = msi.getModifiers(); 310 ArrayList<Integer> ignoreModifiers = Modifiers.GetPredeterminedModifiersList(); 311 modifiersToGet = new ArrayList<>(); 312 for (int i : initialModifiers) { 313 if (!ignoreModifiers.contains(i)) { 314 modifiersToGet.add(i); 315 Log.d("onSelectPressed", "added modifier: " + i); 316 } 317 }*/ 318 319 // construct neutral present (---4--0) code for all weather symbols 320 String weatherSymbolID = selectedSymbolVersion + "04" + selectedSymbolSet + "0000" + selectedSymbolEntityCode + "00000000000000"; 321 Intent resultIntent = new Intent(); 322 resultIntent.putExtra(selectedSymbolIdKey, weatherSymbolID); 323 resultIntent.putExtra(modifiersKey, modifiersToSend); 324 resultIntent.putExtra(attributesKey, attributesToSend); 325 setResult(Activity.RESULT_OK, resultIntent); 326 finish(); 327 return; 328 } 329 330 mainModifiersDialog = new Dialog(this); 331 mainModifiersDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 332 mainModifiersDialog.setContentView(R.layout.modifiers_page1); 333 mainModifiersDialog.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); 334 mainModifiersDialog.show(); 335 336 contextRadioGroup = mainModifiersDialog.findViewById(R.id.context_radio_group); 337 contextRadioGroup.setOnCheckedChangeListener((radioGroup, i) -> updateSymbolPreview()); 338 339 stdIdSpinner = mainModifiersDialog.findViewById(R.id.std_id_spinner); 340 ArrayAdapter<CharSequence> stdIdAdapter = ArrayAdapter.createFromResource(this, 341 R.array.std_id_array, android.R.layout.simple_spinner_item); 342 stdIdAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 343 stdIdSpinner.setAdapter(stdIdAdapter); 344 stdIdSpinner.setSelection(3); // Default to friendly identity 345 stdIdSpinner.setOnItemSelectedListener(new ModifierSpinnerListener()); 346 347 statusSpinner = mainModifiersDialog.findViewById(R.id.status_spinner); 348 ArrayAdapter<CharSequence> statusAdapter = ArrayAdapter.createFromResource(this, 349 R.array.status_array, android.R.layout.simple_spinner_item); 350 statusAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 351 statusSpinner.setAdapter(statusAdapter); 352 statusSpinner.setOnItemSelectedListener(new ModifierSpinnerListener()); 353 354 hqSpinner = mainModifiersDialog.findViewById(R.id.hq_spinner); 355 ArrayAdapter<CharSequence> hqAdapter = ArrayAdapter.createFromResource(this, 356 R.array.hq_array, android.R.layout.simple_spinner_item); 357 hqAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 358 hqSpinner.setAdapter(hqAdapter); 359 hqSpinner.setOnItemSelectedListener(new ModifierSpinnerListener()); 360 361 ampCategorySpinner = mainModifiersDialog.findViewById(R.id.amplifier_category_spinner); 362 ArrayAdapter<CharSequence> ampCategoryAdapter = ArrayAdapter.createFromResource(this, 363 R.array.amplifier_category_array, android.R.layout.simple_spinner_item); 364 ampCategoryAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 365 ampCategorySpinner.setAdapter(ampCategoryAdapter); 366 ampCategorySpinner.setOnItemSelectedListener(new AmplifierSelectedItemListener()); 367 368 ampTypeLayout = mainModifiersDialog.findViewById(R.id.amplifier_type_layout); 369 ampTypeLayout.setVisibility(View.GONE); 370 371 ampTypeSpinner = mainModifiersDialog.findViewById(R.id.amplifier_type_spinner); 372 ArrayAdapter<CharSequence> ampTypeAdapter = ArrayAdapter.createFromResource(this, 373 R.array.amp0_unknown_array, android.R.layout.simple_spinner_item); 374 ampTypeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 375 ampTypeSpinner.setAdapter(ampTypeAdapter); 376 ampTypeSpinner.setOnItemSelectedListener(new ModifierSpinnerListener()); 377 378 int sector1ArrayID; 379 int sector2ArrayID; 380 381 switch (Integer.parseInt(selectedSymbolSet)) { 382 case SymbolID.SymbolSet_Air: 383 sector1ArrayID = R.array.ss_Air_sector1_array; 384 sector2ArrayID = R.array.ss_Air_sector2_array; 385 break; 386 case SymbolID.SymbolSet_AirMissile: 387 sector1ArrayID = R.array.ss_AirMissile_sector1_array; 388 sector2ArrayID = R.array.ss_AirMissile_sector2_array; 389 break; 390 case SymbolID.SymbolSet_Space: 391 sector1ArrayID = R.array.ss_Space_sector1_array; 392 sector2ArrayID = R.array.ss_Space_sector2_array; 393 break; 394 case SymbolID.SymbolSet_SpaceMissile: 395 sector1ArrayID = R.array.ss_SpaceMissile_sector1_array; 396 sector2ArrayID = R.array.ss_SpaceMissile_sector2_array; 397 break; 398 case SymbolID.SymbolSet_LandUnit: 399 sector1ArrayID = R.array.ss_LandUnit_sector1_array; 400 sector2ArrayID = R.array.ss_LandUnit_sector2_array; 401 break; 402 case SymbolID.SymbolSet_LandCivilianUnit_Organization: 403 sector1ArrayID = R.array.ss_LandCivilianUnitOrganization_sector1_array; 404 sector2ArrayID = R.array.ss_LandCivilianUnitOrganization_sector2_array; 405 break; 406 case SymbolID.SymbolSet_LandEquipment: 407 sector1ArrayID = R.array.ss_LandEquipment_sector1_array; 408 sector2ArrayID = R.array.ss_LandEquipment_sector2_array; 409 break; 410 case SymbolID.SymbolSet_LandInstallation: 411 sector1ArrayID = R.array.ss_LandInstallation_sector1_array; 412 sector2ArrayID = R.array.ss_LandInstallation_sector2_array; 413 break; 414 case SymbolID.SymbolSet_ControlMeasure: 415 // Control Measures only use Sector 1 416 sector1ArrayID = R.array.ss_ControlMeasure_sector1_array; 417 sector2ArrayID = R.array.ss_NotApplicable_array; 418 break; 419 case SymbolID.SymbolSet_SeaSurface: 420 sector1ArrayID = R.array.ss_SeaSurface_sector1_array; 421 sector2ArrayID = R.array.ss_SeaSurface_sector2_array; 422 break; 423 case SymbolID.SymbolSet_SeaSubsurface: 424 sector1ArrayID = R.array.ss_SeaSubsurface_sector1_array; 425 sector2ArrayID = R.array.ss_SeaSubsurface_sector2_array; 426 break; 427 case SymbolID.SymbolSet_Activities: 428 sector1ArrayID = R.array.ss_Activities_sector1_array; 429 sector2ArrayID = R.array.ss_Activities_sector2_array; 430 break; 431 case SymbolID.SymbolSet_SignalsIntelligence_Space: 432 case SymbolID.SymbolSet_SignalsIntelligence_Air: 433 case SymbolID.SymbolSet_SignalsIntelligence_Land: 434 case SymbolID.SymbolSet_SignalsIntelligence_SeaSurface: 435 case SymbolID.SymbolSet_SignalsIntelligence_SeaSubsurface: 436 sector1ArrayID = R.array.ss_SignalsIntelligence_sector1_array; 437 sector2ArrayID = R.array.ss_SignalsIntelligence_sector2_array; 438 break; 439 default: 440 // Unknown, MineWarfare, Cyberspace 441 // (also Atmospheric, Oceanographic, and MeteorologicalSpace if we don't skip their modifier dialogs) 442 sector1ArrayID = R.array.ss_NotApplicable_array; 443 sector2ArrayID = R.array.ss_NotApplicable_array; 444 break; 445 } 446 447 View sectorsView = mainModifiersDialog.findViewById(R.id.sectors_layout); 448 if (sector1ArrayID == R.array.ss_NotApplicable_array) { 449 sectorsView.setVisibility(View.GONE); 450 hasSectorModifiers = false; 451 } else { 452 sectorsView.setVisibility(View.VISIBLE); 453 hasSectorModifiers = true; 454 455 sector1Spinner = mainModifiersDialog.findViewById(R.id.sector_1_spinner); 456 ArrayAdapter<CharSequence> sector1Adapter; 457 String[] landUnitSector1Mods = getResources().getStringArray(R.array.ss_LandUnit_sector1_array); 458 if (sector1ArrayID == R.array.ss_LandUnit_sector1_array) { 459 // create custom adapter to skip two "{Reserved for future use}" codes without altering the positions of the rest 460 sector1Adapter = new ArrayAdapter<CharSequence>(this, android.R.layout.simple_spinner_item, landUnitSector1Mods) { 461 @Override 462 public View getDropDownView(int position, View convertView, ViewGroup parent) { 463 View v; 464 465 if ("{Reserved for future use}".equals(landUnitSector1Mods[position])) { 466 TextView tv = new TextView(getContext()); 467 tv.setHeight(0); 468 tv.setVisibility(View.GONE); 469 v = tv; 470 } else { 471 // Pass convertView as null to prevent reuse of special case views 472 v = super.getDropDownView(position, null, parent); 473 } 474 return v; 475 } 476 }; 477 } else { 478 sector1Adapter = ArrayAdapter.createFromResource(this, 479 sector1ArrayID, android.R.layout.simple_spinner_item); 480 } 481 482 sector1Adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 483 sector1Spinner.setAdapter(sector1Adapter); 484 sector1Spinner.setOnItemSelectedListener(new ModifierSpinnerListener()); 485 486 sector2Spinner = mainModifiersDialog.findViewById(R.id.sector_2_spinner); 487 ArrayAdapter<CharSequence> sector2Adapter = ArrayAdapter.createFromResource(this, 488 sector2ArrayID, android.R.layout.simple_spinner_item); 489 sector2Adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 490 sector2Spinner.setAdapter(sector2Adapter); 491 sector2Spinner.setOnItemSelectedListener(new ModifierSpinnerListener()); 492 } 493 494 boolean isControlMeasure = (Integer.parseInt(selectedSymbolSet) == SymbolID.SymbolSet_ControlMeasure); 495 if (isControlMeasure) { 496 // set up Mines checkboxes for Control Measures 497 View mineContainer = mainModifiersDialog.findViewById(R.id.mine_type_layout); 498 sector1Spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 499 @Override 500 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { 501 if (position == SECTOR_1_MINE_INDEX) { 502 mineContainer.setVisibility(View.VISIBLE); 503 } else { 504 mineContainer.setVisibility(View.GONE); 505 } 506 updateSymbolPreview(); // necessary since this ItemSelectedListener overwrites the previously set ModifierSpinnerListener 507 } 508 509 @Override 510 public void onNothingSelected(AdapterView<?> parent) { 511 // Auto-generated method stub 512 } 513 }); 514 515 String line; 516 String[] segments; 517 mineSectorLookup = new SparseArray<>(51); // needs space for indices up to 50 518 try (InputStream in = this.getResources().openRawResource(R.raw.mine_bitmasks_to_codes); 519 BufferedReader reader = new BufferedReader(new InputStreamReader(in))) { 520 while ((line = reader.readLine()) != null) { 521 segments = line.split("\t+"); 522 // converts segments[0] binary string to int index, and reads segments[1] as string for symbol code 523 mineSectorLookup.put(Integer.parseInt(segments[0], 2), segments[1]); 524 } 525 } catch (IOException e) { 526 throw new RuntimeException(e); 527 } 528 529 AntipersonnelMineBox = mainModifiersDialog.findViewById(R.id.antipersonnel_mine_checkbox); 530 AntipersonnelDirectionalMineBox = mainModifiersDialog.findViewById(R.id.antipersonnel_mine_directional_checkbox); 531 AntitankMineBox = mainModifiersDialog.findViewById(R.id.antitank_mine_checkbox); 532 AntitankAntihandlingMineBox = mainModifiersDialog.findViewById(R.id.antitank_mine_antihandling_checkbox); 533 WideAreaAntitankMineBox = mainModifiersDialog.findViewById(R.id.wide_area_antitank_mine_checkbox); 534 MineClusterBox = mainModifiersDialog.findViewById(R.id.mine_cluster_checkbox); 535 536 mineCheckBoxList = new ArrayList<>(); 537 mineCheckBoxList.add(AntipersonnelMineBox); 538 mineCheckBoxList.add(AntipersonnelDirectionalMineBox); 539 mineCheckBoxList.add(AntitankMineBox); 540 mineCheckBoxList.add(AntitankAntihandlingMineBox); 541 mineCheckBoxList.add(WideAreaAntitankMineBox); 542 mineCheckBoxList.add(MineClusterBox); 543 544 CompoundButton.OnCheckedChangeListener mineSelectionLimiter = (cb, isChecked) -> { 545 if (isChecked) { 546 numMinesChecked++; 547 if (numMinesChecked == 3) { 548 for (CheckBox mine : mineCheckBoxList) { 549 if (!mine.isChecked()) { 550 mine.setEnabled(false); 551 } 552 } 553 } 554 } else { 555 numMinesChecked--; 556 for (CheckBox mine : mineCheckBoxList) { 557 mine.setEnabled(true); 558 } 559 } 560 updateSymbolPreview(); 561 }; 562 563 for (CheckBox mine : mineCheckBoxList) { 564 mine.setOnCheckedChangeListener(mineSelectionLimiter); 565 } 566 } 567 568 View countryView = mainModifiersDialog.findViewById(R.id.country_layout); 569 MSInfo msi = MSLookup.getInstance().getMSLInfo(selectedSymbolSet + selectedSymbolEntityCode, selectedSymbolVersion); 570 if (!msi.getModifiers().contains(Modifiers.AS_COUNTRY)) { 571 countryView.setVisibility(View.GONE); 572 hasCountryModifier = false; 573 } else { 574 countryView.setVisibility(View.VISIBLE); 575 hasCountryModifier = true; 576 countryTextView = mainModifiersDialog.findViewById(R.id.country_textview); 577 countryTextView.setBackgroundColor(Color.TRANSPARENT); 578 579 countryTextView.setOnClickListener(v -> { 580 countrySearchDialog = new Dialog(this); 581 countrySearchDialog.setContentView(R.layout.country_selector); 582 countrySearchDialog.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); 583 countrySearchDialog.show(); 584 585 EditText editText = countrySearchDialog.findViewById(R.id.country_edittext); 586 editText.setBackgroundColor(Color.TRANSPARENT); 587 ListView listView = countrySearchDialog.findViewById(R.id.country_listview); 588 589 ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, countryNames); 590 listView.setAdapter(adapter); 591 editText.addTextChangedListener(new TextWatcher() { 592 @Override 593 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 594 // Auto-generated method stub 595 } 596 597 @Override 598 public void onTextChanged(CharSequence s, int start, int before, int count) { 599 adapter.getFilter().filter(s); 600 } 601 602 @Override 603 public void afterTextChanged(Editable s) { 604 // Auto-generated method stub 605 } 606 }); 607 608 listView.setOnItemClickListener((parent, view, position, id) -> { 609 countryTextView.setText(adapter.getItem(position)); 610 countrySearchDialog.dismiss(); 611 updateSymbolPreview(); 612 }); 613 }); 614 } 615 616 Button modifiersBackButton = mainModifiersDialog.findViewById(R.id.modifiers_page1_back_button); 617 modifiersBackButton.setOnClickListener(w -> mainModifiersDialog.dismiss()); 618 619 ViewGroup symbolPreviewView = mainModifiersDialog.findViewById(R.id.symbol_picker_modifiers_preview); 620 TextView symbolPreviewTV = symbolPreviewView.findViewById(R.id.symbol_picker_cell_TV); 621 symbolPreviewTV.setText(selectedSymbolName); 622 TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(symbolPreviewTV, 8, 623 12, 1, TypedValue.COMPLEX_UNIT_SP); 624 ViewGroup.LayoutParams params = symbolPreviewView.getLayoutParams(); 625 params.height = cellSize; 626 params.width = cellSize; 627 symbolPreviewView.setLayoutParams(params); 628 629 Button sendButton = mainModifiersDialog.findViewById(R.id.send_button); 630 sendButton.setOnClickListener(view -> onPickSymbol()); 631 sendButton.setText(getString(R.string.send_btn_label, "'" + selectedSymbolName + "'")); 632 633 // build list of modifiers that aren't already determined by the symbol code 634 ArrayList<String> initialModifiers = msi.getModifiers(); 635 ArrayList<String> ignoreModifiers = Modifiers.GetSymbolCodeModifiersList(); 636 modifiersToGet = new ArrayList<>(); 637 for (String i : initialModifiers) { 638 if (!ignoreModifiers.contains(i)) { 639 modifiersToGet.add(i); 640 } 641 } 642 643 Button extraModifiersButton = mainModifiersDialog.findViewById(R.id.extra_modifiers_button); 644 if (!modifiersToGet.isEmpty() || isControlMeasure) { 645 extraModifiersButton.setEnabled(true); 646 extraModifiersButton.setOnClickListener(v -> { 647 extraModifiersDialog = new Dialog(this); 648 extraModifiersDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 649 extraModifiersDialog.setContentView(R.layout.modifiers_page2); 650 extraModifiersDialog.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); 651 extraModifiersDialog.show(); 652 653 altitudeUnitSpinner = null; 654 altitudeModeSpinner = null; 655 656 // Attributes 657 LinearLayout attributesLayout = extraModifiersDialog.findViewById(R.id.attributes_layout); 658 CheckBox outlineCheckbox = extraModifiersDialog.findViewById(R.id.outline_checkbox); 659 // show outline checkbox just for single-point Control Measures 660 String tempCode = selectedSymbolVersion + "03" + selectedSymbolSet + "0000" + selectedSymbolEntityCode + "0000"; 661 if (isControlMeasure && SymbolUtilities.isMultiPoint(tempCode) == false) { 662 outlineCheckbox.setVisibility(View.VISIBLE); 663 } 664 665 EditText lineColorField = extraModifiersDialog.findViewById(R.id.edit_LineColor); 666 EditText fillColorField = extraModifiersDialog.findViewById(R.id.edit_FillColor); 667 EditText lineWidthField = extraModifiersDialog.findViewById(R.id.edit_LineWidth); 668 EditText textColorField = extraModifiersDialog.findViewById(R.id.edit_TextColor); 669 if (isControlMeasure) { 670 attributesLayout.setVisibility(View.VISIBLE); 671 // restores previous values if dialog was reopened 672 outlineCheckbox.setChecked(Boolean.parseBoolean(attributesToSend.get(MilStdAttributes.OutlineSymbol))); 673 lineColorField.setText(attributesToSend.getOrDefault(MilStdAttributes.LineColor, "")); 674 fillColorField.setText(attributesToSend.getOrDefault(MilStdAttributes.FillColor, "")); 675 lineWidthField.setText(attributesToSend.getOrDefault(MilStdAttributes.LineWidth, "")); 676 textColorField.setText(attributesToSend.getOrDefault(MilStdAttributes.TextColor, "")); 677 } 678 679 // Extra modifiers 680 LinearLayout ll = extraModifiersDialog.findViewById(R.id.modifiers_layout); 681 LinearLayout unitLayout = null; 682 683 for (String i : modifiersToGet) { 684 EditText et = new EditText(this); 685 LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 686 et.setLayoutParams(p); 687 688 // set maximum char length for the field? 689 /* 690 et.setFilters(new InputFilter[] { new InputFilter.LengthFilter(3) }); 691 //Disabling suggestions seems to be the only way to stop the buffer from 692 //filling past the length limit if you keep typing. 693 et.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); 694 */ 695 696 et.setHint(Modifiers.getModifierLetterCode(i) + ": " + Modifiers.getModifierName(i)); 697 //et.setId(i); 698 et.setId(convertStringIDtoInt(i)); 699 et.setText(modifiersToSend.getOrDefault(i, "")); // restores previous values if dialog was reopened 700 701 // DateTime fields 702 if (i == Modifiers.W_DTG_1 || i == Modifiers.W1_DTG_2) { 703 et.setInputType(InputType.TYPE_NULL); // prevents users from typing directly in datetime field 704 // ensures the EditText only has to be clicked once to show dialog 705 et.setOnFocusChangeListener((d, hasFocus) -> { 706 if (hasFocus) 707 callDatetimeDialog(et); 708 }); 709 et.setOnClickListener(l -> callDatetimeDialog(et)); 710 } 711 712 // Dynamically add spinners for multi-point graphics' X (altitude/depth) Units and Mode. 713 // (For single-point graphics, the X field is free-type.) 714 if (i == Modifiers.X_ALTITUDE_DEPTH) { 715 if (isControlMeasure) { 716 LinearLayout.LayoutParams altParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 717 718 // build unit array, label, and spinner 719 List<String> unitArray = new ArrayList<>(); 720 for (AltitudeUnits u : AltitudeUnits.values()) { 721 unitArray.add(u.name() + " " + u.desc); 722 } 723 724 unitLayout = new LinearLayout(this); 725 unitLayout.setLayoutParams(altParams); 726 TextView unitLabel = new TextView(this); 727 unitLabel.setText(R.string.altitude_unit_label); 728 729 altitudeUnitSpinner = new Spinner(this); 730 ArrayAdapter<String> unitAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, unitArray) { 731 @Override 732 public View getView(int position, View convertView, ViewGroup parent) { 733 View v = super.getView(position, convertView, parent); 734 v.setMinimumHeight((int) (48*this.getContext().getResources().getDisplayMetrics().density)); 735 return v; 736 } 737 }; 738 unitAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 739 altitudeUnitSpinner.setAdapter(unitAdapter); 740 altitudeUnitSpinner.setOnItemSelectedListener(new ModifierSpinnerListener()); 741 742 unitLayout.addView(unitLabel); 743 unitLayout.addView(altitudeUnitSpinner); 744 745 // build mode array, label, and spinner 746 List<String> modeArray = new ArrayList<>(); 747 for (AltitudeModes m : AltitudeModes.values()) { 748 modeArray.add(m.name() + " " + m.desc); 749 } 750 751 LinearLayout modeLayout = new LinearLayout(this); 752 modeLayout.setLayoutParams(altParams); 753 TextView modeLabel = new TextView(this); 754 modeLabel.setText(R.string.altitude_mode_label); 755 756 altitudeModeSpinner = new Spinner(this); 757 ArrayAdapter<String> modeAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, modeArray) { 758 @Override 759 public View getView(int position, View convertView, ViewGroup parent) { 760 View v = super.getView(position, convertView, parent); 761 v.setMinimumHeight((int) (48*this.getContext().getResources().getDisplayMetrics().density)); 762 return v; 763 } 764 }; 765 modeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 766 altitudeModeSpinner.setAdapter(modeAdapter); 767 altitudeModeSpinner.setOnItemSelectedListener(new ModifierSpinnerListener()); 768 769 modeLayout.addView(modeLabel); 770 modeLayout.addView(altitudeModeSpinner); 771 772 // change multi-point X hint and add X, Units, and Mode fields to the main view 773 et.setHint(Modifiers.getModifierLetterCode(i) + ": " + Modifiers.getModifierName(i) + "\n(Type one or more comma-separated numbers)"); 774 ll.addView(et); 775 ll.addView(unitLayout); 776 ll.addView(modeLayout); 777 778 // if the calling application passes any invalid values for altitude units or mode, this sets both to the defaults 779 try { 780 altitudeUnitSpinner.setSelection(AltitudeUnits.valueOf(attributesToSend.getOrDefault(MilStdAttributes.AltitudeUnits, "1,M").split(",")[1]).index); 781 altitudeModeSpinner.setSelection(AltitudeModes.valueOf(attributesToSend.getOrDefault(MilStdAttributes.AltitudeMode, "AMSL")).index); 782 } catch (Exception e) { 783 altitudeUnitSpinner.setSelection(AltitudeUnits.valueOf("M").index); 784 altitudeModeSpinner.setSelection(AltitudeModes.valueOf("AMSL").index); 785 } 786 continue; 787 } else { 788 // change single-point X hint 789 et.setHint(Modifiers.getModifierLetterCode(i) + ": " + Modifiers.getModifierName(i) + "\n(Type anything, e.g. 500 M AMSL)"); 790 } 791 } 792 ll.addView(et); 793 } 794 795 Button saveExtraModsButton = extraModifiersDialog.findViewById(R.id.modifiers_dialog_save_button); 796 saveExtraModsButton.setOnClickListener(w -> { 797 // save attributes 798 if (isControlMeasure) { 799 attributesToSend.put(MilStdAttributes.OutlineSymbol, String.valueOf(outlineCheckbox.isChecked())); 800 801 String value = String.valueOf(lineColorField.getText()); 802 if (value.isEmpty()) { 803 attributesToSend.remove(MilStdAttributes.LineColor); 804 } else { 805 attributesToSend.put(MilStdAttributes.LineColor, value); 806 } 807 808 value = String.valueOf(fillColorField.getText()); 809 if (value.isEmpty()) { 810 attributesToSend.remove(MilStdAttributes.FillColor); 811 } else { 812 attributesToSend.put(MilStdAttributes.FillColor, value); 813 } 814 815 value = String.valueOf(lineWidthField.getText()); 816 if (value.isEmpty()) { 817 attributesToSend.remove(MilStdAttributes.LineWidth); 818 } else { 819 attributesToSend.put(MilStdAttributes.LineWidth, value); 820 } 821 822 value = String.valueOf(textColorField.getText()); 823 if (value.isEmpty()) { 824 attributesToSend.remove(MilStdAttributes.TextColor); 825 } else { 826 attributesToSend.put(MilStdAttributes.TextColor, value); 827 } 828 } 829 830 if (altitudeUnitSpinner != null && altitudeUnitSpinner.getVisibility() == View.VISIBLE) { 831 // We are assuming the user will not do any unit conversion--they will always type a value in (e.g.) meters if they want to display meters. 832 // Therefore the AltitudeUnits string should always be "1,<x>" where <x> is one of M/KM/FT/SM/FL because the conversion factor will always be 1. 833 attributesToSend.put(MilStdAttributes.AltitudeUnits, "1," + ((String) altitudeUnitSpinner.getSelectedItem()).split(" ")[0]); 834 attributesToSend.put(MilStdAttributes.AltitudeMode, ((String) altitudeModeSpinner.getSelectedItem()).split(" ")[0]); 835 } 836 837 // save extra modifiers 838 for (String j : modifiersToGet) { 839 EditText et = extraModifiersDialog.findViewById(convertStringIDtoInt(j)); 840 String value = String.valueOf(et.getText()); 841 if (value.isEmpty()) { 842 modifiersToSend.remove(j); 843 } else { 844 modifiersToSend.put(j, value); 845 } 846 } 847 extraModifiersDialog.dismiss(); 848 updateSymbolPreview(); 849 }); 850 }); 851 updateSymbolPreview(); 852 } else { 853 extraModifiersButton.setEnabled(false); 854 } 855 } 856 857 /** 858 * Used to call Datetime selector from Extra Modifiers page (should only be called from that page) 859 * @param et Datetime EditText field that called this dialog 860 */ 861 private void callDatetimeDialog(EditText et) { 862 Dialog datetimeDialog = new Dialog(this); 863 datetimeDialog.setContentView(R.layout.datetime_selector); 864 datetimeDialog.getWindow().setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT); 865 datetimeDialog.show(); 866 867 DatePicker datePicker = datetimeDialog.findViewById(R.id.datePicker); 868 TimePicker timePicker = datetimeDialog.findViewById(R.id.timePicker); 869 timePicker.setIs24HourView(true); 870 871 Button nowButton = datetimeDialog.findViewById(R.id.datetime_now_button); 872 nowButton.setOnClickListener(n -> { 873 Calendar c = Calendar.getInstance(); 874 int year = c.get(Calendar.YEAR); 875 int month = c.get(Calendar.MONTH); 876 int day = c.get(Calendar.DAY_OF_MONTH); 877 datePicker.init(year, month, day, null); 878 timePicker.setHour(c.get(Calendar.HOUR_OF_DAY)); 879 timePicker.setMinute(c.get(Calendar.MINUTE)); 880 }); 881 nowButton.callOnClick(); 882 883 Button onOrderButton = datetimeDialog.findViewById(R.id.datetime_on_order_button); 884 onOrderButton.setOnClickListener(o -> { 885 et.setText("O/O"); 886 datetimeDialog.dismiss(); 887 updateSymbolPreview(); 888 }); 889 890 Button confirmTimeButton = datetimeDialog.findViewById(R.id.datetime_confirm_button); 891 confirmTimeButton.setOnClickListener(c -> { 892 Calendar myCalendar = new GregorianCalendar(datePicker.getYear(), datePicker.getMonth(), datePicker.getDayOfMonth(), timePicker.getHour(), timePicker.getMinute()); 893 Date selectedDate = myCalendar.getTime(); 894 et.setText(SymbolUtilities.getDateLabel(selectedDate)); 895 datetimeDialog.dismiss(); 896 updateSymbolPreview(); 897 }); 898 } 899 900 private int getMineBitmask() { 901 int mineBitmask = 0; 902 903 if (Integer.parseInt(selectedSymbolNode.getSymbolSetCode()) == SymbolID.SymbolSet_ControlMeasure) { 904 if (AntipersonnelMineBox.isChecked()) 905 mineBitmask |= ANTIPERSONNEL_MINE; 906 if (AntipersonnelDirectionalMineBox.isChecked()) 907 mineBitmask |= ANTIPERSONNEL_MINE_DIRECTIONAL; 908 if (AntitankMineBox.isChecked()) 909 mineBitmask |= ANTITANK_MINE; 910 if (AntitankAntihandlingMineBox.isChecked()) 911 mineBitmask |= ANTITANK_MINE_ANTIHANDLING; 912 if (WideAreaAntitankMineBox.isChecked()) 913 mineBitmask |= WIDE_AREA_ANTITANK_MINE; 914 if (MineClusterBox.isChecked()) 915 mineBitmask |= MINE_CLUSTER; 916 } 917 return mineBitmask; 918 } 919 920 // Updates preview in configure symbol page 921 // Uses modifiers - different than update selected symbol 922 private void updateSymbolPreview() { 923 // update Control Measures modifier N ("ENY" if hostile) 924 boolean isHostile = (SymbolID.getAffiliation(getSymbolID()) == SymbolID.StandardIdentity_Affiliation_Hostile_Faker); 925 modifiersToSend.put(Modifiers.N_HOSTILE, isHostile ? "ENY" : null); 926 927 Map<String,String> modifiers = new HashMap<>(); 928 for (String i : modifiersToSend.keySet()) { 929 modifiers.put(i, modifiersToSend.get(i)); 930 } 931 Map<String,String> attributes = new HashMap<>(); 932 for (String i : attributesToSend.keySet()) { 933 attributes.put(i, attributesToSend.get(i)); 934 } 935 attributes.put(MilStdAttributes.PixelSize, "240"); 936 937 ImageInfo ii = mir.RenderIcon(getSymbolID(), modifiers, attributes); 938 939 ViewGroup symbolPreviewView = mainModifiersDialog.findViewById(R.id.symbol_picker_modifiers_preview); 940 ImageView symbolPreviewIV = symbolPreviewView.findViewById(R.id.symbol_picker_cell_IV); 941 symbolPreviewIV.setBackgroundColor(Color.LTGRAY); 942 943 if (ii != null && ii.getImage() != null) 944 symbolPreviewIV.setImageBitmap(ii.getImage()); 945 else 946 // If can't render show empty preview 947 symbolPreviewIV.setImageBitmap(emptyBitmap); 948 } 949 950 // Returns symbol id with no modifiers just symbol set and entity code in correct positions 951 private String getGenericSymbolID(Node symbolNode) { 952 return symbolNode.getVersion() + "03" + symbolNode.getSymbolSetCode() + "0011" + symbolNode.getCode() + "00000000000000"; 953 } 954 955 // Includes modifiers in code 956 private String getSymbolID() { 957 final String selectedSymbolSet = selectedSymbolNode.getSymbolSetCode(); 958 final String selectedSymbolEntityCode = selectedSymbolNode.getCode(); 959 960 // Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, 961 // so Google recommends using if/else for them instead of switch statements. 962 int checkedRadioButtonId = contextRadioGroup.getCheckedRadioButtonId(); 963 String contextCode; 964 if (checkedRadioButtonId == R.id.context_radio_exercise) { 965 contextCode = String.valueOf(SymbolID.StandardIdentity_Context_Exercise); 966 } else if (checkedRadioButtonId == R.id.context_radio_simulation) { 967 contextCode = String.valueOf(SymbolID.StandardIdentity_Context_Simulation); 968 } else { 969 contextCode = String.valueOf(SymbolID.StandardIdentity_Context_Reality); 970 } 971 972 // For spinners, the selected item position equals the digit to use in the symbol code 973 // (except for ampTypeSpinner, which is position+1 when not category Unknown). 974 String stdIdCode = String.valueOf(stdIdSpinner.getSelectedItemPosition()); 975 String statusCode = String.valueOf(statusSpinner.getSelectedItemPosition()); 976 String hqCode = String.valueOf(hqSpinner.getSelectedItemPosition()); 977 978 String amplifierCode = "00"; 979 if (ampCategorySpinner.getSelectedItemPosition() != 0) { 980 amplifierCode = ampCategorySpinner.getSelectedItemPosition() + 981 String.valueOf(ampTypeSpinner.getSelectedItemPosition() + 1); 982 } 983 984 String sector1Code = "00"; 985 String sector2Code = "00"; 986 if (hasSectorModifiers) { 987 DecimalFormat twoDigitFormatter = new DecimalFormat("00"); 988 sector1Code = twoDigitFormatter.format(sector1Spinner.getSelectedItemPosition()); 989 sector2Code = twoDigitFormatter.format(sector2Spinner.getSelectedItemPosition()); 990 991 // (Control Measures) Gets symbol code section matching mineBitmask, or returns value for "Unspecified Mine" if combination is not found. 992 if (Integer.parseInt(selectedSymbolSet) == SymbolID.SymbolSet_ControlMeasure && Integer.parseInt(sector1Code) == SECTOR_1_MINE_INDEX) { 993 sector1Code = mineSectorLookup.get(getMineBitmask(), String.valueOf(SECTOR_1_MINE_INDEX)); 994 } 995 } 996 997 String countryCode = "000"; 998 if (hasCountryModifier) { 999 String key = (String) countryTextView.getText(); 1000 if (!Objects.equals(key, "")) { 1001 countryCode = countryMap.get(key); 1002 } 1003 } 1004 1005 return selectedSymbolNode.getVersion() + contextCode + stdIdCode + selectedSymbolSet + statusCode + hqCode + amplifierCode + 1006 selectedSymbolEntityCode + sector1Code + sector2Code + "0000000" + countryCode; 1007 } 1008 1009 // Called when pick button is selected from the configure window 1010 private void onPickSymbol() { 1011 String canRender; 1012 int mineBitmask = getMineBitmask(); 1013 final String symbolID = getSymbolID(); 1014 if (SymbolUtilities.isMultiPoint(symbolID)) { 1015 Map<String,String> modifiers = new HashMap<>(); 1016 for (String i : modifiersToSend.keySet()) { 1017 modifiers.put(i, modifiersToSend.get(i)); 1018 } 1019 canRender = MultiPointHandler.canRenderMultiPoint(symbolID, modifiers, Integer.MAX_VALUE); 1020 if (!canRender.equals("true")) { 1021 // Clean up error message for user 1022 canRender = selectedSymbolNode.getName() + canRender.substring(30); 1023 canRender = canRender.replace("a modifiers object that has ", ""); 1024 } 1025 } else { 1026 Map<String,String> attributes = new HashMap<>(); 1027 for (String i : attributesToSend.keySet()) { 1028 attributes.put(i, attributesToSend.get(i)); 1029 } 1030 if (MilStdIconRenderer.getInstance().CanRender(symbolID, attributes)) { 1031 canRender = "true"; 1032 } else { 1033 // Shouldn't be able to get here with a single point that can't be rendered 1034 canRender = "Unable to render " + selectedSymbolNode.getName(); 1035 } 1036 } 1037 1038 if (invalidMineTrios.contains(mineBitmask)) { 1039 Toast.makeText(mainModifiersDialog.getContext(), "Invalid combination of mines. Please make a different selection.", Toast.LENGTH_LONG).show(); 1040 } else if (canRender.equals("true")) { 1041 mainModifiersDialog.dismiss(); 1042 Intent resultIntent = new Intent(); 1043 resultIntent.putExtra(selectedSymbolIdKey, getSymbolID()); 1044 resultIntent.putExtra(modifiersKey, modifiersToSend); 1045 resultIntent.putExtra(attributesKey, attributesToSend); 1046 setResult(Activity.RESULT_OK, resultIntent); 1047 finish(); 1048 } else { 1049 Toast.makeText(mainModifiersDialog.getContext(), canRender, Toast.LENGTH_LONG).show(); 1050 } 1051 } 1052 1053 // Updates selected symbol variable, selected symbol preview and symbols on page if necessary 1054 private void updateSelectedSymbol(Node newSelectedSymbol) { 1055 // Update the selected symbol set code 1056 selectedSymbolNode = newSelectedSymbol; 1057 ImageView selectedSymbolIV = findViewById(R.id.selected_symbol_iv); 1058 Bitmap render = getRender(selectedSymbolNode); 1059 1060 String selectedSymbolName; 1061 if (selectedSymbolNode.getName().equalsIgnoreCase("root")) { 1062 selectedSymbolName = "Symbol"; 1063 } else { 1064 selectedSymbolName = selectedSymbolNode.getName(); 1065 } 1066 configureButton.setText(getString(R.string.configure_btn_label, "'" + selectedSymbolName + "'")); 1067 1068 if (render != null) { 1069 selectedSymbolIV.setImageBitmap(render); 1070 configureButton.setEnabled(canRender(getGenericSymbolID(selectedSymbolNode))); 1071 1072 // change button text for weather symbols because the modifier dialogs are skipped 1073 switch (Integer.parseInt(selectedSymbolNode.getSymbolSetCode())) { 1074 case SymbolID.SymbolSet_Atmospheric: 1075 case SymbolID.SymbolSet_Oceanographic: 1076 case SymbolID.SymbolSet_MeteorologicalSpace: 1077 configureButton.setText(getString(R.string.send_btn_label, "'" + selectedSymbolName + "'")); 1078 } 1079 } else { 1080 selectedSymbolIV.setImageResource(R.drawable.baseline_folder_24); 1081 configureButton.setEnabled(false); 1082 } 1083 1084 if (!newSelectedSymbol.getChildren().isEmpty()) { 1085 pageTrail.add(selectedSymbolNode); 1086 updateSymbolTable(); 1087 } 1088 } 1089 1090 // Updates symbols in page based on top of pageTrail 1091 private void updateSymbolTable() { 1092 Node symbolTree = pageTrail.peek(); 1093 ArrayList<Node> symbols; // Symbols to be in new page 1094 1095 // Scroll to top 1096 GridView symbolTable = findViewById(R.id.symbol_picker_table); 1097 symbolTable.smoothScrollToPositionFromTop(0, 0, 0); 1098 1099 if (!flattenTreeToggle.isChecked()) { 1100 symbols = new ArrayList<>(symbolTree.getChildren()); 1101 } else { 1102 symbols = new ArrayList<>(symbolTree.flatten()); 1103 } 1104 1105 TextView symbolPath = findViewById(R.id.symbol_picker_path); 1106 1107 StringBuilder pathStr = new StringBuilder(); 1108 if (pageTrail.size() == 1) { 1109 pathStr.append("Home"); 1110 } else if (activeSearch) { 1111 int searchNodeIndex = 1; 1112 while (!pageTrail.get(searchNodeIndex).getName().equals(searchNodeName)) { 1113 searchNodeIndex++; 1114 } 1115 1116 pathStr.append("Search"); 1117 for (int i = searchNodeIndex + 1; i < pageTrail.size(); i++) 1118 pathStr.append(" > ").append(pageTrail.get(i).getName()); 1119 } else { 1120 pathStr.append(pageTrail.get(1).getName()); 1121 for (int i = 2; i < pageTrail.size(); i++) 1122 pathStr.append(" > ").append(pageTrail.get(i).getName()); 1123 } 1124 symbolPath.setText(pathStr); 1125 1126 // (optional) sort symbols by name 1127 symbols.sort(Comparator.comparing(Node::getName)); 1128 1129 symbolTableAdapter.clear(); 1130 symbolTableAdapter.addAll(symbols); 1131 } 1132 1133 // returns null if can't render or other error 1134 // Does not include modifiers 1135 private Bitmap getRender(Node symbolNode) { 1136 final String version = symbolNode.getVersion(); 1137 final String symbolSetCode = symbolNode.getSymbolSetCode(); 1138 final String entityCode = symbolNode.getCode(); 1139 String symbolID = getGenericSymbolID(symbolNode); 1140 1141 if (version.equals("XX") || entityCode.equals("XXXXXX") || entityCode.equals("XX")) 1142 // Error in code 1143 return null; 1144 else if (entityCode.equals("000000") && 1145 (symbolSetCode.equals(SymbolID.SymbolSet_Atmospheric + "") || 1146 symbolSetCode.equals(SymbolID.SymbolSet_ControlMeasure + "") || 1147 symbolSetCode.equals(SymbolID.SymbolSet_MineWarfare + "") || 1148 symbolSetCode.equals(SymbolID.SymbolSet_Oceanographic + ""))) 1149 // Top level symbol set that can't be rendered 1150 return null; 1151 else if (!entityCode.equals("000000") && !canRender(symbolID)) 1152 // Check if can render icon. canRender() says it can't render "000000" but RenderIcon() will 1153 // return an empty frame for the symbol sets not excluded above 1154 return null; 1155 1156 Map<String,String> modifiers = new HashMap<>(); 1157 Map<String,String> attributes = new HashMap<>(); 1158 1159 attributes.put(MilStdAttributes.PixelSize, "50"); 1160 attributes.put(MilStdAttributes.DrawAsIcon, "true"); // Make all symbols same size 1161 1162 ImageInfo ii = mir.RenderIcon(symbolID, modifiers, attributes); 1163 if (ii != null) 1164 return ii.getImage(); 1165 else 1166 return null; 1167 } 1168 1169 // Same functionality and MilStdIconRenderer.CanRender() - doesn't log if can't render 1170 // Symbol picker calls canRender() with symbols it doesn't expect to be valid 1171 private Boolean canRender(String symbolID) { 1172 int version = SymbolID.getVersion(symbolID); 1173 String lookupID = SymbolUtilities.getBasicSymbolID(symbolID); 1174 String lookupSVGID = SVGLookup.getMainIconID(symbolID); 1175 MSInfo msi = MSLookup.getInstance().getMSLInfo(lookupID,SymbolID.getVersion(symbolID)); 1176 SVGInfo si = SVGLookup.getInstance().getSVGLInfo(lookupSVGID, version); 1177 1178 // msi should never be null 1179 return msi != null && msi.getDrawRule() != DrawRules.DONOTDRAW && si != null; 1180 } 1181 1182 private class AmplifierSelectedItemListener implements AdapterView.OnItemSelectedListener { 1183 @Override 1184 // specifically used for the Amplifier Category spinner to show/hide and change the subtype spinner 1185 public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) { 1186 int arrayId; 1187 switch (ampCategorySpinner.getSelectedItemPosition()) { 1188 case 1: 1189 arrayId = R.array.amp1_echelon_bb_array; 1190 break; 1191 case 2: 1192 arrayId = R.array.amp2_echelon_da_array; 1193 break; 1194 case 3: 1195 arrayId = R.array.amp3_eqp_land_array; 1196 break; 1197 case 4: 1198 arrayId = R.array.amp4_eqp_snow_array; 1199 break; 1200 case 5: 1201 arrayId = R.array.amp5_eqp_water_array; 1202 break; 1203 case 6: 1204 arrayId = R.array.amp6_naval_towed_array; 1205 break; 1206 case 0: 1207 default: 1208 arrayId = R.array.amp0_unknown_array; 1209 break; 1210 } 1211 if (arrayId == R.array.amp0_unknown_array) { 1212 ampTypeLayout.setVisibility(View.GONE); 1213 } else { 1214 ampTypeLayout.setVisibility(View.VISIBLE); 1215 } 1216 ArrayAdapter<CharSequence> ampTypeAdapter = ArrayAdapter.createFromResource(SymbolPickerActivity.this, 1217 arrayId, android.R.layout.simple_spinner_item); 1218 ampTypeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 1219 ampTypeSpinner.setAdapter(ampTypeAdapter); 1220 1221 updateSymbolPreview(); // necessary for ampCategorySpinner since this AmplifierSelectedItemListener is used instead of ModifierSpinnerListener 1222 } 1223 1224 @Override 1225 public void onNothingSelected(AdapterView<?> adapterView) { 1226 // Auto-generated method stub 1227 } 1228 } 1229 1230 private class SearchBoxListener implements SearchView.OnQueryTextListener { 1231 private List<Node> searchSymbolTree(String searchQuery) { 1232 // Search by name 1233 List<Node> searchResults = treeManager.mil2525Tree.flatten().stream() 1234 .filter(node -> node.getName().trim().toLowerCase(Locale.ROOT).replaceAll("-", " ") 1235 .contains(searchQuery.toLowerCase(Locale.ROOT).replaceAll("-", " "))).collect(Collectors.toList()); 1236 // Search by entity code with symbol set 1237 searchResults.addAll(treeManager.mil2525Tree.flatten().stream() 1238 .filter(node -> (node.getSymbolSetCode() + node.getCode()) 1239 .contains(searchQuery)).collect(Collectors.toList())); 1240 // Search by entity code 1241 searchResults.addAll(treeManager.mil2525Tree.flatten().stream() 1242 .filter(node -> node.getCode().contains(searchQuery)).collect(Collectors.toList())); 1243 1244 // Remove duplicates 1245 searchResults = searchResults.stream().distinct().collect(Collectors.toList()); 1246 1247 return searchResults; 1248 } 1249 1250 @Override 1251 public boolean onQueryTextSubmit(String s) { 1252 return false; 1253 } 1254 1255 // Removes search page if query is empty or updates table with search results 1256 @Override 1257 public boolean onQueryTextChange(String s) { 1258 // Remove all pages at and below searchNode 1259 while (activeSearch) { 1260 if (pageTrail.pop().getName().equals(searchNodeName)) { 1261 activeSearch = false; 1262 } 1263 } 1264 1265 if (s != null && !s.equals("")) { 1266 List<Node> searchResults = searchSymbolTree(s); 1267 Node searchNode = new Node(searchNodeName, "XX", "XX", "XX"); 1268 searchNode.addChildren(searchResults); 1269 pageTrail.add(searchNode); 1270 activeSearch = true; 1271 } 1272 updateSymbolTable(); 1273 return true; 1274 } 1275 } 1276 1277 /** 1278 * Symbol Grid View Adapter 1279 * Renders views for symbols from ArrayList of tree nodes for each page 1280 */ 1281 private class SymbolGVAdapter extends ArrayAdapter<Node> { 1282 public SymbolGVAdapter(Context context, ArrayList<Node> nodeArrayList) { 1283 super(context, 0, nodeArrayList); 1284 } 1285 1286 // Gets individual view for symbol for table 1287 @Override 1288 public View getView(int position, View convertView, ViewGroup parent) { 1289 Node symbolTree = getItem(position); 1290 1291 if (convertView == null) { 1292 // Layout Inflater inflates each item to be displayed in GridView 1293 convertView = LayoutInflater.from(getContext()).inflate(R.layout.symbol_cell, parent, false); 1294 } 1295 ViewGroup symbolCell = (ViewGroup) convertView; 1296 1297 // On cell click either open folder or select symbol 1298 symbolCell.setOnClickListener(view -> updateSelectedSymbol(symbolTree)); 1299 1300 TextView symbolTV = symbolCell.findViewById(R.id.symbol_picker_cell_TV); 1301 symbolTV.setText(symbolTree.getName()); 1302 // Add auto text sizing to fit longer symbol names 1303 TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(symbolTV, 8, 1304 12, 1, TypedValue.COMPLEX_UNIT_SP); 1305 ImageView symbolIV = symbolCell.findViewById(R.id.symbol_picker_cell_IV); 1306 Bitmap render = getRender(symbolTree); 1307 if (render != null) 1308 // Whether folder or leaf set icon if can render 1309 symbolIV.setImageBitmap(render); 1310 else if (!symbolTree.getChildren().isEmpty()) 1311 // Can't render use folder icon 1312 symbolIV.setImageResource(R.drawable.baseline_folder_24); 1313 else 1314 // Can't render leaf node 1315 symbolIV.setImageBitmap(emptyBitmap); 1316 1317 // Add a folder icon in corner if rendering a symbol with children 1318 ImageView cellFolder = symbolCell.findViewById(R.id.symbol_picker_cell_folder); 1319 if (render != null && !symbolTree.getChildren().isEmpty()) 1320 cellFolder.setImageResource(R.drawable.baseline_folder_24); 1321 else 1322 cellFolder.setImageBitmap(emptyBitmap); 1323 1324 ViewGroup.LayoutParams params = symbolCell.getLayoutParams(); 1325 params.height = cellSize; 1326 params.width = cellSize; 1327 symbolCell.setLayoutParams(params); 1328 1329 return symbolCell; 1330 } 1331 } 1332 1333 private int convertStringIDtoInt(String key) 1334 { 1335 try { 1336 StringBuilder sb = new StringBuilder(); 1337 if (key != null & key.length() > 0) 1338 { 1339 /*for (int i = 0; i < key.length(); i++) { 1340 String temp = String.valueOf((int) key.charAt(i)); 1341 if (temp.length() == 1) 1342 temp = "0" + temp; 1343 sb.append(temp); 1344 }//*/ 1345 for (int i = 0; (i < 3); i++) { 1346 String temp = String.valueOf((int) key.charAt(i)); 1347 sb.append(temp); 1348 } 1349 return Integer.parseInt(sb.toString()); 1350 } else 1351 return -1; 1352 } 1353 catch(Exception exc) 1354 { 1355 System.out.println(exc.getMessage()); 1356 } 1357 return -1; 1358 } 1359 1360 private String convertIntToStringID(int id) 1361 { 1362 if(id==-1) 1363 return null; 1364 1365 StringBuilder sb = new StringBuilder(); 1366 String tempID = String.valueOf(id); 1367 for(int i = 0; i+1 < tempID.length(); i=i+2) 1368 { 1369 int c = Integer.parseInt(tempID.substring(i,i+2)); 1370 1371 sb.append(Character.toChars(c)); 1372 } 1373 return sb.toString(); 1374 } 1375}