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.
16 package com
.intellij
.ide
.actionMacro
;
18 import com
.intellij
.ide
.IdeBundle
;
19 import com
.intellij
.ide
.IdeEventQueue
;
20 import com
.intellij
.openapi
.actionSystem
.*;
21 import com
.intellij
.openapi
.actionSystem
.ex
.ActionManagerEx
;
22 import com
.intellij
.openapi
.actionSystem
.ex
.AnActionListener
;
23 import com
.intellij
.openapi
.application
.ApplicationManager
;
24 import com
.intellij
.openapi
.application
.PathManager
;
25 import com
.intellij
.openapi
.components
.ExportableApplicationComponent
;
26 import com
.intellij
.openapi
.diagnostic
.Logger
;
27 import com
.intellij
.openapi
.project
.Project
;
28 import com
.intellij
.openapi
.ui
.Messages
;
29 import com
.intellij
.openapi
.ui
.playback
.PlaybackRunner
;
30 import com
.intellij
.openapi
.util
.InvalidDataException
;
31 import com
.intellij
.openapi
.util
.NamedJDOMExternalizable
;
32 import com
.intellij
.openapi
.util
.WriteExternalException
;
33 import com
.intellij
.openapi
.util
.registry
.Registry
;
34 import com
.intellij
.openapi
.wm
.IdeFrame
;
35 import com
.intellij
.openapi
.wm
.WindowManager
;
36 import org
.jdom
.Element
;
37 import org
.jetbrains
.annotations
.NonNls
;
38 import org
.jetbrains
.annotations
.NotNull
;
42 import java
.awt
.event
.InputEvent
;
43 import java
.awt
.event
.KeyEvent
;
45 import java
.util
.ArrayList
;
46 import java
.util
.HashSet
;
47 import java
.util
.Iterator
;
48 import java
.util
.List
;
53 public class ActionMacroManager
implements ExportableApplicationComponent
, NamedJDOMExternalizable
{
54 private static final Logger LOG
= Logger
.getInstance("#com.intellij.ide.actionMacro.ActionMacroManager");
56 private boolean myIsRecording
;
57 private final ActionManagerEx myActionManager
;
58 private ActionMacro myLastMacro
;
59 private ActionMacro myRecordingMacro
;
60 private ArrayList
<ActionMacro
> myMacros
= new ArrayList
<ActionMacro
>();
61 private String myLastMacroName
= null;
62 private boolean myIsPlaying
= false;
64 private static final String ELEMENT_MACRO
= "macro";
65 private IdeEventQueue
.EventDispatcher myKeyProcessor
;
67 private InputEvent myLastActionInputEvent
;
69 public ActionMacroManager(ActionManagerEx actionManagerEx
) {
70 myActionManager
= actionManagerEx
;
71 myActionManager
.addAnActionListener(new AnActionListener() {
72 public void beforeActionPerformed(AnAction action
, DataContext dataContext
, AnActionEvent event
) {
74 String id
= myActionManager
.getId(action
);
75 //noinspection HardCodedStringLiteral
76 if (id
!= null && !"StartStopMacroRecording".equals(id
)) {
77 myRecordingMacro
.appendAction(id
);
80 myLastActionInputEvent
= event
.getInputEvent();
84 public void beforeEditorTyping(char c
, DataContext dataContext
) {
87 public void afterActionPerformed(final AnAction action
, final DataContext dataContext
, AnActionEvent event
) {
91 myKeyProcessor
= new MyKeyPostpocessor();
92 IdeEventQueue
.getInstance().addPostprocessor(myKeyProcessor
, null);
95 public void readExternal(Element element
) throws InvalidDataException
{
96 myMacros
= new ArrayList
<ActionMacro
>();
97 final List macros
= element
.getChildren(ELEMENT_MACRO
);
98 for (Iterator iterator
= macros
.iterator(); iterator
.hasNext();) {
99 Element macroElement
= (Element
)iterator
.next();
100 ActionMacro macro
= new ActionMacro();
101 macro
.readExternal(macroElement
);
108 public String
getExternalFileName() {
113 public File
[] getExportFiles() {
114 return new File
[]{PathManager
.getOptionsFile(this)};
118 public String
getPresentableName() {
119 return IdeBundle
.message("title.macros");
122 public void writeExternal(Element element
) throws WriteExternalException
{
123 for (ActionMacro macro
: myMacros
) {
124 Element macroElement
= new Element(ELEMENT_MACRO
);
125 macro
.writeExternal(macroElement
);
126 element
.addContent(macroElement
);
130 public static ActionMacroManager
getInstance() {
131 return ApplicationManager
.getApplication().getComponent(ActionMacroManager
.class);
135 public String
getComponentName() {
136 return "ActionMacroManager";
139 public void initComponent() { }
141 public void startRecording(String macroName
) {
142 LOG
.assertTrue(!myIsRecording
);
143 myIsRecording
= true;
144 myRecordingMacro
= new ActionMacro(macroName
);
147 public void stopRecording(Project project
) {
148 LOG
.assertTrue(myIsRecording
);
149 myIsRecording
= false;
152 macroName
= Messages
.showInputDialog(project
,
153 IdeBundle
.message("prompt.enter.macro.name"),
154 IdeBundle
.message("title.enter.macro.name"),
155 Messages
.getQuestionIcon());
156 if (macroName
== null) {
157 myRecordingMacro
= null;
161 if ("".equals(macroName
)) macroName
= null;
163 while (macroName
!= null && !checkCanCreateMacro(macroName
));
165 myLastMacro
= myRecordingMacro
;
166 addRecordedMacroWithName(macroName
);
170 private void addRecordedMacroWithName(String macroName
) {
171 if (macroName
!= null) {
172 myRecordingMacro
.setName(macroName
);
173 myMacros
.add(myRecordingMacro
);
174 myRecordingMacro
= null;
177 for (int i
= 0; i
< myMacros
.size(); i
++) {
178 ActionMacro macro
= myMacros
.get(i
);
179 if (IdeBundle
.message("macro.noname").equals(macro
.getName())) {
180 myMacros
.set(i
, myRecordingMacro
);
181 myRecordingMacro
= null;
185 if (myRecordingMacro
!= null) {
186 myMacros
.add(myRecordingMacro
);
187 myRecordingMacro
= null;
192 public void playbackLastMacro() {
193 if (myLastMacro
!= null) {
194 playbackMacro(myLastMacro
);
198 private void playbackMacro(ActionMacro macro
) {
199 final IdeFrame frame
= WindowManager
.getInstance().getIdeFrame(null);
200 assert frame
!= null;
202 StringBuffer script
= new StringBuffer();
203 ActionMacro
.ActionDescriptor
[] actions
= macro
.getActions();
204 for (ActionMacro
.ActionDescriptor each
: actions
) {
205 each
.generateTo(script
);
208 final PlaybackRunner runner
= new PlaybackRunner(script
.toString(), new PlaybackRunner
.StatusCallback
.Edt() {
209 public void errorEdt(String text
, int curentLine
) {
210 frame
.getStatusBar().setInfo("Line " + curentLine
+ ":" + " Error: " + text
);
213 public void messageEdt(String text
, int curentLine
) {
214 frame
.getStatusBar().setInfo("Line " + curentLine
+ ": " + text
);
216 }, Registry
.is("actionSystem.playback.useDirectActionCall"));
220 runner
.run().doWhenDone(new Runnable() {
222 frame
.getStatusBar().setInfo("Script execution finished");
224 }).doWhenProcessed(new Runnable() {
231 public boolean isRecording() {
232 return myIsRecording
;
235 public void disposeComponent() {
236 IdeEventQueue
.getInstance().removePostprocessor(myKeyProcessor
);
239 public ActionMacro
[] getAllMacros() {
240 return myMacros
.toArray(new ActionMacro
[myMacros
.size()]);
243 public void removeAllMacros() {
244 if (myLastMacro
!= null) {
245 myLastMacroName
= myLastMacro
.getName();
248 myMacros
= new ArrayList
<ActionMacro
>();
251 public void addMacro(ActionMacro macro
) {
253 if (myLastMacroName
!= null && myLastMacroName
.equals(macro
.getName())) {
255 myLastMacroName
= null;
259 public void playMacro(ActionMacro macro
) {
260 playbackMacro(macro
);
264 public boolean hasRecentMacro() {
265 return myLastMacro
!= null;
268 public void registerActions() {
270 HashSet
<String
> registeredIds
= new HashSet
<String
>(); // to prevent exception if 2 or more targets have the same name
272 ActionMacro
[] macros
= getAllMacros();
273 for (final ActionMacro macro
: macros
) {
274 String actionId
= macro
.getActionId();
276 if (!registeredIds
.contains(actionId
)) {
277 registeredIds
.add(actionId
);
278 myActionManager
.registerAction(actionId
, new InvokeMacroAction(macro
));
283 public void unregisterActions() {
285 // unregister Tool actions
286 String
[] oldIds
= myActionManager
.getActionIds(ActionMacro
.MACRO_ACTION_PREFIX
);
287 for (final String oldId
: oldIds
) {
288 myActionManager
.unregisterAction(oldId
);
292 public boolean checkCanCreateMacro(String name
) {
293 final ActionManagerEx actionManager
= (ActionManagerEx
)ActionManager
.getInstance();
294 final String actionId
= ActionMacro
.MACRO_ACTION_PREFIX
+ name
;
295 if (actionManager
.getAction(actionId
) != null) {
296 if (Messages
.showYesNoDialog(IdeBundle
.message("message.macro.exists", name
),
297 IdeBundle
.message("title.macro.name.already.used"),
298 Messages
.getWarningIcon()) != 0) {
301 actionManager
.unregisterAction(actionId
);
308 private void removeMacro(String name
) {
309 for (int i
= 0; i
< myMacros
.size(); i
++) {
310 ActionMacro macro
= myMacros
.get(i
);
311 if (name
.equals(macro
.getName())) {
318 public boolean isPlaying() {
322 private static class InvokeMacroAction
extends AnAction
{
323 private final ActionMacro myMacro
;
325 InvokeMacroAction(ActionMacro macro
) {
327 getTemplatePresentation().setText(macro
.getName(), false);
330 public void actionPerformed(AnActionEvent e
) {
331 getInstance().playMacro(myMacro
);
334 public void update(AnActionEvent e
) {
336 e
.getPresentation().setEnabled(!getInstance().isPlaying() &&
337 PlatformDataKeys
.EDITOR
.getData(e
.getDataContext()) != null);
341 private class MyKeyPostpocessor
implements IdeEventQueue
.EventDispatcher
{
343 public boolean dispatch(AWTEvent e
) {
344 if (isRecording() && e
instanceof KeyEvent
) {
345 postProcessKeyEvent((KeyEvent
)e
);
350 public void postProcessKeyEvent(KeyEvent e
) {
351 final boolean isChar
= e
.getKeyChar() != KeyEvent
.CHAR_UNDEFINED
;
352 boolean hasActionModifiers
= e
.isAltDown() | e
.isControlDown() | e
.isMetaDown();
353 boolean plainType
= isChar
&& !hasActionModifiers
;
354 final boolean isEnter
= e
.getKeyCode() == KeyEvent
.VK_ENTER
;
356 boolean noModifierKeyIsPressed
= e
.getKeyCode() != KeyEvent
.VK_CONTROL
357 && e
.getKeyCode() != KeyEvent
.VK_ALT
358 && e
.getKeyCode() != KeyEvent
.VK_META
359 && e
.getKeyCode() != KeyEvent
.VK_SHIFT
;
361 if (e
.getID() == KeyEvent
.KEY_PRESSED
&& plainType
&& !isEnter
) {
362 myRecordingMacro
.appendKeytyped(e
.getKeyChar(), e
.getKeyCode(), e
.getModifiers());
363 } else if (e
.getID() == KeyEvent
.KEY_PRESSED
&& noModifierKeyIsPressed
&& (!plainType
|| isEnter
)) {
364 final boolean waiting
= IdeEventQueue
.getInstance().getKeyEventDispatcher().isWaitingForSecondKeyStroke();
365 if ((!e
.equals(myLastActionInputEvent
) && !waiting
) || isEnter
) {
366 final String stroke
= KeyStroke
.getKeyStrokeForEvent(e
).toString();
368 final int pressed
= stroke
.indexOf("pressed");
369 String key
= stroke
.substring(pressed
+ "pressed".length());
370 String modifiers
= stroke
.substring(0, pressed
);
372 String ready
= (modifiers
.replaceAll("ctrl", "control").trim() + " " + key
.trim()).trim();
374 myRecordingMacro
.appendShortuct(ready
);
376 myLastActionInputEvent
= null;