예제 #1
0
  /** @return A list of the settable fields. */
  public static List<Field> getSettableFields() {
    // Init the search in the root package.
    Reflections reflections = new Reflections("org.saucistophe", new FieldAnnotationsScanner());
    Set<Field> annotatedFields = reflections.getFieldsAnnotatedWith(SettingsField.class);

    // Turn the set to a list to sort it.
    List<Field> fieldsList = new ArrayList<>(annotatedFields);
    fieldsList.sort(
        (Field field1, Field field2) -> {
          // Retrieve the fields info.
          SettingsField fieldInfo1 = field1.getAnnotation(SettingsField.class);
          SettingsField fieldInfo2 = field2.getAnnotation(SettingsField.class);

          // If the name wasn't set, get the field's declared name.
          String actualName1 = fieldInfo1.name().isEmpty() ? field1.getName() : fieldInfo1.name();
          String actualName2 = fieldInfo2.name().isEmpty() ? field2.getName() : fieldInfo2.name();

          // Elaborate a sortable string representation.
          String sortableString1 = fieldInfo1.category() + "." + actualName1;
          String sortableString2 = fieldInfo2.category() + "." + actualName2;

          return sortableString1.compareTo(sortableString2);
        });

    return fieldsList;
  }
예제 #2
0
  /**
   * Reads the settings from the settings JSON file, and call all callbacks. Typically used during
   * the application init.
   */
  public static void readFromFile() {
    Map<String, Map<String, Object>> categories;

    Gson gson = new Gson();

    // Read from file
    try (BufferedReader bufferedReader = new BufferedReader(new FileReader("settings.json"))) {
      @SuppressWarnings("unchecked")
      Map<String, Map<String, Object>> dummyCategories = gson.fromJson(bufferedReader, Map.class);
      categories = dummyCategories;

      // Then, for each setting, try to find its value.
      for (Field settableField : getSettableFields()) {
        SettingsField fieldInfo = settableField.getAnnotation(SettingsField.class);
        if (categories.containsKey(fieldInfo.category())) {
          Map<String, Object> category = categories.get(fieldInfo.category());

          // If the name wasn't set, get the field's declared n0ame.
          String actualName =
              fieldInfo.name().isEmpty() ? settableField.getName() : fieldInfo.name();

          if (category.containsKey(actualName)) {
            // Deserialize from String.
            set(
                settableField,
                toObject(settableField.getType(), (String) category.get(actualName)));
          }
        }
      }
    } catch (JsonSyntaxException exception) {
      // On incorrect properties, log a warning.
      Logger.getLogger(SettingsHandler.class.getName())
          .log(Level.WARNING, "Syntax error in JSON settings file.");
      Logger.getLogger(SettingsHandler.class.getName()).log(Level.WARNING, null, exception);
    } catch (IOException exception) {
      // If the file is not readable, or does not exist.
      Logger.getLogger(SettingsHandler.class.getName())
          .log(Level.WARNING, "Could not read JSON settings file.");
    } finally {
      triggerCallbacks();
    }
  }
예제 #3
0
  /** Saves the state of the current settings to a file. */
  public static void saveToFile() {
    // Create a map of fields, indexed on categories then names.
    // Those are sorted maps, to preserve alphabetical order.
    SortedMap<String, SortedMap<String, Object>> categories = new TreeMap<>();

    for (Field field : getSettableFields()) {
      // Retrieve the field info.
      SettingsField fieldInfo = field.getAnnotation(SettingsField.class);

      // Create a category map, if it doesn't exist.
      SortedMap<String, Object> category;
      if (categories.containsKey(fieldInfo.category())) {
        category = categories.get(fieldInfo.category());
      } else {
        category = new TreeMap<>();
        categories.put(fieldInfo.category(), category);
      }

      // If the name wasn't set, get the field's declared name.
      String actualName = fieldInfo.name().isEmpty() ? field.getName() : fieldInfo.name();

      // Put the field and its value, as a string.
      category.put(actualName, get(field).toString());
    }

    // Export the result to a json file.
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.setPrettyPrinting();
    Gson gson = gsonBuilder.create();
    String jsonString = gson.toJson(categories);

    // Write to file.
    try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("settings.json"))) {
      bufferedWriter.write(jsonString);
    } catch (IOException ex) {
      // If the file can't be written, it's an error.
      Logger.getLogger(SettingsHandler.class.getName()).log(Level.SEVERE, null, ex);
    }
  }
예제 #4
0
  /** Shows a swing dialog to eit the available settings. */
  public static void showSettingsDialog() {
    // Create a tabbed panel, one tab per category.
    JTabbedPane categoriesPane = new JTabbedPane();

    // A list of actions taken when validating, i.e. when setting all settings to the input values.
    List<Supplier<Boolean>> actions = new ArrayList<>();

    // Create a panel.
    // For each available setting:
    for (Field settingField : getSettableFields()) {
      // Retrieve the annotation.
      SettingsField fieldInfo = settingField.getAnnotation(SettingsField.class);

      // Search for an existing category panel.
      Integer categoryIndex = null;
      for (int candidateCategoryIndex = 0;
          candidateCategoryIndex < categoriesPane.getTabCount();
          candidateCategoryIndex++) {
        if (categoriesPane.getTitleAt(candidateCategoryIndex).equals(fieldInfo.category())) {
          categoryIndex = candidateCategoryIndex;
          break;
        }
      }

      // If the category exists, retrieve its pane, otherwise create it.
      JPanel categoryPanel = null;
      if (categoryIndex != null) {
        categoryPanel = (JPanel) categoriesPane.getComponentAt(categoryIndex);
      } else {
        categoryPanel = new JPanel();
        categoryPanel.setLayout(new BoxLayout(categoryPanel, BoxLayout.Y_AXIS));
        categoriesPane.add(categoryPanel, fieldInfo.category());
      }

      // If the name wasn't set, get the field's declared name.
      String actualName = fieldInfo.name().isEmpty() ? settingField.getName() : fieldInfo.name();

      // Prepare a label text.
      String labelText = fieldInfo.description().isEmpty() ? actualName : fieldInfo.description();
      // Create an optional label for component that need it.
      JLabel label = new JLabel(labelText);

      JComponent editionComponent;

      // Handle enums.
      if (settingField.getType().isEnum()) {
        JComboBox<?> comboBox = new JComboBox<>(settingField.getType().getEnumConstants());
        comboBox.setSelectedItem(get(settingField));
        actions.add(
            () -> {
              set(settingField, comboBox.getSelectedItem());
              return true;
            });
        // Add a label then the component.
        editionComponent = new JPanel();
        editionComponent.add(label);
        editionComponent.add(comboBox);
      } else {
        // Handle primitive types
        switch (settingField.getType().getSimpleName().toLowerCase()) {
          case "boolean":
            JCheckBox checkbox = new JCheckBox(labelText, (boolean) get(settingField));
            editionComponent = checkbox;
            actions.add(
                () -> {
                  set(settingField, checkbox.isSelected());
                  return true;
                });
            break;
          case "string":
            // If there are possible values, use a combobox.
            if (fieldInfo.possibleValues().length != 0) {
              JComboBox<String> comboBox = new JComboBox<>(fieldInfo.possibleValues());
              actions.add(
                  () -> {
                    set(settingField, comboBox.getSelectedItem());
                    return true;
                  });
              // Add a label then the component.
              editionComponent = new JPanel();
              editionComponent.add(label);
              editionComponent.add(comboBox);
            } else {
              // Otherwise, use a simple text field.
              JTextField textField = new JTextField((String) get(settingField));
              actions.add(
                  () -> {
                    set(settingField, textField.getText());
                    return true;
                  });
              // Add a label then the component.
              editionComponent = new JPanel();
              editionComponent.add(label);
              editionComponent.add(textField);
            }
            break;
          case "int":
          case "integer":
            int currentIntValue = (int) get(settingField);
            SpinnerModel spinnerModel =
                new SpinnerNumberModel(
                    currentIntValue, fieldInfo.minValue(), fieldInfo.maxValue(), 1);
            JSpinner spinner = new JSpinner(spinnerModel);

            spinner.setValue(currentIntValue);
            actions.add(
                () -> {
                  set(settingField, spinner.getValue());
                  return true;
                });
            // Add a label then the component.
            editionComponent = new JPanel();
            editionComponent.add(label);
            editionComponent.add(spinner);
            break;
          default:
            editionComponent = new JTextField("Unknown setting type");
            editionComponent.setEnabled(false);
            break;
        }
      }

      categoryPanel.add(editionComponent);

      // Add a fancy tooltip.
      if (fieldInfo.description() != null && !fieldInfo.description().isEmpty()) {
        editionComponent.setToolTipText(fieldInfo.description());
      }
    }

    // Put the panel in a modal frame.
    JDialog dialog = new JDialog((Frame) null, "Settings", true);
    dialog.add(categoriesPane);

    // Add a validation and cancel button.
    JPanel bottomButtons = new JPanel(new FlowLayout(TRAILING));
    dialog.add(bottomButtons, BorderLayout.SOUTH);
    JButton okButton = new JButton("OK");
    {
      okButton.addActionListener(
          e -> {
            for (Supplier<Boolean> action : actions) {
              action.get();
            }
            saveToFile();
            // Call all callbacks on all categories.
            Set<Method> callbacks = getSettingsCallbacks();
            callbacks
                .stream()
                .forEach(
                    m -> {
                      try {
                        m.invoke(null);
                      } catch (IllegalAccessException
                          | IllegalArgumentException
                          | InvocationTargetException ex) {
                        Logger.getLogger(SettingsHandler.class.getName())
                            .log(Level.SEVERE, null, ex);
                      }
                    });

            dialog.dispose();
          });
    }
    bottomButtons.add(okButton);
    JButton cancelButton = new JButton("Cancel");
    {
      cancelButton.addActionListener(
          e -> {
            dialog.dispose();
          });
    }
    bottomButtons.add(cancelButton);

    dialog.pack();
    dialog.setVisible(true);
  }