<commit_before>/*
 * Copyright (c) 1999-2005 Stephen Williams (steve@icarus.com)
 *
 *    This source code is free software; you can redistribute it
 *    and/or modify it in source code form under the terms of the GNU
 *    General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */
#ifdef HAVE_CVS_IDENT
#ident "$Id: pad_to_width.cc,v 1.19 2005/07/07 16:22:49 steve Exp $"
#endif

# include "config.h"

# include  "netlist.h"
# include  "netmisc.h"

/*
 * This function transforms an expression by padding the high bits
 * with V0 until the expression has the desired width. This may mean
 * not transforming the expression at all, if it is already wide
 * enough.
 */
NetExpr*pad_to_width(NetExpr*expr, unsigned wid)
{
      if (wid <= expr->expr_width())
	    return expr;

	/* If the expression is a const, then replace it with a wider
	   const. This is a more efficient result. */
      if (NetEConst*tmp = dynamic_cast<NetEConst*>(expr)) {
	    verinum eval = tmp->value();
	    bool signed_flag = eval.has_sign();

	    verinum::V pad = verinum::V0;
	    if (signed_flag)
		  pad = eval.get(eval.len()-1);
	    verinum oval (pad, wid, eval.has_len());

	    for (unsigned idx = 0 ;  idx < eval.len() ;  idx += 1)
		  oval.set(idx, eval.get(idx));

	    oval.has_sign(signed_flag);
	    tmp = new NetEConst(oval);
	    delete expr;
	    return tmp;
      }

	/* Do it the hard way, with a concatenation. */
      verinum pad(verinum::V0, wid - expr->expr_width());
      NetEConst*co = new NetEConst(pad);
      co->set_line(*expr);
      NetEConcat*cc = new NetEConcat(2);
      cc->set_line(*expr);
      cc->set(0, co);
      cc->set(1, expr);
      cc->set_width(wid);
      expr = cc;

      return expr;
}

/*
 * Pad a NetNet to the desired vector width by concatenating a
 * NetConst of constant zeros. Use a NetConcat node to do the
 * concatenation.
 */
NetNet*pad_to_width(Design*des, NetNet*net, unsigned wid)
{
      NetScope*scope = net->scope();

      if (net->vector_width() >= wid)
	    return net;

	// Make the NetConcat and connect the input net to the lsb input.
      NetConcat*cc = new NetConcat(scope, scope->local_symbol(), wid, 2);
      des->add_node(cc);
      connect(cc->pin(1), net->pin(0));

	// Make a NetConst of the desired width and connect in to the
	// lsb input of the NetConcat.
      verinum pad(verinum::V0, wid - net->vector_width());
      NetConst*con = new NetConst(scope, scope->local_symbol(), pad);
      des->add_node(con);
      connect(cc->pin(2), con->pin(0));

	// Make a NetNet for the NetConst to NetConcat link.
      NetNet*tmp = new NetNet(scope, scope->local_symbol(),
			       NetNet::WIRE, wid - net->vector_width());
      tmp->data_type( net->data_type() );
      tmp->local_flag(true);
      connect(cc->pin(2), tmp->pin(0));

	// Create a NetNet of the output width and connect it to the
	// NetConcat node output pin.
      tmp = new NetNet(scope, scope->local_symbol(),
		       NetNet::WIRE, wid);
      tmp->data_type( net->data_type() );
      tmp->local_flag(true);
      connect(cc->pin(0), tmp->pin(0));

      return tmp;
}

NetNet*pad_to_width_signed(Design*des, NetNet*net, unsigned wid)
{
      NetScope*scope = net->scope();

      if (net->vector_width() >= wid)
	    return net;

      NetSignExtend*se
	    = new NetSignExtend(scope, scope->local_symbol(), wid);
      se->set_line(*net);
      des->add_node(se);

      NetNet*tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, wid);
      tmp->set_line(*net);
      tmp->local_flag(true);
      tmp->data_type(net->data_type());
      tmp->set_signed(true);

      connect(tmp->pin(0), se->pin(0));
      connect(se->pin(1), net->pin(0));

      return tmp;
}

NetNet*crop_to_width(Design*des, NetNet*net, unsigned wid)
{
      NetScope*scope = net->scope();

      if (net->vector_width() <= wid)
	    return net;

      NetPartSelect*ps = new NetPartSelect(net, 0, wid, NetPartSelect::VP);
      des->add_node(ps);
      ps->set_line(*net);

      NetNet*tmp = new NetNet(scope, scope->local_symbol(),
			      NetNet::WIRE, wid);
      tmp->data_type(net->data_type());
      tmp->local_flag(true);
      tmp->set_line(*tmp);
      connect(ps->pin(0), tmp->pin(0));

      return tmp;
}

/*
 * $Log: pad_to_width.cc,v $
 * Revision 1.19  2005/07/07 16:22:49  steve
 *  Generalize signals to carry types.
 *
 * Revision 1.18  2005/05/24 01:44:28  steve
 *  Do sign extension of structuran nets.
 *
 * Revision 1.17  2005/04/24 23:44:02  steve
 *  Update DFF support to new data flow.
 *
 * Revision 1.16  2005/01/12 03:17:37  steve
 *  Properly pad vector widths in pgassign.
 *
 * Revision 1.15  2004/02/18 17:11:57  steve
 *  Use perm_strings for named langiage items.
 *
 * Revision 1.14  2003/03/06 00:28:42  steve
 *  All NetObj objects have lex_string base names.
 *
 * Revision 1.13  2003/01/27 05:09:17  steve
 *  Spelling fixes.
 *
 * Revision 1.12  2003/01/26 21:15:59  steve
 *  Rework expression parsing and elaboration to
 *  accommodate real/realtime values and expressions.
 *
 * Revision 1.11  2002/08/12 01:35:00  steve
 *  conditional ident string using autoconfig.
 *
 * Revision 1.10  2002/05/25 16:43:22  steve
 *  Better padding of constants.
 *
 * Revision 1.9  2001/10/28 01:14:53  steve
 *  NetObj constructor finally requires a scope.
 *
 * Revision 1.8  2001/07/25 03:10:49  steve
 *  Create a config.h.in file to hold all the config
 *  junk, and support gcc 3.0. (Stephan Boettcher)
 */

<commit_msg> pad_to_width handles signed expressions.<commit_after>/*
 * Copyright (c) 1999-2005 Stephen Williams (steve@icarus.com)
 *
 *    This source code is free software; you can redistribute it
 *    and/or modify it in source code form under the terms of the GNU
 *    General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */
#ifdef HAVE_CVS_IDENT
#ident "$Id: pad_to_width.cc,v 1.20 2005/12/22 15:43:47 steve Exp $"
#endif

# include "config.h"

# include  "netlist.h"
# include  "netmisc.h"

/*
 * This function transforms an expression by padding the high bits
 * with V0 until the expression has the desired width. This may mean
 * not transforming the expression at all, if it is already wide
 * enough.
 */
NetExpr*pad_to_width(NetExpr*expr, unsigned wid)
{
      if (wid <= expr->expr_width())
	    return expr;

	/* If the expression is a const, then replace it with a wider
	   const. This is a more efficient result. */
      if (NetEConst*tmp = dynamic_cast<NetEConst*>(expr)) {
	    verinum eval = tmp->value();
	    bool signed_flag = eval.has_sign();

	    verinum::V pad = verinum::V0;
	    if (signed_flag)
		  pad = eval.get(eval.len()-1);
	    verinum oval (pad, wid, eval.has_len());

	    for (unsigned idx = 0 ;  idx < eval.len() ;  idx += 1)
		  oval.set(idx, eval.get(idx));

	    oval.has_sign(signed_flag);
	    tmp = new NetEConst(oval);
	    delete expr;
	    return tmp;
      }

      NetESelect*tmp = new NetESelect(expr, 0, wid);
      tmp->set_line(*expr);
      tmp->cast_signed(expr->has_sign());
      return tmp;
}

/*
 * Pad a NetNet to the desired vector width by concatenating a
 * NetConst of constant zeros. Use a NetConcat node to do the
 * concatenation.
 */
NetNet*pad_to_width(Design*des, NetNet*net, unsigned wid)
{
      NetScope*scope = net->scope();

      if (net->vector_width() >= wid)
	    return net;

	// Make the NetConcat and connect the input net to the lsb input.
      NetConcat*cc = new NetConcat(scope, scope->local_symbol(), wid, 2);
      des->add_node(cc);
      connect(cc->pin(1), net->pin(0));

	// Make a NetConst of the desired width and connect in to the
	// lsb input of the NetConcat.
      verinum pad(verinum::V0, wid - net->vector_width());
      NetConst*con = new NetConst(scope, scope->local_symbol(), pad);
      des->add_node(con);
      connect(cc->pin(2), con->pin(0));

	// Make a NetNet for the NetConst to NetConcat link.
      NetNet*tmp = new NetNet(scope, scope->local_symbol(),
			       NetNet::WIRE, wid - net->vector_width());
      tmp->data_type( net->data_type() );
      tmp->local_flag(true);
      connect(cc->pin(2), tmp->pin(0));

	// Create a NetNet of the output width and connect it to the
	// NetConcat node output pin.
      tmp = new NetNet(scope, scope->local_symbol(),
		       NetNet::WIRE, wid);
      tmp->data_type( net->data_type() );
      tmp->local_flag(true);
      connect(cc->pin(0), tmp->pin(0));

      return tmp;
}

NetNet*pad_to_width_signed(Design*des, NetNet*net, unsigned wid)
{
      NetScope*scope = net->scope();

      if (net->vector_width() >= wid)
	    return net;

      NetSignExtend*se
	    = new NetSignExtend(scope, scope->local_symbol(), wid);
      se->set_line(*net);
      des->add_node(se);

      NetNet*tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, wid);
      tmp->set_line(*net);
      tmp->local_flag(true);
      tmp->data_type(net->data_type());
      tmp->set_signed(true);

      connect(tmp->pin(0), se->pin(0));
      connect(se->pin(1), net->pin(0));

      return tmp;
}

NetNet*crop_to_width(Design*des, NetNet*net, unsigned wid)
{
      NetScope*scope = net->scope();

      if (net->vector_width() <= wid)
	    return net;

      NetPartSelect*ps = new NetPartSelect(net, 0, wid, NetPartSelect::VP);
      des->add_node(ps);
      ps->set_line(*net);

      NetNet*tmp = new NetNet(scope, scope->local_symbol(),
			      NetNet::WIRE, wid);
      tmp->data_type(net->data_type());
      tmp->local_flag(true);
      tmp->set_line(*tmp);
      connect(ps->pin(0), tmp->pin(0));

      return tmp;
}

/*
 * $Log: pad_to_width.cc,v $
 * Revision 1.20  2005/12/22 15:43:47  steve
 *  pad_to_width handles signed expressions.
 *
 * Revision 1.19  2005/07/07 16:22:49  steve
 *  Generalize signals to carry types.
 *
 * Revision 1.18  2005/05/24 01:44:28  steve
 *  Do sign extension of structuran nets.
 *
 * Revision 1.17  2005/04/24 23:44:02  steve
 *  Update DFF support to new data flow.
 *
 * Revision 1.16  2005/01/12 03:17:37  steve
 *  Properly pad vector widths in pgassign.
 *
 * Revision 1.15  2004/02/18 17:11:57  steve
 *  Use perm_strings for named langiage items.
 *
 * Revision 1.14  2003/03/06 00:28:42  steve
 *  All NetObj objects have lex_string base names.
 *
 * Revision 1.13  2003/01/27 05:09:17  steve
 *  Spelling fixes.
 *
 * Revision 1.12  2003/01/26 21:15:59  steve
 *  Rework expression parsing and elaboration to
 *  accommodate real/realtime values and expressions.
 *
 * Revision 1.11  2002/08/12 01:35:00  steve
 *  conditional ident string using autoconfig.
 *
 * Revision 1.10  2002/05/25 16:43:22  steve
 *  Better padding of constants.
 *
 * Revision 1.9  2001/10/28 01:14:53  steve
 *  NetObj constructor finally requires a scope.
 *
 * Revision 1.8  2001/07/25 03:10:49  steve
 *  Create a config.h.in file to hold all the config
 *  junk, and support gcc 3.0. (Stephan Boettcher)
 */

<|endoftext|>


<commit_before>#include <ClickButton.h>
#include <Homie.h>
#include <EEPROM.h>

#define FW_NAME "homie-sonoff-touch"
// Version number, with letter suffix for any library changes
#define FW_VERSION "2.0.10a"

// Disable this if you don't want the relay to turn on with any single tap event

/* Magic sequence for Autodetectable Binary Upload */
const char *__FLAGGED_FW_NAME = "\xbf\x84\xe4\x13\x54" FW_NAME "\x93\x44\x6b\xa7\x75";
const char *__FLAGGED_FW_VERSION = "\x6a\x3f\x3e\x0e\xe1" FW_VERSION "\xb0\x30\x48\xd4\x1a";
/* End of magic sequence for Autodetectable Binary Upload */

// LED_PIN controls the LED under the wifi symbol on the front plate.
// RELAY_PIN controls the relay, *AND* the LED under the touchpad.
// Scheme is similar to https://github.com/enc-X/sonoff-homie
// ie, mosquitto_pub -h homeauto.vpn.glasgownet.com -t 'devices/85376d51/relay/relayState/set' -m 'ON'

// Nr. of buttons in the array
const int buttons = 1;

const int PIN_RELAY = 12;
const int PIN_LED = 13;
const int PIN_BUTTON = 0;

int function = 0;
unsigned long connectedMillis = 0;
unsigned long watchDogCounterStart=0;
unsigned long keepAliveReceived=0;

// Timer related for dimming delays
unsigned long previousMillis = 0;
// Milliseconds to wait per cycle for a held command.
const int waitInterval = 100;

struct EEpromDataStruct {
  // EEPROM structure
  int keepAliveTimeOut; // 0 - disabled, keepalive time - seconds
  bool initialState;  // Initial state (just after boot - homie independet)
  int watchDogTimeOut; // 0 - disabled, watchdog time limit - seconds
};

EEpromDataStruct EEpromData;

// Associate the PIN_BUTTON GPIO with the ClickButton library
ClickButton button1(PIN_BUTTON, LOW, CLICKBTN_PULLUP);

// Register our two HomieNode instances
HomieNode relayNode("relay", "relay");
HomieNode buttonNode("button", "button");
HomieNode keepAliveNode("keepalive", "keepalive");
HomieNode watchDogNode("watchdog", "Watchdog mode");

bool watchdogTickHandler(const HomieRange& range, const String& value) {
  // Recevied tick message for watchdog
  if (value == "0")
  {
    watchDogCounterStart = 0;
  } else {
    watchDogCounterStart = millis();
  }
  return true;
}

bool watchdogTimeOutHandler(const HomieRange& range, const String& value) {
  // Received watchdog timeout value
  int oldValue = EEpromData.watchDogTimeOut;
  if (value.toInt() > 15)
  {
    EEpromData.watchDogTimeOut = value.toInt();
  }
  if (value=="0")
  {
    EEpromData.watchDogTimeOut = 0;
  }

  if (oldValue!=EEpromData.watchDogTimeOut)
  {
    String outMsg = String(EEpromData.watchDogTimeOut);
    watchDogNode.setProperty("timeOut").send(outMsg);
    EEPROM.put(0, EEpromData);
    EEPROM.commit();
  }
}

bool relayInitModeHandler(HomieRange range, String value) {
  // Initial mode handler
  int oldValue = EEpromData.initialState;
  if (value.toInt() == 1 or value=="ON")
  {
    relayNode.setProperty("relayInitMode").setRetained(false).send("1");
    EEpromData.initialState=1;
  } else {
    relayNode.setProperty("relayInitMode").setRetained(false).send( "0");
    EEpromData.initialState=0;
  }
  if (oldValue!=EEpromData.initialState)
  {
    EEPROM.put(0, EEpromData);
    EEPROM.commit();
  }
  return true;
}

void setupHandler() {
  // Homie setup handler
  HomieRange emptyRange;
  if (EEpromData.initialState) {
    RelayHandler(emptyRange, "ON");
    relayNode.setProperty("relayInitMode").setRetained(false).send("1");
  } else {
    RelayHandler(emptyRange, "OFF");
    relayNode.setProperty("relayInitMode").setRetained(false).send("0");
  }
  String outMsg = String(EEpromData.keepAliveTimeOut);
  keepAliveNode.setProperty("timeOut").send(outMsg);
  outMsg = EEpromData.watchDogTimeOut;
  watchDogNode.setProperty("timeOut").send(outMsg);
  keepAliveReceived=millis();
  #ifdef SONOFFS20
  digitalWrite(PIN_LED, LOW);
  ledNode.setProperty("state").send("off");
  #endif
}

bool RelayHandler(const HomieRange& range, const String& value) {
  // Here we handle incoming requests to set the state of the relay
  // Set the RELAY_PIN to the appropriate level, and additionally set the
  // state on the relayState topic.
  if (value == "ON") {
    digitalWrite(PIN_RELAY, HIGH);
    if ( Homie.isConnected() ) { relayNode.setProperty("relayState").send("ON"); }
    Serial.println("Relay is on");
  } else if (value == "OFF") {
    digitalWrite(PIN_RELAY, LOW);
    if ( Homie.isConnected() ) { relayNode.setProperty("relayState").send("OFF"); }
    Serial.println("Relay is off");
  } else {
    Serial.print("Unknown value: ");
    Serial.println(value);
    return false;
  }
  return true;
}

bool keepAliveTickHandler(HomieRange range, String value) {
  // Keepalive tick handler
  keepAliveReceived=millis();
  return true;
}

bool keepAliveTimeOutHandler(HomieRange range, String value) {
  // Keepalive message handler
  int oldValue = EEpromData.keepAliveTimeOut;
  if (value.toInt() > 0)
  {
    EEpromData.keepAliveTimeOut = value.toInt();
  }
  if (value=="0")
  {
    EEpromData.keepAliveTimeOut = 0;
  }
  if (oldValue!=EEpromData.keepAliveTimeOut)
  {
    String outMsg = String(EEpromData.keepAliveTimeOut);
    keepAliveNode.setProperty("timeOut").send(outMsg);
    EEPROM.put(0, EEpromData);
    EEPROM.commit();
  }
}

// void loopHandler() {
//   //
// }

void onHomieEvent(const HomieEvent& event) {
  // Homie event handler
  switch(event.type) {
    case HomieEventType::CONFIGURATION_MODE: // Default eeprom data in configuration mode
      digitalWrite(PIN_RELAY, LOW);
      EEpromData.keepAliveTimeOut = 0;
      EEpromData.initialState = false;
      EEpromData.watchDogTimeOut = 0;
      EEPROM.put(0, EEpromData);
      EEPROM.commit();
      break;
    case HomieEventType::NORMAL_MODE:
      // Do whatever you want when normal mode is started
      break;
    case HomieEventType::OTA_STARTED:
      // Do whatever you want when OTA mode is started
      digitalWrite(PIN_RELAY, LOW);
      break;
    case HomieEventType::ABOUT_TO_RESET:
      // Do whatever you want when the device is about to reset
      break;
    case HomieEventType::WIFI_CONNECTED:
      // Do whatever you want when Wi-Fi is connected in normal mode
      break;
    case HomieEventType::WIFI_DISCONNECTED:
      // Do whatever you want when Wi-Fi is disconnected in normal mode
      break;
    case HomieEventType::MQTT_DISCONNECTED:
      // Do whatever you want when MQTT is disconnected in normal mode
      break;
  }
}

void setup() {

  Serial.begin(115200);
  Serial.println("Startup: Delaying");
  delay(5000);

  EEPROM.begin(sizeof(EEpromData));
  EEPROM.get(0,EEpromData);
  pinMode(PIN_RELAY, OUTPUT);
  //digitalWrite(PIN_RELAY, LOW);
  if (EEpromData.initialState)
  {
    Serial.println("Startup: Relay On");
    digitalWrite(PIN_RELAY,HIGH);
  } else {
    Serial.println("Startup: Relay Off");
    digitalWrite(PIN_RELAY,LOW);
    EEpromData.initialState=0;
  }

  // Setup button timers (all in milliseconds / ms)
  // (These are default if not set, but changeable for convenience)
  button1.debounceTime   = 20;   // Debounce timer in ms
  button1.multiclickTime = 250;  // Time limit for multi clicks
  button1.longClickTime  = 1000; // time until "held-down clicks" register

  Homie_setFirmware(FW_NAME, FW_VERSION);
  Homie.setLedPin(PIN_LED, HIGH); // Status LED
  // This is a full reset, and will wipe the config
  Homie.setResetTrigger(PIN_BUTTON, LOW, 30000);
  Homie.setSetupFunction(setupHandler);
  //Homie.setLoopFunction(loopHandler);
  relayNode.advertise("relayState").settable(RelayHandler);
  //relayNode.advertise("relayTimer").settable(relayTimerHandler);
  relayNode.advertise("relayInitMode").settable(relayInitModeHandler);
  watchDogNode.advertise("tick").settable(watchdogTickHandler);
  watchDogNode.advertise("timeOut").settable(watchdogTimeOutHandler);
  keepAliveNode.advertise("tick").settable(keepAliveTickHandler);
  keepAliveNode.advertise("timeOut").settable(keepAliveTimeOutHandler);

  Homie.onEvent(onHomieEvent);
  Homie.setup();
  Serial.println("Startup: Complete");
}

void loop() {

  if ( Homie.isConnected() ) {
    // This counter will stop when disconnected
    connectedMillis = millis();
  } else {
    // If disconnected for over 5 minutes
    if ( millis() - connectedMillis >= 300000 ) {
      Serial.println("No connection - restarting");
      // The chances of being in OTA whilst disconnected from MQTT are slim.
      ESP.restart();
    };
  }

  // New section
  // Check if keepalive is supported and expired
  if (EEpromData.keepAliveTimeOut != 0 && (millis() - keepAliveReceived) > EEpromData.keepAliveTimeOut*1000 )
  {
    Serial.println("No keepalive - restarting");
    ESP.restart();
  }
  if (watchDogCounterStart!=0 && EEpromData.watchDogTimeOut!=0 && (millis() - watchDogCounterStart) > EEpromData.watchDogTimeOut * 1000 )
  {
    HomieRange emptyRange;
    //relayTimerHandler(emptyRange, "10"); // Disable relay for 10 sec
    watchDogCounterStart = millis();
  }

  // Update button state
  button1.Update();

  // Save click codes in function, as click codes are reset at next Update()
  if(button1.clicks != 0) function = button1.clicks;

  // These are single event button presses.
  // Handle them and reset function back to 0
  if ( function > 0 ) {
    Serial.println("One-shot");
    if ( function == 1 ) {
      // If not connected, toggle the relay like a normal switch.
      // Calls to relayNode don't work whilst offline.
      if ( !Homie.isConnected() ) {
        Serial.println("Offline toggle of relay");
        digitalWrite(PIN_RELAY, !digitalRead(PIN_RELAY));
      }
      if ( Homie.isConnected() ) {
        relayNode.setProperty("relayState").send("ON");
        buttonNode.setProperty("event").setRetained(false).send("SINGLE");
      }
      // Read the new state, and use it to set the initial startup state
      String PIN_STATE = String(digitalRead(PIN_RELAY));
      // The retained initmode state really needs to be set by the HA system
      // Determine if the local bulb is on or off, and set the desired InitMode of the switch
      //relayNode.setProperty("relayInitMode").setRetained(false).send(PIN_STATE);
      Serial.println("SINGLE click");
    }

    if ( function == 2 ) {
      if ( Homie.isConnected() ) { buttonNode.setProperty("event").setRetained(false).send("DOUBLE"); }
      Serial.println("DOUBLE click");
    }

    if ( function == 3 ) {
      if ( Homie.isConnected() ) { buttonNode.setProperty("event").setRetained(false).send("TRIPLE"); }
      Serial.println("TRIPLE click");
    }
    // This has been a single event.
    function = 0;
  }

  // These are repeat events, where the button is being held down.
  // Handle them, but don't reset function back to 0 unless the button is released.
  if ( function < 0 ) {
    if ( millis() - previousMillis >= waitInterval ) {
      previousMillis = millis();
      if ( function == -1 ) {
        if ( Homie.isConnected() ) { buttonNode.setProperty("event").setRetained(false).send("SINGLEHELD"); }
        Serial.println("SINGLE LONG click");
      }

      if ( function == -2 ) {
        if ( Homie.isConnected() ) { buttonNode.setProperty("event").setRetained(false).send("DOUBLEHELD"); }
        Serial.println("DOUBLE LONG click");
      }

      if ( function == -3 ) {
        if ( Homie.isConnected() ) { buttonNode.setProperty("event").setRetained(false).send("TRIPLEHELD"); }
        Serial.println("TRIPLE LONG click");
      }
    }

    // Decide whether the button is being held or not
    // I much prefer the idea at https://www.home-assistant.io/cookbook/dim_and_brighten_lights/
    // Where a pair of scripts are triggered by one message, and then loop until another message halts them.
    if ( button1.depressed == 1 ) {
      // This will need rate limited
      Serial.println("Held");
    }
    if ( button1.depressed == 0 ) {
      Serial.println("Released");
      function = 0;
    }
    // Rate limit...
    delay(5);
  }

  Homie.loop();
}
<commit_msg>include Arduino.h<commit_after>#include <ClickButton.h>
#include <Homie.h>
#include <EEPROM.h>
#include <Arduino.h>

#define FW_NAME "homie-sonoff-touch"
// Version number, with letter suffix for any library changes
#define FW_VERSION "2.0.10a"

// Disable this if you don't want the relay to turn on with any single tap event

/* Magic sequence for Autodetectable Binary Upload */
const char *__FLAGGED_FW_NAME = "\xbf\x84\xe4\x13\x54" FW_NAME "\x93\x44\x6b\xa7\x75";
const char *__FLAGGED_FW_VERSION = "\x6a\x3f\x3e\x0e\xe1" FW_VERSION "\xb0\x30\x48\xd4\x1a";
/* End of magic sequence for Autodetectable Binary Upload */

// LED_PIN controls the LED under the wifi symbol on the front plate.
// RELAY_PIN controls the relay, *AND* the LED under the touchpad.
// Scheme is similar to https://github.com/enc-X/sonoff-homie
// ie, mosquitto_pub -h homeauto.vpn.glasgownet.com -t 'devices/85376d51/relay/relayState/set' -m 'ON'

// Nr. of buttons in the array
const int buttons = 1;

const int PIN_RELAY = 12;
const int PIN_LED = 13;
const int PIN_BUTTON = 0;

int function = 0;
unsigned long connectedMillis = 0;
unsigned long watchDogCounterStart=0;
unsigned long keepAliveReceived=0;

// Timer related for dimming delays
unsigned long previousMillis = 0;
// Milliseconds to wait per cycle for a held command.
const int waitInterval = 100;

struct EEpromDataStruct {
  // EEPROM structure
  int keepAliveTimeOut; // 0 - disabled, keepalive time - seconds
  bool initialState;  // Initial state (just after boot - homie independet)
  int watchDogTimeOut; // 0 - disabled, watchdog time limit - seconds
};

EEpromDataStruct EEpromData;

// Associate the PIN_BUTTON GPIO with the ClickButton library
ClickButton button1(PIN_BUTTON, LOW, CLICKBTN_PULLUP);

// Register our two HomieNode instances
HomieNode relayNode("relay", "relay");
HomieNode buttonNode("button", "button");
HomieNode keepAliveNode("keepalive", "keepalive");
HomieNode watchDogNode("watchdog", "Watchdog mode");

bool watchdogTickHandler(const HomieRange& range, const String& value) {
  // Recevied tick message for watchdog
  if (value == "0")
  {
    watchDogCounterStart = 0;
  } else {
    watchDogCounterStart = millis();
  }
  return true;
}

bool watchdogTimeOutHandler(const HomieRange& range, const String& value) {
  // Received watchdog timeout value
  int oldValue = EEpromData.watchDogTimeOut;
  if (value.toInt() > 15)
  {
    EEpromData.watchDogTimeOut = value.toInt();
  }
  if (value=="0")
  {
    EEpromData.watchDogTimeOut = 0;
  }

  if (oldValue!=EEpromData.watchDogTimeOut)
  {
    String outMsg = String(EEpromData.watchDogTimeOut);
    watchDogNode.setProperty("timeOut").send(outMsg);
    EEPROM.put(0, EEpromData);
    EEPROM.commit();
  }
}

bool relayInitModeHandler(HomieRange range, String value) {
  // Initial mode handler
  int oldValue = EEpromData.initialState;
  if (value.toInt() == 1 or value=="ON")
  {
    relayNode.setProperty("relayInitMode").setRetained(false).send("1");
    EEpromData.initialState=1;
  } else {
    relayNode.setProperty("relayInitMode").setRetained(false).send( "0");
    EEpromData.initialState=0;
  }
  if (oldValue!=EEpromData.initialState)
  {
    EEPROM.put(0, EEpromData);
    EEPROM.commit();
  }
  return true;
}

void setupHandler() {
  // Homie setup handler
  HomieRange emptyRange;
  if (EEpromData.initialState) {
    RelayHandler(emptyRange, "ON");
    relayNode.setProperty("relayInitMode").setRetained(false).send("1");
  } else {
    RelayHandler(emptyRange, "OFF");
    relayNode.setProperty("relayInitMode").setRetained(false).send("0");
  }
  String outMsg = String(EEpromData.keepAliveTimeOut);
  keepAliveNode.setProperty("timeOut").send(outMsg);
  outMsg = EEpromData.watchDogTimeOut;
  watchDogNode.setProperty("timeOut").send(outMsg);
  keepAliveReceived=millis();
  #ifdef SONOFFS20
  digitalWrite(PIN_LED, LOW);
  ledNode.setProperty("state").send("off");
  #endif
}

bool RelayHandler(const HomieRange& range, const String& value) {
  // Here we handle incoming requests to set the state of the relay
  // Set the RELAY_PIN to the appropriate level, and additionally set the
  // state on the relayState topic.
  if (value == "ON") {
    digitalWrite(PIN_RELAY, HIGH);
    if ( Homie.isConnected() ) { relayNode.setProperty("relayState").send("ON"); }
    Serial.println("Relay is on");
  } else if (value == "OFF") {
    digitalWrite(PIN_RELAY, LOW);
    if ( Homie.isConnected() ) { relayNode.setProperty("relayState").send("OFF"); }
    Serial.println("Relay is off");
  } else {
    Serial.print("Unknown value: ");
    Serial.println(value);
    return false;
  }
  return true;
}

bool keepAliveTickHandler(HomieRange range, String value) {
  // Keepalive tick handler
  keepAliveReceived=millis();
  return true;
}

bool keepAliveTimeOutHandler(HomieRange range, String value) {
  // Keepalive message handler
  int oldValue = EEpromData.keepAliveTimeOut;
  if (value.toInt() > 0)
  {
    EEpromData.keepAliveTimeOut = value.toInt();
  }
  if (value=="0")
  {
    EEpromData.keepAliveTimeOut = 0;
  }
  if (oldValue!=EEpromData.keepAliveTimeOut)
  {
    String outMsg = String(EEpromData.keepAliveTimeOut);
    keepAliveNode.setProperty("timeOut").send(outMsg);
    EEPROM.put(0, EEpromData);
    EEPROM.commit();
  }
}

// void loopHandler() {
//   //
// }

void onHomieEvent(const HomieEvent& event) {
  // Homie event handler
  switch(event.type) {
    case HomieEventType::CONFIGURATION_MODE: // Default eeprom data in configuration mode
      digitalWrite(PIN_RELAY, LOW);
      EEpromData.keepAliveTimeOut = 0;
      EEpromData.initialState = false;
      EEpromData.watchDogTimeOut = 0;
      EEPROM.put(0, EEpromData);
      EEPROM.commit();
      break;
    case HomieEventType::NORMAL_MODE:
      // Do whatever you want when normal mode is started
      break;
    case HomieEventType::OTA_STARTED:
      // Do whatever you want when OTA mode is started
      digitalWrite(PIN_RELAY, LOW);
      break;
    case HomieEventType::ABOUT_TO_RESET:
      // Do whatever you want when the device is about to reset
      break;
    case HomieEventType::WIFI_CONNECTED:
      // Do whatever you want when Wi-Fi is connected in normal mode
      break;
    case HomieEventType::WIFI_DISCONNECTED:
      // Do whatever you want when Wi-Fi is disconnected in normal mode
      break;
    case HomieEventType::MQTT_DISCONNECTED:
      // Do whatever you want when MQTT is disconnected in normal mode
      break;
  }
}

void setup() {

  Serial.begin(115200);
  Serial.println("Startup: Delaying");
  delay(5000);

  EEPROM.begin(sizeof(EEpromData));
  EEPROM.get(0,EEpromData);
  pinMode(PIN_RELAY, OUTPUT);
  //digitalWrite(PIN_RELAY, LOW);
  if (EEpromData.initialState)
  {
    Serial.println("Startup: Relay On");
    digitalWrite(PIN_RELAY,HIGH);
  } else {
    Serial.println("Startup: Relay Off");
    digitalWrite(PIN_RELAY,LOW);
    EEpromData.initialState=0;
  }

  // Setup button timers (all in milliseconds / ms)
  // (These are default if not set, but changeable for convenience)
  button1.debounceTime   = 20;   // Debounce timer in ms
  button1.multiclickTime = 250;  // Time limit for multi clicks
  button1.longClickTime  = 1000; // time until "held-down clicks" register

  Homie_setFirmware(FW_NAME, FW_VERSION);
  Homie.setLedPin(PIN_LED, HIGH); // Status LED
  // This is a full reset, and will wipe the config
  Homie.setResetTrigger(PIN_BUTTON, LOW, 30000);
  Homie.setSetupFunction(setupHandler);
  //Homie.setLoopFunction(loopHandler);
  relayNode.advertise("relayState").settable(RelayHandler);
  //relayNode.advertise("relayTimer").settable(relayTimerHandler);
  relayNode.advertise("relayInitMode").settable(relayInitModeHandler);
  watchDogNode.advertise("tick").settable(watchdogTickHandler);
  watchDogNode.advertise("timeOut").settable(watchdogTimeOutHandler);
  keepAliveNode.advertise("tick").settable(keepAliveTickHandler);
  keepAliveNode.advertise("timeOut").settable(keepAliveTimeOutHandler);

  Homie.onEvent(onHomieEvent);
  Homie.setup();
  Serial.println("Startup: Complete");
}

void loop() {

  if ( Homie.isConnected() ) {
    // This counter will stop when disconnected
    connectedMillis = millis();
  } else {
    // If disconnected for over 5 minutes
    if ( millis() - connectedMillis >= 300000 ) {
      Serial.println("No connection - restarting");
      // The chances of being in OTA whilst disconnected from MQTT are slim.
      ESP.restart();
    };
  }

  // New section
  // Check if keepalive is supported and expired
  if (EEpromData.keepAliveTimeOut != 0 && (millis() - keepAliveReceived) > EEpromData.keepAliveTimeOut*1000 )
  {
    Serial.println("No keepalive - restarting");
    ESP.restart();
  }
  if (watchDogCounterStart!=0 && EEpromData.watchDogTimeOut!=0 && (millis() - watchDogCounterStart) > EEpromData.watchDogTimeOut * 1000 )
  {
    HomieRange emptyRange;
    //relayTimerHandler(emptyRange, "10"); // Disable relay for 10 sec
    watchDogCounterStart = millis();
  }

  // Update button state
  button1.Update();

  // Save click codes in function, as click codes are reset at next Update()
  if(button1.clicks != 0) function = button1.clicks;

  // These are single event button presses.
  // Handle them and reset function back to 0
  if ( function > 0 ) {
    Serial.println("One-shot");
    if ( function == 1 ) {
      // If not connected, toggle the relay like a normal switch.
      // Calls to relayNode don't work whilst offline.
      if ( !Homie.isConnected() ) {
        Serial.println("Offline toggle of relay");
        digitalWrite(PIN_RELAY, !digitalRead(PIN_RELAY));
      }
      if ( Homie.isConnected() ) {
        relayNode.setProperty("relayState").send("ON");
        buttonNode.setProperty("event").setRetained(false).send("SINGLE");
      }
      // Read the new state, and use it to set the initial startup state
      String PIN_STATE = String(digitalRead(PIN_RELAY));
      // The retained initmode state really needs to be set by the HA system
      // Determine if the local bulb is on or off, and set the desired InitMode of the switch
      //relayNode.setProperty("relayInitMode").setRetained(false).send(PIN_STATE);
      Serial.println("SINGLE click");
    }

    if ( function == 2 ) {
      if ( Homie.isConnected() ) { buttonNode.setProperty("event").setRetained(false).send("DOUBLE"); }
      Serial.println("DOUBLE click");
    }

    if ( function == 3 ) {
      if ( Homie.isConnected() ) { buttonNode.setProperty("event").setRetained(false).send("TRIPLE"); }
      Serial.println("TRIPLE click");
    }
    // This has been a single event.
    function = 0;
  }

  // These are repeat events, where the button is being held down.
  // Handle them, but don't reset function back to 0 unless the button is released.
  if ( function < 0 ) {
    if ( millis() - previousMillis >= waitInterval ) {
      previousMillis = millis();
      if ( function == -1 ) {
        if ( Homie.isConnected() ) { buttonNode.setProperty("event").setRetained(false).send("SINGLEHELD"); }
        Serial.println("SINGLE LONG click");
      }

      if ( function == -2 ) {
        if ( Homie.isConnected() ) { buttonNode.setProperty("event").setRetained(false).send("DOUBLEHELD"); }
        Serial.println("DOUBLE LONG click");
      }

      if ( function == -3 ) {
        if ( Homie.isConnected() ) { buttonNode.setProperty("event").setRetained(false).send("TRIPLEHELD"); }
        Serial.println("TRIPLE LONG click");
      }
    }

    // Decide whether the button is being held or not
    // I much prefer the idea at https://www.home-assistant.io/cookbook/dim_and_brighten_lights/
    // Where a pair of scripts are triggered by one message, and then loop until another message halts them.
    if ( button1.depressed == 1 ) {
      // This will need rate limited
      Serial.println("Held");
    }
    if ( button1.depressed == 0 ) {
      Serial.println("Released");
      function = 0;
    }
    // Rate limit...
    delay(5);
  }

  Homie.loop();
}
<|endoftext|>


<commit_before>#include "Config.hpp"
#include "EventOutputQueue.hpp"
#include "PressDownKeys.hpp"
#include "RemapClass.hpp"
#include "VirtualKey.hpp"

namespace org_pqrs_KeyRemap4MacBook {
  Queue* EventOutputQueue::queue_ = NULL;
  IntervalChecker EventOutputQueue::ic_;
  TimerWrapper EventOutputQueue::timer_;

  void
  EventOutputQueue::initialize(IOWorkLoop& workloop)
  {
    queue_ = new Queue();
    ic_.begin();
    timer_.initialize(&workloop, NULL, EventOutputQueue::fire);
  }

  void
  EventOutputQueue::terminate(void)
  {
    timer_.terminate();

    if (queue_) {
      for (;;) {
        EventOutputQueue::Item* p = static_cast<EventOutputQueue::Item*>(queue_->front());
        if (! p) break;

        queue_->pop();
        delete p;
      }
      delete queue_;
    }
  }

  // ----------------------------------------------------------------------
  void
  EventOutputQueue::push(Item& p)
  {
    IOLockWrapper::ScopedLock lk(timer_.getlock());

    if (! queue_) return;

    bool isempty = queue_->empty();
    queue_->push(&p);

    // ------------------------------------------------------------
    if (isempty && ic_.getmillisec() > DELAY) {
      fire_nolock();
    } else {
      IOLOG_DEVEL("Params_KeyboardEventCallBack::Queue enqueued ic_.getmillisec() = %d\n", ic_.getmillisec());
      timer_.setTimeoutMS(DELAY, false);
    }

    ic_.begin();
  }

  void
  EventOutputQueue::push(const Params_KeyboardEventCallBack& p)
  {
    Item* newp = new Item(p);
    if (! newp) return;
    push(*newp);
  }

  void
  EventOutputQueue::push(const Params_UpdateEventFlagsCallback& p)
  {
    Item* newp = new Item(p);
    if (! newp) return;
    push(*newp);
  }

  void
  EventOutputQueue::push(const Params_KeyboardSpecialEventCallback& p)
  {
    Item* newp = new Item(p);
    if (! newp) return;
    push(*newp);
  }

  void
  EventOutputQueue::push(const Params_RelativePointerEventCallback& p)
  {
    Item* newp = new Item(p);
    if (! newp) return;
    push(*newp);
  }

  void
  EventOutputQueue::push(const Params_ScrollWheelEventCallback& p)
  {
    Item* newp = new Item(p);
    if (! newp) return;
    push(*newp);
  }

  // ----------------------------------------------------------------------
  void
  EventOutputQueue::fire(OSObject* owner, IOTimerEventSource* sender)
  {
    IOLockWrapper::ScopedLock lk(timer_.getlock());
    fire_nolock();
  }

  void
  EventOutputQueue::fire_nolock(void)
  {
    if (! queue_) return;

    Item* p = static_cast<Item*>(queue_->front());
    if (! p) return;

    // ----------------------------------------
    queue_->pop();

    switch ((p->params).type) {
      case ParamsUnion::KEYBOARD:
        ((p->params).params.params_KeyboardEventCallBack)->apply();
        break;
      case ParamsUnion::UPDATE_FLAGS:
        ((p->params).params.params_UpdateEventFlagsCallback)->apply();
        break;
      case ParamsUnion::KEYBOARD_SPECIAL:
        ((p->params).params.params_KeyboardSpecialEventCallback)->apply();
        break;
      case ParamsUnion::RELATIVE_POINTER:
        ((p->params).params.params_RelativePointerEventCallback)->apply();
        break;
      case ParamsUnion::SCROLL_POINTER:
        ((p->params).params.params_ScrollWheelEventCallback)->apply();
        break;
    }

    delete p;

    // ----------------------------------------
    if (! queue_->empty()) {
      timer_.setTimeoutMS(DELAY);
    }
  }

  // ======================================================================
  Flags EventOutputQueue::FireModifiers::lastFlags_(0);

  void
  EventOutputQueue::FireModifiers::fire(Flags toFlags, KeyboardType keyboardType)
  {
    // At first I handle KeyUp and handle KeyDown next.
    // We need to end KeyDown at Command+Space to Option_L+Shift_L.
    //
    // When Option_L+Shift_L has a meaning (switch input language at Windows),
    // it does not works well when the last is KeyUp of Command.

    // ------------------------------------------------------------
    // Handle KEYPAD first

    // KeyUp
    if (lastFlags_.isOn(ModifierFlag::KEYPAD) && ! toFlags.isOn(ModifierFlag::KEYPAD)) {
      lastFlags_.remove(ModifierFlag::KEYPAD);

      Params_UpdateEventFlagsCallback::auto_ptr ptr(Params_UpdateEventFlagsCallback::alloc(lastFlags_));
      if (ptr) {
        EventOutputQueue::push(*ptr);
      }
    }
    if (! lastFlags_.isOn(ModifierFlag::KEYPAD) && toFlags.isOn(ModifierFlag::KEYPAD)) {
      lastFlags_.add(ModifierFlag::KEYPAD);

      Params_UpdateEventFlagsCallback::auto_ptr ptr(Params_UpdateEventFlagsCallback::alloc(lastFlags_));
      if (ptr) {
        EventOutputQueue::push(*ptr);
      }
    }

    // ------------------------------------------------------------
    // KeyUp
    for (int i = 0;; ++i) {
      ModifierFlag flag = FlagStatus::getFlag(i);
      if (flag == ModifierFlag::NONE) break;
      if (flag == ModifierFlag::EXTRA1) continue;
      if (flag == ModifierFlag::EXTRA2) continue;
      if (flag == ModifierFlag::EXTRA3) continue;
      if (flag == ModifierFlag::EXTRA4) continue;
      if (flag == ModifierFlag::EXTRA5) continue;

      if (! lastFlags_.isOn(flag)) continue;
      if (toFlags.isOn(flag)) continue;

      lastFlags_.remove(flag);

      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY, lastFlags_, flag.getKeyCode(), keyboardType, false));
      if (! ptr) continue;
      EventOutputQueue::push(*ptr);
    }

    // KeyDown
    for (int i = 0;; ++i) {
      ModifierFlag flag = FlagStatus::getFlag(i);
      if (flag == ModifierFlag::NONE) break;
      if (flag == ModifierFlag::EXTRA1) continue;
      if (flag == ModifierFlag::EXTRA2) continue;
      if (flag == ModifierFlag::EXTRA3) continue;
      if (flag == ModifierFlag::EXTRA4) continue;
      if (flag == ModifierFlag::EXTRA5) continue;

      if (! toFlags.isOn(flag)) continue;
      if (lastFlags_.isOn(flag)) continue;

      lastFlags_.add(flag);

      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY, lastFlags_, flag.getKeyCode(), keyboardType, false));
      if (! ptr) continue;
      EventOutputQueue::push(*ptr);
    }
  }

  // ======================================================================
  void
  EventOutputQueue::FireKey::fire(const Params_KeyboardEventCallBack& params)
  {
    // ----------------------------------------
    // handle virtual keys
    Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(params));
    if (! ptr) return;
    Params_KeyboardEventCallBack& p = *ptr;

    if (Handle_VK_LOCK::handle(p)) return;
    if (Handle_VK_STICKY::handle(p)) return;
    if (Handle_VK_LAZY::handle(p)) return;
    if (Handle_VK_CHANGE_INPUTMODE::handle(p)) return;
    if (Handle_VK_CONFIG::handle(p)) return;
    if (Handle_VK_JIS_TOGGLE_EISUU_KANA::handle(p)) return;
    if (handle_VK_JIS_BACKSLASH(p)) return;
    if (handle_VK_JIS_YEN(p)) return;
    if (Handle_VK_JIS_TEMPORARY::handle(p)) return;
    if (RemapClassManager::handlevirtualkey(p)) return;
    if (p.key == KeyCode::VK_MODIFIER_EXTRA1 ||
        p.key == KeyCode::VK_MODIFIER_EXTRA2 ||
        p.key == KeyCode::VK_MODIFIER_EXTRA3 ||
        p.key == KeyCode::VK_MODIFIER_EXTRA4 ||
        p.key == KeyCode::VK_MODIFIER_EXTRA5) return;

    // ------------------------------------------------------------
    p.key.reverseNormalizeKey(p.flags, p.eventType, p.keyboardType);
    p.flags.stripEXTRA();

    // skip no-outputable keycodes.
    // Note: check before FireModifiers to avoid meaningless modifier event.
    if (p.key == KeyCode::VK_NONE ||
        p.key == KeyCode::VK_PSEUDO_KEY) {
      return;
    }

    FireModifiers::fire(p.flags, p.keyboardType);

    if (p.eventType == EventType::DOWN || p.eventType == EventType::UP) {
      EventOutputQueue::push(p);

      if (p.eventType == EventType::DOWN) {
        PressDownKeys::add(p.key, p.keyboardType);
      } else {
        PressDownKeys::remove(p.key, p.keyboardType);
      }
    }
  }

  void
  EventOutputQueue::FireKey::fire_downup(Flags flags, KeyCode key, KeyboardType keyboardType)
  {
    Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY, flags, key, keyboardType, false));
    if (! ptr) return;
    Params_KeyboardEventCallBack& params = *ptr;

    Flags f = key.getModifierFlag();

    if (f != ModifierFlag::NONE) {
      // We operate FlagStatus for the case "key == KeyCode::CAPSLOCK".
      FlagStatus::increase(f);
      params.flags.add(f);
      FireKey::fire(params);

      FlagStatus::decrease(f);
      params.flags = flags;
      FireKey::fire(params);

    } else {
      params.eventType = EventType::DOWN;
      FireKey::fire(params);
      params.eventType = EventType::UP;
      FireKey::fire(params);
    }
  }

  // ======================================================================
  void
  EventOutputQueue::FireConsumer::fire(const Params_KeyboardSpecialEventCallback& params)
  {
    Params_KeyboardSpecialEventCallback::auto_ptr ptr(Params_KeyboardSpecialEventCallback::alloc(params));
    if (! ptr) return;

    Params_KeyboardSpecialEventCallback& p = *ptr;

    p.flags.stripEXTRA();

    // skip no-outputable keycodes.
    // Note: check before FireModifiers to avoid meaningless modifier event.
    if (p.key == ConsumerKeyCode::VK_NONE ||
        p.key == ConsumerKeyCode::VK_PSEUDO_KEY) {
      return;
    }

    FireModifiers::fire();

    EventOutputQueue::push(p);
  }

  // ======================================================================
  Buttons EventOutputQueue::FireRelativePointer::lastButtons_(0);

  void
  EventOutputQueue::FireRelativePointer::fire(Buttons toButtons, int dx, int dy)
  {
    // We do not fire event if no need.
    if (dx == 0 && dy == 0 && toButtons == lastButtons_) return;

    Params_RelativePointerEventCallback::auto_ptr ptr(Params_RelativePointerEventCallback::alloc(toButtons, dx, dy));
    if (! ptr) return;
    Params_RelativePointerEventCallback& params = *ptr;

    lastButtons_ = toButtons;
    FireModifiers::fire();
    EventOutputQueue::push(params);
  }

  // ======================================================================
  void
  EventOutputQueue::FireScrollWheel::fire(const Params_ScrollWheelEventCallback& params)
  {
    FireModifiers::fire();
    EventOutputQueue::push(params);
  }
}
<commit_msg>add extra NULL check at util/EventOutputQueue.cpp<commit_after>#include "Config.hpp"
#include "EventOutputQueue.hpp"
#include "PressDownKeys.hpp"
#include "RemapClass.hpp"
#include "VirtualKey.hpp"

namespace org_pqrs_KeyRemap4MacBook {
  Queue* EventOutputQueue::queue_ = NULL;
  IntervalChecker EventOutputQueue::ic_;
  TimerWrapper EventOutputQueue::timer_;

  void
  EventOutputQueue::initialize(IOWorkLoop& workloop)
  {
    queue_ = new Queue();
    ic_.begin();
    timer_.initialize(&workloop, NULL, EventOutputQueue::fire);
  }

  void
  EventOutputQueue::terminate(void)
  {
    timer_.terminate();

    if (queue_) {
      for (;;) {
        EventOutputQueue::Item* p = static_cast<EventOutputQueue::Item*>(queue_->front());
        if (! p) break;

        queue_->pop();
        delete p;
      }
      delete queue_;
    }
  }

  // ----------------------------------------------------------------------
  void
  EventOutputQueue::push(Item& p)
  {
    IOLockWrapper::ScopedLock lk(timer_.getlock());

    if (! queue_) return;

    bool isempty = queue_->empty();
    queue_->push(&p);

    // ------------------------------------------------------------
    if (isempty && ic_.getmillisec() > DELAY) {
      fire_nolock();
    } else {
      IOLOG_DEVEL("Params_KeyboardEventCallBack::Queue enqueued ic_.getmillisec() = %d\n", ic_.getmillisec());
      timer_.setTimeoutMS(DELAY, false);
    }

    ic_.begin();
  }

  void
  EventOutputQueue::push(const Params_KeyboardEventCallBack& p)
  {
    Item* newp = new Item(p);
    if (! newp) return;
    push(*newp);
  }

  void
  EventOutputQueue::push(const Params_UpdateEventFlagsCallback& p)
  {
    Item* newp = new Item(p);
    if (! newp) return;
    push(*newp);
  }

  void
  EventOutputQueue::push(const Params_KeyboardSpecialEventCallback& p)
  {
    Item* newp = new Item(p);
    if (! newp) return;
    push(*newp);
  }

  void
  EventOutputQueue::push(const Params_RelativePointerEventCallback& p)
  {
    Item* newp = new Item(p);
    if (! newp) return;
    push(*newp);
  }

  void
  EventOutputQueue::push(const Params_ScrollWheelEventCallback& p)
  {
    Item* newp = new Item(p);
    if (! newp) return;
    push(*newp);
  }

  // ----------------------------------------------------------------------
  void
  EventOutputQueue::fire(OSObject* owner, IOTimerEventSource* sender)
  {
    IOLockWrapper::ScopedLock lk(timer_.getlock());
    fire_nolock();
  }

  void
  EventOutputQueue::fire_nolock(void)
  {
    if (! queue_) return;

    Item* p = static_cast<Item*>(queue_->front());
    if (! p) return;

    // ----------------------------------------
    queue_->pop();

#define CALL_APPLY(PARAMS) { \
    if (PARAMS) {            \
      PARAMS->apply();       \
    }                        \
}

    switch ((p->params).type) {
      case ParamsUnion::KEYBOARD:
        CALL_APPLY((p->params).params.params_KeyboardEventCallBack);
        break;
      case ParamsUnion::UPDATE_FLAGS:
        CALL_APPLY((p->params).params.params_UpdateEventFlagsCallback);
        break;
      case ParamsUnion::KEYBOARD_SPECIAL:
        CALL_APPLY((p->params).params.params_KeyboardSpecialEventCallback);
        break;
      case ParamsUnion::RELATIVE_POINTER:
        CALL_APPLY((p->params).params.params_RelativePointerEventCallback);
        break;
      case ParamsUnion::SCROLL_POINTER:
        CALL_APPLY((p->params).params.params_ScrollWheelEventCallback);
        break;
    }

#undef CALL_APPLY

    delete p;

    // ----------------------------------------
    if (! queue_->empty()) {
      timer_.setTimeoutMS(DELAY);
    }
  }

  // ======================================================================
  Flags EventOutputQueue::FireModifiers::lastFlags_(0);

  void
  EventOutputQueue::FireModifiers::fire(Flags toFlags, KeyboardType keyboardType)
  {
    // At first I handle KeyUp and handle KeyDown next.
    // We need to end KeyDown at Command+Space to Option_L+Shift_L.
    //
    // When Option_L+Shift_L has a meaning (switch input language at Windows),
    // it does not works well when the last is KeyUp of Command.

    // ------------------------------------------------------------
    // Handle KEYPAD first

    // KeyUp
    if (lastFlags_.isOn(ModifierFlag::KEYPAD) && ! toFlags.isOn(ModifierFlag::KEYPAD)) {
      lastFlags_.remove(ModifierFlag::KEYPAD);

      Params_UpdateEventFlagsCallback::auto_ptr ptr(Params_UpdateEventFlagsCallback::alloc(lastFlags_));
      if (ptr) {
        EventOutputQueue::push(*ptr);
      }
    }
    if (! lastFlags_.isOn(ModifierFlag::KEYPAD) && toFlags.isOn(ModifierFlag::KEYPAD)) {
      lastFlags_.add(ModifierFlag::KEYPAD);

      Params_UpdateEventFlagsCallback::auto_ptr ptr(Params_UpdateEventFlagsCallback::alloc(lastFlags_));
      if (ptr) {
        EventOutputQueue::push(*ptr);
      }
    }

    // ------------------------------------------------------------
    // KeyUp
    for (int i = 0;; ++i) {
      ModifierFlag flag = FlagStatus::getFlag(i);
      if (flag == ModifierFlag::NONE) break;
      if (flag == ModifierFlag::EXTRA1) continue;
      if (flag == ModifierFlag::EXTRA2) continue;
      if (flag == ModifierFlag::EXTRA3) continue;
      if (flag == ModifierFlag::EXTRA4) continue;
      if (flag == ModifierFlag::EXTRA5) continue;

      if (! lastFlags_.isOn(flag)) continue;
      if (toFlags.isOn(flag)) continue;

      lastFlags_.remove(flag);

      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY, lastFlags_, flag.getKeyCode(), keyboardType, false));
      if (! ptr) continue;
      EventOutputQueue::push(*ptr);
    }

    // KeyDown
    for (int i = 0;; ++i) {
      ModifierFlag flag = FlagStatus::getFlag(i);
      if (flag == ModifierFlag::NONE) break;
      if (flag == ModifierFlag::EXTRA1) continue;
      if (flag == ModifierFlag::EXTRA2) continue;
      if (flag == ModifierFlag::EXTRA3) continue;
      if (flag == ModifierFlag::EXTRA4) continue;
      if (flag == ModifierFlag::EXTRA5) continue;

      if (! toFlags.isOn(flag)) continue;
      if (lastFlags_.isOn(flag)) continue;

      lastFlags_.add(flag);

      Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY, lastFlags_, flag.getKeyCode(), keyboardType, false));
      if (! ptr) continue;
      EventOutputQueue::push(*ptr);
    }
  }

  // ======================================================================
  void
  EventOutputQueue::FireKey::fire(const Params_KeyboardEventCallBack& params)
  {
    // ----------------------------------------
    // handle virtual keys
    Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(params));
    if (! ptr) return;
    Params_KeyboardEventCallBack& p = *ptr;

    if (Handle_VK_LOCK::handle(p)) return;
    if (Handle_VK_STICKY::handle(p)) return;
    if (Handle_VK_LAZY::handle(p)) return;
    if (Handle_VK_CHANGE_INPUTMODE::handle(p)) return;
    if (Handle_VK_CONFIG::handle(p)) return;
    if (Handle_VK_JIS_TOGGLE_EISUU_KANA::handle(p)) return;
    if (handle_VK_JIS_BACKSLASH(p)) return;
    if (handle_VK_JIS_YEN(p)) return;
    if (Handle_VK_JIS_TEMPORARY::handle(p)) return;
    if (RemapClassManager::handlevirtualkey(p)) return;
    if (p.key == KeyCode::VK_MODIFIER_EXTRA1 ||
        p.key == KeyCode::VK_MODIFIER_EXTRA2 ||
        p.key == KeyCode::VK_MODIFIER_EXTRA3 ||
        p.key == KeyCode::VK_MODIFIER_EXTRA4 ||
        p.key == KeyCode::VK_MODIFIER_EXTRA5) return;

    // ------------------------------------------------------------
    p.key.reverseNormalizeKey(p.flags, p.eventType, p.keyboardType);
    p.flags.stripEXTRA();

    // skip no-outputable keycodes.
    // Note: check before FireModifiers to avoid meaningless modifier event.
    if (p.key == KeyCode::VK_NONE ||
        p.key == KeyCode::VK_PSEUDO_KEY) {
      return;
    }

    FireModifiers::fire(p.flags, p.keyboardType);

    if (p.eventType == EventType::DOWN || p.eventType == EventType::UP) {
      EventOutputQueue::push(p);

      if (p.eventType == EventType::DOWN) {
        PressDownKeys::add(p.key, p.keyboardType);
      } else {
        PressDownKeys::remove(p.key, p.keyboardType);
      }
    }
  }

  void
  EventOutputQueue::FireKey::fire_downup(Flags flags, KeyCode key, KeyboardType keyboardType)
  {
    Params_KeyboardEventCallBack::auto_ptr ptr(Params_KeyboardEventCallBack::alloc(EventType::MODIFY, flags, key, keyboardType, false));
    if (! ptr) return;
    Params_KeyboardEventCallBack& params = *ptr;

    Flags f = key.getModifierFlag();

    if (f != ModifierFlag::NONE) {
      // We operate FlagStatus for the case "key == KeyCode::CAPSLOCK".
      FlagStatus::increase(f);
      params.flags.add(f);
      FireKey::fire(params);

      FlagStatus::decrease(f);
      params.flags = flags;
      FireKey::fire(params);

    } else {
      params.eventType = EventType::DOWN;
      FireKey::fire(params);
      params.eventType = EventType::UP;
      FireKey::fire(params);
    }
  }

  // ======================================================================
  void
  EventOutputQueue::FireConsumer::fire(const Params_KeyboardSpecialEventCallback& params)
  {
    Params_KeyboardSpecialEventCallback::auto_ptr ptr(Params_KeyboardSpecialEventCallback::alloc(params));
    if (! ptr) return;

    Params_KeyboardSpecialEventCallback& p = *ptr;

    p.flags.stripEXTRA();

    // skip no-outputable keycodes.
    // Note: check before FireModifiers to avoid meaningless modifier event.
    if (p.key == ConsumerKeyCode::VK_NONE ||
        p.key == ConsumerKeyCode::VK_PSEUDO_KEY) {
      return;
    }

    FireModifiers::fire();

    EventOutputQueue::push(p);
  }

  // ======================================================================
  Buttons EventOutputQueue::FireRelativePointer::lastButtons_(0);

  void
  EventOutputQueue::FireRelativePointer::fire(Buttons toButtons, int dx, int dy)
  {
    // We do not fire event if no need.
    if (dx == 0 && dy == 0 && toButtons == lastButtons_) return;

    Params_RelativePointerEventCallback::auto_ptr ptr(Params_RelativePointerEventCallback::alloc(toButtons, dx, dy));
    if (! ptr) return;
    Params_RelativePointerEventCallback& params = *ptr;

    lastButtons_ = toButtons;
    FireModifiers::fire();
    EventOutputQueue::push(params);
  }

  // ======================================================================
  void
  EventOutputQueue::FireScrollWheel::fire(const Params_ScrollWheelEventCallback& params)
  {
    FireModifiers::fire();
    EventOutputQueue::push(params);
  }
}
<|endoftext|>


<commit_before>/*=============================================================================
	Copyright (c) 2012-2013 Richard Otis

	Based on example code from Boost MultiIndex.
	Copyright (c) 2003-2008 Joaquin M Lopez Munoz.
	See http://www.boost.org/libs/multi_index for library home page.

    Distributed under the Boost Software License, Version 1.0. (See accompanying
    file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
=============================================================================*/

// header for Gibbs energy model structures
#ifndef INCLUDED_MODELS_HPP
#define INCLUDED_MODELS_HPP

#include "libgibbs/include/optimizer/opt_Gibbs.hpp"
#include <string>
#include <sstream>
#include <boost/spirit/include/support_utree.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>

struct sublattice_entry {
	int index; // sublattice index
	int opt_index; // variable index (for optimizer)
	double num_sites; // number of sites
	double sitefrac; // value of fraction (for optimizer)
	std::string phase;
	std::string species; // species name
	sublattice_entry (
			int index_, int opt_index_, double num_sites_, std::string phase_, std::string species_) :
				index(index_),
				opt_index(opt_index_),
				num_sites(num_sites_),
				phase(phase_),
				species(species_),
				sitefrac(-1) {}
	const std::string name() const {
		// std::to_string exists in C++11 but some compilers are buggy
		std::stringstream ss;
		ss << phase << "_" << index << "_" << species;
		return (const std::string)ss.str();
	}
};

/* Tags for multi-indexing */
struct myindex{};
struct optimizer_index{};
struct phases{};

/* Sublattice entries are sorted first by phase name, then index, then by species.
 * NB: The use of derivation here instead of simple typedef is explained in
 * Compiler specifics: type hiding.
 */

struct subl_sort_key : boost::multi_index::composite_key<
sublattice_entry,
BOOST_MULTI_INDEX_MEMBER(sublattice_entry, std::string,phase),
BOOST_MULTI_INDEX_MEMBER(sublattice_entry,int,index),
BOOST_MULTI_INDEX_MEMBER(sublattice_entry,std::string,species)
>{};

/* see Compiler specifics: composite_key in compilers without partial
 * template specialization, for info on composite_key_result_less
 */

typedef boost::multi_index::multi_index_container<
		sublattice_entry,
		boost::multi_index::indexed_by<
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<myindex>,
		BOOST_MULTI_INDEX_MEMBER(sublattice_entry,int,index)
		>,
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<optimizer_index>,
		BOOST_MULTI_INDEX_MEMBER(sublattice_entry,int,opt_index)
		>,
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<phases>,
		BOOST_MULTI_INDEX_MEMBER(sublattice_entry,std::string,phase)
		>
>
> sublattice_set;

// TODO: fix the view object to work on pointers, not the actual object
typedef boost::multi_index::multi_index_container<
		const sublattice_entry*,
		boost::multi_index::indexed_by<
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<myindex>,
		BOOST_MULTI_INDEX_MEMBER(sublattice_entry,const int,index)
		>,
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<optimizer_index>,
		BOOST_MULTI_INDEX_MEMBER(sublattice_entry,const int,opt_index)
		>,
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<phases>,
		BOOST_MULTI_INDEX_MEMBER(sublattice_entry,const std::string,phase)
		>
>
> sublattice_set_view;

// bridge function between Database and EnergyModels
sublattice_set build_variable_map(
		const Phase_Collection::const_iterator p_begin,
		const Phase_Collection::const_iterator p_end,
		const evalconditions &conditions,
		std::map<std::string, int> &indices
		);

// prototype model
class EnergyModel {
public:
	EnergyModel(const std::string &phasename, const sublattice_set &subl_set, const parameter_set &param_set) {
		// implementation
	}
	EnergyModel(const std::string &phasename, const sublattice_set &subl_set) {
		// implementation
	};
	boost::spirit::utree get_ast() { return model_ast; }
protected:
	boost::spirit::utree model_ast;
	double count_mixing_sites(const sublattice_set_view &ssv);
	boost::spirit::utree add_interaction_factor (
			const std::string &lhs_varname,
			const std::string &rhs_varname,
			const double &degree,
			const boost::spirit::utree &input_tree
			);
	void normalize_utree(boost::spirit::utree &input_tree, const sublattice_set_view &ssv);
	boost::spirit::utree find_parameter_ast(const sublattice_set_view &subl_view, const parameter_set_view &param_view);
	boost::spirit::utree permute_site_fractions (
			const sublattice_set_view &total_view, // all sublattices
			const sublattice_set_view &subl_view, // the active sublattice permutation
			const parameter_set_view &param_view,
			const int &sublindex,
			const double &param_division_factor = 1
			);
	boost::spirit::utree permute_site_fractions_with_interactions (
			const sublattice_set_view &total_view, // all sublattices
			const sublattice_set_view &subl_view, // the active sublattice permutation
			const parameter_set_view &param_view,
			const int &sublindex,
			const double &param_division_factor = 1
			);
private:
	EnergyModel(const EnergyModel&);
	EnergyModel& operator=(const EnergyModel&);
};

// default models
class PureCompoundEnergyModel : public EnergyModel {
public:
	PureCompoundEnergyModel(
			const std::string &phasename,
			const sublattice_set &subl_set,
			const parameter_set &param_set
			);
};

class IdealMixingModel : public EnergyModel {
public:
	IdealMixingModel(
			const std::string &phasename,
			const sublattice_set &subl_set
			);
protected:
	void protect_domain(const std::string &varname, boost::spirit::utree &input_tree);
	boost::spirit::utree make_xlnx(const std::string &varname);
};

class RedlichKisterExcessEnergyModel : public EnergyModel {
public:
	RedlichKisterExcessEnergyModel(
			const std::string &phasename,
			const sublattice_set &subl_set,
			const parameter_set &param_set
			);
};

class IHJMagneticModel : public EnergyModel {
public:
	IHJMagneticModel(
			const std::string &phasename,
			const sublattice_set &subl_set,
			const parameter_set &param_set,
			const double &afm_factor,
			const double &sro_enthalpy_order_fraction
			);
};

#endif
<commit_msg>Fix recursive include<commit_after>/*=============================================================================
	Copyright (c) 2012-2013 Richard Otis

	Based on example code from Boost MultiIndex.
	Copyright (c) 2003-2008 Joaquin M Lopez Munoz.
	See http://www.boost.org/libs/multi_index for library home page.

    Distributed under the Boost Software License, Version 1.0. (See accompanying
    file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
=============================================================================*/

// header for Gibbs energy model structures
#ifndef INCLUDED_MODELS_HPP
#define INCLUDED_MODELS_HPP

#include <string>
#include <sstream>
#include <boost/spirit/include/support_utree.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>

struct sublattice_entry {
	int index; // sublattice index
	int opt_index; // variable index (for optimizer)
	double num_sites; // number of sites
	double sitefrac; // value of fraction (for optimizer)
	std::string phase;
	std::string species; // species name
	sublattice_entry (
			int index_, int opt_index_, double num_sites_, std::string phase_, std::string species_) :
				index(index_),
				opt_index(opt_index_),
				num_sites(num_sites_),
				phase(phase_),
				species(species_),
				sitefrac(-1) {}
	const std::string name() const {
		// std::to_string exists in C++11 but some compilers are buggy
		std::stringstream ss;
		ss << phase << "_" << index << "_" << species;
		return (const std::string)ss.str();
	}
};

/* Tags for multi-indexing */
struct myindex{};
struct optimizer_index{};
struct phases{};

/* Sublattice entries are sorted first by phase name, then index, then by species.
 * NB: The use of derivation here instead of simple typedef is explained in
 * Compiler specifics: type hiding.
 */

struct subl_sort_key : boost::multi_index::composite_key<
sublattice_entry,
BOOST_MULTI_INDEX_MEMBER(sublattice_entry, std::string,phase),
BOOST_MULTI_INDEX_MEMBER(sublattice_entry,int,index),
BOOST_MULTI_INDEX_MEMBER(sublattice_entry,std::string,species)
>{};

/* see Compiler specifics: composite_key in compilers without partial
 * template specialization, for info on composite_key_result_less
 */

typedef boost::multi_index::multi_index_container<
		sublattice_entry,
		boost::multi_index::indexed_by<
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<myindex>,
		BOOST_MULTI_INDEX_MEMBER(sublattice_entry,int,index)
		>,
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<optimizer_index>,
		BOOST_MULTI_INDEX_MEMBER(sublattice_entry,int,opt_index)
		>,
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<phases>,
		BOOST_MULTI_INDEX_MEMBER(sublattice_entry,std::string,phase)
		>
>
> sublattice_set;

// TODO: fix the view object to work on pointers, not the actual object
typedef boost::multi_index::multi_index_container<
		const sublattice_entry*,
		boost::multi_index::indexed_by<
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<myindex>,
		BOOST_MULTI_INDEX_MEMBER(sublattice_entry,const int,index)
		>,
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<optimizer_index>,
		BOOST_MULTI_INDEX_MEMBER(sublattice_entry,const int,opt_index)
		>,
		boost::multi_index::ordered_non_unique<
		boost::multi_index::tag<phases>,
		BOOST_MULTI_INDEX_MEMBER(sublattice_entry,const std::string,phase)
		>
>
> sublattice_set_view;

// bridge function between Database and EnergyModels
sublattice_set build_variable_map(
		const Phase_Collection::const_iterator p_begin,
		const Phase_Collection::const_iterator p_end,
		const evalconditions &conditions,
		std::map<std::string, int> &indices
		);

// prototype model
class EnergyModel {
public:
	EnergyModel(const std::string &phasename, const sublattice_set &subl_set, const parameter_set &param_set) {
		// implementation
	}
	EnergyModel(const std::string &phasename, const sublattice_set &subl_set) {
		// implementation
	};
	boost::spirit::utree get_ast() { return model_ast; }
protected:
	boost::spirit::utree model_ast;
	double count_mixing_sites(const sublattice_set_view &ssv);
	boost::spirit::utree add_interaction_factor (
			const std::string &lhs_varname,
			const std::string &rhs_varname,
			const double &degree,
			const boost::spirit::utree &input_tree
			);
	void normalize_utree(boost::spirit::utree &input_tree, const sublattice_set_view &ssv);
	boost::spirit::utree find_parameter_ast(const sublattice_set_view &subl_view, const parameter_set_view &param_view);
	boost::spirit::utree permute_site_fractions (
			const sublattice_set_view &total_view, // all sublattices
			const sublattice_set_view &subl_view, // the active sublattice permutation
			const parameter_set_view &param_view,
			const int &sublindex,
			const double &param_division_factor = 1
			);
	boost::spirit::utree permute_site_fractions_with_interactions (
			const sublattice_set_view &total_view, // all sublattices
			const sublattice_set_view &subl_view, // the active sublattice permutation
			const parameter_set_view &param_view,
			const int &sublindex,
			const double &param_division_factor = 1
			);
private:
	EnergyModel(const EnergyModel&);
	EnergyModel& operator=(const EnergyModel&);
};

// default models
class PureCompoundEnergyModel : public EnergyModel {
public:
	PureCompoundEnergyModel(
			const std::string &phasename,
			const sublattice_set &subl_set,
			const parameter_set &param_set
			);
};

class IdealMixingModel : public EnergyModel {
public:
	IdealMixingModel(
			const std::string &phasename,
			const sublattice_set &subl_set
			);
protected:
	void protect_domain(const std::string &varname, boost::spirit::utree &input_tree);
	boost::spirit::utree make_xlnx(const std::string &varname);
};

class RedlichKisterExcessEnergyModel : public EnergyModel {
public:
	RedlichKisterExcessEnergyModel(
			const std::string &phasename,
			const sublattice_set &subl_set,
			const parameter_set &param_set
			);
};

class IHJMagneticModel : public EnergyModel {
public:
	IHJMagneticModel(
			const std::string &phasename,
			const sublattice_set &subl_set,
			const parameter_set &param_set,
			const double &afm_factor,
			const double &sro_enthalpy_order_fraction
			);
};

#endif
<|endoftext|>


<commit_before>#include "editor.h"
#include "ui_editor.h"

#include "ui/link_item.h"
#include "ui/socket_item.h"

#include "ui/colors.h"
#include "ui/elements_list.h"

#include <QAction>
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QDockWidget>
#include <QFileDialog>
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QMessageBox>
#include <QPainterPath>
#include <QPolygonF>
#include <QPushButton>
#include <QToolBox>
#include <QUrl>
#include <cctype>
#include <fstream>
#include <typeinfo>
#include <vector>

#include "core/registry.h"
#include "core/version.h"
#include "elements/logic/all.h"
#include "nodes/node.h"
#include "ui/package_view.h"

QString const PACKAGES_DIR{ "../packages" };

Editor::Editor(QWidget *a_parent)
  : QMainWindow{ a_parent }
  , m_ui{ new Ui::Editor }
{
  setObjectName("SpaghettiEditor");
  m_ui->setupUi(this);
  m_ui->libraryToolBox->removeItem(0);
  m_ui->tabWidget->removeTab(0);

  connect(m_ui->actionNew, &QAction::triggered, this, &Editor::newPackage);
  connect(m_ui->actionOpen, &QAction::triggered, this, &Editor::openPackage);
  connect(m_ui->actionSave, &QAction::triggered, this, &Editor::savePackage);
  connect(m_ui->actionClose, &QAction::triggered, this, &Editor::closePackage);
  connect(m_ui->actionCloseAll, &QAction::triggered, this, &Editor::closeAllPackages);

  connect(m_ui->actionDeleteElement, &QAction::triggered, this, &Editor::deleteElement);

  connect(m_ui->actionShowLibrary, &QAction::triggered, this, &Editor::showLibrary);
  connect(m_ui->actionShowProperties, &QAction::triggered, this, &Editor::showProperties);

  connect(m_ui->actionBuildCommit, &QAction::triggered, this, &Editor::buildCommit);
  connect(m_ui->actionRecentChanges, &QAction::triggered, this, &Editor::recentChanges);
  connect(m_ui->actionAbout, &QAction::triggered, this, &Editor::about);
  connect(m_ui->actionAboutQt, &QAction::triggered, this, &Editor::aboutQt);
  connect(m_ui->tabWidget, &QTabWidget::tabCloseRequested, this, &Editor::tabCloseRequested);
  connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, &Editor::tabChanged);

  QDir packagesDir{ PACKAGES_DIR };
  if (!packagesDir.exists()) packagesDir.mkpath(".");

  m_ui->propertiesTable->clear();
  m_ui->propertiesTable->setColumnCount(2);
  m_ui->propertiesTable->setHorizontalHeaderLabels(QString("Name;Value").split(";"));
  m_ui->propertiesTable->horizontalHeader()->setStretchLastSection(true);
  m_ui->propertiesTable->setRowCount(3);

  populateLibrary();
  newPackage();
}

Editor::~Editor()
{
  closeAllPackages();
  delete m_ui;
}

void Editor::tabCloseRequested(int a_index)
{
  QTabWidget *const tab{ m_ui->tabWidget };
  QWidget *const widget{ tab->widget(a_index) };
  PackageView *const packageView{ reinterpret_cast<PackageView *>(widget) };

  if (packageView->canClose()) {
    tab->removeTab(a_index);
    delete packageView;
  }
}

void Editor::tabChanged(int a_index)
{
  m_packageViewIndex = a_index;
}

void Editor::populateLibrary()
{
  core::Registry const &registry{ core::Registry::get() };
  auto const &elements{ registry.elements() };

  for (auto &&element : elements) {
    auto const &info = element.second;
    std::string const path{ info.type };
    std::string category{ path };

    if (auto const it = path.find_first_of('/'); it != std::string::npos) category = path.substr(0, it);
    category[0] = static_cast<char>(std::toupper(category[0]));

    addElement(QString::fromStdString(category), QString::fromStdString(info.name), QString::fromStdString(info.type),
               QString::fromStdString(info.icon));
  }
}

void Editor::addElement(QString a_category, QString a_name, QString a_type, QString a_icon)
{
  QToolBox *const toolbox{ m_ui->libraryToolBox };

  ElementsList *list{};

  int const count{ toolbox->count() };
  for (int i = 0; i < count; ++i) {
    QString const text{ toolbox->itemText(i) };
    if (text != a_category) continue;

    list = qobject_cast<ElementsList *>(toolbox->widget(i));
    assert(list);
    break;
  }

  if (list == nullptr) {
    list = new ElementsList{ this };
    toolbox->addItem(list, a_category);
  }

  QListWidgetItem *const item{ new QListWidgetItem{ a_name } };
  item->setData(ElementsList::eMetaDataType, a_type);
  item->setData(ElementsList::eMetaDataName, a_name);
  item->setData(ElementsList::eMetaDataIcon, a_icon);
  item->setIcon(QIcon(a_icon));
  list->addItem(item);

  list->sortItems();
}

void Editor::aboutToQuit() {}

void Editor::showEvent(QShowEvent *a_event)
{
  static bool s_firstTime{ true };

  if (s_firstTime) {
    s_firstTime = false;
    auto *const tab = m_ui->tabWidget;
    auto const index = tab->currentIndex();
    auto packageView = qobject_cast<PackageView *const>(tab->widget(index));
    packageView->center();
  }

  QMainWindow::showEvent(a_event);
}

PackageView *Editor::packageViewForIndex(int const a_index) const
{
  auto *const tabWidget = m_ui->tabWidget;
  auto const index = a_index == -1 ? tabWidget->currentIndex() : a_index;
  auto packageView = qobject_cast<PackageView *const>(tabWidget->widget(index));
  return packageView;
}

int Editor::openPackageViews() const
{
  return m_ui->tabWidget->count();
}

void Editor::newPackage()
{
  auto *const packageView = new PackageView{ m_ui->propertiesTable };
  m_packageViewIndex = m_ui->tabWidget->addTab(packageView, "New package");
  m_ui->tabWidget->setCurrentIndex(m_packageViewIndex);
  packageView->showProperties();
}

void Editor::openPackage()
{
  QString const filename{ QFileDialog::getOpenFileName(this, "Open .package", PACKAGES_DIR, "*.package") };
  if (filename.isEmpty()) return;

  newPackage();

  auto *const packageView = packageViewForIndex(m_packageViewIndex);
  packageView->setFilename(filename);

  QDir const packagesDir{ PACKAGES_DIR };
  m_ui->tabWidget->setTabText(m_packageViewIndex, packagesDir.relativeFilePath(filename));

  packageView->open();
  packageView->showProperties();
}

void Editor::savePackage()
{
  assert(m_packageViewIndex >= 0);

  auto *const packageView = packageViewForIndex(m_packageViewIndex);

  if (packageView->filename().isEmpty()) {
    QString filename{ QFileDialog::getSaveFileName(this, "Save .package", PACKAGES_DIR, "*.package") };
    if (filename.isEmpty()) return;
    if (!filename.endsWith(".package")) filename += ".package";

    packageView->setFilename(filename);
    QDir const packagesDir{ PACKAGES_DIR };
    m_ui->tabWidget->setTabText(m_packageViewIndex, packagesDir.relativeFilePath(filename));
  }

  packageView->save();
}

void Editor::closePackage()
{
  closePackageView(m_packageViewIndex);
}

void Editor::closeAllPackages()
{
  while (int count = openPackageViews()) closePackageView(count - 1);
}

void Editor::closePackageView(int const a_index)
{
  auto *const packageView = packageViewForIndex(a_index);
  if (packageView->canClose()) m_ui->tabWidget->removeTab(a_index);
  delete packageView;
}

void Editor::deleteElement()
{
  auto *const packageView = packageViewForIndex(m_packageViewIndex);
  packageView->deleteElement();
}

void Editor::showLibrary(bool a_checked)
{
  m_ui->library->setVisible(a_checked);
}

void Editor::showProperties(bool a_checked)
{
  m_ui->properties->setVisible(a_checked);
}

void Editor::buildCommit()
{
  QUrl const url{ QString("https://github.com/aljen/spaghetti/tree/%1").arg(core::version::COMMIT_HASH) };
  QDesktopServices::openUrl(url);
}

void Editor::recentChanges()
{
  QUrl const url{ QString("https://github.com/aljen/spaghetti/compare/%1...master").arg(core::version::COMMIT_HASH) };
  QDesktopServices::openUrl(url);
}

void Editor::about()
{
  QMessageBox::about(
      this, "About Spaghetti",
      QString("Spaghetti version: %1
"
              "
"
              "Copyright © 2017 Artur Wyszyński
"
              "
"
              "Build date: %2, %3
"
              "Git branch: %4
"
              "Git commit: %5
"
              "
"
              "Used libraries:
"
              "JSON for Modern C++ by Niels Lohmann
"
              "An industrial-strength lock-free queue for "
              "C++ by cameron314
"
              "A fast, memory efficient hash map for C++ by "
              "Gregory Popovitch
"
              "Boost libraries
")
          .arg(core::version::STRING)
          .arg(__DATE__)
          .arg(__TIME__)
          .arg(core::version::BRANCH)
          .arg(core::version::COMMIT_SHORT_HASH));
}

void Editor::aboutQt()
{
  QMessageBox::aboutQt(this);
}
<commit_msg>Reset selected node in Editor<commit_after>#include "editor.h"
#include "ui_editor.h"

#include "ui/link_item.h"
#include "ui/socket_item.h"

#include "ui/colors.h"
#include "ui/elements_list.h"

#include <QAction>
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QDockWidget>
#include <QFileDialog>
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QMessageBox>
#include <QPainterPath>
#include <QPolygonF>
#include <QPushButton>
#include <QToolBox>
#include <QUrl>
#include <cctype>
#include <fstream>
#include <typeinfo>
#include <vector>

#include "core/registry.h"
#include "core/version.h"
#include "elements/logic/all.h"
#include "nodes/node.h"
#include "ui/package_view.h"

QString const PACKAGES_DIR{ "../packages" };

Editor::Editor(QWidget *a_parent)
  : QMainWindow{ a_parent }
  , m_ui{ new Ui::Editor }
{
  setObjectName("SpaghettiEditor");
  m_ui->setupUi(this);
  m_ui->libraryToolBox->removeItem(0);
  m_ui->tabWidget->removeTab(0);

  connect(m_ui->actionNew, &QAction::triggered, this, &Editor::newPackage);
  connect(m_ui->actionOpen, &QAction::triggered, this, &Editor::openPackage);
  connect(m_ui->actionSave, &QAction::triggered, this, &Editor::savePackage);
  connect(m_ui->actionClose, &QAction::triggered, this, &Editor::closePackage);
  connect(m_ui->actionCloseAll, &QAction::triggered, this, &Editor::closeAllPackages);

  connect(m_ui->actionDeleteElement, &QAction::triggered, this, &Editor::deleteElement);

  connect(m_ui->actionShowLibrary, &QAction::triggered, this, &Editor::showLibrary);
  connect(m_ui->actionShowProperties, &QAction::triggered, this, &Editor::showProperties);

  connect(m_ui->actionBuildCommit, &QAction::triggered, this, &Editor::buildCommit);
  connect(m_ui->actionRecentChanges, &QAction::triggered, this, &Editor::recentChanges);
  connect(m_ui->actionAbout, &QAction::triggered, this, &Editor::about);
  connect(m_ui->actionAboutQt, &QAction::triggered, this, &Editor::aboutQt);
  connect(m_ui->tabWidget, &QTabWidget::tabCloseRequested, this, &Editor::tabCloseRequested);
  connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, &Editor::tabChanged);

  QDir packagesDir{ PACKAGES_DIR };
  if (!packagesDir.exists()) packagesDir.mkpath(".");

  m_ui->propertiesTable->clear();
  m_ui->propertiesTable->setColumnCount(2);
  m_ui->propertiesTable->setHorizontalHeaderLabels(QString("Name;Value").split(";"));
  m_ui->propertiesTable->horizontalHeader()->setStretchLastSection(true);
  m_ui->propertiesTable->setRowCount(3);

  populateLibrary();
  newPackage();
}

Editor::~Editor()
{
  closeAllPackages();
  delete m_ui;
}

void Editor::tabCloseRequested(int a_index)
{
  QTabWidget *const tab{ m_ui->tabWidget };
  QWidget *const widget{ tab->widget(a_index) };
  PackageView *const packageView{ reinterpret_cast<PackageView *>(widget) };

  if (packageView->canClose()) {
    tab->removeTab(a_index);
    delete packageView;
  }
}

void Editor::tabChanged(int a_index)
{
  m_packageViewIndex = a_index;
}

void Editor::populateLibrary()
{
  core::Registry const &registry{ core::Registry::get() };
  auto const &elements{ registry.elements() };

  for (auto &&element : elements) {
    auto const &info = element.second;
    std::string const path{ info.type };
    std::string category{ path };

    if (auto const it = path.find_first_of('/'); it != std::string::npos) category = path.substr(0, it);
    category[0] = static_cast<char>(std::toupper(category[0]));

    addElement(QString::fromStdString(category), QString::fromStdString(info.name), QString::fromStdString(info.type),
               QString::fromStdString(info.icon));
  }
}

void Editor::addElement(QString a_category, QString a_name, QString a_type, QString a_icon)
{
  QToolBox *const toolbox{ m_ui->libraryToolBox };

  ElementsList *list{};

  int const count{ toolbox->count() };
  for (int i = 0; i < count; ++i) {
    QString const text{ toolbox->itemText(i) };
    if (text != a_category) continue;

    list = qobject_cast<ElementsList *>(toolbox->widget(i));
    assert(list);
    break;
  }

  if (list == nullptr) {
    list = new ElementsList{ this };
    toolbox->addItem(list, a_category);
  }

  QListWidgetItem *const item{ new QListWidgetItem{ a_name } };
  item->setData(ElementsList::eMetaDataType, a_type);
  item->setData(ElementsList::eMetaDataName, a_name);
  item->setData(ElementsList::eMetaDataIcon, a_icon);
  item->setIcon(QIcon(a_icon));
  list->addItem(item);

  list->sortItems();
}

void Editor::aboutToQuit() {}

void Editor::showEvent(QShowEvent *a_event)
{
  static bool s_firstTime{ true };

  if (s_firstTime) {
    s_firstTime = false;
    auto *const tab = m_ui->tabWidget;
    auto const index = tab->currentIndex();
    auto packageView = qobject_cast<PackageView *const>(tab->widget(index));
    packageView->center();
  }

  QMainWindow::showEvent(a_event);
}

PackageView *Editor::packageViewForIndex(int const a_index) const
{
  auto *const tabWidget = m_ui->tabWidget;
  auto const index = a_index == -1 ? tabWidget->currentIndex() : a_index;
  auto packageView = qobject_cast<PackageView *const>(tabWidget->widget(index));
  return packageView;
}

int Editor::openPackageViews() const
{
  return m_ui->tabWidget->count();
}

void Editor::newPackage()
{
  auto *const packageView = new PackageView{ m_ui->propertiesTable };
  m_packageViewIndex = m_ui->tabWidget->addTab(packageView, "New package");
  m_ui->tabWidget->setCurrentIndex(m_packageViewIndex);
  packageView->setSelectedNode(nullptr);
  packageView->showProperties();
}

void Editor::openPackage()
{
  QString const filename{ QFileDialog::getOpenFileName(this, "Open .package", PACKAGES_DIR, "*.package") };
  if (filename.isEmpty()) return;

  newPackage();

  auto *const packageView = packageViewForIndex(m_packageViewIndex);
  packageView->setFilename(filename);

  QDir const packagesDir{ PACKAGES_DIR };
  m_ui->tabWidget->setTabText(m_packageViewIndex, packagesDir.relativeFilePath(filename));

  packageView->open();
  packageView->setSelectedNode(nullptr);
  packageView->showProperties();
}

void Editor::savePackage()
{
  assert(m_packageViewIndex >= 0);

  auto *const packageView = packageViewForIndex(m_packageViewIndex);

  if (packageView->filename().isEmpty()) {
    QString filename{ QFileDialog::getSaveFileName(this, "Save .package", PACKAGES_DIR, "*.package") };
    if (filename.isEmpty()) return;
    if (!filename.endsWith(".package")) filename += ".package";

    packageView->setFilename(filename);
    QDir const packagesDir{ PACKAGES_DIR };
    m_ui->tabWidget->setTabText(m_packageViewIndex, packagesDir.relativeFilePath(filename));
  }

  packageView->save();
}

void Editor::closePackage()
{
  closePackageView(m_packageViewIndex);
}

void Editor::closeAllPackages()
{
  while (int count = openPackageViews()) closePackageView(count - 1);
}

void Editor::closePackageView(int const a_index)
{
  auto *const packageView = packageViewForIndex(a_index);
  if (packageView->canClose()) m_ui->tabWidget->removeTab(a_index);
  delete packageView;
}

void Editor::deleteElement()
{
  auto *const packageView = packageViewForIndex(m_packageViewIndex);
  packageView->deleteElement();
}

void Editor::showLibrary(bool a_checked)
{
  m_ui->library->setVisible(a_checked);
}

void Editor::showProperties(bool a_checked)
{
  m_ui->properties->setVisible(a_checked);
}

void Editor::buildCommit()
{
  QUrl const url{ QString("https://github.com/aljen/spaghetti/tree/%1").arg(core::version::COMMIT_HASH) };
  QDesktopServices::openUrl(url);
}

void Editor::recentChanges()
{
  QUrl const url{ QString("https://github.com/aljen/spaghetti/compare/%1...master").arg(core::version::COMMIT_HASH) };
  QDesktopServices::openUrl(url);
}

void Editor::about()
{
  QMessageBox::about(
      this, "About Spaghetti",
      QString("Spaghetti version: %1
"
              "
"
              "Copyright © 2017 Artur Wyszyński
"
              "
"
              "Build date: %2, %3
"
              "Git branch: %4
"
              "Git commit: %5
"
              "
"
              "Used libraries:
"
              "JSON for Modern C++ by Niels Lohmann
"
              "An industrial-strength lock-free queue for "
              "C++ by cameron314
"
              "A fast, memory efficient hash map for C++ by "
              "Gregory Popovitch
"
              "Boost libraries
")
          .arg(core::version::STRING)
          .arg(__DATE__)
          .arg(__TIME__)
          .arg(core::version::BRANCH)
          .arg(core::version::COMMIT_SHORT_HASH));
}

void Editor::aboutQt()
{
  QMessageBox::aboutQt(this);
}
<|endoftext|>