Class LogoGUI
In: LogoGUI.rb
Parent: FXMainWindow

The default RubyLogo application graphical user interface. It provides the following interface components:

  • a drawing area for a Turtle object
  • a text window in which logo programs can be edited
  • an input field to execute drawing commands immediately when they are entered
  • a terminal window which displays errors encountered by the code parser
  • a button bar which offers options to load, save and execute Logo programs
  • a status line providing information about the current state of the drawing turtle

The interface initializes a Turtle and an associated Parser and manages the communication between its input/output components and these modules.

Methods

Constants

DEG = Math::PI / 180   Converts degrees to radians

Public Class methods

Initialize a new RubyLogo application window and its components.

[Source]

# File LogoGUI.rb, line 39
  def initialize(app)
    begin
      super(app, "RubyLogo", FXPNGIcon.new(app, File.open("icon.png", "rb"){|f| f.read},
        0, 0, 40, 40), nil, DECOR_ALL, 50, 50, 1000, 650)
    rescue StandardError
      super(app, "RubyLogo", nil, nil, DECOR_ALL, 50, 50, 1000, 650)
    end
    FXToolTip.new(app)

    # Menu bar and buttons at the top of the main window
    menubar = FXMenuBar.new(self, LAYOUT_SIDE_TOP|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH,
      0,0,0,0,0,0,0,0, 20)

    begin
      pushNew = FXButton.new(menubar, "&New\t\tCreate new logo script",
        FXPNGIcon.new(app, File.open("new.png", "rb"){|f| f.read}, 0, 0, 40, 40),
        nil, 0, FRAME_RAISED|BUTTON_TOOLBAR|ICON_BEFORE_TEXT)
    rescue StandardError
      pushNew = FXButton.new(menubar, "&New\t\tCreate new logo script", nil,
        nil, 0, FRAME_RAISED|BUTTON_TOOLBAR|ICON_BEFORE_TEXT)
    end
    pushNew.connect(SEL_COMMAND, method(:btnNew))
    pushNew.tipText = "New"
    pushNew.backColor = FXRGB(205,205,205)

    begin
      pushLoad = FXButton.new(menubar, "&Load\t\tLoad logo script",
        FXPNGIcon.new(app, File.open("load.png", "rb"){|f| f.read}, 0, 0, 40, 40),
        nil, 0, FRAME_RAISED|BUTTON_TOOLBAR|ICON_BEFORE_TEXT)
    rescue StandardError
      pushLoad = FXButton.new(menubar, "&Load\t\tLoad logo script", nil,
        nil, 0, FRAME_RAISED|BUTTON_TOOLBAR|ICON_BEFORE_TEXT)
    end
    pushLoad.connect(SEL_COMMAND, method(:btnLoad))
    pushLoad.tipText = "Load Script"
    pushLoad.backColor = FXRGB(205,205,205)

    begin
      pushSave = FXButton.new(menubar, "&Save\t\tSave logo script",
        FXPNGIcon.new(app, File.open("save.png", "rb"){|f| f.read}, 0, 0, 40, 40),
        nil, 0, FRAME_RAISED|BUTTON_TOOLBAR|ICON_BEFORE_TEXT)
    rescue StandardError
      pushSave = FXButton.new(menubar, "&Save\t\tSave logo script", nil,
        nil, 0, FRAME_RAISED|BUTTON_TOOLBAR|ICON_BEFORE_TEXT)
    end
    pushSave.connect(SEL_COMMAND, method(:btnSave))
    pushSave.tipText = "Save Script"
    pushSave.backColor = FXRGB(205,205,205)

    begin
      pushRun = FXButton.new(menubar, "&Run\t\tRun logo script",
        FXPNGIcon.new(app, File.open("run.png", "rb"){|f| f.read}, 0, 0, 40, 40),
        nil, 0, FRAME_RAISED|BUTTON_TOOLBAR|ICON_BEFORE_TEXT)
    rescue StandardError
      pushRun = FXButton.new(menubar, "&Run\t\tRun logo script", nil,
        nil, 0, FRAME_RAISED|BUTTON_TOOLBAR|ICON_BEFORE_TEXT)
    end
    pushRun.connect(SEL_COMMAND, method(:btnRun))
    pushRun.tipText = "Run"
    pushRun.backColor = FXRGB(205,205,205)

    begin
      pushExit = FXButton.new(menubar, "&Exit\t\tExit program",
        FXPNGIcon.new(app, File.open("exit.png", "rb"){|f| f.read}, 0, 0, 40, 40),
        nil, 0, FRAME_RAISED|BUTTON_TOOLBAR|LAYOUT_RIGHT|ICON_BEFORE_TEXT)
    rescue StandardError
      pushExit = FXButton.new(menubar, "&Exit\t\tExit program", nil,
        nil, 0, FRAME_RAISED|BUTTON_TOOLBAR|LAYOUT_RIGHT|ICON_BEFORE_TEXT)
    end
    pushExit.connect(SEL_COMMAND) {exit}
    pushExit.tipText = "Exit"
    pushExit.backColor = FXRGB(205,205,205)

    begin
      pushHelp = FXButton.new(menubar, "&Help\t\tShow help page",
        FXPNGIcon.new(app, File.open("help.png", "rb"){|f| f.read}, 0, 0, 40, 40),
        nil, 0, FRAME_RAISED|BUTTON_TOOLBAR|LAYOUT_RIGHT|ICON_BEFORE_TEXT)
    rescue StandardError
      pushHelp = FXButton.new(menubar, "&Help\t\tShow help page", nil,
        nil, 0, FRAME_RAISED|BUTTON_TOOLBAR|LAYOUT_RIGHT|ICON_BEFORE_TEXT)
    end
    pushHelp.connect(SEL_COMMAND, method(:btnHelp))
    pushHelp.tipText = "Help"
    pushHelp.backColor = FXRGB(205,205,205)

    # Status bar at the bottom of the main window
    @statusbar = FXStatusBar.new(self, LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X)
    @statusbar.statusLine.normalText = "RubyLogo"
    @turtleinfo = FXLabel.new(@statusbar, "", nil,
      LAYOUT_RIGHT|LAYOUT_FILL_Y|FRAME_SUNKEN, 0, 0)

    # Layout composites for the main area of the user interface
    splith = FXSplitter.new(self,
      LAYOUT_FILL_X|LAYOUT_FILL_Y|SPLITTER_HORIZONTAL|SPLITTER_TRACKING, 0, 0)
    splitv = FXSplitter.new(splith,
      LAYOUT_FILL_X|SPLITTER_VERTICAL|SPLITTER_TRACKING|SPLITTER_REVERSED, 0, 0, 400)
    editbox = FXPacker.new(splitv, LAYOUT_FILL_X|LAYOUT_FILL_Y,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

    # Input line which sends entered Logo commands immediately to the parser
    @cmdline = FXTextField.new(editbox, 0, nil, 0,
      LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|FRAME_GROOVE|TEXTFIELD_ENTER_ONLY)
    @cmdline.backColor = FXRGB(255, 255, 255)
    @cmdline.textColor = FXRGB(0, 0, 0)
    @cmdline.connect(SEL_COMMAND, method(:lineEval))

    # Editor window for Logo programs
    @editor = FXText.new(editbox, nil, 0,
      LAYOUT_FILL_X|LAYOUT_FILL_Y|VSCROLLER_ALWAYS)
    @editor.backColor = FXRGB(255, 255, 255)
    @editor.textColor = FXRGB(0, 0, 0)
    @editor.tabColumns = 2

    # Read-only terminal area for status and error messages
    @terminal = FXText.new(splitv, nil, 0,
      LAYOUT_FILL_X|VSCROLLER_ALWAYS|TEXT_READONLY, 0, 0, 400, 100)
    @terminal.backColor = FXRGB(50, 50, 50)
    @terminal.textColor = FXRGB(0, 255, 0)
    @terminal.tabColumns = 2

    # Drawing area for the turtle
    @scrollwin = FXScrollWindow.new(splith, LAYOUT_FILL_X|LAYOUT_FILL_Y)
    scrollfix = FXPacker.new(@scrollwin, LAYOUT_FILL_X|LAYOUT_FILL_Y,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
    # scrollwin has wrong range associated to its scrollbars
    # manually update to known real values
    @scrollwin.verticalScrollBar.range = 5000
    @scrollwin.horizontalScrollBar.range = 5000

    replaceTurtle # create new turtle for the drawing area
    @turtlearea = FXCanvas.new(scrollfix, nil, 0,
      LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT, 0, 0, 5000, 5000)
    @turtlearea.connect(SEL_PAINT) { |sender, sel, event|
      FXDCWindow.new(@turtlearea, event) { |terrarium|
        # drawing area background
        terrarium.foreground = @tortoise.background
        terrarium.fillRectangle(event.rect.x, event.rect.y, event.rect.w, event.rect.h)
        # lines drawn by the turtle
        @tortoise.getLines { |line|
          terrarium.foreground = line.color # defaults to 'black' if not a valid color
          begin
            # move coordinates to adapt to drawing area
            terrarium.drawLine(line.startx+2500, 2500-line.starty,
              line.endx+2500, 2500-line.endy)
          rescue RangeError
            @tortoise.log "ERROR: Drawing coordinates out of range"
            writeLog @tortoise.messages
          end
        }
        drawTurtle(terrarium)         # draw turtle at its current position
      }
    }
    refreshTurtle                     # update status for turtle
  end

Public Instance methods

Action when the button ‘Help’ is pushed. Open a LogoGUIInfoBox message window containing helpful information.

[Source]

# File LogoGUI.rb, line 263
  def btnHelp(sender, sel, event)
    infobox = LogoGUIInfoBox.new(self)
    infobox.execute(PLACEMENT_SCREEN)
  end

Action when the button ‘Load’ is pushed. Open a file browser and load the selected file into the editor area.

[Source]

# File LogoGUI.rb, line 207
  def btnLoad(sender, sel, event)
    openDialog = FXFileDialog.new(self, "Load Script")
    openDialog.patternList = ["Logo (*.logo)", "All Files (*)"]
    if openDialog.execute(PLACEMENT_SCREEN) != 0
      btnNew(sender, sel, event) # clear all before loading new file
      begin
        File.open(openDialog.filename) { |file| @editor.setText(file.read) }
      rescue StandardError => error
        FXMessageBox.error(self, MBOX_OK, "Error", "#{error}")
      end
    end
    return 1
  end

Action when the button ‘New’ is pushed. Reset the program to its startup state. Replace Turtle and Parser with new instances.

[Source]

# File LogoGUI.rb, line 198
  def btnNew(sender, sel, event)
    @editor.setText("")
    replaceTurtle
    refreshTurtle
  end

Action when the button ‘Run’ is pushed. Let the Parser evaluate the current contents of the editor window and display the corresponding actions of the Turtle.

[Source]

# File LogoGUI.rb, line 253
  def btnRun(sender, sel, event)
    replaceTurtle
    @statusbar.statusLine.text = "Please wait. Parser is busy."
    @lineparser.parse(@editor.text)
    refreshTurtle
  end

Action when the button ‘Save’ is pushed. Open a file browser and save the contents of the editor area to the entered file.

[Source]

# File LogoGUI.rb, line 224
  def btnSave(sender, sel, event)
    saveDialog = FXFileDialog.new(self, "Save Script")
    saveDialog.patternList = ["Logo (*.logo)", "All Files (*)"]
    if saveDialog.execute(PLACEMENT_SCREEN) != 0
      if File.exists? saveDialog.filename
        if MBOX_CLICKED_NO == FXMessageBox.warning(self, MBOX_YES_NO,
          "Overwrite File?", "File #{saveDialog.filename} already exists. Overwrite?")
          return 1
        end
      end
      begin
        File.open(saveDialog.filename, "w") { |file| file.write(@editor.text) }
      rescue Errno::ENOSPC => error
        File.delete(saveDialog.filename) if File.exist?(saveDialog.filename)
        FXMessageBox.error(self, MBOX_OK, "Error", "#{error}")
      rescue EOFError => error
        File.delete(saveDialog.filename) if File.exist?(saveDialog.filename)
        FXMessageBox.error(self, MBOX_OK, "Error", "#{error}")
      rescue StandardError => error
        FXMessageBox.error(self, MBOX_OK, "Error", "#{error}")
      end
    end
    return 1
  end

Draw the Turtle to the drawing area. The Turtle has the shape of an arrowhead and is drawn in it’s current pen color.

[Source]

# File LogoGUI.rb, line 291
  def drawTurtle(drawarea)
    if @tortoise.visible
      drawarea.foreground = @tortoise.color
      # rotate and move turtle to adapt to drawing area coordinate system
      dr = @tortoise.direction + 180
      x0 = @tortoise.posx+2500
      y0 = 2500-@tortoise.posy
      x1 = x0 + 20 * Math.cos((dr-45) * DEG)
      y1 = y0 + 20 * Math.sin((dr-45) * DEG)
      x2 = x0 + 20 * Math.cos((dr+225) * DEG)
      y2 = y0 + 20 * Math.sin((dr+225) * DEG)
      x3 = x0 + 20 * Math.cos((dr+90) * DEG)
      y3 = y0 + 20 * Math.sin((dr+90) * DEG)
      begin
        drawarea.drawLine(x0, y0, x1, y1)
        drawarea.drawLine(x0, y0, x2, y2)
        drawarea.drawLine(x1, y1, x3, y3)
        drawarea.drawLine(x2, y2, x3, y3)
      rescue RangeError
        @tortoise.log "ERROR: Turtle coordinates out of range"
        writeLog @tortoise.messages
      end
    end
  end

Evaluate the contents of the input line

[Source]

# File LogoGUI.rb, line 270
  def lineEval(sender, sel, event)
    @editor.setText(@editor.text.chomp+"\n"+event+"\n")
    @cmdline.setText("")
    @editor.makePositionVisible(@editor.getLength)
    @lineparser.parse(event+"\n")
    refreshTurtle
    @tortoise.clearMessages
  end

Update the drawn image, message log and status line information. Check if the color attribute of the Turtle is a compatible color string and replace it if necessary.

[Source]

# File LogoGUI.rb, line 330
  def  refreshTurtle
    # GUI defaults to 'black' for unknown color names. Let's tell our turtle
    @tortoise.color = "black" if fxcolorfromname(@tortoise.color) == 0
    # Default background should be white. Catch invalid names and replace
    @tortoise.background = "white" if @tortoise.background != "black" &&
      fxcolorfromname(@tortoise.background) == 0
    # don't show color if pen is up
    @tortoise.pen ? currentcol = @tortoise.color : currentcol = "NONE"
    @turtleinfo.text = sprintf("Position: %.2f, %.2f  Direction: %.2f°  Color: \
#{currentcol}  BG: #{@tortoise.background}",
@tortoise.posx, @tortoise.posy, @tortoise.direction)
    writeLog @tortoise.messages
    @turtlearea.update
  end

Replace the Turtle with a new object instance and associate a new Parser object to it.

[Source]

# File LogoGUI.rb, line 319
  def replaceTurtle
    @terminal.setText("")
    @tortoise = LogoTurtle.new(0, 0, 0, "black", "white")
    @lineparser = LogoParser.new(@tortoise)
    @scrollwin.setPosition(-2210, -2210)
  end

Enter the given string into the read-only output window.

[Source]

# File LogoGUI.rb, line 281
  def writeLog(messages)
    messages.each do |line|
      @terminal.appendText(line+"\n") if line =~ /ERROR/ # show only error messages
      @terminal.makePositionVisible(@terminal.getLength)
    end
  end

[Validate]