1/*
2 * Copyright 2011 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Copyright 1999 M.Kawamura
6 */
7
8
9#include "CannaLooper.h"
10
11#include <string.h>
12
13#include <Debug.h>
14
15#include <Alert.h>
16#include <FindDirectory.h>
17#include <Input.h>
18#include <Menu.h>
19#include <MenuItem.h>
20#include <Messenger.h>
21#include <Path.h>
22#include <Screen.h>
23
24#include "CannaCommon.h"
25#include "CannaMethod.h"
26#include "KouhoWindow.h"
27#include "PaletteWindow.h"
28
29
30CannaLooper::CannaLooper(CannaMethod* method)
31	: BLooper("Canna Looper"),
32	fOwner(method),
33	fCanna(NULL),
34	fKouhoWindow(NULL),
35	fPaletteWindow(NULL)
36{
37	fMenu = new BMenu(B_EMPTY_STRING);
38	fMenu->SetFont(be_plain_font);
39	fMenu->AddItem(new BMenuItem("About CannaIM" B_UTF8_ELLIPSIS,
40		new BMessage( B_ABOUT_REQUESTED)));
41	fMenu->AddSeparatorItem();
42	fMenu->AddItem(new BMenuItem("Convert arrow keys",
43		new BMessage(ARROW_KEYS_FLIPPED)));
44	fMenu->AddItem(new BMenuItem("Reload Init file",
45		new BMessage(RELOAD_INIT_FILE)));
46
47	if (gSettings.convert_arrowkey) {
48		BMenuItem* item = fMenu->FindItem(ARROW_KEYS_FLIPPED);
49		item->SetMarked(true);
50	}
51
52	Run();
53}
54
55
56status_t
57CannaLooper::Init()
58{
59	char basePath[B_PATH_NAME_LENGTH + 1];
60	status_t err = ReadSettings(basePath);
61	if (err != B_NO_ERROR)
62		return err;
63
64	fCanna = new CannaInterface(basePath);
65
66	return fCanna->InitCheck();
67}
68
69
70void
71CannaLooper::Quit()
72{
73	// delete palette here
74	SERIAL_PRINT(("CannaLooper: destructor called.\n"));
75	delete fCanna;
76
77	if (fKouhoWindow != NULL) {
78		SERIAL_PRINT(("CannaLooper: Sending QUIT to kouho window...\n"));
79
80		fKouhoWindow->Lock();
81		fKouhoWindow->Quit();
82	}
83
84	if (fPaletteWindow) {
85		SERIAL_PRINT(("CannaLooper: Sending QUIT to palette...\n"));
86
87		fPaletteWindow->Lock();
88		fPaletteWindow->Quit();
89	}
90
91	fOwner->SetMenu(NULL, BMessenger());
92	delete fMenu;
93	BLooper::Quit();
94}
95
96
97status_t
98CannaLooper::ReadSettings(char* basePath)
99{
100	BPath path;
101	status_t status = find_directory(B_SYSTEM_DATA_DIRECTORY, &path);
102	if (status != B_OK)
103		return status;
104
105	path.Append("Canna");
106
107	strlcpy(basePath, path.Path(), B_PATH_NAME_LENGTH);
108	strlcat(basePath, "/", B_PATH_NAME_LENGTH);
109
110	font_family family;
111	font_style style;
112	strcpy(family, "VL PGothic");
113	strcpy(style, "regular");
114
115	fKouhoFont.SetFamilyAndStyle(family, style);
116	fKouhoFont.SetSize(12);
117
118	return B_OK;
119}
120
121
122void
123CannaLooper::EnqueueMessage(BMessage* msg)
124{
125	fOwner->EnqueueMessage(msg);
126}
127
128
129void
130CannaLooper::MessageReceived(BMessage* msg)
131{
132	SERIAL_PRINT(("CannaLooper: Entering MessageReceived() what=%.4s\n",
133		(char*)&msg->what));
134
135	switch (msg->what) {
136		case B_KEY_DOWN:
137		case NUM_SELECTED_FROM_KOUHO_WIN:
138			_HandleKeyDown(msg);
139			break;
140
141		case B_INPUT_METHOD_EVENT:
142			uint32 opcode, result;
143			msg->FindInt32("be:opcode", (int32*)&opcode);
144
145			switch (opcode) {
146				case B_INPUT_METHOD_LOCATION_REQUEST:
147					_HandleLocationReply(msg);
148					break;
149
150				case B_INPUT_METHOD_STOPPED:
151					SERIAL_PRINT(("CannaLooper: B_INPUT_METHOD_STOPPED "
152						"received\n"));
153					_ForceKakutei();
154					break;
155			}
156			break;
157
158		case CANNA_METHOD_ACTIVATED:
159			SERIAL_PRINT(("CannaLooper: CANNA_METHOD_ACTIVATED received\n"));
160			_HandleMethodActivated(msg->HasBool("active"));
161			break;
162
163		case MODE_CHANGED_FROM_PALETTE:
164			_ForceKakutei();
165			int32 mode;
166			msg->FindInt32("mode", &mode);
167			result = fCanna->ChangeMode(mode);
168			_ProcessResult(result);
169			break;
170
171		case B_ABOUT_REQUESTED:
172		{
173			SERIAL_PRINT(("CannaLooper: B_ABOUT_REQUESTED received\n"));
174
175			BAlert* panel = new BAlert( "", "Canna Input Method\n"
176				"  by Masao Kawamura 1999\n\n"
177				"Canna\n"
178				"  Copyright 1992 NEC Corporation, Tokyo, Japan\n"
179				"  Special thanks to T.Murai for porting\n",
180				"OK");
181			panel->SetFlags(panel->Flags() | B_CLOSE_ON_ESCAPE);
182			panel->Go();
183			break;
184		}
185
186		case RELOAD_INIT_FILE:
187			_ForceKakutei();
188			fCanna->Reset();
189			break;
190
191		case ARROW_KEYS_FLIPPED:
192		{
193			BMenuItem* item = fMenu->FindItem(ARROW_KEYS_FLIPPED);
194			gSettings.convert_arrowkey = !gSettings.convert_arrowkey;
195			item->SetMarked(gSettings.convert_arrowkey);
196			fCanna->SetConvertArrowKey(gSettings.convert_arrowkey);
197			break;
198		}
199
200		default:
201			BLooper::MessageReceived(msg);
202			break;
203	}
204}
205
206
207void
208CannaLooper::_HandleKeyDown(BMessage* msg)
209{
210	uint32 modifier;
211	int32 key;
212	msg->FindInt32("modifiers", (int32*)&modifier);
213	msg->FindInt32("key", &key);
214
215	if ((modifier & B_COMMAND_KEY) != 0) {
216		EnqueueMessage(DetachCurrentMessage());
217		return;
218	}
219
220	char character;
221	msg->FindInt8("byte", (int8*)&character);
222
223	// The if clause below is to avoid processing key input which char code
224	// is 0x80 or more.
225	// if mikakutei string exists, dispose current message.
226	// Otherwise, send it to application as usual B_KEY_DOWN message.
227	if ((character & 0x80) != 0) {
228		if (fCanna->MikakuteiLength() != 0)
229			delete DetachCurrentMessage();
230		else
231			EnqueueMessage(DetachCurrentMessage());
232
233		return;
234	}
235
236	SERIAL_PRINT(("CannaLooper: HandleKeyDown() calling "
237		"CannaInterface::KeyIn()...\n"));
238
239	uint32 result = fCanna->KeyIn(character, modifier, key);
240
241	SERIAL_PRINT(("CannaLooper: HandleKeyDown() received result = %d from "
242		"CannaInterface.\n", result));
243
244	_ProcessResult(result);
245}
246
247
248void
249CannaLooper::_ProcessResult(uint32 result)
250{
251	SERIAL_PRINT(("CannaLooper: _ProcessResult() processing result = %d\n",
252		result));
253
254	if ((result & GUIDELINE_APPEARED) != 0) {
255		SERIAL_PRINT(("CannaLooper: _ProcessResult() processing "
256			"GUIDELINE_APPEARED\n"));
257
258		if (fCanna->MikakuteiLength() != 0) {
259			// usual guideline i.e. kouho
260
261			BMessage* msg = new BMessage(B_INPUT_METHOD_EVENT);
262			msg->AddInt32("be:opcode", B_INPUT_METHOD_LOCATION_REQUEST);
263			fOwner->EnqueueMessage(msg);
264
265			SERIAL_PRINT(("CannaLooper: B_INPUT_METHOD_LOCATION_REQUEST has "
266				"been sent\n"));
267		} else {
268			// guideline exists, but no mikakutei string - means extend mode
269			// and such.
270			SERIAL_PRINT(("  GUIDELINE_APPEARED: calling "
271				"GenerateKouho()...\n"));
272
273			fKouhoWindow->PostMessage(fCanna->GenerateKouhoString());
274			SERIAL_PRINT(("  GUIDELINE_APPEARED: posting KouhoMsg to "
275				"KouhoWindow %x...\n", fKouhoWindow));
276
277			fKouhoWindow->PostMessage(KOUHO_WINDOW_SHOW_ALONE);
278		}
279	}
280
281	if ((result & GUIDELINE_DISAPPEARED) != 0) {
282		SERIAL_PRINT(("CannaLooper: _ProcessResult() processing "
283			"GUIDELINE_DISAPPEARED\n"));
284		fKouhoWindow->PostMessage(KOUHO_WINDOW_HIDE);
285	}
286
287	if ((result & MODE_CHANGED) != 0) {
288		SERIAL_PRINT(("CannaLooper: _ProcessResult() processing "
289			"MODE_CHANGED\n"));
290
291		BMessage message(PALETTE_WINDOW_BUTTON_UPDATE);
292		message.AddInt32("mode", fCanna->GetMode());
293		fPaletteWindow->PostMessage(&message);
294
295		SERIAL_PRINT(("CannaLooper: PALETTE_BUTTON_UPDATE has been sent. "
296			"mode = %d\n", fCanna->GetMode()));
297	}
298
299	if ((result & GUIDELINE_CHANGED) != 0) {
300		SERIAL_PRINT(("CannaLooper: _ProcessResult() processing "
301			"GUIDELINE_CHANGED\n"));
302		fKouhoWindow->PostMessage(fCanna->GenerateKouhoString());
303	}
304
305	if ((result & THROUGH_INPUT) != 0) {
306		SERIAL_PRINT(("CannaLooper: _ProcessResult() processing "
307			"THROUGH_INPUT\n"));
308		EnqueueMessage(DetachCurrentMessage());
309	}
310
311	if ((result & NEW_INPUT_STARTED) != 0) {
312		SERIAL_PRINT(("CannaLooper: _ProcessResult() processing "
313			"NEW_INPUT_STARTED\n"));
314		SendInputStarted();
315	}
316
317	if ((result & KAKUTEI_EXISTS) != 0) {
318		SERIAL_PRINT(("CannaLooper: _ProcessResult() processing "
319			"KAKUTEI_EXISTS\n"));
320
321		BMessage* msg = new BMessage(B_INPUT_METHOD_EVENT);
322		msg->AddInt32("be:opcode", B_INPUT_METHOD_CHANGED);
323
324		msg->AddString("be:string", fCanna->GetKakuteiStr());
325		msg->AddInt32("be:clause_start", 0);
326		msg->AddInt32("be:clause_end", fCanna->KakuteiLength());
327		msg->AddInt32("be:selection", fCanna->KakuteiLength());
328		msg->AddInt32("be:selection", fCanna->KakuteiLength());
329		msg->AddBool("be:confirmed", true);
330		fOwner->EnqueueMessage(msg);
331
332		SERIAL_PRINT(("CannaLooper: B_INPUT_METHOD_CHANGED (confired) has "
333			"been sent\n"));
334
335		// if both kakutei and mikakutei exist, do not send B_INPUT_STOPPED
336		if (!(result & MIKAKUTEI_EXISTS))
337			SendInputStopped();
338	}
339
340	if ((result & MIKAKUTEI_EXISTS) != 0) {
341		SERIAL_PRINT(("CannaLooper: _ProcessResult() processing "
342			"MIKAKUTEI_EXISTS\n" ));
343
344		int32 start, finish;
345		BMessage* msg = new BMessage(B_INPUT_METHOD_EVENT);
346		msg->AddInt32("be:opcode", B_INPUT_METHOD_CHANGED);
347		msg->AddString("be:string", fCanna->GetMikakuteiStr());
348
349		if (fCanna->HasRev()) {
350			fCanna->GetRevPosition( &start, &finish);
351			msg->AddInt32("be:clause_start", 0);
352			msg->AddInt32("be:clause_end", start);
353			msg->AddInt32("be:clause_start", start);
354			msg->AddInt32("be:clause_end", finish);
355			msg->AddInt32("be:clause_start", finish);
356			msg->AddInt32("be:clause_end", fCanna->MikakuteiLength());
357		} else {
358			start = finish = fCanna->MikakuteiLength();
359			msg->AddInt32("be:clause_start", 0);
360			msg->AddInt32("be:clause_end", fCanna->MikakuteiLength());
361		}
362
363		msg->AddInt32("be:selection", start);
364		msg->AddInt32("be:selection", finish);
365		//msg->AddBool("be:confirmed", false);
366		fOwner->EnqueueMessage(msg);
367
368		SERIAL_PRINT(("CannaLooper: B_INPUT_METHOD_CHANGED (non-confirmed) "
369			"has been sent\n"));
370	}
371
372	if ((result & MIKAKUTEI_BECOME_EMPTY) != 0) {
373		SERIAL_PRINT(("CannaLooper: _ProcessResult() processing "
374			"MIKAKUTEI_BECOME_EMPTY\n" ));
375		BMessage* msg = new BMessage(B_INPUT_METHOD_EVENT);
376		msg->AddInt32("be:opcode", B_INPUT_METHOD_CHANGED);
377
378		msg->AddString("be:string", B_EMPTY_STRING);
379		msg->AddInt32("be:clause_start", 0);
380		msg->AddInt32("be:clause_end", 0);
381		msg->AddInt32("be:selection", 0);
382		msg->AddInt32("be:selection", 0);
383		msg->AddBool( "be:confirmed", true);
384		fOwner->EnqueueMessage(msg);
385
386		SERIAL_PRINT(( "CannaLooper: B_INPUT_METHOD_CHANGED (NULL, confired) "
387			"has been sent\n"));
388
389		SendInputStopped();
390	}
391}
392
393
394void
395CannaLooper::_HandleLocationReply(BMessage* msg)
396{
397	BPoint where = B_ORIGIN;
398	float height = 0.0;
399	int32 start;
400
401	SERIAL_PRINT(("CannaLooper: B_INPUT_METHOD_LOCATION_REQUEST received\n"));
402
403	start = fCanna->GetRevStartPositionInChar();
404
405#ifdef DEBUG
406	type_code type;
407	int32 count;
408	msg->GetInfo("be:location_reply", &type, &count);
409	SERIAL_PRINT(("CannaLooper: LOCATION_REPLY has %d locations.\n", count));
410	SERIAL_PRINT(("CannaLooper: RevStartPosition is %d.\n", start));
411#endif
412
413	msg->FindPoint("be:location_reply", start, &where);
414	msg->FindFloat("be:height_reply", start, &height);
415	BMessage m(KOUHO_WINDOW_SHOWAT);
416	m.AddPoint("position", where);
417	m.AddFloat("height", height);
418
419	fKouhoWindow->PostMessage(fCanna->GenerateKouhoString());
420	fKouhoWindow->PostMessage(&m);
421}
422
423
424void
425CannaLooper::_HandleMethodActivated(bool active)
426{
427	if (active) {
428		if (!fPaletteWindow) {
429			// first time input method activated
430			float x = gSettings.palette_loc.x;
431			float y = gSettings.palette_loc.y;
432			BRect frame(x, y, x + 114, y + 44);
433			fPaletteWindow = new PaletteWindow(frame, this);
434			fPaletteWindow->Show();
435			fKouhoWindow = new KouhoWindow(&fKouhoFont, this);
436			fKouhoWindow->Run();
437		}
438
439		fPaletteWindow->PostMessage(PALETTE_WINDOW_SHOW);
440		fOwner->SetMenu(fMenu, this);
441	} else {
442		_ForceKakutei();
443		fCanna->ChangeMode(CANNA_MODE_HenkanMode);
444		BMessage m(PALETTE_WINDOW_BUTTON_UPDATE);
445		m.AddInt32("mode", CANNA_MODE_HenkanMode);
446		fPaletteWindow->PostMessage(&m);
447		fPaletteWindow->PostMessage(PALETTE_WINDOW_HIDE);
448		fKouhoWindow->PostMessage(KOUHO_WINDOW_HIDE);
449		fOwner->SetMenu(NULL, this);
450	}
451}
452
453
454void
455CannaLooper::_ForceKakutei()
456{
457	SERIAL_PRINT(( "CannaLooper: _ForceKakutei() called\n" ));
458
459	uint32 result = fCanna->Kakutei();
460
461	SERIAL_PRINT(("CannaLooper: returned from Kakutei(). result = %d\n",
462		result));
463
464	if (result != NOTHING_TO_KAKUTEI)
465		_ProcessResult(result);
466	else
467		SendInputStopped();
468}
469
470
471void
472CannaLooper::SendInputStopped()
473{
474	BMessage* msg = new BMessage(B_INPUT_METHOD_EVENT);
475	msg->AddInt32("be:opcode", B_INPUT_METHOD_STOPPED);
476	EnqueueMessage(msg);
477
478	SERIAL_PRINT(("CannaLooper: B_INPUT_METHOD_STOPPED has been sent\n"));
479}
480
481
482void
483CannaLooper::SendInputStarted()
484{
485	BMessage* msg = new BMessage(B_INPUT_METHOD_EVENT);
486	msg->AddInt32("be:opcode", B_INPUT_METHOD_STARTED);
487	msg->AddMessenger("be:reply_to", BMessenger(NULL, this));
488	EnqueueMessage(msg);
489
490	SERIAL_PRINT(("CannaLooper: B_INPUT_METHOD_STARTED has been sent\n"));
491}
492
493