/* * Copyright (C) 2015-present, osfans * waxaca@163.com https://github.com/osfans * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package com.osfans.trime; import android.content.Context; import android.content.SharedPreferences; import android.content.res.AssetManager; import android.content.res.Resources; import android.graphics.BitmapFactory; import android.graphics.Typeface; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.os.SystemClock; import android.util.Log; import android.util.TypedValue; import com.osfans.trime.enums.InlineModeType; import com.osfans.trime.enums.WindowsPositionType; import java.io.File; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; /** 解析YAML配置文件 */ public class Config { // 默认的用户数据路径 private static final String RIME = "rime"; private static final String TAG = "Config"; private static String userDataDir; private static String sharedDataDir; private Map mStyle, mDefaultStyle; private String themeName; private static String defaultName = "trime"; private String schema_id; private static Config self = null; private SharedPreferences mPref; private Map fallbackColors; private Map presetColorSchemes, presetKeyboards; public Config(Context context) { self = this; mPref = Function.getPref(context); userDataDir = context.getString(R.string.default_user_data_dir); sharedDataDir = context.getString(R.string.default_shared_data_dir); themeName = mPref.getString("pref_selected_theme", "trime"); prepareRime(context); deployTheme(); init(); } public String getTheme() { return themeName; } public String getSharedDataDir() { return mPref.getString("shared_data_dir", sharedDataDir); } public String getUserDataDir() { return mPref.getString("user_data_dir", userDataDir); } public String getResDataDir(String sub) { String name = new File(getSharedDataDir(), sub).getPath(); if (new File(name).exists()) return name; return new File(getUserDataDir(), sub).getPath(); } private void prepareRime(Context context) { boolean isExist = new File(getSharedDataDir()).exists(); boolean isOverwrite = Function.isDiffVer(context); String defaultFile = "trime.yaml"; if (isOverwrite) { copyFileOrDir(context, RIME, true); } else if (isExist) { String path = new File(RIME, defaultFile).getPath(); copyFileOrDir(context, path, false); } else { copyFileOrDir(context, RIME, false); } while (!new File(getSharedDataDir(), defaultFile).exists()) { SystemClock.sleep(3000); copyFileOrDir(context, RIME, isOverwrite); } Rime.get(!isExist); //覆蓋時不強制部署 } public static String[] getThemeKeys(boolean isUser) { File d = new File(isUser ? get().getUserDataDir() : get().getSharedDataDir()); FilenameFilter trimeFilter = new FilenameFilter() { @Override public boolean accept(File dir, String filename) { return filename.endsWith("trime.yaml"); } }; return d.list(trimeFilter); } public static String[] getThemeNames(String[] keys) { if (keys == null) return null; int n = keys.length; String[] names = new String[n]; for (int i = 0; i < n; i++) { String k = keys[i].replace(".trime.yaml", "").replace(".yaml", ""); names[i] = k; } return names; } public static boolean deployOpencc() { String dataDir = get().getResDataDir("opencc"); File d = new File(dataDir); if (d.exists()) { FilenameFilter txtFilter = new FilenameFilter() { @Override public boolean accept(File dir, String filename) { return filename.endsWith(".txt"); } }; for (String txtName : d.list(txtFilter)) { txtName = new File(dataDir, txtName).getPath(); String ocdName = txtName.replace(".txt", ".ocd"); Rime.opencc_convert_dictionary(txtName, ocdName, "text", "ocd"); } } return true; } public static String[] list(Context context, String path) { AssetManager assetManager = context.getAssets(); String assets[] = null; try { assets = assetManager.list(path); } catch (IOException ex) { Log.e(TAG, "I/O Exception", ex); } return assets; } public boolean copyFileOrDir(Context context, String path, boolean overwrite) { AssetManager assetManager = context.getAssets(); String assets[] = null; try { assets = assetManager.list(path); if (assets.length == 0) { copyFile(context, path, overwrite); } else { File dir = new File(getSharedDataDir(), path.length() >= 5 ? path.substring(5) : ""); if (!dir.exists()) dir.mkdir(); for (int i = 0; i < assets.length; ++i) { String assetPath = new File(path, assets[i]).getPath(); copyFileOrDir(context, assetPath, overwrite); } } } catch (IOException ex) { Log.e(TAG, "I/O Exception", ex); return false; } return true; } private boolean copyFile(Context context, String filename, boolean overwrite) { AssetManager assetManager = context.getAssets(); InputStream in = null; OutputStream out = null; try { in = assetManager.open(filename); String newFileName = new File(filename.endsWith(".bin") ? getUserDataDir() : getSharedDataDir(), filename.length() >= 5 ? filename.substring(5) : "").getPath(); if (new File(newFileName).exists() && !overwrite) return true; out = new FileOutputStream(newFileName); int BLK_SIZE = 1024; byte[] buffer = new byte[BLK_SIZE]; int read; while ((read = in.read(buffer)) != -1) { out.write(buffer, 0, read); } in.close(); in = null; out.flush(); out.close(); out = null; } catch (Exception e) { Log.e(TAG, e.getMessage()); return false; } return true; } private void deployTheme() { if (getUserDataDir().contentEquals(getSharedDataDir())) return; //相贝思科件夾不部署主題 String[] configs = get().getThemeKeys(false); for (String config: configs) Rime.deploy_config_file(config, "config_version"); } public void setTheme(String theme) { themeName = theme; SharedPreferences.Editor edit = mPref.edit(); edit.putString("pref_selected_theme", themeName); edit.apply(); init(); } private void init() { try { Rime.deploy_config_file(themeName + ".yaml", "config_version"); Map m = Rime.config_get_map(themeName, ""); if (m == null) { themeName = defaultName; m = Rime.config_get_map(themeName, ""); } Map mk = (Map) m.get("android_keys"); mDefaultStyle = (Map) m.get("style"); fallbackColors = (Map) m.get("fallback_colors"); Key.androidKeys = (List) mk.get("name"); Key.setSymbolStart(Key.androidKeys.contains("A") ? Key.androidKeys.indexOf("A") : 284); Key.setSymbols((String) mk.get("symbols")); if (Function.isEmpty(Key.getSymbols())) Key.setSymbols("ABCDEFGHIJKLMNOPQRSTUVWXYZ!\"$%&:<>?^_{|}~"); Key.presetKeys = (Map) m.get("preset_keys"); presetColorSchemes = (Map) m.get("preset_color_schemes"); presetKeyboards = (Map) m.get("preset_keyboards"); Rime.setShowSwitches(getShowSwitches()); reset(); } catch (Exception e) { Log.e(TAG, e.getMessage()); setTheme(defaultName); } } public void reset() { schema_id = Rime.getSchemaId(); mStyle = (Map) Rime.schema_get_value(schema_id, "style"); } private Object _getValue(String k1, String k2) { Map m; if (mStyle != null && mStyle.containsKey(k1)) { m = (Map) mStyle.get(k1); if (m != null && m.containsKey(k2)) return m.get(k2); } if (mDefaultStyle != null && mDefaultStyle.containsKey(k1)) { m = (Map) mDefaultStyle.get(k1); if (m != null && m.containsKey(k2)) return m.get(k2); } return null; } private Object _getValue(String k1) { if (mStyle != null && mStyle.containsKey(k1)) return mStyle.get(k1); if (mDefaultStyle != null && mDefaultStyle.containsKey(k1)) return mDefaultStyle.get(k1); return null; } public Object getValue(String s) { String[] ss = s.split("/"); if (ss.length == 1) return _getValue(ss[0]); else if (ss.length == 2) return _getValue(ss[0], ss[1]); return null; } public boolean hasKey(String s) { return getValue(s) != null; } private String getKeyboardName(String name) { if (name.contentEquals(".default")) { if (presetKeyboards.containsKey(schema_id)) name = schema_id; //匹配方案名 else { if (schema_id.indexOf("_") >= 0) name = schema_id.split("_")[0]; if (!presetKeyboards.containsKey(name)) { //匹配“_”前的方案名 Object o = Rime.schema_get_value(schema_id, "speller/alphabet"); name = "qwerty"; //26 if (o != null) { String alphabet = o.toString(); if (presetKeyboards.containsKey(alphabet)) name = alphabet; //匹配字母表 else { if (alphabet.indexOf(",") >= 0 || alphabet.indexOf(";") >= 0) name += "_"; if (alphabet.indexOf("0") >= 0 || alphabet.indexOf("1") >= 0) name += "0"; } } } } } if (!presetKeyboards.containsKey(name)) name = "default"; Map m = (Map) presetKeyboards.get(name); if (m.containsKey("import_preset")) { name = m.get("import_preset").toString(); } return name; } public List getKeyboardNames() { List names = (List) getValue("keyboards"); List keyboards = new ArrayList(); for (String s : names) { s = getKeyboardName(s); if (!keyboards.contains(s)) keyboards.add(s); } return keyboards; } public Map getKeyboard(String name) { if (!presetKeyboards.containsKey(name)) name = "default"; return (Map) presetKeyboards.get(name); } public static Config get() { return self; } public static Config get(Context context) { if (self == null) self = new Config(context); return self; } public void destroy() { if (mDefaultStyle != null) mDefaultStyle.clear(); if (mStyle != null) mStyle.clear(); self = null; } private static int getPixel(Float f) { if (f == null) return 0; return (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, f, Resources.getSystem().getDisplayMetrics()); } public int getPixel(String key) { return getPixel(getFloat(key)); } public static Integer getPixel(Map m, String k, Object s) { Object o = getValue(m, k, s); if (o == null) return null; return getPixel(Float.valueOf(o.toString())); } public static Integer getPixel(Map m, String k) { return getPixel(m, k, null); } public static Integer getColor(Map m, String k) { Integer color = null; if (m.containsKey(k)) { Object o = m.get(k); String s = o.toString(); color = parseColor(s); if (color == null) color = get().getCurrentColor(s); } return color; } public static Drawable getColorDrawable(Map m, String k) { if (m.containsKey(k)) { Object o = m.get(k); String s = o.toString(); Integer color = parseColor(s); if (color != null) { GradientDrawable gd = new GradientDrawable(); gd.setColor(color); return gd; } else { Config config = get(); Drawable d = config.getCurrentColorDrawable(s); if (d == null) d = config.drawableObject(o); return d; } } return null; } public static Object getValue(Map m, String k, Object o) { return m.containsKey(k) ? m.get(k) : o; } public static Integer getInt(Map m, String k, Object s) { Object o = getValue(m, k, s); if (o == null) return null; return Long.decode(o.toString()).intValue(); } public static Float getFloat(Map m, String k) { Object o = getValue(m, k, null); if (o == null) return null; return Float.valueOf(o.toString()); } public static Double getDouble(Map m, String k, Object s) { Object o = getValue(m, k, s); if (o == null) return null; return Double.valueOf(o.toString()); } public static String getString(Map m, String k, Object s) { Object o = getValue(m, k, s); if (o == null) return ""; return o.toString(); } public static String getString(Map m, String k) { return getString(m, k, ""); } public static Boolean getBoolean(Map m, String k, Object s) { Object o = getValue(m, k, s); if (o == null) return null; return Boolean.valueOf(o.toString()); } public static Boolean getBoolean(Map m, String k) { return getBoolean(m, k, true); } public boolean getBoolean(String key) { Object o = getValue(key); if (o == null) return true; return Boolean.valueOf(o.toString()); } public double getDouble(String key) { Object o = getValue(key); if (o == null) return 0d; return Double.valueOf(o.toString()); } public float getFloat(String key) { Object o = getValue(key); if (o == null) return 0f; return Float.valueOf(o.toString()); } public int getInt(String key) { Object o = getValue(key); if (o == null) return 0; return Long.decode(o.toString()).intValue(); } public String getString(String key) { Object o = getValue(key); if (o == null) return ""; return o.toString(); } private Object getColorObject(String key) { String scheme = getColorScheme(); if (!presetColorSchemes.containsKey(scheme)) scheme = getString("color_scheme"); //主題中指定的配色 if (!presetColorSchemes.containsKey(scheme)) scheme = "default"; //主題中的default配色 Map map = (Map) presetColorSchemes.get(scheme); if (map == null) return null; setColor(scheme); Object o = map.get(key); String fallbackKey = key; while (o == null && fallbackColors.containsKey(fallbackKey)) { fallbackKey = fallbackColors.get(fallbackKey); o = map.get(fallbackKey); } return o; } private static Integer parseColor(String s) { Integer color = null; if (s.contains(".")) return color; //picture name try { s = s.toLowerCase(Locale.getDefault()); if (s.startsWith("0x")) { if (s.length() == 3 || s.length() == 4) s = String.format("#%02x000000", Long.decode(s.substring(2))); //0xAA else if (s.length() < 8) s = String.format("#%06x", Long.decode(s.substring(2))); else if (s.length() == 9) s = "#0" + s.substring(2); } color = Color.parseColor(s.replace("0x", "#")); } catch (Exception e) { //Log.e(TAG, "unknown color " + s); } return color; } public Integer getCurrentColor(String key) { Object o = getColorObject(key); if (o == null) return null; return parseColor(o.toString()); } public Integer getColor(String key) { Object o = getColorObject(key); if (o == null) { o = ((Map) presetColorSchemes.get("default")).get(key); } if (o == null) return null; return parseColor(o.toString()); } public String getColorScheme() { return mPref.getString("pref_selected_color_scheme", "default"); } public void setColor(String color) { SharedPreferences.Editor edit = mPref.edit(); edit.putString("pref_selected_color_scheme", color); edit.apply(); //deployTheme(); } public String[] getColorKeys() { if (presetColorSchemes == null) return null; String[] keys = new String[presetColorSchemes.size()]; presetColorSchemes.keySet().toArray(keys); return keys; } public String[] getColorNames(String[] keys) { if (keys == null) return null; int n = keys.length; String[] names = new String[n]; for (int i = 0; i < n; i++) { Map m = (Map) presetColorSchemes.get(keys[i]); names[i] = m.get("name").toString(); } return names; } public Typeface getFont(String key) { String name = getString(key); if (name != null) { File f = new File(getResDataDir("fonts"), name); if (f.exists()) return Typeface.createFromFile(f); } return Typeface.DEFAULT; } private Drawable drawableObject(Object o) { if (o == null) return null; String name = o.toString(); Integer color = parseColor(name); if (color != null) { GradientDrawable gd = new GradientDrawable(); gd.setColor(color); return gd; } else { String nameDirectory = getResDataDir("backgrounds"); name = new File(nameDirectory, name).getPath(); File f = new File(name); if (f.exists()) { return new BitmapDrawable(BitmapFactory.decodeFile(name)); } } return null; } private Drawable getCurrentColorDrawable(String key) { Object o = getColorObject(key); return drawableObject(o); } public Drawable getColorDrawable(String key) { Object o = getColorObject(key); if (o == null) { o = ((Map) presetColorSchemes.get("default")).get(key); } return drawableObject(o); } public Drawable getDrawable(String key) { Object o = getValue(key); return drawableObject(o); } public InlineModeType getInlinePreedit() { switch (mPref.getString("inline_preedit", "preview")) { case "preview": case "preedit": case "true": return InlineModeType.INLINE_PREVIEW; case "composition": return InlineModeType.INLINE_COMPOSITION; case "input": return InlineModeType.INLINE_INPUT; } return InlineModeType.INLINE_NONE; } public WindowsPositionType getWinPos() { return WindowsPositionType.fromString(getString("layout/position")); } public boolean isShowStatusIcon() { return mPref.getBoolean("pref_notification_icon", false); } public boolean isDestroyOnQuit() { return mPref.getBoolean("pref_destroy_on_quit", false); } public int getLongTimeout() { int progress = mPref.getInt("longpress_timeout", 20); if (progress > 60) progress = 60; return progress * 10 + 100; } public int getRepeatInterval() { int progress = mPref.getInt("repeat_interval", 4); if (progress > 9) progress = 9; return progress * 10 + 10; } private boolean getShowSwitches() { return mPref.getBoolean("show_switches", true); } public boolean getShowPreview() { return mPref.getBoolean("show_preview", false); } public boolean getShowWindow() { return mPref.getBoolean("show_window", true) && hasKey("window"); } public boolean getSoftCursor() { return mPref.getBoolean("soft_cursor", true); } }