Auto Size and no decorations (no-bevel) TextFields and their use

VERSION 1 Published

Created on:Jul 21, 2008 8:47 PM by Kamal - Last Modified:  Jul 21, 2008 9:02 PM by Kamal

Recently I replied to a question about editable nodes in a TreeControl

{message:id=2115}

See: http://developers.curl.com/message/2115#2115

I quickly wrote some code and showed how to do it. I later thought to clean it up a bit as you may need to do similar thing for, example with a TabContainer label. Also note that this code will not work with previous versions of Curl as it uses the new TextField.editable? API. However it will be trivial to use my example in the above link to make it work for the previous versions.

1. First we will make a TextFieldUI that is auto resizing.

The height and width of the TextField controls is determined by its UI. Here I will present some approaches so that your TextField can re-sizes depending on its contents and the dimensions available to it. Also, it makes sure that it's width is no smaller than 4pt. You can add more code to make this configurable.


{curl 6.0 applet}
{curl-file-attributes character-encoding = "windows-latin-1"}
 
{define-class public  AutoResizeTextFieldUI {inherits StandardTextFieldUI}
  {constructor package {default control:#TextField = null, ... }
    {construct-super control = control, opaque-to-events? = true, ...}
  }
    
  {method public open {get-width-preference lc:LayoutContext}:Dimension
    {return 
        {self.adjust-width-preference 
            {super.get-width-preference lc}, lc
        }
    }
  }
            
  {method public open {constrain-height
                          lc:LayoutContext,
                          ascent:Distance,
                          descent:Distance}:Dimension
    {return
        {self.adjust-width-preference 
            {super.constrain-height lc, ascent, descent}, lc
        }
    }
  }
 
  || This returns the cell preferences of the child of this UI. So the default width
  || that was returned by StandardTextFieldUI is completely ignored. If the child
  || preference is less than 4pt, it returns 4pt.
  {method protected open {adjust-width-preference
                             d:Dimension, lc:LayoutContext
                         }:Dimension
    {return
        {if-non-null child = self.child then
            def oe:OriginElastic = child.cell-width-preference
            {if oe.preferred-size < 4pt then
                4pt
             else
                oe
            }
         else
            d
        }
    }
  }
}
 


2. In this step we will make a no bevel TextFieldUI.

Here we attempt to make a TextFieldUI that has no decorations. We are sub-classing this from AutoResizeTextFieldUI as we also want the UI to be no bigger than its content.


{define-class package NoBevelAutoResizeTextFieldUI {inherits AutoResizeTextFieldUI}
  field public constant no-bevel?:bool
  
  {constructor package {default
                           control:#TextField = null,
                           no-bevel?:bool = true,
                           ...
                       }
    set self.no-bevel? = no-bevel?
    {construct-super control = control, ...}
  }
 
  || This setter is called when the TextFieldUI is associated with the TextField. We
  || use this opportunity to set any border and/or margin that was set on the UI or
  || its children.
  {setter public open {control value:Control}:void
    set super.control = value
    {self.flatten-hierarchy}
  }
 
  || Resursively remove the border and margin for each child elements as
  || we want that this TextFieldUI should not take any more space than it
  || is needed..
  {method private {flatten-hierarchy}:void
    {if not self.no-bevel? then {return}}
 
    {self.visit-subtree
        self,
        {proc {g:Graphic}:void
            set g.border-width = 0pt
            set g.margin = 0pt
            set g.border-spec = null
        }
    }
  }
 
  {method private {visit-subtree 
                      g:Graphic, 
                      action-proc:{proc-type {Graphic}:void}
                  }:void
    {action-proc g}
    {type-switch g
     case b:Box do
        {for child:Graphic in b.graphical-children do
            {self.visit-subtree child, action-proc}
        }
    }
  }
 
  || Do not draw any bevels.
  {method protected open {overdraw-control renderer2d:Renderer2d}:void
    {if not self.no-bevel? then
        {super.overdraw-control renderer2d}
    }
  }
}
 


Here is an example of the two UI's that have created. Play with it to see its effect.


{TextField
    value = "Hello There!",
    ui-object = {AutoResizeTextFieldUI  control-content-background = "lime"}   
}
 
{TextField
    value = "Hello There!",
    ui-object =
        {NoBevelAutoResizeTextFieldUI control-content-background = "lime"}
}
 


Step 3. Using the auto resizing no-bevel TextFieldUI

3.a. Making a TextField that will become editable on PointerRelease and not editable on ValueFinished.

In Curl version 6.0 and above TextField has this "editable?" option. We use this here to make a TextField editable and not editable. In the example below the TextField starts as not editable. When it sees a pointer release and if the shift key is down, it becomes editable. When this TextField gets a ValueFinished it becomes non-editable.


{TextField
    value = "Hello There!",
    editable? = false,
    ui-object =
        {NoBevelAutoResizeTextFieldUI control-content-background = "lime"},
    {on e:PointerRelease at tf:TextField do
        {if e.button == right-button and
            e.state-mask.shift? and
            not e.moved-since-press?
         then
            set tf.editable? = true
        }
    },
    {on e:ValueFinished at tf:TextField do
        set tf.editable? = false
    }
}
 


3.b. Making a Subcalss of TextField

You can use this idea to make a subclass of a TextField that can become editable? when it sees a PointerPress followed by a PointerRelease (a pointer click notion). You can then add the logic to support handling of some KeyPresses like esc, that could mean that what so ever was typed in the TextField should be simply ignored and should go back to the value that it had before the TextField was made editable.

Note that this class fires an Action even on commit and revert . We could use a custom events like a CommitEvent and a RevertEvent for the same.


{define-class package CustomTextField {inherits TextField}
  field private cached-value:#String
  
  {constructor package {default
                           data-model:#StringDataModel = null,
                           value:#String = null,
                           prompt:#String = null,
                           max-chars:int = -1,
                           ui-object:#TextFieldUI = null,
                           ...
                       }
    {construct-super
        data-model = data-model,
        value = value,
        prompt = prompt,
        max-chars = max-chars,
        ui-object = ui-object,
        ...
    }
    set self.cached-value = self.value                             
    {self.add-event-handler
        {on e:ValueFinished do
            {self.maybe-commit}
        }
    }
  }
 
  {method public open {maybe-commit}:void
    let constant val:#String = self.value
    
    {if val == null or val.empty? then
        {self.revert}
     else
        {self.commit}
    }
  }
  
  {method public open {on-focus-out e:FocusOut}:void
    {super.on-focus-out e}
    {self.maybe-commit}
  }
 
  {method private {revert}:void
    {if-non-null val = self.cached-value then
        set self.value = val
     else
        {self.unset-value}
    }
    set self.editable? = false
    {self.enqueue-event {Action}}
  }
 
  {method private {commit}:void
    set self.cached-value = self.value
    set self.value = {non-null self.cached-value}
    set self.editable? = false
   {self.enqueue-event {Action}}
  }
  
  {method public open {on-key-press e:KeyPress}:void
    {if e.value == KeyPressValue.esc then
        {self.revert}
        {e.consume}
    }
    {super.on-key-press e}
  }
}
 
{CustomTextField
    editable? = false,
    value = "Fruits",
    ui-object =
        {NoBevelAutoResizeTextFieldUI control-content-background = "lime"},
    {on e:PointerRelease at tf:TextField do
        {if e.button == right-button and
            e.state-mask.shift? and
            not e.moved-since-press? and
            not tf.editable?
         then
            {dump e}
            set tf.editable? = true
            {e.consume}
        }
    }
}
 


Step 4. Using the above TextField.

Making use of the CustomTextField for the labels of a TabContainer.

Since we need the Labels of a TabContainer to have mnemonics we will use a Label object for the TabContainer. This Label object will be inside a Frame. When this Frame will see a right click then it will replace the Label with a CustomTextField object. When the CustomTextField object gets a ValueFinished, we will replace it with a new Label.


{define-proc package {get-tab-label str:String}:Graphic
    let label:Label =  {Label str}
    def frm:Frame = {Frame opaque-to-events? = true, label}
    def tf:TextField =
        {CustomTextField
            value = str,
            ui-object = {AutoResizeTextFieldUI},
            editable? = true
        }
    {frm.add-event-handler
        {on e:PointerRelease at frm:Frame do
            {if e.button == right-button and
                not e.moved-since-press?
             then
                {if frm.child == null or frm.child.graphic != tf then
                    set tf.editable? = true
                    {frm.add tf, replace? = true}
                    {tf.become-active}
                }
            }
            {e.consume}
        }
    }
 
    {tf.add-event-handler
        {on e:Action do
            || We create a new Label as there seems to be no API that we
            || can call on a Label to change its text and honour the nemonic
            || associated with it.
            def  new-label = {Label {tf.get-text}}
            {frm.add new-label, replace? = true}
            set label = new-label
        }
    }
 
    {return frm}
}
 
{TabContainer
    {TabPane
        label = {get-tab-label "&One"},
        tab-button-tooltip = "first tab",
        {TextFlowBox font-size = 28pt, "1"}
    },
    {TabPane
        label = {get-tab-label "&Two"},
        tab-button-tooltip = "second tab",
        show? = true,
        {TextFlowBox font-size = 28pt, "2"}
    },
    {TabPane
        label = {get-tab-label "T&hree"},
        tab-button-tooltip = "third tab",
        {TextFlowBox font-size = 28pt, "3"}
    }
}
 
Average User Rating
(0 ratings)




There are no comments on this document