KHtml

editor.cpp
1 /* This file is part of the KDE project
2  *
3  * Copyright (C) 2004 Leo Savernik <[email protected]>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this library; see the file COPYING.LIB. If not, write to
17  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20 
21 #include "editor.h"
22 
23 #include "jsediting.h"
24 #include "htmlediting_impl.h"
25 
26 #include "css/css_renderstyledeclarationimpl.h"
27 #include "css/css_valueimpl.h"
28 #include "xml/dom_selection.h"
29 #include "xml/dom_docimpl.h"
30 #include "xml/dom_elementimpl.h"
31 #include "xml/dom_textimpl.h"
32 #include "xml/dom2_rangeimpl.h"
33 #include "khtml_part.h"
34 #include "khtml_ext.h"
35 #include "khtmlpart_p.h"
36 
37 #include <QStack>
38 #include <QKeyEvent>
39 
40 #ifndef APPLE_CHANGES
41 # ifdef assert
42 # undef assert
43 # endif
44 # define assert(x) Q_ASSERT(x)
45 #endif
46 
47 #define PREPARE_JSEDITOR_CALL(command, retval) \
48  JSEditor *js = m_part->xmlDocImpl() ? m_part->xmlDocImpl()->jsEditor() : nullptr; \
49  if (!js) return retval; \
50  const CommandImp *imp = js->commandImp(command)
51 
52 #define DEBUG_COMMANDS
53 
54 using namespace WTF;
55 
56 using namespace DOM;
57 
58 using khtml::RenderStyleDeclarationImpl;
59 using khtml::EditCommandImpl;
60 using khtml::ApplyStyleCommandImpl;
61 using khtml::TypingCommandImpl;
62 using khtml::EditorContext;
63 using khtml::IndentOutdentCommandImpl;
64 
65 // --------------------------------------------------------------------------
66 
67 namespace DOM
68 {
69 
70 static const int sMaxUndoSteps = 1000;
71 
72 class EditorPrivate
73 {
74 public:
75  void registerUndo(EditCommandImpl *cmd, bool clearRedoStack = true)
76  {
77  if (m_undo.count() >= sMaxUndoSteps) {
78  m_undo.pop_front();
79  }
80  if (clearRedoStack) {
81  m_redo.clear();
82  }
83  m_undo.push(cmd);
84  }
85  void registerRedo(EditCommandImpl *cmd)
86  {
87  if (m_redo.count() >= sMaxUndoSteps) {
88  m_redo.pop_front();
89  }
90  m_redo.push(cmd);
91  }
92  RefPtr<EditCommandImpl> m_lastEditCommand;
95 };
96 
97 }
98 
99 // ==========================================================================
100 
101 Editor::Editor(KHTMLPart *part)
102  : d(new EditorPrivate), m_typingStyle(nullptr), m_part(part)
103 {
104 }
105 
106 Editor::~Editor()
107 {
108  if (m_typingStyle) {
109  m_typingStyle->deref();
110  }
111  delete d;
112 }
113 
114 bool Editor::execCommand(const DOMString &command, bool userInterface, const DOMString &value)
115 {
116  PREPARE_JSEDITOR_CALL(command, false);
117  return js->execCommand(imp, userInterface, value);
118 }
119 
121 {
122  PREPARE_JSEDITOR_CALL(command, false);
123  return js->queryCommandEnabled(imp);
124 }
125 
127 {
128  PREPARE_JSEDITOR_CALL(command, false);
129  return js->queryCommandIndeterm(imp);
130 }
131 
133 {
134  PREPARE_JSEDITOR_CALL(command, false);
135  return js->queryCommandState(imp);
136 }
137 
139 {
140  PREPARE_JSEDITOR_CALL(command, false);
141  return js->queryCommandSupported(imp);
142 }
143 
145 {
146  PREPARE_JSEDITOR_CALL(command, DOMString());
147  return js->queryCommandValue(imp);
148 }
149 
150 bool Editor::execCommand(EditorCommand command, bool userInterface, const DOMString &value)
151 {
152  PREPARE_JSEDITOR_CALL(command, false);
153  return js->execCommand(imp, userInterface, value);
154 }
155 
157 {
158  PREPARE_JSEDITOR_CALL(command, false);
159  return js->queryCommandEnabled(imp);
160 }
161 
163 {
164  PREPARE_JSEDITOR_CALL(command, false);
165  return js->queryCommandIndeterm(imp);
166 }
167 
169 {
170  PREPARE_JSEDITOR_CALL(command, false);
171  return js->queryCommandState(imp);
172 }
173 
175 {
176  PREPARE_JSEDITOR_CALL(command, false);
177  return js->queryCommandSupported(imp);
178 }
179 
181 {
182  PREPARE_JSEDITOR_CALL(command, DOMString());
183  return js->queryCommandValue(imp);
184 }
185 
187 {
188  static_cast<KHTMLPartBrowserExtension *>(m_part->browserExtension())->copy();
189 }
190 
192 {
193  // ###
194  static_cast<KHTMLPartBrowserExtension *>(m_part->browserExtension())->cut();
195 }
196 
198 {
199  // ###
200  // security?
201  // static_cast<KHTMLPartBrowserExtension*>(m_part->browserExtension())->paste();
202 }
203 
205 {
206  static_cast<KHTMLPartBrowserExtension *>(m_part->browserExtension())->print();
207 }
208 
209 bool Editor::canPaste() const
210 {
211  // ###
212  return false;
213 }
214 
216 {
217  if (d->m_redo.isEmpty()) {
218  return;
219  }
220  RefPtr<EditCommandImpl> e = d->m_redo.pop();
221  e->reapply();
222 }
223 
225 {
226  if (d->m_undo.isEmpty()) {
227  return;
228  }
229  RefPtr<EditCommandImpl> e = d->m_undo.pop();
230  e->unapply();
231 }
232 
233 bool Editor::canRedo() const
234 {
235  return !d->m_redo.isEmpty();
236 }
237 
238 bool Editor::canUndo() const
239 {
240  return !d->m_undo.isEmpty();
241 }
242 
243 void Editor::applyStyle(CSSStyleDeclarationImpl *style)
244 {
245  switch (m_part->caret().state()) {
246  case Selection::NONE:
247  // do nothing
248  break;
249  case Selection::CARET:
250  // FIXME: This blows away all the other properties of the typing style.
251  setTypingStyle(style);
252  break;
253  case Selection::RANGE:
254  if (m_part->xmlDocImpl() && style) {
255 #ifdef DEBUG_COMMANDS
256  // qCDebug(KHTML_LOG) << "[create ApplyStyleCommand]";
257 #endif
258  // FIXME
259  (new ApplyStyleCommandImpl(m_part->xmlDocImpl(), style))->apply();
260  }
261  break;
262  }
263 }
264 
265 static void updateState(CSSStyleDeclarationImpl *desiredStyle, CSSStyleDeclarationImpl *computedStyle, bool &atStart, Editor::TriState &state)
266 {
267  QListIterator<CSSProperty *> it(*desiredStyle->values());
268  while (it.hasNext()) {
269  int propertyID = it.next()->id();
270  DOMString desiredProperty = desiredStyle->getPropertyValue(propertyID);
271  DOMString computedProperty = computedStyle->getPropertyValue(propertyID);
272  Editor::TriState propertyState = strcasecmp(desiredProperty, computedProperty) == 0
273  ? Editor::TrueTriState : Editor::FalseTriState;
274  if (atStart) {
275  state = propertyState;
276  atStart = false;
277  } else if (state != propertyState) {
278  state = Editor::MixedTriState;
279  break;
280  }
281  }
282 }
283 
284 Editor::TriState Editor::selectionHasStyle(CSSStyleDeclarationImpl *style) const
285 {
286  bool atStart = true;
287  TriState state = FalseTriState;
288 
289  EditorContext *ctx = m_part->editorContext();
290  if (ctx->m_selection.state() != Selection::RANGE) {
291  NodeImpl *nodeToRemove;
292  CSSStyleDeclarationImpl *selectionStyle = selectionComputedStyle(nodeToRemove);
293  if (!selectionStyle) {
294  return FalseTriState;
295  }
296  selectionStyle->ref();
297  updateState(style, selectionStyle, atStart, state);
298  selectionStyle->deref();
299  if (nodeToRemove) {
300  int exceptionCode = 0;
301  nodeToRemove->remove(exceptionCode);
302  assert(exceptionCode == 0);
303  }
304  } else {
305  for (NodeImpl *node = ctx->m_selection.start().node(); node; node = node->traverseNextNode()) {
306  if (node->isHTMLElement()) {
307  CSSStyleDeclarationImpl *computedStyle = new RenderStyleDeclarationImpl(node);
308  computedStyle->ref();
309  updateState(style, computedStyle, atStart, state);
310  computedStyle->deref();
311  if (state == MixedTriState) {
312  break;
313  }
314  }
315  if (node == ctx->m_selection.end().node()) {
316  break;
317  }
318  }
319  }
320 
321  return state;
322 }
323 
324 bool Editor::selectionStartHasStyle(CSSStyleDeclarationImpl *style) const
325 {
326  NodeImpl *nodeToRemove;
327  CSSStyleDeclarationImpl *selectionStyle = selectionComputedStyle(nodeToRemove);
328  if (!selectionStyle) {
329  return false;
330  }
331 
332  selectionStyle->ref();
333 
334  bool match = true;
335 
336  QListIterator<CSSProperty *> it(*style->values());
337  while (it.hasNext()) {
338  int propertyID = it.next()->id();
339  DOMString desiredProperty = style->getPropertyValue(propertyID);
340  DOMString selectionProperty = selectionStyle->getPropertyValue(propertyID);
341  if (strcasecmp(selectionProperty, desiredProperty) != 0) {
342  match = false;
343  break;
344  }
345  }
346 
347  selectionStyle->deref();
348 
349  if (nodeToRemove) {
350  int exceptionCode = 0;
351  nodeToRemove->remove(exceptionCode);
352  assert(exceptionCode == 0);
353  }
354 
355  return match;
356 }
357 
359 {
360  NodeImpl *nodeToRemove;
361  CSSStyleDeclarationImpl *selectionStyle = selectionComputedStyle(nodeToRemove);
362  if (!selectionStyle) {
363  return DOMString();
364  }
365 
366  selectionStyle->ref();
367  DOMString value = selectionStyle->getPropertyValue(stylePropertyID);
368  selectionStyle->deref();
369 
370  if (nodeToRemove) {
371  int exceptionCode = 0;
372  nodeToRemove->remove(exceptionCode);
373  assert(exceptionCode == 0);
374  }
375 
376  return value;
377 }
378 
379 CSSStyleDeclarationImpl *Editor::selectionComputedStyle(NodeImpl *&nodeToRemove) const
380 {
381  nodeToRemove = nullptr;
382 
383  if (!m_part->xmlDocImpl()) {
384  return nullptr;
385  }
386 
387  EditorContext *ctx = m_part->editorContext();
388  if (ctx->m_selection.state() == Selection::NONE) {
389  return nullptr;
390  }
391 
392  Range range(ctx->m_selection.toRange());
393  Position pos(range.startContainer().handle(), range.startOffset());
394  assert(pos.notEmpty());
395  ElementImpl *elem = pos.element();
396  ElementImpl *styleElement = elem;
397  int exceptionCode = 0;
398 
399  if (m_typingStyle) {
400  styleElement = m_part->xmlDocImpl()->createHTMLElement("SPAN");
401 // assert(exceptionCode == 0);
402 
403  styleElement->setAttribute(ATTR_STYLE, m_typingStyle->cssText().implementation());
404 // assert(exceptionCode == 0);
405 
406  TextImpl *text = m_part->xmlDocImpl()->createEditingTextNode("");
407  styleElement->appendChild(text, exceptionCode);
408  assert(exceptionCode == 0);
409 
410  elem->appendChild(styleElement, exceptionCode);
411  assert(exceptionCode == 0);
412 
413  nodeToRemove = styleElement;
414  }
415 
416  return new RenderStyleDeclarationImpl(styleElement);
417 }
418 
419 PassRefPtr<EditCommandImpl> Editor::lastEditCommand() const
420 {
421  return d->m_lastEditCommand;
422 }
423 
424 void Editor::appliedEditing(EditCommandImpl *cmd)
425 {
426 #ifdef DEBUG_COMMANDS
427  // qCDebug(KHTML_LOG) << "[Applied editing]";
428 #endif
429  // make sure we have all the changes in rendering tree applied with relayout if needed before setting caret
430  // in particular that could be required for inline boxes recomputation when inserting text
431  m_part->xmlDocImpl()->updateLayout();
432 
433  m_part->setCaret(cmd->endingSelection(), false);
434  // Command will be equal to last edit command only in the case of typing
435  if (d->m_lastEditCommand == cmd) {
436  assert(cmd->isTypingCommand());
437  } else {
438  // Only register a new undo command if the command passed in is
439  // different from the last command
440  d->registerUndo(cmd);
441  d->m_lastEditCommand = cmd;
442  }
443  m_part->editorContext()->m_selection.setNeedsLayout(true);
444  m_part->selectionLayoutChanged();
445  // ### only emit if caret pos changed
446  m_part->emitCaretPositionChanged(cmd->endingSelection().caretPos());
447 }
448 
449 void Editor::unappliedEditing(EditCommandImpl *cmd)
450 {
451  // see comment in appliedEditing()
452  m_part->xmlDocImpl()->updateLayout();
453 
454  m_part->setCaret(cmd->startingSelection());
455  d->registerRedo(cmd);
456 #ifdef APPLE_CHANGES
457  KWQ(this)->respondToChangedContents();
458 #else
459  m_part->editorContext()->m_selection.setNeedsLayout(true);
460  m_part->selectionLayoutChanged();
461  // ### only emit if caret pos changed
462  m_part->emitCaretPositionChanged(cmd->startingSelection().caretPos());
463 #endif
464  d->m_lastEditCommand = nullptr;
465 }
466 
467 void Editor::reappliedEditing(EditCommandImpl *cmd)
468 {
469  // see comment in appliedEditing()
470  m_part->xmlDocImpl()->updateLayout();
471 
472  m_part->setCaret(cmd->endingSelection());
473  d->registerUndo(cmd, false /*clearRedoStack*/);
474 #ifdef APPLE_CHANGES
475  KWQ(this)->respondToChangedContents();
476 #else
477  m_part->selectionLayoutChanged();
478  // ### only emit if caret pos changed
479  m_part->emitCaretPositionChanged(cmd->endingSelection().caretPos());
480 #endif
481  d->m_lastEditCommand = nullptr;
482 }
483 
484 CSSStyleDeclarationImpl *Editor::typingStyle() const
485 {
486  return m_typingStyle;
487 }
488 
489 void Editor::setTypingStyle(CSSStyleDeclarationImpl *style)
490 {
491  CSSStyleDeclarationImpl *old = m_typingStyle;
492  m_typingStyle = style;
493  if (m_typingStyle) {
494  m_typingStyle->ref();
495  }
496  if (old) {
497  old->deref();
498  }
499 }
500 
502 {
503  setTypingStyle(nullptr);
504 }
505 
506 void Editor::closeTyping()
507 {
508  EditCommandImpl *lastCommand = lastEditCommand().get();
509  if (lastCommand && lastCommand->isTypingCommand()) {
510  static_cast<TypingCommandImpl *>(lastCommand)->closeTyping();
511  }
512 }
513 
515 {
516  RefPtr<IndentOutdentCommandImpl> command = new IndentOutdentCommandImpl(m_part->xmlDocImpl(),
517  IndentOutdentCommandImpl::Indent);
518  command->apply();
519 }
520 
521 void Editor::outdent()
522 {
523  RefPtr<IndentOutdentCommandImpl> command = new IndentOutdentCommandImpl(m_part->xmlDocImpl(),
524  IndentOutdentCommandImpl::Outdent);
525  command->apply();
526 }
527 
528 bool Editor::handleKeyEvent(QKeyEvent *_ke)
529 {
530  bool handled = false;
531 
532  bool ctrl = _ke->modifiers() & Qt::ControlModifier;
533  bool alt = _ke->modifiers() & Qt::AltModifier;
534  //bool shift = _ke->modifiers() & Qt::ShiftModifier;
535  bool meta = _ke->modifiers() & Qt::MetaModifier;
536 
537  if (ctrl || alt || meta) {
538  return false;
539  }
540 
541  switch (_ke->key()) {
542 
543  case Qt::Key_Delete: {
544  Selection selectionToDelete = m_part->caret();
545 #ifdef DEBUG_COMMANDS
546  // qCDebug(KHTML_LOG) << "========== KEY_DELETE ==========";
547 #endif
548  if (selectionToDelete.state() == Selection::CARET) {
549  Position pos(selectionToDelete.start());
550 #ifdef DEBUG_COMMANDS
551  // qCDebug(KHTML_LOG) << "pos.inLastEditableInRootEditableElement " << pos.inLastEditableInRootEditableElement() << " pos.offset " << pos.offset() << " pos.max " << pos.node()->caretMaxRenderedOffset();
552 #endif
553  if (pos.nextCharacterPosition() == pos) {
554  // we're at the end of a root editable block...do nothing
555 #ifdef DEBUG_COMMANDS
556  // qCDebug(KHTML_LOG) << "no delete!!!!!!!!!!";
557 #endif
558  break;
559  }
560  m_part->d->editor_context.m_selection
561  = Selection(pos, pos.nextCharacterPosition());
562  }
563  // fall through
564  }
565  case Qt::Key_Backspace:
566  TypingCommandImpl::deleteKeyPressed0(m_part->xmlDocImpl());
567  handled = true;
568  break;
569 
570  case Qt::Key_Return:
571  case Qt::Key_Enter:
572 // if (shift)
573  TypingCommandImpl::insertNewline0(m_part->xmlDocImpl());
574 // else
575 // TypingCommand::insertParagraph(m_part->xmlDocImpl());
576  handled = true;
577  break;
578 
579  case Qt::Key_Escape:
580  case Qt::Key_Insert:
581  // FIXME implement me
582  handled = true;
583  break;
584 
585  default:
586 // handle_input:
587  if (m_part->caret().state() != Selection::CARET) {
588  // We didn't get a chance to grab the caret, likely because
589  // a script messed with contentEditable in the middle of events
590  // acquire it now if there isn't a selection
591  // qCDebug(KHTML_LOG) << "Editable node w/o caret!";
592  DOM::NodeImpl *focus = m_part->xmlDocImpl()->focusNode();
593  if (m_part->caret().state() == Selection::NONE) {
594  if (focus) {
595  m_part->setCaret(Position(focus, focus->caretMinOffset()));
596  } else {
597  break;
598  }
599  }
600  }
601 
602  if (!_ke->text().isEmpty()) {
603  TypingCommandImpl::insertText0(m_part->xmlDocImpl(), _ke->text());
604  handled = true;
605  }
606 
607  }
608 
609  //if (handled) {
610  // ### check when to emit it
611 // m_part->emitSelectionChanged();
612  //}
613 
614  return handled;
615 
616 }
617 
DOMString queryCommandValue(const DOMString &command)
Returns the given command&#39;s value.
Definition: editor.cpp:144
void reappliedEditing(khtml::EditCommandImpl *)
Called when editing has been reapplied.
Definition: editor.cpp:467
ControlModifier
Qt::KeyboardModifiers modifiers() const const
EditorCommand
const T & next()
void redo()
redo last undone action
Definition: editor.cpp:215
bool execCommand(const DOMString &command, bool userInterface, const DOMString &value)
Executes the given editor command.
Definition: editor.cpp:114
bool queryCommandEnabled(const DOMString &command)
Checks whether the given command is enabled.
Definition: editor.cpp:120
This class is khtml&#39;s main class.
Definition: khtml_part.h:208
bool selectionStartHasStyle(DOM::CSSStyleDeclarationImpl *) const
returns whether the selection has got applied the given style
Definition: editor.cpp:324
DOM::CSSStyleDeclarationImpl * selectionComputedStyle(DOM::NodeImpl *&nodeToRemove) const
computed style of current selection
Definition: editor.cpp:379
bool canPaste() const
returns whether clipboard contains data to be pasted
Definition: editor.cpp:209
TriState
Tri-state boolean.
Definition: editor.h:74
bool queryCommandIndeterm(const DOMString &command)
Checks whether the given command&#39;s style is indeterminate.
Definition: editor.cpp:126
void copy()
copy selection to clipboard
Definition: editor.cpp:186
void unappliedEditing(khtml::EditCommandImpl *)
Called when editing has been unapplied.
Definition: editor.cpp:449
DOM::DOMString selectionStartStylePropertyValue(int stylePropertyID) const
?
Definition: editor.cpp:358
bool isEmpty() const const
QString text() const const
bool queryCommandState(const DOMString &command)
Checks whether the given command&#39;s style is state.
Definition: editor.cpp:132
This class implements the basic string we use in the DOM.
Definition: dom_string.h:44
void applyStyle(DOM::CSSStyleDeclarationImpl *)
applies the given style to the current selection
Definition: editor.cpp:243
bool canRedo() const
returns whether any actions can be redone
Definition: editor.cpp:233
int key() const const
void appliedEditing(khtml::EditCommandImpl *)
Called when editing has been applied.
Definition: editor.cpp:424
void cut()
cut selection and insert into clipboard
Definition: editor.cpp:191
This library provides a full-featured HTML parser and widget.
bool canUndo() const
returns whether any actions can be undone
Definition: editor.cpp:238
void indent()
indent/outdent current selection
Definition: editor.cpp:514
void print()
prints the current document
Definition: editor.cpp:204
void undo()
undo last action
Definition: editor.cpp:224
bool queryCommandSupported(const DOMString &command)
Checks whether the given command is supported in the current context.
Definition: editor.cpp:138
This is the BrowserExtension for a KHTMLPart document.
Definition: khtml_ext.h:45
DOM::CSSStyleDeclarationImpl * typingStyle() const
Returns the typing style for the document.
Definition: editor.cpp:484
WTF::PassRefPtr< khtml::EditCommandImpl > lastEditCommand() const
Returns the most recent edit command applied.
Definition: editor.cpp:419
Definition: css_base.h:371
void paste()
paste into current selection from clipboard
Definition: editor.cpp:197
void clearTypingStyle()
Clears the typing style for the document.
Definition: editor.cpp:501
TriState selectionHasStyle(DOM::CSSStyleDeclarationImpl *) const
returns whether the selection has got applied the given style
Definition: editor.cpp:284
void setTypingStyle(DOM::CSSStyleDeclarationImpl *)
Sets the typing style for the document.
Definition: editor.cpp:489
Key_Delete
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Oct 26 2021 22:48:01 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.