Merge branch 'master' of git@git.labs.intellij.net:idea/community into tool-window
[fedora-idea.git] / platform / platform-impl / src / com / intellij / ide / actionMacro / ActionMacroManager.java
blob00386b88f282bec54bfbce06bd0c670127b2f178
1 /*
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;
40 import javax.swing.*;
41 import java.awt.*;
42 import java.awt.event.InputEvent;
43 import java.awt.event.KeyEvent;
44 import java.io.File;
45 import java.util.ArrayList;
46 import java.util.HashSet;
47 import java.util.Iterator;
48 import java.util.List;
50 /**
51 * @author max
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;
63 @NonNls
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) {
73 if (myIsRecording) {
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) {
89 });
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);
102 myMacros.add(macro);
105 registerActions();
108 public String getExternalFileName() {
109 return "macros";
112 @NotNull
113 public File[] getExportFiles() {
114 return new File[]{PathManager.getOptionsFile(this)};
117 @NotNull
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);
134 @NotNull
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;
150 String macroName;
151 do {
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;
158 return;
161 if ("".equals(macroName)) macroName = null;
163 while (macroName != null && !checkCanCreateMacro(macroName));
165 myLastMacro = myRecordingMacro;
166 addRecordedMacroWithName(macroName);
167 registerActions();
170 private void addRecordedMacroWithName(String macroName) {
171 if (macroName != null) {
172 myRecordingMacro.setName(macroName);
173 myMacros.add(myRecordingMacro);
174 myRecordingMacro = null;
176 else {
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;
182 break;
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"));
218 myIsPlaying = true;
220 runner.run().doWhenDone(new Runnable() {
221 public void run() {
222 frame.getStatusBar().setInfo("Script execution finished");
224 }).doWhenProcessed(new Runnable() {
225 public void run() {
226 myIsPlaying = false;
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();
246 myLastMacro = null;
248 myMacros = new ArrayList<ActionMacro>();
251 public void addMacro(ActionMacro macro) {
252 myMacros.add(macro);
253 if (myLastMacroName != null && myLastMacroName.equals(macro.getName())) {
254 myLastMacro = macro;
255 myLastMacroName = null;
259 public void playMacro(ActionMacro macro) {
260 playbackMacro(macro);
261 myLastMacro = macro;
264 public boolean hasRecentMacro() {
265 return myLastMacro != null;
268 public void registerActions() {
269 unregisterActions();
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) {
299 return false;
301 actionManager.unregisterAction(actionId);
302 removeMacro(name);
305 return true;
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())) {
312 myMacros.remove(i);
313 break;
318 public boolean isPlaying() {
319 return myIsPlaying;
322 private static class InvokeMacroAction extends AnAction {
323 private final ActionMacro myMacro;
325 InvokeMacroAction(ActionMacro macro) {
326 myMacro = macro;
327 getTemplatePresentation().setText(macro.getName(), false);
330 public void actionPerformed(AnActionEvent e) {
331 getInstance().playMacro(myMacro);
334 public void update(AnActionEvent e) {
335 super.update(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);
347 return false;
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);
375 if (!isEnter) {
376 myLastActionInputEvent = null;