File save dialog bugfix.
[lsystem3d.git] / src / gui.cpp
index d068cd5eacc177e4d834e1402f97beb042de30df..16d712ac90d76ba06e63da0d07b60d298f482082 100644 (file)
@@ -1,6 +1,6 @@
 // Copyright (C) 2006 Erik Dahlberg
 // 
-// This file is part of LSystem3d.
+// This file is part of LSystem3D.
 // 
 // LSystem3D is free software; you can redistribute it and/or
 // modify it under the terms of the GNU General Public License
@@ -19,7 +19,7 @@
 
 
 
-#include <map>
+#include <sstream>
 #include <string>
 
 #include "fx.h"
 #include "lindenmayersystem.h"
 #include "lsystemparameters.h"
 #include "renderingsurface.h"
+#include "rule.h"
+#include "ruleset.h"
+
+using namespace std;
 
 
 
 // message map
 FXDEFMAP(GUI) GUIMap[] =
 {
-    FXMAPFUNC(SEL_COMMAND, GUI::ID_GENERATE, GUI::onGenerateButtonPressed)
+    FXMAPFUNC(SEL_COMMAND, GUI::ID_GENERATE,   GUI::onGenerate),
+    FXMAPFUNC(SEL_COMMAND, GUI::ID_LOAD,       GUI::onLoad),
+    FXMAPFUNC(SEL_COMMAND, GUI::ID_SAVE,       GUI::onSave),
+    FXMAPFUNC(SEL_COMMAND, GUI::ID_QUIT,       GUI::onQuit),
+    FXMAPFUNC(SEL_COMMAND, GUI::ID_HELP_USAGE, GUI::onHelpUsage),
+    FXMAPFUNC(SEL_COMMAND, GUI::ID_HELP_RULES, GUI::onHelpRules),
+    FXMAPFUNC(SEL_CLOSE,   0,                  GUI::onQuit)
 };
 
 // macro to set up class implementation
@@ -44,89 +54,149 @@ FXIMPLEMENT(GUI, FXMainWindow, GUIMap, ARRAYNUMBER(GUIMap))
 
 /**
  * Constructor
- * @param application the FOX application object
+ * @param application the application object
  * @param renderingSurface the rendering surface
- * @param lsystem the Lindenmayer-system
+ * @param lsystem the Lindenmayer system generator
  */
 GUI::GUI(FXApp *application, RenderingSurface *renderingSurface, LindenmayerSystem *lsystem) : FXMainWindow(application, "LSystem3D" , NULL, NULL, DECOR_ALL, 920, 100, 0, 0)
 {
-    _lsystem = lsystem;
     _renderingSurface = renderingSurface;
+    _lsystem = lsystem;
     
+        
     
     // create the widgets
     
+    // menu bar
+    FXMenuBar *menubar = new FXMenuBar(this, LAYOUT_FILL_X);
+    
+    // file menu
+    _fileMenuPane = new FXMenuPane(this);
+    new FXMenuTitle(menubar, "&File", NULL, _fileMenuPane);
+    new FXMenuCommand(_fileMenuPane, "&Load...", NULL, this, GUI::ID_LOAD);
+    new FXMenuCommand(_fileMenuPane, "&Save...", NULL, this, GUI::ID_SAVE);
+    new FXMenuCommand(_fileMenuPane, "&Quit",    NULL, this, GUI::ID_QUIT);
+    
+    // help menu
+    _helpMenuPane = new FXMenuPane(this);
+    new FXMenuTitle(menubar, "&Help", NULL, _helpMenuPane);
+    new FXMenuCommand(_helpMenuPane, "&Usage", NULL, this, GUI::ID_HELP_USAGE);
+    new FXMenuCommand(_helpMenuPane, "&Rules", NULL, this, GUI::ID_HELP_RULES);
+    
+    
     // the main frame
     FXVerticalFrame *mainFrame = new FXVerticalFrame(this, LAYOUT_FILL_X);
     
-    // axiom
+    
+    // axiom text field
     FXHorizontalFrame *axiomFrame = new FXHorizontalFrame(mainFrame, LAYOUT_FILL_X);
     new FXLabel(axiomFrame, "Axiom:");
     _axiomTextField = new FXTextField(axiomFrame, 10, NULL, 0, LAYOUT_FILL_X);
-
-    // rules
+    
+    // rules multiline text field
     FXHorizontalFrame *rulesFrame = new FXHorizontalFrame(mainFrame, LAYOUT_FILL_X);
     new FXLabel(rulesFrame, "Rules:");
     _rulesText = new FXText(rulesFrame, NULL, 0, LAYOUT_FILL_X);
     _rulesText->setVisibleColumns(30);
     _rulesText->setVisibleRows(10);
-
-    // angle
+    
+    // angle spinner
     FXHorizontalFrame *angleFrame = new FXHorizontalFrame(mainFrame);
     new FXLabel(angleFrame, "Angle:");
     _angleRealSpinner = new FXRealSpinner(angleFrame, 5);
-
-    // iterations
-    FXHorizontalFrame *iterationsFrame = new FXHorizontalFrame(mainFrame);
-    new FXLabel(iterationsFrame, "Iterations:");
-    _iterationsSpinner = new FXSpinner(iterationsFrame, 5);
-
-    // diameter
+    
+    // depth spinner
+    FXHorizontalFrame *depthFrame = new FXHorizontalFrame(mainFrame);
+    new FXLabel(depthFrame, "Depth:");
+    _depthSpinner = new FXSpinner(depthFrame, 5);
+    
+    // diameter spinner
     FXHorizontalFrame *diameterFrame = new FXHorizontalFrame(mainFrame);
     new FXLabel(diameterFrame, "Diameter:");
     _diameterRealSpinner = new FXRealSpinner(diameterFrame, 5);
-    _diameterRealSpinner->setValue(0.2);
-    _diameterRealSpinner->setIncrement(0.1);
+    _diameterRealSpinner->setValue(0.1);
+    _diameterRealSpinner->setIncrement(0.01);
     
-    // diameter factor
+    // diameter factor spinner
     new FXLabel(diameterFrame, "Factor:");
     _diameterFactorRealSpinner = new FXRealSpinner(diameterFrame, 5);
-    _diameterFactorRealSpinner->setValue(0.8);
-    _diameterFactorRealSpinner->setIncrement(0.1);
+    _diameterFactorRealSpinner->setValue(1.0);
+    _diameterFactorRealSpinner->setIncrement(0.01);
+    
     
     // separator
     new FXHorizontalSeparator(mainFrame, SEPARATOR_RIDGE | LAYOUT_FILL_X);
 
-    // generate
+    
+    // generate button
     new FXButton(mainFrame, "&Generate", NULL, this, GUI::ID_GENERATE);
     
     
+    // usage help dialog
+    stringstream usageHelpText;
+    usageHelpText << "Rendering window:" << endl
+                  << "Left mouse button: Move model in z-plane" << endl
+                  << "Middle mouse button: Zoom in/out" << endl
+                  << "Right mouse button: Rotate around y-axis" << endl
+                  << endl
+                  << "Controller window:" << endl
+                  << "Axiom: Initial rule" << endl
+                  << "Rules: The L-system rules" << endl
+                  << "Angle: Turn/pitch/roll angle" << endl
+                  << "Depth: Depth of recursion" << endl
+                  << "Diameter: Initial diameter of segment" << endl
+                  << "Factor: For each recursion level, multiply segment diameter with this value" << endl
+                  << "Generate: Generate the L-system";
     
-    // load L-system parameters from file and sync with GUI
+    _helpUsageMessageBox = new FXMessageBox(getApp(), "Usage", usageHelpText.str().c_str(), NULL, MBOX_OK);
     
-    _lsystemParameters.load(_lsystem, "lsystem.xml");
     
-    // axiom
-    _axiomTextField->setText(_lsystem->getAxiom().c_str());
+    // rules help dialog
+    stringstream rulesHelpText;
+    rulesHelpText << "Rules:" << endl
+                  << "F : Walk forward, creating a segment" << endl
+                  << "A-Z : Replacement rule" << endl
+                  << "A(0.33) : Probability factor 0.33 for rule \"A\"" << endl
+                  << "= : Rule assignment" << endl
+                  << "+ : Turn left" << endl
+                  << "- : Turn right" << endl
+                  << "&& : Pitch down" << endl
+                  << "^ : Pitch up" << endl
+                  << "\\ : Roll left" << endl
+                  << "/ : Roll right" << endl
+                  << "| : Turn around 180 degrees" << endl
+                  << "[ : Save state to stack" << endl
+                  << "] : Load state from stack" << endl
+                  << "{ : Create a planar surface" << endl
+                  << "} : Close a planar surface" << endl
+                  << "f : One vertex in a planar surface, specified CCW" << endl
+                  << "! : Decrement segment diameter" << endl
+                  << "\' : Increment current index to color table" << endl
+                  << ", : Decrement current index to color table" << endl
+                  << endl
+                  << "Example:" << endl
+                  << "F(0.33)=[+FL]F/[-FL]F!" << endl
+                  << "F(0.33)=F[&&FL]F!" << endl
+                  << "F(0.34)=F/[-FL]&&F!" << endl
+                  << "L={,-f++f-|-f++f-'}";
     
-    // rules
-    map<string,string> allRulesSrc = _lsystem->getRules();
-    string allRulesDst;
-    for (map<string,string>::iterator currentRule = allRulesSrc.begin();
-         currentRule != allRulesSrc.end();
-         currentRule++)
-    {
-        // one rule
-        string rule = currentRule->first + "=" + currentRule->second + '\n';
-        allRulesDst += rule.c_str();
-    }
-    _rulesText->setText(allRulesDst.c_str(), allRulesDst.size());
+    _helpRulesMessageBox = new FXMessageBox(getApp(), "Rules", rulesHelpText.str().c_str(), NULL, MBOX_OK);
     
-    // angle
-    _angleRealSpinner->setValue(_lsystem->getAngle());
     
-    // iterations
-    _iterationsSpinner->setValue(_lsystem->getNumIterations());
+    // load default L-system
+    _lsystemParameters.load(_lsystem, "lsystem.xml");
+    lsystemToWidgets();
+}
+
+
+
+/**
+ * Destructor
+ */
+GUI::~GUI()
+{
+    delete _fileMenuPane;
+    delete _helpMenuPane;
 }
 
 
@@ -144,68 +214,203 @@ void GUI::create()
 
 
 /**
- * Called by the system when the "generate"-button is pressed
+ * Called by the system when the "Generate" button is pressed
+ * @param sender the sender object
+ * @param selector message type and id
+ * @param data event related data
+ * @return 
+ */
+long GUI::onGenerate(FXObject *sender, FXSelector selector, void *data)
+{
+    widgetsToLsystem();
+    
+    // generate and render L-system to screen
+    _lsystem->generate();
+    _renderingSurface->draw();
+}
+
+
+
+/**
+ * Called by the system when the File->Load menu command is selected
+ * @param sender the sender object
+ * @param selector message type and id
+ * @param data event related data
+ * @return 
+ */
+long GUI::onLoad(FXObject *sender, FXSelector selector, void *data)
+{
+    FXString loadFilename = FXFileDialog::getOpenFilename(this, "Load file...", ".");
+    
+    if (!loadFilename.empty())
+    {
+        // load from disk
+        _lsystemParameters.load(_lsystem, loadFilename.text());
+        lsystemToWidgets();
+    }
+}
+
+
+
+/**
+ * Called by the system when the File->Save menu command is selected
+ * @param sender the sender object
+ * @param selector message type and id
+ * @param data event related data
+ * @return 
+ */
+long GUI::onSave(FXObject *sender, FXSelector selector, void *data)
+{
+    FXString saveFilename = FXFileDialog::getSaveFilename(this, "Save file...", ".");
+    
+    if (!saveFilename.empty())
+    {
+        if (FXStat::exists(saveFilename))
+        {
+            // the file already exists
+            
+            if (MBOX_CLICKED_YES == FXMessageBox::question(this, MBOX_YES_NO, "Overwrite file", "Overwrite existing file?"))
+            {
+                // overwrite file
+                widgetsToLsystem();
+                _lsystemParameters.save(_lsystem, saveFilename.text());
+            }
+        }
+        else
+        {
+            // save to file
+            widgetsToLsystem();
+            _lsystemParameters.save(_lsystem, saveFilename.text());
+        }
+    }
+}
+
+
+
+/**
+ * Called by the system when the close button or the File->Quit command item is selected
  * @param sender the sender object
  * @param selector message type and id
  * @param data event related data
  * @return 
  */
-long GUI::onGenerateButtonPressed(FXObject *sender, FXSelector selector, void *data)
+long GUI::onQuit(FXObject *sender, FXSelector selector, void *data)
+{
+    getApp()->exit(0);
+    
+    return 1;
+}
+
+
+
+/**
+ * Called by the system when the Help->Usage menu command is selected
+ * @param sender the sender object
+ * @param selector message type and id
+ * @param data event related data
+ * @return 
+ */
+long GUI::onHelpUsage(FXObject *sender, FXSelector selector, void *data)
+{
+    _helpUsageMessageBox->show(PLACEMENT_OWNER);
+}
+
+
+
+/**
+ * Called by the system when the Help->Rules menu command is selected
+ * @param sender the sender object
+ * @param selector message type and id
+ * @param data event related data
+ * @return 
+ */
+long GUI::onHelpRules(FXObject *sender, FXSelector selector, void *data)
+{
+    _helpRulesMessageBox->show(PLACEMENT_OWNER);
+}
+
+
+
+/**
+ * Put content of widgets into the L-system
+ */
+void GUI::widgetsToLsystem()
 {
     // clear L-system
     _lsystem->clear();
     
     
-    // load data from widgets into L-system
-    
     // axiom
-    _lsystem->setAxiom(_axiomTextField->getText().text());
+    Rule axiom("axiom", _axiomTextField->getText().text(), 1.0);
+    _lsystem->setAxiom(axiom);
     
     // rules
     string ruleSet = _rulesText->getText().text();
-     string currentRule;
-    string ruleName;
-    bool newRule = true;
+    string currentRule;
     for (int i = 0; i < ruleSet.size(); i++)
     {
-        if (newRule)
-        {
-            // get rule name
-            ruleName = ruleSet[i];
-            i += 2;
-            newRule = false;
-        }
-        
-        if (ruleSet[i] != '\n' || i == ruleSet.size() - 1)
+        if (ruleSet[i] != '\n')
         {
-            // part of current rule
+            // rule component
             currentRule += ruleSet[i];
+            
+            if (i == ruleSet.size() - 1)
+            {
+                // last char in whole rule string
+                _lsystem->setRule(Rule(currentRule));
+                currentRule.clear();
+            }
         }
-        
-        if (ruleSet[i] == '\n' || i == ruleSet.size() - 1)
+        else
         {
-            // end of rule
-            _lsystem->setRule(ruleName, currentRule);
-            ruleName.clear();
+            // last char in current rule
+            _lsystem->setRule(Rule(currentRule));
             currentRule.clear();
-            newRule = true;
         }
     }
 
     // angle
     _lsystem->setAngle(_angleRealSpinner->getValue());
     
-    // iterations
-    _lsystem->setNumIterations(_iterationsSpinner->getValue());
+    // depth
+    _lsystem->setDepth(_depthSpinner->getValue());
     
-    // diameter
+    // segment diameter
     _lsystem->setDiameter(_diameterRealSpinner->getValue());
     
-    // diameter factor
+    // segment diameter factor
     _lsystem->setDiameterFactor(_diameterFactorRealSpinner->getValue());
+}
+
+
+
+/**
+ * Put content of the L-system into widgets
+ */
+void GUI::lsystemToWidgets()
+{
+    // axiom
+    _axiomTextField->setText(_lsystem->getAxiom().getContent().c_str());
     
+    // rules
+    rulemap rules = _lsystem->getRules().getRules();
+    string rulesString;    
+    for (rulemap::iterator it = rules.begin(); it != rules.end(); ++it)
+    {
+        rulesString += it->second.toString();
+        rulesString += '\n';
+    }
+    _rulesText->setText(rulesString.c_str(), rulesString.size());
     
-    // generate and render L-system to screen
-    _lsystem->generate();
-    _renderingSurface->draw();
+    // angle
+    _angleRealSpinner->setValue(_lsystem->getAngle());
+    
+    // depth
+    _depthSpinner->setValue(_lsystem->getDepth());
+    
+    // segment diameter
+    _diameterRealSpinner->setValue(_lsystem->getDiameter());
+    
+    // segment diameter factor
+    _diameterFactorRealSpinner->setValue(_lsystem->getDiameterFactor());
 }