ListBoxes and Mouse Cases

Dear fellow Igorians,

In trying to understand how to program with panels and controls, I came across a spinet that allows one to shuffle (drag and drop) items within a list. Please, have a look at the code bellow. The case of double-clicking should be ignored. However, when I double-click an item, I receive the notice: " ** a wave read gave error: Index out of range for wave "testList"." and Igor replaces the value of the list I have doubled-clicked by the last value on that list. Can anyone explain why Igor is doing this?

#pragma rtGlobals=3     // Use modern global access method and strict wave access.
Function OpenDragNDropExample()
    Make/O/T/N=4 root:testList = {"item 1", "item 2", "item 3", "item 4"}
    Make/O/N=4 root:testSel
    DoWindow/K Panel_DragNDrop
    Execute/Q "Panel_DragNDrop()"
End
 
Window Panel_DragNDrop() : Panel
    PauseUpdate; Silent 1       // building window...
    NewPanel /W=(350,125,650,325) as "Drag List Items Example"
    ListBox list0,pos={1,2},size={298,197},proc=ListBoxProc_DragNDropLB
    ListBox list0,listWave=root:testList,selWave=root:testSel,mode= 1,selRow= 1
EndMacro
 
Function ListBoxProc_DragNDropLB(lba) : ListBoxControl
    STRUCT WMListboxAction &lba
 
    Variable row = lba.row
    Variable col = lba.col
    WAVE/T/Z listWave = lba.listWave
    WAVE/Z selWave = lba.selWave
 
    switch( lba.eventCode )
        case -1: // control being killed
            break
        case 1: // mouse down
            Variable/G V_MouseDownRow = row
            break
        case 2: // mouse up
            if(row != V_MouseDownRow)                       // dragged?
                NVAR V_MouseDownRow
                String item = listWave[V_MouseDownRow]
                DeletePoints V_MouseDownRow, 1, listWave    // do swap
                InsertPoints row, 1, listWave
                listWave[row] = item
            endif
            KillVariables V_MouseDownRow    // cleanup variable
            break
        case 3: // double click
            break
        case 4: // cell selection
        case 5: // cell selection plus shift key
            break
        case 6: // begin edit
            break
        case 7: // finish edit
            break
    endswitch
 
    return 0
End
The value of row passed to the action procedure can be more than the last index of the list wave.

See the Debugger screenshot I've attached.

--Jim Prouty
Software Engineer, WaveMetrics, Inc.
ListboxRow4.PNG
The first problem is that a double click actually sends a series of events to your procedure: 1, 4, 2, 3, 4, 2 -- in that order. The case 3 case doesn't capture the entire sequence.

The second problem is that the first execution of case 2 kills your V_MouseDownRow global variable. So the second time case 2 is called, this variable is null. The check for row != null is true, so the following code executes and as Jim pointed out, row is greater than the last index in your wave. The default Igor behavior is to substitute the last value in a wave if the last index is exceeded.
Hence the error message and the reason row is replaced with the last value in the wave.

I've modified your list box proc somewhat and it seems to work for my limited testing.

As a final comment, you can learn a lot about how code works by judiciously sprinkling it with diagnostic print statements, also learning to use the debugger pays off quite handsomely.

Hope this helps.

Function ListBoxProc_DragNDropLB(lba) : ListBoxControl
    STRUCT WMListboxAction &lba
 
    Variable row = lba.row
    Variable col = lba.col
    WAVE/T/Z listWave = lba.listWave
    WAVE/Z selWave = lba.selWave
 
    switch( lba.eventCode )
        case -1: // control being killed
            break
        case 1: // mouse down
            Variable/G V_MouseDownRow = row
            break
        case 2: // mouse up
            //quit if V_MouseDownRow isn't a string or numeric variable
            //otherwise create a pointer to it.
            if( exists( "V_MouseDownRow" ) == 2 )
                NVAR V_MouseDownRow
            else
                break
            endif
            if(row != V_MouseDownRow)                       // dragged?
                String item = listWave[V_MouseDownRow]
                DeletePoints V_MouseDownRow, 1, listWave    // do swap
                InsertPoints row, 1, listWave
                listWave[row] = item
            endif
            KillVariables V_MouseDownRow    // cleanup variable
            break
        case 3: // double click
            break
        case 4: // cell selection
        case 5: // cell selection plus shift key
            break
        case 6: // begin edit
            break
        case 7: // finish edit
            break
    endswitch
    print "code 1: ", lba.eventcode
    return 1
End
Thank you both! It makes more sense to me now!
Thanks for the suggestions also!

R.