• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

KHTML

editor.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE project
00002  *
00003  * Copyright (C) 2004 Leo Savernik <l.savernik@aon.at>
00004  *
00005  * This library is free software; you can redistribute it and/or
00006  * modify it under the terms of the GNU Library General Public
00007  * License as published by the Free Software Foundation; either
00008  * version 2 of the License, or (at your option) any later version.
00009  *
00010  * This library is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013  * Library General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU Library General Public License
00016  * along with this library; see the file COPYING.LIB.  If not, write to
00017  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00018  * Boston, MA 02111-1307, USA.
00019  */
00020 
00021 #include "editor.h"
00022 
00023 #include "jsediting.h"
00024 #include "htmlediting_impl.h"
00025 
00026 #include "css/css_renderstyledeclarationimpl.h"
00027 #include "css/css_valueimpl.h"
00028 #include "xml/dom_selection.h"
00029 #include "xml/dom_docimpl.h"
00030 #include "xml/dom_elementimpl.h"
00031 #include "xml/dom_textimpl.h"
00032 #include "xml/dom2_rangeimpl.h"
00033 #include "khtml_part.h"
00034 #include "khtml_ext.h"
00035 #include "khtmlpart_p.h"
00036 
00037 #include <QStack>
00038 
00039 #ifndef APPLE_CHANGES
00040 #  ifdef assert
00041 #    undef assert
00042 #  endif
00043 #  define assert(x) Q_ASSERT(x)
00044 #endif
00045 
00046 #define PREPARE_JSEDITOR_CALL(command, retval) \
00047     JSEditor *js = m_part->xmlDocImpl() ? m_part->xmlDocImpl()->jsEditor() : 0; \
00048         if (!js) return retval; \
00049         const CommandImp *imp = js->commandImp(command)
00050 
00051 #define DEBUG_COMMANDS
00052 
00053 using namespace WTF;
00054 
00055 using namespace DOM;
00056 
00057 using khtml::RenderStyleDeclarationImpl;
00058 using khtml::EditCommandImpl;
00059 using khtml::ApplyStyleCommandImpl;
00060 using khtml::TypingCommandImpl;
00061 using khtml::EditorContext;
00062 using khtml::IndentOutdentCommandImpl;
00063 
00064 // --------------------------------------------------------------------------
00065 
00066 namespace DOM {
00067 
00068 static const int sMaxUndoSteps = 1000;
00069 
00070 class EditorPrivate {
00071   public:
00072     void registerUndo(EditCommandImpl *cmd, bool clearRedoStack = true) {
00073         if (m_undo.count()>= sMaxUndoSteps)
00074             m_undo.pop_front();
00075         if (clearRedoStack)
00076             m_redo.clear();
00077         m_undo.push(cmd);
00078     }
00079     void registerRedo(EditCommandImpl *cmd) {
00080         if (m_redo.count()>= sMaxUndoSteps)
00081             m_redo.pop_front();
00082         m_redo.push(cmd);
00083     }
00084     RefPtr<EditCommandImpl> m_lastEditCommand;
00085     QStack<RefPtr<EditCommandImpl> > m_undo;
00086     QStack<RefPtr<EditCommandImpl> > m_redo;
00087 };
00088 
00089 }
00090 
00091 // ==========================================================================
00092 
00093 Editor::Editor(KHTMLPart *part)
00094   : d(new EditorPrivate), m_typingStyle(0), m_part(part) {
00095 }
00096 
00097 Editor::~Editor() {
00098   if (m_typingStyle)
00099     m_typingStyle->deref();
00100   delete d;
00101 }
00102 
00103 bool Editor::execCommand(const DOMString &command, bool userInterface, const DOMString &value)
00104 {
00105   PREPARE_JSEDITOR_CALL(command, false);
00106   return js->execCommand(imp, userInterface, value);
00107 }
00108 
00109 bool Editor::queryCommandEnabled(const DOMString &command)
00110 {
00111   PREPARE_JSEDITOR_CALL(command, false);
00112   return js->queryCommandEnabled(imp);
00113 }
00114 
00115 bool Editor::queryCommandIndeterm(const DOMString &command)
00116 {
00117   PREPARE_JSEDITOR_CALL(command, false);
00118   return js->queryCommandIndeterm(imp);
00119 }
00120 
00121 bool Editor::queryCommandState(const DOMString &command)
00122 {
00123   PREPARE_JSEDITOR_CALL(command, false);
00124   return js->queryCommandState(imp);
00125 }
00126 
00127 bool Editor::queryCommandSupported(const DOMString &command)
00128 {
00129   PREPARE_JSEDITOR_CALL(command, false);
00130   return js->queryCommandSupported(imp);
00131 }
00132 
00133 DOMString Editor::queryCommandValue(const DOMString &command)
00134 {
00135   PREPARE_JSEDITOR_CALL(command, DOMString());
00136   return js->queryCommandValue(imp);
00137 }
00138 
00139 bool Editor::execCommand(EditorCommand command, bool userInterface, const DOMString &value)
00140 {
00141   PREPARE_JSEDITOR_CALL(command, false);
00142   return js->execCommand(imp, userInterface, value);
00143 }
00144 
00145 bool Editor::queryCommandEnabled(EditorCommand command)
00146 {
00147   PREPARE_JSEDITOR_CALL(command, false);
00148   return js->queryCommandEnabled(imp);
00149 }
00150 
00151 bool Editor::queryCommandIndeterm(EditorCommand command)
00152 {
00153   PREPARE_JSEDITOR_CALL(command, false);
00154   return js->queryCommandIndeterm(imp);
00155 }
00156 
00157 bool Editor::queryCommandState(EditorCommand command)
00158 {
00159   PREPARE_JSEDITOR_CALL(command, false);
00160   return js->queryCommandState(imp);
00161 }
00162 
00163 bool Editor::queryCommandSupported(EditorCommand command)
00164 {
00165   PREPARE_JSEDITOR_CALL(command, false);
00166   return js->queryCommandSupported(imp);
00167 }
00168 
00169 DOMString Editor::queryCommandValue(EditorCommand command)
00170 {
00171   PREPARE_JSEDITOR_CALL(command, DOMString());
00172   return js->queryCommandValue(imp);
00173 }
00174 
00175 void Editor::copy()
00176 {
00177    static_cast<KHTMLPartBrowserExtension*>(m_part->browserExtension())->copy();
00178 }
00179 
00180 void Editor::cut()
00181 {
00182    // ###
00183    static_cast<KHTMLPartBrowserExtension*>(m_part->browserExtension())->cut();
00184 }
00185 
00186 void Editor::paste()
00187 {
00188   // ###
00189   // security?
00190   // static_cast<KHTMLPartBrowserExtension*>(m_part->browserExtension())->paste();
00191 }
00192 
00193 void Editor::print()
00194 {
00195     static_cast<KHTMLPartBrowserExtension*>(m_part->browserExtension())->print();
00196 }
00197 
00198 bool Editor::canPaste() const
00199 {
00200   // ###
00201   return false;
00202 }
00203 
00204 void Editor::redo()
00205 {
00206     if (d->m_redo.isEmpty())
00207         return;
00208     RefPtr<EditCommandImpl> e = d->m_redo.pop();
00209     e->reapply();
00210 }
00211 
00212 void Editor::undo()
00213 {
00214     if (d->m_undo.isEmpty())
00215         return;
00216     RefPtr<EditCommandImpl> e = d->m_undo.pop();
00217     e->unapply();
00218 }
00219 
00220 bool Editor::canRedo() const
00221 {
00222     return !d->m_redo.isEmpty();
00223 }
00224 
00225 bool Editor::canUndo() const
00226 {
00227     return !d->m_undo.isEmpty();
00228 }
00229 
00230 void Editor::applyStyle(CSSStyleDeclarationImpl *style)
00231 {
00232   switch (m_part->caret().state()) {
00233     case Selection::NONE:
00234             // do nothing
00235       break;
00236     case Selection::CARET:
00237             // FIXME: This blows away all the other properties of the typing style.
00238       setTypingStyle(style);
00239       break;
00240     case Selection::RANGE:
00241       if (m_part->xmlDocImpl() && style) {
00242 #ifdef DEBUG_COMMANDS
00243         kDebug() << "[create ApplyStyleCommand]" << endl;
00244 #endif
00245         // FIXME
00246         (new ApplyStyleCommandImpl(m_part->xmlDocImpl(), style))->apply();
00247       }
00248       break;
00249   }
00250 }
00251 
00252 static void updateState(CSSStyleDeclarationImpl *desiredStyle, CSSStyleDeclarationImpl *computedStyle, bool &atStart, Editor::TriState &state)
00253 {
00254   QListIterator<CSSProperty*> it(*desiredStyle->values());
00255   while (it.hasNext()) {
00256     int propertyID = it.next()->id();
00257     DOMString desiredProperty = desiredStyle->getPropertyValue(propertyID);
00258     DOMString computedProperty = computedStyle->getPropertyValue(propertyID);
00259     Editor::TriState propertyState = strcasecmp(desiredProperty, computedProperty) == 0
00260         ? Editor::TrueTriState : Editor::FalseTriState;
00261     if (atStart) {
00262       state = propertyState;
00263       atStart = false;
00264     } else if (state != propertyState) {
00265       state = Editor::MixedTriState;
00266       break;
00267     }
00268   }
00269 }
00270 
00271 Editor::TriState Editor::selectionHasStyle(CSSStyleDeclarationImpl *style) const
00272 {
00273   bool atStart = true;
00274   TriState state = FalseTriState;
00275 
00276   EditorContext *ctx = m_part->editorContext();
00277   if (ctx->m_selection.state() != Selection::RANGE) {
00278     NodeImpl *nodeToRemove;
00279     CSSStyleDeclarationImpl *selectionStyle = selectionComputedStyle(nodeToRemove);
00280     if (!selectionStyle)
00281       return FalseTriState;
00282     selectionStyle->ref();
00283     updateState(style, selectionStyle, atStart, state);
00284     selectionStyle->deref();
00285     if (nodeToRemove) {
00286       int exceptionCode = 0;
00287       nodeToRemove->remove(exceptionCode);
00288       assert(exceptionCode == 0);
00289     }
00290   } else {
00291     for (NodeImpl *node = ctx->m_selection.start().node(); node; node = node->traverseNextNode()) {
00292       if (node->isHTMLElement()) {
00293         CSSStyleDeclarationImpl *computedStyle = new RenderStyleDeclarationImpl(node);
00294         computedStyle->ref();
00295         updateState(style, computedStyle, atStart, state);
00296         computedStyle->deref();
00297         if (state == MixedTriState)
00298           break;
00299       }
00300       if (node == ctx->m_selection.end().node())
00301         break;
00302     }
00303   }
00304 
00305   return state;
00306 }
00307 
00308 bool Editor::selectionStartHasStyle(CSSStyleDeclarationImpl *style) const
00309 {
00310   NodeImpl *nodeToRemove;
00311   CSSStyleDeclarationImpl *selectionStyle = selectionComputedStyle(nodeToRemove);
00312   if (!selectionStyle)
00313     return false;
00314 
00315   selectionStyle->ref();
00316 
00317   bool match = true;
00318 
00319   QListIterator<CSSProperty*> it(*style->values());
00320   while (it.hasNext()) {
00321     int propertyID = it.next()->id();
00322     DOMString desiredProperty = style->getPropertyValue(propertyID);
00323     DOMString selectionProperty = selectionStyle->getPropertyValue(propertyID);
00324     if (strcasecmp(selectionProperty, desiredProperty) != 0) {
00325       match = false;
00326       break;
00327     }
00328   }
00329 
00330   selectionStyle->deref();
00331 
00332   if (nodeToRemove) {
00333     int exceptionCode = 0;
00334     nodeToRemove->remove(exceptionCode);
00335     assert(exceptionCode == 0);
00336   }
00337 
00338   return match;
00339 }
00340 
00341 DOMString Editor::selectionStartStylePropertyValue(int stylePropertyID) const
00342 {
00343   NodeImpl *nodeToRemove;
00344   CSSStyleDeclarationImpl *selectionStyle = selectionComputedStyle(nodeToRemove);
00345   if (!selectionStyle)
00346     return DOMString();
00347 
00348   selectionStyle->ref();
00349   DOMString value = selectionStyle->getPropertyValue(stylePropertyID);
00350   selectionStyle->deref();
00351 
00352   if (nodeToRemove) {
00353     int exceptionCode = 0;
00354     nodeToRemove->remove(exceptionCode);
00355     assert(exceptionCode == 0);
00356   }
00357 
00358   return value;
00359 }
00360 
00361 CSSStyleDeclarationImpl *Editor::selectionComputedStyle(NodeImpl *&nodeToRemove) const
00362 {
00363   nodeToRemove = 0;
00364 
00365   if (!m_part->xmlDocImpl())
00366     return 0;
00367 
00368   EditorContext *ctx = m_part->editorContext();
00369   if (ctx->m_selection.state() == Selection::NONE)
00370     return 0;
00371 
00372   Range range(ctx->m_selection.toRange());
00373   Position pos(range.startContainer().handle(), range.startOffset());
00374   assert(pos.notEmpty());
00375   ElementImpl *elem = pos.element();
00376   ElementImpl *styleElement = elem;
00377   int exceptionCode = 0;
00378 
00379   if (m_typingStyle) {
00380     styleElement = m_part->xmlDocImpl()->createHTMLElement("SPAN");
00381 //     assert(exceptionCode == 0);
00382 
00383     styleElement->setAttribute(ATTR_STYLE, m_typingStyle->cssText().implementation());
00384 //     assert(exceptionCode == 0);
00385 
00386     TextImpl *text = m_part->xmlDocImpl()->createEditingTextNode("");
00387     styleElement->appendChild(text, exceptionCode);
00388     assert(exceptionCode == 0);
00389 
00390     elem->appendChild(styleElement, exceptionCode);
00391     assert(exceptionCode == 0);
00392 
00393     nodeToRemove = styleElement;
00394   }
00395 
00396   return new RenderStyleDeclarationImpl(styleElement);
00397 }
00398 
00399 PassRefPtr<EditCommandImpl> Editor::lastEditCommand() const
00400 {
00401   return d->m_lastEditCommand;
00402 }
00403 
00404 void Editor::appliedEditing(EditCommandImpl *cmd)
00405 {
00406 #ifdef DEBUG_COMMANDS
00407     kDebug() << "[Applied editing]" << endl;
00408 #endif
00409   // make sure we have all the changes in rendering tree applied with relayout if needed before setting caret
00410   // in particular that could be required for inline boxes recomputation when inserting text
00411   m_part->xmlDocImpl()->updateLayout();
00412 
00413   m_part->setCaret(cmd->endingSelection(), false);
00414     // Command will be equal to last edit command only in the case of typing
00415   if (d->m_lastEditCommand == cmd) {
00416     assert(cmd->isTypingCommand());
00417   } else {
00418         // Only register a new undo command if the command passed in is
00419         // different from the last command
00420         d->registerUndo(cmd);
00421         d->m_lastEditCommand = cmd;
00422   }
00423     m_part->editorContext()->m_selection.setNeedsLayout(true);
00424     m_part->selectionLayoutChanged();
00425   // ### only emit if caret pos changed
00426     m_part->emitCaretPositionChanged(cmd->endingSelection().caretPos());
00427 }
00428 
00429 void Editor::unappliedEditing(EditCommandImpl *cmd)
00430 {
00431   // see comment in appliedEditing()
00432   m_part->xmlDocImpl()->updateLayout();
00433 
00434   m_part->setCaret(cmd->startingSelection());
00435   d->registerRedo(cmd);
00436 #ifdef APPLE_CHANGES
00437   KWQ(this)->respondToChangedContents();
00438 #else
00439   m_part->editorContext()->m_selection.setNeedsLayout(true);
00440   m_part->selectionLayoutChanged();
00441   // ### only emit if caret pos changed
00442   m_part->emitCaretPositionChanged(cmd->startingSelection().caretPos());
00443 #endif
00444   d->m_lastEditCommand = 0;
00445 }
00446 
00447 void Editor::reappliedEditing(EditCommandImpl *cmd)
00448 {
00449   // see comment in appliedEditing()
00450   m_part->xmlDocImpl()->updateLayout();
00451 
00452   m_part->setCaret(cmd->endingSelection());
00453   d->registerUndo(cmd, false /*clearRedoStack*/);
00454 #ifdef APPLE_CHANGES
00455   KWQ(this)->respondToChangedContents();
00456 #else
00457   m_part->selectionLayoutChanged();
00458   // ### only emit if caret pos changed
00459   m_part->emitCaretPositionChanged(cmd->endingSelection().caretPos());
00460 #endif
00461   d->m_lastEditCommand = 0;
00462 }
00463 
00464 CSSStyleDeclarationImpl *Editor::typingStyle() const
00465 {
00466   return m_typingStyle;
00467 }
00468 
00469 void Editor::setTypingStyle(CSSStyleDeclarationImpl *style)
00470 {
00471   CSSStyleDeclarationImpl *old = m_typingStyle;
00472   m_typingStyle = style;
00473   if (m_typingStyle)
00474     m_typingStyle->ref();
00475   if (old)
00476     old->deref();
00477 }
00478 
00479 void Editor::clearTypingStyle()
00480 {
00481   setTypingStyle(0);
00482 }
00483 
00484 void Editor::closeTyping()
00485 {
00486     EditCommandImpl *lastCommand = lastEditCommand().get();
00487     if (lastCommand && lastCommand->isTypingCommand())
00488         static_cast<TypingCommandImpl*>(lastCommand)->closeTyping();
00489 }
00490 
00491 void Editor::indent()
00492 {
00493     RefPtr<IndentOutdentCommandImpl> command = new IndentOutdentCommandImpl(m_part->xmlDocImpl(), 
00494             IndentOutdentCommandImpl::Indent);
00495     command->apply();
00496 }
00497 
00498 void Editor::outdent()
00499 {
00500     RefPtr<IndentOutdentCommandImpl> command = new IndentOutdentCommandImpl(m_part->xmlDocImpl(), 
00501             IndentOutdentCommandImpl::Outdent);
00502     command->apply();
00503 }
00504 
00505 bool Editor::handleKeyEvent(QKeyEvent *_ke)
00506 {
00507   bool handled = false;
00508 
00509   bool ctrl  = _ke->modifiers() & Qt::ControlModifier;
00510   bool alt   = _ke->modifiers() & Qt::AltModifier;
00511   //bool shift = _ke->modifiers() & Qt::ShiftModifier;
00512   bool meta  = _ke->modifiers() & Qt::MetaModifier;
00513 
00514   if (ctrl || alt || meta) {
00515       return false;
00516   }
00517 
00518   switch(_ke->key()) {
00519 
00520     case Qt::Key_Delete: {
00521       Selection selectionToDelete = m_part->caret();
00522 #ifdef DEBUG_COMMANDS
00523       kDebug(6200) << "========== KEY_DELETE ==========" << endl;
00524 #endif
00525       if (selectionToDelete.state() == Selection::CARET) {
00526           Position pos(selectionToDelete.start());
00527 #ifdef DEBUG_COMMANDS
00528           kDebug(6200) << "pos.inLastEditableInRootEditableElement " << pos.inLastEditableInRootEditableElement() << " pos.offset " << pos.offset() << " pos.max " << pos.node()->caretMaxRenderedOffset() << endl;
00529 #endif
00530           if (pos.nextCharacterPosition() == pos) {
00531               // we're at the end of a root editable block...do nothing
00532 #ifdef DEBUG_COMMANDS
00533               kDebug(6200) << "no delete!!!!!!!!!!" << endl;
00534 #endif
00535               break;
00536           }
00537           m_part->d->editor_context.m_selection
00538                                = Selection(pos, pos.nextCharacterPosition());
00539       }
00540       // fall through
00541     }
00542     case Qt::Key_Backspace:
00543       TypingCommandImpl::deleteKeyPressed0(m_part->xmlDocImpl());
00544       handled = true;
00545       break;
00546 
00547     case Qt::Key_Return:
00548     case Qt::Key_Enter:
00549 //       if (shift)
00550         TypingCommandImpl::insertNewline0(m_part->xmlDocImpl());
00551 //       else
00552 //         TypingCommand::insertParagraph(m_part->xmlDocImpl());
00553       handled = true;
00554       break;
00555 
00556     case Qt::Key_Escape:
00557     case Qt::Key_Insert:
00558       // FIXME implement me
00559       handled = true;
00560       break;
00561 
00562     default:
00563 // handle_input:
00564       if (m_part->caret().state() != Selection::CARET) {
00565         // We didn't get a chance to grab the caret, likely because
00566         // a script messed with contentEditable in the middle of events
00567         // acquire it now if there isn't a selection
00568         kDebug(6200) << "Editable node w/o caret!";
00569         DOM::NodeImpl* focus = m_part->xmlDocImpl()->focusNode();
00570         if (m_part->caret().state() == Selection::NONE) {
00571             if (focus)
00572                 m_part->setCaret(Position(focus, focus->caretMinOffset()));
00573             else
00574                 break;
00575         }
00576       }
00577 
00578       if (!_ke->text().isEmpty()) {
00579         TypingCommandImpl::insertText0(m_part->xmlDocImpl(), _ke->text());
00580         handled = true;
00581       }
00582 
00583   }
00584 
00585   //if (handled) {
00586     // ### check when to emit it
00587 //     m_part->emitSelectionChanged();
00588   //}
00589 
00590   return handled;
00591 
00592 }
00593 
00594 
00595 #include "editor.moc"
00596 

KHTML

Skip menu "KHTML"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal