/*
 *  Copyright (C) 2006-2020  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.abstractdialog;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.ArrayUtils;
import org.dbgl.exception.DrivelettersExhaustedException;
import org.dbgl.gui.controls.Button_;
import org.dbgl.gui.controls.Chain;
import org.dbgl.gui.controls.Composite_;
import org.dbgl.gui.controls.DaControl;
import org.dbgl.gui.controls.DaControlConvertorAdapter;
import org.dbgl.gui.controls.Group_;
import org.dbgl.gui.controls.List_;
import org.dbgl.gui.controls.Mess_;
import org.dbgl.gui.controls.MetaControl;
import org.dbgl.gui.dialog.EditMixerDialog;
import org.dbgl.gui.dialog.EditMountDialog;
import org.dbgl.gui.dialog.EditNativeCommandDialog;
import org.dbgl.gui.interfaces.DaControlConvertor;
import org.dbgl.model.NativeCommand;
import org.dbgl.model.aggregate.DosboxVersion;
import org.dbgl.model.conf.Autoexec;
import org.dbgl.model.conf.Configuration;
import org.dbgl.model.entity.TemplateProfileBase;
import org.dbgl.model.helper.DriveLetterHelper;
import org.dbgl.model.repository.DosboxVersionRepository;
import org.dbgl.service.FileLocationService;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ExpandAdapter;
import org.eclipse.swt.events.ExpandEvent;
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.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.ExpandBar;
import org.eclipse.swt.widgets.ExpandItem;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.Text;


public abstract class EditConfigurableDialog<T> extends SizeControlledTabbedDialog<T> {

	protected enum DosboxConfAction {
		SET, SWITCH, RELOAD, RELOAD_TEMPLATE
	}

	protected final List<DaControl> daControls_ = new ArrayList<>();
	protected final List<MetaControl> metaControls_ = new ArrayList<>();
	protected final List<MetaControl> autoexecControls_ = new ArrayList<>();

	protected List<DosboxVersion> dbversionsList_;
	protected int dbversionIndex_;
	protected Combo dbversionCombo_;
	protected Button setButton_;
	protected ExpandItem booterExpandItem_, dosExpandItem_;
	protected org.eclipse.swt.widgets.List mountingpointsList_;

	private Button switchButton_, ipx_;
	private Text ipxNet_;
	private Combo output_, scaler_, machine_, cpuType_, midiDevice_, sbType_, oplMode_;
	private org.eclipse.swt.widgets.List nativeCommandsList_;

	public EditConfigurableDialog(Shell parent, String dialogName) {
		super(parent, dialogName);
	}

	abstract protected void doPerformDosboxConfAction(DosboxConfAction action, DosboxVersion newDosboxVersion);

	@Override
	protected boolean prepare() {
		try {
			dbversionsList_ = new DosboxVersionRepository().listAll();
			dbversionIndex_ = DosboxVersionRepository.indexOfDefault(dbversionsList_);
			return true;
		} catch (SQLException e) {
			Mess_.on(getParent()).exception(e).warning();
			return false;
		}
	}

	protected void updateControlsByConfigurable(TemplateProfileBase configurable, boolean multiEdit) {
		Configuration overrideConf = configurable.getConfiguration();
		Autoexec overrideAuto = overrideConf.getAutoexec();
		Configuration combinedConf = configurable.getCombinedConfiguration();
		Autoexec combinedAuto = combinedConf.getAutoexec();
		DosboxVersion dosbox = configurable.getDosboxVersion();
		Configuration dosboxConf = dosbox.getConfiguration();

		// enable or disable controls
		daControls_.forEach(x -> x.enableOrDisable(dosbox));

		// set possible values for certain dropdowns
		String[] machineValues = dosbox.isUsingNewMachineConfig() ? settings_.getValues("profile", "machine073"): settings_.getValues("profile", "machine");
		if (!Arrays.equals(machine_.getItems(), machineValues)) {
			machine_.setItems(machineValues);
		}

		updateComboItems(output_, dosbox.likelyDirect3DSupport(), new String[] {"openglhq", "direct3d"});
		updateComboItems(scaler_, dosbox.likelyHardwareScalerSupport(), new String[] {"hardware2x", "hardware3x"});
		updateComboItems(machine_, dosbox.likelyAmstradSupport(), new String[] {"cga_mono", "svga_s3_full", "amstrad"});
		updateComboItems(cpuType_, dosbox.likelyAdditionalCPUTypesSupport(), new String[] {"486", "pentium", "pentium_mmx"});
		updateComboItems(midiDevice_, dosbox.hasMT32Support(), new String[] {"mt32", "synth", "timidity"});
		updateComboItems(sbType_, dosbox.likelySoundBlaster16VibraSupport(), new String[] {"sb16vibra"});
		updateComboItems(oplMode_, dosbox.hasHardwareOPLSupport(), new String[] {"hardware", "hardwaregb"});

		// set control values
		daControls_.forEach(x -> x.setControlByConf(dosboxConf, overrideConf, combinedConf, multiEdit));

		DaControl.setFieldIfEnabled(mountingpointsList_, configurable.getMountStringsForUI());
		DaControl.setFieldIfEnabled(dosExpandItem_, !combinedAuto.isBooter());
		DaControl.setFieldIfEnabled(booterExpandItem_, combinedAuto.isBooter());

		autoexecControls_.forEach(x -> x.setControlByAutoexec(overrideAuto, combinedAuto, multiEdit));

		setButton_.setEnabled(false);
		switchButton_.setEnabled(false);

		ipxNet_.setEnabled(ipx_.getSelection());

		updateNativeCommands(-1, configurable);
	}

	protected void updateConfigurableByControls(TemplateProfileBase configurable) {
		daControls_.forEach(x -> x.updateConfigurationByControl(configurable));
		autoexecControls_.forEach(x -> x.updateAutoexecByControl(configurable.getConfiguration().getAutoexec()));

		configurable.setBooter(booterExpandItem_.getExpanded());
	}

	protected void updateComboItems(Combo combo, boolean available, String[] items) {
		List<String> comboItems = new ArrayList<>(Arrays.asList(combo.getItems()));
		boolean changes = false;
		if (available) {
			for (String s: items) {
				if (!comboItems.contains(s)) {
					comboItems.add(s);
					changes = true;
				}
			}
		} else {
			for (String s: items) {
				if (comboItems.contains(s)) {
					comboItems.remove(s);
					changes = true;
				}
			}
		}
		if (changes) {
			combo.setItems(comboItems.toArray(new String[comboItems.size()]));
		}
	}

	protected Group createGeneralTab(String capturesText, String configFileText) {
		Composite composite = createTabWithComposite("dialog.template.tab.general", new GridLayout());

		Group associationGroup = Group_.on(composite).layout(new GridLayout(5, false)).key("dialog.template.association").build();
		associationGroup.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		dbversionCombo_ = Chain.on(associationGroup).lbl(l -> l.key("dialog.template.dosboxversion")).cmb(
			c -> c.wide().items(dbversionsList_.stream().map(x -> x.getTitle()).toArray(String[]::new)).select(dbversionIndex_).visibleItemCount(20)).combo();
		setButton_ = Button_.on(associationGroup).text().key("dialog.template.set").tooltip("dialog.template.set.tooltip").listen(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doPerformDosboxConfAction(DosboxConfAction.SET, dbversionsList_.get(dbversionCombo_.getSelectionIndex()));
			}
		}).ctrl();
		switchButton_ = Button_.on(associationGroup).text().key("dialog.template.switch").tooltip("dialog.template.switch.tooltip").listen(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doPerformDosboxConfAction(DosboxConfAction.SWITCH, dbversionsList_.get(dbversionCombo_.getSelectionIndex()));
			}
		}).ctrl();
		Button_.on(associationGroup).text().key("dialog.template.reloadsettings").tooltip("dialog.template.reloadsettings.tooltip").listen(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doPerformDosboxConfAction(DosboxConfAction.RELOAD, dbversionsList_.get(dbversionCombo_.getSelectionIndex()));
			}
		}).ctrl();
		dbversionCombo_.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				setButton_.setEnabled(true);
				switchButton_.setEnabled(true);
			}
		});

		Group miscGroup = Group_.on(composite).layout(new GridLayout(3, false)).key("dialog.template.miscellaneous").build();
		miscGroup.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		Chain.on(miscGroup).lbl(l -> l).lbl(l -> l.key("dialog.template.active")).lbl(l -> l.key("dialog.template.inactive")).build();
		Chain.on(miscGroup).lbl(l -> l.key("dialog.template.priority")).cmb(c -> c.tooltip("dialog.template.priority.tooltip").items(settings_.getValues("profile", "priority_active"))).cmb(
			c -> c.tooltip("dialog.template.priority.tooltip").items(settings_.getValues("profile", "priority_inactive"))).da(daControls_).section("sdl").item("priority").build();
		Chain.on(miscGroup).lbl(l -> l.key("dialog.template.waitonerror")).but(b -> b.horSpan(2)).da(daControls_).section("sdl").item("waitonerror").build();
		Chain.on(miscGroup).lbl(l -> l.key("dialog.template.exitafterwards")).but(b -> b.horSpan(2)).meta(autoexecControls_).autoexec(Autoexec::getExit, Autoexec::setExit).build();
		Chain.on(miscGroup).lbl(l -> l.key("dialog.template.languagefile")).txt(t -> t.horSpan(2).tooltip("dialog.template.languagefile.tooltip")).da(daControls_).section("dosbox").item(
			"language").build();
		Chain.on(miscGroup).lbl(l -> l.key("dialog.template.captures")).txt(t -> t.horSpan(2).tooltip("dialog.template.captures.tooltip").val(capturesText).nonEditable()).build();
		Chain.on(miscGroup).lbl(l -> l.key("dialog.profile.configfile")).txt(t -> t.horSpan(2).val(configFileText).nonEditable()).build();

		return associationGroup;
	}

	protected void createDisplayTab() {
		TabFolder subTabFolder = createSubTabs("dialog.template.tab.display", 1, 2);
		Composite releaseComposite = (Composite)subTabFolder.getChildren()[0];
		Composite experimentalComposite = (Composite)subTabFolder.getChildren()[1];

		Group groupRelease = Group_.on(releaseComposite).layout(new GridLayout(4, false)).key("dialog.template.general").build();
		groupRelease.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false));

		output_ = Chain.on(groupRelease).lbl(l -> l.key("dialog.template.output")).cmb(c -> c.horSpan(3).tooltip("dialog.template.output.tooltip").items(settings_.getValues("profile", "output"))).da(
			daControls_).section("sdl").item("output").combo();
		Chain.on(groupRelease).lbl(l -> l.key("dialog.template.frameskip")).cmb(
			c -> c.horSpan(3).tooltip("dialog.template.frameskip.tooltip").items(settings_.getValues("profile", "frameskip")).visibleItemCount(15)).da(daControls_).section("render").item(
				"frameskip").build();
		scaler_ = Chain.on(groupRelease).lbl(l -> l.key("dialog.template.scaler")).cmb(
			c -> c.tooltip("dialog.template.scaler.tooltip").items(settings_.getValues("profile", "scaler")).visibleItemCount(15)).lbl(l -> l.key("dialog.template.scalerforced")).but(
				b -> b.tooltip("dialog.template.scalerforced.tooltip")).da(daControls_).section("render").item("scaler").convert(new DaControlConvertorAdapter() {
					public String toConfValue(String[] values) {
						String result = values[0];
						if (Boolean.valueOf(values[1]))
							result += " forced";
						return result;
					}

					public String[] toControlValues(String value) {
						if (value == null)
							return new String[0];
						String[] results = new String[2];
						if (value.endsWith("forced")) {
							results[0] = value.substring(0, value.length() - 7);
							results[1] = "true";
						} else {
							results[0] = value;
							results[1] = "false";
						}
						return results;
					}
				}).combo();

		Chain.on(groupRelease).lbl(l -> l.key("dialog.template.fullscreenresolution")).cmb(
			c -> c.horSpan(3).tooltip("dialog.template.fullscreenresolution.tooltip").items(settings_.getValues("profile", "fullresolution"))).da(daControls_).section("sdl").item(
				"fullresolution").build();
		Chain.on(groupRelease).lbl(l -> l.key("dialog.template.windowresolution")).cmb(
			c -> c.horSpan(3).tooltip("dialog.template.windowresolution.tooltip").items(settings_.getValues("profile", "windowresolution"))).da(daControls_).section("sdl").item(
				"windowresolution").build();
		Chain.on(groupRelease).lbl(l -> l.key("dialog.template.fullscreen")).but(b -> b.horSpan(3).tooltip("dialog.template.fullscreen.tooltip")).da(daControls_).section("sdl").item(
			"fullscreen").build();
		Chain.on(groupRelease).lbl(l -> l.key("dialog.template.doublebuffering")).but(b -> b.horSpan(3).tooltip("dialog.template.doublebuffering.tooltip")).da(daControls_).section("sdl").item(
			"fulldouble").build();
		Chain.on(groupRelease).lbl(l -> l.key("dialog.template.aspectcorrection")).but(b -> b.horSpan(3).tooltip("dialog.template.aspectcorrection.tooltip")).da(daControls_).section("render").item(
			"aspect").build();

		Group groupExpGeneral = Group_.on(experimentalComposite).layout(new GridLayout(3, false)).key("dialog.template.general").build();
		groupExpGeneral.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 3));
		String[] glShaderFiles = FileLocationService.getInstance().listGlShaderFilenames();
		String[] glShaders = (glShaderFiles != null && glShaderFiles.length > 0) ? ArrayUtils.addAll(settings_.getValues("profile", "glshader"), glShaderFiles)
				: settings_.getValues("profile", "glshader");
		Chain.on(groupExpGeneral).lbl(l -> l.key("dialog.template.glshader")).cmb(c -> c.editable().wide().items(glShaders)).but(
			b -> b.browse(true, Button_.BrowseType.FILE, Button_.CanonicalType.GLSHADER, false)).da(daControls_).section("render").item("glshader").build();
		Chain.on(groupExpGeneral).lbl(l -> l.key("dialog.template.autofit")).but(b -> b.horSpan(2)).da(daControls_).section("render").item("autofit").build();
		Combo pixelshader = Chain.on(groupExpGeneral).lbl(l -> l.key("dialog.template.pixelshader")).cmb(c -> c.horSpan(2).visibleItemCount(20)).da(daControls_).section("sdl").item(
			"pixelshader").combo();
		String[] shaders = FileLocationService.getInstance().listShaderFilenames();
		if (shaders != null && shaders.length > 0) {
			pixelshader.setItems(shaders);
			pixelshader.add("none", 0);
		} else {
			pixelshader.setItems(settings_.getValues("profile", "pixelshader"));
		}
		Chain.on(groupExpGeneral).lbl(l -> l.key("dialog.template.linewise")).but(b -> b.horSpan(2)).da(daControls_).section("render").item("linewise").build();
		Chain.on(groupExpGeneral).lbl(l -> l.key("dialog.template.char9")).but(b -> b.horSpan(2)).da(daControls_).section("render").item("char9").build();
		Chain.on(groupExpGeneral).lbl(l -> l.key("dialog.template.multiscan")).but(b -> b.horSpan(2)).da(daControls_).section("render").item("multiscan").build();
		Chain.on(groupExpGeneral).lbl(l -> l.key("dialog.template.cgasnow")).but(b -> b.horSpan(2)).da(daControls_).section("cpu").item("cgasnow").build();
		Chain.on(groupExpGeneral).lbl(l -> l.key("dialog.template.overscan")).cmb(c -> c.horSpan(2).editable().items(settings_.getValues("profile", "overscan"))).da(daControls_).section("sdl").item(
			"overscan").build();
		Chain.on(groupExpGeneral).lbl(l -> l.key("dialog.template.vsyncmode")).cmb(c -> c.horSpan(2).items(settings_.getValues("profile", "vsyncmode"))).da(daControls_).section("vsync").item(
			"vsyncmode").build();
		Chain.on(groupExpGeneral).lbl(l -> l.key("dialog.template.vsyncrate")).txt(t -> t.horSpan(2)).da(daControls_).section("vsync").item("vsyncrate").build();
		Chain.on(groupExpGeneral).lbl(l -> l.key("dialog.template.forcerate")).txt(t -> t.horSpan(2)).da(daControls_).section("cpu").item("forcerate").build();
		Chain.on(groupExpGeneral).lbl(l -> l.key("dialog.template.videoram")).cmb(c -> c.horSpan(2).items(settings_.getValues("profile", "vmemsize"))).da(daControls_).section("dosbox").item(
			"vmemsize").build();
		Chain.on(groupExpGeneral).lbl(l -> l.key("dialog.template.fullborderless")).but(b -> b.horSpan(2).tooltip("dialog.template.fullborderless.tooltip")).da(daControls_).section("sdl").item(
			"fullborderless").build();
		Chain.on(groupExpGeneral).lbl(l -> l.key("dialog.template.glfullvsync")).but(b -> b.horSpan(2).tooltip("dialog.template.glfullvsync.tooltip")).da(daControls_).section("sdl").item(
			"glfullvsync").build();

		Group groupExpGlide = Group_.on(experimentalComposite).layout(new GridLayout(2, false)).key("dialog.template.glide").build();
		Chain.on(groupExpGlide).lbl(l -> l.key("dialog.template.glide")).cmb(c -> c.items(settings_.getValues("profile", "glide"))).da(daControls_).section("glide").item("glide").build();
		Chain.on(groupExpGlide).lbl(l -> l.key("dialog.template.glideport")).txt(t -> t).da(daControls_).section("glide").item("port", "grport").build();
		Chain.on(groupExpGlide).lbl(l -> l.key("dialog.template.lfbglide")).cmb(c -> c.items(settings_.getValues("profile", "lfbglide"))).da(daControls_).section("glide").item("lfb").build();
		Chain.on(groupExpGlide).lbl(l -> l.key("dialog.template.splash3dfx")).but(b -> b).da(daControls_).section("glide").item("splash").build();

		Group groupExpVoodoo = Group_.on(experimentalComposite).layout(new GridLayout(2, false)).key("dialog.template.voodoo").build();
		Chain.on(groupExpVoodoo).lbl(l -> l.key("dialog.template.voodoo")).cmb(c -> c.items(settings_.getValues("profile", "voodoo"))).da(daControls_).section("pci").item("voodoo").build();
		Chain.on(groupExpVoodoo).lbl(l -> l.key("dialog.template.voodoomem")).cmb(c -> c.items(settings_.getValues("profile", "voodoomem"))).da(daControls_).section("pci").item("voodoomem").build();

		Group groupExpPPScaling = Group_.on(experimentalComposite).layout(new GridLayout(2, false)).key("dialog.template.pixelperfectscaling").build();
		Chain.on(groupExpPPScaling).lbl(l -> l.key("dialog.template.surfacenpsharpness")).spn(s -> s.min(0).max(100)).da(daControls_).section("sdl").item("surfacenp-sharpness").build();
	}

	protected Group createMachineTab() {
		TabFolder subTabFolder = createSubTabs("dialog.template.tab.machine", 1, 1);
		Composite releaseComposite = (Composite)subTabFolder.getChildren()[0];
		Composite experimentalComposite = (Composite)subTabFolder.getChildren()[1];

		Group cpuGroup = Group_.on(releaseComposite).layout(new GridLayout(6, false)).key("dialog.template.cpu").build();
		machine_ = Chain.on(cpuGroup).lbl(l -> l.key("dialog.template.machine")).cmb(c -> c.tooltip("dialog.template.machine.tooltip").items(new String[] {}).visibleItemCount(20)).da(
			daControls_).section("dosbox").item("machine").combo();
		cpuType_ = Chain.on(cpuGroup).lbl(l -> l.key("dialog.template.cputype")).cmb(c -> c.horSpan(3).tooltip("dialog.template.cputype.tooltip").items(settings_.getValues("profile", "cputype"))).da(
			daControls_).section("cpu").item("cputype").combo();
		Chain.on(cpuGroup).lbl(l -> l.key("dialog.template.core")).cmb(c -> c.horSpan(5).tooltip("dialog.template.core.tooltip").items(settings_.getValues("profile", "core"))).da(daControls_).section(
			"cpu").item("core").build();
		Chain.on(cpuGroup).lbl(l -> l.key("dialog.template.cycles")).cmb(
			c -> c.editable().tooltip("dialog.template.cycles.tooltip").items(settings_.getValues("profile", "cycles")).visibleItemCount(15)).da(daControls_).section("cpu").item("cycles").build();
		Chain.on(cpuGroup).lbl(l -> l.key("dialog.template.up")).cmb(c -> c.editable().tooltip("dialog.template.up.tooltip").items(settings_.getValues("profile", "cycles_up"))).da(
			daControls_).section("cpu").item("cycleup").build();
		Chain.on(cpuGroup).lbl(l -> l.key("dialog.template.down")).cmb(c -> c.editable().tooltip("dialog.template.down.tooltip").items(settings_.getValues("profile", "cycles_down"))).da(
			daControls_).section("cpu").item("cycledown").build();

		Group memoryGroup = Group_.on(releaseComposite).layout(new GridLayout(4, false)).key("dialog.template.memory").build();
		Chain.on(memoryGroup).lbl(l -> l.key("dialog.template.memorysize")).cmb(c -> c.horSpan(3).tooltip("dialog.template.memorysize.tooltip").items(settings_.getValues("profile", "memsize"))).da(
			daControls_).section("dosbox").item("memsize").build();
		Chain.on(memoryGroup).lbl(l -> l.key("dialog.template.xms")).but(b -> b.horSpan(3).tooltip("dialog.template.xms.tooltip")).da(daControls_).section("dos").item("xms").build();
		Chain.on(memoryGroup).lbl(l -> l.key("dialog.template.ems")).cmb(c -> c.horSpan(3).tooltip("dialog.template.ems.tooltip").items(settings_.getValues("profile", "ems"))).da(daControls_).section(
			"dos").item("ems").build();
		Chain.on(memoryGroup).lbl(l -> l.key("dialog.template.umb")).cmb(c -> c.horSpan(3).tooltip("dialog.template.umb.tooltip").items(settings_.getValues("profile", "umb"))).da(daControls_).section(
			"dos").item("umb").build();

		Group expMemoryGroup = Group_.on(experimentalComposite).layout(new GridLayout(2, false)).key("dialog.template.memory").build();
		Chain.on(expMemoryGroup).lbl(l -> l.key("dialog.template.memorysizekb")).txt(t -> t).da(daControls_).section("dosbox").item("memsizekb").build();
		Chain.on(expMemoryGroup).lbl(l -> l.key("dialog.template.memalias")).cmb(c -> c.editable().items(settings_.getValues("profile", "memalias"))).da(daControls_).section("dosbox").item(
			"memalias").build();

		return memoryGroup;
	}

	protected void createAudioTab() {
		TabFolder subTabFolder = createSubTabs("dialog.template.tab.audio", 3, 3);
		Composite releaseComposite = (Composite)subTabFolder.getChildren()[0];
		Composite experimentalComposite = (Composite)subTabFolder.getChildren()[1];

		Group generalGroup = Group_.on(releaseComposite).layout(new GridLayout(3, false)).key("dialog.template.general").build();
		generalGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		Chain.on(generalGroup).lbl(l -> l.key("dialog.template.silentmode")).but(b -> b.horSpan(2).tooltip("dialog.template.silentmode.tooltip")).da(daControls_).section("mixer").item(
			"nosound").build();
		Chain.on(generalGroup).lbl(l -> l.key("dialog.template.samplerate")).cmb(c -> c.horSpan(2).tooltip("dialog.template.samplerate.tooltip").items(settings_.getValues("profile", "rate"))).da(
			daControls_).section("mixer").item("rate").build();
		Chain.on(generalGroup).lbl(l -> l.key("dialog.template.blocksize")).cmb(c -> c.horSpan(2).tooltip("dialog.template.blocksize.tooltip").items(settings_.getValues("profile", "blocksize"))).da(
			daControls_).section("mixer").item("blocksize").build();
		Chain.on(generalGroup).lbl(l -> l.key("dialog.template.prebuffer")).cmb(
			c -> c.horSpan(2).editable().tooltip("dialog.template.prebuffer.tooltip").items(settings_.getValues("profile", "prebuffer"))).da(daControls_).section("mixer").item("prebuffer").build();
		Chain.on(generalGroup).lbl(l -> l.key("dialog.template.mpu401")).cmb(c -> c.horSpan(2).tooltip("dialog.template.mpu401.tooltip").items(settings_.getValues("profile", "mpu401"))).da(
			daControls_).section("midi", "midi").item("intelligent", "mpu401").convert(new DaControlConvertorAdapter() {
				public String toConfValue(String[] values) {
					return values[0];
				}

				public String[] toControlValues(String value) {
					if (value == null)
						return new String[0];
					return new String[] {value};
				}

				public String[] toConfValues(String[] values) {
					String[] result = new String[2];
					result[0] = String.valueOf(!values[0].equalsIgnoreCase("none"));
					result[1] = String.valueOf(!values[0].equalsIgnoreCase("uart"));
					return result;
				}

				public String[] toControlValues(String[] values) {
					boolean intelligent = Boolean.valueOf(values[0]);
					boolean mpu = Boolean.valueOf(values[1]);
					return new String[] {mpu ? intelligent ? "intelligent": "uart": "none"};
				}
			}).build();

		midiDevice_ = Chain.on(generalGroup).lbl(l -> l.key("dialog.template.mididevice")).cmb(
			c -> c.horSpan(2).tooltip("dialog.template.mididevice.tooltip").items(settings_.getValues("profile", "device"))).da(daControls_).section("midi").item("device", "mididevice").combo();
		Chain.on(generalGroup).lbl(l -> l.key("dialog.template.midiconfig")).txt(t -> t.horSpan(2).tooltip("dialog.template.midiconfig.tooltip")).da(daControls_).section("midi").item("config",
			"midiconfig").build();

		Chain chnMixer = Chain.on(generalGroup).lbl(l -> l.key("dialog.template.mixercommand")).txt(t -> t).meta(autoexecControls_).autoexec(Autoexec::getMixer, Autoexec::setMixer).but(
			b -> b.threedots()).build();
		chnMixer.getButton().addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				String command = new EditMixerDialog(shell_, chnMixer.getText().getText()).open();
				if (command != null)
					chnMixer.getText().setText(command);
			}
		});

		Group soundblasterGroup = Group_.on(releaseComposite).layout(new GridLayout(2, false)).key("dialog.template.soundblaster").build();
		sbType_ = Chain.on(soundblasterGroup).lbl(l -> l.key("dialog.template.sbtype")).cmb(c -> c.tooltip("dialog.template.sbtype.tooltip").items(settings_.getValues("profile", "sbtype"))).da(
			daControls_).section("sblaster").item("type", "sbtype").combo();
		Chain.on(soundblasterGroup).lbl(l -> l.key("dialog.template.sboplrate")).cmb(c -> c.tooltip("dialog.template.sboplrate.tooltip").items(settings_.getValues("profile", "oplrate"))).da(
			daControls_).section("sblaster").item("oplrate").build();
		oplMode_ = Chain.on(soundblasterGroup).lbl(l -> l.key("dialog.template.sboplmode")).cmb(
			c -> c.tooltip("dialog.template.sboplmode.tooltip").items(settings_.getValues("profile", "oplmode"))).da(daControls_).section("sblaster").item("oplmode").combo();
		Chain.on(soundblasterGroup).lbl(l -> l.key("dialog.template.sboplemu")).cmb(c -> c.tooltip("dialog.template.sboplemu.tooltip").items(settings_.getValues("profile", "oplemu"))).da(
			daControls_).section("sblaster").item("oplemu").build();
		Chain.on(soundblasterGroup).lbl(l -> l.key("dialog.template.sbaddress")).cmb(c -> c.tooltip("dialog.template.sbaddress.tooltip").items(settings_.getValues("profile", "sbbase"))).da(
			daControls_).section("sblaster").item("base", "sbbase").combo();
		Chain.on(soundblasterGroup).lbl(l -> l.key("dialog.template.sbirq")).cmb(c -> c.tooltip("dialog.template.sbirq.tooltip").items(settings_.getValues("profile", "irq"))).da(daControls_).section(
			"sblaster").item("irq").build();
		Chain.on(soundblasterGroup).lbl(l -> l.key("dialog.template.sbdma")).cmb(c -> c.tooltip("dialog.template.sbdma.tooltip").items(settings_.getValues("profile", "dma"))).da(daControls_).section(
			"sblaster").item("dma").build();
		Chain.on(soundblasterGroup).lbl(l -> l.key("dialog.template.sbhdma")).cmb(c -> c.tooltip("dialog.template.sbhdma.tooltip").items(settings_.getValues("profile", "hdma"))).da(
			daControls_).section("sblaster").item("hdma").build();
		Chain.on(soundblasterGroup).lbl(l -> l.key("dialog.template.mixer")).but(b -> b.tooltip("dialog.template.mixer.tooltip")).da(daControls_).section("sblaster").item("mixer", "sbmixer").build();

		Group gusGroup = Group_.on(releaseComposite).layout(new GridLayout(2, false)).key("dialog.template.gravisultrasound").build();
		gusGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		Chain.on(gusGroup).lbl(l -> l.key("dialog.template.enablegus")).but(b -> b.tooltip("dialog.template.enablegus.tooltip")).da(daControls_).section("gus").item("gus").build();
		Chain.on(gusGroup).lbl(l -> l.key("dialog.template.gusrate")).cmb(c -> c.tooltip("dialog.template.gusrate.tooltip").items(settings_.getValues("profile", "gusrate"))).da(daControls_).section(
			"gus").item("rate", "gusrate").combo();
		Chain.on(gusGroup).lbl(l -> l.key("dialog.template.gusaddress")).cmb(c -> c.tooltip("dialog.template.gusaddress.tooltip").items(settings_.getValues("profile", "gusbase"))).da(
			daControls_).section("gus").item("base", "gusbase").combo();
		Chain.on(gusGroup).lbl(l -> l.key("dialog.template.gusirq1")).cmb(c -> c.tooltip("dialog.template.gusirq1.tooltip").items(settings_.getValues("profile", "irq1"))).da(daControls_).section(
			"gus").item("irq1", "gusirq").combo();
		Chain.on(gusGroup).lbl(l -> l.key("dialog.template.gusirq2")).cmb(c -> c.tooltip("dialog.template.gusirq1.tooltip").items(settings_.getValues("profile", "irq2"))).da(daControls_).section(
			"gus").item("irq2").build();
		Chain.on(gusGroup).lbl(l -> l.key("dialog.template.gusdma1")).cmb(c -> c.tooltip("dialog.template.gusdma1.tooltip").items(settings_.getValues("profile", "dma1"))).da(daControls_).section(
			"gus").item("dma1", "gusdma").combo();
		Chain.on(gusGroup).lbl(l -> l.key("dialog.template.gusdma2")).cmb(c -> c.tooltip("dialog.template.gusdma1.tooltip").items(settings_.getValues("profile", "dma2"))).da(daControls_).section(
			"gus").item("dma2").build();
		Chain.on(gusGroup).lbl(l -> l.key("dialog.template.ultradir")).txt(t -> t.tooltip("dialog.template.ultradir.tooltip")).da(daControls_).section("gus").item("ultradir").text();

		Group speakerGroup = Group_.on(releaseComposite).layout(new GridLayout(2, false)).key("dialog.template.pcspeaker").build();
		speakerGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		Chain.on(speakerGroup).lbl(l -> l.key("dialog.template.enablepcspeaker")).but(b -> b.tooltip("dialog.template.enablepcspeaker.tooltip")).da(daControls_).section("speaker").item(
			"pcspeaker").build();
		Chain.on(speakerGroup).lbl(l -> l.key("dialog.template.pcrate")).cmb(c -> c.tooltip("dialog.template.pcrate.tooltip").items(settings_.getValues("profile", "pcrate"))).da(daControls_).section(
			"speaker").item("pcrate").build();

		Group tandyGroup = Group_.on(releaseComposite).layout(new GridLayout(2, false)).key("dialog.template.tandy").build();
		tandyGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
		Chain.on(tandyGroup).lbl(l -> l.key("dialog.template.enabletandy")).cmb(c -> c.tooltip("dialog.template.enabletandy.tooltip").items(settings_.getValues("profile", "tandy"))).da(
			daControls_).section("speaker").item("tandy").build();
		Chain.on(tandyGroup).lbl(l -> l.key("dialog.template.tandyrate")).cmb(c -> c.tooltip("dialog.template.tandyrate.tooltip").items(settings_.getValues("profile", "tandyrate"))).da(
			daControls_).section("speaker").item("tandyrate").build();

		Group disneyGroup = Group_.on(releaseComposite).layout(new GridLayout(2, false)).key("dialog.template.miscellaneous").build();
		disneyGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		Chain.on(disneyGroup).lbl(l -> l.key("dialog.template.enablesoundsource")).but(b -> b.tooltip("dialog.template.enablesoundsource.tooltip")).da(daControls_).section("speaker").item(
			"disney").build();

		Group generalExpGroup = Group_.on(experimentalComposite).layout(new GridLayout(2, false)).key("dialog.template.general").build();
		generalExpGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		Chain.on(generalExpGroup).lbl(l -> l.key("dialog.template.swapstereo")).but(b -> b).da(daControls_).section("mixer").item("swapstereo").build();

		Group soundblasterExpGroup = Group_.on(experimentalComposite).layout(new GridLayout(2, false)).key("dialog.template.soundblaster").build();
		soundblasterExpGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
		Chain.on(soundblasterExpGroup).lbl(l -> l.key("dialog.template.hardwaresbaddress")).cmb(c -> c.items(settings_.getValues("profile", "hardwaresbbase"))).da(daControls_).section(
			"sblaster").item("hardwarebase").build();
		Chain.on(soundblasterExpGroup).lbl(l -> l.key("dialog.template.goldplay")).but(b -> b).da(daControls_).section("sblaster").item("goldplay").build();
		Chain.on(soundblasterExpGroup).lbl(l -> l.key("dialog.template.fmstrength")).spn(s -> s.min(1).max(1000)).da(daControls_).section("sblaster").item("fmstrength").build();

		Group mt32ExpGroup = Group_.on(experimentalComposite).layout(new GridLayout(3, false)).key("dialog.template.mt32").build();
		mt32ExpGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 3));
		Chain.on(mt32ExpGroup).lbl(l -> l.key("dialog.template.mt32.romdir")).txt(t -> t).but(b -> b.browse(true, Button_.BrowseType.DIR, Button_.CanonicalType.NONE, false)).da(daControls_).section(
			"midi").item("mt32.romdir").build();
		Chain.on(mt32ExpGroup).lbl(l -> l.key("dialog.template.swapstereo")).but(b -> b.horSpan(2)).da(daControls_).section("midi").item("mt32.reverse.stereo").build();
		Chain.on(mt32ExpGroup).lbl(l -> l.key("dialog.template.mt32.verboselogging")).but(b -> b.horSpan(2)).da(daControls_).section("midi").item("mt32.verbose").build();
		Chain.on(mt32ExpGroup).lbl(l -> l.key("dialog.template.mt32.multithread")).but(b -> b.horSpan(2)).da(daControls_).section("midi").item("mt32.thread").build();
		Chain.on(mt32ExpGroup).lbl(l -> l.key("dialog.template.mt32.chunk")).spn(s -> s.horSpan(2).min(2).max(100)).da(daControls_).section("midi").item("mt32.chunk").build();
		Chain.on(mt32ExpGroup).lbl(l -> l.key("dialog.template.mt32.prebuffer")).spn(s -> s.horSpan(2).min(3).max(200)).da(daControls_).section("midi").item("mt32.prebuffer").build();
		Chain.on(mt32ExpGroup).lbl(l -> l.key("dialog.template.mt32.partials")).spn(s -> s.horSpan(2).min(0).max(256)).da(daControls_).section("midi").item("mt32.partials").build();
		Chain.on(mt32ExpGroup).lbl(l -> l.key("dialog.template.mt32.dac")).cmb(c -> c.horSpan(2).items(settings_.getValues("profile", "mt32dac"))).da(daControls_).section("midi").item(
			"mt32.dac").build();
		Chain.on(mt32ExpGroup).lbl(l -> l.key("dialog.template.mt32.analog")).cmb(c -> c.horSpan(2).items(settings_.getValues("profile", "mt32analog"))).da(daControls_).section("midi").item(
			"mt32.analog").build();
		Chain.on(mt32ExpGroup).lbl(l -> l.key("dialog.template.mt32.reverbmode")).cmb(c -> c.horSpan(2).items(settings_.getValues("profile", "mt32reverbmode"))).da(daControls_).section("midi").item(
			"mt32.reverb.mode").build();
		Chain.on(mt32ExpGroup).lbl(l -> l.key("dialog.template.mt32.reverbtime")).cmb(c -> c.horSpan(2).items(settings_.getValues("profile", "mt32reverbtime"))).da(daControls_).section("midi").item(
			"mt32.reverb.time").build();
		Chain.on(mt32ExpGroup).lbl(l -> l.key("dialog.template.mt32.reverblevel")).cmb(c -> c.horSpan(2).items(settings_.getValues("profile", "mt32reverblevel"))).da(daControls_).section("midi").item(
			"mt32.reverb.level").build();

		Group fluidExpGroup = Group_.on(experimentalComposite).layout(new GridLayout(3, false)).key("dialog.template.fluidsynth").build();
		fluidExpGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 3));
		Chain.on(fluidExpGroup).lbl(l -> l.key("dialog.template.fluidsynth.driver")).cmb(c -> c.horSpan(2).editable().items(settings_.getValues("profile", "fluidsynthdriver"))).da(daControls_).section(
			"midi").item("fluid.driver").build();
		Chain.on(fluidExpGroup).lbl(l -> l.key("dialog.template.fluidsynth.soundfont")).txt(t -> t)
			.but(b -> b.browse(true, Button_.BrowseType.FILE, Button_.CanonicalType.NONE, false)).da(daControls_)
			.da(daControls_).section("midi").item("fluid.soundfont").build();
		Chain.on(fluidExpGroup).lbl(l -> l.key("dialog.template.fluidsynth.samplerate")).cmb(c -> c.horSpan(2).items(settings_.getValues("profile", "fluidsynthsamplerate"))).da(daControls_).section("midi").item(
			"fluid.samplerate").build();
		Chain.on(fluidExpGroup).lbl(l -> l.key("dialog.template.fluidsynth.gain")).txt(t -> t.horSpan(2)).da(daControls_).section("midi").item("fluid.gain").build();

		Group innovaExpGroup = Group_.on(experimentalComposite).layout(new GridLayout(2, false)).key("dialog.template.innova").build();
		innovaExpGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
		Chain.on(innovaExpGroup).lbl(l -> l.key("dialog.template.innovaenable")).but(b -> b).da(daControls_).section("innova").item("innova").build();
		Chain.on(innovaExpGroup).lbl(l -> l.key("dialog.template.innovarate")).cmb(c -> c.items(settings_.getValues("profile", "innovarate"))).da(daControls_).section("innova").item(
			"samplerate").build();
		Chain.on(innovaExpGroup).lbl(l -> l.key("dialog.template.innovaaddress")).cmb(c -> c.items(settings_.getValues("profile", "innovabase"))).da(daControls_).section("innova").item(
			"sidbase").build();
		Chain.on(innovaExpGroup).lbl(l -> l.key("dialog.template.innovaquality")).cmb(c -> c.items(settings_.getValues("profile", "innovaquality"))).da(daControls_).section("innova").item(
			"quality").build();

		Group ps1ExpGroup = Group_.on(experimentalComposite).layout(new GridLayout(2, false)).key("dialog.template.ps1").build();
		ps1ExpGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
		Chain.on(ps1ExpGroup).lbl(l -> l.key("dialog.template.ps1enable")).but(b -> b).da(daControls_).section("speaker").item("ps1audio").onOff().build();
		Chain.on(ps1ExpGroup).lbl(l -> l.key("dialog.template.ps1rate")).cmb(c -> c.items(settings_.getValues("profile", "ps1rate"))).da(daControls_).section("speaker").item("ps1audiorate").build();
	}

	protected void createIOTab() {
		TabFolder subTabFolder = createSubTabs("dialog.template.tab.io", 3, 4);
		Composite releaseComposite = (Composite)subTabFolder.getChildren()[0];
		Composite experimentalComposite = (Composite)subTabFolder.getChildren()[1];

		Group mouseGroup = Group_.on(releaseComposite).layout(new GridLayout(2, false)).key("dialog.template.mouse").build();
		mouseGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
		Chain.on(mouseGroup).lbl(l -> l.key("dialog.template.autolock")).but(b -> b.tooltip("dialog.template.autolock.tooltip")).da(daControls_).section("sdl").item("autolock").build();
		Chain.on(mouseGroup).lbl(l -> l.key("dialog.template.sensitivity")).cmb(
			c -> c.editable().items(settings_.getValues("profile", "sensitivity")).visibleItemCount(20).tooltip("dialog.template.sensitivity.tooltip")).da(daControls_).section("sdl").item(
				"sensitivity").convert(new DaControlConvertor() {
					public String toConfValue(String[] values) {
						return String.join(",", values);
					}

					public String[] toControlValues(String value) {
						return value == null ? new String[0]: new String[] {value};
					}

					public String[] toConfValues(String[] values) {
						return null;
					}

					public String[] toControlValues(String[] values) {
						return null;
					}
				}).build();

		Group keyboardGroup = Group_.on(releaseComposite).layout(new GridLayout(3, false)).key("dialog.template.keyboard").build();
		keyboardGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		Chain.on(keyboardGroup).lbl(l -> l.key("dialog.template.usescancodes")).but(b -> b.horSpan(2).tooltip("dialog.template.usescancodes.tooltip")).da(daControls_).section("sdl").item(
			"usescancodes").build();
		Text mapperfile = Chain.on(keyboardGroup).lbl(l -> l.key("dialog.template.mapperfile")).txt(t -> t.tooltip("dialog.template.mapperfile.tooltip")).da(daControls_).section("sdl").item(
			"mapperfile").text();
		Button_.on(keyboardGroup).star().listen(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				mapperfile.setText(settings_.getValue("profile", "uniquemapperfile"));
			}
		}).ctrl();
		Chain.on(keyboardGroup).lbl(l -> l.key("dialog.template.keyboardlayout")).cmb(
			c -> c.horSpan(2).wide().editable().tooltip("dialog.template.keyboardlayout.tooltip").items(settings_.getValues("profile", "keyboardlayout")).visibleItemCount(15)).da(daControls_).section(
				"dos").item("keyboardlayout").build();
		Chain.on(keyboardGroup).lbl(l -> l.key("dialog.template.keybcommand")).txt(t -> t.horSpan(2)).meta(autoexecControls_).autoexec(Autoexec::getKeyb, Autoexec::setKeyb).build();

		Group joystickGroup = Group_.on(releaseComposite).layout(new GridLayout(2, false)).key("dialog.template.joystick").build();
		joystickGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		Chain.on(joystickGroup).lbl(l -> l.key("dialog.template.joysticktype")).cmb(c -> c.tooltip("dialog.template.joysticktype.tooltip").items(settings_.getValues("profile", "joysticktype"))).da(
			daControls_).section("bios", "joystick").item("joysticktype", "joysticktype").build();
		Chain.on(joystickGroup).lbl(l -> l.key("dialog.template.timedemulation")).but(b -> b.tooltip("dialog.template.timedemulation.tooltip")).da(daControls_).section("joystick").item(
			"timed").build();
		Chain.on(joystickGroup).lbl(l -> l.key("dialog.template.autofire")).but(b -> b.tooltip("dialog.template.autofire.tooltip")).da(daControls_).section("joystick").item("autofire").build();
		Chain.on(joystickGroup).lbl(l -> l.key("dialog.template.swap34")).but(b -> b.tooltip("dialog.template.swap34.tooltip")).da(daControls_).section("joystick").item("swap34").build();
		Chain.on(joystickGroup).lbl(l -> l.key("dialog.template.buttonwrapping")).but(b -> b.tooltip("dialog.template.buttonwrapping.tooltip")).da(daControls_).section("joystick").item(
			"buttonwrap").build();

		Group modemGroup = Group_.on(releaseComposite).layout(new GridLayout(2, false)).key("dialog.template.modem").build();
		modemGroup.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
		Chain.on(modemGroup).lbl(l -> l.key("dialog.template.serial1")).txt(t -> t.tooltip("dialog.template.serial.tooltip")).da(daControls_).section("serial").item("serial1").build();
		Chain.on(modemGroup).lbl(l -> l.key("dialog.template.serial2")).txt(t -> t.tooltip("dialog.template.serial.tooltip")).da(daControls_).section("serial").item("serial2").build();
		Chain.on(modemGroup).lbl(l -> l.key("dialog.template.serial3")).txt(t -> t.tooltip("dialog.template.serial.tooltip")).da(daControls_).section("serial").item("serial3").build();
		Chain.on(modemGroup).lbl(l -> l.key("dialog.template.serial4")).txt(t -> t.tooltip("dialog.template.serial.tooltip")).da(daControls_).section("serial").item("serial4").build();

		Group networkGroup = Group_.on(releaseComposite).layout(new GridLayout(2, false)).key("dialog.template.network").build();
		networkGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		ipx_ = Chain.on(networkGroup).lbl(l -> l.key("dialog.template.enableipx")).but(b -> b.tooltip("dialog.template.enableipx.tooltip")).da(daControls_).section("ipx").item("ipx").button();
		ipxNet_ = Chain.on(networkGroup).lbl(l -> l.horSpan(2).key("dialog.template.ipxnetcommand")).txt(t -> t.horSpan(2)).meta(autoexecControls_).autoexec(Autoexec::getIpxnet,
			Autoexec::setIpxnet).text();

		ipx_.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				ipxNet_.setEnabled(ipx_.getSelection());
			}
		});

		Group mouseExpGroup = Group_.on(experimentalComposite).layout(new GridLayout(2, false)).key("dialog.template.mouse").build();
		mouseExpGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		Chain.on(mouseExpGroup).lbl(l -> l.key("dialog.template.int33")).but(b -> b).da(daControls_).section("dos").item("int33").build();
		Chain.on(mouseExpGroup).lbl(l -> l.key("dialog.template.biosps2")).but(b -> b).da(daControls_).section("dos").item("biosps2").build();
		Chain.on(mouseExpGroup).lbl(l -> l.key("dialog.template.aux")).but(b -> b).da(daControls_).section("keyboard").item("aux").build();
		Chain.on(mouseExpGroup).lbl(l -> l.key("dialog.template.auxdevice")).cmb(c -> c.items(settings_.getValues("profile", "auxdevice"))).da(daControls_).section("keyboard").item(
			"auxdevice").build();

		Group miscExpGroup = Group_.on(experimentalComposite).layout(new GridLayout(2, false)).key("dialog.template.miscellaneous").build();
		miscExpGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		Chain.on(miscExpGroup).lbl(l -> l.key("dialog.template.files")).spn(s -> s.min(8).max(255)).da(daControls_).section("dos").item("files").build();
		Chain.on(miscExpGroup).lbl(l -> l.key("dialog.template.isapnpbios")).but(b -> b).da(daControls_).section("cpu").item("isapnpbios").build();
		Chain.on(miscExpGroup).lbl(l -> l.key("dialog.template.ide1")).but(b -> b).da(daControls_).section("ide, primary").item("enable").build();
		Chain.on(miscExpGroup).lbl(l -> l.key("dialog.template.ide2")).but(b -> b).da(daControls_).section("ide, secondary").item("enable").build();
		Chain.on(miscExpGroup).lbl(l -> l.key("dialog.template.ide3")).but(b -> b).da(daControls_).section("ide, tertiary").item("enable").build();
		Chain.on(miscExpGroup).lbl(l -> l.key("dialog.template.ide4")).but(b -> b).da(daControls_).section("ide, quaternary").item("enable").build();
		Chain.on(miscExpGroup).lbl(l -> l.key("dialog.template.automount")).but(b -> b).da(daControls_).section("dos").item("automount").build();

		Group printerExpGroup = Group_.on(experimentalComposite).layout(new GridLayout(2, false)).key("dialog.template.printer").build();
		printerExpGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		Chain.on(printerExpGroup).lbl(l -> l.key("dialog.template.printerenable")).but(b -> b).da(daControls_).section("printer").item("printer").build();
		Chain.on(printerExpGroup).lbl(l -> l.key("dialog.template.printerdpi")).spn(s -> s.min(0).max((int)Short.MAX_VALUE)).da(daControls_).section("printer").item("dpi").build();
		Chain.on(printerExpGroup).lbl(l -> l.key("dialog.template.printerwidth")).spn(s -> s.min(0).max((int)Short.MAX_VALUE)).da(daControls_).section("printer").item("width").build();
		Chain.on(printerExpGroup).lbl(l -> l.key("dialog.template.printerheight")).spn(s -> s.min(0).max((int)Short.MAX_VALUE)).da(daControls_).section("printer").item("height").build();
		Chain.on(printerExpGroup).lbl(l -> l.key("dialog.template.printeroutput")).cmb(c -> c.items(settings_.getValues("profile", "printeroutput"))).da(daControls_).section("printer").item(
			"printoutput").build();
		Chain.on(printerExpGroup).lbl(l -> l.key("dialog.template.printermultipage")).but(b -> b).da(daControls_).section("printer").item("multipage").build();
		Chain.on(printerExpGroup).lbl(l -> l.key("dialog.template.printerdocpath")).txt(t -> t).da(daControls_).section("printer").item("docpath").build();
		Chain.on(printerExpGroup).lbl(l -> l.key("dialog.template.printertimeout")).spn(s -> s.min(0).max((int)Short.MAX_VALUE)).da(daControls_).section("printer").item("timeout").build();

		Group joystickExpGroup = Group_.on(experimentalComposite).layout(new GridLayout(2, false)).key("dialog.template.joystick").build();
		joystickExpGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		Chain.on(joystickExpGroup).lbl(l -> l.key("dialog.template.circularinput")).but(b -> b.tooltip("dialog.template.circularinput.tooltip")).da(daControls_).section("joystick").item(
			"circularinput").build();
		Chain.on(joystickExpGroup).lbl(l -> l.key("dialog.template.deadzone")).spn(s -> s.tooltip("dialog.template.deadzone.tooltip").min(0).max(100)).da(daControls_).section("joystick").item(
			"deadzone").build();

		Group parallelExpGroup = Group_.on(experimentalComposite).layout(new GridLayout(2, false)).key("dialog.template.parallel").build();
		parallelExpGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
		Chain.on(parallelExpGroup).lbl(l -> l.key("dialog.template.parallel1")).txt(t -> t).da(daControls_).section("parallel").item("parallel1").build();
		Chain.on(parallelExpGroup).lbl(l -> l.key("dialog.template.parallel2")).txt(t -> t).da(daControls_).section("parallel").item("parallel2").build();
		Chain.on(parallelExpGroup).lbl(l -> l.key("dialog.template.parallel3")).txt(t -> t).da(daControls_).section("parallel").item("parallel3").build();
		Chain.on(parallelExpGroup).lbl(l -> l.key("dialog.template.dongle")).but(b -> b).da(daControls_).section("parallel").item("dongle").build();

		Group ne2000ExpGroup = Group_.on(experimentalComposite).layout(new GridLayout(2, false)).key("dialog.template.ne2000").build();
		ne2000ExpGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
		Chain.on(ne2000ExpGroup).lbl(l -> l.key("dialog.template.ne2000enable")).but(b -> b).da(daControls_).section("ne2000").item("ne2000").build();
		Chain.on(ne2000ExpGroup).lbl(l -> l.key("dialog.template.ne2000base")).txt(t -> t).da(daControls_).section("ne2000").item("nicbase").build();
		Chain.on(ne2000ExpGroup).lbl(l -> l.key("dialog.template.ne2000irq")).txt(t -> t).da(daControls_).section("ne2000").item("nicirq").build();
		Chain.on(ne2000ExpGroup).lbl(l -> l.key("dialog.template.ne2000macaddress")).txt(t -> t).da(daControls_).section("ne2000").item("macaddr").build();
		Chain.on(ne2000ExpGroup).lbl(l -> l.key("dialog.template.ne2000realnic")).txt(t -> t).da(daControls_).section("ne2000").item("realnic").build();
	}

	protected void createCustomCommandsTab(TemplateProfileBase configurable, boolean multiEdit) {
		TabFolder subTabFolder = createSubTabs("dialog.template.tab.customcommands", "dialog.template.tab.dosboxautoexec", 2, "dialog.template.tab.native", 2);
		Composite dosboxComposite = (Composite)subTabFolder.getChildren()[0];
		Composite nativeComposite = (Composite)subTabFolder.getChildren()[1];

		for (int i = 0; i < Autoexec.SECTIONS; i++) {
			int j = i + 1;
			Chain.on(dosboxComposite).lbl(l -> l.key("dialog.template.customcommand" + j)).txt(t -> t.multi()).meta(autoexecControls_).autoexec(i, Autoexec::getCustomSection,
				Autoexec::setCustomSection).build();
		}
		nativeCommandsList_ = List_.on(nativeComposite).ctrl();
		nativeCommandsList_.addMouseListener(new MouseAdapter() {
			public void mouseDoubleClick(MouseEvent event) {
				if (nativeCommandsList_.getSelectionIndex() == -1) {
					doAddNativeCommand(configurable);
				} else {
					doEditNativeCommand(configurable);
				}
			}
		});
		Composite nativeButComp = Composite_.on(nativeComposite).layoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false)).innerLayout(1).build();
		Chain.on(nativeButComp).but(b -> b.text().key("dialog.template.mount.add").listen(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doAddNativeCommand(configurable);
			}
		})).but(b -> b.text().key("dialog.template.mount.edit").listen(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doEditNativeCommand(configurable);
			}
		})).but(b -> b.text().key("dialog.template.mount.remove").listen(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doRemoveNativeCommand(configurable);
			}
		})).but(b -> b.arrow(true).listen(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				int sel = nativeCommandsList_.getSelectionIndex();
				if (sel > 0) {
					Collections.swap(configurable.getNativeCommands(), sel, sel - 1);
					updateNativeCommands(sel - 1, configurable);
				}
			}
		})).but(b -> b.arrow(false).listen(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				int sel = nativeCommandsList_.getSelectionIndex();
				if (sel >= 0 && sel < nativeCommandsList_.getItemCount() - 1) {
					Collections.swap(configurable.getNativeCommands(), sel, sel + 1);
					updateNativeCommands(sel + 1, configurable);
				}
			}
		})).build();

		if (multiEdit)
			Arrays.asList(nativeComposite.getChildren()).forEach(x -> x.setEnabled(false));
	}

	protected void updateNativeCommands(int sel, TemplateProfileBase configurable) {
		nativeCommandsList_.removeAll();
		for (NativeCommand cmd: configurable.getNativeCommands())
			nativeCommandsList_.add(cmd.toString());
		nativeCommandsList_.select(sel);
	}

	protected void doAddNativeCommand(TemplateProfileBase configurable) {
		EditNativeCommandDialog cmdDialog = new EditNativeCommandDialog(shell_, null);
		NativeCommand cmd = cmdDialog.open();
		if (cmd != null) {
			int nr = nativeCommandsList_.getSelectionIndex() + 1;
			configurable.getNativeCommands().add(nr, cmd);
			updateNativeCommands(nr, configurable);
		}
	}

	protected void doEditNativeCommand(TemplateProfileBase configurable) {
		int sel = nativeCommandsList_.getSelectionIndex();
		if (sel != -1) {
			NativeCommand cmd = configurable.getNativeCommands().get(sel);
			if (!cmd.isDosboxCommand()) {
				EditNativeCommandDialog cmdDialog = new EditNativeCommandDialog(shell_, cmd);
				cmd = cmdDialog.open();
				if (cmd != null) {
					configurable.getNativeCommands().set(sel, cmd);
					updateNativeCommands(sel, configurable);
				}
			}
		}
	}

	protected void doRemoveNativeCommand(TemplateProfileBase configurable) {
		int sel = nativeCommandsList_.getSelectionIndex();
		if (sel != -1) {
			NativeCommand cmd = configurable.getNativeCommands().get(sel);
			if (!cmd.isDosboxCommand()) {
				configurable.getNativeCommands().remove(sel);
				updateNativeCommands(Math.min(sel, nativeCommandsList_.getItemCount() - 1), configurable);
			}
		}
	}

	protected void createMountingTab(TemplateProfileBase configurable, boolean multiEdit) {
		Composite composite = createTabWithComposite("dialog.template.tab.mounting", new GridLayout());

		Group mountGroup = Group_.on(composite).layout(new GridLayout(2, false)).key("dialog.template.mountingoverview").build();
		mountGroup.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		mountingpointsList_ = List_.on(mountGroup).ctrl();

		Group executeGroup = Group_.on(composite).layout(new FillLayout()).key("dialog.template.execute").build();
		executeGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		ExpandBar dosBooterExpandBar = new ExpandBar(executeGroup, SWT.V_SCROLL);

		Composite booterComposite = Composite_.on(dosBooterExpandBar).build();
		Composite dosComposite = Composite_.on(dosBooterExpandBar).build();

		booterExpandItem_ = createExpandItem(dosBooterExpandBar, "dialog.template.booter", false, booterComposite);
		dosExpandItem_ = createExpandItem(dosBooterExpandBar, "dialog.template.dos", false, dosComposite);

		dosBooterExpandBar.addExpandListener(new ExpandAdapter() {
			public void itemCollapsed(ExpandEvent e) {
				dosBooterExpandBar.getItem((((ExpandItem)e.item).getText().equals(text_.get("dialog.template.dos"))) ? 0: 1).setExpanded(true);
				display_.asyncExec(new Runnable() {
					public void run() {
						composite.layout();
					}
				});
			}

			public void itemExpanded(ExpandEvent e) {
				dosBooterExpandBar.getItem((((ExpandItem)e.item).getText().equals(text_.get("dialog.template.dos"))) ? 0: 1).setExpanded(false);
				display_.asyncExec(new Runnable() {
					public void run() {
						composite.layout();
					}
				});
			}
		});

		mountingpointsList_.addMouseListener(new MouseAdapter() {
			public void mouseDoubleClick(MouseEvent event) {
				if (mountingpointsList_.getSelectionIndex() == -1) {
					doAddMount(shell_, booterExpandItem_.getExpanded(), mountingpointsList_, configurable);
				} else {
					doEditMount(shell_, mountingpointsList_, configurable);
				}
			}
		});
		Composite mntButComp = Composite_.on(mountGroup).innerLayout(1).build();
		Chain.on(mntButComp).but(b -> b.text().key("dialog.template.mount.add").listen(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doAddMount(shell_, booterExpandItem_.getExpanded(), mountingpointsList_, configurable);
			}
		})).but(b -> b.text().key("dialog.template.mount.edit").listen(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doEditMount(shell_, mountingpointsList_, configurable);
			}
		})).but(b -> b.text().key("dialog.template.mount.remove").listen(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				doRemoveMount(configurable);
			}
		})).build();

		if (multiEdit)
			Arrays.asList(mntButComp.getChildren()).forEach(x -> x.setEnabled(false));
	}

	public static void doAddMount(Shell shell, boolean floppy, org.eclipse.swt.widgets.List mountingpoints, TemplateProfileBase configurable) {
		char drive;
		try {
			drive = DriveLetterHelper.getFirstAvailable(floppy, configurable.getNettoMountedDrives());
		} catch (DrivelettersExhaustedException e) {
			// nothing we can do, just take a sensible default
			drive = 'C';
		}
		EditMountDialog addMountDialog = new EditMountDialog(shell, null, drive);
		String mount = addMountDialog.open();
		if (mount != null) {
			configurable.addMount(mount);
			mountingpoints.setItems(configurable.getMountStringsForUI());
			mountingpoints.select(mountingpoints.getItemCount() - 1);
		}
	}

	public static void doEditMount(Shell shell, org.eclipse.swt.widgets.List mountingpoints, TemplateProfileBase configurable) {
		int mounts = mountingpoints.getItemCount();
		int sel = mountingpoints.getSelectionIndex();
		if (sel == -1 && mounts == 1) {
			sel = 0;
			mountingpoints.select(sel);
		}
		if (sel != -1) {
			if (!configurable.getMountingPointsForUI().get(sel).isUnmounted()) {
				EditMountDialog editMountDialog = new EditMountDialog(shell, mountingpoints.getItem(sel), 'C');
				String mount = editMountDialog.open();
				if (mount != null) {
					configurable.editMountBasedOnIndexUI(sel, mount);
					mountingpoints.setItems(configurable.getMountStringsForUI());
					if (mountingpoints.getItemCount() == mounts) {
						mountingpoints.select(sel);
					} else {
						mountingpoints.select(mountingpoints.getItemCount() - 1);
					}
				}
			}
		}
	}

	protected void doRemoveMount(TemplateProfileBase configurable) {
		doRemoveMount(mountingpointsList_, configurable);
	}

	public static void doRemoveMount(org.eclipse.swt.widgets.List mountingpoints, TemplateProfileBase configurable) {
		int mounts = mountingpoints.getItemCount();
		int sel = mountingpoints.getSelectionIndex();
		if (sel == -1 && mounts == 1) {
			sel = 0;
			mountingpoints.select(sel);
		}
		if (sel != -1) {
			configurable.removeMountBasedOnIndexUI(sel);
			mountingpoints.setItems(configurable.getMountStringsForUI());
			if (mountingpoints.getItemCount() == mounts) {
				mountingpoints.select(sel);
			} else {
				if (mountingpoints.getItemCount() > 0) {
					mountingpoints.select(mountingpoints.getItemCount() - 1);
				}
			}
		}
	}

	private TabFolder createSubTabs(String titleKey, int numColumns1, int numColumns2) {
		return createSubTabs(titleKey, "dialog.template.tab.releaseoptions", numColumns1, "dialog.template.tab.experimentaloptions", numColumns2);
	}

	private TabFolder createSubTabs(String titleKey, String key1, int numColumns1, String key2, int numColumns2) {
		TabFolder subTabFolder = new TabFolder(createTabWithComposite(titleKey, new FillLayout()), SWT.NONE);
		Composite_.on(subTabFolder).layout(new GridLayout(numColumns1, false)).tab(key1).build();
		Composite_.on(subTabFolder).layout(new GridLayout(numColumns2, false)).tab(key2).build();
		return subTabFolder;
	}
}
