KHTML
editor.cpp
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
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
00187
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
00232 break;
00233 case Selection::CARET:
00234
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
00376
00377 styleElement->setAttribute(ATTR_STYLE, m_typingStyle->cssText().implementation());
00378
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 m_part->setCaret(cmd.endingSelection(), false);
00401
00402 if (d->m_lastEditCommand == cmd) {
00403 assert(cmd.commandID() == khtml::TypingCommandID);
00404 }
00405 else {
00406
00407
00408 d->registerUndo( cmd );
00409 d->m_lastEditCommand = cmd;
00410 }
00411 m_part->selectionLayoutChanged();
00412
00413 m_part->emitCaretPositionChanged(cmd.endingSelection().caretPos());
00414 }
00415
00416 void Editor::unappliedEditing(EditCommand &cmd)
00417 {
00418 m_part->setCaret(cmd.startingSelection());
00419 d->registerRedo( cmd );
00420 #ifdef APPLE_CHANGES
00421 KWQ(this)->respondToChangedContents();
00422 #else
00423 m_part->selectionLayoutChanged();
00424
00425 m_part->emitCaretPositionChanged(cmd.startingSelection().caretPos());
00426 #endif
00427 d->m_lastEditCommand = EditCommand::emptyCommand();
00428 }
00429
00430 void Editor::reappliedEditing(EditCommand &cmd)
00431 {
00432 m_part->setCaret(cmd.endingSelection());
00433 d->registerUndo( cmd, false );
00434 #ifdef APPLE_CHANGES
00435 KWQ(this)->respondToChangedContents();
00436 #else
00437 m_part->selectionLayoutChanged();
00438
00439 m_part->emitCaretPositionChanged(cmd.endingSelection().caretPos());
00440 #endif
00441 d->m_lastEditCommand = EditCommand::emptyCommand();
00442 }
00443
00444 CSSStyleDeclarationImpl *Editor::typingStyle() const
00445 {
00446 return m_typingStyle;
00447 }
00448
00449 void Editor::setTypingStyle(CSSStyleDeclarationImpl *style)
00450 {
00451 CSSStyleDeclarationImpl *old = m_typingStyle;
00452 m_typingStyle = style;
00453 if (m_typingStyle)
00454 m_typingStyle->ref();
00455 if (old)
00456 old->deref();
00457 }
00458
00459 void Editor::clearTypingStyle()
00460 {
00461 setTypingStyle(0);
00462 }
00463
00464 bool Editor::handleKeyEvent(QKeyEvent *_ke)
00465 {
00466 bool handled = false;
00467
00468 bool ctrl = _ke->modifiers() & Qt::ControlModifier;
00469 bool shift = _ke->modifiers() & Qt::ShiftModifier;
00470
00471 switch(_ke->key()) {
00472
00473 case Qt::Key_Delete: {
00474 Selection selectionToDelete = m_part->caret();
00475 kDebug(6200) << "========== KEY_DELETE ==========" << endl;
00476 if (selectionToDelete.state() == Selection::CARET) {
00477 Position pos(selectionToDelete.start());
00478 kDebug(6200) << "pos.inLastEditableInRootEditableElement " << pos.inLastEditableInRootEditableElement() << " pos.offset " << pos.offset() << " pos.max " << pos.node()->caretMaxRenderedOffset() << endl;
00479 if (pos.nextCharacterPosition() == pos) {
00480
00481 kDebug(6200) << "no delete!!!!!!!!!!" << endl;
00482 break;
00483 }
00484 m_part->d->editor_context.m_selection
00485 = Selection(pos, pos.nextCharacterPosition());
00486 }
00487
00488 }
00489 case Qt::Key_Backspace:
00490 TypingCommand::deleteKeyPressed(m_part->xmlDocImpl());
00491 handled = true;
00492 break;
00493
00494 case Qt::Key_Return:
00495 case Qt::Key_Enter:
00496
00497 TypingCommand::insertNewline(m_part->xmlDocImpl());
00498
00499
00500 handled = true;
00501 break;
00502
00503 default:
00504
00505 if (!_ke->text().isEmpty()) {
00506 TypingCommand::insertText(m_part->xmlDocImpl(), _ke->text());
00507 handled = true;
00508 }
00509
00510 }
00511
00512 if (handled) {
00513
00514
00515 _ke->accept();
00516 }
00517
00518 return handled;
00519
00520 }
00521
00522
00523 #include "editor.moc"
00524