NCL Composer  0.1.5
 All Classes Functions Variables Pages
NCLTextualViewPlugin.cpp
1 /*
2  * Copyright 2011 TeleMidia/PUC-Rio.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see
16  * <http://www.gnu.org/licenses/>.
17  */
18 #include "NCLTextualViewPlugin.h"
19 
20 #include <QMetaObject>
21 #include <QMetaMethod>
22 #include <QMessageBox>
23 
24 #include <QApplication>
25 #include <QProgressDialog>
26 #include <QDomDocument>
27 #include <QTextStream>
28 #include <deque>
29 using namespace std;
30 
31 const QString PROLOG ("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n<!-- Generated by NCL Composer -->\n");
32 
34 {
35  window = new NCLTextEditorMainWindow();
36  nclTextEditor = window->getTextEditor();
37 
38  tmpNclTextEditor = NULL;
39 
40  project = NULL;
41  connect( window,
42  SIGNAL(elementAdded(QString,QString,QMap<QString,QString>&,bool)),
43  this,
44  SIGNAL(addEntity(QString,QString,QMap<QString,QString>&,bool)));
45 
46  isSyncing = false;
47 
48  updateModelShortcut = new QShortcut(window);
49  updateModelShortcut->setKey(QKeySequence("F5"));
50 
51  connect(updateModelShortcut, SIGNAL(activated()),
52  this, SLOT(updateCoreModel()) );
53 
54  connect(nclTextEditor, SIGNAL(focusLosted(QFocusEvent*)),
55  this, SLOT(manageFocusLost(QFocusEvent*)));
56 
57  connect(nclTextEditor, SIGNAL(textChanged()),
58  this, SIGNAL(setCurrentProjectAsDirty()));
59 }
60 
62 {
63  delete window;
64  window = NULL;
65 }
66 
68 {
69  QString data = project->getPluginData("br.puc-rio.telemidia.NCLTextualView");
70 
71  QString startEntitiesSep = "$START_ENTITIES_LINES$";
72  QString endEntitiesSep = "$END_ENTITIES_LINES$";
73  int indexOfStartEntities = data.indexOf(startEntitiesSep);
74  int indexOfEndEntities = data.indexOf(endEntitiesSep);
75 
76  // Just for safety clearing start and end line previous saved.
77  QString key;
78  foreach(key, startEntityOffset.keys())
79  startEntityOffset.remove(key);
80  foreach(key, endEntityOffset.keys())
81  endEntityOffset.remove(key);
82 
83  QString text = data.left(indexOfStartEntities);
84  if(text.isEmpty() || text.isNull())
85  nclTextEditor->setText(PROLOG);
86  else
87  nclTextEditor->setText(text);
88 
89  int indexOfStartEntitiesContent = indexOfStartEntities +
90  startEntitiesSep.length();
91  QString startLines = data.mid(indexOfStartEntitiesContent,
92  indexOfEndEntities - indexOfStartEntitiesContent);
93 
94  QString endLines = data.right(data.length() -
95  (indexOfEndEntities+endEntitiesSep.length()));
96 
97  QStringList listStart = startLines.split(",");
98  QStringList listEnd = endLines.split(",");
99 
100  int i;
101 
102  for(i = 0; i < listStart.size()-1 && i < listEnd.size()-1; i +=2)
103  {
104  startEntityOffset[listStart[i]] = listStart[i+1].toInt();
105  endEntityOffset[listEnd[i]] = listEnd[i+1].toInt();
106  }
107 
108  // qDebug() << i << listStart.size() << listEnd.size();
109  if( i != listStart.size() || i != listEnd.size())
110  {
111  qDebug() << "The data saved in the file is corrupted. Forcing"
112  << " updateFromModel";
113 
114  updateFromModel();
115  }
116 
117  nclTextEditor->setDocumentUrl(project->getLocation());
118 
119  updateErrorMessages();
120 }
121 
123 {
124  return window;
125 }
126 
128 {
129 // qDebug() << "NCLTextualViewPlugin::updateFromModel";
130  incrementalUpdateFromModel();
131 
132  updateErrorMessages();
133 }
134 
136 {
137  nclTextEditor->clear();
138  nclTextEditor->setText(PROLOG);
139  startEntityOffset.clear();
140  endEntityOffset.clear();
141 
142  if(project->getChildren().size())
143  {
144  Entity *entity = project;
145  QList <Entity *> entities;
146  entities.push_back(entity);
147  bool first = true;
148  while(entities.size())
149  {
150  entity = entities.front();
151  entities.pop_front();
152 
153  if(!first) //ignore the project root
154  onEntityAdded("xxx", entity);
155  else
156  first = false;
157  QVector<Entity *> children = entity->getChildren();
158  for(int i = 0; i < children.size(); i++)
159  {
160  entities.push_back(children.at(i));
161  }
162  }
163  }
164 }
165 
167 {
168  nclTextEditor->clear();
169  nclTextEditor->setText(PROLOG);
170  QDomDocument doc ("document");
171  if(project->getChildren().size())
172  {
173  Entity *entity = project;
174  QList <Entity *> entities;
175  QList <QDomNode> elements;
176  QDomNode current;
177  entities.push_back(entity);
178  elements.push_back(doc);
179 
180  bool first = true;
181  while(entities.size())
182  {
183  entity = entities.front();
184  entities.pop_front();
185  current = elements.front();
186  elements.pop_front();
187 
188  if(!first) //ignore the project root
189  {
190 // current->appendChild(el);
191 // onEntityAdded("xxx", entity);
192  }
193  else
194  {
195  first = false;
196  elements.push_back(doc);
197  }
198 
199  QVector<Entity *> children = entity->getChildren();
200  for(int i = 0; i < children.size(); i++)
201  {
202  entities.push_back(children.at(i));
203  QDomElement el = doc.createElement(children[i]->getType());
204  el.setAttribute("oi", "oi2");
205  el.setAttribute("oi2", "oi3");
206  el.setAttribute("oi3", "oi4");
207  elements.push_back(el);
208  current.appendChild(el);
209  }
210  }
211 
212  QString *text = new QString();
213  QTextStream textStream(text);
214  doc.save(textStream, QDomNode::EncodingFromTextStream);
215  nclTextEditor->setText(PROLOG);
216  nclTextEditor->insertAtPos(textStream.readAll(), PROLOG.size());
217  }
218 }
219 
220 void NCLTextualViewPlugin::onEntityAdded(QString pluginID, Entity *entity)
221 {
222  // Return if this is my call to onEntityAdded
223  // qDebug() << " isSyncing= " << isSyncing;
224  if(pluginID == getPluginInstanceID())
225  {
226  currentEntity = entity;
227  return;
228  }
229 
230  QString line = "<" + entity->getType() + "";
231  int insertAtOffset = PROLOG.size();
232  bool hasOpennedTag = false;
233 
234  //get the line number where the new element must be inserted
235  if(entity->getParentUniqueId() != NULL &&
236  entity->getParent()->getType() != "project")
237  {
238  // Test if exists before access from operator[] becaus if doesn't exist
239  // this operator will create a new (and we don't want this!).
240  if(endEntityOffset.count(entity->getParentUniqueId()))
241  {
242  if(isStartEndTag(entity->getParent()))
243  {
244  openStartEndTag(entity->getParent());
245  hasOpennedTag = true;
246 // printEntitiesOffset();
247  }
248 
249  insertAtOffset = endEntityOffset[entity->getParentUniqueId()];
250  }
251  }
252 
253  // fill the attributes (ordered)
254  deque <QString> *attributes_ordered =
255  NCLStructure::getInstance()->getAttributesOrdered(entity->getType());
256 
257  if(attributes_ordered != NULL)
258  {
259  for(int i = 0; i < attributes_ordered->size(); i++)
260  {
261  if(entity->hasAttribute((*attributes_ordered)[i]))
262  {
263  line += " " + (*attributes_ordered)[i] +
264  "=\"" + entity->getAttribute((*attributes_ordered)[i]) + "\"";
265  }
266  }
267  }
268 
269  map <QString, bool> *attributes =
270  NCLStructure::getInstance()->getAttributes(entity->getType());
271  // Search if there is any other attribute that is not part of NCL Language
272  // but the user fills it.
273  QMap <QString, QString>::iterator begin, end, it;
274  entity->getAttributeIterator(begin, end);
275  for (it = begin; it != end; ++it)
276  {
277  if(attributes == NULL || !attributes->count(it.key()))
278  line += " " + it.key() + "=\"" + it.value() + "\"";
279  }
280 
281  line += "/>\n";
282  int startEntitySize = line.size();
283 
284  if(insertAtOffset >= 0 && insertAtOffset <= nclTextEditor->text().length())
285  {
286  nclTextEditor->insertAtPos(line, insertAtOffset);
287 
288  //update all previous offset numbers (when necessary)
289  updateEntitiesOffset(insertAtOffset, line.size());
290 
291  startEntityOffset[entity->getUniqueId()] = insertAtOffset;
292  endEntityOffset[entity->getUniqueId()] = insertAtOffset + startEntitySize-2;
293 
294  window->getTextEditor()->SendScintilla(QsciScintilla::SCI_SETFOCUS, true);
295 
296  if(hasOpennedTag) //Add a new tab to the new inserted element.
297  fixIdentation(insertAtOffset, true);
298  else // keep the previous tabulation
299  fixIdentation(insertAtOffset, false);
300 
301  currentEntity = entity;
302  }
303  else
304  qWarning() << "NCLTextEditor::onEntityAdded Trying to insert a media in a "
305  "position greater than the text size. It will be ignored!"
306  << insertAtOffset;
307 
308 // printEntitiesOffset();
309 }
310 
312 {
313  // qDebug() << "NCLTextualViewPlugin::onEntityAddError(" << error << ")";
314 }
315 
316 void NCLTextualViewPlugin::onEntityChanged(QString pluginID, Entity *entity)
317 {
318  qDebug() << "PLUGIN (" + pluginID + ") changed the Entity (" +
319  entity->getType() + " - " + entity->getUniqueId() +")";
320 
321  //Return if this is my call to onEntityAdded
322  if(pluginID == getPluginInstanceID() && !isSyncing)
323  return;
324 
325  QString line = "<" + entity->getType() + "";
326 
327  QMap <QString, QString>::iterator begin, end, it;
328  entity->getAttributeIterator(begin, end);
329  for (it = begin; it != end; ++it)
330  {
331  if(it.value() != "")
332  line += " " + it.key() + "=\"" + it.value() + "\"";
333  }
334 
335  int insertAtOffset = 0;
336  if(startEntityOffset.contains(entity->getUniqueId()))
337  insertAtOffset = startEntityOffset.value(entity->getUniqueId());
338 
339  if(insertAtOffset >= 0 && insertAtOffset <= nclTextEditor->text().size())
340  {
341  int previous_length = 0;
342  char curChar = nclTextEditor->SendScintilla(QsciScintilla::SCI_GETCHARAT,
343  insertAtOffset+previous_length);
344  while(curChar != '>' &&
345  (insertAtOffset+previous_length) < nclTextEditor->text().size())
346  {
347  previous_length++;
348  curChar = nclTextEditor->SendScintilla(QsciScintilla::SCI_GETCHARAT,
349  insertAtOffset+previous_length);
350  }
351 
352  curChar = nclTextEditor->SendScintilla(QsciScintilla::SCI_GETCHARAT,
353  insertAtOffset+previous_length-1);
354  if(curChar == '/')
355  {
356  line += "/>";
357  }
358  else
359  line += ">";
360 
361  if((insertAtOffset+previous_length) == nclTextEditor->text().size())
362  {
363  qWarning() << "TextEditor could not perform the requested action.";
364  return;
365  }
366 
367  previous_length+=1;
368 
369  // qDebug() << previous_length;
370  // store the current identation (this must keep equal even with the
371  // modifications)
372  /* int lineident = window->getTextEditor()
373  ->SendScintilla( QsciScintilla::SCI_GETLINEINDENTATION,
374  insertAtLine);*/
375 
376  nclTextEditor->SendScintilla(QsciScintilla::SCI_SETSELECTIONSTART,
377  insertAtOffset);
378  nclTextEditor->SendScintilla(QsciScintilla::SCI_SETSELECTIONEND,
379  insertAtOffset+previous_length);
380  nclTextEditor->removeSelectedText();
381 
382  nclTextEditor->insertAtPos(line, insertAtOffset);
383 
384  //update all previous entities line numbers (when necessary)
385  int diff_size = line.size() - previous_length;
386  updateEntitiesOffset(insertAtOffset, diff_size);
387 
388  nclTextEditor->SendScintilla( QsciScintilla::SCI_GOTOPOS, insertAtOffset);
389  //TODO: fix indentation
390  }
391  else
392  qWarning() << "NCLTextEditor::onEntityAdded Trying to insert a media in a "
393  "position greater than the text size. It will be ignored!"
394  << insertAtOffset;
395 }
396 
397 void NCLTextualViewPlugin::onEntityRemoved(QString pluginID, QString entityID)
398 {
399  // skip if this is my own call to onEntityRemoved
400  if(pluginID == getPluginInstanceID() && !isSyncing)
401  return;
402 
403  int startOffset = startEntityOffset[entityID];
404  int endOffset = endEntityOffset[entityID];
405 
406  char curChar = nclTextEditor->SendScintilla(
407  QsciScintilla::SCI_GETCHARAT,
408  startOffset);
409 
410  while(curChar != '>' && startOffset >= 0)
411  {
412  startOffset--;
413  curChar = nclTextEditor->SendScintilla( QsciScintilla::SCI_GETCHARAT,
414  startOffset);
415  }
416  if(curChar == '>')
417  startOffset++; // does not include the '>' character
418 
419  curChar = nclTextEditor->SendScintilla( QsciScintilla::SCI_GETCHARAT,
420  endOffset);
421 
422  while(curChar != '>' && endOffset < nclTextEditor->text().size())
423  {
424  endOffset++;
425  curChar = nclTextEditor->SendScintilla( QsciScintilla::SCI_GETCHARAT,
426  endOffset);
427  }
428  if(endOffset == nclTextEditor->text().size())
429  {
430  qWarning() << "TextEditor could not perform the requested action.";
431  return;
432  }
433 
434  endOffset++; // includes the '>' character
435 
436  while(isspace(curChar) && endOffset < nclTextEditor->text().size())
437  {
438  endOffset++;
439  curChar = nclTextEditor->SendScintilla( QsciScintilla::SCI_GETCHARAT,
440  endOffset);
441  }
442 
443  nclTextEditor->SendScintilla(QsciScintilla::SCI_SETSELECTIONSTART,
444  startOffset);
445  nclTextEditor->SendScintilla(QsciScintilla::SCI_SETSELECTIONEND,
446  endOffset);
447  nclTextEditor->removeSelectedText();
448 
449  QString key;
450  QList<QString> mustRemoveEntity;
451 
452  // check all entities that is removed together with entityID, i.e.
453  // its children
454  // P.S. This could be get from model
455  foreach(key, startEntityOffset.keys())
456  {
457  // if the element is inside the entity that will be removed:
458  if(startEntityOffset[key] >= startOffset &&
459  endEntityOffset[key]+2 <= endOffset)
460  {
461  mustRemoveEntity.append(key);
462  }
463  else
464  {
465  // otherwise if necessary we must update the start and end line of
466  // the entity.
467  if(startEntityOffset[key] >= startOffset)
468  {
469  startEntityOffset[key] -= (endOffset - startOffset);
470  }
471 
472  if(endEntityOffset[key] >= endOffset)
473  {
474  endEntityOffset[key] -= (endOffset - startOffset);
475  }
476  }
477  }
478 
479  // Remove the content in text and update the structures that keep line number
480  // of all entities.
481  QListIterator<QString> iterator( mustRemoveEntity );
482  while( iterator.hasNext() ){
483  key = iterator.next();
484  startEntityOffset.remove(key);
485  endEntityOffset.remove(key);
486  }
487 
488  /* foreach(key, startEntityOffset.keys())
489  {
490  qDebug() << " startOffset=" << startEntityOffset[key]
491  << " endOffset=" << endEntityOffset[key];
492  } */
493 }
494 
496 {
497  QByteArray data;
498  data.append(nclTextEditor->text());
499  data.append("$START_ENTITIES_LINES$");
500  QString key;
501  bool first = true;
502  foreach (key, startEntityOffset.keys())
503  {
504  if(!first)
505  data.append(",");
506  else
507  first = false;
508  data.append(key + ", " + QString::number(startEntityOffset[key]));
509  }
510  data.append("$END_ENTITIES_LINES$");
511  first = true;
512  foreach (key, endEntityOffset.keys())
513  {
514  if(!first)
515  data.append(",");
516  else
517  first = false;
518  data.append(key + "," + QString::number(endEntityOffset[key]));
519  }
520 
521  emit setPluginData(data);
522 
523  return true;
524 }
525 
526 void NCLTextualViewPlugin::changeSelectedEntity(QString pluginID, void *param)
527 {
528  if(isSyncing)
529  return; // do nothing;
530 
531  QString *id = (QString*)param;
532  if(startEntityOffset.contains(*id))
533  {
534  int entityOffset = startEntityOffset.value(*id);
535  // int entityLine = window->getTextEditor()->SendScintilla(
536  // QsciScintilla::SCI_LINEFROMPOSITION,
537  // entityOffset);
538 
539  if(entityOffset < nclTextEditor->text().size())
540  {
541  nclTextEditor->SendScintilla(QsciScintilla::SCI_GOTOPOS,
542  entityOffset);
543  nclTextEditor->SendScintilla(QsciScintilla::SCI_SETFOCUS,
544  true);
545  }
546  }
547  else
548  {
549  qDebug() << "NCLTextualViewPlugin::changeSelectedEntity() "
550  << "Entity doesn't exists!";
551  }
552 }
553 
554 void NCLTextualViewPlugin::updateCoreModel()
555 {
556  syncMutex.lock();
557  bool rebuildComposerModelFromScratch = true;
558 
559  isSyncing = true; //set our current state as syncing
560 
561  QString text = nclTextEditor->text();
562  QString errorMessage;
563  int errorLine, errorColumn;
564  //Create a DOM document with the new content
565  nclTextEditor->clearErrorIndicators();
566  if(!xmlDoc.setContent(text, &errorMessage, &errorLine, &errorColumn))
567  {
568  //if the current XML is not well formed.
569  QMessageBox::warning(nclTextEditor, tr("Error"),
570  tr("Your document is not a Well-formed XML"));
571  nclTextEditor->keepFocused();
572  nclTextEditor->markError(errorMessage, "", errorLine-1, errorColumn);
573 
574  isSyncing = false;
575  syncMutex.unlock();
576  return;
577  }
578 
579 // int line, column;
580 // nclTextEditor->getCursorPosition(&line, &column);
581  sendBroadcastMessage("textualStartSync", NULL);
582  //double-buffering
583  tmpNclTextEditor = nclTextEditor;
584  nclTextEditor = new NCLTextEditor(0);
585  nclTextEditor->setDocumentUrl(project->getLocation());
586  nclTextEditor->setText(tmpNclTextEditor->textWithoutUserInteraction());
587  updateFromModel(); // this is just a precaution
588 
589  if(rebuildComposerModelFromScratch)
590  nonIncrementalUpdateCoreModel();
591  else
592 // incrementalUpdateCoreModelById();
593  incrementalUpdateCoreModel();
594  emit syncFinished();
595  sendBroadcastMessage("textualFinishSync", NULL);
596 
597 // nclTextEditor->setCursorPosition(line, column); //go back to the previous position
598 
599  syncMutex.unlock();
600 }
601 
602 void NCLTextualViewPlugin::nonIncrementalUpdateCoreModel()
603 {
604  //delete the content of the current project
605  if(project->getChildren().size())
606  emit removeEntity(project->getChildren().at(0), true);
607 
608  // clear the entities offset
609  nclTextEditor->clear();
610  nclTextEditor->setText("<?xml version=1.0 encoding=ISO-8859-1?>");
611  startEntityOffset.clear();
612  endEntityOffset.clear();
613 
614  QList <QString> parentUids;
615  QString parentUId = project->getUniqueId();
616  parentUids.push_back(parentUId);
617 
618  QList <QDomElement> nodes;
619  QDomElement current = xmlDoc.firstChildElement();
620  nodes.push_back(current);
621 
622  while(!nodes.empty())
623  {
624  current = nodes.front();
625  nodes.pop_front();
626  parentUId = parentUids.front();
627  parentUids.pop_front();
628 
629  if(current.tagName() == "ncl" && !current.hasAttribute("id"))
630  {
631  current.setAttribute("id", "myNCLDocID");
632  }
633 
634  //Process the node
635  QMap<QString,QString> atts;
636 
637  QDomNamedNodeMap attributes = current.attributes();
638  for (int i = 0; i < attributes.length(); i++)
639  {
640  QDomAttr item = attributes.item(i).toAttr();
641  atts[item.name()] = item.value();
642  }
643 
644  //Send the addEntity to the core plugin
645  emit addEntity(current.tagName(), parentUId, atts, false);
646  parentUId = currentEntity->getUniqueId();
647 
648  QDomElement child = current.firstChildElement();
649  while(!child.isNull())
650  {
651  nodes.push_back(child);
652  parentUids.push_back(parentUId);
653  child = child.nextSiblingElement();
654  }
655  }
656 }
657 
658 void NCLTextualViewPlugin::incrementalUpdateCoreModel()
659 {
660  QProgressDialog dialog(tr("Synchronizing with other plugins..."),
661  tr("Cancel"), 0, 100,
662  nclTextEditor);
663  dialog.setAutoClose(true);
664  dialog.show();
665 
666  //incremental update
667  QList <QDomNode> nodes;
668  QDomNode current = xmlDoc;
669  nodes.push_back(current);
670  Entity *curEntity = project;
671  QList <Entity *> entities;
672  entities.push_back(curEntity);
673 
674  //count how many nodes to update
675  int total_nodes = 0, progress = 0;
676  while(!nodes.empty())
677  {
678  current = nodes.front();
679  nodes.pop_front();
680 
681  total_nodes++;
682 
683  QDomElement child = current.firstChildElement();
684  while(!child.isNull())
685  {
686  nodes.push_back(child);
687  child = child.nextSiblingElement();
688  }
689  }
690 
691  dialog.setRange(0, total_nodes);
692  current = xmlDoc;
693  nodes.push_back(current);
694  while(!nodes.empty())
695  {
696  dialog.setValue(progress++);
697  //dialog.update();
698  //QApplication::processEvents();
699 
700  current = nodes.front();
701  nodes.pop_front();
702  curEntity = entities.front();
703  entities.pop_front();
704 
705  QVector <QDomElement> children;
706  QDomElement child = current.firstChildElement();
707  while(!child.isNull())
708  {
709  children.push_back(child);
710  child = child.nextSiblingElement();
711  }
712 
713  QVector <Entity *> entityChildren = curEntity->getChildren();
714 
715  int i, j;
716  for(i = 0, j = 0;
717  i < children.size() && j < entityChildren.size();
718  i++, j++)
719  {
720  bool sameNCLID = false;
721 
722  if(children[i].hasAttribute("id") && entityChildren[j]->hasAttribute("id"))
723  {
724  if(children[i].attribute("id") == entityChildren[j]->getAttribute("id"))
725  sameNCLID = true;
726  }
727  else if( children[i].hasAttribute("name")
728  && entityChildren[j]->hasAttribute("name"))
729  {
730  if(children[i].attribute("name")
731  == entityChildren[j]->getAttribute("name"))
732  sameNCLID = true;
733  }
734  // testing for alias - remove after
735  else if(children[i].hasAttribute("alias")
736  && entityChildren[j]->hasAttribute("alias"))
737  {
738  if(children[i].attribute("alias")
739  == entityChildren[j]->getAttribute("alias"))
740  sameNCLID = true;
741  }
742  else
743  sameNCLID = true;
744 
745  if( children[i].tagName() == entityChildren[j]->getType()
746  && sameNCLID)
747  {
748  //if the same type, just update the attributes
749  //TODO: Compare attributes
750  QMap<QString, QString> atts;
751  QDomNamedNodeMap attributes = children[i].attributes();
752  for (int k = 0; k < attributes.length(); k++)
753  {
754  QDomNode item = attributes.item(k);
755  qDebug() << item.nodeName() << item.nodeValue();
756  atts.insert(item.nodeName(), item.nodeValue());
757  }
758 
759  QMap <QString, QString>::iterator begin, end, it;
760  entityChildren[j]->getAttributeIterator(begin, end);
761 
762  bool changed = false;
763  int entityChildrenAttrSize = 0;
764  for (it = begin; it != end; ++it)
765  {
766  if(atts.contains(it.key()) && atts[it.key()]== it.value())
767  continue;
768  else
769  changed = true;
770  entityChildrenAttrSize++;
771  }
772 
773  if(entityChildrenAttrSize != atts.size())
774  changed = true;
775 
776  if(changed)
777  emit setAttributes(entityChildren[j], atts, false);
778  }
779  else
780  {
781  qDebug() << entityChildren[j]->getType() << children[i].tagName();
782  //if type are not equal, then we should change the type
783  //i.e. remove the entity
784  emit removeEntity(entityChildren[j], true);
785  //and insert a new entity with the required type.
786  QMap<QString,QString> atts;
787  QDomNamedNodeMap attributes = children[i].attributes();
788  for (int k = 0; k < attributes.length(); k++)
789  {
790  QDomNode item = attributes.item(k);
791  atts[item.nodeName()] = item.nodeValue();
792  }
793  emit addEntity(children[i].tagName(), curEntity->getUniqueId(), atts,
794  false);
795  }
796  }
797 
798  if(i == children.size())
799  {
800  // if there are more entities in the composer model than in the XML
801  for(; j < entityChildren.size(); j++)
802  emit removeEntity(entityChildren[j], true);
803  }
804  else if(j == entityChildren.size())
805  {
806  // if there are more entities in the XML than in the composer model
807  for(; i < children.size(); i++)
808  {
809  //add new entity
810  QMap<QString,QString> atts;
811  QDomNamedNodeMap attributes = children[i].attributes();
812  for (int k = 0; k < attributes.length(); k++)
813  {
814  QDomNode item = attributes.item(k);
815  atts[item.nodeName()] = item.nodeValue();
816  }
817  emit addEntity(children[i].tagName(), curEntity->getUniqueId(), atts,
818  false);
819  }
820  }
821 
822  child = current.firstChildElement();
823  while(!child.isNull())
824  {
825  nodes.push_back(child);
826  child = child.nextSiblingElement();
827  }
828  entityChildren = curEntity->getChildren();
829  for(int i = 0; i < entityChildren.size(); i++)
830  entities.push_back(entityChildren[i]);
831  }
832 
833  dialog.setValue(100);
834 }
835 
836 void NCLTextualViewPlugin::syncFinished()
837 {
838  // tmpNclTextEditor->setText(nclTextEditor->text());
839  delete nclTextEditor;
840  nclTextEditor = tmpNclTextEditor;
841  tmpNclTextEditor = NULL;
842  updateFromModel();
843  nclTextEditor->setTextWithoutUserInteraction(nclTextEditor->text());
844  isSyncing = false;
845 
846  updateErrorMessages();
847 }
848 
849 bool NCLTextualViewPlugin::isStartEndTag(Entity *entity)
850 {
851  int endOffset = endEntityOffset[entity->getUniqueId()];
852 
853  //Check if I am at a START_END_TAG />
854  char curChar = nclTextEditor->SendScintilla(QsciScintilla::SCI_GETCHARAT,
855  endOffset-1);
856 
857  if(curChar == '/')
858  {
859  qDebug() << "isStartEndTag returns true";
860  return true;
861  }
862  else return false;
863 }
864 
865 void NCLTextualViewPlugin::openStartEndTag(Entity *entity)
866 {
867  if(isStartEndTag(entity))
868  {
869  int endOffset = endEntityOffset[entity->getUniqueId()];
870 
871 // printEntitiesOffset(); qDebug() << endl;
872  // If the parent is a START_END, then we should separate the START from
873  // the END.
874  nclTextEditor->SendScintilla(QsciScintilla::SCI_SETSELECTIONSTART,
875  endOffset-1);
876 
877  nclTextEditor->SendScintilla(QsciScintilla::SCI_SETSELECTIONEND,
878  endOffset+1);
879 
880  nclTextEditor->removeSelectedText();
881 
882  QString endTag = ">\n</" + entity->getType() + ">";
883  // Openning the START_END_TAG
884  nclTextEditor->insertAtPos(endTag, endOffset-1);
885  // before we have "/>\n" and now we have to remove update with the difference
886  // i.e. endtag.size() - 3
887 // printEntitiesOffset();
888 // qDebug() << endl;
889  updateEntitiesOffset(endOffset, endTag.size() - 2);
890 // printEntitiesOffset(); qDebug() << endl;
891 
892  fixIdentation(endOffset+2, false);
893  endEntityOffset[entity->getUniqueId()] = endOffset + 1;
894  }
895 }
896 
897 void NCLTextualViewPlugin::fixIdentation(int offset, bool mustAddTab)
898 {
899  /* Fix Indentation */
900  int insertAtLine = nclTextEditor->SendScintilla(
901  QsciScintilla::SCI_LINEFROMPOSITION, offset);
902 
903  int totalLines = nclTextEditor->
904  SendScintilla( QsciScintilla::SCI_GETLINECOUNT);
905 
906  qDebug () << totalLines << insertAtLine;
907 
908  if(insertAtLine + 1 >= totalLines) return; // do nothing
909  // get the identation for the next line
910  int lineIndent = nclTextEditor
911  ->SendScintilla( QsciScintilla::SCI_GETLINEINDENTATION,
912  insertAtLine-1);
913 
914  if(insertAtLine > 1 && mustAddTab)
915  lineIndent += nclTextEditor->tabWidth();
916 
917  nclTextEditor->SendScintilla( QsciScintilla::SCI_SETLINEINDENTATION,
918  insertAtLine,
919  lineIndent);
920 
921  updateEntitiesOffset(offset-1, lineIndent/nclTextEditor->tabWidth());
922 }
923 
924 void NCLTextualViewPlugin::updateEntitiesOffset( int startFrom,
925  int insertedChars)
926 {
927  /* qDebug() << "NCLTextualViewPlugin::updateEntitiesOffset(" << startFrom
928  << ", " << insertedChars << ")"; */
929 
930  if(!insertedChars) //nothing to do
931  return;
932 
933  QString key;
934  foreach(key, startEntityOffset.keys())
935  {
936  if(startEntityOffset[key] > startFrom)
937  startEntityOffset[key] += insertedChars;
938 
939  if(endEntityOffset[key] >= startFrom)
940  endEntityOffset[key] += insertedChars;
941  }
942 }
943 
944 void NCLTextualViewPlugin::printEntitiesOffset()
945 {
946  QString key;
947  foreach(key, startEntityOffset.keys())
948  {
949  int startOffSet = startEntityOffset[key];
950  char startChar = nclTextEditor->SendScintilla(QsciScintilla::SCI_GETCHARAT,
951  startOffSet);
952  int endOffSet = endEntityOffset[key];
953  char endChar = nclTextEditor->SendScintilla(QsciScintilla::SCI_GETCHARAT,
954  endOffSet);
955 
956  qDebug() << "key="<< key << "(" << project->getEntityById(key)->getType()
957  << "; start=" << startOffSet
958  << "; start_char=" << startChar << "; end=" << endOffSet
959  << "; end_char=" << endChar << endl;
960  }
961 }
962 
963 void NCLTextualViewPlugin::manageFocusLost(QFocusEvent *event)
964 {
965 #ifndef NCLEDITOR_STANDALONE
966 
967  // When AutoComplete list gets the focus, the QApplication::focusWidget
968  // has a NULL value. This is an QScintilla issues.
969  // When the focus goes to AutoComplete list we don't want to synchronize with
970  // the core, that is why the test "QApplication::focusWidget() != NULL" is
971  // here.
972  // qDebug() << nclTextEditor << QApplication::focusWidget();
973  if(nclTextEditor->textWithoutUserInteraction() != nclTextEditor->text()
974  && !isSyncing
975  && (QApplication::focusWidget() != NULL))
976  {
977  int ret = QMessageBox::question(window,
978  tr("Textual View synchronization"),
979  tr("You have changed the textual content of the NCL \
980  Document. Do you want to synchronize this text with \
981  other views?"),
982  QMessageBox::Yes |
983  QMessageBox::No |
984  QMessageBox::Cancel,
985 
986  QMessageBox::Cancel);
987 
988  switch(ret)
989  {
990  case QMessageBox::Yes:
991  updateCoreModel();
992  break;
993  case QMessageBox::No:
994  nclTextEditor->setText(nclTextEditor->textWithoutUserInteraction());
995  break;
996  case QMessageBox::Cancel:
997  nclTextEditor->keepFocused();
998  break;
999  }
1000  }
1001  else if(QApplication::focusWidget() == NULL)
1002  {
1003  // If the focus goes to AutoComplete list we force Qt keeps the focus in the
1004  // NCLTextEditor!!!
1005  nclTextEditor->keepFocused();
1006  }
1007 #endif
1008 }
1009 
1011 {
1012  if(isSyncing)
1013  return;
1014 
1015  clearValidationMessages(this->pluginInstanceID, NULL);
1016 
1017  emit sendBroadcastMessage("askAllValidationMessages", NULL);
1018 }
1019 
1021 {
1022  nclTextEditor->clearErrorIndicators();
1023 }
1024 
1025 void NCLTextualViewPlugin::validationError(QString pluginID, void * param)
1026 {
1027  if(isSyncing)
1028  return;
1029 
1030  if (param) {
1031  pair <QString , QString> *p = (pair <QString, QString> *) param;
1032 
1033  int offset = startEntityOffset[p->first];
1034 
1035  int line = nclTextEditor->SendScintilla(
1036  QsciScintilla::SCI_LINEFROMPOSITION,
1037  offset);
1038 
1039  nclTextEditor->markError(p->second, "", line);
1040  }
1041 }