Tizen Native API
5.5
|
WebKit-EFL is independent of any particular toolkit, such as Elementary, so using it on applications requires that the programmer writes a lot of boiler plate code to manage to manage the web object.
For a full featured browser this may make sense, as the programmer will want to have full control of every aspect of the web object, since it's the main component of the application. But other programs with simpler requirements, having to write so much code is undesired.
This is where elm_web comes in. Its purpose is to provide a simple way for developers to embed a simple web object in their programs, simplifying the common use cases.
This is not to say that a browser can't be made out of it, as this example shows.
We'll be making a simple browser, consisting of one window with an URL bar, a toolbar to be used for the tabs and a pager to show one page at a time.
When all tabs are closed, we'll be showing a default view with some custom content, for which we need to get the internal ewk_view
object and use some WebKit functions on it, thus we need to include the necessary headers first.
#include <Elementary.h> #ifdef HAVE_ELEMENTARY_WEB #include <EWebKit.h>
A struct to keep track of the different widgets in use and the currently shown tab. There's also an exiting
flag, used to work around the overly simplistic way in which this example is written, just to avoid some warnings when closing the program.
typedef struct _Tab_Data Tab_Data; typedef struct { Evas_Object *win; Evas_Object *main_box; Evas_Object *naviframe; Evas_Object *url_entry; Evas_Object *default_web; Evas_Object *tabs; Evas_Object *close_tab; Evas_Object *search_box; Evas_Object *search_entry; struct { Evas_Object *back; Evas_Object *fwd; Evas_Object *refresh; } nav; Tab_Data *current_tab; Eina_Bool exiting : 1; } App_Data;
Each tab has its own struct too, but there's not much to it.
struct _Tab_Data { Evas_Object *web; App_Data *app; Elm_Object_Item *tab; };
Whenever the currently selected tab changes, we need to update some state on the application. The back and forward buttons need to be disabled accordingly and the URL bar needs to show the right address.
static void nav_button_update(App_Data *ad) { Eina_Bool back, fwd; back = !elm_web_back_possible_get(ad->current_tab->web); fwd = !elm_web_forward_possible_get(ad->current_tab->web); elm_object_disabled_set(ad->nav.back, back); elm_object_disabled_set(ad->nav.fwd, fwd); } static void tab_current_set(Tab_Data *td) { const char *url; if (td == td->app->current_tab) return; td->app->current_tab = td; url = elm_web_url_get(td->web); elm_object_text_set(td->app->url_entry, url); nav_button_update(td->app); elm_entry_icon_visible_set(td->app->url_entry, EINA_TRUE); elm_naviframe_item_simple_promote(td->app->naviframe, td->web); }
Other updates happen based on events from the web object, like title change to update the name shown in the tab, and URL change which will update the URL bar if the event came from the currently selected tab.
static void _title_changed_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info) { Tab_Data *td = data; const char *title = event_info; char buf[20] = ""; if (title) strncpy(buf, title, sizeof(buf) - 1); elm_object_item_text_set(td->tab, buf); } static void _url_changed_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info) { Tab_Data *td = data; const char *url = event_info; if (td != td->app->current_tab) return; nav_button_update(td->app); elm_object_text_set(td->app->url_entry, url); }
Adding a new tab is just a matter of creating a new web widget, its data and pushing it into the pager. A lot of the things that we should handle here, such as how to react to popups and JavaScript dialogs, are done already in the elm_web
widget, so we can rely on their default implementations. For the JavaScript dialogs we are going to avoid having them open in a new window by setting the Inwin
mode.
There is no default implementation, however, for the requests to create a new window, so we have to handle them by setting a callback function that will ultimately call this very same function to add a new tab.
Tab_Data * tab_add(App_Data *ad) { Tab_Data *td; td = calloc(1, sizeof(Tab_Data)); if (!td) return NULL; td->web = elm_web_add(ad->win); elm_web_window_create_hook_set(td->web, _web_create_window_cb, ad); elm_web_inwin_mode_set(td->web, EINA_TRUE); evas_object_size_hint_weight_set(td->web, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(td->web, EVAS_HINT_FILL, EVAS_HINT_FILL); elm_naviframe_item_simple_push(ad->naviframe, td->web); td->app = ad; td->tab = elm_toolbar_item_append(td->app->tabs, NULL, "New tab", _tab_clicked_cb, td); elm_object_item_del_cb_set(td->tab, _tb_item_del_cb); evas_object_data_set(td->web, "tab_data", td); evas_object_smart_callback_add(td->web, "title,changed", _title_changed_cb, td); evas_object_smart_callback_add(td->web, "url,changed", _url_changed_cb, td); evas_object_event_callback_add(td->web, EVAS_CALLBACK_FREE, _web_free_cb, td); elm_toolbar_item_selected_set(td->tab, EINA_TRUE); return td; }
Entering an address in the URL bar will check if a tab exists, and if not, create one and set the URL for it. The address needs to conform to the URI format, so we check that it does and add the protocol if it's missing.
static char * url_sanitize(const char *url) { char *fixed_url; char *schema; char *tmp; if (!url || !*url) return NULL; tmp = strstr(url, "://"); if (!tmp || (tmp == url) || (tmp > (url + 15))) { char *new_url = NULL; if (ecore_file_exists(url)) { schema = "file"; new_url = ecore_file_realpath(url); } else schema = "http"; if (asprintf(&fixed_url, "%s://%s", schema, new_url ? new_url : url) > 0) { free(new_url); return fixed_url; } free(new_url); } else return strdup(url); return NULL; } static void tab_url_set(Tab_Data *td, const char *url) { char *sane_url = url_sanitize(url); elm_web_url_set(td->web, sane_url); free(sane_url); } static void _url_entry_activated_cb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) { App_Data *ad = data; Tab_Data *td; const char *url = eina_stringshare_ref(elm_object_text_get(obj)); if (!ad->current_tab) td = tab_add(ad); else td = ad->current_tab; tab_url_set(td, url); eina_stringshare_del(url); }
The navigation buttons are simple enough. As for the refresh, it normally reloads the page using anything that may exist in the caches if applicable, but we can press it while holding the Shift
key to avoid the cache.
static void _nav_back_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { App_Data *ad = data; elm_web_back(ad->current_tab->web); } static void _nav_refresh_cb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) { App_Data *ad = data; const Evas_Modifier *mods = evas_key_modifier_get(evas_object_evas_get(obj)); if (evas_key_modifier_is_set(mods, "Shift")) elm_web_reload_full(ad->current_tab->web); else elm_web_reload(ad->current_tab->web); } static void _nav_fwd_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { App_Data *ad = data; elm_web_forward(ad->current_tab->web); }
The callback set for the new window request creates a new tab and returns the web widget associated with it. This is important, this function must return a valid web widget returned by elm_web_add().
static Evas_Object * _web_create_window_cb(void *data, Evas_Object *obj EINA_UNUSED, Eina_Bool js EINA_UNUSED, const Elm_Web_Window_Features *wf EINA_UNUSED) { App_Data *ad = data; Tab_Data *td; td = tab_add(ad); return td->web; }
Pressing Ctrl-F
will bring up the search box. Nothing about the box itself is worth mentioning here, but it works as you would expect from any other browser. While typing on it, it will highlight all occurrences of the searched word. Pressing Enter
will go to the next instance and the two buttons next to the entry will move forward and backwards through the found keywords.
static void _win_free_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { free(data); } static void _search_entry_changed_cb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) { App_Data *ad = data; const char *text; text = elm_object_text_get(obj); elm_web_text_search(ad->current_tab->web, text, EINA_FALSE, EINA_TRUE, EINA_TRUE); elm_web_text_matches_unmark_all(ad->current_tab->web); elm_web_text_matches_mark(ad->current_tab->web, text, EINA_FALSE, EINA_TRUE, 0); } static void _search_entry_activate_cb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) { App_Data *ad = data; const char *text; text = elm_object_text_get(obj); elm_web_text_search(ad->current_tab->web, text, EINA_FALSE, EINA_TRUE, EINA_TRUE); } static void _search_next_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { App_Data *ad = data; const char *text; text = elm_object_text_get(ad->search_entry); elm_web_text_search(ad->current_tab->web, text, EINA_FALSE, EINA_TRUE, EINA_TRUE); } static void _search_prev_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { App_Data *ad = data; const char *text; text = elm_object_text_get(ad->search_entry); elm_web_text_search(ad->current_tab->web, text, EINA_FALSE, EINA_FALSE, EINA_TRUE); } static void _search_close_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { App_Data *ad = data; evas_object_del(ad->search_box); } static void _search_box_del_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) { App_Data *ad = data; ad->search_box = NULL; ad->search_entry = NULL; } static void _win_search_trigger_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info) { Evas_Event_Key_Down *ev = event_info; App_Data *ad = data; Evas_Object *box, *box2, *entry, *btn, *ic; if (strcmp(ev->keyname, "f") || !evas_key_modifier_is_set(ev->modifiers, "Control")) return; if (ad->search_box || !ad->current_tab) return; box = elm_box_add(ad->win); elm_box_horizontal_set(box, EINA_TRUE); evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, 0.0); evas_object_size_hint_align_set(box, EVAS_HINT_FILL, EVAS_HINT_FILL); elm_box_pack_after(ad->main_box, box, ad->url_entry); evas_object_show(box); evas_object_event_callback_add(box, EVAS_CALLBACK_DEL, _search_box_del_cb, ad); entry = elm_entry_add(ad->win); elm_entry_single_line_set(entry, EINA_TRUE); elm_entry_scrollable_set(entry, EINA_TRUE); evas_object_size_hint_weight_set(entry, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(entry, EVAS_HINT_FILL, EVAS_HINT_FILL); elm_box_pack_end(box, entry); evas_object_show(entry); evas_object_smart_callback_add(entry, "changed", _search_entry_changed_cb, ad); evas_object_smart_callback_add(entry, "activated", _search_entry_activate_cb, ad); box2 = elm_box_add(ad->win); elm_box_horizontal_set(box2, EINA_TRUE); elm_object_part_content_set(entry, "end", box2); btn = elm_button_add(ad->win); elm_box_pack_end(box2, btn); evas_object_show(btn); ic = elm_icon_add(ad->win); elm_icon_standard_set(ic, "arrow_up"); elm_object_part_content_set(btn, "icon", ic); evas_object_smart_callback_add(btn, "clicked", _search_prev_cb, ad); btn = elm_button_add(ad->win); elm_box_pack_end(box2, btn); evas_object_show(btn); ic = elm_icon_add(ad->win); elm_icon_standard_set(ic, "arrow_down"); elm_object_part_content_set(btn, "icon", ic); evas_object_smart_callback_add(btn, "clicked", _search_next_cb, ad); btn = elm_button_add(ad->win); elm_box_pack_end(box, btn); evas_object_show(btn); ic = elm_icon_add(ad->win); elm_icon_standard_set(ic, "close"); elm_object_part_content_set(btn, "icon", ic); evas_object_smart_callback_add(btn, "clicked", _search_close_cb, ad); ad->search_box = box; ad->search_entry = entry; elm_object_focus_set(entry, EINA_TRUE); }
Last, create the main window and put all of the things used above in it. It contains a default web widget that will be shown when no tabs exist. This web object is not browsable per se, so history is disabled in it, and we set the same callback to create new windows, on top of setting some custom content of our own on it, with some links that will open new tabs to start browsing quickly.
static void default_content_set(Evas_Object *web) { #ifdef HAVE_ELEMENTARY_WEB Evas_Object *view, *frame; const char contents[] = "" "<html>\n" " <head>\n" " <title>Nothing to see here, move along</title>\n" " </head>\n" " <body>\n" " <a href=\"http://www.enlightenment.org\" target=\"_blank\">E</a>\n" " <br />\n" " <a href=\"http://www.google.com\" target=\"_blank\">Google</a>\n" " <br />\n" " </body>\n" "</html>\n"; view = elm_web_webkit_view_get(web); frame = ewk_view_frame_main_get(view); ewk_frame_contents_set(frame, contents, sizeof(contents) - 1, "text/html", "UTF-8", NULL); #else (void) web; #endif } EAPI_MAIN int elm_main(int argc EINA_UNUSED, char **argv EINA_UNUSED) { Evas_Object *win, *box, *box2, *btn, *ic, *url_bar, *naviframe, *tabs, *web; Evas *e; Evas_Modifier_Mask ctrl_mask; App_Data *ad; if (!elm_need_web()) return -1; ad = calloc(1, sizeof(App_Data)); if (!ad) return -1; elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED); win = elm_win_util_standard_add("example-web", "Web Example"); elm_win_autodel_set(win, EINA_TRUE); e = evas_object_evas_get(win); ctrl_mask = evas_key_modifier_mask_get(e, "Control"); if (!evas_object_key_grab(win, "f", ctrl_mask, 0, EINA_TRUE)) fprintf(stderr, "Could not grab trigger for search dialog\n"); evas_object_smart_callback_add(win, "delete,request", _win_del_request_cb, ad); evas_object_event_callback_add(win, EVAS_CALLBACK_KEY_DOWN, _win_search_trigger_cb, ad); evas_object_event_callback_add(win, EVAS_CALLBACK_FREE, _win_free_cb, ad); box = elm_box_add(win); evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); elm_win_resize_object_add(win, box); evas_object_show(box); url_bar = elm_entry_add(win); elm_entry_single_line_set(url_bar, EINA_TRUE); elm_entry_scrollable_set(url_bar, EINA_TRUE); evas_object_size_hint_weight_set(url_bar, EVAS_HINT_EXPAND, 0.0); evas_object_size_hint_align_set(url_bar, EVAS_HINT_FILL, EVAS_HINT_FILL); elm_box_pack_end(box, url_bar); evas_object_show(url_bar); evas_object_smart_callback_add(url_bar, "activated", _url_entry_activated_cb, ad); box2 = elm_box_add(win); elm_box_horizontal_set(box2, EINA_TRUE); elm_object_part_content_set(url_bar, "icon", box2); elm_entry_icon_visible_set(url_bar, EINA_FALSE); btn = elm_button_add(win); elm_box_pack_end(box2, btn); evas_object_show(btn); ad->nav.back = btn; ic = elm_icon_add(win); elm_icon_standard_set(ic, "arrow_left"); elm_object_part_content_set(btn, "icon", ic); evas_object_smart_callback_add(btn, "clicked", _nav_back_cb, ad); btn = elm_button_add(win); elm_box_pack_end(box2, btn); evas_object_show(btn); ad->nav.refresh = btn; ic = elm_icon_add(win); elm_icon_standard_set(ic, "refresh"); elm_object_part_content_set(btn, "icon", ic); evas_object_smart_callback_add(btn, "clicked", _nav_refresh_cb, ad); btn = elm_button_add(win); elm_box_pack_end(box2, btn); evas_object_show(btn); ad->nav.fwd = btn; ic = elm_icon_add(win); elm_icon_standard_set(ic, "arrow_right"); elm_object_part_content_set(btn, "icon", ic); evas_object_smart_callback_add(btn, "clicked", _nav_fwd_cb, ad); box2 = elm_box_add(win); elm_box_horizontal_set(box2, EINA_TRUE); evas_object_size_hint_weight_set(box2, EVAS_HINT_EXPAND, 0.0); evas_object_size_hint_align_set(box2, EVAS_HINT_FILL, EVAS_HINT_FILL); elm_box_pack_end(box, box2); evas_object_show(box2); btn = elm_button_add(win); elm_box_pack_end(box2, btn); evas_object_show(btn); ic = elm_icon_add(win); elm_icon_standard_set(ic, "file"); elm_object_part_content_set(btn, "icon", ic); evas_object_smart_callback_add(btn, "clicked", _add_tab_cb, ad); tabs = elm_toolbar_add(win); elm_toolbar_align_set(tabs, 0.0); elm_toolbar_select_mode_set(tabs, ELM_OBJECT_SELECT_MODE_ALWAYS); elm_toolbar_homogeneous_set(tabs, EINA_FALSE); elm_toolbar_shrink_mode_set(tabs, ELM_TOOLBAR_SHRINK_MENU); evas_object_size_hint_weight_set(tabs, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(tabs, EVAS_HINT_FILL, EVAS_HINT_FILL); elm_box_pack_end(box2, tabs); evas_object_show(tabs); btn = elm_button_add(win); elm_box_pack_end(box2, btn); evas_object_show(btn); evas_object_smart_callback_add(btn, "clicked", _close_tab_cb, ad); ic = elm_icon_add(win); elm_icon_standard_set(ic, "close"); elm_object_part_content_set(btn, "icon", ic); naviframe = elm_naviframe_add(win); evas_object_size_hint_weight_set(naviframe, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(naviframe, EVAS_HINT_FILL, EVAS_HINT_FILL); elm_box_pack_end(box, naviframe); evas_object_show(naviframe); elm_toolbar_menu_parent_set(tabs, naviframe); web = elm_web_add(win); elm_web_window_create_hook_set(web, _web_create_window_cb, ad); elm_web_history_enabled_set(web, EINA_FALSE); evas_object_size_hint_weight_set(web, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(web, EVAS_HINT_FILL, EVAS_HINT_FILL); elm_naviframe_item_simple_push(naviframe, web); default_content_set(web); ad->win = win; ad->main_box = box; ad->naviframe = naviframe; ad->url_entry = url_bar; ad->default_web = web; ad->tabs = tabs; ad->close_tab = btn; evas_object_resize(win, 480, 640); evas_object_show(win); elm_run(); return 0; } ELM_MAIN()
Some parts of the code were left out, as they are not relevant to the example, but the full listing can be found at web_example_02.c.