/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* 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 org.jkiss.dbeaver.ui.editors.binary;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.*;
import org.jkiss.dbeaver.ui.editors.binary.dialogs.FindReplaceDialog;
import org.jkiss.dbeaver.ui.editors.binary.dialogs.GoToDialog;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Manager of the javahexeditor application, either in its standalone or Eclipse plugin version.
* Manages creation of widgets, and executes menu actions, like File->Save. Call createEditorPart()
* before any menu actions.
*
* @author Jordi
*/
public class HexManager {
private BinaryContent content = null;
private FontData fontData = null; // when null uses default font
private Font fontText = null;
private List<Listener> listOfStatusChangedListeners = null;
private List<SelectionListener> listOfLongListeners = null;
// visual controls
private FindReplaceDialog findDialog = null;
private GoToDialog goToDialog = null;
private HexEditControl hexEditControl = null;
private HexStatusLine statusLine = null;
private Composite textsParent = null;
private IMenuListener menuListener;
public HexEditControl getControl()
{
return hexEditControl;
}
/**
* Blocks the caller until the task is finished. Does not block the user interface thread.
*
* @param task independent of the user inteface thread (no widgets used)
*/
public static void blockUntilFinished(Runnable task)
{
Thread thread = new Thread(task);
thread.start();
Display display = Display.getCurrent();
final boolean[] pollerEnabled = {false};
while (thread.isAlive() && !display.isDisposed()) {
if (!display.readAndDispatch()) {
// awake periodically so it returns when task has finished
if (!pollerEnabled[0]) {
pollerEnabled[0] = true;
display.timerExec(300, new Runnable() {
@Override
public void run()
{
pollerEnabled[0] = false;
}
});
}
display.sleep();
}
}
}
/**
* Helper method to make a shell come closer to another shell
*
* @param fixedShell where movingShell will get closer to
* @param movingShell shell to be relocated
*/
public static void reduceDistance(Shell fixedShell, Shell movingShell)
{
Rectangle fixed = fixedShell.getBounds();
Rectangle moving = movingShell.getBounds();
int[] fixedLower = {fixed.x, fixed.y};
int[] fixedHigher = {fixed.x + fixed.width, fixed.y + fixed.height};
int[] movingLower = {moving.x, moving.y};
int[] movingSpan = {moving.width, moving.height};
for (int i = 0; i < 2; ++i) {
if (movingLower[i] + movingSpan[i] < fixedLower[i])
movingLower[i] = fixedLower[i] - movingSpan[i] + 10;
else if (fixedHigher[i] < movingLower[i])
movingLower[i] = fixedHigher[i] - 10;
}
movingShell.setLocation(movingLower[0], movingLower[1]);
}
/**
* Creates editor part of parent application. Can only be called once per Manager object.
*
* @param parent composite where the part will be drawn.
* @throws IllegalStateException when editor part exists already (method called twice or more)
* @throws NullPointerException if textsParent is null
*/
public Composite createEditorPart(Composite parent, int style)
{
if (hexEditControl != null) throw new IllegalStateException("Editor part exists already");
if (parent == null) throw new NullPointerException("Null parent");
textsParent = parent;
hexEditControl = new HexEditControl(textsParent, style);
hexEditControl.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e)
{
if (fontText != null && !fontText.isDisposed())
fontText.dispose();
}
});
if (fontData != null) {
fontText = new Font(Display.getCurrent(), fontData);
hexEditControl.setFont(fontText);
}
//hexEditControl.addLongSelectionListener(new ControlSelectionAdapter(ControlSelectionAdapter.UPDATE_POSITION_TEXT));
hexEditControl.addListener(SWT.Modify, new Listener() {
@Override
public void handleEvent(Event event)
{
if (statusLine != null)
statusLine.updateInsertModeText(hexEditControl == null || !hexEditControl.isOverwriteMode());
}
});
if (listOfStatusChangedListeners != null) {
for (Listener listOfStatusChangedListener : listOfStatusChangedListeners) {
hexEditControl.addListener(SWT.Modify, listOfStatusChangedListener);
}
listOfStatusChangedListeners = null;
}
if (listOfLongListeners != null) {
for (SelectionListener listOfLongListener : listOfLongListeners) {
hexEditControl.addLongSelectionListener(listOfLongListener);
}
listOfLongListeners = null;
}
{
// Context menu
MenuManager menuManager = new MenuManager();
menuManager.setRemoveAllWhenShown(true);
menuManager.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(IMenuManager manager)
{
if (menuListener != null) {
menuListener.menuAboutToShow(manager);
}
}
});
Menu contextMenu = menuManager.createContextMenu(hexEditControl.getHexText());
hexEditControl.getHexText().setMenu(contextMenu);
contextMenu = menuManager.createContextMenu(hexEditControl.getPreviewText());
hexEditControl.getPreviewText().setMenu(contextMenu);
//getSite().registerContextMenu(menuManager, this);
}
return hexEditControl;
}
void dispose()
{
if (content != null) {
content.dispose();
}
}
/**
* Add a listener to changes of the 'dirty', 'insert/overwrite', 'selection' and 'canUndo/canRedo'
* status
*
* @param aListener the listener to be notified of changes
*/
public void addListener(Listener aListener)
{
if (aListener == null) return;
if (hexEditControl == null) {
if (listOfStatusChangedListeners == null)
listOfStatusChangedListeners = new ArrayList<>();
listOfStatusChangedListeners.add(aListener);
} else {
hexEditControl.addListener(SWT.Modify, aListener);
}
}
/**
* Adds a long selection listener. Events sent to the listener have long start and end points.
*
* @param listener the listener
*/
public void addLongSelectionListener(SelectionListener listener)
{
if (listener == null)
throw new IllegalArgumentException();
if (hexEditControl == null) {
if (listOfLongListeners == null)
listOfLongListeners = new ArrayList<>();
listOfLongListeners.add(listener);
} else {
hexEditControl.addLongSelectionListener(listener);
}
}
/**
* Tells whether the last action can be redone
*
* @return true: an action ca be redone
*/
public boolean canRedo()
{
return hexEditControl != null && hexEditControl.canRedo();
}
/**
* Tells whether the last action can be undone
*
* @return true: an action ca be undone
*/
public boolean canUndo()
{
return hexEditControl != null && hexEditControl.canUndo();
}
/**
* Copies selection into clipboard
*/
public void doCopy()
{
if (hexEditControl == null) return;
hexEditControl.copy();
}
/**
* Cuts selection into clipboard
*/
public void doCut()
{
if (hexEditControl == null) return;
hexEditControl.cut();
}
/**
* While in insert mode, deletes the selection
*/
public void doDelete()
{
hexEditControl.deleteSelected();
}
/**
* Open find dialog
*/
public void doFind()
{
if (findDialog == null) {
findDialog = new FindReplaceDialog(textsParent.getShell());
}
findDialog.setTarget(hexEditControl);
findDialog.open();
}
/**
* Open 'go to' dialog
*/
public void doGoTo()
{
if (content.length() < 1L) return;
if (goToDialog == null)
goToDialog = new GoToDialog(textsParent.getShell());
long location = goToDialog.open(content.length() - 1L);
if (location >= 0L) {
long button = goToDialog.getButtonPressed();
if (button == 1)
hexEditControl.showMark(location);
else
hexEditControl.selectBlock(location, location);
}
}
/**
* Pastes clipboard into editor
*/
public void doPaste()
{
if (hexEditControl == null) return;
hexEditControl.paste();
}
/**
* Selects all file contents in editor
*/
public void doSelectAll()
{
if (hexEditControl == null) return;
hexEditControl.selectAll();
}
/**
* Redoes the last undone action
*/
public void doRedo()
{
hexEditControl.redo();
}
/**
* Undoes the last action
*/
public void doUndo()
{
hexEditControl.undo();
}
/**
* Get the binary content
*
* @return the content being edited
*/
public BinaryContent getContent()
{
return content;
}
/**
* @see HexEditControl#getSelection()
*/
public long[] getSelection()
{
if (hexEditControl == null) {
return new long[]{0L, 0L};
}
return hexEditControl.getSelection();
}
/**
* Get whether the content has been modified or not
*
* @return if changes have been performed
*/
public boolean isDirty()
{
return content != null && content.isDirty();
}
/**
* Tells whether the input is in overwrite or insert mode
*
* @return true: overwriting, false: inserting
*/
public boolean isOverwriteMode()
{
return hexEditControl == null || hexEditControl.isOverwriteMode();
}
/**
* Tells whether the input has text selected
*
* @return true: text is selected, false: no text selected
*/
public boolean isTextSelected()
{
if (hexEditControl == null) return false;
long[] selection = hexEditControl.getSelection();
return selection[0] != selection[1];
}
/**
* Open file for editing
*
* @param aFile the file to be edited
* @throws IOException when a file has no read access
*/
public void openFile(File aFile, String charset)
throws IOException
{
if (content != null) {
content.dispose();
content = null;
}
content = new BinaryContent(aFile); // throws IOException
hexEditControl.setCharset(charset);
hexEditControl.setContentProvider(content);
}
/**
* Causes the text areas to have the keyboard focus
*/
public void setFocus()
{
if (hexEditControl != null)
hexEditControl.setFocus();
if (statusLine != null) {
statusLine.updateInsertModeText(hexEditControl == null || !hexEditControl.isOverwriteMode());
if (hexEditControl != null) {
if (hexEditControl.isSelected())
statusLine.updateSelectionValueText(hexEditControl.getSelection(), hexEditControl.getActualValue());
else
statusLine.updatePositionValueText(hexEditControl.getCaretPos(), hexEditControl.getActualValue());
} else {
statusLine.updatePositionValueText(0L, (byte) 0);
}
}
}
/**
* Delegates to HexTexts.setSelection(start, end)
*
* @see HexEditControl#setSelection(long, long)
*/
public void setSelection(long start, long end)
{
hexEditControl.setSelection(start, end);
}
/**
* Set the editor text font.
*
* @param aFont new font to be used; should be a constant char width font. Use null to set to the
* default font.
*/
public void setTextFont(FontData aFont)
{
fontData = aFont;
if (HexEditControl.DEFAULT_FONT_DATA.equals(fontData))
fontData = null;
// dispose it after setting new one (StyledTextRenderer 3.448 bug in line 994)
Font fontToDispose = fontText;
fontText = null;
if (hexEditControl != null) {
if (fontData != null)
fontText = new Font(Display.getCurrent(), fontData);
hexEditControl.setFont(fontText);
}
if (fontToDispose != null && !fontToDispose.isDisposed())
fontToDispose.dispose();
}
public void setMenuListener(IMenuListener menuListener)
{
this.menuListener = menuListener;
}
}