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