#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pylint:disable=unused-wildcard-import,wildcard-import,attribute-defined-outside-init
"""Options tab for the TKinter GUI."""
import sys
from tkinter import * # noqa: F403
from tkinter import messagebox, simpledialog
from tkinter.ttk import * # noqa: F403
from core import df, embarks, keybinds
from core.lnp import lnp
from . import binding, controls, tkhelpers
from .layout import GridLayouter
from .tab import Tab
[docs]class OptionsTab(Tab):
"""Options tab for the TKinter GUI."""
[docs] def create_variables(self):
self.keybinds = Variable()
self.embarks = Variable()
[docs] def read_data(self):
self.read_keybinds()
if lnp.df_info.version >= '0.28.181.40a':
self.read_embarks()
[docs] def create_controls(self):
options = controls.create_control_group(self, 'Gameplay Options', True)
options.pack(side=TOP, fill=BOTH, expand=N)
grid = GridLayouter(2)
grid.add(controls.create_trigger_option_button(
options, 'Population Cap', 'Maximum population in your fort. '
'Setting this too low may disable certain gameplay features.',
self.set_pop_cap, 'popcap'))
grid.add(controls.create_trigger_option_button(
options, 'Child Cap', 'Maximum children in your fort',
self.set_child_cap, 'childcap'))
if lnp.df_info.version >= '0.40.05':
grid.add(controls.create_trigger_option_button(
options, 'Strict Population Cap',
'Strict limit on population in your fort (blocks births)',
self.set_strict_pop_cap, 'strictPopcap'), 2)
grid.add(controls.create_trigger_option_button(
options, 'Visitor Cap',
'Limit on number of generic visitors in your fort',
self.set_visitor_cap, 'visitorCap'))
grid.add(controls.create_option_button(
options, 'Invaders',
'Toggles whether invaders (goblins, etc.) show up',
'invaders'))
if lnp.df_info.version >= '0.47.01':
grid.add(controls.create_trigger_option_button(
options, 'Temple Petition',
'Number of worshippers that trigger demand for a temple',
self.set_temple_count, 'templeCount'))
grid.add(controls.create_trigger_option_button(
options, 'Guild Size',
'Number of members of same profession that trigger creation of a guild',
self.set_guild_count, 'guildCount'))
grid.add(controls.create_trigger_option_button(
options, 'Invasion Soldier Cap',
'Limit on number of enemy soldiers during an invasion',
self.set_inv_soldier_cap, 'invSoldierCap'), 2)
grid.add(controls.create_trigger_option_button(
options, 'Invasion Monster Cap',
'Limit on number of enemy monsters during an invasion',
self.set_inv_monster_cap, 'invMonsterCap'), 2)
grid.add(controls.create_option_button(
options, 'Cave-ins',
'Toggles whether unsupported bits of terrain will collapse',
'caveins'))
grid.add(controls.create_option_button(
options, 'Temperature',
'Toggles whether things will burn, melt, freeze, etc.',
'temperature'))
grid.add(controls.create_option_button(
options, 'Weather', 'Rain, snow, etc.', 'weather'))
grid.add(controls.create_option_button(
options, 'Entomb Pets',
'Whether deceased pets should be entombed in coffins by default.',
'entombPets'))
grid.add(controls.create_option_button(
options, 'Artifacts',
'Whether dwarfs should enter artifact producing moods.',
'artifacts'))
grid.add(controls.create_option_button(
options, 'Aquifers', 'Whether newly created worlds will have '
'Aquifers in them (Infinite sources of underground water, but may '
'flood your fort', 'aquifers'))
grid.add(controls.create_trigger_option_button(
options, 'Graze Coefficient',
'Scales how often grazing animals need to eat. Larger numbers '
'require less food.', self.set_graze_coef, 'grazeCoef'), 2)
if lnp.df_info.version >= '0.34.03':
if lnp.df_info.version <= '0.34.06':
tooltip = 'Whether labors are enabled by default.'
else:
tooltip = (
'Which labors are enabled by default: by skill level of '
'dwarves, by their unit type, or none')
grid.add(controls.create_option_button(
options, 'Starting Labors', tooltip, 'laborLists'), 2)
keybindings, self.keybinding_entry, self.keybinding_files = \
controls.create_list_with_entry(
self, "Key Bindings", self.keybinds,
[("Save", "Save current keybindings", self.save_keybinds),
("Load", "Load keybindings", self.load_keybinds),
("Delete", "Delete keybindings", self.delete_keybinds),
("Refresh", "Refresh list", self.read_keybinds)],
entry_default="Save current keybindings as...")
keybindings.pack(side=BOTTOM, fill=BOTH, expand=N)
for seq in ("<Double-1>", "<Return>"):
self.keybinding_files.bind(seq, lambda e: self.load_keybinds())
if lnp.df_info.version >= '0.28.181.40a':
embarkframe, self.embark_files = controls.create_file_list(
self, 'Embark profiles', self.embarks, height=3)
self.embark_files.configure(selectmode="single")
refresh = controls.create_trigger_button(
embarkframe, 'Refresh Profiles', 'Refresh list of profiles',
self.read_embarks)
refresh.grid(column=0, row=3, columnspan=2, sticky="sew")
# This hack ensures the listbox never selects anything itself. This
# is much better than the alternative hack required to prevent the
# list selecting the last element when clicking in empty space.
def _deselect_all(event):
for item in event.widget.curselection():
event.widget.selection_clear(item)
self.embark_files.bind("<<ListboxSelect>>", _deselect_all)
for seq in ("<space>", "<Return>", "<1>",
"<2>" if sys.platform == 'darwin' else "<3>"):
self.embark_files.bind(seq, self.toggle_embark)
[docs] @staticmethod
def set_pop_cap():
"""Requests new population cap from the user."""
v = simpledialog.askinteger(
"Settings", "Population cap:",
initialvalue=lnp.settings.popcap)
if v is not None:
df.set_option('popcap', str(v))
binding.update()
[docs] @staticmethod
def set_strict_pop_cap():
"""Requests new strict population cap from the user."""
v = simpledialog.askinteger(
"Settings", "Strict population cap:",
initialvalue=lnp.settings.strictPopcap)
if v is not None:
df.set_option('strictPopcap', str(v))
binding.update()
[docs] @staticmethod
def set_child_cap():
"""Requests new child cap from the user."""
child_split = list(lnp.settings.childcap.split(':'))
child_split.append('0') # In case syntax is invalid
v = simpledialog.askinteger(
"Settings", "Absolute cap on babies + children:",
initialvalue=child_split[0])
if v is not None:
v2 = simpledialog.askinteger(
"Settings", "Max percentage of children in fort:\n"
"(lowest of the two values will be used as the cap)",
initialvalue=child_split[1])
if v2 is not None:
df.set_option('childcap', str(v) + ':' + str(v2))
binding.update()
[docs] @staticmethod
def set_temple_count():
"""Requests new worshippers limits from the user."""
split = list(lnp.settings.templeCount.split(':'))
split.append('0') # In case syntax is invalid
v1 = simpledialog.askinteger(
"Settings", "Min number of worshippers for a regular temple:",
initialvalue=split[0])
if v1 is not None:
v2 = simpledialog.askinteger(
"Settings", "Min number of worshippers for a grand temple:",
initialvalue=split[1])
if v2 is not None:
df.set_option('templeCount', str(v1) + ':' + str(v2))
binding.update()
[docs] @staticmethod
def set_guild_count():
"""Requests new guild members limits from the user."""
split = list(lnp.settings.guildCount.split(':'))
split.append('0') # In case syntax is invalid
v1 = simpledialog.askinteger(
"Settings", "Min number of guild members:",
initialvalue=split[0])
if v1 is not None:
v2 = simpledialog.askinteger(
"Settings", "Min number of guild members requesting a grand guildhall:",
initialvalue=split[1])
if v2 is not None:
df.set_option('guildCount', str(v1) + ':' + str(v2))
binding.update()
[docs] @staticmethod
def set_graze_coef():
"""Requests new graze coefficient from the user."""
v = simpledialog.askinteger(
"Settings", "Graze coefficient:",
initialvalue=lnp.settings.grazeCoef)
if v is not None:
df.set_option('grazeCoef', str(v))
binding.update()
[docs] @staticmethod
def set_visitor_cap():
"""Requests new visitor cap from the user."""
v = simpledialog.askinteger(
"Settings", "Visitor cap:",
initialvalue=lnp.settings.visitorCap)
if v is not None:
df.set_option('visitorCap', str(v))
binding.update()
[docs] @staticmethod
def set_inv_soldier_cap():
"""Requests new invasion soldier cap from the user."""
v = simpledialog.askinteger(
"Settings", "Cap on # of soldiers in invasion:",
initialvalue=lnp.settings.invSoldierCap)
if v is not None:
df.set_option('invSoldierCap', str(v))
binding.update()
[docs] @staticmethod
def set_inv_monster_cap():
"""Requests new invasion monster cap from the user."""
v = simpledialog.askinteger(
"Settings", "Cap on # of monsters in invasion:",
initialvalue=lnp.settings.invMonsterCap)
if v is not None:
df.set_option('invMonsterCap', str(v))
binding.update()
[docs] def read_keybinds(self):
"""Reads list of keybinding files."""
files = keybinds.read_keybinds()
self.keybinds.set(files)
current = keybinds.get_installed_file()
default_bg = Style().lookup('TListbox', 'fill')
for i, f in enumerate(files):
if f == current:
self.keybinding_files.itemconfig(i, bg='pale green')
else:
self.keybinding_files.itemconfig(i, bg=default_bg)
[docs] def load_keybinds(self):
"""Replaces keybindings with selected file."""
if not tkhelpers.check_vanilla_raws():
return
listbox = self.keybinding_files
items = listbox.curselection()
if len(items) > 0:
listbox.selection_clear(items)
keybinds.load_keybinds(listbox.get(items[0]))
self.read_keybinds()
self.keybinding_entry.delete(0, END)
[docs] def save_keybinds(self):
"""Saves keybindings to a file."""
v = self.keybinding_entry.get()
if v and not getattr(self.keybinding_entry, 'default_showing', False):
if not v.endswith('.txt'):
v = v + '.txt'
if (not keybinds.keybind_exists(v) or messagebox.askyesno(
message='Overwrite {0}?'.format(v),
icon='question', title='Overwrite file?')):
self.keybinding_entry.delete(0, END)
keybinds.save_keybinds(v)
self.read_keybinds()
[docs] def delete_keybinds(self):
"""Deletes a keybinding file."""
listbox = self.keybinding_files
if len(listbox.curselection()) > 0:
filename = listbox.get(listbox.curselection()[0])
if messagebox.askyesno(
'Delete file?',
'Are you sure you want to delete {0}?'.format(filename)):
keybinds.delete_keybinds(filename)
self.read_keybinds()
[docs] def read_embarks(self):
"""Reads list of embark profiles."""
files = embarks.read_embarks()
self.embarks.set(files)
current = embarks.get_installed_files()
default_bg = Style().lookup('TListbox', 'fill')
for i, f in enumerate(files):
if f in current:
self.embark_files.itemconfig(i, bg='pale green')
else:
self.embark_files.itemconfig(i, bg=default_bg)
[docs] def install_embarks(self, listbox):
"""
Installs selected embark profiles.
Args:
listbox: Listbox containing the list of embark profiles.
"""
if len(listbox.curselection()) != 0:
files = []
for f in listbox.curselection():
files.append(listbox.get(f))
embarks.install_embarks(files)
self.read_embarks()
[docs] def toggle_embark(self, event):
"""Toggles selected embark profile."""
item = self.embark_files.index('active')
if event.keysym == '??':
# pylint: disable=not-callable
item = self.embark_files.identify(event.y)
# pylint: enable=not-callable
if item is not None:
embark_file = self.embark_files.get(item)
files = embarks.get_installed_files()
if embark_file in files:
files.remove(embark_file)
else:
files.append(embark_file)
embarks.install_embarks(files)
self.read_embarks()