| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2026 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / Scene Compositor sub-project | |
| * | |
| * GPAC is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include "visual_manager.h" | |
| #include "nodes_stacks.h" | |
| #include "mpeg4_grouping.h" | |
| #include <gpac/utf.h> | |
| #ifndef GPAC_DISABLE_COMPOSITOR | |
| static GF_Node *browse_parent_for_focus(GF_Compositor *compositor, GF_Node *elt, Bool prev_focus); | |
| static void gf_sc_reset_collide_cursor(GF_Compositor *compositor) | |
| { | |
| if (compositor->sensor_type == GF_CURSOR_COLLIDE) { | |
| GF_Event evt; | |
| compositor->sensor_type = evt.cursor.cursor_type = GF_CURSOR_NORMAL; | |
| evt.type = GF_EVENT_SET_CURSOR; | |
| compositor->video_out->ProcessEvent(compositor->video_out, &evt); | |
| } | |
| } | |
| static Bool exec_text_selection(GF_Compositor *compositor, GF_Event *event) | |
| { | |
| if (event->type > GF_EVENT_MOUSEMOVE) return GF_FALSE; | |
| if (compositor->edited_text) | |
| return GF_FALSE; | |
| if (compositor->text_selection ) | |
| return compositor->hit_text ? GF_TRUE : GF_FALSE; | |
| switch (event->type) { | |
| case GF_EVENT_MOUSEMOVE: | |
| if (compositor->text_selection && compositor->hit_text) | |
| return GF_TRUE; | |
| break; | |
| case GF_EVENT_MOUSEDOWN: | |
| if (compositor->hit_text) { | |
| compositor->text_selection = compositor->hit_text; | |
| /*return 0: the event may be consumed by the tree*/ | |
| return GF_FALSE; | |
| } | |
| break; | |
| } | |
| return GF_FALSE; | |
| } | |
| static void flush_text_node_edit(GF_Compositor *compositor, Bool final_flush) | |
| { | |
| Bool signal; | |
| if (!compositor->edited_text) return; | |
| /* if this is the final editing and there is text, | |
| we need to remove the caret from the text selection buffer */ | |
| if (final_flush && compositor->sel_buffer_len) { | |
| memmove(&compositor->sel_buffer[compositor->caret_pos], &compositor->sel_buffer[compositor->caret_pos+1], sizeof(u16)*(compositor->sel_buffer_len-compositor->caret_pos)); | |
| compositor->sel_buffer_len--; | |
| compositor->sel_buffer[compositor->sel_buffer_len] = 0; | |
| } | |
| /* Recomputes the edited text */ | |
| if (*compositor->edited_text) { | |
| gf_free(*compositor->edited_text); | |
| *compositor->edited_text = NULL; | |
| } | |
| if (compositor->sel_buffer_len) { | |
| char *txt; | |
| u32 len; | |
| const u16 *lptr; | |
| txt = (char*)gf_malloc(sizeof(char)*2*compositor->sel_buffer_len); | |
| lptr = compositor->sel_buffer; | |
| len = gf_utf8_wcstombs(txt, 2*compositor->sel_buffer_len, &lptr); | |
| if (len == GF_UTF8_FAIL) len = 0; | |
| txt[len] = 0; | |
| *compositor->edited_text = gf_strdup(txt); | |
| gf_free(txt); | |
| } | |
| signal = final_flush; | |
| if ((compositor->focus_text_type==4) && (final_flush==1)) signal = GF_FALSE; | |
| gf_node_dirty_set(compositor->focus_node, 0, GF_TRUE); | |
| //(compositor->focus_text_type==2)); | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| /*notify compositor that text has been edited, in order to update composite textures*/ | |
| //compositor->text_edit_changed = 1; | |
| gf_node_set_private(compositor->focus_highlight->node, NULL); | |
| /* if this is the final flush, we free the selection buffer and edited text buffer | |
| and signal a text content change in the focus node */ | |
| if (final_flush) { | |
| GF_FieldInfo info; | |
| if (compositor->sel_buffer) gf_free(compositor->sel_buffer); | |
| compositor->sel_buffer = NULL; | |
| compositor->sel_buffer_len = compositor->sel_buffer_alloc = 0; | |
| compositor->edited_text = NULL; | |
| if (compositor->focus_node && signal) { | |
| memset(&info, 0, sizeof(GF_FieldInfo)); | |
| info.fieldIndex = (u32) -1; | |
| if (compositor->focus_text_type>=3) { | |
| gf_node_get_field(compositor->focus_node, 0, &info); | |
| #ifndef GPAC_DISABLE_VRML | |
| gf_node_event_out(compositor->focus_node, 0); | |
| #endif | |
| } | |
| gf_node_changed(compositor->focus_node, &info); | |
| } | |
| } | |
| } | |
| GF_EXPORT | |
| GF_Err gf_sc_paste_text(GF_Compositor *compositor, const char *text) | |
| { | |
| u16 *conv_buf; | |
| u32 len; | |
| if (!compositor->sel_buffer || !compositor->edited_text) return GF_BAD_PARAM; | |
| if (!text) return GF_OK; | |
| len = (u32) strlen(text); | |
| if (!len) return GF_OK; | |
| gf_sc_lock(compositor, GF_TRUE); | |
| conv_buf = (u16*)gf_malloc(sizeof(u16)*((len/2)*2+2)); | |
| len = gf_utf8_mbstowcs(conv_buf, len+1, &text); | |
| if (len == GF_UTF8_FAIL) return GF_IO_ERR; | |
| compositor->sel_buffer_alloc += len; | |
| if (compositor->sel_buffer_len == compositor->sel_buffer_alloc) | |
| compositor->sel_buffer_alloc++; | |
| compositor->sel_buffer = (u16*)gf_realloc(compositor->sel_buffer, sizeof(u16)*compositor->sel_buffer_alloc); | |
| memmove(&compositor->sel_buffer[compositor->caret_pos+len], &compositor->sel_buffer[compositor->caret_pos], sizeof(u16)*(compositor->sel_buffer_len-compositor->caret_pos)); | |
| memcpy(&compositor->sel_buffer[compositor->caret_pos], conv_buf, sizeof(u16)*len); | |
| gf_free(conv_buf); | |
| compositor->sel_buffer_len += len; | |
| compositor->caret_pos += len; | |
| compositor->sel_buffer[compositor->sel_buffer_len]=0; | |
| flush_text_node_edit(compositor, GF_FALSE); | |
| gf_sc_lock(compositor, GF_FALSE); | |
| return GF_OK; | |
| } | |
| static Bool load_text_node(GF_Compositor *compositor, u32 cmd_type) | |
| { | |
| char **res = NULL; | |
| u32 prev_pos, pos=0; | |
| s32 caret_pos; | |
| Bool append; | |
| #ifndef GPAC_DISABLE_SVG | |
| Bool delete_cr = GF_FALSE; | |
| #endif | |
| caret_pos = -1; | |
| append = GF_FALSE; | |
| switch (cmd_type) { | |
| /*load last text chunk*/ | |
| case 0: | |
| pos = 0; | |
| break; | |
| /*load prev text chunk*/ | |
| case 4: | |
| #ifndef GPAC_DISABLE_SVG | |
| delete_cr = GF_TRUE; | |
| #endif | |
| case 1: | |
| if (compositor->dom_text_pos == 0) return GF_FALSE; | |
| pos = compositor->dom_text_pos - 1; | |
| break; | |
| /*load next text chunk*/ | |
| case 2: | |
| pos = compositor->dom_text_pos + 1; | |
| caret_pos = 0; | |
| break; | |
| /*split text chunk/append new*/ | |
| case 3: | |
| append = GF_TRUE; | |
| pos = compositor->dom_text_pos; | |
| caret_pos = 0; | |
| break; | |
| } | |
| prev_pos = compositor->dom_text_pos; | |
| compositor->dom_text_pos = 0; | |
| #ifndef GPAC_DISABLE_VRML | |
| if (compositor->focus_text_type>=3) { | |
| MFString *mf = &((M_Text*)compositor->focus_node)->string; | |
| if (append) { | |
| gf_sg_vrml_mf_append(mf, GF_SG_VRML_MFSTRING, (void **)res); | |
| compositor->dom_text_pos = mf->count; | |
| } else if (!cmd_type) { | |
| compositor->dom_text_pos = mf->count; | |
| } else if (pos <= mf->count) { | |
| compositor->dom_text_pos = pos; | |
| } else { | |
| compositor->dom_text_pos = prev_pos; | |
| return GF_FALSE; | |
| } | |
| if (compositor->picked_span_idx >=0) { | |
| compositor->dom_text_pos = 1+compositor->picked_span_idx; | |
| compositor->picked_span_idx = -1; | |
| } | |
| /*take care of loading empty text nodes*/ | |
| if (!mf->count) { | |
| mf->count = 1; | |
| mf->vals = (char**)gf_malloc(sizeof(char*)); | |
| mf->vals[0] = gf_strdup(""); | |
| } | |
| if (!mf->vals[0]) mf->vals[0] = gf_strdup(""); | |
| if (!compositor->dom_text_pos || (compositor->dom_text_pos>mf->count)) { | |
| compositor->dom_text_pos = prev_pos; | |
| return GF_FALSE; | |
| } | |
| res = &mf->vals[compositor->dom_text_pos-1]; | |
| if (compositor->picked_glyph_idx>=0) { | |
| caret_pos = compositor->picked_glyph_idx; | |
| compositor->picked_glyph_idx = -1; | |
| if (caret_pos > (s32) strlen(*res)) | |
| caret_pos = -1; | |
| } | |
| } else | |
| #endif /*GPAC_DISABLE_VRML*/ | |
| if (compositor->focus_node) { | |
| #ifndef GPAC_DISABLE_SVG | |
| GF_ChildNodeItem *child = ((GF_ParentNode *) compositor->focus_node)->children; | |
| while (child) { | |
| switch (gf_node_get_tag(child->node)) { | |
| case TAG_DOMText: | |
| break; | |
| default: | |
| child = child->next; | |
| continue; | |
| } | |
| compositor->dom_text_pos++; | |
| if (!cmd_type) res = &((GF_DOMText *)child->node)->textContent; | |
| else if (pos==compositor->dom_text_pos) { | |
| if (append) { | |
| u16 end; | |
| const u16 *srcp; | |
| GF_DOMText *ntext; | |
| GF_ChildNodeItem *children = ((GF_ParentNode *) compositor->focus_node)->children; | |
| GF_Node *t = gf_node_new(gf_node_get_graph(child->node), TAG_SVG_tbreak); | |
| gf_node_init(t); | |
| gf_node_register(t, compositor->focus_node); | |
| pos = gf_node_list_find_child(children, child->node); | |
| /*we're only inserting a tbreak*/ | |
| if (!compositor->caret_pos) { | |
| gf_node_list_insert_child(&children, t, pos); | |
| res = &((GF_DOMText *)child->node)->textContent; | |
| } else { | |
| u32 len; | |
| GF_DOMText *cur; | |
| gf_node_list_insert_child(&children, t, pos+1); | |
| ntext = (GF_DOMText*) gf_node_new(gf_node_get_graph(child->node), TAG_DOMText); | |
| gf_node_init(t); | |
| gf_node_list_insert_child(&children, (GF_Node *)ntext, pos+2); | |
| gf_node_register((GF_Node*)ntext, compositor->focus_node); | |
| cur = (GF_DOMText*) child->node; | |
| gf_free(cur->textContent); | |
| end = compositor->sel_buffer[compositor->caret_pos]; | |
| compositor->sel_buffer[compositor->caret_pos] = 0; | |
| len = gf_utf8_wcslen(compositor->sel_buffer); | |
| cur->textContent = (char*)gf_malloc(sizeof(char)*(len+1)); | |
| srcp = compositor->sel_buffer; | |
| len = gf_utf8_wcstombs(cur->textContent, len, &srcp); | |
| if (len == GF_UTF8_FAIL) len = 0; | |
| cur->textContent[len] = 0; | |
| compositor->sel_buffer[compositor->caret_pos] = end; | |
| if (compositor->caret_pos+1<compositor->sel_buffer_len) { | |
| len = gf_utf8_wcslen(compositor->sel_buffer + compositor->caret_pos + 1); | |
| ntext->textContent = (char*)gf_malloc(sizeof(char)*(len+1)); | |
| srcp = compositor->sel_buffer + compositor->caret_pos + 1; | |
| len = gf_utf8_wcstombs(ntext->textContent, len, &srcp); | |
| if (len == GF_UTF8_FAIL) len = 0; | |
| ntext->textContent[len] = 0; | |
| } else { | |
| ntext->textContent = gf_strdup(""); | |
| } | |
| res = &ntext->textContent; | |
| compositor->dom_text_pos ++; | |
| compositor->edited_text = NULL; | |
| } | |
| } else { | |
| if (delete_cr && child->next) { | |
| GF_Node *tbreak = child->next->node; | |
| GF_ChildNodeItem *children = ((GF_ParentNode *) compositor->focus_node)->children; | |
| gf_node_list_del_child(&children, tbreak); | |
| gf_node_unregister(tbreak, compositor->focus_node); | |
| if (child->next && (gf_node_get_tag(child->next->node)==TAG_DOMText) ) { | |
| GF_DOMText *n1 = (GF_DOMText *)child->node; | |
| GF_DOMText *n2 = (GF_DOMText *)child->next->node; | |
| if (compositor->edited_text) { | |
| flush_text_node_edit(compositor, GF_TRUE); | |
| } | |
| if (!n1->textContent) n1->textContent = gf_strdup(""); | |
| caret_pos = (u32) strlen(n1->textContent); | |
| if (n2->textContent) { | |
| gf_dynstrcat(&n1->textContent, n2->textContent, NULL); | |
| } | |
| gf_node_list_del_child(&children, (GF_Node*)n2); | |
| gf_node_unregister((GF_Node*)n2, compositor->focus_node); | |
| compositor->edited_text = NULL; | |
| } | |
| } | |
| res = &((GF_DOMText *)child->node)->textContent; | |
| } | |
| break; | |
| } | |
| child = child->next; | |
| continue; | |
| } | |
| /*load of an empty text*/ | |
| if (!res && !cmd_type) { | |
| GF_DOMText *t = gf_dom_add_text_node(compositor->focus_node, gf_strdup("")); | |
| res = &t->textContent; | |
| } | |
| if (!res) { | |
| compositor->dom_text_pos = prev_pos; | |
| return GF_FALSE; | |
| } | |
| #endif | |
| } | |
| if (compositor->edited_text) { | |
| flush_text_node_edit(compositor, GF_TRUE); | |
| } | |
| if (res && *res && strlen(*res) ) { | |
| const char *src = *res; | |
| compositor->sel_buffer_alloc = 2 + (u32) strlen(src); | |
| compositor->sel_buffer = (u16*)gf_realloc(compositor->sel_buffer, sizeof(u16)*compositor->sel_buffer_alloc); | |
| if (caret_pos>=0) { | |
| u32 l = gf_utf8_mbstowcs(compositor->sel_buffer, compositor->sel_buffer_alloc, &src); | |
| if (l == GF_UTF8_FAIL) return GF_FALSE; | |
| compositor->sel_buffer_len = l; | |
| memmove(&compositor->sel_buffer[caret_pos+1], &compositor->sel_buffer[caret_pos], sizeof(u16)*(compositor->sel_buffer_len-caret_pos)); | |
| compositor->sel_buffer[caret_pos] = GF_CARET_CHAR; | |
| compositor->caret_pos = caret_pos; | |
| } else { | |
| u32 l = gf_utf8_mbstowcs(compositor->sel_buffer, compositor->sel_buffer_alloc, &src); | |
| if (l == GF_UTF8_FAIL) return GF_FALSE; | |
| compositor->sel_buffer_len = l; | |
| compositor->sel_buffer[compositor->sel_buffer_len] = GF_CARET_CHAR; | |
| compositor->caret_pos = compositor->sel_buffer_len; | |
| } | |
| compositor->sel_buffer_len++; | |
| compositor->sel_buffer[compositor->sel_buffer_len]=0; | |
| } else { | |
| compositor->sel_buffer_alloc = 2; | |
| compositor->sel_buffer = (u16*)gf_malloc(sizeof(u16)*2); | |
| compositor->sel_buffer[0] = GF_CARET_CHAR; | |
| compositor->sel_buffer[1] = 0; | |
| compositor->caret_pos = 0; | |
| compositor->sel_buffer_len = 1; | |
| } | |
| compositor->edited_text = res; | |
| compositor->text_edit_changed = GF_TRUE; | |
| flush_text_node_edit(compositor, GF_FALSE); | |
| return GF_TRUE; | |
| } | |
| static void exec_text_input(GF_Compositor *compositor, GF_Event *event) | |
| { | |
| Bool is_end = GF_FALSE; | |
| if (!event) { | |
| load_text_node(compositor, 0); | |
| return; | |
| } else if (event->type==GF_EVENT_TEXTINPUT) { | |
| u32 unicode_char = event->character.unicode_char; | |
| //filter all non-text symbols | |
| if (unicode_char <= 31) { | |
| return; | |
| } | |
| { | |
| #ifndef GPAC_DISABLE_SVG | |
| GF_DOM_Event evt; | |
| GF_Node *target; | |
| /*send text input event*/ | |
| memset(&evt, 0, sizeof(GF_DOM_Event)); | |
| evt.key_flags = event->key.flags; | |
| evt.bubbles = 1; | |
| evt.cancelable = 1; | |
| evt.type = event->type; | |
| evt.detail = unicode_char; | |
| target = compositor->focus_node; | |
| if (!target) target = gf_sg_get_root_node(compositor->scene); | |
| gf_dom_event_fire(target, &evt); | |
| /*event has been cancelled*/ | |
| if (evt.event_phase & GF_DOM_EVENT_CANCEL_MASK) return; | |
| if (compositor->sel_buffer_len + 1 == compositor->sel_buffer_alloc) { | |
| compositor->sel_buffer_alloc += 10; | |
| compositor->sel_buffer = (u16*)gf_realloc(compositor->sel_buffer, sizeof(u16)*compositor->sel_buffer_alloc); | |
| } | |
| memmove(&compositor->sel_buffer[compositor->caret_pos+1], &compositor->sel_buffer[compositor->caret_pos], sizeof(u16)*(compositor->sel_buffer_len-compositor->caret_pos)); | |
| compositor->sel_buffer[compositor->caret_pos] = unicode_char; | |
| compositor->sel_buffer_len++; | |
| compositor->caret_pos++; | |
| compositor->sel_buffer[compositor->sel_buffer_len] = 0; | |
| #endif | |
| } | |
| } else if (event->type==GF_EVENT_KEYDOWN) { | |
| u32 prev_caret = compositor->caret_pos; | |
| switch (event->key.key_code) { | |
| /*end of edit mode*/ | |
| case GF_KEY_ESCAPE: | |
| is_end = GF_TRUE; | |
| break; | |
| case GF_KEY_LEFT: | |
| if (compositor->caret_pos) { | |
| if (event->key.flags & GF_KEY_MOD_CTRL) { | |
| while (compositor->caret_pos && (compositor->sel_buffer[compositor->caret_pos] != ' ')) | |
| compositor->caret_pos--; | |
| } else { | |
| compositor->caret_pos--; | |
| } | |
| } | |
| else { | |
| load_text_node(compositor, 1); | |
| return; | |
| } | |
| break; | |
| case GF_KEY_RIGHT: | |
| if (compositor->caret_pos+1<compositor->sel_buffer_len) { | |
| if (event->key.flags & GF_KEY_MOD_CTRL) { | |
| while ((compositor->caret_pos+1<compositor->sel_buffer_len) | |
| && (compositor->sel_buffer[compositor->caret_pos] != ' ')) | |
| compositor->caret_pos++; | |
| } else { | |
| compositor->caret_pos++; | |
| } | |
| } | |
| else { | |
| load_text_node(compositor, 2); | |
| return; | |
| } | |
| break; | |
| case GF_KEY_HOME: | |
| compositor->caret_pos = 0; | |
| break; | |
| case GF_KEY_END: | |
| compositor->caret_pos = compositor->sel_buffer_len-1; | |
| break; | |
| case GF_KEY_TAB: | |
| if (!load_text_node(compositor, (event->key.flags & GF_KEY_MOD_SHIFT) ? 1 : 2)) { | |
| is_end = GF_TRUE; | |
| break; | |
| } | |
| return; | |
| case GF_KEY_BACKSPACE: | |
| if (compositor->caret_pos) { | |
| if (compositor->sel_buffer_len) compositor->sel_buffer_len--; | |
| memmove(&compositor->sel_buffer[compositor->caret_pos-1], &compositor->sel_buffer[compositor->caret_pos], sizeof(u16)*(compositor->sel_buffer_len-compositor->caret_pos+1)); | |
| compositor->sel_buffer[compositor->sel_buffer_len]=0; | |
| compositor->caret_pos--; | |
| flush_text_node_edit(compositor, GF_FALSE); | |
| } else { | |
| load_text_node(compositor, 4); | |
| } | |
| return; | |
| case GF_KEY_DEL: | |
| if (compositor->caret_pos+1<compositor->sel_buffer_len) { | |
| if (compositor->sel_buffer_len) compositor->sel_buffer_len--; | |
| memmove(&compositor->sel_buffer[compositor->caret_pos+1], &compositor->sel_buffer[compositor->caret_pos+2], sizeof(u16)*(compositor->sel_buffer_len-compositor->caret_pos-1)); | |
| compositor->sel_buffer[compositor->sel_buffer_len]=0; | |
| flush_text_node_edit(compositor, GF_FALSE); | |
| return; | |
| } | |
| break; | |
| case GF_KEY_ENTER: | |
| if (compositor->focus_text_type==4) { | |
| flush_text_node_edit(compositor, 2); | |
| break; | |
| } | |
| load_text_node(compositor, 3); | |
| return; | |
| default: | |
| return; | |
| } | |
| if (!is_end && compositor->sel_buffer) { | |
| if (compositor->caret_pos==prev_caret) return; | |
| memmove(&compositor->sel_buffer[prev_caret], &compositor->sel_buffer[prev_caret+1], sizeof(u16)*(compositor->sel_buffer_len-prev_caret)); | |
| memmove(&compositor->sel_buffer[compositor->caret_pos+1], &compositor->sel_buffer[compositor->caret_pos], sizeof(u16)*(compositor->sel_buffer_len-compositor->caret_pos)); | |
| compositor->sel_buffer[compositor->caret_pos]=GF_CARET_CHAR; | |
| } | |
| } else { | |
| return; | |
| } | |
| flush_text_node_edit(compositor, is_end); | |
| } | |
| static void toggle_keyboard(GF_Compositor * compositor, Bool do_show) | |
| { | |
| GF_Event evt; | |
| memset(&evt, 0, sizeof(GF_Event)); | |
| evt.type = do_show ? GF_EVENT_TEXT_EDITING_START : GF_EVENT_TEXT_EDITING_END; | |
| if (compositor->video_out) { | |
| GF_Err e = compositor->video_out->ProcessEvent(compositor->video_out, &evt); | |
| if (e == GF_OK) return; | |
| } | |
| gf_sc_user_event(compositor, &evt); | |
| } | |
| static Bool hit_node_editable(GF_Compositor *compositor, Bool check_focus_node) | |
| { | |
| #ifndef GPAC_DISABLE_SVG | |
| SVGAllAttributes atts; | |
| #endif | |
| u32 tag; | |
| GF_Node *text = check_focus_node ? compositor->focus_node : compositor->hit_node; | |
| if (!text) { | |
| toggle_keyboard(compositor, GF_FALSE); | |
| return GF_FALSE; | |
| } | |
| if (compositor->hit_node==compositor->focus_node) return compositor->focus_text_type ? GF_TRUE : GF_FALSE; | |
| tag = gf_node_get_tag(text); | |
| #ifndef GPAC_DISABLE_VRML | |
| switch (tag) { | |
| case TAG_MPEG4_Text: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Text: | |
| #endif | |
| { | |
| M_FontStyle *fs = compositor_get_font_style(((M_Text *)text)->fontStyle); | |
| if (!fs || !fs->style.buffer) return GF_FALSE; | |
| if (strstr(fs->style.buffer, "editable") || strstr(fs->style.buffer, "EDITABLE")) { | |
| compositor->focus_text_type = 3; | |
| } else if (strstr(fs->style.buffer, "simple_edit") || strstr(fs->style.buffer, "SIMPLE_EDIT")) { | |
| compositor->focus_text_type = 4; | |
| } else { | |
| toggle_keyboard(compositor, GF_FALSE); | |
| return GF_FALSE; | |
| } | |
| compositor->focus_node = text; | |
| toggle_keyboard(compositor, compositor->focus_text_type > 2 ? GF_TRUE : GF_FALSE); | |
| return GF_TRUE; | |
| } | |
| default: | |
| break; | |
| } | |
| #endif /*GPAC_DISABLE_VRML*/ | |
| if (tag <= GF_NODE_FIRST_DOM_NODE_TAG) return GF_FALSE; | |
| #ifndef GPAC_DISABLE_SVG | |
| gf_svg_flatten_attributes((SVG_Element *)text, &atts); | |
| if (!atts.editable || !*atts.editable) return GF_FALSE; | |
| switch (tag) { | |
| case TAG_SVG_text: | |
| case TAG_SVG_textArea: | |
| compositor->focus_text_type = 1; | |
| break; | |
| case TAG_SVG_tspan: | |
| compositor->focus_text_type = 2; | |
| break; | |
| default: | |
| return GF_FALSE; | |
| } | |
| if (compositor->focus_node != text) { | |
| GF_DOM_Event evt; | |
| memset(&evt, 0, sizeof(GF_DOM_Event)); | |
| evt.bubbles = 1; | |
| evt.type = GF_EVENT_FOCUSOUT; | |
| gf_dom_event_fire(compositor->focus_node, &evt); | |
| compositor->focus_node = text; | |
| evt.type = GF_EVENT_FOCUSIN; | |
| gf_dom_event_fire(compositor->focus_node, &evt); | |
| compositor->focus_uses_dom_events = GF_TRUE; | |
| } | |
| compositor->hit_node = NULL; | |
| toggle_keyboard(compositor, compositor->focus_text_type > 0 ? GF_TRUE : GF_FALSE); | |
| #endif | |
| return GF_TRUE; | |
| } | |
| #ifndef GPAC_DISABLE_SVG | |
| static GF_Node *get_parent_focus(GF_Node *node, GF_List *hit_use_stack, u32 cur_idx) | |
| { | |
| GF_Node *parent; | |
| GF_FieldInfo info; | |
| if (!node) return NULL; | |
| if (gf_node_get_attribute_by_tag(node, TAG_SVG_ATT_focusable, GF_FALSE, GF_FALSE, &info)==GF_OK) { | |
| if ( *(SVG_Focusable*)info.far_ptr == SVG_FOCUSABLE_TRUE) return node; | |
| } | |
| parent = gf_node_get_parent(node, 0); | |
| if (cur_idx) { | |
| GF_Node *n = (GF_Node*)gf_list_get(hit_use_stack, cur_idx-1); | |
| if (n==node) { | |
| parent = (GF_Node*)gf_list_get(hit_use_stack, cur_idx-2); | |
| if (cur_idx>1) cur_idx-=2; | |
| else cur_idx=0; | |
| } | |
| } | |
| return get_parent_focus(parent, hit_use_stack, cur_idx); | |
| } | |
| #endif | |
| static Bool exec_event_dom(GF_Compositor *compositor, GF_Event *event) | |
| { | |
| #ifndef GPAC_DISABLE_SVG | |
| GF_DOM_Event evt; | |
| u32 cursor_type; | |
| Bool ret = GF_FALSE; | |
| cursor_type = GF_CURSOR_NORMAL; | |
| /*all mouse events*/ | |
| if (event->type <= GF_EVENT_LAST_MOUSE) { | |
| Fixed X = compositor->hit_world_point.x; | |
| Fixed Y = compositor->hit_world_point.y; | |
| /*flip back to origin at top-left*/ | |
| if (compositor->visual->center_coords) { | |
| X += INT2FIX(compositor->visual->width)/2; | |
| Y = INT2FIX(compositor->visual->height)/2 - Y; | |
| } | |
| if (compositor->hit_node) { | |
| GF_Node *focus; | |
| Bool hit_changed = GF_FALSE; | |
| GF_Node *current_use = (GF_Node*)gf_list_last(compositor->hit_use_stack); | |
| memset(&evt, 0, sizeof(GF_DOM_Event)); | |
| evt.clientX = evt.screenX = FIX2INT(X); | |
| evt.clientY = evt.screenY = FIX2INT(Y); | |
| evt.bubbles = 1; | |
| evt.cancelable = 1; | |
| evt.key_flags = compositor->key_states; | |
| /*first check if the hit node is not the grab (previous) node - this may happen regardless of the | |
| mouse actions (animations, ...)*/ | |
| if ((compositor->grab_node != compositor->hit_node) || (compositor->grab_use != current_use) ) { | |
| /*mouse out*/ | |
| if (compositor->grab_node) { | |
| evt.relatedTarget = compositor->hit_node; | |
| evt.type = GF_EVENT_MOUSEOUT; | |
| ret += gf_dom_event_fire_ex(compositor->grab_node, &evt, compositor->prev_hit_use_stack); | |
| /*prepare mouseOver*/ | |
| evt.relatedTarget = compositor->grab_node; | |
| } | |
| compositor->grab_node = compositor->hit_node; | |
| compositor->grab_use = current_use; | |
| /*mouse over*/ | |
| evt.type = GF_EVENT_MOUSEOVER; | |
| ret += gf_dom_event_fire_ex(compositor->grab_node, &evt, compositor->hit_use_stack); | |
| hit_changed = GF_TRUE; | |
| } | |
| switch (event->type) { | |
| case GF_EVENT_MOUSEMOVE: | |
| evt.cancelable = 0; | |
| if (!hit_changed) { | |
| evt.type = GF_EVENT_MOUSEMOVE; | |
| ret += gf_dom_event_fire_ex(compositor->grab_node, &evt, compositor->hit_use_stack); | |
| } | |
| compositor->num_clicks = 0; | |
| break; | |
| case GF_EVENT_MOUSEDOWN: | |
| if ((compositor->grab_x != X) || (compositor->grab_y != Y)) compositor->num_clicks = 0; | |
| evt.type = GF_EVENT_MOUSEDOWN; | |
| compositor->num_clicks ++; | |
| evt.button = event->mouse.button; | |
| evt.detail = compositor->num_clicks; | |
| ret += gf_dom_event_fire_ex(compositor->grab_node, &evt, compositor->hit_use_stack); | |
| compositor->grab_x = X; | |
| compositor->grab_y = Y; | |
| /*change focus*/ | |
| focus = get_parent_focus(compositor->grab_node, compositor->hit_use_stack, gf_list_count(compositor->hit_use_stack)); | |
| if (focus) gf_sc_focus_switch_ring(compositor, GF_FALSE, focus, 1); | |
| else if (compositor->focus_node) gf_sc_focus_switch_ring(compositor, GF_FALSE, NULL, 1); | |
| break; | |
| case GF_EVENT_MOUSEUP: | |
| evt.type = GF_EVENT_MOUSEUP; | |
| evt.button = event->mouse.button; | |
| evt.detail = compositor->num_clicks; | |
| ret += gf_dom_event_fire_ex(compositor->grab_node, &evt, compositor->hit_use_stack); | |
| /* | |
| TODO quick- fix for iPhone as well | |
| TODO clean: figure out whether we use a mouse or a touch device - if touch device, remove this test | |
| */ | |
| #if !defined(_WIN32_WCE) || !defined(GPAC_CONFIG_ANDROID) | |
| if ((compositor->grab_x == X) && (compositor->grab_y == Y)) | |
| #endif | |
| { | |
| evt.type = GF_EVENT_CLICK; | |
| ret += gf_dom_event_fire_ex(compositor->grab_node, &evt, compositor->hit_use_stack); | |
| } | |
| break; | |
| case GF_EVENT_MOUSEWHEEL: | |
| evt.type = GF_EVENT_MOUSEWHEEL; | |
| evt.button = event->mouse.button; | |
| evt.new_scale = event->mouse.wheel_pos; | |
| ret += gf_dom_event_fire_ex(compositor->grab_node, &evt, compositor->hit_use_stack); | |
| break; | |
| } | |
| cursor_type = evt.has_ui_events ? GF_CURSOR_TOUCH : GF_CURSOR_NORMAL; | |
| } else { | |
| /*mouse out*/ | |
| if (compositor->grab_node) { | |
| memset(&evt, 0, sizeof(GF_DOM_Event)); | |
| evt.clientX = evt.screenX = FIX2INT(X); | |
| evt.clientY = evt.screenY = FIX2INT(Y); | |
| evt.bubbles = 1; | |
| evt.cancelable = 1; | |
| evt.key_flags = compositor->key_states; | |
| evt.type = GF_EVENT_MOUSEOUT; | |
| ret += gf_dom_event_fire_ex(compositor->grab_node, &evt, compositor->prev_hit_use_stack); | |
| } | |
| /*reset focus*/ | |
| if (compositor->focus_node && (event->type==GF_EVENT_MOUSEDOWN)) | |
| gf_sc_focus_switch_ring(compositor, GF_FALSE, NULL, 1); | |
| compositor->grab_node = NULL; | |
| compositor->grab_use = NULL; | |
| /*dispatch event to root SVG*/ | |
| memset(&evt, 0, sizeof(GF_DOM_Event)); | |
| evt.clientX = evt.screenX = FIX2INT(X); | |
| evt.clientY = evt.screenY = FIX2INT(Y); | |
| evt.bubbles = 1; | |
| evt.cancelable = 1; | |
| evt.key_flags = compositor->key_states; | |
| evt.type = event->type; | |
| evt.button = event->mouse.button; | |
| evt.new_scale = event->mouse.wheel_pos; | |
| ret += gf_dom_event_fire_ex(gf_sg_get_root_node(compositor->scene), &evt, compositor->hit_use_stack); | |
| } | |
| if (compositor->sensor_type != cursor_type) { | |
| GF_Event c_evt; | |
| c_evt.type = GF_EVENT_SET_CURSOR; | |
| c_evt.cursor.cursor_type = cursor_type; | |
| compositor->video_out->ProcessEvent(compositor->video_out, &c_evt); | |
| compositor->sensor_type = cursor_type; | |
| } | |
| } | |
| else if ((event->type >= GF_EVENT_KEYUP) && (event->type <= GF_EVENT_LONGKEYPRESS)) { | |
| GF_Node *target; | |
| memset(&evt, 0, sizeof(GF_DOM_Event)); | |
| evt.key_flags = event->key.flags; | |
| evt.bubbles = 1; | |
| evt.cancelable = 1; | |
| evt.type = event->type; | |
| evt.detail = event->key.key_code; | |
| evt.key_hw_code = event->key.hw_code; | |
| target = compositor->focus_node; | |
| /*dirty hack to simulate browserback*/ | |
| if (event->key.key_code==GF_KEY_BACKSPACE && (event->key.flags & GF_KEY_MOD_CTRL)) { | |
| event->key.key_code = GF_KEY_BROWSERBACK; | |
| } | |
| if ((event->key.key_code>=GF_KEY_BROWSERBACK) && (event->key.key_code<=GF_KEY_BROWSERSTOP)) { | |
| target = NULL; | |
| } | |
| if (!target) target = gf_sg_get_root_node(compositor->scene); | |
| ret += gf_dom_event_fire(target, &evt); | |
| if (event->type==GF_EVENT_KEYDOWN) { | |
| switch (event->key.key_code) { | |
| case GF_KEY_ENTER: | |
| evt.type = GF_EVENT_ACTIVATE; | |
| evt.detail = 0; | |
| ret += gf_dom_event_fire(target, &evt); | |
| break; | |
| } | |
| } | |
| } else if (event->type == GF_EVENT_TEXTINPUT) { | |
| GF_Node *target; | |
| switch (event->character.unicode_char) { | |
| case '\r': | |
| case '\n': | |
| case '\t': | |
| //case '\b': | |
| break; | |
| default: | |
| memset(&evt, 0, sizeof(GF_DOM_Event)); | |
| evt.key_flags = event->key.flags; | |
| evt.bubbles = 1; | |
| evt.cancelable = 1; | |
| evt.type = event->type; | |
| evt.detail = event->character.unicode_char; | |
| target = compositor->focus_node; | |
| if (!target) target = gf_sg_get_root_node(compositor->scene); | |
| ret += gf_dom_event_fire(target, &evt); | |
| break; | |
| } | |
| } | |
| return ret; | |
| #else | |
| return 0; | |
| #endif | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| Bool gf_sc_exec_event_vrml(GF_Compositor *compositor, GF_Event *ev) | |
| { | |
| u32 res = 0; | |
| GF_SensorHandler *hs; | |
| GF_List *tmp; | |
| u32 i, count, stype; | |
| /*reset previous composite texture*/ | |
| if (compositor->prev_hit_appear != compositor->hit_appear) { | |
| if (compositor->prev_hit_appear) { | |
| //reset previ appear node to avoid potential recursions | |
| GF_Node *hit_appear = compositor->prev_hit_appear; | |
| compositor->prev_hit_appear = NULL; | |
| compositor_compositetexture_handle_event(compositor, hit_appear, ev, GF_TRUE); | |
| //restore if we have a hit | |
| if (compositor->grabbed_sensor) | |
| compositor->prev_hit_appear = hit_appear; | |
| } | |
| } | |
| /*composite texture*/ | |
| if (compositor->hit_appear) { | |
| GF_Node *appear = compositor->hit_appear; | |
| if (compositor_compositetexture_handle_event(compositor, compositor->hit_appear, ev, GF_FALSE)) { | |
| if (compositor->hit_appear) compositor->prev_hit_appear = appear; | |
| compositor->grabbed_sensor = 0; | |
| /*check if we have grabbed sensors*/ | |
| count = gf_list_count(compositor->previous_sensors); | |
| for (i=0; i<count; i++) { | |
| hs = (GF_SensorHandler*)gf_list_get(compositor->previous_sensors, i); | |
| /*if sensor is grabbed, add it to the list of active sensor for next pick*/ | |
| if (hs->grabbed) { | |
| hs->OnUserEvent(hs, GF_FALSE, GF_TRUE, ev, compositor); | |
| gf_list_add(compositor->sensors, hs); | |
| compositor->grabbed_sensor = 1; | |
| } | |
| } | |
| return GF_TRUE; | |
| } | |
| // compositor->prev_hit_appear = compositor->grabbed_sensor ? compositor->hit_appear : NULL; | |
| compositor->prev_hit_appear = compositor->hit_appear; | |
| } | |
| count = gf_list_count(compositor->sensors); | |
| /*if we have a hit node at the compositor level, use "touch" as default cursor - this avoids | |
| resetting the cursor when the picked node is a DOM node in a composite texture*/ | |
| // stype = (compositor->hit_node!=NULL) ? GF_CURSOR_TOUCH : GF_CURSOR_NORMAL; | |
| stype = GF_CURSOR_NORMAL; | |
| for (i=0; i<count; i++) { | |
| GF_Node *keynav; | |
| Bool check_anchor = GF_FALSE; | |
| hs = (GF_SensorHandler*)gf_list_get(compositor->sensors, i); | |
| /*try to remove this sensor from the previous sensor list*/ | |
| gf_list_del_item(compositor->previous_sensors, hs); | |
| if (gf_node_get_id(hs->sensor)) | |
| stype = gf_node_get_tag(hs->sensor); | |
| keynav = gf_scene_get_keynav(gf_node_get_graph(hs->sensor), hs->sensor); | |
| if (keynav) gf_sc_change_key_navigator(compositor, keynav); | |
| /*call the sensor LAST, as this may triger a destroy of the scene the sensor is in | |
| this is only true for anchors, as other other sensors output events are queued as routes until next pass*/ | |
| res += hs->OnUserEvent(hs, GF_TRUE, GF_FALSE, ev, compositor); | |
| if (stype == TAG_MPEG4_Anchor) check_anchor = GF_TRUE; | |
| #ifndef GPAC_DISABLE_X3D | |
| else if (stype == TAG_X3D_Anchor) check_anchor = GF_TRUE; | |
| #endif | |
| if (check_anchor) { | |
| /*subscene with active sensor has been deleted, we cannot continue process the sensors stack*/ | |
| if (count != gf_list_count(compositor->sensors)) | |
| break; | |
| } | |
| } | |
| compositor->grabbed_sensor = 0; | |
| /*check if we have grabbed sensors*/ | |
| count = gf_list_count(compositor->previous_sensors); | |
| for (i=0; i<count; i++) { | |
| hs = (GF_SensorHandler*)gf_list_get(compositor->previous_sensors, i); | |
| res += hs->OnUserEvent(hs, GF_FALSE, GF_FALSE, ev, compositor); | |
| /*if sensor is grabbed, add it to the list of active sensor for next pick*/ | |
| if (hs->grabbed) { | |
| gf_list_add(compositor->sensors, hs); | |
| compositor->grabbed_sensor = 1; | |
| } | |
| stype = gf_node_get_tag(hs->sensor); | |
| } | |
| gf_list_reset(compositor->previous_sensors); | |
| /*switch sensors*/ | |
| tmp = compositor->sensors; | |
| compositor->sensors = compositor->previous_sensors; | |
| compositor->previous_sensors = tmp; | |
| /*and set cursor*/ | |
| if (compositor->sensor_type != GF_CURSOR_COLLIDE) { | |
| switch (stype) { | |
| case TAG_MPEG4_Anchor: | |
| stype = GF_CURSOR_ANCHOR; | |
| break; | |
| case TAG_MPEG4_PlaneSensor2D: | |
| case TAG_MPEG4_PlaneSensor: | |
| stype = GF_CURSOR_PLANE; | |
| break; | |
| case TAG_MPEG4_CylinderSensor: | |
| case TAG_MPEG4_DiscSensor: | |
| case TAG_MPEG4_SphereSensor: | |
| stype = GF_CURSOR_ROTATE; | |
| break; | |
| case TAG_MPEG4_ProximitySensor2D: | |
| case TAG_MPEG4_ProximitySensor: | |
| stype = GF_CURSOR_PROXIMITY; | |
| break; | |
| case TAG_MPEG4_TouchSensor: | |
| stype = GF_CURSOR_TOUCH; | |
| break; | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Anchor: | |
| stype = GF_CURSOR_ANCHOR; | |
| break; | |
| case TAG_X3D_PlaneSensor: | |
| stype = GF_CURSOR_PLANE; | |
| break; | |
| case TAG_X3D_CylinderSensor: | |
| case TAG_X3D_SphereSensor: | |
| stype = GF_CURSOR_ROTATE; | |
| break; | |
| case TAG_X3D_ProximitySensor: | |
| stype = GF_CURSOR_PROXIMITY; | |
| break; | |
| case TAG_X3D_TouchSensor: | |
| stype = GF_CURSOR_TOUCH; | |
| break; | |
| #endif | |
| default: | |
| stype = GF_CURSOR_NORMAL; | |
| break; | |
| } | |
| if ((stype != GF_CURSOR_NORMAL) || (compositor->sensor_type != stype)) { | |
| GF_Event evt; | |
| evt.type = GF_EVENT_SET_CURSOR; | |
| evt.cursor.cursor_type = stype; | |
| compositor->video_out->ProcessEvent(compositor->video_out, &evt); | |
| compositor->sensor_type = stype; | |
| } | |
| } else { | |
| gf_sc_reset_collide_cursor(compositor); | |
| } | |
| if (res) { | |
| GF_SceneGraph *sg; | |
| /*apply event cascade - this is needed for cases where several events are processed between | |
| 2 simulation tick. If we don't flush the routes stack, the result will likely be wrong | |
| */ | |
| gf_sg_activate_routes(compositor->scene); | |
| i = 0; | |
| while ((sg = (GF_SceneGraph*)gf_list_enum(compositor->extra_scenes, &i))) { | |
| gf_sg_activate_routes(sg); | |
| } | |
| return 1; | |
| } | |
| return GF_FALSE; | |
| } | |
| static Bool exec_vrml_key_event(GF_Compositor *compositor, GF_Node *node, GF_Event *ev, Bool is_focus_out) | |
| { | |
| GF_SensorHandler *hdl = NULL; | |
| GF_ChildNodeItem *child; | |
| u32 ret = 0; | |
| if (!node) node = compositor->focus_node; | |
| if (!node) return GF_FALSE; | |
| switch (gf_node_get_tag(node)) { | |
| case TAG_MPEG4_Text: | |
| return GF_FALSE; | |
| case TAG_MPEG4_Layout: | |
| hdl = compositor_mpeg4_layout_get_sensor_handler(node); | |
| break; | |
| case TAG_MPEG4_Anchor: | |
| hdl = compositor_mpeg4_get_sensor_handler(node); | |
| break; | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Text: | |
| return GF_FALSE; | |
| case TAG_X3D_Anchor: | |
| hdl = compositor_mpeg4_get_sensor_handler(node); | |
| break; | |
| #endif | |
| } | |
| child = ((GF_ParentNode*)node)->children; | |
| if (hdl) { | |
| ret += hdl->OnUserEvent(hdl, is_focus_out ? GF_FALSE : GF_TRUE, GF_FALSE, ev, compositor); | |
| } else { | |
| while (child) { | |
| hdl = compositor_mpeg4_get_sensor_handler(child->node); | |
| if (hdl) { | |
| ret += hdl->OnUserEvent(hdl, is_focus_out ? GF_FALSE : GF_TRUE, GF_FALSE, ev, compositor); | |
| } | |
| child = child->next; | |
| } | |
| } | |
| return ret ? GF_TRUE : GF_FALSE; | |
| } | |
| #endif /*GPAC_DISABLE_VRML*/ | |
| Bool visual_execute_event(GF_VisualManager *visual, GF_TraverseState *tr_state, GF_Event *ev, GF_ChildNodeItem *children) | |
| { | |
| Bool ret; | |
| Bool reset_sel = GF_FALSE; | |
| GF_List *temp_stack; | |
| GF_Compositor *compositor = visual->compositor; | |
| tr_state->traversing_mode = TRAVERSE_PICK; | |
| #ifndef GPAC_DISABLE_3D | |
| tr_state->layer3d = NULL; | |
| #endif | |
| /*preprocess text selection and edition*/ | |
| if ((ev->type < GF_EVENT_LAST_MOUSE_COORDS) && (ev->mouse.button==GF_MOUSE_LEFT)) { | |
| if (compositor->text_selection) { | |
| if (ev->type==GF_EVENT_MOUSEUP) { | |
| if (compositor->store_text_state==GF_SC_TSEL_ACTIVE) | |
| compositor->store_text_state = GF_SC_TSEL_FROZEN; | |
| else { | |
| reset_sel = GF_TRUE; | |
| } | |
| } | |
| else if (ev->type==GF_EVENT_MOUSEDOWN) { | |
| reset_sel = GF_TRUE; | |
| } | |
| } else if (compositor->edited_text) { | |
| if (ev->type==GF_EVENT_MOUSEDOWN) | |
| reset_sel = GF_TRUE; | |
| } | |
| if (ev->type==GF_EVENT_MOUSEUP) { | |
| if (hit_node_editable(compositor, GF_FALSE)) { | |
| compositor->text_selection = NULL; | |
| exec_text_input(compositor, NULL); | |
| return GF_TRUE; | |
| } | |
| #if 0 | |
| else if (!compositor->focus_node) { | |
| gf_sc_focus_switch_ring(compositor, 0, gf_sg_get_root_node(compositor->scene), 1); | |
| } | |
| #endif | |
| } | |
| if (reset_sel) { | |
| flush_text_node_edit(compositor, GF_TRUE); | |
| compositor->store_text_state = GF_SC_TSEL_RELEASED; | |
| compositor->text_selection = NULL; | |
| if (compositor->selected_text) gf_free(compositor->selected_text); | |
| compositor->selected_text = NULL; | |
| if (compositor->sel_buffer) gf_free(compositor->sel_buffer); | |
| compositor->sel_buffer = NULL; | |
| compositor->sel_buffer_alloc = 0; | |
| compositor->sel_buffer_len = 0; | |
| gf_sc_next_frame_state(compositor, GF_SC_DRAW_FRAME); | |
| compositor->text_edit_changed = GF_TRUE; | |
| } else if (compositor->store_text_state == GF_SC_TSEL_RELEASED) { | |
| compositor->store_text_state = GF_SC_TSEL_NONE; | |
| } | |
| } | |
| /*pick node*/ | |
| compositor->hit_appear = NULL; | |
| compositor->hit_text = NULL; | |
| temp_stack = compositor->prev_hit_use_stack; | |
| compositor->prev_hit_use_stack = compositor->hit_use_stack; | |
| compositor->hit_use_stack = temp_stack; | |
| tr_state->pick_x = ev->mouse.x; | |
| tr_state->pick_y = ev->mouse.y; | |
| #ifndef GPAC_DISABLE_3D | |
| if (visual->type_3d) | |
| visual_3d_pick_node(visual, tr_state, ev, children); | |
| else | |
| #endif | |
| visual_2d_pick_node(visual, tr_state, ev, children); | |
| gf_list_reset(tr_state->vrml_sensors); | |
| if (exec_text_selection(compositor, ev)) | |
| return GF_TRUE; | |
| if (compositor->hit_use_dom_events) { | |
| ret = exec_event_dom(compositor, ev); | |
| if (ret) return GF_TRUE; | |
| /*no vrml sensors above*/ | |
| if (!gf_list_count(compositor->sensors) && !gf_list_count(compositor->previous_sensors)) | |
| return GF_FALSE; | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| return gf_sc_exec_event_vrml(compositor, ev); | |
| #else | |
| return 0; | |
| #endif | |
| } | |
| u32 gf_sc_svg_focus_navigate(GF_Compositor *compositor, u32 key_code) | |
| { | |
| #ifndef GPAC_DISABLE_SVG | |
| SVGAllAttributes atts; | |
| GF_DOM_Event evt; | |
| u32 ret = 0; | |
| GF_Node *n; | |
| SVG_Focus *focus = NULL; | |
| /*only for dom-based nodes*/ | |
| if (!compositor->focus_node || !compositor->focus_uses_dom_events) return 0; | |
| n=NULL; | |
| gf_svg_flatten_attributes((SVG_Element *)compositor->focus_node, &atts); | |
| switch (key_code) { | |
| case GF_KEY_LEFT: | |
| focus = atts.nav_left; | |
| break; | |
| case GF_KEY_RIGHT: | |
| focus = atts.nav_right; | |
| break; | |
| case GF_KEY_UP: | |
| focus = atts.nav_up; | |
| break; | |
| case GF_KEY_DOWN: | |
| focus = atts.nav_down; | |
| break; | |
| default: | |
| return 0; | |
| } | |
| if (!focus) return 0; | |
| if (focus->type==SVG_FOCUS_SELF) return 0; | |
| if (focus->type==SVG_FOCUS_AUTO) return 0; | |
| if (!focus->target.target) { | |
| if (!focus->target.string) return 0; | |
| focus->target.target = gf_sg_find_node_by_name(compositor->scene, focus->target.string+1); | |
| } | |
| n = (GF_Node*)focus->target.target; | |
| ret = 0; | |
| if (n != compositor->focus_node) { | |
| /*the event is already handled, even though no listeners may be present*/ | |
| ret = 1; | |
| memset(&evt, 0, sizeof(GF_DOM_Event)); | |
| evt.bubbles = 1; | |
| if (compositor->focus_node) { | |
| evt.type = GF_EVENT_FOCUSOUT; | |
| gf_dom_event_fire(compositor->focus_node, &evt); | |
| } | |
| if (n) { | |
| evt.relatedTarget = n; | |
| evt.type = GF_EVENT_FOCUSIN; | |
| gf_dom_event_fire(n, &evt); | |
| } | |
| compositor->focus_node = n; | |
| //invalidate in case we draw focus rect | |
| gf_sc_invalidate(compositor, NULL); | |
| } | |
| return ret; | |
| #else | |
| return 0; | |
| #endif /*GPAC_DISABLE_SVG*/ | |
| } | |
| /*focus management*/ | |
| #ifndef GPAC_DISABLE_SVG | |
| static Bool is_focus_target(GF_Node *elt) | |
| { | |
| u32 i, count; | |
| u32 tag = gf_node_get_tag(elt); | |
| switch (tag) { | |
| case TAG_SVG_a: | |
| return GF_TRUE; | |
| #ifndef GPAC_DISABLE_VRML | |
| case TAG_MPEG4_Anchor: | |
| return GF_TRUE; | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Anchor: | |
| return GF_TRUE; | |
| #endif | |
| #endif | |
| default: | |
| break; | |
| } | |
| if (tag<=GF_NODE_FIRST_DOM_NODE_TAG) return GF_FALSE; | |
| count = gf_dom_listener_count(elt); | |
| for (i=0; i<count; i++) { | |
| GF_FieldInfo info; | |
| GF_Node *l = gf_dom_listener_get(elt, i); | |
| if (gf_node_get_attribute_by_tag(l, TAG_XMLEV_ATT_event, GF_FALSE, GF_FALSE, &info)==GF_OK) { | |
| switch ( ((XMLEV_Event*)info.far_ptr)->type) { | |
| case GF_EVENT_FOCUSIN: | |
| case GF_EVENT_FOCUSOUT: | |
| case GF_EVENT_ACTIVATE: | |
| /*although this is not in the SVGT1.2 spec, we also enable focus switching if key events are listened on the element*/ | |
| case GF_EVENT_KEYDOWN: | |
| case GF_EVENT_KEYUP: | |
| case GF_EVENT_LONGKEYPRESS: | |
| return GF_TRUE; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| return GF_FALSE; | |
| } | |
| #endif /*GPAC_DISABLE_SVG*/ | |
| #define CALL_SET_FOCUS(__child) { \ | |
| gf_list_add(compositor->focus_ancestors, elt); \ | |
| n = set_focus(compositor, __child, current_focus, prev_focus); \ | |
| if (n) { \ | |
| gf_node_set_cyclic_traverse_flag(elt, 0); \ | |
| return n; \ | |
| } \ | |
| gf_list_rem_last(compositor->focus_ancestors); \ | |
| gf_node_set_cyclic_traverse_flag(elt, 0);\ | |
| return NULL; \ | |
| } \ | |
| #ifndef GPAC_DISABLE_SVG | |
| static void rebuild_focus_ancestor(GF_Compositor *compositor, GF_Node *elt) | |
| { | |
| gf_list_reset(compositor->focus_ancestors); | |
| while (elt) { | |
| GF_Node *par = gf_node_get_parent(elt, 0); | |
| if (!par) break; | |
| gf_list_insert(compositor->focus_ancestors, par, 0); | |
| elt = par; | |
| } | |
| } | |
| #endif // GPAC_DISABLE_SVG | |
| static GF_Node *set_focus(GF_Compositor *compositor, GF_Node *elt, Bool current_focus, Bool prev_focus) | |
| { | |
| u32 tag; | |
| GF_ChildNodeItem *child = NULL; | |
| GF_Node *use_node = NULL; | |
| GF_Node *anim_node = NULL; | |
| GF_Node *n; | |
| if (!elt) return NULL; | |
| /*all return in this function shall be preceeded with gf_node_set_cyclic_traverse_flag(elt, 0) | |
| this ensures that we don't go into cyclic references when moving focus, hence stack overflow*/ | |
| if (! gf_node_set_cyclic_traverse_flag(elt, GF_TRUE)) return NULL; | |
| tag = gf_node_get_tag(elt); | |
| if (tag <= GF_NODE_FIRST_DOM_NODE_TAG) { | |
| switch (tag) { | |
| #ifndef GPAC_DISABLE_VRML | |
| case TAG_MPEG4_Transform: | |
| { | |
| M_Transform *tr=(M_Transform *)elt; | |
| if (!tr->scale.x || !tr->scale.y || !tr->scale.z) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| } | |
| goto test_grouping; | |
| } | |
| case TAG_MPEG4_Transform2D: | |
| { | |
| M_Transform2D *tr=(M_Transform2D *)elt; | |
| if (!tr->scale.x || !tr->scale.y) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| } | |
| goto test_grouping; | |
| } | |
| case TAG_MPEG4_Layer3D: | |
| case TAG_MPEG4_Layer2D: | |
| { | |
| M_Layer2D *l=(M_Layer2D *)elt; | |
| if (!l->size.x || !l->size.y) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| } | |
| goto test_grouping; | |
| } | |
| case TAG_MPEG4_Form: | |
| case TAG_MPEG4_TransformMatrix2D: | |
| case TAG_MPEG4_Group: | |
| case TAG_MPEG4_Billboard: | |
| case TAG_MPEG4_Collision: | |
| case TAG_MPEG4_LOD: | |
| case TAG_MPEG4_OrderedGroup: | |
| case TAG_MPEG4_ColorTransform: | |
| case TAG_MPEG4_PathLayout: | |
| case TAG_MPEG4_Anchor: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Group: | |
| case TAG_X3D_Transform: | |
| case TAG_X3D_Billboard: | |
| case TAG_X3D_Collision: | |
| case TAG_X3D_LOD: | |
| case TAG_X3D_Anchor: | |
| #endif | |
| test_grouping: | |
| if (!current_focus) { | |
| /*get the base grouping stack (*/ | |
| BaseGroupingStack *grp = (BaseGroupingStack*)gf_node_get_private(elt); | |
| if (grp && (grp->flags & (GROUP_HAS_SENSORS | GROUP_IS_ANCHOR) )) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return elt; | |
| } | |
| } | |
| break; | |
| case TAG_MPEG4_Switch: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Switch: | |
| #endif | |
| { | |
| s32 i, wc; | |
| #ifndef GPAC_DISABLE_X3D | |
| if (tag==TAG_X3D_Switch) { | |
| child = ((X_Switch*)elt)->children; | |
| wc = ((X_Switch*)elt)->whichChoice; | |
| } else | |
| #endif | |
| { | |
| child = ((M_Switch*)elt)->choice; | |
| wc = ((M_Switch*)elt)->whichChoice; | |
| } | |
| if (wc==-1) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| } | |
| i=0; | |
| while (child) { | |
| if (i==wc) CALL_SET_FOCUS(child->node); | |
| child = child->next; | |
| } | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| } | |
| case TAG_MPEG4_Text: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Text: | |
| #endif | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| if (!current_focus) { | |
| M_FontStyle *fs = compositor_get_font_style(((M_Text *)elt)->fontStyle); | |
| if (!fs || !fs->style.buffer) return NULL; | |
| if (strstr(fs->style.buffer, "editable") || strstr(fs->style.buffer, "EDITABLE")) { | |
| compositor->focus_text_type = 3; | |
| } else if (strstr(fs->style.buffer, "simple_edit") || strstr(fs->style.buffer, "SIMPLE_EDIT")) { | |
| compositor->focus_text_type = 4; | |
| } else { | |
| return NULL; | |
| } | |
| return elt; | |
| } | |
| return NULL; | |
| case TAG_MPEG4_Layout: | |
| { | |
| M_Layout *l=(M_Layout*)elt; | |
| if (!l->size.x || !l->size.y) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| } | |
| if (!current_focus && (compositor_mpeg4_layout_get_sensor_handler(elt)!=NULL)) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return elt; | |
| } | |
| } | |
| break; | |
| case TAG_ProtoNode: | |
| /*hardcoded proto acting as a grouping node*/ | |
| if (gf_node_proto_is_grouping(elt)) { | |
| GF_FieldInfo info; | |
| if (!current_focus) { | |
| /*get the base grouping stack (*/ | |
| BaseGroupingStack *grp = (BaseGroupingStack*)gf_node_get_private(elt); | |
| if (grp && (grp->flags & (GROUP_HAS_SENSORS | GROUP_IS_ANCHOR) )) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return elt; | |
| } | |
| } | |
| if ( (gf_node_get_field_by_name(elt, "children", &info) != GF_OK) || (info.fieldType != GF_SG_VRML_MFNODE)) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| } | |
| child = *(GF_ChildNodeItem **) info.far_ptr; | |
| } else { | |
| CALL_SET_FOCUS(gf_node_get_proto_root(elt)); | |
| } | |
| break; | |
| case TAG_MPEG4_Inline: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Inline: | |
| #endif | |
| CALL_SET_FOCUS(gf_scene_get_subscene_root(elt)); | |
| case TAG_MPEG4_Shape: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Shape: | |
| #endif | |
| gf_list_add(compositor->focus_ancestors, elt); | |
| n = set_focus(compositor, ((M_Shape*)elt)->geometry, current_focus, prev_focus); | |
| if (!n) n = set_focus(compositor, ((M_Shape*)elt)->appearance, current_focus, prev_focus); | |
| if (n) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return n; | |
| } | |
| gf_list_rem_last(compositor->focus_ancestors); | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| case TAG_MPEG4_Appearance: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Appearance: | |
| #endif | |
| CALL_SET_FOCUS(((M_Appearance*)elt)->texture); | |
| case TAG_MPEG4_CompositeTexture2D: | |
| case TAG_MPEG4_CompositeTexture3D: | |
| /*CompositeTextures are not grouping nodes per say*/ | |
| child = ((GF_ParentNode*)elt)->children; | |
| while (child) { | |
| if (compositor_mpeg4_get_sensor_handler(child->node) != NULL) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return elt; | |
| } | |
| child = child->next; | |
| } | |
| break; | |
| #endif /*GPAC_DISABLE_VRML*/ | |
| default: | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| } | |
| if (!child) | |
| child = ((GF_ParentNode*)elt)->children; | |
| } else { | |
| #ifndef GPAC_DISABLE_SVG | |
| SVGAllAttributes atts; | |
| if (tag==TAG_SVG_defs) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| } | |
| gf_svg_flatten_attributes((SVG_Element *)elt, &atts); | |
| if (atts.display && (*atts.display==SVG_DISPLAY_NONE)) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| } | |
| if (!current_focus) { | |
| Bool is_auto = GF_TRUE; | |
| if (atts.focusable) { | |
| if (*atts.focusable==SVG_FOCUSABLE_TRUE) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return elt; | |
| } | |
| if (*atts.focusable==SVG_FOCUSABLE_FALSE) is_auto = GF_FALSE; | |
| } | |
| if (is_auto && is_focus_target(elt)) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return elt; | |
| } | |
| if (atts.editable && *atts.editable) { | |
| switch (tag) { | |
| case TAG_SVG_text: | |
| case TAG_SVG_textArea: | |
| compositor->focus_text_type = 1; | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return elt; | |
| case TAG_SVG_tspan: | |
| compositor->focus_text_type = 2; | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return elt; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| if (prev_focus) { | |
| if (atts.nav_prev) { | |
| switch (atts.nav_prev->type) { | |
| case SVG_FOCUS_SELF: | |
| /*focus locked on element*/ | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return elt; | |
| case SVG_FOCUS_IRI: | |
| if (!atts.nav_prev->target.target) { | |
| if (!atts.nav_prev->target.string) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| } | |
| atts.nav_prev->target.target = gf_sg_find_node_by_name(compositor->scene, atts.nav_prev->target.string+1); | |
| } | |
| if (atts.nav_prev->target.target) { | |
| rebuild_focus_ancestor(compositor, atts.nav_prev->target.target); | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return atts.nav_prev->target.target; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| } else { | |
| /*check next*/ | |
| if (atts.nav_next) { | |
| switch (atts.nav_next->type) { | |
| case SVG_FOCUS_SELF: | |
| /*focus locked on element*/ | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return elt; | |
| case SVG_FOCUS_IRI: | |
| if (!atts.nav_next->target.target) { | |
| if (!atts.nav_next->target.string) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| } | |
| atts.nav_next->target.target = gf_sg_find_node_by_name(compositor->scene, atts.nav_next->target.string+1); | |
| } | |
| if (atts.nav_next->target.target) { | |
| rebuild_focus_ancestor(compositor, atts.nav_next->target.target); | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return atts.nav_next->target.target; | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| if (atts.xlink_href) { | |
| switch (tag) { | |
| case TAG_SVG_use: | |
| use_node = compositor_svg_get_xlink_resource_node(elt, atts.xlink_href); | |
| break; | |
| case TAG_SVG_animation: | |
| anim_node = compositor_svg_get_xlink_resource_node(elt, atts.xlink_href); | |
| break; | |
| } | |
| } | |
| #endif /*GPAC_DISABLE_SVG*/ | |
| child = ((GF_ParentNode *)elt)->children; | |
| } | |
| if (prev_focus) { | |
| u32 count, i; | |
| /*check all children except if current focus*/ | |
| if (current_focus) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| } | |
| gf_list_add(compositor->focus_ancestors, elt); | |
| count = gf_node_list_get_count(child); | |
| for (i=count; i>0; i--) { | |
| /*get in the subtree*/ | |
| n = gf_node_list_get_child( child, i-1); | |
| n = set_focus(compositor, n, GF_FALSE, GF_TRUE); | |
| if (n) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return n; | |
| } | |
| } | |
| } else { | |
| /*check all children */ | |
| gf_list_add(compositor->focus_ancestors, elt); | |
| while (child) { | |
| /*get in the subtree*/ | |
| n = set_focus(compositor, child->node, GF_FALSE, GF_FALSE); | |
| if (n) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return n; | |
| } | |
| child = child->next; | |
| } | |
| } | |
| if (use_node) { | |
| gf_list_add(compositor->focus_use_stack, use_node); | |
| gf_list_add(compositor->focus_use_stack, elt); | |
| n = set_focus(compositor, use_node, GF_FALSE, GF_FALSE); | |
| if (n) { | |
| compositor->focus_used = elt; | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return n; | |
| } | |
| gf_list_rem_last(compositor->focus_use_stack); | |
| gf_list_rem_last(compositor->focus_use_stack); | |
| } else if (anim_node) { | |
| n = set_focus(compositor, anim_node, GF_FALSE, GF_FALSE); | |
| if (n) { | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return n; | |
| } | |
| } | |
| gf_list_rem_last(compositor->focus_ancestors); | |
| gf_node_set_cyclic_traverse_flag(elt, GF_FALSE); | |
| return NULL; | |
| } | |
| static GF_Node *browse_parent_for_focus(GF_Compositor *compositor, GF_Node *elt, Bool prev_focus) | |
| { | |
| u32 tag; | |
| s32 idx = 0; | |
| GF_ChildNodeItem *child; | |
| GF_Node *n; | |
| GF_Node *par; | |
| par = (GF_Node*)gf_list_last(compositor->focus_ancestors); | |
| if (!par) return NULL; | |
| tag = gf_node_get_tag(par); | |
| if (tag <= GF_NODE_FIRST_DOM_NODE_TAG) { | |
| switch (tag) { | |
| #ifndef GPAC_DISABLE_VRML | |
| case TAG_MPEG4_Group: | |
| case TAG_MPEG4_Transform: | |
| case TAG_MPEG4_Billboard: | |
| case TAG_MPEG4_Collision: | |
| case TAG_MPEG4_LOD: | |
| case TAG_MPEG4_OrderedGroup: | |
| case TAG_MPEG4_Transform2D: | |
| case TAG_MPEG4_TransformMatrix2D: | |
| case TAG_MPEG4_ColorTransform: | |
| case TAG_MPEG4_Layer3D: | |
| case TAG_MPEG4_Layer2D: | |
| case TAG_MPEG4_Layout: | |
| case TAG_MPEG4_PathLayout: | |
| case TAG_MPEG4_Form: | |
| case TAG_MPEG4_Anchor: | |
| #ifndef GPAC_DISABLE_X3D | |
| case TAG_X3D_Anchor: | |
| case TAG_X3D_Group: | |
| case TAG_X3D_Transform: | |
| case TAG_X3D_Billboard: | |
| case TAG_X3D_Collision: | |
| case TAG_X3D_LOD: | |
| #endif | |
| case TAG_MPEG4_CompositeTexture2D: | |
| case TAG_MPEG4_CompositeTexture3D: | |
| child = ((GF_ParentNode*)par)->children; | |
| break; | |
| case TAG_ProtoNode: | |
| /*hardcoded proto acting as a grouping node*/ | |
| if (gf_node_proto_is_grouping(par)) { | |
| GF_FieldInfo info; | |
| if ((gf_node_get_field_by_name(par, "children", &info) == GF_OK) | |
| && (info.fieldType == GF_SG_VRML_MFNODE) | |
| ) { | |
| child = *(GF_ChildNodeItem **) info.far_ptr; | |
| break; | |
| } | |
| } | |
| /*fall through*/ | |
| #endif /*GPAC_DISABLE_VRML*/ | |
| /*for all other node, locate parent*/ | |
| default: | |
| gf_list_rem_last(compositor->focus_ancestors); | |
| return browse_parent_for_focus(compositor, par, prev_focus); | |
| } | |
| } else { | |
| child = ((GF_ParentNode *)par)->children; | |
| } | |
| /*locate element*/ | |
| idx = gf_node_list_find_child(child, elt); | |
| /*use or animation*/ | |
| if (idx<0) { | |
| /*up one level*/ | |
| gf_list_rem_last(compositor->focus_ancestors); | |
| #ifndef GPAC_DISABLE_SVG | |
| if (tag==TAG_SVG_use) { | |
| gf_list_rem_last(compositor->focus_use_stack); | |
| gf_list_rem_last(compositor->focus_use_stack); | |
| if (compositor->focus_used == par) compositor->focus_used = NULL; | |
| } | |
| #endif | |
| return browse_parent_for_focus(compositor, (GF_Node*)par, prev_focus); | |
| } | |
| if (prev_focus) { | |
| u32 i; | |
| /*!! this may happen when walking up PROTO nodes !!*/ | |
| for (i=idx; i>0; i--) { | |
| n = gf_node_list_get_child(child, i-1); | |
| /*get in the subtree*/ | |
| n = set_focus(compositor, n, GF_FALSE, GF_TRUE); | |
| if (n) return n; | |
| } | |
| } else { | |
| /*!! this may happen when walking up PROTO nodes !!*/ | |
| while (child) { | |
| if (idx<0) { | |
| /*get in the subtree*/ | |
| n = set_focus(compositor, child->node, GF_FALSE, GF_FALSE); | |
| if (n) return n; | |
| } | |
| idx--; | |
| child = child->next; | |
| } | |
| } | |
| /*up one level*/ | |
| gf_list_rem_last(compositor->focus_ancestors); | |
| return browse_parent_for_focus(compositor, (GF_Node*)par, prev_focus); | |
| } | |
| GF_EXPORT | |
| u32 gf_sc_focus_switch_ring(GF_Compositor *compositor, Bool move_prev, GF_Node *focus, u32 force_focus) | |
| { | |
| Bool current_focus = GF_TRUE; | |
| #ifndef GPAC_DISABLE_SVG | |
| Bool prev_uses_dom_events; | |
| GF_Node *prev_use; | |
| #endif | |
| u32 ret = 0; | |
| GF_List *cloned_use = NULL; | |
| GF_Node *n, *prev; | |
| compositor->focus_text_type = 0; | |
| prev = compositor->focus_node; | |
| #ifndef GPAC_DISABLE_SVG | |
| prev_use = compositor->focus_used; | |
| prev_uses_dom_events = compositor->focus_uses_dom_events; | |
| #endif | |
| compositor->focus_uses_dom_events = GF_FALSE; | |
| if (!compositor->focus_node) { | |
| compositor->focus_node = (force_focus==2) ? focus : gf_sg_get_root_node(compositor->scene); | |
| gf_list_reset(compositor->focus_ancestors); | |
| if (!compositor->focus_node) return 0; | |
| current_focus = GF_FALSE; | |
| } | |
| if (compositor->focus_used) { | |
| u32 i, count; | |
| cloned_use = gf_list_new(); | |
| count = gf_list_count(compositor->focus_use_stack); | |
| for (i=0; i<count; i++) { | |
| gf_list_add(cloned_use, gf_list_get(compositor->focus_use_stack, i)); | |
| } | |
| } | |
| /*get focus in current doc order*/ | |
| if (force_focus) { | |
| gf_list_reset(compositor->focus_ancestors); | |
| n = focus; | |
| if (force_focus==2) { | |
| current_focus = GF_FALSE; | |
| n = set_focus(compositor, focus, current_focus, move_prev); | |
| if (!n) n = browse_parent_for_focus(compositor, focus, move_prev); | |
| } | |
| } else { | |
| n = set_focus(compositor, compositor->focus_node, current_focus, move_prev); | |
| if (!n) n = browse_parent_for_focus(compositor, compositor->focus_node, move_prev); | |
| if (!n) { | |
| if (!prev) n = gf_sg_get_root_node(compositor->scene); | |
| gf_list_reset(compositor->focus_ancestors); | |
| } | |
| } | |
| if (n && gf_node_get_tag(n)>=GF_NODE_FIRST_DOM_NODE_TAG) { | |
| compositor->focus_uses_dom_events = GF_TRUE; | |
| } | |
| compositor->focus_node = n; | |
| ret = 0; | |
| #ifndef GPAC_DISABLE_SVG | |
| if ((prev != compositor->focus_node) || (prev_use != compositor->focus_used)) { | |
| GF_DOM_Event evt; | |
| GF_Event ev; | |
| memset(&evt, 0, sizeof(GF_DOM_Event)); | |
| memset(&ev, 0, sizeof(GF_Event)); | |
| ev.type = GF_EVENT_KEYDOWN; | |
| ev.key.key_code = move_prev ? GF_KEY_LEFT : GF_KEY_RIGHT; | |
| if (prev) { | |
| if (prev_uses_dom_events) { | |
| evt.bubbles = 1; | |
| evt.type = GF_EVENT_FOCUSOUT; | |
| gf_dom_event_fire_ex(prev, &evt, cloned_use); | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| else { | |
| exec_vrml_key_event(compositor, prev, &ev, GF_TRUE); | |
| } | |
| #endif | |
| } | |
| if (compositor->focus_node) { | |
| /*the event is already handled, even though no listeners may be present*/ | |
| ret = 1; | |
| if (compositor->focus_uses_dom_events) { | |
| evt.bubbles = 1; | |
| evt.type = GF_EVENT_FOCUSIN; | |
| gf_dom_event_fire_ex(compositor->focus_node, &evt, compositor->focus_use_stack); | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| else { | |
| exec_vrml_key_event(compositor, NULL, &ev, GF_FALSE); | |
| } | |
| #endif | |
| } | |
| /*because of offscreen caches and composite texture, we must invalidate subtrees of previous and new focus to force a redraw*/ | |
| if (prev) gf_node_dirty_set(prev, GF_SG_NODE_DIRTY, GF_TRUE); | |
| if (compositor->focus_node) gf_node_dirty_set(compositor->focus_node, GF_SG_NODE_DIRTY, GF_TRUE); | |
| /*invalidate in case we draw focus rect*/ | |
| gf_sc_invalidate(compositor, NULL); | |
| } | |
| #endif /*GPAC_DISABLE_SVG*/ | |
| if (cloned_use) gf_list_del(cloned_use); | |
| if (hit_node_editable(compositor, GF_TRUE)) { | |
| compositor->text_selection = NULL; | |
| exec_text_input(compositor, NULL); | |
| /*invalidate parent graphs*/ | |
| gf_node_dirty_set(compositor->focus_node, GF_SG_NODE_DIRTY, GF_TRUE); | |
| } | |
| return ret; | |
| } | |
| Bool gf_sc_execute_event(GF_Compositor *compositor, GF_TraverseState *tr_state, GF_Event *ev, GF_ChildNodeItem *children) | |
| { | |
| /*filter mouse events and other events (key...)*/ | |
| if (ev->type > GF_EVENT_MOUSEWHEEL) { | |
| Bool ret = GF_FALSE; | |
| /*send text edit*/ | |
| if (compositor->edited_text) { | |
| exec_text_input(compositor, ev); | |
| if (compositor->edited_text) return GF_TRUE; | |
| /*if text is no longer edited, this is focus change so process as usual*/ | |
| } | |
| /*FIXME - this is not working for mixed docs*/ | |
| if (compositor->focus_uses_dom_events) | |
| ret = exec_event_dom(compositor, ev); | |
| #ifndef GPAC_DISABLE_VRML | |
| else | |
| ret = exec_vrml_key_event(compositor, compositor->focus_node, ev, GF_FALSE); | |
| #endif | |
| if (ev->type==GF_EVENT_KEYDOWN) { | |
| switch (ev->key.key_code) { | |
| case GF_KEY_ENTER: | |
| if ((0) && compositor->focus_text_type) { | |
| exec_text_input(compositor, NULL); | |
| ret = GF_TRUE; | |
| #ifndef GPAC_DISABLE_VRML | |
| } else if (compositor->keynav_node && ((M_KeyNavigator*)compositor->keynav_node)->select) { | |
| gf_sc_change_key_navigator(compositor, ((M_KeyNavigator*)compositor->keynav_node)->select); | |
| ret = GF_TRUE; | |
| #endif | |
| } | |
| break; | |
| case GF_KEY_TAB: | |
| ret += gf_sc_focus_switch_ring(compositor, (ev->key.flags & GF_KEY_MOD_SHIFT) ? 1 : 0, NULL, 0); | |
| break; | |
| case GF_KEY_UP: | |
| case GF_KEY_DOWN: | |
| case GF_KEY_LEFT: | |
| case GF_KEY_RIGHT: | |
| if (compositor->focus_uses_dom_events) { | |
| ret += gf_sc_svg_focus_navigate(compositor, ev->key.key_code); | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| else if (compositor->keynav_node) { | |
| GF_Node *next_nav = NULL; | |
| switch (ev->key.key_code) { | |
| case GF_KEY_UP: | |
| next_nav = ((M_KeyNavigator*)compositor->keynav_node)->up; | |
| break; | |
| case GF_KEY_DOWN: | |
| next_nav = ((M_KeyNavigator*)compositor->keynav_node)->down; | |
| break; | |
| case GF_KEY_LEFT: | |
| next_nav = ((M_KeyNavigator*)compositor->keynav_node)->left; | |
| break; | |
| case GF_KEY_RIGHT: | |
| next_nav = ((M_KeyNavigator*)compositor->keynav_node)->right; | |
| break; | |
| } | |
| if (next_nav) { | |
| gf_sc_change_key_navigator(compositor, next_nav); | |
| ret = GF_TRUE; | |
| } | |
| } | |
| #endif | |
| break; | |
| } | |
| } | |
| return ret; | |
| } | |
| /*pick even, call visual handler*/ | |
| return visual_execute_event(compositor->visual, tr_state, ev, children); | |
| } | |
| static Bool forward_event(GF_Compositor *compositor, GF_Event *ev, Bool consumed) | |
| { | |
| Bool ret = gf_filter_forward_gf_event(compositor->filter, ev, consumed, GF_FALSE); | |
| if (consumed) return GF_FALSE; | |
| if ((ev->type==GF_EVENT_MOUSEUP) && (ev->mouse.button==GF_MOUSE_LEFT)) { | |
| u32 now; | |
| GF_Event event; | |
| /*emulate doubleclick unless in step mode*/ | |
| now = gf_sys_clock(); | |
| if (!compositor->step_mode && (now - compositor->last_click_time < DOUBLECLICK_TIME_MS)) { | |
| event.type = GF_EVENT_DBLCLICK; | |
| event.mouse.key_states = compositor->key_states; | |
| event.mouse.x = ev->mouse.x; | |
| event.mouse.y = ev->mouse.y; | |
| ret += gf_sc_send_event(compositor, &event); | |
| } | |
| compositor->last_click_time = now; | |
| } | |
| return ret ? GF_TRUE : GF_FALSE; | |
| } | |
| Bool gf_sc_exec_event(GF_Compositor *compositor, GF_Event *evt) | |
| { | |
| s32 x=0, y=0; | |
| Bool switch_coords = GF_FALSE; | |
| Bool ret = GF_FALSE; | |
| if (evt->type <= GF_EVENT_LAST_MOUSE) { | |
| if (compositor->sgaze) { | |
| if ((compositor->gaze_x != evt->mouse.x) || (compositor->gaze_y != evt->mouse.y)) { | |
| compositor->gaze_changed = GF_TRUE; | |
| compositor->gaze_x = evt->mouse.x; | |
| compositor->gaze_y = evt->mouse.y; | |
| } | |
| } | |
| #ifndef GPAC_DISABLE_3D | |
| else if ((compositor->visual->autostereo_type==GF_3D_STEREO_SIDE) || (compositor->visual->autostereo_type==GF_3D_STEREO_HEADSET)) { | |
| if (evt->mouse.x > compositor->visual->camera.proj_vp.width) | |
| evt->mouse.x -= FIX2INT(compositor->visual->camera.proj_vp.width); | |
| evt->mouse.x *= 2; | |
| } | |
| else if (compositor->visual->autostereo_type==GF_3D_STEREO_TOP) { | |
| if (evt->mouse.y > compositor->visual->camera.proj_vp.height) | |
| evt->mouse.y -= FIX2INT(compositor->visual->camera.proj_vp.height); | |
| evt->mouse.y *= 2; | |
| } | |
| #endif | |
| if (compositor->visual->center_coords) { | |
| x = evt->mouse.x; | |
| y = evt->mouse.y; | |
| evt->mouse.x = evt->mouse.x - compositor->display_width/2; | |
| evt->mouse.y = compositor->display_height/2 - evt->mouse.y; | |
| switch_coords = GF_TRUE; | |
| } | |
| } | |
| /*process regular events except if navigation is grabbed*/ | |
| if ( (compositor->navigation_state<2) && (compositor->interaction_level & GF_INTERACT_NORMAL)) { | |
| Bool res; | |
| res = gf_sc_execute_event(compositor, compositor->traverse_state, evt, NULL); | |
| if (res) { | |
| compositor->navigation_state = 0; | |
| ret = GF_TRUE; | |
| } | |
| } | |
| if (switch_coords) { | |
| evt->mouse.x = x; | |
| evt->mouse.y = y; | |
| } | |
| if (!ret) { | |
| #ifndef GPAC_DISABLE_3D | |
| /*remember active layer on mouse click - may be NULL*/ | |
| if ((evt->type==GF_EVENT_MOUSEDOWN) && (evt->mouse.button==GF_MOUSE_LEFT)) | |
| compositor->active_layer = compositor->traverse_state->layer3d; | |
| #endif | |
| ret = forward_event(compositor, evt, ret); | |
| } | |
| //if event is consumed before forwarding don't apply navigation | |
| else { | |
| forward_event(compositor, evt, ret); | |
| } | |
| if (!ret) { | |
| /*process navigation events*/ | |
| if (compositor->interaction_level & GF_INTERACT_NAVIGATION) | |
| ret = compositor_handle_navigation(compositor, evt); | |
| } | |
| return ret; | |
| } | |
| void gf_sc_change_key_navigator(GF_Compositor *sr, GF_Node *n) | |
| { | |
| #ifndef GPAC_DISABLE_VRML | |
| GF_Node *par; | |
| M_KeyNavigator *kn; | |
| gf_list_reset(sr->focus_ancestors); | |
| if (sr->keynav_node) { | |
| kn = (M_KeyNavigator*)sr->keynav_node; | |
| kn->focusSet = 0; | |
| gf_node_event_out(sr->keynav_node, 9/*"focusSet"*/); | |
| } | |
| sr->keynav_node = n; | |
| kn = (M_KeyNavigator*)n; | |
| if (n) { | |
| kn->focusSet = 1; | |
| gf_node_event_out(sr->keynav_node, 9/*"focusSet"*/); | |
| } | |
| par = n ? kn->sensor : NULL; | |
| if (par) par = gf_node_get_parent(par, 0); | |
| gf_sc_focus_switch_ring(sr, GF_FALSE, par, 1); | |
| #endif | |
| } | |
| void gf_sc_key_navigator_del(GF_Compositor *sr, GF_Node *n) | |
| { | |
| if (sr->keynav_node==n) { | |
| sr->keynav_node = NULL; | |
| } | |
| } | |
| #endif //GPAC_DISABLE_COMPOSITOR |