• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • 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 "edit_command.h"
00024 #include "htmlediting.h"
00025 #include "jsediting.h"
00026 
00027 #include "css/css_renderstyledeclarationimpl.h"
00028 #include "css/css_valueimpl.h"
00029 #include "misc/htmlattrs.h"
00030 #include "xml/dom_selection.h"
00031 #include "xml/dom_docimpl.h"
00032 #include "xml/dom_elementimpl.h"
00033 #include "xml/dom_textimpl.h"
00034 #include "xml/dom2_rangeimpl.h"
00035 #include "khtml_part.h"
00036 #include "khtml_ext.h"
00037 #include "khtmlpart_p.h"
00038 
00039 #include <QStack>
00040 
00041 #ifndef APPLE_CHANGES
00042 #  ifdef assert
00043 #    undef assert
00044 #  endif
00045 #  define assert(x) Q_ASSERT(x)
00046 #endif
00047 
00048 #define PREPARE_JSEDITOR_CALL(command, retval) \
00049     JSEditor *js = m_part->xmlDocImpl() ? m_part->xmlDocImpl()->jsEditor() : 0; \
00050         if (!js) return retval; \
00051         const CommandImp *imp = js->commandImp(command)
00052 
00053 // --------------------------------------------------------------------------
00054 
00055 namespace DOM {
00056 
00057 static const int sMaxUndoSteps = 1000;
00058 
00059 class EditorPrivate {
00060   public:
00061     void registerUndo( const khtml::EditCommand& cmd, bool clearRedoStack = true ) {
00062         if (m_undo.count()>= sMaxUndoSteps)
00063             m_undo.pop_front();
00064         if (clearRedoStack)
00065             m_redo.clear();
00066         m_undo.push( cmd );
00067     }
00068     void registerRedo( const khtml::EditCommand& cmd ) {
00069         if (m_redo.count()>= sMaxUndoSteps)
00070             m_redo.pop_front();
00071         m_redo.push( cmd );
00072     }
00073     khtml::EditCommand m_lastEditCommand;
00074     QStack<khtml::EditCommand> m_undo;
00075     QStack<khtml::EditCommand> m_redo;
00076 };
00077 
00078 }
00079 
00080 
00081 using namespace DOM;
00082 using khtml::ApplyStyleCommand;
00083 using khtml::EditorContext;
00084 using khtml::EditCommand;
00085 using khtml::RenderStyleDeclarationImpl;
00086 using khtml::TypingCommand;
00087 
00088 // ==========================================================================
00089 
00090 Editor::Editor(KHTMLPart *part)
00091   : d(new EditorPrivate), m_typingStyle(0), m_part(part) {
00092 }
00093 
00094 Editor::~Editor() {
00095   if (m_typingStyle)
00096     m_typingStyle->deref();
00097   delete d;
00098 }
00099 
00100 bool Editor::execCommand(const DOMString &command, bool userInterface, const DOMString &value)
00101 {
00102   PREPARE_JSEDITOR_CALL(command, false);
00103   return js->execCommand(imp, userInterface, value);
00104 }
00105 
00106 bool Editor::queryCommandEnabled(const DOMString &command)
00107 {
00108   PREPARE_JSEDITOR_CALL(command, false);
00109   return js->queryCommandEnabled(imp);
00110 }
00111 
00112 bool Editor::queryCommandIndeterm(const DOMString &command)
00113 {
00114   PREPARE_JSEDITOR_CALL(command, false);
00115   return js->queryCommandIndeterm(imp);
00116 }
00117 
00118 bool Editor::queryCommandState(const DOMString &command)
00119 {
00120   PREPARE_JSEDITOR_CALL(command, false);
00121   return js->queryCommandState(imp);
00122 }
00123 
00124 bool Editor::queryCommandSupported(const DOMString &command)
00125 {
00126   PREPARE_JSEDITOR_CALL(command, false);
00127   return js->queryCommandSupported(imp);
00128 }
00129 
00130 DOMString Editor::queryCommandValue(const DOMString &command)
00131 {
00132   PREPARE_JSEDITOR_CALL(command, DOMString());
00133   return js->queryCommandValue(imp);
00134 }
00135 
00136 bool Editor::execCommand(EditorCommand command, bool userInterface, const DOMString &value)
00137 {
00138   PREPARE_JSEDITOR_CALL(command, false);
00139   return js->execCommand(imp, userInterface, value);
00140 }
00141 
00142 bool Editor::queryCommandEnabled(EditorCommand command)
00143 {
00144   PREPARE_JSEDITOR_CALL(command, false);
00145   return js->queryCommandEnabled(imp);
00146 }
00147 
00148 bool Editor::queryCommandIndeterm(EditorCommand command)
00149 {
00150   PREPARE_JSEDITOR_CALL(command, false);
00151   return js->queryCommandIndeterm(imp);
00152 }
00153 
00154 bool Editor::queryCommandState(EditorCommand command)
00155 {
00156   PREPARE_JSEDITOR_CALL(command, false);
00157   return js->queryCommandState(imp);
00158 }
00159 
00160 bool Editor::queryCommandSupported(EditorCommand command)
00161 {
00162   PREPARE_JSEDITOR_CALL(command, false);
00163   return js->queryCommandSupported(imp);
00164 }
00165 
00166 DOMString Editor::queryCommandValue(EditorCommand command)
00167 {
00168   PREPARE_JSEDITOR_CALL(command, DOMString());
00169   return js->queryCommandValue(imp);
00170 }
00171 
00172 void Editor::copy()
00173 {
00174    static_cast<KHTMLPartBrowserExtension*>(m_part->browserExtension())->copy();
00175 }
00176 
00177 void Editor::cut()
00178 {
00179    // ###
00180    static_cast<KHTMLPartBrowserExtension*>(m_part->browserExtension())->cut();
00181 }
00182 
00183 void Editor::paste()
00184 {
00185   // ###
00186   // security?
00187   // static_cast<KHTMLPartBrowserExtension*>(m_part->browserExtension())->paste();
00188 }
00189 
00190 void Editor::print()
00191 {
00192     static_cast<KHTMLPartBrowserExtension*>(m_part->browserExtension())->print();
00193 }
00194 
00195 bool Editor::canPaste() const
00196 {
00197   // ###
00198   return false;
00199 }
00200 
00201 void Editor::redo()
00202 {
00203     if (d->m_redo.isEmpty())
00204         return;
00205     EditCommand e = d->m_redo.pop();
00206     e.reapply();
00207 }
00208 
00209 void Editor::undo()
00210 {
00211     if (d->m_undo.isEmpty())
00212         return;
00213     EditCommand e = d->m_undo.pop();
00214     e.unapply();
00215 }
00216 
00217 bool Editor::canRedo() const
00218 {
00219     return !d->m_redo.isEmpty();
00220 }
00221 
00222 bool Editor::canUndo() const
00223 {
00224     return !d->m_undo.isEmpty();
00225 }
00226 
00227 void Editor::applyStyle(CSSStyleDeclarationImpl *style)
00228 {
00229   switch (m_part->caret().state()) {
00230     case Selection::NONE:
00231             // do nothing
00232       break;
00233     case Selection::CARET:
00234             // FIXME: This blows away all the other properties of the typing style.
00235       setTypingStyle(style);
00236       break;
00237     case Selection::RANGE:
00238       if (m_part->xmlDocImpl() && style) {
00239         ApplyStyleCommand cmd(m_part->xmlDocImpl(), style);
00240         cmd.apply();
00241       }
00242       break;
00243   }
00244 }
00245 
00246 static void updateState(CSSStyleDeclarationImpl *desiredStyle, CSSStyleDeclarationImpl *computedStyle, bool &atStart, Editor::TriState &state)
00247 {
00248   QListIterator<CSSProperty*> it(*desiredStyle->values()); 
00249   while (it.hasNext()) {
00250     int propertyID = it.next()->id();
00251     DOMString desiredProperty = desiredStyle->getPropertyValue(propertyID);
00252     DOMString computedProperty = computedStyle->getPropertyValue(propertyID);
00253     Editor::TriState propertyState = strcasecmp(desiredProperty, computedProperty) == 0
00254         ? Editor::TrueTriState : Editor::FalseTriState;
00255     if (atStart) {
00256       state = propertyState;
00257       atStart = false;
00258     } else if (state != propertyState) {
00259       state = Editor::MixedTriState;
00260       break;
00261     }
00262   }
00263 }
00264 
00265 Editor::TriState Editor::selectionHasStyle(CSSStyleDeclarationImpl *style) const
00266 {
00267   bool atStart = true;
00268   TriState state = FalseTriState;
00269 
00270   EditorContext *ctx = m_part->editorContext();
00271   if (ctx->m_selection.state() != Selection::RANGE) {
00272     NodeImpl *nodeToRemove;
00273     CSSStyleDeclarationImpl *selectionStyle = selectionComputedStyle(nodeToRemove);
00274     if (!selectionStyle)
00275       return FalseTriState;
00276     selectionStyle->ref();
00277     updateState(style, selectionStyle, atStart, state);
00278     selectionStyle->deref();
00279     if (nodeToRemove) {
00280       int exceptionCode = 0;
00281       nodeToRemove->remove(exceptionCode);
00282       assert(exceptionCode == 0);
00283     }
00284   } else {
00285     for (NodeImpl *node = ctx->m_selection.start().node(); node; node = node->traverseNextNode()) {
00286       if (node->isHTMLElement()) {
00287         CSSStyleDeclarationImpl *computedStyle = new RenderStyleDeclarationImpl(node);
00288         computedStyle->ref();
00289         updateState(style, computedStyle, atStart, state);
00290         computedStyle->deref();
00291         if (state == MixedTriState)
00292           break;
00293       }
00294       if (node == ctx->m_selection.end().node())
00295         break;
00296     }
00297   }
00298 
00299   return state;
00300 }
00301 
00302 bool Editor::selectionStartHasStyle(CSSStyleDeclarationImpl *style) const
00303 {
00304   NodeImpl *nodeToRemove;
00305   CSSStyleDeclarationImpl *selectionStyle = selectionComputedStyle(nodeToRemove);
00306   if (!selectionStyle)
00307     return false;
00308 
00309   selectionStyle->ref();
00310 
00311   bool match = true;
00312 
00313   QListIterator<CSSProperty*> it(*style->values());
00314   while (it.hasNext()) {
00315     int propertyID = it.next()->id();
00316     DOMString desiredProperty = style->getPropertyValue(propertyID);
00317     DOMString selectionProperty = selectionStyle->getPropertyValue(propertyID);
00318     if (strcasecmp(selectionProperty, desiredProperty) != 0) {
00319       match = false;
00320       break;
00321     }
00322   }
00323 
00324   selectionStyle->deref();
00325 
00326   if (nodeToRemove) {
00327     int exceptionCode = 0;
00328     nodeToRemove->remove(exceptionCode);
00329     assert(exceptionCode == 0);
00330   }
00331 
00332   return match;
00333 }
00334 
00335 DOMString Editor::selectionStartStylePropertyValue(int stylePropertyID) const
00336 {
00337   NodeImpl *nodeToRemove;
00338   CSSStyleDeclarationImpl *selectionStyle = selectionComputedStyle(nodeToRemove);
00339   if (!selectionStyle)
00340     return DOMString();
00341 
00342   selectionStyle->ref();
00343   DOMString value = selectionStyle->getPropertyValue(stylePropertyID);
00344   selectionStyle->deref();
00345 
00346   if (nodeToRemove) {
00347     int exceptionCode = 0;
00348     nodeToRemove->remove(exceptionCode);
00349     assert(exceptionCode == 0);
00350   }
00351 
00352   return value;
00353 }
00354 
00355 CSSStyleDeclarationImpl *Editor::selectionComputedStyle(NodeImpl *&nodeToRemove) const
00356 {
00357   nodeToRemove = 0;
00358 
00359   if (!m_part->xmlDocImpl())
00360     return 0;
00361 
00362   EditorContext *ctx = m_part->editorContext();
00363   if (ctx->m_selection.state() == Selection::NONE)
00364     return 0;
00365 
00366   Range range(ctx->m_selection.toRange());
00367   Position pos(range.startContainer().handle(), range.startOffset());
00368   assert(pos.notEmpty());
00369   ElementImpl *elem = pos.element();
00370   ElementImpl *styleElement = elem;
00371   int exceptionCode = 0;
00372 
00373   if (m_typingStyle) {
00374     styleElement = m_part->xmlDocImpl()->createHTMLElement("SPAN");
00375 //     assert(exceptionCode == 0);
00376 
00377     styleElement->setAttribute(ATTR_STYLE, m_typingStyle->cssText().implementation());
00378 //     assert(exceptionCode == 0);
00379 
00380     TextImpl *text = m_part->xmlDocImpl()->createEditingTextNode("");
00381     styleElement->appendChild(text, exceptionCode);
00382     assert(exceptionCode == 0);
00383 
00384     elem->appendChild(styleElement, exceptionCode);
00385     assert(exceptionCode == 0);
00386 
00387     nodeToRemove = styleElement;
00388   }
00389 
00390   return new RenderStyleDeclarationImpl(styleElement);
00391 }
00392 
00393 EditCommand Editor::lastEditCommand() const
00394 {
00395   return d->m_lastEditCommand;
00396 }
00397 
00398 void Editor::appliedEditing(EditCommand &cmd)
00399 {
00400   // make sure we have all the changes in rendering tree applied with relayout if needed before setting caret
00401   // in particular that could be required for inline boxes recomputation when inserting text
00402   m_part->xmlDocImpl()->updateLayout();
00403 
00404   m_part->setCaret(cmd.endingSelection(), false);
00405     // Command will be equal to last edit command only in the case of typing
00406   if (d->m_lastEditCommand == cmd) {
00407     assert(cmd.commandID() == khtml::TypingCommandID);
00408   }
00409   else {
00410         // Only register a new undo command if the command passed in is
00411         // different from the last command
00412         d->registerUndo( cmd );
00413         d->m_lastEditCommand = cmd;
00414   }
00415     m_part->selectionLayoutChanged();
00416   // ### only emit if caret pos changed
00417     m_part->emitCaretPositionChanged(cmd.endingSelection().caretPos());
00418 }
00419 
00420 void Editor::unappliedEditing(EditCommand &cmd)
00421 {
00422   // see comment in appliedEditing()
00423   m_part->xmlDocImpl()->updateLayout();
00424 
00425   m_part->setCaret(cmd.startingSelection());
00426   d->registerRedo( cmd );
00427 #ifdef APPLE_CHANGES
00428   KWQ(this)->respondToChangedContents();
00429 #else
00430   m_part->selectionLayoutChanged();
00431   // ### only emit if caret pos changed
00432   m_part->emitCaretPositionChanged(cmd.startingSelection().caretPos());
00433 #endif
00434   d->m_lastEditCommand = EditCommand::emptyCommand();
00435 }
00436 
00437 void Editor::reappliedEditing(EditCommand &cmd)
00438 {
00439   // see comment in appliedEditing()
00440   m_part->xmlDocImpl()->updateLayout();
00441 
00442   m_part->setCaret(cmd.endingSelection());
00443   d->registerUndo( cmd, false /*clearRedoStack*/ );
00444 #ifdef APPLE_CHANGES
00445   KWQ(this)->respondToChangedContents();
00446 #else
00447   m_part->selectionLayoutChanged();
00448   // ### only emit if caret pos changed
00449   m_part->emitCaretPositionChanged(cmd.endingSelection().caretPos());
00450 #endif
00451   d->m_lastEditCommand = EditCommand::emptyCommand();
00452 }
00453 
00454 CSSStyleDeclarationImpl *Editor::typingStyle() const
00455 {
00456   return m_typingStyle;
00457 }
00458 
00459 void Editor::setTypingStyle(CSSStyleDeclarationImpl *style)
00460 {
00461   CSSStyleDeclarationImpl *old = m_typingStyle;
00462   m_typingStyle = style;
00463   if (m_typingStyle)
00464     m_typingStyle->ref();
00465   if (old)
00466     old->deref();
00467 }
00468 
00469 void Editor::clearTypingStyle()
00470 {
00471   setTypingStyle(0);
00472 }
00473 
00474 bool Editor::handleKeyEvent(QKeyEvent *_ke)
00475 {
00476   bool handled = false;
00477 
00478   bool ctrl  = _ke->modifiers() & Qt::ControlModifier;
00479   bool alt   = _ke->modifiers() & Qt::AltModifier;
00480   bool shift = _ke->modifiers() & Qt::ShiftModifier;
00481   bool meta  = _ke->modifiers() & Qt::MetaModifier;
00482 
00483   if (ctrl || alt || meta) {
00484       return false;
00485   }
00486 
00487   switch(_ke->key()) {
00488 
00489     case Qt::Key_Delete: {
00490       Selection selectionToDelete = m_part->caret();
00491       kDebug(6200) << "========== KEY_DELETE ==========" << endl;
00492       if (selectionToDelete.state() == Selection::CARET) {
00493           Position pos(selectionToDelete.start());
00494           kDebug(6200) << "pos.inLastEditableInRootEditableElement " << pos.inLastEditableInRootEditableElement() << " pos.offset " << pos.offset() << " pos.max " << pos.node()->caretMaxRenderedOffset() << endl;
00495           if (pos.nextCharacterPosition() == pos) {
00496               // we're at the end of a root editable block...do nothing
00497               kDebug(6200) << "no delete!!!!!!!!!!" << endl;
00498               break;
00499           }
00500           m_part->d->editor_context.m_selection
00501                                = Selection(pos, pos.nextCharacterPosition());
00502       }
00503       // fall through
00504     }
00505     case Qt::Key_Backspace:
00506       TypingCommand::deleteKeyPressed(m_part->xmlDocImpl());
00507       handled = true;
00508       break;
00509 
00510     case Qt::Key_Return:
00511     case Qt::Key_Enter:
00512 //       if (shift)
00513         TypingCommand::insertNewline(m_part->xmlDocImpl());
00514 //       else
00515 //         TypingCommand::insertParagraph(m_part->xmlDocImpl());
00516       handled = true;
00517       break;
00518 
00519     case Qt::Key_Escape:
00520     case Qt::Key_Insert:
00521       // FIXME implement me
00522       handled = true;
00523       break;
00524     
00525     default:
00526 // handle_input:
00527       if (!_ke->text().isEmpty()) {
00528         TypingCommand::insertText(m_part->xmlDocImpl(), _ke->text());
00529         handled = true;
00530       }
00531 
00532   }
00533 
00534   //if (handled) {
00535     // ### check when to emit it
00536 //     m_part->emitSelectionChanged();
00537   //}
00538 
00539   return handled;
00540 
00541 }
00542 
00543 
00544 #include "editor.moc"
00545 

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
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.7
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