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
KDE 4.6 API Reference