272 lines
11 KiB
Rexx
272 lines
11 KiB
Rexx
|
/*----------------------------------------------------------------------------*/
|
||
|
/* */
|
||
|
/* Copyright (c) 2013-2014 Rexx Language Association. All rights reserved. */
|
||
|
/* */
|
||
|
/* This program and the accompanying materials are made available under */
|
||
|
/* the terms of the Common Public License v1.0 which accompanies this */
|
||
|
/* distribution. A copy is also available at the following address: */
|
||
|
/* https://www.oorexx.org/license.html */
|
||
|
/* */
|
||
|
/* Redistribution and use in source and binary forms, with or */
|
||
|
/* without modification, are permitted provided that the following */
|
||
|
/* conditions are met: */
|
||
|
/* */
|
||
|
/* Redistributions of source code must retain the above copyright */
|
||
|
/* notice, this list of conditions and the following disclaimer. */
|
||
|
/* Redistributions in binary form must reproduce the above copyright */
|
||
|
/* notice, this list of conditions and the following disclaimer in */
|
||
|
/* the documentation and/or other materials provided with the distribution. */
|
||
|
/* */
|
||
|
/* Neither the name of Rexx Language Association nor the names */
|
||
|
/* of its contributors may be used to endorse or promote products */
|
||
|
/* derived from this software without specific prior written permission. */
|
||
|
/* */
|
||
|
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS */
|
||
|
/* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT */
|
||
|
/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS */
|
||
|
/* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT */
|
||
|
/* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, */
|
||
|
/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED */
|
||
|
/* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, */
|
||
|
/* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY */
|
||
|
/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING */
|
||
|
/* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS */
|
||
|
/* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
|
||
|
/* */
|
||
|
/*----------------------------------------------------------------------------*/
|
||
|
|
||
|
/**
|
||
|
* This example shows how to embedd a drop down combo box in a list-view, to
|
||
|
* allow the user to edit the subitems in the list-view.
|
||
|
*
|
||
|
* To activate the editing, the user clicks once on a subitem, then clicks one
|
||
|
* more time to activate the editing. A drop down combo box takes the place of
|
||
|
* the subitem in the list-view. With a drop down combo box the user can
|
||
|
* either enter text directly in the edit part of the combo box, or select an
|
||
|
* item from the list.
|
||
|
*
|
||
|
* When the user is in the editing mode, hitting enter, escape, or clicking the
|
||
|
* mouse any where else on the scren, ends the editing. If enter is hit, the
|
||
|
* changes are accepted, otherwise, the changes are abandoned.
|
||
|
*
|
||
|
* The key to how this works is creating an invisible combo box, which is then
|
||
|
* made a child of the list-view. When editing mode is entered, the combo box
|
||
|
* positioned over the subitem, sized to the size of the subitem, and made
|
||
|
* visible. When editing is over, the combo box is made invisible again.
|
||
|
*
|
||
|
* The Rexx combo box, after it is made a child of the list-view, can be used
|
||
|
* as normal, with one caveat: Since it is a child of the list-view, the combo
|
||
|
* box no longer sends its event nofications to the dialog. They are sent to
|
||
|
* the list-view. This means that connecting the combo box events will have no
|
||
|
* effect.
|
||
|
*/
|
||
|
|
||
|
-- Set the defaults for this application. Use the global .constDir 'O'nly,
|
||
|
-- turn automatic data detection off (.false.) Then we add a few symbols
|
||
|
-- to the global .constDir:
|
||
|
.application~setDefaults('O', , .false)
|
||
|
.constDir[IDC_LISTVIEW] = 200
|
||
|
.constDir[IDC_CB] = 201
|
||
|
|
||
|
dlg = .SimpleLV~new
|
||
|
if dlg~initCode = 0 then do
|
||
|
dlg~create(30, 30, 325, 200, "In-place Editing List View", "VISIBLE")
|
||
|
dlg~execute("SHOWTOP")
|
||
|
end
|
||
|
|
||
|
return 0
|
||
|
-- End of entry point.
|
||
|
|
||
|
::requires "ooDialog.cls"
|
||
|
|
||
|
::class 'SimpleLV' subclass UserDialog
|
||
|
|
||
|
/** defineDialog()
|
||
|
*
|
||
|
* Standard defineDialog. We create a combo box, list-view, and ok button in
|
||
|
* the dialog template. Note the combo box is created invisible.
|
||
|
*
|
||
|
* We add a flag to keep track of whether the combo box is visisble or not. We
|
||
|
* also connect the events we need to monitor.
|
||
|
*/
|
||
|
::method defineDialog
|
||
|
expose cbVisible
|
||
|
|
||
|
cbVisible = .false
|
||
|
|
||
|
self~createComboBox(IDC_CB, 10, 20, 40, 12, 'PARTIAL HIDDEN')
|
||
|
self~createListView(IDC_LISTVIEW, 10, 20, 305, 145, "REPORT SHOWSELALWAYS")
|
||
|
self~createPushButton(IDOK, 280, 175, 35, 15, "DEFAULT", "Close")
|
||
|
|
||
|
self~connectListViewEvent(IDC_LISTVIEW, "CLICK", onClick, .true)
|
||
|
self~connectListViewEvent(IDC_LISTVIEW, "BEGINSCROLL", onBeginScroll, sync)
|
||
|
self~connectListViewEvent(IDC_LISTVIEW, "ENDSCROLL", onBeginScroll, .false)
|
||
|
|
||
|
|
||
|
/** initDialog()
|
||
|
*
|
||
|
* Here we do 2 normal things, populate the list-view and populate the combo
|
||
|
* box.
|
||
|
*
|
||
|
* The rest is what makes this work. Drop down combo boxes create a child edit
|
||
|
* control. When the combo box gets the focus, the focus is actually set to
|
||
|
* the edit control.
|
||
|
*
|
||
|
* We get a Rexx edit object that represents the underlying edit control of the
|
||
|
* combo box. The isGrandchild() method sets up a connection to some of the
|
||
|
* event notifications sent by the grandchild control, to a Rexx method in this
|
||
|
* dialog. We need that event connection to monitor the Esc, Enter key events,
|
||
|
* and the lost focus event.
|
||
|
*
|
||
|
* The other key thing we do is set the parent of the combo box to be the list
|
||
|
* view. This parent / child relation is what keeps the combo box drawn
|
||
|
* correctly, it ensures that the combo box is drawn over the top of the list
|
||
|
* view.
|
||
|
*/
|
||
|
::method initDialog
|
||
|
expose list cb edit
|
||
|
|
||
|
list = self~newListView(IDC_LISTVIEW)
|
||
|
cb = self~newComboBox(IDC_CB)
|
||
|
|
||
|
cb~add('One')
|
||
|
cb~add('Two')
|
||
|
cb~add('Three')
|
||
|
|
||
|
edit = cb~getEditControl
|
||
|
edit~isGrandchild
|
||
|
|
||
|
cb~setParent(list)
|
||
|
|
||
|
self~setUpListView(list)
|
||
|
|
||
|
|
||
|
/** onClick()
|
||
|
*
|
||
|
* This is the event handler for a click on the list-view. We track the clicks
|
||
|
* and when we see that the user has clicked twice in a row on the same subitem
|
||
|
* we enter editing mode.
|
||
|
*
|
||
|
* When we enter editing mode, we get the rectangle of the subitem we are going
|
||
|
* to edit, size the combo box to that size, position the combo box over the
|
||
|
* subitem, and make the combo box visible.
|
||
|
*
|
||
|
* We set our flag so that we know the combo box is now visible, and assign the
|
||
|
* focus to the combo box. And that's it.
|
||
|
*
|
||
|
* Notice that we set the height of the combo box to 4 times the height of the
|
||
|
* subitem. This allows space for the drop down.
|
||
|
*/
|
||
|
::method onClick unguarded
|
||
|
expose cb cbVisible lastIdx lastCol
|
||
|
use arg id, itemIndex, columnIndex, keyState, , lv
|
||
|
|
||
|
if lastIdx == itemIndex & lastCol == columnIndex then do
|
||
|
if columnIndex > 0 then do
|
||
|
r = lv~getSubitemRect(itemIndex, columnIndex, 'LABEL')
|
||
|
ret = cb~setWindowPos(lv~hwnd, r~left, r~top, r~right - r~left, 4 * (r~bottom - r~top), "SHOWWINDOW NOZORDERCHANGE")
|
||
|
cbVisible = .true
|
||
|
cb~assignFocus
|
||
|
end
|
||
|
else do
|
||
|
r = lv~getItemRect(itemIndex, 'LABEL')
|
||
|
ret = cb~setWindowPos(lv~hwnd, r~left, r~top, r~right - r~left, 4 * (r~bottom - r~top), "SHOWWINDOW NOZORDERCHANGE")
|
||
|
cbVisible = .true
|
||
|
cb~assignFocus
|
||
|
end
|
||
|
end
|
||
|
|
||
|
lastIdx = itemIndex; lastCol = columnIndex
|
||
|
|
||
|
return 0
|
||
|
|
||
|
|
||
|
/** onBeginScroll()
|
||
|
*
|
||
|
* This is the event handler for the begin and end scroll events. When the
|
||
|
* user is in the editing mode and then moves a way from the combo box, we
|
||
|
* interpret that as canceling the edit.
|
||
|
*
|
||
|
* This works fine if the user tabs out of the combo box, used the mouse to
|
||
|
* click outside the combo box, brings somer other application to the fore-
|
||
|
* ground. But, for some reason, clicking on the scroll bars fro the list-view
|
||
|
* does not trigger the onEditGrandChildEvent() handler. This seriously messes
|
||
|
* up the logic.
|
||
|
*
|
||
|
* The begin and / or end scroll event is sent as soon as the user clicks on
|
||
|
* the scroll bars. So, we connect that event and use the event handler to
|
||
|
* hide the combo box if it is visible.
|
||
|
*/
|
||
|
::method onBeginScroll unguarded
|
||
|
expose cbVisible edit
|
||
|
use arg ctrlID, dx, dy, lv, isBegin
|
||
|
|
||
|
if cbVisible then self~hideComboBox(edit)
|
||
|
return 0
|
||
|
|
||
|
|
||
|
/** onEditGrandChildEvent()
|
||
|
*
|
||
|
* This is the event handler for events that happen in a grandchild control.
|
||
|
* There are 4 events that get forwarded on to the grandfathe dialog. The Esc,
|
||
|
* Tab, and Enter key events, and the lost focus event. Which event is
|
||
|
* specified by the 2nd argument, which uses a keyword to denote the event.
|
||
|
*
|
||
|
* The isGrandChild() method automatically sets up the connection to the 4
|
||
|
* events. Since we did request the tab key event be connected, we won't get
|
||
|
* that notification. The other 3 notifications, all signal the end of the
|
||
|
* editing mode. On enter, we need to update the subitem text with the new
|
||
|
* text.
|
||
|
*/
|
||
|
::method onEditGrandChildEvent unguarded
|
||
|
expose list lastIdx lastCol
|
||
|
use arg id, key, editCtrl
|
||
|
|
||
|
if key == 'enter' then do
|
||
|
text = editCtrl~getText~strip
|
||
|
if text \== '' then list~setItemText(lastIdx, lastCol, text)
|
||
|
self~hideComboBox(editCtrl)
|
||
|
end
|
||
|
else if key == 'escape' then self~hideComboBox(editCtrl)
|
||
|
else if key == 'killfocus' then self~hideComboBox(editCtrl, .false)
|
||
|
|
||
|
return 0
|
||
|
|
||
|
|
||
|
/** hideComboBox()
|
||
|
*
|
||
|
* Makes the combo box invisible, removes its state, and assign the focus back
|
||
|
* to the list-view. This is done each time the editing mode is ended.
|
||
|
*/
|
||
|
::method hideComboBox private unguarded
|
||
|
expose cb cbVisible list
|
||
|
use strict arg editCtrl, assignFocus = .true
|
||
|
|
||
|
cb~hide
|
||
|
cbVisible = .false
|
||
|
cb~selectIndex
|
||
|
editCtrl~setText("")
|
||
|
|
||
|
if assignFocus then list~assignFocus
|
||
|
|
||
|
|
||
|
/** setUpListView()
|
||
|
*
|
||
|
* Sets up the list view by adding the columns and populating the list with
|
||
|
* rows.
|
||
|
*/
|
||
|
::method setUpListView private
|
||
|
use strict arg list
|
||
|
|
||
|
list~addExtendedStyle("FULLROWSELECT GRIDLINES CHECKBOXES HEADERDRAGDROP")
|
||
|
|
||
|
list~insertColumn(0, "Row (List-view item)", 75)
|
||
|
list~insertColumn(1, "Column 2 (subitem 1)", 70)
|
||
|
list~insertColumn(2, "Column 3 (subitem 2)", 70)
|
||
|
|
||
|
do i = 1 to 200
|
||
|
list~addRow(i, , "Row" i, "Row / Col ("i", 2)", "Row / Col ("i", 3)")
|
||
|
end
|
||
|
|
||
|
|