Difference between revisions of "Binding icons and Setting Alternate Images"

From LinuxMCE
Jump to: navigation, search
m
 
(7 intermediate revisions by one other user not shown)
Line 1: Line 1:
 
Orbiter has some facilities for dynamically changing the images displayed on an Orbiter screen. These essentially fall into two categories, [[Update Object Image]] and Binding Icons/Setting Alternate Images.
 
Orbiter has some facilities for dynamically changing the images displayed on an Orbiter screen. These essentially fall into two categories, [[Update Object Image]] and Binding Icons/Setting Alternate Images.
 +
 +
You will need a copy of [[Designer]] or [[QuickDesigner]] open, to make sense of the following text.
  
 
== Bitmap [[DesignObj]] States ==
 
== Bitmap [[DesignObj]] States ==
Line 23: Line 25:
 
|Display the image specified by Graphic Filename(1). This is the normal, deselected state.
 
|Display the image specified by Graphic Filename(1). This is the normal, deselected state.
 
|-
 
|-
|-1
+
| -1
 
|Display the selected state specified by Selected Graphic File(50).
 
|Display the selected state specified by Selected Graphic File(50).
 
|-
 
|-
|-2
+
| -2
 
|Display the highlighted state specified by Highlighted Graphic File (59)
 
|Display the highlighted state specified by Highlighted Graphic File (59)
 
|-
 
|-
|>0
+
| >0
 
|Anything greater than 0 displays the specified Alternate graphic image. These are specified in order in the Alt Graphic File(51) field, delimited by semicolon.
 
|Anything greater than 0 displays the specified Alternate graphic image. These are specified in order in the Alt Graphic File(51) field, delimited by semicolon.
 
|}
 
|}
 +
 +
=== Caveat ===
 +
 +
This command can be used to great effect to set image states inside a screen, but be careful. Commands attached to a DesignObj execute asynchronously with the render cycle; a screen may render before all the Set Graphic to Display commands are completed, resulting in unexpected behavior. There are two solutions to this, either send a [[Refresh]] command during onTimeout (and setting the Timeout column in DesignObj appropriately, or...
 +
 +
== Bind Icons ==
 +
 +
...Use [[Bind Icon]] to associate a designobj with alternate images, to a name, which can be subsequently changed on the fly with [[Set Bound Icon]]. Each call to Set Bound Icon forces a rendering action, ensuring that the display is always up to date. The [[Set Bound Icon]] command can either be executed as part of the onLoad action in [[Designer]], or it can be done in code, as part of a plugin, like this:
 +
 +
/**
 +
  * Update the scenario icon on the main page of each orbiter with the most recent weather data.
 +
  */
 +
void Weather_PlugIn::UpdateOrbiterWeatherScenarios()
 +
{
 +
  if (!m_setOrbiters.empty())
 +
    {
 +
      string sDeviceIDs;
 +
      for (set<long>::iterator it=m_setOrbiters.begin(); it!=m_setOrbiters.end(); ++it)
 +
        {
 +
          sDeviceIDs+=StringUtils::itos(*it)+",";
 +
        }
 +
      sDeviceIDs=sDeviceIDs.substr(0,sDeviceIDs.size()-1); // strip away trailing comma
 +
 +
      string sText=""; // Doesn't really matter.
 +
      string sType="weather"; // change the weather icon.
 +
      string sValue_To_Assign;
 +
      if (m_mapWeatherValues.find("condicon_current") == m_mapWeatherValues.end())
 +
        {
 +
                sValue_To_Assign="0";
 +
        }
 +
      else
 +
        {
 +
                sValue_To_Assign=StringUtils::itos(m_mapWeatherValues["condicon_current"]);
 +
        }
 +
      CMD_Set_Bound_Icon_DL CMD_Set_Bound_Icon_DL(m_dwPK_Device, sDeviceIDs, sValue_To_Assign, sText, sType);
 +
      SendCommand(CMD_Set_Bound_Icon_DL);
 +
    }
 +
  else
 +
    {
 +
      LoggerWrapper::GetInstance()->Write(LV_WARNING,"Weather_PlugIn::UpdateOrbiterWeatherScenarios() called while I have no orbiters!");
 +
    }
 +
}
 +
 +
In this example, a set of orbiters (which has been acquired by Intercepting the [[Orbiter Registered]] command, and grabbing device IDs and putting them in a set), is being sent a Set Bound Icon command, to update their current weather icon, to a certain value (in this case is numeric, corresponding to one of the alternate images to display).
 +
 +
Meanwhile, the designobj butWeatherMain (6279), contains a Requires Special Handling command, in onStartup. This is handled inside Orbiter.cpp's SpecialHandlingObjectSelected():
 +
 +
// ...
 +
 +
        else if ( pDesignObj_Orbiter->m_iBaseObjectID==DESIGNOBJ_butWeatherMain_CONST || pDesignObj_Orbiter->m_iBaseObjectID==DESIGNOBJ_icoWeatherNow_CONST )
 +
        {
 +
                CMD_Bind_Icon(pDesignObj_Orbiter->m_ObjectID, "weather", true);
 +
        }
 +
 +
// ...
 +
 +
Given the simplicity of this, and not much logic is needed, this could have also been handled in butWeatherMain's onStartup tab as well, in [[Designer]]
 +
 +
The final piece of this puzzle comes in the form of the message interceptors in the Weather Plugin, which react to either an Orbiter registering, or to weather data changing:
 +
 +
In Weather_PlugIn::Register(), a whole bunch of RegisterMsgInterceptors like:
 +
 +
  RegisterMsgInterceptor((MessageInterceptorFn)(&Weather_PlugIn::DataChanged), 0, 0, 0, 0, MESSAGETYPE_EVENT, EVENT_Outside_Temp_Changed_CONST);
 +
 +
Pass any of these events, to the DataChanged() method, which after grabbing the data and populating state, we:
 +
 +
  m_mapWeatherTexts[sName]=sText;
 +
  m_mapWeatherValues[sName]=sValue;
 +
 +
  UpdateOrbiterWeatherScenarios();
 +
 +
return true; // indicating we processed this event and it stops with us.
 +
 +
And there is also a message interceptor for Orbiter Registered:
 +
 +
  RegisterMsgInterceptor((MessageInterceptorFn)(&Weather_PlugIn::OrbiterRegistered),0,0,0,0,MESSAGETYPE_COMMAND,COMMAND_Orbiter_Registered_CONST);
 +
 +
So that when a new orbiter shows up, we keep track of it, and send it an UpdateOrbiterWeatherScenarios() to update its icon:
 +
 +
bool Weather_PlugIn::OrbiterRegistered(class Socket *pSocket, class Message *pMessage, class DeviceData_Base *pDeviceFrom, class DeviceData_Base *pDeviceTo)
 +
{
 +
  bool bRegistered = pMessage->m_mapParameters[COMMANDPARAMETER_OnOff_CONST]=="1";
 +
  LoggerWrapper::GetInstance()->Write(LV_STATUS,"Weather_PlugIn::OrbiterRegistered() Orbiter %d registered %d",pMessage->m_dwPK_Device_From,(int)bRegistered);
 +
 +
  if (bRegistered)
 +
    {
 +
      // Orbiter is Registering.
 +
      if (m_setOrbiters.find(pMessage->m_dwPK_Device_From) == m_setOrbiters.end())
 +
        {
 +
          LoggerWrapper::GetInstance()->Write(LV_STATUS,"Weather_PlugIn::OrbiterRegistered Orbiter %d hasn't registered yet, adding.",pMessage->m_dwPK_Device_From);
 +
          m_setOrbiters.insert(pMessage->m_dwPK_Device_From);
 +
        }
 +
      else
 +
        {
 +
          LoggerWrapper::GetInstance()->Write(LV_WARNING,"Weather_PlugIn::OrbiterRegistered Orbiter %d has attempted to re-register.",pMessage->m_dwPK_Device_From);
 +
        }
 +
    }
 +
  else
 +
    {
 +
      // Orbiter is Unregistering.
 +
      if (m_setOrbiters.find(pMessage->m_dwPK_Device_From) == m_setOrbiters.end())
 +
        {
 +
          LoggerWrapper::GetInstance()->Write(LV_WARNING,"Weather_PlugIn::OrbiterRegistered Orbiter %d asked to unregister, but it wasn't registered!");
 +
        }
 +
      else
 +
        {
 +
          set<long>::iterator it=m_setOrbiters.find(pMessage->m_dwPK_Device_From);
 +
          m_setOrbiters.erase(it);
 +
          LoggerWrapper::GetInstance()->Write(LV_STATUS,"Weather_PlugIn::OrbiterRegistered Orbiter %d has unregistered.",pMessage->m_dwPK_Device_From);
 +
        }
 +
    }
 +
 +
  UpdateOrbiterWeatherScenarios();
 +
 +
  return false; // Let this bubble up the chain.
 +
 +
}
 +
 +
Note that we return false here. We do this, because other plugins (most notably the Orbiter Plugin) need to intercept this event to do important housekeeping procedures!
 +
 +
With all this in place, and a recompile of weather plugin, and the Orbiter binaries, we have a weather icon, that updates whenever Orbiters show up, and whenever weather data changes.
  
 
== Examples of Alternate Image use ==
 
== Examples of Alternate Image use ==
Line 63: Line 186:
  
 
[[Category:HA Designer]]
 
[[Category:HA Designer]]
[[Category:Programmers Guide]]
+
[[Category:Programmer's Guide]]

Latest revision as of 01:07, 9 March 2014

Orbiter has some facilities for dynamically changing the images displayed on an Orbiter screen. These essentially fall into two categories, Update Object Image and Binding Icons/Setting Alternate Images.

You will need a copy of Designer or QuickDesigner open, to make sense of the following text.

Bitmap DesignObj States

A Bitmap DesignObjType can have several image states:

  • Normal - This is the normal rendered image, and is always rendered by OrbiterGen.
  • Selected - This is the image that is shown for a moment while Activating a bitmap, either by pressing on the touch screen, or selecting with remote control. Nominally, this image is displayed for 0.5 seconds, and the delay time is not configurable.
  • Highlighted - When a DesignObj is marked with Is Tab Stop, this indicates that either arrow keys on a remote or a touch screen, can position a cursor over this Bitmap, and it is considered Highlighted.
  • Any number of Alternate Images - Can also be specified. OrbiterGen will render each of these states for this DesignObj, these are not normally activated by user action, but are activated by one of two methods below.

Set Graphic to Display

Orbiters can be sent a Set Graphic To Display, which takes two parameters

  • The DesignObj to change, either in short form, e.g. 6256, or in fully qualified screen form, 6255.0.0.6256
  • The Image to display, which can be one of the following:
# Description
0 Display the image specified by Graphic Filename(1). This is the normal, deselected state.
-1 Display the selected state specified by Selected Graphic File(50).
-2 Display the highlighted state specified by Highlighted Graphic File (59)
>0 Anything greater than 0 displays the specified Alternate graphic image. These are specified in order in the Alt Graphic File(51) field, delimited by semicolon.

Caveat

This command can be used to great effect to set image states inside a screen, but be careful. Commands attached to a DesignObj execute asynchronously with the render cycle; a screen may render before all the Set Graphic to Display commands are completed, resulting in unexpected behavior. There are two solutions to this, either send a Refresh command during onTimeout (and setting the Timeout column in DesignObj appropriately, or...

Bind Icons

...Use Bind Icon to associate a designobj with alternate images, to a name, which can be subsequently changed on the fly with Set Bound Icon. Each call to Set Bound Icon forces a rendering action, ensuring that the display is always up to date. The Set Bound Icon command can either be executed as part of the onLoad action in Designer, or it can be done in code, as part of a plugin, like this:

/**
 * Update the scenario icon on the main page of each orbiter with the most recent weather data.
 */ 
void Weather_PlugIn::UpdateOrbiterWeatherScenarios()
{
 if (!m_setOrbiters.empty())
   {
     string sDeviceIDs;
     for (set<long>::iterator it=m_setOrbiters.begin(); it!=m_setOrbiters.end(); ++it)
       {
         sDeviceIDs+=StringUtils::itos(*it)+",";
       }
     sDeviceIDs=sDeviceIDs.substr(0,sDeviceIDs.size()-1); // strip away trailing comma

     string sText=""; // Doesn't really matter.
     string sType="weather"; // change the weather icon.
     string sValue_To_Assign;
     if (m_mapWeatherValues.find("condicon_current") == m_mapWeatherValues.end())
       {
               sValue_To_Assign="0";
       }
     else
       {
               sValue_To_Assign=StringUtils::itos(m_mapWeatherValues["condicon_current"]);
       }
     CMD_Set_Bound_Icon_DL CMD_Set_Bound_Icon_DL(m_dwPK_Device, sDeviceIDs, sValue_To_Assign, sText, sType);
     SendCommand(CMD_Set_Bound_Icon_DL);
   }
 else
   {
     LoggerWrapper::GetInstance()->Write(LV_WARNING,"Weather_PlugIn::UpdateOrbiterWeatherScenarios() called while I have no orbiters!");
   }
}

In this example, a set of orbiters (which has been acquired by Intercepting the Orbiter Registered command, and grabbing device IDs and putting them in a set), is being sent a Set Bound Icon command, to update their current weather icon, to a certain value (in this case is numeric, corresponding to one of the alternate images to display).

Meanwhile, the designobj butWeatherMain (6279), contains a Requires Special Handling command, in onStartup. This is handled inside Orbiter.cpp's SpecialHandlingObjectSelected():

// ...

       else if ( pDesignObj_Orbiter->m_iBaseObjectID==DESIGNOBJ_butWeatherMain_CONST || pDesignObj_Orbiter->m_iBaseObjectID==DESIGNOBJ_icoWeatherNow_CONST )
       {
               CMD_Bind_Icon(pDesignObj_Orbiter->m_ObjectID, "weather", true);
       }

// ...

Given the simplicity of this, and not much logic is needed, this could have also been handled in butWeatherMain's onStartup tab as well, in Designer

The final piece of this puzzle comes in the form of the message interceptors in the Weather Plugin, which react to either an Orbiter registering, or to weather data changing:

In Weather_PlugIn::Register(), a whole bunch of RegisterMsgInterceptors like:

 RegisterMsgInterceptor((MessageInterceptorFn)(&Weather_PlugIn::DataChanged), 0, 0, 0, 0, MESSAGETYPE_EVENT, EVENT_Outside_Temp_Changed_CONST);

Pass any of these events, to the DataChanged() method, which after grabbing the data and populating state, we:

 m_mapWeatherTexts[sName]=sText;
 m_mapWeatherValues[sName]=sValue;

 UpdateOrbiterWeatherScenarios();

return true; // indicating we processed this event and it stops with us.

And there is also a message interceptor for Orbiter Registered:

 RegisterMsgInterceptor((MessageInterceptorFn)(&Weather_PlugIn::OrbiterRegistered),0,0,0,0,MESSAGETYPE_COMMAND,COMMAND_Orbiter_Registered_CONST);

So that when a new orbiter shows up, we keep track of it, and send it an UpdateOrbiterWeatherScenarios() to update its icon:

bool Weather_PlugIn::OrbiterRegistered(class Socket *pSocket, class Message *pMessage, class DeviceData_Base *pDeviceFrom, class DeviceData_Base *pDeviceTo)
{
 bool bRegistered = pMessage->m_mapParameters[COMMANDPARAMETER_OnOff_CONST]=="1";
 LoggerWrapper::GetInstance()->Write(LV_STATUS,"Weather_PlugIn::OrbiterRegistered() Orbiter %d registered %d",pMessage->m_dwPK_Device_From,(int)bRegistered);

 if (bRegistered)
   {
     // Orbiter is Registering.
     if (m_setOrbiters.find(pMessage->m_dwPK_Device_From) == m_setOrbiters.end())
       {
         LoggerWrapper::GetInstance()->Write(LV_STATUS,"Weather_PlugIn::OrbiterRegistered Orbiter %d hasn't registered yet, adding.",pMessage->m_dwPK_Device_From);
         m_setOrbiters.insert(pMessage->m_dwPK_Device_From);
       }
     else
       {
         LoggerWrapper::GetInstance()->Write(LV_WARNING,"Weather_PlugIn::OrbiterRegistered Orbiter %d has attempted to re-register.",pMessage->m_dwPK_Device_From);
       }
   }
 else
   {
     // Orbiter is Unregistering.
     if (m_setOrbiters.find(pMessage->m_dwPK_Device_From) == m_setOrbiters.end())
       {
         LoggerWrapper::GetInstance()->Write(LV_WARNING,"Weather_PlugIn::OrbiterRegistered Orbiter %d asked to unregister, but it wasn't registered!");
       }
     else
       {
         set<long>::iterator it=m_setOrbiters.find(pMessage->m_dwPK_Device_From);
         m_setOrbiters.erase(it);
         LoggerWrapper::GetInstance()->Write(LV_STATUS,"Weather_PlugIn::OrbiterRegistered Orbiter %d has unregistered.",pMessage->m_dwPK_Device_From);
       }
   }

 UpdateOrbiterWeatherScenarios();

 return false; // Let this bubble up the chain. 

}

Note that we return false here. We do this, because other plugins (most notably the Orbiter Plugin) need to intercept this event to do important housekeeping procedures!

With all this in place, and a recompile of weather plugin, and the Orbiter binaries, we have a weather icon, that updates whenever Orbiters show up, and whenever weather data changes.

Examples of Alternate Image use

PK_DesignObj Description Image What's used
3275 objHouseStatusIndicator ObjHouseStatusIndicator.png This Bitmap is used to indicate house status, there are 5 possible alternate images presented in the icoHouseStatus child inside this object, and these are selected by Set Bound Icon. the UI2 variant of this image also uses Highlighted and Selected states in one of the children to highlight the text part. See the note about UI2 below.
4781 butUserStatus ButUserStatus.png This Bitmap is used to convey each user's user mode. There are again, 5 possible alternative images presented in the icoUserMode, for each user mode. the UI2 variant of this image also uses Highlighted and Selected states in one of the children to highlight the text part. See the note about UI2 below.
5924 butP1KeypadSelect ButP1KeypadSelect.png This Bitmap is used as a radio button toggle between two buttons intended to select whether Player 1 or Player 2 uses a particular orbiter's keypad. There are no normal images, just alternate images which are set both at Startup and on load of the screen. This prevents the problem of odd rendering behavior when these buttons are selected.
6262 butWeatherNow ButWeatherNow.png This is part of a group of Bitmap buttons to indicate which weather screen is selected. A normal state is used for the deselected state, and one alternate image is used for the selected state. (Selected can't be used, because that is only when a Bitmap is activated, and Highlighted also can't be used, because it would change the moment any arrow buttons or keys were to be used.)