File save dialog bugfix.
[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
881fc6e5 186 // load default L-system
187 _lsystemParameters.load(_lsystem, "lsystem.xml");
188 lsystemToWidgets();
eb5d5b4c 189}
190
191
192
193/**
194 * Destructor
195 */
196GUI::~GUI()
197{
198 delete _fileMenuPane;
199 delete _helpMenuPane;
3025996b 200}
201
202
203
204/**
205 * Create and initialize the window
206 */
207void GUI::create()
208{
209 // create the window and make it appear
210 FXMainWindow::create();
211 show();
212}
213
214
215
216/**
6e40d5eb 217 * Called by the system when the "Generate" button is pressed
3025996b 218 * @param sender the sender object
219 * @param selector message type and id
220 * @param data event related data
221 * @return
222 */
eb5d5b4c 223long GUI::onGenerate(FXObject *sender, FXSelector selector, void *data)
3025996b 224{
881fc6e5 225 widgetsToLsystem();
3025996b 226
227 // generate and render L-system to screen
228 _lsystem->generate();
229 _renderingSurface->draw();
230}
6e40d5eb 231
232
233
234/**
eb5d5b4c 235 * Called by the system when the File->Load menu command is selected
236 * @param sender the sender object
237 * @param selector message type and id
238 * @param data event related data
239 * @return
240 */
241long GUI::onLoad(FXObject *sender, FXSelector selector, void *data)
242{
243 FXString loadFilename = FXFileDialog::getOpenFilename(this, "Load file...", ".");
244
245 if (!loadFilename.empty())
246 {
247 // load from disk
881fc6e5 248 _lsystemParameters.load(_lsystem, loadFilename.text());
249 lsystemToWidgets();
eb5d5b4c 250 }
251}
252
253
254
255/**
256 * Called by the system when the File->Save menu command is selected
257 * @param sender the sender object
258 * @param selector message type and id
259 * @param data event related data
260 * @return
261 */
262long GUI::onSave(FXObject *sender, FXSelector selector, void *data)
263{
264 FXString saveFilename = FXFileDialog::getSaveFilename(this, "Save file...", ".");
265
881fc6e5 266 if (!saveFilename.empty())
eb5d5b4c 267 {
881fc6e5 268 if (FXStat::exists(saveFilename))
269 {
270 // the file already exists
271
272 if (MBOX_CLICKED_YES == FXMessageBox::question(this, MBOX_YES_NO, "Overwrite file", "Overwrite existing file?"))
273 {
274 // overwrite file
275 widgetsToLsystem();
276 _lsystemParameters.save(_lsystem, saveFilename.text());
277 }
278 }
279 else
eb5d5b4c 280 {
881fc6e5 281 // save to file
282 widgetsToLsystem();
eb5d5b4c 283 _lsystemParameters.save(_lsystem, saveFilename.text());
284 }
285 }
eb5d5b4c 286}
287
288
289
290/**
291 * Called by the system when the close button or the File->Quit command item is selected
292 * @param sender the sender object
293 * @param selector message type and id
294 * @param data event related data
295 * @return
296 */
297long GUI::onQuit(FXObject *sender, FXSelector selector, void *data)
298{
299 getApp()->exit(0);
300
301 return 1;
302}
303
304
305
306/**
307 * Called by the system when the Help->Usage menu command is selected
308 * @param sender the sender object
309 * @param selector message type and id
310 * @param data event related data
311 * @return
312 */
313long GUI::onHelpUsage(FXObject *sender, FXSelector selector, void *data)
314{
315 _helpUsageMessageBox->show(PLACEMENT_OWNER);
316}
317
318
319
320/**
321 * Called by the system when the Help->Rules menu command is selected
6e40d5eb 322 * @param sender the sender object
323 * @param selector message type and id
324 * @param data event related data
325 * @return
326 */
eb5d5b4c 327long GUI::onHelpRules(FXObject *sender, FXSelector selector, void *data)
6e40d5eb 328{
eb5d5b4c 329 _helpRulesMessageBox->show(PLACEMENT_OWNER);
330}
331
332
333
334/**
881fc6e5 335 * Put content of widgets into the L-system
eb5d5b4c 336 */
881fc6e5 337void GUI::widgetsToLsystem()
eb5d5b4c 338{
881fc6e5 339 // clear L-system
340 _lsystem->clear();
341
342
343 // axiom
344 Rule axiom("axiom", _axiomTextField->getText().text(), 1.0);
345 _lsystem->setAxiom(axiom);
346
347 // rules
348 string ruleSet = _rulesText->getText().text();
349 string currentRule;
350 for (int i = 0; i < ruleSet.size(); i++)
351 {
352 if (ruleSet[i] != '\n')
353 {
354 // rule component
355 currentRule += ruleSet[i];
356
357 if (i == ruleSet.size() - 1)
358 {
359 // last char in whole rule string
360 _lsystem->setRule(Rule(currentRule));
361 currentRule.clear();
362 }
363 }
364 else
365 {
366 // last char in current rule
367 _lsystem->setRule(Rule(currentRule));
368 currentRule.clear();
369 }
370 }
371
372 // angle
373 _lsystem->setAngle(_angleRealSpinner->getValue());
eb5d5b4c 374
881fc6e5 375 // depth
376 _lsystem->setDepth(_depthSpinner->getValue());
eb5d5b4c 377
881fc6e5 378 // segment diameter
379 _lsystem->setDiameter(_diameterRealSpinner->getValue());
eb5d5b4c 380
881fc6e5 381 // segment diameter factor
382 _lsystem->setDiameterFactor(_diameterFactorRealSpinner->getValue());
383}
384
385
386
387/**
388 * Put content of the L-system into widgets
389 */
390void GUI::lsystemToWidgets()
391{
eb5d5b4c 392 // axiom
393 _axiomTextField->setText(_lsystem->getAxiom().getContent().c_str());
394
395 // rules
396 rulemap rules = _lsystem->getRules().getRules();
397 string rulesString;
398 for (rulemap::iterator it = rules.begin(); it != rules.end(); ++it)
399 {
400 rulesString += it->second.toString();
401 rulesString += '\n';
402 }
403 _rulesText->setText(rulesString.c_str(), rulesString.size());
404
405 // angle
406 _angleRealSpinner->setValue(_lsystem->getAngle());
407
408 // depth
409 _depthSpinner->setValue(_lsystem->getDepth());
410
411 // segment diameter
412 _diameterRealSpinner->setValue(_lsystem->getDiameter());
413
414 // segment diameter factor
415 _diameterFactorRealSpinner->setValue(_lsystem->getDiameterFactor());
6e40d5eb 416}