Putting It All Together

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:

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 'fox16'

include Fox

class DragDropWindow < FXMainWindow
  def initialize(anApp)
    # Initialize base class
    super(anApp, "Drag and Drop", :opts => DECOR_ALL, :width => 400, :height => 300)
    
    # Fill main window with canvas
    @canvas = FXCanvas.new(self, :opts => 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) do |sender, sel, event|
      FXDCWindow.new(@canvas, event) do |dc|
        dc.foreground = @canvas.backColor
        dc.fillRectangle(event.rect.x, event.rect.y, event.rect.w, event.rect.h)
      end
    end

    # Handle left button press
    @canvas.connect(SEL_LEFTBUTTONPRESS) do
      #
      # 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)
    end
    
    # Handle mouse motion events
    @canvas.connect(SEL_MOTION) do |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
    end

    # Handle SEL_DND_MOTION messages from the canvas
    @canvas.connect(SEL_DND_MOTION) do
      if @canvas.offeredDNDType?(FROM_DRAGNDROP, FXWindow.colorType)
        @canvas.acceptDrop
      end
    end

    # Handle left button release
    @canvas.connect(SEL_LEFTBUTTONRELEASE) do
      @canvas.ungrab
      @canvas.endDrag
    end
    
    # Handle SEL_DND_DROP message from the canvas
    @canvas.connect(SEL_DND_DROP) do
      # 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

    # Handle request for DND data
    @canvas.connect(SEL_DND_REQUEST) do |sender, sel, event|
      if event.target == FXWindow.colorType
        @canvas.setDNDData(FROM_DRAGNDROP, FXWindow.colorType, Fox.fxencodeColorData(@canvas.backColor))
      end
    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