Added adding/sorting kanji by JLPT grade. A few other minor changes.
[jben.git] / src / widget_kanjihwpad.cpp
blobd5f27d0c578d378287d5cbf390c7f0b0ed8bdcbc
1 /*
2 Project: J-Ben
3 Author: Paul Goins
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>
27 #include <sstream>
28 #include <ctime>
29 #include "string_utils.h"
30 #include "errorlog.h"
31 #include <glibmm/i18n.h>
32 #include <gdk/gdk.h>
33 #include "jben_defines.h"
34 #include <cstring>
36 #ifndef KPENGINE_DATADIR
37 #ifdef __WIN32__
38 #define KPENGINE_DATADIR JB_DATADIR "\\kpengine_data"
39 #else
40 #define KPENGINE_DATADIR JB_DATADIR "/kpengine_data"
41 #endif
42 #endif
44 #ifdef __WIN32__
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. */
48 #include <io.h>
49 #endif
51 /**
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);
64 add(da);
65 da.add_events(
66 #ifndef __WIN32__
67 Gdk::POINTER_MOTION_HINT_MASK |
68 #endif
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));
81 show_all_children();
84 const std::vector<wchar_t>& KanjiHWPad::GetResults() {
85 return results;
88 bool KanjiHWPad::OnExpose(GdkEventExpose* event) {
89 Glib::RefPtr<Gdk::Window> rw = da.get_window();
90 if(rw) {
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);
95 rc->clip();
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);
104 rc->paint();
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);
118 /* Draw lines */
119 rc->set_source_rgb(0,0,0);
120 rc->stroke();
122 return true;
125 bool KanjiHWPad::OnMotion(GdkEventMotion* event) {
126 if(event->state & Gdk::BUTTON1_MASK) {
127 if(pCurrentLine)
128 pCurrentLine->push_back(std::pair<int,int>
129 ((int)event->x, (int)event->y));
130 Update();
132 #ifndef __WIN32__
133 gdk_event_request_motions(event);
134 #endif
135 return true;
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));
144 Update();
146 return true;
149 bool KanjiHWPad::OnRelease(GdkEventButton* event) {
150 switch(event->button) {
151 case 1:
152 if(pCurrentLine) {
153 pCurrentLine->push_back(std::pair<int,int>(event->x, event->y));
154 pCurrentLine = NULL;
156 Update();
157 LookupChars();
158 return false;
159 case 3:
160 if(listLines.size()>0) {
161 pCurrentLine = NULL;
162 listLines.pop_back();
163 Update();
164 LookupChars();
165 return false;
168 return true;
171 void KanjiHWPad::Clear() {
172 pCurrentLine = NULL;
173 listLines.clear();
174 results.clear();
175 Update();
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 */
184 results.clear();
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 */
191 char **argv;
192 argv = new char*[4];
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];
196 argv[3] = NULL;
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;
209 oss << '\n';
211 oss << '\n';
213 /* Call kpengine */
214 GPid child_pid;
215 gint standard_in, standard_out, standard_err;
216 GError *error;
217 gboolean result
218 = g_spawn_async_with_pipes
219 (NULL, /* working dir */
220 argv,
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 */
225 &child_pid,
226 &standard_in, &standard_out, &standard_err, &error);
227 if(result) {
228 ssize_t sz;
230 /* Write to our engine */
231 sz = write(standard_in, oss.str().c_str(), oss.str().length());
232 int result = close(standard_in);
233 if(result) {
234 el.Push(EL_Error,
235 (boost::format(_("%s:%d: An error occurred while "
236 "closing the output stream to "
237 "kpengine."))
238 % __FILE__ % __LINE__).str());
241 /* Wait up to 3 seconds to retrieve data */
242 std::ostringstream ossOut, ossErr;
243 char buffer[80];
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);
248 if(sz) {
249 buffer[sz]=0;
250 ossOut << buffer;
252 if(!sz) {
253 sz = read(standard_err, buffer, 79);
254 if(sz) {
255 buffer[sz]=0;
256 ossErr << buffer;
259 if(!sz) {
260 /* Nothing came in to either stream this time. */
261 /* Check for newline on each stream; that signals a full
262 return message. */
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);
270 close(standard_out);
271 close(standard_err);
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');
278 int temp;
279 if(pos!=std::string::npos) data = data.substr(0,pos);
280 /* Tokenize */
281 std::list<std::string> l = StrTokenize<char>(data, " ");
282 for(std::list<std::string>::iterator
283 it = l.begin(); it != l.end(); it++) {
284 if(!it->empty()) {
285 temp = atoi(it->c_str());
286 if(temp > 0xFFFF || temp < 32) {
287 el.Push(EL_Error,
288 (boost::format(
289 _("%s:%d: kpengine returned an out-of-"
290 "range character (0x%X). The "
291 "character has been dropped."))
292 % __FILE__ % __LINE__ % temp).str());
293 } else
294 results.push_back((wchar_t)temp);
298 } else {
299 ostringstream oss;
300 if (error->domain == G_SPAWN_ERROR) {
301 el.Push(EL_Error,
302 (boost::format(_("%s:%d: G_SPAWN_ERROR, code = %d, message = \"%s\""))
303 % __FILE__ % __LINE__
304 % error->code % error->message).str());
305 } else {
306 el.Push(EL_Error,
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());
313 /* cleanup */
314 for(int i=0;i<3;i++)
315 delete[] argv[i];
316 delete[] argv;