KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > gjt > sp > jedit > textarea > DisplayManager


1 /*
2  * DisplayManager.java - Low-level text display
3  * :tabSize=8:indentSize=8:noTabs=false:
4  * :folding=explicit:collapseFolds=1:
5  *
6  * Copyright (C) 2001, 2005 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.textarea;
24
25 //{{{ Imports
26
import java.awt.Toolkit JavaDoc;
27 import java.util.*;
28 import org.gjt.sp.jedit.buffer.*;
29 import org.gjt.sp.jedit.Debug;
30 import org.gjt.sp.util.Log;
31 //}}}
32

33 /**
34  * Manages low-level text display tasks, such as folding.
35  *
36  * @since jEdit 4.2pre1
37  * @author Slava Pestov
38  * @version $Id: DisplayManager.java 7156 2006-10-02 21:33:17Z kpouer $
39  */

40 public class DisplayManager
41 {
42     //{{{ Static part
43

44     //{{{ getDisplayManager() method
45
static DisplayManager getDisplayManager(JEditBuffer buffer,
46         TextArea textArea)
47     {
48         List l = (List)bufferMap.get(buffer);
49         DisplayManager dmgr;
50         if(l == null)
51         {
52             l = new LinkedList();
53             bufferMap.put(buffer,l);
54         }
55
56         /* An existing display manager's fold visibility map
57         that a new display manager will inherit */

58         DisplayManager copy = null;
59         Iterator liter = l.iterator();
60         while(liter.hasNext())
61         {
62             dmgr = (DisplayManager)liter.next();
63             copy = dmgr;
64             if(!dmgr.inUse && dmgr.textArea == textArea)
65             {
66                 dmgr.inUse = true;
67                 return dmgr;
68             }
69         }
70
71         // if we got here, no unused display manager in list
72
dmgr = new DisplayManager(buffer,textArea,copy);
73         dmgr.inUse = true;
74         l.add(dmgr);
75
76         return dmgr;
77     } //}}}
78

79     //{{{ releaseDisplayManager() method
80
static void releaseDisplayManager(DisplayManager dmgr)
81     {
82         dmgr.inUse = false;
83     } //}}}
84

85     //{{{ bufferClosed() method
86
public static void bufferClosed(JEditBuffer buffer)
87     {
88         bufferMap.remove(buffer);
89     } //}}}
90

91     //{{{ textAreaDisposed() method
92
static void textAreaDisposed(TextArea textArea)
93     {
94         Iterator biter = bufferMap.values().iterator();
95         while(biter.hasNext())
96         {
97             List l = (List)biter.next();
98             Iterator liter = l.iterator();
99             while(liter.hasNext())
100             {
101                 DisplayManager dmgr = (DisplayManager)
102                     liter.next();
103                 if(dmgr.textArea == textArea)
104                 {
105                     dmgr.dispose();
106                     liter.remove();
107                 }
108             }
109         }
110     } //}}}
111

112     private static Map bufferMap = new HashMap();
113     //}}}
114

115     //{{{ getBuffer() method
116
/**
117      * @since jEdit 4.3pre3
118      */

119     public JEditBuffer getBuffer()
120     {
121         return buffer;
122     } //}}}
123

124     //{{{ isLineVisible() method
125
/**
126      * Returns if the specified line is visible.
127      * @param line A physical line index
128      * @since jEdit 4.2pre1
129      */

130     public final boolean isLineVisible(int line)
131     {
132         return folds.search(line) % 2 == 0;
133     } //}}}
134

135     //{{{ getFirstVisibleLine() method
136
/**
137      * Returns the physical line number of the first visible line.
138      * @since jEdit 4.2pre1
139      */

140     public int getFirstVisibleLine()
141     {
142         return folds.first();
143     } //}}}
144

145     //{{{ getLastVisibleLine() method
146
/**
147      * Returns the physical line number of the last visible line.
148      * @since jEdit 4.2pre1
149      */

150     public int getLastVisibleLine()
151     {
152         return folds.last();
153     } //}}}
154

155     //{{{ getNextVisibleLine() method
156
/**
157      * Returns the next visible line after the specified line index.
158      * @param line A physical line index
159      * @since jEdit 4.0pre1
160      */

161     public int getNextVisibleLine(int line)
162     {
163         if(line < 0 || line >= buffer.getLineCount())
164             throw new ArrayIndexOutOfBoundsException JavaDoc(line);
165
166         return folds.next(line);
167     } //}}}
168

169     //{{{ getPrevVisibleLine() method
170
/**
171      * Returns the previous visible line before the specified line index.
172      * @param line A physical line index
173      * @since jEdit 4.0pre1
174      */

175     public int getPrevVisibleLine(int line)
176     {
177         if(line < 0 || line >= buffer.getLineCount())
178             throw new ArrayIndexOutOfBoundsException JavaDoc(line);
179
180         return folds.prev(line);
181     } //}}}
182

183     //{{{ getScreenLineCount() method
184
public final int getScreenLineCount(int line)
185     {
186         if(!screenLineMgr.isScreenLineCountValid(line))
187             throw new RuntimeException JavaDoc("Invalid screen line count: " + line);
188
189         return screenLineMgr.getScreenLineCount(line);
190     } //}}}
191

192     //{{{ getScrollLineCount() method
193
public final int getScrollLineCount()
194     {
195         return scrollLineCount.scrollLine;
196     } //}}}
197

198     //{{{ collapseFold() method
199
/**
200      * Collapses the fold at the specified physical line index.
201      * @param line A physical line index
202      * @since jEdit 4.2pre1
203      */

204     public void collapseFold(int line)
205     {
206         int lineCount = buffer.getLineCount();
207         int start = 0;
208         int end = lineCount - 1;
209
210         // if the caret is on a collapsed fold, collapse the
211
// parent fold
212
if(line != 0
213             && line != buffer.getLineCount() - 1
214             && buffer.isFoldStart(line)
215             && !isLineVisible(line + 1))
216         {
217             line--;
218         }
219
220         int initialFoldLevel = buffer.getFoldLevel(line);
221
222         //{{{ Find fold start and end...
223
if(line != lineCount - 1
224             && buffer.getFoldLevel(line + 1) > initialFoldLevel)
225         {
226             // this line is the start of a fold
227
start = line + 1;
228
229             for(int i = line + 1; i < lineCount; i++)
230             {
231                 if(buffer.getFoldLevel(i) <= initialFoldLevel)
232                 {
233                     end = i - 1;
234                     break;
235                 }
236             }
237         }
238         else
239         {
240             boolean ok = false;
241
242             // scan backwards looking for the start
243
for(int i = line - 1; i >= 0; i--)
244             {
245                 if(buffer.getFoldLevel(i) < initialFoldLevel)
246                 {
247                     start = i + 1;
248                     ok = true;
249                     break;
250                 }
251             }
252
253             if(!ok)
254             {
255                 // no folds in buffer
256
return;
257             }
258
259             for(int i = line + 1; i < lineCount; i++)
260             {
261                 if(buffer.getFoldLevel(i) < initialFoldLevel)
262                 {
263                     end = i - 1;
264                     break;
265                 }
266             }
267         } //}}}
268

269         // Collapse the fold...
270
hideLineRange(start,end);
271
272         notifyScreenLineChanges();
273         textArea.foldStructureChanged();
274     } //}}}
275

276     //{{{ expandFold() method
277
/**
278      * Expands the fold at the specified physical line index.
279      * @param line A physical line index
280      * @param fully If true, all subfolds will also be expanded
281      * @since jEdit 4.2pre1
282      */

283     public int expandFold(int line, boolean fully)
284     {
285         // the first sub-fold. used by JEditTextArea.expandFold().
286
int returnValue = -1;
287
288         int lineCount = buffer.getLineCount();
289         int start = 0;
290         int end = lineCount - 1;
291
292         int initialFoldLevel = buffer.getFoldLevel(line);
293
294         //{{{ Find fold start and fold end...
295
if(line != lineCount - 1
296             && isLineVisible(line)
297             && !isLineVisible(line + 1)
298             && buffer.getFoldLevel(line + 1) > initialFoldLevel)
299         {
300             // this line is the start of a fold
301

302             int index = folds.search(line + 1);
303             if(index == -1)
304             {
305                 expandAllFolds();
306                 return -1;
307             }
308
309             start = folds.lookup(index);
310             if(index != folds.count() - 1)
311                 end = folds.lookup(index + 1) - 1;
312             else
313             {
314                 start = line + 1;
315
316                 for(int i = line + 1; i < lineCount; i++)
317                 {
318                     if(/* isLineVisible(i) && */
319                         buffer.getFoldLevel(i) <= initialFoldLevel)
320                     {
321                         end = i - 1;
322                         break;
323                     }
324                 }
325             }
326         }
327         else
328         {
329             int index = folds.search(line);
330             if(index == -1)
331             {
332                 expandAllFolds();
333                 return -1;
334             }
335
336             start = folds.lookup(index);
337             if(index != folds.count() - 1)
338                 end = folds.lookup(index + 1) - 1;
339             else
340             {
341                 for(int i = line + 1; i < lineCount; i++)
342                 {
343                     //XXX
344
if((isLineVisible(i) &&
345                         buffer.getFoldLevel(i) < initialFoldLevel)
346                         || i == getLastVisibleLine())
347                     {
348                         end = i - 1;
349                         break;
350                     }
351                 }
352             }
353         } //}}}
354

355         //{{{ Expand the fold...
356
if(fully)
357         {
358             showLineRange(start,end);
359         }
360         else
361         {
362             // we need a different value of initialFoldLevel here!
363
initialFoldLevel = buffer.getFoldLevel(start);
364
365             int firstVisible = start;
366
367             for(int i = start; i <= end; i++)
368             {
369                 if(buffer.getFoldLevel(i) > initialFoldLevel)
370                 {
371                     if(returnValue == -1
372                         && i != 0
373                         && buffer.isFoldStart(i - 1))
374                     {
375                         returnValue = i - 1;
376                     }
377
378                     if(firstVisible != i)
379                     {
380                         showLineRange(firstVisible,i - 1);
381                     }
382                     firstVisible = i + 1;
383                 }
384             }
385
386             if(firstVisible == end + 1)
387                 returnValue = -1;
388             else
389                 showLineRange(firstVisible,end);
390
391             if(!isLineVisible(line))
392             {
393                 // this is a hack, and really needs to be done better.
394
expandFold(line,false);
395                 return returnValue;
396             }
397         } //}}}
398

399         notifyScreenLineChanges();
400         textArea.foldStructureChanged();
401
402         return returnValue;
403     } //}}}
404

405     //{{{ expandAllFolds() method
406
/**
407      * Expands all folds.
408      * @since jEdit 4.2pre1
409      */

410     public void expandAllFolds()
411     {
412         showLineRange(0,buffer.getLineCount() - 1);
413         notifyScreenLineChanges();
414         textArea.foldStructureChanged();
415     } //}}}
416

417     //{{{ expandFolds() method
418
/**
419      * This method should only be called from <code>actions.xml</code>.
420      * @since jEdit 4.2pre1
421      */

422     public void expandFolds(char digit)
423     {
424         if(digit < '1' || digit > '9')
425         {
426             Toolkit.getDefaultToolkit().beep();
427             return;
428         }
429         else
430             expandFolds((digit - '1') + 1);
431     } //}}}
432

433     //{{{ expandFolds() method
434
/**
435      * Expands all folds with the specified fold level.
436      * @param foldLevel The fold level
437      * @since jEdit 4.2pre1
438      */

439     public void expandFolds(int foldLevel)
440     {
441         if(buffer.getFoldHandler() instanceof IndentFoldHandler)
442             foldLevel = (foldLevel - 1) * buffer.getIndentSize() + 1;
443
444         showLineRange(0,buffer.getLineCount() - 1);
445
446         /* this ensures that the first line is always visible */
447         boolean seenVisibleLine = false;
448
449         int firstInvisible = 0;
450
451         for(int i = 0; i < buffer.getLineCount(); i++)
452         {
453             if(!seenVisibleLine || buffer.getFoldLevel(i) < foldLevel)
454             {
455                 if(firstInvisible != i)
456                 {
457                     hideLineRange(firstInvisible,
458                         i - 1);
459                 }
460                 firstInvisible = i + 1;
461                 seenVisibleLine = true;
462             }
463         }
464
465         if(firstInvisible != buffer.getLineCount())
466             hideLineRange(firstInvisible,buffer.getLineCount() - 1);
467
468         notifyScreenLineChanges();
469         textArea.foldStructureChanged();
470     } //}}}
471

472     //{{{ narrow() method
473
/**
474      * Narrows the visible portion of the buffer to the specified
475      * line range.
476      * @param start The first line
477      * @param end The last line
478      * @since jEdit 4.2pre1
479      */

480     public void narrow(int start, int end)
481     {
482         if(start > end || start < 0 || end >= buffer.getLineCount())
483             throw new ArrayIndexOutOfBoundsException JavaDoc(start + ", " + end);
484
485         if(start < getFirstVisibleLine() || end > getLastVisibleLine())
486             expandAllFolds();
487
488         if(start != 0)
489             hideLineRange(0,start - 1);
490         if(end != buffer.getLineCount() - 1)
491             hideLineRange(end + 1,buffer.getLineCount() - 1);
492
493         // if we narrowed to a single collapsed fold
494
if(start != buffer.getLineCount() - 1
495             && !isLineVisible(start + 1))
496             expandFold(start,false);
497
498         textArea.fireNarrowActive();
499
500         notifyScreenLineChanges();
501         textArea.foldStructureChanged();
502     } //}}}
503

504     //{{{ Package-private members
505
FirstLine firstLine;
506     ScrollLineCount scrollLineCount;
507     ScreenLineManager screenLineMgr;
508     RangeMap folds;
509
510     //{{{ init() method
511
void init()
512     {
513         if(initialized)
514         {
515             if(!buffer.isLoading())
516                 resetAnchors();
517         }
518         else
519         {
520             initialized = true;
521             folds = new RangeMap();
522             if(buffer.isLoading())
523                 folds.reset(buffer.getLineCount());
524             else
525                 bufferHandler.foldHandlerChanged(buffer);
526             notifyScreenLineChanges();
527         }
528     } //}}}
529

530     //{{{ notifyScreenLineChanges() method
531
void notifyScreenLineChanges()
532     {
533         if(Debug.SCROLL_DEBUG)
534             Log.log(Log.DEBUG,this,"notifyScreenLineChanges()");
535
536         // when the text area switches to us, it will do
537
// a reset anyway
538
if(textArea.getDisplayManager() != this)
539             return;
540
541         try
542         {
543             if(firstLine.callReset)
544                 firstLine.reset();
545             else if(firstLine.callChanged)
546                 firstLine.changed();
547
548             if(scrollLineCount.callReset)
549             {
550                 scrollLineCount.reset();
551                 firstLine.ensurePhysicalLineIsVisible();
552             }
553             else if(scrollLineCount.callChanged)
554                 scrollLineCount.changed();
555             
556             if(firstLine.callChanged || scrollLineCount.callReset
557                 || scrollLineCount.callChanged)
558             {
559                 textArea.updateScrollBar();
560                 textArea.recalculateLastPhysicalLine();
561             }
562         }
563         finally
564         {
565             firstLine.callReset = firstLine.callChanged = false;
566             scrollLineCount.callReset = scrollLineCount.callChanged = false;
567         }
568     } //}}}
569

570     //{{{ setFirstLine() method
571
void setFirstLine(int oldFirstLine, int firstLine)
572     {
573         int visibleLines = textArea.getVisibleLines();
574
575         if(firstLine >= oldFirstLine + visibleLines)
576         {
577             this.firstLine.scrollDown(firstLine - oldFirstLine);
578             textArea.chunkCache.invalidateAll();
579         }
580         else if(firstLine <= oldFirstLine - visibleLines)
581         {
582             this.firstLine.scrollUp(oldFirstLine - firstLine);
583             textArea.chunkCache.invalidateAll();
584         }
585         else if(firstLine > oldFirstLine)
586         {
587             this.firstLine.scrollDown(firstLine - oldFirstLine);
588             textArea.chunkCache.scrollDown(firstLine - oldFirstLine);
589         }
590         else if(firstLine < oldFirstLine)
591         {
592             this.firstLine.scrollUp(oldFirstLine - firstLine);
593             textArea.chunkCache.scrollUp(oldFirstLine - firstLine);
594         }
595
596         notifyScreenLineChanges();
597     } //}}}
598

599     //{{{ setFirstPhysicalLine() method
600
void setFirstPhysicalLine(int amount, int skew)
601     {
602         int oldFirstLine = textArea.getFirstLine();
603
604         if(amount == 0)
605         {
606             skew -= this.firstLine.skew;
607
608             // JEditTextArea.scrollTo() needs this to simplify
609
// its code
610
if(skew < 0)
611                 this.firstLine.scrollUp(-skew);
612             else if(skew > 0)
613                 this.firstLine.scrollDown(skew);
614             else
615             {
616                 // nothing to do
617
return;
618             }
619         }
620         else if(amount > 0)
621             this.firstLine.physDown(amount,skew);
622         else if(amount < 0)
623             this.firstLine.physUp(-amount,skew);
624
625         int firstLine = textArea.getFirstLine();
626         int visibleLines = textArea.getVisibleLines();
627
628         if(firstLine == oldFirstLine)
629             /* do nothing */;
630         else if(firstLine >= oldFirstLine + visibleLines
631             || firstLine <= oldFirstLine - visibleLines)
632         {
633             textArea.chunkCache.invalidateAll();
634         }
635         else if(firstLine > oldFirstLine)
636         {
637             textArea.chunkCache.scrollDown(firstLine - oldFirstLine);
638         }
639         else if(firstLine < oldFirstLine)
640         {
641             textArea.chunkCache.scrollUp(oldFirstLine - firstLine);
642         }
643
644         // we have to be careful
645
notifyScreenLineChanges();
646     } //}}}
647

648     //{{{ invalidateScreenLineCounts() method
649
void invalidateScreenLineCounts()
650     {
651         screenLineMgr.invalidateScreenLineCounts();
652         firstLine.callReset = true;
653         scrollLineCount.callReset = true;
654     } //}}}
655

656     //{{{ updateScreenLineCount() method
657
void updateScreenLineCount(int line)
658     {
659         if(!screenLineMgr.isScreenLineCountValid(line))
660         {
661             int newCount = textArea.chunkCache
662                 .getLineSubregionCount(line);
663
664             setScreenLineCount(line,newCount);
665         }
666     } //}}}
667

668     //{{{ bufferLoaded() method
669
void bufferLoaded()
670     {
671         folds.reset(buffer.getLineCount());
672         screenLineMgr.reset();
673
674         if(textArea.getDisplayManager() == this)
675         {
676             textArea.propertiesChanged();
677             init();
678         }
679
680         int collapseFolds = buffer.getIntegerProperty(
681             "collapseFolds",0);
682         if(collapseFolds != 0)
683             expandFolds(collapseFolds);
684     } //}}}
685

686     //{{{ foldHandlerChanged() method
687
void foldHandlerChanged()
688     {
689         if(buffer.isLoading())
690             return;
691
692         folds.reset(buffer.getLineCount());
693         resetAnchors();
694
695         int collapseFolds = buffer.getIntegerProperty(
696             "collapseFolds",0);
697         if(collapseFolds != 0)
698             expandFolds(collapseFolds);
699     } //}}}
700

701     //}}}
702

703     //{{{ Private members
704
private boolean initialized;
705     private boolean inUse;
706     private JEditBuffer buffer;
707     private TextArea textArea;
708     private BufferHandler bufferHandler;
709
710     //{{{ DisplayManager constructor
711
private DisplayManager(JEditBuffer buffer, TextArea textArea,
712         DisplayManager copy)
713     {
714         this.buffer = buffer;
715         this.screenLineMgr = new ScreenLineManager(this,buffer);
716         this.textArea = textArea;
717
718         scrollLineCount = new ScrollLineCount(this,textArea);
719         firstLine = new FirstLine(this,textArea);
720
721         bufferHandler = new BufferHandler(this,textArea,buffer);
722         // this listener priority thing is a bad hack...
723
buffer.addBufferListener(bufferHandler, JEditBuffer.HIGH_PRIORITY);
724
725         if(copy != null)
726         {
727             folds = new RangeMap(copy.folds);
728             initialized = true;
729         }
730     } //}}}
731

732     //{{{ resetAnchors() method
733
private void resetAnchors()
734     {
735         firstLine.callReset = true;
736         scrollLineCount.callReset = true;
737         notifyScreenLineChanges();
738     } //}}}
739

740     //{{{ dispose() method
741
private void dispose()
742     {
743         buffer.removeBufferListener(bufferHandler);
744     } //}}}
745

746     //{{{ showLineRange() method
747
private void showLineRange(int start, int end)
748     {
749         if(Debug.FOLD_VIS_DEBUG)
750         {
751             Log.log(Log.DEBUG,this,"showLineRange(" + start
752                 + "," + end + ")");
753         }
754
755         for(int i = start; i <= end; i++)
756         {
757             //XXX
758
if(!isLineVisible(i))
759             {
760                 // important: not screenLineMgr.getScreenLineCount()
761
updateScreenLineCount(i);
762                 int screenLines = getScreenLineCount(i);
763                 if(firstLine.physicalLine >= i)
764                 {
765                     firstLine.scrollLine += screenLines;
766                     firstLine.callChanged = true;
767                 }
768                 scrollLineCount.scrollLine += screenLines;
769                 scrollLineCount.callChanged = true;
770             }
771         }
772
773         /* update fold visibility map. */
774         folds.show(start,end);
775     } //}}}
776

777     //{{{ hideLineRange() method
778
private void hideLineRange(int start, int end)
779     {
780         if(Debug.FOLD_VIS_DEBUG)
781         {
782             Log.log(Log.DEBUG,this,"hideLineRange(" + start
783                 + "," + end + ")");
784         }
785
786         int i = start;
787         if(!isLineVisible(i))
788             i = getNextVisibleLine(i);
789         while(i != -1 && i <= end)
790         {
791             int screenLines = screenLineMgr.getScreenLineCount(i);
792             if(i < firstLine.physicalLine)
793             {
794                 firstLine.scrollLine -= screenLines;
795                 firstLine.skew = 0;
796                 firstLine.callChanged = true;
797             }
798
799             scrollLineCount.scrollLine -= screenLines;
800             scrollLineCount.callChanged = true;
801
802             i = getNextVisibleLine(i);
803         }
804
805         /* update fold visibility map. */
806         folds.hide(start,end);
807
808         if(!isLineVisible(firstLine.physicalLine))
809         {
810             int firstVisible = getFirstVisibleLine();
811             if(firstLine.physicalLine < firstVisible)
812             {
813                 firstLine.physicalLine = firstVisible;
814                 firstLine.scrollLine = 0;
815             }
816             else
817             {
818                 firstLine.physicalLine = getPrevVisibleLine(
819                     firstLine.physicalLine);
820                 firstLine.scrollLine -=
821                     screenLineMgr.getScreenLineCount(
822                     firstLine.physicalLine);
823             }
824             firstLine.callChanged = true;
825         }
826     } //}}}
827

828     //{{{ setScreenLineCount() method
829
/**
830      * Sets the number of screen lines that the specified physical line
831      * is split into.
832      * @since jEdit 4.2pre1
833      */

834     private void setScreenLineCount(int line, int count)
835     {
836         int oldCount = screenLineMgr.getScreenLineCount(line);
837
838         // old one so that the screen line manager sets the
839
// validity flag!
840

841         screenLineMgr.setScreenLineCount(line,count);
842
843         if(count == oldCount)
844             return;
845
846         if(!isLineVisible(line))
847             return;
848
849         if(firstLine.physicalLine >= line)
850         {
851             if(firstLine.physicalLine == line)
852                 firstLine.callChanged = true;
853             else
854             {
855                 firstLine.scrollLine += (count - oldCount);
856                 firstLine.callChanged = true;
857             }
858         }
859
860         scrollLineCount.scrollLine += (count - oldCount);
861         scrollLineCount.callChanged = true;
862     } //}}}
863

864     //}}}
865
}
866
Popular Tags