/src/libreoffice/vcl/source/edit/textview.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
5 | | * This Source Code Form is subject to the terms of the Mozilla Public |
6 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
7 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
8 | | * |
9 | | * This file incorporates work covered by the following license notice: |
10 | | * |
11 | | * Licensed to the Apache Software Foundation (ASF) under one or more |
12 | | * contributor license agreements. See the NOTICE file distributed |
13 | | * with this work for additional information regarding copyright |
14 | | * ownership. The ASF licenses this file to you under the Apache |
15 | | * License, Version 2.0 (the "License"); you may not use this file |
16 | | * except in compliance with the License. You may obtain a copy of |
17 | | * the License at http://www.apache.org/licenses/LICENSE-2.0 . |
18 | | */ |
19 | | |
20 | | #include <memory> |
21 | | #include <i18nutil/searchopt.hxx> |
22 | | #include <o3tl/string_view.hxx> |
23 | | #include <utility> |
24 | | #include <vcl/dndlistenercontainer.hxx> |
25 | | #include <vcl/textview.hxx> |
26 | | #include <vcl/texteng.hxx> |
27 | | #include <vcl/settings.hxx> |
28 | | #include "textdoc.hxx" |
29 | | #include <vcl/textdata.hxx> |
30 | | #include <vcl/transfer.hxx> |
31 | | #include <vcl/xtextedt.hxx> |
32 | | #include "textdat2.hxx" |
33 | | #include <vcl/commandevent.hxx> |
34 | | #include <vcl/inputctx.hxx> |
35 | | |
36 | | #include <svl/undo.hxx> |
37 | | #include <vcl/weld/Window.hxx> |
38 | | #include <vcl/window.hxx> |
39 | | #include <vcl/svapp.hxx> |
40 | | #include <tools/stream.hxx> |
41 | | |
42 | | #include <sal/log.hxx> |
43 | | #include <sot/formats.hxx> |
44 | | |
45 | | #include <cppuhelper/weak.hxx> |
46 | | #include <cppuhelper/queryinterface.hxx> |
47 | | #include <com/sun/star/i18n/XBreakIterator.hpp> |
48 | | #include <com/sun/star/i18n/CharacterIteratorMode.hpp> |
49 | | #include <com/sun/star/i18n/WordType.hpp> |
50 | | #include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp> |
51 | | #include <com/sun/star/datatransfer/XTransferable.hpp> |
52 | | #include <com/sun/star/datatransfer/clipboard/XClipboard.hpp> |
53 | | #include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp> |
54 | | #include <com/sun/star/datatransfer/dnd/DNDConstants.hpp> |
55 | | #include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp> |
56 | | #include <com/sun/star/datatransfer/dnd/XDropTarget.hpp> |
57 | | #include <com/sun/star/util/SearchFlags.hpp> |
58 | | |
59 | | #include <vcl/toolkit/edit.hxx> |
60 | | |
61 | | #include <sot/exchange.hxx> |
62 | | |
63 | | #include "TextLine.hxx" |
64 | | |
65 | | #include <algorithm> |
66 | | #include <cstddef> |
67 | | |
68 | 0 | TETextDataObject::TETextDataObject( OUString aText ) : maText(std::move( aText )) |
69 | 0 | { |
70 | 0 | } |
71 | | |
72 | | // css::uno::XInterface |
73 | | css::uno::Any TETextDataObject::queryInterface( const css::uno::Type & rType ) |
74 | 0 | { |
75 | 0 | css::uno::Any aRet = ::cppu::queryInterface( rType, static_cast< css::datatransfer::XTransferable* >(this) ); |
76 | 0 | return (aRet.hasValue() ? aRet : OWeakObject::queryInterface( rType )); |
77 | 0 | } |
78 | | |
79 | | // css::datatransfer::XTransferable |
80 | | css::uno::Any TETextDataObject::getTransferData( const css::datatransfer::DataFlavor& rFlavor ) |
81 | 0 | { |
82 | 0 | css::uno::Any aAny; |
83 | |
|
84 | 0 | SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor ); |
85 | 0 | if ( nT == SotClipboardFormatId::STRING ) |
86 | 0 | { |
87 | 0 | aAny <<= maText; |
88 | 0 | } |
89 | 0 | else if ( nT == SotClipboardFormatId::HTML ) |
90 | 0 | { |
91 | 0 | sal_uInt64 nLen = GetHTMLStream().TellEnd(); |
92 | 0 | GetHTMLStream().Seek(0); |
93 | |
|
94 | 0 | css::uno::Sequence< sal_Int8 > aSeq( nLen ); |
95 | 0 | memcpy( aSeq.getArray(), GetHTMLStream().GetData(), nLen ); |
96 | 0 | aAny <<= aSeq; |
97 | 0 | } |
98 | 0 | else |
99 | 0 | { |
100 | 0 | throw css::datatransfer::UnsupportedFlavorException(); |
101 | 0 | } |
102 | 0 | return aAny; |
103 | 0 | } |
104 | | |
105 | | css::uno::Sequence< css::datatransfer::DataFlavor > TETextDataObject::getTransferDataFlavors( ) |
106 | 0 | { |
107 | 0 | GetHTMLStream().Seek( STREAM_SEEK_TO_END ); |
108 | 0 | bool bHTML = GetHTMLStream().Tell() > 0; |
109 | 0 | css::uno::Sequence< css::datatransfer::DataFlavor > aDataFlavors( bHTML ? 2 : 1 ); |
110 | 0 | SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aDataFlavors.getArray()[0] ); |
111 | 0 | if ( bHTML ) |
112 | 0 | SotExchange::GetFormatDataFlavor( SotClipboardFormatId::HTML, aDataFlavors.getArray()[1] ); |
113 | 0 | return aDataFlavors; |
114 | 0 | } |
115 | | |
116 | | sal_Bool TETextDataObject::isDataFlavorSupported( const css::datatransfer::DataFlavor& rFlavor ) |
117 | 0 | { |
118 | 0 | SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor ); |
119 | 0 | return ( nT == SotClipboardFormatId::STRING ); |
120 | 0 | } |
121 | | |
122 | | TextView::TextView(ExtTextEngine* pEng, vcl::Window* pWindow) |
123 | 0 | { |
124 | 0 | pWindow->EnableRTL( false ); |
125 | |
|
126 | 0 | mpWindow = pWindow; |
127 | 0 | mpTextEngine = pEng; |
128 | |
|
129 | 0 | mbPaintSelection = true; |
130 | 0 | mbAutoScroll = true; |
131 | 0 | mbInsertMode = true; |
132 | 0 | mbReadOnly = false; |
133 | 0 | mbAutoIndent = false; |
134 | 0 | mbCursorEnabled = true; |
135 | 0 | mbClickedInSelection = false; |
136 | 0 | mbCursorAtEndOfLine = false; |
137 | | // mbInSelection = false; |
138 | |
|
139 | 0 | mnTravelXPos = TRAVEL_X_DONTKNOW; |
140 | |
|
141 | 0 | mpSelFuncSet = std::make_unique<TextSelFunctionSet>(this); |
142 | 0 | mpSelEngine = std::make_unique<SelectionEngine>(mpWindow, mpSelFuncSet.get()); |
143 | 0 | mpSelEngine->SetSelectionMode(SelectionMode::Range); |
144 | 0 | mpSelEngine->EnableDrag(true); |
145 | |
|
146 | 0 | mpCursor.reset(new vcl::Cursor); |
147 | 0 | mpCursor->Show(); |
148 | 0 | pWindow->SetCursor(mpCursor.get()); |
149 | 0 | pWindow->SetInputContext( InputContext( pEng->GetFont(), InputContextFlags::Text|InputContextFlags::ExtText ) ); |
150 | |
|
151 | 0 | pWindow->GetOutDev()->SetLineColor(); |
152 | |
|
153 | 0 | if (pWindow->GetDropTarget().is()) |
154 | 0 | { |
155 | 0 | mxDnDListener = new vcl::unohelper::DragAndDropWrapper(this); |
156 | |
|
157 | 0 | pWindow->GetDropTarget()->addDragGestureListener(mxDnDListener); |
158 | 0 | pWindow->GetDropTarget()->addDropTargetListener(mxDnDListener); |
159 | 0 | pWindow->GetDropTarget()->setActive( true ); |
160 | 0 | pWindow->GetDropTarget()->setDefaultActions( css::datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE ); |
161 | 0 | } |
162 | 0 | } |
163 | | |
164 | | TextView::~TextView() |
165 | 0 | { |
166 | 0 | mpSelEngine.reset(); |
167 | 0 | mpSelFuncSet.reset(); |
168 | |
|
169 | 0 | if (mpWindow->GetCursor() == mpCursor.get()) |
170 | 0 | mpWindow->SetCursor(nullptr); |
171 | |
|
172 | 0 | mpCursor.reset(); |
173 | 0 | mpDDInfo.reset(); |
174 | 0 | } |
175 | | |
176 | | void TextView::Invalidate() |
177 | 0 | { |
178 | 0 | mpWindow->Invalidate(); |
179 | 0 | } |
180 | | |
181 | | void TextView::SetSelection( const TextSelection& rTextSel, bool bGotoCursor ) |
182 | 0 | { |
183 | | // if someone left an empty attribute and then the Outliner manipulated the selection |
184 | 0 | if (!maSelection.HasRange()) |
185 | 0 | mpTextEngine->CursorMoved(maSelection.GetStart().GetPara()); |
186 | | |
187 | | // if the selection is manipulated after a KeyInput |
188 | 0 | mpTextEngine->CheckIdleFormatter(); |
189 | |
|
190 | 0 | HideSelection(); |
191 | 0 | TextSelection aNewSel( rTextSel ); |
192 | 0 | mpTextEngine->ValidateSelection(aNewSel); |
193 | 0 | ImpSetSelection( aNewSel ); |
194 | 0 | ShowSelection(); |
195 | 0 | ShowCursor( bGotoCursor ); |
196 | 0 | } |
197 | | |
198 | | void TextView::SetSelection( const TextSelection& rTextSel ) |
199 | 0 | { |
200 | 0 | SetSelection(rTextSel, mbAutoScroll); |
201 | 0 | } |
202 | | |
203 | | const TextSelection& TextView::GetSelection() const |
204 | 0 | { |
205 | 0 | return maSelection; |
206 | 0 | } |
207 | | |
208 | | TextSelection& TextView::GetSelection() |
209 | 0 | { |
210 | 0 | return maSelection; |
211 | 0 | } |
212 | | |
213 | | void TextView::DeleteSelected() |
214 | 0 | { |
215 | | // HideSelection(); |
216 | |
|
217 | 0 | mpTextEngine->UndoActionStart(); |
218 | 0 | TextPaM aPaM = mpTextEngine->ImpDeleteText(maSelection); |
219 | 0 | mpTextEngine->UndoActionEnd(); |
220 | |
|
221 | 0 | ImpSetSelection( aPaM ); |
222 | 0 | mpTextEngine->FormatAndUpdate(this); |
223 | 0 | ShowCursor(); |
224 | 0 | } |
225 | | |
226 | | void TextView::ImpPaint(vcl::RenderContext& rRenderContext, const Point& rStartPos, tools::Rectangle const* pPaintArea, TextSelection const* pSelection) |
227 | 0 | { |
228 | 0 | if (!mbPaintSelection) |
229 | 0 | { |
230 | 0 | pSelection = nullptr; |
231 | 0 | } |
232 | 0 | else |
233 | 0 | { |
234 | | // set correct background color; |
235 | | // unfortunately we cannot detect if it has changed |
236 | 0 | vcl::Font aFont = mpTextEngine->GetFont(); |
237 | 0 | Color aColor = rRenderContext.GetBackground().GetColor(); |
238 | 0 | aColor.SetAlpha(255); |
239 | 0 | if (aColor != aFont.GetFillColor()) |
240 | 0 | { |
241 | 0 | if (aFont.IsTransparent()) |
242 | 0 | aColor = COL_TRANSPARENT; |
243 | 0 | aFont.SetFillColor(aColor); |
244 | 0 | mpTextEngine->maFont = std::move(aFont); |
245 | 0 | } |
246 | 0 | } |
247 | |
|
248 | 0 | mpTextEngine->ImpPaint(&rRenderContext, rStartPos, pPaintArea, pSelection); |
249 | 0 | } |
250 | | |
251 | | void TextView::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) |
252 | 0 | { |
253 | 0 | ImpPaint(rRenderContext, rRect); |
254 | 0 | } |
255 | | |
256 | | void TextView::ImpPaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) |
257 | 0 | { |
258 | 0 | if (!mpTextEngine->GetUpdateMode() || mpTextEngine->IsInUndo()) |
259 | 0 | return; |
260 | | |
261 | 0 | TextSelection *pDrawSelection = nullptr; |
262 | 0 | if (maSelection.HasRange()) |
263 | 0 | pDrawSelection = &maSelection; |
264 | |
|
265 | 0 | Point aStartPos = ImpGetOutputStartPos(maStartDocPos); |
266 | 0 | ImpPaint(rRenderContext, aStartPos, &rRect, pDrawSelection); |
267 | 0 | } |
268 | | |
269 | | void TextView::ImpSetSelection( const TextSelection& rSelection ) |
270 | 0 | { |
271 | 0 | if (rSelection == maSelection) |
272 | 0 | return; |
273 | | |
274 | 0 | bool bCaret = false, bSelection = false; |
275 | 0 | const TextPaM &rEnd = rSelection.GetEnd(); |
276 | 0 | const TextPaM& rOldEnd = maSelection.GetEnd(); |
277 | 0 | bool bGap = rSelection.HasRange(), bOldGap = maSelection.HasRange(); |
278 | 0 | if (rEnd != rOldEnd) |
279 | 0 | bCaret = true; |
280 | 0 | if (bGap || bOldGap) |
281 | 0 | bSelection = true; |
282 | |
|
283 | 0 | maSelection = rSelection; |
284 | |
|
285 | 0 | if (bSelection) |
286 | 0 | mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewSelectionChanged)); |
287 | |
|
288 | 0 | if (bCaret) |
289 | 0 | mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewCaretChanged)); |
290 | 0 | } |
291 | | |
292 | | void TextView::ShowSelection() |
293 | 0 | { |
294 | 0 | ImpShowHideSelection(); |
295 | 0 | } |
296 | | |
297 | | void TextView::HideSelection() |
298 | 0 | { |
299 | 0 | ImpShowHideSelection(); |
300 | 0 | } |
301 | | |
302 | | void TextView::ShowSelection( const TextSelection& rRange ) |
303 | 0 | { |
304 | 0 | ImpShowHideSelection( &rRange ); |
305 | 0 | } |
306 | | |
307 | | void TextView::ImpShowHideSelection(const TextSelection* pRange) |
308 | 0 | { |
309 | 0 | const TextSelection* pRangeOrSelection = pRange ? pRange : &maSelection; |
310 | |
|
311 | 0 | if ( !pRangeOrSelection->HasRange() ) |
312 | 0 | return; |
313 | | |
314 | 0 | if (mpWindow->IsPaintTransparent()) |
315 | 0 | mpWindow->Invalidate(); |
316 | 0 | else |
317 | 0 | { |
318 | 0 | TextSelection aRange( *pRangeOrSelection ); |
319 | 0 | aRange.Justify(); |
320 | 0 | bool bVisCursor = mpCursor->IsVisible(); |
321 | 0 | mpCursor->Hide(); |
322 | 0 | Invalidate(); |
323 | 0 | if (bVisCursor) |
324 | 0 | mpCursor->Show(); |
325 | 0 | } |
326 | 0 | } |
327 | | |
328 | | bool TextView::KeyInput( const KeyEvent& rKeyEvent ) |
329 | 0 | { |
330 | 0 | bool bDone = true; |
331 | 0 | bool bModified = false; |
332 | 0 | bool bMoved = false; |
333 | 0 | bool bEndKey = false; // special CursorPosition |
334 | 0 | bool bAllowIdle = true; |
335 | | |
336 | | // check mModified; |
337 | | // the local bModified is not set e.g. by Cut/Paste, as here |
338 | | // the update happens somewhere else |
339 | 0 | bool bWasModified = mpTextEngine->IsModified(); |
340 | 0 | mpTextEngine->SetModified(false); |
341 | |
|
342 | 0 | TextSelection aCurSel(maSelection); |
343 | 0 | TextSelection aOldSel( aCurSel ); |
344 | |
|
345 | 0 | sal_uInt16 nCode = rKeyEvent.GetKeyCode().GetCode(); |
346 | 0 | KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction(); |
347 | 0 | if ( eFunc != KeyFuncType::DONTKNOW ) |
348 | 0 | { |
349 | 0 | switch ( eFunc ) |
350 | 0 | { |
351 | 0 | case KeyFuncType::CUT: |
352 | 0 | { |
353 | 0 | if (!mbReadOnly) |
354 | 0 | Cut(); |
355 | 0 | } |
356 | 0 | break; |
357 | 0 | case KeyFuncType::COPY: |
358 | 0 | { |
359 | 0 | Copy(); |
360 | 0 | } |
361 | 0 | break; |
362 | 0 | case KeyFuncType::PASTE: |
363 | 0 | { |
364 | 0 | if (!mbReadOnly) |
365 | 0 | Paste(); |
366 | 0 | } |
367 | 0 | break; |
368 | 0 | case KeyFuncType::UNDO: |
369 | 0 | { |
370 | 0 | if (!mbReadOnly) |
371 | 0 | Undo(); |
372 | 0 | } |
373 | 0 | break; |
374 | 0 | case KeyFuncType::REDO: |
375 | 0 | { |
376 | 0 | if (!mbReadOnly) |
377 | 0 | Redo(); |
378 | 0 | } |
379 | 0 | break; |
380 | | |
381 | 0 | default: // might get processed below |
382 | 0 | eFunc = KeyFuncType::DONTKNOW; |
383 | 0 | } |
384 | 0 | } |
385 | 0 | if ( eFunc == KeyFuncType::DONTKNOW ) |
386 | 0 | { |
387 | 0 | switch ( nCode ) |
388 | 0 | { |
389 | 0 | case KEY_UP: |
390 | 0 | case KEY_DOWN: |
391 | 0 | case KEY_LEFT: |
392 | 0 | case KEY_RIGHT: |
393 | 0 | case KEY_HOME: |
394 | 0 | case KEY_END: |
395 | 0 | case KEY_PAGEUP: |
396 | 0 | case KEY_PAGEDOWN: |
397 | 0 | case css::awt::Key::MOVE_WORD_FORWARD: |
398 | 0 | case css::awt::Key::SELECT_WORD_FORWARD: |
399 | 0 | case css::awt::Key::MOVE_WORD_BACKWARD: |
400 | 0 | case css::awt::Key::SELECT_WORD_BACKWARD: |
401 | 0 | case css::awt::Key::MOVE_TO_BEGIN_OF_LINE: |
402 | 0 | case css::awt::Key::MOVE_TO_END_OF_LINE: |
403 | 0 | case css::awt::Key::SELECT_TO_BEGIN_OF_LINE: |
404 | 0 | case css::awt::Key::SELECT_TO_END_OF_LINE: |
405 | 0 | case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH: |
406 | 0 | case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH: |
407 | 0 | case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH: |
408 | 0 | case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH: |
409 | 0 | case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT: |
410 | 0 | case css::awt::Key::MOVE_TO_END_OF_DOCUMENT: |
411 | 0 | case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT: |
412 | 0 | case css::awt::Key::SELECT_TO_END_OF_DOCUMENT: |
413 | 0 | { |
414 | 0 | if ( ( !rKeyEvent.GetKeyCode().IsMod2() || ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) ) |
415 | 0 | && !( rKeyEvent.GetKeyCode().IsMod1() && ( nCode == KEY_PAGEUP || nCode == KEY_PAGEDOWN ) ) ) |
416 | 0 | { |
417 | 0 | aCurSel = ImpMoveCursor( rKeyEvent ); |
418 | 0 | if ( aCurSel.HasRange() ) { |
419 | 0 | css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection()); |
420 | 0 | Copy( aSelection ); |
421 | 0 | } |
422 | 0 | bMoved = true; |
423 | 0 | if ( nCode == KEY_END ) |
424 | 0 | bEndKey = true; |
425 | 0 | } |
426 | 0 | else |
427 | 0 | bDone = false; |
428 | 0 | } |
429 | 0 | break; |
430 | 0 | case KEY_BACKSPACE: |
431 | 0 | case KEY_DELETE: |
432 | 0 | case css::awt::Key::DELETE_WORD_BACKWARD: |
433 | 0 | case css::awt::Key::DELETE_WORD_FORWARD: |
434 | 0 | case css::awt::Key::DELETE_TO_BEGIN_OF_LINE: |
435 | 0 | case css::awt::Key::DELETE_TO_END_OF_LINE: |
436 | 0 | { |
437 | 0 | if (!mbReadOnly && !rKeyEvent.GetKeyCode().IsMod2()) |
438 | 0 | { |
439 | 0 | sal_uInt8 nDel = ( nCode == KEY_DELETE ) ? DEL_RIGHT : DEL_LEFT; |
440 | 0 | sal_uInt8 nMode = rKeyEvent.GetKeyCode().IsMod1() ? DELMODE_RESTOFWORD : DELMODE_SIMPLE; |
441 | 0 | if ( ( nMode == DELMODE_RESTOFWORD ) && rKeyEvent.GetKeyCode().IsShift() ) |
442 | 0 | nMode = DELMODE_RESTOFCONTENT; |
443 | |
|
444 | 0 | switch( nCode ) |
445 | 0 | { |
446 | 0 | case css::awt::Key::DELETE_WORD_BACKWARD: |
447 | 0 | nDel = DEL_LEFT; |
448 | 0 | nMode = DELMODE_RESTOFWORD; |
449 | 0 | break; |
450 | 0 | case css::awt::Key::DELETE_WORD_FORWARD: |
451 | 0 | nDel = DEL_RIGHT; |
452 | 0 | nMode = DELMODE_RESTOFWORD; |
453 | 0 | break; |
454 | 0 | case css::awt::Key::DELETE_TO_BEGIN_OF_LINE: |
455 | 0 | nDel = DEL_LEFT; |
456 | 0 | nMode = DELMODE_RESTOFCONTENT; |
457 | 0 | break; |
458 | 0 | case css::awt::Key::DELETE_TO_END_OF_LINE: |
459 | 0 | nDel = DEL_RIGHT; |
460 | 0 | nMode = DELMODE_RESTOFCONTENT; |
461 | 0 | break; |
462 | 0 | default: break; |
463 | 0 | } |
464 | | |
465 | 0 | mpTextEngine->UndoActionStart(); |
466 | 0 | aCurSel = ImpDelete( nDel, nMode ); |
467 | 0 | mpTextEngine->UndoActionEnd(); |
468 | 0 | bModified = true; |
469 | 0 | bAllowIdle = false; |
470 | 0 | } |
471 | 0 | else |
472 | 0 | bDone = false; |
473 | 0 | } |
474 | 0 | break; |
475 | 0 | case KEY_TAB: |
476 | 0 | { |
477 | 0 | if ( !mbReadOnly && !rKeyEvent.GetKeyCode().IsShift() && |
478 | 0 | !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() && |
479 | 0 | ImplCheckTextLen( u"x" ) ) |
480 | 0 | { |
481 | 0 | aCurSel = mpTextEngine->ImpInsertText(aCurSel, '\t', !IsInsertMode()); |
482 | 0 | bModified = true; |
483 | 0 | } |
484 | 0 | else |
485 | 0 | bDone = false; |
486 | 0 | } |
487 | 0 | break; |
488 | 0 | case KEY_RETURN: |
489 | 0 | { |
490 | | // do not swallow Shift-RETURN, as this would disable multi-line entries |
491 | | // in dialogs & property editors |
492 | 0 | if ( !mbReadOnly && !rKeyEvent.GetKeyCode().IsMod1() && |
493 | 0 | !rKeyEvent.GetKeyCode().IsMod2() && ImplCheckTextLen( u"x" ) ) |
494 | 0 | { |
495 | 0 | mpTextEngine->UndoActionStart(); |
496 | 0 | aCurSel = mpTextEngine->ImpInsertParaBreak(aCurSel); |
497 | 0 | if (mbAutoIndent) |
498 | 0 | { |
499 | 0 | TextNode* pPrev = mpTextEngine->mpDoc->GetNodes()[aCurSel.GetEnd().GetPara() - 1].get(); |
500 | 0 | sal_Int32 n = 0; |
501 | 0 | while ( ( n < pPrev->GetText().getLength() ) && ( |
502 | 0 | ( pPrev->GetText()[ n ] == ' ' ) || |
503 | 0 | ( pPrev->GetText()[ n ] == '\t' ) ) ) |
504 | 0 | { |
505 | 0 | n++; |
506 | 0 | } |
507 | 0 | if ( n ) |
508 | 0 | aCurSel |
509 | 0 | = mpTextEngine->ImpInsertText(aCurSel, pPrev->GetText().copy(0, n)); |
510 | 0 | } |
511 | 0 | mpTextEngine->UndoActionEnd(); |
512 | 0 | bModified = true; |
513 | 0 | } |
514 | 0 | else |
515 | 0 | bDone = false; |
516 | 0 | } |
517 | 0 | break; |
518 | 0 | case KEY_INSERT: |
519 | 0 | { |
520 | 0 | if (!mbReadOnly) |
521 | 0 | SetInsertMode( !IsInsertMode() ); |
522 | 0 | } |
523 | 0 | break; |
524 | 0 | default: |
525 | 0 | { |
526 | 0 | if ( TextEngine::IsSimpleCharInput( rKeyEvent ) ) |
527 | 0 | { |
528 | 0 | sal_Unicode nCharCode = rKeyEvent.GetCharCode(); |
529 | 0 | if (!mbReadOnly && ImplCheckTextLen(OUStringChar(nCharCode))) // otherwise swallow the character anyway |
530 | 0 | { |
531 | 0 | aCurSel = mpTextEngine->ImpInsertText(nCharCode, aCurSel, !IsInsertMode(), true); |
532 | 0 | bModified = true; |
533 | 0 | } |
534 | 0 | } |
535 | 0 | else |
536 | 0 | bDone = false; |
537 | 0 | } |
538 | 0 | } |
539 | 0 | } |
540 | | |
541 | 0 | if ( aCurSel != aOldSel ) // Check if changed, maybe other method already changed maSelection, don't overwrite that! |
542 | 0 | ImpSetSelection( aCurSel ); |
543 | |
|
544 | 0 | if ( ( nCode != KEY_UP ) && ( nCode != KEY_DOWN ) ) |
545 | 0 | mnTravelXPos = TRAVEL_X_DONTKNOW; |
546 | |
|
547 | 0 | if ( bModified ) |
548 | 0 | { |
549 | | // Idle-Formatter only if AnyInput |
550 | 0 | if ( bAllowIdle && Application::AnyInput( VclInputFlags::KEYBOARD) ) |
551 | 0 | mpTextEngine->IdleFormatAndUpdate(this); |
552 | 0 | else |
553 | 0 | mpTextEngine->FormatAndUpdate(this); |
554 | 0 | } |
555 | 0 | else if ( bMoved ) |
556 | 0 | { |
557 | | // selection is painted now in ImpMoveCursor |
558 | 0 | ImpShowCursor(mbAutoScroll, true, bEndKey); |
559 | 0 | } |
560 | |
|
561 | 0 | if (mpTextEngine->IsModified()) |
562 | 0 | mpTextEngine->Broadcast(TextHint(SfxHintId::TextModified)); |
563 | 0 | else if ( bWasModified ) |
564 | 0 | mpTextEngine->SetModified(true); |
565 | |
|
566 | 0 | return bDone; |
567 | 0 | } |
568 | | |
569 | | void TextView::MouseButtonUp( const MouseEvent& rMouseEvent ) |
570 | 0 | { |
571 | 0 | mbClickedInSelection = false; |
572 | 0 | mnTravelXPos = TRAVEL_X_DONTKNOW; |
573 | 0 | mpSelEngine->SelMouseButtonUp(rMouseEvent); |
574 | 0 | if ( rMouseEvent.IsMiddle() && !IsReadOnly() && |
575 | 0 | ( GetWindow()->GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) ) |
576 | 0 | { |
577 | 0 | css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection()); |
578 | 0 | Paste( aSelection ); |
579 | 0 | if (mpTextEngine->IsModified()) |
580 | 0 | mpTextEngine->Broadcast(TextHint(SfxHintId::TextModified)); |
581 | 0 | } |
582 | 0 | else if ( rMouseEvent.IsLeft() && GetSelection().HasRange() ) |
583 | 0 | { |
584 | 0 | css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection()); |
585 | 0 | Copy( aSelection ); |
586 | 0 | } |
587 | 0 | } |
588 | | |
589 | | void TextView::MouseButtonDown( const MouseEvent& rMouseEvent ) |
590 | 0 | { |
591 | 0 | mpTextEngine->CheckIdleFormatter(); // for fast typing and MouseButtonDown |
592 | 0 | mnTravelXPos = TRAVEL_X_DONTKNOW; |
593 | 0 | mbClickedInSelection = IsSelectionAtPoint(rMouseEvent.GetPosPixel()); |
594 | |
|
595 | 0 | mpTextEngine->SetActiveView(this); |
596 | |
|
597 | 0 | mpSelEngine->SelMouseButtonDown(rMouseEvent); |
598 | | |
599 | | // mbu 20.01.2005 - SelMouseButtonDown() possibly triggers a 'selection changed' |
600 | | // notification. The appropriate handler could change the current selection, |
601 | | // which is the case in the MailMerge address block control. To enable select'n'drag |
602 | | // we need to reevaluate the selection after the notification has been fired. |
603 | 0 | mbClickedInSelection = IsSelectionAtPoint(rMouseEvent.GetPosPixel()); |
604 | | |
605 | | // special cases |
606 | 0 | if ( rMouseEvent.IsShift() || ( rMouseEvent.GetClicks() < 2 )) |
607 | 0 | return; |
608 | | |
609 | 0 | if ( rMouseEvent.IsMod2() ) |
610 | 0 | { |
611 | 0 | HideSelection(); |
612 | 0 | ImpSetSelection(maSelection.GetEnd()); |
613 | 0 | SetCursorAtPoint( rMouseEvent.GetPosPixel() ); // not set by SelectionEngine for MOD2 |
614 | 0 | } |
615 | |
|
616 | 0 | if ( rMouseEvent.GetClicks() == 2 ) |
617 | 0 | { |
618 | | // select word |
619 | 0 | if (maSelection.GetEnd().GetIndex() < mpTextEngine->GetTextLen( maSelection.GetEnd().GetPara())) |
620 | 0 | { |
621 | 0 | HideSelection(); |
622 | | // tdf#57879 - expand selection to include connector punctuations |
623 | 0 | TextSelection aNewSel; |
624 | 0 | mpTextEngine->GetWord(maSelection.GetEnd(), &aNewSel.GetStart(), &aNewSel.GetEnd()); |
625 | 0 | ImpSetSelection( aNewSel ); |
626 | 0 | ShowSelection(); |
627 | 0 | ShowCursor(); |
628 | 0 | } |
629 | 0 | } |
630 | 0 | else if ( rMouseEvent.GetClicks() == 3 ) |
631 | 0 | { |
632 | | // select paragraph |
633 | 0 | if (maSelection.GetStart().GetIndex() || (maSelection.GetEnd().GetIndex() < mpTextEngine->GetTextLen(maSelection.GetEnd().GetPara()))) |
634 | 0 | { |
635 | 0 | HideSelection(); |
636 | 0 | TextSelection aNewSel(maSelection); |
637 | 0 | aNewSel.GetStart().GetIndex() = 0; |
638 | 0 | aNewSel.GetEnd().GetIndex() = mpTextEngine->mpDoc->GetNodes()[maSelection.GetEnd().GetPara()]->GetText().getLength(); |
639 | 0 | ImpSetSelection( aNewSel ); |
640 | 0 | ShowSelection(); |
641 | 0 | ShowCursor(); |
642 | 0 | } |
643 | 0 | } |
644 | 0 | } |
645 | | |
646 | | void TextView::MouseMove( const MouseEvent& rMouseEvent ) |
647 | 0 | { |
648 | 0 | mnTravelXPos = TRAVEL_X_DONTKNOW; |
649 | 0 | mpSelEngine->SelMouseMove(rMouseEvent); |
650 | 0 | } |
651 | | |
652 | | void TextView::Command( const CommandEvent& rCEvt ) |
653 | 0 | { |
654 | 0 | mpTextEngine->CheckIdleFormatter(); // for fast typing and MouseButtonDown |
655 | 0 | mpTextEngine->SetActiveView(this); |
656 | |
|
657 | 0 | if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput ) |
658 | 0 | { |
659 | 0 | DeleteSelected(); |
660 | 0 | TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[GetSelection().GetEnd().GetPara()].get(); |
661 | 0 | mpTextEngine->mpIMEInfos = std::make_unique<TEIMEInfos>( |
662 | 0 | GetSelection().GetEnd(), pNode->GetText().copy(GetSelection().GetEnd().GetIndex())); |
663 | 0 | mpTextEngine->mpIMEInfos->bWasCursorOverwrite = !IsInsertMode(); |
664 | 0 | } |
665 | 0 | else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput ) |
666 | 0 | { |
667 | 0 | SAL_WARN_IF(!mpTextEngine->mpIMEInfos, "vcl", |
668 | 0 | "CommandEventId::EndExtTextInput => No Start ?"); |
669 | 0 | if (mpTextEngine->mpIMEInfos) |
670 | 0 | { |
671 | 0 | TEParaPortion* pPortion = mpTextEngine->mpTEParaPortions->GetObject( |
672 | 0 | mpTextEngine->mpIMEInfos->aPos.GetPara()); |
673 | 0 | pPortion->MarkSelectionInvalid(mpTextEngine->mpIMEInfos->aPos.GetIndex()); |
674 | |
|
675 | 0 | bool bInsertMode = !mpTextEngine->mpIMEInfos->bWasCursorOverwrite; |
676 | |
|
677 | 0 | mpTextEngine->mpIMEInfos.reset(); |
678 | |
|
679 | 0 | mpTextEngine->TextModified(); |
680 | 0 | mpTextEngine->FormatAndUpdate(this); |
681 | |
|
682 | 0 | SetInsertMode( bInsertMode ); |
683 | |
|
684 | 0 | if (mpTextEngine->IsModified()) |
685 | 0 | mpTextEngine->Broadcast(TextHint(SfxHintId::TextModified)); |
686 | 0 | } |
687 | 0 | } |
688 | 0 | else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput ) |
689 | 0 | { |
690 | 0 | SAL_WARN_IF(!mpTextEngine->mpIMEInfos, "vcl", "CommandEventId::ExtTextInput => No Start ?"); |
691 | 0 | if (mpTextEngine->mpIMEInfos) |
692 | 0 | { |
693 | 0 | const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData(); |
694 | |
|
695 | 0 | if ( !pData->IsOnlyCursorChanged() ) |
696 | 0 | { |
697 | 0 | TextSelection aSelect(mpTextEngine->mpIMEInfos->aPos); |
698 | 0 | aSelect.GetEnd().GetIndex() += mpTextEngine->mpIMEInfos->nLen; |
699 | 0 | aSelect = mpTextEngine->ImpDeleteText(aSelect); |
700 | 0 | aSelect = mpTextEngine->ImpInsertText(aSelect, pData->GetText()); |
701 | |
|
702 | 0 | if (mpTextEngine->mpIMEInfos->bWasCursorOverwrite) |
703 | 0 | { |
704 | 0 | const sal_Int32 nOldIMETextLen = mpTextEngine->mpIMEInfos->nLen; |
705 | 0 | const sal_Int32 nNewIMETextLen = pData->GetText().getLength(); |
706 | |
|
707 | 0 | if ((nOldIMETextLen > nNewIMETextLen) && |
708 | 0 | ( nNewIMETextLen < mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) ) |
709 | 0 | { |
710 | | // restore old characters |
711 | 0 | sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen; |
712 | 0 | TextPaM aPaM(mpTextEngine->mpIMEInfos->aPos); |
713 | 0 | aPaM.GetIndex() += nNewIMETextLen; |
714 | 0 | mpTextEngine->ImpInsertText(aPaM, mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.copy(nNewIMETextLen, nRestore)); |
715 | 0 | } |
716 | 0 | else if ((nOldIMETextLen < nNewIMETextLen) && |
717 | 0 | ( nOldIMETextLen < mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength() ) ) |
718 | 0 | { |
719 | | // overwrite |
720 | 0 | const sal_Int32 nOverwrite = std::min(nNewIMETextLen, mpTextEngine->mpIMEInfos->aOldTextAfterStartPos.getLength()) - nOldIMETextLen; |
721 | 0 | SAL_WARN_IF( !nOverwrite || (nOverwrite >= 0xFF00), "vcl", "IME Overwrite?!" ); |
722 | 0 | TextPaM aPaM(mpTextEngine->mpIMEInfos->aPos); |
723 | 0 | aPaM.GetIndex() += nNewIMETextLen; |
724 | 0 | TextSelection aSel( aPaM ); |
725 | 0 | aSel.GetEnd().GetIndex() += nOverwrite; |
726 | 0 | mpTextEngine->ImpDeleteText(aSel); |
727 | 0 | } |
728 | 0 | } |
729 | | |
730 | 0 | if ( pData->GetTextAttr() ) |
731 | 0 | { |
732 | 0 | mpTextEngine->mpIMEInfos->CopyAttribs(pData->GetTextAttr(), pData->GetText().getLength()); |
733 | 0 | } |
734 | 0 | else |
735 | 0 | { |
736 | 0 | mpTextEngine->mpIMEInfos->DestroyAttribs(); |
737 | 0 | } |
738 | |
|
739 | 0 | TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject( |
740 | 0 | mpTextEngine->mpIMEInfos->aPos.GetPara()); |
741 | 0 | pPPortion->MarkSelectionInvalid(mpTextEngine->mpIMEInfos->aPos.GetIndex()); |
742 | 0 | mpTextEngine->FormatAndUpdate(this); |
743 | 0 | } |
744 | | |
745 | 0 | TextSelection aNewSel |
746 | 0 | = TextPaM(mpTextEngine->mpIMEInfos->aPos.GetPara(), |
747 | 0 | mpTextEngine->mpIMEInfos->aPos.GetIndex() + pData->GetCursorPos()); |
748 | 0 | SetSelection( aNewSel ); |
749 | 0 | SetInsertMode( !pData->IsCursorOverwrite() ); |
750 | |
|
751 | 0 | if ( pData->IsCursorVisible() ) |
752 | 0 | ShowCursor(); |
753 | 0 | else |
754 | 0 | HideCursor(); |
755 | 0 | } |
756 | 0 | } |
757 | 0 | else if ( rCEvt.GetCommand() == CommandEventId::CursorPos ) |
758 | 0 | { |
759 | 0 | if (mpTextEngine->mpIMEInfos && mpTextEngine->mpIMEInfos->nLen) |
760 | 0 | { |
761 | 0 | TextPaM aPaM( GetSelection().GetEnd() ); |
762 | 0 | tools::Rectangle aR1 = mpTextEngine->PaMtoEditCursor(aPaM); |
763 | |
|
764 | 0 | sal_Int32 nInputEnd |
765 | 0 | = mpTextEngine->mpIMEInfos->aPos.GetIndex() + mpTextEngine->mpIMEInfos->nLen; |
766 | |
|
767 | 0 | if (!mpTextEngine->IsFormatted()) |
768 | 0 | mpTextEngine->FormatDoc(); |
769 | |
|
770 | 0 | TEParaPortion* pParaPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara()); |
771 | 0 | std::vector<TextLine>::size_type nLine = pParaPortion->GetLineNumber( aPaM.GetIndex(), true ); |
772 | 0 | TextLine& rLine = pParaPortion->GetLines()[ nLine ]; |
773 | 0 | if ( nInputEnd > rLine.GetEnd() ) |
774 | 0 | nInputEnd = rLine.GetEnd(); |
775 | 0 | tools::Rectangle aR2 |
776 | 0 | = mpTextEngine->PaMtoEditCursor(TextPaM(aPaM.GetPara(), nInputEnd)); |
777 | |
|
778 | 0 | tools::Long nWidth = aR2.Left()-aR1.Right(); |
779 | 0 | aR1.Move( -GetStartDocPos().X(), -GetStartDocPos().Y() ); |
780 | 0 | GetWindow()->SetCursorRect( &aR1, nWidth ); |
781 | 0 | } |
782 | 0 | else |
783 | 0 | { |
784 | 0 | GetWindow()->SetCursorRect(); |
785 | 0 | } |
786 | 0 | } |
787 | 0 | else |
788 | 0 | { |
789 | 0 | mpSelEngine->Command(rCEvt); |
790 | 0 | } |
791 | 0 | } |
792 | | |
793 | | void TextView::ShowCursor( bool bGotoCursor, bool bForceVisCursor ) |
794 | 0 | { |
795 | | // this setting has more weight |
796 | 0 | if (!mbAutoScroll) |
797 | 0 | bGotoCursor = false; |
798 | 0 | ImpShowCursor( bGotoCursor, bForceVisCursor, false ); |
799 | 0 | } |
800 | | |
801 | | void TextView::HideCursor() |
802 | 0 | { |
803 | 0 | mpCursor->Hide(); |
804 | 0 | } |
805 | | |
806 | | void TextView::Scroll( tools::Long ndX, tools::Long ndY ) |
807 | 0 | { |
808 | 0 | SAL_WARN_IF(!mpTextEngine->IsFormatted(), "vcl", "Scroll: Not formatted!"); |
809 | | |
810 | 0 | if ( !ndX && !ndY ) |
811 | 0 | return; |
812 | | |
813 | 0 | Point aNewStartPos(maStartDocPos); |
814 | | |
815 | | // Vertical: |
816 | 0 | aNewStartPos.AdjustY( -ndY ); |
817 | 0 | if ( aNewStartPos.Y() < 0 ) |
818 | 0 | aNewStartPos.setY( 0 ); |
819 | | |
820 | | // Horizontal: |
821 | 0 | aNewStartPos.AdjustX( -ndX ); |
822 | 0 | if ( aNewStartPos.X() < 0 ) |
823 | 0 | aNewStartPos.setX( 0 ); |
824 | |
|
825 | 0 | tools::Long nDiffX = maStartDocPos.X() - aNewStartPos.X(); |
826 | 0 | tools::Long nDiffY = maStartDocPos.Y() - aNewStartPos.Y(); |
827 | |
|
828 | 0 | if ( nDiffX || nDiffY ) |
829 | 0 | { |
830 | 0 | bool bVisCursor = mpCursor->IsVisible(); |
831 | 0 | mpCursor->Hide(); |
832 | 0 | mpWindow->PaintImmediately(); |
833 | 0 | maStartDocPos = aNewStartPos; |
834 | |
|
835 | 0 | if (mpTextEngine->IsRightToLeft()) |
836 | 0 | nDiffX = -nDiffX; |
837 | 0 | mpWindow->Scroll(nDiffX, nDiffY); |
838 | 0 | mpWindow->PaintImmediately(); |
839 | 0 | mpCursor->SetPos(mpCursor->GetPos() + Point(nDiffX, nDiffY)); |
840 | 0 | if (bVisCursor && !mbReadOnly) |
841 | 0 | mpCursor->Show(); |
842 | 0 | } |
843 | |
|
844 | 0 | mpTextEngine->Broadcast(TextHint(SfxHintId::TextViewScrolled)); |
845 | 0 | } |
846 | | |
847 | | void TextView::Undo() |
848 | 0 | { |
849 | 0 | mpTextEngine->SetActiveView(this); |
850 | 0 | mpTextEngine->GetUndoManager().Undo(); |
851 | 0 | } |
852 | | |
853 | | void TextView::Redo() |
854 | 0 | { |
855 | 0 | mpTextEngine->SetActiveView(this); |
856 | 0 | mpTextEngine->GetUndoManager().Redo(); |
857 | 0 | } |
858 | | |
859 | | void TextView::Cut() |
860 | 0 | { |
861 | 0 | mpTextEngine->UndoActionStart(); |
862 | 0 | Copy(); |
863 | 0 | DeleteSelected(); |
864 | 0 | mpTextEngine->UndoActionEnd(); |
865 | 0 | } |
866 | | |
867 | | void TextView::Copy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard ) |
868 | 0 | { |
869 | 0 | if ( !rxClipboard.is() ) |
870 | 0 | return; |
871 | | |
872 | 0 | rtl::Reference<TETextDataObject> pDataObj = new TETextDataObject( GetSelected() ); |
873 | |
|
874 | 0 | SolarMutexReleaser aReleaser; |
875 | |
|
876 | 0 | try |
877 | 0 | { |
878 | 0 | rxClipboard->setContents( pDataObj, nullptr ); |
879 | |
|
880 | 0 | css::uno::Reference< css::datatransfer::clipboard::XFlushableClipboard > xFlushableClipboard( rxClipboard, css::uno::UNO_QUERY ); |
881 | 0 | if( xFlushableClipboard.is() ) |
882 | 0 | xFlushableClipboard->flushClipboard(); |
883 | 0 | } |
884 | 0 | catch( const css::uno::Exception& ) |
885 | 0 | { |
886 | 0 | } |
887 | 0 | } |
888 | | |
889 | | void TextView::Copy() |
890 | 0 | { |
891 | 0 | css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetWindow()->GetClipboard()); |
892 | 0 | Copy( aClipboard ); |
893 | 0 | } |
894 | | |
895 | | void TextView::Paste( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard ) |
896 | 0 | { |
897 | 0 | if ( !rxClipboard.is() ) |
898 | 0 | return; |
899 | | |
900 | 0 | css::uno::Reference< css::datatransfer::XTransferable > xDataObj; |
901 | |
|
902 | 0 | try |
903 | 0 | { |
904 | 0 | SolarMutexReleaser aReleaser; |
905 | 0 | xDataObj = rxClipboard->getContents(); |
906 | 0 | } |
907 | 0 | catch( const css::uno::Exception& ) |
908 | 0 | { |
909 | 0 | } |
910 | |
|
911 | 0 | if ( !xDataObj.is() ) |
912 | 0 | return; |
913 | | |
914 | 0 | css::datatransfer::DataFlavor aFlavor; |
915 | 0 | SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor ); |
916 | 0 | if ( !xDataObj->isDataFlavorSupported( aFlavor ) ) |
917 | 0 | return; |
918 | | |
919 | 0 | try |
920 | 0 | { |
921 | 0 | css::uno::Any aData = xDataObj->getTransferData( aFlavor ); |
922 | 0 | OUString aText; |
923 | 0 | aData >>= aText; |
924 | 0 | bool bWasTruncated = false; |
925 | 0 | if (mpTextEngine->GetMaxTextLen() != 0) |
926 | 0 | bWasTruncated = ImplTruncateNewText( aText ); |
927 | 0 | InsertText( aText ); |
928 | 0 | mpTextEngine->Broadcast(TextHint(SfxHintId::TextModified)); |
929 | |
|
930 | 0 | if( bWasTruncated ) |
931 | 0 | Edit::ShowTruncationWarning(mpWindow->GetFrameWeld()); |
932 | 0 | } |
933 | 0 | catch( const css::datatransfer::UnsupportedFlavorException& ) |
934 | 0 | { |
935 | 0 | } |
936 | 0 | } |
937 | | |
938 | | void TextView::Paste() |
939 | 0 | { |
940 | 0 | css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetWindow()->GetClipboard()); |
941 | 0 | Paste( aClipboard ); |
942 | 0 | } |
943 | | |
944 | | OUString TextView::GetSelected() const |
945 | 0 | { |
946 | 0 | return GetSelected( GetSystemLineEnd() ); |
947 | 0 | } |
948 | | |
949 | | OUString TextView::GetSelected( LineEnd aSeparator ) const |
950 | 0 | { |
951 | 0 | return mpTextEngine->GetText(maSelection, aSeparator); |
952 | 0 | } |
953 | | |
954 | | void TextView::SetInsertMode( bool bInsert ) |
955 | 0 | { |
956 | 0 | if (mbInsertMode != bInsert) |
957 | 0 | { |
958 | 0 | mbInsertMode = bInsert; |
959 | 0 | ShowCursor(mbAutoScroll, false); |
960 | 0 | } |
961 | 0 | } |
962 | | |
963 | | void TextView::SetReadOnly( bool bReadOnly ) |
964 | 0 | { |
965 | 0 | if (mbReadOnly != bReadOnly) |
966 | 0 | { |
967 | 0 | mbReadOnly = bReadOnly; |
968 | 0 | if (!mbReadOnly) |
969 | 0 | ShowCursor(mbAutoScroll, false); |
970 | 0 | else |
971 | 0 | HideCursor(); |
972 | |
|
973 | 0 | GetWindow()->SetInputContext( |
974 | 0 | InputContext(mpTextEngine->GetFont(), |
975 | 0 | bReadOnly ? InputContextFlags::Text | InputContextFlags::ExtText |
976 | 0 | : InputContextFlags::NONE)); |
977 | 0 | } |
978 | 0 | } |
979 | | |
980 | | TextSelection const & TextView::ImpMoveCursor( const KeyEvent& rKeyEvent ) |
981 | 0 | { |
982 | | // normally only needed for Up/Down; but who cares |
983 | 0 | mpTextEngine->CheckIdleFormatter(); |
984 | |
|
985 | 0 | TextPaM aPaM(maSelection.GetEnd()); |
986 | 0 | TextPaM aOldEnd( aPaM ); |
987 | |
|
988 | 0 | TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom; |
989 | 0 | if (mpTextEngine->IsRightToLeft()) |
990 | 0 | eTextDirection = TextDirectionality::RightToLeft_TopToBottom; |
991 | |
|
992 | 0 | KeyEvent aTranslatedKeyEvent = rKeyEvent.LogicalTextDirectionality( eTextDirection ); |
993 | |
|
994 | 0 | bool bCtrl = aTranslatedKeyEvent.GetKeyCode().IsMod1(); |
995 | 0 | sal_uInt16 nCode = aTranslatedKeyEvent.GetKeyCode().GetCode(); |
996 | |
|
997 | 0 | bool bSelect = aTranslatedKeyEvent.GetKeyCode().IsShift(); |
998 | 0 | switch ( nCode ) |
999 | 0 | { |
1000 | 0 | case KEY_UP: aPaM = CursorUp( aPaM ); |
1001 | 0 | break; |
1002 | 0 | case KEY_DOWN: aPaM = CursorDown( aPaM ); |
1003 | 0 | break; |
1004 | 0 | case KEY_HOME: |
1005 | 0 | if (bCtrl) |
1006 | 0 | { |
1007 | 0 | aPaM = CursorStartOfDoc(); |
1008 | 0 | } |
1009 | 0 | else |
1010 | 0 | { |
1011 | | // tdf#145764 - move cursor to the beginning or the first non-space character in the same line |
1012 | 0 | const TextPaM aFirstWordPaM = CursorFirstWord(aPaM); |
1013 | 0 | aPaM = aPaM.GetIndex() == aFirstWordPaM.GetIndex() ? CursorStartOfLine(aPaM) : aFirstWordPaM; |
1014 | 0 | } |
1015 | 0 | break; |
1016 | 0 | case KEY_END: aPaM = bCtrl ? CursorEndOfDoc() : CursorEndOfLine( aPaM ); |
1017 | 0 | break; |
1018 | 0 | case KEY_PAGEUP: aPaM = bCtrl ? CursorStartOfDoc() : PageUp( aPaM ); |
1019 | 0 | break; |
1020 | 0 | case KEY_PAGEDOWN: aPaM = bCtrl ? CursorEndOfDoc() : PageDown( aPaM ); |
1021 | 0 | break; |
1022 | 0 | case KEY_LEFT: aPaM = bCtrl ? CursorWordLeft( aPaM ) : CursorLeft( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) : sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) ); |
1023 | 0 | break; |
1024 | 0 | case KEY_RIGHT: aPaM = bCtrl ? CursorWordRight( aPaM ) : CursorRight( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) : sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) ); |
1025 | 0 | break; |
1026 | 0 | case css::awt::Key::SELECT_WORD_FORWARD: |
1027 | 0 | bSelect = true; |
1028 | 0 | [[fallthrough]]; |
1029 | 0 | case css::awt::Key::MOVE_WORD_FORWARD: |
1030 | 0 | aPaM = CursorWordRight( aPaM ); |
1031 | 0 | break; |
1032 | 0 | case css::awt::Key::SELECT_WORD_BACKWARD: |
1033 | 0 | bSelect = true; |
1034 | 0 | [[fallthrough]]; |
1035 | 0 | case css::awt::Key::MOVE_WORD_BACKWARD: |
1036 | 0 | aPaM = CursorWordLeft( aPaM ); |
1037 | 0 | break; |
1038 | 0 | case css::awt::Key::SELECT_TO_BEGIN_OF_LINE: |
1039 | 0 | bSelect = true; |
1040 | 0 | [[fallthrough]]; |
1041 | 0 | case css::awt::Key::MOVE_TO_BEGIN_OF_LINE: |
1042 | 0 | aPaM = CursorStartOfLine( aPaM ); |
1043 | 0 | break; |
1044 | 0 | case css::awt::Key::SELECT_TO_END_OF_LINE: |
1045 | 0 | bSelect = true; |
1046 | 0 | [[fallthrough]]; |
1047 | 0 | case css::awt::Key::MOVE_TO_END_OF_LINE: |
1048 | 0 | aPaM = CursorEndOfLine( aPaM ); |
1049 | 0 | break; |
1050 | 0 | case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH: |
1051 | 0 | bSelect = true; |
1052 | 0 | [[fallthrough]]; |
1053 | 0 | case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH: |
1054 | 0 | aPaM = CursorStartOfParagraph( aPaM ); |
1055 | 0 | break; |
1056 | 0 | case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH: |
1057 | 0 | bSelect = true; |
1058 | 0 | [[fallthrough]]; |
1059 | 0 | case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH: |
1060 | 0 | aPaM = CursorEndOfParagraph( aPaM ); |
1061 | 0 | break; |
1062 | 0 | case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT: |
1063 | 0 | bSelect = true; |
1064 | 0 | [[fallthrough]]; |
1065 | 0 | case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT: |
1066 | 0 | aPaM = CursorStartOfDoc(); |
1067 | 0 | break; |
1068 | 0 | case css::awt::Key::SELECT_TO_END_OF_DOCUMENT: |
1069 | 0 | bSelect = true; |
1070 | 0 | [[fallthrough]]; |
1071 | 0 | case css::awt::Key::MOVE_TO_END_OF_DOCUMENT: |
1072 | 0 | aPaM = CursorEndOfDoc(); |
1073 | 0 | break; |
1074 | 0 | } |
1075 | | |
1076 | | // might cause a CreateAnchor or Deselection all |
1077 | 0 | mpSelEngine->CursorPosChanging(bSelect, aTranslatedKeyEvent.GetKeyCode().IsMod1()); |
1078 | |
|
1079 | 0 | if ( aOldEnd != aPaM ) |
1080 | 0 | { |
1081 | 0 | mpTextEngine->CursorMoved(aOldEnd.GetPara()); |
1082 | |
|
1083 | 0 | TextSelection aNewSelection(maSelection); |
1084 | 0 | aNewSelection.GetEnd() = aPaM; |
1085 | 0 | if ( bSelect ) |
1086 | 0 | { |
1087 | | // extend the selection |
1088 | 0 | ImpSetSelection( aNewSelection ); |
1089 | 0 | ShowSelection( TextSelection( aOldEnd, aPaM ) ); |
1090 | 0 | } |
1091 | 0 | else |
1092 | 0 | { |
1093 | 0 | aNewSelection.GetStart() = aPaM; |
1094 | 0 | ImpSetSelection( aNewSelection ); |
1095 | 0 | } |
1096 | 0 | } |
1097 | |
|
1098 | 0 | return maSelection; |
1099 | 0 | } |
1100 | | |
1101 | | void TextView::InsertText( const OUString& rStr ) |
1102 | 0 | { |
1103 | 0 | mpTextEngine->UndoActionStart(); |
1104 | |
|
1105 | 0 | TextSelection aNewSel = mpTextEngine->ImpInsertText(maSelection, rStr); |
1106 | |
|
1107 | 0 | ImpSetSelection( aNewSel ); |
1108 | |
|
1109 | 0 | mpTextEngine->UndoActionEnd(); |
1110 | |
|
1111 | 0 | mpTextEngine->FormatAndUpdate(this); |
1112 | 0 | } |
1113 | | |
1114 | | TextPaM TextView::CursorLeft( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode ) |
1115 | 0 | { |
1116 | 0 | TextPaM aPaM( rPaM ); |
1117 | |
|
1118 | 0 | if ( aPaM.GetIndex() ) |
1119 | 0 | { |
1120 | 0 | TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get(); |
1121 | 0 | css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator(); |
1122 | 0 | sal_Int32 nCount = 1; |
1123 | 0 | aPaM.GetIndex() |
1124 | 0 | = xBI->previousCharacters(pNode->GetText(), aPaM.GetIndex(), mpTextEngine->GetLocale(), |
1125 | 0 | nCharacterIteratorMode, nCount, nCount); |
1126 | 0 | } |
1127 | 0 | else if ( aPaM.GetPara() ) |
1128 | 0 | { |
1129 | 0 | aPaM.GetPara()--; |
1130 | 0 | TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get(); |
1131 | 0 | aPaM.GetIndex() = pNode->GetText().getLength(); |
1132 | 0 | } |
1133 | 0 | return aPaM; |
1134 | 0 | } |
1135 | | |
1136 | | TextPaM TextView::CursorRight( const TextPaM& rPaM, sal_uInt16 nCharacterIteratorMode ) |
1137 | 0 | { |
1138 | 0 | TextPaM aPaM( rPaM ); |
1139 | |
|
1140 | 0 | TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get(); |
1141 | 0 | if ( aPaM.GetIndex() < pNode->GetText().getLength() ) |
1142 | 0 | { |
1143 | 0 | css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator(); |
1144 | 0 | sal_Int32 nCount = 1; |
1145 | 0 | aPaM.GetIndex() |
1146 | 0 | = xBI->nextCharacters(pNode->GetText(), aPaM.GetIndex(), mpTextEngine->GetLocale(), |
1147 | 0 | nCharacterIteratorMode, nCount, nCount); |
1148 | 0 | } |
1149 | 0 | else if (aPaM.GetPara() < (mpTextEngine->mpDoc->GetNodes().size() - 1)) |
1150 | 0 | { |
1151 | 0 | aPaM.GetPara()++; |
1152 | 0 | aPaM.GetIndex() = 0; |
1153 | 0 | } |
1154 | |
|
1155 | 0 | return aPaM; |
1156 | 0 | } |
1157 | | |
1158 | | TextPaM TextView::CursorFirstWord( const TextPaM& rPaM ) |
1159 | 0 | { |
1160 | 0 | TextPaM aPaM(rPaM); |
1161 | 0 | TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get(); |
1162 | |
|
1163 | 0 | css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator(); |
1164 | 0 | aPaM.GetIndex() = xBI->beginOfSentence(pNode->GetText(), 0, mpTextEngine->GetLocale()); |
1165 | |
|
1166 | 0 | return aPaM; |
1167 | 0 | } |
1168 | | |
1169 | | TextPaM TextView::CursorWordLeft( const TextPaM& rPaM ) |
1170 | 0 | { |
1171 | 0 | TextPaM aPaM( rPaM ); |
1172 | |
|
1173 | 0 | if ( aPaM.GetIndex() ) |
1174 | 0 | { |
1175 | | // tdf#57879 - expand selection to the left to include connector punctuations |
1176 | 0 | mpTextEngine->GetWord(rPaM, &aPaM); |
1177 | 0 | if ( aPaM.GetIndex() >= rPaM.GetIndex() ) |
1178 | 0 | { |
1179 | 0 | TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get(); |
1180 | 0 | css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator(); |
1181 | 0 | aPaM.GetIndex() |
1182 | 0 | = xBI->previousWord(pNode->GetText(), rPaM.GetIndex(), mpTextEngine->GetLocale(), |
1183 | 0 | css::i18n::WordType::ANYWORD_IGNOREWHITESPACES).startPos; |
1184 | 0 | if ( aPaM.GetIndex() > 0 ) |
1185 | 0 | mpTextEngine->GetWord(aPaM, &aPaM); |
1186 | 0 | else |
1187 | 0 | aPaM.GetIndex() = 0; |
1188 | 0 | } |
1189 | 0 | } |
1190 | 0 | else if ( aPaM.GetPara() ) |
1191 | 0 | { |
1192 | 0 | aPaM.GetPara()--; |
1193 | 0 | TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get(); |
1194 | 0 | aPaM.GetIndex() = pNode->GetText().getLength(); |
1195 | 0 | } |
1196 | 0 | return aPaM; |
1197 | 0 | } |
1198 | | |
1199 | | TextPaM TextView::CursorWordRight( const TextPaM& rPaM ) |
1200 | 0 | { |
1201 | 0 | TextPaM aPaM( rPaM ); |
1202 | |
|
1203 | 0 | TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get(); |
1204 | 0 | if ( aPaM.GetIndex() < pNode->GetText().getLength() ) |
1205 | 0 | { |
1206 | 0 | css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator(); |
1207 | | // tdf#160202 - NextWord unexpectedly skips two words at the start of any word |
1208 | 0 | const auto aWordBoundary |
1209 | 0 | = xBI->getWordBoundary(pNode->GetText(), aPaM.GetIndex(), mpTextEngine->GetLocale(), |
1210 | 0 | css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true); |
1211 | | |
1212 | | // Check if the current index is inside the word boundary |
1213 | 0 | if (aWordBoundary.startPos <= aPaM.GetIndex() && aPaM.GetIndex() < aWordBoundary.endPos) |
1214 | 0 | aPaM.GetIndex() = aWordBoundary.startPos; |
1215 | 0 | else |
1216 | 0 | aPaM.GetIndex() |
1217 | 0 | = xBI->nextWord(pNode->GetText(), aPaM.GetIndex(), mpTextEngine->GetLocale(), |
1218 | 0 | css::i18n::WordType::ANYWORD_IGNOREWHITESPACES) |
1219 | 0 | .endPos; |
1220 | 0 | mpTextEngine->GetWord(aPaM, nullptr, &aPaM); |
1221 | 0 | } |
1222 | 0 | else if (aPaM.GetPara() < (mpTextEngine->mpDoc->GetNodes().size() - 1)) |
1223 | 0 | { |
1224 | 0 | aPaM.GetPara()++; |
1225 | 0 | aPaM.GetIndex() = 0; |
1226 | 0 | } |
1227 | |
|
1228 | 0 | return aPaM; |
1229 | 0 | } |
1230 | | |
1231 | | TextPaM TextView::ImpDelete( sal_uInt8 nMode, sal_uInt8 nDelMode ) |
1232 | 0 | { |
1233 | 0 | if (maSelection.HasRange()) // only delete selection |
1234 | 0 | return mpTextEngine->ImpDeleteText(maSelection); |
1235 | | |
1236 | 0 | TextPaM aStartPaM = maSelection.GetStart(); |
1237 | 0 | TextPaM aEndPaM = aStartPaM; |
1238 | 0 | if ( nMode == DEL_LEFT ) |
1239 | 0 | { |
1240 | 0 | if ( nDelMode == DELMODE_SIMPLE ) |
1241 | 0 | { |
1242 | 0 | aEndPaM = CursorLeft( aEndPaM, sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCHARACTER) ); |
1243 | 0 | } |
1244 | 0 | else if ( nDelMode == DELMODE_RESTOFWORD ) |
1245 | 0 | { |
1246 | 0 | TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aEndPaM.GetPara()].get(); |
1247 | 0 | css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator(); |
1248 | 0 | css::i18n::Boundary aBoundary = xBI->getWordBoundary( |
1249 | 0 | pNode->GetText(), maSelection.GetEnd().GetIndex(), mpTextEngine->GetLocale(), |
1250 | 0 | css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true); |
1251 | 0 | if (aBoundary.startPos == maSelection.GetEnd().GetIndex()) |
1252 | 0 | aBoundary = xBI->previousWord(pNode->GetText(), maSelection.GetEnd().GetIndex(), |
1253 | 0 | mpTextEngine->GetLocale(), |
1254 | 0 | css::i18n::WordType::ANYWORD_IGNOREWHITESPACES); |
1255 | | // #i63506# startPos is -1 when the paragraph starts with a tab |
1256 | 0 | aEndPaM.GetIndex() = std::max<sal_Int32>(aBoundary.startPos, 0); |
1257 | 0 | } |
1258 | 0 | else // DELMODE_RESTOFCONTENT |
1259 | 0 | { |
1260 | 0 | if ( aEndPaM.GetIndex() != 0 ) |
1261 | 0 | aEndPaM.GetIndex() = 0; |
1262 | 0 | else if ( aEndPaM.GetPara() ) |
1263 | 0 | { |
1264 | | // previous paragraph |
1265 | 0 | aEndPaM.GetPara()--; |
1266 | 0 | aEndPaM.GetIndex() = 0; |
1267 | 0 | } |
1268 | 0 | } |
1269 | 0 | } |
1270 | 0 | else |
1271 | 0 | { |
1272 | 0 | if ( nDelMode == DELMODE_SIMPLE ) |
1273 | 0 | { |
1274 | 0 | aEndPaM = CursorRight( aEndPaM, sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) ); |
1275 | 0 | } |
1276 | 0 | else if ( nDelMode == DELMODE_RESTOFWORD ) |
1277 | 0 | { |
1278 | 0 | TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aEndPaM.GetPara()].get(); |
1279 | 0 | css::uno::Reference<css::i18n::XBreakIterator> xBI = mpTextEngine->GetBreakIterator(); |
1280 | 0 | css::i18n::Boundary aBoundary = xBI->nextWord( |
1281 | 0 | pNode->GetText(), maSelection.GetEnd().GetIndex(), mpTextEngine->GetLocale(), |
1282 | 0 | css::i18n::WordType::ANYWORD_IGNOREWHITESPACES); |
1283 | 0 | aEndPaM.GetIndex() = aBoundary.startPos; |
1284 | 0 | } |
1285 | 0 | else // DELMODE_RESTOFCONTENT |
1286 | 0 | { |
1287 | 0 | TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aEndPaM.GetPara()].get(); |
1288 | 0 | if ( aEndPaM.GetIndex() < pNode->GetText().getLength() ) |
1289 | 0 | aEndPaM.GetIndex() = pNode->GetText().getLength(); |
1290 | 0 | else if (aEndPaM.GetPara() < (mpTextEngine->mpDoc->GetNodes().size() - 1)) |
1291 | 0 | { |
1292 | | // next paragraph |
1293 | 0 | aEndPaM.GetPara()++; |
1294 | 0 | TextNode* pNextNode = mpTextEngine->mpDoc->GetNodes()[aEndPaM.GetPara()].get(); |
1295 | 0 | aEndPaM.GetIndex() = pNextNode->GetText().getLength(); |
1296 | 0 | } |
1297 | 0 | } |
1298 | 0 | } |
1299 | |
|
1300 | 0 | return mpTextEngine->ImpDeleteText(TextSelection(aStartPaM, aEndPaM)); |
1301 | 0 | } |
1302 | | |
1303 | | TextPaM TextView::CursorUp( const TextPaM& rPaM ) |
1304 | 0 | { |
1305 | 0 | TextPaM aPaM( rPaM ); |
1306 | |
|
1307 | 0 | tools::Long nX; |
1308 | 0 | if (mnTravelXPos == TRAVEL_X_DONTKNOW) |
1309 | 0 | { |
1310 | 0 | nX = mpTextEngine->GetEditCursor(rPaM, false).Left(); |
1311 | 0 | mnTravelXPos = static_cast<sal_uInt16>(nX) + 1; |
1312 | 0 | } |
1313 | 0 | else |
1314 | 0 | nX = mnTravelXPos; |
1315 | |
|
1316 | 0 | TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(rPaM.GetPara()); |
1317 | 0 | std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false ); |
1318 | 0 | if ( nLine ) // same paragraph |
1319 | 0 | { |
1320 | 0 | aPaM.GetIndex() = mpTextEngine->GetCharPos(rPaM.GetPara(), nLine - 1, nX); |
1321 | | // If we need to go to the end of a line that was wrapped automatically, |
1322 | | // the cursor ends up at the beginning of the 2nd line |
1323 | | // Problem: Last character of an automatically wrapped line = Cursor |
1324 | 0 | TextLine& rLine = pPPortion->GetLines()[ nLine - 1 ]; |
1325 | 0 | if ( aPaM.GetIndex() && ( aPaM.GetIndex() == rLine.GetEnd() ) ) |
1326 | 0 | --aPaM.GetIndex(); |
1327 | 0 | } |
1328 | 0 | else if ( rPaM.GetPara() ) // previous paragraph |
1329 | 0 | { |
1330 | 0 | aPaM.GetPara()--; |
1331 | 0 | pPPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara()); |
1332 | 0 | std::vector<TextLine>::size_type nL = pPPortion->GetLines().size() - 1; |
1333 | 0 | aPaM.GetIndex() = mpTextEngine->GetCharPos(aPaM.GetPara(), nL, nX + 1); |
1334 | 0 | } |
1335 | |
|
1336 | 0 | return aPaM; |
1337 | 0 | } |
1338 | | |
1339 | | TextPaM TextView::CursorDown( const TextPaM& rPaM ) |
1340 | 0 | { |
1341 | 0 | TextPaM aPaM( rPaM ); |
1342 | |
|
1343 | 0 | tools::Long nX; |
1344 | 0 | if (mnTravelXPos == TRAVEL_X_DONTKNOW) |
1345 | 0 | { |
1346 | 0 | nX = mpTextEngine->GetEditCursor(rPaM, false).Left(); |
1347 | 0 | mnTravelXPos = static_cast<sal_uInt16>(nX) + 1; |
1348 | 0 | } |
1349 | 0 | else |
1350 | 0 | nX = mnTravelXPos; |
1351 | |
|
1352 | 0 | TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(rPaM.GetPara()); |
1353 | 0 | std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( rPaM.GetIndex(), false ); |
1354 | 0 | if ( nLine < ( pPPortion->GetLines().size() - 1 ) ) |
1355 | 0 | { |
1356 | 0 | aPaM.GetIndex() = mpTextEngine->GetCharPos(rPaM.GetPara(), nLine + 1, nX); |
1357 | | |
1358 | | // special case CursorUp |
1359 | 0 | TextLine& rLine = pPPortion->GetLines()[ nLine + 1 ]; |
1360 | 0 | if ( ( aPaM.GetIndex() == rLine.GetEnd() ) && ( aPaM.GetIndex() > rLine.GetStart() ) && aPaM.GetIndex() < pPPortion->GetNode()->GetText().getLength() ) |
1361 | 0 | --aPaM.GetIndex(); |
1362 | 0 | } |
1363 | 0 | else if (rPaM.GetPara() < (mpTextEngine->mpDoc->GetNodes().size() - 1)) // next paragraph |
1364 | 0 | { |
1365 | 0 | aPaM.GetPara()++; |
1366 | 0 | pPPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara()); |
1367 | 0 | aPaM.GetIndex() = mpTextEngine->GetCharPos(aPaM.GetPara(), 0, nX + 1); |
1368 | 0 | TextLine& rLine = pPPortion->GetLines().front(); |
1369 | 0 | if ( ( aPaM.GetIndex() == rLine.GetEnd() ) && ( aPaM.GetIndex() > rLine.GetStart() ) && ( pPPortion->GetLines().size() > 1 ) ) |
1370 | 0 | --aPaM.GetIndex(); |
1371 | 0 | } |
1372 | |
|
1373 | 0 | return aPaM; |
1374 | 0 | } |
1375 | | |
1376 | | TextPaM TextView::CursorStartOfLine( const TextPaM& rPaM ) |
1377 | 0 | { |
1378 | 0 | TextPaM aPaM( rPaM ); |
1379 | |
|
1380 | 0 | TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(rPaM.GetPara()); |
1381 | 0 | std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( aPaM.GetIndex(), false ); |
1382 | 0 | TextLine& rLine = pPPortion->GetLines()[ nLine ]; |
1383 | 0 | aPaM.GetIndex() = rLine.GetStart(); |
1384 | |
|
1385 | 0 | return aPaM; |
1386 | 0 | } |
1387 | | |
1388 | | TextPaM TextView::CursorEndOfLine( const TextPaM& rPaM ) |
1389 | 0 | { |
1390 | 0 | TextPaM aPaM( rPaM ); |
1391 | |
|
1392 | 0 | TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(rPaM.GetPara()); |
1393 | 0 | std::vector<TextLine>::size_type nLine = pPPortion->GetLineNumber( aPaM.GetIndex(), false ); |
1394 | 0 | TextLine& rLine = pPPortion->GetLines()[ nLine ]; |
1395 | 0 | aPaM.GetIndex() = rLine.GetEnd(); |
1396 | |
|
1397 | 0 | if ( rLine.GetEnd() > rLine.GetStart() ) // empty line |
1398 | 0 | { |
1399 | 0 | sal_Unicode cLastChar = pPPortion->GetNode()->GetText()[ aPaM.GetIndex()-1 ]; |
1400 | 0 | if ( ( cLastChar == ' ' ) && ( aPaM.GetIndex() != pPPortion->GetNode()->GetText().getLength() ) ) |
1401 | 0 | { |
1402 | | // for a blank in an automatically-wrapped line it is better to stand before it, |
1403 | | // as the user will intend to stand behind the prior word. |
1404 | | // If there is a change, special case for Pos1 after End! |
1405 | 0 | --aPaM.GetIndex(); |
1406 | 0 | } |
1407 | 0 | } |
1408 | 0 | return aPaM; |
1409 | 0 | } |
1410 | | |
1411 | | TextPaM TextView::CursorStartOfParagraph( const TextPaM& rPaM ) |
1412 | 0 | { |
1413 | 0 | TextPaM aPaM( rPaM ); |
1414 | 0 | aPaM.GetIndex() = 0; |
1415 | 0 | return aPaM; |
1416 | 0 | } |
1417 | | |
1418 | | TextPaM TextView::CursorEndOfParagraph( const TextPaM& rPaM ) |
1419 | 0 | { |
1420 | 0 | TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[rPaM.GetPara()].get(); |
1421 | 0 | TextPaM aPaM( rPaM ); |
1422 | 0 | aPaM.GetIndex() = pNode->GetText().getLength(); |
1423 | 0 | return aPaM; |
1424 | 0 | } |
1425 | | |
1426 | | TextPaM TextView::CursorStartOfDoc() |
1427 | 0 | { |
1428 | 0 | TextPaM aPaM( 0, 0 ); |
1429 | 0 | return aPaM; |
1430 | 0 | } |
1431 | | |
1432 | | TextPaM TextView::CursorEndOfDoc() |
1433 | 0 | { |
1434 | 0 | const sal_uInt32 nNode = static_cast<sal_uInt32>(mpTextEngine->mpDoc->GetNodes().size() - 1); |
1435 | 0 | TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[nNode].get(); |
1436 | 0 | TextPaM aPaM( nNode, pNode->GetText().getLength() ); |
1437 | 0 | return aPaM; |
1438 | 0 | } |
1439 | | |
1440 | | TextPaM TextView::PageUp( const TextPaM& rPaM ) |
1441 | 0 | { |
1442 | 0 | tools::Rectangle aRect = mpTextEngine->PaMtoEditCursor(rPaM); |
1443 | 0 | Point aTopLeft = aRect.TopLeft(); |
1444 | 0 | aTopLeft.AdjustY(-(mpWindow->GetOutputSizePixel().Height() * 9 / 10)); |
1445 | 0 | aTopLeft.AdjustX(1 ); |
1446 | 0 | if ( aTopLeft.Y() < 0 ) |
1447 | 0 | aTopLeft.setY( 0 ); |
1448 | |
|
1449 | 0 | TextPaM aPaM = mpTextEngine->GetPaM(aTopLeft); |
1450 | 0 | return aPaM; |
1451 | 0 | } |
1452 | | |
1453 | | TextPaM TextView::PageDown( const TextPaM& rPaM ) |
1454 | 0 | { |
1455 | 0 | tools::Rectangle aRect = mpTextEngine->PaMtoEditCursor(rPaM); |
1456 | 0 | Point aBottomRight = aRect.BottomRight(); |
1457 | 0 | aBottomRight.AdjustY(mpWindow->GetOutputSizePixel().Height() * 9 / 10); |
1458 | 0 | aBottomRight.AdjustX(1 ); |
1459 | 0 | tools::Long nHeight = mpTextEngine->GetTextHeight(); |
1460 | 0 | if ( aBottomRight.Y() > nHeight ) |
1461 | 0 | aBottomRight.setY( nHeight-1 ); |
1462 | |
|
1463 | 0 | TextPaM aPaM = mpTextEngine->GetPaM(aBottomRight); |
1464 | 0 | return aPaM; |
1465 | 0 | } |
1466 | | |
1467 | | void TextView::ImpShowCursor( bool bGotoCursor, bool bForceVisCursor, bool bSpecial ) |
1468 | 0 | { |
1469 | 0 | if (mpTextEngine->IsFormatting()) |
1470 | 0 | return; |
1471 | 0 | if (!mpTextEngine->GetUpdateMode()) |
1472 | 0 | return; |
1473 | 0 | if (mpTextEngine->IsInUndo()) |
1474 | 0 | return; |
1475 | | |
1476 | 0 | mpTextEngine->CheckIdleFormatter(); |
1477 | 0 | if (!mpTextEngine->IsFormatted()) |
1478 | 0 | mpTextEngine->FormatAndUpdate(this); |
1479 | |
|
1480 | 0 | TextPaM aPaM(maSelection.GetEnd()); |
1481 | 0 | tools::Rectangle aEditCursor = mpTextEngine->PaMtoEditCursor(aPaM, bSpecial); |
1482 | | |
1483 | | // Remember that we placed the cursor behind the last character of a line |
1484 | 0 | mbCursorAtEndOfLine = false; |
1485 | 0 | if( bSpecial ) |
1486 | 0 | { |
1487 | 0 | TEParaPortion* pParaPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara()); |
1488 | 0 | mbCursorAtEndOfLine = pParaPortion->GetLineNumber(aPaM.GetIndex(), true) |
1489 | 0 | != pParaPortion->GetLineNumber(aPaM.GetIndex(), false); |
1490 | 0 | } |
1491 | |
|
1492 | 0 | if (!IsInsertMode() && !maSelection.HasRange()) |
1493 | 0 | { |
1494 | 0 | TextNode* pNode = mpTextEngine->mpDoc->GetNodes()[aPaM.GetPara()].get(); |
1495 | 0 | if ( !pNode->GetText().isEmpty() && ( aPaM.GetIndex() < pNode->GetText().getLength() ) ) |
1496 | 0 | { |
1497 | | // If we are behind a portion, and the next portion has other direction, we must change position... |
1498 | 0 | aEditCursor.SetLeft(mpTextEngine->GetEditCursor(aPaM, false, true).Left()); |
1499 | 0 | aEditCursor.SetRight( aEditCursor.Left() ); |
1500 | |
|
1501 | 0 | TEParaPortion* pParaPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara()); |
1502 | |
|
1503 | 0 | sal_Int32 nTextPortionStart = 0; |
1504 | 0 | std::size_t nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, true ); |
1505 | 0 | TETextPortion& rTextPortion = pParaPortion->GetTextPortions()[ nTextPortion ]; |
1506 | 0 | if ( rTextPortion.GetKind() == PORTIONKIND_TAB ) |
1507 | 0 | { |
1508 | 0 | aEditCursor.AdjustRight(rTextPortion.GetWidth() ); |
1509 | 0 | } |
1510 | 0 | else |
1511 | 0 | { |
1512 | 0 | TextPaM aNext = CursorRight( TextPaM( aPaM.GetPara(), aPaM.GetIndex() ), sal_uInt16(css::i18n::CharacterIteratorMode::SKIPCELL) ); |
1513 | 0 | aEditCursor.SetRight(mpTextEngine->GetEditCursor(aNext, true).Left()); |
1514 | 0 | } |
1515 | 0 | } |
1516 | 0 | } |
1517 | |
|
1518 | 0 | Size aOutSz = mpWindow->GetOutputSizePixel(); |
1519 | 0 | if ( aEditCursor.GetHeight() > aOutSz.Height() ) |
1520 | 0 | aEditCursor.SetBottom( aEditCursor.Top() + aOutSz.Height() - 1 ); |
1521 | |
|
1522 | 0 | aEditCursor.AdjustLeft( -1 ); |
1523 | |
|
1524 | 0 | if ( bGotoCursor |
1525 | | // #i81283# protect maStartDocPos against initialization problems |
1526 | 0 | && aOutSz.Width() && aOutSz.Height() |
1527 | 0 | ) |
1528 | 0 | { |
1529 | 0 | tools::Long nVisStartY = maStartDocPos.Y(); |
1530 | 0 | tools::Long nVisEndY = maStartDocPos.Y() + aOutSz.Height(); |
1531 | 0 | tools::Long nVisStartX = maStartDocPos.X(); |
1532 | 0 | tools::Long nVisEndX = maStartDocPos.X() + aOutSz.Width(); |
1533 | 0 | tools::Long nMoreX = aOutSz.Width() / 4; |
1534 | |
|
1535 | 0 | Point aNewStartPos(maStartDocPos); |
1536 | |
|
1537 | 0 | if ( aEditCursor.Bottom() > nVisEndY ) |
1538 | 0 | { |
1539 | 0 | aNewStartPos.AdjustY( aEditCursor.Bottom() - nVisEndY); |
1540 | 0 | } |
1541 | 0 | else if ( aEditCursor.Top() < nVisStartY ) |
1542 | 0 | { |
1543 | 0 | aNewStartPos.AdjustY( -( nVisStartY - aEditCursor.Top() ) ); |
1544 | 0 | } |
1545 | |
|
1546 | 0 | if ( aEditCursor.Right() >= nVisEndX ) |
1547 | 0 | { |
1548 | 0 | aNewStartPos.AdjustX( aEditCursor.Right() - nVisEndX ); |
1549 | | |
1550 | | // do you want some more? |
1551 | 0 | aNewStartPos.AdjustX(nMoreX ); |
1552 | 0 | } |
1553 | 0 | else if ( aEditCursor.Left() <= nVisStartX ) |
1554 | 0 | { |
1555 | 0 | aNewStartPos.AdjustX( -( nVisStartX - aEditCursor.Left() ) ); |
1556 | | |
1557 | | // do you want some more? |
1558 | 0 | aNewStartPos.AdjustX( -nMoreX ); |
1559 | 0 | } |
1560 | | |
1561 | | // X can be wrong for the 'some more' above: |
1562 | | // sal_uLong nMaxTextWidth = mpTextEngine->GetMaxTextWidth(); |
1563 | | // if ( !nMaxTextWidth || ( nMaxTextWidth > 0x7FFFFFFF ) ) |
1564 | | // nMaxTextWidth = 0x7FFFFFFF; |
1565 | | // long nMaxX = (long)nMaxTextWidth - aOutSz.Width(); |
1566 | 0 | tools::Long nMaxX = mpTextEngine->CalcTextWidth() - aOutSz.Width(); |
1567 | 0 | if ( nMaxX < 0 ) |
1568 | 0 | nMaxX = 0; |
1569 | |
|
1570 | 0 | if ( aNewStartPos.X() < 0 ) |
1571 | 0 | aNewStartPos.setX( 0 ); |
1572 | 0 | else if ( aNewStartPos.X() > nMaxX ) |
1573 | 0 | aNewStartPos.setX( nMaxX ); |
1574 | | |
1575 | | // Y should not be further down than needed |
1576 | 0 | tools::Long nYMax = mpTextEngine->GetTextHeight() - aOutSz.Height(); |
1577 | 0 | if ( nYMax < 0 ) |
1578 | 0 | nYMax = 0; |
1579 | 0 | if ( aNewStartPos.Y() > nYMax ) |
1580 | 0 | aNewStartPos.setY( nYMax ); |
1581 | |
|
1582 | 0 | if (aNewStartPos != maStartDocPos) |
1583 | 0 | Scroll(-(aNewStartPos.X() - maStartDocPos.X()), -(aNewStartPos.Y() - maStartDocPos.Y())); |
1584 | 0 | } |
1585 | |
|
1586 | 0 | if ( aEditCursor.Right() < aEditCursor.Left() ) |
1587 | 0 | { |
1588 | 0 | tools::Long n = aEditCursor.Left(); |
1589 | 0 | aEditCursor.SetLeft( aEditCursor.Right() ); |
1590 | 0 | aEditCursor.SetRight( n ); |
1591 | 0 | } |
1592 | |
|
1593 | 0 | Point aPoint(GetWindowPos(!mpTextEngine->IsRightToLeft() ? aEditCursor.TopLeft() |
1594 | 0 | : aEditCursor.TopRight())); |
1595 | 0 | mpCursor->SetPos(aPoint); |
1596 | 0 | mpCursor->SetSize(aEditCursor.GetSize()); |
1597 | 0 | if (bForceVisCursor && mbCursorEnabled) |
1598 | 0 | mpCursor->Show(); |
1599 | 0 | } |
1600 | | |
1601 | | void TextView::SetCursorAtPoint( const Point& rPosPixel ) |
1602 | 0 | { |
1603 | 0 | mpTextEngine->CheckIdleFormatter(); |
1604 | |
|
1605 | 0 | Point aDocPos = GetDocPos( rPosPixel ); |
1606 | |
|
1607 | 0 | TextPaM aPaM = mpTextEngine->GetPaM(aDocPos); |
1608 | | |
1609 | | // aTmpNewSel: Diff between old and new; not the new selection |
1610 | 0 | TextSelection aTmpNewSel(maSelection.GetEnd(), aPaM); |
1611 | 0 | TextSelection aNewSel(maSelection); |
1612 | 0 | aNewSel.GetEnd() = aPaM; |
1613 | |
|
1614 | 0 | if (!mpSelEngine->HasAnchor()) |
1615 | 0 | { |
1616 | 0 | if (maSelection.GetStart() != aPaM) |
1617 | 0 | mpTextEngine->CursorMoved(maSelection.GetStart().GetPara()); |
1618 | 0 | aNewSel.GetStart() = aPaM; |
1619 | 0 | ImpSetSelection( aNewSel ); |
1620 | 0 | } |
1621 | 0 | else |
1622 | 0 | { |
1623 | 0 | ImpSetSelection( aNewSel ); |
1624 | 0 | ShowSelection( aTmpNewSel ); |
1625 | 0 | } |
1626 | |
|
1627 | 0 | bool bForceCursor = !mpDDInfo; // && !mbInSelection |
1628 | 0 | ImpShowCursor(mbAutoScroll, bForceCursor, false); |
1629 | 0 | } |
1630 | | |
1631 | | bool TextView::IsSelectionAtPoint( const Point& rPosPixel ) |
1632 | 0 | { |
1633 | 0 | Point aDocPos = GetDocPos( rPosPixel ); |
1634 | 0 | TextPaM aPaM = mpTextEngine->GetPaM(aDocPos); |
1635 | | // BeginDrag is only called, however, if IsSelectionAtPoint() |
1636 | | // Problem: IsSelectionAtPoint is not called by Command() |
1637 | | // if before MBDown returned false. |
1638 | 0 | return IsInSelection( aPaM ); |
1639 | 0 | } |
1640 | | |
1641 | | bool TextView::IsInSelection( const TextPaM& rPaM ) const |
1642 | 0 | { |
1643 | 0 | TextSelection aSel = maSelection; |
1644 | 0 | aSel.Justify(); |
1645 | |
|
1646 | 0 | const sal_uInt32 nStartNode = aSel.GetStart().GetPara(); |
1647 | 0 | const sal_uInt32 nEndNode = aSel.GetEnd().GetPara(); |
1648 | 0 | const sal_uInt32 nCurNode = rPaM.GetPara(); |
1649 | |
|
1650 | 0 | if ( ( nCurNode > nStartNode ) && ( nCurNode < nEndNode ) ) |
1651 | 0 | return true; |
1652 | | |
1653 | 0 | if ( nStartNode == nEndNode ) |
1654 | 0 | { |
1655 | 0 | if ( nCurNode == nStartNode ) |
1656 | 0 | if ( ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) ) |
1657 | 0 | return true; |
1658 | 0 | } |
1659 | 0 | else if ( ( nCurNode == nStartNode ) && ( rPaM.GetIndex() >= aSel.GetStart().GetIndex() ) ) |
1660 | 0 | return true; |
1661 | 0 | else if ( ( nCurNode == nEndNode ) && ( rPaM.GetIndex() < aSel.GetEnd().GetIndex() ) ) |
1662 | 0 | return true; |
1663 | | |
1664 | 0 | return false; |
1665 | 0 | } |
1666 | | |
1667 | | void TextView::ImpHideDDCursor() |
1668 | 0 | { |
1669 | 0 | if (mpDDInfo && mpDDInfo->mbVisCursor) |
1670 | 0 | { |
1671 | 0 | mpDDInfo->maCursor.Hide(); |
1672 | 0 | mpDDInfo->mbVisCursor = false; |
1673 | 0 | } |
1674 | 0 | } |
1675 | | |
1676 | | void TextView::ImpShowDDCursor() |
1677 | 0 | { |
1678 | 0 | if (!mpDDInfo->mbVisCursor) |
1679 | 0 | { |
1680 | 0 | tools::Rectangle aCursor = mpTextEngine->PaMtoEditCursor(mpDDInfo->maDropPos, true); |
1681 | 0 | aCursor.AdjustRight( 1 ); |
1682 | 0 | aCursor.SetPos( GetWindowPos( aCursor.TopLeft() ) ); |
1683 | |
|
1684 | 0 | mpDDInfo->maCursor.SetWindow(mpWindow); |
1685 | 0 | mpDDInfo->maCursor.SetPos(aCursor.TopLeft()); |
1686 | 0 | mpDDInfo->maCursor.SetSize(aCursor.GetSize()); |
1687 | 0 | mpDDInfo->maCursor.Show(); |
1688 | 0 | mpDDInfo->mbVisCursor = true; |
1689 | 0 | } |
1690 | 0 | } |
1691 | | |
1692 | | void TextView::SetPaintSelection( bool bPaint ) |
1693 | 0 | { |
1694 | 0 | if (bPaint != mbPaintSelection) |
1695 | 0 | { |
1696 | 0 | mbPaintSelection = bPaint; |
1697 | 0 | ShowSelection(maSelection); |
1698 | 0 | } |
1699 | 0 | } |
1700 | | |
1701 | | void TextView::Read( SvStream& rInput ) |
1702 | 0 | { |
1703 | 0 | mpTextEngine->Read(rInput, &maSelection); |
1704 | 0 | ShowCursor(); |
1705 | 0 | } |
1706 | | |
1707 | | bool TextView::ImplTruncateNewText( OUString& rNewText ) const |
1708 | 0 | { |
1709 | 0 | bool bTruncated = false; |
1710 | |
|
1711 | 0 | const sal_Int32 nMaxLen = mpTextEngine->GetMaxTextLen(); |
1712 | | // 0 means unlimited |
1713 | 0 | if( nMaxLen != 0 ) |
1714 | 0 | { |
1715 | 0 | const sal_Int32 nCurLen = mpTextEngine->GetTextLen(); |
1716 | |
|
1717 | 0 | const sal_Int32 nNewLen = rNewText.getLength(); |
1718 | 0 | if ( nCurLen + nNewLen > nMaxLen ) |
1719 | 0 | { |
1720 | | // see how much text will be replaced |
1721 | 0 | const sal_Int32 nSelLen = mpTextEngine->GetTextLen(maSelection); |
1722 | 0 | if ( nCurLen + nNewLen - nSelLen > nMaxLen ) |
1723 | 0 | { |
1724 | 0 | const sal_Int32 nTruncatedLen = nMaxLen - (nCurLen - nSelLen); |
1725 | 0 | rNewText = rNewText.copy( 0, nTruncatedLen ); |
1726 | 0 | bTruncated = true; |
1727 | 0 | } |
1728 | 0 | } |
1729 | 0 | } |
1730 | 0 | return bTruncated; |
1731 | 0 | } |
1732 | | |
1733 | | bool TextView::ImplCheckTextLen( std::u16string_view rNewText ) const |
1734 | 0 | { |
1735 | 0 | bool bOK = true; |
1736 | 0 | if (mpTextEngine->GetMaxTextLen()) |
1737 | 0 | { |
1738 | 0 | sal_Int32 n = mpTextEngine->GetTextLen() + rNewText.size(); |
1739 | 0 | if (n > mpTextEngine->GetMaxTextLen()) |
1740 | 0 | { |
1741 | | // calculate how much text is being deleted |
1742 | 0 | n -= mpTextEngine->GetTextLen(maSelection); |
1743 | 0 | if (n > mpTextEngine->GetMaxTextLen()) |
1744 | 0 | bOK = false; |
1745 | 0 | } |
1746 | 0 | } |
1747 | 0 | return bOK; |
1748 | 0 | } |
1749 | | |
1750 | | void TextView::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE ) |
1751 | 0 | { |
1752 | 0 | if (!mbClickedInSelection) |
1753 | 0 | return; |
1754 | | |
1755 | 0 | SolarMutexGuard aVclGuard; |
1756 | |
|
1757 | 0 | SAL_WARN_IF(!maSelection.HasRange(), "vcl", |
1758 | 0 | "TextView::dragGestureRecognized: mbClickedInSelection, but no selection?"); |
1759 | | |
1760 | 0 | mpDDInfo.reset(new TextDDInfo); |
1761 | 0 | mpDDInfo->mbStarterOfDD = true; |
1762 | |
|
1763 | 0 | rtl::Reference<TETextDataObject> pDataObj = new TETextDataObject( GetSelected() ); |
1764 | |
|
1765 | 0 | mpCursor->Hide(); |
1766 | |
|
1767 | 0 | sal_Int8 nActions = css::datatransfer::dnd::DNDConstants::ACTION_COPY; |
1768 | 0 | if ( !IsReadOnly() ) |
1769 | 0 | nActions |= css::datatransfer::dnd::DNDConstants::ACTION_MOVE; |
1770 | 0 | rDGE.DragSource->startDrag(rDGE, nActions, 0 /*cursor*/, 0 /*image*/, pDataObj, mxDnDListener); |
1771 | 0 | } |
1772 | | |
1773 | | void TextView::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& ) |
1774 | 0 | { |
1775 | 0 | ImpHideDDCursor(); |
1776 | 0 | mpDDInfo.reset(); |
1777 | 0 | } |
1778 | | |
1779 | | void TextView::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE ) |
1780 | 0 | { |
1781 | 0 | SolarMutexGuard aVclGuard; |
1782 | |
|
1783 | 0 | if (!mbReadOnly && mpDDInfo) |
1784 | 0 | { |
1785 | 0 | ImpHideDDCursor(); |
1786 | | |
1787 | | // Data for deleting after DROP_MOVE: |
1788 | 0 | TextSelection aPrevSel(maSelection); |
1789 | 0 | aPrevSel.Justify(); |
1790 | 0 | const sal_uInt32 nPrevParaCount = mpTextEngine->GetParagraphCount(); |
1791 | 0 | const sal_Int32 nPrevStartParaLen = mpTextEngine->GetTextLen(aPrevSel.GetStart().GetPara()); |
1792 | |
|
1793 | 0 | bool bStarterOfDD = false; |
1794 | 0 | for (sal_uInt16 nView = mpTextEngine->GetViewCount(); nView && !bStarterOfDD; ) |
1795 | 0 | bStarterOfDD = mpTextEngine->GetView( --nView )->mpDDInfo && mpTextEngine->GetView( nView )->mpDDInfo->mbStarterOfDD; |
1796 | |
|
1797 | 0 | HideSelection(); |
1798 | 0 | ImpSetSelection(mpDDInfo->maDropPos); |
1799 | |
|
1800 | 0 | mpTextEngine->UndoActionStart(); |
1801 | |
|
1802 | 0 | OUString aText; |
1803 | 0 | css::uno::Reference< css::datatransfer::XTransferable > xDataObj = rDTDE.Transferable; |
1804 | 0 | if ( xDataObj.is() ) |
1805 | 0 | { |
1806 | 0 | css::datatransfer::DataFlavor aFlavor; |
1807 | 0 | SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor ); |
1808 | 0 | if ( xDataObj->isDataFlavorSupported( aFlavor ) ) |
1809 | 0 | { |
1810 | 0 | css::uno::Any aData = xDataObj->getTransferData( aFlavor ); |
1811 | 0 | OUString aOUString; |
1812 | 0 | aData >>= aOUString; |
1813 | 0 | aText = convertLineEnd(aOUString, LINEEND_LF); |
1814 | 0 | } |
1815 | 0 | } |
1816 | |
|
1817 | 0 | if ( !aText.isEmpty() && ( aText[ aText.getLength()-1 ] == LINE_SEP ) ) |
1818 | 0 | aText = aText.copy(0, aText.getLength()-1); |
1819 | |
|
1820 | 0 | if ( ImplCheckTextLen( aText ) ) |
1821 | 0 | ImpSetSelection(mpTextEngine->ImpInsertText(mpDDInfo->maDropPos, aText)); |
1822 | |
|
1823 | 0 | if ( aPrevSel.HasRange() && |
1824 | 0 | (( rDTDE.DropAction & css::datatransfer::dnd::DNDConstants::ACTION_MOVE ) || !bStarterOfDD) ) |
1825 | 0 | { |
1826 | | // adjust selection if necessary |
1827 | 0 | if ((mpDDInfo->maDropPos.GetPara() < aPrevSel.GetStart().GetPara()) || |
1828 | 0 | ((mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara()) |
1829 | 0 | && (mpDDInfo->maDropPos.GetIndex() < aPrevSel.GetStart().GetIndex()))) |
1830 | 0 | { |
1831 | 0 | const sal_uInt32 nNewParasBeforeSelection |
1832 | 0 | = mpTextEngine->GetParagraphCount() - nPrevParaCount; |
1833 | |
|
1834 | 0 | aPrevSel.GetStart().GetPara() += nNewParasBeforeSelection; |
1835 | 0 | aPrevSel.GetEnd().GetPara() += nNewParasBeforeSelection; |
1836 | |
|
1837 | 0 | if (mpDDInfo->maDropPos.GetPara() == aPrevSel.GetStart().GetPara()) |
1838 | 0 | { |
1839 | 0 | const sal_Int32 nNewChars |
1840 | 0 | = mpTextEngine->GetTextLen(aPrevSel.GetStart().GetPara()) - nPrevStartParaLen; |
1841 | |
|
1842 | 0 | aPrevSel.GetStart().GetIndex() += nNewChars; |
1843 | 0 | if ( aPrevSel.GetStart().GetPara() == aPrevSel.GetEnd().GetPara() ) |
1844 | 0 | aPrevSel.GetEnd().GetIndex() += nNewChars; |
1845 | 0 | } |
1846 | 0 | } |
1847 | 0 | else |
1848 | 0 | { |
1849 | | // adjust current selection |
1850 | 0 | TextPaM aPaM = maSelection.GetStart(); |
1851 | 0 | aPaM.GetPara() -= ( aPrevSel.GetEnd().GetPara() - aPrevSel.GetStart().GetPara() ); |
1852 | 0 | if (aPrevSel.GetEnd().GetPara() == mpDDInfo->maDropPos.GetPara()) |
1853 | 0 | { |
1854 | 0 | aPaM.GetIndex() -= aPrevSel.GetEnd().GetIndex(); |
1855 | 0 | if (aPrevSel.GetStart().GetPara() == mpDDInfo->maDropPos.GetPara()) |
1856 | 0 | aPaM.GetIndex() += aPrevSel.GetStart().GetIndex(); |
1857 | 0 | } |
1858 | 0 | ImpSetSelection( aPaM ); |
1859 | |
|
1860 | 0 | } |
1861 | 0 | mpTextEngine->ImpDeleteText(aPrevSel); |
1862 | 0 | } |
1863 | |
|
1864 | 0 | mpTextEngine->UndoActionEnd(); |
1865 | |
|
1866 | 0 | mpDDInfo.reset(); |
1867 | |
|
1868 | 0 | mpTextEngine->FormatAndUpdate(this); |
1869 | |
|
1870 | 0 | mpTextEngine->Broadcast(TextHint(SfxHintId::TextModified)); |
1871 | 0 | } |
1872 | 0 | rDTDE.Context->dropComplete( false/*bChanges*/ ); |
1873 | 0 | } |
1874 | | |
1875 | | void TextView::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& ) |
1876 | 0 | { |
1877 | 0 | } |
1878 | | |
1879 | | void TextView::dragExit( const css::datatransfer::dnd::DropTargetEvent& ) |
1880 | 0 | { |
1881 | 0 | SolarMutexGuard aVclGuard; |
1882 | 0 | ImpHideDDCursor(); |
1883 | 0 | } |
1884 | | |
1885 | | void TextView::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& rDTDE ) |
1886 | 0 | { |
1887 | 0 | SolarMutexGuard aVclGuard; |
1888 | |
|
1889 | 0 | if (!mpDDInfo) |
1890 | 0 | mpDDInfo.reset(new TextDDInfo); |
1891 | |
|
1892 | 0 | TextPaM aPrevDropPos = mpDDInfo->maDropPos; |
1893 | 0 | Point aMousePos( rDTDE.LocationX, rDTDE.LocationY ); |
1894 | 0 | Point aDocPos = GetDocPos( aMousePos ); |
1895 | 0 | mpDDInfo->maDropPos = mpTextEngine->GetPaM(aDocPos); |
1896 | | |
1897 | | // Don't drop in selection or in read only engine |
1898 | 0 | if (IsReadOnly() || IsInSelection(mpDDInfo->maDropPos)) |
1899 | 0 | { |
1900 | 0 | ImpHideDDCursor(); |
1901 | 0 | rDTDE.Context->rejectDrag(); |
1902 | 0 | } |
1903 | 0 | else |
1904 | 0 | { |
1905 | | // delete old Cursor |
1906 | 0 | if (!mpDDInfo->mbVisCursor || (aPrevDropPos != mpDDInfo->maDropPos)) |
1907 | 0 | { |
1908 | 0 | ImpHideDDCursor(); |
1909 | 0 | ImpShowDDCursor(); |
1910 | 0 | } |
1911 | 0 | rDTDE.Context->acceptDrag( rDTDE.DropAction ); |
1912 | 0 | } |
1913 | 0 | } |
1914 | | |
1915 | | Point TextView::ImpGetOutputStartPos( const Point& rStartDocPos ) const |
1916 | 0 | { |
1917 | 0 | Point aStartPos( -rStartDocPos.X(), -rStartDocPos.Y() ); |
1918 | 0 | if (mpTextEngine->IsRightToLeft()) |
1919 | 0 | { |
1920 | 0 | Size aSz = mpWindow->GetOutputSizePixel(); |
1921 | 0 | aStartPos.setX( rStartDocPos.X() + aSz.Width() - 1 ); // -1: Start is 0 |
1922 | 0 | } |
1923 | 0 | return aStartPos; |
1924 | 0 | } |
1925 | | |
1926 | | Point TextView::GetDocPos( const Point& rWindowPos ) const |
1927 | 0 | { |
1928 | | // Window Position => Document Position |
1929 | |
|
1930 | 0 | Point aPoint; |
1931 | |
|
1932 | 0 | aPoint.setY(rWindowPos.Y() + maStartDocPos.Y()); |
1933 | |
|
1934 | 0 | if (!mpTextEngine->IsRightToLeft()) |
1935 | 0 | { |
1936 | 0 | aPoint.setX(rWindowPos.X() + maStartDocPos.X()); |
1937 | 0 | } |
1938 | 0 | else |
1939 | 0 | { |
1940 | 0 | Size aSz = mpWindow->GetOutputSizePixel(); |
1941 | 0 | aPoint.setX((aSz.Width() - 1) - rWindowPos.X() + maStartDocPos.X()); |
1942 | 0 | } |
1943 | |
|
1944 | 0 | return aPoint; |
1945 | 0 | } |
1946 | | |
1947 | | Point TextView::GetWindowPos( const Point& rDocPos ) const |
1948 | 0 | { |
1949 | | // Document Position => Window Position |
1950 | |
|
1951 | 0 | Point aPoint; |
1952 | |
|
1953 | 0 | aPoint.setY(rDocPos.Y() - maStartDocPos.Y()); |
1954 | |
|
1955 | 0 | if (!mpTextEngine->IsRightToLeft()) |
1956 | 0 | { |
1957 | 0 | aPoint.setX(rDocPos.X() - maStartDocPos.X()); |
1958 | 0 | } |
1959 | 0 | else |
1960 | 0 | { |
1961 | 0 | Size aSz = mpWindow->GetOutputSizePixel(); |
1962 | 0 | aPoint.setX((aSz.Width() - 1) - (rDocPos.X() - maStartDocPos.X())); |
1963 | 0 | } |
1964 | |
|
1965 | 0 | return aPoint; |
1966 | 0 | } |
1967 | | |
1968 | | sal_Int32 TextView::GetLineNumberOfCursorInSelection() const |
1969 | 0 | { |
1970 | | // PROGRESS |
1971 | 0 | sal_Int32 nLineNo = -1; |
1972 | 0 | if (mbCursorEnabled) |
1973 | 0 | { |
1974 | 0 | TextPaM aPaM = GetSelection().GetEnd(); |
1975 | 0 | TEParaPortion* pPPortion = mpTextEngine->mpTEParaPortions->GetObject(aPaM.GetPara()); |
1976 | 0 | nLineNo = pPPortion->GetLineNumber( aPaM.GetIndex(), false ); |
1977 | | //TODO: std::vector<TextLine>::size_type -> sal_Int32! |
1978 | 0 | if (mbCursorAtEndOfLine) |
1979 | 0 | --nLineNo; |
1980 | 0 | } |
1981 | 0 | return nLineNo; |
1982 | 0 | } |
1983 | | |
1984 | | // (+) class TextSelFunctionSet |
1985 | | |
1986 | | TextSelFunctionSet::TextSelFunctionSet( TextView* pView ) |
1987 | 0 | { |
1988 | 0 | mpView = pView; |
1989 | 0 | } |
1990 | | |
1991 | | void TextSelFunctionSet::BeginDrag() |
1992 | 0 | { |
1993 | 0 | } |
1994 | | |
1995 | | void TextSelFunctionSet::CreateAnchor() |
1996 | 0 | { |
1997 | | // TextSelection aSel( mpView->GetSelection() ); |
1998 | | // aSel.GetStart() = aSel.GetEnd(); |
1999 | | // mpView->SetSelection( aSel ); |
2000 | | |
2001 | | // may not be followed by ShowCursor |
2002 | 0 | mpView->HideSelection(); |
2003 | 0 | mpView->ImpSetSelection(mpView->maSelection.GetEnd()); |
2004 | 0 | } |
2005 | | |
2006 | | void TextSelFunctionSet::SetCursorAtPoint( const Point& rPointPixel, bool ) |
2007 | 0 | { |
2008 | 0 | mpView->SetCursorAtPoint( rPointPixel ); |
2009 | 0 | } |
2010 | | |
2011 | | bool TextSelFunctionSet::IsSelectionAtPoint( const Point& rPointPixel ) |
2012 | 0 | { |
2013 | 0 | return mpView->IsSelectionAtPoint( rPointPixel ); |
2014 | 0 | } |
2015 | | |
2016 | | void TextSelFunctionSet::DeselectAll() |
2017 | 0 | { |
2018 | 0 | CreateAnchor(); |
2019 | 0 | } |
2020 | | |
2021 | | void TextSelFunctionSet::DeselectAtPoint( const Point& ) |
2022 | 0 | { |
2023 | | // only for multiple selection |
2024 | 0 | } |
2025 | | |
2026 | | void TextSelFunctionSet::DestroyAnchor() |
2027 | 0 | { |
2028 | | // only for multiple selection |
2029 | 0 | } |
2030 | 0 | TextEngine* TextView::GetTextEngine() const { return mpTextEngine; } |
2031 | | |
2032 | 0 | vcl::Window* TextView::GetWindow() const { return mpWindow; } |
2033 | | |
2034 | 0 | void TextView::EnableCursor(bool bEnable) { mbCursorEnabled = bEnable; } |
2035 | | |
2036 | 0 | bool TextView::IsCursorEnabled() const { return mbCursorEnabled; } |
2037 | | |
2038 | 0 | void TextView::SetStartDocPos(const Point& rPos) { maStartDocPos = rPos; } |
2039 | | |
2040 | 0 | const Point& TextView::GetStartDocPos() const { return maStartDocPos; } |
2041 | | |
2042 | 0 | void TextView::SetAutoIndentMode(bool bAutoIndent) { mbAutoIndent = bAutoIndent; } |
2043 | | |
2044 | 0 | bool TextView::IsReadOnly() const { return mbReadOnly; } |
2045 | | |
2046 | 0 | void TextView::SetAutoScroll(bool bAutoScroll) { mbAutoScroll = bAutoScroll; } |
2047 | | |
2048 | 0 | bool TextView::IsAutoScroll() const { return mbAutoScroll; } |
2049 | | |
2050 | 0 | bool TextView::HasSelection() const { return maSelection.HasRange(); } |
2051 | | |
2052 | 0 | bool TextView::IsInsertMode() const { return mbInsertMode; } |
2053 | | |
2054 | | void TextView::MatchGroup() |
2055 | 0 | { |
2056 | 0 | TextSelection aTmpSel( GetSelection() ); |
2057 | 0 | aTmpSel.Justify(); |
2058 | 0 | if ( ( aTmpSel.GetStart().GetPara() != aTmpSel.GetEnd().GetPara() ) || |
2059 | 0 | ( ( aTmpSel.GetEnd().GetIndex() - aTmpSel.GetStart().GetIndex() ) > 1 ) ) |
2060 | 0 | { |
2061 | 0 | return; |
2062 | 0 | } |
2063 | | |
2064 | 0 | TextSelection aMatchSel = static_cast<ExtTextEngine*>(GetTextEngine())->MatchGroup( aTmpSel.GetStart() ); |
2065 | 0 | if ( aMatchSel.HasRange() ) |
2066 | 0 | SetSelection( aMatchSel ); |
2067 | 0 | } |
2068 | | |
2069 | | void TextView::CenterPaM( const TextPaM& rPaM ) |
2070 | 0 | { |
2071 | | // Get textview size and the corresponding y-coordinates |
2072 | 0 | Size aOutSz = mpWindow->GetOutputSizePixel(); |
2073 | 0 | tools::Long nVisStartY = maStartDocPos.Y(); |
2074 | 0 | tools::Long nVisEndY = maStartDocPos.Y() + aOutSz.Height(); |
2075 | | |
2076 | | // Retrieve the coordinates of the PaM |
2077 | 0 | tools::Rectangle aRect = mpTextEngine->PaMtoEditCursor(rPaM); |
2078 | | |
2079 | | // Recalculate the offset of the center y-coordinates and scroll |
2080 | 0 | Scroll(0, (nVisStartY + nVisEndY) / 2 - aRect.TopLeft().getY()); |
2081 | 0 | } |
2082 | | |
2083 | | bool TextView::Search( const i18nutil::SearchOptions2& rSearchOptions, bool bForward ) |
2084 | 0 | { |
2085 | 0 | bool bFound = false; |
2086 | 0 | TextSelection aSel( GetSelection() ); |
2087 | 0 | if ( static_cast<ExtTextEngine*>(GetTextEngine())->Search( aSel, rSearchOptions, bForward ) ) |
2088 | 0 | { |
2089 | 0 | bFound = true; |
2090 | | // First add the beginning of the word to the selection, |
2091 | | // so that the whole word is in the visible region. |
2092 | 0 | SetSelection( aSel.GetStart() ); |
2093 | 0 | ShowCursor( true, false ); |
2094 | 0 | } |
2095 | 0 | else |
2096 | 0 | { |
2097 | 0 | aSel = GetSelection().GetEnd(); |
2098 | 0 | } |
2099 | |
|
2100 | 0 | SetSelection( aSel ); |
2101 | | // tdf#49482: Move the start of the selection to the center of the textview |
2102 | 0 | if (bFound) |
2103 | 0 | { |
2104 | 0 | CenterPaM( aSel.GetStart() ); |
2105 | 0 | } |
2106 | 0 | ShowCursor(); |
2107 | |
|
2108 | 0 | return bFound; |
2109 | 0 | } |
2110 | | |
2111 | | sal_uInt16 TextView::Replace( const i18nutil::SearchOptions2& rSearchOptions, bool bAll, bool bForward ) |
2112 | 0 | { |
2113 | 0 | sal_uInt16 nFound = 0; |
2114 | |
|
2115 | 0 | if ( !bAll ) |
2116 | 0 | { |
2117 | 0 | if ( GetSelection().HasRange() ) |
2118 | 0 | { |
2119 | 0 | InsertText( rSearchOptions.replaceString ); |
2120 | 0 | nFound = 1; |
2121 | 0 | Search( rSearchOptions, bForward ); // right away to the next |
2122 | 0 | } |
2123 | 0 | else |
2124 | 0 | { |
2125 | 0 | if( Search( rSearchOptions, bForward ) ) |
2126 | 0 | nFound = 1; |
2127 | 0 | } |
2128 | 0 | } |
2129 | 0 | else |
2130 | 0 | { |
2131 | | // the writer replaces all, from beginning to end |
2132 | |
|
2133 | 0 | ExtTextEngine* pTextEngine = static_cast<ExtTextEngine*>(GetTextEngine()); |
2134 | | |
2135 | | // HideSelection(); |
2136 | 0 | TextSelection aSel; |
2137 | |
|
2138 | 0 | bool bSearchInSelection = (0 != (rSearchOptions.searchFlag & css::util::SearchFlags::REG_NOT_BEGINOFLINE) ); |
2139 | 0 | if ( bSearchInSelection ) |
2140 | 0 | { |
2141 | 0 | aSel = GetSelection(); |
2142 | 0 | aSel.Justify(); |
2143 | 0 | } |
2144 | |
|
2145 | 0 | TextSelection aSearchSel( aSel ); |
2146 | |
|
2147 | 0 | bool bFound = pTextEngine->Search( aSel, rSearchOptions ); |
2148 | 0 | if ( bFound ) |
2149 | 0 | pTextEngine->UndoActionStart(); |
2150 | 0 | while ( bFound ) |
2151 | 0 | { |
2152 | 0 | nFound++; |
2153 | |
|
2154 | 0 | TextPaM aNewStart = pTextEngine->ImpInsertText( aSel, rSearchOptions.replaceString ); |
2155 | | // tdf#64690 - extend selection to include inserted text portions |
2156 | 0 | if ( aSel.GetEnd().GetPara() == aSearchSel.GetEnd().GetPara() ) |
2157 | 0 | { |
2158 | 0 | aSearchSel.GetEnd().GetIndex() += rSearchOptions.replaceString.getLength() - 1; |
2159 | 0 | } |
2160 | 0 | aSel = aSearchSel; |
2161 | 0 | aSel.GetStart() = aNewStart; |
2162 | 0 | bFound = pTextEngine->Search( aSel, rSearchOptions ); |
2163 | 0 | } |
2164 | 0 | if ( nFound ) |
2165 | 0 | { |
2166 | 0 | SetSelection( aSel.GetStart() ); |
2167 | 0 | pTextEngine->FormatAndUpdate( this ); |
2168 | 0 | pTextEngine->UndoActionEnd(); |
2169 | 0 | } |
2170 | 0 | } |
2171 | 0 | return nFound; |
2172 | 0 | } |
2173 | | |
2174 | | bool TextView::ImpIndentBlock( bool bRight ) |
2175 | 0 | { |
2176 | 0 | bool bDone = false; |
2177 | |
|
2178 | 0 | TextSelection aSel = GetSelection(); |
2179 | 0 | aSel.Justify(); |
2180 | |
|
2181 | 0 | HideSelection(); |
2182 | 0 | GetTextEngine()->UndoActionStart(); |
2183 | |
|
2184 | 0 | const sal_uInt32 nStartPara = aSel.GetStart().GetPara(); |
2185 | 0 | sal_uInt32 nEndPara = aSel.GetEnd().GetPara(); |
2186 | 0 | if ( aSel.HasRange() && !aSel.GetEnd().GetIndex() ) |
2187 | 0 | { |
2188 | 0 | nEndPara--; // do not indent |
2189 | 0 | } |
2190 | |
|
2191 | 0 | for ( sal_uInt32 nPara = nStartPara; nPara <= nEndPara; ++nPara ) |
2192 | 0 | { |
2193 | 0 | if ( bRight ) |
2194 | 0 | { |
2195 | | // add tabs |
2196 | 0 | GetTextEngine()->ImpInsertText( TextPaM( nPara, 0 ), '\t' ); |
2197 | 0 | bDone = true; |
2198 | 0 | } |
2199 | 0 | else |
2200 | 0 | { |
2201 | | // remove Tabs/Blanks |
2202 | 0 | OUString aText = GetTextEngine()->GetText( nPara ); |
2203 | 0 | if ( !aText.isEmpty() && ( |
2204 | 0 | ( aText[ 0 ] == '\t' ) || |
2205 | 0 | ( aText[ 0 ] == ' ' ) ) ) |
2206 | 0 | { |
2207 | 0 | GetTextEngine()->ImpDeleteText( TextSelection( TextPaM( nPara, 0 ), TextPaM( nPara, 1 ) ) ); |
2208 | 0 | bDone = true; |
2209 | 0 | } |
2210 | 0 | } |
2211 | 0 | } |
2212 | |
|
2213 | 0 | GetTextEngine()->UndoActionEnd(); |
2214 | |
|
2215 | 0 | bool bRange = aSel.HasRange(); |
2216 | 0 | if ( bRight ) |
2217 | 0 | { |
2218 | 0 | ++aSel.GetStart().GetIndex(); |
2219 | 0 | if ( bRange && ( aSel.GetEnd().GetPara() == nEndPara ) ) |
2220 | 0 | ++aSel.GetEnd().GetIndex(); |
2221 | 0 | } |
2222 | 0 | else |
2223 | 0 | { |
2224 | 0 | if ( aSel.GetStart().GetIndex() ) |
2225 | 0 | --aSel.GetStart().GetIndex(); |
2226 | 0 | if ( bRange && aSel.GetEnd().GetIndex() ) |
2227 | 0 | --aSel.GetEnd().GetIndex(); |
2228 | 0 | } |
2229 | |
|
2230 | 0 | ImpSetSelection( aSel ); |
2231 | 0 | GetTextEngine()->FormatAndUpdate( this ); |
2232 | |
|
2233 | 0 | return bDone; |
2234 | 0 | } |
2235 | | |
2236 | | bool TextView::IndentBlock() |
2237 | 0 | { |
2238 | 0 | return ImpIndentBlock( true ); |
2239 | 0 | } |
2240 | | |
2241 | | bool TextView::UnindentBlock() |
2242 | 0 | { |
2243 | 0 | return ImpIndentBlock( false ); |
2244 | 0 | } |
2245 | | |
2246 | | void TextView::ToggleComment() |
2247 | 0 | { |
2248 | | /* To determines whether to add or remove comment markers, the rule is: |
2249 | | * - If any of the lines in the selection does not start with a comment character "'" |
2250 | | * or "REM" then the selection is commented |
2251 | | * - Otherwise, the selection is uncommented (i.e. if all of the lines start with a |
2252 | | * comment marker "'" or "REM") |
2253 | | * - Empty lines, or lines with only blank spaces or tabs are ignored |
2254 | | */ |
2255 | |
|
2256 | 0 | TextEngine* pEngine = GetTextEngine(); |
2257 | 0 | TextSelection aSel = GetSelection(); |
2258 | 0 | sal_uInt32 nStartPara = aSel.GetStart().GetPara(); |
2259 | 0 | sal_uInt32 nEndPara = aSel.GetEnd().GetPara(); |
2260 | | |
2261 | | // True = Comment character will be added; False = Comment marker will be removed |
2262 | 0 | bool bAddCommentChar = false; |
2263 | | |
2264 | | // Indicates whether any change has been made |
2265 | 0 | bool bChanged = false; |
2266 | | |
2267 | | // Indicates whether the selection is downwards (normal) or upwards (reversed) |
2268 | 0 | bool bSelReversed = false; |
2269 | |
|
2270 | 0 | if (nEndPara < nStartPara) |
2271 | 0 | { |
2272 | 0 | std::swap(nStartPara, nEndPara); |
2273 | 0 | bSelReversed = true; |
2274 | 0 | } |
2275 | |
|
2276 | 0 | for (sal_uInt32 n = nStartPara; n <= nEndPara; n++) |
2277 | 0 | { |
2278 | 0 | OUString sText = pEngine->GetText(n).trim(); |
2279 | | |
2280 | | // Empty lines or lines with only blank spaces and tabs are ignored |
2281 | 0 | if (sText.isEmpty()) |
2282 | 0 | continue; |
2283 | | |
2284 | 0 | if (!sText.startsWith("'") && !sText.startsWithIgnoreAsciiCase("REM")) |
2285 | 0 | { |
2286 | 0 | bAddCommentChar = true; |
2287 | 0 | break; |
2288 | 0 | } |
2289 | | |
2290 | | // Notice that a REM comment is only actually a comment if: |
2291 | | // a) There is no subsequent character or |
2292 | | // b) The subsequent character is a blank space or a tab |
2293 | 0 | std::u16string_view sRest; |
2294 | 0 | if (sText.startsWithIgnoreAsciiCase("REM", &sRest)) |
2295 | 0 | { |
2296 | 0 | if (sRest.size() > 0 && !o3tl::starts_with(sRest, u" ") && !o3tl::starts_with(sRest, u"\t")) |
2297 | 0 | { |
2298 | 0 | bAddCommentChar = true; |
2299 | 0 | break; |
2300 | 0 | } |
2301 | 0 | } |
2302 | 0 | } |
2303 | |
|
2304 | 0 | if (bAddCommentChar) |
2305 | 0 | { |
2306 | | // For each line, determine the first position where there is a character that is not |
2307 | | // a blank space or a tab; the comment marker will be the smallest such position |
2308 | 0 | size_t nCommentPos = std::string::npos; |
2309 | |
|
2310 | 0 | for (sal_uInt32 n = nStartPara; n <= nEndPara; n++) |
2311 | 0 | { |
2312 | 0 | OUString sText = pEngine->GetText(n); |
2313 | 0 | std::u16string_view sLine(sText); |
2314 | 0 | sal_uInt32 nCharPos = sLine.find_first_not_of(u" \t"); |
2315 | | |
2316 | | // Update the position where to place the comment marker |
2317 | 0 | if (nCharPos < nCommentPos) |
2318 | 0 | nCommentPos = nCharPos; |
2319 | | |
2320 | | // If the comment position is zero, then there's no more need to keep searching |
2321 | 0 | if (nCommentPos == 0) |
2322 | 0 | break; |
2323 | 0 | } |
2324 | | |
2325 | | // Insert the comment marker in all lines (except empty lines) |
2326 | 0 | for (sal_uInt32 n = nStartPara; n <= nEndPara; n++) |
2327 | 0 | { |
2328 | 0 | OUString sText = pEngine->GetText(n); |
2329 | 0 | std::u16string_view sLine(sText); |
2330 | 0 | if (o3tl::trim(sLine).length() > 0) |
2331 | 0 | { |
2332 | 0 | pEngine->ImpInsertText(TextPaM(n, nCommentPos), u"' "_ustr); |
2333 | 0 | bChanged = true; |
2334 | 0 | } |
2335 | 0 | } |
2336 | 0 | } |
2337 | 0 | else |
2338 | 0 | { |
2339 | | // For each line, find the first comment marker and remove it |
2340 | 0 | for (sal_uInt32 nPara = nStartPara; nPara <= nEndPara; nPara++) |
2341 | 0 | { |
2342 | 0 | OUString sText = pEngine->GetText(nPara); |
2343 | 0 | if (!sText.isEmpty()) |
2344 | 0 | { |
2345 | | // Determine the position of the comment marker and check whether it's |
2346 | | // a single quote "'" or a "REM" comment |
2347 | 0 | sal_Int32 nQuotePos = sText.indexOf("'"); |
2348 | 0 | sal_Int32 nRemPos = sText.toAsciiUpperCase().indexOf("REM"); |
2349 | | |
2350 | | // An empty line or a line with only blank spaces or tabs needs to be skipped |
2351 | 0 | if (nQuotePos == -1 && nRemPos == -1) |
2352 | 0 | continue; |
2353 | | |
2354 | | // nRemPos only refers to a comment if the subsequent character is a blank space or tab |
2355 | 0 | const sal_Int32 nRemSub = nRemPos + 3; |
2356 | 0 | if (nRemPos != -1 && nRemPos < sText.getLength() - 3 && |
2357 | 0 | sText.indexOf(" ", nRemSub) != nRemSub && |
2358 | 0 | sText.indexOf("\t", nRemSub) != nRemSub) |
2359 | 0 | { |
2360 | 0 | nRemPos = -1; |
2361 | 0 | } |
2362 | | |
2363 | | // True = comment uses single quote; False = comment uses REM |
2364 | 0 | bool bQuoteComment = true; |
2365 | | |
2366 | | // Start and end positions to be removed |
2367 | 0 | sal_Int32 nStartPos = nQuotePos; |
2368 | 0 | sal_Int32 nEndPos = nStartPos + 1; |
2369 | |
|
2370 | 0 | if (nQuotePos == -1) |
2371 | 0 | bQuoteComment = false; |
2372 | 0 | else if (nRemPos != -1 && nRemPos < nQuotePos) |
2373 | 0 | bQuoteComment = false; |
2374 | |
|
2375 | 0 | if (!bQuoteComment) |
2376 | 0 | { |
2377 | 0 | nStartPos = nRemPos; |
2378 | 0 | nEndPos = nStartPos + 3; |
2379 | 0 | } |
2380 | | |
2381 | | // Check if the next character is a blank space or a tab |
2382 | 0 | if (sText.indexOf(" ", nEndPos) == nEndPos || sText.indexOf("\t", nEndPos) == nEndPos) |
2383 | 0 | nEndPos++; |
2384 | | |
2385 | | // Remove the comment marker |
2386 | 0 | pEngine->ImpDeleteText(TextSelection(TextPaM(nPara, nStartPos), TextPaM(nPara, nEndPos))); |
2387 | 0 | bChanged = true; |
2388 | 0 | } |
2389 | 0 | } |
2390 | 0 | } |
2391 | | |
2392 | | // Update selection if there was a selection in the first place |
2393 | 0 | if (bChanged) |
2394 | 0 | { |
2395 | 0 | TextPaM aNewStart; |
2396 | 0 | if (!bSelReversed) |
2397 | 0 | aNewStart = TextPaM(nStartPara, std::min(aSel.GetStart().GetIndex(), |
2398 | 0 | pEngine->GetText(nStartPara).getLength())); |
2399 | 0 | else |
2400 | 0 | aNewStart = TextPaM(nStartPara, std::min(aSel.GetEnd().GetIndex(), |
2401 | 0 | pEngine->GetText(nEndPara).getLength())); |
2402 | |
|
2403 | 0 | if (HasSelection()) |
2404 | 0 | { |
2405 | 0 | TextPaM aNewEnd; |
2406 | 0 | if (!bSelReversed) |
2407 | 0 | aNewEnd = TextPaM(nEndPara, pEngine->GetText(nEndPara).getLength()); |
2408 | 0 | else |
2409 | 0 | aNewEnd = TextPaM(nEndPara, pEngine->GetText(nStartPara).getLength()); |
2410 | |
|
2411 | 0 | TextSelection aNewSel(aNewStart, aNewEnd); |
2412 | 0 | ImpSetSelection(aNewSel); |
2413 | 0 | } |
2414 | 0 | else |
2415 | 0 | { |
2416 | 0 | TextSelection aNewSel(aNewStart, aNewStart); |
2417 | 0 | ImpSetSelection(aNewSel); |
2418 | 0 | } |
2419 | 0 | } |
2420 | 0 | } |
2421 | | |
2422 | | |
2423 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |