|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #ifndef NSTEXTSTORE_H_ |
|
7 #define NSTEXTSTORE_H_ |
|
8 |
|
9 #include "nsAutoPtr.h" |
|
10 #include "nsString.h" |
|
11 #include "nsCOMPtr.h" |
|
12 #include "nsIWidget.h" |
|
13 #include "nsWindowBase.h" |
|
14 #include "mozilla/Attributes.h" |
|
15 #include "mozilla/TextRange.h" |
|
16 #include "mozilla/WindowsVersion.h" |
|
17 |
|
18 #include <msctf.h> |
|
19 #include <textstor.h> |
|
20 |
|
21 // GUID_PROP_INPUTSCOPE is declared in inputscope.h using INIT_GUID. |
|
22 // With initguid.h, we get its instance instead of extern declaration. |
|
23 #ifdef INPUTSCOPE_INIT_GUID |
|
24 #include <initguid.h> |
|
25 #endif |
|
26 #include <inputscope.h> |
|
27 |
|
28 // TSF InputScope, for earlier SDK 8 |
|
29 #define IS_SEARCH static_cast<InputScope>(50) |
|
30 |
|
31 struct ITfThreadMgr; |
|
32 struct ITfDocumentMgr; |
|
33 struct ITfDisplayAttributeMgr; |
|
34 struct ITfCategoryMgr; |
|
35 class nsWindow; |
|
36 #ifdef MOZ_METRO |
|
37 class MetroWidget; |
|
38 #endif |
|
39 |
|
40 namespace mozilla { |
|
41 namespace widget { |
|
42 struct MSGResult; |
|
43 } // namespace widget |
|
44 } // namespace mozilla |
|
45 |
|
46 /* |
|
47 * Text Services Framework text store |
|
48 */ |
|
49 |
|
50 class nsTextStore MOZ_FINAL : public ITextStoreACP, |
|
51 public ITfContextOwnerCompositionSink, |
|
52 public ITfActiveLanguageProfileNotifySink, |
|
53 public ITfInputProcessorProfileActivationSink |
|
54 { |
|
55 public: /*IUnknown*/ |
|
56 STDMETHODIMP_(ULONG) AddRef(void); |
|
57 STDMETHODIMP QueryInterface(REFIID, void**); |
|
58 STDMETHODIMP_(ULONG) Release(void); |
|
59 |
|
60 public: /*ITextStoreACP*/ |
|
61 STDMETHODIMP AdviseSink(REFIID, IUnknown*, DWORD); |
|
62 STDMETHODIMP UnadviseSink(IUnknown*); |
|
63 STDMETHODIMP RequestLock(DWORD, HRESULT*); |
|
64 STDMETHODIMP GetStatus(TS_STATUS*); |
|
65 STDMETHODIMP QueryInsert(LONG, LONG, ULONG, LONG*, LONG*); |
|
66 STDMETHODIMP GetSelection(ULONG, ULONG, TS_SELECTION_ACP*, ULONG*); |
|
67 STDMETHODIMP SetSelection(ULONG, const TS_SELECTION_ACP*); |
|
68 STDMETHODIMP GetText(LONG, LONG, WCHAR*, ULONG, ULONG*, TS_RUNINFO*, ULONG, |
|
69 ULONG*, LONG*); |
|
70 STDMETHODIMP SetText(DWORD, LONG, LONG, const WCHAR*, ULONG, TS_TEXTCHANGE*); |
|
71 STDMETHODIMP GetFormattedText(LONG, LONG, IDataObject**); |
|
72 STDMETHODIMP GetEmbedded(LONG, REFGUID, REFIID, IUnknown**); |
|
73 STDMETHODIMP QueryInsertEmbedded(const GUID*, const FORMATETC*, BOOL*); |
|
74 STDMETHODIMP InsertEmbedded(DWORD, LONG, LONG, IDataObject*, TS_TEXTCHANGE*); |
|
75 STDMETHODIMP RequestSupportedAttrs(DWORD, ULONG, const TS_ATTRID*); |
|
76 STDMETHODIMP RequestAttrsAtPosition(LONG, ULONG, const TS_ATTRID*, DWORD); |
|
77 STDMETHODIMP RequestAttrsTransitioningAtPosition(LONG, ULONG, |
|
78 const TS_ATTRID*, DWORD); |
|
79 STDMETHODIMP FindNextAttrTransition(LONG, LONG, ULONG, const TS_ATTRID*, |
|
80 DWORD, LONG*, BOOL*, LONG*); |
|
81 STDMETHODIMP RetrieveRequestedAttrs(ULONG, TS_ATTRVAL*, ULONG*); |
|
82 STDMETHODIMP GetEndACP(LONG*); |
|
83 STDMETHODIMP GetActiveView(TsViewCookie*); |
|
84 STDMETHODIMP GetACPFromPoint(TsViewCookie, const POINT*, DWORD, LONG*); |
|
85 STDMETHODIMP GetTextExt(TsViewCookie, LONG, LONG, RECT*, BOOL*); |
|
86 STDMETHODIMP GetScreenExt(TsViewCookie, RECT*); |
|
87 STDMETHODIMP GetWnd(TsViewCookie, HWND*); |
|
88 STDMETHODIMP InsertTextAtSelection(DWORD, const WCHAR*, ULONG, LONG*, LONG*, |
|
89 TS_TEXTCHANGE*); |
|
90 STDMETHODIMP InsertEmbeddedAtSelection(DWORD, IDataObject*, LONG*, LONG*, |
|
91 TS_TEXTCHANGE*); |
|
92 |
|
93 public: /*ITfContextOwnerCompositionSink*/ |
|
94 STDMETHODIMP OnStartComposition(ITfCompositionView*, BOOL*); |
|
95 STDMETHODIMP OnUpdateComposition(ITfCompositionView*, ITfRange*); |
|
96 STDMETHODIMP OnEndComposition(ITfCompositionView*); |
|
97 |
|
98 public: /*ITfActiveLanguageProfileNotifySink*/ |
|
99 STDMETHODIMP OnActivated(REFCLSID clsid, REFGUID guidProfile, |
|
100 BOOL fActivated); |
|
101 |
|
102 public: /*ITfInputProcessorProfileActivationSink*/ |
|
103 STDMETHODIMP OnActivated(DWORD, LANGID, REFCLSID, REFGUID, REFGUID, |
|
104 HKL, DWORD); |
|
105 |
|
106 protected: |
|
107 typedef mozilla::widget::IMENotification IMENotification; |
|
108 typedef mozilla::widget::IMEState IMEState; |
|
109 typedef mozilla::widget::InputContext InputContext; |
|
110 typedef mozilla::widget::InputContextAction InputContextAction; |
|
111 |
|
112 public: |
|
113 static void Initialize(void); |
|
114 static void Terminate(void); |
|
115 |
|
116 static bool ProcessRawKeyMessage(const MSG& aMsg); |
|
117 static void ProcessMessage(nsWindowBase* aWindow, UINT aMessage, |
|
118 WPARAM& aWParam, LPARAM& aLParam, |
|
119 mozilla::widget::MSGResult& aResult); |
|
120 |
|
121 |
|
122 static void SetIMEOpenState(bool); |
|
123 static bool GetIMEOpenState(void); |
|
124 |
|
125 static void CommitComposition(bool aDiscard) |
|
126 { |
|
127 NS_ENSURE_TRUE_VOID(sTsfTextStore); |
|
128 sTsfTextStore->CommitCompositionInternal(aDiscard); |
|
129 } |
|
130 |
|
131 static void SetInputContext(nsWindowBase* aWidget, |
|
132 const InputContext& aContext, |
|
133 const InputContextAction& aAction); |
|
134 |
|
135 static nsresult OnFocusChange(bool aGotFocus, |
|
136 nsWindowBase* aFocusedWidget, |
|
137 IMEState::Enabled aIMEEnabled); |
|
138 static nsresult OnTextChange(const IMENotification& aIMENotification) |
|
139 { |
|
140 NS_ENSURE_TRUE(sTsfTextStore, NS_ERROR_NOT_AVAILABLE); |
|
141 return sTsfTextStore->OnTextChangeInternal(aIMENotification); |
|
142 } |
|
143 |
|
144 static nsresult OnSelectionChange(void) |
|
145 { |
|
146 NS_ENSURE_TRUE(sTsfTextStore, NS_ERROR_NOT_AVAILABLE); |
|
147 return sTsfTextStore->OnSelectionChangeInternal(); |
|
148 } |
|
149 |
|
150 static nsresult OnLayoutChange() |
|
151 { |
|
152 NS_ENSURE_TRUE(sTsfTextStore, NS_ERROR_NOT_AVAILABLE); |
|
153 return sTsfTextStore->OnLayoutChangeInternal(); |
|
154 } |
|
155 |
|
156 static nsIMEUpdatePreference GetIMEUpdatePreference(); |
|
157 |
|
158 // Returns the address of the pointer so that the TSF automatic test can |
|
159 // replace the system object with a custom implementation for testing. |
|
160 static void* GetNativeData(uint32_t aDataType) |
|
161 { |
|
162 switch (aDataType) { |
|
163 case NS_NATIVE_TSF_THREAD_MGR: |
|
164 Initialize(); // Apply any previous changes |
|
165 return static_cast<void*>(&sTsfThreadMgr); |
|
166 case NS_NATIVE_TSF_CATEGORY_MGR: |
|
167 return static_cast<void*>(&sCategoryMgr); |
|
168 case NS_NATIVE_TSF_DISPLAY_ATTR_MGR: |
|
169 return static_cast<void*>(&sDisplayAttrMgr); |
|
170 default: |
|
171 return nullptr; |
|
172 } |
|
173 } |
|
174 |
|
175 static ITfMessagePump* GetMessagePump() |
|
176 { |
|
177 return sMessagePump; |
|
178 } |
|
179 |
|
180 static void* GetTextStore() |
|
181 { |
|
182 return static_cast<void*>(sTsfTextStore); |
|
183 } |
|
184 |
|
185 static bool ThinksHavingFocus() |
|
186 { |
|
187 return (sTsfTextStore && sTsfTextStore->mContext); |
|
188 } |
|
189 |
|
190 static bool IsInTSFMode() |
|
191 { |
|
192 return sTsfThreadMgr != nullptr; |
|
193 } |
|
194 |
|
195 static bool IsComposing() |
|
196 { |
|
197 return (sTsfTextStore && sTsfTextStore->mComposition.IsComposing()); |
|
198 } |
|
199 |
|
200 static bool IsComposingOn(nsWindowBase* aWidget) |
|
201 { |
|
202 return (IsComposing() && sTsfTextStore->mWidget == aWidget); |
|
203 } |
|
204 |
|
205 static bool IsIMM_IME() |
|
206 { |
|
207 if (!sTsfTextStore || !sTsfTextStore->EnsureInitActiveTIPKeyboard()) { |
|
208 return IsIMM_IME(::GetKeyboardLayout(0)); |
|
209 } |
|
210 return sTsfTextStore->mIsIMM_IME; |
|
211 } |
|
212 |
|
213 static bool IsIMM_IME(HKL aHKL) |
|
214 { |
|
215 return (::ImmGetIMEFileNameW(aHKL, nullptr, 0) > 0); |
|
216 } |
|
217 |
|
218 #ifdef DEBUG |
|
219 // Returns true when keyboard layout has IME (TIP). |
|
220 static bool CurrentKeyboardLayoutHasIME(); |
|
221 #endif // #ifdef DEBUG |
|
222 |
|
223 protected: |
|
224 nsTextStore(); |
|
225 ~nsTextStore(); |
|
226 |
|
227 bool Init(ITfThreadMgr* aThreadMgr); |
|
228 |
|
229 static void MarkContextAsKeyboardDisabled(ITfContext* aContext); |
|
230 static void MarkContextAsEmpty(ITfContext* aContext); |
|
231 |
|
232 static bool IsTIPCategoryKeyboard(REFCLSID aTextService, LANGID aLangID, |
|
233 REFGUID aProfile); |
|
234 static void GetTIPDescription(REFCLSID aTextService, LANGID aLangID, |
|
235 REFGUID aProfile, nsAString& aDescription); |
|
236 |
|
237 bool Create(nsWindowBase* aWidget); |
|
238 bool Destroy(void); |
|
239 |
|
240 bool IsReadLock(DWORD aLock) const |
|
241 { |
|
242 return (TS_LF_READ == (aLock & TS_LF_READ)); |
|
243 } |
|
244 bool IsReadWriteLock(DWORD aLock) const |
|
245 { |
|
246 return (TS_LF_READWRITE == (aLock & TS_LF_READWRITE)); |
|
247 } |
|
248 bool IsReadLocked() const { return IsReadLock(mLock); } |
|
249 bool IsReadWriteLocked() const { return IsReadWriteLock(mLock); } |
|
250 |
|
251 // This is called immediately after a call of OnLockGranted() of mSink. |
|
252 // Note that mLock isn't cleared yet when this is called. |
|
253 void DidLockGranted(); |
|
254 |
|
255 bool GetScreenExtInternal(RECT &aScreenExt); |
|
256 // If aDispatchTextEvent is true, this method will dispatch text event if |
|
257 // this is called during IME composing. aDispatchTextEvent should be true |
|
258 // only when this is called from SetSelection. Because otherwise, the text |
|
259 // event should not be sent from here. |
|
260 HRESULT SetSelectionInternal(const TS_SELECTION_ACP*, |
|
261 bool aDispatchTextEvent = false); |
|
262 bool InsertTextAtSelectionInternal(const nsAString &aInsertStr, |
|
263 TS_TEXTCHANGE* aTextChange); |
|
264 void CommitCompositionInternal(bool); |
|
265 nsresult OnTextChangeInternal(const IMENotification& aIMENotification); |
|
266 nsresult OnSelectionChangeInternal(void); |
|
267 HRESULT GetDisplayAttribute(ITfProperty* aProperty, |
|
268 ITfRange* aRange, |
|
269 TF_DISPLAYATTRIBUTE* aResult); |
|
270 HRESULT RestartCompositionIfNecessary(ITfRange* pRangeNew = nullptr); |
|
271 |
|
272 // Following methods record composing action(s) to mPendingActions. |
|
273 // They will be flushed FlushPendingActions(). |
|
274 HRESULT RecordCompositionStartAction(ITfCompositionView* aCompositionView, |
|
275 ITfRange* aRange, |
|
276 bool aPreserveSelection); |
|
277 HRESULT RecordCompositionUpdateAction(); |
|
278 HRESULT RecordCompositionEndAction(); |
|
279 |
|
280 // FlushPendingActions() performs pending actions recorded in mPendingActions |
|
281 // and clear it. |
|
282 void FlushPendingActions(); |
|
283 |
|
284 nsresult OnLayoutChangeInternal(); |
|
285 HRESULT ProcessScopeRequest(DWORD dwFlags, |
|
286 ULONG cFilterAttrs, |
|
287 const TS_ATTRID *paFilterAttrs); |
|
288 void SetInputScope(const nsString& aHTMLInputType); |
|
289 |
|
290 // Creates native caret over our caret. This method only works on desktop |
|
291 // application. Otherwise, this does nothing. |
|
292 void CreateNativeCaret(); |
|
293 |
|
294 bool EnsureInitActiveTIPKeyboard(); |
|
295 |
|
296 // Holds the pointer to our current win32 or metro widget |
|
297 nsRefPtr<nsWindowBase> mWidget; |
|
298 // Document manager for the currently focused editor |
|
299 nsRefPtr<ITfDocumentMgr> mDocumentMgr; |
|
300 // Edit cookie associated with the current editing context |
|
301 DWORD mEditCookie; |
|
302 // Cookie of installing ITfInputProcessorProfileActivationSink |
|
303 DWORD mIPProfileCookie; |
|
304 // Cookie of installing ITfActiveLanguageProfileNotifySink |
|
305 DWORD mLangProfileCookie; |
|
306 // Editing context at the bottom of mDocumentMgr's context stack |
|
307 nsRefPtr<ITfContext> mContext; |
|
308 // Currently installed notification sink |
|
309 nsRefPtr<ITextStoreACPSink> mSink; |
|
310 // TS_AS_* mask of what events to notify |
|
311 DWORD mSinkMask; |
|
312 // 0 if not locked, otherwise TS_LF_* indicating the current lock |
|
313 DWORD mLock; |
|
314 // 0 if no lock is queued, otherwise TS_LF_* indicating the queue lock |
|
315 DWORD mLockQueued; |
|
316 // Active TIP keyboard's description. If active language profile isn't TIP, |
|
317 // i.e., IMM-IME or just a keyboard layout, this is empty. |
|
318 nsString mActiveTIPKeyboardDescription; |
|
319 |
|
320 class Composition MOZ_FINAL |
|
321 { |
|
322 public: |
|
323 // nullptr if no composition is active, otherwise the current composition |
|
324 nsRefPtr<ITfCompositionView> mView; |
|
325 |
|
326 // Current copy of the active composition string. Only mString is |
|
327 // changed during a InsertTextAtSelection call if we have a composition. |
|
328 // mString acts as a buffer until OnUpdateComposition is called |
|
329 // and mString is flushed to editor through NS_TEXT_TEXT. This |
|
330 // way all changes are updated in batches to avoid |
|
331 // inconsistencies/artifacts. |
|
332 nsString mString; |
|
333 |
|
334 // The latest composition string which was dispatched by composition update |
|
335 // event. |
|
336 nsString mLastData; |
|
337 |
|
338 // The start of the current active composition, in ACP offsets |
|
339 LONG mStart; |
|
340 |
|
341 bool IsComposing() const |
|
342 { |
|
343 return (mView != nullptr); |
|
344 } |
|
345 |
|
346 LONG EndOffset() const |
|
347 { |
|
348 return mStart + static_cast<LONG>(mString.Length()); |
|
349 } |
|
350 |
|
351 // Start() and End() updates the members for emulating the latest state. |
|
352 // Unless flush the pending actions, this data never matches the actual |
|
353 // content. |
|
354 void Start(ITfCompositionView* aCompositionView, |
|
355 LONG aCompositionStartOffset, |
|
356 const nsAString& aCompositionString); |
|
357 void End(); |
|
358 }; |
|
359 // While the document is locked, we cannot dispatch any events which cause |
|
360 // DOM events since the DOM events' handlers may modify the locked document. |
|
361 // However, even while the document is locked, TSF may queries us. |
|
362 // For that, nsTextStore modifies mComposition even while the document is |
|
363 // locked. With mComposition, query methods can returns the text content |
|
364 // information. |
|
365 Composition mComposition; |
|
366 |
|
367 class Selection |
|
368 { |
|
369 public: |
|
370 Selection() : mDirty(true) {} |
|
371 |
|
372 bool IsDirty() const { return mDirty; }; |
|
373 void MarkDirty() { mDirty = true; } |
|
374 |
|
375 TS_SELECTION_ACP& ACP() |
|
376 { |
|
377 MOZ_ASSERT(!mDirty); |
|
378 return mACP; |
|
379 } |
|
380 |
|
381 void SetSelection(const TS_SELECTION_ACP& aSelection) |
|
382 { |
|
383 mDirty = false; |
|
384 mACP = aSelection; |
|
385 // Selection end must be active in our editor. |
|
386 if (mACP.style.ase != TS_AE_START) { |
|
387 mACP.style.ase = TS_AE_END; |
|
388 } |
|
389 // We're not support interim char selection for now. |
|
390 // XXX Probably, this is necessary for supporting South Asian languages. |
|
391 mACP.style.fInterimChar = FALSE; |
|
392 } |
|
393 |
|
394 void SetSelection(uint32_t aStart, uint32_t aLength, bool aReversed) |
|
395 { |
|
396 mDirty = false; |
|
397 mACP.acpStart = static_cast<LONG>(aStart); |
|
398 mACP.acpEnd = static_cast<LONG>(aStart + aLength); |
|
399 mACP.style.ase = aReversed ? TS_AE_START : TS_AE_END; |
|
400 mACP.style.fInterimChar = FALSE; |
|
401 } |
|
402 |
|
403 bool IsCollapsed() const |
|
404 { |
|
405 MOZ_ASSERT(!mDirty); |
|
406 return (mACP.acpStart == mACP.acpEnd); |
|
407 } |
|
408 |
|
409 void CollapseAt(uint32_t aOffset) |
|
410 { |
|
411 mDirty = false; |
|
412 mACP.acpStart = mACP.acpEnd = static_cast<LONG>(aOffset); |
|
413 mACP.style.ase = TS_AE_END; |
|
414 mACP.style.fInterimChar = FALSE; |
|
415 } |
|
416 |
|
417 LONG MinOffset() const |
|
418 { |
|
419 MOZ_ASSERT(!mDirty); |
|
420 LONG min = std::min(mACP.acpStart, mACP.acpEnd); |
|
421 MOZ_ASSERT(min >= 0); |
|
422 return min; |
|
423 } |
|
424 |
|
425 LONG MaxOffset() const |
|
426 { |
|
427 MOZ_ASSERT(!mDirty); |
|
428 LONG max = std::max(mACP.acpStart, mACP.acpEnd); |
|
429 MOZ_ASSERT(max >= 0); |
|
430 return max; |
|
431 } |
|
432 |
|
433 LONG StartOffset() const |
|
434 { |
|
435 MOZ_ASSERT(!mDirty); |
|
436 MOZ_ASSERT(mACP.acpStart >= 0); |
|
437 return mACP.acpStart; |
|
438 } |
|
439 |
|
440 LONG EndOffset() const |
|
441 { |
|
442 MOZ_ASSERT(!mDirty); |
|
443 MOZ_ASSERT(mACP.acpEnd >= 0); |
|
444 return mACP.acpEnd; |
|
445 } |
|
446 |
|
447 LONG Length() const |
|
448 { |
|
449 MOZ_ASSERT(!mDirty); |
|
450 MOZ_ASSERT(mACP.acpEnd >= mACP.acpStart); |
|
451 return std::abs(mACP.acpEnd - mACP.acpStart); |
|
452 } |
|
453 |
|
454 bool IsReversed() const |
|
455 { |
|
456 MOZ_ASSERT(!mDirty); |
|
457 return (mACP.style.ase == TS_AE_START); |
|
458 } |
|
459 |
|
460 TsActiveSelEnd ActiveSelEnd() const |
|
461 { |
|
462 MOZ_ASSERT(!mDirty); |
|
463 return mACP.style.ase; |
|
464 } |
|
465 |
|
466 bool IsInterimChar() const |
|
467 { |
|
468 MOZ_ASSERT(!mDirty); |
|
469 return (mACP.style.fInterimChar != FALSE); |
|
470 } |
|
471 |
|
472 private: |
|
473 TS_SELECTION_ACP mACP; |
|
474 bool mDirty; |
|
475 }; |
|
476 // Don't access mSelection directly except at calling MarkDirty(). |
|
477 // Use CurrentSelection() instead. This is marked as dirty when the |
|
478 // selection or content is changed without document lock. |
|
479 Selection mSelection; |
|
480 |
|
481 // Get "current selection" while the document is locked. The selection is |
|
482 // NOT modified immediately during document lock. The pending changes will |
|
483 // be flushed at unlocking the document. The "current selection" is the |
|
484 // modified selection during document lock. This is also called |
|
485 // CurrentContent() too. |
|
486 Selection& CurrentSelection(); |
|
487 |
|
488 struct PendingAction MOZ_FINAL |
|
489 { |
|
490 enum ActionType MOZ_ENUM_TYPE(uint8_t) |
|
491 { |
|
492 COMPOSITION_START, |
|
493 COMPOSITION_UPDATE, |
|
494 COMPOSITION_END, |
|
495 SELECTION_SET |
|
496 }; |
|
497 ActionType mType; |
|
498 // For compositionstart and selectionset |
|
499 LONG mSelectionStart; |
|
500 LONG mSelectionLength; |
|
501 // For compositionupdate and compositionend |
|
502 nsString mData; |
|
503 // For compositionupdate |
|
504 nsRefPtr<mozilla::TextRangeArray> mRanges; |
|
505 // For selectionset |
|
506 bool mSelectionReversed; |
|
507 }; |
|
508 // Items of mPendingActions are appended when TSF tells us to need to dispatch |
|
509 // DOM composition events. However, we cannot dispatch while the document is |
|
510 // locked because it can cause modifying the locked document. So, the pending |
|
511 // actions should be performed when document lock is unlocked. |
|
512 nsTArray<PendingAction> mPendingActions; |
|
513 |
|
514 PendingAction* GetPendingCompositionUpdate() |
|
515 { |
|
516 if (!mPendingActions.IsEmpty()) { |
|
517 PendingAction& lastAction = mPendingActions.LastElement(); |
|
518 if (lastAction.mType == PendingAction::COMPOSITION_UPDATE) { |
|
519 return &lastAction; |
|
520 } |
|
521 } |
|
522 PendingAction* newAction = mPendingActions.AppendElement(); |
|
523 newAction->mType = PendingAction::COMPOSITION_UPDATE; |
|
524 newAction->mRanges = new mozilla::TextRangeArray(); |
|
525 return newAction; |
|
526 } |
|
527 |
|
528 // When On*Composition() is called without document lock, we need to flush |
|
529 // the recorded actions at quitting the method. |
|
530 // AutoPendingActionAndContentFlusher class is usedful for it. |
|
531 class MOZ_STACK_CLASS AutoPendingActionAndContentFlusher MOZ_FINAL |
|
532 { |
|
533 public: |
|
534 AutoPendingActionAndContentFlusher(nsTextStore* aTextStore) |
|
535 : mTextStore(aTextStore) |
|
536 { |
|
537 MOZ_ASSERT(!mTextStore->mIsRecordingActionsWithoutLock); |
|
538 if (!mTextStore->IsReadWriteLocked()) { |
|
539 mTextStore->mIsRecordingActionsWithoutLock = true; |
|
540 } |
|
541 } |
|
542 |
|
543 ~AutoPendingActionAndContentFlusher() |
|
544 { |
|
545 if (!mTextStore->mIsRecordingActionsWithoutLock) { |
|
546 return; |
|
547 } |
|
548 mTextStore->FlushPendingActions(); |
|
549 mTextStore->mIsRecordingActionsWithoutLock = false; |
|
550 } |
|
551 |
|
552 private: |
|
553 AutoPendingActionAndContentFlusher() {} |
|
554 |
|
555 nsRefPtr<nsTextStore> mTextStore; |
|
556 }; |
|
557 |
|
558 class Content MOZ_FINAL |
|
559 { |
|
560 public: |
|
561 Content(nsTextStore::Composition& aComposition, |
|
562 nsTextStore::Selection& aSelection) : |
|
563 mComposition(aComposition), mSelection(aSelection) |
|
564 { |
|
565 Clear(); |
|
566 } |
|
567 |
|
568 void Clear() |
|
569 { |
|
570 mText.Truncate(); |
|
571 mInitialized = false; |
|
572 } |
|
573 |
|
574 bool IsInitialized() const { return mInitialized; } |
|
575 |
|
576 void Init(const nsAString& aText) |
|
577 { |
|
578 mText = aText; |
|
579 mMinTextModifiedOffset = NOT_MODIFIED; |
|
580 mInitialized = true; |
|
581 } |
|
582 |
|
583 const nsDependentSubstring GetSelectedText() const; |
|
584 const nsDependentSubstring GetSubstring(uint32_t aStart, |
|
585 uint32_t aLength) const; |
|
586 void ReplaceSelectedTextWith(const nsAString& aString); |
|
587 void ReplaceTextWith(LONG aStart, LONG aLength, const nsAString& aString); |
|
588 |
|
589 void StartComposition(ITfCompositionView* aCompositionView, |
|
590 const PendingAction& aCompStart, |
|
591 bool aPreserveSelection); |
|
592 void EndComposition(const PendingAction& aCompEnd); |
|
593 |
|
594 const nsString& Text() const |
|
595 { |
|
596 MOZ_ASSERT(mInitialized); |
|
597 return mText; |
|
598 } |
|
599 |
|
600 // Returns true if layout of the character at the aOffset has not been |
|
601 // calculated. |
|
602 bool IsLayoutChangedAfter(uint32_t aOffset) const |
|
603 { |
|
604 return mInitialized && (mMinTextModifiedOffset < aOffset); |
|
605 } |
|
606 // Returns true if layout of the content has been changed, i.e., the new |
|
607 // layout has not been calculated. |
|
608 bool IsLayoutChanged() const |
|
609 { |
|
610 return mInitialized && (mMinTextModifiedOffset != NOT_MODIFIED); |
|
611 } |
|
612 |
|
613 nsTextStore::Composition& Composition() { return mComposition; } |
|
614 nsTextStore::Selection& Selection() { return mSelection; } |
|
615 |
|
616 private: |
|
617 nsString mText; |
|
618 nsTextStore::Composition& mComposition; |
|
619 nsTextStore::Selection& mSelection; |
|
620 |
|
621 // The minimum offset of modified part of the text. |
|
622 enum MOZ_ENUM_TYPE(uint32_t) |
|
623 { |
|
624 NOT_MODIFIED = UINT32_MAX |
|
625 }; |
|
626 uint32_t mMinTextModifiedOffset; |
|
627 |
|
628 bool mInitialized; |
|
629 }; |
|
630 // mContent caches "current content" of the document ONLY while the document |
|
631 // is locked. I.e., the content is cleared at unlocking the document since |
|
632 // we need to reduce the memory usage. This is initialized by |
|
633 // CurrentContent() automatically, so, don't access this member directly |
|
634 // except at calling Clear(), IsInitialized(), IsLayoutChangedAfter() or |
|
635 // IsLayoutChanged(). |
|
636 Content mContent; |
|
637 |
|
638 Content& CurrentContent(); |
|
639 |
|
640 // The input scopes for this context, defaults to IS_DEFAULT. |
|
641 nsTArray<InputScope> mInputScopes; |
|
642 bool mInputScopeDetected; |
|
643 bool mInputScopeRequested; |
|
644 // If edit actions are being recorded without document lock, this is true. |
|
645 // Otherwise, false. |
|
646 bool mIsRecordingActionsWithoutLock; |
|
647 // During recording actions, we shouldn't call mSink->OnSelectionChange() |
|
648 // because it may cause TSF request new lock. This is a problem if the |
|
649 // selection change is caused by a call of On*Composition() without document |
|
650 // lock since RequestLock() tries to flush the pending actions again (which |
|
651 // are flushing). Therefore, OnSelectionChangeInternal() sets this true |
|
652 // during recoding actions and then, RequestLock() will call |
|
653 // mSink->OnSelectionChange() after mLock becomes 0. |
|
654 bool mPendingOnSelectionChange; |
|
655 // If GetTextExt() or GetACPFromPoint() is called and the layout hasn't been |
|
656 // calculated yet, these methods return TS_E_NOLAYOUT. Then, RequestLock() |
|
657 // will call mSink->OnLayoutChange() and |
|
658 // ITfContextOwnerServices::OnLayoutChange() after the layout is fixed and |
|
659 // the document is unlocked. |
|
660 bool mPendingOnLayoutChange; |
|
661 // While there is native caret, this is true. Otherwise, false. |
|
662 bool mNativeCaretIsCreated; |
|
663 |
|
664 // True if current IME is implemented with IMM. |
|
665 bool mIsIMM_IME; |
|
666 // True if OnActivated() is already called |
|
667 bool mOnActivatedCalled; |
|
668 |
|
669 // TSF thread manager object for the current application |
|
670 static ITfThreadMgr* sTsfThreadMgr; |
|
671 // sMessagePump is QI'ed from sTsfThreadMgr |
|
672 static ITfMessagePump* sMessagePump; |
|
673 // sKeystrokeMgr is QI'ed from sTsfThreadMgr |
|
674 static ITfKeystrokeMgr* sKeystrokeMgr; |
|
675 // TSF display attribute manager |
|
676 static ITfDisplayAttributeMgr* sDisplayAttrMgr; |
|
677 // TSF category manager |
|
678 static ITfCategoryMgr* sCategoryMgr; |
|
679 |
|
680 // TSF client ID for the current application |
|
681 static DWORD sTsfClientId; |
|
682 // Current text store. Currently only ONE nsTextStore instance is ever used, |
|
683 // although Create is called when an editor is focused and Destroy called |
|
684 // when the focused editor is blurred. |
|
685 static nsTextStore* sTsfTextStore; |
|
686 |
|
687 // For IME (keyboard) disabled state: |
|
688 static ITfDocumentMgr* sTsfDisabledDocumentMgr; |
|
689 static ITfContext* sTsfDisabledContext; |
|
690 |
|
691 static ITfInputProcessorProfiles* sInputProcessorProfiles; |
|
692 |
|
693 // Enables/Disables hack for specific TIP. |
|
694 static bool sCreateNativeCaretForATOK; |
|
695 |
|
696 // Message the Tablet Input Panel uses to flush text during blurring. |
|
697 // See comments in Destroy |
|
698 static UINT sFlushTIPInputMessage; |
|
699 |
|
700 private: |
|
701 ULONG mRefCnt; |
|
702 }; |
|
703 |
|
704 #endif /*NSTEXTSTORE_H_*/ |