code:
import os, wx

from lib.Actions.Import import NetworkJobPresentationContext

from lib.GUI.Common import BetterBitmapButton, BetterBoxSizer, BetterButton, \
	BetterChoice, BetterStaticText, ConvertTextToPixels, MenuBitmapButton, \
	NoneableTextCtrl, StaticBox, WrapInGrid, WrapInText
from lib.GUI.Controls import NetworkJobControl, StringMatchButton, \
	StringMatchToStringMatchDictControl, StringToStringDictControl
from lib.GUI.Dialogs import DialogTextEntry, DialogYesNo
from lib.GUI.Dialogs.Quick import SelectFromList
from lib.GUI.ListBoxes import QueueListBox
from lib.GUI.ListCtrl import BetterListCtrl, BetterListCtrlPanel
from lib.GUI.Parsing import EditContentParsersPanel
from lib.GUI.ScrolledPanels import EditPanel, ReviewPanel
from lib.GUI.TopLevelWindows import DialogEdit, FrameThatTakesScrollablePanel

from lib.Hydrus.Data import Call, ConvertTimestampToPrettyExpires, ShowException, \
	TimeHasPassed
from lib.Hydrus.Defaults import GetDefaultLoginScripts
from lib.Hydrus.Exceptions import CancelledException, DataMissing, ValidationException, \
	VetoException
from lib.Hydrus.Paths.Local import LaunchPathInWebBrowser
from lib.Hydrus.Serialisable import SetNonDupeName

from lib.Networking import NetworkEngine
from lib.Networking.Bandwidth import NetworkBandwidthManager
from lib.Networking.Contexts import NetworkContext
from lib.Networking.Domain import NetworkDomainManager
from lib.Networking.Login import LoginCredentialDefinition, LoginScriptDomain, \
	LoginStep, NetworkLoginManager

from lib.Networking.Sessions import NetworkSessionManager

### CONSTANTS ###

import lib.Hydrus.Globals as HG
import lib.Hydrus.Constants as HC
import lib.Hydrus.Constants.Local as CC

from lib.Networking.Login import CREDENTIAL_TYPE_PASS, CREDENTIAL_TYPE_TEXT, credential_type_str_lookup, \
	login_access_type_default_description_lookup, VALIDITY_INVALID, VALIDITY_UNTESTED, \
	LOGIN_ACCESS_TYPE_EVERYTHING, LOGIN_ACCESS_TYPE_NSFW, LOGIN_ACCESS_TYPE_SPECIAL, \
	login_access_type_str_lookup, LOGIN_ACCESS_TYPE_USER_PREFS_ONLY, validity_str_lookup

### CLASSES ###

class EditLoginCredentialsPanel( EditPanel ):
	def __init__( self, parent, credential_definitions, credentials ):
		EditPanel.__init__( self, parent )

		#

		self._control_data = []

		rows = []

		credential_definitions = list( credential_definitions )

		credential_definitions.sort( key = lambda cd: cd.ShouldHide() )

		for credential_definition in credential_definitions:
			if credential_definition.ShouldHide():
				style = wx.TE_PASSWORD

			else:
				style = 0

			control = wx.TextCtrl( self, style = style )

			name = credential_definition.GetName()

			if name in credentials:
				control.SetValue( credentials[ name ] )

			control.Bind( wx.EVT_TEXT, self.EventKey )

			control_st = BetterStaticText( self )

			self._control_data.append( ( credential_definition, control, control_st ) )

			hbox = wx.BoxSizer( wx.HORIZONTAL )

			hbox.Add( control, CC.FLAGS_EXPAND_BOTH_WAYS )
			hbox.Add( control_st, CC.FLAGS_EXPAND_BOTH_WAYS )

			rows.append( ( credential_definition.GetName() + ': ', hbox ) )

		gridbox = WrapInGrid( self, rows )

		self.SetSizer( gridbox )

		self._UpdateSts()

	def _UpdateSts( self ):
		for ( credential_definition, control, control_st ) in self._control_data:
			value = control.GetValue()

			colour = ( 127, 0, 0 )

			if value == '':
				string_match = credential_definition.GetStringMatch()

				if string_match is None:
					st_label = ''

				else:
					st_label = string_match.ToString()

			else:
				try:
					credential_definition.Test( value )

					st_label = 'looks good \u2713'

					colour = ( 0, 127, 0 )

				except Exception as e:
					st_label = str( e )

			control_st.SetLabelText( st_label )
			control_st.SetForegroundColour( colour )

	def CanOK( self ):
		veto_errors = []

		for ( credential_definition, control, control_st ) in self._control_data:
			name = credential_definition.GetName()

			value = control.GetValue()

			if value == '':
				veto_errors.append( 'Value for ' + name + ' is blank!' )

			else:
				try:
					credential_definition.Test( value )

				except Exception as e:
					veto_errors.append( 'For ' + name + ': ' + str( e ) )

		if len( veto_errors ) > 0:
			message = 'These values are invalid--are you sure this is ok?'
			message += os.linesep * 2
			message += os.linesep.join( veto_errors )

			with DialogYesNo( self, message ) as dlg:
				if dlg.ShowModal() != wx.ID_YES:
					return False

		return True

	def EventKey( self, event ):
		self._UpdateSts()

		event.Skip()

	def GetValue( self ):
		credentials = {}

		for ( credential_definition, control, control_st ) in self._control_data:
			name = credential_definition.GetName()

			value = control.GetValue()

			credentials[ name ] = value

		return credentials

class EditLoginCredentialDefinitionPanel( EditPanel ):
	def __init__( self, parent, credential_definition ):
		EditPanel.__init__( self, parent )

		#

		self._name = wx.TextCtrl( self )

		self._credential_type = BetterChoice( self )

		for credential_type in [ CREDENTIAL_TYPE_TEXT, CREDENTIAL_TYPE_PASS ]:
			self._credential_type.Append( credential_type_str_lookup[ credential_type ], credential_type )

		string_match = credential_definition.GetStringMatch()

		self._string_match = StringMatchButton( self, string_match )

		#

		self._name.SetValue( credential_definition.GetName() )
		self._credential_type.SelectClientData( credential_definition.GetType() )

		#

		rows = []

		rows.append( ( 'name: ', self._name ) )
		rows.append( ( 'input type: ', self._credential_type ) )
		rows.append( ( 'permitted input: ', self._string_match ) )

		gridbox = WrapInGrid( self, rows )

		self.SetSizer( gridbox )

	def GetValue( self ):
		name = self._name.GetValue()
		credential_type = self._credential_type.GetChoice()
		string_match = self._string_match.GetValue()

		credential_definition = LoginCredentialDefinition( name = name, credential_type = credential_type, string_match = string_match )

		return credential_definition

class EditLoginsPanel( EditPanel ):
	def __init__( self, parent, engine, login_scripts, domains_to_login_info ):
		EditPanel.__init__( self, parent )

		self._engine = engine
		self._login_scripts = login_scripts

		self._domains_to_login_after_ok = []

		domains_and_login_info_panel = BetterListCtrlPanel( self )

		columns = [ ( 'domain', 20 ), ( 'login script', -1 ), ( 'access given', 36 ), ( 'active?', 8 ), ( 'logged in now?', 28 ), ( 'validity', 28 ), ( 'recent error/delay?', 21 ) ]

		self._domains_and_login_info = BetterListCtrl( domains_and_login_info_panel, 'domains_to_login_info', 8, 36, columns, self._ConvertDomainAndLoginInfoListCtrlTuples, use_simple_delete = True, activation_callback = self._EditCredentials )

		domains_and_login_info_panel.SetListCtrl( self._domains_and_login_info )

		domains_and_login_info_panel.AddButton( 'add', self._Add )
		domains_and_login_info_panel.AddDeleteButton()
		domains_and_login_info_panel.AddSeparator()
		domains_and_login_info_panel.AddButton( 'edit credentials', self._EditCredentials, enabled_check_func = self._CanEditCreds )
		domains_and_login_info_panel.AddButton( 'change login script', self._EditLoginScript, enabled_only_on_selection = True )
		domains_and_login_info_panel.AddButton( 'flip active', self._FlipActive, enabled_only_on_selection = True )
		domains_and_login_info_panel.AddSeparator()
		domains_and_login_info_panel.AddButton( 'scrub invalidity', self._ScrubInvalidity, enabled_check_func = self._CanScrubInvalidity )
		domains_and_login_info_panel.AddButton( 'scrub delays', self._ScrubDelays, enabled_check_func = self._CanScrubDelays )
		domains_and_login_info_panel.NewButtonRow()
		domains_and_login_info_panel.AddButton( 'do login now', self._DoLogin, enabled_check_func = self._CanDoLogin )
		domains_and_login_info_panel.AddButton( 'reset login (delete cookies)', self._ClearSessions, enabled_only_on_selection = True )

		#

		listctrl_data = []

		for ( login_domain, ( login_script_key_and_name, credentials, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) ) in list( domains_to_login_info.items() ):
			credentials_tuple = tuple( credentials.items() )

			domain_and_login_info = ( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason )

			listctrl_data.append( domain_and_login_info )

		self._domains_and_login_info.AddDatas( listctrl_data )

		self._domains_and_login_info.Sort( 0 )

		#

		vbox = wx.BoxSizer( wx.VERTICAL )

		warning = 'WARNING: Your credentials are stored in plaintext! For this and other reasons, I recommend you use throwaway accounts with hydrus!'

		warning_st = BetterStaticText( self, warning )

		warning_st.SetForegroundColour( ( 128, 0, 0 ) )

		vbox.Add( warning_st, CC.FLAGS_CENTER )
		vbox.Add( domains_and_login_info_panel, CC.FLAGS_EXPAND_BOTH_WAYS )

		self.SetSizer( vbox )

	def _Add( self ):
		if len( self._login_scripts ) == 0:
			wx.MessageBox( 'You have no login scripts, so you cannot add a new login!' )

			return

		choice_tuples = [ ( login_script.GetName(), login_script ) for login_script in self._login_scripts ]

		try:
			login_script = SelectFromList( self, 'select the login script to use', choice_tuples )

		except CancelledException:
			return

		example_domains = set( login_script.GetExampleDomains() )

		domains_in_use = { login_domain for ( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) in self._domains_and_login_info.GetData() }

		available_examples = list( example_domains.difference( domains_in_use ) )

		available_examples.sort()

		if len( available_examples ) > 0:
			choice_tuples = [ ( login_domain, login_domain ) for login_domain in available_examples ]

			choice_tuples.append( ( 'use other domain', None ) )

			try:
				login_domain = SelectFromList( self, 'select the domain to use', choice_tuples, sort_tuples = False )

			except CancelledException:
				return

			if login_domain is not None:
				( login_access_type, login_access_text ) = login_script.GetExampleDomainInfo( login_domain )

		else:
			login_domain = None

		if login_domain is None:
			with DialogTextEntry( self, 'enter the domain', default = 'example.com', allow_blank = False ) as dlg:
				if dlg.ShowModal() == wx.ID_OK:
					login_domain = dlg.GetValue()

					if login_domain in domains_in_use:
						wx.MessageBox( 'That domain is already in use!' )

						return

					a_types = [ LOGIN_ACCESS_TYPE_EVERYTHING, LOGIN_ACCESS_TYPE_NSFW, LOGIN_ACCESS_TYPE_SPECIAL, LOGIN_ACCESS_TYPE_USER_PREFS_ONLY ]

					choice_tuples = [ ( login_access_type_str_lookup[ a_type ], a_type ) for a_type in a_types ]

					try:
						login_access_type = SelectFromList( self, 'select what type of access the login gives to this domain', choice_tuples, sort_tuples = False )

					except CancelledException:
						return

					login_access_text = login_access_type_default_description_lookup[ login_access_type ]

					with DialogTextEntry( self, 'edit the access description, if needed', default = login_access_text, allow_blank = False ) as dlg:
						if dlg.ShowModal() == wx.ID_OK:
							login_access_text = dlg.GetValue()

						else:
							return

				else:
					return

		credential_definitions = login_script.GetCredentialDefinitions()

		credentials = {}

		if len( credential_definitions ) > 0:
			with DialogEdit( self, 'edit login' ) as dlg:
				panel = EditLoginCredentialsPanel( dlg, credential_definitions, credentials )

				dlg.SetPanel( panel )

				if dlg.ShowModal() == wx.ID_OK:
					credentials = panel.GetValue()

				else:
					return

		try:
			login_script.CheckCanLogin( credentials )

			validity = VALIDITY_UNTESTED
			validity_error_text = ''

			# hacky: if there are creds, is at least one not empty string?
			creds_are_good = len( credentials ) == 0 or True in ( value != '' for value in list( credentials.values() ) )

		except ValidationException as e:
			validity = VALIDITY_INVALID
			validity_error_text = str( e )

			creds_are_good = False

		if creds_are_good:
			message = 'Activate this login script for this domain?'

			with DialogYesNo( self, message, title = message ) as dlg:
				if dlg.ShowModal() == wx.ID_YES:
					active = True

				else:
					active = False

		else:
			active = False

		login_script_key_and_name = login_script.GetLoginScriptKeyAndName()
		credentials_tuple = tuple( credentials.items() )
		no_work_until = 0
		no_work_until_reason = ''

		domain_and_login_info = ( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason )

		self._domains_and_login_info.AddDatas( ( domain_and_login_info, ) )

		self._domains_and_login_info.Sort()

	def _CanDoLogin( self ):
		domain_and_login_infos = self._domains_and_login_info.GetData( only_selected = True )

		for domain_and_login_info in domain_and_login_infos:
			( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) = domain_and_login_info

			if not active:
				continue

			if validity == VALIDITY_INVALID:
				continue

			try:
				login_script = self._GetLoginScript( login_script_key_and_name )

				network_context = NetworkContext( context_type = CC.NETWORK_CONTEXT_DOMAIN, context_data = login_domain )

				logged_in = login_script.IsLoggedIn( self._engine, network_context )

				if logged_in:
					continue

			except DataMissing:
				continue

			return True

		return False

	def _CanEditCreds( self ):
		domain_and_login_infos = self._domains_and_login_info.GetData( only_selected = True )

		for domain_and_login_info in domain_and_login_infos:
			( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) = domain_and_login_info

			try:
				login_script = self._GetLoginScript( login_script_key_and_name )

				if len( login_script.GetCredentialDefinitions() ) > 0:
					return True

			except DataMissing:
				continue

		return False

	def _CanScrubDelays( self ):
		domain_and_login_infos = self._domains_and_login_info.GetData( only_selected = True )

		for domain_and_login_info in domain_and_login_infos:
			( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) = domain_and_login_info

			if not TimeHasPassed( no_work_until ) or no_work_until_reason != '':
				return True

		return False

	def _CanScrubInvalidity( self ):
		domain_and_login_infos = self._domains_and_login_info.GetData( only_selected = True )

		for domain_and_login_info in domain_and_login_infos:
			( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) = domain_and_login_info

			if validity == VALIDITY_INVALID:
				return True

		return False

	def _ClearSessions( self ):
		domain_and_login_infos = self._domains_and_login_info.GetData( only_selected = True )

		if len( domain_and_login_infos ) > 0:
			message = 'Are you sure you want to clear these domains\' sessions? This will delete all their existing cookies and cannot be undone.'

			with DialogYesNo( self, message ) as dlg:
				if dlg.ShowModal() != wx.ID_YES:
					return

			for domain_and_login_info in domain_and_login_infos:
				( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) = domain_and_login_info

				network_context = NetworkContext( context_type = CC.NETWORK_CONTEXT_DOMAIN, context_data = login_domain )

				self._engine.session_manager.ClearSession( network_context )

			self._domains_and_login_info.UpdateDatas()

	def _ConvertDomainAndLoginInfoListCtrlTuples( self, domain_and_login_info ):
		( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) = domain_and_login_info

		login_expiry = None

		try:
			login_script = self._GetLoginScript( login_script_key_and_name )

			sort_login_script = login_script.GetName()

			network_context = NetworkContext( context_type = CC.NETWORK_CONTEXT_DOMAIN, context_data = login_domain )

			logged_in = login_script.IsLoggedIn( self._engine, network_context )

			if logged_in:
				login_expiry = login_script.GetLoginExpiry( self._engine, network_context )

		except DataMissing:
			sort_login_script = 'login script not found'

			logged_in = False

		access = login_access_type_str_lookup[ login_access_type ] + ' - ' + login_access_text

		if active:
			sort_active = 'yes'

		else:
			sort_active = 'no'

		sort_validity = validity_str_lookup[ validity ]

		if len( validity_error_text ) > 0:
			sort_validity += ' - ' + validity_error_text

		sort_logged_in = ( logged_in, login_expiry )

		if TimeHasPassed( no_work_until ):
			pretty_no_work_until = ''

		else:
			pretty_no_work_until = ConvertTimestampToPrettyExpires( no_work_until ) + ' - ' + no_work_until_reason

		pretty_login_domain = login_domain
		pretty_login_script = sort_login_script
		pretty_access = access
		pretty_active = sort_active

		if active:
			pretty_validity = sort_validity

		else:
			pretty_validity = ''

		if logged_in:
			if login_expiry is None:
				pretty_login_expiry = 'session'

			else:
				pretty_login_expiry = ConvertTimestampToPrettyExpires( login_expiry )

			pretty_logged_in = 'yes - ' + pretty_login_expiry

		else:
			pretty_logged_in = 'no'

		display_tuple = ( pretty_login_domain, pretty_login_script, pretty_access, pretty_active, pretty_logged_in, pretty_validity, pretty_no_work_until )
		sort_tuple = ( login_domain, sort_login_script, access, sort_active, sort_logged_in, sort_validity, no_work_until )

		return ( display_tuple, sort_tuple )

	def _DoLogin( self ):
		domains_to_login = []

		domain_and_login_infos = self._domains_and_login_info.GetData( only_selected = True )

		for domain_and_login_info in domain_and_login_infos:
			( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) = domain_and_login_info

			if not active:
				continue

			if validity == VALIDITY_INVALID:
				continue

			try:
				login_script = self._GetLoginScript( login_script_key_and_name )

				network_context = NetworkContext( context_type = CC.NETWORK_CONTEXT_DOMAIN, context_data = login_domain )

				logged_in = login_script.IsLoggedIn( self._engine, network_context )

				if logged_in:
					continue

			except DataMissing:
				continue

			domains_to_login.append( login_domain )

		if len( domains_to_login ) == 0:
			wx.MessageBox( 'Unfortunately, none of the selected domains appear able to log in. Do you need to activate or scrub something somewhere?' )

		else:
			domains_to_login.sort()

			message = 'It looks like the following domains can log in:'
			message += os.linesep * 2
			message += os.linesep.join( domains_to_login )
			message += os.linesep * 2
			message += 'The dialog will ok and the login attempts will start. Is this ok?'

			with DialogYesNo( self, message ) as dlg:
				if dlg.ShowModal() != wx.ID_YES:
					return

			self._domains_to_login_after_ok = domains_to_login

			self.GetParent().DoOK()

	def _EditCredentials( self ):
		domain_and_login_infos = self._domains_and_login_info.GetData( only_selected = True )

		for domain_and_login_info in domain_and_login_infos:
			( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) = domain_and_login_info

			try:
				login_script = self._GetLoginScript( login_script_key_and_name )

			except DataMissing:
				wx.MessageBox( 'Could not find a login script for "' + login_domain + '"! Please re-add the login script in the other dialog or update the entry here to a new one!' )

				return

			credential_definitions = login_script.GetCredentialDefinitions()

			if len( credential_definitions ) > 0:
				with DialogEdit( self, 'edit login' ) as dlg:
					credentials = dict( credentials_tuple )

					panel = EditLoginCredentialsPanel( dlg, credential_definitions, credentials )

					dlg.SetPanel( panel )

					if dlg.ShowModal() == wx.ID_OK:
						credentials = panel.GetValue()

					else:
						return

			else:
				continue

			try:
				login_script.CheckCanLogin( credentials )

				validity = VALIDITY_UNTESTED
				validity_error_text = ''

				# hacky: if there are creds, is at least one not empty string?
				creds_are_good = len( credentials ) == 0 or True in ( value != '' for value in list( credentials.values() ) )

			except ValidationException as e:
				validity = VALIDITY_INVALID
				validity_error_text = str( e )

				creds_are_good = False

			credentials_tuple = tuple( credentials.items() )

			if creds_are_good:
				if not active:
					message = 'Activate this login script for this domain?'

					with DialogYesNo( self, message, title = message ) as dlg:
						if dlg.ShowModal() == wx.ID_YES:
							active = True

			else:
				active = False

			no_work_until = 0
			no_work_until_reason = ''

			edited_domain_and_login_info = ( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason )

			self._domains_and_login_info.DeleteDatas( ( domain_and_login_info, ) )
			self._domains_and_login_info.AddDatas( ( edited_domain_and_login_info, ) )

		self._domains_and_login_info.Sort()

	def _EditLoginScript( self ):
		domain_and_login_infos = self._domains_and_login_info.GetData( only_selected = True )

		for domain_and_login_info in domain_and_login_infos:
			( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) = domain_and_login_info

			try:
				current_login_script = self._GetLoginScript( login_script_key_and_name )

			except DataMissing:
				current_login_script = None

			potential_login_scripts = list( self._login_scripts )

			potential_login_scripts.sort( key = lambda ls: ls.GetName() )

			matching_potential_login_scripts = [ login_script for login_script in potential_login_scripts if login_domain in login_script.GetExampleDomains() ]
			unmatching_potential_login_scripts = [ login_script for login_script in potential_login_scripts if login_domain not in login_script.GetExampleDomains() ]

			choice_tuples = [ ( login_script.GetName(), login_script ) for login_script in matching_potential_login_scripts ]

			if len( matching_potential_login_scripts ) > 0 and len( unmatching_potential_login_scripts ) > 0:
				choice_tuples.append( ( '------', None ) )

			choice_tuples.extend( [ ( login_script.GetName(), login_script ) for login_script in unmatching_potential_login_scripts ] )

			try:
				login_script = SelectFromList( self, 'select the login script to use', choice_tuples, value_to_select = current_login_script, sort_tuples = False )

			except CancelledException:
				break

			if login_script is None:
				break

			if login_script == current_login_script:
				break

			login_script_key_and_name = login_script.GetLoginScriptKeyAndName()

			try:
				( login_access_type, login_access_text ) = login_script.GetExampleDomainInfo( login_domain )

			except DataMissing:
				a_types = [ LOGIN_ACCESS_TYPE_EVERYTHING, LOGIN_ACCESS_TYPE_NSFW, LOGIN_ACCESS_TYPE_SPECIAL, LOGIN_ACCESS_TYPE_USER_PREFS_ONLY ]

				choice_tuples = [ ( login_access_type_str_lookup[ a_type ], a_type ) for a_type in a_types ]

				try:
					login_access_type = SelectFromList( self, 'select what type of access the login gives to this domain', choice_tuples, sort_tuples = False )

				except CancelledException:
					break

				login_access_text = login_access_type_default_description_lookup[ login_access_type ]

				with DialogTextEntry( self, 'edit the access description, if needed', default = login_access_text, allow_blank = False ) as dlg:
					if dlg.ShowModal() == wx.ID_OK:
						login_access_text = dlg.GetValue()

					else:
						break

			credentials = dict( credentials_tuple )

			try:
				login_script.CheckCanLogin( credentials )

				validity = VALIDITY_UNTESTED
				validity_error_text = ''

				creds_are_good = True

			except ValidationException as e:
				validity = VALIDITY_INVALID
				validity_error_text = str( e )

				creds_are_good = False

			if not creds_are_good:
				active = False

			no_work_until = 0
			no_work_until_reason = ''

			edited_domain_and_login_info = ( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason )

			self._domains_and_login_info.DeleteDatas( ( domain_and_login_info, ) )
			self._domains_and_login_info.AddDatas( ( edited_domain_and_login_info, ) )

		self._domains_and_login_info.Sort()

	def _FlipActive( self ):
		domain_and_login_infos = self._domains_and_login_info.GetData( only_selected = True )

		for domain_and_login_info in domain_and_login_infos:
			( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) = domain_and_login_info

			active = not active

			flipped_domain_and_login_info = ( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason )

			self._domains_and_login_info.DeleteDatas( ( domain_and_login_info, ) )
			self._domains_and_login_info.AddDatas( ( flipped_domain_and_login_info, ) )

		self._domains_and_login_info.Sort()

	def _GetLoginScript( self, login_script_key_and_name ):
		( login_script_key, login_script_name ) = login_script_key_and_name

		for login_script in self._login_scripts:
			if login_script.GetLoginScriptKey() == login_script_key:
				return login_script

		for login_script in self._login_scripts:
			if login_script.GetName() == login_script_name:
				return login_script

		raise DataMissing( 'No login script found!' )

	def _ScrubDelays( self ):
		domain_and_login_infos = self._domains_and_login_info.GetData( only_selected = True )

		for domain_and_login_info in domain_and_login_infos:
			( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) = domain_and_login_info

			no_work_until = 0
			no_work_until_reason = ''

			scrubbed_domain_and_login_info = ( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason )

			self._domains_and_login_info.DeleteDatas( ( domain_and_login_info, ) )
			self._domains_and_login_info.AddDatas( ( scrubbed_domain_and_login_info, ) )

		self._domains_and_login_info.Sort()

	def _ScrubInvalidity( self ):
		domain_and_login_infos = self._domains_and_login_info.GetData( only_selected = True )

		for domain_and_login_info in domain_and_login_infos:
			( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) = domain_and_login_info

			if validity != VALIDITY_INVALID:
				continue

			try:
				try:
					login_script = self._GetLoginScript( login_script_key_and_name )

				except DataMissing:
					continue

				credentials = dict( credentials_tuple )

				login_script.CheckCanLogin( credentials )

				validity = VALIDITY_UNTESTED
				validity_error_text = ''

			except ValidationException as e:
				validity = VALIDITY_INVALID
				validity_error_text = str( e )

			scrubbed_domain_and_login_info = ( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason )

			self._domains_and_login_info.DeleteDatas( ( domain_and_login_info, ) )
			self._domains_and_login_info.AddDatas( ( scrubbed_domain_and_login_info, ) )

		self._domains_and_login_info.Sort()

	def GetDomainsToLoginAfterOK( self ):
		return self._domains_to_login_after_ok

	def GetValue( self ):
		domains_to_login_info = dict()

		for ( login_domain, login_script_key_and_name, credentials_tuple, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason ) in self._domains_and_login_info.GetData():
			credentials = dict( credentials_tuple )

			domains_to_login_info[ login_domain ] = ( login_script_key_and_name, credentials, login_access_type, login_access_text, active, validity, validity_error_text, no_work_until, no_work_until_reason )

		return domains_to_login_info

def GenerateTestNetworkJobPresentationContextFactory( window, network_job_control ):
	def network_job_presentation_context_factory( network_job ):
		def wx_set_it( nj ):
			if not window:
				return

			network_job_control.SetNetworkJob( nj )

		def enter_call():
			wx.CallAfter( wx_set_it, network_job )

		def exit_call():
			wx.CallAfter( wx_set_it, None )

		return NetworkJobPresentationContext( enter_call, exit_call )

	return network_job_presentation_context_factory

class ReviewTestResultPanel( ReviewPanel ):
	def __init__( self, parent, test_result ):
		ReviewPanel.__init__( self, parent )

		( name, url, body, self._downloaded_data, new_temp_strings, new_cookie_strings, result ) = test_result

		self._name = BetterStaticText( self, label = name )
		self._url = wx.TextCtrl( self )

		self._body = wx.TextCtrl( self, style = wx.TE_MULTILINE )
		self._body.SetEditable( False )

		min_size = ConvertTextToPixels( self._body, ( 64, 3 ) )

		self._body.SetMinClientSize( min_size )

		self._data_preview = wx.TextCtrl( self, style = wx.TE_MULTILINE )
		self._data_preview.SetEditable( False )

		min_size = ConvertTextToPixels( self._data_preview, ( 64, 8 ) )

		self._data_preview.SetMinClientSize( min_size )

		self._data_copy_button = BetterBitmapButton( self, CC.GlobalBMPs.copy, self._CopyData )
		self._data_copy_button.SetToolTip( 'Copy the current example data to the clipboard.' )

		self._temp_variables = wx.TextCtrl( self, style = wx.TE_MULTILINE )
		self._temp_variables.SetEditable( False )

		min_size = ConvertTextToPixels( self._temp_variables, ( 64, 6 ) )

		self._temp_variables.SetMinClientSize( min_size )

		self._cookies = wx.TextCtrl( self, style = wx.TE_MULTILINE )
		self._cookies.SetEditable( False )

		min_size = ConvertTextToPixels( self._cookies, ( 64, 6 ) )

		self._cookies.SetMinClientSize( min_size )

		self._result = BetterStaticText( self, label = result )

		#

		self._url.SetValue( url )

		if body is not None:
			try:
				self._body.SetValue( body )

			except Exception:
				self._body.SetValue( str( body ) )

		self._data_preview.SetValue( str( self._downloaded_data[:1024] ) )

		self._temp_variables.SetValue( os.linesep.join( new_temp_strings ) )
		self._cookies.SetValue( os.linesep.join( new_cookie_strings ) )

		#

		rows = []

		rows.append( ( 'name: ', self._name ) )
		rows.append( ( 'url: ', self._url ) )
		rows.append( ( 'body (if set ): ', self._body ) )
		rows.append( ( 'data: ', self._data_preview ) )
		rows.append( ( 'copy data: ', self._data_copy_button ) )
		rows.append( ( 'new temp vars: ', self._temp_variables ) )
		rows.append( ( 'new cookies: ', self._cookies ) )
		rows.append( ( 'result: ', self._result ) )

		gridbox = WrapInGrid( self, rows )

		self.SetSizer( gridbox )

	def _CopyData( self ):
		HG.client_controller.pub( 'clipboard', 'text', self._downloaded_data )

class EditLoginScriptPanel( EditPanel ):
	def __init__( self, parent, login_script ):
		EditPanel.__init__( self, parent )

		self._original_login_script = login_script

		self._currently_testing = False

		self._test_domain = ''
		self._test_credentials = {}

		#

		menu_items = []

		page_func = Call( LaunchPathInWebBrowser, os.path.join( HC.HELP_DIR, 'downloader_login.html' ) )

		menu_items.append( ( 'normal', 'open the login scripts help', 'Open the help page for login scripts in your web browser.', page_func ) )

		help_button = MenuBitmapButton( self, CC.GlobalBMPs.help, menu_items )

		help_hbox = WrapInText( help_button, self, 'help for this panel -->', wx.Colour( 0, 0, 255 ) )

		#

		self._name = wx.TextCtrl( self )

		#

		credential_definitions_box_panel = StaticBox( self, 'credential definitions' )

		credential_definitions_panel = BetterListCtrlPanel( credential_definitions_box_panel )

		columns = [ ( 'name', -1 ), ( 'type', 10 ), ( 'value', 16 ) ]

		self._credential_definitions = BetterListCtrl( credential_definitions_panel, 'credential_definitions', 4, 16, columns, self._ConvertCredentialDefinitionToListCtrlTuples, use_simple_delete = True, activation_callback = self._EditCredentialDefinitions )

		credential_definitions_panel.SetListCtrl( self._credential_definitions )

		credential_definitions_panel.AddButton( 'add', self._AddCredentialDefinition )
		credential_definitions_panel.AddButton( 'edit', self._EditCredentialDefinitions, enabled_only_on_selection = True )
		credential_definitions_panel.AddDeleteButton()

		#

		login_steps_box_panel = StaticBox( self, 'login steps' )

		columns = [ ( 'name', -1 ), ( 'url', 56 ) ]

		self._login_steps = QueueListBox( login_steps_box_panel, 5, self._ConvertLoginStepToListBoxString, add_callable = self._AddLoginStep, edit_callable = self._EditLoginStep )

		#

		required_cookies_info_box_panel = StaticBox( self, 'cookies required to consider session logged in' )

		self._required_cookies_info = StringMatchToStringMatchDictControl( required_cookies_info_box_panel, login_script.GetRequiredCookiesInfo(), min_height = 4, key_name = 'cookie name' )

		#

		example_domains_info_box_panel = StaticBox( self, 'example domains' )

		example_domains_info_panel = BetterListCtrlPanel( example_domains_info_box_panel )

		columns = [ ( 'domain', -1 ), ( 'access type', 14 ), ( 'description', 40 ) ]

		self._example_domains_info = BetterListCtrl( example_domains_info_panel, 'example_domains_info', 6, 16, columns, self._ConvertExampleDomainInfoToListCtrlTuples, use_simple_delete = True, activation_callback = self._EditExampleDomainsInfo )

		example_domains_info_panel.SetListCtrl( self._example_domains_info )

		example_domains_info_panel.AddButton( 'add', self._AddExampleDomainsInfo )
		example_domains_info_panel.AddButton( 'edit', self._EditExampleDomainsInfo, enabled_only_on_selection = True )
		example_domains_info_panel.AddDeleteButton()

		#

		test_panel = StaticBox( self, 'testing' )

		self._test_button = BetterButton( test_panel, 'run test', self._DoTest )

		self._test_network_job_control = NetworkJobControl( test_panel )

		test_listctrl_panel = BetterListCtrlPanel( test_panel )

		columns = [ ( 'step', -1 ), ( 'url', 36 ), ( 'result', 14 ) ]

		self._test_listctrl = BetterListCtrl( test_listctrl_panel, 'test_login_script_results', 6, 20, columns, self._ConvertTestResultToListCtrlTuples, activation_callback = self._ReviewTestResult )

		test_listctrl_panel.SetListCtrl( self._test_listctrl )

		test_listctrl_panel.AddButton( 'review', self._ReviewTestResult, enabled_only_on_selection = True )

		self._final_test_result = BetterStaticText( test_panel )

		#

		self._name.SetValue( login_script.GetName() )

		self._credential_definitions.SetData( login_script.GetCredentialDefinitions() )
		self._login_steps.AddDatas( login_script.GetLoginSteps() )
		self._example_domains_info.SetData( login_script.GetExampleDomainsInfo() )

		#

		credential_definitions_box_panel.Add( credential_definitions_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
		login_steps_box_panel.Add( self._login_steps, CC.FLAGS_EXPAND_BOTH_WAYS )
		required_cookies_info_box_panel.Add( self._required_cookies_info, CC.FLAGS_EXPAND_BOTH_WAYS )
		example_domains_info_box_panel.Add( example_domains_info_panel, CC.FLAGS_EXPAND_BOTH_WAYS )

		test_panel.Add( self._test_button, CC.FLAGS_EXPAND_PERPENDICULAR )
		test_panel.Add( self._test_network_job_control, CC.FLAGS_EXPAND_PERPENDICULAR )
		test_panel.Add( test_listctrl_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
		test_panel.Add( self._final_test_result, CC.FLAGS_EXPAND_PERPENDICULAR )

		#

		rows = []

		rows.append( ( 'name: ', self._name ) )

		gridbox = WrapInGrid( self, rows )

		vbox = BetterBoxSizer( wx.VERTICAL )

		vbox.Add( help_hbox, CC.FLAGS_BUTTON_SIZER )
		vbox.Add( gridbox, CC.FLAGS_EXPAND_SIZER_PERPENDICULAR )
		vbox.Add( credential_definitions_box_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
		vbox.Add( login_steps_box_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
		vbox.Add( required_cookies_info_box_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
		vbox.Add( example_domains_info_box_panel, CC.FLAGS_EXPAND_BOTH_WAYS )

		hbox = BetterBoxSizer( wx.HORIZONTAL )

		hbox.Add( vbox, CC.FLAGS_EXPAND_SIZER_BOTH_WAYS )
		hbox.Add( test_panel, CC.FLAGS_EXPAND_BOTH_WAYS )

		self.SetSizer( hbox )

	def _AddCredentialDefinition( self ):
		new_credential_definition = LoginCredentialDefinition()

		with DialogEdit( self, 'edit parser', frame_key = 'deeply_nested_dialog' ) as dlg_edit:
			panel = EditLoginCredentialDefinitionPanel( dlg_edit, new_credential_definition )

			dlg_edit.SetPanel( panel )

			if dlg_edit.ShowModal() == wx.ID_OK:
				new_credential_definition = panel.GetValue()

				SetNonDupeName( new_credential_definition, self._GetExistingCredentialDefinitionNames() )

				self._credential_definitions.AddDatas( ( new_credential_definition, ) )

				self._credential_definitions.Sort()

	def _AddExampleDomainsInfo( self ):
		( domain, access_type, access_text ) = ( 'example.com', LOGIN_ACCESS_TYPE_NSFW, login_access_type_default_description_lookup[ LOGIN_ACCESS_TYPE_NSFW ] )

		with DialogTextEntry( self, 'edit the domain', default = domain, allow_blank = False ) as dlg:
			if dlg.ShowModal() == wx.ID_OK:
				domain = dlg.GetValue()

			else:
				return

		existing_domains = self._GetExistingDomains()

		if domain in existing_domains:
			wx.MessageBox( 'That domain already exists!' )

			return

		a_types = [ LOGIN_ACCESS_TYPE_EVERYTHING, LOGIN_ACCESS_TYPE_NSFW, LOGIN_ACCESS_TYPE_SPECIAL, LOGIN_ACCESS_TYPE_USER_PREFS_ONLY ]

		choice_tuples = [ ( login_access_type_str_lookup[ a_type ], a_type ) for a_type in a_types ]

		try:
			new_access_type = SelectFromList( self, 'select what type of access the login gives to this domain', choice_tuples, value_to_select = access_type, sort_tuples = False )

		except CancelledException:
			return

		if new_access_type != access_type:
			access_type = new_access_type

			access_text = login_access_type_default_description_lookup[ access_type ]

		with DialogTextEntry( self, 'edit the access description, if needed', default = access_text, allow_blank = False ) as dlg:
			if dlg.ShowModal() == wx.ID_OK:
				access_text = dlg.GetValue()

			else:
				return

		example_domain_info = ( domain, access_type, access_text )

		self._example_domains_info.AddDatas( ( example_domain_info, ) )

		self._example_domains_info.Sort()

	def _AddLoginStep( self ):
		login_step = LoginStep()

		return self._EditLoginStep( login_step )

	def _ConvertCredentialDefinitionToListCtrlTuples( self, credential_definition ):
		name = credential_definition.GetName()
		credential_type = credential_definition.GetType()

		type_string = credential_type_str_lookup[ credential_type ]

		string_match = credential_definition.GetStringMatch()

		value = string_match.ToString()

		pretty_name = name
		pretty_type_string = type_string
		pretty_value = value

		display_tuple = ( pretty_name, pretty_type_string, pretty_value )
		sort_tuple = ( name, type_string, value )

		return ( display_tuple, sort_tuple )

	def _ConvertExampleDomainInfoToListCtrlTuples( self, example_domain_info ):
		( domain, access_type, access_text ) = example_domain_info

		pretty_domain = domain
		pretty_access_type = login_access_type_str_lookup[ access_type ]
		pretty_access_text = access_text

		display_tuple = ( pretty_domain, pretty_access_type, pretty_access_text )
		sort_tuple = ( domain, pretty_access_type, access_text )

		return ( display_tuple, sort_tuple )

	def _ConvertLoginStepToListBoxString( self, login_step ):
		name = login_step.GetName()

		return name

	def _ConvertTestResultToListCtrlTuples( self, test_result ):
		( name, url, body, downloaded_data, new_temp_strings, new_cookie_strings, result ) = test_result

		pretty_name = name

		pretty_url = url

		pretty_result = result

		display_tuple = ( pretty_name, pretty_url, pretty_result )
		sort_tuple = ( name, url, result )

		return ( display_tuple, sort_tuple )

	def _EditCredentialDefinitions( self ):
		credential_definitions = self._credential_definitions.GetData( only_selected = True )

		for credential_definition in credential_definitions:
			with DialogEdit( self, 'edit login script', frame_key = 'deeply_nested_dialog' ) as dlg:
				panel = EditLoginCredentialDefinitionPanel( dlg, credential_definition )

				dlg.SetPanel( panel )

				if dlg.ShowModal() == wx.ID_OK:
					edited_credential_definition = panel.GetValue()

					self._credential_definitions.DeleteDatas( ( credential_definition, ) )

					SetNonDupeName( edited_credential_definition, self._GetExistingCredentialDefinitionNames() )

					self._credential_definitions.AddDatas( ( edited_credential_definition, ) )

				else:
					break

		self._credential_definitions.Sort()

	def _DoTest( self ):
		def wx_add_result( test_result ):
			if not self:
				return

			self._test_listctrl.AddDatas( ( test_result, ) )

		def receive_result( test_result ):
			wx.CallAfter( wx_add_result, test_result )

		def clean_up( final_result ):
			if not self:
				return

			wx.MessageBox( final_result )

			self._final_test_result.SetLabelText( final_result )

			self._test_button.Enable()

			self._currently_testing = False

		def do_it( login_script, domain, credentials, network_job_presentation_context_factory ):
			try:
				login_result = 'login did not finish'

				# a potential here is to properly inform the login manager of the domain map and hence read back the invalidation text
				# but I am catching the info in the raised exception, so nbd really, I think

				bandwidth_manager = NetworkBandwidthManager()
				session_manager = NetworkSessionManager()
				domain_manager = NetworkDomainManager()
				login_manager = NetworkLoginManager()

				network_engine = NetworkEngine( HG.client_controller, bandwidth_manager, session_manager, domain_manager, login_manager )

				HG.client_controller.CallToThreadLongRunning( network_engine.MainLoop )

				network_context = NetworkContext.STATICGenerateForDomain( domain )

				login_result = login_script.Start( network_engine, network_context, credentials, network_job_presentation_context_factory = network_job_presentation_context_factory, test_result_callable = wx_add_result )

			except Exception as e:
				login_result = str( e )

				ShowException( e )

			finally:
				network_engine.Shutdown()

				wx.CallAfter( clean_up, login_result )

		if self._currently_testing:
			wx.MessageBox( 'Currently testing already! Please cancel current job!' )

			return

		try:
			login_script = self.GetValue()

		except VetoException:
			return

		if self._test_domain == '':
			example_domains = list( login_script.GetExampleDomains() )

			example_domains.sort()

			if len( example_domains ) > 0:
				self._test_domain = example_domains[0]

		with DialogTextEntry( self, 'edit the domain', default = self._test_domain, allow_blank = False ) as dlg:
			if dlg.ShowModal() == wx.ID_OK:
				self._test_domain = dlg.GetValue()

			else:
				return

		credential_definitions = login_script.GetCredentialDefinitions()

		if len( credential_definitions ) > 0:
			with DialogEdit( self, 'edit login' ) as dlg:
				panel = EditLoginCredentialsPanel( dlg, credential_definitions, self._test_credentials )

				dlg.SetPanel( panel )

				if dlg.ShowModal() == wx.ID_OK:
					self._test_credentials = panel.GetValue()

				else:
					return

		else:
			self._test_credentials = {}

		self._test_listctrl.DeleteDatas( self._test_listctrl.GetData() )

		self._test_button.Disable()

		network_job_presentation_context_factory = GenerateTestNetworkJobPresentationContextFactory( self, self._test_network_job_control )

		self._currently_testing = True

		HG.client_controller.CallToThread( do_it, login_script, self._test_domain, self._test_credentials, network_job_presentation_context_factory )

	def _EditExampleDomainsInfo( self ):
		selected_example_domains_info = self._example_domains_info.GetData( only_selected = True )

		for example_domain_info in selected_example_domains_info:
			( original_domain, access_type, access_text ) = example_domain_info

			with DialogTextEntry( self, 'edit the domain', default = original_domain, allow_blank = False ) as dlg:
				if dlg.ShowModal() == wx.ID_OK:
					domain = dlg.GetValue()

				else:
					break

			existing_domains = self._GetExistingDomains()

			if domain != original_domain and domain in existing_domains:
				wx.MessageBox( 'That domain already exists!' )

				break

			a_types = [ LOGIN_ACCESS_TYPE_EVERYTHING, LOGIN_ACCESS_TYPE_NSFW, LOGIN_ACCESS_TYPE_SPECIAL, LOGIN_ACCESS_TYPE_USER_PREFS_ONLY ]

			choice_tuples = [ ( login_access_type_str_lookup[ a_type ], a_type ) for a_type in a_types ]

			try:
				new_access_type = SelectFromList( self, 'select what type of access the login gives to this domain', choice_tuples, value_to_select = access_type, sort_tuples = False )

			except CancelledException:
				break

			if new_access_type != access_type:
				access_type = new_access_type

				access_text = login_access_type_default_description_lookup[ access_type ]

			with DialogTextEntry( self, 'edit the access description, if needed', default = access_text, allow_blank = False ) as dlg:
				if dlg.ShowModal() == wx.ID_OK:
					access_text = dlg.GetValue()

				else:
					break

			self._example_domains_info.DeleteDatas( ( example_domain_info, ) )

			edited_example_domain_info = ( domain, access_type, access_text )

			self._example_domains_info.AddDatas( ( edited_example_domain_info, ) )

		self._example_domains_info.Sort()

	def _EditLoginStep( self, login_step ):
		with DialogEdit( self, 'edit login step' ) as dlg:
			panel = EditLoginStepPanel( dlg, login_step )

			dlg.SetPanel( panel )

			if dlg.ShowModal() == wx.ID_OK:
				login_step = panel.GetValue()

				return login_step

			else:
				raise VetoException()

	def _GetExistingCredentialDefinitionNames( self ):
		return { credential_definition.GetName() for credential_definition in self._credential_definitions.GetData() }

	def _GetExistingDomains( self ):
		return { domain for ( domain, access_type, access_text ) in self._example_domains_info.GetData() }

	def _ReviewTestResult( self ):
		for test_result in self._test_listctrl.GetData( only_selected = True ):
			frame = FrameThatTakesScrollablePanel( self, 'login test result' )

			panel = ReviewTestResultPanel( frame, test_result )

			frame.SetPanel( panel )

	def GetValue( self ):
		if self._currently_testing:
			raise VetoException( 'Currently testing! Please cancel it first!' )

		name = self._name.GetValue()

		login_script_key = self._original_login_script.GetLoginScriptKey()

		required_cookies_info = self._required_cookies_info.GetValue()
		credential_definitions = self._credential_definitions.GetData()
		login_steps = self._login_steps.GetData()
		example_domains_info = self._example_domains_info.GetData()

		# credential_names = { credential_definition.GetName() for credential_definition in credential_definitions }

		login_script = LoginScriptDomain( name = name, login_script_key = login_script_key, required_cookies_info = required_cookies_info, credential_definitions = credential_definitions, login_steps = login_steps, example_domains_info = example_domains_info )

		login_script.SetLoginScriptKey( login_script_key )

		try:
			login_script.CheckIsValid()

		except ValidationException as e:
			message = 'There is a problem with this script. The reason is:'
			message += os.linesep * 2
			message += str( e )
			message += os.linesep * 2
			message += 'Do you want to proceed with this invalid script, or go back and fix it?'

			with DialogYesNo( self, message, yes_label = 'ok as invalid', no_label = 'go back' ) as dlg:
				if dlg.ShowModal() != wx.ID_YES:
					raise VetoException( 'The ok event has been cancelled!' )

		return login_script

class EditLoginScriptsPanel( EditPanel ):
	def __init__( self, parent, login_scripts ):
		EditPanel.__init__( self, parent )

		login_scripts_panel = BetterListCtrlPanel( self )

		columns = [ ( 'name', -1 ), ( 'example domains', 40 ) ]

		self._login_scripts = BetterListCtrl( login_scripts_panel, 'login_scripts', 20, 24, columns, self._ConvertLoginScriptToListCtrlTuples, use_simple_delete = True, activation_callback = self._Edit )

		login_scripts_panel.SetListCtrl( self._login_scripts )

		login_scripts_panel.AddButton( 'add', self._Add )
		login_scripts_panel.AddButton( 'edit', self._Edit, enabled_only_on_selection = True )
		login_scripts_panel.AddDeleteButton()
		login_scripts_panel.AddSeparator()
		login_scripts_panel.AddImportExportButtons( ( LoginScriptDomain, ), self._AddLoginScript )
		login_scripts_panel.AddSeparator()
		login_scripts_panel.AddDefaultsButton( GetDefaultLoginScripts, self._AddLoginScript )

		#

		self._login_scripts.AddDatas( login_scripts )

		#

		vbox = wx.BoxSizer( wx.VERTICAL )

		vbox.Add( login_scripts_panel, CC.FLAGS_EXPAND_BOTH_WAYS )

		self.SetSizer( vbox )

	def _Add( self ):
		new_login_script = LoginScriptDomain()

		with DialogEdit( self, 'edit login script', frame_key = 'deeply_nested_dialog' ) as dlg_edit:
			panel = EditLoginScriptPanel( dlg_edit, new_login_script )

			dlg_edit.SetPanel( panel )

			if dlg_edit.ShowModal() == wx.ID_OK:
				new_login_script = panel.GetValue()

				self._AddLoginScript( new_login_script )

				self._login_scripts.Sort()

	def _AddLoginScript( self, login_script ):
		SetNonDupeName( login_script, self._GetExistingNames() )

		login_script.RegenerateLoginScriptKey()

		self._login_scripts.AddDatas( ( login_script, ) )

	def _ConvertLoginScriptToListCtrlTuples( self, login_script ):
		name = login_script.GetName()

		example_domains = list( login_script.GetExampleDomains() )
		example_domains.sort()

		pretty_name = name
		pretty_example_domains = ', '.join( example_domains )

		display_tuple = ( pretty_name, pretty_example_domains )
		sort_tuple = ( name, example_domains )

		return ( display_tuple, sort_tuple )

	def _Edit( self ):
		login_scripts = self._login_scripts.GetData( only_selected = True )

		for login_script in login_scripts:
			with DialogEdit( self, 'edit login script', frame_key = 'deeply_nested_dialog' ) as dlg:
				panel = EditLoginScriptPanel( dlg, login_script )

				dlg.SetPanel( panel )

				if dlg.ShowModal() == wx.ID_OK:
					edited_login_script = panel.GetValue()

					self._login_scripts.DeleteDatas( ( login_script, ) )

					SetNonDupeName( edited_login_script, self._GetExistingNames() )

					self._login_scripts.AddDatas( ( edited_login_script, ) )

				else:
					break

		self._login_scripts.Sort()

	def _GetExistingNames( self ):
		names = { login_script.GetName() for login_script in self._login_scripts.GetData() }

		return names

	def GetValue( self ):
		return self._login_scripts.GetData()

class EditLoginStepPanel( EditPanel ):
	def __init__( self, parent, login_step ):
		EditPanel.__init__( self, parent )

		#

		name = login_step.GetName()

		( scheme, method, subdomain, path, required_credentials, static_args, temp_args, required_cookies_info, content_parsers ) = login_step.ToTuple()

		#

		self._name = wx.TextCtrl( self )

		self._method = BetterChoice( self )

		self._method.Append( 'GET', 'GET' )
		self._method.Append( 'POST', 'POST' )

		self._scheme = BetterChoice( self )

		self._scheme.Append( 'http', 'http' )
		self._scheme.Append( 'https', 'https' )

		self._subdomain = NoneableTextCtrl( self, none_phrase = 'none' )

		self._path = wx.TextCtrl( self )

		required_credentials_panel = StaticBox( self, 'credentials to send' )

		self._required_credentials = StringToStringDictControl( required_credentials_panel, required_credentials, min_height = 4, key_name = 'credential name', value_name = 'parameter name' )

		#

		static_args_panel = StaticBox( self, 'static variables to send' )

		self._static_args = StringToStringDictControl( static_args_panel, static_args, min_height = 4, key_name = 'parameter name', value_name = 'value' )

		#

		temp_args_panel = StaticBox( self, 'temporary variables to send' )

		self._temp_args = StringToStringDictControl( temp_args_panel, temp_args, min_height = 4, key_name = 'temp variable name', value_name = 'parameter name' )

		#

		required_cookies_info_box_panel = StaticBox( self, 'cookies required to consider step successful' )

		self._required_cookies_info = StringMatchToStringMatchDictControl( required_cookies_info_box_panel, required_cookies_info, min_height = 4, key_name = 'cookie name' )

		#

		content_parsers_panel = StaticBox( self, 'content parsers' )

		test_context_callable = lambda: ( {}, '' )

		permitted_content_types = [ HC.CONTENT_TYPE_VARIABLE, HC.CONTENT_TYPE_VETO ]

		self._content_parsers = EditContentParsersPanel( content_parsers_panel, test_context_callable, permitted_content_types )

		# a test panel a la pageparsers

		#

		self._name.SetValue( name )
		self._scheme.SelectClientData( scheme )
		self._method.SelectClientData( method )
		self._subdomain.SetValue( subdomain )
		self._path.SetValue( path )

		self._content_parsers.AddDatas( content_parsers )

		#

		required_credentials_panel.Add( self._required_credentials, CC.FLAGS_EXPAND_BOTH_WAYS )
		static_args_panel.Add( self._static_args, CC.FLAGS_EXPAND_BOTH_WAYS )
		temp_args_panel.Add( self._temp_args, CC.FLAGS_EXPAND_BOTH_WAYS )
		required_cookies_info_box_panel.Add( self._required_cookies_info, CC.FLAGS_EXPAND_BOTH_WAYS )
		content_parsers_panel.Add( self._content_parsers, CC.FLAGS_EXPAND_BOTH_WAYS )

		#

		rows = []

		rows.append( ( 'name: ', self._name ) )
		rows.append( ( 'scheme: ', self._scheme ) )
		rows.append( ( 'method: ', self._method ) )
		rows.append( ( 'subdomain (replaces www, if present ): ', self._subdomain ) )
		rows.append( ( 'path: ', self._path ) )

		gridbox = WrapInGrid( self, rows )

		vbox = BetterBoxSizer( wx.VERTICAL )

		vbox.Add( gridbox, CC.FLAGS_EXPAND_PERPENDICULAR )
		vbox.Add( required_credentials_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
		vbox.Add( static_args_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
		vbox.Add( temp_args_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
		vbox.Add( required_cookies_info_box_panel, CC.FLAGS_EXPAND_BOTH_WAYS )
		vbox.Add( content_parsers_panel, CC.FLAGS_EXPAND_BOTH_WAYS )

		self.SetSizer( vbox )

	def GetValue( self ):
		name = self._name.GetValue()
		scheme = self._scheme.GetChoice()
		method = self._method.GetChoice()
		subdomain = self._subdomain.GetValue()
		path = self._path.GetValue()

		required_credentials = self._required_credentials.GetValue()
		static_args = self._static_args.GetValue()
		temp_args = self._temp_args.GetValue()
		required_cookies_info = self._required_cookies_info.GetValue()
		content_parsers = self._content_parsers.GetData()

		if subdomain == '':
			subdomain = None

		login_step = LoginStep( name = name, scheme = scheme, method = method, subdomain = subdomain, path = path )

		login_step.SetComplicatedVariables( required_credentials, static_args, temp_args, required_cookies_info, content_parsers )

		return login_step