FOX Community

Recent site activity

Cookbook‎ > ‎

Creating a path list widget

Problem

Create a new widget in FXRuby to keep a list of paths. The difference with a standard FXList is that when a user double clicks on an item, a box containing and edit field and a browse button appears on the selected item to let the user edit the path or choose, with a directory dialog, a new path.

Solution

A solution is to inherit from a FXList adding two members: a FXTextField and a FXButton. When a double click is performed on a list item the textfield and the button are shown over it to give the impression that this is a behaviour of the item. Otherwise they are hidden. Their position and size depends on the item width and height and the presence of a vertical scollbar.

###########################################################################################################
#
# The PathList widget is a component that collects a list of paths. It appears as a normal list but when
# the user double clicks an item, an edit field and a browse button appear on the item to let the user edit
# or select a path thorugh a dialog box. An item can be deleted using the Del button or emptying its
# content. An item can be selected also with the arrows keys and edited pressing the return key.
#
# Author: Marco Frailis
#
###########################################################################################################


class PathList < FXList

include Responder

def initialize(composite,tgt = nil, sel = 0, opts = LIST_NORMAL,xpos = 0,ypos = 0,pwidth = 0,pheight = 0)

super(composite, tgt,sel,opts,xpos,ypos,pwidth,pheight)

identifier :ID_DBCLICK, :ID_EDITFIELD, :ID_BRWBUTTON

FXMAPFUNC(SEL_DOUBLECLICKED, 0, 'onCmdDoubleClicked')
FXMAPFUNC(SEL_KEYPRESS, PathList::ID_EDITFIELD, 'onCmdEditKeyPress')
FXMAPFUNC(SEL_FOCUSOUT, PathList::ID_EDITFIELD, 'onCmdFocusOut')
FXMAPFUNC(SEL_COMMAND, PathList::ID_BRWBUTTON, 'onCmdBrowse')
FXMAPFUNC(SEL_KEYPRESS, 0, 'onKeyPress')
FXMAPFUNC(SEL_CHANGED, FXWindow::ID_VSCROLLED, 'onVScrollerDragged')

#An horizontal frame which contains an edit field and a browse button.
#Note that it belongs to the composite passed in the constructor parameters
@editBox=FXHorizontalFrame.new(composite,FRAME_LINE|LAYOUT_FIX_X|LAYOUT_FIX_Y|LAYOUT_FIX_WIDTH,0,0,300,100,0,0,0,0)
@editBox.setVSpacing(0)
@editBox.setHSpacing(0)

#A textfield to insert or edit a path into the list
@textField=FXTextField.new(@editBox,10,self,ID_EDITFIELD,LAYOUT_FILL_X|LAYOUT_FILL_Y)
#A button opening a dialog to select a particular directory
@brwButton=FXButton.new(@editBox,"...",nil,self,ID_BRWBUTTON,BUTTON_NORMAL|LAYOUT_FILL_Y)
#The last item in the list is always empty (contains a space character)
appendItem(" ")

@lastItem = 0
@selectedItem = nil

#The edit box is hidden when no action is performed on the list
@editBox.hide

#This variable becomes true when the user press the return key
@returnPressed = false
end

#This method is called when the user double clicks a list item
def onCmdDoubleClicked(sender,sel,ptr)
scrollbar = verticalScrollBar
@selectedItem = getCurrentItem
#if the item selected is the last, than it's empty
if (@selectedItem == @lastItem)
@textField.text = ""
#otherwise the text field is filled with the path contained into the item
else
@textField.text = getItemText(@selectedItem)
end

#The edit box is resized and positioned to completely overlap the list item selected
width = (scrollbar.shown) ? getWidth-scrollbar.getWidth : getWidth
height = getItemHeight(@selectedItem)
@editBox.position(x,y+(height*@selectedItem)-scrollbar.position,width,height)
#The edit box is shown
@editBox.raiseWindow()
@editBox.show
@textField.setFocus
@textField.onCmdCursorEnd(nil,0,nil)
end

#This method is called when the user press a key.
#If the edit box is active and the user press return
#the edit box is hidden loosing its focus (see the foolowing method)
#otherwise it returns false so that the onKeyPress method is called
def onCmdEditKeyPress(sender,sel,ptr)
event = ptr
if (event.code == KEY_Return) && (@textField.hasFocus)
@returnPressed = true
@editBox.hide
else
return false
end
end

#This method "in practice" is called when the edit box looses its focus
#It sets a list item text to the path in the edit box. If the edit box
#is empty, the item is removed
def onCmdFocusOut(sender,sel,ptr)
posx,posy,z = parent.getCursorPosition
if (parent.getChildAt(posx,posy) != @editBox) || @returnPressed
if (@textField.text == "")
setCurrentItem = @selectedItem
removeElement
else
setItemText(@selectedItem,@textField.text)
@textField.text=""
if (@selectedItem == @lastItem)
#The last item contains only a space
appendItem(" ")
@lastItem += 1
end
end
@returnPressed = false
@editBox.hide
end
end

#This method is called when the user press the small browse button in the
# edit box. It raises a directory dialog box.
def onCmdBrowse(sender,sel,ptr)
dirDiag = FXDirDialog.new(self,"Path selection")
if (@textField.text != "")
dirDiag.setDirectory(@textField.text)
dirDiag.forceRefresh()
end
if (dirDiag.execute != 0)
@textField.text = dirDiag.getDirectory
@textField.onCmdCursorEnd(nil,0,nil)
end
@textField.setFocus
end

#If the key pressed by the user is a delete. the selected item
#is deleted
def onKeyPress(sender,sel,ptr)
event = ptr
if (event.code == KEY_Delete)
i = 0
while (i < getNumItems-1) do
if isItemSelected(i)
setCurrentItem(i)
removeElement
else
i += 1
end
end
else
super
end
end

#If the edit box is visible and the user scrolls the list
#it's position is adjusted to remain over the selected item
def onVScrollerDragged(sender,sel,ptr)
if (@editBox.shown)
width = getWidth
height = getItemHeight(@selectedItem)
scrollbar = verticalScrollBar
@editBox.position(x,y+(height*@selectedItem)-scrollbar.position,width-scrollbar.getWidth,height)
end
super
end

#Initializes the PathList with an array of paths
def setItems(items)
clearItems
items.each do |itemText|
appendItem(itemText)
end
appendItem(" ")
@lastItem = getNumItems-1
end

#Returns an array containing all the paths in the PathList
def getItems
a = []
0.upto(@lastItem-1) do |itemIndex|

a << getItemText(itemIndex)
end
return a
end

private

#Remove the current list item
def removeElement
if (getCurrentItem >= 0)
if (getCurrentItem==@lastItem)
setItemText(@lastItem," ")
else
removeItem(getCurrentItem)
@lastItem -= 1
end
end
end

end