/* * Copyright (C) 2015-2017 Emanuel Moecklin * * 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. */ package com.onegravity.rteditor.toolbar; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; import android.text.Layout; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.LinearLayout; import android.widget.Spinner; import com.onegravity.colorpicker.ColorPickerDialog; import com.onegravity.colorpicker.ColorPickerListener; import com.onegravity.colorpicker.SetColorPickerListenerEvent; import com.onegravity.rteditor.RTToolbar; import com.onegravity.rteditor.RTToolbarListener; import com.onegravity.rteditor.effects.Effects; import com.onegravity.rteditor.fonts.FontManager; import com.onegravity.rteditor.fonts.RTTypeface; import com.onegravity.rteditor.toolbar.spinner.BGColorSpinnerItem; import com.onegravity.rteditor.toolbar.spinner.ColorSpinnerItem; import com.onegravity.rteditor.toolbar.spinner.FontColorSpinnerItem; import com.onegravity.rteditor.toolbar.spinner.FontSizeSpinnerItem; import com.onegravity.rteditor.toolbar.spinner.FontSpinnerItem; import com.onegravity.rteditor.toolbar.spinner.SpinnerItem; import com.onegravity.rteditor.toolbar.spinner.SpinnerItemAdapter; import com.onegravity.rteditor.toolbar.spinner.SpinnerItems; import com.onegravity.rteditor.utils.Helper; import java.util.SortedSet; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** * This class is a concrete implementation of the RTToolbar interface. It uses * toggle buttons for the effects with a simple on/off (like bold/not bold) and * Spinners for the more complex formatting (background color, font color, font * size). * <p> * While the included rte_toolar layout puts all icons in a row it's easy to use * multiple toolbars, each with a subset of formatting options (e.g. one for the * character formatting, one for the paragraph formatting, one for all the rest * like insert image, undo/redo etc.). */ public class HorizontalRTToolbar extends LinearLayout implements RTToolbar, View.OnClickListener { /* * We need a unique id for the toolbar because the RTManager is capable of managing multiple toolbars */ private static AtomicInteger sIdCounter = new AtomicInteger(0); private int mId; private RTToolbarListener mListener; private ViewGroup mToolbarContainer; /* * The buttons */ private RTToolbarImageButton mBold; private RTToolbarImageButton mItalic; private RTToolbarImageButton mUnderline; private RTToolbarImageButton mStrikethrough; private RTToolbarImageButton mSuperscript; private RTToolbarImageButton mSubscript; private RTToolbarImageButton mAlignLeft; private RTToolbarImageButton mAlignCenter; private RTToolbarImageButton mAlignRight; private RTToolbarImageButton mBullet; private RTToolbarImageButton mNumber; /* * The Spinners and their SpinnerAdapters */ private Spinner mFont; private SpinnerItemAdapter<FontSpinnerItem> mFontAdapter; private Spinner mFontSize; private SpinnerItemAdapter<FontSizeSpinnerItem> mFontSizeAdapter; private Spinner mFontColor; private SpinnerItemAdapter<? extends ColorSpinnerItem> mFontColorAdapter; private Spinner mBGColor; private SpinnerItemAdapter<? extends ColorSpinnerItem> mBGColorAdapter; private int mCustomColorFont = Color.BLACK; private int mCustomColorBG = Color.BLACK; private int mPickerId = -1; private ColorPickerListener mColorPickerListener; // ****************************************** Initialize Methods ******************************************* public HorizontalRTToolbar(Context context) { super(context); init(); } public HorizontalRTToolbar(Context context, AttributeSet attrs) { super(context, attrs); init(); } public HorizontalRTToolbar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { synchronized (sIdCounter) { mId = sIdCounter.getAndIncrement(); } SetColorPickerListenerEvent.setListener(mPickerId, mColorPickerListener); } @Override protected void onFinishInflate() { super.onFinishInflate(); // configure regular action buttons mBold = initImageButton(R.id.toolbar_bold); mItalic = initImageButton(R.id.toolbar_italic); mUnderline = initImageButton(R.id.toolbar_underline); mStrikethrough = initImageButton(R.id.toolbar_strikethrough); mSuperscript = initImageButton(R.id.toolbar_superscript); mSubscript = initImageButton(R.id.toolbar_subscript); mAlignLeft = initImageButton(R.id.toolbar_align_left); mAlignCenter = initImageButton(R.id.toolbar_align_center); mAlignRight = initImageButton(R.id.toolbar_align_right); mBullet = initImageButton(R.id.toolbar_bullet); mNumber = initImageButton(R.id.toolbar_number); initImageButton(R.id.toolbar_inc_indent); initImageButton(R.id.toolbar_dec_indent); initImageButton(R.id.toolbar_link); initImageButton(R.id.toolbar_image); initImageButton(R.id.toolbar_undo); initImageButton(R.id.toolbar_redo); initImageButton(R.id.toolbar_clear); // enable/disable capture picture depending on whether the device // has a camera or not PackageManager packageMgr = getContext().getPackageManager(); if (packageMgr.hasSystemFeature(PackageManager.FEATURE_CAMERA)) { initImageButton(R.id.toolbar_image_capture); } else { View imageCapture = findViewById(R.id.toolbar_image_capture); if (imageCapture != null) imageCapture.setVisibility(View.GONE); } // configure font button mFont = (Spinner) findViewById(R.id.toolbar_font); mFontAdapter = createDropDownNav(mFont, R.layout.rte_toolbar_font_spinner, R.layout.rte_toolbar_spinner_item, getFontItems(), mFontListener); // configure font size button mFontSize = (Spinner) findViewById(R.id.toolbar_fontsize); mFontSizeAdapter = createDropDownNav(mFontSize, R.layout.rte_toolbar_fontsize_spinner, R.layout.rte_toolbar_spinner_item, getTextSizeItems(), mFontSizeListener); // configure font color button mFontColor = (Spinner) findViewById(R.id.toolbar_fontcolor); mFontColorAdapter = createDropDownNav(mFontColor, R.layout.rte_toolbar_fontcolor_spinner, R.layout.rte_toolbar_fontcolor_spinner_item, getFontColorItems(), mFontColorListener); // configure bg color button mBGColor = (Spinner) findViewById(R.id.toolbar_bgcolor); mBGColorAdapter = createDropDownNav(mBGColor, R.layout.rte_toolbar_bgcolor_spinner, R.layout.rte_toolbar_bgcolor_spinner_item, getBGColorItems(), mBGColorListener); } private RTToolbarImageButton initImageButton(int id) { RTToolbarImageButton button = (RTToolbarImageButton) findViewById(id); if (button != null) { button.setOnClickListener(this); } return button; } private SpinnerItems<FontSpinnerItem> getFontItems() { /* * Retrieve the fonts. */ SortedSet<RTTypeface> fonts = FontManager.getFonts(getContext()); /* * Create the spinner items */ SpinnerItems<FontSpinnerItem> spinnerItems = new SpinnerItems<FontSpinnerItem>(); spinnerItems.add(new FontSpinnerItem(null)); // empty element for (RTTypeface typeface : fonts) { spinnerItems.add(new FontSpinnerItem(typeface)); } return spinnerItems; } private SpinnerItems<FontSizeSpinnerItem> getTextSizeItems() { SpinnerItems<FontSizeSpinnerItem> spinnerItems = new SpinnerItems<FontSizeSpinnerItem>(); Resources res = getResources(); // empty size spinnerItems.add(new FontSizeSpinnerItem(-1, "", true)); // regular sizes String[] fontSizeEntries = res.getStringArray(R.array.rte_toolbar_fontsizes_entries); int[] fontSizeValues = res.getIntArray(R.array.rte_toolbar_fontsizes_values); for (int i = 0; i < fontSizeEntries.length; i++) { spinnerItems.add(new FontSizeSpinnerItem(fontSizeValues[i], fontSizeEntries[i], false)); } return spinnerItems; } private SpinnerItems<FontColorSpinnerItem> getFontColorItems() { SpinnerItems<FontColorSpinnerItem> spinnerItems = new SpinnerItems<FontColorSpinnerItem>(); Context context = getContext(); // empty color String name = context.getString(R.string.rte_toolbar_color_text); FontColorSpinnerItem spinnerItem = new FontColorSpinnerItem(mCustomColorFont, name, true, false); spinnerItems.add(spinnerItem); // regular colors for (String fontColor : getResources().getStringArray(R.array.rte_toolbar_fontcolors_values)) { int color = Integer.parseInt(fontColor, 16); spinnerItem = new FontColorSpinnerItem(color, name, false, false); spinnerItems.add(spinnerItem); } // custom color name = context.getString(R.string.rte_toolbar_color_custom); spinnerItem = new FontColorSpinnerItem(mCustomColorFont, name, false, true); spinnerItems.add(spinnerItem); return spinnerItems; } private SpinnerItems<BGColorSpinnerItem> getBGColorItems() { SpinnerItems<BGColorSpinnerItem> spinnerItems = new SpinnerItems<BGColorSpinnerItem>(); Context context = getContext(); // empty color String name = context.getString(R.string.rte_toolbar_color_text); BGColorSpinnerItem spinnerItem = new BGColorSpinnerItem(mCustomColorFont, name, true, false); spinnerItems.add(spinnerItem); // regular colors for (String fontColor : getResources().getStringArray(R.array.rte_toolbar_fontcolors_values)) { int color = Integer.parseInt(fontColor, 16); spinnerItem = new BGColorSpinnerItem(color, name, false, false); spinnerItems.add(spinnerItem); } // custom color name = context.getString(R.string.rte_toolbar_color_custom); spinnerItem = new BGColorSpinnerItem(mCustomColorFont, name, false, true); spinnerItems.add(spinnerItem); return spinnerItems; } private <T extends SpinnerItem> SpinnerItemAdapter<T> createDropDownNav(Spinner spinner, int spinnerId, int spinnerItemId, SpinnerItems<T> spinnerItems, final DropDownNavListener<T> listener) { if (spinner != null) { Context context = getContext(); // create custom adapter final SpinnerItemAdapter<T> dropDownNavAdapter = new SpinnerItemAdapter<T>(context, spinnerItems, spinnerId, spinnerItemId); // configure spinner spinner.setPadding(spinner.getPaddingLeft(), 0, spinner.getPaddingRight(), 0); spinner.setAdapter(dropDownNavAdapter); // we need this because otherwise the first item will be selected by // default and the OnItemSelectedListener won't get called spinner.setSelection(spinnerItems.getSelectedItem()); spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { private AtomicBoolean mFirstCall = new AtomicBoolean(true); @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if (!mFirstCall.getAndSet(false) && dropDownNavAdapter.getSelectedItem() != position) { listener.onItemSelected(dropDownNavAdapter.getItem(position), position); } dropDownNavAdapter.setSelectedItem(position); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); return dropDownNavAdapter; } return null; } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (mColorPickerListener != null && mPickerId != -1) { SetColorPickerListenerEvent.setListener(mPickerId, mColorPickerListener); } } // ****************************************** RTToolbar Methods ******************************************* @Override public void setToolbarContainer(ViewGroup toolbarContainer) { mToolbarContainer = toolbarContainer; } @Override public ViewGroup getToolbarContainer() { return mToolbarContainer == null ? this : mToolbarContainer; } @Override public void setToolbarListener(RTToolbarListener listener) { mListener = listener; } @Override public void removeToolbarListener() { mListener = null; } @Override public int getId() { return mId; } @Override public void setBold(boolean enabled) { if (mBold != null) mBold.setChecked(enabled); } @Override public void setItalic(boolean enabled) { if (mItalic != null) mItalic.setChecked(enabled); } @Override public void setUnderline(boolean enabled) { if (mUnderline != null) mUnderline.setChecked(enabled); } @Override public void setStrikethrough(boolean enabled) { if (mStrikethrough != null) mStrikethrough.setChecked(enabled); } @Override public void setSuperscript(boolean enabled) { if (mSuperscript != null) mSuperscript.setChecked(enabled); } @Override public void setSubscript(boolean enabled) { if (mSubscript != null) mSubscript.setChecked(enabled); } @Override public void setBullet(boolean enabled) { if (mBullet != null) mBullet.setChecked(enabled); } @Override public void setNumber(boolean enabled) { if (mNumber != null) mNumber.setChecked(enabled); } @Override public void setAlignment(Layout.Alignment alignment) { if (mAlignLeft != null) mAlignLeft.setChecked(alignment == Layout.Alignment.ALIGN_NORMAL); if (mAlignCenter != null) mAlignCenter.setChecked(alignment == Layout.Alignment.ALIGN_CENTER); if (mAlignRight != null) mAlignRight.setChecked(alignment == Layout.Alignment.ALIGN_OPPOSITE); } @Override public void setFont(RTTypeface typeface) { if (mFont != null) { if (typeface != null) { for (int pos = 0; pos < mFontAdapter.getCount(); pos++) { FontSpinnerItem item = mFontAdapter.getItem(pos); if (typeface.equals(item.getTypeface())) { mFontAdapter.setSelectedItem(pos); mFont.setSelection(pos); break; } } } else { mFontAdapter.setSelectedItem(0); mFont.setSelection(0); } } } /** * Set the text size. * * @param size the text size, if -1 then no text size is set (e.g. when selection spans more than one text size) */ @Override public void setFontSize(int size) { if (mFontSize != null) { if (size <= 0) { mFontSizeAdapter.updateSpinnerTitle(""); mFontSizeAdapter.setSelectedItem(0); mFontSize.setSelection(0); } else { size = Helper.convertSpToPx(size); mFontSizeAdapter.updateSpinnerTitle(Integer.toString(size)); for (int pos = 0; pos < mFontSizeAdapter.getCount(); pos++) { FontSizeSpinnerItem item = mFontSizeAdapter.getItem(pos); if (size == item.getFontSize()) { mFontSizeAdapter.setSelectedItem(pos); mFontSize.setSelection(pos); break; } } } } } @Override public void setFontColor(int color) { if (mFontColor != null) setFontColor(color, mFontColor, mFontColorAdapter); } @Override public void setBGColor(int color) { if (mBGColor != null) setFontColor(color, mBGColor, mBGColorAdapter); } @Override public void removeFontColor() { if (mFontColor != null) { mFontColorAdapter.setSelectedItem(0); mFontColor.setSelection(0); } } @Override public void removeBGColor() { if (mBGColor != null) { mBGColorAdapter.setSelectedItem(0); mBGColor.setSelection(0); } } private void setFontColor(int color, Spinner spinner, SpinnerItemAdapter<? extends ColorSpinnerItem> adapter) { int color2Compare = color & 0xffffff; for (int pos = 0; pos < adapter.getCount(); pos++) { ColorSpinnerItem item = adapter.getItem(pos); if (!item.isEmpty() && color2Compare == (item.getColor() & 0xffffff)) { adapter.setSelectedItem(pos); spinner.setSelection(pos); break; } } } // ****************************************** Item Selected Methods ******************************************* interface DropDownNavListener<T extends SpinnerItem> { void onItemSelected(T spinnerItem, int position); } private DropDownNavListener<FontSpinnerItem> mFontListener = new DropDownNavListener<FontSpinnerItem>() { @Override public void onItemSelected(FontSpinnerItem spinnerItem, int position) { RTTypeface typeface = spinnerItem.getTypeface(); mListener.onEffectSelected(Effects.TYPEFACE, typeface); } }; private DropDownNavListener<FontSizeSpinnerItem> mFontSizeListener = new DropDownNavListener<FontSizeSpinnerItem>() { @Override public void onItemSelected(FontSizeSpinnerItem spinnerItem, int position) { int size = spinnerItem.getFontSize(); mFontSizeAdapter.updateSpinnerTitle(spinnerItem.isEmpty() ? "" : Integer.toString(size)); size = Helper.convertPxToSp(size); mListener.onEffectSelected(Effects.FONTSIZE, size); } }; private DropDownNavListener<FontColorSpinnerItem> mFontColorListener = new DropDownNavListener<FontColorSpinnerItem>() { @Override public void onItemSelected(final FontColorSpinnerItem spinnerItem, int position) { if (spinnerItem.isCustom()) { mColorPickerListener = new ColorPickerListener() { @Override public void onColorChanged(int color) { mCustomColorFont = color; spinnerItem.setColor(color); mFontColorAdapter.notifyDataSetChanged(); if (mListener != null) { mListener.onEffectSelected(Effects.FONTCOLOR, color); } } @Override public void onDialogClosing() { mPickerId = -1; } }; mPickerId = new ColorPickerDialog(getContext(), mCustomColorFont, false).show(); SetColorPickerListenerEvent.setListener(mPickerId, mColorPickerListener); } else if (mListener != null) { Integer color = spinnerItem.isEmpty() ? null : spinnerItem.getColor(); mListener.onEffectSelected(Effects.FONTCOLOR, color); } } }; private DropDownNavListener<BGColorSpinnerItem> mBGColorListener = new DropDownNavListener<BGColorSpinnerItem>() { @Override public void onItemSelected(final BGColorSpinnerItem spinnerItem, int position) { if (spinnerItem.isCustom()) { mColorPickerListener = new ColorPickerListener() { @Override public void onColorChanged(int color) { mCustomColorBG = color; spinnerItem.setColor(color); mBGColorAdapter.notifyDataSetChanged(); if (mListener != null) { mListener.onEffectSelected(Effects.BGCOLOR, color); } } public void onDialogClosing() { mPickerId = -1; } }; mPickerId = new ColorPickerDialog(getContext(), mCustomColorBG, false).show(); SetColorPickerListenerEvent.setListener(mPickerId, mColorPickerListener); } else if (mListener != null) { Integer color = spinnerItem.isEmpty() ? null : spinnerItem.getColor(); mListener.onEffectSelected(Effects.BGCOLOR, color); } } }; @Override public void onClick(View v) { if (mListener != null) { int id = v.getId(); if (id == R.id.toolbar_bold) { mBold.setChecked(!mBold.isChecked()); mListener.onEffectSelected(Effects.BOLD, mBold.isChecked()); } else if (id == R.id.toolbar_italic) { mItalic.setChecked(!mItalic.isChecked()); mListener.onEffectSelected(Effects.ITALIC, mItalic.isChecked()); } else if (id == R.id.toolbar_underline) { mUnderline.setChecked(!mUnderline.isChecked()); mListener.onEffectSelected(Effects.UNDERLINE, mUnderline.isChecked()); } else if (id == R.id.toolbar_strikethrough) { mStrikethrough.setChecked(!mStrikethrough.isChecked()); mListener.onEffectSelected(Effects.STRIKETHROUGH, mStrikethrough.isChecked()); } else if (id == R.id.toolbar_superscript) { mSuperscript.setChecked(!mSuperscript.isChecked()); mListener.onEffectSelected(Effects.SUPERSCRIPT, mSuperscript.isChecked()); if (mSuperscript.isChecked() && mSubscript != null) { mSubscript.setChecked(false); mListener.onEffectSelected(Effects.SUBSCRIPT, mSubscript.isChecked()); } } else if (id == R.id.toolbar_subscript) { mSubscript.setChecked(!mSubscript.isChecked()); mListener.onEffectSelected(Effects.SUBSCRIPT, mSubscript.isChecked()); if (mSubscript.isChecked() && mSuperscript != null) { mSuperscript.setChecked(false); mListener.onEffectSelected(Effects.SUPERSCRIPT, mSuperscript.isChecked()); } } else if (id == R.id.toolbar_align_left) { if (mAlignLeft != null) mAlignLeft.setChecked(true); if (mAlignCenter != null) mAlignCenter.setChecked(false); if (mAlignRight != null) mAlignRight.setChecked(false); mListener.onEffectSelected(Effects.ALIGNMENT, Layout.Alignment.ALIGN_NORMAL); } else if (id == R.id.toolbar_align_center) { if (mAlignLeft != null) mAlignLeft.setChecked(false); if (mAlignCenter != null) mAlignCenter.setChecked(true); if (mAlignRight != null) mAlignRight.setChecked(false); mListener.onEffectSelected(Effects.ALIGNMENT, Layout.Alignment.ALIGN_CENTER); } else if (id == R.id.toolbar_align_right) { if (mAlignLeft != null) mAlignLeft.setChecked(false); if (mAlignCenter != null) mAlignCenter.setChecked(false); if (mAlignRight != null) mAlignRight.setChecked(true); mListener.onEffectSelected(Effects.ALIGNMENT, Layout.Alignment.ALIGN_OPPOSITE); } else if (id == R.id.toolbar_bullet) { mBullet.setChecked(!mBullet.isChecked()); boolean isChecked = mBullet.isChecked(); mListener.onEffectSelected(Effects.BULLET, isChecked); if (isChecked && mNumber != null) { mNumber.setChecked(false); // numbers will be removed by the NumberEffect.applyToSelection } } else if (id == R.id.toolbar_number) { mNumber.setChecked(!mNumber.isChecked()); boolean isChecked = mNumber.isChecked(); mListener.onEffectSelected(Effects.NUMBER, isChecked); if (isChecked && mBullet != null) { mBullet.setChecked(false); // bullets will be removed by the BulletEffect.applyToSelection } } else if (id == R.id.toolbar_inc_indent) { mListener.onEffectSelected(Effects.INDENTATION, Helper.getLeadingMarging()); } else if (id == R.id.toolbar_dec_indent) { mListener.onEffectSelected(Effects.INDENTATION, -Helper.getLeadingMarging()); } else if (id == R.id.toolbar_link) { mListener.onCreateLink(); } else if (id == R.id.toolbar_image) { mListener.onPickImage(); } else if (id == R.id.toolbar_image_capture) { mListener.onCaptureImage(); } else if (id == R.id.toolbar_clear) { mListener.onClearFormatting(); } else if (id == R.id.toolbar_undo) { mListener.onUndo(); } else if (id == R.id.toolbar_redo) { mListener.onRedo(); } } } }