1 #ifndef NO_EDITOR
2 #include <boost/bind.hpp>
3 
4 #include <algorithm>
5 
6 #include "border_widget.hpp"
7 #include "button.hpp"
8 #include "code_editor_dialog.hpp"
9 #include "code_editor_widget.hpp"
10 #include "custom_object.hpp"
11 #include "custom_object_callable.hpp"
12 #include "custom_object_type.hpp"
13 #include "drag_widget.hpp"
14 #include "filesystem.hpp"
15 #include "font.hpp"
16 #include "foreach.hpp"
17 #include "formatter.hpp"
18 #include "formula_function_registry.hpp"
19 #include "frame.hpp"
20 #include "image_widget.hpp"
21 #include "json_parser.hpp"
22 #include "label.hpp"
23 #include "level.hpp"
24 #include "module.hpp"
25 #include "object_events.hpp"
26 #include "raster.hpp"
27 #include "surface_cache.hpp"
28 #include "text_editor_widget.hpp"
29 #include "tile_map.hpp"
30 #include "tileset_editor_dialog.hpp"
31 #include "unit_test.hpp"
32 
33 std::set<level*>& get_all_levels_set();
34 
code_editor_dialog(const rect & r)35 code_editor_dialog::code_editor_dialog(const rect& r)
36   : dialog(r.x(), r.y(), r.w(), r.h()), invalidated_(0), modified_(false),
37     suggestions_prefix_(-1)
38 {
39 	init();
40 }
41 
init()42 void code_editor_dialog::init()
43 {
44 	clear();
45 
46 	using namespace gui;
47 
48 	if(!editor_) {
49 		editor_.reset(new code_editor_widget(width() - 40, height() - 60));
50 	}
51 
52 	button* save_button = new button("Save", boost::bind(&code_editor_dialog::save, this));
53 	button* increase_font = new button("+", boost::bind(&code_editor_dialog::change_font_size, this, 1));
54 	button* decrease_font = new button("-", boost::bind(&code_editor_dialog::change_font_size, this, -1));
55 
56 	//std::cerr << "CED: " << x() << "," << y() << "; " << width() << "," << height() << std::endl;
57 	drag_widget* dragger = new drag_widget(x(), y(), width(), height(),
58 		drag_widget::DRAG_HORIZONTAL, [](int, int){},
59 		boost::bind(&code_editor_dialog::on_drag_end, this, _1, _2),
60 		boost::bind(&code_editor_dialog::on_drag, this, _1, _2));
61 
62 	search_ = new text_editor_widget(120);
63 	replace_ = new text_editor_widget(120);
64 	const SDL_Color col = {255,255,255,255};
65 	widget_ptr find_label(label::create("Find: ", col));
66 	replace_label_ = label::create("Replace: ", col);
67 	status_label_ = label::create("Ok", col);
68 	error_label_ = label::create("", col);
69 	add_widget(find_label, 42, 12, MOVE_RIGHT);
70 	add_widget(widget_ptr(search_), MOVE_RIGHT);
71 	add_widget(replace_label_, MOVE_RIGHT);
72 	add_widget(widget_ptr(replace_), MOVE_RIGHT);
73 	add_widget(widget_ptr(save_button), MOVE_RIGHT);
74 	add_widget(widget_ptr(increase_font), MOVE_RIGHT);
75 	add_widget(widget_ptr(decrease_font), MOVE_RIGHT);
76 	add_widget(editor_, find_label->x(), find_label->y() + save_button->height() + 2);
77 	add_widget(status_label_);
78 	add_widget(error_label_, status_label_->x() + 480, status_label_->y());
79 	add_widget(widget_ptr(dragger));
80 
81 	replace_label_->set_visible(false);
82 	replace_->set_visible(false);
83 
84 	search_->set_on_tab_handler(boost::bind(&code_editor_dialog::on_tab, this));
85 	replace_->set_on_tab_handler(boost::bind(&code_editor_dialog::on_tab, this));
86 
87 	search_->set_on_change_handler(boost::bind(&code_editor_dialog::on_search_changed, this));
88 	search_->set_on_enter_handler(boost::bind(&code_editor_dialog::on_search_enter, this));
89 	replace_->set_on_enter_handler(boost::bind(&code_editor_dialog::on_replace_enter, this));
90 
91 
92 	init_files_grid();
93 }
94 
init_files_grid()95 void code_editor_dialog::init_files_grid()
96 {
97 	if(files_grid_) {
98 		remove_widget(files_grid_);
99 	}
100 
101 	if(files_.empty()) {
102 		return;
103 	}
104 
105 	using namespace gui;
106 
107 	files_grid_.reset(new grid(1));
108 	files_grid_->allow_selection();
109 	files_grid_->register_selection_callback(boost::bind(&code_editor_dialog::select_file, this, _1));
110 	foreach(const KnownFile& f, files_) {
111 		if(f.anim) {
112 			image_widget* img = new image_widget(f.anim->img());
113 			img->set_dim(42, 42);
114 			img->set_area(f.anim->area());
115 
116 			files_grid_->add_col(widget_ptr(img));
117 		} else {
118 			std::string fname = f.fname;
119 			while(std::find(fname.begin(), fname.end(), '/') != fname.end()) {
120 				fname.erase(fname.begin(), std::find(fname.begin(), fname.end(), '/')+1);
121 			}
122 			if(fname.size() > 6) {
123 				fname.resize(6);
124 			}
125 
126 			files_grid_->add_col(label::create(fname, graphics::color_white()));
127 		}
128 	}
129 
130 	add_widget(files_grid_, 2, 2);
131 }
132 
load_file(std::string fname,bool focus)133 void code_editor_dialog::load_file(std::string fname, bool focus)
134 {
135 	if(fname_ == fname) {
136 		return;
137 	}
138 
139 	using namespace gui;
140 
141 	int index = 0;
142 	foreach(const KnownFile& f, files_) {
143 		if(f.fname == fname) {
144 			break;
145 		}
146 
147 		++index;
148 	}
149 
150 	if(index == files_.size()) {
151 		KnownFile f;
152 		f.fname = fname;
153 		f.editor.reset(new code_editor_widget(width() - 40, height() - 60));
154 		std::string text = json::get_file_contents(fname);
155 		try {
156 			variant doc = json::parse(text, json::JSON_NO_PREPROCESSOR);
157 			std::cerr << "CHECKING FOR PROTOTYPES: " << doc["prototype"].write_json() << "\n";
158 			if(doc["prototype"].is_list()) {
159 				std::map<std::string,std::string> paths;
160 				module::get_unique_filenames_under_dir("data/object_prototypes", &paths);
161 				foreach(variant proto, doc["prototype"].as_list()) {
162 					std::string name = proto.as_string() + ".cfg";
163 					std::map<std::string,std::string>::const_iterator itor = module::find(paths, name);
164 					if(itor != paths.end()) {
165 						load_file(itor->second, false);
166 					}
167 				}
168 			}
169 		} catch(...) {
170 		}
171 
172 		f.editor->set_text(json::get_file_contents(fname));
173 		f.editor->set_on_change_handler(boost::bind(&code_editor_dialog::on_code_changed, this));
174 		f.editor->set_on_move_cursor_handler(boost::bind(&code_editor_dialog::on_move_cursor, this));
175 
176 		foreach(const std::string& obj_type, custom_object_type::get_all_ids()) {
177 			const std::string* path = custom_object_type::get_object_path(obj_type + ".cfg");
178 			if(path && *path == fname) {
179 				f.anim.reset(new frame(custom_object_type::get(obj_type)->default_frame()));
180 				break;
181 			}
182 		}
183 
184 		//in case any files have been inserted, update the index.
185 		index = files_.size();
186 		files_.push_back(f);
187 	}
188 
189 	KnownFile f = files_[index];
190 
191 	if(editor_) {
192 		f.editor->set_font_size(editor_->get_font_size());
193 	}
194 
195 	if(!focus) {
196 		return;
197 	}
198 
199 	files_.erase(files_.begin() + index);
200 	files_.insert(files_.begin(), f);
201 
202 	add_widget(f.editor, editor_->x(), editor_->y());
203 	remove_widget(editor_);
204 
205 	editor_ = f.editor;
206 	editor_->set_focus(true);
207 
208 	init_files_grid();
209 
210 	fname_ = fname;
211 
212 	modified_ = editor_->text() != sys::read_file(module::map_file(fname));
213 	on_move_cursor();
214 }
215 
select_file(int index)216 void code_editor_dialog::select_file(int index)
217 {
218 	if(index < 0 || index >= files_.size()) {
219 		return;
220 	}
221 
222 	std::cerr << "select file " << index << " -> " << files_[index].fname << "\n";
223 
224 	load_file(files_[index].fname);
225 }
226 
has_keyboard_focus() const227 bool code_editor_dialog::has_keyboard_focus() const
228 {
229 	return editor_->has_focus() || search_->has_focus() || replace_->has_focus();
230 }
231 
handle_event(const SDL_Event & event,bool claimed)232 bool code_editor_dialog::handle_event(const SDL_Event& event, bool claimed)
233 {
234 	if(animation_preview_) {
235 		claimed = animation_preview_->process_event(event, claimed) || claimed;
236 		if(claimed) {
237 			return claimed;
238 		}
239 	}
240 
241 	if(suggestions_grid_) {
242 		//since an event could cause removal of the suggestions grid,
243 		//make sure the object doesn't get cleaned up until after the event.
244 		gui::widget_ptr suggestions = suggestions_grid_;
245 		claimed = suggestions->process_event(event, claimed) || claimed;
246 		if(claimed) {
247 			return claimed;
248 		}
249 	}
250 
251 	claimed = claimed || dialog::handle_event(event, claimed);
252 	if(claimed) {
253 		return claimed;
254 	}
255 
256 	if(has_keyboard_focus()) {
257 		switch(event.type) {
258 		case SDL_KEYDOWN: {
259 			if(event.key.keysym.sym == SDLK_f && (event.key.keysym.mod&KMOD_CTRL)) {
260 				search_->set_focus(true);
261 				replace_->set_focus(false);
262 				editor_->set_focus(false);
263 				return true;
264 			} else if(event.key.keysym.sym == SDLK_s && (event.key.keysym.mod&KMOD_CTRL)) {
265 				save();
266 				return true;
267 			} else if(event.key.keysym.sym == SDLK_TAB && (event.key.keysym.mod&KMOD_CTRL) && files_grid_) {
268 				if(!files_grid_->has_must_select()) {
269 					files_grid_->must_select(true, 1);
270 				} else {
271 					files_grid_->must_select(true, (files_grid_->selection()+1)%files_.size());
272 				}
273 			}
274 			break;
275 		}
276 		case SDL_KEYUP: {
277 			if(files_grid_ && files_grid_->has_must_select() && (event.key.keysym.sym == SDLK_LCTRL || event.key.keysym.sym == SDLK_RCTRL)) {
278 				select_file(files_grid_->selection());
279 			}
280 			break;
281 		}
282 		}
283 	}
284 
285 	return claimed;
286 }
287 
handle_draw_children() const288 void code_editor_dialog::handle_draw_children() const
289 {
290 	dialog::handle_draw_children();
291 	if(animation_preview_) {
292 		animation_preview_->draw();
293 	}
294 
295 	if(suggestions_grid_) {
296 		suggestions_grid_->draw();
297 	}
298 }
299 
change_font_size(int amount)300 void code_editor_dialog::change_font_size(int amount)
301 {
302 	if(editor_) {
303 		editor_->change_font_size(amount);
304 	}
305 }
306 
process()307 void code_editor_dialog::process()
308 {
309 	using namespace gui;
310 
311 	if(invalidated_ && SDL_GetTicks() > invalidated_ + 200) {
312 		try {
313 			custom_object::reset_current_debug_error();
314 
315 #if defined(USE_GLES2)
316 			gles2::shader::get_and_clear_runtime_error();
317 #endif
318 
319 
320 			if(strstr(fname_.c_str(), "/tiles/")) {
321 				std::cerr << "INIT TILE MAP\n";
322 
323 				const std::string old_contents = json::get_file_contents(fname_);
324 
325 				//verify the text is parseable before we bother setting it.
326 				json::parse(editor_->text());
327 				json::set_file_contents(fname_, editor_->text());
328 				const variant tiles_data = json::parse_from_file("data/tiles.cfg");
329 				tile_map::prepare_rebuild_all();
330 				try {
331 					std::cerr << "tile_map::init()\n";
332 					tile_map::init(tiles_data);
333 					tile_map::rebuild_all();
334 					std::cerr << "done tile_map::init()\n";
335 					editor_dialogs::tileset_editor_dialog::global_tile_update();
336 					foreach(level* lvl, get_all_levels_set()) {
337 						lvl->rebuild_tiles();
338 					}
339 				} catch(...) {
340 					json::set_file_contents(fname_, old_contents);
341 					const variant tiles_data = json::parse_from_file("data/tiles.cfg");
342 					tile_map::init(tiles_data);
343 					tile_map::rebuild_all();
344 					editor_dialogs::tileset_editor_dialog::global_tile_update();
345 					foreach(level* lvl, get_all_levels_set()) {
346 						lvl->rebuild_tiles();
347 					}
348 					throw;
349 				}
350 				std::cerr << "INIT TILE MAP OK\n";
351 #if defined(USE_GLES2)
352 			} else if(strstr(fname_.c_str(), "data/shaders.cfg")) {
353 				std::cerr << "CODE_EDIT_DIALOG FILE: " << fname_ << std::endl;
354 				gles2::program::load_shaders(editor_->text());
355 				foreach(level* lvl, get_all_levels_set()) {
356 					lvl->shaders_updated();
357 				}
358 #endif
359 			} else {
360 				std::cerr << "SET FILE: " << fname_ << "\n";
361 				custom_object_type::set_file_contents(fname_, editor_->text());
362 			}
363 			error_label_->set_text("Ok");
364 			error_label_->set_tooltip("");
365 		} catch(validation_failure_exception& e) {
366 			error_label_->set_text("Error");
367 			error_label_->set_tooltip(e.msg);
368 		} catch(...) {
369 			error_label_->set_text("Error");
370 			error_label_->set_tooltip("Unknown error");
371 		}
372 		invalidated_ = 0;
373 	} else if(custom_object::current_debug_error()) {
374 		error_label_->set_text("Runtime Error");
375 		error_label_->set_tooltip(*custom_object::current_debug_error());
376 	}
377 
378 #if defined(USE_GLES2)
379 	const std::string shader_error = gles2::shader::get_and_clear_runtime_error();
380 	if(shader_error != "") {
381 		error_label_->set_text("Runtime Shader Error");
382 		error_label_->set_tooltip(shader_error);
383 	}
384 #endif
385 
386 	const bool show_replace = editor_->has_search_matches();
387 	replace_label_->set_visible(show_replace);
388 	replace_->set_visible(show_replace);
389 
390 	const int cursor_pos = editor_->row_col_to_text_pos(editor_->cursor_row(), editor_->cursor_col());
391 	const std::string& text = editor_->current_text();
392 
393 	const gui::code_editor_widget::ObjectInfo info = editor_->get_current_object();
394 	const json::Token* selected_token = NULL;
395 	int token_pos = 0;
396 	foreach(const json::Token& token, info.tokens) {
397 		const int begin_pos = token.begin - text.c_str();
398 		const int end_pos = token.end - text.c_str();
399 		if(cursor_pos >= begin_pos && cursor_pos <= end_pos) {
400 			token_pos = cursor_pos - begin_pos;
401 			selected_token = &token;
402 			break;
403 		}
404 	}
405 
406 	std::vector<Suggestion> suggestions;
407 	if(selected_token != NULL) {
408 
409 		const bool at_end = token_pos == selected_token->end - selected_token->begin;
410 		std::string str(selected_token->begin, selected_token->end);
411 		suggestions_prefix_ = 0;
412 		if(str.size() >= 3 && std::string(str.begin(), str.begin()+3) == "on_" && at_end) {
413 			const std::string id(str.begin()+3, str.end());
414 			for(int i = 0; i != NUM_OBJECT_BUILTIN_EVENT_IDS; ++i) {
415 				const std::string& event_str = get_object_event_str(i);
416 				if(event_str.size() >= id.size() && std::equal(id.begin(), id.end(), event_str.begin())) {
417 					Suggestion s = { "on_" + event_str, "", ": \"\",", 3 };
418 					suggestions.push_back(s);
419 				}
420 			}
421 
422 			static std::vector<std::string> animations;
423 
424 			if(info.obj.is_map() && info.obj["animation"].is_list()) {
425 				animations.clear();
426 				foreach(variant anim, info.obj["animation"].as_list()) {
427 					if(anim.is_map() && anim["id"].is_string()) {
428 						animations.push_back(anim["id"].as_string());
429 					}
430 				}
431 			}
432 
433 			foreach(const std::string& str, animations) {
434 				static const std::string types[] = {"enter", "end", "leave", "process"};
435 				foreach(const std::string& type, types) {
436 					const std::string event_str = type + "_" + str + (type == "process" ? "" : "_anim");
437 					if(event_str.size() >= id.size() && std::equal(id.begin(), id.end(), event_str.begin())) {
438 						Suggestion s = { "on_" + event_str, "", ": \"\",", 3 };
439 						suggestions.push_back(s);
440 					}
441 				}
442 			}
443 
444 			suggestions_prefix_ = str.size();
445 		} else if(selected_token->type == json::Token::TYPE_STRING) {
446 			try {
447 				const std::string formula_str(selected_token->begin, selected_token->end);
448 				std::vector<formula_tokenizer::token> tokens;
449 				std::string::const_iterator i1 = formula_str.begin();
450 				formula_tokenizer::token t = formula_tokenizer::get_token(i1, formula_str.end());
451 				while(t.type != formula_tokenizer::TOKEN_INVALID) {
452 					tokens.push_back(t);
453 					if(i1 == formula_str.end()) {
454 						break;
455 					}
456 
457 					t = formula_tokenizer::get_token(i1, formula_str.end());
458 				}
459 
460 				const formula_tokenizer::token* selected = NULL;
461 				const std::string::const_iterator itor = formula_str.begin() + token_pos;
462 
463 				foreach(const formula_tokenizer::token& tok, tokens) {
464 					if(tok.end == itor) {
465 						selected = &tok;
466 						break;
467 					}
468 				}
469 
470 				if(selected && selected->type == formula_tokenizer::TOKEN_IDENTIFIER) {
471 					const std::string identifier(selected->begin, selected->end);
472 
473 					static const custom_object_callable obj_definition;
474 					for(int n = 0; n != obj_definition.num_slots(); ++n) {
475 						const std::string id = obj_definition.get_entry(n)->id;
476 						if(id.size() > identifier.size() && std::equal(identifier.begin(), identifier.end(), id.begin())) {
477 							Suggestion s = { id, "", "", 0 };
478 							suggestions.push_back(s);
479 						}
480 					}
481 
482 					std::vector<std::string> helpstrings;
483 					foreach(const std::string& s, function_helpstrings("core")) {
484 						helpstrings.push_back(s);
485 					}
486 					foreach(const std::string& s, function_helpstrings("custom_object")) {
487 						helpstrings.push_back(s);
488 					}
489 
490 					foreach(const std::string& str, helpstrings) {
491 						std::string::const_iterator paren = std::find(str.begin(), str.end(), '(');
492 						std::string::const_iterator colon = std::find(paren, str.end(), ':');
493 						if(colon == str.end()) {
494 							continue;
495 						}
496 
497 						const std::string id(str.begin(), paren);
498 						const std::string text(str.begin(), colon);
499 						if(id.size() > identifier.size() && std::equal(identifier.begin(), identifier.end(), id.begin())) {
500 							Suggestion s = { id, text, "()", 1 };
501 							suggestions.push_back(s);
502 						}
503 					}
504 
505 					suggestions_prefix_ = identifier.size();
506 				}
507 			} catch(formula_tokenizer::token_error&) {
508 			}
509 		}
510 
511 	}
512 
513 	std::sort(suggestions.begin(), suggestions.end());
514 
515 	if(suggestions != suggestions_) {
516 		suggestions_ = suggestions;
517 		suggestions_grid_.reset();
518 
519 		if(suggestions_.empty() == false) {
520 			grid_ptr suggestions_grid(new grid(1));
521 			suggestions_grid->register_selection_callback(boost::bind(&code_editor_dialog::select_suggestion, this, _1));
522 			suggestions_grid->swallow_clicks();
523 			suggestions_grid->allow_selection(true);
524 			suggestions_grid->set_show_background(true);
525 			suggestions_grid->set_max_height(160);
526 			foreach(const Suggestion& s, suggestions_) {
527 				suggestions_grid->add_col(widget_ptr(new label(s.suggestion_text.empty() ? s.suggestion : s.suggestion_text)));
528 			}
529 
530 			suggestions_grid_.reset(new border_widget(suggestions_grid, graphics::color(255,255,255,255)));
531 		}
532 		std::cerr << "SUGGESTIONS: " << suggestions_.size() << ":\n";
533 		foreach(const Suggestion& suggestion, suggestions_) {
534 			std::cerr << " - " << suggestion.suggestion << "\n";
535 		}
536 	}
537 
538 	if(suggestions_grid_) {
539 		const std::pair<int,int> cursor_pos = editor_->char_position_on_screen(editor_->cursor_row(), editor_->cursor_col());
540 		suggestions_grid_->set_loc(x() + editor_->x() + cursor_pos.second, y() + editor_->y() + cursor_pos.first - suggestions_grid_->height());
541 
542 		if(suggestions_grid_->y() < 10) {
543 			suggestions_grid_->set_loc(suggestions_grid_->x(), suggestions_grid_->y() + suggestions_grid_->height() + 14);
544 		}
545 
546 		if(suggestions_grid_->x() + suggestions_grid_->width() + 20 > graphics::screen_width()) {
547 			suggestions_grid_->set_loc(graphics::screen_width() - suggestions_grid_->width() - 20, suggestions_grid_->y());
548 		}
549 	}
550 
551 	try {
552 		editor_->set_highlight_current_object(false);
553 		if(gui::animation_preview_widget::is_animation(info.obj)) {
554 			if(!animation_preview_) {
555 				animation_preview_.reset(new gui::animation_preview_widget(info.obj));
556 				animation_preview_->set_rect_handler(boost::bind(&code_editor_dialog::set_animation_rect, this, _1));
557 				animation_preview_->set_solid_handler(boost::bind(&code_editor_dialog::move_solid_rect, this, _1, _2));
558 				animation_preview_->set_pad_handler(boost::bind(&code_editor_dialog::set_integer_attr, this, "pad", _1));
559 				animation_preview_->set_num_frames_handler(boost::bind(&code_editor_dialog::set_integer_attr, this, "frames", _1));
560 				animation_preview_->set_frames_per_row_handler(boost::bind(&code_editor_dialog::set_integer_attr, this, "frames_per_row", _1));
561 				animation_preview_->set_loc(x() - 520, y() + 100);
562 				animation_preview_->set_dim(500, 400);
563 				animation_preview_->init();
564 			} else {
565 				animation_preview_->set_object(info.obj);
566 			}
567 
568 			editor_->set_highlight_current_object(true);
569 		} else {
570 			animation_preview_.reset();
571 		}
572 	} catch(type_error&) {
573 		if(animation_preview_) {
574 			animation_preview_.reset();
575 		}
576 	} catch(frame::error&) {
577 		// skip
578 	} catch(validation_failure_exception&) {
579 		if(animation_preview_) {
580 			animation_preview_.reset();
581 		}
582 	} catch(graphics::load_image_error&) {
583 		if(animation_preview_) {
584 			animation_preview_.reset();
585 		}
586 	}
587 
588 	if(animation_preview_) {
589 		animation_preview_->process();
590 	}
591 
592 }
593 
change_width(int amount)594 void code_editor_dialog::change_width(int amount)
595 {
596 	int new_width = width() + amount;
597 	if(new_width < 200) {
598 		new_width = 200;
599 	}
600 
601 	if(new_width > 1000) {
602 		new_width = 1000;
603 	}
604 
605 	amount = new_width - width();
606 	set_loc(x() - amount, y());
607 	set_dim(new_width, height());
608 
609 
610 	foreach(KnownFile& f, files_) {
611 		f.editor->set_dim(width() - 40, height() - 60);
612 	}
613 	init();
614 }
615 
on_drag(int dx,int dy)616 void code_editor_dialog::on_drag(int dx, int dy)
617 {
618 	int new_width = width() + dx;
619 	int min_width = int(graphics::screen_width() * 0.17);
620 	int max_width = int(graphics::screen_width() * 0.83);
621 	//std::cerr << "ON_DRAG: " << dx << ", " << min_width << ", " << max_width << std::endl;
622 	if(new_width < min_width) {
623 		new_width = min_width;
624 	}
625 
626 	if(new_width > max_width) {
627 		new_width = max_width;
628 	}
629 
630 	dx = new_width - width();
631 	set_loc(x() - dx, y());
632 	set_dim(new_width, height());
633 
634 
635 	foreach(KnownFile& f, files_) {
636 		f.editor->set_dim(width() - 40, height() - 60);
637 	}
638 	//init();
639 }
640 
on_drag_end(int x,int y)641 void code_editor_dialog::on_drag_end(int x, int y)
642 {
643 	init();
644 }
645 
on_tab()646 void code_editor_dialog::on_tab()
647 {
648 	if(search_->has_focus()) {
649 		search_->set_focus(false);
650 		if(editor_->has_search_matches()) {
651 			replace_->set_focus(true);
652 		} else {
653 			editor_->set_focus(true);
654 		}
655 	} else if(replace_->has_focus()) {
656 		replace_->set_focus(false);
657 		editor_->set_focus(true);
658 	}
659 }
660 
on_search_changed()661 void code_editor_dialog::on_search_changed()
662 {
663 	editor_->set_search(search_->text());
664 }
665 
on_search_enter()666 void code_editor_dialog::on_search_enter()
667 {
668 	editor_->next_search_match();
669 }
670 
on_replace_enter()671 void code_editor_dialog::on_replace_enter()
672 {
673 	editor_->replace(replace_->text());
674 }
675 
on_code_changed()676 void code_editor_dialog::on_code_changed()
677 {
678 	if(!modified_) {
679 		modified_ = true;
680 		on_move_cursor();
681 	}
682 
683 	if(!invalidated_) {
684 		invalidated_ = SDL_GetTicks();
685 		error_label_->set_text("Processing...");
686 	}
687 }
688 
on_move_cursor()689 void code_editor_dialog::on_move_cursor()
690 {
691 	status_label_->set_text(formatter() << "Line " << (editor_->cursor_row()+1) << " Col " << (editor_->cursor_col()+1) << (modified_ ? " (Modified)" : ""));
692 }
693 
set_animation_rect(rect r)694 void code_editor_dialog::set_animation_rect(rect r)
695 {
696 	const gui::code_editor_widget::ObjectInfo info = editor_->get_current_object();
697 	variant v = info.obj;
698 	if(v.is_null() == false) {
699 		v.add_attr(variant("rect"), r.write());
700 		editor_->modify_current_object(v);
701 		try {
702 			animation_preview_->set_object(v);
703 		} catch(frame::error& e) {
704 		}
705 	}
706 }
707 
move_solid_rect(int dx,int dy)708 void code_editor_dialog::move_solid_rect(int dx, int dy)
709 {
710 	const gui::code_editor_widget::ObjectInfo info = editor_->get_current_object();
711 	variant v = info.obj;
712 	if(v.is_null() == false) {
713 		variant solid_area = v["solid_area"];
714 		if(!solid_area.is_list() || solid_area.num_elements() != 4) {
715 			return;
716 		}
717 
718 		foreach(const variant& num, solid_area.as_list()) {
719 			if(!num.is_int()) {
720 				return;
721 			}
722 		}
723 
724 		rect area(solid_area);
725 		area = rect(area.x() + dx, area.y() + dy, area.w(), area.h());
726 		v.add_attr(variant("solid_area"), area.write());
727 		editor_->modify_current_object(v);
728 		try {
729 			animation_preview_->set_object(v);
730 		} catch(frame::error& e) {
731 		}
732 	}
733 }
734 
set_integer_attr(const char * attr,int value)735 void code_editor_dialog::set_integer_attr(const char* attr, int value)
736 {
737 	const gui::code_editor_widget::ObjectInfo info = editor_->get_current_object();
738 	variant v = info.obj;
739 	if(v.is_null() == false) {
740 		v.add_attr(variant(attr), variant(value));
741 		editor_->modify_current_object(v);
742 		try {
743 			animation_preview_->set_object(v);
744 		} catch(frame::error& e) {
745 		}
746 	}
747 }
748 
save()749 void code_editor_dialog::save()
750 {
751 	sys::write_file(module::map_file(fname_), editor_->text());
752 	status_label_->set_text(formatter() << "Saved " << fname_);
753 	modified_ = false;
754 }
755 
select_suggestion(int index)756 void code_editor_dialog::select_suggestion(int index)
757 {
758 	if(index >= 0 && index < suggestions_.size()) {
759 		std::cerr << "SELECT " << suggestions_[index].suggestion << "\n";
760 		const std::string& str = suggestions_[index].suggestion;
761 		if(suggestions_prefix_ >= 0 && suggestions_prefix_ < str.size()) {
762 			const int col = editor_->cursor_col();
763 			const std::string insert(str.begin() + suggestions_prefix_, str.end());
764 			const std::string postfix = suggestions_[index].postfix;
765 			const std::string row = editor_->get_data()[editor_->cursor_row()];
766 			const std::string new_row = std::string(row.begin(), row.begin() + editor_->cursor_col()) + insert + postfix + std::string(row.begin() + editor_->cursor_col(), row.end());
767 			editor_->set_row_contents(editor_->cursor_row(), new_row);
768 			editor_->set_cursor(editor_->cursor_row(), col + insert.size() + suggestions_[index].postfix_index);
769 		}
770 	} else {
771 		suggestions_grid_.reset();
772 	}
773 }
774 
COMMAND_LINE_UTILITY(codeedit)775 COMMAND_LINE_UTILITY(codeedit)
776 {
777 	SDL_Init(SDL_INIT_VIDEO);
778 	SDL_SetVideoMode(600, 600, 0, SDL_OPENGL|SDL_RESIZABLE);
779 #ifdef USE_GLES2
780 	glViewport(0, 0, 600, 600);
781 	glEnable(GL_BLEND);
782 	glEnable(GL_TEXTURE_2D);
783 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
784 #else
785 	glShadeModel(GL_SMOOTH);
786 	glEnable(GL_BLEND);
787 	glEnable(GL_TEXTURE_2D);
788 	glEnableClientState(GL_VERTEX_ARRAY);
789 	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
790 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
791 #endif
792 
793 	const font::manager font_manager;
794 	graphics::texture::manager texture_manager;
795 
796 	variant gui_node = json::parse_from_file("data/gui.cfg");
797 	gui_section::init(gui_node);
798 
799 	framed_gui_element::init(gui_node);
800 
801 	code_editor_dialog d(rect(0,0,600,600));
802 	std::cerr << "CREATE DIALOG\n";
803 	if(args.empty() == false) {
804 		d.load_file(args[0]);
805 	}
806 
807 	d.show_modal();
808 
809 
810 	SDL_Quit();
811 }
812 
813 #endif // NO_EDITOR
814