package edu.kufpg.armatus.console; import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentTransaction; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Typeface; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.DragEvent; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.MeasureSpec; import android.view.View.OnClickListener; import android.view.View.OnDragListener; import android.view.View.OnLayoutChangeListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.AbsListView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.EditText; import android.widget.ExpandableListView; import android.widget.ExpandableListView.ExpandableListContextMenuInfo; import android.widget.ExpandableListView.OnChildClickListener; import android.widget.ImageButton; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu; import edu.kufpg.armatus.BaseActivity; import edu.kufpg.armatus.BaseApplication; import edu.kufpg.armatus.MainActivity; import edu.kufpg.armatus.Prefs; import edu.kufpg.armatus.Prefs.NetworkSource; import edu.kufpg.armatus.R; import edu.kufpg.armatus.console.CommandExpandableMenuAdapter.CommandExpandableMenuItem; import edu.kufpg.armatus.console.ConsoleWordSearcher.MatchParams; import edu.kufpg.armatus.console.ConsoleWordSearcher.SearchDirection; import edu.kufpg.armatus.data.CommandInfo; import edu.kufpg.armatus.data.CommandResponse; import edu.kufpg.armatus.data.HistoryCommand; import edu.kufpg.armatus.dialog.CommandHelpDialog; import edu.kufpg.armatus.dialog.ConsoleEntrySelectionDialog; import edu.kufpg.armatus.dialog.GestureDialog; import edu.kufpg.armatus.dialog.InputCompletionDialog; import edu.kufpg.armatus.dialog.KeywordSwapDialog; import edu.kufpg.armatus.dialog.ScrollEntriesDialog; import edu.kufpg.armatus.dialog.YesOrNoDialog; import edu.kufpg.armatus.input.SpecialKeyAdapter; import edu.kufpg.armatus.networking.BluetoothDeviceListActivity; import edu.kufpg.armatus.networking.BluetoothUtils; import edu.kufpg.armatus.networking.InternetUtils; import edu.kufpg.armatus.util.OnExpandableItemLongClickListener; import edu.kufpg.armatus.util.StringUtils; import edu.kufpg.armatus.util.Views; import org.lucasr.twowayview.TwoWayView; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; public class ConsoleActivity extends BaseActivity { public static final int DEFAULT_FONT_SIZE = 15; public static final int CONSOLE_ENTRY_LIMIT = 100; private static final int LONG_CLICKED_GROUP = 42; private static final int DRAGGED_GROUP = 43; private static final int SCROLL_DURATION = 500; private static final String SCROLL_ENTRIES_TAG = "scrollentries"; private static final String SELECTION_TAG = "selection"; private static final String KEYWORD_SWAP_TAG = "keywordswap"; private static final String WORD_COMPLETION_TAG = "wordcomplete"; public static Typeface TYPEFACE; private static final String TYPEFACE_PATH = "fonts/DroidSansMonoDotted.ttf"; private HermitClient mHermitClient; protected RelativeLayout mConsoleListLayout; private RelativeLayout mConsoleInputLayout, mConsoleOptionsBar; protected ConsoleListView mConsoleListView; private ConsoleEntryAdapter mConsoleListAdapter; private ArrayList<ConsoleEntry> mConsoleEntries; private ListView mCommandHistoryView; private CommandHistoryAdapter mCommandHistoryAdapter; private ArrayList<HistoryCommand> mCommandHistory; protected ExpandableListView mCommandExpandableMenuView; private CommandExpandableMenuAdapter mCommandExpandableMenuAdapter; protected EditText mCommandExpandableSearchView; private ConsoleWordSearcher mSearcher; private CharSequence mMenuSearchConstraint; private ConsoleEntry mNextConsoleEntry; private ArrayList<String> mUserInputHistory; private int mUserInputHistoryChoice; private TextView mConsoleInputNumView, mSearchMatches; private ConsoleInputEditText mConsoleInputEditText; private View mConsoleEmptySpace; private EditText mSearchInputView; protected SlidingMenu mSlidingMenu; private String mTempCommand, mTempSearchInput, mPrevSearchCriterion; private boolean mInputEnabled = true; private boolean mSoftKeyboardVisible = true; private boolean mSearchEnabled = false; private int mConsoleInputNum = 0; private int mConsoleEntriesHeight, mConsoleInputHeight, mScreenHeight, mConsoleWidth; @Override protected void onCreate(Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_PROGRESS); super.onCreate(savedInstanceState); getActionBar().setDisplayHomeAsUpEnabled(true); setContentView(R.layout.console_sliding_menu_activity); setProgressBarIndeterminate(true); mConsoleListLayout = (RelativeLayout) findViewById(R.id.console_list_layout); mConsoleListView = (ConsoleListView) findViewById(R.id.console_list_view); mSlidingMenu = (SlidingMenu) findViewById(R.id.console_sliding_menu); mCommandHistoryView = (ListView) findViewById(R.id.command_history_list_view); mCommandExpandableMenuView = (ExpandableListView) findViewById(R.id.command_expandable_menu); mCommandExpandableSearchView = (EditText) findViewById(R.id.command_expandable_menu_search); mConsoleEmptySpace = findViewById(R.id.console_empty_space); mSearchMatches = (TextView) findViewById(R.id.console_search_matches_indicator); final View rootView = ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0); mConsoleInputLayout = (RelativeLayout) getLayoutInflater().inflate(R.layout.console_input, null); mConsoleInputNumView = (TextView) mConsoleInputLayout.findViewById(R.id.console_input_num); mConsoleInputEditText = (ConsoleInputEditText) mConsoleInputLayout.findViewById(R.id.console_input_edit_text); mConsoleOptionsBar = (RelativeLayout) findViewById(R.id.console_options_bar); final ImageButton nextEntryButton = (ImageButton) findViewById(R.id.console_input_next_entry); final ImageButton scrollEntriesButton = (ImageButton) findViewById(R.id.console_input_scroll_entries); final ImageButton prevEntryButton = (ImageButton) findViewById(R.id.console_input_previous_entry); final ImageButton toggleSpecialKeysButton = (ImageButton) findViewById(R.id.console_options_toggle_special_keys); final ImageButton hideOptionsBarButton = (ImageButton) findViewById(R.id.console_options_hide_button); final TwoWayView specialKeyRow = (TwoWayView) findViewById(R.id.console_special_key_list); TYPEFACE = Typeface.createFromAsset(getAssets(), TYPEFACE_PATH); if (savedInstanceState == null) { setSoftKeyboardVisibility(true); mCommandHistory = new ArrayList<HistoryCommand>(); mConsoleEntries = new ArrayList<ConsoleEntry>(); mConsoleInputLayout.setLayoutParams(new AbsListView.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); mConsoleInputEditText.requestFocus(); mHermitClient = new HermitClient(this); mUserInputHistory = new ArrayList<String>(); mUserInputHistory.add(""); mUserInputHistoryChoice = 0; } else { mConsoleInputEditText.setText(savedInstanceState.getString("consoleInput")); mSoftKeyboardVisible = savedInstanceState.getBoolean("softKeyboardVisibility"); setSoftKeyboardVisibility(mSoftKeyboardVisible); mSearchEnabled = savedInstanceState.getBoolean("findTextEnabled"); if (mSearchEnabled) { mSearchMatches.setVisibility(View.VISIBLE); mSearchMatches.setText(savedInstanceState.getString("searchMatches")); mTempSearchInput = savedInstanceState.getString("searchInput"); mPrevSearchCriterion = savedInstanceState.getString("prevSearchCriterion"); } else { mConsoleInputEditText.setSelection(savedInstanceState.getInt("consoleInputCursor")); } mCommandHistory = savedInstanceState.getParcelableArrayList("commandHistory"); mConsoleInputNum = savedInstanceState.getInt("consoleInputNum"); mConsoleEntries = savedInstanceState.getParcelableArrayList("consoleEntries"); mHermitClient = savedInstanceState.getParcelable("hermitClient"); mHermitClient.attachConsole(this); mUserInputHistory = savedInstanceState.getStringArrayList("userInputHistory"); mUserInputHistoryChoice = savedInstanceState.getInt("userInputHistoryChoice"); } mConsoleListLayout.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { mScreenHeight = mConsoleListLayout.getMeasuredHeight(); resizeEmptySpace(); } }); rootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override //Detects whether soft keyboard is open or closed public void onGlobalLayout() { int rootHeight = rootView.getRootView().getHeight(); int heightDiff = rootHeight - rootView.getHeight(); if (heightDiff > rootHeight/3) { //This works on Nexus 7s, at the very least mSoftKeyboardVisible = true; } else { mSoftKeyboardVisible = false; } specialKeyRow.setVisibility((mSoftKeyboardVisible && Prefs.getSpecialKeysVisible(ConsoleActivity.this)) ? View.VISIBLE : View.GONE); } }); registerForContextMenu(mConsoleEmptySpace); mConsoleEmptySpace.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mConsoleInputEditText.setSelection(getInputLength()); mConsoleInputEditText.requestFocus(); if (!mSoftKeyboardVisible) { //setSoftKeyboardVisibility(true) doesn't work here for some weird reason ((InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE)). toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); } } }); mConsoleEmptySpace.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { openContextMenu(mConsoleEmptySpace); return true; } }); mConsoleEmptySpace.setOnDragListener(new OnDragListener() { @Override public boolean onDrag(View v, DragEvent event) { switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: mSlidingMenu.showContent(); return true; case DragEvent.ACTION_DROP: case DragEvent.ACTION_DRAG_ENDED: if (event.getLocalState() instanceof View) { View dragSource = (View) event.getLocalState(); if (dragSource != null) { dragSource.setVisibility(View.VISIBLE); } } return true; default: return false; } } }); registerForContextMenu(mConsoleListView); mConsoleListAdapter = new ConsoleEntryAdapter(this, mConsoleEntries); mConsoleListView.addFooterView(mConsoleInputLayout); mConsoleListView.setAdapter(mConsoleListAdapter); //MUST be called after addFooterView() mConsoleListView.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { mConsoleEntriesHeight = 0; mainloop: for (int groupPos = 0; groupPos < mConsoleEntries.size(); groupPos++) { View group = mConsoleListView.getExpandableListAdapter().getGroupView(groupPos, true, null, mConsoleListView); if (measureView(group)) { break mainloop; } int children = mConsoleListView.getExpandableListAdapter().getChildrenCount(groupPos); for (int childPos = 0; childPos < children; childPos++) { View child = mConsoleListView.getExpandableListAdapter().getChildView(groupPos, childPos, true, null, mConsoleListView); if (measureView(child)) { break mainloop; } } } resizeEmptySpace(); } private boolean measureView(View v) { if (v.getLayoutParams() == null) { v.setLayoutParams(new LayoutParams(0, 0)); // Prevent crashing on CyanogenMod } v.measure(MeasureSpec.makeMeasureSpec(mConsoleWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); mConsoleEntriesHeight += v.getMeasuredHeight(); if (mConsoleEntriesHeight >= mScreenHeight) { mConsoleEntriesHeight = mScreenHeight; return true; } else { return false; } } }); if (savedInstanceState == null) { mSearcher = new ConsoleWordSearcher(mConsoleListAdapter); } else { mSearcher = savedInstanceState.getParcelable("consoleSearcher"); mSearcher.attachAdapter(mConsoleListAdapter); } mCommandHistoryAdapter = new CommandHistoryAdapter(this, mCommandHistory); mCommandHistoryView.setAdapter(mCommandHistoryAdapter); mCommandHistoryView.setEmptyView(findViewById(R.id.command_history_empty_view)); if (savedInstanceState != null) { mMenuSearchConstraint = savedInstanceState.getCharSequence("menuSearchConstraint"); if (mMenuSearchConstraint != null) { mCommandExpandableMenuAdapter = new CommandExpandableMenuAdapter(this, mMenuSearchConstraint); } else { mCommandExpandableMenuAdapter = new CommandExpandableMenuAdapter(this); } } else { mCommandExpandableMenuAdapter = new CommandExpandableMenuAdapter(this); } mCommandExpandableMenuView.setAdapter(mCommandExpandableMenuAdapter); mCommandExpandableMenuView.setOnChildClickListener(new OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { String cmdName = mCommandExpandableMenuAdapter.getChild(groupPosition, childPosition); appendInputText(cmdName + StringUtils.NBSP); return true; } }); mCommandExpandableMenuView.setOnItemLongClickListener(new OnExpandableItemLongClickListener() { @Override public boolean onChildLongClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { CommandExpandableMenuItem item = (CommandExpandableMenuItem) mCommandExpandableMenuAdapter. getChildView(groupPosition, childPosition, false, v, parent).getTag(); String commandName = item.commandName.getText().toString(); List<? extends CommandInfo> commandInfos = CommandHolder.getCommandsFromName(commandName); CommandHelpDialog helpDialog = CommandHelpDialog.newInstance(commandInfos); helpDialog.show(getFragmentManager(), "commandHelp"); return true; } }); //commandExpandableMenuSearch.setCompoundDrawable mCommandExpandableSearchView.setOnEditorActionListener(new OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (event == null || event.getAction() == KeyEvent.ACTION_UP) { CharSequence constraint = mCommandExpandableSearchView.getText(); mMenuSearchConstraint = constraint.length() == 0 ? null : constraint; mCommandExpandableMenuAdapter.getFilter().filter(mMenuSearchConstraint); mCommandExpandableSearchView.clearFocus(); mConsoleInputEditText.requestFocus(); return true; } return false; } }); // if (savedInstanceState != null) { // mMenuSearchConstraint = savedInstanceState.getCharSequence("menuSearchConstraint"); // if (mMenuSearchConstraint != null) { // mCommandExpandableMenuAdapter.getFilter().filter(mMenuSearchConstraint); // } // } mConsoleInputNumView.setTypeface(TYPEFACE); mConsoleInputEditText.setTypeface(TYPEFACE); mConsoleInputEditText.addTextChangedListener(new TextWatcher() { @Override public void afterTextChanged(Editable s) {} @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void onTextChanged(CharSequence s, int start, int before, int count) { //Closes SlidingMenu and scroll to bottom if user begins typing mSlidingMenu.showContent(); mConsoleListView.setActionModeVisible(false); if (mSearchEnabled) { executeSearch(null, SearchAction.END, null); } mConsoleListView.post(new Runnable() { @Override public void run() { //DON'T use scrollToBottom(); it will cause a strange jaggedy scroll effect mConsoleListView.setSelection(mConsoleListView.getCount() - 1); } }); } }); //Processes user input (and runs command, if input is a command) when Enter is pressed mConsoleInputEditText.setOnEditorActionListener(new OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (event == null || event.getAction() == KeyEvent.ACTION_UP) { if (mInputEnabled) { String input = mConsoleInputEditText.getText().toString(); if (input.isEmpty() || input.matches(StringUtils.WHITESPACE)) { addUserInputEntry(input); } else { int size = mUserInputHistory.size(); String trimput = input.trim(); if (size == 1 || (size > 1 && !mUserInputHistory.get(size - 2).equals(trimput))) { mUserInputHistory.set(size - 1, trimput); mUserInputHistory.add(""); mUserInputHistoryChoice = size; } else { mUserInputHistoryChoice = size - 1; } float lineWidth = mConsoleInputEditText.getMeasuredWidth(); float charWidth = mConsoleInputEditText.getPaint().measureText(" "); mHermitClient.runCommand(input, (int) (lineWidth / charWidth)); } mConsoleInputEditText.setText(""); return true; } } return false; } }); mConsoleInputLayout.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { mConsoleInputHeight = mConsoleInputLayout.getMeasuredHeight(); mConsoleWidth = mConsoleInputLayout.getMeasuredWidth(); resizeEmptySpace(); } }); nextEntryButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mUserInputHistoryChoice < mUserInputHistory.size() - 1) { selectFromUserInputHistory(mUserInputHistoryChoice + 1); } } }); scrollEntriesButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mUserInputHistory.size() > 1) { showDialog(ScrollEntriesDialog.newInstance(mUserInputHistoryChoice, mUserInputHistory), SCROLL_ENTRIES_TAG); } } }); prevEntryButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mUserInputHistoryChoice > 0) { selectFromUserInputHistory(mUserInputHistoryChoice - 1); } } }); toggleSpecialKeysButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Prefs.setSpecialKeysVisible(ConsoleActivity.this, !Prefs.getSpecialKeysVisible(ConsoleActivity.this)); } }); hideOptionsBarButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mConsoleOptionsBar.setVisibility(View.GONE); invalidateOptionsMenu(); } }); final SpecialKeyAdapter ska = new SpecialKeyAdapter(this, Lists.newArrayList("|", "[", "]", "{", "}", "∀", "#", "→", "△", "▲", "λ")); ska.setTypeface(TYPEFACE); specialKeyRow.setAdapter(ska); specialKeyRow.post(new Runnable() { @Override public void run() { View charView = ska.getView(0, null, specialKeyRow); charView.measure(MeasureSpec.makeMeasureSpec(mConsoleWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); specialKeyRow.getLayoutParams().height = charView.getMeasuredHeight(); specialKeyRow.requestLayout(); } }); updateConsoleEntries(); resizeSlidingMenu(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelableArrayList("commandHistory", mCommandHistory); outState.putInt("consoleInputNum", mConsoleInputNum); outState.putString("consoleInput", mConsoleInputEditText.getText().toString()); outState.putParcelable("consoleSearcher", mSearcher); outState.putInt("consoleInputCursor", mConsoleInputEditText.getSelectionStart()); if (mSearchInputView != null) { outState.putString("searchMatches", mSearchMatches.getText().toString()); outState.putString("searchInput", mSearchInputView.getText().toString()); outState.putString("prevSearchCriterion", mPrevSearchCriterion); } outState.putCharSequence("menuSearchConstraint", mMenuSearchConstraint); outState.putBoolean("softKeyboardVisibility", mSoftKeyboardVisible); outState.putBoolean("findTextEnabled", mSearchEnabled); outState.putParcelableArrayList("consoleEntries", mConsoleEntries); outState.putParcelable("hermitClient", mHermitClient); outState.putStringArrayList("userInputHistory", mUserInputHistory); outState.putInt("userInputHistoryChoice", mUserInputHistoryChoice); } @Override public void onBackPressed() { exit(false); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case BluetoothUtils.REQUEST_ENABLE_BLUETOOTH: if (mHermitClient.isRequestDelayed()) { if (resultCode == RESULT_OK) { mHermitClient.runDelayedRequest(); } else { appendErrorResponse("ERROR: Failed to enable Bluetooth."); } } break; case InternetUtils.REQUEST_ENABLE_WIFI: if (mHermitClient.isRequestDelayed()) { //Unfortunately, ACTION_PICK_WIFI_NETWORK doesn't use RESULT_OK, so we //have to check the Wi-Fi state manually. if (InternetUtils.isWifiConnected(this)) { mHermitClient.runDelayedRequest(); } else { appendErrorResponse("ERROR: Failed to enable Wi-Fi."); } } break; case BluetoothUtils.REQUEST_FIND_BLUETOOTH_DEVICE: if (mHermitClient.isRequestDelayed()) { if (resultCode == RESULT_OK) { String name = data.getStringExtra(BluetoothDeviceListActivity.EXTRA_DEVICE_NAME); String address = data.getStringExtra(BluetoothDeviceListActivity.EXTRA_DEVICE_ADDRESS); Prefs.setBluetoothDeviceName(this, name); Prefs.setBluetoothDeviceAddress(this, address); mHermitClient.runDelayedRequest(); } else { appendErrorResponse("ERROR: Failed to locate Bluetooth device."); } } break; } super.onActivityResult(requestCode, resultCode, data); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); menu.setGroupVisible(R.id.menu_icons_group, !mSearchEnabled); menu.setGroupVisible(R.id.history_group, true); menu.findItem(R.id.find_text_option).setVisible(!mSearchEnabled); menu.findItem(R.id.find_text_action).setVisible(mSearchEnabled); menu.findItem(R.id.show_console_options_bar).setVisible(mConsoleOptionsBar.getVisibility() == View.GONE); if (mSearchEnabled) { View actionView = menu.findItem(R.id.find_text_action).getActionView(); mSearchInputView = (EditText) actionView.findViewById(R.id.find_text_box); final ImageButton nextButton, prevButton, cancelButton; nextButton = (ImageButton) actionView.findViewById(R.id.find_text_next); prevButton = (ImageButton) actionView.findViewById(R.id.find_text_previous); cancelButton = (ImageButton) actionView.findViewById(R.id.find_text_cancel); final OnClickListener actionLayoutButtonListener = new OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { case R.id.find_text_next: executeSearch(null, SearchAction.CONTINUE, SearchDirection.NEXT); break; case R.id.find_text_previous: executeSearch(null, SearchAction.CONTINUE, SearchDirection.PREVIOUS); break; case R.id.find_text_cancel: executeSearch(null, SearchAction.END, null); mConsoleInputEditText.requestFocus(); break; } } }; nextButton.setOnClickListener(actionLayoutButtonListener); prevButton.setOnClickListener(actionLayoutButtonListener); cancelButton.setOnClickListener(actionLayoutButtonListener); if (mTempSearchInput != null) { mSearchInputView.setText(mTempSearchInput); mSearchInputView.setSelection(mSearchInputView.length()); setTextSearchCaption(); } if (mPrevSearchCriterion != null) { executeSearch(null, SearchAction.RESUME, null); } mSearchInputView.setOnEditorActionListener(new OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (event == null || event.getAction() == KeyEvent.ACTION_UP) { String searchCriterion = StringUtils.charWrap(mSearchInputView.getText().toString()); if (!searchCriterion.equalsIgnoreCase(mPrevSearchCriterion)) { executeSearch(searchCriterion, SearchAction.BEGIN, null); } else { executeSearch(null, SearchAction.CONTINUE, SearchDirection.NEXT); } mPrevSearchCriterion = searchCriterion; return true; } return false; } }); mSearchInputView.setTypeface(TYPEFACE); mSearchInputView.requestFocus(); } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: exit(false); return true; case R.id.gestures: GestureDialog gd = new GestureDialog(); gd.show(getFragmentManager(), "gesture"); return true; case R.id.complete: mHermitClient.completeInput(getInput()); return true; case R.id.find_text_option: mSearchEnabled = true; invalidateOptionsMenu(); return true; case R.id.show_console_options_bar: mConsoleOptionsBar.setVisibility(View.VISIBLE); invalidateOptionsMenu(); return true; case R.id.save_history: mHermitClient.fetchHistory(); return true; case R.id.load_history: mHermitClient.loadHistory(); return true; default: return super.onOptionsItemSelected(item); } } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { switch (v.getId()) { case R.id.console_empty_space: super.onCreateContextMenu(menu, v, menuInfo); menu.setHeaderTitle("Input options"); getMenuInflater().inflate(R.menu.console_empty_space_context_menu, menu); if (getInputLength() == 0) { menu.findItem(R.id.console_input_paste_append).setTitle("Paste"); menu.findItem(R.id.console_input_paste_append).setTitleCondensed("Paste"); menu.findItem(R.id.console_input_paste_replace).setVisible(false); } break; case R.id.console_list_view: if (!(menuInfo instanceof AdapterContextMenuInfo)) { ExpandableListContextMenuInfo expInfo = (ExpandableListContextMenuInfo) menuInfo; int groupPos = ExpandableListView.getPackedPositionGroup(expInfo.packedPosition); if (!mConsoleEntries.get(groupPos).getShortContents().toString().isEmpty()) { //To prevent empty lines super.onCreateContextMenu(menu, v, menuInfo); menu.setHeaderTitle("Entry " + groupPos + ": Commands found"); menu.add(Menu.NONE, Menu.NONE, 1, "Sample transformation (does nothing)"); } } break; } } @Override public boolean onContextItemSelected(MenuItem item) { if (item != null) { switch (item.getGroupId()) { case DRAGGED_GROUP: String keywordNStr = item.getTitle().toString(); if (mInputEnabled) { CustomCommandDispatcher.runCustomCommand(this, mTempCommand, keywordNStr); } else { mConsoleInputEditText.setText(mTempCommand + StringUtils.NBSP + keywordNStr); } break; case LONG_CLICKED_GROUP: // Fill in with something useful later break; case R.id.console_input_group: switch (item.getItemId()) { case R.id.console_input_select: mConsoleInputEditText.setSelection(0, getInputLength()); break; case R.id.console_input_copy: { ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData copiedText = ClipData.newPlainText("copiedText", getInput()); clipboard.setPrimaryClip(copiedText); showToast("Selection copied to clipboard!"); break; } case R.id.console_input_paste_append: case R.id.console_input_paste_replace: { ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); if (clipboard.hasPrimaryClip() && clipboard.getPrimaryClipDescription().hasMimeType("text/plain")) { CharSequence pasteData = clipboard.getPrimaryClip().getItemAt(0).getText(); if (pasteData != null) { String pastedText = StringUtils.charWrap(pasteData.toString()); if (item.getItemId() == R.id.console_input_paste_append) { mConsoleInputEditText.append(pastedText); } else { mConsoleInputEditText.setText(pastedText); } mConsoleInputEditText.setSelection(getInputLength()); } } break; } } break; } mConsoleInputEditText.requestFocus(); //Prevents ListView from stealing focus } return super.onContextItemSelected(item); } @Override public void onContextMenuClosed(Menu menu) { //Ensures that this temp variable does not persist to next context menu opening mTempCommand = null; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_TAB: mHermitClient.completeInput(getInput()); return true; } return super.onKeyDown(keyCode, event); } public void addCommandHistoryEntry(int fromAst, String command, int toAst) { mCommandHistory.add(new HistoryCommand(fromAst, command, toAst)); mCommandHistoryAdapter.notifyDataSetChanged(); } public void addUserInputEntry(String userInput) { addEntry(userInput, null, null); } public void addCommandResponseEntry(CommandResponse commandResponse) { addEntry(null, commandResponse, null); } public void addErrorResponseEntry(String errorResponse) { addEntry(null, null, errorResponse); } private void addEntry(String userInput, CommandResponse commandResponse, String errorResponse) { if (userInput != null) { mNextConsoleEntry.setUserInput(userInput); } if (commandResponse != null) { mNextConsoleEntry.appendCommandResponse(commandResponse); } if (errorResponse != null) { mNextConsoleEntry.appendErrorResponse(errorResponse); } mConsoleInputNum++; mConsoleEntries.add(mNextConsoleEntry); updateConsoleEntries(); scrollToBottom(); } public void clearCommandHistory() { mCommandHistory.clear(); mCommandHistoryAdapter.notifyDataSetChanged(); } public void removeConsoleEntry() { if (!mConsoleEntries.isEmpty()) { mConsoleInputNum--; mConsoleEntries.remove(getEntryCount() - 1); updateConsoleEntries(); scrollToBottom(); } } public void appendErrorResponse(String errorResponse) { if (!mConsoleEntries.isEmpty()) { ConsoleEntry prevEntry = mConsoleEntries.get(getEntryCount() - 1); if (prevEntry.getErrorResponse() == null) { prevEntry.appendErrorResponse(errorResponse); updateConsoleEntries(); scrollToBottom(); } else { addErrorResponseEntry(errorResponse); } } else { addErrorResponseEntry(errorResponse); } } public void appendCommandResponse(CommandResponse commandResponse) { if (!mConsoleEntries.isEmpty()) { ConsoleEntry prevEntry = mConsoleEntries.get(getEntryCount() - 1); if (prevEntry.getCommandResponse() == null) { prevEntry.appendCommandResponse(commandResponse); updateConsoleEntries(); scrollToBottom(); } else { addCommandResponseEntry(commandResponse); } } else { addCommandResponseEntry(commandResponse); } } public void appendInputText(String text) { mConsoleInputEditText.getText().append(text); mConsoleInputEditText.setSelection(getInputLength()); } public void clear() { mConsoleInputNum = 0; mConsoleEntries.clear(); updateConsoleEntries(); } public void disableInput(boolean hideInput) { mInputEnabled = false; mConsoleInputLayout.setVisibility(hideInput ? View.INVISIBLE : View.VISIBLE); } public void enableInput() { mInputEnabled = true; mConsoleInputLayout.setVisibility(View.VISIBLE); mConsoleInputEditText.requestFocus(); } public void exit(boolean forceExit) { if (forceExit) { exitForced(); } else { String title = getResources().getString(R.string.console_exit_title); String message = getResources().getString(R.string.console_exit_message); YesOrNoDialog exitDialog = new YesOrNoDialog(title, message) { @Override protected void yes(DialogInterface dialog, int whichButton) { exitForced(); } }; exitDialog.show(getFragmentManager(), "exit"); } } private void exitForced() { if (Prefs.getNetworkSource(this).equals(NetworkSource.BLUETOOTH_SERVER)) { if (BluetoothUtils.isBluetoothConnected(this)) { BluetoothUtils.closeBluetooth(); } } ((BaseApplication) getApplication()).cancelTasks(this); Intent intent = new Intent(this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } public ConsoleEntry getEntry(int index) { return mConsoleEntries.get(index); } public CharSequence getLine(int entryIndex, int lineIndex) { return getEntry(entryIndex).getContentLines().get(lineIndex); } /** * @return the total number of console entries. */ public int getEntryCount() { return mConsoleEntries.size(); } public HermitClient getHermitClient() { return mHermitClient; } public String getInput() { return StringUtils.noCharWrap(mConsoleInputEditText.getText().toString()); } public int getInputLength() { return mConsoleInputEditText.length(); } /** * @return the entry number for the console input field. */ public int getInputNum() { return mConsoleInputNum; } public boolean isSoftKeyboardVisible() { return mSoftKeyboardVisible; } public void selectFromUserInputHistory(int choice) { mUserInputHistoryChoice = choice; mConsoleInputEditText.setText(mUserInputHistory.get(mUserInputHistoryChoice)); mConsoleInputEditText.setSelection(getInputLength()); mConsoleInputEditText.requestFocus(); } public void setInputEnabled(boolean enabled) { mInputEnabled = enabled; } public void setInputText(String text) { setInputText(0, getInputLength(), text); } public void setInputText(int start, int end, String text) { mConsoleInputEditText.getText().replace(start, end, text); mConsoleInputEditText.setSelection(getInputLength()); } public void showEntrySelectionDialog(SortedSet<ConsoleLineParams> lineParams) { showDialog(ConsoleEntrySelectionDialog.newInstance(lineParams), SELECTION_TAG); } public void showKeywordSwapDialog(int entryNum, String entryContents) { showDialog(KeywordSwapDialog.newInstance(entryNum, entryContents), KEYWORD_SWAP_TAG); } public void showInputCompletionDialog(int replaceIndex, Collection<String> suggestions) { if (suggestions.size() > 0) { showDialog(InputCompletionDialog.newInstance(replaceIndex, suggestions), WORD_COMPLETION_TAG); } } private void showDialog(DialogFragment fragment, String tag) { FragmentTransaction ft = getFragmentManager().beginTransaction(); Fragment prev = getFragmentManager().findFragmentByTag(tag); if (prev != null) { ft.remove(prev); } ft.addToBackStack(null); ft.add(fragment, tag); ft.commit(); } public void attemptInputCompletion(Collection<String> existingSuggestions) { SortedSet<String> suggestions = new TreeSet<String>(); if (existingSuggestions != null) { suggestions.addAll(existingSuggestions); } final String input = mConsoleInputEditText.getText().toString(); int replaceIndex = Math.max(0, input.length() - 1); if (input.length() > 0 && !String.valueOf(input.charAt(replaceIndex)).matches(StringUtils.WHITESPACE)) { replaceIndex = StringUtils.findLastWordIndex(input); } if (replaceIndex == StringUtils.findFirstWordIndex(input) || input.isEmpty() || input.matches(StringUtils.WHITESPACE)) { final String trimput = StringUtils.trim(input); suggestions.addAll(Collections2.filter(CustomCommandDispatcher.getCommandSet(), new Predicate<String>() { @Override public boolean apply(String suggestion) { return suggestion.startsWith(trimput); } })); } if (suggestions.size() == 1) { for (String completion : suggestions) { completeInput(replaceIndex, completion); break; } } else if (suggestions.size() > 1) { showInputCompletionDialog(replaceIndex, suggestions); } } public void completeInput(int replaceIndex, String completion) { String input = getInput(); if (input.length() > 0 && String.valueOf(input.charAt(input.length() - 1)).matches(StringUtils.WHITESPACE)) { completion = StringUtils.NBSP + completion; } setInputText(replaceIndex, getInputLength(), completion + StringUtils.NBSP); } private void executeSearch(String criterion, SearchAction action, SearchDirection direction) { if (mInputEnabled) { setInputEnabled(false); MatchParams params = null; switch (action) { case BEGIN: mSearchMatches.setVisibility(View.VISIBLE); params = mSearcher.beginSearch(criterion); break; case CONTINUE: params = mSearcher.continueSearch(direction); case RESUME: params = mSearcher.getSelectedMatch(); break; case END: mSearchEnabled = false; mPrevSearchCriterion = null; mSearcher.endSearch(); mSearchMatches.setVisibility(View.GONE); invalidateOptionsMenu(); setInputEnabled(true); return; } // if (params != null && !Views.isEntryVisible(mConsoleListView, params.listIndex)) { // mConsoleListView.smoothScrollToPositionFromTop(params.listIndex, // params.textViewOffset, SCROLL_DURATION); // } if (params != null) { int flatListPos = Views.getFlatListPosition(mConsoleListView, params.entryIndex, params.lineIndex); if (!Views.isEntryVisible(mConsoleListView, flatListPos)) { mConsoleListView.smoothScrollToPositionFromTop(flatListPos, params.textViewOffset, SCROLL_DURATION); } } setTextSearchCaption(); setInputEnabled(true); } } private synchronized void resizeEmptySpace() { mConsoleEmptySpace.post(new Runnable() { @Override public void run() { mConsoleEmptySpace.getLayoutParams().height = mScreenHeight - mConsoleEntriesHeight - mConsoleInputHeight; mConsoleEmptySpace.requestLayout(); } }); } /** * Changes the SlidingMenu width depending on the screen size. */ private void resizeSlidingMenu() { int iconWidth = getResources().getDrawable(R.drawable.template_white).getIntrinsicWidth(); int plusWidth = getResources().getDrawable(R.drawable.ic_plus_light).getIntrinsicWidth(); int totalWidth = iconWidth + plusWidth; mSlidingMenu.setBehindWidth(Math.round(totalWidth*1.05f)); } /** * Show the entry at the bottom of the console ListView. */ private void scrollToBottom() { mConsoleListView.post(new Runnable() { @Override public void run() { mConsoleListView.setSelection(mConsoleListView.getCount()); mConsoleListView.smoothScrollToPosition(mConsoleListView.getCount()); } }); } /** * Shows or hides the soft keyboard. * @param visible Set to true to show soft keyboard, false to hide. */ public void setSoftKeyboardVisibility(boolean visible) { if (visible) { getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); } else { getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); } } void setCommandHistory(List<HistoryCommand> historyCommands) { mCommandHistory = new ArrayList<HistoryCommand>(historyCommands); Collections.sort(mCommandHistory); mCommandHistoryAdapter = new CommandHistoryAdapter(this, mCommandHistory); mCommandHistoryView.setAdapter(mCommandHistoryAdapter); } private void setTextSearchCaption() { int matches = mSearcher.getMatchesCount(); String caption; if (matches > 0) { caption = mSearcher.getSelectedMatchPosition() + "/" + matches; } else { caption = "0"; } mSearchMatches.setText(caption + " match" + (caption.endsWith("1") ? "" : "es")); } /** * Refreshes the console entries, removing excessive entries from the top if ENTRY_LIMIT is exceeded. */ void updateConsoleEntries() { if (mConsoleEntries.size() > CONSOLE_ENTRY_LIMIT) { mConsoleEntries.remove(0); } mConsoleListView.expandAllGroups(); mConsoleListAdapter.notifyDataSetChanged(); updateInput(); } void updateCommandExpandableMenu() { int count = mCommandExpandableMenuAdapter.getGroupCount(); for (int i = 0; i < count; i++) { mCommandExpandableMenuView.collapseGroup(i); } mCommandExpandableMenuAdapter.notifyDataSetChanged(); } void updateInput() { mNextConsoleEntry = new ConsoleEntry(getInputNum(), mHermitClient.getAst(), null); mConsoleInputNumView.setText(mNextConsoleEntry.getFullContentsPrefix()); mConsoleInputNumView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); final int width = mConsoleInputNumView.getMeasuredWidth(); final int padding = mConsoleInputNumView.getPaddingLeft(); mConsoleInputEditText.setIndent(width - padding); } private enum SearchAction { BEGIN, CONTINUE, RESUME, END } }