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