]> Shamusworld >> Repos - guemap/blob - src/mapdoc.cpp
Fix room adding to work properly, misc. small changes.
[guemap] / src / mapdoc.cpp
1 //
2 // GUEmap
3 // (C) 1997-2007 Christopher J. Madsen
4 // (C) 2019 James Hammons
5 //
6 // GUEmap is licensed under either version 2 of the GPL, or (at your option)
7 // any later version.  See LICENSE file for details.
8 //
9 // Implementation of the MapDoc class
10 //
11
12 #include "mapdoc.h"
13 #include "file.h"
14 #include "undo.h"
15
16
17 const RoomCorner oppositeCorner[rcNumCorners] = {
18         rcS, rcN, rcW, rcE,
19         rcSW, rcSE, rcNW, rcNE,
20         rcSSW, rcSSE, rcNNW, rcNNE
21 };
22
23
24 #if 0
25 /////////////////////////////////////////////////////////////////////////////
26 // MapDoc
27
28 IMPLEMENT_DYNCREATE(MapDoc, CDocument)
29
30 BEGIN_MESSAGE_MAP(MapDoc, CDocument)
31         //{{AFX_MSG_MAP(MapDoc)
32         ON_COMMAND(ID_EDIT_NAVIGATE, OnNavigationMode)
33         ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
34         ON_COMMAND(ID_FILE_EXPORT, OnFileExport)
35         ON_UPDATE_COMMAND_UI(ID_EDIT_NAVIGATE, OnUpdateNavigationMode)
36         ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
37         //}}AFX_MSG_MAP
38   // Send Mail command:
39   ON_COMMAND(ID_FILE_SEND_MAIL, OnFileSendMail)
40   ON_UPDATE_COMMAND_UI(ID_FILE_SEND_MAIL, OnUpdateFileSendMail)
41   // My commands
42   ON_COMMAND_RANGE(ID_EDIT_MOVE_BOTTOM, ID_EDIT_MOVE_TOP, OnEditMoveMap)
43   ON_UPDATE_COMMAND_UI_RANGE(ID_EDIT_MOVE_BOTTOM, ID_EDIT_MOVE_TOP,
44                              OnUpdateEditMoveMap)
45 END_MESSAGE_MAP();
46 #endif
47
48
49 /////////////////////////////////////////////////////////////////////////////
50 // MapDoc construction/destruction
51
52 MapDoc::MapDoc(): locked(false), pageSize(42 * gridX, 53 * gridY), undoData(NULL)
53 {
54         // Initialize the document...  :-P
55         DeleteContents();
56 }
57
58
59 MapDoc::~MapDoc()
60 {
61         if (undoData)
62                 delete undoData;
63 }
64
65
66 #if 0
67 bool MapDoc::OnNewDocument()
68 {
69         if (!CDocument::OnNewDocument())
70                 return FALSE;
71
72         // TODO: add reinitialization code here
73         // (SDI documents will reuse this document)
74         DeleteContents();
75
76         return true;
77 }
78
79
80 /////////////////////////////////////////////////////////////////////////////
81 // MapDoc serialization
82 //--------------------------------------------------------------------
83 // Make a backup file when we Save As:
84
85 bool MapDoc::DoSave(const char * pathName, bool replace)
86 {
87         const bool neededBackup = needBackup;
88
89         if (!pathName)
90                 needBackup = true; // Saving to new name
91
92         const bool  result = CDocument::DoSave(pathName, replace);
93
94         if (!pathName && (!result || !replace))
95                 needBackup = neededBackup;  // Did not save (or saved a copy)
96
97         return result;
98 }
99
100
101 //
102 // Allocate a CFile object for loading/saving a file:
103 //
104 // Rename the existing file if this is the first save after a load.
105 //
106 CFile * MapDoc::GetFile(const char * fileName, UINT openFlags, CFileException * error)
107 {
108         if (needBackup && (openFlags & (CFile::modeReadWrite | CFile::modeWrite)))
109         {
110                 String  backupFileName(fileName);
111
112                 // Change the extension to .GM~:
113                 //   Look for a period after the last backslash (a period before
114                 //   the last backslash would be part of a directory name).
115                 StrIdx  dot = backupFileName.find_last_of(_T('.'));
116
117                 if ((dot == String::npos) || (dot < backupFileName.find_last_of(_T('\\'))))
118                         dot = backupFileName.size(); // No extension
119
120                 backupFileName.replace(dot, String::npos, _T(".gm~"));
121
122                 if (_tcsicmp(fileName, backupFileName.c_str()) == 0)
123                         needBackup = false;       // This file already has a .GM~ extension
124                 else
125                 {
126                         ::DeleteFile(backupFileName.c_str());
127
128                         if (::MoveFile(fileName, backupFileName.c_str()))
129                                 needBackup = false;      // We made a backup file
130                 }
131         }
132
133         // The rest is copied from CDocument::GetFile:
134         CMirrorFile * file = new CMirrorFile;
135         ASSERT(file != NULL);
136
137         if (!file->Open(fileName, openFlags, error))
138         {
139                 delete file;
140                 file = NULL;
141         }
142
143         return file;
144 }
145
146
147 //
148 // Report errors:
149 //
150 void MapDoc::ReportSaveLoadException(const char * lpszPathName, CException * e, bool bSaving, UINT nIDPDefault)
151 {
152         if (e && e->IsKindOf(RUNTIME_CLASS(CArchiveException)))
153         {
154                 UINT msg = 0;
155
156                 switch (static_cast<CArchiveException *>(e)->m_cause)
157                 {
158                 case CArchiveException::badClass:  msg = IDS_AR_NOT_GUEMAP;  break;
159                 case CArchiveException::badSchema: msg = IDS_AR_BAD_VERSION; break;
160
161                 case CArchiveException::badIndex:
162                 case CArchiveException::endOfFile:
163                         msg = IDS_AR_CORRUPT;
164                         break;
165                 }
166
167                 if (msg)
168                 {
169                         AfxMessageBox(msg, MB_ICONEXCLAMATION);
170                         return;
171                 }
172         }
173
174         CDocument::ReportSaveLoadException(lpszPathName, e, bSaving, nIDPDefault);
175 }
176 #endif
177
178
179 //
180 // My Functions:
181 //--------------------------------------------------------------------
182 // Add a corner to an edge
183 //
184 // Input:
185 //   rNum: The room to add a corner by
186 //   eNum: The edge to add a corner in
187 //
188 // Returns:
189 //   The number of the newly created room
190 //   -1 if we couldn't add a corner
191 //
192 int MapDoc::addCorner(RoomNum rNum, int eNum)
193 {
194         const QPoint cornerOffset[] = {
195                 { gridX, -gridY }, { gridX, roomHeight },                       // N, S
196                 { roomWidth, gridY }, { -gridX, gridY },                        // E, W
197                 { roomWidth, -gridY }, { -gridX, -gridY },                      // NE, NW
198                 { roomWidth, roomHeight }, { -gridX, roomHeight },      // SE, SW
199                 { 2 * gridX, -gridY }, { 0, -gridY },                           // NNE, NNW
200                 { 2 * gridX, roomHeight }, { 0, roomHeight }            // SSE, SSW
201         };
202
203         if (room.size() == maxRooms)
204                 return -1;
205
206         MapEdge e1(edge[eNum]);
207         MapEdge e2(e1);
208
209         ASSERT(!(e1.type1 & etNoRoom2));
210
211         RoomCorner corner = (e1.room1 == rNum ? e1.end1 : e1.end2);
212
213         e1.type1 &= ~etOneWay;               // Clear one-way
214         e1.type2 = etNormal;
215         e2.type1 &= etObstructed | etOneWay; // Clear all but obstructed & one-way
216         e1.end2 = e2.end1 = rcCorner;
217         e1.room2 = e2.room1 = room.size();
218
219         QPoint pos = room[rNum].pos + cornerOffset[corner];
220
221         if (pos.x() < 0 || pos.x() + gridX > docSize.width() || pos.y() < 0 || pos.y() + gridY > docSize.height())
222                 return -1;
223
224         MapRoom * newRoom = new MapRoom();
225         newRoom->flags = rfCorner;
226         newRoom->pos = pos;
227 //      newRoom->computeNameLength();
228
229         setUndoData(new UndoChanges(isDirty, e1.room2, 2, edge[eNum]));
230         deleteEdge(eNum);
231         addRoom(e1.room2, newRoom);
232         addEdge(e1);
233         addEdge(e2);
234
235         return e1.room2;
236 }
237
238
239 //
240 // Remove a corner from a connection:
241 //
242 // Input:
243 //   r:  The room number of the corner to be removed
244 //
245 void MapDoc::deleteCorner(RoomNum r)
246 {
247         ASSERT(r < room.size() && room[r].isCorner());
248
249         MapEdge edges[2];
250         EdgeVec delEdges;
251         int count = 0;
252
253         memset(edges, 0, sizeof(edges));
254
255         delEdges.reserve(2);
256
257         for(EdgeConstItr e=edge.begin(); (count<2) && (e!=edge.end()); ++e)
258         {
259                 if (e->room1 == r)
260                 {
261                         edges[count].room1 = e->room2;
262                         edges[count].end1  = e->end2;
263                         edges[count].type1  |= e->type2;
264                         edges[!count].type1 |= e->type1 & etSpecial;
265                         delEdges.push_back(*e);
266                         ++count;
267                 }
268                 else if (e->room2 == r)
269                 {
270                         edges[count].room1 = e->room1;
271                         edges[count].end1  = e->end1;
272                         edges[count].type1|= e->type1;
273                         delEdges.push_back(*e);
274                         ++count;
275                 }
276         }
277
278         if (count < 2)
279         {
280 //              MessageBeep(MB_ICONASTERISK); // Corner without 2 edges
281                 deleteRoom(r);
282                 setUndoData(NULL);
283                 return;
284         }
285
286         int dest = 0;
287         int source = 1;
288
289         if (edges[1].type1 & etSpecial)
290         {
291                 dest = 1;
292                 source = 0;
293         }
294
295         edges[dest].end2  = edges[source].end1;
296         edges[dest].room2 = edges[source].room1;
297         edges[dest].type2 = edges[source].type1 & ~etSpecial;
298
299         if (edges[dest].room1 > r)
300                 --edges[dest].room1;
301
302         if (edges[dest].room2 > r)
303                 --edges[dest].room2;
304
305   const bool isMod = isDirty;
306   setUndoData(new UndoChanges(isMod, r, extractRoom(r), delEdges));
307   addEdge(edges[dest]);
308 }
309
310
311 void MapDoc::addEdge(const MapEdge & newEdge)
312 {
313         edge.push_back(newEdge);
314         isDirty = true;
315 //      QRect rect;
316 //      getEdgeRect(newEdge, rect);
317 //      UpdateAllViews(NULL, 0, reinterpret_cast<CObject *>(&rect));
318 }
319
320
321 void MapDoc::addEdges(int n)
322 {
323         edge.reserve(edge.size() + n);
324 }
325
326
327 void MapDoc::deleteEdge(int n)
328 {
329         ASSERT(n < edge.size());
330
331         isDirty = true;
332         QRect rect;
333         getEdgeRect(edge[n], rect);
334
335         edge.erase(edge.begin() + n);
336
337 //      UpdateAllViews(NULL, 0, reinterpret_cast<CObject *>(&rect));
338 }
339
340
341 //
342 // Find the edge (if any) connected to a given corner:
343 //
344 // Input:
345 //   n:      The room number to look for
346 //   corner: The corner of room N we want
347 //
348 // Output:
349 //   newEdge.room1: The room at the other end of this edge
350 //   newEdge.end1:  The corner of the other room
351 //   newEdge.type1: The type of connection at the other end
352 //   newEdge.type2: The type of connection at this end etObstructed is moved
353 //                  from type2 to type1 if necessary
354 //
355 // Returns:
356 //   The edge number
357 //   -1 if no edge is attached to the specified corner
358 //
359 int MapDoc::findEdge(RoomNum n, RoomCorner corner, MapEdge & newEdge) const
360 {
361         EdgeConstItr e = edge.begin();
362
363         if (corner < rcNumCorners)
364         {
365                 for(int i=edge.size()-1; i>=0; i--)
366                 {
367                         if ((e[i].room1 == n) && (e[i].end1 == corner))
368                         {
369                                 newEdge = e[i].Swapped();
370                                 return i;
371                         }
372                         else if ((e[i].room2 == n) && (e[i].end2 == corner)
373                                 && !(e[i].type1 & etNoRoom2))
374                         {
375                                 newEdge = e[i];
376                                 return i;
377                         }
378                 }
379         }
380         else
381         {
382                 // Direction (up/down/in/out)
383                 const EdgeType corner2et[] = { etUp, etDown, etIn, etOut };
384                 const EdgeType dir = corner2et[corner - rcNumCorners];
385
386                 for(int i=edge.size()-1; i>=0; i--)
387                 {
388                         if ((e[i].room1 == n) && ((e[i].type1 & etDirection) == dir))
389                         {
390                                 newEdge = e[i].Swapped();
391                                 return i;
392                         }
393                         else if ((e[i].room2 == n) && ((e[i].type2 & etDirection) == dir)
394                                 && !(e[i].type1 & etNoRoom2))
395                         {
396                                 newEdge = e[i];
397                                 return i;
398                         }
399                 }
400         }
401
402         newEdge.Clear();
403
404         return -1;
405 }
406
407
408 //
409 // Find the other end of a connection with corners:
410 //
411 // Input:
412 //   start:
413 //     The edge we want to follow
414 //
415 // Output:
416 //   cornerRooms:  (if not NULL)
417 //     Contains the room numbers of all corners in this connection.
418 //     If the vector was not empty, the new entries are appended.
419 //
420 // Returns:
421 //   The number of the room at the other end
422 //   -1 if the connection doesn't lead anywhere
423 //
424 int MapDoc::findOtherEnd(EdgeConstItr start, RoomNumVec * cornerRooms) const
425 {
426         EdgeConstItr oldEdge = start;
427         RoomNum room = (start->end1 == rcCorner ? start->room1 : start->room2);
428
429         if (cornerRooms)
430                 cornerRooms->push_back(room);
431
432         for(;;)
433         {
434                 EdgeConstItr lastTime = oldEdge;
435
436                 for(EdgeConstItr e=edge.begin(); e<edge.end(); e++)
437                 {
438                         if (e == oldEdge)
439                                 continue;
440
441                         if (e->room1 == room)
442                         {
443                                 room = e->room2;
444
445                                 if (e->end2 != rcCorner)
446                                         return room;
447
448                                 if (cornerRooms)
449                                         cornerRooms->push_back(room);
450
451                                 oldEdge = e;
452                         }
453                         else if (e->room2 == room)
454                         {
455                                 room = e->room1;
456
457                                 if (e->end1 != rcCorner)
458                                         return room;
459
460                                 if (cornerRooms)
461                                         cornerRooms->push_back(room);
462
463                                 oldEdge = e;
464                         }
465                 }
466
467                 if (lastTime == oldEdge)
468                         return -1;                // We didn't go anywhere
469         }
470 }
471
472
473 int MapDoc::findOtherEnd(int n) const
474 {
475         return findOtherEnd(edge.begin() + n);
476 }
477
478
479 void MapDoc::getEdgePoints(const MapEdge & e, QPoint & p1, QPoint & p2) const
480 {
481         const QPoint cornerOffset[] = {
482                 { roomWidth / 2, 0 }, { roomWidth / 2, roomHeight },
483                 { roomWidth, roomHeight / 2 }, { 0, roomHeight / 2 },
484                 { roomWidth, 0 }, { 0, 0 },
485                 { roomWidth, roomHeight }, { 0, roomHeight },
486                 { 3 * roomWidth / 4, 0 }, { roomWidth / 4, 0 },
487                 { 3 * roomWidth / 4, roomHeight }, { roomWidth / 4, roomHeight },
488                 { gridX / 2, gridY / 2 }
489         };
490
491 /*      const QPoint nowhereOffset[] = {
492                 { 0, -gridY / 2 }, { 0, gridY / 2 }, { gridX / 2, 0 }, { -gridX / 2, 0 }, // N, S, E, W
493                 { gridX / 2, -gridY / 2 }, { -gridX / 2, -gridY / 2 }, { gridX / 2, gridY / 2 }, { -gridX / 2, gridY / 2 }, // NE, NW, SE, SW
494                 { 0, -gridY / 2 }, { 0, -gridY / 2 }, { 0, gridY / 2 }, { 0, gridY / 2 } // NNE, NNW, SSE, SSW
495         };*/
496         const int stub = (int)((gridY / 2.0) - 15.0);
497         const int stubd = (int)(((gridY / 2.0) - 15.0) * 0.7071);
498         const QPoint nowhereOffset[] = {
499                 { 0, -stub }, { 0, stub }, { stub, 0 }, { -stub, 0 }, // N, S, E, W
500                 { stubd, -stubd }, { -stubd, -stubd }, { stubd, stubd }, { -stubd, stubd }, // NE, NW, SE, SW
501                 { 0, -stub }, { 0, -stub }, { 0, stub }, { 0, stub } // NNE, NNW, SSE, SSW
502         };
503
504         p1 = room[e.room1].pos + cornerOffset[e.end1];
505
506         if (e.type1 & etNoRoom2)
507         {
508                 if (e.type1 & etNoExit)
509                         p2 = p1 + nowhereOffset[e.end1];
510                 else
511                         p2 = p1 + (nowhereOffset[e.end1] * 3);
512         }
513         else
514                 p2 = room[e.room2].pos + cornerOffset[e.end2];
515 }
516
517
518 void MapDoc::getEdgePoints(int n, QPoint & p1, QPoint & p2) const
519 {
520         getEdgePoints(edge[n], p1, p2);
521 }
522
523
524 void MapDoc::getEdgeRect(const MapEdge & e, QRect & r) const
525 {
526 //      getEdgePoints(e, r.left, r.top, r.right, r.bottom);
527 //      r.NormalizeRect();
528 //      r.InflateRect(gridX, gridY);
529         QPoint p1, p2;
530         getEdgePoints(e, p1, p2);
531         r = QRect(p1, p2).normalized();
532         r += QMargins(gridX, gridY, gridX, gridY);
533 }
534
535
536 //
537 // Compute the actual size of the map:
538 //
539 // Output:
540 //   r:  The rectangle enclosing the map in logical coordinates
541 //
542 void MapDoc::getGridRect(QRect & rect)
543 {
544 //      rect.SetRectEmpty();
545         rect = QRect();
546
547         for(RoomConstItr r=room.getVector().begin(); r!=room.getVector().end(); r++)
548                 rect |= (*r)->getRect();
549
550         for(PageConstItr p=page.begin(); p!=page.end(); p++)
551                 rect |= getPageRect(*p);
552 }
553
554
555 //
556 // Compute the actual size of the map:
557 //
558 // Output:
559 //   size: The size of the map in grid units
560 //     Does not consider stubs or labels that may extend off the edge
561 //
562 void MapDoc::getGridSize(QSize & size)
563 {
564         QRect rect;
565         getGridRect(rect);
566
567         size.setWidth(rect.right() / gridX);
568         size.setHeight(rect.top() / -gridY);
569 }
570
571
572 //
573 // Add a page to the map:
574 //
575 // Input:
576 //   n:        The new page number (0 based)
577 //   newPage:  The page information
578 //
579 void MapDoc::addPage(int n, const MapPage & newPage)
580 {
581         ASSERT(n >= 0 && n <= page.size());
582
583         if (page.size() == maxRooms)
584                 return;
585
586         page.insert(page.begin() + n, newPage);
587         isDirty = true;
588 //      UpdateAllViews(NULL, dupPageCount, NULL);
589 }
590
591
592 //
593 // Append pages to the map:
594 //
595 // Input:
596 //   newPages:  The page information
597 //
598 void MapDoc::addPages(const PageVec & newPages)
599 {
600         if (page.size() + newPages.size() > maxRooms)
601                 return;
602
603         page.insert(page.end(), newPages.begin(), newPages.end());
604         isDirty = true;
605 //      UpdateAllViews(NULL, dupPageCount, NULL);
606 }
607
608
609 void MapDoc::addPages(int n)
610 {
611         page.reserve(page.size() + n);
612 }
613
614
615 //
616 // Delete a page from the map:
617 //
618 // Input:
619 //   n:  The (0 based) page number to delete
620 //
621 void MapDoc::deletePage(int n)
622 {
623         ASSERT(n >= 0 && n < page.size());
624         ASSERT(page.size() > 1);
625
626         isDirty = true;
627         QRect rect = getPageRect(n);
628
629         page.erase(page.begin() + n);
630
631 //      UpdateAllViews(NULL, dupPageCount, NULL);
632 }
633
634
635 QRect MapDoc::getPageRect(int p)
636 {
637         return QRect(page[p].pos, pageSize);
638 }
639
640
641 QRect MapDoc::getPageRect(const MapPage & p)
642 {
643         return QRect(p.pos, pageSize);
644 }
645
646
647 //
648 // Place pages to cover the entire map:
649 //
650 void MapDoc::layoutPages()
651 {
652 // Adds nothing for now, so nuke it
653 #if 0
654         UndoPaginate * undoRec = new UndoPaginate(*this);
655
656         QRect r;
657         getGridRect(r); // Just counts rooms, since UndoPaginate cleared the pages
658
659         const int
660                 width  = r.width(),
661                 height = r.height(),
662                 numX   = (width  + pageSize.width() - 1) / pageSize.width(),
663                 numY   = (height + pageSize.height() - 1) / pageSize.height();
664
665         if (!numX)
666                 page.insert(page.begin());  // One page at 0,0
667         else
668         {
669                 int i;
670                 float delta, step;
671                 PageItr p, p2;
672                 page.resize(numX * numY);
673
674                 if (numX == 1)
675                 {
676                         i = r.left - (pageSize.width() - width) / 2;
677                         i -= i % gridX;
678
679                         if (i < 0)
680                                 i = 0;
681
682                         for(p=page.begin(); p!=page.end(); ++p)
683                                 p->pos.x = i;
684                 }
685                 else
686                 {
687                         step = float(width - pageSize.width()) / float(numX);
688                         delta = 0;
689
690                         for(p=page.begin(), p2=p+numX; p!=p2; ++p, delta+=step)
691                         {
692                                 p->pos.x = r.left + delta;
693                                 p->pos.x -= p->pos.x % gridX;
694                         }
695
696                         p[-1].pos.x = r.right - pageSize.width();
697
698                         for(p2=page.begin(); p!=page.end(); ++p, ++p2)
699                                 p->pos.x = p2->pos.x;
700                 }
701
702                 if (numY == 1)
703                 {
704                         i = r.bottom + (pageSize.height() - height)/2;
705                         i -= i % gridY;
706
707                         if (i > 0)
708                                 i = 0;
709
710                         for(p=page.begin(); p!=page.end(); ++p)
711                                 p->pos.y = i;
712                 }
713                 else
714                 {
715                         step = float(pageSize.height() - height) / float(numY);
716                         delta = 0;
717                         p = p2 = page.begin();
718
719                         while (p2 != page.end())
720                         {
721                                 p2 += numX;
722
723                                 if (p2 == page.end())
724                                         i = r.top + pageSize.height();
725                                 else
726                                 {
727                                         i = r.bottom + delta;
728                                         i -= i % gridY;
729                                         delta += step;
730                                 }
731
732                                 while (p != p2)
733                                         (p++)->pos.y = i;
734                         }
735                 }
736         }
737
738         if (undoRec->getPages() == page)
739                 delete undoRec;             // No changes
740         else
741         {
742                 setUndoData(undoRec);
743                 isDirty = true;
744 //              UpdateAllViews(NULL, dupPageCount, NULL);
745         }
746 #endif
747 }
748
749
750 //
751 // Move a page:
752 //
753 // Input:
754 //   n:       The (0 based) page number to move
755 //   offset:  How much to move it
756 //
757 // Note:
758 //   Must not change undo data (may be used during undo)
759 //
760 void MapDoc::movePage(int n, const QSize & offset)
761 {
762         isDirty = true;
763
764 //      page[n].pos += offset;
765         page[n].pos += QPoint(offset.width(), offset.height());
766 //      UpdateAllViews(NULL);
767 }
768
769
770 //
771 // Decide if we need to repaginate:
772 //
773 // Returns:
774 //   true:   Some rooms are not (completely) on any page
775 //   false:  All rooms are on a page
776 //
777 bool MapDoc::needRepaginate()// const
778 {
779         const PageConstItr pBegin = page.begin(), pEnd = page.end();
780 //      QRect rr, pr;
781
782         for(RoomConstItr r=room.getVector().begin(); r!=room.getVector().end(); r++)
783         {
784                 QRect rr = (**r).getRect();
785
786                 for(PageConstItr p=pBegin; p!=pEnd; p++)
787                 {
788                         QRect pr = getPageRect(*p);
789                         QRect i = pr & rr;
790
791                         if (!i.isNull() && (i == rr))
792 //                      if (i.IntersectRect(&pr, &rr) && (i == rr))
793                                 goto nextRoom;
794                 }
795
796                 return true;                // This room not on any page
797
798                 nextRoom:
799                 ;
800         }
801
802         return false;
803 }
804
805
806 //
807 // Determine which page (if any) contains a given point:
808 //
809 // The point must be in the page border (one of the grid cells which
810 // displays the page number).
811 //
812 // Input:
813 //   pos:  The position to test
814 //
815 // Returns:
816 //   The (0 based) page number of the page containing the point
817 //   -1 if no page contains the point
818 //
819 int MapDoc::pageBorderHit(const QPoint & pos)
820 {
821         for(int i=page.size()-1; i>=0; i--)
822         {
823                 QRect r = getPageRect(i);
824
825                 if (r.contains(pos) &&
826 //              if (r.PtInRect(pos) &&
827                         (((pos.x() - r.left() < gridX) || (r.right()  - pos.x() < gridX)) !=
828                         ((pos.y() - r.top()  < gridY) || (r.bottom() - pos.y() < gridY))))
829                         // Point is in the X border or the Y border, but not both
830                         return i;
831         }
832
833         // Not in any page border
834         return -1;
835 }
836
837
838 //
839 // Add a new room:
840 //
841 // Returns:
842 //   The room number of the new room (-1 if error)
843 //
844 int MapDoc::addRoom(const QPoint & pos)
845 {
846         if (room.size() == maxRooms)
847                 return -1;
848
849         MapRoom * newRoom = new MapRoom();
850         newRoom->flags = rfBorder;
851         newRoom->pos = pos;
852 //      newRoom->computeNameLength();
853         addRoom(room.size(), newRoom);
854
855         return room.size() - 1;
856 }
857
858
859 //
860 // Add a new room:
861 //
862 // Input:
863 //   n:        The position for the new room [0..size]
864 //   newRoom:  A MapRoom object allocated by new
865 //
866 void MapDoc::addRoom(int n, MapRoom * newRoom)
867 {
868         ASSERT(n >= 0 && n <= room.size());
869         ASSERT(newRoom);
870
871         room.insert(n, newRoom);
872
873         if (n != room.size()-1)
874         {
875                 for(int i=edge.size()-1; i>=0; i--)
876                 {
877                         if (edge[i].room1 >= n)
878                                 ++edge[i].room1;
879
880                         if (edge[i].room2 >= n)
881                                 ++edge[i].room2;
882                 }
883         }
884
885         isDirty = true;
886         QRect rect = newRoom->getRect();
887 //      UpdateAllViews(NULL, 0, reinterpret_cast<CObject *>(&rect));
888 }
889
890
891 void MapDoc::addRooms(int n)
892 {
893         room.reserve(room.size() + n);
894 }
895
896
897 //
898 // Extract a room from the map:
899 //
900 // Input:
901 //   n:  The room number to remove
902 //
903 // Returns:
904 //   A pointer to the room extracted from the map
905 //   You can use this in the undo record
906 //
907 MapRoom * MapDoc::extractRoom(int n)
908 {
909         ASSERT(n >= 0 && n < room.size());
910
911         isDirty = true;
912
913         for(int i=edge.size()-1; i>=0; i--)
914         {
915                 if ((edge[i].room1 == n) ||
916                         (!(edge[i].type1 & etNoRoom2) && (edge[i].room2 == n)))
917                         deleteEdge(i);
918                 else
919                 {
920                         if (edge[i].room1 > n)
921                                 --edge[i].room1;
922
923                         if (edge[i].room2 > n)
924                                 --edge[i].room2;
925                 }
926         }
927
928         MapRoom * oldRoom = room.extract(n);
929         QRect rect = oldRoom->getRect();
930
931 //      UpdateAllViews(NULL, dupDeletedRoom, reinterpret_cast<CObject *>(&rect));
932
933         return oldRoom;
934 }
935
936
937 void MapDoc::deleteRoom(int n)
938 {
939         delete extractRoom(n);
940 }
941
942
943 //
944 // Find out if a new room would intersect an old one:
945 //
946 // There must be at least one grid of space around the room.  Ignores corners.
947 //
948 // Input:
949 //   pos: The proposed location for a new room
950 //
951 // Returns:
952 //   The room number of an intersecting room
953 //   -1 if the space is unoccupied
954 //   -2 if the space is off the grid
955 //
956 int MapDoc::findRoom(const QPoint & pos) const
957 {
958         if (pos.x() < 0 || pos.x() + roomWidth > docSize.width()
959                 || pos.y() < 0 || pos.y() + roomHeight > docSize.height())
960                 return -2;
961
962         RoomConstItr rm = room.getVector().begin();
963         QRect newRoom(pos, defaultRoomSize);
964 //      newRoom.InflateRect(gridX - 1, gridY - 1);
965         newRoom += QMargins(gridX - 1, gridY - 1, gridX - 1, gridY - 1);
966
967         for(int i=room.size()-1; i>=0; i--)
968         {
969                 if (!rm[i]->isCorner()
970                         && newRoom.intersects(rm[i]->getRect()))
971                         return i;
972         }
973
974         return -1;
975 }
976
977
978 //
979 // Move a room:
980 //
981 // Input:
982 //   n:       The room number to move
983 //   offset:  How much to move it
984 //
985 // Note:
986 //   Must not change undo data (may be used during undo)
987 //
988 void MapDoc::moveRoom(int n, const QSize & offset)
989 {
990         isDirty = true;
991
992 //      room[n].pos += offset;
993         room[n].pos += QPoint(offset.width(), offset.height());
994 //      UpdateAllViews(NULL);
995 }
996
997
998 //
999 // Determine which room (if any) contains a given point:
1000 //
1001 // Input:
1002 //   pos:  The position to test
1003 //
1004 // Output:
1005 //   corner:
1006 //     Which corner of the room was hit (rcNone if in center)
1007 //
1008 // Returns:
1009 //   The room number of the room containing the point
1010 //   -1 if no room contains the point
1011 //
1012 int MapDoc::roomHit(const QPoint & pos, RoomCorner * corner/*= NULL*/)
1013 {
1014         if (corner)
1015                 *corner = rcNone;
1016
1017         for(int i=room.size()-1; i>=0; i--)
1018         {
1019                 QRect r = room[i].getRect();
1020
1021                 if (r.contains(pos) == false)
1022                         continue;
1023
1024                 if (corner && (room[i].isCorner() == false))
1025                         *corner = CornerHit(pos - r.topLeft());
1026
1027                 return i;
1028         }
1029
1030         // Not in any room
1031         return -1;
1032 }
1033
1034
1035 RoomCorner MapDoc::CornerHit(const QPoint & pos)
1036 {
1037         long x = pos.x(), y = pos.y();
1038
1039         // FIXME this assumes all rooms are the same size
1040         if (y < roomHeight / 4)
1041         {
1042                 if (x < roomWidth * 3 / 16)
1043                         return rcNW;
1044                 else if (x < roomWidth * 5 / 16)
1045                         return rcNNW;
1046                 else if (x > roomWidth * 13 / 16)
1047                         return rcNE;
1048                 else if (x > roomWidth * 11 / 16)
1049                         return rcNNE;
1050                 else
1051                         return rcN;
1052         }
1053         else if (y > roomHeight * 3 / 4)
1054         {
1055                 if (x < roomWidth * 3 / 16)
1056                         return rcSW;
1057                 else if (x < roomWidth * 5 / 16)
1058                         return rcSSW;
1059                 else if (x > roomWidth * 13 / 16)
1060                         return rcSE;
1061                 else if (x > roomWidth * 11 / 16)
1062                         return rcSSE;
1063                 else
1064                         return rcS;
1065         }
1066         else
1067         {
1068                 if (x < roomWidth / 4)
1069                         return rcW;
1070                 else if (x > roomWidth * 3 / 4)
1071                         return rcE;
1072         }
1073
1074         return rcNone;
1075 }
1076
1077
1078 //
1079 // Change the map's title:
1080 //
1081 // Input:
1082 //   newName:  The new map title
1083 //
1084 void MapDoc::setName(const char * newName)
1085 {
1086         if (name == newName)
1087                 return;  // Nothing to do
1088
1089         name = newName;
1090         isDirty = true;
1091 }
1092
1093
1094 //
1095 // Change the map's comments:
1096 //
1097 // Input:
1098 //   newNote:  The new map comments
1099 //
1100 void MapDoc::setNote(const char * newNote)
1101 {
1102         if (note == newNote)
1103                 return;  // Nothing to do
1104
1105         note = newNote;
1106         trimRight(note);
1107         isDirty = true;
1108 }
1109
1110
1111 //
1112 // Change a room's flags:
1113 //
1114 // Input:
1115 //   n:        The room number to change
1116 //   set:      The flags to set
1117 //   clear:    The flags to clear (default 0)
1118 //
1119 // Note:
1120 //   flags in both SET and CLEAR will be set.
1121 //
1122 void MapDoc::setRoomFlags(int n, RoomFlags set, RoomFlags clear)
1123 {
1124         MapRoom & rm = room[n];
1125         RoomFlags old = rm.flags;
1126
1127         rm.flags &= ~clear;
1128         rm.flags |= set;
1129
1130         if (rm.flags != old)
1131         {
1132                 isDirty = true;
1133
1134 //              rm.getRect(rect).InflateRect(5, 5);
1135                 QRect rect = rm.getRect() += QMargins(5, 5, 5, 5);
1136 //              UpdateAllViews(NULL, 0, reinterpret_cast<CObject *>(&rect));
1137         }
1138 }
1139
1140
1141 //
1142 // Change a room's name
1143 //
1144 // Input:
1145 //   n:       The room number to change
1146 //   newName: The new room name
1147 //
1148 void MapDoc::setRoomName(int n, const char * newName)
1149 {
1150         MapRoom & rm = room[n];
1151
1152         if (rm.name == newName)
1153                 return; // Nothing to do
1154
1155         rm.name = newName;
1156         isDirty = true;
1157
1158 //      rm.getRect(rect).DeflateRect(5, 5);
1159         QRect rect = rm.getRect() -= QMargins(5, 5, 5, 5);
1160 //      UpdateAllViews(NULL, 0, reinterpret_cast<CObject *>(&rect));
1161 }
1162
1163
1164 //
1165 // Change a room's comments
1166 //
1167 // Input:
1168 //   n:       The room number to change
1169 //   newNote: The new room note
1170 //
1171 void MapDoc::setRoomNote(int n, const char * newNote)
1172 {
1173         MapRoom & rm = room[n];
1174
1175         if (rm.note == newNote)
1176                 return; // Nothing to do
1177
1178         const bool redraw = (rm.note.empty() || !*newNote);
1179         rm.note = newNote;
1180         trimRight(rm.note);
1181         isDirty = true;
1182
1183         if (redraw)
1184         {
1185 //              rm.getRect(rect).DeflateRect(5, 5); // PORT
1186                 QRect rect = rm.getRect() -= QMargins(5, 5, 5, 5);
1187 //              UpdateAllViews(NULL, dupRoomComment, reinterpret_cast<CObject *>(&rect));
1188         }
1189 //      else
1190 //              UpdateAllViews(NULL, dupRoomComment, NULL);
1191 }
1192
1193
1194 //
1195 // Undo handling:
1196 //--------------------------------------------------------------------
1197 // Set undo data:
1198 //
1199 void MapDoc::setUndoData(UndoRec * newUndo)
1200 {
1201         if (undoData != newUndo)
1202         {
1203                 delete undoData;
1204                 undoData = newUndo;
1205         }
1206 }
1207
1208
1209 #if 0
1210 //
1211 // Undo the last operation:
1212 //
1213 void MapDoc::OnEditUndo()
1214 {
1215         if (undoData)
1216                 setUndoData(undoData->undo(*this));
1217 }
1218
1219
1220 //
1221 // Update the Undo menu item:
1222 //
1223 void MapDoc::OnUpdateEditUndo(CCmdUI * pCmdUI)
1224 {
1225         pCmdUI->Enable(undoData && !locked);
1226
1227         CString text;
1228
1229         if (undoData)
1230         {
1231                 text = "&Undo ";
1232                 text += undoData->getName();
1233         }
1234         else
1235                 text = "Can't &Undo";
1236
1237         text += "\tCtrl+Z";
1238         pCmdUI->SetText(text);
1239 }
1240 #endif
1241
1242
1243 //
1244 // MapDoc commands
1245 //
1246 // Erase contents of document:
1247 //
1248 void MapDoc::DeleteContents(void)
1249 {
1250         locked = false;
1251         edge.erase(edge.begin(), edge.end());
1252         page.resize(1);
1253         page.front().pos.setX(0);
1254         page.front().pos.setY(0);
1255         room.removeAll();
1256         name.erase();
1257         note.erase();
1258         docSize = QSize(271 * gridX, 451 * gridY);
1259         setUndoData(NULL);
1260         needBackup = true;
1261 }
1262
1263
1264 #if 0
1265 //
1266 // Move all the rooms around:
1267 //
1268 // This moves only rooms that have connections.  Rooms that stand
1269 // alone (used as a title box, for example) are not moved.
1270 //
1271 void MapDoc::OnEditMoveMap(UINT cmd)
1272 {
1273         const int numEdges = edge.size();
1274         const int numRooms = room.size();
1275
1276         ByteVec selected;
1277         selected.resize(numRooms, false);
1278
1279         int i;
1280
1281         // Select all rooms that have a connection to somewhere:
1282         for(i=0; i<numEdges; i++)
1283         {
1284                 selected[edge[i].room1] = true;
1285
1286                 if ((edge[i].type1 & etNoRoom2) == 0)
1287                         selected[edge[i].room2] = true;
1288         }
1289
1290         // Compute the bounding rectangle of the selected rooms:
1291         int numSelected = 0;
1292         QRect r, temp;
1293
1294         r.SetRectEmpty();
1295
1296         for(i=0; i<numRooms; i++)
1297         {
1298                 if (selected[i])
1299                 {
1300                         ++numSelected;
1301                         r |= room[i].getRect(temp);
1302                 }
1303         }
1304
1305         // Calculate the distance to move them:
1306         QSize offset(0, 0);
1307
1308         switch (cmd)
1309         {
1310         case ID_EDIT_MOVE_CENTER:
1311                 offset.width() = (docSize.width() - r.right - r.left) / 2;
1312                 offset.height() = (docSize.height() + r.top + r.bottom) / -2;
1313                 break;
1314         case ID_EDIT_MOVE_TOP:    offset.height() = -r.bottom;             break;
1315         case ID_EDIT_MOVE_BOTTOM: offset.height() = -(docSize.height() + r.top); break;
1316         case ID_EDIT_MOVE_LEFT:   offset.width() = -r.left;               break;
1317         case ID_EDIT_MOVE_RIGHT:  offset.width() = docSize.width() - r.right;  break;
1318         }
1319
1320         offset.width() -= offset.width() % gridX; // Make sure the rooms stay on the grid
1321         offset.height() -= offset.height() % gridY;
1322
1323         // Do the move:
1324         if (!offset.width() && !offset.height())
1325                 return;                     // Do nothing
1326
1327         setUndoData(new UndoMove(isDirty, offset, numSelected, selected, 0, selected)); // No selected pages
1328
1329         for(i=0; i<numRooms; i++)
1330         {
1331                 if (selected[i])
1332                         moveRoom(i, offset);
1333         }
1334 }
1335
1336
1337 void MapDoc::OnNavigationMode()
1338 {
1339         locked = !locked;
1340
1341         if (locked)
1342         {
1343                 if (getRoomCount() > 0)
1344                         UpdateAllViews(NULL, dupNavigationMode);
1345                 else
1346                         locked = false;           // Can't enter navigation mode
1347         }
1348 }
1349
1350
1351 void MapDoc::OnUpdateEditMoveMap(CCmdUI * pCmdUI)
1352 {
1353         // You can't use Move Map unless you have edges:
1354         pCmdUI->Enable(!locked && edge.size());
1355 }
1356
1357
1358 void MapDoc::OnUpdateNavigationMode(CCmdUI * pCmdUI)
1359 {
1360         pCmdUI->SetCheck(locked);
1361
1362         if (!locked)
1363                 pCmdUI->Enable(getRoomCount() > 0);
1364 }
1365 #endif
1366