File save dialog bugfix.
[lsystem3d.git] / src / gui.cpp
1 // Copyright (C) 2006 Erik Dahlberg
2 //
3 // This file is part of LSystem3D.
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
22 #include <sstream>
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"
31 #include "rule.h"
32 #include "ruleset.h"
33
34 using namespace std;
35
36
37
38 // message map
39 FXDEFMAP(GUI) GUIMap[] =
40 {
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)
48 };
49
50 // macro to set up class implementation
51 FXIMPLEMENT(GUI, FXMainWindow, GUIMap, ARRAYNUMBER(GUIMap))
52
53
54
55 /**
56 * Constructor
57 * @param application the application object
58 * @param renderingSurface the rendering surface
59 * @param lsystem the Lindenmayer system generator
60 */
61 GUI::GUI(FXApp *application, RenderingSurface *renderingSurface, LindenmayerSystem *lsystem) : FXMainWindow(application, "LSystem3D" , NULL, NULL, DECOR_ALL, 920, 100, 0, 0)
62 {
63 _renderingSurface = renderingSurface;
64 _lsystem = lsystem;
65
66
67
68 // create the widgets
69
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
87 // the main frame
88 FXVerticalFrame *mainFrame = new FXVerticalFrame(this, LAYOUT_FILL_X);
89
90
91 // axiom text field
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);
95
96 // rules multiline text field
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);
102
103 // angle spinner
104 FXHorizontalFrame *angleFrame = new FXHorizontalFrame(mainFrame);
105 new FXLabel(angleFrame, "Angle:");
106 _angleRealSpinner = new FXRealSpinner(angleFrame, 5);
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
114 FXHorizontalFrame *diameterFrame = new FXHorizontalFrame(mainFrame);
115 new FXLabel(diameterFrame, "Diameter:");
116 _diameterRealSpinner = new FXRealSpinner(diameterFrame, 5);
117 _diameterRealSpinner->setValue(0.1);
118 _diameterRealSpinner->setIncrement(0.01);
119
120 // diameter factor spinner
121 new FXLabel(diameterFrame, "Factor:");
122 _diameterFactorRealSpinner = new FXRealSpinner(diameterFrame, 5);
123 _diameterFactorRealSpinner->setValue(1.0);
124 _diameterFactorRealSpinner->setIncrement(0.01);
125
126
127 // separator
128 new FXHorizontalSeparator(mainFrame, SEPARATOR_RIDGE | LAYOUT_FILL_X);
129
130
131 // generate button
132 new FXButton(mainFrame, "&Generate", NULL, this, GUI::ID_GENERATE);
133
134
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";
150
151 _helpUsageMessageBox = new FXMessageBox(getApp(), "Usage", usageHelpText.str().c_str(), NULL, MBOX_OK);
152
153
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 // load default L-system
187 _lsystemParameters.load(_lsystem, "lsystem.xml");
188 lsystemToWidgets();
189 }
190
191
192
193 /**
194 * Destructor
195 */
196 GUI::~GUI()
197 {
198 delete _fileMenuPane;
199 delete _helpMenuPane;
200 }
201
202
203
204 /**
205 * Create and initialize the window
206 */
207 void GUI::create()
208 {
209 // create the window and make it appear
210 FXMainWindow::create();
211 show();
212 }
213
214
215
216 /**
217 * Called by the system when the "Generate" button is pressed
218 * @param sender the sender object
219 * @param selector message type and id
220 * @param data event related data
221 * @return
222 */
223 long GUI::onGenerate(FXObject *sender, FXSelector selector, void *data)
224 {
225 widgetsToLsystem();
226
227 // generate and render L-system to screen
228 _lsystem->generate();
229 _renderingSurface->draw();
230 }
231
232
233
234 /**
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 */
241 long 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
248 _lsystemParameters.load(_lsystem, loadFilename.text());
249 lsystemToWidgets();
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 */
262 long GUI::onSave(FXObject *sender, FXSelector selector, void *data)
263 {
264 FXString saveFilename = FXFileDialog::getSaveFilename(this, "Save file...", ".");
265
266 if (!saveFilename.empty())
267 {
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
280 {
281 // save to file
282 widgetsToLsystem();
283 _lsystemParameters.save(_lsystem, saveFilename.text());
284 }
285 }
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 */
297 long 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 */
313 long 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
322 * @param sender the sender object
323 * @param selector message type and id
324 * @param data event related data
325 * @return
326 */
327 long GUI::onHelpRules(FXObject *sender, FXSelector selector, void *data)
328 {
329 _helpRulesMessageBox->show(PLACEMENT_OWNER);
330 }
331
332
333
334 /**
335 * Put content of widgets into the L-system
336 */
337 void GUI::widgetsToLsystem()
338 {
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());
374
375 // depth
376 _lsystem->setDepth(_depthSpinner->getValue());
377
378 // segment diameter
379 _lsystem->setDiameter(_diameterRealSpinner->getValue());
380
381 // segment diameter factor
382 _lsystem->setDiameterFactor(_diameterFactorRealSpinner->getValue());
383 }
384
385
386
387 /**
388 * Put content of the L-system into widgets
389 */
390 void GUI::lsystemToWidgets()
391 {
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());
416 }