Source code: org/gjt/sp/jedit/View.java
1 /*
2 * View.java - jEdit view
3 * :tabSize=8:indentSize=8:noTabs=false:
4 * :folding=explicit:collapseFolds=1:
5 *
6 * Copyright (C) 1998, 2003 Slava Pestov
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */
22
23 package org.gjt.sp.jedit;
24
25 //{{{ Imports
26 import javax.swing.event.*;
27 import javax.swing.text.*;
28 import javax.swing.*;
29 import java.awt.*;
30 import java.awt.event.*;
31 import java.io.IOException;
32 import java.io.StreamTokenizer;
33 import java.io.StringReader;
34 import java.net.Socket;
35 import java.util.*;
36 import org.gjt.sp.jedit.msg.*;
37 import org.gjt.sp.jedit.gui.*;
38 import org.gjt.sp.jedit.search.*;
39 import org.gjt.sp.jedit.textarea.*;
40 import org.gjt.sp.util.Log;
41 //}}}
42
43 /**
44 * A <code>View</code> is jEdit's top-level frame window.<p>
45 *
46 * In a BeanShell script, you can obtain the current view instance from the
47 * <code>view</code> variable.<p>
48 *
49 * The largest component it contains is an {@link EditPane} that in turn
50 * contains a {@link org.gjt.sp.jedit.textarea.JEditTextArea} that displays a
51 * {@link Buffer}.
52 * A view can have more than one edit pane in a split window configuration.
53 * A view also contains a menu bar, an optional toolbar and other window
54 * decorations, as well as docked windows.<p>
55 *
56 * The <b>View</b> class performs two important operations
57 * dealing with plugins: creating plugin menu items, and managing dockable
58 * windows.
59 *
60 * <ul>
61 * <li>When a view is being created, its initialization routine
62 * iterates through the collection of loaded plugins and constructs the
63 * <b>Plugins</b> menu using the properties as specified in the
64 * {@link EditPlugin} class.</li>
65 * <li>The view also creates and initializes a
66 * {@link org.gjt.sp.jedit.gui.DockableWindowManager}
67 * object. This object is
68 * responsible for creating, closing and managing dockable windows.</li>
69 * </ul>
70 *
71 * This class does not have a public constructor.
72 * Views can be opened and closed using methods in the <code>jEdit</code>
73 * class.
74 *
75 * @see org.gjt.sp.jedit.jEdit#newView(View)
76 * @see org.gjt.sp.jedit.jEdit#newView(View,Buffer)
77 * @see org.gjt.sp.jedit.jEdit#newView(View,Buffer,boolean)
78 * @see org.gjt.sp.jedit.jEdit#closeView(View)
79 *
80 * @author Slava Pestov
81 * @author John Gellene (API documentation)
82 * @version $Id: View.java,v 1.107 2003/11/18 22:31:48 spestov Exp $
83 */
84 public class View extends JFrame implements EBComponent
85 {
86 //{{{ User interface
87
88 //{{{ ToolBar-related constants
89
90 //{{{ Groups
91 /**
92 * The group of tool bars above the DockableWindowManager
93 * @see #addToolBar(int,int,java.awt.Component)
94 * @since jEdit 4.0pre7
95 */
96 public static final int TOP_GROUP = 0;
97
98 /**
99 * The group of tool bars below the DockableWindowManager
100 * @see #addToolBar(int,int,java.awt.Component)
101 * @since jEdit 4.0pre7
102 */
103 public static final int BOTTOM_GROUP = 1;
104 public static final int DEFAULT_GROUP = TOP_GROUP;
105 //}}}
106
107 //{{{ Layers
108
109 // Common layers
110 /**
111 * The highest possible layer.
112 * @see #addToolBar(int,int,java.awt.Component)
113 * @since jEdit 4.0pre7
114 */
115 public static final int TOP_LAYER = Integer.MAX_VALUE;
116
117 /**
118 * The default layer for tool bars with no preference.
119 * @see #addToolBar(int,int,java.awt.Component)
120 * @since jEdit 4.0pre7
121 */
122 public static final int DEFAULT_LAYER = 0;
123
124 /**
125 * The lowest possible layer.
126 * @see #addToolBar(int,int,java.awt.Component)
127 * @since jEdit 4.0pre7
128 */
129 public static final int BOTTOM_LAYER = Integer.MIN_VALUE;
130
131 // Layers for top group
132 /**
133 * Above system tool bar layer.
134 * @see #addToolBar(int,int,java.awt.Component)
135 * @since jEdit 4.0pre7
136 */
137 public static final int ABOVE_SYSTEM_BAR_LAYER = 150;
138
139 /**
140 * System tool bar layer.
141 * jEdit uses this for the main tool bar.
142 * @see #addToolBar(int,int,java.awt.Component)
143 * @since jEdit 4.0pre7
144 */
145 public static final int SYSTEM_BAR_LAYER = 100;
146
147 /**
148 * Below system tool bar layer.
149 * @see #addToolBar(int,int,java.awt.Component)
150 * @since jEdit 4.0pre7
151 */
152 public static final int BELOW_SYSTEM_BAR_LAYER = 75;
153
154 /**
155 * Search bar layer.
156 * @see #addToolBar(int,int,java.awt.Component)
157 * @since jEdit 4.0pre7
158 */
159 public static final int SEARCH_BAR_LAYER = 75;
160
161 /**
162 * Below search bar layer.
163 * @see #addToolBar(int,int,java.awt.Component)
164 * @since jEdit 4.0pre7
165 */
166 public static final int BELOW_SEARCH_BAR_LAYER = 50;
167
168 // Layers for bottom group
169 /**
170 * @deprecated Status bar no longer added as a tool bar.
171 */
172 public static final int ABOVE_ACTION_BAR_LAYER = -50;
173
174 /**
175 * Action bar layer.
176 * @see #addToolBar(int,int,java.awt.Component)
177 * @since jEdit 4.2pre1
178 */
179 public static final int ACTION_BAR_LAYER = -75;
180
181 /**
182 * Status bar layer.
183 * @see #addToolBar(int,int,java.awt.Component)
184 * @since jEdit 4.2pre1
185 */
186 public static final int STATUS_BAR_LAYER = -100;
187
188 /**
189 * Status bar layer.
190 * @see #addToolBar(int,int,java.awt.Component)
191 * @since jEdit 4.2pre1
192 */
193 public static final int BELOW_STATUS_BAR_LAYER = -150;
194 //}}}
195
196 //}}}
197
198 //{{{ getDockableWindowManager() method
199 /**
200 * Returns the dockable window manager associated with this view.
201 * @since jEdit 2.6pre3
202 */
203 public DockableWindowManager getDockableWindowManager()
204 {
205 return dockableWindowManager;
206 } //}}}
207
208 //{{{ getToolBar() method
209 /**
210 * Returns the view's tool bar.
211 * @since jEdit 4.2pre1
212 */
213 public Box getToolBar()
214 {
215 return toolBar;
216 } //}}}
217
218 //{{{ addToolBar() method
219 /**
220 * Adds a tool bar to this view.
221 * @param toolBar The tool bar
222 */
223 public void addToolBar(Component toolBar)
224 {
225 addToolBar(DEFAULT_GROUP, DEFAULT_LAYER, toolBar);
226 } //}}}
227
228 //{{{ addToolBar() method
229 /**
230 * Adds a tool bar to this view.
231 * @param group The tool bar group to add to
232 * @param toolBar The tool bar
233 * @see org.gjt.sp.jedit.gui.ToolBarManager
234 * @since jEdit 4.0pre7
235 */
236 public void addToolBar(int group, Component toolBar)
237 {
238 addToolBar(group, DEFAULT_LAYER, toolBar);
239 } //}}}
240
241 //{{{ addToolBar() method
242 /**
243 * Adds a tool bar to this view.
244 * @param group The tool bar group to add to
245 * @param layer The layer of the group to add to
246 * @param toolBar The tool bar
247 * @see org.gjt.sp.jedit.gui.ToolBarManager
248 * @since jEdit 4.0pre7
249 */
250 public void addToolBar(int group, int layer, Component toolBar)
251 {
252 toolBarManager.addToolBar(group, layer, toolBar);
253 getRootPane().revalidate();
254 } //}}}
255
256 //{{{ removeToolBar() method
257 /**
258 * Removes a tool bar from this view.
259 * @param toolBar The tool bar
260 */
261 public void removeToolBar(Component toolBar)
262 {
263 toolBarManager.removeToolBar(toolBar);
264 getRootPane().revalidate();
265 } //}}}
266
267 //{{{ showWaitCursor() method
268 /**
269 * Shows the wait cursor. This method and
270 * {@link #hideWaitCursor()} are implemented using a reference
271 * count of requests for wait cursors, so that nested calls work
272 * correctly; however, you should be careful to use these methods in
273 * tandem.<p>
274 *
275 * To ensure that {@link #hideWaitCursor()} is always called
276 * after a {@link #showWaitCursor()}, use a
277 * <code>try</code>/<code>finally</code> block, like this:
278 * <pre>try
279 *{
280 * view.showWaitCursor();
281 * // ...
282 *}
283 *finally
284 *{
285 * view.hideWaitCursor();
286 *}</pre>
287 */
288 public synchronized void showWaitCursor()
289 {
290 if(waitCount++ == 0)
291 {
292 Cursor cursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
293 setCursor(cursor);
294 EditPane[] editPanes = getEditPanes();
295 for(int i = 0; i < editPanes.length; i++)
296 {
297 EditPane editPane = editPanes[i];
298 editPane.getTextArea().getPainter()
299 .setCursor(cursor);
300 }
301 }
302 } //}}}
303
304 //{{{ hideWaitCursor() method
305 /**
306 * Hides the wait cursor.
307 */
308 public synchronized void hideWaitCursor()
309 {
310 if(waitCount > 0)
311 waitCount--;
312
313 if(waitCount == 0)
314 {
315 // still needed even though glass pane
316 // has a wait cursor
317 Cursor cursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
318 setCursor(cursor);
319 cursor = Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR);
320 EditPane[] editPanes = getEditPanes();
321 for(int i = 0; i < editPanes.length; i++)
322 {
323 EditPane editPane = editPanes[i];
324 editPane.getTextArea().getPainter()
325 .setCursor(cursor);
326 }
327 }
328 } //}}}
329
330 //{{{ getSearchBar() method
331 /**
332 * Returns the search bar.
333 * @since jEdit 2.4pre4
334 */
335 public final SearchBar getSearchBar()
336 {
337 return searchBar;
338 } //}}}
339
340 //{{{ getActionBar() method
341 /**
342 * Returns the action bar.
343 * @since jEdit 4.2pre3
344 */
345 public final ActionBar getActionBar()
346 {
347 return actionBar;
348 } //}}}
349
350 //{{{ getStatus() method
351 /**
352 * Returns the status bar. The
353 * {@link org.gjt.sp.jedit.gui.StatusBar#setMessage(String)} and
354 * {@link org.gjt.sp.jedit.gui.StatusBar#setMessageAndClear(String)} methods can
355 * be called on the return value of this method to display status
356 * information to the user.
357 * @since jEdit 3.2pre2
358 */
359 public StatusBar getStatus()
360 {
361 return status;
362 } //}}}
363
364 //{{{ quickIncrementalSearch() method
365 /**
366 * Quick search.
367 * @since jEdit 4.0pre3
368 */
369 public void quickIncrementalSearch(boolean word)
370 {
371 if(searchBar == null)
372 searchBar = new SearchBar(this,true);
373 if(searchBar.getParent() == null)
374 addToolBar(TOP_GROUP,SEARCH_BAR_LAYER,searchBar);
375
376 searchBar.setHyperSearch(false);
377
378 JEditTextArea textArea = getTextArea();
379
380 if(word)
381 {
382 String text = textArea.getSelectedText();
383 if(text == null)
384 {
385 textArea.selectWord();
386 text = textArea.getSelectedText();
387 }
388 else if(text.indexOf('\n') != -1)
389 text = null;
390
391 searchBar.getField().setText(text);
392 }
393
394 searchBar.getField().requestFocus();
395 searchBar.getField().selectAll();
396 } //}}}
397
398 //{{{ quickHyperSearch() method
399 /**
400 * Quick HyperSearch.
401 * @since jEdit 4.0pre3
402 */
403 public void quickHyperSearch(boolean word)
404 {
405 JEditTextArea textArea = getTextArea();
406
407 if(word)
408 {
409 String text = textArea.getSelectedText();
410 if(text == null)
411 {
412 textArea.selectWord();
413 text = textArea.getSelectedText();
414 }
415
416 if(text != null && text.indexOf('\n') == -1)
417 {
418 HistoryModel.getModel("find").addItem(text);
419 SearchAndReplace.setSearchString(text);
420 SearchAndReplace.setSearchFileSet(new CurrentBufferSet());
421 SearchAndReplace.hyperSearch(this);
422
423 return;
424 }
425 }
426
427 if(searchBar == null)
428 searchBar = new SearchBar(this,true);
429 if(searchBar.getParent() == null)
430 addToolBar(TOP_GROUP,SEARCH_BAR_LAYER,searchBar);
431
432 searchBar.setHyperSearch(true);
433 searchBar.getField().setText(null);
434 searchBar.getField().requestFocus();
435 searchBar.getField().selectAll();
436 } //}}}
437
438 //{{{ actionBar() method
439 /**
440 * Shows the action bar if needed, and sends keyboard focus there.
441 * @since jEdit 4.2pre1
442 */
443 public void actionBar()
444 {
445 if(actionBar == null)
446 actionBar = new ActionBar(this,true);
447 if(actionBar.getParent() == null)
448 addToolBar(BOTTOM_GROUP,ACTION_BAR_LAYER,actionBar);
449
450 actionBar.goToActionBar();
451 } //}}}
452
453 //}}}
454
455 //{{{ Input handling
456
457 //{{{ getKeyEventInterceptor() method
458 /**
459 * Returns the listener that will handle all key events in this
460 * view, if any.
461 */
462 public KeyListener getKeyEventInterceptor()
463 {
464 return keyEventInterceptor;
465 } //}}}
466
467 //{{{ setKeyEventInterceptor() method
468 /**
469 * Sets the listener that will handle all key events in this
470 * view. For example, the complete word command uses this so
471 * that all key events are passed to the word list popup while
472 * it is visible.
473 * @param comp The component
474 */
475 public void setKeyEventInterceptor(KeyListener listener)
476 {
477 this.keyEventInterceptor = listener;
478 } //}}}
479
480 //{{{ getInputHandler() method
481 /**
482 * Returns the input handler.
483 */
484 public InputHandler getInputHandler()
485 {
486 return inputHandler;
487 } //}}}
488
489 //{{{ setInputHandler() method
490 /**
491 * Sets the input handler.
492 * @param inputHandler The new input handler
493 */
494 public void setInputHandler(InputHandler inputHandler)
495 {
496 this.inputHandler = inputHandler;
497 } //}}}
498
499 //{{{ getMacroRecorder() method
500 /**
501 * Returns the macro recorder.
502 */
503 public Macros.Recorder getMacroRecorder()
504 {
505 return recorder;
506 } //}}}
507
508 //{{{ setMacroRecorder() method
509 /**
510 * Sets the macro recorder.
511 * @param recorder The macro recorder
512 */
513 public void setMacroRecorder(Macros.Recorder recorder)
514 {
515 this.recorder = recorder;
516 } //}}}
517
518 //{{{ processKeyEvent() method
519 /**
520 * Forwards key events directly to the input handler.
521 * This is slightly faster than using a KeyListener
522 * because some Swing overhead is avoided.
523 */
524 public void processKeyEvent(KeyEvent evt)
525 {
526 processKeyEvent(evt,VIEW);
527 } //}}}
528
529 //{{{ processKeyEvent() method
530 /**
531 * Forwards key events directly to the input handler.
532 * This is slightly faster than using a KeyListener
533 * because some Swing overhead is avoided.
534 */
535 public void processKeyEvent(KeyEvent evt, boolean calledFromTextArea)
536 {
537 processKeyEvent(evt,calledFromTextArea
538 ? TEXT_AREA
539 : VIEW);
540 } //}}}
541
542 //{{{ processKeyEvent() method
543 public static final int VIEW = 0;
544 public static final int TEXT_AREA = 1;
545 public static final int ACTION_BAR = 2;
546 /**
547 * Forwards key events directly to the input handler.
548 * This is slightly faster than using a KeyListener
549 * because some Swing overhead is avoided.
550 */
551 public void processKeyEvent(KeyEvent evt, int from)
552 {
553 if(Debug.DUMP_KEY_EVENTS && from != VIEW)
554 {
555 Log.log(Log.DEBUG,this,"Key event: "
556 + GrabKeyDialog.toString(evt));
557 }
558
559 if(getTextArea().hasFocus() && from == VIEW)
560 return;
561
562 evt = _preprocessKeyEvent(evt);
563 if(evt == null)
564 return;
565
566 switch(evt.getID())
567 {
568 case KeyEvent.KEY_TYPED:
569 boolean focusOnTextArea = false;
570 // if the user pressed eg C+e n n in the
571 // search bar we want focus to go back there
572 // after the prefix is done
573 if(prefixFocusOwner != null)
574 {
575 if(prefixFocusOwner.isShowing())
576 {
577 prefixFocusOwner.requestFocus();
578 focusOnTextArea = true;
579 }
580 }
581
582 if(keyEventInterceptor != null)
583 keyEventInterceptor.keyTyped(evt);
584 else if(from == ACTION_BAR
585 || inputHandler.isPrefixActive()
586 || getTextArea().hasFocus())
587 {
588 KeyEventTranslator.Key keyStroke
589 = KeyEventTranslator
590 .translateKeyEvent(evt);
591 if(keyStroke != null)
592 {
593 if(inputHandler.handleKey(keyStroke))
594 evt.consume();
595 }
596 }
597
598 // we might have been closed as a result of
599 // the above
600 if(isClosed())
601 return;
602
603 // this is a weird hack.
604 // we don't want C+e a to insert 'a' in the
605 // search bar if the search bar has focus...
606 if(inputHandler.isPrefixActive())
607 {
608 if(getFocusOwner() instanceof JTextComponent)
609 {
610 prefixFocusOwner = getFocusOwner();
611 getTextArea().requestFocus();
612 }
613 else if(focusOnTextArea)
614 {
615 getTextArea().requestFocus();
616 }
617 else
618 {
619 prefixFocusOwner = null;
620 }
621 }
622 else
623 {
624 prefixFocusOwner = null;
625 }
626
627 break;
628 case KeyEvent.KEY_PRESSED:
629 if(keyEventInterceptor != null)
630 keyEventInterceptor.keyPressed(evt);
631 else
632 {
633 /* boolean */ focusOnTextArea = false;
634 if(prefixFocusOwner != null)
635 {
636 if(prefixFocusOwner.isShowing())
637 {
638 prefixFocusOwner.requestFocus();
639 focusOnTextArea = true;
640 }
641 prefixFocusOwner = null;
642 }
643
644 KeyEventTranslator.Key keyStroke
645 = KeyEventTranslator
646 .translateKeyEvent(evt);
647 if(keyStroke != null)
648 {
649 if(inputHandler.handleKey(keyStroke))
650 evt.consume();
651 }
652
653 // we might have been closed as a result of
654 // the above
655 if(isClosed())
656 return;
657
658 // this is a weird hack.
659 // we don't want C+e a to insert 'a' in the
660 // search bar if the search bar has focus...
661 if(inputHandler.isPrefixActive())
662 {
663 if(getFocusOwner() instanceof JTextComponent)
664 {
665 prefixFocusOwner = getFocusOwner();
666 getTextArea().requestFocus();
667 }
668 else if(focusOnTextArea)
669 {
670 getTextArea().requestFocus();
671 }
672 else
673 {
674 prefixFocusOwner = null;
675 }
676 }
677 else
678 {
679 prefixFocusOwner = null;
680 }
681 }
682 break;
683 case KeyEvent.KEY_RELEASED:
684 if(keyEventInterceptor != null)
685 keyEventInterceptor.keyReleased(evt);
686 break;
687 }
688
689 if(!evt.isConsumed())
690 super.processKeyEvent(evt);
691 } //}}}
692
693 //}}}
694
695 //{{{ Buffers, edit panes, split panes
696
697 //{{{ splitHorizontally() method
698 /**
699 * Splits the view horizontally.
700 * @since jEdit 4.1pre2
701 */
702 public EditPane splitHorizontally()
703 {
704 return split(JSplitPane.VERTICAL_SPLIT);
705 } //}}}
706
707 //{{{ splitVertically() method
708 /**
709 * Splits the view vertically.
710 * @since jEdit 4.1pre2
711 */
712 public EditPane splitVertically()
713 {
714 return split(JSplitPane.HORIZONTAL_SPLIT);
715 } //}}}
716
717 //{{{ split() method
718 /**
719 * Splits the view.
720 * @since jEdit 4.1pre2
721 */
722 public EditPane split(int orientation)
723 {
724 editPane.saveCaretInfo();
725 EditPane oldEditPane = editPane;
726 setEditPane(createEditPane(oldEditPane.getBuffer()));
727 editPane.loadCaretInfo();
728
729 JComponent oldParent = (JComponent)oldEditPane.getParent();
730
731 final JSplitPane newSplitPane = new JSplitPane(orientation);
732 newSplitPane.setOneTouchExpandable(true);
733 newSplitPane.setBorder(null);
734 newSplitPane.setMinimumSize(new Dimension(0,0));
735
736 int parentSize = (orientation == JSplitPane.VERTICAL_SPLIT
737 ? oldEditPane.getHeight() : oldEditPane.getWidth());
738 final int dividerPosition = (int)((double)(parentSize
739 - newSplitPane.getDividerSize()) * 0.5);
740 newSplitPane.setDividerLocation(dividerPosition);
741
742 if(oldParent instanceof JSplitPane)
743 {
744 JSplitPane oldSplitPane = (JSplitPane)oldParent;
745 int dividerPos = oldSplitPane.getDividerLocation();
746
747 Component left = oldSplitPane.getLeftComponent();
748
749 if(left == oldEditPane)
750 oldSplitPane.setLeftComponent(newSplitPane);
751 else
752 oldSplitPane.setRightComponent(newSplitPane);
753
754 newSplitPane.setLeftComponent(oldEditPane);
755 newSplitPane.setRightComponent(editPane);
756
757 oldSplitPane.setDividerLocation(dividerPos);
758 }
759 else
760 {
761 this.splitPane = newSplitPane;
762
763 newSplitPane.setLeftComponent(oldEditPane);
764 newSplitPane.setRightComponent(editPane);
765
766 oldParent.add(newSplitPane,0);
767 oldParent.revalidate();
768 }
769
770 SwingUtilities.invokeLater(new Runnable()
771 {
772 public void run()
773 {
774 newSplitPane.setDividerLocation(dividerPosition);
775 }
776 });
777
778 editPane.focusOnTextArea();
779
780 return editPane;
781 } //}}}
782
783 //{{{ unsplit() method
784 /**
785 * Unsplits the view.
786 * @since jEdit 2.3pre2
787 */
788 public void unsplit()
789 {
790 if(splitPane != null)
791 {
792 EditPane[] editPanes = getEditPanes();
793 for(int i = 0; i < editPanes.length; i++)
794 {
795 EditPane _editPane = editPanes[i];
796 if(editPane != _editPane)
797 _editPane.close();
798 }
799
800 JComponent parent = (JComponent)splitPane.getParent();
801
802 parent.remove(splitPane);
803 parent.add(editPane,0);
804 parent.revalidate();
805
806 splitPane = null;
807 updateTitle();
808
809 editPane.focusOnTextArea();
810 }
811 else
812 getToolkit().beep();
813 } //}}}
814
815 //{{{ unsplitCurrent() method
816 /**
817 * Removes the current split.
818 * @since jEdit 2.3pre2
819 */
820 public void unsplitCurrent()
821 {
822 if(splitPane != null)
823 {
824 // find first split pane parenting current edit pane
825 Component comp = editPane;
826 while(!(comp instanceof JSplitPane))
827 {
828 comp = comp.getParent();
829 }
830
831 // get rid of any edit pane that is a child
832 // of the current edit pane's parent splitter
833 EditPane[] editPanes = getEditPanes();
834 for(int i = 0; i < editPanes.length; i++)
835 {
836 EditPane _editPane = editPanes[i];
837 if(GUIUtilities.isAncestorOf(comp,_editPane)
838 && _editPane != editPane)
839 _editPane.close();
840 }
841
842 JComponent parent = (JComponent)comp.getParent();
843
844 if(parent instanceof JSplitPane)
845 {
846 JSplitPane parentSplit = (JSplitPane)parent;
847 int pos = parentSplit.getDividerLocation();
848 if(parentSplit.getLeftComponent() == comp)
849 parentSplit.setLeftComponent(editPane);
850 else
851 parentSplit.setRightComponent(editPane);
852 parentSplit.setDividerLocation(pos);
853 }
854 else
855 {
856 parent.remove(comp);
857 parent.add(editPane,0);
858 splitPane = null;
859 }
860
861 parent.revalidate();
862
863 updateTitle();
864
865 editPane.focusOnTextArea();
866 }
867 else
868 getToolkit().beep();
869 } //}}}
870
871 //{{{ nextTextArea() method
872 /**
873 * Moves keyboard focus to the next text area.
874 * @since jEdit 2.7pre4
875 */
876 public void nextTextArea()
877 {
878 EditPane[] editPanes = getEditPanes();
879 for(int i = 0; i < editPanes.length; i++)
880 {
881 if(editPane == editPanes[i])
882 {
883 if(i == editPanes.length - 1)
884 editPanes[0].focusOnTextArea();
885 else
886 editPanes[i+1].focusOnTextArea();
887 break;
888 }
889 }
890 } //}}}
891
892 //{{{ prevTextArea() method
893 /**
894 * Moves keyboard focus to the previous text area.
895 * @since jEdit 2.7pre4
896 */
897 public void prevTextArea()
898 {
899 EditPane[] editPanes = getEditPanes();
900 for(int i = 0; i < editPanes.length; i++)
901 {
902 if(editPane == editPanes[i])
903 {
904 if(i == 0)
905 editPanes[editPanes.length - 1].focusOnTextArea();
906 else
907 editPanes[i-1].focusOnTextArea();
908 break;
909 }
910 }
911 } //}}}
912
913 //{{{ getSplitPane() method
914 /**
915 * Returns the top-level split pane, if any.
916 * @since jEdit 2.3pre2
917 */
918 public JSplitPane getSplitPane()
919 {
920 return splitPane;
921 } //}}}
922
923 //{{{ getBuffer() method
924 /**
925 * Returns the current edit pane's buffer.
926 */
927 public Buffer getBuffer()
928 {
929 if(editPane == null)
930 return null;
931 else
932 return editPane.getBuffer();
933 } //}}}
934
935 //{{{ setBuffer() method
936 /**
937 * Sets the current edit pane's buffer.
938 */
939 public void setBuffer(Buffer buffer)
940 {
941 editPane.setBuffer(buffer);
942 } //}}}
943
944 //{{{ goToBuffer() method
945 /**
946 * If this buffer is open in one of the view's edit panes, sets focus
947 * to that edit pane. Otherwise, opens the buffer in the currently
948 * active edit pane.
949 * @param buffer The buffer
950 * @since jEdit 4.2pre1
951 */
952 public EditPane goToBuffer(Buffer buffer)
953 {
954 if(editPane.getBuffer() == buffer)
955 {
956 editPane.focusOnTextArea();
957 return editPane;
958 }
959
960 EditPane[] editPanes = getEditPanes();
961 for(int i = 0; i < editPanes.length; i++)
962 {
963 EditPane ep = editPanes[i];
964 if(ep.getBuffer() == buffer
965 /* ignore zero-height splits, etc */
966 && ep.getTextArea().getVisibleLines() > 1)
967 {
968 setEditPane(ep);
969 ep.focusOnTextArea();
970 return ep;
971 }
972 }
973 setBuffer(buffer);
974 return editPane;
975 } //}}}
976
977 //{{{ getTextArea() method
978 /**
979 * Returns the current edit pane's text area.
980 */
981 public JEditTextArea getTextArea()
982 {
983 if(editPane == null)
984 return null;
985 else
986 return editPane.getTextArea();
987 } //}}}
988
989 //{{{ getEditPane() method
990 /**
991 * Returns the current edit pane.
992 * @since jEdit 2.5pre2
993 */
994 public EditPane getEditPane()
995 {
996 return editPane;
997 } //}}}
998
999 //{{{ getEditPanes() method
1000 /**
1001 * Returns all edit panes.
1002 * @since jEdit 2.5pre2
1003 */
1004 public EditPane[] getEditPanes()
1005 {
1006 if(splitPane == null)
1007 {
1008 EditPane[] ep = { editPane };
1009 return ep;
1010 }
1011 else
1012 {
1013 Vector vec = new Vector();
1014 getEditPanes(vec,splitPane);
1015 EditPane[] ep = new EditPane[vec.size()];
1016 vec.copyInto(ep);
1017 return ep;
1018 }
1019 } //}}}
1020
1021 //{{{ getViewConfig() method
1022 /**
1023 * @since jEdit 4.2pre1
1024 */
1025 public ViewConfig getViewConfig()
1026 {
1027 StringBuffer splitConfig = new StringBuffer();
1028 if(splitPane != null)
1029 getSplitConfig(splitPane,splitConfig);
1030 else
1031 {
1032 splitConfig.append('"');
1033 splitConfig.append(MiscUtilities.charsToEscapes(
1034 getBuffer().getPath()));
1035 splitConfig.append("\" buffer");
1036 }
1037
1038 ViewConfig config = new ViewConfig();
1039 config.plainView = isPlainView();
1040 config.splitConfig = splitConfig.toString();
1041 config.x = getX();
1042 config.y = getY();
1043 config.width = getWidth();
1044 config.height = getHeight();
1045 config.extState = GUIUtilities.getExtendedState(this);
1046
1047 config.top = dockableWindowManager.getTopDockingArea().getCurrent();
1048 config.left = dockableWindowManager.getLeftDockingArea().getCurrent();
1049 config.bottom = dockableWindowManager.getBottomDockingArea().getCurrent();
1050 config.right = dockableWindowManager.getRightDockingArea().getCurrent();
1051
1052 config.topPos = dockableWindowManager.getTopDockingArea().getDimension();
1053 config.leftPos = dockableWindowManager.getLeftDockingArea().getDimension();
1054 config.bottomPos = dockableWindowManager.getBottomDockingArea().getDimension();
1055 config.rightPos = dockableWindowManager.getRightDockingArea().getDimension();
1056
1057 return config;
1058 } //}}}
1059
1060 //}}}
1061
1062 //{{{ isClosed() method
1063 /**
1064 * Returns true if this view has been closed with
1065 * {@link jEdit#closeView(View)}.
1066 */
1067 public boolean isClosed()
1068 {
1069 return closed;
1070 } //}}}
1071
1072 //{{{ isPlainView() method
1073 /**
1074 * Returns true if this is an auxilliary view with no dockable windows.
1075 * @since jEdit 4.1pre2
1076 */
1077 public boolean isPlainView()
1078 {
1079 return plainView;
1080 } //}}}
1081
1082 //{{{ getNext() method
1083 /**
1084 * Returns the next view in the list.
1085 */
1086 public View getNext()
1087 {
1088 return next;
1089 } //}}}
1090
1091 //{{{ getPrev() method
1092 /**
1093 * Returns the previous view in the list.
1094 */
1095 public View getPrev()
1096 {
1097 return prev;
1098 } //}}}
1099
1100 //{{{ handleMessage() method
1101 public void handleMessage(EBMessage msg)
1102 {
1103 if(msg instanceof PropertiesChanged)
1104 propertiesChanged();
1105 else if(msg instanceof SearchSettingsChanged)
1106 {
1107 if(searchBar != null)
1108 searchBar.update();
1109 }
1110 else if(msg instanceof BufferUpdate)
1111 handleBufferUpdate((BufferUpdate)msg);
1112 else if(msg instanceof EditPaneUpdate)
1113 handleEditPaneUpdate((EditPaneUpdate)msg);
1114 else if(msg instanceof PluginUpdate)
1115 {
1116 if(actionBar != null)
1117 actionBar.actionListChanged();
1118 }
1119 } //}}}
1120
1121 //{{{ getMinimumSize() method
1122 public Dimension getMinimumSize()
1123 {
1124 return new Dimension(0,0);
1125 } //}}}
1126
1127 //{{{ setWaitSocket() method
1128 /**
1129 * This socket is closed when the buffer is closed.
1130 */
1131 public void setWaitSocket(Socket waitSocket)
1132 {
1133 this.waitSocket = waitSocket;
1134 } //}}}
1135
1136 //{{{ toString() method
1137 public String toString()
1138 {
1139 return getClass().getName() + "["
1140 + (jEdit.getActiveView() == this
1141 ? "active" : "inactive")
1142 + "]";
1143 } //}}}
1144
1145 //{{{ Package-private members
1146 View prev;
1147 View next;
1148
1149 //{{{ View constructor
1150 View(Buffer buffer, ViewConfig config)
1151 {
1152 this.plainView = config.plainView;
1153
1154 enableEvents(AWTEvent.KEY_EVENT_MASK);
1155
1156 setIconImage(GUIUtilities.getEditorIcon());
1157
1158 dockableWindowManager = new DockableWindowManager(this,config);
1159
1160 topToolBars = new JPanel(new VariableGridLayout(
1161 VariableGridLayout.FIXED_NUM_COLUMNS,
1162 1));
1163 bottomToolBars = new JPanel(new VariableGridLayout(
1164 VariableGridLayout.FIXED_NUM_COLUMNS,
1165 1));
1166
1167 toolBarManager = new ToolBarManager(topToolBars, bottomToolBars);
1168
1169 status = new StatusBar(this);
1170
1171 inputHandler = new DefaultInputHandler(this,(DefaultInputHandler)
1172 jEdit.getInputHandler());
1173
1174 try
1175 {
1176 Component comp = restoreSplitConfig(buffer,config.splitConfig);
1177 dockableWindowManager.add(comp,0);
1178 }
1179 catch(IOException e)
1180 {
1181 // this should never throw an exception.
1182 throw new InternalError();
1183 }
1184
1185 getContentPane().add(BorderLayout.CENTER,dockableWindowManager);
1186
1187 dockableWindowManager.init();
1188
1189 // tool bar and status bar gets added in propertiesChanged()
1190 // depending in the 'tool bar alternate layout' setting.
1191 propertiesChanged();
1192
1193 setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
1194 addWindowListener(new WindowHandler());
1195
1196 EditBus.addToBus(this);
1197
1198 SearchDialog.preloadSearchDialog(this);
1199 } //}}}
1200
1201 //{{{ close() method
1202 void close()
1203 {
1204 GUIUtilities.saveGeometry(this,plainView ? "plain-view" : "view");
1205 closed = true;
1206
1207 // save dockable window geometry, and close 'em
1208 dockableWindowManager.close();
1209
1210 EditBus.removeFromBus(this);
1211 dispose();
1212
1213 EditPane[] editPanes = getEditPanes();
1214 for(int i = 0; i < editPanes.length; i++)
1215 editPanes[i].close();
1216
1217 // null some variables so that retaining references
1218 // to closed views won't hurt as much.
1219 toolBarManager = null;
1220 toolBar = null;
1221 searchBar = null;
1222 splitPane = null;
1223 inputHandler = null;
1224 recorder = null;
1225
1226 getContentPane().removeAll();
1227
1228 // notify clients with -wait
1229 if(waitSocket != null)
1230 {
1231 try
1232 {
1233 waitSocket.getOutputStream().write('\0');
1234 waitSocket.getOutputStream().flush();
1235 waitSocket.getInputStream().close();
1236 waitSocket.getOutputStream().close();
1237 waitSocket.close();
1238 }
1239 catch(IOException io)
1240 {
1241 //Log.log(Log.ERROR,this,io);
1242 }
1243 }
1244 } //}}}
1245
1246 //{{{ updateTitle() method
1247 /**
1248 * Updates the title bar.
1249 */
1250 void updateTitle()
1251 {
1252 Vector buffers = new Vector();
1253 EditPane[] editPanes = getEditPanes();
1254 for(int i = 0; i < editPanes.length; i++)
1255 {
1256 Buffer buffer = editPanes[i].getBuffer();
1257 if(buffers.indexOf(buffer) == -1)
1258 buffers.addElement(buffer);
1259 }
1260
1261 StringBuffer title = new StringBuffer(jEdit.getProperty("view.title"));
1262 for(int i = 0; i < buffers.size(); i++)
1263 {
1264 if(i != 0)
1265 title.append(", ");
1266
1267 Buffer buffer = (Buffer)buffers.elementAt(i);
1268 title.append((showFullPath && !buffer.isNewFile())
1269 ? buffer.getPath() : buffer.getName());
1270 if(buffer.isDirty())
1271 title.append(jEdit.getProperty("view.title.dirty"));
1272 }
1273 setTitle(title.toString());
1274 } //}}}
1275
1276 //}}}
1277
1278 //{{{ Private members
1279
1280 //{{{ Instance variables
1281 private boolean closed;
1282
1283 private DockableWindowManager dockableWindowManager;
1284
1285 private JPanel topToolBars;
1286 private JPanel bottomToolBars;
1287 private ToolBarManager toolBarManager;
1288
1289 private Box toolBar;
1290 private SearchBar searchBar;
1291 private ActionBar actionBar;
1292
1293 private EditPane editPane;
1294 private JSplitPane splitPane;
1295
1296 private StatusBar status;
1297
1298 private KeyListener keyEventInterceptor;
1299 private InputHandler inputHandler;
1300 private Macros.Recorder recorder;
1301 private Component prefixFocusOwner;
1302
1303 private int waitCount;
1304
1305 private boolean showFullPath;
1306
1307 private boolean plainView;
1308
1309 private Socket waitSocket;
1310 //}}}
1311
1312 //{{{ getEditPanes() method
1313 private void getEditPanes(Vector vec, Component comp)
1314 {
1315 if(comp instanceof EditPane)
1316 vec.addElement(comp);
1317 else if(comp instanceof JSplitPane)
1318 {
1319 JSplitPane split = (JSplitPane)comp;
1320 getEditPanes(vec,split.getLeftComponent());
1321 getEditPanes(vec,split.getRightComponent());
1322 }
1323 } //}}}
1324
1325 //{{{ getSplitConfig() method
1326 /*
1327 * The split config is recorded in a simple RPN "language".
1328 */
1329 private void getSplitConfig(JSplitPane splitPane,
1330 StringBuffer splitConfig)
1331 {
1332 Component right = splitPane.getRightComponent();
1333 if(right instanceof JSplitPane)
1334 getSplitConfig((JSplitPane)right,splitConfig);
1335 else
1336 {
1337 splitConfig.append('"');
1338 splitConfig.append(MiscUtilities.charsToEscapes(
1339 ((EditPane)right).getBuffer().getPath()));
1340 splitConfig.append("\" buffer");
1341 }
1342
1343 splitConfig.append(' ');
1344
1345 Component left = splitPane.getLeftComponent();
1346 if(left instanceof JSplitPane)
1347 getSplitConfig((JSplitPane)left,splitConfig);
1348 else
1349 {
1350 splitConfig.append('"');
1351 splitConfig.append(MiscUtilities.charsToEscapes(
1352 ((EditPane)left).getBuffer().getPath()));
1353 splitConfig.append("\" buffer");
1354 }
1355
1356 splitConfig.append(' ');
1357 splitConfig.append(splitPane.getDividerLocation());
1358 splitConfig.append(' ');
1359 splitConfig.append(splitPane.getOrientation()
1360 == JSplitPane.VERTICAL_SPLIT ? "vertical" : "horizontal");
1361 } //}}}
1362
1363 //{{{ restoreSplitConfig() method
1364 private Component restoreSplitConfig(Buffer buffer, String splitConfig)
1365 throws IOException
1366 // this is where checked exceptions piss me off. this method only uses
1367 // a StringReader which can never throw an exception...
1368 {
1369 if(buffer != null)
1370 return (editPane = createEditPane(buffer));
1371 else if(splitConfig == null)
1372 return (editPane = createEditPane(jEdit.getFirstBuffer()));
1373
1374 Buffer[] buffers = jEdit.getBuffers();
1375
1376 Stack stack = new Stack();
1377
1378 // we create a stream tokenizer for parsing a simple
1379 // stack-based language
1380 StreamTokenizer st = new StreamTokenizer(new StringReader(
1381 splitConfig));
1382 st.whitespaceChars(0,' ');
1383 /* all printable ASCII characters */
1384 st.wordChars('#','~');
1385 st.commentChar('!');
1386 st.quoteChar('"');
1387 st.eolIsSignificant(false);
1388
1389loop: for(;;)
1390 {
1391 switch(st.nextToken())
1392 {
1393 case StreamTokenizer.TT_EOF:
1394 break loop;
1395 case StreamTokenizer.TT_WORD:
1396 if(st.sval.equals("vertical") ||
1397 st.sval.equals("horizontal"))
1398 {
1399 int orientation
1400 = (st.sval.equals("vertical")
1401 ? JSplitPane.VERTICAL_SPLIT
1402 : JSplitPane.HORIZONTAL_SPLIT);
1403 int divider = ((Integer)stack.pop())
1404 .intValue();
1405 stack.push(splitPane = new JSplitPane(
1406 orientation,
1407 (Component)stack.pop(),
1408 (Component)stack.pop()));
1409 splitPane.setOneTouchExpandable(true);
1410 splitPane.setBorder(null);
1411 splitPane.setMinimumSize(
1412 new Dimension(0,0));
1413 splitPane.setDividerLocation(divider);
1414 }
1415 else if(st.sval.equals("buffer"))
1416 {
1417 Object obj = stack.pop();
1418 if(obj instanceof Integer)
1419 {
1420 int index = ((Integer)obj).intValue();
1421 if(index >= 0 && index < buffers.length)
1422 buffer = buffers[index];
1423 }
1424 else if(obj instanceof String)
1425 {
1426 String path = (String)obj;
1427 buffer = jEdit.getBuffer(path);
1428 }
1429
1430 if(buffer == null)
1431 buffer = jEdit.getFirstBuffer();
1432
1433 stack.push(editPane = createEditPane(
1434 buffer));
1435 }
1436 break;
1437 case StreamTokenizer.TT_NUMBER:
1438 stack.push(new Integer((int)st.nval));
1439 break;
1440 case '"':
1441 stack.push(st.sval);
1442 break;
1443 }
1444 }
1445
1446 updateGutterBorders();
1447
1448 return (Component)stack.peek();
1449 } //}}}
1450
1451 //{{{ propertiesChanged() method
1452 /**
1453 * Reloads various settings from the properties.
1454 */
1455 private void propertiesChanged()
1456 {
1457 setJMenuBar(GUIUtilities.loadMenuBar("view.mbar"));
1458
1459 loadToolBars();
1460
1461 showFullPath = jEdit.getBooleanProperty("view.showFullPath");
1462 updateTitle();
1463
1464 status.propertiesChanged();
1465
1466 removeToolBar(status);
1467 getContentPane().remove(status);
1468
1469 if(jEdit.getBooleanProperty("view.toolbar.alternateLayout"))
1470 {
1471 getContentPane().add(BorderLayout.NORTH,topToolBars);
1472 getContentPane().add(BorderLayout.SOUTH,bottomToolBars);
1473 if(!plainView && jEdit.getBooleanProperty("view.status.visible"))
1474 addToolBar(BOTTOM_GROUP,STATUS_BAR_LAYER,status);
1475 }
1476 else
1477 {
1478 dockableWindowManager.add(topToolBars,
1479 DockableWindowManager.DockableLayout
1480 .TOP_TOOLBARS,0);
1481 dockableWindowManager.add(bottomToolBars,
1482 DockableWindowManager.DockableLayout
1483 .BOTTOM_TOOLBARS,0);
1484 if(!plainView && jEdit.getBooleanProperty("view.status.visible"))
1485 getContentPane().add(BorderLayout.SOUTH,status);
1486 }
1487
1488 getRootPane().revalidate();
1489
1490 //SwingUtilities.updateComponentTreeUI(getRootPane());
1491 } //}}}
1492
1493 //{{{ loadToolBars() method
1494 private void loadToolBars()
1495 {
1496 if(jEdit.getBooleanProperty("view.showToolbar") && !plainView)
1497 {
1498 if(toolBar != null)
1499 toolBarManager.removeToolBar(toolBar);
1500
1501 toolBar = GUIUtilities.loadToolBar("view.toolbar");
1502
1503 addToolBar(TOP_GROUP, SYSTEM_BAR_LAYER, toolBar);
1504 }
1505 else if(toolBar != null)
1506 {
1507 removeToolBar(toolBar);
1508 toolBar = null;
1509 }
1510
1511 if(searchBar != null)
1512 removeToolBar(searchBar);
1513
1514 if(jEdit.getBooleanProperty("view.showSearchbar") && !plainView)
1515 {
1516 if(searchBar == null)
1517 searchBar = new SearchBar(this,false);
1518 searchBar.propertiesChanged();
1519 addToolBar(TOP_GROUP,SEARCH_BAR_LAYER,searchBar);
1520 }
1521 } //}}}
1522
1523 //{{{ createEditPane() method
1524 private EditPane createEditPane(Buffer buffer)
1525 {
1526 EditPane editPane = new EditPane(this,buffer);
1527 JEditTextArea textArea = editPane.getTextArea();
1528 textArea.addFocusListener(new FocusHandler());
1529 textArea.addCaretListener(new CaretHandler());
1530 textArea.addScrollListener(new ScrollHandler());
1531 EditBus.send(new EditPaneUpdate(editPane,EditPaneUpdate.CREATED));
1532 return editPane;
1533 } //}}}
1534
1535 //{{{ setEditPane() method
1536 private void setEditPane(EditPane editPane)
1537 {
1538 this.editPane = editPane;
1539 status.updateCaretStatus();
1540 status.updateBufferStatus();
1541 status.updateMiscStatus();
1542
1543 // repaint the gutter so that the border color
1544 // reflects the focus state
1545 updateGutterBorders();
1546
1547 EditBus.send(new ViewUpdate(this,ViewUpdate.EDIT_PANE_CHANGED));
1548 } //}}}
1549
1550 //{{{ handleBufferUpdate() method
1551 private void handleBufferUpdate(BufferUpdate msg)
1552 {
1553 Buffer buffer = msg.getBuffer();
1554 if(msg.getWhat() == BufferUpdate.DIRTY_CHANGED
1555 || msg.getWhat() == BufferUpdate.LOADED)
1556 {
1557 EditPane[] editPanes = getEditPanes();
1558 for(int i = 0; i < editPanes.length; i++)
1559 {
1560 if(editPanes[i].getBuffer() == buffer)
1561 {
1562 updateTitle();
1563 break;
1564 }
1565 }
1566 }
1567 } //}}}
1568
1569 //{{{ handleEditPaneUpdate() method
1570 private void handleEditPaneUpdate(EditPaneUpdate msg)
1571 {
1572 EditPane editPane = msg.getEditPane();
1573 if(editPane.getView() == this
1574 && msg.getWhat() == EditPaneUpdate.BUFFER_CHANGED
1575 && editPane.getBuffer().isLoaded())
1576 {
1577 status.updateCaretStatus();
1578 status.updateBufferStatus();
1579 status.updateMiscStatus();
1580 }
1581 } //}}}
1582
158