Table of Contents
Table of Contents
The primary goal of this project was (and is) to provide a complete interface to FOX from Ruby. Ruby programs should be able to access FOX classes transparently; this includes deriving new Ruby classes from FOX classes and overriding their virtual functions. This goal has been met pretty well at this point although there are undoubtedly a number of bugs waiting to be discovered.
A secondary goal of the project is to promote Ruby and FOX, two great open-source projects that both deserve wider recognition. After discovering Ruby and monitoring the comp.lang.ruby newsgroup postings for only a few weeks, it became apparent that users were dissatisfied with the existing GUI options for Ruby. As with Python, Tk is the de facto standard because of its maturity and availability on a number of platforms (including the Macintosh). But Tk is also showing its age in many ways and it has failed to keep pace with some of the "younger" cross-platform GUI toolkits like FOX, wxWindows, FLTK, Qt and GTK+. Of the latter five, only Qt and GTK+ appeared (at the time) to have usable Ruby interfaces and there are some problems associated with these as well; for Qt, it's the restrictive license for the Windows platform version, and for GTK+ it's a Windows version that often lags far behind the standard Linux/Unix version. There is clearly a need for a modern, open-source, cross-platform GUI for Ruby, and FOX fills that need.
Since its first public release in January 2001, FXRuby has become one of the most popular GUI options for Ruby:
In a Ruby Garden poll held in July 2001, FXRuby edged out Ruby/GTK as the most-preferred GUI writing toolkit for Ruby.
In August 2001, FXRuby was added to the Pragmatic Programmers' Windows installer for Ruby.
In October 2001, Lyle gave a presentation on "Developing GUIs with FOX and Ruby" at the first annual Ruby Conference in Tampa, Florida.
Although the lack of documentation has been a sore spot for some time, several Ruby books (such as the Ruby Developer's Guide and The Ruby Way) feature FXRuby as a Ruby GUI development option.
Most recently, work has focused on keeping FXRuby up-to-date with the still evolving FOX library while looking for new ways to make Ruby GUI development fun. For example, FXRuby is under consideration for use as a GUI front-end to the fledgling FreeRIDE project. If you have suggestions about where you'd like to see things go, feel free to drop me an e-mail.
A few words of advice, before getting started:
If you're planning to use FXRuby on Windows, with the Pragmatic Programmers' Ruby installer for Windows, you may wish to just download the pre-compiled binaries from the SourceForge project page for FXRuby.
If you're planning to use FXRuby on Mac OS X, you may want to consult this page from the FOX Community Wiki, in addition to the standard build instructions listed below.
These instructions assume that you've already downloaded, compiled and installed FOX. Next, you'll need to download the FXRuby source code tarball and unpack it by typing:
$ tar xzf FXRuby-1.0.21.tar.gzThis will create a new directory called FXRuby-1.0.21. Change to the top-level directory and configure the build by typing:
$ ruby install.rb configBy default, the install.rb script will look for the FOX include files and library in the standard /usr/local/include/fox and /usr/local/lib directories, respectively. You can override these locations by passing a few additional arguments to install.rb during this step, e.g.
$ ruby install.rb config -- \
--with-fox-include=/home/lyle/fox-1.0.34/include \
--with-fox-lib=/home/lyle/fox-1.0.34/src/.libsOnce the build has been configured, you can start the build by typing:
$ ruby install.rb setupIt will take quite awhile to build FXRuby, even on a fast machine, so this might be a good time to take a coffee break. If you run into problems during the compilation, please check the list of things that can go wrong for workarounds for those problems.
Once it's finished compiling, install FXRuby by typing:
$ ruby install.rb installAs a quick sanity check, to make sure that all is well, you should probably fire up irb and try to import FXRuby:
$ irb
irb(main):001:0> require 'fox'
true
irb(main):002:0>If the import failed (usually with a message along the lines of "Cannot load library"), check the list of things that can go wrong for known problems. If that still doesn't solve your problem, drop me an e-mail or ask around on the Ruby newsgroup or mailing list; it's quite likely that someone else has run into this problem too. Once you do have a working FXRuby installation, you're ready to check out the example programs.
This section describes how to compile FXRuby using Microsoft Visual C++, for use with a Ruby that was also compiled using Visual C++.
This discussion assumes that you've built Ruby using the instructions and build files distributed with the standard Ruby source code. To review, you should have started by unpacking the source code tarball, changing into the top-level source code directory (e.g. C:\ruby-1.6.8) and then typing:
C:\ruby-1.6.8>win32\configure type 'nmake' to make ruby for mswin32. C:\ruby-1.6.8>nmake
After the compilation finished, you installed Ruby somewhere by typing, e.g.,
C:\ruby-1.6.8>nmake DESTDIR=C:\ruby installSimilarly, I'm assuming that you built the FOX library using the Developer Studio project files distributed with the standard FOX source code distribution. Although it's possible to build FXRuby against either the static library build of FOX (fox.lib) or the DLL build (foxdll.dll), these instructions currently cover only the static library build. Before you get started building FXRuby itself, you'll need to rename the static FOX library from its default filename (fox.lib) to foxst.lib. The reason for this rename is a charming little quirk in Microsoft's LINK utility and the fact that the eventual output name for the FXRuby DLL is also fox.so; for now, just take my word for it. So if your FOX source code distribution and build are found in the C:\fox-1.0.34 directory, you'd need to rename the file:
C:\fox-1.0.34\lib>rename fox.lib foxst.libNow you can configure the FXRuby build by typing:
C:\FXRuby-1.0.21>ruby install.rb config --make-prog=nmake -- \
--with-fox-include=C:\fox-1.0.21\include \
--with-fox-lib=C:\fox-1.0.21\libOnce the build has been configured, you can start the build by typing:
C:\FXRuby-1.0.21> ruby install.rb setupIt will take quite awhile to build FXRuby, even on a fast machine, so this might be a good time to take a coffee break. Because Visual C++ is such a strict compiler (usually a good thing), you will probably run into a few problems with non-ANSI declarations in the Ruby header files. If you do run into problems during the compilation, just check the next section for a list of things that could go wrong, and workarounds for those problems. None of them are showstoppers and none require you to restart the compile from scratch (just type ruby install.rb setup to pick up where you left off).
Once it's finished compiling, install FXRuby by typing:
C:\FXRuby-1.0.21> ruby install.rb installAs a quick sanity check, to make sure that all is well, you should probably fire up irb and try to import FXRuby:
C:\FXRuby-1.0.21> irb
irb(main):001:0> require 'fox'
true
irb(main):002:0>If the import failed (usually with a message along the lines of "Cannot load library"), check the list of things that can go wrong for known problems. If that still doesn't solve your problem, drop me an e-mail or ask around on the Ruby newsgroup or mailing list; it's quite likely that someone else has run into this problem too. Once you do have a working FXRuby installation, you're ready to check out the example programs.
"Too many arguments to function..."
The include files for Ruby versions 1.6.7 and earlier still have several function prototypes in the older "K & R" C style, where the function's argument list is not included; for example, the function rb_thread_wait_for() takes a single argument of type struct timeval, but its prototype in intern.h is:
void rb_thread_wait_for();
Because FXRuby is written in C++, and C++ requires strict ANSI C-style function prototypes, code that attempts to call one of these functions will fail to compile under some compilers. For example, the error message from gcc will look something like this:
FXRbApp.cpp: In method `long int FXRbApp::onChoreThreads (FXObject *, unsigned int, void *)': /usr/local/lib/ruby/1.6/i686-linux/intern.h:172: too many arguments to function `void rb_thread_wait_for ()' FXRbApp.cpp:100: at this point in file make: *** [FXRbApp.o] Error 1
while the error message from Microsoft's Visual C++ compiler looks something like this:
FXRbApp.cpp(109): error C2660: 'rb_thread_wait_for' : function does not take 1 parameters NMAKE : fatal error U1077: 'cl' : return code '0x2' Stop.
This problem with the Ruby header files has been corrected for Ruby versions 1.6.8 (and later), but if you're building for an older version of Ruby you can do one of two things to work around the problem:
If you're using gcc version 2.95 or earlier, try modifying the compiler flags (CFLAGS) in the FXRuby Makefile to include the -fno-strict-prototype option; this should instruct the compiler to allow these kinds of discrepancies. Unfortunately, this flag is not supported in more recent versions of gcc.
A more direct approach is to just fix the offending declarations in the Ruby include file(s), i.e. change the declaration for rb_thread_wait_for() in intern.h to read:
void rb_thread_wait_for(struct timeval);
and change the declaration for rb_gc_mark() in intern.h to read:
void rb_gc_mark(VALUE);
"Virtual Memory Exhausted"
For FXRuby releases earlier than version 0.99.173 it was common for the compiler to run out of memory trying to compile core_wrap.cpp, with an error message like:
core_wrap.cpp: In function 'void Init_core()': core_wrap.cpp:108596: virtual memory exhausted
This failure was due to the use of optimizations by the compiler; the FXRuby source code makes heavy use of C++ templates and some versions of gcc require a lot of memory to process these. Starting with FXRuby version 0.99.173, the extconf.rb script should disable compiler optimizations when it generates the FXRuby Makefile. If you suspect that it's not disabling optimizations (or can see this by watching the compile command lines), try modifying the compiler flags (CFLAGS) in the Makefile by hand to do so.
"Cannot load library"
On Linux and other Unix systems that support shared libraries, FOX is typically installed as a shared library named libFOX.so. After all of the source files for FXRuby are compiled, the last step is to link all of the FXRuby object files together with the FOX library (and possibly other system libraries) to produce a new shared library, named fox.so, that Ruby can import as an extension module.
There are a few things that can go wrong when you try to import this extension into Ruby. A common problem is that the operating system cannot locate the FOX shared library (libFOX.so) when it tries to dynamically load the FXRuby extension module; when this happens, the error message will look something like:
$ irb
irb(main):001:0> require 'fox'
LoadError: libFOX-0.99.so.173: cannot open shared object file: No such file or directory - /usr/local/lib/ruby/1.6/i586-linux/fox.so
from (irb):1:in 'require'
from (irb):1
One workaround for this problem is to modify the LD_LIBRARY_PATH environment variable to include the directory where libFOX.so is installed. For example, if libFOX.so is installed in /usr/local/lib, try setting:
$ export LD_LIBRARY_PATH=/usr/local/lib $ irb irb(main):001:0> require 'fox'
If this works, you can of course permanently add the LD_LIBRARY_PATH setting to your login file(s) so that you don't have to remember to type it each time. Another approach that should work for Linux is to modify your /etc/ld.so.conf file to include the installation directory (e.g. /usr/local/lib). If you'd like to do this instead, you'll need to (as root):
Edit your /etc/ld.so.conf file and add the directory where libFOX.so is installed; and,
At the shell prompt, type ldconfig to reload the linker configuration.
"Undefined symbol..."
Another problem that has been reported by users of both Debian GNU/Linux and NetBSD 1.5 is an error message along the lines of:
/usr/lib/libstdc++.so.2: Undefined symbol "__vt_9exception"...
The fix for this problem is reported to be to modify the FXRuby Makefile and add -lgcc to the LIBS line.
Starting with FXRuby version 1.2, FXRuby uses RubyGems as its packaging and distribution method. The code is available both as
a "source" gem, which is compiled locally on your computer before it's installed; and,
a "binary" gem, which contains a precompiled version of the code for a specific operating system (such as Windows).
To install a source gem on a Unix/Linux box, you'd type something like:
$ sudo ruby fxruby-1.2.0.gemNote the use of the sudo command to invoke superuser privileges, since you'll typically need superuser privileges to install the library files. By default, the source gem will look for your FOX (and optionally, FXScintilla) installation in a few standard places, such as the /usr, /usr/local and /sw directories. If you've installed those libraries under some other directory (for example, in your home directory) you might need to pass some additional arguments on the command line, e.g.
$ sudo ruby fxruby-1.2.0.gem --force -- \
--with-fox-include=/home/lyle/include/fox-1.2 \
--with-fox-lib=/home/lyle/libIf you're installing a source gem on a Windows box, you'd instead type something like:
C:\> ruby fxruby-1.2.0.gem --force -- \
--with-fox-include=C:\include\fox-1.2 \
--with-fox-lib=C:\libIf you're installing a source gem, it can take quite awhile to build FXRuby, so this might be a good time to take a coffee break. You won't see any compiler output appear on the screen while the gem is compiling, but rest assured that the output is being saved into the gem_make.out file.
As a quick sanity check, to make sure that all is well, you should probably fire up irb and try to require the FXRuby gem once the installation is complete:
$ irb irb(main):001:0> require 'rubygems' true irb(main):002:0> require_gem 'fxruby' true
If the import failed (usually with a message along the lines of "Cannot load library"), drop me an e-mail or ask around on the Ruby newsgroup or mailing list; it's quite likely that someone else has run into this problem too. Once you do have a working FXRuby installation, you're ready to check out the example programs.
To install a binary gem for Windows, just type:
C:\> ruby fxruby-1.2.0-mswin32.gemAs a quick sanity check, to make sure that all is well, you should probably fire up irb and try to require the FXRuby gem once the installation is complete:
C:\> irb irb(main):001:0> require 'rubygems' true irb(main):002:0> require_gem 'fxruby' true
If the import failed (usually with a message along the lines of "Cannot load library"), drop me an e-mail or ask around on the Ruby newsgroup or mailing list; it's quite likely that someone else has run into this problem too. Once you do have a working FXRuby installation, you're ready to check out the example programs.
Table of Contents
There are a few things common to all programs that use FXRuby, and the purpose of this chapter is to help you get familiar with those. We'll do this by developing a short program that simply displays a button containing the text, "Hello, World!". For reference, this program is included in the examples subdirectory of the standard FXRuby source code distribution.
All of the code associated with the FXRuby extension is provided by the Fox module, so we need to start by require-ing this feature:
require 'fox'
Since all of the FXRuby classes are defined under the Fox module, you'd normally need to refer to them using their "fully qualified" names (i.e. names that begin with the Fox:: prefix). Because this can get a little tedious, and because there's not really much chance of name conflicts between FXRuby and other Ruby extensions, I usually like to add an include Fox statement so that all of the names in the Fox module are "included" into the global namespace:
require 'fox'
include FoxEvery FXRuby program begins by creating an FXApp instance:
require 'fox'
include Fox
theApp = FXApp.newThe FXApp instance has a lot of responsibilities in an FXRuby application. One of the most frequent ways you'll use it is to kick off the application's main event loop (which you'll see later in this tutorial). The application is also the object responsible for managing "global" resources like timers and the FOX registry. It is a different approach from some other GUI toolkits for Ruby, where these kinds of global resources are accessed using module-level methods. As you continue to develop programs using FXRuby, you'll learn about other ways that the FXApp object is used.
The next step is to create an FXMainWindow instance to serve as the application's main window. If you've used Ruby/Tk before, this is similar to its TkRoot window:
require 'fox'
include Fox
theApp = FXApp.new
theMainWindow = FXMainWindow.new(theApp, "Hello")Here, we pass theApp as the first argument to FXMainWindow.new to associate the new FXMainWindow instance with this FXApp. The second argument to FXMainWindow.new is a string that will be used for the main window's title.
So far, all we've done is instantiate the client-side objects. Unlike most other toolkits, FOX makes a distinction between client-side data (such as an FXMainWindow object) and server-side data (such as the X window associated with that Ruby object). To create the server-side objects associated with the already-constructed client-side objects, we call FXApp#create:
require 'fox'
include Fox
theApp = FXApp.new
theMainWindow = FXMainWindow.new(theApp, "Hello")
theApp.createBy default, all windows in FXRuby programs are invisible, so we need to call our main window's show instance method:
require 'fox'
include Fox
theApp = FXApp.new
theMainWindow = FXMainWindow.new(theApp, "Hello")
theApp.create
theMainWindow.showThe last step is to start the program's main loop by calling theApp's run instance method:
require 'fox'
include Fox
theApp = FXApp.new
theMainWindow = FXMainWindow.new(theApp, "Hello")
theApp.create
theMainWindow.show
theApp.runThe FXApp#run method doesn't return until the program exits. It is similar to the mainloop method used for Ruby/Tk and Ruby/GTK, and you can in fact use FXApp#mainloop as an alias for run if you prefer.
At this point, we have a working (if not very interesting) program that uses FXRuby. If you run it, you'll see something like this:

Obviously, we need to add a few things to make it more interesting. Let's start by putting a button inside the main window. The FXButton class provides a standard push-button widget:
require 'fox'
include Fox
theApp = FXApp.new
theMainWindow = FXMainWindow.new(theApp, "Hello")
FXButton.new(theMainWindow, "Hello, World!")
theApp.create
theMainWindow.show
theApp.runAs you might guess, passing theMainWindow as the first argument to FXButton.new tells FXRuby that the new button is a child of the main window. The second argument to FXButton.new is a string that will be displayed on the button. If you run the program now, you should see this:

Now we're cookin' with Crisco, but let's press on and see what other things we can do to improve this. You may have noticed by now that the only way to quit the program is to close the window using the window manager's "close window" option, or to just kill the program outright. We can do better than that. Let's add a message handler for the FXButton such that when you click the button, it causes the program to exit:
require 'fox'
include Fox
theApp = FXApp.new
theMainWindow = FXMainWindow.new(theApp, "Hello")
theButton = FXButton.new(theMainWindow, "Hello, World!")
theButton.connect(SEL_COMMAND) do |sender, selector, data|
exit
end
theApp.create
theMainWindow.show
theApp.runMost FOX objects send out messages (also known as events) when something interesting happens. FOX messages have four important elements:
The message sender is the object that sends the message. In this case, the FXButton instance is the sender.
The message type is a predefined integer constant that indicates what kind of event has occurred (i.e. why this message is being sent). In this case, the message type is SEL_COMMAND, which indicates that the command associated with this widget should be invoked.
The message identifier is another integer constant that is used to distinguish between different messages of the same type. For example, the message that tells a FOX window to make itself visible is a SEL_COMMAND message with the identifier FXWindow::ID_SHOW (where ID_SHOW is a constant defined in the FXWindow class). A different message identifier, FXWindow::ID_HIDE, tells an FXWindow instance to make itself invisible.
The message data is an object containing message-specific information. For this case (the FXButton's SEL_COMMAND message, there is no interesting message data, but we'll see other kinds of messages where the message data is useful.
For historical reasons, the message type and identifier are usually packed into a single 32-bit unsigned integer known as the selector, and this is the value that is passed into the message handler block. Since we don't actually need to use the sender, selector or data arguments for this particular message handler, we can just ignore them and shorten the code to:
theButton.connect(SEL_COMMAND) { exit }Re-run the program and push the button to convince yourself that it works.
To wrap up this introduction, we'd like to add a few finishing touches to the program. The first addition is to add a tool tip to the button, such that when the mouse cursor hovers over the button for a short while, it will pop up a little message describing what the button does:
require 'fox' include Fox theApp = FXApp.new theMainWindow = FXMainWindow.new(theApp, "Hello") theButton = FXButton.new(theMainWindow, "Hello, World!") theButton.tipText = "Push Me!" theButton.connect(SEL_COMMAND) { exit } FXTooltip.new(theApp) theApp.create theMainWindow.show theApp.run
There are two changes involved here. The first is to set the tool tip text for the button using the tipText accessor, and for this example we're setting the button's tip text to "Push Me!". The second change is to create the (single) FXTooltip instance for the application. Although this program shows the FXTooltip instance being created after the FXButton, it doesn't really matter when you do it. You just want to have instantiated the FXTooltip before you drop into the main event loop by calling FXApp#run. If you run this version and hover over the button for a second or so, you should see the tooltip pop up:

The final change is to add an icon to the button to make things a little more festive. FOX supports all of the popular image file formats (e.g. BMP, GIF, JPEG, PNG and TIFF) and you can use any of them as icons on buttons and labels. For this example, we'll use the one of the "Powered By Ruby" images created by Hal Fulton (and posted at the Ruby Garden Wiki):
require 'fox'
include Fox
theApp = FXApp.new
theMainWindow = FXMainWindow.new(theApp, "Hello")
theButton = FXButton.new(theMainWindow, "Hello, World!")
theButton.tipText = "Push Me!"
iconFile = File.open("pbr.jpg", "rb")
theButton.icon = FXJPGIcon.new(theApp, iconFile.read)
iconFile.close
theButton.connect(SEL_COMMAND) { exit }
FXTooltip.new(theApp)
theApp.create
theMainWindow.show
theApp.runHere, pbr.jpg is the file name of the JPEG image file. You want to be sure to open the file in binary mode (i.e. including the "b" mode flag), because there is a difference on the Windows platform. Since it's a JPEG image, we need to use the FXJPGIcon class to instantiate this icon. The first argument to FXJPGIcon.new is just a reference to the FXApp instance, and the second argument is the contents of the image file. We associate this icon object with our button using the button's icon accessor method. If you run this example, you should see:
When you have both text and an icon displayed on a button (or its superclass, FXLabel) the default positioning is to display the icon to the left of the text. For this particular example, however, it would probably be more appropriate to display the icon above the text. We can achieve this using the button's iconPosition accessor method:
theButton.iconPosition = ICON_ABOVE_TEXT
If you re-run the program after adding this line, you should see:
The last change we're going to make is to make the icon transparent. FOX allows you to specify that some regions of an icon should be treated as "transparent", meaning that whatever's underneath them shows through. FOX distinguishes those transparent regions from the non-transparent ones using a transparency color, and any pixels in the original image that have that color become transparent. In most cases, FOX can determine this transparency color automatically (indeed, for image file formats like GIF it's part of the image information). You can also specify the transparency color explicitly if you like.
For the icon we've chosen, it's pretty obvious that the transparency color is white, but let's let FOX figure that out for us. We want to activate two options for the icon:
the IMAGE_ALPHACOLOR option, which tells FOX that some regions of this image should be treated as transparent; and,
the IMAGE_ALPHAGUESS option, which tells FOX to guess the appropriate transparency color using the colors used in the four corners of the image.
To set these options, add this line to the program:
theButton.icon.options = IMAGE_ALPHACOLOR | IMAGE_ALPHAGUESS
and then re-run the program after making this change to see the final result:
Table of Contents
Two of the standard FOX widgets, FXText and FXTextField, provide clipboard support out of the box. For example, you can select some text in an FXTextField and then press Ctrl+C to copy that text to the system clipboard. You can also press Ctrl+X to "cut" the selected text to the clipboard, or Ctrl+V to paste text from the clipboard into an FXText or FXTextField widget. The purpose of this tutorial is to demonstrate how to interact with the clipboard programmatically, so that you can integrate additional clipboard support into your FXRuby applications.
In order to illustrate how to integrate cut and paste operations into your application, we'll start from a simple FXRuby application that doesn't yet provide any clipboard support. This application simply presents a list of customers (from some external source).
require 'fox'
require 'customer'
include Fox
class ClipMainWindow < FXMainWindow
def initialize(anApp)
# Initialize base class first
super(anApp, "Clipboard Example", nil, nil, DECOR_ALL, 0, 0, 400, 300)
# Place the list in a sunken frame
sunkenFrame = FXVerticalFrame.new(self, LAYOUT_FILL_X|LAYOUT_FILL_Y|FRAME_SUNKEN|FRAME_THICK,
0, 0, 0, 0, 0, 0, 0, 0)
# Customer list
customerList = FXList.new(sunkenFrame, 6, nil, 0, LIST_BROWSESELECT|LAYOUT_FILL_X|LAYOUT_FILL_Y)
$customers.each do |customer|
customerList.appendItem(customer.name, nil, customer)
end
end
def create
super
show(PLACEMENT_SCREEN)
end
end
if __FILE__ == $0
FXApp.new("ClipboardExample", "FXRuby") do |theApp|
ClipMainWindow.new(theApp)
theApp.create
theApp.run
end
end
We're assuming that the "customer" module defines a Customer class and a global array $customers that contains the list of customers. For a real world application, you might access this information from a database or some other source, but for this example we'll just use a hard-coded array:
# customer.rb
Customer = Struct.new("Customer", :name, :address, :zip)
$customers = []
$customers << Customer.new("Reed Richards", "123 Maple, Central City, NY", 010111)
$customers << Customer.new("Sue Storm", "123 Maple, Anytown, NC", 12345)
$customers << Customer.new("Benjamin J. Grimm", "123 Maple, Anytown, NC", 12345)
$customers << Customer.new("Johnny Storm", "123 Maple, Anytown, NC", 12345)
The goals for the next few sections are to extend this application so that users can select a customer from the list and copy that customer's information to the clipboard, and subsequently paste that information into another copy of the program (or some other clipboard-aware application).
Let's begin by augmenting the GUI to include a row of buttons along the bottom of the main window for copying and pasting:
require 'fox'
require 'customer'
include Fox
class ClipMainWindow < FXMainWindow
def initialize(anApp)
# Initialize base class first
super(anApp, "Clipboard Example", nil, nil, DECOR_ALL, 0, 0, 400, 300)
# Horizontal frame contains buttons
buttons = FXHorizontalFrame.new(self, LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH)
# Cut and paste buttons
copyButton = FXButton.new(buttons, "Copy")
pasteButton = FXButton.new(buttons, "Paste")
# Place the list in a sunken frame
sunkenFrame = FXVerticalFrame.new(self, LAYOUT_FILL_X|LAYOUT_FILL_Y|FRAME_SUNKEN|FRAME_THICK,
0, 0, 0, 0, 0, 0, 0, 0)
# Customer list
customerList = FXList.new(sunkenFrame, 6, nil, 0, LIST_BROWSESELECT|LAYOUT_FILL_X|LAYOUT_FILL_Y)
$customers.each do |customer|
customerList.appendItem(customer.name, nil, customer)
end
end
def create
super
show(PLACEMENT_SCREEN)
end
end
if __FILE__ == $0
FXApp.new("ClipboardExample", "FXRuby") do |theApp|
ClipMainWindow.new(theApp)
theApp.create
theApp.run
end
end
Note that the lines which appear in bold face are those which have been added (or changed) since the previous source code listing.
The clipboard is a kind of shared resource in the operating system. Copying (or cutting) data to the clipboard begins with some window in your application requesting "ownership" of the clipboard by calling the acquireClipboard() instance method. Let's add a handler for the "Copy" button press which does just that:
# User clicks Copy
copyButton.connect(SEL_COMMAND) do
customer = customerList.getItemData(customerList.currentItem)
types = [ FXWindow.stringType ]
if acquireClipboard(types)
@clippedCustomer = customer
end
end
The acquireClipboard() method takes as its input an array of drag types. A drag type is just a unique value, assigned by the window system, that identifies a particular kind of data. In this case, we're using one of FOX's pre-registered drag types (stringType) to indicate that we have some string data to place on the clipboard. Later, we'll see how to register customized, application-specific drag types as well.
The acquireClipboard() method returns true on success; since we called acquireClipboard() on the main window, this means that the main window is now the clipboard owner. At this time, we want to save a reference to the currently selected customer in the @clippedCustomer instance variable so that if its value is requested later, we'll be able to return the correct customer's information.
Whenever some other window requests the clipboard's contents (e.g. as a result of a "paste" operation) FOX will send a SEL_CLIPBOARD_REQUEST message to the current clipboard owner. Remember, the clipboard owner is the window that called acquireClipboard(). For our example, the main window is acting as the clipboard owner and so it needs to handle the SEL_CLIPBOARD_REQUEST message:
# Handle clipboard request self.connect(SEL_CLIPBOARD_REQUEST) do setDNDData(FROM_CLIPBOARD, FXWindow.stringType, Fox.fxencodeStringData(@clippedCustomer.to_s)) end
The setDNDData() method takes three arguments. The first argument tells FOX which kind of data transfer we're trying to accomplish; as it turns out, this method can be used for drag-and-drop (FROM_DRAGNDROP) and X11 selection (FROM_SELECTION) data transfer as well. The second argument to setDNDData() is the drag type for the data and the last argument is the data itself, a binary string.
If you're wondering why we need to call the fxencodeStringData() module method to preprocess the string returned by the call to Customer#to_s, that's a reasonable thing to wonder about. In order for FOX to play nice with other clipboard-aware applications, it must be able to store string data on the clipboard in the format expected by those applications. Unfortunately, that expected format is platform-dependent and does not always correspond directly to the format that Ruby uses internally to store its string data. The fxencodeStringData() method (and the corresponding fxdecodeStringData() method) provide you with a platform-independent way of sending (or receiving) string data with the stringType drag type.
If you run the program as it currently stands, you should now be able to select a customer from the list, click the "Copy" button and then paste the selected customer data (as a string) into some other application. For example, if you're trying this tutorial on a Windows machine, try pasting into a copy of Notepad or Microsoft Word. The pasted text should look something like:
#<struct Struct::Customer name="Joe Smith", address="123 Maple, Anytown, NC", zip=12345>
We've seen one side of the equation, copying string data to the clipboard. But before we can "round-trip" that customer data and paste it back into another copy of our customer list application, we're clearly going to need to transfer the data in some more useful format. That is to say, if we were to receive the customer data in the format that it's currently stored on the clipboard:
#<struct Struct::Customer name="Joe Smith", address="123 Maple, Anytown, NC", zip=12345>
We'd have to parse that string and try to extract the relevant data from it. We can do better than that. The approach we'll use instead is to serialize and deserialize the objects using YAML. First, make sure that the YAML module is loaded by adding this line:
require 'yaml'
somewhere near the top of the program. Next, register a custom drag type for Customer objects. We can do that by adding one line to our main window's create instance method:
def create
super
@customerDragType = getApp().registerDragType("application/x-customer")
show(PLACEMENT_SCREEN)
end
Note that by convention, the name of the drag type is the MIME type for the data, but any unique string will do. In our case, we'll use the string "application/x-customer" to identify the drag type for our YAML-serialized Customer objects.
With that in place, we can now go back and slightly change some of our previous code. When we acquire the clipboard, we'd now like to be able to offer the selected customer's information either as plain text (i.e. the previous format) or as a YAML document, so we'll include both of these types in the array of drag types passed to acquireClipboard():
# User clicks Copy
copyButton.connect(SEL_COMMAND) do
customer = customerList.getItemData(customerList.currentItem)
types = [ FXWindow.stringType, @customerDragType ]
if acquireClipboard(types)
@clippedCustomer = customer
end
end
Similarly, when we're handling the SEL_CLIPBOARD_REQUEST message, we now need to pay attention to which drag type (i.e. which data format) the requestor specified. We can do that by inspecting the target attribute of the FXEvent instance passed along with the SEL_CLIPBOARD_REQUEST message:
# Handle clipboard request
self.connect(SEL_CLIPBOARD_REQUEST) do |sender, sel, event|
case event.target
when FXWindow.stringType
setDNDData(FROM_CLIPBOARD, FXWindow.stringType, Fox.fxencodeStringData(@clippedCustomer.to_s))
when @customerDragType
setDNDData(FROM_CLIPBOARD, @customerDragType, @clippedCustomer.to_yaml)
else
# Ignore requests for unrecognized drag types
end
end
With these changes in place, we can now add a handler for the "Paste" button which requests the clipboard data in YAML format, deserializes it, and then adds an item to the customer list:
# User clicks Paste
pasteButton.connect(SEL_COMMAND) do
data = getDNDData(FROM_CLIPBOARD, @customerDragType)
if data
customer = YAML.load(data)
customerList.appendItem(customer.name, nil, customer)
end
end
The getDNDData() method used here is the inverse of the setDNDData() method we used earlier to push data to some other application requesting our clipboard data. As with setDNDData(), the arguments to getDNDData() indicate the kind of data transfer we're performing (e.g. FROM_CLIPBOARD) and the drag type for the data we're requesting. If some failure occurs (usually, because the clipboard owner can't provide its data in the requested format) getDNDData() will simply return nil.
Table of Contents
One of the more powerful features available to FOX applications is drag-and-drop. It's also one of the more complicated to understand. For more background, see the standard FOX documentation on Drag and Drop.
We're going to start by presenting a skeleton application consisting of a main window widget (a DropSite instance) that parents an FXCanvas widget:
require 'fox'
include Fox
class DropSite < FXMainWindow
def initialize(anApp)
# Initialize base class
super(anApp, "Drop Site", nil, nil, DECOR_ALL, 0, 0, 400, 300)
# Fill main window with canvas
@canvas = FXCanvas.new(self, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y)
end
def create
# Create the main window and canvas
super
# Show the main window
show(PLACEMENT_SCREEN)
end
end
if __FILE__ == $0
FXApp.new("DropSite", "FXRuby") do |theApp|
DropSite.new(theApp)
theApp.create
theApp.run
end
end
Since the main program (i.e. the part at the end) won't change for the rest of the tutorial, I won't show that code anymore. Since an FXCanvas widget relies on some other object (its message target) to draw its contents, we need to handle SEL_PAINT messages generated by the canvas. We'll do that by adding a handler that clears the canvas to its current background color:
require 'fox'
include Fox
class DropSite < FXMainWindow
def initialize(anApp)
# Initialize base class
super(anApp, "Drop Site", nil, nil, DECOR_ALL, 0, 0, 400, 300)
# Fill main window with canvas
@canvas = FXCanvas.new(self, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y)
# Handle expose events on the canvas
@canvas.connect(SEL_PAINT) { |sender, sel, event|
FXDCWindow.new(@canvas, event) { |dc|
dc.foreground = @canvas.backColor
dc.fillRectangle(event.rect.x, event.rect.y, event.rect.w, event.rect.h)
}
}
end
def create
# Create the main window and canvas
super
# Show the main window
show(PLACEMENT_SCREEN)
end
end
Run this basic version of the program to be sure that it's working properly so far. You should simply see an empty window with a white background.
Now, on to the fun stuff. Our goal is to be able to drag color data from some other window, such as an FXColorWell widget, and drop it onto the canvas in order to change the canvas' background color. In order for a FOX widget to be able to accept drops at all, we need to first call its dropEnable() method:
def initialize(anApp)
# Initialize base class
super(anApp, "Drop Site", nil, nil, DECOR_ALL, 0, 0, 400, 300)
# Fill main window with canvas
@canvas = FXCanvas.new(self, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y)
# Handle expose events on the canvas
@canvas.connect(SEL_PAINT) { |sender, sel, event|
FXDCWindow.new(@canvas, event) { |dc|
dc.foreground = @canvas.backColor
dc.fillRectangle(event.rect.x, event.rect.y, event.rect.w, event.rect.h)
}
}
# Enable canvas for drag-and-drop messages
@canvas.dropEnable
end
At this point, let's try a little test to see if the program does anything interesting yet. Start by running some other FOX or FXRuby program to use as a drag source for the color data. You should be able to use any program that displays an FXColorWell widget, and this includes the standard color dialog box shown here:

Each of the small colored boxes near the bottom of the color dialog box are color wells, and the large box on the left-hand side of the color dialog box is also a color well. Now start your drag-and-drop test program and try to drag a color from one of these color wells onto this window. At this point, the mouse pointer should turn into a stop sign, indicating that the canvas isn't accepting drops of color data yet.
To correct this problem, we need to use the canvas' acceptDrop() method to indicate whether or not we'll accept certain kinds of drops. You can call acceptDrop() any time after receiving the initial SEL_DND_ENTER message, but it's usually done in response to a SEL_DND_MOTION message. Let's add a handler for SEL_DND_MOTION messages from the canvas in DropSite's initialize() method. For now, we'll unconditionally accept drops from any drag source, regardless of what kind of data they're offering:
def initialize(anApp)
# Initialize base class
super(anApp, "Drop Site", nil, nil, DECOR_ALL, 0, 0, 400, 300)
# Fill main window with canvas
@canvas = FXCanvas.new(self, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y)
# Handle expose events on the canvas
@canvas.connect(SEL_PAINT) { |sender, sel, event|
FXDCWindow.new(@canvas, event) { |dc|
dc.foreground = @canvas.backColor
dc.fillRectangle(event.rect.x, event.rect.y, event.rect.w, event.rect.h)
}
}
# Enable canvas for drag-and-drop messages
@canvas.dropEnable
# Handle SEL_DND_MOTION messages from the canvas
@canvas.connect(SEL_DND_MOTION) {
# Accept drops unconditionally (for now)
@canvas.acceptDrop
}
end
Now try the previous test again. This time, when you try to drag from a color well to the drop-enabled canvas, you should see the mouse pointer turn into a small filled square. This is a visual cue to the user indicating that the canvas will accept a drop of the drag-and-drop data.
Now it's time to get more specific about what kind of data is being dragged between these applications, and how to process that data. So far, our drop-enabled canvas merely knows that you're dragging some kind of data from a drag source, but it doesn't know what kind of data it is. We really need to be more exclusive about what kinds of data are acceptable. FOX uses unique drag types to distinguish between different kinds of "draggable" data. As you'll see later, you have the freedom to define drag types for any kind of application-specific data that you need; but for now, we're going to use FOX's built-in drag type for color data.
Drag types (even the standard ones) must be registered before they can be used, and so we'll start by adding a few lines to DropSite's create() method to register the drag type for color data:
def create
# Create the main window and canvas
super
# Register the drag type for colors
FXWindow.colorType = getApp().registerDragType(FXWindow.colorTypeName)
# Show the main window
show(PLACEMENT_SCREEN)
end
Note that the first time that registerDragType() is called for a particular drag type name (such as FXWindow.colorTypeName) it will generate a unique identifier for that drag type. Subsequent calls to registerDragType() for the same drag type name will just return the previously-generated drag type. Now, we want to modify our SEL_DND_MOTION handler so that it's a little more picky about which kinds of drops it will accept:
# Handle SEL_DND_MOTION messages from the canvas
@canvas.connect(SEL_DND_MOTION) {
if @canvas.offeredDNDType?(FROM_DRAGNDROP, FXWindow.colorType)
@canvas.acceptDrop
end
}
Here, we call the canvas' offeredDNDType? method to ask if the drag source can provide its data in the requested format. Only if offeredDNDType? returns true will we call acceptDrop() as before.
The last step is to actually handle the drop, and for that we add a handler for the SEL_DND_DROP message:
# Handle SEL_DND_DROP message from the canvas
@canvas.connect(SEL_DND_DROP) {
# Try to obtain the data as color values first
data = @canvas.getDNDData(FROM_DRAGNDROP, FXWindow.colorType)
unless data.nil?
# Update canvas background color
@canvas.backColor = Fox.fxdecodeColorData(data)
end
}Assuming that the drag source is able to provide its data in the requested format, the getDNDData() method will return a String (which for our purposes is just a byte buffer). If you've defined your own application-specific drag types, this data can of course be anything, and we'll see examples of this in a later tutorial. But the data for standard drag types like FXWindow.colorType can be decoded using the appropriate built-in library functions. In this case, we use the fxdecodeColorData() method to convert the bytes into a color value that we can use.
Now comes the moment of truth. Try running your test program again (one that displays a color well). Now, when you drag a color from a color well and drop it onto the DropSite window, the canvas should change its background color accordingly.
The complete program is listed below, and is included in the examples directory under the file name dropsite.rb.
require 'fox'
include Fox
class DropSite < FXMainWindow
def initialize(anApp)
# Initialize base class
super(anApp, "Drop Site", nil, nil, DECOR_ALL, 0, 0, 400, 300)
# Fill main window with canvas
@canvas = FXCanvas.new(self, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y)
# Handle expose events on the canvas
@canvas.connect(SEL_PAINT) { |sender, sel, event|
FXDCWindow.new(@canvas, event) { |dc|
dc.foreground = @canvas.backColor
dc.fillRectangle(event.rect.x, event.rect.y, event.rect.w, event.rect.h)
}
}
# Enable canvas for drag-and-drop messages
@canvas.dropEnable
# Handle SEL_DND_MOTION messages from the canvas
@canvas.connect(SEL_DND_MOTION) {
if @canvas.offeredDNDType?(FROM_DRAGNDROP, FXWindow.colorType)
@canvas.acceptDrop
end
}
# Handle SEL_DND_DROP message from the canvas
@canvas.connect(SEL_DND_DROP) {
# Try to obtain the data as color values first
data = @canvas.getDNDData(FROM_DRAGNDROP, FXWindow.colorType)
unless data.nil?
# Update canvas background color
@canvas.backColor = Fox.fxdecodeColorData(data)
end
}
end
def create
# Create the main window and canvas
super
# Register the drag type for colors
FXWindow.colorType = getApp().registerDragType(FXWindow.colorTypeName)
# Show the main window
show(PLACEMENT_SCREEN)
end
end
if __FILE__ == $0
FXApp.new("DropSite", "FXRuby") do |theApp|
DropSite.new(theApp)
theApp.create
theApp.run
end
end
As before, we're going to start by presenting a skeleton application consisting of a main window widget (a DragSource instance) that parents an FXCanvas widget:
require 'fox'
include Fox
class DragSource < FXMainWindow
def initialize(anApp)
# Initialize base class
super(anApp, "Drag Source", nil, nil, DECOR_ALL, 0, 0, 400, 300)
# Fill main window with canvas
@canvas = FXCanvas.new(self, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y)
@canvas.backColor = "red"
# Handle expose events on the canvas
@canvas.connect(SEL_PAINT) { |sender, sel, event|
FXDCWindow.new(@canvas, event) { |dc|
dc.foreground = @canvas.backColor
dc.fillRectangle(event.rect.x, event.rect.y, event.rect.w, event.rect.h)
}
}
end
def create
# Create the main window and canvas
super
# Register the drag type for colors
FXWindow.colorType = getApp().registerDragType(FXWindow.colorTypeName)
# Show the main window
show(PLACEMENT_SCREEN)
end
end
if __FILE__ == $0
FXApp.new("DragSource", "FXRuby") do |theApp|
DragSource.new(theApp)
theApp.create
theApp.run
end
end
Since the main program (i.e. the part at the end) won't change for the rest of the tutorial, I won't show that code anymore. Furthermore, the DragSource class has a few things in common with the DropSite class from the previous example, namely:
We've defined a SEL_PAINT handler for the canvas widget that just clears the canvas to its current background color; and,
The drag type for colors (FXWindow.colorType) is registered in the DragSource's create() method.
As before, you may want to run this basic version of the program to be sure that it's working properly so far. You should simply see an empty window with a red background.
Now for this application, we want a drag operation to begin when the user presses the left mouse button inside the canvas and starts "dragging" away from the canvas. So the first change we need to make is to add a handler for the SEL_LEFTBUTTONPRESS message from the canvas:
@canvas.connect(SEL_LEFTBUTTONPRESS) {
#
# Capture (grab) the mouse when the button goes down, so that all future
# mouse events will be reported to this widget, even if those events occur
# outside of this widget.
#
@canvas.grab
# Advertise which drag types we can offer
dragTypes = [FXWindow.colorType]
@canvas.beginDrag(dragTypes)
}
Note that there are usually two things you're going to want to do in the SEL_LEFTBUTTONPRESS handler for a drag source. The first is to call grab() on the window that acts as the drag source. Calling grab() "captures" the mouse in the sense that subsequent mouse motion events will be reported as if they occurred inside the grab window. This is important, since in our case we're going to be dragging from this window to some other window, possibly a window in a different application altogether.
The second thing you'll want to do in the SEL_LEFTBUTTONPRESS handler for a drag source is to call beginDrag(). This not only kicks the application into drag-and-drop mode, but also provides a way for you to inform the system about the formats of data (i.e. the drag types) that this drag source is able to provide. For this example, the drag source is just going to offer one drag type.
Since releasing the left mouse button should end the drag operation, it's important to also add a handler for the SEL_LEFTBUTTONRELEASE message:
@canvas.connect(SEL_LEFTBUTTONRELEASE) {
@canvas.ungrab
@canvas.endDrag
}
This is pretty much the mirror image of our SEL_LEFTBUTTONPRESS handler. We call ungrab() to release the mouse capture, and endDrag() to clean up the drag-and-drop state.
The next change is to add a SEL_MOTION handler, to handle mouse motion events during the drag operation:
@canvas.connect(SEL_MOTION) { |sender, sel, event|
if @canvas.dragging?
@canvas.handleDrag(event.root_x, event.root_y)
unless @canvas.didAccept == DRAG_REJECT
@canvas.dragCursor = getApp().getDefaultCursor(DEF_SWATCH_CURSOR)
else
@canvas.dragCursor = getApp().getDefaultCursor(DEF_DNDSTOP_CURSOR)
end
end
}
The dragging? method returns true if we're in the middle of a drag-and-drop operation for the drag source window, i.e. after the call to beginDrag() but before the call to endDrag(). If we're not currently processing a drag operation, we're not really interested in mouse motion events for the canvas.
If we are processing a drag operation, however, we need to call handleDrag() on the drag source window. FOX uses this information to send drag-and-drop messages (such as SEL_DND_ENTER, SEL_DND_MOTION and SEL_DND_LEAVE) to the window that the mouse is currently over. Note that the coordinates passed to handleDrag() are root window coordinates, and not window-local coordinates.
Another good thing to consider doing here is to change the shape of the cursor depending on the drop site's response to the drag-and-drop messages from the drag source. For this example, we change the drag cursor to one of FOX's built-in cursor shapes (DEF_SWATCH_CURSOR) if we get any response other than DRAG_REJECT from the drop site. If the drop site rejects this drag source's overtures, we instead change the drag cursor to a cursor that resembles a stop sign (DEF_DNDSTOP_CURSOR).
I've purposely avoided suggesting that you run the program for the last couple of changes, since until now there wasn't going to be any visual indication that anything interesting was happening. But now you should be able to run the application in its current state and see a few things.
A first test is to confirm that the cursor shape changes to the "stop" sign when you attempt to drag over some window that isn't willing to accept a drop of the FXWindow.colorType drag type. Start up one of the other FXRuby example programs (such as the scribble.rb example) alongside your drag source program, and then try "dragging" from the drag source's canvas onto the Scribble program's canvas. During the drag attempt, the cursor should maintain its shape as a "stop" sign since the canvas in the scribble.rb example isn't drop-enabled.
Your next test can confirm that the cursor shape changes to the "swatch" cursor (a small square) when you attempt to drag over a window that is drop-enabled, and which is willing to accepts drops of this drag type. The obvious choice is the example from the previous section (the dropsite.rb program), but you should have success with any FXRuby example program that displays color wells. Start up one of these drop-enabled programs alongside your drag source program, and then try "dragging" from the drag source's canvas onto the drop site. While the mouse pointer is over a drop-enabled window, the cursor should change its shape.
The last (and most important) step is to actually complete the data transfer. At any time during a drag-and-drop operation, a drop site may request a copy of the drag-and-drop data by calling the getDNDData() method (as described in the previous section). When this happens, FOX sends a SEL_DND_REQUEST message to the current drag source window, and so we now add a handler for that message:
@canvas.connect(SEL_DND_REQUEST) { |sender, sel, event|
if event.target == FXWindow.colorType
@canvas.setDNDData(FROM_DRAGNDROP, FXWindow.colorType, Fox.fxencodeColorData(@canvas.backColor))
end
}
The first important thing to note here is that the target field of the FXEvent instance will contain the drag type requested by the drop site. If your drag source offers its data in multiple formats, you definitely need to check this in order to know how to package-up the data for transfer. However, even if you're only offering the data in a single format (as in this example) it's still a good idea to check the requested type, since a rogue drop site could ask for the data in some unexpected format.
Assuming that the drag type is as expected, the last step is send the data to the drop site by calling setDNDData(). As with the call to getDNDData() for our previous drop site program, you want to be sure to specify the origin of the data (FROM_DRAGNDROP), the drag type (FXWindow.colorType) and the data itself as a binary string.
As a final test of the completed program, try dragging from this window to some drop-enabled window (as described earlier). Now, when you release the left mouse button to complete the "drop", the drop site should change its color to red.
The complete program is listed below, and is included in the examples directory under the file name dragsource.rb.
require 'fox'
include Fox
class DragSource < FXMainWindow
def initialize(anApp)
# Initialize base class
super(anApp, "Drag Source", nil, nil, DECOR_ALL, 0, 0, 400, 300)
# Fill main window with canvas
@canvas = FXCanvas.new(self, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y)
@canvas.backColor = "red"
# Handle expose events on the canvas
@canvas.connect(SEL_PAINT) { |sender, sel, event|
FXDCWindow.new(@canvas, event) { |dc|
dc.foreground = @canvas.backColor
dc.fillRectangle(event.rect.x, event.rect.y, event.rect.w, event.rect.h)
}
}
# Handle left button press
@canvas.connect(SEL_LEFTBUTTONPRESS) {
#
# Capture (grab) the mouse when the button goes down, so that all future
# mouse events will be reported to this widget, even if those events occur
# outside of this widget.
#
@canvas.grab
# Advertise which drag types we can offer
dragTypes = [FXWindow.colorType]
@canvas.beginDrag(dragTypes)
}
# Handle mouse motion events
@canvas.connect(SEL_MOTION) { |sender, sel, event|
if @canvas.dragging?
@canvas.handleDrag(event.root_x, event.root_y)
unless @canvas.didAccept == DRAG_REJECT
@canvas.dragCursor = getApp().getDefaultCursor(DEF_SWATCH_CURSOR)
else
@canvas.dragCursor = getApp().getDefaultCursor(DEF_DNDSTOP_CURSOR)
end
end
}
# Handle left button release
@canvas.connect(SEL_LEFTBUTTONRELEASE) {
@canvas.ungrab
@canvas.endDrag
}
# Handle request for DND data
@canvas.connect(SEL_DND_REQUEST) { |sender, sel, event|
if event.target == FXWindow.colorType
@canvas.setDNDData(FROM_DRAGNDROP, FXWindow.colorType, Fox.fxencodeColorData(@canvas.backColor))
end
}
end
def create
# Create the main window and canvas
super
# Register the drag type for colors
FXWindow.colorType = getApp().registerDragType(FXWindow.colorTypeName)
# Show the main window
show(PLACEMENT_SCREEN)
end
end
if __FILE__ == $0
FXApp.new("DragSource", "FXRuby") do |theApp|
DragSource.new(theApp)
theApp.create
theApp.run
end
end
We've studied drag-and-drop enabled applications from two points of view, that of a drag source and that of a drop site. But for most applications, you'll want a window to act as both a drag source and a drop site. As it turns out, this is just a simple combination of the techniques we've already seen in the previous sections.
If we use our completed drag source example program from the previous section as a starting point, and then compare it to the drop site example from the first section, it is apparent that the only "pieces" missing are:
A call to dropEnable(), to make DragSource's canvas drop-enabled;
A handler for the SEL_DND_MOTION message; and,
A handler for the SEL_DND_DROP message.
If you merge those three short bits of code from dropsite.rb into dragsource.rb, you should end up with a program that can act as both a drag source and a drop site. To test this program, you should be able to start up two separate copies side-by-side and then drag from one to the other. Since, as written, both copies will start up with a red background, you might want to modify one of them to have a different initial backgroud color. Another interesting possibility is to start up a third program (such as an FXRuby example that has displays a color dialog) and drag colors back and forth between all three programs. You could spend days doing this and never leave the house.
The complete program is listed below, and is included in the examples directory under the file name dragdrop.rb.
require 'fox'
include Fox
class DragDropWindow < FXMainWindow
def initialize(anApp)
# Initialize base class
super(anApp, "Drag and Drop", nil, nil, DECOR_ALL, 0, 0, 400, 300)
# Fill main window with canvas
@canvas = FXCanvas.new(self, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y)
@canvas.backColor = "red"
# Enable canvas for drag-and-drop messages
@canvas.dropEnable
# Handle expose events on the canvas
@canvas.connect(SEL_PAINT) { |sender, sel, event|
FXDCWindow.new(@canvas, event) { |dc|
dc.foreground = @canvas.backColor
dc.fillRectangle(event.rect.x, event.rect.y, event.rect.w, event.rect.h)
}
}
# Handle left button press
@canvas.connect(SEL_LEFTBUTTONPRESS) {
#
# Capture (grab) the mouse when the button goes down, so that all future
# mouse events will be reported to this widget, even if those events occur
# outside of this widget.
#
@canvas.grab
# Advertise which drag types we can offer
dragTypes = [FXWindow.colorType]
@canvas.beginDrag(dragTypes)
}
# Handle mouse motion events
@canvas.connect(SEL_MOTION) { |sender, sel, event|
if @canvas.dragging?
@canvas.handleDrag(event.root_x, event.root_y)
unless @canvas.didAccept == DRAG_REJECT
@canvas.dragCursor = getApp().getDefaultCursor(DEF_SWATCH_CURSOR)
else
@canvas.dragCursor = getApp().getDefaultCursor(DEF_DNDSTOP_CURSOR)
end
end
}
# Handle SEL_DND_MOTION messages from the canvas
@canvas.connect(SEL_DND_MOTION) {
if @canvas.offeredDNDType?(FROM_DRAGNDROP, FXWindow.colorType)
@canvas.acceptDrop
end
}
# Handle left button release
@canvas.connect(SEL_LEFTBUTTONRELEASE) {
@canvas.ungrab
@canvas.endDrag
}
# Handle SEL_DND_DROP message from the canvas
@canvas.connect(SEL_DND_DROP) {
# Try to obtain the data as color values first
data = @canvas.getDNDData(FROM_DRAGNDROP, FXWindow.colorType)
unless data.nil?
# Update canvas background color
@canvas.backColor = Fox.fxdecodeColorData(data)
end
}
# Handle request for DND data
@canvas.connect(SEL_DND_REQUEST) { |sender, sel, event|
if event.target == FXWindow.colorType
@canvas.setDNDData(FROM_DRAGNDROP, FXWindow.colorType, Fox.fxencodeColorData(@canvas.backColor))
end
}
end
def create
# Create the main window and canvas
super
# Register the drag type for colors
FXWindow.colorType = getApp().registerDragType(FXWindow.colorTypeName)
# Show the main window
show(PLACEMENT_SCREEN)
end
end
if __FILE__ == $0
FXApp.new("DragDrop", "FXRuby") do |theApp|
DragDropWindow.new(theApp)
theApp.create
theApp.run
end
end
The hello.rb example program is about as short as it gets for a working FXRuby program. Use this as a starting point for understanding the basic elements of an FXRuby program, especially if you're new to GUI programming in general.

The hello2.rb example kicks it up a notch by adding an icon and tooltip to the button from the hello.rb example.

The scribble.rb example is a good demonstration of how to obtain a device context for a window (in this case, an FXCanvas) and draw into that window. It also provides a basic demonstration of how FOX's GUI updating mechanism can be used to automatically update the state of widgets based on the application's state. Observe the "Clear" button becoming enabled and disabled (greyed-out) depending on whether the canvas is currently "dirty" or "clean", and then see how this updating is actually handled in the code.

The button.rb example program shows off the various options (or button styles) for FXButton widgets.

The datatarget.rb example program demonstrates most or all of the widgets that can work with FOX data targets (that is, instances of class FXDataTarget). Data targets are special objects that have a a string, float or integer value associated with them, and can interact with widgets to keep the data target's value in sync with the widget's setting. For example, you can create a data target with a string value and attach that to a text field widget. When the user types a new value in the text field, the data target's value is automatically updated; and when the data target's value is changed, the text field will update its setting. Since a single data targets can be attached to multiple widgets, this can be a useful way to keep multiple controls for the same logical value in sync with each other.

The dialog.rb example is a simple program demonstrating how to construct and display modal and non-modal dialog boxes.

The dirlist.rb example program demonstrates the FXDirList widget. The directory list is a special kind of tree list, where each tree item represents a directory (or folder) in the file system.

The iconlist.rb example program demonstrates the FXIconList widget. An icon list is a special kind of list widget that can display its contents in one of three basic modes (details mode, small icons mode or large icons mode). The first screenshot below shows an icon list in details mode, while the second shows the same icon list in "big icons" mode.
The mditest.rb example program demonstrates FOX's Multiple Document Interface (MDI) capabilities, specifically the FXMDIClient and FXMDIChild classes.

The groupbox.rb example program is a kind of "periodic table of widgets" demonstration, FOX-style. It shows off a lot of the FOX widgets as well as providing a good exercise of FOX's layout managers.

The header.rb example program mainly demonstrates the FXHeader widget and the FXSplitter layout manager.

The image.rb example demonstrates how to draw directly into an FXImage object and then "draw" that image into a canvas.

The splitter.rb example demonstrates the FXSplitter layout manager. It also provides an example of the FXTreeList widget (on the left side of the split) and the FXMatrix layout manager (in the middle pane).

The foursplit.rb example program demonstrates the FX4Splitter layout manager. This four-way split is especially useful for CAD-type programs where it's necessary to show multiple views of the model simultaneously.

The shutter.rb example provides a simple demonstration of the FXShutter widget, with the skeleton of a PIM-type application. The very nice icons used for this program are courtesy of Gort's Icons.

The tabbook.rb example exists mainly to demonstrate the FXTabBook widget, but shows off a few other features in the process.

The table.rb example features the FXTable widget, sometimes known as a "grid" or "spreadsheet" widget in other toolkits.

The gltest.rb example program demonstrates how to create a basic OpenGL canvas (i.e. an instance of the FXGLCanvas widget) and draw into it. It also demonstrates how to use timers and chores. This example requires the Ruby/OpenGL extension, available from the Ruby Application Archive.

The glviewer.rb example program demonstrates how to use the FXGLViewer widget and draw various kinds of GL objects into it. It can also be used as model for a fairly complicated FXRuby application, since it includes a lot of typical features (like a menu bar, toolbar, status line, etc.).

Like the glviewer.rb example, the imageviewer.rb can be used as a model for a typical full-featured GUI application, with a menu bar, toolbar, and so forth. It also features the FXImageView widget.

The dilbert.rb example fetches the "Daily Dilbert" cartoon and displays it in a window. This was just a fun little exercise for me, but it does provide a more bare-bones example of the FXImageView widget than that provided by the (more complicated) imageviewer.rb example.
This example program requires the html-parser extension, available from the Ruby Application Archive.

The raabrowser.rb example program shows a treelist view of the current Ruby Application Archive (RAA) contents, and product-specific information for the currently selected product in the panel on the right. This is a good demonstration of the following features:
the FXSplitter layout manager, used to split the left side (containing the tree list) from the right side (containing the information panel). If the panel on the left is too narrow to see all of its contents (especially when you've expanded the tree) try resizing the split.
the FXTreeList widget, used to display the RAA contents.
data targets (i.e. instances of class FXDataTarget), which are used for the contents of the fields in the information panel.
This example program requires the SOAP4R extension.

The babelfish.rb example program, like the raabrowser.rb example, depends on the SOAP4R extension. Other than that it doesn't bring anything new to the table.

The browser.rb example program is mainly a "me too" for the class browser distributed with Ruby/GTK. It's hard for me to get excited about it, but here it is.

One of the biggest flaws with earlier releases of FXRuby was its strict reproduction of FOX's process for mapping GUI events (messages) to instance methods (handlers). That process involved four distinct steps:
Initializing a message identifier, an integer that helps to disambiguate the sender of the message and/or its purpose.
Mapping a specific message type and identifier to an instance method for the message target object.
Implementing the actual handler method in the message target.
Registering the message target and message identifier with the widget that's going to send the messages.
So, for example, let's say you wanted to create a button widget that, when pressed, prints the string "Ouch!" to the terminal. In the old scheme of things, you'd need to identify some object to act as the target for any messages generated by this button. To keep things simple, let's say that the application's main window (mainWindow) is designated as the target for the button. We'll need to generate a unique identifier associated with the button:
class MyMainWindow < FXMainWindow include Responder ID_BUTTON = FXMainWindow::ID_LAST ... other stuff ... end
Next, you'd want to specify the mapping for a specific message type to the target's instance method that handles that message:
FXMAPFUNC(SEL_COMMAND, MyMainWindow::ID_BUTTON, 'onCmdButton')
Finally, you'd need to implement the instance method (onCmdButton) named in the call to FXMAPFUNC:
def onCmdButton(sender, sel, ptr) puts "Ouch!" end
The last step is to tell the button who it's message target is, and which message identifier to use when sending messages to that target:
aButton = FXButton.new(parent, "Push Me", nil, mainWindow, ID_BUTTON)
This was an extremely tedious process, especially for programmers who are used to Ruby/Tk's or Ruby/GTK's approach for connecting signals (events) to blocks that handle the signal. After some discussions at RubyConf 2001 and subsequent discussions on the Ruby newsgroup, a new model was proposed and hashed out on the RubyGarden Wiki. This new model was introduced with the FXRuby-0.99.179 release.
FXRuby implements a new, simplified approach to this built on top of the old model. It more or less mimics the syntax used in Ruby/GTK; you can attach a message handler block to a widget using a new connect instance method, e.g.
aButton = FXButton.new(parent, "Push Me")
aButton.connect(SEL_COMMAND) { |sender, sel, ptr|
puts "Ouch!"
}Alternate forms of the FXObject#connect method can take either a Method or Proc instance as a second argument (i.e. instead of attaching a block), e.g.
def push(sender, sel, ptr) puts "Ouch!" end aButton = FXButton.new(parent, "Push Me") aButton.connect(SEL_COMMAND, method(:push))
It works by creating a special target object (behind the scenes) that stands-in as the message target for your widget and passes off incoming messages to the appropriate block. The single argument to connect is the FOX message type you're handling (e.g. SEL_COMMAND, SEL_CHANGED, etc.) The three arguments to the block are the same as those for regular FOX message handler methods, namely, the sender object, the message type and identifier and the message data. And of course, for simple handlers like this one, you can just leave the arguments off altogether:
aButton = FXButton.new(parent, "Push Me")
aButton.connect(SEL_COMMAND) { puts "Ouch!" }Timers are scheduled by calling FXApp#addTimeout. There are three different forms of addTimeout, but the first argument to each is the timeout interval in milliseconds. The most primitive version of this method takes two additional arguments to specify the target object and message identifier for the object that will handle the timeout event:
aTimer = getApp().addTimeout(1000, timeoutHandlerObj, ID_TIMER)
The second form takes either a Method or Proc instance as its second argument, e.g.
aTimer = getApp().addTimeout(1000, method(:timeoutHandlerMethod))
The last form uses a code block as the handler for the timeout event:
aTimer = getApp().addTimeout(1000) { |sender, sel, ptr|
# handle this timeout event
}Chores are scheduled by calling FXApp#addChore. There are three different forms of addChore; the most primitive version requires two arguments to specify the target object and message identifier for the object that will handle the chore event:
aChore = getApp().addChore(choreHandlerObj, ID_CHORE)
The second form takes either a Method or Proc instance as its single argument, e.g.
aChore = getApp().addChore(method(:choreHandlerMethod))
The last form uses a code block as the handler for the chore:
aChore = getApp().addChore { |sender, sel, ptr|
# handle this chore
}Operating system signal handlers are designated by calling FXApp#addSignal. There are three different forms of addSignal, but the first argument to each is the signal name (e.g. "SIGINT") or number. Each version also has two optional arguments (which should come at the end of the list) to specify immediate and flags. The most primitive version of this method takes two additional arguments to specify the target object and message identifier for the object that will handle this operating system signal:
aSignal = getApp().addSignal("SIGINT", signalHandlerObj, ID_SIGINT)The second form takes either a Method or Proc instance as its second argument, e.g.
aSignal = getApp().addSignal("SIGINT", method(:signalHandlerMethod))The last form uses a code block as the handler for the signal:
aSignal = getApp().addSignal("SIGINT") { |sender, sel, ptr|
# handle this signal
}Input event handlers are designated by calling FXApp#addInput. There are three different forms of addInput, but the first two arguments to each are the file object (including sockets) and the mode flag (some combination of INPUT_READ, INPUT_WRITE and INPUT_EXCEPT). The most primitive version of this method takes two additional arguments to specify the target object and message identifier for the object that will handle this input event:
getApp().addInput(aFile, INPUT_READ, inputHandlerObj, ID_INPUT)
The second form takes either a Method or Proc instance as its third argument, e.g.
getApp().addInput(aSocket, INPUT_READ|INPUT_EXCEPT, method(:inputHandlerMethod))
The last form uses a code block as the handler for the input event:
getApp().addInput(aFile, INPUT_WRITE|INPUT_EXCEPT) { |sender, sel, ptr|
# handle this input
}This API is a little different from the other cases. For example, timeout events always send the same message type (SEL_TIMEOUT) to their message target, so you just have a single handler method (or block) to handle the timeout. In contrast, input sources (e.g. pipes or sockets) can generate three different FOX messages, SEL_IO_READ, SEL_IO_WRITE and SEL_IO_EXCEPTION, depending on what happens, so your handler method (block) needs to check the message type, e.g.
getApp().addInput(socket, INPUT_READ|INPUT_WRITE) { |sender, sel, ptr|
case SELTYPE(sel)
when SEL_IO_READ
# handle read event
when SEL_IO_WRITE
# handle write event
end
}I haven't yet paid much attention to some of the more esoteric issues such as multithreaded applications, or interactions with other popular Ruby extensions. FXRuby seems to cooperate well with Ruby's thread scheduler on all platforms, although threading support for the mswin32 build of Ruby was broken for Ruby versions 1.6.7 and earlier. For best results, use Ruby versions 1.6.8 or later.
More examples are always good. Instead of (or in addition to) merely cloning the C++ examples from the standard FOX distribution, it would be nice to have original example programs that demonstrate Ruby's special strengths.
Documentation is of course a big weakness at this point. I am slowly building up a set of "pseudo-sources" with RDoc-style comments that can be used to generate API documentation but this is far from complete (see the rdoc-sources directory in the standard source distribution).
Many people have expressed interest in a framework similar to Tk's Canvas widget that allows you to draw and drag 2-D shapes around, etc. Should be able to implement this kind of thing directly in Ruby code (i.e. no need to write it in C++ first and then "wrap" it).
There is no native Mac OS X port of FOX, but many people are successfully building and running FOX and FXRuby applications on Mac OS X using Apple's X11 server implementation. For more information, please see the Mac OS X page at the FOX Community Wiki site.
Need to investigate how well FXRuby-based applications work with exerb, and perhaps add a section to the documentation about how to do this.
As of this writing, FOX doesn't provide any support for internationalization (I18N) or Unicode text and so FXRuby doesn't either. It is possible that FOX 1.2 will include some I18N support (see the FAQ at the FOX Community Wiki site), and if so, appropriate changes will be made to FXRuby at that time.
FOX's list-like widgets (such as FXList) behave differently from Ruby container objects (such as Array) in the sense that when a list widget is destroyed, it typically destroys all of its contained items at the same time. One consequence of this behavior is that you can end up with Ruby objects that refer to dead C++ objects (a variation of the dangling pointer problem familiar to C/C++ programmers). I would like to see if this behavior can be modified for FXRuby so that it's impossible to end up with these "dangling references".
There are no books entirely dedicated to programming with FXRuby, but the following Ruby books contain sections on FXRuby:
The Ruby Way, by Hal Fulton.
Ruby Developer's Guide, by Michael Neumann, Robert Feldt and Lyle Johnson.
The current FXRuby API Reference Documentation is a work in progress, but serves as a pretty good guide to the available methods and attributes for the different classes. The regular FOX API Reference Documentation can be used as a fallback source of information, although it is intended for users of the C++ library.
The FOX toolkit home page is still one of the best places to find information about GUI programming with FOX. This is the site maintained by Jeroen van der Zijp, the creator and primary developer of FOX, and serves as the official download site for the FOX library. In addition to a number of articles about programming with FOX, you'll find links to projects using FOX. A newer web site, the FOX Community Wiki, is maintained by Sander Jansen. Like most Wiki sites, the information here is a little more scattered but tends to be more up-to-date than some information on the main FOX site. This site includes a number of tutorial articles, as well as "cookbook" and "how-to" pages on many topics.
One or more of the following mailing lists may be of interest:
The fxruby-announce@lists.sourceforge.net mailing list is a very low-volume list for FXRuby-related announcements. To subscribe to this list, follow the instructions at http://lists.sourceforge.net/lists/listinfo/fxruby-announce
The fxruby-users@lists.sourceforge.net mailing list is a higher-volume list for FXRuby-related discussions. To subscribe to this list, follow the instructions at http://lists.sourceforge.net/lists/listinfo/fxruby-users
The foxgui-announce@lists.sourceforge.net mailing list is a very low-volume list for FOX-related announcements. To subscribe to this list, follow the instructions at http://lists.sourceforge.net/lists/listinfo/foxgui-announce
The foxgui-users@lists.sourceforge.net mailing list is a higher-volume list for FOX-related discussions. To subscribe to this list, follow the instructions at http://lists.sourceforge.net/lists/listinfo/foxgui-users
The ruby-talk@ruby-lang.org mailing list (or its mirror, the comp.lang.ruby newsgroup) is a high-volume list for Ruby-related discussions. To subscribe to this list, follow the instructions at http://www.ruby-lang.org/en/ml.html
This is the first "alpha" release of FXRuby 1.2. This release should be compatible with any FOX library version 1.2; it is not compatible with any previous FOX library versions. As this is an alpha release, users should expect a certain amount of instability, bugs, etc.
The intent of this first alpha release is twofold. The primary intent is allow application developers who have current projects based on FXRuby 1.0 to begin the process of updating their applications for compatibility with FXRuby 1.2. For this release, all of the classes that existed in FXRuby 1.0 have been updated for compatibility with FOX 1.2, and so developers should at least be able to begin to "port" their applications forward now. Note that there have been a number of changes for FOX 1.2 and FXRuby 1.2, both in terms of API changes and less obvious "behavioral" changes. For a detailed summary of these changes, please see "What's New in FOX 1.2" (also available as a PDF). Note that few, if any, of the new classes introduced in FOX 1.2 are available in this first alpha release of FXRuby 1.2. Support for those new classes should come along quickly in subsequent alpha releases of FXRuby 1.2.
The secondary intent of this first alpha release is to introduce the new RubyGems-based packaging of FXRuby and to begin to work out the inevitable kinks in that system.
The binary gem for Windows was built with FOX version 1.2.4 and FXScintilla version 1.57.
There was no way to directly obtain the name of the current "redo" command for an FXUndoList (see SourceForge Bug 928312). For consistency with API changes being made in FOX 1.2, I've added two new methods to the FXUndoList class. The FXUndoList#undoName method returns the name of the next available undo command, or nil if there is none. Similarly, the FXUndoList#redoName method returns the name of the next available redo command (or nil). Thanks to Daniel Sperl for reporting this problem.
TheFXUndoList#cutmethod had a bug which could cause programs to crash if this method was called before the marker was set (see SourceForge Bug 928303). This has been fixed. Thanks to Daniel Sperl for reporting this problem.
The indexed accessor for FXHMat was returning the wrong result for a index of zero (see SourceForge Bug 920672). This has been fixed. That accessor was also not checking array bounds, so that (for example) an index greater than three would lead to erroneous result. Now, out of bounds array indices cause an IndexError to be raised. Thanks to ggarramuno for reporting these problems.
The default constructor for FXHMat was not initializing the matrix elements (see SourceForge Bug 920671); now, they are all initialized to zeroes. Thanks to ggarramuno for reporting this problem.
The FXTreeList widget code was printing spurious debugging messages (see SourceForge Bug 904788). Thanks to Allen Mitchell for reporting this problem.
The dilbert.rb example program was failing for the patched version of the html-parser library (see SourceForge Bug 881127). The example program has been modified so that it now works with either version of the html-parser library. Thanks to Glenn Lewis for reporting this problem.
The raabrowser.rb example program had an unnecessary dependency on the Devel::Logger package, and that dependency has now been removed (see SourceForge Bug 881139). Thanks to Glenn Lewis for reporting this.
The RAA.rb file needed for the raabrowser.rb example was missing from the Windows installer (see SourceForge Bug 881131). This has been corrected. Thanks to Glenn Lewis for reporting this problem.
The Windows installers for this release were built with FOX version 1.0.51 and FXScintilla version 1.57.
Due to past changes to the FXDirList API, the textedit example program was calling some now non-existent instance methods for that class (see SourceForge Bug 838048). This has been fixed. Thanks to Simon Strandgaard for reporting this problem.
Removed the getClassName() method for FXObject. This method returned the name of the underlying C++ class and is not needed for the Ruby interface (see SourceForge Bug 873860). Thanks to Rich for reporting this.
Some of the wrapped instance methods would not return Ruby instances of the most-derived type. For example, calling getFirst() on an FXFileSelector instance should return an FXHorizontalFrame instance, since that is the true type of the first child window of a file selector, but it was instead returning an FXWindow instance (see SourceForge Bug 873862). This has been fixed. Thanks to Rich for reporting this problem.
None of the documented accessor methods for the FX4Splitter class (like hSplit or vSplit) were actually defined (see SourceForge Bug 875459). This has been fixed. Thanks to Joel VanderWerf for reporting this problem.
The FXMemoryStream class was seriously broken. It has now been fixed, and has unit tests.
Modified the build scripts to search /sw/include/fox (in addition to /usr/include/fox and /usr/local/include/fox) for the FOX header files. This should be useful for Mac OS X installations using fink.
Added a new tutorial that describes how to add clipboard support to your FXRuby applications.
The Windows installers for this release were built with FOX version 1.0.49 and FXScintilla version 1.57.
The image.rb example program got broken somewhere along the way and wasn't working properly under Windows (see SourceForge Bug 785921). This has been fixed. Thanks to Simon Ditner for reporting this problem.
Due to recent changes in the SOAP interface to the Ruby Application Archive (RAA), the raabrowser.rb example program was no longer working properly. NaHi provided some patches for raabrowser.rb (as well as some of the supporting files) to bring it up to date. Many thanks to NaHi for those patches!
The no-argument version of the overloaded FXScrollArea#position method was not working properly for instances of subclasses of FXScrollArea, such as FXScrollWindow (see SourceForge Bug 811852). This has been fixed. Thanks to Meinrad Recheis for reporting this problem.
Under some circumstances, FXRuby-based applications could crash the Ruby interpreter during shutdown (see SourceForge Bug 815142). A workaround has been implemented, and a more correct solution suggested by Kevin Burge is under consideration. Thanks to both Joel VanderWerf and Kevin Burge for reporting this problem.
The makeItemVisible instance methods for the FXIconList and FXList classes now accept either the integer index of the list item, or a reference to the list item, as their single argument. Thanks to Meinrad Recheis for this suggestion.
The initialize method for the FXOption included a superfluous (but harmless) MENUBUTTON_DOWN flag in its list of default flags (see SourceForge Bug 822380). This has been removed. Thanks to Meinrad Recheis for pointing out this problem.
The currentItem attribute for the FXList class was marked as both readable and writeable in the API documentation, but no currentItem=() instance method was actually provided. This has been fixed. Thanks to Hajime Simokawa for reporting this omission.
Added the fxencodeStringData() and fxdecodeStringData() module methods (for the Fox module) to account for platform dependencies in how clipboard string data is represented. Thanks to Dalibor Sramek for helping to identify and diagnose this problem.
The API documentation for the various FXMenuButton flags was unclear (see SourceForge Bug 822377). This has been improved. Thanks to Meinrad Recheis for this suggestion.
The API documentation for the initialize method of the FXProgressBar class contained a small typo (see SourceForge Bug 820309). This has been corrected. Thanks to Meinrad Recheis for reporting this error.
The Windows installers for this release were built with FOX version 1.0.46 and FXScintilla version 1.53.
Since FXEvent objects are usually only created by the application and sent along as message data with FOX messages, this class had no new singleton method (see SourceForge Bug 797893). This restriction made it impossible to construct dummy instances of FXEvent for unit-testing purposes, and so I added a new class method. Thanks to Steve Conover, Jr. for this suggestion.
Thanks to Hugh Sasse, who submitted a patch for the babelfish.rb example program to support the additional translation modes of English-Japanese and English-Korean.
Modified the constructors for FXArc and FXSegment so that they now accept optional arguments to specify their attributes at construction time, as opposed to first constructing an instance and then initializing its values (see SourceForge Bug 797331). Thanks to Fredrik Jagenheim for this suggestion.
There were several discrepancies between the API documentation for the FXDirList class and its actual interface (see SourceForge Bug 797369). To make this more consistent, the #showFiles, #showFiles=, #showHiddenFiles and #showHiddenFiles= instance methods for FXDirList have been removed, and the missing #filesShown= and #hiddenFilesShown= instance methods have been added. Thanks to "unomi" from #ruby-lang for pointing out this problem.
The FXColorDialog#rgba and FXColorSelector#rgba attribute reader methods were misspelled as "rbga" (see SourceForge Bug 796960). This has been corrected. Thanks to Recheis Meinrad for reporting this problem.
Someone pointed out that the type information for FXShutterItem's text attribute was incorrectly listed as Integer instead of String in the API documentation. I can't remember who pointed this out, but thanks, and it's fixed now!
The current? alias for the FXGLCanvas#isCurrent method mentioned in the API documentation was not actually present. Thanks to Recheis Meinrad for reporting this problem.
The unit test case for FXId#created? has been broken for awhile, and so I finally got around to fixing it. Thanks to Simon Strandgaard for pointing out this problem.
The Windows installers for this release were built with FOX version 1.0.46 and FXScintilla version 1.53.
An error introduced in version 1.0.23 caused a warning message ("useless use of a constant in void context") to be issued when Ruby was run with warnings enabled (see SourceForge Bug 754235). This bug has been fixed. Thanks to Hugh Sasse for reporting the problem.
The dilbert.rb example program, which fetches the daily "Dilbert" cartoon from the Dilbert web site, got broken sometime recently when the image URLs changed (see SourceForge Bug 755040). The program has now been fixed. Thanks to Joel VanderWerf for reporting this problem.
The FXGLGroup class mixed-in Ruby's Enumerable module but didn't provide an each method for the Enumberable module to use (see SourceForge Bug 756609). This problem was fixed by aliasing the existing each_child method to each. Thanks to Joel VanderWerf for reporting this problem.
Corrected the return value of FXVec's normalize! method, which should have been returning a reference to self but was instead returning a reference to a new FXVec instance.
Added a normalize! instance method to the FXHVec class, for consistency with FXVec.
Due to an error in the GC-related code for FXTable and FXTableItem, table items were not being properly marked as in-use and as a result could get garbage-collected prematurely (see SourceForge Bug 772337). This bug has been fixed; thanks to Emmanuel Touzery for reporting it.
The Fox.fxloadRGB() module method was implemented incorrectly; it was calling the C++ FOX library's fxloadGIF() function instead. This bug has been fixed.
Finally added support for overriding some of FXIconItem's virtual methods, such as draw, hitItem, drawBigIcon, drawMiniIcon and drawDetails (see SourceForge Bug 675769). Thanks to Jamey Cribbs for this suggestion.
A change made in a previous version of FXRuby introduced a bug for some applications (such as DbTalk) when running on Windows (see SourceForge Bug 756672). This bug has been fixed. Thanks to Dalibor Sramek for reporting this problem.
Added the drawCircle and fillCircle instance methods to the FXDC class.
Renamed FXRectangle's move() instance method to move!, to indicate that it is a mutator method.
The Windows installers for this release were built with FOX version 1.0.42 and FXScintilla version 1.51.
The initialize methods for the FXGLLine and FXGLPoint classes did not properly call the base class version of initialize, and as a result these objects were basically unusable (see SourceForge Bug 750940). These bugs have been fixed. Thanks to Peter J. Puchyr for reporting the problem.
The each_child method for FXGLGroup was implemented incorrectly (see SourceForge Bug 751345). This bug has been fixed; thanks to Joel VanderWerf for reporting the problem.
The division operator for the FXVec class now raises ZeroDivisionError if the dividend is identically zero.
The normalize, lo and hi methods for the FXVec class are now instance methods instead of class singleton methods. Also added a normalize! instance method to the FXVec class for in-place normalization of the vector.
Corrected the implementation of the fxloadTGA and fxloadTIF module methods. For fxloadTIF, the image pixel data buffer returned by this method was being truncated and was as a result unusable. For fxloadTGA, the data was only being truncated for the case where the image contained all four channels (red, green, blue and alpha).
Added FXVisual#visualType() as an alias for getType().
Renamed several instance methods for the FXRectangle class: the contains method is now spelled contains? (with the trailing question mark); grow, move and shrink are now grow!, move! and shrink!, to indicate that they are mutator methods; and the "+" and "*" operator methods have been renamed to union and intersect, respectively, to clearly indicate what they do.
Due to a buggy implementation of Ruby's NUM2UINT macro, the conversion from Ruby integers to C++ unsigned integers could fail on 64-bit platforms. This made it impossible to set certain colors in FXRuby programs (see SourceForge Bug 746606). The problem has been reported to the Ruby core developers, and a workaround was implemented in FXRuby. Thanks to John Kaurin for reporting this problem.
A couple of bugs in the TextEdit example program caused the undo feature for that program to work incorrectly (see SourceForge Bug 744588). These bugs have been fixed. Thanks to Bill Ramsay for reporting this problem.
The beginPrint and beginPage instance methods for FXDCPrint were returning nil (instead of true or false) when no block was passed. This bug has been corrected.
The Windows installers for this release were built with FOX version 1.0.40 and FXScintilla version 1.51.
The contents of this chapter (the Change History) have been confusing for some readers, for a number of reasons. One problem is my choice of wording for the section headings ("Changes Since Version XXX" as opposed to "Changes For Version YYY"), which isn't how most software projects tend to document new releases. Another problem (for users of the Windows installers) is that it was never stated which versions of FOX and FXScintilla were linked into that version of FXRuby. This will now always be listed as one of the bullet points in the change list for a new release. Thanks to John Kaurin and others for their suggestions on how to improve the format.
The install.rb script now tries to automatically detect an FXScintilla installation and enable FXScintilla support for FXRuby. This should work if you installed FXScintilla under either /usr or /usr/include.
The FXWindow#raise method was hiding Ruby's regular raise method, and as a result you couldn't raise exceptions within an instance method of any class derived from FXWindow (see SourceForge Bug 728993). The FXWindow instance method has been renamed to raiseWindow, which of course will break programs that were calling raise with the intention of raising a window to the top of the stacking order. But, hey, it's the right thing to do. Thanks to Mark White for reporting this problem.
Due to an error in how the FXFileStream#close and FXMemoryStream#close methods were wrapped, calling close() on an FXFileStream or FXMemoryStream instance actually resulted in a call to the base class method (FXStream#close). As a result, neither file streams nor memory streams were being closed properly (see SourceForge Bug 733933). This has been fixed; thanks to Thomas Stammeier for reporting this problem.
The transactional form of FXFileStream.open (i.e. the class singleton method that takes a block) had no way of reporting errors to the caller (see SourceForge Bug 732849). An error could occur, for example, if you tried to open a read-only file for writing:
FXFileStream.open("ReadOnlyFile.doc", FXStreamSave) { |fileStream|
...
}Now, FXFileStream.open will raise an FXStreamNoWriteError exception if it fails for opening a file FXStreamSave mode, or an FXStreamNoReadError exception if it fails for opening a file in FXStreamLoad mode. So to be exception-safe, you'll want to write code that accounts for these possibilities, e.g.
begin
FXFileStream.open("ReadOnlyFile.doc", FXStreamSave) { |fileStream|
...
}
rescue FXStreamNoWriteError
FXMessageBox.error(getApp().getMainWindow(), MBOX_OK, "Error", "Couldn't open file for writing")
endThanks to Thomas Stammeier, for reporting a different bug that made me notice this bug.
A previously corrected bug that caused some objects to be prematurely garbage-collected somehow made its way back into the code (see SourceForge Bug 734738). This bug has been fixed, again, and this time I left myself a nasty comment in the source code and added a regression test to catch it if it tries to resurface in the future.
Fixed a few problems in the FXRuby example programs related to loading icons from files. One problem, reported by Peter J. Puchyr, was that the loadIcon method in the glviewer.rb example would attempt to display a message box window before the application had been created if some error occurred while reading the icon file (see SourceForge Bug 740122). Another, more pervasive problem, was that the files weren't being closed properly after we were done reading their contents. Both of these problems have been fixed.
Finished off the drag-and-drop tutorial chapter.
The Windows installers for this release were built with FOX version 1.0.40 and FXScintilla version 1.51.
Invalid message data was being sent with various messages from various widgets to their message targets; as a result, some programs could crash if trying to handle those messages (see SourceForge Bugs 711008, 712941 and 714082). All reported bugs have been fixed; thanks to Allen Mitchell, Gilles Filippini and James Adam for reporting these problems.
If you replaced the application's normal font, default visual, or one of its default cursors with a custom resource, the application could crash on exit, depending on the order in which those objects are garbage-collected (see SourceForge Bug 723450). This bug has been fixed; thanks to Michael Neumann for reporting it.
Color values can now be specified using a string (or symbol) containing the color name. So, for example, to set the arrow color for an FXArrowButton widget to yellow, you could use any of the following three forms:
arrowButton.arrowColor = FXRGB(255, 255, 0) arrowButton.arrowColor = "yellow" arrowButton.arrowColor = :yellow
Note that this feature uses FOX's built in list of color names, which is for the most part identical to the standard X11 color names. The color name matching is case-insensitive, so "yellow", "Yellow" and "YELLOW" will all yield the same result.
Started a new tutorial section on adding drag-and-drop support to FXRuby applications.
I've made a lot of progress on the API reference documentation, with perhaps 75% of the FXRuby classes now documented. If you have questions about the documentation, or suggestions about how it can be improved, please feel free to drop me an e-mail.
Invalid message data was being sent with the SEL_CLOSE and SEL_CLOSEALL messages from an FXMDIChild window to its message target; as a result, some programs could crash if trying to handle those messages (see SourceForge Bug #691672). This bug has been fixed; thanks to Allen Mitchell for reporting it.
The bounce.rb example program was crashing (on Windows only) because the SEL_CONFIGURE message handler gets invoked before we've had a chance to call create() on the back-buffer image used for the animation (see SourceForge Bug #695091). This has been fixed; thanks to Soso for reporting it.
Invalid message data was being sent with SEL_CHORE messages; as a result, some programs could crash if trying to handle those messages (see SourceForge Bug #702483). This bug has been fixed; thanks to Joel VanderWerf for reporting it.
For some cases, objects were getting garbage-collected prematurely (see SourceForge Bug #703721). This bug has been fixed; thanks to Michael Mueller for reporting it.
The message data for SEL_COMMAND messages from FXMenuCommand objects was not being properly converted to a Ruby instance. This bug has been fixed.
Corrected the return type for FXHeader#getArrowDir. This method was returning either true or false, but should have been returning an integer (one of TRUE, FALSE or MAYBE) to indicate the arrow direction for a given header item. Also added the arrowUp?, arrowDown? and arrowMaybe? instance methods, which return true or false depending on the arrow direction for a given header item.
Incorporated Gilles Filippini's patches for the iface.rb script, which generates the scintilla.rb library file from the Scintilla.iface interface description file. Also used this to update the scintilla.rb library file, based on the Scintilla.iface file from the FXScintilla 1.51 distribution.
Added shared? as an alias for FXGLContext#isShared.
Added itemCurrent? as an alias for FXListBox#isItemCurrent.
Added size as an alias for FXMemoryBuffer#getSize.
Added offeredDNDType? as an alias for FXWindow#offeredDNDType.
When invoking a Ruby message handler for a SEL_UPDATE message, we should have been passing Ruby's nil as the message data for the handler, but this wasn't happening. This has been fixed.
Device contexts should be treated as application-sensitive objects (just like other resources) so that they're destroyed before the application object is during the Ruby interpreter's finalization on exit (see SourceForge Bug #682961). Thanks to Mikkel Jorgensen for reporting this problem.
The bounds checking for a number of FXTable instance methods was incorrect (see SourceForge Bug #689321 and SourceForge Bug #689325). All known bugs have been fixed, and several new tests were added. Thanks to Dalibor Sramek for reporting this.
The FXTableItem#getStipple and FXTableItem#setStipple instance methods were broken (see SourceForge Bug #689447). Thanks to Dalibor Sramek for reporting this.
Corrected a typo in the INSTALL file; the URL for the "Things That Can Go Wrong" section of the FXRuby build instructions was incorrect. Thanks to Glenn Lewis and others for reporting this.
Added locked? as an alias for FXGLViewer#getViewLock().
Added doesTurbo? as an alias for FXGLViewer#doesTurbo.
Added currentItem= as an alias for FXIconList#setCurrentItem.
Replaced the swapBytes accessor methods for the FXStream class with bytesSwapped= and bytesSwapped?.
Added the new iconlist.rb example program.
The font object associated with FXGroupBox objects was not being properly marked as in use during garbage collection. The result was that the font object could get reclaimed prematurely, causing your program to crash (see SourceForge Bug #676498). This bug has been fixed. Thanks to Jason Persampieri for reporting this problem.
The index bounds checking for some of the FXIconList instance methods was using the wrong upper limit and could thus raise a bogus IndexError when the index was in fact legal. This was broken for all of the FXIconList methods that expect indices into the icon list's header control (e.g. FXIconList#removeHeader and FXIconList#getHeaderText). This has been fixed.
The previous release introduced an unintended dependency for all of FXRuby on the Ruby/OpenGL extension (see SourceForge Bug #676089). The code has been modified so that only the FXGLGroup class has this dependency; if your code doesn't use this class you shouldn't need to have Ruby/OpenGL installed.
The Windows installer was missing the new version.rb library file. This has been fixed.
The Responder2 module, which provides the connect instance method for associating messages with handler code blocks, was mistakenly being mixed into the FXObject class. This module should really only be mixed into those classes that have a message target (namely, FXDataTarget, FXRecentFiles and FXWindow). This has been fixed.
When a widget sends a SEL_MOUSEWHEEL message to its message target, it should send an FXEvent object as its message data. This was not being done (see SourceForge Bug #681189). Thanks to Aaron Schrab for reporting this and for providing a patch to fix the problem.
Added the FXSettings#each_section method, for iterating over the sections in an FXSettings object.
Added the FXDict#each_key method, for iterating over the keys in an FXDict object.
Reorganized the SWIG modules in an attempt to reduce the file sizes for FXRuby's C++ source code. Hopefully, this will make it easier to build FXRuby from the source code on machines with limited memory or other problems (see, for example, SourceForge Bug #676490).
Replaced the SWIG-wrapped version of the FXGLGroup C++ class with a Ruby implementation. The previous implementation required some hairy bookkeeping in order to keep track of which group members were "owned" by an FXGLGroup object and which ones were not, which in turn determined whether they could be garbage-collected or not. The new implementation is, as a result, much simpler.
Fixed a bug for FXText#extractStyle; if the extracted style bytes contained zeroes (indicating the default style) it would prematurely truncate the returned string. This has been fixed.
Modified the implementations of FXGLViewer#readPixels, FXText#extractText, FXText#getText, and FXText#extractStyle so that they use FOX's memory allocation routines instead of Ruby's ALLOCA_N macro. This change was prompted by numerous reports of the code not compiling on certain platforms (e.g. Solaris) because of the use of ALLOCA_N.
Removed the -fno-strict-prototype flag from CFLAGS for the Cygwin/MinGW platform builds. This compiler flag was being used as a workaround for some broken function prototypes in the Ruby header files. However, since this flag is no longer supported for gcc-3.2, and since those prototypes are fixed in the Ruby 1.6.8 header files, it's not needed anymore.
Updated the test cases for compatibility with Test::Unit version 0.1.6.
Fixed a bug related to the garbage collection of Ruby objects that point to C++ objects that were already destroyed (see SourceForge Bug #660452). Under some circumstances this bug could cause the Ruby interpreter to crash.
Fixed a bug related to garbage collection for FXScintilla widgets (see SourceForge Bug #660541). During garbage collection, the FXScintilla objects were being marked as reachable, but they were not in turn marking the objects that they held references to (e.g. their message targets). As a result, the code could eventually crash.
Added the Fox.fxrubyversion module method that returns a string indicating the installed version of FXRuby (e.g. "1.0.18"). For consistency, also changed the return value of Fox.fxversion so that it returns a string instead of a three-element array. Thanks to Dalibor Sramek for this suggestion (see SourceForge Change Request #663170).
A large number of instance methods (especially for list-like classes) have integer index arguments that could potentially be out of bounds. In previous versions of FXRuby, these errors weren't handled very well (i.e. the program would exit abruptly with an error message); now, the program should raise an IndexError exception when an out-of-bounds index is passed into one of these methods. The affected methods are in the FXComboBox, FXFileList, FXHeader, FXIconList, FXList, FXListBox, FXSwitcher, FXTable and FXWindow classes. A little common sense should tell you which methods are affected.
Fixed a bug for the FXTable#setItem method. The table item passed in to this method should have been modified so that it was marked as "owned" by the table, but that wasn't happening.
Fixed things so that the FXTable#createItem, and the draw, drawButton, drawBorders, drawContent, drawPattern, and drawBackground instance methods for the FXTableItem class, can be overridden in Ruby code.
The patch made for Cygwin builds in the FXRuby 1.0.17 release was not quite right; it has been fixed. Thanks to James Adam for reporting this problem.
Some of the code from responder.rb (one of the FXRuby library files) was generating warnings (see SourceForge Bug #658873). This has been fixed. Thanks to Kurt Hindenburg and John Kaurin for reporting this.
Added hasFocus? as an alias for FXTableItem#hasFocus.
Added the FXDC#stipple method, which returns the stipple bitmap (from FXDC#stippleBitmap) if it's not nil, otherwise the stipple pattern number (from FXDC#stipplePattern).
Added horizontalGridShown? and verticalGridShown? as aliases for FXTable#isHorzGridShown and FXTable#vertGridShown.
Fixed a typo for the method name FXCursor#height, which was misspelled "heigh". Thanks to Roger Sperberg for reporting this.
In some cases, the internal mapping between C++ FXEvent objects and Ruby FXEvent instances was not broken when the Ruby was garbage-collected (see SourceForge Bug #665860). This has been fixed. Thanks to Marco Frailis for reporting this problem.
The message data associated with the FXTreeList#onDoubleClicked is expected to be an FXTreeItem instance, but was instead being passed as an FXEvent instance (see SourceForge Bug #670834). This has been fixed. Thanks to Marco Frailis for reporting this problem.
Added grabbedKeyboard? as an alias for FXWindow#grabbedKeyboard.
The return value for blocks assigned as message handlers wasn't properly getting passed back to the FOX event loop (see SourceForge Bug #675137). This has been fixed. Thanks to Babu Proof for reporting this.
The FXMenuCommand#checked? and FXMenuCommand#radioChecked? instance methods were erroneously returning integer values (zero or one) instead of true or false (see SourceForge Bug #642261). This has been fixed; thanks to Joel VanderWerf for reporting this bug.
The fxhsv_to_rgb and fxrgb_to_hsv module methods were not being wrapped (see SourceForge Bug #645878). This has been fixed; thanks to Thomas Husterer for reporting this bug.
The FXTopWindow#setPadLeft, FXTopWindow#getPadRight and FXTopWindow#setPadRight instance methods, and the corresponding aliases, were missing (see SourceForge Bug #647799). This has been fixed; thanks to John Kaurin for reporting this bug.
The code wasn't compiling properly with gcc under Cygwin because of a name conflict between the OpenFile function declaration (in the <windows.h> header file) and the OpenFile struct declaration (in the rubyio.h header file). See SourceForge Bug #647792 for more of the ugly details. This has been fixed; thanks to Philippe Le Dreau for reporting this bug.
In some circumstances the use of a large number of timeouts would eventually cause the program to crash; this bug has been fixed. This problem was first reported by David Naseby (see SourceForge Bug #614345) and more recently by Steve Tuckner (see SourceForge Bug #648128). Thanks to both David and Steve for reporting this.
A bug introduced in FXRuby 1.0.14 could lead to memory leaks in some long-running applications, because some objects were never being garbage-collected, even after they were no longer in use. This bug has been fixed.
The behavior of the "[]=" method for the FXVec, FXHVec and FXMemoryBuffer classes was inconsistent with that of the Array#[]= method. For consistency, those methods should return the value just set (but they were returning nil). This has been fixed. Thanks to Joel VanderWerf for catching this one.
Added the FXGLShape#position and FXGLShape#position= accessor methods for getting and setting an OpenGL shape's position. Thanks to Joel VanderWerf for this suggestion.
When the active FXMDIChild window for an FXMDIClient changes, the client is supposed to send a SEL_CHANGED message to its message target and the message data is supposed to be a reference to the active FXMDIChild window. This was not working properly (see SourceForge Bug #652099). Thanks to Michael Libby for reporting this bug.
The glviewer.rb didn't work exactly like its C++ equivalent from the regular FOX examples, in that it didn't display a context-sensitive popup menu when you right-clicked in the FXGLViewer window background (see SourceForge Bug #646252). This inconsistency has been fixed. Thanks to Joel VanderWerf for reporting this.
Added the FXRegistry#readBoolEntry and FXRegistry#writeBoolEntry methods for reading and writing boolean registry values, so that you don't have to convert those to integers or strings (see SourceForge Bug #653279). Thanks to John Kaurin for this suggestion.
The FXCheckButton#setCheck and FXRadioButton#setCheck instance methods weren't allowing an input of MAYBE to set the "indeterminate" state (see SourceForge Bug #654653). This has been fixed. Thanks to Joel VanderWerf for pointing out this problem.
Under some circumstances, the an application could crash while the Ruby interpreter is doing its final garbage collection sweep during finalization (see SourceForge Bug #654871). This has been fixed. Thanks to Joel VanderWerf for reporting this problem and providing test cases to help to diagnose it.
Added FXWindow#each_child for iterating over the child windows of a (parent) container window, e.g.
parentFrame.each_child do |aChild| aChild.foo end
Thanks to Joel VanderWerf for this suggestion.
FXCheckButton and FXRadioButton widgets can exist in one of three states: checked, unchecked or indeterminate. FOX uses the constants TRUE, FALSE and MAYBE to enumerate these three states. For an example of when an FXCheckButton might be in the indeterminate state, consider an application that displays a list of club members. A check button near the list indicates whether the selected member(s) have paid their membership dues. If all of the selected members are paid up, the check button is displayed with a solid check mark (indicating the TRUE state). In contrast, if none of the selected members are paid up, the check button should be empty and display no check mark (indicating the FALSE state). But what about the case when some of the selected members are paid up and some are not? In this case, the check button's state is MAYBE, which is displayed by drawing a "greyed-out" check mark in the box.
In previous releases of FXRuby, the FXCheckButton#getCheck and FXRadioButton#getCheck instance methods returned true if the button state was either TRUE or MAYBE; otherwise, those methods returned false. This was a problem since it left no way to (programatically) distinguish between the TRUE and MAYBE states.
For this reason, I'm adding the new getCheckState instance method to FXCheckButton and FXRadioButton and deprecating the getCheck method. The getCheckState method returns an integer value that is equal to one of the three Fox module constants TRUE, FALSE or MAYBE. I'm also adding three predicate methods, checked?, unchecked? and maybe? which return true if the corresponding state is in effect for that button. The getCheck method will continue to work exactly as it did before, but you'll get a warning message if you're running Ruby in verbose mode (i.e. with the '-w' command line argument).
Fixed all of the FXRuby library files to ensure that all of them require the "fox" feature before re-opening that module, but that none of them include the Fox module (because that would pollute the global namespace). Also make sure that the fully-qualified names for Fox module constants and methods are used when necessary. Thanks to Rich Kilmer for helping me sort out these issues.
FXDCWindow#new now takes an optional code block. If a code block is provided, the new device context will be passed into the block as an argument and FXDCWindow#end will be called automatically when the block terminates. As an example, this code snippet (from the scribble.rb example program):
dc = FXDCWindow.new(@canvas) dc.foreground = @canvas.backColor dc.fillRectangle(0, 0, @canvas.width, @canvas.height) @dirty = false dc.end
can now be written as:
FXDCWindow.new(@canvas) do |dc| dc.foreground = @canvas.backColor dc.fillRectangle(0, 0, @canvas.width, @canvas.height) @dirty = false end
Along the same lines, FXDCPrint#beginPrint and FXDCPrint#beginPage can now take an optional code block. If a code block is provided, the device context will passed into the block as an argument and the appropriate "end" method (either FXDCPrint#endPrint or FXDCPrint#endPage) will be called automatically when the block terminates.
Assorted changes to some example programs. Disabled thread checking in the gltest.rb example program so that when you tell it to spin the scene using FOX chores, it will be able to do so as fast as possible. Updated the table.rb example for consistency with FOX's table.cpp example (per Joel VanderWerf's suggestion).
Fixed a GC-related bug that could cause programs to crash after a call to FXTable#setTableSize (see SourceForge Bug #635633). Thanks to Joel VanderWerf for reporting this bug.
Added a new instance method FXId#created? which simply checks to see if the server-side resource associated with an FXId has been created (see SourceForge Bug #633398). Thanks to Derek Ney for this suggestion.
Made a slight change for the initialization blocks added in version 1.0.14 (see SourceForge Feature Request #623878). The optional initialization blocks are no longer evaluated in instance scope. Instead, a reference to the new instance is passed into the block as its single argument. So code that would have been written as:
FXButton.new(subsplitter, "&Of course\tThis splitter does NOT track") do self.frameStyle = FRAME_SUNKEN|FRAME_THICK self.backColor = FXRGB(0, 128, 0) self.textColor = FXRGB(255, 255, 255) end
would now be written as:
FXButton.new(subsplitter, "&Of course\tThis splitter does NOT track") do |theButton| theButton.frameStyle = FRAME_SUNKEN|FRAME_THICK theButton.backColor = FXRGB(0, 128, 0) theButton.textColor = FXRGB(255, 255, 255) end
This approach has the advantage that the initialization code now has access to the outer scope, as blocks normally would. Thanks to Rich Kilmer for this suggestion.
Added FXGLGroup#<<, FXIconList#<<, FXList#<< and FXListBox#<< as aliases for those classes' "append item" methods, so that you can write code like:.
aList = FXList.new(...)
aList << FXListItem.new("First Item")
aList << FXListItem.new("Second Item")Added an FXDataTarget#to_s method that returns the result of calling to_s on its value (see SourceForge Feature Request #598271).
Corrected a bug in the return value for the class method FXFont.listFonts; it was basically returning garbage.
Updated the RAA browser example program (raabrowser.rb) to reflect the latest SOAP interfaces to the RAA.
Corrected one of the tests in the TC_FXHVec.rb test case to use Object#class instead of Object#type; this was triggering a warning message about Object#type being deprecated in Ruby 1.7.3.
Fixed a bug in the extconf.rb script for the $CFLAGS setting (needed an extra space between some of the compiler flags).
Fixed a bug that caused the SCNotification#nmhdr instance method to return an object of the wrong type (see SourceForge Bug #636713). Thanks to Laurent Julliard for reporting this bug.
Updated the build instructions and the Scintilla library file (i.e. fox/scintilla.rb) for consistency with FXScintilla version 1.49.
Fixed a long-standing problem related to the FXRuby class hierarchy. Due to some decisions made early on in the FXRuby implementation, the class hierarchy has always been a little broken, but no so much that casual users would notice. Basically, instead of a class hierarchy that strictly follows FOX's, e.g.
Object > FXObject > FXId > FXDrawable > FXWindow > FXFrame > FXLabel
you had this weird, shadow hierarchy thing going on, i.e.
Object > FX_Object > FX_Id > FX_Drawable > FX_Window > FX_Frame > FX_Label > FXLabel
There was never a lack of functionality and, for the most part, things worked as expected, but it's been nagging at me for a long time and so I finally fixed it. You absolutely should not need to make any changes to your code, and, as I said earlier, many people weren't aware of this problem anyways. But I feel much better now.
Fixed a couple of bugs related to saving image files from the imageviewer.rb example program (see SourceForge Bug #607610). Thanks to Joel VanderWerf for reporting one of these (the use of fxexists() instead of File.exists?).
The FXApp#addInput and FXApp#removeInput instance methods weren't set up quite right for dealing with dual-ported IO objects (see SourceForge Bug #607614). This has been corrected.
Several of the example programs were printing out debugging messages to the console, which is all well and good until somebody gets hurt. It turns out that when you print to the console from a non-console build of Ruby for Windows (i.e. the rubyw.exe version) it will cause the program to crash (see SourceForge Bug #611272). So I've purged all of those print statements, or in some cases turned them into message boxes.
The FXProgressDialog didn't understand how to show itself with a specified placement (e.g. show(PLACEMENT_OWNER)) and so calls to FXProgressDialog#execute would also fail as a side effect (see SourceForge Bug #621552). Thanks to Austin Ziegler for catching this one. Austin also contributed an updated version of the datatarget.rb example that incorporates a dial-shaped FXProgressBar widget into its display, showing yet another potential "consumer" of data targets.
Fixed a bug for the connect() instance method. The second argument should accept either a Method or Proc instance, but it was only handling Method inputs. This has been corrected.
Added initialization blocks for the different classes' new methods (see SourceForge Feature Request #611977). So for example, a section of code previously written like this:
button = FXButton.new(subsplitter, "&Of course\tThis splitter does NOT track", nil, nil, 0, FRAME_SUNKEN|FRAME_THICK) button.backColor = FXRGB(0, 128, 0) button.textColor = FXRGB(255, 255, 255)
could now be replaced with a block like this:
FXButton.new(subsplitter, "&Of course\tThis splitter does NOT track") do self.frameStyle = FRAME_SUNKEN|FRAME_THICK self.backColor = FXRGB(0, 128, 0) self.textColor = FXRGB(255, 255, 255) end
I resisted doing this for awhile, because I couldn't see any big advantages over the other approach (i.e. the "before" code snippet shown above). But Hal pointed out that with the initialization code block approach you don't have to introduce a new temporary variable to set additional initialization parameters, and that's a good enough reason for me to add it. As with Ruby/Tk, the code in these blocks is evaluated in the new instance's scope (and not the enclosing scope), so keep that in mind.
It's no longer necessary to call FXApp#init after constructing a new FXApp instance; this is done automatically. Old code that does call FXApp#init should continue to work as before (i.e. there's no harm in calling init more than once).
The FXApp#beginWaitCursor instance method now takes an optional code block. If a code block is provided, FXApp#endWaitCursor will be called automatically when the block terminates. So, for example, if you have some time-consuming process (say, loading a large file) you can signal this to your application's user like this:
getApp().beginWaitCursor do load_the_file(filename) end
Modified the FXMAPFUNC method (and similar methods) to accept symbols in addition to strings for the message handler method names.
Added an FXRuby version of the "Pig It!" example (one of the Ruby/Tk examples from Programming Ruby).
Fixed a bug reported by Dalibor Sramek (see SourceForge Bug #596492) for the overloaded versions of some methods (such as FXList#appendItem). This bug was introduced in the FXRuby-1.0.12 release.
Added the FXCalendar widget, contributed by David Naseby.
Some of the example programs depend on other Ruby modules; e.g. the RAA browser example requires the SOAP extension. If you tried to run one of these examples and it couldn't load the required dependencies the program would just bail out, with an error message to the console. This was OK (sort of) for Linux users, but many Windows users were trying to run these examples by double-clicking on their icons in the Windows Explorer. For those unlucky individuals, the program simply doesn't start up and there's no indication of what's wrong. This has been fixed: now you'll get a little error dialog indicating what's missing. Thanks to "Bob" for pointing out this problem.
Per Hugh Sasse's suggestion, added links from the Examples section directly to the Ruby code for the examples.
Fixed a bug reported by an anonymous user (see SourceForge Bug #586128) for the various docking options in the imageviewer.rb example program.
Fixed a bug that caused bad data to be sent along with the SEL_SELECTED and SEL_DESELECTED messages from an FXTreeList instance to its message target (see SourceForge Bug #591276). Thanks to Tom Jordan for reporting this bug.
Fixed some bugs related to the use of styled text in an FXText widget and added a small example (styledtext.rb) to demonstrate how this works.
Fixed a problem related to sorting of list items for various widgets (the overridden comparison functions weren't always getting called). Thanks to Tom Jordan for reporting this bug.
Removed the SWIG typemaps for FXTableRange and FXTablePos output values which would previously have tried to look up existing Ruby references to the same C++ pointers and return those. This practice (which works fine in most cases) was leading to an obscure bug (see SourceForge Bug #560128). See the ChangeLog comments for more details about the problem. Thanks to Joel VanderWerf for reporting this problem.
Made the constructors for FXDrawable, FXShell and FXTopWindow public so that these classes can be subclassed (see SourceForge Bug #568765). Thanks to Laurent Julliard for reporting this problem.
Fixed a bug in the inputs.rb example program that caused it to loop indefinitely even after the read pipe was empty. This would cause the text buffer to get filled with garbage for some cases.
Updated the FXScintilla-related documentation and build scripts to reflect the new build and installation process for FXScintilla version 1.46. Thanks to Laurent Julliard for providing me with most of this information.
Corrected the code for FXTreeList#clearItems so that after the C++ objects (the tree items) are destroyed, any Ruby instances holding references to those C++ objects are notified of their demise. This one was inadvertently overlooked when other, similar, fixes were made in the previous release. Thanks to Gilles Filippini for catching this.
The API for Ruby's rb_rescue2() function changed in Ruby 1.6.7, but I missed this since I had only compiled against the Ruby 1.6.6 and Ruby 1.7 header files. The API change led to a compile error for FXRuby.cpp; this has been fixed. Thanks to Bil Kleb for catching this one.
The FXApp#enableThreads and FXApp#disableThreads instance methods have been replaced by a single instance method, FXApp#threadsEnabled=, which takes a boolean argument as its input. Also added a new instance method, FXApp#threadsEnabled?, which returns the current value for this setting.
The FXToolbar#dock instance method could crash if the default value of nil was used for the second argument (see SourceForge Bug #550417). This has been fixed.
Calls to FXFileDialog.getOpenFilename could crash if the fourth argument's default value was used instead of explicitly specifying its value (see SourceForge Bug #550349). This has been fixed.
Added input typemaps for FXVec and FXHVec, such that any method which expects one of these types as input will also accept a Ruby array of the same size. For example, FXGLViewer#backgroundColor= can now be called in either of these equivalent forms:
glviewer.backgroundColor = FXHVec.new(0.5, 0.5, 0.5, 1.0)or
glviewer.backgroundColor = [0.5, 0.5, 0.5, 1.0]
Updated the test case for FXMaterial, added a new test case for FXFileStream.
Added aliases posVisible? (for FXTextField#isPosVisible), posSelected? (for FXTextField#isPosSelected), itemCurrent? (for FXComboBox#isItemCurrent), hasAccel? (for FXAccelTable#hasAccel), error= (for FXStream#setError), position= (for FXStream#setPosition) and position (for FXStream#getPosition).
The FXStream, FXFileStream and FXMemoryStream classes were not implemented properly, in the sense that if you subclassed one of these classes and then attempted to override one of their virtual functions, that override might not get called (see SourceForge Bug #535955). This has been fixed.
Did some more work on completing the FXDC interface, including adding a test case for the same. Also added a new example dctest.rb that demonstrates many of the FXDC interface.
All GIF image files have now been replaced with PNG equivalents. For more information on why this is a good idea, see this site.
Starting with this release, Red Hat Linux 7.2 RPMs are now provided in addition to the Windows installer.
The previous implementation of FXImage#data returned a copy of the underlying image pixel data instead of a pointer to the actual data (see SourceForge Bug #550996). This has been fixed. FXImage#data now returns an instance of FXMemoryBuffer which is an array or string-like class that acts as a very thin layer over the pointer to the pixel data. The initialize methods for images and icons were also updated so that they will now accept either strings (as before) or FXMemoryBuffer instances as inputs for the image data.
Modified the range and range= methods for FXDial, FXSlider and FXSpinner so that they work with Ruby Range objects instead of pairs of integers for the low and high values.
Lots of other little fixes here and there...
Upgraded to the latest version of Minero Aoki's install.rb script.
Switched from using DocBook/SGML to DocBook/XML for the FXRuby documentation. This is mainly a maintenance issue; it's just much less complicated to "publish" documents using DocBook/XML as compared to DocBook/SGML. You should see few (if any) differences in the resulting HTML pages, but let me know if you notice anything squirrely.
Improved support for customized sorting of list items for the FXComboBox, FXIconList, FXList, FXListBox, FXTreeList and FXTreeListBox classes. The model is different from that used in the C++ FOX library, where you set a global sorting function for each list instance. For FXRuby, you instead just override the list item's <=> method to compare one list item to another. Thanks to Martin Stannard for prompting me to resolve this problem.
Added instructions about how to modify your /etc/ld.so.conf file (on Linux) to include the libFOX.so installation directory. Thanks to Giuseppe Cacopardo for providing this information.
Updated the test cases (again) for use with Nathaniel Talbott's TestUnit, which is apparently the successor to Lapidary.
Added support for the FXCURCursor, FXRGBIcon, FXRGBImage classes.
Fixed a longstanding bug related to the ownership (for garbage collection purposes) of FXGLGroup and FXGLShape instances. This is the bug that was causing the glviewer.rb example program to crash on exit.
Fixed a different (but also longstanding) bug related to FXRuby's hanging on to "stale" Ruby object references after those Ruby objects had already been garbage-collected. This bug manifested itself in a number of ways, but the most common symptom would be for an object (like an FXEvent instance) to suddenly lose its mind and believe it was some other object. Oh, and your program would usually crash at that point. I think this problem is now fixed.
Added some safeguards for "destructive" functions like FXList#clearItems, which can destroy the C++ objects backing-up Ruby objects in FXRuby, so that any outstanding Ruby references to those destroyed C++ objects are left in a safe state when accessed by Ruby's garbage collector.
Performed a major overhaul on the shutter.rb example program, which still demonstrates the FXShutter widget but otherwise doesn't resemble its previous incarnation at all.
Added a new example program (raabrowser.rb) that allows you to browse the Ruby Application Archive via its SOAP interface. Requires the SOAP4R extension.
Added a new example program (babelfish.rb) that allows you to use the Babelfish translator web service via its SOAP interface. Requires the SOAP4R extension.
Added a new page to the documentation to briefly describe each of the example programs (including screenshots).
The CVS repository for FXRuby is now hosted at the SourceForge site. For those who would like access to the latest version of FXRuby (in-between official releases) this is now an option. For more details, see the instructions at the SourceForge project CVS page.
Updated the interfaces for compatibility with fox-1.0.3.
Fixed a big bug related to the FXApp#addSignal and FXApp#removeSignal functions, which should accept a string signal name as an argument. The list of recognized signal names was not constructed properly and as a result most any call to these methods with a string signal name would fail. This has been corrected, and the methods now throw exceptions like those thrown from Process::kill when the signal name is unrecognized or the argument type is wrong.
The imageviewer.rb example program now supports loading TARGA, ICO and TIFF image files.
The configuration process on Windows should now detect the presence of libtiff.lib properly.
Updated the interfaces for compatibility with fox-0.99.189.
Removed the -fno-strict-prototype and -fpermissive flags from the CFLAGS for Linux builds, since these two flags are no longer supported for more recent versions of gcc. Thanks to Thomas Lundqvist for reporting this.
Some of the source files included in the previous release had DOS-style line endings and this caused gcc to choke while compiling them; this has been fixed. Thanks to Thomas Lundqvist for reporting this.
Updated the FXRuby test cases (such as they are) to use Nathaniel Talbott's Lapidary unit testing framework.
Migrated yet more code from the C++ extension to pure Ruby versions. Most of the code for the FXPoint, FXRectangle and FXSize classes is now implemented in Ruby.
Fixed a bug in the browser.rb example. I had meant for the methods and constants lists to be sorted but had failed to use the in-place sort! method. Thanks to Robert Gustavsson for reporting this.
Completed a lot of the initial work for integrating the FXScintilla widget into FXRuby. This is not usable yet, but I'm working with Rich Kilmer and others to try to make this happen.
Updated the build instructions to provide more information about building the Visual C++ version of FXRuby (i.e. for use with the Visual C++ build of Ruby) on Windows. Thanks to a final tip from Martin Stannard we now have a working build for this compiler.
For fun, added a new example program that downloads today's Dilbert cartoon from the DilbertZone web site and displays it in an FXImageViewer widget. Requires the html-parser module, listed in the Ruby Application Archive.
Updated the interfaces for compatibility with fox-0.99.188.
Added slices, stacks and loops accessors for the FXGLCone and FXGLCylinder classes, to provide finer control over the rendering fidelity for these shapes (this was already done for FXGLSphere in the previous release).
Updated the interfaces for compatibility with fox-0.99.181.
Moved the class definitions for FXGLPoint, FXGLLine, FXGLCube, FXGLCylinder, FXGLSphere and FXGLCone to a new library module fox/glshapes.rb. The interfaces are the same as the C++ versions of these classes, this is just a "pure Ruby" implementation of the classes instead of wrappers around the C++ classes. See examples/glviewer.rb for an example of their use.
Fixed a bug related to object ownership for GL objects added to a FXGLGroup.
Added support for overriding the virtual layout method in classes derived from FXWindow. This will allow developers to, for example, develop new kinds of layout managers.
Replaced the previous WISE-based installer for the Windows version with an Inno Setup-based installer and reorganized things to better reflect the organization used in the standard Ruby installer.
Updated the setup and build script to the latest version of Minero Aoki's scripts (version 3.0.2). The main change for FXRuby end-users is that the name of the build script is now install.rb instead of setup.rb.
Added the colors.rb library file, for predefined color names in the FXColor namespace. The use of this module allows you to use symbolic names like FXColor::Red instead of an RGB constant like FXRGB(255, 0, 0). Many thanks to Jeff Heard for this suggestion and the contributed file.
The FXRegion was accidentally being renamed to FX_Region (a little behind-the-scenes magic I'm doing in FXRuby) and as a result you couldn't use this class. Thanks to Steven Grady for catching this bug.
The FXFileStream class now supports a Ruby-style open singleton method that provides transactional control for closing the file stream when it's done. See the image.rb and imageviewer.rb examples for how this works.
After some discussions at RubyConf and follow-up discussions on the comp.lang.ruby newsgroup, the procedure for attaching events handlers to FXRuby widgets has been greatly simplified. Most of the example programs have been updated to reflect these changes, and a new documentation section has been added to describe how it works. For some of the background, please see this page on the RubyGarden Wiki.
Added support for the each_row and each_column iterators for the FXTable class. These iterators yield an array of references to FXTableItem instances, one per row or column, respectively. Note that the each method is just an alias for each_row.
Removed the interfaces for fxrandom, fxmalloc, fxcalloc, fxresize, fxmemdump, and fxfree. These utility functions are not relevant for FXRuby.
Corrected interfaces for fxhsv_to_rgb and fxrgb_to_hsv so that they return three-element arrays of the converted color components.
Corrected interfaces for FXWindow#acquireSelection, FXWindow#acquireClipboard and FXWindow#beginDrag to take an array of drag types.
Corrected interfaces for fxsaveBMP, fxsaveGIF, fxsaveICO, fxsavePCX, fxsavePNG, fxsaveTIF and fxsaveXPM so that they expect a Ruby string (containing the image pixel data) as their second argument.
Updated the interfaces for compatibility with fox-0.99.180.
Moved all of the method name aliases out of the C interface code and into a new library file (fox/aliases.rb). This file is loaded automatically so you don't need to change your code. Similarly, moved all of the iterator methods out of the C code and into a library file (fox/iterators.rb). The main purpose of these changes is to reduce the size of the C++ code (especially core_wrap.cpp) where possible. Obviously, compared to recompiling the C++ source code, it's also much more efficient to quickly patch the Ruby files and re-run when there are problems.
A few errors made it into the undolist.rb library module and the textedit.rb example last time; I think these have been fixed.
I meant to add support for the new FXPCXIcon and FXPCXImage classes with the last release, but somehow I overlooked those. They are now supported, along with the other new classes introduced by FOX version 0.99.174: FXTIFIcon, FXTIFImage and FXProgressDialog.
Fixed a bug in the GC-related code for "marking" C++ objects. I had not accounted for the possibility that the pointer passed to my mark functions could be a NULL pointer, and as a result the code would seg fault during garbage collection, under some circumstances. Many thanks to Ralf Canis for catching this bug.
Updated the source code and extconf.rb files so that FXRuby configures and builds correctly for the "mswin32" builds. Thanks very much to Lorien Dunn for prompting me to get this stuff up-to-date!
Fixed things so that the FXApp#addInput and FXApp#removeInput instance methods work properly for generating input messages. The first argument to both of these methods should be an IO object of some kind (specifically, an object that implements a fileno method). For more information about how this works, see the "Timers, Chores, Signals and Input Messages" section of the FOX documentation. Also see the new inputs.rb program in the examples directory for an example of how this works. Thanks to Ralf Canis for reminding me that I left this broken!
Completed the basic code changes required for FXObject#handle to properly convert its message data into something that the C++ objects recognize (see the first item in the FXRuby To-Do List for more information). A lot of message types and identifiers are now handled correctly, especially those that are common to all FXWindows. Most of the more widget-specific messages are not handled yet, and this is going to take awhile to complete; it's just a tedious process.
Updated the interfaces for compatibility with fox-0.99.174.
Changed the build and installation process to use Minero Aoki's setup.rb tools. Looking ahead I can see that FXRuby will probably consist of a core C++ extension module and a collection of Ruby library scripts, and so now was the right time to make that transition.
If you have previously installed FXRuby (and written programs with the same) there are a few changes that may affect you. First, setup.rb will install the shared library (fox.so) in your site_ruby directory instead of the "core" libraries directory, where it was previously installed. So you should be sure to remove the old version of fox.so before installing and using this one.
The other change to be aware of is that there is now a fox directory containing FXRuby library scripts, and the responder.rb module is the first entry for this directory. You can import this file into your Ruby scripts with the line:
require 'fox/responder'
and it's no longer necessary to drag copies of that file around.
Aliased the getText instance method to to_s for a number of classes. This change should make things a little more convenient when inspecting the contents of widgets whose primary purpose is text entry or display. Thanks to Barry Shultz for this suggestion.
Added the FXWindow#removeChild method for removing child widgets from a container window. This method doesn't exist for the C++ FXWindow class because it isn't needed; in C++ programs you simply delete the C++ object and it automatically gets removed from its parent. Note that after you call FXWindow#removeChild any outstanding references to the recently deceased child widget are invalid and should be set to nil or otherwise disposed of. Thanks to Ted Meng for noticing this omission.
Modified some of the OpenGL method calls in gltest.rb for compatibility with Ruby/OpenGL 0.32. You should now be able to use Ruby/OpenGL with FXRuby unmodified.
Added each() instance methods for FXComboBox, FXGLGroup, FXHeader, FXIconList, FXList, FXListBox, FXTreeItem, FXTreeList and FXTreeListBox in support of iterating over their sub-items. Also mixed the Enumerable module into all of these classes.
Corrected the implementations of getData() and setData() for a variety of classes. You should now be able to attach arbitrary (application-defined) data to any FOX object that supports these APIs.
As a debugging tool, you can now optionally catch exceptions raised in message handlers. To turn on this feature, call the setIgnoreExceptions(true) module method. When this is enabled, any exceptions raised in message handler functions will cause a standard stack trace to be dumped to the standard output, but then your application will, for better or worse, proceed normally. Thanks to Ted Meng for this suggestion.
Extended the interfaces for FXApp#addSignal and FXApp#removeSignal to accept either a string or integer as their first argument. If it's a string (e.g. "SIGINT" or just "INT") the code will determine the corresponding signal number for you (similar to the Process.kill module method). For examples of how to use this, see the datatarget.rb or imageviewer.rb example programs.
Corrected the implementations of fxparsefontdesc() and fxunparsefontdesc() module methods.
Added a pure Ruby implementation of the standard FXCommand and FXUndoList classes from the standard FOX library.
Added the splitter.rb example, to demonstrate the FXSplitter class and its options.
Completed the initial version of browser.rb, which is just a simple tool to inspect the methods and constants exposed by different FOX classes. Thanks to Albert Wagner for pointing out some bugs in this one and providing me with the motivation to complete it. I don't know how useful it is, but it seems to be a required utility for every GUI toolkit for Ruby ;) If you'd like to suggest further improvements, please feel free!
Corrected the constructors for FXXPMIcon and FXXPMImage so that they accept a list of strings as their second argument. The list of strings should be an XPM format image file. You can also pass nil to construct an initially-empty icon or image.
Corrected the message data sent by FXList to its message target for the SEL_SELECTED, SEL_DESELECTED, SEL_INSERTED, SEL_DELETED, and SEL_REPLACED messages. For each of these messages, the data should be an integer indicating the affected list item's index.
Added typemaps to convert Ruby message data back into C++ void pointers when calling the base class versions of message handlers. Please see the to-do list for a brief discussion of the issues that this fix addressed, and what remains to be done.
Fixed a subtle GC bug related to object ownership. I'll use the FXList and FXListItem classes to describe the problem, but it's also relevant for several other FOX classes.
There are two ways to add a new list item to an FXList instance. One of those ways involves creating a new FXListItem instance explicitly (i.e. using FXListItem.new) and then passing it into an FXList instance method like FXList#appendItem. Before you add the item to the list, the item is "self-owned"; in other words, if Ruby's garbage collector decides to kill off that FXListItem instance, it is appropriate to also destroy the underlying C++ object. After the list item has been added to an FXList, however, the FXList owns that list item and is responsible for destroying it.
This bug became an issue when you added FXListItem instances to a list, because the code didn't properly recognize the fact that "ownership" of the list item had been transferred from the FXListItem instance to the FXList. More to the point, Ruby's garbage collector assumed that it was still OK to destroy the FXListItem instances that it knew about, and so objects could get deleted twice. This would usually result in a core dump.
Many thanks to Albert Wagner for submitting an example program that demonstrated this problem.
Updated the interfaces for compatibility with fox-0.99.173.
Completed the coding for "safe" coexistence with Ruby's garbage collector; it should no longer be necessary to call GC.disable at the top of your FXRuby programs. Although all of the example programs now work correctly without disabling the garbage collector, this doesn't mean that there aren't still some bugs lurking. If your FXRuby program(s) crash mysteriously, try adding GC.disable to the top to see if it fixes things. If this does make a difference, please send me the program (or another that reproduces the problem) so I can track down what's going wrong.
Added aliases for all classes' accessor functions so that the related properties can be accessed more directly; for example, FXLabel#getText is aliased to FXLabel#text and FXLabel#setText is aliased to FXLabel#text=. Although the different forms are functionally equivalent, the new form is often easier to read. For example, consider this snippet of code that modifies a label's text:
aLabel.setText(aLabel.getText() + " (modified)")
and this version of the same, now using the propery accessor functions:
aLabel.text += " (modified)"
None of the standard FOX class APIs have been removed, so you shouldn't need to modify any already-working code.
Corrected the message data sent from the FXText widget to its message target for the SEL_SELECTED, SEL_DESELECTED, SEL_INSERTED, SEL_DELETED and SEL_REPLACED message types. For the first four messages, the associated message data sent to the target will be an array of two integers indicating the starting position in the text buffer and text length for the affected text. For the SEL_REPLACED message type, the message data will be an array of three integers, indicating the starting position in the text buffer, the length of the old (replaced) text, and the length of the new text.
Updated the interfaces for compatibility with fox-0.99.172.
Corrected the interfaces for FXInputDialog.getString, FXInputDialog.getReal and FXInputDialog.getInteger to either return the requested type or nil if the user cancels the dialog.
Added code at the top of all the examples to disable Ruby's garbage collector, until the issues with GC are resolved.
Corrected implementations for the overloaded versions of FXWindow#update. This method can be invoke with no arguments (in which case it updates the entire window) or with four arguments indicating the x, y, w and h of the client area to be updated.
Modified how the return values from Ruby message handler functions are interpreted, to make things a little more convenient for programmers. If the result is a numeric type (Fixnum, Bignum or Float) we try to cast it to a long integer but also trap the value to either zero or one. If it's a boolean result we map false and true to zero and one, respectively. For any other return type (nil, strings, etc.) we just assume they meant to return 1. Thanks to Ted Meng for this suggestion.
Modified the interfaces for FXFileDialog#getPatternList, FXFileSelector#getPatternList, FXFileDialog#setPatternList and FXFileSelector#setPatternList to take (or return) an array of strings (one array item per pattern) of the form "C/C++ Files (*.cpp)". This is sort-of a compromise between the currently available overloaded C++ versions of these functions, both of which look pretty awkward in Ruby. For an example of how this works now, see the imageviewer.rb example program.
Added the FOX key cap definitions (i.e. the symbols from fxkeys.h) into FXRuby. Thanks to Benedikt Grundmann for pointing out this omission.
Added initial support for multithreaded FXRuby applications. The current implementation does what is also done in Ruby/GTK; it turns over some idle processing time to the Ruby thread scheduler to let other threads do their thing. As I learn more about Ruby's threading implementation I may try something different, but this seems to work OK for now. As a simple example, I modified the groupbox.rb example program so that the clock label that appears in the lower right-hand corner is continuously updated (by a separate thread) instead of just displaying static text.
If you suspect that FXRuby's threads support is interfering with your application's performance, you may want to try tweaking the amount of time that the main application thread "sleeps" during idle processing; do this by setting the FXApp object's sleepTime attribute. The default value for FXApp#sleepTime is 100 milliseconds. You can also disable the threads support completely by calling FXApp#disableThreads (and subsequently re-enable it with FXApp#enableThreads.
Started adding RubyUnit-style test cases to the tests subdirectory. There's not much there yet but we've got to start somewhere!
Converted most of the documentation over to DocBook format and reorganized the web page accordingly.
Updated the interfaces for compatibility with fox-0.99.167.
Corrected a problem with the binary distribution (i.e. the fox.so file) for Windows, which made it unusable on most Windows installations. This problem was related to an incompatibility between the versions of Cygwin that I was using to compile FOX and FXRuby, and the version used to compile the standard Ruby 1.6.2 distribution for Windows. Many, many thanks to Pete, Bene and Robert for helping me to test and resolve this issue!
Added support for FOX data targets, as demonstrated by the new datatarget.rb example in the examples directory. A data target is a special kind of FOX object that can be designated as the message target of any widget that has an associated "value", and then the value of the data target and that widget become automatically linked to each other. For example, you can create a data target with a string value and then make it the message target for an FXTextField widget. From then on, changes made to the text field will be automatically reflected in the data target's value and vice versa. (For those familiar with Tk, this is the same principal as its TkVariable class.)
Made the extconf.rb script a lot more intelligent and robust. Now, if you don't explicitly specify the --with-fox-include and --with-fox-lib arguments it will look in the standard FOX installation locations (/usr/local/include/fox and /usr/local/lib, respectively). If it doesn't find the appropriate files there either, it will stop with an error message. The new extconf.rb script also compares the FOX version number from the FOX's fxver.h file to be sure it's consistent with the version of FXRuby that you're building.
Revived the missing FXApp#addTimeout and FXApp#removeTimeout methods, which accidentally got clobbered in the 0.99.166 release.
Corrected the implementation of FXWindow#getCursorPosition. Previously it thought it needed three inputs when in fact it doesn't; this method takes no arguments and returns a 3-element array containing the current x and y positions (in local window coordinates) and a flag indicating the mouse button states.
Corrected the implementation of FXDebugTarget.messageTypeName to return an array of strings instead of an array of symbols (IDs).
Added missing wrappers for the FXJPEGImage and FXJPEGIcon classes; somehow these got overlooked previously.
Corrected several bugs in the example programs groupbox.rb, imageviewer.rb and table.rb.
In previous releases of FXRuby, not all of the constructor arguments for some class constructors were exposed; this was due to an obscure problem with how SWIG generated code for functions with more than nine default arguments. This problem has been corrected and all class constructor argument lists should now be consistent with the C++ library versions of the same.
Corrected several bugs related to the conversions between FOX's FXbool (boolean) type and Ruby's true and false values. These bugs manifested themselves in many weird and wonderful ways; the most obvious one was the way that opening up pulldown menus (for example, in the foursplit.rb example) would cause the entire program to lock up. If you were seeing some unusual behavior like this in the previous release my best advice is to see if you can still reproduce the problem (and if so, contact me).
Added several new example programs (dialog.rb, dirlist.rb, groupbox.rb, header.rb, imageviewer.rb, tabbook.rb and table.rb) in the examples subdirectory.
Added support for the FXFile class' singleton methods. Most or all of this functionality is already available in Ruby's standard library but FXFile is included for completeness.
Generally speaking, I corrected the (Ruby-side) implementations of a lot of functions and I believe that all of the important ones are exposed correctly. See the ChangeLog file for specific cases.
Started work on tutorial documentation (in the doc subdirectory) but this isn't very useful yet. Plans are to beef this up considerably next time.
Updated the API for compatibility with FOX version 0.99.166.
The code has been reworked a good deal in an effort to reduce the compile time (although more can and will be done). One part of this change was to break the extension code up into multiple files instead of a single file. The more significant change was to move a lot of inlined template function instantiations out of the FXRuby header file (FXRuby.h) and into a separate source code file that only needs to be compiled once. To give you an idea of the compile times after this change, I can now compile FXRuby under Cygwin-on-Win2000 in about 6 minutes (on a Pentium III-700MHz) or under Linux in about 30 minutes (on a Pentium "classic" 133MHz).
For classes FXHeader, FXIconList and FXList, added support for the overloaded member functions appendItem, insertItem, prependItem, and replaceItem. For classes FXTreeList and FXTreeListBox, added support for the overloaded member functions addItemAfter, addItemBefore, addItemFirst, and addItemLast.
Added the header.rb example program to demonstrate the FXHeader widget.
Updated the API for compatibility with FOX version 0.99.161.
Table of Contents
Abstract
FOX provides extensive support for OpenGL through its FXGLCanvas and FXGLViewer widgets, and FXRuby in turn provides interfaces to those classes. By combining FXRuby with the OpenGL interface for Ruby (described below) you can develop very powerful 3-D graphics applications. This chapter gives you the information you'll need to get started.
OpenGL is a platform-independent API for 2D and 3D graphics. The home page is http://www.opengl.org. Because it's a fairly open standard, highly optimized OpenGL drivers are available for most operating systems (including Windows and Linux).
This extension module, developed by Yoshiyuki Kusano, provides interfaces to not only the basic OpenGL API, but also the GLU and GLUT APIs. As of this writing, the currently released version is 0.32b and is available for download from http://www2.giganet.net/~yoshi/rbogl-0.32b.tgz. Be sure to check the Ruby Application Archive for the latest version of this extension as it is still under development.
Once you've downloaded the tarball, you should extract its contents to the working directory of your choice by typing:
$ tar xzf rbogl-0.32b.tgzAfter executing this command you should have a new opengl (not rbogl-0.32b) subdirectory. Change to this directory and then type:
$ ruby extconf.rbThis should create a Makefile configured appropriately for your local Ruby installation. To now build the OpenGL module, type:
$ makeYou can safely ignore the warning(s) about glut_KeyboardFunc when it's compiling glut.c. Well, I ignore them and it hasn't hurt me yet ;) Assuming you get an otherwise clean build, install the OpenGL extensions by typing:
$ make installPlease note that I'm not the maintainer of this particular Ruby extension, so I can't really accept bug fixes for it. But if you're having trouble integrating Ruby/OpenGL with FXRuby, let me know and we'll see what we can do.
An FXGLVisual object describes the capabilities of an FXGLCanvas or FXGLViewer window. Typically, an X server supports many different visuals with varying capabilities, but the ones with greater capabilities require more resources than those with fewer capbilities. To construct an FXGLVisual object, just call FXGLVisual.new:
aVisual = FXGLVisual.new(theApp, VISUAL_DOUBLEBUFFER)
The first argument to FXGLVisual.new is a reference to the application object. The second argument is a set of options indicating the requested capabilities for the visual. If one or more of the requested capabilities aren't available, FOX will try to gracefully degrade to a working GL visual; but if you're counting on a specific capability, be sure to check the returned visual to see if it actually supports that capability. For example, say you request a visual with double-buffering and stereographic capabilities:
anotherVisual = FXGLVisual.new(theApp, VISUAL_DOUBLEBUFFER | VISUAL_STEREO)
Double-buffering is pretty commonplace these days, but stereo may not be available on the system. We can check to see whether the visual we got supports these capabilities by calling the FXGLVisual#doubleBuffered? and FXGLVisual#stereo? methods:
anotherVisual = FXGLVisual.new(theApp, VISUAL_DOUBLEBUFFER | VISUAL_STEREO) if anotherVisual.doubleBuffered? puts "It's double-buffered." else puts "It's single-buffered." end if anotherVisual.stereo? puts "It's stereo." else puts "It isn't stereo." end
Some FXGLVisual object must be associated with every FXGLCanvas or FXGLViewer window, but you don't need to have a separate FXGLVisual object for each window. For most applications, you can just construct a single FXGLVisual object that's shared among all the OpenGL windows.
The FXGLCanvas widget provides a very simple OpenGL-capable window with minimal functionality. To construct an FXGLCanvas, call FXGLCanvas.new:
glCanvas = FXGLCanvas.new(parent, vis)
The first argument to FXGLCanvas.new is the parent (container) widget and the second argument is the FXGLVisual that should be used for this window.
The FXGLViewer widget provides a higher-level OpenGL-capable window with a lot of built-in functionality. To construct an FXGLViewer, call FXGLViewer.new:
glViewer = FXGLViewer.new(parent, vis)
The first argument to FXGLViewer.new is the parent (container) widget and the second argument is the FXGLVisual that should be used for this window.
Scintilla is a free source code editing component developed by Neil Hodgson for the Win32 and GTK+ platforms.
FXScintilla is a FOX widget that wraps around the Scintilla component, or, if you wish, the FOX "port" of Scintilla. It is being developed by Gilles Filippini, and as of this writing the latest release is available for download from http://savannah.nongnu.org/download/fxscintilla/FXScintilla.pkg/1.53/fxscintilla-1.53.tar.gz.
The FXScintilla distribution contains everything you need to build the FXScintilla widget and begin using it in your C++-based FOX applications. That is to say, you do not have to separately download the Scintilla source code from the Scintilla home page. When you unpack the FXScintilla tarball, you should get a new fxscintilla-1.53 directory containing the source code for the FOX port of the Scintilla widget.
As of the 1.46 release of FXScintilla, the build process has been "autoconfiscated" and should seem very familiar to you if you've built other open-source software (like FOX) from the source code. The INSTALL file in the top-level directory should provide enough instruction for you to build and install FXScintilla for either Unix or Microsoft Windows.
The next step is to build a version of FXRuby (from its source code) with the optional FXScintilla support enabled. If you're working on a Unix or Linux system and have installed FXScintilla in one of the standard installation directories (e.g. under /usr/include or /usr/local/include), the regular FXRuby build process should automatically detect it and enable FXScintilla support.
If you have installed FXScintilla in some non-standard place, or if you're building the code on Microsoft Windows, you will need to specify a few additional configuration options at the beginning.
You can configure the build on Unix or Linux systems by typing:
$ ruby install.rb config -- \
--with-fxscintilla-include=/usr/local/include/fxscintilla \
--with-fxscintilla-lib=/usr/local/lib
or, when compiling with Microsoft Visual C++, by typing:
C:\FXRuby-1.0.25>ruby install.rb config --make-prog=nmake -- \
--with-fox-include=C:\fox-1.0.46\include \
--with-fox-lib=C:\fox-1.0.46\lib \
--with-fxscintilla-include=C:\fxscintilla-1.53\include \
--with-fxscintilla-lib=C:\fxscintilla-1.53\lib
Past this point, the build and installation process for either platform should be the same as for standard builds. To test your new FXScintilla-enabled build of FXRuby, try running the scintilla-test.rb example program in the FXRuby examples subdirectory.
The FXRuby API follows the FOX API very closely and for the most part, you should be able to use the standard FOX class documentation as a reference. In some cases, however, fundamental differences between Ruby and C++ necessitated slight changes in the API. For some other cases, FOX classes were enhanced to take advantage of Ruby language features (such as iterators). The purpose of this chapter is to identify some of the differences between the C++ and Ruby interfaces to FOX.
One difference that should be easy to cope with is the substitution of Ruby Strings for FXStrings. Any function that would normally expect an FXString input argument insteads takes a Ruby String. Similarly, functions that would return an FXString will instead return a Ruby string. For functions that would normally accept a NULL or empty string argument, just pass nil or an empty string ("").
One common pattern in FOX member function argument lists is to expect a pointer to an array of values, followed by an integer indicating the number of values in the array. This of course isn't necessary in Ruby, where Array objects "know" their lengths. As a result, functions such as FXWindow::acquireClipboard(), whose C++ declaration looks like this:
FXbool acquireClipboard(const FXDragType *types, FXuint numTypes);
are called from Ruby code by passing in a single Array argument, e.g.
myWindow.acquireClipboard(typesArray)
Many FOX methods take advantage of the C++ language feature of returning values by reference. For example, the getCursorPos() member function for class FXWindow has the declaration:
FXint getCursorPos(FXint& x, FXint& y, FXint& buttons) const;
which indicates that the function takes references to three integers (x, y and buttons). To call this function from a C++ program, you'd write code like this:
FXint x, y; FXuint buttons; if (window->getCursorPosition(x, y, buttons)) fprintf(stderr, "Current position is (%d, %d)\n", x, y);
Since this idiom doesn't translate well to Ruby, some functions' interfaces have been slightly modified. For example, the FXRuby implementation of getCursorPos() returns the three values as an Array, e.g.:
x, y, buttons = aWindow.getCursorPos()
The following table shows how these kinds of functions are implemented in FXRuby:
| Instance Method | Return Value |
|---|---|
| FXDial#range | Returns a Range instance. |
| FXDial#range=(aRange) | Accepts a Range instance as its input. |
| FXFontDialog#fontSelection | Returns the FXFontDesc instance |
| FXFontSelector#fontSelection | Returns the FXFontDesc instance |
| FXGLObject#bounds(range) | Takes an FXRange instance as its input and returns a (possibly modified) FXRange instance. |
| FXGLViewer#eyeToScreen(eye) | Takes an array of eye coordinates (floats) as its input and returns the screen point coordinate as an array of integers [sx, sy] |
| FXGLViewer#getBoreVector(sx, sy) | Returns the endpoint and direction vector as an array of arrays [point, dir] |
| FXGLViewer#light | Returns a FXLight instance |
| FXGLViewer#viewport | Returns an FXViewport instance. |
| FXPrinterDialog#printer | Returns the FXPrinter instance |
| FXScrollArea#position | Returns the position as an array of integers [x, y] |
| FXSlider#range | Returns a Range instance. |
| FXSlider#range=(aRange) | Accepts a Range instance as its input. |
| FXSpinner#range | Returns a Range instance. |
| FXSpinner#range=(aRange) | Accepts a Range instance as its input. |
| FXText#appendText(text, notify=false) | Append text to the end of the buffer. |
| FXText#appendStyledText(text, style=0, notify=false) | Append styled text to the end of the buffer. |
| FXText#extractText(pos, n) | Extracts n characters from the buffer beginning at position pos and returns the result as a String. |
| FXText#extractStyle(pos, n) | Extracts n style characters from the buffer beginning at position pos and returns the result as a String. |
| FXText#insertText(pos, text, notify=false) | Insert text at position pos in the buffer. |
| FXText#insertStyledText(pos, text, style=0, notify=false) | Insert text at position pos in the buffer. |
| FXText#replaceText(pos, m, text, notify=false) | Replace m characters at pos by text. |
| FXText#replaceStyledText(pos, m, text, style=0, notify=false) | Replace m characters at pos by text. |
| FXText#setDelimiters(delimiters) | Change delimiters of words (delimiters is a string). |
| FXText#getDelimiters() | Return word delimiters as a string. |
| FXWindow#cursorPosition | Returns an array of integers [x, y, buttons] |
| FXWindow#translateCoordinatesFrom(window, x, y) | Returns the translated coordinates as an array [x, y] |
| FXWindow#translateCoordinatesTo(window, x, y) | Returns the translated coordinates as an array [x, y] |
Several classes have been extended with an each method to provide Ruby-style iterators. These classes include FXComboBox, FXGLGroup, FXHeader, FXIconList, FXList, FXListBox, FXTreeItem, FXTreeList and FXTreeListBox. These classes also mix-in Ruby's Enumerable module so that you can take full advantage of the iterators.
The block parameters passed to your code block vary depending on the class. For example, iterating over an FXList instance yields FXListItem parameters:
aList.each { |aListItem|
puts "text for this item = #{aListItem.getText()}"
}whereas iterating over an FXComboBox instance yields two parameters, the item text (a string) and the item data:
aComboBox.each { |itemText, itemData|
puts "text for this item = #{itemText}"
}The following table shows the block parameters for each of these classes' iterators:
| Class | Block Parameters |
|---|---|
| FXComboBox | the item text (a string) and user data |
| FXGLGroup | an FXGLObject instance |
| FXHeader | an FXHeaderItem instance |
| FXIconList | an FXIconItem instance |
| FXList | an FXListItem instance |
| FXListBox | the item text (a string), icon (an FXIcon instance) and user data |
| FXTreeItem | an FXTreeItem instance |
| FXTreeList | an FXTreeItem instance |
| FXTreeListBox | an FXTreeItem instance |
FOX strictly handles access to all object attributes through member functions, e.g. setBackgroundColor and getBackgroundColor or setText and getText. FXRuby exposes all of these functions but also provides aliases that look more like regular Ruby attribute accessors. The names for these accessors are based on the FOX method names; for example, setBackgroundColor and getBackgroundColor are aliased to backgroundColor= and backgroundColor, respectively.
In many cases these aliases allow you to write more compact and legible code. For example, consider this code snippet:
aLabel.setText(aLabel.getText() + " (modified)")
Now consider a different code snippet, using the aliased accessor method names:
aLabel.text += " (modified)"
While these two are functionally equivalent, the latter is a bit easier to read and understand at first glance.
FOX message maps are implemented as static C++ class members. With FXRuby, you just associate messages with message handlers in the class initialize method using the FXMAPFUNC(), FXMAPTYPE(), FXMAPTYPES() or FXMAPFUNCS() methods. See almost any of the example programs for examples of how this is done.
As in C++ FOX, the last argument passed to your message handler functions contains message-specific data. For instance, all SEL_PAINT messages pass an FXEvent object through this argument to give you some information about the size of the exposed rectangle. On the other hand, a SEL_COMMAND message from an FXHeader object passes the index of the selected header item through this argument. Instead of guessing what's in this last argument, your best bet is to instead invoke a member function on the sending object to find out what you need, instead of relying on the data passed through this pointer. For example, if you get a SEL_COMMAND message from an FXColorWell object, the data passed through that last argument is supposed to be the new RGB color value. Instead of trying to interpret the argument's contents, just turn around and call the color well's getRGBA() member function to retrieve its color. Similarly, if you get a SEL_COMMAND message from a tree list, call its getCurrentItem() method to find out which item was selected.
The FXApp#addSignal and FXApp#removeSignal methods have been enhanced to accept either a string or integer as their first argument. If it's a string (e.g. "SIGINT" or just "INT") the code will determine the corresponding signal number for you (similar to the standard Ruby library's Process.kill module method). For examples of how to use this, see the datatarget.rb or imageviewer.rb example programs.
There is some support for multithreaded FXRuby applications, but it's not wonderful. The current implementation does what is also done in Ruby/GTK; it turns over some idle processing time to the Ruby thread scheduler to let other threads do their thing. As I learn more about Ruby's threading implementation I may try something different, but this seems to work OK for now. For a simple example, see the groupbox.rb example program, in which the clock label that appears in the lower right-hand corner is continuously updated (by a separate thread).
If you suspect that FXRuby's threads support is interfering with your application's performance, you may want to try tweaking the amount of time that the main application thread "sleeps" during idle processing; do this by setting the FXApp object's sleepTime attribute. The default value for FXApp#sleepTime is 100 milliseconds. You can also disable the threads support completely by calling FXApp#threadsEnabled=false (and subsequently re-enable it with FXApp#threadsEnabled=true).
As a debugging tool, you can optionally catch exceptions raised in message handlers. To turn on this feature, call the setIgnoreExceptions(true) module method. When this is enabled, any exceptions raised in message handler functions will cause a standard stack trace to be dumped to the standard output, but then your application will, for better or worse, proceed normally. Thanks to Ted Meng for this suggestion.
While the majority of FXRuby is in fact implemented by an extension module, some parts are provided instead by "pure Ruby" code. This section describes the classes and modules available in the FXRuby standard library.
The fox/undolist.rb file provides the FXCommand and FXUndoList classes. These serve the same purpose as the FXCommand and FXUndoList classes from the standard FOX distribution, but they're implemented entirely in Ruby.
For a complete description of these classes and how to use them, see the RD documentation in fox/undolist.rb.
The fox/aliases.rb implements most of the accessor-style aliases for methods. This file is loaded automatically when you
require 'fox'
and so you should never need to load it directly.
The fox/colors.rb file, contributed by Jeff Heard, provides a bunch of predefined color values (based on the standard X11 color names). You can use these color constants anywhere that FOX expects an RGB color value, e.g.
dc = FXDCWindow.new(drawable, ev) dc.foreground = FXColor::MistyRose # instead of FXRGB(255, 228, 225) dc.background = FXColor::MidnightBlue # instead of FXRGB( 25, 25, 112)
The fox/glshapes.rb library provides Ruby implementations of a number of basic 3-D shapes (all derived from the built-in FXGLShape class) that can be used with the FXGLViewer. Several of these shapes are used in the glviewer.rb example program. These shapes were originally implemented in C++ and wrapped using SWIG, but they are straightforward enough to implement in Ruby so they were moved out to this library instead.
The fox/iterators.rb library just adds an each instance method for the FXComboBox, FXGLGroup, FXHeader, FXIconList, FXList, FXListBox, FXTable, FXTreeItem, FXTreeList and FXTreeListBox classes, so that you can iterate over their members in a Ruby-friendly way. It also mixes the Enumerable module into each of these classes.
The fox/keys.rb library file defines all of the key codes (e.g. KEY_space) that might show up in the code field of an FXEvent instance. This file is loaded automatically when you
require 'fox'
and so you should never need to load it directly.
Table of Contents
The development and maintenance of FXRuby would be almost impossible without the help of Dave Beazley's excellent SWIG. The complete set of SWIG interface files used to generate FXRuby is included in the standard FXRuby source code distribution, and if you'd like you can even regenerate the FXRuby sources using SWIG. Because FXRuby relies on functionality in the latest development version of SWIG, you will need to check out that version from the SWIG CVS repository. For instructions on how to do so, please see this page.
To regenerate the FXRuby sources from the SWIG interface files, change directories to the swig-interfaces subdirectory of the FXRuby source tree and type make . Any time that you make a change to the SWIG interface files, you'll need to repeat this step to update the C++ sources.
One of the more difficult issues to deal with was understanding the "life cycle" of FOX objects (that is, the actual C++ objects) and their relationship to the associated Ruby instances. Understanding this relationship is critical when dealing with Ruby's garbage collector, to ensure that objects are disposed of at the right time.
For our purposes, we can divide the set of all objects in an FXRuby program into two groups, those objects that Ruby "owns" and those that it doesn't. The first group (the "owned" objects) includes those that you create explicitly from Ruby code. This is usually done by calling a class' new method, e.g.
myIcon = FXPNGIcon.new(myApp, File.open("icon.png", "rb").read)
myButton = FXButton.new(parentWin, "Hello, World!", myIcon)It's important to keep in mind that when you create an object like this you're not only creating the Ruby instance part (i.e. whatever overhead is usually associated with a Ruby instance) but a C++ FOX object as well. Because we created these objects, we would reasonably expect them to be destroyed when they are garbage-collected so that our programs don't leak memory.
The other group of objects (those not owned by Ruby) are those returned from most class instance methods; they are references to already- existing objects. For example, FXStatusBar#statusline returns a reference to the status bar's enclosed status line instance.
A C++ FXGLGroup object owns all of the FXGLObject objects it "contains". In other words, when that FXGLGroup object is destroyed, it will also destroy all of the FXGLObject objects for which it holds pointers.
In order to keep track of which GL objects have been added to an FXGLGroup, all of the FXRuby C++ classes derived from FXGLObject have a boolean member variable owned that indicates whether this object is "owned" or not. Until an FXGLObject object is added to a group, this member variable should stay false.
One of the design requirements for FXRuby was to ensure that any virtual function call made on a FOX object (from the C++ library layer) is routed to the proper Ruby instance method, even if that method has been overridden in a derived class.
For example, many of the FXRuby example programs are structured with a main window class that subclasses FXMainWindow and then overrides its create instance method:
class MyMainWindow < Fox::FXMainWindow # overrides the base class version, FXMainWindow#create def create end end
This create method isn't called directly from Ruby code, however; it's called as a side effect of calling FXApp#create. Inside the C++ FOX library, the FXApp::create() member function recursively calls create() on all of the application's windows and eventually calls the virtual FXMainWindow::create() member function as well:

To ensure that our main window's overridden create method is invoked instead of the base class implementation, we need some special machinery in place.
For every C++ class that declares virtual member functions, we derive a new C++ class that overrides all of those virtual functions with special stubs that know how to make method calls on Ruby instances. The naming convention for these classes is that the "FX" prefix is replaced with "FXRb"; for example, our FXRbButton C++ class is derived from FOX's FXButton:

Although the implementation of these "stubs" varies from function to function, the basic requirements are always the same:
Look up the Ruby object associated with this C++ object.
Convert each of the function's arguments to their Ruby representation. For example, arguments of type FXint are converted to Ruby Fixnums.
Call the Ruby object's method, using the rb_funcall() function from Ruby's C API, and capture its result (which is itself a Ruby object).
Convert the method call's result back to the virtual function's C++ return type, and then return that result from the C++ function. For example, if the C++ function has a FXbool return type, we would expect the corresponding Ruby method to return either Qtrue or Qfalse.
All of the source code for FXRuby is available for anonymous, read-only CVS access. This chapter describes how to check out the sources for either the stable or development release of FXRuby and then build FXRuby from those sources. The information in this chapter builds on the basic CVS instructions provided for any SourceForge hosted project, and specifically those instructions for the FXRuby project hosted at SourceForge.
There are some prerequisites. Obviously, you're going to need to have some kind of CVS client installed on your system and have a clue about how to use it to check out code from a remote repository. Please do not send me questions about how to install or use CVS. The best source of information in this case is the CVS home page and, in particular, Per Cederqvist's CVS manual. Next, if you intend to make changes to the SWIG interface files (e.g. to change the behavior of some of the wrapped functions, or to add new ones), you'll need to have a working SWIG installation so that you can regenerate the C++ source files from the modified SWIG interface files. I always use the latest development version of SWIG, but any release after, say, SWIG 1.3.15 should work fine. The older SWIG 1.1 releases will definitely not work.
The stable version of FXRuby is the 1.0.x branch and is compatible with any of the FOX 1.0.x releases. It is not compatible with the development releases of FOX (i.e. the FOX 1.1.x releases).
To check out the stable version of FXRuby, do the following:
Log in to the CVS server by typing:
cvs -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/fxruby login
When prompted for a password for anonymous, simply press the Enter key.
Check out the stable branch of FXRuby by typing:
cvs -z3 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/fxruby co -rrelease10 FXRuby
At this point, you should be ready to change to the top-level directory and go through the normal build and installation process, as described in an earlier chapter.
If you make changes to any of the SWIG interface files (the files ending with a .i extension, in the swig-interfaces subdirectory) you will need to re-run SWIG to regenerate parts of the FXRuby source code:
Change directories to the swig-interfaces subdirectory of the FXRuby source tree.
Type the following command to create a "bootstrap" dependencies file:
touch dependencies
You should only need to do this for the initial checkout of the sources; after that, the dependencies file will be updated as described in the next step.
Build the "depend" target to generate the real dependencies file by typing:
make depend
Finally, regenerate the sources by typing:
make