.. _knobs-writing_knobs:

Creating custom knobs
=====================

Creating a custom knob allows you to pull off one or more of the following:

* Store arbitrary data into the NUKE scripts.
* Present open gl interface handles in the viewer, with which the user can optionally interact.
* Post NUKE 6.3v1, draw Qt based user interface widgets to the node's user interface panel.
* Provide a Python interface into the C++ plug-in

This section is broken up into essentially these main topic areas, prefixed by a short breakdown of the knob architecture itself.

Knob architecture
-----------------

.. cpp:class:: DD::Image::Knob

  It is possible to create custom knobs within plugins by inheriting from this class.
  This can be necessary if the Op/Iop needs to keep
  around state that is not representable with one of the built-in knobs.   

  To include a custom Knob in your Op, you would for example do the following in your ``knobs`` function::

    CustomKnob1(DemoKnob, f, &_data, "data");

  This would invoke your knob constructor as follows::

    new DemoKnob(&f, &_data, "data");

  If you do not wish to specify a data pointer, you can use ``CustomKnob0``.  If you wish to specify
  a label as well as a name, you can use ``CustomKnob2``.

.. cpp:function:: bool DD::Image::Knob::not_default() const

  Knobs are written out as part of the saved script (or when copied to the clipboard)
  only if the not_default() function on them returns true.  Otherwise, they are assumed
  to still be at their default value, and not be worth writing out.

  A knob's default value can per-knob rather than per-knob-class.  It is entirely up to
  the knob how this is handled, except that it should remain invariant for the lifetime of
  that knob.

  It is possible simply to always return true from this.

.. cpp:function:: void DD::Image::Knob::to_script(std::ostream& o, const OutputContext* oc, bool quote) const
.. cpp:function:: const char* DD::Image::Knob::get_text(const OutputContext* oc) const

  ``to_script`` should serialise the data represented by the knob.  The default implementation calls
  ``get_text`` to get the string to write out, and then automatically handles
  any quoting necessary.

  Both functions take a pointer to an ``OutputContext``.  If this is NULL, it is intended 
  for saving or copying, and therefore should serialise the entire internal state of the knob.

  If it is not NULL, then it is being invoked from the ``value`` function, or similar, and should
  give a serialisation of that knob's state at the given frame and view.  For example, a simple 
  knob representation an animation might choose to produce key-value pairs in its full serialisation
  format.  For example, a line between the points (0, 0) could be represented as "0 0 100 1" (0, 0) to
  (100, 1).  If the functions were to be called with an OutputContext, it should then evaluate that curve
  at the appropriate frame and time.  For example, it might take the time value as x on the curve.
  Therefore, if ``OutputContext`` had a frame of 50, it should produce an output of "0.5".

  The "quote" parameter on ``to_script`` indicates whether or not the output needs quoting for TCL safety.
  When writing to a file, the output from ``to_script`` is directly placed after the knob name.  For example,
  in this::

    GainExample {
      gain 0.5
      name GainExample1
    }

  the text "0.5" was the output from ``to_script`` for that knob.  In a more complex example, where the gain
  knob has been split, this will look like::

    GainExample {
      gain {0.1 0.2 0.3 0.4}
      name GainExample1
    }

  The extra quoting is needed as newlines are treated no differently to other whitespace, and otherwise
  it would assume 0.2 was the name of the next knob.  Therefore, the "quote" parameter is passed as true,
  to indicate that the output needs potentially quoting if there are any characters in it which would
  disrupt TCL parsing.  The ``DD::Image::Knob::cstring`` helper class exists to deal with this, a simple implementation
  might be:

  .. code-block:: c

      void to_script(std::ostream& o, const OutputContext* oc, bool quote) const
      {
        if (quote)  
          o << cstring(_data);
        else
          o << _data;
      }

  Note that this extra layer of quoting must not be put if the "quote" parameter is false, and note that it will
  be stripped out by the time the value is passed to from_script.

.. cpp:function:: bool DD::Image::Knob::from_script(const char*)
 
  ``from_script`` is passed the serialised value.  It should set the knob's internal state to match.  This is a
  value that has already stripped of any quoting that was added in ``to_script`` because of ``quote``.  For example,
  if the script file is ::
  
    GainExample {
      gain {0.1 0.2 0.3 0.4}
      name GainExample1
    }

  then ``from_script`` will be passed the string "0.1 0.2 0.3 0.4".  You can use the helper class
  ``DD::Image::Knob::Script_List`` to help parse TCL-formatted lists.  For example, constructing a ``Script_List`` with
  the string "0.1 0.2 0.3 0.4" as a constructor will construct an object with size() = 4, whose elements are
  "0.1" "0.2" "0.3" and "0.4".  ``Script_List`` can deal with elements themselves being TCL-quoted structures, so
  "{0 1} {2 3}" would parse to a two-element list, with elements "0 1" and "2 3".  ``Script_List`` can then be
  used in turn on these elements, etc.
      
  ``from_script`` should return true if the value changed.  Also, if the value changed, it should have called 
  ``new_undo`` before the internal state changed, and ``changed`` after it does.  This is so that NUKE is aware
  that the script may need evaluating, and also so that the undo system can be maintained.

.. cpp:function:: void DD::Image::Knob::changed()

  Any change made to the internal state of the knob should be surrounded by a call to ``new_undo`` before
  (at the point of the ``new_undo`` a ``to_script`` should result in the original value), and ``changed`` after.
  If the knob is not to be included in the Undo stack then it can be marked with the flag ``Knob::NO_UNDO``,
  and calls to ``new_undo`` can be omitted, but calls to ``changed`` should still be made.

.. cpp:function:: void DD::Image::Knob::reset_to_default()

  By default this calls ``from_script("")``, which is expected to reset the knob to its default value.  If this
  would not happen, you should override this.

.. cpp:function:: void DD::Image::Knob::store(StoreType, void* dst, Hash& hash, const OutputContext& context)

  As described in [...], NUKE updates your ``Op`` with the new values of knobs by calling ``knobs`` and arranging
  for the closure to place the new values in those pointed to.  It does this by calling the ``store`` function
  on the knobs.  You therefore need to implement the store function.

  ``StoreType`` is used internally in NUKE for type information.  In a custom knob it can reasonably be ignored,
  as it will be 'Custom'.  dst here is the pointer that was passed to the ``CustomKnob1`` or ``CustomKnob2`` function as
  its third argument. For example, if you included your knob in ``knobs`` like so::

    CustomKnob1(DemoKnob, f, &_data, "data");

  then it would result in store() being called with the dst pointing at &_data.

  It is expected that ``store`` will evaluate the knob at the frame and time given in context, and copy that cooked
  data into the destination pointer.  If the knob doesn't have any animation, this might be a simple memcpy.
  ``store`` is also an appropriate place to do any proxy-scaling, as the ``OutputContext`` contains the proxy scaling
  information.

  It is important that custom knobs do this, and that in particular that ``engine`` functions do not directly access 
  the same data store, as the internal knob value could be changed _during_ a call to ``engine``, which might result
  in the cache being poisoned.

  In addition to this, the ``store`` function should also add the cooked data it stored to the hash it has been passed in.
  This will ensure proper calculation of the Op's hash.

.. cpp:function:: void DD::Image::Knob::append(Hash& hash, const OutputContext* context)

  If context is specified, this should add the same thing to the hash that a call to ``store`` would with the same
  context.

  If context is NULL, it should hash the entire representation of the curve, for all times and views.  This is
  used for the 'curve hash'.

Storing Arbitrary Data
----------------------

The previous section touched briefly on writing arbitrary data using the to_ and from_ script methods provided on the **Knob** class, so go back and read it if you haven't already. In this section we'll break down a more complete example from the NDK - namely Serialize.cpp, the interesting parts of which are shown in the snippets below. In the first part, we set up the custom SerializeKnob as a sub-class of **Knob**, allowing an instance of CallbackHandler supplied to be the constructor. The idea is that we make our plug-in a CallbackHandler, then when NUKE asks our SerializeKnob if it wants to save any data (i.e. by calling its **to_script()** function), the SerializeKnob will then ask our plug-in to covert any important data into a **std::string** (i.e. serialize the data) and pass it back to the SerializeKnob to be saved in the script. In the reverse process, when the script contains some saved information about the SerializeKnob, NUKE asks the SerializeKnob to process the data (stored as a string) in the script by calling its **from_script()** function. This passes the stored data string to our plug-in, and asks it to deserialize the data into something useful.

.. code-block:: c

 class SerializeKnob : public Knob {
 private:
   CallbackHandler * _host;
   const char *Class() const { return "SerializeKnob"; }

 public:
   SerializeKnob(Knob_Closure *kc, CallbackHandler * host, const char* n): Knob(kc, n),_host(host)
   {}

   bool not_default() const {
     return true;
   }

   virtual bool from_script(const char* v) {

     std::string loadString(v);

     printf("Function from_script() called, with param: %s.\n", loadString.c_str());

     if (_host && loadString!="") {
       bool success = false;	

       try {
         success = _host->load(loadString);
         if(!success)
           error("Failed to load from script");
       }catch (const char * msg) {
         printf("Failed to load from script: %s\n", msg);
       }catch (...) {
         error("Failed to load from script");
       }

       return success;
     }

     return true;
   }

   virtual void to_script(std::ostream& theStream, const OutputContext*, bool quote) const {

     printf("Function to_script() called, with \"quote\" param: %d.\n", quote);

     std::string saveString;

     if (_host) {
       try {
         _host->save( saveString );
       }catch (const char * msg) {
         printf("Failed to save to script: %s\n", msg);
       }catch (...) {
         error("Failed to save to script");
       }
     }

     if(quote) {
       saveString.insert(saveString.begin(),'\"');
       saveString+="\"";
     }

     printf("\tSaved data: %s.\n", saveString.c_str());

     theStream << saveString;
   }
 };

The following code snippet shows how our plug-in “Serialize” inherits and implements the load and save functions of the CallbackHandler. In this contrived example, we’re simply loading and saving the std::string data found in the **important_data_to_keep** variable.

.. code-block:: c

 class Serialize :
 public Iop, public CallbackHandler
 {
 protected:
   /* lines omitted for brevity */
   SerializeKnob * _serializeKnob;
   std::string important_data_to_keep;
   /* lines omitted for brevity */

   virtual void knobs( Knob_Callback f ) {
     _serializeKnob = CustomKnob1(SerializeKnob, f, this, "serializeKnob");
   }	
   /* lines omitted for brevity */

   bool load(std::string loadString){

       /* going to pretend that we've de-serialized loadString
        and are copying the meaningful results back into
        "important_data_to_keep" */

     important_data_to_keep = loadString;

     printf("Trying to load \"%s\" from script.\n", loadString.c_str());

     return true;
   }

   void save(std::string &saveString){
       /* if we were using boost to save the data */
     //std::ostringstream archive_stream;
     //boost::archive::text_oarchive archive(archive_stream);
     //archive << test;
     //std::string outbound_data_ = archive_stream.str();

     /* saving some stuff to the string */
     //saveString += outbound_data_;//"Hello World!";

       /* instead we're going to pretend "important_data_to_keep"
        already contains serialized data */
     saveString += important_data_to_keep; 
       /* i.e. saveString += "Hello World!"; */

     printf("Trying to save \"%s\" to script.\n", saveString.c_str());

   }
 }

As we’re only manipulating string data, the equivalent could be accomplished by a simple Text_knob, however the idea is to show how it could be extended to more complicated scenarios. In practice, you’d store your data in an appropriate structure or class, and then use a robust serialization scheme to handle the object-to-string conversion, such as the one found in the Boost library (http://www.boost.org/). The code for saving to a Boost archive is commented out in the above snippet.


.. TODO: Viewer Handles


.. TODO: Hit detection and event handling


.. TODO: Static 2D HUD-style overlays

Custom knob widgets using Qt
----------------------------

As of NUKE 6.3 and greater you can add your own GUI to a custom knob using NUKE's inbuilt Qt toolkit.

Installing the Qt build environment
___________________________________

In order use Qt in your custom knob you will need to get the Qt source code and Qt tool binaries from the Foundry web site.

You can download them from the `NUKE Developer site <http://docs.thefoundry.co.uk/nuke/>`_.

Unpack the files into a directory and then at the command-line set the QTDIR environment variable to point to that directory, so that the example Makefile will be able to find the toolkit.

Compiling the Example
_____________________

Included in the example directory is a custom node with a custom knob that implements a 'add' or 'gain' operation.

To compile to this at the command line ensuring the QTDIR is set and pointing to your Qt toolkit, plus your NDKDIR set and pointing to the directory up from the DDImage include directory, then::
  
  make -f Makefile.qt
  
Once the plugin is compiled ensure it is in your plugin path, fire up NUKE and create the new node 'AddCustomQt'.  You should see a simple node with a dial instead of a slider to control the gain level.

The source files are in AddCustomQt.cpp and AddCustomQt.h

Example breakdown
_________________

The basic steps required to add a custom GUI to your knob are:

  1.  Create a new widget class the inherits from QWidget for your UI
  2.  Register and handle knob callbacks from NUKE.
  3.  Implement the knob method make_widget() and return your new Qt widget from this.

Let break down the example step by step to show how it works.

The first thing we need is the widget itself.  In this case we have a widget the inherits from QDial.

.. code-block:: c

  class MyKnob;
  
  class MyWidget : public QDial {
    Q_OBJECT
  
  public:
    MyWidget( MyKnob* knob );
    ~MyWidget();
    
    void update();
    void destroy();
    static int WidgetCallback( void* closure, Knob::CallbackReason reason );
    
   public Q_SLOTS:
     void valueChanged ( int value );
   
  private:
    MyKnob* _knob;
  };
  
Notice here we are passing a back pointer to the widgets knob, and defining a callback called WidgetCallback.  

The constructor registers the widget with the knob to handle callbacks.
  
.. code-block:: c

  MyWidget::MyWidget(MyKnob* knob) : _knob(knob)
  {
    setNotchesVisible( true );
    setWrapping( false );
    _knob->addCallback( WidgetCallback, this );
     connect( this, SIGNAL(valueChanged(int)), this, SLOT(valueChanged(int)) );
  }
  
The destructor removes the callback.  Its important to check for the knob still being valid here as there are some circumstances when it could have been destroyed before the widget.

.. code-block:: c
 
  MyWidget::~MyWidget()
  {
    if ( _knob ) 
      _knob->removeCallback( WidgetCallback, this );
  }
  
The widget then handles three callbacks from NUKE.

  * Knob::kIsVisible - return true if the widget is visible, false otherwise. Used to decide if handles are shown in the viewer
  
  * Knob::kUpdateWidgets - the widget should fetch the current value from the knob and update the UI.
  
  * Knob::kDestroying - the widget's knob is being destroyed and therefore any knob back pointer is now invalid and should no longer be used.
  
If widget needs to push a new value into knob it should set the knob value and then called 'changed()'.  

For instance the Qt slot valueChanged is called with the dial changes value.
  
.. code-block:: c

  void MyWidget::valueChanged(int value)
  {
    _knob->setValue( value );
  }

And in the knob the setValue function looks like this:

.. code-block:: c

  void MyKnob::setValue( int value ) {
    new_undo ( "setValue" );
    _data = value;
    changed();
  }

Finally to actually create the UI the knob implements the make_widget function.

.. code-block:: c

  WidgetPointer MyKnob::make_widget() {
    
    MyWidget* widget = new MyWidget( this );
    return widget;
  }
  
.. note::

 Keep in mind that a knob can have multiple instances of widgets ( eg make_widget can be called more than once ).  For example when link knobs are used more than one widget can be created.  Make sure you don't rely on a one to one mapping between widgets and knobs.
 

.. TODO: Exposing python bindings


