/*******************************************************************************
* Copyright (c) 2013 Zend Techologies Ltd.
* 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:
* Zend Technologies Ltd. - initial API and implementation
*******************************************************************************/
package org.eclipse.php.formatter.ui.preferences;
import java.util.*;
import java.util.List;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.viewers.*;
import org.eclipse.php.formatter.core.CodeFormatterConstants;
import org.eclipse.php.formatter.core.profiles.CodeFormatterPreferences;
import org.eclipse.php.formatter.ui.FormatterMessages;
import org.eclipse.php.formatter.ui.FormatterUIPlugin;
import org.eclipse.php.internal.ui.util.Messages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.*;
/**
* @author yaronm
*/
public class LineWrappingTabPage extends ModifyDialogTabPage {
/**
* Represents a line wrapping category. All members are final.
*/
private static final class Category {
private static final String LINE_WRAP_POLICY_KEY = "_line_wrap_policy";
private static final String INDENT_POLICY_KEY = "_indent_policy";
public final String key;
public final String name;
public final String previewText;
public final List<Category> children;
public int index;
public Category(String _key, String _previewText, String _name) {
this.key = _key;
this.name = _name;
this.previewText = _previewText != null ? createPreviewHeader(_name) + _previewText : null;
children = new ArrayList<>();
}
/**
* @param _name
* Category name
*/
public Category(String _name) {
this(null, null, _name);
}
@Override
public String toString() {
return name;
}
public String getLineWrappingPolicyKey() {
return key + LINE_WRAP_POLICY_KEY;
}
public String getIndentPolicyKey() {
return key + INDENT_POLICY_KEY;
}
public String getForceSplitKey() {
return key + "_force_split";
}
}
private static final String PREF_CATEGORY_INDEX = FormatterUIPlugin.PLUGIN_ID
+ "formatter_page.line_wrapping_tab_page.last_category_index"; //$NON-NLS-1$
private final class CategoryListener implements ISelectionChangedListener, IDoubleClickListener {
private final List<Category> fCategoriesList;
private int fIndex = 0;
public CategoryListener(List<Category> categoriesTree) {
fCategoriesList = new ArrayList<>();
flatten(fCategoriesList, categoriesTree);
}
private void flatten(List<Category> categoriesList, List<Category> categoriesTree) {
for (final Iterator<Category> iter = categoriesTree.iterator(); iter.hasNext();) {
final Category category = iter.next();
category.index = fIndex++;
categoriesList.add(category);
flatten(categoriesList, category.children);
}
}
@Override
public void selectionChanged(SelectionChangedEvent event) {
if (event != null) {
fSelection = (IStructuredSelection) event.getSelection();
}
if (fSelection.size() == 0) {
disableAll();
return;
}
if (!fOptionsGroup.isEnabled())
enableDefaultComponents(true);
fSelectionState.refreshState(fSelection);
final Category category = (Category) fSelection.getFirstElement();
fDialogSettings.put(PREF_CATEGORY_INDEX, category.index);
fOptionsGroup.setText(getGroupLabel(category));
}
private String getGroupLabel(Category category) {
if (fSelection.size() == 1) {
if (fSelectionState.getElements().size() == 1) {
return Messages.format(FormatterMessages.LineWrappingTabPage_group, category.name.toLowerCase());
}
return Messages.format(FormatterMessages.LineWrappingTabPage_multi_group, new String[] {
category.name.toLowerCase(), Integer.toString(fSelectionState.getElements().size()) });
}
return Messages.format(FormatterMessages.LineWrappingTabPage_multiple_selections,
new String[] { Integer.toString(fSelectionState.getElements().size()) });
}
private void disableAll() {
enableDefaultComponents(false);
fIndentStyleCombo.setEnabled(false);
fForceSplit.setEnabled(false);
}
private void enableDefaultComponents(boolean enabled) {
fOptionsGroup.setEnabled(enabled);
fWrappingStyleCombo.setEnabled(enabled);
fWrappingStylePolicy.setEnabled(enabled);
}
public void restoreSelection() {
int index;
try {
index = fDialogSettings.getInt(PREF_CATEGORY_INDEX);
} catch (NumberFormatException ex) {
index = -1;
}
if (index < 0 || index > fCategoriesList.size() - 1) {
index = 1; // In order to select a category with preview
// initially
}
final Category category = fCategoriesList.get(index);
fCategoriesViewer.setSelection(new StructuredSelection(new Category[] { category }));
}
@Override
public void doubleClick(DoubleClickEvent event) {
final ISelection selection = event.getSelection();
if (selection instanceof IStructuredSelection) {
final Category node = (Category) ((IStructuredSelection) selection).getFirstElement();
fCategoriesViewer.setExpandedState(node, !fCategoriesViewer.getExpandedState(node));
}
}
}
private class SelectionState {
private List<Category> fElements = new ArrayList<>();
public void refreshState(IStructuredSelection selection) {
fElements.clear();
Iterator<Category> iterator = selection.iterator();
evaluateElements(iterator);
setPreviewText(getPreviewText());
refreshControls(fElements.get(0));
updateControlEnablement();
}
public List<Category> getElements() {
return fElements;
}
private void evaluateElements(Iterator<Category> iterator) {
Category category;
while (iterator.hasNext()) {
category = iterator.next();
if (category.children.size() == 0) {
if (!fElements.contains(category)) {
fElements.add(category);
}
} else {
evaluateElements(category.children.iterator());
}
}
}
private String getPreviewText() {
Iterator<Category> iterator = fElements.iterator();
String previewText = "<?php\n"; //$NON-NLS-1$
while (iterator.hasNext()) {
Category category = iterator.next();
previewText = previewText + category.previewText + "\n\n"; //$NON-NLS-1$
}
return previewText + "?>";
}
private void refreshControls(Category category) {
updateCombos(category);
updateButton(category);
doUpdatePreview();
}
private void updateButton(Category category) {
boolean isSelected = Boolean
.valueOf(codeFormatterPreferences.getMap().get(category.getForceSplitKey()).toString());
fForceSplit.setSelection(isSelected);
}
private void updateCombos(Category category) {
fWrappingStyleCombo.select(Integer
.parseInt((String) codeFormatterPreferences.getMap().get(category.getLineWrappingPolicyKey())));
fIndentStyleCombo.select(
Integer.parseInt((String) codeFormatterPreferences.getMap().get(category.getIndentPolicyKey())));
}
}
protected static final String[] INDENT_NAMES = { FormatterMessages.LineWrappingTabPage_indentation_default,
FormatterMessages.LineWrappingTabPage_indentation_on_column,
FormatterMessages.LineWrappingTabPage_indentation_by_one };
protected static final String[] WRAPPING_NAMES = { FormatterMessages.LineWrappingTabPage_splitting_do_not_split,
FormatterMessages.LineWrappingTabPage_splitting_wrap_when_necessary, // COMPACT_SPLIT
FormatterMessages.LineWrappingTabPage_splitting_always_wrap_first_others_when_necessary, // COMPACT_FIRST_BREAK_SPLIT
FormatterMessages.LineWrappingTabPage_splitting_wrap_always, // ONE_PER_LINE_SPLIT
FormatterMessages.LineWrappingTabPage_splitting_wrap_always_indent_all_but_first, // NEXT_SHIFTED_SPLIT
FormatterMessages.LineWrappingTabPage_splitting_wrap_always_except_first_only_if_necessary };
private final Category fTypeDeclarationSuperinterfacesCategory = new Category(
CodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_SUPERINTERFACES_IN_TYPE_DECLARATION_KEY,
"class Example implements I1, I2, I3 {}", //$NON-NLS-1$
FormatterMessages.LineWrappingTabPage_implements_clause);
private final Category fMethodDeclarationsParametersCategory = new Category(
CodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION_KEY,
"class Example {function foo( $a1, $a2, $a3, $a4, $a5, $a6) {}}", //$NON-NLS-1$
FormatterMessages.LineWrappingTabPage_parameters);
private final Category fMessageSendArgumentsCategory = new Category(
CodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION_KEY,
"class Example {function foo() { Other::bar( 100, 200, 300, 400, 500, 600, 700, 800, 900 );}}", //$NON-NLS-1$
FormatterMessages.LineWrappingTabPage_arguments);
private final Category fAllocationExpressionArgumentsCategory = new Category(
CodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_ALLOCATION_EXPRESSION_KEY,
"class Example {function foo() {return new SomeClass(100, 200, 300, 400, 500, 600, 700, 800, 900 );}}", //$NON-NLS-1$
FormatterMessages.LineWrappingTabPage_object_allocation);
private final Category fArrayInitializerExpressionsCategory = new Category(
CodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER_KEY,
"class Example {var $a = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);}", //$NON-NLS-1$
FormatterMessages.LineWrappingTabPage_array_init);
private final Category fBinaryExpressionCategory = new Category(
CodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_BINARY_EXPRESSION_KEY,
"class Example extends AnotherClass {" + //$NON-NLS-1$
"function foo() {" + //$NON-NLS-1$
" $sum= 100 + 200 + 300 + 400 + 500 + 600 + 700 + 800;" + //$NON-NLS-1$
" $product= 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10;" + //$NON-NLS-1$
" $val = true && false && true && false && true;" + //$NON-NLS-1$
" return product / sum;}}", //$NON-NLS-1$
FormatterMessages.LineWrappingTabPage_binary_exprs);
/**
* The default preview line width.
*/
private static int DEFAULT_PREVIEW_WINDOW_LINE_WIDTH = 40;
/**
* The key to save the user's preview window width in the dialog settings.
*/
private static final String PREF_PREVIEW_LINE_WIDTH = FormatterUIPlugin.PLUGIN_ID
+ "formatter_page.line_wrapping_tab_page.preview_line_width"; //$NON-NLS-1$
/**
* The dialog settings.
*/
protected final IDialogSettings fDialogSettings;
protected TreeViewer fCategoriesViewer;
protected Label fWrappingStylePolicy;
protected Combo fWrappingStyleCombo;
protected Label fIndentStylePolicy;
protected Combo fIndentStyleCombo;
protected Button fForceSplit;
protected CodeFormatterPreview fPreview;
private NumberPreference fMaxLineWidthPref;
private NumberPreference fDefaultIndentWrapLines;
// private NumberPreference fPreviewLineWidth;
protected Group fOptionsGroup;
/**
* A collection containing the categories tree. This is used as model for
* the tree viewer.
*
* @see TreeViewer
*/
private final List<Category> fCategories;
/**
* The category listener which makes the selection persistent.
*/
protected final CategoryListener fCategoryListener;
/**
* The current selection of elements.
*/
protected IStructuredSelection fSelection;
/**
* An object containing the state for the UI.
*/
SelectionState fSelectionState;
/**
* A special options store wherein the preview line width is kept.
*/
protected final Map<String, String> fPreviewPreferences;
/**
* The key for the preview line width.
*/
private final String LINE_SPLIT = CodeFormatterConstants.FORMATTER_LINE_SPLIT;
private boolean isInitialized;
/**
* Create a new line wrapping tab page.
*
* @param modifyDialog
* @param workingValues
*/
public LineWrappingTabPage(ModifyDialog modifyDialog, CodeFormatterPreferences codeFormatterPreferences) {
super(modifyDialog, codeFormatterPreferences);
fDialogSettings = FormatterUIPlugin.getDefault().getDialogSettings();
final String previewLineWidth = fDialogSettings.get(PREF_PREVIEW_LINE_WIDTH);
fPreviewPreferences = new HashMap<>();
fPreviewPreferences.put(LINE_SPLIT,
previewLineWidth != null ? previewLineWidth : Integer.toString(DEFAULT_PREVIEW_WINDOW_LINE_WIDTH));
fCategories = createCategories();
fCategoryListener = new CategoryListener(fCategories);
}
/**
* @return Create the categories tree.
*/
protected List<Category> createCategories() {
final Category classDeclarations = new Category(FormatterMessages.LineWrappingTabPage_class_decls);
classDeclarations.children.add(fTypeDeclarationSuperinterfacesCategory);
final Category methodDeclarations = new Category(null, null,
FormatterMessages.LineWrappingTabPage_method_decls);
methodDeclarations.children.add(fMethodDeclarationsParametersCategory);
final Category functionCalls = new Category(FormatterMessages.LineWrappingTabPage_function_calls);
functionCalls.children.add(fMessageSendArgumentsCategory);
functionCalls.children.add(fAllocationExpressionArgumentsCategory);
final Category expressions = new Category(FormatterMessages.LineWrappingTabPage_expressions);
expressions.children.add(fBinaryExpressionCategory);
expressions.children.add(fArrayInitializerExpressionsCategory);
final List<Category> root = new ArrayList<>();
root.add(classDeclarations);
root.add(methodDeclarations);
root.add(functionCalls);
root.add(expressions);
return root;
}
@Override
protected void doCreatePreferences(Composite composite, int numColumns) {
final Group lineWidthGroup = createGroup(numColumns, composite,
FormatterMessages.LineWrappingTabPage_width_indent);
fMaxLineWidthPref = createNumberPref(lineWidthGroup, numColumns,
FormatterMessages.LineWrappingTabPage_width_indent_option_max_line_width, 0, 9999);
fMaxLineWidthPref.setValue(codeFormatterPreferences.line_wrap_line_split);
fDefaultIndentWrapLines = createNumberPref(lineWidthGroup, numColumns,
FormatterMessages.LineWrappingTabPage_width_indent_option_default_indent_wrapped, 0, 9999);
fDefaultIndentWrapLines.setValue(codeFormatterPreferences.line_wrap_wrapped_lines_indentation);
fCategoriesViewer = new TreeViewer(composite /* categoryGroup */,
SWT.MULTI | SWT.BORDER | SWT.READ_ONLY | SWT.V_SCROLL);
fCategoriesViewer.setContentProvider(new ITreeContentProvider() {
@Override
public Object[] getElements(Object inputElement) {
return ((Collection<?>) inputElement).toArray();
}
@Override
public Object[] getChildren(Object parentElement) {
return ((Category) parentElement).children.toArray();
}
@Override
public Object getParent(Object element) {
return null;
}
@Override
public boolean hasChildren(Object element) {
return !((Category) element).children.isEmpty();
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
@Override
public void dispose() {
}
});
fCategoriesViewer.setLabelProvider(new LabelProvider());
fCategoriesViewer.setInput(fCategories);
fCategoriesViewer.setExpandedElements(fCategories.toArray());
final GridData gd = createGridData(numColumns, GridData.FILL_BOTH, SWT.DEFAULT);
fCategoriesViewer.getControl().setLayoutData(gd);
fOptionsGroup = createGroup(numColumns, composite, ""); //$NON-NLS-1$
// label "Select split style:"
fWrappingStylePolicy = createLabel(numColumns, fOptionsGroup,
FormatterMessages.LineWrappingTabPage_wrapping_policy_label_text);
// combo SplitStyleCombo
fWrappingStyleCombo = new Combo(fOptionsGroup, SWT.SINGLE | SWT.READ_ONLY);
fWrappingStyleCombo.setItems(WRAPPING_NAMES);
fWrappingStyleCombo.setLayoutData(createGridData(numColumns, GridData.HORIZONTAL_ALIGN_FILL, 0));
// label "Select indentation style:"
fIndentStylePolicy = createLabel(numColumns, fOptionsGroup,
FormatterMessages.LineWrappingTabPage_indentation_policy_label_text);
// combo SplitStyleCombo
fIndentStyleCombo = new Combo(fOptionsGroup, SWT.SINGLE | SWT.READ_ONLY);
fIndentStyleCombo.setItems(INDENT_NAMES);
fIndentStyleCombo.setLayoutData(createGridData(numColumns, GridData.HORIZONTAL_ALIGN_FILL, 0));
// button "Force split"
fForceSplit = new Button(fOptionsGroup, SWT.CHECK);
fForceSplit.setLayoutData(createGridData(numColumns, GridData.HORIZONTAL_ALIGN_FILL, 0));
fForceSplit.setText(FormatterMessages.LineWrappingTabPage_force_split_checkbox_text);
// selection state object
fSelectionState = new SelectionState();
isInitialized = true;
}
protected void setPreviewText(String text) {
fPreview.setPreviewText(text);
}
protected void updateControlEnablement() {
boolean isLineWrapEnabled = fWrappingStyleCombo.getSelectionIndex() != 0;
fIndentStyleCombo.setEnabled(isLineWrapEnabled);
fForceSplit.setEnabled(isLineWrapEnabled);
}
@Override
protected void updatePreferences() {
if (isInitialized) {
codeFormatterPreferences.line_wrap_line_split = fMaxLineWidthPref.getValue();
codeFormatterPreferences.line_wrap_wrapped_lines_indentation = fDefaultIndentWrapLines.getValue();
}
}
@Override
protected PHPPreview doCreatePhpPreview(Composite parent) {
fPreview = new CodeFormatterPreview(codeFormatterPreferences, parent);
return fPreview;
}
@Override
protected void doUpdatePreview() {
}
@Override
protected void initializePage() {
fCategoriesViewer.addSelectionChangedListener(fCategoryListener);
fCategoriesViewer.addDoubleClickListener(fCategoryListener);
fForceSplit.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
boolean isSelected = fForceSplit.getSelection();
Iterator<Category> iter = fSelectionState.fElements.iterator();
Map<String, Object> map = codeFormatterPreferences.getMap();
while (iter.hasNext()) {
map.put(iter.next().getForceSplitKey(), Boolean.toString(isSelected));
}
codeFormatterPreferences.setPreferencesValues(map);
notifyValuesModified();
}
});
fIndentStyleCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
int comboSelection = fIndentStyleCombo.getSelectionIndex();
Iterator<Category> iter = fSelectionState.fElements.iterator();
Map<String, Object> map = codeFormatterPreferences.getMap();
while (iter.hasNext()) {
map.put(iter.next().getIndentPolicyKey(), Integer.toString(comboSelection));
}
codeFormatterPreferences.setPreferencesValues(map);
notifyValuesModified();
}
});
fWrappingStyleCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
updateControlEnablement();
int comboSelection = fWrappingStyleCombo.getSelectionIndex();
Iterator<Category> iter = fSelectionState.fElements.iterator();
Map<String, Object> map = codeFormatterPreferences.getMap();
while (iter.hasNext()) {
map.put(iter.next().getLineWrappingPolicyKey(), Integer.toString(comboSelection));
}
codeFormatterPreferences.setPreferencesValues(map);
notifyValuesModified();
}
});
fCategoryListener.restoreSelection();
fDefaultFocusManager.add(fCategoriesViewer.getControl());
fDefaultFocusManager.add(fWrappingStyleCombo);
fDefaultFocusManager.add(fIndentStyleCombo);
fDefaultFocusManager.add(fForceSplit);
}
@Override
protected void notifyValuesModified() {
super.notifyValuesModified();
if (fPreview != null) {
fPreview.update();
}
}
}