Issue 60 * November 7 2008

See, Ma, no objects!
Displaying a clipboard window in a handler

by Jeanne DeVoto

One common method of application development uses a framework, a skeleton application that includes all the objects and code required for basic application functions. Frameworks can be very handy to work within, but they require a lot of overhead and, if you're rolling your own, a great deal of time to develop and debug. Yet basic application capabilities, or even not-so-basic ones, are used over and over in a programmer's life. The ability to drop such a capability into a program, without needing to include an entire application framework, is valuable. This article shows you one technique for developing such a capability: a window that shows the contents of the clipboard, similar to the window that appears when you choose Edit > Show Clipboard in the Mac Finder.

 

Clipboard Screenshot

 

The handler discussed here will display the contents of the clipboard in a stack, which contains two fields. The interesting thing about the displayClipboardWindow handler is that it doesn't require you to create these objects, copy them, or keep track of them: the handler creates and updates them instead. In other words, although this handler uses objects, it stands completely alone and can be dropped into any script without needing to copy its objects along with it.

You use the handler simply by calling it‚ for example, from a menuPick handler:

on menuPick theItem
   ...
case "Show Clipboard"
   displayClipboardWindow
   ... 

Let's take a look at the displayClipboardWindow handler, piece by piece:

Setting up the window

on displayClipboardWindow
   if there is a stack "jaedClipboardDisplay" then
      -- clipboard window already exists because we've shown it
      -- before:
      set the defaultStack to "jaedClipboardDisplay"
   else -- create a window to show the clipboard
      -- set up the template stack - the clipboard window will
      -- have these properties:
      set the rect of the templateStack to "100,400,450,550"
      set the minHeight of the templateStack to "120"
      set the minWidth of the templateStack to "200"
      set the style of the templateStack to modeless
      set the liveResizing of the templateStack to true
      set the visible of the templateStack to false
      set the destroyStack of the templateStack to false
      -- set the script to resize the fields when the clipboard
      -- window is resized:
      set the script of the templateStack to \
      "on resizeStack newWidth,newHeight" & return & \
      "set the rect of field" && quote & "Clipboard Display" & \
      quote \
      && "to 0,newHeight - 15,newWidth - 15,newHeight" & return \
      & \
      "set the rect of field" && quote & "Clipboard Text" & \
      quote \
      && "to -1,-2,newWidth, newHeight - 15" & return & \
      "end resizeStack" 

First, the handler checks whether the stack already exists. This prevents the user from choosing the "Show Clipboard" menu item twice and creating two (or more!) clipboard windows. Because Revolution (by default) leaves stacks in memory even after the window is closed, if the user has previously opened the Clipboard window, the stack will already exist and won't need to be re-created. In this case, we'll just set the defaultStack property to the existing stack, so the changes we make to display the clipboard will automatically apply to the correct stack. Otherwise, the handler will create the stack.

We'll want the Clipboard window to have various properties, such as its size and location. We'll also set the visible property of the templateStack to false so the user won't see any flashing while we create fields in the stack. The handler sets these up ahead of time by changing the templateStack. (There's a template for each of Revolution's object types, and the properties of this template determine the starting properties of the object when it's created.)

The destroyStack property is set to false so the stack won't be removed from memory when the Clipboard window is closed; this means if the user moves or resizes the window, the application will remember those settings for the current session, instead of just re-opening the window in the default location. If you want the window to be created afresh, in the default location, every time it's re-opened, just change the destroyStack setting to true instead of false.

The handler also sets the script of the template stack. This script is a simple one: all it does is update the stack contents when the stack is resized. Here's how the script will look when it's placed in the clipboard stack:

on resizeStack newWidth,newHeight
   set the rect of field "Clipboard Display" to 0,newHeight - \
   15,newWidth - 15,newHeight
   set the rect of field "Clipboard Text" to -1,-2,newWidth, \
   newHeight - 15"
end resizeStack 
Creating the window
-- create the stack:
create stack "jaedClipboardDisplay"
-- remove the changes we made to the template:
reset the templateStack
set the defaultStack to "jaedClipboardDisplay" 

Once the templateStack property has been set up, we create the stack. You can name it anything you want, as long as the same name is used consistently throughout the handler and as long as it doesn't duplicate the name of another stack in your application. Remember, since the templateStack's visible property is set to false, the new stack can't be seen yet.

Finally, the handler clears the changes we made to the templateStack by using the reset command. This ensures your application can use the templateStack without any surprises.

The handler now sets the defaultStack property to the new stack's name, so the two fields it's about to create will end up in the correct stack.

 

Creating a field for the clipboard data type

set the lockText of the templateField to true
set the traversalOn of the templateField to false
set the showBorder of the templateField to false
set the opaque of the templateField to false
set the margins of the templateField to 4
set the rect of the templateField to 0,\
the height of stack "jaedClipboardDisplay" - 15, \
the width of stack "jaedClipboardDisplay" - 15, \
the height of stack "jaedClipboardDisplay"
create field "Clipboard Type" 
This small field sits at the bottom of the Clipboard window and specifies whether the clipboard consists of text, an image, or something else, or whether it's empty. As with the stack, we'll set up the field's properties ahead of time using the templateField mechanism, then create the field.

We're about to create another field that will share most of the same properties, so we won't reset the templateField yet.


Creating a field to hold the clipboard contents
set the vScrollbar of the templateField to true
set the hScrollbar of the templateField to true
set the opaque of the templateField to true
set the showBorder of the templateField to true
set the margins of the templateField to 8
set the fixedLineHeight of the templateField to false
set the rect of the templateField to "-1,-2", \
the width of stack "jaedClipboardDisplay", \
the height of stack "jaedClipboardDisplay" - 15
create field "Clipboard Text"
-- clean up:
reset the templateField
end if 

All the properties we've already set still apply to the templateField, so (for example) we don't need to set the lockText property. This field should scroll, since we don't know how much data it will contain. Because it might contain an image of any size, the handler also sets the hScroll to true to let it scroll horizontally.

 

Showing the clipboard contents

put "Clipboard contents:" && the clipboard into field \
"Clipboard Type"
if the clipboard is "text" then
   set the HTMLText of field "Clipboard Text" to the \
   clipboardData["HTML"]
else if the clipboard is "files" then
   set the text of field "Clipboard Text" to the \
   clipboardData["files"]
else if the clipboard is "image" then
   -- put it into a hidden image and then set the imagesource
   -- of the field:
   create invisible image "Clipboard Image"
   put the clipboardData["image"] into image "Clipboard Image"
   put space into field "Clipboard Text" -- could be any character
   set the imagesource of char 1 of field "Clipboard Text" to \
   the ID of image "Clipboard Image"
else if the clipboard is empty then
   put "Empty clipboard" into field "Clipboard Type"
   hide field "Clipboard Text"
else -- or some other type we don't display
   hide field "Clipboard Text"
end if 

The clipboard function tells us what kind of data is on the clipboard. (This handler deals with images and styled text.) The clipboardData property, on the other hand, gives us the data itself. The clipboardData is an array: the array key gives the data type.

When the clipboard contains styled or plain text, the clipboard function returns "text". In this case, we can use the HTML, RTF, or text elements of the clipboardData. The handler uses the HTML element, clipboardData["HTML"], because it includes styles. If we used clipboardData["text"], we'd get the plain text with no style information. This clarifies an important point: the correspondence between the possible values of the clipboard function and the possible elements of the clipboardData is not exact, so we can't just use the clipboard function's value as the key for the clipboardData.

When the clipboard contains an image, the handler creates a new hidden image and puts the data into it, then displays the image in the field, using the imageSource property. Why not just show the image and hide the field? Because if the image is too large for the Clipboard window, we want to allow the user to scroll. Since the field is already set up for scrolling in both directions, displaying the image in the field is the way to go.

 

Showing the window

show stack "jaedClipboardDisplay"
go to stack "jaedClipboardDisplay" -- bring to front
end displayClipboardWindow 

Remember that the stack was created invisible, so the preceding shenanigans have not been seen by the user. We now show the Clipboard window, and bring it to the front (in case it already existed but was behind another window). This completes the handler.

For easy copying and pasting, here's the whole thing:

on displayClipboardWindow
   if there is a stack "jaedClipboardDisplay" then
      -- clipboard window already exists because we've shown it
      -- before:
      set the defaultStack to "jaedClipboardDisplay"
   else -- create a window to show the clipboard
      -- set up the template stack - the clipboard window will
      -- have these properties:
      set the rect of the templateStack to "100,400,450,550"
      set the minHeight of the templateStack to "120"
      set the minWidth of the templateStack to "200"
      set the style of the templateStack to modeless
      set the liveResizing of the templateStack to true
      set the visible of the templateStack to false
      set the destroyStack of the templateStack to false
      -- set the script to resize the fields when the clipboard
      -- window is resized:
      set the script of the templateStack to \
      "on resizeStack newWidth,newHeight" & return & \
      "set the rect of field" && quote & "Clipboard Type" & \
      quote \
      && "to 0,newHeight - 15,newWidth - 15,newHeight" & return \
      & \
      "set the rect of field" && quote & "Clipboard Text" & \
      quote \
      && "to -1,-2,newWidth, newHeight - 15" & return & \
      "end resizeStack"
      -- create the stack:
      create stack "jaedClipboardDisplay"
      -- remove the changes we made to the template:
      reset the templateStack
      set the defaultStack to "jaedClipboardDisplay"
      set the lockText of the templateField to true
      set the traversalOn of the templateField to false
      set the showBorder of the templateField to false
      set the opaque of the templateField to false
      set the margins of the templateField to 4
      set the rect of the templateField to 0,\
      the height of stack "jaedClipboardDisplay" - 15, \
      the width of stack "jaedClipboardDisplay" - 15, \
      the height of stack "jaedClipboardDisplay"
      create field "Clipboard Type"
      set the vScrollbar of the templateField to true
      set the hScrollbar of the templateField to true
      set the opaque of the templateField to true
      set the showBorder of the templateField to true
      set the margins of the templateField to 8
      set the fixedLineHeight of the templateField to false
      set the rect of the templateField to "-1,-2", \
      the width of stack "jaedClipboardDisplay", \
      the height of stack "jaedClipboardDisplay" - 15
      create field "Clipboard Text"
      -- clean up:
      reset the templateField
   end if -- we've now created the stack if needed
   put "Clipboard contents:" && the clipboard into field \
   "Clipboard Type"
   if the clipboard is "text" then
      set the HTMLText of field "Clipboard Text" to the \
      clipboardData["HTML"]
   else if the clipboard is "files" then
      set the text of field "Clipboard Text" to the \
      clipboardData["files"]
   else if the clipboard is "image" then
      -- put it into a hidden image and then set the
      -- imagesource of the field:
      create invisible image "Clipboard Image"
      put the clipboardData["image"] into image "Clipboard Image"
      put space into field "Clipboard Text" -- could be any \
      character
      set the imagesource of char 1 of field "Clipboard Text" \
      to the ID of image "Clipboard Image"
   else if the clipboard is empty then
      put "Empty clipboard" into field "Clipboard Type"
      hide field "Clipboard Text"
   else -- or some other type we don't display
      hide field "Clipboard Text"
   end if
   show stack "jaedClipboardDisplay"
   go to stack "jaedClipboardDisplay" -- bring to front
end displayClipboardWindow 
Main Menu What's New