/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * muCommander is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mucommander.ui.main; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.mucommander.bookmark.Bookmark; import com.mucommander.bookmark.BookmarkManager; import com.mucommander.commons.file.AbstractFile; import com.mucommander.commons.file.FileURL; import com.mucommander.commons.file.protocol.FileProtocols; import com.mucommander.commons.file.protocol.local.LocalFile; import com.mucommander.commons.file.protocol.local.UNCFile; import com.mucommander.commons.file.util.PathUtils; import com.mucommander.commons.runtime.OsFamily; import com.mucommander.ui.autocomplete.AutocompleterTextComponent; import com.mucommander.ui.autocomplete.CompleterFactory; import com.mucommander.ui.autocomplete.TextFieldCompletion; import com.mucommander.ui.event.LocationEvent; import com.mucommander.ui.event.LocationListener; import com.mucommander.ui.progress.ProgressTextField; import com.mucommander.ui.theme.ColorChangedEvent; import com.mucommander.ui.theme.FontChangedEvent; import com.mucommander.ui.theme.Theme; import com.mucommander.ui.theme.ThemeListener; import com.mucommander.ui.theme.ThemeManager; /** * A TextField which is located on each panel and used to display the location presented in the panel's file-table, * and for letting the user change this location. * * This TextField support: * - auto-completion * - location changing progress indicator * - Theme settings * * @author Maxence Bernard, Arik Hadas */ public class LocationTextField extends ProgressTextField implements LocationListener, FocusListener, ThemeListener { /** FolderPanel this text field is displayed in */ private FolderPanel folderPanel; /** True while a folder is being changed after a path was entered in the location field and validated by the user */ private boolean folderChangeInitiatedByLocationField; /** Used to save the path that was entered by the user after validation of the location textfield */ private String locationFieldTextSave; /** For windows path, regex that finds trailing space characters at the end of a path */ private static Pattern windowsTrailingSpacePattern; static { if(OsFamily.WINDOWS.isCurrent()) windowsTrailingSpacePattern = Pattern.compile("[ ]+[\\\\]*$"); } /** * Creates a new LocationTextField for use in the given FolderPanel. * * @param folderPanel FolderPanel this text field is displayed in */ public LocationTextField(FolderPanel folderPanel) { // Use a custom text field that can display loading progress when changing folders super(0, ThemeManager.getCurrentColor(Theme.LOCATION_BAR_PROGRESS_COLOR)); this.folderPanel = folderPanel; // Applies theme values. setFont(ThemeManager.getCurrentFont(Theme.LOCATION_BAR_FONT)); setDisabledTextColor(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_FOREGROUND_COLOR)); setForeground(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_FOREGROUND_COLOR)); setBackground(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_BACKGROUND_COLOR)); setSelectedTextColor(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_SELECTED_FOREGROUND_COLOR)); setSelectionColor(ThemeManager.getCurrentColor(Theme.LOCATION_BAR_SELECTED_BACKGROUND_COLOR)); // Listen to location changes to update popup menu choices and disable this component while the location is // being changed folderPanel.getLocationManager().addLocationListener(this); // Listen to focus events to temporarily disable the MainFrame's JMenuBar when this component has the keyboard focus. // Not doing so would trigger unwanted menu bar actions when typing. addFocusListener(this); enableAutoCompletion(); ThemeManager.addCurrentThemeListener(this); } /** * Adds auto-completion capabilities to this text field. */ private void enableAutoCompletion() { new TextFieldCompletion(new AutocompleterTextComponent(this) { @Override public void OnEnterPressed(KeyEvent keyEvent) { if (textFieldValidated()) // if a malformed url was entered. folderChangeCompleted(false); // /!\ Consume the event so to prevent JTextField from firing an ActionEvent keyEvent.consume(); } @Override public void OnEscPressed(KeyEvent keyEvent) { textFieldCancelled(); } }, CompleterFactory.getLocationCompleter()); } /** * Re-enable this text field after a folder change was completed, cancelled by the user or has failed. * * <p>If the folder change was the result of the user manually entering a path in the location field and the folder * change failed or was cancelled, keeps the path intact and request focus on the text field so the user can modify it. */ private void folderChangeCompleted(boolean folderChangedSuccessfully) { if(folderChangedSuccessfully || !folderChangeInitiatedByLocationField) { // Set the location field's contents to the new current folder's path setText(folderPanel.getCurrentFolder().getAbsolutePath()); } // Re-enable this text field setEnabled(true); // If the location was entered and validated in the location field and the folder change failed or was cancelled... if(!folderChangedSuccessfully && folderChangeInitiatedByLocationField) { // Restore the text that was entered by the user setText(locationFieldTextSave); // Select the text to grab user's attention and make it easier to modify selectAll(); // Request focus (focus was on FileTable) requestFocus(); } // Reset field for next folder change folderChangeInitiatedByLocationField = false; } ////////////////////////////// // LocationListener methods // ////////////////////////////// public void locationChanging(LocationEvent e) { // Change the location field's text to the folder being changed, only if the folder change was not initiated // by the location field (to preserve the path entered by the user while the folder is being changed) if(!folderChangeInitiatedByLocationField) { FileURL folderURL = e.getFolderURL(); String locationText; if(folderURL.getScheme().equals(FileProtocols.FILE)) { // Do not display the URL's scheme & host for local files if (FileURL.LOCALHOST.equals(folderURL.getHost())) { locationText = folderURL.getPath(); // Under for OSes with 'root drives' (Windows, OS/2), remove the leading '/' character if(LocalFile.hasRootDrives()) locationText = PathUtils.removeLeadingSeparator(locationText, "/"); } // For network files with FILE scheme display the URL in UNC format else { locationText = "\\\\" + folderURL.getHost() + folderURL.getPath().replace('/', '\\'); if(!locationText.endsWith(UNCFile.SEPARATOR)) locationText += UNCFile.SEPARATOR; } } // Display the full URL for protocols other than 'file' else { locationText = folderURL.toString(false); } setText(locationText); } // Disable component until the folder has been changed, cancelled or failed. // Note: if the focus currently is in the location field, the focus manager will release focus and give it // to the next component (i.e. FileTable) setEnabled(false); } public void locationChanged(LocationEvent e) { // Re-enable component and change the location field's text to the new current folder's path folderChangeCompleted(true); } public void locationCancelled(LocationEvent e) { // Re-enable component and change the location field's text to the new current folder's path. // If the path was entered in the location field, keep the path to give the user a chance to correct it. folderChangeCompleted(false); } public void locationFailed(LocationEvent e) { // Re-enable component and change the location field's text to the new current folder's path. // If the path was entered in the location field, keep the path to give the user a chance to correct it. folderChangeCompleted(false); } /** * * @return true if a malformed url was entered, false otherwise. */ public boolean textFieldValidated() { String location = getText(); // Under Windows, trim the entered path for the following reason. // If a file 'A' (e.g. "C:\temp") exists and 'A ' (e.g. "C:\temp ") is requested, the java.io.File will resolve // (file.exists() will return true), but this file will be a strange one, listing bogus children files with // weird attributes (in the case of a directory). // Windows (or java.io.File under Windows) is somehow space-tolerant but then unable to deal with // those files properly. So if a path ends with space characters, we remove them to prevent those weirdnesses. // Note that Win32 doesn't allow creating files with trailing spaces (in Explorer, command prompt...), but // those files can still be manually crafted and thus exist on one's hard drive. // Mucommander should in theory be able to access such files without any problem but this hasn't been tested. if(OsFamily.WINDOWS.isCurrent() && location.indexOf(":\\")==1) { // Looks for trailing spaces and if some Matcher matcher = windowsTrailingSpacePattern.matcher(location); if(matcher.find()) location = location.substring(0, matcher.start()); } // Save the path that was entered in case the location change fails or is cancelled locationFieldTextSave = location; // Indicate we search for location corresponding to the given string. // it will be false we'll find one. boolean tryToInterpretEnteredString = true; // Look for a bookmark which name is the entered string (case insensitive) Bookmark b = BookmarkManager.getBookmark(location); if(b!=null) { // Change the current folder to the bookmark's location setText(location = b.getLocation()); tryToInterpretEnteredString = false; } // Look for a volume whose name is the entered string (case insensitive) AbstractFile volumes[] = LocalFile.getVolumes(); for(int i=0; tryToInterpretEnteredString && i<volumes.length; i++) { if(volumes[i].getName().equalsIgnoreCase(location)) { // Change the current folder to the volume folder setText(location = volumes[i].getAbsolutePath()); tryToInterpretEnteredString = false; } } // Todo: replace this by env:// filesystem ? // Look for a system variable which name is the entered string (case insensitive) if (tryToInterpretEnteredString && location.startsWith("$")) { String variableKey = location.substring(1); String variableValue = System.getenv(variableKey); if (variableValue != null) setText(location = variableValue); } // Remember that the folder change was initiated by the location field folderChangeInitiatedByLocationField = true; // Change folder return folderPanel.tryChangeCurrentFolder(location) == null; } public void textFieldCancelled() { setText(folderPanel.getCurrentFolder().getAbsolutePath()); transferFocus(); } /////////////////////////// // FocusListener methods // /////////////////////////// public void focusGained(FocusEvent e) { // Disable menu bar when this component has gained focus folderPanel.getMainFrame().getJMenuBar().setEnabled(false); } public void focusLost(FocusEvent e) { // // If we are not in the middle of a folder change, and focus has been // // lost then ensure location field's text is set to the current directory. // if (!folderPanel.isFolderChanging()) // locationField.setText(folderPanel.getCurrentFolder().getAbsolutePath()); // Enable menu bar when this component has lost focus folderPanel.getMainFrame().getJMenuBar().setEnabled(true); } // - Theme listening ------------------------------------------------------------- // ------------------------------------------------------------------------------- /** * Receives theme color changes notifications. */ public void colorChanged(ColorChangedEvent event) { switch(event.getColorId()) { case Theme.LOCATION_BAR_PROGRESS_COLOR: setProgressColor(event.getColor()); break; case Theme.LOCATION_BAR_FOREGROUND_COLOR: setDisabledTextColor(event.getColor()); setForeground(event.getColor()); break; case Theme.LOCATION_BAR_BACKGROUND_COLOR: setBackground(event.getColor()); break; case Theme.LOCATION_BAR_SELECTED_FOREGROUND_COLOR: setSelectedTextColor(event.getColor()); break; case Theme.LOCATION_BAR_SELECTED_BACKGROUND_COLOR: setSelectionColor(event.getColor()); break; } } /** * Receives theme font changes notifications. */ public void fontChanged(FontChangedEvent event) { if(event.getFontId() == Theme.LOCATION_BAR_FONT) setFont(event.getFont()); } }