/******************************************************************************* * Copyright (c) 2013 Google, Inc and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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.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; 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; /** * 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<IncludeGroupStyle>(styles); Collections.sort(orderedStyles); // Sort according to values returned by getOrder() method. List<IncludeGroupStyle> groupedStyles = new ArrayList<IncludeGroupStyle>(); 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<IncludeKind, IncludeGroupStyle>(); 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<IncludeGroupStyle>(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<IncludeGroupStyle>(nElements); List<IncludeGroupStyle> floating = new ArrayList<IncludeGroupStyle>(); 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; } } }