blob: e923b62873549400f4abe103423197975c36f274 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.wm.impl.status;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.fileEditor.*;
import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileAdapter;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.VirtualFilePropertyEvent;
import com.intellij.openapi.vfs.encoding.ChangeFileEncodingAction;
import com.intellij.openapi.vfs.encoding.EncodingManager;
import com.intellij.openapi.vfs.encoding.EncodingManagerImpl;
import com.intellij.openapi.vfs.encoding.EncodingUtil;
import com.intellij.openapi.vfs.impl.BulkVirtualFileListenerAdapter;
import com.intellij.openapi.wm.CustomStatusBarWidget;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.openapi.wm.StatusBarWidget;
import com.intellij.ui.ClickListener;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.Alarm;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.nio.charset.Charset;
/**
* @author cdr
*/
public class EncodingPanel extends EditorBasedWidget implements StatusBarWidget.Multiframe, CustomStatusBarWidget {
private final TextPanel myComponent;
private boolean actionEnabled;
private final Alarm update;
private volatile Reference<Editor> myEditor = new WeakReference<Editor>(null); // store editor here to avoid expensive and EDT-only getSelectedEditor() retrievals
public EncodingPanel(@NotNull final Project project) {
super(project);
update = new Alarm(this);
myComponent = new TextPanel() {
@Override
protected void paintComponent(@NotNull final Graphics g) {
super.paintComponent(g);
if (actionEnabled && getText() != null) {
final Rectangle r = getBounds();
final Insets insets = getInsets();
AllIcons.Ide.Statusbar_arrows.paintIcon(this, g, r.width - insets.right - AllIcons.Ide.Statusbar_arrows.getIconWidth() - 2,
r.height / 2 - AllIcons.Ide.Statusbar_arrows.getIconHeight() / 2);
}
}
};
new ClickListener() {
@Override
public boolean onClick(MouseEvent e, int clickCount) {
update();
showPopup(e);
return true;
}
}.installOn(myComponent);
myComponent.setBorder(WidgetBorder.INSTANCE);
}
@Nullable("returns null if charset set cannot be determined from content")
private static Charset cachedCharsetFromContent(final VirtualFile virtualFile) {
if (virtualFile == null) return null;
final Document document = FileDocumentManager.getInstance().getDocument(virtualFile);
if (document == null) return null;
return EncodingManager.getInstance().getCachedCharsetFromContent(document);
}
@Override
public void selectionChanged(@NotNull FileEditorManagerEvent event) {
if (ApplicationManager.getApplication().isUnitTestMode()) return;
VirtualFile newFile = event.getNewFile();
fileChanged(newFile);
}
private void fileChanged(VirtualFile newFile) {
FileEditor fileEditor = newFile == null ? null : FileEditorManager.getInstance(getProject()).getSelectedEditor(newFile);
Editor editor = fileEditor instanceof TextEditor ? ((TextEditor)fileEditor).getEditor() : null;
myEditor = new WeakReference<Editor>(editor);
update();
}
@Override
public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) {
fileChanged(file);
}
@Override
public StatusBarWidget copy() {
return new EncodingPanel(getProject());
}
@Override
@NotNull
public String ID() {
return "Encoding";
}
@Override
public WidgetPresentation getPresentation(@NotNull PlatformType type) {
return null;
}
@Override
public void install(@NotNull StatusBar statusBar) {
super.install(statusBar);
// should update to reflect encoding-from-content
EncodingManager.getInstance().addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(EncodingManagerImpl.PROP_CACHED_ENCODING_CHANGED)) {
Document document = evt.getSource() instanceof Document ? (Document)evt.getSource() : null;
updateForDocument(document);
}
}
}, this);
ApplicationManager.getApplication().getMessageBus().connect(this).subscribe(VirtualFileManager.VFS_CHANGES, new BulkVirtualFileListenerAdapter(new VirtualFileAdapter() {
@Override
public void propertyChanged(VirtualFilePropertyEvent event) {
if (VirtualFile.PROP_ENCODING.equals(event.getPropertyName())) {
updateForFile(event.getFile());
}
}
}));
EditorFactory.getInstance().getEventMulticaster().addDocumentListener(new DocumentAdapter() {
@Override
public void documentChanged(DocumentEvent e) {
Document document = e.getDocument();
updateForDocument(document);
}
}, this);
}
private void updateForDocument(@Nullable("null means update anyway") Document document) {
Editor selectedEditor = myEditor.get();
if (document != null && (selectedEditor == null || selectedEditor.getDocument() != document)) return;
update();
}
private void updateForFile(@Nullable("null means update anyway") VirtualFile file) {
if (file == null) {
update();
}
else {
updateForDocument(FileDocumentManager.getInstance().getCachedDocument(file));
}
}
private void showPopup(@NotNull MouseEvent e) {
if (!actionEnabled) {
return;
}
DataContext dataContext = getContext();
ListPopup popup = new ChangeFileEncodingAction().createPopup(dataContext);
if (popup != null) {
Dimension dimension = popup.getContent().getPreferredSize();
Point at = new Point(0, -dimension.height);
popup.show(new RelativePoint(e.getComponent(), at));
Disposer.register(this, popup); // destroy popup on unexpected project close
}
}
@NotNull
private DataContext getContext() {
Editor editor = getEditor();
DataContext parent = DataManager.getInstance().getDataContext((Component)myStatusBar);
return SimpleDataContext.getSimpleContext(CommonDataKeys.VIRTUAL_FILE.getName(), getSelectedFile(),
SimpleDataContext.getSimpleContext(CommonDataKeys.PROJECT.getName(), getProject(),
SimpleDataContext.getSimpleContext(PlatformDataKeys.CONTEXT_COMPONENT.getName(), editor == null ? null : editor.getComponent(), parent)
));
}
private void update() {
if (update.isDisposed()) return;
update.cancelAllRequests();
update.addRequest(new Runnable() {
@Override
public void run() {
if (isDisposed()) return;
VirtualFile file = getSelectedFile();
actionEnabled = false;
String charsetName = null;
Pair<Charset, String> check = null;
if (file != null) {
check = EncodingUtil.checkSomeActionEnabled(file);
Charset charset = null;
if (LoadTextUtil.wasCharsetDetectedFromBytes(file) != null) {
charset = cachedCharsetFromContent(file);
}
if (charset == null) {
charset = file.getCharset();
}
actionEnabled = check == null || check.second == null;
if (!actionEnabled) {
charset = check.first;
}
if (charset != null) {
charsetName = charset.displayName();
}
}
if (charsetName == null) {
charsetName = "n/a";
}
String toolTipText;
if (actionEnabled) {
toolTipText = String.format(
"File Encoding%n%s", charsetName);
myComponent.setForeground(UIUtil.getActiveTextColor());
myComponent.setTextAlignment(Component.LEFT_ALIGNMENT);
}
else {
String failReason = check == null ? "" : check.second;
toolTipText = String.format("File encoding is disabled%n%s",
failReason);
myComponent.setForeground(UIUtil.getInactiveTextColor());
myComponent.setTextAlignment(Component.CENTER_ALIGNMENT);
}
myComponent.setToolTipText(toolTipText);
myComponent.setText(charsetName);
if (myStatusBar != null) {
myStatusBar.updateWidget(ID());
}
}
}, 200, ModalityState.any());
}
@Override
public JComponent getComponent() {
return myComponent;
}
}