001: package tide.editor;
002:
003: import snow.utils.gui.Icons;
004: import tide.sources.*;
005: import snow.utils.StringUtils;
006: import javax.swing.JMenu;
007: import java.awt.event.ActionEvent;
008: import java.awt.event.ActionListener;
009: import javax.swing.JMenuItem;
010: import javax.swing.JPopupMenu;
011: import javax.swing.event.ChangeListener;
012: import snow.texteditor.DocumentUtils;
013: import java.util.*;
014:
015: /** Records jumps to offer undo and redo and useful jumps.
016: * Part of the EditorPanel. Used by the SourcesNavigationBar.
017: */
018: public final class JumpManager {
019: final EditorPanel ep;
020:
021: private final List<String> positionsUndoHistory = new ArrayList<String>();
022: private final List<String> positionsRedoHistory = new ArrayList<String>();
023: private final List<ChangeListener> changeListeners = new ArrayList<ChangeListener>();
024:
025: JumpManager(EditorPanel ep) {
026: this .ep = ep;
027: }
028:
029: public void clear() {
030: positionsUndoHistory.clear();
031: positionsRedoHistory.clear();
032: }
033:
034: /** Called from the editor from the scroll method. After the scroll has been made !
035: * Called from outside.
036: */
037: void rememberPositionInEditor(int line) {
038: if (!recordJumps)
039: return;
040: rememberThisPositionInJumpHistory(line, true);
041: }
042:
043: boolean hasUndo() {
044: return positionsUndoHistory.size() > 0;
045: }
046:
047: boolean hasRedo() {
048: return positionsRedoHistory.size() > 0;
049: }
050:
051: void addChangeListener(ChangeListener cl) {
052: changeListeners.add(cl);
053: }
054:
055: private void notifyListeners() {
056: for (ChangeListener cl : changeListeners
057: .toArray(new ChangeListener[changeListeners.size()])) // avoid concurrent modifs ...
058: {
059: cl.stateChanged(null);
060: }
061: }
062:
063: public final List<String> getPositionsUndoHistory_REF() {
064: return positionsUndoHistory;
065: }
066:
067: public final List<String> getPositionsRedoHistory_REF() {
068: return positionsRedoHistory;
069: }
070:
071: /** Undo jump.
072: */
073: void jumpToPrevious() {
074: if (positionsUndoHistory.isEmpty())
075: return;
076: String jp = positionsUndoHistory.get(positionsUndoHistory
077: .size() - 1);
078: positionsUndoHistory.remove(positionsUndoHistory.size() - 1);
079: notifyListeners();
080:
081: int pp = jp.indexOf(':');
082: String jn = jp.substring(0, pp);
083: int ln = Integer.parseInt(jp.substring(pp + 1));
084: MainEditorFrame.debugOut("Undo jump " + jn + ":" + ln);
085:
086: FileItem fi = MainEditorFrame.instance.getFileItem(jn, "");
087: if (fi == null)
088: return;
089:
090: //fill redo buffer with actual position
091: int actualLine = DocumentUtils.getLineNumber(ep.doc,
092: ep.textPane.getCaretPosition());
093: rememberThisPositionInJumpHistory(actualLine, false);
094:
095: recordJumps = false;
096: MainEditorFrame.instance.setSourceOrItemToEditOrView(fi, true);
097: ep.selectLine(ln);
098: recordJumps = true;
099: }
100:
101: /** Redo jump.
102: */
103: void redoUndoedJump() {
104: if (positionsRedoHistory.isEmpty())
105: return;
106: String jp = positionsRedoHistory.get(positionsRedoHistory
107: .size() - 1);
108: positionsRedoHistory.remove(positionsRedoHistory.size() - 1);
109: notifyListeners();
110:
111: int pp = jp.indexOf(':');
112: String jn = jp.substring(0, pp);
113: int ln = Integer.parseInt(jp.substring(pp + 1));
114: System.out.println("Redo jump " + jn + ":" + ln);
115:
116: FileItem fi = MainEditorFrame.instance.getFileItem(jn, "");
117: if (fi == null)
118: return;
119:
120: MainEditorFrame.instance.setSourceOrItemToEditOrView(fi, true);
121: ep.selectLine(ln);
122:
123: }
124:
125: boolean recordJumps = true;
126:
127: private void rememberThisPositionInJumpHistory(int line,
128: boolean forUndoOrRedo) {
129: List<String> undoOrRedo = (forUndoOrRedo ? positionsUndoHistory
130: : positionsRedoHistory);
131:
132: if (ep.getActualDisplayedFile() == null)
133: return;
134: String jn = ep.getActualDisplayedFile().getJavaName();
135: StringBuilder sb = new StringBuilder(jn);
136: //final int line = DocumentUtils.getLineNumber(ep.getDocument(), ep.textPane.getCaretPosition());
137: sb.append(":" + line);
138: String this Pos = sb.toString();
139:
140: if (undoOrRedo.size() > 0) {
141: // don't add if already the same pos !
142: String prev = undoOrRedo.get(undoOrRedo.size() - 1);
143: if (prev.equals(this Pos)) {
144: //System.out.println("already remembered "+prev); // occurs ?
145: return;
146: }
147:
148: if (prev.equals(jn + ":0")) {
149: // don't remember position 0 if next is a line>0 (?better: TODO: "cleverer" add in editor)
150: undoOrRedo.remove(undoOrRedo.size() - 1);
151: }
152: }
153:
154: undoOrRedo.add(this Pos);
155: //System.out.println("remember "+thisPos+" for "+(forUndoOrRedo?"undo":"redo"));
156: notifyListeners();
157: }
158:
159: public List<JMenuItem> getRecentViewedJumpMenuItems(int max) {
160: final List<String> ur = this .positionsUndoHistory;
161: List<JMenuItem> acts = new ArrayList<JMenuItem>();
162: List<String> reducedHistory = reduceHistory(ur);
163:
164: int n = 0;
165: fl: for (int i = reducedHistory.size() - 1; i >= 0; i--) {
166: final String jp = reducedHistory.get(i);
167:
168: JMenuItem mi = new JMenuItem(StringUtils
169: .removeAfterLastIncluded(jp, ":"));
170: mi.addActionListener(createJumpAction(jp));
171: acts.add(mi);
172: n++;
173: if (n > max) {
174: //acts.add(new JMenuItem("... "+(reducedHistory.size()-n)+" more items ..."));
175: break fl;
176: }
177: }
178:
179: if (n == 0) {
180: acts.add(new JMenuItem("no source was recently viewed"));
181: }
182:
183: return acts;
184: }
185:
186: public List<JMenuItem> getRecentEditedJumpMenuItems(int max) {
187: List<JMenuItem> acts = new ArrayList<JMenuItem>();
188:
189: int n = 0;
190: fl: for (final FileItem fi : ep.allChangedFilesHistory) {
191: JMenuItem mi = new JMenuItem(fi.getJavaName());
192: mi.addActionListener(createJumpAction(fi.getJavaName()
193: + ":" + fi.getCaretLinePosition()));
194: acts.add(mi);
195: n++;
196: if (n > max) {
197: //acts.add(new JMenuItem("... "+(ep.allChangedFilesHistory.size()-n)+" more items ..."));
198: break fl;
199: }
200: }
201:
202: if (n == 0) {
203: acts.add(new JMenuItem("no source was recently edited"));
204: }
205:
206: return acts;
207: }
208:
209: List<String> reduceHistory(final List<String> ur) {
210: // [May2007]: populate the distinct names (so we have an history)
211: Set<String> alreadyAddJavaNames = new HashSet<String>();
212: List<String> reducedHistory = new ArrayList<String>();
213:
214: fl: for (int i = ur.size() - 1; i >= 0; i--) // keep the latest position when several times the same file
215: {
216: final String jp = ur.get(i);
217: int pp = jp.indexOf(':');
218: String jn = jp.substring(0, pp);
219: if (!alreadyAddJavaNames.contains(jn)) {
220: reducedHistory.add(jp);
221: alreadyAddJavaNames.add(jn);
222: }
223: }
224:
225: return reducedHistory;
226: }
227:
228: public JPopupMenu getDependenciesPopup() {
229: JPopupMenu pop = new JPopupMenu("");
230:
231: // [Feb2008]: add the dependencies links
232: FileItem actualSource = MainEditorFrame.instance.editorPanel
233: .getActualDisplayedFile();
234: if (actualSource != null && actualSource instanceof SourceFile) {
235: final SourceFile sf = (SourceFile) actualSource;
236:
237: final ArrayList<SourceFile> usedBy = new ArrayList<SourceFile>(
238: sf.sourceFileDependencies.getClassesUsedBy_REF_());
239: Comparator<SourceFile> nameComp = new Comparator<SourceFile>() {
240: public int compare(SourceFile f1, SourceFile f2) {
241: return f1.getJavaName().compareTo(f2.getJavaName());
242: }
243: };
244: Collections.sort(usedBy, nameComp);
245:
246: //pop.addSeparator();
247:
248: JMenu usedByM = new JMenu(""
249: + usedBy.size()
250: + " source"
251: + (usedBy.size() == 1 ? "" : "s")
252: + " used by "
253: + sf.getJavaPartName()
254: + (sf.sourceFileDependencies
255: .areDependenciesActual() ? ""
256: : " (NOT ACTUAL)"));
257: usedByM
258: .setIcon(new Icons.HArrow(14, 14, false, true,
259: false));
260: pop.add(usedByM);
261: if (usedBy.size() > 10) {
262: JMenuItem mi = new JMenuItem("Browse");
263: usedByM.add(mi);
264: usedByM.addSeparator();
265: mi.addActionListener(new ActionListener() {
266: public void actionPerformed(ActionEvent ae) {
267: new SourcesExplorer(usedBy,
268: MainEditorFrame.instance,
269: "Sources used by "
270: + sf.getJavaPartName());
271: }
272: });
273: }
274: int n = 0;
275: fl: for (SourceFile usi : usedBy) {
276: JMenuItem mi = new JMenuItem(usi.getJavaName());
277: usedByM.add(mi);
278: mi
279: .addActionListener(createJumpAction(usi
280: .getJavaName()));
281: n++;
282: if (n > 30) {
283: usedByM.add("... " + (usedBy.size() - n)
284: + " more items ...");
285: break fl;
286: }
287: }
288:
289: final ArrayList<SourceFile> using = new ArrayList<SourceFile>(
290: sf.sourceFileDependencies
291: .getClassesUsingThis_REF_());
292: Collections.sort(using, nameComp);
293:
294: JMenu usingM = new JMenu(""
295: + using.size()
296: + " source"
297: + (using.size() == 1 ? "" : "s")
298: + " using "
299: + sf.getJavaPartName()
300: + (sf.sourceFileDependencies
301: .areDependenciesActual() ? ""
302: : " (NOT ACTUAL)"));
303: usingM
304: .setIcon(new Icons.HArrow(14, 14, true, false,
305: false));
306: // TODO: ?? maybe actual if all of these sources have actual deps !! ??
307: pop.add(usingM);
308: if (using.size() > 10) {
309: JMenuItem mi = new JMenuItem("Browse");
310: usingM.add(mi);
311: usingM.addSeparator();
312: mi.addActionListener(new ActionListener() {
313: public void actionPerformed(ActionEvent ae) {
314: new SourcesExplorer(using,
315: MainEditorFrame.instance,
316: "Sources using " + sf.getJavaPartName());
317: }
318: });
319: }
320: n = 0;
321: fl: for (SourceFile usi : using) {
322: JMenuItem mi = new JMenuItem(usi.getJavaName());
323: usingM.add(mi);
324: mi
325: .addActionListener(createJumpAction(usi
326: .getJavaName()));
327: n++;
328: if (n > 50) {
329: usedByM.add("... " + (using.size() - n)
330: + " more items ...");
331: break fl;
332: }
333: }
334: }
335: return pop;
336: }
337:
338: public JPopupMenu getRecentViewsPopup(final List<String> ur) {
339: JPopupMenu pop = new JPopupMenu("");
340: List<String> reducedHistory = reduceHistory(ur);
341:
342: int n = 0;
343: if (reducedHistory.size() > 2) {
344: JMenu reducedMenu = new JMenu("Recently viewed");
345: pop.add(reducedMenu);
346:
347: fl: for (String jp : reducedHistory) {
348: // ?? Do not add if in ep.allChangedFilesHistory ??
349:
350: JMenuItem mi = new JMenuItem(StringUtils
351: .removeAfterLastIncluded(jp, ":"));
352: mi.addActionListener(createJumpAction(jp));
353: reducedMenu.add(mi);
354: n++;
355: if (n > 30 && (reducedHistory.size() - n) > 0) {
356: reducedMenu.add("... "
357: + (reducedHistory.size() - n)
358: + " more items ...");
359: break fl;
360: }
361: }
362: }
363:
364: if (ep.allChangedFilesHistory.size() > 0) {
365: JMenu reducedMenu2 = new JMenu("Recently edited");
366: pop.add(reducedMenu2);
367: n = 0;
368: fl: for (int i = ep.allChangedFilesHistory.size() - 1; i >= 0; i--) {
369: final FileItem fi = ep.allChangedFilesHistory.get(i);
370:
371: JMenuItem mi = new JMenuItem(fi.getJavaName());
372: mi.addActionListener(createJumpAction(fi.getJavaName()
373: + ":" + fi.getCaretLinePosition()));
374: reducedMenu2.add(mi);
375: n++;
376: if (n > 30
377: && (ep.allChangedFilesHistory.size() - n) > 0) {
378: reducedMenu2.add("... "
379: + (ep.allChangedFilesHistory.size() - n)
380: + " more items ...");
381: break fl;
382: }
383: }
384: }
385:
386: JMenuItem recb = new JMenu("Recently bookmarked");
387: pop.add(recb);
388: SharedUtils.addRecentBookmarks(recb, 20);
389:
390: return pop;
391: }
392:
393: /** Used on context click on the undo button (SourcesNavigationBar).
394: * Plus useful jumps, in the recent viewed, recent edited and dependencies of the actual source.
395: */
396: public JPopupMenu getUndoPopup(final List<String> ur) {
397: JPopupMenu pop = new JPopupMenu("");
398: //List<String> reducedHistory = reduceHistory(ur);
399:
400: // Detailled 30 previous positions
401:
402: int n = 0;
403: fl: for (int i = ur.size() - 1; i >= 0; i--) {
404: final String jp = ur.get(i);
405: JMenuItem mi = new JMenuItem(jp);
406: mi.addActionListener(createJumpAction(jp));
407: pop.add(mi);
408:
409: n++;
410: if (n > 30 && (ur.size() - n) > 0) {
411: pop.add("... " + (ur.size() - n) + " more items ...");
412: break fl;
413: }
414: }
415: return pop;
416: }
417:
418: /** @param jp is in the form javaName:pos
419: */
420: ActionListener createJumpAction(final String jp) {
421: return new ActionListener() {
422: public void actionPerformed(ActionEvent ae) {
423: int pp = jp.indexOf(':');
424: String jn = jp;
425: int ln = -1;
426: if (pp >= 0) {
427: jn = jp.substring(0, pp);
428: ln = Integer.parseInt(jp.substring(pp + 1));
429: }
430:
431: FileItem fi = MainEditorFrame.instance.getFileItem(jn,
432: "");
433: if (fi == null)
434: return;
435: //fill redo buffer with actual position
436: //int actualLine = DocumentUtils.getLineNumber(ep.doc, ep.textPane.getCaretPosition());
437: //rememberThisPositionInJumpHistory(actualLine, false);
438: //recordJumps = false;
439: MainEditorFrame.instance.setSourceOrItemToEditOrView(
440: fi, true);
441: ep.selectLine(ln);
442: //recordJumps = true;
443: }
444: };
445: }
446:
447: }
|