src/Tools/jEdit/src/jedit/ScrollerDockable.scala
changeset 34366 2f6e50fa7ac4
parent 34365 5691af1d34cd
child 34367 0c7a4957b4da
equal deleted inserted replaced
34365:5691af1d34cd 34366:2f6e50fa7ac4
     1 /*
     1 /*
     2  * ScrollerDockable.scala
     2  * ScrollerDockable.scala
     3  *
     3  *
     4  * TODO: clearer seperation of tasks: layout message_view, layout panel (with preferredsize), repaint, relayout, ...
     4  * TODO: 
     5  * + relayout on ValueIsAdjusting while scrolling
       
     6  * + scrolling *one* panel 
     5  * + scrolling *one* panel 
     7  */
     6  */
     8 
     7 
     9 package isabelle.jedit
     8 package isabelle.jedit
    10 
     9 
    11 import scala.collection.mutable.ArrayBuffer
    10 import scala.collection.mutable.{ArrayBuffer, HashMap}
    12 
    11 
    13 import java.awt.{ BorderLayout, Adjustable }
    12 import java.awt.{ BorderLayout, Adjustable }
    14 import java.awt.event.{ ActionListener, ActionEvent, AdjustmentListener, AdjustmentEvent, ComponentListener, ComponentEvent }
    13 import java.awt.event.{ ActionListener, ActionEvent, AdjustmentListener, AdjustmentEvent, ComponentListener, ComponentEvent }
    15 import javax.swing.{ JPanel, JRadioButton, JScrollBar, JTextArea, Timer }
    14 import javax.swing.{ JFrame, JPanel, JRadioButton, JScrollBar, JTextArea, Timer }
    16 
    15 
    17 import org.w3c.dom.Document
    16 import org.w3c.dom.Document
    18 
    17 
    19 import org.xhtmlrenderer.simple.XHTMLPanel
    18 import org.xhtmlrenderer.simple.XHTMLPanel
    20 import org.xhtmlrenderer.context.AWTFontResolver
    19 import org.xhtmlrenderer.context.AWTFontResolver
    21 
    20 
    22 import org.gjt.sp.jedit.View
    21 import org.gjt.sp.jedit.View
    23 
    22 
    24 class ScrollerDockable(view : View, position : String) extends JPanel with AdjustmentListener with ComponentListener {
    23 trait Unrendered[T] {
    25   //holding the unrendered messages
    24   def addUnrendered (id: Int, u: T) : Unit
    26   val message_buffer = new ArrayBuffer[Document]()
    25   def getUnrendered (id: Int) : Option[T]
    27   //rendered messages
    26   def size : Int
    28   var message_cache = Map[Int, XHTMLPanel]()
    27 }
    29   // defining the current view
    28 
    30   val scrollunit = 1
    29 trait Rendered[T] {
    31   var message_offset = 0 //how many pixels of the lowest message are hidden
    30   def getRendered (id: Int) : Option[T]
    32   var message_no = -1  //index of the lowest message
    31 }
    33   // absolute positioning for messages
    32 
    34   val message_panel = new JPanel
    33 object Renderer {
    35   message_panel.setLayout(null)
       
    36   // setting up view
       
    37   setLayout(new BorderLayout())
       
    38   val vscroll = new JScrollBar(Adjustable.VERTICAL, 0, 1, 0, 1)
       
    39   vscroll.addAdjustmentListener(this)
       
    40   add (vscroll, BorderLayout.EAST)
       
    41   add (message_panel, BorderLayout.CENTER)
       
    42   addComponentListener(this)
       
    43   //automated scrolling, new message ind
       
    44   val infopanel = new JPanel
       
    45   val auto_scroll = new JRadioButton("Auto Scroll", false)
       
    46   //indicates new messages with a new color, and shows number of messages in cache
       
    47   val message_ind = new JTextArea("0")
       
    48   infopanel.add(message_ind)
       
    49   infopanel.add(auto_scroll)
       
    50   add (infopanel, BorderLayout.SOUTH)
       
    51 
       
    52   // DoubleBuffering for smoother updates
       
    53   this.setDoubleBuffered(true)
       
    54   message_panel.setDoubleBuffered(true)
       
    55 
       
    56   //Render a message to a Panel
       
    57   def render (message: Document): XHTMLPanel = {
    34   def render (message: Document): XHTMLPanel = {
    58     val panel = new XHTMLPanel(new UserAgent())
    35     val panel = new XHTMLPanel(new UserAgent())
    59     val fontResolver =
    36     val fontResolver =
    60       panel.getSharedContext.getFontResolver.asInstanceOf[AWTFontResolver]
    37       panel.getSharedContext.getFontResolver.asInstanceOf[AWTFontResolver]
    61     if (Plugin.plugin.viewFont != null)
    38     if (Plugin.plugin.viewFont != null)
    62       fontResolver.setFontMapping("Isabelle", Plugin.plugin.viewFont)
    39       fontResolver.setFontMapping("Isabelle", Plugin.plugin.viewFont)
    63 
    40 
    64     Plugin.plugin.viewFontChanged.add(font => {
    41     Plugin.plugin.viewFontChanged.add(font => {
    65       if (Plugin.plugin.viewFont != null)
    42       if (Plugin.plugin.viewFont != null)
    66         fontResolver.setFontMapping("Isabelle", Plugin.plugin.viewFont)
    43         fontResolver.setFontMapping("Isabelle", Plugin.plugin.viewFont)
    67 
       
    68       panel.relayout()
    44       panel.relayout()
    69     })
    45     })
    70 
       
    71     panel.setDocument(message, UserAgent.baseURL)
    46     panel.setDocument(message, UserAgent.baseURL)
    72     panel
    47     panel
    73   }
    48   }
    74   
    49   
    75   // panel has to be displayable in container message_view for doLayout to have
    50   def relayout_panel (panel: XHTMLPanel, width : Int) {
    76   // an effect, especially returning correct preferredSize
    51     // ATTENTION:
    77   def calculate_preferred_size(panel: XHTMLPanel){
    52     // panel has to be displayable in a frame/container message_view for doDocumentLayout to have
    78     message_panel.add (panel)
    53     // an effect, especially returning correct preferredSize
    79     panel.setBounds (0, 0, message_panel.getWidth, 1) // document has to fit into width
    54     panel.setBounds (0, 0, width, 1) // document has to fit into width
    80     panel.doDocumentLayout (panel.getGraphics) //lay out, preferred size is set then
    55     panel.doDocumentLayout (panel.getGraphics) //lay out, preferred size is set then
    81     // if there are thousands of empty panels, all have to be rendered -
    56     // if there are thousands of empty panels, all have to be rendered -
    82     // but this takes time (even for empty panels); therefore minimum size
    57     // but this takes time (even for empty panels); therefore minimum size
    83     panel.setPreferredSize(new java.awt.Dimension(message_panel.getWidth,Math.max(50, panel.getPreferredSize.getHeight.toInt)))
    58     panel.setPreferredSize(new java.awt.Dimension(width,Math.max(25, panel.getPreferredSize.getHeight.toInt)))
    84   }
    59   }
    85    
    60 
    86   //render and load a message into cache, place its bottom at y-coordinate;
    61 }
    87   def set_message (message_no: Int, y: Int) = {
    62 
       
    63 class MessagePanel(cache: Rendered[XHTMLPanel]) extends JPanel {
       
    64   // defining the current view
       
    65   var offset = 0 //how many pixels of the lowest message are hidden
       
    66   var no = -1  //index of the lowest message
       
    67 
       
    68   // preferences
       
    69   val scrollunit = 5
       
    70   setLayout(null)
       
    71   
       
    72   // place the bottom of the message at y-coordinate and return the rendered panel
       
    73   def place_message (message_no: Int, y: Int) = {
    88     //add panel to cache if necessary and possible
    74     //add panel to cache if necessary and possible
    89     if(!message_cache.isDefinedAt(message_no) && message_buffer.isDefinedAt(message_no)){
    75     cache.getRendered(message_no) match {
    90       message_cache = message_cache.update (message_no, render (message_buffer(message_no)))
       
    91     }
       
    92     val result = message_cache.get(message_no) match {
       
    93       case Some(panel) => {
    76       case Some(panel) => {
       
    77         //panel has to be displayable before calculating preferred size!
       
    78         add(panel)
    94         // recalculate preferred size after resizing the message_view
    79         // recalculate preferred size after resizing the message_view
    95         if(panel.getPreferredSize.getWidth.toInt != message_panel.getWidth)
    80         if(panel.getPreferredSize.getWidth.toInt != getWidth){
    96           calculate_preferred_size (panel)
    81           Renderer.relayout_panel (panel, getWidth)
    97         //place message on view
    82         }
    98         val width = panel.getPreferredSize.getWidth.toInt
    83         val width = panel.getPreferredSize.getWidth.toInt
    99         val height = panel.getPreferredSize.getHeight.toInt
    84         val height = panel.getPreferredSize.getHeight.toInt
   100         panel.setBounds(0, y - height, width, height)
    85         panel.setBounds(0, y - height, width, height)
   101         message_panel.add(panel)
       
   102         panel
    86         panel
   103       }
    87       }
   104       case None => null
    88       case None => null
   105     }
    89     }
   106     result
       
   107   }
    90   }
   108 
    91 
   109   //move view a given amount of pixels
    92   //move view a given amount of pixels
   110   // attention: y should be small, because messages are rendered incremental
    93   // attention: y should be small, because messages are rendered incremental
   111   // (no knowledge on height of messages)
    94   // (no knowledge on height of messages)
   112   def move_view (y : Int) = {
    95   def move_view (y : Int) = {
   113     var update = false
    96     var update = false
   114     if(message_panel.getComponentCount >= 1){
    97     if(getComponentCount >= 1){
   115       message_offset += y
    98       offset += y
   116       //remove bottommost panels
    99       //remove bottommost panels
   117       while (message_offset >= message_panel.getComponent(0).getHeight)
   100       while (offset >= getComponent(0).getHeight)
   118       {
   101       {
   119         message_offset -= message_panel.getComponent(0).getHeight
   102         offset -= getComponent(0).getHeight
   120         message_no -= 1
   103         no -= 1
   121         update_view
   104         invalidate
   122         update = true
   105         update = true
   123       }
   106       }
   124       //insert panels at the bottom
   107       //insert panels at the bottom
   125       while (message_offset < 0) {
   108       while (offset < 0) {
   126         message_no += 1
   109         no += 1
   127         val panel = set_message (message_no, 0)
   110         val panel = place_message (no, 0)
   128         message_offset += panel.getHeight
   111         offset += panel.getHeight
   129         update_view
   112         invalidate
   130         update = true
   113         update = true
   131      }
   114      }
   132       //insert panel at the top
   115       //insert panel at the top
   133       if (message_panel.getComponent(message_panel.getComponentCount - 1).getY + y > 0){
   116       if (getComponent(getComponentCount - 1).getY + y > 0){
   134         update_view
   117         invalidate
   135         update = true
   118         update = true
   136       }
   119       }
   137       //simply move panels
   120       //simply move panels
   138       if(!update){
   121       if(!update){
   139         message_panel.getComponents map (c => {
   122         getComponents map (c => {
   140             val newrect = c.getBounds
   123             val newrect = c.getBounds
   141             newrect.y = newrect.y + y
   124             newrect.y = newrect.y + y
   142             c.setBounds(newrect)
   125             c.setBounds(newrect)
   143           })
   126           })
   144         repaint()
   127         repaint()
   145       } else {
   128       } else {
   146         vscroll.setValue(message_no)
   129         //vscroll.setValue(no)
   147       }
   130         //TODO: create method to update vscroll
   148     }
   131         System.err.println("lookatme2")
   149   }
   132       }
   150   
   133     }
   151   def update_view = {
   134   }
   152     message_panel.removeAll()
   135   
   153     var n = message_no
   136   override def doLayout = {
   154     var y:Int = message_panel.getHeight + message_offset
   137     removeAll()
       
   138     var n = no
       
   139     var y:Int = getHeight + offset
   155     while (y >= 0 && n >= 0){
   140     while (y >= 0 && n >= 0){
   156       val panel = set_message (n, y)
   141       val panel = place_message (n, y)
   157       panel.setBorder(javax.swing.border.LineBorder.createBlackLineBorder)
   142       panel.setBorder(javax.swing.border.LineBorder.createBlackLineBorder)
   158       y = y - panel.getHeight
   143       y = y - panel.getHeight
   159       n = n - 1
   144       n = n - 1
   160     }
   145     }
   161     repaint()
   146   }  
   162   }
   147 }
   163 
   148 
       
   149 class InfoPanel extends JPanel {
       
   150   val auto_scroll = new JRadioButton("Auto Scroll", false)
       
   151   val message_ind = new JTextArea("0")
       
   152   add(message_ind)
       
   153   add(auto_scroll)
       
   154   
       
   155   def isAutoScroll = auto_scroll.isSelected
       
   156   def setIndicator(b: Boolean) = {
       
   157     message_ind.setBackground(if (b) java.awt.Color.red else java.awt.Color.white)
       
   158   }
       
   159   def setText(s: String) {
       
   160     message_ind.setText(s)
       
   161   }
       
   162   
       
   163 }
       
   164 
       
   165 class ScrollerDockable(view : View, position : String) extends JPanel with AdjustmentListener {
       
   166 
       
   167   val buffer:Unrendered[Document] = new MessageBuffer()
       
   168   val cache:Rendered[XHTMLPanel] = new PanelCache(buffer)
       
   169   
       
   170   // set up view
       
   171   val message_panel = new MessagePanel(cache)
       
   172   val infopanel = new InfoPanel
       
   173   val vscroll = new JScrollBar(Adjustable.VERTICAL, 0, 1, 0, 1)
       
   174   vscroll.addAdjustmentListener(this)
       
   175   
       
   176   setLayout(new BorderLayout())
       
   177   add (vscroll, BorderLayout.EAST)
       
   178   add (message_panel, BorderLayout.CENTER)
       
   179   add (infopanel, BorderLayout.SOUTH)
       
   180   
   164   // do not show every new message, wait a certain amount of time
   181   // do not show every new message, wait a certain amount of time
   165   val message_ind_timer : Timer = new Timer(250, new ActionListener {
   182   val message_ind_timer : Timer = new Timer(250, new ActionListener {
   166       // this method is called to indicate a new message
   183       // this method is called to indicate a new message
   167       override def actionPerformed(e:ActionEvent){
   184       override def actionPerformed(e:ActionEvent){
   168         //fire scroll-event if necessary and wanted
   185         //fire scroll-event if necessary and wanted
   169         if(message_no != message_buffer.size
   186         if(message_panel.no != buffer.size && infopanel.isAutoScroll) {
   170           && auto_scroll.isSelected) {
   187           vscroll.setValue(buffer.size - 1)
   171           vscroll.setValue(message_buffer.size - 1)
       
   172         }
   188         }
   173         message_ind.setBackground(java.awt.Color.white)
   189         infopanel.setIndicator(false)
   174       }
   190       }
   175     })
   191     })
   176 
   192 
   177   def add_message (message: Document) = {
   193   def add_message (message: Document) = {
   178     message_buffer += message
   194     buffer.addUnrendered(buffer.size, message)
   179     vscroll.setMaximum (Math.max(1, message_buffer.size))
   195     vscroll.setMaximum (Math.max(1, buffer.size))
   180     message_ind.setBackground(java.awt.Color.red)
   196     infopanel.setIndicator(true)
   181     message_ind.setText(message_buffer.size.toString)
   197     infopanel.setText(buffer.size.toString)
       
   198 
   182     if (! message_ind_timer.isRunning()){
   199     if (! message_ind_timer.isRunning()){
   183       if(auto_scroll.isSelected) vscroll.setValue(message_buffer.size - 1)
   200       if(infopanel.isAutoScroll){
       
   201         vscroll.setValue(buffer.size - 1)
       
   202       }
   184       message_ind_timer.setRepeats(false)
   203       message_ind_timer.setRepeats(false)
   185       message_ind_timer.start()
   204       message_ind_timer.start()
   186     }
   205     }
   187   }
   206 
   188   
   207     if(message_panel.no == -1) {
       
   208       message_panel.no = 0
       
   209       message_panel.invalidate
       
   210     }
       
   211   }
       
   212 
   189   override def adjustmentValueChanged (e : AdjustmentEvent) = {
   213   override def adjustmentValueChanged (e : AdjustmentEvent) = {
   190     //event-handling has to be so general (without UNIT_INCR,...)
   214     //event-handling has to be so general (without UNIT_INCR,...)
   191     // because all events could be sent as TRACK e.g. on my mac
   215     // because all events could be sent as TRACK e.g. on my mac
   192     if (e.getSource == vscroll){
   216     if (e.getSource == vscroll){
   193         message_no = e.getValue
   217         message_panel.no = e.getValue
   194         message_offset = 0
   218         message_panel.offset = 0
   195         update_view
   219         message_panel.invalidate
   196     }
   220         System.err.println("event: "+message_panel.no)
   197   }
   221         vscroll.setModel(new javax.swing.DefaultBoundedRangeModel(99,1,0,1000))
   198 
   222         System.err.println("hello"+e.getValue)
   199   // handle Component-Events
   223     }
   200   override def componentShown(e: ComponentEvent){}
   224   }
   201   override def componentHidden(e: ComponentEvent){}
   225 
   202   override def componentMoved(e: ComponentEvent){}
       
   203   override def componentResized(e: ComponentEvent){
       
   204     update_view
       
   205   }
       
   206   
   226   
   207   // TODO: register somewhere
   227   // TODO: register somewhere
   208   // here only 'emulation of message stream'
   228   // here only 'emulation of message stream'
   209   Plugin.plugin.stateUpdate.add(state => {
   229   Plugin.plugin.stateUpdate.add(state => {
   210     var i = 0
   230     var i = 0
   211     if(state != null) new Thread{
   231     if(state != null) new Thread{
   212       override def run() {
   232       override def run() {
   213         while (i < 500) {
   233         while (i < 1) {
   214           add_message(state.document)
   234           add_message(state.document)
   215           i += 1
   235           i += 1
   216           try {Thread.sleep(3)}
   236           try {Thread.sleep(3)}
   217           catch{case _ =>}
   237           catch{case _ =>}
   218         }
   238         }
   220     }.start
   240     }.start
   221   })
   241   })
   222 }
   242 }
   223 
   243 
   224 
   244 
       
   245 
       
   246 //containing the unrendered messages
       
   247 class MessageBuffer extends HashMap[Int,Document] with Unrendered[Document]{
       
   248   override def addUnrendered (id: Int, m: Document) {
       
   249     update(id, m)
       
   250   }
       
   251   override def getUnrendered (id: Int): Option[Document] = {
       
   252     if(id < size && id >= 0 && apply(id) != null) Some (apply(id))
       
   253     else None
       
   254   }
       
   255   override def size = super.size
       
   256 }
       
   257 
       
   258 //containing the rendered messages
       
   259 class PanelCache (buffer: Unrendered[Document]) extends HashMap[Int, XHTMLPanel] with Rendered[XHTMLPanel]{
       
   260   override def getRendered (id: Int): Option[XHTMLPanel] = {
       
   261     //get message from buffer and render it if necessary
       
   262     if(!isDefinedAt(id)){
       
   263       buffer.getUnrendered(id) match {
       
   264         case Some (message) =>
       
   265           update (id, Renderer.render (message))
       
   266         case None =>
       
   267       }
       
   268     }
       
   269     val result = try {
       
   270       Some (apply(id))
       
   271     } catch {
       
   272       case _ => {
       
   273           None
       
   274       }
       
   275     }
       
   276     return result
       
   277   }
       
   278 }