--- /dev/null
+/*
+* Copyright 2016 Samsung Electronics Co., Ltd
+*
+* Licensed under the Flora License, Version 1.1 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://floralicense.org/license/
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#ifndef _CLOCK_VIEW_WORLD_MAP_H_
+#define _CLOCK_VIEW_WORLD_MAP_H_
+
+#include <Elementary.h>
+#include <system_settings.h>
+#include <cairo.h>
+#include <vector>
+#include <ctime>
+
+#include "View/View.h"
+
+namespace view {
+
+class WorldClockMap : public ui::IView {
+public:
+
+ Evas_Object *GetEvasObject();
+ WorldClockMap(ui::IView &parent);
+ ~WorldClockMap();
+
+private:
+
+ Evas_Object *map_;
+ Evas_Object *scroller_;
+ Evas_Object *line_scroller_;
+ Evas_Object *shadow_;
+ Evas_Object *line_;
+ Ecore_Timer *timer_;
+
+ cairo_t *line_cairo_;
+ cairo_surface_t *line_surface_;
+
+ cairo_t *shadow_cairo_;
+ cairo_surface_t *shadow_surface_;
+
+ int mapX = -1, mapY = -1, mapW = -1, mapH = -1;
+ int currentYDay = -1;
+
+ static const int LATITUDE_RESOLUTION_ = 181;
+ std::vector<int> day_times_ = std::vector<int>(LATITUDE_RESOLUTION_, 0);
+
+ /**
+ * @brief Returns today's sun declination angle.
+ *
+ * During the Earth movement, sun's position on the horizon
+ * changes from -23.44 to +23.44 degree. This value stores the
+ * angle for current day.
+ *
+ * @param now Current time structure.
+ *
+ * @return Sun declination angle in degrees.
+ *
+ * @note Returned value is double so for example 21o15' is
+ * 21.25 in floating point value.
+ */
+ double SunDeclination(std::tm now);
+
+ /**
+ * @brief Returns angle of the time where currently is day.
+ *
+ * @param latitude The latitude
+ * @param declination The sun declination of current day.
+ *
+ * @return Angle in degrees.
+ *
+ * @see GetSunDeclination()
+ */
+ double TimeAngle(int latitude, double declination);
+
+ /**
+ * @brief Requests for map update.
+ *
+ */
+ void MapUpdate();
+
+ /**
+ * @brief Draws night shadow with day/night line.
+ *
+ * @param now The current time structure
+ * @param declination The declination of the current day
+ *
+ * @return True on success, false on failure.
+ */
+ bool ShadowCreate(std::tm now, double declination);
+
+ /**
+ * @brief Creates night shadow.
+ */
+ void ShadowDraw();
+
+ /**
+ * @brief Creates day/night line.
+ */
+ void LineDraw();
+
+ /**
+ * @brief Creates scroller for night shadow.
+ */
+ void ShadowScrollerCreate();
+
+ /**
+ * @brief Creates scroller for day/night line.
+ */
+ void LineScrollerCreate();
+
+ /**
+ * @brief Creates scroller.
+ *
+ * @param part The parent's swallow part.
+ *
+ * @return The Evas_Object of created scroller.
+ */
+ Evas_Object *ScrollerCreate();
+
+ /**
+ * @brief Changes scroller's visible area position.
+ *
+ * @param minutes The move offset
+ */
+ void ShadowMove(int minutes);
+
+ /**
+ * @brief Task to be done on particular time update request.
+ */
+ static Eina_Bool TimerCb(void *data);
+
+ /**
+ * @brief Callback called on properly shown map image.
+ */
+ static void ScrollerResizeCb(void *data, Evas *e, Evas_Object *obj, void *event_info);
+
+ /**
+ * @brief Callback called on time or timezone change.
+ */
+ static void TimeChangedCb(system_settings_key_e key, void *user_data);
+
+};
+
+}
+
+#endif //_CLOCK_VIEW_WORLD_MAP_H_
--- /dev/null
+/*
+* Copyright 2016 Samsung Electronics Co., Ltd
+*
+* Licensed under the Flora License, Version 1.1 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://floralicense.org/license/
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#include <cmath>
+
+#include "View/WorldClockMap.h"
+#include "Utils/Utils.h"
+#include "Utils/Log.h"
+#include "Utils/Time.h"
+
+namespace view {
+
+using namespace utils;
+
+WorldClockMap::WorldClockMap(ui::IView &parent)
+ : map_(nullptr), shadow_(nullptr), line_(nullptr), timer_(nullptr)
+{
+ map_ = elm_layout_add(parent.GetEvasObject());
+ if (!elm_layout_file_set(map_,
+ Utils::GetAppResourcePath(Utils::APP_DIR_RESOURCE, "edje/WorldClock.edj"),
+ "map"))
+ FAT("elm_layout_file_set failed");
+
+ evas_object_size_hint_align_set(map_, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_size_hint_weight_set(map_, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ evas_object_show(map_);
+
+ ShadowScrollerCreate();
+ /*
+ * Line should not be clipped, moreover it is below the map layer so that is why used
+ * separate scroller.
+ */
+ LineScrollerCreate();
+
+ system_settings_set_changed_cb(SYSTEM_SETTINGS_KEY_TIME_CHANGED, TimeChangedCb, this);
+ system_settings_set_changed_cb(SYSTEM_SETTINGS_KEY_LOCALE_TIMEZONE, TimeChangedCb, this);
+}
+
+WorldClockMap::~WorldClockMap()
+{
+}
+
+void WorldClockMap::ShadowScrollerCreate()
+{
+ scroller_ = ScrollerCreate();
+ elm_object_part_content_set(map_, "scroller", scroller_);
+
+ evas_object_event_callback_add(scroller_, EVAS_CALLBACK_MOVE, ScrollerResizeCb, this);
+}
+
+void WorldClockMap::LineScrollerCreate()
+{
+ line_scroller_ = ScrollerCreate();
+ elm_object_part_content_set(map_, "line_scroller", line_scroller_);
+}
+
+Evas_Object *WorldClockMap::ScrollerCreate()
+{
+ Evas_Object *scroller = elm_scroller_add(map_);
+ evas_object_size_hint_align_set(scroller, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_size_hint_weight_set(scroller, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ elm_scroller_policy_set(scroller, ELM_SCROLLER_POLICY_OFF, ELM_SCROLLER_POLICY_OFF);
+ elm_scroller_movement_block_set(scroller, (Elm_Scroller_Movement_Block)
+ (ELM_SCROLLER_MOVEMENT_BLOCK_HORIZONTAL | ELM_SCROLLER_MOVEMENT_BLOCK_VERTICAL));
+ elm_scroller_loop_set(scroller, EINA_TRUE, EINA_FALSE);
+
+ evas_object_show(scroller);
+
+ Evas_Object *box = elm_box_add(map_);
+ elm_box_horizontal_set(box, EINA_TRUE);
+ elm_box_homogeneous_set(box, EINA_TRUE);
+ evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ evas_object_size_hint_align_set(box, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(box);
+
+ elm_object_content_set(scroller, box);
+
+ return scroller;
+}
+
+void WorldClockMap::ScrollerResizeCb(void *data, Evas *e, Evas_Object *obj, void *event_info)
+{
+ WorldClockMap *view = static_cast<WorldClockMap *>(data);
+ view->MapUpdate();
+}
+
+void WorldClockMap::TimeChangedCb(system_settings_key_e key, void *user_data)
+{
+ if (key != SYSTEM_SETTINGS_KEY_LOCALE_TIMEZONE && key != SYSTEM_SETTINGS_KEY_TIME_CHANGED)
+ return;
+
+ WorldClockMap *view = static_cast<WorldClockMap *>(user_data);
+ view->MapUpdate();
+}
+
+void WorldClockMap::MapUpdate()
+{
+ std::time_t now = time(NULL);
+ std::tm *utcTime = gmtime(&now);
+
+ if (utcTime->tm_yday != currentYDay) {
+ double declination = SunDeclination(*utcTime);
+ if (!ShadowCreate(*utcTime, declination))
+ return;
+
+ currentYDay = utcTime->tm_yday;
+ }
+
+ int timeDiff = (utcTime->tm_hour * 60 + utcTime->tm_min) - 12 * 60;
+ ShadowMove(timeDiff);
+}
+
+void WorldClockMap::ShadowMove(int minutes)
+{
+ int y, w, h;
+ int x = minutes/(24.0 * 60.0) * mapW;
+
+ x = x < 0 ? mapW + x : x; //moving -100px is the same as 720 - 100 = 620 px
+
+ elm_scroller_region_get(scroller_, NULL, &y, &w, &h);
+ elm_scroller_region_show(scroller_, x, y, w, h);
+
+ elm_scroller_region_get(line_scroller_, NULL, &y, &w, &h);
+ elm_scroller_region_show(line_scroller_, x, y, w, h);
+}
+
+Evas_Object *WorldClockMap::GetEvasObject()
+{
+ return map_;
+}
+
+bool WorldClockMap::ShadowCreate(std::tm now, double declination)
+{
+ evas_object_geometry_get(scroller_, &mapX, &mapY, &mapW, &mapH);
+
+ if (mapX < 0 || mapY < 0 || mapW <= 0 || mapH <= 0)
+ return false;
+ ++mapW; //evas_object_geometry_get returns item's width - 1px
+
+ for (int i = 0; i < LATITUDE_RESOLUTION_; ++i) {
+ double time = TimeAngle(90 - i, declination);
+
+ int mapLength = time/360.0 * mapW;
+ day_times_[i] = mapLength;
+ }
+
+ ShadowDraw();
+ LineDraw();
+
+ if (timer_)
+ ecore_timer_del(timer_);
+
+ timer_ = ecore_timer_add(60.0, TimerCb, this);
+ if (!timer_)
+ ERR("ecore_timer_add failed");
+
+ return true;
+}
+
+void WorldClockMap::ShadowDraw()
+{
+ if (shadow_) {
+ evas_object_del(shadow_);
+
+ cairo_destroy(shadow_cairo_);
+ cairo_surface_destroy(shadow_surface_);
+ }
+
+ shadow_ = evas_object_image_filled_add(evas_object_evas_get(scroller_));
+ evas_object_image_alpha_set(shadow_, EINA_TRUE);
+ evas_object_show(shadow_);
+
+ evas_object_geometry_set(shadow_, mapX, mapY, mapW, mapH);
+ evas_object_image_size_set(shadow_, mapW, mapH);
+
+ shadow_surface_ = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, mapW, mapH);
+ shadow_cairo_ = cairo_create(shadow_surface_);
+
+ cairo_set_source_rgba(shadow_cairo_, 1.0, 1.0, 1.0, 1.0);
+ cairo_set_line_width(shadow_cairo_, 1.0);
+
+ for (auto it = day_times_.begin(); it != day_times_.end(); ++it)
+ cairo_line_to(shadow_cairo_, (mapW - *it)/2.0, ((it - day_times_.begin())/180.0 * mapH));
+
+ int idx = LATITUDE_RESOLUTION_ - 1;
+ for (auto it = day_times_.rbegin(); it != day_times_.rend(); ++it, --idx)
+ cairo_line_to(shadow_cairo_, (mapW + *it)/2.0, (idx/180.0 * mapH));
+
+ cairo_close_path(shadow_cairo_);
+ cairo_fill(shadow_cairo_);
+
+ cairo_stroke(shadow_cairo_);
+ cairo_surface_flush(shadow_surface_);
+
+ unsigned char *imageData = cairo_image_surface_get_data(cairo_get_target(shadow_cairo_));
+ evas_object_image_data_set(shadow_, imageData);
+ evas_object_image_data_update_add(shadow_, 0, 0, mapW, mapH);
+
+ evas_object_size_hint_weight_set(shadow_, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ evas_object_size_hint_align_set(shadow_, EVAS_HINT_FILL, EVAS_HINT_FILL);
+
+ Evas_Object *box = elm_object_content_get(scroller_);
+ elm_box_pack_end(box, shadow_);
+}
+
+void WorldClockMap::LineDraw()
+{
+ if (line_) {
+ evas_object_del(line_);
+
+ cairo_destroy(line_cairo_);
+ cairo_surface_destroy(line_surface_);
+ }
+
+ line_ = evas_object_image_filled_add(evas_object_evas_get(line_scroller_));
+ evas_object_image_alpha_set(line_, EINA_TRUE);
+ evas_object_show(line_);
+
+ evas_object_geometry_set(line_, mapX, mapY, mapW, mapH);
+ evas_object_image_size_set(line_, mapW, mapH);
+
+ line_surface_ = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, mapW, mapH);
+ line_cairo_ = cairo_create(line_surface_);
+ cairo_paint_with_alpha(line_cairo_, 0.0);
+
+ cairo_set_source_rgba(line_cairo_, 1.0, 1.0, 1.0, 0.5);
+ cairo_set_line_width(line_cairo_, 3.0);
+
+ for (auto it = day_times_.begin(); it != day_times_.end(); ++it) {
+ if (*it <= 0 || *it >= mapW) {
+ if ((it - day_times_.begin()) > LATITUDE_RESOLUTION_/2.0) {
+ cairo_line_to(line_cairo_, (mapW - *it)/2.0,
+ ((it - day_times_.begin())/180.0 * mapH));
+ break;
+ }
+ cairo_move_to(line_cairo_, (mapW - *it)/2.0,
+ ((it - day_times_.begin())/180.0 * mapH));
+ } else
+ cairo_line_to(line_cairo_, (mapW - *it)/2.0, ((it - day_times_.begin())/180.0 * mapH));
+ }
+
+
+ int idx = LATITUDE_RESOLUTION_ - 1;
+ cairo_move_to(line_cairo_, (mapW + *day_times_.rbegin())/2.0, (idx/180.0 * mapH));
+ for (auto it = day_times_.rbegin(); it != day_times_.rend(); ++it, --idx) {
+ if (*it <= 0 || *it >= mapW) {
+ if (idx < LATITUDE_RESOLUTION_/2.0) {
+ cairo_line_to(line_cairo_, (mapW + *it)/2.0, (idx/180.0 * mapH));
+ break;
+ }
+ cairo_move_to(line_cairo_, (mapW + *it)/2.0, (idx/180.0 * mapH));
+ } else
+ cairo_line_to(line_cairo_, (mapW + *it)/2.0, (idx/180.0 * mapH));
+ }
+
+ cairo_stroke(line_cairo_);
+ cairo_surface_flush(line_surface_);
+
+ unsigned char *imageData = cairo_image_surface_get_data(cairo_get_target(line_cairo_));
+ evas_object_image_data_set(line_, imageData);
+ evas_object_image_data_update_add(line_, 0, 0, mapW, mapH);
+
+ evas_object_size_hint_weight_set(line_, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ evas_object_size_hint_align_set(line_, EVAS_HINT_FILL, EVAS_HINT_FILL);
+
+ Evas_Object *box = elm_object_content_get(line_scroller_);
+ elm_box_pack_end(box, line_);
+}
+
+Eina_Bool WorldClockMap::TimerCb(void *data)
+{
+ WorldClockMap *view = static_cast<WorldClockMap *>(data);
+ view->MapUpdate();
+
+ return ECORE_CALLBACK_RENEW;
+}
+
+/**
+ * M_PI/180.0 is to convert angle from degrees to radians and 180.0/M_PI is in
+ * opposite direction. Tw is the angle from 0 to sunrise or sunset. The sunrise
+ * is 12h - Tw and sunset 12h + Tw. Because returned value is in degrees the
+ * total angle from sunrise to sunset is uqual 2Tw.
+ */
+double WorldClockMap::TimeAngle(int latitude, double declination)
+{
+ double cosTw = std::tan((declination * M_PI/180)) * std::tan(latitude * M_PI/180);
+
+ if (cosTw <= -1.0)
+ return 360.0;
+ else if (cosTw >= 1.0)
+ return 0.0;
+
+ double Tw = std::acos(cosTw) * 180.0/M_PI;
+
+ return 2 * Tw;
+}
+
+double WorldClockMap::SunDeclination(std::tm now)
+{
+ return 23.44 * std::cos((360.0 / 365.0) * (now.tm_yday + 10) * M_PI/180);
+}
+
+}