]> Shamusworld >> Repos - guemap/commitdiff
Initial commit of GUEmap v3.
authorShamus Hammons <jlhamm@acm.org>
Wed, 28 Apr 2021 02:38:55 +0000 (21:38 -0500)
committerShamus Hammons <jlhamm@acm.org>
Wed, 28 Apr 2021 02:38:55 +0000 (21:38 -0500)
I believe this is the minimal set of files to get GUEmap v3; if anything
turns up missing I will add it post-haste.

30 files changed:
.gitignore [new file with mode: 0644]
LICENSE [new file with mode: 0644]
guemap.pro [new file with mode: 0644]
res/guemap.ico [new file with mode: 0644]
res/guemap.qrc [new file with mode: 0644]
res/toolbar.bmp [new file with mode: 0644]
src/about.cpp [new file with mode: 0644]
src/about.h [new file with mode: 0644]
src/array.h [new file with mode: 0644]
src/file.cpp [new file with mode: 0644]
src/file.h [new file with mode: 0644]
src/globals.cpp [new file with mode: 0644]
src/globals.h [new file with mode: 0644]
src/guemapapp.cpp [new file with mode: 0644]
src/guemapapp.h [new file with mode: 0644]
src/mainwin.cpp [new file with mode: 0644]
src/mainwin.h [new file with mode: 0644]
src/mapdoc.cpp [new file with mode: 0644]
src/mapdoc.h [new file with mode: 0644]
src/mapview.cpp [new file with mode: 0644]
src/mapview.h [new file with mode: 0644]
src/mathconstants.h [new file with mode: 0644]
src/roomdialog.cpp [new file with mode: 0644]
src/roomdialog.h [new file with mode: 0644]
src/roomwidget.cpp [new file with mode: 0644]
src/roomwidget.h [new file with mode: 0644]
src/undo.cpp [new file with mode: 0644]
src/undo.h [new file with mode: 0644]
web/guemap.css [new file with mode: 0644]
web/index.html [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..950d20b
--- /dev/null
@@ -0,0 +1,7 @@
+guemap
+guemap.exe
+*.zip
+obj/
+.qmake.stash
+GUEmap*
+*.gmp
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..08ddefd
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,340 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/guemap.pro b/guemap.pro
new file mode 100644 (file)
index 0000000..63c8dd5
--- /dev/null
@@ -0,0 +1,91 @@
+# Use 'qmake -o Makefile guemap.pro'
+
+#CONFIG     += qt warn_on release c++11
+CONFIG     += qt warn_on debug c++11
+RESOURCES  += res/guemap.qrc
+#LIBS       += -Ldxflib/lib -ldxf
+#LIBS       += -lao
+#QMAKE_LIBS += -static
+QT         += widgets
+
+# We stuff all the intermediate crap into obj/ so it won't confuse us mere mortals ;-)
+OBJECTS_DIR = obj
+MOC_DIR     = obj
+RCC_DIR     = obj
+UI_DIR      = obj
+
+#win32: RC_ICONS = res/woz-icon.ico
+
+INCLUDEPATH += \
+        src
+
+DEPENDPATH = \
+        src
+
+HEADERS = \
+        src/about.h         \
+        src/array.h         \
+        src/file.h          \
+        src/globals.h       \
+        src/guemapapp.h     \
+        src/mainwin.h       \
+        src/mapdialog.h     \
+        src/mapdoc.h        \
+        src/mapview.h       \
+        src/mathconstants.h \
+        src/roomdialog.h    \
+        src/roomwidget.h    \
+        src/undo.h
+
+SOURCES = \
+        src/about.cpp      \
+        src/file.cpp       \
+        src/globals.cpp    \
+        src/guemapapp.cpp  \
+        src/mainwin.cpp    \
+        src/mapdialog.cpp  \
+        src/mapdoc.cpp     \
+        src/mapview.cpp    \
+        src/roomdialog.cpp \
+        src/roomwidget.cpp \
+        src/undo.cpp
+
+#HEADERS = \
+#        src/array.h \
+#       src/childfrm.h \
+#       src/cjm_algorithm.h \
+#       src/commentdlg.h \
+#       src/file.h \
+#       src/finddlg.h \
+#       src/globals.h \
+#       src/guemap.h \
+#       src/mainfrm.h \
+#       src/mapdoc.h \
+#       src/mapprop.h \
+#       src/mapview.h \
+#       src/navopt.h \
+#       src/properties.h \
+#       src/resource.h \
+#       src/scrollzoom.h \
+#       src/stdafx.h \
+#       src/undo.h
+
+#SOURCES = \
+#       src/childfrm.cpp \
+#       src/commentdlg.cpp \
+#       src/export.cpp \
+#       src/file.cpp \
+#       src/finddlg.cpp \
+#       src/findpath.cpp \
+#       src/globals.cpp \
+#       src/guemap.cpp \
+#       src/mainfrm.cpp \
+#       src/mapdoc.cpp \
+#       src/mapprop.cpp \
+#       src/mapview.cpp \
+#       src/navopt.cpp \
+#       src/properties.cpp \
+#       src/scrollzoom.cpp \
+#       src/stdafx.cpp \
+#       src/undo.cpp
+
diff --git a/res/guemap.ico b/res/guemap.ico
new file mode 100644 (file)
index 0000000..d0789bb
Binary files /dev/null and b/res/guemap.ico differ
diff --git a/res/guemap.qrc b/res/guemap.qrc
new file mode 100644 (file)
index 0000000..3ed226f
--- /dev/null
@@ -0,0 +1,7 @@
+<!DOCTYPE RCC><RCC version="1.0">
+       <qresource prefix="/res">
+               <file>guemap.ico</file>
+               <file>toolbar.bmp</file>
+       </qresource>
+</RCC>
+
diff --git a/res/toolbar.bmp b/res/toolbar.bmp
new file mode 100644 (file)
index 0000000..ebaff56
Binary files /dev/null and b/res/toolbar.bmp differ
diff --git a/src/about.cpp b/src/about.cpp
new file mode 100644 (file)
index 0000000..d62a929
--- /dev/null
@@ -0,0 +1,67 @@
+//
+// about.cpp - Credits
+//
+// by James Hammons
+// (C) 2019 Underground Software
+//
+
+#include "about.h"
+
+
+AboutWindow::AboutWindow(QWidget * parent/*= 0*/): QWidget(parent, Qt::Dialog)
+{
+       setWindowTitle(tr("About GUEmap..."));
+
+       layout = new QVBoxLayout();
+//     layout->setSizeConstraint(QLayout::SetFixedSize);
+       setLayout(layout);
+
+       QString s = QString(tr(
+               "<table>"
+               "<tr>"
+               "<td style='padding-right:15px; float:left'><img src=':/res/guemap.ico'></td>"
+               "<td>"
+//             "<img src=':/res/about-logo.png' style='padding-right:15px; float:left'>"
+
+               "<table>"
+               "<tr><td align='right' width='140'><b>GUEmap: </b></td><td>Free, IF Mapping Software</td></tr>"
+               "<tr><td align='right'><b>Version: </b></td><td>3.0.0</td></tr>"
+               "<tr><td align='right'><b>License: </b></td><td>GPL v2 or later</td></tr>"
+               "<tr><td align='right'><b>Chief Architect: </b></td><td>James Hammons (shamus)</td></tr>"
+               "<tr><td align='right'><b>Coders: </b></td><td>James Hammons (shamus)<br>Christopher J. Madsen</td></tr>"
+//             "<tr><td align='right'><b>Testers: </b></td><td>shamus</td></tr>"
+               "<tr><td align='right'><b>Homepage: </b></td><td>http://shamusworld.gotdns.org/guemap/</td></tr>"
+               "</table>"
+               "</td>"
+               "</tr>"
+               "</table>"
+
+               "<table>"
+               "<tr><td>"
+               "<br>"
+               "This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.<br><br>"
+               "This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.<br><br>"
+               "You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA."
+               "<br><hr><br>"
+               "<i>&ldquo;Do nothing without deliberation; and when you have acted, do not regret it.  Do not go on a path full of hazards, and do not stumble over stony ground.  Do not be overconfident on a smooth way, and give good heed to your paths.&rdquo;</i>"
+               "</td></tr>"
+               "<tr><td align='right'>"
+               "<i>&mdash;Sirach 32:19-22</i>"
+               "</td></tr>"
+               "</table>"
+       ));
+
+       text = new QLabel(s);
+       text->setWordWrap(true);
+//     text->setMinimumWidth(480);
+//     text->setMaximumWidth(800);
+       layout->addWidget(text);
+}
+
+
+void AboutWindow::keyPressEvent(QKeyEvent * e)
+{
+       if (e->key() == Qt::Key_Escape || e->key() == Qt::Key_Return)
+               hide();
+}
+
diff --git a/src/about.h b/src/about.h
new file mode 100644 (file)
index 0000000..5e4d80b
--- /dev/null
@@ -0,0 +1,28 @@
+//
+// about.h: Credits where credits are due ;-)
+//
+// by James Hammons
+// (C) 2019 Underground Software
+//
+
+#ifndef __ABOUT_H__
+#define __ABOUT_H__
+
+#include <QtWidgets>
+
+class AboutWindow: public QWidget
+{
+       public:
+               AboutWindow(QWidget * parent = 0);
+
+       protected:
+               void keyPressEvent(QKeyEvent *);
+
+       private:
+               QVBoxLayout * layout;
+               QLabel * text;
+               QLabel * image;
+};
+
+#endif // __ABOUT_H__
+
diff --git a/src/array.h b/src/array.h
new file mode 100644 (file)
index 0000000..83b1504
--- /dev/null
@@ -0,0 +1,163 @@
+//\r
+// GUEmap\r
+// Copyright 1997-2007 by Christopher J. Madsen\r
+// (C) 2019 James Hammons\r
+//\r
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)\r
+// any later version.  See LICENSE file for details.\r
+//\r
+// Template class for dynamic array (based on STL vector)\r
+//\r
+\r
+#ifndef __ARRAY_H__\r
+#define __ARRAY_H__\r
+\r
+// CURRENTLY included by globals.h.  !!! FIX !!!\r
+\r
+//#include "globals.h"\r
+typedef vector<uint8_t>::size_type VecSize;\r
+\r
+//typedef Array<MapRoom, RoomVec> RoomArray;\r
+//Type == MapRoom, TVec == RoomVec == vector<MapRoom *>\r
+\r
+//template <class Type, class TVec>\r
+class RoomArray\r
+{\r
+       public:\r
+               RoomVec v;\r
+\r
+       public:\r
+               RoomArray() {}\r
+               RoomArray(RoomVec & v2) { v.swap(v2); }\r
+               ~RoomArray() { RoomArray::removeAll(); }\r
+\r
+               MapRoom & operator[](VecSize pos)\r
+               {\r
+                       ASSERT(pos < v.size());\r
+                       return *v[pos];\r
+               }\r
+\r
+               const MapRoom & operator[](VecSize pos) const\r
+               {\r
+                       ASSERT(pos < v.size());\r
+                       return *v[pos];\r
+               }\r
+\r
+               bool empty() const\r
+               {\r
+                       return v.empty();\r
+               }\r
+\r
+               MapRoom * extract(VecSize pos)\r
+               {\r
+                       ASSERT(pos < v.size());\r
+                       MapRoom * r = v[pos];\r
+                       v.erase(v.begin() + pos);\r
+                       return r;\r
+               }\r
+\r
+               void extractAll(RoomVec & v2) { v2.clear(); v.swap(v2); }\r
+\r
+               MapRoom * getPtr(VecSize pos)\r
+               {\r
+                       ASSERT(pos < v.size());\r
+                       return v[pos];\r
+               }\r
+\r
+               const MapRoom * getPtr(VecSize pos) const\r
+               {\r
+                       ASSERT(pos < v.size());\r
+                       return v[pos];\r
+               }\r
+\r
+               RoomVec & getVector()\r
+               {\r
+                       return v;\r
+               }\r
+\r
+               const RoomVec & getVector() const\r
+               {\r
+                       return v;\r
+               }\r
+\r
+               void append(MapRoom * obj)\r
+               {\r
+                       v.insert(v.end(), obj);\r
+               }\r
+\r
+               void insert(VecSize pos, MapRoom * obj)\r
+               {\r
+                       ASSERT(pos <= v.size());\r
+                       v.insert(v.begin() + pos, obj);\r
+               }\r
+\r
+               void remove(VecSize pos)\r
+               {\r
+                       ASSERT(pos < v.size());\r
+                       delete v[pos];\r
+                       v.erase(v.begin() + pos);\r
+               }\r
+\r
+               RoomVec::iterator remove(RoomVec::iterator itr)\r
+               {\r
+                       delete *itr;\r
+                       return v.erase(itr);\r
+               }\r
+\r
+               void removeAll()\r
+               {\r
+                       RoomVec::iterator p = v.begin();\r
+\r
+                       while (p != v.end())\r
+                               delete *(p++);\r
+\r
+                       v.clear();\r
+               }\r
+\r
+               void reserve(VecSize n)\r
+               {\r
+                       v.reserve(n);\r
+               }\r
+\r
+               void resize(VecSize n)\r
+               {\r
+                       ASSERT(v.size() <= n);\r
+                       v.insert(v.end(), n - v.size(), NULL);\r
+               }\r
+\r
+               VecSize size() const\r
+               {\r
+                       return v.size();\r
+               }\r
+\r
+               RoomArray & operator=(const RoomArray & source)\r
+               {\r
+                       if (this != &source)\r
+                       {\r
+                               removeAll();\r
+                               v.reserve(source.v.size());\r
+\r
+                               for(RoomVec::const_iterator e=source.v.begin(); e!=source.v.end(); e++)\r
+                                       append(new MapRoom(**e));\r
+                       }\r
+\r
+                       return *this;\r
+               }\r
+\r
+               bool operator==(const RoomArray & x) const\r
+               {\r
+                       if (v.size() != x.v.size())\r
+                               return false;\r
+\r
+                       for(RoomVec::const_iterator a=v.begin(), b=x.v.begin(); a!=v.end(); a++, b++)\r
+                       {\r
+                               if (**a != **b)\r
+                                       return false;\r
+                       }\r
+\r
+                       return true;\r
+               }\r
+};\r
+\r
+#endif // __ARRAY_H__\r
+\r
diff --git a/src/file.cpp b/src/file.cpp
new file mode 100644 (file)
index 0000000..38ac4b5
--- /dev/null
@@ -0,0 +1,665 @@
+//
+// GUEmap
+//
+// (C) 1997-2007 Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+// Implementation of the FileReader class
+//
+// N.B.: These functions are now endian-safe (before v3, they were whatever
+//       Microsoft's serializer made them); the native format is Little Endian
+//       and can be properly read and written regardless of the underlying
+//       architecture.
+//
+
+#include "file.h"
+
+#include "globals.h"
+#include "mapdoc.h"
+
+
+static const uint8_t fileHeader[8] = { 0xC7, 0x55, 0xC5, 0x6D, 0xE1, 0x70, 0x83, 0x0D };
+
+//
+// Class FileReader:
+//--------------------------------------------------------------------
+// Member Variables:
+//   ar:  The CArchive we're reading from
+//   rcCache:
+//     A cached record code (from peekNextRecord)
+//      -1 if cache is empty
+//       0 if at end of file
+//
+//--------------------------------------------------------------------
+// Constructor:
+//
+// Input:
+//   archive:  The CArchive to read from
+//
+FileReader::FileReader(FILE * archive): ar(archive), rcCache(-1)
+{
+}
+
+
+uint8_t FileReader::byte()
+{
+       return fgetc(ar);
+}
+
+
+uint16_t FileReader::word()
+{
+       uint16_t b1 = fgetc(ar);
+       uint16_t b2 = fgetc(ar);
+
+       return (b2 << 8) | b1;
+}
+
+
+uint32_t FileReader::dword()
+{
+       uint32_t dword = 0;
+
+       for(int i=0; i<4; i++)
+               dword = (dword << 8) | fgetc(ar);
+
+       return dword;
+}
+
+
+void FileReader::str(string & s)
+{
+       StrIdx len;
+       uint8_t lenB = byte();
+
+       if (lenB != 0xFF)
+               len = lenB;
+       else
+       {
+               uint16_t lenW = word();
+
+               if (lenW != 0xFFFF)
+                       len = lenW;
+               else
+               {
+                       uint32_t lenDW = dword();
+                       len = lenDW;
+               }
+       }
+
+       if (len)
+       {
+               char buf[len + 1];
+               fread(buf, 1, len, ar);
+               buf[len] = 0;
+               s = buf;
+       }
+       else
+               s.erase();
+}
+
+
+bool FileReader::boolean()
+{
+       return bool(byte());
+}
+
+
+//
+// Skip to the beginning of a record:
+//
+// Throws a badIndex (corrupt file) exception if the stream is not at
+// the beginning of a record.  If the record is not of the specified
+// type, it is skipped and the process repeats until the specified
+// type of record is found.  If EOF is encountered while searching, it
+// throws endOfFile.
+//
+// If no errors occur, the record header is removed from the stream.
+//
+// Input:
+//   type:  The record type to search for
+//
+void FileReader::findRecord(uint8_t type)
+{
+       uint8_t rc, c;
+
+       // Repeat until error or success
+       for(;;)
+       {
+               // Make sure we are at the beginning of a record:
+               if (rcCache > 0)
+               {
+                       rc = rcCache;
+                       rcCache = -1;
+               }
+               else
+               {
+                       if ((rcCache == 0) || (byte() != fcStartRecord))
+                       {
+                               corrupt:
+//                             AfxThrowArchiveException(CArchiveException::badIndex, ar.GetFile()->GetFilePath());
+                               return;
+                       }
+
+                       rc = byte();
+               }
+
+               // See if we found the right kind of record:
+               if (rc == type)
+                       return;
+
+               // Wrong record type, so skip fields until we find the end of the record
+               while ((c = byte()) != fcEndRecord)
+               {
+                       skipField(c);
+               }
+
+               if (byte() != rc)
+                       // The ending record type does not match the beginning type
+                       goto corrupt;
+       }
+}
+
+
+//
+// Find out what type the next record is:
+//
+// The stream must be at the beginning of a record or at end of file.
+//
+// Returns:
+//   The type of the record about to be read
+//   0 if stream is at end of file (WITHOUT throwing endOfFile)
+//
+uint8_t FileReader::peekNextRecord()
+{
+       if (rcCache >= 0)
+               return rcCache;
+
+       uint8_t b;
+
+#if 0
+       try
+       {
+               ar >> b;
+       }
+       catch (CArchiveException * e)
+       {
+               if (e->m_cause == CArchiveException::endOfFile)
+               {
+                       e->Delete();
+                       return (rcCache = 0);     // Signal end-of-file
+               }
+
+               throw;                      // Re-throw any other exception
+       }
+#else
+       int c = fgetc(ar);
+
+       if (c == EOF)
+       {
+               return (rcCache = 0);
+       }
+
+       b = (uint8_t)c;
+#endif
+
+       if (b != fcStartRecord)
+//             AfxThrowArchiveException(CArchiveException::badIndex, ar.GetFile()->GetFilePath());
+               return 0xFF;//???
+
+       b = fgetc(ar);
+
+       rcCache = b;
+       return b;
+}
+
+
+//
+// Skip an unrecognized field:
+//
+// Skips over an unknown or unwanted field.  If this is an extended field code,
+// you MUST read the extended type info before calling skipField().
+//
+// Input:
+//   fieldCode:  The field code for this field.  For an extended field, this is
+//               the fcExtended code (with size information), not the extended
+//               ID byte.
+//
+void FileReader::skipField(uint8_t fieldCode)
+{
+       // Get the field size (lower 3 bits of code)
+       uint32_t size = fieldCode & fcSizeMask;
+
+       switch (size)
+       {
+       case 0: // String
+               size = byte();
+
+               if (size == 0xFF)
+               {
+                       size = word();
+
+                       if (size == 0xFFFF)
+                               size = dword();
+               }
+               break;
+       case 1:                      // 1-4 byte fields
+       case 2:
+       case 3:
+       case 4:
+               break;
+       case 5:
+               // 8 byte field
+               size = 8;
+               break;
+       case 6:
+               // Variable length field of size byte
+               size = byte();
+               break;
+       case 7:
+               // Variable length field of size word
+               size = word();
+               break;
+       }
+
+       while (size-- > 0)
+               fgetc(ar);
+}
+
+
+void WriteByte(FILE * fp, uint8_t b)
+{
+       fputc(b, fp);
+}
+
+
+void WriteWord(FILE * fp, uint16_t w)
+{
+       fputc(w & 0xFF, fp);
+       fputc(w >> 8, fp);
+}
+
+
+void WriteDWord(FILE * fp, uint32_t dw)
+{
+       fputc((dw >> 0) & 0xFF, fp);
+       fputc((dw >> 8) & 0xFF, fp);
+       fputc((dw >> 16) & 0xFF, fp);
+       fputc((dw >> 24) & 0xFF, fp);
+}
+
+
+void WriteBool(FILE * fp, bool b)
+{
+       fputc((b ? 1 : 0), fp);
+}
+
+
+void WriteString(FILE * fp, std::string s)
+{
+       const int len = s.length();
+
+       if (len < 0xFF)
+               WriteByte(fp, len);
+       else
+       {
+               WriteByte(fp, 0xFF);
+
+               if (len < 0xFFFF)
+                       WriteWord(fp, len);
+               else
+               {
+                       WriteWord(fp, 0xFFFF);
+                       WriteDWord(fp, len);
+               }
+       }
+
+       if (len)
+               fwrite(s.c_str(), 1, len, fp);
+}
+
+
+//
+// Current GUEmap file version is 5; can read 2 thru 4 too
+//
+bool ReadFile(MapDoc * doc, const char * name)
+{
+       FILE * ar = fopen(name, "r");
+
+       if (!ar)
+               return false;
+
+       uint8_t buf[sizeof(fileHeader)];
+
+       if ((fread(buf, 1, sizeof(buf), ar) != sizeof(buf))
+               || memcmp(fileHeader, buf, sizeof(buf)))
+       {
+//                     AfxThrowArchiveException(CArchiveException::badClass, ar.GetFile()->GetFilePath());
+               printf("ReadFile: Bad header\n");
+               return false;
+       }
+
+       FileReader fr(ar);
+
+       uint16_t version = fr.word();
+printf("ReadFile: Version # is %i\n", version);
+
+       if ((version < 2) || (version > 5))
+       {
+//                     AfxThrowArchiveException(CArchiveException::badSchema, ar.GetFile()->GetFilePath());
+               printf("ReadFile: Bad version number (%u)\n", version);
+               return false;
+       }
+
+       FileReader read(ar);
+       uint16_t edgeCount, roomCount;
+       uint8_t pageCount = 0;
+       uint8_t b;
+
+       // Get version number (??? Again ???)
+       /*uint16_t version2 =*/ read.word();
+
+       // Read map information
+       read.findRecord(recMap);
+
+       while ((b = read.byte()) != fcEndRecord)
+       {
+               switch (b)
+               {
+               case fcMapName:
+                       read.str(doc->name);
+                       break;
+               case fcMapNote:
+                       read.str(doc->note);
+                       break;
+               case fcMapNumEdges:
+                       edgeCount = read.word();
+                       break;
+               case fcMapNumPages:
+                       pageCount = read.byte();
+                       break;
+               case fcMapNumRooms:
+                       roomCount = read.word();
+                       break;
+               default:
+                       read.skipField(b);
+                       break;
+               }
+       }
+//printf("Number of edges/rooms/pages: %u/%u/%u\n", edgeCount, roomCount, pageCount);
+
+       if (read.byte() != recMap)
+       {
+               corruptFile:
+//                     AfxThrowArchiveException(CArchiveException::badIndex, ar.GetFile()->GetFilePath());
+               printf("ReadFile: Corrupted file\n");
+               return false;
+       }
+
+       // Read rooms
+       doc->room.resize(roomCount);
+
+       for(RoomItr r=doc->room.getVector().begin(); r!=doc->room.getVector().end(); r++)
+       {
+               read.findRecord(recRoom);
+               *r = new MapRoom();
+               (**r).flags = rfBorder;
+
+               while ((b = read.byte()) != fcEndRecord)
+               {
+                       switch (b)
+                       {
+                       case fcRoomFlags: (**r).flags = read.byte(); break;
+                       case fcRoomName:  read.str((**r).name);      break;
+                       case fcRoomNote:  read.str((**r).note);      break;
+                       case fcRoomPosition:
+                       (**r).pos.rx() = read.word() * gridX;
+                       (**r).pos.ry() = read.word() * gridY;
+                       break;
+                       default: read.skipField(b); break;
+                       }
+               }
+
+               // Adjust for wonky coordinates for old map versions (2-4)
+               if (version < 5)
+                       (**r).pos.ry() -= gridY * ((**r).flags & rfCorner ? 1 : 3);
+
+               QRect rect = (**r).getRect();
+               uint8_t tmp = read.byte();
+
+               if ((tmp != recRoom) || (rect.right() > doc->docSize.width()) || (rect.top() > doc->docSize.height()))
+               {
+                       printf("\ntmp=%u (should be 2), rect.right=%i, docSize.width=%i, rect.bottom=%i, rect.top=%i, docSize.height=%i\n", tmp, rect.right(), doc->docSize.width(), rect.bottom(), rect.top(), doc->docSize.height());
+                       goto corruptFile;
+               }
+       }
+
+       // Read edges
+       doc->edge.resize(edgeCount);
+
+       for(EdgeItr e=doc->edge.begin(); e!=doc->edge.end(); e++)
+       {
+               read.findRecord(recEdge);
+               memset(&(*e), 0, sizeof(*e));
+
+               while ((b = read.byte()) != fcEndRecord)
+               {
+                       switch (b)
+                       {
+                       case fcEdgeEnd1:
+                               e->end1  = RoomCorner(read.byte());
+                               e->room1 = read.byte();
+                               break;
+                       case fcEdgeEnd1L:
+                               e->end1  = RoomCorner(read.byte());
+                               e->room1 = read.word();
+                               break;
+                       case fcEdgeEnd2:
+                               e->end2  = RoomCorner(read.byte());
+                               e->room2 = read.byte();
+                               break;
+                       case fcEdgeEnd2L:
+                               e->end2  = RoomCorner(read.byte());
+                               e->room2 = read.word();
+                               break;
+                       case fcEdgeType:
+                               e->type1 = read.byte();
+                               e->type2 = read.byte();
+                               break;
+                       default: read.skipField(b);
+                               break;
+                       }
+               }
+
+               if ((read.byte() != recEdge) || (e->room1 >= roomCount)
+                       || (e->end1 < 0) || (e->end1 >= rcNumCorners)
+                       || ((e->type1 & etDirection) > etOut) || (!(e->type1 & etNoRoom2)
+                       && ((e->room2 >= roomCount) || (e->room2 == e->room1)
+                       || (e->end2 < 0) || (e->end2 >= rcNumCorners)
+                       || ((e->type2 & etDirection) > etOut))))
+                       goto corruptFile;
+       }
+
+       // Read pages
+       if (pageCount)
+       {
+               doc->page.resize(pageCount);
+
+               for(PageItr p=doc->page.begin(); p!=doc->page.end(); p++)
+               {
+                       read.findRecord(recPage);
+
+                       while ((b = read.byte()) != fcEndRecord)
+                       {
+                               switch (b)
+                               {
+                               case fcPagePosition:
+                                       p->pos.rx() = read.word() * gridX;
+                                       p->pos.ry() = read.word() * gridY;
+                                       break;
+                               default:
+                                       read.skipField(b);
+                                       break;
+                               }
+                       }
+
+                       QRect rect = doc->getPageRect(*p);
+
+                       if ((read.byte() != recPage)
+                               || (rect.right() > doc->docSize.width())
+                               || (rect.top() > doc->docSize.height()))
+                               goto corruptFile;
+               }
+       }
+
+       doc->needBackup = true;
+       fclose(ar);
+
+       return true;
+}
+
+
+//
+// Write a GUEmap v5 file
+//
+bool WriteFile(MapDoc * doc, const char * name)
+{
+       FILE * file = fopen(name, "w");
+
+       if (!file)
+               return false;
+
+       fwrite(fileHeader, 1, sizeof(fileHeader), file);
+
+       QSize gridSize;
+       doc->getGridSize(gridSize);
+
+       // We only write version 5 now, no turning back  :-P
+       WriteWord(file, 5);
+
+       // Write the map information record
+       WriteWord(file, 4);                             // Version 4 (??? OF WHAT ???)
+       WriteByte(file, fcStartRecord);
+       WriteByte(file, recMap);
+
+       if (!doc->name.empty())
+       {
+               WriteByte(file, fcMapName);
+               WriteString(file, doc->name);
+       }
+
+       if (!doc->note.empty())
+       {
+               WriteByte(file, fcMapNote);
+               WriteString(file, doc->note);
+       }
+
+       WriteByte(file, fcMapNumRooms);
+       WriteWord(file, doc->room.size());
+       WriteByte(file, fcMapNumEdges);
+       WriteWord(file, doc->edge.size());
+       WriteByte(file, fcMapNumPages);
+       WriteByte(file, doc->page.size());
+       WriteByte(file, fcMapSize);
+       WriteWord(file, gridSize.width());
+       WriteWord(file, gridSize.height());
+       WriteByte(file, fcEndRecord);
+       WriteByte(file, recMap);
+
+       // Write the rooms
+       for(RoomConstItr r=doc->room.getVector().begin(); r!=doc->room.getVector().end(); r++)
+       {
+               WriteByte(file, fcStartRecord);
+               WriteByte(file, recRoom);
+               WriteByte(file, fcRoomPosition);
+               WriteWord(file, (*r)->pos.x() / gridX);
+               WriteWord(file, (*r)->pos.y() / gridY);
+
+               if (!(*r)->name.empty())
+               {
+                       WriteByte(file, fcRoomName);
+                       WriteString(file, (*r)->name);
+               }
+
+               if (!(*r)->note.empty())
+               {
+                       WriteByte(file, fcRoomNote);
+                       WriteString(file, (*r)->note);
+               }
+
+               if ((*r)->flags != rfBorder)
+               {
+                       WriteByte(file, fcRoomFlags);
+                       WriteByte(file, (*r)->flags);
+               }
+
+               WriteByte(file, fcEndRecord);
+               WriteByte(file, recRoom);
+       }
+
+       // Write the edges
+       for(EdgeConstItr e=doc->edge.begin(); e!=doc->edge.end(); e++)
+       {
+               WriteByte(file, fcStartRecord);
+               WriteByte(file, recEdge);
+
+               if (e->room1 > 0xFF)
+               {
+                       WriteByte(file, fcEdgeEnd1L);
+                       WriteByte(file, e->end1);
+                       WriteWord(file, e->room1);
+               }
+               else
+               {
+                       WriteByte(file, fcEdgeEnd1);
+                       WriteByte(file, e->end1);
+                       WriteByte(file, e->room1);
+               }
+
+               if (e->type1 || e->type2)
+               {
+                       WriteByte(file, fcEdgeType);
+                       WriteByte(file, e->type1);
+                       WriteByte(file, e->type2);
+               }
+
+               if (!(e->type1 & etNoRoom2))
+               {
+                       if (e->room2 > 0xFF)
+                       {
+                               WriteByte(file, fcEdgeEnd2L);
+                               WriteByte(file, e->end2);
+                               WriteWord(file, e->room2);
+                       }
+                       else
+                       {
+                               WriteByte(file, fcEdgeEnd2);
+                               WriteByte(file, e->end2);
+                               WriteByte(file, e->room2);
+                       }
+               }
+
+               WriteByte(file, fcEndRecord);
+               WriteByte(file, recEdge);
+       }
+
+       // Write the pages
+       for(PageConstItr p=doc->page.begin(); p!=doc->page.end(); p++)
+       {
+               WriteByte(file, fcStartRecord);
+               WriteByte(file, recPage);
+               WriteByte(file, fcPagePosition);
+               WriteWord(file, p->pos.x() / gridX);
+               WriteWord(file, p->pos.y() / gridY);
+               WriteByte(file, fcEndRecord);
+               WriteByte(file, recPage);
+       }
+
+       fclose(file);
+
+       return true;
+}
+
diff --git a/src/file.h b/src/file.h
new file mode 100644 (file)
index 0000000..2be4301
--- /dev/null
@@ -0,0 +1,99 @@
+//
+// GUEmap
+//
+// (C) 1997-2007 Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+// File constants and interface of the FileReader class
+//
+
+#ifndef __FILE_H__
+#define __FILE_H__
+
+#include <string>
+#include <stdint.h>
+#include <stdio.h>
+
+class MapDoc;
+
+//
+// Codes for GUEmap files:
+//--------------------------------------------------------------------
+// Normal codes:
+
+const uint8_t
+       fcSizeMask    = 0007,
+       fcStartRecord = 0011,
+       fcEndRecord   = 0000,
+
+//
+// Record specific codes:
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+// Map Information:
+
+       fcMapNumRooms = 0022,
+       fcMapNumEdges = 0032,
+       fcMapName     = 0040,
+       fcMapNote     = 0050,
+       fcMapSize     = 0064,
+       fcMapNumPages = 0071,
+
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+// Page:
+
+       fcPagePosition = 0024,
+
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+// Room:
+
+       fcRoomName       = 0020,
+       fcRoomPosition = 0034,
+       fcRoomNote     = 0040,
+       fcRoomFlags    = 0051,
+
+//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
+// Edge:
+
+       fcEdgeEnd1      = 0022,
+       fcEdgeEnd1L     = 0023,
+       fcEdgeEnd2      = 0032,
+       fcEdgeEnd2L     = 0033,
+       fcEdgeType      = 0042,
+
+//
+// Record codes:
+//
+       recMap  = 1,
+       recRoom = 2,
+       recEdge = 3,
+       recPage = 4;
+
+
+class FileReader
+{
+       private:
+               FILE * ar;
+               int rcCache;
+
+       public:
+               FileReader(FILE * archive);
+               uint8_t byte();
+               uint16_t word();
+               uint32_t dword();
+               void str(std::string &);
+               bool boolean();
+               void findRecord(uint8_t type);
+               uint8_t peekNextRecord();
+               void skipField(uint8_t fieldCode);
+};
+
+// Exported functions
+
+bool ReadFile(MapDoc *, const char *);
+bool WriteFile(MapDoc *, const char *);
+
+#endif // __FILE_H__
+
diff --git a/src/globals.cpp b/src/globals.cpp
new file mode 100644 (file)
index 0000000..804960c
--- /dev/null
@@ -0,0 +1,279 @@
+//
+// GUEmap
+// (C) 1997-2007 Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+
+#include "globals.h"
+#include <math.h>
+
+
+//
+// Class MapEdge:
+//
+// Member Variables:
+//   end1:   The corner of room1 where this edge begins
+//   end2:   The corner of room2 where this edge ends
+//   room1:  The room where this edge begins
+//   room2:  The room where this edge ends
+//   type1:
+//     The type of exit this is from room1
+//     The etSpecial codes are only used with type1
+//   type2:
+//     The type2 of exit this is from room2
+//
+MapEdge::MapEdge(): room1(0), room2(0), end1(rcNone), end2(rcNone), type1(0), type2(0)
+{
+}
+
+
+MapEdge::MapEdge(RoomNum r1, RoomNum r2, EdgeType t1, EdgeType t2, RoomCorner e1, RoomCorner e2): room1(r1), room2(r2), end1(e1), end2(e2), type1(t1), type2(t2)
+{
+}
+
+
+//
+// Actually swap the rooms for this edge
+//
+void MapEdge::Swap(void)
+{
+       RoomNum tr1 = room1;
+       room1 = room2;
+       room2 = tr1;
+
+       EdgeType tt = type1;
+       type1 = type2;
+       type2 = tt;
+
+       RoomCorner tc = end1;
+       end1 = end2;
+       end2 = tc;
+}
+
+
+bool MapEdge::HasRoom(RoomNum r)
+{
+       if ((room1 != r) && (room2 != r))
+               return false;
+
+       return true;
+}
+
+
+//
+// Return the MapEdge but with the RoomNums, EdgeTypes, & RoomCorners swapped
+//
+MapEdge MapEdge::Swapped(void) const
+{
+       // Not sure why, but MapDoc::findEdge() keeps the etObstructed on room 1 for some reason...
+       return MapEdge(room2, room1, type2 | (type1 & etObstructed), type1 & ~etObstructed, end2, end1);
+}
+
+
+//
+// Class MapRoom:
+//
+// Member Variables:
+//   flags:
+//     Bitmask of RoomFlags constants:
+//       rfBorder:  Room has a border (default)
+//       rfCorner:  Room is a corner connector (no name, one grid square)
+//   pos:
+//     The position of the top left corner of the room
+//     Given in logical MM_HIMETRIC coordinates.
+//   name:
+//     The name of the room.
+//
+MapRoom::MapRoom(): flags(0)
+{
+}
+
+
+MapRoom::MapRoom(const MapRoom & o)
+{
+       *this = o;
+}
+
+
+MapRoom & MapRoom::operator=(const MapRoom & source)
+{
+       pos = source.pos;
+       name = source.name;
+       note = source.note;
+       flags = source.flags;
+       return *this;
+}
+
+
+QRect MapRoom::getRect(void)
+{
+       return QRect(pos,
+               (flags & rfCorner ? QSize(gridX, gridY) : QSize(roomWidth, roomHeight)));
+}
+
+
+QRect MapRoom::getTranslatedRect(QPoint t)
+{
+       return QRect(pos + t,
+               (flags & rfCorner ? QSize(gridX, gridY) : QSize(roomWidth, roomHeight)));
+}
+
+
+QPoint MapRoom::GetCenter(void)
+{
+       return QPoint(pos +
+               (flags & rfCorner
+                       ? QPoint(gridX / 2, gridY / 2)
+                       : QPoint(roomWidth / 2, roomHeight / 2)));
+}
+
+
+RoomFlags MapRoom::isCorner() const
+{
+       return (flags & rfCorner);
+}
+
+
+bool MapRoom::operator!=(const MapRoom & mr) const
+{
+       if (pos != mr.pos)
+               return false;
+
+       if (name != mr.name)
+               return false;
+
+       if (note != mr.note)
+               return false;
+
+       return true;
+}
+
+
+//
+// Miscellaneous functions:
+//--------------------------------------------------------------------
+//
+// Trim whitespace from left of string:
+//
+void trimLeft(string & s)
+{
+       StrIdx  p = s.find_first_not_of("\n\r\t ");
+
+       if (p > 0)
+               s.erase(0, p);
+}
+
+
+//
+// Trim whitespace from right of string:
+//
+void trimRight(string & s)
+{
+       StrIdx  p = s.find_last_not_of("\n\r\t ");
+
+       if (p < s.length())
+               s.erase(p + 1);
+}
+
+
+//
+// Consolidates action creation from a multi-step process to a single-step one.
+//
+QAction * CreateAction(QString name, QString tooltip,
+       QString statustip, QIcon icon, QKeySequence key, bool checkable/*= false*/, QObject * parent/*= 0*/)
+{
+       QAction * action = new QAction(icon, name, parent);
+       action->setToolTip(tooltip);
+       action->setStatusTip(statustip);
+       action->setShortcut(key);
+       action->setCheckable(checkable);
+
+       return action;
+}
+
+
+//
+// This is essentially the same as the previous function, but this allows more
+// than one key sequence to be added as key shortcuts.
+//
+QAction * CreateAction(QString name, QString tooltip,
+       QString statustip, QIcon icon, QKeySequence key1, QKeySequence key2,
+       bool checkable/*= false*/, QObject * parent/*= 0*/)
+{
+       QAction * action = new QAction(icon, name, parent);
+       action->setToolTip(tooltip);
+       action->setStatusTip(statustip);
+       QList<QKeySequence> keyList;
+       keyList.append(key1);
+       keyList.append(key2);
+       action->setShortcuts(keyList);
+       action->setCheckable(checkable);
+
+       return action;
+}
+
+
+//
+// Miscellaneous geometrical helper functions
+//
+double Magnitude(QPointF p)
+{
+       return sqrt((p.x() * p.x()) + (p.y() * p.y()));
+}
+
+
+QPointF UnitVector(QPointF p)
+{
+       double magnitude = Magnitude(p);
+
+       if (magnitude == 0)
+               return QPointF(0, 0);
+
+       return QPointF(p.x() / magnitude, p.y() / magnitude);
+}
+
+
+double Angle(QPointF p)
+{
+       return atan2(p.y(), p.x());
+}
+
+
+double Angle(QPoint p)
+{
+       return atan2((double)p.y(), (double)p.x());
+}
+
+
+double Dot(QPointF a, QPointF b)
+{
+       return (a.x() * b.x()) + (a.y() * b.y());
+}
+
+
+double Determinant(QPointF a, QPointF b)
+{
+       return (a.x() * b.y()) - (b.x() * a.y());
+}
+
+
+// Returns the parameter of a point in space to this vector. If the parameter
+// is between 0 and 1, the normal of the vector to the point is on the vector.
+double ParameterOfLineAndPoint(QPointF tail, QPointF head, QPointF point)
+{
+       // Geometric interpretation:
+       // The parameterized point on the vector lineSegment is where the normal of
+       // the lineSegment to the point intersects lineSegment. If the pp < 0, then
+       // the perpendicular lies beyond the 1st endpoint. If pp > 1, then the
+       // perpendicular lies beyond the 2nd endpoint.
+
+       QPointF lineSegment = head - tail;
+       double magnitude = Magnitude(lineSegment);
+       QPointF pointSegment = point - tail;
+       double t = Dot(lineSegment, pointSegment) / (magnitude * magnitude);
+       return t;
+}
+
diff --git a/src/globals.h b/src/globals.h
new file mode 100644 (file)
index 0000000..a39836c
--- /dev/null
@@ -0,0 +1,158 @@
+//
+// GUEmap
+// (C) 1997-2007 Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+
+#ifndef __GLOBALS_H__
+#define __GLOBALS_H__
+
+#include <QtWidgets>
+#include <QObject>
+#include <assert.h>
+#include <stdint.h>
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#define ASSERT(x) assert(x)
+
+using namespace std;
+
+typedef uint16_t RoomNum;
+typedef uint8_t  EdgeType;
+typedef uint8_t  RoomFlags;
+
+const int
+       gridX        = 120,      // PORT 0.25 in
+       gridY        = 72,       // PORT 0.15 in
+       maxPages     = 250,
+       maxRooms     = 65535,
+       roomWidth    = 3 * gridX,
+       roomHeight   = 3 * gridY;
+
+const EdgeType
+       etNormal     = 0x00,
+       etUp         = 0x01,
+       etDown       = 0x02,
+       etIn         = 0x03,
+       etOut        = 0x04,
+       etDirection  = 0x07,
+       etObstructed = 0x08,
+       etNoExit     = 0x10,
+       etUnexplored = 0x20,
+       etOneWay     = 0x40,
+       etLoopBack   = 0x80,
+       etNoRoom2    = etNoExit | etUnexplored | etLoopBack,
+       etSpecial    = etObstructed | etNoExit | etOneWay | etUnexplored | etLoopBack;
+
+const RoomFlags
+       rfBorder = 0x01,
+       rfCorner = 0x02;
+
+enum RoomCorner
+{
+       rcNone = -1,
+       rcN, rcS, rcE, rcW,
+       rcNE, rcNW, rcSE, rcSW,
+       rcNNE, rcNNW, rcSSE, rcSSW,
+       rcCorner,
+       rcNumCorners,
+       rcUp = rcNumCorners, rcDown, rcIn, rcOut,
+       rcNumDirections
+};
+
+struct MapEdge
+{
+       RoomNum room1, room2;
+       RoomCorner end1, end2;
+       EdgeType type1, type2;
+
+       MapEdge();
+       MapEdge(RoomNum r1, RoomNum r2, EdgeType t1, EdgeType t2, RoomCorner e1, RoomCorner e2);
+       void Swap(void);
+       MapEdge Swapped(void) const;
+       bool HasRoom(RoomNum);
+};
+
+struct MapPage
+{
+       QPoint pos;
+
+       MapPage(): pos(0, 0) {}
+       bool operator==(const MapPage & o) const { return pos == o.pos; }
+};
+
+struct MapRoom
+{
+       QPoint pos;
+       string name;
+       string note;
+       RoomFlags flags;
+
+       MapRoom();
+       MapRoom(const MapRoom & o);
+       MapRoom & operator=(const MapRoom & source);
+       QRect getRect(void);
+       QRect getTranslatedRect(QPoint);
+       QPoint GetCenter(void);
+       RoomFlags isCorner(void) const;
+       bool operator!=(const MapRoom & mr) const;
+};
+
+
+typedef string::size_type       StrIdx;
+typedef string::iterator        StrItr;
+typedef string::const_iterator  StrConstItr;
+
+typedef vector<uint8_t>         ByteVec;
+typedef ByteVec::iterator       ByteItr;
+typedef ByteVec::const_iterator ByteConstItr;
+//typedef ByteVec::size_type      VecSize;
+
+typedef vector<uint16_t>        WordVec;
+
+typedef vector<MapEdge>         EdgeVec;
+typedef EdgeVec::iterator       EdgeItr;
+typedef EdgeVec::const_iterator EdgeConstItr;
+
+typedef vector<MapPage>         PageVec;
+typedef PageVec::iterator       PageItr;
+typedef PageVec::const_iterator PageConstItr;
+
+typedef vector<MapRoom *>       RoomVec;
+typedef RoomVec::iterator       RoomItr;
+typedef RoomVec::const_iterator RoomConstItr;
+//typedef Array<MapRoom, RoomVec> RoomArray;
+
+typedef ByteVec                 PageNumVec;
+typedef ByteVec::iterator       PNItr;
+typedef ByteVec::const_iterator PNConstItr;
+
+typedef WordVec                 RoomNumVec;
+typedef WordVec::iterator       RNItr;
+typedef WordVec::const_iterator RNConstItr;
+
+#include "array.h"
+
+// Exported functions
+void trimLeft(string & s);
+void trimRight(string & s);
+QAction * CreateAction(QString name, QString tooltip, QString statustip,
+       QIcon icon, QKeySequence key, bool checkable = false, QObject * parent = 0);
+QAction * CreateAction(QString name, QString tooltip, QString statustip,
+       QIcon icon, QKeySequence key1, QKeySequence key2, bool checkable = false,
+       QObject * parent = 0);
+double Magnitude(QPointF);
+QPointF UnitVector(QPointF);
+double Angle(QPointF);
+double Angle(QPoint);
+double Dot(QPointF, QPointF);
+double Determinant(QPointF, QPointF);
+double ParameterOfLineAndPoint(QPointF tail, QPointF head, QPointF point);
+
+#endif // __GLOBALS_H__
+
diff --git a/src/guemapapp.cpp b/src/guemapapp.cpp
new file mode 100644 (file)
index 0000000..5f0a5da
--- /dev/null
@@ -0,0 +1,657 @@
+//
+// GUEmap
+// Copyright 1997-2007 by Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+// GUEmap.cpp: Defines the class behaviors for the application
+//
+
+#include "guemapapp.h"
+
+#include <QApplication>
+#include "mainwin.h"
+
+// Main app constructor--we stick globally accessible stuff here...
+
+GUEMapApp::GUEMapApp(int & argc, char * argv[]): QApplication(argc, argv)//, charWnd(NULL)
+{
+       mainWindow = new MainWin;
+       mainWindow->show();
+}
+
+
+// Here's the main application loop--short and simple...
+int main(int argc, char * argv[])
+{
+       Q_INIT_RESOURCE(guemap);        // This must the same name as the qrc filename
+
+       GUEMapApp app(argc, argv);
+
+       return app.exec();
+}
+
+#if 0
+#include "StdAfx.hpp"
+#include "GUEmap.hpp"
+
+#include <winspool.h>           // For printer settings
+
+#include "MainFrm.hpp"
+#include "ChildFrm.hpp"
+#include "CommentDlg.hpp"
+#include "FindDlg.hpp"
+#include "MapDoc.hpp"
+#include "MapView.hpp"
+#include "NavOpt.hpp"
+
+#ifdef _DEBUG
+#define new DEBUG_NEW
+#undef THIS_FILE
+static char THIS_FILE[] = __FILE__;
+#endif
+
+bool     GUEmapEatClicks = true;
+HCURSOR  handCursor = NULL;
+
+const char *const iniCDlg        = "CommentDialog";
+const char *const iniMainWin     = "MainWindow";
+const char *const iniWinLeft     = "Left";
+const char *const iniWinRight    = "Right";
+const char *const iniWinTop      = "Top";
+const char *const iniWinBottom   = "Bottom";
+
+const char *const iniNavigation  = "Navigation";
+const char *const iniNavCopy     = "AutoCopy";
+const char *const iniNavCRLF     = "AddCRLF";
+const char *const iniNavEdit     = "AutoEdit";
+const char *const iniNavStub     = "PreferUnexplored";
+
+/////////////////////////////////////////////////////////////////////////////
+// Miscellaneous functions:
+//--------------------------------------------------------------------
+// Copy a string, replacing CRLF with space:
+//
+// Assumes that any CR is followed by a LF.
+//
+// Input:
+//   source:  The string to copy
+//
+// Output:
+//   dest:    The copied string, with any CRLF replaced by a space
+
+void copyToOneLine(String& dest, const String& source)
+{
+  dest = source;
+  StrIdx  p = 0;
+  while ((p = dest.find(_T('\r'),p)) != String::npos) {
+    dest.erase(p,1);
+    dest[p] = _T(' ');
+  }
+} // end copyToOneLine
+
+//--------------------------------------------------------------------
+// Fill paragraphs in a string:
+//
+// Removes all leading and trailing space, then removes CRLF pairs
+// unless they are on a blank line or followed by a space.  Assumes
+// that any CR is followed by a LF.
+//
+// Input:
+//   s:  The string to fill
+//
+// Output:
+//   s:  The filled string
+
+void fillParagraphs(String& s)
+{
+  trimRight(s);
+  trimLeft(s);
+
+  StrIdx  p = 0;
+  // Convert tabs to spaces (just in case):
+  while ((p = s.find(_T('\t'),p)) != String::npos)
+    s[p] = _T(' ');
+
+  p = 0;
+  while ((p = s.find(_T('\r'),p)) != String::npos) {
+    if ((s[p-1] != _T('\n')) &&                       // Not a blank line &
+        s.find_first_not_of(_T("\n\r "), p) == p+2) { // next char not a space
+      StrIdx  len = 1;
+      while (s[p-1] == _T(' ')) {
+        --p;
+        ++len;
+      } // end while spaces precede this CR
+      s.erase(p,len);           // Erase the CR and preceding spaces
+      s[p] = _T(' ');           // Change the LF to a space
+    } else
+      ++p; // Don't remove this CRLF
+  } // end while more CRs
+} // end fillParagraphs
+
+//--------------------------------------------------------------------
+// Set a window's position:
+//
+// Input:
+//   pos:  The new position of the window (in screen coordinates)
+//   w:    The window to move
+//
+// Note:
+//   If pos.right is 0, or any part of the window is outside the
+//   current desktop, then we don't move the window.
+
+void setWindowPos(const CRect& pos, CWnd* w)
+{
+  if (pos.right && (pos.left < pos.right) && (pos.top < pos.bottom)) {
+    CRect desktop;
+    ::GetWindowRect(::GetDesktopWindow(),&desktop);
+    if ((pos.left   >= desktop.left)  &&
+        (pos.right  <= desktop.right) &&
+        (pos.top    >= desktop.top)   &&
+        (pos.bottom <= desktop.bottom))
+      w->MoveWindow(&pos);
+  }
+} // end setWindowPos
+
+//--------------------------------------------------------------------
+// Trim whitespace from left of string:
+
+void trimLeft(String& s)
+{
+  StrIdx  p = s.find_first_not_of(_T("\n\r\t "));
+  if (p > 0) s.erase(0, p);
+} // end trimLeft
+
+//--------------------------------------------------------------------
+// Trim whitespace from right of string:
+
+void trimRight(String& s)
+{
+  StrIdx  p = s.find_last_not_of(_T("\n\r\t "));
+  if (p < s.length()) s.erase(p+1);
+} // end trimRight
+
+/////////////////////////////////////////////////////////////////////////////
+// CMapApp
+
+BEGIN_MESSAGE_MAP(CMapApp, CWinApp)
+       //{{AFX_MSG_MAP(CMapApp)
+       ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
+       ON_COMMAND(ID_EDIT_FIND, OnEditFind)
+       ON_COMMAND(ID_VIEW_COMMENTS, OnViewComments)
+       ON_COMMAND(ID_VIEW_SETTINGS, OnViewSettings)
+       ON_UPDATE_COMMAND_UI(ID_VIEW_COMMENTS, OnUpdateViewComments)
+       //}}AFX_MSG_MAP
+       // Standard file based document commands
+       ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
+       ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
+       // Standard print setup command
+       ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
+       // Standard help commands (see also MainFrame.cpp)
+       ON_COMMAND(ID_HELP_INDEX, CWinApp::OnHelpIndex)
+END_MESSAGE_MAP();
+
+/////////////////////////////////////////////////////////////////////////////
+// CMapApp construction
+
+CMapApp::CMapApp()
+: clipboard(NULL),
+  commentDlg(NULL),
+  findDlg(NULL),
+  initialized(false)
+{
+       // TODO: add construction code here,
+       // Place all significant initialization in InitInstance
+}
+
+CMapApp::~CMapApp()
+{
+  delete clipboard;
+  delete commentDlg;
+  delete findDlg;
+} // end CMapApp::~CMapApp
+
+/////////////////////////////////////////////////////////////////////////////
+// The one and only CMapApp object
+
+CMapApp theApp;
+String  mapWinClass;
+
+/////////////////////////////////////////////////////////////////////////////
+// CMapApp initialization
+
+BOOL CMapApp::InitInstance()
+{
+  // Standard initialization
+  // If you are not using these features and wish to reduce the size
+  //  of your final executable, you should remove from the following
+  //  the specific initialization routines you do not need.
+
+#ifdef _AFXDLL
+  Enable3dControls();           // Call this when using MFC in a shared DLL
+#else
+  Enable3dControlsStatic();    // Call this when linking to MFC statically
+#endif
+
+  mapWinClass = AfxRegisterWndClass(
+    CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW,
+    LoadStandardCursor(IDC_ARROW), // Standard cursor
+    (HBRUSH)::GetStockObject(WHITE_BRUSH)
+  );
+
+  handCursor = LoadCursor(IDC_HAND);
+
+  LoadStdProfileSettings(9); // Load standard INI file options (including MRU)
+
+  // Register the application's document templates.  Document templates
+  //  serve as the connection between documents, frame windows and views.
+
+  CMultiDocTemplate* pDocTemplate;
+  pDocTemplate = new CMultiDocTemplate(
+    IDR_GUEMAPTYPE,
+    RUNTIME_CLASS(CMapDoc),
+    RUNTIME_CLASS(CChildFrame), // custom MDI child frame
+    RUNTIME_CLASS(CMapView));
+  AddDocTemplate(pDocTemplate);
+
+  // create room comments dialog
+  if (!(commentDlg = new CCommentDlg()))
+    return FALSE;
+
+  loadWindowPos(commentDlg->pos, iniCDlg);
+
+  // create main MDI Frame window
+  CMainFrame* pMainFrame = new CMainFrame;
+  if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
+    return FALSE;
+  m_pMainWnd = pMainFrame;
+
+  // Enable file manager drag/drop and DDE Execute open
+  EnableShellOpen();
+  RegisterShellFileTypes(TRUE);
+
+  // Parse command line for standard shell commands, DDE, file open
+  CCommandLineInfo cmdInfo;
+  ParseCommandLine(cmdInfo);
+
+  // Dispatch commands specified on the command line
+  if (!ProcessShellCommand(cmdInfo))
+    return FALSE;
+
+  // The main window has been initialized, so show and update it.
+  CRect  pos;
+  loadWindowPos(pos, iniMainWin);
+  setWindowPos(pos, pMainFrame);
+  pMainFrame->ShowWindow(m_nCmdShow);
+  pMainFrame->UpdateWindow();
+  pMainFrame->DragAcceptFiles(TRUE);
+
+  setLandscape();               // Switch to landscape mode
+
+  // Load navigation options:
+  autoEdit = GetProfileInt(iniNavigation,iniNavEdit,true);
+  naviCopy = GetProfileInt(iniNavigation,iniNavCopy,true);
+  naviCRLF = GetProfileInt(iniNavigation,iniNavCRLF,false);
+  preferUnexplored = GetProfileInt(iniNavigation,iniNavStub,true);
+
+  initialized = true;
+  return TRUE;
+} // end CMapApp::InitInstance
+
+//--------------------------------------------------------------------
+// Load a window position from the INI file:
+//
+// Input:
+//   section:  The title of the section to load it from
+//
+// Output:
+//   pos:  The window position
+
+void CMapApp::loadWindowPos(CRect& pos, LPCTSTR section)
+{
+  pos.left   = GetProfileInt(section, iniWinLeft,   0);
+  pos.right  = GetProfileInt(section, iniWinRight,  0);
+  pos.top    = GetProfileInt(section, iniWinTop,    0);
+  pos.bottom = GetProfileInt(section, iniWinBottom, 0);
+} // end CMapApp::loadWindowPos
+
+//--------------------------------------------------------------------
+// Set landscape mode as default:
+//
+// From KB: Q126897 (1-21-96)
+
+void CMapApp::setLandscape()
+{
+  // Get default printer settings.
+  PRINTDLG   pd;
+
+  pd.lStructSize = (DWORD) sizeof(PRINTDLG);
+  if (GetPrinterDeviceDefaults(&pd)) {
+    // Lock memory handle.
+    DEVMODE FAR* pDevMode =
+      (DEVMODE FAR*)::GlobalLock(m_hDevMode);
+    LPDEVNAMES lpDevNames;
+    LPTSTR lpszDriverName, lpszDeviceName, lpszPortName;
+    HANDLE hPrinter;
+
+    if (pDevMode) {
+      // Change printer settings in here.
+      pDevMode->dmOrientation = DMORIENT_LANDSCAPE;
+      // Unlock memory handle.
+      lpDevNames = (LPDEVNAMES)GlobalLock(pd.hDevNames);
+      lpszDriverName = (LPTSTR )lpDevNames + lpDevNames->wDriverOffset;
+      lpszDeviceName = (LPTSTR )lpDevNames + lpDevNames->wDeviceOffset;
+      lpszPortName   = (LPTSTR )lpDevNames + lpDevNames->wOutputOffset;
+
+      ::OpenPrinter(lpszDeviceName, &hPrinter, NULL);
+      ::DocumentProperties(NULL,hPrinter,lpszDeviceName,pDevMode,
+                           pDevMode, DM_IN_BUFFER|DM_OUT_BUFFER);
+
+      // Sync the pDevMode.
+      // See SDK help for DocumentProperties for more info.
+      ::ClosePrinter(hPrinter);
+      ::GlobalUnlock(m_hDevNames);
+      ::GlobalUnlock(m_hDevMode);
+    }
+  }
+} // end CMapApp::setLandscape
+
+/////////////////////////////////////////////////////////////////////////////
+// CMapApp shutdown:
+
+int CMapApp::ExitInstance()
+{
+  if (initialized) { // We finished initialization
+    WriteProfileInt(iniNavigation, iniNavEdit, autoEdit);
+    WriteProfileInt(iniNavigation, iniNavCopy, naviCopy);
+    WriteProfileInt(iniNavigation, iniNavCRLF, naviCRLF);
+    WriteProfileInt(iniNavigation, iniNavStub, preferUnexplored);
+
+    if (commentDlg) {
+      if (commentDlg->GetSafeHwnd())
+        OnViewComments();       // Destroy comment window
+      saveWindowPos(commentDlg->pos, iniCDlg);
+    }
+
+    if (findDlg && findDlg->GetSafeHwnd())
+      findDlg->DestroyWindow();
+  }
+
+  return CWinApp::ExitInstance();
+} // end CMapApp::ExitInstance
+
+//--------------------------------------------------------------------
+// Save a window position to the INI file:
+//
+// Input:
+//   pos:      The window position
+//   section:  The title of the section to save it in
+
+void CMapApp::saveWindowPos(LPCRECT pos, LPCTSTR section)
+{
+  WriteProfileInt(section, iniWinLeft,   pos->left);
+  WriteProfileInt(section, iniWinRight,  pos->right);
+  WriteProfileInt(section, iniWinTop,    pos->top);
+  WriteProfileInt(section, iniWinBottom, pos->bottom);
+} // end CMapApp::saveWindowPos
+
+/////////////////////////////////////////////////////////////////////////////
+// CAboutDlg dialog used for App About
+
+class CAboutDlg : public CDialog
+{
+ public:
+  CAboutDlg();
+
+  // Dialog Data
+  //{{AFX_DATA(CAboutDlg)
+  enum { IDD = IDD_ABOUTBOX };
+  CString      username;
+  //}}AFX_DATA
+
+  // ClassWizard generated virtual function overrides
+  //{{AFX_VIRTUAL(CAboutDlg)
+ protected:
+  virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
+  //}}AFX_VIRTUAL
+
+  // Implementation
+ protected:
+  //{{AFX_MSG(CAboutDlg)
+  //}}AFX_MSG
+  DECLARE_MESSAGE_MAP();
+}; // end CAboutDlg
+
+CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
+{
+       //{{AFX_DATA_INIT(CAboutDlg)
+       username = _T("");
+       //}}AFX_DATA_INIT
+}
+
+void CAboutDlg::DoDataExchange(CDataExchange* pDX)
+{
+       CDialog::DoDataExchange(pDX);
+       //{{AFX_DATA_MAP(CAboutDlg)
+       //}}AFX_DATA_MAP
+}
+
+BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
+       //{{AFX_MSG_MAP(CAboutDlg)
+       //}}AFX_MSG_MAP
+END_MESSAGE_MAP();
+
+// App command to run the dialog
+void CMapApp::OnAppAbout()
+{
+  CAboutDlg aboutDlg;
+  aboutDlg.DoModal();
+} // end CMapApp::OnAppAbout
+
+/////////////////////////////////////////////////////////////////////////////
+// CMapApp commands
+//--------------------------------------------------------------------
+// Find text in comments:
+
+void CMapApp::OnEditFind()
+{
+  if (!findDlg) {
+    VERIFY(findDlg = new CFindDlg(commentDlg));
+    if (!findDlg) return;
+  }
+
+  if (findDlg->GetSafeHwnd() != NULL) {
+    findDlg->SetActiveWindow();
+    findDlg->GotoDlgCtrl(findDlg->GetDlgItem(IDC_FIND_WHAT));
+  } else {
+    findDlg->Create();
+    setWindowPos(findDlg->pos, findDlg);
+  }
+} // end CMapApp::OnEditFind
+
+//====================================================================
+// The Room Comments dialog:
+//--------------------------------------------------------------------
+// Display or hide the free-floating Room Comments dialog:
+
+void CMapApp::OnViewComments()
+{
+  if (commentDlg->GetSafeHwnd() != NULL) {
+    commentDlg->GetWindowRect(&commentDlg->pos);
+    commentDlg->DestroyWindow();
+  } else {
+    commentDlg->Create();
+    setWindowPos(commentDlg->pos, commentDlg);
+    commentDlg->SetWindowText(commentDlg->title.empty()
+                              ? _T("Room Comments")
+                              : commentDlg->title.c_str());
+    commentDlg->SendDlgItemMessage(IDC_CD_COMMENT,WM_ENABLE,
+                                   commentDlg->fromView != NULL);
+    commentDlg->SendDlgItemMessage(IDC_CD_COMMENT,EM_SETREADONLY,
+                                   commentDlg->fromView == NULL);
+    commentDlg->SendDlgItemMessage(IDC_CD_COMMENT,EM_SETSEL, -1, -2);
+  }
+} // end CMapApp::OnViewComments
+
+void CMapApp::OnUpdateViewComments(CCmdUI* pCmdUI)
+{
+  pCmdUI->SetCheck(commentDlg->GetSafeHwnd() != NULL);
+} // end CMapApp::OnUpdateViewComments
+
+//--------------------------------------------------------------------
+// Move the Room Comments dialog so it doesn't obscure the map:
+//
+// Input:
+//   mapPos:
+//     The screen coordinates that should not be obscured
+//     Must be normalized
+
+void CMapApp::adjustCommentPos(LPCRECT mapPos)
+{
+  if (commentDlg->GetSafeHwnd()) {
+    CRect  dlgPos, r;
+    commentDlg->GetWindowRect(&dlgPos);
+    if (r.IntersectRect(dlgPos, mapPos)) {
+      CRect  mainPos;
+      m_pMainWnd->GetClientRect(&mainPos);
+      m_pMainWnd->ClientToScreen(&mainPos);
+      BOOL max;
+      const CWnd* curWin =
+        static_cast<CMainFrame*>(m_pMainWnd)->MDIGetActive(&max);
+      if (max) {
+        curWin = curWin->GetTopWindow(); // Get the active view
+        curWin->GetClientRect(&r);       // and make sure we don't
+        curWin->ClientToScreen(&r);      // cover up the scroll bars
+        mainPos.bottom = r.bottom;       // on the right and bottom
+        mainPos.right  = r.right;
+      } // end if active window is maximized
+      dlgPos.OffsetRect(mainPos.right - dlgPos.right, // Top right
+                        mainPos.top - dlgPos.top);
+      if (!r.IntersectRect(dlgPos, mapPos)) goto move;
+      dlgPos.OffsetRect(mainPos.right - dlgPos.right, // Bottom right
+                        mainPos.bottom - dlgPos.bottom);
+      if (!r.IntersectRect(dlgPos, mapPos)) goto move;
+      dlgPos.OffsetRect(mainPos.left - dlgPos.left,   // Bottom left
+                        mainPos.bottom - dlgPos.bottom);
+      if (!r.IntersectRect(dlgPos, mapPos)) goto move;
+      dlgPos.OffsetRect(mainPos.left - dlgPos.left,   // Top left
+                        mainPos.top - dlgPos.top);
+      if (r.IntersectRect(dlgPos, mapPos)) return;  // Give up
+     move:
+      commentDlg->MoveWindow(dlgPos);
+    } // end if area is obscured
+  } // end if commentDlg is visible
+} // end CMapApp::adjustCommentPos
+
+//--------------------------------------------------------------------
+// Disable comment dialog when closing its view:
+//
+// Input:
+//   view:  The view that is closing
+
+void CMapApp::closingView(const CMapView* view)
+{
+  if (commentDlg->fromView == view)
+    setComment(NULL, NULL);
+} // end CMapApp::closingView
+
+//--------------------------------------------------------------------
+// Switch to the Room Comments dialog:
+
+void CMapApp::editComment()
+{
+  if (commentDlg->GetSafeHwnd() == NULL)
+    OnViewComments();
+  else {
+    commentDlg->SetFocus();
+    commentDlg->SendDlgItemMessage(IDC_CD_COMMENT, EM_SETSEL, -1, -2);
+  }
+} // end CMapApp::editComment
+
+//--------------------------------------------------------------------
+// Update the Room Comments dialog:
+//
+// Input:
+//   fromView:
+//     The map view which is posting this comment
+//     NULL means the view is closing, so disable the dialog
+//   room:
+//     The room whose comment should be displayed in the dialog
+//     NULL means no room is selected, so display map comment
+//   takeFocus: (default true)
+//     TRUE means to override a comment from a different view
+
+void CMapApp::setComment(CMapView* fromView, const MapRoom* room,
+                         bool takeFocus) const
+{
+  if (!commentDlg->active &&
+      (takeFocus || (commentDlg->fromView == fromView))) {
+    commentDlg->fromView = fromView;
+    if (room && !(room->flags & rfCorner)) {
+      Strcpy(commentDlg->comment, room->note);
+      copyToOneLine(commentDlg->title, room->name);
+      commentDlg->roomComment = true;
+    } else {
+      if (fromView) {
+        const CMapDoc*  doc = fromView->GetDocument();
+        ASSERT_VALID(doc);
+
+        Strcpy(commentDlg->comment, doc->getNote());
+        commentDlg->title = doc->getName();
+        if (!commentDlg->title.empty())
+          commentDlg->title += _T(" -- ");
+      } else {
+        commentDlg->comment.Empty();
+        commentDlg->title.erase();
+      } // end else no active view
+      commentDlg->title += _T("Map Comments");
+      commentDlg->roomComment = false;
+    } // end else no current room
+    if (commentDlg->GetSafeHwnd()) {
+      commentDlg->UpdateData(FALSE);
+      commentDlg->SetWindowText(commentDlg->title.empty()
+                                ? _T("Room Comments")
+                                : commentDlg->title.c_str());
+      commentDlg->SendDlgItemMessage(IDC_CD_COMMENT,WM_ENABLE,
+                                     fromView != NULL);
+      commentDlg->SendDlgItemMessage(IDC_CD_COMMENT,EM_SETREADONLY,
+                                     fromView == NULL);
+    } // end if comment dialog is open
+  } // end if we should update the comment
+} // end CMapApp::setComment
+
+//====================================================================
+// CMapApp Options dialog:
+//--------------------------------------------------------------------
+// Bring up the Options dialog:
+
+void CMapApp::OnViewSettings()
+{
+  editOptions();
+} // end CMapApp::OnViewSettings
+
+//--------------------------------------------------------------------
+// Bring up the Options dialog:
+
+void CMapApp::editOptions()
+{
+  CPropertySheet dlg(IDR_OPTIONS);
+
+  COptNavPage  nav;
+  nav.copy = naviCopy;
+  nav.CRLF = naviCRLF;
+  nav.editProps = autoEdit;
+  nav.stubs = (preferUnexplored ? 0 : 1);
+  dlg.AddPage(&nav);
+
+  if (dlg.DoModal() == IDOK) {
+    autoEdit = nav.editProps;
+    naviCopy = nav.copy;
+    naviCRLF = nav.CRLF;
+    preferUnexplored = (nav.stubs == 0);
+  } // end if OK pressed
+} // end CMapApp::editOptions
+#endif
+
diff --git a/src/guemapapp.h b/src/guemapapp.h
new file mode 100644 (file)
index 0000000..3f25e0d
--- /dev/null
@@ -0,0 +1,123 @@
+//
+// GUEmap
+// Copyright 1997-2007 by Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+// GUEmap.h: main header file for the GUEmap application
+//
+
+#ifndef __GUEMAPAPP_H__
+#define __GUEMAPAPP_H__
+
+#include <QtWidgets>
+
+// Forward declarations
+//class CharWindow;
+class MainWin;
+
+class GUEMapApp: public QApplication
+{
+       public:
+               GUEMapApp(int & argc, char * argv[]);
+
+       public:
+//             CharWindow * charWnd;
+               MainWin * mainWindow;
+};
+
+
+//#ifndef __AFXWIN_H__
+//     #error include 'stdafx.h' before including this file for PCH
+//#endif
+
+//#include "resource.h"       // main symbols
+
+/////////////////////////////////////////////////////////////////////////////
+// Miscellaneous functions:
+#if 0
+void copyToOneLine(String & dest, const String & source);
+void fillParagraphs(String & s);
+void trimLeft(String & s);
+void trimRight(String & s);
+
+/////////////////////////////////////////////////////////////////////////////
+// CMapApp:
+// See GUEmap.cpp for the implementation of this class
+//
+
+class CAboutDlg;
+class CCommentDlg;
+class CFindDlg;
+class CMapView;
+class MapRoom;
+class RoomScrap;
+
+extern bool GUEmapEatClicks;
+extern const char * const iniMainWin;
+extern HCURSOR handCursor;
+extern String mapWinClass;
+
+class CMapApp: public CWinApp
+{
+       protected:
+               bool autoEdit;
+               bool preferUnexplored;
+               bool initialized;
+               bool naviCopy;
+               bool naviCRLF;
+               CString username;
+               CString password;
+               RoomScrap * clipboard;
+               CCommentDlg * commentDlg;
+               CFindDlg * findDlg;
+
+       public:
+               CMapApp();
+               ~CMapApp();
+
+               void adjustCommentPos(LPCRECT r);
+               void closingView(const CMapView * view);
+               void editComment();
+               void editOptions();
+               const RoomScrap * getClipboard() const { return clipboard; };
+               void loadWindowPos(CRect & pos, LPCTSTR section);
+               bool navigationCopy() const { return naviCopy; };
+               bool navigationCRLF() const { return naviCRLF; };
+               bool editAfterAdd()   const { return autoEdit; };
+               bool stub1Unexplored()const { return preferUnexplored; };
+               void saveWindowPos(LPCRECT pos, LPCTSTR section);
+               void setClipboard(RoomScrap * newScrap);
+               void setComment(CMapView * fromView, const MapRoom * room,
+                       bool takeFocus=true) const;
+               void setLandscape();
+               void setupAbout(CAboutDlg & dlg) const;
+
+       // Overrides
+       // ClassWizard generated virtual function overrides
+       //{{AFX_VIRTUAL(CMapApp)
+       public:
+               virtual BOOL InitInstance();
+               virtual int ExitInstance();
+       //}}AFX_VIRTUAL
+
+  // Implementation
+
+       //{{AFX_MSG(CMapApp)
+               void OnAppAbout();
+               void OnEditFind();
+               void OnUpdateViewComments(CCmdUI* pCmdUI);
+               void OnViewComments();
+               void OnViewSettings();
+       //}}AFX_MSG
+
+       DECLARE_MESSAGE_MAP();
+};
+
+inline CMapApp * gueApp() { return static_cast<CMapApp *>(AfxGetApp()); }
+#endif
+
+#endif // __GUEMAP_H__
+
diff --git a/src/mainwin.cpp b/src/mainwin.cpp
new file mode 100644 (file)
index 0000000..b12ab24
--- /dev/null
@@ -0,0 +1,692 @@
+//
+// GUEmap
+// Copyright 1997-2007 by Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+// mainwin.cpp: implementation of the MainWin class
+//
+
+#include "mainwin.h"
+#include "about.h"
+#include "file.h"
+#include "globals.h"
+#include "mapdoc.h"
+#include "mapview.h"
+#include "roomwidget.h"
+#include "undo.h"
+
+
+// CMainWin
+
+#if 0
+IMPLEMENT_DYNAMIC(CMainWin, CMDIFrameWnd)
+
+BEGIN_MESSAGE_MAP(CMainWin, CMDIFrameWnd)
+       //{{AFX_MSG_MAP(CMainWin)
+       ON_WM_ACTIVATEAPP()
+       ON_WM_CREATE()
+       ON_WM_DESTROY()
+       //}}AFX_MSG_MAP
+       // Global help commands
+       ON_COMMAND(ID_HELP_FINDER, CMDIFrameWnd::OnHelpFinder)
+       ON_COMMAND(ID_HELP, CMDIFrameWnd::OnHelp)
+       ON_COMMAND(ID_CONTEXT_HELP, CMDIFrameWnd::OnContextHelp)
+       ON_COMMAND(ID_DEFAULT_HELP, CMDIFrameWnd::OnHelpFinder)
+  // Toggle the navigation bar:
+  ON_UPDATE_COMMAND_UI(ID_VIEW_NAVBAR, OnUpdateControlBarMenu)
+  ON_COMMAND_EX(ID_VIEW_NAVBAR, OnBarCheck)
+END_MESSAGE_MAP();
+
+static UINT indicators[] =
+{
+       ID_SEPARATOR,           // status line indicator
+       ID_INDICATOR_CAPS,
+       ID_INDICATOR_NUM,
+       ID_INDICATOR_SCRL,
+};
+#endif
+
+
+MainWin::MainWin(): settings("Underground Software", "GUEmap")
+{
+       setCentralWidget(&mdiArea);
+       mdiArea.setViewMode(QMdiArea::TabbedView);
+       mdiArea.setTabsClosable(true);
+
+       setWindowIcon(QIcon(":/res/guemap.ico"));
+       setWindowTitle("GUEmap");
+
+       aboutWin = new AboutWindow(this);
+
+       CreateActions();
+       CreateMenus();
+       CreateToolbars();
+
+       undoAct->setEnabled(false);
+       statusBar()->showMessage(tr("Ready"));
+
+       ReadSettings();
+
+       // Create Dock widgets
+       QDockWidget * dock1 = new QDockWidget(tr("Rooms"), this);
+       /*RoomWidget * */rw = new RoomWidget;
+       dock1->setWidget(rw);
+       addDockWidget(Qt::RightDockWidgetArea, dock1);
+//     QDockWidget * dock2 = new QDockWidget(tr("Blocks"), this);
+//     BlockWidget * bw = new BlockWidget;
+//     dock2->setWidget(bw);
+//     addDockWidget(Qt::RightDockWidgetArea, dock2);
+//     QDockWidget * dock3 = new QDockWidget(tr("Object"), this);
+//     ObjectWidget * ow = new ObjectWidget;
+//     dock3->setWidget(ow);
+//     addDockWidget(Qt::RightDockWidgetArea, dock3);
+       // Needed for saveState()
+       dock1->setObjectName("Rooms");
+//     dock2->setObjectName("Blocks");
+//     dock3->setObjectName("Object");
+
+       // Create status bar
+//     zoomIndicator = new QLabel("Grid: 12.0\" BU: Inch");
+//     statusBar()->addPermanentWidget(zoomIndicator);
+//     statusBar()->showMessage(tr("Ready"));
+
+//     ReadSettings();
+//     setUnifiedTitleAndToolBarOnMac(true);
+//     Global::font =  new QFont("Verdana", 15, QFont::Bold);
+
+//     connect(lw, SIGNAL(LayerDeleted(int)), drawing, SLOT(DeleteCurrentLayer(int)));
+//     connect(lw, SIGNAL(LayerToggled()), drawing, SLOT(HandleLayerToggle()));
+//     connect(lw, SIGNAL(LayersSwapped(int, int)), drawing, SLOT(HandleLayerSwap(int, int)));
+
+//     connect(drawing, SIGNAL(ObjectHovered(Object *)), ow, SLOT(ShowInfo(Object *)));
+
+}
+
+
+void MainWin::closeEvent(QCloseEvent * event)
+{
+       WriteSettings();
+       event->accept();                // Use ignore() if can't close for some reason
+       //Do we have a memory leak here if we don't delete the font in the Object???
+}
+
+
+void MainWin::FileNew(void)
+{
+       MapView * map = new MapView;
+       map->setWindowTitle("Untitled");
+       map->clearSelection();
+       QMdiSubWindow * sw = mdiArea.addSubWindow(map);
+       mdiArea.setActiveSubWindow(sw);
+       sw->showMaximized();
+       sw->update();
+
+       connect(map, SIGNAL(RoomClicked(MapDoc *, int)), rw, SLOT(ShowInfo(MapDoc *, int)));
+
+       statusBar()->showMessage(tr("New map created."), 2000);
+}
+
+
+void MainWin::FileOpen(void)
+{
+       QString filename = QFileDialog::getOpenFileName(this, tr("Open Map"), "",
+               tr("GUEmap files (*.gmp)"));
+
+       // User cancelled open
+       if (filename.isEmpty())
+               return;
+
+       MapView * map = new MapView;
+       bool successful = ReadFile(map->doc, filename.toUtf8().data());
+
+       if (!successful)
+       {
+               // Make sure to delete any hanging objects in the container...
+               delete map;
+
+               QMessageBox msg;
+               msg.setText(QString(tr("Could not load file \"%1\"!")).arg(filename));
+               msg.setIcon(QMessageBox::Critical);
+               msg.exec();
+               return;
+       }
+
+       map->doc->filename = filename.toStdString();
+       map->setWindowTitle(map->doc->name.c_str());
+       map->clearSelection();
+       QMdiSubWindow * sw = mdiArea.addSubWindow(map);
+       mdiArea.setActiveSubWindow(sw);
+       sw->showMaximized();
+       sw->update();
+
+       connect(map, SIGNAL(RoomClicked(MapDoc *, int)), rw, SLOT(ShowInfo(MapDoc *, int)));
+
+       statusBar()->showMessage(tr("Map loaded."), 2000);
+}
+
+
+void MainWin::FileSaveBase(MapView * map, QString filename)
+{
+       if (filename.endsWith(".gmp") == false)
+               filename += ".gmp";
+
+       bool successful = WriteFile(map->doc, filename.toUtf8().data());
+
+       if (!successful)
+       {
+               // In this case, we should unlink the created file, since it's not
+               // right...
+               QFile::remove(filename);
+
+               QMessageBox msg;
+               msg.setText(QString(tr("Could not save file \"%1\"!")).arg(filename));
+               msg.setIcon(QMessageBox::Critical);
+               msg.exec();
+               return;
+       }
+
+       map->doc->filename = filename.toStdString();
+       map->doc->isDirty = false;
+       statusBar()->showMessage(tr("Map saved."), 2000);
+}
+
+
+void MainWin::FileSave(void)
+{
+       QMdiSubWindow * sw = mdiArea.activeSubWindow();
+       MapView * map = (MapView *)(sw->widget());
+       QString filename = map->doc->filename.c_str();
+
+       if (filename.isEmpty())
+       {
+               filename = QFileDialog::getSaveFileName(this, tr("Save Map"), "",
+                       tr("GUEmap maps (*.gmp)"));
+
+               // Bail if the user clicked "Cancel"
+               if (filename.isEmpty())
+                       return;
+       }
+
+       FileSaveBase(map, filename);
+}
+
+
+void MainWin::FileSaveAs(void)
+{
+       QMdiSubWindow * sw = mdiArea.activeSubWindow();
+       MapView * map = (MapView *)(sw->widget());
+//     QString filename = map->doc->filename.c_str();
+
+       QString filename = QFileDialog::getSaveFileName(this, tr("Save Map As"),
+               map->doc->filename.c_str(), tr("GUEmap maps (*.gmp)"));
+
+       // Bail if the user clicked "Cancel"
+       if (filename.isEmpty())
+               return;
+
+       FileSaveBase(map, filename);
+}
+
+
+void MainWin::FileClose(void)
+{
+       QMdiSubWindow * sw = mdiArea.activeSubWindow();
+       MapView * map = (MapView *)(sw->widget());
+
+       if (map->doc->isDirty)
+       {
+               QMessageBox msg;
+               msg.setText(tr("The map has been modified."));
+               msg.setInformativeText(tr("Do you want to save your changes?"));
+               msg.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
+               msg.setDefaultButton(QMessageBox::Save);
+               msg.setIcon(QMessageBox::Warning);
+
+               int response = msg.exec();
+
+               if (response == QMessageBox::Save)
+                       FileSave();
+       }
+
+       sw->close();
+       statusBar()->showMessage(tr("Map closed."), 2000);
+}
+
+
+void MainWin::EditUndo(void)
+{
+       QMdiSubWindow * sw = mdiArea.activeSubWindow();
+
+       if (sw == 0)
+               return;
+
+       MapView * map = (MapView *)(sw->widget());
+
+       if (map == 0)
+               return;
+
+       if (map->doc->undoData != 0)
+       {
+               map->doc->setUndoData(map->doc->undoData->undo(*(map->doc)));
+               statusBar()->showMessage(tr("Undo successful."));
+               map->update();
+               return;
+       }
+
+       statusBar()->showMessage(tr("Ready."));
+}
+
+
+void MainWin::MenuFixUndo(void)
+{
+       QMdiSubWindow * sw = mdiArea.activeSubWindow();
+
+       if (sw == 0)
+               return;
+
+       MapView * map = (MapView *)(sw->widget());
+       UndoRec * undo = map->doc->undoData;
+
+       if (undo)
+       {
+               undoAct->setText(QString(tr("&Undo %1")).arg(undo->getName()));
+               undoAct->setEnabled(true);
+       }
+       else
+       {
+               undoAct->setText(tr("Can't Undo"));
+               undoAct->setEnabled(false);
+       }
+}
+
+
+void MainWin::EditDelete(void)
+{
+       QMdiSubWindow * sw = mdiArea.activeSubWindow();
+
+       if (sw == 0)
+               return;
+
+       MapView * map = (MapView *)(sw->widget());
+
+       if (map == 0)
+               return;
+
+       map->deleteSelection();
+       map->update();
+}
+
+
+void MainWin::EditSelectAll(void)
+{
+       QMdiSubWindow * sw = mdiArea.activeSubWindow();
+
+       if (sw == 0)
+               return;
+
+       MapView * map = (MapView *)(sw->widget());
+
+       if (map == 0)
+               return;
+
+       for(int i=map->doc->room.size()-1; i>=0; i--)
+               map->selectRoom(i);
+
+       map->update();
+}
+
+
+void MainWin::HelpAbout(void)
+{
+       aboutWin->show();
+}
+
+
+void MainWin::CreateActions(void)
+{
+       fileNewAct = CreateAction(tr("&New Map"), tr("New Map"), tr("Creates a new map."), QIcon(":/res/file-new.png"), QKeySequence(tr("Ctrl+n")));
+       connect(fileNewAct, SIGNAL(triggered()), this, SLOT(FileNew()));
+
+       fileOpenAct = CreateAction(tr("&Open Map"), tr("Open Map"), tr("Opens an existing map from a file."), QIcon(":/res/file-open.png"), QKeySequence(tr("Ctrl+o")));
+       connect(fileOpenAct, SIGNAL(triggered()), this, SLOT(FileOpen()));
+
+       fileSaveAct = CreateAction(tr("&Save Map"), tr("Save Map"), tr("Saves the current map to a file."), QIcon(":/res/file-save.png"), QKeySequence(tr("Ctrl+s")));
+       connect(fileSaveAct, SIGNAL(triggered()), this, SLOT(FileSave()));
+
+       fileSaveAsAct = CreateAction(tr("Save Map &As"), tr("Save As"), tr("Saves the current map to a file with a different name."), QIcon(":/res/file-save-as.png"), QKeySequence(tr("Ctrl+Shift+s")));
+       connect(fileSaveAsAct, SIGNAL(triggered()), this, SLOT(FileSaveAs()));
+
+       fileCloseAct = CreateAction(tr("&Close Map"), tr("Close Map"), tr("Closes the current map."), QIcon(":/res/file-close.png"), QKeySequence(tr("Ctrl+c")));
+       connect(fileCloseAct, SIGNAL(triggered()), this, SLOT(FileClose()));
+
+       exitAct = CreateAction(tr("&Quit"), tr("Quit"), tr("Exits the application."), QIcon(":/res/quit.png"), QKeySequence(tr("Ctrl+q")));
+       connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));
+
+       undoAct = CreateAction(tr("&Undo"), tr("Undo"), tr("Undoes the last action."), QIcon(), QKeySequence(tr("Ctrl+z")));
+       connect(undoAct, SIGNAL(triggered()), this, SLOT(EditUndo()));
+
+       deleteAct = CreateAction(tr("&Delete"), tr("Delete"), tr("Deletes the selected items."), QIcon(), QKeySequence(tr("Delete")));
+       connect(deleteAct, SIGNAL(triggered()), this, SLOT(EditDelete()));
+
+       selectAllAct = CreateAction(tr("Select &All"), tr("Select All"), tr("Selects all items."), QIcon(), QKeySequence(tr("Ctrl+a")));
+       connect(selectAllAct, SIGNAL(triggered()), this, SLOT(EditSelectAll()));
+
+       aboutAct = CreateAction(tr("About &GUEmap"), tr("About GUEmap"), tr("Gives information about this program."), QIcon(), QKeySequence());
+       connect(aboutAct, SIGNAL(triggered()), this, SLOT(HelpAbout()));
+
+#if 0
+       snapToGridAct = CreateAction(tr("Snap To &Grid"), tr("Snap To Grid"), tr("Snaps mouse cursor to the visible grid when moving/creating objects."), QIcon(":/res/snap-to-grid-tool.png"), QKeySequence(tr("S")), true);
+       connect(snapToGridAct, SIGNAL(triggered()), this, SLOT(SnapToGridTool()));
+
+       fixAngleAct = CreateAction(tr("Fix &Angle"), tr("Fix Angle"), tr("Fixes the angle of an object."),
+               QIcon(":/res/fix-angle.png"), QKeySequence(tr("F,A")), true);
+       connect(fixAngleAct, SIGNAL(triggered()), this, SLOT(FixAngle()));
+
+       fixLengthAct = CreateAction(tr("Fix &Length"), tr("Fix Length"), tr("Fixes the length of an object."),
+               QIcon(":/res/fix-length.png"), QKeySequence(tr("F,L")), true);
+       connect(fixLengthAct, SIGNAL(triggered()), this, SLOT(FixLength()));
+
+       deleteAct = CreateAction(tr("&Delete"), tr("Delete Object"), tr("Deletes selected objects."), QIcon(":/res/delete-tool.png"), QKeySequence(tr("Delete")), true);
+       connect(deleteAct, SIGNAL(triggered()), this, SLOT(DeleteTool()));
+
+       addDimensionAct = CreateAction(tr("Add &Dimension"), tr("Add Dimension"), tr("Adds a dimension to the drawing."), QIcon(":/res/dimension-tool.png"), QKeySequence("D,I"), true);
+       connect(addDimensionAct, SIGNAL(triggered()), this, SLOT(DimensionTool()));
+
+       addLineAct = CreateAction(tr("Add &Line"), tr("Add Line"), tr("Adds lines to the drawing."), QIcon(":/res/add-line-tool.png"), QKeySequence("A,L"), true);
+       connect(addLineAct, SIGNAL(triggered()), this, SLOT(AddLineTool()));
+
+       addCircleAct = CreateAction(tr("Add &Circle"), tr("Add Circle"), tr("Adds circles to the drawing."), QIcon(":/res/add-circle-tool.png"), QKeySequence("A,C"), true);
+       connect(addCircleAct, SIGNAL(triggered()), this, SLOT(AddCircleTool()));
+
+       addArcAct = CreateAction(tr("Add &Arc"), tr("Add Arc"), tr("Adds arcs to the drawing."), QIcon(":/res/add-arc-tool.png"), QKeySequence("A,A"), true);
+       connect(addArcAct, SIGNAL(triggered()), this, SLOT(AddArcTool()));
+
+       addPolygonAct = CreateAction(tr("Add &Polygon"), tr("Add Polygon"), tr("Add polygons to the drawing."), QIcon(":/res/add-polygon-tool.png"), QKeySequence("A,P"), true);
+       connect(addPolygonAct, SIGNAL(triggered()), this, SLOT(AddPolygonTool()));
+
+       addSplineAct = CreateAction(tr("Add &Spline"), tr("Add Spline"), tr("Add a NURB spline to the drawing."), QIcon(":/res/add-spline-tool.png"), QKeySequence("A,S"), true);
+       connect(addSplineAct, SIGNAL(triggered()), this, SLOT(AddSplineTool()));
+
+       rotateAct = CreateAction(tr("&Rotate Objects"), tr("Rotate"), tr("Rotate object(s) around an arbitrary center."), QIcon(":/res/rotate-tool.png"), QKeySequence(tr("R,O")), true);
+       connect(rotateAct, SIGNAL(triggered()), this, SLOT(RotateTool()));
+
+       zoomInAct = CreateAction(tr("Zoom &In"), tr("Zoom In"), tr("Zoom in on the document."), QIcon(":/res/zoom-in.png"), QKeySequence(tr("+")), QKeySequence(tr("=")));
+       connect(zoomInAct, SIGNAL(triggered()), this, SLOT(ZoomInTool()));
+
+       zoomOutAct = CreateAction(tr("Zoom &Out"), tr("Zoom Out"), tr("Zoom out of the document."), QIcon(":/res/zoom-out.png"), QKeySequence(tr("-")));
+       connect(zoomOutAct, SIGNAL(triggered()), this, SLOT(ZoomOutTool()));
+
+       settingsAct = CreateAction(tr("&Settings"), tr("Settings"), tr("Change certain defaults for Architektonas."), QIcon(":/res/settings.png"), QKeySequence());
+       connect(settingsAct, SIGNAL(triggered()), this, SLOT(Settings()));
+
+       groupAct = CreateAction(tr("&Group"), tr("Group"), tr("Group/ungroup selected objects."), QIcon(":/res/group-tool.png"), QKeySequence("g"));
+       connect(groupAct, SIGNAL(triggered()), this, SLOT(HandleGrouping()));
+
+       connectAct = CreateAction(tr("&Connect"), tr("Connect"), tr("Connect objects at point."), QIcon(":/res/connect-tool.png"), QKeySequence("c,c"));
+       connect(connectAct, SIGNAL(triggered()), this, SLOT(HandleConnection()));
+
+       disconnectAct = CreateAction(tr("&Disconnect"), tr("Disconnect"), tr("Disconnect objects joined at point."), QIcon(":/res/disconnect-tool.png"), QKeySequence("d,d"));
+       connect(disconnectAct, SIGNAL(triggered()), this, SLOT(HandleDisconnection()));
+
+       mirrorAct = CreateAction(tr("&Mirror"), tr("Mirror"), tr("Mirror selected objects around a line."), QIcon(":/res/mirror-tool.png"), QKeySequence("m,i"), true);
+       connect(mirrorAct, SIGNAL(triggered()), this, SLOT(MirrorTool()));
+
+       trimAct = CreateAction(tr("&Trim"), tr("Trim"), tr("Trim extraneous lines from selected objects."), QIcon(":/res/trim-tool.png"), QKeySequence("t,r"), true);
+       connect(trimAct, SIGNAL(triggered()), this, SLOT(TrimTool()));
+
+       triangulateAct = CreateAction(tr("&Triangulate"), tr("Triangulate"), tr("Make triangles from selected lines, preserving their lengths."), QIcon(":/res/triangulate-tool.png"), QKeySequence("t,g"), true);
+       connect(triangulateAct, SIGNAL(triggered()), this, SLOT(TriangulateTool()));
+#endif
+}
+
+
+void MainWin::CreateMenus(void)
+{
+       QMenu * menu = menuBar()->addMenu(tr("&File"));
+       menu->addAction(fileNewAct);
+       menu->addAction(fileOpenAct);
+       menu->addAction(fileSaveAct);
+       menu->addAction(fileSaveAsAct);
+       menu->addAction(fileCloseAct);
+       menu->addSeparator();
+       menu->addAction(exitAct);
+
+       menu = menuBar()->addMenu(tr("&Edit"));
+       menu->addAction(undoAct);
+       connect(menu, SIGNAL(aboutToShow()), this, SLOT(MenuFixUndo()));
+       menu->addSeparator();
+       menu->addAction(deleteAct);
+       menu->addAction(selectAllAct);
+#if 0
+       menu = menuBar()->addMenu(tr("&View"));
+       menu->addAction(zoomInAct);
+       menu->addAction(zoomOutAct);
+
+       // EDIT
+       menu->addAction(snapToGridAct);
+       menu->addAction(groupAct);
+       menu->addAction(fixAngleAct);
+       menu->addAction(fixLengthAct);
+       menu->addAction(rotateAct);
+       menu->addAction(mirrorAct);
+       menu->addAction(trimAct);
+       menu->addAction(triangulateAct);
+       menu->addAction(connectAct);
+       menu->addAction(disconnectAct);
+       menu->addSeparator();
+       menu->addAction(addLineAct);
+       menu->addAction(addCircleAct);
+       menu->addAction(addArcAct);
+       menu->addAction(addPolygonAct);
+       menu->addAction(addSplineAct);
+       menu->addAction(addDimensionAct);
+       menu->addSeparator();
+       menu->addAction(settingsAct);
+#endif
+
+       menu = menuBar()->addMenu(tr("&Help"));
+       menu->addAction(aboutAct);
+}
+
+
+void MainWin::CreateToolbars(void)
+{
+#if 0
+       QToolBar * toolbar = addToolBar(tr("File"));
+       toolbar->setObjectName("File"); // Needed for saveState()
+       toolbar->addAction(fileNewAct);
+       toolbar->addAction(fileOpenAct);
+       toolbar->addAction(fileSaveAct);
+       toolbar->addAction(fileSaveAsAct);
+       toolbar->addAction(fileCloseAct);
+//     toolbar->addAction(exitAct);
+
+       toolbar = addToolBar(tr("View"));
+       toolbar->setObjectName("View");
+       toolbar->addAction(zoomInAct);
+       toolbar->addAction(zoomOutAct);
+
+       QSpinBox * spinbox = new QSpinBox;
+       toolbar->addWidget(spinbox);
+//     QLineEdit * lineedit = new QLineEdit;
+       toolbar->addWidget(baseUnitInput);
+       toolbar->addWidget(dimensionSizeInput);
+
+       toolbar = addToolBar(tr("Edit"));
+       toolbar->setObjectName("Edit");
+       toolbar->addAction(snapToGridAct);
+       toolbar->addAction(groupAct);
+       toolbar->addAction(fixAngleAct);
+       toolbar->addAction(fixLengthAct);
+       toolbar->addAction(rotateAct);
+       toolbar->addAction(mirrorAct);
+       toolbar->addAction(trimAct);
+       toolbar->addAction(triangulateAct);
+       toolbar->addAction(deleteAct);
+       toolbar->addAction(connectAct);
+       toolbar->addAction(disconnectAct);
+       toolbar->addSeparator();
+       toolbar->addAction(addLineAct);
+       toolbar->addAction(addCircleAct);
+       toolbar->addAction(addArcAct);
+       toolbar->addAction(addPolygonAct);
+       toolbar->addAction(addSplineAct);
+       toolbar->addAction(addDimensionAct);
+
+       spinbox->setRange(4, 256);
+       spinbox->setValue(12);
+       baseUnitInput->setText("12");
+       connect(spinbox, SIGNAL(valueChanged(int)), this, SLOT(HandleGridSizeInPixels(int)));
+       connect(baseUnitInput, SIGNAL(textChanged(QString)), this, SLOT(HandleGridSizeInBaseUnits(QString)));
+       connect(dimensionSizeInput, SIGNAL(textChanged(QString)), this, SLOT(HandleDimensionSize(QString)));
+
+       PenWidget * pw = new PenWidget();
+       toolbar = addToolBar(tr("Pen"));
+       toolbar->setObjectName(tr("Pen"));
+       toolbar->addWidget(pw);
+       connect(drawing, SIGNAL(ObjectSelected(Object *)), pw, SLOT(SetFields(Object *)));
+       connect(pw, SIGNAL(WidthSelected(float)), drawing, SLOT(HandlePenWidth(float)));
+       connect(pw, SIGNAL(StyleSelected(int)), drawing, SLOT(HandlePenStyle(int)));
+       connect(pw, SIGNAL(ColorSelected(uint32_t)), drawing, SLOT(HandlePenColor(uint32_t)));
+#endif
+}
+
+
+void MainWin::ReadSettings(void)
+{
+       QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
+       QSize size = settings.value("size", QSize(400, 400)).toSize();
+//     drawing->useAntialiasing = settings.value("useAntialiasing", true).toBool();
+//     snapToGridAct->setChecked(settings.value("snapToGrid", true).toBool());
+       resize(size);
+       move(pos);
+       restoreState(settings.value("windowState").toByteArray());
+}
+
+
+void MainWin::WriteSettings(void)
+{
+       settings.setValue("pos", pos());
+       settings.setValue("size", size());
+       settings.setValue("windowState", saveState());
+//     settings.setValue("useAntialiasing", drawing->useAntialiasing);
+//     settings.setValue("snapToGrid", snapToGridAct->isChecked());
+}
+
+
+#if 0
+int MainWin::OnCreate(LPCREATESTRUCT lpCreateStruct)
+{
+       if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
+               return -1;
+
+       if (!m_wndToolBar.Create(this) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
+       {
+               TRACE0("Failed to create toolbar\n");
+               return -1;      // fail to create
+       }
+
+       if (!wndNavBar.Create(this, IDD_NAVBAR, CBRS_TOP, ID_VIEW_NAVBAR))
+       {
+               TRACE0("Failed to create navigation bar\n");
+               return -1;      // fail to create
+       }
+
+       if (!m_wndStatusBar.Create(this)
+               || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators) / sizeof(UINT)))
+       {
+               TRACE0("Failed to create status bar\n");
+               return -1;      // fail to create
+       }
+
+
+       // Add tool tips and resizeable toolbar:
+       m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
+       // Make toolbar dockable:
+       m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
+       EnableDocking(CBRS_ALIGN_ANY);
+       DockControlBar(&m_wndToolBar);
+
+       wndNavBar.SetBarStyle(wndNavBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY);
+       wndNavBar.EnableDocking(CBRS_ALIGN_TOP | CBRS_ALIGN_BOTTOM);
+       DockControlBarLeftOf(&wndNavBar, &m_wndToolBar);
+
+       // Restore toolbar state:
+       CDockState state;
+       state.LoadState("Toolbars");
+       SetDockState(state);
+
+       return 0;
+}
+
+
+void MainWin::OnDestroy()
+{
+       CDockState  state;
+
+       GetDockState(state);
+       state.SaveState("Toolbars");
+
+       WINDOWPLACEMENT pos;
+       GetWindowPlacement(&pos);
+       static_cast<CMapApp *>(AfxGetApp())->saveWindowPos(&pos.rcNormalPosition, iniMainWin);
+
+       CMDIFrameWnd::OnDestroy();
+}
+
+
+bool MainWin::PreCreateWindow(CREATESTRUCT & cs)
+{
+       // TODO: Modify the Window class or styles here by modifying
+       //  the CREATESTRUCT cs
+
+       return CMDIFrameWnd::PreCreateWindow(cs);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// CMainWin message handlers
+//--------------------------------------------------------------------
+// Keep track of whether we should be eating mouse clicks:
+
+void MainWin::OnActivateApp(bool bActive, HTASK task)
+{
+       GUEmapEatClicks = !bActive;
+       CMDIFrameWnd::OnActivateApp(bActive, task);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Dock control bars on the same line:
+//
+// Taken from \MSDev\Samples\MFC\General\DockTool\MainFrm.cpp
+
+void MainWin::DockControlBarLeftOf(CControlBar * Bar, CControlBar * LeftOf)
+{
+       CRect rect;
+       DWORD dw;
+       UINT n;
+
+       // get MFC to adjust the dimensions of all docked ToolBars
+       // so that GetWindowRect will be accurate
+       RecalcLayout();
+       LeftOf->GetWindowRect(&rect);
+       rect.OffsetRect(1, 0);
+       dw = LeftOf->GetBarStyle();
+
+       n = 0;
+       n = (dw & CBRS_ALIGN_TOP ? AFX_IDW_DOCKBAR_TOP : n);
+       n = (dw & CBRS_ALIGN_BOTTOM && n == 0 ? AFX_IDW_DOCKBAR_BOTTOM : n);
+       n = (dw & CBRS_ALIGN_LEFT && n == 0 ? AFX_IDW_DOCKBAR_LEFT : n);
+       n = (dw & CBRS_ALIGN_RIGHT && n == 0 ? AFX_IDW_DOCKBAR_RIGHT : n);
+
+       // When we take the default parameters on rect, DockControlBar will dock
+       // each Toolbar on a seperate line.  By calculating a rectangle, we in effect
+       // are simulating a Toolbar being dragged to that location and docked.
+       DockControlBar(Bar, n, &rect);
+}
+#endif
+
diff --git a/src/mainwin.h b/src/mainwin.h
new file mode 100644 (file)
index 0000000..f2b05df
--- /dev/null
@@ -0,0 +1,103 @@
+//
+// GUEmap
+// Copyright 1997-2007 by Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+// MainFrm.h: interface of the MainFrame class
+//
+
+#ifndef __MAINWIN_H__
+#define __MAINWIN_H__
+
+#include <QtWidgets>
+
+class AboutWindow;
+class MapView;
+class RoomWidget;
+
+//wants to be an MDI window... (And now is!!!)
+class MainWin: public QMainWindow
+{
+       // All Qt apps require this macro
+       Q_OBJECT
+
+       public:
+               MainWin();
+
+       protected:
+               void closeEvent(QCloseEvent * event);
+
+       protected slots:
+               void FileNew(void);
+               void FileOpen(void);
+               void FileSaveBase(MapView *, QString);
+               void FileSave(void);
+               void FileSaveAs(void);
+               void FileClose(void);
+               void EditUndo(void);
+               void MenuFixUndo(void);
+               void EditDelete(void);
+               void EditSelectAll(void);
+               void HelpAbout(void);
+
+       private:
+               void CreateActions(void);
+               void CreateMenus(void);
+               void CreateToolbars(void);
+               void ReadSettings(void);
+               void WriteSettings(void);
+
+       public:
+//             CDialogBar wndNavBar;
+               QMdiArea mdiArea;
+               QSettings settings;
+               AboutWindow * aboutWin;
+               RoomWidget * rw;
+
+       private:
+               QAction * fileNewAct;
+               QAction * fileOpenAct;
+               QAction * fileSaveAct;
+               QAction * fileSaveAsAct;
+               QAction * fileCloseAct;
+               QAction * exitAct;
+               QAction * undoAct;
+               QAction * deleteAct;
+               QAction * selectAllAct;
+               QAction * aboutAct;
+//             QAction * Act;
+#if 0
+       public:
+//             void setStatusBar(LPCTSTR text) {m_wndStatusBar.SetWindowText(text);};
+
+       // Overrides
+       // ClassWizard generated virtual function overrides
+       //{{AFX_VIRTUAL(CMainFrame)
+//             virtual BOOL PreCreateWindow(CREATESTRUCT & cs);
+       //}}AFX_VIRTUAL
+
+       // Implementation
+//     public:
+//             ~MainFrame();
+
+//             void DockControlBarLeftOf(CControlBar * Bar,CControlBar * LeftOf);
+
+       protected:  // control bar embedded members
+//             CStatusBar  m_wndStatusBar;
+//             CToolBar    m_wndToolBar;
+
+       // Generated message map functions
+       protected:
+//             int OnCreate(LPCREATESTRUCT lpCreateStruct);
+//             void OnActivateApp(BOOL bActive, HTASK hTask);
+//             void OnDestroy();
+
+//     DECLARE_MESSAGE_MAP();
+#endif
+};
+
+#endif // __MAINWIN_H__
+
diff --git a/src/mapdoc.cpp b/src/mapdoc.cpp
new file mode 100644 (file)
index 0000000..e01046c
--- /dev/null
@@ -0,0 +1,1364 @@
+//
+// GUEmap
+// (C) 1997-2007 Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+// Implementation of the MapDoc class
+//
+
+#include "mapdoc.h"
+#include "file.h"
+#include "undo.h"
+
+
+const RoomCorner oppositeCorner[rcNumCorners] = {
+       rcS, rcN, rcW, rcE,
+       rcSW, rcSE, rcNW, rcNE,
+       rcSSW, rcSSE, rcNNW, rcNNE
+};
+
+
+#if 0
+/////////////////////////////////////////////////////////////////////////////
+// MapDoc
+
+IMPLEMENT_DYNCREATE(MapDoc, CDocument)
+
+BEGIN_MESSAGE_MAP(MapDoc, CDocument)
+       //{{AFX_MSG_MAP(MapDoc)
+       ON_COMMAND(ID_EDIT_NAVIGATE, OnNavigationMode)
+       ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
+       ON_COMMAND(ID_FILE_EXPORT, OnFileExport)
+       ON_UPDATE_COMMAND_UI(ID_EDIT_NAVIGATE, OnUpdateNavigationMode)
+       ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
+       //}}AFX_MSG_MAP
+  // Send Mail command:
+  ON_COMMAND(ID_FILE_SEND_MAIL, OnFileSendMail)
+  ON_UPDATE_COMMAND_UI(ID_FILE_SEND_MAIL, OnUpdateFileSendMail)
+  // My commands
+  ON_COMMAND_RANGE(ID_EDIT_MOVE_BOTTOM, ID_EDIT_MOVE_TOP, OnEditMoveMap)
+  ON_UPDATE_COMMAND_UI_RANGE(ID_EDIT_MOVE_BOTTOM, ID_EDIT_MOVE_TOP,
+                             OnUpdateEditMoveMap)
+END_MESSAGE_MAP();
+#endif
+
+
+/////////////////////////////////////////////////////////////////////////////
+// MapDoc construction/destruction
+
+MapDoc::MapDoc(): locked(false), pageSize(42 * gridX, 53 * gridY), undoData(NULL)
+{
+       // Initialize the document...  :-P
+       DeleteContents();
+}
+
+
+MapDoc::~MapDoc()
+{
+       if (undoData)
+               delete undoData;
+}
+
+
+#if 0
+bool MapDoc::OnNewDocument()
+{
+       if (!CDocument::OnNewDocument())
+               return FALSE;
+
+       // TODO: add reinitialization code here
+       // (SDI documents will reuse this document)
+       DeleteContents();
+
+       return true;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// MapDoc serialization
+//--------------------------------------------------------------------
+// Make a backup file when we Save As:
+
+bool MapDoc::DoSave(const char * pathName, bool replace)
+{
+       const bool neededBackup = needBackup;
+
+       if (!pathName)
+               needBackup = true; // Saving to new name
+
+       const bool  result = CDocument::DoSave(pathName, replace);
+
+       if (!pathName && (!result || !replace))
+               needBackup = neededBackup;  // Did not save (or saved a copy)
+
+       return result;
+}
+
+
+//
+// Allocate a CFile object for loading/saving a file:
+//
+// Rename the existing file if this is the first save after a load.
+//
+CFile * MapDoc::GetFile(const char * fileName, UINT openFlags, CFileException * error)
+{
+       if (needBackup && (openFlags & (CFile::modeReadWrite | CFile::modeWrite)))
+       {
+               String  backupFileName(fileName);
+
+               // Change the extension to .GM~:
+               //   Look for a period after the last backslash (a period before
+               //   the last backslash would be part of a directory name).
+               StrIdx  dot = backupFileName.find_last_of(_T('.'));
+
+               if ((dot == String::npos) || (dot < backupFileName.find_last_of(_T('\\'))))
+                       dot = backupFileName.size(); // No extension
+
+               backupFileName.replace(dot, String::npos, _T(".gm~"));
+
+               if (_tcsicmp(fileName, backupFileName.c_str()) == 0)
+                       needBackup = false;       // This file already has a .GM~ extension
+               else
+               {
+                       ::DeleteFile(backupFileName.c_str());
+
+                       if (::MoveFile(fileName, backupFileName.c_str()))
+                               needBackup = false;      // We made a backup file
+               }
+       }
+
+       // The rest is copied from CDocument::GetFile:
+       CMirrorFile * file = new CMirrorFile;
+       ASSERT(file != NULL);
+
+       if (!file->Open(fileName, openFlags, error))
+       {
+               delete file;
+               file = NULL;
+       }
+
+       return file;
+}
+
+
+//
+// Report errors:
+//
+void MapDoc::ReportSaveLoadException(const char * lpszPathName, CException * e, bool bSaving, UINT nIDPDefault)
+{
+       if (e && e->IsKindOf(RUNTIME_CLASS(CArchiveException)))
+       {
+               UINT msg = 0;
+
+               switch (static_cast<CArchiveException *>(e)->m_cause)
+               {
+               case CArchiveException::badClass:  msg = IDS_AR_NOT_GUEMAP;  break;
+               case CArchiveException::badSchema: msg = IDS_AR_BAD_VERSION; break;
+
+               case CArchiveException::badIndex:
+               case CArchiveException::endOfFile:
+                       msg = IDS_AR_CORRUPT;
+                       break;
+               }
+
+               if (msg)
+               {
+                       AfxMessageBox(msg, MB_ICONEXCLAMATION);
+                       return;
+               }
+       }
+
+       CDocument::ReportSaveLoadException(lpszPathName, e, bSaving, nIDPDefault);
+}
+#endif
+
+
+//
+// My Functions:
+//--------------------------------------------------------------------
+// Add a corner to an edge
+//
+// Input:
+//   rNum: The room to add a corner by
+//   eNum: The edge to add a corner in
+//
+// Returns:
+//   The number of the newly created room
+//   -1 if we couldn't add a corner
+//
+int MapDoc::addCorner(RoomNum rNum, int eNum)
+{
+       const QPoint cornerOffset[] = {
+               { gridX, -gridY }, { gridX, roomHeight },                       // N, S
+               { roomWidth, gridY }, { -gridX, gridY },                        // E, W
+               { roomWidth, -gridY }, { -gridX, -gridY },                      // NE, NW
+               { roomWidth, roomHeight }, { -gridX, roomHeight },      // SE, SW
+               { 2 * gridX, -gridY }, { 0, -gridY },                           // NNE, NNW
+               { 2 * gridX, roomHeight }, { 0, roomHeight }            // SSE, SSW
+       };
+
+       if (room.size() == maxRooms)
+               return -1;
+
+       MapEdge e1(edge[eNum]);
+       MapEdge e2(e1);
+
+       ASSERT(!(e1.type1 & etNoRoom2));
+
+       RoomCorner corner = (e1.room1 == rNum ? e1.end1 : e1.end2);
+
+       e1.type1 &= ~etOneWay;               // Clear one-way
+       e1.type2 = etNormal;
+       e2.type1 &= etObstructed | etOneWay; // Clear all but obstructed & one-way
+       e1.end2 = e2.end1 = rcCorner;
+       e1.room2 = e2.room1 = room.size();
+
+       QPoint pos = room[rNum].pos + cornerOffset[corner];
+
+       if (pos.x() < 0 || pos.x() + gridX > docSize.width() || pos.y() < 0 || pos.y() + gridY > docSize.height())
+               return -1;
+
+       MapRoom * newRoom = new MapRoom();
+       newRoom->flags = rfCorner;
+       newRoom->pos = pos;
+//     newRoom->computeNameLength();
+
+       setUndoData(new UndoChanges(isDirty, e1.room2, 2, edge[eNum]));
+       deleteEdge(eNum);
+       addRoom(e1.room2, newRoom);
+       addEdge(e1);
+       addEdge(e2);
+
+       return e1.room2;
+}
+
+
+//
+// Remove a corner from a connection:
+//
+// Input:
+//   r:  The room number of the corner to be removed
+//
+void MapDoc::deleteCorner(RoomNum r)
+{
+       ASSERT(r < room.size() && room[r].isCorner());
+
+       MapEdge edges[2];
+       EdgeVec delEdges;
+       int count = 0;
+
+       memset(edges, 0, sizeof(edges));
+
+       delEdges.reserve(2);
+
+       for(EdgeConstItr e=edge.begin(); (count<2) && (e!=edge.end()); ++e)
+       {
+               if (e->room1 == r)
+               {
+                       edges[count].room1 = e->room2;
+                       edges[count].end1  = e->end2;
+                       edges[count].type1  |= e->type2;
+                       edges[!count].type1 |= e->type1 & etSpecial;
+                       delEdges.push_back(*e);
+                       ++count;
+               }
+               else if (e->room2 == r)
+               {
+                       edges[count].room1 = e->room1;
+                       edges[count].end1  = e->end1;
+                       edges[count].type1|= e->type1;
+                       delEdges.push_back(*e);
+                       ++count;
+               }
+       }
+
+       if (count < 2)
+       {
+//             MessageBeep(MB_ICONASTERISK); // Corner without 2 edges
+               deleteRoom(r);
+               setUndoData(NULL);
+               return;
+       }
+
+       int dest = 0;
+       int source = 1;
+
+       if (edges[1].type1 & etSpecial)
+       {
+               dest = 1;
+               source = 0;
+       }
+
+       edges[dest].end2  = edges[source].end1;
+       edges[dest].room2 = edges[source].room1;
+       edges[dest].type2 = edges[source].type1 & ~etSpecial;
+
+       if (edges[dest].room1 > r)
+               --edges[dest].room1;
+
+       if (edges[dest].room2 > r)
+               --edges[dest].room2;
+
+  const bool isMod = isDirty;
+  setUndoData(new UndoChanges(isMod, r, extractRoom(r), delEdges));
+  addEdge(edges[dest]);
+}
+
+
+void MapDoc::addEdge(const MapEdge & newEdge)
+{
+       edge.push_back(newEdge);
+       isDirty = true;
+//     QRect rect;
+//     getEdgeRect(newEdge, rect);
+//     UpdateAllViews(NULL, 0, reinterpret_cast<CObject *>(&rect));
+}
+
+
+void MapDoc::addEdges(int n)
+{
+       edge.reserve(edge.size() + n);
+}
+
+
+void MapDoc::deleteEdge(int n)
+{
+       ASSERT(n < edge.size());
+
+       isDirty = true;
+       QRect rect;
+       getEdgeRect(edge[n], rect);
+
+       edge.erase(edge.begin() + n);
+
+//     UpdateAllViews(NULL, 0, reinterpret_cast<CObject *>(&rect));
+}
+
+
+//
+// Find the edge (if any) connected to a given corner:
+//
+// Input:
+//   n:      The room number to look for
+//   corner: The corner of room N we want
+//
+// Output:
+//   newEdge.room1: The room at the other end of this edge
+//   newEdge.end1:  The corner of the other room
+//   newEdge.type1: The type of connection at the other end
+//   newEdge.type2: The type of connection at this end etObstructed is moved
+//                  from type2 to type1 if necessary
+//
+// Returns:
+//   The edge number
+//   -1 if no edge is attached to the specified corner
+//
+int MapDoc::findEdge(RoomNum n, RoomCorner corner, MapEdge & newEdge) const
+{
+       EdgeConstItr e = edge.begin();
+
+       if (corner < rcNumCorners)
+       {
+               for(int i=edge.size()-1; i>=0; i--)
+               {
+                       if ((e[i].room1 == n) && (e[i].end1 == corner))
+                       {
+                               newEdge = e[i].Swapped();
+                               return i;
+                       }
+                       else if ((e[i].room2 == n) && (e[i].end2 == corner)
+                               && !(e[i].type1 & etNoRoom2))
+                       {
+                               newEdge = e[i];
+                               return i;
+                       }
+               }
+       }
+       else
+       {
+               // Direction (up/down/in/out)
+               const EdgeType corner2et[] = { etUp, etDown, etIn, etOut };
+               const EdgeType dir = corner2et[corner - rcNumCorners];
+
+               for(int i=edge.size()-1; i>=0; i--)
+               {
+                       if ((e[i].room1 == n) && ((e[i].type1 & etDirection) == dir))
+                       {
+                               newEdge = e[i].Swapped();
+                               return i;
+                       }
+                       else if ((e[i].room2 == n) && ((e[i].type2 & etDirection) == dir)
+                               && !(e[i].type1 & etNoRoom2))
+                       {
+                               newEdge = e[i];
+                               return i;
+                       }
+               }
+       }
+
+       return -1;
+}
+
+
+//
+// Find the other end of a connection with corners:
+//
+// Input:
+//   start:
+//     The edge we want to follow
+//
+// Output:
+//   cornerRooms:  (if not NULL)
+//     Contains the room numbers of all corners in this connection.
+//     If the vector was not empty, the new entries are appended.
+//
+// Returns:
+//   The number of the room at the other end
+//   -1 if the connection doesn't lead anywhere
+//
+int MapDoc::findOtherEnd(EdgeConstItr start, RoomNumVec * cornerRooms) const
+{
+       EdgeConstItr oldEdge = start;
+       RoomNum room = (start->end1 == rcCorner ? start->room1 : start->room2);
+
+       if (cornerRooms)
+               cornerRooms->push_back(room);
+
+       for(;;)
+       {
+               EdgeConstItr lastTime = oldEdge;
+
+               for(EdgeConstItr e=edge.begin(); e<edge.end(); e++)
+               {
+                       if (e == oldEdge)
+                               continue;
+
+                       if (e->room1 == room)
+                       {
+                               room = e->room2;
+
+                               if (e->end2 != rcCorner)
+                                       return room;
+
+                               if (cornerRooms)
+                                       cornerRooms->push_back(room);
+
+                               oldEdge = e;
+                       }
+                       else if (e->room2 == room)
+                       {
+                               room = e->room1;
+
+                               if (e->end1 != rcCorner)
+                                       return room;
+
+                               if (cornerRooms)
+                                       cornerRooms->push_back(room);
+
+                               oldEdge = e;
+                       }
+               }
+
+               if (lastTime == oldEdge)
+                       return -1;                // We didn't go anywhere
+       }
+}
+
+
+int MapDoc::findOtherEnd(int n) const
+{
+       return findOtherEnd(edge.begin() + n);
+}
+
+
+void MapDoc::getEdgePoints(const MapEdge & e, QPoint & p1, QPoint & p2) const
+{
+       const QPoint cornerOffset[] = {
+               { roomWidth / 2, 0 }, { roomWidth / 2, roomHeight },
+               { roomWidth, roomHeight / 2 }, { 0, roomHeight / 2 },
+               { roomWidth, 0 }, { 0, 0 },
+               { roomWidth, roomHeight }, { 0, roomHeight },
+               { 3 * roomWidth / 4, 0 }, { roomWidth / 4, 0 },
+               { 3 * roomWidth / 4, roomHeight }, { roomWidth / 4, roomHeight },
+               { gridX / 2, gridY / 2 }
+       };
+
+/*     const QPoint nowhereOffset[] = {
+               { 0, -gridY / 2 }, { 0, gridY / 2 }, { gridX / 2, 0 }, { -gridX / 2, 0 }, // N, S, E, W
+               { gridX / 2, -gridY / 2 }, { -gridX / 2, -gridY / 2 }, { gridX / 2, gridY / 2 }, { -gridX / 2, gridY / 2 }, // NE, NW, SE, SW
+               { 0, -gridY / 2 }, { 0, -gridY / 2 }, { 0, gridY / 2 }, { 0, gridY / 2 } // NNE, NNW, SSE, SSW
+       };*/
+       const int stub = (int)((gridY / 2.0) - 15.0);
+       const int stubd = (int)(((gridY / 2.0) - 15.0) * 0.7071);
+       const QPoint nowhereOffset[] = {
+               { 0, -stub }, { 0, stub }, { stub, 0 }, { -stub, 0 }, // N, S, E, W
+               { stubd, -stubd }, { -stubd, -stubd }, { stubd, stubd }, { -stubd, stubd }, // NE, NW, SE, SW
+               { 0, -stub }, { 0, -stub }, { 0, stub }, { 0, stub } // NNE, NNW, SSE, SSW
+       };
+
+       p1 = room[e.room1].pos + cornerOffset[e.end1];
+
+       if (e.type1 & etNoRoom2)
+       {
+               if (e.type1 & etNoExit)
+                       p2 = p1 + nowhereOffset[e.end1];
+               else
+                       p2 = p1 + (nowhereOffset[e.end1] * 3);
+       }
+       else
+               p2 = room[e.room2].pos + cornerOffset[e.end2];
+}
+
+
+void MapDoc::getEdgePoints(int n, QPoint & p1, QPoint & p2) const
+{
+       getEdgePoints(edge[n], p1, p2);
+}
+
+
+void MapDoc::getEdgeRect(const MapEdge & e, QRect & r) const
+{
+//     getEdgePoints(e, r.left, r.top, r.right, r.bottom);
+//     r.NormalizeRect();
+//     r.InflateRect(gridX, gridY);
+       QPoint p1, p2;
+       getEdgePoints(e, p1, p2);
+       r = QRect(p1, p2).normalized();
+       r += QMargins(gridX, gridY, gridX, gridY);
+}
+
+
+//
+// Compute the actual size of the map:
+//
+// Output:
+//   r:  The rectangle enclosing the map in logical coordinates
+//
+void MapDoc::getGridRect(QRect & rect)
+{
+//     rect.SetRectEmpty();
+       rect = QRect();
+
+       for(RoomConstItr r=room.getVector().begin(); r!=room.getVector().end(); r++)
+               rect |= (*r)->getRect();
+
+       for(PageConstItr p=page.begin(); p!=page.end(); p++)
+               rect |= getPageRect(*p);
+}
+
+
+//
+// Compute the actual size of the map:
+//
+// Output:
+//   size: The size of the map in grid units
+//     Does not consider stubs or labels that may extend off the edge
+//
+void MapDoc::getGridSize(QSize & size)
+{
+       QRect rect;
+       getGridRect(rect);
+
+       size.setWidth(rect.right() / gridX);
+       size.setHeight(rect.top() / -gridY);
+}
+
+
+//
+// Add a page to the map:
+//
+// Input:
+//   n:        The new page number (0 based)
+//   newPage:  The page information
+//
+void MapDoc::addPage(int n, const MapPage & newPage)
+{
+       ASSERT(n >= 0 && n <= page.size());
+
+       if (page.size() == maxRooms)
+               return;
+
+       page.insert(page.begin() + n, newPage);
+       isDirty = true;
+//     UpdateAllViews(NULL, dupPageCount, NULL);
+}
+
+
+//
+// Append pages to the map:
+//
+// Input:
+//   newPages:  The page information
+//
+void MapDoc::addPages(const PageVec & newPages)
+{
+       if (page.size() + newPages.size() > maxRooms)
+               return;
+
+       page.insert(page.end(), newPages.begin(), newPages.end());
+       isDirty = true;
+//     UpdateAllViews(NULL, dupPageCount, NULL);
+}
+
+
+void MapDoc::addPages(int n)
+{
+       page.reserve(page.size() + n);
+}
+
+
+//
+// Delete a page from the map:
+//
+// Input:
+//   n:  The (0 based) page number to delete
+//
+void MapDoc::deletePage(int n)
+{
+       ASSERT(n >= 0 && n < page.size());
+       ASSERT(page.size() > 1);
+
+       isDirty = true;
+       QRect rect = getPageRect(n);
+
+       page.erase(page.begin() + n);
+
+//     UpdateAllViews(NULL, dupPageCount, NULL);
+}
+
+
+QRect MapDoc::getPageRect(int p)
+{
+       return QRect(page[p].pos, pageSize);
+}
+
+
+QRect MapDoc::getPageRect(const MapPage & p)
+{
+       return QRect(p.pos, pageSize);
+}
+
+
+//
+// Place pages to cover the entire map:
+//
+void MapDoc::layoutPages()
+{
+// Adds nothing for now, so nuke it
+#if 0
+       UndoPaginate * undoRec = new UndoPaginate(*this);
+
+       QRect r;
+       getGridRect(r); // Just counts rooms, since UndoPaginate cleared the pages
+
+       const int
+               width  = r.width(),
+               height = r.height(),
+               numX   = (width  + pageSize.width() - 1) / pageSize.width(),
+               numY   = (height + pageSize.height() - 1) / pageSize.height();
+
+       if (!numX)
+               page.insert(page.begin());  // One page at 0,0
+       else
+       {
+               int i;
+               float delta, step;
+               PageItr p, p2;
+               page.resize(numX * numY);
+
+               if (numX == 1)
+               {
+                       i = r.left - (pageSize.width() - width) / 2;
+                       i -= i % gridX;
+
+                       if (i < 0)
+                               i = 0;
+
+                       for(p=page.begin(); p!=page.end(); ++p)
+                               p->pos.x = i;
+               }
+               else
+               {
+                       step = float(width - pageSize.width()) / float(numX);
+                       delta = 0;
+
+                       for(p=page.begin(), p2=p+numX; p!=p2; ++p, delta+=step)
+                       {
+                               p->pos.x = r.left + delta;
+                               p->pos.x -= p->pos.x % gridX;
+                       }
+
+                       p[-1].pos.x = r.right - pageSize.width();
+
+                       for(p2=page.begin(); p!=page.end(); ++p, ++p2)
+                               p->pos.x = p2->pos.x;
+               }
+
+               if (numY == 1)
+               {
+                       i = r.bottom + (pageSize.height() - height)/2;
+                       i -= i % gridY;
+
+                       if (i > 0)
+                               i = 0;
+
+                       for(p=page.begin(); p!=page.end(); ++p)
+                               p->pos.y = i;
+               }
+               else
+               {
+                       step = float(pageSize.height() - height) / float(numY);
+                       delta = 0;
+                       p = p2 = page.begin();
+
+                       while (p2 != page.end())
+                       {
+                               p2 += numX;
+
+                               if (p2 == page.end())
+                                       i = r.top + pageSize.height();
+                               else
+                               {
+                                       i = r.bottom + delta;
+                                       i -= i % gridY;
+                                       delta += step;
+                               }
+
+                               while (p != p2)
+                                       (p++)->pos.y = i;
+                       }
+               }
+       }
+
+       if (undoRec->getPages() == page)
+               delete undoRec;             // No changes
+       else
+       {
+               setUndoData(undoRec);
+               isDirty = true;
+//             UpdateAllViews(NULL, dupPageCount, NULL);
+       }
+#endif
+}
+
+
+//
+// Move a page:
+//
+// Input:
+//   n:       The (0 based) page number to move
+//   offset:  How much to move it
+//
+// Note:
+//   Must not change undo data (may be used during undo)
+//
+void MapDoc::movePage(int n, const QSize & offset)
+{
+       isDirty = true;
+
+//     page[n].pos += offset;
+       page[n].pos += QPoint(offset.width(), offset.height());
+//     UpdateAllViews(NULL);
+}
+
+
+//
+// Decide if we need to repaginate:
+//
+// Returns:
+//   true:   Some rooms are not (completely) on any page
+//   false:  All rooms are on a page
+//
+bool MapDoc::needRepaginate()// const
+{
+       const PageConstItr pBegin = page.begin(), pEnd = page.end();
+//     QRect rr, pr;
+
+       for(RoomConstItr r=room.getVector().begin(); r!=room.getVector().end(); r++)
+       {
+               QRect rr = (**r).getRect();
+
+               for(PageConstItr p=pBegin; p!=pEnd; p++)
+               {
+                       QRect pr = getPageRect(*p);
+                       QRect i = pr & rr;
+
+                       if (!i.isNull() && (i == rr))
+//                     if (i.IntersectRect(&pr, &rr) && (i == rr))
+                               goto nextRoom;
+               }
+
+               return true;                // This room not on any page
+
+               nextRoom:
+               ;
+       }
+
+       return false;
+}
+
+
+//
+// Determine which page (if any) contains a given point:
+//
+// The point must be in the page border (one of the grid cells which
+// displays the page number).
+//
+// Input:
+//   pos:  The position to test
+//
+// Returns:
+//   The (0 based) page number of the page containing the point
+//   -1 if no page contains the point
+//
+int MapDoc::pageBorderHit(const QPoint & pos)
+{
+       for(int i=page.size()-1; i>=0; i--)
+       {
+               QRect r = getPageRect(i);
+
+               if (r.contains(pos) &&
+//             if (r.PtInRect(pos) &&
+                       (((pos.x() - r.left() < gridX) || (r.right()  - pos.x() < gridX)) !=
+                       ((pos.y() - r.top()  < gridY) || (r.bottom() - pos.y() < gridY))))
+                       // Point is in the X border or the Y border, but not both
+                       return i;
+       }
+
+       // Not in any page border
+       return -1;
+}
+
+
+//
+// Add a new room:
+//
+// Returns:
+//   The room number of the new room (-1 if error)
+//
+int MapDoc::addRoom(const QPoint & pos)
+{
+       if (room.size() == maxRooms)
+               return -1;
+
+       MapRoom * newRoom = new MapRoom();
+       newRoom->flags = rfBorder;
+       newRoom->pos = pos;
+//     newRoom->computeNameLength();
+       addRoom(room.size(), newRoom);
+
+       return room.size() - 1;
+}
+
+
+//
+// Add a new room:
+//
+// Input:
+//   n:        The position for the new room [0..size]
+//   newRoom:  A MapRoom object allocated by new
+//
+void MapDoc::addRoom(int n, MapRoom * newRoom)
+{
+       ASSERT(n >= 0 && n <= room.size());
+       ASSERT(newRoom);
+
+       room.insert(n, newRoom);
+
+       if (n != room.size()-1)
+       {
+               for(int i=edge.size()-1; i>=0; i--)
+               {
+                       if (edge[i].room1 >= n)
+                               ++edge[i].room1;
+
+                       if (edge[i].room2 >= n)
+                               ++edge[i].room2;
+               }
+       }
+
+       isDirty = true;
+       QRect rect = newRoom->getRect();
+//     UpdateAllViews(NULL, 0, reinterpret_cast<CObject *>(&rect));
+}
+
+
+void MapDoc::addRooms(int n)
+{
+       room.reserve(room.size() + n);
+}
+
+
+//
+// Extract a room from the map:
+//
+// Input:
+//   n:  The room number to remove
+//
+// Returns:
+//   A pointer to the room extracted from the map
+//   You can use this in the undo record
+//
+MapRoom * MapDoc::extractRoom(int n)
+{
+       ASSERT(n >= 0 && n < room.size());
+
+       isDirty = true;
+
+       for(int i=edge.size()-1; i>=0; i--)
+       {
+               if ((edge[i].room1 == n) ||
+                       (!(edge[i].type1 & etNoRoom2) && (edge[i].room2 == n)))
+                       deleteEdge(i);
+               else
+               {
+                       if (edge[i].room1 > n)
+                               --edge[i].room1;
+
+                       if (edge[i].room2 > n)
+                               --edge[i].room2;
+               }
+       }
+
+       MapRoom * oldRoom = room.extract(n);
+       QRect rect = oldRoom->getRect();
+
+//     UpdateAllViews(NULL, dupDeletedRoom, reinterpret_cast<CObject *>(&rect));
+
+       return oldRoom;
+}
+
+
+void MapDoc::deleteRoom(int n)
+{
+       delete extractRoom(n);
+}
+
+
+//
+// Find out if a new room would intersect an old one:
+//
+// There must be at least one grid of space around the room.  Ignores corners.
+//
+// Input:
+//   pos: The proposed location for a new room
+//
+// Returns:
+//   The room number of an intersecting room
+//   -1 if the space is unoccupied
+//   -2 if the space is off the grid
+//
+int MapDoc::findRoom(const QPoint & pos) const
+{
+       if (pos.x() < 0 || pos.x() + roomWidth > docSize.width()
+               || pos.y() < 0 || pos.y() + roomHeight > docSize.height())
+               return -2;
+
+       RoomConstItr rm = room.getVector().begin();
+       QRect newRoom(pos, defaultRoomSize);
+//     newRoom.InflateRect(gridX - 1, gridY - 1);
+       newRoom += QMargins(gridX - 1, gridY - 1, gridX - 1, gridY - 1);
+
+       for(int i=room.size()-1; i>=0; i--)
+       {
+               if (!rm[i]->isCorner()
+                       && newRoom.intersects(rm[i]->getRect()))
+                       return i;
+       }
+
+       return -1;
+}
+
+
+//
+// Move a room:
+//
+// Input:
+//   n:       The room number to move
+//   offset:  How much to move it
+//
+// Note:
+//   Must not change undo data (may be used during undo)
+//
+void MapDoc::moveRoom(int n, const QSize & offset)
+{
+       isDirty = true;
+
+//     room[n].pos += offset;
+       room[n].pos += QPoint(offset.width(), offset.height());
+//     UpdateAllViews(NULL);
+}
+
+
+//
+// Determine which room (if any) contains a given point:
+//
+// Input:
+//   pos:  The position to test
+//
+// Output:
+//   corner:
+//     Which corner of the room was hit (rcNone if in center)
+//
+// Returns:
+//   The room number of the room containing the point
+//   -1 if no room contains the point
+//
+int MapDoc::roomHit(const QPoint & pos, RoomCorner * corner/*= NULL*/)
+{
+       if (corner)
+               *corner = rcNone;
+
+       for(int i=room.size()-1; i>=0; i--)
+       {
+               QRect r = room[i].getRect();
+
+               if (r.contains(pos) == false)
+                       continue;
+
+               if (corner && (room[i].isCorner() == false))
+                       *corner = CornerHit(pos - r.topLeft());
+
+               return i;
+       }
+
+       // Not in any room
+       return -1;
+}
+
+
+RoomCorner MapDoc::CornerHit(const QPoint & pos)
+{
+       long x = pos.x(), y = pos.y();
+
+       // FIXME this assumes all rooms are the same size
+       if (y < roomHeight / 4)
+       {
+               if (x < roomWidth * 3 / 16)
+                       return rcNW;
+               else if (x < roomWidth * 5 / 16)
+                       return rcNNW;
+               else if (x > roomWidth * 13 / 16)
+                       return rcNE;
+               else if (x > roomWidth * 11 / 16)
+                       return rcNNE;
+               else
+                       return rcN;
+       }
+       else if (y > roomHeight * 3 / 4)
+       {
+               if (x < roomWidth * 3 / 16)
+                       return rcSW;
+               else if (x < roomWidth * 5 / 16)
+                       return rcSSW;
+               else if (x > roomWidth * 13 / 16)
+                       return rcSE;
+               else if (x > roomWidth * 11 / 16)
+                       return rcSSE;
+               else
+                       return rcS;
+       }
+       else
+       {
+               if (x < roomWidth / 4)
+                       return rcW;
+               else if (x > roomWidth * 3 / 4)
+                       return rcE;
+       }
+
+       return rcNone;
+}
+
+
+//
+// Change the map's title:
+//
+// Input:
+//   newName:  The new map title
+//
+void MapDoc::setName(const char * newName)
+{
+       if (name == newName)
+               return;  // Nothing to do
+
+       name = newName;
+       isDirty = true;
+}
+
+
+//
+// Change the map's comments:
+//
+// Input:
+//   newNote:  The new map comments
+//
+void MapDoc::setNote(const char * newNote)
+{
+       if (note == newNote)
+               return;  // Nothing to do
+
+       note = newNote;
+       trimRight(note);
+       isDirty = true;
+}
+
+
+//
+// Change a room's flags:
+//
+// Input:
+//   n:        The room number to change
+//   set:      The flags to set
+//   clear:    The flags to clear (default 0)
+//
+// Note:
+//   flags in both SET and CLEAR will be set.
+//
+void MapDoc::setRoomFlags(int n, RoomFlags set, RoomFlags clear)
+{
+       MapRoom & rm = room[n];
+       RoomFlags old = rm.flags;
+
+       rm.flags &= ~clear;
+       rm.flags |= set;
+
+       if (rm.flags != old)
+       {
+               isDirty = true;
+
+//             rm.getRect(rect).InflateRect(5, 5);
+               QRect rect = rm.getRect() += QMargins(5, 5, 5, 5);
+//             UpdateAllViews(NULL, 0, reinterpret_cast<CObject *>(&rect));
+       }
+}
+
+
+//
+// Change a room's name
+//
+// Input:
+//   n:       The room number to change
+//   newName: The new room name
+//
+void MapDoc::setRoomName(int n, const char * newName)
+{
+       MapRoom & rm = room[n];
+
+       if (rm.name == newName)
+               return; // Nothing to do
+
+       rm.name = newName;
+       isDirty = true;
+
+//     rm.getRect(rect).DeflateRect(5, 5);
+       QRect rect = rm.getRect() -= QMargins(5, 5, 5, 5);
+//     UpdateAllViews(NULL, 0, reinterpret_cast<CObject *>(&rect));
+}
+
+
+//
+// Change a room's comments
+//
+// Input:
+//   n:       The room number to change
+//   newNote: The new room note
+//
+void MapDoc::setRoomNote(int n, const char * newNote)
+{
+       MapRoom & rm = room[n];
+
+       if (rm.note == newNote)
+               return; // Nothing to do
+
+       const bool redraw = (rm.note.empty() || !*newNote);
+       rm.note = newNote;
+       trimRight(rm.note);
+       isDirty = true;
+
+       if (redraw)
+       {
+//             rm.getRect(rect).DeflateRect(5, 5); // PORT
+               QRect rect = rm.getRect() -= QMargins(5, 5, 5, 5);
+//             UpdateAllViews(NULL, dupRoomComment, reinterpret_cast<CObject *>(&rect));
+       }
+//     else
+//             UpdateAllViews(NULL, dupRoomComment, NULL);
+}
+
+
+//
+// Undo handling:
+//--------------------------------------------------------------------
+// Set undo data:
+//
+void MapDoc::setUndoData(UndoRec * newUndo)
+{
+       if (undoData != newUndo)
+       {
+               delete undoData;
+               undoData = newUndo;
+       }
+}
+
+
+#if 0
+//
+// Undo the last operation:
+//
+void MapDoc::OnEditUndo()
+{
+       if (undoData)
+               setUndoData(undoData->undo(*this));
+}
+
+
+//
+// Update the Undo menu item:
+//
+void MapDoc::OnUpdateEditUndo(CCmdUI * pCmdUI)
+{
+       pCmdUI->Enable(undoData && !locked);
+
+       CString text;
+
+       if (undoData)
+       {
+               text = "&Undo ";
+               text += undoData->getName();
+       }
+       else
+               text = "Can't &Undo";
+
+       text += "\tCtrl+Z";
+       pCmdUI->SetText(text);
+}
+#endif
+
+
+//
+// MapDoc commands
+//
+// Erase contents of document:
+//
+void MapDoc::DeleteContents(void)
+{
+       locked = false;
+       edge.erase(edge.begin(), edge.end());
+       page.resize(1);
+       page.front().pos.setX(0);
+       page.front().pos.setY(0);
+       room.removeAll();
+       name.erase();
+       note.erase();
+       docSize = QSize(271 * gridX, 451 * gridY);
+       setUndoData(NULL);
+       needBackup = true;
+}
+
+
+#if 0
+//
+// Move all the rooms around:
+//
+// This moves only rooms that have connections.  Rooms that stand
+// alone (used as a title box, for example) are not moved.
+//
+void MapDoc::OnEditMoveMap(UINT cmd)
+{
+       const int numEdges = edge.size();
+       const int numRooms = room.size();
+
+       ByteVec selected;
+       selected.resize(numRooms, false);
+
+       int i;
+
+       // Select all rooms that have a connection to somewhere:
+       for(i=0; i<numEdges; i++)
+       {
+               selected[edge[i].room1] = true;
+
+               if ((edge[i].type1 & etNoRoom2) == 0)
+                       selected[edge[i].room2] = true;
+       }
+
+       // Compute the bounding rectangle of the selected rooms:
+       int numSelected = 0;
+       QRect r, temp;
+
+       r.SetRectEmpty();
+
+       for(i=0; i<numRooms; i++)
+       {
+               if (selected[i])
+               {
+                       ++numSelected;
+                       r |= room[i].getRect(temp);
+               }
+       }
+
+       // Calculate the distance to move them:
+       QSize offset(0, 0);
+
+       switch (cmd)
+       {
+       case ID_EDIT_MOVE_CENTER:
+               offset.width() = (docSize.width() - r.right - r.left) / 2;
+               offset.height() = (docSize.height() + r.top + r.bottom) / -2;
+               break;
+       case ID_EDIT_MOVE_TOP:    offset.height() = -r.bottom;             break;
+       case ID_EDIT_MOVE_BOTTOM: offset.height() = -(docSize.height() + r.top); break;
+       case ID_EDIT_MOVE_LEFT:   offset.width() = -r.left;               break;
+       case ID_EDIT_MOVE_RIGHT:  offset.width() = docSize.width() - r.right;  break;
+       }
+
+       offset.width() -= offset.width() % gridX; // Make sure the rooms stay on the grid
+       offset.height() -= offset.height() % gridY;
+
+       // Do the move:
+       if (!offset.width() && !offset.height())
+               return;                     // Do nothing
+
+       setUndoData(new UndoMove(isDirty, offset, numSelected, selected, 0, selected)); // No selected pages
+
+       for(i=0; i<numRooms; i++)
+       {
+               if (selected[i])
+                       moveRoom(i, offset);
+       }
+}
+
+
+void MapDoc::OnNavigationMode()
+{
+       locked = !locked;
+
+       if (locked)
+       {
+               if (getRoomCount() > 0)
+                       UpdateAllViews(NULL, dupNavigationMode);
+               else
+                       locked = false;           // Can't enter navigation mode
+       }
+}
+
+
+void MapDoc::OnUpdateEditMoveMap(CCmdUI * pCmdUI)
+{
+       // You can't use Move Map unless you have edges:
+       pCmdUI->Enable(!locked && edge.size());
+}
+
+
+void MapDoc::OnUpdateNavigationMode(CCmdUI * pCmdUI)
+{
+       pCmdUI->SetCheck(locked);
+
+       if (!locked)
+               pCmdUI->Enable(getRoomCount() > 0);
+}
+#endif
+
diff --git a/src/mapdoc.h b/src/mapdoc.h
new file mode 100644 (file)
index 0000000..f84c9f4
--- /dev/null
@@ -0,0 +1,149 @@
+//
+// GUEmap
+// (C) 1997-2007 by Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+// mapdoc.h: interface of the MapDoc class
+//
+
+#ifndef __MAPDOC_H__
+#define __MAPDOC_H__
+
+#include <QtWidgets>
+#include <stdio.h>
+//#include <stdint.h>
+#include "globals.h"
+
+const long
+       dupDeletedRoom    = 1,
+       dupNavigationMode = 2,
+       dupRoomComment    = 3,
+       dupPageCount      = 4;
+
+extern const RoomCorner oppositeCorner[rcNumCorners];
+
+const QSize defaultRoomSize(roomWidth, roomHeight);
+
+
+class UndoRec;
+
+
+class MapDoc
+{
+       public:
+               RoomArray room;
+               EdgeVec   edge;
+               PageVec   page;
+               string    name;
+               string    note;
+               QSize     docSize;
+               QSize     pageSize;
+               UndoRec * undoData;
+               bool      needBackup;
+               string filename;
+               bool isDirty;
+               bool locked;
+
+       // Stuff that needs implementation
+       public:
+               void UpdateAllViews(void *, int, void *) { /* SORRY, NOTHING */ }
+
+               MapDoc();
+               virtual ~MapDoc();
+
+#if 0
+       // Overrides
+       public:
+               virtual CFile * GetFile(const char * fileName, unsigned int openFlags, CFileException * error);
+
+       protected:
+               virtual bool DoSave(const char * pathName, bool replace = TRUE);
+
+       // ClassWizard generated virtual function overrides
+       //{{AFX_VIRTUAL(MapDoc)
+       public:
+               virtual bool OnNewDocument();
+               virtual void ReportSaveLoadException(const char * lpszPathName, CException * e, bool bSaving, unsigned int nIDPDefault);
+               virtual void Serialize(CArchive & ar);
+       //}}AFX_VIRTUAL
+#endif
+               void DeleteContents(void);
+
+       public:
+               void getGridRect(QRect & rect);
+               void getGridSize(QSize & size);
+               void setUndoData(UndoRec * newUndo);
+               int addCorner(RoomNum r, int e);
+               void addEdge(const MapEdge & newEdge);
+               void addEdges(int n);
+               void deleteCorner(RoomNum r);
+               void deleteEdge(int n);
+               int findEdge(RoomNum n, RoomCorner corner, MapEdge & newEdge) const;
+               int findOtherEnd(EdgeConstItr start, RoomNumVec * cornerRooms = NULL) const;
+               int findOtherEnd(int n) const;
+               void getEdgePoints(const MapEdge & e, QPoint & p1, QPoint & p2) const;
+               void getEdgePoints(int n, QPoint & p1, QPoint & p2) const;
+               void getEdgeRect(const MapEdge & e, QRect & r) const;
+               void addPage(int n, const MapPage & newPage);
+               void addPages(int n);
+               void addPages(const PageVec & newPages);
+               void deletePage(int n);
+               QRect getPageRect(int p);
+               QRect getPageRect(const MapPage & p);
+               void layoutPages();
+               void movePage(int n, const QSize & offset);
+               bool needRepaginate();
+               int pageBorderHit(const QPoint & pos);
+
+               int addRoom(const QPoint & pos);
+               void addRoom(int n, MapRoom * newRoom);
+               void addRooms(int n);
+               MapRoom * extractRoom(int n);
+               void deleteRoom(int n);
+               int findRoom(const QPoint & pos) const;
+               void moveRoom(int n, const QSize & offset);
+               int roomHit(const QPoint & pos, RoomCorner * corner = NULL);
+               RoomCorner CornerHit(const QPoint & pos);
+               void setName(const char * newName);
+               void setNote(const char * newNote);
+               void setRoomFlags(int n, RoomFlags set, RoomFlags clear = 0);
+               void setRoomName(int n, const char * newName);
+               void setRoomNote(int n, const char * newNote);
+               void shortestPath(string & path, RoomNum start, RoomNum end) const;
+
+#if 0
+       // Generated message map functions
+       protected:
+       //{{AFX_MSG(MapDoc)
+               afx_msg void OnNavigationMode();
+//             afx_msg void OnEditUndo();
+               afx_msg void OnFileExport();
+//             afx_msg void OnUpdateEditUndo(CCmdUI * pCmdUI);
+               afx_msg void OnUpdateNavigationMode(CCmdUI * pCmdUI);
+               afx_msg void OnEditMoveMap(unsigned int cmd);
+               afx_msg void OnUpdateEditMoveMap(CCmdUI * pCmdUI);
+       //}}AFX_MSG
+#endif
+};
+
+
+#if 0
+class RoomScrap
+{
+       protected:
+               EdgeVec   edges;
+               PageVec   pages;
+               RoomArray rooms;
+
+       public:
+               RoomScrap(const MapDoc & doc, int numSelected, const ByteVec & selectedRooms, int numSelectedPages, const ByteVec & selectedPages);
+               ~RoomScrap() {}
+               UndoRec * paste(MapDoc & doc) const;
+};
+#endif
+
+#endif // __MAPDOC_H__
+
diff --git a/src/mapview.cpp b/src/mapview.cpp
new file mode 100644 (file)
index 0000000..7a915ed
--- /dev/null
@@ -0,0 +1,4204 @@
+//
+// GUEmap
+// (C) 1997-2007 Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+// mapview.cpp: implementation of the CMapView class
+//
+
+#include "mapview.h"
+
+#include <math.h>
+#include "mainwin.h"
+#include "mapdialog.h"
+#include "mapdoc.h"
+#include "mathconstants.h"
+//#include "properties.h"
+#include "roomdialog.h"
+#include "undo.h"
+
+
+const int
+       penEdgeWidth = 9,
+       penPageWidth = 20,
+       penRoomWidth = 5,
+       maxZoom      = 200,
+       minZoom      = 20,
+       minZoomGrid  = 50;
+
+const unsigned int
+       clickTicks   = 500,
+       menuTimer    = 1;
+
+struct DirectionName
+{
+       RoomCorner dir;
+       const char * name;
+};
+
+static const DirectionName directions[] = {
+       { rcN,   "n" },   { rcS,   "s" },   { rcE,   "e" },   { rcW,   "w" },
+       { rcNE,  "ne" },  { rcNW,  "nw" },  { rcSE,  "se" },  { rcSW,  "sw" },
+       { rcNNE, "nne" }, { rcNNW, "nnw" }, { rcSSE, "sse" }, { rcSSW, "ssw" },
+       { rcUp,  "u" },   { rcDown, "d" },  { rcIn,  "i" },   { rcOut, "o" },
+       { rcN, "north" }, { rcS, "south" }, { rcE, "east" },  { rcW, "west" },
+       { rcNE, "northeast" }, { rcNW, "northwest" },
+       { rcSE, "southeast" }, { rcSW, "southwest" },
+       { rcUp, "up" }, { rcDown, "down" }, { rcIn, "in" }, { rcOut, "out" },
+       { rcNone, NULL }
+};
+
+static const char a2z[] = "abcdefghijklmnopqrstuvwxyz";
+
+
+//
+// Parse a direction word:
+//
+// Input:
+//   text:  The string to be tested (must be in lowercase, may be empty)
+//
+// Output:
+//   text:  Has the direction word and whitespace stripped from the beginning
+//
+// Returns:
+//   The direction indicated by the text
+//   rcNone if not a direction
+//
+RoomCorner parseDirection(string & text)
+{
+#if 0
+       const CString word(text.SpanIncluding(a2z));
+
+       if (!word.IsEmpty())
+       {
+               for(int i=0; directions[i].dir != rcNone; i++)
+               {
+                       if (word == directions[i].name)
+                       {
+                               text = text.Mid(word.GetLength());
+                               text.TrimLeft();
+                               return directions[i].dir;
+                       }
+               }
+       }
+#endif
+
+       return rcNone;
+}
+
+
+#if 0
+/////////////////////////////////////////////////////////////////////////////
+// CRepaginateDlg dialog
+
+class CRepaginateDlg : public CDialog
+{
+       // Construction
+       public:
+               CRepaginateDlg();   // standard constructor
+
+       // Dialog Data
+       //{{AFX_DATA(CRepaginateDlg)
+       enum { IDD = IDD_REPAGINATE };
+       //}}AFX_DATA
+
+       // Overrides
+       // ClassWizard generated virtual function overrides
+       //{{AFX_VIRTUAL(CRepaginateDlg)
+       //}}AFX_VIRTUAL
+
+       // Implementation
+       protected:
+               // Generated message map functions
+               //{{AFX_MSG(CRepaginateDlg)
+               afx_msg void OnYes();
+               virtual bool OnInitDialog();
+               //}}AFX_MSG
+
+       DECLARE_MESSAGE_MAP();
+};
+#endif
+
+
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+// CMapView
+
+#if 0
+IMPLEMENT_DYNCREATE(CMapView, CScrollZoomView)
+
+BEGIN_MESSAGE_MAP(CMapView, CScrollZoomView)
+       //{{AFX_MSG_MAP(CMapView)
+       ON_COMMAND(ID_EDIT_ADD_CORNER, OnEditAddCorner)
+       ON_COMMAND(ID_EDIT_ADD_PAGE, OnEditAddPage)
+       ON_COMMAND(ID_EDIT_ADD_ROOM, OnEditAddRoom)
+       ON_COMMAND(ID_EDIT_CLEAR, OnEditClear)
+       ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
+       ON_COMMAND(ID_EDIT_CUT, OnEditCut)
+       ON_COMMAND(ID_EDIT_PAGINATE, OnEditPaginate)
+       ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
+       ON_COMMAND(ID_EDIT_SELECT_ALL, OnEditSelectAll)
+       ON_COMMAND(ID_EDIT_SELECT_CONNECTED, OnEditSelectConnected)
+       ON_COMMAND(ID_VIEW_GRID, OnViewGrid)
+       ON_COMMAND(ID_WINDOW_REFRESH, OnWindowRefresh)
+       ON_UPDATE_COMMAND_UI(ID_EDIT_ADD_CORNER, OnUpdateEditAddCorner)
+       ON_UPDATE_COMMAND_UI(ID_EDIT_CLEAR, OnUpdateSelectedUnlocked)
+       ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateSelectedUnlocked)
+       ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateSelectedUnlocked)
+       ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
+       ON_UPDATE_COMMAND_UI(ID_EDIT_SELECT_ALL, OnUpdateUnlocked)
+       ON_UPDATE_COMMAND_UI(ID_EDIT_SELECT_CONNECTED, OnUpdateSelectedUnlocked)
+       ON_UPDATE_COMMAND_UI(ID_VIEW_GRID, OnUpdateViewGrid)
+       ON_WM_CHAR()
+       ON_WM_KEYDOWN()
+       ON_WM_LBUTTONDBLCLK()
+       ON_WM_LBUTTONDOWN()
+       ON_WM_LBUTTONUP()
+       ON_WM_MOUSEMOVE()
+       ON_WM_MOUSEWHEEL()
+       ON_WM_RBUTTONDOWN()
+       ON_WM_RBUTTONUP()
+       ON_WM_SIZE()
+       ON_WMtrIMER()
+       //}}AFX_MSG_MAP
+       ON_WM_MOUSEACTIVATE()
+       ON_BN_CLICKED(IDC_NAV_GO, OnNavGo)
+       ON_COMMAND_RANGE(ID_EDIT_PROPERTIES, ID_EDIT_MAP_PROPERTIES, OnEditProperties)
+       ON_COMMAND_RANGE(ID_NAV_GO_NORTH, ID_NAV_STUB2_OUT, OnNavGoDir)
+       ON_COMMAND_RANGE(ID_VIEW_ZOOM_IN, ID_VIEW_ZOOM_OUT, OnViewZoom)
+       ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_ZOOM_IN, ID_VIEW_ZOOM_OUT, OnUpdateViewZoom)
+       // Standard printing commands
+       ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
+       ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
+       ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
+END_MESSAGE_MAP();
+#endif
+
+//===========================================================================
+// Class CMapView:
+//
+// Member Variables:
+//   opInProgress:  Indicates what's happening while the mouse button is down
+//     gmoAddCorner:
+//       iTmp:     The edge that needs a corner
+//     gmoAddEdge:
+//       bTmp:     The modification state of the document
+//       edgeTmp:  The edge being created
+//       e2Tmp:    The original edge data (if iTmp >= 0)
+//       iTmp:     -1 if this is a new edge, >=0 if changing an old edge
+//       p1Tmp:    The start point (fixed) of the rubber band cursor
+//       p2Tmp:    The end point (moving) of the rubber band cursor
+//     gmoChangeEdge:
+//       bTmp:          The modification state of the document
+//       edgeTmp:       Info about the other end of this edge
+//       iTmp:          The edge number about to be changed
+//       p1Tmp, p2Tmp:  The start point (fixed) of the rubber band cursor
+//     gmoDeleting:
+//       Used in OnEditClear to tell OnUpdate not to clear the selection
+//     gmoSelectBox:
+//       p1Tmp:  The fixed corner of the selection rectangle
+//       p2Tmp:  The moving corner of the selection rectangle
+//       rTmp:   The current selection rectangle
+//     gmoControlDown:
+//       bTmp:   TRUE if the room or page was already selected
+//       b2Tmp:  True if this is a page instead of a room
+//       iTmp:   The room number where the button was pressed (if bTmp==TRUE)
+//       p1Tmp:  The grid cell where the mouse button was pressed
+//       p2Tmp:  The offset to the current grid cell (in logical coordinates)
+//     gmoShiftDown:
+//       iTmp:   The number of the selected page (-1 if a room)
+//       p1Tmp:  The grid cell where the mouse button was pressed
+//       p2Tmp:  The position of the selected room
+//     gmoDrag: (all dragging operations)
+//       p2Tmp:
+//         The offset to the current grid cell (in logical coordinates)
+//       rTmp:
+//         A rectangle enclosing the original position of all rooms being
+//         dragged.  Used to ensure we don't push a room off the edge.
+//   font:
+//     The font used for room names
+//   numSelected:
+//     The number of rooms currently selected
+//   numSelectedPages:
+//     The number of pages currently selected
+//   printingPage:
+//     The number of the page being printed or previewed
+//     0 means normal screen display
+//   scrollDrag:
+//     True if we are in the middle of a scroll-by-drag operation
+//   scrollDragStart:
+//     The point under the cursor when a scrollDrag started
+//   selectedOne:
+//     The selected room's number, if exactly 1 room is selected
+//     -1 otherwise
+//   selected:
+//     selected[N] is non-zero iff room N is selected
+//       2 means the room should stay selected during gmoSelectBox
+//   selectedPage:
+//     selectedPage[N] is non-zero iff page N is selected
+//       2 means the page should stay selected during gmoSelectBox
+//   zoom:
+//     Zoom factor in percent
+//
+
+//
+// CMapView construction/destruction
+//
+MapView::MapView(QWidget * parent/*= NULL*/): QWidget(parent),
+       opInProgress(gmoNone), hoveredEdge(-1), printingPage(0), scrollDrag(false),
+       scrollDragTimer(0), font("Arial", 24), showCorners(false), showGrid(true),
+//     showPages(false), zoom(100), shiftDown(false), ctrlDown(false),
+       showPages(false), zoom(20.0), shiftDown(false), ctrlDown(false),
+       altDown(false), mouseDown(false), offset(0, 0)
+{
+       doc = new MapDoc();
+
+       selectedPage.resize(doc->page.size(), 0);
+       clearSelection(false);
+
+       setFocusPolicy(Qt::StrongFocus);
+       // Make sure we get *ALL* mouse move events!
+       setMouseTracking(true);
+
+       // Set the document size:
+//scrollzoom.cpp ->    init(4 * gridX, doc->getDocSize(), QSize(gridX, gridY));
+//  CClientDC dc(this);
+//  OnPrepareDC(&dc);
+//     LOGFONT lf;
+//     memset(&lf, 0, sizeof(lf));
+//     lf.lfHeight = 62;
+//     strcpy(lf.lfFaceName, "Arial");
+//     font.CreateFontIndirect(&lf);
+
+//     if (doc->locked)
+//             OnUpdate(NULL, dupNavigationMode, NULL);
+
+//     CScrollZoomView::OnInitialUpdate();
+//     setScrollBars();              // Now fix the scroll bars
+
+       // Actions
+       deleteRoomAct = CreateAction("Delete", "Delete room", "Deletes selected rooms", QIcon(), QKeySequence(), false, this);
+       connect(deleteRoomAct, SIGNAL(triggered()), this, SLOT(HandleDelete()));
+
+       roomPropertiesAct = CreateAction("Properties...", "Properties", "Opens the properties dialog for this room", QIcon(), QKeySequence(), false, this);
+       connect(roomPropertiesAct, SIGNAL(triggered()), this, SLOT(HandleRoomProperties()));
+
+       mapPropertiesAct = CreateAction("Properties...", "Properties", "Opens the properties dialog for this map", QIcon(), QKeySequence(), false, this);
+       connect(mapPropertiesAct, SIGNAL(triggered()), this, SLOT(HandleMapProperties()));
+
+       selectConnectedAct = CreateAction("Select connected rooms", "Select connected", "Selects connected rooms", QIcon(), QKeySequence(), false, this);
+
+       addCornerAct = CreateAction("Add corner", "Add corner", "Adds a corner to the selected room", QIcon(), QKeySequence(), false, this);
+       connect(addCornerAct, SIGNAL(triggered()), this, SLOT(HandleAddCorner()));
+
+       addRoomAct = CreateAction("Add room", "Add room", "Adds a rom to the map", QIcon(), QKeySequence(), false, this);
+       connect(addRoomAct, SIGNAL(triggered()), this, SLOT(HandleAddRoom()));
+
+       addPageAct = CreateAction("Add Page", "Add Page", "Adds a page to the map", QIcon(), QKeySequence(), false, this);
+
+       addUnexploredAct = CreateAction("Unexplored", "Unexplored", "Adds an unexplored notation to the selected corner", QIcon(), QKeySequence(), false, this);
+       connect(addUnexploredAct, SIGNAL(triggered()), this, SLOT(HandleAddUnexplored()));
+
+       addLoopBackAct = CreateAction("Loop back", "Loop back", "Adds a loop back notation to the selected corner", QIcon(), QKeySequence(), false, this);
+       connect(addLoopBackAct, SIGNAL(triggered()), this, SLOT(HandleAddLoopBack()));
+
+       addNoExitAct = CreateAction("No Exit", "No Exit", "Adds a no exit notation to the selected corner", QIcon(), QKeySequence(), false, this);
+       connect(addNoExitAct, SIGNAL(triggered()), this, SLOT(HandleAddNoExit()));
+
+       addUpAct = CreateAction("Up", "Up", "Marks the selected corner as Up", QIcon(), QKeySequence(), false, this);
+       connect(addUpAct, SIGNAL(triggered()), this, SLOT(HandleAddUp()));
+
+       addDownAct = CreateAction("Down", "Down", "Marks the selected corner as Down", QIcon(), QKeySequence(), false, this);
+       connect(addDownAct, SIGNAL(triggered()), this, SLOT(HandleAddDown()));
+
+       addInAct = CreateAction("In", "In", "Marks the selected corner as In", QIcon(), QKeySequence(), false, this);
+       connect(addInAct, SIGNAL(triggered()), this, SLOT(HandleAddIn()));
+
+       addOutAct = CreateAction("Out", "Out", "Marks the selected corner as Out", QIcon(), QKeySequence(), false, this);
+       connect(addOutAct, SIGNAL(triggered()), this, SLOT(HandleAddOut()));
+
+       addOneWayAct = CreateAction("One Way", "One Way", "Marks the selected corner as one way exit", QIcon(), QKeySequence(), false, this);
+       connect(addOneWayAct, SIGNAL(triggered()), this, SLOT(HandleAddOneWay()));
+
+       clearOneWayAct = CreateAction("Two Way", "Two Way", "Marks the selected corner as traversable both ways", QIcon(), QKeySequence(), false, this);
+       connect(clearOneWayAct, SIGNAL(triggered()), this, SLOT(HandleClearOneWay()));
+
+       addRestrictedAct = CreateAction("Restricted", "Restricted", "Marks the selected corner as a restricted exit", QIcon(), QKeySequence(), false, this);
+       connect(addRestrictedAct, SIGNAL(triggered()), this, SLOT(HandleAddRestricted()));
+
+       clearRestrictedAct = CreateAction("Unrestricted", "Unrestricted", "Marks the selected corner as an unrestricted exit", QIcon(), QKeySequence(), false, this);
+       connect(clearRestrictedAct, SIGNAL(triggered()), this, SLOT(HandleClearRestricted()));
+
+       // Popup menus
+       roomContextMenu = new QMenu(this);
+       mapContextMenu = new QMenu(this);
+
+       roomContextMenu->addAction(deleteRoomAct);
+       roomContextMenu->addAction(roomPropertiesAct);
+       roomContextMenu->addAction(selectConnectedAct);
+       roomContextMenu->addAction(addCornerAct);
+       QMenu * sub = roomContextMenu->addMenu(tr("Edge"));
+       sub->addAction(addLoopBackAct);
+       sub->addAction(addUnexploredAct);
+       sub->addAction(addNoExitAct);
+       sub->addAction(addUpAct);
+       sub->addAction(addDownAct);
+       sub->addAction(addInAct);
+       sub->addAction(addOutAct);
+       sub->addAction(addOneWayAct);
+       sub->addAction(clearOneWayAct);
+       sub->addAction(addRestrictedAct);
+       sub->addAction(clearRestrictedAct);
+
+       mapContextMenu->addAction(mapPropertiesAct);
+       mapContextMenu->addAction(addRoomAct);
+       mapContextMenu->addAction(addPageAct);
+       mapContextMenu->addAction(deleteRoomAct);
+}
+
+
+MapView::~MapView()
+{
+       delete doc;
+//     gueApp()->closingView(this);  // Remove any comment for this view
+}
+
+
+void MapView::DrawArrowhead(QPainter * painter, QPointF head, QPointF tail)
+{
+       QPolygonF arrow;
+
+       const double angle = Angle(tail - head);
+       const double orthoAngle = angle + QTR_TAU;
+       const double size = 5.0;
+
+       QPointF ortho(cos(orthoAngle), sin(orthoAngle));
+       QPointF unit = UnitVector(head - tail);
+
+       QPointF p1 = head - (unit * 9.0 * size);
+       QPointF p2 = p1 + (ortho * 3.0 * size);
+       QPointF p3 = p1 - (ortho * 3.0 * size);
+
+       arrow << head << p2 << p3;
+       painter->drawPolygon(arrow);
+}
+
+
+void MapView::DrawNoExit(QPainter * painter, QPointF head, QPointF tail)
+{
+       const double angle = Angle(tail - head);
+       const double orthoAngle = angle + QTR_TAU;
+       const double size = 5.0;
+
+       QPointF ortho(cos(orthoAngle), sin(orthoAngle));
+       QPointF unit = UnitVector(head - tail);
+
+       QPointF p2 = head + (ortho * 3.0 * size);
+       QPointF p3 = head - (ortho * 3.0 * size);
+
+       painter->drawLine(p2, p3);
+}
+
+
+//
+// MapView drawing
+//
+void MapView::paintEvent(QPaintEvent * /*event*/)
+{
+       const char edgeLabel[] = "UDIO";
+
+       const QPoint edgLblOffset[] = {
+               // N, S, E, W
+               { roomWidth / 2 + 24, -13 }, { roomWidth / 2 - 48, roomHeight + 52 },
+               { roomWidth + 24, roomHeight / 2 + 57 }, { -48, roomHeight / 2 - 19 },
+               // NE, NW, SE, SW
+               { roomWidth - 48, -13 }, { 24, -13 },
+               { roomWidth - 48, roomHeight + 52 }, { 24, roomHeight + 52 },
+               // NNE, NNW, SSE, SSW
+               { 3 * roomWidth / 4 + 24, -13 }, { roomWidth / 4 + 24, -13 },
+               { 3 * roomWidth / 4 - 48, roomHeight + 52 },
+               { roomWidth / 4 - 48, roomHeight + 52 }
+       };
+
+       const int
+               cantGoSide   = 19,
+               cantGoAngleX = gridY / 6,
+               cantGoAngleY = gridX / 6;
+/*
+       rcN, rcS, rcE, rcW,
+       rcNE, rcNW, rcSE, rcSW,
+       rcNNE, rcNNW, rcSSE, rcSSW,
+*/
+       QRect cornerRect[] = {
+               { roomWidth * 5 / 16, 0, roomWidth * 6 / 16, roomHeight / 4 },
+               { roomWidth * 5 / 16, roomHeight * 3 / 4, roomWidth * 6 / 16, roomHeight / 4 },
+               { roomWidth * 3 / 4, roomHeight / 4, roomWidth / 4, roomHeight / 2 },
+               { 0, roomHeight / 4, roomWidth / 4, roomHeight / 2 },
+               { roomWidth * 13 / 16, 0, roomWidth * 3 / 16, roomHeight / 4 },
+               { 0, 0, roomWidth * 3 / 16, roomHeight / 4 },
+               { roomWidth * 13 / 16, roomHeight * 3 / 4, roomWidth * 3 / 16, roomHeight / 4 },
+               { 0, roomHeight * 3 / 4, roomWidth * 3 / 16, roomHeight / 4 },
+               { roomWidth * 11 / 16, 0, roomWidth * 2 / 16, roomHeight / 4 },
+               { roomWidth * 3 / 16, 0, roomWidth * 2 / 16, roomHeight / 4 },
+               { roomWidth * 11 / 16, roomHeight * 3 / 4, roomWidth * 2 / 16, roomHeight / 4 },
+               { roomWidth * 3 / 16, roomHeight * 3 / 4, roomWidth * 2 / 16, roomHeight / 4 },
+       };
+
+       QPainter painter(this);
+       QPainter * dc = &painter;
+
+       painter.setRenderHint(QPainter::Antialiasing);
+
+#if 0
+       dc->save();
+       dc->setFont(font);
+       dc->setPen(QColor(0xFF, 0x7F, 0x00));
+//     dc->drawText(20, 50, QString("<%1, %2>").arg(mouse.x()).arg(mouse.y()));
+       dc->drawText(20, 50, QString("<%1, %2>, %3").arg(mouse.x()).arg(mouse.y()).arg(hoveredEdge));
+       dc->restore();
+#endif
+
+       QTransform t1;
+       t1.scale(zoom / 100.0, zoom / 100.0);
+//     t1.scale(+0.20, +0.20);
+//     t1.scale(+0.40, +0.40);
+       t1.translate(offset.x(), offset.y());
+       painter.setTransform(t1);
+#if 0
+//     QPainter painter(this);
+       painter.save();
+       painter.setPen(QPen(Qt::blue, 1, Qt::DashLine));
+       painter.drawRect(0, 0, 100, 100);
+
+       QTransform transform;
+       transform.translate(50, 50);
+       transform.rotate(45);
+       transform.scale(0.5, -1.0);
+       painter.setTransform(transform);
+
+       painter.setFont(QFont("Helvetica", 24));
+       painter.setPen(QPen(Qt::black, 1));
+       painter.drawText(20, 10, "QTransform");
+       painter.restore();
+#endif
+
+#if 0
+       QBrush focusBrush;
+       QPen penEdge;
+       QPen penGrid;
+       QPen penRoom;
+       LOGBRUSH lb;
+       lb.lbStyle = BS_SOLID;
+       lb.lbColor = RGB(0, 0, 0);
+
+       if (!focusBrush.CreateHatchBrush(HS_BDIAGONAL, PALETTERGB(128, 128, 128))
+               || !penEdge.CreatePen(PS_GEOMETRIC | PS_ENDCAP_FLAT, penEdgeWidth, &lb)
+               || !penGrid.CreatePen(PS_DOT, 0, RGB(0, 0, 0))
+               || !penRoom.CreatePen(PS_SOLID, penRoomWidth, RGB(0, 0, 0)))
+               return;
+
+       QBrush * const whiteBrush = dc->GetCurrentBrush();
+       QPen * const oldPen = dc->GetCurrentPen();
+       QFont * const oldFont = dc->SelectObject(&font);
+       const unsigned int oldAlign = dc->SetTextAlign(TA_CENTER | TA_BASELINE | TA_NOUPDATECP);
+#else
+       QBrush focusBrush(QColor(0xC0, 0xC0, 0xC0), Qt::BDiagPattern);
+       QPen penEdge(QColor(0, 0, 0), penEdgeWidth);
+       QPen penEdgeDashed(QColor(0, 0, 0), penEdgeWidth, Qt::DashLine);
+       QPen penEdgeHover(QColor(0xFF, 0, 0), penEdgeWidth);
+       QPen penEdgeDashedHover(QColor(0xFF, 0, 0), penEdgeWidth, Qt::DashLine);
+       QPen penGrid(QColor(0, 0, 0), 1, Qt::DotLine);
+       QPen penRoom(QColor(0, 0, 0), 1);
+       dc->setFont(font);
+#endif
+
+       if (printingPage)
+       {
+               // Clipping in OnPrepareDC screws up print preview
+               QRect r = doc->getPageRect(printingPage - 1);
+///            dc->SetWindowOrg(r.left(), r.bottom()); // FIXME adjust for margins
+///            dc->IntersectClipRect(&r);
+//             dc->setTransform(QTransform::translate(r.left(), r.bottom()));
+       }
+
+#if 0
+       // Draw the page outlines:
+       if ((showPages || numSelectedPages) && !dc->IsPrinting())
+       {
+               dc->SetBkMode(TRANSPARENT);
+               QPen penPage;
+               lb.lbColor = RGB(192, 192, 192);
+               VERIFY(penPage.CreatePen(PS_GEOMETRIC | PS_ENDCAP_FLAT, penPageWidth, &lb));
+               dc->SelectObject(&penPage);
+               const PageVec & pageVec = doc->page;
+               TCHAR pageNum[4];
+               int pNum = 1;
+               dc->SelectStockObject(NULL_BRUSH);
+               QBrush * const nullBrush = dc->GetCurrentBrush();
+               bool focused = false;
+
+               for(PageConstItr p=pageVec.begin(); p!=pageVec.end(); ++p, ++pNum)
+               {
+                               doc->getPageRect(*p, r);
+
+                       if (dc->RectVisible(r))
+                       {
+                               const bool selectedI = selectedPage[pNum - 1] != 0;
+
+                               if (!selectedI && !showPages)
+                                       continue; // Only show selected pages
+
+                               if (selectedI != focused)
+                               {
+                                       focused = selectedI;
+                                       dc->SelectObject(focused ? &focusBrush : nullBrush);
+                               }
+///    CRgn inside,outside;
+///    VERIFY(outside.CreateRectRgnIndirect(&r));
+///    VERIFY(inside.CreateRectRgn(r.left + gridX, r.bottom - gridY,
+///                                r.right - gridX, r.top + gridY));
+///    outside.CombineRgn(&outside, &inside, RGN_DIFF);
+///    dc->FillRgn(&outside, &focusBrush);
+
+                               dc->Rectangle(r.left + penPageWidth / 4, r.bottom - penPageWidth / 4, r.right, r.top);
+                               ASSERT(pNum < 1000);
+                               _itot(pNum, pageNum, 10);
+                               const int pageNumL = strlen(pageNum);
+
+                               for(i=r.left+3*gridX/2; i<r.right-gridX; i+=gridX)
+                               {
+                                       dc->TextOut(i, r.bottom - gridY * 7 / 8, pageNum, pageNumL);
+                                       dc->TextOut(i, r.top + gridY / 4, pageNum, pageNumL);
+                               }
+
+                               for(i=r.bottom-gridY*15/8; i>r.top+gridY; i-=gridY)
+                               {
+                                       dc->TextOut(r.left + gridX / 2 + penPageWidth / 4, i, pageNum, pageNumL);
+                                       dc->TextOut(r.right- gridX / 2 - penPageWidth / 4, i, pageNum, pageNumL);
+                               }
+                       } // end if page visible
+               } // end for pages
+
+               dc->SetBkMode(OPAQUE);
+               dc->SelectObject(whiteBrush);
+               dc->SelectObject(&penEdge); // Before destroying penPage
+       }
+       else // not showing pages
+               dc->SelectObject(&penEdge);
+#else
+       dc->setPen(penEdge);
+#endif
+
+       // Draw the background grid:
+       if (showGrid)// && (zoom >= minZoomGrid))//&& !dc->IsPrinting())
+       {
+#if 0
+printf("  Preparing to draw grid...\n");
+//             dc->SetBkMode(TRANSPARENT);
+//             dc->SelectObject(&penGrid);
+               dc->setBrush(Qt::NoBrush);
+               dc->setPen(penGrid);
+
+               QRect clip;
+//             dc->GetClipBox(&clip);
+//             clip = dc->viewport();
+//             clip = dc->window();
+               clip = geometry();
+printf("    clip = %i, %i, %i, %i\n", clip.x(), clip.y(), clip.width(), clip.height());
+
+               clip.setLeft(clip.left() - (clip.left() % gridX));
+               clip.setBottom(clip.bottom() - (clip.bottom() % gridY + gridY));
+
+               if (clip.top() < -gridY)
+                       clip.setTop(clip.top() + gridY);
+
+               QSize docSize(doc->getDocSize());
+               docSize.setHeight(docSize.height() * -1);
+
+               if (clip.right() > docSize.width())
+                       clip.setRight(docSize.width());
+
+               if (clip.bottom() < docSize.height())
+                       clip.setBottom(docSize.height());
+
+printf("    clip = %i, %i, %i, %i; docSize = %i, %i\n", clip.left(), clip.right(), clip.bottom(), clip.top(), docSize.width(), docSize.height());
+               for(i=clip.left(); i<=clip.right(); i+=gridX)
+               {
+                       dc->drawLine(i, 0, i, docSize.height());
+               }
+
+//             for(i=clip.bottom(); i<=clip.top(); i+=gridY)
+               for(i=-clip.bottom(); i<=clip.top(); i+=gridY)
+               {
+//                     dc->drawLine(0, i, docSize.width(), i);
+                       dc->drawLine(0, i, docSize.width(), i);
+               }
+
+//             dc->SetBkMode(OPAQUE);
+#else
+               dc->setBrush(Qt::NoBrush);
+               dc->setPen(penGrid);
+               QSize docSize(doc->docSize);
+
+               for(int i=0; i<=docSize.width(); i+=gridX)
+                       dc->drawLine(i, 0, i, docSize.height());
+
+               for(int i=0; i<=docSize.height(); i+=gridY)
+                       dc->drawLine(0, i, docSize.width(), i);
+#endif
+       }
+
+       // Draw the room connections
+       int i = doc->edge.size();
+       EdgeConstItr edge = doc->edge.end() - 1;
+       RoomConstItr room = doc->room.v.begin();
+       dc->setPen(penEdge);
+
+       while (i-- > 0)
+       {
+               QPoint start, end;
+
+               doc->getEdgePoints(*edge, start, end);
+
+               // Offset the edge if we're dragging it...
+               if (opInProgress >= gmoDrag)
+               {
+                       if (selected[edge->room1])
+                               start += p2Tmp;
+
+                       if ((selected[edge->room1] && (edge->type1 & etNoRoom2))
+                               || selected[edge->room2])
+                               end += p2Tmp;
+               }
+
+               if (edge->type1 & etObstructed)
+                       dc->setPen(hoveredEdge == i ? penEdgeDashedHover : penEdgeDashed);
+               else
+                       dc->setPen(hoveredEdge == i ? penEdgeHover : penEdge);
+
+               dc->drawLine(start, end);
+
+               if (edge->type1 & etOneWay)
+               {
+                       dc->save();
+                       dc->setBrush(Qt::black);
+                       DrawArrowhead(dc, end, start);
+                       dc->restore();
+               }
+               else if (edge->type1 & etNoExit)
+               {
+                       DrawNoExit(dc, end, start);
+               }
+               else if (edge->type1 & etLoopBack)
+               {
+                       const double angle = Angle(start - end);
+                       const double orthoAngle = angle + QTR_TAU;
+                       const double size = 5.0;
+
+                       QPointF ortho(cos(orthoAngle), sin(orthoAngle));
+                       QPointF unit = UnitVector(end - start);
+
+                       QPointF p1 = start + (ortho * 6.0 * size);
+                       QPointF p2 = end + (ortho * 6.0 * size);
+                       QPointF p3 = end + (ortho * 3.0 * size);
+                       p3.rx() -= 3.0 * size;
+                       p3.ry() -= 3.0 * size;
+                       dc->drawLine(p1, p2);
+                       QRectF r(p3, QSizeF(6.0 * size, 6.0 * size));
+                       // Because painter.drawArc() uses a Cartesian (non-Y-inverted) coordinate system to determine its angles, we have to invert our Y-coords to get correct angles to feed to it.  :-/
+                       QPoint funnyStart(start.x(), start.y() * -1), funnyEnd(end.x(), end.y() * -1);
+                       double funnyAngle = Angle(funnyStart - funnyEnd);
+                       dc->drawArc(r, (int)((funnyAngle + QTR_TAU) * RADIANS_TO_DEGREES) * 16, 180 * 16);
+                       dc->save();
+                       dc->setBrush(Qt::black);
+                       DrawArrowhead(dc, p1, p2);
+                       dc->restore();
+               }
+
+               char elBuf[2] = "?";
+
+               if (edge->type1 & etDirection)
+               {
+                       const QPoint & p = room[edge->room1]->pos;
+//                     dc->TextOut(p.x + edgLblXOffset[edge->end1], p.y + edgLblYOffset[edge->end1], &edgeLabel[(edge->type1 & etDirection) - 1], 1);
+                       elBuf[0] = edgeLabel[(edge->type1 & etDirection) - 1];
+                       dc->drawText(p + edgLblOffset[edge->end1], elBuf);
+               }
+
+               if (edge->type2 & etDirection)
+               {
+                       const QPoint & p = room[edge->room2]->pos;
+//                     dc->TextOut(p.x + edgLblXOffset[edge->end2], p.y + edgLblYOffset[edge->end2], &edgeLabel[(edge->type2 & etDirection) - 1], 1);
+                       elBuf[0] = edgeLabel[(edge->type2 & etDirection) - 1];
+                       dc->drawText(p + edgLblOffset[edge->end2], elBuf);
+               }
+
+               edge--;
+       }
+
+       // Draw the rooms
+       dc->setPen(penRoom);
+       bool focused = false;
+       i = 0;
+
+       for(const int count=doc->room.size(); i<count; i++, room++)
+       {
+               QRect r = (*room)->getRect();
+               QPoint cp = (*room)->GetCenter();
+
+               // Translate room if dragging operation in progress
+               if ((opInProgress >= gmoDrag) && selected[i])// && !(*room)->isCorner())
+               {
+                       r.translate(p2Tmp.x(), p2Tmp.y());
+                       cp += p2Tmp;
+               }
+
+//             if (!dc->RectVisible((*room)->getRect(r)))
+//             if (!r.intersects(dc->viewport()))
+//                     continue;
+
+               const bool selectedI = (selected[i] != 0);
+
+               if (selectedI != focused)
+                       focused = selectedI;
+
+               bool hovered = r.contains(mouse);
+               RoomCorner rc = rcNone;
+
+               if (hovered)
+                       rc = doc->CornerHit(mouse - r.topLeft());
+
+               dc->setPen(hovered ? penEdgeHover : penRoom);
+
+               dc->setBrush(focused ? focusBrush : Qt::white);
+
+               bool needOpaqueText = false;
+
+               if ((*room)->flags & rfBorder)
+               {
+                       dc->drawRect(r);
+               }
+               else if (focused)
+               {
+                       dc->setBrush(focusBrush);
+                       dc->drawRect(r);
+               }
+               else // Room has no border and is not focused
+                       needOpaqueText = !(*room)->name.empty();
+
+               if ((*room)->isCorner() && (focused || showCorners))
+               {
+                       QRect centerRect(cp - QPoint(20, 20), QSize(40, 40));
+                       dc->save();
+                       dc->setPen(Qt::black);
+                       dc->setBrush(Qt::black);
+                       dc->drawEllipse(centerRect);
+                       dc->restore();
+               }
+
+//             dc->SetBkMode(TRANSPARENT);
+
+               if ((rc >= 0) && (rc <= 11) && ((*room)->isCorner() == false))
+               {
+                       QRect rCrnr = cornerRect[rc].translated(r.topLeft());
+//                     rCrnr.translate(r.topLeft());
+                       dc->save();
+                       dc->setPen(Qt::NoPen);
+                       dc->setBrush(Qt::green);
+                       dc->drawRect(rCrnr);
+                       dc->restore();
+               }
+
+               if (!(*room)->note.empty())
+               {
+                       QPoint noteOffset(roomWidth - 29, 57);
+
+                       dc->save();
+                       dc->setPen(QColor(0xFF, 0x00, 0x00));
+                       dc->drawText(r.topLeft() + noteOffset, "*");
+                       dc->restore();
+               }
+
+//             if (needOpaqueText)
+//                     dc->SetBkMode(OPAQUE);
+
+               // Shrink the rect left/right margins just a bit
+               QMargins margin(20, 0, 20, 0); // LTRB
+               r = r.marginsRemoved(margin);
+//dc->save();
+//dc->setPen(Qt::blue);
+//dc->drawRect(r);
+//dc->restore();
+
+               dc->drawText(r, Qt::AlignCenter | Qt::TextWordWrap, (*room)->name.c_str());
+
+//             dc->SetBkMode(OPAQUE);
+       }
+
+       // JLH: Draw special stuffs (crap that was stuffed into OnMouseDown & OnMouseMove & the like...
+       if (opInProgress == gmoSelectBox)
+       {
+               dc->setPen(QPen(QColor(0x00, 0xFF, 0x00, 0xFF)));
+               dc->setBrush(QBrush(QColor(0x00, 0xFF, 0x00, 0x64)));
+               dc->drawRect(rTmp);
+       }
+       else if (opInProgress == gmoAddEdge)
+       {
+               dc->setPen(penEdge);
+               dc->drawLine(p1Tmp, p2Tmp);
+       }
+}
+
+
+void MapView::keyPressEvent(QKeyEvent * event)
+{
+       bool oldShift = shiftDown;
+       bool oldCtrl = ctrlDown;
+       bool oldAlt = altDown;
+
+       if (event->key() == Qt::Key_Shift)
+               shiftDown = true;
+       else if (event->key() == Qt::Key_Control)
+               ctrlDown = true;
+       else if (event->key() == Qt::Key_Alt)
+               altDown = true;
+
+       double oldZoom = zoom;
+
+       if (event->key() == Qt::Key_0)
+               zoom = 100.0;
+       else if (event->key() == Qt::Key_9)
+               zoom = 90.0;
+       else if (event->key() == Qt::Key_8)
+               zoom = 80.0;
+       else if (event->key() == Qt::Key_7)
+               zoom = 70.0;
+       else if (event->key() == Qt::Key_6)
+               zoom = 60.0;
+       else if (event->key() == Qt::Key_5)
+               zoom = 50.0;
+       else if (event->key() == Qt::Key_4)
+               zoom = 40.0;
+       else if (event->key() == Qt::Key_3)
+               zoom = 30.0;
+       else if (event->key() == Qt::Key_2)
+               zoom = 20.0;
+       else if (event->key() == Qt::Key_1)
+               zoom = 10.0;
+
+       if (zoom != oldZoom)
+               update();
+
+#if 0
+       if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
+       {
+               if (Global::tool)
+                       ToolHandler(ToolKeyDown, Point(0, 0));
+
+               update();
+       }
+#endif
+
+       if (oldAlt != altDown)
+       {
+               scrollDrag = true;
+               setCursor(Qt::SizeAllCursor);
+//             oldPoint = oldScrollPoint;
+       }
+
+#if 0
+       if (select.size() > 0)
+       {
+               if (event->key() == Qt::Key_Up)
+               {
+                       TranslateObjects(select, Point(0, +1.0));
+                       update();
+               }
+               else if (event->key() == Qt::Key_Down)
+               {
+                       TranslateObjects(select, Point(0, -1.0));
+                       update();
+               }
+               else if (event->key() == Qt::Key_Right)
+               {
+                       TranslateObjects(select, Point(+1.0, 0));
+                       update();
+               }
+               else if (event->key() == Qt::Key_Left)
+               {
+                       TranslateObjects(select, Point(-1.0, 0));
+                       update();
+               }
+       }
+#endif
+}
+
+
+void MapView::keyReleaseEvent(QKeyEvent * event)
+{
+       bool oldShift = shiftDown;
+       bool oldCtrl = ctrlDown;
+       bool oldAlt = altDown;
+
+       if (event->key() == Qt::Key_Shift)
+               shiftDown = false;
+       else if (event->key() == Qt::Key_Control)
+               ctrlDown = false;
+       else if (event->key() == Qt::Key_Alt)
+               altDown = false;
+
+#if 0
+       if ((oldShift != shiftDown) || (oldCtrl != ctrlDown))
+       {
+               if (Global::tool)
+                       ToolHandler(ToolKeyUp, Point(0, 0));
+
+               update();
+       }
+#endif
+
+       if (oldAlt != altDown)
+       {
+               scrollDrag = false;
+               setCursor(Qt::ArrowCursor);
+       }
+}
+
+
+#if 0
+/////////////////////////////////////////////////////////////////////////////
+// CMapView printing
+//--------------------------------------------------------------------
+// Record which page we're printing, if any:
+//
+// Output Variables:
+//   printingPage
+
+void MapView::OnPrepareDC(QPainter * dc, CPrintInfo * pInfo)
+{
+       CScrollZoomView::OnPrepareDC(dc, pInfo);
+
+       if (pInfo)
+               printingPage = pInfo->m_nCurPage;
+       else
+               printingPage = 0;
+}
+
+
+bool MapView::OnPreparePrinting(CPrintInfo * pInfo)
+{
+       // Require registration before printing map with more than 10 rooms:
+       MapDoc * const doc = GetDocument();
+       ASSERT_VALID(doc);
+
+       if (doc->needRepaginate())
+       {
+               CRepaginateDlg  d;
+
+               switch (d.DoModal())
+               {
+               case IDCANCEL:
+                       return false;
+               case IDYES:
+                       doc->layoutPages();
+                       break;
+               }
+       }
+
+       pInfo->SetMaxPage(doc->page.size());
+       return DoPreparePrinting(pInfo);
+}
+
+
+void MapView::OnBeginPrinting(QPainter * /*pDC*/, CPrintInfo * /*pInfo*/)
+{
+       // Don't show selection or corners while printing:
+       //   We do this here instead of OnPreparePrinting, because OnEndPrinting
+       //   is only guaranteed to be called if OnBeginPrinting is.
+       clearSelection();
+       selectDone();
+       showCorners = false;
+}
+
+
+void MapView::OnEndPrinting(QPainter* /*pDC*/, CPrintInfo* /*pInfo*/)
+{
+       if (GetDocument()->locked)
+               OnUpdate(NULL, dupNavigationMode, NULL); // Select a room
+}
+#endif
+
+
+//
+// CMapView miscellaneous
+//
+// Add a room:
+//
+// Input:
+//   point:  The point where the mouse was clicked (in logical coordinates)
+//
+void MapView::addRoom(QPoint & point)
+{
+       QSize docSize(doc->docSize);
+
+       if (doc->locked || (point.x() > docSize.width()) || (point.y() > docSize.height()))
+       {
+//             MessageBeep(MB_ICONASTERISK);
+               return;
+       }
+
+       point.rx() -= roomWidth / 2 - gridX / 2;
+       point.rx() -= point.x() % gridX;
+       point.ry() -= roomHeight / 2 - gridY / 2;
+       point.ry() -= point.y() % gridY;
+
+       if (point.x() < 0)
+               point.setX(0);
+       else if (point.x() + roomWidth > docSize.width())
+               point.setX(docSize.width() - roomWidth);
+
+       if (point.y() < 0)
+               point.setY(0);
+       else if (point.y() + roomHeight > docSize.height())
+               point.setY(docSize.height() - roomHeight);
+
+       clearSelection();
+       const bool wasModified = doc->isDirty;
+       const int rNum = doc->addRoom(point);
+
+       if (rNum >= 0)
+               selectRoom(rNum);
+
+       selectDone();               // FIXME disable floating comments
+       editProperties(epuAddRoom, wasModified);
+}
+
+
+//
+// Make sure we aren't dragging a room off the edge:
+//
+// Input:
+//   p:  The proposed offset for the selected rooms
+//
+// Input Variables:
+//   rTmp:  Rectangle enclosing the original positions of the selected rooms
+//
+// Output:
+//   p:  The corrected (if necessary) offset for selected rooms
+//
+void MapView::adjustOffset(QPoint & p) const
+{
+       const QSize size(doc->docSize);
+
+       if (p.x() + rTmp.left() < 0)
+               p.rx() = -rTmp.left();
+
+       if (p.x() + rTmp.right() > size.width())
+               p.rx() = size.width() - rTmp.right();
+
+       if (p.y() + rTmp.top() < 0)
+               p.ry() = -rTmp.top();
+
+       if (p.y() + rTmp.bottom() > size.height())
+               p.ry() = size.height() - rTmp.bottom();
+}
+
+
+//
+// Deselect all rooms:
+//
+// Input:
+//   update:  TRUE means the selected rooms should be redrawn
+//
+void MapView::clearSelection(bool update/* = true*/)
+{
+       if (doc->room.size() != selected.size())
+               selected.resize(doc->room.size(), 0);
+
+       if (update && numSelected)
+       {
+#if 0
+               QRect r;
+               CClientDC dc(this);
+               OnPrepareDC(&dc);
+               RoomConstItr room = doc->room.getVector().begin();
+
+               for(int i=doc->room.size()-1; numSelected && i>=0; --i)
+               {
+                       if (selected[i])
+                       {
+                               numSelected--;
+                               room[i]->getRect(r);
+                               dc.LPtoDP(r);
+                               r.NormalizeRect();
+                               InvalidateRect(r);
+                       }
+               }
+#endif
+       }
+
+       numSelected = 0;
+
+       for(ByteItr b=selected.begin(); b!=selected.end(); ++b)
+               *b = 0;
+
+       deselectPages(update);
+}
+
+
+//
+// Compute a rectangle enclosing all selected rooms:
+//
+// Input Variables:
+//   numSelected
+//   numSelectedPages
+//   selected
+//   selectedPage
+//
+// Output:
+//   r:  A rectangle enclosing all selected rooms
+//
+void MapView::computeSelectedRect(QRect & r) const
+{
+//     r.SetRectEmpty();
+       r = QRect();
+
+       if (numSelected)
+       {
+               RoomConstItr room = doc->room.getVector().begin();
+
+               for(int i=doc->room.size()-1; i>=0; i--)
+               {
+                       if (selected[i])
+                               r |= room[i]->getRect();
+               }
+       }
+
+       if (numSelectedPages)
+       {
+               for(int i=doc->page.size()-1; i>=0; i--)
+               {
+                       if (selectedPage[i])
+                               r |= doc->getPageRect(i);
+               }
+       }
+}
+
+
+//
+// Deselect a page:
+//
+// Input:
+//   n:       The page number to be deselected
+//   update:  TRUE means the page should be redrawn if necessary
+//
+// Output Variables:
+//   numSelectedPages
+//   selectedOne
+//   selectedPage
+//
+void MapView::deselectPage(int n, bool update/* = true*/)
+{
+       ASSERT((n >= 0) && (n < doc->page.size()));
+
+       if (selectedPage[n])
+       {
+               selectedPage[n] = false;
+
+               if ((--numSelectedPages == 0) && (numSelected == 1))
+               {
+                       for(selectedOne=0; !selected[selectedOne]; selectedOne++)
+                               ; // Find the selected room
+               }
+               else
+                       selectedOne = -1;
+
+#if 0
+               if (update)
+               {
+                       CClientDC dc(this);
+                       OnPrepareDC(&dc);
+                       QRect r;
+                       doc->getPageRect(n, r);
+                       dc.LPtoDP(r);
+                       r.NormalizeRect();
+                       InvalidateRect(r);
+               }
+#endif
+       }
+}
+
+
+//
+// Deselect all pages:
+//
+// Input:
+//   update:  TRUE means the selected pages should be redrawn
+//
+// Output Variables:
+//   numSelectedPages
+//   selectedOne
+//   selectedPage
+//
+void MapView::deselectPages(bool update/* = true*/)
+{
+       ASSERT(selectedPage.size() == doc->page.size());
+
+       if (update && numSelectedPages)
+       {
+               QRect r;
+//             CClientDC dc(this);
+//             OnPrepareDC(&dc);
+
+               for(int i=doc->page.size()-1; numSelectedPages&&i>=0; i--)
+               {
+                       if (selectedPage[i])
+                       {
+                               numSelectedPages--;
+                               r = doc->getPageRect(i);
+
+                               if (!showPages)
+                               {
+                                       // make sure it disappears
+                                       r.setBottom(r.bottom() + penPageWidth);
+                                       r.setLeft(r.left() - penPageWidth);
+                               }
+
+//                             dc.LPtoDP(r);
+//                             r.NormalizeRect();
+//                             InvalidateRect(r);
+                       }
+               }
+       }
+
+       numSelectedPages = 0;
+
+       for(ByteItr b=selectedPage.begin(); b!=selectedPage.end(); b++)
+               *b = 0;
+
+       if (numSelected == 1)
+       {
+               for(selectedOne=0; !selected[selectedOne]; selectedOne++)
+                       ; // Find the selected room
+       }
+       else
+               selectedOne = -1;
+}
+
+
+//
+// Deselect a room:
+//
+// Input:
+//   n:       The room number to be deselected
+//   update:  TRUE means the room should be redrawn if necessary
+//
+// Output Variables:
+//   numSelected
+//   selectedOne
+//   selected
+//
+void MapView::deselectRoom(RoomNum n, bool update/* = true*/)
+{
+       ASSERT((n >= 0) && (n < doc->room.size()));
+
+       if (doc->room.size() != selected.size())
+               selected.resize(doc->room.size(), 0);
+
+       if (selected[n])
+       {
+               selected[n] = false;
+
+               if ((--numSelected == 1) && !numSelectedPages)
+               {
+                       for (selectedOne=0; !selected[selectedOne]; selectedOne++)
+                               ; // Find the selected room
+               }
+               else
+                       selectedOne = -1;
+
+#if 0
+               if (update)
+               {
+                       CClientDC dc(this);
+                       OnPrepareDC(&dc);
+                       QRect r;
+                       doc->room[n].getRect(r);
+                       dc.LPtoDP(r);
+                       r.NormalizeRect();
+                       InvalidateRect(r);
+               }
+#endif
+       }
+}
+
+
+//
+// Update the room comments dialog after the selection changes:
+//
+void MapView::selectDone()
+{
+//     static_cast<CMapApp *>(AfxGetApp())->setComment(this, (selectedOne >= 0 ? &(GetDocument()->getRoom(selectedOne)) : NULL));
+}
+
+
+//
+// Select a page:
+//
+// Input:
+//   n:       The page number to select
+//   update:  TRUE means the selected page should be redrawn if necessary
+//
+// Output Variables:
+//   numSelectedPages
+//   selectedOne
+//   selectedPage
+//
+void MapView::selectPage(int n, bool update/* = true*/)
+{
+       if (!selectedPage[n])
+       {
+               selectedPage[n] = true;
+               numSelectedPages++;
+               selectedOne = -1;
+
+#if 0
+               if (update)
+               {
+                       CClientDC dc(this);
+                       OnPrepareDC(&dc);
+                       QRect r;
+                       doc->getPageRect(n, r);
+                       dc.LPtoDP(r);
+                       r.NormalizeRect();
+                       InvalidateRect(r);
+               }
+#endif
+       }
+}
+
+
+//
+// Select a room:
+//
+// Input:
+//   n:       The room number to select
+//   update:  TRUE means the selected room should be redrawn if necessary
+//
+// Output Variables:
+//   numSelected
+//   selectedOne
+//   selected
+//
+void MapView::selectRoom(RoomNum n, bool update/*= true*/)
+{
+       if (doc->room.size() != selected.size())
+               selected.resize(doc->room.size(), 0);
+
+       if (!selected[n])
+       {
+               selected[n] = true;
+
+               if ((++numSelected == 1) && !numSelectedPages)
+                       selectedOne = n;
+               else
+                       selectedOne = -1;
+
+#if 0
+               if (update)
+               {
+                       CClientDC dc(this);
+                       OnPrepareDC(&dc);
+                       QRect r;
+                       doc->room[n].getRect(r);
+                       dc.LPtoDP(r);
+                       r.NormalizeRect();
+                       InvalidateRect(r);
+               }
+#endif
+       }
+}
+
+
+void MapView::selectOnlyRoom(RoomNum n)
+{
+       if (selectedOne != n)
+       {
+               clearSelection();
+               selectRoom(n);
+               selectDone();
+       }
+}
+
+
+//
+// Make sure that a room is visible:
+//
+// Input:
+//   n:  The number of the room to be made visible
+//
+void MapView::makeRoomVisible(RoomNum n)
+{
+#if 0
+       QRect clientRect;
+       GetClientRect(&clientRect);
+       QSize viewSize(clientRect.right, clientRect.bottom);
+
+       CClientDC cdc(this);
+       OnPrepareDC(&cdc);
+       cdc.DPtoLP(&viewSize);
+
+       const QSize docSize(doc->getDocSize());
+       const QPoint curPos(getScrollPosition());
+       QRect roomRect;
+       doc->room[n].getRect(roomRect);
+
+       QPoint newPos(curPos);
+
+       if (roomRect.left - curPos.x < gridX)
+       {
+               newPos.x -= curPos.x - roomRect.left + gridX;
+
+               if (newPos.x < 0)
+                       newPos.x = 0;
+       }
+       else if (roomRect.right - curPos.x + gridX > viewSize.cx)
+       {
+               newPos.x += roomRect.right + gridX - viewSize.cx - curPos.x;
+
+               if (newPos.x + viewSize.cx > docSize.cx)
+                       newPos.x = docSize.cx - viewSize.cx;
+       }
+
+       if (curPos.y - roomRect.bottom < gridY)
+       {
+               newPos.y += roomRect.bottom + gridY - curPos.y;
+
+               if (newPos.y > 0)
+                       newPos.y = 0;
+       }
+       else if (curPos.y - roomRect.top + gridY > viewSize.cy)
+       {
+               newPos.y += roomRect.top + viewSize.cy - curPos.y - gridY;
+
+               if (viewSize.cy - newPos.y > docSize.cy)
+                       newPos.y = viewSize.cy - docSize.cy;
+       }
+
+       if (newPos != curPos)
+       {
+               scrollToPosition(newPos);
+               // Must adjust the room position because the DC won't be updated:
+               roomRect.OffsetRect(curPos.x - newPos.x, curPos.y - newPos.y);
+       }
+
+       // Make sure the floating comments dialog isn't in the way:
+       cdc.LPtoDP(&roomRect);
+       ClientToScreen(&roomRect);
+       roomRect.NormalizeRect();
+       static_cast<CMapApp *>(AfxGetApp())->adjustCommentPos(roomRect);
+#endif
+}
+
+
+//
+// Paste a string to the clipboard:
+//
+// Input:
+//   text:  The string to paste
+//
+// Returns:
+//   True:   String was successfully pasted
+//   False:  Could not paste (clipboard unavailable or out of memory)
+//
+bool MapView::pasteClipboard(const string & text)
+{
+#if 0
+       if (!OpenClipboard())
+               return false;
+
+       EmptyClipboard();
+
+       // Allocate a global memory object for the text
+       HGLOBAL mem = GlobalAlloc(GMEM_DDESHARE, text.length() + sizeof(TCHAR));
+
+       if (mem == NULL)
+       {
+               CloseClipboard();
+               return false;
+       }
+
+       // Lock the handle and copy the text to the buffer
+
+       strcpy(LPTSTR(GlobalLock(mem)), text.c_str());
+       GlobalUnlock(mem);
+
+       SetClipboardData(CFtrEXT, mem);  // Place the handle on the clipboard
+       CloseClipboard();
+#endif
+
+       return true;
+}
+
+
+//
+// Update the selected room's comment from the floating dialog:
+//
+// Input:
+//   comment:  The new room comment (must not be NULL)
+//
+void MapView::setRoomNote(const char * comment)
+{
+       ASSERT(selectedOne >= 0);
+
+       if (doc->room[selectedOne].note != comment)
+       {
+               doc->setUndoData(new UndoRoomInfo(*doc, selectedOne));
+               doc->setRoomNote(selectedOne, comment);
+       }
+}
+
+
+int MapView::FindHoveredEdge(void)
+{
+       for(int i=0; i<doc->edge.size(); i++)
+       {
+               QPoint ep1, ep2;
+
+               doc->getEdgePoints(doc->edge[i], ep1, ep2);
+
+               QPointF lineSegment = ep2 - ep1;
+               QPointF v1 = mouse - ep1;
+               QPointF v2 = mouse - ep2;
+               double t = ParameterOfLineAndPoint(ep1, ep2, mouse);
+               double distance;
+
+               if (t < 0.0)
+                       distance = Magnitude(v1);
+               else if (t > 1.0)
+                       distance = Magnitude(v2);
+               else
+                       // distance = ?Det?(ls, v1) / |ls|
+                       distance = fabs(Determinant(lineSegment, v1)
+                               / Magnitude(lineSegment));
+
+               if (distance < 20.0)
+                       return i;
+       }
+
+       // Didn't find a closely hovered edge
+       return -1;
+}
+
+
+//
+// Set the step size of the scroll bars:
+//
+void MapView::setScrollBars()
+{
+#if 0
+       QRect clientRect;
+       GetClientRect(&clientRect);
+       QSize viewSize(clientRect.right, clientRect.bottom);
+
+       CClientDC cdc(this);
+       OnPrepareDC(&cdc);
+       cdc.DPtoLP(&viewSize);
+
+       viewSize.cx -= gridX / 2;
+       viewSize.cx -= viewSize.cx % gridX + roomWidth;
+       viewSize.cx = max(viewSize.cx, gridX);
+
+       viewSize.cy -= gridY / 2;
+       viewSize.cy -= viewSize.cy % gridY + roomHeight;
+       viewSize.cy = max(viewSize.cy, gridY);
+
+       setPageStep(viewSize);
+#endif
+}
+
+
+//
+// Zoom the display:
+//
+// Input:
+//   newZoom:  The new zoom factor (in percent)
+//
+void MapView::zoomTo(short newZoom)
+{
+       if (opInProgress == gmoAddCorner)
+               opInProgress = gmoNone;
+
+       if (newZoom == zoom)
+               return;
+
+       if (opInProgress || scrollDrag || (newZoom < minZoom) || (newZoom > maxZoom))
+               ;//MessageBeep(MB_OK);
+       else
+       {
+//             setDisplayZoom(zoom = newZoom);
+
+               if (selectedOne >= 0)
+                       makeRoomVisible(selectedOne);
+
+               setScrollBars();
+//             GetParentFrame()->OnUpdateFrameTitle(TRUE);
+       }
+}
+
+
+//
+// Navigation:
+//---------------------------------------------------------------------------
+// Keyboard navigation:
+//
+// Input:
+//   corner:       The direction to move
+//   toggleStubs:  TRUE means adding an existing stub should delete it
+//
+// Input Variables:
+//   edgeTmp:      If type1 or type2 is nonzero, use that for a new edge
+//   selectedOne:  Must be a valid room number
+//
+void MapView::navigate(RoomCorner corner, bool toggleStubs/* = true*/)
+{
+       const short roomXoffset[] =
+       {
+               0, 0, gridX + roomWidth, -(gridX + roomWidth),
+               gridX + roomWidth, -(gridX + roomWidth), gridX + roomWidth, -(gridX + roomWidth)
+       }; // FIXME add NNE, etc.
+
+       const short roomYoffset[] =
+       {
+               gridY + roomHeight, -(gridY + roomHeight), 0, 0,
+               gridY + roomHeight, gridY + roomHeight, -(gridY + roomHeight), -(gridY + roomHeight)
+       }; // FIXME add NNE, etc.
+
+       MapEdge e;
+
+       ASSERT(selectedOne >= 0);
+
+       if (doc->room[selectedOne].isCorner())
+       {
+//             MessageBeep(MB_ICONASTERISK);
+               return;
+       }
+
+       // Look for an existing connection:
+       int eNum = doc->findEdge(selectedOne, corner, e);
+
+       if ((eNum >= 0) && !(e.type2 & etUnexplored))
+       {
+               // Found existing connection, and 2nd end is not explored
+               if (toggleStubs && (e.type2 & etNoExit))
+               {
+                       if (edgeTmp.type1 & etNoExit)
+                       {
+                               doc->setUndoData(new UndoDelete(doc->isDirty, doc->edge[eNum]));
+                       }
+                       else
+                       {
+                               doc->setUndoData(new UndoChangeEdge(doc->isDirty, doc->edge[eNum]));
+                               edgeTmp.room1 = selectedOne;
+                               edgeTmp.end1 = corner;
+                               doc->addEdge(edgeTmp);
+                       }
+
+                       doc->deleteEdge(eNum);
+                       return;
+               }
+
+               if (edgeTmp.type1 || edgeTmp.type2 || (e.type1 & etOneWay)
+                       || (e.type2 & etNoRoom2))
+               {
+                       // We were trying to add connection
+//                     MessageBeep(MB_ICONASTERISK);
+                       return;
+               }
+
+               if (e.end1 == rcCorner)
+               {
+                       int r = doc->findOtherEnd(eNum);
+
+                       if (r >= 0)
+                               e.room1 = r;
+                       else
+                       {
+//                             MessageBeep(MB_ICONASTERISK);
+                               return;
+                       }
+               } // end if this connection leads to a corner
+
+               selectOnlyRoom(e.room1);
+               makeRoomVisible(e.room1);
+               eNum = -1;                  // Don't delete this edge
+       }
+       else // eNum < 0 || e.type2 & etUnexplored
+       {
+               // Try to add a new connection
+               if (doc->locked || (corner >= rcNNE))
+               {
+//                     MessageBeep(MB_ICONASTERISK);
+                       return;
+               }
+
+               const bool wasModified = doc->isDirty;
+               bool added = false;
+
+               // If adding stub where there's already a stub
+               if ((eNum >= 0) && (e.type2 & etUnexplored)
+                       && (edgeTmp.type1 & etUnexplored))
+               {
+                       if (toggleStubs)
+                       {
+                               // Remove stub connection:
+                               doc->setUndoData(new UndoDelete(wasModified, doc->edge[eNum]));
+                               doc->deleteEdge(eNum);
+                       }
+//                     else
+//                             MessageBeep(MB_ICONASTERISK);
+
+                       return;
+               }
+
+               e.room1 = selectedOne;
+               e.end1  = corner;
+
+               if (edgeTmp.type1 || edgeTmp.type2)
+               {
+                       e.type1 = edgeTmp.type1;
+                       e.type2 = edgeTmp.type2;
+               }
+               else
+                       setEdgeType(e);
+
+               EdgeVec deletedEdges;
+
+               // If there's a room #2 connected to this corner of the room...
+               if (!(e.type1 & etNoRoom2))
+               {
+                       QPoint pos(doc->room[selectedOne].pos);
+                       pos.rx() += roomXoffset[corner];
+                       pos.ry() += roomYoffset[corner];
+
+                       int room = doc->findRoom(pos);
+
+                       if (room < -1)
+                       {
+                               // We're off the grid
+//                             MessageBeep(MB_ICONASTERISK);
+                               return;
+                       }
+                       else if (room < 0)
+                       {
+                               // We need to add a new room
+                               room = doc->addRoom(pos);
+                               added = true;
+                       }
+                       else
+                       {
+                               // Existing room, check for existing connection
+                               MapEdge oEdge;
+                               int oeNum = doc->findEdge(room, oppositeCorner[corner], oEdge);
+
+                               if (oeNum >= 0)
+                               {       // There is an existing connection
+                                       if (oEdge.type2 & etUnexplored)
+                                       {
+                                               deletedEdges.push_back(doc->edge[oeNum]);
+                                               doc->deleteEdge(oeNum);
+
+                                               if (eNum > oeNum)
+                                                       eNum--; // The edge number might have changed
+                                       }
+                                       else
+                                               room = -1; // Room has a non-stub connection there
+                               }
+                       }
+
+                       if (room < 0)
+                       {
+//                             MessageBeep(MB_ICONASTERISK);
+                               return;
+                       }
+
+                       selectOnlyRoom(room);
+                       makeRoomVisible(room);
+
+                       e.room2 = room;
+                       e.end2 = oppositeCorner[corner];
+               }
+
+               if (eNum >= 0)
+               {
+                       deletedEdges.push_back(doc->edge[eNum]);
+                       doc->deleteEdge(eNum);
+               }
+
+               doc->addEdge(e);
+
+#if 0
+               if (added)
+               {
+                       // Enter room name
+                       if (gueApp()->editAfterAdd()) //editAfterAdd() returns autoEdit
+                               editProperties(epuAddRoomEdge, wasModified, (deletedEdges.size() ? &deletedEdges : NULL));
+                       else if (deletedEdges.size())
+                               doc->setUndoData(new UndoChanges(wasModified, selectedOne, 1, deletedEdges));
+                       else
+                               doc->setUndoData(new UndoAdd(wasModified, selectedOne, 1));
+               }
+               else if (deletedEdges.size())
+               {
+                       if (deletedEdges.size() == 1)
+                               doc->setUndoData(new UndoChangeEdge(wasModified, deletedEdges[0]));
+                       else
+                               doc->setUndoData(new UndoChanges(wasModified, -1, 1, deletedEdges));
+               }
+               else
+                       doc->setUndoData(new UndoAdd(wasModified));  // Undo new edge
+#endif
+       }
+}
+
+
+//
+// Fill in the edge type from the navigation box:
+//
+// If the text in the navigation bar is up, down, in, or out, then set
+// the edge type appropriately.  Otherwise, set the edge type to normal.
+// Always clears the navigation bar text.
+//
+// Can only create up-down and in-out passages.
+//
+// Output:
+//   e.type1 and e.type2
+//
+void MapView::setEdgeType(MapEdge & e)
+{
+       e.type1 = e.type2 = etNormal;
+
+       RoomCorner corner1, corner2;
+       char initial, separator;
+       getNavText(initial, corner1, separator, corner2);
+
+       if (initial && initial != '%' && initial != '>')
+       {
+//             MessageBeep(MB_ICONASTERISK);
+               return;
+       }
+
+       if (separator)
+       {
+               switch (corner1)
+               {
+               case rcUp:   e.type1 = etUp;   break;
+               case rcDown: e.type1 = etDown; break;
+               case rcIn:   e.type1 = etIn;   break;
+               case rcOut:  e.type1 = etOut;  break;
+               }
+
+               if (separator == '-')
+               {
+                       switch (corner2)
+                       {
+                       case rcUp:   e.type2 = etUp;   break;
+                       case rcDown: e.type2 = etDown; break;
+                       case rcIn:   e.type2 = etIn;   break;
+                       case rcOut:  e.type2 = etOut;  break;
+                       }
+               }
+       }
+       else // standard edge
+       {
+               switch (corner1)
+               {
+               case rcUp:   e.type1 = etUp;   e.type2 = etDown; break;
+               case rcDown: e.type1 = etDown; e.type2 = etUp;   break;
+               case rcIn:   e.type1 = etIn;   e.type2 = etOut;  break;
+               case rcOut:  e.type1 = etOut;  e.type2 = etIn;   break;
+               }
+       }
+
+       if (initial == '%')
+               e.type1 |= etObstructed;
+
+       if (initial == '>' || separator == '>')
+               e.type1 |= etOneWay;
+
+       if (initial == '?' || separator == '?')
+       {
+               e.type1 &= ~etSpecial;
+               e.type1 |= etUnexplored;
+               e.room2 = 0;
+               e.end2 = rcSW;
+       }
+       else if (initial == '!' || separator == '!')
+       {
+               e.type1 &= ~etSpecial;
+               e.type1 |= etNoExit;
+               e.room2 = 0;
+               e.end2 = rcSW;
+       }
+}
+
+
+//
+// Start typing in the navigation bar:
+//
+// Stuffs the given char in the navigation bar and sets focus to there.
+//
+// Input:
+//   c:  The character to put in the navigation bar.
+//
+void MapView::setNavText(char c)
+{
+#if 0
+       CDialogBar & nb = static_cast<CMainFrame *>(AfxGetMainWnd())->wndNavBar;
+
+       string text(c);
+       nb.SetDlgItemText(IDC_NAV_DIR, text);
+       nb.SetFocus();
+       CEdit * dir = static_cast<CEdit *>(nb.GetDlgItem(IDC_NAV_DIR));
+       dir->SetSel(1, 1);
+       dir->SetSel(-1, 2);
+#endif
+}
+
+
+#if 0
+void MapView::OnChar(unsigned int c, unsigned int nRepCnt, unsigned int nFlags)
+{
+       if (((c >= 'A') && (c <= 'Z'))
+               || ((c >= 'a') && (c <= 'z'))
+               || (c == '-') || (c == '!') || (c == '?') || (c == '/')
+               || (c == '>') || (c == '.') || (c == '~') || (c == '`')
+               || (c == '%'))
+               setNavText(c);
+}
+#endif
+
+
+//
+// Parse the text in the navigation bar:
+//
+// Input:
+//   mustBeMove: (default FALSE)
+//     If TRUE, beep and don't clear text if not a direction.
+//     If FALSE, clear text and don't beep even if it's meaningless.
+//
+// Output:
+//   initial:
+//     The non-alphabetic character beginning the text (0 if none)
+//   dir1:
+//     The direction indicated by the first direction word
+//     rcNone if no direction words
+//   separator:
+//     The non-alphabetic character following the first direction word
+//     0 if none
+//     Must be either '-', '>', or '?'
+//   dir2:
+//     The direction indicated by the second direction word
+//     rcNone if no second direction
+//
+void MapView::getNavText(char & initial, RoomCorner & dir1, char & separator, RoomCorner & dir2, bool mustBeMove)
+{
+#if 0
+       CDialogBar & nb = static_cast<CMainFrame *>(AfxGetMainWnd())->wndNavBar;
+       string text;
+
+       nb.GetDlgItemText(IDC_NAV_DIR, text);
+       text.MakeLower();
+
+       dir1 = dir2 = rcNone;
+       initial = separator = '\0';
+
+       if (!text.IsEmpty() && (text[0] < 'a' || text[0] > 'z'))
+       {
+               initial = text[0];
+               text = text.Mid(1);
+       }
+
+       if (initial == '-')
+       {
+               initial = '\0';
+               separator = '-';
+       }
+       else if (!text.IsEmpty() && (text[0] == '-' || text[0] == '>'))
+       {
+               separator = text[0];
+               text = text.Mid(1);
+       }
+       else
+       {
+               dir1 = parseDirection(text);
+
+               if (!text.IsEmpty() && (text[0]=='-' || text[0]=='>' || text[0]=='?'))
+               {
+                       separator = text[0];
+                       text = text.Mid(1);
+               }
+       }
+
+       if (separator)
+               dir2 = parseDirection(text);
+
+       if (mustBeMove && (dir1 == rcNone || separator == '-' || ((initial || separator) && (dir1 >= rcNumCorners))))
+               MessageBeep(MB_ICONASTERISK);
+       else
+       {
+               nb.SetDlgItemText(IDC_NAV_DIR, tr(""));
+               SetFocus();
+       }
+#endif
+}
+
+
+#if 0
+//
+// Handle the Go button in the navigation bar:
+//
+void MapView::OnNavGo()
+{
+       if (selectedOne < 0)
+       {
+               MessageBeep(MB_ICONASTERISK);
+               return;
+       }
+
+       RoomCorner corner, junk2;
+       char initial, separator;
+       getNavText(initial, corner, separator, junk2, true);
+
+       if (separator == '-' || junk2 != rcNone)
+       {
+               MessageBeep(MB_ICONASTERISK);
+               return;
+       }
+
+       if (corner != rcNone)
+       {
+               edgeTmp.type1 = edgeTmp.type2 = etNormal;
+
+               if (initial == '%')
+                       edgeTmp.type1 = etObstructed;
+
+               if (initial == '>' || separator == '>')
+                       edgeTmp.type1 |= etOneWay;
+
+               if (initial == '?' || separator == '?')
+                       edgeTmp.type1 = etUnexplored;
+               else if (initial == '!' || separator == '!')
+                       edgeTmp.type1 = etNoExit;
+
+               navigate(corner);
+       }
+}
+
+
+//
+// Handle the direction keys on the numeric keypad:
+//
+// Input:
+//   cmd:  Which command was executed
+//
+void MapView::OnNavGoDir(unsigned int cmd)
+{
+       ASSERT(cmd >= ID_NAV_GO_NORTH && cmd <= ID_NAV_STUB2_OUT);
+
+       if (selectedOne >= 0)
+       {
+               SetFocus();
+               edgeTmp.type1 = edgeTmp.type2 = etNormal;
+               bool toggleStubs = false;
+
+               if (cmd >= ID_NAV_STUB1_NORTH)
+               {
+                       if (cmd >= ID_NAV_STUB2_NORTH)
+                       {
+                               // Stub 2
+                               edgeTmp.type1 = (gueApp()->stub1Unexplored() ? etNoExit : etUnexplored);
+                               cmd -= ID_NAV_STUB2_NORTH - ID_NAV_GO_NORTH;
+                       }
+                       else
+                       {
+                               // Stub 1
+                               edgeTmp.type1 = (gueApp()->stub1Unexplored() ? etUnexplored : etNoExit);
+                               cmd -= ID_NAV_STUB1_NORTH - ID_NAV_GO_NORTH;
+                       }
+
+                       toggleStubs = true;
+               }
+
+               navigate(RoomCorner(cmd - ID_NAV_GO_NORTH), toggleStubs);
+       }
+       else
+               MessageBeep(MB_ICONASTERISK);
+}
+
+
+//
+// CMapView message handlers
+//---------------------------------------------------------------------------
+// Update the comments dialog when the view is activated:
+//
+void MapView::OnActivateView(bool bActivate, CView * pActivateView, CView * pDeactiveView)
+{
+       if (bActivate)
+               selectDone();  // Update comments dialog
+
+       CScrollZoomView::OnActivateView(bActivate, pActivateView, pDeactiveView);
+}
+#endif
+
+
+// N.B.: If you click on a corner with no edge coming out of it, it will crash here...  !!! FIX !!! [MOSTLY DONE--there's still a way to make it crash with "Add Corner", but I can't remember the specifics...]
+//void MapView::OnEditAddCorner()
+void MapView::HandleAddCorner(void)
+{
+//printf("MapView::HandleAddCorner... (iTmp=%i, opInProgress=%i)\n", iTmp, opInProgress);
+       ASSERT(opInProgress == gmoAddCorner);
+       ASSERT(selectedOne >= 0);
+
+       int room = doc->addCorner(selectedOne, iTmp);
+
+       if (room >= 0)
+               selectOnlyRoom(room);
+//     else
+//             MessageBeep(MB_ICONASTERISK);
+
+       opInProgress = gmoNone;
+       update();
+}
+
+
+void MapView::HandleAddUnexplored(void)
+{
+       EdgeVec deletedEdges;
+       const bool wasModified = doc->isDirty;
+       MapEdge e;
+
+       edgeTmp.room1 = roomClicked;
+       edgeTmp.end1 = cornerClicked;
+       // This is from MapView::setEdgeType()
+       edgeTmp.type1 &= ~etSpecial;
+       edgeTmp.type1 |= etUnexplored;
+       edgeTmp.room2 = 0;
+       edgeTmp.end2 = rcSW;
+
+       // Look for an existing connection:
+//     int eNum = doc->findEdge(selectedOne, cornerClicked, e);
+       int eNum = doc->findEdge(roomClicked, cornerClicked, e);
+
+       if (eNum >= 0)
+       {
+               deletedEdges.push_back(doc->edge[eNum]);
+               doc->deleteEdge(eNum);
+       }
+
+       doc->addEdge(edgeTmp);
+
+       if (deletedEdges.size())
+               doc->setUndoData(new UndoChangeEdge(wasModified, deletedEdges[0]));
+       else
+               doc->setUndoData(new UndoAdd(wasModified));  // Undo new edge
+
+       opInProgress = gmoNone;
+       update();
+}
+
+
+void MapView::HandleAddLoopBack(void)
+{
+       EdgeVec deletedEdges;
+       const bool wasModified = doc->isDirty;
+       MapEdge e;
+
+       edgeTmp.room1 = roomClicked;
+       edgeTmp.end1 = cornerClicked;
+       // This is from MapView::setEdgeType()
+       edgeTmp.type1 &= ~etSpecial;
+       edgeTmp.type1 |= etLoopBack;
+       edgeTmp.room2 = 0;
+       edgeTmp.end2 = rcSW;
+
+       // Look for an existing connection:
+//     int eNum = doc->findEdge(selectedOne, cornerClicked, e);
+       int eNum = doc->findEdge(roomClicked, cornerClicked, e);
+
+       if (eNum >= 0)
+       {
+               deletedEdges.push_back(doc->edge[eNum]);
+               doc->deleteEdge(eNum);
+       }
+
+       doc->addEdge(edgeTmp);
+
+       if (deletedEdges.size())
+               doc->setUndoData(new UndoChangeEdge(wasModified, deletedEdges[0]));
+       else
+               doc->setUndoData(new UndoAdd(wasModified));  // Undo new edge
+
+       opInProgress = gmoNone;
+       update();
+}
+
+
+void MapView::HandleAddNoExit(void)
+{
+       EdgeVec deletedEdges;
+       const bool wasModified = doc->isDirty;
+       MapEdge e;
+
+       edgeTmp.room1 = roomClicked;
+       edgeTmp.end1 = cornerClicked;
+       // This is from MapView::setEdgeType()
+       edgeTmp.type1 &= ~etSpecial;
+       edgeTmp.type1 |= etNoExit;
+       edgeTmp.room2 = 0;
+       edgeTmp.end2 = rcSW;
+
+       // Look for an existing connection:
+//     int eNum = doc->findEdge(selectedOne, cornerClicked, e);
+       int eNum = doc->findEdge(roomClicked, cornerClicked, e);
+
+       if (eNum >= 0)
+       {
+               deletedEdges.push_back(doc->edge[eNum]);
+               doc->deleteEdge(eNum);
+       }
+
+       doc->addEdge(edgeTmp);
+
+       if (deletedEdges.size())
+               doc->setUndoData(new UndoChangeEdge(wasModified, deletedEdges[0]));
+       else
+               doc->setUndoData(new UndoAdd(wasModified));  // Undo new edge
+
+       opInProgress = gmoNone;
+       update();
+}
+
+
+void MapView::SetEdgeDirection(EdgeType et)
+{
+       MapEdge e;
+
+       // Look for an existing connection:
+       int eNum = doc->findEdge(roomClicked, cornerClicked, e);
+
+       if (eNum >= 0)
+       {
+               // Is the room we clicked on room1 of this edge?
+               if (doc->edge[eNum].room1 == roomClicked)
+               {
+                       doc->edge[eNum].type1 &= ~etDirection;
+                       doc->edge[eNum].type1 |= et;
+               }
+               else
+               {
+                       doc->edge[eNum].type2 &= ~etDirection;
+                       doc->edge[eNum].type2 |= et;
+               }
+       }
+
+       opInProgress = gmoNone;
+       update();
+}
+
+
+void MapView::HandleAddUp(void)
+{
+       SetEdgeDirection(etUp);
+}
+
+
+void MapView::HandleAddDown(void)
+{
+       SetEdgeDirection(etDown);
+}
+
+
+void MapView::HandleAddIn(void)
+{
+       SetEdgeDirection(etIn);
+}
+
+
+void MapView::HandleAddOut(void)
+{
+       SetEdgeDirection(etOut);
+}
+
+
+void MapView::SetEdges(int room, int edgeNum, EdgeType eType)
+{
+/*
+OK, this is where the data structures let us down because they were poorly designed.  Apparently, we have to walk thru the Edge vector for EVERY edge that has a corner to get to the other side.  THIS LITERALLY FUCKING SUCKS.
+
+Algorithm:
+1) Figure out which side of the Edge has the room we're starting with.
+2) Using the other room # from the current Edge, loop thru the Edge vector to find the Edge connected to the room # we're looking at.
+3) Once the Edge is found, check to see if it's an rfCorner.
+   if so, go to 1)
+   else, we're done.
+
+Also, it seems that etOneWay only gets picked up by... type1.  Which means, you have to swap the edge to get the arrowhead to point the other way...
+Which means you could do away with type2, only it uses that to distinguish the Up/Down/In/Out annotations put on map edges...
+Bleah.
+
+One way to make that less awful is to overload the RoomCorner (which, for some reason, already has I/O/U/D in it as distinct members) so that it has bitfields reserved for labels attached...  Then EdgeType would really be an edge type and separate from the room exits.
+*/
+       // Go thru from current room to all connected "corner" rooms
+       while (true)
+       {
+               // Check for the unlikely case that we got passed bad info
+               if (doc->edge[edgeNum].HasRoom(room) == false)
+                       break;
+
+               if (doc->edge[edgeNum].room2 == room)
+                       doc->edge[edgeNum].Swap();
+
+               if (((eType == etOneWay) && (doc->edge[edgeNum].end2 != rcCorner))
+                       || (eType != etOneWay))
+                       doc->edge[edgeNum].type1 |= eType;
+
+               if (doc->edge[edgeNum].end2 != rcCorner)
+                       return;
+
+               // Find next Edge...  :-/
+               room = doc->edge[edgeNum].room2;
+
+               for(int i=0; i<doc->edge.size(); i++)
+               {
+                       // Exclude the edge we just came from...
+                       if (i == edgeNum)
+                               continue;
+
+                       if (doc->edge[i].HasRoom(room))
+                       {
+                               edgeNum = i;
+                               break;
+                       }
+               }
+       }
+}
+
+
+void MapView::ClearEdges(int room, int edgeNum, EdgeType eType)
+{
+       // Go thru from current room to all connected "corner" rooms
+       while (true)
+       {
+               // Check for the unlikely case that we got passed bad info
+               if (doc->edge[edgeNum].HasRoom(room) == false)
+                       break;
+
+               if (doc->edge[edgeNum].room2 == room)
+                       doc->edge[edgeNum].Swap();
+
+               doc->edge[edgeNum].type1 &= ~eType;
+
+               if (doc->edge[edgeNum].end2 != rcCorner)
+                       return;
+
+               // Find next Edge...  :-/
+               room = doc->edge[edgeNum].room2;
+
+               for(int i=0; i<doc->edge.size(); i++)
+               {
+                       // Exclude the edge we just came from...
+                       if (i == edgeNum)
+                               continue;
+
+                       if (doc->edge[i].HasRoom(room))
+                       {
+                               edgeNum = i;
+                               break;
+                       }
+               }
+       }
+}
+
+
+void MapView::HandleAddOneWay(void)
+{
+       MapEdge e;
+
+       // Look for an existing connection:
+       int eNum = doc->findEdge(roomClicked, cornerClicked, e);
+
+       if (eNum >= 0)
+               SetEdges(roomClicked, eNum, etOneWay);
+
+       opInProgress = gmoNone;
+       update();
+}
+
+
+void MapView::HandleClearOneWay(void)
+{
+       MapEdge e;
+
+       // Look for an existing connection:
+       int eNum = doc->findEdge(roomClicked, cornerClicked, e);
+
+       if (eNum >= 0)
+               ClearEdges(roomClicked, eNum, etOneWay);
+
+       opInProgress = gmoNone;
+       update();
+}
+
+
+void MapView::HandleAddRestricted(void)
+{
+       MapEdge e;
+
+       // Look for an existing connection:
+       int eNum = doc->findEdge(roomClicked, cornerClicked, e);
+
+       if (eNum >= 0)
+               SetEdges(roomClicked, eNum, etObstructed);
+
+       opInProgress = gmoNone;
+       update();
+}
+
+
+void MapView::HandleClearRestricted(void)
+{
+       MapEdge e;
+
+       // Look for an existing connection:
+       int eNum = doc->findEdge(roomClicked, cornerClicked, e);
+
+       if (eNum >= 0)
+               ClearEdges(roomClicked, eNum, etObstructed);
+
+       opInProgress = gmoNone;
+       update();
+}
+
+
+//void MapView::OnEditAddRoom()
+void MapView::HandleAddRoom(void)
+{
+       addRoom(scrollDragStart);
+}
+
+
+void MapView::HandleMapProperties(void)
+{
+       MapDialog dlg;
+
+       dlg.title.setText(doc->name.c_str());
+       dlg.comment.setPlainText(doc->note.c_str());
+       dlg.navMode.setChecked(doc->locked);
+       dlg.displayGrid.setChecked(showGrid);
+       dlg.showCorners.setChecked(showCorners);
+       dlg.showPages.setChecked(showPages);
+
+       if (dlg.exec() == false)
+               return;
+
+       doc->setName(dlg.title.text().toUtf8().data());
+       doc->setNote(dlg.comment.document()->toPlainText().toUtf8().data());
+       doc->locked = dlg.navMode.isChecked();
+       showGrid = dlg.displayGrid.isChecked();
+       showCorners = dlg.showCorners.isChecked();
+       showPages = dlg.showPages.isChecked();
+
+       if (dlg.title.text().isEmpty() == false)
+               setWindowTitle(dlg.title.text());
+
+       update();
+}
+
+
+//void MapView::OnEditClear()
+void MapView::HandleDelete(void)
+{
+       deleteSelection();
+       update();
+}
+
+
+#if 0
+//--------------------------------------------------------------------
+void MapView::OnEditAddPage()
+{
+       MapDoc * const doc = GetDocument();
+       ASSERT_VALID(doc);
+
+       const int p = doc->page.size();
+
+       if (p < maxPages)
+       {
+               MapPage page;
+               page.pos = scrollDragStart;
+               page.pos.x -= page.pos.x % gridX;
+               page.pos.y -= page.pos.y % gridY;
+               doc->setUndoData(new UndoAdd(doc->isDirty, p));
+               showPages = true;
+               doc->addPage(p, page);
+               clearSelection();
+               selectPage(p);
+       }
+}
+#endif
+
+
+//
+// Input:
+//   removeCorner: (default true)
+//     When TRUE, if a single corner is selected, remove it but leave
+//     the connection.  When FALSE, the connection is always removed.
+//
+void MapView::deleteSelection(bool removeCorner/* = true*/)
+{
+       if (numSelected || numSelectedPages)
+       {
+               if (doc->locked || (numSelectedPages == doc->page.size()))
+               {
+//                     MessageBeep(MB_ICONASTERISK);
+                       return;
+               }
+
+               if (removeCorner && (selectedOne >= 0)
+                       && doc->room[selectedOne].isCorner())
+               {
+                       // Replace this corner with a single edge:
+                       doc->deleteCorner(selectedOne);
+                       clearSelection(false);
+                       selectDone();
+                       return;
+               }
+               else
+               {
+                       // Select all corners if one corner or one end is selected:
+                       const EdgeVec & edge = doc->edge;
+                       int wereSelected = 0;
+
+                       while (wereSelected != numSelected)
+                       {
+                               wereSelected = numSelected;
+
+                               for(EdgeConstItr e=edge.begin(); e!=edge.end(); ++e)
+                               {
+                                       if ((e->end1 == rcCorner) && selected[e->room2] && !selected[e->room1])
+                                               selectRoom(e->room1, false);
+                                       else if ((e->end2==rcCorner) && selected[e->room1] && !selected[e->room2])
+                                               selectRoom(e->room2, false);
+                               }
+                       }
+               }
+
+               UndoDelete * undoRec = new UndoDelete(*doc, numSelected, selected, numSelectedPages, selectedPage);
+               opInProgress = gmoDeleting; // Don't clear selection after first room
+               int pos = numSelected;
+
+               for(int i=doc->room.size()-1; i>=0; i--)
+               {
+                       if (selected[i])
+                               undoRec->addRoom(--pos, i, doc->extractRoom(i));
+               }
+
+               for(int i=doc->page.size()-1; i>=0; i--)
+               {
+                       if (selectedPage[i])
+                               doc->deletePage(i);
+               }
+
+               selectedPage.resize(doc->page.size(), 0);
+               opInProgress = gmoNone;
+               doc->setUndoData(undoRec);
+
+               clearSelection(false);
+               selectDone();
+       }
+}
+
+
+#if 0
+//--------------------------------------------------------------------
+// Cut, Copy & Paste:
+
+void MapView::OnEditCopy()
+{
+       if (numSelected || numSelectedPages)
+       {
+               MapDoc * doc = GetDocument();
+               ASSERT_VALID(doc);
+
+               // Deselect all corner rooms:
+               for(int i=doc->room.size()-1; i>=0; --i)
+               {
+                       if (selected[i] && doc->room[i].isCorner())
+                               deselectRoom(i);
+               }
+
+               if (!numSelected && !numSelectedPages)
+               {
+                       selectDone();
+                       MessageBeep(MB_ICONASTERISK);
+                       return;
+               } // end if only corners were selected
+
+               // Select corners between selected rooms:
+               const EdgeVec& edges = doc->edge;
+
+               for (EdgeConstItr e = edges.begin(); e != edges.end(); ++e)
+               {
+                       if ((selected[e->room1] && (e->end2 == rcCorner)
+                               && (e->end1 != rcCorner) && !selected[e->room2])
+                               || (selected[e->room2] && (e->end1 == rcCorner)
+                               && (e->end2 != rcCorner) && !selected[e->room1]))
+                       {
+                               RoomNumVec cornerRooms;
+                               cornerRooms.reserve(16); // Should be plenty
+                               int otherEnd = doc->findOtherEnd(e, &cornerRooms);
+
+                               if ((otherEnd >= 0) && selected[otherEnd])
+                               {
+                                       for(RNConstItr rn=cornerRooms.begin(); rn!=cornerRooms.end(); ++rn)
+                                               selectRoom(*rn);
+                               }
+                       } // end if edge connects selected room to unselected corner
+               }
+
+               selectDone();
+
+               // Copy selected rooms and pages:
+               gueApp()->setClipboard(new RoomScrap(*doc, numSelected, selected, numSelectedPages, selectedPage));
+       }
+}
+
+
+void MapView::OnEditCut()
+{
+       OnEditCopy();
+       deleteSelection();
+}
+
+
+void MapView::OnEditPaste()
+{
+       const RoomScrap * scrap = static_cast<CMapApp *>(AfxGetApp())->getClipboard();
+
+       if (scrap)
+       {
+               MapDoc * doc = GetDocument();
+               ASSERT_VALID(doc);
+
+               int i = doc->room.size();
+
+               doc->setUndoData(scrap->paste(*doc));
+               const int numRooms = doc->room.size();
+
+               if (numRooms > i)
+                       makeRoomVisible(i);
+
+               clearSelection(true);
+
+               while (i < numRooms)
+                       selectRoom(i++, true);
+
+               selectDone();
+       }
+}
+
+
+void MapView::OnEditPaginate()
+{
+       showPages = true;
+       GetDocument()->layoutPages();
+}
+#endif
+
+
+//
+// Handle Edit Properties:
+//
+void MapView::HandleRoomProperties(void)
+{
+//     editProperties((cmd == ID_EDIT_MAP_PROPERTIES) ? epuMapInfo : epuRoomInfo);
+       editProperties(epuRoomInfo);
+}
+
+
+//
+// Bring up the Properties dialog:
+//
+// Input:
+//   undoType:
+//     The type of action we should create an undo record for
+//       epuRoomInfo:     Just changing room info
+//       epuAddRoom:      Adding a new room
+//       epuAddRoomEdge:  Adding a new room and a new edge
+//   wasModified:
+//     The modification state of the document
+//       (Necessary only if undoType is not epuRoomInfo)
+//   edges: (default NULL)
+//     A pointer to the edges that were deleted before adding this room
+//       NULL if no edges were deleted
+//       Must be NULL unless undoType is epuAddRoomEdge
+//       The vector is emptied but not deleted
+//
+void MapView::editProperties(EditPropUndo undoType, bool wasModified/*= false*/, EdgeVec * edges/*= NULL*/)
+{
+       bool forceMap = false;
+
+       if (undoType == epuMapInfo)
+       {
+               undoType = epuRoomInfo;
+               forceMap = true;
+       }
+
+//     CPropertySheet dlg(tr("Properties"));
+       RoomDialog dlg(this);
+//     CRoomPropPage room;
+       UndoRoomInfo * undoRoom = NULL;
+       const bool roomTab = (selectedOne >= 0) && !doc->room[selectedOne].isCorner();
+
+//     CMapPropPage map;
+//     Strcpy(map.name, doc->getName());
+//     Strcpy(map.note, doc->getNote());
+//     map.corners = showCorners;
+//     map.locked = doc->locked;
+//     map.grid = showGrid;
+//     map.pages = showPages;
+//     dlg.AddPage(&map);
+
+//     if (roomTab)
+       {
+//             VERIFY(undoRoom = new UndoRoomInfo(*doc, selectedOne));
+               undoRoom = new UndoRoomInfo(*doc, selectedOne);
+//             room.border = bool((undoRoom->getRoom().flags & rfBorder) != 0);
+//             Strcpy(room.name, undoRoom->getRoom().name);
+//             Strcpy(room.note, undoRoom->getRoom().note);
+//             dlg.AddPage(&room);
+//             static_cast<CMapApp *>(AfxGetApp())->setComment(this, NULL, false);
+               dlg.name.setText(undoRoom->room.name.c_str());
+               dlg.comment.insertPlainText(undoRoom->room.note.c_str());
+               dlg.border.setChecked(bool((undoRoom->room.flags & rfBorder) != 0));
+
+//             if (!forceMap)
+//                     dlg.SetActivePage(&room);
+       }
+
+//     if (dlg.DoModal() == IDOK)
+       if (dlg.exec())
+       {
+               if (roomTab)
+               {
+                       bool changed = false;
+
+//                     if (room.border != (0 != (undoRoom->getRoom().flags & rfBorder)))
+                       if (dlg.border.isChecked() != (0 != (undoRoom->room.flags & rfBorder)))
+                       {
+//                             doc->setRoomFlags(selectedOne, (room.border ? rfBorder : 0), rfBorder);
+                               doc->setRoomFlags(selectedOne, (dlg.border.isChecked() ? rfBorder : 0), rfBorder);
+                               changed = true;
+                       }
+
+//                     if (room.name != undoRoom->getRoom().name.c_str())
+                       if (dlg.name.text().toUtf8().data() != undoRoom->room.name.c_str())
+                       {
+//                             if (room.name.Find('\n') < 0)
+//                                     wrapRoomName(room.name);
+
+                               doc->setRoomName(selectedOne, dlg.name.text().toUtf8().data());
+                               changed = true;
+                       }
+
+//                     room.note.TrimRight();
+
+                       if (dlg.comment.document()->toPlainText().toUtf8().data() != undoRoom->room.note.c_str())
+                       {
+//                             doc->setRoomNote(selectedOne, room.note);
+                               doc->setRoomNote(selectedOne, dlg.comment.document()->toPlainText().toUtf8().data());
+                               changed = true;
+                       }
+
+                       if (changed && (undoType == epuRoomInfo))
+                       {
+                               doc->setUndoData(undoRoom);
+                               undoRoom = NULL; // undoRoom now belongs to the document
+                       }
+               }
+
+               if (undoType != epuRoomInfo)
+               {
+                       ASSERT(selectedOne >= 0);
+
+                       if (edges)
+                               doc->setUndoData(new UndoChanges(wasModified, selectedOne, 1, *edges));
+                       else
+                               doc->setUndoData(new UndoAdd(wasModified, selectedOne, (undoType == epuAddRoomEdge ? 1 : 0)));
+               }
+
+//             doc->setName(map.name);
+//             doc->setNote(map.note);
+
+//             if (doc->locked != bool(map.locked))
+//                     doc->OnNavigationMode();
+
+/*             if ((showCorners != bool(map.corners))
+                       || (showGrid != bool(map.grid))
+                       || (showPages != bool(map.pages)))
+               {
+                       showCorners = map.corners != 0;
+                       showGrid    = map.grid    != 0;
+                       showPages   = map.pages   != 0;
+
+                       if (!showPages)
+                               deselectPages(false);
+
+//                     InvalidateRect(NULL);
+               }*/
+       }
+       else if (undoType != epuRoomInfo)
+       {
+               // Cancel adding the room:
+               ASSERT(selectedOne >= 0);
+               const RoomNum roomNum = selectedOne;
+               clearSelection();
+
+               if (undoType == epuAddRoomEdge)
+               {
+                       const MapEdge & edge = doc->edge[doc->edge.size() - 1];
+                       selectOnlyRoom(edge.room1 == roomNum ? edge.room2 : edge.room1);
+               }
+
+               opInProgress = gmoDeleting; // Don't clear selection
+               doc->deleteRoom(roomNum);
+               opInProgress = gmoNone;
+
+               if (edges)
+               {
+                       doc->addEdges(edges->size());
+
+                       for(EdgeConstItr e=edges->begin(); e!=edges->end(); e++)
+                               doc->addEdge(*e);
+               }
+
+               doc->isDirty = wasModified;
+       }
+
+       selectDone();
+       update();
+       delete undoRoom;
+}
+
+
+#if 0
+//
+// Select all rooms:
+//
+void MapView::OnEditSelectAll()
+{
+       for(int i=doc->room.size()-1; i>=0; i--)
+               selectRoom(i);
+
+       selectDone();
+}
+
+
+//---------------------------------------------------------------------------
+// Select connected rooms:
+
+void MapView::OnEditSelectConnected()
+{
+       const MapDoc * doc = GetDocument();
+       ASSERT_VALID(doc);
+
+       const int numEdges = doc->getEdgeCount();
+       const EdgeVec & edge = doc->edge;
+
+       int wasSelected = 0;
+
+       while (wasSelected != numSelected)
+       {
+               wasSelected = numSelected;
+
+               for(int i=0; i<numEdges; i++)
+               {
+                       if ((edge[i].type1 & etNoRoom2) == 0)
+                       {
+                               if (selected[edge[i].room1])
+                                       selectRoom(edge[i].room2);
+                               else if (selected[edge[i].room2])
+                                       selectRoom(edge[i].room1);
+                       } // end if edge connects two rooms
+               }
+       } // end while more rooms were selected on this pass
+
+       selectDone();
+}
+
+
+void MapView::OnInitialUpdate()
+{
+       const MapDoc * doc = GetDocument();
+       ASSERT_VALID(doc);
+
+       selectedPage.resize(doc->page.size(), 0);
+       clearSelection(false);
+
+       // Set the document size:
+       init(4 * gridX, doc->getDocSize(), QSize(gridX, gridY));
+//  CClientDC dc(this);
+//  OnPrepareDC(&dc);
+       LOGFONT lf;
+       memset(&lf, 0, sizeof(lf));
+       lf.lfHeight = 62;
+       strcpy(lf.lfFaceName, "Arial");
+       font.CreateFontIndirect(&lf);
+
+       if (doc->locked)
+               OnUpdate(NULL, dupNavigationMode, NULL);
+
+       CScrollZoomView::OnInitialUpdate();
+       setScrollBars();              // Now fix the scroll bars
+}
+
+
+void MapView::OnKeyDown(unsigned int c, unsigned int nRepCnt, unsigned int nFlags)
+{
+       bool ctrl = (GetKeyState(VK_CONTROL) < 0);
+
+       switch (c)
+       {
+       case VK_LEFT:  OnHScroll(ctrl ? SB_PAGELEFT  : SB_LINELEFT,  0, NULL); break;
+       case VK_RIGHT: OnHScroll(ctrl ? SB_PAGERIGHT : SB_LINERIGHT, 0, NULL); break;
+       case VK_UP:    OnVScroll(ctrl ? SB_PAGEUP    : SB_LINEUP,    0, NULL); break;
+       case VK_DOWN:  OnVScroll(ctrl ? SB_PAGEDOWN  : SB_LINEDOWN,  0, NULL); break;
+       case VK_PRIOR: OnVScroll(ctrl ? SBtrOP       : SB_PAGEUP,    0, NULL); break;
+       case VK_NEXT:  OnVScroll(ctrl ? SB_BOTTOM    : SB_PAGEDOWN,  0, NULL); break;
+       case VKtrAB:   gueApp()->editComment();                                break;
+       default:
+               if ((GetKeyState(VK_SHIFT) >= 0) && (c >= '0') && (c <= '9'))
+               {
+                       if (!ctrl && (c < '2'))
+                               zoomTo(100);
+                       else if (ctrl && (c == '0'))
+                               zoomTo(200);
+                       else
+                               zoomTo(10 * (c - '0') + (ctrl ? 100 : 0));
+               } // end if number key (not shifted)
+
+               break;
+       }
+}
+#endif
+
+
+void MapView::mouseDoubleClickEvent(QMouseEvent * event)
+//void MapView::OnLButtonDblClk(unsigned int nFlags, QPoint point)
+{
+       if (scrollDrag || scrollDragTimer)
+               return;
+
+//     QPoint point = event->pos() * 5;
+       QPointF ptf = event->localPos() * (100.0 / zoom);
+       QPoint point = ptf.toPoint();
+//     QPoint point = event->pos() * (100.0 / zoom);
+       int nFlags = event->modifiers();
+
+       // Scroll support
+       point -= offset;
+
+       opInProgress = gmoNone;
+
+//     if (GetCapture() == this)
+//             ReleaseCapture();
+
+//     CClientDC dc(this);
+//     OnPrepareDC(&dc);
+//     dc.DPtoLP(&point);
+
+       const int i = doc->roomHit(point);
+//printf("MapView::mouseDoubleClickEvent: roomHit=%i\n", i);
+
+       if ((i >= 0) && (doc->room[i].isCorner() == false))
+       {
+               selectOnlyRoom(i);
+               editProperties(epuRoomInfo);
+       }
+       else if (i < 0)
+               addRoom(point);
+}
+
+
+/* N.B.: Handles RButton & MButton too */
+void MapView::mousePressEvent(QMouseEvent * event)
+//void MapView::OnLButtonDown(unsigned int nFlags, QPoint point)
+/*
+nFlags = MK_CONTROL, MK_SHIFT, MK_{LMR}BUTTON
+*/
+{
+       // We need to adjust the position to take into account the scale factor, which in this case, is 1/5:
+//     QPoint point = event->pos() * 5;
+       QPointF ptf = event->localPos() * (100.0 / zoom);
+       QPoint point = ptf.toPoint();
+//     QPoint point = event->pos() * (100 / zoom);
+       int nFlags = event->modifiers();
+       mouseDown = true;//will prolly have to separate this out to L, M & R  :-P
+/*
+Qt::NoModifier, Qt::ShiftModifier, Qt::ControlModifier, Qt::AltModifier, etc.
+*/
+
+       // Scrolling support (save this for now)...
+       point -= offset;
+       offsetScrollDragStart = offset;
+       scrollDragStart = point + offset;
+
+       if (event->button() == Qt::LeftButton)
+       {
+               if (scrollDrag || scrollDragTimer)
+                       return;
+
+//     CClientDC dc(this);
+//     OnPrepareDC(&dc);
+//     dc.DPtoLP(&point);
+
+               RoomCorner corner;
+               int room = doc->roomHit(point, &corner);
+
+               if (room < 0)
+               {
+                       if (doc->locked)
+                               return; // Must have 1 room selected during navigation
+
+                       if (showPages && ((room = doc->pageBorderHit(point)) >= 0))
+                       {
+                               iTmp  = room;
+
+//                             if (nFlags & MK_CONTROL)
+                               if (nFlags & Qt::ControlModifier)
+                               {
+                                       opInProgress = gmoControlDown;
+                                       bTmp = selectedPage[room];
+                                       b2Tmp = true;           // In page
+                               }
+                               else
+                               {
+//                                     if (nFlags & MK_SHIFT)
+                                       if (nFlags & Qt::ShiftModifier)
+                                               opInProgress = gmoShiftDown;
+                                       else
+                                               opInProgress = gmoNone;
+
+                                       clearSelection();
+                               }
+
+                               selectPage(room);
+                               selectDone();
+
+                               if (opInProgress)
+                               {
+//                                     SetCapture();
+                                       p1Tmp.rx() = point.x() / gridX;
+                                       p1Tmp.ry() = point.y() / gridY;
+                                       p2Tmp = doc->page[room].pos;
+                               }
+                       }
+                       else
+                       {
+//                             SetCapture();
+                               opInProgress = gmoSelectBox;
+
+//                             if (nFlags & MK_CONTROL)
+                               if (nFlags & Qt::ControlModifier)
+                               {
+                                       // If Ctrl pressed, don't clear selection:
+                                       for(ByteItr b=selected.begin(); b!=selected.end(); b++)
+                                       {
+                                               if (*b)
+                                                       *b = 2; // Mark the rooms that should stay selected
+                                       }
+
+                                       for(ByteItr b=selectedPage.begin(); b!=selectedPage.end(); b++)
+                                       {
+                                               if (*b)
+                                                       *b = 2; // Mark the pages that should stay selected
+                                       }
+                               }
+                               else
+                               {
+                                       clearSelection();
+                                       selectDone();
+                               }
+
+                               p1Tmp = point;
+                               p2Tmp = point;
+//                             rTmp.left = rTmp.right = point.x;
+//                             rTmp.bottom = rTmp.top = point.y;
+                               rTmp.setCoords(point.x(), point.y(), point.x(), point.y());
+                       }
+
+                       update();
+               }
+               else if (doc->locked)
+               {
+#if 0
+                       ASSERT(selectedOne >= 0);
+
+                       if ((room != selectedOne) && !doc->room[room].isCorner())
+                       {
+                               String path;
+                               doc->shortestPath(path, selectedOne, room);
+
+                               if (path.empty())
+                                       path = tr("Sorry, I don't know how to get there.");
+                               else if (path.find('.') != String::npos)
+                               {
+                                       const CMapApp * app = static_cast<CMapApp *>(AfxGetApp());
+
+                                       if (app->navigationCopy())
+                                       {
+                                               if (app->navigationCRLF())
+                                                       path += tr("\r\n");
+
+                                               pasteClipboard(path);
+                                       }
+                               }
+                               // end else if path has multiple steps
+
+                               static_cast<CMainFrame *>(AfxGetMainWnd())->setStatusBar(path.c_str());
+                               selectOnlyRoom(room);
+                       }
+                       // end if moving to new room in navigation mode
+#endif
+               }
+//             else if (nFlags & MK_CONTROL)
+               else if (nFlags & Qt::ControlModifier)
+               {
+//                     SetCapture();
+                       opInProgress = gmoControlDown;
+                       ASSERT(room < selected.size());
+                       bTmp = selected[room];
+                       b2Tmp = false;              // In room
+                       iTmp = room;
+                       selectRoom(room);
+                       selectDone();
+                       p1Tmp.rx() = point.x() / gridX;
+                       p1Tmp.ry() = point.y() / gridY;
+                       update();
+               }
+//             else if (nFlags & MK_SHIFT)
+               else if (nFlags & Qt::ShiftModifier)
+               {
+//                     SetCapture();
+                       opInProgress = gmoShiftDown;
+                       selectOnlyRoom(room);
+                       iTmp = -1;                  // Not a page
+                       p1Tmp.rx() = point.x() / gridX;
+                       p1Tmp.ry() = point.y() / gridY;
+                       p2Tmp = doc->room[room].pos;
+               }
+               else
+               {
+                       selectOnlyRoom(room);
+
+                       if (!(doc->room[room].isCorner()))
+                               emit(RoomClicked(doc, room));
+
+                       if (doc->room[room].isCorner())
+                       {
+//                             SetCapture();
+                               opInProgress = gmoControlDown;
+                               bTmp = b2Tmp = false;     // Leave the corner selected; in room
+                               p1Tmp.rx() = point.x() / gridX;
+                               p1Tmp.ry() = point.y() / gridY;
+                       }
+                       else if (corner != rcNone)
+                       {
+//                             SetCapture();
+                               opInProgress = gmoAddEdge;
+                               bTmp = doc->isDirty;
+                               iTmp = doc->findEdge(room, corner, edgeTmp);
+
+                               if (iTmp >= 0)
+                               {
+                                       opInProgress = gmoChangeEdge;
+                                       rTmp.setCoords(point.x() - 29, point.y() - 29, point.x() + 29, point.y() + 29);
+//                                     rTmp.InflateRect(29, 29);
+                               }
+
+                               if ((iTmp < 0) || (edgeTmp.type2 & etNoRoom2))
+                               {
+                                       edgeTmp.room1 = room;
+                                       edgeTmp.end1 = corner;
+                                       setEdgeType(edgeTmp);
+                               }
+
+                               edgeTmp.room2 = 0;
+                               edgeTmp.end2 = rcSW;
+                               doc->getEdgePoints(edgeTmp, p1Tmp, p2Tmp);
+
+                               p2Tmp = p1Tmp;
+//                             OnPaint();                // Update the view immediately
+
+#if 0
+                               if (opInProgress == gmoAddEdge)
+                               {
+                                       dc.SetROP2(R2_NOT);
+                                       QPen penEdge;
+
+                                       if (!penEdge.CreatePen(PS_SOLID, penEdgeWidth, RGB(0, 0, 0)))
+                                               return;
+
+                                       QPen * oldPen = dc.SelectObject(&penEdge);
+                                       dc.MoveTo(p1Tmp);
+                                       dc.LineTo(p2Tmp);
+                                       dc.SelectObject(oldPen);
+                               }
+#endif
+                       }
+
+                       update();
+               }
+       }
+       else if (event->button() == Qt::RightButton)
+       {
+//void MapView::OnRButtonDown(unsigned int nFlags, QPoint point)
+//{
+/*             if (opInProgress == gmoAddCorner)
+                       opInProgress = gmoNone;
+
+//             CClientDC dc(this);
+//             OnPrepareDC(&dc);
+//             dc.DPtoLP(&point);
+
+               scrollDragStart = point;
+
+               if (opInProgress)
+               {
+//                     SetCapture();
+                       scrollDrag = true;
+//                     ::SetCursor(handCursor);
+               }//*/
+//             else
+//                     scrollDragTimer = SetTimer(menuTimer, clickTicks, NULL);
+//}
+       }
+}
+
+#if 0
+// From scrollzoom
+bool MapView::calcScrollPosition(QPoint & pt) const
+{
+       if (pt.x() < 0)
+               pt.rx() = 0;
+
+//     if (pt.y() > 0)
+       if (pt.y() < 0)
+               pt.ry() = 0;
+
+//     SCROLLINFO si;
+//     VERIFY(const_cast<CScrollZoomView *>(this)->GetScrollInfo(SB_HORZ, &si, SIF_PAGE | SIF_POS | SIF_RANGE));
+
+//     if (pt.x + si.nPage > si.nMax + scrollStep.cx)
+//             pt.x = si.nMax - si.nPage + scrollStep.cx;
+
+//     VERIFY(const_cast<CScrollZoomView *>(this)->GetScrollInfo(SB_VERT, &si, SIF_PAGE | SIF_POS | SIF_RANGE));
+
+//     if (-pt.y + si.nPage > si.nMax + scrollStep.cy)
+//             pt.y = si.nPage - si.nMax - scrollStep.cy;
+
+//     pt.x -= pt.x % scrollStep.cx;
+//     pt.y -= pt.y % scrollStep.cy;
+
+       return offset != pt;
+}
+
+
+// From scrollzoom
+void MapView::scrollToPosition(const QPoint & pt)
+{
+       QPoint newOffset(pt);
+
+       if (!calcScrollPosition(newOffset))
+               return; // Didn't scroll
+
+//     QSize delta(newOffset - offset);
+       offset = newOffset;
+
+//     CClientDC  dc(this);
+//     OnPrepareDC(&dc);
+//     dc.LPtoDP(&delta);
+
+//     SetScrollPos(SB_VERT, -offset.y);
+//     SetScrollPos(SB_HORZ, offset.x);
+//     ScrollWindow(-delta.cx, delta.cy);
+}
+#endif
+
+void MapView::mouseMoveEvent(QMouseEvent * event)
+//void MapView::OnMouseMove(unsigned int nFlags, QPoint point)
+{
+       // We need to adjust the position to take into account the scale factor, which in this case, is 1/5:
+//     QPoint point = event->pos() * 5;
+       QPointF ptf = event->localPos() * (100.0 / zoom);
+       QPoint point = ptf.toPoint();
+//     QPoint point = event->pos() * (100 / zoom);
+       int nFlags = event->modifiers();
+
+       // Scroll support
+       point -= offset;
+
+       // Location where the mouse is currently pointing
+       mouse = point;
+
+       // Bail if we're just mousing around...
+       if (mouseDown == false)
+       {
+               hoveredEdge = FindHoveredEdge();
+
+               update();
+               return;
+       }
+
+/*     if (scrollDragTimer)
+       {
+//             KillTimer(scrollDragTimer);
+               scrollDragTimer = 0;
+//             SetCapture();
+               scrollDrag = true;
+//             ::SetCursor(handCursor);
+       }//*/
+
+//     if (GetCapture() != this)
+//             return;
+
+//     CClientDC dc(this);
+//     OnPrepareDC(&dc);
+//     dc.DPtoLP(&point);
+
+       if (scrollDrag)
+       {
+#if 0
+//             MSG msg;
+               QPoint p(offset);
+//             p.Offset(scrollDragStart - point);
+               p += (scrollDragStart - point);
+
+               if (!calcScrollPosition(p)
+)//                    || PeekMessage(&msg, m_hWnd, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_NOREMOVE))
+                       return;
+
+               if ((opInProgress == gmoAddEdge) || (opInProgress == gmoSelectBox)
+                       || (opInProgress >= gmoDrag))
+               {
+#if 0
+                       // We must handle these specially to avoid display artifacts:
+                       dc.SetROP2(R2_NOT);
+                       CGdiObject * old = NULL;
+                       QPen pen;
+                       QBrush brush;
+
+                       switch (opInProgress)
+                       {
+                       case gmoAddEdge:
+                               if (!pen.CreatePen(PS_SOLID, penEdgeWidth, RGB(0, 0 ,0)))
+                                       return;
+
+                               old = dc.SelectObject(&pen);
+                               dc.MoveTo(p1Tmp);
+                               dc.LineTo(p2Tmp);
+                               break;
+
+                       case gmoSelectBox:
+                               old = dc.SelectStockObject(HOLLOW_BRUSH);
+                               dc.Rectangle(rTmp);
+                               break;
+
+                       default:
+                               drawSelected(&dc);
+                               break; // Must be gmoDrag
+                       }
+#endif
+
+                       scrollToPosition(p);
+//                     OnPaint();
+//                     OnPrepareDC(&dc);         // Update with new offset
+
+#if 0
+                       switch (opInProgress)
+                       {
+                       case gmoAddEdge:    dc.MoveTo(p1Tmp);  dc.LineTo(p2Tmp);  break;
+                       case gmoSelectBox:  dc.Rectangle(rTmp);                   break;
+                       default: /* drag*/  drawSelected(&dc);                    break;
+                       }
+
+                       if (old)
+                               dc.SelectObject(old);
+#endif
+               }
+               else
+                       scrollToPosition(p);
+#endif
+               QPoint delta = (point + offset) - scrollDragStart;
+               offset = offsetScrollDragStart + delta;
+               update();
+
+               return;
+       }
+
+       if (opInProgress == gmoChangeEdge)
+       {
+//             if (rTmp.PtInRect(point))
+               if (rTmp.contains(point))
+                       return;
+
+//             e2Tmp = doc->getEdge(iTmp); // Save the current edge data
+               e2Tmp = doc->edge[iTmp];    // Save the current edge data
+               doc->deleteEdge(iTmp);      // Delete the old edge
+//             OnPaint();                  // Update the view immediately
+               opInProgress = gmoAddEdge;  // Now fall through to gmoAddEdge
+       }
+
+       if (opInProgress == gmoAddEdge)
+       {
+//             dc.SetROP2(R2_NOT);
+//             QPen penEdge;
+
+//             if (!penEdge.CreatePen(PS_SOLID, penEdgeWidth, RGB(0, 0, 0))) // PORT
+//                     return;
+
+//             QPen * oldPen = dc.SelectObject(&penEdge);
+//             dc.MoveTo(p1Tmp);
+//             dc.LineTo(p2Tmp);
+
+               RoomCorner corner;
+               int room = doc->roomHit(point, &corner);
+
+               if (corner != rcNone && room != edgeTmp.room1)
+               {
+                       edgeTmp.room2 = room;
+                       edgeTmp.end2  = corner;
+                       doc->getEdgePoints(edgeTmp, p1Tmp, p2Tmp);
+               }
+               else
+               {
+                       p2Tmp = point;
+               }
+
+//             dc.MoveTo(p1Tmp);
+//             dc.LineTo(p2Tmp);
+//             dc.SelectObject(oldPen);
+       }
+       else if (opInProgress == gmoSelectBox)
+       {
+               if (p2Tmp != point)
+               {
+                       p2Tmp = point;
+//                     dc.SetROP2(R2_NOT);
+//                     QBrush * oldBrush = static_cast<QBrush *>(dc.SelectStockObject(HOLLOW_BRUSH));
+//                     dc.Rectangle(rTmp);
+
+                       rTmp = QRect(p1Tmp, point).normalized();
+//                     rTmp.NormalizeRect();
+                       RoomConstItr room = doc->room.getVector().begin();
+//                     QRect intersect, r;
+
+                       for(int i=doc->room.size()-1; i>=0; i--)
+                       {
+                               if (selected[i] == 2)
+                                       continue; // Room was already selected before selection box
+
+#if 0
+                               room[i]->getRect(r);
+//                             intersect.IntersectRect(rTmp, r);
+                               intersect = rTmp & r;
+#else
+                               QRect r = room[i]->getRect();
+                               QRect intersect = rTmp & r;
+#endif
+
+                               if ((selected[i] != 0) != bool(r == intersect))
+                               {
+                                       selected[i] = !selected[i];
+                                       numSelected += (selected[i] ? 1 : -1);
+//                                     r.DeflateRect(5, 5);
+//                                     dc.LPtoDP(&r);
+//                                     r.NormalizeRect();
+//                                     InvalidateRect(&r);
+                               }
+                       }
+
+                       if (showPages)
+                       {
+                               for(int i=doc->page.size()-1; i>=0; i--)
+                               {
+                                       if (selectedPage[i] == 2)
+                                               continue; // Page was already selected before selection box
+
+#if 0
+                                       doc->getPageRect(i, r);
+//                                     intersect.IntersectRect(rTmp, r);
+                                       intersect = rTmp & r;
+#else
+                                       QRect r = doc->getPageRect(i);
+                                       QRect intersect = rTmp & r;
+#endif
+
+                                       if ((selectedPage[i] != 0) != bool(r == intersect))
+                                       {
+                                               selectedPage[i]  = !selectedPage[i];
+                                               numSelectedPages += (selectedPage[i] ? 1 : -1);
+//                                             r.DeflateRect(5, 5);
+//                                             dc.LPtoDP(&r);
+//                                             r.NormalizeRect();
+//                                             InvalidateRect(&r);
+                                       }
+                               }
+                       }
+
+                       if ((numSelected == 1) && !numSelectedPages)
+                       {
+                               for(selectedOne=0; !selected[selectedOne]; selectedOne++)
+                                       ;                     // Find the selected room
+                       }
+                       else
+                               selectedOne = -1;
+
+                       selectDone();
+
+//                     if (GetUpdateRect(NULL))
+//                             OnPaint();
+
+//                     dc.Rectangle(rTmp);
+//                     dc.SelectObject(oldBrush);
+               }
+       }
+       else if (opInProgress == gmoControlDown)
+       {
+               p2Tmp.rx() = gridX * (point.x() / gridX - p1Tmp.x());
+               p2Tmp.ry() = gridY * (point.y() / gridY - p1Tmp.y());
+
+               if (p2Tmp.x() || p2Tmp.y())
+               {
+                       opInProgress = gmoControlDrag;
+                       computeSelectedRect(rTmp);
+//                     drawBackground(&dc);
+//                     dc.SetROP2(R2_NOT);
+//                     drawSelected(&dc);
+               }
+       }
+       else if (opInProgress == gmoShiftDown)
+       {
+               long ox = point.x() / gridX;
+               long oy = point.y() / gridY;
+
+               if (ox < p1Tmp.x())
+                       opInProgress = gmoShiftDragLeft;
+               else if (ox > p1Tmp.x())
+                       opInProgress = gmoShiftDragRight;
+               else if (oy > p1Tmp.y())
+                       opInProgress = gmoShiftDragUp;
+               else if (oy < p1Tmp.y())
+                       opInProgress = gmoShiftDragDown;
+               else
+                       return;
+
+               // if dragging a page
+               if (iTmp >= 0)
+               {
+                       ASSERT(!numSelected && (numSelectedPages == 1));
+                       rTmp = doc->getPageRect(iTmp);
+
+                       switch (opInProgress)
+                       {
+                       case gmoShiftDragUp:
+                               p2Tmp.ry() = rTmp.top();
+                               break;
+                       case gmoShiftDragLeft:
+                               p2Tmp.rx() = rTmp.right() - roomWidth + gridX;
+                               break;
+                       }
+               }
+
+               RoomConstItr room = doc->room.getVector().begin();
+               const int roomCount = doc->room.size();
+
+               for(int i=0; i<roomCount; i++)
+               {
+                       if ((opInProgress == gmoShiftDragLeft && room[i]->pos.x() <= p2Tmp.x())
+                               || (opInProgress == gmoShiftDragRight && room[i]->pos.x() >= p2Tmp.x())
+                               || (opInProgress == gmoShiftDragUp && room[i]->pos.y() >= p2Tmp.y())
+                               || (opInProgress == gmoShiftDragDown && room[i]->pos.y() <= p2Tmp.y()))
+                               selectRoom(i, false);
+
+                       if (iTmp < 0)
+                       {
+                               // shift-dragging a room
+                               if (opInProgress == gmoShiftDragLeft)
+                                       p2Tmp.rx() += roomWidth;
+                               else if (opInProgress == gmoShiftDragDown)
+                                       p2Tmp.ry() += roomHeight;
+                       }
+                       else
+                       {
+                               // shift-dragging a page
+                               if (opInProgress == gmoShiftDragLeft)
+                                       p2Tmp.rx() += gridX;
+                               else if (opInProgress == gmoShiftDragRight)
+                                       p2Tmp.rx() -= gridX;
+                               else if (opInProgress == gmoShiftDragUp)
+                                       p2Tmp.ry() -= gridY;
+                               else if (opInProgress == gmoShiftDragDown)
+                                       p2Tmp.ry() += gridY;
+                       }
+
+                       for(i=doc->page.size()-1; i>=0; --i)
+                       {
+                               rTmp = doc->getPageRect(i);
+
+                               if ((opInProgress == gmoShiftDragLeft && rTmp.right() < p2Tmp.x())
+                                       || (opInProgress == gmoShiftDragRight && rTmp.left() > p2Tmp.x())
+                                       || (opInProgress == gmoShiftDragUp && rTmp.top() > p2Tmp.y())
+                                       || (opInProgress == gmoShiftDragDown && rTmp.bottom() < p2Tmp.y()))
+                                       selectPage(i, false);
+                       }
+               }
+
+               selectDone();
+
+               p2Tmp.rx() = gridX * (point.x() / gridX - p1Tmp.x());
+               p2Tmp.ry() = gridY * (point.y() / gridY - p1Tmp.y());
+               computeSelectedRect(rTmp);
+//             drawBackground(&dc);
+//             dc.SetROP2(R2_NOT);
+//             drawSelected(&dc);
+       }
+       // end else if gmoShiftDown
+       else if (opInProgress >= gmoDrag)
+       {
+//             MSG msg;
+
+//             if (PeekMessage(&msg, m_hWnd, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_NOREMOVE))
+//                     return; // The mouse has already been moved again
+
+               QPoint p(gridX * (point.x() / gridX - p1Tmp.x()), gridY * (point.y() / gridY - p1Tmp.y()));
+
+               if (p != p2Tmp)
+               {
+                       adjustOffset(p);
+
+                       if (p == p2Tmp)
+                               return;
+
+//                     dc.SetROP2(R2_NOT);
+//                     drawSelected(&dc);
+                       p2Tmp = p;
+//                     drawSelected(&dc);
+               }
+       }
+
+       // Maybe have more discriminant updating...  Maybe.
+       update();
+}
+
+
+//void MapView::OnLButtonUp(unsigned int nFlags, QPoint point)
+void MapView::mouseReleaseEvent(QMouseEvent * event)
+{
+       // We need to adjust the position to take into account the scale factor, which in this case, is 1/5:
+//     QPoint point = event->pos() * 5;
+       QPointF ptf = event->localPos() * (100.0 / zoom);
+       QPoint point = ptf.toPoint();
+//     QPoint point = event->pos() * (100 / zoom);
+       int nFlags = event->modifiers();
+       mouseDown = false;
+
+       // Scroll support
+       point -= offset;
+
+       if (event->button() == Qt::LeftButton)
+       {
+       //      if (GetCapture() != this)
+       //      {
+       //              if (selectedOne >= 0)
+       //                      makeRoomVisible(selectedOne);
+       //
+       //              return;
+       //      }
+
+       //      if (!scrollDrag)
+       //              ReleaseCapture();
+
+       //      CClientDC dc(this);
+       //      OnPrepareDC(&dc);
+       //      dc.DPtoLP(&point);
+
+               if (opInProgress == gmoAddEdge)
+               {
+                       // Erase the rubber band:
+       //              dc.SetROP2(R2_NOT);
+       //              QPen penEdge;
+
+       //              if (!penEdge.CreatePen(PS_SOLID, penEdgeWidth, RGB(0, 0, 0)))
+       //                      return;
+
+       //              QPen * oldPen = dc.SelectObject(&penEdge);
+       //              dc.MoveTo(p1Tmp);
+       //              dc.LineTo(p2Tmp);
+       //              dc.SelectObject(oldPen);
+
+                       // Find out where the edge goes:
+                       RoomCorner corner;
+                       int room = doc->roomHit(point, &corner);
+
+                       if (corner != rcNone && room != edgeTmp.room1)
+                       {
+                               // The edge goes somewhere
+                               // If there's a stub on the other end, delete it:
+                               EdgeVec deletedEdges;
+                               MapEdge oldEdge;
+                               int oldEdgeNum = doc->findEdge(room, corner, oldEdge);
+
+                               if (oldEdgeNum >= 0 && (oldEdge.type2 & etUnexplored))
+                               {
+       //                              deletedEdges.push_back(doc->getEdge(oldEdgeNum));
+                                       deletedEdges.push_back(doc->edge[oldEdgeNum]);
+                                       doc->deleteEdge(oldEdgeNum);
+                               }
+
+                               // Add the new or changed edge:
+                               if (edgeTmp.type2 & etOneWay)
+                               {
+                                       edgeTmp.room2 = edgeTmp.room1;
+                                       edgeTmp.end2  = edgeTmp.end1;
+                                       edgeTmp.type1 = edgeTmp.type2 | (edgeTmp.type1 & etObstructed);
+                                       edgeTmp.type2 = etNormal;
+                                       edgeTmp.room1 = room;
+                                       edgeTmp.end1  = corner;
+                               }
+                               else
+                               {
+                                       edgeTmp.room2 = room;
+                                       edgeTmp.end2  = corner;
+                               }
+
+                               if (iTmp >= 0)
+                                       deletedEdges.push_back(e2Tmp);
+
+                               if (deletedEdges.size())
+                               {
+                                       if (deletedEdges.size() == 1)
+                                               doc->setUndoData(new UndoChangeEdge(bTmp, deletedEdges[0]));
+                                       else
+                                               doc->setUndoData(new UndoChanges(bTmp, -1, 1, deletedEdges));
+                               }
+                               else
+                                       doc->setUndoData(new UndoAdd(bTmp));
+
+                               doc->addEdge(edgeTmp);
+                       }
+                       else
+                       {
+                               if (iTmp >= 0)
+                               {
+                                       // We just deleted the old edge
+                                       if ((e2Tmp.end1 == rcCorner)
+                                               || (!(e2Tmp.type1 & etNoRoom2) && (e2Tmp.end2 == rcCorner)))
+                                       {
+                                               doc->addEdge(e2Tmp);  // Put it back temporarily
+                                               selectOnlyRoom((e2Tmp.end1 == rcCorner) ? e2Tmp.room1 : e2Tmp.room2);
+                                               deleteSelection(false); // Remove entire connection
+                                       }
+                                       else
+                                               // not a connection to a corner
+                                               doc->setUndoData(new UndoDelete(bTmp, e2Tmp));
+                               }
+                               else if (selectedOne >= 0)
+                                       makeRoomVisible(selectedOne); // We just clicked on a room corner
+                       }
+                       // end if edge doesn't go anywhere
+               }
+               else if (opInProgress == gmoChangeEdge)
+               {
+                       // We didn't change the edge
+                       if (selectedOne >= 0)
+                               makeRoomVisible(selectedOne);
+               }
+       //      else if (opInProgress == gmoSelectBox)
+       //      {
+       //              dc.SetROP2(R2_NOT);
+       //              QBrush * oldBrush = static_cast<QBrush *>(dc.SelectStockObject(HOLLOW_BRUSH));
+       //              dc.Rectangle(rTmp);
+       //              dc.SelectObject(oldBrush);
+       //      }
+               else if (opInProgress == gmoControlDown)
+               {
+                       if (bTmp)
+                       {
+                               if (b2Tmp)
+                                       deselectPage(iTmp);
+                               else
+                                       deselectRoom(iTmp);
+
+                               selectDone();
+                       }
+                       // end if room or page was already selected
+               }
+               else if (opInProgress >= gmoDrag)
+               {
+                       QPoint p(gridX * (point.x() / gridX - p1Tmp.x()), gridY * (point.y() / gridY - p1Tmp.y()));
+                       adjustOffset(p);
+
+                       if (p.x() || p.y())
+                       {
+                               const QSize offset1(p.x(), p.y());
+                               doc->setUndoData(new UndoMove(doc->isDirty, offset1, numSelected, selected, numSelectedPages, selectedPage));
+
+                               for(int i=doc->room.size()-1; i>=0; i--)
+                               {
+                                       if (selected[i])
+                                               doc->moveRoom(i, offset1);
+                               }
+
+                               for(int i=doc->page.size()-1; i>=0; i--)
+                               {
+                                       if (selectedPage[i])
+                                               doc->movePage(i, offset1);
+                               }
+                       }
+       //              else
+       //                      InvalidateRect(NULL);
+               }
+
+               opInProgress = gmoNone;
+       }
+       else if (event->button() == Qt::RightButton)
+       {
+//void MapView::OnRButtonUp(unsigned int nFlags, QPoint point)
+//{
+//             if (scrollDragTimer)
+               if (true)
+               {
+       //              KillTimer(scrollDragTimer);
+                       scrollDragTimer = 0;
+
+                       RoomCorner corner;
+                       int room = doc->roomHit(point, &corner);
+                       cornerClicked = corner; // Bleah
+                       roomClicked = room;     // Bleah
+
+                       if (room < 0)
+                       {
+                               if (showPages && (room = doc->pageBorderHit(point)) >= 0)
+                               {
+                                       clearSelection();
+                                       selectPage(room);
+                                       selectDone();
+                               }
+
+                               mapContextMenu->popup(mapToGlobal(event->pos()));
+                       }
+                       else
+                       {
+                               selectOnlyRoom(room);
+                               opInProgress = gmoNone;
+
+                               if ((corner != rcNone) && !doc->locked)
+                               {
+                                       iTmp = doc->findEdge(room, corner, edgeTmp);
+
+                                       if ((iTmp >= 0) && !(edgeTmp.type2 & etNoRoom2))
+                                       {
+                                               opInProgress = gmoAddCorner;
+                                               addCornerAct->setEnabled(true);
+                                       }
+                                       else
+                                               addCornerAct->setEnabled(false);
+                               }
+
+                               roomContextMenu->popup(mapToGlobal(event->pos()));
+                               mouse = QPoint(0, 0);
+                       }
+
+       //              dc.LPtoDP(&point);
+       //              ClientToScreen(&point);
+       //              pop->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, AfxGetMainWnd());
+       //              //opInProgress = gmoNone;  // FIXME
+               }
+       //      else if (!scrollDrag || (GetCapture() != this))
+       //              CScrollZoomView::OnRButtonUp(nFlags, point);
+               else
+               {
+       //              if (opInProgress == gmoNone)
+       //                      ReleaseCapture();
+       //              else
+       //                      ::SetCursor(::LoadCursor(NULL, IDC_ARROW));
+
+//                     scrollDrag = false;
+               }
+//}
+       }
+
+       update();
+}
+
+
+#if 0
+int MapView::OnMouseActivate(CWnd * wnd, unsigned int hitTest, unsigned int message)
+{
+       const int  result = CScrollZoomView::OnMouseActivate(wnd, hitTest, message);
+
+       if ((result == MA_ACTIVATE) && GUEmapEatClicks)
+               return MA_ACTIVATEANDEAT;
+       else
+               return result;
+}
+
+
+bool MapView::OnMouseWheel(unsigned int nFlags, short zDelta, QPoint pt)
+{
+       if (nFlags == MK_CONTROL)
+       {
+               OnViewZoom((zDelta > 0) ? ID_VIEW_ZOOM_IN : ID_VIEW_ZOOM_OUT);
+               return TRUE;
+       }
+
+       return CScrollZoomView::OnMouseWheel(nFlags, zDelta, pt);
+}
+
+
+void MapView::OnSize(unsigned int nType, int cx, int cy)
+{
+       CScrollZoomView::OnSize(nType, cx, cy);
+
+       if (initialized())  // Make sure view has been initialized
+               setScrollBars();
+}
+
+
+void MapView::OnTimer(unsigned int idEvent)
+{
+       if (idEvent == menuTimer)
+       {
+               // We've held the button down long enough, switch to dragging:
+               SetCapture();
+               KillTimer(scrollDragTimer);
+               scrollDragTimer = 0;
+               scrollDrag = true;
+               ::SetCursor(handCursor);
+       }
+       else
+               CScrollZoomView::OnTimer(idEvent);
+}
+
+
+//
+// Update the view after the document changes:
+//
+// Input:
+//   lHint:
+//     dupDeletedRoom:
+//       A room has just been deleted
+//     dupPageCount:
+//       The number of pages has changed
+//     dupRoomComment:
+//       A room comment was just changed
+//         Do not invalidate the view unless pHint is not NULL.
+//     dupNavigationMode:
+//       We are entering navigation mode
+//         Ignore pHint, do not invalidate the view, just make sure
+//         exactly one room is selected and that it's visible.
+//   pHint:
+//     NULL means invalidate the entire view (but see lHint)
+//     Otherwise, it's a QRect* to the area to invalidate
+//
+void MapView::OnUpdate(CView * pSender, LPARAM lHint, CObject * pHint)
+{
+       if (lHint == dupDeletedRoom && opInProgress != gmoDeleting && numSelected)
+       {
+               // Clear selection unless this is the view doing the deleting:
+               clearSelection();
+               selectDone();
+       }
+       else if (lHint == dupPageCount && opInProgress != gmoDeleting)
+       {
+               selectedPage.resize(GetDocument()->getPages().size(), 0);
+
+               if (numSelectedPages)
+               {
+                       deselectPages();
+                       selectDone();
+               }
+       }
+       else if (lHint == dupRoomComment)
+       {
+               // Update comment for this view, but don't override any other view:
+               static_cast<CMapApp*>(AfxGetApp())->setComment(this,
+                       (selectedOne >= 0 ? &(GetDocument()->getRoom(selectedOne)) : NULL),  false);
+
+               if (!pHint)
+                       return;         // Don't invalidate entire view
+       }
+
+       if (lHint == dupNavigationMode)
+       {
+               const MapDoc * doc = GetDocument();
+               ASSERT_VALID(doc);
+               const int numRooms = doc->room.size();
+
+               if (selectedOne < 0 || doc->room[selectedOne].isCorner())
+                       selectOnlyRoom(0);
+
+               makeRoomVisible(selectedOne);
+       } // end if switching to navigation mode
+       else if (pHint)
+       {
+               QRect rect(*reinterpret_cast<const QRect *>(pHint));
+               CClientDC dc(this);
+               OnPrepareDC(&dc);
+               dc.LPtoDP(&rect);
+               rect.NormalizeRect();
+               InvalidateRect(rect);
+       }
+       else if (lHint != dupRoomComment)
+               Invalidate();
+}
+
+
+void MapView::OnUpdateEditAddCorner(CCmdUI* pCmdUI)
+{
+       pCmdUI->Enable(opInProgress == gmoAddCorner);
+}
+
+
+void MapView::OnUpdateEditPaste(CCmdUI* pCmdUI)
+{
+       pCmdUI->Enable((static_cast<CMapApp*>(AfxGetApp())->getClipboard() != NULL)
+               && !GetDocument()->locked);
+}
+
+
+//
+// Commands which require selected rooms and unlocked document:
+//
+// Edit|Clear
+// Edit|Cut
+// Edit|Copy
+// Edit|Select connected
+//
+void MapView::OnUpdateSelectedUnlocked(CCmdUI * pCmdUI)
+{
+       MapDoc * const doc = GetDocument();
+       ASSERT_VALID(doc);
+
+       bool  selected = numSelected;
+
+       if ((pCmdUI->m_nID != ID_EDIT_SELECT_CONNECTED) && numSelectedPages)
+       {
+               if ((pCmdUI->m_nID != ID_EDIT_COPY)
+                       && (doc->page.size() == numSelectedPages))
+                       selected = false;         // Can't delete all pages
+               else
+                       selected = true;
+       }
+
+       pCmdUI->Enable(selected && !doc->locked);
+}
+
+
+//
+// Commands which require the document to be unlocked:
+//
+// Edit|Select all
+//
+void MapView::OnUpdateUnlocked(CCmdUI* pCmdUI)
+{
+       pCmdUI->Enable(!GetDocument()->locked);
+}
+
+
+//
+// Toggle the grid on and off:
+//
+void MapView::OnViewGrid()
+{
+       showGrid = !showGrid;
+       InvalidateRect(NULL);
+}
+
+
+void MapView::OnUpdateViewGrid(CCmdUI * pCmdUI)
+{
+       pCmdUI->SetCheck(showGrid);
+}
+
+
+void MapView::OnViewZoom(unsigned int cmd)
+{
+       ASSERT((cmd == ID_VIEW_ZOOM_IN) || (cmd == ID_VIEW_ZOOM_OUT));
+
+       if (cmd == ID_VIEW_ZOOM_OUT)
+               zoomTo(zoom % 10 ? zoom - zoom % 10 : zoom - 10);
+       else
+               zoomTo(zoom + 10 - zoom % 10);
+}
+
+
+void MapView::OnUpdateViewZoom(CCmdUI * pCmdUI)
+{
+       pCmdUI->Enable((pCmdUI->m_nID == ID_VIEW_ZOOM_IN)
+               ? (zoom < maxZoom) : (zoom > minZoom));
+}
+
+
+//
+// Redraw the window:
+//
+void MapView::OnWindowRefresh()
+{
+       InvalidateRect(NULL);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+// CRepaginateDlg dialog
+
+CRepaginateDlg::CRepaginateDlg(): CDialog(CRepaginateDlg::IDD, NULL)
+{
+       //{{AFX_DATA_INIT(CRepaginateDlg)
+       //}}AFX_DATA_INIT
+}
+
+BEGIN_MESSAGE_MAP(CRepaginateDlg, CDialog)
+       //{{AFX_MSG_MAP(CRepaginateDlg)
+       ON_BN_CLICKED(IDYES, OnYes)
+       //}}AFX_MSG_MAP
+END_MESSAGE_MAP();
+
+/////////////////////////////////////////////////////////////////////////////
+// CRepaginateDlg message handlers
+
+
+bool CRepaginateDlg::OnInitDialog()
+{
+       CDialog::OnInitDialog();
+
+       SendDlgItemMessage(IDC_EXCLAMATION, STM_SETICON, (WPARAM) gueApp()->LoadStandardIcon(IDI_EXCLAMATION));
+       MessageBeep(MB_ICONEXCLAMATION);
+
+       return true;  // return TRUE unless you set the focus to a control
+}
+
+
+void CRepaginateDlg::OnYes()
+{
+       EndDialog(IDYES);
+}
+#endif
+
diff --git a/src/mapview.h b/src/mapview.h
new file mode 100644 (file)
index 0000000..3a46579
--- /dev/null
@@ -0,0 +1,233 @@
+//
+// GUEmap
+// (C) 1997-2007 Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+// mapview.h: Interface of the MapView class
+//
+
+#ifndef __MAPVIEW_H__
+#define __MAPVIEW_H__
+
+#include <QtWidgets>
+#include "globals.h"
+
+class MapDoc;
+
+enum GmOp
+{
+       gmoNone, gmoAddEdge, gmoChangeEdge, gmoSelectBox,       gmoDeleting, gmoAddCorner,
+       gmoControlDown, gmoShiftDown,
+       gmoShiftDragUp, gmoShiftDragDown, gmoShiftDragLeft, gmoShiftDragRight,
+       gmoControlDrag,
+       gmoDrag = gmoShiftDragUp      // Drag operations must come last
+};
+
+enum EditPropUndo
+{
+       epuRoomInfo, epuAddRoom, epuAddRoomEdge, epuMapInfo
+};
+
+
+class MapView: public QWidget
+{
+       Q_OBJECT
+
+       public:
+               QMenu * roomContextMenu;
+               QMenu * mapContextMenu;
+
+               QAction * deleteRoomAct;
+               QAction * roomPropertiesAct;
+               QAction * mapPropertiesAct;
+               QAction * selectConnectedAct;
+               QAction * addCornerAct;
+               QAction * addRoomAct;
+               QAction * addPageAct;
+               QAction * addUnexploredAct;
+               QAction * addLoopBackAct;
+               QAction * addNoExitAct;
+               QAction * addUpAct;
+               QAction * addDownAct;
+               QAction * addInAct;
+               QAction * addOutAct;
+               QAction * addOneWayAct;
+               QAction * clearOneWayAct;
+               QAction * addRestrictedAct;
+               QAction * clearRestrictedAct;
+
+       public:
+               GmOp    opInProgress;
+               bool    bTmp;
+               bool    b2Tmp;
+               MapEdge edgeTmp;
+               MapEdge e2Tmp;
+               int     iTmp;
+               QPoint  p1Tmp;
+               QPoint  p2Tmp;
+               QPoint  mouse;
+               QRect   rTmp;
+               int hoveredEdge;
+               RoomCorner cornerClicked;
+               int roomClicked;
+
+               QPoint scrollDragStart;
+               QPoint offsetScrollDragStart;
+               unsigned int scrollDragTimer;
+               bool scrollDrag;
+
+               QFont font;
+               bool showCorners;
+               bool showGrid;
+               bool showPages;
+
+               int numSelected;
+               int numSelectedPages;
+               int selectedOne;
+               ByteVec selected;
+               ByteVec selectedPage;
+
+               short printingPage;
+               double zoom;
+               MapDoc * doc;
+
+               bool shiftDown, ctrlDown, altDown;
+               bool mouseDown;
+
+               // From scrollzoom.h
+               QPoint offset;
+
+       public:
+               MapView(QWidget * parent = NULL);
+               virtual ~MapView();
+
+       public:
+               void DrawArrowhead(QPainter *, QPointF, QPointF);
+               void DrawNoExit(QPainter *, QPointF, QPointF);
+               void clearSelection(bool update = true);
+               void deleteSelection(bool removeCorner = true);
+               void deselectPage(int n, bool update = true);
+               void deselectPages(bool update = true);
+               void deselectRoom(RoomNum n, bool update = true);
+               void selectDone();
+               void selectPage(int n, bool update = true);
+               void selectRoom(RoomNum n, bool update = true);
+               void selectOnlyRoom(RoomNum n);
+               void makeRoomVisible(RoomNum n);
+               void navigate(RoomCorner corner, bool toggleStubs = false);
+               void setRoomNote(const char * comment);
+               int FindHoveredEdge(void);
+
+       protected:
+               void paintEvent(QPaintEvent *);
+               void keyPressEvent(QKeyEvent *);
+               void keyReleaseEvent(QKeyEvent *);
+               void mouseDoubleClickEvent(QMouseEvent *);
+               void mousePressEvent(QMouseEvent *);
+               void mouseMoveEvent(QMouseEvent *);
+               void mouseReleaseEvent(QMouseEvent *);
+#if 0
+       // ClassWizard generated virtual function overrides
+       //{{AFX_VIRTUAL(CMapView)
+               virtual void OnInitialUpdate();
+               virtual void OnPrepareDC(QPainter * pDC, CPrintInfo * pInfo = NULL);
+               protected:
+               virtual bool OnPreparePrinting(CPrintInfo * pInfo);
+               virtual bool PreCreateWindow(CREATESTRUCT & cs);
+               virtual void OnActivateView(bool bActivate, CView * pActivateView, CView * pDeactiveView);
+               virtual void OnBeginPrinting(QPainter * pDC, CPrintInfo * pInfo);
+               virtual void OnEndPrinting(QPainter * pDC, CPrintInfo * pInfo);
+               virtual void OnUpdate(CView * pSender, LPARAM lHint, CObject * pHint);
+#endif
+       //}}AFX_VIRTUAL
+
+       private slots:
+               void HandleAddCorner(void);
+               void HandleAddUnexplored(void);
+               void HandleAddLoopBack(void);
+               void HandleAddNoExit(void);
+               void SetEdgeDirection(EdgeType);
+               void HandleAddUp(void);
+               void HandleAddDown(void);
+               void HandleAddIn(void);
+               void HandleAddOut(void);
+               void HandleAddRoom(void);
+               void SetEdges(int, int, EdgeType);
+               void ClearEdges(int, int, EdgeType);
+               void HandleAddOneWay(void);
+               void HandleClearOneWay(void);
+               void HandleAddRestricted(void);
+               void HandleClearRestricted(void);
+               void HandleRoomProperties(void);
+               void HandleMapProperties(void);
+               void HandleDelete(void);
+
+       signals:
+               void RoomClicked(MapDoc *, int);
+
+       protected:
+               void addRoom(QPoint & point);
+               void adjustOffset(QPoint & p) const;
+               void computeSelectedRect(QRect & r) const;
+
+//             void drawBackground(QPainter * dc);
+//             void drawSelected(QPainter * dc);
+
+               void editProperties(EditPropUndo undoType, bool wasModified = false, EdgeVec * edges = NULL);
+
+               bool pasteClipboard(const string & text);
+
+               void getNavText(char & initial, RoomCorner & dir1, char & separator, RoomCorner & dir2, bool mustBeMove = false);
+               void setEdgeType(MapEdge & e);
+               void setNavText(char c);
+
+               void setScrollBars();
+               void zoomTo(short newZoom);
+
+#if 0
+       // Generated message map functions
+       protected:
+               //{{AFX_MSG(CMapView)
+               void OnEditProperties(unsigned int cmd);
+               bool OnMouseWheel(unsigned int nFlags, short zDelta, CPoint pt);
+               void OnChar(unsigned int nChar, unsigned int nRepCnt, unsigned int nFlags);
+//             void OnEditAddCorner();
+               void OnEditAddPage();
+//             void OnEditAddRoom();
+//             void OnEditClear();
+               void OnEditCopy();
+               void OnEditCut();
+               void OnEditPaginate();
+               void OnEditPaste();
+//             void OnEditSelectAll();
+               void OnEditSelectConnected();
+               void OnKeyDown(unsigned int nChar, unsigned int nRepCnt, unsigned int nFlags);
+//             void OnLButtonDblClk(unsigned int nFlags, CPoint point);
+//             void OnLButtonDown(unsigned int nFlags, CPoint point);
+//             void OnLButtonUp(unsigned int nFlags, CPoint point);
+//             void OnMouseMove(unsigned int nFlags, CPoint point);
+//             void OnRButtonDown(unsigned int nFlags, CPoint point);
+//             void OnRButtonUp(unsigned int nFlags, CPoint point);
+               void OnSize(unsigned int nType, int cx, int cy);
+               void OnTimer(unsigned int nIDEvent);
+               void OnUpdateEditAddCorner(CCmdUI * pCmdUI);
+               void OnUpdateEditPaste(CCmdUI * pCmdUI);
+               void OnUpdateSelectedUnlocked(CCmdUI * pCmdUI);
+               void OnUpdateUnlocked(CCmdUI * pCmdUI);
+               void OnUpdateViewGrid(CCmdUI * pCmdUI);
+               void OnViewGrid();
+               void OnWindowRefresh();
+               int OnMouseActivate(CWnd *, unsigned int, unsigned int);
+               void OnNavGo();
+               void OnNavGoDir(unsigned int cmd);
+               void OnUpdateViewZoom(CCmdUI * pCmdUI);
+               void OnViewZoom(unsigned int cmd);
+               //}}AFX_MSG
+#endif
+};
+
+#endif // __MAPVIEW_H__
+
diff --git a/src/mathconstants.h b/src/mathconstants.h
new file mode 100644 (file)
index 0000000..7a0aa5e
--- /dev/null
@@ -0,0 +1,22 @@
+// Mathematical Constants used by GUEmap
+//
+// Part of the GUEmap Project
+// (C) 2019 Underground Software
+// See the README and GPLv2 files for licensing and warranty information
+//
+// NOTE: Since this has no code associated with it, there is no corresponding
+//       .cpp file.
+//
+
+#define TAU                6.28318530717958647692528676655
+#define TAU_1QTR           (TAU * 0.25)
+#define TAU_2QTR           (TAU * 0.50)
+#define TAU_3QTR           (TAU * 0.75)
+#define RADIANS_TO_DEGREES (360.0 / TAU)
+#define DEGREES_TO_RADIANS (TAU / 360.0)
+
+// Convenience definitions
+#define HALF_TAU           (TAU_2QTR)
+#define QTR_TAU            (TAU_1QTR)
+#define THREE_QTR_TAU      (TAU_3QTR)
+
diff --git a/src/roomdialog.cpp b/src/roomdialog.cpp
new file mode 100644 (file)
index 0000000..437dd86
--- /dev/null
@@ -0,0 +1,40 @@
+//
+// GUEmap
+// Copyright 1997-2007 by Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+// roomdialog.cpp: Dialog for changing GUEmap room settings
+//
+
+#include "roomdialog.h"
+
+
+RoomDialog::RoomDialog(QWidget * parent/*= 0*/): QDialog(parent)
+{
+       QFormLayout * fl = new QFormLayout;
+
+       fl->addRow("&Name:", &name);
+       fl->addRow("&Comment:", &comment);
+       fl->addRow("&Border:", &border);
+
+       buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+
+       connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
+       connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+
+       QVBoxLayout * mainLayout = new QVBoxLayout;
+       mainLayout->addLayout(fl);
+       mainLayout->addWidget(buttonBox);
+       setLayout(mainLayout);
+
+       setWindowTitle(tr("Room Settings"));
+}
+
+
+RoomDialog::~RoomDialog()
+{
+}
+
diff --git a/src/roomdialog.h b/src/roomdialog.h
new file mode 100644 (file)
index 0000000..3de1fe1
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef __ROOMDIALOG_H__
+#define __ROOMDIALOG_H__
+
+#include <QtWidgets>
+
+class RoomDialog: public QDialog
+{
+       Q_OBJECT
+
+       public:
+               RoomDialog(QWidget * parent = 0);
+               ~RoomDialog();
+
+       private:
+               QDialogButtonBox * buttonBox;
+
+       public:
+               QLineEdit name;
+               QTextEdit comment;
+               QCheckBox border;
+};
+
+#endif // __ROOMDIALOG_H__
+
diff --git a/src/roomwidget.cpp b/src/roomwidget.cpp
new file mode 100644 (file)
index 0000000..ef22ad8
--- /dev/null
@@ -0,0 +1,165 @@
+// roomwidget.cpp: room tweaking widget
+//
+// Part of the GUEmap Project
+// (C) 2019 Underground Software
+// See the README and GPLv2 files for licensing and warranty information
+//
+
+#include "roomwidget.h"
+//#include "mathconstants.h"
+#include "globals.h"
+#include "mapdoc.h"
+
+
+RoomWidget::RoomWidget(void): QWidget()
+{
+#if 0
+#if 0
+       QListWidget * qlw = new QListWidget;
+       QListWidgetItem * qli1 = new QListWidgetItem(qlw);
+       QListWidgetItem * qli2 = new QListWidgetItem(qlw);
+       QListWidgetItem * qli3 = new QListWidgetItem(qlw);
+       QListWidgetItem * qli4 = new QListWidgetItem(qlw);
+       QListWidgetItem * qli5 = new QListWidgetItem(qlw);
+#endif
+       label = new QLabel;
+
+#if 0
+       QPushButton * pb1 = new QPushButton("+");
+       QPushButton * pb2 = new QPushButton("-");
+       QPushButton * pb3 = new QPushButton("Edit");
+       QPushButton * pb4 = new QPushButton("Import");
+#else
+       QToolButton * pb1 = new QToolButton;
+       QToolButton * pb2 = new QToolButton;
+       QToolButton * pb3 = new QToolButton;
+       QToolButton * pb4 = new QToolButton;
+
+       pb1->setIcon(QIcon(":/res/layer-add.png"));
+       pb2->setIcon(QIcon(":/res/layer-delete.png"));
+       pb3->setIcon(QIcon(":/res/layer-edit.png"));
+       pb4->setIcon(QIcon(":/res/block-import.png"));
+
+       pb1->setToolTip(tr("Add block"));
+       pb2->setToolTip(tr("Remove block"));
+       pb3->setToolTip(tr("Edit block"));
+       pb4->setToolTip(tr("Import block"));
+#endif
+
+       QHBoxLayout * hbox1 = new QHBoxLayout;
+       hbox1->addWidget(pb1);
+       hbox1->addWidget(pb2);
+       hbox1->addWidget(pb3);
+       hbox1->addWidget(pb4);
+       hbox1->addStretch();
+
+       QVBoxLayout * mainLayout = new QVBoxLayout;
+       mainLayout->addWidget(label);
+       mainLayout->addStretch();
+       mainLayout->addLayout(hbox1);
+
+       setLayout(mainLayout);
+#endif
+       QFormLayout * fl = new QFormLayout;
+
+       fl->addRow("&Name:", &name);
+       fl->addRow("&Comment:", &comment);
+       fl->addRow("&Border:", &border);
+
+//     buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+
+//     connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
+//     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
+
+       QVBoxLayout * mainLayout = new QVBoxLayout;
+       mainLayout->addLayout(fl);
+//     mainLayout->addWidget(buttonBox);
+       setLayout(mainLayout);
+
+       setWindowTitle(tr("Room Settings"));
+}
+
+
+RoomWidget::~RoomWidget()
+{
+}
+
+
+void RoomWidget::ShowInfo(MapDoc * doc, int roomNo)
+{
+       MapRoom & rm = doc->room[roomNo];
+
+       name.setText(rm.name.c_str());
+       comment.clear();
+       comment.insertPlainText(rm.note.c_str());
+       border.setChecked(bool((rm.flags & rfBorder) != 0));
+}
+
+#if 0
+void RoomWidget::ShowInfo(Object * obj)
+{
+       const char objName[OTCount][16] = {
+               "None", "Line", "Circle", "Ellipse", "Arc", "Polygon", "Dimension", "Spline", "Text", "Container"
+       };
+       const char dimName[DTCount][32] = {
+               "Linear", "Vertical", "Horizontal", "Radial", "Diametric",
+               "Circumferential", "Angular", "Leader"
+       };
+
+       // Sanity check
+       if (obj == NULL)
+               return;
+
+       QString s = QString("<b>%1</b><br><br>").arg(QString(objName[obj->type]));
+
+       switch (obj->type)
+       {
+       case OTLine:
+       {
+               Vector line(obj->p[0], obj->p[1]);
+               s += QString("&lt;%1, %2&gt; to &lt;%3, %4&gt;<br>Length: %5<br>Angle: %6&#x00B0;<br>").arg(obj->p[0].x).arg(obj->p[0].y).arg(obj->p[1].x).arg(obj->p[1].y).arg(line.Magnitude()).arg(line.Angle() * RADIANS_TO_DEGREES);
+               break;
+       }
+
+       case OTCircle:
+               s += QString("Center: &lt;%1, %2&gt;<br>Radius: %3<br>").arg(obj->p[0].x).arg(obj->p[0].y).arg(obj->radius[0]);
+               break;
+
+       case OTEllipse:
+               break;
+
+       case OTArc:
+               s += QString("Center: &lt;%1, %2&gt;<br>Radius: %3<br>Start: %4&#x00B0;<br>Span: %5&#x00B0;<br>").arg(obj->p[0].x).arg(obj->p[0].y).arg(obj->radius[0]).arg(obj->angle[0] * RADIANS_TO_DEGREES).arg(obj->angle[1] * RADIANS_TO_DEGREES);
+               break;
+
+       case OTPolygon:
+               break;
+
+       case OTDimension:
+       {
+               Dimension * d = (Dimension *)obj;
+               s += QString("Type: %1<br>").arg(dimName[d->subtype]);
+               break;
+       }
+
+       case OTSpline:
+               break;
+
+       case OTText:
+       {
+               Text * t = (Text *)obj;
+               s += QString("&lt;%1, %2&gt;<br>Width/Height: %3/%4<br>Angle: %5&#x00B0;<br>").arg(t->p[0].x).arg(t->p[0].y).arg(t->extents.Width()).arg(t->extents.Height()).arg(obj->angle[0] * RADIANS_TO_DEGREES);
+               break;
+       }
+
+       case OTContainer:
+               break;
+
+       default:
+               break;
+       }
+
+       label->setText(s);
+}
+#endif
+
diff --git a/src/roomwidget.h b/src/roomwidget.h
new file mode 100644 (file)
index 0000000..5424b7b
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef __ROOMWIDGET_H__
+#define __ROOMWIDGET_H__
+
+#include <QtWidgets>
+//#include "structs.h"
+
+class MapDoc;
+
+class RoomWidget: public QWidget
+{
+       Q_OBJECT
+
+       public:
+               RoomWidget(void);
+               ~RoomWidget();
+
+       public slots:
+//             void ShowInfo(Object *);
+               void ShowInfo(MapDoc *, int);
+
+       public:
+               QLabel * label;
+               QLineEdit name;
+               QTextEdit comment;
+               QCheckBox border;
+};
+
+#endif // __ROOMWIDGET_H__
+
diff --git a/src/undo.cpp b/src/undo.cpp
new file mode 100644 (file)
index 0000000..b3a5158
--- /dev/null
@@ -0,0 +1,637 @@
+//
+// GUEmap
+// Copyright 1997-2007 by Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+// Implementation of UndoRec and its derived classes
+//
+
+#include "undo.h"
+#include "mapdoc.h"
+
+
+//
+// Class UndoRec:
+//
+// UndoRec is the virtual base class for all undo records.
+//
+// Member Variables:
+//   oldModified:
+//     The modification state of the document before the change that
+//     this undo record applies to was made.
+//
+
+UndoRec::UndoRec(bool modified): oldModified(modified)
+{
+}
+
+
+UndoRec::~UndoRec()
+{
+}
+
+
+//
+// Get the type of undo record:
+//
+// Returns:
+//   The name of this undo operation (The XXX in "Undo XXX")
+//
+// char * UndoRec::getName() const
+//
+//--------------------------------------------------------------------
+// Undo:
+//
+// Undo the change that this undo record applies to.  This is the main entry
+// point; it calls undoChange to actually do the work.
+//
+// If the document was not modified before the original change, and is
+// modified now, then it is marked as unmodified.  Otherwise, it is marked as
+// modified.  This should cause the modification flag to agree with the actual
+// state of the document in memory vs. the file on disk.
+//
+// Input:
+//   doc:  The MapDoc to change
+//
+// Returns:
+//   A pointer to the "redo" record
+//   NULL if the operation cannot be redone
+//
+// Note:
+//   undo is a one time function.  After calling undo, you cannot call any
+//   other member functions; you must delete the object immediately.
+//
+UndoRec * UndoRec::undo(MapDoc & doc)
+{
+       bool newModified = doc.isDirty;
+
+       UndoRec * redo = undoChange(doc);
+
+       doc.isDirty = (!oldModified && newModified ? false : true);
+
+       if (redo)
+               redo->oldModified = newModified;
+
+       return redo;
+}
+
+
+//
+// Class UndoAdd:
+//
+// UndoAdd undoes the addition of 1 or more rooms and/or edges.  Since
+// edges are always added at the end of the list, we only need to know
+// how many were added.
+//
+// Member Variables:
+//   edges:    The number of edges that were added
+//   pageNums: The numbers of the pages that were added
+//   roomNums: The numbers of the rooms that were added
+//--------------------------------------------------------------------
+// Constructor for the redo record for UndoDelete:
+//
+// Input:
+//   modified:  The modification state of the document
+//   edgeCount: The number of edges being added
+//   rns:       The numbers of the rooms being added
+//   pns:       The numbers of the pages being added
+//
+UndoAdd::UndoAdd(bool modified, int edgeCount, RoomNumVec & rns, PageNumVec & pns): UndoRec(modified), edges(edgeCount)
+{
+       pageNums.swap(pns);
+       roomNums.swap(rns);
+}
+
+
+//
+// Constructor for a one-room addition:
+//
+// Input:
+//   modified:  The modification state of the document
+//   roomNum:   The number of the room being added
+//   edgeCount: The number of edges being added
+//
+UndoAdd::UndoAdd(bool modified, RoomNum roomNum, int edgeCount): UndoRec(modified), edges(edgeCount)
+{
+       roomNums.push_back(roomNum);
+}
+
+
+//
+// Constructor for a one-page addition:
+//
+// Input:
+//   modified: The modification state of the document
+//   pageNum:  The number of the page being added
+//
+UndoAdd::UndoAdd(bool modified, short pageNum): UndoRec(modified), edges(0)
+{
+       pageNums.push_back(pageNum);
+}
+
+
+//
+// Constructor for a one-edge addition:
+//
+// Input:
+//   modified:   The modification state of the document
+//
+UndoAdd::UndoAdd(bool modified): UndoRec(modified), edges(1)
+{
+}
+
+
+const char * UndoAdd::getName() const
+{
+       return "Add";
+}
+
+
+//
+// Undo the addition of rooms, edges, and/or pages:
+//
+UndoRec * UndoAdd::undoChange(MapDoc & doc)
+{
+       UndoRec * redo = new UndoDelete(doc, edges, roomNums, pageNums);
+       int e = edges;
+
+       for(int i=doc.edge.size()-1; e; e--)
+               doc.deleteEdge(i--);
+
+       RNConstItr r = roomNums.end();
+
+       while (r != roomNums.begin())
+               doc.deleteRoom(*(--r));
+
+       PNConstItr p = pageNums.end();
+
+       while (p != pageNums.begin())
+               doc.deletePage(*(--p));
+
+       return redo;
+}
+
+
+//
+// Class UndoChangeEdge:
+//
+// UndoChangeEdge is used when an existing connection is changed.
+// Since edges are changed by being deleted and re-added, it assumes
+// that the edge is the last one in the list.  (N.B.: This is bad, but we'll roll with it for now.)
+//
+// Member Variables:
+//   edge:  The original edge
+//--------------------------------------------------------------------
+// Constructor:
+//
+// Input:
+//   modified:  The modification state of the document
+//   theEdge:   The original edge
+//
+UndoChangeEdge::UndoChangeEdge(bool modified, const MapEdge & theEdge): UndoRec(modified), edge(theEdge)
+{
+}
+
+
+const char * UndoChangeEdge::getName() const
+{
+       return "Reconnection";
+}
+
+
+//
+// Undo a change to a connection:
+//
+UndoRec * UndoChangeEdge::undoChange(MapDoc & doc)
+{
+       const int edgeNum = doc.edge.size() - 1;
+       UndoRec * redo = new UndoChangeEdge(true, doc.edge[edgeNum]);
+
+       doc.deleteEdge(edgeNum);
+       doc.addEdge(edge);
+
+       return redo;
+}
+
+
+//
+// Class UndoChanges:
+//
+// UndoChanges combines both UndoAdd & UndoDelete in one record.
+//--------------------------------------------------------------------
+// Constructor for adding one room & deleting one edge:
+//
+// Input:
+//   modified:   The modification state of the document
+//   roomNum:    The number of the room being added
+//   edgeCount:  The number of edges being added
+//   edge:       The edge being deleted
+//
+UndoChanges::UndoChanges(bool modified, RoomNum roomNum, int edgeCount, const MapEdge & edge): UndoRec(modified), add(new UndoAdd(modified, roomNum, edgeCount)), del(new UndoDelete(modified, edge))
+{
+}
+
+
+//
+// Constructor for adding one room & deleting one edge:
+//
+// Input:
+//   modified:   The modification state of the document
+//   roomNum:    The number of the room being added (-1 if no room added)
+//   edgeCount:  The number of edges being added
+//   edges:      The edges being deleted
+//
+// Output:
+//   edges:      The vector is emptied
+//
+UndoChanges::UndoChanges(bool modified, int roomNum, int edgeCount, EdgeVec & edges): UndoRec(modified), del(new UndoDelete(modified, edges))
+{
+       if (roomNum >= 0)
+               add = new UndoAdd(modified, roomNum, edgeCount);
+       else
+               add = new UndoAdd(modified);
+}
+
+
+//
+// Constructor for removing a corner:
+//
+// Input:
+//   modified:   The modification state of the document
+//   roomNum:    The number of the room being deleted
+//   room:       The room being deleted
+//   edges:      The edges being deleted
+//
+// Output:
+//   edges:  The vector is emptied
+//
+UndoChanges::UndoChanges(bool modified, RoomNum roomNum, MapRoom * room, EdgeVec & edges): UndoRec(modified), add(new UndoAdd(modified)),/* Adding 1 edge */ del(new UndoDelete(modified, edges))
+{
+       static_cast<UndoDelete *>(del)->addRooms(1);
+       static_cast<UndoDelete *>(del)->addRoom(0, roomNum, room);
+}
+
+
+const char * UndoChanges::getName() const
+{
+       return "Change";          // FIXME better name
+}
+
+
+UndoRec * UndoChanges::undoChange(MapDoc & doc)
+{
+       UndoRec * redo = NULL;
+
+       UndoRec * newDel = add->undoChange(doc);
+       delete add;
+
+       add = del->undoChange(doc);
+       delete del;
+       del = newDel;
+
+       if (add && del)
+               redo = this;
+
+       return redo;
+}
+
+
+//
+// Class UndoDelete:
+//
+// UndoDelete undoes the deletion of 1 or more rooms and/or edges.  Edges are
+// restored to the end of the list, but rooms regain their original numbers.
+//
+// Member Variables:
+//   edges:
+//     The edges that were deleted.
+//     They are stored with the original room numbers.
+//   pages:
+//     The pages that were deleted.
+//   pageNums:
+//     The numbers of the pages that were deleted.
+//   rooms:
+//     The rooms that were deleted.
+//   roomNums:
+//     The numbers of the rooms that were deleted.
+//--------------------------------------------------------------------
+// Constructor used when deleting the selection:
+//
+// Builds a list of the selected rooms and all edges which connect to them.
+//
+// Input:
+//   doc:              The document we're working with
+//   numSelected:      The number of selected rooms
+//   selectedRooms:    TRUE means the room is selected
+//   numSelectedPages: The number of selected pages
+//   selectedPages:    TRUE means the page is selected
+//
+UndoDelete::UndoDelete(const MapDoc & doc, int numSelected, const ByteVec & selectedRooms, int numSelectedPages, const ByteVec & selectedPages): UndoRec(doc.isDirty)
+{
+       int i;
+       addRooms(numSelected);
+       EdgeConstItr edge = doc.edge.begin();
+
+       for(i=doc.edge.size()-1; i>=0; edge++, i--)
+       {
+               if (selectedRooms[edge->room1] || selectedRooms[edge->room2])
+                       edges.push_back(*edge);
+       }
+
+       pages.resize(numSelectedPages);
+       pageNums.resize(numSelectedPages);
+       PageConstItr page = doc.page.begin();
+       PageItr p = pages.begin();
+       PNItr pn = pageNums.begin();
+
+       for(i=0; numSelectedPages; i++)
+       {
+               if (selectedPages[i])
+               {
+                       numSelectedPages--;
+                       *(p++) = page[i];
+                       *(pn++) = i;
+               }
+       }
+}
+
+
+//
+// Constructor used for the redo record for UndoAdd:
+//
+// Input:
+//   doc:          The document we're working with
+//   numEdges:     The number of edges being deleted
+//   theRoomNums:  The numbers of the rooms being deleted
+//   thePageNums:  The numbers of the pages being deleted
+//
+UndoDelete::UndoDelete(const MapDoc & doc, int numEdges, const RoomNumVec & theRoomNums, const PageNumVec & thePageNums): UndoRec(doc.isDirty), pageNums(thePageNums), roomNums(theRoomNums)
+{
+       edges.insert(edges.begin(), doc.edge.end() - numEdges, doc.edge.end());
+
+       RoomConstItr  room = doc.room.getVector().begin();
+       rooms.reserve(roomNums.size());
+
+       for(RNConstItr r=roomNums.begin(); r!=roomNums.end(); ++r)
+               rooms.append(new MapRoom(*room[*r]));
+
+       PageConstItr page = doc.page.begin();
+       pages.reserve(pageNums.size());
+
+       for(PNConstItr p=pageNums.begin(); p!=pageNums.end(); ++p)
+               pages.push_back(page[*p]);
+}
+
+
+//
+// Constructor used when deleting one edge:
+//
+// Input:
+//   modified:  The modification state of the document
+//   edge:      The edge that was deleted
+//
+UndoDelete::UndoDelete(bool modified, const MapEdge & edge): UndoRec(modified)
+{
+       edges.push_back(edge);
+}
+
+
+void UndoDelete::addRoom(VecSize pos, RoomNum n, MapRoom * r)
+{
+       roomNums[pos] = n;
+       rooms.getVector()[pos] = r;
+}
+
+
+void UndoDelete::addRooms(VecSize n)
+{
+       rooms.resize(n);
+       roomNums.resize(n);
+}
+
+
+//
+// Constructor used when deleting several edges:
+//
+// Input:
+//   modified:  The modification state of the document
+//   theEdges:  The edges that were deleted
+//
+// Output:
+//   theEdges:  The vector is emptied
+//
+UndoDelete::UndoDelete(bool modified, EdgeVec & theEdges): UndoRec(modified)
+{
+       edges.swap(theEdges);
+}
+
+
+const char * UndoDelete::getName() const
+{
+       return "Deletion";
+}
+
+
+//
+// Undo the deletion of rooms and/or edges:
+//
+UndoRec * UndoDelete::undoChange(MapDoc & doc)
+{
+       ASSERT(rooms.size() == roomNums.size());
+       doc.addRooms(rooms.size());
+       RoomItr r = rooms.getVector().begin();
+
+       for(RNConstItr rn=roomNums.begin(); rn!=roomNums.end(); r++, rn++)
+       {
+               doc.addRoom(*rn, *r);
+               *r = NULL;
+       }
+
+       doc.addEdges(edges.size());
+
+       for(EdgeConstItr e=edges.begin(); e!=edges.end(); ++e)
+               doc.addEdge(*e);
+
+       ASSERT(pages.size() == pageNums.size());
+       doc.addPages(pages.size());
+       PageConstItr p = pages.begin();
+
+       for(PNConstItr pn=pageNums.begin(); pn!=pageNums.end(); p++, pn++)
+               doc.addPage(*pn, *p);
+
+       return new UndoAdd(true, edges.size(), roomNums, pageNums);
+}
+
+
+//
+// Class UndoMove:
+//
+// UndoMove undoes room movement.
+//
+// Member Variables:
+//   offset:    The amount the rooms were moved
+//   pageNums:  The numbers of the pages that were moved
+//   roomNums:  The numbers of the rooms that were moved
+//--------------------------------------------------------------------
+// Constructor used after dragging the selection:
+//
+// Input:
+//   modified:          The modification state of the document
+//   moved:             The distance the rooms were moved
+//   numSelected:       The number of selected rooms
+//   selectedRooms:     TRUE means the room is selected
+//   numSelectedPages:  The number of selected pages
+//   selectedPages:     TRUE means the page is selected
+//
+UndoMove::UndoMove(bool modified, const QSize & moved, int numSelected, const ByteVec & selectedRooms, int numSelectedPages, const ByteVec & selectedPages): UndoRec(modified), offset(moved)
+{
+       int i, j;
+       pageNums.resize(numSelectedPages);
+
+       for(i=0, j=0; i<numSelectedPages; j++)
+       {
+               if (selectedPages[j])
+                       pageNums[i++] = j;
+       }
+
+       roomNums.resize(numSelected);
+
+       for(i=0, j=0; i<numSelected; j++)
+       {
+               if (selectedRooms[j])
+                       roomNums[i++] = j;
+       }
+}
+
+
+//
+// Constructor used for the redo record:
+//
+// Input:
+//   modified:  The modification state of the document
+//   moved:     The distance the rooms were moved
+//   rooms:     The numbers of the rooms being moved
+//   pages:     The numbers of the pages being moved
+//
+// Output:
+//   rooms & pages:  Empty
+//
+UndoMove::UndoMove(bool modified, const QSize & moved, RoomNumVec & rooms, PageNumVec & pages): UndoRec(modified), offset(moved)
+{
+       pageNums.swap(pages);
+       roomNums.swap(rooms);
+}
+
+
+const char * UndoMove::getName() const
+{
+       return "Move";
+}
+
+
+//
+// Undo room movement
+//
+UndoRec * UndoMove::undoChange(MapDoc & doc)
+{
+       offset *= -1;
+
+       for(PNConstItr p=pageNums.begin(); p!=pageNums.end(); p++)
+               doc.movePage(*p, offset);
+
+       for(RNConstItr r=roomNums.begin(); r!=roomNums.end(); r++)
+               doc.moveRoom(*r, offset);
+
+       return new UndoMove(false, offset, roomNums, pageNums);
+}
+
+
+//
+// Class UndoPaginate:
+//
+// UndoPaginate undoes a repagination.
+//
+// Member Variables:
+//   pages:  The old pages
+//--------------------------------------------------------------------
+// Constructor:
+//
+// This removes the pages from the document.
+//
+// Input:
+//   doc:  The document we're working with
+//
+UndoPaginate::UndoPaginate(MapDoc & doc): UndoRec(doc.isDirty)
+{
+       pages.swap(doc.page);
+}
+
+
+const PageVec & UndoPaginate::getPages() const
+{
+       return pages;
+}
+
+
+const char * UndoPaginate::getName() const
+{
+       return "Page Layout";
+}
+
+
+//
+// Undo the deletion of rooms and/or edges:
+//
+UndoRec * UndoPaginate::undoChange(MapDoc & doc)
+{
+       UndoRec * redo = new UndoPaginate(doc);
+       pages.swap(doc.page);
+       doc.UpdateAllViews(NULL, dupPageCount, NULL);
+
+       return redo;
+}
+
+
+//
+// Class UndoRoomInfo:
+//
+// UndoRoomInfo undoes changes made in the Room Properties dialog.
+// Currently, this is only the room name.
+//
+// Member Variables:
+//   num:   The number of the room that was modified
+//   room:  The room information
+//--------------------------------------------------------------------
+// Constructor:
+//
+// Input:
+//   doc:      The document we're working with
+//   roomNum:  The number of the room being modified
+//
+UndoRoomInfo::UndoRoomInfo(const MapDoc & doc, RoomNum roomNum): UndoRec(doc.isDirty), num(roomNum), room(doc.room[roomNum])
+{
+}
+
+
+const char * UndoRoomInfo::getName() const
+{
+       return "Room Information";
+}
+
+
+//
+// Undo a change in room information:
+//
+UndoRec * UndoRoomInfo::undoChange(MapDoc & doc)
+{
+       UndoRec * redo = new UndoRoomInfo(doc, num);
+
+       doc.setRoomName(num, room.name.c_str());
+       doc.setRoomNote(num, room.note.c_str());
+
+       return redo;
+}
+
diff --git a/src/undo.h b/src/undo.h
new file mode 100644 (file)
index 0000000..35db9e8
--- /dev/null
@@ -0,0 +1,158 @@
+//
+// GUEmap
+// Copyright 1997-2007 by Christopher J. Madsen
+// (C) 2019 James Hammons
+//
+// GUEmap is licensed under either version 2 of the GPL, or (at your option)
+// any later version.  See LICENSE file for details.
+//
+// undo.h: interface of UndoRec and its derived classes
+//
+
+#ifndef __UNDO_H__
+#define __UNDO_H__
+
+#include "globals.h"
+
+class MapDoc;
+
+
+class UndoRec
+{
+       public:
+               bool oldModified;
+
+       public:
+               UndoRec(bool modified);
+               virtual ~UndoRec();
+
+               // Functions unique to UndoRec
+               UndoRec * undo(MapDoc & doc);
+
+               // Pure virtual functions (MUST be implemented by derived classes)
+               virtual const char * getName() const = 0;
+               virtual UndoRec * undoChange(MapDoc & doc) = 0;
+};
+
+
+class UndoAdd: public UndoRec
+{
+       public:
+               int edges;
+               PageNumVec pageNums;
+               RoomNumVec roomNums;
+
+       public:
+               UndoAdd(bool modified, int edgeCount, RoomNumVec & theRoomNums, PageNumVec & thePageNums);
+               UndoAdd(bool modified, RoomNum roomNum, int edgeCount);
+               UndoAdd(bool modified, short pageNum);
+               UndoAdd(bool modified);
+
+               // Overloaded functions
+               const char * getName() const;
+               UndoRec * undoChange(MapDoc & doc);
+};
+
+
+class UndoChangeEdge: public UndoRec
+{
+       public:
+               MapEdge edge;
+
+       public:
+               UndoChangeEdge(bool modified, const MapEdge & theEdge);
+
+               // Overloaded functions
+               const char * getName() const;
+               UndoRec * undoChange(MapDoc & doc);
+};
+
+
+class UndoDelete: public UndoRec
+{
+       public:
+               EdgeVec edges;
+               PageVec pages;
+               PageNumVec pageNums;
+               RoomArray rooms;
+               RoomNumVec roomNums;
+
+       public:
+               UndoDelete(const MapDoc & doc, int numSelected, const ByteVec & selectedRooms, int numSelectedPages, const ByteVec & selectedPages);
+               UndoDelete(const MapDoc & doc, int numEdges, const RoomNumVec & theRoomNums, const PageNumVec & thePageNums);
+               UndoDelete(bool modified, const MapEdge & edge);
+               UndoDelete(bool modified, EdgeVec & theEdges);
+               void addRoom(VecSize pos, RoomNum n, MapRoom * r);
+               void addRooms(VecSize n);
+
+               // Overloaded functions
+               const char * getName() const;
+               UndoRec * undoChange(MapDoc & doc);
+};
+
+
+class UndoChanges: public UndoRec
+{
+       public:
+               UndoRec * add;
+               UndoRec * del;
+
+       public:
+               UndoChanges(bool modified, RoomNum roomNum, int edgeCount, const MapEdge & edge);
+               UndoChanges(bool modified, int roomNum, int edgeCount, EdgeVec & edges);
+               UndoChanges(bool modified, RoomNum roomNum, MapRoom * room, EdgeVec & edges);
+
+               // Overloaded functions
+               const char * getName() const;
+               UndoRec * undoChange(MapDoc & doc);
+};
+
+
+class UndoMove: public UndoRec
+{
+       public:
+               QSize offset;
+               PageNumVec pageNums;
+               RoomNumVec roomNums;
+
+       public:
+               UndoMove(bool modified, const QSize & moved, int numSelected, const ByteVec & selectedRooms, int numSelectedPages, const ByteVec & selectedPages);
+               UndoMove(bool modified, const QSize & moved, RoomNumVec & rooms, PageNumVec & pages);
+
+               // Overloaded functions
+               const char * getName() const;
+               UndoRec * undoChange(MapDoc & doc);
+};
+
+
+class UndoPaginate: public UndoRec
+{
+       public:
+               PageVec pages;
+
+       public:
+               UndoPaginate(MapDoc & doc);
+               const PageVec & getPages() const;
+
+               // Overloaded functions
+               const char * getName() const;
+               UndoRec * undoChange(MapDoc & doc);
+};
+
+
+class UndoRoomInfo : public UndoRec
+{
+       public:
+               RoomNum num;
+               MapRoom room;
+
+       public:
+               UndoRoomInfo(const MapDoc & doc, RoomNum roomNum);
+
+               // Overloaded functions
+               const char * getName() const;
+               UndoRec * undoChange(MapDoc & doc);
+};
+
+#endif // __UNDO_H__
+
diff --git a/web/guemap.css b/web/guemap.css
new file mode 100644 (file)
index 0000000..dacaf6c
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+
+CSS for GUEmap
+
+*/
+
+body
+{
+       color: #007F00;
+       background-color: #C0FFF0;
+       font: 12.0pt verdana, sans-serif;
+       text-align: center; /* Crappy hack for IE--die, Die, DIE! */
+}
+
+h1
+{
+       font-size: 48pt;
+       font-weight: bold;
+       color: #FF0000;
+}
+
+h2
+{
+       font-size: 20pt;
+       font-weight: bold;
+       color: #FF7F00;
+}
+
+p, ul, h3
+{
+       text-align: left;
+}
+
+p#footer
+{
+       margin-top: 40px;
+       color: #AF0000;
+       font-size: 10pt;
+       font-style: italic;
+}
+
diff --git a/web/index.html b/web/index.html
new file mode 100644 (file)
index 0000000..1488b02
--- /dev/null
@@ -0,0 +1,86 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>GUEmap - Free IF Mapping Software</title>
+
+<meta name="description" content="GUEmap Homepage">
+<meta name="keywords" content="interactive, fiction, mapping, software, multi-platform, free">
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+<meta name="generator" content="Kate">
+
+<link rel="stylesheet" href="guemap.css" type="text/css">
+</head>
+
+<body class="mainpage">
+
+<h1>GUEmap</h1>
+
+<h2>Free Multi-platform IF Mapping Software</h2>
+
+<hr>
+
+<h3>History</h3>
+
+<p>Anyone remember <a href="https://www.cjmweb.net/GUEmap/">GUEmap</a>?&ensp;It was crippleware (and I mean <i>seriously</i> crippled crippleware) mapping software, written by <a href="https://www.cjmweb.net/">Christopher J. Madsen</a>, that was pretty much the only game in town if you wanted a piece of software to make maps of your forays into the realms of Interactive Fiction.&ensp;And so, having added hard drive support to <a href="http://shamusworld.gotdns.org/apple2/">Apple2</a> in order to be able to play 4am&rsquo;s Pitch Dark (and finally get around to finishing up <i>Planetfall</i>), I cast about for some mapping software&mdash;and found that the only thing available was&hellip;&ensp;GUEmap.</p>
+
+<p>So a quick visit to the GUEmap homepage showed that it hadn&rsquo;t been developed for well over twelve years.&ensp;And before the Peanut Gallery pipes up with witty aphorisms about people living in glass houses throwing stones and such, let me just say that I understand perfectly just how difficult it is to keep your motivation going on long lived projects like these.&ensp;So there.</p>
+
+<p>So, feeling a pang of mild discouragement, I downloaded v2 of GUEmap and gave it a whirl; and it was just as bad as I remembered it.&ensp;It was unintuitive, difficult to use and klunky.&ensp;To me this was sad, as it could have been <i>much</i> better with just a few changes here and there, but, alas, this was not to be.</p>
+
+<h3>The Plot Thickens (With A Little Added Cornstarch)</h3>
+
+<p>Or was it?&ensp;I noticed, with a rasied eyebrow, that there was a source code archive available, and it had been released under the GPL!&ensp;This was, in a word, interesting.&ensp;And so, my curiosity having been piqued, I downloaded the source and took a look and oh, my&hellip;</p>
+
+<p>Now before I ever saw the source, GUEmap had a few things going for it (like being the only real mapping software available) and a few things going against it (like being written for Windows only, and being crippleware).&ensp;A quick look at the source showed it had yet another thing going against it: it was written in Microsoft Foundation Classes (or MFC for short).</p>
+
+<p>When future historians look back on the history of software development they will find that we were living and coding in a Dark Age, and that one of the worst implements of torture devised to torment programmers of that era will be found to have been MFC.&ensp;Scoff all you want, I lived through those times and recall them with horror; even the mere <i>mention</i> of MFC still causes me to shudder to this day.</p>
+
+<p>So, in weighing my options, I briefly considered porting it to WxWidgets, but, having inhabited that Circle of Hell for a season, I decided against it.&ensp;Yes, it probably would have been far easier to get it working that way, but at what cost?&ensp;Especially since WxWidgets is basically an open source version of MFC.&ensp;So that was right out.&ensp;Also out was keeping it as a Windows only program, as I long ago learned that writing cross-platform code is the only way to keep one&rsquo;s hard-earned code from the evils of bit-rot and platform lock-in.&ensp;So that left pretty much Qt as the only viable option.</p>
+
+<p>Now I know that quite a few people seem to have this irrational hatred of Qt, which I absolutely cannot fathom.&ensp;But it&rsquo;s just that, irrational.&ensp;Qt provides a first-class framework for application development that pretty much gives you cross-platform executables for free.&ensp;It truly is a remarkable piece of middleware and I will miss it when it&rsquo;s gone.</p>
+
+<h3>Development Begins In Earnest</h3>
+
+<p>And so I dug into the code, converting it to Qt as best as I could.&ensp;It became apparent early on that this would be nigh on impossble to do all in one fell swoop, so I decided to see if I could get something minimal working in order to assess how much work it would be and whether or not it would be worth doing.&ensp;And so I found a copy of an old GUEmap file of <i>Zork I</i> that I had kicking around after all these years and set about hardcoding it to load.&ensp;Once I had that, I dug into the document code and the view code (mapdoc and mapview respectively).&ensp;If I could get that to properly display the map on my Qt application window I knew I could get the rest working fairly easily.</p>
+
+<p>Converting code like this is usually a tedious slog, and this was no exception.&ensp;I was able to figure out the endianness of the file format (as it used MFC&rsquo;s serialization code for saving and loading) and remove all the C++isms from the loading code.&ensp;It seemed like it was successfully loading and parsing my <i>Zork I</i> file (and some well placed logging seemed to confirm this), so I pressed on with getting the view code compiling.</p>
+
+<p>To accomplish this, I pretty much commented out all the code except for the drawing code and whatever functions it called.&ensp;Eventually I was able to get the compiler if not happy with, at least tolerant of the code at that point.&ensp;So I turned my attention to the document code and slogged my way through that as well.&ensp;I could see that I was going to have to make decision here whether or not to cut out all the undo code, and, following the Programmer&rsquo;s Prime Dictum (that being to do as little as possible), decided it would be less work to keep it in than to excise it and add it in later.</p>
+
+<p>So eventually, after much adjustment and porting, I was able to get the compiler to grudgingly compile a minimal set of code that would load a file (hardcoded to load a specific file) and display it on the screen.&ensp;I fired it up, and saw&hellip;&ensp;Nothing.</p>
+
+<p>Now a trap that some programmers fall into when coding up an application like this is to get hung up on the fact that while the X-axis conforms to the expected Cartesian ideal, the Y-axis is inverted with respect to said ideal.&ensp;And indeed, it looked like Mr. Madsen had fallen into this particular trap.&ensp;But really, for a simple application like GUEmap, this was completely unnecessary and an unwanted complication that added nothing.&nbsp;So, having determined this, I quickly coded up a transformation in Qt&rsquo;s rendering code to mimic what he had done in MFC and lo and behold!&ensp;It drew something!</p>
+
+<p>What it drew was basically the "edges" (the lines connecting rooms to each other) of the map and nothing else.&ensp;But this was progress, at least.&ensp;But I knew this would only be a stop-gap measure at best, because I had run into this kind of thing in coding up <a href="http://shamusworld.gotdns.org/architektonas/">Architektonas</a>.</p>
+
+<p>One major difference between the way Windows renders things using a coordinate transform to invert the Y-axis and the way Qt does it, is that Qt renders its text paths through the transformation while Windows does not.&nbsp;Which means that if you really need to have an non-inverted Y-axis (i.e., Cartesian), using a simple approach of using a transformation matrix to invert the Y-axis will give you <i>inverted</i> text; this is a consequence of Qt&rsquo;s rendering being a <i>left-handed</i> system and, as such, it cannot be made into a right-handed system no matter what you do&mdash;it&rsquo;s a mathematical impossibility.</p>
+
+<p>And so, once I had the rooms and text labels displaying properly, the text was inverted as I knew it would be.&nbsp;So then I had to make yet another decision, and it seemed clear to me that the right way forward was to remove the trap from the code and keep the Y-axis inverted (from the Cartesian POV).&ensp;Fortunately for me, it was fairly easy to do so and the major problem that I foresaw, that being the un-inverted Y-axis being baked into the file format, turned out to be mostly a non-issue as Mr. Madsen was inverting the Y-coordinates as he read the file from disk.&ensp;It wasn&rsquo;t fully a non-issue, as there were some side-effects that sprang from the un-inverted Y-axis assumption, such as the bottom of rooms being treated as the top (hello, unnecessary complication!).&ensp;Once I addressed all these problems, I had the map drawing as it should.&ensp;Progress!</p>
+
+<p>So then, the next tentative thing to tackle was to see if I could translate the mouse loops without too much difficulty.&ensp;I decided to convert the mouse down logic first, and took a similar approach to the rendering code in that I commented out the body of the function and added back in things little by little.&ensp;Once I had the mouse down logic compiling, I decided to see if I could make it select and deselect rooms on the map; after finding and fixing all the places where it was checking for an inverted Y-axis, I was successful.</p>
+
+<p>And so it went with the mouse move and mouse up functions; eventually I was able to pretty much restore all the missing functionality that made it able to create and edit maps.&ensp;So, after a few days of hacking on the conversion, I could see it starting to pay dividends and that finishing it up would likely be worth the effort.</p>
+
+<h3>Current Development</h3>
+
+<p>And so, I am proud to present GUEmap v3!&ensp;It will load v1 and v2 maps, but only save in v3 format.&ensp;Thanks to Christopher J. Madsen for opening up the source and, in keeping with the license for GUEmap v2, GUEmap v3 (and its successors) is also licensed under the GPL v2 or later.</p>
+
+<p>Binary downloads will be coming soon.</p>
+
+<p>If you don&rsquo;t mind working with semi-broken code, you can check out and compile it on your own. The GIT repository is located at:</p>
+
+<p class="url">https://shamusworld.gotdns.org/git/guemap</p>
+
+<p>If you choose to go this route, you&rsquo;re on your own for now until I can set up a reliable line of communication. Good Luck!</p>
+
+<p></p>
+
+<hr>
+
+<p id="footer">* If the images and text on this site look wrong and/or strange, then you&rsquo;re probably not using a standards compliant browser. You can get a great<sup>&dagger;</sup> one for free <a href="http://www.mozilla.org/products/firefox/">here</a>!</p>
+
+<p id="footer">&dagger; Ah, the days when that was written and that was true and Firefox was lightweight and fast and small.&ensp;Now, we&rsquo;re all pretty much stuck with browsers that are huge, bloated, slow, and riddled through with spyware and other junk.&ensp;I still use Firefox these days, but only because it sucks the least out of all the alternatives&mdash;a truly deplorable situation!</p>
+</body>
+
+</html>