Ver Fonte

Refactor

cooperku_kdanmobile há 5 anos atrás
pai
commit
b6c0ca23a1

+ 11 - 287
src/main/java/com/kdanmobile/reader/ReaderViewModel.kt

@@ -5,23 +5,10 @@ import android.arch.lifecycle.MutableLiveData
 import android.arch.lifecycle.ViewModel
 import android.content.Context
 import android.graphics.Color
-import android.graphics.PointF
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.StateListDrawable
 import android.net.Uri
-import android.os.Build
 import android.support.v4.content.ContextCompat
-import android.support.v7.widget.MyRoundRectDrawableWithShadow
-import android.view.Gravity
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.LinearLayout
-import android.widget.PopupWindow
-import android.widget.TextView
 import com.kdanmobile.kmpdfkit.annotation.Annotation
 import com.kdanmobile.kmpdfkit.annotation.bean.*
-import com.kdanmobile.kmpdfkit.annotation.freeText.view.KMPDFFreeTextEditView
 import com.kdanmobile.kmpdfkit.globaldata.AnnotConfig
 import com.kdanmobile.kmpdfkit.globaldata.Config
 import com.kdanmobile.kmpdfkit.globaldata.KMPDFAnnotEditMode
@@ -29,18 +16,17 @@ import com.kdanmobile.kmpdfkit.manager.KMPDFFactory
 import com.kdanmobile.kmpdfkit.manager.controller.*
 import com.kdanmobile.kmpdfkit.manager.listener.KMPDFAddAnnotCallback
 import com.kdanmobile.kmpdfkit.manager.listener.KMPDFAnnotEditModeChangeListener
-import com.kdanmobile.kmpdfkit.manager.listener.KMPDFContextMenuCallback
 import com.kdanmobile.kmpdfkit.pdfcommon.*
 import com.kdanmobile.reader.annotationattribute.AnnotationAttribute
 import com.kdanmobile.reader.annotationattribute.Brush
 import com.kdanmobile.reader.annotationattribute.InkAttribute
+import com.kdanmobile.reader.screen.contextmenu.MyKMPDFContextMenuCallback
 import com.kdanmobile.reader.screen.contextmenu.TextBoxContextMenuActionListener
 import com.kdanmobile.reader.screen.data.ShapeAttribute
 import com.kdanmobile.reader.screen.data.SignatureAttribute
 import com.kdanmobile.reader.screen.data.StampAttribute
 import com.kdanmobile.reader.screen.data.TextBoxAttribute
 import com.kdanmobile.reader.screen.handler.*
-import com.kdanmobile.reader.utils.DensityUtil
 import java.io.File
 import java.util.*
 import kotlin.collections.ArrayList
@@ -186,6 +172,14 @@ class ReaderViewModel(private val readerModelManager: ReaderModelManager, val ur
         SUCCESS, FAILED, PASSWORD_PROTECTED
     }
 
+    private val contextMenuCallback by lazy {
+        object : MyKMPDFContextMenuCallback(kmpdfFactory) {
+            override fun onLongPress() {
+                annotationEitModeLiveData.postValue(ReaderViewModel.AnnotationEitMode.FREETEXT_MODIFY)
+            }
+        }
+    }
+
     @JvmOverloads
     fun openPdfFile(context: Context, password: String, onRequestPassword: Runnable, type: String? = null): OpenFileResult {
         if (!isVerified) {
@@ -454,281 +448,11 @@ class ReaderViewModel(private val readerModelManager: ReaderModelManager, val ur
 
     private fun setupContextMenu() {
         val controller = kmpdfFactory?.getController(KMPDFFactory.ControllerType.CONTEXT_MENU) as KMPDFContextMenuController?
-        controller?.contextMenuCallback = object : KMPDFContextMenuCallback {
-
-            private var popupWindow: PopupWindow? = null
-
-            private fun dismissPopupWindow() {
-                popupWindow?.dismiss()
-                popupWindow = null
-            }
-
-            override fun onDismissContextMenu(type: KMPDFContextMenuCallback.ContextMenuType?, view: View?) {
-                dismissPopupWindow()
-            }
-
-            override fun onShowContextMenu(type: KMPDFContextMenuCallback.ContextMenuType?, view: View?) {
-                if (null != view && null == popupWindow) {
-
-                    //  create context menu depended on ContextMenuType
-                    val context = view.context!!
-                    val contentView = when (type) {
-                        KMPDFContextMenuCallback.ContextMenuType.Markup -> createMarkupContextMenuView(context)
-                        KMPDFContextMenuCallback.ContextMenuType.INK -> createInkContextMenuView(context)
-                        KMPDFContextMenuCallback.ContextMenuType.SelectText -> createSelectTextContextMenuView(context)
-                        KMPDFContextMenuCallback.ContextMenuType.Shape -> createShapeContextMenuView(context)
-                        KMPDFContextMenuCallback.ContextMenuType.Freetext -> createTextBoxContextMenuView(context)
-                        KMPDFContextMenuCallback.ContextMenuType.Signature -> createSignatureContextMenuView(context)
-                        KMPDFContextMenuCallback.ContextMenuType.Stamp -> createStampContextMenuView(context)
-                        KMPDFContextMenuCallback.ContextMenuType.LongPress -> createLongPressContextMenuView(context)
-                        else -> LinearLayout(context)
-                    }
-
-                    //  LongPress on TextBox
-                    if (type == KMPDFContextMenuCallback.ContextMenuType.Freetext) {
-                        if (view is KMPDFFreeTextEditView) {
-                            view.setOnLoogPressListener {
-                                dismissPopupWindow()
-                            }
-                        }
-                    }
-
-                    //  get the size of context menu
-                    contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
-                    val windowWidth = contentView.measuredWidth
-                    val windowHeight = contentView.measuredHeight
-                    popupWindow = PopupWindow(contentView, LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, true)
-
-                    //  get position y offset and margin of context menu
-                    val margin = context.resources.getDimensionPixelSize(R.dimen.reader_context_menu_window_position_margin)
-                    val offset = context.resources.getDimensionPixelSize(when (type) {
-                        KMPDFContextMenuCallback.ContextMenuType.Markup,
-                        KMPDFContextMenuCallback.ContextMenuType.INK -> R.dimen.reader_context_menu_window_position_offset_markup
-                        KMPDFContextMenuCallback.ContextMenuType.SelectText -> R.dimen.reader_context_menu_window_position_offset_select_text
-                        KMPDFContextMenuCallback.ContextMenuType.Shape,
-                        KMPDFContextMenuCallback.ContextMenuType.Freetext,
-                        KMPDFContextMenuCallback.ContextMenuType.Signature,
-                        KMPDFContextMenuCallback.ContextMenuType.Stamp -> R.dimen.reader_context_menu_window_position_offset_annotation
-                        else -> R.dimen.reader_context_menu_window_position_offset
-                    })
-
-                    //  get location of view
-                    val anchorLoc = arrayOf(0, 0).toIntArray()
-                    view.getLocationOnScreen(anchorLoc)
-
-                    //  compute the position of context menu
-                    val screenHeight = DensityUtil.getScreenHeightPx(context)
-                    val screenWidth = DensityUtil.getScreenWidthPx(context)
-                    val topPositionY = anchorLoc[1] - windowHeight - offset
-                    val bottomPositionY = anchorLoc[1] + view.height + offset
-                    val leftPositionX = anchorLoc[0] - windowWidth - offset
-                    val rightPositionX = anchorLoc[0] + view.width + offset
-
-                    //  determine the final position of context menu
-                    val positionY = when {
-                        topPositionY - margin > 0 -> topPositionY                                                               //  top
-                        screenHeight - windowHeight - bottomPositionY - margin > 0 -> bottomPositionY                           //  bottom
-                        else -> (topPositionY + bottomPositionY) / 2                                                            //  center
-                    }
-                    val positionX = when {
-                        positionY == topPositionY || positionY == bottomPositionY -> (leftPositionX + rightPositionX) / 2       //  center
-                        leftPositionX - margin > 0 -> leftPositionX                                                             //  left
-                        screenWidth - windowWidth - rightPositionX - margin > 0 -> rightPositionX                               //  right
-                        else -> (leftPositionX + rightPositionX) / 2                                                            //  center
-                    }
-
-                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-                        popupWindow?.elevation = context.resources.getDimension(R.dimen.reader_context_menu_window_elevation)
-                    } else {
-                        setElevationBelowAndroidKitKat(context, contentView)
-                    }
-
-                    popupWindow?.isOutsideTouchable = false
-                    popupWindow?.isFocusable = false
-                    popupWindow?.showAtLocation(view, Gravity.LEFT or Gravity.TOP, positionX, positionY)
-                }
-            }
-
-            private fun setElevationBelowAndroidKitKat(context: Context, view: View) {
-                val elevation = context.resources.getDimension(R.dimen.reader_context_menu_window_elevation)
-                val drawable = MyRoundRectDrawableWithShadow(context.resources, Color.TRANSPARENT, 0f, elevation, elevation)
-                setViewBackground(view, drawable)
-            }
-
-            private fun setViewBackground(view: View, drawable: Drawable) {
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
-                    view.background = drawable
-                } else {
-                    view.setBackgroundDrawable(drawable)
-                }
-            }
-
-            private fun createTextBoxSelector(context: Context, topLeft: Boolean, topRight: Boolean, bottomRight: Boolean, bottomLeft: Boolean): Drawable {
-                val radius = context.resources.getDimension(R.dimen.reader_context_menu_text_box_radius)
-                val topLeftRadius = if (topLeft) PointF(radius, radius) else PointF()
-                val topRightRadius = if (topRight) PointF(radius, radius) else PointF()
-                val bottomRightRadius = if (bottomRight) PointF(radius, radius) else PointF()
-                val bottomLeftRadius = if (bottomLeft) PointF(radius, radius) else PointF()
-                val cornerRadii = floatArrayOf(topLeftRadius.x, topLeftRadius.y, topRightRadius.x, topRightRadius.y, bottomRightRadius.x, bottomRightRadius.y, bottomLeftRadius.x, bottomLeftRadius.y)
-
-                val normalDrawable = GradientDrawable()
-                normalDrawable.cornerRadii = cornerRadii
-                normalDrawable.setColor(ContextCompat.getColor(context, R.color.reader_contextMenu_bgNormal))
-                val pressedDrawable = GradientDrawable()
-                pressedDrawable.cornerRadii = cornerRadii
-                pressedDrawable.setColor(ContextCompat.getColor(context, R.color.reader_contextMenu_bgPressed))
-                val drawable = StateListDrawable()
-                drawable.addState(intArrayOf(android.R.attr.state_pressed), pressedDrawable)
-                drawable.addState(intArrayOf(android.R.attr.state_enabled), normalDrawable)
-                return drawable
-            }
-
-            private fun createMarkupContextMenuView(context: Context): View {
-                /** Just set HIGH_LIGHT then highlight, strike, underline will be triggered. **/
-                /** WTF !!!!!!!!!!! **/
-                val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
-                val contentView = layoutInflater.inflate(R.layout.view_context_menu_normal, null)
-                contentView.findViewById<TextView>(R.id.btnDelete_contextMenu).setOnClickListener {
-                    (kmpdfFactory?.getController(KMPDFFactory.ControllerType.MARKER_UP) as KMPDFMarkupController).deleteMarkupAnnotView()
-                    dismissPopupWindow()
-                }
-                return contentView
-            }
-
-            private fun createInkContextMenuView(context: Context): View {
-                val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
-                val contentView = layoutInflater.inflate(R.layout.view_context_menu_normal, null)
-                contentView.findViewById<TextView>(R.id.btnDelete_contextMenu).setOnClickListener {
-                    (kmpdfFactory?.getController(KMPDFFactory.ControllerType.INK) as KMPDFInkController).deleteInkAnnotView()
-                    dismissPopupWindow()
-                }
-                return contentView
-            }
-
-            private fun createSelectTextContextMenuView(context: Context): View {
-                val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
-                val contentView = layoutInflater.inflate(R.layout.view_context_menu_select_text, null)
-
-                val btnCopy = contentView.findViewById<TextView>(R.id.btnCopy_contextMenu)
-                val btnHighlight = contentView.findViewById<TextView>(R.id.btnHighlight_contextMenu)
-                val btnUnderline = contentView.findViewById<TextView>(R.id.btnUnderline_contextMenu)
-                val btnStrikeout = contentView.findViewById<TextView>(R.id.btnStrikeout_contextMenu)
-
-                setViewBackground(btnCopy, createTextBoxSelector(context, true, false, false, true))
-                setViewBackground(btnHighlight, createTextBoxSelector(context, false, false, false, false))
-                setViewBackground(btnUnderline, createTextBoxSelector(context, false, false, false, false))
-                setViewBackground(btnStrikeout, createTextBoxSelector(context, false, true, true, false))
-
-                btnCopy.setOnClickListener {
-                    (kmpdfFactory?.getController(KMPDFFactory.ControllerType.SELECT_TEXT) as KMPDFSelectTextController).copySelectedText()
-                    dismissPopupWindow()
-                }
-                btnHighlight.setOnClickListener {
-                    (kmpdfFactory?.getController(KMPDFFactory.ControllerType.SELECT_TEXT) as KMPDFSelectTextController).highlightSelectedText()
-                    dismissPopupWindow()
-                }
-                btnUnderline.setOnClickListener {
-                    (kmpdfFactory?.getController(KMPDFFactory.ControllerType.SELECT_TEXT) as KMPDFSelectTextController).underlineSelectedText()
-                    dismissPopupWindow()
-                }
-                btnStrikeout.setOnClickListener {
-                    (kmpdfFactory?.getController(KMPDFFactory.ControllerType.SELECT_TEXT) as KMPDFSelectTextController).strikeoutSelectedText()
-                    dismissPopupWindow()
-                }
-                return contentView
-            }
-
-            private fun createShapeContextMenuView(context: Context): View {
-                val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
-                val contentView = layoutInflater.inflate(R.layout.view_context_menu_normal, null)
-                contentView.findViewById<TextView>(R.id.btnDelete_contextMenu).setOnClickListener {
-                    (kmpdfFactory?.getController(KMPDFFactory.ControllerType.SHAPE) as KMPDFShapeAnnotController).deleteShapeAnnotView()
-                    dismissPopupWindow()
-                }
-                return contentView
-            }
-
-            private fun createTextBoxContextMenuView(context: Context): View {
-                val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
-                val contentView = layoutInflater.inflate(R.layout.view_context_menu_textbox, null)
-
-                val btnDelete = contentView.findViewById<TextView>(R.id.btnDelete_contextMenu)
-                val btnStyle = contentView.findViewById<TextView>(R.id.btnStyle_contextMenu)
-                val btnEdit = contentView.findViewById<TextView>(R.id.btnEdit_contextMenu)
-                val btnCopy = contentView.findViewById<TextView>(R.id.btnCopy_contextMenu)
-
-                setViewBackground(btnDelete, createTextBoxSelector(context, true, false, false, true))
-                setViewBackground(btnStyle, createTextBoxSelector(context, false, false, false, false))
-                setViewBackground(btnEdit, createTextBoxSelector(context, false, false, false, false))
-                setViewBackground(btnCopy, createTextBoxSelector(context, false, true, true, false))
-
-                btnDelete.setOnClickListener {
-                    textBoxContextMenuActionListener?.onDelete()
-                    dismissPopupWindow()
-                }
-                btnStyle.setOnClickListener {
-                    textBoxContextMenuActionListener?.onEditStyle()
-                    dismissPopupWindow()
-                }
-                btnEdit.setOnClickListener {
-                    textBoxContextMenuActionListener?.onEditText()
-                    dismissPopupWindow()
-                }
-                btnCopy.setOnClickListener {
-                    textBoxContextMenuActionListener?.onCopy()
-                    dismissPopupWindow()
-                }
-                return contentView
-            }
-
-            private fun createSignatureContextMenuView(context: Context): View {
-                val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
-                val contentView = layoutInflater.inflate(R.layout.view_context_menu_signature, null)
-
-                val btnDelete = contentView.findViewById<TextView>(R.id.btnDelete_contextMenu)
-                val btnAdd = contentView.findViewById<TextView>(R.id.btnAdd_contextMenu)
-
-                setViewBackground(btnDelete, createTextBoxSelector(context, true, false, false, true))
-                setViewBackground(btnAdd, createTextBoxSelector(context, false, true, true, false))
-
-                btnDelete.setOnClickListener {
-                    (kmpdfFactory?.getController(KMPDFFactory.ControllerType.SIGNATURE) as KMPDFSignatureController).deleteSignatureAnnotView()
-                    dismissPopupWindow()
-                }
-                btnAdd.setOnClickListener {
-                    (kmpdfFactory?.getController(KMPDFFactory.ControllerType.SIGNATURE) as KMPDFSignatureController).saveSignatureAnnot()
-                    dismissPopupWindow()
-                }
-                return contentView
-            }
-
-            private fun createStampContextMenuView(context: Context): View {
-                val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
-                val contentView = layoutInflater.inflate(R.layout.view_context_menu_normal, null)
-                contentView.findViewById<TextView>(R.id.btnDelete_contextMenu).setOnClickListener {
-                    (kmpdfFactory?.getController(KMPDFFactory.ControllerType.STAMP) as KMPDFStampController).deleteStampAnnotView()
-                    dismissPopupWindow()
-                }
-                return contentView
-            }
-
-            private fun createLongPressContextMenuView(context: Context): View {
-                val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
-                val contentView = layoutInflater.inflate(R.layout.view_context_menu_long_press, null)
-                contentView.findViewById<TextView>(R.id.btnPaste_contextMenu).setOnClickListener {
-                    (kmpdfFactory?.getController(KMPDFFactory.ControllerType.LONGCLICK) as KMPDFLongPressCreateAnnotController).longPress_Paste()
-                    annotationEitModeLiveData.postValue(AnnotationEitMode.FREETEXT_MODIFY)
-                    dismissPopupWindow()
-                }
-                return contentView
-            }
-        }
+        controller?.contextMenuCallback = contextMenuCallback
     }
 
-    private var textBoxContextMenuActionListener: TextBoxContextMenuActionListener? = null
-
     fun setTextBoxContextMenuActions(listener: TextBoxContextMenuActionListener) {
-        textBoxContextMenuActionListener = listener
+        contextMenuCallback.setTextBoxContextMenuActions(listener)
     }
 
     private fun restoreStateBeforeDestroy() {

+ 314 - 0
src/main/java/com/kdanmobile/reader/screen/contextmenu/MyKMPDFContextMenuCallback.kt

@@ -0,0 +1,314 @@
+package com.kdanmobile.reader.screen.contextmenu
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.StateListDrawable
+import android.os.Build
+import android.support.v4.content.ContextCompat
+import android.support.v7.widget.MyRoundRectDrawableWithShadow
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.LinearLayout
+import android.widget.PopupWindow
+import android.widget.TextView
+import com.kdanmobile.kmpdfkit.annotation.freeText.view.KMPDFFreeTextEditView
+import com.kdanmobile.kmpdfkit.manager.KMPDFFactory
+import com.kdanmobile.kmpdfkit.manager.controller.*
+import com.kdanmobile.kmpdfkit.manager.listener.KMPDFContextMenuCallback
+import com.kdanmobile.reader.R
+import com.kdanmobile.reader.utils.DensityUtil
+
+open class MyKMPDFContextMenuCallback(private val kmpdfFactory: KMPDFFactory?) : KMPDFContextMenuCallback {
+
+    private var popupWindow: PopupWindow? = null
+
+    private var textBoxContextMenuActionListener: TextBoxContextMenuActionListener? = null
+
+    open fun onLongPress() {}
+
+    override fun onDismissContextMenu(type: KMPDFContextMenuCallback.ContextMenuType?, view: View?) {
+        dismissPopupWindow()
+    }
+
+    override fun onShowContextMenu(type: KMPDFContextMenuCallback.ContextMenuType?, view: View?) {
+        if (null != view && null == popupWindow) {
+
+            //  create context menu depended on ContextMenuType
+            val context = view.context!!
+            val contentView = when (type) {
+                KMPDFContextMenuCallback.ContextMenuType.Markup -> createMarkupContextMenuView(context)
+                KMPDFContextMenuCallback.ContextMenuType.INK -> createInkContextMenuView(context)
+                KMPDFContextMenuCallback.ContextMenuType.SelectText -> createSelectTextContextMenuView(context)
+                KMPDFContextMenuCallback.ContextMenuType.Shape -> createShapeContextMenuView(context)
+                KMPDFContextMenuCallback.ContextMenuType.Freetext -> createTextBoxContextMenuView(context)
+                KMPDFContextMenuCallback.ContextMenuType.Signature -> createSignatureContextMenuView(context)
+                KMPDFContextMenuCallback.ContextMenuType.Stamp -> createStampContextMenuView(context)
+                KMPDFContextMenuCallback.ContextMenuType.LongPress -> createLongPressContextMenuView(context)
+                else -> LinearLayout(context)
+            }
+
+            //  LongPress on TextBox
+            if (type == KMPDFContextMenuCallback.ContextMenuType.Freetext) {
+                if (view is KMPDFFreeTextEditView) {
+                    view.setOnLoogPressListener {
+                        dismissPopupWindow()
+                    }
+                }
+            }
+
+            val position = computeDisplayPosition(context, type, view, contentView)
+
+            popupWindow = PopupWindow(contentView, LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, true)
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                popupWindow?.elevation = context.resources.getDimension(R.dimen.reader_context_menu_window_elevation)
+            } else {
+                setElevationBelowAndroidKitKat(context, contentView)
+            }
+            popupWindow?.isOutsideTouchable = false
+            popupWindow?.isFocusable = false
+            popupWindow?.showAtLocation(view, Gravity.LEFT or Gravity.TOP, position.x, position.y)
+        }
+    }
+
+    private fun computeDisplayPosition(context: Context, type: KMPDFContextMenuCallback.ContextMenuType?, view: View, contentView: View): Point {
+        //  get the size of context menu
+        contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
+        val windowWidth = contentView.measuredWidth
+        val windowHeight = contentView.measuredHeight
+
+        //  get position y offset and margin of context menu
+        val margin = context.resources.getDimensionPixelSize(R.dimen.reader_context_menu_window_position_margin)
+        val offset = context.resources.getDimensionPixelSize(when (type) {
+            KMPDFContextMenuCallback.ContextMenuType.Markup,
+            KMPDFContextMenuCallback.ContextMenuType.INK -> R.dimen.reader_context_menu_window_position_offset_markup
+            KMPDFContextMenuCallback.ContextMenuType.SelectText -> R.dimen.reader_context_menu_window_position_offset_select_text
+            KMPDFContextMenuCallback.ContextMenuType.Shape,
+            KMPDFContextMenuCallback.ContextMenuType.Freetext,
+            KMPDFContextMenuCallback.ContextMenuType.Signature,
+            KMPDFContextMenuCallback.ContextMenuType.Stamp -> R.dimen.reader_context_menu_window_position_offset_annotation
+            else -> R.dimen.reader_context_menu_window_position_offset
+        })
+
+        //  get location of view
+        val anchorLoc = arrayOf(0, 0).toIntArray()
+        view.getLocationOnScreen(anchorLoc)
+
+        //  compute the position of context menu
+        val screenHeight = DensityUtil.getScreenHeightPx(context)
+        val screenWidth = DensityUtil.getScreenWidthPx(context)
+        val topPositionY = anchorLoc[1] - windowHeight - offset
+        val bottomPositionY = anchorLoc[1] + view.height + offset
+        val leftPositionX = anchorLoc[0] - windowWidth - offset
+        val rightPositionX = anchorLoc[0] + view.width + offset
+
+        //  determine the final position of context menu
+        val positionY = when {
+            topPositionY - margin > 0 -> topPositionY                                                               //  top
+            screenHeight - windowHeight - bottomPositionY - margin > 0 -> bottomPositionY                           //  bottom
+            else -> (topPositionY + bottomPositionY) / 2                                                            //  center
+        }
+        val positionX = when {
+            positionY == topPositionY || positionY == bottomPositionY -> (leftPositionX + rightPositionX) / 2       //  center
+            leftPositionX - margin > 0 -> leftPositionX                                                             //  left
+            screenWidth - windowWidth - rightPositionX - margin > 0 -> rightPositionX                               //  right
+            else -> (leftPositionX + rightPositionX) / 2                                                            //  center
+        }
+
+        return Point(positionX, positionY)
+    }
+
+    private fun dismissPopupWindow() {
+        popupWindow?.dismiss()
+        popupWindow = null
+    }
+
+    private fun setElevationBelowAndroidKitKat(context: Context, view: View) {
+        val elevation = context.resources.getDimension(R.dimen.reader_context_menu_window_elevation)
+        val drawable = MyRoundRectDrawableWithShadow(context.resources, Color.TRANSPARENT, 0f, elevation, elevation)
+        setViewBackground(view, drawable)
+    }
+
+    private fun setViewBackground(view: View, drawable: Drawable) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            view.background = drawable
+        } else {
+            view.setBackgroundDrawable(drawable)
+        }
+    }
+
+    private fun createTextBoxSelector(context: Context, topLeft: Boolean, topRight: Boolean, bottomRight: Boolean, bottomLeft: Boolean): Drawable {
+        val radius = context.resources.getDimension(R.dimen.reader_context_menu_text_box_radius)
+        val topLeftRadius = if (topLeft) PointF(radius, radius) else PointF()
+        val topRightRadius = if (topRight) PointF(radius, radius) else PointF()
+        val bottomRightRadius = if (bottomRight) PointF(radius, radius) else PointF()
+        val bottomLeftRadius = if (bottomLeft) PointF(radius, radius) else PointF()
+        val cornerRadii = floatArrayOf(topLeftRadius.x, topLeftRadius.y, topRightRadius.x, topRightRadius.y, bottomRightRadius.x, bottomRightRadius.y, bottomLeftRadius.x, bottomLeftRadius.y)
+
+        val normalDrawable = GradientDrawable()
+        normalDrawable.cornerRadii = cornerRadii
+        normalDrawable.setColor(ContextCompat.getColor(context, R.color.reader_contextMenu_bgNormal))
+        val pressedDrawable = GradientDrawable()
+        pressedDrawable.cornerRadii = cornerRadii
+        pressedDrawable.setColor(ContextCompat.getColor(context, R.color.reader_contextMenu_bgPressed))
+        val drawable = StateListDrawable()
+        drawable.addState(intArrayOf(android.R.attr.state_pressed), pressedDrawable)
+        drawable.addState(intArrayOf(android.R.attr.state_enabled), normalDrawable)
+        return drawable
+    }
+
+    private fun createMarkupContextMenuView(context: Context): View {
+        /** Just set HIGH_LIGHT then highlight, strike, underline will be triggered. **/
+        /** WTF !!!!!!!!!!! **/
+        val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+        val contentView = layoutInflater.inflate(R.layout.view_context_menu_normal, null)
+        contentView.findViewById<TextView>(R.id.btnDelete_contextMenu).setOnClickListener {
+            val kmpdfMarkupController = kmpdfFactory?.getController(KMPDFFactory.ControllerType.MARKER_UP) as KMPDFMarkupController?
+            kmpdfMarkupController?.deleteMarkupAnnotView()
+            dismissPopupWindow()
+        }
+        return contentView
+    }
+
+    private fun createInkContextMenuView(context: Context): View {
+        val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+        val contentView = layoutInflater.inflate(R.layout.view_context_menu_normal, null)
+        contentView.findViewById<TextView>(R.id.btnDelete_contextMenu).setOnClickListener {
+            val kmpdfInkController = kmpdfFactory?.getController(KMPDFFactory.ControllerType.INK) as KMPDFInkController?
+            kmpdfInkController?.deleteInkAnnotView()
+            dismissPopupWindow()
+        }
+        return contentView
+    }
+
+    private fun createSelectTextContextMenuView(context: Context): View {
+        val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+        val contentView = layoutInflater.inflate(R.layout.view_context_menu_select_text, null)
+
+        val btnCopy = contentView.findViewById<TextView>(R.id.btnCopy_contextMenu)
+        val btnHighlight = contentView.findViewById<TextView>(R.id.btnHighlight_contextMenu)
+        val btnUnderline = contentView.findViewById<TextView>(R.id.btnUnderline_contextMenu)
+        val btnStrikeout = contentView.findViewById<TextView>(R.id.btnStrikeout_contextMenu)
+
+        setViewBackground(btnCopy, createTextBoxSelector(context, true, false, false, true))
+        setViewBackground(btnHighlight, createTextBoxSelector(context, false, false, false, false))
+        setViewBackground(btnUnderline, createTextBoxSelector(context, false, false, false, false))
+        setViewBackground(btnStrikeout, createTextBoxSelector(context, false, true, true, false))
+
+        val kmpdfSelectTextController = kmpdfFactory?.getController(KMPDFFactory.ControllerType.SELECT_TEXT) as KMPDFSelectTextController?
+        btnCopy.setOnClickListener {
+            kmpdfSelectTextController?.copySelectedText()
+            dismissPopupWindow()
+        }
+        btnHighlight.setOnClickListener {
+            kmpdfSelectTextController?.highlightSelectedText()
+            dismissPopupWindow()
+        }
+        btnUnderline.setOnClickListener {
+            kmpdfSelectTextController?.underlineSelectedText()
+            dismissPopupWindow()
+        }
+        btnStrikeout.setOnClickListener {
+            kmpdfSelectTextController?.strikeoutSelectedText()
+            dismissPopupWindow()
+        }
+        return contentView
+    }
+
+    private fun createShapeContextMenuView(context: Context): View {
+        val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+        val contentView = layoutInflater.inflate(R.layout.view_context_menu_normal, null)
+        contentView.findViewById<TextView>(R.id.btnDelete_contextMenu).setOnClickListener {
+            val kmpdfShapeAnnotController = kmpdfFactory?.getController(KMPDFFactory.ControllerType.SHAPE) as KMPDFShapeAnnotController?
+            kmpdfShapeAnnotController?.deleteShapeAnnotView()
+            dismissPopupWindow()
+        }
+        return contentView
+    }
+
+    private fun createTextBoxContextMenuView(context: Context): View {
+        val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+        val contentView = layoutInflater.inflate(R.layout.view_context_menu_textbox, null)
+
+        val btnDelete = contentView.findViewById<TextView>(R.id.btnDelete_contextMenu)
+        val btnStyle = contentView.findViewById<TextView>(R.id.btnStyle_contextMenu)
+        val btnEdit = contentView.findViewById<TextView>(R.id.btnEdit_contextMenu)
+        val btnCopy = contentView.findViewById<TextView>(R.id.btnCopy_contextMenu)
+
+        setViewBackground(btnDelete, createTextBoxSelector(context, true, false, false, true))
+        setViewBackground(btnStyle, createTextBoxSelector(context, false, false, false, false))
+        setViewBackground(btnEdit, createTextBoxSelector(context, false, false, false, false))
+        setViewBackground(btnCopy, createTextBoxSelector(context, false, true, true, false))
+
+        btnDelete.setOnClickListener {
+            textBoxContextMenuActionListener?.onDelete()
+            dismissPopupWindow()
+        }
+        btnStyle.setOnClickListener {
+            textBoxContextMenuActionListener?.onEditStyle()
+            dismissPopupWindow()
+        }
+        btnEdit.setOnClickListener {
+            textBoxContextMenuActionListener?.onEditText()
+            dismissPopupWindow()
+        }
+        btnCopy.setOnClickListener {
+            textBoxContextMenuActionListener?.onCopy()
+            dismissPopupWindow()
+        }
+        return contentView
+    }
+
+    private fun createSignatureContextMenuView(context: Context): View {
+        val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+        val contentView = layoutInflater.inflate(R.layout.view_context_menu_signature, null)
+
+        val btnDelete = contentView.findViewById<TextView>(R.id.btnDelete_contextMenu)
+        val btnAdd = contentView.findViewById<TextView>(R.id.btnAdd_contextMenu)
+
+        setViewBackground(btnDelete, createTextBoxSelector(context, true, false, false, true))
+        setViewBackground(btnAdd, createTextBoxSelector(context, false, true, true, false))
+
+        val kmpdfSignatureController = kmpdfFactory?.getController(KMPDFFactory.ControllerType.SIGNATURE) as KMPDFSignatureController?
+        btnDelete.setOnClickListener {
+            kmpdfSignatureController?.deleteSignatureAnnotView()
+            dismissPopupWindow()
+        }
+        btnAdd.setOnClickListener {
+            kmpdfSignatureController?.saveSignatureAnnot()
+            dismissPopupWindow()
+        }
+        return contentView
+    }
+
+    private fun createStampContextMenuView(context: Context): View {
+        val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+        val contentView = layoutInflater.inflate(R.layout.view_context_menu_normal, null)
+        contentView.findViewById<TextView>(R.id.btnDelete_contextMenu).setOnClickListener {
+            val kmpdfStampController = kmpdfFactory?.getController(KMPDFFactory.ControllerType.STAMP) as KMPDFStampController?
+            kmpdfStampController?.deleteStampAnnotView()
+            dismissPopupWindow()
+        }
+        return contentView
+    }
+
+    private fun createLongPressContextMenuView(context: Context): View {
+        val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+        val contentView = layoutInflater.inflate(R.layout.view_context_menu_long_press, null)
+        contentView.findViewById<TextView>(R.id.btnPaste_contextMenu).setOnClickListener {
+            val kmpdfLongPressCreateAnnotController = kmpdfFactory?.getController(KMPDFFactory.ControllerType.LONGCLICK) as KMPDFLongPressCreateAnnotController?
+            kmpdfLongPressCreateAnnotController?.longPress_Paste()
+            onLongPress()
+            dismissPopupWindow()
+        }
+        return contentView
+    }
+
+    fun setTextBoxContextMenuActions(listener: TextBoxContextMenuActionListener) {
+        textBoxContextMenuActionListener = listener
+    }
+}