blob: f2aa7a59831ab7b97f3a5fc6064e9794f02ce044 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013 Google, Inc and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Sergey Prigogin (Google) - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.ui.preferences;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.cdt.internal.ui.dialogs.IStatusChangeListener;
import org.eclipse.cdt.internal.ui.dialogs.StatusInfo;
import org.eclipse.cdt.internal.ui.refactoring.includes.IncludeGroupStyle;
import org.eclipse.cdt.internal.ui.refactoring.includes.IncludeGroupStyle.IncludeKind;
import org.eclipse.cdt.internal.ui.wizards.dialogfields.ListDialogField;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;
/**
* The preference block for configuring relative order of include statements.
*/
public class IncludeOrderBlock extends OptionsConfigurationBlock {
private static final int IDX_UP = 0;
private static final int IDX_DOWN = 1;
private static final String[] UP_DOWN_LABELS = { PreferencesMessages.IncludeOrderBlock_up,
PreferencesMessages.IncludeOrderBlock_down };
private final List<IncludeGroupStyle> styles;
private Map<IncludeKind, IncludeGroupStyle> stylesByKind;
private GroupListField includeGroupList;
private PixelConverter pixelConverter;
public IncludeOrderBlock(IStatusChangeListener context, IProject project, IWorkbenchPreferenceContainer container,
List<IncludeGroupStyle> styles) {
super(context, project, new Key[0], container);
this.styles = styles;
}
@Override
protected Control createContents(Composite parent) {
pixelConverter = new PixelConverter(parent);
setShell(parent.getShell());
Composite composite = new Composite(parent, SWT.NONE);
composite.setFont(parent.getFont());
GridLayout layout = new GridLayout(2, false);
layout.marginHeight = 0;
layout.marginWidth = 0;
composite.setLayout(layout);
includeGroupList = new GroupListField();
includeGroupList.setLabelText(PreferencesMessages.IncludeOrderBlock_order_of_includes);
Label label = includeGroupList.getLabelControl(composite);
label.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, false, false, 2, 1));
Control control = includeGroupList.getListControl(composite);
GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
gd.widthHint = pixelConverter.convertWidthInCharsToPixels(50);
gd.heightHint = pixelConverter.convertHeightInCharsToPixels(5);
control.setLayoutData(gd);
Control buttonsControl = includeGroupList.getButtonBox(composite);
buttonsControl.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, false, false));
updateControls();
return composite;
}
@Override
protected void updateControls() {
super.updateControls();
stylesByKind = getStylesByKind(styles);
List<IncludeGroupStyle> orderedStyles = new ArrayList<>(styles);
Collections.sort(orderedStyles); // Sort according to values returned by getOrder() method.
List<IncludeGroupStyle> groupedStyles = new ArrayList<>();
int order = 0;
for (IncludeGroupStyle style : orderedStyles) {
style.setOrder(order++);
IncludeKind includeKind = style.getIncludeKind();
if (style.isKeepTogether()
&& (!includeKind.hasChildren() || hasUngroupedChildren(includeKind, stylesByKind))) {
groupedStyles.add(style);
}
}
// Adjust order of groups to satisfy higher order grouping.
for (int i = 0; i < groupedStyles.size(); i++) {
IncludeGroupStyle style = groupedStyles.get(i);
IncludeKind groupingKind = getGroupingParentKind(style);
if (groupingKind != null) {
while (++i < groupedStyles.size() && getGroupingParentKind(groupedStyles.get(i)) == groupingKind) {
}
for (int j = i + 1; j < groupedStyles.size(); j++) {
if (getGroupingParentKind(groupedStyles.get(j)) == groupingKind) {
groupedStyles.add(i++, groupedStyles.remove(j));
}
}
}
}
includeGroupList.setElements(groupedStyles);
}
private boolean areKeptTogether(IncludeGroupStyle style1, IncludeGroupStyle style2) {
IncludeKind kind = getGroupingParentKind(style1);
return kind != null && kind == getGroupingParentKind(style2);
}
private IncludeKind getGroupingParentKind(IncludeGroupStyle style) {
IncludeKind kind = style.getIncludeKind().parent;
if (kind == null)
return null;
while (true) {
// "Other" include kind is special since it applies only to non grouped includes.
if (kind == IncludeKind.OTHER && style.isKeepTogether())
break;
IncludeGroupStyle parent = stylesByKind.get(kind);
if (parent != null && parent.isKeepTogether())
return kind;
if (kind == IncludeKind.OTHER)
break;
kind = IncludeKind.OTHER;
}
return null;
}
private static Map<IncludeKind, IncludeGroupStyle> getStylesByKind(List<IncludeGroupStyle> styles) {
Map<IncludeKind, IncludeGroupStyle> stylesByKind = new HashMap<>();
for (IncludeGroupStyle style : styles) {
if (style.getIncludeKind() != IncludeKind.MATCHING_PATTERN)
stylesByKind.put(style.getIncludeKind(), style);
}
return stylesByKind;
}
private boolean hasUngroupedChildren(IncludeKind includeKind, Map<IncludeKind, IncludeGroupStyle> stylesByKind) {
// This code relies on the fact that IncludeKind hierarchy is only two levels deep.
for (IncludeKind childKind : includeKind.children) {
if (!stylesByKind.get(childKind).isKeepTogether())
return true;
}
// "Other" include kind is special since it effectively includes all other ungrouped includes.
if (includeKind == IncludeKind.OTHER) {
for (IncludeKind kind : stylesByKind.keySet()) {
if (kind != IncludeKind.OTHER && kind.hasChildren() && !stylesByKind.get(kind).isKeepTogether()
&& hasUngroupedChildren(kind, stylesByKind)) {
return true;
}
}
}
return false;
}
@Override
protected void validateSettings(Key changedKey, String oldValue, String newValue) {
fContext.statusChanged(new StatusInfo());
}
private class GroupListField extends ListDialogField<IncludeGroupStyle> {
GroupListField() {
super(null, UP_DOWN_LABELS, new GroupLabelProvider());
}
@Override
public void dialogFieldChanged() {
super.dialogFieldChanged();
int order = 0;
for (IncludeGroupStyle style : getElements()) {
style.setOrder(order++);
}
}
@Override
protected boolean getManagedButtonState(ISelection sel, int index) {
if (index == IDX_UP) {
return !sel.isEmpty() && canMoveUp();
} else if (index == IDX_DOWN) {
return !sel.isEmpty() && canMoveDown();
}
return true;
}
@Override
protected boolean managedButtonPressed(int index) {
if (index == IDX_UP) {
up();
} else if (index == IDX_DOWN) {
down();
} else {
return false;
}
return true;
}
@Override
protected void up() {
boolean[] selected = getSelectionMask(false);
extendSelectionForMovingUp(selected, fElements);
if (selected != null) {
setElements(moveUp(fElements, selected));
fTable.reveal(fElements.get(getFirstSelected(selected)));
}
}
@Override
protected void down() {
boolean[] selected = getSelectionMask(true);
List<IncludeGroupStyle> reversed = reverse(fElements);
extendSelectionForMovingUp(selected, reversed);
if (selected != null) {
setElements(reverse(moveUp(reversed, selected)));
fTable.reveal(fElements.get(getFirstSelected(selected)));
}
}
private List<IncludeGroupStyle> reverse(List<IncludeGroupStyle> p) {
List<IncludeGroupStyle> reverse = new ArrayList<>(p.size());
for (int i = p.size(); --i >= 0;) {
reverse.add(p.get(i));
}
return reverse;
}
private boolean[] getSelectionMask(boolean reverse) {
boolean[] selectionMask = null;
if (isOkToUse(fTableControl)) {
int nElements = fElements.size();
for (int i : fTable.getTable().getSelectionIndices()) {
if (selectionMask == null)
selectionMask = new boolean[nElements];
selectionMask[reverse ? nElements - 1 - i : i] = true;
}
}
return selectionMask;
}
private void extendSelectionForMovingUp(boolean[] selection, List<IncludeGroupStyle> styles) {
for (int i = 1; i < selection.length; i++) {
int j = i - 1;
if (!selection[i] && selection[j] && areKeptTogether(styles.get(i), styles.get(j))) {
selection[i] = true;
}
}
}
private List<IncludeGroupStyle> moveUp(List<IncludeGroupStyle> elements, boolean[] selected) {
int nElements = elements.size();
List<IncludeGroupStyle> res = new ArrayList<>(nElements);
List<IncludeGroupStyle> floating = new ArrayList<>();
for (int i = 0; i < nElements; i++) {
IncludeGroupStyle curr = elements.get(i);
if (selected[i]) {
res.add(curr);
} else {
if (!floating.isEmpty() && !areKeptTogether(curr, floating.get(0))) {
res.addAll(floating);
floating.clear();
}
floating.add(curr);
}
}
res.addAll(floating);
return res;
}
private int getFirstSelected(boolean[] selected) {
for (int i = 0; i < selected.length; i++) {
if (selected[i])
return i;
}
return -1;
}
@Override
protected boolean canMoveUp() {
boolean[] selected = getSelectionMask(false);
if (selected == null || selected[0])
return false;
return canMoveUp(fElements, selected);
}
@Override
protected boolean canMoveDown() {
boolean[] selected = getSelectionMask(true);
if (selected == null || selected[0])
return false;
return canMoveUp(reverse(fElements), selected);
}
private boolean canMoveUp(List<IncludeGroupStyle> elements, boolean[] selected) {
for (int i = 1; i < selected.length; i++) {
int j = i - 1;
if (selected[i] && !selected[j] && areKeptTogether(elements.get(i), elements.get(j))) {
while (++i < selected.length && selected[i]) {
}
if (!areKeptTogether(elements.get(i - 1), elements.get(j)))
return false; // Cannot break a group.
}
}
return true;
}
}
private static class GroupLabelProvider extends LabelProvider {
@Override
public Image getImage(Object element) {
return null;
}
@Override
public String getText(Object element) {
IncludeGroupStyle style = (IncludeGroupStyle) element;
String name = style.getName();
if (name == null) {
name = style.getIncludeKind().name;
}
return name;
}
}
}