/*******************************************************************************
 * Copyright (c) 2007 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.ui.internal.editors.text;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.StringTokenizer;

import com.ibm.icu.text.Collator;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ColumnLayoutData;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.Viewer;

import org.eclipse.ui.editors.text.EditorsUI;

import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.HyperlinkDetectorDescriptor;
import org.eclipse.ui.texteditor.HyperlinkDetectorTargetDescriptor;


/**
 * Configures hyperlink detector preferences.
 *
 * @since 3.3
 */
class HyperlinkDetectorsConfigurationBlock implements IPreferenceConfigurationBlock {
	
	
	private static final class ListItem {
		final String id;
		final String name;
		final String description;
		final String targetId;
		final String targetName;
		final String targetDescription;
		String stateMask;
		String modifierKeys= ""; //$NON-NLS-1$

		ListItem(String id, String name, String description, String targetId, String targetName, String targetDescription, String modifierKeys) {
			this.id= id;
			this.name= name;
			this.description= description;
			this.targetId= targetId;
			this.targetName= targetName;
			this.targetDescription= targetDescription;
			this.modifierKeys= modifierKeys;
		}
	}

	
	private static final class ItemContentProvider implements IStructuredContentProvider {

		public Object[] getElements(Object inputElement) {
			return (ListItem[]) inputElement;
		}

		public void dispose() {
		}

		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		}
	}
	

	private final class ItemLabelProvider implements ITableLabelProvider {
		
		public Image getImage(Object element) {
			return null;
		}

		/*
		 * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnImage(java.lang.Object, int)
		 */
		public Image getColumnImage(Object element, int columnIndex) {
			return null;
		}

		/*
		 * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnText(java.lang.Object, int)
		 */
		public String getColumnText(Object element, int columnIndex) {
			switch (columnIndex) {
			case 0:
				return ((ListItem) element).name;
			case 1:
				String text= ((ListItem) element).modifierKeys;
				if (text == null)
					return fHyperlinkDefaultKeyModifierText.getText();
				return text;
			case 2:
				return ((ListItem) element).targetName;
			default:
				Assert.isLegal(false);
			}
			return null; // cannot happen
		}

		/*
		 * @see org.eclipse.jface.viewers.IBaseLabelProvider#addListener(org.eclipse.jface.viewers.ILabelProviderListener)
		 */
		public void addListener(ILabelProviderListener listener) {
		}

		/*
		 * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
		 */
		public void dispose() {
		}

		/*
		 * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object, java.lang.String)
		 */
		public boolean isLabelProperty(Object element, String property) {
			return false;
		}

		/*
		 * @see org.eclipse.jface.viewers.IBaseLabelProvider#removeListener(org.eclipse.jface.viewers.ILabelProviderListener)
		 */
		public void removeListener(ILabelProviderListener listener) {
		}
	}

	
	private static final String MODIFIER_DELIMITER= TextEditorMessages.HyperlinkKeyModifier_delimiter;


	private OverlayPreferenceStore fStore;

	private CheckboxTableViewer fHyperlinkDetectorsViewer;
	private ListItem[] fListModel;
	private final HyperlinkDetectorDescriptor[] fHyperlinkDetectorDescriptors;
	
	private Text fHyperlinkDefaultKeyModifierText;
	private Text fHyperlinkKeyModifierText;
	private Button fHyperlinksEnabledCheckBox;
	private StatusInfo fHyperlinkKeyModifierStatus;

	private PreferencePage fPreferencePage;



	public HyperlinkDetectorsConfigurationBlock(PreferencePage preferencePage, OverlayPreferenceStore store) {
		Assert.isNotNull(store);
		Assert.isNotNull(preferencePage);
		fStore= store;
		fPreferencePage= preferencePage;
		fHyperlinkDetectorDescriptors= EditorsUI.getHyperlinkDetectorRegistry().getHyperlinkDetectorDescriptors();
		fStore.addKeys(createOverlayStoreKeys());
	}

	private OverlayPreferenceStore.OverlayKey[] createOverlayStoreKeys() {
		ArrayList overlayKeys= new ArrayList();
		
		for (int i= 0; i < fHyperlinkDetectorDescriptors.length; i++) {
			overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, fHyperlinkDetectorDescriptors[i].getId()));
			overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.INT, fHyperlinkDetectorDescriptors[i].getId() + HyperlinkDetectorDescriptor.STATE_MASK_POSTFIX));
		}
		
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINK_KEY_MODIFIER));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINKS_ENABLED));

		OverlayPreferenceStore.OverlayKey[] keys= new OverlayPreferenceStore.OverlayKey[overlayKeys.size()];
		overlayKeys.toArray(keys);
		return keys;
	}

	/*
	 * @see org.eclipse.ui.internal.editors.text.IPreferenceConfigurationBlock#createControl(org.eclipse.swt.widgets.Composite)
	 */
	public Control createControl(Composite parent) {

		PixelConverter pixelConverter= new PixelConverter(parent);

		Composite composite= new Composite(parent, SWT.NONE);
		GridLayout layout= new GridLayout();
		layout.numColumns= 2;
		layout.marginHeight= 0;
		layout.marginWidth= 0;
		composite.setLayout(layout);
		
		addFiller(composite, 2);

		String label= TextEditorMessages.HyperlinksEnabled_label;
		fHyperlinksEnabledCheckBox= addCheckBox(composite, label, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINKS_ENABLED, 0);
		fHyperlinksEnabledCheckBox.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				boolean state= fHyperlinksEnabledCheckBox.getSelection();
				fHyperlinkDefaultKeyModifierText.setEnabled(state);
				fHyperlinkKeyModifierText.setEnabled(state && getSelectedItem() != null);
				fHyperlinkDetectorsViewer.getTable().setEnabled(state);
				handleHyperlinkKeyModifierModified();
			}
			public void widgetDefaultSelected(SelectionEvent e) {
			}
		});

		// Text field for default modifier string
		label= TextEditorMessages.HyperlinkDefaultKeyModifier_label;
		fHyperlinkDefaultKeyModifierText= (Text)addTextField(composite, label, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINK_KEY_MODIFIER, 15, 20, pixelConverter)[1];

		fHyperlinkDefaultKeyModifierText.addKeyListener(new KeyListener() {
			private boolean isModifierCandidate;
			public void keyPressed(KeyEvent e) {
				isModifierCandidate= e.keyCode > 0 && e.character == 0 && e.stateMask == 0;
			}

			public void keyReleased(KeyEvent e) {
				if (isModifierCandidate && e.stateMask > 0 && e.stateMask == e.stateMask && e.character == 0) {// && e.time -time < 1000) {
					String modifierString= fHyperlinkDefaultKeyModifierText.getText();
					Point selection= fHyperlinkDefaultKeyModifierText.getSelection();
					int i= selection.x - 1;
					while (i > -1 && Character.isWhitespace(modifierString.charAt(i))) {
						i--;
					}
					boolean needsPrefixDelimiter= i > -1 && !String.valueOf(modifierString.charAt(i)).equals(MODIFIER_DELIMITER);

					i= selection.y;
					while (i < modifierString.length() && Character.isWhitespace(modifierString.charAt(i))) {
						i++;
					}
					boolean needsPostfixDelimiter= i < modifierString.length() && !String.valueOf(modifierString.charAt(i)).equals(MODIFIER_DELIMITER);

					String insertString;

					if (needsPrefixDelimiter && needsPostfixDelimiter)
						insertString= NLSUtility.format(TextEditorMessages.HyperlinkKeyModifier_insertDelimiterAndModifierAndDelimiter, Action.findModifierString(e.stateMask));
					else if (needsPrefixDelimiter)
						insertString= NLSUtility.format(TextEditorMessages.HyperlinkKeyModifier_insertDelimiterAndModifier, Action.findModifierString(e.stateMask));
					else if (needsPostfixDelimiter)
						insertString= NLSUtility.format(TextEditorMessages.HyperlinkKeyModifier_insertModifierAndDelimiter, Action.findModifierString(e.stateMask));
					else
						insertString= Action.findModifierString(e.stateMask);

					fHyperlinkDefaultKeyModifierText.insert(insertString);
				}
			}
		});

		fHyperlinkDefaultKeyModifierText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				handleHyperlinkDefaultKeyModifierModified();
			}
		});
		
		addFiller(composite, 2);

		Composite editorComposite= new Composite(composite, SWT.NONE);
		GridData gd;
		
		// XXX: workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=178097
		if ("gtk".equals(SWT.getPlatform())) //$NON-NLS-1$
				gd= new GridData(SWT.FILL, SWT.FILL, true, true);
			else
				gd= new GridData(SWT.FILL, SWT.TOP, true, false);
		
		gd.horizontalSpan= 2;
		gd.horizontalIndent= 20;
		editorComposite.setLayoutData(gd);
		TableColumnLayout tableColumnlayout= new TableColumnLayout();
		editorComposite.setLayout(tableColumnlayout);
		
		// Hyperlink detector table
		Table hyperlinkDetectorTable= new Table(editorComposite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.SINGLE | SWT.BORDER | SWT.FULL_SELECTION | SWT.CHECK);
		hyperlinkDetectorTable.setHeaderVisible(true);
		hyperlinkDetectorTable.setLinesVisible(true);
		hyperlinkDetectorTable.setFont(parent.getFont());
		
		hyperlinkDetectorTable.addSelectionListener(new SelectionListener() {
			public void widgetSelected(SelectionEvent e) {
				handleListSelection();
			}
			public void widgetDefaultSelected(SelectionEvent e) {
			}
		});
		
		ColumnLayoutData columnLayoutData= new ColumnWeightData(1);
		
		TableColumn nameColumn= new TableColumn(hyperlinkDetectorTable, SWT.NONE, 0);
		nameColumn.setText(TextEditorMessages.HyperlinkDetectorTable_nameColumn); 
		tableColumnlayout.setColumnData(nameColumn, columnLayoutData);
		
		TableColumn modifierKeysColumn= new TableColumn(hyperlinkDetectorTable, SWT.NONE, 1);
		modifierKeysColumn.setText(TextEditorMessages.HyperlinkDetectorTable_modifierKeysColumn);
		tableColumnlayout.setColumnData(modifierKeysColumn, columnLayoutData);
		
		TableColumn targetNameColumn= new TableColumn(hyperlinkDetectorTable, SWT.NONE, 2);
		targetNameColumn.setText(TextEditorMessages.HyperlinkDetectorTable_targetNameColumn);
		tableColumnlayout.setColumnData(targetNameColumn, columnLayoutData);

		fHyperlinkDetectorsViewer= new CheckboxTableViewer(hyperlinkDetectorTable);
		fHyperlinkDetectorsViewer.setUseHashlookup(true);
		
		fHyperlinkDetectorsViewer.addCheckStateListener(new ICheckStateListener() {
			/*
			 * @see org.eclipse.jface.viewers.ICheckStateListener#checkStateChanged(org.eclipse.jface.viewers.CheckStateChangedEvent)
			 */
			public void checkStateChanged(CheckStateChangedEvent event) {
				String id= ((ListItem)event.getElement()).id;
				if (id == null)
					return;
				fStore.setValue(id, !event.getChecked());
			}
		});
		
		fHyperlinkDetectorsViewer.setLabelProvider(new ItemLabelProvider());
		fHyperlinkDetectorsViewer.setContentProvider(new ItemContentProvider());
		gd= new GridData(SWT.FILL, SWT.FILL, true , false);
		gd.heightHint= pixelConverter.convertHeightInCharsToPixels(10);
		fHyperlinkDetectorsViewer.getControl().setLayoutData(gd);
		
//		addFiller(composite, 2);
		
		// Text field for modifier string
		label= TextEditorMessages.HyperlinkKeyModifier_label;
		fHyperlinkKeyModifierText= (Text)addTextField(composite, label, null, 15, 20, pixelConverter)[1];

		fHyperlinkKeyModifierText.addKeyListener(new KeyListener() {
			private boolean isModifierCandidate;
			public void keyPressed(KeyEvent e) {
				isModifierCandidate= e.keyCode > 0 && e.character == 0 && e.stateMask == 0;
			}

			public void keyReleased(KeyEvent e) {
				if (isModifierCandidate && e.stateMask > 0 && e.stateMask == e.stateMask && e.character == 0) {// && e.time -time < 1000) {
					String modifierString= fHyperlinkKeyModifierText.getText();
					Point selection= fHyperlinkKeyModifierText.getSelection();
					int i= selection.x - 1;
					while (i > -1 && Character.isWhitespace(modifierString.charAt(i))) {
						i--;
					}
					boolean needsPrefixDelimiter= i > -1 && !String.valueOf(modifierString.charAt(i)).equals(MODIFIER_DELIMITER);

					i= selection.y;
					while (i < modifierString.length() && Character.isWhitespace(modifierString.charAt(i))) {
						i++;
					}
					boolean needsPostfixDelimiter= i < modifierString.length() && !String.valueOf(modifierString.charAt(i)).equals(MODIFIER_DELIMITER);

					String insertString;

					if (needsPrefixDelimiter && needsPostfixDelimiter)
						insertString= NLSUtility.format(TextEditorMessages.HyperlinkKeyModifier_insertDelimiterAndModifierAndDelimiter, Action.findModifierString(e.stateMask));
					else if (needsPrefixDelimiter)
						insertString= NLSUtility.format(TextEditorMessages.HyperlinkKeyModifier_insertDelimiterAndModifier, Action.findModifierString(e.stateMask));
					else if (needsPostfixDelimiter)
						insertString= NLSUtility.format(TextEditorMessages.HyperlinkKeyModifier_insertModifierAndDelimiter, Action.findModifierString(e.stateMask));
					else
						insertString= Action.findModifierString(e.stateMask);

					fHyperlinkKeyModifierText.insert(insertString);
				}
			}
		});

		fHyperlinkKeyModifierText.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				handleHyperlinkKeyModifierModified();
			}
		});
		
		return composite;
	}
	
	private void addFiller(Composite composite, int horizontalSpan) {
		PixelConverter pixelConverter= new PixelConverter(composite);
		Label filler= new Label(composite, SWT.LEFT );
		GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		gd.horizontalSpan= horizontalSpan;
		gd.heightHint= pixelConverter.convertHeightInCharsToPixels(1) / 2;
		filler.setLayoutData(gd);
	}

	private Button addCheckBox(Composite composite, String label, final String key, int indentation) {
		final Button checkBox= new Button(composite, SWT.CHECK);
		checkBox.setText(label);

		GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
		gd.horizontalIndent= indentation;
		gd.horizontalSpan= 2;
		checkBox.setLayoutData(gd);
		checkBox.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				boolean value= checkBox.getSelection();
				fStore.setValue(key, value);
			}
		});

		return checkBox;
	}

	private Control[] addTextField(Composite composite, String label, final String key, int textLimit, int indentation, PixelConverter pixelConverter) {
		Label labelControl= new Label(composite, SWT.NONE);
		labelControl.setText(label);
		GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
		gd.horizontalIndent= indentation;
		labelControl.setLayoutData(gd);

		final Text textControl= new Text(composite, SWT.BORDER | SWT.SINGLE);
		gd= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
		gd.widthHint= pixelConverter.convertWidthInCharsToPixels(textLimit + 1);
		textControl.setLayoutData(gd);
		textControl.setTextLimit(textLimit);

		textControl.addModifyListener(new ModifyListener() {
			public void modifyText(ModifyEvent e) {
				String value= textControl.getText();
				if (key != null)
					fStore.setValue(key, value);
			}
		});

		return new Control[] {labelControl, textControl};
	}

	private Object[] getCheckedItems() {
		List result= new ArrayList();
		for (int i= 0; i < fListModel.length; i++)
			if (!fStore.getBoolean(fListModel[i].id))
				result.add(fListModel[i]);
		return result.toArray();
	}

	/*
	 * @see org.eclipse.ui.internal.editors.text.IPreferenceConfigurationBlock#canPerformOk()
	 */
	public boolean canPerformOk() {
		return true;
	}

	/*
	 * @see PreferencePage#performOk()
	 */
	public void performOk() {
		fStore.setValue(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINK_KEY_MODIFIER_MASK, computeStateMask(fHyperlinkKeyModifierText.getText()));
	}

	/*
	 * @see PreferencePage#performDefaults()
	 */
	public void performDefaults() {
		fStore.loadDefaults();
		initialize();
	}

	private void handleListSelection() {
		ListItem item= getSelectedItem();
		if (item == null) {
			fHyperlinkKeyModifierText.setEnabled(false);
			return;
		}
		fHyperlinkKeyModifierText.setEnabled(fHyperlinkDetectorsViewer.getChecked(item));
		String text= item.modifierKeys;
		if (text == null)
			text= fHyperlinkDefaultKeyModifierText.getText();
		fHyperlinkKeyModifierText.setText(text);
		
	}

	public void initialize() {
		String modifierString= fStore.getString(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINK_KEY_MODIFIER);
		if (computeStateMask(modifierString) == -1) {
			// Fix possible illegal modifier string
			int stateMask= fStore.getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINK_KEY_MODIFIER_MASK);
			if (stateMask == -1)
				fHyperlinkDefaultKeyModifierText.setText(""); //$NON-NLS-1$
			else
				fHyperlinkDefaultKeyModifierText.setText(getModifierString(stateMask));
		} else
			fHyperlinkDefaultKeyModifierText.setText(modifierString);
		boolean isEnabled= fStore.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINKS_ENABLED);
		fHyperlinksEnabledCheckBox.setSelection(isEnabled);
		fHyperlinkKeyModifierText.setEnabled(isEnabled);
		fHyperlinkDefaultKeyModifierText.setEnabled(isEnabled);
		
		fListModel= createListModel();
		fHyperlinkDetectorsViewer.setInput(fListModel);
		fHyperlinkDetectorsViewer.setAllChecked(false);
		fHyperlinkDetectorsViewer.setCheckedElements(getCheckedItems());
		fHyperlinkDetectorsViewer.getTable().setEnabled(isEnabled);

		fHyperlinkKeyModifierText.setText(""); //$NON-NLS-1$

		handleListSelection();
	}

	private ListItem[] createListModel() {
		ArrayList listModelItems= new ArrayList();
		for (int i= 0; i < fHyperlinkDetectorDescriptors.length; i++) {
			HyperlinkDetectorDescriptor desc= fHyperlinkDetectorDescriptors[i];
			HyperlinkDetectorTargetDescriptor target= desc.getTarget();
			
			int stateMask= fStore.getInt(desc.getId() + HyperlinkDetectorDescriptor.STATE_MASK_POSTFIX);
			String modifierKeys= getModifierString(stateMask);
			
			listModelItems.add(new ListItem(
					desc.getId(), desc.getName(), desc.getDescription(), desc.getTargetId(), target.getName(), target.getDescription(), modifierKeys));
		}

		Comparator comparator= new Comparator() {
			/*
			 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
			 */
			public int compare(Object o1, Object o2) {
				if (!(o2 instanceof ListItem))
					return -1;
				if (!(o1 instanceof ListItem))
					return 1;

				String label1= ((ListItem)o1).name;
				String label2= ((ListItem)o2).name;

				return Collator.getInstance().compare(label1, label2);

			}
		};
		Collections.sort(listModelItems, comparator);

		ListItem[] items= new ListItem[listModelItems.size()];
		listModelItems.toArray(items);
		return items;
	}

	private ListItem getSelectedItem() {
		return (ListItem)((IStructuredSelection) fHyperlinkDetectorsViewer.getSelection()).getFirstElement();
	}

	private void handleHyperlinkKeyModifierModified() {
		String modifiers= fHyperlinkKeyModifierText.getText();
		
		int stateMask= computeStateMask(modifiers);

		if (fHyperlinksEnabledCheckBox.getSelection() && (stateMask == -1 || (stateMask & SWT.SHIFT) != 0)) {
			if (stateMask == -1)
				fHyperlinkKeyModifierStatus= new StatusInfo(IStatus.ERROR, NLSUtility.format(TextEditorMessages.HyperlinkKeyModifier_error_modifierIsNotValid, modifiers));
			else
				fHyperlinkKeyModifierStatus= new StatusInfo(IStatus.ERROR, TextEditorMessages.HyperlinkKeyModifier_error_shiftIsDisabled);
			applyToStatusLine(getHyperlinkKeyModifierStatus());
			fPreferencePage.setValid(getHyperlinkKeyModifierStatus().isOK());
		} else {
			ListItem item= getSelectedItem();
			if (item != null) {
				if (item.modifierKeys != null || !modifiers.equalsIgnoreCase(fHyperlinkDefaultKeyModifierText.getText()))
					item.modifierKeys= modifiers;
				fHyperlinkDetectorsViewer.refresh(getSelectedItem());
				fStore.setValue(item.id + HyperlinkDetectorDescriptor.STATE_MASK_POSTFIX, stateMask);
			}
			fHyperlinkKeyModifierStatus= new StatusInfo();
			fPreferencePage.setValid(true);
			applyToStatusLine(fHyperlinkKeyModifierStatus);
		}
	}
	
	private void handleHyperlinkDefaultKeyModifierModified() {
		String modifiers= fHyperlinkDefaultKeyModifierText.getText();
		
		int stateMask= computeStateMask(modifiers);
		
		if (fHyperlinksEnabledCheckBox.getSelection() && (stateMask == -1 || (stateMask & SWT.SHIFT) != 0)) {
			if (stateMask == -1)
				fHyperlinkKeyModifierStatus= new StatusInfo(IStatus.ERROR, NLSUtility.format(TextEditorMessages.HyperlinkKeyModifier_error_modifierIsNotValid, modifiers));
			else
				fHyperlinkKeyModifierStatus= new StatusInfo(IStatus.ERROR, TextEditorMessages.HyperlinkKeyModifier_error_shiftIsDisabled);
			applyToStatusLine(getHyperlinkKeyModifierStatus());
			fPreferencePage.setValid(getHyperlinkKeyModifierStatus().isOK());
		} else {
			fStore.setValue(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINK_KEY_MODIFIER, modifiers);
			fHyperlinkKeyModifierStatus= new StatusInfo();
			fPreferencePage.setValid(true);
			applyToStatusLine(fHyperlinkKeyModifierStatus);
			fHyperlinkDetectorsViewer.refresh();
			handleListSelection();
		}
	}

	private IStatus getHyperlinkKeyModifierStatus() {
		if (fHyperlinkKeyModifierStatus == null)
		fHyperlinkKeyModifierStatus= new StatusInfo();
		return fHyperlinkKeyModifierStatus;
	}

	/**
	 * Computes the state mask for the given modifier string.
	 *
	 * @param modifiers	the string with the modifiers, separated by '+', '-', ';', ',' or '.'
	 * @return the state mask or -1 if the input is invalid
	 */
	private static final int computeStateMask(String modifiers) {
		if (modifiers == null)
			return -1;

		if (modifiers.length() == 0)
			return SWT.NONE;

		int stateMask= 0;
		StringTokenizer modifierTokenizer= new StringTokenizer(modifiers, ",;.:+-* "); //$NON-NLS-1$
		while (modifierTokenizer.hasMoreTokens()) {
			int modifier= findLocalizedModifier(modifierTokenizer.nextToken());
			if (modifier == 0 || (stateMask & modifier) == modifier)
				return -1;
			stateMask= stateMask | modifier;
		}
		return stateMask;
	}

	/**
	 * Maps the localized modifier name to a code in the same
	 * manner as #findModifier.
	 *
	 * @param modifierName the modifier name
	 * @return the SWT modifier bit, or <code>0</code> if no match was found
	 */
	private static final int findLocalizedModifier(String modifierName) {
		if (modifierName == null)
			return 0;

		if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.CTRL)))
			return SWT.CTRL;
		if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.SHIFT)))
			return SWT.SHIFT;
		if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.ALT)))
			return SWT.ALT;
		if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.COMMAND)))
			return SWT.COMMAND;

		return 0;
	}

	/**
	 * Returns the modifier string for the given SWT modifier
	 * modifier bits.
	 *
	 * @param stateMask	the SWT modifier bits
	 * @return the modifier string
	 */
	private static final String getModifierString(int stateMask) {
		if (stateMask == -1)
			return null;
		
		String modifierString= ""; //$NON-NLS-1$
		if ((stateMask & SWT.CTRL) == SWT.CTRL)
			modifierString= appendModifierString(modifierString, SWT.CTRL);
		if ((stateMask & SWT.ALT) == SWT.ALT)
			modifierString= appendModifierString(modifierString, SWT.ALT);
		if ((stateMask & SWT.SHIFT) == SWT.SHIFT)
			modifierString= appendModifierString(modifierString, SWT.SHIFT);
		if ((stateMask & SWT.COMMAND) == SWT.COMMAND)
			modifierString= appendModifierString(modifierString,  SWT.COMMAND);

		return modifierString;
	}

	/**
	 * Appends to modifier string of the given SWT modifier bit
	 * to the given modifierString.
	 *
	 * @param modifierString	the modifier string
	 * @param modifier			an int with SWT modifier bit
	 * @return the concatenated modifier string
	 */
	private static final String appendModifierString(String modifierString, int modifier) {
		if (modifierString == null)
			modifierString= ""; //$NON-NLS-1$
		String newModifierString= Action.findModifierString(modifier);
		if (modifierString.length() == 0)
			return newModifierString;
		return NLSUtility.format(TextEditorMessages.HyperlinkKeyModifier_concatModifierStrings, new String[] {modifierString, newModifierString});
	}

	/**
	 * Applies the status to the status line of a dialog page.
	 *
	 * @param status the status
	 */
	private void applyToStatusLine(IStatus status) {
		String message= status.getMessage();
		switch (status.getSeverity()) {
			case IStatus.OK:
				fPreferencePage.setMessage(message, IMessageProvider.NONE);
				fPreferencePage.setErrorMessage(null);
				break;
			case IStatus.WARNING:
				fPreferencePage.setMessage(message, IMessageProvider.WARNING);
				fPreferencePage.setErrorMessage(null);
				break;
			case IStatus.INFO:
				fPreferencePage.setMessage(message, IMessageProvider.INFORMATION);
				fPreferencePage.setErrorMessage(null);
				break;
			default:
				if (message.length() == 0) {
					message= null;
				}
				fPreferencePage.setMessage(null);
				fPreferencePage.setErrorMessage(message);
				break;
		}
	}

	/*
	 * @see org.eclipse.ui.internal.editors.text.IPreferenceConfigurationBlock#dispose()
	 */
	public void dispose() {
	}

}
