2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package org
.intellij
.plugins
.intelliLang
;
19 import com
.intellij
.lang
.Language
;
20 import com
.intellij
.openapi
.actionSystem
.*;
21 import com
.intellij
.openapi
.fileChooser
.FileChooserDescriptor
;
22 import com
.intellij
.openapi
.fileChooser
.FileChooserDialog
;
23 import com
.intellij
.openapi
.fileChooser
.FileChooserFactory
;
24 import com
.intellij
.openapi
.fileTypes
.FileType
;
25 import com
.intellij
.openapi
.fileTypes
.StdFileTypes
;
26 import com
.intellij
.openapi
.options
.Configurable
;
27 import com
.intellij
.openapi
.project
.Project
;
28 import com
.intellij
.openapi
.ui
.Messages
;
29 import com
.intellij
.openapi
.ui
.SplitterProportionsData
;
30 import com
.intellij
.openapi
.ui
.popup
.JBPopupFactory
;
31 import com
.intellij
.openapi
.util
.Comparing
;
32 import com
.intellij
.openapi
.util
.Condition
;
33 import com
.intellij
.openapi
.util
.Factory
;
34 import com
.intellij
.openapi
.util
.IconLoader
;
35 import com
.intellij
.openapi
.util
.text
.StringUtil
;
36 import com
.intellij
.openapi
.vfs
.VirtualFile
;
37 import com
.intellij
.peer
.PeerFactory
;
38 import com
.intellij
.ui
.*;
39 import com
.intellij
.ui
.table
.TableView
;
40 import com
.intellij
.ui
.tabs
.BetterJTable
;
41 import com
.intellij
.util
.Consumer
;
42 import com
.intellij
.util
.Function
;
43 import com
.intellij
.util
.Icons
;
44 import com
.intellij
.util
.Processor
;
45 import com
.intellij
.util
.containers
.ContainerUtil
;
46 import com
.intellij
.util
.ui
.ColumnInfo
;
47 import com
.intellij
.util
.ui
.ListTableModel
;
48 import gnu
.trove
.THashMap
;
49 import org
.intellij
.plugins
.intelliLang
.inject
.InjectedLanguage
;
50 import org
.intellij
.plugins
.intelliLang
.inject
.InjectorUtils
;
51 import org
.intellij
.plugins
.intelliLang
.inject
.LanguageInjectionSupport
;
52 import org
.intellij
.plugins
.intelliLang
.inject
.config
.BaseInjection
;
53 import org
.jetbrains
.annotations
.Nls
;
54 import org
.jetbrains
.annotations
.Nullable
;
57 import javax
.swing
.table
.TableCellRenderer
;
59 import java
.awt
.event
.KeyEvent
;
61 import java
.util
.List
;
64 * @author Gregory.Shrago
66 public class InjectionsSettingsUI
implements Configurable
{
68 private final Project myProject
;
69 private final Configuration myConfiguration
;
70 private List
<BaseInjection
> myInjections
;
71 private List
<BaseInjection
> myOriginalInjections
;
74 private final JPanel myRoot
;
75 private final InjectionsTable myInjectionsTable
;
76 private final Map
<String
, LanguageInjectionSupport
> mySupports
= new THashMap
<String
, LanguageInjectionSupport
>();
77 private final Map
<String
, AnAction
> myEditActions
= new THashMap
<String
, AnAction
>();
78 private final List
<AnAction
> myAddActions
= new ArrayList
<AnAction
>();
79 private ActionToolbar myToolbar
;
81 public InjectionsSettingsUI(final Project project
, final Configuration configuration
) {
83 myConfiguration
= configuration
;
85 myOriginalInjections
= ContainerUtil
86 .concat(InjectorUtils
.getActiveInjectionSupportIds(), new Function
<String
, Collection
<?
extends BaseInjection
>>() {
87 public Collection
<?
extends BaseInjection
> fun(final String s
) {
88 return ContainerUtil
.findAll(myConfiguration
.getInjections(s
), new Condition
<BaseInjection
>() {
89 public boolean value(final BaseInjection injection
) {
90 return InjectedLanguage
.findLanguageById(injection
.getInjectedLanguageId()) != null;
95 sortInjections(myOriginalInjections
);
96 myInjections
= new ArrayList
<BaseInjection
>();
97 for (BaseInjection injection
: myOriginalInjections
) {
98 myInjections
.add(injection
.copy());
101 myRoot
= new JPanel(new BorderLayout());
103 myInjectionsTable
= new InjectionsTable(myInjections
);
104 final JPanel tablePanel
= new JPanel(new BorderLayout());
105 //tablePanel.setBorder(BorderFactory.createTitledBorder("Available Injections"));
106 tablePanel
.add(BetterJTable
.createStripedJScrollPane(myInjectionsTable
), BorderLayout
.CENTER
);
107 tablePanel
.add(Box
.createVerticalStrut(10), BorderLayout
.SOUTH
);
109 final DefaultActionGroup group
= createActions();
111 myToolbar
= ActionManager
.getInstance().createActionToolbar(ActionPlaces
.UNKNOWN
, group
, true);
112 myToolbar
.setTargetComponent(myInjectionsTable
);
113 myRoot
.add(myToolbar
.getComponent(), BorderLayout
.NORTH
);
114 myRoot
.add(tablePanel
, BorderLayout
.CENTER
);
117 private DefaultActionGroup
createActions() {
118 final Consumer
<BaseInjection
> consumer
= new Consumer
<BaseInjection
>() {
119 public void consume(final BaseInjection injection
) {
120 addInjection(injection
);
123 final Factory
<BaseInjection
> producer
= new Factory
<BaseInjection
>() {
124 public BaseInjection
create() {
125 return getSelectedInjection();
128 for (LanguageInjectionSupport support
: InjectorUtils
.getActiveInjectionSupports()) {
129 myAddActions
.addAll(Arrays
.asList(support
.createAddActions(myProject
, consumer
)));
130 ContainerUtil
.putIfNotNull(support
.getId(), support
.createEditAction(myProject
, producer
), myEditActions
);
131 mySupports
.put(support
.getId(), support
);
134 final DefaultActionGroup group
= new DefaultActionGroup();
135 final AnAction addAction
= new AnAction("Add", "Add", Icons
.ADD_ICON
) {
137 public void update(final AnActionEvent e
) {
138 e
.getPresentation().setEnabled(!myAddActions
.isEmpty());
142 public void actionPerformed(final AnActionEvent e
) {
146 final AnAction removeAction
= new AnAction("Remove", "Remove", Icons
.DELETE_ICON
) {
148 public void update(final AnActionEvent e
) {
149 e
.getPresentation().setEnabled(!getSelectedInjections().isEmpty());
153 public void actionPerformed(final AnActionEvent e
) {
158 final AnAction editAction
= new AnAction("Edit", "Edit", Icons
.PROPERTIES_ICON
) {
160 public void update(final AnActionEvent e
) {
161 final AnAction action
= getEditAction();
162 e
.getPresentation().setEnabled(action
!= null);
163 if (action
!= null) action
.update(e
);
167 public void actionPerformed(final AnActionEvent e
) {
168 final AnAction action
= getEditAction();
169 action
.actionPerformed(e
);
172 group
.add(addAction
);
173 group
.add(removeAction
);
174 group
.add(editAction
);
176 addAction
.registerCustomShortcutSet(CommonShortcuts
.INSERT
, myInjectionsTable
);
177 removeAction
.registerCustomShortcutSet(CommonShortcuts
.DELETE
, myInjectionsTable
);
178 editAction
.registerCustomShortcutSet(CommonShortcuts
.ENTER
, myInjectionsTable
);
180 group
.add(new AnAction("Import", "Import", IconLoader
.getIcon("/actions/install.png")) {
182 public void actionPerformed(final AnActionEvent e
) {
183 doImportAction(e
.getDataContext());
186 group
.addSeparator();
187 group
.add(new AnAction("Enabled Selected Injections", "Enabled Selected Injections", Icons
.SELECT_ALL_ICON
) {
189 public void actionPerformed(final AnActionEvent e
) {
190 performSelectedInjectionsEnabled(true);
193 group
.add(new AnAction("Disabled Selected Injections", "Disabled Selected Injections", Icons
.UNSELECT_ALL_ICON
) {
195 public void actionPerformed(final AnActionEvent e
) {
196 performSelectedInjectionsEnabled(false);
200 new AnAction("Toggle") {
202 public void actionPerformed(final AnActionEvent e
) {
203 performToggleAction();
205 }.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke
.getKeyStroke(KeyEvent
.VK_SPACE
, 0)), myInjectionsTable
);
209 private AnAction
getEditAction() {
210 final BaseInjection injection
= getSelectedInjection();
211 final String supportId
= injection
== null?
null : injection
.getSupportId();
212 return supportId
== null?
null : myEditActions
.get(supportId
);
215 private void addInjection(final BaseInjection injection
) {
216 injection
.initializePlaces(true);
217 myInjections
.add(injection
);
218 ((ListTableModel
<BaseInjection
>)myInjectionsTable
.getModel()).setItems(myInjections
);
219 final int index
= myInjections
.indexOf(injection
);
220 myInjectionsTable
.getSelectionModel().setSelectionInterval(index
, index
);
221 TableUtil
.scrollSelectionToVisible(myInjectionsTable
);
224 private void sortInjections(final List
<BaseInjection
> injections
) {
225 Collections
.sort(injections
, new Comparator
<BaseInjection
>() {
226 public int compare(final BaseInjection o1
, final BaseInjection o2
) {
227 final int support
= Comparing
.compare(o1
.getSupportId(), o2
.getSupportId());
228 if (support
!= 0) return support
;
229 final int lang
= Comparing
.compare(o1
.getInjectedLanguageId(), o2
.getInjectedLanguageId());
230 if (lang
!= 0) return lang
;
231 return Comparing
.compare(o1
.getDisplayName(), o2
.getDisplayName());
236 public JComponent
createComponent() {
240 public void reset() {
241 myInjections
.clear();
242 for (BaseInjection injection
: myOriginalInjections
) {
243 myInjections
.add(injection
.copy());
245 ((ListTableModel
<BaseInjection
>)myInjectionsTable
.getModel()).setItems(myInjections
);
248 public void disposeUIResources() {
251 public void apply() {
252 myConfiguration
.replaceInjections(myInjections
, myOriginalInjections
);
253 myOriginalInjections
.clear();
254 myOriginalInjections
.addAll(myInjections
);
255 sortInjections(myOriginalInjections
);
259 public boolean isModified() {
260 final List
<BaseInjection
> copy
= new ArrayList
<BaseInjection
>(myInjections
);
261 sortInjections(copy
);
262 return !myOriginalInjections
.equals(copy
);
265 private void performSelectedInjectionsEnabled(final boolean enabled
) {
266 for (BaseInjection injection
: getSelectedInjections()) {
267 injection
.setPlaceEnabled(null, enabled
);
269 myInjectionsTable
.updateUI();
272 private void performToggleAction() {
273 final List
<BaseInjection
> selectedInjections
= getSelectedInjections();
274 boolean enabledExists
= false;
275 boolean disabledExists
= false;
276 for (BaseInjection injection
: selectedInjections
) {
277 if (injection
.isEnabled()) enabledExists
= true;
278 else disabledExists
= true;
279 if (enabledExists
&& disabledExists
) break;
281 boolean allEnabled
= !enabledExists
&& disabledExists
;
282 performSelectedInjectionsEnabled(allEnabled
);
285 private void performRemove() {
286 final int selectedRow
= myInjectionsTable
.getSelectedRow();
287 if (selectedRow
< 0) return;
288 myInjections
.removeAll(getSelectedInjections());
289 ((ListTableModel
)myInjectionsTable
.getModel()).fireTableDataChanged();
290 final int index
= Math
.min(myInjections
.size() - 1, selectedRow
);
291 myInjectionsTable
.getSelectionModel().setSelectionInterval(index
, index
);
292 TableUtil
.scrollSelectionToVisible(myInjectionsTable
);
295 private List
<BaseInjection
> getSelectedInjections() {
296 final ArrayList
<BaseInjection
> toRemove
= new ArrayList
<BaseInjection
>();
297 for (int row
: myInjectionsTable
.getSelectedRows()) {
298 toRemove
.add((BaseInjection
)myInjectionsTable
.getItems().get(row
));
304 private BaseInjection
getSelectedInjection() {
305 final int row
= myInjectionsTable
.getSelectedRow();
306 return row
< 0?
null : (BaseInjection
)myInjectionsTable
.getItems().get(row
);
309 private void performAdd(final AnActionEvent e
) {
310 final DefaultActionGroup group
= new DefaultActionGroup();
311 for (AnAction action
: myAddActions
) {
315 JBPopupFactory
.getInstance().createActionGroupPopup(null, group
, e
.getDataContext(), JBPopupFactory
.ActionSelectionAid
.NUMBERING
, true)
316 .showUnderneathOf(myToolbar
.getComponent());
320 public String
getDisplayName() {
324 public Icon
getIcon() {
328 public String
getHelpTopic() {
332 private class InjectionsTable
extends TableView
{
333 private InjectionsTable(final List
<BaseInjection
> injections
) {
334 super(new ListTableModel
<BaseInjection
>(createInjectionColumnInfos(), injections
, -1));
335 setAutoResizeMode(AUTO_RESIZE_LAST_COLUMN
);
336 getColumnModel().getColumn(2).setCellRenderer(createLanguageCellRenderer());
337 getColumnModel().getColumn(1).setCellRenderer(createDisplayNameCellRenderer());
338 getColumnModel().getColumn(0).setResizable(false);
340 setShowVerticalLines(false);
341 setGridColor(getForeground());
343 getColumnModel().getColumn(0).setMaxWidth(new JCheckBox().getPreferredSize().width
);
344 final int[] preffered
= new int[] {0} ;
345 ContainerUtil
.process(myInjections
, new Processor
<BaseInjection
>() {
346 public boolean process(final BaseInjection injection
) {
347 final String languageId
= injection
.getInjectedLanguageId();
348 if (preffered
[0] < languageId
.length()) preffered
[0] = languageId
.length();
352 final int[] max
= new int[] {0} ;
353 ContainerUtil
.process(InjectedLanguage
.getAvailableLanguageIDs(), new Processor
<String
>() {
354 public boolean process(final String languageId
) {
355 if (max
[0] < languageId
.length()) max
[0] = languageId
.length();
359 getColumnModel().getColumn(2).setResizable(false);
360 final Icon icon
= StdFileTypes
.PLAIN_TEXT
.getIcon();
361 final int preferred
= new JLabel(StringUtil
.repeatSymbol('m', preffered
[0]), icon
, SwingConstants
.LEFT
).getPreferredSize().width
;
362 getColumnModel().getColumn(2).setMinWidth(preferred
);
363 getColumnModel().getColumn(2).setPreferredWidth(preferred
);
364 getColumnModel().getColumn(2).setMaxWidth(new JLabel(StringUtil
.repeatSymbol('m', max
[0]), icon
, SwingConstants
.LEFT
).getPreferredSize().width
);
365 new TableViewSpeedSearch(this) {
368 protected String
getElementText(final Object element
) {
369 final BaseInjection injection
= (BaseInjection
)element
;
370 return injection
.getSupportId() + " " + injection
.getInjectedLanguageId() + " " + injection
.getDisplayName();
377 private ColumnInfo
[] createInjectionColumnInfos() {
378 final TableCellRenderer booleanCellRenderer
= createBooleanCellRenderer();
379 final TableCellRenderer displayNameCellRenderer
= createDisplayNameCellRenderer();
380 final TableCellRenderer languageCellRenderer
= createLanguageCellRenderer();
381 final Comparator
<BaseInjection
> languageComparator
= new Comparator
<BaseInjection
>() {
382 public int compare(final BaseInjection o1
, final BaseInjection o2
) {
383 return Comparing
.compare(o1
.getInjectedLanguageId(), o2
.getInjectedLanguageId());
386 final Comparator
<BaseInjection
> displayNameComparator
= new Comparator
<BaseInjection
>() {
387 public int compare(final BaseInjection o1
, final BaseInjection o2
) {
388 final int support
= Comparing
.compare(o1
.getSupportId(), o2
.getSupportId());
389 if (support
!= 0) return support
;
390 return Comparing
.compare(o1
.getDisplayName(), o2
.getDisplayName());
393 return new ColumnInfo
[]{new ColumnInfo
<BaseInjection
, Boolean
>(" ") {
395 public Class
getColumnClass() {
396 return Boolean
.class;
400 public Boolean
valueOf(final BaseInjection o
) {
401 return o
.isEnabled();
405 public boolean isCellEditable(final BaseInjection injection
) {
410 public void setValue(final BaseInjection injection
, final Boolean value
) {
411 injection
.setPlaceEnabled(null, value
.booleanValue());
415 public TableCellRenderer
getRenderer(final BaseInjection injection
) {
416 return booleanCellRenderer
;
418 }, new ColumnInfo
<BaseInjection
, BaseInjection
>("Display Name") {
420 public BaseInjection
valueOf(final BaseInjection injection
) {
425 public Comparator
<BaseInjection
> getComparator() {
426 return displayNameComparator
;
430 public TableCellRenderer
getRenderer(final BaseInjection injection
) {
431 return displayNameCellRenderer
;
433 }, new ColumnInfo
<BaseInjection
, BaseInjection
>("Language") {
435 public BaseInjection
valueOf(final BaseInjection injection
) {
440 public Comparator
<BaseInjection
> getComparator() {
441 return languageComparator
;
445 public TableCellRenderer
getRenderer(final BaseInjection injection
) {
446 return languageCellRenderer
;
451 private static BooleanTableCellRenderer
createBooleanCellRenderer() {
452 return new BooleanTableCellRenderer() {
454 public Component
getTableCellRendererComponent(final JTable table
,
456 final boolean isSelected
,
457 final boolean hasFocus
,
460 return setLabelColors(super.getTableCellRendererComponent(table
, value
, isSelected
, hasFocus
, row
, column
), table
, isSelected
, row
);
465 private static TableCellRenderer
createLanguageCellRenderer() {
466 return new TableCellRenderer() {
467 final JLabel label
= new JLabel();
469 public Component
getTableCellRendererComponent(final JTable table
, final Object value
, final boolean isSelected
, final boolean hasFocus
,
472 final BaseInjection injection
= (BaseInjection
)value
;
473 final Language language
= InjectedLanguage
.findLanguageById(injection
.getInjectedLanguageId());
474 final FileType fileType
= language
== null ?
null : language
.getAssociatedFileType();
475 label
.setIcon(fileType
== null ?
null : fileType
.getIcon());
476 label
.setText(language
== null ? injection
.getInjectedLanguageId() : language
.getDisplayName());
477 setLabelColors(label
, table
, isSelected
, row
);
483 private TableCellRenderer
createDisplayNameCellRenderer() {
484 return new TableCellRenderer() {
485 final SimpleColoredComponent myLabel
= new SimpleColoredComponent();
486 final SimpleColoredText myText
= new SimpleColoredText();
488 public Component
getTableCellRendererComponent(final JTable table
, final Object value
, final boolean isSelected
, final boolean hasFocus
,
492 final BaseInjection injection
= (BaseInjection
)value
;
493 final SimpleTextAttributes grayAttrs
= isSelected ? SimpleTextAttributes
.REGULAR_ATTRIBUTES
: SimpleTextAttributes
.GRAY_ATTRIBUTES
;
494 myText
.append(injection
.getSupportId() + ": ", grayAttrs
);
495 mySupports
.get(injection
.getSupportId()).setupPresentation(injection
, myText
, isSelected
);
496 myText
.appendToComponent(myLabel
);
498 setLabelColors(myLabel
, table
, isSelected
, row
);
504 private static Component
setLabelColors(final Component label
, final JTable table
, final boolean isSelected
, final int row
) {
505 if (label
instanceof JComponent
) {
506 ((JComponent
)label
).setOpaque(isSelected
);
508 label
.setForeground(isSelected ? table
.getSelectionForeground() : table
.getForeground());
509 label
.setBackground(isSelected ? table
.getSelectionBackground() : table
.getBackground());
513 private void doImportAction(final DataContext dataContext
) {
514 final FileChooserDescriptor descriptor
= new FileChooserDescriptor(true, false, true, false, true, false) {
516 public boolean isFileVisible(VirtualFile file
, boolean showHiddenFiles
) {
517 return super.isFileVisible(file
, showHiddenFiles
) &&
518 (file
.isDirectory() || "xml".equals(file
.getExtension()) || file
.getFileType() == StdFileTypes
.ARCHIVE
);
522 public boolean isFileSelectable(VirtualFile file
) {
523 return file
.getFileType() == StdFileTypes
.XML
;
526 descriptor
.setDescription("Please select the configuration file (usually named IntelliLang.xml) to import.");
527 descriptor
.setTitle("Import Configuration");
529 descriptor
.putUserData(LangDataKeys
.MODULE_CONTEXT
, LangDataKeys
.MODULE
.getData(dataContext
));
531 final FileChooserDialog chooser
= FileChooserFactory
.getInstance().createFileChooser(descriptor
, myProject
);
533 final SplitterProportionsData splitterData
= PeerFactory
.getInstance().getUIHelper().createSplitterProportionsData();
534 splitterData
.externalizeFromDimensionService("IntelliLang.ImportSettingsKey.SplitterProportions");
536 final VirtualFile
[] files
= chooser
.choose(null, myProject
);
537 if (files
.length
!= 1) return;
539 final Configuration cfg
= Configuration
.load(files
[0].getInputStream());
541 Messages
.showWarningDialog(myProject
, "The selected file does not contain any importable configuration.", "Nothing to Import");
544 final List
<BaseInjection
> newInjections
=
545 ContainerUtil
.concat(cfg
.getAllInjectorIds(), new Function
<String
, Collection
<?
extends BaseInjection
>>() {
546 public Collection
<?
extends BaseInjection
> fun(final String s
) {
547 return cfg
.getInjections(s
);
550 newInjections
.removeAll(myInjections
);
551 myInjections
.addAll(newInjections
);
552 final int n
= newInjections
.size();
554 Messages
.showInfoMessage(myProject
, n
+ " entries have been successfully imported", "Import Successful");
557 Messages
.showInfoMessage(myProject
, "One entry has been successfully imported", "Import Successful");
560 Messages
.showInfoMessage(myProject
, "No new entries have been imported", "Import");
563 catch (Exception e1
) {
564 Configuration
.LOG
.error("Unable to load Settings", e1
);
566 final String msg
= e1
.getLocalizedMessage();
567 Messages
.showErrorDialog(myProject
, msg
!= null && msg
.length() > 0 ? msg
: e1
.toString(), "Could not load Settings");