Logo Search packages:      
Sourcecode: kboincspy version File versions  Download package

kbspredictormoleculeview.cpp

/***************************************************************************
 *   Copyright (C) 2005 by Roberto Virga                                   *
 *   rvirga@users.sourceforge.net                                          *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it 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.             *
 ***************************************************************************/

#include <math.h>

#include <qdom.h>
#include <qfile.h>
#include <qimage.h>
#include <qmap.h>
#include <qtextstream.h>

#include <kaboutdata.h>
#include <kfilterdev.h>
#include <kglobal.h>
#include <kinstance.h>

#include "kbspredictormoleculeview.h"

QColor progressColor(double progress)
{
  double r, g, b;
  if(progress <= 0.25) {
    r = 0.0;
    g = progress / 0.25;
    b = 1.0;
  } else if(progress <= 0.50) {
    r = 0.0;
    g = 1.0;
    b = 1.0 - (progress - 0.25) / 0.25;
  } else if(progress <= 0.75) {
    r = (progress - 0.50) / 0.25;
    g = 1.0;
    b = 0.0;
  } else {
    r = 1.0;
    g = 1.0 - (progress - 0.75) / 0.25;
    b = 0.0;
  }
  
  return QColor(int(r * 255), int(g * 255), int(b * 255));
}

QColor shapelyColor(PredictorAminoAcid aa)
{
  switch(aa) {
    case ALA:
      return QColor(140, 255, 140);
    case GLY:
      return QColor(255, 255, 255);
    case LEU:
      return QColor( 69,  94,  69);
    case SER:
      return QColor(255, 112,  66);
    case VAL:
      return QColor(255, 140, 255);
    case THR:
      return QColor(184,  76,   0);
    case LYS:
      return QColor( 71,  71, 184);
    case ASP:
      return QColor(160,   0,  66);
    case ILE:
      return QColor(  0,  76,   0);
    case ASN:
      return QColor(255, 124, 112);
    case GLU:
      return QColor(102,   0,   0);
    case PRO:
      return QColor( 82,  82,  82);
    case ARG:
      return QColor(  0,   0, 124);
    case PHE:
      return QColor( 83,  76,  66);
    case GLN:
      return QColor(255,  76,  76);
    case TYR:
      return QColor(140, 112,  76);
    case HIS:
      return QColor(112, 112, 255);
    case CYS:
      return QColor(255, 255, 112);
    case MET:
      return QColor(184, 160,  66);
    case TRP:
      return QColor( 79,  70,   0);
    default:
      return QColor(255,   0, 255);
  }
}

QColor cpkColor(const QString &element)
{
  if(element == "H")
    return QColor(255, 255, 255);
  else if(element == "HE")
    return QColor(255, 192, 203);
  else if(element == "LI")
    return QColor(178,  34,  34);
  else if(element == "B" || element == "CL")
    return QColor(  0, 255,   0);
  else if(element == "C")
    return QColor(200, 200, 200);
  else if(element == "N")
    return QColor(143, 143, 255);
  else if(element == "O")
    return QColor(240,   0,   0);
  else if(element == "F" || element == "SI" || element == "AU")
    return QColor(218, 165,  32);
  else if(element == "NA")
    return QColor(  0,   0, 255);
  else if(element == "MG")
    return QColor( 34, 139,  34);
  else if(element == "AL" || element == "CA" || element == "TI" || element == "CR"
                          || element == "MN" || element == "AG")
    return QColor(128, 128, 144);
  else if(element == "P" || element == "FE" || element == "BA")
    return QColor(255, 165,   0);
  else if(element == "S")
    return QColor(255, 200,  50);
  else if(element == "NI" || element == "CU" || element == "ZN" || element == "BR")
    return QColor(165,  42,  42);
  else if(element == "I")
    return QColor(160,  32, 240);
  else
    return QColor(255,  20, 147);
}

QColor monochromeColor()
{
  return Qt::lightGray;
}

double atomRadius(const QString &element)
{
  if(element == "H")
    return 1.20;
  else if(element == "C")
    return 1.70;
  else if(element == "N")
    return 1.55;
  else if(element == "O")
    return 1.52;
  else if(element == "F")
    return 1.47;
  else if(element == "P")
    return 1.80;
  else if(element == "S")
    return 1.80;
  else if(element == "CL")
    return 1.75;
  else if(element == "K")
    return 2.75;
  else if(element == "I")
    return 1.98;
  else if(element == "AG")
    return 1.72;
  else if(element == "AR")
    return 1.88;
  else if(element == "AS")
    return 1.85;
  else if(element == "AU")
    return 1.66;
  else if(element == "BR")
    return 1.85;
  else if(element == "CD")
    return 1.58;
  else if(element == "CU")
    return 1.40;
  else if(element == "GA")
    return 1.87;
  else if(element == "HE")
    return 1.40;
  else if(element == "HG")
    return 1.55;
  else if(element == "IN")
    return 1.93;
  else if(element == "KR")
    return 2.02;
  else if(element == "LI")
    return 1.82;
  else if(element == "MG")
    return 1.73;
  else if(element == "NA")
    return 2.27;
  else if(element == "NE")
    return 1.54;
  else if(element == "NI")
    return 1.63;
  else if(element == "PB")
    return 2.02;
  else if(element == "PD")
    return 1.63;
  else if(element == "PT")
    return 1.72;
  else if(element == "SE")
    return 1.90;
  else if(element == "SI")
    return 2.10;
  else if(element == "SN")
    return 2.17;
  else if(element == "TE")
    return 2.06;
  else if(element == "TL")
    return 1.96;
  else if(element == "U")
    return 1.86;
  else if(element == "XE")
    return 2.16;
  else if(element == "ZN")
    return 1.39;
  else
    return 2.00;
}

double distance(GLfloat *v1, GLfloat *v2)
{
  double sqr = 0.0;
  for(unsigned i = 0; i < 3; ++i) {
    double d = v2[i] - v1[i];
    sqr += d * d;
  }
  
  return sqrt(sqr);
}

const unsigned KBSPredictorMoleculeModel::s_divisions = 10;

KBSPredictorMoleculeModel::KBSPredictorMoleculeModel(QObject *parent, char *name)
                         : QObject(parent, name),
                           m_groups(0), m_backbone(NULL), m_atom(NULL),
                           m_style(Backbone), m_coloring(Group)
{
}

KBSPredictorMoleculeModel::~KBSPredictorMoleculeModel()
{
  if(NULL != m_backbone) delete m_backbone;
  if(NULL != m_atom) delete m_atom;
}

KBSPredictorMoleculeModel::Style KBSPredictorMoleculeModel::style() const
{
  return m_style;
}

void KBSPredictorMoleculeModel::setStyle(Style style)
{
  if(m_style == style || !isSupportedStyle(style)) return;
  m_style = style;
  
  if(!isSupportedColoring(m_coloring)) m_coloring = Monochrome;
  
  emit styleChanged();
}

bool KBSPredictorMoleculeModel::isSupportedStyle(Style style) const
{
  return(style < Wireframe || !m_atoms.isEmpty());
}

KBSPredictorMoleculeModel::Coloring KBSPredictorMoleculeModel::coloring() const
{
  return m_coloring;
}

void KBSPredictorMoleculeModel::setColoring(Coloring coloring)
{
  if(m_coloring == coloring || !isSupportedColoring(coloring)) return;
  m_coloring = coloring;
  
  emit coloringChanged();
}

bool KBSPredictorMoleculeModel::isSupportedColoring(Coloring coloring) const
{
  switch(coloring) {
    case CPK:
      return(m_style >= Wireframe && !m_atoms.isEmpty()); 
    case Shapely:
      return(!m_seq.isEmpty());
    default:
      return true;
  }
}

void KBSPredictorMoleculeModel::setChain(const QValueList<PredictorMonssterAtom> &chain)
{
  m_groups = chain.count() >= 2 ? chain.count() - 2 : 0;
  if(m_seq.count() != m_groups) m_seq.clear();
  m_atoms.clear();
  
  if(NULL != m_atom) delete m_atom;
  m_atom = NULL;
  
  if(NULL != m_backbone) delete m_backbone;
  m_backbone = NULL;

  if(0 == m_groups) return;
  
  const unsigned points = m_groups + s_divisions * (m_groups - 1);
  m_backbone = new GLfloat[3 * points];
  
  unsigned g;
  QValueList<PredictorMonssterAtom>::const_iterator atom;
  for(g = 0, atom = chain.at(1); g < m_groups; ++g, ++atom)
  {
    const unsigned p = g * (s_divisions + 1);
    
    m_backbone[3*p + 0] = 0.1 * ((*atom).x - 50.0);
    m_backbone[3*p + 1] = 0.1 * ((*atom).y - 50.0);
    m_backbone[3*p + 2] = 0.1 * ((*atom).z - 50.0);
  }
  
  interpolateBackbone();
  
  emit dataChanged();
}

void KBSPredictorMoleculeModel::setSeq(const PredictorMonssterSeq &seq)
{
  m_seq = seq.groups;
  
  emit dataChanged();
}

void KBSPredictorMoleculeModel::setPDB(const PredictorProteinPDB &pdb)
{
  m_groups = pdb.groups;
  m_seq.clear();
  m_atoms = pdb.atoms;
  
  const unsigned atoms = pdb.atoms.count();
  
  if(NULL != m_atom) delete m_atom;
  m_atom = new GLfloat[3 * atoms];
  
  if(NULL != m_backbone) delete m_backbone;
  const unsigned points = m_groups + s_divisions * (m_groups - 1);
  m_backbone = new GLfloat[3 * points];
  
  unsigned a, g;
  QValueList<PredictorAtomPDB>::const_iterator atom;
  for(a = 0, g = 0, atom = pdb.atoms.begin(); atom != pdb.atoms.end(); ++a, ++atom)
  {
    m_atom[3*a + 0] = 0.1 * (*atom).x;
    m_atom[3*a + 1] = 0.1 * (*atom).y;
    m_atom[3*a + 2] = 0.1 * (*atom).z;
    
    if((*atom).name.iupac == "CA")
    {
      const unsigned p = g++ * (s_divisions + 1);
      
      m_backbone[3*p + 0] = 0.1 * (*atom).x;
      m_backbone[3*p + 1] = 0.1 * (*atom).y;
      m_backbone[3*p + 2] = 0.1 * (*atom).z;
      
      PredictorMonssterResidue seq;
      seq.resSeq = (*atom).resSeq;
      seq.resName = (*atom).resName;
      seq.count[0] = seq.count[1] = 1;
      m_seq << seq;
    }
  }
  
  interpolateBackbone();
  
  emit dataChanged();
}

void KBSPredictorMoleculeModel::rotateData(int dx, int dy)
{
  const double alpha = 1e-2 * dx, beta = 1e-2 * dy;
  
  const double sinAlpha = sin(alpha), cosAlpha = cos(alpha),
               sinBeta = sin(beta), cosBeta = cos(beta);
  
  if(NULL != m_backbone)
  {
    const unsigned points = m_groups + s_divisions * (m_groups - 1);
    
    for(unsigned p = 0; p < points; ++p)
    {
      const double x = m_backbone[3*p + 0],
                   y = m_backbone[3*p + 1],
                   z = m_backbone[3*p + 2];
      
      const double tx = x * cosAlpha + z * sinAlpha,
                   ty = x * sinAlpha * sinBeta + y * cosBeta - z * cosAlpha * sinBeta,
                   tz = -x * sinAlpha * cosBeta + y * sinBeta + z * cosAlpha * cosBeta;
      
      m_backbone[3*p + 0] = tx;
      m_backbone[3*p + 1] = ty;
      m_backbone[3*p + 2] = tz;
    }
  }
  
  if(NULL != m_atom)
  {
    const unsigned atoms = m_atoms.count();
    
    for(unsigned a = 0; a < atoms; ++a)
    {
      const double x = m_atom[3*a + 0],
                   y = m_atom[3*a + 1],
                   z = m_atom[3*a + 2];
      
      const double tx = x * cosAlpha + z * sinAlpha,
                   ty = x * sinAlpha * sinBeta + y * cosBeta - z * cosAlpha * sinBeta,
                   tz = -x * sinAlpha * cosBeta + y * sinBeta + z * cosAlpha * cosBeta;
      
      m_atom[3*a + 0] = tx;
      m_atom[3*a + 1] = ty;
      m_atom[3*a + 2] = tz;
    }
  }

  emit dataChanged();
}

bool KBSPredictorMoleculeModel::exportVRML(const QString &fileName)
{
  if(0 == m_groups) return false;
  
  QIODevice *compressed =
    KFilterDev::deviceForFile(fileName, "application/x-gzip", true);
  
  if(!compressed->open(IO_WriteOnly)) {
    delete compressed;
    return false;
  }
  
  QTextStream text(compressed);
  
  const KAboutData *aboutData = KGlobal::instance()->aboutData();
  QString comment = i18n("Generated by %1 %2").arg(aboutData->programName())
                                              .arg(aboutData->version());
  text <<  QString("#VRML V2.0 utf8 %1\n").arg(comment);
  
  text << "NavigationInfo { type \"EXAMINE\" }\n";
  text << "Group {\n";
    text << "children [\n";
      switch(m_style) {
        case Backbone:
          {
            text << "Shape {\n";
              text << "geometry IndexedLineSet {";
                
                QString coords, colors;
                
                for(unsigned g = 0; g < m_groups; ++g)
                {
                  QColor color;
                  
                  switch(m_coloring) {
                    case Group:
                      color = progressColor(double(g) / (m_groups - 1));
                      break;
                    case Shapely:
                      if(g < m_seq.count()) {
                        color = shapelyColor(m_seq[g].resName);
                        break;
                      }
                    default:
                      color = monochromeColor();
                      break;
                  }
                  
                  const QString rgb = QString("%1 %2 %3, ")
                                        .arg(double(color.red()) / 255)
                                        .arg(double(color.green()) / 255)
                                        .arg(double(color.blue()) / 255);
                  
                  const unsigned p1 = g * (s_divisions + 1);
                  
                  const QString xyz = QString("%1 %2 %3, ")
                                        .arg(m_backbone[3*p1 + 0])
                                        .arg(m_backbone[3*p1 + 1])
                                        .arg(m_backbone[3*p1 + 2]);
                  
                  if(g > 0)
                  {
                    const unsigned p0 = p1 - (s_divisions + 1);
                    
                    GLfloat v[] = {
                                    (m_backbone[3*p0 + 0] + m_backbone[3*p1 + 0]) / 2,
                                    (m_backbone[3*p0 + 1] + m_backbone[3*p1 + 1]) / 2,
                                    (m_backbone[3*p0 + 2] + m_backbone[3*p1 + 2]) / 2
                                  };
                    
                    coords += QString("%1 %2 %3, ").arg(v[0]).arg(v[1]).arg(v[2]);
                    colors += rgb;
                    
                    coords +=  xyz;
                    colors += rgb;
                  }
                  if(g < m_groups - 1)
                  {
                    coords += xyz;
                    colors += rgb;
                    
                    const unsigned p2 = p1 + (s_divisions + 1);
                    
                    GLfloat v[] = {
                                  (m_backbone[3*p1 + 0] + m_backbone[3*p2 + 0]) / 2,
                                  (m_backbone[3*p1 + 1] + m_backbone[3*p2 + 1]) / 2,
                                  (m_backbone[3*p1 + 2] + m_backbone[3*p2 + 2]) / 2
                                };
                    
                    coords += QString("%1 %2 %3, ").arg(v[0]).arg(v[1]).arg(v[2]);
                    colors += rgb;
                  }
                }  
                text << "coord Coordinate { point [ " << coords << " ] }\n";
                text << "color Color { color [ " << colors << " ] }\n";
                text << "coordIndex [ ";
                  for(unsigned i = 0; i < 4 * (m_groups - 1); ++i)
                    text << QString::number(i) << " ";
                text << "]\n"; // coordIndex
              text << "}\n"; // IndexedLineSet
            text << "}\n"; // Shape
          }
          break;
        case Spline:
          {
            text << "Shape {\n";
              text << "geometry IndexedLineSet {";
                
                QString coords, colors;
                
                const unsigned points = m_groups + s_divisions * (m_groups - 1);
                
                for(unsigned p = 0; p < points; ++p)
                {
                  const unsigned g = unsigned(double(p) / (s_divisions + 1) + 0.5);
                  
                  QColor color;
                  
                  switch(m_coloring) {
                    case Group:
                      color = progressColor(double(g) / (m_groups - 1));
                      break;
                    case Shapely:
                      if(g < m_seq.count()) {
                        color = shapelyColor(m_seq[g].resName);
                        break;
                      }
                    default:
                      color = monochromeColor();
                      break;
                  }
                  
                  coords += QString("%1 %2 %3, ").arg(m_backbone[3*p + 0])
                                                 .arg(m_backbone[3*p + 1])
                                                 .arg(m_backbone[3*p + 2]);
                  
                  colors += QString("%1 %2 %3, ").arg(double(color.red()) / 255)
                                                 .arg(double(color.green()) / 255)
                                                 .arg(double(color.blue()) / 255);
                }  
                text << "coord Coordinate { point [ " << coords << " ] }\n";
                text << "color Color { color [ " << colors << " ] }\n";
                text << "coordIndex [ ";
                  for(unsigned p = 0; p < points; ++p)
                    text << QString::number(p) << " ";
                text << "]\n"; // coordIndex
              text << "}\n"; // IndexedLineSet
            text << "}\n"; // Shape
          }
          break;
        case Wireframe:
          {
            text << "Shape {\n";
              text << "geometry IndexedLineSet {";
                
                unsigned vertices = 0;
                QString coords, indices, colors;
                
                unsigned resSeq = 0;
                QValueList<PredictorAtomPDB>::const_iterator atom, curr, prev, n, ca, c, o;
                for(atom = m_atoms.begin(), c = m_atoms.end(); atom != m_atoms.end(); ++atom)
                {
                  QValueList<PredictorAtomPDB>::const_iterator parent;
                  
                  if((*atom).resSeq != resSeq) {
                    resSeq = (*atom).resSeq;
                    curr = atom; prev = c;
                    n = ca = c = o = m_atoms.end();
                  }
                  
                  if((*atom).name.iupac == "N") {
                    n = atom; parent = prev;
                  } else if((*atom).name.iupac == "CA") {
                    ca = atom; parent = n;
                  } else if((*atom).name.iupac == "C") {
                    c = atom; parent = ca;
                  } else if((*atom).name.iupac == "O") {
                    o = atom; parent = c;
                  } else {
                    QValueList<PredictorAtomPDB>::const_iterator uncle = m_atoms.end();
                    for(parent = curr; parent != atom; ++parent)
                      if((*parent).name.remoteness + 1 == (*atom).name.remoteness)
                        if((*atom).name.branch == (*parent).name.branch)
                          break;
                        else if((*atom).name.branch == (*parent).name.branch + 1)
                          uncle = parent;
                    
                    if(parent == atom) parent = uncle;
                  }
                  
                  if(parent == m_atoms.end()) continue;
                  
                  const unsigned pa = (*atom).serial - 1,
                                 pp = (*parent).serial - 1;
                  
                  GLfloat v[] = {
                                  (m_atom[3*pp + 0] + m_atom[3*pa + 0]) / 2,
                                  (m_atom[3*pp + 1] + m_atom[3*pa + 1]) / 2,
                                  (m_atom[3*pp + 2] + m_atom[3*pa + 2]) / 2
                                };
                  
                  const QString xyz = QString("%1 %2 %3, ").arg(v[0])
                                                           .arg(v[1])
                                                           .arg(v[2]); 
                  
                  coords += QString("%1 %2 %3, ").arg(m_atom[3*pp + 0])
                                                 .arg(m_atom[3*pp + 1])
                                                 .arg(m_atom[3*pp + 2]);
                  coords += xyz;
                  coords += xyz;
                  coords += QString("%1 %2 %3, ").arg(m_atom[3*pa + 0])
                                                 .arg(m_atom[3*pa + 1])
                                                 .arg(m_atom[3*pa + 2]);
                  
                  indices += QString("%1 %2 %3 %4 -1 ").arg(vertices + 0)
                                                       .arg(vertices + 1)
                                                       .arg(vertices + 2)
                                                       .arg(vertices + 3);
                  vertices += 4;
                  
                  const unsigned g = resSeq - 1;
                  
                  QColor color;
                  QString rgb;
                  
                  switch(m_coloring) {
                    case Group:
                      color = progressColor(double(g) / (m_groups - 1));
                      break;
                    case Shapely:
                      color = shapelyColor(m_seq[g].resName);
                      break;
                    case CPK:
                      color = cpkColor((*parent).element);
                      break;
                    default:
                      color = monochromeColor();
                      break;
                  }
                  
                  rgb = QString("%1 %2 %3, ")
                          .arg(double(color.red()) / 255)
                          .arg(double(color.green()) / 255)
                          .arg(double(color.blue()) / 255);
                          
                  colors += rgb;
                  colors += rgb;
                  
                  switch(m_coloring) {
                    case Group:
                      color = progressColor(double(g) / (m_groups - 1));
                      break;
                    case Shapely:
                      color = shapelyColor(m_seq[g].resName);
                      break;
                    case CPK:
                      color = cpkColor((*atom).element);
                      break;
                    default:
                      color = monochromeColor();
                      break;
                  }
                  
                  rgb = QString("%1 %2 %3, ")
                          .arg(double(color.red()) / 255)
                          .arg(double(color.green()) / 255)
                          .arg(double(color.blue()) / 255);
                          
                  colors += rgb;
                  colors += rgb;
                }
                text << "coord Coordinate { point [ " << coords << " ] }\n";
                text << "color Color { color [ " << colors << " ] }\n";
                text << "coordIndex [ " << indices << "]\n";
                text << "colorIndex [ " << indices << "]\n";
              text << "}\n"; // IndexedLineSet
            text << "}\n"; // Shape
          }
          break;
        case Spacefill:
          {
            unsigned a;
            QValueList<PredictorAtomPDB>::const_iterator atom;
            for(a = 0, atom = m_atoms.begin(); atom != m_atoms.end(); ++a, ++atom)
            {
              QColor color;
              
              const unsigned g = (*atom).resSeq - 1;
              
              switch(m_coloring) {
                case Group:
                  color = progressColor(double(g) / (m_groups - 1));
                  break;
                case Shapely:
                  color = shapelyColor(m_seq[g].resName);
                  break;
                case CPK:
                  color = cpkColor((*atom).element);
                  break;
                default:
                  color = monochromeColor();
                  break;
              }
              
              text << "Transform {\n";
                text << "translation "
                     << QString("%1 %2 %3").arg(m_atom[3*a + 0])
                                           .arg(m_atom[3*a + 1])
                                           .arg(m_atom[3*a + 2])
                     << "\n";
                text << "scale 0.1 0.1 0.1\n";
                text << "children Shape {\n";
                  text << "geometry Sphere { radius "
                       << QString::number(atomRadius((*atom).element))
                       << " }\n";
                  text << "appearance Appearance {\n";
                    text << "material Material { diffuseColor " 
                         << QString("%1 %2 %3").arg(double(color.red()) / 255)
                                               .arg(double(color.green()) / 255)
                                               .arg(double(color.blue()) / 255)
                         << " }\n";
                  text << "}\n"; // Appearance
                text << "}\n"; // Shape
              text << "}\n"; // Transform
            }
          }
          break;
        default:
          break;
      }
    text << "]\n"; // children
  text << "}\n"; // Group
  
  compressed->close();
  delete compressed;  
  return true;
}

bool KBSPredictorMoleculeModel::exportX3D(const QString &fileName)
{
  if(0 == m_groups) return false;
  
  QIODevice *compressed = KFilterDev::deviceForFile(fileName, "application/x-gzip");
  
  if(!compressed->open(IO_WriteOnly)) {
    delete compressed;
    return false;
  }
  
  QTextStream text(compressed);
  
  text << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
  
  QDomDocumentType docType = QDomImplementation().createDocumentType(
                               "X3D",
                               "ISO//Web3D//DTD X3D 3.0//EN", 
                               "http://www.web3d.org/specifications/x3d-3.0.dtd"
                             );
  
  QDomDocument doc(docType);
  
  QDomElement x3d = doc.createElement("X3D");
  x3d.setAttribute("profile", "Immersive");
  doc.appendChild(x3d);
  
  QDomElement scene = doc.createElement("Scene");
  x3d.appendChild(scene);
  
  QDomElement navigationInfo = doc.createElement("NavigationInfo");
  navigationInfo.setAttribute("type", "EXAMINE");
  scene.appendChild(navigationInfo);
  
  QDomElement group = doc.createElement("Group");
  scene.appendChild(group);
  
  switch(m_style) {
    case Backbone:
      {
        QDomElement shape = doc.createElement("Shape");
        group.appendChild(shape);
        
        QString indices;
        
        for(unsigned i = 0; i < 4 * (m_groups - 1); ++i)
          indices += QString("%1 ").arg(i);

        QDomElement indexedLineSet = doc.createElement("IndexedLineSet");
        indexedLineSet.setAttribute("coordIndex", indices);
        shape.appendChild(indexedLineSet);
        
        QString coords, colors;
        
        for(unsigned g = 0; g < m_groups; ++g)
        {
          QColor color;
          
          switch(m_coloring) {
            case Group:
              color = progressColor(double(g) / (m_groups - 1));
              break;
            case Shapely:
              if(g < m_seq.count()) {
                color = shapelyColor(m_seq[g].resName);
                break;
              }
            default:
              color = monochromeColor();
              break;
          }
          
          const QString rgb = QString("%1 %2 %3, ").arg(double(color.red()) / 255)
                                                   .arg(double(color.green()) / 255)
                                                   .arg(double(color.blue()) / 255);
          
          const unsigned p1 = g * (s_divisions + 1);
          
          const QString xyz = QString("%1 %2 %3, ").arg(m_backbone[3*p1 + 0])
                                                   .arg(m_backbone[3*p1 + 1])
                                                   .arg(m_backbone[3*p1 + 2]);
          
          if(g > 0)
          {
            const unsigned p0 = p1 - (s_divisions + 1);
            
            GLfloat v[] = {
                            (m_backbone[3*p0 + 0] + m_backbone[3*p1 + 0]) / 2,
                            (m_backbone[3*p0 + 1] + m_backbone[3*p1 + 1]) / 2,
                            (m_backbone[3*p0 + 2] + m_backbone[3*p1 + 2]) / 2
                          };
            
            coords += QString("%1 %2 %3, ").arg(v[0]).arg(v[1]).arg(v[2]);
            colors += rgb;
            
            coords +=  xyz;
            colors += rgb;
          }
          if(g < m_groups - 1)
          {
            coords += xyz;
            colors += rgb;
            
            const unsigned p2 = p1 + (s_divisions + 1);
            
            GLfloat v[] = {
                            (m_backbone[3*p1 + 0] + m_backbone[3*p2 + 0]) / 2,
                            (m_backbone[3*p1 + 1] + m_backbone[3*p2 + 1]) / 2,
                            (m_backbone[3*p1 + 2] + m_backbone[3*p2 + 2]) / 2
                          };
            
            coords += QString("%1 %2 %3, ").arg(v[0]).arg(v[1]).arg(v[2]);
            colors += rgb;
          }
        }
        
        QDomElement coordinate = doc.createElement("Coordinate");
        coordinate.setAttribute("point", coords);
        indexedLineSet.appendChild(coordinate);
        
        QDomElement color = doc.createElement("Color");
        color.setAttribute("color", colors);
        indexedLineSet.appendChild(color);
      }
      break;
    case Spline:
      {
        QDomElement shape = doc.createElement("Shape");
        group.appendChild(shape);
        
        const unsigned points = m_groups + s_divisions * (m_groups - 1);
        
        QString indices;
        
        for(unsigned i = 0; i < points; ++i)
          indices += QString("%1 ").arg(i);
        
        QDomElement indexedLineSet = doc.createElement("IndexedLineSet");
        indexedLineSet.setAttribute("coordIndex", indices);
        shape.appendChild(indexedLineSet);
        
        QString coords, colors;
        
        for(unsigned p = 0; p < points; ++p)
        {
          const unsigned g = unsigned(double(p) / (s_divisions + 1) + 0.5);
          
          QColor color;
          
          switch(m_coloring) {
            case Group:
              color = progressColor(double(g) / (m_groups - 1));
              break;
            case Shapely:
              if(g < m_seq.count()) {
                color = shapelyColor(m_seq[g].resName);
                break;
              }
            default:
              color = monochromeColor();
              break;
          }
          
          coords += QString("%1 %2 %3, ").arg(m_backbone[3*p + 0])
                                         .arg(m_backbone[3*p + 1])
                                         .arg(m_backbone[3*p + 2]);
          
          colors += QString("%1 %2 %3, ").arg(double(color.red()) / 255)
                                         .arg(double(color.green()) / 255)
                                         .arg(double(color.blue()) / 255);
        }  
        
        QDomElement coordinate = doc.createElement("Coordinate");
        coordinate.setAttribute("point", coords);
        indexedLineSet.appendChild(coordinate);
        
        QDomElement color = doc.createElement("Color");
        color.setAttribute("color", colors);
        indexedLineSet.appendChild(color);
      }
      break;
    case Wireframe:
      {
        QDomElement shape = doc.createElement("Shape");
        group.appendChild(shape);
        
        unsigned vertices = 0;
        QString coords, indices, colors;
        
        unsigned resSeq = 0;
        QValueList<PredictorAtomPDB>::const_iterator atom, curr, prev, n, ca, c, o;
        for(atom = m_atoms.begin(), c = m_atoms.end(); atom != m_atoms.end(); ++atom)
        {
          QValueList<PredictorAtomPDB>::const_iterator parent;
          
          if((*atom).resSeq != resSeq) {
            resSeq = (*atom).resSeq;
            curr = atom; prev = c;
            n = ca = c = o = m_atoms.end();
          }
          
          if((*atom).name.iupac == "N") {
            n = atom; parent = prev;
          } else if((*atom).name.iupac == "CA") {
            ca = atom; parent = n;
          } else if((*atom).name.iupac == "C") {
            c = atom; parent = ca;
          } else if((*atom).name.iupac == "O") {
            o = atom; parent = c;
          } else {
            QValueList<PredictorAtomPDB>::const_iterator uncle = m_atoms.end();
            for(parent = curr; parent != atom; ++parent)
              if((*parent).name.remoteness + 1 == (*atom).name.remoteness)
                if((*atom).name.branch == (*parent).name.branch)
                  break;
                else if((*atom).name.branch == (*parent).name.branch + 1)
                  uncle = parent;
            
            if(parent == atom) parent = uncle;
          }
          
          if(parent == m_atoms.end()) continue;
          
          const unsigned pa = (*atom).serial - 1,
                         pp = (*parent).serial - 1;
          
          GLfloat v[] = {
                          (m_atom[3*pp + 0] + m_atom[3*pa + 0]) / 2,
                          (m_atom[3*pp + 1] + m_atom[3*pa + 1]) / 2,
                          (m_atom[3*pp + 2] + m_atom[3*pa + 2]) / 2
                        };
          
          const QString xyz = QString("%1 %2 %3, ").arg(v[0])
                                                   .arg(v[1])
                                                   .arg(v[2]);
          
          coords += QString("%1 %2 %3, ").arg(m_atom[3*pp + 0])
                                         .arg(m_atom[3*pp + 1])
                                         .arg(m_atom[3*pp + 2]);
          coords += xyz;
          coords += xyz;
          coords += QString("%1 %2 %3, ").arg(m_atom[3*pa + 0])
                                         .arg(m_atom[3*pa + 1])
                                         .arg(m_atom[3*pa + 2]);
          
          indices += QString("%1 %2 %3 %4 -1 ").arg(vertices + 0)
                                               .arg(vertices + 1)
                                               .arg(vertices + 2)
                                               .arg(vertices + 3);
          vertices += 4;
          
          const unsigned g = resSeq - 1;
          
          QColor color;
          QString rgb;
          
          switch(m_coloring) {
            case Group:
              color = progressColor(double(g) / (m_groups - 1));
              break;
            case Shapely:
              color = shapelyColor(m_seq[g].resName);
              break;
            case CPK:
              color = cpkColor((*parent).element);
              break;
            default:
              color = monochromeColor();
              break;
          }
           
          rgb = QString("%1 %2 %3, ")
                  .arg(double(color.red()) / 255)
                  .arg(double(color.green()) / 255)
                  .arg(double(color.blue()) / 255);
                  
          colors += rgb;
          colors += rgb;
          
          switch(m_coloring) {
            case Group:
              color = progressColor(double(g) / (m_groups - 1));
              break;
            case Shapely:
              color = shapelyColor(m_seq[g].resName);
              break;
            case CPK:
              color = cpkColor((*atom).element);
              break;
            default:
              color = monochromeColor();
              break;
          }
          
          rgb = QString("%1 %2 %3, ")
                  .arg(double(color.red()) / 255)
                  .arg(double(color.green()) / 255)
                  .arg(double(color.blue()) / 255);
                  
          colors += rgb;
          colors += rgb;
        }
        
        QDomElement indexedLineSet = doc.createElement("IndexedLineSet");
        indexedLineSet.setAttribute("coordIndex", indices);
        indexedLineSet.setAttribute("colorIndex", indices);
        shape.appendChild(indexedLineSet);
        
        QDomElement coordinate = doc.createElement("Coordinate");
        coordinate.setAttribute("point", coords);
        indexedLineSet.appendChild(coordinate);
        
        QDomElement color = doc.createElement("Color");
        color.setAttribute("color", colors);
        indexedLineSet.appendChild(color);
      }
      break;
    case Spacefill:
      {
        unsigned a;
        QValueList<PredictorAtomPDB>::const_iterator atom;
        for(a = 0, atom = m_atoms.begin(); atom != m_atoms.end(); ++a, ++atom)
        {
          QColor color;
          
          const unsigned g = (*atom).resSeq - 1;
          
          switch(m_coloring) {
            case Group:
              color = progressColor(double(g) / (m_groups - 1));
              break;
            case Shapely:
              color = shapelyColor(m_seq[g].resName);
              break;
            case CPK:
              color = cpkColor((*atom).element);
              break;
            default:
              color = monochromeColor();
              break;
          }
          
          const QString rgb = QString("%1 %2 %3").arg(double(color.red()) / 255)
                                                 .arg(double(color.green()) / 255)
                                                 .arg(double(color.blue()) / 255);
          
          const QString xyz = QString("%1 %2 %3").arg(m_atom[3*a + 0])
                                                 .arg(m_atom[3*a + 1])
                                                 .arg(m_atom[3*a + 2]);
          
          const QString radius = QString::number(atomRadius((*atom).element));
          
          QDomElement transform = doc.createElement("Transform");
          transform.setAttribute("translation", xyz);
          transform.setAttribute("scale", "0.1 0.1 0.1");
          group.appendChild(transform);
          
          QDomElement shape = doc.createElement("Shape");
          transform.appendChild(shape);
          
          QDomElement sphere = doc.createElement("Sphere");
          sphere.setAttribute("radius", radius);
          shape.appendChild(sphere);
          
          QDomElement appearance = doc.createElement("Appearance");
          shape.appendChild(appearance);
          
          QDomElement material = doc.createElement("Material");
          material.setAttribute("diffuseColor", rgb);
          appearance.appendChild(material);
        }
      }
      break;
    default:
      break;
  }
  
  text << doc.toString();
  
  compressed->close();
  delete compressed;
  return true;
}


void KBSPredictorMoleculeModel::interpolateBackbone()
{
  for(unsigned g = 0; g < m_groups - 1; ++g)
  {
    const unsigned p1 = g * (s_divisions + 1),
                   p2 = p1 + (s_divisions + 1);
    
    const double d12 = distance(m_backbone + 3*p1, m_backbone + 3*p2);
    
    GLfloat a1[3], a2[3];
    for(unsigned i = 0; i < 3; ++i) {
      if(g > 0) {
        const unsigned p0 = p1 - (s_divisions + 1);
        
        a1[i] = (m_backbone[3*p2 + i] - m_backbone[3*p0 + i]);
        a1[i] *= d12 / distance(m_backbone + 3*p2, m_backbone + 3*p0);
      } else
        a1[i] = m_backbone[3*p2 + i] - m_backbone[3*p1 + i];
     
      a1[i] *= 0.4;
      a1[i] += m_backbone[3*p1 + i];
      
      if(g < m_groups-2) {
        const unsigned p3 = p2 + (s_divisions + 1);
        
        a2[i] = (m_backbone[3*p3 + i] - m_backbone[3*p1 + i]);
        a2[i] *= d12 / distance(m_backbone + 3*p3, m_backbone + 3*p1);
      }
      else
        a2[i] = m_backbone[3*p2 + i] - m_backbone[3*p1 + i];
      
      a2[i] *= -0.4;
      a2[i] += m_backbone[3*p2 + i];
    }
    
    for(unsigned p = p1 + 1; p < p2; ++p)
    {
      const double t = double(p - p1) / (s_divisions + 1),
                   t1 = 1 - t;
      
      double cp1 = t1 * t1 * t1,
             ca1 = 3 * cp1 * t / t1,
             ca2 = ca1 * t / t1,
             cp2 = (ca2 / 3) * t / t1;
      
      for(unsigned i = 0; i < 3; ++i)
        m_backbone[3*p + i] = cp1 * m_backbone[3*p1 + i]
                            + ca1 * a1[i] + ca2 * a2[i]
                            + cp2 * m_backbone[3*p2 + i];
    }
  }
}

KBSPredictorMoleculeView::KBSPredictorMoleculeView(QWidget *parent, char *name)
                        : QGLWidget(parent, name), m_scale(1),
                          m_model(new KBSPredictorMoleculeModel(this)),
                          m_tracking(false), m_quadric(NULL), m_base(0)
{
  setFocusPolicy(StrongFocus);

  connect(m_model, SIGNAL(styleChanged()), this, SLOT(updateGL()));
  connect(m_model, SIGNAL(coloringChanged()), this, SLOT(updateGL()));
  connect(m_model, SIGNAL(dataChanged()), this, SLOT(updateGL()));
}

KBSPredictorMoleculeView::~KBSPredictorMoleculeView()
{
  makeCurrent();
  if(0 != m_base) glDeleteLists(m_base, Shapes);
  if(NULL != m_quadric) gluDeleteQuadric(m_quadric);
}

KBSPredictorMoleculeModel *KBSPredictorMoleculeView::model() const
{
  return m_model;
}

QPixmap KBSPredictorMoleculeView::pixmap()
{
  updateGL();

  QImage image = grabFrameBuffer();

  QPixmap out;
  out.convertFromImage(image, AvoidDither);

  return out;
}

void KBSPredictorMoleculeView::initializeGL()
{
  qglClearColor(black);
  glShadeModel(GL_SMOOTH);
  
  glEnable(GL_COLOR_MATERIAL);
  
  GLfloat lightPosition[] = { 0, 0, 10, 0.0 };
  glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
  glEnable(GL_LIGHT0);
  
  glEnable(GL_DEPTH_TEST);
  
  m_quadric = gluNewQuadric();
  m_base = glGenLists(Shapes);
  
  gluQuadricDrawStyle(m_quadric, GLU_FILL);
  glNewList(m_base + Ball, GL_COMPILE);
    gluSphere(m_quadric, 0.09, 15, 10);
  glEndList();
}

void KBSPredictorMoleculeView::resizeGL(int width, int height)
{
  if(0 == height) height++;
  glViewport(0, 0, width, height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0, GLfloat(width) / GLfloat(height), 1, 200);
  glMatrixMode(GL_MODELVIEW);
}

void KBSPredictorMoleculeView::paintGL()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
  if(0 == m_model->m_groups) return;
  
  glPushMatrix();
  
  gluLookAt(0, 0, 10, 0, 0, 0, 0, 1, 0);
  
  glScaled(m_scale, m_scale, m_scale);
  
  switch(m_model->style()) {
    case KBSPredictorMoleculeModel::Backbone:
      {
        for(unsigned g = 0; g < m_model->m_groups; ++g)
        {
          switch(m_model->coloring()) {
            case KBSPredictorMoleculeModel::Group:
              qglColor(progressColor(double(g) / (m_model->m_groups - 1)));
              break;
            case KBSPredictorMoleculeModel::Shapely:
              if(g < m_model->m_seq.count()) {
                qglColor(shapelyColor(m_model->m_seq[g].resName));
                break;
              }
            default:
              qglColor(monochromeColor());
              break;
          }
          
          const unsigned p1 = g * (m_model->s_divisions + 1);
          
          glBegin(GL_LINES);
          if(g > 0)
          {
            const unsigned p0 = p1 - (m_model->s_divisions + 1);
            
            GLfloat v[] = {
                            (m_model->m_backbone[3*p0 + 0] + m_model->m_backbone[3*p1 + 0]) / 2,
                            (m_model->m_backbone[3*p0 + 1] + m_model->m_backbone[3*p1 + 1]) / 2,
                            (m_model->m_backbone[3*p0 + 2] + m_model->m_backbone[3*p1 + 2]) / 2
                          };
            
            glVertex3fv(v);
            glVertex3fv(m_model->m_backbone + 3*p1);
          }
          if(g < m_model->m_groups - 1)
          {
            const unsigned p2 = p1 + (m_model->s_divisions + 1);
            
            GLfloat v[] = {
                            (m_model->m_backbone[3*p1 + 0] + m_model->m_backbone[3*p2 + 0]) / 2,
                            (m_model->m_backbone[3*p1 + 1] + m_model->m_backbone[3*p2 + 1]) / 2,
                            (m_model->m_backbone[3*p1 + 2] + m_model->m_backbone[3*p2 + 2]) / 2
                          };
            
            glVertex3fv(v);
            glVertex3fv(m_model->m_backbone + 3*p1);
          }
          glEnd();
        }
      }
      break;
    case KBSPredictorMoleculeModel::Spline:
      {
        glBegin(GL_LINE_STRIP);
        
        const unsigned points = m_model->m_groups + m_model->s_divisions * (m_model->m_groups - 1);
        for(unsigned p = 0; p < points; ++p)
        {
          const unsigned g = unsigned(double(p) / (m_model->s_divisions + 1) + 0.5);
          
          switch(m_model->coloring()) {
            case KBSPredictorMoleculeModel::Group:
              qglColor(progressColor(double(g) / (m_model->m_groups - 1)));
              break;
            case KBSPredictorMoleculeModel::Shapely:
              if(g < m_model->m_seq.count()) {
                qglColor(shapelyColor(m_model->m_seq[g].resName));
                break;
              }
            default:
              qglColor(monochromeColor());
              break;
          }
          
          glVertex3fv(m_model->m_backbone + 3*p);
        }
        
        glEnd();
      }
      break;
    case KBSPredictorMoleculeModel::Wireframe:
      {
        glBegin(GL_LINES);
        
        unsigned resSeq = 0;
        QValueList<PredictorAtomPDB>::const_iterator atom, curr, prev, n, ca, c, o;
        for(atom = m_model->m_atoms.begin(), c = m_model->m_atoms.end();
            atom != m_model->m_atoms.end(); ++atom)
        {
          QValueList<PredictorAtomPDB>::const_iterator parent;
          
          if((*atom).resSeq != resSeq) {
            resSeq = (*atom).resSeq;
            curr = atom; prev = c;
            n = ca = c = o = m_model->m_atoms.end();
          }
          
          if((*atom).name.iupac == "N") {
            n = atom; parent = prev;
          } else if((*atom).name.iupac == "CA") {
            ca = atom; parent = n;
          } else if((*atom).name.iupac == "C") {
            c = atom; parent = ca;
          } else if((*atom).name.iupac == "O") {
            o = atom; parent = c;
          } else {
            QValueList<PredictorAtomPDB>::const_iterator uncle = m_model->m_atoms.end();
            for(parent = curr; parent != atom; ++parent)
              if((*parent).name.remoteness + 1 == (*atom).name.remoteness)
                if((*atom).name.branch == (*parent).name.branch)
                  break;
                else if((*atom).name.branch == (*parent).name.branch + 1)
                  uncle = parent;
            
            if(parent == atom) parent = uncle;
          }
          
          if(parent == m_model->m_atoms.end()) continue;
          
          const unsigned g = resSeq - 1,
                         pa = (*atom).serial - 1,
                         pp = (*parent).serial - 1;
          
          GLfloat v[] = {
                          (m_model->m_atom[3*pp + 0] + m_model->m_atom[3*pa + 0]) / 2,
                          (m_model->m_atom[3*pp + 1] + m_model->m_atom[3*pa + 1]) / 2,
                          (m_model->m_atom[3*pp + 2] + m_model->m_atom[3*pa + 2]) / 2
                        };
            
          switch(m_model->coloring()) {
            case KBSPredictorMoleculeModel::Group:
              qglColor(progressColor(double(g) / (m_model->m_groups - 1)));
              break;
            case KBSPredictorMoleculeModel::Shapely:
              qglColor(shapelyColor(m_model->m_seq[g].resName));
              break;
            case KBSPredictorMoleculeModel::CPK:
              qglColor(cpkColor((*parent).element));
              break;
            default:
              qglColor(monochromeColor());
              break;
          }
          
          glVertex3fv(m_model->m_atom + 3*pp);
          glVertex3fv(v);
          
          switch(m_model->coloring()) {
            case KBSPredictorMoleculeModel::Group:
              qglColor(progressColor(double(g) / (m_model->m_groups - 1)));
              break;
            case KBSPredictorMoleculeModel::Shapely:
              qglColor(shapelyColor(m_model->m_seq[g].resName));
              break;
            case KBSPredictorMoleculeModel::CPK:
              qglColor(cpkColor((*atom).element));
              break;
            default:
              qglColor(monochromeColor());
              break;
          }
          
          glVertex3fv(v);
          glVertex3fv(m_model->m_atom + 3*pa);
        }
        
        glEnd();
      }
      break;
    case KBSPredictorMoleculeModel::Spacefill:
      {
        glEnable(GL_LIGHTING);
        
        unsigned a;
        QValueList<PredictorAtomPDB>::const_iterator atom;
        for(a = 0, atom = m_model->m_atoms.begin(); atom != m_model->m_atoms.end(); ++a, ++atom)
        {
          const unsigned g = (*atom).resSeq - 1;
          
          switch(m_model->coloring()) {
            case KBSPredictorMoleculeModel::Group:
              qglColor(progressColor(double(g) / (m_model->m_groups - 1)));
              break;
            case KBSPredictorMoleculeModel::Shapely:
              qglColor(shapelyColor(m_model->m_seq[g].resName));
              break;
            case KBSPredictorMoleculeModel::CPK:
              qglColor(cpkColor((*atom).element));
              break;
            default:
              qglColor(monochromeColor());
              break;
          }
          
          const double radius = atomRadius((*atom).element);
          
          glPushMatrix();          
          glTranslatef(m_model->m_atom[3*a + 0],
                       m_model->m_atom[3*a + 1],
                       m_model->m_atom[3*a + 2]);
          glScaled(radius, radius, radius);
          glCallList(m_base + Ball);
          glPopMatrix();
        }
        
        glDisable(GL_LIGHTING);
      }
      break;  
    default:
      break;
  }
  
  glPopMatrix();
  
  glFlush();
}

void KBSPredictorMoleculeView::keyPressEvent(QKeyEvent *e)
{
  switch(e->key()) {
    case Key_Up:
      m_model->rotateData(0, -10);
      break;
    case Key_Down:
      m_model->rotateData(0, 10);
      break;
    case Key_Left:
      m_model->rotateData(-10, 0);
      break;
    case Key_Right:
      m_model->rotateData(10, 0);
      break;
    case Key_Plus:
      m_scale *= 1.05;
      updateGL();
      break;
    case Key_Minus:
      m_scale /= 1.05;
      updateGL();
      break;
    default:
      e->ignore();
      break;
  }
}

void KBSPredictorMoleculeView::mousePressEvent(QMouseEvent *e)
{
  if(e->button() == LeftButton) {
    m_tracking = true;
    m_last = e->pos();
  } else
    e->ignore();
}

void KBSPredictorMoleculeView::mouseReleaseEvent(QMouseEvent *e)
{
  if(m_tracking)
  {
    if(e->state() & ControlButton) {
      const int dy = (e->pos().y() - m_last.y());
      if(dy > 0) m_scale /= (1 + 1e-3 * dy); else m_scale *= (1 - 1e-3 * dy);
      updateGL();
    } else
      m_model->rotateData(e->pos().x() - m_last.x(), e->pos().y() - m_last.y());
    
    m_tracking = false;
  }
  else
    e->ignore();
}

void KBSPredictorMoleculeView::mouseMoveEvent(QMouseEvent *e)
{
  if(m_tracking)
  {
    if(e->state() & ControlButton) {
      const int dy = (e->pos().y() - m_last.y());
      if(dy > 0) m_scale /= (1 + 1e-3 * dy); else m_scale *= (1 - 1e-3 * dy);
      updateGL();
    } else
      m_model->rotateData(e->pos().x() - m_last.x(), e->pos().y() - m_last.y());
    
    m_last = e->pos();
  }
  else
    e->ignore();
}

Generated by  Doxygen 1.6.0   Back to index