umbrello API Documentation

codeeditor.cpp

00001 
00002 /***************************************************************************
00003                           codeviewerdialog.cpp  -  description
00004                              -------------------
00005     begin                : Fri Aug 1 2003
00006     copyright            : (C) 2003 by Brian Thomas
00007     email                : brian.thomas@gsfc.nasa.gov
00008  ***************************************************************************/
00009 
00010 /***************************************************************************
00011  *                                                                         *
00012  *   This program is free software; you can redistribute it and/or modify  *
00013  *   it under the terms of the GNU General Public License as published by  *
00014  *   the Free Software Foundation; either version 2 of the License, or     *
00015  *   (at your option) any later version.                                   *
00016  *                                                                         *
00017  *   copyright (C) 2004-2007                                               *
00018  *   Umbrello UML Modeller Authors <uml-devel@uml.sf.net>                  *
00019  ***************************************************************************/
00020 
00021 // own header
00022 #include "codeeditor.h"
00023 
00024 // qt/kde includes
00025 #include <qkeysequence.h>
00026 #include <qcursor.h>
00027 #include <qcolor.h>
00028 #include <qlabel.h>
00029 #include <qbrush.h>
00030 #include <qlayout.h>
00031 #include <qregexp.h>
00032 #include <kdebug.h>
00033 #include <klocale.h>
00034 
00035 // local includes
00036 #include "../attribute.h"
00037 #include "../classifier.h"
00038 #include "../umldoc.h"
00039 #include "../umlrole.h"
00040 
00041 #include "../codeaccessormethod.h"
00042 #include "../codegenerator.h"
00043 #include "../codeclassfield.h"
00044 #include "../codeclassfielddeclarationblock.h"
00045 #include "../codedocument.h"
00046 #include "../codeoperation.h"
00047 #include "../codemethodblock.h"
00048 #include "../classifiercodedocument.h"
00049 #include "../ownedhierarchicalcodeblock.h"
00050 #include "../codegenerators/codegenfactory.h"
00051 
00052 #include "codeviewerdialog.h"
00053 #include "classpropdlg.h"
00054 #include "umlattributedialog.h"
00055 #include "umlroledialog.h"
00056 #include "umloperationdialog.h"
00057 
00058 CodeEditor::CodeEditor ( const QString & text, const QString & context, CodeViewerDialog * parent, const char * name , CodeDocument * doc)
00059         : QTextEdit ( text, context, parent, name)
00060 {
00061     init(parent, doc);
00062 }
00063 
00064 CodeEditor::CodeEditor ( CodeViewerDialog * parent, const char* name, CodeDocument * doc )
00065         : QTextEdit ( parent, name )
00066 {
00067     init(parent, doc);
00068 }
00069 
00070 /*
00071  *  Destroys the object and frees any allocated resources
00072  */
00073 CodeEditor::~CodeEditor() { }
00074 
00075 // clear the display of all text
00076 void CodeEditor::clearText () {
00077 
00078     //        setCaption( tr2i18n("") );
00079     m_selectedTextBlock = 0;
00080     m_textBlockList.clear();
00081     m_tbInfoMap->clear();
00082 
00083     // now call super-class
00084     clear();
00085 
00086 }
00087 
00088 Settings::CodeViewerState CodeEditor::getState()
00089 {
00090     return m_parentDlg->getState();
00091 }
00092 
00093 QLabel * CodeEditor::getComponentLabel() {
00094     return m_parentDlg->componentLabel;
00095 }
00096 
00097 
00098 // FIX: used only for debugging right now.. eliminate eventually -b.t.
00099 void CodeEditor::clicked(int para, int pos)
00100 {
00101     getComponentLabel()->setText("para:"+QString::number(para)+" pos:"+QString::number(pos));
00102 }
00103 
00104 
00105 bool CodeEditor::close ( bool alsoDelete )
00106 {
00107 
00108     // capture last code block, if it exists
00109     if(m_lastTextBlockToBeEdited)
00110     {
00111         updateTextBlockFromText (m_lastTextBlockToBeEdited);
00112         m_lastTextBlockToBeEdited = 0;
00113     }
00114 
00115     return QTextEdit::close(alsoDelete);
00116 
00117 }
00118 
00119 void CodeEditor::doubleClicked(int para, int pos)
00120 {
00121 
00122     m_lastPara = para;
00123     m_lastPos = pos;
00124 
00125     // ugh. more ugliness. We want to be able to call up the
00126     // correct editing dialog for the given attribute.
00127     TextBlock * tBlock = m_textBlockList.at(para);
00128     editTextBlock(tBlock, para);
00129 
00130 }
00131 
00132 // allow us to edit, as appropriate, the parent UMLObject of the
00133 // given text block.
00134 void CodeEditor::editTextBlock(TextBlock * tBlock, int para) {
00135 
00136     if(tBlock)
00137     {
00138         TextBlockInfo *info = (*m_tbInfoMap)[tBlock];
00139         if(info) {
00140             UMLObject *obj = info->getParent();
00141             if(obj)
00142             {
00143 
00144                 UMLAttribute * at = 0;
00145                 UMLRole * role = 0;
00146                 UMLOperation * op = 0;
00147 
00148                 if( (at = dynamic_cast<UMLAttribute*>(obj)) )
00149                 {
00150                     UMLAttributeDialog dlg( this, at);
00151                     if( dlg.exec() ) { rebuildView(para); }
00152                 }
00153                 else if( (dynamic_cast<UMLClassifier*>(obj)) )
00154                 {
00155                     if (obj->showProperties())
00156                         rebuildView(para);
00157                 }
00158                 else if( (role = dynamic_cast<UMLRole*>(obj)))
00159                 {
00160                     UMLRoleDialog dlg(this,role);
00161                     if( dlg.exec() ) { rebuildView(para); }
00162                 }
00163                 else if( (op = dynamic_cast<UMLOperation*>(obj)) )
00164                     //else if( (cop = dynamic_cast<CodeOperation*>(tBlock)) )
00165                 {
00166                     UMLOperationDialog dlg(this,op);
00167                     if( dlg.exec() ) { rebuildView(para); }
00168                 }
00169                 else
00170                 {
00171                     kError()<<" CodeViewerDlg ERROR: UNKNOWN parent for textBlock"<<endl;
00172                 }
00173 
00174             }
00175         }
00176     }
00177 }
00178 
00179 // return whether is empty or just whitespace
00180 bool CodeEditor::StringIsBlank(const QString &str)
00181 {
00182     if(str.isEmpty() || str.stripWhiteSpace().isEmpty())
00183         return true;
00184     return false;
00185 }
00186 
00187 void CodeEditor::keyPressEvent ( QKeyEvent * e ) {
00188 
00189     // kDebug() <<"KEY PRESS EVENT:["<<e->text().latin1()<<"] ascii CODE:"<<e->ascii();
00190 
00191     if((e->ascii() == 8) ) // || (e->ascii() == 127)) // what about delete?
00192         m_backspacePressed = true;
00193 
00194     // Q: can the MAC or WIN/DOS sequences occur?
00195     if((e->ascii() == 10) || (e->ascii() == 13) || (e->text() == "\r\n"))
00196         m_newLinePressed = true;
00197 
00198     QTextEdit::keyPressEvent(e);
00199 }
00200 
00201 void CodeEditor::loadFromDocument ()
00202 {
00203 
00204     // clear the tool
00205     clearText();
00206 
00207     // set caption on tool
00208     QString caption = m_parentDoc->getFileName() + m_parentDoc->getFileExtension();
00209     setCaption( tr2i18n( caption.latin1() ) );
00210 
00211     // header for document
00212     QString header = m_parentDoc->getHeader()->toString();
00213     QString componentName = QString("header for file ") +caption;
00214     if(!StringIsBlank(header))
00215         insert(header,m_parentDoc->getHeader(),false,getState().fontColor,
00216                getState().nonEditBlockColor,0,componentName);
00217 
00218     // now all the text blocks in the document
00219     TextBlockList * items = m_parentDoc->getTextBlockList();
00220     appendText(items);
00221 
00222     setCursorPosition(0,0);
00223 
00224 }
00225 
00226 void CodeEditor::insert (const QString & text, TextBlock * parent, bool editable, const QColor & fgcolor, const QColor & bgcolor, UMLObject * umlobj, const QString & displayName, int startLine)
00227 {
00228 
00229     // set some params
00230     bool isInsert = false;
00231     setColor(fgcolor);
00232 
00233     // its an append op if startLine is -1, otherwise its
00234     // an actual insert, which is more complicated
00235     if(startLine == -1)
00236     {
00237         startLine = paragraphs()-1;
00238         QTextEdit::append(text); // put actual text in. Use insert instead of append so history is preserved?
00239     }
00240     else
00241     {
00242         isInsert = true;
00243         QTextEdit::insertAt(text, startLine, 0);
00244     }
00245 
00246     // actual put in text
00247 
00248     // now do 'paragraph' background highlighting
00249     //        int endLine = paragraphs()-2;
00250     int endLine = text.contains(QRegExp("\n")) + startLine -1;
00251     if(m_isHighlighted)
00252         for(int para=startLine;para<=endLine;para++)
00253             setParagraphBackgroundColor(para,bgcolor);
00254 
00255     // record paragraph information
00256     // Did we already start recording info for this parent object?
00257     TextBlockInfo * tbinfo;
00258     if(m_tbInfoMap->contains(parent))
00259         tbinfo = (*m_tbInfoMap)[parent];
00260     else {
00261         tbinfo = new TextBlockInfo();
00262         tbinfo->displayName = displayName;
00263         tbinfo->isCodeAccessorMethod = dynamic_cast<CodeAccessorMethod*>(parent) ? true : false;
00264         m_tbInfoMap->insert(parent,tbinfo);
00265     }
00266 
00267     // set a parent, if its not already set
00268     if(umlobj && !tbinfo->getParent())
00269     {
00270         tbinfo->displayName = displayName;
00271         tbinfo->setParent(umlobj);
00272         tbinfo->isClickable = textBlockIsClickable(umlobj);
00273     }
00274 
00275     // now mark all lines that we just inserted as belonging to the parent
00276     for(int para=startLine;para<=endLine;para++)
00277         m_textBlockList.insert(para,parent);
00278 
00279     // lastly, update the para info
00280     // start position is relative to the FIRST parent position
00281     int start = startLine - m_textBlockList.findRef(parent);
00282     int size = endLine-startLine;
00283 
00284     // create the object that records this particular "paragraph"
00285     ParaInfo * item = new ParaInfo();
00286     item->start = start;
00287     item->size= size;
00288     item->fgcolor = fgcolor;
00289     item->bgcolor = bgcolor;
00290     item->isEditable = editable;
00291 
00292     if(isInsert)
00293     {
00294         // now we have to fix the 'start' value for all the para
00295         // info blocks that coorspond to textblocks that we inserted
00296         // inside of. This means parent tblock paragraph locations
00297         // that are greater than zero in that type of textblock
00298 
00299         int increase = size + 1;
00300         QMap<TextBlock*,TextBlockInfo*>::Iterator it;
00301         for ( it = m_tbInfoMap->begin(); it != m_tbInfoMap->end(); ++it )
00302         {
00303             TextBlock * tblock = it.key();
00304             TextBlockInfo * thisTbInfo = it.data();
00305             int firstLoc = m_textBlockList.findRef(tblock);
00306             ParaInfo * lastPi = thisTbInfo->m_paraList.last();
00307 
00308             for(ParaInfo * pi = thisTbInfo->m_paraList.first(); pi; pi = thisTbInfo->m_paraList.next())
00309             {
00310                 int minPara = pi->start+firstLoc;
00311 
00312                 // only worth doing if in range of the whole
00313                 // representation
00314                 if(!pi->start && (startLine > (lastPi->start+firstLoc+lastPi->size) || endLine < minPara) )
00315                     break;
00316 
00317                 // now, only for those paraInfo blocks which
00318                 // have exceeded our last line, we increase them
00319                 if(pi->start && minPara >= endLine )
00320                     pi->start += increase;
00321 
00322             }
00323         }
00324 
00325     }
00326 
00327     tbinfo->m_paraList.append(item);
00328 
00329 }
00330 
00331 void CodeEditor::appendText(TextBlockList * items)
00332 {
00333 
00334     for (TextBlock *tb = items->first(); tb; tb = items->next())
00335     {
00336         // types of things we may cast our text block into
00337         // This isnt efficient, and is a vote for recording
00338         // code block types in an enumerated list somewhere,
00339         // as well as a generic attribute "blockType" we could
00340         // quickly access, rather than casting. -b.t.
00341         HierarchicalCodeBlock * hb = dynamic_cast<HierarchicalCodeBlock *>(tb);
00342         CodeMethodBlock * mb = 0;
00343         CodeClassFieldDeclarationBlock * db = 0;
00344         CodeBlockWithComments * cb = 0;
00345         // CodeComment * cm = 0;
00346         if(hb)
00347             appendText(hb);
00348         else if ( (mb = dynamic_cast<CodeMethodBlock*>(tb)) )
00349             appendText(mb);
00350         else if ( (db = dynamic_cast<CodeClassFieldDeclarationBlock*>(tb)) )
00351             appendText(db);
00352         else if ( (cb = dynamic_cast<CodeBlockWithComments*>(tb)) )
00353             appendText(cb);
00354         /*
00355                         // no! shouldn't be any 'naked' comments floating about. Always
00356                         // are assocated with a parent code block
00357                         else if ( (cm = dynamic_cast<CodeComment*>(tb)) )
00358                                 appendText(cm);
00359         */
00360         else
00361             appendText(tb); // no cast worked. Just do a text block
00362     }
00363 
00364 }
00365 
00366 void CodeEditor::appendText (CodeComment * comment, TextBlock * parent, UMLObject * umlObj , const QString & componentName)
00367 {
00368 
00369     if(!comment->getWriteOutText() && !m_showHiddenBlocks)
00370         return;
00371 
00372     QColor bgcolor = getState().nonEditBlockColor;
00373     if(!comment->getWriteOutText() && m_showHiddenBlocks)
00374         bgcolor = getState().hiddenColor;
00375 
00376     QString indent = comment->getIndentationString();
00377     QString text = comment->toString(); // use comment formatting, NOT formatMultiLineText(comment->toString(), indent, "\n");
00378     if(!StringIsBlank(text))
00379         insert(text,parent,true,getState().fontColor, bgcolor, umlObj, componentName);
00380 
00381 }
00382 
00383 void CodeEditor::appendText (CodeBlockWithComments * cb ) {
00384 
00385     if(!cb->getWriteOutText() && !m_showHiddenBlocks)
00386         return;
00387 
00388     QString indent = cb->getIndentationString();
00389     QString body = cb->formatMultiLineText (cb->getText(), indent, "\n");
00390 
00391     QColor bgcolor = getState().editBlockColor;
00392     QString componentName = QString("CodeBlock");
00393 
00394     appendText(cb->getComment(), cb, 0, componentName);
00395 
00396     if(!cb->getWriteOutText() && m_showHiddenBlocks)
00397         bgcolor = getState().hiddenColor;
00398 
00399     if(!StringIsBlank(body))
00400         insert(body,cb,true,getState().fontColor,bgcolor,0);
00401 
00402 }
00403 
00404 void CodeEditor::appendText (CodeClassFieldDeclarationBlock * db ) {
00405 
00406     if(!db->getWriteOutText() && !m_showHiddenBlocks)
00407         return;
00408 
00409     QString indent = db->getIndentationString();
00410     QString body = db->formatMultiLineText (db->getText(), indent, "\n");
00411 
00412     UMLObject * parentObj = db->getParentClassField()->getParentObject();
00413 
00414     QColor bgcolor = getState().editBlockColor;
00415     QString componentName = QString("");
00416     if(parentObj)
00417     {
00418         if(db->getParentClassField()->parentIsAttribute()) {
00419             componentName = parentDocName + "::attribute_field(" + parentObj->getName() + ')';
00420         } else {
00421             UMLRole * role = dynamic_cast<UMLRole*>(parentObj);
00422             componentName = parentDocName + "::association_field(" + role->getName() + ')';
00423         }
00424         bgcolor = getState().umlObjectColor;
00425     }
00426 
00427     appendText(db->getComment(), db, parentObj,componentName);
00428 
00429     if(!db->getWriteOutText() && m_showHiddenBlocks)
00430         bgcolor = getState().hiddenColor;
00431 
00432     if(!StringIsBlank(body))
00433         insert(body,db,false,getState().fontColor,bgcolor,parentObj);
00434 
00435 
00436 }
00437 
00438 void CodeEditor::appendText (CodeMethodBlock * mb) {
00439 
00440     // Note: IF CodeAccessors are hidden, we DON'T show
00441     // it even when requested as the hiddeness of these methods
00442     // should be controled by the class fields, not the user in the editor.
00443     if(!mb->getWriteOutText() && (!m_showHiddenBlocks || dynamic_cast<CodeAccessorMethod*>(mb)))
00444         return;
00445 
00446     QColor bgcolor = getState().umlObjectColor;
00447     QString indent = mb->getIndentationString();
00448     QString bodyIndent = mb->getIndentationString(mb->getIndentationLevel()+1);
00449 
00450     QString startText = mb->formatMultiLineText ( mb->getStartMethodText(), indent, "\n");
00451     QString body = mb->formatMultiLineText (mb->getText(), bodyIndent, "\n");
00452     QString endText = mb->formatMultiLineText( mb->getEndMethodText(), indent, "\n");
00453 
00454     if(body.isEmpty())
00455         body = " \n";
00456 
00457     if(!mb->getWriteOutText() && m_showHiddenBlocks)
00458     {
00459         // it gets the 'hidden' color
00460         bgcolor = getState().hiddenColor;
00461     }
00462 
00463     QString componentName = QString("<b>parentless method\?</b>");
00464 
00465     // ugly, but we need to know if there is a parent object here.
00466     CodeOperation * op = dynamic_cast<CodeOperation*>(mb);
00467     CodeAccessorMethod * accessor = dynamic_cast<CodeAccessorMethod*>(mb);
00468     UMLObject * parentObj = 0;
00469     if(op)
00470     {
00471         parentObj = op->getParentOperation();
00472         if(((UMLOperation*)parentObj)->isConstructorOperation())
00473             componentName = parentDocName + "::operation("+ parentObj->getName()+") constructor method";
00474         else
00475             componentName = parentDocName + "::operation("+ parentObj->getName()+") method";
00476     }
00477     if(accessor)
00478     {
00479         parentObj = accessor->getParentObject();
00480         if(accessor->getParentClassField()->parentIsAttribute()) {
00481             componentName = parentDocName + "::attribute_field(" + parentObj->getName() + ") accessor method";
00482         } else {
00483             UMLRole * role = dynamic_cast<UMLRole*>(parentObj);
00484             componentName = parentDocName + "::association_field(" + role->getName() + ") accessor method";
00485         }
00486 
00487     }
00488 
00489     //appendText(mb->getComment(), mb, parentObj, componentName);
00490     appendText(mb->getComment(), mb->getComment(), parentObj, componentName);
00491 
00492     if(!StringIsBlank(startText))
00493         insert(startText,mb,false,getState().fontColor,bgcolor,parentObj);
00494     // always insert body for methods..IF we don't, we create a
00495     // situation where the user cant edit the body (!)
00496     insert(body,mb,true,getState().fontColor,bgcolor,parentObj);
00497     if(!StringIsBlank(endText))
00498         insert(endText,mb,false,getState().fontColor,bgcolor,parentObj);
00499 
00500 }
00501 
00502 void CodeEditor::appendText (TextBlock * tb) {
00503 
00504     if(!tb->getWriteOutText() && !m_showHiddenBlocks)
00505         return;
00506 
00507     QColor bgcolor = getState().nonEditBlockColor;
00508     if(!tb->getWriteOutText() && m_showHiddenBlocks)
00509         bgcolor = getState().hiddenColor;
00510 
00511     QString str = tb->toString();
00512     insert(str,tb,false,getState().fontColor,bgcolor);
00513 
00514 }
00515 
00516 void CodeEditor::appendText(HierarchicalCodeBlock * hblock)
00517 {
00518 
00519     if(!hblock->getWriteOutText() && !m_showHiddenBlocks)
00520         return;
00521 
00522     OwnedHierarchicalCodeBlock * test = dynamic_cast<OwnedHierarchicalCodeBlock *>(hblock);
00523     UMLObject * parentObj = 0;
00524     QString componentName = QString("");
00525     QColor paperColor = getState().nonEditBlockColor;
00526     if(test)
00527     {
00528         parentObj = test->getParentObject();
00529         UMLClassifier *c = dynamic_cast<UMLClassifier*>(parentObj);
00530         if (c) {
00531             QString typeStr;
00532             if (c->isInterface())
00533                 typeStr = "Interface";
00534             else
00535                 typeStr = "Class";
00536             componentName = parentDocName + "::" + typeStr + '(' + parentObj->getName() + ')';
00537         } else {
00538             componentName = parentDocName + "::UNKNOWN(" + parentObj->getName() + ')';
00539         }
00540 
00541         paperColor = getState().umlObjectColor;
00542     }
00543 
00544     if(!hblock->getWriteOutText() && m_showHiddenBlocks)
00545         paperColor = getState().hiddenColor;
00546 
00547     TextBlockList * items = hblock->getTextBlockList();
00548     QString indent = hblock->getIndentationString();
00549     QString startText = hblock->formatMultiLineText ( hblock->getStartText(), indent, "\n");
00550     QString endText = hblock->formatMultiLineText( hblock->getEndText(), indent, "\n");
00551 
00552     appendText(hblock->getComment(), hblock, parentObj, componentName);
00553 
00554     if(!StringIsBlank(startText))
00555         insert(startText,hblock,false,getState().fontColor,paperColor, parentObj);
00556     appendText(items);
00557     if(!StringIsBlank(endText))
00558         insert(endText,hblock,false,getState().fontColor,paperColor);
00559 
00560 }
00561 
00562 void CodeEditor::insertParagraph ( const QString & text, int para )
00563 {
00564     QTextEdit::insertParagraph(text,para);
00565 }
00566 
00567 void CodeEditor::removeParagraph ( int para )
00568 {
00569     QTextEdit::removeParagraph(para);
00570 }
00571 
00572 // All umlobjects which may have pop-up boxes should return true here
00573 // Yes, a CRAPPY way of doing this. Im not proud. =b.t.
00574 bool CodeEditor::textBlockIsClickable(UMLObject * obj)
00575 {
00576 
00577     if( dynamic_cast<UMLAttribute*>(obj) )
00578         return true;
00579     else if( dynamic_cast<UMLClassifier*>(obj) )
00580         return true;
00581     else if( dynamic_cast<UMLRole*>(obj) )
00582         return true;
00583     else if( dynamic_cast<UMLOperation*>(obj) )
00584         return true;
00585 
00586     return false;
00587 }
00588 
00589 void CodeEditor::slotChangeSelectedBlockView()
00590 {
00591     TextBlock * tb = m_selectedTextBlock;
00592     if(tb) {
00593         tb->setWriteOutText(tb->getWriteOutText() ? false : true );
00594         rebuildView(m_lastPara);
00595     }
00596 
00597 }
00598 
00599 // change the status of the comment writeOutText value to
00600 // opposite of current value
00601 void CodeEditor::slotChangeSelectedBlockCommentView()
00602 {
00603 
00604     TextBlock * tb = m_selectedTextBlock;
00605     CodeBlockWithComments * cb = 0;
00606     if(tb && (cb = dynamic_cast<CodeBlockWithComments*>(tb)))
00607     {
00608         cb->getComment()->setWriteOutText(cb->getComment()->getWriteOutText() ? false : true );
00609         rebuildView( m_lastPara );
00610     }
00611 
00612 }
00613 
00614 void CodeEditor::slotInsertCodeBlockBeforeSelected()
00615 {
00616 
00617     TextBlock * tb = m_selectedTextBlock;
00618     CodeBlockWithComments * newBlock = m_parentDoc->newCodeBlockWithComments();
00619     newBlock->setText("<<INSERT>>");
00620     newBlock->getComment()->setWriteOutText(false);
00621 
00622     m_parentDoc->insertTextBlock(newBlock, tb, false);
00623 
00624     int location = m_textBlockList.findRef(m_selectedTextBlock); // find first para of selected block
00625 
00626     QString body = newBlock->formatMultiLineText (newBlock->getText(), newBlock->getIndentationString(), "\n");
00627 
00628     insert(body,newBlock,true,getState().fontColor,
00629            getState().editBlockColor,0,QString("CodeBlock"),location);
00630 
00631 }
00632 
00633 void CodeEditor::slotInsertCodeBlockAfterSelected()
00634 {
00635 
00636     TextBlock * tb = m_selectedTextBlock;
00637     CodeBlockWithComments * newBlock = m_parentDoc->newCodeBlockWithComments();
00638     newBlock->setText("<<INSERT>>");
00639     newBlock->getComment()->setWriteOutText(false);
00640 
00641     m_parentDoc->insertTextBlock(newBlock, tb, true);
00642 
00643     // find last para of selected block
00644     TextBlockInfo *tbinfo = (*m_tbInfoMap)[m_selectedTextBlock];
00645     ParaInfo * lastpi = tbinfo->m_paraList.last();
00646     int location = m_textBlockList.findRef(m_selectedTextBlock) + lastpi->start + lastpi->size + 1;
00647 
00648     QString body = newBlock->formatMultiLineText (newBlock->getText(), newBlock->getIndentationString(), "\n");
00649 
00650     insert(body,newBlock,true,getState().fontColor,
00651            getState().editBlockColor,0,QString("CodeBlock"),location);
00652 
00653 }
00654 
00655 QPopupMenu * CodeEditor::createPopupMenu ( const QPoint & pos )
00656 {
00657 
00658     TextBlock * tb = m_selectedTextBlock;
00659     m_lastPara = paragraphAt(pos);
00660 
00661     QPopupMenu * menu = new QPopupMenu(this);
00662     // ugh. A bug in the Qt interaction between QTextEdit and Menu
00663     // can sometimes trigger a clear() call of the text area after
00664     // the popup menu is destroyed. The workaround is to disable
00665     // the behavior by blocking the destroy signal from the menu.
00666     menu->blockSignals(true);
00667 
00668     if (m_selectedTextBlock)
00669     {
00670         if(tb->getWriteOutText())
00671             menu->insertItem("Hide",this,SLOT(slotChangeSelectedBlockView()), Key_H, 0);
00672         else
00673             menu->insertItem("Show",this,SLOT(slotChangeSelectedBlockView()), Key_S, 0);
00674 
00675         CodeBlockWithComments * cb = dynamic_cast<CodeBlockWithComments*>(tb);
00676         if(cb)
00677             if(cb->getComment()->getWriteOutText())
00678                 menu->insertItem("Hide Comment",this,SLOT(slotChangeSelectedBlockCommentView()), CTRL+Key_H, 1);
00679             else
00680                 menu->insertItem("Show Comment",this,SLOT(slotChangeSelectedBlockCommentView()), CTRL+Key_S, 1);
00681         menu->insertSeparator();
00682 
00683         menu->insertItem("Insert Code Block Before",this,SLOT(slotInsertCodeBlockBeforeSelected()), CTRL+Key_B, 2);
00684         menu->insertItem("Insert Code Block After",this,SLOT(slotInsertCodeBlockAfterSelected()), CTRL+Key_A, 3);
00685 
00686         menu->insertSeparator();
00687 
00688         menu->insertItem("Copy",this,SLOT(slotCopyTextBlock()), CTRL+Key_C, 4);
00689         menu->insertItem("Paste",this,SLOT(slotPasteTextBlock()), CTRL+Key_V, 5);
00690         menu->insertItem("Cut",this,SLOT(slotCutTextBlock()), CTRL+Key_X, 6);
00691 
00692         // enable/disable based on conditions
00693         if(m_selectedTextBlock == m_parentDoc->getHeader())
00694             menu->setItemEnabled (2, false);
00695 
00696         if(!m_textBlockToPaste)
00697             menu->setItemEnabled (5, false);
00698 
00699         if(!tb->canDelete())
00700             menu->setItemEnabled (6, false);
00701 
00702         // manythings cant be copied. RIght now, lets just limit ourselves to
00703         // owned things and hierarchicalcodeblocks
00704         if(dynamic_cast<OwnedCodeBlock*>(m_selectedTextBlock) ||
00705                 dynamic_cast<HierarchicalCodeBlock*>(m_selectedTextBlock))
00706             menu->setItemEnabled (4, false);
00707 
00708         // TBD
00709         // m_selectedTextBlock->insertCodeEditMenuItems(menu, this);
00710     }
00711 
00712     return menu;
00713 }
00714 
00715 void CodeEditor::slotCopyTextBlock ( )
00716 {
00717     // make a copy
00718     if(dynamic_cast<HierarchicalCodeBlock*>(m_selectedTextBlock))
00719         m_textBlockToPaste = m_parentDoc->newHierarchicalCodeBlock();
00720     else if(dynamic_cast<CodeBlockWithComments*>(m_selectedTextBlock))
00721         m_textBlockToPaste = m_parentDoc->newCodeBlockWithComments();
00722     else if(dynamic_cast<CodeBlock*>(m_selectedTextBlock))
00723         m_textBlockToPaste = m_parentDoc->newCodeBlock();
00724     else if(dynamic_cast<CodeComment*>(m_selectedTextBlock))
00725         m_textBlockToPaste = CodeGenFactory::newCodeComment(m_parentDoc);
00726     else
00727     {
00728         kError()<<" ERROR: CodeEditor can't copy selected block:"<<m_selectedTextBlock<<" of unknown type"<<endl;
00729         m_textBlockToPaste = 0;
00730         return; // error!
00731     }
00732 
00733     m_textBlockToPaste->setAttributesFromObject(m_selectedTextBlock);
00734 
00735 }
00736 
00737 void CodeEditor::slotCutTextBlock ( ) {
00738 
00739     // make a copy first
00740     slotCopyTextBlock();
00741 
00742     // This could cause problems, but we are OK as
00743     // long as we only try to delete 'canDelete' textblocks
00744     if(m_selectedTextBlock->canDelete())
00745     {
00746         // just in case there are pending edits
00747         // we don't want to lose them
00748         if (m_lastTextBlockToBeEdited && m_lastTextBlockToBeEdited == (CodeBlock*) m_selectedTextBlock)
00749         {
00750             updateTextBlockFromText (m_lastTextBlockToBeEdited);
00751             m_lastTextBlockToBeEdited = 0;
00752         }
00753 
00754         m_parentDoc->removeTextBlock(m_selectedTextBlock);
00755         rebuildView(m_lastPara);
00756         // removeTextBlock(m_selectedTextBlock);
00757         m_selectedTextBlock = 0;
00758     }
00759 
00760 }
00761 
00762 void CodeEditor::slotPasteTextBlock ( ) {
00763 
00764     if(m_textBlockToPaste)
00765     {
00766         m_parentDoc->insertTextBlock(m_textBlockToPaste, m_selectedTextBlock);
00767         m_textBlockToPaste = 0;
00768         rebuildView(m_lastPara);
00769     }
00770 
00771 }
00772 
00773 void CodeEditor::slotRedrawText() {
00774     rebuildView(m_lastPara);
00775 }
00776 
00777 void CodeEditor::init ( CodeViewerDialog * parentDlg, CodeDocument * parentDoc ) {
00778 
00779     // safety to insure that we are up to date
00780     parentDoc->synchronize();
00781 
00782     m_parentDlg = parentDlg;
00783     m_parentDoc = parentDoc;
00784 
00785     setUndoRedoEnabled( false );
00786     setCursor( QCursor( 0 ) );
00787     setMouseTracking( true );
00788     setReadOnly (true);
00789     m_isHighlighted = getState().blocksAreHighlighted;
00790     m_showHiddenBlocks = getState().showHiddenBlocks;
00791 
00792     m_newLinePressed = false;
00793     m_backspacePressed = false;
00794     m_textBlockToPaste = 0;
00795     m_selectedTextBlock = 0;
00796     m_lastTextBlockToBeEdited = 0;
00797     m_tbInfoMap = new QMap<TextBlock *, TextBlockInfo*>;
00798 
00799     setFont( getState().font );
00800 
00801     // set name of parent doc
00802     ClassifierCodeDocument * cdoc = dynamic_cast<ClassifierCodeDocument*>(m_parentDoc);
00803     if(cdoc)
00804         parentDocName = cdoc->getParentClassifier()->getName();
00805     else
00806         parentDocName = "";
00807 
00808     // set some viewability parameters
00809     //int margin = fontMetrics().height();
00810 
00811     QBrush pbrush = QBrush ( getState().paperColor);
00812     setPaper(pbrush);
00813 
00814     // setMargin(margin);
00815 
00816     //       connect(this,SIGNAL(newLinePressed()),this,SLOT(newLinePressed()));
00817     //       connect(this,SIGNAL(backspacePressed()),this,SLOT(backspacePressed()));
00818     connect(this,SIGNAL(doubleClicked(int,int)),this,SLOT(doubleClicked(int,int)));
00819     connect(this,SIGNAL(cursorPositionChanged(int,int)),this,SLOT(cursorPositionChanged(int,int)));
00820 
00821     // do this last
00822     loadFromDocument();
00823 
00824 }
00825 
00826 void CodeEditor::updateTextBlockFromText (TextBlock * block) {
00827 
00828     if (block) {
00829 
00830         CodeMethodBlock * cmb = dynamic_cast<CodeMethodBlock*>(block);
00831         //QString baseIndent = block->getNewEditorLine(block->getIndentationLevel()+(cmb ? 1 : 0));
00832         QString baseIndent = block->getIndentationString(block->getIndentationLevel()+(cmb ? 1 : 0));
00833 
00834         TextBlockInfo *info = (*m_tbInfoMap)[block];
00835         UMLObject * parentObj = info->getParent();
00836         int pstart = m_textBlockList.findRef(block);
00837         QString content = "";
00838 
00839         // Assemble content from editiable paras
00840         QPtrList<ParaInfo> list = info->m_paraList;
00841         for(ParaInfo * item = list.first(); item; item=list.next())
00842         {
00843             if(item->isEditable)
00844             {
00845                 int lastpara = item->start+pstart+item->size;
00846                 int endEdit = block->lastEditableLine();
00847                 int lastLineToAddNewLine = lastpara + endEdit;
00848                 for(int para=(item->start+pstart);para<=lastpara;para++)
00849                 {
00850                     QString line = block->unformatText(text(para), baseIndent);
00851                     content += line;
00852                     // \n are implicit in the editor (!) so we should put them
00853                     // back in, if there is any content from the line
00854                     if(!line.isEmpty() && para != lastLineToAddNewLine)
00855                         content += "\n";
00856                 }
00857             }
00858         }
00859 
00860         //cerr<<"UPDATE GOT CONTENT:["<<content.latin1()<<"] to block:"<<block<<endl;
00861         block->setText(content);
00862 
00863         // if a parent for the block, try to set its documentation
00864         // as long as its NOT an accessor codeblock.
00865         if(parentObj && !info->isCodeAccessorMethod)
00866             parentObj->setDoc(content);
00867 
00868         // make note that its now user generated
00869         if(cmb)
00870             cmb->setContentType(CodeBlock::UserGenerated);
00871 
00872     }
00873 }
00874 
00875 void CodeEditor::cursorPositionChanged(int para, int pos)
00876 {
00877 
00878     // safety.. this is endemic of a 'bad' pointer event and can crash us otherwise
00879     if(pos < 0)
00880         return;
00881 
00882     //      bool lastParaIsEditable = isReadOnly() ? false : true;
00883     bool lastParaIsEditable = isParaEditable(m_lastPara);
00884 
00885     // IF last para where cursor is coming from was editable
00886     // we have a variety of things to look out for.
00887     if(lastParaIsEditable)
00888     {
00889         // If we got here as the result of a newline, then expansion
00890         // of a para editablity occurs.
00891         if((para-1) == m_lastPara && m_newLinePressed )
00892             expandSelectedParagraph ( m_lastPara );
00893 
00894         // conversely, we contract the zone of editablity IF we
00895         // got to current position as result of backspace
00896         if((para+1) == m_lastPara && m_backspacePressed )
00897             contractSelectedParagraph( para );
00898 
00899     }
00900 
00901     // now check if the current paragraph is really editiable, and if so,
00902     // so some things
00903     bool editPara = isParaEditable(para);
00904     if(editPara) {
00905 
00906         TextBlock * tBlock = m_textBlockList.at(para);
00907         CodeMethodBlock * cmb = dynamic_cast<CodeMethodBlock*>(tBlock);
00908 
00909         // auto-indent new lines
00910         QString currentParaText = text(para);
00911         QString baseIndent = tBlock->getNewEditorLine(tBlock->getIndentationLevel()+(cmb ? 1 : 0));
00912         // cerr<<"AUTO INDENT:["<<baseIndent.latin1()<<"] isMethod?"<<(cmb?"true":"false")<<endl;
00913         int minPos = baseIndent.length();
00914 
00915         // add indent chars to the current line, if missing
00916         if(!m_backspacePressed && !currentParaText.contains(QRegExp('^'+baseIndent)))
00917         {
00918             insertAt(baseIndent,para,0);
00919             setCursorPosition(para,pos+minPos);
00920             return;
00921         }
00922 
00923         if(pos<minPos)
00924         {
00925 
00926             bool priorParaIsEditable = isParaEditable(para-1);
00927             if(m_backspacePressed && para && priorParaIsEditable)
00928             {
00929                 int endOfPriorLine = paragraphLength(para-1);
00930                 // IN this case, we remove old (para) line, and tack its
00931                 // contents on the line we are going to.
00932                 QString contents = text(para);
00933                 contents = contents.right(contents.length()-m_lastPos+1);
00934 
00935                 // this next thing happens when we arent deleting last line
00936                 // of editable text, so we want to append whats left of this line
00937                 // onto the one we are backspacing into
00938                 if(paraIsNotSingleLine(para))
00939                 {
00940                     removeParagraph(para);
00941                     insertAt(contents,(para-1),endOfPriorLine);
00942                     setCursorPosition((para-1),endOfPriorLine);
00943                 }
00944 
00945             } else {
00946                 // well, if the following is true, then they
00947                 // are trying to hack away at the last line, which
00948                 // we cant allow to entirely disappear. Lets preserve
00949                 // the indentation
00950                 if(m_backspacePressed && !priorParaIsEditable)
00951                 {
00952                     QString contents = text(para);
00953                     contents = contents.right(contents.length()-m_lastPos+1);
00954                     contents = baseIndent + contents.left(contents.length()-1); // left is to remove trailing space
00955                     insertParagraph(contents,para+1);
00956                     removeParagraph(para);
00957 
00958                     // furthermore, IF its nothing but indentation + whitespace
00959                     // we switch this back to Auto-Generated.
00960                     if(cmb && contents.contains(QRegExp('^'+baseIndent+"\\s$")))
00961                     {
00962                         cmb->setContentType(CodeBlock::AutoGenerated);
00963                         cmb->syncToParent();
00964                     }
00965 
00966                 }
00967 
00968                 // send them to the first spot in the line which is editable
00969                 setCursorPosition(para,minPos);
00970 
00971             }
00972             return;
00973         }
00974     }
00975 
00976     // look for changes in editability, if they occur, we need to record
00977     // the edits which have been made
00978     if((editPara && !m_lastTextBlockToBeEdited) || (!editPara && m_lastTextBlockToBeEdited)) {
00979 
00980         setReadOnly(editPara ? false : true);
00981 
00982         // IF this is a different text block, update the body of the method
00983         // it belongs to
00984         if(m_lastTextBlockToBeEdited && (m_lastTextBlockToBeEdited != m_textBlockList.at(para) || !editPara))
00985         {
00986             updateTextBlockFromText (m_lastTextBlockToBeEdited);
00987             m_lastTextBlockToBeEdited = 0;
00988         }
00989 
00990         if(editPara)
00991             m_lastTextBlockToBeEdited = m_textBlockList.at(para);
00992         else
00993             m_lastTextBlockToBeEdited = 0;
00994 
00995     }
00996 
00997     m_lastPara = para;
00998     m_lastPos = pos;
00999     m_newLinePressed = false;
01000     m_backspacePressed = false;
01001 
01002 }
01003 
01004 bool CodeEditor::paraIsNotSingleLine (int para)
01005 {
01006     TextBlock * tBlock = m_textBlockList.at(para);
01007     if(tBlock)
01008     {
01009         int pstart = m_textBlockList.findRef(tBlock);
01010         TextBlockInfo *info = (*m_tbInfoMap)[tBlock];
01011         QPtrList<ParaInfo> list = info->m_paraList;
01012 
01013         for(ParaInfo * item = list.first(); item; item=list.next())
01014             if((pstart+item->start) <= para && (item->start+pstart+item->size) >= para )
01015                 if(item->size > 0)
01016                     return true;
01017     }
01018     return false;
01019 }
01020 
01021 bool CodeEditor::isParaEditable (int para) {
01022 
01023     if (para <0)
01024         return false;
01025 
01026     TextBlock * tBlock = m_textBlockList.at(para);
01027     if(tBlock)
01028     {
01029         int editStart = tBlock->firstEditableLine();
01030         int editEnd = tBlock->lastEditableLine();
01031         bool hasEditableRange = (editStart > 0 || editEnd < 0) ? true : false;
01032         TextBlockInfo *info = (*m_tbInfoMap)[tBlock];
01033         int pstart = m_textBlockList.findRef(tBlock);
01034         int relativeLine = para - pstart;
01035         QPtrList<ParaInfo> list = info->m_paraList;
01036         for(ParaInfo * item = list.first(); item; item=list.next())
01037         {
01038             if((item->start+pstart) <= para && (item->start+pstart+item->size) >= para)
01039                 if(item->isEditable && hasEditableRange)
01040                 {
01041                     if ( relativeLine >= editStart && relativeLine <= (item->size + editEnd) )
01042                         return true;
01043                     else
01044                         return false;
01045                 } else
01046                     return item->isEditable;
01047         }
01048     }
01049     return false;
01050 }
01051 
01052 void CodeEditor::changeTextBlockHighlighting(TextBlock * tBlock, bool selected) {
01053 
01054     if(tBlock)
01055     {
01056         TextBlockInfo *info = (*m_tbInfoMap)[tBlock];
01057         QPtrList<ParaInfo> list = info->m_paraList;
01058         int pstart = m_textBlockList.findRef(tBlock);
01059         for(ParaInfo * item = list.first(); item; item=list.next())
01060             for(int p=(item->start+pstart);p<=(item->start+pstart+item->size);p++)
01061                 if(selected)
01062                     if(info->isClickable)
01063                         setParagraphBackgroundColor(p,getState().selectedColor);
01064                     else
01065                         setParagraphBackgroundColor(p,getState().nonEditBlockColor);
01066                 else if(m_isHighlighted)
01067                     setParagraphBackgroundColor(p,item->bgcolor);
01068                 else
01069                     setParagraphBackgroundColor(p,getState().paperColor);
01070     }
01071 
01072 }
01073 
01074 void CodeEditor::changeShowHidden (int signal) {
01075 
01076     if(signal)
01077         m_showHiddenBlocks = true;
01078     else
01079         m_showHiddenBlocks = false;
01080 
01081     rebuildView(m_lastPara);
01082 
01083 }
01084 
01085 // colorizes/uncolorizes type for ALL paragraphs
01086 void CodeEditor::changeHighlighting(int signal) {
01087 
01088     int total_para = paragraphs()-1;
01089     if(signal) {
01090         // we want to highlight
01091         m_isHighlighted = true;
01092         for(int para=0;para<total_para;para++)
01093         {
01094             TextBlock * tblock = m_textBlockList.at(para);
01095             changeTextBlockHighlighting(tblock,false);
01096         }
01097 
01098 
01099     } else {
01100         // we DON'T want to highlight
01101         m_isHighlighted = false;
01102         for(int para=0;para<total_para;para++)
01103             setParagraphBackgroundColor(para,getState().paperColor);
01104     }
01105 
01106     // now redo the "selected" para, should it exist
01107     if(m_selectedTextBlock)
01108         changeTextBlockHighlighting(m_selectedTextBlock,true);
01109 
01110 }
01111 
01112 void CodeEditor::contractSelectedParagraph( int paraToRemove ) {
01113     TextBlock * tBlock = m_textBlockList.at(paraToRemove);
01114     if(tBlock)
01115     {
01116         int pstart = m_textBlockList.findRef(tBlock);
01117         TextBlockInfo *info = (*m_tbInfoMap)[tBlock];
01118         QPtrList<ParaInfo> list = info->m_paraList;
01119 
01120         bool lowerStartPosition = false;
01121         for(ParaInfo * item = list.first(); item; item=list.next())
01122         {
01123             if(lowerStartPosition)
01124                 item->start -= 1;
01125 
01126             if((pstart+item->start) <= paraToRemove && (item->start+pstart+item->size) >= paraToRemove)
01127             {
01128                 item->size -= 1;
01129                 // a little cheat.. we don't want to remove last line as we need
01130                 // to leave a place that can be 'edited' by the tool IF the user
01131                 // changes their mind about method body content
01132                 if(item->size < 0)
01133                     item->size = 0;
01134                 lowerStartPosition = true;
01135             }
01136         }
01137 
01138         m_textBlockList.remove(paraToRemove);
01139     }
01140 }
01141 
01142 void CodeEditor::expandSelectedParagraph( int priorPara ) {
01143 
01144 
01145     TextBlock * tBlock = m_textBlockList.at(priorPara);
01146     if(tBlock)
01147     {
01148         // add this tBlock in
01149         m_textBlockList.insert(priorPara,tBlock);
01150         TextBlockInfo *info = (*m_tbInfoMap)[tBlock];
01151         QPtrList<ParaInfo> list = info->m_paraList;
01152         int pstart = m_textBlockList.findRef(tBlock);
01153 
01154         // now update the paragraph information
01155         bool upStartPosition = false;
01156         for(ParaInfo * item = list.first(); item; item=list.next())
01157         {
01158             // AFTER we get a match, then following para's need to have start position upped too
01159             if(upStartPosition)
01160                 item->start += 1;
01161 
01162             if((pstart+item->start) <= priorPara && (item->start+pstart+item->size) >= priorPara)
01163             {
01164                 item->size += 1;
01165                 cursorPositionChanged(m_lastPara, m_lastPos);
01166                 upStartPosition = true;
01167             }
01168         }
01169     }
01170 
01171 }
01172 
01173 void CodeEditor::contentsMouseMoveEvent ( QMouseEvent * e )
01174 {
01175 
01176     int para = paragraphAt(e->pos());
01177 
01178     if (para < 0)
01179         return; // shouldn't happen..
01180 
01181     TextBlock * tblock = m_textBlockList.at(para);
01182     if (tblock && m_selectedTextBlock != tblock ) {
01183         TextBlockInfo * info = (*m_tbInfoMap)[tblock];
01184 
01185         // unhighlight old selected textblock regardless of whether
01186         // it was selected or not.
01187         changeTextBlockHighlighting(m_selectedTextBlock,false);
01188 
01189         // highlight new block
01190         changeTextBlockHighlighting(tblock,true);
01191 
01192         // FIX: update the label that shows what type of component this is
01193         getComponentLabel()->setText("<b>"+info->displayName+"</b>");
01194 
01195         m_selectedTextBlock = tblock;
01196 
01197         if(m_lastTextBlockToBeEdited)
01198         {
01199             updateTextBlockFromText (m_lastTextBlockToBeEdited);
01200             m_lastTextBlockToBeEdited = 0;
01201         }
01202     }
01203 
01204     // record this as the last paragraph
01205 
01206 }
01207 
01208 
01209 // Rebuild our view of the document. Happens whenever we change
01210 // some field/aspect of an underlying UML object used to create
01211 // the view.
01212 // If connections are right, then the UMLObject will send out the modified()
01213 // signal which will trigger a call to re-generate the appropriate code within
01214 // the code document. Our burden is to appropriately prepare the tool: we clear
01215 // out ALL the textblocks in the QTextEdit widget and then re-show them
01216 // after the dialog disappears
01217 void CodeEditor::rebuildView( int startCursorPos ) {
01218 
01219     loadFromDocument();
01220 
01221     // make a minima attempt to leave the cursor (view of the code) where
01222     // we started
01223     int new_nrof_para = paragraphs() -1;
01224     setCursorPosition((startCursorPos < new_nrof_para ? startCursorPos : 0), 0);
01225 
01226 }
01227 
01228 
01229 
01230 
01231 #include "codeeditor.moc"
KDE Logo
This file is part of the documentation for umbrello Version 3.1.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Tue Jun 26 08:07:55 2007 by doxygen 1.4.1 written by Dimitri van Heesch, © 1997-2003