Groovy
  1. Groovy
  2. GROOVY-4474

Enhanced SwingBuilder to add a columnModel into a table, added cellEditor support to tables and trees, implemented optional DefaultRenderer-behaviour for cellRenderers

    Details

    • Type: New Feature New Feature
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 1.7.5, 1.8-beta-2
    • Fix Version/s: 1.7.6, 1.8-beta-3
    • Component/s: Swing
    • Labels:
      None
    • Flags:
      Patch

      Description

      Features in this patch:

      • Definition of a columnModel in a table (important when using custom tableModel or glazedLists)
      • Value of a column is used as identifier
      • property width does not set the width, but minWidth, preferredWidth and maxWidth
        if with is a Number:
        set all three to this width to define it as fixed width
        if with is a Collection with ...:
        ... 3 values: set min, preferred, max
        ... 2 values: set min, preferred
        ... 1 value : set preferred
      • columns may contain a cellRenderer, a headerRenderer and a cellEditor element
      • cellRenderer, headerRenderer and cellEditor deliver the Component created by the default behaviour as rendercomponent if not specified
      • cellRenderer and cellEditor now work for JTree aswell

      Example:

          new SwingBuilder.edt {
            frame(id: 'test', title: 'Frame', size: [300, 300], show: true) {
              borderLayout()
              def data = [ ['Chang', 'Peng', 30], ['John', 'Doe', 17], ['Hans', 'Meyer', 25]] as Object[][]
              scrollPane {
                table(new JTable(data, ['name', 'prename', 'age'] as Object[])) {
                  columnModel {
                    column('name', modelIndex: 0, headerValue: 'Lastname', width: [100, 150, 250]) {
                      headerRenderer {
                        onRender { children[0].text = value.toUpperCase() }
                      }
                    }
                    column('prename', modelIndex: 1, headerValue: 'Prename', width: [100, 150]) {
                      cellRenderer {
                        label(foreground: java.awt.Color.RED)
                        onRender { children[0].text = value }
                      }
                    }
                    column('age', modelIndex: 2, headerValue: 'Age', width: 70) {
                      cellEditor {
                        prepareEditor { children[0].text = value*2 }
                        getEditorValue { children[0].text.toInteger() }
                      }
                    }
                  }
                }
              }
            }
          }
      

        Activity

        Hide
        Hamlet D'Arcy added a comment -

        rolled back to branch

        Show
        Hamlet D'Arcy added a comment - rolled back to branch
        Hide
        Hamlet D'Arcy added a comment -

        fixed in head, rolling back to 1.7.6

        Show
        Hamlet D'Arcy added a comment - fixed in head, rolling back to 1.7.6
        Hide
        Alexander Klein added a comment - - edited

        Hamlet,

        here are two examples, runnable in a 1.7.6 GroovyConsole, one for a JTable, one for a JTree

        JTable-Example

        import groovy.swing.*
        import javax.swing.*
        
        new SwingBuilder().edt {
          frame(id: 'test', title: 'Frame', size: [300, 300], show: true) {
            borderLayout()
            def data = [ ['Chang', 'Peng', 30], ['John', 'Doe', 17], ['Hans', 'Meyer', 25]] as Object[][]
            scrollPane {
              table(new JTable(data, ['name', 'prename', 'age'] as Object[])) {
                columnModel {
                  column('name', modelIndex: 0, headerValue: 'Lastname', width: [100, 150, 250]) {
                    headerRenderer {
                      onRender { children[0].text = value.toUpperCase() }
                    }
                  }
                  column('prename', modelIndex: 1, headerValue: 'Prename', width: [100, 150]) {
                    cellRenderer {
                      label(foreground: java.awt.Color.RED)
                      onRender { children[0].text = value }
                    }
                  }
                  column('age', modelIndex: 2, headerValue: 'Age', width: 70) {
                    cellEditor {
                      prepareEditor { children[0].text = value*2 }
                      editorValue { children[0].text.toInteger() }
                    }
                  }
                }
              }
            }
          }
        }
        

        JTree-Example

        import groovy.swing.*
        import javax.swing.*
        
        new SwingBuilder().edt {
          frame(id: 'test', title: 'Frame', size: [300, 300], show: true) {
            borderLayout()
            scrollPane {
                tree(rootVisible: false, editable: true) {
                  cellRenderer {
                    onRender {
                      children[0].text = value.userObject.toUpperCase()
                    }
                  }
                  cellEditor {
                    textField()
                    prepareEditor {
                        children[0].text = value.userObject
                    }
                    editorValue {
                        value.userObject = children[0].text
                    }
                  }
                }
              }
           }  
        }
        
        Show
        Alexander Klein added a comment - - edited Hamlet, here are two examples, runnable in a 1.7.6 GroovyConsole, one for a JTable, one for a JTree JTable-Example import groovy.swing.* import javax.swing.* new SwingBuilder().edt { frame(id: 'test', title: 'Frame', size: [300, 300], show: true ) { borderLayout() def data = [ ['Chang', 'Peng', 30], ['John', 'Doe', 17], ['Hans', 'Meyer', 25]] as Object [][] scrollPane { table( new JTable(data, ['name', 'prename', 'age'] as Object [])) { columnModel { column('name', modelIndex: 0, headerValue: 'Lastname', width: [100, 150, 250]) { headerRenderer { onRender { children[0].text = value.toUpperCase() } } } column('prename', modelIndex: 1, headerValue: 'Prename', width: [100, 150]) { cellRenderer { label(foreground: java.awt.Color.RED) onRender { children[0].text = value } } } column('age', modelIndex: 2, headerValue: 'Age', width: 70) { cellEditor { prepareEditor { children[0].text = value*2 } editorValue { children[0].text.toInteger() } } } } } } } } JTree-Example import groovy.swing.* import javax.swing.* new SwingBuilder().edt { frame(id: 'test', title: 'Frame', size: [300, 300], show: true ) { borderLayout() scrollPane { tree(rootVisible: false , editable: true ) { cellRenderer { onRender { children[0].text = value.userObject.toUpperCase() } } cellEditor { textField() prepareEditor { children[0].text = value.userObject } editorValue { value.userObject = children[0].text } } } } } }
        Hide
        Hamlet D'Arcy added a comment -

        Alexander,

        I am ready to commit this work, thank you.

        However, I cannot get a working example running. Can you please update your code example in the JIRA description so that it runs in GroovyConsole?
        The example from the unit test runs but is not a good test because it doesn't show any data or header info, as the sample here does.

        THanks,

        Show
        Hamlet D'Arcy added a comment - Alexander, I am ready to commit this work, thank you. However, I cannot get a working example running. Can you please update your code example in the JIRA description so that it runs in GroovyConsole? The example from the unit test runs but is not a good test because it doesn't show any data or header info, as the sample here does. THanks,
        Hide
        Alexander Klein added a comment -

        I applied all the comments from Andres.
        I addition I added the last two comments to RendererFactory / RendererUpdateFactory aswell,
        because this Factorys had been implemented the same way.

        Show
        Alexander Klein added a comment - I applied all the comments from Andres. I addition I added the last two comments to RendererFactory / RendererUpdateFactory aswell, because this Factorys had been implemented the same way.
        Hide
        Hamlet D'Arcy added a comment -

        Alright, I will merge this patch in this weekend when I have an hour of free time to account for Andres' feedback. Great feedback Andres!

        Show
        Hamlet D'Arcy added a comment - Alright, I will merge this patch in this weekend when I have an hour of free time to account for Andres' feedback. Great feedback Andres!
        Hide
        Andres Almiray added a comment -

        The patch looks good however I have a few comments.

        ColumnModelFactory

        • checking if the current node is a JTable during newInstance() is a bit aggressive, as you can't create column models on separate closures (think a reusable column model making closure). I'd prefer the check to be added to the onNodeCompleted() method (which the factory already defines). Avoid throwing an exception, you can switch to logging a warning for example.

        ClosureCellEditor

        • it looks to me there's a chance that the callbacks field might throw an NPE if the 1st constructor is not used. I'd prefer to initialize the field to an empty Map, then add the provided callbacks (if any) using putAll() .
        • why does ClosureCellEditor needs to implement GroovyInterceptable?

        ColumnFactory

        • same check for the current parent as with ColumnModelFactory, better move them to setParent()/onNodeCompleted where it fits.
        • once you know the width attribute is a Number you can call .integerValue() on it, faster than 'as Integer'.

        GetCellEditorFactory

        • I'd prefer the node name to be just 'editorValue' not 'getEditorValue'

        CellEditorFactory, CellEditorGetValueFactory, CellEditorPrepareFactory

        • these factories use the builder's binding to store temporal data, it's better to use the builder's context. Each node has its own context. The current context points to its parent context (if any) so you can share data between children and parents while the children are being processed. Using the context is much better as builder variables might break using Griffon's CompositeBuilder.

        CellEditorGetValueFactory, CellEditorPrepareFactory

        • these factories assume an specific parent which is why they return the builder.current. Be aware that this might be a bug as the parent might be added to the grandparent. If you do not care about the current node then return an empty map (use Collections.emptyMap() to guard against undesired attributes).

        Cheers
        Andres

        Show
        Andres Almiray added a comment - The patch looks good however I have a few comments. ColumnModelFactory checking if the current node is a JTable during newInstance() is a bit aggressive, as you can't create column models on separate closures (think a reusable column model making closure). I'd prefer the check to be added to the onNodeCompleted() method (which the factory already defines). Avoid throwing an exception, you can switch to logging a warning for example. ClosureCellEditor it looks to me there's a chance that the callbacks field might throw an NPE if the 1st constructor is not used. I'd prefer to initialize the field to an empty Map, then add the provided callbacks (if any) using putAll() . why does ClosureCellEditor needs to implement GroovyInterceptable? ColumnFactory same check for the current parent as with ColumnModelFactory, better move them to setParent()/onNodeCompleted where it fits. once you know the width attribute is a Number you can call .integerValue() on it, faster than 'as Integer'. GetCellEditorFactory I'd prefer the node name to be just 'editorValue' not 'getEditorValue' CellEditorFactory , CellEditorGetValueFactory , CellEditorPrepareFactory these factories use the builder's binding to store temporal data, it's better to use the builder's context. Each node has its own context. The current context points to its parent context (if any) so you can share data between children and parents while the children are being processed. Using the context is much better as builder variables might break using Griffon's CompositeBuilder. CellEditorGetValueFactory , CellEditorPrepareFactory these factories assume an specific parent which is why they return the builder.current. Be aware that this might be a bug as the parent might be added to the grandparent. If you do not care about the current node then return an empty map (use Collections.emptyMap() to guard against undesired attributes). Cheers Andres
        Hide
        Hamlet D'Arcy added a comment -

        The request and sample code seems reasonable. I will take a look at the implementation and commit tonight if it looks good. Also, I'll check with Andres to see if Griffon breaks.

        Show
        Hamlet D'Arcy added a comment - The request and sample code seems reasonable. I will take a look at the implementation and commit tonight if it looks good. Also, I'll check with Andres to see if Griffon breaks.
        Hide
        Jochen Theodorou added a comment -

        Hamlet, since you are the expert here, what do you think of this?

        Show
        Jochen Theodorou added a comment - Hamlet, since you are the expert here, what do you think of this?
        Hide
        Roshan Dawrani added a comment -

        Added code tags.

        Show
        Roshan Dawrani added a comment - Added code tags.

          People

          • Assignee:
            Hamlet D'Arcy
            Reporter:
            Alexander Klein
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development