From f8ca028f5f57db29f619c0fa4d40dc0a044ace89 Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Tue, 2 Oct 2012 15:35:23 -0300 Subject: [PATCH] messages implementation. full messages implementation, squashed in a single commit. --- Makefile.am | 16 +- README | 4 +- configure.ac | 3 +- data/themes/default-hd.edc | 9 + data/themes/default-sd.edc | 9 + data/themes/default.edc | 3 +- data/themes/images/bg_chat_incoming.png | Bin 0 -> 1187 bytes data/themes/images/bg_chat_outgoing.png | Bin 0 -> 1192 bytes data/themes/images/ico_ball.png | Bin 0 -> 1492 bytes data/themes/images/ico_msg_delivered.png | Bin 0 -> 1183 bytes data/themes/includes/compose.edc | 2204 ++++++++++++++++++++++- data/themes/includes/messages-overview-list.edc | 332 ++++ data/themes/includes/messages-overview.edc | 430 +++++ data/themes/includes/messages.edc | 16 + data/themes/includes/scroller.edc | 13 + messages/compose.c | 953 +++++++++- messages/compose.h | 2 + messages/gui.c | 55 +- messages/gui.h | 14 + messages/overview.c | 1162 ++++++++++++ messages/overview.h | 32 + tizen/message_daemon.c | 148 ++ utils/contacts-ofono-efl.h | 9 + utils/contacts-tizen.c | 245 +++ utils/contacts.c | 143 +- utils/util.c | 33 +- utils/util.h | 1 + 27 files changed, 5815 insertions(+), 21 deletions(-) create mode 100644 data/themes/images/bg_chat_incoming.png create mode 100644 data/themes/images/bg_chat_outgoing.png create mode 100644 data/themes/images/ico_ball.png create mode 100644 data/themes/images/ico_msg_delivered.png create mode 100644 data/themes/includes/messages-overview-list.edc create mode 100644 data/themes/includes/messages-overview.edc create mode 100644 messages/overview.c create mode 100644 messages/overview.h create mode 100644 tizen/message_daemon.c diff --git a/Makefile.am b/Makefile.am index 1932d51..7976663 100644 --- a/Makefile.am +++ b/Makefile.am @@ -78,6 +78,8 @@ messages_messages_SOURCES = \ messages/main.c \ messages/rc.c \ messages/rc.h \ + messages/overview.c \ + messages/overview.h \ messages/gui.c \ messages/gui.h \ messages/compose.c \ @@ -92,6 +94,7 @@ tizen_answer_daemon_LDADD = @EFL_LIBS@ @TIZEN_LIBS@ if HAVE_TIZEN bin_PROGRAMS += \ + tizen/message_daemon \ tizen/dialer_open \ tizen/dialer_daemon @@ -101,6 +104,9 @@ tizen_dialer_open_LDADD = @EFL_LIBS@ @TIZEN_LIBS@ tizen_dialer_daemon_SOURCES = tizen/dialer_daemon.c tizen_dialer_daemon_LDADD = @EFL_LIBS@ @TIZEN_LIBS@ +tizen_message_daemon_SOURCES = tizen/message_daemon.c +tizen_message_daemon_LDADD = @EFL_LIBS@ @TIZEN_LIBS@ utils/libofono-efl-utils.la + %.service: %.service.in Makefile sed 's:@bindir[@]:$(bindir):g' $< > $@ @@ -161,14 +167,17 @@ am__v_EDJ_ = $(am__v_EDJ_$(AM_DEFAULT_VERBOSITY)) am__v_EDJ_0 = @echo " EDJ " $@; THEME_IMAGES = \ -data/themes/images/bg_call.jpg \ -data/themes/images/arrow_right.png \ data/themes/images/arrow_left.png \ +data/themes/images/arrow_right.png \ +data/themes/images/bg_call.jpg \ +data/themes/images/bg_chat_incoming.png \ +data/themes/images/bg_chat_outgoing.png \ data/themes/images/bg_keypad.jpg \ data/themes/images/ico_add_call.png \ data/themes/images/ico_add_contact.png \ data/themes/images/ico_arrow_right.png \ data/themes/images/ico_backspace.png \ +data/themes/images/ico_ball.png \ data/themes/images/ico_call.png \ data/themes/images/ico_contacts.png \ data/themes/images/ico_del.png \ @@ -176,6 +185,7 @@ data/themes/images/ico_edit.png \ data/themes/images/ico_history.png \ data/themes/images/ico_keypad.png \ data/themes/images/ico_merge.png \ +data/themes/images/ico_msg_delivered.png \ data/themes/images/ico_multiparty.png \ data/themes/images/ico_multiparty_hangup.png \ data/themes/images/ico_multiparty_private.png \ @@ -206,6 +216,8 @@ data/themes/includes/dialer.edc \ data/themes/includes/history-bg.edc \ data/themes/includes/history.edc \ data/themes/includes/keypad.edc \ +data/themes/includes/messages-overview-list.edc \ +data/themes/includes/messages-overview.edc \ data/themes/includes/messages.edc \ data/themes/includes/multiparty-list.edc \ data/themes/includes/popup.edc \ diff --git a/README b/README index 596f8e3..f9a3279 100644 --- a/README +++ b/README @@ -19,8 +19,8 @@ To help debug, the following environment variables could be set: EINA_LOG_LEVEL=4 toggles debug (level=4) of whole EFL - EINA_LOG_LEVELS=dialer:4 - toggles debug of dialer logging domain. + EINA_LOG_LEVELS=dialer:4,messages:4 + toggles debug of dialer and messages logging domains. EINA_LOG_ABORT=1 make it abort on critical errors. diff --git a/configure.ac b/configure.ac index d452242..d049edb 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ([2.60]) -AC_INIT([ofono-efl], [1]) +AC_INIT([ofono-efl], [2]) AM_INIT_AUTOMAKE([foreign subdir-objects]) AM_CONFIG_HEADER([config.h]) @@ -52,6 +52,7 @@ if test "$want_tizen" != "no"; then utilX aul appsvc + notification ], [have_tizen="yes"], [have_tizen="no"]) diff --git a/data/themes/default-hd.edc b/data/themes/default-hd.edc index 4b22f68..4abad67 100644 --- a/data/themes/default-hd.edc +++ b/data/themes/default-hd.edc @@ -41,4 +41,13 @@ #define CALL_TEXT_OFFSET 32 #define CALL_PHOTO_SIZE 180 +#define MESSAGE_IMG_SENT_SIZE 40 + +#define MESSAGE_IMG_SENT_Y_OFFSET 0 + +#define MESSAGE_SEPARATOR_WIDTH 344 +#define MESSAGE_SEPARATOR_HEIGHT 32 + +#define MULTI_BUTTON_ENTRY_MAX_SIZE 300 + #include "default.edc" diff --git a/data/themes/default-sd.edc b/data/themes/default-sd.edc index bf229b7..89ba0c5 100644 --- a/data/themes/default-sd.edc +++ b/data/themes/default-sd.edc @@ -41,4 +41,13 @@ #define CALL_TEXT_OFFSET 16 #define CALL_PHOTO_SIZE 90 +#define MESSAGE_IMG_SENT_SIZE 16 + +#define MESSAGE_IMG_SENT_Y_OFFSET 0 + +#define MESSAGE_SEPARATOR_WIDTH 172 +#define MESSAGE_SEPARATOR_HEIGHT 16 + +#define MULTI_BUTTON_ENTRY_MAX_SIZE 150 + #include "default.edc" diff --git a/data/themes/default.edc b/data/themes/default.edc index ba2f5c2..ec38bf8 100644 --- a/data/themes/default.edc +++ b/data/themes/default.edc @@ -17,5 +17,6 @@ collections { #include "includes/messages.edc" #include "includes/compose.edc" - +#include "includes/messages-overview.edc" +#include "includes/messages-overview-list.edc" } diff --git a/data/themes/images/bg_chat_incoming.png b/data/themes/images/bg_chat_incoming.png new file mode 100644 index 0000000000000000000000000000000000000000..64c890403408c711dc7e92f3397aedd8f29752ff GIT binary patch literal 1187 zcmaJ>U1-x#6i&Oj&Ve}8`3K@9iYT>7)2*3}t83S^uCmP06<2U-v*d2ew)`}?v)ROd zeV8u_K8S+&MR4kaqCO}nI8hKm(VMwWAFLsSa}MA4ou7MdM%r38 z&#PKqMG(ZiWHX;42v-K%pt1sgJNmMYvkb*Es9oAm^v=Va)Ej<^N={3DR*-zJ~5LEdttH{A%mmo zd_GU*Ybo9Ap_x!9 zWi@MNOn6|lEgCdK`JI-EKtcFFR8?ou7D~ZC_5M@XN)H*3PC-i_G$nj+-K(5b1{X7- zh;%cp>jT9pw)N^rw|aF0#M)V~RuCmca{}w88G^tiH4BNF1e1K2#2S>M$Q;Ag)rX=9 zJ`v*U7$(j%)d$&lED?yaQHJ;XV?{2nOM@!ZP?0PD;l@jHon=rBoSBEFvKz_?Q&+)s z)0|SemZp+=GhDfJEwPeZ8W%%5NBh@N7ejb|9CLPYu`|2+P{aFf;>Gq>O}g+ct4Z?F zwEg_UB(tR~y6FC^mW74KkMDAxsmh&q51+5~Y&rSp-QqXfo3GtD^l+E>ZIj({rR7t_ zzQX9&@v`jAC#R0??Hk>(-#VQsJL0~v;AL#Z+cTbiPxkHo8*}fNj)CfbZtoazuoyZq_rt~O pZrQzY#2Z3 literal 0 HcmV?d00001 diff --git a/data/themes/images/bg_chat_outgoing.png b/data/themes/images/bg_chat_outgoing.png new file mode 100644 index 0000000000000000000000000000000000000000..72506401b364bfbdd266dc6a099dba670e392faa GIT binary patch literal 1192 zcmeAS@N?(olHy`uVBq!ia0y~yV2l8=6*$;{WO!Brh$C6z8c`CQpH@+tIX_n~5u`@1BDVmjn}NZ`zM>#8IXksP zAt^OIGtXA({qFrr3YjUkO5vuy2EGN(sTr9bRYj@6RemAKRoTgwDN6Qs3N{s1Km&49 zOA-|-a&z*EttxDlz~)*3*&tzkB?YjOl5ATgh@&EW0~DO|i&7O#^^A3s4Gawx%*^x* zO-#+q40RNY3=EC*4S>i@*U-eu)Xd7rTmcG{fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8EkR}&8R-I5=oVMzl_XZ^<`pZ$OmImpPAEg{v+u2}(t{7puX=A(aKG z`a!A1`K3k4z=%sz23b{Lk7XLqkJXLnm`nGgoIfBUdv= zLl*-BXP91>{N&Qy)Vvay-V}shC!Bggi6OTDXtPUdQD#|cid#{Bt^(N8R+(7bVrXV! z4s?l|o282hcDF$EreJXkSg(_T0ZzU8K*#8Vq82HtVM4(417gAxE|3FH`l)%q^j-u^ z*j^qpZv%6gl&6bhNX4x;Hx!EwC5W^JDhZUD>Z~X<)oDyL&RM-{4V$O5%P$EZb!Wjd z^~#ru)84tLKI-&eB7MA&)uDRs0j57)N!M+^#%=wwRc?7^f~eRJ{c~HlcfHv7wVhS$ zf!NJ`O20KXFudj6`n~JTahb-wVzC*Gs=NM$)OxyqTYuo)UHOO&zHbAf)s?IDH!yUE zf4qII?c=`pQTLTLT;|_xqp*orNqa-Xi8eUNuc+K z&L^iT=pbClK1UfrKsai0iAqJ=K`Oc5MgRL1l-htDnm{r-UW|C{B$; literal 0 HcmV?d00001 diff --git a/data/themes/images/ico_ball.png b/data/themes/images/ico_ball.png new file mode 100644 index 0000000000000000000000000000000000000000..3ba4d2f3be2106dbfb5544b02759fc31755d7bf4 GIT binary patch literal 1492 zcmeAS@N?(olHy`uVBq!ia0vp^79h;Q1|(OsS<3+_$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%qp275hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|81#=KlDb#X~hD#E>34K5C;EJ)Q4 zN-fSWElLJPT$(b-ssbzLqSVBa{GyQj{2W*)24v)y-fM)1rW~Nw~n!A`AyO_He7`s^-8oC-;x|liwS!z^yTu8oc~HG6 zxZUE6Q?EYIG5VmWMT%;e5HS6KnDB%PFYj^COR%^Uma*0MM8+0q4!5kb2aKe;yH z>h=Q%Q{BDydLMe5dTra*jAaE=3*>v(Ic6OzDE04S*kn-q(o|QYpi}aI=Bg=WzVTJO z=aZc?pKfYeb~SYNH_IJ4BL0bX@1z+uO^VePW}b0pkcs2I=zpwWdCiec=UzDK%sE+o zr*-wq4N;-5<^BX1wFp|6SAIBQW7E6yMd;a83OlsKmwy$Qt5G>?jL#M247z^ zcq}sTsqK&SEL-o?-}i0Ft0#`}1`losZ?l!Sz0|>KK^5aZhP!M&OPR%*esZzcaIl>b zFc9XMq4XfmXo1*`v+>)!OE!zX*qd7`|3{SLNAbHCtZgr!E);$lJ>h%U)%IQNJLPZB zH!n}M`mvN)_6*k|I|FtE47(h%Kjz;zZ{C%)efs#!$2!4>i&gNS0@=NKEH+a9(x1k1gr>-W1EH&n``JFP_C*zWB#-&j&vo zZ_N4hCzG{p8%y`mDi7D+JC<%enQ`*C)6bW;FV1IRX1LeG-#_QW93@aW>*?y}vd$@? F2>?ovGnN1V literal 0 HcmV?d00001 diff --git a/data/themes/images/ico_msg_delivered.png b/data/themes/images/ico_msg_delivered.png new file mode 100644 index 0000000000000000000000000000000000000000..8a8c88f7983f00db5a8f1bb7ba540a4f1f275c94 GIT binary patch literal 1183 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m{l@EB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#2-5f)Yb+0nlcb)S}F?)D*X({9FaFr>!z^yTuu&c~HG6 zxZUD{Q?EYIG5VmWMT%;e5HS6KnDB%Papq}|rxPy#a?%~D%7dP==F^OU7xW6d!L(eR+oFDz1LK&ha3LB$_9S7ZHe;rbdOGX)k1l^{Gs!7HF}1@zS4-@0xbUY>@+Q3e8!M+jS>SldLbQ7| zAA_N`prf#p&ytOd9|Lt}Emv51@|e>8DOVPK;}mnA)EFVyv4275eukvPpvm(!1^zo2 zzYrIQKay0)urt%*TQT!o!ijC)37g+3qmo|U089E=}rO%96(>~3Aa ndn9~8+k1v2yP`9U90m-fbuXV)=6sqDDl$A>{an^LB{Ts5SjC9@ literal 0 HcmV?d00001 diff --git a/data/themes/includes/compose.edc b/data/themes/includes/compose.edc index 004cb5e..1906b9f 100644 --- a/data/themes/includes/compose.edc +++ b/data/themes/includes/compose.edc @@ -6,20 +6,53 @@ group { * * Parts: * + * TEXT: elm.text.name - Name of the contact/number. + * TEXT: elm.text.size - Size of the message (number of characters) + * SWALLOW: elm.swallow.destination - Text Entry - Contacts that the message will be sent + * SWALLOW: elm.swallow.message - Text Entry - Content of the message + * SWALLOW: elm.swallow.genlist - Where the message thread is going to appear + * IMAGE: elm.img.btn.send - A Button to send the message + * * Signals: * Emit (source is "gui"): + * clicked,: key was clicked (press and release in the key) + * clicked,edit - When the edit button is cliked + * clicked,edit,done - When The Button "Done" while editing is clicked + * clicked,send_msg - When elm.img.btn.send is clicked + * * Listens (source is "gui"): + * show,genlist - Show the genlist with the current message thread + * hidden,genlist - Hide the genlist + * toggle,on,edit - User is editing + * toggle,off,edit - User finished the editing + * + * Messages: + * 1 - INT_SET: tuple of message size and maximum size. + * + * Where is the name of the button * */ + images { + image: "bg_keypad.jpg" COMP; + image: "ico_arrow_right.png" COMP; + image: "ico_ball.png" COMP; + } + parts { part { name: "bg"; - type: RECT; + type: IMAGE; mouse_events: 0; description { state: "default" 0.0; - color: 255 0 0 255; /* TODO */ + color: 255 255 255 255; + color_class: "dark"; + image { + normal: "bg_keypad.jpg"; + scale_hint: STATIC; + } + fill.smooth: 1; } } @@ -37,5 +70,2168 @@ group { } } } - } -} + + part { + name: "button.area.clipper"; + type: RECT; + description { + state: "default" 0.0; + rel1.to: "bg.buttons"; + rel2 { + to: "bg.buttons"; + offset: -1 SEPARATOR_HEIGHT; + } + } + } + + part { + name: "button.clipper"; + type: RECT; + clip_to: "button.area.clipper"; + description { + state: "default" 0.0; + rel1.to: "button.area.clipper"; + rel2.to: "button.area.clipper"; + } + description { + state: "alternate" 0.0; + color: 255 255 255 0; + visible: 0; + } + } + + part { + name: "edit-button.clipper"; + type: RECT; + clip_to: "button.area.clipper"; + description { + state: "default" 0.0; + color: 255 255 255 0; + visible: 0; + rel1.to: "button.area.clipper"; + rel2.to: "button.area.clipper"; + } + description { + state: "alternate" 0.0; + color: 255 255 255 255; + visible: 1; + } + } + + part { + name: "edit.clipper"; + type: RECT; + description { + state: "default" 0.0; + rel1.to: "bg.edit"; + rel2 { + to: "bg.edit"; + offset: -1 SEPARATOR_HEIGHT; + } + } + } + + part { + name: "bg.buttons"; + type: RECT; + mouse_events: 0; + clip_to: "button.clipper"; + description { + state: "default" 0.0; + color: 0 0 0 0; + fixed: 1 1; + min: 0 (ACTION_HEIGHT / 2); + rel1 { + to: "notification.bar"; + relative: 0.0 1.0; + } + rel2 { + to_x: "bg.edit"; + to_y: "notification.bar"; + relative: 0.0 1.0; + offset: (-ITEM_PADDING - 1) (ACTION_HEIGHT / 2 - 1); + } + } + description { + state: "compose" 0.0; + inherit: "default" 0.0; + rel2 { + to: "notification.bar"; + relative: 1.0 1.0; + offset: -1 (ACTION_HEIGHT / 2 - 1); + } + } + } + + part { + name: "header.name.bg"; + type: RECT; + mouse_events: 0; + clip_to: "button.clipper"; + description { + state: "default" 0.0; + color: 255 255 255 255; + color_class: "action"; + rel1.to: "bg.buttons"; + rel2.to: "bg.buttons"; + } + } + + part { + name: "elm.text.name"; + type: TEXT; + mouse_events: 0; + clip_to: "button.clipper"; + api: "name" "message destination name"; + description { + state: "default" 0.0; + color: 16 16 16 255; + color_class: "action"; + rel1 { + to: "bg.buttons"; + offset: ITEM_PADDING 0; + } + rel2 { + to: "bg.buttons"; + offset: (-ITEM_PADDING - 1) -1; + } + text { + font: FONT_NORMAL; + size: SIZE_LARGE; + align: 0.5 0.5; + ellipsis: 0.0; + } + } + } + + part { + name: "bg.edit"; + type: RECT; + mouse_events: 0; + clip_to: "edit.clipper"; + description { + state: "default" 0.0; + color: 0 0 0 0; + min: 0 (ACTION_HEIGHT / 2); + rel1 { + to: "notification.bar"; + relative: 1.0 1.0; + offset: (-LIST_ICON_SIZE - ITEM_PADDING - BORDER_PADDING) 0; + } + rel2 { + to: "notification.bar"; + relative: 1.0 1.0; + offset: -1 (ACTION_HEIGHT / 2 - 1); + } + } + } + +#define SEPARATOR(id, clip, rely, offy, relto) \ + part { \ + name: "separator.dark."##id; \ + type: RECT; \ + mouse_events: 0; \ + clip_to: clip; \ + description { \ + state: "default" 0.0; \ + color: 255 255 255 255; \ + color_class: "dark"; \ + rel1 { \ + relative: 0.0 rely; \ + offset: 0 offy; \ + to_y: relto; \ + } \ + rel2 { \ + relative: 1.0 rely; \ + offset: -1 (offy + SEPARATOR_HEIGHT / 2); \ + to_y: relto; \ + } \ + } \ + } \ + part { \ + name: "separator.bg."##id; \ + type: RECT; \ + mouse_events: 0; \ + clip_to: clip; \ + description { \ + state: "default" 0.0; \ + color: 255 255 255 255; \ + color_class: "bg"; \ + rel1 { \ + relative: 0.0 rely; \ + offset: 0 (offy + SEPARATOR_HEIGHT / 2); \ + to_y: relto; \ + } \ + rel2 { \ + relative: 1.0 rely; \ + offset: -1 (offy + SEPARATOR_HEIGHT); \ + to_y: relto; \ + } \ + } \ + } + + SEPARATOR("button", "button.area.clipper", 1.0, 0, "bg.buttons"); + SEPARATOR("edit-button", "button.area.clipper", 1.0, 0, "bg.buttons"); + SEPARATOR("edit", "edit.clipper", 1.0, 0, "bg.edit"); +#undef SEPARATOR + + +#define BUTTON(id, label, ccls, clip, r1, r2) \ + part { \ + name: "button."##id; \ + type: RECT; \ + mouse_events: 1; \ + clip_to: clip; \ + description { \ + state: "default" 0.0; \ + color: 255 255 255 0; \ + color_class: "action"; \ + rel1 { \ + to: "bg.buttons"; \ + relative: r1; \ + } \ + rel2 { \ + to: "bg.buttons"; \ + relative: r2; \ + } \ + } \ + description { \ + state: "pressed" 0.0; \ + inherit: "default" 0.0; \ + color: 255 255 255 255; \ + } \ + } \ + part { \ + name: "label."##id; \ + type: TEXT; \ + mouse_events: 0; \ + clip_to: clip; \ + description { \ + state: "default" 0.0; \ + color: 255 255 255 255; \ + color_class: ccls; \ + rel1.to: "button."##id; \ + rel2 { \ + to: "button."##id; \ + } \ + text { \ + text: label; \ + font: FONT_NORMAL; \ + size: SIZE_MEDIUM; \ + align: 0.5 0.5; \ + } \ + } \ + description { \ + state: "pressed" 0.0; \ + inherit: "default" 0.0; \ + color: 16 16 16 255; \ + } \ + } \ + \ + programs { \ + program { \ + signal: "toggle,on,"##id; \ + source: "gui"; \ + action: STATE_SET "pressed" 0.0; \ + target: "button."##id; \ + target: "label."##id; \ + } \ + program { \ + signal: "toggle,off,"##id; \ + source: "gui"; \ + action: STATE_SET "default" 0.0; \ + target: "button."##id; \ + target: "label."##id; \ + } \ + program { \ + signal: "mouse,clicked,1"; \ + source: "button."##id; \ + action: SIGNAL_EMIT "clicked,"##id "gui"; \ + api: id"_clicked" id" was clicked"; \ + } \ + } + + BUTTON("clear", "Clear", "caution", "edit-button.clipper", 0.0 0.0, 0.5 1.0); + BUTTON("edit,done", "Done", "action", "edit-button.clipper", 0.5 0.0, 1.0 1.0); +#undef BUTTON + + part { + name: "button.edit"; + type: RECT; + mouse_events: 1; + clip_to: "edit.clipper"; + description { + state: "default" 0.0; + color: 255 255 255 0; + color_class: "caution"; + rel1.to: "bg.edit"; + rel2.to: "bg.edit"; + } + description { + state: "pressed" 0.0; + inherit: "default" 0.0; + color: 255 255 255 255; + } + description { + state: "compose" 0.0; + inherit: "default" 0.0; + visible: 0; + } + } + part { + name: "ico.edit"; + type: IMAGE; + mouse_events: 0; + clip_to: "edit.clipper"; + description { + state: "default" 0.0; + min: LIST_ICON_SIZE LIST_ICON_SIZE; + max: LIST_ICON_SIZE LIST_ICON_SIZE; + align: 0.0 0.5; + color: 255 255 255 255; + color_class: "caution"; + rel1 { + to: "button.edit"; + offset: ITEM_PADDING 0; + } + rel2 { + to: "button.edit"; + offset: (-BORDER_PADDING - 1) -1; + } + image { + normal: "ico_del.png"; + scale_hint: STATIC; + } + } + description { + state: "pressed" 0.0; + inherit: "default" 0.0; + color: 16 16 16 255; + } + description { + state: "compose" 0.0; + inherit: "default" 0.0; + visible: 0; + } + } + part { + name: "button.over.edit"; + type: RECT; + mouse_events: 1; + clip_to: "edit.clipper"; + description { + state: "default" 0.0; + color: 0 0 0 0; + visible: 0; + rel1.to: "bg.edit"; + rel2.to: "bg.edit"; + } + description { + state: "pressed" 0.0; + inherit: "default" 0.0; + visible: 1; + } + description { + state: "compose" 0.0; + inherit: "default" 0.0; + visible: 0; + } + } + + part { + name: "ico.ball"; + type: IMAGE; + mouse_events: 0; + description { + state: "default" 0.0; + visible: 0; + color: 255 255 255 255; + color_class: "action"; + rel1.to: "bg.edit"; + rel2.to: "bg.edit"; + min: LIST_ICON_SIZE LIST_ICON_SIZE; + max: LIST_ICON_SIZE LIST_ICON_SIZE; + image { + normal: "ico_ball.png"; + scale_hint: STATIC; + } + } + description { + state: "visible" 0.0; + inherit: "default" 0.0; + visible: 1; + } + } + + part { + name: "elm.text.names_count"; + type: TEXT; + mouse_events: 0; + description { + state: "default" 0.0; + color: 0 0 0 255; + visible: 0; + rel1.to: "ico.ball"; + rel2.to: "ico.ball"; + text { + font: FONT_NORMAL; + size: SIZE_LARGE; + size_range: SIZE_SMALL SIZE_LARGE; + fit: 1 1; + align: 0.5 0.5; + } + } + description { + state: "visible" 0.0; + inherit: "default" 0.0; + visible: 1; + } + } + + part { + name: "elm.swallow.destination"; + type: SWALLOW; + api: "destination" "where to swallow destination field"; + description { + state: "default" 0.0; + visible: 1; + rel1 { + to_y: "separator.bg.button"; + relative: 0.0 1.0; + offset: BORDER_PADDING 0; + } + rel2 { + to: "separator.bg.button"; + relative: 0.0 1.0; + offset: (WIDTH -2 * BORDER_PADDING) ACTION_HEIGHT; + } + } + description { + state: "hidden" 0.0; + inherit: "default" 0.0; + visible: 0; + } + } + + part { + name: "clipper.contacts.visible"; + type: RECT; + mouse_events: 0; + description { + state: "default" 0.0; + color: 255 255 255 255; + visible: 1; + } + description { + state: "alternate" 0.0; + inherit: "default" 0.0; + visible: 0; + color: 255 255 255 0; + } + } + + part { + name: "elm.swallow.genlist"; + type: SWALLOW; + api: "genlist" "where to swallow list of messages"; + clip_to: "clipper.contacts.visible"; + description { + state: "default" 0.0; + visible: 0; + rel1 { + to_y: "separator.bg.button"; + relative: 0.0 1.0; + } + rel2 { + to: "bg.entry.msg"; + relative: 1.0 0.0; + offset: -1 -1; + } + } + description { + state: "visible" 0.0; + inherit: "default" 0.0; + visible: 1; + } + } + + part { + name: "bg.entry.msg"; + type: RECT; + mouse_events: 0; + clip_to: "clipper.contacts.visible"; + description { + state: "default" 0.0; + color: 0 0 0 255; + rel1 { + to: "separator.dark.back"; + relative: 0.0 0.0; + offset: 0 (-ACTION_HEIGHT); + } + rel2 { + to: "separator.dark.back"; + relative: 1.0 0.0; + offset: 0 -1; + } + } + } + + part { + name: "elm.swallow.message"; + type: SWALLOW; + api: "message" "where to swallow the message editor"; + clip_to: "clipper.contacts.visible"; + description { + state: "default" 0.0; + rel1 { + to: "bg.entry.msg"; + relative: 0.0 0.0; + } + rel2 { + to_x: "elm.img.btn.send"; + to_y: "bg.entry.msg"; + relative: 0.0 0.6; + offset: -ITEM_PADDING -1; + } + } + } + + part { + name: "elm.img.btn.send"; + type: IMAGE; + clip_to: "clipper.contacts.visible"; + description { + state: "default" 0.0; + color: 255 255 255 255; + color_class: "action"; + min: LIST_ICON_SIZE LIST_ICON_SIZE; + max: LIST_ICON_SIZE LIST_ICON_SIZE; + align: 1.0 0.5; + fixed: 1 1; + rel1 { + to: "bg.entry.msg"; + relative: 1.0 0.0; + offset: -BORDER_PADDING 0; + } + rel2 { + to: "bg.entry.msg"; + relative: 1.0 1.0; + offset: -BORDER_PADDING -1; + } + image { + normal: "ico_arrow_right.png"; + } + } + } + + part { + name: "elm.text.size"; + type: TEXT; + mouse_events: 0; + api: "size" "message size (in characters)"; + clip_to: "clipper.contacts.visible"; + description { + state: "default" 0.0; + color: 255 255 255 255; + color_class: "light"; + fixed: 1 1; + rel1 { + to: "elm.text.separator_size"; + relative: 0.0 0.0; + offset: (-ITEM_PADDING - 1 - ITEM_PADDING) 0; + } + rel2 { + to: "elm.text.separator_size"; + relative: 0.0 1.0; + offset: 0 -1; + } + text { + text: "999"; + font: FONT_NORMAL; + size: SIZE_SMALL; + min: 1 0; + } + } + } + + part { + name: "elm.text.separator_size"; + type: TEXT; + mouse_events: 0; + clip_to: "clipper.contacts.visible"; + api: "separator" "A separator for size and max size"; + description { + state: "default" 0.0; + color: 255 255 255 255; + color_class: "light"; + fixed: 1 1; + rel1 { + to: "elm.img.btn.send"; + relative: 0.5 1.0; + offset: 0 ITEM_PADDING; + } + rel2 { + to_x: "elm.img.btn.send"; + to_y: "bg.entry.msg"; + relative: 0.5 1.0; + } + text { + text: "/"; + font: FONT_NORMAL; + size: SIZE_SMALL; + min: 1 0; + } + } + } + + part { + name: "elm.text.max_size"; + type: TEXT; + mouse_events: 0; + api: "max size" "max size of the SMS"; + clip_to: "clipper.contacts.visible"; + description { + state: "default" 0.0; + color: 255 255 255 255; + color_class: "light"; + rel1 { + to: "elm.text.separator_size"; + relative: 1.0 0.0; + } + rel2 { + to: "bg.entry.msg"; + relative: 1.0 1.0; + } + text { + text: "160"; + font: FONT_NORMAL; + size: SIZE_SMALL; + align: 0.0 0.5; + } + } + } + +#define SEPARATOR(id, rely, offy, relto) \ + part { \ + name: "separator.dark."##id; \ + type: RECT; \ + mouse_events: 0; \ + clip_to: "clipper.contacts.visible"; \ + description { \ + state: "default" 0.0; \ + color: 255 255 255 255; \ + color_class: "dark"; \ + rel1 { \ + relative: 0.0 rely; \ + offset: 0 offy; \ + to_y: relto; \ + } \ + rel2 { \ + relative: 1.0 rely; \ + offset: -1 (offy + SEPARATOR_HEIGHT / 2); \ + to_y: relto; \ + } \ + } \ + } \ + part { \ + name: "separator.bg."##id; \ + type: RECT; \ + clip_to: "clipper.contacts.visible"; \ + mouse_events: 0; \ + description { \ + state: "default" 0.0; \ + color: 255 255 255 255; \ + color_class: "bg"; \ + rel1 { \ + relative: 0.0 rely; \ + offset: 0 (offy + SEPARATOR_HEIGHT / 2); \ + to_y: relto; \ + } \ + rel2 { \ + relative: 1.0 rely; \ + offset: -1 (offy + SEPARATOR_HEIGHT); \ + to_y: relto; \ + } \ + } \ + } + + SEPARATOR("back", 0.0, -SEPARATOR_HEIGHT, "bg.buttons.back"); +#undef SEPARATOR + + part { + name: "bg.buttons.back"; + type: RECT; + mouse_events: 0; + clip_to: "clipper.contacts.visible"; + description { + state: "default" 0.0; + color: 0 0 0 0; + min: WIDTH ACTION_HEIGHT; + max: 99999 99999; /* keep it tight centered */ + rel1 { + relative: 0.0 1.0; + offset: 0 -ACTION_HEIGHT; + } + } + } +#define BUTTON(id, label, ccls, r1, r2) \ + part { \ + name: "button."##id; \ + type: RECT; \ + clip_to: "clipper.contacts.visible"; \ + mouse_events: 1; \ + description { \ + state: "default" 0.0; \ + color: 255 255 255 0; \ + color_class: "action"; \ + rel1 { \ + to: "bg.buttons.back"; \ + relative: r1; \ + } \ + rel2 { \ + to: "bg.buttons.back"; \ + relative: r2; \ + } \ + } \ + description { \ + state: "pressed" 0.0; \ + inherit: "default" 0.0; \ + color: 255 255 255 255; \ + } \ + } \ + part { \ + name: "label."##id; \ + type: TEXT; \ + clip_to: "clipper.contacts.visible"; \ + mouse_events: 0; \ + description { \ + state: "default" 0.0; \ + color: 255 255 255 255; \ + color_class: ccls; \ + rel1.to: "button."##id; \ + rel2 { \ + to: "button."##id; \ + } \ + text { \ + text: label; \ + font: FONT_NORMAL; \ + size: SIZE_HUGE; \ + align: 0.5 0.5; \ + } \ + } \ + description { \ + state: "pressed" 0.0; \ + inherit: "default" 0.0; \ + color: 16 16 16 255; \ + } \ + } \ + \ + programs { \ + program { \ + signal: "mouse,up,1"; \ + source: "button."##id; \ + action: SIGNAL_EMIT "released,"##id "keypad"; \ + after: "show_up_"##id; \ + api: id"_released" id" was released"; \ + } \ + program { \ + name: "show_up_"##id; \ + action: STATE_SET "default" 0.0; \ + transition: DECELERATE 0.1; \ + target: "button."##id; \ + target: "label."##id; \ + } \ + program { \ + signal: "mouse,down,1"; \ + source: "button."##id; \ + after: "show_down_"##id; \ + action: SIGNAL_EMIT "pressed,"##id "keypad"; \ + api: id"_pressed" id" was pressed"; \ + } \ + program { \ + name: "show_down_"##id; \ + action: STATE_SET "pressed" 0.0; \ + transition: ACCELERATE 0.1; \ + target: "button."##id; \ + target: "label."##id; \ + } \ + program { \ + signal: "mouse,clicked,1"; \ + source: "button."##id; \ + action: SIGNAL_EMIT "clicked,"##id "gui"; \ + api: id"_clicked" id" was clicked"; \ + } \ + } + + BUTTON("back", "Back", "action", 0.0 0.0, 1.0 1.0); +#undef BUTTON + + part { + name: "elm.swallow.genlist.contacts"; + type: SWALLOW; + api: "contacts genlist" "A genlist to be filled with contacts"; + description { + state: "default" 0.0; + visible: 0; + rel1 { + to_y: "elm.swallow.destination"; + relative: 0.0 1.0; + offset: 0 ITEM_PADDING; + } + } + description { + state: "alternate" 0.0; + inherit: "default" 0.0; + visible: 1; + } + } + } + programs { + + program { + signal: "contacts,hidden"; + source: "gui"; + action: STATE_SET "default" 0.0; + target: "elm.swallow.genlist.contacts"; + target: "clipper.contacts.visible"; + } + program { + signal: "contacts,show"; + source: "gui"; + action: STATE_SET "alternate" 0.0; + target: "elm.swallow.genlist.contacts"; + target: "clipper.contacts.visible"; + } + program { + signal: "show,genlist"; + source: "gui"; + after: "hide,entry,to"; + after: "show,genlist"; + } + program { + signal: "hidden,genlist"; + source: "gui"; + after: "show,entry,to"; + after: "hide,genlist"; + } + program { + name: "hide,entry,to"; + action: STATE_SET "hidden" 0.0; + target: "elm.swallow.destination"; + } + program { + name: "show,entry,to"; + action: STATE_SET "default" 0.0; + target: "elm.swallow.destination"; + } + program { + name: "hide,genlist"; + action: STATE_SET "default" 0.0; + target: "elm.swallow.genlist"; + } + program { + name: "show,genlist"; + action: STATE_SET "visible" 0.0; + target: "elm.swallow.genlist"; + } + program { + signal: "mouse,clicked,1"; + source: "elm.img.btn.send"; + action: SIGNAL_EMIT "clicked,send_msg" "gui"; + } + program { + signal: "toggle,on,edit"; + source: "gui"; + action: STATE_SET "pressed" 0.0; + target: "button.edit"; + target: "button.over.edit"; + target: "ico.edit"; + after: "show_edit_buttons"; + } + program { + name: "show_edit_buttons"; + action: STATE_SET "alternate" 0.0; + transition: ACCELERATE 0.3; + target: "button.clipper"; + target: "edit-button.clipper"; + } + + program { + signal: "toggle,off,edit"; + source: "gui"; + action: STATE_SET "default" 0.0; + target: "button.edit"; + target: "button.over.edit"; + target: "ico.edit"; + after: "hide_edit_buttons"; + } + program { + name: "hide_edit_buttons"; + action: STATE_SET "default" 0.0; + transition: ACCELERATE 0.3; + target: "button.clipper"; + target: "edit-button.clipper"; + } + + program { + signal: "mouse,clicked,1"; + source: "button.edit"; + action: SIGNAL_EMIT "clicked,edit" "gui"; + api: "edit was clicked"; + } + program { + signal: "mouse,clicked,1"; + source: "button.over.edit"; + action: SIGNAL_EMIT "clicked,edit,done" "gui"; + api: "edit_done was clicked"; + } + program { + signal: "composing"; + source: "gui"; + action: STATE_SET "compose" 0.0; + target: "bg.buttons"; + target: "button.edit"; + target: "ico.edit"; + target: "button.over.edit"; + } + program { + signal: "viewing"; + source: "gui"; + action: STATE_SET "default" 0.0; + target: "bg.buttons"; + target: "button.edit"; + target: "ico.edit"; + target: "button.over.edit"; + } + program { + signal: "names_count,show"; + source: "gui"; + action: STATE_SET "default" 0.0; + target: "bg.buttons"; + after: "names_count.visible"; + } + program { + name: "names_count.visible"; + action: STATE_SET "visible" 0.0; + target: "elm.text.names_count"; + target: "ico.ball"; + } + program { + signal: "names_count,hidden"; + source: "gui"; + action: STATE_SET "default" 0.0; + target: "elm.text.names_count"; + target: "ico.ball"; + } + + } +} + +group { + name: "elm/genlist/item_compress/messages-outgoing/default"; + + data { + item: "texts" "text.msg.content text.msg.time"; + item: "states" "sent failed"; + item: "contents" "swallow.btn.resend"; + } + + images { + image: "bg_chat_outgoing.png" COMP; + image: "ico_msg_delivered.png" COMP; + } + + styles { + style { + name: "entry_textblock_style_outgoing"; + base: "font="FONT_NORMAL" font_size="SIZE_MEDIUM" color="TEXTBLOCK_COLOR_LIGHT" wrap=mixed valign=top align=right"; + tag: "br" "\n"; + } + style { + name: "entry_textblock_style_outgoing_failed"; + base: "font="FONT_NORMAL" font_size="SIZE_MEDIUM" color="TEXTBLOCK_COLOR_CAUTION" wrap=mixed valign=top align=right"; + tag: "br" "\n"; + } + } + + parts { + part { + name: "bg"; + type: RECT; + mouse_events: 1; + description { + state: "default" 0.0; + color: 255 255 255 0; + color_class: "action"; + } + description { + state: "selected" 0.0; + inherit: "default" 0.0; + color: 255 255 255 255; + } + } + + part { + name: "text.msg.content"; + type: TEXTBLOCK; + mouse_events: 0; + description { + state: "default" 0.0; + align: 0.0 0.0; + rel1 { + relative: 0.25 0.0; + offset: 0 ITEM_PADDING; + } + rel2 { + offset: (-BORDER_PADDING - ITEM_PADDING - 1) (-MESSAGE_IMG_SENT_SIZE -MESSAGE_SEPARATOR_HEIGHT -ITEM_PADDING -1); + } + text { + style: "entry_textblock_style_outgoing"; + min: 0 1; + } + } + description { + state: "failed" 0.0; + inherit: "default" 0.0; + text { + min: 0 1; + style: "entry_textblock_style_outgoing_failed"; + } + } + } + + part { + name: "img.msg.separator"; + type: IMAGE; + mouse_events: 0; + description { + state: "default" 0.0; + color: 255 255 255 255; + color_class: "light"; + min: MESSAGE_SEPARATOR_WIDTH MESSAGE_SEPARATOR_HEIGHT; + max: MESSAGE_SEPARATOR_WIDTH MESSAGE_SEPARATOR_HEIGHT; + align: 1.0 1.0; + rel1 { + relative: 1.0 1.0; + offset: (-ITEM_PADDING -ITEM_PADDING - 1) (-MESSAGE_IMG_SENT_SIZE - ITEM_PADDING - 1); + } + rel2 { + relative: 1.0 1.0; + offset: (-ITEM_PADDING -ITEM_PADDING - 1) (-MESSAGE_IMG_SENT_SIZE - ITEM_PADDING - 1); + } + image.normal: "bg_chat_outgoing.png"; + } + description { + state: "failed" 0.0; + inherit: "default" 0.0; + color_class: "caution"; + } + } + + part { + name: "text.msg.time"; + type: TEXT; + mouse_events: 0; + description { + state: "default" 0.0; + color: 255 255 255 128; + color_class: "light"; + align: 0.0 0.0; + fixed: 1 1; + rel1 { + to_x: "img.msg.separator"; + to_y: "text.msg.content"; + relative: 0.0 1.0; + offset: 0 SEPARATOR_HEIGHT; + } + rel2 { + relative: 1.0 1.0; + offset: (-MESSAGE_IMG_SENT_SIZE - 2 * BORDER_PADDING - ITEM_PADDING - 1) (-ITEM_PADDING -1); + } + text { + text: "time"; + font: FONT_NORMAL; + size: SIZE_MEDIUM; + align: 0.0 0.0; + max: 1 0; + } + } + description { + state: "failed" 0.0; + inherit: "default" 0.0; + visible: 0; + } + } + + part { + name: "swallow.btn.resend"; + type: SWALLOW; + description { + state: "default" 0.0; + color: 255 255 255 128; + color_class: "caution"; + align: 0.0 0.0; + fixed: 1 1; + visible: 0; + rel1 { + to_x: "img.msg.separator"; + to_y: "text.msg.content"; + relative: 0.0 1.0; + offset: 0 SEPARATOR_HEIGHT; + } + rel2 { + to: "img.msg.separator"; + relative: 1.0 1.0; + offset: -ITEM_PADDING ITEM_PADDING; + } + } + description { + state: "failed" 0.0; + inherit: "default" 0.0; + visible: 1; + } + } + + part { + name: "img.sent"; + type: IMAGE; + description { + color: 255 255 255 255; + color_class: "light"; + min: MESSAGE_IMG_SENT_SIZE MESSAGE_IMG_SENT_SIZE; + max: MESSAGE_IMG_SENT_SIZE MESSAGE_IMG_SENT_SIZE; + visible: 0; + align: 0.0 0.0; + rel1 { + to: "text.msg.time"; + relative: 1.0 0.0; + offset: ITEM_PADDING -MESSAGE_IMG_SENT_Y_OFFSET; + } + image.normal: "ico_msg_delivered.png"; + } + description { + state: "visible" 0.0; + inherit: "default" 0.0; + visible: 1; + } + } + } + + programs { + program { + signal: "elm,state,sent,active"; + source: "elm"; + action: STATE_SET "visible" 0.0; + target: "img.sent"; + } + program { + signal: "elm,state,sent,passive"; + source: "elm"; + action: STATE_SET "default" 0.0; + target: "img.sent"; + } + program { + signal: "elm,state,failed,active"; + source: "elm"; + action: STATE_SET "failed" 0.0; + target: "img.msg.separator"; + target: "text.msg.content"; + target: "swallow.btn.resend"; + target: "text.msg.time"; + } + program { + signal: "elm,state,failed,passive"; + source: "elm"; + action: STATE_SET "default" 0.0; + target: "img.msg.separator"; + target: "text.msg.content"; + target: "swallow.btn.resend"; + target: "text.msg.time"; + } + } +} + +group { + name: "elm/genlist/item_compress/messages-incoming/default"; + data { + item: "texts" "text.msg.content text.msg.time"; + } + images { + image: "bg_chat_incoming.png" COMP; + } + + styles { + style { + name: "entry_textblock_style_incoming"; + base: "font="FONT_NORMAL" font_size="SIZE_MEDIUM" color="TEXTBLOCK_COLOR_ACTION" wrap=mixed valign=top align=left"; + tag: "br" "\n"; + } + } + + parts { + part { + name: "bg"; + type: RECT; + mouse_events: 1; + description { + state: "default" 0.0; + color: 255 255 255 0; + color_class: "action"; + } + description { + state: "selected" 0.0; + inherit: "default" 0.0; + color: 255 255 255 255; + } + } + + part { + name: "text.msg.content"; + type: TEXTBLOCK; + mouse_events: 0; + description { + state: "default" 0.0; + align: 0.0 0.0; + rel1.offset: BORDER_PADDING ITEM_PADDING; + rel2 { + relative: 0.75 1.0; + offset: 0 (-MESSAGE_IMG_SENT_SIZE -MESSAGE_SEPARATOR_HEIGHT -ITEM_PADDING -1); + } + text { + style: "entry_textblock_style_incoming"; + min: 0 1; + } + } + } + + part { + name: "img.msg.separator"; + type: IMAGE; + mouse_events: 0; + description { + state: "default" 0.0; + color: 255 255 255 255; + color_class: "action"; + min: MESSAGE_SEPARATOR_WIDTH MESSAGE_SEPARATOR_HEIGHT; + max: MESSAGE_SEPARATOR_WIDTH MESSAGE_SEPARATOR_HEIGHT; + align: 0.0 0.0; + rel1 { + to_y: "text.msg.content"; + relative: 0.0 1.0; + offset: ITEM_PADDING 0; + } + rel2 { + relative: 0.0 1.0; + offset: (ITEM_PADDING + MESSAGE_SEPARATOR_WIDTH) -1; + } + image.normal: "bg_chat_incoming.png"; + } + } + + part { + name: "text.msg.time"; + type: TEXT; + mouse_events: 0; + description { + state: "default" 0.0; + color: 255 255 255 128; + color_class: "action"; + align: 1.0 0.0; + fixed: 1 1; + rel1 { + to_x: "img.msg.separator"; + to_y: "text.msg.content"; + relative: 0.0 1.0; + offset: BORDER_PADDING SEPARATOR_HEIGHT; + } + rel2.to_x: "img.msg.separator"; + text { + text: "time"; + font: FONT_NORMAL; + size: SIZE_MEDIUM; + align: 1.0 0.0; + max: 1 0; + } + } + } + } +} + +group { + name: "elm/genlist/item/contacts-compose/default"; + data.item: "texts" "text.contact.name text.contact.number text.contact.type"; + + parts { + part { + name: "bg"; + type: RECT; + mouse_events: 1; + description { + state: "default" 0.0; + color: 255 255 255 0; + color_class: "action"; + min: ACTION_WIDTH LIST_ITEM_HEIGHT; + } + description { + state: "selected" 0.0; + inherit: "default" 0.0; + color: 255 255 255 255; + } + } + + part{ + name: "text.contact.name"; + type: TEXT; + mouse_events: 0; + description { + color: 255 255 255 255; + color_class: "action"; + rel1 { + relative: 0.0 0.0; + offset: BORDER_PADDING ITEM_PADDING; + } + rel2 { + relative: 1.0 0.5; + offset: (-BORDER_PADDING - 1) 0; + } + text { + text: "James Bond"; + font: FONT_NORMAL; + size: SIZE_LARGE; + align: 0.0 0.5; + } + } + description { + state: "selected" 0.0; + inherit: "default" 0.0; + color_class: "bg"; + } + } + + part{ + name: "text.contact.number"; + type: TEXT; + mouse_events: 0; + description { + color: 255 255 255 255; + color_class: "light"; + rel1 { + to: "text.contact.name"; + relative: 0.0 1.0; + offset: 0 (ITEM_PADDING + 1); + } + rel2 { + to_y: "bg"; + to_x: "text.contact.type"; + relative: 0.0 1.0; + offset: (-ITEM_PADDING - 1) (-ITEM_PADDING - 1); + } + text { + text: "0000007"; + font: FONT_NORMAL; + size: SIZE_MEDIUM; + align: 0.0 0.5; + } + } + description { + state: "selected" 0.0; + inherit: "default" 0.0; + color_class: "bg"; + } + } + + part{ + name: "text.contact.type"; + type: TEXT; + mouse_events: 0; + description { + color: 255 255 255 255; + color_class: "light"; + align: 1.0 0.5; + fixed: 1 1; + rel1 { + to_y: "text.contact.name"; + to_x: "bg"; + relative: 1.0 1.0; + offset: (-BORDER_PADDING - 1) ITEM_PADDING; + } + rel2 { + to: "bg"; + relative: 1.0 1.0; + offset: (-BORDER_PADDING - 1) (-ITEM_PADDING - 1); + } + text { + text: "0000007"; + font: FONT_NORMAL; + size: SIZE_MEDIUM; + min: 1 0; + align: 1.0 0.5; + } + } + description { + state: "selected" 0.0; + inherit: "default" 0.0; + color_class: "bg"; + } + } + + } + + programs { + program { + signal: "elm,state,selected"; + source: "elm"; + after: "show_selected"; + } + program { + signal: "elm,state,unselected"; + source: "elm"; + after: "show_default"; + } + program { + name: "show_selected"; + action: STATE_SET "selected" 0.0; + target: "text.contact.number"; + target: "text.contact.name"; + target: "text.contact.type"; + target: "bg"; + } + program { + name: "show_default"; + action: STATE_SET "default" 0.0; + target: "text.contact.number"; + target: "text.contact.name"; + target: "text.contact.type"; + target: "bg"; + } + } +} + +group { + name: "elm/genlist/item_compress/outgoing-delete/default"; + alias: "elm/genlist/item_compress_odd/outoing-delete/default"; + + data { + item: "contents" "msg.swallow.delete"; + item: "mode_part" "elm.swallow.decorate.content"; + } + + parts { + part { + name: "content.clipper"; + type: RECT; + description { + state: "default" 0.0; + } + description { + state: "decorated" 0.0; + inherit: "default" 0.0; + rel1 { + offset: (-BORDER_PADDING - LIST_ICON_SIZE - ITEM_PADDING - 1) -1; + } + } + } + + part { + name: "elm.swallow.decorate.content"; + type: SWALLOW; + mouse_events: 1; + scale: 1; + clip_to: "content.clipper"; + description { + state: "default" 0.0; + } + } + + part { + name: "msg.swallow.delete"; + type: SWALLOW; + mouse_events: 1; + scale: 1; + description { + state: "default" 0.0; + visible: 0; + fixed: 1 0; + align: 1.0 0.0; + rel1 { + relative: 0.0 0.0; + offset: 0 0; + } + rel2 { + relative: 0.0 1.0; + offset: 0 0; + } + } + description { + state: "decorated" 0.0; + inherit: "default" 0.0; + visible: 1; + align: 0.0 0.0; + rel1 { + relative: 0.0 0.0; + offset: BORDER_PADDING 0; + } + rel2 { + relative: 0.0 1.0; + offset: BORDER_PADDING 0; + } + } + } + + programs { + program { + name: "animate_decorated"; + signal: "elm,state,decorate,enabled,effect"; + source: "elm"; + action: STATE_SET "decorated" 0.0; + target: "content.clipper"; + after: "animate_decorated2"; + } + program { + name: "animate_decorated2"; + action: STATE_SET "decorated" 0.0; + transition: ACCELERATE 0.15; + target: "msg.swallow.delete"; + } + + program { + name: "animate_default"; + signal: "elm,state,decorate,disabled,effect"; + source: "elm"; + action: STATE_SET "default" 0.0; + transition: ACCELERATE 0.15; + target: "msg.swallow.delete"; + after: "animate_default2"; + } + program { + name: "animate_default2"; + action: STATE_SET "default" 0.0; + target: "content.clipper"; + } + + program { + signal: "elm,state,decorate,enabled"; + source: "elm"; + action: STATE_SET "decorated" 0.0; + target: "content.clipper"; + target: "msg.swallow.delete"; + } + program { + signal: "elm,state,decorate,disabled"; + source: "elm"; + action: STATE_SET "default" 0.0; + target: "content.clipper"; + target: "msg.swallow.delete"; + } + + program { + signal: "elm,state,slide,active"; + source: "elm"; + after: "animate_decorated"; + } + program { + signal: "elm,state,slide,passive"; + source: "elm"; + after: "animate_default"; + } + } + } +} + +group { + name: "elm/genlist/item_compress/incoming-delete/default"; + alias: "elm/genlist/item_compress_odd/incoming-delete/default"; + + data { + item: "contents" "msg.swallow.delete"; + item: "mode_part" "elm.swallow.decorate.content"; + } + + parts { + part { + name: "content.clipper"; + type: RECT; + description { + state: "default" 0.0; + } + description { + state: "decorated" 0.0; + inherit: "default" 0.0; + rel2 { + offset: (-BORDER_PADDING - LIST_ICON_SIZE - ITEM_PADDING - 1) -1; + } + } + } + + part { + name: "elm.swallow.decorate.content"; + type: SWALLOW; + mouse_events: 1; + scale: 1; + clip_to: "content.clipper"; + description { + state: "default" 0.0; + } + } + + part { + name: "msg.swallow.delete"; + type: SWALLOW; + mouse_events: 1; + scale: 1; + description { + state: "default" 0.0; + visible: 0; + fixed: 1 0; + align: 0.0 0.5; + rel1 { + relative: 1.0 0.0; + } + } + description { + state: "decorated" 0.0; + inherit: "default" 0.0; + visible: 1; + align: 1.0 0.5; + rel1 { + relative: 1.0 0.0; + offset: -BORDER_PADDING 0; + } + rel2 { + relative: 1.0 1.0; + offset: -BORDER_PADDING -1; + } + } + } + + programs { + program { + name: "animate_decorated"; + signal: "elm,state,decorate,enabled,effect"; + source: "elm"; + action: STATE_SET "decorated" 0.0; + target: "content.clipper"; + after: "animate_decorated2"; + } + program { + name: "animate_decorated2"; + action: STATE_SET "decorated" 0.0; + transition: ACCELERATE 0.15; + target: "msg.swallow.delete"; + } + + program { + name: "animate_default"; + signal: "elm,state,decorate,disabled,effect"; + source: "elm"; + action: STATE_SET "default" 0.0; + transition: ACCELERATE 0.15; + target: "msg.swallow.delete"; + after: "animate_default2"; + } + program { + name: "animate_default2"; + action: STATE_SET "default" 0.0; + target: "content.clipper"; + } + + program { + signal: "elm,state,decorate,enabled"; + source: "elm"; + action: STATE_SET "decorated" 0.0; + target: "content.clipper"; + target: "msg.swallow.delete"; + } + program { + signal: "elm,state,decorate,disabled"; + source: "elm"; + action: STATE_SET "default" 0.0; + target: "content.clipper"; + target: "msg.swallow.delete"; + } + + program { + signal: "elm,state,slide,active"; + source: "elm"; + after: "animate_decorated"; + } + program { + signal: "elm,state,slide,passive"; + source: "elm"; + after: "animate_default"; + } + } + } +} + +group { + name: "elm/entry/base/compose"; + + styles { + style { + name: "compose_textblock_style"; + base: "font="FONT_NORMAL" font_size="SIZE_MEDIUM" color="TEXTBLOCK_COLOR_ACTION" wrap=mixed valign=top align=auto"; + tag: "br" "\n"; + } + style { + name: "compose_textblock_disabled_style"; + base: "font="FONT_NORMAL" font_size="SIZE_MEDIUM" color="TEXTBLOCK_COLOR_ACTION" wrap=mixed valign=top align=auto"; + tag: "br" "\n"; + } + } + + parts { + part { + name: "elm.text"; + type: TEXTBLOCK; + mouse_events: 1; + scale: 1; + entry_mode: EDITABLE; + select_mode: EXPLICIT; + source: "elm/entry/selection/dialer"; + source4: "elm/entry/cursor/dialer"; + multiline: 1; + description { + state: "default" 0.0; + align: 0.0 0.0; + rel1.offset: BORDER_PADDING 0; + text { + style: "compose_textblock_style"; + min: 0 1; + max: 1 1; + align: -1.0 0.0; + } + } + description { + state: "disabled" 0.0; + inherit: "default" 0.0; + text { + style: "compose_textblock_disabled_style"; + } + } + } + } + + programs { + program { + signal: "load"; + source: ""; + action: FOCUS_SET; + target: "elm.text"; + } + program { + signal: "elm,state,disabled"; + source: "elm"; + action: STATE_SET "disabled" 0.0; + target: "elm.text"; + } + program { + signal: "elm,state,enabled"; + source: "elm"; + action: STATE_SET "default" 0.0; + target: "elm.text"; + } + } +} + +group { + name: "elm/button/base/compose-resend"; + + parts { + part { + name: "elm.text"; + type: TEXT; + description { + state: "default" 0.0; + color: 255 255 255 255; + color_class: "caution"; + text { + text: "A Label"; + font: FONT_NORMAL; + size: SIZE_MEDIUM; + size_range: SIZE_MEDIUM SIZE_HUGE; + fit: 1 1; + } + } + description { + state: "pressed" 0.0; + inherit: "default" 0.0; + color: 255 255 255 128; + } + } + } + programs { + program { + signal: "mouse,down,1"; + source: "elm.text"; + action: SIGNAL_EMIT "elm,action,press" ""; + } + program { + signal: "mouse,up,1"; + source: "elm.text"; + action: SIGNAL_EMIT "elm,action,unpress" ""; + } + program { + signal: "mouse,clicked,1"; + source: "elm.text"; + action: SIGNAL_EMIT "elm,action,click" ""; + } + + program { + signal: "mouse,up,1"; + source: "elm.text"; + action: STATE_SET "default" 0.0; + transition: ACCELERATE 0.1; + target: "elm.text"; + } + + program { + signal: "mouse,down,1"; + source: "elm.text"; + action: STATE_SET "pressed" 0.0; + transition: ACCELERATE 0.1; + target: "elm.text"; + } + } +} + +group { + name: "elm/entry/base-single/default"; + inherit: "elm/entry/base/compose"; + styles + { + style { + name: "entry_single_textblock_compose_guide_style"; + base: "font="FONT_NORMAL" font_size="SIZE_MEDIUM" color="TEXTBLOCK_COLOR_ACTION" wrap=mixed text_class=entry left_margin=2 right_margin=2 ellipsis=0.0"; + } + } + parts { + part { + name: "elm.guide"; + type: TEXTBLOCK; + mouse_events: 0; + scale: 1; + description { + state: "default" 0.0; + rel1.to: "elm.text"; + rel2.to: "elm.text"; + text { + style: "entry_single_textblock_compose_guide_style"; + min: 0 1; + align: 0.0 0.0; + } + } + description { + state: "hidden" 0.0; + inherit: "default" 0.0; + visible: 0; + } + } + part { + name: "elm.text"; + multiline: 0; + description { + state: "default" 0.0; + text { + style: "compose_textblock_style"; + min: 1 1; + max: 0 0; + align: 0.0 0.5; + } + } + description { + state: "disabled" 0.0; + inherit: "default" 0.0; + text { + style: "compose_textblock_disabled_style"; + } + } + } + } +} + +group { + name: "elm/multibuttonentry/base/compose"; + data.item: "closed_button_type" "image"; /* image, label, default : label */ + data.item: "closed_height" 46; + parts { + part { + name: "box.swallow"; + type: SWALLOW; + description { + state: "default" 0.0; + align: 0.0 0.0; + } + } + } +} + + +group { + name: "elm/multibuttonentry/guidetext/compose"; + parts { + part { + name: "elm.text.bg"; + type: RECT; + mouse_events: 0; + scale: 1; + description { + state: "default" 0.0; + color: 0 0 0 0; + visible: 0; + rel1.to: "elm.text"; + rel2.to: "elm.text"; + } + } + part { + name: "elm.text"; + type: TEXT; + scale: 1; + description { + state: "default" 0.0; + color: 255 255 255 255; + color_class: "light"; + rel1.offset: BORDER_PADDING 0; + text { + font: FONT_NORMAL; + size: SIZE_MEDIUM; + min: 0 1; + align: 0.0 0.5; + } + } + } + } +} + + +group { + name: "elm/multibuttonentry/btn/compose"; + data.item: "button_max_size" MULTI_BUTTON_ENTRY_MAX_SIZE; + styles { + style { name: "multibuttonentry_textblock_style"; + base: "font="FONT_NORMAL" align=0.5 font_size="SIZE_MEDIUM" color="TEXTBLOCK_COLOR_ACTION" text_class=label ellipsis=1.0"; + tag: "br" "\n"; + tag: "ps" "ps"; + tag: "hilight" "+ font="FONT_BOLD""; + tag: "b" "+ font="FONT_BOLD""; + tag: "tab" "\t"; + } + } + + parts { + part { + name: "elm.base"; + type: RECT; + mouse_events: 1; + scale: 1; + description { + state: "default" 0.0; + min: 0 LIST_ICON_SIZE; + max: 5000 LIST_ICON_SIZE; + fixed: 1 1; + color: 0 0 0 0; + } + } + part { + name: "right.padding"; + type: RECT; + scale: 1; + description { + state: "default" 0.0; + min : ITEM_PADDING 0; + rel1.relative: 1.0 0.0; + rel2.relative: 1.0 1.0; + color: 0 0 0 0; + align: 1 1; + } + } + part { + name: "elm.btn.bg"; + type: RECT; + mouse_events: 1; + scale: 1; + description { + state: "default" 0.0; + min : ITEM_PADDING LIST_ICON_SIZE; + rel2 { + relative: 0.0 1.0; + to_x: "right.padding"; + } + color: 255 255 255 0; + color_class: "light"; + } + description { + state: "focused" 0.0; + inherit: "default" 0.0; + color: 255 255 255 255; + } + } + part { + name: "elm.btn.text"; + type: TEXTBLOCK; + mouse_events: 1; + scale: 1; + description { + state: "default" 0.0; + min: ITEM_PADDING LIST_ICON_SIZE; + max: (MULTI_BUTTON_ENTRY_MAX_SIZE -2 * ITEM_PADDING) LIST_ICON_SIZE; + fixed: 0 1; + text { + text: "Auto Resized textblock"; + style: "multibuttonentry_textblock_style"; + min: 1 0; + align: 0.5 0.5; + } + rel1 { + to: "elm.btn.bg"; + offset: ITEM_PADDING 0; + } + rel2 { + to: "elm.btn.bg"; + offset: (-ITEM_PADDING -1) -1; + } + + } + } + } + programs { + program { + name: "clicked"; + signal: "mouse,clicked,1"; + source: "elm.btn.bg"; + action: SIGNAL_EMIT "clicked" "elm"; + } + program { + name: "default"; + signal: "default"; + action: STATE_SET "default" 0.0; + target: "elm.btn.bg"; + } + program { + name: "focused"; + signal: "focused"; + action: STATE_SET "focused" 0.0; + target: "elm.btn.bg"; + } + } +} + + +group { + name: "elm/multibuttonentry/label/default"; + parts { + part { + name: "mbe.label.bg"; + type: RECT; + scale: 1; + mouse_events: 0; + description { + state: "default" 0.0; + color: 0 0 0 0; + min: 0 0; + fixed: 1 1; + } + description { + state: "no_text" 0.0; + inherit: "default" 0.0; + visible: 0; + } + } + part { + name: "mbe.label.left.padding"; + type: RECT; + mouse_events: 0; + scale: 1; + description { + state: "default" 0.0; + color: 0 0 0 0; + min: 3 0; + fixed: 1 1; + rel1.to: "mbe.label.bg"; + rel2 { + to: "mbe.label.bg"; + relative: 0.0 1.0; + } + align: 0 0.5; + } + description { + state: "no_text" 0.0; + inherit: "default" 0.0; + visible: 0; + } + } + part { + name: "mbe.label.right.padding"; + type: RECT; + mouse_events: 0; + scale: 1; + description { + state: "default" 0.0; + color: 0 0 0 0; + min: 3 0; + fixed: 1 1; + rel1 { + to: "mbe.label.bg"; + relative: 1.0 0.0; + } + rel2.to: "mbe.label.bg"; + align: 1 1.0; + } + description { + state: "no_text" 0.0; + inherit: "default" 0.0; + visible: 0; + } + } + part { + name: "mbe.label"; + type: TEXT; + mouse_events: 0; + scale: 1; + description { + state: "default" 0.0; + color: 255 255 255 255; + color_class: "action"; + min: 0 33; + fixed: 1 1; + text { + font: FONT_NORMAL; + size: SIZE_MEDIUM; + min: 1 1; + align: 0.0 0.428; + } + rel1 { relative: 1.0 1.0; to: "mbe.label.left.padding"; } + rel2 { relative: 0.0 0.0; to: "mbe.label.right.padding"; } + } + description { + state: "no_text" 0.0; + inherit: "default" 0.0; + min: 6 33; + } + } + } + programs { + program { + name: "has_text"; + signal: "elm,mbe,set_text"; + source: ""; + action: STATE_SET "default" 0.0; + target: "mbe.label.bg"; + target: "mbe.label.left.padding"; + target: "mbe.label.right.padding"; + target: "mbe.label"; + } + program { + name: "no_text"; + signal: "elm,mbe,clear_text"; + source: ""; + action: STATE_SET "no_text" 0.0; + target: "mbe.label.bg"; + target: "mbe.label.left.padding"; + target: "mbe.label.right.padding"; + target: "mbe.label"; + } + } +} + + diff --git a/data/themes/includes/messages-overview-list.edc b/data/themes/includes/messages-overview-list.edc new file mode 100644 index 0000000..413be88 --- /dev/null +++ b/data/themes/includes/messages-overview-list.edc @@ -0,0 +1,332 @@ +group { + name: "elm/genlist/item/messages-overview/default"; + + data { + item: "texts" "elm.text.name elm.text.content elm.text.time"; + item: "contents" "elm.swallow.more"; + } + + images { + image: "ico_arrow_right.png" COMP; + image: "ico_del.png" COMP; + } + + parts { + part { + name: "bg"; + type: RECT; + mouse_events: 1; + description { + state: "default" 0.0; + color: 255 255 255 0; + color_class: "action"; + min: WIDTH LIST_CONTACT_HEIGHT; + } + description { + state: "selected" 0.0; + inherit: "default" 0.0; + color: 255 255 255 255; + } + } + + part { + name: "msg.img.more"; + type: IMAGE; + mouse_events: 0; + description { + state: "default" 0.0; + rel1.to: "elm.swallow.more"; + rel2.to: "elm.swallow.more"; + color_class: "action"; + image { + normal: "ico_arrow_right.png"; + scale_hint: STATIC; + } + } + description { + state: "selected" 0.0; + inherit: "default" 0.0; + color_class: "bg"; + } + } + + part { + name: "elm.swallow.more"; + type: SWALLOW; + mouse_events: 1; + description { + state: "default" 0.0; + min: LIST_ICON_SIZE LIST_ICON_SIZE; + max: LIST_ICON_SIZE LIST_ICON_SIZE; + align: 1.0 0.5; + fixed: 1 1; + rel1 { + relative: 1.0 0.0; + offset: -BORDER_PADDING 0; + } + rel2 { + relative: 1.0 1.0; + offset: -BORDER_PADDING -1; + } + } + } + + part { + name: "elm.text.name"; + type: TEXT; + scale: 1; + mouse_events: 0; + description { + state: "default" 0.0; + color: 255 255 255 255; + color_class: "action"; + rel1 { + relative: 0.0 0.0; + offset: BORDER_PADDING ITEM_PADDING; + } + rel2 { + to_x: "elm.text.time"; + relative: 0.0 0.5; + offset: -ITEM_PADDING -1; + } + text { + font: FONT_NORMAL; + size: SIZE_LARGE; + align: 0.0 1.0; + ellipsis: 0.0; + } + } + description { + state: "selected" 0.0; + inherit: "default" 0.0; + color_class: "bg"; + } + } + + part { + name: "elm.text.content"; + type: TEXT; + scale: 1; + mouse_events: 0; + description { + state: "default" 0.0; + color: 255 255 255 128; + color_class: "action"; + fixed: 1 1; + rel1 { + to: "elm.text.name"; + relative: 0.0 1.0; + offset: 0 0; + } + rel2 { + to: "elm.swallow.more"; + relative: 0.0 1.0; + offset: -ITEM_PADDING -ITEM_PADDING; + } + text { + font: FONT_NORMAL; + size: SIZE_MEDIUM; + align: 0.0 0.0; + ellipsis: 0.0; + } + } + description { + state: "selected" 0.0; + inherit: "default" 0.0; + color_class: "bg"; + } + } + + part { + name: "elm.text.time"; + type: TEXT; + scale: 1; + mouse_events: 0; + description { + fixed: 1 1; + state: "default" 0.0; + color: 255 255 255 128; + color_class: "action"; + align: 1.0 0.5; + rel1 { + to_x: "elm.swallow.more"; + relative: 0.0 0.0; + offset: -ITEM_PADDING ITEM_PADDING; + } + rel2 { + to_x: "elm.swallow.more"; + relative: 0.0 0.5; + offset: -ITEM_PADDING -1; + } + text { + font: FONT_NORMAL; + size: SIZE_MEDIUM; + align: 1.0 1.0; + ellipsis: 0.0; + min: 1 0; + } + } + description { + state: "selected" 0.0; + inherit: "default" 0.0; + color_class: "bg"; + } + } + } + programs { + program { + signal: "elm,state,selected"; + source: "elm"; + after: "show_selected"; + } + program { + signal: "elm,state,unselected"; + source: "elm"; + after: "show_default"; + } + program { + name: "show_default"; + action: STATE_SET "default" 0.0; + target: "bg"; + target: "elm.text.name"; + target: "elm.text.time"; + target: "elm.text.content"; + target: "msg.img.more"; + } + program { + name: "show_selected"; + action: STATE_SET "selected" 0.0; + target: "bg"; + target: "elm.text.name"; + target: "elm.text.time"; + target: "elm.text.content"; + target: "msg.img.more"; + } + } +} + +group { + name: "elm/genlist/item/messages-overview-delete/default"; + alias: "elm/genlist/item_odd/messages-overview-delete/default"; + + data { + item: "contents" "elm.swallow.delete"; + item: "mode_part" "elm.swallow.decorate.content"; + } + + parts { + part { + name: "content.clipper"; + type: RECT; + description { + state: "default" 0.0; + } + description { + state: "decorated" 0.0; + inherit: "default" 0.0; + rel2 { + offset: (-BORDER_PADDING - LIST_ICON_SIZE - ITEM_PADDING - 1) -1; + } + } + } + + part { + name: "elm.swallow.decorate.content"; + type: SWALLOW; + mouse_events: 1; + scale: 1; + clip_to: "content.clipper"; + description { + state: "default" 0.0; + } + } + + part { + name: "elm.swallow.delete"; + type: SWALLOW; + mouse_events: 1; + scale: 1; + description { + state: "default" 0.0; + visible: 0; + fixed: 1 0; + align: 0.0 0.5; + rel1 { + relative: 1.0 0.0; + } + } + description { + state: "decorated" 0.0; + inherit: "default" 0.0; + visible: 1; + align: 1.0 0.5; + rel1 { + relative: 1.0 0.0; + offset: -BORDER_PADDING 0; + } + rel2 { + relative: 1.0 1.0; + offset: -BORDER_PADDING -1; + } + } + } + + programs { + program { + name: "animate_decorated"; + signal: "elm,state,decorate,enabled,effect"; + source: "elm"; + action: STATE_SET "decorated" 0.0; + target: "content.clipper"; + after: "animate_decorated2"; + } + program { + name: "animate_decorated2"; + action: STATE_SET "decorated" 0.0; + transition: ACCELERATE 0.15; + target: "elm.swallow.delete"; + } + + program { + name: "animate_default"; + signal: "elm,state,decorate,disabled,effect"; + source: "elm"; + action: STATE_SET "default" 0.0; + transition: ACCELERATE 0.15; + target: "elm.swallow.delete"; + after: "animate_default2"; + } + program { + name: "animate_default2"; + action: STATE_SET "default" 0.0; + target: "content.clipper"; + } + + program { + signal: "elm,state,decorate,enabled"; + source: "elm"; + action: STATE_SET "decorated" 0.0; + target: "content.clipper"; + target: "elm.swallow.delete"; + } + program { + signal: "elm,state,decorate,disabled"; + source: "elm"; + action: STATE_SET "default" 0.0; + target: "content.clipper"; + target: "elm.swallow.delete"; + } + + program { + signal: "elm,state,slide,active"; + source: "elm"; + after: "animate_decorated"; + } + program { + signal: "elm,state,slide,passive"; + source: "elm"; + after: "animate_default"; + } + } + } +} diff --git a/data/themes/includes/messages-overview.edc b/data/themes/includes/messages-overview.edc new file mode 100644 index 0000000..108a51e --- /dev/null +++ b/data/themes/includes/messages-overview.edc @@ -0,0 +1,430 @@ +group { + name: "elm/layout/ofono-efl/messages-overview"; + + /* + * Represents the messages compose layout with actions and swallows. + * + * Parts: + * + * SWALLOW: elm.swallow.genlist - Where all the conversations will appear + * + * Signals: + * Emit (source is "gui"): + * clicked,edit - When the edit button is cliked + * clicked,edit,done - When The Button "Done" while editing is clicked + * + * Listens (source is "gui"): + * show,genlist - Show the genlist with the current message thread + * hidden,genlist - Hide the genlist + * toggle,on,edit - User is editing + * toggle,off,edit - User finished the editing + * + * Where is the name of the button + * + */ + + images { + image: "bg_keypad.jpg" COMP; + } + + parts { + part { + name: "bg"; + type: IMAGE; + mouse_events: 0; + description { + state: "default" 0.0; + color: 255 255 255 255; + color_class: "dark"; + image { + normal: "bg_keypad.jpg"; + scale_hint: STATIC; + } + fill.smooth: 1; + + } + } + + part { + name: "notification.bar"; + type: RECT; + mouse_events: 0; + description { + state: "default" 0.0; + color: 0 0 0 255; + rel1.relative: 0.0 0.0; + rel2 { + relative: 1.0 0.0; + offset: -1 NOTIFICATION_BAR_HEIGHT; + } + } + } + + part { + name: "button.area.clipper"; + type: RECT; + description { + state: "default" 0.0; + rel1.to: "bg.buttons"; + rel2 { + to: "bg.buttons"; + offset: -1 SEPARATOR_HEIGHT; + } + } + } + + + part { + name: "button.clipper"; + type: RECT; + clip_to: "button.area.clipper"; + description { + state: "default" 0.0; + rel1.to: "button.area.clipper"; + rel2.to: "button.area.clipper"; + } + description { + state: "alternate" 0.0; + color: 255 255 255 0; + visible: 0; + } + } + + part { + name: "edit-button.clipper"; + type: RECT; + clip_to: "button.area.clipper"; + description { + state: "default" 0.0; + color: 255 255 255 0; + visible: 0; + rel1.to: "button.area.clipper"; + rel2.to: "button.area.clipper"; + } + description { + state: "alternate" 0.0; + color: 255 255 255 255; + visible: 1; + } + } + + part { + name: "edit.clipper"; + type: RECT; + description { + state: "default" 0.0; + rel1.to: "bg.edit"; + rel2 { + to: "bg.edit"; + offset: -1 SEPARATOR_HEIGHT; + } + } + } + + part { + name: "bg.buttons"; + type: RECT; + mouse_events: 0; + clip_to: "button.clipper"; + description { + state: "default" 0.0; + color: 0 0 0 0; + fixed: 1 1; + min: 0 (ACTION_HEIGHT / 2); + rel1 { + to: "notification.bar"; + relative: 0.0 1.0; + } + rel2 { + to_x: "bg.edit"; + to_y: "notification.bar"; + relative: 0.0 1.0; + offset: (-ITEM_PADDING - 1) (ACTION_HEIGHT / 2 - 1); + } + } + } + + part { + name: "bg.edit"; + type: RECT; + mouse_events: 0; + clip_to: "edit.clipper"; + description { + state: "default" 0.0; + color: 0 0 0 0; + min: 0 (ACTION_HEIGHT / 2); + rel1 { + to: "notification.bar"; + relative: 1.0 1.0; + offset: (-LIST_ICON_SIZE - ITEM_PADDING - BORDER_PADDING) 0; + } + rel2 { + to: "notification.bar"; + relative: 1.0 1.0; + offset: -1 (ACTION_HEIGHT / 2 - 1); + } + } + } + +#define SEPARATOR(id, clip, rely, offy, relto) \ + part { \ + name: "separator.dark."##id; \ + type: RECT; \ + mouse_events: 0; \ + clip_to: clip; \ + description { \ + state: "default" 0.0; \ + color: 255 255 255 255; \ + color_class: "dark"; \ + rel1 { \ + relative: 0.0 rely; \ + offset: 0 offy; \ + to_y: relto; \ + } \ + rel2 { \ + relative: 1.0 rely; \ + offset: -1 (offy + SEPARATOR_HEIGHT / 2); \ + to_y: relto; \ + } \ + } \ + } \ + part { \ + name: "separator.bg."##id; \ + type: RECT; \ + mouse_events: 0; \ + clip_to: clip; \ + description { \ + state: "default" 0.0; \ + color: 255 255 255 255; \ + color_class: "bg"; \ + rel1 { \ + relative: 0.0 rely; \ + offset: 0 (offy + SEPARATOR_HEIGHT / 2); \ + to_y: relto; \ + } \ + rel2 { \ + relative: 1.0 rely; \ + offset: -1 (offy + SEPARATOR_HEIGHT); \ + to_y: relto; \ + } \ + } \ + } + + SEPARATOR("button", "button.area.clipper", 1.0, 0, "bg.buttons"); + SEPARATOR("edit-button", "button.area.clipper", 1.0, 0, "bg.buttons"); + SEPARATOR("edit", "edit.clipper", 1.0, 0, "bg.edit"); +#undef SEPARATOR + + +#define BUTTON(id, label, ccls, clip, r1, r2) \ + part { \ + name: "button."##id; \ + type: RECT; \ + mouse_events: 1; \ + clip_to: clip; \ + description { \ + state: "default" 0.0; \ + color: 255 255 255 0; \ + color_class: "action"; \ + rel1 { \ + to: "bg.buttons"; \ + relative: r1; \ + } \ + rel2 { \ + to: "bg.buttons"; \ + relative: r2; \ + } \ + } \ + description { \ + state: "pressed" 0.0; \ + inherit: "default" 0.0; \ + color: 255 255 255 255; \ + } \ + } \ + part { \ + name: "label."##id; \ + type: TEXT; \ + mouse_events: 0; \ + clip_to: clip; \ + description { \ + state: "default" 0.0; \ + color: 255 255 255 255; \ + color_class: ccls; \ + rel1.to: "button."##id; \ + rel2 { \ + to: "button."##id; \ + } \ + text { \ + text: label; \ + font: FONT_NORMAL; \ + size: SIZE_MEDIUM; \ + align: 0.5 0.5; \ + } \ + } \ + description { \ + state: "pressed" 0.0; \ + inherit: "default" 0.0; \ + color: 16 16 16 255; \ + } \ + } \ + \ + programs { \ + program { \ + signal: "toggle,on,"##id; \ + source: "gui"; \ + action: STATE_SET "pressed" 0.0; \ + target: "button."##id; \ + target: "label."##id; \ + } \ + program { \ + signal: "toggle,off,"##id; \ + source: "gui"; \ + action: STATE_SET "default" 0.0; \ + target: "button."##id; \ + target: "label."##id; \ + } \ + program { \ + signal: "mouse,clicked,1"; \ + source: "button."##id; \ + action: SIGNAL_EMIT "clicked,"##id "gui"; \ + api: id"_clicked" id" was clicked"; \ + } \ + } + + BUTTON("view", "View", "action", "button.clipper", 0.0 0.0, 0.5 1.0); + BUTTON("compose", "Compose", "action", "button.clipper", 0.5 0.0, 1.0 1.0); + + BUTTON("clear", "Clear", "caution", "edit-button.clipper", 0.0 0.0, 0.5 1.0); + BUTTON("edit,done", "Done", "action", "edit-button.clipper", 0.5 0.0, 1.0 1.0); +#undef BUTTON + + part { + name: "button.edit"; + type: RECT; + mouse_events: 1; + clip_to: "edit.clipper"; + description { + state: "default" 0.0; + color: 255 255 255 0; + color_class: "caution"; + rel1.to: "bg.edit"; + rel2.to: "bg.edit"; + } + description { + state: "pressed" 0.0; + inherit: "default" 0.0; + color: 255 255 255 255; + } + } + part { + name: "ico.edit"; + type: IMAGE; + mouse_events: 0; + clip_to: "edit.clipper"; + description { + state: "default" 0.0; + min: LIST_ICON_SIZE LIST_ICON_SIZE; + max: LIST_ICON_SIZE LIST_ICON_SIZE; + align: 0.0 0.5; + color: 255 255 255 255; + color_class: "caution"; + rel1 { + to: "button.edit"; + offset: ITEM_PADDING 0; + } + rel2 { + to: "button.edit"; + offset: (-BORDER_PADDING - 1) -1; + } + image { + normal: "ico_del.png"; + scale_hint: STATIC; + } + } + description { + state: "pressed" 0.0; + inherit: "default" 0.0; + color: 16 16 16 255; + } + } + part { + name: "button.over.edit"; + type: RECT; + mouse_events: 1; + clip_to: "edit.clipper"; + description { + state: "default" 0.0; + color: 0 0 0 0; + visible: 0; + rel1.to: "bg.edit"; + rel2.to: "bg.edit"; + } + description { + state: "pressed" 0.0; + inherit: "default" 0.0; + visible: 1; + } + } + + part { + name: "elm.swallow.genlist"; + type: SWALLOW; + description { + state: "default" 0.0; + rel1 { + to_y: "separator.bg.button"; + relative: 0.0 1.0; + } + } + } + } + + programs { + program { + signal: "toggle,on,edit"; + source: "gui"; + action: STATE_SET "pressed" 0.0; + target: "button.edit"; + target: "button.over.edit"; + target: "ico.edit"; + after: "show_edit_buttons"; + } + program { + name: "show_edit_buttons"; + action: STATE_SET "alternate" 0.0; + transition: ACCELERATE 0.3; + target: "button.clipper"; + target: "edit-button.clipper"; + } + + program { + signal: "toggle,off,edit"; + source: "gui"; + action: STATE_SET "default" 0.0; + target: "button.edit"; + target: "button.over.edit"; + target: "ico.edit"; + after: "hide_edit_buttons"; + } + program { + name: "hide_edit_buttons"; + action: STATE_SET "default" 0.0; + transition: ACCELERATE 0.3; + target: "button.clipper"; + target: "edit-button.clipper"; + } + + program { + signal: "mouse,clicked,1"; + source: "button.edit"; + action: SIGNAL_EMIT "clicked,edit" "gui"; + api: "edit was clicked"; + } + program { + signal: "mouse,clicked,1"; + source: "button.over.edit"; + action: SIGNAL_EMIT "clicked,edit,done" "gui"; + api: "edit_done was clicked"; + } + } +} diff --git a/data/themes/includes/messages.edc b/data/themes/includes/messages.edc index 5cb8252..fb08ab3 100644 --- a/data/themes/includes/messages.edc +++ b/data/themes/includes/messages.edc @@ -14,6 +14,10 @@ group { * */ + images { + image: "ico_edit.png" COMP; + } + parts { part { name: "bg"; @@ -39,5 +43,17 @@ group { } } } + + part { + name: "elm.swallow.overview"; + type: SWALLOW; + description { + state: "default" 0.0; + rel1 { + to_y: "notification.bar"; + relative: 0.0 1.0; + } + } + } } } diff --git a/data/themes/includes/scroller.edc b/data/themes/includes/scroller.edc index 9237ba4..c652b08 100644 --- a/data/themes/includes/scroller.edc +++ b/data/themes/includes/scroller.edc @@ -2,6 +2,9 @@ group { name: "elm/scroller/base/multiparty-details"; alias: "elm/genlist/base/history"; alias: "elm/genlist/base/contacts"; + alias: "elm/genlist/base/messages-overview"; + alias: "elm/scroller/entry/compose"; + alias: "elm/genlist/base/compose"; script { public sbvis_v, sbalways_v, sbvis_timer; @@ -31,6 +34,16 @@ group { } part { + name: "bg"; + type: RECT; + clip_to: "clipper"; + description { + state: "default" 0.0; + color: 0 0 0 0; + } + } + + part { name: "elm.swallow.content"; clip_to: "clipper"; type: SWALLOW; diff --git a/messages/compose.c b/messages/compose.c index f1d1f94..825ab66 100644 --- a/messages/compose.c +++ b/messages/compose.c @@ -2,25 +2,802 @@ #include "config.h" #endif #include +#include +#include +#include #include "log.h" #include "util.h" +#include "gui.h" +#include "overview.h" +#include "ofono.h" typedef struct _Compose { Evas_Object *layout; + Evas_Object *entry_msg; + Evas_Object *mb_entry; + Evas_Object *genlist; + Evas_Object *genlist_contacts; + Elm_Genlist_Item_Class *itc_inc; + Elm_Genlist_Item_Class *itc_out; + Elm_Genlist_Item_Class *itc_c_name; + Eina_Bool composing; + Eina_List *current_thread; + const char *number; + Eina_List *composing_numbers; + Ecore_Poller *updater; + double last_update; } Compose; +typedef struct _Contact_Genlist { + const char *name; + const char *number; + const char *type; + const char *picture; + Compose *compose; +} Contact_Genlist; + +static OFono_Callback_List_Incoming_SMS_Node *incoming_sms = NULL; +static OFono_Callback_List_Sent_SMS_Node *sent_sms = NULL; + +static void _send_sms(Compose *compose); +static void _compose_timer_updater_start(Compose *compose); + +static void _message_remove_from_genlist(Message *msg) +{ + Elm_Object_Item *it; + + it = message_object_item_get(msg); + EINA_SAFETY_ON_NULL_RETURN(it); + elm_object_item_del(it); +} + +static void _message_from_file_delete(Compose *compose, Message *msg, + const char *contact) +{ + Message *msg_aux; + Eina_List *last; + + gui_message_from_file_delete(msg, contact); + _message_remove_from_genlist(msg); + + last = eina_list_last(compose->current_thread); + msg_aux = eina_list_data_get(last); + + if (msg_aux == msg) { + msg_aux = eina_list_data_get(eina_list_prev(last)); + if (msg_aux != NULL) + gui_overview_genlist_update(msg_aux, contact); + } + compose->current_thread = eina_list_remove(compose->current_thread, + msg); + message_del(msg); +} + static void _on_del(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event __UNUSED__) { Compose *compose = data; + Message *msg; + const char *number; + + ofono_incoming_sms_cb_del(incoming_sms); + ofono_sent_sms_changed_cb_del(sent_sms); + elm_genlist_item_class_free(compose->itc_inc); + elm_genlist_item_class_free(compose->itc_out); + elm_genlist_item_class_free(compose->itc_c_name); + EINA_LIST_FREE(compose->current_thread, msg) + message_del(msg); + + EINA_LIST_FREE(compose->composing_numbers, number) + eina_stringshare_del(number); + + eina_stringshare_del(compose->number); free(compose); } +static void _compose_exit(Compose *compose) +{ + Message *msg; + const char *number; + + compose->composing = EINA_TRUE; + eina_stringshare_replace(&(compose->number), NULL); + elm_object_part_text_set(compose->layout, "elm.text.name", + "New Message"); + elm_object_signal_emit(compose->layout, "hidden,genlist", "gui"); + elm_object_signal_emit(compose->layout, "composing", "gui"); + elm_genlist_clear(compose->genlist); + elm_genlist_clear(compose->genlist_contacts); + + EINA_LIST_FREE(compose->current_thread, msg) + message_del(msg); + + EINA_LIST_FREE(compose->composing_numbers, number) + eina_stringshare_del(number); + + elm_object_part_text_set(compose->entry_msg, NULL, ""); + elm_genlist_decorate_mode_set(compose->genlist, EINA_FALSE); + elm_multibuttonentry_clear(compose->mb_entry); + elm_object_signal_emit(compose->layout, "contacts,hidden", "gui"); + elm_object_signal_emit(compose->layout, "names_count,hidden", "gui"); + gui_compose_exit(); +} + +static void _on_layout_clicked(void *data, Evas_Object *o, + const char *emission, const char *source __UNUSED__) +{ + Compose *compose = data; + DBG("signal: %s", emission); + + EINA_SAFETY_ON_FALSE_RETURN(eina_str_has_prefix(emission, "clicked,")); + emission += strlen("clicked,"); + + if (strcmp(emission, "back") == 0) + _compose_exit(compose); + else if (strcmp(emission, "send_msg") == 0) + _send_sms(compose); + else if (strcmp(emission, "edit") == 0) { + elm_object_signal_emit(o, "toggle,on,edit", "gui"); + elm_genlist_decorate_mode_set(compose->genlist, EINA_TRUE); + } else if (strcmp(emission, "edit,done") == 0) { + elm_object_signal_emit(o, "toggle,off,edit", "gui"); + elm_genlist_decorate_mode_set(compose->genlist, EINA_FALSE); + } else if (strcmp(emission, "clear") == 0) { + const char *contact = compose->number; + gui_overview_all_contact_messages_clear(contact); + Message *msg; + EINA_LIST_FREE(compose->current_thread, msg) { + _message_remove_from_genlist(msg); + message_del(msg); + } + elm_object_signal_emit(o, "toggle,off,edit", "gui"); + } else + ERR("Unkown emission: %s", emission); +} + +static void _send_sms_reply(void *data __UNUSED__, OFono_Error error, + OFono_Sent_SMS *sms) +{ + if (error != OFONO_ERROR_NONE) { + ERR("Error when trying to send a new message"); + return; + } + + DBG("SMS Sent to: %s, message: %s", ofono_sent_sms_destination_get(sms), + ofono_sent_sms_message_get(sms)); +} + +static void _send_sms(Compose *compose) +{ + const char *msg_content; + char *msg_utf; + Message *msg; + Elm_Object_Item *it; + Contact_Info *c_info; + const char *to; + Eina_List *l; + + if (elm_entry_is_empty(compose->entry_msg)) { + DBG("Returning, empty message"); + return; + } + + if (compose->composing && (!compose->composing_numbers)) { + DBG("Returning, no contacts to send"); + return; + } + + msg_content = elm_object_part_text_get(compose->entry_msg, NULL); + + msg_utf = elm_entry_markup_to_utf8(msg_content); + + msg = message_new(time(NULL), msg_utf, + EINA_FALSE, OFONO_SENT_SMS_STATE_PENDING); + EINA_SAFETY_ON_NULL_RETURN(msg); + message_data_set(msg, compose); + it = elm_genlist_item_append(compose->genlist, compose->itc_out, + msg, NULL, ELM_GENLIST_ITEM_NONE, NULL, + NULL); + elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_IN); + message_object_item_set(msg, it); + elm_object_signal_emit(compose->layout, "show,genlist", "gui"); + + compose->current_thread = + eina_list_append(compose->current_thread, msg); + elm_object_part_text_set(compose->entry_msg, NULL, ""); + + _compose_timer_updater_start(compose); + + if (!compose->composing) { + ofono_sms_send(compose->number, msg_utf, _send_sms_reply, NULL); + DBG("New Message to: %s content: %s", compose->number, msg_utf); + c_info = gui_contact_search(compose->number, NULL); + + if (!c_info) + elm_object_part_text_set(compose->layout, + "elm.text.name", + compose->number); + else { + to = contact_info_full_name_get(c_info); + elm_object_part_text_set(compose->layout, + "elm.text.name", + to); + } + } else { + const char *names = NULL, *name; + char size[5]; + const Eina_List *items; + int i = 0; + items = elm_multibuttonentry_items_get(compose->mb_entry); + EINA_SAFETY_ON_NULL_GOTO(items, exit); + + EINA_LIST_FOREACH(compose->composing_numbers, l, to) { + ofono_sms_send(to, msg_utf, _send_sms_reply, NULL); + DBG("New Message to: %s content: %s", to, msg_utf); + it = eina_list_nth(items, i); + name = elm_object_item_part_text_get(it, NULL); + if (i == 0) + names = eina_stringshare_add(name); + else + names = eina_stringshare_printf("%s, %s", + names, name); + i++; + } + + elm_object_part_text_set(compose->layout, + "elm.text.name", names); + snprintf(size, sizeof(size), "%d", + eina_list_count(compose->composing_numbers)); + elm_object_signal_emit(compose->layout, "names_count,show", + "gui"); + elm_object_part_text_set(compose->layout, + "elm.text.names_count", + size); + eina_stringshare_del(names); + } +exit: + free(msg_utf); +} + +static char *_item_c_name_label_get(void *data, Evas_Object *obj __UNUSED__, + const char *part) +{ + Contact_Genlist *c_genlist = data; + + if (strncmp(part, "text.contact.", strlen("text.contact."))) + return NULL; + + part += strlen("text.contact."); + + if (strcmp(part, "name") == 0) + return strdup(c_genlist->name); + else if (strcmp(part, "number") == 0) + return strdup(c_genlist->number); + else if (strcmp(part, "type") == 0) + return strdup(c_genlist->type); + + ERR("Unexpected text part: %s", part); + return NULL; +} + +static Evas_Object *_item_c_name_content_get(void *data, Evas_Object *obj, + const char *part) +{ + Contact_Genlist *c_genlist = data; + + if (strncmp(part, "elm.swallow.", strlen("elm.swallow."))) + return NULL; + + part += strlen("elm.swallow."); + + if (strcmp(part, "photo") == 0) + return picture_icon_get(obj, c_genlist->picture); + + ERR("Unexpected part name: %s", part); + return NULL; +} + + +static char *_item_label_get(void *data, Evas_Object *obj __UNUSED__, + const char *part) +{ + Message *msg = data; + + if (strncmp(part, "text.msg.", strlen("text.msg."))) + return NULL; + + part += strlen("text.msg."); + + if (strcmp(part, "content") == 0) { + const char *content = message_content_get(msg); + if (!content) + return NULL; + return elm_entry_utf8_to_markup(content); + } + + if (strcmp(part, "time") == 0) { + time_t t_msg = message_time_get(msg); + if (t_msg == -1) + return NULL; + return date_format(t_msg); + } + + ERR("Unexpected text part: %s", part); + return NULL; +} + +static Eina_Bool _item_state_get(void *data, Evas_Object *obj __UNUSED__, + const char *part) +{ + Message *msg = data; + + if (strcmp(part, "sent") == 0) { + if (message_state_get(msg) == OFONO_SENT_SMS_STATE_SENT) + return EINA_TRUE; + return EINA_FALSE; + } else if (strcmp(part, "failed") == 0) { + if (message_state_get(msg) == OFONO_SENT_SMS_STATE_FAILED) + return EINA_TRUE; + return EINA_FALSE; + } + + ERR("Unexpected state part: %s", part); + return EINA_FALSE; +} + +static void _sms_size_calc(const char *str, int *size, int *max) +{ + Eina_Bool all_isprint = EINA_TRUE; + const char *s; + + for (s = str; *s != '\0'; s++) { + if (!isprint(*s)) { + all_isprint = EINA_FALSE; + break; + } + } + + *size = strlen(str); + + if (all_isprint) { + if (*size <= 160) + *max = 160; + else + *max = ((*size / 153) + 1) * 153; + } else { + if (*size <= 70) + *max = 70; + else + *max = ((*size / 67) + 1) * 67; + } +} + +static void _on_text_changed(void *data, Evas_Object *obj, + void *event_info __UNUSED__) +{ + Compose *compose = data; + const char *msg = elm_object_part_text_get(obj, NULL); + Evas_Object *ed; + char *msg_utf8; + int size, max; + char buf[PATH_MAX]; + Edje_Message_Int_Set *ed_msg; + + if (!msg) + return; + + msg_utf8 = elm_entry_markup_to_utf8(msg); + _sms_size_calc(msg_utf8, &size, &max); + snprintf(buf,sizeof(buf), "%d", size); + elm_object_part_text_set(compose->layout, "elm.text.size", buf); + snprintf(buf,sizeof(buf), "%d", max); + elm_object_part_text_set(compose->layout, "elm.text.max_size", buf); + free(msg_utf8); + + ed_msg = alloca(sizeof(Edje_Message_Float_Set) + sizeof(int)); + ed_msg->count = 2; + ed_msg->val[0] = size; + ed_msg->val[1] = max; + ed = elm_layout_edje_get(compose->layout); + edje_object_message_send(ed, EDJE_MESSAGE_INT_SET, 1, ed_msg); +} + +static void _incoming_sms_cb(void *data, unsigned int sms_class, + time_t timestamp, const char *sender, + const char *message) +{ + Compose *compose = data; + Message *msg; + Elm_Object_Item *it; + + /* Users can only send class 1. This is OFono/GSM detail */ + if (sms_class != 1) + return; + + if (compose->number && strcmp(compose->number, sender) != 0) + return; + + msg = message_new(timestamp, message, EINA_FALSE, + OFONO_SENT_SMS_STATE_SENT); + + EINA_SAFETY_ON_NULL_RETURN(msg); + message_data_set(msg, compose); + it = elm_genlist_item_append(compose->genlist, compose->itc_inc, msg, + NULL, ELM_GENLIST_ITEM_NONE, NULL, + NULL); + elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_TOP); + message_object_item_set(msg, it); + compose->current_thread = + eina_list_append(compose->current_thread, msg); + + _compose_timer_updater_start(compose); +} + +static Eina_Bool _compose_time_updater(void *data) +{ + Compose *ctx = data; + double now = ecore_loop_time_get(); + const double interval_threshold = 30.0; + Elm_Object_Item *it; + const long long update_threshold = time(NULL) - WEEK - DAY; + + if (!ctx->current_thread) { + ctx->updater = NULL; + return EINA_FALSE; + } + + if (now - ctx->last_update < interval_threshold) + return EINA_TRUE; + ctx->last_update = now; + + it = elm_genlist_first_item_get(ctx->genlist); + for (; it != NULL; it = elm_genlist_item_next_get(it)) { + Message *msg = elm_object_item_data_get(it); + long long t = message_time_get(msg); + if (EINA_UNLIKELY(t == 0)) { + t = message_time_get(msg); + } + if (EINA_UNLIKELY(t < update_threshold)) + break; + elm_genlist_item_update(it); + } + + return EINA_TRUE; +} + +static void _compose_timer_updater_start(Compose *compose) +{ + Evas *e = evas_object_evas_get(compose->layout); + Eina_Bool win_focused = evas_focus_state_get(e); + Eina_Bool obj_visible = evas_object_visible_get(compose->layout); + + DBG("poller %p, win_focused=%hhu, obj_visible=%hhu", + compose->updater, win_focused, obj_visible); + if (compose->updater) + return; + if (!compose->current_thread) + return; + if ((!win_focused) || (!obj_visible)) + return; + + DBG("start poller messages"); + /* ECORE_POLLER_CORE is 1/8th of second. */ + compose->updater = ecore_poller_add(ECORE_POLLER_CORE, 8 * 60, + _compose_time_updater, + compose); + _compose_time_updater(compose); +} + +static void _compose_time_updater_stop(Compose *compose) +{ + Evas *e = evas_object_evas_get(compose->layout); + Eina_Bool win_focused = evas_focus_state_get(e); + Eina_Bool obj_visible = evas_object_visible_get(compose->layout); + + DBG("poller %p, win_focused=%hhu, obj_visible=%hhu", + compose->updater, win_focused, obj_visible); + if (!compose->updater) + return; + if (win_focused && obj_visible) + return; + + DBG("delete poller %p", compose->updater); + ecore_poller_del(compose->updater); + compose->updater = NULL; +} + +static void _on_show(void *data, Evas *e __UNUSED__, + Evas_Object *obj __UNUSED__, void *event __UNUSED__) +{ + Compose *compose = data; + DBG("Overview became visible"); + _compose_timer_updater_start(compose); +} + +static void _on_win_focus_in(void *data, Evas *e __UNUSED__, + void *event_info __UNUSED__) +{ + Compose *compose = data; + DBG("window is focused"); + _compose_timer_updater_start(compose); +} + +static void _on_win_focus_out(void *data, Evas *e __UNUSED__, + void *event_info __UNUSED__) +{ + Compose *compose = data; + DBG("window is unfocused"); + _compose_time_updater_stop(compose); +} + +static void _on_hide(void *data, Evas *e __UNUSED__, + Evas_Object *obj __UNUSED__, void *event __UNUSED__) +{ + Compose *compose = data; + DBG("Overview became hidden"); + _compose_time_updater_stop(compose); +} + +static void _on_del_clicked(void *data, Evas_Object *obj __UNUSED__, + void *event_info __UNUSED__) +{ + Message *msg = data; + Compose *compose = message_data_get(msg); + + EINA_SAFETY_ON_NULL_RETURN(compose); + _message_from_file_delete(compose, msg, compose->number); +} + +static void _on_resend_clicked(void *data, Evas_Object *obj __UNUSED__, + void *event_info __UNUSED__) +{ + Message *msg = data; + const char *content, *number; + Compose *compose = message_data_get(msg); + + EINA_SAFETY_ON_NULL_RETURN(compose); + + content = message_content_get(msg); + EINA_SAFETY_ON_NULL_RETURN(content); + number = message_phone_get(msg); + EINA_SAFETY_ON_NULL_RETURN(number); + + INF("%s %s", number, content); + ofono_sms_send(number, content, _send_sms_reply, NULL); +} + +static Evas_Object *_item_content_get(void *data, Evas_Object *obj, + const char *part) +{ + Evas_Object *btn = NULL; + Message *msg = data; + + if (strcmp(part, "msg.swallow.delete") == 0) { + btn = elm_button_add(obj); + EINA_SAFETY_ON_NULL_RETURN_VAL(btn, NULL); + elm_object_style_set(btn, "history-delete"); + elm_object_text_set(btn, "delete"); + evas_object_smart_callback_add(btn, "clicked", _on_del_clicked, + msg); + evas_object_propagate_events_set(btn, EINA_FALSE); + } else if (strcmp(part, "swallow.btn.resend") == 0) { + btn = elm_button_add(obj); + EINA_SAFETY_ON_NULL_RETURN_VAL(btn, NULL); + elm_object_style_set(btn, "compose-resend"); + elm_object_text_set(btn, "Failed. Try Again?"); + evas_object_smart_callback_add(btn, "clicked", _on_resend_clicked, + msg); + evas_object_propagate_events_set(btn, EINA_FALSE); + } else + ERR("unknown content part '%s'", part); + + return btn; +} + +static void _sent_sms_cb(void *data, OFono_Error error, OFono_Sent_SMS *sms) +{ + Compose *compose = data; + Eina_List *l; + Message *msg; + + if (error != OFONO_ERROR_NONE) { + ERR("OFono error - Sending a SMS"); + return; + } + + EINA_LIST_FOREACH(compose->current_thread, l, msg) { + const char *m_sms, *m_msg; + time_t t_msg, t_sms; + unsigned char state; + + m_sms = ofono_sent_sms_message_get(sms); + m_msg = message_content_get(msg); + t_msg = message_time_get(msg); + state = message_state_get(msg); + t_sms = ofono_sent_sms_timestamp_get(sms); + + if ((m_sms == m_msg && t_msg == t_sms) || + (m_sms == m_msg && state == OFONO_SENT_SMS_STATE_FAILED)) { + message_state_set(msg, ofono_sent_sms_state_get(sms)); + elm_genlist_item_update(message_object_item_get(msg)); + break; + } + } +} + +static void _on_c_genlist_clicked(void *data, Evas_Object *obj __UNUSED__, + void *event_info) +{ + Elm_Object_Item *it = event_info; + Contact_Genlist *c_genlist = data; + Compose *compose = c_genlist->compose; + + elm_genlist_item_selected_set(it, EINA_FALSE); + /* It will be used in _item_filter_cb so we will know if this + * number was added already + */ + elm_multibuttonentry_item_prepend(compose->mb_entry, c_genlist->number, + NULL, NULL); +} + +Contact_Genlist *_contact_genlist_new(const char *name, + const char *number, + const char *type, + const char *picture, + Compose *compose) +{ + Contact_Genlist *c_genlist = calloc(1, sizeof(Contact_Genlist)); + EINA_SAFETY_ON_NULL_RETURN_VAL(c_genlist, NULL); + c_genlist->name = eina_stringshare_add(name); + c_genlist->number = eina_stringshare_add(number); + c_genlist->type = eina_stringshare_add(type); + c_genlist->picture = eina_stringshare_add(picture); + c_genlist->compose = compose; + return c_genlist; +} + +static void _on_text_mb_entry_changed(void *data, Evas_Object *obj, + void *event_info __UNUSED__) +{ + Compose *compose = data; + Contact_Partial_Match *pm; + Eina_List *result, *l; + const char *query; + + query = elm_object_part_text_get(obj, NULL); + + elm_genlist_clear(compose->genlist_contacts); + + if ((!query) || (*query == '\0')) { + elm_object_signal_emit(compose->layout, + "contacts,hidden", "gui"); + return; + } + + DBG("Searching for contact with number: %s", query); + + result = gui_contact_partial_match_search(query); + if (!result) { + DBG("No contact match: '%s'", query); + elm_object_signal_emit(compose->layout, + "contacts,hidden", "gui"); + return; + } + + EINA_LIST_FOREACH(result, l, pm) { + const Contact_Info *c_info; + const char *type, *name, *number, *picture; + Contact_Genlist *c_genlist; + + type = contact_partial_match_type_get(pm); + c_info = contact_partial_match_info_get(pm); + name = contact_info_full_name_get(c_info); + number = contact_info_detail_get(c_info, type); + picture = contact_info_picture_get(c_info); + + c_genlist = _contact_genlist_new(name, number, type, + picture, compose); + EINA_SAFETY_ON_NULL_RETURN(c_genlist); + elm_genlist_item_append(compose->genlist_contacts, + compose->itc_c_name, + c_genlist, NULL, + ELM_GENLIST_ITEM_NONE, + _on_c_genlist_clicked, + c_genlist); + } + contact_partial_match_search_free(result); + + elm_object_signal_emit(compose->layout, "contacts,show", "gui"); +} + +static void _on_item_added(void *data, Evas_Object *obj __UNUSED__, + void *event_info) +{ + Compose *compose = data; + const char *number, *name; + Contact_Info *c_info; + Elm_Object_Item *it = event_info; + + number = elm_object_item_part_text_get(it, NULL); + DBG("Item added number = %s", number); + + elm_object_signal_emit(compose->layout, "contacts,hidden", "gui"); + + if (!number) + return; + + c_info = gui_contact_search(number, NULL); + + if (!c_info) { + DBG("No contact found"); + return; + } + + name = contact_info_full_name_get(c_info); + DBG("Number=%s Name=%s", number, name); + elm_object_item_part_text_set(it, NULL, name); +} + +static Eina_Bool entry_is_number(const char *str) +{ + int i; + + for(i = 0; str[i] != '\0'; i++) { + if (isdigit(str[i]) == 0) + return EINA_FALSE; + } + + return EINA_TRUE; +} + +static Eina_Bool _item_filter_cb(Evas_Object *obj, const char* item_label, + void *item_data __UNUSED__, + void *data) +{ + Evas_Object *entry; + Compose *compose = data; + const char *number; + Eina_List *l; + + /* Did the user entered a phone number? */ + if (entry_is_number(item_label)) { + EINA_LIST_FOREACH(compose->composing_numbers, l, number) { + DBG("current= %s, number= %s", number, item_label); + if (strcmp(number, item_label) == 0) { + entry = elm_multibuttonentry_entry_get(obj); + elm_object_part_text_set(entry, NULL, ""); + DBG("Filtered, number already in."); + return EINA_FALSE; + } + } + number = eina_stringshare_add(item_label); + compose->composing_numbers = + eina_list_append(compose->composing_numbers, number); + return EINA_TRUE; + } + + return EINA_FALSE; +} + +static void _item_c_genlist_del(void *data, Evas_Object *obj __UNUSED__) +{ + Contact_Genlist *c_genlist = data; + + DBG("Deleting item: %p name: %s number: %s", c_genlist, + c_genlist->name, c_genlist->number); + eina_stringshare_del(c_genlist->name); + eina_stringshare_del(c_genlist->number); + eina_stringshare_del(c_genlist->type); + eina_stringshare_del(c_genlist->picture); + free(c_genlist); +} + Evas_Object *compose_add(Evas_Object *parent) { Compose *compose; - Evas_Object *obj; + Evas_Object *obj, *entry, *genlist, *mb_entry; + Evas *e; compose = calloc(1, sizeof(Compose)); EINA_SAFETY_ON_NULL_RETURN_VAL(compose, NULL); @@ -31,9 +808,132 @@ Evas_Object *compose_add(Evas_Object *parent) evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _on_del, compose); + elm_object_signal_callback_add(obj, "clicked,*", "gui", + _on_layout_clicked, compose); + evas_object_data_set(obj, "compose.ctx", compose); + + entry = elm_entry_add(obj); + EINA_SAFETY_ON_NULL_GOTO(entry, err_entry); + elm_object_part_content_set(obj, "elm.swallow.message", entry); + evas_object_smart_callback_add(entry, "changed", _on_text_changed, + compose); + elm_object_style_set(entry, "compose"); + elm_entry_editable_set(entry, EINA_TRUE); + elm_entry_scrollable_set(entry, EINA_TRUE); + compose->entry_msg = entry; + + mb_entry = elm_multibuttonentry_add(obj); + EINA_SAFETY_ON_NULL_GOTO(mb_entry, err_entry); + elm_object_part_content_set(obj, "elm.swallow.destination", mb_entry); + elm_object_style_set(mb_entry, "compose"); + elm_object_text_set(mb_entry, "To: "); + elm_object_part_text_set(mb_entry, "guide", "Tap to add recipient"); + compose->mb_entry = mb_entry; + + evas_object_smart_callback_add(mb_entry, "item,added", + _on_item_added, compose); + /* Don't let equal numbers be added */ + elm_multibuttonentry_item_filter_append(mb_entry, _item_filter_cb, + compose); + + entry = elm_multibuttonentry_entry_get(mb_entry); + EINA_SAFETY_ON_NULL_GOTO(entry, err_entry); + evas_object_smart_callback_add(entry, "changed", + _on_text_mb_entry_changed, + compose); + + genlist = elm_genlist_add(obj); + EINA_SAFETY_ON_NULL_GOTO(genlist, err_genlist); + elm_genlist_mode_set(genlist, ELM_LIST_COMPRESS); + elm_object_style_set(genlist, "compose"); + elm_object_part_content_set(obj, "elm.swallow.genlist", genlist); + compose->genlist = genlist; + + genlist = elm_genlist_add(obj); + EINA_SAFETY_ON_NULL_GOTO(genlist, err_genlist); + elm_object_style_set(genlist, "compose"); + elm_object_part_content_set(obj, "elm.swallow.genlist.contacts", + genlist); + compose->genlist_contacts = genlist; + elm_object_focus_allow_set(genlist, EINA_FALSE); + +#ifdef HAVE_TIZEN + elm_genlist_scroller_policy_set(compose->genlist, + ELM_SCROLLER_POLICY_OFF, + ELM_SCROLLER_POLICY_AUTO); + elm_genlist_scroller_policy_set(compose->genlist_contacts, + ELM_SCROLLER_POLICY_OFF, + ELM_SCROLLER_POLICY_AUTO); +#else + elm_scroller_policy_set(compose->genlist, + ELM_SCROLLER_POLICY_OFF, + ELM_SCROLLER_POLICY_AUTO); + elm_scroller_policy_set(compose->genlist_contacts, + ELM_SCROLLER_POLICY_OFF, + ELM_SCROLLER_POLICY_AUTO); +#endif + + compose->itc_inc = elm_genlist_item_class_new(); + EINA_SAFETY_ON_NULL_GOTO(compose->itc_inc, err_itc); + compose->itc_inc->item_style = "messages-incoming"; + compose->itc_inc->func.text_get = _item_label_get; + compose->itc_inc->func.content_get = _item_content_get; + compose->itc_inc->func.state_get = NULL; + compose->itc_inc->func.del = NULL; + compose->itc_inc->decorate_all_item_style = "incoming-delete"; + compose->itc_inc->decorate_item_style = "incoming-delete"; + + compose->itc_out = elm_genlist_item_class_new(); + EINA_SAFETY_ON_NULL_GOTO(compose->itc_out, err_itc_out); + compose->itc_out->item_style = "messages-outgoing"; + compose->itc_out->func.text_get = _item_label_get; + compose->itc_out->func.content_get = _item_content_get; + compose->itc_out->func.state_get = _item_state_get; + compose->itc_out->func.del = NULL; + compose->itc_out->decorate_all_item_style = "outgoing-delete"; + compose->itc_out->decorate_item_style = "outgoing-delete"; + + compose->itc_c_name = elm_genlist_item_class_new(); + EINA_SAFETY_ON_NULL_GOTO(compose->itc_c_name, err_names); + compose->itc_c_name->item_style = "contacts-compose"; + compose->itc_c_name->func.text_get = _item_c_name_label_get; + compose->itc_c_name->func.content_get = _item_c_name_content_get; + compose->itc_c_name->func.state_get = NULL; + compose->itc_c_name->func.del = _item_c_genlist_del; + + elm_object_part_text_set(compose->layout, "elm.text.name", + "New Message"); + elm_object_signal_emit(compose->layout, "hide,genlist", "gui"); + compose->composing = EINA_TRUE; + elm_object_signal_emit(compose->layout, "composing", "gui"); + + incoming_sms = ofono_incoming_sms_cb_add(_incoming_sms_cb, compose); + sent_sms = ofono_sent_sms_changed_cb_add(_sent_sms_cb, compose); + + evas_object_event_callback_add(obj, EVAS_CALLBACK_HIDE, _on_hide, + compose); + evas_object_event_callback_add(obj, EVAS_CALLBACK_SHOW, _on_show, + compose); + + e = evas_object_evas_get(obj); + evas_event_callback_add(e, EVAS_CALLBACK_CANVAS_FOCUS_OUT, + _on_win_focus_out, compose); + evas_event_callback_add(e, EVAS_CALLBACK_CANVAS_FOCUS_IN, + _on_win_focus_in, compose); + return obj; +err_names: + elm_genlist_item_class_free(compose->itc_out); +err_itc_out: + elm_genlist_item_class_free(compose->itc_inc); +err_itc: + evas_object_del(genlist); +err_genlist: + evas_object_del(entry); +err_entry: + evas_object_del(obj); err_obj: free(compose); return NULL; @@ -53,3 +953,54 @@ void compose_set(Evas_Object *obj, const char *number, const char *message, ERR("TODO '%s' '%s' %d", number, message, do_auto); } + +void compose_messages_set(Evas_Object *obj, Eina_List *list, const char *number) +{ + Compose *compose; + Message *msg; + Eina_List *l; + Elm_Genlist_Item_Class *itc; + Elm_Object_Item *it = NULL; + Contact_Info *c_info; + + EINA_SAFETY_ON_NULL_RETURN(obj); + compose = evas_object_data_get(obj, "compose.ctx"); + EINA_SAFETY_ON_NULL_RETURN(compose); + + eina_stringshare_replace(&(compose->number), number); + + elm_genlist_clear(compose->genlist); + + EINA_LIST_FOREACH(list, l, msg) { + Eina_Bool outgoing = message_outgoing_get(msg); + + if (outgoing) + itc = compose->itc_out; + else + itc = compose->itc_inc; + message_data_set(msg, compose); + it = elm_genlist_item_append(compose->genlist, itc, msg, NULL, + ELM_GENLIST_ITEM_NONE, NULL, + NULL); + message_object_item_set(msg, it); + message_ref(msg); + } + if (it) + elm_genlist_item_show(it, ELM_GENLIST_ITEM_SCROLLTO_IN); + + compose->current_thread = list; + elm_object_signal_emit(compose->layout, "show,genlist", "gui"); + + c_info = gui_contact_search(number, NULL); + + if (!c_info) + elm_object_part_text_set(compose->layout, "elm.text.name", + number); + else + elm_object_part_text_set(compose->layout, "elm.text.name", + contact_info_full_name_get(c_info)); + + compose->composing = EINA_FALSE; + _compose_timer_updater_start(compose); + elm_object_signal_emit(compose->layout, "viewing", "gui"); +} diff --git a/messages/compose.h b/messages/compose.h index 5da0083..72a7ea1 100644 --- a/messages/compose.h +++ b/messages/compose.h @@ -6,4 +6,6 @@ Evas_Object *compose_add(Evas_Object *parent); void compose_set(Evas_Object *obj, const char *number, const char *message, Eina_Bool do_auto); +void compose_messages_set(Evas_Object *obj, Eina_List *list, const char *number); + #endif diff --git a/messages/gui.c b/messages/gui.c index 86a2173..20d75f4 100644 --- a/messages/gui.c +++ b/messages/gui.c @@ -8,6 +8,7 @@ #include "compose.h" #include "util.h" #include "simple-popup.h" +#include "contacts-ofono-efl.h" #ifdef HAVE_TIZEN #include @@ -19,11 +20,43 @@ static Evas_Object *win = NULL; static Evas_Object *main_layout = NULL; static Evas_Object *cs = NULL; static Evas_Object *flip = NULL; +static Evas_Object *ov = NULL; +static Evas_Object *contacts = NULL; /* XXX elm_flip should just do the right thing, but it does not */ static Eina_Bool in_compose = EINA_FALSE; static Eina_Bool in_flip_anim = EINA_FALSE; +Eina_List * gui_contact_partial_match_search(const char *query) +{ + return contact_partial_match_search(contacts, query); +} + +void gui_overview_genlist_update(Message *msg, const char *contact) +{ + overview_genlist_update(ov, msg, contact); +} + +void gui_overview_all_contact_messages_clear(const char *contact) +{ + overview_all_contact_messages_clear(ov, contact); +} + +void gui_message_from_file_delete(Message *msg, const char *contact) +{ + overview_message_from_file_delete(ov, msg, contact); +} + +void gui_compose_messages_set(Eina_List *list, const char *number) +{ + compose_messages_set(cs, list, number); +} + +Contact_Info *gui_contact_search(const char *number, const char **type) +{ + return contact_search(contacts, number, type); +} + Evas_Object *gui_simple_popup(const char *title, const char *message) { return simple_popup_add(win, title, message); @@ -86,13 +119,11 @@ static void _on_clicked(void *data __UNUSED__, Evas_Object *o __UNUSED__, EINA_SAFETY_ON_FALSE_RETURN(eina_str_has_prefix(emission, "clicked,")); emission += strlen("clicked,"); - - ERR("TODO: %s", emission); } Eina_Bool gui_init(void) { - Evas_Object *lay, *obj; + Evas_Object *lay, *obj, *conform; Evas_Coord w, h; /* messages should never, ever quit */ @@ -101,6 +132,7 @@ Eina_Bool gui_init(void) win = elm_win_util_standard_add("ofono-messages", "oFono Messages"); EINA_SAFETY_ON_NULL_RETURN_VAL(win, EINA_FALSE); elm_win_autodel_set(win, EINA_FALSE); + elm_win_conformant_set(win, EINA_TRUE); #ifdef HAVE_TIZEN appcore_set_i18n("ofono-efl", "en-US"); @@ -127,14 +159,29 @@ Eina_Bool gui_init(void) elm_object_signal_callback_add(lay, "clicked,*", "gui", _on_clicked, NULL); + contacts = obj = contacts_add(win); + EINA_SAFETY_ON_NULL_RETURN_VAL(contacts, EINA_FALSE); + cs = obj = compose_add(win); EINA_SAFETY_ON_NULL_RETURN_VAL(obj, EINA_FALSE); evas_object_size_hint_weight_set(obj, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); evas_object_size_hint_align_set(obj, EVAS_HINT_FILL, EVAS_HINT_FILL); - elm_object_part_content_set(flip, "back", obj); evas_object_show(obj); + ov = obj = overview_add(win); + EINA_SAFETY_ON_NULL_RETURN_VAL(obj, EINA_FALSE); + elm_object_part_content_set(lay, "elm.swallow.overview", obj); + evas_object_show(ov); + + conform = elm_conformant_add(win); + EINA_SAFETY_ON_NULL_RETURN_VAL(conform, EINA_FALSE); + elm_win_resize_object_add(win, conform); + evas_object_size_hint_weight_set(conform, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_show(conform); + elm_object_content_set(conform, cs); + elm_object_part_content_set(flip, "back", conform); + /* TODO: make it match better with Tizen: icon and other properties */ obj = elm_layout_edje_get(lay); edje_object_size_min_get(obj, &w, &h); diff --git a/messages/gui.h b/messages/gui.h index 8e3e2db..b9207a5 100644 --- a/messages/gui.h +++ b/messages/gui.h @@ -3,6 +3,8 @@ #include "contacts-ofono-efl.h" #include "ofono.h" +#include "overview.h" +#include Evas_Object *gui_simple_popup(const char *title, const char *message); @@ -15,4 +17,16 @@ void gui_compose_exit(void); Eina_Bool gui_init(void); void gui_shutdown(void); +Contact_Info *gui_contact_search(const char *number, const char **type); + +void gui_compose_messages_set(Eina_List *list, const char *number); + +void gui_message_from_file_delete(Message *msg, const char *contact); + +void gui_overview_genlist_update(Message *msg, const char *contact); + +void gui_overview_all_contact_messages_clear(const char *contact); + +Eina_List *gui_contact_partial_match_search(const char *query); + #endif diff --git a/messages/overview.c b/messages/overview.c new file mode 100644 index 0000000..795aec5 --- /dev/null +++ b/messages/overview.c @@ -0,0 +1,1162 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include "overview.h" +#include "log.h" +#include "util.h" +#include "gui.h" +#include "contacts-ofono-efl.h" + +#define ALL_MESSAGES "all_messages" + +#ifndef EET_COMPRESSION_DEFAULT +#define EET_COMPRESSION_DEFAULT 1 +#endif + +/* This struct hold the content of the messages that will be showed + * in the main screen + */ +typedef struct _Messages_List +{ + Eina_List *list; + Eina_Bool dirty; /* not in eet */ + Ecore_Poller *save_poller; /* not in eet */ +} Messages_List; + +struct _Message +{ + const char *content; + unsigned char outgoing; + unsigned char state; + long long time; + const char *phone; /* phone number - not in eet */ + int refcount; /* not in eet */ + void *data; /* not in eet */ + Elm_Object_Item *it; /* not in eet */ +}; + +typedef struct _Overview +{ + Eet_Data_Descriptor *edd_msg_info; + Eet_Data_Descriptor *edd_msg_list; + Eet_Data_Descriptor *edd_msg; + Eet_Data_Descriptor *edd_c_msg; + Messages_List *messages; + /* Pending conversations, not saved in eet yet */ + Messages_List *p_conversations; + Evas_Object *layout, *genlist; + char *msg_path, *base_dir, *msg_bkp; + Ecore_Poller *updater; + double last_update; + Elm_Genlist_Item_Class *itc; + Eina_Hash *pending_sms; +} Overview; + +/* Messages showed in the main screen */ +typedef struct _Message_Info +{ + const char *sender, *last_msg; + long long time; + int count; + Overview *ov; /*not in eet */ + Elm_Object_Item *it; /* not in eet */ +} Message_Info; + +static OFono_Callback_List_Incoming_SMS_Node *incoming_sms = NULL; +static OFono_Callback_List_Sent_SMS_Node *sent_sms = NULL; + +static void _overview_messages_save(Overview *ov); +static Message_Info *_message_info_search(Overview *ov, const char *sender); +static void _message_info_del(Message_Info *m_info); +static void _overview_messages_save(Overview *ov); +static void _conversation_eet_update_save(Overview *ov, Message *msg); +static void _message_free(Message *msg); + +void message_ref(Message *msg) +{ + EINA_SAFETY_ON_NULL_RETURN(msg); + msg->refcount++; +} + +static Eina_Bool _overview_messages_save_do(void *data) +{ + Overview *ov = data; + Eet_File *efile; + EINA_SAFETY_ON_NULL_RETURN_VAL(ov->messages, ECORE_CALLBACK_DONE); + + ov->messages->save_poller = NULL; + ov->messages->dirty = EINA_FALSE; + + ecore_file_unlink(ov->msg_bkp); + ecore_file_mv(ov->msg_path, ov->msg_bkp); + efile = eet_open(ov->msg_path, EET_FILE_MODE_WRITE); + EINA_SAFETY_ON_NULL_GOTO(efile, failed); + + if (!(eet_data_write(efile, + ov->edd_msg_list, ALL_MESSAGES, + ov->messages, EET_COMPRESSION_DEFAULT))) + ERR("Could in the messages log file"); + + eet_close(efile); + return ECORE_CALLBACK_DONE; + +failed: + ecore_file_mv(ov->msg_bkp, ov->msg_path); + return ECORE_CALLBACK_DONE; +} + +void overview_genlist_update(Evas_Object *obj, Message *msg, + const char *contact) +{ + Overview *ov; + Message_Info *m_info; + + EINA_SAFETY_ON_NULL_RETURN(obj); + ov = evas_object_data_get(obj, "overview.ctx"); + EINA_SAFETY_ON_NULL_RETURN(ov); + EINA_SAFETY_ON_NULL_RETURN(msg); + + m_info = _message_info_search(ov, contact); + EINA_SAFETY_ON_NULL_RETURN(m_info); + + m_info->time = msg->time; + eina_stringshare_replace(&m_info->last_msg, msg->content); + _overview_messages_save(ov); + elm_genlist_item_update(m_info->it); +} + +void message_object_item_set(Message *msg, Elm_Object_Item *it) +{ + EINA_SAFETY_ON_NULL_RETURN(msg); + msg->it = it; +} + +Elm_Object_Item *message_object_item_get(const Message *msg) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(msg, NULL); + return msg->it; +} + +void message_state_set(Message *msg, unsigned char state) +{ + EINA_SAFETY_ON_NULL_RETURN(msg); + msg->state = state; +} + +void *message_data_get(const Message *msg) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(msg, NULL); + return msg->data; +} + +void message_data_set(Message *msg, void *data) +{ + EINA_SAFETY_ON_NULL_RETURN(msg); + msg->data = data; +} + +static Eina_Bool _message_is_equal(Message *m1, Message *m2) +{ + if (strcmp(m1->content, m2->content) != 0) + return EINA_FALSE; + + if (m1->outgoing != m2->outgoing) + return EINA_FALSE; + + if (m1->state != m2->state) + return EINA_FALSE; + + if (m1->time != m2->time) + return EINA_FALSE; + + return EINA_TRUE; +} + +void overview_message_from_file_delete(Evas_Object *obj, Message *msg, + const char *contact) +{ + Eet_File *efile; + Overview *ov; + char buf[PATH_MAX], bkp[PATH_MAX]; + Messages_List *messages = NULL; + Eina_List *l; + Message *msg_aux; + + EINA_SAFETY_ON_NULL_RETURN(obj); + ov = evas_object_data_get(obj, "overview.ctx"); + EINA_SAFETY_ON_NULL_RETURN(ov); + EINA_SAFETY_ON_NULL_RETURN(msg); + + snprintf(buf, sizeof(buf), "%s/%s.eet", ov->base_dir, contact); + snprintf(bkp, sizeof(bkp), "%s/%s.eet.bkp", ov->base_dir, contact); + + efile = eet_open(buf, EET_FILE_MODE_READ_WRITE); + + if (efile) { + messages = eet_data_read(efile, ov->edd_c_msg, ALL_MESSAGES); + } + + if (!messages) { + efile = eet_open(bkp, EET_FILE_MODE_READ_WRITE); + if (efile) { + messages = eet_data_read(efile, ov->edd_c_msg, + ALL_MESSAGES); + } + } + + if (!messages) + return; + + EINA_LIST_FOREACH(messages->list, l, msg_aux) { + if (_message_is_equal(msg, msg_aux)) { + _message_free(msg_aux); + messages->list = eina_list_remove_list(messages->list, + l); + break; + } + } + Message_Info *m_info = _message_info_search(ov, contact); + EINA_SAFETY_ON_NULL_RETURN(m_info); + m_info->count--; + if (messages->list && eina_list_count(messages->list) != 0) { + eet_data_write(efile, ov->edd_c_msg, ALL_MESSAGES, messages, + EET_COMPRESSION_DEFAULT); + eet_close(efile); + } else { + eet_close(efile); + _message_info_del(m_info); + } + + EINA_LIST_FREE(messages->list, msg_aux) + _message_free(msg_aux); + free(messages); +} + +unsigned char message_state_get(const Message *msg) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(msg, '\0'); + return msg->state; +} + +const char * message_content_get(const Message *msg) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(msg, NULL); + return msg->content; +} + +const char * message_phone_get(const Message *msg) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(msg, NULL); + return msg->phone; +} + +unsigned char message_outgoing_get(const Message *msg) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(msg, '\0'); + return msg->outgoing; +} + +long long message_time_get(const Message *msg) +{ + + EINA_SAFETY_ON_NULL_RETURN_VAL(msg, -1); + return msg->time; +} + +static Message_Info *_message_info_search(Overview *ov, const char *sender) +{ + Eina_List *l; + Message_Info *m_info; + + EINA_SAFETY_ON_NULL_RETURN_VAL(ov->messages, NULL); + + EINA_LIST_FOREACH(ov->messages->list, l, m_info) { + if (strcmp(sender, m_info->sender) == 0) + return m_info; + } + + return NULL; +} + +static void _message_free(Message *msg) +{ + DBG("MSG=%p", msg); + eina_stringshare_del(msg->content); + eina_stringshare_del(msg->phone); + free(msg); +} + +void message_del(Message *msg) +{ + + EINA_SAFETY_ON_NULL_RETURN(msg); + DBG("MSG=%p, refcount=%d", msg, msg->refcount); + EINA_SAFETY_ON_TRUE_RETURN(msg->refcount <= 0); + msg->refcount--; + + if (msg->refcount == 0) + _message_free(msg); +} + +static void _message_info_free(Message_Info *m_info) +{ + eina_stringshare_del(m_info->sender); + eina_stringshare_del(m_info->last_msg); + free(m_info); +} + +static void _messages_file_delete(char *dir, const char *sender) +{ + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/%s.eet", dir, sender); + ecore_file_unlink(path); + snprintf(path, sizeof(path), "%s/%s.eet.bkp", dir, sender); + ecore_file_unlink(path); +} + +static void _message_info_del(Message_Info *m_info) +{ + Overview *ctx = m_info->ov; + Eina_List *l, *next; + Message *msg; + + EINA_SAFETY_ON_NULL_RETURN(ctx); + + elm_object_item_del(m_info->it); + + ctx->messages->list = eina_list_remove(ctx->messages->list, m_info); + ctx->messages->dirty = EINA_TRUE; + + /* Remove unsaved SMSs */ + EINA_LIST_FOREACH_SAFE(ctx->p_conversations->list, l, next, msg) { + if (msg->phone == m_info->sender) { + ctx->p_conversations->list = + eina_list_remove(ctx->p_conversations->list, + msg); + message_del(msg); + } + } + + _messages_file_delete(ctx->base_dir, m_info->sender); + _overview_messages_save(ctx); + + if ((!ctx->messages->list) && (ctx->updater)) { + ecore_poller_del(ctx->updater); + ctx->updater = NULL; + } + + _message_info_free(m_info); +} + +static void _on_del(void *data, Evas *e __UNUSED__, + Evas_Object *obj __UNUSED__, void *event __UNUSED__) +{ + Overview *ov = data; + Message_Info *m_info; + Message *msg; + + ofono_incoming_sms_cb_del(incoming_sms); + ofono_sent_sms_changed_cb_del(sent_sms); + + if (ov->messages->save_poller) + ecore_poller_del(ov->messages->save_poller); + + if (ov->p_conversations->save_poller) + ecore_poller_del(ov->p_conversations->save_poller); + + if (ov->messages->dirty) + _overview_messages_save_do(ov); + + eina_hash_free(ov->pending_sms); + + if (!ov->p_conversations->dirty) { + EINA_LIST_FREE(ov->p_conversations->list, msg) + message_del(msg); + } else { + EINA_LIST_FREE(ov->p_conversations->list, msg) { + _conversation_eet_update_save(ov, msg); + message_del(msg); + } + } + + EINA_LIST_FREE(ov->messages->list, m_info) + _message_info_free(m_info); + + eet_data_descriptor_free(ov->edd_msg_info); + eet_data_descriptor_free(ov->edd_msg_list); + eet_data_descriptor_free(ov->edd_msg); + eet_data_descriptor_free(ov->edd_c_msg); + + elm_genlist_item_class_free(ov->itc); + free(ov->messages); + free(ov->msg_path); + free(ov->base_dir); + free(ov->msg_bkp); + free(ov->p_conversations); + free(ov); + + eet_shutdown(); + ecore_file_shutdown(); +} + +static void _eet_descriptors_init(Eet_Data_Descriptor **messages, + Eet_Data_Descriptor **msg_info, + Eet_Data_Descriptor **edd_msg, + Eet_Data_Descriptor **edd_c_msg) +{ + Eet_Data_Descriptor_Class eddc; + Eet_Data_Descriptor_Class eddcM; + + EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Message_Info); + *msg_info = eet_data_descriptor_stream_new(&eddc); + + EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddc, Messages_List); + *messages = eet_data_descriptor_stream_new(&eddc); + + EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddcM, Message); + *edd_msg = eet_data_descriptor_stream_new(&eddcM); + + EET_EINA_STREAM_DATA_DESCRIPTOR_CLASS_SET(&eddcM, Messages_List); + *edd_c_msg = eet_data_descriptor_stream_new(&eddcM); + + EET_DATA_DESCRIPTOR_ADD_BASIC(*edd_msg, Message, + "content", content, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(*edd_msg, Message, + "time", time, EET_T_LONG_LONG); + EET_DATA_DESCRIPTOR_ADD_BASIC(*edd_msg, Message, + "outgoing", outgoing, EET_T_UCHAR); + EET_DATA_DESCRIPTOR_ADD_BASIC(*edd_msg, Message, + "state", state, EET_T_UCHAR); + + EET_DATA_DESCRIPTOR_ADD_BASIC(*msg_info, Message_Info, + "sender", sender, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(*msg_info, Message_Info, + "last_msg", last_msg, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(*msg_info, Message_Info, + "time", time, EET_T_LONG_LONG); + EET_DATA_DESCRIPTOR_ADD_BASIC(*msg_info, Message_Info, + "count", count, EET_T_INT); + + EET_DATA_DESCRIPTOR_ADD_LIST(*messages, Messages_List, "list", list, + *msg_info); + + EET_DATA_DESCRIPTOR_ADD_LIST(*edd_c_msg, Messages_List, "list", list, + *edd_msg); + +} + +static void _overview_messages_save(Overview *ov) +{ + if (ov->messages->save_poller) + return; + ov->messages->save_poller = ecore_poller_add(ECORE_POLLER_CORE, 32, + _overview_messages_save_do, + ov); +} + +static void _on_item_clicked(void *data, Evas_Object *obj __UNUSED__, + void *event_info) +{ + Message_Info *m_info = data; + Elm_Object_Item *it = event_info; + char buf[PATH_MAX], bkp[PATH_MAX]; + Overview *ov = m_info->ov; + Eet_File *efile; + Messages_List *messages = NULL; + + elm_genlist_item_selected_set(it, EINA_FALSE); + + snprintf(buf, sizeof(buf), "%s/%s.eet", ov->base_dir, m_info->sender); + snprintf(bkp, sizeof(bkp), "%s/%s.eet.bkp", ov->base_dir, + m_info->sender); + + efile = eet_open(buf, EET_FILE_MODE_READ); + + if (efile) { + messages = eet_data_read(efile, ov->edd_c_msg, ALL_MESSAGES); + eet_close(efile); + } + + if (!messages) { + efile = eet_open(bkp, EET_FILE_MODE_READ); + if (efile) { + messages = eet_data_read(efile, ov->edd_c_msg, + ALL_MESSAGES); + eet_close(efile); + } + } + if (messages) { + gui_compose_messages_set(messages->list, m_info->sender); + gui_compose_enter(); + /* Compose will free the messages->list for me */ + free(messages); + } else + INF("Could not read the messages list!"); +} + +static void _overview_messages_read(Overview *ov) +{ + Eet_File *efile; + Messages_List *msg_list = NULL; + Eina_List *l; + Message_Info *m_info; + Elm_Object_Item *it; + + efile = eet_open(ov->msg_path, EET_FILE_MODE_READ); + + if (efile) { + msg_list = eet_data_read(efile, ov->edd_msg_list, ALL_MESSAGES); + eet_close(efile); + } + + if (!msg_list) { + efile = eet_open(ov->msg_bkp, EET_FILE_MODE_READ); + if (efile) { + msg_list = eet_data_read(efile, ov->edd_msg_list, + ALL_MESSAGES); + eet_close(efile); + } + } + + if (!msg_list) + msg_list = calloc(1, sizeof(Messages_List)); + + ov->messages = msg_list; + EINA_SAFETY_ON_NULL_RETURN(ov->messages); + + EINA_LIST_FOREACH(ov->messages->list, l, m_info) { + it = elm_genlist_item_append(ov->genlist, + ov->itc, m_info, NULL, + ELM_GENLIST_ITEM_NONE, + _on_item_clicked, m_info); + m_info->ov = ov; + m_info->it = it; + } +} + +static Eina_Bool _overview_time_updater(void *data) +{ + Overview *ctx = data; + double now = ecore_loop_time_get(); + const double interval_threshold = 30.0; + Elm_Object_Item *it; + const long long update_threshold = time(NULL) - WEEK - DAY; + + if (!ctx->messages->list) { + ctx->updater = NULL; + return EINA_FALSE; + } + + if (now - ctx->last_update < interval_threshold) + return EINA_TRUE; + ctx->last_update = now; + + it = elm_genlist_first_item_get(ctx->genlist); + for (; it != NULL; it = elm_genlist_item_next_get(it)) { + const Message_Info *m_info = elm_object_item_data_get(it); + long long t = m_info->time; + if (EINA_UNLIKELY(t == 0)) { + t = m_info->time; + } + if (EINA_UNLIKELY(t < update_threshold)) + break; + elm_genlist_item_update(it); + } + + return EINA_TRUE; +} + +static void _overview_timer_updater_start(Overview *ov) +{ + Evas *e = evas_object_evas_get(ov->layout); + Eina_Bool win_focused = evas_focus_state_get(e); + Eina_Bool obj_visible = evas_object_visible_get(ov->layout); + + DBG("poller %p, win_focused=%hhu, obj_visible=%hhu", + ov->updater, win_focused, obj_visible); + if (ov->updater) + return; + if (!ov->messages->list) + return; + if ((!win_focused) || (!obj_visible)) + return; + + DBG("start poller messages"); + /* ECORE_POLLER_CORE is 1/8th of second. */ + ov->updater = ecore_poller_add(ECORE_POLLER_CORE, 8 * 60, + _overview_time_updater, + ov); + _overview_time_updater(ov); +} + +static void _overview_time_updater_stop(Overview *ov) +{ + Evas *e = evas_object_evas_get(ov->layout); + Eina_Bool win_focused = evas_focus_state_get(e); + Eina_Bool obj_visible = evas_object_visible_get(ov->layout); + + DBG("poller %p, win_focused=%hhu, obj_visible=%hhu", + ov->updater, win_focused, obj_visible); + if (!ov->updater) + return; + if (win_focused && obj_visible) + return; + + DBG("delete poller %p", ov->updater); + ecore_poller_del(ov->updater); + ov->updater = NULL; +} + +static Message *_message_list_search(Eina_List *list, Message *msg) +{ + Eina_List *l; + Message *m; + + EINA_LIST_FOREACH(list, l, m) { + if ((m->content == msg->content && + m->time == msg->time) || + (msg->content == m->content && m->state != msg->state)) + return m; + } + + return NULL; +} + +static void _conversation_eet_update_save(Overview *ov, Message *msg) +{ + + Eet_File *efile; + char buf[PATH_MAX], bkp[PATH_MAX]; + Messages_List *messages = NULL; + Message *msg_aux; + + snprintf(buf, sizeof(buf), "%s/%s.eet", ov->base_dir, msg->phone); + snprintf(bkp, sizeof(bkp), "%s/%s.eet.bkp", ov->base_dir, msg->phone); + + efile = eet_open(buf, EET_FILE_MODE_READ); + + if (efile) { + messages = eet_data_read(efile, ov->edd_c_msg, ALL_MESSAGES); + eet_close(efile); + } + + if (!messages) { + efile = eet_open(bkp, EET_FILE_MODE_READ); + if (efile) { + messages = eet_data_read(efile, ov->edd_c_msg, + ALL_MESSAGES); + eet_close(efile); + } + } + + /* New file */ + if (!messages) { + messages = calloc(1, sizeof(Messages_List)); + EINA_SAFETY_ON_NULL_RETURN(messages); + } + + msg_aux = _message_list_search(messages->list, msg); + + if (!msg_aux) { + messages->list = eina_list_append(messages->list, msg); + msg->refcount++; + } else + msg_aux->state = msg->state; + + ecore_file_unlink(bkp); + ecore_file_mv(buf, bkp); + + efile = eet_open(buf, EET_FILE_MODE_WRITE); + EINA_SAFETY_ON_NULL_RETURN(efile); + + eet_data_write(efile, ov->edd_c_msg, ALL_MESSAGES, messages, + EET_COMPRESSION_DEFAULT); + + eet_close(efile); + EINA_LIST_FREE(messages->list, msg_aux) { + if (msg_aux == msg) + message_del(msg); + else /* messages read from eet have no refcount */ + _message_free(msg_aux); + } + free(messages); +} + +static Eina_Bool _conversation_eet_update_do(void *data) +{ + Overview *ov = data; + Message *msg; + + ov->p_conversations->dirty = EINA_FALSE; + ov->p_conversations->save_poller = NULL; + + EINA_LIST_FREE(ov->p_conversations->list, msg) { + _conversation_eet_update_save(ov, msg); + message_del(msg); + } + ov->p_conversations->list = NULL; + return ECORE_CALLBACK_DONE; +} + +static void _conversation_eet_update(Overview *ov) +{ + if (ov->p_conversations->save_poller) + return; + + ov->p_conversations->save_poller = ecore_poller_add(ECORE_POLLER_CORE, + 32, + _conversation_eet_update_do, + ov); +} + +static void _message_info_genlist_update(Overview *ov, time_t timestamp, + const char *sender, + const char *message) +{ + Message_Info *m_info; + Elm_Object_Item *it; + + m_info = _message_info_search(ov, sender); + + if (!m_info) { + m_info = calloc(1, sizeof(Message_Info)); + EINA_SAFETY_ON_NULL_RETURN(m_info); + m_info->sender = eina_stringshare_add(sender); + ov->messages->list = eina_list_prepend(ov->messages->list, + m_info); + m_info->ov = ov; + } else { + ov->messages->list = eina_list_remove(ov->messages->list, + m_info); + ov->messages->list = eina_list_prepend(ov->messages->list, + m_info); + if (m_info->it) + elm_object_item_del(m_info->it); + } + + m_info->count++; + m_info->time = timestamp; + eina_stringshare_replace(&m_info->last_msg, message); + ov->messages->dirty = EINA_TRUE; + + it = elm_genlist_item_prepend(ov->genlist, + ov->itc, m_info, NULL, + ELM_GENLIST_ITEM_NONE, + _on_item_clicked, m_info); + m_info->it = it; + + elm_genlist_item_show(m_info->it, ELM_GENLIST_ITEM_SCROLLTO_TOP); + _overview_timer_updater_start(ov); + _overview_messages_save(ov); +} + +Message *message_new(time_t timestamp, const char *content, + Eina_Bool outgoing, OFono_Sent_SMS_State state) +{ + Message *msg; + + msg = calloc(1, sizeof(Message)); + EINA_SAFETY_ON_NULL_RETURN_VAL(msg, NULL); + msg->time = timestamp; + msg->outgoing = outgoing; + msg->content = eina_stringshare_add(content); + msg->state = state; + msg->refcount++; + return msg; +} + +static void _incoming_sms_cb(void *data, unsigned int sms_class, + time_t timestamp, const char *sender, + const char *message) +{ + Overview *ov = data; + Message *msg; + + /* Users can only send class 1. This is OFono/GSM detail */ + if (sms_class != 1) + return; + + msg = message_new(timestamp, message, EINA_FALSE, + OFONO_SENT_SMS_STATE_SENT); + EINA_SAFETY_ON_NULL_RETURN(msg); + msg->phone = eina_stringshare_add(sender); + + _message_info_genlist_update(ov, timestamp, sender, message); + + ov->p_conversations->list = eina_list_append(ov->p_conversations->list, + msg); + ov->p_conversations->dirty = EINA_TRUE; + _conversation_eet_update(ov); +} + +static void _on_show(void *data, Evas *e __UNUSED__, + Evas_Object *obj __UNUSED__, void *event __UNUSED__) +{ + Overview *ov = data; + DBG("Overview became visible"); + _overview_timer_updater_start(ov); +} + +static void _on_win_focus_in(void *data, Evas *e __UNUSED__, + void *event_info __UNUSED__) +{ + Overview *ov = data; + DBG("window is focused"); + _overview_timer_updater_start(ov); +} + +static void _on_win_focus_out(void *data, Evas *e __UNUSED__, + void *event_info __UNUSED__) +{ + Overview *ov = data; + DBG("window is unfocused"); + _overview_time_updater_stop(ov); +} + +static void _on_hide(void *data, Evas *e __UNUSED__, + Evas_Object *obj __UNUSED__, void *event __UNUSED__) +{ + Overview *ov = data; + DBG("Overview became hidden"); + _overview_time_updater_stop(ov); +} + +static char *_item_label_get(void *data, Evas_Object *obj __UNUSED__, + const char *part) +{ + Message_Info *m_info = data; + + if (strncmp(part, "elm.text.", strlen("elm.text."))) + return NULL; + part += strlen("elm.text."); + + if (strcmp(part, "name") == 0) { + Contact_Info *c_info = gui_contact_search(m_info->sender, NULL); + + if (!c_info) + return strdup(m_info->sender); + else + return strdup(contact_info_full_name_get(c_info)); + } + + if (strcmp(part, "content") == 0) + return elm_entry_utf8_to_markup(m_info->last_msg); + + if (strcmp(part, "time") == 0) { + time_t time_msg = m_info->time; + return date_short_format(time_msg); + } + + ERR("Unexpected text part: %s", part); + return NULL; +} + +static void _on_more_clicked(void *data __UNUSED__, Evas_Object *obj __UNUSED__, + const char *emission __UNUSED__, + const char *source __UNUSED__) +{ + DBG("TODO - ON MORE CLICKED"); +} + +static void _on_del_clicked(void *data, Evas_Object *obj __UNUSED__, + void *event_info __UNUSED__) +{ + Message_Info *m_info = data; + _message_info_del(m_info); +} + +static Evas_Object *_item_content_get(void *data, Evas_Object *obj, + const char *part) +{ + Evas_Object *btn = NULL; + Message_Info *msg = data; + + if (strncmp(part, "elm.swallow.", strlen("elm.swallow."))) + return NULL; + part += strlen("elm.swallow."); + + if (strcmp(part, "more") == 0) { + /* We can use the same button here */ + btn = layout_add(obj, "history/img"); + EINA_SAFETY_ON_NULL_RETURN_VAL(btn, NULL); + elm_object_signal_callback_add(btn, "clicked,more", "gui", + _on_more_clicked, msg); + } else if (strcmp(part, "delete") == 0) { + btn = elm_button_add(obj); + EINA_SAFETY_ON_NULL_RETURN_VAL(btn, NULL); + elm_object_style_set(btn, "history-delete"); + elm_object_text_set(btn, "delete"); + evas_object_smart_callback_add(btn, "clicked", _on_del_clicked, + msg); + evas_object_propagate_events_set(btn, EINA_FALSE); + } + else + ERR("unknown content part '%s'", part); + + return btn; +} + +static void _on_list_slide_enter(void *data __UNUSED__, + Evas_Object *obj, + void *event_info) +{ + Elm_Object_Item *it = elm_genlist_decorated_item_get(obj); + DBG("cancel decorated item=%p", it); + if (it) + elm_genlist_item_decorate_mode_set(it, "slide", EINA_FALSE); + + it = event_info; + EINA_SAFETY_ON_NULL_RETURN(it); + DBG("it=%p", it); + elm_genlist_item_decorate_mode_set(it, "slide", EINA_TRUE); +} + +static void _on_list_slide_cancel(void *data __UNUSED__, + Evas_Object *obj, + void *event_info __UNUSED__) +{ + Elm_Object_Item *it = elm_genlist_decorated_item_get(obj); + DBG("it=%p", it); + if (it) + elm_genlist_item_decorate_mode_set(it, "slide", EINA_FALSE); +} + +void _overview_messages_clear(Overview *ov) +{ + Message_Info *m_info; + Eina_List *l, *l_next; + + EINA_LIST_FOREACH_SAFE(ov->messages->list, l, l_next, m_info) + _message_info_del(m_info); +} + +static void _on_layout_clicked(void *data, Evas_Object *o, + const char *emission, const char *source __UNUSED__) +{ + Overview *ov = data; + DBG("signal: %s", emission); + + EINA_SAFETY_ON_FALSE_RETURN(eina_str_has_prefix(emission, "clicked,")); + emission += strlen("clicked,"); + + if (strcmp(emission, "edit") == 0) { + elm_object_signal_emit(o, "toggle,on,edit", "gui"); + elm_genlist_decorate_mode_set(ov->genlist, EINA_TRUE); + } else if (strcmp(emission, "compose") == 0) { + elm_object_signal_emit(o, "toggle,on,compose", "gui"); + elm_object_signal_emit(o, "toggle,off,view", "gui"); + gui_compose_enter(); + elm_object_signal_emit(o, "toggle,off,compose", "gui"); + elm_object_signal_emit(o, "toggle,on,view", "gui"); + } else if (strcmp(emission, "view") == 0) { + elm_object_signal_emit(o, "toggle,off,compose", "gui"); + elm_object_signal_emit(o, "toggle,on,view", "gui"); + } else if (strcmp(emission, "edit,done") == 0) { + elm_object_signal_emit(o, "toggle,off,edit", "gui"); + elm_genlist_decorate_mode_set(ov->genlist, EINA_FALSE); + } else if (strcmp(emission, "clear") == 0) { + _overview_messages_clear(ov); + elm_object_signal_emit(o, "toggle,off,edit", "gui"); + } else + ERR("Unkown emission: %s", emission); +} + +static void _sent_sms_cb(void *data, OFono_Error error, OFono_Sent_SMS *sms) +{ + Overview *ov = data; + Message *msg; + OFono_Sent_SMS_State state; + time_t timestamp; + const char *dest, *message; + + if (error != OFONO_ERROR_NONE) { + ERR("OFono error - Sending a SMS"); + return; + } + + state = ofono_sent_sms_state_get(sms); + dest = ofono_sent_sms_destination_get(sms); + message = ofono_sent_sms_message_get(sms); + msg = eina_hash_find(ov->pending_sms, sms); + timestamp = ofono_sent_sms_timestamp_get(sms); + + DBG("SMS Sent to: %s, message: %s, time: %ld", dest, message, + timestamp); + /* New SMS */ + if (!msg) { + msg = message_new(timestamp, message, EINA_TRUE, state); + EINA_SAFETY_ON_NULL_RETURN(msg); + msg->phone = eina_stringshare_add(dest); + + _message_info_genlist_update(ov, timestamp, dest, message); + } else + msg->state = state; + + if (state == OFONO_SENT_SMS_STATE_FAILED || + state == OFONO_SENT_SMS_STATE_SENT) + eina_hash_del_by_key(ov->pending_sms, sms); + else if (state == OFONO_SENT_SMS_STATE_PENDING) { + msg->refcount++; + eina_hash_add(ov->pending_sms, sms, msg); + ov->p_conversations->list = eina_list_append(ov->p_conversations->list, + msg); + DBG("Added to pending conversations - msg:%p - to: %s - msg: %s", + msg, dest, message); + ov->p_conversations->dirty = EINA_TRUE; + } + + _conversation_eet_update(ov); +} + +Evas_Object *overview_add(Evas_Object *parent) +{ + Evas_Object *obj, *genlist; + Overview *ov; + const char *config_path; + int r; + Evas *e; + Elm_Genlist_Item_Class *itc; + + eet_init(); + ecore_file_init(); + + ov = calloc(1, sizeof(Overview)); + EINA_SAFETY_ON_NULL_RETURN_VAL(ov, NULL); + + ov->p_conversations = calloc(1, sizeof(Messages_List)); + EINA_SAFETY_ON_NULL_GOTO(ov->p_conversations, err_conversations); + + ov->layout = obj = layout_add(parent, "messages-overview"); + EINA_SAFETY_ON_NULL_GOTO(obj, err_obj); + + elm_object_signal_callback_add(obj, "clicked,*", "gui", + _on_layout_clicked, ov); + + genlist = elm_genlist_add(obj); + EINA_SAFETY_ON_NULL_GOTO(genlist, err_genlist); + elm_object_style_set(genlist, "messages-overview"); + elm_genlist_homogeneous_set(genlist, EINA_TRUE); + + +#ifdef HAVE_TIZEN + elm_genlist_scroller_policy_set(genlist, ELM_SCROLLER_POLICY_OFF, + ELM_SCROLLER_POLICY_AUTO); +#else + elm_scroller_policy_set(genlist, ELM_SCROLLER_POLICY_OFF, + ELM_SCROLLER_POLICY_AUTO); +#endif + + itc = elm_genlist_item_class_new(); + EINA_SAFETY_ON_NULL_GOTO(itc, err_itc); + itc->item_style = "messages-overview"; + itc->func.text_get = _item_label_get; + itc->func.content_get = _item_content_get; + itc->func.state_get = NULL; + itc->func.del = NULL; + itc->decorate_all_item_style = "messages-overview-delete"; + itc->decorate_item_style = "messages-overview-delete"; + ov->genlist = genlist; + ov->itc = itc; + + evas_object_smart_callback_add(genlist, "drag,start,right", + _on_list_slide_enter, ov); + evas_object_smart_callback_add(genlist, "drag,start,left", + _on_list_slide_cancel, ov); + evas_object_smart_callback_add(genlist, "drag,start,down", + _on_list_slide_cancel, ov); + evas_object_smart_callback_add(genlist, "drag,start,up", + _on_list_slide_cancel, ov); + + elm_object_part_content_set(obj, "elm.swallow.genlist", genlist); + + evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL, _on_del, + ov); + evas_object_event_callback_add(obj, EVAS_CALLBACK_HIDE, _on_hide, + ov); + evas_object_event_callback_add(obj, EVAS_CALLBACK_SHOW, _on_show, + ov); + + e = evas_object_evas_get(obj); + evas_event_callback_add(e, EVAS_CALLBACK_CANVAS_FOCUS_OUT, + _on_win_focus_out, ov); + evas_event_callback_add(e, EVAS_CALLBACK_CANVAS_FOCUS_IN, + _on_win_focus_in, ov); + + evas_object_data_set(obj, "overview.ctx", ov); + + config_path = efreet_config_home_get(); + + r = asprintf(&ov->base_dir, "%s/%s/messages", config_path, + PACKAGE_NAME); + + if (r < 0) + goto err_base_dir; + + ecore_file_mkpath(ov->base_dir); + + r = asprintf(&ov->msg_path, "%s/%s/messages/messages.eet", config_path, + PACKAGE_NAME); + + if (r < 0) + goto err_path; + + r = asprintf(&ov->msg_bkp, "%s/%s/messages/messages.eet.bkp", + config_path, PACKAGE_NAME); + + if (r < 0) + goto err_bkp; + + _eet_descriptors_init(&ov->edd_msg_list, &ov->edd_msg_info, + &ov->edd_msg, &ov->edd_c_msg); + _overview_messages_read(ov); + + ov->pending_sms = eina_hash_pointer_new(EINA_FREE_CB(message_del)); + EINA_SAFETY_ON_NULL_GOTO(ov->pending_sms, err_hash); + + incoming_sms = ofono_incoming_sms_cb_add(_incoming_sms_cb, ov); + sent_sms = ofono_sent_sms_changed_cb_add(_sent_sms_cb, ov); + elm_object_signal_emit(ov->layout, "toggle,on,view", "gui"); + + return obj; + +err_hash: + free(ov->msg_bkp); +err_bkp: + free(ov->msg_path); +err_path: + free(ov->base_dir); +err_base_dir: + elm_genlist_item_class_free(itc); +err_itc: + evas_object_del(genlist); +err_genlist: + evas_object_del(obj); +err_obj: + free(ov->p_conversations); +err_conversations: + free(ov); + ecore_file_shutdown(); + eet_shutdown(); + return NULL; +} + +void overview_all_contact_messages_clear(Evas_Object *obj, const char *contact) +{ + Overview *ov; + Message_Info *m_info; + + EINA_SAFETY_ON_NULL_RETURN(obj); + ov = evas_object_data_get(obj, "overview.ctx"); + EINA_SAFETY_ON_NULL_RETURN(ov); + + m_info = _message_info_search(ov, contact); + EINA_SAFETY_ON_NULL_RETURN(m_info); + _message_info_del(m_info); +} diff --git a/messages/overview.h b/messages/overview.h new file mode 100644 index 0000000..cbb60bc --- /dev/null +++ b/messages/overview.h @@ -0,0 +1,32 @@ +#ifndef _EFL_OFONO_OVERVIEW_H__ +#define _EFL_OFONO_OVERVIEW_H__ 1 + +#include "ofono.h" + +typedef struct _Message Message; + +Evas_Object *overview_add(Evas_Object *parent); +long long message_time_get(const Message *msg); +const char * message_content_get(const Message *msg); +const char * message_phone_get(const Message *msg); +unsigned char message_outgoing_get(const Message *msg); +unsigned char message_state_get(const Message *msg); +void message_state_set(Message *msg, unsigned char state); +void message_ref(Message *msg); + +void overview_message_from_file_delete(Evas_Object *obj, Message *msg, const char *contact); + +void message_del(Message *msg); + +void message_data_set(Message *msg, void *data); +void *message_data_get(const Message *msg); + +Elm_Object_Item *message_object_item_get(const Message *msg); +void message_object_item_set(Message *msg, Elm_Object_Item *it); + +void overview_genlist_update(Evas_Object *obj, Message *msg,const char *contact); +void overview_all_contact_messages_clear(Evas_Object *obj, const char *contact); + +Message *message_new(time_t timestamp, const char *content, + Eina_Bool outgoing, OFono_Sent_SMS_State state); +#endif diff --git a/tizen/message_daemon.c b/tizen/message_daemon.c new file mode 100644 index 0000000..9c0c74c --- /dev/null +++ b/tizen/message_daemon.c @@ -0,0 +1,148 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include "ofono.h" + +#define APP_NAME "org.tizen.message_daemon" + +static OFono_Callback_List_Incoming_SMS_Node *inc_sms = NULL; + +static char _img_path[PATH_MAX]; + +int _log_domain = -1; + +#define ERR(...) EINA_LOG_DOM_ERR(_log_domain, __VA_ARGS__) +#define INF(...) EINA_LOG_DOM_INFO(_log_domain, __VA_ARGS__) +#define DBG(...) EINA_LOG_DOM_DBG(_log_domain, __VA_ARGS__) + +static void _notification_create(const char *sender, const char *message) +{ + int id; + notification_h noti; + notification_error_e err; + + noti = notification_new(NOTIFICATION_TYPE_NOTI, + NOTIFICATION_GROUP_ID_NONE, + NOTIFICATION_PRIV_ID_NONE); + EINA_SAFETY_ON_NULL_RETURN(noti); + + err = notification_set_text(noti, NOTIFICATION_TEXT_TYPE_TITLE, + sender, NULL, + NOTIFICATION_VARIABLE_TYPE_NONE); + + if (err != NOTIFICATION_ERROR_NONE) + ERR("Could not set the title"); + + err = notification_set_text(noti, NOTIFICATION_TEXT_TYPE_CONTENT, + message, NULL, + NOTIFICATION_VARIABLE_TYPE_NONE); + + if (err != NOTIFICATION_ERROR_NONE) + ERR("Could not set the body"); + + err = notification_set_image(noti, NOTIFICATION_IMAGE_TYPE_ICON, _img_path); + + if (err != NOTIFICATION_ERROR_NONE) + ERR("Could not set the image"); + + err = notification_insert(noti, &id); + + if (err != NOTIFICATION_ERROR_NONE) + ERR("Could not show the notification"); + + notification_free(noti); +} + +static Eina_Bool _phone_locked(void) +{ + int lock; + if (vconf_get_int(VCONFKEY_IDLE_LOCK_STATE, &lock) == -1) + return EINA_FALSE; + + if (lock == VCONFKEY_IDLE_LOCK) + return EINA_TRUE; + + return EINA_FALSE; +} + +static void _inc_sms_cb(void *data __UNUSED__, unsigned int sms_class, + time_t timestamp __UNUSED__, const char *sender, + const char *message) +{ + if (sms_class != 1) + return; + + if (_phone_locked()) + power_wakeup(); + + _notification_create(sender, message); +} + +static int _create(void *data __UNUSED__) +{ + if (!ofono_init()) + return -1; + + inc_sms = ofono_incoming_sms_cb_add(_inc_sms_cb, NULL); + + elm_app_compile_data_dir_set(PACKAGE_DATA_DIR); + /* TODO SET IMAGE */ + snprintf(_img_path, sizeof(_img_path), "%s/img", + elm_app_data_dir_get()); + return 0; +} + +static int _reset(bundle *b __UNUSED__, void *data __UNUSED__) +{ + return 0; +} + +static int _resume(void *data __UNUSED__) +{ + return 0; +} + +static int _pause(void *data __UNUSED__) +{ + return 0; +} + +static int _terminate(void *data __UNUSED__) +{ + ofono_incoming_sms_cb_del(inc_sms); + ofono_shutdown(); + return 0; +} + +int main(int argc, char **argv) +{ + int r = EXIT_FAILURE; + struct appcore_ops ops = { + .create = _create, + .resume = _resume, + .reset = _reset, + .pause = _pause, + .terminate = _terminate, + }; + ops.data = NULL; + + eina_init(); + e_dbus_init(); + + _log_domain = eina_log_domain_register("answer_daemon", NULL); + + r = appcore_efl_main(APP_NAME, &argc, &argv, &ops); + + eina_shutdown(); + e_dbus_shutdown(); + + return r; +} diff --git a/utils/contacts-ofono-efl.h b/utils/contacts-ofono-efl.h index 53fdf24..1b71a64 100644 --- a/utils/contacts-ofono-efl.h +++ b/utils/contacts-ofono-efl.h @@ -13,6 +13,8 @@ const char *contact_info_full_name_get(const Contact_Info *c); const char *contact_info_first_name_get(const Contact_Info *c); const char *contact_info_last_name_get(const Contact_Info *c); +Eina_List *contact_info_all_numbers_get(const Contact_Info *c); + const char *contact_info_detail_get(const Contact_Info *c, const char *type); const char *contact_info_number_check(const Contact_Info *c, const char *number); @@ -32,4 +34,11 @@ void contact_info_on_del_callback_add(Contact_Info *c, void (*cb)(void *data, co void contact_info_on_del_callback_del(Contact_Info *c, void (*cb)(void *data, const Contact_Info *c), const void *data); void contact_info_del(Contact_Info *c); +typedef struct _Contact_Partial_Match Contact_Partial_Match; +Eina_List *contact_partial_match_search(Evas_Object *obj, const char *query); +void contact_partial_match_search_free(Eina_List *results); +const char *contact_partial_match_type_get(const Contact_Partial_Match *pm); +const Contact_Info *contact_partial_match_info_get(const Contact_Partial_Match *pm); +Eina_Bool contact_partial_match_name_match_get(const Contact_Partial_Match *pm); + #endif diff --git a/utils/contacts-tizen.c b/utils/contacts-tizen.c index 81a3bf5..c8f88e9 100644 --- a/utils/contacts-tizen.c +++ b/utils/contacts-tizen.c @@ -33,6 +33,11 @@ typedef struct _Contact_Number { char number[]; } Contact_Number; +typedef struct _Contact_Name_Search { + const char *name; + Contact_Info *c_info; +} Contact_Name_Search; + typedef struct _Contact_Number_Entry { Eina_List *contacts; char number[]; @@ -68,6 +73,22 @@ typedef struct _Contact_Info_On_Changed_Ctx { Eina_Bool deleted; } Contact_Info_On_Changed_Ctx; +struct _Contact_Partial_Match +{ + const Contact_Info *info; + const char *type; + Eina_Bool name_match : 1; +}; + +typedef struct _Partial_Match_Search +{ + Eina_List *matches; + const Contacts *contacts; +} Partial_Match_Search; + + +static void _contact_number_entry_add(const char *number, Contact_Info *c_info); + static const char *phone_type_get(contact_number_h number); static void _contact_info_free(Contact_Info *c_info); @@ -75,6 +96,230 @@ static void _contact_number_add(char *number, Contact_Info *c_info, contact_number_h number_h); +const char *contact_info_number_check(const Contact_Info *c, + const char *number); + +static void _partial_match_add(Eina_List **p_list, const char *type, + const Contact_Info *c_info, + Eina_Bool name_match) +{ + Contact_Partial_Match *pm = malloc(sizeof(Contact_Partial_Match)); + EINA_SAFETY_ON_NULL_RETURN(pm); + pm->info = c_info; + pm->type = type; + pm->name_match = name_match; + *p_list = eina_list_append(*p_list, pm); +} + +static void _partial_number_match_add(Eina_List **p_list, const char *type, + const Contact_Info *c_info) +{ + _partial_match_add(p_list, type, c_info, EINA_FALSE); +} + +static bool _number_partial_search(contact_query_number_s *query, void *data) +{ + Partial_Match_Search *pm_search = data; + const Contacts *contacts = pm_search->contacts; + char *n; + contact_h tizen_c; + const char *type; + contact_number_iterator_h it; + contact_number_h number_h; + Contact_Info *c_info; + Contact_Number *cn; + + c_info = eina_hash_find(contacts->hash_ids, &query->contact_db_id); + + if (c_info) + goto exit; + + c_info = calloc(1, sizeof(Contact_Info)); + EINA_SAFETY_ON_NULL_RETURN_VAL(c_info, true); + c_info->first_name = eina_stringshare_add(query->first_name); + c_info->last_name = eina_stringshare_add(query->last_name); + c_info->picture = eina_stringshare_add(query->contact_image_path); + c_info->id = query->contact_db_id; + if (contact_get_from_db(c_info->id, &tizen_c) != CONTACTS_ERROR_NONE) + return true; + + if (contact_get_number_iterator(tizen_c, &it) != + CONTACTS_ERROR_NONE) + return true; + + while (contact_number_iterator_has_next(it)) { + if (contact_number_iterator_next(&it, &number_h) != + CONTACTS_ERROR_NONE) + continue; + if (contact_number_get_number(number_h, &n) != + CONTACTS_ERROR_NONE) + continue; + _contact_number_add(n, c_info, number_h); + free(n); + } + contact_destroy(tizen_c); + + /* New contact */ + c_info->contacts = (Contacts *)contacts; + eina_hash_add(contacts->hash_ids, &c_info->id, c_info); + EINA_INLIST_FOREACH(c_info->numbers, cn) + _contact_number_entry_add(cn->number, c_info); + +exit: + type = contact_info_number_check(c_info, query->phone_number); + _partial_number_match_add(&pm_search->matches, type, + c_info); + return true; + +} + +static bool _name_partial_search(contact_query_name_s *query, void *data) +{ + Partial_Match_Search *pm_search = data; + const Contacts *contacts = pm_search->contacts; + char *n; + contact_h tizen_c; + contact_number_iterator_h it; + contact_number_h number_h; + Contact_Info *c_info; + Contact_Number *cn; + Eina_Bool c_info_new = EINA_FALSE; + + + c_info = eina_hash_find(contacts->hash_ids, &query->contact_db_id); + + if (c_info) + goto exit; + + c_info = calloc(1, sizeof(Contact_Info)); + EINA_SAFETY_ON_NULL_RETURN_VAL(c_info, true); + c_info->first_name = eina_stringshare_add(query->first_name); + c_info->last_name = eina_stringshare_add(query->last_name); + c_info->picture = eina_stringshare_add(query->contact_image_path); + c_info->id = query->contact_db_id; + if (contact_get_from_db(c_info->id, &tizen_c) != CONTACTS_ERROR_NONE) + return true; + + if (contact_get_number_iterator(tizen_c, &it) != + CONTACTS_ERROR_NONE) + return true; + + while (contact_number_iterator_has_next(it)) { + if (contact_number_iterator_next(&it, &number_h) != + CONTACTS_ERROR_NONE) + continue; + if (contact_number_get_number(number_h, &n) != + CONTACTS_ERROR_NONE) + continue; + _contact_number_add(n, c_info, number_h); + free(n); + } + contact_destroy(tizen_c); + + /* New contact */ + c_info->contacts = (Contacts *)contacts; + eina_hash_add(contacts->hash_ids, &c_info->id, c_info); + c_info_new = EINA_TRUE; +exit: + EINA_INLIST_FOREACH(c_info->numbers, cn) { + if (c_info_new) + _contact_number_entry_add(cn->number, c_info); + _partial_match_add(&pm_search->matches, cn->type, c_info, + EINA_TRUE); + } + return true; +} + +Eina_List *contact_partial_match_search(Evas_Object *obj, const char *query) +{ + + const Contacts *contacts; + int i, j; + Eina_Bool name_search = EINA_FALSE; + char *query_number; + Partial_Match_Search pm_search; + + EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(query, NULL); + contacts = evas_object_data_get(obj, "contacts.ctx"); + EINA_SAFETY_ON_NULL_RETURN_VAL(contacts, NULL); + + if (!contacts->contacts_on) + return NULL; + + /* Check if it is numeric */ + query_number = alloca(strlen(query) + 1); + EINA_SAFETY_ON_NULL_RETURN_VAL(query_number, NULL); + for (i = 0, j = 0; query[i] != '\0'; i++) { + if (isalpha(query[i])) { + name_search = EINA_TRUE; + break; + } else if (isdigit(query[i])) + query_number[j++] = query[i]; + } + + pm_search.contacts = contacts; + pm_search.matches = NULL; + + if (name_search) { + if (contact_query_contact_by_name(_name_partial_search, query, + &pm_search) < 0) { + ERR("Could not search in contacts DB the name: %s", + query); + return NULL; + } + } else { + query_number[j] = '\0'; + if (contact_query_contact_by_number(_number_partial_search, + query, + &pm_search) < 0) { + ERR("Could not search in contacts DB the number: %s", + query); + return NULL; + } + } + + return pm_search.matches; +} + +void contact_partial_match_search_free(Eina_List *results) +{ + Contact_Partial_Match *pm; + EINA_LIST_FREE(results, pm) + free(pm); +} + +const char *contact_partial_match_type_get(const Contact_Partial_Match *pm) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(pm, NULL); + return pm->type; +} + +const Contact_Info *contact_partial_match_info_get(const Contact_Partial_Match *pm) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(pm, NULL); + return pm->info; +} + +Eina_Bool contact_partial_match_name_match_get(const Contact_Partial_Match *pm) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(pm, EINA_FALSE); + return pm->name_match; +} + +Eina_List *contact_info_all_numbers_get(const Contact_Info *c) +{ + Eina_List *l = NULL; + Contact_Number *cn; + + EINA_SAFETY_ON_NULL_RETURN_VAL(c, NULL); + + EINA_INLIST_FOREACH(c->numbers, cn) + l = eina_list_append(l, cn->number); + + return l; +} + static void _contact_number_entry_add(const char *number, Contact_Info *c_info) { diff --git a/utils/contacts.c b/utils/contacts.c index 3dd14b9..386f1d0 100644 --- a/utils/contacts.c +++ b/utils/contacts.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "log.h" #include "ofono.h" @@ -65,6 +66,144 @@ typedef struct _Contact_Info_On_Changed_Ctx { Eina_Bool deleted; } Contact_Info_On_Changed_Ctx; +Eina_List *contact_info_all_numbers_get(const Contact_Info *c) +{ + Eina_List *l = NULL; + + EINA_SAFETY_ON_NULL_RETURN_VAL(c, NULL); + + if (c->mobile) + l = eina_list_append(l, c->mobile); + if (c->work) + l = eina_list_append(l, c->work); + if (c->home) + l = eina_list_append(l, c->home); + + return l; +} + +static Eina_Bool _number_match(const char *n1, const char *n2) +{ + if (eina_str_has_suffix(n1, n2)) + return EINA_TRUE; + + return EINA_FALSE; +} + +struct _Contact_Partial_Match +{ + const Contact_Info *info; + const char *type; + Eina_Bool name_match : 1; +}; + +static void _partial_match_add(Eina_List **p_list, const char *type, + const Contact_Info *c_info, + Eina_Bool name_match) +{ + Contact_Partial_Match *pm = malloc(sizeof(Contact_Partial_Match)); + EINA_SAFETY_ON_NULL_RETURN(pm); + pm->info = c_info; + pm->type = type; + pm->name_match = name_match; + *p_list = eina_list_append(*p_list, pm); +} + +static void _partial_name_match_add(Eina_List **p_list, + const Contact_Info *c_info) +{ + if (c_info->mobile) + _partial_match_add(p_list, "Mobile", c_info, EINA_TRUE); + if (c_info->work) + _partial_match_add(p_list, "Work", c_info, EINA_TRUE); + if (c_info->home) + _partial_match_add(p_list, "Home", c_info, EINA_TRUE); +} + +static void _partial_number_match_add(Eina_List **p_list, const char *type, + const Contact_Info *c_info) +{ + _partial_match_add(p_list, type, c_info, EINA_FALSE); +} + +Eina_List *contact_partial_match_search(Evas_Object *obj, const char *query) +{ + const Contact_Info *c_info; + const Contacts *contacts; + Eina_List *ret = NULL, *l; + int i, j; + Eina_Bool name_search = EINA_FALSE; + char *query_number; + + EINA_SAFETY_ON_NULL_RETURN_VAL(obj, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(query, NULL); + contacts = evas_object_data_get(obj, "contacts.ctx"); + EINA_SAFETY_ON_NULL_RETURN_VAL(contacts, NULL); + + /* Check if it is numeric */ + query_number = alloca(strlen(query) + 1); + EINA_SAFETY_ON_NULL_RETURN_VAL(query_number, NULL); + for (i = 0, j = 0; query[i] != '\0'; i++) { + if (isalpha(query[i])) { + name_search = EINA_TRUE; + break; + } else if (isdigit(query[i])) + query_number[j++] = query[i]; + } + + if (name_search) { + EINA_LIST_FOREACH(contacts->c_list->list, l, c_info) { + const char *full_name; + + full_name = contact_info_full_name_get(c_info); + if (strcasestr(full_name, query)) + _partial_name_match_add(&ret, c_info); + } + } else { + query_number[j] = '\0'; + EINA_LIST_FOREACH(contacts->c_list->list, l, c_info) { + if (_number_match(c_info->mobile, query_number)) + _partial_number_match_add(&ret, "Mobile", + c_info); + + if (_number_match(c_info->work, query_number)) + _partial_number_match_add(&ret, "Work", + c_info); + + if (_number_match(c_info->home, query_number)) + _partial_number_match_add(&ret, "Home", + c_info); + } + } + + return ret; +} + +void contact_partial_match_search_free(Eina_List *results) +{ + Contact_Partial_Match *pm; + EINA_LIST_FREE(results, pm) + free(pm); +} + +const char *contact_partial_match_type_get(const Contact_Partial_Match *pm) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(pm, NULL); + return pm->type; +} + +const Contact_Info *contact_partial_match_info_get(const Contact_Partial_Match *pm) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(pm, NULL); + return pm->info; +} + +Eina_Bool contact_partial_match_name_match_get(const Contact_Partial_Match *pm) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(pm, EINA_FALSE); + return pm->name_match; +} + Contact_Info *contact_search(Evas_Object *obj, const char *number, const char **type) { Contact_Info *c_info; @@ -144,8 +283,8 @@ const char *contact_info_detail_get(const Contact_Info *c, const char *type) const char *contact_info_number_check(const Contact_Info *c, const char *number) { - EINA_SAFETY_ON_NULL_RETURN_VAL(c, EINA_FALSE); - EINA_SAFETY_ON_NULL_RETURN_VAL(number, EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(c, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(number, NULL); if (strcmp(number, c->mobile) == 0) return "Mobile"; diff --git a/utils/util.c b/utils/util.c index 46f4761..7b72681 100644 --- a/utils/util.c +++ b/utils/util.c @@ -80,8 +80,7 @@ char *date_format(time_t date) else if (dt < (HOUR * 4)) r = asprintf(&buf, "%dh ago", (int)dt/3600); else if (dt <= DAY) { - struct tm *f_time = gmtime(&date); - EINA_SAFETY_ON_NULL_GOTO(f_time, err_gmtime); + struct tm *f_time = localtime(&date); r = asprintf(&buf, "%02d:%02d", f_time->tm_hour, f_time->tm_min); } else if (dt < WEEK) { @@ -100,9 +99,35 @@ char *date_format(time_t date) buf = strdup(""); return buf; +} + +char *date_short_format(time_t date) +{ + time_t now = time(NULL); + double dt = difftime(now, date); + char *buf; + int r; -err_gmtime: - return strdup(""); + if (dt <= DAY) { + struct tm *f_time = localtime(&date); + r = asprintf(&buf, "%02d:%02d", f_time->tm_hour, + f_time->tm_min); + } else if (dt < WEEK) { + char tmp[256]; + struct tm *tm = localtime(&date); + strftime(tmp, sizeof(tmp), "%A", tm); + r = asprintf(&buf, "%s", tmp); + } else { + char tmp[256]; + struct tm *tm = localtime(&date); + strftime(tmp, sizeof(tmp), "%x", tm); + r = asprintf(&buf, "%s", tmp); + } + + if (r < 0) + buf = strdup(""); + + return buf; } Evas_Object *picture_icon_get(Evas_Object *parent, const char *picture) diff --git a/utils/util.h b/utils/util.h index e5d60fe..246dbe6 100644 --- a/utils/util.h +++ b/utils/util.h @@ -11,6 +11,7 @@ char *phone_format(const char *number); char *date_format(time_t date); +char *date_short_format(time_t date); Evas_Object *picture_icon_get(Evas_Object *parent, const char *picture); -- 2.7.4