/***************************************************************************
 *   Copyright (c) 2013 Thomas Anderson <blobfish[at]gmx.com>              *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/


#include "PreCompiled.h"
#ifndef _PreCpmp_
# include <QButtonGroup>
# include <QPushButton>
#endif


#include <sstream>

#include <TopoDS_Shape.hxx>
#include <TopoDS_Vertex.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS.hxx>
#include <BRepExtrema_DistShapeShape.hxx>
#include <BRep_Tool.hxx>
#include <TopExp.hxx>
#include <Geom_ElementarySurface.hxx>
#include <Geom_CylindricalSurface.hxx>
#include <Geom_SphericalSurface.hxx>
#include <Geom_Line.hxx>
#include <GeomAPI_ProjectPointOnCurve.hxx>
#include <GeomAPI_ExtremaCurveCurve.hxx>

#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoMatrixTransform.h>
#include <Inventor/nodes/SoVertexProperty.h>
#include <Inventor/nodes/SoLineSet.h>
#include <Inventor/nodes/SoIndexedLineSet.h>
#include <Inventor/nodes/SoText2.h>
#include <Inventor/nodes/SoFont.h>
#include <Inventor/nodes/SoAnnotation.h>
#include <Inventor/nodekits/SoShapeKit.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoCone.h>
#include <Inventor/nodes/SoCoordinate3.h>
#include <Inventor/nodes/SoNurbsCurve.h>
#include <Inventor/engines/SoComposeVec3f.h>
#include <Inventor/engines/SoCalculator.h>
#include <Inventor/nodes/SoResetTransform.h>
#include <Inventor/engines/SoConcatenate.h>
#include <Inventor/engines/SoComposeRotationFromTo.h>
#include <Inventor/engines/SoComposeRotation.h>
#include <Inventor/nodes/SoMaterial.h>

#include <Base/Console.h>
#include <Base/UnitsApi.h>
#include "../App/PartFeature.h"
#include <Gui/Application.h>
#include <Gui/Selection.h>
#include <Gui/Document.h>
#include <Gui/View3DInventor.h>
#include <Gui/View3DInventorViewer.h>
#include <Gui/BitmapFactory.h>
#include <Gui/Control.h>

#include "TaskDimension.h"

bool PartGui::getShapeFromStrings(TopoDS_Shape &shapeOut, const std::string &doc, const std::string &object, const std::string &sub)
{
  App::Document *docPointer = App::GetApplication().getDocument(doc.c_str());
  if (!docPointer)
    return false;
  App::DocumentObject *objectPointer = docPointer->getObject(object.c_str());
  if (!objectPointer)
    return false;
  Part::Feature *feature = dynamic_cast<Part::Feature *>(objectPointer);
  if (!feature)
    return false;
  shapeOut = feature->Shape.getValue();
  if (sub.size() > 0)
    shapeOut = feature->Shape.getShape().getSubShape(sub.c_str());
  if (shapeOut.IsNull())
    return false;
  return true;
}

bool PartGui::evaluateLinearPreSelection(TopoDS_Shape &shape1, TopoDS_Shape &shape2)
{
  std::vector<Gui::SelectionSingleton::SelObj> selections = Gui::Selection().getSelection();
  if (selections.size() != 2)
    return false;
  std::vector<Gui::SelectionSingleton::SelObj>::iterator it;
  std::vector<TopoDS_Shape> shapes;
  
  for (it = selections.begin(); it != selections.end(); ++it)
  {
    Part::Feature *feature = dynamic_cast<Part::Feature *>((*it).pObject);
    if (!feature)
      break;
    TopoDS_Shape shape = feature->Shape.getValue();
    if (strlen((*it).SubName) > 0)
      shape = feature->Shape.getShape().getSubShape((*it).SubName);
    if (shape.IsNull())
      break;
    shapes.push_back(shape);
  }
  
  if (shapes.size() != 2)
    return false;
  
  shape1 = shapes.front();
  shape2 = shapes.back();
  
  return true;
}

void PartGui::goDimensionLinearRoot()
{
  PartGui::ensureSomeDimensionVisible();
  
  TopoDS_Shape shape1, shape2;
  if(evaluateLinearPreSelection(shape1, shape2))
  {
    goDimensionLinearNoTask(shape1, shape2);
    Gui::Selection().clearSelection();
  }
  else
  {
    Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
    if (!dlg)
    {
      Gui::Selection().clearSelection();
      dlg = new PartGui::TaskMeasureLinear();
    }
    Gui::Control().showDialog(dlg);
  }
}

void PartGui::goDimensionLinearNoTask(const TopoDS_Shape &shape1, const TopoDS_Shape &shape2)
{
    //Warning: BRepExtrema_DistShapeShape solution array is NOT 0 based.
  BRepExtrema_DistShapeShape measure(shape1, shape2);
  if (!measure.IsDone() || measure.NbSolution() < 1)
    return;
  
  dumpLinearResults(measure);
  addLinearDimensions(measure);
  
  //if we ever make this a class add viewer to member.
  Gui::View3DInventorViewer *viewer = getViewer();
  if (!viewer)
    return;
}

void PartGui::dumpLinearResults(const BRepExtrema_DistShapeShape &measure)
{
  std::ostringstream out;
  //switch to initializer list when switch to c++11
  std::vector<std::string> typeNames;
  typeNames.resize(3);
  typeNames[0] = "Vertex";
  typeNames[1] = "Edge";
  typeNames[2] = "Face";
  
  Base::Quantity quantity(measure.Value(), Base::Unit::Length);
  out << std::endl<< std::setprecision(std::numeric_limits<double>::digits10 + 1) << "distance = " <<
    measure.Value() << "mm    unit distance = " << quantity.getUserString().toUtf8().constData() << std::endl <<
    "solution count: " << measure.NbSolution() << std::endl;
    
  for (int index = 1; index < measure.NbSolution() + 1; ++index) //not zero based.
  {
    gp_Pnt point1 = measure.PointOnShape1(index);
    gp_Pnt point2 = measure.PointOnShape2(index);
    out << "   solution " << index << ":" << std::endl << std::setprecision(std::numeric_limits<double>::digits10 + 1) <<
           "      point1 " << point1.X() << "   " << point1.Y() << "   " << point1.Z() << std::endl <<
           "      point2 " << point2.X() << "   " << point2.Y() << "   " << point2.Z() << std::endl <<
           "      DeltaX " << fabs(point2.X() - point1.X()) << std::endl <<
           "      DeltaY " << fabs(point2.Y() - point1.Y()) << std::endl <<
           "      DeltaZ " << fabs(point2.Z() - point1.Z()) << std::endl <<
           "      shape type on object1 is: " << typeNames.at(measure.SupportTypeShape1(index)) << std::endl <<
           "      shape type on object2 is: " << typeNames.at(measure.SupportTypeShape2(index)) << std::endl;
  }
  out << std::endl;
  Base::Console().Message(out.str().c_str());
}

Gui::View3DInventorViewer * PartGui::getViewer()
{
  Gui::View3DInventor *view = dynamic_cast<Gui::View3DInventor*>(Gui::Application::Instance->
    activeDocument()->getActiveView());
  if (!view)
    return 0;
  Gui::View3DInventorViewer *viewer = view->getViewer();
  if (!viewer)
    return 0;
  return viewer;
}

void PartGui::addLinearDimensions(const BRepExtrema_DistShapeShape &measure)
{
  Gui::View3DInventorViewer *viewer = getViewer();
  if (!viewer)
    return;
  gp_Pnt point1 = measure.PointOnShape1(1);
  gp_Pnt point2 = measure.PointOnShape2(1);
  viewer->addDimension3d(createLinearDimension(point1, point2, SbColor(1.0, 0.0, 0.0)));
  
  //create deltas. point1 will always be the same.
  gp_Pnt temp = point1;
  gp_Pnt lastTemp = temp;
  temp.SetX(point2.X());
  viewer->addDimensionDelta(createLinearDimension(lastTemp, temp, SbColor(0.0, 1.0, 0.0)));
  lastTemp = temp;
  temp.SetY(point2.Y());
  viewer->addDimensionDelta(createLinearDimension(lastTemp, temp, SbColor(0.0, 1.0, 0.0)));
  lastTemp = temp;
  temp.SetZ(point2.Z());
  viewer->addDimensionDelta(createLinearDimension(lastTemp, temp, SbColor(0.0, 1.0, 0.0)));
}

SoNode* PartGui::createLinearDimension(const gp_Pnt &point1, const gp_Pnt &point2, const SbColor &color)
{
  SbVec3f vec1(point1.X(), point1.Y(), point1.Z());
  SbVec3f vec2(point2.X(), point2.Y(), point2.Z());
  if ((vec2-vec1).length() < FLT_EPSILON)
    return new SoSeparator(); //empty object.
  PartGui::DimensionLinear *dimension = new PartGui::DimensionLinear();
  dimension->point1.setValue(vec1);
  dimension->point2.setValue(vec2);
  
  Base::Quantity quantity(static_cast<double>((vec2-vec1).length()), Base::Unit::Length);
  dimension->text.setValue(quantity.getUserString().toUtf8().constData());
  
  dimension->dColor.setValue(color);
  return dimension;
}

void PartGui::eraseAllDimensions()
{
  Gui::View3DInventor *view = dynamic_cast<Gui::View3DInventor*>(Gui::Application::Instance->
    activeDocument()->getActiveView());
  if (!view)
    return;
  Gui::View3DInventorViewer *viewer = view->getViewer();
  if (!viewer)
    return;
  viewer->eraseAllDimensions();
}

void PartGui::toggle3d()
{
  ParameterGrp::handle group = App::GetApplication().GetUserParameter().
    GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("View");
  bool visibility = group->GetBool("Dimensions3dVisible", true);
  if (visibility)
    group->SetBool("Dimensions3dVisible", false);
  else
    group->SetBool("Dimensions3dVisible", true);
}

void PartGui::toggleDelta()
{
  ParameterGrp::handle group = App::GetApplication().GetUserParameter().
    GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("View");
  bool visibility = group->GetBool("DimensionsDeltaVisible", true);
  if (visibility)
    group->SetBool("DimensionsDeltaVisible", false);
  else
    group->SetBool("DimensionsDeltaVisible", true);
}

void PartGui::ensureSomeDimensionVisible()
{
  ParameterGrp::handle group = App::GetApplication().GetUserParameter().
    GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("View");
  bool visibilityAll = group->GetBool("DimensionsVisible", true);
  if (!visibilityAll)
    group->SetBool("DimensionsVisible", true);
  bool visibility3d = group->GetBool("Dimensions3dVisible", true);
  bool visibilityDelta = group->GetBool("DimensionsDeltaVisible", true);
  
  if (!visibility3d && !visibilityDelta) //both turned off.
    group->SetBool("Dimensions3dVisible", true); //turn on 3d, so something is visible.
}

void PartGui::ensure3dDimensionVisible()
{
  ParameterGrp::handle group = App::GetApplication().GetUserParameter().
    GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("View");
  bool visibilityAll = group->GetBool("DimensionsVisible", true);
  if (!visibilityAll)
    group->SetBool("DimensionsVisible", true);
  bool visibility3d = group->GetBool("Dimensions3dVisible", true);
  
  if (!visibility3d) //both turned off.
    group->SetBool("Dimensions3dVisible", true); //turn on 3d, so something is visible.
}


SO_KIT_SOURCE(PartGui::DimensionLinear);

void PartGui::DimensionLinear::initClass()
{
    SO_KIT_INIT_CLASS(DimensionLinear, SoSeparatorKit, "SeparatorKit");
}

PartGui::DimensionLinear::DimensionLinear()
{
    SO_KIT_CONSTRUCTOR(PartGui::DimensionLinear);

    SO_KIT_ADD_CATALOG_ENTRY(transformation, SoTransform, true, topSeparator,"" , true);
    SO_KIT_ADD_CATALOG_ENTRY(annotate, SoAnnotation, true, topSeparator,"" , true);
    SO_KIT_ADD_CATALOG_ENTRY(leftArrow, SoShapeKit, true, topSeparator,"" ,true);
    SO_KIT_ADD_CATALOG_ENTRY(rightArrow, SoShapeKit, true, topSeparator,"" ,true);
    SO_KIT_ADD_CATALOG_ENTRY(line, SoShapeKit, true, annotate,"" ,true);
    SO_KIT_ADD_CATALOG_ENTRY(textSep, SoSeparator, true, annotate,"" ,true);

    SO_KIT_INIT_INSTANCE();

    SO_NODE_ADD_FIELD(rotate, (1.0, 0.0, 0.0, 0.0));//postion orientation of the dimension.
    SO_NODE_ADD_FIELD(length, (1.0));//turns into dimension length
    SO_NODE_ADD_FIELD(origin, (0.0, 0.0, 0.0));//static
    SO_NODE_ADD_FIELD(text, ("test"));//dimension text
    SO_NODE_ADD_FIELD(dColor, (1.0, 0.0, 0.0));//dimension color.
    
    point1.setValue(SbVec3f(0.0, 0.0, 0.0));
    point2.setValue(SbVec3f(1.0, 0.0, 0.0));

    setupDimension();
}

PartGui::DimensionLinear::~DimensionLinear()
{

}

SbBool PartGui::DimensionLinear::affectsState() const
{
    return false;
}

void PartGui::DimensionLinear::setupDimension()
{
  //transformation
  SoTransform *trans = static_cast<SoTransform *>(getPart("transformation", true));
  trans->translation.connectFrom(&point1);
  //build engine for vector subtraction and length.
  SoCalculator *hyp = new SoCalculator();
  hyp->A.connectFrom(&point1);
  hyp->B.connectFrom(&point2);
  hyp->expression.set1Value(0, "oA = B-A");
  hyp->expression.set1Value(1, "oB = normalize(oA)");
  hyp->expression.set1Value(2, "oa = length(oA)");
  length.connectFrom(&hyp->oa);
  //build engine for rotation.
  SoComposeRotationFromTo *rotationEngine = new SoComposeRotationFromTo();
  rotationEngine->from.setValue(SbVec3f(1.0, 0.0, 0.0));
  rotationEngine->to.connectFrom(&hyp->oB);
  trans->rotation.connectFrom(&rotationEngine->rotation);

  //color
  SoMaterial *material = new SoMaterial;
  material->diffuseColor.connectFrom(&dColor);

  //dimension arrows.
  SoCone *cone = new SoCone();
  cone->bottomRadius.setValue(0.25);
  cone->height.setValue(0.5);

  setPart("leftArrow.shape", cone);
  set("leftArrow.transform", "rotation 0.0 0.0 1.0 1.5707963");
  set("leftArrow.transform", "translation 0.25 0.0 0.0"); //half cone height.
  setPart("rightArrow.shape", cone);
  set("rightArrow.transform", "rotation 0.0 0.0 -1.0 1.5707963"); //no constant for PI.
  //have use local here to do the offset because the main is wired up to length of dimension.
  set("rightArrow.localTransform", "translation 0.0 -0.25 0.0"); //half cone height.

  SoTransform *transform = static_cast<SoTransform *>(getPart("rightArrow.transform", false));
  if (!transform)
      return;//what to do here?
  SoComposeVec3f *vec = new SoComposeVec3f;
  vec->x.connectFrom(&length);
  vec->y.setValue(0.0);
  vec->z.setValue(0.0);
  transform->translation.connectFrom(&vec->vector);

  setPart("leftArrow.material", material);
  setPart("rightArrow.material", material);

  //line
  SoConcatenate *catEngine = new SoConcatenate(SoMFVec3f::getClassTypeId());
  //don't know how to get around having this dummy origin. cat engine wants to connectfrom?
  catEngine->input[0]->connectFrom(&origin);
  catEngine->input[1]->connectFrom(&vec->vector);

  SoVertexProperty *lineVerts = new SoVertexProperty;
  lineVerts->vertex.connectFrom(catEngine->output);

  int lineVertexMap[] = {0, 1};
  int lineVertexMapSize(sizeof(lineVertexMap)/sizeof(int));
  SoIndexedLineSet *line = new SoIndexedLineSet;
  line->vertexProperty = lineVerts;
  line->coordIndex.setValues(0, lineVertexMapSize, lineVertexMap);

  setPart("line.shape", line);
  setPart("line.material", material);

  //text
  SoSeparator *textSep = static_cast<SoSeparator *>(getPart("textSep", true));
  if (!textSep)
      return;

  textSep->addChild(material);

  SoCalculator *textVecCalc = new SoCalculator();
  textVecCalc->A.connectFrom(&vec->vector);
  textVecCalc->B.set1Value(0, 0.0, 0.250, 0.0);
  textVecCalc->expression.set1Value(0, "oA = (A / 2) + B");

  SoTransform *textTransform =  new SoTransform();
  textTransform->translation.connectFrom(&textVecCalc->oA);
  textSep->addChild(textTransform);
  
  SoFont *fontNode = new SoFont();
  fontNode->name.setValue("defaultFont");
  fontNode->size.setValue(30);
  textSep->addChild(fontNode);

  SoText2 *textNode = new SoText2();
  textNode->justification = SoText2::CENTER;
  textNode->string.connectFrom(&text);
  textSep->addChild(textNode);

  //this prevents the 2d text from screwing up the bounding box for a viewall
  SoResetTransform *rTrans = new SoResetTransform;
  rTrans->whatToReset = SoResetTransform::BBOX;
  textSep->addChild(rTrans);
}

PartGui::TaskMeasureLinear::TaskMeasureLinear(): selections1(), selections2(), buttonSelectedIndex(0)
{
  setUpGui();
}

PartGui::TaskMeasureLinear::~TaskMeasureLinear()
{
  Gui::Selection().clearSelection();
}

void PartGui::TaskMeasureLinear::onSelectionChanged(const Gui::SelectionChanges& msg)
{
  if (buttonSelectedIndex == 0)
  {
    if (msg.Type == Gui::SelectionChanges::AddSelection)
    {
      DimSelections::DimSelection newSelection;
      newSelection.documentName = msg.pDocName;
      newSelection.objectName = msg.pObjectName;
      newSelection.subObjectName = msg.pSubName;
      newSelection.x = msg.x;
      newSelection.y = msg.y;
      newSelection.z = msg.z;
      selections1.selections.clear();//we only want one item.
      selections1.selections.push_back(newSelection);
      QTimer::singleShot(0, this, SLOT(selectionClearDelayedSlot()));
      stepped->getButton(1)->setEnabled(true);
      stepped->getButton(1)->setChecked(true);
      return;
    }
  }
  if (buttonSelectedIndex == 1)
  {
    if (msg.Type == Gui::SelectionChanges::AddSelection)
    {
      DimSelections::DimSelection newSelection;
      newSelection.documentName = msg.pDocName;
      newSelection.objectName = msg.pObjectName;
      newSelection.subObjectName = msg.pSubName;
      newSelection.x = msg.x;
      newSelection.y = msg.y;
      newSelection.z = msg.z;
      selections2.selections.clear();//we only want one item.
      selections2.selections.push_back(newSelection);
      buildDimension();
      clearSelectionStrings();
      QTimer::singleShot(0, this, SLOT(selectionClearDelayedSlot()));
      stepped->getButton(0)->setChecked(true);
      stepped->getButton(1)->setEnabled(false);
      return;
    }
  }
}

void PartGui::TaskMeasureLinear::selectionClearDelayedSlot()
{
  //hack.
  //clearing selections are not working as I hoped. Apparently the observer callback gets called
  //before the actual selection takes place. Resulting in selections being left. this addresses this
  //by being called from the event loop.
  this->blockConnection(true);
  Gui::Selection().clearSelection();
  this->blockConnection(false);
}

void PartGui::TaskMeasureLinear::buildDimension()
{
  if(selections1.selections.size() != 1 || selections2.selections.size() != 1)
    return;
  
  DimSelections::DimSelection current1 = selections1.selections.at(0);
  DimSelections::DimSelection current2 = selections2.selections.at(0);
  
  TopoDS_Shape shape1, shape2;
  if (!getShapeFromStrings(shape1, current1.documentName, current1.objectName, current1.subObjectName))
  {
    Base::Console().Message("\nFailed to get shape\n\n");
    return;
  }
  if (!getShapeFromStrings(shape2, current2.documentName, current2.objectName, current2.subObjectName))
  {
    Base::Console().Message("\nFailed to get shape\n\n");
    return;
  }
  goDimensionLinearNoTask(shape1, shape2);
}

void PartGui::TaskMeasureLinear::clearSelectionStrings()
{
  selections1.selections.clear();
  selections2.selections.clear();
}

void PartGui::TaskMeasureLinear::setUpGui()
{
  QPixmap mainIcon = Gui::BitmapFactory().pixmap("Part_Measure_Linear");
  
  Gui::TaskView::TaskBox* selectionTaskBox = new Gui::TaskView::TaskBox
    (mainIcon, QObject::tr("Selections"), false, 0);
  QVBoxLayout *selectionLayout = new QVBoxLayout();
  stepped = new SteppedSelection(2, selectionTaskBox);
  selectionLayout->addWidget(stepped);
  selectionTaskBox->groupLayout()->addLayout(selectionLayout);
  
  Gui::TaskView::TaskBox* controlTaskBox = new Gui::TaskView::TaskBox
    (mainIcon, QObject::tr("Control"), false, 0);
  QVBoxLayout *controlLayout = new QVBoxLayout();
  
  DimensionControl *control = new DimensionControl(controlTaskBox);
  controlLayout->addWidget(control);
  controlTaskBox->groupLayout()->addLayout(controlLayout);
  QObject::connect(control->resetButton, SIGNAL(clicked(bool)), this, SLOT(resetDialogSlot(bool)));
  
  this->setButtonPosition(TaskDialog::South);
  Content.push_back(selectionTaskBox);
  Content.push_back(controlTaskBox);
  
  stepped->getButton(0)->setChecked(true);//before wired up.
  stepped->getButton(0)->setEnabled(true);
  QObject::connect(stepped->getButton(0), SIGNAL(toggled(bool)), this, SLOT(selection1Slot(bool)));
  QObject::connect(stepped->getButton(1), SIGNAL(toggled(bool)), this, SLOT(selection2Slot(bool)));
}

void PartGui::TaskMeasureLinear::selection1Slot(bool checked)
{
  if (!checked)
  {
    if (selections1.selections.size() > 0)
      stepped->setIconDone(0);
    return;
  }
  buttonSelectedIndex = 0;
  
  this->blockConnection(true);
  Gui::Selection().clearSelection();
  //we should only be working with 1 entity, but oh well do the loop anyway.
  std::vector<DimSelections::DimSelection>::const_iterator it;
  for (it = selections1.selections.begin(); it != selections1.selections.end(); ++it)
    Gui::Selection().addSelection(it->documentName.c_str(), it->objectName.c_str(), it->subObjectName.c_str());
  this->blockConnection(false);
}

void PartGui::TaskMeasureLinear::selection2Slot(bool checked)
{
  if (!checked)
    return;
  buttonSelectedIndex = 1;
  this->blockConnection(true);
  Gui::Selection().clearSelection();
  std::vector<DimSelections::DimSelection>::const_iterator it;
  for (it = selections2.selections.begin(); it != selections2.selections.end(); ++it)
    Gui::Selection().addSelection(it->documentName.c_str(), it->objectName.c_str(), it->subObjectName.c_str());
  this->blockConnection(false);
}

void PartGui::TaskMeasureLinear::resetDialogSlot(bool)
{
  clearSelectionStrings();
  this->blockConnection(true);
  Gui::Selection().clearSelection();
  stepped->getButton(0)->setChecked(true);
  stepped->getButton(1)->setEnabled(false);
  this->blockConnection(false);
}

void PartGui::TaskMeasureLinear::toggle3dSlot(bool)
{
  PartGui::toggle3d();
}

void PartGui::TaskMeasureLinear::toggleDeltaSlot(bool)
{
  PartGui::toggleDelta();
}

void PartGui::TaskMeasureLinear::clearAllSlot(bool)
{
  PartGui::eraseAllDimensions();
}

PartGui::VectorAdapter::VectorAdapter() : status(false), vector()
{
}

PartGui::VectorAdapter::VectorAdapter(const TopoDS_Face &faceIn, const gp_Vec &pickedPointIn) :
  status(false), vector(), origin(pickedPointIn)
{
  Handle_Geom_Surface surface = BRep_Tool::Surface(faceIn);
  if (surface->IsKind(STANDARD_TYPE(Geom_ElementarySurface)))
  {
    Handle_Geom_ElementarySurface eSurface = Handle(Geom_ElementarySurface)::DownCast(surface);
    gp_Dir direction = eSurface->Axis().Direction();
    vector = direction;
    vector.Normalize();
    if (faceIn.Orientation() == TopAbs_REVERSED)
      vector.Reverse();
    if (surface->IsKind(STANDARD_TYPE(Geom_CylindricalSurface)) || 
      surface->IsKind(STANDARD_TYPE(Geom_SphericalSurface))
    )
    {
      origin = eSurface->Axis().Location().XYZ();
      projectOriginOntoVector(pickedPointIn);
    }
    else
      origin = pickedPointIn + vector;
    status = true;
  }
}

PartGui::VectorAdapter::VectorAdapter(const TopoDS_Edge &edgeIn, const gp_Vec &pickedPointIn) :
  status(false), vector(), origin(pickedPointIn)
{
  TopoDS_Vertex firstVertex = TopExp::FirstVertex(edgeIn, Standard_True);
  TopoDS_Vertex lastVertex = TopExp::LastVertex(edgeIn, Standard_True);
  vector = PartGui::convert(lastVertex) - PartGui::convert(firstVertex);
  if (vector.Magnitude() < Precision::Confusion())
    return;
  vector.Normalize();
  
  status = true;
  projectOriginOntoVector(pickedPointIn);
}

PartGui::VectorAdapter::VectorAdapter(const TopoDS_Vertex &vertex1In, const TopoDS_Vertex &vertex2In) :
  status(false), vector(), origin()
{
  vector = PartGui::convert(vertex2In) - PartGui::convert(vertex1In);
  vector.Normalize();
  
  //build origin half way.
  gp_Vec tempVector = (PartGui::convert(vertex2In) - PartGui::convert(vertex1In));
  double mag = tempVector.Magnitude();
  tempVector.Normalize();
  tempVector *= (mag / 2.0);
  origin = tempVector + PartGui::convert(vertex1In);
  
  status = true;
}

PartGui::VectorAdapter::VectorAdapter(const gp_Vec &vector1, const gp_Vec &vector2) :
  status(false), vector(), origin()
{
  vector = vector2- vector1;
  vector.Normalize();
  
  //build origin half way.
  gp_Vec tempVector = vector2 - vector1;
  double mag = tempVector.Magnitude();
  tempVector.Normalize();
  tempVector *= (mag / 2.0);
  origin = tempVector + vector1;
  
  status = true;
}

void PartGui::VectorAdapter::projectOriginOntoVector(const gp_Vec &pickedPointIn)
{
  Handle_Geom_Curve heapLine = new Geom_Line(origin.XYZ(), vector.XYZ());
  gp_Pnt tempPoint(pickedPointIn.XYZ());
  GeomAPI_ProjectPointOnCurve projection(tempPoint, heapLine);
  if (projection.NbPoints() < 1)
    return;
  origin.SetXYZ(projection.Point(1).XYZ());
}

PartGui::VectorAdapter::operator gp_Lin() const
{
  gp_Pnt tempOrigin;
  tempOrigin.SetXYZ(origin.XYZ());
  return gp_Lin(tempOrigin, gp_Dir(vector));
}

gp_Vec PartGui::convert(const TopoDS_Vertex &vertex)
{
  gp_Pnt point = BRep_Tool::Pnt(vertex);
  gp_Vec out(point.X(), point.Y(), point.Z());
  return out;
}

void PartGui::goDimensionAngularRoot()
{
  PartGui::ensure3dDimensionVisible();
  
  VectorAdapter adapter1, adapter2;
  if(PartGui::evaluateAngularPreSelection(adapter1, adapter2))
    goDimensionAngularNoTask(adapter1, adapter2);
  else
  {
    Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog();
    if (!dlg)
    {
      Gui::Selection().clearSelection();
      dlg = new PartGui::TaskMeasureAngular();
    }
    Gui::Control().showDialog(dlg);
  }
  Gui::Selection().clearSelection();
}

bool PartGui::evaluateAngularPreSelection(VectorAdapter &vector1Out, VectorAdapter &vector2Out)
{
  std::vector<Gui::SelectionSingleton::SelObj> selections = Gui::Selection().getSelection();
  if (selections.size() > 4 || selections.size() < 2)
    return false;
  std::vector<Gui::SelectionSingleton::SelObj>::iterator it;
  std::vector<VectorAdapter> adapters;
  TopoDS_Vertex lastVertex;
  for (it = selections.begin(); it != selections.end(); ++it)
  {
    Part::Feature *feature = dynamic_cast<Part::Feature *>((*it).pObject);
    if (!feature)
      break;
    TopoDS_Shape shape = feature->Shape.getValue();
    if (strlen((*it).SubName) > 0)
      shape = feature->Shape.getShape().getSubShape((*it).SubName);
    if (shape.IsNull())
      break;
    
    if (shape.ShapeType() == TopAbs_VERTEX)
    {
      TopoDS_Vertex currentVertex = TopoDS::Vertex(shape);
      if (!lastVertex.IsNull())
      {
	//need something here for 0 length vector.
	//create a point half way between to vertices.
	adapters.push_back(VectorAdapter(currentVertex, lastVertex));
	lastVertex = TopoDS_Vertex();
      }
      else
      {
	lastVertex = currentVertex;
      }
      continue;
    }
    //vertices have to be selected in succession. so if we make it here clear the last vertex.
    lastVertex = TopoDS_Vertex();
    
    gp_Vec pickPoint(it->x, it->y, it->z);
    //can't use selections without a pick point.
    if (pickPoint.IsEqual(gp_Vec(0.0, 0.0, 0.0), Precision::Confusion(), Precision::Angular()))
    {
      Base::Console().Message("Can't use selections without a pick point.\n");
      continue;
    }
    
    if (shape.ShapeType() == TopAbs_EDGE)
    {
      TopoDS_Edge edge = TopoDS::Edge(shape);
      // make edge orientation so that end of edge closest to pick is head of vector.
      gp_Vec firstPoint = PartGui::convert(TopExp::FirstVertex(edge, Standard_True));
      gp_Vec lastPoint = PartGui::convert(TopExp::LastVertex(edge, Standard_True));
      double firstDistance = (firstPoint - pickPoint).Magnitude();
      double lastDistance = (lastPoint - pickPoint).Magnitude();
      if (lastDistance > firstDistance)
      {
	if (edge.Orientation() == TopAbs_FORWARD)
	  edge.Orientation(TopAbs_REVERSED);
	else
	  edge.Orientation(TopAbs_FORWARD);
      }
      adapters.push_back(VectorAdapter(edge, pickPoint));
      continue;
    }
    
    if (shape.ShapeType() == TopAbs_FACE)
    {
      TopoDS_Face face = TopoDS::Face(shape);
      adapters.push_back(VectorAdapter(face, pickPoint));
      continue;
    }
  }
  
  if (adapters.size() != 2)
    return false;
  if (!adapters.front().isValid() || !adapters.back().isValid())
    return false;
  
  vector1Out = adapters.front();
  vector2Out = adapters.back();
  
  //making sure pick points are not equal
  if ((vector1Out.getPickPoint() - vector2Out.getPickPoint()).Magnitude() < std::numeric_limits<float>::epsilon())
  {
    Base::Console().Message("pick points are equal\n");
    return false;
  }
  
  return true;
}

void PartGui::goDimensionAngularNoTask(const VectorAdapter &vector1Adapter, const VectorAdapter &vector2Adapter)
{
  gp_Vec vector1 = vector1Adapter;
  gp_Vec vector2 = vector2Adapter;
  double angle = vector1.Angle(vector2);
  
  std::ostringstream stream;
  stream << std::setprecision(std::numeric_limits<double>::digits10 + 1) << std::fixed << std::endl <<
    "angle in radians is: " << angle << std::endl <<
    "angle in degrees is: " << 180 * angle / M_PI << std::endl;
  if (angle < M_PI / 2.0)
    stream << std::setprecision(std::numeric_limits<double>::digits10 + 1) << 
    "complement in radians is: " << M_PI / 2.0 - angle << std::endl <<
    "complement in degrees is: " << 90 - 180 * angle / M_PI << std::endl;
  //I don't think we get anything over 180, but just in case.
  if (angle > M_PI / 2.0 && angle < M_PI)
    stream << std::setprecision(std::numeric_limits<double>::digits10 + 1) << 
    "supplement in radians is: " << M_PI - angle << std::endl <<
    "supplement in degrees is: " << 180 - 180 * angle / M_PI << std::endl;
  Base::Console().Message(stream.str().c_str());
  
  SbMatrix dimSys;
  double radius;
  double displayAngle;//have to fake the angle in the 3d.
  
  if (vector1.IsParallel(vector2, Precision::Angular()))
  {
    //take first point project it onto second vector.
    Handle_Geom_Curve heapLine2 = new Geom_Line(vector2Adapter);
    gp_Pnt tempPoint(vector1Adapter.getPickPoint().XYZ());
    
    GeomAPI_ProjectPointOnCurve projection(tempPoint, heapLine2);
    if (projection.NbPoints() < 1)
    {
      Base::Console().Message("parallel vectors: couldn't project onto line\n");
      return;
    }
    gp_Vec newPoint2;
    newPoint2.SetXYZ(projection.Point(1).XYZ());
    
    //if points are colinear, projection doesn't work and returns the same point.
    //In this case we just use the original point.
    if ((newPoint2 - vector1Adapter.getPickPoint()).Magnitude() < Precision::Confusion())
      newPoint2 = vector2Adapter.getPickPoint();
    
    //now get midpoint between for dim origin.
    gp_Vec point1 = vector1Adapter.getPickPoint();
    gp_Vec midPointProjection = newPoint2 - point1;
    double distance = midPointProjection.Magnitude();
    midPointProjection.Normalize();
    midPointProjection *= distance / 2.0;
    
    gp_Vec origin = point1 + midPointProjection;
    
    //yaxis should be the same as vector1, but doing this to eliminate any potential slop from
    //using precision::angular. If lines are colinear and we have no plane, we can't establish zAxis from crossing.
    //we just the absolute axis.
    gp_Vec xAxis = (point1 - origin).Normalized();
    gp_Vec zAxis;
    if (xAxis.IsParallel(vector1, Precision::Angular()))
    {
      if (!xAxis.IsParallel(gp_Vec(0.0, 0.0, 1.0), Precision::Angular()))
	zAxis = gp_Vec(0.0, 0.0, 1.0);
      else
	zAxis = gp_Vec(0.0, 1.0, 0.0);
    }
    else
      zAxis = xAxis.Crossed(vector1).Normalized();
    gp_Vec yAxis = zAxis.Crossed(xAxis).Normalized();
    zAxis = xAxis.Crossed(yAxis).Normalized();
    
    dimSys = SbMatrix
    (
      xAxis.X(), yAxis.X(), zAxis.X(), origin.X(),
      xAxis.Y(), yAxis.Y(), zAxis.Y(), origin.Y(),
      xAxis.Z(), yAxis.Z(), zAxis.Z(), origin.Z(),
      0.0, 0.0, 0.0, 1.0
    );
    dimSys = dimSys.transpose();
    
    radius = midPointProjection.Magnitude();
    displayAngle = M_PI;
  }
  else
  {
    Handle_Geom_Curve heapLine1 = new Geom_Line(vector1Adapter);
    Handle_Geom_Curve heapLine2 = new Geom_Line(vector2Adapter);
    
    GeomAPI_ExtremaCurveCurve extrema(heapLine1, heapLine2);
    
    if (extrema.NbExtrema() < 1)
    {
      Base::Console().Message("couldn't get extrema\n");
      return;
    }
    
    gp_Pnt extremaPoint1, extremaPoint2, dimensionOriginPoint;
    extrema.Points(1, extremaPoint1, extremaPoint2);
    if (extremaPoint1.Distance(extremaPoint2) < Precision::Confusion())
      dimensionOriginPoint = extremaPoint1;
    else
    {
      //find half way point in between extrema points for dimension origin.
      gp_Vec vec1(extremaPoint1.XYZ());
      gp_Vec vec2(extremaPoint2.XYZ());
      gp_Vec connection(vec2-vec1);
      Standard_Real distance = connection.Magnitude();
      connection.Normalize();
      connection *= (distance / 2.0);
      dimensionOriginPoint.SetXYZ((vec1 + connection).XYZ());
    }
    
    gp_Vec thirdPoint(vector2Adapter.getPickPoint());
    gp_Vec originVector(dimensionOriginPoint.XYZ());
    gp_Vec extrema2Vector(extremaPoint2.XYZ());
    radius = (vector1Adapter.getPickPoint() - originVector).Magnitude();
    double legOne = (extrema2Vector - originVector).Magnitude();
    displayAngle = angle;
    if (legOne > Precision::Confusion())
    {
      double legTwo = sqrt(pow(radius, 2) - pow(legOne, 2));
      gp_Vec projectionVector(vector2);
      projectionVector.Normalize();
      projectionVector *= legTwo;
      thirdPoint = extrema2Vector + projectionVector;
      gp_Vec hyp(thirdPoint - originVector);
      hyp.Normalize();
      gp_Vec otherSide(vector1Adapter.getPickPoint() - originVector);
      otherSide.Normalize();
      displayAngle = hyp.Angle(otherSide);
    }
    
    gp_Vec xAxis = (vector1Adapter.getPickPoint() - originVector).Normalized();
    gp_Vec fakeYAxis = (thirdPoint - originVector).Normalized();
    gp_Vec zAxis = (xAxis.Crossed(fakeYAxis)).Normalized();
    gp_Vec yAxis = zAxis.Crossed(xAxis).Normalized();
    
    dimSys = SbMatrix
    (
      xAxis.X(), yAxis.X(), zAxis.X(), dimensionOriginPoint.X(),
      xAxis.Y(), yAxis.Y(), zAxis.Y(), dimensionOriginPoint.Y(),
      xAxis.Z(), yAxis.Z(), zAxis.Z(), dimensionOriginPoint.Z(),
      0.0, 0.0, 0.0, 1.0
    );
    
    dimSys = dimSys.transpose();
  }
  
  DimensionAngular *dimension = new DimensionAngular();
  dimension->matrix.setValue(dimSys);
  dimension->radius.setValue(radius);
  dimension->angle.setValue(static_cast<float>(displayAngle));
  dimension->text.setValue((Base::Quantity(180 * angle / M_PI, Base::Unit::Angle)).getUserString().toUtf8().constData());
  dimension->dColor.setValue(SbColor(0.0, 0.0, 1.0));
  
  Gui::View3DInventorViewer *viewer = getViewer();
  if (!viewer)
    return;
  
  viewer->addDimension3d(dimension);
}

SO_KIT_SOURCE(PartGui::DimensionAngular);

void PartGui::DimensionAngular::initClass()
{
    SO_KIT_INIT_CLASS(DimensionAngular, SoSeparatorKit, "SeparatorKit");
}

PartGui::DimensionAngular::DimensionAngular()
{
    SO_KIT_CONSTRUCTOR(PartGui::DimensionAngular);

    SO_KIT_ADD_CATALOG_ENTRY(transformation, SoMatrixTransform, true, topSeparator,"" , true);
    SO_KIT_ADD_CATALOG_ENTRY(annotate, SoAnnotation, true, topSeparator,"" , true);
    SO_KIT_ADD_CATALOG_ENTRY(arrow1, SoShapeKit, true, topSeparator,"" ,true);
    SO_KIT_ADD_CATALOG_ENTRY(arrow2, SoShapeKit, true, topSeparator,"" ,true);
    SO_KIT_ADD_CATALOG_ENTRY(arcSep, SoSeparator, true, annotate,"" ,true);
    SO_KIT_ADD_CATALOG_ENTRY(textSep, SoSeparator, true, annotate,"" ,true);

    SO_KIT_INIT_INSTANCE();

    SO_NODE_ADD_FIELD(radius, (10.0));
    SO_NODE_ADD_FIELD(angle, (1.0));
    SO_NODE_ADD_FIELD(text, ("test"));//dimension text
    SO_NODE_ADD_FIELD(dColor, (1.0, 0.0, 0.0));//dimension color.
    SO_NODE_ADD_FIELD(matrix, (1.0, 0.0, 0.0, 0.0,
                               0.0, 1.0, 0.0, 0.0,
			       0.0, 0.0, 1.0, 0.0,
			       0.0, 0.0, 0.0, 1.0));
    
    setupDimension();
}

PartGui::DimensionAngular::~DimensionAngular()
{

}

SbBool PartGui::DimensionAngular::affectsState() const
{
    return false;
}


void PartGui::DimensionAngular::setupDimension()
{
  //transformation
  SoMatrixTransform *trans = static_cast<SoMatrixTransform *>(getPart("transformation", true));
  trans->matrix.connectFrom(&matrix);

  //color
  SoMaterial *material = new SoMaterial;
  material->diffuseColor.connectFrom(&dColor);

  //dimension arrows.
  SoCone *cone = new SoCone();
  cone->bottomRadius.setValue(0.25);
  cone->height.setValue(0.5);

  setPart("arrow1.shape", cone);
  set("arrow1.localTransform", "rotation 0.0 0.0 1.0 3.1415927");
  set("arrow1.localTransform", "translation 0.0 0.25 0.0"); //half cone height.
  setPart("arrow2.shape", cone);
  set("arrow2.transform", "rotation 0.0 0.0 1.0 0.0");
  set("arrow2.localTransform", "translation 0.0 -0.25 0.0"); //half cone height.
  
  //I was getting errors if I didn't manually allocate for these transforms. Not sure why.
  SoTransform *arrow1Transform = new SoTransform();
  SoComposeVec3f *arrow1Compose = new SoComposeVec3f();
  arrow1Compose->x.connectFrom(&radius);
  arrow1Compose->y.setValue(0.0);
  arrow1Compose->y.setValue(0.0);
  arrow1Transform->translation.connectFrom(&arrow1Compose->vector);
  setPart("arrow1.transform", arrow1Transform);
  
  SoComposeRotation *arrow2Rotation = new SoComposeRotation();
  arrow2Rotation->angle.connectFrom(&angle);
  arrow2Rotation->axis.setValue(0.0, 0.0, 1.0);
  SoTransform *arrow2Transform = new SoTransform();
  arrow2Transform->rotation.connectFrom(&arrow2Rotation->rotation);
  SoCalculator *arrow2LocationCalc = new SoCalculator();
  arrow2LocationCalc->a.connectFrom(&angle);
  arrow2LocationCalc->b.connectFrom(&radius);
  arrow2LocationCalc->expression.set1Value(0, "oa = cos(a) * b"); //xLocation
  arrow2LocationCalc->expression.set1Value(1, "ob = sin(a) * b"); //yLocation
  SoComposeVec3f *arrow2Compose = new SoComposeVec3f();
  arrow2Compose->x.connectFrom(&arrow2LocationCalc->oa);
  arrow2Compose->y.connectFrom(&arrow2LocationCalc->ob);
  arrow2Compose->z.setValue(0.0f);
  arrow2Transform->translation.connectFrom(&arrow2Compose->vector);
  setPart("arrow2.transform", arrow2Transform);

  setPart("arrow1.material", material);
  setPart("arrow2.material", material);
  
  ArcEngine *arcEngine = new ArcEngine();
  arcEngine->angle.connectFrom(&angle);
  arcEngine->radius.connectFrom(&radius);
  arcEngine->deviation.setValue(0.1f);
  
  SoCoordinate3 *coordinates = new SoCoordinate3();
  coordinates->point.connectFrom(&arcEngine->points);
  
  SoLineSet *lineSet = new SoLineSet();
  lineSet->vertexProperty.setValue(coordinates);
  lineSet->numVertices.connectFrom(&arcEngine->pointCount);
  lineSet->startIndex.setValue(0);
  
  SoSeparator *arcSep = static_cast<SoSeparator *>(getPart("arcSep", true));
  if (!arcSep)
    return;
  arcSep->addChild(material);
  arcSep->addChild(lineSet);

  //text
  SoSeparator *textSep = static_cast<SoSeparator *>(getPart("textSep", true));
  if (!textSep)
      return;

  textSep->addChild(material);

  SoCalculator *textVecCalc = new SoCalculator();
  textVecCalc->a.connectFrom(&angle);
  textVecCalc->b.connectFrom(&radius);
  textVecCalc->expression.set1Value(0, "oa = a / 2.0");
  textVecCalc->expression.set1Value(1, "ob = cos(oa) * b"); //x
  textVecCalc->expression.set1Value(2, "oc = sin(oa) * b"); //y
  
  SoComposeVec3f *textLocation = new SoComposeVec3f();
  textLocation->x.connectFrom(&textVecCalc->ob);
  textLocation->y.connectFrom(&textVecCalc->oc);
  textLocation->z.setValue(0.0);
  

  SoTransform *textTransform =  new SoTransform();
  textTransform->translation.connectFrom(&textLocation->vector);
  textSep->addChild(textTransform);
  
  SoFont *fontNode = new SoFont();
  fontNode->name.setValue("defaultFont");
  fontNode->size.setValue(30);
  textSep->addChild(fontNode);

  SoText2 *textNode = new SoText2();
  textNode->justification = SoText2::CENTER;
  textNode->string.connectFrom(&text);
  textSep->addChild(textNode);

  //this prevents the 2d text from screwing up the bounding box for a viewall
  SoResetTransform *rTrans = new SoResetTransform;
  rTrans->whatToReset = SoResetTransform::BBOX;
  textSep->addChild(rTrans);
}

SO_ENGINE_SOURCE(PartGui::ArcEngine);

PartGui::ArcEngine::ArcEngine()
{
  SO_ENGINE_CONSTRUCTOR(ArcEngine);

  SO_ENGINE_ADD_INPUT(radius, (10.0));
  SO_ENGINE_ADD_INPUT(angle, (1.0));
  SO_ENGINE_ADD_INPUT(deviation, (0.25));

  SO_ENGINE_ADD_OUTPUT(points, SoMFVec3f);
  SO_ENGINE_ADD_OUTPUT(pointCount, SoSFInt32);
}

void PartGui::ArcEngine::initClass()
{
  SO_ENGINE_INIT_CLASS(ArcEngine, SoEngine, "Engine");
}

void PartGui::ArcEngine::evaluate()
{
  if (radius.getValue() < std::numeric_limits<float>::epsilon() ||
    angle.getValue() < std::numeric_limits<float>::epsilon() ||
    deviation.getValue() < std::numeric_limits<float>::epsilon())
  {
    defaultValues();
    return;
  }
  
  float deviationAngle(acos((radius.getValue() - deviation.getValue()) / radius.getValue()));
  std::vector<SbVec3f> tempPoints;
  int segmentCount;
  if (deviationAngle >= angle.getValue())
    segmentCount = 1;
  else
  {
    segmentCount = static_cast<int>(angle.getValue() / deviationAngle) + 1;
    if (segmentCount < 2)
    {
      defaultValues();
      return;
    }
  }
  float angleIncrement = angle.getValue() / static_cast<float>(segmentCount);
  for (int index = 0; index < segmentCount + 1; ++index)
  {
    SbVec3f currentNormal(1.0, 0.0, 0.0);
    float currentAngle = index * angleIncrement;
    SbRotation rotation(SbVec3f(0.0, 0.0, 1.0), currentAngle);
    rotation.multVec(currentNormal, currentNormal);
    tempPoints.push_back(currentNormal * radius.getValue());
  }
  int tempCount = tempPoints.size(); //for macro.
  SO_ENGINE_OUTPUT(points, SoMFVec3f, setNum(tempCount));
  SO_ENGINE_OUTPUT(pointCount, SoSFInt32, setValue(tempCount));
  std::vector<SbVec3f>::const_iterator it;
  for (it = tempPoints.begin(); it != tempPoints.end(); ++it)
  {
    int currentIndex = it-tempPoints.begin(); //for macro.
    SbVec3f temp(*it); //for macro
    SO_ENGINE_OUTPUT(points, SoMFVec3f, set1Value(currentIndex, temp));
  }

}

void PartGui::ArcEngine::defaultValues()
{
  //just some non-failing info.
  SO_ENGINE_OUTPUT(points, SoMFVec3f, setNum(2));
  SbVec3f point1(10.0, 0.0, 0.0);
  SO_ENGINE_OUTPUT(points, SoMFVec3f, set1Value(0, point1));
  SbVec3f point2(7.07f, 7.07f, 0.0);
  SO_ENGINE_OUTPUT(points, SoMFVec3f, set1Value(1, point2));
  SO_ENGINE_OUTPUT(pointCount, SoSFInt32, setValue(2));
}

PartGui::SteppedSelection::SteppedSelection(const uint& buttonCountIn, QWidget* parent):
  QWidget(parent)
{
  if (buttonCountIn < 1)
    return;
  
  QVBoxLayout *mainLayout = new QVBoxLayout();
  this->setLayout(mainLayout);
  
  QButtonGroup *buttonGroup = new QButtonGroup();
  buttonGroup->setExclusive(true);
  
  for (uint index = 0; index < buttonCountIn; ++index)
  {
    ButtonIconPairType tempPair;
    
    std::ostringstream stream;
    stream << "Selection " << ((index < 10) ? "0" : "") <<  index + 1;
    QString buttonText = QObject::tr(stream.str().c_str());
    QPushButton *button = new QPushButton(buttonText, this);
    button->setCheckable(true);
    button->setEnabled(false);
    buttonGroup->addButton(button);
    connect(button, SIGNAL(toggled(bool)), this, SLOT(selectionSlot(bool)));
    
    QLabel *label = new QLabel;
    
    tempPair.first = button;
    tempPair.second = label;
    buttons.push_back(tempPair);
    
    QHBoxLayout *layout = new QHBoxLayout();
    mainLayout->addLayout(layout);
    layout->addWidget(button);
    layout->addSpacing(10);
    layout->addWidget(label);
    layout->addStretch();
  }
  mainLayout->addStretch();
  
  buildPixmaps(); //uses button size
}

PartGui::SteppedSelection::~SteppedSelection()
{
  if(stepActive)
  {
    delete stepActive;
    stepActive = 0;
  }
  if (stepDone)
  {
    delete stepDone;
    stepDone = 0;
  }
}

void PartGui::SteppedSelection::buildPixmaps()
{
  assert(buttons.size() > 0);
  int iconHeight(buttons.at(0).first->height()-6);
  stepActive = new QPixmap(Gui::BitmapFactory().pixmap("Part_Measure_Step_Active").scaled
    (iconHeight, iconHeight, Qt::KeepAspectRatio));
  stepDone = new QPixmap(Gui::BitmapFactory().pixmap("Part_Measure_Step_Done").scaled
    (iconHeight, iconHeight, Qt::KeepAspectRatio));
}

void PartGui::SteppedSelection::selectionSlot(bool checked)
{
  QPushButton *sender = qobject_cast<QPushButton*>(QObject::sender());
  assert(sender != 0);
  std::vector<ButtonIconPairType>::iterator it;
  for (it = buttons.begin(); it != buttons.end(); ++it)
    if (it->first == sender)
      break;
  assert(it != buttons.end());
  
  if (checked)
    it->second->setPixmap(*stepActive);
  else
    it->second->setPixmap(QPixmap());
}

QPushButton* PartGui::SteppedSelection::getButton(const uint& index)
{
  return buttons.at(index).first;
}

void PartGui::SteppedSelection::setIconDone(const uint& index)
{
  buttons.at(index).second->setPixmap(*stepDone);
}

PartGui::DimensionControl::DimensionControl(QWidget* parent): QWidget(parent)
{
  QVBoxLayout *commandLayout = new QVBoxLayout();
  this->setLayout(commandLayout);
  
  resetButton = new QPushButton(Gui::BitmapFactory().pixmap("Part_Measure_Linear"),
				QObject::tr("Reset Dialog"), this);
  commandLayout->addWidget(resetButton);
  
  QPushButton *toggle3dButton = new QPushButton(Gui::BitmapFactory().pixmap("Part_Measure_Toggle_3d"),
						QObject::tr("Toggle 3d"), this);
  QObject::connect(toggle3dButton, SIGNAL(clicked(bool)), this, SLOT(toggle3dSlot(bool)));
  commandLayout->addWidget(toggle3dButton);
  
  QPushButton *toggleDeltaButton = new QPushButton(Gui::BitmapFactory().pixmap("Part_Measure_Toggle_Delta"),
						QObject::tr("Toggle Delta"), this);
  QObject::connect(toggleDeltaButton, SIGNAL(clicked(bool)), this, SLOT(toggleDeltaSlot(bool)));
  commandLayout->addWidget(toggleDeltaButton);
  
  QPushButton *clearAllButton = new QPushButton(Gui::BitmapFactory().pixmap("Part_Measure_Clear_All"),
						QObject::tr("Clear All"), this);
  QObject::connect(clearAllButton, SIGNAL(clicked(bool)), this, SLOT(clearAllSlot(bool)));
  commandLayout->addWidget(clearAllButton);
}

void PartGui::DimensionControl::toggle3dSlot(bool)
{
  PartGui::toggle3d();
}

void PartGui::DimensionControl::toggleDeltaSlot(bool)
{
  PartGui::toggleDelta();
}

void PartGui::DimensionControl::clearAllSlot(bool)
{
  PartGui::eraseAllDimensions();
}

PartGui::TaskMeasureAngular::TaskMeasureAngular(): selections1(), selections2(), buttonSelectedIndex(0)
{
  setUpGui();
}

PartGui::TaskMeasureAngular::~TaskMeasureAngular()
{
  Gui::Selection().clearSelection();
}

void PartGui::TaskMeasureAngular::onSelectionChanged(const Gui::SelectionChanges& msg)
{
  TopoDS_Shape shape;
  if (!getShapeFromStrings(shape, std::string(msg.pDocName), std::string(msg.pObjectName), std::string(msg.pSubName)))
    return;
  DimSelections::DimSelection newSelection;
  newSelection.documentName = msg.pDocName;
  newSelection.objectName = msg.pObjectName;
  newSelection.subObjectName = msg.pSubName;
  newSelection.x = msg.x;
  newSelection.y = msg.y;
  newSelection.z = msg.z;
  gp_Vec pickPoint(msg.x, msg.y, msg.z);
  if (buttonSelectedIndex == 0)
  {
    if (msg.Type == Gui::SelectionChanges::AddSelection)
    {
      if (shape.ShapeType() == TopAbs_VERTEX)
      {
	//if we have previous selection it should be only one vertex.
	if (selections1.selections.size() > 1)
	  selections1.selections.clear();
	else if(selections1.selections.size() == 1)
	{
	  //make sure it is a vertex.
	  if (selections1.selections.at(0).shapeType != DimSelections::Vertex)
	    selections1.selections.clear();
	}
	
	newSelection.shapeType = DimSelections::Vertex;
	selections1.selections.push_back(newSelection);
	if (selections1.selections.size() == 1)
	  return;
	//here we should have 2 vertices, but will check to make sure.
	assert(selections1.selections.size() == 2);
	assert(selections1.selections.at(0).shapeType == DimSelections::Vertex);
	assert(selections1.selections.at(1).shapeType == DimSelections::Vertex);
	
	QTimer::singleShot(0, this, SLOT(selectionClearDelayedSlot()));
	stepped->getButton(1)->setEnabled(true);
	stepped->getButton(1)->setChecked(true);
	return;
      }
      
      //here there should only be one in the selections container. so just clear it.
      selections1.selections.clear();
      
      if (shape.ShapeType() == TopAbs_EDGE)
      {
	newSelection.shapeType = DimSelections::Edge;
	selections1.selections. push_back(newSelection);
      }
      
      if (shape.ShapeType() == TopAbs_FACE)
      {
	newSelection.shapeType = DimSelections::Face;
	selections1.selections.push_back(newSelection);
      }
      
      QTimer::singleShot(0, this, SLOT(selectionClearDelayedSlot()));
      stepped->getButton(1)->setEnabled(true);
      stepped->getButton(1)->setChecked(true);
      return;
    }
  }
  if (buttonSelectedIndex == 1)
  {
    if (msg.Type == Gui::SelectionChanges::AddSelection)
    {
      if (shape.ShapeType() == TopAbs_VERTEX)
      {
	//if we have previous selection it should be only one vertex.
	if (selections2.selections.size() > 1)
	  selections2.selections.clear();
	else if(selections2.selections.size() == 1)
	{
	  //make sure it is a vertex.
	  if (selections2.selections.at(0).shapeType != DimSelections::Vertex)
	    selections2.selections.clear();
	}
	
	newSelection.shapeType = DimSelections::Vertex;
	selections2.selections.push_back(newSelection);
	if (selections2.selections.size() == 1)
	  return;
	//here we should have 2 vertices, but will check to make sure.
	assert(selections2.selections.size() == 2);
	assert(selections2.selections.at(0).shapeType == DimSelections::Vertex);
	assert(selections2.selections.at(1).shapeType == DimSelections::Vertex);
	
	buildDimension();
	clearSelection();
	QTimer::singleShot(0, this, SLOT(selectionClearDelayedSlot()));
	stepped->getButton(0)->setChecked(true);
	stepped->getButton(1)->setEnabled(false);
	return;
      }
      //vertices have to be selected in succession. if we get here,clear temp selection.
      selections2.selections.clear();
      
      if (shape.ShapeType() == TopAbs_EDGE)
      {
	newSelection.shapeType = DimSelections::Edge;
	selections2.selections. push_back(newSelection);
      }
      
      if (shape.ShapeType() == TopAbs_FACE)
      {
	newSelection.shapeType = DimSelections::Face;
	selections2.selections.push_back(newSelection);
      }
      
      buildDimension();
      clearSelection();
      QTimer::singleShot(0, this, SLOT(selectionClearDelayedSlot()));
      stepped->getButton(0)->setChecked(true);
      stepped->getButton(1)->setEnabled(false);
      return;
    }
  }
}

void PartGui::TaskMeasureAngular::selectionClearDelayedSlot()
{
  //hack.
  //clearing selections are not working as I hoped. Apparently the observer callback gets called
  //before the actual selection takes place. Resulting in selections being left. this addresses this
  //by being called from the event loop.
  this->blockConnection(true);
  Gui::Selection().clearSelection();
  this->blockConnection(false);
}

PartGui::VectorAdapter PartGui::TaskMeasureAngular::buildAdapter(const PartGui::DimSelections& selection) const
{
  assert(selection.selections.size() > 0 && selection.selections.size() < 3);
  if (selection.selections.size() == 1)
  {
    DimSelections::DimSelection current = selection.selections.at(0);
    if (current.shapeType == DimSelections::Edge)
    {
      TopoDS_Shape edgeShape;
      if (!getShapeFromStrings(edgeShape, current.documentName, current.objectName, current.subObjectName))
	return VectorAdapter();
      TopoDS_Edge edge = TopoDS::Edge(edgeShape);
      // make edge orientation so that end of edge closest to pick is head of vector.
      TopoDS_Vertex firstVertex = TopExp::FirstVertex(edge, Standard_True);
      TopoDS_Vertex lastVertex = TopExp::LastVertex(edge, Standard_True);
      if (firstVertex.IsNull() || lastVertex.IsNull())
        return VectorAdapter();
      gp_Vec firstPoint = PartGui::convert(firstVertex);
      gp_Vec lastPoint = PartGui::convert(lastVertex);
      gp_Vec pickPoint(current.x, current.y, current.z);
      double firstDistance = (firstPoint - pickPoint).Magnitude();
      double lastDistance = (lastPoint - pickPoint).Magnitude();
      if (lastDistance > firstDistance)
      {
	if (edge.Orientation() == TopAbs_FORWARD)
	  edge.Orientation(TopAbs_REVERSED);
	else
	  edge.Orientation(TopAbs_FORWARD);
      }
      return VectorAdapter(edge, pickPoint);
    }
    if (current.shapeType == DimSelections::Face)
    {
      TopoDS_Shape faceShape;
      if (!getShapeFromStrings(faceShape, current.documentName, current.objectName, current.subObjectName))
	return VectorAdapter();
      
      TopoDS_Face face = TopoDS::Face(faceShape);
      gp_Vec pickPoint(current.x, current.y, current.z);
      return VectorAdapter(face, pickPoint);
    }
  }
  //selection size == 2.
  DimSelections::DimSelection current1 = selection.selections.at(0);
  DimSelections::DimSelection current2 = selection.selections.at(1);
  assert(current1.shapeType == DimSelections::Vertex);
  assert(current2.shapeType == DimSelections::Vertex);
  TopoDS_Shape vertexShape1, vertexShape2;
  if(!getShapeFromStrings(vertexShape1, current1.documentName, current1.objectName, current1.subObjectName))
    return VectorAdapter();
  if(!getShapeFromStrings(vertexShape2, current2.documentName, current2.objectName, current2.subObjectName))
    return VectorAdapter();

  TopoDS_Vertex vertex1 = TopoDS::Vertex(vertexShape1);
  TopoDS_Vertex vertex2 = TopoDS::Vertex(vertexShape2);

  //build a temp adapter to make sure it is valid.
  return VectorAdapter(PartGui::convert(vertex2), PartGui::convert(vertex1));
}

void PartGui::TaskMeasureAngular::buildDimension()
{
  //build adapters.
  VectorAdapter adapt1 = buildAdapter(selections1);
  VectorAdapter adapt2 = buildAdapter(selections2);
  
  if (!adapt1.isValid() || !adapt2.isValid())
  {
    Base::Console().Message("\ncouldn't build adapter\n\n");
    return;
  }
  goDimensionAngularNoTask(adapt1, adapt2);
}

void PartGui::TaskMeasureAngular::clearSelection()
{
  selections1.selections.clear();
  selections2.selections.clear();
}

void PartGui::TaskMeasureAngular::setUpGui()
{
  QPixmap mainIcon = Gui::BitmapFactory().pixmap("Part_Measure_Angular");
  
  Gui::TaskView::TaskBox* selectionTaskBox = new Gui::TaskView::TaskBox
    (mainIcon, QObject::tr("Selections"), false, 0);
  QVBoxLayout *selectionLayout = new QVBoxLayout();
  stepped = new SteppedSelection(2, selectionTaskBox);
  selectionLayout->addWidget(stepped);
  selectionTaskBox->groupLayout()->addLayout(selectionLayout);
  
  Gui::TaskView::TaskBox* controlTaskBox = new Gui::TaskView::TaskBox
    (mainIcon, QObject::tr("Control"), false, 0);
  QVBoxLayout *controlLayout = new QVBoxLayout();
  
  DimensionControl *control = new DimensionControl(controlTaskBox);
  controlLayout->addWidget(control);
  controlTaskBox->groupLayout()->addLayout(controlLayout);
  QObject::connect(control->resetButton, SIGNAL(clicked(bool)), this, SLOT(resetDialogSlot(bool)));
  
  this->setButtonPosition(TaskDialog::South);
  Content.push_back(selectionTaskBox);
  Content.push_back(controlTaskBox);
  
  stepped->getButton(0)->setChecked(true);//before wired up.
  stepped->getButton(0)->setEnabled(true);
  QObject::connect(stepped->getButton(0), SIGNAL(toggled(bool)), this, SLOT(selection1Slot(bool)));
  QObject::connect(stepped->getButton(1), SIGNAL(toggled(bool)), this, SLOT(selection2Slot(bool)));
}

void PartGui::TaskMeasureAngular::selection1Slot(bool checked)
{
  if (checked)
  {
    buttonSelectedIndex = 0;
    this->blockConnection(true);
    Gui::Selection().clearSelection();
    std::vector<DimSelections::DimSelection>::const_iterator it;
    for (it = selections1.selections.begin(); it != selections1.selections.end(); ++it)
      Gui::Selection().addSelection(it->documentName.c_str(), it->objectName.c_str(), it->subObjectName.c_str());
    this->blockConnection(false);
  }
  else
  {
    if (selections1.selections.size() > 0)
      stepped->setIconDone(0);
  }
}

void PartGui::TaskMeasureAngular::selection2Slot(bool checked)
{
  if (checked)
    buttonSelectedIndex = 1;
  this->blockConnection(true);
  Gui::Selection().clearSelection();
  std::vector<DimSelections::DimSelection>::const_iterator it;
  for (it = selections2.selections.begin(); it != selections2.selections.end(); ++it)
    Gui::Selection().addSelection(it->documentName.c_str(), it->objectName.c_str(), it->subObjectName.c_str());
  this->blockConnection(false);
}

void PartGui::TaskMeasureAngular::resetDialogSlot(bool)
{
  clearSelection();
  this->blockConnection(true);
  Gui::Selection().clearSelection();
  stepped->getButton(0)->setChecked(true);
  stepped->getButton(1)->setEnabled(false);
  this->blockConnection(false);
}

void PartGui::TaskMeasureAngular::toggle3dSlot(bool)
{
  PartGui::toggle3d();
}

void PartGui::TaskMeasureAngular::toggleDeltaSlot(bool)
{
  PartGui::toggleDelta();
}

void PartGui::TaskMeasureAngular::clearAllSlot(bool)
{
  PartGui::eraseAllDimensions();
}

#include "moc_TaskDimension.cpp"
