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
|