294 lines
13 KiB
Rexx
294 lines
13 KiB
Rexx
|
/*----------------------------------------------------------------------------*/
|
||
|
/* */
|
||
|
/* Copyright (c) 2012-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. */
|
||
|
/* */
|
||
|
/*----------------------------------------------------------------------------*/
|
||
|
|
||
|
/**
|
||
|
* ToolTip control example.
|
||
|
*
|
||
|
* This example demonstrates several advanced techniques in using ToolTips.
|
||
|
*
|
||
|
* Typically a single tool is created for a dialog control. This works fine
|
||
|
* for creating tools for, say, buttons. Even if you have 20 buttons, it is
|
||
|
* pretty reasonable to create 20 tools in a ToolTip.
|
||
|
*
|
||
|
* But, what if you want to display a tip for each item in a tree-view? What if
|
||
|
* the tree-view has hundreds, or even thousands, of items. Creating several
|
||
|
* thousand tools is no longer reasonable. One technique is to create one tool
|
||
|
* and dynamically make it appear to be multiple tools.
|
||
|
*
|
||
|
* That technique is demonstrated in this example. We use a tree-view control
|
||
|
* to show the technique. The basic principle is to track where the mouse is,
|
||
|
* and dynamically update the tool each time it moves from one item to another.
|
||
|
*
|
||
|
* Normally you would program this by monitoring the mouse moves and updating
|
||
|
* the tool when the mouse moves require it. To do this, you can not use the
|
||
|
* SUBCLASS flag for the tool, because you need to dynamically update tool
|
||
|
* _before_ the tool sees the mouse move. You would monitor the mouse moves,
|
||
|
* decide whether to update or not the tool, and then forward every mouse move
|
||
|
* on to the tool. For the puposes of discussion, we will refer to the
|
||
|
* forwarding of the mouse moves as 'relaying' the moves. And, actually for the
|
||
|
* ToolTip to work correctly it needs to see all the mouse related messages.
|
||
|
* So, the relaying is really relaying all mouse messages, not just the mouse
|
||
|
* move message.
|
||
|
*
|
||
|
* This presents a problem in ooDialog because mouse messages are sent to the
|
||
|
* window under the mouse, in this case the tree-view. In addition, if we set
|
||
|
* up a tool for the tree-view, but can not use the SUBCLASS flags, the ToolTip
|
||
|
* event notifications are sent to the tree-view, not to our dialog.
|
||
|
*
|
||
|
* In this case, we need a way to monitor the mouse messages sent to the
|
||
|
* tree-view and a way to intercept the ToolTip event notifications that would
|
||
|
* be sent to the tree-view. For ToolTips, ooDialog provides a method, the
|
||
|
* manageAtypicalTool() method, that provides this functionality.
|
||
|
*
|
||
|
* This method monitors all mouse messages and relays the messages to the tool.
|
||
|
* Before it relays the mouse messages to the took, it will invoke a method in
|
||
|
* the Rexx dialog for the relay event. In addition, it intercepts all the
|
||
|
* ToolTip event notifications sent to the dialog control and invokes a method
|
||
|
* in the Rexx dialog for each event.
|
||
|
*
|
||
|
* Of course, the application may not need to have an event handler for all the
|
||
|
* events, or even for the relay event. So, manageAtypicalTool() allows the
|
||
|
* programmer to just connect the events it needs.
|
||
|
*
|
||
|
* Although this example just focuses on a specific use of manageAtypicalTool(),
|
||
|
* the method can be used in any situation where custom ToolTips are needed for
|
||
|
* dialog controls. The same principles that necessitate its use here, will be
|
||
|
* found in many situations.
|
||
|
*
|
||
|
* Note that in this specific case, there is an easier way to produce the same
|
||
|
* results as this example does. The customPositionToolTip.rex example shows
|
||
|
* how to use the easier method. However, this example is very useful in
|
||
|
* showing how the manageAtypicalToo() works. The principles here can be used
|
||
|
* in other situations.
|
||
|
*/
|
||
|
|
||
|
sd = locate()
|
||
|
.application~setDefaults('O', sd'manageControlTool.h', .false)
|
||
|
|
||
|
dlg = .TreeViewDialog~new(sd'manageControlTool.rc', IDD_TREEVIEW)
|
||
|
dlg~execute("SHOWTOP", IDI_DLG_OOREXX)
|
||
|
|
||
|
|
||
|
::requires "ooDialog.cls"
|
||
|
|
||
|
::class 'TreeViewDialog' subclass RcDialog
|
||
|
|
||
|
::method initDialog
|
||
|
expose currentItem currentLocation currentText
|
||
|
|
||
|
-- These variables are used to help track which tool needs to be shown.
|
||
|
currentItem = 0
|
||
|
currentText = ''
|
||
|
currentLocation = 'NOWHERE'
|
||
|
|
||
|
tv = self~newTreeView(IDC_TREE)
|
||
|
|
||
|
-- Set up our ToolTip
|
||
|
tt = self~createToolTip(IDC_TT)
|
||
|
|
||
|
rect = tv~clientRect
|
||
|
|
||
|
-- We need to specify very precisely the attributes of the tool we are going
|
||
|
-- to add. We can not do that using the addTool() or addToolRect()
|
||
|
-- convenience methods. So we need a ToolInfo object and to use the
|
||
|
-- addToolEx() method.
|
||
|
|
||
|
toolInfo = .ToolInfo~new(tv, 10, '', 'TRANSPARENT', rect)
|
||
|
tt~addToolEx(toolInfo)
|
||
|
|
||
|
tt~setMaxTipWidth(250)
|
||
|
|
||
|
-- Set up our tree-view and populate it.
|
||
|
tv~setItemHeight(20)
|
||
|
self~populateTree(tv)
|
||
|
|
||
|
-- Now start the monitoring process.
|
||
|
tt~manageAtypicalTool(tv, .array~of('RELAY', 'NEEDTEXT', 'SHOW'))
|
||
|
|
||
|
|
||
|
/** onRelay()
|
||
|
*
|
||
|
* This is the event handler for the mouse message relaying event. It is
|
||
|
* invoked by our monitoring process for every mouse message. The method is
|
||
|
* invoked _before_ the monitor relays the mouse message to the ToolTip.
|
||
|
*
|
||
|
* This method is crucial to how this example works. For every mouse message,
|
||
|
* we calculate if the mouse is still over the label of the same item it was
|
||
|
* over previously. If it is not, we hide the ToolTip window by invoking the
|
||
|
* pop().
|
||
|
*
|
||
|
* After we return form this method, the mouse message is forwarded on to the
|
||
|
* ToolTip to let it do its ToolTip thing. When the ToolTip decides the mouse
|
||
|
* has hovered long enough in one spot, it displays its window. Since we have
|
||
|
* set the current text to the string for the tree-view item when the mouse
|
||
|
* first moved over the item, the correct "tool" text is displayed.
|
||
|
*
|
||
|
* The text to display is actually passed on to the ToolTip from our onNeedTex()
|
||
|
* method.
|
||
|
*/
|
||
|
::method onRelay unguarded
|
||
|
expose currentItem currentText currentLocation
|
||
|
use arg toolTip, pos, mouseMsg, treeView
|
||
|
|
||
|
-- Determine which, if any, item the mouse is over.
|
||
|
d = treeView~hitTestInfo(pos)
|
||
|
|
||
|
-- Save the old item and location and sent the current item and location to
|
||
|
-- where the mouse is at this moment.
|
||
|
oldItem = currentItem
|
||
|
oldLocation = currentLocation
|
||
|
currentItem = d~hItem
|
||
|
currentLocation = d~location
|
||
|
|
||
|
-- If mouse is over a new item or location, hide the ToolTip. Set our
|
||
|
-- current text to the text for the item we are now over. If we are not
|
||
|
-- over the label of an item, set the current text to the empty string.
|
||
|
if oldItem \== currentItem | oldLocation \== currentLocation then do
|
||
|
if currentItem <> 0, currentLocation~wordPos('ONLABEL') <> 0 then do
|
||
|
currentText = treeView~getItemData(currentItem)
|
||
|
end
|
||
|
else do
|
||
|
currentText = ''
|
||
|
end
|
||
|
toolTip~pop
|
||
|
end
|
||
|
|
||
|
return 0
|
||
|
|
||
|
|
||
|
/** onNeedText()
|
||
|
*
|
||
|
* This is the event handler for the ToolTip's NEEDTEXT event notification.
|
||
|
*
|
||
|
* The notification was actually sent to the tree-view, but, our monitor process
|
||
|
* has intercepted the notification and sent it to us instead of the tree-view.
|
||
|
*
|
||
|
* The real work was already done in the onRelay() method. All we need to do is
|
||
|
* set the TEXT index in the info .Directory object to what the onRelay() method
|
||
|
* set the current text to.
|
||
|
*/
|
||
|
::method onNeedText unguarded
|
||
|
expose currentItem currentText
|
||
|
use arg toolTip, treeView, info, userData, flags
|
||
|
|
||
|
info~text = currentText
|
||
|
|
||
|
-- Return false so that the ToolTip does not store the text. We want it to
|
||
|
-- always ask us for the text because the mouse could be over any item.
|
||
|
return .false
|
||
|
|
||
|
|
||
|
|
||
|
/** onShow()
|
||
|
*
|
||
|
* This is our event handler for the ToolTip's SHOW event notification.
|
||
|
*
|
||
|
* The notification was actually sent to the tree-view, but our monitor process
|
||
|
* has intercepted the notificatio and sent it to us instead of the tree-view.
|
||
|
*
|
||
|
* Normally the ToolTip places its window below the tree-view item. This often
|
||
|
* over-laps and obscures the other items. What we want is to place the ToolTip
|
||
|
* window to the right and centered with the item. This way nothing is
|
||
|
* obscured.
|
||
|
*
|
||
|
* The custom positioning of the ToolTip window is straight-forward. We get the
|
||
|
* bounding rectangle of the tree-view item's label, convert the point to client
|
||
|
* coordinates, and then reposition the ToolTip window to where we want it.
|
||
|
*
|
||
|
* Note that we must return .true then so that the ToolTip is informed not to
|
||
|
* position the window in its default position.
|
||
|
*/
|
||
|
::method onShow unguarded
|
||
|
expose currentItem
|
||
|
use arg toolTip, treeView
|
||
|
|
||
|
r = .Rect~new
|
||
|
if treeView~getItemRect(currentItem, r) then do
|
||
|
treeView~client2screen(r)
|
||
|
|
||
|
toolTip~setWindowPos(0, r~right + 5, r~top, 0, 0, "NOACTIVATE NOSIZE NOZORDER")
|
||
|
return .true
|
||
|
end
|
||
|
|
||
|
return .false
|
||
|
|
||
|
|
||
|
/** populateTree()
|
||
|
*
|
||
|
* This is a private method used to populate the tree-view control with our
|
||
|
* items.
|
||
|
*
|
||
|
* Note that each item has an item data object assigned to it. In this case the
|
||
|
* object is a string. The string is the text to display for the tool tip for
|
||
|
* that item.
|
||
|
*/
|
||
|
::method populateTree private
|
||
|
use strict arg tv
|
||
|
|
||
|
hItem = tv~add("Peter", , ,"BOLD EXPANDED", , 'Data unique to Peter.')
|
||
|
|
||
|
hItem = tv~add(,"Mike", , ,"EXPANDED", , 'Mike talks a lot.')
|
||
|
|
||
|
data = 'George was famous once.'.endOfLine'He is unknown to my'.endOfLine'niece.'
|
||
|
hItem = tv~add(, ,"George", , , , , data)
|
||
|
|
||
|
hItem = tv~add(, ,"Monique", , , , , 'Monique has never been to France.')
|
||
|
|
||
|
hItem = tv~add(, , ,"John", , , , , 'John owns 4 dogs.')
|
||
|
|
||
|
hItem = tv~add(, ,"Chris", , , , , 'Chris will never be president.')
|
||
|
|
||
|
hItem = tv~add( , "Maud", , , , , 'Maud likes to bake.')
|
||
|
|
||
|
hItem = tv~add( , "Ringo", , , , , 'Ringo has a LOT of money.')
|
||
|
|
||
|
hItem = tv~add("Paul", , ,"BOLD EXPANDED", , "Two roads lead to Paul's house.")
|
||
|
|
||
|
hItem = tv~add( , "Dave", , , , , 'No one lives with Dave.')
|
||
|
|
||
|
hItem = tv~add( , "Sam", , , , , 'Sam loves pizza.')
|
||
|
|
||
|
hItem = tv~add( , "Jeff", , , , , 'Jeff rides his bike to work.')
|
||
|
|
||
|
hItem = tv~add("Mary", , ,"BOLD EXPANDED", , 'Mary owes $49.50 to her dentist.')
|
||
|
|
||
|
hItem = tv~add( , "Helen", , , , , 'A closer look at Helen is warranted.')
|
||
|
|
||
|
hItem = tv~add( , "Michelle", , , , , 'The Toyota Camray belongs to Michelle.')
|
||
|
|
||
|
hItem = tv~add( , "Diana", , , , , 'Go go Chargers.')
|