/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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.tools.idea.editors.strings; import com.android.ide.common.res2.ResourceItem; import com.android.tools.idea.configurations.LocaleMenuAction; import com.android.tools.idea.editors.strings.table.*; import com.android.tools.idea.rendering.LocalResourceRepository; import com.android.tools.idea.rendering.Locale; import com.google.common.collect.ImmutableSet; import com.intellij.icons.AllIcons; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.*; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBList; import com.intellij.ui.components.JBLoadingPanel; import com.intellij.ui.table.JBTable; import com.intellij.util.ui.UIUtil; import icons.AndroidIcons; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.text.JTextComponent; import java.awt.*; import java.awt.event.*; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; public class StringResourceViewPanel { private final AndroidFacet myFacet; private JPanel myContainer; private JBLoadingPanel myLoadingPanel; private JBTable myTable; private JTextField myKey; private TextFieldWithBrowseButton myDefaultValueWithBrowseBtn; private final JTextField myDefaultValue; private TextFieldWithBrowseButton myTranslationWithBrowseBtn; private final JTextField myTranslation; private JPanel myToolbarPanel; private LocalResourceRepository myResourceRepository; private long myModificationCount; private StringResourceData myData; private final StringResourceTableModel myTableModel; public StringResourceViewPanel(AndroidFacet facet, Disposable parentDisposable) { myFacet = facet; myLoadingPanel = new JBLoadingPanel(new BorderLayout(), parentDisposable, 200); myLoadingPanel.add(myContainer); ActionToolbar toolbar = createToolbar(); myToolbarPanel.add(toolbar.getComponent(), BorderLayout.CENTER); myToolbarPanel.setBorder(IdeBorderFactory.createBorder(SideBorder.BOTTOM)); myDefaultValueWithBrowseBtn.setButtonIcon(AllIcons.Actions.ShowViewer); myTranslationWithBrowseBtn.setButtonIcon(AllIcons.Actions.ShowViewer); ActionListener showMultilineActionListener = new ShowMultilineActionListener(); myDefaultValueWithBrowseBtn.addActionListener(showMultilineActionListener); myTranslationWithBrowseBtn.addActionListener(showMultilineActionListener); myDefaultValue = myDefaultValueWithBrowseBtn.getTextField(); myTranslation = myTranslationWithBrowseBtn.getTextField(); initEditPanel(); initTable(); myTableModel = new StringResourceTableModel(null); myTable.setModel(myTableModel); myLoadingPanel.setLoadingText("Loading string resource data"); myLoadingPanel.startLoading(); new ParseTask("Loading string resource data").queue(); } public void reloadData() { myLoadingPanel.setLoadingText("Updating string resource data"); myLoadingPanel.startLoading(); new ParseTask("Updating string resource data").queue(); } private ActionToolbar createToolbar() { DefaultActionGroup group = new DefaultActionGroup(); final ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true); final AnAction addKeyAction = new AnAction("Add Key", "", AllIcons.ToolbarDecorator.Add) { @Override public void update(AnActionEvent e) { e.getPresentation().setEnabled(myData != null); } @Override public void actionPerformed(AnActionEvent e) { NewStringKeyDialog dialog = new NewStringKeyDialog(myFacet, ImmutableSet.copyOf(myData.getKeys())); if (dialog.showAndGet()) { StringsWriteUtils.createItem(myFacet, dialog.getResFolder(), null, dialog.getKey(), dialog.getDefaultValue(), true); reloadData(); } } }; group.add(addKeyAction); final AnAction addLocaleAction = new AnAction("Add Locale", "", AndroidIcons.Globe) { @Override public void update(AnActionEvent e) { e.getPresentation().setEnabled(myData != null); } @Override public void actionPerformed(AnActionEvent e) { List<Locale> currentLocales = myData.getLocales(); List<Locale> missingLocales = LocaleMenuAction.getAllLocales(); missingLocales.removeAll(currentLocales); Collections.sort(missingLocales, Locale.LANGUAGE_NAME_COMPARATOR); final JBList list = new JBList(missingLocales); list.setFixedCellHeight(20); list.setCellRenderer(new ColoredListCellRenderer<Locale>() { @Override protected void customizeCellRenderer(JList list, Locale value, int index, boolean selected, boolean hasFocus) { append(LocaleMenuAction.getLocaleLabel(value, false)); setIcon(value.getFlagImage()); } }); new ListSpeedSearch(list) { @Override protected String getElementText(Object element) { if (element instanceof Locale) { return LocaleMenuAction.getLocaleLabel((Locale)element, false); } return super.getElementText(element); } }; JBPopupFactory.getInstance().createListPopupBuilder(list).setItemChoosenCallback(new Runnable() { @Override public void run() { // pick some value to add to this locale Map<String, ResourceItem> defaultValues = myData.getDefaultValues(); String key = "app_name"; ResourceItem defaultValue = defaultValues.get(key); if (defaultValue == null) { Map.Entry<String, ResourceItem> firstEntry = defaultValues.entrySet().iterator().next(); key = firstEntry.getKey(); defaultValue = firstEntry.getValue(); } Locale l = (Locale)list.getSelectedValue(); StringsWriteUtils.createItem(myFacet, myFacet.getPrimaryResourceDir(), l, key, StringResourceData.resourceToString(defaultValue), true); reloadData(); } }).createPopup().showUnderneathOf(toolbar.getComponent()); } }; group.addAction(addLocaleAction); return toolbar; } public boolean dataIsCurrent() { return myResourceRepository != null && myModificationCount >= myResourceRepository.getModificationCount(); } private void initEditPanel() { KeyListener keyListener = new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { JTextComponent component = (JTextComponent)e.getComponent(); if (component.isEditable()) { onTextFieldUpdate(component); } } }; myKey.addKeyListener(keyListener); myDefaultValue.addKeyListener(keyListener); myTranslation.addKeyListener(keyListener); } private void onTextFieldUpdate(JTextComponent component) { StringResourceTableModel model = (StringResourceTableModel)myTable.getModel(); if (myTable.getSelectedColumnCount() != 1 || myTable.getSelectedRowCount() != 1) { return; } int row = myTable.getSelectedRow(); int column; if (component == myKey) { column = ConstantColumn.KEY.ordinal(); } else if (component == myDefaultValue) { column = ConstantColumn.DEFAULT_VALUE.ordinal(); } else { assert component == myTranslation; column = myTable.getSelectedColumn(); } String value = component.getText(); model.setValueAt(value, row, column); } private void initTable() { myTable.setCellSelectionEnabled(true); myTable.getTableHeader().setReorderingAllowed(false); MouseAdapter headerListener = new HeaderCellSelectionListener(myTable); myTable.getTableHeader().addMouseListener(headerListener); myTable.getTableHeader().addMouseMotionListener(headerListener); CellSelectionListener selectionListener = new CellSelectionListener(); myTable.getSelectionModel().addListSelectionListener(selectionListener); myTable.getColumnModel().getSelectionModel().addListSelectionListener(selectionListener); myTable.setDefaultEditor(String.class, new StringsCellEditor()); myTable.getParent().addComponentListener(new ResizeListener(myTable)); new TableSpeedSearch(myTable); } @NotNull public JPanel getComponent() { return myLoadingPanel; } @NotNull public JBTable getPreferredFocusedComponent() { return myTable; } private void createUIComponents() { myTable = new JBTable(); } public JTable getTable() { return myTable; } private class ParseTask extends Task.Backgroundable { private AtomicReference<LocalResourceRepository> myResourceRepositoryRef = new AtomicReference<LocalResourceRepository>(null); private AtomicReference<StringResourceData> myResourceDataRef = new AtomicReference<StringResourceData>(null); public ParseTask(String description) { super(myFacet.getModule().getProject(), description, false); } @Override public void run(@NotNull ProgressIndicator indicator) { indicator.setIndeterminate(true); LocalResourceRepository moduleResources = myFacet.getModuleResources(true); myResourceRepositoryRef.set(moduleResources); myResourceDataRef.set(StringResourceParser.parse(myFacet, moduleResources)); } @Override public void onSuccess() { myLoadingPanel.stopLoading(); myData = myResourceDataRef.get(); myResourceRepository = myResourceRepositoryRef.get(); myModificationCount = myResourceRepository.getModificationCount(); myTableModel.setData(myData); myTableModel.fireTableStructureChanged(); ColumnUtil.setColumns(myTable); } @Override public void onCancel() { myLoadingPanel.stopLoading(); } } private class CellSelectionListener implements ListSelectionListener { @Override public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) { return; } if (myTable.getSelectedColumnCount() != 1 || myTable.getSelectedRowCount() != 1) { setTextAndEditable(myKey, "", false); setTextAndEditable(myDefaultValue, "", false); setTextAndEditable(myTranslation, "", false); myDefaultValueWithBrowseBtn.getButton().setEnabled(false); myTranslationWithBrowseBtn.getButton().setEnabled(false); return; } StringResourceTableModel model = (StringResourceTableModel) myTable.getModel(); int row = myTable.getSelectedRow(); int column = myTable.getSelectedColumn(); Locale locale = model.localeOfColumn(column); String key = model.keyOfRow(row); setTextAndEditable(myKey, key, false); // TODO: keys are not editable, we want them to be refactor operations String defaultValue = (String)model.getValueAt(row, ConstantColumn.DEFAULT_VALUE.ordinal()); boolean defaultValueEditable = !StringUtil.containsChar(defaultValue, '\n'); // don't allow editing multiline chars in a text field setTextAndEditable(myDefaultValue, defaultValue, defaultValueEditable); myDefaultValueWithBrowseBtn.getButton().setEnabled(true); boolean translationEditable = false; String translation = ""; if (locale != null) { translation = (String)model.getValueAt(row, column); translationEditable = !StringUtil.containsChar(translation, '\n'); // don't allow editing multiline chars in a text field } setTextAndEditable(myTranslation, translation, translationEditable); myTranslationWithBrowseBtn.getButton().setEnabled(locale != null); } private void setTextAndEditable(@NotNull JTextComponent component, @NotNull String text, boolean editable) { component.setText(text); component.setCaretPosition(0); component.setEditable(editable); // If a text component is not editable when it gains focus and becomes editable while still focused, // the caret does not appear, so we need to set the caret visibility manually component.getCaret().setVisible(editable && component.hasFocus()); component.setFont(FontUtil.getFontAbleToDisplay(text, component.getFont())); } } private class ShowMultilineActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { if (myTable.getSelectedRowCount() != 1 || myTable.getSelectedColumnCount() != 1) { return; } int row = myTable.getSelectedRow(); int column = myTable.getSelectedColumn(); StringResourceTableModel model = (StringResourceTableModel)myTable.getModel(); String key = model.keyOfRow(row); String value = (String)model.getValueAt(row, ConstantColumn.DEFAULT_VALUE.ordinal()); Locale locale = model.localeOfColumn(column); String translation = locale == null ? null : (String)model.getValueAt(row, column); MultilineStringEditorDialog d = new MultilineStringEditorDialog(myFacet, key, value, locale, translation); if (d.showAndGet()) { if (!StringUtil.equals(value, d.getDefaultValue())) { model.setValueAt(d.getDefaultValue(), row, ConstantColumn.DEFAULT_VALUE.ordinal()); } if (locale != null && !StringUtil.equals(translation, d.getTranslation())) { model.setValueAt(d.getTranslation(), row, column); } } } } }