text: /*
    ==============================================================================
  
     This file is part of the JUCE library.
     Copyright (c) 2017 - ROLI Ltd.
  
     JUCE is an open source library subject to commercial or open-source
     licensing.
  
     By using JUCE, you agree to the terms of both the JUCE 5 End-User License
     Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
     27th April 2017).
  
     End User License Agreement: www.juce.com/juce-5-licence
     Privacy Policy: www.juce.com/juce-5-privacy-policy
  
     Or: You may also use this code under the terms of the GPL v3 (see
     www.gnu.org/licenses).
  
     JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
     EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
     DISCLAIMED.
  
    ==============================================================================
  */
  
  
  /**
      This component runs in a slave process, draws the part of the canvas that this
      particular client covers, and updates itself when messages arrive from the master
      containing new canvas states.
  */
  class SlaveCanvasComponent  : public Component,
                                private OSCSender,
                                private OSCReceiver,
                                private OSCReceiver::Listener<OSCReceiver::RealtimeCallback>,
                                private AsyncUpdater,
                                private Timer
  {
  public:
      SlaveCanvasComponent (PropertiesFile& p, int windowIndex)  : properties (p)
      {
          {
              String uuidPropName ("UUID" + String (windowIndex));
              clientName = properties.getValue (uuidPropName);
  
              if (clientName.isEmpty())
              {
                  clientName = "CLIENT_" + String (Random().nextInt (10000)).toUpperCase();
                  properties.setValue (uuidPropName, clientName);
              }
          }
  
          setOpaque (true);
          setSize (1500, 900);
  
          if (! OSCSender::connect (getBroadcastIPAddress(), clientPortNumber))
              error = "Client app OSC sender: network connection error.";
  
          if (! OSCReceiver::connect (masterPortNumber))
              error = "Client app OSC receiver: network connection error.";
  
          OSCReceiver::addListener (this);
  
          timerCallback();
          startTimer (2000);
      }
  
      ~SlaveCanvasComponent()
      {
          OSCReceiver::removeListener (this);
      }
  
  private:
      void mouseDrag (const MouseEvent& e) override
      {
          auto clientArea = getAreaInGlobalSpace();
  
          if (! clientArea.isEmpty())
          {
              OSCMessage message (userInputOSCAddress);
  
              message.addString (clientName);
              message.addFloat32 (e.position.x * clientArea.getWidth()  / getWidth()  + clientArea.getX());
              message.addFloat32 (e.position.y * clientArea.getHeight() / getHeight() + clientArea.getY());
  
              send (message);
          }
      }
  
      //==============================================================================
      void oscMessageReceived (const OSCMessage& message) override
      {
          auto address = message.getAddressPattern();
  
          if (address.matches (canvasStateOSCAddress))
              canvasStateOSCMessageReceived (message);
      }
  
      struct NewStateMessage  : public Message
      {
          NewStateMessage (const MemoryBlock& d) : data (d) {}
          MemoryBlock data;
      };
  
      void canvasStateOSCMessageReceived (const OSCMessage& message)
      {
          if (message.isEmpty() || ! message[0].isBlob())
              return;
  
          if (packetiser.appendIncomingBlock (message[0].getBlob()))
          {
              const ScopedLock sl (canvasLock);
  
              MemoryBlock newCanvasData;
  
              if (packetiser.reassemble (newCanvasData))
              {
                  MemoryInputStream i (newCanvasData.getData(), newCanvasData.getSize(), false);
                  canvas2.load (i);
                  triggerAsyncUpdate();
              }
          }
      }
  
      //==============================================================================
      String getMachineInfoToDisplay() const
      {
          auto display = Desktop::getInstance().getDisplays().findDisplayForPoint (getScreenBounds().getCentre());
          return getOSName() + "   " + String (display.dpi) + "   "  + String (display.scale);
      }
  
      static String getOSName()
      {
         #if JUCE_MAC
          return "Mac OSX";
         #elif JUCE_ANDROID
          return "Android";
         #elif JUCE_IOS
          return "iOS";
         #elif JUCE_WINDOWS
          return "Windows";
         #elif JUCE_LINUX
          return "Linux";
         #endif
      }
  
      void paint (Graphics& g) override
      {
          g.fillAll (canvas.backgroundColour);
  
          auto clientArea = getAreaInGlobalSpace();
  
          if (clientArea.isEmpty())
          {
              g.setColour (Colours::red.withAlpha (0.5f));
              g.setFont (20.0f);
              g.drawText ("Not Connected", getLocalBounds(), Justification::centred, false);
              return;
          }
  
          canvas.draw (g, getLocalBounds().toFloat(), clientArea);
  
          g.setFont (Font (34.0f));
          g.setColour (Colours::white.withAlpha (0.6f));
  
          g.drawText (getMachineInfoToDisplay(),
                      getLocalBounds().reduced (10).removeFromBottom (20),
                      Justification::centredRight, true);
  
          if (error.isNotEmpty())
          {
              g.setColour (Colours::red);
              g.drawText (error, getLocalBounds().reduced (10).removeFromBottom (80),
                          Justification::centredRight, true);
          }
      }
  
      Rectangle<float> getAreaInGlobalSpace() const
      {
          if (auto client = canvas.findClient (clientName))
          {
              auto screenBounds = getScreenBounds();
              auto display = Desktop::getInstance().getDisplays().findDisplayForPoint (screenBounds.getCentre());
              return ((screenBounds - display.userArea.getCentre()).toFloat() / (client->scaleFactor * display.dpi / display.scale)) + client->centre;
          }
  
          return {};
      }
  
      Rectangle<float> getScreenAreaInGlobalSpace() const
      {
          if (auto client = canvas.findClient (clientName))
          {
              auto display = Desktop::getInstance().getDisplays().findDisplayForPoint (getScreenBounds().getCentre());
              return (display.userArea.toFloat() / (client->scaleFactor * display.dpi / display.scale)).withCentre (client->centre);
          }
  
          return {};
      }
  
      void timerCallback() override
      {
          send (newClientOSCAddress, clientName + ":" + IPAddress::getLocalAddress().toString()
                                                + ":" + getScreenAreaInGlobalSpace().toString());
      }
  
      void handleAsyncUpdate() override
      {
          const ScopedLock sl (canvasLock);
          canvas.swapWith (canvas2);
          repaint();
      }
  
      SharedCanvasDescription canvas, canvas2;
      PropertiesFile& properties;
      String clientName, error;
  
      CriticalSection canvasLock;
      BlockPacketiser packetiser;
  
      JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SlaveCanvasComponent)
  };
  