]> gitweb.fperrin.net Git - Dictionary.git/commitdiff
Switching to WebView!
authorThad Hughes <thad.hughes@gmail.com>
Mon, 16 Jan 2012 19:43:16 +0000 (11:43 -0800)
committerThad Hughes <thad.hughes@gmail.com>
Mon, 16 Jan 2012 19:43:16 +0000 (11:43 -0800)
13 files changed:
res/values/strings.xml
res/values/themes.xml
src/com/hughes/android/dictionary/C.java
src/com/hughes/android/dictionary/DictionaryActivity.java
src/com/hughes/android/dictionary/DictionaryEditActivity.java
src/com/hughes/android/dictionary/DictionaryManagerActivity.java
src/com/hughes/android/dictionary/QuickDicConfig.java
src/com/hughes/android/dictionary/engine/AbstractEntry.java
src/com/hughes/android/dictionary/engine/Dictionary.java
src/com/hughes/android/dictionary/engine/EntrySource.java
src/com/hughes/android/dictionary/engine/Language.java
src/com/hughes/android/dictionary/engine/PairEntry.java
src/com/hughes/android/dictionary/engine/TextEntry.java

index 3a90f6bcccbc76d2e63ccbc3e414b064f52ebd05..4688787a3f6c6a1b684eb02a8b77254f14261260 100644 (file)
@@ -3,12 +3,13 @@
 
        <string name="app_name">QuickDic</string>
  
-  <!-- DictionaryListActivity -->
+  <!-- DictionaryManagerActivity -->
   <string name="dictionaryManager">Dictionary manager</string>
   <string name="selectADictionary"><![CDATA[Select a dictionary.
 \nLong-press to edit the dictionary config. Press "menu" to add a new dictionary.]]></string>
   <string name="dictionaryConfig">Dictionary config</string>
   <string name="moveToTop">Move to top</string>
+  <string name="deleteDictionary">Delete dictionary</string>
   <string name="notOnDevice">%s (not on device)</string>
 
   <string name="thanksForUpdatingVersion">3.0.1h</string>
index 7403f79ac958ca07e3e2c659fd4fe28617076639..d070800a15b536271b3a78ccd747001157802da7 100644 (file)
@@ -9,7 +9,6 @@
     <item name="android:textColor">#FFFFFF</item>
   </style>
   
-  <color name="theme_default_token_row_fg">#FFFFFF</color>
   <color name="theme_default_token_row_main_bg">#0000FF</color>
   <color name="theme_default_token_row_other_bg">#888888</color>
   <color name="theme_default_other_lang_bg">#222222</color>
index 4c6fff4189c9029cb649bd417eaf0570c9407ee5..acb10ee4f346dd7a0d73bb83bb5f39158e540ef2 100644 (file)
@@ -15,7 +15,7 @@
 package com.hughes.android.dictionary;
 
 public class C {
-  
+
   static final String DICTIONARY_CONFIGS = "dictionaryConfigs";
 
   static final String DICT_INDEX = "dictIndex";
@@ -23,15 +23,21 @@ public class C {
   static final String SEARCH_TOKEN = "searchToken";
 
   public static final String THANKS_FOR_UPDATING_VERSION = "thanksForUpdatingVersion";
-  
+
   enum Theme {
-    DEFAULT(R.style.Theme_Default, R.style.Theme_Light_TokenRow_Fg, R.drawable.theme_default_token_row_main_bg, R.drawable.theme_default_token_row_other_bg, R.drawable.theme_default_other_lang_bg),
-    LIGHT(R.style.Theme_Light, R.style.Theme_Default_TokenRow_Fg, R.drawable.theme_light_token_row_main_bg, R.drawable.theme_light_token_row_other_bg, R.drawable.theme_light_other_lang_bg);
-    
-    private Theme(final int themeId, 
-        final int tokenRowFg,
-        final int tokenRowMainBg,
-        final int tokenRowOtherBg,
+    DEFAULT(R.style.Theme_Default, R.style.Theme_Light_TokenRow_Fg,
+        R.drawable.theme_default_token_row_main_bg,
+        R.drawable.theme_default_token_row_other_bg,
+        R.drawable.theme_default_other_lang_bg),
+        
+        LIGHT(R.style.Theme_Light,
+        R.style.Theme_Light_TokenRow_Fg,
+        R.drawable.theme_light_token_row_main_bg,
+        R.drawable.theme_light_token_row_other_bg,
+        R.drawable.theme_light_other_lang_bg);
+
+    private Theme(final int themeId, final int tokenRowFg,
+        final int tokenRowMainBg, final int tokenRowOtherBg,
         final int otherLangBg) {
       this.themeId = themeId;
       this.tokenRowFg = tokenRowFg;
@@ -39,7 +45,7 @@ public class C {
       this.tokenRowOtherBg = tokenRowOtherBg;
       this.otherLangBg = otherLangBg;
     }
-    
+
     final int themeId;
     final int tokenRowFg;
     final int tokenRowMainBg;
@@ -47,5 +53,4 @@ public class C {
     final int otherLangBg;
   }
 
-
 }
index 0a846e6d9bf6a52bfdfb3d648ddd08ef98dbde76..ac9fd9b03a1a39d3358fd3e39aca957576266a88 100644 (file)
@@ -52,6 +52,7 @@ import android.view.View;
 import android.view.View.OnClickListener;\r
 import android.view.ViewGroup;\r
 import android.view.inputmethod.InputMethodManager;\r
+import android.webkit.WebView;\r
 import android.widget.AdapterView;\r
 import android.widget.AdapterView.AdapterContextMenuInfo;\r
 import android.widget.BaseAdapter;\r
@@ -425,7 +426,7 @@ public class DictionaryActivity extends ListActivity {
     }\r
 \r
     {\r
-      final MenuItem dictionaryList = menu.add(getString(R.string.dictionaryList));\r
+      final MenuItem dictionaryList = menu.add(getString(R.string.dictionaryManager));\r
       dictionaryList.setOnMenuItemClickListener(new OnMenuItemClickListener() {\r
         public boolean onMenuItemClick(final MenuItem menuItem) {\r
           startActivity(DictionaryManagerActivity.getIntent(DictionaryActivity.this));\r
@@ -435,17 +436,6 @@ public class DictionaryActivity extends ListActivity {
       });\r
     }\r
 \r
-    {\r
-      final MenuItem dictionaryEdit = menu.add(getString(R.string.editDictionary));\r
-      dictionaryEdit.setOnMenuItemClickListener(new OnMenuItemClickListener() {\r
-        public boolean onMenuItemClick(final MenuItem menuItem) {\r
-          final Intent intent = DictionaryEditActivity.getIntent(dictIndex);\r
-          startActivity(intent);\r
-          return false;\r
-        }\r
-      });\r
-    }\r
-\r
     {\r
       final MenuItem about = menu.add(getString(R.string.about));\r
       about.setOnMenuItemClickListener(new OnMenuItemClickListener() {\r
@@ -560,6 +550,8 @@ public class DictionaryActivity extends ListActivity {
       return true;\r
     }\r
     if (keyCode == KeyEvent.KEYCODE_BACK) {\r
+      Log.d(LOG, "Clearing dictionary prefs.");\r
+      DictionaryActivity.clearDictionaryPrefs(this);\r
     }\r
     if (keyCode == KeyEvent.KEYCODE_ENTER) {\r
       Log.d(LOG, "Trying to hide soft keyboard.");\r
@@ -704,67 +696,46 @@ public class DictionaryActivity extends ListActivity {
     }\r
 \r
     private View getView(PairEntry.Row row, ViewGroup parent) {\r
-      final TableLayout result = new TableLayout(parent.getContext());\r
+      final WebView result = new WebView(parent.getContext());\r
       final PairEntry entry = row.getEntry();\r
       final int rowCount = entry.pairs.size();\r
+      final StringBuilder html = new StringBuilder();\r
+      html.append("<html><table width=\"100%\">");\r
       for (int r = 0; r < rowCount; ++r) {\r
-        final TableRow tableRow = new TableRow(result.getContext());\r
+        html.append("<tr>");\r
 \r
-        final EditText column1 = new EditText(tableRow.getContext());\r
-        final EditText column2 = new EditText(tableRow.getContext());\r
-        final TableRow.LayoutParams layoutParams = new TableRow.LayoutParams();\r
-        layoutParams.weight = 0.5f;\r
+        final Pair pair = entry.pairs.get(r);\r
+        // TODO: escape both the token and the text.\r
+        final String token = row.getTokenRow(true).getToken();\r
+        final String col1Text = index.swapPairEntries ? pair.lang2 : pair.lang1;\r
+        final String col2Text = index.swapPairEntries ? pair.lang1 : pair.lang2;\r
+        \r
+        col1Text.replaceAll(token, String.format("<b>%s</b", token));\r
 \r
+        // Column1\r
+        html.append("<td width=\"50%\">");\r
         if (r > 0) {\r
-          final TextView spacer = new TextView(tableRow.getContext());\r
-          spacer.setText(" • ");\r
-          tableRow.addView(spacer);\r
+          html.append("<li>");\r
         }\r
-        tableRow.addView(column1, layoutParams);\r
-        if (r > 0) {\r
-          final TextView spacer = new TextView(tableRow.getContext());\r
-          spacer.setText(" • ");\r
-          tableRow.addView(spacer);\r
-        }\r
-        tableRow.addView(column2, layoutParams);\r
-\r
-        column1.setWidth(1);\r
-        column2.setWidth(1);\r
+        html.append(col1Text);\r
+        html.append("</td>");\r
 \r
-        // TODO: color words by gender\r
-        final Pair pair = entry.pairs.get(r);\r
-        final String col1Text = Language.fixBidiText(index.swapPairEntries ? pair.lang2 : pair.lang1);\r
-        column1.setText(col1Text, TextView.BufferType.SPANNABLE);\r
-        final Spannable col1Spannable = (Spannable) column1.getText();\r
-        \r
-        int startPos = 0;\r
-        final String token = row.getTokenRow(true).getToken();\r
-        while ((startPos = col1Text.indexOf(token, startPos)) != -1) {\r
-          col1Spannable.setSpan(new StyleSpan(Typeface.BOLD), startPos,\r
-              startPos + token.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);\r
-          startPos += token.length();\r
+        // Column2\r
+        html.append("<td width=\"50%\">");\r
+        if (r > 0) {\r
+          html.append("<li>");\r
         }\r
+        html.append(col2Text);\r
+        html.append("</td>");\r
 \r
-        String col2Text = index.swapPairEntries ? pair.lang1 : pair.lang2;\r
-        col2Text = Language.fixBidiText(col2Text);\r
-        column2.setText(col2Text, TextView.BufferType.NORMAL);\r
-        \r
-        column1.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSizeSp);\r
-        column2.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSizeSp);\r
-        column2.setBackgroundResource(theme.otherLangBg);\r
-        \r
-        column2.setOnLongClickListener(new EditText.OnLongClickListener() {\r
-          @Override\r
-          public boolean onLongClick(View v) {\r
-            final int start = column2.getSelectionStart();\r
-            final int end = column2.getSelectionStart();\r
-            Log.i(LOG, "Long click on: " + column2.getText().toString().substring(start, end));\r
-            return false;\r
-          }\r
-        });\r
+//        column1.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSizeSp);\r
+//        column2.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSizeSp);\r
 \r
-        result.addView(tableRow);\r
+        html.append("</tr>");\r
       }\r
+      html.append("</html></table>");\r
+      \r
+      result.loadData(html.toString(), "text/html", null);\r
 \r
       return result;\r
     }\r
index 909db75ee9a4729f4e1d1388e2dc8e87e1483c8b..8ad0e258a713026b97126d5ab2eef6b81f64dd47 100644 (file)
@@ -170,7 +170,7 @@ public class DictionaryEditActivity extends Activity {
           }
         });
     
-    final MenuItem dictionaryList = menu.add(getString(R.string.dictionaryList));
+    final MenuItem dictionaryList = menu.add(getString(R.string.dictionaryManager));
     dictionaryList.setOnMenuItemClickListener(new OnMenuItemClickListener() {
       public boolean onMenuItemClick(final MenuItem menuItem) {
         startActivity(DictionaryManagerActivity.getIntent(DictionaryEditActivity.this));
index ac1d97b47f8c61c862f0fc9015f056fbda395699..90578038775171ce21aebb7be70a93efd2d51dc3 100644 (file)
@@ -162,15 +162,6 @@ public class DictionaryManagerActivity extends ListActivity {
     
     final AdapterContextMenuInfo adapterContextMenuInfo = (AdapterContextMenuInfo) menuInfo;
     
-    final MenuItem editMenuItem = menu.add(R.string.editDictionary);
-    editMenuItem.setOnMenuItemClickListener(new OnMenuItemClickListener() {
-      @Override
-      public boolean onMenuItemClick(MenuItem item) {
-        startActivity(DictionaryEditActivity.getIntent(adapterContextMenuInfo.position));
-        return true;
-      }
-    });
-
     if (adapterContextMenuInfo.position > 0) {
       final MenuItem moveToTopMenuItem = menu.add(R.string.moveToTop);
       moveToTopMenuItem.setOnMenuItemClickListener(new OnMenuItemClickListener() {
index 0ad8b38bb01890c7e9291f7001cc16912ccd72d7..ac32748545d487e703fb268bfc11da98a40816b1 100644 (file)
@@ -54,13 +54,13 @@ public final class QuickDicConfig implements Serializable {
     
     final BufferedReader reader = new BufferedReader(new InputStreamReader(context.getResources().openRawResource(R.raw.dictionary_info)));
     String line;
-    String name = "";
     try {
       while ((line = reader.readLine()) != null) {
         if (line.startsWith("#") || line.length() == 0) {
           continue;
         }
         final DictionaryInfo dictionaryInfo = new DictionaryInfo(line);
+        String name = "";
         for (int i = 0; i < dictionaryInfo.langIsos.length; ++i) {
           final Integer langCode = Language.isoCodeToResourceId.get(dictionaryInfo.langIsos[i]);
           final String lang = langCode != null ? context.getString(langCode) : dictionaryInfo.langIsos[i];
index 7fc5012cfd51db70c426e32cef38442cf4133116..24a1d82afb26e8ede46aca8f42cde3f1eac02370 100644 (file)
 
 package com.hughes.android.dictionary.engine;
 
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
 
 public abstract class AbstractEntry {
+  
+  final EntrySource entrySource;
+  
+  protected AbstractEntry(EntrySource entrySource) {
+    this.entrySource = entrySource;
+  }
+
+  public AbstractEntry(Dictionary dictionary, RandomAccessFile raf) throws IOException {
+    if (dictionary.dictFileVersion >= 1) {
+      final int entrySouceIdx = raf.readShort();
+      this.entrySource = dictionary.sources.get(entrySouceIdx);
+    } else {
+      this.entrySource = null;
+    }
+  }
+
+  public void write(RandomAccessFile raf) throws IOException {
+    raf.writeShort(entrySource.index());
+  }
 
   public abstract int addToDictionary(final Dictionary dictionary);
-  
+
 }
index a6e217d6757c78e8c06071401bb3dde4c70f309c..c6629989d77bf7b72ea9cf85e7194a61b7e80f68 100644 (file)
@@ -41,8 +41,14 @@ public class Dictionary implements RAFSerializable<Dictionary> {
   public final List<EntrySource> sources;
   public final List<Index> indices;
   
+  /**
+   * dictFileVersion 1 adds:
+   * <li> counts of tokens in indices.
+   * <li> links to sources?
+   */
+  
   public Dictionary(final String dictInfo) {
-    this.dictFileVersion = 0;
+    this.dictFileVersion = 1;
     this.creationMillis = System.currentTimeMillis();
     this.dictInfo = dictInfo;
     pairEntries = new ArrayList<PairEntry>();
@@ -53,14 +59,19 @@ public class Dictionary implements RAFSerializable<Dictionary> {
 
   public Dictionary(final RandomAccessFile raf) throws IOException {
     dictFileVersion = raf.readInt();
-    if (dictFileVersion != 0) {
+    if (dictFileVersion < 0 || dictFileVersion > 1) {
       throw new IOException("Invalid dictionary version: " + dictFileVersion);
     }
     creationMillis = raf.readLong();
     dictInfo = raf.readUTF();
-    sources = CachingList.createFullyCached(RAFList.create(raf, EntrySource.SERIALIZER, raf.getFilePointer()));
-    pairEntries = CachingList.create(RAFList.create(raf, PairEntry.SERIALIZER, raf.getFilePointer()), CACHE_SIZE);
-    textEntries = CachingList.create(RAFList.create(raf, TextEntry.SERIALIZER, raf.getFilePointer()), CACHE_SIZE);
+    
+    // Load the sources, then seek past them, because reading them later disrupts the offset.
+    final RAFList<EntrySource> rafSources = RAFList.create(raf, EntrySource.SERIALIZER, raf.getFilePointer());
+    sources = new ArrayList<EntrySource>(rafSources);
+    raf.seek(rafSources.getEndOffset());
+    
+    pairEntries = CachingList.create(RAFList.create(raf, new PairEntry.Serializer(this), raf.getFilePointer()), CACHE_SIZE);
+    textEntries = CachingList.create(RAFList.create(raf, new TextEntry.Serializer(this), raf.getFilePointer()), CACHE_SIZE);
     indices = CachingList.createFullyCached(RAFList.create(raf, indexSerializer, raf.getFilePointer()));
     final String end = raf.readUTF(); 
     if (!end.equals(END_OF_DICTIONARY)) {
@@ -74,8 +85,8 @@ public class Dictionary implements RAFSerializable<Dictionary> {
     raf.writeLong(creationMillis);
     raf.writeUTF(dictInfo);
     RAFList.write(raf, sources, EntrySource.SERIALIZER);
-    RAFList.write(raf, pairEntries, PairEntry.SERIALIZER);
-    RAFList.write(raf, textEntries, TextEntry.SERIALIZER);
+    RAFList.write(raf, pairEntries, new PairEntry.Serializer(this));
+    RAFList.write(raf, textEntries, new TextEntry.Serializer(this));
     RAFList.write(raf, indices, indexSerializer);
     raf.writeUTF(END_OF_DICTIONARY);
   }
index f624817d29057d75f2501e73649fa5930c786b59..80c4ebf8fe290d8b04871fa827259114e4da4e4f 100644 (file)
@@ -26,12 +26,10 @@ public class EntrySource extends IndexedObject implements Serializable {
   private static final long serialVersionUID = -1323165134846120269L;
   
   final String name;
-  final int pairEntryStart;
   
-  public EntrySource(final int index, final String name, final int pairEntryStart) {
+  public EntrySource(final int index, final String name) {
     super(index);
     this.name = name;
-    this.pairEntryStart = pairEntryStart;
   }
   
   @Override
@@ -46,14 +44,12 @@ public class EntrySource extends IndexedObject implements Serializable {
     public EntrySource read(RandomAccessFile raf, int readIndex)
         throws IOException {
       final String name = raf.readUTF();
-      final int pairEntryStart = raf.readInt();
-      return new EntrySource(readIndex, name, pairEntryStart);
+      return new EntrySource(readIndex, name);
     }
 
     @Override
     public void write(RandomAccessFile raf, EntrySource t) throws IOException {
       raf.writeUTF(t.name);
-      raf.writeInt(t.pairEntryStart);
     }    
   };
   
index fcf74f1e97bc1564cd660936bd01674c596b78b9..fe24d1f77940d7556be5102d85fd35273029971f 100644 (file)
@@ -14,6 +14,7 @@
 \r
 package com.hughes.android.dictionary.engine;\r
 \r
+import java.util.ArrayList;\r
 import java.util.LinkedHashMap;\r
 import java.util.Locale;\r
 import java.util.Map;\r
@@ -88,6 +89,11 @@ public class Language {
     isoCodeToResourceId.put("CI", R.string.CI);\r
     isoCodeToResourceId.put("YI", R.string.YI);\r
     isoCodeToResourceId.put("ZU", R.string.ZU);\r
+    \r
+    // Hack to allow lower-case ISO codes to work:\r
+    for (final String isoCode : new ArrayList<String>(isoCodeToResourceId.keySet())) {\r
+      isoCodeToResourceId.put(isoCode.toLowerCase(), isoCodeToResourceId.get(isoCode));\r
+    }\r
   }\r
 \r
 \r
@@ -133,10 +139,11 @@ public class Language {
    */\r
   private static final String rtlChars =\r
       "\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC";\r
-  private static final Pattern RTL_TOKEN = Pattern.compile("[" + rtlChars + "]+");\r
+  private static final Pattern RTL_TOKEN = Pattern.compile("[" + rtlChars + "]");\r
   \r
   public static String fixBidiText(final String text) {\r
-    return RTL_TOKEN.matcher(text).replaceAll("\u200e $0 \u200e");\r
+    // TODO: fix me!, use me!\r
+    return text;\r
   }\r
   \r
   // ----------------------------------------------------------------\r
index d42dd681d5dc95ec3ceb28a0ab8ba8d0a233af3f..ebfd84a14f4fd9bbdafc62c4679b6f4ad51721f3 100644 (file)
@@ -27,16 +27,18 @@ public class PairEntry extends AbstractEntry implements RAFSerializable<PairEntr
   
   public final List<Pair> pairs;
 
-  public PairEntry() {
-    pairs = new ArrayList<Pair>(1);
+  public PairEntry(final EntrySource entrySource) {
+    super(entrySource);
+    pairs = new ArrayList<Pair>(1);    
   }
 
-  public PairEntry(final String lang1, final String lang2) {
-    pairs = new ArrayList<Pair>(1);
+  public PairEntry(final EntrySource entrySource, final String lang1, final String lang2) {
+    this(entrySource);
     this.pairs.add(new Pair(lang1, lang2));
   }
   
-  public PairEntry(final RandomAccessFile raf) throws IOException {
+  public PairEntry(final Dictionary dictionary, final RandomAccessFile raf) throws IOException {
+    super(dictionary, raf);
     final int size = raf.readInt();
     pairs = new ArrayList<PairEntry.Pair>(size);
     for (int i = 0; i < size; ++i) {
@@ -45,18 +47,27 @@ public class PairEntry extends AbstractEntry implements RAFSerializable<PairEntr
   }
   @Override
   public void write(RandomAccessFile raf) throws IOException {
+    super.write(raf);
     // TODO: this could be a short.
     raf.writeInt(pairs.size());
     for (int i = 0; i < pairs.size(); ++i) {
+      assert pairs.get(i).lang1.length() > 0;
       raf.writeUTF(pairs.get(i).lang1);
       raf.writeUTF(pairs.get(i).lang2);
     }
   }
   
-  static final RAFSerializer<PairEntry> SERIALIZER = new RAFSerializer<PairEntry>() {
+  static final class Serializer implements RAFSerializer<PairEntry> {
+    
+    final Dictionary dictionary;
+    
+    Serializer(Dictionary dictionary) {
+      this.dictionary = dictionary;
+    }
+
     @Override
     public PairEntry read(RandomAccessFile raf) throws IOException {
-      return new PairEntry(raf);
+      return new PairEntry(dictionary, raf);
     }
 
     @Override
index fa3dc5326a8f65885eb56a11a7f2afbbb0eef30f..8bef29482ec67c1cc65c2901f778829023a0e9d1 100644 (file)
@@ -25,18 +25,27 @@ public class TextEntry extends AbstractEntry implements RAFSerializable<TextEntr
   
   final String text;
   
-  public TextEntry(final RandomAccessFile raf) throws IOException {
+  public TextEntry(final Dictionary dictionary, final RandomAccessFile raf) throws IOException {
+    super(dictionary, raf);
     text = raf.readUTF();
   }
   @Override
   public void write(RandomAccessFile raf) throws IOException {
+    super.write(raf);
     raf.writeUTF(text);
   }
   
-  static final RAFSerializer<TextEntry> SERIALIZER = new RAFSerializer<TextEntry>() {
+  static final class Serializer implements RAFSerializer<TextEntry> {
+    
+    final Dictionary dictionary;
+    
+    Serializer(Dictionary dictionary) {
+      this.dictionary = dictionary;
+    }
+
     @Override
     public TextEntry read(RandomAccessFile raf) throws IOException {
-      return new TextEntry(raf);
+      return new TextEntry(dictionary, raf);
     }
 
     @Override
@@ -44,6 +53,7 @@ public class TextEntry extends AbstractEntry implements RAFSerializable<TextEntr
       t.write(raf);
     }
   };
+
   
   @Override
   public int addToDictionary(final Dictionary dictionary) {