package org.syncany.gui.history;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.syncany.config.GuiEventBus;
import org.syncany.config.Logging;
import org.syncany.database.PartialFileHistory.FileHistoryId;
import org.syncany.gui.Dialog;
import org.syncany.gui.Panel;
import org.syncany.gui.util.DesktopUtil;
import org.syncany.gui.util.I18n;
import org.syncany.gui.util.WidgetDecorator;
import org.syncany.operations.daemon.messages.LsFolderRequest;
/**
* The history dialog allows the user to browse old database versions and file versions
* in a tree view, a log view, and restore certain files via a detail view.
*
* <p>The dialog itself knows only the {@link MainPanel} and the {@link DetailPanel} and
* can switch between them. The main logic is implemented in the {@link MainPanel}. The dialog
* and the panels implement a not-quite-textbook version of MVC, with the {@link HistoryModel}
* being the model, and the panels being the views and the controllers.
*
* @author Philipp C. Heckel <philipp.heckel@gmail.com>
*/
public class HistoryDialog extends Dialog {
private static final Logger logger = Logger.getLogger(HistoryDialog.class.getSimpleName());
private Shell windowShell;
private Composite stackComposite;
private StackLayout stackLayout;
private Panel currentPanel;
private MainPanel mainPanel;
private DetailPanel detailPanel;
private HistoryModel model;
private GuiEventBus eventBus;
public HistoryDialog() {
this.windowShell = null;
this.stackComposite = null;
this.stackLayout = null;
this.currentPanel = null;
this.mainPanel = null;
this.detailPanel = null;
this.model = new HistoryModel();
this.eventBus = GuiEventBus.getInstance();
this.eventBus.register(this);
}
/**
* Main method to run/test the history dialog without connecting
* to the daemon. This only partially works, because no actual data
* is retrieved from the daemon.
*/
public static void main(String[] args) {
Logging.init();
String intlPackage = I18n.class.getPackage().getName().replace(".", "/");
I18n.registerBundleName(intlPackage + "/i18n/messages");
HistoryDialog dialog = new HistoryDialog();
dialog.open();
}
/**
* Open dialog, switch to main panel and enter the
* dispatch loop. This method blocks until the dialog
* is closed.
*/
public void open() {
// Create controls
createContents();
createStackComposite();
createPanels();
showMainPanel();
// Open shell
DesktopUtil.centerOnScreen(windowShell);
windowShell.open();
windowShell.layout();
// Dispatch loop
Display display = Display.getDefault();
while (!windowShell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
windowShell.dispose();
}
/**
* Return window shell, i.e. the dialog window.
*/
public Shell getWindowShell() {
return windowShell;
}
/**
* Switch to the detail panel, and send {@link LsFolderRequest} (which will
* update the detail panel, once the response is returned).
*/
public void showDetailsPanel(String root, FileHistoryId fileHistoryId) {
logger.log(Level.INFO, "History dialog: Sending LsRequest for history ID " + fileHistoryId + " (root " + root + "); and switching to detail view ...");
detailPanel.resetPanel(root, fileHistoryId);
setCurrentPanel(detailPanel);
}
/**
* Switch to main panel
*/
public void showMainPanel() {
logger.log(Level.INFO, "History dialog: Switching to main view ...");
setCurrentPanel(mainPanel);
}
private void createContents() {
logger.log(Level.INFO, "History dialog: Creating dialog contents ...");
GridLayout shellGridLayout = new GridLayout(1, false);
shellGridLayout.marginTop = 0;
shellGridLayout.marginLeft = 0;
shellGridLayout.marginHeight = 0;
shellGridLayout.marginWidth = 0;
shellGridLayout.horizontalSpacing = 0;
shellGridLayout.verticalSpacing = 0;
windowShell = new Shell(Display.getDefault(), SWT.SHELL_TRIM | SWT.DOUBLE_BUFFERED);
windowShell.setToolTipText("");
windowShell.setBackground(WidgetDecorator.COLOR_WIDGET);
windowShell.setSize(900, 560);
windowShell.setText(I18n.getText("org.syncany.gui.history.HistoryDialog.title"));
windowShell.setLayout(shellGridLayout);
windowShell.addListener(SWT.Close, new Listener() {
public void handleEvent(Event event) {
dispose();
}
});
}
private void createStackComposite() {
logger.log(Level.INFO, "History dialog: Creating stack composite (for main/detail panel) ...");
stackLayout = new StackLayout();
stackLayout.marginHeight = 0;
stackLayout.marginWidth = 0;
GridData stackCompositeGridData = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1);
stackCompositeGridData.minimumWidth = 500;
stackComposite = new Composite(windowShell, SWT.DOUBLE_BUFFERED);
stackComposite.setLayout(stackLayout);
stackComposite.setLayoutData(stackCompositeGridData);
}
private void createPanels() {
logger.log(Level.INFO, "History dialog: Creating main and detail panel ...");
mainPanel = new MainPanel(stackComposite, SWT.NONE, model, this);
detailPanel = new DetailPanel(stackComposite, SWT.NONE, model, this);
}
private void setCurrentPanel(final Panel newPanel) {
logger.log(Level.INFO, "History dialog: Setting current panel to " + newPanel.getClass().getSimpleName() + " ...");
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
currentPanel = newPanel;
stackLayout.topControl = currentPanel;
stackComposite.layout();
currentPanel.setFocus();
}
});
}
private void dispose() {
logger.log(Level.INFO, "History dialog: Disposing dialog ...");
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
eventBus.unregister(HistoryDialog.this);
if (!mainPanel.isDisposed()) {
mainPanel.dispose();
}
if (!detailPanel.isDisposed()) {
detailPanel.dispose();
}
if (!windowShell.isDisposed()) {
windowShell.dispose();
}
}
});
}
}