[dali_1.9.35] Merge branch 'devel/master' 11/246111/1
authoradam.b <adam.b@samsung.com>
Fri, 23 Oct 2020 10:46:01 +0000 (11:46 +0100)
committeradam.b <adam.b@samsung.com>
Fri, 23 Oct 2020 10:46:01 +0000 (11:46 +0100)
Change-Id: I3a8a0787c378b25b10d24b5d654a88e6ef620a17

16 files changed:
automated-tests/README.md
automated-tests/patch-coverage.pl
automated-tests/src/dali-toolkit/utc-Dali-ImageView.cpp
dali-toolkit/internal/controls/control/control-data-impl.cpp
dali-toolkit/internal/controls/control/control-data-impl.h
dali-toolkit/internal/controls/text-controls/text-label-impl.cpp
dali-toolkit/internal/controls/text-controls/text-label-impl.h
dali-toolkit/internal/file.list
dali-toolkit/internal/text/rendering/text-typesetter.cpp
dali-toolkit/internal/text/text-controller-impl-event-handler.cpp [new file with mode: 0644]
dali-toolkit/internal/text/text-controller-impl-event-handler.h [new file with mode: 0644]
dali-toolkit/internal/text/text-controller-impl.cpp
dali-toolkit/internal/text/text-controller-impl.h
dali-toolkit/internal/visuals/transition-data-impl.cpp
dali-toolkit/public-api/dali-toolkit-version.cpp
packaging/dali-toolkit.spec

index c062b8a..590571d 100644 (file)
@@ -123,11 +123,29 @@ To execute a subset of tests, you can run individual test sets, e.g.
 
     ./execute.sh dali-toolkit
 
-To get coverage output (you need to first build dali libraries with
+To get full coverage output (you need to first build dali libraries with
 --coverage), run
 
     ./coverage.sh
 
+To check the coverage of your patch, (the build server uses its own copy
+of these scripts), you can use
+
+    ./patch-coverage.pl -q [diff-spec]
+
+to get a summary, or
+
+    ./patch-coverage.pl [diff-spec]
+
+to get textual output, or
+
+    ./patch-coverage.pl -o out.html [diff-spec]
+
+to get HTML output (used by build server).
+
+diff-spec is any refspec accepted by git-diff. If it's left out, it creates
+a refspec to the latest commit, or uses the index/working tree.
+
 
 Testing on target
 =================
index c5d9083..702dc0b 100755 (executable)
@@ -19,33 +19,59 @@ use strict;
 use Git;
 use Getopt::Long;
 use Error qw(:try);
-use HTML::Element;
 use Pod::Usage;
 use File::Basename;
-#use Data::Dumper;
 use File::stat;
 use Scalar::Util qw /looks_like_number/;
 use Cwd qw /getcwd/;
 use Term::ANSIColor qw(:constants);
+use Data::Dumper;
 
-# Program to run gcov on files in patch (that are in source dirs - needs to be dali-aware).
+# Dali specific program to run lcov and parse output for files in patch
 
-# A) Get patch
-# B) Remove uninteresting files
-# C) Find matching gcno/gcda files
-# D) Copy and rename them to match source prefix (i.e. strip library name off front)
-# E) Generate patch output with covered/uncovered lines marked in green/red
-# F) Generate coverage data for changed lines
-# G) Exit status should be 0 for high coverage (90% line coverage for all new/changed lines)
+# A) Generate lcov output from lib & test cases
+# B) Get patch using git diff
+# C) Generate patch output with covered/uncovered lines marked in green/red
+# D) Generate coverage data for changed lines
+# E) Exit status should be 0 for high coverage (90% line coverage for all new/changed lines)
 #    or 1 for low coverage
 
 # Sources for conversion of gcno/gcda files:
-# ~/bin/lcov
+# /usr/bin/lcov & genhtml
 # Python git-coverage (From http://stef.thewalter.net/git-coverage-useful-code-coverage.html)
 
+# From genhtml:
+sub read_info_file($);
+sub get_info_entry($);
+sub set_info_entry($$$$$$$$$;$$$$$$);
+sub combine_info_entries($$$);
+sub combine_info_files($$);
+sub compress_brcount($);
+sub brcount_to_db($);
+sub db_to_brcount($;$);
+sub brcount_db_combine($$$);
+sub add_counts($$);
+sub info(@);
+
 our $repo = Git->repository();
-our $debug=0;
+our $debug=1;
 our $pd_debug=0;
+our $root;
+our %info_data; # Hash containing all data from .info files
+
+# Settings from genhtml:
+our $func_coverage;     # If set, generate function coverage statistics
+our $no_func_coverage;  # Disable func_coverage
+our $br_coverage;       # If set, generate branch coverage statistics
+our $no_br_coverage;    # Disable br_coverage
+our $sort = 1;          # If set, provide directory listings with sorted entries
+our $no_sort;           # Disable sort
+
+# Branch data combination types
+our $BR_SUB = 0;
+our $BR_ADD = 1;
+
+# Command line options
 our $opt_cached;
 our $opt_help;
 our $opt_output;
@@ -64,6 +90,793 @@ GetOptions( %longOptions ) or pod2usage(2);
 pod2usage(1) if $opt_help;
 
 
+# From genhtml:
+#
+# read_info_file(info_filename)
+#
+# Read in the contents of the .info file specified by INFO_FILENAME. Data will
+# be returned as a reference to a hash containing the following mappings:
+#
+# %result: for each filename found in file -> \%data
+#
+# %data: "test"    -> \%testdata
+#        "sum"     -> \%sumcount
+#        "func"    -> \%funcdata
+#        "found"   -> $lines_found (number of instrumented lines found in file)
+#        "hit"     -> $lines_hit (number of executed lines in file)
+#        "f_found" -> $fn_found (number of instrumented functions found in file)
+#        "f_hit"   -> $fn_hit (number of executed functions in file)
+#        "b_found" -> $br_found (number of instrumented branches found in file)
+#        "b_hit"   -> $br_hit (number of executed branches in file)
+#        "check"   -> \%checkdata
+#        "testfnc" -> \%testfncdata
+#        "sumfnc"  -> \%sumfnccount
+#        "testbr"  -> \%testbrdata
+#        "sumbr"   -> \%sumbrcount
+#
+# %testdata   : name of test affecting this file -> \%testcount
+# %testfncdata: name of test affecting this file -> \%testfnccount
+# %testbrdata:  name of test affecting this file -> \%testbrcount
+#
+# %testcount   : line number   -> execution count for a single test
+# %testfnccount: function name -> execution count for a single test
+# %testbrcount : line number   -> branch coverage data for a single test
+# %sumcount    : line number   -> execution count for all tests
+# %sumfnccount : function name -> execution count for all tests
+# %sumbrcount  : line number   -> branch coverage data for all tests
+# %funcdata    : function name -> line number
+# %checkdata   : line number   -> checksum of source code line
+# $brdata      : vector of items: block, branch, taken
+#
+# Note that .info file sections referring to the same file and test name
+# will automatically be combined by adding all execution counts.
+#
+# Note that if INFO_FILENAME ends with ".gz", it is assumed that the file
+# is compressed using GZIP. If available, GUNZIP will be used to decompress
+# this file.
+#
+# Die on error.
+#
+sub read_info_file($)
+{
+    my $tracefile = $_[0];      # Name of tracefile
+    my %result;             # Resulting hash: file -> data
+    my $data;           # Data handle for current entry
+    my $testdata;           #       "             "
+    my $testcount;          #       "             "
+    my $sumcount;           #       "             "
+    my $funcdata;           #       "             "
+    my $checkdata;          #       "             "
+    my $testfncdata;
+    my $testfnccount;
+    my $sumfnccount;
+    my $testbrdata;
+    my $testbrcount;
+    my $sumbrcount;
+    my $line;           # Current line read from .info file
+    my $testname;           # Current test name
+    my $filename;           # Current filename
+    my $hitcount;           # Count for lines hit
+    my $count;          # Execution count of current line
+    my $negative;           # If set, warn about negative counts
+    my $changed_testname;       # If set, warn about changed testname
+    my $line_checksum;      # Checksum of current line
+    my $notified_about_relative_paths;
+    local *INFO_HANDLE;         # Filehandle for .info file
+
+    info("Reading data file $tracefile\n");
+
+    # Check if file exists and is readable
+    stat($tracefile);
+    if (!(-r _))
+    {
+        die("ERROR: cannot read file $tracefile!\n");
+    }
+
+    # Check if this is really a plain file
+    if (!(-f _))
+    {
+        die("ERROR: not a plain file: $tracefile!\n");
+    }
+
+    # Check for .gz extension
+    if ($tracefile =~ /\.gz$/)
+    {
+        # Check for availability of GZIP tool
+        system_no_output(1, "gunzip" ,"-h")
+            and die("ERROR: gunzip command not available!\n");
+
+        # Check integrity of compressed file
+        system_no_output(1, "gunzip", "-t", $tracefile)
+            and die("ERROR: integrity check failed for ".
+                    "compressed file $tracefile!\n");
+
+        # Open compressed file
+        open(INFO_HANDLE, "-|", "gunzip -c '$tracefile'")
+            or die("ERROR: cannot start gunzip to decompress ".
+                   "file $tracefile!\n");
+    }
+    else
+    {
+        # Open decompressed file
+        open(INFO_HANDLE, "<", $tracefile)
+            or die("ERROR: cannot read file $tracefile!\n");
+    }
+
+    $testname = "";
+    while (<INFO_HANDLE>)
+    {
+        chomp($_);
+        $line = $_;
+
+        # Switch statement
+        foreach ($line)
+        {
+            /^TN:([^,]*)(,diff)?/ && do
+            {
+                # Test name information found
+                $testname = defined($1) ? $1 : "";
+                if ($testname =~ s/\W/_/g)
+                {
+                    $changed_testname = 1;
+                }
+                $testname .= $2 if (defined($2));
+                last;
+            };
+
+            /^[SK]F:(.*)/ && do
+            {
+                # Filename information found
+                # Retrieve data for new entry
+                $filename = File::Spec->rel2abs($1, $root);
+
+                if (!File::Spec->file_name_is_absolute($1) &&
+                    !$notified_about_relative_paths)
+                {
+                    info("Resolved relative source file ".
+                         "path \"$1\" with CWD to ".
+                         "\"$filename\".\n");
+                    $notified_about_relative_paths = 1;
+                }
+
+                $data = $result{$filename};
+                ($testdata, $sumcount, $funcdata, $checkdata,
+                 $testfncdata, $sumfnccount, $testbrdata,
+                 $sumbrcount) =
+                    get_info_entry($data);
+
+                if (defined($testname))
+                {
+                    $testcount = $testdata->{$testname};
+                    $testfnccount = $testfncdata->{$testname};
+                    $testbrcount = $testbrdata->{$testname};
+                }
+                else
+                {
+                    $testcount = {};
+                    $testfnccount = {};
+                    $testbrcount = {};
+                }
+                last;
+            };
+
+            /^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do
+            {
+                # Fix negative counts
+                $count = $2 < 0 ? 0 : $2;
+                if ($2 < 0)
+                {
+                    $negative = 1;
+                }
+                # Execution count found, add to structure
+                # Add summary counts
+                $sumcount->{$1} += $count;
+
+                # Add test-specific counts
+                if (defined($testname))
+                {
+                    $testcount->{$1} += $count;
+                }
+
+                # Store line checksum if available
+                if (defined($3))
+                {
+                    $line_checksum = substr($3, 1);
+
+                    # Does it match a previous definition
+                    if (defined($checkdata->{$1}) &&
+                        ($checkdata->{$1} ne
+                         $line_checksum))
+                    {
+                        die("ERROR: checksum mismatch ".
+                            "at $filename:$1\n");
+                    }
+
+                    $checkdata->{$1} = $line_checksum;
+                }
+                last;
+            };
+
+            /^FN:(\d+),([^,]+)/ && do
+            {
+                last if (!$func_coverage);
+
+                # Function data found, add to structure
+                $funcdata->{$2} = $1;
+
+                # Also initialize function call data
+                if (!defined($sumfnccount->{$2})) {
+                    $sumfnccount->{$2} = 0;
+                }
+                if (defined($testname))
+                {
+                    if (!defined($testfnccount->{$2})) {
+                        $testfnccount->{$2} = 0;
+                    }
+                }
+                last;
+            };
+
+            /^FNDA:(\d+),([^,]+)/ && do
+            {
+                last if (!$func_coverage);
+                # Function call count found, add to structure
+                # Add summary counts
+                $sumfnccount->{$2} += $1;
+
+                # Add test-specific counts
+                if (defined($testname))
+                {
+                    $testfnccount->{$2} += $1;
+                }
+                last;
+            };
+
+            /^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do {
+                # Branch coverage data found
+                my ($line, $block, $branch, $taken) =
+                    ($1, $2, $3, $4);
+
+                last if (!$br_coverage);
+                $sumbrcount->{$line} .=
+                    "$block,$branch,$taken:";
+
+                # Add test-specific counts
+                if (defined($testname)) {
+                    $testbrcount->{$line} .=
+                        "$block,$branch,$taken:";
+                }
+                last;
+            };
+
+            /^end_of_record/ && do
+            {
+                # Found end of section marker
+                if ($filename)
+                {
+                    # Store current section data
+                    if (defined($testname))
+                    {
+                        $testdata->{$testname} =
+                            $testcount;
+                        $testfncdata->{$testname} =
+                            $testfnccount;
+                        $testbrdata->{$testname} =
+                            $testbrcount;
+                    }
+
+                    set_info_entry($data, $testdata,
+                                   $sumcount, $funcdata,
+                                   $checkdata, $testfncdata,
+                                   $sumfnccount,
+                                   $testbrdata,
+                                   $sumbrcount);
+                    $result{$filename} = $data;
+                    last;
+                }
+            };
+
+            # default
+            last;
+        }
+    }
+    close(INFO_HANDLE);
+
+    # Calculate lines_found and lines_hit for each file
+    foreach $filename (keys(%result))
+    {
+        $data = $result{$filename};
+
+        ($testdata, $sumcount, undef, undef, $testfncdata,
+         $sumfnccount, $testbrdata, $sumbrcount) =
+            get_info_entry($data);
+
+        # Filter out empty files
+        if (scalar(keys(%{$sumcount})) == 0)
+        {
+            delete($result{$filename});
+            next;
+        }
+        # Filter out empty test cases
+        foreach $testname (keys(%{$testdata}))
+        {
+            if (!defined($testdata->{$testname}) ||
+                scalar(keys(%{$testdata->{$testname}})) == 0)
+            {
+                delete($testdata->{$testname});
+                delete($testfncdata->{$testname});
+            }
+        }
+
+        $data->{"found"} = scalar(keys(%{$sumcount}));
+        $hitcount = 0;
+
+        foreach (keys(%{$sumcount}))
+        {
+            if ($sumcount->{$_} > 0) { $hitcount++; }
+        }
+
+        $data->{"hit"} = $hitcount;
+
+        # Get found/hit values for function call data
+        $data->{"f_found"} = scalar(keys(%{$sumfnccount}));
+        $hitcount = 0;
+
+        foreach (keys(%{$sumfnccount})) {
+            if ($sumfnccount->{$_} > 0) {
+                $hitcount++;
+            }
+        }
+        $data->{"f_hit"} = $hitcount;
+
+        # Combine branch data for the same branches
+        (undef, $data->{"b_found"}, $data->{"b_hit"}) = compress_brcount($sumbrcount);
+        foreach $testname (keys(%{$testbrdata})) {
+            compress_brcount($testbrdata->{$testname});
+        }
+    }
+
+    if (scalar(keys(%result)) == 0)
+    {
+        die("ERROR: no valid records found in tracefile $tracefile\n");
+    }
+    if ($negative)
+    {
+        warn("WARNING: negative counts found in tracefile ".
+             "$tracefile\n");
+    }
+    if ($changed_testname)
+    {
+        warn("WARNING: invalid characters removed from testname in ".
+             "tracefile $tracefile\n");
+    }
+
+    return(\%result);
+}
+
+sub print_simplified_info
+{
+    for my $key (keys(%info_data))
+    {
+        print "K $key: \n";
+        my $sumcountref = $info_data{$key}->{"sum"};
+        for my $line (sort{$a<=>$b}(keys(%$sumcountref)))
+        {
+            print "L  $line: $sumcountref->{$line}\n";
+        }
+    }
+}
+
+# From genhtml:
+#
+# get_info_entry(hash_ref)
+#
+# Retrieve data from an entry of the structure generated by read_info_file().
+# Return a list of references to hashes:
+# (test data hash ref, sum count hash ref, funcdata hash ref, checkdata hash
+#  ref, testfncdata hash ref, sumfnccount hash ref, lines found, lines hit,
+#  functions found, functions hit)
+#
+
+sub get_info_entry($)
+{
+    my $testdata_ref = $_[0]->{"test"};
+    my $sumcount_ref = $_[0]->{"sum"};
+    my $funcdata_ref = $_[0]->{"func"};
+    my $checkdata_ref = $_[0]->{"check"};
+    my $testfncdata = $_[0]->{"testfnc"};
+    my $sumfnccount = $_[0]->{"sumfnc"};
+    my $testbrdata = $_[0]->{"testbr"};
+    my $sumbrcount = $_[0]->{"sumbr"};
+    my $lines_found = $_[0]->{"found"};
+    my $lines_hit = $_[0]->{"hit"};
+    my $fn_found = $_[0]->{"f_found"};
+    my $fn_hit = $_[0]->{"f_hit"};
+    my $br_found = $_[0]->{"b_found"};
+    my $br_hit = $_[0]->{"b_hit"};
+
+    return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref,
+            $testfncdata, $sumfnccount, $testbrdata, $sumbrcount,
+            $lines_found, $lines_hit, $fn_found, $fn_hit,
+            $br_found, $br_hit);
+}
+
+
+# From genhtml:
+#
+# set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref,
+#                checkdata_ref, testfncdata_ref, sumfcncount_ref,
+#                testbrdata_ref, sumbrcount_ref[,lines_found,
+#                lines_hit, f_found, f_hit, $b_found, $b_hit])
+#
+# Update the hash referenced by HASH_REF with the provided data references.
+#
+
+sub set_info_entry($$$$$$$$$;$$$$$$)
+{
+    my $data_ref = $_[0];
+
+    $data_ref->{"test"} = $_[1];
+    $data_ref->{"sum"} = $_[2];
+    $data_ref->{"func"} = $_[3];
+    $data_ref->{"check"} = $_[4];
+    $data_ref->{"testfnc"} = $_[5];
+    $data_ref->{"sumfnc"} = $_[6];
+    $data_ref->{"testbr"} = $_[7];
+    $data_ref->{"sumbr"} = $_[8];
+
+    if (defined($_[9])) { $data_ref->{"found"} = $_[9]; }
+    if (defined($_[10])) { $data_ref->{"hit"} = $_[10]; }
+    if (defined($_[11])) { $data_ref->{"f_found"} = $_[11]; }
+    if (defined($_[12])) { $data_ref->{"f_hit"} = $_[12]; }
+    if (defined($_[13])) { $data_ref->{"b_found"} = $_[13]; }
+    if (defined($_[14])) { $data_ref->{"b_hit"} = $_[14]; }
+}
+
+# From genhtml:
+#
+# combine_info_entries(entry_ref1, entry_ref2, filename)
+#
+# Combine .info data entry hashes referenced by ENTRY_REF1 and ENTRY_REF2.
+# Return reference to resulting hash.
+#
+sub combine_info_entries($$$)
+{
+    my $entry1 = $_[0];     # Reference to hash containing first entry
+    my $testdata1;
+    my $sumcount1;
+    my $funcdata1;
+    my $checkdata1;
+    my $testfncdata1;
+    my $sumfnccount1;
+    my $testbrdata1;
+    my $sumbrcount1;
+
+    my $entry2 = $_[1];     # Reference to hash containing second entry
+    my $testdata2;
+    my $sumcount2;
+    my $funcdata2;
+    my $checkdata2;
+    my $testfncdata2;
+    my $sumfnccount2;
+    my $testbrdata2;
+    my $sumbrcount2;
+
+    my %result;         # Hash containing combined entry
+    my %result_testdata;
+    my $result_sumcount = {};
+    my $result_funcdata;
+    my $result_testfncdata;
+    my $result_sumfnccount;
+    my $result_testbrdata;
+    my $result_sumbrcount;
+    my $lines_found;
+    my $lines_hit;
+    my $fn_found;
+    my $fn_hit;
+    my $br_found;
+    my $br_hit;
+
+    my $testname;
+    my $filename = $_[2];
+
+    # Retrieve data
+    ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1,
+     $sumfnccount1, $testbrdata1, $sumbrcount1) = get_info_entry($entry1);
+    ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2,
+     $sumfnccount2, $testbrdata2, $sumbrcount2) = get_info_entry($entry2);
+
+#    # Merge checksums
+#    $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename);
+#
+#    # Combine funcdata
+#    $result_funcdata = merge_func_data($funcdata1, $funcdata2, $filename);
+#
+#    # Combine function call count data
+#    $result_testfncdata = add_testfncdata($testfncdata1, $testfncdata2);
+#    ($result_sumfnccount, $fn_found, $fn_hit) =
+#        add_fnccount($sumfnccount1, $sumfnccount2);
+#
+#    # Combine branch coverage data
+#    $result_testbrdata = add_testbrdata($testbrdata1, $testbrdata2);
+#    ($result_sumbrcount, $br_found, $br_hit) =
+#        combine_brcount($sumbrcount1, $sumbrcount2, $BR_ADD);
+#
+    # Combine testdata
+    foreach $testname (keys(%{$testdata1}))
+    {
+        if (defined($testdata2->{$testname}))
+        {
+            # testname is present in both entries, requires
+            # combination
+            ($result_testdata{$testname}) =
+                add_counts($testdata1->{$testname},
+                           $testdata2->{$testname});
+        }
+        else
+        {
+            # testname only present in entry1, add to result
+            $result_testdata{$testname} = $testdata1->{$testname};
+        }
+
+        # update sum count hash
+        ($result_sumcount, $lines_found, $lines_hit) =
+            add_counts($result_sumcount,
+                       $result_testdata{$testname});
+    }
+
+    foreach $testname (keys(%{$testdata2}))
+    {
+        # Skip testnames already covered by previous iteration
+        if (defined($testdata1->{$testname})) { next; }
+
+        # testname only present in entry2, add to result hash
+        $result_testdata{$testname} = $testdata2->{$testname};
+
+        # update sum count hash
+        ($result_sumcount, $lines_found, $lines_hit) =
+            add_counts($result_sumcount,
+                       $result_testdata{$testname});
+    }
+
+    # Calculate resulting sumcount
+
+    # Store result
+    set_info_entry(\%result, \%result_testdata, $result_sumcount,
+                   $result_funcdata, $checkdata1, $result_testfncdata,
+                   $result_sumfnccount, $result_testbrdata,
+                   $result_sumbrcount, $lines_found, $lines_hit,
+                   $fn_found, $fn_hit, $br_found, $br_hit);
+
+    return(\%result);
+}
+
+# From genhtml:
+#
+# combine_info_files(info_ref1, info_ref2)
+#
+# Combine .info data in hashes referenced by INFO_REF1 and INFO_REF2. Return
+# reference to resulting hash.
+#
+sub combine_info_files($$)
+{
+    my %hash1 = %{$_[0]};
+    my %hash2 = %{$_[1]};
+    my $filename;
+
+    foreach $filename (keys(%hash2))
+    {
+        if ($hash1{$filename})
+        {
+            # Entry already exists in hash1, combine them
+            $hash1{$filename} =
+                combine_info_entries($hash1{$filename},
+                                     $hash2{$filename},
+                                     $filename);
+        }
+        else
+        {
+            # Entry is unique in both hashes, simply add to
+            # resulting hash
+            $hash1{$filename} = $hash2{$filename};
+        }
+    }
+
+    return(\%hash1);
+}
+
+# From genhtml:
+#
+# add_counts(data1_ref, data2_ref)
+#
+# DATA1_REF and DATA2_REF are references to hashes containing a mapping
+#
+#   line number -> execution count
+#
+# Return a list (RESULT_REF, LINES_FOUND, LINES_HIT) where RESULT_REF
+# is a reference to a hash containing the combined mapping in which
+# execution counts are added.
+#
+sub add_counts($$)
+{
+    my $data1_ref = $_[0];  # Hash 1
+    my $data2_ref = $_[1];  # Hash 2
+    my %result;             # Resulting hash
+    my $line;               # Current line iteration scalar
+    my $data1_count;        # Count of line in hash1
+    my $data2_count;        # Count of line in hash2
+    my $found = 0;          # Total number of lines found
+    my $hit = 0;            # Number of lines with a count > 0
+
+    foreach $line (keys(%$data1_ref))
+    {
+        $data1_count = $data1_ref->{$line};
+        $data2_count = $data2_ref->{$line};
+
+        # Add counts if present in both hashes
+        if (defined($data2_count)) { $data1_count += $data2_count; }
+
+        # Store sum in %result
+        $result{$line} = $data1_count;
+
+        $found++;
+        if ($data1_count > 0) { $hit++; }
+    }
+
+    # Add lines unique to data2_ref
+    foreach $line (keys(%$data2_ref))
+    {
+        # Skip lines already in data1_ref
+        if (defined($data1_ref->{$line})) { next; }
+
+        # Copy count from data2_ref
+        $result{$line} = $data2_ref->{$line};
+
+        $found++;
+        if ($result{$line} > 0) { $hit++; }
+    }
+
+    return (\%result, $found, $hit);
+}
+
+
+# From genhtml:
+sub compress_brcount($)
+{
+    my ($brcount) = @_;
+    my $db;
+
+    $db = brcount_to_db($brcount);
+    return db_to_brcount($db, $brcount);
+}
+
+#
+# brcount_to_db(brcount)
+#
+# Convert brcount data to the following format:
+#
+# db:          line number    -> block hash
+# block hash:  block number   -> branch hash
+# branch hash: branch number  -> taken value
+#
+
+sub brcount_to_db($)
+{
+    my ($brcount) = @_;
+    my $line;
+    my $db;
+
+    # Add branches to database
+    foreach $line (keys(%{$brcount})) {
+        my $brdata = $brcount->{$line};
+
+        foreach my $entry (split(/:/, $brdata)) {
+            my ($block, $branch, $taken) = split(/,/, $entry);
+            my $old = $db->{$line}->{$block}->{$branch};
+
+            if (!defined($old) || $old eq "-") {
+                $old = $taken;
+            } elsif ($taken ne "-") {
+                $old += $taken;
+            }
+
+            $db->{$line}->{$block}->{$branch} = $old;
+        }
+    }
+
+    return $db;
+}
+
+
+#
+# db_to_brcount(db[, brcount])
+#
+# Convert branch coverage data back to brcount format. If brcount is specified,
+# the converted data is directly inserted in brcount.
+#
+
+sub db_to_brcount($;$)
+{
+    my ($db, $brcount) = @_;
+    my $line;
+    my $br_found = 0;
+    my $br_hit = 0;
+
+    # Convert database back to brcount format
+    foreach $line (sort({$a <=> $b} keys(%{$db}))) {
+        my $ldata = $db->{$line};
+        my $brdata;
+        my $block;
+
+        foreach $block (sort({$a <=> $b} keys(%{$ldata}))) {
+            my $bdata = $ldata->{$block};
+            my $branch;
+
+            foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) {
+                my $taken = $bdata->{$branch};
+
+                $br_found++;
+                $br_hit++ if ($taken ne "-" && $taken > 0);
+                $brdata .= "$block,$branch,$taken:";
+            }
+        }
+        $brcount->{$line} = $brdata;
+    }
+
+    return ($brcount, $br_found, $br_hit);
+}
+
+
+#
+# brcount_db_combine(db1, db2, op)
+#
+# db1 := db1 op db2, where
+#   db1, db2: brcount data as returned by brcount_to_db
+#   op:       one of $BR_ADD and BR_SUB
+#
+sub brcount_db_combine($$$)
+{
+    my ($db1, $db2, $op) = @_;
+
+    foreach my $line (keys(%{$db2})) {
+        my $ldata = $db2->{$line};
+
+        foreach my $block (keys(%{$ldata})) {
+            my $bdata = $ldata->{$block};
+
+            foreach my $branch (keys(%{$bdata})) {
+                my $taken = $bdata->{$branch};
+                my $new = $db1->{$line}->{$block}->{$branch};
+
+                if (!defined($new) || $new eq "-") {
+                    $new = $taken;
+                } elsif ($taken ne "-") {
+                    if ($op == $BR_ADD) {
+                        $new += $taken;
+                    } elsif ($op == $BR_SUB) {
+                        $new -= $taken;
+                        $new = 0 if ($new < 0);
+                    }
+                }
+
+                $db1->{$line}->{$block}->{$branch} = $new;
+            }
+        }
+    }
+}
+
+# From genhtml
+sub info(@)
+{
+    if($debug)
+    {
+        # Print info string
+        printf(@_);
+    }
+}
+
+# NEW STUFF
+
 ## Format per file, repeated, no linebreak
 # <diffcmd>
 # index c1..c2 c3
@@ -78,6 +891,12 @@ pod2usage(1) if $opt_help;
 # 3 lines of context
 #
 # output:
+# <dali-source-file>: source / header files in dali/dali-toolkit
+# \%filter: <dali-source-file> -> \%filedata
+# %filedata: "patch"   -> \@checklines
+#            "b_lines" -> \%b_lines
+# @checklines: vector of \[start, length] # line numbers of new/modified lines
+# %b_lines: <line-number> -> patch line in b-file.
 sub parse_diff
 {
     my $patchref = shift;
@@ -184,7 +1003,7 @@ sub parse_diff
         }
     }
 
-    return {%filter};
+    return {%filter};#copy? - test and fixme
 }
 
 sub show_patch_lines
@@ -203,133 +1022,11 @@ sub show_patch_lines
     }
 }
 
-sub get_gcno_file
-{
-    # Assumes test cases have been run, and "make rename_cov_data" has been executed
-
-    my $file = shift;
-    my ($name, $path, $suffix) = fileparse($file, (".c", ".cpp", ".h"));
-    my $gcno_file = $repo->wc_path() . "/build/tizen/.cov/$name.gcno";
-
-    # Note, will translate headers to their source's object, which
-    # may miss execution code in the headers (e.g. inlines are usually
-    # not all used in the implementation, and require getting coverage
-    # from test cases.
-
-    if( -f $gcno_file )
-    {
-        my $gcno_st = stat($gcno_file);
-        my $fq_file = $repo->wc_path() . $file;
-        my $src_st = stat($fq_file);
-        if($gcno_st->ctime < $src_st->mtime)
-        {
-            print "WARNING: GCNO $gcno_file older than SRC $fq_file\n";
-            $gcno_file="";
-        }
-
-    }
-    else
-    {
-        print("WARNING: No equivalent gcno file for $file\n");
-    }
-    return $gcno_file;
-}
-
-our %gcovfiles=();
-sub get_coverage
-{
-    my $file = shift;
-    my $filesref = shift;
-    print("get_coverage($file)\n") if $debug;
-
-    my $gcno_file = get_gcno_file($file);
-    my @gcov_files = ();
-    my $gcovfile;
-    if( $gcno_file )
-    {
-        print "Running gcov on $gcno_file:\n" if $debug;
-        open( my $fh,  "gcov --preserve-paths $gcno_file |") || die "Can't run gcov:$!\n";
-        while( <$fh> )
-        {
-            print $_ if $debug>=3;
-            chomp;
-            if( m!'(.*\.gcov)'$! )
-            {
-                my $coverage_file = $1; # File has / replaced with # and .. replaced with ^
-                my $source_file = $coverage_file;
-                $source_file =~ s!\^!..!g;  # Change ^ to ..
-                $source_file =~ s!\#!/!g;   # change #'s to /s
-                $source_file =~ s!.gcov$!!; # Strip off .gcov suffix
-
-                print "Matching $file against $source_file\n" if $debug >= 3;
-                # Only want the coverage files matching source file:
-                if(index( $source_file, $file ) > 0 )
-                {
-                    $gcovfile = $coverage_file;
-                    # Some header files do not produce an equivalent gcov file so we shouldn't parse them
-                    if(($source_file =~ /\.h$/) && (! -e $gcovfile))
-                    {
-                        print "Omitting Header: $source_file\n" if $debug;
-                        $gcovfile = ""
-                    }
-                    last;
-                }
-            }
-        }
-        close($fh);
-
-        if($gcovfile)
-        {
-            if($gcovfiles{$gcovfile} == undef)
-            {
-                # Only parse a gcov file once
-                $gcovfiles{$gcovfile}->{"seen"}=1;
-
-                print "Getting coverage data from $gcovfile\n" if $debug;
 
-                open( FH, "< $gcovfile" ) || die "Can't open $gcovfile for reading:$!\n";
-                while(<FH>)
-                {
-                    my ($cov, $line, @code ) = split( /:/, $_ );
-                    $cov =~ s/^\s+//; # Strip leading space
-                    $line =~ s/^\s+//;
-                    my $code=join(":", @code);
-                    if($cov =~ /\#/)
-                    {
-                        # There is no coverage data for these executable lines
-                        $gcovfiles{$gcovfile}->{"uncovered"}->{$line}++;
-                        $gcovfiles{$gcovfile}->{"src"}->{$line}=$code;
-                    }
-                    elsif( $cov ne "-" && looks_like_number($cov) && $cov > 0 )
-                    {
-                        $gcovfiles{$gcovfile}->{"covered"}->{$line}=$cov;
-                        $gcovfiles{$gcovfile}->{"src"}->{$line}=$code;
-                    }
-                    else
-                    {
-                        # All other lines are not executable.
-                        $gcovfiles{$gcovfile}->{"src"}->{$line}=$code;
-                    }
-                }
-                close( FH );
-            }
-            $filesref->{$file}->{"coverage"} = $gcovfiles{$gcovfile}; # store hashref
-        }
-        else
-        {
-            # No gcov output - the gcno file produced no coverage of the source/header
-            # Probably means that there is no coverage for the file (with the given
-            # test case - there may be some somewhere, but for the sake of speed, don't
-            # check (yet).
-        }
-    }
-}
-
-# Run the git diff command to get the patch, then check the coverage
-# output for the patch.
+# Run the git diff command to get the patch
+# Output - see parse_diff
 sub run_diff
 {
-    #print "run_diff(" . join(" ", @_) . ")\n";
     my ($fh, $c) = $repo->command_output_pipe(@_);
     our @patch=();
     while(<$fh>)
@@ -343,23 +1040,8 @@ sub run_diff
 
     # @patch has slurped diff for all files...
     my $filesref = parse_diff ( \@patch );
-    show_patch_lines($filesref) if $debug;
-
-    print "Checking coverage:\n" if $debug;
+    show_patch_lines($filesref) if $debug>1;
 
-    my $cwd=getcwd();
-    chdir ".cov" || die "Can't find $cwd/.cov:$!\n";
-
-    for my $file (keys(%$filesref))
-    {
-        my ($name, $path, $suffix) = fileparse($file, qr{\.[^.]*$});
-        next if($path !~ /^dali/);
-        if($suffix eq ".cpp" || $suffix eq ".c" || $suffix eq ".h")
-        {
-            get_coverage($file, $filesref);
-        }
-    }
-    chdir $cwd;
     return $filesref;
 }
 
@@ -378,35 +1060,45 @@ sub calc_patch_coverage_percentage
         my $uncovered_lines = 0;
 
         my $patchref = $filesref->{$file}->{"patch"};
-        my $coverage_ref = $filesref->{$file}->{"coverage"};
-        if( $coverage_ref )
+
+        my $abs_filename = File::Spec->rel2abs($file, $root);
+        my $sumcountref = $info_data{$abs_filename}->{"sum"};
+
+        if( $sumcountref )
         {
             for my $patch (@$patchref)
             {
                 for(my $i = 0; $i < $patch->[1]; $i++ )
                 {
                     my $line = $i + $patch->[0];
-                    if($coverage_ref->{"covered"}->{$line})
-                    {
-                        $covered_lines++;
-                        $total_covered_lines++;
-                    }
-                    if($coverage_ref->{"uncovered"}->{$line})
+                    if(exists($sumcountref->{$line}))
                     {
-                        $uncovered_lines++;
-                        $total_uncovered_lines++;
+                        if( $sumcountref->{$line} > 0 )
+                        {
+                            $covered_lines++;
+                            $total_covered_lines++;
+                        }
+                        else
+                        {
+                            $uncovered_lines++;
+                            $total_uncovered_lines++;
+                        }
                     }
                 }
             }
-            $coverage_ref->{"covered_lines"} = $covered_lines;
-            $coverage_ref->{"uncovered_lines"} = $uncovered_lines;
+            $filesref->{$file}->{"covered_lines"} = $covered_lines;
+            $filesref->{$file}->{"uncovered_lines"} = $uncovered_lines;
             my $total = $covered_lines + $uncovered_lines;
             my $percent = 0;
             if($total > 0)
             {
                 $percent = $covered_lines / $total;
             }
-            $coverage_ref->{"percent_covered"} = 100 * $percent;
+            $filesref->{$file}->{"percent_covered"} = 100 * $percent;
+        }
+        else
+        {
+            print "Can't find coverage data for $abs_filename\n";
         }
     }
     my $total_exec = $total_covered_lines + $total_uncovered_lines;
@@ -416,6 +1108,8 @@ sub calc_patch_coverage_percentage
     return [ $total_exec, $percent ];
 }
 
+#
+#
 sub patch_output
 {
     my $filesref = shift;
@@ -424,18 +1118,22 @@ sub patch_output
         my ($name, $path, $suffix) = fileparse($file, qr{\.[^.]*$});
         next if($path !~ /^dali/);
 
-        my $patchref = $filesref->{$file}->{"patch"};
-        my $b_lines_ref = $filesref->{$file}->{"b_lines"};
-        my $coverage_ref = $filesref->{$file}->{"coverage"};
+        my $fileref = $filesref->{$file};
+        my $patchref = $fileref->{"patch"};
+        my $b_lines_ref = $fileref->{"b_lines"};
+
+        my $abs_filename = File::Spec->rel2abs($file, $root);
+        my $sumcountref = $info_data{$abs_filename}->{"sum"};
+
         print BOLD, "$file  ";
 
-        if($coverage_ref)
+        if($fileref)
         {
-            if( $coverage_ref->{"covered_lines"} > 0
+            if( $fileref->{"covered_lines"} > 0
                 ||
-                $coverage_ref->{"uncovered_lines"} > 0 )
+                $fileref->{"uncovered_lines"} > 0 )
             {
-                print GREEN, "Covered: " . $coverage_ref->{"covered_lines"}, RED, " Uncovered: " . $coverage_ref->{"uncovered_lines"}, RESET;
+                print GREEN, "Covered: " . $fileref->{"covered_lines"}, RED, " Uncovered: " . $fileref->{"uncovered_lines"}, RESET;
             }
         }
         else
@@ -461,28 +1159,29 @@ sub patch_output
                 my $line = $i + $patch->[0];
                 printf "%-6s  ", $line;
 
-                if($coverage_ref)
+                if($sumcountref)
                 {
                     my $color;
-                    if($coverage_ref->{"covered"}->{$line})
-                    {
-                        $color=GREEN;
-                    }
-                    elsif($coverage_ref->{"uncovered"}->{$line})
+                    if(exists($sumcountref->{$line}))
                     {
-                        $color=BOLD . RED;
+                        if($sumcountref->{$line} > 0)
+                        {
+                            $color=GREEN;
+                        }
+                        else
+                        {
+                            $color=BOLD . RED;
+                        }
                     }
                     else
                     {
-                        $color=BLACK;
+                        $color=CYAN;
                     }
-                    my $src=$coverage_ref->{"src"}->{$line};
-                    chomp($src);
+                    my $src = $b_lines_ref->{$line};
                     print $color, "$src\n", RESET;
                 }
                 else
                 {
-                    # We don't have coverage data, so print it from the patch instead.
                     my $src = $b_lines_ref->{$line};
                     print "$src\n";
                 }
@@ -491,70 +1190,65 @@ sub patch_output
     }
 }
 
-
+#
+#
 sub patch_html_output
 {
     my $filesref = shift;
 
-    my $html = HTML::Element->new('html');
-    my $head = HTML::Element->new('head');
-    my $title = HTML::Element->new('title');
-    $title->push_content("Patch Coverage");
-    $head->push_content($title, "\n");
-    $html->push_content($head, "\n");
+    open( my $filehandle, ">", $opt_output ) || die "Can't open $opt_output for writing:$!\n";
 
-    my $body = HTML::Element->new('body');
-    $body->attr('bgcolor', "white");
+    my $OUTPUT_FH = select;
+    select $filehandle;
+    print <<EOH;
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
+"http://www.w3.org/TR/REC-html40/loose.dtd">
+<html>
+<head>
+<title>Patch Coverage</title>
+</head>
+<body bgcolor="white">
+EOH
 
-    foreach my $file (sort(keys(%$filesref)))
+    foreach my $file (keys(%$filesref))
     {
         my ($name, $path, $suffix) = fileparse($file, qr{\.[^.]*$});
         next if($path !~ /^dali/);
 
-        my $patchref = $filesref->{$file}->{"patch"};
-        my $b_lines_ref = $filesref->{$file}->{"b_lines"};
-        my $coverage_ref = $filesref->{$file}->{"coverage"};
-
-        my $header = HTML::Element->new('h2');
-        $header->push_content($file);
-        $body->push_content($header);
-        $body->push_content("\n");
-        if($coverage_ref)
+        my $fileref = $filesref->{$file};
+        my $patchref = $fileref->{"patch"};
+        my $b_lines_ref = $fileref->{"b_lines"};
+
+        my $abs_filename = File::Spec->rel2abs($file, $root);
+        my $sumcountref = $info_data{$abs_filename}->{"sum"};
+
+        print "<h2>$file</h2>\n";
+
+        if($fileref)
         {
-            if( $coverage_ref->{"covered_lines"} > 0
+            if( $fileref->{"covered_lines"} > 0
                 ||
-                $coverage_ref->{"uncovered_lines"} > 0 )
+                $fileref->{"uncovered_lines"} > 0 )
             {
-                my $para = HTML::Element->new('p');
-                my $covered = HTML::Element->new('span');
-                $covered->attr('style', "color:green;");
-                $covered->push_content("Covered: " . $coverage_ref->{"covered_lines"} );
-                $para->push_content($covered);
-
-                my $para2 = HTML::Element->new('p');
-                my $uncovered = HTML::Element->new('span');
-                $uncovered->attr('style', "color:red;");
-                $uncovered->push_content("Uncovered: " . $coverage_ref->{"uncovered_lines"} );
-                $para2->push_content($uncovered);
-                $body->push_content($para, $para2);
-            }
-            else
-            {
-                #print "coverage ref exists for $file:\n" . Data::Dumper::Dumper($coverage_ref) . "\n";
+                print "<p style=\"color:green;\">Covered: " .
+                    $fileref->{"covered_lines"} . "<p>" .
+                    "<p style=\"color:red;\">Uncovered: " .
+                    $fileref->{"uncovered_lines"} . "</span></p>";
             }
         }
         else
         {
-            my $para = HTML::Element->new('p');
-            my $span = HTML::Element->new('span');
+            print "<p>";
+            my $span=0;
             if($suffix eq ".cpp" || $suffix eq ".c" || $suffix eq ".h")
             {
-                $span->attr('style', "color:red;");
+                print "<span style=\"color:red;\">";
+                $span=1;
             }
-            $span->push_content("No coverage found");
-            $para->push_content($span);
-            $body->push_content($para);
+            print "No coverage found";
+            print "</span>" if $span;
         }
+        print "</p>";
 
         for my $patch (@$patchref)
         {
@@ -563,71 +1257,54 @@ sub patch_html_output
             {
                 $hunkstr .= " - " . ($patch->[0]+$patch->[1]-1);
             }
+            print "<p style=\"font-weight:bold;\">" . $hunkstr . "</p>";
 
-            my $para = HTML::Element->new('p');
-            my $span = HTML::Element->new('span');
-            $span->attr('style', "font-weight:bold;");
-            $span->push_content($hunkstr);
-            $para->push_content($span);
-            $body->push_content($para);
-
-            my $codeHunk = HTML::Element->new('pre');
+            print "<pre>";
             for(my $i = 0; $i < $patch->[1]; $i++ )
             {
                 my $line = $i + $patch->[0];
                 my $num_line_digits=log($line)/log(10);
                 for $i (0..(6-$num_line_digits-1))
                 {
-                    $codeHunk->push_content(" ");
+                    print " ";
                 }
+                print "$line  ";
 
-                $codeHunk->push_content("$line  ");
-
-                my $srcLine = HTML::Element->new('span');
-                if($coverage_ref)
+                if($sumcountref)
                 {
                     my $color;
-
-                    if($coverage_ref->{"covered"}->{$line})
-                    {
-                        $srcLine->attr('style', "color:green;");
-                    }
-                    elsif($coverage_ref->{"uncovered"}->{$line})
+                    if(exists($sumcountref->{$line}))
                     {
-                        $srcLine->attr('style', "color:red;font-weight:bold;");
+                        if($sumcountref->{$line} > 0)
+                        {
+                            print("<span style=\"color:green;\">");
+                        }
+                        else
+                        {
+                            print("<span style=\"color:red;font-weight:bold;\">");
+                        }
                     }
                     else
                     {
-                        $srcLine->attr('style', "color:black;font-weight:normal;");
+                        print("<span style=\"color:black;font-weight:normal;\">");
                     }
-                    my $src=$coverage_ref->{"src"}->{$line};
+                    my $src=$b_lines_ref->{$line};
                     chomp($src);
-                    $srcLine->push_content($src);
+                    print "$src</span>\n";
                 }
                 else
                 {
-                    # We don't have coverage data, so print it from the patch instead.
                     my $src = $b_lines_ref->{$line};
-                    $srcLine->attr('style', "color:black;font-weight:normal;");
-                    $srcLine->push_content($src);
+                    print "$src\n";
                 }
-                $codeHunk->push_content($srcLine, "\n");
             }
-            $body->push_content($codeHunk, "\n");
+            print "<\pre>\n";
         }
     }
-    $body->push_content(HTML::Element->new('hr'));
-    $html->push_content($body, "\n");
-
-    open( my $filehandle, ">", $opt_output ) || die "Can't open $opt_output for writing:$!\n";
 
-    print $filehandle <<EOH;
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
-"http://www.w3.org/TR/REC-html40/loose.dtd">
-EOH
-;
-    print $filehandle $html->as_HTML();
+    print $filehandle "<hr>\n</body>\n</html>\n";
     close $filehandle;
+    select $OUTPUT_FH;
 }
 
 
@@ -635,15 +1312,38 @@ EOH
 ##                                    MAIN                                    ##
 ################################################################################
 
-my $cwd = getcwd();
-chdir $repo->wc_path();
-chdir "build/tizen";
-`make rename_cov_data`;
+my $cwd = getcwd(); # expect this to be automated-tests folder
 
-my @cmd=('--no-pager','diff','--no-ext-diff','-U0','--no-color');
+# execute coverage.sh, generating build/tizen/dali.info from lib, and
+# *.dir/dali.info. Don't generate html
+`coverage.sh -n`;
+chdir "..";
+$root = getcwd();
+
+our %info_data; # Hash of all data from .info files
+my @info_files = split(/\n/, `find . -name dali.info`);
+my %new_info;
 
+# Read in all specified .info files
+foreach (@info_files)
+{
+    %new_info = %{read_info_file($_)};
+
+    # Combine %new_info with %info_data
+    %info_data = %{combine_info_files(\%info_data, \%new_info)};
+}
+
+
+# Generate git diff command
+my @cmd=('--no-pager','diff','--no-ext-diff','-U0','--no-color');
 my $status = $repo->command("status", "-s");
-if( $status eq "" && !scalar(@ARGV))
+
+if(scalar(@ARGV)) # REMOVE ME
+{
+    # REMOVE ME - temp to get past modifying this script in place.
+    push @cmd, @ARGV;
+}
+elsif( $status eq "" && !scalar(@ARGV))
 {
     # There are no changes in the index or working tree, and
     # no diff arguments to append. Use the last patch instead.
@@ -674,6 +1374,8 @@ else
 }
 
 push @cmd, @ARGV;
+
+# Execute diff & coverage from root directory
 my $filesref = run_diff(@cmd);
 
 chdir $cwd;
@@ -693,6 +1395,9 @@ if( $filecount == 0 )
     exit 0;    # Exit with no error.
 }
 
+#print_simplified_info() if $debug;
+#exit 0;
+
 my $percentref = calc_patch_coverage_percentage($filesref);
 if($percentref->[0] == 0)
 {
index 7b0b54d..f31da7c 100644 (file)
@@ -2749,11 +2749,20 @@ void OnResourceReadySignal( Control control )
 {
   gResourceReadySignalCounter++;
 
-  if( gResourceReadySignalCounter == 1 )
+  if(control.GetVisualResourceStatus(ImageView::Property::IMAGE) == Visual::ResourceStatus::READY)
   {
-    // Set image twice
-    ImageView::DownCast( control ).SetImage( gImage_34_RGBA );
-    ImageView::DownCast( control ).SetImage( gImage_34_RGBA );
+    if( gResourceReadySignalCounter == 1 )
+    {
+      // Set image twice
+      // It makes the first new visual be deleted immediately
+      ImageView::DownCast( control ).SetImage( gImage_34_RGBA );
+      ImageView::DownCast( control ).SetImage( gImage_34_RGBA );
+    }
+  }
+  else if(control.GetVisualResourceStatus(ImageView::Property::IMAGE) == Visual::ResourceStatus::FAILED)
+  {
+    // Make the resource ready immediately
+    control[ImageView::Property::IMAGE] = TEST_RESOURCE_DIR "/svg1.svg";
   }
 }
 
@@ -2781,5 +2790,22 @@ int UtcDaliImageViewSetImageOnResourceReadySignal(void)
 
   DALI_TEST_EQUALS( imageView.IsResourceReady(), true, TEST_LOCATION );
 
+  // Reset count
+  gResourceReadySignalCounter = 0;
+
+  imageView[ImageView::Property::IMAGE] = "invalid.jpg";
+
+  DALI_TEST_EQUALS( Test::WaitForEventThreadTrigger( 1 ), true, TEST_LOCATION );
+
+  application.SendNotification();
+  application.Render();
+
+  // Run idle callback
+  application.RunIdles();
+
+  DALI_TEST_EQUALS( gResourceReadySignalCounter, 2, TEST_LOCATION );
+
+  DALI_TEST_EQUALS( imageView.IsResourceReady(), true, TEST_LOCATION );
+
   END_TEST;
 }
index 8a2dfd7..368ab0a 100755 (executable)
@@ -26,6 +26,7 @@
 #include <dali/devel-api/scripting/scripting.h>
 #include <dali/integration-api/debug.h>
 #include <dali/public-api/object/type-registry-helper.h>
+#include <dali/integration-api/adaptor-framework/adaptor.h>
 #include <cstring>
 #include <limits>
 
@@ -322,7 +323,7 @@ Control::Impl::Impl( Control& controlImpl )
   mDownFocusableActorId( -1 ),
   mStyleName(""),
   mBackgroundColor(Color::TRANSPARENT),
-  mStartingPinchScale( NULL ),
+  mStartingPinchScale(nullptr),
   mMargin( 0, 0, 0, 0 ),
   mPadding( 0, 0, 0, 0 ),
   mKeyEventSignal(),
@@ -336,9 +337,12 @@ Control::Impl::Impl( Control& controlImpl )
   mLongPressGestureDetector(),
   mTooltip( NULL ),
   mInputMethodContext(),
+  mIdleCallback(nullptr),
   mFlags( Control::ControlBehaviour( CONTROL_BEHAVIOUR_DEFAULT ) ),
   mIsKeyboardNavigationSupported( false ),
-  mIsKeyboardFocusGroup( false )
+  mIsKeyboardFocusGroup( false ),
+  mIsEmittingResourceReadySignal(false),
+  mNeedToEmitResourceReady(false)
 {
 }
 
@@ -346,6 +350,12 @@ Control::Impl::~Impl()
 {
   // All gesture detectors will be destroyed so no need to disconnect.
   delete mStartingPinchScale;
+
+  if(mIdleCallback && Adaptor::IsAvailable())
+  {
+    // Removes the callback from the callback manager in case the control is destroyed before the callback is executed.
+    Adaptor::Get().RemoveIdle(mIdleCallback);
+  }
 }
 
 Control::Impl& Control::Impl::Get( Internal::Control& internalControl )
@@ -670,8 +680,10 @@ void Control::Impl::ResourceReady( Visual::Base& object)
   // Emit signal if all enabled visuals registered by the control are ready.
   if( IsResourceReady() )
   {
-    Dali::Toolkit::Control handle( mControlImpl.GetOwner() );
-    mResourceReadySignal.Emit( handle );
+    // Reset the flag
+    mNeedToEmitResourceReady = false;
+
+    EmitResourceReadySignal();
   }
 }
 
@@ -1448,6 +1460,58 @@ void Control::Impl::ClearShadow()
    mControlImpl.RelayoutRequest();
 }
 
+void Control::Impl::EmitResourceReadySignal()
+{
+  if(!mIsEmittingResourceReadySignal)
+  {
+    // Guard against calls to emit the signal during the callback
+    mIsEmittingResourceReadySignal = true;
+
+    // If the signal handler changes visual, it may become ready during this call & therefore this method will
+    // get called again recursively. If so, mNeedToEmitResourceReady is set below, and we act on it after that secondary
+    // invocation has completed by notifying in an Idle callback to prevent further recursion.
+    Dali::Toolkit::Control handle(mControlImpl.GetOwner());
+    mResourceReadySignal.Emit(handle);
+
+    if(mNeedToEmitResourceReady)
+    {
+      // Add idler to emit the signal again
+      if(!mIdleCallback)
+      {
+        // The callback manager takes the ownership of the callback object.
+        mIdleCallback = MakeCallback( this, &Control::Impl::OnIdleCallback);
+        Adaptor::Get().AddIdle(mIdleCallback, false);
+      }
+    }
+
+    mIsEmittingResourceReadySignal = false;
+  }
+  else
+  {
+    mNeedToEmitResourceReady = true;
+  }
+}
+
+void Control::Impl::OnIdleCallback()
+{
+  if(mNeedToEmitResourceReady)
+  {
+    // Reset the flag
+    mNeedToEmitResourceReady = false;
+
+    // A visual is ready so control may need relayouting if staged
+    if(mControlImpl.Self().GetProperty<bool>(Actor::Property::CONNECTED_TO_SCENE))
+    {
+      mControlImpl.RelayoutRequest();
+    }
+
+    EmitResourceReadySignal();
+  }
+
+  // Set the pointer to null as the callback manager deletes the callback after execute it.
+  mIdleCallback = nullptr;
+}
+
 } // namespace Internal
 
 } // namespace Toolkit
index 43cab69..e042113 100755 (executable)
@@ -386,6 +386,16 @@ private:
    */
   void RegisterVisual( Property::Index index, Toolkit::Visual::Base& visual, VisualState::Type enabled, DepthIndexValue::Type depthIndexValueSet, int depthIndex = 0 );
 
+  /**
+   * @brief Emits the resource ready signal.
+   */
+  void EmitResourceReadySignal();
+
+  /**
+   * @brief Callbacks called on idle.
+   */
+  void OnIdleCallback();
+
 public:
 
   Control& mControlImpl;
@@ -419,10 +429,13 @@ public:
   TooltipPtr mTooltip;
 
   InputMethodContext mInputMethodContext;
+  CallbackBase* mIdleCallback;             ///< The idle callback to emit the resource ready signal.
 
   ControlBehaviour mFlags : CONTROL_BEHAVIOUR_FLAG_COUNT;    ///< Flags passed in from constructor.
   bool mIsKeyboardNavigationSupported :1;  ///< Stores whether keyboard navigation is supported by the control.
   bool mIsKeyboardFocusGroup :1;           ///< Stores whether the control is a focus group.
+  bool mIsEmittingResourceReadySignal :1;  ///< True during ResourceReady().
+  bool mNeedToEmitResourceReady :1;        ///< True if need to emit the resource ready signal again.
 
   RegisteredVisualContainer mRemoveVisuals;         ///< List of visuals that are being replaced by another visual once ready
 
index 1cdb0f8..d1c1b1a 100755 (executable)
@@ -62,33 +62,29 @@ namespace Internal
 
 namespace
 {
-  const unsigned int DEFAULT_RENDERING_BACKEND = Dali::Toolkit::DevelText::DEFAULT_RENDERING_BACKEND;
-
-  /**
-   * @brief How the text visual should be aligned vertically inside the control.
-   *
-   * 0.0f aligns the text to the top, 0.5f aligns the text to the center, 1.0f aligns the text to the bottom.
-   * The alignment depends on the alignment value of the text label (Use Text::VerticalAlignment enumerations).
-   */
-  const float VERTICAL_ALIGNMENT_TABLE[ Text::VerticalAlignment::BOTTOM + 1 ] =
-  {
-    0.0f,  // VerticalAlignment::TOP
-    0.5f,  // VerticalAlignment::CENTER
-    1.0f   // VerticalAlignment::BOTTOM
-  };
-
-  const std::string TEXT_FIT_ENABLE_KEY( "enable" );
-  const std::string TEXT_FIT_MIN_SIZE_KEY( "minSize" );
-  const std::string TEXT_FIT_MAX_SIZE_KEY( "maxSize" );
-  const std::string TEXT_FIT_STEP_SIZE_KEY( "stepSize" );
-  const std::string TEXT_FIT_FONT_SIZE_TYPE_KEY( "fontSizeType" );
-}
+const unsigned int DEFAULT_RENDERING_BACKEND = Dali::Toolkit::DevelText::DEFAULT_RENDERING_BACKEND;
 
-namespace
+/**
+ * @brief How the text visual should be aligned vertically inside the control.
+ *
+ * 0.0f aligns the text to the top, 0.5f aligns the text to the center, 1.0f aligns the text to the bottom.
+ * The alignment depends on the alignment value of the text label (Use Text::VerticalAlignment enumerations).
+ */
+const float VERTICAL_ALIGNMENT_TABLE[ Text::VerticalAlignment::BOTTOM + 1 ] =
 {
+  0.0f,  // VerticalAlignment::TOP
+  0.5f,  // VerticalAlignment::CENTER
+  1.0f   // VerticalAlignment::BOTTOM
+};
+
+const std::string TEXT_FIT_ENABLE_KEY( "enable" );
+const std::string TEXT_FIT_MIN_SIZE_KEY( "minSize" );
+const std::string TEXT_FIT_MAX_SIZE_KEY( "maxSize" );
+const std::string TEXT_FIT_STEP_SIZE_KEY( "stepSize" );
+const std::string TEXT_FIT_FONT_SIZE_TYPE_KEY( "fontSizeType" );
 
 #if defined ( DEBUG_ENABLED )
-  Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
 #endif
 
 const Scripting::StringEnum AUTO_SCROLL_STOP_MODE_TABLE[] =
@@ -145,6 +141,73 @@ DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION( Toolkit,    TextLabel, "textCol
 DALI_ANIMATABLE_PROPERTY_COMPONENT_REGISTRATION( Toolkit,    TextLabel, "textColorAlpha", TEXT_COLOR_ALPHA, TEXT_COLOR, 3  )
 DALI_TYPE_REGISTRATION_END()
 
+/// Parses the property map for the TEXT_FIT property
+void ParseTextFitProperty(Text::ControllerPtr& controller, const Property::Map* propertiesMap)
+{
+  if ( propertiesMap && !propertiesMap->Empty() )
+  {
+    bool enabled = false;
+    float minSize = 0.f;
+    float maxSize = 0.f;
+    float stepSize = 0.f;
+    bool isMinSizeSet = false, isMaxSizeSet = false, isStepSizeSet = false;
+    Controller::FontSizeType type = Controller::FontSizeType::POINT_SIZE;
+
+    const unsigned int numberOfItems = propertiesMap->Count();
+
+    // Parses and applies
+    for( unsigned int index = 0u; index < numberOfItems; ++index )
+    {
+      const KeyValuePair& valueGet = propertiesMap->GetKeyValue( index );
+
+      if( ( Controller::TextFitInfo::Property::TEXT_FIT_ENABLE == valueGet.first.indexKey ) || ( TEXT_FIT_ENABLE_KEY == valueGet.first.stringKey ) )
+      {
+        /// Enable key.
+        enabled = valueGet.second.Get< bool >();
+      }
+      else if( ( Controller::TextFitInfo::Property::TEXT_FIT_MIN_SIZE == valueGet.first.indexKey ) || ( TEXT_FIT_MIN_SIZE_KEY == valueGet.first.stringKey ) )
+      {
+        /// min size.
+        minSize = valueGet.second.Get< float >();
+        isMinSizeSet = true;
+      }
+      else if( ( Controller::TextFitInfo::Property::TEXT_FIT_MAX_SIZE == valueGet.first.indexKey ) || ( TEXT_FIT_MAX_SIZE_KEY == valueGet.first.stringKey ) )
+      {
+        /// max size.
+        maxSize = valueGet.second.Get< float >();
+        isMaxSizeSet = true;
+      }
+      else if( ( Controller::TextFitInfo::Property::TEXT_FIT_STEP_SIZE == valueGet.first.indexKey ) || ( TEXT_FIT_STEP_SIZE_KEY == valueGet.first.stringKey ) )
+      {
+        /// step size.
+        stepSize = valueGet.second.Get< float >();
+        isStepSizeSet = true;
+      }
+      else if( ( Controller::TextFitInfo::Property::TEXT_FIT_FONT_SIZE_TYPE == valueGet.first.indexKey ) || ( TEXT_FIT_FONT_SIZE_TYPE_KEY == valueGet.first.stringKey ) )
+      {
+        if( "pixelSize" == valueGet.second.Get< std::string >() )
+        {
+          type = Controller::FontSizeType::PIXEL_SIZE;
+        }
+      }
+    }
+
+    controller->SetTextFitEnabled( enabled );
+    if( isMinSizeSet )
+    {
+      controller->SetTextFitMinSize( minSize, type );
+    }
+    if( isMaxSizeSet )
+    {
+      controller->SetTextFitMaxSize( maxSize, type );
+    }
+    if( isStepSizeSet )
+    {
+      controller->SetTextFitStepSize( stepSize, type );
+    }
+  }
+}
+
 } // namespace
 
 Toolkit::TextLabel TextLabel::New()
@@ -275,101 +338,61 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr
       }
       case Toolkit::TextLabel::Property::AUTO_SCROLL_STOP_MODE:
       {
-        if( !impl.mTextScroller )
-        {
-          impl.mTextScroller = Text::TextScroller::New( impl );
-        }
-        Toolkit::TextLabel::AutoScrollStopMode::Type stopMode = impl.mTextScroller->GetStopMode();
+        Text::TextScrollerPtr textScroller = impl.GetTextScroller();
+        Toolkit::TextLabel::AutoScrollStopMode::Type stopMode = textScroller->GetStopMode();
         if( Scripting::GetEnumerationProperty< Toolkit::TextLabel::AutoScrollStopMode::Type >( value,
-                                                                                                    AUTO_SCROLL_STOP_MODE_TABLE,
-                                                                                                    AUTO_SCROLL_STOP_MODE_TABLE_COUNT,
-                                                                                                    stopMode ) )
+                                                                                               AUTO_SCROLL_STOP_MODE_TABLE,
+                                                                                               AUTO_SCROLL_STOP_MODE_TABLE_COUNT,
+                                                                                               stopMode ) )
         {
-            impl.mTextScroller->SetStopMode( stopMode );
+          textScroller->SetStopMode( stopMode );
         }
         break;
       }
       case Toolkit::TextLabel::Property::AUTO_SCROLL_SPEED:
       {
-        if( !impl.mTextScroller )
-        {
-          impl.mTextScroller = Text::TextScroller::New( impl );
-        }
-        impl.mTextScroller->SetSpeed( value.Get<int>() );
+        impl.GetTextScroller()->SetSpeed( value.Get<int>() );
         break;
       }
       case Toolkit::TextLabel::Property::AUTO_SCROLL_LOOP_COUNT:
       {
-        if( !impl.mTextScroller )
-        {
-          impl.mTextScroller = Text::TextScroller::New( impl );
-        }
-        impl.mTextScroller->SetLoopCount( value.Get<int>() );
+        impl.GetTextScroller()->SetLoopCount( value.Get<int>() );
         break;
       }
       case Toolkit::TextLabel::Property::AUTO_SCROLL_LOOP_DELAY:
       {
-         if( !impl.mTextScroller )
-        {
-          impl.mTextScroller = Text::TextScroller::New( impl );
-        }
-        impl.mTextScroller->SetLoopDelay( value.Get<float>() );
+        impl.GetTextScroller()->SetLoopDelay( value.Get<float>() );
         break;
       }
       case Toolkit::TextLabel::Property::AUTO_SCROLL_GAP:
       {
-        if( !impl.mTextScroller )
-        {
-          impl.mTextScroller = Text::TextScroller::New( impl );
-        }
-        impl.mTextScroller->SetGap( value.Get<float>() );
+        impl.GetTextScroller()->SetGap( value.Get<float>() );
         break;
       }
       case Toolkit::TextLabel::Property::LINE_SPACING:
       {
         const float lineSpacing = value.Get<float>();
-
-        // Don't trigger anything if the line spacing didn't change
-        if( impl.mController->SetDefaultLineSpacing( lineSpacing ) )
-        {
-          impl.mTextUpdateNeeded = true;
-        }
+        impl.mTextUpdateNeeded = impl.mController->SetDefaultLineSpacing( lineSpacing ) || impl.mTextUpdateNeeded;
         break;
       }
       case Toolkit::TextLabel::Property::UNDERLINE:
       {
-        const bool update = SetUnderlineProperties( impl.mController, value, Text::EffectStyle::DEFAULT );
-        if( update )
-        {
-          impl.mTextUpdateNeeded = true;
-        }
+        impl.mTextUpdateNeeded = SetUnderlineProperties( impl.mController, value, Text::EffectStyle::DEFAULT ) || impl.mTextUpdateNeeded;
         break;
       }
       case Toolkit::TextLabel::Property::SHADOW:
       {
-        const bool update = SetShadowProperties( impl.mController, value, Text::EffectStyle::DEFAULT );
-        if( update )
-        {
-          impl.mTextUpdateNeeded = true;
-        }
+        impl.mTextUpdateNeeded = SetShadowProperties( impl.mController, value, Text::EffectStyle::DEFAULT ) || impl.mTextUpdateNeeded;
         break;
       }
       case Toolkit::TextLabel::Property::EMBOSS:
       {
-        const bool update = SetEmbossProperties( impl.mController, value, Text::EffectStyle::DEFAULT );
-        if( update )
-        {
-          impl.mTextUpdateNeeded = true;
-        }
+        impl.mTextUpdateNeeded = SetEmbossProperties( impl.mController, value, Text::EffectStyle::DEFAULT ) || impl.mTextUpdateNeeded;
         break;
       }
       case Toolkit::TextLabel::Property::OUTLINE:
       {
-        const bool update = SetOutlineProperties( impl.mController, value, Text::EffectStyle::DEFAULT );
-        if( update )
-        {
-          impl.mTextUpdateNeeded = true;
-        }
+        impl.mTextUpdateNeeded = SetOutlineProperties( impl.mController, value, Text::EffectStyle::DEFAULT ) || impl.mTextUpdateNeeded;
         break;
       }
       case Toolkit::TextLabel::Property::PIXEL_SIZE:
@@ -419,11 +442,7 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr
       }
       case Toolkit::DevelTextLabel::Property::BACKGROUND:
       {
-        const bool update = SetBackgroundProperties( impl.mController, value, Text::EffectStyle::DEFAULT );
-        if( update )
-        {
-          impl.mTextUpdateNeeded = true;
-        }
+        impl.mTextUpdateNeeded = SetBackgroundProperties( impl.mController, value, Text::EffectStyle::DEFAULT ) || impl.mTextUpdateNeeded;
         break;
       }
       case Toolkit::DevelTextLabel::Property::IGNORE_SPACES_AFTER_TEXT:
@@ -438,80 +457,13 @@ void TextLabel::SetProperty( BaseObject* object, Property::Index index, const Pr
       }
       case Toolkit::DevelTextLabel::Property::TEXT_FIT:
       {
-        const Property::Map& propertiesMap = value.Get<Property::Map>();
-
-        bool enabled = false;
-        float minSize = 0.f;
-        float maxSize = 0.f;
-        float stepSize = 0.f;
-        bool isMinSizeSet = false, isMaxSizeSet = false, isStepSizeSet = false;
-        Controller::FontSizeType type = Controller::FontSizeType::POINT_SIZE;
-
-        if ( !propertiesMap.Empty() )
-        {
-          const unsigned int numberOfItems = propertiesMap.Count();
-
-          // Parses and applies
-          for( unsigned int index = 0u; index < numberOfItems; ++index )
-          {
-            const KeyValuePair& valueGet = propertiesMap.GetKeyValue( index );
-
-            if( ( Controller::TextFitInfo::Property::TEXT_FIT_ENABLE == valueGet.first.indexKey ) || ( TEXT_FIT_ENABLE_KEY == valueGet.first.stringKey ) )
-            {
-              /// Enable key.
-              enabled = valueGet.second.Get< bool >();
-            }
-            else if( ( Controller::TextFitInfo::Property::TEXT_FIT_MIN_SIZE == valueGet.first.indexKey ) || ( TEXT_FIT_MIN_SIZE_KEY == valueGet.first.stringKey ) )
-            {
-              /// min size.
-              minSize = valueGet.second.Get< float >();
-              isMinSizeSet = true;
-            }
-            else if( ( Controller::TextFitInfo::Property::TEXT_FIT_MAX_SIZE == valueGet.first.indexKey ) || ( TEXT_FIT_MAX_SIZE_KEY == valueGet.first.stringKey ) )
-            {
-              /// max size.
-              maxSize = valueGet.second.Get< float >();
-              isMaxSizeSet = true;
-            }
-            else if( ( Controller::TextFitInfo::Property::TEXT_FIT_STEP_SIZE == valueGet.first.indexKey ) || ( TEXT_FIT_STEP_SIZE_KEY == valueGet.first.stringKey ) )
-            {
-              /// step size.
-              stepSize = valueGet.second.Get< float >();
-              isStepSizeSet = true;
-            }
-            else if( ( Controller::TextFitInfo::Property::TEXT_FIT_FONT_SIZE_TYPE == valueGet.first.indexKey ) || ( TEXT_FIT_FONT_SIZE_TYPE_KEY == valueGet.first.stringKey ) )
-            {
-              if( "pixelSize" == valueGet.second.Get< std::string >() )
-              {
-                type = Controller::FontSizeType::PIXEL_SIZE;
-              }
-            }
-          }
-
-          impl.mController->SetTextFitEnabled( enabled );
-          if( isMinSizeSet )
-          {
-            impl.mController->SetTextFitMinSize( minSize, type );
-          }
-          if( isMaxSizeSet )
-          {
-            impl.mController->SetTextFitMaxSize( maxSize, type );
-          }
-          if( isStepSizeSet )
-          {
-            impl.mController->SetTextFitStepSize( stepSize, type );
-          }
-        }
+        ParseTextFitProperty(impl.mController, value.GetMap());
         break;
       }
       case Toolkit::DevelTextLabel::Property::MIN_LINE_SIZE:
       {
         const float lineSize = value.Get<float>();
-
-        if( impl.mController->SetDefaultLineSize( lineSize ) )
-        {
-          impl.mTextUpdateNeeded = true;
-        }
+        impl.mTextUpdateNeeded = impl.mController->SetDefaultLineSize( lineSize ) || impl.mTextUpdateNeeded;
         break;
       }
     }
index bdbdbbc..a863c9c 100644 (file)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_INTERNAL_TEXT_LABEL_H
 
 /*
- * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -142,6 +142,19 @@ private:
    */
   void SetUpAutoScrolling();
 
+  /**
+   * Creates a text-scroller if one has not been created.
+   * @return The text scroller.
+   */
+  Text::TextScrollerPtr GetTextScroller()
+  {
+    if( !mTextScroller )
+    {
+      mTextScroller = Text::TextScroller::New( *this );
+    }
+    return mTextScroller;
+  }
+
 private: // Data
 
   Text::ControllerPtr mController;
index 0d6b781..72a5626 100644 (file)
@@ -142,6 +142,7 @@ SET( toolkit_src_files
    ${toolkit_src_dir}/text/text-controller.cpp
    ${toolkit_src_dir}/text/text-controller-event-handler.cpp
    ${toolkit_src_dir}/text/text-controller-impl.cpp
+   ${toolkit_src_dir}/text/text-controller-impl-event-handler.cpp
    ${toolkit_src_dir}/text/text-controller-input-font-handler.cpp
    ${toolkit_src_dir}/text/text-controller-placeholder-handler.cpp
    ${toolkit_src_dir}/text/text-effects-style.cpp
index 0256a43..05b4a67 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -278,6 +278,149 @@ bool IsGlyphUnderlined( GlyphIndex index,
   return false;
 }
 
+/// Helper method to fetch the underline metrics for the specified font glyph
+void FetchFontUnderlineMetrics(
+    TextAbstraction::FontClient& fontClient,
+    const GlyphInfo* const glyphInfo,
+    float& currentUnderlinePosition,
+    const float underlineHeight,
+    float& currentUnderlineThickness,
+    float& maxUnderlineThickness,
+    FontId& lastUnderlinedFontId)
+{
+  FontMetrics fontMetrics;
+  fontClient.GetFontMetrics( glyphInfo->fontId, fontMetrics );
+  currentUnderlinePosition = ceil( fabsf( fontMetrics.underlinePosition ) );
+  const float descender = ceil( fabsf( fontMetrics.descender ) );
+
+  if( fabsf( underlineHeight ) < Math::MACHINE_EPSILON_1000 )
+  {
+    currentUnderlineThickness = fontMetrics.underlineThickness;
+
+    // Ensure underline will be at least a pixel high
+    if ( currentUnderlineThickness < 1.0f )
+    {
+      currentUnderlineThickness = 1.0f;
+    }
+    else
+    {
+      currentUnderlineThickness = ceil( currentUnderlineThickness );
+    }
+  }
+
+  // The underline thickness should be the max underline thickness of all glyphs of the line.
+  if ( currentUnderlineThickness > maxUnderlineThickness )
+  {
+    maxUnderlineThickness = currentUnderlineThickness;
+  }
+
+  // Clamp the underline position at the font descender and check for ( as EFL describes it ) a broken font
+  if( currentUnderlinePosition > descender )
+  {
+    currentUnderlinePosition = descender;
+  }
+
+  if( fabsf( currentUnderlinePosition ) < Math::MACHINE_EPSILON_1000 )
+  {
+    // Move offset down by one ( EFL behavior )
+    currentUnderlinePosition = 1.0f;
+  }
+
+  lastUnderlinedFontId = glyphInfo->fontId;
+}
+
+/// Draws the specified color to the pixel buffer
+void WriteColorToPixelBuffer(
+    GlyphData& glyphData,
+    uint32_t* bitmapBuffer,
+    const Vector4& color,
+    const unsigned int x,
+    const unsigned int y)
+{
+  // Always RGBA image for text with styles
+  uint32_t pixel = *( bitmapBuffer + y * glyphData.width + x );
+  uint8_t* pixelBuffer = reinterpret_cast<uint8_t*>( &pixel );
+
+  // Write the color to the pixel buffer
+  uint8_t colorAlpha = static_cast< uint8_t >( color.a * 255.f );
+  *( pixelBuffer + 3u ) = colorAlpha;
+  *( pixelBuffer + 2u ) = static_cast< uint8_t >( color.b * colorAlpha );
+  *( pixelBuffer + 1u ) = static_cast< uint8_t >( color.g * colorAlpha );
+  *( pixelBuffer      ) = static_cast< uint8_t >( color.r * colorAlpha );
+
+  *( bitmapBuffer + y * glyphData.width + x ) = pixel;
+}
+
+/// Draws the specified underline color to the buffer
+void DrawUnderline(
+    const Vector4& underlineColor,
+    const unsigned int bufferWidth,
+    const unsigned int bufferHeight,
+    GlyphData& glyphData,
+    const float baseline,
+    const float currentUnderlinePosition,
+    const float maxUnderlineThickness,
+    const float lineExtentLeft,
+    const float lineExtentRight)
+{
+  int underlineYOffset = glyphData.verticalOffset + baseline + currentUnderlinePosition;
+  uint32_t* bitmapBuffer = reinterpret_cast< uint32_t* >( glyphData.bitmapBuffer.GetBuffer() );
+
+  for( unsigned int y = underlineYOffset; y < underlineYOffset + maxUnderlineThickness; y++ )
+  {
+    if( y > bufferHeight - 1 )
+    {
+      // Do not write out of bounds.
+      break;
+    }
+
+    for( unsigned int x = glyphData.horizontalOffset + lineExtentLeft; x <= glyphData.horizontalOffset + lineExtentRight; x++ )
+    {
+      if( x > bufferWidth - 1 )
+      {
+        // Do not write out of bounds.
+        break;
+      }
+
+      WriteColorToPixelBuffer(glyphData, bitmapBuffer, underlineColor, x, y);
+    }
+  }
+}
+
+/// Draws the background color to the buffer
+void DrawBackgroundColor(
+    Vector4 backgroundColor,
+    const unsigned int bufferWidth,
+    const unsigned int bufferHeight,
+    GlyphData& glyphData,
+    const float baseline,
+    const LineRun& line,
+    const float lineExtentLeft,
+    const float lineExtentRight)
+{
+  uint32_t* bitmapBuffer = reinterpret_cast< uint32_t* >( glyphData.bitmapBuffer.GetBuffer() );
+
+  for( int y = glyphData.verticalOffset + baseline - line.ascender; y < glyphData.verticalOffset + baseline - line.descender; y++ )
+  {
+    if( ( y < 0 ) || ( y > static_cast<int>(bufferHeight - 1) ) )
+    {
+      // Do not write out of bounds.
+      continue;
+    }
+
+    for( int x = glyphData.horizontalOffset + lineExtentLeft; x <= glyphData.horizontalOffset + lineExtentRight; x++ )
+    {
+      if( ( x < 0 ) || ( x > static_cast<int>(bufferWidth - 1) ) )
+      {
+        // Do not write out of bounds.
+        continue;
+      }
+
+      WriteColorToPixelBuffer(glyphData, bitmapBuffer, backgroundColor, x, y);
+    }
+  }
+}
+
 } // namespace
 
 TypesetterPtr Typesetter::New( const ModelInterface* const model )
@@ -593,45 +736,7 @@ Devel::PixelBuffer Typesetter::CreateImageBuffer( const unsigned int bufferWidth
       if( underlineGlyph && ( glyphInfo->fontId != lastUnderlinedFontId ) )
       {
         // We need to fetch fresh font underline metrics
-        FontMetrics fontMetrics;
-        fontClient.GetFontMetrics( glyphInfo->fontId, fontMetrics );
-        currentUnderlinePosition = ceil( fabsf( fontMetrics.underlinePosition ) );
-        const float descender = ceil( fabsf( fontMetrics.descender ) );
-
-        if( fabsf( underlineHeight ) < Math::MACHINE_EPSILON_1000 )
-        {
-          currentUnderlineThickness = fontMetrics.underlineThickness;
-
-          // Ensure underline will be at least a pixel high
-          if ( currentUnderlineThickness < 1.0f )
-          {
-            currentUnderlineThickness = 1.0f;
-          }
-          else
-          {
-            currentUnderlineThickness = ceil( currentUnderlineThickness );
-          }
-        }
-
-        // The underline thickness should be the max underline thickness of all glyphs of the line.
-        if ( currentUnderlineThickness > maxUnderlineThickness )
-        {
-          maxUnderlineThickness = currentUnderlineThickness;
-        }
-
-        // Clamp the underline position at the font descender and check for ( as EFL describes it ) a broken font
-        if( currentUnderlinePosition > descender )
-        {
-          currentUnderlinePosition = descender;
-        }
-
-        if( fabsf( currentUnderlinePosition ) < Math::MACHINE_EPSILON_1000 )
-        {
-          // Move offset down by one ( EFL behavior )
-          currentUnderlinePosition = 1.0f;
-        }
-
-        lastUnderlinedFontId = glyphInfo->fontId;
+        FetchFontUnderlineMetrics(fontClient, glyphInfo, currentUnderlinePosition, underlineHeight, currentUnderlineThickness, maxUnderlineThickness, lastUnderlinedFontId);
       } // underline
 
       // Retrieves the glyph's position.
@@ -728,77 +833,13 @@ Devel::PixelBuffer Typesetter::CreateImageBuffer( const unsigned int bufferWidth
     // Draw the underline from the leftmost glyph to the rightmost glyph
     if ( thereAreUnderlinedGlyphs && style == Typesetter::STYLE_UNDERLINE )
     {
-      int underlineYOffset = glyphData.verticalOffset + baseline + currentUnderlinePosition;
-
-      for( unsigned int y = underlineYOffset; y < underlineYOffset + maxUnderlineThickness; y++ )
-      {
-        if( y > bufferHeight - 1 )
-        {
-          // Do not write out of bounds.
-          break;
-        }
-
-        for( unsigned int x = glyphData.horizontalOffset + lineExtentLeft; x <= glyphData.horizontalOffset + lineExtentRight; x++ )
-        {
-          if( x > bufferWidth - 1 )
-          {
-            // Do not write out of bounds.
-            break;
-          }
-
-          // Always RGBA image for text with styles
-          uint32_t* bitmapBuffer = reinterpret_cast< uint32_t* >( glyphData.bitmapBuffer.GetBuffer() );
-          uint32_t underlinePixel = *( bitmapBuffer + y * glyphData.width + x );
-          uint8_t* underlinePixelBuffer = reinterpret_cast<uint8_t*>( &underlinePixel );
-
-          // Write the underline color to the pixel buffer
-          uint8_t colorAlpha = static_cast< uint8_t >( underlineColor.a * 255.f );
-          *( underlinePixelBuffer + 3u ) = colorAlpha;
-          *( underlinePixelBuffer + 2u ) = static_cast< uint8_t >( underlineColor.b * colorAlpha );
-          *( underlinePixelBuffer + 1u ) = static_cast< uint8_t >( underlineColor.g * colorAlpha );
-          *( underlinePixelBuffer      ) = static_cast< uint8_t >( underlineColor.r * colorAlpha );
-
-          *( bitmapBuffer + y * glyphData.width + x ) = underlinePixel;
-        }
-      }
+      DrawUnderline(underlineColor, bufferWidth, bufferHeight, glyphData, baseline, currentUnderlinePosition, maxUnderlineThickness, lineExtentLeft, lineExtentRight);
     }
 
     // Draw the background color from the leftmost glyph to the rightmost glyph
     if ( style == Typesetter::STYLE_BACKGROUND )
     {
-      Vector4 backgroundColor = mModel->GetBackgroundColor();
-
-      for( int y = glyphData.verticalOffset + baseline - line.ascender; y < glyphData.verticalOffset + baseline - line.descender; y++ )
-      {
-        if( ( y < 0 ) || ( y > static_cast<int>(bufferHeight - 1) ) )
-        {
-          // Do not write out of bounds.
-          continue;
-        }
-
-        for( int x = glyphData.horizontalOffset + lineExtentLeft; x <= glyphData.horizontalOffset + lineExtentRight; x++ )
-        {
-          if( ( x < 0 ) || ( x > static_cast<int>(bufferWidth - 1) ) )
-          {
-            // Do not write out of bounds.
-            continue;
-          }
-
-          // Always RGBA image for text with styles
-          uint32_t* bitmapBuffer = reinterpret_cast< uint32_t* >( glyphData.bitmapBuffer.GetBuffer() );
-          uint32_t backgroundPixel = *( bitmapBuffer + y * glyphData.width + x );
-          uint8_t* backgroundPixelBuffer = reinterpret_cast<uint8_t*>( &backgroundPixel );
-
-          // Write the background color to the pixel buffer
-          uint8_t colorAlpha = static_cast< uint8_t >( backgroundColor.a * 255.f );
-          *( backgroundPixelBuffer + 3u ) = colorAlpha;
-          *( backgroundPixelBuffer + 2u ) = static_cast< uint8_t >( backgroundColor.b * colorAlpha );
-          *( backgroundPixelBuffer + 1u ) = static_cast< uint8_t >( backgroundColor.g * colorAlpha );
-          *( backgroundPixelBuffer      ) = static_cast< uint8_t >( backgroundColor.r * colorAlpha );
-
-          *( bitmapBuffer + y * glyphData.width + x ) = backgroundPixel;
-        }
-      }
+      DrawBackgroundColor(mModel->GetBackgroundColor(), bufferWidth, bufferHeight, glyphData, baseline, line, lineExtentLeft, lineExtentRight);
     }
 
     // Increases the vertical offset with the line's descender.
diff --git a/dali-toolkit/internal/text/text-controller-impl-event-handler.cpp b/dali-toolkit/internal/text/text-controller-impl-event-handler.cpp
new file mode 100644 (file)
index 0000000..7b50a5c
--- /dev/null
@@ -0,0 +1,791 @@
+/*
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include <dali-toolkit/internal/text/text-controller-impl-event-handler.h>
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/adaptor-framework/key.h>
+#include <dali/integration-api/debug.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/cursor-helper-functions.h>
+
+using namespace Dali;
+
+namespace
+{
+
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
+#endif
+
+} // namespace
+
+namespace Dali
+{
+
+namespace Toolkit
+{
+
+namespace Text
+{
+
+void ControllerImplEventHandler::OnCursorKeyEvent(Controller::Impl& impl, const Event& event)
+{
+  if( NULL == impl.mEventData || !impl.IsShowingRealText() )
+  {
+    // Nothing to do if there is no text input.
+    return;
+  }
+
+  int keyCode = event.p1.mInt;
+  bool isShiftModifier = event.p2.mBool;
+  EventData& eventData = *impl.mEventData;
+  ModelPtr& model = impl.mModel;
+  LogicalModelPtr& logicalModel = model->mLogicalModel;
+  VisualModelPtr& visualModel = model->mVisualModel;
+
+  CharacterIndex& primaryCursorPosition = eventData.mPrimaryCursorPosition;
+  CharacterIndex previousPrimaryCursorPosition = primaryCursorPosition;
+
+  if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
+  {
+    if( primaryCursorPosition > 0u )
+    {
+      if ( !isShiftModifier && eventData.mDecorator->IsHighlightVisible() )
+      {
+        primaryCursorPosition = std::min(eventData.mLeftSelectionPosition, eventData.mRightSelectionPosition);
+      }
+      else
+      {
+        primaryCursorPosition = impl.CalculateNewCursorIndex( primaryCursorPosition - 1u );
+      }
+    }
+  }
+  else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
+  {
+    if( logicalModel->mText.Count() > primaryCursorPosition )
+    {
+      if ( !isShiftModifier && eventData.mDecorator->IsHighlightVisible() )
+      {
+        primaryCursorPosition = std::max(eventData.mLeftSelectionPosition, eventData.mRightSelectionPosition);
+      }
+      else
+      {
+        primaryCursorPosition = impl.CalculateNewCursorIndex( primaryCursorPosition );
+      }
+    }
+  }
+  else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier )
+  {
+    // Ignore Shift-Up for text selection for now.
+
+    // Get first the line index of the current cursor position index.
+    CharacterIndex characterIndex = 0u;
+
+    if( primaryCursorPosition > 0u )
+    {
+      characterIndex = primaryCursorPosition - 1u;
+    }
+
+    const LineIndex lineIndex = visualModel->GetLineOfCharacter( characterIndex );
+    const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
+
+    // Retrieve the cursor position info.
+    CursorInfo cursorInfo;
+    impl.GetCursorPosition( primaryCursorPosition,
+                            cursorInfo );
+
+    // Get the line above.
+    const LineRun& line = *( visualModel->mLines.Begin() + previousLineIndex );
+
+    // Get the next hit 'y' point.
+    const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
+
+    // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
+    bool matchedCharacter = false;
+    primaryCursorPosition = Text::GetClosestCursorIndex( visualModel,
+                                                         logicalModel,
+                                                         impl.mMetrics,
+                                                         eventData.mCursorHookPositionX,
+                                                         hitPointY,
+                                                         CharacterHitTest::TAP,
+                                                         matchedCharacter );
+  }
+  else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier )
+  {
+    // Ignore Shift-Down for text selection for now.
+
+    // Get first the line index of the current cursor position index.
+    CharacterIndex characterIndex = 0u;
+
+    if( primaryCursorPosition > 0u )
+    {
+      characterIndex = primaryCursorPosition - 1u;
+    }
+
+    const LineIndex lineIndex = visualModel->GetLineOfCharacter( characterIndex );
+
+    if( lineIndex + 1u < visualModel->mLines.Count() )
+    {
+      // Retrieve the cursor position info.
+      CursorInfo cursorInfo;
+      impl.GetCursorPosition( primaryCursorPosition, cursorInfo );
+
+      // Get the line below.
+      const LineRun& line = *( visualModel->mLines.Begin() + lineIndex + 1u );
+
+      // Get the next hit 'y' point.
+      const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
+
+      // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
+      bool matchedCharacter = false;
+      primaryCursorPosition = Text::GetClosestCursorIndex( visualModel,
+                                                           logicalModel,
+                                                           impl.mMetrics,
+                                                           eventData.mCursorHookPositionX,
+                                                           hitPointY,
+                                                           CharacterHitTest::TAP,
+                                                           matchedCharacter );
+    }
+  }
+
+  if ( !isShiftModifier && eventData.mState != EventData::SELECTING )
+  {
+    // Update selection position after moving the cursor
+    eventData.mLeftSelectionPosition = primaryCursorPosition;
+    eventData.mRightSelectionPosition = primaryCursorPosition;
+  }
+
+  if ( isShiftModifier && impl.IsShowingRealText() && eventData.mShiftSelectionFlag )
+  {
+    // Handle text selection
+    bool selecting = false;
+
+    if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
+    {
+      // Shift-Left/Right to select the text
+      int cursorPositionDelta = primaryCursorPosition - previousPrimaryCursorPosition;
+      if ( cursorPositionDelta > 0 || eventData.mRightSelectionPosition > 0u ) // Check the boundary
+      {
+        eventData.mRightSelectionPosition += cursorPositionDelta;
+      }
+      selecting = true;
+    }
+    else if ( eventData.mLeftSelectionPosition != eventData.mRightSelectionPosition )
+    {
+      // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
+      selecting = true;
+    }
+
+    if ( selecting )
+    {
+      // Notify the cursor position to the InputMethodContext.
+      if( eventData.mInputMethodContext )
+      {
+        eventData.mInputMethodContext.SetCursorPosition( primaryCursorPosition );
+        eventData.mInputMethodContext.NotifyCursorPosition();
+      }
+
+      impl.ChangeState( EventData::SELECTING );
+
+      eventData.mUpdateLeftSelectionPosition = true;
+      eventData.mUpdateRightSelectionPosition = true;
+      eventData.mUpdateGrabHandlePosition = true;
+      eventData.mUpdateHighlightBox = true;
+
+      // Hide the text selection popup if select the text using keyboard instead of moving grab handles
+      if( eventData.mGrabHandlePopupEnabled )
+      {
+        eventData.mDecorator->SetPopupActive( false );
+      }
+    }
+  }
+  else
+  {
+    // Handle normal cursor move
+    impl.ChangeState( EventData::EDITING );
+    eventData.mUpdateCursorPosition = true;
+  }
+
+  eventData.mUpdateInputStyle = true;
+  eventData.mScrollAfterUpdatePosition = true;
+}
+
+void ControllerImplEventHandler::OnTapEvent(Controller::Impl& impl, const Event& event)
+{
+  if( impl.mEventData )
+  {
+    const unsigned int tapCount = event.p1.mUint;
+    EventData& eventData = *impl.mEventData;
+    ModelPtr& model = impl.mModel;
+    LogicalModelPtr& logicalModel = model->mLogicalModel;
+    VisualModelPtr& visualModel = model->mVisualModel;
+
+    if( 1u == tapCount )
+    {
+      if( impl.IsShowingRealText() )
+      {
+        // Convert from control's coords to text's coords.
+        const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
+        const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
+
+        // Keep the tap 'x' position. Used to move the cursor.
+        eventData.mCursorHookPositionX = xPosition;
+
+        // Whether to touch point hits on a glyph.
+        bool matchedCharacter = false;
+        eventData.mPrimaryCursorPosition = Text::GetClosestCursorIndex( visualModel,
+                                                                        logicalModel,
+                                                                        impl.mMetrics,
+                                                                        xPosition,
+                                                                        yPosition,
+                                                                        CharacterHitTest::TAP,
+                                                                        matchedCharacter );
+
+        // When the cursor position is changing, delay cursor blinking
+        eventData.mDecorator->DelayCursorBlink();
+      }
+      else
+      {
+        eventData.mPrimaryCursorPosition = 0u;
+      }
+
+      // Update selection position after tapping
+      eventData.mLeftSelectionPosition = eventData.mPrimaryCursorPosition;
+      eventData.mRightSelectionPosition = eventData.mPrimaryCursorPosition;
+
+      eventData.mUpdateCursorPosition = true;
+      eventData.mUpdateGrabHandlePosition = true;
+      eventData.mScrollAfterUpdatePosition = true;
+      eventData.mUpdateInputStyle = true;
+
+      // Notify the cursor position to the InputMethodContext.
+      if( eventData.mInputMethodContext )
+      {
+        eventData.mInputMethodContext.SetCursorPosition( eventData.mPrimaryCursorPosition );
+        eventData.mInputMethodContext.NotifyCursorPosition();
+      }
+    }
+    else if( 2u == tapCount )
+    {
+      if( eventData.mSelectionEnabled )
+      {
+        // Convert from control's coords to text's coords.
+        const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
+        const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
+
+        // Calculates the logical position from the x,y coords.
+        impl.RepositionSelectionHandles( xPosition, yPosition, eventData.mDoubleTapAction );
+      }
+    }
+  }
+}
+
+void ControllerImplEventHandler::OnPanEvent(Controller::Impl& impl, const Event& event)
+{
+  if( impl.mEventData )
+  {
+    EventData& eventData = *impl.mEventData;
+    DecoratorPtr& decorator = eventData.mDecorator;
+
+    const bool isHorizontalScrollEnabled = decorator->IsHorizontalScrollEnabled();
+    const bool isVerticalScrollEnabled = decorator->IsVerticalScrollEnabled();
+
+    if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
+    {
+      // Nothing to do if scrolling is not enabled.
+      return;
+    }
+
+    const GestureState state = static_cast<GestureState>( event.p1.mInt );
+    switch( state )
+    {
+      case GestureState::STARTED:
+      {
+        // Will remove the cursor, handles or text's popup, ...
+        impl.ChangeState( EventData::TEXT_PANNING );
+        break;
+      }
+      case GestureState::CONTINUING:
+      {
+        ModelPtr& model = impl.mModel;
+
+        const Vector2& layoutSize = model->mVisualModel->GetLayoutSize();
+        Vector2& scrollPosition = model->mScrollPosition;
+        const Vector2 currentScroll = scrollPosition;
+
+        if( isHorizontalScrollEnabled )
+        {
+          const float displacementX = event.p2.mFloat;
+          scrollPosition.x += displacementX;
+
+          impl.ClampHorizontalScroll( layoutSize );
+        }
+
+        if( isVerticalScrollEnabled )
+        {
+          const float displacementY = event.p3.mFloat;
+          scrollPosition.y += displacementY;
+
+          impl.ClampVerticalScroll( layoutSize );
+        }
+
+        decorator->UpdatePositions( scrollPosition - currentScroll );
+        break;
+      }
+      case GestureState::FINISHED:
+      case GestureState::CANCELLED: // FALLTHROUGH
+      {
+        // Will go back to the previous state to show the cursor, handles, the text's popup, ...
+        impl.ChangeState( eventData.mPreviousState );
+        break;
+      }
+      default:
+        break;
+    }
+  }
+}
+
+void ControllerImplEventHandler::OnLongPressEvent(Controller::Impl& impl, const Event& event)
+{
+  DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
+
+  if( impl.mEventData )
+  {
+    EventData& eventData = *impl.mEventData;
+
+    if( !impl.IsShowingRealText() && ( EventData::EDITING == eventData.mState ) )
+    {
+      impl.ChangeState( EventData::EDITING_WITH_POPUP );
+      eventData.mDecoratorUpdated = true;
+      eventData.mUpdateInputStyle = true;
+    }
+    else
+    {
+      if( eventData.mSelectionEnabled )
+      {
+        ModelPtr& model = impl.mModel;
+
+        // Convert from control's coords to text's coords.
+        const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
+        const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
+
+        // Calculates the logical position from the x,y coords.
+        impl.RepositionSelectionHandles( xPosition, yPosition, eventData.mLongPressAction );
+      }
+    }
+  }
+}
+
+void ControllerImplEventHandler::OnHandleEvent(Controller::Impl& impl, const Event& event)
+{
+  if( impl.mEventData )
+  {
+    const unsigned int state = event.p1.mUint;
+    const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
+    const bool isSmoothHandlePanEnabled = impl.mEventData->mDecorator->IsSmoothHandlePanEnabled();
+
+    if( HANDLE_PRESSED == state )
+    {
+      OnHandlePressed(impl, event, isSmoothHandlePanEnabled);
+    } // end ( HANDLE_PRESSED == state )
+    else if( ( HANDLE_RELEASED == state ) ||
+             handleStopScrolling )
+    {
+      OnHandleReleased(impl, event, isSmoothHandlePanEnabled, handleStopScrolling);
+    } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
+    else if( HANDLE_SCROLLING == state )
+    {
+      OnHandleScrolling(impl, event, isSmoothHandlePanEnabled);
+    } // end ( HANDLE_SCROLLING == state )
+  }
+}
+
+void ControllerImplEventHandler::OnSelectEvent(Controller::Impl& impl, const Event& event )
+{
+  if( impl.mEventData && impl.mEventData->mSelectionEnabled )
+  {
+    ModelPtr& model = impl.mModel;
+    const Vector2& scrollPosition = model->mScrollPosition;
+
+    // Convert from control's coords to text's coords.
+    const float xPosition = event.p2.mFloat - scrollPosition.x;
+    const float yPosition = event.p3.mFloat - scrollPosition.y;
+
+    // Calculates the logical position from the x,y coords.
+    impl.RepositionSelectionHandles( xPosition, yPosition, Controller::NoTextTap::HIGHLIGHT );
+  }
+}
+
+void ControllerImplEventHandler::OnSelectAllEvent(Controller::Impl& impl)
+{
+  DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", impl.mEventData->mSelectionEnabled?"true":"false");
+
+  if( impl.mEventData )
+  {
+    EventData& eventData = *impl.mEventData;
+    if( eventData.mSelectionEnabled )
+    {
+      ModelPtr& model = impl.mModel;
+      const Vector2& scrollPosition = model->mScrollPosition;
+
+      // Calculates the logical position from the start.
+      impl.RepositionSelectionHandles( 0.f - scrollPosition.x,
+                                       0.f - scrollPosition.y,
+                                       Controller::NoTextTap::HIGHLIGHT );
+
+      eventData.mLeftSelectionPosition = 0u;
+      eventData.mRightSelectionPosition = model->mLogicalModel->mText.Count();
+    }
+  }
+}
+
+void ControllerImplEventHandler::OnSelectNoneEvent(Controller::Impl& impl)
+{
+  DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectNoneEvent mEventData->mSelectionEnabled%s \n", impl.mEventData->mSelectionEnabled?"true":"false");
+
+  if( impl.mEventData )
+  {
+    EventData& eventData = *impl.mEventData;
+    if( eventData.mSelectionEnabled && eventData.mState == EventData::SELECTING)
+    {
+      eventData.mPrimaryCursorPosition = 0u;
+      eventData.mLeftSelectionPosition = eventData.mRightSelectionPosition = eventData.mPrimaryCursorPosition;
+      impl.ChangeState( EventData::INACTIVE );
+      eventData.mUpdateCursorPosition = true;
+      eventData.mUpdateInputStyle = true;
+      eventData.mScrollAfterUpdatePosition = true;
+    }
+  }
+}
+
+void ControllerImplEventHandler::OnHandlePressed(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled)
+{
+  ModelPtr& model = impl.mModel;
+  const Vector2& scrollPosition = model->mScrollPosition;
+
+  // Convert from decorator's coords to text's coords.
+  const float xPosition = event.p2.mFloat - scrollPosition.x;
+  const float yPosition = event.p3.mFloat - scrollPosition.y;
+
+  // Need to calculate the handle's new position.
+  bool matchedCharacter = false;
+  const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( model->mVisualModel,
+                                                                        model->mLogicalModel,
+                                                                        impl.mMetrics,
+                                                                        xPosition,
+                                                                        yPosition,
+                                                                        CharacterHitTest::SCROLL,
+                                                                        matchedCharacter );
+
+  EventData& eventData = *impl.mEventData;
+
+  if( Event::GRAB_HANDLE_EVENT == event.type )
+  {
+    impl.ChangeState ( EventData::GRAB_HANDLE_PANNING );
+
+    if( handleNewPosition != eventData.mPrimaryCursorPosition )
+    {
+      // Updates the cursor position if the handle's new position is different than the current one.
+      eventData.mUpdateCursorPosition = true;
+      // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
+      eventData.mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
+      eventData.mPrimaryCursorPosition = handleNewPosition;
+    }
+
+    // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
+    eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
+  }
+  else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
+  {
+    impl.ChangeState ( EventData::SELECTION_HANDLE_PANNING );
+
+    if( ( handleNewPosition != eventData.mLeftSelectionPosition ) &&
+        ( handleNewPosition != eventData.mRightSelectionPosition ) )
+    {
+      // Updates the highlight box if the handle's new position is different than the current one.
+      eventData.mUpdateHighlightBox = true;
+      // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
+      eventData.mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
+      eventData.mLeftSelectionPosition = handleNewPosition;
+    }
+
+    // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
+    eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
+
+    // Will define the order to scroll the text to match the handle position.
+    eventData.mIsLeftHandleSelected = true;
+    eventData.mIsRightHandleSelected = false;
+  }
+  else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
+  {
+    impl.ChangeState ( EventData::SELECTION_HANDLE_PANNING );
+
+    if( ( handleNewPosition != eventData.mRightSelectionPosition ) &&
+        ( handleNewPosition != eventData.mLeftSelectionPosition ) )
+    {
+      // Updates the highlight box if the handle's new position is different than the current one.
+      eventData.mUpdateHighlightBox = true;
+      // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
+      eventData.mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
+      eventData.mRightSelectionPosition = handleNewPosition;
+    }
+
+    // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
+    eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
+
+    // Will define the order to scroll the text to match the handle position.
+    eventData.mIsLeftHandleSelected = false;
+    eventData.mIsRightHandleSelected = true;
+  }
+}
+
+void ControllerImplEventHandler::OnHandleReleased(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled, const bool handleStopScrolling)
+{
+  CharacterIndex handlePosition = 0u;
+  if( handleStopScrolling || isSmoothHandlePanEnabled )
+  {
+    ModelPtr& model = impl.mModel;
+    const Vector2& scrollPosition = model->mScrollPosition;
+
+    // Convert from decorator's coords to text's coords.
+    const float xPosition = event.p2.mFloat - scrollPosition.x;
+    const float yPosition = event.p3.mFloat - scrollPosition.y;
+
+    bool matchedCharacter = false;
+    handlePosition = Text::GetClosestCursorIndex( model->mVisualModel,
+                                                  model->mLogicalModel,
+                                                  impl.mMetrics,
+                                                  xPosition,
+                                                  yPosition,
+                                                  CharacterHitTest::SCROLL,
+                                                  matchedCharacter );
+  }
+
+  EventData& eventData = *impl.mEventData;
+
+  if( Event::GRAB_HANDLE_EVENT == event.type )
+  {
+    eventData.mUpdateCursorPosition = true;
+    eventData.mUpdateGrabHandlePosition = true;
+    eventData.mUpdateInputStyle = true;
+
+    if( !impl.IsClipboardEmpty() )
+    {
+      impl.ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
+    }
+
+    if( handleStopScrolling || isSmoothHandlePanEnabled )
+    {
+      eventData.mScrollAfterUpdatePosition = true;
+      eventData.mPrimaryCursorPosition = handlePosition;
+    }
+  }
+  else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
+  {
+    impl.ChangeState( EventData::SELECTING );
+
+    eventData.mUpdateHighlightBox = true;
+    eventData.mUpdateLeftSelectionPosition = true;
+    eventData.mUpdateRightSelectionPosition = true;
+
+    if( handleStopScrolling || isSmoothHandlePanEnabled )
+    {
+      eventData.mScrollAfterUpdatePosition = true;
+
+      if( ( handlePosition != eventData.mRightSelectionPosition ) &&
+          ( handlePosition != eventData.mLeftSelectionPosition ) )
+      {
+        eventData.mLeftSelectionPosition = handlePosition;
+      }
+    }
+  }
+  else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
+  {
+    impl.ChangeState( EventData::SELECTING );
+
+    eventData.mUpdateHighlightBox = true;
+    eventData.mUpdateRightSelectionPosition = true;
+    eventData.mUpdateLeftSelectionPosition = true;
+
+    if( handleStopScrolling || isSmoothHandlePanEnabled )
+    {
+      eventData.mScrollAfterUpdatePosition = true;
+      if( ( handlePosition != eventData.mRightSelectionPosition ) &&
+          ( handlePosition != eventData.mLeftSelectionPosition ) )
+      {
+        eventData.mRightSelectionPosition = handlePosition;
+      }
+    }
+  }
+
+  eventData.mDecoratorUpdated = true;
+}
+
+void ControllerImplEventHandler::OnHandleScrolling(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled)
+{
+  ModelPtr& model = impl.mModel;
+  Vector2& scrollPosition = model->mScrollPosition;
+  VisualModelPtr& visualModel = model->mVisualModel;
+
+  const float xSpeed = event.p2.mFloat;
+  const float ySpeed = event.p3.mFloat;
+  const Vector2& layoutSize = visualModel->GetLayoutSize();
+  const Vector2 currentScrollPosition = scrollPosition;
+
+  scrollPosition.x += xSpeed;
+  scrollPosition.y += ySpeed;
+
+  impl.ClampHorizontalScroll( layoutSize );
+  impl.ClampVerticalScroll( layoutSize );
+
+  EventData& eventData = *impl.mEventData;
+  DecoratorPtr& decorator = eventData.mDecorator;
+
+  bool endOfScroll = false;
+  if( Vector2::ZERO == ( currentScrollPosition - scrollPosition ) )
+  {
+    // Notify the decorator there is no more text to scroll.
+    // The decorator won't send more scroll events.
+    decorator->NotifyEndOfScroll();
+    // Still need to set the position of the handle.
+    endOfScroll = true;
+  }
+
+  // Set the position of the handle.
+  const bool scrollRightDirection = xSpeed > 0.f;
+  const bool scrollBottomDirection = ySpeed > 0.f;
+  const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
+  const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
+
+  if( Event::GRAB_HANDLE_EVENT == event.type )
+  {
+    impl.ChangeState( EventData::GRAB_HANDLE_PANNING );
+
+    // Get the grab handle position in decorator coords.
+    Vector2 position = decorator->GetPosition( GRAB_HANDLE );
+
+    if( decorator->IsHorizontalScrollEnabled() )
+    {
+      // Position the grag handle close to either the left or right edge.
+      position.x = scrollRightDirection ? 0.f : visualModel->mControlSize.width;
+    }
+
+    if( decorator->IsVerticalScrollEnabled() )
+    {
+      position.x = eventData.mCursorHookPositionX;
+
+      // Position the grag handle close to either the top or bottom edge.
+      position.y = scrollBottomDirection ? 0.f : visualModel->mControlSize.height;
+    }
+
+    // Get the new handle position.
+    // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
+    bool matchedCharacter = false;
+    const CharacterIndex handlePosition = Text::GetClosestCursorIndex( visualModel,
+                                                                       impl.mModel->mLogicalModel,
+                                                                       impl.mMetrics,
+                                                                       position.x - scrollPosition.x,
+                                                                       position.y - scrollPosition.y,
+                                                                       CharacterHitTest::SCROLL,
+                                                                       matchedCharacter );
+
+    if( eventData.mPrimaryCursorPosition != handlePosition )
+    {
+      eventData.mUpdateCursorPosition = true;
+      eventData.mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
+      eventData.mScrollAfterUpdatePosition = true;
+      eventData.mPrimaryCursorPosition = handlePosition;
+    }
+    eventData.mUpdateInputStyle = eventData.mUpdateCursorPosition;
+
+    // Updates the decorator if the soft handle panning is enabled.
+    eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
+  }
+  else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
+  {
+    impl.ChangeState( EventData::SELECTION_HANDLE_PANNING );
+
+    // Get the selection handle position in decorator coords.
+    Vector2 position = decorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
+
+    if( decorator->IsHorizontalScrollEnabled() )
+    {
+      // Position the selection handle close to either the left or right edge.
+      position.x = scrollRightDirection ? 0.f : visualModel->mControlSize.width;
+    }
+
+    if( decorator->IsVerticalScrollEnabled() )
+    {
+      position.x = eventData.mCursorHookPositionX;
+
+      // Position the grag handle close to either the top or bottom edge.
+      position.y = scrollBottomDirection ? 0.f : visualModel->mControlSize.height;
+    }
+
+    // Get the new handle position.
+    // The selection handle's position is in decorator's coords. Need to transform to text's coords.
+    bool matchedCharacter = false;
+    const CharacterIndex handlePosition = Text::GetClosestCursorIndex( visualModel,
+                                                                       impl.mModel->mLogicalModel,
+                                                                       impl.mMetrics,
+                                                                       position.x - scrollPosition.x,
+                                                                       position.y - scrollPosition.y,
+                                                                       CharacterHitTest::SCROLL,
+                                                                       matchedCharacter );
+
+    if( leftSelectionHandleEvent )
+    {
+      const bool differentHandles = ( eventData.mLeftSelectionPosition != handlePosition ) && ( eventData.mRightSelectionPosition != handlePosition );
+
+      if( differentHandles || endOfScroll )
+      {
+        eventData.mUpdateHighlightBox = true;
+        eventData.mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
+        eventData.mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
+        eventData.mLeftSelectionPosition = handlePosition;
+      }
+    }
+    else
+    {
+      const bool differentHandles = ( eventData.mRightSelectionPosition != handlePosition ) && ( eventData.mLeftSelectionPosition != handlePosition );
+      if( differentHandles || endOfScroll )
+      {
+        eventData.mUpdateHighlightBox = true;
+        eventData.mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
+        eventData.mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
+        eventData.mRightSelectionPosition = handlePosition;
+      }
+    }
+
+    if( eventData.mUpdateLeftSelectionPosition || eventData.mUpdateRightSelectionPosition )
+    {
+      impl.RepositionSelectionHandles();
+
+      eventData.mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
+    }
+  }
+  eventData.mDecoratorUpdated = true;
+}
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
diff --git a/dali-toolkit/internal/text/text-controller-impl-event-handler.h b/dali-toolkit/internal/text/text-controller-impl-event-handler.h
new file mode 100644 (file)
index 0000000..52ac94a
--- /dev/null
@@ -0,0 +1,139 @@
+#ifndef DALI_TOOLKIT_TEXT_CONTROLLER_IMPL_EVENT_HANDLER_H
+#define DALI_TOOLKIT_TEXT_CONTROLLER_IMPL_EVENT_HANDLER_H
+
+/*
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/text-controller-impl.h>
+
+namespace Dali
+{
+
+namespace Toolkit
+{
+
+namespace Text
+{
+
+/**
+ * Contains all the event handling methods for Text::Controller::Impl
+ */
+struct ControllerImplEventHandler
+{
+  /**
+   * @brief Called by Controller::Impl when a cursor key event is received.
+   *
+   * @param controllerImpl A reference to Controller::Impl
+   * @param event The event
+   */
+  static void OnCursorKeyEvent(Controller::Impl& controllerImpl, const Event& event);
+
+  /**
+   * @brief Called by Controller::Impl when a tap event is received.
+   *
+   * @param controllerImpl A reference to Controller::Impl
+   * @param event The event
+   */
+  static void OnTapEvent(Controller::Impl& controllerImpl, const Event& event);
+
+  /**
+   * @brief Called by Controller::Impl when a pan event is received.
+   *
+   * @param controllerImpl A reference to Controller::Impl
+   * @param event The event
+   */
+  static void OnPanEvent(Controller::Impl& controllerImpl, const Event& event);
+
+  /**
+   * @brief Called by Controller::Impl when a long press event is received.
+   *
+   * @param controllerImpl A reference to Controller::Impl
+   * @param event The event
+   */
+  static void OnLongPressEvent(Controller::Impl& controllerImpl, const Event& event);
+
+  /**
+   * @brief Called by Controller::Impl when a handle event is received.
+   *
+   * @param controllerImpl A reference to Controller::Impl
+   * @param event The event
+   */
+  static void OnHandleEvent(Controller::Impl& controllerImpl, const Event& event);
+
+  /**
+   * @brief Called by Controller::Impl when a select event is received.
+   *
+   * @param controllerImpl A reference to Controller::Impl
+   * @param event The event
+   */
+  static void OnSelectEvent(Controller::Impl& controllerImpl, const Event& event );
+
+  /**
+   * @brief Called by Controller::Impl when a select all event is received.
+   *
+   * @param controllerImpl A reference to Controller::Impl
+   * @param event The event
+   */
+  static void OnSelectAllEvent(Controller::Impl& controllerImpl);
+
+  /**
+   * @brief Called by Controller::Impl when a select none event is received.
+   *
+   * @param controllerImpl A reference to Controller::Impl
+   * @param event The event
+   */
+  static void OnSelectNoneEvent(Controller::Impl& controllerImpl);
+
+private:
+
+  /**
+   * @brief Called by OnHandleEvent when we are in the Pressed state.
+   *
+   * @param impl A reference to Controller::Impl
+   * @param event The event
+   * @param isSmoothHandlePanEnabled Whether smooth handle pan is enabled
+   */
+  static void OnHandlePressed(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled);
+
+  /**
+   * @brief Called by OnHandleEvent when we are in the Released state.
+   *
+   * @param impl A reference to Controller::Impl
+   * @param event The event
+   * @param isSmoothHandlePanEnabled Whether smooth handle pan is enabled
+   * @param handleStopScrolling Whether we should handle stop scrolling or not
+   */
+  static void OnHandleReleased(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled, const bool handleStopScrolling);
+
+  /**
+   * @brief Called by OnHandleEvent when we are in the Scrolling state.
+   *
+   * @param impl A reference to Controller::Impl
+   * @param event The event
+   * @param isSmoothHandlePanEnabled Whether smooth handle pan is enabled
+   */
+  static void OnHandleScrolling(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled);
+};
+
+} // namespace Text
+
+} // namespace Toolkit
+
+} // namespace Dali
+
+#endif // DALI_TOOLKIT_TEXT_CONTROLLER_IMPL_EVENT_HANDLER_H
index bfce755..0cd6adb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@
 #include <dali-toolkit/internal/text/text-controller-impl.h>
 
 // EXTERNAL INCLUDES
-#include <dali/public-api/adaptor-framework/key.h>
 #include <dali/public-api/rendering/renderer.h>
 #include <dali/integration-api/debug.h>
 #include <limits>
@@ -34,6 +33,7 @@
 #include <dali-toolkit/internal/text/segmentation.h>
 #include <dali-toolkit/internal/text/shaper.h>
 #include <dali-toolkit/internal/text/text-control-interface.h>
+#include <dali-toolkit/internal/text/text-controller-impl-event-handler.h>
 #include <dali-toolkit/internal/text/text-run-container.h>
 
 using namespace Dali;
@@ -53,7 +53,7 @@ struct SelectionBoxInfo
 };
 
 #if defined(DEBUG_ENABLED)
-  Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
 #endif
 
 const float MAX_FLOAT = std::numeric_limits<float>::max();
@@ -1341,704 +1341,42 @@ float Controller::Impl::GetDefaultFontLineHeight()
 
 void Controller::Impl::OnCursorKeyEvent( const Event& event )
 {
-  if( NULL == mEventData || !IsShowingRealText() )
-  {
-    // Nothing to do if there is no text input.
-    return;
-  }
-
-  int keyCode = event.p1.mInt;
-  bool isShiftModifier = event.p2.mBool;
-
-  CharacterIndex previousPrimaryCursorPosition = mEventData->mPrimaryCursorPosition;
-
-  if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
-  {
-    if( mEventData->mPrimaryCursorPosition > 0u )
-    {
-      if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
-      {
-        mEventData->mPrimaryCursorPosition = std::min(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
-      }
-      else
-      {
-        mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
-      }
-    }
-  }
-  else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
-  {
-    if( mModel->mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition )
-    {
-      if ( !isShiftModifier && mEventData->mDecorator->IsHighlightVisible() )
-      {
-        mEventData->mPrimaryCursorPosition = std::max(mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition);
-      }
-      else
-      {
-        mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
-      }
-    }
-  }
-  else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier )
-  {
-    // Ignore Shift-Up for text selection for now.
-
-    // Get first the line index of the current cursor position index.
-    CharacterIndex characterIndex = 0u;
-
-    if( mEventData->mPrimaryCursorPosition > 0u )
-    {
-      characterIndex = mEventData->mPrimaryCursorPosition - 1u;
-    }
-
-    const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
-    const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
-
-    // Retrieve the cursor position info.
-    CursorInfo cursorInfo;
-    GetCursorPosition( mEventData->mPrimaryCursorPosition,
-                       cursorInfo );
-
-    // Get the line above.
-    const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + previousLineIndex );
-
-    // Get the next hit 'y' point.
-    const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
-
-    // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
-    bool matchedCharacter = false;
-    mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
-                                                                      mModel->mLogicalModel,
-                                                                      mMetrics,
-                                                                      mEventData->mCursorHookPositionX,
-                                                                      hitPointY,
-                                                                      CharacterHitTest::TAP,
-                                                                      matchedCharacter );
-  }
-  else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier )
-  {
-    // Ignore Shift-Down for text selection for now.
-
-    // Get first the line index of the current cursor position index.
-    CharacterIndex characterIndex = 0u;
-
-    if( mEventData->mPrimaryCursorPosition > 0u )
-    {
-      characterIndex = mEventData->mPrimaryCursorPosition - 1u;
-    }
-
-    const LineIndex lineIndex = mModel->mVisualModel->GetLineOfCharacter( characterIndex );
-
-    if( lineIndex + 1u < mModel->mVisualModel->mLines.Count() )
-    {
-      // Retrieve the cursor position info.
-      CursorInfo cursorInfo;
-      GetCursorPosition( mEventData->mPrimaryCursorPosition,
-                         cursorInfo );
-
-      // Get the line below.
-      const LineRun& line = *( mModel->mVisualModel->mLines.Begin() + lineIndex + 1u );
-
-      // Get the next hit 'y' point.
-      const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
-
-      // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
-      bool matchedCharacter = false;
-      mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
-                                                                        mModel->mLogicalModel,
-                                                                        mMetrics,
-                                                                        mEventData->mCursorHookPositionX,
-                                                                        hitPointY,
-                                                                        CharacterHitTest::TAP,
-                                                                        matchedCharacter );
-    }
-  }
-
-  if ( !isShiftModifier && mEventData->mState != EventData::SELECTING )
-  {
-    // Update selection position after moving the cursor
-    mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
-    mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
-  }
-
-  if ( isShiftModifier && IsShowingRealText() && mEventData->mShiftSelectionFlag )
-  {
-    // Handle text selection
-    bool selecting = false;
-
-    if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
-    {
-      // Shift-Left/Right to select the text
-      int cursorPositionDelta = mEventData->mPrimaryCursorPosition - previousPrimaryCursorPosition;
-      if ( cursorPositionDelta > 0 || mEventData->mRightSelectionPosition > 0u ) // Check the boundary
-      {
-        mEventData->mRightSelectionPosition += cursorPositionDelta;
-      }
-      selecting = true;
-    }
-    else if ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition )
-    {
-      // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
-      selecting = true;
-    }
-
-    if ( selecting )
-    {
-      // Notify the cursor position to the InputMethodContext.
-      if( mEventData->mInputMethodContext )
-      {
-        mEventData->mInputMethodContext.SetCursorPosition( mEventData->mPrimaryCursorPosition );
-        mEventData->mInputMethodContext.NotifyCursorPosition();
-      }
-
-      ChangeState( EventData::SELECTING );
-
-      mEventData->mUpdateLeftSelectionPosition = true;
-      mEventData->mUpdateRightSelectionPosition = true;
-      mEventData->mUpdateGrabHandlePosition = true;
-      mEventData->mUpdateHighlightBox = true;
-
-      // Hide the text selection popup if select the text using keyboard instead of moving grab handles
-      if( mEventData->mGrabHandlePopupEnabled )
-      {
-        mEventData->mDecorator->SetPopupActive( false );
-      }
-    }
-  }
-  else
-  {
-    // Handle normal cursor move
-    ChangeState( EventData::EDITING );
-    mEventData->mUpdateCursorPosition = true;
-  }
-
-  mEventData->mUpdateInputStyle = true;
-  mEventData->mScrollAfterUpdatePosition = true;
+  ControllerImplEventHandler::OnCursorKeyEvent(*this, event);
 }
 
 void Controller::Impl::OnTapEvent( const Event& event )
 {
-  if( NULL != mEventData )
-  {
-    const unsigned int tapCount = event.p1.mUint;
-
-    if( 1u == tapCount )
-    {
-      if( IsShowingRealText() )
-      {
-        // Convert from control's coords to text's coords.
-        const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
-        const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
-
-        // Keep the tap 'x' position. Used to move the cursor.
-        mEventData->mCursorHookPositionX = xPosition;
-
-        // Whether to touch point hits on a glyph.
-        bool matchedCharacter = false;
-        mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
-                                                                          mModel->mLogicalModel,
-                                                                          mMetrics,
-                                                                          xPosition,
-                                                                          yPosition,
-                                                                          CharacterHitTest::TAP,
-                                                                          matchedCharacter );
-
-        // When the cursor position is changing, delay cursor blinking
-        mEventData->mDecorator->DelayCursorBlink();
-      }
-      else
-      {
-        mEventData->mPrimaryCursorPosition = 0u;
-      }
-
-      // Update selection position after tapping
-      mEventData->mLeftSelectionPosition = mEventData->mPrimaryCursorPosition;
-      mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
-
-      mEventData->mUpdateCursorPosition = true;
-      mEventData->mUpdateGrabHandlePosition = true;
-      mEventData->mScrollAfterUpdatePosition = true;
-      mEventData->mUpdateInputStyle = true;
-
-      // Notify the cursor position to the InputMethodContext.
-      if( mEventData->mInputMethodContext )
-      {
-        mEventData->mInputMethodContext.SetCursorPosition( mEventData->mPrimaryCursorPosition );
-        mEventData->mInputMethodContext.NotifyCursorPosition();
-      }
-    }
-    else if( 2u == tapCount )
-    {
-      if( mEventData->mSelectionEnabled )
-      {
-        // Convert from control's coords to text's coords.
-        const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
-        const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
-
-        // Calculates the logical position from the x,y coords.
-        RepositionSelectionHandles( xPosition,
-                                    yPosition,
-                                    mEventData->mDoubleTapAction );
-      }
-    }
-  }
+  ControllerImplEventHandler::OnTapEvent(*this, event);
 }
 
 void Controller::Impl::OnPanEvent( const Event& event )
 {
-  if( NULL == mEventData )
-  {
-    // Nothing to do if there is no text input.
-    return;
-  }
-
-  const bool isHorizontalScrollEnabled = mEventData->mDecorator->IsHorizontalScrollEnabled();
-  const bool isVerticalScrollEnabled = mEventData->mDecorator->IsVerticalScrollEnabled();
-
-  if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
-  {
-    // Nothing to do if scrolling is not enabled.
-    return;
-  }
-
-  const GestureState state = static_cast<GestureState>( event.p1.mInt );
-  switch( state )
-  {
-    case GestureState::STARTED:
-    {
-      // Will remove the cursor, handles or text's popup, ...
-      ChangeState( EventData::TEXT_PANNING );
-      break;
-    }
-    case GestureState::CONTINUING:
-    {
-      const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
-      const Vector2 currentScroll = mModel->mScrollPosition;
-
-      if( isHorizontalScrollEnabled )
-      {
-        const float displacementX = event.p2.mFloat;
-        mModel->mScrollPosition.x += displacementX;
-
-        ClampHorizontalScroll( layoutSize );
-      }
-
-      if( isVerticalScrollEnabled )
-      {
-        const float displacementY = event.p3.mFloat;
-        mModel->mScrollPosition.y += displacementY;
-
-        ClampVerticalScroll( layoutSize );
-      }
-
-      mEventData->mDecorator->UpdatePositions( mModel->mScrollPosition - currentScroll );
-      break;
-    }
-    case GestureState::FINISHED:
-    case GestureState::CANCELLED: // FALLTHROUGH
-    {
-      // Will go back to the previous state to show the cursor, handles, the text's popup, ...
-      ChangeState( mEventData->mPreviousState );
-      break;
-    }
-    default:
-      break;
-  }
+  ControllerImplEventHandler::OnPanEvent(*this, event);
 }
 
 void Controller::Impl::OnLongPressEvent( const Event& event )
 {
-  DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
-
-  if( !IsShowingRealText() && ( EventData::EDITING == mEventData->mState ) )
-  {
-    ChangeState( EventData::EDITING_WITH_POPUP );
-    mEventData->mDecoratorUpdated = true;
-    mEventData->mUpdateInputStyle = true;
-  }
-  else
-  {
-    if( mEventData->mSelectionEnabled )
-    {
-      // Convert from control's coords to text's coords.
-      const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
-      const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
-
-      // Calculates the logical position from the x,y coords.
-      RepositionSelectionHandles( xPosition,
-                                  yPosition,
-                                  mEventData->mLongPressAction );
-    }
-  }
+  ControllerImplEventHandler::OnLongPressEvent(*this, event);
 }
 
 void Controller::Impl::OnHandleEvent( const Event& event )
 {
-  if( NULL == mEventData )
-  {
-    // Nothing to do if there is no text input.
-    return;
-  }
-
-  const unsigned int state = event.p1.mUint;
-  const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
-  const bool isSmoothHandlePanEnabled = mEventData->mDecorator->IsSmoothHandlePanEnabled();
-
-  if( HANDLE_PRESSED == state )
-  {
-    // Convert from decorator's coords to text's coords.
-    const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
-    const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
-
-    // Need to calculate the handle's new position.
-    bool matchedCharacter = false;
-    const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
-                                                                          mModel->mLogicalModel,
-                                                                          mMetrics,
-                                                                          xPosition,
-                                                                          yPosition,
-                                                                          CharacterHitTest::SCROLL,
-                                                                          matchedCharacter );
-
-    if( Event::GRAB_HANDLE_EVENT == event.type )
-    {
-      ChangeState ( EventData::GRAB_HANDLE_PANNING );
-
-      if( handleNewPosition != mEventData->mPrimaryCursorPosition )
-      {
-        // Updates the cursor position if the handle's new position is different than the current one.
-        mEventData->mUpdateCursorPosition = true;
-        // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
-        mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
-        mEventData->mPrimaryCursorPosition = handleNewPosition;
-      }
-
-      // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
-      mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
-    }
-    else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
-    {
-      ChangeState ( EventData::SELECTION_HANDLE_PANNING );
-
-      if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) &&
-          ( handleNewPosition != mEventData->mRightSelectionPosition ) )
-      {
-        // Updates the highlight box if the handle's new position is different than the current one.
-        mEventData->mUpdateHighlightBox = true;
-        // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
-        mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
-        mEventData->mLeftSelectionPosition = handleNewPosition;
-      }
-
-      // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
-      mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
-
-      // Will define the order to scroll the text to match the handle position.
-      mEventData->mIsLeftHandleSelected = true;
-      mEventData->mIsRightHandleSelected = false;
-    }
-    else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
-    {
-      ChangeState ( EventData::SELECTION_HANDLE_PANNING );
-
-      if( ( handleNewPosition != mEventData->mRightSelectionPosition ) &&
-          ( handleNewPosition != mEventData->mLeftSelectionPosition ) )
-      {
-        // Updates the highlight box if the handle's new position is different than the current one.
-        mEventData->mUpdateHighlightBox = true;
-        // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
-        mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
-        mEventData->mRightSelectionPosition = handleNewPosition;
-      }
-
-      // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
-      mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
-
-      // Will define the order to scroll the text to match the handle position.
-      mEventData->mIsLeftHandleSelected = false;
-      mEventData->mIsRightHandleSelected = true;
-    }
-  } // end ( HANDLE_PRESSED == state )
-  else if( ( HANDLE_RELEASED == state ) ||
-           handleStopScrolling )
-  {
-    CharacterIndex handlePosition = 0u;
-    if( handleStopScrolling || isSmoothHandlePanEnabled )
-    {
-      // Convert from decorator's coords to text's coords.
-      const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
-      const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
-
-      bool matchedCharacter = false;
-      handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
-                                                    mModel->mLogicalModel,
-                                                    mMetrics,
-                                                    xPosition,
-                                                    yPosition,
-                                                    CharacterHitTest::SCROLL,
-                                                    matchedCharacter );
-    }
-
-    if( Event::GRAB_HANDLE_EVENT == event.type )
-    {
-      mEventData->mUpdateCursorPosition = true;
-      mEventData->mUpdateGrabHandlePosition = true;
-      mEventData->mUpdateInputStyle = true;
-
-      if( !IsClipboardEmpty() )
-      {
-        ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
-      }
-
-      if( handleStopScrolling || isSmoothHandlePanEnabled )
-      {
-        mEventData->mScrollAfterUpdatePosition = true;
-        mEventData->mPrimaryCursorPosition = handlePosition;
-      }
-    }
-    else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
-    {
-      ChangeState( EventData::SELECTING );
-
-      mEventData->mUpdateHighlightBox = true;
-      mEventData->mUpdateLeftSelectionPosition = true;
-      mEventData->mUpdateRightSelectionPosition = true;
-
-      if( handleStopScrolling || isSmoothHandlePanEnabled )
-      {
-        mEventData->mScrollAfterUpdatePosition = true;
-
-        if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
-            ( handlePosition != mEventData->mLeftSelectionPosition ) )
-        {
-          mEventData->mLeftSelectionPosition = handlePosition;
-        }
-      }
-    }
-    else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
-    {
-      ChangeState( EventData::SELECTING );
-
-      mEventData->mUpdateHighlightBox = true;
-      mEventData->mUpdateRightSelectionPosition = true;
-      mEventData->mUpdateLeftSelectionPosition = true;
-
-      if( handleStopScrolling || isSmoothHandlePanEnabled )
-      {
-        mEventData->mScrollAfterUpdatePosition = true;
-        if( ( handlePosition != mEventData->mRightSelectionPosition ) &&
-            ( handlePosition != mEventData->mLeftSelectionPosition ) )
-        {
-          mEventData->mRightSelectionPosition = handlePosition;
-        }
-      }
-    }
-
-    mEventData->mDecoratorUpdated = true;
-  } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
-  else if( HANDLE_SCROLLING == state )
-  {
-    const float xSpeed = event.p2.mFloat;
-    const float ySpeed = event.p3.mFloat;
-    const Vector2& layoutSize = mModel->mVisualModel->GetLayoutSize();
-    const Vector2 currentScrollPosition = mModel->mScrollPosition;
-
-    mModel->mScrollPosition.x += xSpeed;
-    mModel->mScrollPosition.y += ySpeed;
-
-    ClampHorizontalScroll( layoutSize );
-    ClampVerticalScroll( layoutSize );
-
-    bool endOfScroll = false;
-    if( Vector2::ZERO == ( currentScrollPosition - mModel->mScrollPosition ) )
-    {
-      // Notify the decorator there is no more text to scroll.
-      // The decorator won't send more scroll events.
-      mEventData->mDecorator->NotifyEndOfScroll();
-      // Still need to set the position of the handle.
-      endOfScroll = true;
-    }
-
-    // Set the position of the handle.
-    const bool scrollRightDirection = xSpeed > 0.f;
-    const bool scrollBottomDirection = ySpeed > 0.f;
-    const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
-    const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
-
-    if( Event::GRAB_HANDLE_EVENT == event.type )
-    {
-      ChangeState( EventData::GRAB_HANDLE_PANNING );
-
-      // Get the grab handle position in decorator coords.
-      Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE );
-
-      if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
-      {
-        // Position the grag handle close to either the left or right edge.
-        position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
-      }
-
-      if( mEventData->mDecorator->IsVerticalScrollEnabled() )
-      {
-        position.x = mEventData->mCursorHookPositionX;
-
-        // Position the grag handle close to either the top or bottom edge.
-        position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
-      }
-
-      // Get the new handle position.
-      // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
-      bool matchedCharacter = false;
-      const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
-                                                                         mModel->mLogicalModel,
-                                                                         mMetrics,
-                                                                         position.x - mModel->mScrollPosition.x,
-                                                                         position.y - mModel->mScrollPosition.y,
-                                                                         CharacterHitTest::SCROLL,
-                                                                         matchedCharacter );
-
-      if( mEventData->mPrimaryCursorPosition != handlePosition )
-      {
-        mEventData->mUpdateCursorPosition = true;
-        mEventData->mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
-        mEventData->mScrollAfterUpdatePosition = true;
-        mEventData->mPrimaryCursorPosition = handlePosition;
-      }
-      mEventData->mUpdateInputStyle = mEventData->mUpdateCursorPosition;
-
-      // Updates the decorator if the soft handle panning is enabled.
-      mEventData->mDecoratorUpdated = isSmoothHandlePanEnabled;
-    }
-    else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
-    {
-      ChangeState( EventData::SELECTION_HANDLE_PANNING );
-
-      // Get the selection handle position in decorator coords.
-      Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
-
-      if( mEventData->mDecorator->IsHorizontalScrollEnabled() )
-      {
-        // Position the selection handle close to either the left or right edge.
-        position.x = scrollRightDirection ? 0.f : mModel->mVisualModel->mControlSize.width;
-      }
-
-      if( mEventData->mDecorator->IsVerticalScrollEnabled() )
-      {
-        position.x = mEventData->mCursorHookPositionX;
-
-        // Position the grag handle close to either the top or bottom edge.
-        position.y = scrollBottomDirection ? 0.f : mModel->mVisualModel->mControlSize.height;
-      }
-
-      // Get the new handle position.
-      // The selection handle's position is in decorator's coords. Need to transform to text's coords.
-      bool matchedCharacter = false;
-      const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mModel->mVisualModel,
-                                                                         mModel->mLogicalModel,
-                                                                         mMetrics,
-                                                                         position.x - mModel->mScrollPosition.x,
-                                                                         position.y - mModel->mScrollPosition.y,
-                                                                         CharacterHitTest::SCROLL,
-                                                                         matchedCharacter );
-
-      if( leftSelectionHandleEvent )
-      {
-        const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition );
-
-        if( differentHandles || endOfScroll )
-        {
-          mEventData->mUpdateHighlightBox = true;
-          mEventData->mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
-          mEventData->mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
-          mEventData->mLeftSelectionPosition = handlePosition;
-        }
-      }
-      else
-      {
-        const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition );
-        if( differentHandles || endOfScroll )
-        {
-          mEventData->mUpdateHighlightBox = true;
-          mEventData->mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
-          mEventData->mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
-          mEventData->mRightSelectionPosition = handlePosition;
-        }
-      }
-
-      if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
-      {
-        RepositionSelectionHandles();
-
-        mEventData->mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
-      }
-    }
-    mEventData->mDecoratorUpdated = true;
-  } // end ( HANDLE_SCROLLING == state )
+  ControllerImplEventHandler::OnHandleEvent(*this, event);
 }
 
 void Controller::Impl::OnSelectEvent( const Event& event )
 {
-  if( NULL == mEventData )
-  {
-    // Nothing to do if there is no text.
-    return;
-  }
-
-  if( mEventData->mSelectionEnabled )
-  {
-    // Convert from control's coords to text's coords.
-    const float xPosition = event.p2.mFloat - mModel->mScrollPosition.x;
-    const float yPosition = event.p3.mFloat - mModel->mScrollPosition.y;
-
-    // Calculates the logical position from the x,y coords.
-    RepositionSelectionHandles( xPosition,
-                                yPosition,
-                                Controller::NoTextTap::HIGHLIGHT );
-  }
+  ControllerImplEventHandler::OnSelectEvent(*this, event);
 }
 
 void Controller::Impl::OnSelectAllEvent()
 {
-  DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
-
-  if( NULL == mEventData )
-  {
-    // Nothing to do if there is no text.
-    return;
-  }
-
-  if( mEventData->mSelectionEnabled )
-  {
-    // Calculates the logical position from the start.
-    RepositionSelectionHandles( 0.f - mModel->mScrollPosition.x,
-                                0.f - mModel->mScrollPosition.y,
-                                Controller::NoTextTap::HIGHLIGHT );
-
-    mEventData->mLeftSelectionPosition = 0u;
-    mEventData->mRightSelectionPosition = mModel->mLogicalModel->mText.Count();
-  }
+  ControllerImplEventHandler::OnSelectAllEvent(*this);
 }
 
 void Controller::Impl::OnSelectNoneEvent()
 {
-  DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectNoneEvent mEventData->mSelectionEnabled%s \n", mEventData->mSelectionEnabled?"true":"false");
-
-  if( NULL == mEventData )
-  {
-    // Nothing to do if there is no text.
-    return;
-  }
-
-  if( mEventData->mSelectionEnabled && mEventData->mState == EventData::SELECTING)
-  {
-    mEventData->mPrimaryCursorPosition = 0u;
-    mEventData->mLeftSelectionPosition = mEventData->mRightSelectionPosition = mEventData->mPrimaryCursorPosition;
-    ChangeState( EventData::INACTIVE );
-    mEventData->mUpdateCursorPosition = true;
-    mEventData->mUpdateInputStyle = true;
-    mEventData->mScrollAfterUpdatePosition = true;
-  }
+  ControllerImplEventHandler::OnSelectNoneEvent(*this);
 }
 
 void Controller::Impl::SetTextSelectionRange(const uint32_t *pStart, const uint32_t *pEnd)
@@ -2072,6 +1410,8 @@ void Controller::Impl::SetTextSelectionRange(const uint32_t *pStart, const uint3
     {
       ChangeState( EventData::SELECTING );
       mEventData->mUpdateHighlightBox = true;
+      mEventData->mUpdateLeftSelectionPosition = true;
+      mEventData->mUpdateRightSelectionPosition = true;
     }
   }
 }
index 3fc33b3..fc30d7b 100755 (executable)
@@ -2,7 +2,7 @@
 #define DALI_TOOLKIT_TEXT_CONTROLLER_IMPL_H
 
 /*
- * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -47,6 +47,7 @@ const float DEFAULT_TEXTFIT_STEP = 1.f;
 //Forward declarations
 struct CursorInfo;
 struct FontDefaults;
+struct ControllerImplEventHandler;
 
 class SelectableControlInterface;
 
@@ -823,6 +824,9 @@ public:
   float mTextFitMaxSize;                   ///< Maximum Font Size for text fit. Default 100
   float mTextFitStepSize;                  ///< Step Size for font intervalse. Default 1
   bool  mTextFitEnabled : 1;               ///< Whether the text's fit is enabled.
+
+private:
+  friend ControllerImplEventHandler;
 };
 
 } // namespace Text
index 78dab18..bda70a4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -65,6 +65,114 @@ namespace Toolkit
 namespace Internal
 {
 
+namespace
+{
+
+/// Parses a Property::Array and sets up the animator appropriately
+void ParseArray(TransitionData::Animator* animator, const Property::Array* array)
+{
+  bool valid = true;
+  Vector4 controlPoints;
+  if( array && array->Count() >= 4 )
+  {
+    for( size_t vecIdx = 0; vecIdx < 4; ++vecIdx )
+    {
+      const Property::Value& v = array->GetElementAt(vecIdx);
+      if( v.GetType() == Property::FLOAT )
+      {
+        controlPoints[vecIdx] = v.Get<float>();
+      }
+      else
+      {
+        valid = false;
+        break;
+      }
+    }
+  }
+  else
+  {
+    valid = false;
+  }
+
+  if( valid )
+  {
+    Vector2 controlPoint1( controlPoints.x, controlPoints.y );
+    Vector2 controlPoint2( controlPoints.z, controlPoints.w );
+    animator->alphaFunction = AlphaFunction( controlPoint1, controlPoint2 );
+  }
+  else
+  {
+    animator->animate = false;
+  }
+}
+
+/// Parses a string value and sets up the animator appropriately
+void ParseString(TransitionData::Animator* animator, std::string alphaFunctionValue)
+{
+  if( alphaFunctionValue == "LINEAR" )
+  {
+    animator->alphaFunction = AlphaFunction(AlphaFunction::LINEAR);
+  }
+  else if( ! alphaFunctionValue.compare(0, 5, "EASE_" ) )
+  {
+    if( alphaFunctionValue == "EASE_IN" )
+    {
+      animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN);
+    }
+    else if( alphaFunctionValue == "EASE_OUT" )
+    {
+      animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT);
+    }
+    else if( ! alphaFunctionValue.compare( 5, 3, "IN_" ) )
+    {
+      if( ! alphaFunctionValue.compare(8, -1, "SQUARE" ))
+      {
+        animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_SQUARE);
+      }
+      else if( ! alphaFunctionValue.compare(8, -1, "OUT" ))
+      {
+        animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_OUT);
+      }
+      else if( ! alphaFunctionValue.compare(8, -1, "OUT_SINE" ))
+      {
+        animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_OUT_SINE);
+      }
+      else if( ! alphaFunctionValue.compare(8, -1, "SINE" ))
+      {
+        animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_SINE);
+      }
+    }
+    else if( ! alphaFunctionValue.compare( 5, 4, "OUT_" ) )
+    {
+      if( ! alphaFunctionValue.compare(9, -1, "SQUARE" ) )
+      {
+        animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT_SQUARE);
+      }
+      else if( ! alphaFunctionValue.compare(9, -1, "SINE" ) )
+      {
+        animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT_SINE);
+      }
+      else if( ! alphaFunctionValue.compare(9, -1, "BACK" ) )
+      {
+        animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT_BACK);
+      }
+    }
+  }
+  else if( alphaFunctionValue == "REVERSE" )
+  {
+    animator->alphaFunction = AlphaFunction(AlphaFunction::REVERSE);
+  }
+  else if( alphaFunctionValue == "BOUNCE" )
+  {
+    animator->alphaFunction = AlphaFunction(AlphaFunction::BOUNCE);
+  }
+  else if( alphaFunctionValue == "SIN" )
+  {
+    animator->alphaFunction = AlphaFunction(AlphaFunction::SIN);
+  }
+}
+} // unnamed namespace
+
 TransitionData::TransitionData()
 {
 }
@@ -171,40 +279,7 @@ TransitionData::Animator* TransitionData::ConvertMap( const Property::Map& map)
         {
           if( value.GetType() == Property::ARRAY )
           {
-            bool valid = true;
-            Vector4 controlPoints;
-            const Property::Array* array = value.GetArray();
-            if( array && array->Count() >= 4 )
-            {
-              for( size_t vecIdx = 0; vecIdx < 4; ++vecIdx )
-              {
-                const Property::Value& v = array->GetElementAt(vecIdx);
-                if( v.GetType() == Property::FLOAT )
-                {
-                  controlPoints[vecIdx] = v.Get<float>();
-                }
-                else
-                {
-                  valid = false;
-                  break;
-                }
-              }
-            }
-            else
-            {
-              valid = false;
-            }
-
-            if( valid )
-            {
-              Vector2 controlPoint1( controlPoints.x, controlPoints.y );
-              Vector2 controlPoint2( controlPoints.z, controlPoints.w );
-              animator->alphaFunction = AlphaFunction( controlPoint1, controlPoint2 );
-            }
-            else
-            {
-              animator->animate = false;
-            }
+            ParseArray(animator, value.GetArray());
           }
           else if( value.GetType() == Property::VECTOR4 )
           {
@@ -215,69 +290,7 @@ TransitionData::Animator* TransitionData::ConvertMap( const Property::Map& map)
           }
           else if( value.GetType() == Property::STRING )
           {
-            std::string alphaFunctionValue = value.Get< std::string >();
-
-            if( alphaFunctionValue == "LINEAR" )
-            {
-              animator->alphaFunction = AlphaFunction(AlphaFunction::LINEAR);
-            }
-            else if( ! alphaFunctionValue.compare(0, 5, "EASE_" ) )
-            {
-              if( alphaFunctionValue == "EASE_IN" )
-              {
-                animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN);
-              }
-              else if( alphaFunctionValue == "EASE_OUT" )
-              {
-                animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT);
-              }
-              else if( ! alphaFunctionValue.compare( 5, 3, "IN_" ) )
-              {
-                if( ! alphaFunctionValue.compare(8, -1, "SQUARE" ))
-                {
-                  animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_SQUARE);
-                }
-                else if( ! alphaFunctionValue.compare(8, -1, "OUT" ))
-                {
-                  animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_OUT);
-                }
-                else if( ! alphaFunctionValue.compare(8, -1, "OUT_SINE" ))
-                {
-                  animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_OUT_SINE);
-                }
-                else if( ! alphaFunctionValue.compare(8, -1, "SINE" ))
-                {
-                  animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_IN_SINE);
-                }
-              }
-              else if( ! alphaFunctionValue.compare( 5, 4, "OUT_" ) )
-              {
-                if( ! alphaFunctionValue.compare(9, -1, "SQUARE" ) )
-                {
-                  animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT_SQUARE);
-                }
-                else if( ! alphaFunctionValue.compare(9, -1, "SINE" ) )
-                {
-                  animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT_SINE);
-                }
-                else if( ! alphaFunctionValue.compare(9, -1, "BACK" ) )
-                {
-                  animator->alphaFunction = AlphaFunction(AlphaFunction::EASE_OUT_BACK);
-                }
-              }
-            }
-            else if( alphaFunctionValue == "REVERSE" )
-            {
-              animator->alphaFunction = AlphaFunction(AlphaFunction::REVERSE);
-            }
-            else if( alphaFunctionValue == "BOUNCE" )
-            {
-              animator->alphaFunction = AlphaFunction(AlphaFunction::BOUNCE);
-            }
-            else if( alphaFunctionValue == "SIN" )
-            {
-              animator->alphaFunction = AlphaFunction(AlphaFunction::SIN);
-            }
+            ParseString(animator, value.Get< std::string >());
           }
           else
           {
index b7dc35f..b9a4da5 100644 (file)
@@ -29,7 +29,7 @@ namespace Toolkit
 {
 const unsigned int TOOLKIT_MAJOR_VERSION = 1;
 const unsigned int TOOLKIT_MINOR_VERSION = 9;
-const unsigned int TOOLKIT_MICRO_VERSION = 34;
+const unsigned int TOOLKIT_MICRO_VERSION = 35;
 const char* const  TOOLKIT_BUILD_DATE    = __DATE__ " " __TIME__;
 
 #ifdef DEBUG_ENABLED
index 482b4d9..8023790 100644 (file)
@@ -1,6 +1,6 @@
 Name:       dali2-toolkit
 Summary:    Dali 3D engine Toolkit
-Version:    1.9.34
+Version:    1.9.35
 Release:    1
 Group:      System/Libraries
 License:    Apache-2.0 and BSD-3-Clause and MIT