aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/FragmentMenu.java
blob: f7085fc12d9e904b55d29f13776ae5fe904a0851 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
/*
 * Copyright (C) 2011 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.internal.editors.layout.gle2;

import static com.android.SdkConstants.ANDROID_LAYOUT_RESOURCE_PREFIX;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_CLASS;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata.KEY_FRAGMENT_LAYOUT;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.resources.CyclicDependencyValidator;
import com.android.ide.eclipse.adt.internal.ui.ResourceChooser;
import com.android.resources.ResourceType;
import com.android.utils.Pair;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Menu;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import java.util.ArrayList;
import java.util.List;

/**
 * Fragment context menu allowing a layout to be chosen for previewing in the fragment frame.
 */
public class FragmentMenu extends SubmenuAction {
    private static final String R_LAYOUT_RESOURCE_PREFIX = "R.layout."; //$NON-NLS-1$
    private static final String ANDROID_R_PREFIX = "android.R.layout"; //$NON-NLS-1$

    /** Associated canvas */
    private final LayoutCanvas mCanvas;

    /**
     * Creates a "Preview Fragment" menu
     *
     * @param canvas associated canvas
     */
    public FragmentMenu(LayoutCanvas canvas) {
        super("Fragment Layout");
        mCanvas = canvas;
    }

    @Override
    protected void addMenuItems(Menu menu) {
        IAction action = new PickLayoutAction("Choose Layout...");
        new ActionContributionItem(action).fill(menu, -1);

        SelectionManager selectionManager = mCanvas.getSelectionManager();
        List<SelectionItem> selections = selectionManager.getSelections();
        if (selections.size() == 0) {
            return;
        }

        SelectionItem first = selections.get(0);
        UiViewElementNode node = first.getViewInfo().getUiViewNode();
        if (node == null) {
            return;
        }
        Element element = (Element) node.getXmlNode();

        String selected = getSelectedLayout();
        if (selected != null) {
            if (selected.startsWith(ANDROID_LAYOUT_RESOURCE_PREFIX)) {
                selected = selected.substring(ANDROID_LAYOUT_RESOURCE_PREFIX.length());
            }
        }

        String fqcn = getFragmentClass(element);
        if (fqcn != null) {
            // Look up the corresponding activity class and try to figure out
            // which layouts it is referring to and list these here as reasonable
            // guesses
            IProject project = mCanvas.getEditorDelegate().getEditor().getProject();
            String source = null;
            try {
                IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
                IType type = javaProject.findType(fqcn);
                if (type != null) {
                    source = type.getSource();
                }
            } catch (CoreException e) {
                AdtPlugin.log(e, null);
            }
            // Find layouts. This is based on just skimming the Fragment class and looking
            // for layout references of the form R.layout.*.
            if (source != null) {
                String self = mCanvas.getLayoutResourceName();
                // Pair of <title,layout> to be displayed to the user
                List<Pair<String, String>> layouts = new ArrayList<Pair<String, String>>();

                if (source.contains("extends ListFragment")) { //$NON-NLS-1$
                    layouts.add(Pair.of("list_content", //$NON-NLS-1$
                            "@android:layout/list_content")); //$NON-NLS-1$
                }

                int index = 0;
                while (true) {
                    index = source.indexOf(R_LAYOUT_RESOURCE_PREFIX, index);
                    if (index == -1) {
                        break;
                    } else {
                        index += R_LAYOUT_RESOURCE_PREFIX.length();
                        int end = index;
                        while (end < source.length()) {
                            char c = source.charAt(end);
                            if (!Character.isJavaIdentifierPart(c)) {
                                break;
                            }
                            end++;
                        }
                        if (end > index) {
                            String title = source.substring(index, end);
                            String layout;
                            // Is this R.layout part of an android.R.layout?
                            int len = ANDROID_R_PREFIX.length() + 1; // prefix length to check
                            if (index > len && source.startsWith(ANDROID_R_PREFIX, index - len)) {
                                layout = ANDROID_LAYOUT_RESOURCE_PREFIX + title;
                            } else {
                                layout = LAYOUT_RESOURCE_PREFIX + title;
                            }
                            if (!self.equals(title)) {
                                layouts.add(Pair.of(title, layout));
                            }
                        }
                    }

                    index++;
                }

                if (layouts.size() > 0) {
                    new Separator().fill(menu, -1);
                    for (Pair<String, String> layout : layouts) {
                        action = new SetFragmentLayoutAction(layout.getFirst(),
                                layout.getSecond(), selected);
                        new ActionContributionItem(action).fill(menu, -1);
                    }
                }
            }
        }

        if (selected != null) {
            new Separator().fill(menu, -1);
            action = new SetFragmentLayoutAction("Clear", null, null);
            new ActionContributionItem(action).fill(menu, -1);
        }
    }

    /**
     * Returns the class name of the fragment associated with the given {@code <fragment>}
     * element.
     *
     * @param element the element for the fragment tag
     * @return the fully qualified fragment class name, or null
     */
    @Nullable
    public static String getFragmentClass(@NonNull Element element) {
        String fqcn = element.getAttribute(ATTR_CLASS);
        if (fqcn == null || fqcn.length() == 0) {
            fqcn = element.getAttributeNS(ANDROID_URI, ATTR_NAME);
        }
        if (fqcn != null && fqcn.length() > 0) {
            return fqcn;
        } else {
            return null;
        }
    }

    /**
     * Returns the layout to be shown for the given {@code <fragment>} node.
     *
     * @param node the node corresponding to the {@code <fragment>} element
     * @return the resource path to a layout to render for this fragment, or null
     */
    @Nullable
    public static String getFragmentLayout(@NonNull Node node) {
        String layout = LayoutMetadata.getProperty(
                node, LayoutMetadata.KEY_FRAGMENT_LAYOUT);
        if (layout != null) {
            return layout;
        }

        return null;
    }

    /** Returns the name of the currently displayed layout in the fragment, or null */
    @Nullable
    private String getSelectedLayout() {
        SelectionManager selectionManager = mCanvas.getSelectionManager();
        for (SelectionItem item : selectionManager.getSelections()) {
            UiViewElementNode node = item.getViewInfo().getUiViewNode();
            if (node != null) {
                String layout = getFragmentLayout(node.getXmlNode());
                if (layout != null) {
                    return layout;
                }
            }
        }
        return null;
    }

    /**
     * Set the given layout as the new fragment layout
     *
     * @param layout the layout resource name to show in this fragment
     */
    public void setNewLayout(@Nullable String layout) {
        LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
        GraphicalEditorPart graphicalEditor = delegate.getGraphicalEditor();
        SelectionManager selectionManager = mCanvas.getSelectionManager();

        for (SelectionItem item : selectionManager.getSnapshot()) {
            UiViewElementNode node = item.getViewInfo().getUiViewNode();
            if (node != null) {
                Node xmlNode = node.getXmlNode();
                LayoutMetadata.setProperty(delegate.getEditor(), xmlNode, KEY_FRAGMENT_LAYOUT,
                        layout);
            }
        }

        // Refresh
        graphicalEditor.recomputeLayout();
        mCanvas.redraw();
    }

    /** Action to set the given layout as the new layout in a fragment */
    private class SetFragmentLayoutAction extends Action {
        private final String mLayout;

        public SetFragmentLayoutAction(String title, String layout, String selected) {
            super(title, IAction.AS_RADIO_BUTTON);
            mLayout = layout;

            if (layout != null && layout.equals(selected)) {
                setChecked(true);
            }
        }

        @Override
        public void run() {
            if (isChecked()) {
                setNewLayout(mLayout);
            }
        }
    }

    /**
     * Action which brings up the "Create new XML File" wizard, pre-selected with the
     * animation category
     */
    private class PickLayoutAction extends Action {

        public PickLayoutAction(String title) {
            super(title, IAction.AS_PUSH_BUTTON);
        }

        @Override
        public void run() {
            LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
            IFile file = delegate.getEditor().getInputFile();
            GraphicalEditorPart editor = delegate.getGraphicalEditor();
            ResourceChooser dlg = ResourceChooser.create(editor, ResourceType.LAYOUT)
                .setInputValidator(CyclicDependencyValidator.create(file))
                .setInitialSize(85, 10)
                .setCurrentResource(getSelectedLayout());
            int result = dlg.open();
            if (result == ResourceChooser.CLEAR_RETURN_CODE) {
                setNewLayout(null);
            } else if (result == Window.OK) {
                String newType = dlg.getCurrentResource();
                setNewLayout(newType);
            }
        }
    }
}