Java tutorial
/** * Aptana Studio * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.theme.preferences; import java.io.File; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.IInputValidator; import org.eclipse.jface.dialogs.InputDialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.fieldassist.ControlDecoration; import org.eclipse.jface.fieldassist.FieldDecoration; import org.eclipse.jface.fieldassist.FieldDecorationRegistry; import org.eclipse.jface.layout.TableColumnLayout; import org.eclipse.jface.preference.ColorSelector; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.preference.PreferenceConverter; import org.eclipse.jface.preference.PreferencePage; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.TextAttribute; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.BaseLabelProvider; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ColumnPixelData; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.EditingSupport; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.TableEditor; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DragSource; import org.eclipse.swt.dnd.DragSourceAdapter; import org.eclipse.swt.dnd.DragSourceEvent; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.dnd.DropTargetAdapter; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.ColorDialog; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.FontDialog; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Scrollable; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPreferencePage; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.internal.themes.ThemeElementHelper; import org.eclipse.ui.themes.ITheme; import org.osgi.service.prefs.BackingStoreException; import com.aptana.core.CoreStrings; import com.aptana.core.logging.IdeLog; import com.aptana.core.util.EclipseUtil; import com.aptana.core.util.StringUtil; import com.aptana.scope.ScopeSelector; import com.aptana.theme.ConsoleThemer; import com.aptana.theme.DelayedTextAttribute; import com.aptana.theme.IThemeManager; import com.aptana.theme.RGBa; import com.aptana.theme.TextmateImporter; import com.aptana.theme.Theme; import com.aptana.theme.ThemeExporter; import com.aptana.theme.ThemePlugin; import com.aptana.theme.ThemeRule; @SuppressWarnings("restriction") public class ThemePreferencePage extends PreferencePage implements IWorkbenchPreferencePage, SelectionListener, IInputValidator, IPropertyChangeListener { /** * ID of the pref page to use when opening/referring to it programmatically. */ public static final String ID = "com.aptana.theme.preferencePage"; //$NON-NLS-1$ /** * Key to store the dialog settings for the initial directory to open when importing themes (saves last directory). */ private static final String THEME_DIRECTORY = "themeDirectory"; //$NON-NLS-1$ /** * Key to store the dialog settings for the initial directory to open when exporting themes (saves last directory). */ private static final String THEME_EXPORT_DIRECTORY = "themeExportDirectory"; //$NON-NLS-1$ /** * The list of "standard" token types to set up for a theme. */ private static List<String> tokenTypeNames = new ArrayList<String>(); static { tokenTypeNames.add("comment"); //$NON-NLS-1$ tokenTypeNames.add("comment.block"); //$NON-NLS-1$ tokenTypeNames.add("comment.documentation"); //$NON-NLS-1$ tokenTypeNames.add("comment.line"); //$NON-NLS-1$ tokenTypeNames.add("constant"); //$NON-NLS-1$ tokenTypeNames.add("constant.character"); //$NON-NLS-1$ tokenTypeNames.add("constant.language"); //$NON-NLS-1$ tokenTypeNames.add("constant.numeric"); //$NON-NLS-1$ tokenTypeNames.add("constant.other"); //$NON-NLS-1$ tokenTypeNames.add(ConsoleThemer.CONSOLE_ERROR); tokenTypeNames.add(ConsoleThemer.CONSOLE_INPUT); tokenTypeNames.add(ConsoleThemer.CONSOLE_OUTPUT); tokenTypeNames.add(ConsoleThemer.CONSOLE_PROMPT); tokenTypeNames.add(ConsoleThemer.CONSOLE_WARNING); tokenTypeNames.add("entity.name"); //$NON-NLS-1$ tokenTypeNames.add("entity.name.class"); //$NON-NLS-1$ tokenTypeNames.add("entity.name.function"); //$NON-NLS-1$ tokenTypeNames.add("entity.name.tag"); //$NON-NLS-1$ tokenTypeNames.add("entity.other"); //$NON-NLS-1$ tokenTypeNames.add("entity.other.attribute-name"); //$NON-NLS-1$ tokenTypeNames.add("entity.other.inherited-class"); //$NON-NLS-1$ tokenTypeNames.add("invalid"); //$NON-NLS-1$ tokenTypeNames.add("invalid.deprecated"); //$NON-NLS-1$ tokenTypeNames.add("invalid.illegal"); //$NON-NLS-1$ tokenTypeNames.add("keyword"); //$NON-NLS-1$ tokenTypeNames.add("keyword.control"); //$NON-NLS-1$ tokenTypeNames.add("keyword.operator"); //$NON-NLS-1$ tokenTypeNames.add("keyword.other"); //$NON-NLS-1$ tokenTypeNames.add("storage"); //$NON-NLS-1$ tokenTypeNames.add("storage.modifier"); //$NON-NLS-1$ tokenTypeNames.add("storage.other"); //$NON-NLS-1$ tokenTypeNames.add("storage.type"); //$NON-NLS-1$ tokenTypeNames.add("string"); //$NON-NLS-1$ tokenTypeNames.add("string.interpolated"); //$NON-NLS-1$ tokenTypeNames.add("string.other"); //$NON-NLS-1$ tokenTypeNames.add("string.quoted"); //$NON-NLS-1$ tokenTypeNames.add("string.regexp"); //$NON-NLS-1$ tokenTypeNames.add("string.unquoted"); //$NON-NLS-1$ tokenTypeNames.add("support"); //$NON-NLS-1$ tokenTypeNames.add("support.class"); //$NON-NLS-1$ tokenTypeNames.add("support.constant"); //$NON-NLS-1$ tokenTypeNames.add("support.function"); //$NON-NLS-1$ tokenTypeNames.add("support.other"); //$NON-NLS-1$ tokenTypeNames.add("support.type"); //$NON-NLS-1$ tokenTypeNames.add("variable"); //$NON-NLS-1$ tokenTypeNames.add("variable.language"); //$NON-NLS-1$ tokenTypeNames.add("variable.other"); //$NON-NLS-1$ tokenTypeNames.add("variable.parameter"); //$NON-NLS-1$ } private static final int ROW_HEIGHT = 20; protected Theme fSelectedTheme; private ColorSelector fgSelector; private ColorSelector bgSelector; private ColorSelector lineHighlightSelector; private ColorSelector selectionSelector; private ColorSelector caretSelector; private Combo fThemeCombo; private TableViewer tableViewer; private Set<TableEditor> fTableEditors; private Button renameThemeButton; private Button deleteThemeButton; private HashMap<Integer, Font> fFonts; private Button fInvasiveThemeCheckbox; private Button fInvasiveFontCheckbox; private Button fAddThemeButton; private Button fImportButton; private Button fAddTokenButton; private Button fRemoveTokenButton; private Combo fScopeText; private Button fExportButton; private Font fFont; private Text fFontText; private boolean reorderingRules = false; private ControlDecoration fScopeSelectorDecoration; private Button fAptanaEditorsOnlyCheckbox; @Override protected Control createContents(Composite parent) { Composite composite = new Composite(parent, SWT.NONE); composite.setLayout(new GridLayout()); composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); Group group = new Group(composite, SWT.SHADOW_IN); group.setLayout(new GridLayout()); group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); createThemeListControls(group); createGlobalColorControls(group); createTokenEditTable(group); createFontArea(composite); createInvasivePrefArea(composite); setTheme(getThemeManager().getCurrentTheme().getName()); return composite; } private void createFontArea(Composite composite) { Composite themesComp = new Composite(composite, SWT.NONE); themesComp.setLayout(new GridLayout(3, false)); themesComp.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); Label label = new Label(themesComp, SWT.NONE); label.setText(Messages.ThemePreferencePage_FontNameLabel); fFont = JFaceResources.getFontRegistry().get(JFaceResources.TEXT_FONT); fFontText = new Text(themesComp, SWT.BORDER | SWT.SINGLE | SWT.READ_ONLY); fFontText.setText(toString(fFont)); fFontText.setFont(fFont); fFontText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); Button selectFontButton = new Button(themesComp, SWT.PUSH); selectFontButton.setText(Messages.ThemePreferencePage_SelectFontButtonLabel); selectFontButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { final FontDialog fontDialog = new FontDialog(getShell()); fontDialog.setFontList(fFont.getFontData()); final FontData data = fontDialog.open(); if (data != null) { setFont(new Font(fFont.getDevice(), fontDialog.getFontList())); } } }); } private static String toString(Font font) { if (font == null || font.getFontData() == null || font.getFontData().length <= 0) { return StringUtil.EMPTY; } FontData data = font.getFontData()[0]; return MessageFormat.format(Messages.ThemePreferencePage_FontName, data.getName(), data.getHeight()); } private void createInvasivePrefArea(Composite composite) { Composite themesComp = new Composite(composite, SWT.NONE); themesComp.setLayout(new GridLayout(1, false)); // do we have "invasive" theming on? This applies our colors to non-Aptana views (and possibly editors) fInvasiveThemeCheckbox = new Button(themesComp, SWT.CHECK); fInvasiveThemeCheckbox.setText(Messages.ThemePreferencePage_ApplyToAllViews); fInvasiveThemeCheckbox.setSelection(ThemePlugin.applyToViews()); fInvasiveThemeCheckbox.addSelectionListener(this); fInvasiveThemeCheckbox.setToolTipText(Messages.ThemePreferencePage_ApplyToAllViewsToolTip); // Do we apply the colors to editors that aren't ours? fAptanaEditorsOnlyCheckbox = new Button(themesComp, SWT.CHECK); fAptanaEditorsOnlyCheckbox.setText(Messages.ThemePreferencePage_ApplyToAllEditors); fAptanaEditorsOnlyCheckbox.setSelection(ThemePlugin.applyToAllEditors()); fAptanaEditorsOnlyCheckbox.addSelectionListener(this); // Do we force the views to use the editor font? fInvasiveFontCheckbox = new Button(themesComp, SWT.CHECK); fInvasiveFontCheckbox.setText(Messages.ThemePreferencePage_InvasiveFontLBL); fInvasiveFontCheckbox.setSelection(Platform.getPreferencesService().getBoolean(ThemePlugin.PLUGIN_ID, IPreferenceConstants.INVASIVE_FONT, false, null)); fInvasiveFontCheckbox.addSelectionListener(this); fInvasiveFontCheckbox.setToolTipText(Messages.ThemePreferencePage_InvasiveFontToolTip); } protected IThemeManager getThemeManager() { return ThemePlugin.getDefault().getThemeManager(); } private void createThemeListControls(Composite composite) { Composite themesComp = new Composite(composite, SWT.NONE); themesComp.setLayout(new GridLayout(6, false)); fThemeCombo = new Combo(themesComp, SWT.DROP_DOWN | SWT.READ_ONLY); loadThemeNames(); fThemeCombo.addSelectionListener(this); fAddThemeButton = new Button(themesComp, SWT.PUSH | SWT.FLAT); fAddThemeButton.setText(Messages.ThemePreferencePage_AddTokenLabel); fAddThemeButton.addSelectionListener(this); renameThemeButton = new Button(themesComp, SWT.PUSH | SWT.FLAT); renameThemeButton.setText(CoreStrings.RENAME); renameThemeButton.addSelectionListener(this); deleteThemeButton = new Button(themesComp, SWT.PUSH | SWT.FLAT); deleteThemeButton.setText(Messages.ThemePreferencePage_RemoveTokenLabel); deleteThemeButton.addSelectionListener(this); // Textmate Import fImportButton = new Button(themesComp, SWT.PUSH | SWT.FLAT); fImportButton.setText(Messages.ThemePreferencePage_ImportLabel); fImportButton.addSelectionListener(this); fExportButton = new Button(themesComp, SWT.PUSH | SWT.FLAT); fExportButton.setText(Messages.ThemePreferencePage_ExportLabel); fExportButton.addSelectionListener(this); } private void loadThemeNames() { fThemeCombo.removeAll(); List<String> themeNames = new ArrayList<String>(getThemeManager().getThemeNames()); Collections.sort(themeNames, new Comparator<String>() { public int compare(String o1, String o2) { return o1.compareToIgnoreCase(o2); } }); for (String themeName : themeNames) { fThemeCombo.add(themeName); } } private void createGlobalColorControls(Composite composite) { Composite colors = new Composite(composite, SWT.NONE); colors.setLayout(new GridLayout(4, false)); // TODO Make the ColorSelector buttons be SWT.FLAT, and let them handle alpha values as Textmate does Label label = new Label(colors, SWT.NONE); label.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); label.setText(Messages.ThemePreferencePage_ForegroundLabel); fgSelector = new ColorSelector(colors); fgSelector.addListener(this); label = new Label(colors, SWT.NONE); label.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); label.setText(Messages.ThemePreferencePage_SelectionLabel); selectionSelector = new ColorSelector(colors); selectionSelector.addListener(this); label = new Label(colors, SWT.NONE); label.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); label.setText(Messages.ThemePreferencePage_BackgroundLabel); bgSelector = new ColorSelector(colors); bgSelector.addListener(this); label = new Label(colors, SWT.NONE); label.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); label.setText(Messages.ThemePreferencePage_LineHighlightLabel); lineHighlightSelector = new ColorSelector(colors); lineHighlightSelector.addListener(this); label = new Label(colors, SWT.NONE); label.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false)); label.setText(Messages.ThemePreferencePage_CaretLabel); caretSelector = new ColorSelector(colors); caretSelector.addListener(this); } private void createTokenEditTable(Composite composite) { // FIXME allow drag and drop to sort items in the table! Composite comp = new Composite(composite, SWT.NONE); GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); gridData.heightHint = 200; comp.setLayoutData(gridData); TableColumnLayout layout = new TableColumnLayout(); comp.setLayout(layout); final Table table = new Table(comp, SWT.FULL_SELECTION | SWT.SINGLE | SWT.V_SCROLL); table.setHeaderVisible(true); table.setLinesVisible(false); // Hack to force a specific row height table.addListener(SWT.MeasureItem, new Listener() { public void handleEvent(Event event) { event.height = ROW_HEIGHT; } }); // Hack to draw the underline in first column table.addListener(SWT.PaintItem, new Listener() { public void handleEvent(Event event) { if ((event.detail & SWT.FOREGROUND) != 0 && event.index == 0) { TableItem item = (TableItem) event.item; ThemeRule token = (ThemeRule) item.getData(); if ((token.getTextAttribute().style & TextAttribute.UNDERLINE) != 0) { int y = event.getBounds().y + event.getBounds().height - 6; int x2 = event.getBounds().width; Color oldFG = event.gc.getForeground(); Color fg; RGBa rgb = token.getTextAttribute().foreground; if (rgb == null) { fg = ThemePlugin.getDefault().getColorManager().getColor(getTheme().getForeground()); } else { fg = ThemePlugin.getDefault().getColorManager().getColor(rgb.toRGB()); } event.gc.setForeground(fg); event.gc.drawLine(0, y, x2, y); event.gc.setForeground(oldFG); event.detail &= ~SWT.FOREGROUND; } } } }); Listener selectionOverride = new Listener() { public void handleEvent(Event event) { if ((event.detail & SWT.SELECTED) != 0) { Scrollable scrollable = (Scrollable) event.widget; Rectangle clientArea = scrollable.getClientArea(); int clientWidth = clientArea.width; GC gc = event.gc; Color oldBackground = gc.getBackground(); gc.setBackground(ThemePlugin.getDefault().getColorManager() .getColor(getTheme().getSelectionAgainstBG())); gc.fillRectangle(clientArea.x, event.y, clientWidth, event.height); gc.setBackground(oldBackground); event.detail &= ~SWT.SELECTED; event.detail &= ~SWT.BACKGROUND; // force foreground color. Otherwise on dark themes we get black FG (all the time on Win, on // non-focus for Mac) gc.setForeground( ThemePlugin.getDefault().getColorManager().getColor(getTheme().getForeground())); } } }; table.addListener(SWT.EraseItem, selectionOverride); tableViewer = new TableViewer(table); tableViewer.setContentProvider(new IStructuredContentProvider() { private Theme theme; public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { this.theme = (Theme) newInput; } public void dispose() { } public Object[] getElements(Object inputElement) { return theme.getTokens().toArray(); } }); tableViewer.setLabelProvider(new TokenLabelProvider()); tableViewer.getTable().addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent e) { Table table = tableViewer.getTable(); // If user is clicking in the FG/BG column when it's empty, pop open a color dialog int fgColX = table.getColumn(0).getWidth(); // scope col width int fgColWidth = table.getColumn(1).getWidth(); // fg col width int bgColX = fgColX + fgColWidth; int bgColWidth = table.getColumn(2).getWidth() + 2; if (e.x > fgColX && e.x < (fgColX + fgColWidth)) { // user clicked in FG column ColorDialog colorDialog = new ColorDialog(table.getShell()); colorDialog.setRGB(getTheme().getForeground()); RGB newRGB = colorDialog.open(); if (newRGB == null) { return; // no color selected, don't change a thing! } TableItem tableItem = table.getItem(new Point(e.x, e.y)); ThemeRule token = (ThemeRule) tableItem.getData(); int index = table.indexOf(tableItem); getTheme().updateRule(index, token.updateFG(new RGBa(newRGB))); } else if (e.x > bgColX && e.x < (bgColX + bgColWidth)) // is user clicking in the BG column? { ColorDialog colorDialog = new ColorDialog(table.getShell()); colorDialog.setRGB(getTheme().getBackground()); RGB newRGB = colorDialog.open(); if (newRGB == null) { return; // no color selected, don't change a thing! } TableItem tableItem = table.getItem(new Point(e.x, e.y)); ThemeRule token = (ThemeRule) tableItem.getData(); int index = table.indexOf(tableItem); getTheme().updateRule(index, token.updateBG(new RGBa(newRGB))); } else { return; } tableViewer.refresh(); addCustomTableEditorControls(); } }); TableViewerColumn column = new TableViewerColumn(tableViewer, SWT.NONE); final TableColumn tokenName = column.getColumn(); tokenName.setText("Element"); //$NON-NLS-1$ layout.setColumnData(tokenName, new ColumnWeightData(100, true)); column.setLabelProvider(new ColumnLabelProvider() { public String getText(Object element) { ThemeRule token = (ThemeRule) element; return token.getName(); } public Color getForeground(Object element) { ThemeRule token = (ThemeRule) element; // TODO How do we handle alpha? RGBa fg = token.getTextAttribute().foreground; if (fg == null) return ThemePlugin.getDefault().getColorManager().getColor(getTheme().getForeground()); return ThemePlugin.getDefault().getColorManager().getColor(fg.toRGB()); } public Color getBackground(Object element) { ThemeRule token = (ThemeRule) element; // TODO How do we handle alpha? RGBa bg = token.getTextAttribute().background; if (bg == null) return ThemePlugin.getDefault().getColorManager().getColor(getTheme().getBackground()); return ThemePlugin.getDefault().getColorManager().getColor(bg.toRGB()); } public Font getFont(Object element) { ThemeRule token = (ThemeRule) element; if (token.getTextAttribute().style == 0) // TODO Limit to only checking for bold or italic return fFont; return lazyFont(fFont, token.getTextAttribute().style); } }); column.setEditingSupport(new EditingSupport(tableViewer) { private TextCellEditor cellEditor; @Override protected void setValue(Object element, Object value) { ThemeRule token = (ThemeRule) element; String newName = (String) value; if (newName.equals(token.getName())) { return; } // update the token in the theme int index = getTheme().getTokens().indexOf(token); getTheme().updateRule(index, token.setName(newName)); tableViewer.refresh(); } @Override protected Object getValue(Object element) { ThemeRule token = (ThemeRule) element; return token.getName(); } @Override protected CellEditor getCellEditor(Object element) { ThemeRule token = (ThemeRule) element; cellEditor = new TextCellEditor(table); cellEditor.setValue(token.getName()); return cellEditor; } @Override protected boolean canEdit(Object element) { return true; } }); column = new TableViewerColumn(tableViewer, SWT.NONE); final TableColumn foreground = column.getColumn(); foreground.setResizable(false); foreground.setText(Messages.ThemePreferencePage_ForegroundColumnLabel); layout.setColumnData(foreground, new ColumnPixelData(30, false)); column.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { return ""; //$NON-NLS-1$ } }); column = new TableViewerColumn(tableViewer, SWT.NONE); final TableColumn background = column.getColumn(); background.setResizable(false); background.setText(Messages.ThemePreferencePage_BackgroundColumnLabel); layout.setColumnData(background, new ColumnPixelData(30, false)); column.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { return ""; //$NON-NLS-1$ } }); final TableColumn fontStyle = new TableColumn(table, SWT.NONE); fontStyle.setResizable(false); fontStyle.setText(Messages.ThemePreferencePage_FontStyleColumnLabel); layout.setColumnData(fontStyle, new ColumnPixelData(75, false)); Composite editTokenList = new Composite(composite, SWT.NONE); editTokenList.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); GridLayout grid = new GridLayout(2, false); editTokenList.setLayout(grid); Composite buttons = new Composite(editTokenList, SWT.NONE); GridLayout buttonsLayout = new GridLayout(2, true); buttonsLayout.marginWidth = 0; buttonsLayout.horizontalSpacing = 0; buttons.setLayout(buttonsLayout); fAddTokenButton = new Button(buttons, SWT.PUSH | SWT.FLAT); fAddTokenButton.setBounds(0, 0, 16, 16); fAddTokenButton.setLayoutData(new GridData(GridData.FILL_BOTH)); fAddTokenButton.setText(Messages.ThemePreferencePage_AddTokenLabel); fAddTokenButton.addSelectionListener(this); fRemoveTokenButton = new Button(buttons, SWT.PUSH | SWT.FLAT); fRemoveTokenButton.setLayoutData(new GridData(GridData.FILL_BOTH)); fRemoveTokenButton.setText(Messages.ThemePreferencePage_RemoveTokenLabel); fRemoveTokenButton.addSelectionListener(this); Composite textField = new Composite(editTokenList, SWT.NONE); textField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); textField.setLayout(new GridLayout(2, false)); Label addTokenLabel = new Label(textField, SWT.RIGHT); addTokenLabel.setText(Messages.ThemePreferencePage_ScopeSelectoreLabel); fScopeText = new Combo(textField, SWT.SINGLE | SWT.BORDER); GridData data = new GridData(GridData.FILL_HORIZONTAL); fScopeText.setLayoutData(data); for (String preset : tokenTypeNames) { fScopeText.add(preset); } table.addSelectionListener(this); fScopeSelectorDecoration = new ControlDecoration(fScopeText, SWT.RIGHT); fScopeText.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent e) { // Update the scope selector for the current token! TableItem[] selection = table.getSelection(); if (selection == null || selection.length == 0) { return; } TableItem item = selection[0]; ThemeRule rule = (ThemeRule) item.getData(); String scopeSelectorText = fScopeText.getText(); // Validate the value isn't a duplicate! ScopeSelector selector = new ScopeSelector(scopeSelectorText); ThemeRule match = getTheme().getRuleForSelector(selector); if (scopeSelectorText.length() > 0 && match != null && match != rule) { FieldDecoration dec = FieldDecorationRegistry.getDefault() .getFieldDecoration(FieldDecorationRegistry.DEC_WARNING); fScopeSelectorDecoration.setImage(dec.getImage()); fScopeSelectorDecoration.setDescriptionText(MessageFormat .format(Messages.ThemePreferencePage_DuplicateScopeSelectorRules, match.getName())); fScopeText.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED)); } else { fScopeSelectorDecoration.setDescriptionText(null); fScopeSelectorDecoration.setImage(null); fScopeText.setForeground(null); } int index = table.indexOf(item); ThemeRule newRule = rule.setScopeSelector(selector); getTheme().updateRule(index, newRule); item.setData(newRule); } }); addDNDToTable(table); } private void addDNDToTable(final Table table) { Transfer[] types = new Transfer[] { TextTransfer.getInstance() }; DragSource source = new DragSource(table, DND.DROP_MOVE); source.setTransfer(types); source.addDragListener(new DragSourceAdapter() { @Override public void dragStart(DragSourceEvent event) { reorderingRules = true; super.dragStart(event); } @Override public void dragFinished(DragSourceEvent event) { reorderingRules = false; super.dragFinished(event); } public void dragSetData(DragSourceEvent event) { // Get the selected items in the drag source DragSource ds = (DragSource) event.widget; Table table = (Table) ds.getControl(); int selected = table.getSelectionIndex(); event.data = Integer.toString(selected); } }); // Create the drop target DropTarget target = new DropTarget(table, DND.DROP_MOVE); target.setTransfer(types); target.addDropListener(new DropTargetAdapter() { public void dragEnter(DropTargetEvent event) { // Allow dropping text only for (int i = 0, n = event.dataTypes.length; i < n; i++) { if (TextTransfer.getInstance().isSupportedType(event.dataTypes[i])) { event.currentDataType = event.dataTypes[i]; } } } public void dragOver(DropTargetEvent event) { if (reorderingRules) { event.feedback = DND.FEEDBACK_INSERT_AFTER | DND.FEEDBACK_INSERT_BEFORE | DND.FEEDBACK_SCROLL; } else { // Don't show insertion feedback if it's a button! event.feedback = DND.FEEDBACK_SCROLL; } } public void drop(DropTargetEvent event) { if (TextTransfer.getInstance().isSupportedType(event.currentDataType)) { String data = (String) event.data; // Handle drgagging of fg/bg color to remove it if (data.startsWith("button:")) //$NON-NLS-1$ { String[] parts = data.split(":"); //$NON-NLS-1$ int row = Integer.parseInt(parts[1]); int fgBg = Integer.parseInt(parts[2]); TableItem item = table.getItem(row); Rectangle bounds = item.getBounds(fgBg); bounds = event.display.map(table, null, bounds); if (bounds.contains(event.x, event.y)) { // Hasn't been dragged out of it's area, don't do anything return; } // remove the fg or bg for the rule ThemeRule rule = (ThemeRule) item.getData(); if (fgBg == 1) { getTheme().updateRule(row, rule.updateFG(null)); } else { getTheme().updateRule(row, rule.updateBG(null)); } tableViewer.refresh(true); event.display.asyncExec(new Runnable() { public void run() { addCustomTableEditorControls(); } }); return; } // It's not a button drag to remove the fg/bg, so... // Re-order rules DropTarget target = (DropTarget) event.widget; Table table = (Table) target.getControl(); int selectionIndex = Integer.parseInt(data); TableItem item = (TableItem) event.item; int insertionIndex = table.indexOf(item); getTheme().reorderRule(selectionIndex, insertionIndex); tableViewer.refresh(true); addCustomTableEditorControls(); } } }); } protected Font lazyFont(Font font, int style) { if (fFonts == null) fFonts = new HashMap<Integer, Font>(); Font returnFont = fFonts.get(style); if (returnFont == null) { returnFont = new Font(font.getDevice(), font.getFontData()[0].getName(), font.getFontData()[0].getHeight(), style); fFonts.put(style, returnFont); } return returnFont; } static class TokenLabelProvider extends BaseLabelProvider implements ITableLabelProvider { public Image getColumnImage(Object element, int columnIndex) { return null; } public String getColumnText(Object element, int columnIndex) { ThemeRule commit = (ThemeRule) element; if (commit == null) return ""; //$NON-NLS-1$ switch (columnIndex) { case 0: return commit.getName(); case 1: return commit.getTextAttribute().foreground == null ? "" //$NON-NLS-1$ : commit.getTextAttribute().foreground.toString(); case 2: return commit.getTextAttribute().background == null ? "" //$NON-NLS-1$ : commit.getTextAttribute().background.toString(); default: return ""; //$NON-NLS-1$ } } } protected void setTheme(Theme newTheme) { fSelectedTheme = newTheme; newTheme.save(); Theme theme = getTheme(); fgSelector.setColorValue(theme.getForeground()); bgSelector.setColorValue(theme.getBackground()); lineHighlightSelector.setColorValue(theme.getLineHighlight().toRGB()); caretSelector.setColorValue(theme.getCaret()); selectionSelector.setColorValue(theme.getSelection().toRGB()); fThemeCombo.setText(theme.getName()); tableViewer.setInput(theme); addCustomTableEditorControls(); if (getThemeManager().isBuiltinTheme(theme.getName())) { renameThemeButton.setEnabled(false); deleteThemeButton.setEnabled(false); } else { renameThemeButton.setEnabled(true); deleteThemeButton.setEnabled(true); } } private void addCustomTableEditorControls() { clearTableEditors(); final Table table = tableViewer.getTable(); TableItem[] items = table.getItems(); for (int i = 0; i < items.length; i++) { ThemeRule rule = (ThemeRule) items[i].getData(); if (rule.getTextAttribute().foreground != null) { createButton(table, items[i], 1, rule.getTextAttribute().foreground); } if (rule.getTextAttribute().background != null) { createButton(table, items[i], 2, rule.getTextAttribute().background); } createFontStyle(table, items[i], rule.getTextAttribute()); } } private void clearTableEditors() { if (fTableEditors == null) fTableEditors = new HashSet<TableEditor>(); for (TableEditor tableEditor : fTableEditors) { tableEditor.getEditor().dispose(); tableEditor.dispose(); } fTableEditors.clear(); } private void createFontStyle(final Table table, final TableItem item, DelayedTextAttribute text) { boolean isBold = (text.style & SWT.BOLD) != 0; boolean isItalic = (text.style & SWT.ITALIC) != 0; boolean isUnderline = (text.style & TextAttribute.UNDERLINE) != 0; TableEditor editor = new TableEditor(table); Composite buttons = new Composite(table, SWT.NONE); GridLayout grid = new GridLayout(3, true); grid.marginHeight = 0; grid.marginWidth = 0; grid.horizontalSpacing = 0; buttons.setLayout(grid); final Button b = new Button(buttons, SWT.TOGGLE | SWT.FLAT); b.setText(Messages.ThemePreferencePage_BoldButtonLabel); b.setSelection(isBold); b.setSize(16, 16); b.setLayoutData(new GridData(GridData.FILL_BOTH)); final Button italic = new Button(buttons, SWT.TOGGLE | SWT.FLAT); italic.setText(Messages.ThemePreferencePage_ItalicButtonLabel); italic.setSelection(isItalic); italic.setLayoutData(new GridData(GridData.FILL_BOTH)); italic.setSize(16, 16); final Button u = new Button(buttons, SWT.TOGGLE | SWT.FLAT); u.setText(Messages.ThemePreferencePage_UnderlineButtonLabel); u.setSelection(isUnderline); u.setLayoutData(new GridData(GridData.FILL_BOTH)); u.setSize(16, 16); buttons.pack(); editor.minimumWidth = buttons.getSize().x; editor.horizontalAlignment = SWT.LEFT; editor.setEditor(buttons, item, 3); fTableEditors.add(editor); SelectionAdapter selectionAdapter = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { ThemeRule token = (ThemeRule) item.getData(); int style = 0; if (u.getSelection()) { style |= TextAttribute.UNDERLINE; } if (b.getSelection()) { style |= SWT.BOLD; } if (italic.getSelection()) { style |= SWT.ITALIC; } int index = table.indexOf(item); getTheme().updateRule(index, token.updateFontStyle(style)); tableViewer.refresh(); } }; b.addSelectionListener(selectionAdapter); italic.addSelectionListener(selectionAdapter); u.addSelectionListener(selectionAdapter); } private void createButton(final Table table, final TableItem tableItem, final int index, final RGBa color) { TableEditor editor = new TableEditor(table); Button button = new Button(table, SWT.PUSH | SWT.FLAT); Image image = createColorImage(table, color); button.setImage(image); button.pack(); editor.minimumWidth = button.getSize().x - 4; editor.horizontalAlignment = SWT.CENTER; editor.setEditor(button, tableItem, index); fTableEditors.add(editor); button.setData("color", color); //$NON-NLS-1$ button.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { ColorDialog colorDialog = new ColorDialog(table.getShell()); Button self = ((Button) e.widget); RGBa theColor = (RGBa) self.getData("color"); //$NON-NLS-1$ if (theColor == null) { theColor = color; } colorDialog.setRGB(theColor.toRGB()); RGB newRGB = colorDialog.open(); if (newRGB == null) { return; } ThemeRule token = (ThemeRule) tableItem.getData(); RGBa newColor = new RGBa(newRGB); if (index == 1) { getTheme().updateRule(table.indexOf(tableItem), token.updateFG(newColor)); } else { getTheme().updateRule(table.indexOf(tableItem), token.updateBG(newColor)); } // Update the image for this button! self.setImage(createColorImage(table, newColor)); self.setData("color", newColor); //$NON-NLS-1$ tableViewer.refresh(); } }); // Allow dragging the button out of it's location to remove the fg/bg for the rule! Transfer[] types = new Transfer[] { TextTransfer.getInstance() }; final DragSource source = new DragSource(button, DND.DROP_MOVE); source.setTransfer(types); source.addDragListener(new DragSourceAdapter() { public void dragSetData(DragSourceEvent event) { event.data = "button:" + table.indexOf(tableItem) + ":" + index; //$NON-NLS-1$ //$NON-NLS-2$ } }); } protected Image createColorImage(final Table table, final RGBa color) { Image image = new Image(table.getDisplay(), 16, 16); GC gc = new GC(image); if (color != null) { gc.setBackground(ThemePlugin.getDefault().getColorManager().getColor(color.toRGB())); } gc.fillRectangle(0, 0, 16, 16); gc.dispose(); return image; } public void init(IWorkbench workbench) { } @Override public boolean performOk() { performOkFonts(); getTheme().save(); getThemeManager().setCurrentTheme(getTheme()); return super.performOk(); } protected void performOkFonts() { final String[] fontIds = new String[] { JFaceResources.TEXT_FONT, "org.eclipse.ui.workbench.texteditor.blockSelectionModeFont" }; //$NON-NLS-1$ FontData[] data = fFont.getFontData(); for (String fontId : fontIds) { setFont(fontId, data); } // Shrink by 2 for views! data = fFont.getFontData(); FontData[] smaller = new FontData[data.length]; int i = 0; for (FontData fd : data) { int height = fd.getHeight(); if (height >= 12) { fd.setHeight(height - 2); } else if (height >= 10) { fd.setHeight(height - 1); } smaller[i++] = fd; } setFont(IThemeManager.VIEW_FONT_NAME, smaller); } private void setFont(String fontId, FontData[] data) { String fdString = PreferenceConverter.getStoredRepresentation(data); // Only set new values if they're different from existing! Font existing = JFaceResources.getFont(fontId); String existingString = ""; //$NON-NLS-1$ if (!existing.isDisposed()) { existingString = PreferenceConverter.getStoredRepresentation(existing.getFontData()); } if (!existingString.equals(fdString)) { // put in registry... JFaceResources.getFontRegistry().put(fontId, data); // Save to prefs... ITheme currentTheme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme(); String key = ThemeElementHelper.createPreferenceKey(currentTheme, fontId); IPreferenceStore store = WorkbenchPlugin.getDefault().getPreferenceStore(); store.setValue(key, fdString); } } @Override protected void performDefaults() { // Reset the font to what it was originally! setFont(JFaceResources.getFont(JFaceResources.TEXT_FONT)); try { Theme theme = getTheme(); theme.loadFromDefaults(); setTheme(fSelectedTheme); } catch (Exception e) { IdeLog.logError(ThemePlugin.getDefault(), e); } super.performDefaults(); } protected Theme getTheme() { return fSelectedTheme; } @Override public void dispose() { if (fFonts != null) { for (Font font : fFonts.values()) { font.dispose(); } fFonts.clear(); fFonts = null; } fFont = null; super.dispose(); } public void widgetDefaultSelected(SelectionEvent e) { } public void widgetSelected(SelectionEvent e) { Object source = e.getSource(); if (source == fInvasiveThemeCheckbox) { IEclipsePreferences prefs = EclipseUtil.instanceScope().getNode(ThemePlugin.PLUGIN_ID); prefs.putBoolean(IPreferenceConstants.APPLY_TO_ALL_VIEWS, fInvasiveThemeCheckbox.getSelection()); try { prefs.flush(); } catch (BackingStoreException e1) { IdeLog.logError(ThemePlugin.getDefault(), e1); } } else if (source == fAptanaEditorsOnlyCheckbox) { IEclipsePreferences prefs = EclipseUtil.instanceScope().getNode(ThemePlugin.PLUGIN_ID); prefs.putBoolean(IPreferenceConstants.APPLY_TO_ALL_EDITORS, fAptanaEditorsOnlyCheckbox.getSelection()); try { prefs.flush(); } catch (BackingStoreException e1) { IdeLog.logError(ThemePlugin.getDefault(), e1); } } else if (source == fInvasiveFontCheckbox) { IEclipsePreferences prefs = EclipseUtil.instanceScope().getNode(ThemePlugin.PLUGIN_ID); prefs.putBoolean(IPreferenceConstants.INVASIVE_FONT, fInvasiveFontCheckbox.getSelection()); try { prefs.flush(); } catch (BackingStoreException e1) { IdeLog.logError(ThemePlugin.getDefault(), e1); } } else if (source == fThemeCombo) { setTheme(fThemeCombo.getText()); } else if (source == fAddThemeButton) { // Pop a dialog to ask for new name InputDialog dialog = new InputDialog(getShell(), Messages.ThemePreferencePage_NewThemeTitle, Messages.ThemePreferencePage_NewThemeMsg, getUniqueNewThemeName(fSelectedTheme.getName()), this); if (dialog.open() == Window.OK) { Theme newTheme = getTheme().copy(dialog.getValue()); // Add theme to theme list, make current theme this one getThemeManager().setCurrentTheme(newTheme); loadThemeNames(); setTheme(newTheme.getName()); } } else if (source == renameThemeButton) { // Pop a dialog to ask for new name InputDialog dialog = new InputDialog(getShell(), Messages.ThemePreferencePage_RenameThemeTitle, Messages.ThemePreferencePage_RenameThemeMsg, fSelectedTheme.getName(), this); if (dialog.open() == Window.OK) { Theme oldTheme = getTheme(); Theme newTheme = oldTheme.copy(dialog.getValue()); getThemeManager().setCurrentTheme(newTheme); oldTheme.delete(); loadThemeNames(); setTheme(newTheme.getName()); } } else if (source == deleteThemeButton) { boolean ok = MessageDialog.openConfirm(getShell(), MessageFormat.format(Messages.ThemePreferencePage_DeleteThemeTitle, fSelectedTheme.getName()), MessageFormat.format(Messages.ThemePreferencePage_DeleteThemeMsg, fSelectedTheme.getName())); if (ok) { getTheme().delete(); loadThemeNames(); setTheme(getThemeManager().getCurrentTheme().getName()); } } else if (source == fImportButton) { FileDialog fileDialog = new FileDialog(getShell(), SWT.OPEN); IDialogSettings editorSettings = ThemePlugin.getDefault().getDialogSettings(); String value = editorSettings.get(THEME_DIRECTORY); if (value != null) { fileDialog.setFilterPath(value); } fileDialog.setFilterExtensions(new String[] { "*.tmTheme" }); //$NON-NLS-1$ String path = fileDialog.open(); if (path == null) { return; } File themeFile = new File(path); editorSettings.put(THEME_DIRECTORY, themeFile.getParent()); Theme theme = new TextmateImporter().convert(themeFile); if (theme != null) { getThemeManager().addTheme(theme); getThemeManager().setCurrentTheme(theme); loadThemeNames(); setTheme(theme.getName()); } else { // FIXME Show an error dialog? } } else if (source == fExportButton) { FileDialog fileDialog = new FileDialog(getShell(), SWT.SAVE); IDialogSettings editorSettings = ThemePlugin.getDefault().getDialogSettings(); String value = editorSettings.get(THEME_EXPORT_DIRECTORY); if (value != null) { fileDialog.setFilterPath(value); } fileDialog.setFileName(getTheme().getName() + ".tmTheme"); //$NON-NLS-1$ String path = fileDialog.open(); if (path == null) { return; } File themeFile = new File(path); editorSettings.put(THEME_EXPORT_DIRECTORY, themeFile.getParent()); new ThemeExporter().export(themeFile, getTheme()); } else if (source == fAddTokenButton) { // Add a new row to the table by adding a basic token to the theme Theme theme = getTheme(); String newName = "untitled"; //$NON-NLS-1$ // Insert into the rules at the index after current selection in table! int index = tableViewer.getTable().getSelectionIndex(); if (index == -1) { index = tableViewer.getTable().getItemCount(); } else { index++; } theme.addNewDefaultToken(index, newName); setTheme(fSelectedTheme); // Have the new addition in an edit mode Object newElement = tableViewer.getElementAt(index); if (newElement != null) { tableViewer.editElement(newElement, 0); fScopeText.setText(""); //$NON-NLS-1$ } } else if (source == fRemoveTokenButton) { TableItem[] items = tableViewer.getTable().getSelection(); if (items == null || items.length == 0) { return; } Theme theme = getTheme(); for (TableItem tableItem : items) { ThemeRule entry = (ThemeRule) tableItem.getData(); theme.remove(entry); } theme.save(); setTheme(fSelectedTheme); } else if (source == tableViewer.getTable()) { TableItem item = (TableItem) e.item; ThemeRule token = (ThemeRule) item.getData(); fScopeText.setText(token.getScopeSelector().toString()); } } private String getUniqueNewThemeName(String themeName) { String newName = MessageFormat.format(Messages.ThemePreferencePage_NewThemeDefaultName, themeName); int index = 2; while (getThemeManager().getTheme(newName) != null) { newName = MessageFormat.format(Messages.ThemePreferencePage_NewThemeDefaultName_2, index++, themeName); } return newName; } private void setTheme(String text) { setTheme(getThemeManager().getTheme(text)); } public String isValid(String newText) { IStatus status = getThemeManager().validateThemeName(newText); if (status.isOK()) { return null; } return status.getMessage(); } public void propertyChange(PropertyChangeEvent event) { Object value = event.getNewValue(); if (value == null) { return; } RGB newColor = (RGB) value; Theme theme = getTheme(); Object source = event.getSource(); if (source == fgSelector) { theme.updateFG(newColor); } else if (source == selectionSelector) { theme.updateSelection(newColor); } else if (source == bgSelector) { theme.updateBG(newColor); } else if (source == lineHighlightSelector) { theme.updateLineHighlight(newColor); } else if (source == caretSelector) { theme.updateCaret(newColor); } setTheme(fSelectedTheme); } protected void setFont(Font font) { if (fFont.equals(font)) // TODO Also same if FontData arrays are equal! { return; } fFont = font; fFontText.setFont(fFont); fFontText.setText(ThemePreferencePage.toString(fFont)); // Set the fFont on the table! if (fFonts != null) { fFonts.clear(); } tableViewer.refresh(); } }