<commit_before>/******************************************************************************
 * Copyright 2019 Google
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/
#include "CloudIoTCoreMqtt.h"

// Forward global callback declarations
String getJwt();
void setupCert();
Client* setupNetwork(bool);
void messageReceived(String &topic, String &payload);

///////////////////////////////
// MQTT common functions
///////////////////////////////
CloudIoTCoreMqtt::CloudIoTCoreMqtt(
    MQTTClient *_mqttClient, Client *_netClient, CloudIoTCoreDevice *_device){
  this->mqttClient = _mqttClient;
  this->netClient = _netClient;
  this->device = _device;
}

void CloudIoTCoreMqtt::loop() {
  if (millis() > device->getExpMillis() && mqttClient->connected()) {
    // reconnect
    Serial.println("Reconnecting before JWT expiration");
    iat = 0; // Force JWT regeneration
    getJwt(); // Regenerate JWT using device function
    mqttClient->disconnect();
    mqttConnect(true); // TODO: should we skip closing connection
  }
  this->mqttClient->loop();
}

void CloudIoTCoreMqtt::mqttConnect(bool skip) {
  Serial.print("\nconnecting...");
  bool keepgoing = true;
  while (keepgoing) {
    bool result =
        this->mqttClient->connect(
            device->getClientId().c_str(),
            "unused",
            getJwt().c_str(),
            skip);

    if (this->mqttClient->lastError() != LWMQTT_SUCCESS && result){
      // TODO: refactorme
      // Inform the client why it could not connect and help debugging.
      logError();
      logReturnCode();
      logConfiguration(false);

      // See https://cloud.google.com/iot/docs/how-tos/exponential-backoff
      if (this->__backoff__ < this->__minbackoff__) {
        this->__backoff__ = this->__minbackoff__;
      }
      this->__backoff__ = (this->__backoff__ * this->__factor__) + random(this->__jitter__);
      if (this->__backoff__ > this->__max_backoff__) {
        this->__backoff__ = this->__max_backoff__;
      }

      // Clean up the client
      this->mqttClient->disconnect();
      skip = false;
      Serial.println("Delaying " + String(this->__backoff__) + "ms");
      delay(this->__backoff__);
      keepgoing = true;
    } else {
      Serial.println(mqttClient->connected() ? "connected" : "not connected");
      if (!mqttClient->connected()) {
        Serial.println("Settings incorrect or missing a cyper for SSL");
        mqttClient->disconnect();
        logConfiguration(false);
        skip = false;
        keepgoing = true;
        Serial.println("Waiting 60 seconds, retry will likely fail");
        delay(this->__max_backoff__);
      } else {
        // We're now connected
        Serial.println("\nLibrary connected!");
        keepgoing = false;
        this->__backoff__ = this->__minbackoff__;
      }
    }
  }

  // Set QoS to 1 (ack) for configuration messages
  this->mqttClient->subscribe(device->getConfigTopic(), 1);
  // QoS 0 (no ack) for commands
  this->mqttClient->subscribe(device->getCommandsTopic(), 0);

  onConnect();
}

void CloudIoTCoreMqtt::mqttConnectAsync(bool skip) {
  Serial.print("\nconnecting...");

  bool result =
      this->mqttClient->connect(
          device->getClientId().c_str(),
          "unused",
          getJwt().c_str(),
          skip);

  if (this->mqttClient->lastError() != LWMQTT_SUCCESS && result == true){
    // TODO: refactorme
    // Inform the client why it could not connect and help debugging.
    logError();
    logReturnCode();
    logConfiguration(false);

    // See https://cloud.google.com/iot/docs/how-tos/exponential-backoff
    if (this->__backoff__ < this->__minbackoff__) {
      this->__backoff__ = this->__minbackoff__;
    }
    this->__backoff__ = (this->__backoff__ * this->__factor__) + random(this->__jitter__);
    if (this->__backoff__ > this->__max_backoff__) {
      this->__backoff__ = this->__max_backoff__;
    }

    // Clean up the client
    this->mqttClient->disconnect();
    skip = false;
    // Serial.println("Delaying " + String(this->__backoff__) + "ms");
    // delay(this->__backoff__);
  } else {
    Serial.println(mqttClient->connected() ? "connected" : "not connected");
    if (!mqttClient->connected()) {
      Serial.println("No internet or Settings incorrect or missing a cyper for SSL");
      mqttClient->disconnect();
      logConfiguration(false);
      skip = false;
      Serial.println("\naborting mqtt connection attempt, lets rety later...\tLibrary not connected!");
      // delay(this->__max_backoff__);
    } else {
      // We're now connected
      Serial.println("\nLibrary connected!");
      this->__backoff__ = this->__minbackoff__;
    }
  }

  // Set QoS to 1 (ack) for configuration messages
  this->mqttClient->subscribe(device->getConfigTopic(), 1);
  // QoS 0 (no ack) for commands
  this->mqttClient->subscribe(device->getCommandsTopic(), 0);

  onConnect();
}

void CloudIoTCoreMqtt::startMQTT() {
  if (this->useLts) {
    this->mqttClient->begin(CLOUD_IOT_CORE_MQTT_HOST_LTS, CLOUD_IOT_CORE_MQTT_PORT, *netClient);
  } else {
    this->mqttClient->begin(CLOUD_IOT_CORE_MQTT_HOST, CLOUD_IOT_CORE_MQTT_PORT, *netClient);
  }
  this->mqttClient->onMessage(messageReceived);
}

bool CloudIoTCoreMqtt::publishTelemetry(String data) {
  return this->mqttClient->publish(device->getEventsTopic(), data);
}

bool CloudIoTCoreMqtt::publishTelemetry(String data, int qos) {
  return this->mqttClient->publish(device->getEventsTopic(), data, false, qos);
}

bool CloudIoTCoreMqtt::publishTelemetry(const char* data, int length) {
  return this->mqttClient->publish(device->getEventsTopic().c_str(), data, length);
}

bool CloudIoTCoreMqtt::publishTelemetry(String subtopic, String data) {
  return this->mqttClient->publish(device->getEventsTopic() + subtopic, data);
}

bool CloudIoTCoreMqtt::publishTelemetry(String subtopic, String data, int qos) {
  return this->mqttClient->publish(device->getEventsTopic() + subtopic, data, false, qos);
}

bool CloudIoTCoreMqtt::publishTelemetry(String subtopic, const char* data, int length) {
  return this->mqttClient->publish(String(device->getEventsTopic() + subtopic).c_str(), data, length);
}

// Helper that just sends default sensor
bool CloudIoTCoreMqtt::publishState(String data) {
  return this->mqttClient->publish(device->getStateTopic(), data);
}

bool CloudIoTCoreMqtt::publishState(const char* data, int length) {
  return this->mqttClient->publish(device->getStateTopic().c_str(), data, length);
}

void CloudIoTCoreMqtt::logError() {
  Serial.println(this->mqttClient->lastError());
  switch(this->mqttClient->lastError()) {
    case (LWMQTT_BUFFER_TOO_SHORT):
      Serial.println("LWMQTT_BUFFER_TOO_SHORT");
      break;
    case (LWMQTT_VARNUM_OVERFLOW):
      Serial.println("LWMQTT_VARNUM_OVERFLOW");
      break;
    case (LWMQTT_NETWORK_FAILED_CONNECT):
      Serial.println("LWMQTT_NETWORK_FAILED_CONNECT");
      break;
    case (LWMQTT_NETWORK_TIMEOUT):
      Serial.println("LWMQTT_NETWORK_TIMEOUT");
      break;
    case (LWMQTT_NETWORK_FAILED_READ):
      Serial.println("LWMQTT_NETWORK_FAILED_READ");
      break;
    case (LWMQTT_NETWORK_FAILED_WRITE):
      Serial.println("LWMQTT_NETWORK_FAILED_WRITE");
      break;
    case (LWMQTT_REMAINING_LENGTH_OVERFLOW):
      Serial.println("LWMQTT_REMAINING_LENGTH_OVERFLOW");
      break;
    case (LWMQTT_REMAINING_LENGTH_MISMATCH):
      Serial.println("LWMQTT_REMAINING_LENGTH_MISMATCH");
      break;
    case (LWMQTT_MISSING_OR_WRONG_PACKET):
      Serial.println("LWMQTT_MISSING_OR_WRONG_PACKET");
      break;
    case (LWMQTT_CONNECTION_DENIED):
      Serial.println("LWMQTT_CONNECTION_DENIED");
      break;
    case (LWMQTT_FAILED_SUBSCRIPTION):
      Serial.println("LWMQTT_FAILED_SUBSCRIPTION");
      break;
    case (LWMQTT_SUBACK_ARRAY_OVERFLOW):
      Serial.println("LWMQTT_SUBACK_ARRAY_OVERFLOW");
      break;
    case (LWMQTT_PONG_TIMEOUT):
      Serial.println("LWMQTT_PONG_TIMEOUT");
      break;
    default:
      Serial.println("This error code should never be reached.");
      break;
  }
}

void CloudIoTCoreMqtt::logConfiguration(bool showJWT) {
  Serial.println("Connect with " + String(CLOUD_IOT_CORE_MQTT_HOST_LTS) +
      ":" + String(CLOUD_IOT_CORE_MQTT_PORT));
  Serial.println("ClientId: " + device->getClientId());
  if (showJWT) {
    Serial.println("JWT: " + getJwt());
  }
}

void CloudIoTCoreMqtt::logReturnCode() {
  Serial.println(this->mqttClient->returnCode());
  switch(this->mqttClient->returnCode()) {
    case (LWMQTT_CONNECTION_ACCEPTED):
      Serial.println("OK");
      break;
    case (LWMQTT_UNACCEPTABLE_PROTOCOL):
      Serial.println("LWMQTT_UNACCEPTABLE_PROTOCOLL");
      break;
    case (LWMQTT_IDENTIFIER_REJECTED):
      Serial.println("LWMQTT_IDENTIFIER_REJECTED");
      break;
    case (LWMQTT_SERVER_UNAVAILABLE):
      Serial.println("LWMQTT_SERVER_UNAVAILABLE");
      break;
    case (LWMQTT_BAD_USERNAME_OR_PASSWORD):
      Serial.println("LWMQTT_BAD_USERNAME_OR_PASSWORD");
      iat = 0; // Force JWT regeneration
      break;
    case (LWMQTT_NOT_AUTHORIZED):
      Serial.println("LWMQTT_NOT_AUTHORIZED");
      iat = 0; // Force JWT regeneration
      break;
    case (LWMQTT_UNKNOWN_RETURN_CODE):
      Serial.println("LWMQTT_UNKNOWN_RETURN_CODE");
      break;
    default:
      Serial.println("This return code should never be reached.");
      break;
  }
}

void CloudIoTCoreMqtt::onConnect() {
  if (logConnect) {
    publishState("connected");
    publishTelemetry("/events", device->getDeviceId() + String("-connected"));
  }
}

void CloudIoTCoreMqtt::setLogConnect(boolean enabled) {
  this->logConnect = enabled;
}

void CloudIoTCoreMqtt::setUseLts(boolean enabled) {
  this->useLts = enabled;
}
<commit_msg>fix: Log correct host (#192)<commit_after>/******************************************************************************
 * Copyright 2019 Google
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/
#include "CloudIoTCoreMqtt.h"

// Forward global callback declarations
String getJwt();
void setupCert();
Client* setupNetwork(bool);
void messageReceived(String &topic, String &payload);

///////////////////////////////
// MQTT common functions
///////////////////////////////
CloudIoTCoreMqtt::CloudIoTCoreMqtt(
    MQTTClient *_mqttClient, Client *_netClient, CloudIoTCoreDevice *_device){
  this->mqttClient = _mqttClient;
  this->netClient = _netClient;
  this->device = _device;
}

void CloudIoTCoreMqtt::loop() {
  if (millis() > device->getExpMillis() && mqttClient->connected()) {
    // reconnect
    Serial.println("Reconnecting before JWT expiration");
    iat = 0; // Force JWT regeneration
    getJwt(); // Regenerate JWT using device function
    mqttClient->disconnect();
    mqttConnect(true); // TODO: should we skip closing connection
  }
  this->mqttClient->loop();
}

void CloudIoTCoreMqtt::mqttConnect(bool skip) {
  Serial.print("\nconnecting...");
  bool keepgoing = true;
  while (keepgoing) {
    bool result =
        this->mqttClient->connect(
            device->getClientId().c_str(),
            "unused",
            getJwt().c_str(),
            skip);

    if (this->mqttClient->lastError() != LWMQTT_SUCCESS && result){
      // TODO: refactorme
      // Inform the client why it could not connect and help debugging.
      logError();
      logReturnCode();
      logConfiguration(false);

      // See https://cloud.google.com/iot/docs/how-tos/exponential-backoff
      if (this->__backoff__ < this->__minbackoff__) {
        this->__backoff__ = this->__minbackoff__;
      }
      this->__backoff__ = (this->__backoff__ * this->__factor__) + random(this->__jitter__);
      if (this->__backoff__ > this->__max_backoff__) {
        this->__backoff__ = this->__max_backoff__;
      }

      // Clean up the client
      this->mqttClient->disconnect();
      skip = false;
      Serial.println("Delaying " + String(this->__backoff__) + "ms");
      delay(this->__backoff__);
      keepgoing = true;
    } else {
      Serial.println(mqttClient->connected() ? "connected" : "not connected");
      if (!mqttClient->connected()) {
        Serial.println("Settings incorrect or missing a cyper for SSL");
        mqttClient->disconnect();
        logConfiguration(false);
        skip = false;
        keepgoing = true;
        Serial.println("Waiting 60 seconds, retry will likely fail");
        delay(this->__max_backoff__);
      } else {
        // We're now connected
        Serial.println("\nLibrary connected!");
        keepgoing = false;
        this->__backoff__ = this->__minbackoff__;
      }
    }
  }

  // Set QoS to 1 (ack) for configuration messages
  this->mqttClient->subscribe(device->getConfigTopic(), 1);
  // QoS 0 (no ack) for commands
  this->mqttClient->subscribe(device->getCommandsTopic(), 0);

  onConnect();
}

void CloudIoTCoreMqtt::mqttConnectAsync(bool skip) {
  Serial.print("\nconnecting...");

  bool result =
      this->mqttClient->connect(
          device->getClientId().c_str(),
          "unused",
          getJwt().c_str(),
          skip);

  if (this->mqttClient->lastError() != LWMQTT_SUCCESS && result == true){
    // TODO: refactorme
    // Inform the client why it could not connect and help debugging.
    logError();
    logReturnCode();
    logConfiguration(false);

    // See https://cloud.google.com/iot/docs/how-tos/exponential-backoff
    if (this->__backoff__ < this->__minbackoff__) {
      this->__backoff__ = this->__minbackoff__;
    }
    this->__backoff__ = (this->__backoff__ * this->__factor__) + random(this->__jitter__);
    if (this->__backoff__ > this->__max_backoff__) {
      this->__backoff__ = this->__max_backoff__;
    }

    // Clean up the client
    this->mqttClient->disconnect();
    skip = false;
    // Serial.println("Delaying " + String(this->__backoff__) + "ms");
    // delay(this->__backoff__);
  } else {
    Serial.println(mqttClient->connected() ? "connected" : "not connected");
    if (!mqttClient->connected()) {
      Serial.println("No internet or Settings incorrect or missing a cyper for SSL");
      mqttClient->disconnect();
      logConfiguration(false);
      skip = false;
      Serial.println("\naborting mqtt connection attempt, lets rety later...\tLibrary not connected!");
      // delay(this->__max_backoff__);
    } else {
      // We're now connected
      Serial.println("\nLibrary connected!");
      this->__backoff__ = this->__minbackoff__;
    }
  }

  // Set QoS to 1 (ack) for configuration messages
  this->mqttClient->subscribe(device->getConfigTopic(), 1);
  // QoS 0 (no ack) for commands
  this->mqttClient->subscribe(device->getCommandsTopic(), 0);

  onConnect();
}

void CloudIoTCoreMqtt::startMQTT() {
  this->mqttClient->begin(useLts ? CLOUD_IOT_CORE_MQTT_HOST_LTS : CLOUD_IOT_CORE_MQTT_HOST,
    CLOUD_IOT_CORE_MQTT_PORT, *netClient);
  this->mqttClient->onMessage(messageReceived);
}

bool CloudIoTCoreMqtt::publishTelemetry(String data) {
  return this->mqttClient->publish(device->getEventsTopic(), data);
}

bool CloudIoTCoreMqtt::publishTelemetry(String data, int qos) {
  return this->mqttClient->publish(device->getEventsTopic(), data, false, qos);
}

bool CloudIoTCoreMqtt::publishTelemetry(const char* data, int length) {
  return this->mqttClient->publish(device->getEventsTopic().c_str(), data, length);
}

bool CloudIoTCoreMqtt::publishTelemetry(String subtopic, String data) {
  return this->mqttClient->publish(device->getEventsTopic() + subtopic, data);
}

bool CloudIoTCoreMqtt::publishTelemetry(String subtopic, String data, int qos) {
  return this->mqttClient->publish(device->getEventsTopic() + subtopic, data, false, qos);
}

bool CloudIoTCoreMqtt::publishTelemetry(String subtopic, const char* data, int length) {
  return this->mqttClient->publish(String(device->getEventsTopic() + subtopic).c_str(), data, length);
}

// Helper that just sends default sensor
bool CloudIoTCoreMqtt::publishState(String data) {
  return this->mqttClient->publish(device->getStateTopic(), data);
}

bool CloudIoTCoreMqtt::publishState(const char* data, int length) {
  return this->mqttClient->publish(device->getStateTopic().c_str(), data, length);
}

void CloudIoTCoreMqtt::logError() {
  Serial.println(this->mqttClient->lastError());
  switch(this->mqttClient->lastError()) {
    case (LWMQTT_BUFFER_TOO_SHORT):
      Serial.println("LWMQTT_BUFFER_TOO_SHORT");
      break;
    case (LWMQTT_VARNUM_OVERFLOW):
      Serial.println("LWMQTT_VARNUM_OVERFLOW");
      break;
    case (LWMQTT_NETWORK_FAILED_CONNECT):
      Serial.println("LWMQTT_NETWORK_FAILED_CONNECT");
      break;
    case (LWMQTT_NETWORK_TIMEOUT):
      Serial.println("LWMQTT_NETWORK_TIMEOUT");
      break;
    case (LWMQTT_NETWORK_FAILED_READ):
      Serial.println("LWMQTT_NETWORK_FAILED_READ");
      break;
    case (LWMQTT_NETWORK_FAILED_WRITE):
      Serial.println("LWMQTT_NETWORK_FAILED_WRITE");
      break;
    case (LWMQTT_REMAINING_LENGTH_OVERFLOW):
      Serial.println("LWMQTT_REMAINING_LENGTH_OVERFLOW");
      break;
    case (LWMQTT_REMAINING_LENGTH_MISMATCH):
      Serial.println("LWMQTT_REMAINING_LENGTH_MISMATCH");
      break;
    case (LWMQTT_MISSING_OR_WRONG_PACKET):
      Serial.println("LWMQTT_MISSING_OR_WRONG_PACKET");
      break;
    case (LWMQTT_CONNECTION_DENIED):
      Serial.println("LWMQTT_CONNECTION_DENIED");
      break;
    case (LWMQTT_FAILED_SUBSCRIPTION):
      Serial.println("LWMQTT_FAILED_SUBSCRIPTION");
      break;
    case (LWMQTT_SUBACK_ARRAY_OVERFLOW):
      Serial.println("LWMQTT_SUBACK_ARRAY_OVERFLOW");
      break;
    case (LWMQTT_PONG_TIMEOUT):
      Serial.println("LWMQTT_PONG_TIMEOUT");
      break;
    default:
      Serial.println("This error code should never be reached.");
      break;
  }
}

void CloudIoTCoreMqtt::logConfiguration(bool showJWT) {
  Serial.println("Connect with " + String(useLts ? CLOUD_IOT_CORE_MQTT_HOST_LTS : CLOUD_IOT_CORE_MQTT_HOST) +
      ":" + String(CLOUD_IOT_CORE_MQTT_PORT));
  Serial.println("ClientId: " + device->getClientId());
  if (showJWT) {
    Serial.println("JWT: " + getJwt());
  }
}

void CloudIoTCoreMqtt::logReturnCode() {
  Serial.println(this->mqttClient->returnCode());
  switch(this->mqttClient->returnCode()) {
    case (LWMQTT_CONNECTION_ACCEPTED):
      Serial.println("OK");
      break;
    case (LWMQTT_UNACCEPTABLE_PROTOCOL):
      Serial.println("LWMQTT_UNACCEPTABLE_PROTOCOLL");
      break;
    case (LWMQTT_IDENTIFIER_REJECTED):
      Serial.println("LWMQTT_IDENTIFIER_REJECTED");
      break;
    case (LWMQTT_SERVER_UNAVAILABLE):
      Serial.println("LWMQTT_SERVER_UNAVAILABLE");
      break;
    case (LWMQTT_BAD_USERNAME_OR_PASSWORD):
      Serial.println("LWMQTT_BAD_USERNAME_OR_PASSWORD");
      iat = 0; // Force JWT regeneration
      break;
    case (LWMQTT_NOT_AUTHORIZED):
      Serial.println("LWMQTT_NOT_AUTHORIZED");
      iat = 0; // Force JWT regeneration
      break;
    case (LWMQTT_UNKNOWN_RETURN_CODE):
      Serial.println("LWMQTT_UNKNOWN_RETURN_CODE");
      break;
    default:
      Serial.println("This return code should never be reached.");
      break;
  }
}

void CloudIoTCoreMqtt::onConnect() {
  if (logConnect) {
    publishState("connected");
    publishTelemetry("/events", device->getDeviceId() + String("-connected"));
  }
}

void CloudIoTCoreMqtt::setLogConnect(boolean enabled) {
  this->logConnect = enabled;
}

void CloudIoTCoreMqtt::setUseLts(boolean enabled) {
  this->useLts = enabled;
}
<|endoftext|>


<commit_before>// Copyright 2010 Anders Bakken
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <QtTest/QtTest>

#include <textedit.h>
#include <textdocument.h>
QT_FORWARD_DECLARE_CLASS(TextEdit)

//TESTED_CLASS=
//TESTED_FILES=

// Need Qt 4.6.0> for qWaitForWindowShown
#if (QT_VERSION < QT_VERSION_CHECK(4, 6, 0))

#ifdef Q_WS_X11
extern void qt_x11_wait_for_window_manager(QWidget *w);
#endif

namespace QTest
{
    inline static bool qWaitForWindowShown(QWidget *window)
    {
#if defined(Q_WS_X11)
        qt_x11_wait_for_window_manager(window);
        QCoreApplication::processEvents();
#elif defined(Q_WS_QWS)
        Q_UNUSED(window);
        qWait(100);
#else
        Q_UNUSED(window);
        qWait(50);
#endif
        return true;
    }

}

QT_END_NAMESPACE

#endif // Qt < 4.6.0

class tst_TextEdit : public QObject
{
    Q_OBJECT

public:
    tst_TextEdit();
    virtual ~tst_TextEdit();

private slots:
    void clickInReadOnlyEdit();
    void scrollReadOnly();
    void scrollLines();
    void cursorForPosition();
    void columnNumberIssue();
    void selectionTest();
    void sectionTest();
    void deleteTest();
    void emptyDocumentTest();
    void changeDocumentTest();
};

tst_TextEdit::tst_TextEdit()
{
}

tst_TextEdit::~tst_TextEdit()
{
}

void tst_TextEdit::scrollReadOnly()
{
    TextEdit edit;
    QString line = "12434567890\n";
    QString text;
    for (int i=0; i<1000; ++i) {
        text.append(line);
    }
    edit.document()->setText(text);
    edit.setReadOnly(true);
    edit.resize(400, 400);
    edit.show();
    QTest::qWaitForWindowShown(&edit);
//     edit.setCursorPosition(0);
//     edit.verticalScrollBar()->setValue(0);
//    qDebug() << edit.cursorPosition() << edit.verticalScrollBar()->value();
//     QEventLoop loop;
//     loop.exec();
    QTest::keyClick(&edit, Qt::Key_Down);
    QVERIFY(edit.verticalScrollBar()->value() != 0);
}

void tst_TextEdit::scrollLines()
{
    TextEdit edit;
    QString text = "Line 1\nLine2\nLine 3\n\nLine 5\n\nLine 7\nLine 8\n\nLine 10\n";
    int numLines = text.count('\n');
    edit.document()->setText(text);

    QList<int> lineSteps;
    lineSteps << 1 << 3; // Single-click, mouse wheel

    QList<int> directions;
    directions << 1 << -1; // Forwards, backwards

    QScrollBar *vsb = edit.verticalScrollBar();

    foreach(int lineStep, lineSteps) {
        int lineNo = 0;
        //printf("%d-line step\n", lineStep);
        foreach(int direction, directions) {
            //printf("  %s\n", direction>0?"Forwards":"Backwards");
            if (direction>0) {
                // Forwards
                vsb->setSliderPosition( 0 );
            }

            // Forwards
            while (lineNo <= numLines && lineNo >= 0) {
                int actualLineNo = edit.lineNumber(edit.viewportPosition());
                //printf("    Line %d\n", actualLineNo);
                if (lineNo != actualLineNo) {
                    printf("  %s: expected to be at line %d, actually at line %d\n",
                           (direction > 0 ? "Forwards" : "Backwards"), lineNo, actualLineNo);
                }
                QCOMPARE(lineNo, actualLineNo);
                for (int i=0; i<lineStep; lineNo+=direction, ++i) {
                    vsb->triggerAction(direction > 0
                                       ? QAbstractSlider::SliderSingleStepAdd
                                       : QAbstractSlider::SliderSingleStepSub);
                }
            }
            lineNo = numLines;
        }
    }
}

void tst_TextEdit::clickInReadOnlyEdit()
{
    TextEdit edit;
    QString line = "1234567890\n";
    QString text;
    for (int i=0; i<1000; ++i) {
        text.append(line);
    }
    edit.document()->setText(text);
    edit.setReadOnly(true);
    edit.resize(400, 400);
    edit.show();
    QTest::qWaitForWindowShown(&edit);
//     edit.setCursorPosition(0);
//     edit.verticalScrollBar()->setValue(0);
//    qDebug() << edit.cursorPosition() << edit.verticalScrollBar()->value();
//     QEventLoop loop;
//     loop.exec();
    QTest::mouseClick(edit.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(20, 20));
    QVERIFY(edit.verticalScrollBar()->value() == 0);

}

void tst_TextEdit::cursorForPosition()
{
    TextEdit edit;
    const QString string = "123456";
    QSet<QChar> all;
    for (int i=0; i<all.size(); ++i) {
        all.insert(string.at(i));
    }
    edit.setText(string);
    QFont font;
    font.setPixelSize(10);
    edit.setFont(font);
    edit.resize(500, 500);
    const QRect r = edit.viewport()->rect();
    QPoint pos(0, 0);
    for (int x=r.left(); x<r.right(); ++x) {
        for (int y=r.top(); y<r.bottom(); ++y) {
            TextCursor cursor = edit.cursorForPosition(pos);
            if (!cursor.isValid())
                continue;
            QCOMPARE(cursor.position(), edit.textPositionAt(pos));
            const QChar ch = cursor.cursorCharacter();
            const QChar readChar = edit.readCharacter(cursor.position());
            QCOMPARE(readChar, ch);
            if (all.remove(ch) && all.isEmpty()) {
                goto end;
            }
        }
    }
end:
    QCOMPARE(all.size(), 0);
}

void tst_TextEdit::columnNumberIssue()
{
    QCOMPARE(1, 1);
    return;
    TextEdit edit;
    edit.resize(200, 200);
    edit.setText("This is a very very very long line and it should most certainly wrap around at least once.\n"
                 "This is a shorter line\n"
                 "1\n"
                 "2\n"
                 "3\n"
                 "4\n"
                 "5\n"
                 "6\n"
                 "7\n"
                 "8\n"
                 "9\n"
                 "10\n"
                 "11\n"
                 "12\n");
    edit.show();
    QTest::qWaitForWindowShown(&edit);
    QVERIFY(edit.verticalScrollBar()->maximum() != 0);
    while (edit.cursorPosition() < edit.document()->documentSize()) {
        QCOMPARE(0, edit.textCursor().columnNumber());
        QTest::keyClick(&edit, Qt::Key_Down);
    }
//    QVERIFY(edit.verticalScrollBar()->value() != 0);
    while (edit.cursorPosition() > 0) {
//        qDebug() << edit.cursorPosition();
        QTest::keyClick(&edit, Qt::Key_Up);
        QCOMPARE(0, edit.textCursor().columnNumber());
    }
//     QEventLoop loop;
//     loop.exec();
}

void tst_TextEdit::selectionTest()
{
    TextEdit edit;
    QString line = "1234567890\n";
    edit.document()->setText(line);
//    edit.setReadOnly(true);
    edit.resize(400, 400);
    edit.show();
    QTest::qWaitForWindowShown(&edit);

    QCOMPARE(edit.selectedText(), QString());
    QVERIFY(!edit.textCursor().hasSelection());

    QTest::keyClick(&edit, Qt::Key_Right, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.selectedText(), QString("1"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 1);

    QTest::keyClick(&edit, Qt::Key_Right, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.selectedText(), QString("12"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 2);

    QTest::keyClick(&edit, Qt::Key_Right, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString("123"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 3);

    QTest::keyClick(&edit, Qt::Key_Left, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString("12"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 2);

    QTest::keyClick(&edit, Qt::Key_Left, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString("1"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 1);

    QTest::keyClick(&edit, Qt::Key_Left, Qt::ShiftModifier);
    QVERIFY(!edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString());
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 0);
}

void tst_TextEdit::sectionTest()
{
    TextEdit edit;
    QString  text = "This is some\nrandom document string\nthing\n";
    edit.document()->setText(text);
    TextSection *ts = edit.insertTextSection(5, 10);
    edit.document()->takeTextSection(ts);
    QVERIFY( !edit.sections().contains(ts) );
}

void tst_TextEdit::deleteTest()
{
    TextDocument *doc = new TextDocument();
    doc->setText("Test to make sure that deleting a text edit with sections doesn't result in the sections being deleted again in the document destructor\n");
    TextEdit *edit = new TextEdit();
    edit->setDocument(doc);

    TextSection *s = edit->insertTextSection(4, 4);
    Q_UNUSED(s);
    delete edit;
    delete doc;
}

void tst_TextEdit::emptyDocumentTest()
{
    TextEdit edit;
    for(int i=0; i < 100 ; i++) {
	edit.append(QString("This is text on line %1\n").arg(i));
    }
    edit.show();
    QTest::qWaitForWindowShown(&edit);
    edit.setDocument(new TextDocument()); /// This causes the problem
    QTest::qWaitForWindowShown(&edit);
}

void tst_TextEdit::changeDocumentTest()
{
    TextDocument doc1;
    doc1.setText("abcdefg");

    TextDocument doc2;
    doc2.setText("hijklmn");

    TextEdit editor1;
    editor1.setDocument(&doc1);
    editor1.setDocument(&doc2);

    TextEdit editor2;
    editor2.setDocument(&doc1);

    TextSection *s = editor2.insertTextSection(0, 1);
    s->setCursor(Qt::PointingHandCursor);
}

QTEST_MAIN(tst_TextEdit)
#include "tst_textedit.moc"
<commit_msg>add a test from GB<commit_after>// Copyright 2010 Anders Bakken
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <QtTest/QtTest>

#include <textedit.h>
#include <textdocument.h>
QT_FORWARD_DECLARE_CLASS(TextEdit)

//TESTED_CLASS=
//TESTED_FILES=

// Need Qt 4.6.0> for qWaitForWindowShown
#if (QT_VERSION < QT_VERSION_CHECK(4, 6, 0))

#ifdef Q_WS_X11
extern void qt_x11_wait_for_window_manager(QWidget *w);
#endif

namespace QTest
{
    inline static bool qWaitForWindowShown(QWidget *window)
    {
#if defined(Q_WS_X11)
        qt_x11_wait_for_window_manager(window);
        QCoreApplication::processEvents();
#elif defined(Q_WS_QWS)
        Q_UNUSED(window);
        qWait(100);
#else
        Q_UNUSED(window);
        qWait(50);
#endif
        return true;
    }

}

QT_END_NAMESPACE

#endif // Qt < 4.6.0

class tst_TextEdit : public QObject
{
    Q_OBJECT

public:
    tst_TextEdit();
    virtual ~tst_TextEdit();

private slots:
    void clickInReadOnlyEdit();
    void scrollReadOnly();
    void scrollLines();
    void cursorForPosition();
    void columnNumberIssue();
    void selectionTest();
    void sectionTest();
    void deleteTest();
    void emptyDocumentTest();
    void changeDocumentTest();
    void clickInBlankAreaTest();
};

tst_TextEdit::tst_TextEdit()
{
}

tst_TextEdit::~tst_TextEdit()
{
}

void tst_TextEdit::scrollReadOnly()
{
    TextEdit edit;
    QString line = "12434567890\n";
    QString text;
    for (int i=0; i<1000; ++i) {
        text.append(line);
    }
    edit.document()->setText(text);
    edit.setReadOnly(true);
    edit.resize(400, 400);
    edit.show();
    QTest::qWaitForWindowShown(&edit);
//     edit.setCursorPosition(0);
//     edit.verticalScrollBar()->setValue(0);
//    qDebug() << edit.cursorPosition() << edit.verticalScrollBar()->value();
//     QEventLoop loop;
//     loop.exec();
    QTest::keyClick(&edit, Qt::Key_Down);
    QVERIFY(edit.verticalScrollBar()->value() != 0);
}

void tst_TextEdit::scrollLines()
{
    TextEdit edit;
    QString text = "Line 1\nLine2\nLine 3\n\nLine 5\n\nLine 7\nLine 8\n\nLine 10\n";
    int numLines = text.count('\n');
    edit.document()->setText(text);

    QList<int> lineSteps;
    lineSteps << 1 << 3; // Single-click, mouse wheel

    QList<int> directions;
    directions << 1 << -1; // Forwards, backwards

    QScrollBar *vsb = edit.verticalScrollBar();

    foreach(int lineStep, lineSteps) {
        int lineNo = 0;
        //printf("%d-line step\n", lineStep);
        foreach(int direction, directions) {
            //printf("  %s\n", direction>0?"Forwards":"Backwards");
            if (direction>0) {
                // Forwards
                vsb->setSliderPosition( 0 );
            }

            // Forwards
            while (lineNo <= numLines && lineNo >= 0) {
                int actualLineNo = edit.lineNumber(edit.viewportPosition());
                //printf("    Line %d\n", actualLineNo);
                if (lineNo != actualLineNo) {
                    printf("  %s: expected to be at line %d, actually at line %d\n",
                           (direction > 0 ? "Forwards" : "Backwards"), lineNo, actualLineNo);
                }
                QCOMPARE(lineNo, actualLineNo);
                for (int i=0; i<lineStep; lineNo+=direction, ++i) {
                    vsb->triggerAction(direction > 0
                                       ? QAbstractSlider::SliderSingleStepAdd
                                       : QAbstractSlider::SliderSingleStepSub);
                }
            }
            lineNo = numLines;
        }
    }
}

void tst_TextEdit::clickInReadOnlyEdit()
{
    TextEdit edit;
    QString line = "1234567890\n";
    QString text;
    for (int i=0; i<1000; ++i) {
        text.append(line);
    }
    edit.document()->setText(text);
    edit.setReadOnly(true);
    edit.resize(400, 400);
    edit.show();
    QTest::qWaitForWindowShown(&edit);
//     edit.setCursorPosition(0);
//     edit.verticalScrollBar()->setValue(0);
//    qDebug() << edit.cursorPosition() << edit.verticalScrollBar()->value();
//     QEventLoop loop;
//     loop.exec();
    QTest::mouseClick(edit.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(20, 20));
    QVERIFY(edit.verticalScrollBar()->value() == 0);

}

void tst_TextEdit::cursorForPosition()
{
    TextEdit edit;
    const QString string = "123456";
    QSet<QChar> all;
    for (int i=0; i<all.size(); ++i) {
        all.insert(string.at(i));
    }
    edit.setText(string);
    QFont font;
    font.setPixelSize(10);
    edit.setFont(font);
    edit.resize(500, 500);
    const QRect r = edit.viewport()->rect();
    QPoint pos(0, 0);
    for (int x=r.left(); x<r.right(); ++x) {
        for (int y=r.top(); y<r.bottom(); ++y) {
            TextCursor cursor = edit.cursorForPosition(pos);
            if (!cursor.isValid())
                continue;
            QCOMPARE(cursor.position(), edit.textPositionAt(pos));
            const QChar ch = cursor.cursorCharacter();
            const QChar readChar = edit.readCharacter(cursor.position());
            QCOMPARE(readChar, ch);
            if (all.remove(ch) && all.isEmpty()) {
                goto end;
            }
        }
    }
end:
    QCOMPARE(all.size(), 0);
}

void tst_TextEdit::columnNumberIssue()
{
    QCOMPARE(1, 1);
    return;
    TextEdit edit;
    edit.resize(200, 200);
    edit.setText("This is a very very very long line and it should most certainly wrap around at least once.\n"
                 "This is a shorter line\n"
                 "1\n"
                 "2\n"
                 "3\n"
                 "4\n"
                 "5\n"
                 "6\n"
                 "7\n"
                 "8\n"
                 "9\n"
                 "10\n"
                 "11\n"
                 "12\n");
    edit.show();
    QTest::qWaitForWindowShown(&edit);
    QVERIFY(edit.verticalScrollBar()->maximum() != 0);
    while (edit.cursorPosition() < edit.document()->documentSize()) {
        QCOMPARE(0, edit.textCursor().columnNumber());
        QTest::keyClick(&edit, Qt::Key_Down);
    }
//    QVERIFY(edit.verticalScrollBar()->value() != 0);
    while (edit.cursorPosition() > 0) {
//        qDebug() << edit.cursorPosition();
        QTest::keyClick(&edit, Qt::Key_Up);
        QCOMPARE(0, edit.textCursor().columnNumber());
    }
//     QEventLoop loop;
//     loop.exec();
}

void tst_TextEdit::selectionTest()
{
    TextEdit edit;
    QString line = "1234567890\n";
    edit.document()->setText(line);
//    edit.setReadOnly(true);
    edit.resize(400, 400);
    edit.show();
    QTest::qWaitForWindowShown(&edit);

    QCOMPARE(edit.selectedText(), QString());
    QVERIFY(!edit.textCursor().hasSelection());

    QTest::keyClick(&edit, Qt::Key_Right, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.selectedText(), QString("1"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 1);

    QTest::keyClick(&edit, Qt::Key_Right, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.selectedText(), QString("12"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 2);

    QTest::keyClick(&edit, Qt::Key_Right, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString("123"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 3);

    QTest::keyClick(&edit, Qt::Key_Left, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString("12"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 2);

    QTest::keyClick(&edit, Qt::Key_Left, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString("1"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 1);

    QTest::keyClick(&edit, Qt::Key_Left, Qt::ShiftModifier);
    QVERIFY(!edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString());
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 0);
}

void tst_TextEdit::sectionTest()
{
    TextEdit edit;
    QString  text = "This is some\nrandom document string\nthing\n";
    edit.document()->setText(text);
    TextSection *ts = edit.insertTextSection(5, 10);
    edit.document()->takeTextSection(ts);
    QVERIFY( !edit.sections().contains(ts) );
}

void tst_TextEdit::deleteTest()
{
    TextDocument *doc = new TextDocument();
    doc->setText("Test to make sure that deleting a text edit with sections doesn't result in the sections being deleted again in the document destructor\n");
    TextEdit *edit = new TextEdit();
    edit->setDocument(doc);

    TextSection *s = edit->insertTextSection(4, 4);
    Q_UNUSED(s);
    delete edit;
    delete doc;
}

void tst_TextEdit::emptyDocumentTest()
{
    TextEdit edit;
    for(int i=0; i < 100 ; i++) {
	edit.append(QString("This is text on line %1\n").arg(i));
    }
    edit.show();
    QTest::qWaitForWindowShown(&edit);
    edit.setDocument(new TextDocument()); /// This causes the problem
    QTest::qWaitForWindowShown(&edit);
}

void tst_TextEdit::changeDocumentTest()
{
    TextDocument doc1;
    doc1.setText("abcdefg");

    TextDocument doc2;
    doc2.setText("hijklmn");

    TextEdit editor1;
    editor1.setDocument(&doc1);
    editor1.setDocument(&doc2);

    TextEdit editor2;
    editor2.setDocument(&doc1);

    TextSection *s = editor2.insertTextSection(0, 1);
    s->setCursor(Qt::PointingHandCursor);
}

void tst_TextEdit::clickInBlankAreaTest()
{
    TextEdit edit;
    edit.load("spectre.scs");
    // Make the window big enough to leave blank space below
    edit.setMinimumSize(QSize(1000, 1200));
    edit.show();
    QTest::qWaitForWindowShown(&edit);
    // Click below the visible text
    QTest::mouseClick(edit.viewport(), Qt::LeftButton, Qt::NoModifier,
		      QPoint(50, 1000));
}

QTEST_MAIN(tst_TextEdit)
#include "tst_textedit.moc"
<|endoftext|>


<commit_before>// Copyright 2010 Anders Bakken
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <QtTest/QtTest>

#include <textedit.h>
#include <textdocument.h>
QT_FORWARD_DECLARE_CLASS(TextEdit)

//TESTED_CLASS=
//TESTED_FILES=

// Need Qt 4.6.0> for qWaitForWindowShown
#if (QT_VERSION < QT_VERSION_CHECK(4, 6, 0))

#ifdef Q_WS_X11
extern void qt_x11_wait_for_window_manager(QWidget *w);
#endif

namespace QTest
{
    inline static bool qWaitForWindowShown(QWidget *window)
    {
#if defined(Q_WS_X11)
        qt_x11_wait_for_window_manager(window);
        QCoreApplication::processEvents();
#elif defined(Q_WS_QWS)
        Q_UNUSED(window);
        qWait(100);
#else
        Q_UNUSED(window);
        qWait(50);
#endif
        return true;
    }

}

QT_END_NAMESPACE

#endif // Qt < 4.6.0

class tst_TextEdit : public QObject
{
    Q_OBJECT

public:
    tst_TextEdit();
    virtual ~tst_TextEdit();

private slots:
    void clickInReadOnlyEdit();
    void scrollReadOnly();
    void scrollLines();
    void cursorForPosition();
    void columnNumberIssue();
    void selectionTest();
    void sectionTest();
    void deleteTest();
    void emptyDocumentTest();
};

tst_TextEdit::tst_TextEdit()
{
}

tst_TextEdit::~tst_TextEdit()
{
}

void tst_TextEdit::scrollReadOnly()
{
    TextEdit edit;
    QString line = "12434567890\n";
    QString text;
    for (int i=0; i<1000; ++i) {
        text.append(line);
    }
    edit.document()->setText(text);
    edit.setReadOnly(true);
    edit.resize(400, 400);
    edit.show();
    QTest::qWaitForWindowShown(&edit);
//     edit.setCursorPosition(0);
//     edit.verticalScrollBar()->setValue(0);
//    qDebug() << edit.cursorPosition() << edit.verticalScrollBar()->value();
//     QEventLoop loop;
//     loop.exec();
    QTest::keyClick(&edit, Qt::Key_Down);
    QVERIFY(edit.verticalScrollBar()->value() != 0);
}

void tst_TextEdit::scrollLines()
{
    TextEdit edit;
    QString text = "Line 1\nLine2\nLine 3\n\nLine 5\n\nLine 7\nLine 8\n\nLine 10\n";
    int numLines = text.count('\n');
    edit.document()->setText(text);

    QList<int> lineSteps;
    lineSteps << 1 << 3; // Single-click, mouse wheel

    QList<int> directions;
    directions << 1 << -1; // Forwards, backwards

    QScrollBar *vsb = edit.verticalScrollBar();

    foreach(int lineStep, lineSteps) {
        int lineNo = 0;
        //printf("%d-line step\n", lineStep);
        foreach(int direction, directions) {
            //printf("  %s\n", direction>0?"Forwards":"Backwards");
            if (direction>0) {
                // Forwards
                vsb->setSliderPosition( 0 );
            }

            // Forwards
            while (lineNo <= numLines && lineNo >= 0) {
                int actualLineNo = edit.lineNumber(edit.viewportPosition());
                //printf("    Line %d\n", actualLineNo);
                if (lineNo != actualLineNo) {
                    printf("  %s: expected to be at line %d, actually at line %d\n",
                           (direction > 0 ? "Forwards" : "Backwards"), lineNo, actualLineNo);
                }
                QCOMPARE(lineNo, actualLineNo);
                for (int i=0; i<lineStep; lineNo+=direction, ++i) {
                    vsb->triggerAction(direction > 0
                                       ? QAbstractSlider::SliderSingleStepAdd
                                       : QAbstractSlider::SliderSingleStepSub);
                }
            }
            lineNo = numLines;
        }
    }
}

void tst_TextEdit::clickInReadOnlyEdit()
{
    TextEdit edit;
    QString line = "1234567890\n";
    QString text;
    for (int i=0; i<1000; ++i) {
        text.append(line);
    }
    edit.document()->setText(text);
    edit.setReadOnly(true);
    edit.resize(400, 400);
    edit.show();
    QTest::qWaitForWindowShown(&edit);
//     edit.setCursorPosition(0);
//     edit.verticalScrollBar()->setValue(0);
//    qDebug() << edit.cursorPosition() << edit.verticalScrollBar()->value();
//     QEventLoop loop;
//     loop.exec();
    QTest::mouseClick(edit.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(20, 20));
    QVERIFY(edit.verticalScrollBar()->value() == 0);

}

void tst_TextEdit::cursorForPosition()
{
    TextEdit edit;
    const QString string = "123456";
    QSet<QChar> all;
    for (int i=0; i<all.size(); ++i) {
        all.insert(string.at(i));
    }
    edit.setText(string);
    QFont font;
    font.setPixelSize(10);
    edit.setFont(font);
    edit.resize(500, 500);
    const QRect r = edit.viewport()->rect();
    QPoint pos(0, 0);
    for (int x=r.left(); x<r.right(); ++x) {
        for (int y=r.top(); y<r.bottom(); ++y) {
            TextCursor cursor = edit.cursorForPosition(pos);
            if (!cursor.isValid())
                continue;
            QCOMPARE(cursor.position(), edit.textPositionAt(pos));
            const QChar ch = cursor.cursorCharacter();
            const QChar readChar = edit.readCharacter(cursor.position());
            QCOMPARE(readChar, ch);
            if (all.remove(ch) && all.isEmpty()) {
                goto end;
            }
        }
    }
end:
    QCOMPARE(all.size(), 0);
}

void tst_TextEdit::columnNumberIssue()
{
    QCOMPARE(1, 1);
    return;
    TextEdit edit;
    edit.resize(200, 200);
    edit.setText("This is a very very very long line and it should most certainly wrap around at least once.\n"
                 "This is a shorter line\n"
                 "1\n"
                 "2\n"
                 "3\n"
                 "4\n"
                 "5\n"
                 "6\n"
                 "7\n"
                 "8\n"
                 "9\n"
                 "10\n"
                 "11\n"
                 "12\n");
    edit.show();
    QTest::qWaitForWindowShown(&edit);
    QVERIFY(edit.verticalScrollBar()->maximum() != 0);
    while (edit.cursorPosition() < edit.document()->documentSize()) {
        QCOMPARE(0, edit.textCursor().columnNumber());
        QTest::keyClick(&edit, Qt::Key_Down);
    }
//    QVERIFY(edit.verticalScrollBar()->value() != 0);
    while (edit.cursorPosition() > 0) {
//        qDebug() << edit.cursorPosition();
        QTest::keyClick(&edit, Qt::Key_Up);
        QCOMPARE(0, edit.textCursor().columnNumber());
    }
//     QEventLoop loop;
//     loop.exec();
}

void tst_TextEdit::selectionTest()
{
    TextEdit edit;
    QString line = "1234567890\n";
    edit.document()->setText(line);
//    edit.setReadOnly(true);
    edit.resize(400, 400);
    edit.show();
    QTest::qWaitForWindowShown(&edit);

    QCOMPARE(edit.selectedText(), QString());
    QVERIFY(!edit.textCursor().hasSelection());

    QTest::keyClick(&edit, Qt::Key_Right, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.selectedText(), QString("1"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 1);

    QTest::keyClick(&edit, Qt::Key_Right, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.selectedText(), QString("12"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 2);

    QTest::keyClick(&edit, Qt::Key_Right, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString("123"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 3);

    QTest::keyClick(&edit, Qt::Key_Left, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString("12"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 2);

    QTest::keyClick(&edit, Qt::Key_Left, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString("1"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 1);

    QTest::keyClick(&edit, Qt::Key_Left, Qt::ShiftModifier);
    QVERIFY(!edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString());
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 0);
}

void tst_TextEdit::sectionTest()
{
    TextEdit edit;
    QString  text = "This is some\nrandom document string\nthing\n";
    edit.document()->setText(text);
    TextSection *ts = edit.insertTextSection(5, 10);
    edit.document()->takeTextSection(ts);
    QVERIFY( !edit.sections().contains(ts) );
}

void tst_TextEdit::deleteTest()
{
    TextDocument *doc = new TextDocument();
    doc->setText("Test to make sure that deleting a text edit with sections doesn't result in the sections being deleted again in the document destructor\n");
    TextEdit *edit = new TextEdit();
    edit->setDocument(doc);

    TextSection *s = edit->insertTextSection(4, 4);
    Q_UNUSED(s);
    delete edit;
    delete doc;
}

void tst_TextEdit::emptyDocumentTest()
{
    TextEdit edit;
    for(int i=0; i < 100 ; i++) {
	edit.append(QString("This is text on line %1\n").arg(i));
    }
    edit.show();
    QTest::qWaitForWindowShown(&edit);
    edit.setDocument(new TextDocument()); /// This causes the problem
    QTest::qWaitForWindowShown(&edit);
}

QTEST_MAIN(tst_TextEdit)
#include "tst_textedit.moc"
<commit_msg>Add a test for changing documents on a textedit<commit_after>// Copyright 2010 Anders Bakken
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <QtTest/QtTest>

#include <textedit.h>
#include <textdocument.h>
QT_FORWARD_DECLARE_CLASS(TextEdit)

//TESTED_CLASS=
//TESTED_FILES=

// Need Qt 4.6.0> for qWaitForWindowShown
#if (QT_VERSION < QT_VERSION_CHECK(4, 6, 0))

#ifdef Q_WS_X11
extern void qt_x11_wait_for_window_manager(QWidget *w);
#endif

namespace QTest
{
    inline static bool qWaitForWindowShown(QWidget *window)
    {
#if defined(Q_WS_X11)
        qt_x11_wait_for_window_manager(window);
        QCoreApplication::processEvents();
#elif defined(Q_WS_QWS)
        Q_UNUSED(window);
        qWait(100);
#else
        Q_UNUSED(window);
        qWait(50);
#endif
        return true;
    }

}

QT_END_NAMESPACE

#endif // Qt < 4.6.0

class tst_TextEdit : public QObject
{
    Q_OBJECT

public:
    tst_TextEdit();
    virtual ~tst_TextEdit();

private slots:
    void clickInReadOnlyEdit();
    void scrollReadOnly();
    void scrollLines();
    void cursorForPosition();
    void columnNumberIssue();
    void selectionTest();
    void sectionTest();
    void deleteTest();
    void emptyDocumentTest();
    void changeDocumentTest();
};

tst_TextEdit::tst_TextEdit()
{
}

tst_TextEdit::~tst_TextEdit()
{
}

void tst_TextEdit::scrollReadOnly()
{
    TextEdit edit;
    QString line = "12434567890\n";
    QString text;
    for (int i=0; i<1000; ++i) {
        text.append(line);
    }
    edit.document()->setText(text);
    edit.setReadOnly(true);
    edit.resize(400, 400);
    edit.show();
    QTest::qWaitForWindowShown(&edit);
//     edit.setCursorPosition(0);
//     edit.verticalScrollBar()->setValue(0);
//    qDebug() << edit.cursorPosition() << edit.verticalScrollBar()->value();
//     QEventLoop loop;
//     loop.exec();
    QTest::keyClick(&edit, Qt::Key_Down);
    QVERIFY(edit.verticalScrollBar()->value() != 0);
}

void tst_TextEdit::scrollLines()
{
    TextEdit edit;
    QString text = "Line 1\nLine2\nLine 3\n\nLine 5\n\nLine 7\nLine 8\n\nLine 10\n";
    int numLines = text.count('\n');
    edit.document()->setText(text);

    QList<int> lineSteps;
    lineSteps << 1 << 3; // Single-click, mouse wheel

    QList<int> directions;
    directions << 1 << -1; // Forwards, backwards

    QScrollBar *vsb = edit.verticalScrollBar();

    foreach(int lineStep, lineSteps) {
        int lineNo = 0;
        //printf("%d-line step\n", lineStep);
        foreach(int direction, directions) {
            //printf("  %s\n", direction>0?"Forwards":"Backwards");
            if (direction>0) {
                // Forwards
                vsb->setSliderPosition( 0 );
            }

            // Forwards
            while (lineNo <= numLines && lineNo >= 0) {
                int actualLineNo = edit.lineNumber(edit.viewportPosition());
                //printf("    Line %d\n", actualLineNo);
                if (lineNo != actualLineNo) {
                    printf("  %s: expected to be at line %d, actually at line %d\n",
                           (direction > 0 ? "Forwards" : "Backwards"), lineNo, actualLineNo);
                }
                QCOMPARE(lineNo, actualLineNo);
                for (int i=0; i<lineStep; lineNo+=direction, ++i) {
                    vsb->triggerAction(direction > 0
                                       ? QAbstractSlider::SliderSingleStepAdd
                                       : QAbstractSlider::SliderSingleStepSub);
                }
            }
            lineNo = numLines;
        }
    }
}

void tst_TextEdit::clickInReadOnlyEdit()
{
    TextEdit edit;
    QString line = "1234567890\n";
    QString text;
    for (int i=0; i<1000; ++i) {
        text.append(line);
    }
    edit.document()->setText(text);
    edit.setReadOnly(true);
    edit.resize(400, 400);
    edit.show();
    QTest::qWaitForWindowShown(&edit);
//     edit.setCursorPosition(0);
//     edit.verticalScrollBar()->setValue(0);
//    qDebug() << edit.cursorPosition() << edit.verticalScrollBar()->value();
//     QEventLoop loop;
//     loop.exec();
    QTest::mouseClick(edit.viewport(), Qt::LeftButton, Qt::NoModifier, QPoint(20, 20));
    QVERIFY(edit.verticalScrollBar()->value() == 0);

}

void tst_TextEdit::cursorForPosition()
{
    TextEdit edit;
    const QString string = "123456";
    QSet<QChar> all;
    for (int i=0; i<all.size(); ++i) {
        all.insert(string.at(i));
    }
    edit.setText(string);
    QFont font;
    font.setPixelSize(10);
    edit.setFont(font);
    edit.resize(500, 500);
    const QRect r = edit.viewport()->rect();
    QPoint pos(0, 0);
    for (int x=r.left(); x<r.right(); ++x) {
        for (int y=r.top(); y<r.bottom(); ++y) {
            TextCursor cursor = edit.cursorForPosition(pos);
            if (!cursor.isValid())
                continue;
            QCOMPARE(cursor.position(), edit.textPositionAt(pos));
            const QChar ch = cursor.cursorCharacter();
            const QChar readChar = edit.readCharacter(cursor.position());
            QCOMPARE(readChar, ch);
            if (all.remove(ch) && all.isEmpty()) {
                goto end;
            }
        }
    }
end:
    QCOMPARE(all.size(), 0);
}

void tst_TextEdit::columnNumberIssue()
{
    QCOMPARE(1, 1);
    return;
    TextEdit edit;
    edit.resize(200, 200);
    edit.setText("This is a very very very long line and it should most certainly wrap around at least once.\n"
                 "This is a shorter line\n"
                 "1\n"
                 "2\n"
                 "3\n"
                 "4\n"
                 "5\n"
                 "6\n"
                 "7\n"
                 "8\n"
                 "9\n"
                 "10\n"
                 "11\n"
                 "12\n");
    edit.show();
    QTest::qWaitForWindowShown(&edit);
    QVERIFY(edit.verticalScrollBar()->maximum() != 0);
    while (edit.cursorPosition() < edit.document()->documentSize()) {
        QCOMPARE(0, edit.textCursor().columnNumber());
        QTest::keyClick(&edit, Qt::Key_Down);
    }
//    QVERIFY(edit.verticalScrollBar()->value() != 0);
    while (edit.cursorPosition() > 0) {
//        qDebug() << edit.cursorPosition();
        QTest::keyClick(&edit, Qt::Key_Up);
        QCOMPARE(0, edit.textCursor().columnNumber());
    }
//     QEventLoop loop;
//     loop.exec();
}

void tst_TextEdit::selectionTest()
{
    TextEdit edit;
    QString line = "1234567890\n";
    edit.document()->setText(line);
//    edit.setReadOnly(true);
    edit.resize(400, 400);
    edit.show();
    QTest::qWaitForWindowShown(&edit);

    QCOMPARE(edit.selectedText(), QString());
    QVERIFY(!edit.textCursor().hasSelection());

    QTest::keyClick(&edit, Qt::Key_Right, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.selectedText(), QString("1"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 1);

    QTest::keyClick(&edit, Qt::Key_Right, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.selectedText(), QString("12"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 2);

    QTest::keyClick(&edit, Qt::Key_Right, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString("123"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 3);

    QTest::keyClick(&edit, Qt::Key_Left, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString("12"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 2);

    QTest::keyClick(&edit, Qt::Key_Left, Qt::ShiftModifier);
    QVERIFY(edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString("1"));
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 1);

    QTest::keyClick(&edit, Qt::Key_Left, Qt::ShiftModifier);
    QVERIFY(!edit.textCursor().hasSelection());
    QCOMPARE(edit.textCursor().selectedText(), QString());
    QCOMPARE(edit.textCursor().anchor(), 0);
    QCOMPARE(edit.textCursor().position(), 0);
}

void tst_TextEdit::sectionTest()
{
    TextEdit edit;
    QString  text = "This is some\nrandom document string\nthing\n";
    edit.document()->setText(text);
    TextSection *ts = edit.insertTextSection(5, 10);
    edit.document()->takeTextSection(ts);
    QVERIFY( !edit.sections().contains(ts) );
}

void tst_TextEdit::deleteTest()
{
    TextDocument *doc = new TextDocument();
    doc->setText("Test to make sure that deleting a text edit with sections doesn't result in the sections being deleted again in the document destructor\n");
    TextEdit *edit = new TextEdit();
    edit->setDocument(doc);

    TextSection *s = edit->insertTextSection(4, 4);
    Q_UNUSED(s);
    delete edit;
    delete doc;
}

void tst_TextEdit::emptyDocumentTest()
{
    TextEdit edit;
    for(int i=0; i < 100 ; i++) {
	edit.append(QString("This is text on line %1\n").arg(i));
    }
    edit.show();
    QTest::qWaitForWindowShown(&edit);
    edit.setDocument(new TextDocument()); /// This causes the problem
    QTest::qWaitForWindowShown(&edit);
}

void tst_TextEdit::changeDocumentTest()
{
    TextDocument doc1;
    doc1.setText("abcdefg");

    TextDocument doc2;
    doc2.setText("hijklmn");

    TextEdit editor1;
    editor1.setDocument(&doc1);
    editor1.setDocument(&doc2);

    TextEdit editor2;
    editor2.setDocument(&doc1);

    TextSection *s = editor2.insertTextSection(0, 1);
    s->setCursor(Qt::PointingHandCursor);
}

QTEST_MAIN(tst_TextEdit)
#include "tst_textedit.moc"
<|endoftext|>


<commit_before>/*
 * Lines.cpp
 *
 *  Created on: Oct 15, 2014
 *      Author: Carsten Uphoff (uphoff@mytum.de)
 */
#include "CoordinateSystem.hpp"
#include <cstdio>
#include <algorithm>
#include "Statistics.hpp"

namespace hpl
{
CoordinateSystem::CoordinateSystem(Registry<Drawable>& dataContainer, std::map<Drawable::ID, unsigned int>& dataRevisions)
    : data(dataContainer), dataRevisions(dataRevisions)
{
    setUpCoordLines();
}

CoordinateSystem::~CoordinateSystem()
{
    delete[] linesX;
    delete[] linesY;
    //delete[] labels;
}

void CoordinateSystem::setColor(const Color& c)
{
    coordLinesColor = c;
    if (coordLines != nullptr) {
        coordLines->setColor(c);
    }
}

void CoordinateSystem::setGeometry(Geometry geom)
{
    geometry = geom;

    setUpCoordLines();

    geom.leftOffset += XOffset * geom.width;
    geom.topOffset += YOffset * geom.height;
    geom.width *= (1.0 - XOffset);
    geom.height *= (1.0 - YOffset);

    for (auto it = myPlots.begin(); it != myPlots.end(); ++it) {
        if (*it != coordLinesID) {
            data.lookup(*it).setGeometry(geom);
        }
    }
}

void CoordinateSystem::updateLimits(double xmin, double xmax, double ymin, double ymax)
{
    this->xmin = std::min(this->xmin, xmin);
    this->xmax = std::min(this->xmax, xmax);
    this->ymin = std::min(this->ymin, ymin);
    this->ymax = std::min(this->ymax, ymax);

    if (coordLines != nullptr) {
        coordLines->setLimits(this->xmin, this->ymin, this->xmax, this->ymax);
    }

    for (auto it = myPlots.begin(); it != myPlots.end(); ++it) {
        data.lookup(*it).setLimits(this->xmin, this->ymin, this->xmax, this->ymax);
    }

    updateLabels = true;
    needLimitUpdate = false;
    changed.invoke(Drawable::ID());
}

/*void CoordinateSystem::init(GLuint lineprogram, GLuint textprogram, GLuint mapprogram)
{
    glGenBuffers(1, &textBuffer);

    textpos = glGetAttribLocation(textprogram, "Position");
    textuv = glGetAttribLocation(textprogram, "UV");
    textrect = glGetUniformLocation(textprogram, "Rect");
    textglyphs = glGetUniformLocation(textprogram, "Glyphs");
	textmvp = glGetUniformLocation(textprogram, "MVP");
    textcolor = glGetUniformLocation(textprogram, "Color");
}

void CoordinateSystem::update()
{
    delete[] labels;
    labels = new Label[2*Ticks];
    numChars = 0;

	float xspacing = (1.0 - XOffset) / (Ticks + 1.0f);
    for (int i = 0; i < Ticks; ++i) {
		labels[i].len = sprintf(labels[i].label, "%.2f", (i+1) / (Ticks + 1.0f) * (xmax-xmin) + xmin);
		numChars += labels[i].len;
		labels[i].x = x;
		labels[i].y = YOffset / 2.0;
	}

    for (int i = Ticks; i < 2*Ticks; ++i) {
		labels[i].len = sprintf(labels[i].label, "%.2f", (i-Ticks+1) / (Ticks + 1.0f) * (ymax-ymin) + ymin);
		numChars += labels[i].len;
		labels[i].x = XOffset / 2.0;
		labels[i].y = y;
    }

    float* text = new float[numChars*4*4];
	float maxTextWidth = std::min(XOffset-TickLength, 0.8f*xspacing);
	float maxTextHeight = YOffset-TickLength;

	Header header = font->header();
	int c = 0;
	for (int i = 0; i < 2*Ticks; ++i) {
		float textw = 0.0;
		for (int j = 0; j < labels[i].len; ++j) {
            textw += font->ch(labels[i].label[j]).xadvance;
		}
		float xscale = maxTextWidth / textw;
		float yscale = maxTextHeight / header.lineHeight;
		float scale = (xscale < yscale) ? xscale : yscale;
		float xadv = 0.0;
		for (int j = 0; j < labels[i].len; ++j) {
			Char ch = font->ch(labels[i].label[j]);
			float x = labels[i].x + scale * (xadv + ch.xoffset - textw / 2.0);
			float y = labels[i].y + scale * (header.lineHeight / 2.0 - ch.yoffset);
			xadv += ch.xadvance;
			text[4*4*c + 0] = x;
			text[4*4*c + 1] = y - ch.height*scale;
			text[4*4*c + 2] = ch.x / header.width;
			text[4*4*c + 3] = (ch.y + ch.height) / header.height;

			text[4*4*c + 4] = x;
			text[4*4*c + 5] = y;
			text[4*4*c + 6] = ch.x / header.width;
			text[4*4*c + 7] = ch.y / header.height;

			text[4*4*c + 8] = x + ch.width*scale;
			text[4*4*c + 9] = y;
			text[4*4*c + 10] = (ch.x + ch.width) / header.width;
			text[4*4*c + 11] = ch.y / header.height;

			text[4*4*c + 12] = x + ch.width*scale;
			text[4*4*c + 13] = y - ch.height*scale;
			text[4*4*c + 14] = (ch.x + ch.width) / header.width;
			text[4*4*c + 15] = (ch.y + ch.height) / header.height;

            labels[i].width += ch.xadvance + ch.xoffset;
            labels[i].height += ch.height;
			++c;
        }
        labels[i].width *= scale;
        labels[i].height *= scale / labels[i].len;
	}

    glBindBuffer(GL_ARRAY_BUFFER, textBuffer);
	glBufferData(GL_ARRAY_BUFFER, numChars*4*4*sizeof(float), text, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    delete[] text;
}

void CoordinateSystem::destroy()
{
    glDeleteBuffers(1, &textBuffer);
}

void CoordinateSystem::draw(float const* mvp)
{
    if (updateLabels) {
		updateLabels = false;
		update();
    }
    glUseProgram(textprogram);
    glBindBuffer(GL_ARRAY_BUFFER, textBuffer);
	glVertexAttribPointer(
		textpos,
		2,
		GL_FLOAT,
		GL_FALSE,
		4*sizeof(GLfloat),
		(GLvoid const*) 0
	);
	glEnableVertexAttribArray(textpos);
	glVertexAttribPointer(
		textuv,
		2,
		GL_FLOAT,
		GL_FALSE,
        4*sizeof(GLfloat),
		(GLvoid const*) (2*sizeof(float))
	);
	glEnableVertexAttribArray(textuv);
    glUniform4f(textrect, geometry.leftOffset, geometry.topOffset, geometry.width, geometry.height);
	glUniformMatrix3fv(textmvp, 1, GL_FALSE, mvp);
    glUniform3f(textcolor, drawColor.r, drawColor.g, drawColor.b);
	font->bind(textglyphs, 0);

	glDrawArrays(GL_QUADS, 0, 4*numChars);
	glDisableVertexAttribArray(textpos);
    glDisableVertexAttribArray(textuv);
}*/

void CoordinateSystem::setAxisProperties(int xFlags, int yFlags)
{
    this->xFlags = xFlags;
    this->yFlags = yFlags;
    //! @todo give logscale attribute to plots
    setUpCoordLines();
    changed.invoke(Drawable::ID());
}

void CoordinateSystem::setTickMode(TickMode mode)
{
    this->tickMode = mode;
    //! @todo do sth with data?
    setUpCoordLines();
    changed.invoke(Drawable::ID());
}

Drawable::ID CoordinateSystem::addNewPlot(Drawable* plot)
{
    Drawable::ID id = data.add(plot);
    plot->setId(id);
    dataRevisions[id] = 1;
    myPlots.push_back(id);
    plot->setGeometry(geometry);
    plot->setLimits(xmin, ymin, xmax, ymax);
    plot->changed.template bind<Delegate<Drawable::ID>, &Delegate<Drawable::ID>::invoke>(&changed);
    changed.invoke(id);
    return id;
}

void CoordinateSystem::removePlot(Drawable::ID id)
{
    if (data.has(id)) {
        data.remove(id);
    }

    for (auto it = myPlots.begin(); it != myPlots.end(); it++) {
        if (*it == id) {
            myPlots.erase(it);
            break;
        }
    }

    for (auto it = dataRevisions.begin(); it != dataRevisions.end(); it++) {
        if (it->first == id) {
            dataRevisions.erase(it);
            break;
        }
    }
}

void CoordinateSystem::setUpCoordLines()
{
    const int n = (2 + 2 * Ticks) * (((xFlags & Axis_PaintPrimary) ? 1 : 0) + ((xFlags & Axis_PaintSecondary) ? 1 : 0)
                                   + ((yFlags & Axis_PaintPrimary) ? 1 : 0) + ((yFlags & Axis_PaintSecondary) ? 1 : 0));
    delete[] linesX;
    delete[] linesY;

    if (n != 0) {
        linesX = new double[n];
        linesY = new double[n];
        unsigned int offset = 0;

        if (xFlags & Axis_PaintPrimary) {
            setUpHorizontalAxis(linesX, linesY, offset, YOffset, xFlags & Axis_Logscale);
            offset += 2 + 2 * Ticks;
        }
        if (xFlags & Axis_PaintSecondary) {
            setUpHorizontalAxis(linesX, linesY, offset, 1.0, xFlags & Axis_Logscale);
            offset += 2 + 2 * Ticks;
        }
        if (yFlags & Axis_PaintPrimary) {
            setUpVerticalAxis(linesX, linesY, offset, XOffset, yFlags & Axis_Logscale);
            offset += 2 + 2 * Ticks;
        }
        if (yFlags & Axis_PaintSecondary) {
            setUpVerticalAxis(linesX, linesY, offset, 1.0, yFlags & Axis_Logscale);
            offset += 2 + 2 * Ticks;
        }
    } else {
        linesX = nullptr;
        linesY = nullptr;
    }

    if (coordLines != nullptr) {
        removePlot(coordLinesID);
    }

    if (n != 0) {
        coordLines = new Lines(n, linesX, linesY, true);
        coordLinesID = addNewPlot(coordLines);
        coordLines->setLimits(0.0, 0.0, 1.0, 1.0);
        coordLines->setColor(coordLinesColor);
    } else {
        coordLines = nullptr;
        coordLinesID = Drawable::ID();
    }
}

void CoordinateSystem::setUpHorizontalAxis(double* linesX, double* linesY, unsigned int indexOffset, double yMean, bool log) const
{
    linesX[indexOffset] = 1.0;
    linesY[indexOffset] = yMean;
    linesX[indexOffset + 1] = XOffset;
    linesY[indexOffset + 1] = yMean;

    if (tickMode == FixedAmount) {
        if (log) {
            //! @todo implement
        } else {
            float xspacing = (1.0 - XOffset) / (Ticks + 1.0f);
            for (int i = 0; i < Ticks; ++i) {
                setUpTick(linesX, linesY, indexOffset+2+2*i, XOffset+(i+1)*xspacing, yMean);
            }
        }
    } else if (tickMode == Smart) {
        std::vector<double> tickPoints = getDataPointsForTicks(xmin, xmax, log);
        float xspacing = (1.0 - XOffset) / (tickPoints.size() + 1.0f);
        for (unsigned int i = 0; i < tickPoints.size(); ++i) {
            setUpTick(linesX, linesY, indexOffset+2+2*i, XOffset+(i+1)*xspacing, yMean);
        }
    } else {
        //! @todo implement
    }
}

void CoordinateSystem::setUpVerticalAxis(double* linesX, double* linesY, unsigned int indexOffset, double xMean, bool log) const
{
    linesX[indexOffset] = xMean;
    linesY[indexOffset] = 1.0;
    linesX[indexOffset + 1] = xMean;
    linesY[indexOffset + 1] = YOffset;

    if (tickMode == FixedAmount) {
        if (log) {
            //! @todo implement
        } else {
            float yspacing = (1.0 - YOffset) / (Ticks + 1.0f);
            for (int i = 0; i < Ticks; ++i) {
                setUpTick(linesY, linesX, indexOffset+2+2*i, YOffset+(i+1)*yspacing, xMean);
            }
        }
    } else if (tickMode == Smart) {
        std::vector<double> tickPoints = getDataPointsForTicks(ymin, ymax, log);
        float yspacing = (1.0 - YOffset) / (tickPoints.size() + 1.0f);
        for (unsigned int i = 0; i < tickPoints.size(); ++i) {
            setUpTick(linesY, linesX, indexOffset+2+2*i, YOffset+(i+1)*yspacing, xMean);
        }
    } else {
        //! @todo implement
    }
}

void CoordinateSystem::setUpTick(double* primary, double* secondary, unsigned int indexOffset, double primaryValue, double secondaryMeanValue) const
{
    primary[indexOffset] = primaryValue;
    primary[indexOffset+1] = primaryValue;
    secondary[indexOffset] = secondaryMeanValue-0.5*TickLength;
    secondary[indexOffset+1] = secondaryMeanValue+0.5*TickLength;
}

std::vector<double> CoordinateSystem::getDataPointsForTicks(double min, double max, bool log) const
{
    std::vector<double> ret;
    double div = 10.0, exponent = 1;
    bool todo = true, up = false, down = false;
    unsigned int countMin = 2, countMax = 8;
    int index = 0, size;
    double* steps;

    if (log) {
        size = 1;
        steps = new double[1] {1.0};
    } else {
        size = 3;
        steps = new double[3] {1.0, 2.0, 5.0};
    }

    while (todo) {
        ret = getDataPointsInside(min, max, div);
        if (ret.size() < countMin) {
            index ++;
            if (index >= size) {
                index = 0;
                exponent ++;
            }
            div = steps[index] * pow(10.0, exponent);
            up = true;
        } else if (ret.size() > countMax) {
            index --;
            if (index < 0) {
                index = size - 1;
                exponent --;
            }
            div = steps[index] * pow(10.0, exponent);
            down = true;
            if (down && up) {
                todo = false;
            }
        } else {
            todo = false;
        }
    }

    delete[] steps;
    return ret;
}

std::vector<double> CoordinateSystem::getDataPointsInside(double min, double max, double divisor) const
{
    std::vector<double> ret;

    double val = ceil(min / divisor) * divisor;
    while (val <= max) {
        ret.push_back(val);
        val += divisor;
    }

    return ret;
}
}
<commit_msg>Bugfix for setting limits with new coord axis options. Still buggy<commit_after>/*
 * Lines.cpp
 *
 *  Created on: Oct 15, 2014
 *      Author: Carsten Uphoff (uphoff@mytum.de)
 */
#include "CoordinateSystem.hpp"
#include <cstdio>
#include <algorithm>
#include "Statistics.hpp"

namespace hpl
{
CoordinateSystem::CoordinateSystem(Registry<Drawable>& dataContainer, std::map<Drawable::ID, unsigned int>& dataRevisions)
    : data(dataContainer), dataRevisions(dataRevisions)
{
    setUpCoordLines();
}

CoordinateSystem::~CoordinateSystem()
{
    delete[] linesX;
    delete[] linesY;
    //delete[] labels;
}

void CoordinateSystem::setColor(const Color& c)
{
    coordLinesColor = c;
    if (coordLines != nullptr) {
        coordLines->setColor(c);
    }
}

void CoordinateSystem::setGeometry(Geometry geom)
{
    geometry = geom;

    setUpCoordLines();

    geom.leftOffset += XOffset * geom.width;
    geom.topOffset += YOffset * geom.height;
    geom.width *= (1.0 - XOffset);
    geom.height *= (1.0 - YOffset);

    for (auto it = myPlots.begin(); it != myPlots.end(); ++it) {
        if (*it != coordLinesID) {
            data.lookup(*it).setGeometry(geom);
        }
    }
}

void CoordinateSystem::updateLimits(double xmin, double xmax, double ymin, double ymax)
{
    this->xmin = std::min(this->xmin, xmin);
    this->xmax = std::min(this->xmax, xmax);
    this->ymin = std::min(this->ymin, ymin);
    this->ymax = std::min(this->ymax, ymax);

    setUpCoordLines();

    for (auto it = myPlots.begin(); it != myPlots.end(); ++it) {
        data.lookup(*it).setLimits(this->xmin, this->ymin, this->xmax, this->ymax);
    }

    updateLabels = true;
    needLimitUpdate = false;
    changed.invoke(Drawable::ID());
}

/*void CoordinateSystem::init(GLuint lineprogram, GLuint textprogram, GLuint mapprogram)
{
    glGenBuffers(1, &textBuffer);

    textpos = glGetAttribLocation(textprogram, "Position");
    textuv = glGetAttribLocation(textprogram, "UV");
    textrect = glGetUniformLocation(textprogram, "Rect");
    textglyphs = glGetUniformLocation(textprogram, "Glyphs");
	textmvp = glGetUniformLocation(textprogram, "MVP");
    textcolor = glGetUniformLocation(textprogram, "Color");
}

void CoordinateSystem::update()
{
    delete[] labels;
    labels = new Label[2*Ticks];
    numChars = 0;

	float xspacing = (1.0 - XOffset) / (Ticks + 1.0f);
    for (int i = 0; i < Ticks; ++i) {
		labels[i].len = sprintf(labels[i].label, "%.2f", (i+1) / (Ticks + 1.0f) * (xmax-xmin) + xmin);
		numChars += labels[i].len;
		labels[i].x = x;
		labels[i].y = YOffset / 2.0;
	}

    for (int i = Ticks; i < 2*Ticks; ++i) {
		labels[i].len = sprintf(labels[i].label, "%.2f", (i-Ticks+1) / (Ticks + 1.0f) * (ymax-ymin) + ymin);
		numChars += labels[i].len;
		labels[i].x = XOffset / 2.0;
		labels[i].y = y;
    }

    float* text = new float[numChars*4*4];
	float maxTextWidth = std::min(XOffset-TickLength, 0.8f*xspacing);
	float maxTextHeight = YOffset-TickLength;

	Header header = font->header();
	int c = 0;
	for (int i = 0; i < 2*Ticks; ++i) {
		float textw = 0.0;
		for (int j = 0; j < labels[i].len; ++j) {
            textw += font->ch(labels[i].label[j]).xadvance;
		}
		float xscale = maxTextWidth / textw;
		float yscale = maxTextHeight / header.lineHeight;
		float scale = (xscale < yscale) ? xscale : yscale;
		float xadv = 0.0;
		for (int j = 0; j < labels[i].len; ++j) {
			Char ch = font->ch(labels[i].label[j]);
			float x = labels[i].x + scale * (xadv + ch.xoffset - textw / 2.0);
			float y = labels[i].y + scale * (header.lineHeight / 2.0 - ch.yoffset);
			xadv += ch.xadvance;
			text[4*4*c + 0] = x;
			text[4*4*c + 1] = y - ch.height*scale;
			text[4*4*c + 2] = ch.x / header.width;
			text[4*4*c + 3] = (ch.y + ch.height) / header.height;

			text[4*4*c + 4] = x;
			text[4*4*c + 5] = y;
			text[4*4*c + 6] = ch.x / header.width;
			text[4*4*c + 7] = ch.y / header.height;

			text[4*4*c + 8] = x + ch.width*scale;
			text[4*4*c + 9] = y;
			text[4*4*c + 10] = (ch.x + ch.width) / header.width;
			text[4*4*c + 11] = ch.y / header.height;

			text[4*4*c + 12] = x + ch.width*scale;
			text[4*4*c + 13] = y - ch.height*scale;
			text[4*4*c + 14] = (ch.x + ch.width) / header.width;
			text[4*4*c + 15] = (ch.y + ch.height) / header.height;

            labels[i].width += ch.xadvance + ch.xoffset;
            labels[i].height += ch.height;
			++c;
        }
        labels[i].width *= scale;
        labels[i].height *= scale / labels[i].len;
	}

    glBindBuffer(GL_ARRAY_BUFFER, textBuffer);
	glBufferData(GL_ARRAY_BUFFER, numChars*4*4*sizeof(float), text, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    delete[] text;
}

void CoordinateSystem::destroy()
{
    glDeleteBuffers(1, &textBuffer);
}

void CoordinateSystem::draw(float const* mvp)
{
    if (updateLabels) {
		updateLabels = false;
		update();
    }
    glUseProgram(textprogram);
    glBindBuffer(GL_ARRAY_BUFFER, textBuffer);
	glVertexAttribPointer(
		textpos,
		2,
		GL_FLOAT,
		GL_FALSE,
		4*sizeof(GLfloat),
		(GLvoid const*) 0
	);
	glEnableVertexAttribArray(textpos);
	glVertexAttribPointer(
		textuv,
		2,
		GL_FLOAT,
		GL_FALSE,
        4*sizeof(GLfloat),
		(GLvoid const*) (2*sizeof(float))
	);
	glEnableVertexAttribArray(textuv);
    glUniform4f(textrect, geometry.leftOffset, geometry.topOffset, geometry.width, geometry.height);
	glUniformMatrix3fv(textmvp, 1, GL_FALSE, mvp);
    glUniform3f(textcolor, drawColor.r, drawColor.g, drawColor.b);
	font->bind(textglyphs, 0);

	glDrawArrays(GL_QUADS, 0, 4*numChars);
	glDisableVertexAttribArray(textpos);
    glDisableVertexAttribArray(textuv);
}*/

void CoordinateSystem::setAxisProperties(int xFlags, int yFlags)
{
    this->xFlags = xFlags;
    this->yFlags = yFlags;
    //! @todo give logscale attribute to plots
    setUpCoordLines();
    changed.invoke(Drawable::ID());
}

void CoordinateSystem::setTickMode(TickMode mode)
{
    this->tickMode = mode;
    //! @todo do sth with data?
    setUpCoordLines();
    changed.invoke(Drawable::ID());
}

Drawable::ID CoordinateSystem::addNewPlot(Drawable* plot)
{
    Drawable::ID id = data.add(plot);
    plot->setId(id);
    dataRevisions[id] = 1;
    myPlots.push_back(id);
    plot->setGeometry(geometry);
    plot->setLimits(xmin, ymin, xmax, ymax);
    plot->changed.template bind<Delegate<Drawable::ID>, &Delegate<Drawable::ID>::invoke>(&changed);
    changed.invoke(id);
    return id;
}

void CoordinateSystem::removePlot(Drawable::ID id)
{
    if (data.has(id)) {
        data.remove(id);
    }

    for (auto it = myPlots.begin(); it != myPlots.end(); it++) {
        if (*it == id) {
            myPlots.erase(it);
            break;
        }
    }

    for (auto it = dataRevisions.begin(); it != dataRevisions.end(); it++) {
        if (it->first == id) {
            dataRevisions.erase(it);
            break;
        }
    }
}

void CoordinateSystem::setUpCoordLines()
{
    const int n = (2 + 2 * Ticks) * (((xFlags & Axis_PaintPrimary) ? 1 : 0) + ((xFlags & Axis_PaintSecondary) ? 1 : 0)
                                   + ((yFlags & Axis_PaintPrimary) ? 1 : 0) + ((yFlags & Axis_PaintSecondary) ? 1 : 0));
    delete[] linesX;
    delete[] linesY;

    if (n != 0) {
        linesX = new double[n];
        linesY = new double[n];
        unsigned int offset = 0;

        if (xFlags & Axis_PaintPrimary) {
            setUpHorizontalAxis(linesX, linesY, offset, YOffset, xFlags & Axis_Logscale);
            offset += 2 + 2 * Ticks;
        }
        if (xFlags & Axis_PaintSecondary) {
            setUpHorizontalAxis(linesX, linesY, offset, 1.0, xFlags & Axis_Logscale);
            offset += 2 + 2 * Ticks;
        }
        if (yFlags & Axis_PaintPrimary) {
            setUpVerticalAxis(linesX, linesY, offset, XOffset, yFlags & Axis_Logscale);
            offset += 2 + 2 * Ticks;
        }
        if (yFlags & Axis_PaintSecondary) {
            setUpVerticalAxis(linesX, linesY, offset, 1.0, yFlags & Axis_Logscale);
            offset += 2 + 2 * Ticks;
        }
    } else {
        linesX = nullptr;
        linesY = nullptr;
    }

    if (coordLines != nullptr) {
        removePlot(coordLinesID);
    }

    if (n != 0) {
        coordLines = new Lines(n, linesX, linesY, true);
        coordLinesID = addNewPlot(coordLines);
        coordLines->setLimits(0.0, 0.0, 1.0, 1.0);
        coordLines->setColor(coordLinesColor);
    } else {
        coordLines = nullptr;
        coordLinesID = Drawable::ID();
    }
}

void CoordinateSystem::setUpHorizontalAxis(double* linesX, double* linesY, unsigned int indexOffset, double yMean, bool log) const
{
    linesX[indexOffset] = 1.0;
    linesY[indexOffset] = yMean;
    linesX[indexOffset + 1] = XOffset;
    linesY[indexOffset + 1] = yMean;

    if (tickMode == FixedAmount) {
        if (log) {
            //! @todo implement
        } else {
            float xspacing = (1.0 - XOffset) / (Ticks + 1.0f);
            for (int i = 0; i < Ticks; ++i) {
                setUpTick(linesX, linesY, indexOffset+2+2*i, XOffset+(i+1)*xspacing, yMean);
            }
        }
    } else if (tickMode == Smart) {
        std::vector<double> tickPoints = getDataPointsForTicks(xmin, xmax, log);
        float xspacing = (1.0 - XOffset) / (tickPoints.size() + 1.0f);
        for (unsigned int i = 0; i < tickPoints.size(); ++i) {
            setUpTick(linesX, linesY, indexOffset+2+2*i, XOffset+(i+1)*xspacing, yMean);
        }
    } else {
        //! @todo implement
    }
}

void CoordinateSystem::setUpVerticalAxis(double* linesX, double* linesY, unsigned int indexOffset, double xMean, bool log) const
{
    linesX[indexOffset] = xMean;
    linesY[indexOffset] = 1.0;
    linesX[indexOffset + 1] = xMean;
    linesY[indexOffset + 1] = YOffset;

    if (tickMode == FixedAmount) {
        if (log) {
            //! @todo implement
        } else {
            float yspacing = (1.0 - YOffset) / (Ticks + 1.0f);
            for (int i = 0; i < Ticks; ++i) {
                setUpTick(linesY, linesX, indexOffset+2+2*i, YOffset+(i+1)*yspacing, xMean);
            }
        }
    } else if (tickMode == Smart) {
        std::vector<double> tickPoints = getDataPointsForTicks(ymin, ymax, log);
        float yspacing = (1.0 - YOffset) / (tickPoints.size() + 1.0f);
        for (unsigned int i = 0; i < tickPoints.size(); ++i) {
            setUpTick(linesY, linesX, indexOffset+2+2*i, YOffset+(i+1)*yspacing, xMean);
        }
    } else {
        //! @todo implement
    }
}

void CoordinateSystem::setUpTick(double* primary, double* secondary, unsigned int indexOffset, double primaryValue, double secondaryMeanValue) const
{
    primary[indexOffset] = primaryValue;
    primary[indexOffset+1] = primaryValue;
    secondary[indexOffset] = secondaryMeanValue-0.5*TickLength;
    secondary[indexOffset+1] = secondaryMeanValue+0.5*TickLength;
}

std::vector<double> CoordinateSystem::getDataPointsForTicks(double min, double max, bool log) const
{
    std::vector<double> ret;
    double div = 10.0, exponent = 1;
    bool todo = true, up = false, down = false;
    unsigned int countMin = 2, countMax = 8;
    int index = 0, size;
    double* steps;

    if (log) {
        size = 1;
        steps = new double[1] {1.0};
    } else {
        size = 3;
        steps = new double[3] {1.0, 2.0, 5.0};
    }

    while (todo) {
        ret = getDataPointsInside(min, max, div);
        if (ret.size() < countMin) {
            index ++;
            if (index >= size) {
                index = 0;
                exponent ++;
            }
            div = steps[index] * pow(10.0, exponent);
            up = true;
        } else if (ret.size() > countMax) {
            index --;
            if (index < 0) {
                index = size - 1;
                exponent --;
            }
            div = steps[index] * pow(10.0, exponent);
            down = true;
            if (down && up) {
                todo = false;
            }
        } else {
            todo = false;
        }
    }

    delete[] steps;
    return ret;
}

std::vector<double> CoordinateSystem::getDataPointsInside(double min, double max, double divisor) const
{
    std::vector<double> ret;

    double val = ceil(min / divisor) * divisor;
    while (val <= max) {
        ret.push_back(val);
        val += divisor;
    }

    return ret;
}
}
<|endoftext|>