1 //------------------------------------------------------------------------
2 //  LEVEL MISC STUFF
3 //------------------------------------------------------------------------
4 //
5 //  Eureka DOOM Editor
6 //
7 //  Copyright (C) 2001-2019 Andrew Apted
8 //  Copyright (C) 1997-2003 Andr� Majorel et al
9 //
10 //  This program is free software; you can redistribute it and/or
11 //  modify it under the terms of the GNU General Public License
12 //  as published by the Free Software Foundation; either version 2
13 //  of the License, or (at your option) any later version.
14 //
15 //  This program is distributed in the hope that it will be useful,
16 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
17 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 //  GNU General Public License for more details.
19 //
20 //------------------------------------------------------------------------
21 //
22 //  Based on Yadex which incorporated code from DEU 5.21 that was put
23 //  in the public domain in 1994 by Rapha�l Quinet and Brendon Wyber.
24 //
25 //------------------------------------------------------------------------
26 
27 #include "main.h"
28 
29 #include "m_bitvec.h"
30 #include "m_game.h"
31 #include "e_hover.h"
32 #include "e_linedef.h"
33 #include "e_main.h"
34 #include "e_path.h"
35 #include "e_things.h"
36 #include "e_vertex.h"
37 #include "r_render.h"
38 #include "r_subdiv.h"
39 #include "w_rawdef.h"
40 
41 #include "ui_window.h"
42 
43 
44 Editor_State_t  edit;
45 
46 
47 double Map_bound_x1 =  32767;   /* minimum X value of map */
48 double Map_bound_y1 =  32767;   /* minimum Y value of map */
49 double Map_bound_x2 = -32767;   /* maximum X value of map */
50 double Map_bound_y2 = -32767;   /* maximum Y value of map */
51 
52 int MadeChanges;
53 
54 static bool recalc_map_bounds;
55 static int  new_vertex_minimum;
56 static int  moved_vertex_count;
57 
58 static selection_c * last_Sel;
59 
60 extern bool sound_propagation_invalid;
61 
62 
63 // config items
64 int default_edit_mode = 3;  // Vertices
65 
66 bool same_mode_clears_selection = false;
67 
68 int sector_render_default = (int)SREND_Floor;
69 int  thing_render_default = 1;
70 
71 
72 //
73 // adjust zoom factor to make whole map fit in window
74 //
zoom_fit()75 static void zoom_fit()
76 {
77 	if (NumVertices == 0)
78 		return;
79 
80 	double xzoom = 1;
81 	double yzoom = 1;
82 
83 	int ScrMaxX = main_win->canvas->w();
84 	int ScrMaxY = main_win->canvas->h();
85 
86 	if (Map_bound_x1 < Map_bound_x2)
87 		xzoom = ScrMaxX / (Map_bound_x2 - Map_bound_x1);
88 
89 	if (Map_bound_y1 < Map_bound_y2)
90 		yzoom = ScrMaxY / (Map_bound_y2 - Map_bound_y1);
91 
92 	grid.NearestScale(MIN(xzoom, yzoom));
93 
94 	grid.MoveTo((Map_bound_x1 + Map_bound_x2) / 2, (Map_bound_y1 + Map_bound_y2) / 2);
95 }
96 
97 
ZoomWholeMap()98 void ZoomWholeMap()
99 {
100 	if (MadeChanges)
101 		CalculateLevelBounds();
102 
103 	zoom_fit();
104 
105 	RedrawMap();
106 }
107 
108 
RedrawMap()109 void RedrawMap()
110 {
111 	if (! main_win)
112 		return;
113 
114 	UpdateHighlight();
115 
116 	main_win->scroll->UpdateRenderMode();
117 	main_win->info_bar->UpdateSecRend();
118 	main_win->status_bar->redraw();
119 	main_win->canvas->redraw();
120 }
121 
122 
UpdatePanel()123 static void UpdatePanel()
124 {
125 	// -AJA- I think the highlighted object is always the same type as
126 	//       the current editing mode.  But do this check for safety.
127 	if (edit.highlight.valid() &&
128 		edit.highlight.type != edit.mode)
129 		return;
130 
131 
132 	// Choose object to show in right-hand panel:
133 	//   - the highlighted object takes priority
134 	//   - otherwise show the selection (first + count)
135 	//
136 	// It's a little more complicated since highlight may or may not
137 	// be part of the selection.
138 
139 	int obj_idx   = edit.highlight.num;
140 	int obj_count = edit.Selected->count_obj();
141 
142 	// the highlight is usually turned off when dragging, so compensate
143 	if (obj_idx < 0 && edit.action == ACT_DRAG)
144 		obj_idx = edit.dragged.num;
145 
146 	if (obj_idx >= 0)
147 	{
148 		if (! edit.Selected->get(obj_idx))
149 			obj_count = 0;
150 	}
151 	else if (obj_count > 0)
152 	{
153 		// in linedef mode, we prefer showing a two-sided linedef
154 		if (edit.mode == OBJ_LINEDEFS && obj_count > 1)
155 			obj_idx = Selection_FirstLine(edit.Selected);
156 		else
157 			obj_idx = edit.Selected->find_first();
158 	}
159 
160 	switch (edit.mode)
161 	{
162 		case OBJ_THINGS:
163 			main_win->thing_box->SetObj(obj_idx, obj_count);
164 			break;
165 
166 		case OBJ_LINEDEFS:
167 			main_win->line_box->SetObj(obj_idx, obj_count);
168 			break;
169 
170 		case OBJ_SECTORS:
171 			main_win->sec_box->SetObj(obj_idx, obj_count);
172 			break;
173 
174 		case OBJ_VERTICES:
175 			main_win->vert_box->SetObj(obj_idx, obj_count);
176 			break;
177 
178 		default: break;
179 	}
180 }
181 
182 
UpdateDrawLine()183 void UpdateDrawLine()
184 {
185 	if (edit.action != ACT_DRAW_LINE || edit.draw_from.is_nil())
186 		return;
187 
188 	const Vertex *V = Vertices[edit.draw_from.num];
189 
190 	double new_x = edit.map_x;
191 	double new_y = edit.map_y;
192 
193 	if (grid.ratio > 0)
194 	{
195 		grid.RatioSnapXY(new_x, new_y, V->x(), V->y());
196 	}
197 	else if (edit.highlight.valid())
198 	{
199 		new_x = Vertices[edit.highlight.num]->x();
200 		new_y = Vertices[edit.highlight.num]->y();
201 	}
202 	else if (edit.split_line.valid())
203 	{
204 		new_x = edit.split_x;
205 		new_y = edit.split_y;
206 	}
207 	else
208 	{
209 		new_x = grid.SnapX(new_x);
210 		new_y = grid.SnapY(new_y);
211 	}
212 
213 	edit.draw_to_x = new_x;
214 	edit.draw_to_y = new_y;
215 
216 	// when drawing mode, highlight a vertex at the snap position
217 	if (grid.snap && edit.highlight.is_nil() && edit.split_line.is_nil())
218 	{
219 		int near_vert = Vertex_FindExact(TO_COORD(new_x), TO_COORD(new_y));
220 		if (near_vert >= 0)
221 		{
222 			edit.highlight = Objid(OBJ_VERTICES, near_vert);
223 		}
224 	}
225 
226 	// never highlight the vertex we are drawing from
227 	if (edit.draw_from.valid() &&
228 		edit.draw_from == edit.highlight)
229 	{
230 		edit.highlight.clear();
231 	}
232 }
233 
234 
UpdateSplitLine(double map_x,double map_y)235 static void UpdateSplitLine(double map_x, double map_y)
236 {
237 	edit.split_line.clear();
238 
239 	// splitting usually disabled while dragging stuff, EXCEPT a single vertex
240 	if (edit.action == ACT_DRAG && edit.dragged.is_nil())
241 		goto done;
242 
243 	// in vertex mode, see if there is a linedef which would be split by
244 	// adding a new vertex.
245 
246 	if (edit.mode == OBJ_VERTICES &&
247 		edit.pointer_in_window &&
248 	    edit.highlight.is_nil())
249 	{
250 		FindSplitLine(edit.split_line, edit.split_x, edit.split_y,
251 					  map_x, map_y, edit.dragged.num);
252 
253 		// NOTE: OK if the split line has one of its vertices selected
254 		//       (that case is handled by Insert_Vertex)
255 	}
256 
257 done:
258 	main_win->canvas->UpdateHighlight();
259 }
260 
261 
UpdateHighlight()262 void UpdateHighlight()
263 {
264 	if (edit.render3d)
265 	{
266 		Render3D_UpdateHighlight();
267 		UpdatePanel();
268 		return;
269 	}
270 
271 	// find the object to highlight
272 	edit.highlight.clear();
273 
274 	// don't highlight when dragging, EXCEPT when dragging a single vertex
275 	if (edit.pointer_in_window &&
276 	    (edit.action != ACT_DRAG || (edit.mode == OBJ_VERTICES && edit.dragged.valid()) ))
277 	{
278 		GetNearObject(edit.highlight, edit.mode, edit.map_x, edit.map_y);
279 
280 		// guarantee that we cannot drag a vertex onto itself
281 		if (edit.action == ACT_DRAG && edit.dragged.valid() &&
282 			edit.highlight.valid() && edit.dragged.num == edit.highlight.num)
283 		{
284 			edit.highlight.clear();
285 		}
286 
287 		// if drawing a line and ratio lock is ON, only highlight a
288 		// vertex if it is *exactly* the right ratio.
289 		if (grid.ratio > 0 && edit.action == ACT_DRAW_LINE &&
290 			edit.mode == OBJ_VERTICES && edit.highlight.valid())
291 		{
292 			const Vertex *V = Vertices[edit.highlight.num];
293 			const Vertex *S = Vertices[edit.draw_from.num];
294 
295 			double vx = V->x();
296 			double vy = V->y();
297 
298 			grid.RatioSnapXY(vx, vy, S->x(), S->y());
299 
300 			if (MakeValidCoord(vx) != V->raw_x ||
301 				MakeValidCoord(vy) != V->raw_y)
302 			{
303 				edit.highlight.clear();
304 			}
305 		}
306 	}
307 
308 	UpdateSplitLine(edit.map_x, edit.map_y);
309 	UpdateDrawLine();
310 
311 	main_win->canvas->UpdateHighlight();
312 	main_win->canvas->CheckGridSnap();
313 
314 	UpdatePanel();
315 }
316 
317 
Editor_ClearErrorMode()318 void Editor_ClearErrorMode()
319 {
320 	if (edit.error_mode)
321 	{
322 		Selection_Clear();
323 	}
324 }
325 
326 
Editor_ChangeMode_Raw(obj_type_e new_mode)327 void Editor_ChangeMode_Raw(obj_type_e new_mode)
328 {
329 	// keep selection after a "Find All" and user dismisses panel
330 	if (new_mode == edit.mode && main_win->isSpecialPanelShown())
331 		edit.error_mode = false;
332 
333 	edit.mode = new_mode;
334 
335 	Editor_ClearAction();
336 	Editor_ClearErrorMode();
337 
338 	edit.highlight.clear();
339 	edit.split_line.clear();
340 }
341 
342 
Editor_ChangeMode(char mode_char)343 void Editor_ChangeMode(char mode_char)
344 {
345 	obj_type_e  prev_type = edit.mode;
346 
347 	// Set the object type according to the new mode.
348 	switch (mode_char)
349 	{
350 		case 't': Editor_ChangeMode_Raw(OBJ_THINGS);   break;
351 		case 'l': Editor_ChangeMode_Raw(OBJ_LINEDEFS); break;
352 		case 's': Editor_ChangeMode_Raw(OBJ_SECTORS);  break;
353 		case 'v': Editor_ChangeMode_Raw(OBJ_VERTICES); break;
354 
355 		default:
356 			Beep("Unknown mode: %c\n", mode_char);
357 			return;
358 	}
359 
360 	if (prev_type != edit.mode)
361 	{
362 		Selection_Push();
363 
364 		main_win->NewEditMode(edit.mode);
365 
366 		// convert the selection
367 		selection_c *prev_sel = edit.Selected;
368 		edit.Selected = new selection_c(edit.mode, true /* extended */);
369 
370 		ConvertSelection(prev_sel, edit.Selected);
371 		delete prev_sel;
372 	}
373 	else if (main_win->isSpecialPanelShown())
374 	{
375 		// same mode, but this removes the special panel
376 		main_win->NewEditMode(edit.mode);
377 	}
378 	// -AJA- Yadex (DEU?) would clear the selection if the mode didn't
379 	//       change.  We optionally emulate that behavior here.
380 	else if (same_mode_clears_selection)
381 	{
382 		Selection_Clear();
383 	}
384 
385 	RedrawMap();
386 }
387 
388 
389 //------------------------------------------------------------------------
390 
391 
UpdateLevelBounds(int start_vert)392 void UpdateLevelBounds(int start_vert)
393 {
394 	for (int i = start_vert ; i < NumVertices ; i++)
395 	{
396 		const Vertex * V = Vertices[i];
397 
398 		if (V->x() < Map_bound_x1) Map_bound_x1 = V->x();
399 		if (V->y() < Map_bound_y1) Map_bound_y1 = V->y();
400 
401 		if (V->x() > Map_bound_x2) Map_bound_x2 = V->x();
402 		if (V->y() > Map_bound_y2) Map_bound_y2 = V->y();
403 	}
404 }
405 
CalculateLevelBounds()406 void CalculateLevelBounds()
407 {
408 	if (NumVertices == 0)
409 	{
410 		Map_bound_x1 = Map_bound_x2 = 0;
411 		Map_bound_y1 = Map_bound_y2 = 0;
412 		return;
413 	}
414 
415 	Map_bound_x1 = 32767; Map_bound_x2 = -32767;
416 	Map_bound_y1 = 32767; Map_bound_y2 = -32767;
417 
418 	UpdateLevelBounds(0);
419 }
420 
421 
MapStuff_NotifyBegin()422 void MapStuff_NotifyBegin()
423 {
424 	recalc_map_bounds  = false;
425 	new_vertex_minimum = -1;
426 	moved_vertex_count =  0;
427 
428 	sound_propagation_invalid = true;
429 }
430 
MapStuff_NotifyInsert(obj_type_e type,int objnum)431 void MapStuff_NotifyInsert(obj_type_e type, int objnum)
432 {
433 	if (type == OBJ_VERTICES)
434 	{
435 		if (new_vertex_minimum < 0 || objnum < new_vertex_minimum)
436 			new_vertex_minimum = objnum;
437 	}
438 }
439 
MapStuff_NotifyDelete(obj_type_e type,int objnum)440 void MapStuff_NotifyDelete(obj_type_e type, int objnum)
441 {
442 	if (type == OBJ_VERTICES)
443 	{
444 		recalc_map_bounds = true;
445 
446 		if (edit.action == ACT_DRAW_LINE &&
447 			edit.draw_from.num == objnum)
448 		{
449 			Editor_ClearAction();
450 		}
451 	}
452 }
453 
MapStuff_NotifyChange(obj_type_e type,int objnum,int field)454 void MapStuff_NotifyChange(obj_type_e type, int objnum, int field)
455 {
456 	if (type == OBJ_VERTICES)
457 	{
458 		// NOTE: for performance reasons we don't recalculate the
459 		//       map bounds when only moving a few vertices.
460 		moved_vertex_count++;
461 
462 		const Vertex * V = Vertices[objnum];
463 
464 		if (V->x() < Map_bound_x1) Map_bound_x1 = V->x();
465 		if (V->y() < Map_bound_y1) Map_bound_y1 = V->y();
466 
467 		if (V->x() > Map_bound_x2) Map_bound_x2 = V->x();
468 		if (V->y() > Map_bound_y2) Map_bound_y2 = V->y();
469 
470 		// TODO: only invalidate sectors touching vertex
471 		Subdiv_InvalidateAll();
472 	}
473 
474 	if (type == OBJ_SIDEDEFS && field == SideDef::F_SECTOR)
475 		Subdiv_InvalidateAll();
476 
477 	if (type == OBJ_LINEDEFS && (field == LineDef::F_LEFT || field == LineDef::F_RIGHT))
478 		Subdiv_InvalidateAll();
479 
480 	if (type == OBJ_SECTORS && (field == Sector::F_FLOORH || field == Sector::F_CEILH))
481 		Subdiv_InvalidateAll();
482 }
483 
MapStuff_NotifyEnd()484 void MapStuff_NotifyEnd()
485 {
486 	if (recalc_map_bounds || moved_vertex_count > 10)  // TODO: CONFIG
487 	{
488 		CalculateLevelBounds();
489 	}
490 	else if (new_vertex_minimum >= 0)
491 	{
492 		UpdateLevelBounds(new_vertex_minimum);
493 	}
494 }
495 
496 
497 //------------------------------------------------------------------------
498 //  ObjectBox Notification handling
499 //------------------------------------------------------------------------
500 
501 static bool invalidated_totals;
502 static bool invalidated_panel_obj;
503 static bool changed_panel_obj;
504 static bool changed_recent_list;
505 
506 
ObjectBox_NotifyBegin()507 void ObjectBox_NotifyBegin()
508 {
509 	invalidated_totals = false;
510 	invalidated_panel_obj = false;
511 	changed_panel_obj = false;
512 	changed_recent_list = false;
513 }
514 
515 
ObjectBox_NotifyInsert(obj_type_e type,int objnum)516 void ObjectBox_NotifyInsert(obj_type_e type, int objnum)
517 {
518 	invalidated_totals = true;
519 
520 	if (type != edit.mode)
521 		return;
522 
523 	if (objnum > main_win->GetPanelObjNum())
524 		return;
525 
526 	invalidated_panel_obj = true;
527 }
528 
529 
ObjectBox_NotifyDelete(obj_type_e type,int objnum)530 void ObjectBox_NotifyDelete(obj_type_e type, int objnum)
531 {
532 	invalidated_totals = true;
533 
534 	if (type != edit.mode)
535 		return;
536 
537 	if (objnum > main_win->GetPanelObjNum())
538 		return;
539 
540 	invalidated_panel_obj = true;
541 }
542 
543 
ObjectBox_NotifyChange(obj_type_e type,int objnum,int field)544 void ObjectBox_NotifyChange(obj_type_e type, int objnum, int field)
545 {
546 	if (type != edit.mode)
547 		return;
548 
549 	if (objnum != main_win->GetPanelObjNum())
550 		return;
551 
552 	changed_panel_obj = true;
553 }
554 
555 
ObjectBox_NotifyEnd()556 void ObjectBox_NotifyEnd()
557 {
558 	if (invalidated_totals)
559 		main_win->UpdateTotals();
560 
561 	if (invalidated_panel_obj)
562 	{
563 		main_win->InvalidatePanelObj();
564 	}
565 	else if (changed_panel_obj)
566 	{
567 		main_win->UpdatePanelObj();
568 	}
569 
570 	if (changed_recent_list)
571 		main_win->browser->RecentUpdate();
572 }
573 
574 
575 //------------------------------------------------------------------------
576 //  Selection Notification, ETC
577 //------------------------------------------------------------------------
578 
579 static bool invalidated_selection;
580 static bool invalidated_last_sel;
581 
582 
Selection_NotifyBegin()583 void Selection_NotifyBegin()
584 {
585 	invalidated_selection = false;
586 	invalidated_last_sel  = false;
587 }
588 
589 
Selection_NotifyInsert(obj_type_e type,int objnum)590 void Selection_NotifyInsert(obj_type_e type, int objnum)
591 {
592 	if (type == edit.Selected->what_type() &&
593 		objnum <= edit.Selected->max_obj())
594 	{
595 		invalidated_selection = true;
596 	}
597 
598 	if (last_Sel &&
599 		type == last_Sel->what_type() &&
600 		objnum <= last_Sel->max_obj())
601 	{
602 		invalidated_last_sel = true;
603 	}
604 }
605 
606 
Selection_NotifyDelete(obj_type_e type,int objnum)607 void Selection_NotifyDelete(obj_type_e type, int objnum)
608 {
609 	if (objnum <= edit.Selected->max_obj())
610 	{
611 		invalidated_selection = true;
612 	}
613 
614 	if (last_Sel &&
615 		type == last_Sel->what_type() &&
616 		objnum <= last_Sel->max_obj())
617 	{
618 		invalidated_last_sel = true;
619 	}
620 }
621 
622 
Selection_NotifyChange(obj_type_e type,int objnum,int field)623 void Selection_NotifyChange(obj_type_e type, int objnum, int field)
624 {
625 	// field changes never affect the current selection
626 }
627 
628 
Selection_NotifyEnd()629 void Selection_NotifyEnd()
630 {
631 	if (invalidated_selection)
632 	{
633 		// this clears AND RESIZES the selection_c object
634 		edit.Selected->change_type(edit.mode);
635 	}
636 
637 	if (invalidated_last_sel)
638 		Selection_InvalidateLast();
639 }
640 
641 
642 //
643 //  list the contents of a selection (for debugging)
644 //
DumpSelection(selection_c * list)645 void DumpSelection(selection_c * list)
646 {
647 	SYS_ASSERT(list);
648 
649 	printf("Selection:");
650 
651 	for (sel_iter_c it(list); ! it.done(); it.next())
652 		printf(" %d", *it);
653 
654 	printf("\n");
655 }
656 
657 
ConvertSelection(selection_c * src,selection_c * dest)658 void ConvertSelection(selection_c * src, selection_c * dest)
659 {
660 	if (src->what_type() == dest->what_type())
661 	{
662 		dest->merge(*src);
663 		return;
664 	}
665 
666 
667 	if (src->what_type() == OBJ_SECTORS && dest->what_type() == OBJ_THINGS)
668 	{
669 		for (int t = 0 ; t < NumThings ; t++)
670 		{
671 			const Thing *T = Things[t];
672 
673 			Objid obj;
674 			GetNearObject(obj, OBJ_SECTORS, T->x(), T->y());
675 
676 			if (! obj.is_nil() && src->get(obj.num))
677 			{
678 				dest->set(t);
679 			}
680 		}
681 		return;
682 	}
683 
684 
685 	if (src->what_type() == OBJ_SECTORS && dest->what_type() == OBJ_LINEDEFS)
686 	{
687 		for (int l = 0 ; l < NumLineDefs ; l++)
688 		{
689 			const LineDef *L = LineDefs[l];
690 
691 			if ( (L->Right() && src->get(L->Right()->sector)) ||
692 				 (L->Left()  && src->get(L->Left()->sector)) )
693 			{
694 				dest->set(l);
695 			}
696 		}
697 		return;
698 	}
699 
700 
701 	if (src->what_type() == OBJ_SECTORS && dest->what_type() == OBJ_VERTICES)
702 	{
703 		for (int l = 0 ; l < NumLineDefs ; l++)
704 		{
705 			const LineDef *L = LineDefs[l];
706 
707 			if ( (L->Right() && src->get(L->Right()->sector)) ||
708 				 (L->Left()  && src->get(L->Left()->sector)) )
709 			{
710 				dest->set(L->start);
711 				dest->set(L->end);
712 			}
713 		}
714 		return;
715 	}
716 
717 
718 	if (src->what_type() == OBJ_LINEDEFS && dest->what_type() == OBJ_SIDEDEFS)
719 	{
720 		for (sel_iter_c it(src); ! it.done(); it.next())
721 		{
722 			const LineDef *L = LineDefs[*it];
723 
724 			if (L->Right()) dest->set(L->right);
725 			if (L->Left())  dest->set(L->left);
726 		}
727 		return;
728 	}
729 
730 	if (src->what_type() == OBJ_SECTORS && dest->what_type() == OBJ_SIDEDEFS)
731 	{
732 		for (int n = 0 ; n < NumSideDefs ; n++)
733 		{
734 			const SideDef * SD = SideDefs[n];
735 
736 			if (src->get(SD->sector))
737 				dest->set(n);
738 		}
739 		return;
740 	}
741 
742 
743 	if (src->what_type() == OBJ_LINEDEFS && dest->what_type() == OBJ_VERTICES)
744 	{
745 		for (sel_iter_c it(src); ! it.done(); it.next())
746 		{
747 			const LineDef *L = LineDefs[*it];
748 
749 			dest->set(L->start);
750 			dest->set(L->end);
751 		}
752 		return;
753 	}
754 
755 
756 	if (src->what_type() == OBJ_VERTICES && dest->what_type() == OBJ_LINEDEFS)
757 	{
758 		// select all linedefs that have both ends selected
759 		for (int l = 0 ; l < NumLineDefs ; l++)
760 		{
761 			const LineDef *L = LineDefs[l];
762 
763 			if (src->get(L->start) && src->get(L->end))
764 			{
765 				dest->set(l);
766 			}
767 		}
768 	}
769 
770 
771 	// remaining conversions are L->S and V->S
772 
773 	if (dest->what_type() != OBJ_SECTORS)
774 		return;
775 
776 	if (src->what_type() != OBJ_LINEDEFS && src->what_type() != OBJ_VERTICES)
777 		return;
778 
779 
780 	// step 1: select all sectors (except empty ones)
781 	int l;
782 
783 	for (l = 0 ; l < NumLineDefs ; l++)
784 	{
785 		const LineDef *L = LineDefs[l];
786 
787 		if (L->Right()) dest->set(L->Right()->sector);
788 		if (L->Left())  dest->set(L->Left()->sector);
789 	}
790 
791 	// step 2: unselect any sectors if a component is not selected
792 
793 	for (l = 0 ; l < NumLineDefs ; l++)
794 	{
795 		const LineDef *L = LineDefs[l];
796 
797 		if (src->what_type() == OBJ_VERTICES)
798 		{
799 			if (src->get(L->start) && src->get(L->end))
800 				continue;
801 		}
802 		else
803 		{
804 			if (src->get(l))
805 				continue;
806 		}
807 
808 		if (L->Right()) dest->clear(L->Right()->sector);
809 		if (L->Left())  dest->clear(L->Left()->sector);
810 	}
811 }
812 
813 
814 //
815 // Return the line to show in the LineDef panel from the selection.
816 // When the selection is a mix of one-sided and two-sided lines, then
817 // we want the first TWO-SIDED line.
818 //
819 // NOTE: this is slow, as it may need to search the whole list.
820 //
Selection_FirstLine(selection_c * list)821 int Selection_FirstLine(selection_c *list)
822 {
823 	for (sel_iter_c it(list); ! it.done(); it.next())
824 	{
825 		const LineDef *L = LineDefs[*it];
826 
827 		if (L->TwoSided())
828 			return *it;
829 	}
830 
831 	// return first entry (a one-sided line)
832 	return list->find_first();
833 }
834 
835 
836 //
837 // This is a helper to handle performing an operation on the
838 // selection if it is non-empty, otherwise the highlight.
839 // Returns false if both selection and highlight are empty.
840 //
Selection_Or_Highlight()841 soh_type_e Selection_Or_Highlight()
842 {
843 	if (! edit.Selected->empty())
844 		return SOH_OK;
845 
846 	if (edit.highlight.valid())
847 	{
848 		Selection_Add(edit.highlight);
849 		return SOH_Unselect;
850 	}
851 
852 	return SOH_Empty;
853 }
854 
855 
856 //
857 // select all objects inside a given box
858 //
SelectObjectsInBox(selection_c * list,int objtype,double x1,double y1,double x2,double y2)859 void SelectObjectsInBox(selection_c *list, int objtype, double x1, double y1, double x2, double y2)
860 {
861 	if (x2 < x1)
862 		std::swap(x1, x2);
863 
864 	if (y2 < y1)
865 		std::swap(y1, y2);
866 
867 	switch (objtype)
868 	{
869 		case OBJ_THINGS:
870 			for (int n = 0 ; n < NumThings ; n++)
871 			{
872 				const Thing *T = Things[n];
873 
874 				double tx = T->x();
875 				double ty = T->y();
876 
877 				if (x1 <= tx && tx <= x2 && y1 <= ty && ty <= y2)
878 				{
879 					list->toggle(n);
880 				}
881 			}
882 			break;
883 
884 		case OBJ_VERTICES:
885 			for (int n = 0 ; n < NumVertices ; n++)
886 			{
887 				const Vertex *V = Vertices[n];
888 
889 				double vx = V->x();
890 				double vy = V->y();
891 
892 				if (x1 <= vx && vx <= x2 && y1 <= vy && vy <= y2)
893 				{
894 					list->toggle(n);
895 				}
896 			}
897 			break;
898 
899 		case OBJ_LINEDEFS:
900 			for (int n = 0 ; n < NumLineDefs ; n++)
901 			{
902 				const LineDef *L = LineDefs[n];
903 
904 				/* the two ends of the line must be in the box */
905 				if (x1 <= L->Start()->x() && L->Start()->x() <= x2 &&
906 				    y1 <= L->Start()->y() && L->Start()->y() <= y2 &&
907 				    x1 <= L->End()->x()   && L->End()->x() <= x2 &&
908 				    y1 <= L->End()->y()   && L->End()->y() <= y2)
909 				{
910 					list->toggle(n);
911 				}
912 			}
913 			break;
914 
915 		case OBJ_SECTORS:
916 		{
917 			selection_c  in_sectors(OBJ_SECTORS);
918 			selection_c out_sectors(OBJ_SECTORS);
919 
920 			for (int n = 0 ; n < NumLineDefs ; n++)
921 			{
922 				const LineDef *L = LineDefs[n];
923 
924 				// Get the numbers of the sectors on both sides of the linedef
925 				int s1 = L->Right() ? L->Right()->sector : -1;
926 				int s2 = L->Left( ) ? L->Left() ->sector : -1;
927 
928 				if (x1 <= L->Start()->x() && L->Start()->x() <= x2 &&
929 				    y1 <= L->Start()->y() && L->Start()->y() <= y2 &&
930 				    x1 <= L->End()->x()   && L->End()->x() <= x2 &&
931 				    y1 <= L->End()->y()   && L->End()->y() <= y2)
932 				{
933 					if (s1 >= 0) in_sectors.set(s1);
934 					if (s2 >= 0) in_sectors.set(s2);
935 				}
936 				else
937 				{
938 					if (s1 >= 0) out_sectors.set(s1);
939 					if (s2 >= 0) out_sectors.set(s2);
940 				}
941 			}
942 
943 			for (int i = 0 ; i < NumSectors ; i++)
944 				if (in_sectors.get(i) && ! out_sectors.get(i))
945 					list->toggle(i);
946 
947 			break;
948 		}
949 	}
950 }
951 
952 
953 
Selection_InvalidateLast()954 void Selection_InvalidateLast()
955 {
956 	delete last_Sel;
957 	last_Sel = NULL;
958 }
959 
960 
Selection_Push()961 void Selection_Push()
962 {
963 	if (edit.Selected->empty())
964 		return;
965 
966 	if (last_Sel && last_Sel->test_equal(*edit.Selected))
967 		return;
968 
969 	// OK copy it
970 
971 	if (last_Sel)
972 		delete last_Sel;
973 
974 	last_Sel = new selection_c(edit.Selected->what_type(), true);
975 
976 	last_Sel->merge(*edit.Selected);
977 }
978 
979 
Selection_Clear(bool no_save)980 void Selection_Clear(bool no_save)
981 {
982 	if (! no_save)
983 		Selection_Push();
984 
985 	// this always clears it
986 	edit.Selected->change_type(edit.mode);
987 
988 	edit.error_mode = false;
989 
990 	if (main_win)
991 		main_win->UnselectPics();
992 
993 	RedrawMap();
994 }
995 
996 
Selection_Add(Objid & obj)997 void Selection_Add(Objid& obj)
998 {
999 	// validate the mode is correct
1000 	if (obj.type != edit.mode)
1001 		return;
1002 
1003 	if (obj.parts == 0)
1004 	{
1005 		// ignore the add if object is already set.
1006 		// [ since the selection may have parts, and we don't want to
1007 		//   forget those parts ]
1008 		if (! edit.Selected->get(obj.num))
1009 			edit.Selected->set(obj.num);
1010 		return;
1011 	}
1012 
1013 	byte cur = edit.Selected->get_ext(obj.num);
1014 
1015 	cur = 1 | obj.parts;
1016 
1017 	edit.Selected->set_ext(obj.num, cur);
1018 }
1019 
1020 
Selection_Remove(Objid & obj)1021 void Selection_Remove(Objid& obj)
1022 {
1023 	if (obj.type != edit.mode)
1024 		return;
1025 
1026 	if (obj.parts == 0)
1027 	{
1028 		edit.Selected->clear(obj.num);
1029 		return;
1030 	}
1031 
1032 	byte cur = edit.Selected->get_ext(obj.num);
1033 	if (cur == 0)
1034 		return;
1035 
1036 	cur = 1 | (cur & ~obj.parts);
1037 
1038 	// if we unset all the parts, then unset the object itself
1039 	if (cur == 1)
1040 		cur = 0;
1041 
1042 	edit.Selected->set_ext(obj.num, cur);
1043 }
1044 
1045 
Selection_Toggle(Objid & obj)1046 void Selection_Toggle(Objid& obj)
1047 {
1048 	if (obj.type != edit.mode)
1049 		return;
1050 
1051 	if (obj.parts == 0)
1052 	{
1053 		edit.Selected->toggle(obj.num);
1054 		return;
1055 	}
1056 
1057 	byte cur = edit.Selected->get_ext(obj.num);
1058 
1059 	// if object was simply selected, then just clear it
1060 	if (cur == 1)
1061 	{
1062 		edit.Selected->clear(obj.num);
1063 		return;
1064 	}
1065 
1066 	cur = 1 | (cur ^ obj.parts);
1067 
1068 	// if we toggled off all the parts, then unset the object itself
1069 	if (cur == 1)
1070 		cur = 0;
1071 
1072 	edit.Selected->set_ext(obj.num, cur);
1073 }
1074 
1075 
Selection_Validate()1076 void Selection_Validate()
1077 {
1078 	int num_obj = NumObjects(edit.mode);
1079 
1080 	if (edit.Selected->max_obj() >= num_obj)
1081 	{
1082 		edit.Selected->frob_range(num_obj, edit.Selected->max_obj(), BOP_REMOVE);
1083 
1084 		Beep("BUG: invalid selection");
1085 	}
1086 }
1087 
1088 
CMD_LastSelection()1089 void CMD_LastSelection()
1090 {
1091 	if (! last_Sel)
1092 	{
1093 		Beep("No last selection (or was invalidated)");
1094 		return;
1095 	}
1096 
1097 	bool changed_mode = false;
1098 
1099 	if (last_Sel->what_type() != edit.mode)
1100 	{
1101 		changed_mode = true;
1102 		Editor_ChangeMode_Raw(last_Sel->what_type());
1103 		main_win->NewEditMode(edit.mode);
1104 	}
1105 
1106 	std::swap(last_Sel, edit.Selected);
1107 
1108 	// ensure everything is kosher
1109 	Selection_Validate();
1110 
1111 	if (changed_mode)
1112 		GoToSelection();
1113 
1114 	RedrawMap();
1115 }
1116 
1117 
1118 //------------------------------------------------------------------------
1119 //  RECENTLY USED TEXTURES (etc)
1120 //------------------------------------------------------------------------
1121 
1122 
1123 // the containers for the textures (etc)
1124 Recently_used  recent_textures;
1125 Recently_used  recent_flats;
1126 Recently_used  recent_things;
1127 
1128 
Recently_used()1129 Recently_used::Recently_used() :
1130 	size(0),
1131 	keep_num(RECENTLY_USED_MAX - 2)
1132 {
1133 	memset(name_set, 0, sizeof(name_set));
1134 }
1135 
1136 
~Recently_used()1137 Recently_used::~Recently_used()
1138 {
1139 	for (int i = 0 ; i < size ; i++)
1140 	{
1141 		StringFree(name_set[i]);
1142 		name_set[i] = NULL;
1143 	}
1144 }
1145 
1146 
find(const char * name)1147 int Recently_used::find(const char *name)
1148 {
1149 	for (int k = 0 ; k < size ; k++)
1150 		if (y_stricmp(name_set[k], name) == 0)
1151 			return k;
1152 
1153 	return -1;	// not found
1154 }
1155 
find_number(int val)1156 int Recently_used::find_number(int val)
1157 {
1158 	char buffer[64];
1159 
1160 	snprintf(buffer, sizeof(buffer), "%d", val);
1161 
1162 	return find(buffer);
1163 }
1164 
1165 
insert(const char * name)1166 void Recently_used::insert(const char *name)
1167 {
1168 	// ignore '-' texture
1169 	if (is_null_tex(name))
1170 		return;
1171 
1172 	// ignore empty strings to prevent potential problems
1173 	if (strlen(name) == 0)
1174 		return;
1175 
1176 	int idx = find(name);
1177 
1178 	// optimisation
1179 	if (idx >= 0 && idx < 4)
1180 		return;
1181 
1182 	if (idx >= 0)
1183 		erase(idx);
1184 
1185 	push_front(name);
1186 
1187 	// mark browser for later update
1188 	// [ this method may be called very often by basis, too expensive to
1189 	//   update the browser here ]
1190 	changed_recent_list = true;
1191 }
1192 
insert_number(int val)1193 void Recently_used::insert_number(int val)
1194 {
1195 	char buffer[64];
1196 
1197 	snprintf(buffer, sizeof(buffer), "%d", val);
1198 
1199 	insert(buffer);
1200 }
1201 
1202 
erase(int index)1203 void Recently_used::erase(int index)
1204 {
1205 	SYS_ASSERT(0 <= index && index < size);
1206 
1207 	StringFree(name_set[index]);
1208 
1209 	size--;
1210 
1211 	for ( ; index < size ; index++)
1212 	{
1213 		name_set[index] = name_set[index + 1];
1214 	}
1215 
1216 	name_set[index] = NULL;
1217 }
1218 
1219 
push_front(const char * name)1220 void Recently_used::push_front(const char *name)
1221 {
1222 	if (size >= keep_num)
1223 	{
1224 		erase(keep_num - 1);
1225 	}
1226 
1227 	// shift elements up
1228 	for (int k = size - 1 ; k >= 0 ; k--)
1229 	{
1230 		name_set[k + 1] = name_set[k];
1231 	}
1232 
1233 	name_set[0] = StringDup(name);
1234 
1235 	size++;
1236 }
1237 
1238 
clear()1239 void Recently_used::clear()
1240 {
1241 	size = 0;
1242 
1243 	memset(name_set, 0, sizeof(name_set));
1244 }
1245 
1246 
RecUsed_ClearAll()1247 void RecUsed_ClearAll()
1248 {
1249 	recent_textures.clear();
1250 	recent_flats   .clear();
1251 	recent_things  .clear();
1252 
1253 	if (main_win)
1254 		main_win->browser->RecentUpdate();
1255 }
1256 
1257 
1258 /* --- Save and Restore --- */
1259 
WriteUser(FILE * fp,char letter)1260 void Recently_used::WriteUser(FILE *fp, char letter)
1261 {
1262 	for (int i = 0 ; i < size ; i++)
1263 	{
1264 		fprintf(fp, "recent_used %c \"%s\"\n", letter, StringTidy(name_set[i]));
1265 	}
1266 }
1267 
1268 
RecUsed_WriteUser(FILE * fp)1269 void RecUsed_WriteUser(FILE *fp)
1270 {
1271 	fprintf(fp, "\n");
1272 	fprintf(fp, "recent_used clear\n");
1273 
1274 	recent_textures.WriteUser(fp, 'T');
1275 	recent_flats   .WriteUser(fp, 'F');
1276 	recent_things  .WriteUser(fp, 'O');
1277 }
1278 
1279 
RecUsed_ParseUser(const char ** tokens,int num_tok)1280 bool RecUsed_ParseUser(const char ** tokens, int num_tok)
1281 {
1282 	if (strcmp(tokens[0], "recent_used") != 0 || num_tok < 2)
1283 		return false;
1284 
1285 	if (strcmp(tokens[1], "clear") == 0)
1286 	{
1287 		RecUsed_ClearAll();
1288 		return true;
1289 	}
1290 
1291 	// syntax is:  recent_used  <kind>  <name>
1292 	if (num_tok < 3)
1293 		return false;
1294 
1295 	switch (tokens[1][0])
1296 	{
1297 		case 'T':
1298 			recent_textures.insert(tokens[2]);
1299 			break;
1300 
1301 		case 'F':
1302 			recent_flats.insert(tokens[2]);
1303 			break;
1304 
1305 		case 'O':
1306 			recent_things.insert(tokens[2]);
1307 			break;
1308 
1309 		default:
1310 			// ignore it
1311 			break;
1312 	}
1313 
1314 	if (main_win)
1315 		main_win->browser->RecentUpdate();
1316 
1317 	return true;
1318 }
1319 
1320 
1321 //------------------------------------------------------------------------
1322 
1323 
1324 // this in e_commands.cc
1325 void Editor_RegisterCommands();
1326 
1327 
Editor_Init()1328 void Editor_Init()
1329 {
1330 	switch (default_edit_mode)
1331 	{
1332 		case 1:  edit.mode = OBJ_LINEDEFS; break;
1333 		case 2:  edit.mode = OBJ_SECTORS;  break;
1334 		case 3:  edit.mode = OBJ_VERTICES; break;
1335 		default: edit.mode = OBJ_THINGS;   break;
1336 	}
1337 
1338 	edit.render3d = false;
1339 
1340 	Editor_DefaultState();
1341 
1342 	Nav_Clear();
1343 
1344 	edit.pointer_in_window = false;
1345 	edit.map_x = 0;
1346 	edit.map_y = 0;
1347 	edit.map_z = -1;
1348 
1349 	edit.Selected = new selection_c(edit.mode, true /* extended */);
1350 
1351 	edit.highlight.clear();
1352 	edit.split_line.clear();
1353 	edit.clicked.clear();
1354 
1355 	grid.Init();
1356 
1357 	MadeChanges = 0;
1358 
1359 	  Editor_RegisterCommands();
1360 	Render3D_RegisterCommands();
1361 }
1362 
1363 
Editor_DefaultState()1364 void Editor_DefaultState()
1365 {
1366 	edit.action = ACT_NOTHING;
1367 	edit.sticky_mod = 0;
1368 	edit.is_panning = false;
1369 
1370 	edit.dragged.clear();
1371 	edit.draw_from.clear();
1372 
1373 	edit.error_mode = false;
1374 	edit.show_object_numbers = false;
1375 
1376 	edit.sector_render_mode = sector_render_default;
1377 	edit. thing_render_mode =  thing_render_default;
1378 }
1379 
1380 
Editor_ParseUser(const char ** tokens,int num_tok)1381 bool Editor_ParseUser(const char ** tokens, int num_tok)
1382 {
1383 	if (strcmp(tokens[0], "edit_mode") == 0 && num_tok >= 2)
1384 	{
1385 		Editor_ChangeMode(tokens[1][0]);
1386 		return true;
1387 	}
1388 
1389 	if (strcmp(tokens[0], "render_mode") == 0 && num_tok >= 2)
1390 	{
1391 		edit.render3d = atoi(tokens[1]);
1392 		RedrawMap();
1393 		return true;
1394 	}
1395 
1396 	if (strcmp(tokens[0], "sector_render_mode") == 0 && num_tok >= 2)
1397 	{
1398 		edit.sector_render_mode = atoi(tokens[1]);
1399 		RedrawMap();
1400 		return true;
1401 	}
1402 
1403 	if (strcmp(tokens[0], "thing_render_mode") == 0 && num_tok >= 2)
1404 	{
1405 		edit.thing_render_mode = atoi(tokens[1]);
1406 		RedrawMap();
1407 		return true;
1408 	}
1409 
1410 	if (strcmp(tokens[0], "show_object_numbers") == 0 && num_tok >= 2)
1411 	{
1412 		edit.show_object_numbers = atoi(tokens[1]);
1413 		RedrawMap();
1414 		return true;
1415 	}
1416 
1417 	return false;
1418 }
1419 
1420 
Editor_WriteUser(FILE * fp)1421 void Editor_WriteUser(FILE *fp)
1422 {
1423 	switch (edit.mode)
1424 	{
1425 		case OBJ_THINGS:   fprintf(fp, "edit_mode t\n"); break;
1426 		case OBJ_LINEDEFS: fprintf(fp, "edit_mode l\n"); break;
1427 		case OBJ_SECTORS:  fprintf(fp, "edit_mode s\n"); break;
1428 		case OBJ_VERTICES: fprintf(fp, "edit_mode v\n"); break;
1429 
1430 		default: break;
1431 	}
1432 
1433 	fprintf(fp, "render_mode %d\n", edit.render3d ? 1 : 0);
1434 	fprintf(fp, "sector_render_mode %d\n", edit.sector_render_mode);
1435 	fprintf(fp, "thing_render_mode %d\n",  edit.thing_render_mode);
1436 	fprintf(fp, "show_object_numbers %d\n", edit.show_object_numbers ? 1 : 0);
1437 }
1438 
1439 
1440 //--- editor settings ---
1441 // vi:ts=4:sw=4:noexpandtab
1442