// --------------------------------------------------------------------
// Constructor for AppUi
// --------------------------------------------------------------------
/*

    This file is part of the extensible drawing editor Ipe.
    Copyright (C) 1993-2004  Otfried Cheong

    Ipe 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.

    As a special exception, you have permission to link Ipe with the
    CGAL library and distribute executables, as long as you follow the
    requirements of the Gnu General Public License in regard to all of
    the software in the executable aside from CGAL.

    Ipe 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 Ipe; if not, you can find it at
    "http://www.gnu.org/copyleft/gpl.html", or write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include "ipedoc.h"
#include "ipeq.h"

#include "appui.h"

#include <qaction.h>
#include <qapplication.h>
#include <qbuttongroup.h>
#include <qclipboard.h>
#include <qcolordialog.h>
#include <qcombobox.h>
#include <qdatetime.h>
#include <qdragobject.h>
#include <qvalidator.h>
#include <qfiledialog.h>
#include <qfocusdata.h>
#include <qheader.h>
#include <qinputdialog.h>
#include <qlabel.h>
#include <qlineedit.h>
#include <qlistbox.h>
#include <qmainwindow.h>
#include <qmenubar.h>
#include <qmessagebox.h>
#include <qpushbutton.h>
#include <qstatusbar.h>
#include <qtextbrowser.h>
#include <qtextstream.h>
#include <qtoolbar.h>
#include <qtoolbutton.h>
#include <qtooltip.h>
#include <qwhatsthis.h>
#include <qwidgetstack.h>

QPixmap ipeIcon(const char* name);
QPixmap penguinIcon(int width);

// --------------------------------------------------------------------

IpeSpinBox::IpeSpinBox(QWidget *parent, const char *name,
		       int minValue, int maxValue, int step)
  : QSpinBox(minValue, maxValue, step, parent, name)
{
  // nothing
}

//! This does not emit any valueChanged() signal.
void IpeSpinBox::Set(int val, int maxVal)
{
  // set value to 1 first, in case maxVal is less than current value
  directSetValue(1);
  setMaxValue(maxVal);
  directSetValue(val);
  updateDisplay();
}

//! This does not emit any valueChanged() signal.
void IpeSpinBox::Set(int val)
{
  directSetValue(val);
  updateDisplay();
}

// --------------------------------------------------------------------

DecimalSpinBox::DecimalSpinBox(int minValue, int maxValue, int step,
			       int decimals, QWidget *parent)
  : QSpinBox(minValue, maxValue, step, parent)
{
  iDecimals = decimals;
  setValidator(new QDoubleValidator(minValue / 1000.0, maxValue / 1000.0,
				    iDecimals, this));
}

QString DecimalSpinBox::mapValueToText(int value)
{
  QString s1, s2;
  s1.sprintf("%d.", value / 1000);
  s2.sprintf("%03d", value % 1000);
  return s1 + s2.left(iDecimals);
}

int DecimalSpinBox::mapTextToValue(bool *ok)
{
  *ok = false;
  QString str = cleanText();
  IpeLex lex(IpeQ(str));
  IpeFixed d = lex.GetFixed();
  lex.SkipWhitespace();
  if (lex.Eos()) {
    *ok = true;
    return d.Internal();
  }
  return 0;
}

void DecimalSpinBox::setValueFromAttribute(int value)
{
  directSetValue(value);
  updateDisplay();
}

// --------------------------------------------------------------------

class ZoomSpinBox : public QSpinBox {
public:
  ZoomSpinBox(QWidget *parent = 0);
public slots:
  virtual void stepUp();
  virtual void stepDown();
protected:
  virtual QString mapValueToText(int value);
  virtual int mapTextToValue(bool *ok);
};

ZoomSpinBox::ZoomSpinBox(QWidget *parent)
  : QSpinBox(10000, 1000000, 1000, parent)
{
  // nothing
}

void ZoomSpinBox::stepUp()
{
  int res = value();
  res = int(res * 1.3 + 0.5);
  if (res > maxValue())
    res = maxValue();
  setValue(res);
}

void ZoomSpinBox::stepDown()
{
  int res = value();
  res = int(res / 1.3 + 0.5);
  if (res < minValue())
    res = minValue();
  setValue(res);
}

QString ZoomSpinBox::mapValueToText(int value)
{
  QString s;
  s.sprintf("%d", int(value * 0.001 + 0.5));
  return s;
}

int ZoomSpinBox::mapTextToValue(bool *ok)
{
  return 1000 * QSpinBox::mapTextToValue(ok);
}

// --------------------------------------------------------------------

class LayerBox : public QListBox {
public:
  LayerBox(QWidget *parent, const char *name) :
    QListBox(parent, name) { /* nothing else */ }
  virtual void mousePressEvent(QMouseEvent *e);
};

void LayerBox::mousePressEvent(QMouseEvent *e)
{
  QListBoxItem *i = itemAt(e->pos());
  // ignore mouse outside items
  if (!i)
    return;
  if (e->button() == LeftButton) {
    QListBox::mousePressEvent(e);
  } else
    emit rightButtonPressed(i, e->globalPos());
}

// --------------------------------------------------------------------

const char * fileNewText = QT_TR_NOOP(
"<qt><img source=\"new\"> "
"This button creates a new document. "
"You can also select the <b>New</b> command from the File menu.</qt>");
const char * fileOpenText = QT_TR_NOOP(
"<qt><img source=\"open\"> "
"This button opens a document from a file. "
"You can also select the <b>Open</b> command from the File menu.</qt>");
const char * fileSaveText = QT_TR_NOOP(
"<qt><img source=\"save\"> "
"This button saves the current document. If it is a new document, "
"you will be prompted for a file name. "
"You can also select the <b>Save</b> command from the File menu.</qt>");
const char * cutcopyText = QT_TR_NOOP(
"<qt>Cut <img source=\"cut\">, copy <img source=\"copy\">, and paste "
"<img source=\"paste\"> pass Ipe objects to and from the system clipboard. "
"When pasting objects, they are inserted into the active layer. "
"You can examine Ipe objects by copying them to the clipboard, and "
"pasting into your favorite text editor.</qt>");
const char * strokeColorText = QT_TR_NOOP(
"<qt>Select the <b>stroke color</b> here."
"<p>The stroke color is used to outline the shape of objects, to draw "
"lines, curves, arcs, marks, and for text.  To draw filled objects without "
"an outline, set the stroke color to <tt>void</tt>.</p></qt>");
const char * fillColorText = QT_TR_NOOP(
"<qt>Select the <b>fill color</b> here."
"<p>The fill color is used for the interior of objects such as polygons or "
"circles.  It is not used for text or marks.  To draw unfilled objects, "
"that is, polygons or circles with an outline only, set the fill "
"color to <tt>void</tt>.</p></qt>");
const char * resolutionText = QT_TR_NOOP(
"<qt>You control the resolution of the screen display here. "
"The number indicates the number of screen pixels being used to "
"display one inch in the document."
"<p>By setting this to the actual screen resolution (that is, "
"the number of pixels per inch of your monitor), you can view a "
"document in its original size "
"(the size it will have when printed).</p></qt>");
const char * pageNumberText = QT_TR_NOOP(
"<qt>The current page number.</qt>");
const char * viewNumberText = QT_TR_NOOP(
"<qt>Each page of an Ipe document can consist of several <em>views</em>. "
"Each view becomes one PDF/Postscript page when saving in that format. "
"A view is defined by selecting some layers of the page that will be "
"presented together.</qt>");
const char * lineWidthText = QT_TR_NOOP(
"<qt>This setting determines the width of lines you create. "
"If you change the setting while you have objects "
"selected, the line width of the selected objects will be changed "
"accordingly.</qt>");
const char * lineDashText = QT_TR_NOOP(
"<qt>This setting determines the dash-dot pattern of lines that you create. "
"If you change the setting while you have objects "
"selected, the dash-dot pattern of the selected objects will be changed "
"accordingly.</qt>");
const char * textSizeText = QT_TR_NOOP(
"<qt>This setting determines the font size of text objects that you create. "
"If you change the setting while you have text objects "
"selected, their font size will be changed "
"accordingly.</qt>");
const char * arrowText = QT_TR_NOOP(
"<qt>This setting determines the arrow heads on polyline, spline, or arc "
"objects that you create.  If you change the setting while you have objects "
"selected, the arrows on the selected objects will be changed "
"accordingly.</qt>");
const char * arrowSizeText = QT_TR_NOOP(
"<qt>This setting determines the size of arrow heads for objects that you "
"create.  If you change the setting while you have objects selected, "
"the arrow heads on the selected objects will be resized.</qt>");
const char * markShapeText = QT_TR_NOOP(
"<qt>This setting determines the shape of mark objects that you "
"create. If you change the setting while you have mark objects selected, "
"their shape will be changed.</qt>");
const char * markSizeText = QT_TR_NOOP(
"<qt>This setting determines the size of mark objects that you "
"create.  If you change the setting while you have mark objects selected, "
"the marks will be resized.</qt>");
const char * gridSizeText = QT_TR_NOOP(
"<qt>This setting determines the distance between grid points in "
"the snapping grid.</qt");
const char * angleSizeText = QT_TR_NOOP(
"<qt>This setting determines the angle between consecutive lines "
"in the angular snapping mode.</qt>");
const char * layerListText = QT_TR_NOOP(
"<qt>This list shows the layers of the current page. "
"<p>The selected layers are in the current <em>view</em>. "
"When you create new objects, they are inserted into the <em>active layer</em>, "
"indicated by the angular brackets. "
"<p>The letters H, D, L, S stand for <em>hidden</em>, <em>dimmed</em>, "
"<em>locked</em>, and <em>no snapping</em>.</p>"
"</qt>");
const char * penguinText = QT_TR_NOOP(
"<qt>This is a picture of penguins."
"<p>It is not related to Tux, the Linux-penguin. "
"In fact, Ipe has used a penguin icon since 1993, "
"long before I had ever heard of Linux.</p></qt>");
const char * snapText = QT_TR_NOOP (
"<qt>These buttons select the various snapping modes.  When the button "
"is pressed in, the mode is on, otherwise it is off. From left to right, "
"the buttons enable the following modes:"
"<ul><li>Snapping to object vertices,"
"<li>Snapping to path boundaries,"
"<li>Snapping to intersection points (currently only of straight segments),"
"<li>Snapping to the grid,"
"<li>Angular snapping,"
"<li>Automatic angular snapping."
"</ul>See the manual for more details about snapping.</qt>");
const char * canvasText = QT_TR_NOOP(
"<qt>This is the canvas, where you create your drawing."
"<p>Most operations can be done with the left mouse button. Its "
"function depends on the current mode.</p>"
"<p>The right mouse button pops up the object menu (you can change it "
"to select objects in the <em>Preferences</em> dialog - in that case "
"you get the object menu by holding the Control key with the right "
"mouse button).</p>"
"<p>The following mouse shortcuts are available:"
"<ul><li>Ctrl+Left mouse: Stretch"
"<li>Ctrl+Shift+Left mouse: Scale"
"<li>Middle mouse: Move"
"<li>Ctrl+Middle mouse: Rotate"
"<li>Shift+Middle mouse: Pan"
"<li>Shift+Ctrl+Middle mouse: Horizontal/vertical move"
"</ul></p></qt>");

// --------------------------------------------------------------------

/*! \class AppUi
  \brief AppUi encapsulates the Ipe user interface.

*/
/*! The constructor does not create an Ipe document.
  Clients need to call either Load or NewDoc after construction,
  before calling show. */
AppUi::AppUi(QWidget* parent, const char* name, WFlags f)
  : QMainWindow(parent,name,f), iKeyTranslator(0)
{
  QPixmap newIcon = ipeIcon("new");
  QPixmap openIcon = ipeIcon("open");
  QPixmap saveIcon = ipeIcon("save");
  QPixmap undoIcon = ipeIcon("undo");
  QPixmap redoIcon = ipeIcon("redo");
  QPixmap cutIcon = ipeIcon("cut");
  QPixmap copyIcon = ipeIcon("copy");
  QPixmap pasteIcon = ipeIcon("paste");

  QMimeSourceFactory::defaultFactory()->setPixmap("new", newIcon);
  QMimeSourceFactory::defaultFactory()->setPixmap("open", openIcon);
  QMimeSourceFactory::defaultFactory()->setPixmap("save", saveIcon);
  QMimeSourceFactory::defaultFactory()->setPixmap("undo", undoIcon);
  QMimeSourceFactory::defaultFactory()->setPixmap("redo", undoIcon);
  QMimeSourceFactory::defaultFactory()->setPixmap("cut", cutIcon);
  QMimeSourceFactory::defaultFactory()->setPixmap("copy", copyIcon);
  QMimeSourceFactory::defaultFactory()->setPixmap("paste", pasteIcon);

  IpePreferences *prefs = IpePreferences::Static();

  // --------------------------------------------------------------------
  // File tool bar
  // --------------------------------------------------------------------

  setUsesBigPixmaps(prefs->iBigToolButtons);

  iFileTools = new QToolBar( this, "file tools" );
  iFileTools->setLabel(tr("File tools"));

  QToolButton *fileNew
    = new QToolButton(newIcon, tr("New document"), QString::null,
		      this, SLOT(NewDoc()), iFileTools, "new document" );
  QWhatsThis::add(fileNew, tr(fileNewText));

  QToolButton *fileOpen
    = new QToolButton(openIcon, tr("Open file"), QString::null,
		      this, SLOT(Load()), iFileTools, "open file" );
  QWhatsThis::add(fileOpen, tr(fileOpenText));

  QToolButton *fileSave
    = new QToolButton(saveIcon, tr("Save file"), QString::null,
		      this, SLOT(Save()), iFileTools, "save file" );
  QWhatsThis::add(fileSave, tr(fileSaveText));

  iFileTools->addSeparator();

  QToolButton *editCut
    = new QToolButton(cutIcon, tr("Cut"), QString::null,
		      this, SLOT(Cut()), iFileTools, "cut" );
  QWhatsThis::add(editCut, tr(cutcopyText));

  QToolButton *editCopy
    = new QToolButton(copyIcon, tr("Copy"), QString::null,
		      this, SLOT(Copy()), iFileTools, "copy" );
  QWhatsThis::add(editCopy, tr(cutcopyText));

  QToolButton *editPaste
    = new QToolButton(pasteIcon, tr("Paste"), QString::null,
		      this, SLOT(Paste()), iFileTools, "paste" );
  QWhatsThis::add(editPaste, tr(cutcopyText));

  iFileTools->addSeparator();

  (void) QWhatsThis::whatsThisButton(iFileTools);

  // --------------------------------------------------------------------
  // Resolution tool bar
  // --------------------------------------------------------------------

  iResolutionTools = new QToolBar(this, "resolution tools");
  iResolutionTools->setLabel(tr("Resolution tool"));

  iResolution = new ZoomSpinBox(iResolutionTools);
  QToolTip::add(iResolution, tr("Screen resolution"));
  QWhatsThis::add(iResolution, tr(resolutionText));
  iResolution->setSuffix(tr(" dpi"));
  connect(iResolution, SIGNAL(valueChanged(int)),
	  this, SLOT(ResolutionChanged(int)));

  // --------------------------------------------------------------------
  // Page tool bar
  // --------------------------------------------------------------------

  iPageTools = new QToolBar(this, "page tools");
  iPageTools->setLabel(tr("Page tools"));

  iPageNumber = new IpeSpinBox(iPageTools, "page number");
  QToolTip::add(iPageNumber, tr("Current page"));
  QWhatsThis::add(iPageNumber, tr(pageNumberText));
  iPageNumber->setPrefix(tr("Page "));
  connect(iPageNumber, SIGNAL(valueChanged(int)),
	  this, SLOT(PageChanged(int)));
  // iPageNumber->setFocusPolicy(NoFocus);

  iViewNumber = new IpeSpinBox(iPageTools, "view number");
  QToolTip::add(iViewNumber, tr("Current view"));
  QWhatsThis::add(iViewNumber, tr(viewNumberText));
  iViewNumber->setPrefix(tr("View "));
  connect(iViewNumber, SIGNAL(valueChanged(int)),
	  this, SLOT(ViewChanged(int)));
  // iViewNumber->setFocusPolicy(NoFocus);

  // --------------------------------------------------------------------
  // Color tool bar
  // --------------------------------------------------------------------

  iColorTools = new QToolBar(this, "color tools");
  iColorTools->setLabel(tr("Color tools"));

  iStrokeColorStack = new QWidgetStack(iColorTools);
  QToolTip::add(iStrokeColorStack, tr("Stroke color"));
  QWhatsThis::add(iStrokeColorStack, tr(strokeColorText));
  iStrokeColor = new QComboBox(false);
  connect(iStrokeColor, SIGNAL(activated(int)),
	  SLOT(SetStrokeColorName(int)));
  iAbsStrokeColor = new QPushButton(tr("Stroke"), 0);
  iStrokeColorStack->setMaximumHeight(iStrokeColor->sizeHint().height());
  connect(iAbsStrokeColor, SIGNAL(clicked()),
	  this, SLOT(SetStrokeColor()));
  iStrokeColorStack->addWidget(iStrokeColor, 0);
  iStrokeColorStack->addWidget(iAbsStrokeColor, 1);

  iFillColorStack = new QWidgetStack(iColorTools);
  QToolTip::add(iFillColorStack, tr("Fill color"));
  QWhatsThis::add(iFillColorStack, tr(fillColorText));
  iFillColor = new QComboBox(false);
  connect(iFillColor, SIGNAL(activated(int)),
	  SLOT(SetFillColorName(int)));
  iAbsFillColor = new QPushButton(tr("Fill"), 0);
  iFillColorStack->setMaximumHeight(iFillColor->sizeHint().height());
  connect(iAbsFillColor, SIGNAL(clicked()),
	  this, SLOT(SetFillColor()));
  iFillColorStack->addWidget(iFillColor, 0);
  iFillColorStack->addWidget(iAbsFillColor, 1);

  // --------------------------------------------------------------------
  // Line width/style/arrow tool bar
  // --------------------------------------------------------------------

  iLineTools = new QToolBar(this, "line tools");
  iLineTools->setLabel(tr("Line style settings"));

  iLineWidthStack = new QWidgetStack(iLineTools);
  QToolTip::add(iLineWidthStack, tr("Line width"));
  QWhatsThis::add(iLineWidthStack, tr(lineWidthText));
  iLineWidth = new QComboBox(false);
  connect(iLineWidth, SIGNAL(activated(int)),
	  SLOT(LineWidthChanged(int)));
  iAbsLineWidth = new DecimalSpinBox(0, 10000, 200);
  connect(iAbsLineWidth, SIGNAL(valueChanged(int)),
	  SLOT(AbsLineWidthChanged(int)));
  iLineWidthStack->addWidget(iLineWidth, 0);
  iLineWidthStack->addWidget(iAbsLineWidth, 1);

  iDashStyle = new QComboBox(false, iLineTools);
  QToolTip::add(iDashStyle, tr("Line dash pattern"));
  QWhatsThis::add(iDashStyle, tr(lineDashText));
  connect(iDashStyle, SIGNAL(activated(int)),
	  SLOT(DashStyleChanged(int)));

  QPixmap arrowIcon1 = ipeIcon("arrowNone");
  QPixmap arrowIcon2 = ipeIcon("arrowRight");
  QPixmap arrowIcon3 = ipeIcon("arrowLeft");
  QPixmap arrowIcon4 = ipeIcon("arrowBoth");
  iArrow = new QComboBox(false, iLineTools);
  QToolTip::add(iArrow, tr("Arrow"));
  QWhatsThis::add(iArrow, tr(arrowText));
  iArrow->insertItem(arrowIcon1);
  iArrow->insertItem(arrowIcon2);
  iArrow->insertItem(arrowIcon3);
  iArrow->insertItem(arrowIcon4);
  connect(iArrow, SIGNAL(activated(int)),
	  SLOT(ArrowChanged(int)));
  iAttributes.iForwardArrow = false;
  iAttributes.iBackwardArrow = false;

  iArrowSizeStack = new QWidgetStack(iLineTools);
  QToolTip::add(iArrowSizeStack, tr("Arrow size"));
  QWhatsThis::add(iArrowSizeStack, tr(arrowSizeText));
  iArrowSize = new QComboBox(false);
  connect(iArrowSize, SIGNAL(activated(int)),
	  SLOT(ArrowSizeChanged(int)));
  iAbsArrowSize = new DecimalSpinBox(200, 100000, 1000);
  connect(iAbsArrowSize, SIGNAL(valueChanged(int)),
	  SLOT(AbsArrowSizeChanged(int)));
  iArrowSizeStack->addWidget(iArrowSize, 0);
  iArrowSizeStack->addWidget(iAbsArrowSize, 1);


  // --------------------------------------------------------------------
  // Mark and text sizes
  // --------------------------------------------------------------------

  iSizeTools = new QToolBar(this, "size tools");
  iSizeTools->setLabel(tr("Mark and text size settings"));

  QPixmap markIcon = ipeIcon("marks");
  iMarkShape = new QComboBox(false, iSizeTools, "mark shape");
  QToolTip::add(iMarkShape, tr("Mark shape"));
  QWhatsThis::add(iMarkShape, tr(markShapeText));
  connect(iMarkShape, SIGNAL(activated(int)),
	  SLOT(MarkShapeChanged(int)));
  iMarkShape->insertItem(markIcon, tr("Circle"));
  iMarkShape->insertItem(markIcon, tr("Disc"));
  iMarkShape->insertItem(markIcon, tr("Box"));
  iMarkShape->insertItem(markIcon, tr("Square"));
  iMarkShape->insertItem(markIcon, tr("Cross"));
  iAttributes.iMarkShape = iMarkShape->currentItem() + 1;

  iMarkSizeStack = new QWidgetStack(iSizeTools);
  QToolTip::add(iMarkSizeStack, tr("Mark size"));
  QWhatsThis::add(iMarkSizeStack, tr(markSizeText));
  iMarkSize = new QComboBox(false);
  connect(iMarkSize, SIGNAL(activated(int)),
	  SLOT(MarkSizeChanged(int)));
  iAbsMarkSize = new DecimalSpinBox(200, 100000, 1000);
  connect(iAbsMarkSize, SIGNAL(valueChanged(int)),
	  SLOT(AbsMarkSizeChanged(int)));
  iMarkSizeStack->addWidget(iMarkSize, 0);
  iMarkSizeStack->addWidget(iAbsMarkSize, 1);

  iTextSizeStack = new QWidgetStack(iSizeTools);
  QToolTip::add(iTextSizeStack, tr("Font size"));
  QWhatsThis::add(iTextSizeStack, tr(textSizeText));
  iTextSize = new QComboBox(false);
  connect(iTextSize, SIGNAL(activated(int)),
	  SLOT(TextSizeChanged(int)));
  iAbsTextSize = new DecimalSpinBox(4000, 50000, 1000);
  connect(iAbsTextSize, SIGNAL(valueChanged(int)),
	  SLOT(AbsTextSizeChanged(int)));
  iTextSizeStack->addWidget(iTextSize, 0);
  iTextSizeStack->addWidget(iAbsTextSize, 1);

  // --------------------------------------------------------------------
  // Snap tool bar
  // --------------------------------------------------------------------

  QPixmap snapVtxIcon = ipeIcon("snapvtx");
  QPixmap snapBdIcon = ipeIcon("snapbd");
  QPixmap snapIntIcon = ipeIcon("snapint");
  QPixmap snapGridIcon = ipeIcon("snapgrid");
  QPixmap snapAngleIcon = ipeIcon("snapangle");
  QPixmap snapAutoIcon = ipeIcon("snapauto");

  QMimeSourceFactory::defaultFactory()->setPixmap("snapvtx", snapVtxIcon);
  QMimeSourceFactory::defaultFactory()->setPixmap("snapbd", snapVtxIcon);
  QMimeSourceFactory::defaultFactory()->setPixmap("snapint", snapVtxIcon);
  QMimeSourceFactory::defaultFactory()->setPixmap("snapgrid", snapGridIcon);
  QMimeSourceFactory::defaultFactory()->setPixmap("snapangle", snapAngleIcon);
  QMimeSourceFactory::defaultFactory()->setPixmap("snapauto", snapAutoIcon);

  iSnapTools = new QToolBar(this, "snap tools");
  iSnapTools->setLabel(tr("Snap tools"));

  iSnapActionGroup = new QActionGroup(this, 0, false);

  iSnapAction[0] = new QAction(iSnapActionGroup);
  iSnapAction[0]->setText(tr("Snap to vertices"));
  iSnapAction[0]->setAccel(Key("F4", "Snap|Vertices"));
  iSnapAction[0]->setIconSet(snapVtxIcon);
  iSnapAction[0]->addTo(iSnapTools);

  iSnapAction[1] = new QAction(iSnapActionGroup);
  iSnapAction[1]->setText(tr("Snap to boundary"));
  iSnapAction[1]->setAccel(Key("F5", "Snap|Boundary"));
  iSnapAction[1]->setIconSet(snapBdIcon);
  iSnapAction[1]->addTo(iSnapTools);

  iSnapAction[2] = new QAction(iSnapActionGroup);
  iSnapAction[2]->setText(tr("Snap to intersection"));
  iSnapAction[2]->setAccel(Key("F6", "Snap|Intersections"));
  iSnapAction[2]->setIconSet(snapIntIcon);
  iSnapAction[2]->addTo(iSnapTools);

  iSnapTools->addSeparator();

  iSnapAction[3] = new QAction(iSnapActionGroup);
  iSnapAction[3]->setText(tr("Snap to grid"));
  iSnapAction[3]->setAccel(Key("F7", "Snap|Grid"));
  iSnapAction[3]->setIconSet(snapGridIcon);
  iSnapAction[3]->addTo(iSnapTools);

  iGridSizeStack = new QWidgetStack(iSnapTools);
  QToolTip::add(iGridSizeStack, tr("Grid size"));
  QWhatsThis::add(iGridSizeStack, tr(gridSizeText));
  iGridSize = new QComboBox(false);
  connect(iGridSize, SIGNAL(activated(int)),
	  SLOT(GridSizeChanged(int)));
  iAbsGridSize = new IpeSpinBox(0, 0, 4, 100, 2);
  connect(iAbsGridSize, SIGNAL(valueChanged(int)),
	  SLOT(AbsGridSizeChanged(int)));
  iGridSizeStack->addWidget(iGridSize, 0);
  iGridSizeStack->addWidget(iAbsGridSize, 1);

  iSnapTools->addSeparator();

  iSnapAction[4] = new QAction(iSnapActionGroup);
  iSnapAction[4]->setText(tr("Angular snap"));
  iSnapAction[4]->setAccel(Key("F8", "Snap|Angular"));
  iSnapAction[4]->setIconSet(snapAngleIcon);
  iSnapAction[4]->addTo(iSnapTools);

  iSnapAction[5] = new QAction(iSnapActionGroup);
  iSnapAction[5]->setText(tr("Automatic angular snap"));
  iSnapAction[5]->setAccel(Key("F9", "Snap|Automatic angular"));
  iSnapAction[5]->setIconSet(snapAutoIcon);
  iSnapAction[5]->addTo(iSnapTools);

  iAngleSizeStack = new QWidgetStack(iSnapTools);
  QToolTip::add(iAngleSizeStack, tr("Angular snapping angle"));
  QWhatsThis::add(iAngleSizeStack, tr(angleSizeText));
  iAngleSize = new QComboBox(false);
  connect(iAngleSize, SIGNAL(activated(int)),
	  SLOT(AngleSizeChanged(int)));
  iAbsAngleSize = new DecimalSpinBox(3000, 360000, 2000);
  connect(iAbsAngleSize, SIGNAL(valueChanged(int)),
	  SLOT(AbsAngleSizeChanged(int)));
  iAngleSizeStack->addWidget(iAngleSize, 0);
  iAngleSizeStack->addWidget(iAbsAngleSize, 1);

  for (int i = 0; i < 6; ++i) {
    iSnapAction[i]->setToggleAction(true);
    iSnapAction[i]->setWhatsThis(tr(snapText));
    connect(iSnapAction[i], SIGNAL(toggled(bool)),
	    this, SLOT(SnapChanged(bool)));
  }

  // ------------------------------------------------------------
  // Bookmark and layer toolbar
  // ------------------------------------------------------------

  iBookmarkTools = new QToolBar(tr("Bookmark tools"), this,
				QMainWindow::Right, false, "bookmark tools");
#if 0
  iBookmarks = new QListView(iLayerTools, "bookmarks");
  QToolTip::add(iBookmarks, tr("Bookmarks of this document"));
  iBookmarks->addColumn("Section");
  iBookmarks->setSorting(-1); // don't resort
  iBookmarks->header()->hide();
  iBookmarks->setRootIsDecorated(true);
#else
  iBookmarks = new QListBox(iBookmarkTools, "bookmarks");
  QToolTip::add(iBookmarks, tr("Bookmarks of this document"));
  iBookmarks->setFocusPolicy(NoFocus);
  connect(iBookmarks, SIGNAL(selected(int)),
	  this, SLOT(BookmarkSelected(int)));
#endif

  iLayerTools = new QToolBar(tr("Layer tools"), this,
			     QMainWindow::Left, false, "layer tools");
  iLayerTools->setVerticalStretchable(true);
  iLayerList = new LayerBox(iLayerTools, "layer list box");
  iLayerList->setSelectionMode(QListBox::Multi);
  iLayerList->setFocusPolicy(NoFocus);
  QToolTip::add(iLayerList, tr("Layers of this page"));
  QWhatsThis::add(iLayerList, tr(layerListText));

  QLabel *penguins = new QLabel(iLayerTools);
  penguins->setPixmap(penguinIcon(iLayerList->sizeHint().width()));
  QWhatsThis::add(penguins, tr(penguinText));

  iLayerTools->setStretchableWidget(iLayerList);
  iBookmarkTools->setStretchableWidget(iBookmarks);
  iBookmarks->setMinimumWidth(iLayerList->sizeHint().width());

  iMouse = new QLabel(statusBar());
  statusBar()->addWidget(iMouse, 0, true);

  // --------------------------------------------------------------------
  // Object creators tool bar
  // --------------------------------------------------------------------

  iObjectTools = new QToolBar(this, "mode tools");
  iObjectTools->setLabel(tr("Mode tools"));

  iModeActionGroup = new QActionGroup(this);
  connect(iModeActionGroup, SIGNAL(selected(QAction*)),
	  SLOT(SetCreator(QAction*)));

  for (int i = 0; i < IpeOverlayFactory::Num; ++i) {
    QPixmap icon = ipeIcon(IpeOverlayFactory::PixmapName(i));
    iModeAction[i] = new QAction(iModeActionGroup);
    iModeAction[i]->setText(IpeOverlayFactory::Tooltip(i));
    iModeAction[i]->setIconSet(icon);
    iModeAction[i]->setWhatsThis(tr(IpeOverlayFactory::WhatsThis(i)));
    iModeAction[i]->setAccel(Key(IpeOverlayFactory::Shortcut(i),
				 IpeOverlayFactory::Context(i)));
    iModeAction[i]->setToggleAction(true);
    iModeAction[i]->addTo(iObjectTools);
    if (i == 4)
      iObjectTools->addSeparator();
  }

  // ------------------------------------------------------------
  // Menu
  // ------------------------------------------------------------

  QMenuBar* menu = menuBar();
  int id;

  iFileMenu = new QPopupMenu;
  iFileMenu->insertItem(tr("New &Window"), this, SLOT(NewWindow()),
			Key("none", "File|New Window"));

  id = iFileMenu->insertItem(newIcon, tr("&New"), this, SLOT(NewDoc()),
			     Key("Ctrl+N", "File|New"));
  iFileMenu->setWhatsThis(id, fileNewText );

  id = iFileMenu->insertItem(openIcon, tr("&Open"),
			     this, SLOT(Load()),
			     Key("Ctrl+O", "File|Open"));
  iFileMenu->setWhatsThis(id, fileOpenText );

  id = iFileMenu->insertItem(saveIcon, tr("&Save"), this, SLOT(Save()),
			     Key("Ctrl+S", "File|Save"));
  iFileMenu->setWhatsThis(id, fileSaveText );

  id = iFileMenu->insertItem(tr("Save &as..."), this, SLOT(SaveAs()),
			     Key("none", "File|Save as"));
  iFileMenu->setWhatsThis(id, fileSaveText );

  iFileMenu->insertItem(tr("Save as &bitmap"), this, SLOT(SaveAsBitmap()),
			Key("none", "File|Save as bitmap"));
  iFileMenu->insertSeparator();

  iFileMenu->insertItem(tr("Run &Latex"), this, SLOT(RunLatex(int)),
			Key("Ctrl+L", "File|Run Latex"), 1);
  iFileMenu->insertSeparator();

  iFileMenu->insertItem(tr("&Close"), this, SLOT(close()),
			Key("Ctrl+Q", "File|Close"));
  iFileMenu->insertItem(tr("E&xit"), qApp, SLOT(closeAllWindows()),
			Key("none", "File|Exit"));
  menu->insertItem(tr("&File"), iFileMenu);

  iEditMenu = new QPopupMenu;
  iEditMenu->insertItem(tr("&Absolute attributes"),
			this, SLOT(AbsoluteAttributes()),
			Key("Ctrl+T", "Edit|Absolute attributes"),
			EAbsoluteAttributesMenuId);
  iEditMenu->insertSeparator();
  iEditMenu->insertItem(undoIcon, tr("&Undo"),
			this, SLOT(UndoCmd(int)),
			Key("Ctrl+Z", "Edit|Undo"), ECmdUndo);
  iEditMenu->insertItem(redoIcon, tr("&Redo"),
			this, SLOT(UndoCmd(int)),
			Key("Ctrl+Y", "Edit|Redo"), ECmdRedo);
  iEditMenu->insertSeparator();
  iEditMenu->insertItem(cutIcon, tr("Cu&t"),
			this, SLOT(Cmd(int)),
			Key("Ctrl+X", "Edit|Cut"), ECmdCut);
  iEditMenu->insertItem(copyIcon, tr("&Copy"),
			this, SLOT(Cmd(int)),
			Key("Ctrl+C", "Edit|Copy"), ECmdCopy);
  iEditMenu->insertItem(pasteIcon, tr("&Paste"),
			this, SLOT(Cmd(int)),
			Key("Ctrl+V", "Edit|Paste"), ECmdPaste);
  iEditMenu->insertItem(tr("&Delete"),
			this, SLOT(Cmd(int)),
			Key("Del", "Edit|Delete"), ECmdDelete);
  iEditMenu->insertSeparator();
  iEditMenu->insertItem(tr("&Group"), this, SLOT(Cmd(int)),
			Key("Ctrl+G", "Edit|Group"), ECmdGroup);
  iEditMenu->insertItem(tr("&Ungroup"), this, SLOT(Cmd(int)),
			Key("Ctrl+U", "Edit|Ungroup"), ECmdUngroup);
  iEditMenu->insertItem(tr("&Front"), this, SLOT(Cmd(int)),
			Key("Ctrl+F", "Edit|Front"), ECmdFront);
  iEditMenu->insertItem(tr("&Back"), this, SLOT(Cmd(int)),
			Key("Ctrl+B", "Edit|Back"), ECmdBack);
  iEditMenu->insertItem(tr("&Duplicate"),
			this, SLOT(Cmd(int)),
			Key("d", "Edit|Duplicate"), ECmdDuplicate);
  iEditMenu->insertItem(tr("Select &all"), this, SLOT(Cmd(int)),
			Key("Ctrl+A", "Edit|Select all"), ECmdSelectAll);
  iEditMenu->insertSeparator();
  iEditMenu->insertItem(tr("Co&mpose paths"), this, SLOT(Cmd(int)),
			Key("none", "Edit|Compose paths"), ECmdComposePaths);
  iEditMenu->insertItem(tr("&Join paths"), this, SLOT(Cmd(int)),
			Key("Ctrl+J", "Edit|Join paths"), ECmdJoinPaths);
  //iEditMenu->insertItem(tr("Dec&ompose path"), this, SLOT(Cmd(int)),
  //Key_O, ECmdDecomposePath);
  iEditMenu->insertSeparator();
  iEditMenu->insertItem(tr("Insert text box"), this, SLOT(InsertTextBox()),
			Key("F10", "Edit|Insert text box"));
  iEditMenu->insertItem(tr("Insert item box"), this, SLOT(InsertItemBox()),
			Key("Shift+F10", "Edit|Insert text box"));
  iEditMenu->insertItem(tr("Edit object"), this, SLOT(EditObject()),
			Key("Ctrl+E", "Edit|Edit"), ECmdEdit);
  iEditMenu->insertSeparator();
  iEditMenu->insertItem(tr("D&ocument properties"),
			this, SLOT(EditDocProps()),
			Key("Ctrl+Shift+P", "Edit|Document properties"));
  iEditMenu->insertItem(tr("St&yle sheets"), this, SLOT(StyleSheets()),
			Key("Ctrl+Shift+S", "Edit|Style sheets"));
  connect(iEditMenu, SIGNAL(aboutToShow()),
	  this, SLOT(AboutToShowEditMenu()));
  menu->insertItem(tr("&Edit"), iEditMenu);

  iModeMenu = new QPopupMenu;
  for (int i = 0; i < IpeOverlayFactory::Num; ++i)
    iModeAction[i]->addTo(iModeMenu);

  menu->insertItem(tr("&Mode"), iModeMenu);

  iSnapMenu = new QPopupMenu;
  iSnapMenu->insertItem(tr("&Absolute grid/angle size"),
			this, SLOT(AbsoluteSnapping()),
			Key("Ctrl+Shift+T", "Snap|Absolute"),
			EAbsoluteSnappingMenuId);
  iSnapMenu->insertSeparator();
  iSnapActionGroup->addTo(iSnapMenu);
  iSnapMenu->insertSeparator();
  iSnapMenu->insertItem(tr("Set origin"), this,
			SLOT(SnapCmd(int)),
			Key("F1", "Snap|Set origin"), ECmdSetOrigin);
  iSnapMenu->insertItem(tr("Set origin && snap"), this,
			SLOT(SnapCmd(int)),
			Key("none", "Snap|Set origin & snap"),
			ECmdSetOriginSnap);
  iSnapMenu->insertItem(tr("Hide axes"), this,
			SLOT(SnapCmd(int)),
			Key("Shift+F1", "Snap|Hide axes"), ECmdResetOrigin);
  iSnapMenu->insertItem(tr("Set direction"), this,
			SLOT(SnapCmd(int)),
			Key("F2", "Snap|Set direction"), ECmdSetDirection);
  iSnapMenu->insertItem(tr("Set line"), this,
			SLOT(SnapCmd(int)),
			Key("F3", "Snap|Set line"), ECmdSetLine);
  iSnapMenu->insertItem(tr("Set line && snap"), this,
			SLOT(SnapCmd(int)),
			Key("Shift+F3", "Snap|Set line & snap"),
			ECmdSetLineSnap);
  menu->insertItem(tr("&Snap"), iSnapMenu, ESnapMenuId);

  iZoomMenu = new QPopupMenu;
  iZoomMenu->insertItem(tr("&Grid visible"),
			this, SLOT(GridVisible()),
			Key("F12", "Zoom|Grid"),
			EGridVisibleMenuId);
  iZoomMenu->insertItem(tr("&Coordinates visible"),
			this, SLOT(CoordinatesVisible()),
			Key("Shift+F12", "Zoom|Coordinates"),
			ECoordinatesVisibleMenuId);
  iZoomMenu->insertSeparator();
  iZoomMenu->insertItem(tr("Zoom &in"), iResolution, SLOT(stepUp()),
			Key("Ctrl+PgUp", "Zoom|Zoom in"));
  iZoomMenu->insertItem(tr("Zoom &out"), iResolution, SLOT(stepDown()),
			Key("Ctrl+PgDown", "Zoom|Zoom out"));
  iZoomMenu->insertItem(tr("&Normal size"), this, SLOT(NormalSize()),
			Key("/", "Zoom|Normal size"));
  iZoomMenu->insertItem(tr("Fit &page"), this, SLOT(FitPage()),
			Key("\\", "Zoom|Fit page"));
  iZoomMenu->insertItem(tr("&Fit objects"),
			this, SLOT(FitObjects()),
			Key("=", "Zoom|Fit objects"));
  iZoomMenu->insertItem(tr("Fit &selection"),
			this, SLOT(FitSelection()),
			Key("F11", "Zoom|Fit selection"));
  iZoomMenu->insertSeparator();
  iZoomMenu->insertItem(tr("Pan &here"), this,
			SLOT(SnapCmd(int)),
			Key("x", "view|Pan here"), ECmdHere);
  menu->insertItem(tr("&Zoom"), iZoomMenu, EZoomMenuId);

  iLayerMenu = new QPopupMenu;
  iLayerMenu->insertItem(tr("&New layer"), this, SLOT(Cmd(int)),
			 Key("Ctrl+Shift+N", "Layers|New"),
			 ECmdNewLayer);
  iLayerMenu->insertItem(tr("&Rename layer"), this, SLOT(Cmd(int)),
			 Key("Ctrl+Shift+R", "Layers|Rename"),
			 ECmdRenameLayer);
  iLayerMenu->insertSeparator();
  iLayerMenu->insertItem(tr("&Select all in layer"),
			 this, SLOT(Cmd(int)),
			 Key("Ctrl+Shift+A", "Layers|Select all"),
			 ECmdSelectLayer);
  iLayerMenu->insertItem(tr("&Move objects to layer"),
			 this, SLOT(Cmd(int)),
			 Key("Ctrl+Shift+M", "Layers|Move objects"),
			 ECmdMoveToLayer);
  menu->insertItem(tr("&Layers"), iLayerMenu);

  iViewMenu = new QPopupMenu;
  iViewMenu->insertItem(tr("Next vie&w"), this, SLOT(NextView()),
			Key("PgDown", "Views|Next view"));
  iViewMenu->insertItem(tr("&Previous view"), this, SLOT(PreviousView()),
			Key("PgUp", "Views|Previous view"));
  iViewMenu->insertItem(tr("&First view"), this, SLOT(Cmd(int)),
			Key("Home", "Views|First view"),
			ECmdFirstView);
  iViewMenu->insertItem(tr("&Last view"), this, SLOT(Cmd(int)),
			Key("End", "Views|Last view"),
			ECmdLastView);
  iViewMenu->insertSeparator();
  iViewMenu->insertItem(tr("&New layer, new view"), this, SLOT(Cmd(int)),
			Key("Ctrl+Shift+I", "Views|New layer, new view"),
			ECmdNewLayerNewView);
  iViewMenu->insertItem(tr("New &view"), this, SLOT(Cmd(int)),
			Key("none", "Views|New view"),
			ECmdNewView);
  iViewMenu->insertItem(tr("&Delete view"), this, SLOT(Cmd(int)),
			Key("none", "Views|Delete view"),
			ECmdDeleteView);
  iViewMenu->insertSeparator();
  iViewMenu->insertItem(tr("&Edit views"), this,
			SLOT(EditViews()),
			Key("Ctrl+P", "Views|Edit views"));
  menu->insertItem(tr("&Views"), iViewMenu);

  iPageMenu = new QPopupMenu;
  iPageMenu->insertItem(tr("&Next page"), iPageNumber, SLOT(stepUp()),
		       Key("Shift+PgDown", "Page|Next"));
  iPageMenu->insertItem(tr("&Previous page"), iPageNumber, SLOT(stepDown()),
		       Key("Shift+PgUp", "Page|Previous"));
  iPageMenu->insertItem(tr("&First page"), this, SLOT(FirstPage()),
			Key("Shift+Home", "Page|First"));
  iPageMenu->insertItem(tr("&Last page"), this, SLOT(LastPage()),
			Key("Shift+End", "Page|Last"));
  iPageMenu->insertSeparator();
  iPageMenu->insertItem(tr("&Insert page"), this, SLOT(CreatePage()),
		       Key("Ctrl+I", "Page|Insert"));
  iPageMenu->insertItem(tr("&Cut page"), this, SLOT(CopyPage(int)),
		       Key("Ctrl+Shift+X", "Page|Cut"), 1);
  iPageMenu->insertItem(tr("Cop&y page"), this, SLOT(CopyPage(int)),
		       Key("Ctrl+Shift+C", "Page|Copy"), 0);
  iPageMenu->insertItem(tr("P&aste page"), this, SLOT(PastePage()),
		       Key("Ctrl+Shift+V", "Page|Paste"));
  iPageMenu->insertItem(tr("&Delete page"), this, SLOT(DeletePage()),
		       Key("none", "Page|Delete"));
  menu->insertItem(tr("&Pages"), iPageMenu);

  iIpeletMenu = new QPopupMenu;
  iPasteBitmapId = 0;
  for (uint i = 0; i < iIpeletMaster.size(); ++i) {
    Ipelet *ipelet = iIpeletMaster[i];
    if (ipelet->NumFunctions() > 1) {
      QPopupMenu *pop = new QPopupMenu;
      for (int j = 0; j < ipelet->NumFunctions(); ++j) {
	int id = pop->insertItem(ipelet->SubLabel(j),
				 this, SLOT(RunIpelet(int)),
				 Key(ipelet->KeySequence(j), "Ipelet"));
	pop->setItemParameter(id, i * 0x1000 + j);
      }
      if (!qstrcmp(ipelet->Label(), "Insert image")) {
	// personal treatment
	iFileMenu->insertItem(ipelet->Label(), pop, -1, 6);
	iFileMenu->insertSeparator(6);
	iPasteBitmapId = i * 0x1000 + 2;
      } else
	iIpeletMenu->insertItem(ipelet->Label(), pop);
    } else {
      int id = iIpeletMenu->insertItem(ipelet->Label(),
				       this, SLOT(RunIpelet(int)),
				       Key(ipelet->KeySequence(0), "Ipelet"));
      iIpeletMenu->setItemParameter(id, i * 0x1000 + 1);
    }
  }
  menu->insertItem(tr("&Ipelets"), iIpeletMenu);

  menu->insertSeparator();

  QPopupMenu* help = new QPopupMenu;
  help->insertItem(tr("Ipe &manual"), this, SLOT(Manual()));
  help->insertItem(tr("&What's this"), this, SLOT(whatsThis()));
  help->insertSeparator();
  help->insertItem(tr("Pre&ferences"), this, SLOT(EditPrefs()));
  help->insertSeparator();
  help->insertItem(tr("&About Ipe..."), this, SLOT(About()));
  menu->insertItem(tr("&Help"), help);

  // --------------------------------------------------------------------

  iCanvas = new IpeCanvas(this, statusBar(), this);
  setCentralWidget(iCanvas);
  statusBar();
  QWhatsThis::add(iCanvas, tr(canvasText));

  iResolution->setValue(72000);

  iAbsoluteAttributes = false;
  iAbsoluteSnapping = false;
  SwitchAttributeUi();
  SwitchSnapUi();

  iSnapData.iSnap = IpeSnapData::ESnapNone;
  iSnapData.iGridSize = prefs->iGridSize;
  iSnapData.iGridVisible = prefs->iGridVisible;
  iSnapData.iSnapDistance = prefs->iSnapDistance;
  iSnapData.iSelectDistance = prefs->iSelectDistance;
  iSnapData.iWithAxes = false;
  iSnapData.iOrigin = IpeVector::Zero;
  iSnapData.iDir = 0.0;

  iDoc = 0;
  SetCreator(iModeAction[0]);

  connect(prefs, SIGNAL(Changed()),
	  this, SLOT(PreferencesChanged()));

  // filter all key events for focussable widgets
  // installEventFilter(this);
  QFocusData *fd = focusData();
  int num = fd->count();
  QWidget *w = fd->home();
  while (num--) {
    if (w) w->installEventFilter(this);
    w = fd->next();
  }
}

// ----------------------------------------------------------

//! Destructor.
AppUi::~AppUi()
{
  delete iDoc;
}

// --------------------------------------------------------------------
