Home | History | Annotate | Download | only in gle2
      1 /*
      2  * Copyright (C) 2011 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.editors.layout.gle2;
     17 
     18 import static com.android.SdkConstants.ANDROID_LAYOUT_RESOURCE_PREFIX;
     19 import static com.android.SdkConstants.ANDROID_URI;
     20 import static com.android.SdkConstants.ATTR_CLASS;
     21 import static com.android.SdkConstants.ATTR_NAME;
     22 import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
     23 import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata.KEY_FRAGMENT_LAYOUT;
     24 
     25 import com.android.annotations.NonNull;
     26 import com.android.annotations.Nullable;
     27 import com.android.ide.eclipse.adt.AdtPlugin;
     28 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
     29 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
     30 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
     31 import com.android.ide.eclipse.adt.internal.resources.CyclicDependencyValidator;
     32 import com.android.ide.eclipse.adt.internal.ui.ResourceChooser;
     33 import com.android.resources.ResourceType;
     34 import com.android.utils.Pair;
     35 
     36 import org.eclipse.core.resources.IFile;
     37 import org.eclipse.core.resources.IProject;
     38 import org.eclipse.core.runtime.CoreException;
     39 import org.eclipse.jdt.core.IJavaProject;
     40 import org.eclipse.jdt.core.IType;
     41 import org.eclipse.jface.action.Action;
     42 import org.eclipse.jface.action.ActionContributionItem;
     43 import org.eclipse.jface.action.IAction;
     44 import org.eclipse.jface.action.Separator;
     45 import org.eclipse.jface.window.Window;
     46 import org.eclipse.swt.widgets.Menu;
     47 import org.w3c.dom.Element;
     48 import org.w3c.dom.Node;
     49 
     50 import java.util.ArrayList;
     51 import java.util.List;
     52 
     53 /**
     54  * Fragment context menu allowing a layout to be chosen for previewing in the fragment frame.
     55  */
     56 public class FragmentMenu extends SubmenuAction {
     57     private static final String R_LAYOUT_RESOURCE_PREFIX = "R.layout."; //$NON-NLS-1$
     58     private static final String ANDROID_R_PREFIX = "android.R.layout"; //$NON-NLS-1$
     59 
     60     /** Associated canvas */
     61     private final LayoutCanvas mCanvas;
     62 
     63     /**
     64      * Creates a "Preview Fragment" menu
     65      *
     66      * @param canvas associated canvas
     67      */
     68     public FragmentMenu(LayoutCanvas canvas) {
     69         super("Fragment Layout");
     70         mCanvas = canvas;
     71     }
     72 
     73     @Override
     74     protected void addMenuItems(Menu menu) {
     75         IAction action = new PickLayoutAction("Choose Layout...");
     76         new ActionContributionItem(action).fill(menu, -1);
     77 
     78         SelectionManager selectionManager = mCanvas.getSelectionManager();
     79         List<SelectionItem> selections = selectionManager.getSelections();
     80         if (selections.size() == 0) {
     81             return;
     82         }
     83 
     84         SelectionItem first = selections.get(0);
     85         UiViewElementNode node = first.getViewInfo().getUiViewNode();
     86         if (node == null) {
     87             return;
     88         }
     89         Element element = (Element) node.getXmlNode();
     90 
     91         String selected = getSelectedLayout();
     92         if (selected != null) {
     93             if (selected.startsWith(ANDROID_LAYOUT_RESOURCE_PREFIX)) {
     94                 selected = selected.substring(ANDROID_LAYOUT_RESOURCE_PREFIX.length());
     95             }
     96         }
     97 
     98         String fqcn = getFragmentClass(element);
     99         if (fqcn != null) {
    100             // Look up the corresponding activity class and try to figure out
    101             // which layouts it is referring to and list these here as reasonable
    102             // guesses
    103             IProject project = mCanvas.getEditorDelegate().getEditor().getProject();
    104             String source = null;
    105             try {
    106                 IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
    107                 IType type = javaProject.findType(fqcn);
    108                 if (type != null) {
    109                     source = type.getSource();
    110                 }
    111             } catch (CoreException e) {
    112                 AdtPlugin.log(e, null);
    113             }
    114             // Find layouts. This is based on just skimming the Fragment class and looking
    115             // for layout references of the form R.layout.*.
    116             if (source != null) {
    117                 String self = mCanvas.getLayoutResourceName();
    118                 // Pair of <title,layout> to be displayed to the user
    119                 List<Pair<String, String>> layouts = new ArrayList<Pair<String, String>>();
    120 
    121                 if (source.contains("extends ListFragment")) { //$NON-NLS-1$
    122                     layouts.add(Pair.of("list_content", //$NON-NLS-1$
    123                             "@android:layout/list_content")); //$NON-NLS-1$
    124                 }
    125 
    126                 int index = 0;
    127                 while (true) {
    128                     index = source.indexOf(R_LAYOUT_RESOURCE_PREFIX, index);
    129                     if (index == -1) {
    130                         break;
    131                     } else {
    132                         index += R_LAYOUT_RESOURCE_PREFIX.length();
    133                         int end = index;
    134                         while (end < source.length()) {
    135                             char c = source.charAt(end);
    136                             if (!Character.isJavaIdentifierPart(c)) {
    137                                 break;
    138                             }
    139                             end++;
    140                         }
    141                         if (end > index) {
    142                             String title = source.substring(index, end);
    143                             String layout;
    144                             // Is this R.layout part of an android.R.layout?
    145                             int len = ANDROID_R_PREFIX.length() + 1; // prefix length to check
    146                             if (index > len && source.startsWith(ANDROID_R_PREFIX, index - len)) {
    147                                 layout = ANDROID_LAYOUT_RESOURCE_PREFIX + title;
    148                             } else {
    149                                 layout = LAYOUT_RESOURCE_PREFIX + title;
    150                             }
    151                             if (!self.equals(title)) {
    152                                 layouts.add(Pair.of(title, layout));
    153                             }
    154                         }
    155                     }
    156 
    157                     index++;
    158                 }
    159 
    160                 if (layouts.size() > 0) {
    161                     new Separator().fill(menu, -1);
    162                     for (Pair<String, String> layout : layouts) {
    163                         action = new SetFragmentLayoutAction(layout.getFirst(),
    164                                 layout.getSecond(), selected);
    165                         new ActionContributionItem(action).fill(menu, -1);
    166                     }
    167                 }
    168             }
    169         }
    170 
    171         if (selected != null) {
    172             new Separator().fill(menu, -1);
    173             action = new SetFragmentLayoutAction("Clear", null, null);
    174             new ActionContributionItem(action).fill(menu, -1);
    175         }
    176     }
    177 
    178     /**
    179      * Returns the class name of the fragment associated with the given {@code <fragment>}
    180      * element.
    181      *
    182      * @param element the element for the fragment tag
    183      * @return the fully qualified fragment class name, or null
    184      */
    185     @Nullable
    186     public static String getFragmentClass(@NonNull Element element) {
    187         String fqcn = element.getAttribute(ATTR_CLASS);
    188         if (fqcn == null || fqcn.length() == 0) {
    189             fqcn = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
    190         }
    191         if (fqcn != null && fqcn.length() > 0) {
    192             return fqcn;
    193         } else {
    194             return null;
    195         }
    196     }
    197 
    198     /**
    199      * Returns the layout to be shown for the given {@code <fragment>} node.
    200      *
    201      * @param node the node corresponding to the {@code <fragment>} element
    202      * @return the resource path to a layout to render for this fragment, or null
    203      */
    204     @Nullable
    205     public static String getFragmentLayout(@NonNull Node node) {
    206         String layout = LayoutMetadata.getProperty(
    207                 node, LayoutMetadata.KEY_FRAGMENT_LAYOUT);
    208         if (layout != null) {
    209             return layout;
    210         }
    211 
    212         return null;
    213     }
    214 
    215     /** Returns the name of the currently displayed layout in the fragment, or null */
    216     @Nullable
    217     private String getSelectedLayout() {
    218         SelectionManager selectionManager = mCanvas.getSelectionManager();
    219         for (SelectionItem item : selectionManager.getSelections()) {
    220             UiViewElementNode node = item.getViewInfo().getUiViewNode();
    221             if (node != null) {
    222                 String layout = getFragmentLayout(node.getXmlNode());
    223                 if (layout != null) {
    224                     return layout;
    225                 }
    226             }
    227         }
    228         return null;
    229     }
    230 
    231     /**
    232      * Set the given layout as the new fragment layout
    233      *
    234      * @param layout the layout resource name to show in this fragment
    235      */
    236     public void setNewLayout(@Nullable String layout) {
    237         LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
    238         GraphicalEditorPart graphicalEditor = delegate.getGraphicalEditor();
    239         SelectionManager selectionManager = mCanvas.getSelectionManager();
    240 
    241         for (SelectionItem item : selectionManager.getSnapshot()) {
    242             UiViewElementNode node = item.getViewInfo().getUiViewNode();
    243             if (node != null) {
    244                 Node xmlNode = node.getXmlNode();
    245                 LayoutMetadata.setProperty(delegate.getEditor(), xmlNode, KEY_FRAGMENT_LAYOUT,
    246                         layout);
    247             }
    248         }
    249 
    250         // Refresh
    251         graphicalEditor.recomputeLayout();
    252         mCanvas.redraw();
    253     }
    254 
    255     /** Action to set the given layout as the new layout in a fragment */
    256     private class SetFragmentLayoutAction extends Action {
    257         private final String mLayout;
    258 
    259         public SetFragmentLayoutAction(String title, String layout, String selected) {
    260             super(title, IAction.AS_RADIO_BUTTON);
    261             mLayout = layout;
    262 
    263             if (layout != null && layout.equals(selected)) {
    264                 setChecked(true);
    265             }
    266         }
    267 
    268         @Override
    269         public void run() {
    270             if (isChecked()) {
    271                 setNewLayout(mLayout);
    272             }
    273         }
    274     }
    275 
    276     /**
    277      * Action which brings up the "Create new XML File" wizard, pre-selected with the
    278      * animation category
    279      */
    280     private class PickLayoutAction extends Action {
    281 
    282         public PickLayoutAction(String title) {
    283             super(title, IAction.AS_PUSH_BUTTON);
    284         }
    285 
    286         @Override
    287         public void run() {
    288             LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
    289             IFile file = delegate.getEditor().getInputFile();
    290             GraphicalEditorPart editor = delegate.getGraphicalEditor();
    291             ResourceChooser dlg = ResourceChooser.create(editor, ResourceType.LAYOUT)
    292                 .setInputValidator(CyclicDependencyValidator.create(file))
    293                 .setInitialSize(85, 10)
    294                 .setCurrentResource(getSelectedLayout());
    295             int result = dlg.open();
    296             if (result == ResourceChooser.CLEAR_RETURN_CODE) {
    297                 setNewLayout(null);
    298             } else if (result == Window.OK) {
    299                 String newType = dlg.getCurrentResource();
    300                 setNewLayout(newType);
    301             }
    302         }
    303     }
    304 }
    305