Added File and Help menus.
[lsystem3d.git] / src / gui.cpp
CommitLineData
3025996b 1// Copyright (C) 2006 Erik Dahlberg
2//
6e40d5eb 3// This file is part of LSystem3D.
3025996b 4//
5// LSystem3D is free software; you can redistribute it and/or
6// modify it under the terms of the GNU General Public License
7// as published by the Free Software Foundation; either version 2
8// of the License, or (at your option) any later version.
9//
10// LSystem3D is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with LSystem3D; if not, write to the Free Software
17// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
19
20
21
6e40d5eb 22#include <sstream>
3025996b 23#include <string>
24
25#include "fx.h"
26
27#include "gui.h"
28#include "lindenmayersystem.h"
29#include "lsystemparameters.h"
30#include "renderingsurface.h"
6e40d5eb 31#include "rule.h"
32#include "ruleset.h"
33
34using namespace std;
3025996b 35
36
37
38// message map
39FXDEFMAP(GUI) GUIMap[] =
40{
eb5d5b4c 41 FXMAPFUNC(SEL_COMMAND, GUI::ID_GENERATE, GUI::onGenerate),
42 FXMAPFUNC(SEL_COMMAND, GUI::ID_LOAD, GUI::onLoad),
43 FXMAPFUNC(SEL_COMMAND, GUI::ID_SAVE, GUI::onSave),
44 FXMAPFUNC(SEL_COMMAND, GUI::ID_QUIT, GUI::onQuit),
45 FXMAPFUNC(SEL_COMMAND, GUI::ID_HELP_USAGE, GUI::onHelpUsage),
46 FXMAPFUNC(SEL_COMMAND, GUI::ID_HELP_RULES, GUI::onHelpRules),
47 FXMAPFUNC(SEL_CLOSE, 0, GUI::onQuit)
3025996b 48};
49
50// macro to set up class implementation
51FXIMPLEMENT(GUI, FXMainWindow, GUIMap, ARRAYNUMBER(GUIMap))
52
53
54
55/**
56 * Constructor
eb5d5b4c 57 * @param application the application object
3025996b 58 * @param renderingSurface the rendering surface
eb5d5b4c 59 * @param lsystem the Lindenmayer system generator
3025996b 60 */
61GUI::GUI(FXApp *application, RenderingSurface *renderingSurface, LindenmayerSystem *lsystem) : FXMainWindow(application, "LSystem3D" , NULL, NULL, DECOR_ALL, 920, 100, 0, 0)
62{
3025996b 63 _renderingSurface = renderingSurface;
eb5d5b4c 64 _lsystem = lsystem;
3025996b 65
eb5d5b4c 66
6e40d5eb 67
3025996b 68 // create the widgets
69
eb5d5b4c 70 // menu bar
71 FXMenuBar *menubar = new FXMenuBar(this, LAYOUT_FILL_X);
72
73 // file menu
74 _fileMenuPane = new FXMenuPane(this);
75 new FXMenuTitle(menubar, "&File", NULL, _fileMenuPane);
76 new FXMenuCommand(_fileMenuPane, "&Load...", NULL, this, GUI::ID_LOAD);
77 new FXMenuCommand(_fileMenuPane, "&Save...", NULL, this, GUI::ID_SAVE);
78 new FXMenuCommand(_fileMenuPane, "&Quit", NULL, this, GUI::ID_QUIT);
79
80 // help menu
81 _helpMenuPane = new FXMenuPane(this);
82 new FXMenuTitle(menubar, "&Help", NULL, _helpMenuPane);
83 new FXMenuCommand(_helpMenuPane, "&Usage", NULL, this, GUI::ID_HELP_USAGE);
84 new FXMenuCommand(_helpMenuPane, "&Rules", NULL, this, GUI::ID_HELP_RULES);
85
86
3025996b 87 // the main frame
88 FXVerticalFrame *mainFrame = new FXVerticalFrame(this, LAYOUT_FILL_X);
89
eb5d5b4c 90
6e40d5eb 91 // axiom text field
3025996b 92 FXHorizontalFrame *axiomFrame = new FXHorizontalFrame(mainFrame, LAYOUT_FILL_X);
93 new FXLabel(axiomFrame, "Axiom:");
94 _axiomTextField = new FXTextField(axiomFrame, 10, NULL, 0, LAYOUT_FILL_X);
6e40d5eb 95
96 // rules multiline text field
3025996b 97 FXHorizontalFrame *rulesFrame = new FXHorizontalFrame(mainFrame, LAYOUT_FILL_X);
98 new FXLabel(rulesFrame, "Rules:");
99 _rulesText = new FXText(rulesFrame, NULL, 0, LAYOUT_FILL_X);
100 _rulesText->setVisibleColumns(30);
101 _rulesText->setVisibleRows(10);
6e40d5eb 102
103 // angle spinner
3025996b 104 FXHorizontalFrame *angleFrame = new FXHorizontalFrame(mainFrame);
105 new FXLabel(angleFrame, "Angle:");
106 _angleRealSpinner = new FXRealSpinner(angleFrame, 5);
6e40d5eb 107
108 // depth spinner
109 FXHorizontalFrame *depthFrame = new FXHorizontalFrame(mainFrame);
110 new FXLabel(depthFrame, "Depth:");
111 _depthSpinner = new FXSpinner(depthFrame, 5);
112
113 // diameter spinner
3025996b 114 FXHorizontalFrame *diameterFrame = new FXHorizontalFrame(mainFrame);
115 new FXLabel(diameterFrame, "Diameter:");
116 _diameterRealSpinner = new FXRealSpinner(diameterFrame, 5);
eb5d5b4c 117 _diameterRealSpinner->setValue(0.1);
118 _diameterRealSpinner->setIncrement(0.01);
3025996b 119
6e40d5eb 120 // diameter factor spinner
3025996b 121 new FXLabel(diameterFrame, "Factor:");
122 _diameterFactorRealSpinner = new FXRealSpinner(diameterFrame, 5);
eb5d5b4c 123 _diameterFactorRealSpinner->setValue(1.0);
6e40d5eb 124 _diameterFactorRealSpinner->setIncrement(0.01);
3025996b 125
eb5d5b4c 126
3025996b 127 // separator
128 new FXHorizontalSeparator(mainFrame, SEPARATOR_RIDGE | LAYOUT_FILL_X);
129
eb5d5b4c 130
6e40d5eb 131 // generate button
3025996b 132 new FXButton(mainFrame, "&Generate", NULL, this, GUI::ID_GENERATE);
133
3025996b 134
eb5d5b4c 135 // usage help dialog
136 stringstream usageHelpText;
137 usageHelpText << "Rendering window:" << endl
138 << "Left mouse button: Move model in z-plane" << endl
139 << "Middle mouse button: Zoom in/out" << endl
140 << "Right mouse button: Rotate around y-axis" << endl
141 << endl
142 << "Controller window:" << endl
143 << "Axiom: Initial rule" << endl
144 << "Rules: The L-system rules" << endl
145 << "Angle: Turn/pitch/roll angle" << endl
146 << "Depth: Depth of recursion" << endl
147 << "Diameter: Initial diameter of segment" << endl
148 << "Factor: For each recursion level, multiply segment diameter with this value" << endl
149 << "Generate: Generate the L-system";
3025996b 150
eb5d5b4c 151 _helpUsageMessageBox = new FXMessageBox(getApp(), "Usage", usageHelpText.str().c_str(), NULL, MBOX_OK);
3025996b 152
3025996b 153
eb5d5b4c 154 // rules help dialog
155 stringstream rulesHelpText;
156 rulesHelpText << "Rules:" << endl
157 << "F : Walk forward, creating a segment" << endl
158 << "A-Z : Replacement rule" << endl
159 << "A(0.33) : Probability factor 0.33 for rule \"A\"" << endl
160 << "= : Rule assignment" << endl
161 << "+ : Turn left" << endl
162 << "- : Turn right" << endl
163 << "&& : Pitch down" << endl
164 << "^ : Pitch up" << endl
165 << "\\ : Roll left" << endl
166 << "/ : Roll right" << endl
167 << "| : Turn around 180 degrees" << endl
168 << "[ : Save state to stack" << endl
169 << "] : Load state from stack" << endl
170 << "{ : Create a planar surface" << endl
171 << "} : Close a planar surface" << endl
172 << "f : One vertex in a planar surface, specified CCW" << endl
173 << "! : Decrement segment diameter" << endl
174 << "\' : Increment current index to color table" << endl
175 << ", : Decrement current index to color table" << endl
176 << endl
177 << "Example:" << endl
178 << "F(0.33)=[+FL]F/[-FL]F!" << endl
179 << "F(0.33)=F[&&FL]F!" << endl
180 << "F(0.34)=F/[-FL]&&F!" << endl
181 << "L={,-f++f-|-f++f-'}";
182
183 _helpRulesMessageBox = new FXMessageBox(getApp(), "Rules", rulesHelpText.str().c_str(), NULL, MBOX_OK);
184
185
186 loadLSystem("lsystem.xml");
187}
188
189
190
191/**
192 * Destructor
193 */
194GUI::~GUI()
195{
196 delete _fileMenuPane;
197 delete _helpMenuPane;
3025996b 198}
199
200
201
202/**
203 * Create and initialize the window
204 */
205void GUI::create()
206{
207 // create the window and make it appear
208 FXMainWindow::create();
209 show();
210}
211
212
213
214/**
6e40d5eb 215 * Called by the system when the "Generate" button is pressed
3025996b 216 * @param sender the sender object
217 * @param selector message type and id
218 * @param data event related data
219 * @return
220 */
eb5d5b4c 221long GUI::onGenerate(FXObject *sender, FXSelector selector, void *data)
3025996b 222{
223 // clear L-system
224 _lsystem->clear();
225
226
6e40d5eb 227 // get user input form widgets
3025996b 228
229 // axiom
6e40d5eb 230 Rule axiom("axiom", _axiomTextField->getText().text(), 1.0);
231 _lsystem->setAxiom(axiom);
3025996b 232
233 // rules
234 string ruleSet = _rulesText->getText().text();
6e40d5eb 235 string currentRule;
3025996b 236 for (int i = 0; i < ruleSet.size(); i++)
237 {
6e40d5eb 238 if (ruleSet[i] != '\n')
3025996b 239 {
6e40d5eb 240 // rule component
3025996b 241 currentRule += ruleSet[i];
6e40d5eb 242
243 if (i == ruleSet.size() - 1)
244 {
245 // last char in whole rule string
246 _lsystem->setRule(Rule(currentRule));
247 currentRule.clear();
248 }
3025996b 249 }
6e40d5eb 250 else
3025996b 251 {
6e40d5eb 252 // last char in current rule
253 _lsystem->setRule(Rule(currentRule));
3025996b 254 currentRule.clear();
3025996b 255 }
256 }
257
258 // angle
259 _lsystem->setAngle(_angleRealSpinner->getValue());
260
6e40d5eb 261 // depth
262 _lsystem->setDepth(_depthSpinner->getValue());
3025996b 263
264 // diameter
265 _lsystem->setDiameter(_diameterRealSpinner->getValue());
266
267 // diameter factor
268 _lsystem->setDiameterFactor(_diameterFactorRealSpinner->getValue());
269
270
271 // generate and render L-system to screen
272 _lsystem->generate();
273 _renderingSurface->draw();
274}
6e40d5eb 275
276
277
278/**
eb5d5b4c 279 * Called by the system when the File->Load menu command is selected
280 * @param sender the sender object
281 * @param selector message type and id
282 * @param data event related data
283 * @return
284 */
285long GUI::onLoad(FXObject *sender, FXSelector selector, void *data)
286{
287 FXString loadFilename = FXFileDialog::getOpenFilename(this, "Load file...", ".");
288
289 if (!loadFilename.empty())
290 {
291 // load from disk
292 loadLSystem(loadFilename.text());
293 }
294}
295
296
297
298/**
299 * Called by the system when the File->Save menu command is selected
300 * @param sender the sender object
301 * @param selector message type and id
302 * @param data event related data
303 * @return
304 */
305long GUI::onSave(FXObject *sender, FXSelector selector, void *data)
306{
307 FXString saveFilename = FXFileDialog::getSaveFilename(this, "Save file...", ".");
308
309 if (FXStat::exists(saveFilename))
310 {
311 // the file already exists
312
313 if (MBOX_CLICKED_YES == FXMessageBox::question(this, MBOX_YES_NO, "Overwrite file", "Overwrite existing file?"))
314 {
315 // overwrite file
316 _lsystemParameters.save(_lsystem, saveFilename.text());
317 }
318 }
319 else
320 {
321 // save to file
322 _lsystemParameters.save(_lsystem, saveFilename.text());
323 }
324}
325
326
327
328/**
329 * Called by the system when the close button or the File->Quit command item is selected
330 * @param sender the sender object
331 * @param selector message type and id
332 * @param data event related data
333 * @return
334 */
335long GUI::onQuit(FXObject *sender, FXSelector selector, void *data)
336{
337 getApp()->exit(0);
338
339 return 1;
340}
341
342
343
344/**
345 * Called by the system when the Help->Usage menu command is selected
346 * @param sender the sender object
347 * @param selector message type and id
348 * @param data event related data
349 * @return
350 */
351long GUI::onHelpUsage(FXObject *sender, FXSelector selector, void *data)
352{
353 _helpUsageMessageBox->show(PLACEMENT_OWNER);
354}
355
356
357
358/**
359 * Called by the system when the Help->Rules menu command is selected
6e40d5eb 360 * @param sender the sender object
361 * @param selector message type and id
362 * @param data event related data
363 * @return
364 */
eb5d5b4c 365long GUI::onHelpRules(FXObject *sender, FXSelector selector, void *data)
6e40d5eb 366{
eb5d5b4c 367 _helpRulesMessageBox->show(PLACEMENT_OWNER);
368}
369
370
371
372/**
373 * Load L-system from file and sync with GUI
374 * @param filename path of the L-system file
375 */
376void GUI::loadLSystem(string filename)
377{
378 // load from file
379 _lsystemParameters.load(_lsystem, filename);
380
381
382 // sync with GUI
383
384 // axiom
385 _axiomTextField->setText(_lsystem->getAxiom().getContent().c_str());
386
387 // rules
388 rulemap rules = _lsystem->getRules().getRules();
389 string rulesString;
390 for (rulemap::iterator it = rules.begin(); it != rules.end(); ++it)
391 {
392 rulesString += it->second.toString();
393 rulesString += '\n';
394 }
395 _rulesText->setText(rulesString.c_str(), rulesString.size());
396
397 // angle
398 _angleRealSpinner->setValue(_lsystem->getAngle());
399
400 // depth
401 _depthSpinner->setValue(_lsystem->getDepth());
402
403 // segment diameter
404 _diameterRealSpinner->setValue(_lsystem->getDiameter());
405
406 // segment diameter factor
407 _diameterFactorRealSpinner->setValue(_lsystem->getDiameterFactor());
6e40d5eb 408}