emailuis/nmailui/src/nmeditorcontent.cpp
changeset 74 6c59112cfd31
parent 68 83cc6bae1de8
equal deleted inserted replaced
69:4e54af54a4a1 74:6c59112cfd31
    75     // The following line is necessary in terms of being able to add emoticons
    75     // The following line is necessary in terms of being able to add emoticons
    76     // (smileys) to an empty document (mail content). Otherwise the private
    76     // (smileys) to an empty document (mail content). Otherwise the private
    77     // pointer of the QTextDocument which the smiley engine has is NULL and
    77     // pointer of the QTextDocument which the smiley engine has is NULL and
    78     // inserting a smiley will lead to an error.
    78     // inserting a smiley will lead to an error.
    79     mEditorWidget->setPlainText("");
    79     mEditorWidget->setPlainText("");
       
    80     
       
    81     QCoreApplication::instance()->installEventFilter(this); // see eventFilter()
    80 }
    82 }
    81 
    83 
    82 /*!
    84 /*!
    83     Destructor
    85     Destructor
    84 */
    86 */
    85 NmEditorContent::~NmEditorContent()
    87 NmEditorContent::~NmEditorContent()
    86 {
    88 {
    87     NM_FUNCTION;
    89     NM_FUNCTION;
       
    90 
       
    91     QCoreApplication::instance()->removeEventFilter(this); // see eventFilter()
    88 }
    92 }
    89 
    93 
    90 /*!
    94 /*!
    91     Sets the body content. If reply envelopw is present, reply header is generated and set to 
    95     Sets the body content. If reply envelopw is present, reply header is generated and set to 
    92     editor. Reply envelope ownership is not transferred here.
    96     editor. Reply envelope ownership is not transferred here.
   108     
   112     
   109 	QTextCursor cursor(mEditorWidget->document());
   113 	QTextCursor cursor(mEditorWidget->document());
   110     
   114     
   111     // Create the "reply" header (also for forward message)
   115     // Create the "reply" header (also for forward message)
   112 	// sets the font color of the reply header and the original body text to black
   116 	// sets the font color of the reply header and the original body text to black
   113     if ((editorStartMode==NmUiEditorReply || editorStartMode==NmUiEditorReplyAll || 
   117     if ((editorStartMode == NmUiEditorReply || editorStartMode == NmUiEditorReplyAll || 
   114         editorStartMode==NmUiEditorForward) && originalMessage) {
   118         editorStartMode == NmUiEditorForward) && originalMessage) {
   115 		bodyContent.append(QString("<style type=\"text/css\">* { color: black; }</style>"));
   119 		bodyContent.append(QString("<style type=\"text/css\">* { color: black; }</style>"));
   116         bodyContent.append(NmUtilities::createReplyHeader(originalMessage->envelope()));
   120         bodyContent.append(NmUtilities::createReplyHeader(originalMessage->envelope()));
   117     }
   121     }
   118     
   122     
   119     // Check which part is present. Html or plain text part. We use the original message parts.
   123     // Check which part is present. Html or plain text part. We use the original message parts.
   124         plainPart = originalMessage->plainTextBodyPart();
   128         plainPart = originalMessage->plainTextBodyPart();
   125     }
   129     }
   126  
   130  
   127     if (htmlPart) {
   131     if (htmlPart) {
   128         QString bodyText(htmlPart->textContent());
   132         QString bodyText(htmlPart->textContent());
   129         if (editorStartMode==NmUiEditorReply || editorStartMode==NmUiEditorReplyAll || 
   133         if (editorStartMode == NmUiEditorReply || editorStartMode == NmUiEditorReplyAll || 
   130                 editorStartMode==NmUiEditorForward) {
   134                 editorStartMode == NmUiEditorForward) {
   131             convertBodyStylesToDivision(bodyText);
   135             convertBodyStylesToDivision(bodyText);
   132         }
   136         }
   133         
   137         
   134         if(editorStartMode==NmUiEditorReply || editorStartMode==NmUiEditorReplyAll ) {
   138         if(editorStartMode == NmUiEditorReply || editorStartMode == NmUiEditorReplyAll ) {
   135             removeEmbeddedImages(bodyText);
   139             removeEmbeddedImages(bodyText);
   136         }
   140         }
   137         
       
   138         bodyContent.append(bodyText);
   141         bodyContent.append(bodyText);
   139         cursor.insertHtml(bodyContent);
       
   140     }
   142     }
   141     else if (plainPart) {
   143     else if (plainPart) {
   142         // Plain text part was present, set it to HbTextEdit as HTML
   144         // Plain text part was present, set it to HbTextEdit as HTML
   143         bodyContent.append(QString("<html><body><p>"));
   145         bodyContent.append(QString("<html><body><p>"));
   144         bodyContent.append(plainPart->textContent());
   146         bodyContent.append(plainPart->textContent());
   149     // Update of the body width is done when next contentChanged signal comes from the body.
   151     // Update of the body width is done when next contentChanged signal comes from the body.
   150     mNeedForWidthAdjustment = true;
   152     mNeedForWidthAdjustment = true;
   151 	cursor.clearSelection();
   153 	cursor.clearSelection();
   152 	cursor.setPosition(0);
   154 	cursor.setPosition(0);
   153 	cursor.insertHtml(QString("<html><body></body></html>"));
   155 	cursor.insertHtml(QString("<html><body></body></html>"));
       
   156 	mEditorWidget->moveCursor(QTextCursor::Start, QTextCursor::MoveAnchor);
       
   157 	QMetaObject::invokeMethod(this, "ensureCursorVisibility", Qt::QueuedConnection);
   154 }  
   158 }  
   155 
   159 
   156 /*!
   160 /*!
   157    This method creates all needed signal-slot connections
   161    This method creates all needed signal-slot connections
   158  */
   162  */
   173         Qt::QueuedConnection);
   177         Qt::QueuedConnection);
   174 
   178 
   175     // we are interested in the document's height changes
   179     // we are interested in the document's height changes
   176     connect(mEditorWidget->document()->documentLayout(), SIGNAL(documentSizeChanged(QSizeF)), this,
   180     connect(mEditorWidget->document()->documentLayout(), SIGNAL(documentSizeChanged(QSizeF)), this,
   177         SLOT(setEditorContentHeight()), Qt::QueuedConnection);
   181         SLOT(setEditorContentHeight()), Qt::QueuedConnection);
   178 
       
   179     // We need to update the scroll position according the editor's cursor position
       
   180     connect(mHeader->toEdit(), SIGNAL(cursorPositionChanged(int, int)), this, 
       
   181         SLOT(ensureCursorVisibility()), Qt::QueuedConnection);
       
   182     connect(mHeader->ccEdit(), SIGNAL(cursorPositionChanged(int, int)), this, 
       
   183         SLOT(ensureCursorVisibility()), Qt::QueuedConnection);
       
   184     connect(mHeader->bccEdit(), SIGNAL(cursorPositionChanged(int, int)), this, 
       
   185         SLOT(ensureCursorVisibility()), Qt::QueuedConnection);
       
   186     connect(mHeader->subjectEdit(), SIGNAL(cursorPositionChanged(int, int)), this, 
       
   187         SLOT(ensureCursorVisibility()), Qt::QueuedConnection);
       
   188     connect(mEditorWidget, SIGNAL(cursorPositionChanged(int, int)), this, 
       
   189         SLOT(ensureCursorVisibility()), Qt::QueuedConnection);
       
   190 
   182 
   191     // listen to the parent's (NmEditorView) size changes which happen eg. when VKB is opened/closed
   183     // listen to the parent's (NmEditorView) size changes which happen eg. when VKB is opened/closed
   192     connect(parent(), SIGNAL(sizeChanged()), this, SLOT(ensureCursorVisibility()),
   184     connect(parent(), SIGNAL(sizeChanged()), this, SLOT(ensureCursorVisibility()),
   193         Qt::QueuedConnection);
   185         Qt::QueuedConnection);
   194 
   186 
   274     }
   266     }
   275 }
   267 }
   276 
   268 
   277 /*!
   269 /*!
   278     This slot is called when the cursor visibility has to be ensured ie. the scroll position is 
   270     This slot is called when the cursor visibility has to be ensured ie. the scroll position is 
   279     adjusted so that the cursor can be seen.
   271     adjusted so that the cursor can be seen. For defining exatcly what area is ensured the 
       
   272     same algorithm that HbAbstractEditPrivate::ensurePositionVisible() uses is used here. 
   280 */
   273 */
   281 void NmEditorContent::ensureCursorVisibility()
   274 void NmEditorContent::ensureCursorVisibility()
   282 {
   275 {
   283     NM_FUNCTION;
   276     NM_FUNCTION;
   284     
   277     
   307         localRect = mEditorWidget->rectForCursorPosition();
   300         localRect = mEditorWidget->rectForCursorPosition();
   308     }
   301     }
   309 
   302 
   310     // ensure that the cursor position is visible
   303     // ensure that the cursor position is visible
   311     if (focused && !localRect.isEmpty()) {
   304     if (focused && !localRect.isEmpty()) {
   312         QPointF topLeftPos = focused->mapToItem(mScrollAreaContents, localRect.topLeft());
   305         QRectF rect = focused->mapRectToItem(mScrollAreaContents, localRect);
   313         QPointF bottomRightPos =
   306         mScrollArea->ensureVisible(rect.center(), rect.width(), rect.height() / 2 );
   314             focused->mapToItem(mScrollAreaContents, localRect.bottomRight());
       
   315         qreal marginRight = 0;
       
   316         if (mScrollArea->style()) {
       
   317             mScrollArea->style()->parameter("hb-param-margin-gene-right", marginRight);
       
   318         }
       
   319         bottomRightPos.rx() += marginRight;
       
   320 
       
   321         mScrollArea->ensureVisible(topLeftPos);
       
   322         mScrollArea->ensureVisible(bottomRightPos);
       
   323     }
   307     }
   324 }
   308 }
   325 /*!
   309 /*!
   326     Removes embedded images from the message body
   310     Removes embedded images from the message body
   327  */
   311  */
   350     qreal headerWidth = mApplication.screenSize().width() - margin - margin;
   334     qreal headerWidth = mApplication.screenSize().width() - margin - margin;
   351 
   335 
   352     // Create translation object for header position adjustment.
   336     // Create translation object for header position adjustment.
   353     QRectF editorBodyRect = mEditorWidget->geometry();
   337     QRectF editorBodyRect = mEditorWidget->geometry();
   354     QTransform tr;
   338     QTransform tr;
   355     qreal leftMovementThreshold(editorBodyRect.width() - headerWidth);
   339 
       
   340     qreal bodyWidth = editorBodyRect.width();
       
   341     if ( bodyWidth < mApplication.screenSize().width() ) {
       
   342     	    bodyWidth = mApplication.screenSize().width();
       
   343     }
       
   344 
       
   345     qreal leftMovementThreshold( bodyWidth - headerWidth);
       
   346 
   356     if (scrollPosition.x() < 0) {
   347     if (scrollPosition.x() < 0) {
   357         // Left side positioning. Allow left side baunch effect.
   348         // Left side positioning. Allow left side baunch effect.
   358         tr.translate(editorBodyRect.topLeft().x() - margin ,0);
   349         tr.translate(editorBodyRect.topLeft().x() - margin ,0);
   359     }
   350     }
   360     else if (scrollPosition.x() >= 0 && scrollPosition.x() < leftMovementThreshold) {
   351     else if (scrollPosition.x() >= 0 && scrollPosition.x() < leftMovementThreshold) {
   432     QRegExp bodyStyleDefinedRegExp(bodyStyleDefined, Qt::CaseInsensitive);
   423     QRegExp bodyStyleDefinedRegExp(bodyStyleDefined, Qt::CaseInsensitive);
   433     bodyStyleDefinedRegExp.setMinimal(true);
   424     bodyStyleDefinedRegExp.setMinimal(true);
   434     
   425     
   435     QString bodyStartReplacement("<body>\n<div");
   426     QString bodyStartReplacement("<body>\n<div");
   436     
   427     
   437     if(bodyContent.contains(bodyStyleDefinedRegExp)) {
   428     if (bodyContent.contains(bodyStyleDefinedRegExp)) {
   438         QString headPartString = bodyStyleDefinedRegExp.cap(0);
   429         QString headPartString = bodyStyleDefinedRegExp.cap(0);
   439         QString headBodyStyleString("body(\\s)*");
   430         QString headBodyStyleString("body(\\s)*");
   440         QRegExp bodyStyleReplacementRegExp(headBodyStyleString, Qt::CaseInsensitive);
   431         QRegExp bodyStyleReplacementRegExp(headBodyStyleString, Qt::CaseInsensitive);
   441         
   432         
   442         if(headPartString.contains(bodyStyleReplacementRegExp)) {
   433         if (headPartString.contains(bodyStyleReplacementRegExp)) {
   443             headPartString.replace(bodyStyleReplacementRegExp, "div.reply ");
   434             headPartString.replace(bodyStyleReplacementRegExp, "div.reply ");
   444             bodyContent.replace(bodyStyleDefinedRegExp, headPartString);
   435             bodyContent.replace(bodyStyleDefinedRegExp, headPartString);
   445             
   436             
   446             bodyStartReplacement = "<body>\n<div class=\"reply\"";
   437             bodyStartReplacement = "<body>\n<div class=\"reply\"";
   447         }
   438         }
   463  *  <body>    
   454  *  <body>    
   464  *  <div style="background: #00ff00; color: red; border: solid">
   455  *  <div style="background: #00ff00; color: red; border: solid">
   465  *  This is the body text.
   456  *  This is the body text.
   466  *  </div>
   457  *  </div>
   467  *  </body>
   458  *  </body>
   468  *  
       
   469  *  TODO: Tämä tapahtuu toistaiseksi riippumatta siitä onko bodyssa style-määrittelyä.
       
   470  *  TODO: Entä jos dokumentissa on määritelty useampi <body>? 
       
   471  */
   459  */
   472 void NmEditorContent::convertBodyToDiv(QString &bodyContent, const QString &replacementString)
   460 void NmEditorContent::convertBodyToDiv(QString &bodyContent, const QString &replacementString)
   473 {
   461 {
   474     NM_FUNCTION;
   462     NM_FUNCTION;
   475     
   463     
   495  */
   483  */
   496 void NmEditorContent::convertBGColorToStyle(QString &bodyContent)
   484 void NmEditorContent::convertBGColorToStyle(QString &bodyContent)
   497 {
   485 {
   498     NM_FUNCTION;
   486     NM_FUNCTION;
   499     
   487     
   500     QString bgColorInBodyFetchString("<body[^<]+(bgcolor(\\s|)=(\\s|)(\"|)(#|))*>");
   488     QString bgColorInBodyFetchString("<body"
       
   489         "[^<]+"             // 1...* any character except '<'
       
   490         "(bgcolor"
       
   491         "(\\s|)"            // White space or nothing
       
   492         "="
       
   493         "(\\s|)"            // White space or nothing
       
   494         "(\"|)"             // '"' or nothing
       
   495         "(#|))"             // '#' or nothing
       
   496         "*>");              // 0...* Any character + >
   501     QRegExp bgColorInBodyRegExp(bgColorInBodyFetchString, Qt::CaseInsensitive);
   497     QRegExp bgColorInBodyRegExp(bgColorInBodyFetchString, Qt::CaseInsensitive);
   502     
   498     
   503     if(bodyContent.contains(bgColorInBodyRegExp)) {
   499     if (bodyContent.contains(bgColorInBodyRegExp)) {
   504         // There can be only one meaningful bgcolor, the first one.
   500         // There can be only one meaningful bgcolor, the first one.
   505         QString bgColorString = bgColorInBodyRegExp.cap(0);
   501         QString bgColorString = bgColorInBodyRegExp.cap(0);
   506         
   502         
   507         // Extract the color code from the string
   503         // Extract the color code from the string
   508         QString colorCode(bgColorString);
   504         QString colorCode(bgColorString);
   514             "="
   510             "="
   515             "(\\s|)"        // White space or nothing
   511             "(\\s|)"        // White space or nothing
   516             "(\"|)"         // '"' or nothing
   512             "(\"|)"         // '"' or nothing
   517             "(#|)",         // '#' or nothing
   513             "(#|)",         // '#' or nothing
   518             Qt::CaseInsensitive);
   514             Qt::CaseInsensitive);
       
   515         
   519         colorCode.remove(removeBeginningRegExp);
   516         colorCode.remove(removeBeginningRegExp);
   520         
   517         
   521         QRegExp removeEndRegExp(""
   518         QRegExp removeEndRegExp(""
   522             "((\"|\\s)"     // '"' or white space
   519             "((\"|\\s)"     // '"' or white space
   523             "([^<]*)"       // 0...* any characters except '<'
   520             "([^<]*)"       // 0...* any characters except '<'
   524             ">)"            // '>'           
   521             ">)"            // '>'           
   525             "|>");          // ... or nothing before this, just '>'
   522             "|>");          // ... or nothing before this, just '>'
       
   523         
   526         colorCode.remove(removeEndRegExp);
   524         colorCode.remove(removeEndRegExp);
   527         
   525         
   528         QString plainBgColorFetchString("bgcolor(\\s|)*=(\\s|)*[^\\s]+(\\s|(?=>))");
   526         QString plainBgColorFetchString("bgcolor"
       
   527             "(\\s|)*"       // 0...* White space or nothing
       
   528             "="             
       
   529             "(\\s|)*"       // 0...* White space or nothing
       
   530             "[^\\s]+"       // 1...* any character except white space
       
   531             "(\\s|(?=>))"); // White space or follower by '>'
       
   532         
   529         QString bgColorReplacement("style=\"background: #"+colorCode+"\" ");
   533         QString bgColorReplacement("style=\"background: #"+colorCode+"\" ");
   530         QRegExp plainBgColorFetchRegExp(plainBgColorFetchString, Qt::CaseInsensitive);
   534         QRegExp plainBgColorFetchRegExp(plainBgColorFetchString, Qt::CaseInsensitive);
   531         
   535         
   532         // Create temporary string for bgcolor
   536         // Create temporary string for bgcolor
   533         bgColorString.replace(plainBgColorFetchRegExp, bgColorReplacement);
   537         bgColorString.replace(plainBgColorFetchRegExp, bgColorReplacement);
   534         
   538         
   535         // Copy the contents of the temporary string to the message
   539         // Copy the contents of the temporary string to the message
   536         bodyContent.replace(bgColorInBodyRegExp, bgColorString);
   540         bodyContent.replace(bgColorInBodyRegExp, bgColorString);
   537     }    
   541     }    
   538 }
   542 }
       
   543 
       
   544 /*!
       
   545  *  Listen to the input method events (eg. cursor position changes, preedit text changes etc.) and 
       
   546  *  invoke ensureCursorVisibility. Qt FW does not automatically ensure the visiblity of a text in a
       
   547  *  edit widget in our custom scroll area. Filtering events is the only way to get events of the
       
   548  *  preedit text changes.
       
   549  */
       
   550 bool NmEditorContent::eventFilter(QObject *obj, QEvent *event)
       
   551 {
       
   552     // let the FW handle the event first
       
   553     bool ret = QObject::eventFilter(obj, event);
       
   554     
       
   555     if (event && event->type() == QEvent::InputMethod) {
       
   556         QMetaObject::invokeMethod(this, "ensureCursorVisibility", Qt::QueuedConnection);
       
   557     }
       
   558     
       
   559     return ret;
       
   560 }
       
   561