/*
 *  Copyright (C) 2006-2019  Ronald Blankendaal
 *
 *  This program 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
package org.dbgl.gui.dialog;

import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.xml.xpath.XPathExpressionException;
import org.apache.commons.lang3.StringUtils;
import org.dbgl.connect.Messaging;
import org.dbgl.constants.Constants;
import org.dbgl.gui.GeneralPurposeDialogs;
import org.dbgl.gui.abstractdialog.SizeControlledDialog;
import org.dbgl.gui.controls.ProfilesList;
import org.dbgl.gui.controls.ProfilesList.ProfilesListItem;
import org.dbgl.gui.controls.ProfilesList.ProfilesListType;
import org.dbgl.gui.dialog.wizard.AddGameWizardDialog;
import org.dbgl.gui.dialog.wizard.DFendImportDialog;
import org.dbgl.gui.dialog.wizard.ExportDialog;
import org.dbgl.gui.dialog.wizard.ImportDialog;
import org.dbgl.gui.dialog.wizard.MigrateDialog;
import org.dbgl.gui.interfaces.ReOrderable;
import org.dbgl.model.OrderingVector;
import org.dbgl.model.SearchResult;
import org.dbgl.model.SearchResult.ResultType;
import org.dbgl.model.ThumbInfo;
import org.dbgl.model.ViewType;
import org.dbgl.model.aggregate.DosboxVersion;
import org.dbgl.model.aggregate.Profile;
import org.dbgl.model.aggregate.Template;
import org.dbgl.model.entity.Filter;
import org.dbgl.model.repository.DosboxVersionRepository;
import org.dbgl.model.repository.FilterRepository;
import org.dbgl.model.repository.ProfileRepository;
import org.dbgl.model.repository.TemplateRepository;
import org.dbgl.model.repository.TitledEntityRepository;
import org.dbgl.service.DatabaseService;
import org.dbgl.service.FileLocationService;
import org.dbgl.service.ImageService;
import org.dbgl.service.ImportExportTemplatesService;
import org.dbgl.service.TextService;
import org.dbgl.util.ExecuteUtils;
import org.dbgl.util.ExecuteUtils.ProfileRunMode;
import org.dbgl.util.FilesUtils;
import org.dbgl.util.SystemUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabFolder2Adapter;
import org.eclipse.swt.custom.CTabFolderEvent;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MenuAdapter;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.xml.sax.SAXException;


public class MainWindow extends SizeControlledDialog<Object> implements ReOrderable {

	private static final String[] ICOS_DBGL = {"ico/016.png", "ico/024.png", "ico/032.png", "ico/048.png", "ico/064.png", "ico/128.png", "ico/256.png"};

	private String filterClause_;
	private OrderingVector orderingVector_;

	private List<Profile> profilesList_;
	private List<DosboxVersion> dbversionsList_;
	private List<Template> templatesList_;
	private List<Filter> filtersList_;

	private Messaging mess_;

	public static String[] columnNames_;
	private int[] columnIds_;

	private ToolItem setupToolItem_, viewSelector_;
	private CTabFolder filterFolder_;
	private ProfilesList profileTable_;
	private Text notesField_;
	private Link[] link_;
	private SashForm sashInfoForm_;
	private ScrolledComposite scrolledCompositeForThumbs_;
	private Composite thumbsComposite_;
	private int thumbHeight_;
	private File currentThumbFile_ = null;
	private Menu thumbMenu_;
	private Table dbversionTable_, templateTable_;

	public MainWindow() {
		super(new Shell(), SWT.PRIMARY_MODAL, StringUtils.EMPTY);
	}

	@Override
	protected String getDialogTitle() {
		return text_.get("main.title", new Object[] {Constants.PROGRAM_VERSION});
	}

	@Override
	protected boolean prepare() {
		if (!super.prepare())
			return false;

		try {
			dbversionsList_ = new DosboxVersionRepository().listAll();

			ProfileRepository profileRepo = new ProfileRepository();

			List<Profile> invalidProfiles = profileRepo.listInvalidProfiles(dbversionsList_);
			if (invalidProfiles.size() > 0) {
				String titles = invalidProfiles.stream().map(x -> x.getTitle()).collect(Collectors.joining(", "));
				if (GeneralPurposeDialogs.confirmMessage(new Shell(), text_.get("dialog.main.confirm.removeinvalidprofiles", new Object[] {invalidProfiles.size(), titles}))) {
					for (Profile prof: invalidProfiles)
						profileRepo.remove(prof, false, false, false);
				}
			}

			templatesList_ = new TemplateRepository().listAll(dbversionsList_);

			orderingVector_ = new OrderingVector(settings_.getIntValues("gui", "sortcolumn"), settings_.getBooleanValues("gui", "sortascending"));

			filtersList_ = new FilterRepository().listAll();
			filtersList_.add(0, new Filter(text_.get("dialog.main.allprofiles"), null));
			filterClause_ = filtersList_.get(settings_.getIntValue("gui", "filtertab")).getFilter();

			profilesList_ = profileRepo.list(orderingVector_.toClause(), filterClause_, dbversionsList_);
		} catch (Exception e) {
			GeneralPurposeDialogs.warningMessage(new Shell(), e);
			return false;
		}

		if (settings_.getBooleanValue("communication", "port_enabled")) {
			mess_ = new Messaging(settings_.getIntValue("communication", "port"), this, text_);
			mess_.start();
		}

		return true;
	}

	@Override
	protected void onShellCreated() {
		createAppMenuBar();

		shell_.setLayout(new FillLayout());
		shell_.setImages(ImageService.getResourceImages(display_, ICOS_DBGL));

		TabFolder tabFolder = new TabFolder(shell_, SWT.NONE);
		createProfilesTab(tabFolder);
		createDosboxVersionsTab(tabFolder);
		createTemplatesTab(tabFolder);

		shell_.addListener(SWT.Activate, new Listener() {
			public void handleEvent(Event arg0) {
				if (tabFolder.getSelectionIndex() == 0) {
					profileTable_.setFocus();
					displayProfileInformation(true);
				}
			}
		});

		// init values
		profilesList_.forEach(x -> addProfileToTable(x));
		dbversionsList_.forEach(x -> addDosboxVersionToTable(x));
		templatesList_.forEach(x -> addTemplateToTable(x));

		if (DatabaseService.getInstance().isInitializedNewDatabase()) {
			doLocateDosbox(false);
			if (dbversionsList_.size() > 0)
				doImportDefaultTemplates(false);
		}
	}

	@Override
	protected void onShellOpened() {
		super.onShellOpened();

		profileTable_.setFocus();
		profileTable_.setSelection(settings_.getIntValue("gui", "selectedprofile"));
		displayProfileInformation(false);
	}

	@Override
	protected void onClose() {
		super.onClose();

		ImageService.clearCache();
		display_.dispose();

		if (mess_ != null)
			mess_.close();

		try {
			settings_.save();
		} catch (IOException e) {
			GeneralPurposeDialogs.warningMessage(shell_, e);
		}
		try {
			DatabaseService.getInstance().shutdown();
		} catch (SQLException e) {
			// nothing we can do
		}
	}

	private void initColumnIds() {
		columnNames_ = new String[Constants.RO_COLUMN_NAMES + Constants.EDIT_COLUMN_NAMES + 8];

		int c = 0;
		columnNames_[c++] = text_.get("dialog.main.profiles.column.title");
		columnNames_[c++] = text_.get("dialog.main.profiles.column.setup");
		columnNames_[c++] = text_.get("dialog.main.profiles.column.developer");
		columnNames_[c++] = text_.get("dialog.main.profiles.column.publisher");
		columnNames_[c++] = text_.get("dialog.main.profiles.column.genre");
		columnNames_[c++] = text_.get("dialog.main.profiles.column.year");
		columnNames_[c++] = text_.get("dialog.main.profiles.column.status");
		columnNames_[c++] = text_.get("dialog.main.profiles.column.favorite");
		columnNames_[c++] = text_.get("dialog.main.profiles.column.id");
		columnNames_[c++] = text_.get("dialog.main.profiles.column.dosboxversionid");
		for (int i = 0; i < Constants.EDIT_COLUMN_NAMES_1; i++)
			columnNames_[c++] = settings_.getValue("gui", "custom" + (i + 1));
		columnNames_[c++] = text_.get("dialog.main.profiles.column.screenshot");
		columnNames_[c++] = text_.get("dialog.main.profiles.column.dosboxversiontitle");
		columnNames_[c++] = text_.get("dialog.main.generic.column.created");
		columnNames_[c++] = text_.get("dialog.main.generic.column.lastmodify");
		columnNames_[c++] = text_.get("dialog.main.generic.column.lastrun");
		columnNames_[c++] = text_.get("dialog.main.generic.column.lastsetup");
		columnNames_[c++] = text_.get("dialog.main.generic.column.runs");
		columnNames_[c++] = text_.get("dialog.main.generic.column.setups");
		for (int i = 0; i < Constants.EDIT_COLUMN_NAMES_2; i++)
			columnNames_[c++] = settings_.getValue("gui", "custom" + (i + 1 + Constants.EDIT_COLUMN_NAMES_1));

		columnIds_ = IntStream.range(0, c).filter(x -> settings_.getBooleanValue("gui", "column" + (x + 1) + "visible")).toArray();
	}

	private void createAppMenuBar() {
		Menu appMenuBar = display_.getMenuBar();
		if (appMenuBar == null) {
			appMenuBar = new Menu(shell_, SWT.BAR);
			shell_.setMenuBar(appMenuBar);
		}

		SelectionAdapter settingsAdapter = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doOpenSettingsDialog();
			}
		};
		SelectionAdapter logAdapter = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				new LogDialog(shell_).open();
			}
		};
		SelectionAdapter cleanupAdapter = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				try {
					if (GeneralPurposeDialogs.confirmMessage(shell_, text_.get("dialog.main.confirm.databasecleanup"))) {
						int itemsRemoved = new TitledEntityRepository().cleanup();
						GeneralPurposeDialogs.infoMessage(shell_, text_.get("dialog.main.notice.databasecleanupok", new Object[] {itemsRemoved}));
					}
				} catch (SQLException e) {
					GeneralPurposeDialogs.warningMessage(shell_, e);
				}
			}
		};
		SelectionAdapter openAboutAdapter = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				new AboutDialog(shell_).open();
			}
		};
		SelectionAdapter quitAdapter = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				shell_.close();
			}
		};

		Menu systemMenu = display_.getSystemMenu();
		if (systemMenu != null) {
			getMenuItemById(systemMenu, SWT.ID_PREFERENCES).addSelectionListener(settingsAdapter);
			getMenuItemById(systemMenu, SWT.ID_ABOUT).addSelectionListener(openAboutAdapter);

			int prefsIndex = systemMenu.indexOf(getMenuItemById(systemMenu, SWT.ID_PREFERENCES));
			createMenuItem(systemMenu, prefsIndex + 1, text_.get("dialog.main.menu.log"), logAdapter);
			createMenuItem(systemMenu, prefsIndex + 2, text_.get("dialog.main.menu.databasecleanup"), cleanupAdapter);
		} else {
			Menu fileMenu = createMenu(appMenuBar, text_.get("dialog.main.menu.file"));
			createMenuItem(fileMenu, text_.get("dialog.main.menu.adjustsettings"), ImageService.IMG_SETTINGS, settingsAdapter);
			createMenuItem(fileMenu, text_.get("dialog.main.menu.log"), ImageService.IMG_LOG, logAdapter);
			createMenuItem(fileMenu, text_.get("dialog.main.menu.databasecleanup"), ImageService.IMG_CLEAN, cleanupAdapter);
			createMenuItem(fileMenu, text_.get("dialog.main.menu.exit"), ImageService.IMG_EXIT, quitAdapter);
		}

		Menu profilesMenu = createMenu(appMenuBar, text_.get("dialog.main.menu.profiles"));
		createMenuItem(profilesMenu, SWT.NONE, text_.get("dialog.main.menu.import"), ImageService.IMG_IMPORT, SWT.MOD1 | SWT.MOD3 | 'I', new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doImportProfiles(true);
			}
		});
		createMenuItem(profilesMenu, text_.get("dialog.main.menu.importprofile"), ImageService.IMG_IMPORT, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doImportProfiles(false);
			}
		});
		createMenuItem(profilesMenu, text_.get("dialog.main.menu.importdfendprofiles"), ImageService.IMG_DFEND, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				DosboxVersion defaultDbversion = requireDefaultDBVersion();
				if (defaultDbversion == null)
					return;

				if (settings_.getIntValue("profiledefaults", "confpath") == 1)
					GeneralPurposeDialogs.infoMessage(shell_, text_.get("dialog.main.notice.dfendimportconflocation", new Object[] {SettingsDialog.confLocations[0]}));

				if (new DFendImportDialog(shell_, defaultDbversion).open() != null) {
					updateProfilesList(getSelectedProfileIds());
					displayProfileInformation(true);
				}
			}
		});
		createSeparatorMenuItem(profilesMenu);
		createMenuItem(profilesMenu, SWT.NONE, text_.get("dialog.main.menu.export"), ImageService.IMG_TABLEEXPORT, SWT.MOD1 | SWT.MOD3 | 'E', new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				if (profileTable_.getSelectionIndex() != -1) {
					List<Profile> loadedProfiles = new ProfileLoader(shell_, getSelectedProfiles()).open();
					if (loadedProfiles != null)
						new ExportDialog(shell_, loadedProfiles).open();
				}
			}
		});
		createMenuItem(profilesMenu, text_.get("dialog.main.menu.exportprofileslist"), ImageService.IMG_TABLEEXPORT, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				new ExportListDialog(shell_, profilesList_).open();
			}
		});
		createSeparatorMenuItem(profilesMenu);
		createMenuItem(profilesMenu, text_.get("dialog.main.menu.migrateprofiles"), ImageService.IMG_MIGRATE, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doMigrate();
			}
		});

		Menu dbversionsMenu = createMenu(appMenuBar, text_.get("dialog.main.menu.dosboxversions"));
		createMenuItem(dbversionsMenu, text_.get("dialog.main.menu.locatedosbox"), ImageService.IMG_ZOOM, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doLocateDosbox(true);
			}
		});

		Menu templatesMenu = createMenu(appMenuBar, text_.get("dialog.main.menu.templates"));
		if (SystemUtils.IS_WINDOWS && SystemUtils.IS_LINUX) // comment this line out to be able to export templates
			createMenuItem(templatesMenu, text_.get("dialog.main.menu.exporttemplates"), ImageService.IMG_TABLEEXPORT, new SelectionAdapter() {
				public void widgetSelected(SelectionEvent event) {
					try {
						String warnings = ImportExportTemplatesService.export(templatesList_);
						if (StringUtils.isNotEmpty(warnings))
							GeneralPurposeDialogs.warningMessage(shell_, warnings);
					} catch (Exception e) {
						GeneralPurposeDialogs.fatalMessage(shell_, e.toString(), e);
					}
				}
			});
		createMenuItem(templatesMenu, text_.get("dialog.main.menu.importdefaulttemplates"), ImageService.IMG_IMPORT, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doImportDefaultTemplates(true);
			}
		});

		Menu filterMenu = createMenu(appMenuBar, text_.get("dialog.main.menu.filter"));
		createMenuItem(filterMenu, text_.get("dialog.main.menu.addfilter"), ImageService.IMG_FILTER, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doAddFilter();
			}
		});
		createMenuItem(filterMenu, text_.get("dialog.main.menu.editfilter"), ImageService.IMG_EDITFILTER, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doEditFilter();
			}
		});

		if (systemMenu == null) {
			Menu helpMenu = createMenu(appMenuBar, text_.get("dialog.main.menu.help"));
			createMenuItem(helpMenu, text_.get("dialog.main.menu.about"), ImageService.IMG_ABOUT, openAboutAdapter);
		}
	}

	private static MenuItem getMenuItemById(Menu menu, int id) {
		for (MenuItem item: menu.getItems())
			if (item.getID() == id)
				return item;
		return null;
	}

	private void createProfilesTab(TabFolder tabFolder) {
		Composite composite = createTabWithInnerComposite(tabFolder, text_.get("dialog.main.profiles"), 1);

		RowLayout rowLayout = new RowLayout();
		rowLayout.spacing = 20;
		rowLayout.marginTop = rowLayout.marginBottom = rowLayout.marginLeft = 0;
		Composite toolbarComposite = createComposite(composite, new GridData(SWT.LEFT, SWT.TOP, false, false), rowLayout);

		ToolBar toolBar = new ToolBar(toolbarComposite, SWT.HORIZONTAL);
		createImageToolItem(toolBar, text_.get("dialog.main.addprofile"), ImageService.IMG_TB_NEW, addProfAdapter);
		createImageToolItem(toolBar, text_.get("dialog.main.editprofile"), ImageService.IMG_TB_EDIT, editProfAdapter);
		createImageToolItem(toolBar, text_.get("dialog.main.removeprofile"), ImageService.IMG_TB_DELETE, removeProfAdapter);
		createImageToolItem(toolBar, text_.get("dialog.main.runprofile"), ImageService.IMG_TB_RUN, runProfAdapter);
		setupToolItem_ = createImageToolItem(toolBar, text_.get("dialog.main.runprofilesetup"), ImageService.IMG_TB_SETUP, setupProfAdapter);
		createSeparatorToolItem(toolBar, 40);
		createImageToolItem(toolBar, text_.get("dialog.main.addwizard"), ImageService.IMG_TB_ADDGAMEWIZARD, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				updateWithAddedProfile(new AddGameWizardDialog(shell_).open());
			}
		});

		initColumnIds();

		ViewType[] views = new ViewType[] {new ViewType(ProfilesListType.TABLE.toString(), ImageService.IMG_TABLE, text_.get("dialog.main.profiles.viewtype.table")),
				new ViewType(ProfilesListType.SMALL_TILES.toString(), ImageService.IMG_TILES_SMALL, text_.get("dialog.main.profiles.viewtype.smalltiles")),
				new ViewType(ProfilesListType.MEDIUM_TILES.toString(), ImageService.IMG_TILES_MEDIUM, text_.get("dialog.main.profiles.viewtype.mediumtiles")),
				new ViewType(ProfilesListType.LARGE_TILES.toString(), ImageService.IMG_TILES_LARGE, text_.get("dialog.main.profiles.viewtype.largetiles")),
				new ViewType(ProfilesListType.SMALL_BOXES.toString(), ImageService.IMG_BOXES_SMALL, text_.get("dialog.main.profiles.viewtype.smallboxes")),
				new ViewType(ProfilesListType.MEDIUM_BOXES.toString(), ImageService.IMG_BOXES_MEDIUM, text_.get("dialog.main.profiles.viewtype.mediumboxes")),
				new ViewType(ProfilesListType.LARGE_BOXES.toString(), ImageService.IMG_BOXES_LARGE, text_.get("dialog.main.profiles.viewtype.largeboxes"))};
		ViewType currentViewType = settings_.getValue("gui", "viewstyle").equalsIgnoreCase(views[0].getName()) ? views[0]: views[1];

		Menu viewMenu = new Menu(shell_, SWT.POP_UP);
		for (ViewType view: views) {
			createMenuItem(viewMenu, SWT.PUSH, view.getDisplayName(), view.getImage(), 1, new SelectionAdapter() {
				public void widgetSelected(SelectionEvent event) {
					MenuItem menuItem = (MenuItem)event.widget;
					ViewType newViewType = views[menuItem.getParent().indexOf(menuItem)];
					if (!settings_.getValue("gui", "viewstyle").equalsIgnoreCase(newViewType.getName())) {
						toggleProfileViewType(newViewType);
					}
				}
			});
		}

		ToolBar toolBarRight = new ToolBar(toolbarComposite, SWT.HORIZONTAL);
		viewSelector_ = createImageToolItem(toolBarRight, SWT.DROP_DOWN, ImageService.getResourceImage(shell_.getDisplay(), currentViewType.getImage()), currentViewType.getDisplayName(),
			new SelectionAdapter() {
				public void widgetSelected(SelectionEvent event) {
					if (event.detail == SWT.ARROW) {
						Rectangle rect = viewSelector_.getBounds();
						Point pt = new Point(rect.x, rect.y + rect.height);
						pt = toolBarRight.toDisplay(pt);
						viewMenu.setLocation(pt.x, pt.y);
						viewMenu.setVisible(true);
					} else {
						for (int i = 0; i < views.length; i++) {
							if (settings_.getValue("gui", "viewstyle").equalsIgnoreCase(views[i].getName())) {
								toggleProfileViewType(views[(i + 1) % views.length]);
								break;
							}
						}
					}
				}
			});
		createSeparatorToolItem(toolBarRight, 4);
		ToolItem displayScreenshots = createImageToolItem(toolBarRight, SWT.CHECK, ImageService.getResourceImage(shell_.getDisplay(), ImageService.IMG_SCREENSHOTS),
			text_.get("dialog.main.profiles.togglebutton.screenshots"), new SelectionAdapter() {
				public void widgetSelected(SelectionEvent event) {
					ToolItem item = (ToolItem)event.widget;
					((GridData)scrolledCompositeForThumbs_.getLayoutData()).exclude = !item.getSelection();
					scrolledCompositeForThumbs_.getParent().layout();
					settings_.setBooleanValue("gui", "screenshotsvisible", item.getSelection());
					displayProfileInformation(false);
				}
			});
		displayScreenshots.setSelection(settings_.getBooleanValue("gui", "screenshotsvisible"));
		createSeparatorToolItem(toolBarRight, 4);
		ToolItem displayNotes = createImageToolItem(toolBarRight, SWT.CHECK, ImageService.getResourceImage(shell_.getDisplay(), ImageService.IMG_NOTES),
			text_.get("dialog.main.profiles.togglebutton.notes"), new SelectionAdapter() {
				public void widgetSelected(SelectionEvent event) {
					ToolItem item = (ToolItem)event.widget;
					sashInfoForm_.setMaximizedControl(item.getSelection() ? null: filterFolder_);
					settings_.setBooleanValue("gui", "notesvisible", item.getSelection());
					displayProfileInformation(false);
				}
			});
		displayNotes.setSelection(settings_.getBooleanValue("gui", "notesvisible"));

		sashInfoForm_ = new SashForm(composite, SWT.NONE);
		sashInfoForm_.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		filterFolder_ = new CTabFolder(sashInfoForm_, SWT.BORDER);
		filterFolder_.setUnselectedCloseVisible(true);
		filterFolder_.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				updateProfilesAfterTabAction();
			}
		});
		filterFolder_.addCTabFolder2Listener(new CTabFolder2Adapter() {
			public void close(CTabFolderEvent event) {
				if (GeneralPurposeDialogs.confirmMessage(shell_, text_.get("dialog.main.confirm.removefilter", new Object[] {((CTabItem)event.item).getText().trim()}))) {
					boolean currentTabToBeClosed = (event.item == filterFolder_.getSelection());
					try {
						FilterRepository filterRepo = new FilterRepository();
						filterRepo.remove(FilterRepository.findById(filtersList_, (Integer)event.item.getData()));
						filtersList_ = filterRepo.listAll();
						filtersList_.add(0, new Filter(text_.get("dialog.main.allprofiles"), null));
					} catch (SQLException e) {
						GeneralPurposeDialogs.warningMessage(shell_, e);
					}
					if (currentTabToBeClosed) {
						filterFolder_.setSelection(0);
						updateProfilesAfterTabAction();
					}
				} else {
					event.doit = false;
				}
			}
		});
		filterFolder_.addMouseListener(new MouseAdapter() {
			public void mouseDoubleClick(MouseEvent event) {
				doEditFilter();
			}
		});
		filtersList_.forEach(x -> addFilterTab(x));
		filterFolder_.setSelection(settings_.getIntValue("gui", "filtertab"));
		filterFolder_.getSelection().setToolTipText(text_.get("dialog.filter.notice.results", new Object[] {profilesList_.size()}));

		constructProfilesList();

		Composite informationGroup = createInnerComposite(sashInfoForm_, null, 1);
		informationGroup.addControlListener(new ControlAdapter() {
			public void controlResized(ControlEvent event) {
				settings_.setIntValues("gui", "sashweights", sashInfoForm_.getWeights());
			}
		});
		notesField_ = createTextarea(informationGroup, true, true, 1, 1, null);
		notesField_.setFont(stringToFont(display_, settings_.getValues("gui", "notesfont"), notesField_.getFont()));
		GridLayout linksGridLayout = new GridLayout();
		linksGridLayout.marginWidth = 0;
		linksGridLayout.marginHeight = 1;
		linksGridLayout.verticalSpacing = 2;
		Composite linksComposite = createComposite(informationGroup, new GridData(SWT.FILL, SWT.BEGINNING, true, false), linksGridLayout);
		link_ = new Link[Profile.NR_OF_LINK_DESTINATIONS];
		for (int i = 0; i < link_.length; i++) {
			link_[i] = createLink(linksComposite, StringUtils.EMPTY, new SelectionAdapter() {
				public void widgetSelected(SelectionEvent event) {
					SystemUtils.openForBrowsing(event.text);
				}
			});
		}

		thumbHeight_ = settings_.getIntValue("gui", "screenshotsheight");
		scrolledCompositeForThumbs_ = new ScrolledComposite(composite, SWT.BORDER | SWT.H_SCROLL);
		scrolledCompositeForThumbs_.setMinHeight(thumbHeight_ + 20);
		GridData thumbsGridData = new GridData(SWT.FILL, SWT.TOP, true, false);
		thumbsGridData.exclude = !settings_.getBooleanValue("gui", "screenshotsvisible");
		scrolledCompositeForThumbs_.setLayoutData(thumbsGridData);

		thumbsComposite_ = createComposite(scrolledCompositeForThumbs_, null, new RowLayout(SWT.HORIZONTAL));
		scrolledCompositeForThumbs_.setContent(thumbsComposite_);
		scrolledCompositeForThumbs_.getHorizontalBar().setPageIncrement(300);
		scrolledCompositeForThumbs_.getHorizontalBar().setIncrement(50);

		sashInfoForm_.setWeights(settings_.getIntValues("gui", "sashweights"));
		sashInfoForm_.setMaximizedControl(settings_.getBooleanValue("gui", "notesvisible") ? null: filterFolder_);

		thumbMenu_ = new Menu(thumbsComposite_);
		createMenuItem(thumbMenu_, text_.get("dialog.main.thumb.remove"), ImageService.IMG_DELETE, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				if (currentThumbFile_ != null) {
					if (GeneralPurposeDialogs.confirmMessage(shell_, text_.get("dialog.main.confirm.removethumb", new Object[] {currentThumbFile_}))) {
						FilesUtils.removeFile(currentThumbFile_);
						ImageService.clearCache(currentThumbFile_.getPath());
						displayProfileInformation(true);
					}
					currentThumbFile_ = null;
				}
			}
		});
		createSeparatorMenuItem(thumbMenu_);
		createMenuItem(thumbMenu_, text_.get("dialog.main.thumb.openfolder"), ImageService.IMG_FOLDER, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				if (currentThumbFile_ != null)
					SystemUtils.openDirForViewing(currentThumbFile_.getParentFile());
			}
		});
		createMenuItem(thumbMenu_, text_.get("dialog.main.thumb.refresh"), ImageService.IMG_REFRESH, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				for (ProfilesListItem item: profileTable_.getItems())
					item.resetCachedInfo();
				ImageService.clearCache();
				displayProfileInformation(true);
			}
		});
	}

	private void constructProfilesList() {
		if (settings_.getValue("gui", "viewstyle").equalsIgnoreCase(ProfilesListType.SMALL_TILES.toString())) {
			profileTable_ = new ProfilesList(filterFolder_, ProfilesListType.SMALL_TILES);
		} else if (settings_.getValue("gui", "viewstyle").equalsIgnoreCase(ProfilesListType.MEDIUM_TILES.toString())) {
			profileTable_ = new ProfilesList(filterFolder_, ProfilesListType.MEDIUM_TILES);
		} else if (settings_.getValue("gui", "viewstyle").equalsIgnoreCase(ProfilesListType.LARGE_TILES.toString())) {
			profileTable_ = new ProfilesList(filterFolder_, ProfilesListType.LARGE_TILES);
		} else if (settings_.getValue("gui", "viewstyle").equalsIgnoreCase(ProfilesListType.SMALL_BOXES.toString())) {
			profileTable_ = new ProfilesList(filterFolder_, ProfilesListType.SMALL_BOXES);
		} else if (settings_.getValue("gui", "viewstyle").equalsIgnoreCase(ProfilesListType.MEDIUM_BOXES.toString())) {
			profileTable_ = new ProfilesList(filterFolder_, ProfilesListType.MEDIUM_BOXES);
		} else if (settings_.getValue("gui", "viewstyle").equalsIgnoreCase(ProfilesListType.LARGE_BOXES.toString())) {
			profileTable_ = new ProfilesList(filterFolder_, ProfilesListType.LARGE_BOXES);
		} else {
			profileTable_ = new ProfilesList(filterFolder_, ProfilesListType.TABLE, this, columnIds_, columnNames_);
		}

		for (CTabItem tab: filterFolder_.getItems())
			tab.setControl(profileTable_.getControl());

		Menu menu = new Menu(profileTable_.getControl());
		createSeparatorMenuItem(menu);
		createMenuItem(menu, text_.get("dialog.main.profile.openfolder"), ImageService.IMG_FOLDER, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				int index = profileTable_.getSelectionIndex();
				if (index != -1) {
					Profile prof = profilesList_.get(index);
					try {
						String warnings = prof.resetAndLoadConfiguration();
						if (StringUtils.isNotEmpty(warnings))
							GeneralPurposeDialogs.warningMessage(shell_, warnings);
						SystemUtils.openDirForViewing(prof.getConfiguration().getAutoexec().getCanonicalGameDir());
					} catch (IOException e) {
						GeneralPurposeDialogs.warningMessage(shell_, e);
					}
				}
			}
		});
		createMenuItem(menu, text_.get("dialog.main.profile.opencapturesfolder"), ImageService.IMG_FOLDER, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				int index = profileTable_.getSelectionIndex();
				if (index != -1)
					SystemUtils.openDirForViewing(profilesList_.get(index).getCanonicalCaptures());
			}
		});
		Menu profileViewSubMenu = createMenu(menu, text_.get("dialog.main.profile.view"), ImageService.IMG_ZOOM);
		createSeparatorMenuItem(menu);
		createMenuItem(menu, text_.get("dialog.main.profile.add"), ImageService.IMG_NEW, addProfAdapter);
		createMenuItem(menu, text_.get("dialog.main.profile.edit"), ImageService.IMG_EDIT, editProfAdapter);
		createMenuItem(menu, text_.get("dialog.main.profile.duplicate"), ImageService.IMG_DUPLICATE, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doDuplicateProfile();
			}
		});
		createMenuItem(menu, text_.get("dialog.main.profile.remove"), ImageService.IMG_DELETE, removeProfAdapter);
		createSeparatorMenuItem(menu);
		createMenuItem(menu, text_.get("dialog.main.profile.togglefavorite"), ImageService.IMG_FAVORITE, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doToggleFavoriteProfile();
			}
		});
		if (SystemUtils.IS_WINDOWS || SystemUtils.IS_LINUX) {
			createSeparatorMenuItem(menu);
			createMenuItem(menu, text_.get("dialog.main.profile.createshortcut"), ImageService.IMG_SHORTCUT, new SelectionAdapter() {
				public void widgetSelected(SelectionEvent event) {
					IntStream.of(profileTable_.getSelectionIndices()).forEach(x -> {
						try {
							ExecuteUtils.createShortcut(profilesList_.get(x));
						} catch (IOException e) {
							GeneralPurposeDialogs.warningMessage(shell_, e);
						}
					});
				}
			});
		}

		menu.addMenuListener(new MenuAdapter() {
			public void menuShown(MenuEvent event) {
				if (profileTable_.getSelectionIndex() != -1) {
					Profile prof = profilesList_.get(profileTable_.getSelectionIndex());

					for (MenuItem it: menu.getItems()) {
						if (it.getStyle() == SWT.SEPARATOR)
							break;
						it.dispose();
					}

					if (dbversionsList_.size() > 1)
						createTopMenuItem(menu, text_.get("dialog.main.profile.startmanuallywith"), null, null).setMenu(createDosboxVersionsSubmenu(menu, ProfileRunMode.NORMAL, true));
					createTopMenuItem(menu, text_.get("dialog.main.profile.startmanually"), null, new SelectionAdapter() {
						public void widgetSelected(SelectionEvent event) {
							doRunProfile(ProfileRunMode.NORMAL, true);
						}
					});
					if (prof.hasAltExe(1)) {
						if (dbversionsList_.size() > 1)
							createTopMenuItem(menu, prof.getAltExeFilenames()[1], null, null).setMenu(createDosboxVersionsSubmenu(menu, ProfileRunMode.ALT2, false));
						createTopMenuItem(menu, prof.getAltExeFilenames()[1], null, new SelectionAdapter() {
							public void widgetSelected(SelectionEvent event) {
								doRunProfile(ProfileRunMode.ALT2, false);
							}
						});
					}
					if (prof.hasAltExe(0)) {
						if (dbversionsList_.size() > 1)
							createTopMenuItem(menu, prof.getAltExeFilenames()[0], null, null).setMenu(createDosboxVersionsSubmenu(menu, ProfileRunMode.ALT1, false));
						createTopMenuItem(menu, prof.getAltExeFilenames()[0], null, new SelectionAdapter() {
							public void widgetSelected(SelectionEvent event) {
								doRunProfile(ProfileRunMode.ALT1, false);
							}
						});
					}
					if (prof.hasSetup()) {
						if (dbversionsList_.size() > 1)
							createTopMenuItem(menu, text_.get("dialog.main.profile.setupwith"), null, null).setMenu(createDosboxVersionsSubmenu(menu, ProfileRunMode.SETUP, false));
						createTopMenuItem(menu, text_.get("dialog.main.profile.setup"), ImageService.IMG_SETUP, setupProfAdapter);
					}
					if (dbversionsList_.size() > 1)
						createTopMenuItem(menu, text_.get("dialog.main.profile.runwith"), null, null).setMenu(createDosboxVersionsSubmenu(menu, ProfileRunMode.NORMAL, false));
					createTopMenuItem(menu, text_.get("dialog.main.profile.run"), ImageService.IMG_RUN, runProfAdapter);

					for (MenuItem item: profileViewSubMenu.getItems())
						item.dispose();
					for (org.dbgl.model.Link lnk: prof.getLinks()) {
						String url = lnk.getUrl();
						if (StringUtils.isNotBlank(url)) {
							createMenuItem(profileViewSubMenu, StringUtils.abbreviateMiddle(lnk.getDisplayTitle(), "....", 80), null, new SelectionAdapter() {
								public void widgetSelected(SelectionEvent event) {
									SystemUtils.openForBrowsing(url);
								}
							});
						}
					}
					createMenuItem(profileViewSubMenu, text_.get("dialog.main.profile.view.conf"), null, new SelectionAdapter() {
						public void widgetSelected(SelectionEvent event) {
							SystemUtils.openForEditing(prof.getConfigurationCanonicalFile());
						}
					});
				}
			}
		});

		profileTable_.setMenu(menu);
		profileTable_.addMouseListener(new MouseAdapter() {
			public void mouseDoubleClick(MouseEvent event) {
				doRunProfile(ProfileRunMode.NORMAL, false);
			}
		});
		profileTable_.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent event) {
				if (event.keyCode == SWT.DEL || (event.stateMask == SWT.MOD1 && (Character.toLowerCase(event.keyCode) == 'r'))) {
					doRemoveProfile();
				} else if (event.keyCode == SWT.INSERT || (event.stateMask == SWT.MOD1 && (Character.toLowerCase(event.keyCode) == 'n'))) {
					doAddProfile(null);
				} else if (event.keyCode == SWT.F2) {
					doEditProfile(true);
				} else if (event.stateMask == SWT.MOD1 && (Character.toLowerCase(event.keyCode) == 'm')) {
					doToggleFavoriteProfile();
				} else if (event.stateMask == SWT.MOD1 && (Character.toLowerCase(event.keyCode) == 'd')) {
					doDuplicateProfile();
				} else if (event.stateMask == SWT.MOD1 && (Character.toLowerCase(event.keyCode) == 'a')) {
					profileTable_.selectAll();
				} else if (event.stateMask == SWT.MOD1 && (Character.toLowerCase(event.keyCode) == 'f')) {
					doAddFilter();
				} else if (event.stateMask == SWT.MOD1 && (Character.toLowerCase(event.keyCode) == 'c')) {
					int index = profileTable_.getSelectionIndex();
					if (index != -1)
						SystemUtils.openForEditing(profilesList_.get(index).getConfigurationCanonicalFile());
				} else if (event.stateMask == SWT.MOD1 && (Character.toLowerCase(event.keyCode) == 'w')) {
					updateWithAddedProfile(new AddGameWizardDialog(shell_).open());
				}
			}
		});
		profileTable_.addTraverseListener(new TraverseListener() {
			public void keyTraversed(TraverseEvent event) {
				if ((event.stateMask == SWT.MOD1) && (event.detail == SWT.TRAVERSE_RETURN))
					doEditProfile(false);
				else if ((event.stateMask == SWT.SHIFT) && (event.detail == SWT.TRAVERSE_RETURN))
					doRunProfile(ProfileRunMode.SETUP, false);
				else if (event.detail == SWT.TRAVERSE_RETURN)
					doRunProfile(ProfileRunMode.NORMAL, false);
			}
		});
		profileTable_.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				displayProfileInformation(false);
			}
		});

		DropTarget target = new DropTarget(profileTable_.getControl(), DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_DEFAULT);
		target.setTransfer(new Transfer[] {FileTransfer.getInstance()});
		target.addDropListener(new DropTargetAdapter() {
			public void drop(DropTargetEvent event) {
				String[] filenames = (String[])event.data;
				if (filenames != null && filenames.length == 1)
					doAddProfile(filenames[0]);
			}
		});
	}

	private void rebuildProfilesTable() {
		Set<Integer> selectedProfileIds = getSelectedProfileIds();

		initColumnIds();

		for (CTabItem tab: filterFolder_.getItems())
			tab.setControl(null);
		profileTable_.dispose();

		constructProfilesList();

		updateProfilesList(selectedProfileIds);
	}

	private void updateWithAddedProfile(Profile profile) {
		if (profile != null) {
			if (settings_.getBooleanValue("gui", "autosortonupdate") || (filterFolder_.getSelectionIndex() > 0)) {
				updateProfilesList(new HashSet<>(Arrays.asList(profile.getId())));
			} else {
				profilesList_.add(profile);
				addProfileToTable(profile);
				profileTable_.setSelection(profileTable_.getItemCount() - 1);
				profileTable_.setFocus();
			}
			displayProfileInformation(false);
		}
	}

	private void updateProfileListAfterEdit(int index, Profile profile) {
		boolean quickUpdate = true;
		if (settings_.getBooleanValue("gui", "autosortonupdate") || (filterFolder_.getSelectionIndex() > 0)) {
			try {
				profilesList_ = new ProfileRepository().list(orderingVector_.toClause(), filterClause_, dbversionsList_);
				if (index != ProfileRepository.indexOf(profilesList_, profile))
					quickUpdate = false;
			} catch (SQLException e) {
				GeneralPurposeDialogs.warningMessage(shell_, e);
			}
		}
		if (quickUpdate) {
			profilesList_.set(index, profile);
			setProfileTableItem(profileTable_.getItem(index), profile);
		} else {
			updateProfilesList(new HashSet<>(Arrays.asList(profile.getId())));
		}
	}

	private void updateProfilesAfterTabAction() {
		int tabIndex = filterFolder_.getSelectionIndex();
		settings_.setIntValue("gui", "filtertab", tabIndex);
		filterClause_ = filtersList_.get(tabIndex).getFilter();
		updateProfilesList(getSelectedProfileIds());
		for (CTabItem tab: filterFolder_.getItems())
			tab.setToolTipText(null);
		filterFolder_.getSelection().setToolTipText(text_.get("dialog.filter.notice.results", new Object[] {profilesList_.size()}));
		displayProfileInformation(false);
	}

	private void toggleProfileViewType(ViewType newViewType) {
		viewSelector_.setImage(ImageService.getResourceImage(shell_.getDisplay(), newViewType.getImage()));
		viewSelector_.setToolTipText(newViewType.getDisplayName());
		settings_.setValue("gui", "viewstyle", newViewType.getName().toLowerCase());

		/* Workaround for bug in Gallery, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=416476 */
		viewSelector_.setEnabled(false);
		Display.getCurrent().timerExec(250, new Runnable() {
			@Override
			public void run() {
				viewSelector_.setEnabled(true);
			}
		});

		rebuildProfilesTable();
	}

	public void doReorder(int columnId, int dir) {
		Set<Integer> selectedProfiles = getSelectedProfileIds();
		try {
			orderingVector_.addOrdering(columnIds_[columnId], dir == SWT.UP);
			profilesList_ = new ProfileRepository().list(orderingVector_.toClause(), filterClause_, dbversionsList_);
		} catch (SQLException e) {
			GeneralPurposeDialogs.warningMessage(shell_, e);
		}
		for (int i = 0; i < profilesList_.size(); i++) {
			setProfileTableItem(profileTable_.getItem(i), profilesList_.get(i));
		}
		profileTable_.setSelection(getIndicesByIds(selectedProfiles));
		settings_.setIntValues("gui", "sortcolumn", orderingVector_.getColumns());
		settings_.setBooleanValues("gui", "sortascending", orderingVector_.getAscendings());
	}

	private Menu createDosboxVersionsSubmenu(Menu parent, ProfileRunMode mode, boolean prepareOnly) {
		Menu dosboxVersionsSubMenu = new Menu(parent);
		for (DosboxVersion dbVersion: dbversionsList_) {
			createMenuItem(dosboxVersionsSubMenu, dbVersion.getTitle(), null, new SelectionAdapter() {
				public void widgetSelected(SelectionEvent event) {
					Profile prof = profilesList_.get(profileTable_.getSelectionIndex());
					try {
						ExecuteUtils.doRunProfile(mode, prof, dbVersion, prepareOnly, display_);
					} catch (IOException e) {
						GeneralPurposeDialogs.warningMessage(shell_, e);
					}
				}
			});
		}
		return dosboxVersionsSubMenu;
	}

	private void displayProfileInformation(boolean forceRefresh) {
		int index = profileTable_.getSelectionIndex();
		boolean screenshotsVisible = settings_.getBooleanValue("gui", "screenshotsvisible");
		boolean notesVisible = settings_.getBooleanValue("gui", "notesvisible");
		if (index == -1) {
			if (screenshotsVisible)
				displayScreenshots(null, null, forceRefresh);
			if (notesVisible) {
				notesField_.setText(StringUtils.EMPTY);
				displayLinks(new org.dbgl.model.Link[] {});
			}
			setupToolItem_.setEnabled(false);
		} else {
			settings_.setIntValue("gui", "selectedprofile", index);
			Profile prof = profilesList_.get(index);
			if (screenshotsVisible)
				displayScreenshots(prof, profileTable_.getItem(index), forceRefresh);
			else if (forceRefresh)
				((ThumbInfo)profileTable_.getItem(index).getData()).resetCachedInfo();
			if (forceRefresh)
				profileTable_.redraw();
			if (notesVisible) {
				if (!StringUtils.equals(prof.getNotes(), notesField_.getText()))
					notesField_.setText(prof.getNotes());
				displayLinks(prof.getLinks());
			}
			setupToolItem_.setEnabled(prof.hasSetup());
		}
	}

	private void displayLinks(org.dbgl.model.Link[] profileLinks) {
		boolean showLinks = false;
		for (int i = 0; i < profileLinks.length; i++) {
			String url = profileLinks[i].getUrl();
			boolean isBlank = StringUtils.isBlank(url);
			if (!isBlank) {
				link_[i].setText(profileLinks[i].getAnchor());
				link_[i].setToolTipText(url);
			} else {
				link_[i].setText(StringUtils.EMPTY);
				link_[i].setToolTipText(StringUtils.EMPTY);
			}
			link_[i].pack();
			((GridData)link_[i].getLayoutData()).exclude = isBlank;
			showLinks |= !isBlank;
		}
		((GridData)link_[0].getParent().getLayoutData()).exclude = !showLinks;
		link_[0].getParent().layout();
		link_[0].getParent().getParent().layout();
	}

	private void displayScreenshots(Profile prof, ProfilesListItem profilesListItem, boolean forceRefresh) {
		for (Control c: thumbsComposite_.getChildren()) {
			c.setMenu(null);
			c.dispose();
		}
		if (prof != null) {
			ThumbInfo thumbInfo = (ThumbInfo)profilesListItem.getData();
			if (forceRefresh)
				thumbInfo.resetCachedInfo();
			File[] files = thumbInfo.getAllThumbs();
			if (files != null) {
				for (File file: files) {
					String label = null;
					if (settings_.getBooleanValue("gui", "screenshotsfilename")) {
						label = file.getName().toLowerCase();
						label = ' ' + label.substring(0, label.lastIndexOf('.')) + ' ';
					}
					Button buttonItem = createImageButton(thumbsComposite_, SWT.FLAT, null, ImageService.getCachedHeightLimitedImage(display_, thumbHeight_, file.getPath(), label), null,
						file.getPath(), new SelectionAdapter() {
							public void widgetSelected(SelectionEvent event) {
								new Thumb(shell_, file.getPath()).open();
							}
						}, false);
					buttonItem.addMouseListener(new MouseAdapter() {
						public void mouseDown(MouseEvent arg0) {
							currentThumbFile_ = new File(file.getPath());
						}
					});
					buttonItem.setMenu(thumbMenu_);
				}
				if (!forceRefresh && thumbInfo.isUpdated())
					profilesListItem.setData(thumbInfo);
			}
		}
		thumbsComposite_.setVisible(thumbsComposite_.getChildren().length != 0);
		thumbsComposite_.layout();
		thumbsComposite_.pack();
	}

	private void createDosboxVersionsTab(TabFolder tabFolder) {
		SelectionAdapter addDosboxAdapter = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doAddDosboxVersion();
			}
		};
		SelectionAdapter editDosboxAdapter = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doEditDosboxVersion();
			}
		};
		SelectionAdapter removeDosboxAdapter = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doRemoveDosboxVersion();
			}
		};
		SelectionAdapter runDosboxAdapter = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doRunDosbox();
			}
		};

		Composite composite = createTabWithInnerComposite(tabFolder, text_.get("dialog.main.dosboxversions"), 1);
		ToolBar toolBar = new ToolBar(composite, SWT.NONE);
		createImageToolItem(toolBar, text_.get("dialog.main.addversion"), ImageService.IMG_TB_NEW, addDosboxAdapter);
		createImageToolItem(toolBar, text_.get("dialog.main.editversion"), ImageService.IMG_TB_EDIT, editDosboxAdapter);
		createImageToolItem(toolBar, text_.get("dialog.main.removeversion"), ImageService.IMG_TB_DELETE, removeDosboxAdapter);
		createImageToolItem(toolBar, text_.get("dialog.main.runversion"), ImageService.IMG_TB_RUN, runDosboxAdapter);
		dbversionTable_ = new Table(composite, SWT.FULL_SELECTION | SWT.BORDER);
		dbversionTable_.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		dbversionTable_.setLinesVisible(true);
		dbversionTable_.setHeaderVisible(true);
		addDBColumn(text_.get("dialog.main.dosboxversions.column.title"), 0);
		addDBColumn(text_.get("dialog.main.dosboxversions.column.version"), 1);
		addDBColumn(text_.get("dialog.main.dosboxversions.column.path"), 2);
		addDBColumn(text_.get("dialog.main.dosboxversions.column.default"), 3);
		addDBColumn(text_.get("dialog.main.dosboxversions.column.id"), 4);
		addDBColumn(text_.get("dialog.main.generic.column.created"), 5);
		addDBColumn(text_.get("dialog.main.generic.column.lastmodify"), 6);
		addDBColumn(text_.get("dialog.main.generic.column.lastrun"), 7);
		addDBColumn(text_.get("dialog.main.generic.column.runs"), 8);

		Menu menu = new Menu(dbversionTable_);
		createMenuItem(menu, text_.get("dialog.main.dosboxversion.run"), ImageService.IMG_RUN, runDosboxAdapter);
		createSeparatorMenuItem(menu);
		createMenuItem(menu, text_.get("dialog.main.dosboxversion.openfolder"), ImageService.IMG_FOLDER, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				int index = dbversionTable_.getSelectionIndex();
				if (index != -1)
					SystemUtils.openDirForViewing(dbversionsList_.get(index).getConfigurationCanonicalFile().getParentFile());
			}
		});
		Menu viewDosboxSubMenu = createMenu(menu, text_.get("dialog.main.profile.view"), ImageService.IMG_ZOOM);
		createMenuItem(viewDosboxSubMenu, text_.get("dialog.main.profile.view.conf"), null, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				if (dbversionTable_.getSelectionIndex() != -1) {
					DosboxVersion dbversion = dbversionsList_.get(dbversionTable_.getSelectionIndex());
					SystemUtils.openForEditing(dbversion.getConfigurationCanonicalFile());
				}
			}
		});
		createSeparatorMenuItem(menu);
		createMenuItem(menu, text_.get("dialog.main.dosboxversion.add"), ImageService.IMG_NEW, addDosboxAdapter);
		createMenuItem(menu, text_.get("dialog.main.dosboxversion.edit"), ImageService.IMG_EDIT, editDosboxAdapter);
		createMenuItem(menu, text_.get("dialog.main.dosboxversion.remove"), ImageService.IMG_DELETE, removeDosboxAdapter);
		createSeparatorMenuItem(menu);
		createMenuItem(menu, text_.get("dialog.main.dosboxversion.toggledefault"), ImageService.IMG_HOME, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doToggleDefaultVersion();
			}
		});

		dbversionTable_.setMenu(menu);
		dbversionTable_.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent event) {
				if (event.keyCode == SWT.DEL || (event.stateMask == SWT.MOD1 && (Character.toLowerCase(event.keyCode) == 'r'))) {
					doRemoveDosboxVersion();
				} else if (event.keyCode == SWT.INSERT || (event.stateMask == SWT.MOD1 && (Character.toLowerCase(event.keyCode) == 'n'))) {
					doAddDosboxVersion();
				} else if (event.stateMask == SWT.MOD1 && (Character.toLowerCase(event.keyCode) == 'm')) {
					doToggleDefaultVersion();
				}
			}
		});
		dbversionTable_.addTraverseListener(new TraverseListener() {
			public void keyTraversed(TraverseEvent event) {
				if ((event.stateMask == SWT.MOD1) && (event.detail == SWT.TRAVERSE_RETURN)) {
					doEditDosboxVersion();
				} else if (event.detail == SWT.TRAVERSE_RETURN) {
					doRunDosbox();
				}
			}
		});
		dbversionTable_.addMouseListener(new MouseAdapter() {
			public void mouseDoubleClick(MouseEvent event) {
				doRunDosbox();
			}
		});
	}

	private void addDBColumn(String title, int colIndex) {
		String width = "column2_" + (colIndex + 1) + "width";
		createTableColumn(dbversionTable_, settings_.getIntValue("gui", width), title).addControlListener(new ControlAdapter() {
			public void controlResized(ControlEvent event) {
				settings_.setIntValue("gui", width, ((TableColumn)event.widget).getWidth());
			}
		});
	}

	private void createTemplatesTab(TabFolder tabFolder) {
		SelectionAdapter addTemplAdapter = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doAddTemplate();
			}
		};
		SelectionAdapter editTemplAdapter = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doEditTemplate();
			}
		};
		SelectionAdapter removeTemplAdapter = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doRemoveTemplate();
			}
		};
		SelectionAdapter runTemplAdapter = new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doRunTemplate();
			}
		};

		Composite composite = createTabWithInnerComposite(tabFolder, text_.get("dialog.main.templates"), 1);
		ToolBar toolBar = new ToolBar(composite, SWT.NONE);
		createImageToolItem(toolBar, text_.get("dialog.main.addtemplate"), ImageService.IMG_TB_NEW, addTemplAdapter);
		createImageToolItem(toolBar, text_.get("dialog.main.edittemplate"), ImageService.IMG_TB_EDIT, editTemplAdapter);
		createImageToolItem(toolBar, text_.get("dialog.main.removetemplate"), ImageService.IMG_TB_DELETE, removeTemplAdapter);
		createImageToolItem(toolBar, text_.get("dialog.main.runtemplate"), ImageService.IMG_TB_RUN, runTemplAdapter);
		templateTable_ = new Table(composite, SWT.FULL_SELECTION | SWT.BORDER);
		templateTable_.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		templateTable_.setLinesVisible(true);
		templateTable_.setHeaderVisible(true);
		addTemplateColumn(text_.get("dialog.main.templates.column.title"), 0);
		addTemplateColumn(text_.get("dialog.main.templates.column.default"), 1);
		addTemplateColumn(text_.get("dialog.main.templates.column.id"), 2);
		addTemplateColumn(text_.get("dialog.main.generic.column.created"), 3);
		addTemplateColumn(text_.get("dialog.main.generic.column.lastmodify"), 4);
		addTemplateColumn(text_.get("dialog.main.generic.column.lastrun"), 5);
		addTemplateColumn(text_.get("dialog.main.generic.column.runs"), 6);

		Menu menu = new Menu(templateTable_);
		createMenuItem(menu, text_.get("dialog.main.template.run"), ImageService.IMG_RUN, runTemplAdapter);
		createSeparatorMenuItem(menu);
		Menu viewTemplateSubMenu = createMenu(menu, text_.get("dialog.main.profile.view"), ImageService.IMG_ZOOM);
		createMenuItem(viewTemplateSubMenu, text_.get("dialog.main.profile.view.conf"), null, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				if (templateTable_.getSelectionIndex() != -1) {
					Template template = templatesList_.get(templateTable_.getSelectionIndex());
					SystemUtils.openForEditing(template.getConfigurationCanonicalFile());
				}
			}
		});
		createSeparatorMenuItem(menu);
		createMenuItem(menu, text_.get("dialog.main.template.add"), ImageService.IMG_NEW, addTemplAdapter);
		createMenuItem(menu, text_.get("dialog.main.template.edit"), ImageService.IMG_EDIT, editTemplAdapter);
		createMenuItem(menu, text_.get("dialog.main.template.duplicate"), ImageService.IMG_DUPLICATE, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doDuplicateTemplate();
			}
		});
		createMenuItem(menu, text_.get("dialog.main.template.remove"), ImageService.IMG_DELETE, removeTemplAdapter);
		createSeparatorMenuItem(menu);
		createMenuItem(menu, text_.get("dialog.main.template.toggledefault"), ImageService.IMG_HOME, new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doToggleDefaultTemplate();
			}
		});

		templateTable_.setMenu(menu);
		templateTable_.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent event) {
				if (event.keyCode == SWT.DEL || (event.stateMask == SWT.MOD1 && (Character.toLowerCase(event.keyCode) == 'r'))) {
					doRemoveTemplate();
				} else if (event.keyCode == SWT.INSERT || (event.stateMask == SWT.MOD1 && (Character.toLowerCase(event.keyCode) == 'n'))) {
					doAddTemplate();
				} else if (event.stateMask == SWT.MOD1 && (Character.toLowerCase(event.keyCode) == 'm')) {
					doToggleDefaultTemplate();
				}
			}
		});
		templateTable_.addTraverseListener(new TraverseListener() {
			public void keyTraversed(TraverseEvent event) {
				if ((event.stateMask == SWT.MOD1) && (event.detail == SWT.TRAVERSE_RETURN)) {
					doEditTemplate();
				} else if (event.detail == SWT.TRAVERSE_RETURN) {
					doRunTemplate();
				}
			}
		});
		templateTable_.addMouseListener(new MouseAdapter() {
			public void mouseDoubleClick(MouseEvent event) {
				doRunTemplate();
			}
		});
	}

	private void addTemplateColumn(String title, int colIndex) {
		String width = "column3_" + (colIndex + 1) + "width";
		createTableColumn(templateTable_, settings_.getIntValue("gui", width), title).addControlListener(new ControlAdapter() {
			public void controlResized(ControlEvent event) {
				settings_.setIntValue("gui", width, ((TableColumn)event.widget).getWidth());
			}
		});
	}

	private void addProfileToTable(Profile prof) {
		setProfileTableItem(profileTable_.new ProfilesListItem(profileTable_), prof);
	}

	private void addDosboxVersionToTable(DosboxVersion dbversion) {
		setDosboxTableItem(new TableItem(dbversionTable_, SWT.BORDER), dbversion);
	}

	private void addTemplateToTable(Template template) {
		setTemplateTableItem(new TableItem(templateTable_, SWT.BORDER), template);
	}

	private void setProfileTableItem(ProfilesListItem newItemTableItem, Profile prof) {
		for (int i = 0; i < columnIds_.length; i++) {
			final String value;
			switch (columnIds_[i]) {
				case 0:
					value = prof.getTitle();
					break;
				case 1:
					value = prof.hasSetup() ? text_.get("general.yes"): text_.get("general.no");
					break;
				case 2:
					value = prof.getDeveloper();
					break;
				case 3:
					value = prof.getPublisher();
					break;
				case 4:
					value = prof.getGenre();
					break;
				case 5:
					value = prof.getYear();
					break;
				case 6:
					value = prof.getStatus();
					break;
				case 7:
					value = prof.isFavorite() ? text_.get("general.yes"): text_.get("general.no");
					break;
				case 8:
					value = String.valueOf(prof.getId());
					break;
				case 9:
					value = String.valueOf(prof.getDosboxVersion().getId());
					break;
				case 10:
				case 11:
				case 12:
				case 13:
				case 14:
				case 15:
				case 16:
				case 17:
					value = prof.getCustomStrings()[columnIds_[i] - Constants.RO_COLUMN_NAMES];
					break;
				case 18:
					value = prof.getCustomInts()[0] + " %";
					break;
				case 19:
					value = String.valueOf(prof.getCustomInts()[1]);
					break;
				case 21:
					value = prof.getDosboxVersion().getTitle();
					break;
				case 22:
					value = text_.toString(prof.getStats().getCreated(), DateFormat.SHORT);
					break;
				case 23:
					value = text_.toString(prof.getStats().getModified(), DateFormat.SHORT);
					break;
				case 24:
					value = text_.toString(prof.getStats().getLastRun(), DateFormat.SHORT);
					break;
				case 25:
					value = text_.toString(prof.getProfileStats().getLastSetup(), DateFormat.SHORT);
					break;
				case 26:
					value = String.valueOf(prof.getStats().getRuns());
					break;
				case 27:
					value = String.valueOf(prof.getProfileStats().getSetups());
					break;
				case 28:
				case 29:
				case 30:
				case 31:
					value = prof.getCustomStrings()[columnIds_[i] - Constants.RO_COLUMN_NAMES - Constants.EDIT_COLUMN_NAMES_1];
					break;
				default:
					value = "";
			}
			if (columnIds_[i] != 20) {
				newItemTableItem.setText(i, columnIds_[i], value);
			}
		}
		newItemTableItem.setData(new ThumbInfo(prof.getCapturesString()));
	}

	private void setDosboxTableItem(TableItem newItemTableItem, DosboxVersion dbversion) {
		newItemTableItem.setText(0, dbversion.getTitle());
		newItemTableItem.setText(1, dbversion.getVersion());
		newItemTableItem.setText(2, dbversion.getPath().getPath());
		newItemTableItem.setText(3, dbversion.isDefault() ? text_.get("general.yes"): text_.get("general.no"));
		newItemTableItem.setText(4, String.valueOf(dbversion.getId()));
		newItemTableItem.setText(5, text_.toString(dbversion.getStats().getCreated(), DateFormat.SHORT));
		newItemTableItem.setText(6, text_.toString(dbversion.getStats().getModified(), DateFormat.SHORT));
		newItemTableItem.setText(7, text_.toString(dbversion.getStats().getLastRun(), DateFormat.SHORT));
		newItemTableItem.setText(8, String.valueOf(dbversion.getStats().getRuns()));
	}

	private void setTemplateTableItem(TableItem newItemTableItem, Template template) {
		newItemTableItem.setText(0, template.getTitle());
		newItemTableItem.setText(1, template.isDefault() ? text_.get("general.yes"): text_.get("general.no"));
		newItemTableItem.setText(2, String.valueOf(template.getId()));
		newItemTableItem.setText(3, text_.toString(template.getStats().getCreated(), DateFormat.SHORT));
		newItemTableItem.setText(4, text_.toString(template.getStats().getModified(), DateFormat.SHORT));
		newItemTableItem.setText(5, text_.toString(template.getStats().getLastRun(), DateFormat.SHORT));
		newItemTableItem.setText(6, String.valueOf(template.getStats().getRuns()));
	}

	private void updateProfilesList(Set<Integer> profileIds) {
		try {
			profilesList_ = new ProfileRepository().list(orderingVector_.toClause(), filterClause_, dbversionsList_);
		} catch (SQLException e) {
			GeneralPurposeDialogs.warningMessage(shell_, e);
		}
		profileTable_.setRedraw(false);
		profileTable_.removeAll();
		for (Profile prof: profilesList_) {
			addProfileToTable(prof);
		}
		profileTable_.setSelection(getIndicesByIds(profileIds));
		profileTable_.setRedraw(true);
		profileTable_.setFocus();
	}

	private void updateDosboxVersionList(DosboxVersion dbversion) {
		try {
			dbversionsList_ = new DosboxVersionRepository().listAll();
		} catch (SQLException e) {
			GeneralPurposeDialogs.warningMessage(shell_, e);
		}
		dbversionTable_.removeAll();
		for (DosboxVersion version: dbversionsList_) {
			addDosboxVersionToTable(version);
		}
		dbversionTable_.setSelection(DosboxVersionRepository.indexOf(dbversionsList_, dbversion));
		dbversionTable_.setFocus();
	}

	private void updateTemplateList(Template template) {
		try {
			templatesList_ = new TemplateRepository().listAll(dbversionsList_);
		} catch (SQLException e) {
			GeneralPurposeDialogs.warningMessage(shell_, e);
		}
		templateTable_.removeAll();
		for (Template temp: templatesList_) {
			addTemplateToTable(temp);
		}
		templateTable_.setSelection(TemplateRepository.indexOf(templatesList_, template));
		templateTable_.setFocus();
	}

	private void doAddProfile(String filename) {
		if (requireDefaultDBVersion() == null)
			return;

		if (filename == null || FilesUtils.isBooterImage(filename) || FilesUtils.isExecutable(filename) || FilesUtils.isConfFile(filename)) {
			Profile profile = new EditSingleProfileDialog(shell_, null, filename, false).open();
			if (profile != null)
				updateWithAddedProfile(profile);
		} else if (FilesUtils.isArchive(filename)) {
			Boolean updateCustomFields = new ImportDialog(shell_, dbversionsList_, new File(filename)).open();
			if (updateCustomFields != null) {
				if (updateCustomFields) {
					rebuildProfilesTable();
				} else {
					updateProfilesList(getSelectedProfileIds());
				}
				displayProfileInformation(true);
			}
		} else {
			GeneralPurposeDialogs.warningMessage(shell_, text_.get("general.error.cannotimportunknownfile"));
		}
	}

	public void addProfile(String file) {
		display_.syncExec(new Runnable() {
			public void run() {
				doAddProfile(file);
			}
		});
	}

	private void doAddDosboxVersion() {
		DosboxVersion dbversion = new EditDosboxVersionDialog(shell_, DosboxVersionRepository.findDefault(dbversionsList_) == null, null).open();
		if (dbversion != null)
			updateDosboxVersionList(dbversion);
	}

	private void doAddTemplate() {
		if (requireDefaultDBVersion() == null)
			return;

		Template template = new EditTemplateDialog(shell_, null).open();
		if (template != null)
			updateTemplateList(template);
	}

	private void doAddFilter() {
		Filter filter = new EditFilterDialog(shell_, null, profileTable_.getSelectionCount() > 1 ? getSelectedProfileIds(): null).open();
		if (filter != null) {
			filtersList_.add(filter);
			addFilterTab(filter).setControl(profileTable_.getControl());
			filterFolder_.setSelection(filterFolder_.getItemCount() - 1);
			updateProfilesAfterTabAction();
		}
	}

	private CTabItem addFilterTab(Filter filter) {
		CTabItem item = new CTabItem(filterFolder_, filter.getFilter() == null ? SWT.NONE: SWT.CLOSE);
		item.setText("    " + filter.getTitle() + "    ");
		item.setData(filter.getId());
		return item;
	}

	private void doEditProfile(boolean focusTitle) {
		int index = profileTable_.getSelectionIndex();
		if (index != -1) {
			if (profileTable_.getSelectionCount() > 1) {
				List<Profile> loadedProfiles = new ProfileLoader(shell_, getSelectedProfiles()).open();
				if (loadedProfiles != null) {
					Object results = (loadedProfiles.size() > 1) ? new EditMultiProfileDialog(shell_, loadedProfiles).open()
							: new EditSingleProfileDialog(shell_, loadedProfiles.get(0), null, focusTitle).open();
					if (results != null) {
						updateProfilesList(getSelectedProfileIds());
						displayProfileInformation(false);
					}
				}
			} else {
				Profile profile = new EditSingleProfileDialog(shell_, profilesList_.get(index), null, focusTitle).open();
				if (profile != null) {
					updateProfileListAfterEdit(index, profile);
					displayProfileInformation(false);
				}
			}
		}
	}

	private void doEditDosboxVersion() {
		int index = dbversionTable_.getSelectionIndex();
		if (index != -1) {
			DosboxVersion dbversion = new EditDosboxVersionDialog(shell_, false, dbversionsList_.get(index)).open();
			if (dbversion != null)
				updateDosboxVersionList(dbversion);
		}
	}

	private void doEditTemplate() {
		int index = templateTable_.getSelectionIndex();
		if (index != -1) {
			Template template = new EditTemplateDialog(shell_, templatesList_.get(index)).open();
			if (template != null)
				updateTemplateList(template);
		}
	}

	private void doEditFilter() {
		int index = filterFolder_.getSelectionIndex();
		if (index > 0) {
			Filter filter = new EditFilterDialog(shell_, filtersList_.get(index), null).open();
			if (filter != null) {
				filtersList_.set(index, filter);
				filterFolder_.getSelection().setText("    " + filter.getTitle() + "    ");
				updateProfilesAfterTabAction();
			}
		}
	}

	private void doToggleFavoriteProfile() {
		int index = profileTable_.getSelectionIndex();
		if (index != -1) {
			Profile profile = profilesList_.get(index);
			profile.setFavorite(!profile.isFavorite());
			try {
				String warningsLog = profile.resetAndLoadConfiguration();
				if (StringUtils.isNotBlank(warningsLog))
					GeneralPurposeDialogs.warningMessage(shell_, warningsLog);
				new ProfileRepository().update(profile);
				setProfileTableItem(profileTable_.getItem(index), profile);
			} catch (SQLException | IOException e) {
				profile.setFavorite(!profile.isFavorite());
				GeneralPurposeDialogs.warningMessage(shell_, e);
			}
		}
	}

	private void doToggleDefaultVersion() {
		int index = dbversionTable_.getSelectionIndex();
		if (index != -1) {
			DosboxVersion dbversion = dbversionsList_.get(index);
			dbversion.setDefault(!dbversion.isDefault());
			try {
				new DosboxVersionRepository().update(dbversion);
				updateDosboxVersionList(dbversion);
			} catch (SQLException e) {
				dbversion.setDefault(!dbversion.isDefault());
				GeneralPurposeDialogs.warningMessage(shell_, e);
			}
		}
	}

	private void doToggleDefaultTemplate() {
		int index = templateTable_.getSelectionIndex();
		if (index != -1) {
			Template template = templatesList_.get(index);
			template.setDefault(!template.isDefault());
			try {
				String warningsLog = template.resetAndLoadConfiguration();
				if (StringUtils.isNotBlank(warningsLog))
					GeneralPurposeDialogs.warningMessage(shell_, warningsLog);
				new TemplateRepository().update(template);
				updateTemplateList(template);
			} catch (SQLException | IOException e) {
				template.setDefault(!template.isDefault());
				GeneralPurposeDialogs.warningMessage(shell_, e);
			}
		}
	}

	private void doRemoveProfile() {
		int index = profileTable_.getSelectionIndex();
		if (index != -1) {
			if (new DeleteProfilesDialog(shell_, getSelectedProfiles()).open() != null) {
				int[] idxs = profileTable_.getSelectionIndices();
				Arrays.sort(idxs);
				for (int i = idxs.length - 1; i >= 0; i--) {
					profileTable_.remove(idxs[i]);
					profilesList_.remove(idxs[i]);
				}
				if (idxs[0] > 0)
					profileTable_.setSelection(idxs[0] - 1);
				displayProfileInformation(false);
			}
		}
	}

	private void doRemoveDosboxVersion() {
		int index = dbversionTable_.getSelectionIndex();
		if ((index != -1) && GeneralPurposeDialogs.confirmMessage(shell_, text_.get("dialog.main.confirm.removedosboxversion"))) {
			try {
				new DosboxVersionRepository().remove(dbversionsList_.get(index));
				dbversionTable_.remove(index);
				dbversionsList_.remove(index);
			} catch (SQLException e) {
				GeneralPurposeDialogs.warningMessage(shell_, e);
			}
		}
	}

	private void doRemoveTemplate() {
		int index = templateTable_.getSelectionIndex();
		if ((index != -1) && GeneralPurposeDialogs.confirmMessage(shell_, text_.get("dialog.main.confirm.removetemplate"))) {
			Template template = templatesList_.get(index);
			boolean removeConfig = GeneralPurposeDialogs.confirmMessage(shell_, text_.get("dialog.main.confirm.removetemplateconf", new Object[] {template.getConfigurationFile().getPath()}));
			try {
				new TemplateRepository().remove(template, removeConfig);
				templateTable_.remove(index);
				templatesList_.remove(index);
			} catch (SQLException e) {
				GeneralPurposeDialogs.warningMessage(shell_, e);
			}
		}
	}

	private void doDuplicateProfile() {
		int index = profileTable_.getSelectionIndex();
		if (index != -1) {
			try {
				Profile profile = profilesList_.get(index);
				String warnings = profile.resetAndLoadConfiguration();
				if (StringUtils.isNotEmpty(warnings))
					GeneralPurposeDialogs.warningMessage(shell_, warnings);
				Profile duplicate = new ProfileRepository().duplicate(profile);
				updateWithAddedProfile(duplicate);
			} catch (SQLException | IOException e) {
				GeneralPurposeDialogs.warningMessage(shell_, e);
			}
		}
	}

	private void doDuplicateTemplate() {
		int index = templateTable_.getSelectionIndex();
		if (index != -1) {
			try {
				Template template = templatesList_.get(index);
				String warnings = template.resetAndLoadConfiguration();
				if (StringUtils.isNotEmpty(warnings))
					GeneralPurposeDialogs.warningMessage(shell_, warnings);
				Template duplicate = new TemplateRepository().duplicate(template);
				updateTemplateList(duplicate);
			} catch (SQLException | IOException e) {
				GeneralPurposeDialogs.warningMessage(shell_, e);
			}
		}
	}

	private void doRunProfile(ProfileRunMode mode, boolean prepareOnly) {
		int index = profileTable_.getSelectionIndex();
		if (index != -1) {
			Profile prof = profilesList_.get(index);
			if (mode != ProfileRunMode.SETUP || prof.hasSetup()) {
				try {
					String warningsLog = prof.resetAndLoadConfiguration();
					if (StringUtils.isNotEmpty(warningsLog))
						GeneralPurposeDialogs.warningMessage(shell_, warningsLog);
					ExecuteUtils.doRunProfile(mode, prof, prepareOnly, display_);
					if (mode == ProfileRunMode.NORMAL) {
						prof = new ProfileRepository().registerRun(prof);
					} else if (mode == ProfileRunMode.SETUP) {
						prof = new ProfileRepository().registerSetup(prof);
					}
					updateProfileListAfterEdit(index, prof);
				} catch (IOException | SQLException e) {
					GeneralPurposeDialogs.warningMessage(shell_, e);
				}
			}
		}
	}

	private void doRunDosbox() {
		int index = dbversionTable_.getSelectionIndex();
		if (index != -1) {
			DosboxVersion dbversion = dbversionsList_.get(index);
			try {
				ExecuteUtils.doRunDosbox(dbversion);
				new DosboxVersionRepository().registerRun(dbversion);
				updateDosboxVersionList(dbversion);
			} catch (IOException | SQLException e) {
				GeneralPurposeDialogs.warningMessage(shell_, e);
			}
		}
	}

	private void doRunTemplate() {
		int index = templateTable_.getSelectionIndex();
		if (index != -1) {
			Template template = templatesList_.get(index);
			try {
				String warningsLog = template.resetAndLoadConfiguration();
				if (StringUtils.isNotEmpty(warningsLog))
					GeneralPurposeDialogs.warningMessage(shell_, warningsLog);
				ExecuteUtils.doRunTemplate(template, display_);
				new TemplateRepository().registerRun(template);
				updateTemplateList(template);
			} catch (IOException | SQLException e) {
				GeneralPurposeDialogs.warningMessage(shell_, e);
			}
		}
	}

	private DosboxVersion requireDefaultDBVersion() {
		DosboxVersion dbv = DosboxVersionRepository.findDefault(dbversionsList_);
		if (dbv == null)
			GeneralPurposeDialogs.infoMessage(shell_, text_.get("dialog.main.required.defaultdosboxversion"));
		return dbv;
	}

	private void doOpenSettingsDialog() {
		if (new SettingsDialog(shell_).open())
			rebuildProfilesTable();
		notesField_.setFont(stringToFont(display_, settings_.getValues("gui", "notesfont"), notesField_.getFont()));
	}

	private void doImportProfiles(boolean defaultToGamepacks) {
		List<String> names = new ArrayList<>(Arrays.asList(text_.get("filetype.conf"), text_.get("filetype.exe") + ", " + text_.get("filetype.booterimage"), FilesUtils.ALL_FILTER));
		List<String> extensions = new ArrayList<>(Arrays.asList(FilesUtils.CNF_FILTER, FilesUtils.EXE_FILTER + ";" + FilesUtils.BTR_FILTER, FilesUtils.ALL_FILTER));
		if (defaultToGamepacks) {
			names.add(0, text_.get("filetype.gamepack"));
			extensions.add(0, FilesUtils.ARC_FILTER);
		}
		FileDialog dialog = new FileDialog(shell_, SWT.OPEN);
		dialog.setFilterNames(names.toArray(new String[0]));
		dialog.setFilterExtensions(extensions.toArray(new String[0]));
		String result = dialog.open();
		if (result != null)
			doAddProfile(result);
	}

	private void doMigrate() {
		GeneralPurposeDialogs.infoMessage(shell_, text_.get("dialog.main.notice.premigration"));
		String from = new MigrateDialog(shell_).open();
		if (from != null) {
			updateProfilesList(getSelectedProfileIds());
			displayProfileInformation(true);
			GeneralPurposeDialogs.infoMessage(shell_, text_.get("dialog.main.notice.postmigration", new Object[] {from, FileLocationService.getInstance().getDosroot()}));
		}
	}

	private void doLocateDosbox(boolean interactive) {
		SearchResult result = FileLocationService.getInstance().findDosbox();
		if (result.result_ == ResultType.NOTFOUND) {
			GeneralPurposeDialogs.warningMessage(shell_, text_.get("dialog.locatedosbox.notice.notfound"));
			return;
		}

		if (result.result_ == ResultType.COMPLETE && !interactive) {
			try {
				DosboxVersion newDbversion = new DosboxVersionRepository().add(result.dosbox_);
				updateDosboxVersionList(newDbversion);
				GeneralPurposeDialogs.infoMessage(null, text_.get("dialog.locatedosbox.notice.foundandadded"));
			} catch (SQLException e) {
				GeneralPurposeDialogs.warningMessage(shell_, e);
			}
		} else {
			DosboxVersion dbversion = new EditDosboxVersionDialog(shell_, DosboxVersionRepository.findDefault(dbversionsList_) == null, result.dosbox_).open();
			if (dbversion != null)
				updateDosboxVersionList(dbversion);
		}
	}

	private void doImportDefaultTemplates(boolean interactive) {
		if (!interactive || GeneralPurposeDialogs.confirmMessage(shell_, text_.get("dialog.importdefaulttemplates.confirm.start"))) {
			if (requireDefaultDBVersion() == null)
				return;

			try {
				List<Template> importedTemplates = new ArrayList<>();
				String warnings = ImportExportTemplatesService.doImport(importedTemplates);
				if (StringUtils.isNotEmpty(warnings)) {
					GeneralPurposeDialogs.warningMessage(shell_, warnings);
				} else {
					if (interactive)
						GeneralPurposeDialogs.infoMessage(shell_, text_.get("dialog.import.notice.importok"));
				}
				if (!importedTemplates.isEmpty())
					updateTemplateList(importedTemplates.get(0));
			} catch (XPathExpressionException | SAXException e) {
				GeneralPurposeDialogs.fatalMessage(shell_, text_.get("dialog.importdefaulttemplates.error.defaultxmlinvalidformat", new Object[] {e.toString()}), e);
			} catch (Exception e) {
				GeneralPurposeDialogs.fatalMessage(shell_, e.toString(), e);
			}
		}
	}

	public static void openSendToProfileDialog(String file) {
		Shell shell = new Shell();
		shell.setMinimized(true);
		shell.open();
		try {
			List<DosboxVersion> dbversionsList = new DosboxVersionRepository().listAll();
			if (DosboxVersionRepository.findDefault(dbversionsList) == null) {
				GeneralPurposeDialogs.infoMessage(shell, TextService.getInstance().get("dialog.main.required.defaultdosboxversion"));
			} else {
				if (FilesUtils.isGamePackArchiveFile(file))
					new ImportDialog(shell, dbversionsList, new File(file)).open();
				else
					new EditSingleProfileDialog(shell, null, file, false).open();
			}
		} catch (SQLException e) {
			GeneralPurposeDialogs.warningMessage(shell, e);
		} finally {
			try {
				DatabaseService.getInstance().shutdown();
			} catch (SQLException e) {
				// nothing we can do
			}
		}
	}

	private List<Profile> getSelectedProfiles() {
		return Arrays.stream(profileTable_.getSelectionIndices()).mapToObj(x -> profilesList_.get(x)).collect(Collectors.toList());
	}

	private Set<Integer> getSelectedProfileIds() {
		return getSelectedProfiles().stream().map(x -> x.getId()).collect(Collectors.toSet());
	}

	private int[] getIndicesByIds(Set<Integer> profileIds) {
		return IntStream.range(0, profilesList_.size()).filter(x -> profileIds.contains(profilesList_.get(x).getId())).toArray();
	}

	private final SelectionAdapter addProfAdapter = new SelectionAdapter() {
		public void widgetSelected(SelectionEvent event) {
			doAddProfile(null);
		}
	};
	private final SelectionAdapter editProfAdapter = new SelectionAdapter() {
		public void widgetSelected(SelectionEvent event) {
			doEditProfile(false);
		}
	};
	private final SelectionAdapter removeProfAdapter = new SelectionAdapter() {
		public void widgetSelected(SelectionEvent event) {
			doRemoveProfile();
		}
	};
	private final SelectionAdapter setupProfAdapter = new SelectionAdapter() {
		public void widgetSelected(SelectionEvent event) {
			doRunProfile(ProfileRunMode.SETUP, false);
		}
	};
	private final SelectionAdapter runProfAdapter = new SelectionAdapter() {
		public void widgetSelected(SelectionEvent event) {
			doRunProfile(ProfileRunMode.NORMAL, false);
		}
	};
}
