Skip to content
Snippets Groups Projects
glviewer.rb 23.9 KiB
Newer Older
#!/usr/bin/env ruby

require 'fox16'
require 'fox16/responder'
begin
  require 'fox16/glshapes'
rescue LoadError
  require 'fox16/missingdep'
  MSG = <<EOM
  Sorry, this example depends on the OpenGL extension. Please
  check the Ruby Application Archives for an appropriate
  download site.
EOM
  missingDependency(MSG)
end

include Fox

class TabBook < FXTabBook
  def createAnglesPage(panels, mdiclient)
    # Angles tab
    FXTabItem.new(panels,
      "Angles\tCamera Angles\tSwitch to camera angles panel.")

    # Angles page
    angles = FXMatrix.new(panels, 3,
      :opts => FRAME_THICK|FRAME_RAISED|MATRIX_BY_COLUMNS|LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT,
      :padding => 10)

    FXLabel.new(angles, "X:")
    FXTextField.new(angles, 6,
      mdiclient, FXGLViewer::ID_ROLL,
      TEXTFIELD_INTEGER|JUSTIFY_RIGHT|FRAME_SUNKEN|FRAME_THICK)
    x_dial = FXDial.new(angles,
      mdiclient, FXGLViewer::ID_DIAL_X,
      :opts => FRAME_SUNKEN|FRAME_THICK|DIAL_CYCLIC|DIAL_HORIZONTAL|LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT|LAYOUT_CENTER_Y,
      :width => 160, :height => 14, :padding => 0)
    x_dial.tipText = "Rotate about X"
    x_dial.notchOffset = 900

    FXLabel.new(angles, "Y:")
    FXTextField.new(angles, 6,
      mdiclient, FXGLViewer::ID_PITCH,
      TEXTFIELD_INTEGER|JUSTIFY_RIGHT|FRAME_SUNKEN|FRAME_THICK)
    y_dial = FXDial.new(angles,
      mdiclient, FXGLViewer::ID_DIAL_Y,
      :opts => FRAME_SUNKEN|FRAME_THICK|DIAL_CYCLIC|DIAL_HORIZONTAL|LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT|LAYOUT_CENTER_Y,
      :width => 160, :height => 14, :padding => 0)
    y_dial.tipText = "Rotate about Y"
    y_dial.notchOffset = 900

    FXLabel.new(angles, "Z:")
    FXTextField.new(angles, 6,
      mdiclient, FXGLViewer::ID_YAW,
      TEXTFIELD_INTEGER|JUSTIFY_RIGHT|FRAME_SUNKEN|FRAME_THICK)
    z_dial = FXDial.new(angles,
      mdiclient, FXGLViewer::ID_DIAL_Z,
      :opts => FRAME_SUNKEN|FRAME_THICK|DIAL_CYCLIC|DIAL_HORIZONTAL|LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT|LAYOUT_CENTER_Y,
      :width => 160, :height => 14, :padding => 0)
    z_dial.tipText = "Rotate about Z"
    z_dial.notchOffset = 900

    FXLabel.new(angles, "FOV:")
    fov = FXTextField.new(angles, 5, mdiclient, FXGLViewer::ID_FOV,
      JUSTIFY_RIGHT|FRAME_SUNKEN|FRAME_THICK)
    FXFrame.new(angles, :opts => FRAME_NONE)
    fov.tipText = "Field of view"

    FXLabel.new(angles, "Zoom:")
    zz = FXTextField.new(angles, 5, mdiclient, FXGLViewer::ID_ZOOM,
      JUSTIFY_RIGHT|FRAME_SUNKEN|FRAME_THICK)
    FXFrame.new(angles, :opts => FRAME_NONE)
    zz.tipText = "Zooming"

    FXLabel.new(angles, "Scale X:")
    FXTextField.new(angles, 5, mdiclient, FXGLViewer::ID_SCALE_X,
      JUSTIFY_RIGHT|FRAME_SUNKEN|FRAME_THICK)
    FXFrame.new(angles, :opts => FRAME_NONE)
    FXLabel.new(angles, "Scale Y:")
    FXTextField.new(angles, 5, mdiclient, FXGLViewer::ID_SCALE_Y,
      JUSTIFY_RIGHT|FRAME_SUNKEN|FRAME_THICK)
    FXFrame.new(angles, :opts => FRAME_NONE)
    FXLabel.new(angles, "Scale Z:")
    FXTextField.new(angles, 5, mdiclient, FXGLViewer::ID_SCALE_Z,
      JUSTIFY_RIGHT|FRAME_SUNKEN|FRAME_THICK)
    FXFrame.new(angles, :opts => FRAME_NONE)
  end

  def createColorsPage(panels, mdiclient)
    # Colors tab
    FXTabItem.new(panels, "Colors\tColors\tSwitch to color panel.")

    # Colors page
    colors = FXMatrix.new(panels, 2,
      MATRIX_BY_COLUMNS|FRAME_THICK|FRAME_RAISED|LAYOUT_FILL_Y|LAYOUT_CENTER_X|LAYOUT_TOP|LAYOUT_LEFT,
      :padding => 10)
    FXLabel.new(colors, "Background:", nil,
      LAYOUT_RIGHT|LAYOUT_CENTER_Y|JUSTIFY_RIGHT)
    FXColorWell.new(colors, 0, mdiclient, FXGLViewer::ID_BACK_COLOR,
      COLORWELL_OPAQUEONLY|LAYOUT_TOP|LAYOUT_LEFT, :padding => 0)
    FXLabel.new(colors, "Ambient:", nil,
      LAYOUT_RIGHT|LAYOUT_CENTER_Y|JUSTIFY_RIGHT)
    FXColorWell.new(colors, 0, mdiclient, FXGLViewer::ID_AMBIENT_COLOR,
      COLORWELL_OPAQUEONLY|LAYOUT_TOP|LAYOUT_LEFT, :padding => 0)

    FXLabel.new(colors, "Light Amb:", nil,
      LAYOUT_RIGHT|LAYOUT_CENTER_Y|JUSTIFY_RIGHT)
    FXColorWell.new(colors, 0, mdiclient, FXGLViewer::ID_LIGHT_AMBIENT,
      COLORWELL_OPAQUEONLY|LAYOUT_TOP|LAYOUT_LEFT, :padding => 0)
    FXLabel.new(colors, "Light Diff:", nil,
      LAYOUT_RIGHT|LAYOUT_CENTER_Y|JUSTIFY_RIGHT)
    FXColorWell.new(colors, 0, mdiclient, FXGLViewer::ID_LIGHT_DIFFUSE,
      COLORWELL_OPAQUEONLY|LAYOUT_TOP|LAYOUT_LEFT, :padding => 0)
    FXLabel.new(colors, "Light Spec:", nil,
      LAYOUT_RIGHT|LAYOUT_CENTER_Y|JUSTIFY_RIGHT)
    FXColorWell.new(colors, 0, mdiclient, FXGLViewer::ID_LIGHT_SPECULAR,
      COLORWELL_OPAQUEONLY|LAYOUT_TOP|LAYOUT_LEFT, :padding => 0)
  end

  def createSwitchesPage(panels, mdiclient)
    # Settings tab
    FXTabItem.new(panels, "Settings\tSettings\tSwitche to settings panel.")

    # Settings page
    settings = FXVerticalFrame.new(panels,
      FRAME_THICK|FRAME_RAISED|LAYOUT_FILL_Y|LAYOUT_CENTER_X|LAYOUT_TOP|LAYOUT_LEFT,
      :padding => 10)
    FXCheckButton.new(settings, "Lighting", mdiclient, FXGLViewer::ID_LIGHTING,
      ICON_BEFORE_TEXT)
    FXCheckButton.new(settings, "Fog", mdiclient, FXGLViewer::ID_FOG,
      ICON_BEFORE_TEXT)
    FXCheckButton.new(settings, "Dither", mdiclient, FXGLViewer::ID_DITHER,
      ICON_BEFORE_TEXT)
    FXCheckButton.new(settings, "Turbo", mdiclient, FXGLViewer::ID_TURBO,
      ICON_BEFORE_TEXT)
  end

  def initialize(frame, mdiclient)
    super(frame)
    createAnglesPage(self, mdiclient)
    createColorsPage(self, mdiclient)
    createSwitchesPage(self, mdiclient)
  end
end

class GLViewWindow < FXMainWindow

  include Responder
  ID_QUERY_MODE = FXMainWindow::ID_LAST
  ID_GLVIEWER   = ID_QUERY_MODE + 1
  # Load the named PNG icon from a file
  def loadIcon(filename)
    filename = File.expand_path("../icons/#{filename}.png", __FILE__)
    File.open(filename, "rb") do |f|
      FXPNGIcon.new(getApp(), f.read)
    end
  end

  def initialize(app)
    # Initialize base class first
    super(app, "OpenGL Example Application", :opts => DECOR_ALL, :width => 800, :height => 600)

    # Define message identifiers for this class

    # Set up the message map
    FXMAPFUNC(SEL_UPDATE,  GLViewWindow::ID_QUERY_MODE, :onUpdMode)
    FXMAPFUNC(SEL_COMMAND, FXWindow::ID_QUERY_MENU,	:onQueryMenu)

    # Main window icon
    peng = loadIcon("penguin")
    setIcon(peng)

    # The colors dialog
    colordlg = FXColorDialog.new(self, "Color Dialog", DECOR_TITLE|DECOR_BORDER)

    # Menubar
    menubar = FXMenuBar.new(self, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)

    # Tool bar
    FXHorizontalSeparator.new(self,
      LAYOUT_SIDE_TOP|SEPARATOR_GROOVE|LAYOUT_FILL_X);
    toolbar = FXToolBar.new(self, LAYOUT_SIDE_TOP|LAYOUT_FILL_X,
      :padLeft => 4, :padRight => 4, :padTop => 0, :padBottom => 0,
      :hSpacing => 0, :vSpacing => 0)

    # Make status bar
    statusbar = FXStatusBar.new(self,
      LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|STATUSBAR_WITH_DRAGCORNER)

    # The good old penguin, what would we be without it?
    FXButton.new(statusbar,
      "\tHello, I'm Tux...\nThe symbol for the Linux Operating System.\nAnd all it stands for.",
      :icon => peng, :opts => LAYOUT_RIGHT)

    # Contents
    frame = FXHorizontalFrame.new(self,
      LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y, :padding => 0, :hSpacing => 0, :vSpacing => 0)

    # Nice sunken box around GL viewer
    box = FXVerticalFrame.new(frame,
      FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_FILL_Y, :padding => 0)

    # MDI Client
    @mdiclient = FXMDIClient.new(box, LAYOUT_FILL_X|LAYOUT_FILL_Y)
    @mdimenu = FXMDIMenu.new(self, @mdiclient)

    # MDI buttons in menu:- note the message ID's!!!!!
    # Normally, MDI commands are simply sensitized or desensitized;
    # Under the menubar, however, they're hidden if the MDI Client is
    # not maximized.  To do this, they must have different ID's.
    FXMDIWindowButton.new(menubar, @mdimenu, @mdiclient,
      FXMDIClient::ID_MDI_MENUWINDOW, LAYOUT_LEFT)
    FXMDIDeleteButton.new(menubar, @mdiclient,
      FXMDIClient::ID_MDI_MENUCLOSE, FRAME_RAISED|LAYOUT_RIGHT)
    FXMDIRestoreButton.new(menubar, @mdiclient,
      FXMDIClient::ID_MDI_MENURESTORE, FRAME_RAISED|LAYOUT_RIGHT)
    FXMDIMinimizeButton.new(menubar, @mdiclient,
      FXMDIClient::ID_MDI_MENUMINIMIZE, FRAME_RAISED|LAYOUT_RIGHT)

    # Icon for MDI Child
    @mdiicon = loadIcon("winapp")

    # Make MDI Window Menu
    @mdimenu = FXMDIMenu.new(self, @mdiclient)

    # Make an MDI Child
    mdichild = FXMDIChild.new(@mdiclient, "FOX GL Viewer", @mdiicon,
      @mdimenu, MDI_NORMAL, 30, 30, 300, 200)
    @count = 1

    # A visual to drag OpenGL in double-buffered mode; note the glvisual is
    # shared between all windows which need the same depths and numbers of
    # buffers. Thus, while the first visual may take some time to initialize,
    # each subsequent window can be created very quickly; we need to determine
    # graphics hardware characteristics only once.
    @glvisual = FXGLVisual.new(getApp(), VISUAL_DOUBLEBUFFER)

    # Make it active
    @mdiclient.setActiveChild(mdichild)

    # Drawing gl canvas
    viewer = FXGLViewer.new(mdichild, @glvisual, self, ID_GLVIEWER,
      LAYOUT_FILL_X|LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT)

    # Tab book with switchable panels
    panels = TabBook.new(frame, @mdiclient)

    # Construct these icons
    newdoc = loadIcon("filenew")
    opendoc = loadIcon("fileopen")
    savedoc = loadIcon("filesave")
    saveasdoc = loadIcon("filesaveas")

    # File Menu
    filemenu = FXMenuPane.new(self)
    FXMenuTitle.new(menubar, "&File", nil, filemenu)
    FXMenuCommand.new(filemenu, "&New...\tCtl-N\tCreate new document.", newdoc)
    openCmd = FXMenuCommand.new(filemenu, "&Open...\tCtl-O\tOpen document file.", opendoc)
    openCmd.connect(SEL_COMMAND, method(:onCmdOpen))
    FXMenuCommand.new(filemenu, "&Save\tCtl-S\tSave document.", savedoc)
    FXMenuCommand.new(filemenu, "Save &As...\t\tSave document to another file.", saveasdoc)
    FXMenuCommand.new(filemenu, "&Print Image...\t\tPrint snapshot image.", nil, @mdiclient, FXGLViewer::ID_PRINT_IMAGE, MENU_AUTOGRAY)
    FXMenuCommand.new(filemenu, "&Print Vector...\t\tPrint geometry.", nil, @mdiclient, FXGLViewer::ID_PRINT_VECTOR, MENU_AUTOGRAY)
    FXMenuCommand.new(filemenu, "&Dump...\t\tDump widgets.", nil, getApp(), FXApp::ID_DUMP)
    FXMenuCommand.new(filemenu, "&Quit\tCtl-Q\tQuit the application.", nil, getApp(), FXApp::ID_QUIT)

    # Edit Menu
    editmenu = FXMenuPane.new(self)
    FXMenuTitle.new(menubar, "&Edit", nil, editmenu)
    FXMenuCommand.new(editmenu, "Undo")
    FXMenuCommand.new(editmenu, "Copy", nil, @mdiclient, FXGLViewer::ID_COPY_SEL, MENU_AUTOGRAY)
    FXMenuCommand.new(editmenu, "Cut", nil, @mdiclient, FXGLViewer::ID_CUT_SEL, MENU_AUTOGRAY)
    FXMenuCommand.new(editmenu, "Paste", nil, @mdiclient, FXGLViewer::ID_PASTE_SEL, MENU_AUTOGRAY)
    FXMenuCommand.new(editmenu, "Delete", nil, @mdiclient, FXGLViewer::ID_DELETE_SEL, MENU_AUTOGRAY)

    # File manipulation
    FXButton.new(toolbar, "\tNew\tCreate new document.", newdoc, nil, 0,
      FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    openBtn = FXButton.new(toolbar, "\tOpen\tOpen document file.", opendoc, nil, 0,
      FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    openBtn.connect(SEL_COMMAND, method(:onCmdOpen))
    FXButton.new(toolbar, "\tSave\tSave document.", savedoc, nil, 0,
      FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    FXButton.new(toolbar, "\tSave As\tSave document to another file.",
      saveasdoc, nil, 0, FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    FXButton.new(toolbar, "\tNew Folder\tNo comment", loadIcon("newfolder"),
      nil, 0, FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)

    # Print
    FXFrame.new(toolbar,
      LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT, :width => 4, :height => 20)
    FXButton.new(toolbar, "\tPrint Image\tPrint shapshot image.",
      loadIcon("printicon"), @mdiclient, FXGLViewer::ID_PRINT_IMAGE,
      BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    # Editing
    FXFrame.new(toolbar,
      LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT, :width => 4, :height => 20)
    FXButton.new(toolbar, "\tCut", loadIcon("cut"), @mdiclient,
      FXGLViewer::ID_CUT_SEL, (BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|
      LAYOUT_TOP|LAYOUT_LEFT))
    FXButton.new(toolbar, "\tCopy", loadIcon("copy"), @mdiclient,
      FXGLViewer::ID_COPY_SEL, (BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|
      LAYOUT_TOP|LAYOUT_LEFT))
    FXButton.new(toolbar, "\tPaste", loadIcon("paste"), @mdiclient,
      FXGLViewer::ID_PASTE_SEL, (BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|
      LAYOUT_TOP|LAYOUT_LEFT))
    # Projections
    FXFrame.new(toolbar, (LAYOUT_TOP|LAYOUT_LEFT|
      LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT), :width => 8, :height => 20)
    FXButton.new(toolbar, "\tPerspective\tSwitch to perspective projection.",
      loadIcon("perspective"), @mdiclient, FXGLViewer::ID_PERSPECTIVE,
      BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    FXButton.new(toolbar, "\tParallel\tSwitch to parallel projection.",
      loadIcon("parallel"), @mdiclient, FXGLViewer::ID_PARALLEL,
      BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    # Shading model
    FXFrame.new(toolbar, (LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FIX_WIDTH|
      LAYOUT_FIX_HEIGHT), :width => 8, :height => 20)
    FXButton.new(toolbar, "\tNo shading\tTurn light sources off.",
      loadIcon("nolight"), @mdiclient, FXGLShape::ID_SHADEOFF,
      BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    FXButton.new(toolbar, "\tFlat shading\tTurn on faceted (flat) shading.",
      loadIcon("light"), @mdiclient, FXGLShape::ID_SHADEON,
      BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    FXButton.new(toolbar, "\tSmooth shading\tTurn on smooth shading.",
      loadIcon("smoothlight"), @mdiclient, FXGLShape::ID_SHADESMOOTH,
      BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    FXFrame.new(toolbar, (LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FIX_WIDTH|
      LAYOUT_FIX_HEIGHT), :width => 8, :height => 20)
    FXToggleButton.new(toolbar, "\tToggle Light\tToggle light source.", nil,
      loadIcon("nolight"), loadIcon("light"), nil, 0,
      FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    # View orientation
    FXFrame.new(toolbar, (LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FIX_WIDTH|
      LAYOUT_FIX_HEIGHT), :width => 8, :height => 20)
    FXButton.new(toolbar, "\tFront View\tView objects from the front.",
      loadIcon("frontview"), @mdiclient, FXGLViewer::ID_FRONT,
      BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    FXButton.new(toolbar, "\tBack View\tView objects from behind.",
      loadIcon("backview"), @mdiclient, FXGLViewer::ID_BACK,
      BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    FXButton.new(toolbar, "\tLeft View\tView objects from the left.",
      loadIcon("leftview"), @mdiclient, FXGLViewer::ID_LEFT,
      BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    FXButton.new(toolbar, "\tRight View\tView objects from the right.",
      loadIcon("rightview"), @mdiclient, FXGLViewer::ID_RIGHT,
      BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    FXButton.new(toolbar, "\tTop View\tView objects from the top.",
      loadIcon("topview"), @mdiclient, FXGLViewer::ID_TOP,
      BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    FXButton.new(toolbar, "\tBottom View\tView objects from below.",
      loadIcon("bottomview"), @mdiclient, FXGLViewer::ID_BOTTOM,
      BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    # Miscellaneous buttons
    FXFrame.new(toolbar, (LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FIX_WIDTH|
      LAYOUT_FIX_HEIGHT), :width => 8, :height => 20)
    FXButton.new(toolbar, nil, loadIcon("zoom"), nil, 0,
      FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    FXButton.new(toolbar, "\tColors\tDisplay color dialog.",
      loadIcon("colorpal"), colordlg, FXWindow::ID_SHOW,
      FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    FXButton.new(toolbar, nil, loadIcon("camera"), nil, 0,
      FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    FXButton.new(toolbar, nil, loadIcon("foxicon"), nil, 0,
      FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    # Dangerous delete a bit on the side
    FXFrame.new(toolbar, (LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FIX_WIDTH|
      LAYOUT_FIX_HEIGHT), :width => 10, :height => 20)
    FXButton.new(toolbar, "\tDelete\tDelete the selected object.",
      loadIcon("kill"), @mdiclient, FXGLViewer::ID_DELETE_SEL,
      BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
    # View menu
    viewmenu = FXMenuPane.new(self)
    FXMenuTitle.new(menubar, "&View", nil, viewmenu)
    FXMenuCommand.new(viewmenu,
      "Parallel\t\tSwitch to parallel projection.", nil,
      @mdiclient, FXGLViewer::ID_PARALLEL, MENU_AUTOGRAY)
    FXMenuCommand.new(viewmenu,
      "Perspective\t\tSwitch to perspective projection.", nil,
      @mdiclient, FXGLViewer::ID_PERSPECTIVE, MENU_AUTOGRAY)
    FXMenuCommand.new(viewmenu, "&Front\tCtl-F\tFront view.", nil,
      @mdiclient, FXGLViewer::ID_FRONT, MENU_AUTOGRAY)
    FXMenuCommand.new(viewmenu, "&Back\tCtl-B\tBack view.", nil,
      @mdiclient, FXGLViewer::ID_BACK, MENU_AUTOGRAY)
    FXMenuCommand.new(viewmenu, "&Left\tCtl-L\tLeft view.", nil,
      @mdiclient, FXGLViewer::ID_LEFT, MENU_AUTOGRAY)
    FXMenuCommand.new(viewmenu, "&Right\tCtl-R\tRight view.", nil,
      @mdiclient, FXGLViewer::ID_RIGHT, MENU_AUTOGRAY)
    FXMenuCommand.new(viewmenu, "&Top\tCtl-T\tTop view.", nil,
      @mdiclient, FXGLViewer::ID_TOP, MENU_AUTOGRAY)
    FXMenuCommand.new(viewmenu, "&Bottom\tCtl-K\tBottom view.", nil,
      @mdiclient, FXGLViewer::ID_BOTTOM, MENU_AUTOGRAY)
    FXMenuCommand.new(viewmenu, "F&it\t\tFit to view.", nil,
      @mdiclient, FXGLViewer::ID_FITVIEW, MENU_AUTOGRAY)
    FXMenuCommand.new(viewmenu,
      "R&eset\tCtl-G\tReset all viewing parameters", nil,
      @mdiclient, FXGLViewer::ID_RESETVIEW, MENU_AUTOGRAY)
    FXMenuCommand.new(viewmenu, "Zoom...\t\tZoom in on area", nil,
      @mdiclient, FXGLViewer::ID_LASSO_ZOOM, MENU_AUTOGRAY)
    FXMenuCommand.new(viewmenu, "Select...\t\tZoom in on area", nil,
      @mdiclient, FXGLViewer::ID_LASSO_SELECT, MENU_AUTOGRAY)
    # Rendering menu
    rendermenu = FXMenuPane.new(self)
    FXMenuTitle.new(menubar, "&Rendering", nil, rendermenu)
    FXMenuCommand.new(rendermenu, "Points\t\tRender as points.", nil,
      @mdiclient, FXGLShape::ID_STYLE_POINTS, MENU_AUTOGRAY)
    FXMenuCommand.new(rendermenu, "Wire Frame\t\tRender as wire frame.", nil,
      @mdiclient, FXGLShape::ID_STYLE_WIREFRAME, MENU_AUTOGRAY)
    FXMenuCommand.new(rendermenu, "Surface \t\tRender solid surface.", nil,
      @mdiclient, FXGLShape::ID_STYLE_SURFACE, MENU_AUTOGRAY)
    FXMenuCommand.new(rendermenu,
      "Bounding Box\t\tRender bounding box only.", nil,
      @mdiclient, FXGLShape::ID_STYLE_BOUNDINGBOX, MENU_AUTOGRAY)
    # Window menu
    windowmenu = FXMenuPane.new(self)
    FXMenuTitle.new(menubar,"&Windows", nil, windowmenu)
    newViewerCmd = FXMenuCommand.new(windowmenu, "New Viewer\t\tCreate new viewer window.")
    newViewerCmd.connect(SEL_COMMAND) do
      mdichild = FXMDIChild.new(@mdiclient, "GL Viewer #{@count}", @mdiicon,
        @mdimenu, MDI_NORMAL, 30, 30, 300, 200)
      view = FXGLViewer.new(mdichild, @glvisual, self, ID_GLVIEWER)
      view.scene = @scene
      mdichild.create
      @count += 1
    end
    FXMenuCommand.new(windowmenu,
      "Tile Horizontally\t\tTile windows horizontally.", nil,
      @mdiclient, FXMDIClient::ID_MDI_TILEHORIZONTAL)
    FXMenuCommand.new(windowmenu,
      "Tile Vertically\t\tTile windows vertically.", nil,
      @mdiclient, FXMDIClient::ID_MDI_TILEVERTICAL)
    FXMenuCommand.new(windowmenu, "Cascade\t\tCascade windows.", nil,
      @mdiclient, FXMDIClient::ID_MDI_CASCADE)
    FXMenuCommand.new(windowmenu, "Toolbar", nil,
      toolbar, FXWindow::ID_TOGGLESHOWN)
    FXMenuCommand.new(windowmenu, "Control panel", nil,
      panels, FXWindow::ID_TOGGLESHOWN)
    FXMenuCommand.new(windowmenu,
      "Delete\t\tDelete current viewer window.", nil,
      @mdiclient, FXMDIClient::ID_MDI_CLOSE)
    sep1 = FXMenuSeparator.new(windowmenu)
    sep1.setTarget(@mdiclient)
    sep1.setSelector(FXMDIClient::ID_MDI_ANY)
    FXMenuCommand.new(windowmenu, nil, nil, @mdiclient, FXMDIClient::ID_MDI_1)
    FXMenuCommand.new(windowmenu, nil, nil, @mdiclient, FXMDIClient::ID_MDI_2)
    FXMenuCommand.new(windowmenu, nil, nil, @mdiclient, FXMDIClient::ID_MDI_3)
    FXMenuCommand.new(windowmenu, nil, nil, @mdiclient, FXMDIClient::ID_MDI_4)
    # Help menu
    helpmenu = FXMenuPane.new(self)
    FXMenuTitle.new(menubar, "&Help", nil, helpmenu, LAYOUT_RIGHT)
    aboutCmd = FXMenuCommand.new(helpmenu,
      "&About FOX...\t\tDisplay FOX about panel.")
    aboutCmd.connect(SEL_COMMAND) {
      FXMessageBox.information(self, MBOX_OK, "About FOX",
        "FOX OpenGL Example.\nCopyright (C) 1998 Jeroen van der Zijp")
    }
    # Make a tool tip
    FXToolTip.new(getApp(), 0)
    # The status bar shows our mode
    statusbar.statusLine.target = self
    statusbar.statusLine.selector = ID_QUERY_MODE
    # Make a scene!
    @scene = FXGLGroup.new
    gp2 = FXGLGroup.new
    @scene.append(gp2)
    sphere  = FXGLSphere.new(1.0, 1.0, 0.0, 0.5)
    sphere2 = FXGLSphere.new(0.0, 0.0, 0.0, 0.8)
    sphere.tipText = "Sphere"
    gp2.append(FXGLCube.new(-1.0, 0.0, 0.0,  1.0, 1.0, 1.0))
    gp2.append(FXGLCube.new( 1.0, 0.0, 0.0,  1.0, 1.0, 1.0))
    gp2.append(FXGLCube.new( 0.0,-1.0, 0.0,  1.0, 1.0, 1.0))
    gp2.append(FXGLCube.new( 0.0, 1.0, 0.0,  1.0, 1.0, 1.0))
    gp2.append(FXGLCone.new(1.0,-1.5, 0.0, 1.0, 0.5))
    gp2.append(FXGLCylinder.new(-1.0, 0.5, 0.0, 1.0, 0.5))
    gp2.append(sphere)
    gp2.append(sphere2)
    # Add scene to GL viewer
    viewer.scene = @scene
  end

  # Create and show the main window
  def create
    super
    show(PLACEMENT_SCREEN)
  end

  def onCmdOpen(sender, sel, ptr)
    dlg = FXFileDialog.new(self, "Open some file")
    dlg.setPatternList([
      "All Files (*)",
      "C++ Source Files (*.[Cc][Pp][Pp])",
      "C++ Header Files (*.[Hh])",
      "Object Files (*.o)",
      "HTML Files (*.[Hh][Tt][Mm][Ll]"])
    if dlg.execute() != 0
      FXMessageBox.information(self, MBOX_OK, "Huzzah!", "You selected the file: #{dlg.filename}")
    end
    return 1
  end

  def onUpdMode(sender, sel, ptr)
    sender.text = "Ready."
  end

  # When the user right-clicks in the GLViewer background, the viewer first
  # sends a SEL_COMMAND message with identifier FXWindow::ID_QUERY_MENU to
  # the selected GLObject (if any). If that message isn't handled, it tries
  # to send it to the GLViewer's target (which in our case is the main
  # window).
  def onQueryMenu(sender, sel, event)
    pane = FXMenuPane.new(self)
    FXMenuCommand.new(pane, "Parallel\t\tSwitch to parallel projection.",
      nil, sender, FXGLViewer::ID_PARALLEL)
    FXMenuCommand.new(pane, "Perspective\t\tSwitch to perspective projection.",
      nil, sender, FXGLViewer::ID_PERSPECTIVE)
    FXMenuSeparator.new(pane)
    FXMenuCommand.new(pane, "&Front\t\tFront view.", nil,
      sender,FXGLViewer::ID_FRONT)
    FXMenuCommand.new(pane, "&Back\t\tBack view.", nil,
      sender, FXGLViewer::ID_BACK)
    FXMenuCommand.new(pane, "&Left\t\tLeft view.", nil,
      sender, FXGLViewer::ID_LEFT)
    FXMenuCommand.new(pane, "&Right\t\tRight view.", nil,
      sender, FXGLViewer::ID_RIGHT)
    FXMenuCommand.new(pane, "&Top\t\tTop view.", nil,
      sender, FXGLViewer::ID_TOP)
    FXMenuCommand.new(pane, "&Bottom\t\tBottom view.", nil,
      sender, FXGLViewer::ID_BOTTOM)
    FXMenuSeparator.new(pane)
    FXMenuCommand.new(pane, "F&it\t\tFit to view.", nil,
      sender, FXGLViewer::ID_FITVIEW)
    FXMenuCommand.new(pane, "R&eset\t\tReset all viewing parameters", nil,
      sender, FXGLViewer::ID_RESETVIEW)
    pane.create
    pane.popup(nil, event.root_x, event.root_y)
    getApp().runModalWhileShown(pane)
    return 1
  end
end

if __FILE__ == $0
  # Make application
  application = FXApp.new("GLViewer", "FoxTest")
  application.threadsEnabled = false

  # Make window
  GLViewWindow.new(application)

  # Create the application windows
  application.create

  # Run the application
  application.run
end