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