4 Website: http://www.vultaire.net/software/jben/
5 License: GNU General Public License (GPL) version 2
6 (http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt)
8 File: widget_kanjihwpad.cpp
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program. If not, see <http://www.gnu.org/licenses/>
24 #include "widget_kanjihwpad.h"
25 #include <cairomm/context.h>
26 #include <boost/format.hpp>
29 #include "string_utils.h"
31 #include <glibmm/i18n.h>
33 #include "jben_defines.h"
36 #ifndef KPENGINE_DATADIR
38 #define KPENGINE_DATADIR JB_DATADIR "\\kpengine_data"
40 #define KPENGINE_DATADIR JB_DATADIR "/kpengine_data"
45 /* NOT clearly documented. To use read/write/open/close/etc, include io.h.
46 On Windows they're preceded with an underscore, but GLib adds
47 #defines for cross-platform convenience. */
52 * A widget for accepting handwritten kanji.
53 * This widget looks up kanji characters based on user input. Updates to the
54 * results can be captured by catching the button-release-event signal.
55 * Button 1 means a line got added, button 3 means one was removed. This
56 * difference should be irrelevant outside the widget implementation; just
57 * check for updates on any button-release-event signal.
59 KanjiHWPad::KanjiHWPad() {
60 /* The label line was added for Win32 builds since my dev version of GTK
61 doesn't handle labelless frames properly. */
62 set_label(_("Draw Kanji (Right click erases)"));
63 set_shadow_type(Gtk::SHADOW_IN
);
67 Gdk::POINTER_MOTION_HINT_MASK
|
69 Gdk::BUTTON1_MOTION_MASK
|
70 Gdk::BUTTON_PRESS_MASK
|
71 Gdk::BUTTON_RELEASE_MASK
);
72 da
.signal_expose_event()
73 .connect(sigc::mem_fun(*this, &KanjiHWPad::OnExpose
));
74 da
.signal_motion_notify_event()
75 .connect(sigc::mem_fun(*this, &KanjiHWPad::OnMotion
));
76 da
.signal_button_press_event()
77 .connect(sigc::mem_fun(*this, &KanjiHWPad::OnPress
));
78 da
.signal_button_release_event()
79 .connect(sigc::mem_fun(*this, &KanjiHWPad::OnRelease
));
84 const std::vector
<wchar_t>& KanjiHWPad::GetResults() {
88 bool KanjiHWPad::OnExpose(GdkEventExpose
* event
) {
89 Glib::RefPtr
<Gdk::Window
> rw
= da
.get_window();
91 Cairo::RefPtr
<Cairo::Context
> rc
= rw
->create_cairo_context();
92 /* Clip update region */
93 rc
->rectangle(event
->area
.x
, event
->area
.y
,
94 event
->area
.width
, event
->area
.height
);
97 /* Set drawing style */
98 rc
->set_line_width(3);
99 rc
->set_line_join(Cairo::LINE_JOIN_ROUND
);
100 rc
->set_line_cap(Cairo::LINE_CAP_ROUND
);
102 /* Fill background */
103 rc
->set_source_rgb(1,1,1);
106 /* Create path from stored data */
107 std::list
< std::list
< std::pair
<int,int> > >::iterator itLine
;
108 std::list
< std::pair
<int,int> >::iterator itPoint
;
109 for(itLine
= listLines
.begin(); itLine
!= listLines
.end(); itLine
++) {
110 if(itLine
->size()<2) continue;
111 itPoint
= itLine
->begin();
112 rc
->move_to(itPoint
->first
, itPoint
->second
);
113 for(itPoint
++; itPoint
!= itLine
->end(); itPoint
++) {
114 rc
->line_to(itPoint
->first
, itPoint
->second
);
119 rc
->set_source_rgb(0,0,0);
125 bool KanjiHWPad::OnMotion(GdkEventMotion
* event
) {
126 if(event
->state
& Gdk::BUTTON1_MASK
) {
128 pCurrentLine
->push_back(std::pair
<int,int>
129 ((int)event
->x
, (int)event
->y
));
133 gdk_event_request_motions(event
);
138 bool KanjiHWPad::OnPress(GdkEventButton
* event
) {
139 if(event
->button
==1) {
140 /* Put a new line in the list and retrieve it. */
141 listLines
.push_back(std::list
< std::pair
<int,int> >());
142 pCurrentLine
= &listLines
.back();
143 pCurrentLine
->push_back(std::pair
<int,int>(event
->x
, event
->y
));
149 bool KanjiHWPad::OnRelease(GdkEventButton
* event
) {
150 switch(event
->button
) {
153 pCurrentLine
->push_back(std::pair
<int,int>(event
->x
, event
->y
));
160 if(listLines
.size()>0) {
162 listLines
.pop_back();
171 void KanjiHWPad::Clear() {
178 void KanjiHWPad::Update() {
179 gdk_window_invalidate_rect(da
.get_window()->gobj(), NULL
, false);
182 void KanjiHWPad::LookupChars() {
183 /* Make our call to jben_kpengine(.exe) and get candidate chars */
185 if(listLines
.size()>0) {
186 /* Here we do a Gtk-style async process call... ug-ly! */
187 const char exename
[] = "jben_kpengine";
188 const char args
[] = "-d";
190 /* Prep command line data */
193 argv
[0] = new char[strlen(exename
)+1];
194 argv
[1] = new char[strlen(args
) +1];
195 argv
[2] = new char[strlen(KPENGINE_DATADIR
)+1];
197 strcpy(argv
[0], exename
);
198 strcpy(argv
[1], args
);
199 strcpy(argv
[2], KPENGINE_DATADIR
);
201 /* Prep data for stdin */
202 std::ostringstream oss
;
203 for(std::list
< std::list
< std::pair
<int,int> > >::iterator
204 li
=listLines
.begin(); li
!=listLines
.end(); li
++) {
205 for(std::list
< std::pair
<int,int> >::iterator
206 pi
=li
->begin(); pi
!=li
->end(); pi
++) {
207 oss
<< boost::format("%d %d ") % pi
->first
% pi
->second
;
215 gint standard_in
, standard_out
, standard_err
;
218 = g_spawn_async_with_pipes
219 (NULL
, /* working dir */
221 NULL
, /* environment - default to parent */
222 (GSpawnFlags
) (G_SPAWN_DO_NOT_REAP_CHILD
| G_SPAWN_SEARCH_PATH
),
223 NULL
, /* child_setup func and user_data, which are */
224 NULL
, /* not useful for Windows builds */
226 &standard_in
, &standard_out
, &standard_err
, &error
);
230 /* Write to our engine */
231 sz
= write(standard_in
, oss
.str().c_str(), oss
.str().length());
232 int result
= close(standard_in
);
235 (boost::format(_("%s:%d: An error occurred while "
236 "closing the output stream to "
238 % __FILE__
% __LINE__
).str());
241 /* Wait up to 3 seconds to retrieve data */
242 std::ostringstream ossOut
, ossErr
;
244 memset((void*)buffer
, 0, 80);
245 time_t st
= std::time(NULL
);
246 while(std::time(NULL
) - st
< 3) {
247 sz
= read(standard_out
, buffer
, 79);
253 sz
= read(standard_err
, buffer
, 79);
260 /* Nothing came in to either stream this time. */
261 /* Check for newline on each stream; that signals a full
263 if(ossOut
.str().find('\n')!=std::string::npos
) break;
264 if(ossErr
.str().find('\n')!=std::string::npos
) break;
268 /* cleanup our child process's stuff */
269 g_spawn_close_pid(child_pid
);
273 if(ossErr
.str().empty() && (!ossOut
.str().empty())) {
274 /* Create new wchar_t list */
275 /* First: trim the response string */
276 std::string data
= ossOut
.str().substr(1); /* skip the "k" */
277 size_t pos
= data
.find('\n');
279 if(pos
!=std::string::npos
) data
= data
.substr(0,pos
);
281 std::list
<std::string
> l
= StrTokenize
<char>(data
, " ");
282 for(std::list
<std::string
>::iterator
283 it
= l
.begin(); it
!= l
.end(); it
++) {
285 temp
= atoi(it
->c_str());
286 if(temp
> 0xFFFF || temp
< 32) {
289 _("%s:%d: kpengine returned an out-of-"
290 "range character (0x%X). The "
291 "character has been dropped."))
292 % __FILE__
% __LINE__
% temp
).str());
294 results
.push_back((wchar_t)temp
);
300 if (error
->domain
== G_SPAWN_ERROR
) {
302 (boost::format(_("%s:%d: G_SPAWN_ERROR, code = %d, message = \"%s\""))
303 % __FILE__
% __LINE__
304 % error
->code
% error
->message
).str());
307 (boost::format(_("%s:%d: Bad call to kpengine! Domain = %d, code = %d, message = \"%s\""))
308 % __FILE__
% __LINE__
309 % error
->domain
% error
->code
% error
->message
).str());