QWO4: some tweaks with diagonals
[mokosuite2:mokowm.git] / src / qwo4.c
1 /*
2  * Mokosuite
3  * Quickwriting4 input method
4  * Copyright (C) 2009-2010 Daniele Ricci <daniele.athome@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include <Evas.h>
22 #include <fakekey/fakekey.h>
23
24 #include "globals.h"
25 #include "qwo4.h"
26
27 // FIXME these have to be fixed
28 #define LANDSCAPE_INPUT_WIDTH     640
29 #define LANDSCAPE_INPUT_HEIGHT    254 //234
30 #define LANDSCAPE_INPUT_X         0
31 #define LANDSCAPE_INPUT_Y         (480 - LANDSCAPE_INPUT_HEIGHT)
32
33
34 #define PORTRAIT_INPUT_WIDTH     480
35 #define PORTRAIT_INPUT_HEIGHT    320
36 #define PORTRAIT_INPUT_X         0
37 #define PORTRAIT_INPUT_Y         (640 - PORTRAIT_INPUT_HEIGHT)
38
39 #define GLYPH_FONT          "Sans:style=bold"
40 #define GLYPH_SIZE_NORMAL   18
41 #define GLYPH_SIZE_ACTIVE   20
42 #define GLYPH_COLOR_NORMAL  200, 200, 200, 255
43 #define GLYPH_COLOR_ACTIVE  0, 0, 0, 255
44
45 #define XCOORD(v)       (v[0])
46 #define YCOORD(v)       (v[1])
47
48 #define SECTION_OFFSET  50
49
50 // il quadrato sara' calcolato all'avvio
51 #define CENTER_RADIUS   60
52 static int center_radius = CENTER_RADIUS * CENTER_RADIUS;
53
54 // sezioni
55 #define NUM_SECTIONS        4
56
57 // lettere disponibili
58 #define LETTERS_PER_SECTION     NUM_SECTIONS
59 //static char letters[] = { "abcdefghijklmnopqrstuvwzyx.,?!@'" };
60 static char letters[] = { "abcd?!.,ijklefghqrstmnopyz@'uvwx" };
61
62 typedef struct {
63     FakeKey* fk;
64
65     // TRUE quando siamo fuori dal centro per navigare
66     bool moving;
67     // TRUE quando stiamo muovendo il mouse nel centro
68     bool in_center;
69     // TRUE se non siamo ancora usciti dal centro dopo una pressione
70     bool is_space;
71
72     // sezione corrente
73     int cur_section; // = -1;
74     // sezione di partenza
75     int start_section; // = -1;
76     // indice di lettera corrente
77     int cur_letter;
78
79     Evas_Object** glyphs;
80     Evas_Object* old_glyph;
81     int sections[NUM_SECTIONS][3][2];
82
83     int center_point[2];
84
85 } qwo4_private_data;
86
87 // thanks to http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
88 static int pnpoly(int nvert, int vert[][2], int testx, int testy)
89 {
90   int i, j, c = 0;
91   for (i = 0, j = nvert-1; i < nvert; j = i++) {
92     if ( ( (YCOORD(vert[i]) > testy) != (YCOORD(vert[j]) > testy) ) &&
93      (testx < (XCOORD(vert[j]) - XCOORD(vert[i])) * (testy - YCOORD(vert[i])) / (YCOORD(vert[j]) - YCOORD(vert[i])) + XCOORD(vert[i])) )
94        c = !c;
95   }
96   return c;
97 }
98
99 static int distance2(int x1, int y1, int x2, int y2)
100 {
101     int xx = x2 - x1;
102     int yy = y2 - y1;
103     return (xx * xx) + (yy * yy);
104 }
105
106 static bool inside_center(qwo4_private_data* priv, int x, int y)
107 {
108     return distance2(x, y, XCOORD(priv->center_point), YCOORD(priv->center_point)) <= center_radius;
109 }
110
111 static void highlight_letter(qwo4_private_data* priv)
112 {
113     Evas_Object* gly = NULL;
114     if (priv->cur_letter)
115         gly = priv->glyphs[
116             (priv->start_section * LETTERS_PER_SECTION * 2) + // macroblocco (sezione)
117             ((priv->cur_letter < 0 ? 1 : 0) * LETTERS_PER_SECTION) + // microblocco (destra-sinistra)
118             (abs(priv->cur_letter) - 1) // lettera
119         ];
120
121     if (priv->old_glyph) {
122         evas_object_text_font_set(priv->old_glyph, GLYPH_FONT, GLYPH_SIZE_NORMAL);
123         evas_object_color_set(priv->old_glyph, GLYPH_COLOR_NORMAL);
124         priv->old_glyph = NULL;
125     }
126     if (gly) {
127         /*
128         EINA_LOG_DBG("Activating glyph %p [%d]", gly,
129             (priv->start_section * LETTERS_PER_SECTION * 2) + // macroblocco (sezione)
130             ((priv->cur_letter < 0 ? 1 : 0) * LETTERS_PER_SECTION) + // microblocco (destra-sinistra)
131             (abs(priv->cur_letter) - 1) // lettera
132         );
133         */
134         evas_object_text_font_set(gly, GLYPH_FONT, GLYPH_SIZE_ACTIVE);
135         evas_object_color_set(gly, GLYPH_COLOR_ACTIVE);
136         priv->old_glyph = gly;
137     }
138 }
139
140 static void reset(qwo4_private_data* priv)
141 {
142     priv->start_section = -1;
143     priv->cur_section = -1;
144     priv->cur_letter = 0;
145     highlight_letter(priv);
146 }
147
148 static int find_section(qwo4_private_data* priv, int x, int y)
149 {
150     int i;
151 #if 1
152     for (i = 0; i < NUM_SECTIONS; i++)
153         if (pnpoly(3, priv->sections[i], x, y))
154             return i;
155 #else
156     for (i = 0; i < NUM_SECTIONS; i++) {
157         int n = pnpoly(3, priv->sections[i], x, y);
158         EINA_LOG_DBG("poly[%d]=%d", i, n);
159         if (n) return i;
160     }
161 #endif
162
163     return -1;
164 }
165
166 static void _press(void *data, Evas *e, Evas_Object *obj, void *event_info)
167 {
168     wm_input_client* ic = data;
169     qwo4_private_data* priv = ic->private;
170
171     Evas_Event_Mouse_Down* event = event_info;
172     int x = event->canvas.x;
173     int y = event->canvas.y;
174
175     // iniziata pressione!
176     if (inside_center(priv, x, y)) {
177         EINA_LOG_DBG("Press inside center!");
178         priv->moving = TRUE;
179         priv->is_space = TRUE;
180         priv->cur_section = -1;
181         priv->in_center = TRUE;
182     }
183 }
184
185 static void _move(void *data, Evas *e, Evas_Object *obj, void *event_info)
186 {
187     wm_input_client* ic = data;
188     qwo4_private_data* priv = ic->private;
189
190     if (priv->moving) {
191         // subito ferma lo spazio
192         priv->is_space = FALSE;
193
194         Evas_Event_Mouse_Move* event = event_info;
195         int x = event->cur.canvas.x;
196         int y = event->cur.canvas.y;
197         int section = find_section(priv, event->cur.canvas.x, event->cur.canvas.y);
198         bool move_center = inside_center(priv, x, y);
199         //EINA_LOG_DBG("x=%d, y=%d, section=%d", x, y, section);
200
201         // aaaaahhh!!!!
202         if (section < 0 && !move_center) {
203             //EINA_LOG_DBG("AAAAAHHH!!!");
204             //moving = FALSE;
205             //reset();
206             return;
207         }
208
209         // siamo al centro?
210         if (!priv->in_center) {
211             if (move_center) {
212                 // lettera! :)
213                 // bell'algoritmo :D
214                 EINA_LOG_DBG("Got letter (start_section=%d, cur_letter=%d)", priv->start_section, priv->cur_letter);
215                 if (priv->cur_letter) {
216                     char letter = letters[
217                         (priv->start_section * LETTERS_PER_SECTION * 2) + // macroblocco (sezione)
218                         ((priv->cur_letter < 0 ? 1 : 0) * LETTERS_PER_SECTION) + // microblocco (destra-sinistra)
219                         (abs(priv->cur_letter) - 1) // lettera
220                     ];
221
222                     EINA_LOG_DBG("back to center, letter: \"%c\"", letter);
223                     fakekey_press(priv->fk, (unsigned char *) &letter, 1, 0);
224                     fakekey_release(priv->fk);
225                 }
226                 else {
227                     EINA_LOG_DBG("back to center, no letter");
228                 }
229
230                 // reset
231                 reset(priv);
232             }
233         }
234
235         priv->in_center = move_center;
236         // siamo al centro :D
237         if (priv->in_center) return;
238
239         // ci siamo spostati!
240         if (priv->cur_section >= 0) {
241             int diff = section - priv->cur_section;
242             if (diff != 0) {
243                 // caso particolare: boundaries settori (evita di usare abs())
244                 if (diff != 1 && diff != -1) {
245                     // evita di usare abs()
246                     if (diff < 0) diff = 1;
247                     else diff = -1;
248                 }
249
250                 EINA_LOG_DBG("move difference %d (new_section=%d, cur_section=%d)", diff, section, priv->cur_section);
251                 priv->cur_letter += diff;
252                 if (priv->cur_letter > LETTERS_PER_SECTION)
253                     priv->cur_letter = 1;
254                 else if (priv->cur_letter < -LETTERS_PER_SECTION)
255                     priv->cur_letter = -1;
256
257                 highlight_letter(priv);
258             }
259         }
260         else {
261             priv->start_section = section;
262             priv->cur_letter = 0;
263         }
264
265         priv->cur_section = section;
266     }
267 }
268
269 static void _release(void *data, Evas *e, Evas_Object *obj, void *event_info)
270 {
271     wm_input_client* ic = data;
272     qwo4_private_data* priv = ic->private;
273
274     if (priv->is_space) {
275         fakekey_press(priv->fk, (unsigned char *) " ", 1, 0);
276         fakekey_release(priv->fk);
277     }
278
279     EINA_LOG_DBG("releasing");
280     priv->moving = FALSE;
281     priv->is_space = FALSE;
282     reset(priv);
283 }
284
285 static Evas_Object* draw_letter(qwo4_private_data* priv, Evas* evas, const char* text, int center_x, int center_y)
286 {
287     EINA_LOG_DBG("Glyph %s, x=%d, y=%d", text, center_x, center_y);
288     Evas_Object* a = evas_object_text_add(evas);
289     evas_object_text_font_set(a, GLYPH_FONT, GLYPH_SIZE_NORMAL);
290     evas_object_color_set(a, GLYPH_COLOR_NORMAL);
291     evas_object_pass_events_set(a, TRUE);
292     evas_object_text_text_set(a, text);
293     evas_object_move(a, priv->center_point[0] + center_x, priv->center_point[1] + center_y);
294     evas_object_show(a);
295     return a;
296 }
297
298 void qwo4_show(wm_input_client* ic)
299 {
300     ecore_evas_show(ic->window);
301 }
302
303 void qwo4_hide(wm_input_client* ic)
304 {
305     ecore_evas_hide(ic->window);
306 }
307
308 void qwo4_set_orientation(wm_input_client* ic, bool landscape)
309 {
310     EINA_LOG_WARN("orientation change not supported yet.");
311 }
312
313 wm_input_client* qwo4_create(wm_client* c, bool landscape)
314 {
315     wm_input_client* ic = calloc(1, sizeof(wm_input_client));
316     qwo4_private_data* priv = calloc(1, sizeof(qwo4_private_data));
317
318     ic->client = c;
319     ic->landscape = landscape;
320     ic->private = priv;
321
322     ic->set_orientation = qwo4_set_orientation;
323     ic->show = qwo4_show;
324     ic->hide = qwo4_hide;
325
326     if (landscape) {
327         ic->width = LANDSCAPE_INPUT_WIDTH;
328         ic->height = LANDSCAPE_INPUT_HEIGHT;
329         ic->x = LANDSCAPE_INPUT_X;
330         ic->y = LANDSCAPE_INPUT_Y;
331     }
332     else {
333         ic->width = PORTRAIT_INPUT_WIDTH;
334         ic->height = PORTRAIT_INPUT_HEIGHT;
335         ic->x = PORTRAIT_INPUT_X;
336         ic->y = PORTRAIT_INPUT_Y;
337     }
338
339     priv->center_point[0] = ic->width / 2;
340     priv->center_point[1] = ic->height / 2;
341
342     // upper section
343     priv->sections[0][0][0] = SECTION_OFFSET;
344     priv->sections[0][1][0] = ic->width - SECTION_OFFSET;
345     priv->sections[0][2][0] = XCOORD(priv->center_point);
346     priv->sections[0][2][1] = YCOORD(priv->center_point);
347
348     // right section
349     priv->sections[1][0][0] = ic->width - SECTION_OFFSET;
350     priv->sections[1][1][0] = ic->width - SECTION_OFFSET;
351     priv->sections[1][1][1] = ic->height;
352     priv->sections[1][2][0] = XCOORD(priv->center_point);
353     priv->sections[1][2][1] = YCOORD(priv->center_point);
354
355     // bottom section
356     priv->sections[2][0][0] = SECTION_OFFSET;
357     priv->sections[2][0][1] = ic->height;
358     priv->sections[2][1][0] = ic->width - SECTION_OFFSET;
359     priv->sections[2][1][1] = ic->height;
360     priv->sections[2][2][0] = XCOORD(priv->center_point);
361     priv->sections[2][2][1] = YCOORD(priv->center_point);
362
363     // left section
364     priv->sections[3][0][0] = SECTION_OFFSET;
365     priv->sections[3][0][1] = ic->height;
366     priv->sections[3][1][0] = SECTION_OFFSET;
367     priv->sections[3][2][0] = XCOORD(priv->center_point);
368     priv->sections[3][2][1] = YCOORD(priv->center_point);
369
370 #if 0
371     { {0, 0}, {WIDTH, 0}, POINT_CENTER },               // sezione superiore
372     { {WIDTH, 0}, {WIDTH, HEIGHT}, POINT_CENTER },      // sezione destra
373     { {0, HEIGHT}, {WIDTH, HEIGHT}, POINT_CENTER },     // sezione inferiore
374     { {0, HEIGHT}, {0, 0}, POINT_CENTER }               // sezione sinistra
375 #endif
376
377     // finestra input principale
378     ic->window = ecore_evas_new(NULL, ic->x, ic->y, ic->width, ic->height, NULL);
379     if (!ic->window) {
380         EINA_LOG_ERR("unable to create input window canvas");
381         free(ic->private);
382         free(ic);
383         return NULL;
384     }
385
386     ecore_x_netwm_window_type_set(ecore_evas_software_x11_window_get(ic->window),
387         ECORE_X_WINDOW_TYPE_UTILITY);
388
389     ecore_evas_title_set(ic->window, "qwo4");
390     Evas* evas = ecore_evas_get(ic->window);
391
392     //Evas_Object *bg = evas_object_rectangle_add(evas);
393     Evas_Object *bg = evas_object_image_filled_add(evas);
394     evas_object_image_file_set(bg, DATADIR "/mokosuite/wm/qwo4_back.png", NULL);
395     evas_object_resize(bg, ic->width, ic->height);
396     evas_object_color_set(bg, 255, 255, 255, 255);
397     evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
398     evas_object_size_hint_align_set(bg, EVAS_HINT_FILL, EVAS_HINT_FILL);
399     evas_object_show(bg);
400
401 #if 0
402     Evas_Object* diag1 = evas_object_line_add(evas);
403     evas_object_color_set(diag1, 0, 255, 0, 255);
404     evas_object_line_xy_set(diag1, SECTION_OFFSET, 0, ic->width - SECTION_OFFSET, ic->height);
405     evas_object_pass_events_set(diag1, TRUE);
406     evas_object_show(diag1);
407
408     Evas_Object* diag2 = evas_object_line_add(evas);
409     evas_object_color_set(diag2, 0, 0, 255, 255);
410     evas_object_line_xy_set(diag2, SECTION_OFFSET, ic->height, ic->width - SECTION_OFFSET, 0);
411     evas_object_pass_events_set(diag2, TRUE);
412     evas_object_show(diag2);
413 #endif
414
415     Evas_Object** glyphs = priv->glyphs = calloc(strlen(letters), sizeof(Evas_Object*));
416
417     // diagonale primaria \ y = -K * x
418     // -40 = x*30
419     // x*30 = -40
420     // x = -40/30 = -4/3
421 #define LINE_PRI(x, off)     (((float)-4/5*x) - off)
422 #define SECT1_OFF       60
423     glyphs[0] = draw_letter(priv, evas, "a", 40, LINE_PRI(40, SECT1_OFF));
424     glyphs[1] = draw_letter(priv, evas, "b", 70, LINE_PRI(70, SECT1_OFF));
425     glyphs[2] = draw_letter(priv, evas, "c", 100, LINE_PRI(100, SECT1_OFF));
426     glyphs[3] = draw_letter(priv, evas, "d", 130, LINE_PRI(130, SECT1_OFF));
427
428 #define SECT2_OFF       -10
429     glyphs[12] = draw_letter(priv, evas, "e", 80, LINE_PRI(80, SECT2_OFF));
430     glyphs[13] = draw_letter(priv, evas, "f", 110, LINE_PRI(110, SECT2_OFF));
431     glyphs[14] = draw_letter(priv, evas, "g", 140, LINE_PRI(140, SECT2_OFF));
432     glyphs[15] = draw_letter(priv, evas, "h", 170, LINE_PRI(170, SECT2_OFF));
433
434 #define SECT4_OFF       -20
435     glyphs[16] = draw_letter(priv, evas, "q", -40, LINE_PRI(-40, SECT4_OFF));
436     glyphs[17] = draw_letter(priv, evas, "r", -70, LINE_PRI(-70, SECT4_OFF));
437     glyphs[18] = draw_letter(priv, evas, "s", -100, LINE_PRI(-100, SECT4_OFF));
438     glyphs[19] = draw_letter(priv, evas, "t", -130, LINE_PRI(-130, SECT4_OFF));
439
440 #define SECT5_OFF       50
441     glyphs[28] = draw_letter(priv, evas, "u", -80, LINE_PRI(-80, SECT5_OFF));
442     glyphs[29] = draw_letter(priv, evas, "v", -110, LINE_PRI(-110, SECT5_OFF));
443     glyphs[30] = draw_letter(priv, evas, "w", -140, LINE_PRI(-140, SECT5_OFF));
444     glyphs[31] = draw_letter(priv, evas, "x", -170, LINE_PRI(-170, SECT5_OFF));
445
446     // diagonale secondaria /
447 #define LINE_SEC(x, off)     (((float)3.5/5*x) - off)
448     glyphs[4] = draw_letter(priv, evas, "?", -40, LINE_SEC(-40, SECT1_OFF));
449     glyphs[5] = draw_letter(priv, evas, "!", -70, LINE_SEC(-70, SECT1_OFF));
450     glyphs[6] = draw_letter(priv, evas, ".", -100, LINE_SEC(-100, SECT1_OFF));
451     glyphs[7] = draw_letter(priv, evas, ",", -130, LINE_SEC(-130, SECT1_OFF));
452
453 #define SECT2_1_OFF       -20
454     glyphs[24] = draw_letter(priv, evas, "y", -80, LINE_SEC(-80, SECT2_1_OFF));
455     glyphs[25] = draw_letter(priv, evas, "z", -110, LINE_SEC(-110, SECT2_1_OFF));
456     glyphs[26] = draw_letter(priv, evas, "@", -140, LINE_SEC(-140, SECT2_1_OFF));
457     glyphs[27] = draw_letter(priv, evas, "'", -170, LINE_SEC(-170, SECT2_1_OFF));
458
459 #define SECT5_1_OFF       40
460     glyphs[8] = draw_letter(priv, evas, "i", 80, LINE_SEC(80, SECT5_1_OFF));
461     glyphs[9] = draw_letter(priv, evas, "j", 110, LINE_SEC(110, SECT5_1_OFF));
462     glyphs[10] = draw_letter(priv, evas, "k", 140, LINE_SEC(140, SECT5_1_OFF));
463     glyphs[11] = draw_letter(priv, evas, "l", 170, LINE_SEC(170, SECT5_1_OFF));
464
465 #define SECT2_2_OFF      -35
466     glyphs[20] = draw_letter(priv, evas, "m", 30, LINE_SEC(30, SECT2_2_OFF));
467     glyphs[21] = draw_letter(priv, evas, "n", 70, LINE_SEC(70, SECT2_2_OFF));
468     glyphs[22] = draw_letter(priv, evas, "o", 100, LINE_SEC(100, SECT2_2_OFF));
469     glyphs[23] = draw_letter(priv, evas, "p", 130, LINE_SEC(130, SECT2_2_OFF));
470
471     // i callback sono sullo sfondo
472     evas_object_event_callback_add(bg, EVAS_CALLBACK_MOUSE_MOVE, _move, ic);
473     evas_object_event_callback_add(bg, EVAS_CALLBACK_MOUSE_DOWN, _press, ic);
474     evas_object_event_callback_add(bg, EVAS_CALLBACK_MOUSE_UP, _release, ic);
475
476     // inizializza fakekey
477     priv->fk = fakekey_init(ecore_x_display_get());
478     EINA_LOG_DBG("FakeKey instance created (%p)", priv->fk);
479
480     reset(priv);
481     return ic;
482 }