This Question is Answered

11 "helpful" answers available (3 pts)
6 Replies Last post: Jun 4, 2008 1:58 AM by htabussum

Regarding PointerPress event in DropDown List.

Jun 2, 2008 5:08 AM

Click to view htabussum's profile Level 1 htabussum 6 posts since
Jan 31, 2008

Hi,

I am having a dropdown list and i want to append a new item to the list
before i could open the list. (Infact i would like to call a method
when i click on the list).


For that i trigger a PointerPress event.


My code is given below.



{let sample-list:DefaultListModel =
    {DefaultListModel
        "One"
    }
}
{DropdownList
    prompt = "Choose a state:",
    data-model = list-of-states,
    dropdown-height = 2in,
    {on PointerPress at ddl:DropdownList
        {Popup-message "Pointer Press Event"}
        {ddl.append "One More"}
    },
    {on ValueFinished at ddl:DropdownList do
        {popup-message
            {text You have chosen the state of {value ddl.value}.}
        }
    }
}
 


But the contents are not updated or the event might not have been fired. What could be the best solution for this case.
Click to view Kamal's profile Curl Kamal 149 posts since
Oct 17, 2007
1. Re: Regarding PointerPress event in DropDown List. Jun 2, 2008 6:12 AM
This is a strange user interface that you want. The way you have put up the code, you want to add a new element to a DropdownList every time a user performs a PointerPress on it?

I will first try to explain a little bit about how the PointerEvent(s) propagate and then give you a solution that will help you write the code that you want to (although I still feel strange about your requirement).

When a View gets a PointerEvent, it is first wrapped in a PointerEnvelopeEvent and fired at its Child. PointerEnvelopeEvent.contents points to the actual PointerEvent. This event is then propagated up the graphical hierarchy till there are no children and hence has moved up to the topmost visible child. Now the actual PointerEvent is fired at the topmost child and then to its parents, following the same path that the PointerEnvelopeEvent took, except that the direction is now reversed. However, if a child consumes this PointerEvent, it will no longer be propagated to its parent.

Having said that, note that a DropdownList has a child, the UI of the DropdownList. That UI may have several other graphical components. The UI or any of its graphical components can consume the event and hence may not be propagated to the parent and therefore to the control you installed your PointerEvent handlers. If you want to tap this event, the best chance is a PointerEnvelopeEvent. So in your code you may try


{DropdownList
    prompt = "Choose a state:",
||--    data-model = list-of-states,
    dropdown-height = 2in,
    {on e:PointerEnvelopeEvent at ddl:DropdownList do
        {if e.contents isa PointerPress then
            {popup-message "Pointer Press Event"}
            {ddl.append "One More"}
        }
    },
    {on ValueFinished at ddl:DropdownList do
        {popup-message
            {text You have chosen the state of {value ddl.value}.}
        }
    }
}
 


Hope this helps.
Kamal
Click to view carl's profile Curl carl 83 posts since
Oct 17, 2007
2. Re: Regarding PointerPress event in DropDown List. Jun 2, 2008 11:31 PM
in response to: Kamal
I assume that the actual desire is to run some kind of synchronization just before it drops down, and the example was just a test case.

What Kamal said about PointerEnvelopeEvents was correct, but using gui events as a trigger is not the correct way to perform this action. The particular problem is that you are assuming PointerPress is the only way that the DropdownList might open. This is not always true with StandardDropdownListUI and is true even less often when using skinnable UIs. In particular, certain KeyPress events (e.g. down arrow) can open the menu as well. Additionally, doing work in advance in this event handler is assuming that a PointerPress will go on to actually open the menu, and that's not a given.

A more correct approach would be to override the appropriate methods in the UI (see show-dropdown). This is relatively easy if you are using skinnable controls, and a bit more involved but still possible if you are using the standard controls.
Click to view Kamal's profile Curl Kamal 149 posts since
Oct 17, 2007
3. Re: Regarding PointerPress event in DropDown List. Jun 3, 2008 5:51 AM
in response to: carl
Carl is right that you can open a DropdownList in other ways too. So the correct may would be to subclass the UI's for the DropdownList. I agree that there should be an easier way to detect it and I will propose some API changes for our next release, but for now sub classing the UI is the way to go.

Kamal
Click to view carl's profile Curl carl 83 posts since
Oct 17, 2007
4. Re: Regarding PointerPress event in DropDown List. Jun 3, 2008 5:50 PM
Here's a test applet showing both the skinnable and standard UI variants. As mentioned, using the Standard UI is uglier because of some nonpublic code that has to be copied over.


{curl 7.0 applet}
 
Using SkinnableDropdownListUI (easier):
 
{define-class public open SkinnableBallooningDDLUI
  {inherits SkinnableDropdownListUI}
 
  {constructor public {default
                          control:#DropdownList = null,
                          control-skin:DropdownListSkin = {DropdownListSkin},
                          control-feel:DropdownListFeel = {DropdownListFeel},
                          ...
                      }
    {construct-super
        control = control,
        control-skin = control-skin,
        control-feel = control-feel,
        ...
    }
  }
 
  {method public open {show-dropdown}:void
    {type-switch self.control
     case ddl:DropdownList do
        || >>>> Insert any special handling here:
        {ddl.append "One More"}
        || <<<<
    }
    {super.show-dropdown}
  }
 
}
 
{let sample-list-1:DefaultListModel =
    {DefaultListModel
        "One",
        "Two",
        "Three"
    }
}
 
{DropdownList
    prompt = "Choose a state:",
    data-model = sample-list-1,
    dropdown-height = 2in,
    ui-object =
        {SkinnableBallooningDDLUI
            control-skin = {SkinnedDropdownListSkin},
            control-feel =
                {platform-switch
                 case "mac" do
                    {AquaDropdownListFeel}
                 else
                    {SkinnedDropdownListFeel}
                }
        }
}
 
Using StandardDropdownListUI (harder):
 
{define-class public open StandardBallooningDDLUI
  {inherits StandardDropdownListUI}
 
  field private _last-search-time:#DateTime
  field private _search-buf:#StringBuf
 
  {constructor public {default
                          control:#DropdownList = null,
                          ...
                      }
    {construct-super
        control = control,
        ...
    }
  }
 
  {getter private {last-search-time}:DateTime
    {return
        {if-non-null dt = self._last-search-time then
            dt
         else
            def dt = {DateTime}
            set self._last-search-time = dt
            dt
        }
    }
  }
 
  {getter private {search-buf}:StringBuf
    {return
        {if-non-null buf = self._search-buf then
            buf
         else
            def buf = {StringBuf}
            set self._search-buf = buf
            buf
        }
    }
  }
 
  {method protected open {create-dropdown-helper}:StandardDropdownHelper
    def helper = {BallooningDropdownHelper self}
    || Unfortunately, the original event handler we would like to
    || attach here is not public, so we inline a copy from open controls.
    {helper.menu.add-event-handler
        {on e:KeyPress do
            {self.process-menu-event e}
        }
    }
    {return helper}
  }
 
  {method private {process-menu-event e:KeyPress}:void
    {if e.insertable? then
        || Get the current selected index.
        def menu = self.dropdown-helper.menu
        let current-selected:int = -1
        {for i = 0 below menu.size do
            {type-switch menu[i]
             case mi:MenuItem do
                {if mi.item-active? then
                    set current-selected = i
                    {break}
                }
            }
        }
        def index = {self.get-index-for-selection e.value, current-selected}
        {if index != -1 then
            def ddl = self.control asa DropdownList
            def item = ddl[index]
            {if-non-null menu-action = 
                {item.find-ancestor 
                    {proc {g:Graphic}:bool {return g isa MenuAction}}
                } asa #MenuAction
             then
                {menu-action.become-active-item}
            }                
        }
    }
 
  }
 
  {method private {get-index-for-selection 
                      c:char,
                      current-index:int
                  }:int
    def ddl = self.control asa DropdownList
    let size:int = ddl.for-loop-count
    def last-search-time = self.last-search-time
    def search-buf = self.search-buf
 
    def reset?:bool = {last-search-time.elapsed} > 0.5s
 
    def sel-index = 
        {if reset? then
            {max current-index + 1, 0}
         else
            {max current-index, 0}
        }
 
    {if reset? then
        {search-buf.clear}
    }
    {search-buf.append c}
    set self._last-search-time = {DateTime}
 
    {for i:int = 0 below size do
        let index:int = (i+sel-index) mod size
        {if ddl[index].selectable? then
            {type-switch ddl[index].value 
             case str:StringInterface do
                {if not str.empty? then
                    {if {str.prefix? search-buf, ignore-case? = true} then
                        {return index}
                    }
                }
             case ch:char do
                {if {char-downcase ch} == {char-downcase c} then
                    {return index}
                }
            }
        }
    }
 
    || Make sure that if some one is searching for the same character
    || quickly and we added it to our list then collapse and search
    || again.
    {if search-buf.size == 2 and 
        search-buf[0] == search-buf[1]
     then
        {search-buf.remove 1}
    }   
 
    {for i:int = 0 below size do
        let index:int = (i+sel-index + 1) mod size
        {if ddl[index].selectable? then
            {type-switch ddl[index].value 
             case str:StringInterface do
                {if not str.empty? then
                    {if {str.prefix? search-buf, ignore-case? = true} then
                        {return index}
                    }
                }
             case ch:char do
                {if {char-downcase ch} == {char-downcase c} then
                    {return index}
                }
            }
        }
    }
 
    {return -1}
  }
 
}
 
{define-class public open BallooningDropdownHelper
  {inherits StandardDropdownHelper}
 
  {constructor public {default owner:StandardBaseDropdownUI}
    {construct-super owner}
  }
 
  {method public open {show-dropdown}:void
    {type-switch (self.owner asa ControlUI).control
     case ddl:DropdownList do
        || >>>> Insert any special handling here:
        {ddl.append "One More"}
        || <<<<
    }
    {super.show-dropdown}    
  }
 
}
 
 
{let sample-list-2:DefaultListModel =
    {DefaultListModel
        "One",
        "Two",
        "Three"
    }
}
 
{DropdownList
    prompt = "Choose a state:",
    data-model = sample-list-2,
    dropdown-height = 2in,
    ui-object = {StandardBallooningDDLUI}
}
 
Click to view carl's profile Curl carl 83 posts since
Oct 17, 2007
5. Re: Regarding PointerPress event in DropDown List. Jun 3, 2008 6:01 PM
in response to: carl
The version that I posted would be for the Nitro beta. For actual use just copy and paste the half of the solution that you want into your existing applet. To use the skinnable solution your applet would also have to be importing STYLED-CONTROLS.
Click to view htabussum's profile Level 1 htabussum 6 posts since
Jan 31, 2008
6. Re: Regarding PointerPress event in DropDown List. Jun 4, 2008 1:58 AM
in response to: carl

Hi Carl & Kamal,

Thanks for the Replies. This has really helped me a lot in my application.