summaryrefslogtreecommitdiffstats
path: root/widget/windows/IMMHandler.h
blob: e012541faedf78140e2ba1427bda600c6c4a5d25 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef IMMHandler_h_
#define IMMHandler_h_

#include "mozilla/ContentData.h"
#include "mozilla/EventForwards.h"
#include "mozilla/TextEventDispatcher.h"
#include "mozilla/WritingModes.h"

#include "windef.h"
#include "winnetwk.h"
#include "npapi.h"

#include "nsCOMPtr.h"
#include "nsIWidget.h"
#include "nsRect.h"
#include "nsString.h"
#include "nsTArray.h"

#include <windows.h>

class nsWindow;

namespace mozilla {
namespace widget {

struct MSGResult;

class IMEContext final {
 public:
  IMEContext() : mWnd(nullptr), mIMC(nullptr) {}

  explicit IMEContext(HWND aWnd);
  explicit IMEContext(nsWindow* aWindowBase);

  ~IMEContext() { Clear(); }

  HIMC get() const { return mIMC; }

  void Init(HWND aWnd);
  void Init(nsWindow* aWindowBase);
  void Clear();

  bool IsValid() const { return !!mIMC; }

  void SetOpenState(bool aOpen) const {
    if (!mIMC) {
      return;
    }
    ::ImmSetOpenStatus(mIMC, aOpen);
  }

  bool GetOpenState() const {
    if (!mIMC) {
      return false;
    }
    return (::ImmGetOpenStatus(mIMC) != FALSE);
  }

  bool AssociateDefaultContext() {
    // We assume that there is only default IMC, no new IMC has been created.
    if (mIMC) {
      return false;
    }
    if (!::ImmAssociateContextEx(mWnd, nullptr, IACE_DEFAULT)) {
      return false;
    }
    mIMC = ::ImmGetContext(mWnd);
    return (mIMC != nullptr);
  }

  bool Disassociate() {
    if (!mIMC) {
      return false;
    }
    if (!::ImmAssociateContextEx(mWnd, nullptr, 0)) {
      return false;
    }
    ::ImmReleaseContext(mWnd, mIMC);
    mIMC = nullptr;
    return true;
  }

 protected:
  IMEContext(const IMEContext& aOther) { MOZ_CRASH("Don't copy IMEContext"); }

  HWND mWnd;
  HIMC mIMC;
};

class IMMHandler final {
 public:
  static void Initialize();
  static void Terminate();

  // If Process*() returns true, the caller shouldn't do anything anymore.
  static bool ProcessMessage(nsWindow* aWindow, UINT msg, WPARAM& wParam,
                             LPARAM& lParam, MSGResult& aResult);
  static bool IsComposing() { return IsComposingOnOurEditor(); }
  static bool IsComposingOn(nsWindow* aWindow) {
    return IsComposing() && IsComposingWindow(aWindow);
  }

#ifdef DEBUG
  /**
   * IsIMEAvailable() returns TRUE when current keyboard layout has IME.
   * Otherwise, FALSE.
   */
  static bool IsIMEAvailable() { return !!::ImmIsIME(::GetKeyboardLayout(0)); }
#endif

  // If aForce is TRUE, these methods doesn't check whether we have composition
  // or not.  If you don't set it to TRUE, these method doesn't commit/cancel
  // the composition on uexpected window.
  static void CommitComposition(nsWindow* aWindow, bool aForce = false);
  static void CancelComposition(nsWindow* aWindow, bool aForce = false);
  static void OnFocusChange(bool aFocus, nsWindow* aWindow);
  static void OnUpdateComposition(nsWindow* aWindow);
  static void OnSelectionChange(nsWindow* aWindow,
                                const IMENotification& aIMENotification,
                                bool aIsIMMActive);

  static IMENotificationRequests GetIMENotificationRequests();

  // Returns NS_SUCCESS_EVENT_CONSUMED if the mouse button event is consumed by
  // IME.  Otherwise, NS_OK.
  static nsresult OnMouseButtonEvent(nsWindow* aWindow,
                                     const IMENotification& aIMENotification);

#define DECL_IS_IME_ACTIVE(aReadableName) \
  static bool Is##aReadableName##Active();

  // Japanese IMEs
  DECL_IS_IME_ACTIVE(ATOK2006)
  DECL_IS_IME_ACTIVE(ATOK2007)
  DECL_IS_IME_ACTIVE(ATOK2008)
  DECL_IS_IME_ACTIVE(ATOK2009)
  DECL_IS_IME_ACTIVE(ATOK2010)
  DECL_IS_IME_ACTIVE(GoogleJapaneseInput)
  DECL_IS_IME_ACTIVE(Japanist2003)

#undef DECL_IS_IME_ACTIVE

  /**
   * IsActiveIMEInBlockList() returns true if we know active keyboard layout's
   * IME has some crash bugs or something which make some damage to us.  When
   * this returns true, IMC shouldn't be associated with any windows.
   */
  static bool IsActiveIMEInBlockList();

 protected:
  static void EnsureHandlerInstance();

  static bool IsComposingOnOurEditor();
  static bool IsComposingWindow(nsWindow* aWindow);

  static bool ShouldDrawCompositionStringOurselves();
  static bool IsVerticalWritingSupported();
  // aWindow can be nullptr if it's called without receiving WM_INPUTLANGCHANGE.
  static void InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout);
  static UINT GetKeyboardCodePage();

  /**
   * Checks whether the window is top level window of the composing window.
   * In this method, the top level window means in all windows, not only in all
   * OUR windows.  I.e., if the aWindow is embedded, this always returns FALSE.
   */
  static bool IsTopLevelWindowOfComposition(nsWindow* aWindow);

  static bool ProcessInputLangChangeMessage(nsWindow* aWindow, WPARAM wParam,
                                            LPARAM lParam, MSGResult& aResult);

  IMMHandler();
  ~IMMHandler();

  // On*() methods return true if the caller of message handler shouldn't do
  // anything anymore.  Otherwise, false.
  static bool OnKeyDownEvent(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
                             MSGResult& aResult);

  bool OnIMEStartComposition(nsWindow* aWindow, MSGResult& aResult);
  bool OnIMEComposition(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
                        MSGResult& aResult);
  bool OnIMEEndComposition(nsWindow* aWindow, MSGResult& aResult);
  bool OnIMERequest(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
                    MSGResult& aResult);
  bool OnChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
              MSGResult& aResult);
  void OnInputLangChange(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
                         MSGResult& aResult);

  // These message handlers don't use instance members, we should not create
  // the instance by the messages.  So, they should be static.
  static bool OnIMEChar(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
                        MSGResult& aResult);
  static bool OnIMESetContext(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
                              MSGResult& aResult);
  static bool OnIMECompositionFull(nsWindow* aWindow, MSGResult& aResult);
  static bool OnIMENotify(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
                          MSGResult& aResult);
  static bool OnIMESelect(nsWindow* aWindow, WPARAM wParam, LPARAM lParam,
                          MSGResult& aResult);

  // The result of Handle* method mean "Processed" when it's TRUE.
  void HandleStartComposition(nsWindow* aWindow, const IMEContext& aContext);
  bool HandleComposition(nsWindow* aWindow, const IMEContext& aContext,
                         LPARAM lParam);
  // If aCommitString is null, this commits composition with the latest
  // dispatched data.  Otherwise, commits composition with the value.
  void HandleEndComposition(nsWindow* aWindow,
                            const nsAString* aCommitString = nullptr);
  bool HandleReconvert(nsWindow* aWindow, LPARAM lParam, LRESULT* oResult);
  bool HandleQueryCharPosition(nsWindow* aWindow, LPARAM lParam,
                               LRESULT* oResult);
  bool HandleDocumentFeed(nsWindow* aWindow, LPARAM lParam, LRESULT* oResult);

  /**
   *  When a window's IME context is activating but we have composition on
   *  another window, we should commit our composition because IME context is
   *  shared by all our windows (including plug-ins).
   *  @param aWindow is a new activated window.
   *  If aWindow is our composing window, this method does nothing.
   *  Otherwise, this commits the composition on the previous window.
   *  If this method did commit a composition, this returns TRUE.
   */
  bool CommitCompositionOnPreviousWindow(nsWindow* aWindow);

  /**
   *  ResolveIMECaretPos
   *  Convert the caret rect of a composition event to another widget's
   *  coordinate system.
   *
   *  @param aReferenceWidget The origin widget of aCursorRect.
   *                          Typically, this is mReferenceWidget of the
   *                          composing events. If the aCursorRect is in screen
   *                          coordinates, set nullptr.
   *  @param aCursorRect      The cursor rect.
   *  @param aNewOriginWidget aOutRect will be in this widget's coordinates. If
   *                          this is nullptr, aOutRect will be in screen
   *                          coordinates.
   *  @param aOutRect         The converted cursor rect.
   */
  void ResolveIMECaretPos(nsIWidget* aReferenceWidget,
                          mozilla::LayoutDeviceIntRect& aCursorRect,
                          nsIWidget* aNewOriginWidget,
                          mozilla::LayoutDeviceIntRect& aOutRect);

  bool ConvertToANSIString(const nsString& aStr, UINT aCodePage,
                           nsACString& aANSIStr);

  bool SetIMERelatedWindowsPos(nsWindow* aWindow, const IMEContext& aContext);
  /**
   * GetCharacterRectOfSelectedTextAt() returns character rect of the offset
   * from the selection start or the start of composition string if there is
   * a composition.
   *
   * @param aWindow         The window which has focus.
   * @param aOffset         Offset from the selection start or the start of
   *                        composition string when there is a composition.
   *                        This must be in the selection range or
   *                        the composition string.
   * @param aCharRect       The result.
   * @param aWritingMode    The writing mode of current selection.  When this
   *                        is nullptr, this assumes that the selection is in
   *                        horizontal writing mode.
   * @return                true if this succeeded to retrieve the rect.
   *                        Otherwise, false.
   */
  bool GetCharacterRectOfSelectedTextAt(
      nsWindow* aWindow, uint32_t aOffset,
      mozilla::LayoutDeviceIntRect& aCharRect,
      mozilla::WritingMode* aWritingMode = nullptr);
  /**
   * GetCaretRect() returns caret rect at current selection start.
   *
   * @param aWindow         The window which has focus.
   * @param aCaretRect      The result.
   * @param aWritingMode    The writing mode of current selection.  When this
   *                        is nullptr, this assumes that the selection is in
   *                        horizontal writing mode.
   * @return                true if this succeeded to retrieve the rect.
   *                        Otherwise, false.
   */
  bool GetCaretRect(nsWindow* aWindow, mozilla::LayoutDeviceIntRect& aCaretRect,
                    mozilla::WritingMode* aWritingMode = nullptr);
  void GetCompositionString(const IMEContext& aContext, DWORD aIndex,
                            nsAString& aCompositionString) const;

  /**
   * AdjustCompositionFont() makes IME vertical writing mode if it's supported.
   * If aForceUpdate is true, it will update composition font even if writing
   * mode isn't being changed.
   */
  void AdjustCompositionFont(nsWindow* aWindow, const IMEContext& aContext,
                             const mozilla::WritingMode& aWritingMode,
                             bool aForceUpdate = false);

  /**
   * MaybeAdjustCompositionFont() calls AdjustCompositionFont() when the
   * locale of active IME is CJK.  Note that this creates an instance even
   * when there is no composition but the locale is CJK.
   */
  static void MaybeAdjustCompositionFont(
      nsWindow* aWindow, const mozilla::WritingMode& aWritingMode,
      bool aForceUpdate = false);

  /**
   *  Get the current target clause of composition string.
   *  If there are one or more characters whose attribute is ATTR_TARGET_*,
   *  this returns the first character's offset and its length.
   *  Otherwise, e.g., the all characters are ATTR_INPUT, this returns
   *  the composition string range because the all is the current target.
   *
   *  aLength can be null (default), but aOffset must not be null.
   *
   *  The aOffset value is offset in the contents.  So, when you need offset
   *  in the composition string, you need to subtract mCompositionStart from it.
   */
  bool GetTargetClauseRange(uint32_t* aOffset, uint32_t* aLength = nullptr);

  /**
   * DispatchEvent() dispatches aEvent if aWidget hasn't been destroyed yet.
   */
  static void DispatchEvent(nsWindow* aWindow, WidgetGUIEvent& aEvent);

  /**
   * DispatchCompositionChangeEvent() dispatches eCompositionChange event
   * with clause information (it'll be retrieved by CreateTextRangeArray()).
   * I.e., this should be called only during composing.  If a composition is
   * being committed, only HandleCompositionEnd() should be called.
   *
   * @param aWindow     The window which has the composition.
   * @param aContext    Native IME context which has the composition.
   */
  void DispatchCompositionChangeEvent(nsWindow* aWindow,
                                      const IMEContext& aContext);

  nsresult EnsureClauseArray(int32_t aCount);
  nsresult EnsureAttributeArray(int32_t aCount);

  /**
   * When WM_IME_CHAR is received and passed to DefWindowProc, we need to
   * record the messages.  In other words, we should record the messages
   * when we receive WM_IME_CHAR on windowless plug-in (if we have focus,
   * we always eat them).  When focus is moved from a windowless plug-in to
   * our window during composition, WM_IME_CHAR messages were received when
   * the plug-in has focus.  However, WM_CHAR messages are received after the
   * plug-in lost focus.  So, we need to ignore the WM_CHAR messages because
   * they make unexpected text input events on us.
   */
  nsTArray<MSG> mPassedIMEChar;

  bool IsIMECharRecordsEmpty() { return mPassedIMEChar.IsEmpty(); }
  void ResetIMECharRecords() { mPassedIMEChar.Clear(); }
  void DequeueIMECharRecords(WPARAM& wParam, LPARAM& lParam) {
    MSG msg = mPassedIMEChar.ElementAt(0);
    wParam = msg.wParam;
    lParam = msg.lParam;
    mPassedIMEChar.RemoveElementAt(0);
  }
  void EnqueueIMECharRecords(WPARAM wParam, LPARAM lParam) {
    MSG msg;
    msg.wParam = wParam;
    msg.lParam = lParam;
    mPassedIMEChar.AppendElement(msg);
  }

  TextEventDispatcher* GetTextEventDispatcherFor(nsWindow* aWindow);

  nsWindow* mComposingWindow;
  RefPtr<TextEventDispatcher> mDispatcher;
  nsString mCompositionString;
  nsTArray<uint32_t> mClauseArray;
  nsTArray<uint8_t> mAttributeArray;

  int32_t mCursorPosition;
  uint32_t mCompositionStart;

  // mContentSelection stores the latest selection data only when sHasFocus is
  // true. Don't access mContentSelection directly.  You should use
  // GetContentSelectionWithQueryIfNothing() for getting proper state.
  Maybe<ContentSelection> mContentSelection;

  const Maybe<ContentSelection>& GetContentSelectionWithQueryIfNothing(
      nsWindow* aWindow) {
    // When IME has focus, mContentSelection is automatically updated by
    // NOTIFY_IME_OF_SELECTION_CHANGE.
    if (sHasFocus) {
      if (mContentSelection.isNothing()) {
        // But if this is the first access of mContentSelection, we need to
        // query selection now.
        mContentSelection = QueryContentSelection(aWindow);
      }
      return mContentSelection;
    }
    // Otherwise, i.e., While IME doesn't have focus, we cannot observe
    // selection changes.  So, in such case, we need to query selection
    // when it's necessary.
    static Maybe<ContentSelection> sTempContentSelection;
    sTempContentSelection = QueryContentSelection(aWindow);
    return sTempContentSelection;
  }

  /**
   * Query content selection on aWindow with WidgetQueryContent event.
   */
  static Maybe<ContentSelection> QueryContentSelection(nsWindow* aWindow);

  bool mIsComposing;

  static mozilla::WritingMode sWritingModeOfCompositionFont;
  static nsString sIMEName;
  static UINT sCodePage;
  static DWORD sIMEProperty;
  static DWORD sIMEUIProperty;
  static bool sAssumeVerticalWritingModeNotSupported;
  static bool sHasFocus;
};

}  // namespace widget
}  // namespace mozilla

#endif  // IMMHandler_h_