1 /* This file is part of the KDE project
3 * Copyright (C) 2004 Leo Savernik <l.savernik@aon.at>
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.
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.
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.
23 #include "edit_command.h"
24 #include "htmlediting.h"
25 #include "jsediting.h"
27 #include "css/css_renderstyledeclarationimpl.h"
28 #include "css/css_valueimpl.h"
29 #include "misc/htmlattrs.h"
30 #include "xml/dom_selection.h"
31 #include "xml/dom_docimpl.h"
32 #include "xml/dom_elementimpl.h"
33 #include "xml/dom_textimpl.h"
34 #include "xml/dom2_rangeimpl.h"
35 #include "khtml_part.h"
36 #include "khtml_ext.h"
37 #include "khtmlpart_p.h"
45 # define assert(x) Q_ASSERT(x)
48 #define PREPARE_JSEDITOR_CALL(command, retval) \
49 JSEditor *js = m_part->xmlDocImpl() ? m_part->xmlDocImpl()->jsEditor() : 0; \
50 if (!js) return retval; \
51 const CommandImp *imp = js->commandImp(command)
53 // --------------------------------------------------------------------------
57 static const int sMaxUndoSteps
= 1000;
61 void registerUndo( const khtml::EditCommand
& cmd
, bool clearRedoStack
= true ) {
62 if (m_undo
.count()>= sMaxUndoSteps
)
68 void registerRedo( const khtml::EditCommand
& cmd
) {
69 if (m_redo
.count()>= sMaxUndoSteps
)
73 khtml::EditCommand m_lastEditCommand
;
74 QStack
<khtml::EditCommand
> m_undo
;
75 QStack
<khtml::EditCommand
> m_redo
;
82 using khtml::ApplyStyleCommand
;
83 using khtml::EditorContext
;
84 using khtml::EditCommand
;
85 using khtml::RenderStyleDeclarationImpl
;
86 using khtml::TypingCommand
;
88 // ==========================================================================
90 Editor::Editor(KHTMLPart
*part
)
91 : d(new EditorPrivate
), m_typingStyle(0), m_part(part
) {
96 m_typingStyle
->deref();
100 bool Editor::execCommand(const DOMString
&command
, bool userInterface
, const DOMString
&value
)
102 PREPARE_JSEDITOR_CALL(command
, false);
103 return js
->execCommand(imp
, userInterface
, value
);
106 bool Editor::queryCommandEnabled(const DOMString
&command
)
108 PREPARE_JSEDITOR_CALL(command
, false);
109 return js
->queryCommandEnabled(imp
);
112 bool Editor::queryCommandIndeterm(const DOMString
&command
)
114 PREPARE_JSEDITOR_CALL(command
, false);
115 return js
->queryCommandIndeterm(imp
);
118 bool Editor::queryCommandState(const DOMString
&command
)
120 PREPARE_JSEDITOR_CALL(command
, false);
121 return js
->queryCommandState(imp
);
124 bool Editor::queryCommandSupported(const DOMString
&command
)
126 PREPARE_JSEDITOR_CALL(command
, false);
127 return js
->queryCommandSupported(imp
);
130 DOMString
Editor::queryCommandValue(const DOMString
&command
)
132 PREPARE_JSEDITOR_CALL(command
, DOMString());
133 return js
->queryCommandValue(imp
);
136 bool Editor::execCommand(EditorCommand command
, bool userInterface
, const DOMString
&value
)
138 PREPARE_JSEDITOR_CALL(command
, false);
139 return js
->execCommand(imp
, userInterface
, value
);
142 bool Editor::queryCommandEnabled(EditorCommand command
)
144 PREPARE_JSEDITOR_CALL(command
, false);
145 return js
->queryCommandEnabled(imp
);
148 bool Editor::queryCommandIndeterm(EditorCommand command
)
150 PREPARE_JSEDITOR_CALL(command
, false);
151 return js
->queryCommandIndeterm(imp
);
154 bool Editor::queryCommandState(EditorCommand command
)
156 PREPARE_JSEDITOR_CALL(command
, false);
157 return js
->queryCommandState(imp
);
160 bool Editor::queryCommandSupported(EditorCommand command
)
162 PREPARE_JSEDITOR_CALL(command
, false);
163 return js
->queryCommandSupported(imp
);
166 DOMString
Editor::queryCommandValue(EditorCommand command
)
168 PREPARE_JSEDITOR_CALL(command
, DOMString());
169 return js
->queryCommandValue(imp
);
174 static_cast<KHTMLPartBrowserExtension
*>(m_part
->browserExtension())->copy();
180 static_cast<KHTMLPartBrowserExtension
*>(m_part
->browserExtension())->cut();
187 // static_cast<KHTMLPartBrowserExtension*>(m_part->browserExtension())->paste();
192 static_cast<KHTMLPartBrowserExtension
*>(m_part
->browserExtension())->print();
195 bool Editor::canPaste() const
203 if (d
->m_redo
.isEmpty())
205 EditCommand e
= d
->m_redo
.pop();
211 if (d
->m_undo
.isEmpty())
213 EditCommand e
= d
->m_undo
.pop();
217 bool Editor::canRedo() const
219 return !d
->m_redo
.isEmpty();
222 bool Editor::canUndo() const
224 return !d
->m_undo
.isEmpty();
227 void Editor::applyStyle(CSSStyleDeclarationImpl
*style
)
229 switch (m_part
->caret().state()) {
230 case Selection::NONE
:
233 case Selection::CARET
:
234 // FIXME: This blows away all the other properties of the typing style.
235 setTypingStyle(style
);
237 case Selection::RANGE
:
238 if (m_part
->xmlDocImpl() && style
) {
239 ApplyStyleCommand
cmd(m_part
->xmlDocImpl(), style
);
246 static void updateState(CSSStyleDeclarationImpl
*desiredStyle
, CSSStyleDeclarationImpl
*computedStyle
, bool &atStart
, Editor::TriState
&state
)
248 QListIterator
<CSSProperty
*> it(*desiredStyle
->values());
249 while (it
.hasNext()) {
250 int propertyID
= it
.next()->id();
251 DOMString desiredProperty
= desiredStyle
->getPropertyValue(propertyID
);
252 DOMString computedProperty
= computedStyle
->getPropertyValue(propertyID
);
253 Editor::TriState propertyState
= strcasecmp(desiredProperty
, computedProperty
) == 0
254 ? Editor::TrueTriState
: Editor::FalseTriState
;
256 state
= propertyState
;
258 } else if (state
!= propertyState
) {
259 state
= Editor::MixedTriState
;
265 Editor::TriState
Editor::selectionHasStyle(CSSStyleDeclarationImpl
*style
) const
268 TriState state
= FalseTriState
;
270 EditorContext
*ctx
= m_part
->editorContext();
271 if (ctx
->m_selection
.state() != Selection::RANGE
) {
272 NodeImpl
*nodeToRemove
;
273 CSSStyleDeclarationImpl
*selectionStyle
= selectionComputedStyle(nodeToRemove
);
275 return FalseTriState
;
276 selectionStyle
->ref();
277 updateState(style
, selectionStyle
, atStart
, state
);
278 selectionStyle
->deref();
280 int exceptionCode
= 0;
281 nodeToRemove
->remove(exceptionCode
);
282 assert(exceptionCode
== 0);
285 for (NodeImpl
*node
= ctx
->m_selection
.start().node(); node
; node
= node
->traverseNextNode()) {
286 if (node
->isHTMLElement()) {
287 CSSStyleDeclarationImpl
*computedStyle
= new RenderStyleDeclarationImpl(node
);
288 computedStyle
->ref();
289 updateState(style
, computedStyle
, atStart
, state
);
290 computedStyle
->deref();
291 if (state
== MixedTriState
)
294 if (node
== ctx
->m_selection
.end().node())
302 bool Editor::selectionStartHasStyle(CSSStyleDeclarationImpl
*style
) const
304 NodeImpl
*nodeToRemove
;
305 CSSStyleDeclarationImpl
*selectionStyle
= selectionComputedStyle(nodeToRemove
);
309 selectionStyle
->ref();
313 QListIterator
<CSSProperty
*> it(*style
->values());
314 while (it
.hasNext()) {
315 int propertyID
= it
.next()->id();
316 DOMString desiredProperty
= style
->getPropertyValue(propertyID
);
317 DOMString selectionProperty
= selectionStyle
->getPropertyValue(propertyID
);
318 if (strcasecmp(selectionProperty
, desiredProperty
) != 0) {
324 selectionStyle
->deref();
327 int exceptionCode
= 0;
328 nodeToRemove
->remove(exceptionCode
);
329 assert(exceptionCode
== 0);
335 DOMString
Editor::selectionStartStylePropertyValue(int stylePropertyID
) const
337 NodeImpl
*nodeToRemove
;
338 CSSStyleDeclarationImpl
*selectionStyle
= selectionComputedStyle(nodeToRemove
);
342 selectionStyle
->ref();
343 DOMString value
= selectionStyle
->getPropertyValue(stylePropertyID
);
344 selectionStyle
->deref();
347 int exceptionCode
= 0;
348 nodeToRemove
->remove(exceptionCode
);
349 assert(exceptionCode
== 0);
355 CSSStyleDeclarationImpl
*Editor::selectionComputedStyle(NodeImpl
*&nodeToRemove
) const
359 if (!m_part
->xmlDocImpl())
362 EditorContext
*ctx
= m_part
->editorContext();
363 if (ctx
->m_selection
.state() == Selection::NONE
)
366 Range
range(ctx
->m_selection
.toRange());
367 Position
pos(range
.startContainer().handle(), range
.startOffset());
368 assert(pos
.notEmpty());
369 ElementImpl
*elem
= pos
.element();
370 ElementImpl
*styleElement
= elem
;
371 int exceptionCode
= 0;
374 styleElement
= m_part
->xmlDocImpl()->createHTMLElement("SPAN");
375 // assert(exceptionCode == 0);
377 styleElement
->setAttribute(ATTR_STYLE
, m_typingStyle
->cssText().implementation());
378 // assert(exceptionCode == 0);
380 TextImpl
*text
= m_part
->xmlDocImpl()->createEditingTextNode("");
381 styleElement
->appendChild(text
, exceptionCode
);
382 assert(exceptionCode
== 0);
384 elem
->appendChild(styleElement
, exceptionCode
);
385 assert(exceptionCode
== 0);
387 nodeToRemove
= styleElement
;
390 return new RenderStyleDeclarationImpl(styleElement
);
393 EditCommand
Editor::lastEditCommand() const
395 return d
->m_lastEditCommand
;
398 void Editor::appliedEditing(EditCommand
&cmd
)
400 // make sure we have all the changes in rendering tree applied with relayout if needed before setting caret
401 // in particular that could be required for inline boxes recomputation when inserting text
402 m_part
->xmlDocImpl()->updateLayout();
404 m_part
->setCaret(cmd
.endingSelection(), false);
405 // Command will be equal to last edit command only in the case of typing
406 if (d
->m_lastEditCommand
== cmd
) {
407 assert(cmd
.commandID() == khtml::TypingCommandID
);
410 // Only register a new undo command if the command passed in is
411 // different from the last command
412 d
->registerUndo( cmd
);
413 d
->m_lastEditCommand
= cmd
;
415 m_part
->selectionLayoutChanged();
416 // ### only emit if caret pos changed
417 m_part
->emitCaretPositionChanged(cmd
.endingSelection().caretPos());
420 void Editor::unappliedEditing(EditCommand
&cmd
)
422 // see comment in appliedEditing()
423 m_part
->xmlDocImpl()->updateLayout();
425 m_part
->setCaret(cmd
.startingSelection());
426 d
->registerRedo( cmd
);
428 KWQ(this)->respondToChangedContents();
430 m_part
->selectionLayoutChanged();
431 // ### only emit if caret pos changed
432 m_part
->emitCaretPositionChanged(cmd
.startingSelection().caretPos());
434 d
->m_lastEditCommand
= EditCommand::emptyCommand();
437 void Editor::reappliedEditing(EditCommand
&cmd
)
439 // see comment in appliedEditing()
440 m_part
->xmlDocImpl()->updateLayout();
442 m_part
->setCaret(cmd
.endingSelection());
443 d
->registerUndo( cmd
, false /*clearRedoStack*/ );
445 KWQ(this)->respondToChangedContents();
447 m_part
->selectionLayoutChanged();
448 // ### only emit if caret pos changed
449 m_part
->emitCaretPositionChanged(cmd
.endingSelection().caretPos());
451 d
->m_lastEditCommand
= EditCommand::emptyCommand();
454 CSSStyleDeclarationImpl
*Editor::typingStyle() const
456 return m_typingStyle
;
459 void Editor::setTypingStyle(CSSStyleDeclarationImpl
*style
)
461 CSSStyleDeclarationImpl
*old
= m_typingStyle
;
462 m_typingStyle
= style
;
464 m_typingStyle
->ref();
469 void Editor::clearTypingStyle()
474 bool Editor::handleKeyEvent(QKeyEvent
*_ke
)
476 bool handled
= false;
478 bool ctrl
= _ke
->modifiers() & Qt::ControlModifier
;
479 bool alt
= _ke
->modifiers() & Qt::AltModifier
;
480 bool shift
= _ke
->modifiers() & Qt::ShiftModifier
;
481 bool meta
= _ke
->modifiers() & Qt::MetaModifier
;
483 if (ctrl
|| alt
|| meta
) {
489 case Qt::Key_Delete
: {
490 Selection selectionToDelete
= m_part
->caret();
491 kDebug(6200) << "========== KEY_DELETE ==========" << endl
;
492 if (selectionToDelete
.state() == Selection::CARET
) {
493 Position
pos(selectionToDelete
.start());
494 kDebug(6200) << "pos.inLastEditableInRootEditableElement " << pos
.inLastEditableInRootEditableElement() << " pos.offset " << pos
.offset() << " pos.max " << pos
.node()->caretMaxRenderedOffset() << endl
;
495 if (pos
.nextCharacterPosition() == pos
) {
496 // we're at the end of a root editable block...do nothing
497 kDebug(6200) << "no delete!!!!!!!!!!" << endl
;
500 m_part
->d
->editor_context
.m_selection
501 = Selection(pos
, pos
.nextCharacterPosition());
505 case Qt::Key_Backspace
:
506 TypingCommand::deleteKeyPressed(m_part
->xmlDocImpl());
513 TypingCommand::insertNewline(m_part
->xmlDocImpl());
515 // TypingCommand::insertParagraph(m_part->xmlDocImpl());
521 // FIXME implement me
527 if (!_ke
->text().isEmpty()) {
528 TypingCommand::insertText(m_part
->xmlDocImpl(), _ke
->text());
535 // ### check when to emit it
536 // m_part->emitSelectionChanged();
544 #include "editor.moc"