Improve: AutoComplete PopupWindow will not cover the IME.

This commit is contained in:
zt515 2017-07-22 23:42:31 +08:00
parent 57e5b99ed9
commit 89d4140a71
14 changed files with 224 additions and 37 deletions

View File

@ -17,14 +17,14 @@ android {
applicationId "io.neoterm" applicationId "io.neoterm"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 25 targetSdkVersion 25
versionCode 14 versionCode 15
versionName "1.2.0-rc3" versionName "1.2.0-rc4"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
resConfigs "zh" resConfigs "zh-rCN", "zh-rTW"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
cppFlags "-std=c++11" cppFlags "-std=c++11"
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' abiFilters 'x86', 'x86_64', 'arm64-v8a'
} }
} }
sourceSets { sourceSets {

View File

@ -2,7 +2,7 @@ package io.neoterm
import android.app.Application import android.app.Application
import io.neoterm.customize.color.ColorSchemeManager import io.neoterm.customize.color.ColorSchemeManager
import io.neoterm.customize.completion.AutoCompletionManager import io.neoterm.customize.completion.CompletionProviderManager
import io.neoterm.customize.font.FontManager import io.neoterm.customize.font.FontManager
import io.neoterm.customize.script.UserScriptManager import io.neoterm.customize.script.UserScriptManager
import io.neoterm.preference.NeoPreference import io.neoterm.preference.NeoPreference
@ -22,7 +22,7 @@ class App : Application() {
ColorSchemeManager.init(this) ColorSchemeManager.init(this)
FontManager.init(this) FontManager.init(this)
UserScriptManager.init(this) UserScriptManager.init(this)
AutoCompletionManager.init(this) CompletionProviderManager.init(this)
} }
companion object { companion object {

View File

@ -7,7 +7,7 @@ import io.neoterm.frontend.completion.CompletionManager
/** /**
* @author kiva * @author kiva
*/ */
object AutoCompletionManager { object CompletionProviderManager {
fun init(context: Context) { fun init(context: Context) {
CompletionManager.registerProvider(PathProvider()) CompletionManager.registerProvider(PathProvider())
} }

View File

@ -1,12 +1,9 @@
package io.neoterm.frontend.client package io.neoterm.frontend.client
import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import io.neoterm.BuildConfig import io.neoterm.frontend.completion.widget.CandidatePopupWindow
import io.neoterm.customize.completion.widget.CandidatePopupWindow
import io.neoterm.frontend.completion.CompletionManager import io.neoterm.frontend.completion.CompletionManager
import io.neoterm.frontend.completion.listener.OnAutoCompleteListener import io.neoterm.frontend.completion.listener.OnAutoCompleteListener
import io.neoterm.frontend.completion.model.CompletionCandidate
import io.neoterm.frontend.completion.model.CompletionResult import io.neoterm.frontend.completion.model.CompletionResult
import io.neoterm.view.TerminalView import io.neoterm.view.TerminalView
import java.util.* import java.util.*
@ -17,20 +14,18 @@ import java.util.*
class TermCompleteListener(var terminalView: TerminalView?) : OnAutoCompleteListener { class TermCompleteListener(var terminalView: TerminalView?) : OnAutoCompleteListener {
private val inputStack = Stack<Char>() private val inputStack = Stack<Char>()
private val popupWindow = CandidatePopupWindow(terminalView!!.context) private var popupWindow: CandidatePopupWindow? = null
override fun onKeyCode(keyCode: Int, keyMod: Int) { override fun onKeyCode(keyCode: Int, keyMod: Int) {
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_DEL -> { KeyEvent.KEYCODE_DEL -> {
Log.e("NeoTerm-AC", "BackSpace")
popChar() popChar()
activateAutoCompletion() activateAutoCompletion()
} }
KeyEvent.KEYCODE_ENTER -> { KeyEvent.KEYCODE_ENTER -> {
Log.e("NeoTerm-AC", "Clear Chars")
clearChars() clearChars()
popupWindow.dismiss() popupWindow?.dismiss()
} }
} }
} }
@ -44,10 +39,22 @@ class TermCompleteListener(var terminalView: TerminalView?) : OnAutoCompleteList
} }
override fun onCleanUp() { override fun onCleanUp() {
popupWindow.cleanup() popupWindow?.dismiss()
popupWindow?.cleanup()
popupWindow = null
terminalView = null terminalView = null
} }
override fun onFinishCompletion(): Boolean {
val popWindow = popupWindow ?: return false
if (popWindow.isShowing()) {
popWindow.dismiss()
return true
}
return false
}
private fun activateAutoCompletion() { private fun activateAutoCompletion() {
val text = getCurrentEditingText() val text = getCurrentEditingText()
if (text.isEmpty()) { if (text.isEmpty()) {
@ -62,20 +69,24 @@ class TermCompleteListener(var terminalView: TerminalView?) : OnAutoCompleteList
result.markScore(0) result.markScore(0)
return return
} }
if (BuildConfig.DEBUG) {
val candidates = result.candidates
Log.e("NeoTerm-AC", "Completing for $text")
candidates.forEach {
Log.e("NeoTerm-AC", " Candidate: ${it.completeString}")
}
}
showAutoCompleteCandidates(result) showAutoCompleteCandidates(result)
} }
private fun showAutoCompleteCandidates(result: CompletionResult) { private fun showAutoCompleteCandidates(result: CompletionResult) {
popupWindow.candidates = result.candidates val termView = terminalView
popupWindow.show(terminalView!!) var popWindow = popupWindow
if (termView == null) {
return
}
if (popWindow == null) {
popWindow = CandidatePopupWindow(termView.context)
this.popupWindow = popWindow
}
popWindow.candidates = result.candidates
popWindow.show(termView)
} }
private fun getCurrentEditingText(): String { private fun getCurrentEditingText(): String {

View File

@ -9,4 +9,6 @@ interface TermUiPresenter {
fun requirePaste() fun requirePaste()
fun requireUpdateTitle(title: String?) fun requireUpdateTitle(title: String?)
fun requireOnSessionFinished() fun requireOnSessionFinished()
fun requireHideIme()
fun requireFinishAutoCompletion(): Boolean
} }

View File

@ -2,6 +2,7 @@ package io.neoterm.frontend.client
import android.content.Context import android.content.Context
import android.media.AudioManager import android.media.AudioManager
import android.util.Log
import android.view.InputDevice import android.view.InputDevice
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
@ -62,6 +63,12 @@ class TermViewClient(val context: Context) : TerminalViewClient {
} }
return false return false
} }
KeyEvent.KEYCODE_BACK -> {
if (e?.action == KeyEvent.ACTION_DOWN) {
return termUI?.requireFinishAutoCompletion() ?: false
}
return false
}
} }
if (e != null && e.isCtrlPressed && e.isAltPressed) { if (e != null && e.isCtrlPressed && e.isAltPressed) {
// Get the unmodified code point: // Get the unmodified code point:

View File

@ -11,4 +11,6 @@ interface OnAutoCompleteListener {
fun onKeyCode(keyCode: Int, keyMod: Int) fun onKeyCode(keyCode: Int, keyMod: Int)
fun onCleanUp() fun onCleanUp()
fun onFinishCompletion(): Boolean
} }

View File

@ -1,18 +1,17 @@
package io.neoterm.customize.completion.widget package io.neoterm.frontend.completion.widget
import android.content.Context import android.content.Context
import android.util.Log
import android.view.Gravity import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.BaseAdapter import android.widget.*
import android.widget.ListView
import android.widget.PopupWindow
import android.widget.TextView
import io.neoterm.R import io.neoterm.R
import io.neoterm.backend.TerminalColors import io.neoterm.backend.TerminalColors
import io.neoterm.customize.color.ColorSchemeManager import io.neoterm.customize.color.ColorSchemeManager
import io.neoterm.frontend.completion.model.CompletionCandidate import io.neoterm.frontend.completion.model.CompletionCandidate
import io.neoterm.view.MaxHeightView
import io.neoterm.view.TerminalView import io.neoterm.view.TerminalView
/** /**
@ -30,8 +29,17 @@ class CandidatePopupWindow(val context: Context) {
} }
candidateAdapter?.notifyDataSetChanged() candidateAdapter?.notifyDataSetChanged()
if (!(popupWindow?.isShowing ?: false)) {
popupWindow?.showAtLocation(terminalView, Gravity.BOTTOM.and(Gravity.START), val popWindow = popupWindow
if (popWindow != null) {
// Ensure that the popup window will not cover the IME.
val rootView = popWindow.contentView
if (rootView is MaxHeightView) {
val maxHeight = terminalView.height
rootView.setMaxHeight(maxHeight)
}
popWindow.showAtLocation(terminalView, Gravity.BOTTOM.and(Gravity.START),
terminalView.cursorAbsX, terminalView.cursorAbsX,
terminalView.cursorAbsY) terminalView.cursorAbsY)
} }
@ -41,6 +49,10 @@ class CandidatePopupWindow(val context: Context) {
popupWindow?.dismiss() popupWindow?.dismiss()
} }
fun isShowing(): Boolean {
return popupWindow?.isShowing ?: false
}
private fun createPopupWindow(): PopupWindow { private fun createPopupWindow(): PopupWindow {
val popupWindow = PopupWindow(context) val popupWindow = PopupWindow(context)
popupWindow.isOutsideTouchable = true popupWindow.isOutsideTouchable = true

View File

@ -226,7 +226,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
.takeWhile { it is TermTab } .takeWhile { it is TermTab }
.forEach { .forEach {
val termTab = it as TermTab val termTab = it as TermTab
// After stopped, window locatinos may changed // After stopped, window locations may changed
// Rebind it at next time. // Rebind it at next time.
termTab.resetAutoCompleteStatus() termTab.resetAutoCompleteStatus()
} }

View File

@ -37,7 +37,7 @@ class TermTab(title: CharSequence) : Tab(title), TermUiPresenter {
resetAutoCompleteStatus() resetAutoCompleteStatus()
} }
fun requireHideIme() { override fun requireHideIme() {
val terminalView = termData.termView val terminalView = termData.termView
if (terminalView != null) { if (terminalView != null) {
val imm = terminalView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager val imm = terminalView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
@ -47,6 +47,10 @@ class TermTab(title: CharSequence) : Tab(title), TermUiPresenter {
} }
} }
override fun requireFinishAutoCompletion(): Boolean {
return termData.onAutoCompleteListener?.onFinishCompletion() ?: false
}
override fun requireToggleFullScreen() { override fun requireToggleFullScreen() {
EventBus.getDefault().post(ToggleFullScreenEvent()) EventBus.getDefault().post(ToggleFullScreenEvent())
} }

View File

@ -0,0 +1,54 @@
package io.neoterm.view
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.LinearLayout
class MaxHeightView : LinearLayout {
private var maxHeight = -1
constructor(context: Context) : super(context) {}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {}
fun setMaxHeight(maxHeight: Int) {
this.maxHeight = maxHeight
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var finalHeightMeasureSpec = heightMeasureSpec
if (maxHeight > 0) {
val heightMode = View.MeasureSpec.getMode(heightMeasureSpec)
var heightSize = View.MeasureSpec.getSize(heightMeasureSpec)
if (heightMode == View.MeasureSpec.EXACTLY) {
heightSize = if (heightSize <= maxHeight)
heightSize
else
maxHeight
}
if (heightMode == View.MeasureSpec.UNSPECIFIED) {
heightSize = if (heightSize <= maxHeight)
heightSize
else
maxHeight
}
if (heightMode == View.MeasureSpec.AT_MOST) {
heightSize = if (heightSize <= maxHeight)
heightSize
else
maxHeight
}
finalHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(heightSize,
heightMode)
}
super.onMeasure(widthMeasureSpec, finalHeightMeasureSpec)
}
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <io.neoterm.view.MaxHeightView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/popup_background" android:background="@color/popup_background"
@ -10,4 +10,4 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</LinearLayout> </io.neoterm.view.MaxHeightView>

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">NeoTerm</string>
<string name="about">關於</string>
<string name="copy_text">複製</string>
<string name="general_settings">一般設定</string>
<string name="new_session">新增終端</string>
<string name="package_settings">軟件包</string>
<string name="paste_text">貼上</string>
<string name="pref_general_backspace_map_to_esc">返回鍵發送ESC</string>
<string name="pref_general_backspace_map_to_esc_desc">按下返回鍵時發送ESC而不關閉</string>
<string name="pref_general_bell">響鈴</string>
<string name="pref_general_bell_desc">收到 \'\\a\' 時響鈴</string>
<string name="pref_general_shell">登入程式</string>
<string name="pref_general_shell_desc">登入時使用指定的程式</string>
<string name="pref_general_vibrate">震動</string>
<string name="pref_general_vibrate_desc">收到 \'\\a\' 時震動</string>
<string name="pref_general_program_selection">程式選擇模式</string>
<string name="pref_general_program_selection_desc">當 NeoTerm 和 系統 都有同一個程式時,選擇要被執行的程式模式</string>
<string name="pref_general_initial_command">初始化指令</string>
<string name="pref_general_initial_command_desc">在新的創建新的終端時執行指令</string>
<string name="pref_package_source">軟體源</string>
<string name="pref_ui_close_tab_anim_next_tab">向下切換窗口</string>
<string name="pref_ui_close_tab_anim_next_tab_desc">關閉目前視窗時切換到下一個而不是上一個</string>
<string name="customization_settings_desc">字體,主題,擴充鍵盤,自動填充</string>
<string name="customization_settings">個人化</string>
<string name="pref_ui_fullscreen">全螢幕</string>
<string name="pref_ui_hide_toolbar">隱藏標題欄</string>
<string name="pref_ui_hide_toolbar_desc">在鍵盤顯示時隱藏標題欄</string>
<string name="pref_ui_suggestions">顯示建議 (需要 oh-my-zsh)</string>
<string name="pref_ui_suggestions_desc">使用一些程式時,在螢幕底部顯示快捷鍵</string>
<string name="pref_ui_wide_char_weight_explicit">為寬字元設定權重</string>
<string name="pref_ui_wide_char_weight_explicit_desc">如果快捷輸入欄顯示錯誤,請勾選此項目</string>
<string name="pref_customization_color_scheme">色彩主題</string>
<string name="pref_customization_font">字體</string>
<string name="settings">設定</string>
<string name="text_selection_more">更多</string>
<string name="toggle_ime">切換輸入法</string>
<string name="toggle_tab_switcher_menu_item">切換視窗</string>
<string name="ui_settings">介面設定</string>
<string name="shell_not_found">找不到 Shell %s, 請先安裝.</string>
<string name="installer_message">正在安装</string>
<string name="installer_install_zsh_required">將要安装 oh-my-zsh 並將登入程式切換到 zsh</string>
<string name="fullscreen_mode_changed">全螢幕模式已改變,請重新開啟 NeoTerm</string>
<string name="permission_denied">NeoTerm 無法取得必需的權限,正在退出</string>
<string name="error">還有這種操作?</string>
<string name="use_system_shell">使用系统Shell</string>
<string name="retry">重試</string>
<string name="source_changed">APT 源已改變,可能需要執行 apt update 來更新</string>
<string name="package_details">軟體包: %s\n版本: %s\n依賴: %s\n使用空間: %s\n描述: %s\n首頁: %s</string>
<string name="package_list_empty">軟體包列表為空,請檢查你的軟體源</string>
<string name="menu_refresh_list">重新整理</string>
<string name="menu_update">更新並重新整理</string>
<string name="float_up">懸浮視窗</string>
<string-array name="pref_general_program_selection_entries">
<item>只使用 NeoTerm</item>
<item>優先使用 NeoTerm</item>
<item>優先使用 System</item>
</string-array>
<string name="done">完成</string>
<string name="install">安裝</string>
<string-array name="pref_package_source_entries">
<item>預設軟體源 (穩定,推薦)</item>
<item>偵錯軟體源 (可能不穩定)</item>
<item>手動輸入…</item>
</string-array>
<string name="pref_customization_eks">擴充鍵盤</string>
<string name="general_settings_desc">響鈴震動Shell初始化指令</string>
<string name="ui_settings_desc">全螢幕,標題欄,切換動畫</string>
<string name="package_settings_desc">軟體源,更新,升級</string>
<string name="install_font">安裝字體</string>
<string name="install_color">安裝色彩主題</string>
<string name="setup_hello">發現</string>
<string name="setup_info">你好NeoTerm</string>
<string name="setup_info2">輕觸以選擇你的最愛</string>
<string name="setup_next">安裝</string>
<string name="discovery">發現</string>
<string name="crash_model">裝置: %s</string>
<string name="crash_app">程式: %s</string>
<string name="crash_stack_trace">錯誤訊息</string>
<string name="crash_tips">我們正在努力讓這個 Activity 永不見天日…</string>
<string name="service_status_text">%d 個終端</string>
<string name="service_lock_acquired"> (永不休眠)</string>
<string name="service_acquire_lock">開啟休眠鎖</string>
<string name="service_release_lock">關閉休眠鎖</string>
<string name="exit">離開</string>
<string name="term_here">在此處打開終端</string>
<string name="user_script">使用者腳本</string>
<string name="no_user_script_found_or_files_selected">没有找到可用的使用者腳本或是沒有檔案被選擇</string>
<string name="files_to_handle">處理的檔案</string>
<string name="available_user_scripts">可用的使用者腳本</string>
<string name="confirm_remove_file_from_list">從操作列表中中移除檔案?</string>
</resources>