No longer receive prox events for yourself.  (Ie, pres a does not get notified that a is within its query angle.)

system.require('featureObject.em');

(function()
 {
     //if we request the feature object this many times from another
     //visible, and do not receive it, then just fire the prox event
     //for the visible setting featureObject to be undefined.
     var NUM_TRIES_TO_GET_FEATURE_OBJECT = 3;
     //if we haven't heard back from visible in this amount of time,
     //then either re-issue the feature object request (up to
     //NUM_TRIES_TO_GET_FATURE_OBJECT times), or just pass through
     //undefined for the visible's featureObject (if tried that many
     //times, and didn't get a response).
     var TIME_TO_WAIT_FOR_FEATURE_OBJECT_RESP = 10;

     //map
     //keys: sporef of presence
     //values: map of actual visible objects
     var pResultSet = {};

     /**
      These get placed into the awaitingFeatureData and
      haveFeatureDataFor map
      @param {visible} vis - The visible that we request features for.
      @param {presence} featGrabber - The presence that we're using to
      request features and listen for feature updates.
      @param {presence} subscriber (optional) - A presence
      that wants to receive updates on vis.
      */
     function SubscriptionElement (vis,featGrabber,subscriber)
     {
         this.vis = vis;
         this.featGrabber =featGrabber;
         this.subscribers = {};
         if (typeof(subscriber) != 'undefined')
             this.subscribers[subscriber.toString()] = subscriber;
     }

     SubscriptionElement.prototype.addSubscriber =
         function(presToAdd)
     {
         this.subscribers[presToAdd.toString()] = presToAdd;
     };

     //returns true if there are no subscribers left.
     SubscriptionElement.prototype.noSubscribers =
         function()
     {
         for (var s in this.subscribers)
             return false;

         return true;
     };

     SubscriptionElement.prototype.removeSubscriber =
         function(presToRemove)
     {
         if (presToRemove.toString() in this.subscribers)
             delete this.subscribers[presToRemove.toString()];
     };

     //map
     //keys: sporef of visible awaiting data on
     //value: SubscriptionElement
     var awaitingFeatureData = {};

     //map
     //keys: sporef of visibles
     //keys: SubscriptionElement
     var haveFeatureDataFor  = {};

     //map
     //keys: sporef of presence
     //values: array of proximity added functions.  note, some
     //elements may be null if the proximity added function was
     //deregistered.
     var pAddCB = {};

     //map
     //keys: sporef of presence
     //values: array of proximity removed functions.  note, some
     //elements may be null if the proximity removed function was
     //deregistered.
     var pRemCB = {};

     //map
     //keys: sporef of presence
     //values: presences themselves.
     var sporefToPresences = {};

     system.onPresenceConnected(
         function(newPres)
         {
             sporefToPresences[newPres.toString()] = newPres;
             pResultSet[newPres.toString()]        = {};
             pAddCB[newPres.toString()]            = [];
             pRemCB[newPres.toString()]            = [];
         });

     //Does not hold internal data.  Instead, has private-like
     //variables defined above.
     function ProxManager()
     {
     }

     ProxManager.prototype.getProxResultSet = function(pres)
     {
         if (!(pres.toString() in pResultSet))
             throw new Error('Error getting prox result set.  ' +
                             'No record of presence.');

         return pResultSet[pres.toString()];
     };

     ProxManager.prototype.setProxAddCB = function(pres,proxAddCB)
     {
         if (!(pres.toString() in pAddCB))
         {
             throw new Error('Cannot add prox callback because do ' +
                             'Not have associated presence in ProxManager.');
         }

         var pArray = pAddCB[pres.toString()];

	 for (var i = 0; i <= pArray.length; i++)
	 {
	     if ((typeof(pArray[i]) == 'undefined') || (pArray[i] == null))
	     {
		 pArray[i] = proxAddCB;
		 return i;
	     }
	 }
         //shouldn't ever get here (notice the <= on exit condition of
         //for loop).  Just added so that syntax
         //highlighting of emacs doesn't keep bothering me.
         return null;
     };

     ProxManager.prototype.setProxRemCB = function (pres,proxRemCB)
     {
         if (!(pres.toString() in pRemCB))
         {
             throw new Error('Cannot add prox rem callback because do ' +
                             'not have associated presence in ProxManager.');
         }

         var pArray = pRemCB[pres.toString()];

	 for (var i = 0; i <= pArray.length; i++)
	 {
	     if ((typeof(pArray[i]) == 'undefined') || (pArray[i] == null))
	     {
		 pArray[i] = proxRemCB;
		 return i;
	     }
	 }
         //shouldn't ever get here (notice the <= on exit condition of
         //for loop).  Just added so that syntax
         //highlighting of emacs doesn't keep bothering me.
         return null;
     };

     ProxManager.prototype.delProxAddCB = function(pres,addID)
     {
         if (!(pres.toString() in pAddCB))
         {
             throw new Error('Cannot del prox add callback because do ' +
                             'not have associated presence in ProxManager.');
         }

         var pArray = pAddCB[pres.toString()];

         if (!(typeof(pArray[addID]) == 'undefined'))
             pArray[addID] = null;
     };

     ProxManager.prototype.delProxRemCB = function(pres,remID)
     {
         if (!(pres.toString() in pRemCB))
         {
             throw new Error('Cannot del prox rem callback because do ' +
                             'not have associated presence in ProxManager.');
         }

         var pArray = pRemCB[pres.toString()];

         if (!(typeof(pArray[addID]) == 'undefined'))
             pArray[addID] = null;
     };

     /**
      Gets called by system whenever visibleObj is within proximity
      range of pres.  Will trigger proxadded callbacks to be called
      if we already have the featureData for visibleObj.  Otherwise,
      queue visibleObj onto awaitingFeatureData.
      */
     ProxManager.prototype.proxAddedEvent = function (pres,visibleObj)
     {
         //ignore events where you have entered your own prox set.
         if (pres.toString() == visibleObj.toString())
             return;

         if (!(pres.toString() in pAddCB))
         {
             throw new Error('Cannot call add event callback because do ' +
                             'not have associated presence in ProxManager.');
         }

         if (visibleObj.toString() in haveFeatureDataFor)
         {
             //we already have feature data for this visible.  add pres
             //as subscriber to haveFeatureDataFor map and trigger callback.
             var subElement = haveFeatureDataFor[visibleObj.toString()];
             subElement.addSubscriber(pres);
             this.triggerAddCallback(pres,subElement.vis);
         }
         else
         {
             //we do not already have feature data for this visible.
             //Check if we're waiting on feature data for this object.
             //If we are, then, just add pres as a subscriber.  If we
             //are not, initiate feature object subscription using
             //pres as the requester.  If we are, waiting, then add
             //pres as a subscriber for this feature data.
             if (visibleObj.toString() in awaitingFeatureData)
             {
                 var subElement = awaitingFeatureData[visibleObj.toString()];
                 subElement.addSubscriber(pres);
             }
             else
             {
                 var subElement = new SubscriptionElement(visibleObj,pres,pres);
                 this.beginFeatureSubscribe(pres, visibleObj);
                 awaitingFeatureData[visibleObj.toString()] = subElement;
             }
         }
     };

     //tells pres to listen for feature vector from visibleObj.
     //numTriesLeft should be left blank except when this function
     //calls itself.  (the idea is that it tries to get feature object
     //data from a presence multiple times and numTriesLeft is a
     //simple way for the function to keep track of how many times
     //we've attempted to get feature object data from other side.
     ProxManager.prototype.beginFeatureSubscribe =
         function(pres,visibleObj,numTriesLeft)
     {
         if (typeof(numTriesLeft) == 'undefined')
             numTriesLeft = NUM_TRIES_TO_GET_FEATURE_OBJECT;

         //If we try multiple times, and still don't get a response, the visible
         //probably is not running the feature object protocol.  We
         //can't do anything about that, and instead fire callback
         //with undefined passed through for featureObject.
         if (numTriesLeft <=0)
         {
             this.subscribeComplete(pres,visibleObj,undefined);
             return;
         }
         --numTriesLeft;

         //see handleFeautreObjectSubscribe (featureObject.em) to see
         //what format the feature object subscription request message
         //should take.
         pres # {'presFeatureSubRequest':true} >> visibleObj >>
             [std.core.bind(handleFeatureData,undefined,this,pres,visibleObj),
              TIME_TO_WAIT_FOR_FEATURE_OBJECT_RESP,
              std.core.bind(
                  this.beginFeatureSubscribe,
                  this,pres,visibleObj,numTriesLeft)];

         // //for now, simple sanity check.  After 1 second, release,
         // //saying that featureData is 1.
         // system.timeout(1,std.core.bind(this.subscribeComplete,this,pres,visibleObj,1));
     };

     function handleFeatureData(proxMan,mPres,vis,featureMsg,visSender)
     {
         //see handleFeatureObjectSubscribe for structure of reply
         //mesages.
         proxMan.subscribeComplete(mPres,vis,featureMsg.data);
     }

     //Call this function when you want pres to unsubscribe for
     //feature data from visibleObj.
     ProxManager.prototype.killSubscription = function(pres,visibleObj)
     {
         //see handleFeautreObjectRequestUnsubscribe message handler
         //in featureObject.em to see what the format of a
         //killSubscription message should look like.
         pres # {'presFeatureReqUnsub': true} >> visibleObj >> [];
     };

     //should get called when a presence has received feature data
     //from visibleObj.
     ProxManager.prototype.subscribeComplete =
         function(pres,visibleObj,featureData)
     {
         visibleObj.featureObject= featureData;
         if (!(visibleObj.toString() in awaitingFeatureData))
         {
             throw new Error('Error in subscribeComplete.  No longer '+
                             'have visibleObj in awaitingFeatureData.');
         }

         var subElement = awaitingFeatureData[visibleObj.toString()];
         delete awaitingFeatureData[visibleObj.toString()];

         if (subElement.noSubscribers())
         {
             //no one is waiting for/actually cares about the new
             //data.
             this.killSubscription(pres,visibleObj);
             return;
         }

         //know that we've received the data and that others want to
         //hear about it.  add subElement to haveFeatureDataFor map
         //and notify all subscribers that visibleObj (with
         //featureData) is in its prox result set and add visibleObj
         //to result set.
         haveFeatureDataFor[visibleObj.toString()] = subElement;
         for (var s in subElement.subscribers)
         {
             this.triggerAddCallback(subElement.subscribers[s],
                                     subElement.vis);
         }
     };

     //actually fire callback for pres's having seen visibleObj in-world
     //and add visibleObj to result set.
     ProxManager.prototype.triggerAddCallback = function(pres,visibleObj)
     {
         pResultSet[pres.toString()][visibleObj.toString()] = visibleObj;
         var pArray = pAddCB[pres.toString()];
         //trigger all non-null non-undefined callbacks
         system.changeSelf(pres);
	 for (var i in pArray)
         {
	     if ((typeof(pArray[i]) != 'undefined') && (pArray[i] != null))
		 pArray[i](visibleObj);
         }
     };

     //called by system when pinto actually states that visibleObj is
     //distant from pres.  If the visible exists in awaitingFeatureData
     //then we just remove a subscriber from it.  If the visible exists in
     //haveFeatureDataFor, then fire removed callback, decrement
     //subscribers.  If no subscribers left in haveFeatureDataFor, then
     //unsubscribe from feature data, and remove element from
     //haveFeatureDataFor.
     ProxManager.prototype.proxRemovedEvent = function (pres,visibleObj)
     {
         //ignore events where you have entered your own prox set.
         if (pres.toString() == visibleObj.toString())
             return;

         if (visibleObj.toString() in awaitingFeatureData)
             awaitingFeatureData[visibleObj.toString()].removeSubscriber(pres);
         else
         {
             //this line ensures that visibleObj will maintain
             //featureData associated with stored vis.
             visibleObj = haveFeatureDataFor[visibleObj.toString()].vis;
             var subElem = haveFeatureDataFor[visibleObj.toString()];
             subElem.removeSubscriber(pres);

             if (subElem.noSubscribers())
             {
                 this.killSubscription(pres,visibleObj);
                 delete haveFeatureDataFor[visibleObj.toString()];
             }

             //actually fire removed callback.
             this.triggerRemoveCallback(pres,visibleObj);
         }
     };

     ProxManager.prototype.triggerRemoveCallback = function(pres,visibleObj)
     {
         if (!(pres.toString() in pRemCB))
         {
             throw new Error('Cannot call rem event callback because do ' +
                             'not have associated presence in ProxManager.');
         }
         if (!(pres.toString() in pResultSet))
         {
             throw new Error('Cannot call rem event callback for presence ' +
                             'because no presence in pResultSet matches.');
         }

         //remove from to proxResultSet
         delete pResultSet[pres.toString()][visibleObj.toString()];

         //actually issue callbacks
         var pArray = pRemCB[pres.toString()];
         system.changeSelf(pres);
         //trigger all non-null non-undefined callbacks

	 for (var i in pArray)
         {
	     if ((typeof(pArray[i]) != 'undefined') && (pArray[i] != null))
		 pArray[i](visibleObj);
         }

     };

     //This function handles all the feature object updates.  For now,
     //we have a very dumb protocol.  We transmit the full feature
     //object instead of the parts that have changed/been updated.
     function handleFeatureObjUpdates(updateMsg,sender)
     {
         var update = updateMsg.featureObjUpdate;

         if (!(sender.toString() in haveFeatureDataFor))
         {
             throw new Error('Error upon reception of feature object ' +
                             'update.  Had no subscription to sender ' +
                             'of message.');
         }

         var localVis = haveFeatureDataFor[sender.toString()].vis;
         localVis.featureObject = update;

         //sends ack to other side so update sender knows we're still
         //alive.
         updateMsg.makeReply({}) >> [];
     }
     //see sendFeaturesToSubscribers function in featureObject.em for
     //format of featureObject update messages.
     handleFeatureObjUpdates << {'featureObjUpdate'::};

     //actually register an instance of proxmanager with system.
     system.__registerProxManager(new ProxManager());
 }
)();

system.require('featureObject.em');

(function()
 {
     //if we request the feature object this many times from another
     //visible, and do not receive it, then just fire the prox event
     //for the visible setting featureObject to be undefined.
     var NUM_TRIES_TO_GET_FEATURE_OBJECT = 3;
     //if we haven't heard back from visible in this amount of time,
     //then either re-issue the feature object request (up to
     //NUM_TRIES_TO_GET_FATURE_OBJECT times), or just pass through
     //undefined for the visible's featureObject (if tried that many
     //times, and didn't get a response).
     var TIME_TO_WAIT_FOR_FEATURE_OBJECT_RESP = 10;

     //map
     //keys: sporef of presence
     //values: map of actual visible objects
     var pResultSet = {};

     /**
      These get placed into the awaitingFeatureData and
      haveFeatureDataFor map
      @param {visible} vis - The visible that we request features for.
      @param {presence} featGrabber - The presence that we're using to
      request features and listen for feature updates.
      @param {presence} subscriber (optional) - A presence
      that wants to receive updates on vis.
      */
     function SubscriptionElement (vis,featGrabber,subscriber)
     {
         this.vis = vis;
         this.featGrabber =featGrabber;
         this.subscribers = {};
         if (typeof(subscriber) != 'undefined')
             this.subscribers[subscriber.toString()] = subscriber;
     }

     SubscriptionElement.prototype.addSubscriber =
         function(presToAdd)
     {
         this.subscribers[presToAdd.toString()] = presToAdd;
     };

     //returns true if there are no subscribers left.
     SubscriptionElement.prototype.noSubscribers =
         function()
     {
         for (var s in this.subscribers)
             return false;

         return true;
     };

     SubscriptionElement.prototype.removeSubscriber =
         function(presToRemove)
     {
         if (presToRemove.toString() in this.subscribers)
             delete this.subscribers[presToRemove.toString()];
     };

     //map
     //keys: sporef of visible awaiting data on
     //value: SubscriptionElement
     var awaitingFeatureData = {};

     //map
     //keys: sporef of visibles
     //keys: SubscriptionElement
     var haveFeatureDataFor  = {};

     //map
     //keys: sporef of presence
     //values: array of proximity added functions.  note, some
     //elements may be null if the proximity added function was
     //deregistered.
     var pAddCB = {};

     //map
     //keys: sporef of presence
     //values: array of proximity removed functions.  note, some
     //elements may be null if the proximity removed function was
     //deregistered.
     var pRemCB = {};

     //map
     //keys: sporef of presence
     //values: presences themselves.
     var sporefToPresences = {};

     system.onPresenceConnected(
         function(newPres)
         {
             sporefToPresences[newPres.toString()] = newPres;
             pResultSet[newPres.toString()]        = {};
             pAddCB[newPres.toString()]            = [];
             pRemCB[newPres.toString()]            = [];
         });

     //Does not hold internal data.  Instead, has private-like
     //variables defined above.
     function ProxManager()
     {
     }

     ProxManager.prototype.getProxResultSet = function(pres)
     {
         if (!(pres.toString() in pResultSet))
             throw new Error('Error getting prox result set.  ' +
                             'No record of presence.');

         return pResultSet[pres.toString()];
     };

     ProxManager.prototype.setProxAddCB = function(pres,proxAddCB)
     {
         if (!(pres.toString() in pAddCB))
         {
             throw new Error('Cannot add prox callback because do ' +
                             'Not have associated presence in ProxManager.');
         }

         var pArray = pAddCB[pres.toString()];

	 for (var i = 0; i <= pArray.length; i++)
	 {
	     if ((typeof(pArray[i]) == 'undefined') || (pArray[i] == null))
	     {
		 pArray[i] = proxAddCB;
		 return i;
	     }
	 }
         //shouldn't ever get here (notice the <= on exit condition of
         //for loop).  Just added so that syntax
         //highlighting of emacs doesn't keep bothering me.
         return null;
     };

     ProxManager.prototype.setProxRemCB = function (pres,proxRemCB)
     {
         if (!(pres.toString() in pRemCB))
         {
             throw new Error('Cannot add prox rem callback because do ' +
                             'not have associated presence in ProxManager.');
         }

         var pArray = pRemCB[pres.toString()];

	 for (var i = 0; i <= pArray.length; i++)
	 {
	     if ((typeof(pArray[i]) == 'undefined') || (pArray[i] == null))
	     {
		 pArray[i] = proxRemCB;
		 return i;
	     }
	 }
         //shouldn't ever get here (notice the <= on exit condition of
         //for loop).  Just added so that syntax
         //highlighting of emacs doesn't keep bothering me.
         return null;
     };

     ProxManager.prototype.delProxAddCB = function(pres,addID)
     {
         if (!(pres.toString() in pAddCB))
         {
             throw new Error('Cannot del prox add callback because do ' +
                             'not have associated presence in ProxManager.');
         }

         var pArray = pAddCB[pres.toString()];

         if (!(typeof(pArray[addID]) == 'undefined'))
             pArray[addID] = null;
     };

     ProxManager.prototype.delProxRemCB = function(pres,remID)
     {
         if (!(pres.toString() in pRemCB))
         {
             throw new Error('Cannot del prox rem callback because do ' +
                             'not have associated presence in ProxManager.');
         }

         var pArray = pRemCB[pres.toString()];

         if (!(typeof(pArray[addID]) == 'undefined'))
             pArray[addID] = null;
     };

     /**
      Gets called by system whenever visibleObj is within proximity
      range of pres.  Will trigger proxadded callbacks to be called
      if we already have the featureData for visibleObj.  Otherwise,
      queue visibleObj onto awaitingFeatureData.
      */
     ProxManager.prototype.proxAddedEvent = function (pres,visibleObj)
     {
         if (!(pres.toString() in pAddCB))
         {
             throw new Error('Cannot call add event callback because do ' +
                             'not have associated presence in ProxManager.');
         }

         if (visibleObj.toString() in haveFeatureDataFor)
         {
             //we already have feature data for this visible.  add pres
             //as subscriber to haveFeatureDataFor map and trigger callback.
             var subElement = haveFeatureDataFor[visibleObj.toString()];
             subElement.addSubscriber(pres);
             this.triggerAddCallback(pres,subElement.vis);
         }
         else
         {
             //we do not already have feature data for this visible.
             //Check if we're waiting on feature data for this object.
             //If we are, then, just add pres as a subscriber.  If we
             //are not, initiate feature object subscription using
             //pres as the requester.  If we are, waiting, then add
             //pres as a subscriber for this feature data.
             if (visibleObj.toString() in awaitingFeatureData)
             {
                 var subElement = awaitingFeatureData[visibleObj.toString()];
                 subElement.addSubscriber(pres);
             }
             else
             {
                 var subElement = new SubscriptionElement(visibleObj,pres,pres);
                 this.beginFeatureSubscribe(pres, visibleObj);
                 awaitingFeatureData[visibleObj.toString()] = subElement;
             }
         }
     };

     //tells pres to listen for feature vector from visibleObj.
     //numTriesLeft should be left blank except when this function
     //calls itself.  (the idea is that it tries to get feature object
     //data from a presence multiple times and numTriesLeft is a
     //simple way for the function to keep track of how many times
     //we've attempted to get feature object data from other side.
     ProxManager.prototype.beginFeatureSubscribe =
         function(pres,visibleObj,numTriesLeft)
     {
         if (typeof(numTriesLeft) == 'undefined')
             numTriesLeft = NUM_TRIES_TO_GET_FEATURE_OBJECT;

         //If we try multiple times, and still don't get a response, the visible
         //probably is not running the feature object protocol.  We
         //can't do anything about that, and instead fire callback
         //with undefined passed through for featureObject.
         if (numTriesLeft <=0)
         {
             this.subscribeComplete(pres,visibleObj,undefined);
             return;
         }
         --numTriesLeft;

         //see handleFeautreObjectSubscribe (featureObject.em) to see
         //what format the feature object subscription request message
         //should take.
         pres # {'presFeatureSubRequest':true} >> visibleObj >>
             [std.core.bind(handleFeatureData,undefined,this,pres,visibleObj),
              TIME_TO_WAIT_FOR_FEATURE_OBJECT_RESP,
              std.core.bind(
                  this.beginFeatureSubscribe,
                  this,pres,visibleObj,numTriesLeft)];

         // //for now, simple sanity check.  After 1 second, release,
         // //saying that featureData is 1.
         // system.timeout(1,std.core.bind(this.subscribeComplete,this,pres,visibleObj,1));
     };

     function handleFeatureData(proxMan,mPres,vis,featureMsg,visSender)
     {
         //see handleFeatureObjectSubscribe for structure of reply
         //mesages.
         proxMan.subscribeComplete(mPres,vis,featureMsg.data);
     }

     //Call this function when you want pres to unsubscribe for
     //feature data from visibleObj.
     ProxManager.prototype.killSubscription = function(pres,visibleObj)
     {
         //see handleFeautreObjectRequestUnsubscribe message handler
         //in featureObject.em to see what the format of a
         //killSubscription message should look like.
         pres # {'presFeatureReqUnsub': true} >> visibleObj >> [];
     };

     //should get called when a presence has received feature data
     //from visibleObj.
     ProxManager.prototype.subscribeComplete =
         function(pres,visibleObj,featureData)
     {
         visibleObj.featureObject= featureData;
         if (!(visibleObj.toString() in awaitingFeatureData))
         {
             throw new Error('Error in subscribeComplete.  No longer '+
                             'have visibleObj in awaitingFeatureData.');
         }

         var subElement = awaitingFeatureData[visibleObj.toString()];
         delete awaitingFeatureData[visibleObj.toString()];

         if (subElement.noSubscribers())
         {
             //no one is waiting for/actually cares about the new
             //data.
             this.killSubscription(pres,visibleObj);
             return;
         }

         //know that we've received the data and that others want to
         //hear about it.  add subElement to haveFeatureDataFor map
         //and notify all subscribers that visibleObj (with
         //featureData) is in its prox result set and add visibleObj
         //to result set.
         haveFeatureDataFor[visibleObj.toString()] = subElement;
         for (var s in subElement.subscribers)
         {
             this.triggerAddCallback(subElement.subscribers[s],
                                     subElement.vis);
         }
     };

     //actually fire callback for pres's having seen visibleObj in-world
     //and add visibleObj to result set.
     ProxManager.prototype.triggerAddCallback = function(pres,visibleObj)
     {
         pResultSet[pres.toString()][visibleObj.toString()] = visibleObj;
         var pArray = pAddCB[pres.toString()];
         //trigger all non-null non-undefined callbacks
         system.changeSelf(pres);
	 for (var i in pArray)
         {
	     if ((typeof(pArray[i]) != 'undefined') && (pArray[i] != null))
		 pArray[i](visibleObj);
         }
     };

     //called by system when pinto actually states that visibleObj is
     //distant from pres.  If the visible exists in awaitingFeatureData
     //then we just remove a subscriber from it.  If the visible exists in
     //haveFeatureDataFor, then fire removed callback, decrement
     //subscribers.  If no subscribers left in haveFeatureDataFor, then
     //unsubscribe from feature data, and remove element from
     //haveFeatureDataFor.
     ProxManager.prototype.proxRemovedEvent = function (pres,visibleObj)
     {
         if (visibleObj.toString() in awaitingFeatureData)
             awaitingFeatureData[visibleObj.toString()].removeSubscriber(pres);
         else
         {
             //this line ensures that visibleObj will maintain
             //featureData associated with stored vis.
             visibleObj = haveFeatureDataFor[visibleObj.toString()].vis;
             var subElem = haveFeatureDataFor[visibleObj.toString()];
             subElem.removeSubscriber(pres);

             if (subElem.noSubscribers())
             {
                 this.killSubscription(pres,visibleObj);
                 delete haveFeatureDataFor[visibleObj.toString()];
             }

             //actually fire removed callback.
             this.triggerRemoveCallback(pres,visibleObj);
         }
     };

     ProxManager.prototype.triggerRemoveCallback = function(pres,visibleObj)
     {
         if (!(pres.toString() in pRemCB))
         {
             throw new Error('Cannot call rem event callback because do ' +
                             'not have associated presence in ProxManager.');
         }
         if (!(pres.toString() in pResultSet))
         {
             throw new Error('Cannot call rem event callback for presence ' +
                             'because no presence in pResultSet matches.');
         }

         //remove from to proxResultSet
         delete pResultSet[pres.toString()][visibleObj.toString()];

         //actually issue callbacks
         var pArray = pRemCB[pres.toString()];
         system.changeSelf(pres);
         //trigger all non-null non-undefined callbacks

	 for (var i in pArray)
         {
	     if ((typeof(pArray[i]) != 'undefined') && (pArray[i] != null))
		 pArray[i](visibleObj);
         }

     };

     //This function handles all the feature object updates.  For now,
     //we have a very dumb protocol.  We transmit the full feature
     //object instead of the parts that have changed/been updated.
     function handleFeatureObjUpdates(updateMsg,sender)
     {
         var update = updateMsg.featureObjUpdate;

         if (!(sender.toString() in haveFeatureDataFor))
         {
             throw new Error('Error upon reception of feature object ' +
                             'update.  Had no subscription to sender ' +
                             'of message.');
         }

         var localVis = haveFeatureDataFor[sender.toString()].vis;
         localVis.featureObject = update;

         //sends ack to other side so update sender knows we're still
         //alive.
         updateMsg.makeReply({}) >> [];
     }
     //see sendFeaturesToSubscribers function in featureObject.em for
     //format of featureObject update messages.
     handleFeatureObjUpdates << {'featureObjUpdate'::};

     //actually register an instance of proxmanager with system.
     system.__registerProxManager(new ProxManager());
 }
)();


Style intro screen - SL #3904

Style intro screen - SL #3904

Set white background and black foreground color like gtkrc does with
the white-bg style class.

Signed-off-by: Manuel Quiñones <6f5069c5b6be23302a13accec56587944be09079@laptop.org>
Acked-by: Simon Schampijer <088e16a1019277b15d58faf0541e11910eb756f6@laptop.org>

${
import math

def my_floor(num):
    return int(math.floor(num))

def my_ceil(num):
    return int(math.ceil(num))

# Should we set the line width in the engine to 2.25, and draw non pixel aligned lines?
# Are these already the correct sizes for the XO?

# These sizes need to be sanity checked ...
if scaling == "100":
    xo = True
    line_width = 2.0        # 2.25px, rounded down
    thick_line_width = 3.5  # 3.5
    subcell_size = 15
    icon_base = 11
    bullet_size = 9.5
    font_height = 24
    default_padding = 6
    toolbutton_padding = 10
else: # About 72% of the XO size, adjusted so that eg. toolbuttons work
    xo = False
    line_width = 2.0            # 1.62 rounded up
    thick_line_width = 3.0      # 2.52
    subcell_size = 11           # 10.8
    icon_base = 8               # 7.92
    bullet_size = 6.5           # 6.84
    # This is a guess on the font size (Sans 10 at 96 DPI)
    font_height = 17
    default_padding = 4         # 4.32
    toolbutton_padding = 7      # 7.68

# Radio size used to be:
#
# radio_size = my_floor(subcell_size + bullet_size + line_width)
#
# But a screenshot shows that the graphic was actually rendered at 26px
# so the SVG displays at the correct size.
# FIXME this only works for 100, has not been tested in 75 resolution
radio_size = 26
scale_slider_width = my_floor(2 * subcell_size + line_width)
thickness = my_ceil(line_width)

icon_small = icon_base * 3
icon_large = icon_base * 5

}

* {
    -sugar-focus-line: @white;

    /* A lot of these will probably need to be changed, but this has to
       be done when the exact sizes are known */
    -GtkWidget-interior-focus: 0;

    /* we have to disable focus border for GtkTreeView, see #1261 */
    -GtkTreeView-interior-focus: 1;
    -GtkTreeView-focus-line-width: 0;

    -GtkTextView-interior-focus: 1;

    -GtkWidget-focus-line-width: 0;  /* Prevents some drawing glitches */
    -GtkEntry-focus-line-width: 0;
    -GtkScale-focus-line-width: 0;
    -GtkScale-focus-line-width: 0;
    -GtkWidget-focus-padding: 0;
    /* 0.05 works good for both the sugar and sugar-xo themes */
    -GtkWidget-cursor-aspect-ratio: 0.05;

    -GtkWidget-wide-separators: false;
    -GtkWidget-separator-height: $thickness;
    -GtkWidget-separator-width: $thickness;

    -GtkWidget-scroll-arrow-hlength: $subcell_size;
    -GtkWidget-scroll-arrow-vlength: $subcell_size;

    -GtkRange-activate-slider: 1;

    /* We fake the default border in the theme */
    -GtkButton-default-border: 0 0 0 0;
    -GtkButton-default-outside-border: 0 0 0 0;
    -GtkButton-image-spacing: $subcell_size;

    -GtkEntry-progress-border: $thickness $thickness $thickness $thickness;

    -GtkScrolledWindow-scrollbar-spacing: 0;

    -GtkExpander-expander-size: $font_height;
    -GtkExpander-expander-spacing: 2;

    -GtkTreeView-expander-size: $font_height;

    -GtkArrow-arrow-size: 1.0;

    -GtkToolbar-space-size: $(2*subcell_size);
    -GtkToolbar-shadow-type: none;

    -GtkProgressBar-min-horizontal-bar-height: $subcell_size;
    -GtkProgressBar-min-vertical-bar-width: $subcell_size;

    -GtkButtonBox-child-min-height: $(3*subcell_size);
    -GtkButtonBox-child-min-width: $(3*subcell_size);
    -GtkButtonBox-child-internal-pad-x: 0;
    -GtkButtonBox-child-internal-pad-y: 0;

    -GtkCheckButton-indicator-size: $radio_size;
    -GtkCheckButton-indicator-spacing: 3;
}

/* Backgrounds and windows */

.background {
    padding: 0px;
    border-width: 0px;
    background-color: @panel_grey;
}

.window {
    background-color: @panel_grey;
    color: @black;
}

SugarIntroWindow.background {
    background-color: @white;
}

SugarIntroWindow {
    color: @black;
}

GtkLabel, GtkLabel:insensitive {
    background-color: transparent;
}

/* Handle this differently? */
.window *:insensitive {
    background-color: @panel_grey;
}

/* Buttons */

.button {
    ${ border = max(0, my_ceil((3*subcell_size/2.0 - icon_small / 2.0))) }
    /* It would be nicer to just set the inner-border, but that does not work
       for combo boxes ... The combobox ignores it, so set it to 0px
       See http://bugzilla.gnome.org/show_bug.cgi?id=485762 */

    -GtkButton-inner-border: 0 0 0 0;
    padding: $(border)px $(border)px $(border)px $(border)px;

    border-width: $(thickness)px;
    border-color: @button_grey;
    border-style: solid;
    border-radius: $(2*subcell_size)px;
    background-color: @button_grey;
    color: @white;
}

.button:focused,
.button:active {
    border-color: @white;
}

.button:active {
    background-color: @white;
}

.button:active:focused {
    color: @black;
}

/* Spin buttons */

.spinbutton.button {
    border-radius: 0px;
    border-width: 0px;
    border-style: solid;
    background-color: @button_grey;
}

.spinbutton.button:last-child {
    border-radius: 0px $(2*subcell_size)px $(2*subcell_size)px 0px;
    border-width: 0px 0px 0px $(thickness)px;
    border-style: solid;
    border-color: @selection_grey;
}

.spinbutton.button:active {
    background-color: @black;
}

.spinbutton.button:insensitive {
    background-color: @selection_grey;
}

/* Toggle buttons */

GtkToggleButton.button:active {
    background-color: @white;
    color: @black;
}

/* Entries and views */

.view {
    border-width: 0px;
    border-style: none;
    border-radius: 0px;
    padding: 0px;
    background-color: @white;
    color: @black;
}

GtkTreeView row:nth-child(even) {
    background-color: @row_even;
}
GtkTreeView row:nth-child(odd) {
    background-color: @row_odd;
}

.entry {
    border-radius: $(2 * subcell_size)px;
    border-width: $(thickness)px;
    border-color: @text_field_grey;
    border-style: solid;
    background-color: @text_field_grey;
    color: @black;
    ${ entry_ythickness = my_ceil(0.2 * (subcell_size*3.0/2.0 - thickness) + thickness) }
    ${ entry_xthickness = my_ceil(0.32 * (subcell_size*3.0/2.0 - thickness) + thickness) }
    padding: $(2 * max(my_ceil((3*subcell_size - font_height - entry_ythickness*2)/2.0),0))px $(2 * max(subcell_size - entry_xthickness, 0))px $(2 * max(my_floor((3*subcell_size - font_height - entry_ythickness*2)/2.0), 0))px $(2 * max(subcell_size - entry_xthickness, 0))px;
}

.entry.progressbar {
    border-radius: $(2 * subcell_size)px;
    border-width: $(thickness)px;
    background-color: @selection_grey;
}

.entry:focused {
    background-color: @white;
}

.toolbar .entry:focused {
    border-color: @white;
}

.entry:insensitive, .view:insensitive {
    background-color: @button_grey;
}

.entry:insensitive {
    border-color: @button_grey;
}

.view:selected {
    background-color: @panel_grey;
    color: @black;
}

.entry:selected, .entry:selected:focused,
.view:selected:focused {
    background-color: @selection_grey;
    color: @black;
}

.entry:selected, .entry:selected:focused {
    border-color: @selection_grey;
}

/* Frames */

GtkScrolledWindow.frame {
    border-style: solid;
    border-color: @selection_grey;
    border-width: $(thickness)px;
    border-radius: 0;
    padding: $(default_padding)px;
}

/* Combo boxes */

GtkComboBox {
    color: @white;
}

GtkComboBox .separator {
    /* Remove the separator turning it transparent */
    color: alpha(@theme_base_color, 0.0);
}

/* Notebooks */

.notebook {
    background-color: @selection_grey;
    color: @black;
    padding: 0px;
    -GtkNotebook-tab-overlap: -2;
    -GtkNotebook-tab-curvature: $default_padding;
}

.notebook tab {
    background-color: @selection_grey;
    color: @white;
}

.notebook tab GtkLabel {
    color: @white;
}

.notebook tab:active {
    background-color: @toolbar_grey;
}

.notebook tab .button {
    border-radius: $(toolbutton_padding)px;
}

/* Browse Widgets */

BrowseTabPage {
    background-color: @black;
}

/* Control panel */

SugarSectionView {
    background-color: @white;
    color: @black;
}

SugarSectionView *:insensitive {
    background-color: @white;
}

/* Alert */

SugarAlert {
    background-color: @black;
    color: @white;
}

SugarAlert GtkLabel {
    color: @white;
}

SugarAlert *:insensitive {
    background-color: @black;
}

.button SugarTimeoutIcon GtkLabel,
.button SugarTimeoutIcon GtkLabel:prelight {
    background-color: @white;
    color: @button_grey;
}

.button SugarTimeoutIcon GtkLabel:active {
    background-color: @toolbar_grey;
    color: @white;
}

/* Tray */

SugarHTray * , SugarVTray * { background-color: @toolbar_grey;}

/* Menus and palettes */

SugarPaletteWindowWidget.background {
    background-color: @black;
}

SugarPaletteWindowWidget GtkLabel {
    color: @white;
}

SugarPaletteMenuWidget.background {
    background-color: @black;
}

SugarPaletteMenuWidget * {
    color: @white;
}

SugarPaletteWindow SugarGroupBox {
    background-color: @toolbar_grey;
    color: @white;
}

SugarPaletteWindow SugarGroupBox *:insensitive {
    background-color: @toolbar_grey;
}

.menu {
    background-color: @black;
    color: @white;

    -GtkMenu-scroll-arrow-vlength: $(my_floor(subcell_size/0.7 + 2*thickness));
    -GtkMenu-horizontal-padding: $thickness;
    -GtkMenu-vertical-padding  : $thickness;
    /* This means the outline of the submenu overlaps with a palette.
     * However in the case of two normal menus, they are next to each other.
     * It is not possible to be smarter about this, because the style comes from
     * the submenu. */
    -GtkMenu-horizontal-offset : 0;
    -GtkMenu-vertical-offset   : 0;

    padding: 0px 0px $(subcell_size)px 0px;
    border-width: 2px;
    border-color: @button_grey;
    border-style: solid;
}

.menu :active {
    background-color: @button_grey;
}

.menuitem {
    padding: $(subcell_size)px $((subcell_size * 3 - font_height) / 2)px;
}

.menuitem:prelight {
    background-color: @button_grey;
}

.menuitem.separator {
    padding: $(subcell_size)px 0px;
    color: @button_grey;
}

SugarPaletteHeader.menuitem {
    padding: 0px $((subcell_size * 3 - font_height) / 2)px;
}

SugarPaletteHeader.menuitem:prelight {
    background-color: @black;
}

SugarPaletteHeaderSeparator.menuitem.separator {
    padding: 0px 0px $(subcell_size)px 0px;
}

.tooltip {
    background-color: @black;
    border-style: solid;
    border-width: $(thickness)px;
    border-color: @button_grey;
}

.tooltip * {
    color: @white;
}

/* Scrollbars */

.scrollbar {
    -GtkRange-slider-width: $subcell_size;
    -GtkRange-trough-border: 0;
    -GtkRange-stepper-size: 0;
    -GtkScrollbar-min-slider-length: $(3*subcell_size);
    -GtkScrollbar-has-forward-stepper: 0;
    -GtkScrollbar-has-backward-stepper: 0;
    -GtkScrollbar-has-secondary-forward-stepper: 0;
    -GtkScrollbar-has-secondary-backward-stepper: 0;
}

.scrollbar.trough {
    background-color: @button_grey;
    border-width: 0px;
}

.scrollbar.slider {
    background-color: @white;
    border-radius: $(2*subcell_size)px;
    border-width: 0px;
}

.scrollbar.slider:active {
    background-color: @text_field_grey;
}

/* Progress bars */

/* Scrollbar padding hack? What was that about? */

GtkProgressBar.progressbar {
    background-color: @white;
    border-color: @white;
    border-radius: 10px;
    border-style: solid;
    border-width: 0px;
}

GtkProgressBar.trough {
    background-color: alpha (@black, 0.0);
    border-style: solid;
    border-radius: 10px;
    border-color: @button_grey;
    border-width: 2px;
}

/* Separators */

GtkVSeparator, GtkHSeparator,
.toolbar GtkSeparatorToolItem {
    color: @button_grey;
}

/* Tool buttons */

.toolbar {
padding: 0px;
background-color: @toolbar_grey;
}

.toolbar GtkLabel {
    color: @white;
}

.toolbar .button,
SugarPaletteWindowWidget SugarRadioToolButton .button {
    border-color: transparent;
    border-radius: 0px;
    border-style: none;
}

.toolbar GtkToolButton .button,
.toolbar SugarRadioToolButton *,
SugarPaletteWindowWidget SugarRadioToolButton *,
SugarPaletteWindowWidget GtkToolButton .button {
    background-color: transparent;
    border-radius: $(toolbutton_padding)px;
    padding: $(toolbutton_padding)px;
}

.toolbar GtkToolButton .button:prelight,
SugarPaletteWindowWidget GtkToolButton .button:prelight {
    background-color: @black;
}

.toolbar SugarRadioToolButton *:active,
SugarPaletteWindowWidget SugarRadioToolButton *:active {
    background-color: @button_grey;
    border-radius: $(toolbutton_padding)px;
}

SugarPaletteWindowWidget GtkToolButton .button:active {
    background-color: @transparent;
}

.toolbar GtkToolButton .button:active {
    background-color: @button_grey;
    border-radius: $(toolbutton_padding)px;
}

SugarPaletteWindowWidget GtkScrolledWindow * {
    background-color: @black;
}

.toolbar GtkComboBox .button {
    border-radius: $(2*subcell_size)px;
}

/* Scales */

GtkScale {
    -GtkScale-slider-length: $scale_slider_width;
    -GtkRange-slider-width: $scale_slider_width;
}

GtkScale.trough {
    background-color: @button_grey;
    border-style: solid;
    border-radius: 30px;
    border-color: @button_grey;
    border-width: 2px;
}

GtkScale.trough:focused {
    border-color: @white;
}

GtkScale.trough.top, GtkScale.trough.left {
    background-color: @white;
}

GtkScale.slider {
    color: alpha(@theme_base_color, 0.0);
    background-color: alpha(@theme_base_color, 0.0);
    background-image: url("assets/scale-slider.svg");
}

GtkScale.slider:active {
    color: alpha(@theme_base_color, 0.0);
    background-image: url("assets/scale-slider-active.svg");
}

/* Radio and check buttons */

GtkCheckButton {
    color: @black;
}

GtkCheckButton:prelight {
    background-color: alpha(@theme_base_color, 0.0);
}

.toolbar GtkCheckButton *:active,
SugarPaletteWindowWidget GtkCheckButton *:active {
    color: @theme_base_color;
}

.radio,
.radio row:selected,
.radio row:selected:focused {
    background-image: url("assets/radio.svg");
    background-color: alpha(@theme_base_color, 0.0);
}

.radio:selected,
.radio:selected row:selected,
.radio:selected row:selected:focused {
    background-image: url("assets/radio-selected.svg");
}

.radio:active,
.radio row:selected:active,
.radio row:selected:focused:active {
    background-image: url("assets/radio-active.svg");
}

.radio:active:selected,
.radio:selected row:selected:active,
.radio:selected row:selected:focused:active {
    background-image: url("assets/radio-active-selected.svg");
}

.check,
.check row:selected,
.check row:selected:focused {
    background-image: url("assets/checkbox-unchecked.svg");
    background-color: alpha(@theme_base_color, 0.0);
}

.check:selected,
.check:selected row:selected,
.check:selected row:selected:focused {
    background-image: url("assets/checkbox-unchecked-selected.svg");
}

.check:active,
.check row:selected:active,
.check row:selected:focused:active {
    background-image: url("assets/checkbox-checked.svg");
}

.check:active:selected,
.check:selected row:selected:active,
.check:selected row:selected:focused:active {
    background-image: url("assets/checkbox-checked-selected.svg");
}

/* Tool items */

.toolitem {
    /* arrow in the toolbarbox */
    color: @white;
    /* outline of the toolbarbutton when palette is expanded */
    border-style: solid;
    border-width: 2px;
    border-color: @button_grey;
}

/* GtkImage */

GtkImage {
    background-color: transparent;
}

/* Sugar Frame Window */

SugarFrameWindow.background {
    background-color: @toolbar_grey;
}

${
import math

def my_floor(num):
    return int(math.floor(num))

def my_ceil(num):
    return int(math.ceil(num))

# Should we set the line width in the engine to 2.25, and draw non pixel aligned lines?
# Are these already the correct sizes for the XO?

# These sizes need to be sanity checked ...
if scaling == "100":
    xo = True
    line_width = 2.0        # 2.25px, rounded down
    thick_line_width = 3.5  # 3.5
    subcell_size = 15
    icon_base = 11
    bullet_size = 9.5
    font_height = 24
    default_padding = 6
    toolbutton_padding = 10
else: # About 72% of the XO size, adjusted so that eg. toolbuttons work
    xo = False
    line_width = 2.0            # 1.62 rounded up
    thick_line_width = 3.0      # 2.52
    subcell_size = 11           # 10.8
    icon_base = 8               # 7.92
    bullet_size = 6.5           # 6.84
    # This is a guess on the font size (Sans 10 at 96 DPI)
    font_height = 17
    default_padding = 4         # 4.32
    toolbutton_padding = 7      # 7.68

# Radio size used to be:
#
# radio_size = my_floor(subcell_size + bullet_size + line_width)
#
# But a screenshot shows that the graphic was actually rendered at 26px
# so the SVG displays at the correct size.
# FIXME this only works for 100, has not been tested in 75 resolution
radio_size = 26
scale_slider_width = my_floor(2 * subcell_size + line_width)
thickness = my_ceil(line_width)

icon_small = icon_base * 3
icon_large = icon_base * 5

}

* {
    -sugar-focus-line: @white;

    /* A lot of these will probably need to be changed, but this has to
       be done when the exact sizes are known */
    -GtkWidget-interior-focus: 0;

    /* we have to disable focus border for GtkTreeView, see #1261 */
    -GtkTreeView-interior-focus: 1;
    -GtkTreeView-focus-line-width: 0;

    -GtkTextView-interior-focus: 1;

    -GtkWidget-focus-line-width: 0;  /* Prevents some drawing glitches */
    -GtkEntry-focus-line-width: 0;
    -GtkScale-focus-line-width: 0;
    -GtkScale-focus-line-width: 0;
    -GtkWidget-focus-padding: 0;
    /* 0.05 works good for both the sugar and sugar-xo themes */
    -GtkWidget-cursor-aspect-ratio: 0.05;

    -GtkWidget-wide-separators: false;
    -GtkWidget-separator-height: $thickness;
    -GtkWidget-separator-width: $thickness;

    -GtkWidget-scroll-arrow-hlength: $subcell_size;
    -GtkWidget-scroll-arrow-vlength: $subcell_size;

    -GtkRange-activate-slider: 1;

    /* We fake the default border in the theme */
    -GtkButton-default-border: 0 0 0 0;
    -GtkButton-default-outside-border: 0 0 0 0;
    -GtkButton-image-spacing: $subcell_size;

    -GtkEntry-progress-border: $thickness $thickness $thickness $thickness;

    -GtkScrolledWindow-scrollbar-spacing: 0;

    -GtkExpander-expander-size: $font_height;
    -GtkExpander-expander-spacing: 2;

    -GtkTreeView-expander-size: $font_height;

    -GtkArrow-arrow-size: 1.0;

    -GtkToolbar-space-size: $(2*subcell_size);
    -GtkToolbar-shadow-type: none;

    -GtkProgressBar-min-horizontal-bar-height: $subcell_size;
    -GtkProgressBar-min-vertical-bar-width: $subcell_size;

    -GtkButtonBox-child-min-height: $(3*subcell_size);
    -GtkButtonBox-child-min-width: $(3*subcell_size);
    -GtkButtonBox-child-internal-pad-x: 0;
    -GtkButtonBox-child-internal-pad-y: 0;

    -GtkCheckButton-indicator-size: $radio_size;
    -GtkCheckButton-indicator-spacing: 3;
}

/* Backgrounds and windows */

.background {
    padding: 0px;
    border-width: 0px;
    background-color: @panel_grey;
}

.window {
    background-color: @panel_grey;
    color: @black;
}

GtkLabel, GtkLabel:insensitive {
    background-color: transparent;
}

/* Handle this differently? */
.window *:insensitive {
    background-color: @panel_grey;
}

/* Buttons */

.button {
    ${ border = max(0, my_ceil((3*subcell_size/2.0 - icon_small / 2.0))) }
    /* It would be nicer to just set the inner-border, but that does not work
       for combo boxes ... The combobox ignores it, so set it to 0px
       See http://bugzilla.gnome.org/show_bug.cgi?id=485762 */

    -GtkButton-inner-border: 0 0 0 0;
    padding: $(border)px $(border)px $(border)px $(border)px;

    border-width: $(thickness)px;
    border-color: @button_grey;
    border-style: solid;
    border-radius: $(2*subcell_size)px;
    background-color: @button_grey;
    color: @white;
}

.button:focused,
.button:active {
    border-color: @white;
}

.button:active {
    background-color: @white;
}

.button:active:focused {
    color: @black;
}

/* Spin buttons */

.spinbutton.button {
    border-radius: 0px;
    border-width: 0px;
    border-style: solid;
    background-color: @button_grey;
}

.spinbutton.button:last-child {
    border-radius: 0px $(2*subcell_size)px $(2*subcell_size)px 0px;
    border-width: 0px 0px 0px $(thickness)px;
    border-style: solid;
    border-color: @selection_grey;
}

.spinbutton.button:active {
    background-color: @black;
}

.spinbutton.button:insensitive {
    background-color: @selection_grey;
}

/* Toggle buttons */

GtkToggleButton.button:active {
    background-color: @white;
    color: @black;
}

/* Entries and views */

.view {
    border-width: 0px;
    border-style: none;
    border-radius: 0px;
    padding: 0px;
    background-color: @white;
    color: @black;
}

GtkTreeView row:nth-child(even) {
    background-color: @row_even;
}
GtkTreeView row:nth-child(odd) {
    background-color: @row_odd;
}

.entry {
    border-radius: $(2 * subcell_size)px;
    border-width: $(thickness)px;
    border-color: @text_field_grey;
    border-style: solid;
    background-color: @text_field_grey;
    color: @black;
    ${ entry_ythickness = my_ceil(0.2 * (subcell_size*3.0/2.0 - thickness) + thickness) }
    ${ entry_xthickness = my_ceil(0.32 * (subcell_size*3.0/2.0 - thickness) + thickness) }
    padding: $(2 * max(my_ceil((3*subcell_size - font_height - entry_ythickness*2)/2.0),0))px $(2 * max(subcell_size - entry_xthickness, 0))px $(2 * max(my_floor((3*subcell_size - font_height - entry_ythickness*2)/2.0), 0))px $(2 * max(subcell_size - entry_xthickness, 0))px;
}

.entry.progressbar {
    border-radius: $(2 * subcell_size)px;
    border-width: $(thickness)px;
    background-color: @selection_grey;
}

.entry:focused {
    background-color: @white;
}

.toolbar .entry:focused {
    border-color: @white;
}

.entry:insensitive, .view:insensitive {
    background-color: @button_grey;
}

.entry:insensitive {
    border-color: @button_grey;
}

.view:selected {
    background-color: @panel_grey;
    color: @black;
}

.entry:selected, .entry:selected:focused,
.view:selected:focused {
    background-color: @selection_grey;
    color: @black;
}

.entry:selected, .entry:selected:focused {
    border-color: @selection_grey;
}

/* Frames */

GtkScrolledWindow.frame {
    border-style: solid;
    border-color: @selection_grey;
    border-width: $(thickness)px;
    border-radius: 0;
    padding: $(default_padding)px;
}

/* Combo boxes */

GtkComboBox {
    color: @white;
}

GtkComboBox .separator {
    /* Remove the separator turning it transparent */
    color: alpha(@theme_base_color, 0.0);
}

/* Notebooks */

.notebook {
    background-color: @selection_grey;
    color: @black;
    padding: 0px;
    -GtkNotebook-tab-overlap: -2;
    -GtkNotebook-tab-curvature: $default_padding;
}

.notebook tab {
    background-color: @selection_grey;
    color: @white;
}

.notebook tab GtkLabel {
    color: @white;
}

.notebook tab:active {
    background-color: @toolbar_grey;
}

.notebook tab .button {
    border-radius: $(toolbutton_padding)px;
}

/* Browse Widgets */

BrowseTabPage {
    background-color: @black;
}

/* Control panel */

SugarSectionView {
    background-color: @white;
    color: @black;
}

SugarSectionView *:insensitive {
    background-color: @white;
}

/* Alert */

SugarAlert {
    background-color: @black;
    color: @white;
}

SugarAlert GtkLabel {
    color: @white;
}

SugarAlert *:insensitive {
    background-color: @black;
}

.button SugarTimeoutIcon GtkLabel,
.button SugarTimeoutIcon GtkLabel:prelight {
    background-color: @white;
    color: @button_grey;
}

.button SugarTimeoutIcon GtkLabel:active {
    background-color: @toolbar_grey;
    color: @white;
}

/* Tray */

SugarHTray * , SugarVTray * { background-color: @toolbar_grey;}

/* Menus and palettes */

SugarPaletteWindowWidget.background {
    background-color: @black;
}

SugarPaletteWindowWidget GtkLabel {
    color: @white;
}

SugarPaletteMenuWidget.background {
    background-color: @black;
}

SugarPaletteMenuWidget * {
    color: @white;
}

SugarPaletteWindow SugarGroupBox {
    background-color: @toolbar_grey;
    color: @white;
}

SugarPaletteWindow SugarGroupBox *:insensitive {
    background-color: @toolbar_grey;
}

.menu {
    background-color: @black;
    color: @white;

    -GtkMenu-scroll-arrow-vlength: $(my_floor(subcell_size/0.7 + 2*thickness));
    -GtkMenu-horizontal-padding: $thickness;
    -GtkMenu-vertical-padding  : $thickness;
    /* This means the outline of the submenu overlaps with a palette.
     * However in the case of two normal menus, they are next to each other.
     * It is not possible to be smarter about this, because the style comes from
     * the submenu. */
    -GtkMenu-horizontal-offset : 0;
    -GtkMenu-vertical-offset   : 0;

    padding: 0px 0px $(subcell_size)px 0px;
    border-width: 2px;
    border-color: @button_grey;
    border-style: solid;
}

.menu :active {
    background-color: @button_grey;
}

.menuitem {
    padding: $(subcell_size)px $((subcell_size * 3 - font_height) / 2)px;
}

.menuitem:prelight {
    background-color: @button_grey;
}

.menuitem.separator {
    padding: $(subcell_size)px 0px;
    color: @button_grey;
}

SugarPaletteHeader.menuitem {
    padding: 0px $((subcell_size * 3 - font_height) / 2)px;
}

SugarPaletteHeader.menuitem:prelight {
    background-color: @black;
}

SugarPaletteHeaderSeparator.menuitem.separator {
    padding: 0px 0px $(subcell_size)px 0px;
}

.tooltip {
    background-color: @black;
    border-style: solid;
    border-width: $(thickness)px;
    border-color: @button_grey;
}

.tooltip * {
    color: @white;
}

/* Scrollbars */

.scrollbar {
    -GtkRange-slider-width: $subcell_size;
    -GtkRange-trough-border: 0;
    -GtkRange-stepper-size: 0;
    -GtkScrollbar-min-slider-length: $(3*subcell_size);
    -GtkScrollbar-has-forward-stepper: 0;
    -GtkScrollbar-has-backward-stepper: 0;
    -GtkScrollbar-has-secondary-forward-stepper: 0;
    -GtkScrollbar-has-secondary-backward-stepper: 0;
}

.scrollbar.trough {
    background-color: @button_grey;
    border-width: 0px;
}

.scrollbar.slider {
    background-color: @white;
    border-radius: $(2*subcell_size)px;
    border-width: 0px;
}

.scrollbar.slider:active {
    background-color: @text_field_grey;
}

/* Progress bars */

/* Scrollbar padding hack? What was that about? */

GtkProgressBar.progressbar {
    background-color: @white;
    border-color: @white;
    border-radius: 10px;
    border-style: solid;
    border-width: 0px;
}

GtkProgressBar.trough {
    background-color: alpha (@black, 0.0);
    border-style: solid;
    border-radius: 10px;
    border-color: @button_grey;
    border-width: 2px;
}

/* Separators */

GtkVSeparator, GtkHSeparator,
.toolbar GtkSeparatorToolItem {
    color: @button_grey;
}

/* Tool buttons */

.toolbar {
padding: 0px;
background-color: @toolbar_grey;
}

.toolbar GtkLabel {
    color: @white;
}

.toolbar .button,
SugarPaletteWindowWidget SugarRadioToolButton .button {
    border-color: transparent;
    border-radius: 0px;
    border-style: none;
}

.toolbar GtkToolButton .button,
.toolbar SugarRadioToolButton *,
SugarPaletteWindowWidget SugarRadioToolButton *,
SugarPaletteWindowWidget GtkToolButton .button {
    background-color: transparent;
    border-radius: $(toolbutton_padding)px;
    padding: $(toolbutton_padding)px;
}

.toolbar GtkToolButton .button:prelight,
SugarPaletteWindowWidget GtkToolButton .button:prelight {
    background-color: @black;
}

.toolbar SugarRadioToolButton *:active,
SugarPaletteWindowWidget SugarRadioToolButton *:active {
    background-color: @button_grey;
    border-radius: $(toolbutton_padding)px;
}

SugarPaletteWindowWidget GtkToolButton .button:active {
    background-color: @transparent;
}

.toolbar GtkToolButton .button:active {
    background-color: @button_grey;
    border-radius: $(toolbutton_padding)px;
}

SugarPaletteWindowWidget GtkScrolledWindow * {
    background-color: @black;
}

.toolbar GtkComboBox .button {
    border-radius: $(2*subcell_size)px;
}

/* Scales */

GtkScale {
    -GtkScale-slider-length: $scale_slider_width;
    -GtkRange-slider-width: $scale_slider_width;
}

GtkScale.trough {
    background-color: @button_grey;
    border-style: solid;
    border-radius: 30px;
    border-color: @button_grey;
    border-width: 2px;
}

GtkScale.trough:focused {
    border-color: @white;
}

GtkScale.trough.top, GtkScale.trough.left {
    background-color: @white;
}

GtkScale.slider {
    color: alpha(@theme_base_color, 0.0);
    background-color: alpha(@theme_base_color, 0.0);
    background-image: url("assets/scale-slider.svg");
}

GtkScale.slider:active {
    color: alpha(@theme_base_color, 0.0);
    background-image: url("assets/scale-slider-active.svg");
}

/* Radio and check buttons */

GtkCheckButton {
    color: @black;
}

GtkCheckButton:prelight {
    background-color: alpha(@theme_base_color, 0.0);
}

.toolbar GtkCheckButton *:active,
SugarPaletteWindowWidget GtkCheckButton *:active {
    color: @theme_base_color;
}

.radio,
.radio row:selected,
.radio row:selected:focused {
    background-image: url("assets/radio.svg");
    background-color: alpha(@theme_base_color, 0.0);
}

.radio:selected,
.radio:selected row:selected,
.radio:selected row:selected:focused {
    background-image: url("assets/radio-selected.svg");
}

.radio:active,
.radio row:selected:active,
.radio row:selected:focused:active {
    background-image: url("assets/radio-active.svg");
}

.radio:active:selected,
.radio:selected row:selected:active,
.radio:selected row:selected:focused:active {
    background-image: url("assets/radio-active-selected.svg");
}

.check,
.check row:selected,
.check row:selected:focused {
    background-image: url("assets/checkbox-unchecked.svg");
    background-color: alpha(@theme_base_color, 0.0);
}

.check:selected,
.check:selected row:selected,
.check:selected row:selected:focused {
    background-image: url("assets/checkbox-unchecked-selected.svg");
}

.check:active,
.check row:selected:active,
.check row:selected:focused:active {
    background-image: url("assets/checkbox-checked.svg");
}

.check:active:selected,
.check:selected row:selected:active,
.check:selected row:selected:focused:active {
    background-image: url("assets/checkbox-checked-selected.svg");
}

/* Tool items */

.toolitem {
    /* arrow in the toolbarbox */
    color: @white;
    /* outline of the toolbarbutton when palette is expanded */
    border-style: solid;
    border-width: 2px;
    border-color: @button_grey;
}

/* GtkImage */

GtkImage {
    background-color: transparent;
}

/* Sugar Frame Window */

SugarFrameWindow.background {
    background-color: @toolbar_grey;
}


Work around a broken binutils release in RHEL 8.4 (#898)

I'm still working out the details, but it appears that when rclpy is
built with the version of binutils shipped in RHEL 8.4, it segfaults
during string initialization.

This workaround installs the RHEL 8.3 version of binutils and prevents
dnf from updating to the latest version.

include('/etc/mock/default.cfg')

# Change the root name since we're modifying the chroot
config_opts['root'] += '-ros-buildfarm'

# Disable mock bootstrapping
config_opts['use_bootstrap'] = False

# Add python3-rpm-macros to resolve %{python3_pkgversion} in rosdep rules
config_opts['chroot_setup_cmd'] += ' python3-rpm-macros'

# Install weak dependencies to get group members
config_opts[f'{config_opts.package_manager}_builddep_opts'] = config_opts.get(f'{config_opts.package_manager}_builddep_opts', []) + ['--setopt=install_weak_deps=True']

@[if env_vars]@
# Set environment vars from the build config
@[for env_key, env_val in env_vars.items()]@
config_opts['environment']['@env_key'] = '@env_val'
@[end for]
@[end if]@
# Make debuginfo/debugsource packages best-effort
config_opts['macros']['%_empty_manifest_terminate_build'] = '%{nil}'
config_opts['macros']['%_missing_build_ids_terminate_build'] = '%{nil}'

# Hack the %{dist} macro to allow release suffixing
config_opts['macros']['%dist'] = '.' + config_opts['dist'] + '%{?dist_suffix}'

# Disable automatic out-of-source CMake builds
config_opts['macros']['%__cmake_in_source_build'] = '1'
config_opts['macros']['%__cmake3_in_source_build'] = '1'

# Required for running mock in Docker
config_opts['use_nspawn'] = False

@[if os_name == 'rhel' and os_code_name == '7']@
# Inject g++ 8 into RHEL 7 builds
config_opts['chroot_setup_cmd'] += ' devtoolset-8-gcc-c++ devtoolset-8-make-nonblocking'
config_opts['macros']['%_buildshell'] = '/usr/bin/scl enable devtoolset-8 -- /bin/sh'

# Disable weak dependencies on RHEL 7 builds
config_opts['macros']['%_without_weak_deps'] = '1'
@[else]@
# Add g++, which is an assumed dependency in ROS
config_opts['chroot_setup_cmd'] += ' gcc-c++ make'
@[end if]@

@[if os_name == 'rhel' and os_code_name == '8']@
# Work around a broken binutils release in RHEL 8.4
config_opts['chroot_setup_cmd'] += ' https://ftp.osuosl.org/pub/ros/download.ros.org/downloads/binutils/binutils-2.30-79.el8.x86_64.rpm'
config_opts[f'{config_opts.package_manager}.conf'] += """
[baseos]
excludepkgs=binutils

"""

@[end if]@
config_opts[f'{config_opts.package_manager}.conf'] += """
@[for i, url in enumerate(distribution_repository_urls)]@
[ros-buildfarm-@(i)]
name=ROS Buildfarm Repository @(i) - $basearch
baseurl=@(url)
gpgkey=file:///etc/pki/mock/RPM-GPG-KEY-ros-buildfarm-@(i)
repo_gpgcheck=@(1 if i < len(distribution_repository_keys) and distribution_repository_keys[i] else 0)
gpgcheck=0
enabled=1
skip_if_unavailable=False

@[end for]@
@[if target_repository]@
[ros-buildfarm-target-source]
name=ROS Buildfarm Repository Target - SRPMS
baseurl=@(target_repository)
repo_gpgcheck=0
gpgcheck=0
enabled=0
skip_if_unavailable=False

@[end if]@
"""

include('/etc/mock/default.cfg')

# Change the root name since we're modifying the chroot
config_opts['root'] += '-ros-buildfarm'

# Disable mock bootstrapping
config_opts['use_bootstrap'] = False

# Add python3-rpm-macros to resolve %{python3_pkgversion} in rosdep rules
config_opts['chroot_setup_cmd'] += ' python3-rpm-macros'

# Install weak dependencies to get group members
config_opts[f'{config_opts.package_manager}_builddep_opts'] = config_opts.get(f'{config_opts.package_manager}_builddep_opts', []) + ['--setopt=install_weak_deps=True']

@[if env_vars]@
# Set environment vars from the build config
@[for env_key, env_val in env_vars.items()]@
config_opts['environment']['@env_key'] = '@env_val'
@[end for]
@[end if]@
# Make debuginfo/debugsource packages best-effort
config_opts['macros']['%_empty_manifest_terminate_build'] = '%{nil}'
config_opts['macros']['%_missing_build_ids_terminate_build'] = '%{nil}'

# Hack the %{dist} macro to allow release suffixing
config_opts['macros']['%dist'] = '.' + config_opts['dist'] + '%{?dist_suffix}'

# Disable automatic out-of-source CMake builds
config_opts['macros']['%__cmake_in_source_build'] = '1'
config_opts['macros']['%__cmake3_in_source_build'] = '1'

# Required for running mock in Docker
config_opts['use_nspawn'] = False

@[if os_name == 'rhel' and os_code_name == '7']@
# Inject g++ 8 into RHEL 7 builds
config_opts['chroot_setup_cmd'] += ' devtoolset-8-gcc-c++ devtoolset-8-make-nonblocking'
config_opts['macros']['%_buildshell'] = '/usr/bin/scl enable devtoolset-8 -- /bin/sh'

# Disable weak dependencies on RHEL 7 builds
config_opts['macros']['%_without_weak_deps'] = '1'
@[else]@
# Add g++, which is an assumed dependency in ROS
config_opts['chroot_setup_cmd'] += ' gcc-c++ make'
@[end if]@

config_opts[f'{config_opts.package_manager}.conf'] += """
@[for i, url in enumerate(distribution_repository_urls)]@
[ros-buildfarm-@(i)]
name=ROS Buildfarm Repository @(i) - $basearch
baseurl=@(url)
gpgkey=file:///etc/pki/mock/RPM-GPG-KEY-ros-buildfarm-@(i)
repo_gpgcheck=@(1 if i < len(distribution_repository_keys) and distribution_repository_keys[i] else 0)
gpgcheck=0
enabled=1
skip_if_unavailable=False

@[end for]@
@[if target_repository]@
[ros-buildfarm-target-source]
name=ROS Buildfarm Repository Target - SRPMS
baseurl=@(target_repository)
repo_gpgcheck=0
gpgcheck=0
enabled=0
skip_if_unavailable=False

@[end if]@
"""


Add snapshot function

Add snapshot function