]> Shamusworld >> Repos - guemap/blob - src/guemapapp.cpp
5f0a5da19c9844c891d67f0746cbe18743ad8a53
[guemap] / src / guemapapp.cpp
1 //
2 // GUEmap
3 // Copyright 1997-2007 by Christopher J. Madsen
4 // (C) 2019 James Hammons
5 //
6 // GUEmap is licensed under either version 2 of the GPL, or (at your option)
7 // any later version.  See LICENSE file for details.
8 //
9 // GUEmap.cpp: Defines the class behaviors for the application
10 //
11
12 #include "guemapapp.h"
13
14 #include <QApplication>
15 #include "mainwin.h"
16
17 // Main app constructor--we stick globally accessible stuff here...
18
19 GUEMapApp::GUEMapApp(int & argc, char * argv[]): QApplication(argc, argv)//, charWnd(NULL)
20 {
21         mainWindow = new MainWin;
22         mainWindow->show();
23 }
24
25
26 // Here's the main application loop--short and simple...
27 int main(int argc, char * argv[])
28 {
29         Q_INIT_RESOURCE(guemap);        // This must the same name as the qrc filename
30
31         GUEMapApp app(argc, argv);
32
33         return app.exec();
34 }
35
36 #if 0
37 #include "StdAfx.hpp"
38 #include "GUEmap.hpp"
39
40 #include <winspool.h>           // For printer settings
41
42 #include "MainFrm.hpp"
43 #include "ChildFrm.hpp"
44 #include "CommentDlg.hpp"
45 #include "FindDlg.hpp"
46 #include "MapDoc.hpp"
47 #include "MapView.hpp"
48 #include "NavOpt.hpp"
49
50 #ifdef _DEBUG
51 #define new DEBUG_NEW
52 #undef THIS_FILE
53 static char THIS_FILE[] = __FILE__;
54 #endif
55
56 bool     GUEmapEatClicks = true;
57 HCURSOR  handCursor = NULL;
58
59 const char *const iniCDlg        = "CommentDialog";
60 const char *const iniMainWin     = "MainWindow";
61 const char *const iniWinLeft     = "Left";
62 const char *const iniWinRight    = "Right";
63 const char *const iniWinTop      = "Top";
64 const char *const iniWinBottom   = "Bottom";
65
66 const char *const iniNavigation  = "Navigation";
67 const char *const iniNavCopy     = "AutoCopy";
68 const char *const iniNavCRLF     = "AddCRLF";
69 const char *const iniNavEdit     = "AutoEdit";
70 const char *const iniNavStub     = "PreferUnexplored";
71
72 /////////////////////////////////////////////////////////////////////////////
73 // Miscellaneous functions:
74 //--------------------------------------------------------------------
75 // Copy a string, replacing CRLF with space:
76 //
77 // Assumes that any CR is followed by a LF.
78 //
79 // Input:
80 //   source:  The string to copy
81 //
82 // Output:
83 //   dest:    The copied string, with any CRLF replaced by a space
84
85 void copyToOneLine(String& dest, const String& source)
86 {
87   dest = source;
88   StrIdx  p = 0;
89   while ((p = dest.find(_T('\r'),p)) != String::npos) {
90     dest.erase(p,1);
91     dest[p] = _T(' ');
92   }
93 } // end copyToOneLine
94
95 //--------------------------------------------------------------------
96 // Fill paragraphs in a string:
97 //
98 // Removes all leading and trailing space, then removes CRLF pairs
99 // unless they are on a blank line or followed by a space.  Assumes
100 // that any CR is followed by a LF.
101 //
102 // Input:
103 //   s:  The string to fill
104 //
105 // Output:
106 //   s:  The filled string
107
108 void fillParagraphs(String& s)
109 {
110   trimRight(s);
111   trimLeft(s);
112
113   StrIdx  p = 0;
114   // Convert tabs to spaces (just in case):
115   while ((p = s.find(_T('\t'),p)) != String::npos)
116     s[p] = _T(' ');
117
118   p = 0;
119   while ((p = s.find(_T('\r'),p)) != String::npos) {
120     if ((s[p-1] != _T('\n')) &&                       // Not a blank line &
121         s.find_first_not_of(_T("\n\r "), p) == p+2) { // next char not a space
122       StrIdx  len = 1;
123       while (s[p-1] == _T(' ')) {
124         --p;
125         ++len;
126       } // end while spaces precede this CR
127       s.erase(p,len);           // Erase the CR and preceding spaces
128       s[p] = _T(' ');           // Change the LF to a space
129     } else
130       ++p; // Don't remove this CRLF
131   } // end while more CRs
132 } // end fillParagraphs
133
134 //--------------------------------------------------------------------
135 // Set a window's position:
136 //
137 // Input:
138 //   pos:  The new position of the window (in screen coordinates)
139 //   w:    The window to move
140 //
141 // Note:
142 //   If pos.right is 0, or any part of the window is outside the
143 //   current desktop, then we don't move the window.
144
145 void setWindowPos(const CRect& pos, CWnd* w)
146 {
147   if (pos.right && (pos.left < pos.right) && (pos.top < pos.bottom)) {
148     CRect desktop;
149     ::GetWindowRect(::GetDesktopWindow(),&desktop);
150     if ((pos.left   >= desktop.left)  &&
151         (pos.right  <= desktop.right) &&
152         (pos.top    >= desktop.top)   &&
153         (pos.bottom <= desktop.bottom))
154       w->MoveWindow(&pos);
155   }
156 } // end setWindowPos
157
158 //--------------------------------------------------------------------
159 // Trim whitespace from left of string:
160
161 void trimLeft(String& s)
162 {
163   StrIdx  p = s.find_first_not_of(_T("\n\r\t "));
164   if (p > 0) s.erase(0, p);
165 } // end trimLeft
166
167 //--------------------------------------------------------------------
168 // Trim whitespace from right of string:
169
170 void trimRight(String& s)
171 {
172   StrIdx  p = s.find_last_not_of(_T("\n\r\t "));
173   if (p < s.length()) s.erase(p+1);
174 } // end trimRight
175
176 /////////////////////////////////////////////////////////////////////////////
177 // CMapApp
178
179 BEGIN_MESSAGE_MAP(CMapApp, CWinApp)
180         //{{AFX_MSG_MAP(CMapApp)
181         ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
182         ON_COMMAND(ID_EDIT_FIND, OnEditFind)
183         ON_COMMAND(ID_VIEW_COMMENTS, OnViewComments)
184         ON_COMMAND(ID_VIEW_SETTINGS, OnViewSettings)
185         ON_UPDATE_COMMAND_UI(ID_VIEW_COMMENTS, OnUpdateViewComments)
186         //}}AFX_MSG_MAP
187         // Standard file based document commands
188         ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
189         ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
190         // Standard print setup command
191         ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
192         // Standard help commands (see also MainFrame.cpp)
193         ON_COMMAND(ID_HELP_INDEX, CWinApp::OnHelpIndex)
194 END_MESSAGE_MAP();
195
196 /////////////////////////////////////////////////////////////////////////////
197 // CMapApp construction
198
199 CMapApp::CMapApp()
200 : clipboard(NULL),
201   commentDlg(NULL),
202   findDlg(NULL),
203   initialized(false)
204 {
205         // TODO: add construction code here,
206         // Place all significant initialization in InitInstance
207 }
208
209 CMapApp::~CMapApp()
210 {
211   delete clipboard;
212   delete commentDlg;
213   delete findDlg;
214 } // end CMapApp::~CMapApp
215
216 /////////////////////////////////////////////////////////////////////////////
217 // The one and only CMapApp object
218
219 CMapApp theApp;
220 String  mapWinClass;
221
222 /////////////////////////////////////////////////////////////////////////////
223 // CMapApp initialization
224
225 BOOL CMapApp::InitInstance()
226 {
227   // Standard initialization
228   // If you are not using these features and wish to reduce the size
229   //  of your final executable, you should remove from the following
230   //  the specific initialization routines you do not need.
231
232 #ifdef _AFXDLL
233   Enable3dControls();           // Call this when using MFC in a shared DLL
234 #else
235   Enable3dControlsStatic();     // Call this when linking to MFC statically
236 #endif
237
238   mapWinClass = AfxRegisterWndClass(
239     CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW,
240     LoadStandardCursor(IDC_ARROW), // Standard cursor
241     (HBRUSH)::GetStockObject(WHITE_BRUSH)
242   );
243
244   handCursor = LoadCursor(IDC_HAND);
245
246   LoadStdProfileSettings(9); // Load standard INI file options (including MRU)
247
248   // Register the application's document templates.  Document templates
249   //  serve as the connection between documents, frame windows and views.
250
251   CMultiDocTemplate* pDocTemplate;
252   pDocTemplate = new CMultiDocTemplate(
253     IDR_GUEMAPTYPE,
254     RUNTIME_CLASS(CMapDoc),
255     RUNTIME_CLASS(CChildFrame), // custom MDI child frame
256     RUNTIME_CLASS(CMapView));
257   AddDocTemplate(pDocTemplate);
258
259   // create room comments dialog
260   if (!(commentDlg = new CCommentDlg()))
261     return FALSE;
262
263   loadWindowPos(commentDlg->pos, iniCDlg);
264
265   // create main MDI Frame window
266   CMainFrame* pMainFrame = new CMainFrame;
267   if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
268     return FALSE;
269   m_pMainWnd = pMainFrame;
270
271   // Enable file manager drag/drop and DDE Execute open
272   EnableShellOpen();
273   RegisterShellFileTypes(TRUE);
274
275   // Parse command line for standard shell commands, DDE, file open
276   CCommandLineInfo cmdInfo;
277   ParseCommandLine(cmdInfo);
278
279   // Dispatch commands specified on the command line
280   if (!ProcessShellCommand(cmdInfo))
281     return FALSE;
282
283   // The main window has been initialized, so show and update it.
284   CRect  pos;
285   loadWindowPos(pos, iniMainWin);
286   setWindowPos(pos, pMainFrame);
287   pMainFrame->ShowWindow(m_nCmdShow);
288   pMainFrame->UpdateWindow();
289   pMainFrame->DragAcceptFiles(TRUE);
290
291   setLandscape();               // Switch to landscape mode
292
293   // Load navigation options:
294   autoEdit = GetProfileInt(iniNavigation,iniNavEdit,true);
295   naviCopy = GetProfileInt(iniNavigation,iniNavCopy,true);
296   naviCRLF = GetProfileInt(iniNavigation,iniNavCRLF,false);
297   preferUnexplored = GetProfileInt(iniNavigation,iniNavStub,true);
298
299   initialized = true;
300   return TRUE;
301 } // end CMapApp::InitInstance
302
303 //--------------------------------------------------------------------
304 // Load a window position from the INI file:
305 //
306 // Input:
307 //   section:  The title of the section to load it from
308 //
309 // Output:
310 //   pos:  The window position
311
312 void CMapApp::loadWindowPos(CRect& pos, LPCTSTR section)
313 {
314   pos.left   = GetProfileInt(section, iniWinLeft,   0);
315   pos.right  = GetProfileInt(section, iniWinRight,  0);
316   pos.top    = GetProfileInt(section, iniWinTop,    0);
317   pos.bottom = GetProfileInt(section, iniWinBottom, 0);
318 } // end CMapApp::loadWindowPos
319
320 //--------------------------------------------------------------------
321 // Set landscape mode as default:
322 //
323 // From KB: Q126897 (1-21-96)
324
325 void CMapApp::setLandscape()
326 {
327   // Get default printer settings.
328   PRINTDLG   pd;
329
330   pd.lStructSize = (DWORD) sizeof(PRINTDLG);
331   if (GetPrinterDeviceDefaults(&pd)) {
332     // Lock memory handle.
333     DEVMODE FAR* pDevMode =
334       (DEVMODE FAR*)::GlobalLock(m_hDevMode);
335     LPDEVNAMES lpDevNames;
336     LPTSTR lpszDriverName, lpszDeviceName, lpszPortName;
337     HANDLE hPrinter;
338
339     if (pDevMode) {
340       // Change printer settings in here.
341       pDevMode->dmOrientation = DMORIENT_LANDSCAPE;
342       // Unlock memory handle.
343       lpDevNames = (LPDEVNAMES)GlobalLock(pd.hDevNames);
344       lpszDriverName = (LPTSTR )lpDevNames + lpDevNames->wDriverOffset;
345       lpszDeviceName = (LPTSTR )lpDevNames + lpDevNames->wDeviceOffset;
346       lpszPortName   = (LPTSTR )lpDevNames + lpDevNames->wOutputOffset;
347
348       ::OpenPrinter(lpszDeviceName, &hPrinter, NULL);
349       ::DocumentProperties(NULL,hPrinter,lpszDeviceName,pDevMode,
350                            pDevMode, DM_IN_BUFFER|DM_OUT_BUFFER);
351
352       // Sync the pDevMode.
353       // See SDK help for DocumentProperties for more info.
354       ::ClosePrinter(hPrinter);
355       ::GlobalUnlock(m_hDevNames);
356       ::GlobalUnlock(m_hDevMode);
357     }
358   }
359 } // end CMapApp::setLandscape
360
361 /////////////////////////////////////////////////////////////////////////////
362 // CMapApp shutdown:
363
364 int CMapApp::ExitInstance()
365 {
366   if (initialized) { // We finished initialization
367     WriteProfileInt(iniNavigation, iniNavEdit, autoEdit);
368     WriteProfileInt(iniNavigation, iniNavCopy, naviCopy);
369     WriteProfileInt(iniNavigation, iniNavCRLF, naviCRLF);
370     WriteProfileInt(iniNavigation, iniNavStub, preferUnexplored);
371
372     if (commentDlg) {
373       if (commentDlg->GetSafeHwnd())
374         OnViewComments();       // Destroy comment window
375       saveWindowPos(commentDlg->pos, iniCDlg);
376     }
377
378     if (findDlg && findDlg->GetSafeHwnd())
379       findDlg->DestroyWindow();
380   }
381
382   return CWinApp::ExitInstance();
383 } // end CMapApp::ExitInstance
384
385 //--------------------------------------------------------------------
386 // Save a window position to the INI file:
387 //
388 // Input:
389 //   pos:      The window position
390 //   section:  The title of the section to save it in
391
392 void CMapApp::saveWindowPos(LPCRECT pos, LPCTSTR section)
393 {
394   WriteProfileInt(section, iniWinLeft,   pos->left);
395   WriteProfileInt(section, iniWinRight,  pos->right);
396   WriteProfileInt(section, iniWinTop,    pos->top);
397   WriteProfileInt(section, iniWinBottom, pos->bottom);
398 } // end CMapApp::saveWindowPos
399
400 /////////////////////////////////////////////////////////////////////////////
401 // CAboutDlg dialog used for App About
402
403 class CAboutDlg : public CDialog
404 {
405  public:
406   CAboutDlg();
407
408   // Dialog Data
409   //{{AFX_DATA(CAboutDlg)
410   enum { IDD = IDD_ABOUTBOX };
411   CString       username;
412   //}}AFX_DATA
413
414   // ClassWizard generated virtual function overrides
415   //{{AFX_VIRTUAL(CAboutDlg)
416  protected:
417   virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
418   //}}AFX_VIRTUAL
419
420   // Implementation
421  protected:
422   //{{AFX_MSG(CAboutDlg)
423   //}}AFX_MSG
424   DECLARE_MESSAGE_MAP();
425 }; // end CAboutDlg
426
427 CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
428 {
429         //{{AFX_DATA_INIT(CAboutDlg)
430         username = _T("");
431         //}}AFX_DATA_INIT
432 }
433
434 void CAboutDlg::DoDataExchange(CDataExchange* pDX)
435 {
436         CDialog::DoDataExchange(pDX);
437         //{{AFX_DATA_MAP(CAboutDlg)
438         //}}AFX_DATA_MAP
439 }
440
441 BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
442         //{{AFX_MSG_MAP(CAboutDlg)
443         //}}AFX_MSG_MAP
444 END_MESSAGE_MAP();
445
446 // App command to run the dialog
447 void CMapApp::OnAppAbout()
448 {
449   CAboutDlg aboutDlg;
450   aboutDlg.DoModal();
451 } // end CMapApp::OnAppAbout
452
453 /////////////////////////////////////////////////////////////////////////////
454 // CMapApp commands
455 //--------------------------------------------------------------------
456 // Find text in comments:
457
458 void CMapApp::OnEditFind()
459 {
460   if (!findDlg) {
461     VERIFY(findDlg = new CFindDlg(commentDlg));
462     if (!findDlg) return;
463   }
464
465   if (findDlg->GetSafeHwnd() != NULL) {
466     findDlg->SetActiveWindow();
467     findDlg->GotoDlgCtrl(findDlg->GetDlgItem(IDC_FIND_WHAT));
468   } else {
469     findDlg->Create();
470     setWindowPos(findDlg->pos, findDlg);
471   }
472 } // end CMapApp::OnEditFind
473
474 //====================================================================
475 // The Room Comments dialog:
476 //--------------------------------------------------------------------
477 // Display or hide the free-floating Room Comments dialog:
478
479 void CMapApp::OnViewComments()
480 {
481   if (commentDlg->GetSafeHwnd() != NULL) {
482     commentDlg->GetWindowRect(&commentDlg->pos);
483     commentDlg->DestroyWindow();
484   } else {
485     commentDlg->Create();
486     setWindowPos(commentDlg->pos, commentDlg);
487     commentDlg->SetWindowText(commentDlg->title.empty()
488                               ? _T("Room Comments")
489                               : commentDlg->title.c_str());
490     commentDlg->SendDlgItemMessage(IDC_CD_COMMENT,WM_ENABLE,
491                                    commentDlg->fromView != NULL);
492     commentDlg->SendDlgItemMessage(IDC_CD_COMMENT,EM_SETREADONLY,
493                                    commentDlg->fromView == NULL);
494     commentDlg->SendDlgItemMessage(IDC_CD_COMMENT,EM_SETSEL, -1, -2);
495   }
496 } // end CMapApp::OnViewComments
497
498 void CMapApp::OnUpdateViewComments(CCmdUI* pCmdUI)
499 {
500   pCmdUI->SetCheck(commentDlg->GetSafeHwnd() != NULL);
501 } // end CMapApp::OnUpdateViewComments
502
503 //--------------------------------------------------------------------
504 // Move the Room Comments dialog so it doesn't obscure the map:
505 //
506 // Input:
507 //   mapPos:
508 //     The screen coordinates that should not be obscured
509 //     Must be normalized
510
511 void CMapApp::adjustCommentPos(LPCRECT mapPos)
512 {
513   if (commentDlg->GetSafeHwnd()) {
514     CRect  dlgPos, r;
515     commentDlg->GetWindowRect(&dlgPos);
516     if (r.IntersectRect(dlgPos, mapPos)) {
517       CRect  mainPos;
518       m_pMainWnd->GetClientRect(&mainPos);
519       m_pMainWnd->ClientToScreen(&mainPos);
520       BOOL max;
521       const CWnd* curWin =
522         static_cast<CMainFrame*>(m_pMainWnd)->MDIGetActive(&max);
523       if (max) {
524         curWin = curWin->GetTopWindow(); // Get the active view
525         curWin->GetClientRect(&r);       // and make sure we don't
526         curWin->ClientToScreen(&r);      // cover up the scroll bars
527         mainPos.bottom = r.bottom;       // on the right and bottom
528         mainPos.right  = r.right;
529       } // end if active window is maximized
530       dlgPos.OffsetRect(mainPos.right - dlgPos.right, // Top right
531                         mainPos.top - dlgPos.top);
532       if (!r.IntersectRect(dlgPos, mapPos)) goto move;
533       dlgPos.OffsetRect(mainPos.right - dlgPos.right, // Bottom right
534                         mainPos.bottom - dlgPos.bottom);
535       if (!r.IntersectRect(dlgPos, mapPos)) goto move;
536       dlgPos.OffsetRect(mainPos.left - dlgPos.left,   // Bottom left
537                         mainPos.bottom - dlgPos.bottom);
538       if (!r.IntersectRect(dlgPos, mapPos)) goto move;
539       dlgPos.OffsetRect(mainPos.left - dlgPos.left,   // Top left
540                         mainPos.top - dlgPos.top);
541       if (r.IntersectRect(dlgPos, mapPos)) return;  // Give up
542      move:
543       commentDlg->MoveWindow(dlgPos);
544     } // end if area is obscured
545   } // end if commentDlg is visible
546 } // end CMapApp::adjustCommentPos
547
548 //--------------------------------------------------------------------
549 // Disable comment dialog when closing its view:
550 //
551 // Input:
552 //   view:  The view that is closing
553
554 void CMapApp::closingView(const CMapView* view)
555 {
556   if (commentDlg->fromView == view)
557     setComment(NULL, NULL);
558 } // end CMapApp::closingView
559
560 //--------------------------------------------------------------------
561 // Switch to the Room Comments dialog:
562
563 void CMapApp::editComment()
564 {
565   if (commentDlg->GetSafeHwnd() == NULL)
566     OnViewComments();
567   else {
568     commentDlg->SetFocus();
569     commentDlg->SendDlgItemMessage(IDC_CD_COMMENT, EM_SETSEL, -1, -2);
570   }
571 } // end CMapApp::editComment
572
573 //--------------------------------------------------------------------
574 // Update the Room Comments dialog:
575 //
576 // Input:
577 //   fromView:
578 //     The map view which is posting this comment
579 //     NULL means the view is closing, so disable the dialog
580 //   room:
581 //     The room whose comment should be displayed in the dialog
582 //     NULL means no room is selected, so display map comment
583 //   takeFocus: (default true)
584 //     TRUE means to override a comment from a different view
585
586 void CMapApp::setComment(CMapView* fromView, const MapRoom* room,
587                          bool takeFocus) const
588 {
589   if (!commentDlg->active &&
590       (takeFocus || (commentDlg->fromView == fromView))) {
591     commentDlg->fromView = fromView;
592     if (room && !(room->flags & rfCorner)) {
593       Strcpy(commentDlg->comment, room->note);
594       copyToOneLine(commentDlg->title, room->name);
595       commentDlg->roomComment = true;
596     } else {
597       if (fromView) {
598         const CMapDoc*  doc = fromView->GetDocument();
599         ASSERT_VALID(doc);
600
601         Strcpy(commentDlg->comment, doc->getNote());
602         commentDlg->title = doc->getName();
603         if (!commentDlg->title.empty())
604           commentDlg->title += _T(" -- ");
605       } else {
606         commentDlg->comment.Empty();
607         commentDlg->title.erase();
608       } // end else no active view
609       commentDlg->title += _T("Map Comments");
610       commentDlg->roomComment = false;
611     } // end else no current room
612     if (commentDlg->GetSafeHwnd()) {
613       commentDlg->UpdateData(FALSE);
614       commentDlg->SetWindowText(commentDlg->title.empty()
615                                 ? _T("Room Comments")
616                                 : commentDlg->title.c_str());
617       commentDlg->SendDlgItemMessage(IDC_CD_COMMENT,WM_ENABLE,
618                                      fromView != NULL);
619       commentDlg->SendDlgItemMessage(IDC_CD_COMMENT,EM_SETREADONLY,
620                                      fromView == NULL);
621     } // end if comment dialog is open
622   } // end if we should update the comment
623 } // end CMapApp::setComment
624
625 //====================================================================
626 // CMapApp Options dialog:
627 //--------------------------------------------------------------------
628 // Bring up the Options dialog:
629
630 void CMapApp::OnViewSettings()
631 {
632   editOptions();
633 } // end CMapApp::OnViewSettings
634
635 //--------------------------------------------------------------------
636 // Bring up the Options dialog:
637
638 void CMapApp::editOptions()
639 {
640   CPropertySheet dlg(IDR_OPTIONS);
641
642   COptNavPage  nav;
643   nav.copy = naviCopy;
644   nav.CRLF = naviCRLF;
645   nav.editProps = autoEdit;
646   nav.stubs = (preferUnexplored ? 0 : 1);
647   dlg.AddPage(&nav);
648
649   if (dlg.DoModal() == IDOK) {
650     autoEdit = nav.editProps;
651     naviCopy = nav.copy;
652     naviCRLF = nav.CRLF;
653     preferUnexplored = (nav.stubs == 0);
654   } // end if OK pressed
655 } // end CMapApp::editOptions
656 #endif
657