!7 set flag Reparse. import javax.swing.filechooser.*; !include #1031138 // Smarty AI Include sbool showHelpOnStart; static int spacing = 20; static JFrame mainFrame; static LL gridComponents; static JComponent mainGrid; static JCheckBoxMenuItem miShowLocations; static SingleComponentPanel scpMainGrid, scpVisionConfig, scpWheelsConfig, scpCurrentSmarty; static JFrame historyFrame, memoryFrame; static JSpinner worldSizeSpinner; static new Smarty smarty; // AI instance sS wallSymbol = unicode_blackSquare(), noWallSymbol = unicode_dottedSquare(); static Color lightBlue = colorFromHex("4169e1"); sS locationLetters = charRange('A', 'Z') + "[\\]^_`" + charRange('a', 'z') + charRange(123, 128); sS smartyUnicode = basicUnicodeSmiley(); static int smartyFontSize = 40; sS windowsFontID = #1400468; sS frameIconID = #1102983; sS worldFileExtension = "WLD"; sS bodyFileExtension = "BDY"; sS historyHelp = unindent_mls([[ A percept is the stimulus or simultaneous combination of stimuli observed. An action is the response or simultaneous combination of responses produced. Binon # numbers refer to the binons in memory that represent the percepts, actions, expectations, and perceptual or action sequences. P = Percept, E = Expectation, PP = Perceptual sequence A = Action, AA = Action sequence Read the paper: "The Perception-Action Hierarchy and its Implementation Using Binons (Binary Neurons)" on the www.adaptroninc.com website for more information. ]]); sbool showTabs = true; static int defaultWorldSize = 7, maxWorldSize = 8; svoid rearrangeLetters { int iLetter = 0; for (int y = 0; y < state.gridRows; y++) for (int x = 0; x < state.gridCols; x++) { JCell cell = getCell(x, y); if (cell.obstacle()) cell.letter = ' '; else cell.letter = locationLetters.charAt(iLetter++); } } sclass VisionMode { sS nextSquare = "next square"; sS distanceToWall = "distance to wall"; sS wallOrNot = "wall or not"; } sclass CameraDirection { sS down = "down"; sS left = "left"; sS right = "right"; sS inFront = "in front"; sS behind = "behind"; static LS all = ll(down, left, right, inFront, behind); } concept SmartyBody { S name = "Smarty0"; new Set cameraDirections; S visionMode = VisionMode.nextSquare; // see VisionMode new Set wheels; // for saving S wheelsFlag(S flag) { ret wheels.contains(flag) ? "1" : "0"; } } concept State { float fontScale = 1.25f; S worldName = "empty"; int gridRows = defaultWorldSize, gridCols = defaultWorldSize; // actually these are always identical, could merge into one bool showLocations = true; int smartyX, smartyY; Rect selection = rect(1, 1, 1, 1); Pt cursorDirection = pt(1, 0); new BitSet obstacles; new Ref body; void setSelection(Rect r) { if (!cset_trueIfChanged(this, selection := r)) ret; if (selection.h == 1 && selection.w > 1) _setField(cursorDirection := pt(1, 0)); else if (selection.w == 1 && selection.h > 1) _setField(cursorDirection := pt(0, 1)); repaint(mainGrid); } void clearSelectedArea { fillSelection(false); } void invertSelection { JCell cell1 = getCell(selection.x, selection.y); fillSelection(!cell1.obstacle()); } void fillSelection(bool b) { for (Pt p : pointsInRect(selection)) getCell(p.x, p.y).setObstacle(b); rearrangeLetters(); moveCursor(); } void moveCursor { setCursor(selection.x+cursorDirection.x, selection.y+cursorDirection.y); } void setCursor(int x, int y) { setSelection(rect(mod(x, gridCols), mod(y, gridRows), 1, 1)); } void moveCursor(int dx, int dy) { _setField(cursorDirection := pt(dx, dy)); setCursor(selection.x+cursorDirection.x, selection.y+cursorDirection.y); } void placeSmarty { getCell(selection.x, selection.y).setObstacle(false); cset(this, smartyX := selection.x, smartyY := selection.y); moveCursor(); } } // end of State static State state; sclass JCell > JComponent { int x, y; char letter; *(int *x, int *y) {} public void paint(Graphics _g) { Graphics2D g = cast _g; int w = getWidth(), h = getHeight(); Color color = Color.white; if (state.selection.contains(x, y)) color = lightBlue; else if (obstacle()) color = Color.black; fillRect(g, 0, 0, w, h, color); if (state.showLocations && !obstacle()) { temp tempSetFont(g, loadFont_cached(windowsFontID)); drawTextWithTopLeftCornerAt(g, str(letter), Pt(2, 0), Color.black); } if (state.smartyX == x && state.smartyY == y) { temp tempSetFontSize(g, smartyFontSize); drawCenteredText(g, smartyUnicode, 0, 0, w, h, Color.black); } } int index() { ret y*state.gridCols+x; } bool obstacle() { ret main contains(state.obstacles, index()); } void setObstacle(bool b) { setBit(state.obstacles, index(), b); state.change(); } { addMouseListener(new MouseAdapter { public void mousePressed(MouseEvent e) { main requestFocus(mainGrid); if (!isLeftButton(e)) ret; state.setSelection(new Rect(x, y, 1, 1)); } }); addMouseMotionListener(new MouseMotionAdapter { public void mouseDragged(MouseEvent e) { _print("Drag in " + x + "/" + y + ": " + e.getPoint()); Component dest = componentAtScreenLocationInWindow(mainFrame, e); if (dest cast JCell) { _print("Target: " + dest); state.setSelection(rectFromPointsInclusiveSorted(x, y, dest.x, dest.y)); } } }); } toString { ret x + "/" + y; } } static JCell getCell(int x, int y) { ret gridComponents.get(y).get(x); } svoid makeMainGrid { gridComponents = map(iotaZeroList(state.gridRows), y -> map(iotaZeroList(state.gridCols), x -> new JCell(x, y))); mainGrid = setFocusable(setBackground(Color.black, hvgrid(gridComponents, 1))); scpMainGrid.setComponent(mainGrid); rearrangeLetters(); } svoid setWorldSize(int size) { state.obstacles.clear(); state.gridCols = state.gridRows = size; setSpinnerValue(worldSizeSpinner, size); state.selection = rect(1, 1); state.smartyX = state.smartyY = 0; state.change(); makeMainGrid(); } p { autoRestart(2.0); setProgramDir(print("Using directory: ", mainDir())); db(); uniq(SmartyBody); // make at least one instance state = uniq(State); if (!state.body.has()) cset(state, body := conceptWhere(SmartyBody)); // default body jtattoo_mcWin(); state.fontScale = 1.5f; // XXX //set swingFontScale_debug; swingFontScale(state.fontScale); loadLibrary(windowsFontID); defaultFrameIcon(frameIconID); mainFrame = showFrame("Smarty"); addMenu(mainFrame, "Worlds", "New... (Ctrl+N)", r newWorld, "Retrieve... (Ctrl+R)", r loadWorldDialog, "Save... (Ctrl+S)", r saveWorldDialog, "---", "Exit", rThread killVM); registerCtrlKey(mainFrame, KeyEvent.VK_N, "New world", r { print("new") }); registerCtrlKey(mainFrame, KeyEvent.VK_R, "Retrieve world", r {}); registerCtrlKey(mainFrame, KeyEvent.VK_S, "Save world", r {}); addMenu(mainFrame, "Bodies", "New... (Ctrl+N)", r newBody, "Retrieve... (Ctrl+R)", r loadBodyDialog, "Save... (Ctrl+S)", r saveBodyDialog, "---", "Exit", rThread killVM); addMenu(mainFrame, "View", miShowLocations = jCheckBoxMenuItem("Locations (Ctrl+L)", state.showLocations, b -> { cset(state, showLocations := b); repaint(mainGrid); })); registerCtrlKey(mainFrame, KeyEvent.VK_L, "Locations", r { cset(state, showLocations := !state.showLocations); setChecked(miShowLocations, state.showLocations); repaint(mainGrid); }); JMenuBar menuBar = getMenuBar(mainFrame); JMenuItem helpItem = jMenuItem("Help F1", rThread showHelp); /*print(minSize := getMinimumSize(helpItem)); print(preferredSize := getPreferredSize(helpItem));*/ addMenuItem(menuBar, jPreferredWidthToMaxWidth(helpItem)); addAndRevalidate(menuBar, jHorizontalGlue()); // spacer registerFunctionKey(mainFrame, 1, rThread showHelp); registerKeyCode(mainFrame, KeyEvent.VK_SPACE, r { state.invertSelection() }); registerKeyCode(mainFrame, KeyEvent.VK_UP, r { state.moveCursor(0, -1) }); registerKeyCode(mainFrame, KeyEvent.VK_LEFT, r { state.moveCursor(-1, 0) }); registerKeyCode(mainFrame, KeyEvent.VK_DOWN, r { state.moveCursor(0, 1) }); registerKeyCode(mainFrame, KeyEvent.VK_RIGHT, r { state.moveCursor(1, 0) }); for (int key = KeyEvent.VK_A; key <= KeyEvent.VK_Z; key++) registerKeyCode(mainFrame, key, r { state.placeSmarty() }); worldSizeSpinner = jSpinner(state.gridCols, 1, maxWorldSize); onChange(worldSizeSpinner, r { int size = intFromSpinner(worldSizeSpinner); if (state.gridCols != size) setWorldSize(size); }); scpCurrentSmarty = singleComponentPanel(); JComponent middleArea = westAndCenterWithMargin(spacing, jvstackWithSpacing(spacing, withTitle("World size (max=\*maxWorldSize*/)", worldSizeSpinner), jbutton("Clear Selected Area", rThread { state.clearSelectedArea() }), withTitle("Current world", disableTextField(jLiveValueTextField(conceptFieldLiveValue worldName(state)))), showTabs ? null : fontSizePlus(2, setForeground(Color.ORANGE, jCenteredLabel("Design or Select a body for Smarty"))), showTabs ? null : jbutton("Design or Select Smarty's Body", r showSmartyConfig), withTitle("Current Smarty", scpCurrentSmarty), ), jvgridWithSpacing(spacing, withTitle("Sensors Values are", jTextArea()), withTitle("Devices Values are", jTextArea()) ) ); JComponent controlArea = northCenterAndSouthWithMargin(spacing, fontSizePlus(2, setForeground(Color.blue, jlabel("Design the World - Read the Help F1"))), middleArea, jCenteredSection("Run Smarty", jflow( jbutton("Go / Continue"), jButton("See History", r showHistory), jbutton("Reset"), jbutton("See Memory", r showMemory), jbutton("EXIT", rThread killVM), )) ); scpMainGrid = singleComponentPanel(); makeMainGrid(); var mainPanel = westAndCenterWithMargins(spacing, jvstackWithSpacing(spacing, jFixedSize(500, scpMainGrid), jhgridWithSpacing(spacing, jlabel("VisionD:"), jlabel("Wheels:")) ), controlArea); if (showTabs) { scpVisionConfig = singleComponentPanel(); scpWheelsConfig = singleComponentPanel(); updateTabs(); setFrameContents(mainFrame, jtabs( "Smarty", mainPanel, "Vision", scpVisionConfig, "Wheels", scpWheelsConfig)); } else setFrameContents(mainFrame, mainPanel); centerPackFrame(mainFrame); if (showHelpOnStart) showHelp(); //hideConsole(); } svoid updateTabs { scpVisionConfig.setComponent(visionConfigPanel(state.body!)); scpWheelsConfig.setComponent(wheelsConfigPanel(state.body!)); scpCurrentSmarty.setComponent(disableTextField(jLiveValueTextField(conceptFieldLiveValue name(state.body!)))); } svoid showHelp() { S text = loadSnippet(#1031105); S heading = firstLine(text); trim(dropFirstLine(text)); showCenterFrame(swingScale(650), swingScale(500), heading, makeUneditableWithTextColor(Color.black, wordWrapTextArea(text))); } // run in Swing thread svoid showHistory { Font ttFont = typeWriterFont(12), helpFont = sansSerifFont(13); if (historyFrame == null) historyFrame = getFrame(showCenterFrame(scalePt(600, 350, swingFontScale()), "Stimulus/Response, Percept/Action History", withMargin(centerAndSouthWithMargin(spacing, jvsplit(0.5, 100, jTextArea(), westAndCenterWithMargin(spacing, setFont(ttFont, topAlignLabel(jMultiLineLabel( linesLL("Step #", "Percept", "Binon #", "", "Expect", "Binon #", "", "Action", "Binon #", "", "Action", "Binon #", "")))), setFont(ttFont, jTextArea())) ), centerAndEastWithMargin( setFont(helpFont, jMultiLineLabel(historyHelp)), jvstackWithSpacing( jButton("See World", r showWorld), jButton("See Memory", r showMemory)) ))))); else frameToFront(historyFrame); } // run in Swing thread svoid showMemory { Font ttFont = typeWriterFont(12), helpFont = sansSerifFont(13); if (memoryFrame == null) memoryFrame = getFrame(showCenterFrame(scalePt(600, 350, swingFontScale()), "Smarty's Memory", jvsplit(0.5, 100, centerAndEastWithMargins( northAndCenterWithMargin( setFont(ttFont, jLabel("bla bla bla")), setFont(ttFont, jTextArea())), jvstackWithSpacing( jButton("See World", r showWorld), jButton("See History", r showHistory)) ), jTextArea(), ) )); else frameToFront(memoryFrame); } svoid showWorld { frameToFront(mainFrame); } static JRadioButton visionModeRadioButton(SmartyBody body, ButtonGroup buttonGroup, S visionMode, S text) { var rb = jradiobutton(buttonGroup, text, eq(body.visionMode, visionMode)); onChange(rb, r { if (isChecked(rb)) cset(body, +visionMode); }); ret rb; } static JComponent bodyNamePanel(SmartyBody body) { ret westAndCenter( jMinWidth(swingScale(250), withLabel("Body name:", jLiveValueTextField_bothWays(conceptFieldLiveValue name(body)))), jpanel()); } static JComponent visionConfigPanel(SmartyBody body) { var buttonGroup = buttonGroup(); ret northAndCenterWithMargins(spacing, bodyNamePanel(body), westAndCenterWithMargin(spacing, jCenteredSection("Camera", withLeftMargin(spacing, jvstackWithSpacing(spacing, itemPlus(jlabel("Select the camera directions"), map(CameraDirection.all, direction -> { var cb = jCheckBox(direction, body.cameraDirections.contains(direction)); onChange(cb, r { addOrRemove(body.cameraDirections, direction, isChecked(cb)); body.change(); }); ret cb; }))))), jCenteredSection("Vision mode", jvstackWithSpacing(spacing, visionModeRadioButton(body, buttonGroup, VisionMode.nextSquare, "Looking at the next square (symbolic values = letter in square)"), withLeftMargin(spacing*2, jMultiLineLabel(unindentMLS(replaceSymbols([[ Square locations are identified with a letter. Looking down provides the letter of the current square. Looking in other directions provides the letter in the next square depending on the direction Smarty is facing. If it is a wall then the symbolic stimulus is a $wallSymbol ]])))), visionModeRadioButton(body, buttonGroup, VisionMode.distanceToWall, "Distance to wall (magnitude values = number of squares)"), withLeftMargin(spacing*2, jMultiLineLabel(unindentMLS([[ Looking at the distance to the nearest wall will provide the number of squares from the current position to the wall in the direction the camera is facing. A wall next to the robot is one square away. Looking down will always return 1. ]]))), visionModeRadioButton(body, buttonGroup, VisionMode.wallOrNot, replaceSymbols("Wall or not (symbolic values = $wallSymbol for a wall and $noWallSymbol for no wall)")), withLeftMargin(spacing*2, jMultiLineLabel(unindentMLS([[ Looking at the next square will either see a wall or an empty square ]]))), )))); } svoid showVisionConfig(SmartyBody body) { showPackedFrame("Configure Smarty's Vision", visionConfigPanel(body)); } static JCheckBox wheelsConfigCheckBox(SmartyBody body, S symbol, S text) { var cb = jCheckBox(text, body.wheels.contains(symbol)); onChange(cb, r { addOrRemove(body.wheels, symbol, isChecked(cb)); body.change(); }); ret cb; } static JComponent wheelsConfigPanel(SmartyBody body) { ret northAndCenterWithMargins(spacing, bodyNamePanel(body), vstackWithSpacing(spacing, westAndCenterWithMargin(spacing, jCenteredSection("Movement", withLeftMargin(spacing, jvstackWithSpacing( jlabel("Select the wheel motions:"), wheelsConfigCheckBox(body, "MoveForward", "Forward = f"), wheelsConfigCheckBox(body, "MoveLeft", "Left = l"), wheelsConfigCheckBox(body, "MoveRight", "Right = r"), wheelsConfigCheckBox(body, "MoveBackward", "Backward = b"), ))), jCenteredSection("Help", withLeftMargin(jMultiLineLabel(unindentMLS([[ Motion in any direction will move one square. If there is a wall in the way it will not move. A "-" indicates no motion. The direction moved depends on the direction Smarty is facing. Smarty will appear as a if it cannot turn. It will then always be facing North (up) so moving right will move to the East (right). ]]))))), westAndCenterWithMargin(spacing, jCenteredSection("Turning", withLeftMargin(spacing, vstackWithSpacing( wheelsConfigCheckBox(body, "TurnRight", "Turn to the right = Rotate clockwise 90 degrees = X"), wheelsConfigCheckBox(body, "TurnLeft", "Turn to the left = Rotate anti-clockwise 90 degrees = X"), wheelsConfigCheckBox(body, "TurnAround", "Turn around = Rotate 180 degrees = X"), ))), jCenteredSection("Help", withLeftMargin(jMultiLineLabel(unindentMLS([[ Turning will stay on the same square. If Smarty is able to turn then it will appear as < , > , ^ or v to indicate the direction it is facing. ]]))))), jCenteredSection("Sensing", withLeftMargin(spacing, wheelsConfigCheckBox(body, "AddAWheelSensor", "Add a wheel sensor to detect if it moved or failed because it was up against a wall")) ) )); } svoid showWheelsConfig(SmartyBody body) { var buttonGroup = buttonGroup(); showPackedFrame("Configure Smarty's Wheels", wheelsConfigPanel(body)); } svoid showSmartyConfig { showPackedFrameMinWidth(400, "Design or Select a body for Smarty", withMargin(centeredButtons( "Vision", r { showVisionConfig(state.body!) }, "Wheels", r { showWheelsConfig(state.body!) }, )) ); } // e.g. $wallSymbol and $noWallSymbol sS replaceSymbols(S s) { ret replaceDollarVars(s, +wallSymbol, +noWallSymbol); } svoid saveWorld(File f) { new LS lines; lines.add(" " + state.gridRows); for (int y = 0; y < state.gridRows; y++) for (int x = 0; x < state.gridCols; x++) { JCell cell = getCell(x, y); lines.add(state.smartyX == x && state.smartyY == y ? "A" : cell.obstacle() ? "#" : " "); } saveTextFile(f, lines(lines)); infoBox("World saved: " + fileName(f)); cset(state, worldName := fileNameWithoutExtension(f)); } svoid loadWorld(File f) { pcall-messagebox { LS lines = linesFromFile_list(f); int i = 0; int gridRows = parseInt(trim(lines.get(i++))); if (gridRows < 1 || gridRows > maxWorldSize) fail("Bad world size: " + gridRows); setWorldSize(gridRows); for y to gridRows: for x to gridRows: { S line = lines.get(i++); JCell cell = getCell(x, y); if (eq(line, "#")) cell.setObstacle(true); else if (eq(line, "A")) cset(state, smartyX := x, smartyY := y); } rearrangeLetters(); repaint(mainGrid); cset(state, worldName := fileNameWithoutExtension(f)); infoBox("World loaded: " + fileName(f)); } } svoid saveWorldDialog swing { JFileChooser fileChooser = new(mainDir()); fileChooser.setFileFilter(new FileNameExtensionFilter("Smarty World files (*.\*worldFileExtension*/)", worldFileExtension)); if (fileChooser.showSaveDialog(mainFrame) == JFileChooser.APPROVE_OPTION) { File f = defaultExtension(worldFileExtension, fileChooser.getSelectedFile()); saveWorld(f); infoBox("World saved as " + f.getName()); } } svoid loadWorldDialog swing { JFileChooser fileChooser = new(mainDir()); fileChooser.setFileFilter(new FileNameExtensionFilter("Smarty World files (*.\*worldFileExtension*/)", worldFileExtension)); if (fileChooser.showOpenDialog(mainFrame) == JFileChooser.APPROVE_OPTION) loadWorld(fileChooser.getSelectedFile()); } svoid newWorld { setWorldSize(defaultWorldSize); cset(state, worldName := "empty"); } static File mainDir() { ret mkdirs(userDir("Adaptron/Smarty")); } svoid saveBody(File f) { S[] Body = new[22]; SmartyBody b = state.body!; Body[1] = escapeNewLines(b.name); // TODO: remaining values from AI? Body[2] = "1"; //Vision.Value = 1 - checked Body[3] = "0"; //Touch.Value Body[4] = b.wheelsFlag("AddAWheelSensor"); Body[5] = "1"; //Wheels.Value Body[6] = "0"; //Arms.Value Body[7] = b.cameraDirections.contains(CameraDirection.down) ? "1" : "0"; //VisionConfig.VisionDown Body[8] = b.cameraDirections.contains(CameraDirection.left) ? "1" : "0"; //VisionConfig.VisionLeft Body[9] = b.cameraDirections.contains(CameraDirection.right) ? "1" : "0"; //VisionConfig.VisionRight Body[10] = b.cameraDirections.contains(CameraDirection.inFront) ? "1" : "0"; //VisionConfig.VisionInFront Body[11] = b.cameraDirections.contains(CameraDirection.behind) ? "1" : "0"; //VisionConfig.VisionBehind Body[12] = eq(b.visionMode, VisionMode.nextSquare) ? "True" : "False"; //VisionConfig.VisionSymbolicOrMagnitude(0) Body[13] = eq(b.visionMode, VisionMode.distanceToWall) ? "True" : "False"; //VisionConfig.VisionSymbolicOrMagnitude(1) Body[14] = b.wheelsFlag("MoveForward"); Body[15] = b.wheelsFlag("MoveLeft"); Body[16] = b.wheelsFlag("MoveRight"); Body[17] = b.wheelsFlag("MoveBackward"); Body[18] = b.wheelsFlag("TurnRight"); Body[19] = b.wheelsFlag("TurnLeft"); Body[20] = b.wheelsFlag("TurnAround"); Body[21] = eq(b.visionMode, VisionMode.wallOrNot) ? "True" : "False"; //VisionConfig.VisionSymbolicOrMagnitude(2) saveTextFile(f, lines(dropFirst(Body))); } svoid newBody { cdelete(state.body!); cset(state, body := cnew(SmartyBody)); updateTabs(); } svoid loadBody(File f) { pcall-messagebox { LS lines = linesFromFile_list(f); S[] Body = asStringArray(itemPlusList("", lines)); newBody(); var b = state.body!; // TODO: remaining values cset(state.body, name := Body[1]); if (eq(Body[4], "1")) b.wheels.add("AddAWheelSensor"); if (eq(Body[7], "1")) b.cameraDirections.add(CameraDirection.down); if (eq(Body[8], "1")) b.cameraDirections.add(CameraDirection.left); if (eq(Body[9], "1")) b.cameraDirections.add(CameraDirection.right); if (eq(Body[10], "1")) b.cameraDirections.add(CameraDirection.inFront); if (eq(Body[11], "1")) b.cameraDirections.add(CameraDirection.behind); if (eq(Body[12], "True")) b.visionMode = VisionMode.nextSquare; if (eq(Body[13], "True")) b.visionMode = VisionMode.distanceToWall; if (eq(Body[14], "1")) b.wheels.add("MoveForward"); if (eq(Body[15], "1")) b.wheels.add("MoveLeft"); if (eq(Body[16], "1")) b.wheels.add("MoveRight"); if (eq(Body[17], "1")) b.wheels.add("MoveBackward"); if (eq(Body[18], "1")) b.wheels.add("TurnRight"); if (eq(Body[19], "1")) b.wheels.add("TurnLeft"); if (eq(Body[20], "1")) b.wheels.add("TurnAround"); if (eq(Body[21], "True")) b.visionMode = VisionMode.wallOrNot; b.change(); updateTabs(); infoBox("Body loaded: " + fileName(f)); } } svoid saveBodyDialog swing { new JFileChooser fileChooser; fileChooser.setSelectedFile(newFile(mainDir(), sanitizeFileName(state.body->name))); fileChooser.setFileFilter(new FileNameExtensionFilter("Smarty Body files (*.\*bodyFileExtension*/)", bodyFileExtension)); if (fileChooser.showSaveDialog(mainFrame) == JFileChooser.APPROVE_OPTION) { File f = defaultExtension(bodyFileExtension, fileChooser.getSelectedFile()); saveBody(f); infoBox("Body saved as " + f.getName()); } } svoid loadBodyDialog swing { JFileChooser fileChooser = new(mainDir()); fileChooser.setFileFilter(new FileNameExtensionFilter("Smarty Body files (*.\*bodyFileExtension*/)", bodyFileExtension)); if (fileChooser.showOpenDialog(mainFrame) == JFileChooser.APPROVE_OPTION) loadBody(fileChooser.getSelectedFile()); }