Home | History | Annotate | Download | only in newxmlfile
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.ide.eclipse.adt.internal.wizards.newxmlfile;
     17 
     18 import static com.android.SdkConstants.FD_RES;
     19 import static com.android.SdkConstants.FD_RES_VALUES;
     20 import static com.android.SdkConstants.RES_QUALIFIER_SEP;
     21 
     22 import com.android.ide.common.rendering.api.ResourceValue;
     23 import com.android.ide.common.res2.ValueXmlHelper;
     24 import com.android.ide.common.resources.ResourceItem;
     25 import com.android.ide.common.resources.configuration.FolderConfiguration;
     26 import com.android.ide.eclipse.adt.AdtPlugin;
     27 import com.android.ide.eclipse.adt.AdtUtils;
     28 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LocaleManager;
     29 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageControl;
     30 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewManager;
     31 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
     32 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
     33 import com.android.resources.ResourceType;
     34 import com.google.common.base.Charsets;
     35 import com.google.common.collect.Maps;
     36 
     37 import org.eclipse.core.resources.IContainer;
     38 import org.eclipse.core.resources.IFile;
     39 import org.eclipse.core.resources.IFolder;
     40 import org.eclipse.core.resources.IProject;
     41 import org.eclipse.core.resources.IWorkspaceRoot;
     42 import org.eclipse.core.resources.ResourcesPlugin;
     43 import org.eclipse.core.runtime.CoreException;
     44 import org.eclipse.core.runtime.NullProgressMonitor;
     45 import org.eclipse.jface.dialogs.Dialog;
     46 import org.eclipse.jface.dialogs.IDialogConstants;
     47 import org.eclipse.jface.viewers.ArrayContentProvider;
     48 import org.eclipse.jface.viewers.CellEditor;
     49 import org.eclipse.jface.viewers.CellLabelProvider;
     50 import org.eclipse.jface.viewers.ColumnViewer;
     51 import org.eclipse.jface.viewers.EditingSupport;
     52 import org.eclipse.jface.viewers.IBaseLabelProvider;
     53 import org.eclipse.jface.viewers.TableViewer;
     54 import org.eclipse.jface.viewers.TableViewerColumn;
     55 import org.eclipse.jface.viewers.TextCellEditor;
     56 import org.eclipse.jface.viewers.ViewerCell;
     57 import org.eclipse.swt.SWT;
     58 import org.eclipse.swt.events.ControlEvent;
     59 import org.eclipse.swt.events.ControlListener;
     60 import org.eclipse.swt.events.SelectionEvent;
     61 import org.eclipse.swt.events.SelectionListener;
     62 import org.eclipse.swt.events.TraverseEvent;
     63 import org.eclipse.swt.events.TraverseListener;
     64 import org.eclipse.swt.graphics.Image;
     65 import org.eclipse.swt.graphics.Point;
     66 import org.eclipse.swt.graphics.Rectangle;
     67 import org.eclipse.swt.layout.GridData;
     68 import org.eclipse.swt.layout.GridLayout;
     69 import org.eclipse.swt.widgets.Button;
     70 import org.eclipse.swt.widgets.Combo;
     71 import org.eclipse.swt.widgets.Composite;
     72 import org.eclipse.swt.widgets.Control;
     73 import org.eclipse.swt.widgets.Label;
     74 import org.eclipse.swt.widgets.Shell;
     75 import org.eclipse.swt.widgets.Table;
     76 import org.eclipse.swt.widgets.TableColumn;
     77 import org.eclipse.ui.ISharedImages;
     78 import org.eclipse.ui.IWorkbench;
     79 import org.eclipse.ui.PlatformUI;
     80 
     81 import java.io.ByteArrayInputStream;
     82 import java.io.InputStream;
     83 import java.util.ArrayList;
     84 import java.util.Arrays;
     85 import java.util.Collection;
     86 import java.util.Collections;
     87 import java.util.List;
     88 import java.util.Map;
     89 import java.util.Set;
     90 import java.util.SortedSet;
     91 
     92 /**
     93  * Dialog which adds a new translation to the project
     94  */
     95 public class AddTranslationDialog extends Dialog implements ControlListener, SelectionListener,
     96         TraverseListener {
     97     private static final int KEY_COLUMN = 0;
     98     private static final int DEFAULT_TRANSLATION_COLUMN = 1;
     99     private static final int NEW_TRANSLATION_COLUMN = 2;
    100     private final FolderConfiguration mConfiguration = new FolderConfiguration();
    101     private final IProject mProject;
    102     private String mTarget;
    103     private boolean mIgnore;
    104     private Map<String, String> mTranslations;
    105     private Set<String> mExistingLanguages;
    106     private String mSelectedLanguage;
    107     private String mSelectedRegion;
    108 
    109     private Table mTable;
    110     private Combo mLanguageCombo;
    111     private Combo mRegionCombo;
    112     private ImageControl mFlag;
    113     private Label mFile;
    114     private Button mOkButton;
    115     private Composite mErrorPanel;
    116     private Label mErrorLabel;
    117     private MyTableViewer mTableViewer;
    118 
    119     /**
    120      * Creates the dialog.
    121      * @param parentShell the parent shell
    122      * @param project the project to add translations into
    123      */
    124     public AddTranslationDialog(Shell parentShell, IProject project) {
    125         super(parentShell);
    126         setShellStyle(SWT.CLOSE | SWT.RESIZE | SWT.TITLE);
    127         mProject = project;
    128     }
    129 
    130     @Override
    131     protected Control createDialogArea(Composite parent) {
    132         Composite container = (Composite) super.createDialogArea(parent);
    133         GridLayout gl_container = new GridLayout(6, false);
    134         gl_container.horizontalSpacing = 0;
    135         container.setLayout(gl_container);
    136 
    137         Label languageLabel = new Label(container, SWT.NONE);
    138         languageLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
    139         languageLabel.setText("Language:");
    140         mLanguageCombo = new Combo(container, SWT.READ_ONLY);
    141         GridData gd_mLanguageCombo = new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1);
    142         gd_mLanguageCombo.widthHint = 150;
    143         mLanguageCombo.setLayoutData(gd_mLanguageCombo);
    144 
    145         Label regionLabel = new Label(container, SWT.NONE);
    146         GridData gd_regionLabel = new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1);
    147         gd_regionLabel.horizontalIndent = 10;
    148         regionLabel.setLayoutData(gd_regionLabel);
    149         regionLabel.setText("Region:");
    150         mRegionCombo = new Combo(container, SWT.READ_ONLY);
    151         GridData gd_mRegionCombo = new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1);
    152         gd_mRegionCombo.widthHint = 150;
    153         mRegionCombo.setLayoutData(gd_mRegionCombo);
    154         mRegionCombo.setEnabled(false);
    155 
    156         mFlag = new ImageControl(container, SWT.NONE, null);
    157         mFlag.setDisposeImage(false);
    158         GridData gd_mFlag = new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1);
    159         gd_mFlag.exclude = true;
    160         gd_mFlag.widthHint = 32;
    161         gd_mFlag.horizontalIndent = 3;
    162         mFlag.setLayoutData(gd_mFlag);
    163 
    164         mFile = new Label(container, SWT.NONE);
    165         mFile.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
    166 
    167         mTableViewer = new MyTableViewer(container, SWT.BORDER | SWT.FULL_SELECTION);
    168         mTable = mTableViewer.getTable();
    169         mTable.setEnabled(false);
    170         mTable.setLinesVisible(true);
    171         mTable.setHeaderVisible(true);
    172         mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 6, 2));
    173         mTable.addControlListener(this);
    174         mTable.addTraverseListener(this);
    175         // If you have difficulty opening up this form in WindowBuilder and it complains about
    176         // the next line, change the type of the mTableViewer field and the above
    177         // constructor call from MyTableViewer to TableViewer
    178         TableViewerColumn keyViewerColumn = new TableViewerColumn(mTableViewer, SWT.NONE);
    179         TableColumn keyColumn = keyViewerColumn.getColumn();
    180         keyColumn.setWidth(100);
    181         keyColumn.setText("Key");
    182         TableViewerColumn defaultViewerColumn = new TableViewerColumn(mTableViewer, SWT.NONE);
    183         TableColumn defaultColumn = defaultViewerColumn.getColumn();
    184         defaultColumn.setWidth(200);
    185         defaultColumn.setText("Default");
    186         TableViewerColumn translationViewerColumn = new TableViewerColumn(mTableViewer, SWT.NONE);
    187         TableColumn translationColumn = translationViewerColumn.getColumn();
    188         translationColumn.setWidth(200);
    189         translationColumn.setText("New Translation");
    190 
    191         mErrorPanel = new Composite(container, SWT.NONE);
    192         GridData gd_mErrorLabel = new GridData(SWT.FILL, SWT.CENTER, false, false, 6, 1);
    193         gd_mErrorLabel.exclude = true;
    194         mErrorPanel.setLayoutData(gd_mErrorLabel);
    195 
    196         translationViewerColumn.setEditingSupport(new TranslationEditingSupport(mTableViewer));
    197 
    198         fillLanguages();
    199         fillRegions();
    200         fillStrings();
    201         updateColumnWidths();
    202         validatePage();
    203 
    204         mLanguageCombo.addSelectionListener(this);
    205         mRegionCombo.addSelectionListener(this);
    206 
    207         return container;
    208     }
    209 
    210     /** Populates the table with keys and default strings */
    211     private void fillStrings() {
    212         ResourceManager manager = ResourceManager.getInstance();
    213         ProjectResources resources = manager.getProjectResources(mProject);
    214         mExistingLanguages = resources.getLanguages();
    215 
    216         Collection<ResourceItem> items = resources.getResourceItemsOfType(ResourceType.STRING);
    217 
    218         ResourceItem[] array = items.toArray(new ResourceItem[items.size()]);
    219         Arrays.sort(array);
    220 
    221         // TODO: Read in the actual XML files providing the default keys here
    222         // (they can be obtained via ResourceItem.getSourceFileList())
    223         // such that we can read all the attributes associated with each
    224         // item, and if it defines translatable=false, or the filename is
    225         // donottranslate.xml, we can ignore it, and in other cases just
    226         // duplicate all the attributes (such as "formatted=true", or other
    227         // local conventions such as "product=tablet", or "msgid="123123123",
    228         // etc.)
    229 
    230         mTranslations = Maps.newHashMapWithExpectedSize(items.size());
    231         IBaseLabelProvider labelProvider = new CellLabelProvider() {
    232             @Override
    233             public void update(ViewerCell cell) {
    234                 Object element = cell.getElement();
    235                 int index = cell.getColumnIndex();
    236                 ResourceItem item = (ResourceItem) element;
    237                 switch (index) {
    238                     case KEY_COLUMN: {
    239                         // Key
    240                         cell.setText(item.getName());
    241                         return;
    242                     }
    243                     case DEFAULT_TRANSLATION_COLUMN: {
    244                         // Default translation
    245                         ResourceValue value = item.getResourceValue(ResourceType.STRING,
    246                                 mConfiguration, false);
    247 
    248                         if (value != null) {
    249                             cell.setText(value.getValue());
    250                             return;
    251                         }
    252                         break;
    253                     }
    254                     case NEW_TRANSLATION_COLUMN: {
    255                         // New translation
    256                         String translation = mTranslations.get(item.getName());
    257                         if (translation != null) {
    258                             cell.setText(translation);
    259                             return;
    260                         }
    261                         break;
    262                     }
    263                     default:
    264                         assert false : index;
    265                 }
    266                 cell.setText("");
    267             }
    268         };
    269 
    270         mTableViewer.setLabelProvider(labelProvider);
    271         mTableViewer.setContentProvider(new ArrayContentProvider());
    272         mTableViewer.setInput(array);
    273     }
    274 
    275     /** Populate the languages dropdown */
    276     private void fillLanguages() {
    277         Set<String> languageCodes = LocaleManager.getLanguageCodes();
    278         List<String> labels = new ArrayList<String>();
    279         for (String code : languageCodes) {
    280             labels.add(code + ": " + LocaleManager.getLanguageName(code)); //$NON-NLS-1$
    281         }
    282         Collections.sort(labels);
    283         labels.add(0, "(Select)");
    284         mLanguageCombo.setItems(labels.toArray(new String[labels.size()]));
    285         mLanguageCombo.select(0);
    286     }
    287 
    288     /** Populate the regions dropdown */
    289     private void fillRegions() {
    290         // TODO: When you switch languages, offer some "default" usable options. For example,
    291         // when you choose English, offer the countries that use English, and so on. Unfortunately
    292         // we don't have good data about this, we'd just need to hardcode a few common cases.
    293         Set<String> regionCodes = LocaleManager.getRegionCodes();
    294         List<String> labels = new ArrayList<String>();
    295         for (String code : regionCodes) {
    296             labels.add(code + ": " + LocaleManager.getRegionName(code)); //$NON-NLS-1$
    297         }
    298         Collections.sort(labels);
    299         labels.add(0, "Any");
    300         mRegionCombo.setItems(labels.toArray(new String[labels.size()]));
    301         mRegionCombo.select(0);
    302     }
    303 
    304     /** React to resizing by distributing the space evenly between the last two columns */
    305     private void updateColumnWidths() {
    306         Rectangle r = mTable.getClientArea();
    307         int availableWidth = r.width;
    308         // Distribute all available space to the last two columns
    309         int columnCount = mTable.getColumnCount();
    310         for (int i = 0; i < columnCount; i++) {
    311             TableColumn column = mTable.getColumn(i);
    312             availableWidth -= column.getWidth();
    313         }
    314         if (availableWidth != 0) {
    315             TableColumn column = mTable.getColumn(DEFAULT_TRANSLATION_COLUMN);
    316             column.setWidth(column.getWidth() + availableWidth / 2);
    317             column = mTable.getColumn(NEW_TRANSLATION_COLUMN);
    318             column.setWidth(column.getWidth() + availableWidth / 2 + availableWidth % 2);
    319         }
    320     }
    321 
    322     @Override
    323     protected void createButtonsForButtonBar(Composite parent) {
    324         mOkButton = createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL,
    325                 // Don't make the OK button default as in most dialogs, since when you press
    326                 // Return thinking you might edit a value it dismisses the dialog instead
    327                 false);
    328         createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
    329         mOkButton.setEnabled(false);
    330 
    331         validatePage();
    332     }
    333 
    334     /**
    335      * Return the initial size of the dialog.
    336      */
    337     @Override
    338     protected Point getInitialSize() {
    339         return new Point(800, 600);
    340     }
    341 
    342     private void updateTarget() {
    343         if (mSelectedLanguage == null) {
    344             mTarget = null;
    345             mFile.setText("");
    346         } else {
    347             String folder = FD_RES + '/' + FD_RES_VALUES + RES_QUALIFIER_SEP + mSelectedLanguage;
    348             if (mSelectedRegion != null) {
    349                 folder = folder + RES_QUALIFIER_SEP + 'r' + mSelectedRegion;
    350             }
    351             mTarget = folder + "/strings.xml"; //$NON-NLS-1$
    352             mFile.setText(String.format("Creating %1$s", mTarget));
    353         }
    354     }
    355 
    356     private void updateFlag() {
    357         if (mSelectedLanguage == null) {
    358             // Nothing selected
    359             ((GridData) mFlag.getLayoutData()).exclude = true;
    360         } else {
    361             LocaleManager manager = LocaleManager.get();
    362             Image flag = manager.getFlag(mSelectedLanguage, mSelectedRegion);
    363             if (flag != null) {
    364                 ((GridData) mFlag.getLayoutData()).exclude = false;
    365                 mFlag.setImage(flag);
    366             }
    367         }
    368 
    369         mFlag.getParent().layout(true);
    370         mFlag.getParent().redraw();
    371     }
    372 
    373     /** Actually create the new translation file and write it to disk */
    374     private void createTranslation() {
    375         List<String> keys = new ArrayList<String>(mTranslations.keySet());
    376         Collections.sort(keys);
    377 
    378         StringBuilder sb = new StringBuilder(keys.size() * 120);
    379         sb.append("<resources>\n\n");          //$NON-NLS-1$
    380         for (String key : keys) {
    381             String value = mTranslations.get(key);
    382             if (value == null || value.trim().isEmpty()) {
    383                 continue;
    384             }
    385             sb.append("    <string name=\"");  //$NON-NLS-1$
    386             sb.append(key);
    387             sb.append("\">");                  //$NON-NLS-1$
    388             sb.append(ValueXmlHelper.escapeResourceString(value));
    389             sb.append("</string>\n");          //$NON-NLS-1$
    390         }
    391         sb.append("\n</resources>");           //$NON-NLS-1$
    392 
    393         IFile file = mProject.getFile(mTarget);
    394 
    395         try {
    396             IContainer parent = file.getParent();
    397             AdtUtils.ensureExists(parent);
    398             InputStream source = new ByteArrayInputStream(sb.toString().getBytes(Charsets.UTF_8));
    399             file.create(source, true, new NullProgressMonitor());
    400             AdtPlugin.openFile(file, null, true /*showEditorTab*/);
    401 
    402             // Ensure that the project resources updates itself to notice the new language.
    403             // In theory, this shouldn't be necessary.
    404             ResourceManager manager = ResourceManager.getInstance();
    405             IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
    406             IFolder folder = root.getFolder(parent.getFullPath());
    407             manager.getResourceFolder(folder);
    408             RenderPreviewManager.bumpRevision();
    409         } catch (CoreException e) {
    410             AdtPlugin.log(e, null);
    411         }
    412     }
    413 
    414     private void validatePage() {
    415         if (mOkButton == null) { // Early initialization
    416             return;
    417         }
    418 
    419         String message = null;
    420 
    421         if (mSelectedLanguage == null) {
    422             message = "Select a language";
    423         } else if (mExistingLanguages.contains(mSelectedLanguage)) {
    424             if (mSelectedRegion == null) {
    425                 message = String.format("%1$s is already translated in this project",
    426                         LocaleManager.getLanguageName(mSelectedLanguage));
    427             } else {
    428                 ResourceManager manager = ResourceManager.getInstance();
    429                 ProjectResources resources = manager.getProjectResources(mProject);
    430                 SortedSet<String> regions = resources.getRegions(mSelectedLanguage);
    431                 if (regions.contains(mSelectedRegion)) {
    432                     message = String.format("%1$s (%2$s) is already translated in this project",
    433                             LocaleManager.getLanguageName(mSelectedLanguage),
    434                             LocaleManager.getRegionName(mSelectedRegion));
    435                 }
    436             }
    437         } else {
    438             // Require all strings to be translated? No, some of these may not
    439             // be translatable (e.g. translatable=false, defined in donottranslate.xml, etc.)
    440             //int missing = mTable.getItemCount() - mTranslations.values().size();
    441             //if (missing > 0) {
    442             //    message = String.format("Missing %1$d translations", missing);
    443             //}
    444         }
    445 
    446         boolean valid = message == null;
    447         mTable.setEnabled(message == null);
    448         mOkButton.setEnabled(valid);
    449         showError(message);
    450     }
    451 
    452     private void showError(String error) {
    453         GridData data = (GridData) mErrorPanel.getLayoutData();
    454 
    455         boolean show = error != null;
    456         if (show == data.exclude) {
    457             if (show) {
    458                 if (mErrorLabel == null) {
    459                     mErrorPanel.setLayout(new GridLayout(2, false));
    460                     IWorkbench workbench = PlatformUI.getWorkbench();
    461                     ISharedImages sharedImages = workbench.getSharedImages();
    462                     String iconName = ISharedImages.IMG_OBJS_ERROR_TSK;
    463                     Image image = sharedImages.getImage(iconName);
    464                     @SuppressWarnings("unused")
    465                     ImageControl icon = new ImageControl(mErrorPanel, SWT.NONE, image);
    466 
    467                     mErrorLabel = new Label(mErrorPanel, SWT.NONE);
    468                     mErrorLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false,
    469                             1, 1));
    470                 }
    471                 mErrorLabel.setText(error);
    472             }
    473             data.exclude = !show;
    474             mErrorPanel.getParent().layout(true);
    475         }
    476     }
    477 
    478     @Override
    479     protected void okPressed() {
    480         mTableViewer.applyEditorValue();
    481 
    482         super.okPressed();
    483         createTranslation();
    484     }
    485 
    486     // ---- Implements ControlListener ----
    487 
    488     @Override
    489     public void controlMoved(ControlEvent e) {
    490     }
    491 
    492     @Override
    493     public void controlResized(ControlEvent e) {
    494         if (mIgnore) {
    495             return;
    496         }
    497 
    498         updateColumnWidths();
    499     }
    500 
    501     // ---- Implements SelectionListener ----
    502 
    503     @Override
    504     public void widgetSelected(SelectionEvent e) {
    505         if (mIgnore) {
    506             return;
    507         }
    508 
    509         Object source = e.getSource();
    510         if (source == mLanguageCombo) {
    511             try {
    512                 mIgnore = true;
    513                 mRegionCombo.select(0);
    514                 mSelectedRegion = null;
    515             } finally {
    516                 mIgnore = false;
    517             }
    518 
    519             int languageIndex = mLanguageCombo.getSelectionIndex();
    520             if (languageIndex == 0) {
    521                 mSelectedLanguage = null;
    522                 mRegionCombo.setEnabled(false);
    523             } else {
    524                 // This depends on the label format
    525                 mSelectedLanguage = mLanguageCombo.getItem(languageIndex).substring(0, 2);
    526                 mRegionCombo.setEnabled(true);
    527             }
    528 
    529             updateTarget();
    530             updateFlag();
    531         } else if (source == mRegionCombo) {
    532             int regionIndex = mRegionCombo.getSelectionIndex();
    533             if (regionIndex == 0) {
    534                 mSelectedRegion = null;
    535             } else {
    536                 mSelectedRegion = mRegionCombo.getItem(regionIndex).substring(0, 2);
    537             }
    538 
    539             updateTarget();
    540             updateFlag();
    541         }
    542 
    543         try {
    544             mIgnore = true;
    545             validatePage();
    546         } finally {
    547             mIgnore = false;
    548         }
    549     }
    550 
    551     @Override
    552     public void widgetDefaultSelected(SelectionEvent e) {
    553     }
    554 
    555     // ---- TraverseListener ----
    556 
    557     @Override
    558     public void keyTraversed(TraverseEvent e) {
    559         // If you press Return and we're not cell editing, start editing the current row
    560         if (e.detail == SWT.TRAVERSE_RETURN && !mTableViewer.isCellEditorActive()) {
    561             int index = mTable.getSelectionIndex();
    562             if (index != -1) {
    563                 Object next = mTable.getItem(index).getData();
    564                 mTableViewer.editElement(next, NEW_TRANSLATION_COLUMN);
    565             }
    566         }
    567     }
    568 
    569     /** Editing support for the translation column */
    570     private class TranslationEditingSupport extends EditingSupport {
    571         /**
    572          * When true, setValue is being called as part of a default action
    573          * (e.g. Return), not due to focus loss
    574          */
    575         private boolean mDefaultAction;
    576 
    577         private TranslationEditingSupport(ColumnViewer viewer) {
    578             super(viewer);
    579         }
    580 
    581         @Override
    582         protected void setValue(Object element, Object value) {
    583             ResourceItem item = (ResourceItem) element;
    584             mTranslations.put(item.getName(), value.toString());
    585             mTableViewer.update(element, null);
    586             validatePage();
    587 
    588             // If the user is pressing Return to finish editing a value (which is
    589             // not the only way this method can get called - for example, if you click
    590             // outside the cell while editing, the focus loss will also result in
    591             // this method getting called), then mDefaultAction is true, and we automatically
    592             // start editing the next row.
    593             if (mDefaultAction) {
    594                 mTable.getDisplay().asyncExec(new Runnable() {
    595                     @Override
    596                     public void run() {
    597                         if (!mTable.isDisposed() && !mTableViewer.isCellEditorActive()) {
    598                             int index = mTable.getSelectionIndex();
    599                             if (index != -1 && index < mTable.getItemCount() - 1) {
    600                                 Object next = mTable.getItem(index + 1).getData();
    601                                 mTableViewer.editElement(next, NEW_TRANSLATION_COLUMN);
    602                             }
    603                         }
    604                     }
    605                 });
    606             }
    607         }
    608 
    609         @Override
    610         protected Object getValue(Object element) {
    611             ResourceItem item = (ResourceItem) element;
    612             String value = mTranslations.get(item.getName());
    613             if (value == null) {
    614                 return "";
    615             }
    616             return value;
    617         }
    618 
    619         @Override
    620         protected CellEditor getCellEditor(Object element) {
    621             return new TextCellEditor(mTable) {
    622                 @Override
    623                 protected void handleDefaultSelection(SelectionEvent event) {
    624                     try {
    625                         mDefaultAction = true;
    626                         super.handleDefaultSelection(event);
    627                     } finally {
    628                         mDefaultAction = false;
    629                     }
    630                 }
    631             };
    632         }
    633 
    634         @Override
    635         protected boolean canEdit(Object element) {
    636             return true;
    637         }
    638     }
    639 
    640     private class MyTableViewer extends TableViewer {
    641         public MyTableViewer(Composite parent, int style) {
    642             super(parent, style);
    643         }
    644 
    645         // Make this public so we can call it to ensure values are applied before the dialog
    646         // is dismissed in {@link #okPressed}
    647         @Override
    648         public void applyEditorValue() {
    649             super.applyEditorValue();
    650         }
    651     }
    652 }
    653