FileDocCategorySizeDatePackage
ConfigurationSelector.javaAPI DocAndroid 1.5 API50257Wed May 06 22:41:10 BST 2009com.android.ide.eclipse.adt.ui

ConfigurationSelector.java

/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.eclipse.org/org/documents/epl-v10.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.adt.ui;

import com.android.ide.eclipse.editors.resources.configurations.CountryCodeQualifier;
import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration;
import com.android.ide.eclipse.editors.resources.configurations.KeyboardStateQualifier;
import com.android.ide.eclipse.editors.resources.configurations.LanguageQualifier;
import com.android.ide.eclipse.editors.resources.configurations.NavigationMethodQualifier;
import com.android.ide.eclipse.editors.resources.configurations.NetworkCodeQualifier;
import com.android.ide.eclipse.editors.resources.configurations.PixelDensityQualifier;
import com.android.ide.eclipse.editors.resources.configurations.RegionQualifier;
import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier;
import com.android.ide.eclipse.editors.resources.configurations.ScreenDimensionQualifier;
import com.android.ide.eclipse.editors.resources.configurations.ScreenOrientationQualifier;
import com.android.ide.eclipse.editors.resources.configurations.TextInputMethodQualifier;
import com.android.ide.eclipse.editors.resources.configurations.TouchScreenQualifier;
import com.android.ide.eclipse.editors.resources.configurations.KeyboardStateQualifier.KeyboardState;
import com.android.ide.eclipse.editors.resources.configurations.NavigationMethodQualifier.NavigationMethod;
import com.android.ide.eclipse.editors.resources.configurations.ScreenOrientationQualifier.ScreenOrientation;
import com.android.ide.eclipse.editors.resources.configurations.TextInputMethodQualifier.TextInputMethod;
import com.android.ide.eclipse.editors.resources.configurations.TouchScreenQualifier.TouchScreenType;
import com.android.ide.eclipse.editors.resources.manager.ResourceManager;

import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
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.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.graphics.Image;
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.Combo;
import org.eclipse.swt.widgets.Composite;
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 java.util.HashMap;

/**
 * Custom UI widget to let user build a Folder configuration.
 * <p/>
 * To use this, instantiate somewhere in the UI and then:
 * <ul>
 * <li>Use {@link #setConfiguration(String)} or {@link #setConfiguration(FolderConfiguration)}.
 * <li>Retrieve the configuration using {@link #getConfiguration(FolderConfiguration)}.
 * </ul> 
 */
public class ConfigurationSelector extends Composite {
    
    public static final int WIDTH_HINT = 600;
    public static final int HEIGHT_HINT = 250;
    
    private Runnable mOnChangeListener;

    private TableViewer mFullTableViewer;
    private TableViewer mSelectionTableViewer;
    private Button mAddButton;
    private Button mRemoveButton;
    private StackLayout mStackLayout;
    
    private boolean mOnRefresh = false;

    private final FolderConfiguration mBaseConfiguration = new FolderConfiguration();
    private final FolderConfiguration mSelectedConfiguration = new FolderConfiguration();
    
    private final HashMap<Class<? extends ResourceQualifier>, QualifierEditBase> mUiMap =
        new HashMap<Class<? extends ResourceQualifier>, QualifierEditBase>();
    private Composite mQualifierEditParent;
    
    /**
     * Basic of {@link VerifyListener} to only accept digits.
     */
    private static class DigitVerifier implements VerifyListener {
        public void verifyText(VerifyEvent e) {
            // check for digit only.
            for (int i = 0 ; i < e.text.length(); i++) {
                char letter = e.text.charAt(i);
                if (letter < '0' || letter > '9') {
                    e.doit = false;
                    return;
                }
            }
        }
    }
    
    /**
     * Implementation of {@link VerifyListener} for Country Code qualifiers.
     */
    public static class MobileCodeVerifier extends DigitVerifier {
        @Override
        public void verifyText(VerifyEvent e) {
            super.verifyText(e);

            // basic tests passed?
            if (e.doit) {
                // check the max 3 digits.
                if (e.text.length() - e.end + e.start +
                        ((Text)e.getSource()).getText().length() > 3) {
                    e.doit = false;
                }
            }
        }
    }
    
    /**
     * Implementation of {@link VerifyListener} for the Language and Region qualifiers.
     */
    public static class LanguageRegionVerifier implements VerifyListener {
        public void verifyText(VerifyEvent e) {
            // check for length
            if (e.text.length() - e.end + e.start + ((Combo)e.getSource()).getText().length() > 2) {
                e.doit = false;
                return;
            }
            
            // check for lower case only.
            for (int i = 0 ; i < e.text.length(); i++) {
                char letter = e.text.charAt(i);
                if ((letter < 'a' || letter > 'z') && (letter < 'A' || letter > 'Z')) {
                    e.doit = false;
                    return;
                }
            }
        }
    }
    
    /**
     * Implementation of {@link VerifyListener} for the Pixel Density qualifier.
     */
    public static class DensityVerifier extends DigitVerifier { }
    
    /**
     * Implementation of {@link VerifyListener} for the Screen Dimension qualifier.
     */
    public static class DimensionVerifier extends DigitVerifier { }
    
    /**
     * Enum for the state of the configuration being created.
     */
    public enum ConfigurationState {
        OK, INVALID_CONFIG, REGION_WITHOUT_LANGUAGE;
    }

    public ConfigurationSelector(Composite parent) {
        super(parent, SWT.NONE);

        mBaseConfiguration.createDefault();

        GridLayout gl = new GridLayout(4, false);
        gl.marginWidth = gl.marginHeight = 0;
        setLayout(gl);
        
        // first column is the first table
        final Table fullTable = new Table(this, SWT.SINGLE | SWT.FULL_SELECTION | SWT.BORDER);
        fullTable.setLayoutData(new GridData(GridData.FILL_BOTH));
        fullTable.setHeaderVisible(true);
        fullTable.setLinesVisible(true);
        
        // create the column
        final TableColumn fullTableColumn = new TableColumn(fullTable, SWT.LEFT);
        // set the header
        fullTableColumn.setText("Available Qualifiers");
        
        fullTable.addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent e) {
                Rectangle r = fullTable.getClientArea();
                fullTableColumn.setWidth(r.width);
            }
        });

        mFullTableViewer = new TableViewer(fullTable);
        mFullTableViewer.setContentProvider(new QualifierContentProvider());
        mFullTableViewer.setLabelProvider(new QualifierLabelProvider(
                false /* showQualifierValue */));
        mFullTableViewer.setInput(mBaseConfiguration);
        mFullTableViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                ISelection selection = event.getSelection();
                if (selection instanceof IStructuredSelection) {
                    IStructuredSelection structSelection = (IStructuredSelection)selection;
                    Object first = structSelection.getFirstElement();
                    
                    if (first instanceof ResourceQualifier) {
                        mAddButton.setEnabled(true);
                        return;
                    }
                }
                
                mAddButton.setEnabled(false);
            }
        });
        
        // 2nd column is the left/right arrow button
        Composite buttonComposite = new Composite(this, SWT.NONE);
        gl = new GridLayout(1, false);
        gl.marginWidth = gl.marginHeight = 0;
        buttonComposite.setLayout(gl);
        buttonComposite.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        
        new Composite(buttonComposite, SWT.NONE);
        mAddButton = new Button(buttonComposite, SWT.BORDER | SWT.PUSH);
        mAddButton.setText("->");
        mAddButton.setEnabled(false);
        mAddButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                IStructuredSelection selection = 
                    (IStructuredSelection)mFullTableViewer.getSelection();
                
                Object first = selection.getFirstElement();
                if (first instanceof ResourceQualifier) {
                    ResourceQualifier qualifier = (ResourceQualifier)first;
                    
                    mBaseConfiguration.removeQualifier(qualifier);
                    mSelectedConfiguration.addQualifier(qualifier);
                    
                    mFullTableViewer.refresh();
                    mSelectionTableViewer.refresh();
                    mSelectionTableViewer.setSelection(new StructuredSelection(qualifier), true);
                    
                    onChange(false /* keepSelection */);
                }
            }
        });

        mRemoveButton = new Button(buttonComposite, SWT.BORDER | SWT.PUSH);
        mRemoveButton.setText("<-");
        mRemoveButton.setEnabled(false);
        mRemoveButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                IStructuredSelection selection = 
                    (IStructuredSelection)mSelectionTableViewer.getSelection();
                
                Object first = selection.getFirstElement();
                if (first instanceof ResourceQualifier) {
                    ResourceQualifier qualifier = (ResourceQualifier)first;
                    
                    mSelectedConfiguration.removeQualifier(qualifier);
                    mBaseConfiguration.addQualifier(qualifier);

                    mFullTableViewer.refresh();
                    mSelectionTableViewer.refresh();
                    
                    onChange(false /* keepSelection */);
                }
            }
        });

        // 3rd column is the selected config table
        final Table selectionTable = new Table(this, SWT.SINGLE | SWT.FULL_SELECTION | SWT.BORDER);
        selectionTable.setLayoutData(new GridData(GridData.FILL_BOTH));
        selectionTable.setHeaderVisible(true);
        selectionTable.setLinesVisible(true);
        
        // create the column
        final TableColumn selectionTableColumn = new TableColumn(selectionTable, SWT.LEFT);
        // set the header
        selectionTableColumn.setText("Chosen Qualifiers");
        
        selectionTable.addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent e) {
                Rectangle r = selectionTable.getClientArea();
                selectionTableColumn.setWidth(r.width);
            }
        });
        mSelectionTableViewer = new TableViewer(selectionTable);
        mSelectionTableViewer.setContentProvider(new QualifierContentProvider());
        mSelectionTableViewer.setLabelProvider(new QualifierLabelProvider(
                true /* showQualifierValue */));
        mSelectionTableViewer.setInput(mSelectedConfiguration);
        mSelectionTableViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                // ignore selection changes during resfreshes in some cases.
                if (mOnRefresh) {
                    return;
                }

                ISelection selection = event.getSelection();
                if (selection instanceof IStructuredSelection) {
                    IStructuredSelection structSelection = (IStructuredSelection)selection;
                    
                    if (structSelection.isEmpty() == false) {
                        Object first = structSelection.getFirstElement();
                        
                        if (first instanceof ResourceQualifier) {
                            mRemoveButton.setEnabled(true);
                            
                            QualifierEditBase composite = mUiMap.get(first.getClass());
    
                            if (composite != null) {
                                composite.setQualifier((ResourceQualifier)first);
                            }
    
                            mStackLayout.topControl = composite;
                            mQualifierEditParent.layout();
                            
                            return;
                        }
                    } else {
                        mStackLayout.topControl = null;
                        mQualifierEditParent.layout();
                    }
                }
                
                mRemoveButton.setEnabled(false);
            }
        });
        
        // 4th column is the detail of the selected qualifier 
        mQualifierEditParent = new Composite(this, SWT.NONE);
        mQualifierEditParent.setLayout(mStackLayout = new StackLayout());
        mQualifierEditParent.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        
        // create the UI for all the qualifiers, and associate them to the ResourceQualifer class.
        mUiMap.put(CountryCodeQualifier.class, new MCCEdit(mQualifierEditParent));
        mUiMap.put(NetworkCodeQualifier.class, new MNCEdit(mQualifierEditParent));
        mUiMap.put(LanguageQualifier.class, new LanguageEdit(mQualifierEditParent));
        mUiMap.put(RegionQualifier.class, new RegionEdit(mQualifierEditParent));
        mUiMap.put(ScreenOrientationQualifier.class, new OrientationEdit(mQualifierEditParent));
        mUiMap.put(PixelDensityQualifier.class, new PixelDensityEdit(mQualifierEditParent));
        mUiMap.put(TouchScreenQualifier.class, new TouchEdit(mQualifierEditParent));
        mUiMap.put(KeyboardStateQualifier.class, new KeyboardEdit(mQualifierEditParent));
        mUiMap.put(TextInputMethodQualifier.class, new TextInputEdit(mQualifierEditParent));
        mUiMap.put(NavigationMethodQualifier.class, new NavigationEdit(mQualifierEditParent));
        mUiMap.put(ScreenDimensionQualifier.class, new ScreenDimensionEdit(mQualifierEditParent));
    }
    
    /**
     * Sets a listener to be notified when the configuration changes.
     * @param listener A {@link Runnable} whose <code>run()</code> method is called when the
     * configuration is changed. The method is called from the UI thread.
     */
    public void setOnChangeListener(Runnable listener) {
        mOnChangeListener = listener;
    }
    
    /**
     * Initialize the UI with a given {@link FolderConfiguration}. This must
     * be called from the UI thread.
     * @param config The configuration.
     */
    public void setConfiguration(FolderConfiguration config) {
        mSelectedConfiguration.set(config);
        mSelectionTableViewer.refresh();
        
        // create the base config, which is the default config minus the qualifiers
        // in SelectedConfiguration
        mBaseConfiguration.substract(mSelectedConfiguration);
        mFullTableViewer.refresh();
    }
    
    /**
     * Initialize the UI with the configuration represented by a resource folder name.
     * This must be called from the UI thread.
     * 
     * @param folderSegments the segments of the folder name,
     *                       split using {@link FolderConfiguration#QUALIFIER_SEP}.
     * @return true if success, or false if the folder name is not a valid name.
     */
    public boolean setConfiguration(String[] folderSegments) {
        FolderConfiguration config = ResourceManager.getInstance().getConfig(folderSegments);
        
        if (config == null) {
            return false;
        }

        setConfiguration(config);

        return true;
    }
    
    /**
     * Initialize the UI with the configuration represented by a resource folder name.
     * This must be called from the UI thread.
     * @param folderName the name of the folder.
     * @return true if success, or false if the folder name is not a valid name.
     */
    public boolean setConfiguration(String folderName) {
        // split the name of the folder in segments.
        String[] folderSegments = folderName.split(FolderConfiguration.QUALIFIER_SEP);

        return setConfiguration(folderSegments);
    }
    
    /**
     * Gets the configuration as setup by the widget.
     * @param config the {@link FolderConfiguration} object to be filled with the information
     * from the UI.
     */
    public void getConfiguration(FolderConfiguration config) {
        config.set(mSelectedConfiguration);
    }
    
    /**
     * Returns the state of the configuration being edited/created.
     */
    public ConfigurationState getState() {
        if (mSelectedConfiguration.getInvalidQualifier() != null) {
            return ConfigurationState.INVALID_CONFIG;
        }
        
        if (mSelectedConfiguration.checkRegion() == false) {
            return ConfigurationState.REGION_WITHOUT_LANGUAGE;
        }
        
        return ConfigurationState.OK;
    }
    
    /**
     * Returns the first invalid qualifier of the configuration being edited/created,
     * or <code>null<code> if they are all valid (or if none exists).
     * <p/>If {@link #getState()} return {@link ConfigurationState#INVALID_CONFIG} then this will
     * not return <code>null</code>.
     */
    public ResourceQualifier getInvalidQualifier() {
        return mSelectedConfiguration.getInvalidQualifier();
    }

    /**
     * Handle changes in the configuration.
     * @param keepSelection if <code>true</code> attemps to avoid triggering selection change in
     * {@link #mSelectedConfiguration}.
     */
    private void onChange(boolean keepSelection) {
        ISelection selection = null;
        if (keepSelection) {
            mOnRefresh = true;
            selection = mSelectionTableViewer.getSelection();
        }

        mSelectionTableViewer.refresh(true);
        
        if (keepSelection) {
            mSelectionTableViewer.setSelection(selection);
            mOnRefresh = false;
        }

        if (mOnChangeListener != null) {
            mOnChangeListener.run();
        }
    }
    
    /**
     * Content provider around a {@link FolderConfiguration}.
     */
    private static class QualifierContentProvider implements IStructuredContentProvider {
        
        private FolderConfiguration mInput;

        public QualifierContentProvider() {
        }

        public void dispose() {
            // pass
        }

        public Object[] getElements(Object inputElement) {
            return mInput.getQualifiers();
        }

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            mInput = null;
            if (newInput instanceof FolderConfiguration) {
                mInput = (FolderConfiguration)newInput;
            }
        }
    }
    
    /**
     * Label provider for {@link ResourceQualifier} objects.
     */
    private static class QualifierLabelProvider implements ITableLabelProvider {
        
        private final boolean mShowQualifierValue;

        public QualifierLabelProvider(boolean showQualifierValue) {
            mShowQualifierValue = showQualifierValue;
        }

        public String getColumnText(Object element, int columnIndex) {
            // only one column, so we can ignore columnIndex
            if (element instanceof ResourceQualifier) {
                if (mShowQualifierValue) {
                    String value = ((ResourceQualifier)element).getStringValue();
                    if (value.length() == 0) {
                        return String.format("%1$s (?)",
                                ((ResourceQualifier)element).getShortName());
                    } else {
                        return value;
                    }
                    
                } else {
                    return ((ResourceQualifier)element).getShortName();
                }
            }

            return null;
        }
        
        public Image getColumnImage(Object element, int columnIndex) {
            // only one column, so we can ignore columnIndex
            if (element instanceof ResourceQualifier) {
                return ((ResourceQualifier)element).getIcon();
            }

            return null;
        }

        public void addListener(ILabelProviderListener listener) {
            // pass
        }

        public void dispose() {
            // pass
        }

        public boolean isLabelProperty(Object element, String property) {
            // pass
            return false;
        }

        public void removeListener(ILabelProviderListener listener) {
            // pass
        }
    }
    
    /**
     * Base class for Edit widget for {@link ResourceQualifier}.
     */
    private abstract static class QualifierEditBase extends Composite {

        public QualifierEditBase(Composite parent, String title) {
            super(parent, SWT.NONE);
            setLayout(new GridLayout(1, false));

            new Label(this, SWT.NONE).setText(title);
        }
        
        public abstract void setQualifier(ResourceQualifier qualifier);
    }
    
    /**
     * Edit widget for {@link CountryCodeQualifier}.
     */
    private class MCCEdit extends QualifierEditBase {

        private Text mText;

        public MCCEdit(Composite parent) {
            super(parent, CountryCodeQualifier.NAME);
            
            mText = new Text(this, SWT.BORDER);
            mText.addVerifyListener(new MobileCodeVerifier());
            mText.addModifyListener(new ModifyListener() {
                public void modifyText(ModifyEvent e) {
                    onTextChange();
                } 
            });

            mText.addFocusListener(new FocusAdapter() {
                @Override
                public void focusLost(FocusEvent e) {
                    onTextChange();
                }
            });
            
            new Label(this, SWT.NONE).setText("(3 digit code)");
        }
        
        private void onTextChange() {
            String value = mText.getText();
            
            if (value.length() == 0) {
                // empty string, means a qualifier with no value.
                // Since the qualifier classes are immutable, and we don't want to
                // remove the qualifier from the configuration, we create a new default one.
                mSelectedConfiguration.setCountryCodeQualifier(new CountryCodeQualifier());
            } else {
                try {
                    CountryCodeQualifier qualifier = CountryCodeQualifier.getQualifier(
                            CountryCodeQualifier.getFolderSegment(Integer.parseInt(value)));
                    if (qualifier != null) {
                        mSelectedConfiguration.setCountryCodeQualifier(qualifier);
                    } else {
                        // Failure! Looks like the value is wrong
                        // (for instance not exactly 3 digits).
                        mSelectedConfiguration.setCountryCodeQualifier(new CountryCodeQualifier());
                    }
                } catch (NumberFormatException nfe) {
                    // Looks like the code is not a number. This should not happen since the text
                    // field has a VerifyListener that prevents it.
                    mSelectedConfiguration.setCountryCodeQualifier(new CountryCodeQualifier());
                }
            }
   
            // notify of change
            onChange(true /* keepSelection */);
        }

        @Override
        public void setQualifier(ResourceQualifier qualifier) {
            CountryCodeQualifier q = (CountryCodeQualifier)qualifier;
            
            mText.setText(Integer.toString(q.getCode()));
        }
    }

    /**
     * Edit widget for {@link NetworkCodeQualifier}.
     */
    private class MNCEdit extends QualifierEditBase {
        private Text mText;

        public MNCEdit(Composite parent) {
            super(parent, NetworkCodeQualifier.NAME);
            
            mText = new Text(this, SWT.BORDER);
            mText.addVerifyListener(new MobileCodeVerifier());
            mText.addModifyListener(new ModifyListener() {
                public void modifyText(ModifyEvent e) {
                    onTextChange();
                }
            });
            mText.addFocusListener(new FocusAdapter() {
                @Override
                public void focusLost(FocusEvent e) {
                    onTextChange();
                }
            });

            new Label(this, SWT.NONE).setText("(1-3 digit code)");
        }
        
        private void onTextChange() {
            String value = mText.getText();
            
            if (value.length() == 0) {
                // empty string, means a qualifier with no value.
                // Since the qualifier classes are immutable, and we don't want to
                // remove the qualifier from the configuration, we create a new default one.
                mSelectedConfiguration.setNetworkCodeQualifier(new NetworkCodeQualifier());
            } else {
                try {
                    NetworkCodeQualifier qualifier = NetworkCodeQualifier.getQualifier(
                            NetworkCodeQualifier.getFolderSegment(Integer.parseInt(value)));
                    if (qualifier != null) {
                        mSelectedConfiguration.setNetworkCodeQualifier(qualifier);
                    } else {
                        // Failure! Looks like the value is wrong
                        // (for instance not exactly 3 digits).
                        mSelectedConfiguration.setNetworkCodeQualifier(new NetworkCodeQualifier());
                    }
                } catch (NumberFormatException nfe) {
                    // Looks like the code is not a number. This should not happen since the text
                    // field has a VerifyListener that prevents it.
                    mSelectedConfiguration.setNetworkCodeQualifier(new NetworkCodeQualifier());
                }
            }
   
            // notify of change
            onChange(true /* keepSelection */);
        } 

        @Override
        public void setQualifier(ResourceQualifier qualifier) {
            NetworkCodeQualifier q = (NetworkCodeQualifier)qualifier;
            
            mText.setText(Integer.toString(q.getCode()));
        }
    }
    
    /**
     * Edit widget for {@link LanguageQualifier}.
     */
    private class LanguageEdit extends QualifierEditBase {
        private Combo mLanguage;

        public LanguageEdit(Composite parent) {
            super(parent, LanguageQualifier.NAME);

            mLanguage = new Combo(this, SWT.DROP_DOWN);
            mLanguage.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
            mLanguage.addVerifyListener(new LanguageRegionVerifier());
            mLanguage.addSelectionListener(new SelectionListener() {
                public void widgetDefaultSelected(SelectionEvent e) {
                    onLanguageChange();
                }
                public void widgetSelected(SelectionEvent e) {
                    onLanguageChange();
                }
            });
            mLanguage.addModifyListener(new ModifyListener() {
                public void modifyText(ModifyEvent e) {
                    onLanguageChange();
                }
            });

            new Label(this, SWT.NONE).setText("(2 letter code)");
        }

        private void onLanguageChange() {
            // update the current config
            String value = mLanguage.getText();
            
            if (value.length() == 0) {
                // empty string, means no qualifier.
                // Since the qualifier classes are immutable, and we don't want to
                // remove the qualifier from the configuration, we create a new default one.
                mSelectedConfiguration.setLanguageQualifier(new LanguageQualifier());
            } else {
                LanguageQualifier qualifier = null;
                String segment = LanguageQualifier.getFolderSegment(value);
                if (segment != null) {
                    qualifier = LanguageQualifier.getQualifier(segment);
                }

                if (qualifier != null) {
                    mSelectedConfiguration.setLanguageQualifier(qualifier);
                } else {
                    // Failure! Looks like the value is wrong (for instance a one letter string).
                    mSelectedConfiguration.setLanguageQualifier(new LanguageQualifier());
                }
            }

            // notify of change
            onChange(true /* keepSelection */);
        }

        @Override
        public void setQualifier(ResourceQualifier qualifier) {
            LanguageQualifier q = (LanguageQualifier)qualifier;

            String value = q.getValue();
            if (value != null) {
                mLanguage.setText(value);
            }
        }
    }

    /**
     * Edit widget for {@link RegionQualifier}.
     */
    private class RegionEdit extends QualifierEditBase {
        private Combo mRegion;

        public RegionEdit(Composite parent) {
            super(parent, RegionQualifier.NAME);

            mRegion = new Combo(this, SWT.DROP_DOWN);
            mRegion.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
            mRegion.addVerifyListener(new LanguageRegionVerifier());
            mRegion.addSelectionListener(new SelectionListener() {
                public void widgetDefaultSelected(SelectionEvent e) {
                    onRegionChange();
                }
                public void widgetSelected(SelectionEvent e) {
                    onRegionChange();
                }
            });
            mRegion.addModifyListener(new ModifyListener() {
                public void modifyText(ModifyEvent e) {
                    onRegionChange();
                }
            });

            new Label(this, SWT.NONE).setText("(2 letter code)");
        }

        private void onRegionChange() {
            // update the current config
            String value = mRegion.getText();
            
            if (value.length() == 0) {
                // empty string, means no qualifier.
                // Since the qualifier classes are immutable, and we don't want to
                // remove the qualifier from the configuration, we create a new default one.
                mSelectedConfiguration.setRegionQualifier(new RegionQualifier());
            } else {
                RegionQualifier qualifier = null;
                String segment = RegionQualifier.getFolderSegment(value);
                if (segment != null) {
                    qualifier = RegionQualifier.getQualifier(segment);
                }

                if (qualifier != null) {
                    mSelectedConfiguration.setRegionQualifier(qualifier);
                } else {
                    // Failure! Looks like the value is wrong (for instance a one letter string).
                    mSelectedConfiguration.setRegionQualifier(new RegionQualifier());
                }
            }

            // notify of change
            onChange(true /* keepSelection */);
        }

        @Override
        public void setQualifier(ResourceQualifier qualifier) {
            RegionQualifier q = (RegionQualifier)qualifier;

            String value = q.getValue();
            if (value != null) {
                mRegion.setText(q.getValue());
            }
        }
    }
    
    /**
     * Edit widget for {@link ScreenOrientationQualifier}.
     */
    private class OrientationEdit extends QualifierEditBase {

        private Combo mOrientation;

        public OrientationEdit(Composite parent) {
            super(parent, ScreenOrientationQualifier.NAME);

            mOrientation = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
            ScreenOrientation[] soValues = ScreenOrientation.values();
            for (ScreenOrientation value : soValues) {
                mOrientation.add(value.getDisplayValue());
            }

            mOrientation.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
            mOrientation.addSelectionListener(new SelectionListener() {
                public void widgetDefaultSelected(SelectionEvent e) {
                    onOrientationChange();
                }
                public void widgetSelected(SelectionEvent e) {
                    onOrientationChange();
                }
            });
        }

        protected void onOrientationChange() {
            // update the current config
            int index = mOrientation.getSelectionIndex();

            if (index != -1) {
                mSelectedConfiguration.setScreenOrientationQualifier(new ScreenOrientationQualifier(
                    ScreenOrientation.getByIndex(index)));
            } else {
                // empty selection, means no qualifier.
                // Since the qualifier classes are immutable, and we don't want to
                // remove the qualifier from the configuration, we create a new default one.
                mSelectedConfiguration.setScreenOrientationQualifier(
                        new ScreenOrientationQualifier());
            }

            // notify of change
            onChange(true /* keepSelection */);
        }

        @Override
        public void setQualifier(ResourceQualifier qualifier) {
            ScreenOrientationQualifier q = (ScreenOrientationQualifier)qualifier;

            ScreenOrientation value = q.getValue();
            if (value == null) {
                mOrientation.clearSelection();
            } else {
                mOrientation.select(ScreenOrientation.getIndex(value));
            }
        }
    }

    /**
     * Edit widget for {@link PixelDensityQualifier}.
     */
    private class PixelDensityEdit extends QualifierEditBase {
        private Text mText;

        public PixelDensityEdit(Composite parent) {
            super(parent, PixelDensityQualifier.NAME);
            
            mText = new Text(this, SWT.BORDER);
            mText.addVerifyListener(new DensityVerifier());
            mText.addModifyListener(new ModifyListener() {
                public void modifyText(ModifyEvent e) {
                    onTextChange();
                }
            });
            mText.addFocusListener(new FocusAdapter() {
                @Override
                public void focusLost(FocusEvent e) {
                    onTextChange();
                }
            });
        }
        
        private void onTextChange() {
            String value = mText.getText();
            
            if (value.length() == 0) {
                // empty string, means a qualifier with no value.
                // Since the qualifier classes are immutable, and we don't want to
                // remove the qualifier from the configuration, we create a new default one.
                mSelectedConfiguration.setPixelDensityQualifier(new PixelDensityQualifier());
            } else {
                try {
                    PixelDensityQualifier qualifier = PixelDensityQualifier.getQualifier(
                            PixelDensityQualifier.getFolderSegment(Integer.parseInt(value)));
                    if (qualifier != null) {
                        mSelectedConfiguration.setPixelDensityQualifier(qualifier);
                    } else {
                        // Failure! Looks like the value is wrong
                        // (for instance a one letter string).
                        // We do nothing in this case.
                        return;
                    }
                } catch (NumberFormatException nfe) {
                    // Looks like the code is not a number. This should not happen since the text
                    // field has a VerifyListener that prevents it.
                    // We do nothing in this case.
                    return;
                }
            }
   
            // notify of change
            onChange(true /* keepSelection */);
        } 

        @Override
        public void setQualifier(ResourceQualifier qualifier) {
            PixelDensityQualifier q = (PixelDensityQualifier)qualifier;
            
            mText.setText(Integer.toString(q.getValue()));
        }
    }

    /**
     * Edit widget for {@link TouchScreenQualifier}.
     */
    private class TouchEdit extends QualifierEditBase {

        private Combo mTouchScreen;

        public TouchEdit(Composite parent) {
            super(parent, TouchScreenQualifier.NAME);

            mTouchScreen = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
            TouchScreenType[] tstValues = TouchScreenType.values();
            for (TouchScreenType value : tstValues) {
                mTouchScreen.add(value.getDisplayValue());
            }

            mTouchScreen.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
            mTouchScreen.addSelectionListener(new SelectionListener() {
                public void widgetDefaultSelected(SelectionEvent e) {
                    onTouchChange();
                }
                public void widgetSelected(SelectionEvent e) {
                    onTouchChange();
                }
            });
        }

        protected void onTouchChange() {
            // update the current config
            int index = mTouchScreen.getSelectionIndex();

            if (index != -1) {
                mSelectedConfiguration.setTouchTypeQualifier(new TouchScreenQualifier(
                        TouchScreenType.getByIndex(index)));
            } else {
                // empty selection, means no qualifier.
                // Since the qualifier classes are immutable, and we don't want to
                // remove the qualifier from the configuration, we create a new default one.
                mSelectedConfiguration.setTouchTypeQualifier(new TouchScreenQualifier());
            }

            // notify of change
            onChange(true /* keepSelection */);
        }

        @Override
        public void setQualifier(ResourceQualifier qualifier) {
            TouchScreenQualifier q = (TouchScreenQualifier)qualifier;

            TouchScreenType value = q.getValue();
            if (value == null) {
                mTouchScreen.clearSelection();
            } else {
                mTouchScreen.select(TouchScreenType.getIndex(value));
            }
        }
    }

    /**
     * Edit widget for {@link KeyboardStateQualifier}.
     */
    private class KeyboardEdit extends QualifierEditBase {

        private Combo mKeyboard;

        public KeyboardEdit(Composite parent) {
            super(parent, KeyboardStateQualifier.NAME);

            mKeyboard = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
            KeyboardState[] ksValues = KeyboardState.values();
            for (KeyboardState value : ksValues) {
                mKeyboard.add(value.getDisplayValue());
            }

            mKeyboard.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
            mKeyboard.addSelectionListener(new SelectionListener() {
                public void widgetDefaultSelected(SelectionEvent e) {
                    onKeyboardChange();
                }
                public void widgetSelected(SelectionEvent e) {
                    onKeyboardChange();
                }
            });
        }

        protected void onKeyboardChange() {
            // update the current config
            int index = mKeyboard.getSelectionIndex();

            if (index != -1) {
                mSelectedConfiguration.setKeyboardStateQualifier(new KeyboardStateQualifier(
                        KeyboardState.getByIndex(index)));
            } else {
                // empty selection, means no qualifier.
                // Since the qualifier classes are immutable, and we don't want to
                // remove the qualifier from the configuration, we create a new default one.
                mSelectedConfiguration.setKeyboardStateQualifier(
                        new KeyboardStateQualifier());
            }

            // notify of change
            onChange(true /* keepSelection */);
        }

        @Override
        public void setQualifier(ResourceQualifier qualifier) {
            KeyboardStateQualifier q = (KeyboardStateQualifier)qualifier;

            KeyboardState value = q.getValue();
            if (value == null) {
                mKeyboard.clearSelection();
            } else {
                mKeyboard.select(KeyboardState.getIndex(value));
            }
        }
    }

    /**
     * Edit widget for {@link TextInputMethodQualifier}.
     */
    private class TextInputEdit extends QualifierEditBase {

        private Combo mTextInput;

        public TextInputEdit(Composite parent) {
            super(parent, TextInputMethodQualifier.NAME);

            mTextInput = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
            TextInputMethod[] timValues = TextInputMethod.values();
            for (TextInputMethod value : timValues) {
                mTextInput.add(value.getDisplayValue());
            }

            mTextInput.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
            mTextInput.addSelectionListener(new SelectionListener() {
                public void widgetDefaultSelected(SelectionEvent e) {
                    onTextInputChange();
                }
                public void widgetSelected(SelectionEvent e) {
                    onTextInputChange();
                }
            });
        }

        protected void onTextInputChange() {
            // update the current config
            int index = mTextInput.getSelectionIndex();

            if (index != -1) {
                mSelectedConfiguration.setTextInputMethodQualifier(new TextInputMethodQualifier(
                        TextInputMethod.getByIndex(index)));
            } else {
                // empty selection, means no qualifier.
                // Since the qualifier classes are immutable, and we don't want to
                // remove the qualifier from the configuration, we create a new default one.
                mSelectedConfiguration.setTextInputMethodQualifier(
                        new TextInputMethodQualifier());
            }

            // notify of change
            onChange(true /* keepSelection */);
        }

        @Override
        public void setQualifier(ResourceQualifier qualifier) {
            TextInputMethodQualifier q = (TextInputMethodQualifier)qualifier;

            TextInputMethod value = q.getValue();
            if (value == null) {
                mTextInput.clearSelection();
            } else {
                mTextInput.select(TextInputMethod.getIndex(value));
            }
        }
    }

    /**
     * Edit widget for {@link NavigationMethodQualifier}.
     */
    private class NavigationEdit extends QualifierEditBase {

        private Combo mNavigation;

        public NavigationEdit(Composite parent) {
            super(parent, NavigationMethodQualifier.NAME);

            mNavigation = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
            NavigationMethod[] nmValues = NavigationMethod.values();
            for (NavigationMethod value : nmValues) {
                mNavigation.add(value.getDisplayValue());
            }

            mNavigation.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
            mNavigation.addSelectionListener(new SelectionListener() {
                public void widgetDefaultSelected(SelectionEvent e) {
                    onNavigationChange();
                }
                public void widgetSelected(SelectionEvent e) {
                    onNavigationChange();
                }
            });
        }

        protected void onNavigationChange() {
            // update the current config
            int index = mNavigation.getSelectionIndex();

            if (index != -1) {
                mSelectedConfiguration.setNavigationMethodQualifier(new NavigationMethodQualifier(
                        NavigationMethod.getByIndex(index)));
            } else {
                // empty selection, means no qualifier.
                // Since the qualifier classes are immutable, and we don't want to
                // remove the qualifier from the configuration, we create a new default one.
                mSelectedConfiguration.setNavigationMethodQualifier(
                        new NavigationMethodQualifier());
            }

            // notify of change
            onChange(true /* keepSelection */);
        }

        @Override
        public void setQualifier(ResourceQualifier qualifier) {
            NavigationMethodQualifier q = (NavigationMethodQualifier)qualifier;

            NavigationMethod value = q.getValue();
            if (value == null) {
                mNavigation.clearSelection();
            } else {
                mNavigation.select(NavigationMethod.getIndex(value));
            }
        }
    }
    
    /**
     * Edit widget for {@link ScreenDimensionQualifier}.
     */
    private class ScreenDimensionEdit extends QualifierEditBase {

        private Text mSize1;
        private Text mSize2;

        public ScreenDimensionEdit(Composite parent) {
            super(parent, ScreenDimensionQualifier.NAME);

            ModifyListener modifyListener = new ModifyListener() {
                public void modifyText(ModifyEvent e) {
                    onSizeChange();
                } 
            };
            
            FocusAdapter focusListener = new FocusAdapter() {
                @Override
                public void focusLost(FocusEvent e) {
                    onSizeChange();
                }
            };

            mSize1 = new Text(this, SWT.BORDER);
            mSize1.addVerifyListener(new DimensionVerifier());
            mSize1.addModifyListener(modifyListener);
            mSize1.addFocusListener(focusListener);

            mSize2 = new Text(this, SWT.BORDER);
            mSize2.addVerifyListener(new DimensionVerifier());
            mSize2.addModifyListener(modifyListener);
            mSize2.addFocusListener(focusListener);
        }
        
        private void onSizeChange() {
            // update the current config
            String size1 = mSize1.getText();
            String size2 = mSize2.getText();
            
            if (size1.length() == 0 || size2.length() == 0) {
                // if one of the strings is empty, reset to no qualifier.
                // Since the qualifier classes are immutable, and we don't want to
                // remove the qualifier from the configuration, we create a new default one.
                mSelectedConfiguration.setScreenDimensionQualifier(new ScreenDimensionQualifier());
            } else {
                ScreenDimensionQualifier qualifier = ScreenDimensionQualifier.getQualifier(size1,
                        size2);

                if (qualifier != null) {
                    mSelectedConfiguration.setScreenDimensionQualifier(qualifier);
                } else {
                    // Failure! Looks like the value is wrong, reset the qualifier
                    // Since the qualifier classes are immutable, and we don't want to
                    // remove the qualifier from the configuration, we create a new default one.
                    mSelectedConfiguration.setScreenDimensionQualifier(
                            new ScreenDimensionQualifier());
                }
            }
   
            // notify of change
            onChange(true /* keepSelection */);
        }

        @Override
        public void setQualifier(ResourceQualifier qualifier) {
            ScreenDimensionQualifier q = (ScreenDimensionQualifier)qualifier;
            
            mSize1.setText(Integer.toString(q.getValue1()));
            mSize2.setText(Integer.toString(q.getValue2()));
        }
    }
}