Need help with Setting layout

Answered

Hello,

I have been developing some settings for my plugin and have been following the Settings Tutorial.  However, I can't get the layout the way I want using the FormBuilder.  I have also tried using regular JPanel with GridBagLayout.  I am sure that there is something fundamental that I am missing to create a nice looking settings panel in IntelliJ.

What I am trying to do is have two labels at the top, stacked one on top of the other, with a table with two columns of check boxes.  If I used the FormBuilder, the labels are left justified, but the columns of check boxes appear somewhere near the middle of the labels.  If I use GridBagLayout in a separate JPanel and add it as a component to the FormBuilder panel, everything is moved to the right, with the check boxes still near the middle of the labels.  Something similar occurs when using the JPanel with GridBagLayout without the FormBuilder panel.

My code is in several methods, so it is not practical to post at this point.

Any suggestions are greatly appreciated.

Thanks in advance,

Todd

2 comments
Comment actions Permalink

Hi Todd,

It's difficult to understand the issue without the screenshots and the code. Building complex layouts with Swing may require experimenting with the Form Builder, so the answer won't be obvious probably.

If there is any similar UI control existing in IDE, you can try using UI Inspector (https://plugins.jetbrains.com/docs/intellij/internal-ui-inspector.html) to check its implementation.

0
Comment actions Permalink

As can be seen in the attached image, the text and checkboxes are far from the left side of the panel where most settings UI panels are normally anchored.

The code below has very few FormBuilder elements since I wasn't making progress with the FormBuilder and refactored to Swing to see if that would help in some way.  So, if you have any suggestions regarding FormBuilder or Swing, please don't hesitate as I am not afraid of refactoring.


public class ProjectSettingsComponent
{
private final Project _project;
private final JPanel _mainPanel;
private final List<JBCheckBox> _languageCheckBoxes = new ArrayList<>();
private final Set<String> _languages;
private final Set<String> _inspectableLanguages;

public ProjectSettingsComponent()
{
_project = getCurrentProject();
assert _project != null;

_inspectableLanguages = getInspectableLanguages();

// Languages must be in the project and inspectable by the current inspection profile
_languages =
ProjectStartupActivity.getProjectInfo(_project).getLanguageIDs().stream()
.filter(language ->
_inspectableLanguages.stream().anyMatch(language :: equalsIgnoreCase))
.collect(Collectors.toSet());

final JPanel formattedPanel = new JPanel(new GridBagLayout());
final GridBagConstraints constraints = new GridBagConstraints();

constraints.gridx = 0;
constraints.gridy = 0;
constraints.anchor = GridBagConstraints.WEST;

++constraints.gridy;
constraints.gridwidth = 3;
formattedPanel.add(
new JBLabel("NOTE: This is a project level setting, NOT an application level setting"),
constraints);

++constraints.gridy;
constraints.insets = JBUI.insetsTop(20);
formattedPanel.add(
new JBLabel("Select inspection languages to process for the '" + _project.getName()
+ "' project"),
constraints);

++constraints.gridy;
constraints.gridwidth = 2;
constraints.insets = JBUI.insetsTop(20);
formattedPanel.add(createLanguagePanel(), constraints);

++constraints.gridy;
constraints.gridwidth = 1;
constraints.insets = JBUI.insetsTop(20);
formattedPanel.add(createButton("None", false), constraints);

++constraints.gridx;
constraints.insets = JBUI.insets(20, 10, 0, 0);
formattedPanel.add(createButton("All", true), constraints);

_mainPanel = FormBuilder.createFormBuilder()
.addComponent(formattedPanel, 1)
.addComponentToRightColumn(new JPanel(), 1)
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}

@NotNull
private JButton createButton(final String text, final boolean isSelected)
{
final Set<String> selectedLanguages = new HashSet<>();
if (isSelected)
{
selectedLanguages.addAll(_languageCheckBoxes.stream().map(AbstractButton :: toString)
.collect(Collectors.toUnmodifiableSet()));
}

final JButton button = new JButton(text);
button.addActionListener(event -> setSelectedLanguages(selectedLanguages));

return button;
}

@Nullable
private static Project getCurrentProject()
{
try
{
final Promise<DataContext> dataContextPromise =
DataManager.getInstance().getDataContextFromFocusAsync();
final DataContext dataContext = dataContextPromise.blockingGet(1, TimeUnit.SECONDS);
if (null != dataContext)
{
return (Project)dataContext.getData(CommonDataKeys.PROJECT.getName());
}
}
catch (final ExecutionException | TimeoutException exception)
{
throw new RuntimeException(exception);
}

return null;
}

// TODO: Update the checkbox states when the inspection profile is changed

@NotNull
@UnmodifiableView
private static Set<String> getInspectableLanguages()
{
final Project project = getCurrentProject();
assert project != null;

final InspectionProfileImpl currentInspectionProfile =
ApplicationInspectionProfileManager.getInstance(project).getCurrentProfile();

final Set<String> languageIds = new HashSet<>();
for (ScopeToolState state : currentInspectionProfile.getDefaultStates(project))
{
// TODO: use selected severity
// if (state.getTool().getDefaultLevel().getSeverity() == HighlightSeverity.ERROR)
{
final String language = state.getTool().getLanguage();
if (language != null)
{
languageIds.add(language);
}
}
}

final Set<String> languages = new HashSet<>();
for (String languageId : languageIds)
{
final Language language = Language.findLanguageByID(languageId);
if (language != null && !(language instanceof MetaLanguage))
{
languages.add(languageId.toLowerCase());
languages.addAll(language.getDialects().stream()
.map(Language :: getDisplayName)
.collect(Collectors.toUnmodifiableSet()));
}
}

return Collections.unmodifiableSet(languages);
}

@NotNull
private JPanel createLanguagePanel()
{
final ProjectSettingsState settings = ProjectSettingsState.getInstance();
final String projectName = _project.getName();

// See if there are settings available
final Set<String> selectedLanguages =
settings._selectedLanguagesByProject.get(projectName);

// The settings will be null for projects not previously opened
final Set<String> projectLanguages =
new HashSet<>(null == selectedLanguages
? _languages
: selectedLanguages);

final ProjectInfo projectInfo = ProjectStartupActivity.getProjectInfo(_project);

@NotNull @UnmodifiableView final Map<Language, Set<Language>> languageMap =
projectInfo.getLanguageMap();

final long numCheckBoxes = languageMap.size()
+ languageMap.values().stream().mapToLong(Collection :: size).sum();
final int half_ish = (int)Math.ceil(numCheckBoxes / 2.);

final JPanel languagePanel = new JPanel(new GridBagLayout());

final GridBagConstraints constraints = new GridBagConstraints();
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.gridx = 0;
constraints.gridy = 0;
constraints.anchor = GridBagConstraints.WEST;
constraints.fill = GridBagConstraints.HORIZONTAL;
Insets baseInsets = JBUI.emptyInsets();

boolean firstColumn = true;
int languagesProcessed = 0;

final Map<String, Double> languageStats = projectInfo.getLanguageStats();

for (final Entry<Language, Set<Language>> entry : languageMap.entrySet())
{
++languagesProcessed;

final Language language = entry.getKey();
final Set<Language> dialects = entry.getValue();

final String languageName = language.getDisplayName();
final String key = determineKeyForLanguage(languageName, languageStats.keySet());

final JBCheckBox mainCheckBox =
createLanguageCheckBox(languageName,
languageStats.computeIfAbsent(key, ignored -> 0.));
constraints.insets = baseInsets;
languagePanel.add(mainCheckBox, constraints);
++constraints.gridy;

for (final Language dialect : dialects)
{
++languagesProcessed;

final String dialectName = dialect.getDisplayName();
final String dialectKey =
determineKeyForLanguage(dialectName, languageStats.keySet());

final JBCheckBox dialectCheckBox =
createLanguageCheckBox(dialectName,
languageStats.computeIfAbsent(dialectKey, ignored -> 0.));
constraints.insets = JBUI.insetsLeft(baseInsets.left + 20);
languagePanel.add(dialectCheckBox, constraints);
++constraints.gridy;
}

if (firstColumn && languagesProcessed >= half_ish)
{
++constraints.gridx;
constraints.gridy = 0;
baseInsets = JBUI.insetsLeft(20);

firstColumn = false;
}
}

setSelectedLanguages(projectLanguages);

// Since creating the panel, need to define the settings
settings._projectName = projectName;
settings._selectedLanguagesByProject.put(projectName, getSelectedLanguages());

return languagePanel;
}

@NotNull
private String determineKeyForLanguage(
@NotNull final String languageName,
@NotNull final Set<String> possibleLanguages)
{
final Optional<String> optionalDialectKey = possibleLanguages
.stream()
.filter(key -> 0 == key.compareToIgnoreCase(languageName))
.findFirst();

return optionalDialectKey.or(() -> Optional.of(languageName)).get();
}

@NotNull
private JBCheckBox createLanguageCheckBox(@NotNull final String language, final double value)
{
final JBCheckBox languageCheckBox = new JBCheckBox(language)
{
@Contract(pure = true)
@NotNull
@Override
public String getText()
{
return this + " [" + String.format("%.1f", 100. * value) + "%]";
}

@Override
public String toString()
{
return language;
}
};
_languageCheckBoxes.add(languageCheckBox);

languageCheckBox.addItemListener(event -> {
System.out.println("event: " + event);
if (languageCheckBox.isSelected())
{
System.out.println("add: " + language);
_languages.add(language);
}
else
{
_languages.remove(language);
}
});

return languageCheckBox;
}

public JPanel getPanel()
{
return _mainPanel;
}

public String getProjectName()
{
return _project.getName();
}

public Set<String> getSelectedLanguages()
{
return Set.copyOf(_languages);
}

public void setSelectedLanguages(final Set<String> languages)
{
// final Set<String> languageIDs = Set.copyOf(languages);

_languages.clear();
// _languageCheckBoxes.forEach(jbCheckBox -> jbCheckBox.setSelected(false));

for (JBCheckBox checkBox : _languageCheckBoxes)
{
final String checkBoxLanguage = checkBox.toString();

final boolean isSelected = null == languages
|| languages.stream().anyMatch(checkBoxLanguage :: equalsIgnoreCase);
final boolean isEnabledAndSelectable =
_inspectableLanguages.stream().anyMatch(checkBoxLanguage :: equalsIgnoreCase);
final boolean isLanguageSelected = isEnabledAndSelectable && isSelected;

checkBox.setEnabled(isEnabledAndSelectable);
checkBox.setSelected(isLanguageSelected);

System.out.println(checkBoxLanguage + ": " + isEnabledAndSelectable + ", "
+ isSelected + ", " + isLanguageSelected);

// This is required because the item listener isn't always triggered programmatically
if (isLanguageSelected)
{
_languages.add(checkBoxLanguage);
}
}
}
}

 

 

 

 

0

Please sign in to leave a comment.