/**
    bespoke synth, a software modular synthesizer

**/
//  CanvasElement.cpp
//  Bespoke
//  Created by Ryan Challinor on 1/4/15.

#include "CanvasElement.h"
#include "Sample.h"
#include "SynthGlobals.h"
#include "Slider.h"
#include "CanvasControls.h"
#include "TextEntry.h"
#include "PatchCableSource.h"
#include "ModularSynth.h"
#include "EventCanvas.h"
#include "SampleCanvas.h"

CanvasElement::CanvasElement(Canvas* canvas, int col, int row, float offset, float length)
: mCanvas(canvas)
, mOffset(offset)
, mLength(length)
, mCol(col)
, mRow(row)
, mHighlighted(false)
{
}

void CanvasElement::Draw(ofVec2f offset)
{
   if (offset.x == 0 && offset.y == 0)
   {
      DrawElement(K(clamp), !K(wrapped), offset);
      if (mCanvas->ShouldWrap() && GetEnd() > 1)
         DrawElement(K(clamp), K(wrapped), offset);
   }
   else
   {
      ofPushStyle();
      ofSetLineWidth(3.0f);
      ofNoFill();
      ofSetColor(255, 255, 255, ofMap(sin(gTime / 500 * PI * 2), -1, 1, 40, 120));
      ofRect(GetRectAtDestination(K(clamp), !K(wrapped), offset), 0);
      if (mCanvas->ShouldWrap() && GetEnd() > 1)
         ofRect(GetRectAtDestination(K(clamp), K(wrapped), offset), 0);
      ofPopStyle();

      DrawElement(!K(clamp), !K(wrapped), offset);
   }
}

void CanvasElement::DrawElement(bool clamp, bool wrapped, ofVec2f offset)
{
   ofRectangle rect = GetRect(clamp, wrapped, offset);
   ofRectangle fullRect = GetRect(false, wrapped, offset);

   if (rect.width < 0)
   {
      ofPushStyle();
      ofFill();
      ofSetColor(255, 0, 0);
      ofRect(rect.x + rect.width, rect.y, -rect.width, rect.height, 0);
      ofPopStyle();
   }

   ofPushStyle();
   DrawContents(clamp, wrapped, offset);
   ofPopStyle();

   if (mHighlighted)
   {
      ofPushStyle();
      ofNoFill();
      ofSetColor(255, 200, 0);
      ofSetLineWidth(.75f);

      ofLine(rect.x, rect.y, rect.x + rect.width, rect.y);
      ofLine(rect.x, rect.y + rect.height, rect.x + rect.width, rect.y + rect.height);

      if (fullRect.x >= 0)
      {
         ofPushStyle();
         if (mCanvas->GetHighlightEnd() == Canvas::kHighlightEnd_Start && IsResizable())
         {
            ofSetLineWidth(1.5f);
            ofSetColor(255, 100, 100);
         }
         ofLine(rect.x, rect.y, rect.x, rect.y + rect.height);
         ofPopStyle();
      }
      if (fullRect.x + fullRect.width <= mCanvas->GetLength() * mCanvas->GetWidth())
      {
         ofPushStyle();
         if (mCanvas->GetHighlightEnd() == Canvas::kHighlightEnd_End && IsResizable())
         {
            ofSetLineWidth(1.5f);
            ofSetColor(255, 100, 100);
         }
         ofLine(rect.x + rect.width, rect.y, rect.x + rect.width, rect.y + rect.height);
         ofPopStyle();
      }

      ofPopStyle();
   }
}

void CanvasElement::DrawOffscreen()
{
   ofPushStyle();
   ofSetLineWidth(1);
   ofSetColor(255, 255, 255);
   {
      ofRectangle rect = GetRect(K(clamp), !K(wrapped));
      if (rect.y < 0)
      {
         rect.y = 0;
         rect.height = 1;
      }
      if (rect.y + rect.height > mCanvas->GetHeight())
      {
         rect.y = mCanvas->GetHeight()-1;
         rect.height = 1;
      }
      rect.width = MAX(rect.width, 1);
      ofRect(rect);
   }
   if (mCanvas->ShouldWrap() && GetEnd() > 1)
   {
      ofRectangle rect = GetRect(K(clamp), K(wrapped));
      if (rect.y < 0)
      {
         rect.y = 0;
         rect.height = 1;
      }
      if (rect.y > mCanvas->GetHeight())
      {
         rect.y = mCanvas->GetHeight() - 1;
         rect.height = 1;
      }
      rect.width = MAX(rect.width, 1);
      ofRect(rect);
   }
   ofPopStyle();
}

float CanvasElement::GetStart(int col, float offset) const
{
   return (col + offset) / mCanvas->GetNumCols();
}

float CanvasElement::GetEnd(int col, float offset, float length) const
{
   return (col + offset + length) / mCanvas->GetNumCols();
}

float CanvasElement::GetStart() const
{
   return GetStart(mCol, mOffset);
}

void CanvasElement::SetStart(float start, bool preserveLength)
{
   float end;
   if (!preserveLength)
      end = GetEnd();
   start *= mCanvas->GetNumCols();
   mCol = int(start + .5f);
   mOffset = start - mCol;
   if (!preserveLength)
      SetEnd(end);
}

float CanvasElement::GetEnd() const
{
   return GetEnd(mCol, mOffset, mLength);
}

void CanvasElement::SetEnd(float end)
{
   mLength = end * mCanvas->GetNumCols() - mCol - mOffset;
}

ofRectangle CanvasElement::GetRect(bool clamp, bool wrapped, ofVec2f offset) const
{
   float wrapOffset = wrapped ? -1 : 0;
   float start = ofMap(GetStart() + wrapOffset,mCanvas->mViewStart/mCanvas->GetLength(),mCanvas->mViewEnd/mCanvas->GetLength(),0,1,clamp) * mCanvas->GetWidth();
   float end = ofMap(GetEnd() + wrapOffset,mCanvas->mViewStart/mCanvas->GetLength(),mCanvas->mViewEnd/mCanvas->GetLength(),0,1,clamp) * mCanvas->GetWidth();
   float y = (float(mRow-mCanvas->GetRowOffset()) / mCanvas->GetNumVisibleRows()) * mCanvas->GetHeight();
   float height = float(mCanvas->GetHeight()) / mCanvas->GetNumVisibleRows();

   return ofRectangle(start + offset.x, y + offset.y, end-start, height);
}

ofRectangle CanvasElement::GetRectAtDestination(bool clamp, bool wrapped, ofVec2f dragOffset) const
{
   int newRow;
   int newCol;
   float newOffset;
   GetDragDestinationData(dragOffset, newRow, newCol, newOffset);

   float wrapOffset = wrapped ? -1 : 0;
   float start = ofMap(GetStart(newCol, newOffset) + wrapOffset, mCanvas->mViewStart / mCanvas->GetLength(), mCanvas->mViewEnd / mCanvas->GetLength(), 0, 1, clamp) * mCanvas->GetWidth();
   float end = ofMap(GetEnd(newCol, newOffset, mLength) + wrapOffset, mCanvas->mViewStart / mCanvas->GetLength(), mCanvas->mViewEnd / mCanvas->GetLength(), 0, 1, clamp) * mCanvas->GetWidth();
   float y = (float(newRow - mCanvas->GetRowOffset()) / mCanvas->GetNumVisibleRows()) * mCanvas->GetHeight();
   float height = float(mCanvas->GetHeight()) / mCanvas->GetNumVisibleRows();

   return ofRectangle(start, y, end - start, height);
}

void CanvasElement::GetDragDestinationData(ofVec2f dragOffset, int& newRow, int& newCol, float& newOffset) const
{
   dragOffset.x *= mCanvas->mViewEnd - mCanvas->mViewStart;

   float colDrag = (dragOffset.x / mCanvas->GetWidth()) * mCanvas->GetNumCols() / mCanvas->GetLength();
   float rowDrag = (dragOffset.y / mCanvas->GetHeight()) * mCanvas->GetNumVisibleRows();

   newCol = mCol;
   if (mCanvas->GetDragMode() & Canvas::kDragHorizontal)
      newCol = ofClamp(int(mCol + colDrag + .5f), 0, mCanvas->GetNumCols() - 1);

   newRow = mRow;
   if (mCanvas->GetDragMode() & Canvas::kDragVertical)
      newRow = ofClamp(int(mRow + rowDrag + .5f), 0, mCanvas->GetNumRows() - 1);

   newOffset = mOffset;
   if (GetKeyModifiers() & kModifier_Alt)   //non-snapped drag
      newOffset = mOffset + colDrag - (newCol - mCol);
   if (GetKeyModifiers() & kModifier_Command)   //quantize
      newOffset = 0;
}

void CanvasElement::MoveElementByDrag(ofVec2f dragOffset)
{
   int newRow;
   int newCol;
   float newOffset;
   GetDragDestinationData(dragOffset, newRow, newCol, newOffset);

   mRow = newRow;
   mCol = newCol;
   mOffset = newOffset;
}

void CanvasElement::AddElementUIControl(IUIControl* control)
{
   mUIControls.push_back(control);
   control->SetShowing(false);
}

void CanvasElement::CheckboxUpdated(std::string label, bool value)
{
   for (auto* control : mUIControls)
   {
      if (control->Name() == label)
         control->SetValue(value);
   }
}

void CanvasElement::FloatSliderUpdated(std::string label, float oldVal, float newVal)
{
   for (auto* control : mUIControls)
   {
      if (control->Name() == label)
         control->SetValue(newVal);
   }
}

void CanvasElement::IntSliderUpdated(std::string label, int oldVal, float newVal)
{
   for (auto* control : mUIControls)
   {
      if (control->Name() == label)
         control->SetValue(newVal);
   }
}

void CanvasElement::ButtonClicked(std::string label)
{
}

namespace
{
   const int kCESaveStateRev = 1;
}

void CanvasElement::SaveState(FileStreamOut& out)
{
   out << kCESaveStateRev;

   out << mOffset;
   out << mLength;
}

void CanvasElement::LoadState(FileStreamIn& in)
{
   int rev;
   in >> rev;
   LoadStateValidate(rev <= kCESaveStateRev);

   if (rev < 1)
   {
      in >> mRow;
      in >> mCol;
   }
   in >> mOffset;
   in >> mLength;
}

NoteCanvasElement::NoteCanvasElement(Canvas* canvas, int col, int row, float offset, float length)
: CanvasElement(canvas,col,row,offset,length)
, mVelocity(.5f)
, mVoiceIdx(-1)
, mPan(0)
{
   if (canvas != nullptr && canvas->GetControls())
   {
      mElementOffsetSlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()), "offset", 0, 0, 100, 15, &mOffset, -1, 1);
      AddElementUIControl(mElementOffsetSlider);
      mElementLengthSlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()), "length", 0, 0, 100, 15, &mLength, 0, mCanvas->GetNumCols());
      AddElementUIControl(mElementLengthSlider);
      mElementRowSlider = new IntSlider(dynamic_cast<IIntSliderListener*>(canvas->GetControls()), "row", 0, 0, 100, 15, &mRow, 0, mCanvas->GetNumRows() - 1);
      AddElementUIControl(mElementRowSlider);
      mElementColSlider = new IntSlider(dynamic_cast<IIntSliderListener*>(canvas->GetControls()), "step", 0, 0, 100, 15, &mCol, 0, mCanvas->GetNumCols() - 1);
      AddElementUIControl(mElementColSlider);
      mVelocitySlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()), "velocity", 0, 0, 100, 15, &mVelocity, 0, 1);
      AddElementUIControl(mVelocitySlider);
   }
}

CanvasElement* NoteCanvasElement::CreateDuplicate() const
{
   NoteCanvasElement* element = new NoteCanvasElement(mCanvas, mCol, mRow, mOffset, mLength);
   element->mVelocity = mVelocity;
   element->mVoiceIdx = mVoiceIdx;
   return element;
}

void NoteCanvasElement::DrawContents(bool clamp, bool wrapped, ofVec2f offset)
{
   ofPushStyle();
   ofFill();

   ofRectangle rect = GetRect(clamp, wrapped, offset);
   float fullHeight = rect.height;
   rect.height *= mVelocity;
   rect.y += (fullHeight - rect.height) * .5f;
   if (rect.width > 0)
   {
      ofSetColorGradient(ofColor::white, ofColor(210,210,210), ofVec2f(ofLerp(rect.getMinX(),rect.getMaxX(),.5f), rect.y), ofVec2f(rect.getMaxX(), rect.y));
      ofRect(rect, 0);
   }

   /*ofSetLineWidth(1.5f * gDrawScale);

   rect = GetRect(clamp, wrapped);
   ofRectangle fullRect = GetRect(false, false);
   if (wrapped)
      fullRect.x -= mCanvas->GetWidth();
   float length = (GetEnd()-GetStart()) * mCanvas->GetLength();

   float start = (float(rect.x-fullRect.x)/fullRect.width);
   float end = ((float(rect.x+rect.width)-(fullRect.x))/fullRect.width);

   mPitchBendCurve.SetPosition(rect.x, rect.y - rect.height/2);
   mPitchBendCurve.SetDimensions(rect.width, rect.height);
   mPitchBendCurve.SetExtents(start*length, end*length);
   mPitchBendCurve.SetColor(ofColor::red);
   mPitchBendCurve.Render();

   mModWheelCurve.SetPosition(rect.x, rect.y);
   mModWheelCurve.SetDimensions(rect.width, rect.height);
   mModWheelCurve.SetExtents(start*length, end*length);
   mModWheelCurve.SetColor(ofColor::green);
   mModWheelCurve.Render();

   mPressureCurve.SetPosition(rect.x, rect.y);
   mPressureCurve.SetDimensions(rect.width, rect.height);
   mPressureCurve.SetExtents(start*length, end*length);
   mPressureCurve.SetColor(ofColor::blue);
   mPressureCurve.Render();*/

   ofPopStyle();
}

void NoteCanvasElement::UpdateModulation(float pos)
{
   float curveTime = (pos - GetStart()) * mCanvas->GetLength();
   FloatWrap(curveTime, mCanvas->GetLength());
   mPitchBend.SetValue(mPitchBendCurve.Evaluate(curveTime));
   mModWheel.SetValue(mModWheelCurve.Evaluate(curveTime));
   mPressure.SetValue(mPressureCurve.Evaluate(curveTime));
   mPan = mPanCurve.Evaluate(curveTime);
}

void NoteCanvasElement::WriteModulation(float pos, float pitchBend, float modWheel, float pressure, float pan)
{
   float curveTime = (pos - GetStart()) * mCanvas->GetLength();
   FloatWrap(curveTime, mCanvas->GetLength());
   mPitchBendCurve.AddPoint(CurvePoint(curveTime, pitchBend));
   mModWheelCurve.AddPoint(CurvePoint(curveTime, modWheel));
   mPressureCurve.AddPoint(CurvePoint(curveTime, pressure));
   mPanCurve.AddPoint(CurvePoint(curveTime, pan));
}

namespace
{
   const int kNCESaveStateRev = 0;
}

void NoteCanvasElement::SaveState(FileStreamOut& out)
{
   CanvasElement::SaveState(out);

   out << kNCESaveStateRev;

   out << mVelocity;
}

void NoteCanvasElement::LoadState(FileStreamIn& in)
{
   CanvasElement::LoadState(in);

   int rev;
   in >> rev;
   LoadStateValidate(rev == kNCESaveStateRev);

   in >> mVelocity;
}

SampleCanvasElement::SampleCanvasElement(Canvas* canvas, int col, int row, float offset, float length)
: CanvasElement(canvas,col,row,offset,length)
, mSample(nullptr)
, mVolume(1)
, mMute(false)
{
   mElementOffsetSlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()),"offset",0,0,100,15,&mOffset,-1,1);
   AddElementUIControl(mElementOffsetSlider);
   mVolumeSlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()),"volume",0,0,100,15,&mVolume,0,2);
   AddElementUIControl(mVolumeSlider);
   mMuteCheckbox = new Checkbox(dynamic_cast<IDrawableModule*>(canvas->GetControls()), "mute", 0, 0, &mMute);
   AddElementUIControl(mMuteCheckbox);
   mSplitSampleButton = new ClickButton(dynamic_cast<IButtonListener*>(canvas->GetControls()), "split", 0, 0);
   AddElementUIControl(mSplitSampleButton);
   mResetSpeedButton = new ClickButton(dynamic_cast<IButtonListener*>(canvas->GetControls()), "reset speed", 0, 0);
   AddElementUIControl(mResetSpeedButton);
}

SampleCanvasElement::~SampleCanvasElement()
{
   delete mSample;
}

CanvasElement* SampleCanvasElement::CreateDuplicate() const
{
   SampleCanvasElement* element = new SampleCanvasElement(mCanvas, mCol, mRow, mOffset, mLength);
   element->mSample = new Sample();
   element->mSample->Create(mSample->Data());
   element->mVolume = mVolume;
   element->mMute = mMute;
   return element;
}

void SampleCanvasElement::SetSample(Sample* sample)
{
   mSample = sample;
}

void SampleCanvasElement::CheckboxUpdated(std::string label, bool value)
{
   CanvasElement::CheckboxUpdated(label, value);
}

void SampleCanvasElement::ButtonClicked(std::string label)
{
   CanvasElement::ButtonClicked(label);
   if (label == "split")
   {
      ChannelBuffer* firstHalf = new ChannelBuffer(mSample->Data()->BufferSize() / 2);
      ChannelBuffer* secondHalf = new ChannelBuffer(mSample->Data()->BufferSize() / 2);
      firstHalf->CopyFrom(mSample->Data(), firstHalf->BufferSize(), 0);
      secondHalf->CopyFrom(mSample->Data(), secondHalf->BufferSize(), mSample->Data()->BufferSize() / 2);

      SampleCanvasElement* element = new SampleCanvasElement(mCanvas, mCol, mRow, mOffset, mLength);
      element->mSample = new Sample();
      element->mSample->Create(secondHalf);
      element->mVolume = mVolume;
      element->mMute = mMute;
      element->SetStart((GetStart() + GetEnd()) * .5f, false);
      mCanvas->AddElement(element);

      mSample->Create(firstHalf);
      mLength /= 2;
   }
   if (label == "reset speed")
   {
      SampleCanvas* sampleCanvas = dynamic_cast<SampleCanvas*>(mCanvas->GetParent());
      if (sampleCanvas != nullptr)
      {
         float lengthMs = mSample->LengthInSamples() / mSample->GetSampleRateRatio() / gSampleRateMs;
         float lengthOriginalSpeed = lengthMs / TheTransport->GetDuration(sampleCanvas->GetInterval());
         mLength = lengthOriginalSpeed;
      }
   }
}

void SampleCanvasElement::DrawContents(bool clamp, bool wrapped, ofVec2f offset)
{
   if (wrapped)
      return;

   ofRectangle rect = GetRect(false, false, offset);

   ofPushMatrix();
   ofTranslate(rect.x,rect.y);
   if (mSample)
   {
      float width = rect.width;
      DrawAudioBuffer(width, rect.height, mSample->Data(), 0, mSample->LengthInSamples(), -1);
   }
   else
   {
      ofSetColor(0,0,0);
      ofRect(0,0,rect.width,rect.height);
   }
   ofPopMatrix();

   if (rect.width < 0)
      rect.set(rect.x + rect.width, rect.y, -rect.width, rect.height);

   SampleCanvas* sampleCanvas = dynamic_cast<SampleCanvas*>(mCanvas->GetParent());
   if (sampleCanvas != nullptr && mSample != nullptr)
   {
      float lengthMs = mSample->LengthInSamples() / mSample->GetSampleRateRatio() / gSampleRateMs;
      float lengthOriginalSpeed = lengthMs / TheTransport->GetDuration(sampleCanvas->GetInterval());
      float speed = lengthOriginalSpeed / mLength;
      ofSetColor(255, 255, 255);
      DrawTextBold(ofToString(speed, 2), rect.x + 3, rect.y + 10, 12);
   }

   if (mMute)
   {
      ofSetColor(0, 0, 0, 140);
      ofFill();
      ofRect(rect);
   }

   ofSetColor(255, 255, 255);
   ofNoFill();
   ofRect(rect)

}

namespace
{
   const int kSCESaveStateRev = 2;
}

void SampleCanvasElement::SaveState(FileStreamOut& out)
{
   CanvasElement::SaveState(out);

   out << kSCESaveStateRev;

   bool hasSample = mSample != nullptr;
   out << hasSample;
   if (mSample != nullptr)
      mSample->SaveState(out);
   out << mVolume;
   out << mMute;
}

void SampleCanvasElement::LoadState(FileStreamIn& in)
{
   CanvasElement::LoadState(in);

   int rev;
   in >> rev;
   LoadStateValidate(rev <= kSCESaveStateRev);

   bool hasSample;
   in >> hasSample;
   if (hasSample)
   {
      mSample = new Sample();
      mSample->LoadState(in);
   }
   if (rev < 2)
   {
      int dummy;
      in >> dummy;
      in >> dummy;
   }
   in >> mVolume;
   if (rev == 0)
   {
      bool dummy;
      in >> dummy;
   }
   if (rev >= 1)
      in >> mMute;
}

EventCanvasElement::EventCanvasElement(Canvas* canvas, int col, int row, float offset)
: CanvasElement(canvas,col,row,offset,.5f)
, mValue(0)
{
   mValueEntry = new TextEntry(dynamic_cast<ITextEntryListener*>(canvas->GetControls()),"value",60,2,7,&mValue,-99999,99999);
   AddElementUIControl(mValueEntry);

   mEventCanvas = dynamic_cast<EventCanvas*>(canvas->GetControls()->GetParent());
   assert(mEventCanvas);
   mUIControl = mEventCanvas->GetUIControlForRow(row);
   mIsCheckbox = dynamic_cast<Checkbox*>(mUIControl) != nullptr;
   mIsButton = dynamic_cast<ClickButton*>(mUIControl) != nullptr;

   if (mUIControl)
      mValue = mUIControl->GetValue();
   if (mIsButton)
      mValue = 1;
}

EventCanvasElement::~EventCanvasElement()
{
}

CanvasElement* EventCanvasElement::CreateDuplicate() const
{
   EventCanvasElement* element = new EventCanvasElement(mCanvas, mCol, mRow, mOffset);
   element->mUIControl = mUIControl;
   element->mIsCheckbox = mIsCheckbox;
   element->mIsButton = mIsButton;
   element->mValue = mValue;
   return element;
}

void EventCanvasElement::DrawContents(bool clamp, bool wrapped, ofVec2f offset)
{
   if (wrapped)
      return;

   if (GetRect(false, false, offset).width != GetRect(clamp, false, offset).width)
      return;  //only draw text for fully visible elements

   ofSetColor(mEventCanvas->GetRowColor(mRow));
   ofFill();
   ofRect(GetRect(clamp, wrapped, offset), 0);

   std::string text;
   if (mIsCheckbox)
   {
      text = mUIControl->Name();
      ofRectangle rect = GetRect(clamp, false, offset);
      ofSetColor(0,0,0);
      DrawTextNormal(text, rect.x+4, rect.y+11);
      ofSetColor(255,255,255);
      DrawTextNormal(text, rect.x+3, rect.y+10);
   }
   else if (mUIControl)
   {
      text += mUIControl->Name();
      text += ":";
      text += mUIControl->GetDisplayValue(mValue);

      ofRectangle rect = GetRect(clamp, false, offset);
      ofSetColor(0,0,0);
      DrawTextNormal(text, rect.x+rect.width+1, rect.y+11);
      ofSetColor(255,255,255);
      DrawTextNormal(text, rect.x+rect.width, rect.y+10);
   }
}

void EventCanvasElement::SetUIControl(IUIControl* control)
{
   bool hadUIControl = mUIControl != nullptr;
   mUIControl = dynamic_cast<IUIControl*>(control);
   mIsCheckbox = dynamic_cast<Checkbox*>(control) != nullptr;
   mIsButton = dynamic_cast<ClickButton*>(control) != nullptr;
   if (mUIControl && !hadUIControl)
      mValue = mUIControl->GetValue();
   if (mIsButton)
      mValue = 1;
}

void EventCanvasElement::Trigger()
{
   if (mUIControl)
   {
      if (mIsCheckbox)
         mUIControl->SetValue(1);
      else
         mUIControl->SetValue(mValue);
   }
}

void EventCanvasElement::TriggerEnd()
{
   if (mUIControl && mIsCheckbox)
       mUIControl->SetValue(0);
}

float EventCanvasElement::GetEnd() const
{
   if (mIsCheckbox)  //normal resizable element
      return CanvasElement::GetEnd();

   float size = 4;
   float span = mCanvas->mViewEnd - mCanvas->mViewStart;
   return GetStart() + size * span / mCanvas->GetWidth();
}

namespace
{
   const int kECESaveStateRev = 1;
}

void EventCanvasElement::SaveState(FileStreamOut& out)
{
   CanvasElement::SaveState(out);

   out << kECESaveStateRev;

   out << mValue;
}

void EventCanvasElement::LoadState(FileStreamIn& in)
{
   CanvasElement::LoadState(in);

   int rev;
   in >> rev;
   LoadStateValidate(rev <= kECESaveStateRev);

   in >> mValue;
   if (rev < 1)
   {
      std::string dummy;
      in >> dummy;
   }
}


if (hasSample)
   {
      mSample = new Sample();
      mSample->LoadState(in);
   }

/**
    bespoke synth, a software modular synthesizer

**/
//  CanvasElement.cpp
//  Bespoke
//  Created by Ryan Challinor on 1/4/15.

#include "CanvasElement.h"
#include "Sample.h"
#include "SynthGlobals.h"
#include "Slider.h"
#include "CanvasControls.h"
#include "TextEntry.h"
#include "PatchCableSource.h"
#include "ModularSynth.h"
#include "EventCanvas.h"
#include "SampleCanvas.h"

CanvasElement::CanvasElement(Canvas* canvas, int col, int row, float offset, float length)
: mCanvas(canvas)
, mOffset(offset)
, mLength(length)
, mCol(col)
, mRow(row)
, mHighlighted(false)
{
}

void CanvasElement::Draw(ofVec2f offset)
{
   if (offset.x == 0 && offset.y == 0)
   {
      DrawElement(K(clamp), !K(wrapped), offset);
      if (mCanvas->ShouldWrap() && GetEnd() > 1)
         DrawElement(K(clamp), K(wrapped), offset);
   }
   else
   {
      ofPushStyle();
      ofSetLineWidth(3.0f);
      ofNoFill();
      ofSetColor(255, 255, 255, ofMap(sin(gTime / 500 * PI * 2), -1, 1, 40, 120));
      ofRect(GetRectAtDestination(K(clamp), !K(wrapped), offset), 0);
      if (mCanvas->ShouldWrap() && GetEnd() > 1)
         ofRect(GetRectAtDestination(K(clamp), K(wrapped), offset), 0);
      ofPopStyle();

      DrawElement(!K(clamp), !K(wrapped), offset);
   }
}

void CanvasElement::DrawElement(bool clamp, bool wrapped, ofVec2f offset)
{
   ofRectangle rect = GetRect(clamp, wrapped, offset);
   ofRectangle fullRect = GetRect(false, wrapped, offset);

   if (rect.width < 0)
   {
      ofPushStyle();
      ofFill();
      ofSetColor(255, 0, 0);
      ofRect(rect.x + rect.width, rect.y, -rect.width, rect.height, 0);
      ofPopStyle();
   }

   ofPushStyle();
   DrawContents(clamp, wrapped, offset);
   ofPopStyle();

   if (mHighlighted)
   {
      ofPushStyle();
      ofNoFill();
      ofSetColor(255, 200, 0);
      ofSetLineWidth(.75f);

      ofLine(rect.x, rect.y, rect.x + rect.width, rect.y);
      ofLine(rect.x, rect.y + rect.height, rect.x + rect.width, rect.y + rect.height);

      if (fullRect.x >= 0)
      {
         ofPushStyle();
         if (mCanvas->GetHighlightEnd() == Canvas::kHighlightEnd_Start && IsResizable())
         {
            ofSetLineWidth(1.5f);
            ofSetColor(255, 100, 100);
         }
         ofLine(rect.x, rect.y, rect.x, rect.y + rect.height);
         ofPopStyle();
      }
      if (fullRect.x + fullRect.width <= mCanvas->GetLength() * mCanvas->GetWidth())
      {
         ofPushStyle();
         if (mCanvas->GetHighlightEnd() == Canvas::kHighlightEnd_End && IsResizable())
         {
            ofSetLineWidth(1.5f);
            ofSetColor(255, 100, 100);
         }
         ofLine(rect.x + rect.width, rect.y, rect.x + rect.width, rect.y + rect.height);
         ofPopStyle();
      }

      ofPopStyle();
   }
}

void CanvasElement::DrawOffscreen()
{
   ofPushStyle();
   ofSetLineWidth(1);
   ofSetColor(255, 255, 255);
   {
      ofRectangle rect = GetRect(K(clamp), !K(wrapped));
      if (rect.y < 0)
      {
         rect.y = 0;
         rect.height = 1;
      }
      if (rect.y + rect.height > mCanvas->GetHeight())
      {
         rect.y = mCanvas->GetHeight()-1;
         rect.height = 1;
      }
      rect.width = MAX(rect.width, 1);
      ofRect(rect);
   }
   if (mCanvas->ShouldWrap() && GetEnd() > 1)
   {
      ofRectangle rect = GetRect(K(clamp), K(wrapped));
      if (rect.y < 0)
      {
         rect.y = 0;
         rect.height = 1;
      }
      if (rect.y > mCanvas->GetHeight())
      {
         rect.y = mCanvas->GetHeight() - 1;
         rect.height = 1;
      }
      rect.width = MAX(rect.width, 1);
      ofRect(rect);
   }
   ofPopStyle();
}

float CanvasElement::GetStart(int col, float offset) const
{
   return (col + offset) / mCanvas->GetNumCols();
}

float CanvasElement::GetEnd(int col, float offset, float length) const
{
   return (col + offset + length) / mCanvas->GetNumCols();
}

float CanvasElement::GetStart() const
{
   return GetStart(mCol, mOffset);
}

void CanvasElement::SetStart(float start, bool preserveLength)
{
   float end;
   if (!preserveLength)
      end = GetEnd();
   start *= mCanvas->GetNumCols();
   mCol = int(start + .5f);
   mOffset = start - mCol;
   if (!preserveLength)
      SetEnd(end);
}

float CanvasElement::GetEnd() const
{
   return GetEnd(mCol, mOffset, mLength);
}

void CanvasElement::SetEnd(float end)
{
   mLength = end * mCanvas->GetNumCols() - mCol - mOffset;
}

ofRectangle CanvasElement::GetRect(bool clamp, bool wrapped, ofVec2f offset) const
{
   float wrapOffset = wrapped ? -1 : 0;
   float start = ofMap(GetStart() + wrapOffset,mCanvas->mViewStart/mCanvas->GetLength(),mCanvas->mViewEnd/mCanvas->GetLength(),0,1,clamp) * mCanvas->GetWidth();
   float end = ofMap(GetEnd() + wrapOffset,mCanvas->mViewStart/mCanvas->GetLength(),mCanvas->mViewEnd/mCanvas->GetLength(),0,1,clamp) * mCanvas->GetWidth();
   float y = (float(mRow-mCanvas->GetRowOffset()) / mCanvas->GetNumVisibleRows()) * mCanvas->GetHeight();
   float height = float(mCanvas->GetHeight()) / mCanvas->GetNumVisibleRows();

   return ofRectangle(start + offset.x, y + offset.y, end-start, height);
}

ofRectangle CanvasElement::GetRectAtDestination(bool clamp, bool wrapped, ofVec2f dragOffset) const
{
   int newRow;
   int newCol;
   float newOffset;
   GetDragDestinationData(dragOffset, newRow, newCol, newOffset);

   float wrapOffset = wrapped ? -1 : 0;
   float start = ofMap(GetStart(newCol, newOffset) + wrapOffset, mCanvas->mViewStart / mCanvas->GetLength(), mCanvas->mViewEnd / mCanvas->GetLength(), 0, 1, clamp) * mCanvas->GetWidth();
   float end = ofMap(GetEnd(newCol, newOffset, mLength) + wrapOffset, mCanvas->mViewStart / mCanvas->GetLength(), mCanvas->mViewEnd / mCanvas->GetLength(), 0, 1, clamp) * mCanvas->GetWidth();
   float y = (float(newRow - mCanvas->GetRowOffset()) / mCanvas->GetNumVisibleRows()) * mCanvas->GetHeight();
   float height = float(mCanvas->GetHeight()) / mCanvas->GetNumVisibleRows();

   return ofRectangle(start, y, end - start, height);
}

void CanvasElement::GetDragDestinationData(ofVec2f dragOffset, int& newRow, int& newCol, float& newOffset) const
{
   dragOffset.x *= mCanvas->mViewEnd - mCanvas->mViewStart;

   float colDrag = (dragOffset.x / mCanvas->GetWidth()) * mCanvas->GetNumCols() / mCanvas->GetLength();
   float rowDrag = (dragOffset.y / mCanvas->GetHeight()) * mCanvas->GetNumVisibleRows();

   newCol = mCol;
   if (mCanvas->GetDragMode() & Canvas::kDragHorizontal)
      newCol = ofClamp(int(mCol + colDrag + .5f), 0, mCanvas->GetNumCols() - 1);

   newRow = mRow;
   if (mCanvas->GetDragMode() & Canvas::kDragVertical)
      newRow = ofClamp(int(mRow + rowDrag + .5f), 0, mCanvas->GetNumRows() - 1);

   newOffset = mOffset;
   if (GetKeyModifiers() & kModifier_Alt)   //non-snapped drag
      newOffset = mOffset + colDrag - (newCol - mCol);
   if (GetKeyModifiers() & kModifier_Command)   //quantize
      newOffset = 0;
}

void CanvasElement::MoveElementByDrag(ofVec2f dragOffset)
{
   int newRow;
   int newCol;
   float newOffset;
   GetDragDestinationData(dragOffset, newRow, newCol, newOffset);

   mRow = newRow;
   mCol = newCol;
   mOffset = newOffset;
}

void CanvasElement::AddElementUIControl(IUIControl* control)
{
   mUIControls.push_back(control);
   control->SetShowing(false);
}

void CanvasElement::CheckboxUpdated(std::string label, bool value)
{
   for (auto* control : mUIControls)
   {
      if (control->Name() == label)
         control->SetValue(value);
   }
}

void CanvasElement::FloatSliderUpdated(std::string label, float oldVal, float newVal)
{
   for (auto* control : mUIControls)
   {
      if (control->Name() == label)
         control->SetValue(newVal);
   }
}

void CanvasElement::IntSliderUpdated(std::string label, int oldVal, float newVal)
{
   for (auto* control : mUIControls)
   {
      if (control->Name() == label)
         control->SetValue(newVal);
   }
}

void CanvasElement::ButtonClicked(std::string label)
{
}

namespace
{
   const int kCESaveStateRev = 1;
}

void CanvasElement::SaveState(FileStreamOut& out)
{
   out << kCESaveStateRev;

   out << mOffset;
   out << mLength;
}

void CanvasElement::LoadState(FileStreamIn& in)
{
   int rev;
   in >> rev;
   LoadStateValidate(rev <= kCESaveStateRev);

   if (rev < 1)
   {
      in >> mRow;
      in >> mCol;
   }
   in >> mOffset;
   in >> mLength;
}

NoteCanvasElement::NoteCanvasElement(Canvas* canvas, int col, int row, float offset, float length)
: CanvasElement(canvas,col,row,offset,length)
, mVelocity(.5f)
, mVoiceIdx(-1)
, mPan(0)
{
   if (canvas != nullptr && canvas->GetControls())
   {
      mElementOffsetSlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()), "offset", 0, 0, 100, 15, &mOffset, -1, 1);
      AddElementUIControl(mElementOffsetSlider);
      mElementLengthSlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()), "length", 0, 0, 100, 15, &mLength, 0, mCanvas->GetNumCols());
      AddElementUIControl(mElementLengthSlider);
      mElementRowSlider = new IntSlider(dynamic_cast<IIntSliderListener*>(canvas->GetControls()), "row", 0, 0, 100, 15, &mRow, 0, mCanvas->GetNumRows() - 1);
      AddElementUIControl(mElementRowSlider);
      mElementColSlider = new IntSlider(dynamic_cast<IIntSliderListener*>(canvas->GetControls()), "step", 0, 0, 100, 15, &mCol, 0, mCanvas->GetNumCols() - 1);
      AddElementUIControl(mElementColSlider);
      mVelocitySlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()), "velocity", 0, 0, 100, 15, &mVelocity, 0, 1);
      AddElementUIControl(mVelocitySlider);
   }
}

CanvasElement* NoteCanvasElement::CreateDuplicate() const
{
   NoteCanvasElement* element = new NoteCanvasElement(mCanvas, mCol, mRow, mOffset, mLength);
   element->mVelocity = mVelocity;
   element->mVoiceIdx = mVoiceIdx;
   return element;
}

void NoteCanvasElement::DrawContents(bool clamp, bool wrapped, ofVec2f offset)
{
   ofPushStyle();
   ofFill();

   ofRectangle rect = GetRect(clamp, wrapped, offset);
   float fullHeight = rect.height;
   rect.height *= mVelocity;
   rect.y += (fullHeight - rect.height) * .5f;
   if (rect.width > 0)
   {
      ofSetColorGradient(ofColor::white, ofColor(210,210,210), ofVec2f(ofLerp(rect.getMinX(),rect.getMaxX(),.5f), rect.y), ofVec2f(rect.getMaxX(), rect.y));
      ofRect(rect, 0);
   }

   /*ofSetLineWidth(1.5f * gDrawScale);

   rect = GetRect(clamp, wrapped);
   ofRectangle fullRect = GetRect(false, false);
   if (wrapped)
      fullRect.x -= mCanvas->GetWidth();
   float length = (GetEnd()-GetStart()) * mCanvas->GetLength();

   float start = (float(rect.x-fullRect.x)/fullRect.width);
   float end = ((float(rect.x+rect.width)-(fullRect.x))/fullRect.width);

   mPitchBendCurve.SetPosition(rect.x, rect.y - rect.height/2);
   mPitchBendCurve.SetDimensions(rect.width, rect.height);
   mPitchBendCurve.SetExtents(start*length, end*length);
   mPitchBendCurve.SetColor(ofColor::red);
   mPitchBendCurve.Render();

   mModWheelCurve.SetPosition(rect.x, rect.y);
   mModWheelCurve.SetDimensions(rect.width, rect.height);
   mModWheelCurve.SetExtents(start*length, end*length);
   mModWheelCurve.SetColor(ofColor::green);
   mModWheelCurve.Render();

   mPressureCurve.SetPosition(rect.x, rect.y);
   mPressureCurve.SetDimensions(rect.width, rect.height);
   mPressureCurve.SetExtents(start*length, end*length);
   mPressureCurve.SetColor(ofColor::blue);
   mPressureCurve.Render();*/

   ofPopStyle();
}

void NoteCanvasElement::UpdateModulation(float pos)
{
   float curveTime = (pos - GetStart()) * mCanvas->GetLength();
   FloatWrap(curveTime, mCanvas->GetLength());
   mPitchBend.SetValue(mPitchBendCurve.Evaluate(curveTime));
   mModWheel.SetValue(mModWheelCurve.Evaluate(curveTime));
   mPressure.SetValue(mPressureCurve.Evaluate(curveTime));
   mPan = mPanCurve.Evaluate(curveTime);
}

void NoteCanvasElement::WriteModulation(float pos, float pitchBend, float modWheel, float pressure, float pan)
{
   float curveTime = (pos - GetStart()) * mCanvas->GetLength();
   FloatWrap(curveTime, mCanvas->GetLength());
   mPitchBendCurve.AddPoint(CurvePoint(curveTime, pitchBend));
   mModWheelCurve.AddPoint(CurvePoint(curveTime, modWheel));
   mPressureCurve.AddPoint(CurvePoint(curveTime, pressure));
   mPanCurve.AddPoint(CurvePoint(curveTime, pan));
}

namespace
{
   const int kNCESaveStateRev = 0;
}

void NoteCanvasElement::SaveState(FileStreamOut& out)
{
   CanvasElement::SaveState(out);

   out << kNCESaveStateRev;

   out << mVelocity;
}

void NoteCanvasElement::LoadState(FileStreamIn& in)
{
   CanvasElement::LoadState(in);

   int rev;
   in >> rev;
   LoadStateValidate(rev == kNCESaveStateRev);

   in >> mVelocity;
}

SampleCanvasElement::SampleCanvasElement(Canvas* canvas, int col, int row, float offset, float length)
: CanvasElement(canvas,col,row,offset,length)
, mSample(nullptr)
, mVolume(1)
, mMute(false)
{
   mElementOffsetSlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()),"offset",0,0,100,15,&mOffset,-1,1);
   AddElementUIControl(mElementOffsetSlider);
   mVolumeSlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()),"volume",0,0,100,15,&mVolume,0,2);
   AddElementUIControl(mVolumeSlider);
   mMuteCheckbox = new Checkbox(dynamic_cast<IDrawableModule*>(canvas->GetControls()), "mute", 0, 0, &mMute);
   AddElementUIControl(mMuteCheckbox);
   mSplitSampleButton = new ClickButton(dynamic_cast<IButtonListener*>(canvas->GetControls()), "split", 0, 0);
   AddElementUIControl(mSplitSampleButton);
   mResetSpeedButton = new ClickButton(dynamic_cast<IButtonListener*>(canvas->GetControls()), "reset speed", 0, 0);
   AddElementUIControl(mResetSpeedButton);
}

SampleCanvasElement::~SampleCanvasElement()
{
   delete mSample;
}

CanvasElement* SampleCanvasElement::CreateDuplicate() const
{
   SampleCanvasElement* element = new SampleCanvasElement(mCanvas, mCol, mRow, mOffset, mLength);
   element->mSample = new Sample();
   element->mSample->Create(mSample->Data());
   element->mVolume = mVolume;
   element->mMute = mMute;
   return element;
}

void SampleCanvasElement::SetSample(Sample* sample)
{
   mSample = sample;
}

void SampleCanvasElement::CheckboxUpdated(std::string label, bool value)
{
   CanvasElement::CheckboxUpdated(label, value);
}

void SampleCanvasElement::ButtonClicked(std::string label)
{
   CanvasElement::ButtonClicked(label);
   if (label == "split")
   {
      ChannelBuffer* firstHalf = new ChannelBuffer(mSample->Data()->BufferSize() / 2);
      ChannelBuffer* secondHalf = new ChannelBuffer(mSample->Data()->BufferSize() / 2);
      firstHalf->CopyFrom(mSample->Data(), firstHalf->BufferSize(), 0);
      secondHalf->CopyFrom(mSample->Data(), secondHalf->BufferSize(), mSample->Data()->BufferSize() / 2);

      SampleCanvasElement* element = new SampleCanvasElement(mCanvas, mCol, mRow, mOffset, mLength);
      element->mSample = new Sample();
      element->mSample->Create(secondHalf);
      element->mVolume = mVolume;
      element->mMute = mMute;
      element->SetStart((GetStart() + GetEnd()) * .5f, false);
      mCanvas->AddElement(element);

      mSample->Create(firstHalf);
      mLength /= 2;
   }
   if (label == "reset speed")
   {
      SampleCanvas* sampleCanvas = dynamic_cast<SampleCanvas*>(mCanvas->GetParent());
      if (sampleCanvas != nullptr)
      {
         float lengthMs = mSample->LengthInSamples() / mSample->GetSampleRateRatio() / gSampleRateMs;
         float lengthOriginalSpeed = lengthMs / TheTransport->GetDuration(sampleCanvas->GetInterval());
         mLength = lengthOriginalSpeed;
      }
   }
}

void SampleCanvasElement::DrawContents(bool clamp, bool wrapped, ofVec2f offset)
{
   if (wrapped)
      return;

   ofRectangle rect = GetRect(false, false, offset);

   ofPushMatrix();
   ofTranslate(rect.x,rect.y);
   if (mSample)
   {
      float width = rect.width;
      DrawAudioBuffer(width, rect.height, mSample->Data(), 0, mSample->LengthInSamples(), -1);
   }
   else
   {
      ofSetColor(0,0,0);
      ofRect(0,0,rect.width,rect.height);
   }
   ofPopMatrix();

   if (rect.width < 0)
      rect.set(rect.x + rect.width, rect.y, -rect.width, rect.height);

   SampleCanvas* sampleCanvas = dynamic_cast<SampleCanvas*>(mCanvas->GetParent());
   if (sampleCanvas != nullptr && mSample != nullptr)
   {
      float lengthMs = mSample->LengthInSamples() / mSample->GetSampleRateRatio() / gSampleRateMs;
      float lengthOriginalSpeed = lengthMs / TheTransport->GetDuration(sampleCanvas->GetInterval());
      float speed = lengthOriginalSpeed / mLength;
      ofSetColor(255, 255, 255);
      DrawTextBold(ofToString(speed, 2), rect.x + 3, rect.y + 10, 12);
   }

   if (mMute)
   {
      ofSetColor(0, 0, 0, 140);
      ofFill();
      ofRect(rect);
   }

   ofSetColor(255, 255, 255);
   ofNoFill();
   ofRect(rect);
}

namespace
{
   const int kSCESaveStateRev = 2;
}

void SampleCanvasElement::SaveState(FileStreamOut& out)
{
   CanvasElement::SaveState(out);

   out << kSCESaveStateRev;

   bool hasSample = mSample != nullptr;
   out << hasSample;
   if (mSample != nullptr)
      mSample->SaveState(out);
   out << mVolume;
   out << mMute;
}

void SampleCanvasElement::LoadState(FileStreamIn& in)
{
   CanvasElement::LoadState(in);

   int rev;
   in >> rev;
   LoadStateValidate(rev <= kSCESaveStateRev);

   bool hasSample;
   in >> hasSample;

   if (rev < 2)
   {
      int dummy;
      in >> dummy;
      in >> dummy;
   }
   in >> mVolume;
   if (rev == 0)
   {
      bool dummy;
      in >> dummy;
   }
   if (rev >= 1)
      in >> mMute;
}

EventCanvasElement::EventCanvasElement(Canvas* canvas, int col, int row, float offset)
: CanvasElement(canvas,col,row,offset,.5f)
, mValue(0)
{
   mValueEntry = new TextEntry(dynamic_cast<ITextEntryListener*>(canvas->GetControls()),"value",60,2,7,&mValue,-99999,99999);
   AddElementUIControl(mValueEntry);

   mEventCanvas = dynamic_cast<EventCanvas*>(canvas->GetControls()->GetParent());
   assert(mEventCanvas);
   mUIControl = mEventCanvas->GetUIControlForRow(row);
   mIsCheckbox = dynamic_cast<Checkbox*>(mUIControl) != nullptr;
   mIsButton = dynamic_cast<ClickButton*>(mUIControl) != nullptr;

   if (mUIControl)
      mValue = mUIControl->GetValue();
   if (mIsButton)
      mValue = 1;
}

EventCanvasElement::~EventCanvasElement()
{
}

CanvasElement* EventCanvasElement::CreateDuplicate() const
{
   EventCanvasElement* element = new EventCanvasElement(mCanvas, mCol, mRow, mOffset);
   element->mUIControl = mUIControl;
   element->mIsCheckbox = mIsCheckbox;
   element->mIsButton = mIsButton;
   element->mValue = mValue;
   return element;
}

void EventCanvasElement::DrawContents(bool clamp, bool wrapped, ofVec2f offset)
{
   if (wrapped)
      return;

   if (GetRect(false, false, offset).width != GetRect(clamp, false, offset).width)
      return;  //only draw text for fully visible elements

   ofSetColor(mEventCanvas->GetRowColor(mRow));
   ofFill();
   ofRect(GetRect(clamp, wrapped, offset), 0);

   std::string text;
   if (mIsCheckbox)
   {
      text = mUIControl->Name();
      ofRectangle rect = GetRect(clamp, false, offset);
      ofSetColor(0,0,0);
      DrawTextNormal(text, rect.x+4, rect.y+11);
      ofSetColor(255,255,255);
      DrawTextNormal(text, rect.x+3, rect.y+10);
   }
   else if (mUIControl)
   {
      text += mUIControl->Name();
      text += ":";
      text += mUIControl->GetDisplayValue(mValue);

      ofRectangle rect = GetRect(clamp, false, offset);
      ofSetColor(0,0,0);
      DrawTextNormal(text, rect.x+rect.width+1, rect.y+11);
      ofSetColor(255,255,255);
      DrawTextNormal(text, rect.x+rect.width, rect.y+10);
   }
}

void EventCanvasElement::SetUIControl(IUIControl* control)
{
   bool hadUIControl = mUIControl != nullptr;
   mUIControl = dynamic_cast<IUIControl*>(control);
   mIsCheckbox = dynamic_cast<Checkbox*>(control) != nullptr;
   mIsButton = dynamic_cast<ClickButton*>(control) != nullptr;
   if (mUIControl && !hadUIControl)
      mValue = mUIControl->GetValue();
   if (mIsButton)
      mValue = 1;
}

void EventCanvasElement::Trigger()
{
   if (mUIControl)
   {
      if (mIsCheckbox)
         mUIControl->SetValue(1);
      else
         mUIControl->SetValue(mValue);
   }
}

void EventCanvasElement::TriggerEnd()
{
   if (mUIControl && mIsCheckbox)
       mUIControl->SetValue(0);
}

float EventCanvasElement::GetEnd() const
{
   if (mIsCheckbox)  //normal resizable element
      return CanvasElement::GetEnd();

   float size = 4;
   float span = mCanvas->mViewEnd - mCanvas->mViewStart;
   return GetStart() + size * span / mCanvas->GetWidth();
}

namespace
{
   const int kECESaveStateRev = 1;
}

void EventCanvasElement::SaveState(FileStreamOut& out)
{
   CanvasElement::SaveState(out);

   out << kECESaveStateRev;

   out << mValue;
}

void EventCanvasElement::LoadState(FileStreamIn& in)
{
   CanvasElement::LoadState(in);

   int rev;
   in >> rev;
   LoadStateValidate(rev <= kECESaveStateRev);

   in >> mValue;
   if (rev < 1)
   {
      std::string dummy;
      in >> dummy;
   }
}


/**
    bespoke synth, a software modular synthesizer

**/
//  CanvasElement.cpp
//  Bespoke
//  Created by Ryan Challinor on 1/4/15.

#include "CanvasElement.h"
#include "Sample.h"
#include "SynthGlobals.h"
#include "Slider.h"
#include "CanvasControls.h"
#include "TextEntry.h"
#include "PatchCableSource.h"
#include "ModularSynth.h"
#include "EventCanvas.h"
#include "SampleCanvas.h"

CanvasElement::CanvasElement(Canvas* canvas, int col, int row, float offset, float length)
: mCanvas(canvas)
, mOffset(offset)
, mLength(length)
, mCol(col)
, mRow(row)
, mHighlighted(false)
{
}

void CanvasElement::Draw(ofVec2f offset)
{
   if (offset.x == 0 && offset.y == 0)
   {
      DrawElement(K(clamp), !K(wrapped), offset);
      if (mCanvas->ShouldWrap() && GetEnd() > 1)
         DrawElement(K(clamp), K(wrapped), offset);
   }
   else
   {
      ofPushStyle();
      ofSetLineWidth(3.0f);
      ofNoFill();
      ofSetColor(255, 255, 255, ofMap(sin(gTime / 500 * PI * 2), -1, 1, 40, 120));
      ofRect(GetRectAtDestination(K(clamp), !K(wrapped), offset), 0);
      if (mCanvas->ShouldWrap() && GetEnd() > 1)
         ofRect(GetRectAtDestination(K(clamp), K(wrapped), offset), 0);
      ofPopStyle();

      DrawElement(!K(clamp), !K(wrapped), offset);
   }
}

void CanvasElement::DrawElement(bool clamp, bool wrapped, ofVec2f offset)
{
   ofRectangle rect = GetRect(clamp, wrapped, offset);
   ofRectangle fullRect = GetRect(false, wrapped, offset);

   if (rect.width < 0)
   {
      ofPushStyle();
      ofFill();
      ofSetColor(255, 0, 0);
      ofRect(rect.x + rect.width, rect.y, -rect.width, rect.height, 0);
      ofPopStyle();
   }

   ofPushStyle();
   DrawContents(clamp, wrapped, offset);
   ofPopStyle();

   if (mHighlighted)
   {
      ofPushStyle();
      ofNoFill();
      ofSetColor(255, 200, 0);
      ofSetLineWidth(.75f);

      ofLine(rect.x, rect.y, rect.x + rect.width, rect.y);
      ofLine(rect.x, rect.y + rect.height, rect.x + rect.width, rect.y + rect.height);

      if (fullRect.x >= 0)
      {
         ofPushStyle();
         if (mCanvas->GetHighlightEnd() == Canvas::kHighlightEnd_Start && IsResizable())
         {
            ofSetLineWidth(1.5f);
            ofSetColor(255, 100, 100);
         }
         ofLine(rect.x, rect.y, rect.x, rect.y + rect.height);
         ofPopStyle();
      }
      if (fullRect.x + fullRect.width <= mCanvas->GetLength() * mCanvas->GetWidth())
      {
         ofPushStyle();
         if (mCanvas->GetHighlightEnd() == Canvas::kHighlightEnd_End && IsResizable())
         {
            ofSetLineWidth(1.5f);
            ofSetColor(255, 100, 100);
         }
         ofLine(rect.x + rect.width, rect.y, rect.x + rect.width, rect.y + rect.height);
         ofPopStyle();
      }

      ofPopStyle();
   }
}

void CanvasElement::DrawOffscreen()
{
   ofPushStyle();
   ofSetLineWidth(1);
   ofSetColor(255, 255, 255);
   {
      ofRectangle rect = GetRect(K(clamp), !K(wrapped));
      if (rect.y < 0)
      {
         rect.y = 0;
         rect.height = 1;
      }
      if (rect.y + rect.height > mCanvas->GetHeight())
      {
         rect.y = mCanvas->GetHeight()-1;
         rect.height = 1;
      }
      rect.width = MAX(rect.width, 1);
      ofRect(rect);
   }
   if (mCanvas->ShouldWrap() && GetEnd() > 1)
   {
      ofRectangle rect = GetRect(K(clamp), K(wrapped));
      if (rect.y < 0)
      {
         rect.y = 0;
         rect.height = 1;
      }
      if (rect.y > mCanvas->GetHeight())
      {
         rect.y = mCanvas->GetHeight() - 1;
         rect.height = 1;
      }
      rect.width = MAX(rect.width, 1);
      ofRect(rect);
   }
   ofPopStyle();
}

float CanvasElement::GetStart(int col, float offset) const
{
   return (col + offset) / mCanvas->GetNumCols();
}

float CanvasElement::GetEnd(int col, float offset, float length) const
{
   return (col + offset + length) / mCanvas->GetNumCols();
}

float CanvasElement::GetStart() const
{
   return GetStart(mCol, mOffset);
}

void CanvasElement::SetStart(float start, bool preserveLength)
{
   float end;
   if (!preserveLength)
      end = GetEnd();
   start *= mCanvas->GetNumCols();
   mCol = int(start + .5f);
   mOffset = start - mCol;
   if (!preserveLength)
      SetEnd(end);
}

float CanvasElement::GetEnd() const
{
   return GetEnd(mCol, mOffset, mLength);
}

void CanvasElement::SetEnd(float end)
{
   mLength = end * mCanvas->GetNumCols() - mCol - mOffset;
}

ofRectangle CanvasElement::GetRect(bool clamp, bool wrapped, ofVec2f offset) const
{
   float wrapOffset = wrapped ? -1 : 0;
   float start = ofMap(GetStart() + wrapOffset,mCanvas->mViewStart/mCanvas->GetLength(),mCanvas->mViewEnd/mCanvas->GetLength(),0,1,clamp) * mCanvas->GetWidth();
   float end = ofMap(GetEnd() + wrapOffset,mCanvas->mViewStart/mCanvas->GetLength(),mCanvas->mViewEnd/mCanvas->GetLength(),0,1,clamp) * mCanvas->GetWidth();
   float y = (float(mRow-mCanvas->GetRowOffset()) / mCanvas->GetNumVisibleRows()) * mCanvas->GetHeight();
   float height = float(mCanvas->GetHeight()) / mCanvas->GetNumVisibleRows();

   return ofRectangle(start + offset.x, y + offset.y, end-start, height);
}

ofRectangle CanvasElement::GetRectAtDestination(bool clamp, bool wrapped, ofVec2f dragOffset) const
{
   int newRow;
   int newCol;
   float newOffset;
   GetDragDestinationData(dragOffset, newRow, newCol, newOffset);

   float wrapOffset = wrapped ? -1 : 0;
   float start = ofMap(GetStart(newCol, newOffset) + wrapOffset, mCanvas->mViewStart / mCanvas->GetLength(), mCanvas->mViewEnd / mCanvas->GetLength(), 0, 1, clamp) * mCanvas->GetWidth();
   float end = ofMap(GetEnd(newCol, newOffset, mLength) + wrapOffset, mCanvas->mViewStart / mCanvas->GetLength(), mCanvas->mViewEnd / mCanvas->GetLength(), 0, 1, clamp) * mCanvas->GetWidth();
   float y = (float(newRow - mCanvas->GetRowOffset()) / mCanvas->GetNumVisibleRows()) * mCanvas->GetHeight();
   float height = float(mCanvas->GetHeight()) / mCanvas->GetNumVisibleRows();

   return ofRectangle(start, y, end - start, height);
}

void CanvasElement::GetDragDestinationData(ofVec2f dragOffset, int& newRow, int& newCol, float& newOffset) const
{
   dragOffset.x *= mCanvas->mViewEnd - mCanvas->mViewStart;

   float colDrag = (dragOffset.x / mCanvas->GetWidth()) * mCanvas->GetNumCols() / mCanvas->GetLength();
   float rowDrag = (dragOffset.y / mCanvas->GetHeight()) * mCanvas->GetNumVisibleRows();

   newCol = mCol;
   if (mCanvas->GetDragMode() & Canvas::kDragHorizontal)
      newCol = ofClamp(int(mCol + colDrag + .5f), 0, mCanvas->GetNumCols() - 1);

   newRow = mRow;
   if (mCanvas->GetDragMode() & Canvas::kDragVertical)
      newRow = ofClamp(int(mRow + rowDrag + .5f), 0, mCanvas->GetNumRows() - 1);

   newOffset = mOffset;
   if (GetKeyModifiers() & kModifier_Alt)   //non-snapped drag
      newOffset = mOffset + colDrag - (newCol - mCol);
   if (GetKeyModifiers() & kModifier_Command)   //quantize
      newOffset = 0;
}

void CanvasElement::MoveElementByDrag(ofVec2f dragOffset)
{
   int newRow;
   int newCol;
   float newOffset;
   GetDragDestinationData(dragOffset, newRow, newCol, newOffset);

   mRow = newRow;
   mCol = newCol;
   mOffset = newOffset;
}

void CanvasElement::AddElementUIControl(IUIControl* control)
{
   mUIControls.push_back(control);
   control->SetShowing(false);
}

void CanvasElement::CheckboxUpdated(std::string label, bool value)
{
   for (auto* control : mUIControls)
   {
      if (control->Name() == label)
         control->SetValue(value);
   }
}

void CanvasElement::FloatSliderUpdated(std::string label, float oldVal, float newVal)
{
   for (auto* control : mUIControls)
   {
      if (control->Name() == label)
         control->SetValue(newVal);
   }
}

void CanvasElement::IntSliderUpdated(std::string label, int oldVal, float newVal)
{
   for (auto* control : mUIControls)
   {
      if (control->Name() == label)
         control->SetValue(newVal);
   }
}

void CanvasElement::ButtonClicked(std::string label)
{
}

namespace
{
   const int kCESaveStateRev = 1;
}

void CanvasElement::SaveState(FileStreamOut& out)
{
   out << kCESaveStateRev;

   out << mOffset;
   out << mLength;
}

void CanvasElement::LoadState(FileStreamIn& in)
{
   int rev;
   in >> rev;
   LoadStateValidate(rev <= kCESaveStateRev);

   if (rev < 1)
   {
      in >> mRow;
      in >> mCol;
   }
   in >> mOffset;
   in >> mLength;
}

NoteCanvasElement::NoteCanvasElement(Canvas* canvas, int col, int row, float offset, float length)
: CanvasElement(canvas,col,row,offset,length)
, mVelocity(.5f)
, mVoiceIdx(-1)
, mPan(0)
{
   if (canvas != nullptr && canvas->GetControls())
   {
      mElementOffsetSlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()), "offset", 0, 0, 100, 15, &mOffset, -1, 1);
      AddElementUIControl(mElementOffsetSlider);
      mElementLengthSlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()), "length", 0, 0, 100, 15, &mLength, 0, mCanvas->GetNumCols());
      AddElementUIControl(mElementLengthSlider);
      mElementRowSlider = new IntSlider(dynamic_cast<IIntSliderListener*>(canvas->GetControls()), "row", 0, 0, 100, 15, &mRow, 0, mCanvas->GetNumRows() - 1);
      AddElementUIControl(mElementRowSlider);
      mElementColSlider = new IntSlider(dynamic_cast<IIntSliderListener*>(canvas->GetControls()), "step", 0, 0, 100, 15, &mCol, 0, mCanvas->GetNumCols() - 1);
      AddElementUIControl(mElementColSlider);
      mVelocitySlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()), "velocity", 0, 0, 100, 15, &mVelocity, 0, 1);
      AddElementUIControl(mVelocitySlider);
   }
}

CanvasElement* NoteCanvasElement::CreateDuplicate() const
{
   NoteCanvasElement* element = new NoteCanvasElement(mCanvas, mCol, mRow, mOffset, mLength);
   element->mVelocity = mVelocity;
   element->mVoiceIdx = mVoiceIdx;
   return element;
}

void NoteCanvasElement::DrawContents(bool clamp, bool wrapped, ofVec2f offset)
{
   ofPushStyle();
   ofFill();

   ofRectangle rect = GetRect(clamp, wrapped, offset);
   float fullHeight = rect.height;
   rect.height *= mVelocity;
   rect.y += (fullHeight - rect.height) * .5f;
   if (rect.width > 0)
   {
      ofSetColorGradient(ofColor::white, ofColor(210,210,210), ofVec2f(ofLerp(rect.getMinX(),rect.getMaxX(),.5f), rect.y), ofVec2f(rect.getMaxX(), rect.y));
      ofRect(rect, 0);
   }

   /*ofSetLineWidth(1.5f * gDrawScale);

   rect = GetRect(clamp, wrapped);
   ofRectangle fullRect = GetRect(false, false);
   if (wrapped)
      fullRect.x -= mCanvas->GetWidth();
   float length = (GetEnd()-GetStart()) * mCanvas->GetLength();

   float start = (float(rect.x-fullRect.x)/fullRect.width);
   float end = ((float(rect.x+rect.width)-(fullRect.x))/fullRect.width);

   mPitchBendCurve.SetPosition(rect.x, rect.y - rect.height/2);
   mPitchBendCurve.SetDimensions(rect.width, rect.height);
   mPitchBendCurve.SetExtents(start*length, end*length);
   mPitchBendCurve.SetColor(ofColor::red);
   mPitchBendCurve.Render();

   mModWheelCurve.SetPosition(rect.x, rect.y);
   mModWheelCurve.SetDimensions(rect.width, rect.height);
   mModWheelCurve.SetExtents(start*length, end*length);
   mModWheelCurve.SetColor(ofColor::green);
   mModWheelCurve.Render();

   mPressureCurve.SetPosition(rect.x, rect.y);
   mPressureCurve.SetDimensions(rect.width, rect.height);
   mPressureCurve.SetExtents(start*length, end*length);
   mPressureCurve.SetColor(ofColor::blue);
   mPressureCurve.Render();*/

   ofPopStyle();
}

void NoteCanvasElement::UpdateModulation(float pos)
{
   float curveTime = (pos - GetStart()) * mCanvas->GetLength();
   FloatWrap(curveTime, mCanvas->GetLength());
   mPitchBend.SetValue(mPitchBendCurve.Evaluate(curveTime));
   mModWheel.SetValue(mModWheelCurve.Evaluate(curveTime));
   mPressure.SetValue(mPressureCurve.Evaluate(curveTime));
   mPan = mPanCurve.Evaluate(curveTime);
}

void NoteCanvasElement::WriteModulation(float pos, float pitchBend, float modWheel, float pressure, float pan)
{
   float curveTime = (pos - GetStart()) * mCanvas->GetLength();
   FloatWrap(curveTime, mCanvas->GetLength());
   m

PitchBendCurve.AddPoint(CurvePoint(curveTime, pitchBend));
   mModWheelCurve.AddPoint(CurvePoint(curveTime, modWheel));
   mPressureCurve.AddPoint(CurvePoint(curveTime, pressure));
   mPanCurve.AddPoint(CurvePoint(curveTime, pan));
}

namespace
{
   const int kNCESaveStateRev = 0;
}

void NoteCanvasElement::SaveState(FileStreamOut& out)
{
   CanvasElement::SaveState(out);

   out << kNCESaveStateRev;

   out << mVelocity;
}

void NoteCanvasElement::LoadState(FileStreamIn& in)
{
   CanvasElement::LoadState(in);

   int rev;
   in >> rev;
   LoadStateValidate(rev == kNCESaveStateRev);

   in >> mVelocity;
}

SampleCanvasElement::SampleCanvasElement(Canvas* canvas, int col, int row, float offset, float length)
: CanvasElement(canvas,col,row,offset,length)
, mSample(nullptr)
, mVolume(1)
, mMute(false)
{
   mElementOffsetSlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()),"offset",0,0,100,15,&mOffset,-1,1);
   AddElementUIControl(mElementOffsetSlider);
   mVolumeSlider = new FloatSlider(dynamic_cast<IFloatSliderListener*>(canvas->GetControls()),"volume",0,0,100,15,&mVolume,0,2);
   AddElementUIControl(mVolumeSlider);
   mMuteCheckbox = new Checkbox(dynamic_cast<IDrawableModule*>(canvas->GetControls()), "mute", 0, 0, &mMute);
   AddElementUIControl(mMuteCheckbox);
   mSplitSampleButton = new ClickButton(dynamic_cast<IButtonListener*>(canvas->GetControls()), "split", 0, 0);
   AddElementUIControl(mSplitSampleButton);
   mResetSpeedButton = new ClickButton(dynamic_cast<IButtonListener*>(canvas->GetControls()), "reset speed", 0, 0);
   AddElementUIControl(mResetSpeedButton);
}

SampleCanvasElement::~SampleCanvasElement()
{
   delete mSample;
}

CanvasElement* SampleCanvasElement::CreateDuplicate() const
{
   SampleCanvasElement* element = new SampleCanvasElement(mCanvas, mCol, mRow, mOffset, mLength);
   element->mSample = new Sample();
   element->mSample->Create(mSample->Data());
   element->mVolume = mVolume;
   element->mMute = mMute;
   return element;
}

void SampleCanvasElement::SetSample(Sample* sample)
{
   mSample = sample;
}

void SampleCanvasElement::CheckboxUpdated(std::string label, bool value)
{
   CanvasElement::CheckboxUpdated(label, value);
}

void SampleCanvasElement::ButtonClicked(std::string label)
{
   CanvasElement::ButtonClicked(label);
   if (label == "split")
   {
      ChannelBuffer* firstHalf = new ChannelBuffer(mSample->Data()->BufferSize() / 2);
      ChannelBuffer* secondHalf = new ChannelBuffer(mSample->Data()->BufferSize() / 2);
      firstHalf->CopyFrom(mSample->Data(), firstHalf->BufferSize(), 0);
      secondHalf->CopyFrom(mSample->Data(), secondHalf->BufferSize(), mSample->Data()->BufferSize() / 2);

      SampleCanvasElement* element = new SampleCanvasElement(mCanvas, mCol, mRow, mOffset, mLength);
      element->mSample = new Sample();
      element->mSample->Create(secondHalf);
      element->mVolume = mVolume;
      element->mMute = mMute;
      element->SetStart((GetStart() + GetEnd()) * .5f, false);
      mCanvas->AddElement(element);

      mSample->Create(firstHalf);
      mLength /= 2;
   }
   if (label == "reset speed")
   {
      SampleCanvas* sampleCanvas = dynamic_cast<SampleCanvas*>(mCanvas->GetParent());
      if (sampleCanvas != nullptr)
      {
         float lengthMs = mSample->LengthInSamples() / mSample->GetSampleRateRatio() / gSampleRateMs;
         float lengthOriginalSpeed = lengthMs / TheTransport->GetDuration(sampleCanvas->GetInterval());
         mLength = lengthOriginalSpeed;
      }
   }
}

void SampleCanvasElement::DrawContents(bool clamp, bool wrapped, ofVec2f offset)
{
   if (wrapped)
      return;

   ofRectangle rect = GetRect(false, false, offset);

   ofPushMatrix();
   ofTranslate(rect.x,rect.y);
   if (mSample)
   {
      float width = rect.width;
      DrawAudioBuffer(width, rect.height, mSample->Data(), 0, mSample->LengthInSamples(), -1);
   }
   else
   {
      ofSetColor(0,0,0);
      ofRect(0,0,rect.width,rect.height);
   }
   ofPopMatrix();

   if (rect.width < 0)
      rect.set(rect.x + rect.width, rect.y, -rect.width, rect.height);

   SampleCanvas* sampleCanvas = dynamic_cast<SampleCanvas*>(mCanvas->GetParent());
   if (sampleCanvas != nullptr && mSample != nullptr)
   {
      float lengthMs = mSample->LengthInSamples() / mSample->GetSampleRateRatio() / gSampleRateMs;
      float lengthOriginalSpeed = lengthMs / TheTransport->GetDuration(sampleCanvas->GetInterval());
      float speed = lengthOriginalSpeed / mLength;
      ofSetColor(255, 255, 255);
      DrawTextBold(ofToString(speed, 2), rect.x + 3, rect.y + 10, 12);
   }

   if (mMute)
   {
      ofSetColor(0, 0, 0, 140);
      ofFill();
      ofRect(rect);
   }

   ofSetColor(255, 255, 255);
   ofNoFill();
   ofRect(rect);
}

namespace
{
   const int kSCESaveStateRev = 2;
}

void SampleCanvasElement::SaveState(FileStreamOut& out)
{
   CanvasElement::SaveState(out);

   out << kSCESaveStateRev;

   bool hasSample = mSample != nullptr;
   out << hasSample;
   if (mSample != nullptr)
      mSample->SaveState(out);
   out << mVolume;
   out << mMute;
}

void SampleCanvasElement::LoadState(FileStreamIn& in)
{
   CanvasElement::LoadState(in);

   int rev;
   in >> rev;
   LoadStateValidate(rev <= kSCESaveStateRev);

   bool hasSample;
   in >> hasSample;
   if (hasSample)
   {
      mSample = new Sample();
      mSample->LoadState(in);
   }
   if (rev < 2)
   {
      int dummy;
      in >> dummy;
      in >> dummy;
   }
   in >> mVolume;
   if (rev == 0)
   {
      bool dummy;
      in >> dummy;
   }
   if (rev >= 1)
      in >> mMute;
}

EventCanvasElement::EventCanvasElement(Canvas* canvas, int col, int row, float offset)
: CanvasElement(canvas,col,row,offset,.5f)
, mValue(0)
{
   mValueEntry = new TextEntry(dynamic_cast<ITextEntryListener*>(canvas->GetControls()),"value",60,2,7,&mValue,-99999,99999);
   AddElementUIControl(mValueEntry);

   mEventCanvas = dynamic_cast<EventCanvas*>(canvas->GetControls()->GetParent());
   assert(mEventCanvas);
   mUIControl = mEventCanvas->GetUIControlForRow(row);
   mIsCheckbox = dynamic_cast<Checkbox*>(mUIControl) != nullptr;
   mIsButton = dynamic_cast<ClickButton*>(mUIControl) != nullptr;

   if (mUIControl)
      mValue = mUIControl->GetValue();
   if (mIsButton)
      mValue = 1;
}

EventCanvasElement::~EventCanvasElement()
{
}

CanvasElement* EventCanvasElement::CreateDuplicate() const
{
   EventCanvasElement* element = new EventCanvasElement(mCanvas, mCol, mRow, mOffset);
   element->mUIControl = mUIControl;
   element->mIsCheckbox = mIsCheckbox;
   element->mIsButton = mIsButton;
   element->mValue = mValue;
   return element;
}

void EventCanvasElement::DrawContents(bool clamp, bool wrapped, ofVec2f offset)
{
   if (wrapped)
      return;

   if (GetRect(false, false, offset).width != GetRect(clamp, false, offset).width)
      return;  //only draw text for fully visible elements

   ofSetColor(mEventCanvas->GetRowColor(mRow));
   ofFill();
   ofRect(GetRect(clamp, wrapped, offset), 0);

   std::string text;
   if (mIsCheckbox)
   {
      text = mUIControl->Name();
      ofRectangle rect = GetRect(clamp, false, offset);
      ofSetColor(0,0,0);
      DrawTextNormal(text, rect.x+4, rect.y+11);
      ofSetColor(255,255,255);
      DrawTextNormal(text, rect.x+3, rect.y+10);
   }
   else if (mUIControl)
   {
      text += mUIControl->Name();
      text += ":";
      text += mUIControl->GetDisplayValue(mValue);

      ofRectangle rect = GetRect(clamp, false, offset);
      ofSetColor(0,0,0);
      DrawTextNormal(text, rect.x+rect.width+1, rect.y+11);
      ofSetColor(255,255,255);
      DrawTextNormal(text, rect.x+rect.width, rect.y+10);
   }
}

void EventCanvasElement::SetUIControl(IUIControl* control)
{
   bool hadUIControl = mUIControl != nullptr;
   mUIControl = dynamic_cast<IUIControl*>(control);
   mIsCheckbox = dynamic_cast<Checkbox*>(control) != nullptr;
   mIsButton = dynamic_cast<ClickButton*>(control) != nullptr;
   if (mUIControl && !hadUIControl)
      mValue = mUIControl->GetValue();
   if (mIsButton)
      mValue = 1;
}

void EventCanvasElement::Trigger()
{
   if (mUIControl)
   {
      if (mIsCheckbox)
         mUIControl->SetValue(1);
      else
         mUIControl->SetValue(mValue);
   }
}

void EventCanvasElement::TriggerEnd()
{
   if (mUIControl && mIsCheckbox)
       mUIControl->SetValue(0);
}

float EventCanvasElement::GetEnd() const
{
   if (mIsCheckbox)  //normal resizable element
      return CanvasElement::GetEnd();

   float size = 4;
   float span = mCanvas->mViewEnd - mCanvas->mViewStart;
   return GetStart() + size * span / mCanvas->GetWidth();
}

namespace
{
   const int kECESaveStateRev = 1;
}

void EventCanvasElement::SaveState(FileStreamOut& out)
{
   CanvasElement::SaveState(out);

   out << kECESaveStateRev;

   out << mValue;
}

void EventCanvasElement::LoadState(FileStreamIn& in)
{
   CanvasElement::LoadState(in);

   int rev;
   in >> rev;
   LoadStateValidate(rev <= kECESaveStateRev);

   in >> mValue;
   if (rev < 1)
   {
      std::string dummy;
      in >> dummy;
   }
}


QTextOption::QTextOption()
    : QTextOption(Qt::AlignLeft)

/****************************************************************************
**
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: http://example.com and
** http://example.com.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qtextoption.h"
#include "qguiapplication.h"
#include "qlist.h"

QT_BEGIN_NAMESPACE

struct QTextOptionPrivate
{
    QList<QTextOption::Tab> tabStops;
};

/*!
    Constructs a text option with default properties for text.
    The text alignment property is set to Qt::AlignLeft. The
    word wrap property is set to QTextOption::WordWrap. The
    using of design metrics flag is set to false.
*/

{
    direction = Qt::LayoutDirectionAuto;
}

/*!
    Constructs a text option with the given \a alignment for text.
    The word wrap property is set to QTextOption::WordWrap. The using
    of design metrics flag is set to false.
*/
QTextOption::QTextOption(Qt::Alignment alignment)
    : align(alignment),
      wordWrap(QTextOption::WordWrap),
      design(false),
      unused(0),
      f(0),
      tab(-1),
      d(nullptr)
{
    direction = QGuiApplication::layoutDirection();
}

/*!
    Destroys the text option.
*/
QTextOption::~QTextOption()
{
    delete d;
}

/*!
    \fn QTextOption::QTextOption(const QTextOption &other)

    Construct a copy of the \a other text option.
*/
QTextOption::QTextOption(const QTextOption &o)
    : align(o.align),
      wordWrap(o.wordWrap),
      design(o.design),
      direction(o.direction),
      unused(o.unused),
      f(o.f),
      tab(o.tab),
      d(nullptr)
{
    if (o.d)
        d = new QTextOptionPrivate(*o.d);
}

/*!
    \fn QTextOption &QTextOption::operator=(const QTextOption &other)

    Returns \c true if the text option is the same as the \a other text option;
    otherwise returns \c false.
*/
QTextOption &QTextOption::operator=(const QTextOption &o)
{
    if (this == &o)
        return *this;

    QTextOptionPrivate* dNew = nullptr;
    if (o.d)
        dNew = new QTextOptionPrivate(*o.d);
    delete d;
    d = dNew;

    align = o.align;
    wordWrap = o.wordWrap;
    design = o.design;
    direction = o.direction;
    unused = o.unused;
    f = o.f;
    tab = o.tab;
    return *this;
}

/*!
    Sets the tab positions for the text layout to those specified by
    \a tabStops.

    \sa tabArray(), setTabStopDistance(), setTabs()
*/
void QTextOption::setTabArray(const QList<qreal> &tabStops)
{
    if (!d)
        d = new QTextOptionPrivate;
    QList<QTextOption::Tab> tabs;
    QTextOption::Tab tab;
    tabs.reserve(tabStops.count());
    for (qreal pos : tabStops) {
        tab.position = pos;
        tabs.append(tab);
    }
    d->tabStops = tabs;
}

/*!
    \since 4.4
    Sets the tab positions for the text layout to those specified by
    \a tabStops.

    \sa tabStopDistance()
*/
void QTextOption::setTabs(const QList<QTextOption::Tab> &tabStops)
{
    if (!d)
        d = new QTextOptionPrivate;
    d->tabStops = tabStops;
}

/*!
    Returns a list of tab positions defined for the text layout.

    \sa setTabArray(), tabStopDistance()
*/
QList<qreal> QTextOption::tabArray() const
{
    QList<qreal> answer;
    if (!d)
        return answer;

    answer.reserve(d->tabStops.count());
    QList<QTextOption::Tab>::ConstIterator iter = d->tabStops.constBegin();
    while(iter != d->tabStops.constEnd()) {
        answer.append( (*iter).position);
        ++iter;
    }
    return answer;
}

QList<QTextOption::Tab> QTextOption::tabs() const
{
    if (!d)
        return QList<QTextOption::Tab>();
    return d->tabStops;
}

/*!
    \class QTextOption
    \reentrant

    \brief The QTextOption class provides a description of general rich text
    properties.
    \inmodule QtGui

    \ingroup richtext-processing

    QTextOption is used to encapsulate common rich text properties in a single
    object. It contains information about text alignment, layout direction,
    word wrapping, and other standard properties associated with text rendering
    and layout.

    \sa QTextEdit, QTextDocument, QTextCursor
*/

/*!
    \enum QTextOption::WrapMode

    This enum describes how text is wrapped in a document.

    \value NoWrap       Text is not wrapped at all.
    \value WordWrap     Text is wrapped at word boundaries.
    \value ManualWrap   Same as QTextOption::NoWrap
    \value WrapAnywhere Text can be wrapped at any point on a line, even if
                        it occurs in the middle of a word.
    \value WrapAtWordBoundaryOrAnywhere If possible, wrapping occurs at a word
                        boundary; otherwise it will occur at the appropriate
                        point on the line, even in the middle of a word.
*/

/*!
  \fn void QTextOption::setUseDesignMetrics(bool enable)

    If \a enable is true then the layout will use design metrics;
    otherwise it will use the metrics of the paint device (which is
    the default behavior).

    \sa useDesignMetrics()
*/

/*!
  \fn bool QTextOption::useDesignMetrics() const

    Returns \c true if the layout uses design rather than device metrics;
    otherwise returns \c false.

    \sa setUseDesignMetrics()
*/

/*!
  \fn Qt::Alignment QTextOption::alignment() const

  Returns the text alignment defined by the option.

  \sa setAlignment()
*/

/*!
  \fn void QTextOption::setAlignment(Qt::Alignment alignment);

  Sets the option's text alignment to the specified \a alignment.

  \sa alignment()
*/

/*!
  \fn Qt::LayoutDirection QTextOption::textDirection() const

  Returns the direction of the text layout defined by the option.

  \sa setTextDirection()
*/

/*!
  \fn void QTextOption::setTextDirection(Qt::LayoutDirection direction)

  Sets the direction of the text layout defined by the option to the
  given \a direction.

  \sa textDirection()
*/

/*!
  \fn WrapMode QTextOption::wrapMode() const

  Returns the text wrap mode defined by the option.

  \sa setWrapMode()
*/

/*!
  \fn void QTextOption::setWrapMode(WrapMode mode)

  Sets the option's text wrap mode to the given \a mode.
*/

/*!
  \enum QTextOption::Flag

  \value IncludeTrailingSpaces When this option is set, QTextLine::naturalTextWidth() and naturalTextRect() will
                               return a value that includes the width of trailing spaces in the text; otherwise
                               this width is excluded.
  \value ShowTabsAndSpaces Visualize spaces with little dots, and tabs with little arrows. Non-breaking spaces are
            shown differently to breaking spaces.
  \value ShowLineAndParagraphSeparators Visualize line and paragraph separators with appropriate symbol characters.
  \value ShowDocumentTerminator Visualize the end of the document with a section sign. This enum value was added
            in Qt 5.7.
  \value AddSpaceForLineAndParagraphSeparators While determining the line-break positions take into account the
            space added for drawing a separator character.
  \value SuppressColors Suppress all color changes in the character formats (except the main selection).
*/

/*!
  \fn Flags QTextOption::flags() const

  Returns the flags associated with the option.

  \sa setFlags()
*/

/*!
  \fn void QTextOption::setFlags(Flags flags)

  Sets the flags associated with the option to the given \a flags.

  \sa flags()
*/

/*!
  \fn qreal QTextOption::tabStopDistance() const
  \since 5.10

  Returns the distance in device units between tab stops.

  \sa setTabStopDistance(), tabArray(), setTabs(), tabs()
*/

/*!
  \fn void QTextOption::setTabStopDistance(qreal tabStopDistance)
  \since 5.10

  Sets the default distance in device units between tab stops to the value specified
  by \a tabStopDistance.

  \sa tabStopDistance(), setTabArray(), setTabs(), tabs()
*/

/*!
    \enum QTextOption::TabType
    \since 4.4

    This enum holds the different types of tabulator

    \value LeftTab      A left-tab
    \value RightTab     A right-tab
    \value CenterTab    A centered-tab
    \value DelimiterTab A tab stopping at a certain delimiter-character
*/

/*!
    \class QTextOption::Tab
    \since 4.4
    \inmodule QtGui
    Each tab definition is represented by this struct.
*/

/*!
    \variable Tab::position
    Distance from the start of the paragraph.
    The position of a tab is from the start of the paragraph which implies that when
    the alignment of the paragraph is set to centered, the tab is interpreted to be
    moved the same distance as the left edge of the paragraph does.
    In case the paragraph is set to have a layoutDirection() RightToLeft the position
    is interpreted to be from the right side of the paragraph with higher numbers moving
    the tab to the left.
*/

/*!
    \variable QTextOption::Tab::type
    Determine which type is used.
    In a paragraph that has layoutDirection() RightToLeft the type LeftTab will
    be interpreted to be a RightTab and vice versa.
*/

/*!
    \variable QTextOption::Tab::delimiter
    If type is DelimitorTab; tab until this char is found in the text.
*/

/*!
    \fn QTextOption::Tab::Tab()
    Creates a default left tab with position 80.
*/

/*!
    \fn QTextOption::Tab::Tab(qreal pos, TabType tabType, QChar delim = QChar())

    Creates a tab with the given position, tab type, and delimiter
    (\a pos, \a tabType, \a delim).

    \note \a delim is only used when \a tabType is DelimiterTab.

    \since 4.7
*/

/*!
    \fn bool QTextOption::Tab::operator==(const QTextOption::Tab &other) const

    Returns \c true if tab \a other is equal to this tab;
    otherwise returns \c false.
*/

/*!
    \fn bool QTextOption::Tab::operator!=(const QTextOption::Tab &other) const

    Returns \c true if tab \a other is not equal to this tab;
    otherwise returns \c false.
*/

/*!
  \since 4.4
  \fn QList<QTextOption::Tab> QTextOption::tabs() const
  Returns a list of tab positions defined for the text layout.

  \sa tabStopDistance(), setTabs(), setTabStopDistance()
*/

QT_END_NAMESPACE


{
    if (!d)
        d = new QTextOptionPrivate;
    QList<QTextOption::Tab> tabs;
    QTextOption::Tab tab;
    tabs.reserve(tabStops.count());
    for (qreal pos : tabStops) {
        tab.position = pos;
        tabs.append(tab);
    }
    d->tabStops = tabs;
}

/****************************************************************************
**
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: http://example.com and
** http://example.com.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qtextoption.h"
#include "qguiapplication.h"
#include "qlist.h"

QT_BEGIN_NAMESPACE

struct QTextOptionPrivate
{
    QList<QTextOption::Tab> tabStops;
};

/*!
    Constructs a text option with default properties for text.
    The text alignment property is set to Qt::AlignLeft. The
    word wrap property is set to QTextOption::WordWrap. The
    using of design metrics flag is set to false.
*/
QTextOption::QTextOption()
    : QTextOption(Qt::AlignLeft)
{
    direction = Qt::LayoutDirectionAuto;
}

/*!
    Constructs a text option with the given \a alignment for text.
    The word wrap property is set to QTextOption::WordWrap. The using
    of design metrics flag is set to false.
*/
QTextOption::QTextOption(Qt::Alignment alignment)
    : align(alignment),
      wordWrap(QTextOption::WordWrap),
      design(false),
      unused(0),
      f(0),
      tab(-1),
      d(nullptr)
{
    direction = QGuiApplication::layoutDirection();
}

/*!
    Destroys the text option.
*/
QTextOption::~QTextOption()
{
    delete d;
}

/*!
    \fn QTextOption::QTextOption(const QTextOption &other)

    Construct a copy of the \a other text option.
*/
QTextOption::QTextOption(const QTextOption &o)
    : align(o.align),
      wordWrap(o.wordWrap),
      design(o.design),
      direction(o.direction),
      unused(o.unused),
      f(o.f),
      tab(o.tab),
      d(nullptr)
{
    if (o.d)
        d = new QTextOptionPrivate(*o.d);
}

/*!
    \fn QTextOption &QTextOption::operator=(const QTextOption &other)

    Returns \c true if the text option is the same as the \a other text option;
    otherwise returns \c false.
*/
QTextOption &QTextOption::operator=(const QTextOption &o)
{
    if (this == &o)
        return *this;

    QTextOptionPrivate* dNew = nullptr;
    if (o.d)
        dNew = new QTextOptionPrivate(*o.d);
    delete d;
    d = dNew;

    align = o.align;
    wordWrap = o.wordWrap;
    design = o.design;
    direction = o.direction;
    unused = o.unused;
    f = o.f;
    tab = o.tab;
    return *this;
}

/*!
    Sets the tab positions for the text layout to those specified by
    \a tabStops.

    \sa tabArray(), setTabStopDistance(), setTabs()
*/
void QTextOption::setTabArray(const QList<qreal> &tabStops)

/*!
    \since 4.4
    Sets the tab positions for the text layout to those specified by
    \a tabStops.

    \sa tabStopDistance()
*/
void QTextOption::setTabs(const QList<QTextOption::Tab> &tabStops)
{
    if (!d)
        d = new QTextOptionPrivate;
    d->tabStops = tabStops;
}

/*!
    Returns a list of tab positions defined for the text layout.

    \sa setTabArray(), tabStopDistance()
*/
QList<qreal> QTextOption::tabArray() const
{
    QList<qreal> answer;
    if (!d)
        return answer;

    answer.reserve(d->tabStops.count());
    QList<QTextOption::Tab>::ConstIterator iter = d->tabStops.constBegin();
    while(iter != d->tabStops.constEnd()) {
        answer.append( (*iter).position);
        ++iter;
    }
    return answer;
}

QList<QTextOption::Tab> QTextOption::tabs() const
{
    if (!d)
        return QList<QTextOption::Tab>();
    return d->tabStops;
}

/*!
    \class QTextOption
    \reentrant

    \brief The QTextOption class provides a description of general rich text
    properties.
    \inmodule QtGui

    \ingroup richtext-processing

    QTextOption is used to encapsulate common rich text properties in a single
    object. It contains information about text alignment, layout direction,
    word wrapping, and other standard properties associated with text rendering
    and layout.

    \sa QTextEdit, QTextDocument, QTextCursor
*/

/*!
    \enum QTextOption::WrapMode

    This enum describes how text is wrapped in a document.

    \value NoWrap       Text is not wrapped at all.
    \value WordWrap     Text is wrapped at word boundaries.
    \value ManualWrap   Same as QTextOption::NoWrap
    \value WrapAnywhere Text can be wrapped at any point on a line, even if
                        it occurs in the middle of a word.
    \value WrapAtWordBoundaryOrAnywhere If possible, wrapping occurs at a word
                        boundary; otherwise it will occur at the appropriate
                        point on the line, even in the middle of a word.
*/

/*!
  \fn void QTextOption::setUseDesignMetrics(bool enable)

    If \a enable is true then the layout will use design metrics;
    otherwise it will use the metrics of the paint device (which is
    the default behavior).

    \sa useDesignMetrics()
*/

/*!
  \fn bool QTextOption::useDesignMetrics() const

    Returns \c true if the layout uses design rather than device metrics;
    otherwise returns \c false.

    \sa setUseDesignMetrics()
*/

/*!
  \fn Qt::Alignment QTextOption::alignment() const

  Returns the text alignment defined by the option.

  \sa setAlignment()
*/

/*!
  \fn void QTextOption::setAlignment(Qt::Alignment alignment);

  Sets the option's text alignment to the specified \a alignment.

  \sa alignment()
*/

/*!
  \fn Qt::LayoutDirection QTextOption::textDirection() const

  Returns the direction of the text layout defined by the option.

  \sa setTextDirection()
*/

/*!
  \fn void QTextOption::setTextDirection(Qt::LayoutDirection direction)

  Sets the direction of the text layout defined by the option to the
  given \a direction.

  \sa textDirection()
*/

/*!
  \fn WrapMode QTextOption::wrapMode() const

  Returns the text wrap mode defined by the option.

  \sa setWrapMode()
*/

/*!
  \fn void QTextOption::setWrapMode(WrapMode mode)

  Sets the option's text wrap mode to the given \a mode.
*/

/*!
  \enum QTextOption::Flag

  \value IncludeTrailingSpaces When this option is set, QTextLine::naturalTextWidth() and naturalTextRect() will
                               return a value that includes the width of trailing spaces in the text; otherwise
                               this width is excluded.
  \value ShowTabsAndSpaces Visualize spaces with little dots, and tabs with little arrows. Non-breaking spaces are
            shown differently to breaking spaces.
  \value ShowLineAndParagraphSeparators Visualize line and paragraph separators with appropriate symbol characters.
  \value ShowDocumentTerminator Visualize the end of the document with a section sign. This enum value was added
            in Qt 5.7.
  \value AddSpaceForLineAndParagraphSeparators While determining the line-break positions take into account the
            space added for drawing a separator character.
  \value SuppressColors Suppress all color changes in the character formats (except the main selection).
*/

/*!
  \fn Flags QTextOption::flags() const

  Returns the flags associated with the option.

  \sa setFlags()
*/

/*!
  \fn void QTextOption::setFlags(Flags flags)

  Sets the flags associated with the option to the given \a flags.

  \sa flags()
*/

/*!
  \fn qreal QTextOption::tabStopDistance() const
  \since 5.10

  Returns the distance in device units between tab stops.

  \sa setTabStopDistance(), tabArray(), setTabs(), tabs()
*/

/*!
  \fn void QTextOption::setTabStopDistance(qreal tabStopDistance)
  \since 5.10

  Sets the default distance in device units between tab stops to the value specified
  by \a tabStopDistance.

  \sa tabStopDistance(), setTabArray(), setTabs(), tabs()
*/

/*!
    \enum QTextOption::TabType
    \since 4.4

    This enum holds the different types of tabulator

    \value LeftTab      A left-tab
    \value RightTab     A right-tab
    \value CenterTab    A centered-tab
    \value DelimiterTab A tab stopping at a certain delimiter-character
*/

/*!
    \class QTextOption::Tab
    \since 4.4
    \inmodule QtGui
    Each tab definition is represented by this struct.
*/

/*!
    \variable Tab::position
    Distance from the start of the paragraph.
    The position of a tab is from the start of the paragraph which implies that when
    the alignment of the paragraph is set to centered, the tab is interpreted to be
    moved the same distance as the left edge of the paragraph does.
    In case the paragraph is set to have a layoutDirection() RightToLeft the position
    is interpreted to be from the right side of the paragraph with higher numbers moving
    the tab to the left.
*/

/*!
    \variable QTextOption::Tab::type
    Determine which type is used.
    In a paragraph that has layoutDirection() RightToLeft the type LeftTab will
    be interpreted to be a RightTab and vice versa.
*/

/*!
    \variable QTextOption::Tab::delimiter
    If type is DelimitorTab; tab until this char is found in the text.
*/

/*!
    \fn QTextOption::Tab::Tab()
    Creates a default left tab with position 80.
*/

/*!
    \fn QTextOption::Tab::Tab(qreal pos, TabType tabType, QChar delim = QChar())

    Creates a tab with the given position, tab type, and delimiter
    (\a pos, \a tabType, \a delim).

    \note \a delim is only used when \a tabType is DelimiterTab.

    \since 4.7
*/

/*!
    \fn bool QTextOption::Tab::operator==(const QTextOption::Tab &other) const

    Returns \c true if tab \a other is equal to this tab;
    otherwise returns \c false.
*/

/*!
    \fn bool QTextOption::Tab::operator!=(const QTextOption::Tab &other) const

    Returns \c true if tab \a other is not equal to this tab;
    otherwise returns \c false.
*/

/*!
  \since 4.4
  \fn QList<QTextOption::Tab> QTextOption::tabs() const
  Returns a list of tab positions defined for the text layout.

  \sa tabStopDistance(), setTabs(), setTabStopDistance()
*/

QT_END_NAMESPACE


{
    if (this == &o)
        return *this;

    QTextOptionPrivate* dNew = nullptr;
    if (o.d)
        dNew = new QTextOptionPrivate(*o.d);
    delete d;

/****************************************************************************
**
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: http://example.com and
** http://example.com.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qtextoption.h"
#include "qguiapplication.h"
#include "qlist.h"

QT_BEGIN_NAMESPACE

struct QTextOptionPrivate
{
    QList<QTextOption::Tab> tabStops;
};

/*!
    Constructs a text option with default properties for text.
    The text alignment property is set to Qt::AlignLeft. The
    word wrap property is set to QTextOption::WordWrap. The
    using of design metrics flag is set to false.
*/
QTextOption::QTextOption()
    : QTextOption(Qt::AlignLeft)
{
    direction = Qt::LayoutDirectionAuto;
}

/*!
    Constructs a text option with the given \a alignment for text.
    The word wrap property is set to QTextOption::WordWrap. The using
    of design metrics flag is set to false.
*/
QTextOption::QTextOption(Qt::Alignment alignment)
    : align(alignment),
      wordWrap(QTextOption::WordWrap),
      design(false),
      unused(0),
      f(0),
      tab(-1),
      d(nullptr)
{
    direction = QGuiApplication::layoutDirection();
}

/*!
    Destroys the text option.
*/
QTextOption::~QTextOption()
{
    delete d;
}

/*!
    \fn QTextOption::QTextOption(const QTextOption &other)

    Construct a copy of the \a other text option.
*/
QTextOption::QTextOption(const QTextOption &o)
    : align(o.align),
      wordWrap(o.wordWrap),
      design(o.design),
      direction(o.direction),
      unused(o.unused),
      f(o.f),
      tab(o.tab),
      d(nullptr)
{
    if (o.d)
        d = new QTextOptionPrivate(*o.d);
}

/*!
    \fn QTextOption &QTextOption::operator=(const QTextOption &other)

    Returns \c true if the text option is the same as the \a other text option;
    otherwise returns \c false.
*/
QTextOption &QTextOption::operator=(const QTextOption &o)

    d = dNew;

    align = o.align;
    wordWrap = o.wordWrap;
    design = o.design;
    direction = o.direction;
    unused = o.unused;
    f = o.f;
    tab = o.tab;
    return *this;
}

/*!
    Sets the tab positions for the text layout to those specified by
    \a tabStops.

    \sa tabArray(), setTabStopDistance(), setTabs()
*/
void QTextOption::setTabArray(const QList<qreal> &tabStops)
{
    if (!d)
        d = new QTextOptionPrivate;
    QList<QTextOption::Tab> tabs;
    QTextOption::Tab tab;
    tabs.reserve(tabStops.count());
    for (qreal pos : tabStops) {
        tab.position = pos;
        tabs.append(tab);
    }
    d->tabStops = tabs;
}

/*!
    \since 4.4
    Sets the tab positions for the text layout to those specified by
    \a tabStops.

    \sa tabStopDistance()
*/
void QTextOption::setTabs(const QList<QTextOption::Tab> &tabStops)
{
    if (!d)
        d = new QTextOptionPrivate;
    d->tabStops = tabStops;
}

/*!
    Returns a list of tab positions defined for the text layout.

    \sa setTabArray(), tabStopDistance()
*/
QList<qreal> QTextOption::tabArray() const
{
    QList<qreal> answer;
    if (!d)
        return answer;

    answer.reserve(d->tabStops.count());
    QList<QTextOption::Tab>::ConstIterator iter = d->tabStops.constBegin();
    while(iter != d->tabStops.constEnd()) {
        answer.append( (*iter).position);
        ++iter;
    }
    return answer;
}

QList<QTextOption::Tab> QTextOption::tabs() const
{
    if (!d)
        return QList<QTextOption::Tab>();
    return d->tabStops;
}

/*!
    \class QTextOption
    \reentrant

    \brief The QTextOption class provides a description of general rich text
    properties.
    \inmodule QtGui

    \ingroup richtext-processing

    QTextOption is used to encapsulate common rich text properties in a single
    object. It contains information about text alignment, layout direction,
    word wrapping, and other standard properties associated with text rendering
    and layout.

    \sa QTextEdit, QTextDocument, QTextCursor
*/

/*!
    \enum QTextOption::WrapMode

    This enum describes how text is wrapped in a document.

    \value NoWrap       Text is not wrapped at all.
    \value WordWrap     Text is wrapped at word boundaries.
    \value ManualWrap   Same as QTextOption::NoWrap
    \value WrapAnywhere Text can be wrapped at any point on a line, even if
                        it occurs in the middle of a word.
    \value WrapAtWordBoundaryOrAnywhere If possible, wrapping occurs at a word
                        boundary; otherwise it will occur at the appropriate
                        point on the line, even in the middle of a word.
*/

/*!
  \fn void QTextOption::setUseDesignMetrics(bool enable)

    If \a enable is true then the layout will use design metrics;
    otherwise it will use the metrics of the paint device (which is
    the default behavior).

    \sa useDesignMetrics()
*/

/*!
  \fn bool QTextOption::useDesignMetrics() const

    Returns \c true if the layout uses design rather than device metrics;
    otherwise returns \c false.

    \sa setUseDesignMetrics()
*/

/*!
  \fn Qt::Alignment QTextOption::alignment() const

  Returns the text alignment defined by the option.

  \sa setAlignment()
*/

/*!
  \fn void QTextOption::setAlignment(Qt::Alignment alignment);

  Sets the option's text alignment to the specified \a alignment.

  \sa alignment()
*/

/*!
  \fn Qt::LayoutDirection QTextOption::textDirection() const

  Returns the direction of the text layout defined by the option.

  \sa setTextDirection()
*/

/*!
  \fn void QTextOption::setTextDirection(Qt::LayoutDirection direction)

  Sets the direction of the text layout defined by the option to the
  given \a direction.

  \sa textDirection()
*/

/*!
  \fn WrapMode QTextOption::wrapMode() const

  Returns the text wrap mode defined by the option.

  \sa setWrapMode()
*/

/*!
  \fn void QTextOption::setWrapMode(WrapMode mode)

  Sets the option's text wrap mode to the given \a mode.
*/

/*!
  \enum QTextOption::Flag

  \value IncludeTrailingSpaces When this option is set, QTextLine::naturalTextWidth() and naturalTextRect() will
                               return a value that includes the width of trailing spaces in the text; otherwise
                               this width is excluded.
  \value ShowTabsAndSpaces Visualize spaces with little dots, and tabs with little arrows. Non-breaking spaces are
            shown differently to breaking spaces.
  \value ShowLineAndParagraphSeparators Visualize line and paragraph separators with appropriate symbol characters.
  \value ShowDocumentTerminator Visualize the end of the document with a section sign. This enum value was added
            in Qt 5.7.
  \value AddSpaceForLineAndParagraphSeparators While determining the line-break positions take into account the
            space added for drawing a separator character.
  \value SuppressColors Suppress all color changes in the character formats (except the main selection).
*/

/*!
  \fn Flags QTextOption::flags() const

  Returns the flags associated with the option.

  \sa setFlags()
*/

/*!
  \fn void QTextOption::setFlags(Flags flags)

  Sets the flags associated with the option to the given \a flags.

  \sa flags()
*/

/*!
  \fn qreal QTextOption::tabStopDistance() const
  \since 5.10

  Returns the distance in device units between tab stops.

  \sa setTabStopDistance(), tabArray(), setTabs(), tabs()
*/

/*!
  \fn void QTextOption::setTabStopDistance(qreal tabStopDistance)
  \since 5.10

  Sets the default distance in device units between tab stops to the value specified
  by \a tabStopDistance.

  \sa tabStopDistance(), setTabArray(), setTabs(), tabs()
*/

/*!
    \enum QTextOption::TabType
    \since 4.4

    This enum holds the different types of tabulator

    \value LeftTab      A left-tab
    \value RightTab     A right-tab
    \value CenterTab    A centered-tab
    \value DelimiterTab A tab stopping at a certain delimiter-character
*/

/*!
    \class QTextOption::Tab
    \since 4.4
    \inmodule QtGui
    Each tab definition is represented by this struct.
*/

/*!
    \variable Tab::position
    Distance from the start of the paragraph.
    The position of a tab is from the start of the paragraph which implies that when
    the alignment of the paragraph is set to centered, the tab is interpreted to be
    moved the same distance as the left edge of the paragraph does.
    In case the paragraph is set to have a layoutDirection() RightToLeft the position
    is interpreted to be from the right side of the paragraph with higher numbers moving
    the tab to the left.
*/

/*!
    \variable QTextOption::Tab::type
    Determine which type is used.
    In a paragraph that has layoutDirection() RightToLeft the type LeftTab will
    be interpreted to be a RightTab and vice versa.
*/

/*!
    \variable QTextOption::Tab::delimiter
    If type is DelimitorTab; tab until this char is found in the text.
*/

/*!
    \fn QTextOption::Tab::Tab()
    Creates a default left tab with position 80.
*/

/*!
    \fn QTextOption::Tab::Tab(qreal pos, TabType tabType, QChar delim = QChar())

    Creates a tab with the given position, tab type, and delimiter
    (\a pos, \a tabType, \a delim).

    \note \a delim is only used when \a tabType is DelimiterTab.

    \since 4.7
*/

/*!
    \fn bool QTextOption::Tab::operator==(const QTextOption::Tab &other) const

    Returns \c true if tab \a other is equal to this tab;
    otherwise returns \c false.
*/

/*!
    \fn bool QTextOption::Tab::operator!=(const QTextOption::Tab &other) const

    Returns \c true if tab \a other is not equal to this tab;
    otherwise returns \c false.
*/

/*!
  \since 4.4
  \fn QList<QTextOption::Tab> QTextOption::tabs() const
  Returns a list of tab positions defined for the text layout.

  \sa tabStopDistance(), setTabs(), setTabStopDistance()
*/

QT_END_NAMESPACE