]> Shamusworld >> Repos - guemap/blob - src/mapdoc.cpp
Initial commit of GUEmap v3.
[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         return -1;
403 }
404
405
406 //
407 // Find the other end of a connection with corners:
408 //
409 // Input:
410 //   start:
411 //     The edge we want to follow
412 //
413 // Output:
414 //   cornerRooms:  (if not NULL)
415 //     Contains the room numbers of all corners in this connection.
416 //     If the vector was not empty, the new entries are appended.
417 //
418 // Returns:
419 //   The number of the room at the other end
420 //   -1 if the connection doesn't lead anywhere
421 //
422 int MapDoc::findOtherEnd(EdgeConstItr start, RoomNumVec * cornerRooms) const
423 {
424         EdgeConstItr oldEdge = start;
425         RoomNum room = (start->end1 == rcCorner ? start->room1 : start->room2);
426
427         if (cornerRooms)
428                 cornerRooms->push_back(room);
429
430         for(;;)
431         {
432                 EdgeConstItr lastTime = oldEdge;
433
434                 for(EdgeConstItr e=edge.begin(); e<edge.end(); e++)
435                 {
436                         if (e == oldEdge)
437                                 continue;
438
439                         if (e->room1 == room)
440                         {
441                                 room = e->room2;
442
443                                 if (e->end2 != rcCorner)
444                                         return room;
445
446                                 if (cornerRooms)
447                                         cornerRooms->push_back(room);
448
449                                 oldEdge = e;
450                         }
451                         else if (e->room2 == room)
452                         {
453                                 room = e->room1;
454
455                                 if (e->end1 != rcCorner)
456                                         return room;
457
458                                 if (cornerRooms)
459                                         cornerRooms->push_back(room);
460
461                                 oldEdge = e;
462                         }
463                 }
464
465                 if (lastTime == oldEdge)
466                         return -1;                // We didn't go anywhere
467         }
468 }
469
470
471 int MapDoc::findOtherEnd(int n) const
472 {
473         return findOtherEnd(edge.begin() + n);
474 }
475
476
477 void MapDoc::getEdgePoints(const MapEdge & e, QPoint & p1, QPoint & p2) const
478 {
479         const QPoint cornerOffset[] = {
480                 { roomWidth / 2, 0 }, { roomWidth / 2, roomHeight },
481                 { roomWidth, roomHeight / 2 }, { 0, roomHeight / 2 },
482                 { roomWidth, 0 }, { 0, 0 },
483                 { roomWidth, roomHeight }, { 0, roomHeight },
484                 { 3 * roomWidth / 4, 0 }, { roomWidth / 4, 0 },
485                 { 3 * roomWidth / 4, roomHeight }, { roomWidth / 4, roomHeight },
486                 { gridX / 2, gridY / 2 }
487         };
488
489 /*      const QPoint nowhereOffset[] = {
490                 { 0, -gridY / 2 }, { 0, gridY / 2 }, { gridX / 2, 0 }, { -gridX / 2, 0 }, // N, S, E, W
491                 { gridX / 2, -gridY / 2 }, { -gridX / 2, -gridY / 2 }, { gridX / 2, gridY / 2 }, { -gridX / 2, gridY / 2 }, // NE, NW, SE, SW
492                 { 0, -gridY / 2 }, { 0, -gridY / 2 }, { 0, gridY / 2 }, { 0, gridY / 2 } // NNE, NNW, SSE, SSW
493         };*/
494         const int stub = (int)((gridY / 2.0) - 15.0);
495         const int stubd = (int)(((gridY / 2.0) - 15.0) * 0.7071);
496         const QPoint nowhereOffset[] = {
497                 { 0, -stub }, { 0, stub }, { stub, 0 }, { -stub, 0 }, // N, S, E, W
498                 { stubd, -stubd }, { -stubd, -stubd }, { stubd, stubd }, { -stubd, stubd }, // NE, NW, SE, SW
499                 { 0, -stub }, { 0, -stub }, { 0, stub }, { 0, stub } // NNE, NNW, SSE, SSW
500         };
501
502         p1 = room[e.room1].pos + cornerOffset[e.end1];
503
504         if (e.type1 & etNoRoom2)
505         {
506                 if (e.type1 & etNoExit)
507                         p2 = p1 + nowhereOffset[e.end1];
508                 else
509                         p2 = p1 + (nowhereOffset[e.end1] * 3);
510         }
511         else
512                 p2 = room[e.room2].pos + cornerOffset[e.end2];
513 }
514
515
516 void MapDoc::getEdgePoints(int n, QPoint & p1, QPoint & p2) const
517 {
518         getEdgePoints(edge[n], p1, p2);
519 }
520
521
522 void MapDoc::getEdgeRect(const MapEdge & e, QRect & r) const
523 {
524 //      getEdgePoints(e, r.left, r.top, r.right, r.bottom);
525 //      r.NormalizeRect();
526 //      r.InflateRect(gridX, gridY);
527         QPoint p1, p2;
528         getEdgePoints(e, p1, p2);
529         r = QRect(p1, p2).normalized();
530         r += QMargins(gridX, gridY, gridX, gridY);
531 }
532
533
534 //
535 // Compute the actual size of the map:
536 //
537 // Output:
538 //   r:  The rectangle enclosing the map in logical coordinates
539 //
540 void MapDoc::getGridRect(QRect & rect)
541 {
542 //      rect.SetRectEmpty();
543         rect = QRect();
544
545         for(RoomConstItr r=room.getVector().begin(); r!=room.getVector().end(); r++)
546                 rect |= (*r)->getRect();
547
548         for(PageConstItr p=page.begin(); p!=page.end(); p++)
549                 rect |= getPageRect(*p);
550 }
551
552
553 //
554 // Compute the actual size of the map:
555 //
556 // Output:
557 //   size: The size of the map in grid units
558 //     Does not consider stubs or labels that may extend off the edge
559 //
560 void MapDoc::getGridSize(QSize & size)
561 {
562         QRect rect;
563         getGridRect(rect);
564
565         size.setWidth(rect.right() / gridX);
566         size.setHeight(rect.top() / -gridY);
567 }
568
569
570 //
571 // Add a page to the map:
572 //
573 // Input:
574 //   n:        The new page number (0 based)
575 //   newPage:  The page information
576 //
577 void MapDoc::addPage(int n, const MapPage & newPage)
578 {
579         ASSERT(n >= 0 && n <= page.size());
580
581         if (page.size() == maxRooms)
582                 return;
583
584         page.insert(page.begin() + n, newPage);
585         isDirty = true;
586 //      UpdateAllViews(NULL, dupPageCount, NULL);
587 }
588
589
590 //
591 // Append pages to the map:
592 //
593 // Input:
594 //   newPages:  The page information
595 //
596 void MapDoc::addPages(const PageVec & newPages)
597 {
598         if (page.size() + newPages.size() > maxRooms)
599                 return;
600
601         page.insert(page.end(), newPages.begin(), newPages.end());
602         isDirty = true;
603 //      UpdateAllViews(NULL, dupPageCount, NULL);
604 }
605
606
607 void MapDoc::addPages(int n)
608 {
609         page.reserve(page.size() + n);
610 }
611
612
613 //
614 // Delete a page from the map:
615 //
616 // Input:
617 //   n:  The (0 based) page number to delete
618 //
619 void MapDoc::deletePage(int n)
620 {
621         ASSERT(n >= 0 && n < page.size());
622         ASSERT(page.size() > 1);
623
624         isDirty = true;
625         QRect rect = getPageRect(n);
626
627         page.erase(page.begin() + n);
628
629 //      UpdateAllViews(NULL, dupPageCount, NULL);
630 }
631
632
633 QRect MapDoc::getPageRect(int p)
634 {
635         return QRect(page[p].pos, pageSize);
636 }
637
638
639 QRect MapDoc::getPageRect(const MapPage & p)
640 {
641         return QRect(p.pos, pageSize);
642 }
643
644
645 //
646 // Place pages to cover the entire map:
647 //
648 void MapDoc::layoutPages()
649 {
650 // Adds nothing for now, so nuke it
651 #if 0
652         UndoPaginate * undoRec = new UndoPaginate(*this);
653
654         QRect r;
655         getGridRect(r); // Just counts rooms, since UndoPaginate cleared the pages
656
657         const int
658                 width  = r.width(),
659                 height = r.height(),
660                 numX   = (width  + pageSize.width() - 1) / pageSize.width(),
661                 numY   = (height + pageSize.height() - 1) / pageSize.height();
662
663         if (!numX)
664                 page.insert(page.begin());  // One page at 0,0
665         else
666         {
667                 int i;
668                 float delta, step;
669                 PageItr p, p2;
670                 page.resize(numX * numY);
671
672                 if (numX == 1)
673                 {
674                         i = r.left - (pageSize.width() - width) / 2;
675                         i -= i % gridX;
676
677                         if (i < 0)
678                                 i = 0;
679
680                         for(p=page.begin(); p!=page.end(); ++p)
681                                 p->pos.x = i;
682                 }
683                 else
684                 {
685                         step = float(width - pageSize.width()) / float(numX);
686                         delta = 0;
687
688                         for(p=page.begin(), p2=p+numX; p!=p2; ++p, delta+=step)
689                         {
690                                 p->pos.x = r.left + delta;
691                                 p->pos.x -= p->pos.x % gridX;
692                         }
693
694                         p[-1].pos.x = r.right - pageSize.width();
695
696                         for(p2=page.begin(); p!=page.end(); ++p, ++p2)
697                                 p->pos.x = p2->pos.x;
698                 }
699
700                 if (numY == 1)
701                 {
702                         i = r.bottom + (pageSize.height() - height)/2;
703                         i -= i % gridY;
704
705                         if (i > 0)
706                                 i = 0;
707
708                         for(p=page.begin(); p!=page.end(); ++p)
709                                 p->pos.y = i;
710                 }
711                 else
712                 {
713                         step = float(pageSize.height() - height) / float(numY);
714                         delta = 0;
715                         p = p2 = page.begin();
716
717                         while (p2 != page.end())
718                         {
719                                 p2 += numX;
720
721                                 if (p2 == page.end())
722                                         i = r.top + pageSize.height();
723                                 else
724                                 {
725                                         i = r.bottom + delta;
726                                         i -= i % gridY;
727                                         delta += step;
728                                 }
729
730                                 while (p != p2)
731                                         (p++)->pos.y = i;
732                         }
733                 }
734         }
735
736         if (undoRec->getPages() == page)
737                 delete undoRec;             // No changes
738         else
739         {
740                 setUndoData(undoRec);
741                 isDirty = true;
742 //              UpdateAllViews(NULL, dupPageCount, NULL);
743         }
744 #endif
745 }
746
747
748 //
749 // Move a page:
750 //
751 // Input:
752 //   n:       The (0 based) page number to move
753 //   offset:  How much to move it
754 //
755 // Note:
756 //   Must not change undo data (may be used during undo)
757 //
758 void MapDoc::movePage(int n, const QSize & offset)
759 {
760         isDirty = true;
761
762 //      page[n].pos += offset;
763         page[n].pos += QPoint(offset.width(), offset.height());
764 //      UpdateAllViews(NULL);
765 }
766
767
768 //
769 // Decide if we need to repaginate:
770 //
771 // Returns:
772 //   true:   Some rooms are not (completely) on any page
773 //   false:  All rooms are on a page
774 //
775 bool MapDoc::needRepaginate()// const
776 {
777         const PageConstItr pBegin = page.begin(), pEnd = page.end();
778 //      QRect rr, pr;
779
780         for(RoomConstItr r=room.getVector().begin(); r!=room.getVector().end(); r++)
781         {
782                 QRect rr = (**r).getRect();
783
784                 for(PageConstItr p=pBegin; p!=pEnd; p++)
785                 {
786                         QRect pr = getPageRect(*p);
787                         QRect i = pr & rr;
788
789                         if (!i.isNull() && (i == rr))
790 //                      if (i.IntersectRect(&pr, &rr) && (i == rr))
791                                 goto nextRoom;
792                 }
793
794                 return true;                // This room not on any page
795
796                 nextRoom:
797                 ;
798         }
799
800         return false;
801 }
802
803
804 //
805 // Determine which page (if any) contains a given point:
806 //
807 // The point must be in the page border (one of the grid cells which
808 // displays the page number).
809 //
810 // Input:
811 //   pos:  The position to test
812 //
813 // Returns:
814 //   The (0 based) page number of the page containing the point
815 //   -1 if no page contains the point
816 //
817 int MapDoc::pageBorderHit(const QPoint & pos)
818 {
819         for(int i=page.size()-1; i>=0; i--)
820         {
821                 QRect r = getPageRect(i);
822
823                 if (r.contains(pos) &&
824 //              if (r.PtInRect(pos) &&
825                         (((pos.x() - r.left() < gridX) || (r.right()  - pos.x() < gridX)) !=
826                         ((pos.y() - r.top()  < gridY) || (r.bottom() - pos.y() < gridY))))
827                         // Point is in the X border or the Y border, but not both
828                         return i;
829         }
830
831         // Not in any page border
832         return -1;
833 }
834
835
836 //
837 // Add a new room:
838 //
839 // Returns:
840 //   The room number of the new room (-1 if error)
841 //
842 int MapDoc::addRoom(const QPoint & pos)
843 {
844         if (room.size() == maxRooms)
845                 return -1;
846
847         MapRoom * newRoom = new MapRoom();
848         newRoom->flags = rfBorder;
849         newRoom->pos = pos;
850 //      newRoom->computeNameLength();
851         addRoom(room.size(), newRoom);
852
853         return room.size() - 1;
854 }
855
856
857 //
858 // Add a new room:
859 //
860 // Input:
861 //   n:        The position for the new room [0..size]
862 //   newRoom:  A MapRoom object allocated by new
863 //
864 void MapDoc::addRoom(int n, MapRoom * newRoom)
865 {
866         ASSERT(n >= 0 && n <= room.size());
867         ASSERT(newRoom);
868
869         room.insert(n, newRoom);
870
871         if (n != room.size()-1)
872         {
873                 for(int i=edge.size()-1; i>=0; i--)
874                 {
875                         if (edge[i].room1 >= n)
876                                 ++edge[i].room1;
877
878                         if (edge[i].room2 >= n)
879                                 ++edge[i].room2;
880                 }
881         }
882
883         isDirty = true;
884         QRect rect = newRoom->getRect();
885 //      UpdateAllViews(NULL, 0, reinterpret_cast<CObject *>(&rect));
886 }
887
888
889 void MapDoc::addRooms(int n)
890 {
891         room.reserve(room.size() + n);
892 }
893
894
895 //
896 // Extract a room from the map:
897 //
898 // Input:
899 //   n:  The room number to remove
900 //
901 // Returns:
902 //   A pointer to the room extracted from the map
903 //   You can use this in the undo record
904 //
905 MapRoom * MapDoc::extractRoom(int n)
906 {
907         ASSERT(n >= 0 && n < room.size());
908
909         isDirty = true;
910
911         for(int i=edge.size()-1; i>=0; i--)
912         {
913                 if ((edge[i].room1 == n) ||
914                         (!(edge[i].type1 & etNoRoom2) && (edge[i].room2 == n)))
915                         deleteEdge(i);
916                 else
917                 {
918                         if (edge[i].room1 > n)
919                                 --edge[i].room1;
920
921                         if (edge[i].room2 > n)
922                                 --edge[i].room2;
923                 }
924         }
925
926         MapRoom * oldRoom = room.extract(n);
927         QRect rect = oldRoom->getRect();
928
929 //      UpdateAllViews(NULL, dupDeletedRoom, reinterpret_cast<CObject *>(&rect));
930
931         return oldRoom;
932 }
933
934
935 void MapDoc::deleteRoom(int n)
936 {
937         delete extractRoom(n);
938 }
939
940
941 //
942 // Find out if a new room would intersect an old one:
943 //
944 // There must be at least one grid of space around the room.  Ignores corners.
945 //
946 // Input:
947 //   pos: The proposed location for a new room
948 //
949 // Returns:
950 //   The room number of an intersecting room
951 //   -1 if the space is unoccupied
952 //   -2 if the space is off the grid
953 //
954 int MapDoc::findRoom(const QPoint & pos) const
955 {
956         if (pos.x() < 0 || pos.x() + roomWidth > docSize.width()
957                 || pos.y() < 0 || pos.y() + roomHeight > docSize.height())
958                 return -2;
959
960         RoomConstItr rm = room.getVector().begin();
961         QRect newRoom(pos, defaultRoomSize);
962 //      newRoom.InflateRect(gridX - 1, gridY - 1);
963         newRoom += QMargins(gridX - 1, gridY - 1, gridX - 1, gridY - 1);
964
965         for(int i=room.size()-1; i>=0; i--)
966         {
967                 if (!rm[i]->isCorner()
968                         && newRoom.intersects(rm[i]->getRect()))
969                         return i;
970         }
971
972         return -1;
973 }
974
975
976 //
977 // Move a room:
978 //
979 // Input:
980 //   n:       The room number to move
981 //   offset:  How much to move it
982 //
983 // Note:
984 //   Must not change undo data (may be used during undo)
985 //
986 void MapDoc::moveRoom(int n, const QSize & offset)
987 {
988         isDirty = true;
989
990 //      room[n].pos += offset;
991         room[n].pos += QPoint(offset.width(), offset.height());
992 //      UpdateAllViews(NULL);
993 }
994
995
996 //
997 // Determine which room (if any) contains a given point:
998 //
999 // Input:
1000 //   pos:  The position to test
1001 //
1002 // Output:
1003 //   corner:
1004 //     Which corner of the room was hit (rcNone if in center)
1005 //
1006 // Returns:
1007 //   The room number of the room containing the point
1008 //   -1 if no room contains the point
1009 //
1010 int MapDoc::roomHit(const QPoint & pos, RoomCorner * corner/*= NULL*/)
1011 {
1012         if (corner)
1013                 *corner = rcNone;
1014
1015         for(int i=room.size()-1; i>=0; i--)
1016         {
1017                 QRect r = room[i].getRect();
1018
1019                 if (r.contains(pos) == false)
1020                         continue;
1021
1022                 if (corner && (room[i].isCorner() == false))
1023                         *corner = CornerHit(pos - r.topLeft());
1024
1025                 return i;
1026         }
1027
1028         // Not in any room
1029         return -1;
1030 }
1031
1032
1033 RoomCorner MapDoc::CornerHit(const QPoint & pos)
1034 {
1035         long x = pos.x(), y = pos.y();
1036
1037         // FIXME this assumes all rooms are the same size
1038         if (y < roomHeight / 4)
1039         {
1040                 if (x < roomWidth * 3 / 16)
1041                         return rcNW;
1042                 else if (x < roomWidth * 5 / 16)
1043                         return rcNNW;
1044                 else if (x > roomWidth * 13 / 16)
1045                         return rcNE;
1046                 else if (x > roomWidth * 11 / 16)
1047                         return rcNNE;
1048                 else
1049                         return rcN;
1050         }
1051         else if (y > roomHeight * 3 / 4)
1052         {
1053                 if (x < roomWidth * 3 / 16)
1054                         return rcSW;
1055                 else if (x < roomWidth * 5 / 16)
1056                         return rcSSW;
1057                 else if (x > roomWidth * 13 / 16)
1058                         return rcSE;
1059                 else if (x > roomWidth * 11 / 16)
1060                         return rcSSE;
1061                 else
1062                         return rcS;
1063         }
1064         else
1065         {
1066                 if (x < roomWidth / 4)
1067                         return rcW;
1068                 else if (x > roomWidth * 3 / 4)
1069                         return rcE;
1070         }
1071
1072         return rcNone;
1073 }
1074
1075
1076 //
1077 // Change the map's title:
1078 //
1079 // Input:
1080 //   newName:  The new map title
1081 //
1082 void MapDoc::setName(const char * newName)
1083 {
1084         if (name == newName)
1085                 return;  // Nothing to do
1086
1087         name = newName;
1088         isDirty = true;
1089 }
1090
1091
1092 //
1093 // Change the map's comments:
1094 //
1095 // Input:
1096 //   newNote:  The new map comments
1097 //
1098 void MapDoc::setNote(const char * newNote)
1099 {
1100         if (note == newNote)
1101                 return;  // Nothing to do
1102
1103         note = newNote;
1104         trimRight(note);
1105         isDirty = true;
1106 }
1107
1108
1109 //
1110 // Change a room's flags:
1111 //
1112 // Input:
1113 //   n:        The room number to change
1114 //   set:      The flags to set
1115 //   clear:    The flags to clear (default 0)
1116 //
1117 // Note:
1118 //   flags in both SET and CLEAR will be set.
1119 //
1120 void MapDoc::setRoomFlags(int n, RoomFlags set, RoomFlags clear)
1121 {
1122         MapRoom & rm = room[n];
1123         RoomFlags old = rm.flags;
1124
1125         rm.flags &= ~clear;
1126         rm.flags |= set;
1127
1128         if (rm.flags != old)
1129         {
1130                 isDirty = true;
1131
1132 //              rm.getRect(rect).InflateRect(5, 5);
1133                 QRect rect = rm.getRect() += QMargins(5, 5, 5, 5);
1134 //              UpdateAllViews(NULL, 0, reinterpret_cast<CObject *>(&rect));
1135         }
1136 }
1137
1138
1139 //
1140 // Change a room's name
1141 //
1142 // Input:
1143 //   n:       The room number to change
1144 //   newName: The new room name
1145 //
1146 void MapDoc::setRoomName(int n, const char * newName)
1147 {
1148         MapRoom & rm = room[n];
1149
1150         if (rm.name == newName)
1151                 return; // Nothing to do
1152
1153         rm.name = newName;
1154         isDirty = true;
1155
1156 //      rm.getRect(rect).DeflateRect(5, 5);
1157         QRect rect = rm.getRect() -= QMargins(5, 5, 5, 5);
1158 //      UpdateAllViews(NULL, 0, reinterpret_cast<CObject *>(&rect));
1159 }
1160
1161
1162 //
1163 // Change a room's comments
1164 //
1165 // Input:
1166 //   n:       The room number to change
1167 //   newNote: The new room note
1168 //
1169 void MapDoc::setRoomNote(int n, const char * newNote)
1170 {
1171         MapRoom & rm = room[n];
1172
1173         if (rm.note == newNote)
1174                 return; // Nothing to do
1175
1176         const bool redraw = (rm.note.empty() || !*newNote);
1177         rm.note = newNote;
1178         trimRight(rm.note);
1179         isDirty = true;
1180
1181         if (redraw)
1182         {
1183 //              rm.getRect(rect).DeflateRect(5, 5); // PORT
1184                 QRect rect = rm.getRect() -= QMargins(5, 5, 5, 5);
1185 //              UpdateAllViews(NULL, dupRoomComment, reinterpret_cast<CObject *>(&rect));
1186         }
1187 //      else
1188 //              UpdateAllViews(NULL, dupRoomComment, NULL);
1189 }
1190
1191
1192 //
1193 // Undo handling:
1194 //--------------------------------------------------------------------
1195 // Set undo data:
1196 //
1197 void MapDoc::setUndoData(UndoRec * newUndo)
1198 {
1199         if (undoData != newUndo)
1200         {
1201                 delete undoData;
1202                 undoData = newUndo;
1203         }
1204 }
1205
1206
1207 #if 0
1208 //
1209 // Undo the last operation:
1210 //
1211 void MapDoc::OnEditUndo()
1212 {
1213         if (undoData)
1214                 setUndoData(undoData->undo(*this));
1215 }
1216
1217
1218 //
1219 // Update the Undo menu item:
1220 //
1221 void MapDoc::OnUpdateEditUndo(CCmdUI * pCmdUI)
1222 {
1223         pCmdUI->Enable(undoData && !locked);
1224
1225         CString text;
1226
1227         if (undoData)
1228         {
1229                 text = "&Undo ";
1230                 text += undoData->getName();
1231         }
1232         else
1233                 text = "Can't &Undo";
1234
1235         text += "\tCtrl+Z";
1236         pCmdUI->SetText(text);
1237 }
1238 #endif
1239
1240
1241 //
1242 // MapDoc commands
1243 //
1244 // Erase contents of document:
1245 //
1246 void MapDoc::DeleteContents(void)
1247 {
1248         locked = false;
1249         edge.erase(edge.begin(), edge.end());
1250         page.resize(1);
1251         page.front().pos.setX(0);
1252         page.front().pos.setY(0);
1253         room.removeAll();
1254         name.erase();
1255         note.erase();
1256         docSize = QSize(271 * gridX, 451 * gridY);
1257         setUndoData(NULL);
1258         needBackup = true;
1259 }
1260
1261
1262 #if 0
1263 //
1264 // Move all the rooms around:
1265 //
1266 // This moves only rooms that have connections.  Rooms that stand
1267 // alone (used as a title box, for example) are not moved.
1268 //
1269 void MapDoc::OnEditMoveMap(UINT cmd)
1270 {
1271         const int numEdges = edge.size();
1272         const int numRooms = room.size();
1273
1274         ByteVec selected;
1275         selected.resize(numRooms, false);
1276
1277         int i;
1278
1279         // Select all rooms that have a connection to somewhere:
1280         for(i=0; i<numEdges; i++)
1281         {
1282                 selected[edge[i].room1] = true;
1283
1284                 if ((edge[i].type1 & etNoRoom2) == 0)
1285                         selected[edge[i].room2] = true;
1286         }
1287
1288         // Compute the bounding rectangle of the selected rooms:
1289         int numSelected = 0;
1290         QRect r, temp;
1291
1292         r.SetRectEmpty();
1293
1294         for(i=0; i<numRooms; i++)
1295         {
1296                 if (selected[i])
1297                 {
1298                         ++numSelected;
1299                         r |= room[i].getRect(temp);
1300                 }
1301         }
1302
1303         // Calculate the distance to move them:
1304         QSize offset(0, 0);
1305
1306         switch (cmd)
1307         {
1308         case ID_EDIT_MOVE_CENTER:
1309                 offset.width() = (docSize.width() - r.right - r.left) / 2;
1310                 offset.height() = (docSize.height() + r.top + r.bottom) / -2;
1311                 break;
1312         case ID_EDIT_MOVE_TOP:    offset.height() = -r.bottom;             break;
1313         case ID_EDIT_MOVE_BOTTOM: offset.height() = -(docSize.height() + r.top); break;
1314         case ID_EDIT_MOVE_LEFT:   offset.width() = -r.left;               break;
1315         case ID_EDIT_MOVE_RIGHT:  offset.width() = docSize.width() - r.right;  break;
1316         }
1317
1318         offset.width() -= offset.width() % gridX; // Make sure the rooms stay on the grid
1319         offset.height() -= offset.height() % gridY;
1320
1321         // Do the move:
1322         if (!offset.width() && !offset.height())
1323                 return;                     // Do nothing
1324
1325         setUndoData(new UndoMove(isDirty, offset, numSelected, selected, 0, selected)); // No selected pages
1326
1327         for(i=0; i<numRooms; i++)
1328         {
1329                 if (selected[i])
1330                         moveRoom(i, offset);
1331         }
1332 }
1333
1334
1335 void MapDoc::OnNavigationMode()
1336 {
1337         locked = !locked;
1338
1339         if (locked)
1340         {
1341                 if (getRoomCount() > 0)
1342                         UpdateAllViews(NULL, dupNavigationMode);
1343                 else
1344                         locked = false;           // Can't enter navigation mode
1345         }
1346 }
1347
1348
1349 void MapDoc::OnUpdateEditMoveMap(CCmdUI * pCmdUI)
1350 {
1351         // You can't use Move Map unless you have edges:
1352         pCmdUI->Enable(!locked && edge.size());
1353 }
1354
1355
1356 void MapDoc::OnUpdateNavigationMode(CCmdUI * pCmdUI)
1357 {
1358         pCmdUI->SetCheck(locked);
1359
1360         if (!locked)
1361                 pCmdUI->Enable(getRoomCount() > 0);
1362 }
1363 #endif
1364