From b6c8761e108eade0d1451580fbacf1e3d3f6a957 Mon Sep 17 00:00:00 2001 From: pecksin Date: Thu, 18 Mar 2021 16:47:36 -0400 Subject: re-apply everything manually because git --- src/gui/guiChatConsole.cpp | 32 +++++++++++++++++++++++++++++++- src/gui/guiChatConsole.h | 3 +++ 2 files changed, 34 insertions(+), 1 deletion(-) (limited to 'src/gui') diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index ef471106d..f5336e3ad 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -90,6 +90,8 @@ GUIChatConsole::GUIChatConsole( // set default cursor options setCursor(true, true, 2.0, 0.1); + + m_cache_clickable_chat_weblinks = g_settings->getBool("clickable_chat_weblinks"); } GUIChatConsole::~GUIChatConsole() @@ -321,6 +323,7 @@ void GUIChatConsole::drawText() #if USE_FREETYPE if (m_font->getType() == irr::gui::EGFT_CUSTOM) { + // Draw colored text if FreeType is enabled irr::gui::CGUITTFont *tmp = dynamic_cast(m_font); tmp->draw( @@ -404,9 +407,24 @@ bool GUIChatConsole::OnEvent(const SEvent& event) { ChatPrompt &prompt = m_chat_backend->getPrompt(); + static bool isctrldown; // track this for mouse event - if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) + if(event.EventType == EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown) { + // CTRL up + if(event.KeyInput.Key == KEY_LCONTROL || event.KeyInput.Key == KEY_RCONTROL || !event.KeyInput.Control) + { + isctrldown = false; + } + } + else if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) + { + // CTRL down + if(event.KeyInput.Key == KEY_LCONTROL || event.KeyInput.Key == KEY_RCONTROL || event.KeyInput.Control) + { + isctrldown = true; + } + // Key input if (KeyPress(event.KeyInput) == getKeySetting("keymap_console")) { closeConsole(); @@ -624,6 +642,18 @@ bool GUIChatConsole::OnEvent(const SEvent& event) s32 rows = myround(-3.0 * event.MouseInput.Wheel); m_chat_backend->scroll(rows); } + // Middle click opens weblink, if enabled in config + else if(m_cache_clickable_chat_weblinks && ( + event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN || + (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN && isctrldown) + )) + { + // because console prompt and hardcoded margins + if(event.MouseInput.Y / m_fontsize.Y < (m_height / m_fontsize.Y) - 1 ) + { + m_chat_backend->middleClick(event.MouseInput.X / m_fontsize.X, event.MouseInput.Y / m_fontsize.Y); + } + } } return Parent ? Parent->OnEvent(event) : false; diff --git a/src/gui/guiChatConsole.h b/src/gui/guiChatConsole.h index 896342ab0..501bf50cb 100644 --- a/src/gui/guiChatConsole.h +++ b/src/gui/guiChatConsole.h @@ -124,4 +124,7 @@ private: // font gui::IGUIFont *m_font = nullptr; v2u32 m_fontsize; + + // Enable clickable chat weblinks + bool m_cache_clickable_chat_weblinks; }; -- cgit v1.2.3 From 038a317c213db41227beaa014100dc43b46e392c Mon Sep 17 00:00:00 2001 From: pecksin Date: Fri, 19 Mar 2021 16:25:44 -0400 Subject: user-configurable 'ctrl' keys for ctrl-leftclick in chat weblinks --- minetest.conf.example | 4 +++ src/client/keycode.cpp | 9 +++++++ src/client/keycode.h | 3 +++ src/defaultsettings.cpp | 1 + src/gui/guiChatConsole.cpp | 67 +++++++++++++++++++++++++++++++++++++++++++--- src/gui/guiChatConsole.h | 6 +++++ 6 files changed, 87 insertions(+), 3 deletions(-) (limited to 'src/gui') diff --git a/minetest.conf.example b/minetest.conf.example index 814e34e43..524ef6334 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -24,6 +24,10 @@ # type: string # chat_weblink_color = 8888FF +# If clickable_chat_weblinks is enabled, specify the keys held down to treat left-click as middle-click. +# type: string (comma-delimited) +# chat_weblink_ctrl_keys = KEY_LCONTROL,KEY_RCONTROL,KEY_CONTROL + # If enabled, you can place blocks at the position (feet + eye level) where you stand. # This is helpful when working with nodeboxes in small areas. # type: bool diff --git a/src/client/keycode.cpp b/src/client/keycode.cpp index ce5214f54..08aff43ce 100644 --- a/src/client/keycode.cpp +++ b/src/client/keycode.cpp @@ -386,3 +386,12 @@ irr::EKEY_CODE keyname_to_keycode(const char *name) { return lookup_keyname(name).Key; } + +irr::EKEY_CODE keyname_to_keycode_safemode(const char *name) +{ + try { + return lookup_keyname(name).Key; + } catch (UnknownKeycode &e) { + return irr::KEY_KEY_CODES_COUNT; + } +} diff --git a/src/client/keycode.h b/src/client/keycode.h index 7036705d1..cb3114099 100644 --- a/src/client/keycode.h +++ b/src/client/keycode.h @@ -65,3 +65,6 @@ KeyPress getKeySetting(const char *settingname); void clearKeyCache(); irr::EKEY_CODE keyname_to_keycode(const char *name); + +// Now with internal exception handling. return irr::KEY_KEY_CODES_COUNT on fail +irr::EKEY_CODE keyname_to_keycode_safemode(const char *name); diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index c59b93e88..132e31d2b 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -284,6 +284,7 @@ void set_default_settings() settings->setDefault("joystick_deadzone", "2048"); settings->setDefault("clickable_chat_weblinks", "false"); settings->setDefault("chat_weblink_color", "8888FF"); + settings->setDefault("chat_weblink_ctrl_keys", "KEY_LCONTROL,KEY_RCONTROL,KEY_CONTROL"); // Main menu settings->setDefault("main_menu_path", ""); diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index f5336e3ad..08d26efc2 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -90,8 +90,19 @@ GUIChatConsole::GUIChatConsole( // set default cursor options setCursor(true, true, 2.0, 0.1); - + m_cache_clickable_chat_weblinks = g_settings->getBool("clickable_chat_weblinks"); + if(m_cache_clickable_chat_weblinks) + { + std::string ctrlkeystoparse = g_settings->get("chat_weblink_ctrl_keys"); + if(setupChatClickCtrlKeys(ctrlkeystoparse) == 0) + { + // if fail then try again w hardcoded string + g_logger.log(LL_WARNING, "Failed to parse chat_weblink_ctrl_keys. Using hardcoded default."); + ctrlkeystoparse = "KEY_CONTROL,KEY_LCONTROL,KEY_RCONTROL"; + setupChatClickCtrlKeys(ctrlkeystoparse); + } + } } GUIChatConsole::~GUIChatConsole() @@ -412,7 +423,7 @@ bool GUIChatConsole::OnEvent(const SEvent& event) if(event.EventType == EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown) { // CTRL up - if(event.KeyInput.Key == KEY_LCONTROL || event.KeyInput.Key == KEY_RCONTROL || !event.KeyInput.Control) + if(isInCtrlKeys(event.KeyInput.Key) || !event.KeyInput.Control) { isctrldown = false; } @@ -420,7 +431,7 @@ bool GUIChatConsole::OnEvent(const SEvent& event) else if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) { // CTRL down - if(event.KeyInput.Key == KEY_LCONTROL || event.KeyInput.Key == KEY_RCONTROL || event.KeyInput.Control) + if(isInCtrlKeys(event.KeyInput.Key) || event.KeyInput.Control) { isctrldown = true; } @@ -669,3 +680,53 @@ void GUIChatConsole::setVisible(bool visible) } } +// Return how many "ctrl" keycodes successfully found in string, or 0 on fail +int GUIChatConsole::setupChatClickCtrlKeys(std::string inputline) +{ + m_cache_chat_weblink_ctrl_keys.clear(); + + irr::EKEY_CODE kc; + std::string stemp; + size_t startpos = 0, endpos = 0; + while(startpos < inputline.size()) + { + // Foreach delimited string, + endpos = inputline.find(',', startpos); + endpos = std::min(endpos, inputline.find(' ', startpos)); + // Ignore space/comma + if(endpos == startpos) + { + ++endpos; + } + // Ignore consecutive space/comma + else if(endpos - startpos > 1) + { + // If valid keycode, add it to cached list + stemp = inputline.substr(startpos, endpos - startpos); + kc = keyname_to_keycode_safemode(stemp.c_str()); + if(kc != irr::KEY_KEY_CODES_COUNT) + { + m_cache_chat_weblink_ctrl_keys.push_back(kc); + } + else + { + stemp = "Ignoring unknown keycode '" + stemp + "' for chat_weblink_ctrl_keys, check your conf"; + g_logger.log(LL_WARNING, stemp); + } + } + startpos = endpos; + } + + return m_cache_chat_weblink_ctrl_keys.size(); +} + +bool GUIChatConsole::isInCtrlKeys(const irr::EKEY_CODE& kc) +{ + // To avoid including + for(size_t i=0; i m_cache_chat_weblink_ctrl_keys; }; -- cgit v1.2.3 From 7054232044c1b0ccf50e80d40e5d3d8a37062415 Mon Sep 17 00:00:00 2001 From: pecksin Date: Mon, 22 Mar 2021 17:21:28 -0400 Subject: minor cleanup --- po/minetest.pot | 1 + src/chat.cpp | 14 ++------------ src/gui/guiChatConsole.cpp | 1 - src/gui/guiChatConsole.h | 2 +- 4 files changed, 4 insertions(+), 14 deletions(-) (limited to 'src/gui') diff --git a/po/minetest.pot b/po/minetest.pot index 6f527022b..408995dc5 100644 --- a/po/minetest.pot +++ b/po/minetest.pot @@ -6364,3 +6364,4 @@ msgstr "" #: src/chat.cpp msgid "Failed to open webpage" msgstr "" + diff --git a/src/chat.cpp b/src/chat.cpp index a67371441..5a8afa908 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -37,6 +37,7 @@ ChatBuffer::ChatBuffer(u32 scrollback): if (m_scrollback == 0) m_scrollback = 1; m_empty_formatted_line.first = true; + m_cache_clickable_chat_weblinks = g_settings->getBool("clickable_chat_weblinks"); if(m_cache_clickable_chat_weblinks) { @@ -338,7 +339,7 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, // remaining_in_{in,out}put. Try to end the fragment // on a word boundary. u32 frag_length = 1, space_pos = 0; - + if(!m_cache_clickable_chat_weblinks) { while (frag_length < remaining_in_input && @@ -440,17 +441,6 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols, next_frags.push_back(temp_frag); in_pos += frag_length; remaining_in_output -= std::min(frag_length, remaining_in_output); - - /* - std::cout << "----------" << std::endl; - std::cout << "http_pos = " << http_pos << std::endl; - std::cout << "space_pos = " << space_pos << std::endl; - std::cout << "frag: '" << temp_frag.text.size() << "'" << std::endl; - std::cout << "in_pos: '" << in_pos << std::endl; - std::cout << "remain: '" << line.text.substr(in_pos).size() << "'" << std::endl; - std::cout << "remn in in: '" << remaining_in_input << "'" << std::endl; - std::cout << "remn in out: '" << remaining_in_output << "'" << std::endl; - */ } // handled for fragments individually text_processing = false; diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index 08d26efc2..527a28bdb 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -334,7 +334,6 @@ void GUIChatConsole::drawText() #if USE_FREETYPE if (m_font->getType() == irr::gui::EGFT_CUSTOM) { - // Draw colored text if FreeType is enabled irr::gui::CGUITTFont *tmp = dynamic_cast(m_font); tmp->draw( diff --git a/src/gui/guiChatConsole.h b/src/gui/guiChatConsole.h index 058b78f28..0618f2956 100644 --- a/src/gui/guiChatConsole.h +++ b/src/gui/guiChatConsole.h @@ -128,7 +128,7 @@ private: // font gui::IGUIFont *m_font = nullptr; v2u32 m_fontsize; - + // Enable clickable chat weblinks bool m_cache_clickable_chat_weblinks; // Set of "control" keys for weblink mouseclicks -- cgit v1.2.3 From 1b39c96683f984b82432cb9f9417245680982b41 Mon Sep 17 00:00:00 2001 From: pecksin Date: Mon, 22 Mar 2021 20:57:29 -0400 Subject: only use user-configured ctrl keys for weblink click --- src/gui/guiChatConsole.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/gui') diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp index 527a28bdb..c27ade23d 100644 --- a/src/gui/guiChatConsole.cpp +++ b/src/gui/guiChatConsole.cpp @@ -422,7 +422,7 @@ bool GUIChatConsole::OnEvent(const SEvent& event) if(event.EventType == EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown) { // CTRL up - if(isInCtrlKeys(event.KeyInput.Key) || !event.KeyInput.Control) + if(isInCtrlKeys(event.KeyInput.Key)) { isctrldown = false; } @@ -430,7 +430,7 @@ bool GUIChatConsole::OnEvent(const SEvent& event) else if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) { // CTRL down - if(isInCtrlKeys(event.KeyInput.Key) || event.KeyInput.Control) + if(isInCtrlKeys(event.KeyInput.Key)) { isctrldown = true; } -- cgit v1.2.3 From 7e8e3153e5227eb9b35ed2379efb54d17ba9aeae Mon Sep 17 00:00:00 2001 From: pecksin Date: Tue, 23 Mar 2021 18:18:44 -0400 Subject: move middleClick() into guiChatConsole.cpp where it belongs --- src/chat.cpp | 58 ---------------------------------------------- src/chat.h | 4 ---- src/gui/guiChatConsole.cpp | 58 +++++++++++++++++++++++++++++++++++++++++++++- src/gui/guiChatConsole.h | 4 +++- 4 files changed, 60 insertions(+), 64 deletions(-) (limited to 'src/gui') diff --git a/src/chat.cpp b/src/chat.cpp index c58b6e7cb..9bceb3535 100644 --- a/src/chat.cpp +++ b/src/chat.cpp @@ -28,8 +28,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/strfnd.h" #include "util/string.h" #include "util/numeric.h" -#include "porting.h" -#include "gettext.h" ChatBuffer::ChatBuffer(u32 scrollback): m_scrollback(scrollback) @@ -886,59 +884,3 @@ void ChatBackend::scrollPageUp() { m_console_buffer.scroll(-(s32)m_console_buffer.getRows()); } - -void ChatBackend::middleClick(s32 col, s32 row) -{ - // Prevent accidental rapid clicking - static u32 oldtime = 0; - // seriously.. - u32 newtime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - - // 0.6 seconds should suffice - if(newtime - oldtime < 600) - return; - oldtime = newtime; - - const std::vector & frags = getConsoleBuffer().getFormattedLine(row).fragments; - std::string weblink = ""; // from frag meta - - // Identify targetted fragment, if exists - int ind = frags.size() - 1; - while(u32(col - 1) < frags[ind].column) - { - --ind; - } - if(ind > -1) - { - weblink = frags[ind].meta; - } - - // Debug help - std::string ws; - ws = "Middleclick: (" + std::to_string(col) + ',' + std::to_string(row) + ')' + " frags:"; - for(u32 i=0;imiddleClick(event.MouseInput.X / m_fontsize.X, event.MouseInput.Y / m_fontsize.Y); + middleClick(event.MouseInput.X / m_fontsize.X, event.MouseInput.Y / m_fontsize.Y); } } } @@ -729,3 +729,59 @@ bool GUIChatConsole::isInCtrlKeys(const irr::EKEY_CODE& kc) } return false; } + +void GUIChatConsole::middleClick(s32 col, s32 row) +{ + // Prevent accidental rapid clicking + static u32 oldtime = 0; + // seriously.. + u32 newtime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + // 0.6 seconds should suffice + if(newtime - oldtime < 600) + return; + oldtime = newtime; + + const std::vector & frags = m_chat_backend->getConsoleBuffer().getFormattedLine(row).fragments; + std::string weblink = ""; // from frag meta + + // Identify targetted fragment, if exists + int ind = frags.size() - 1; + while(u32(col - 1) < frags[ind].column) + { + --ind; + } + if(ind > -1) + { + weblink = frags[ind].meta; + } + + // Debug help + std::string ws; + ws = "Middleclick: (" + std::to_string(col) + ',' + std::to_string(row) + ')' + " frags:"; + for(u32 i=0;iaddUnparsedMessage(utf8_to_wide(mesg)); + } +} diff --git a/src/gui/guiChatConsole.h b/src/gui/guiChatConsole.h index 0618f2956..169fb7eb7 100644 --- a/src/gui/guiChatConsole.h +++ b/src/gui/guiChatConsole.h @@ -82,9 +82,11 @@ private: void drawText(); void drawPrompt(); - // Parse conf and populate "ctrl" keys for clickable chat + // Clickable weblink stuff int setupChatClickCtrlKeys(std::string inputline); bool isInCtrlKeys(const irr::EKEY_CODE& kc); + // If clicked fragment has a web url, send it to the system default web browser + void middleClick(s32 col, s32 row); private: ChatBackend* m_chat_backend; -- cgit v1.2.3 7' href='#n467'>467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725
-- Track Circuit Breaks and Track Sections - Player interaction

local players_assign_tcb = {}
local players_assign_signal = {}
local players_link_ts = {}

local ildb = advtrains.interlocking.db
local ilrs = advtrains.interlocking.route

local sigd_equal = advtrains.interlocking.sigd_equal

local lntrans = { "A", "B" }

local function sigd_to_string(sigd)
	return minetest.pos_to_string(sigd.p).." / "..lntrans[sigd.s]
end

minetest.register_node("advtrains_interlocking:tcb_node", {
	drawtype = "mesh",
	paramtype="light",
	paramtype2="facedir",
	walkable = false,
	selection_box = {
		type = "fixed",
		fixed = {-1/6, -1/2, -1/6, 1/6, 1/4, 1/6},
	},
	mesh = "at_il_tcb_node.obj",
	tiles = {"at_il_tcb_node.png"},
	description="Track Circuit Break",
	sunlight_propagates=true,
	groups = {
		cracky=3,
		not_blocking_trains=1,
		--save_in_at_nodedb=2,
		at_il_track_circuit_break = 1,
	},
	after_place_node = function(pos, node, player)
		local meta = minetest.get_meta(pos)
		meta:set_string("infotext", "Unconfigured Track Circuit Break, right-click to assign.")
	end,
	on_rightclick = function(pos, node, player)
		local pname = player:get_player_name()
		if not minetest.check_player_privs(pname, "interlocking") then
			minetest.chat_send_player(pname, "Insufficient privileges to use this!")
			return
		end
		
		local meta = minetest.get_meta(pos)
		local tcbpts = meta:get_string("tcb_pos")
		if tcbpts ~= "" then
			local tcbpos = minetest.string_to_pos(tcbpts)
			advtrains.interlocking.show_tcb_form(tcbpos, pname)
		else
			if not minetest.check_player_privs(pname, "interlocking") then
				minetest.chat_send_player(pname, "Insufficient privileges to use this!")
				return
			end
			--unconfigured
			minetest.chat_send_player(pname, "Configuring TCB: Please punch the rail you want to assign this TCB to.")
			
			players_assign_tcb[pname] = pos
		end	
	end,
	--on_punch = function(pos, node, player)
	--	local meta = minetest.get_meta(pos)
	--	local tcbpts = meta:get_string("tcb_pos")
	--	if tcbpts ~= "" then
	--		local tcbpos = minetest.string_to_pos(tcbpts)
	--		advtrains.interlocking.show_tcb_marker(tcbpos)
	--	end	
	--end,
	can_dig = function(pos, player)
		local pname = player:get_player_name()

		-- Those markers can only be dug when all adjacent TS's are set
		-- as EOI.
		local meta = minetest.get_meta(pos)
		local tcbpts = meta:get_string("tcb_pos")
		if tcbpts ~= "" then
			if not minetest.check_player_privs(pname, "interlocking") then
				minetest.chat_send_player(pname, "Insufficient privileges to use this!")
				return
			end			
			local tcbpos = minetest.string_to_pos(tcbpts)
			local tcb = ildb.get_tcb(tcbpos)
			if not tcb then return true end
			for connid=1,2 do
				if tcb[connid].ts_id then
					return false
				end
			end
		end	
		return true
	end,
	after_dig_node = function(pos, oldnode, oldmetadata, player)
		if not oldmetadata or not oldmetadata.fields then return end
		local tcbpts = oldmetadata.fields.tcb_pos
		if tcbpts and tcbpts ~= "" then
			local tcbpos = minetest.string_to_pos(tcbpts)
			ildb.remove_tcb(tcbpos)
		end
	end,
})

minetest.register_on_punchnode(function(pos, node, player, pointed_thing)
	local pname = player:get_player_name()
	if not minetest.check_player_privs(pname, "interlocking") then
		return
	end
	-- TCB assignment
	local tcbnpos = players_assign_tcb[pname]
	if tcbnpos then
		if vector.distance(pos, tcbnpos)<=20 then
			local node_ok, conns, rhe = advtrains.get_rail_info_at(pos, advtrains.all_tracktypes)
			if node_ok and #conns == 2 then
				local ok = ildb.create_tcb(pos)
				
				if not ok then
					minetest.chat_send_player(pname, "Configuring TCB: TCB already exists at this position. Aborted.")
				end
				
				ildb.sync_tcb_neighbors(pos, 1)
				ildb.sync_tcb_neighbors(pos, 2)
				
				local meta = minetest.get_meta(tcbnpos)
				meta:set_string("tcb_pos", minetest.pos_to_string(pos))
				meta:set_string("infotext", "TCB assigned to "..minetest.pos_to_string(pos))
				minetest.chat_send_player(pname, "Configuring TCB: Successfully configured TCB")
			else
				minetest.chat_send_player(pname, "Configuring TCB: This is not a normal two-connection rail! Aborted.")
			end
		else
			minetest.chat_send_player(pname, "Configuring TCB: Node is too far away. Aborted.")
		end
		players_assign_tcb[pname] = nil
	end
	
	-- Signal assignment
	local sigd = players_assign_signal[pname]
	if sigd then
		if vector.distance(pos, sigd.p)<=50 then
			local is_signal = minetest.get_item_group(node.name, "advtrains_signal") >= 2
			if is_signal then
				local ndef = minetest.registered_nodes[node.name]
				if ndef and ndef.advtrains and ndef.advtrains.set_aspect then
					local tcbs = ildb.get_tcbs(sigd)
					if tcbs then
						tcbs.signal = pos
						if not tcbs.signal_name then
							tcbs.signal_name = "Signal at "..minetest.pos_to_string(sigd.p)
						end
						if not tcbs.routes then
							tcbs.routes = {}
						end
						ildb.set_sigd_for_signal(pos, sigd)
						minetest.chat_send_player(pname, "Configuring TCB: Successfully assigned signal.")
						advtrains.interlocking.show_ip_form(pos, pname, true)
					else
						minetest.chat_send_player(pname, "Configuring TCB: Internal error, TCBS doesn't exist. Aborted.")
					end
				else
					minetest.chat_send_player(pname, "Configuring TCB: Cannot use static signals for routesetting. Aborted.")
				end
			else
				minetest.chat_send_player(pname, "Configuring TCB: Not a compatible signal. Aborted.")
			end
		else
			minetest.chat_send_player(pname, "Configuring TCB: Node is too far away. Aborted.")
		end
		players_assign_signal[pname] = nil
	end
end)

-- TCB Form

local function mktcbformspec(tcbs, btnpref, offset, pname)
	local form = ""
	local ts
	if tcbs.ts_id then
		ts = ildb.get_ts(tcbs.ts_id)
	end
	if ts then
		form = form.."label[0.5,"..offset..";Side "..btnpref..": "..ts.name.."]"
		form = form.."button[0.5,"..(offset+0.5)..";5,1;"..btnpref.."_gotots;Show track section]"
		form = form.."button[0.5,"..(offset+1.5)..";2.5,1;"..btnpref.."_update;Update near TCBs]"
		form = form.."button[3  ,"..(offset+1.5)..";2.5,1;"..btnpref.."_remove;Remove from section]"
	else
		tcbs.ts_id = nil
		form = form.."label[0.5,"..offset..";Side "..btnpref..": ".."End of interlocking]"
		form = form.."button[0.5,"..(offset+0.5)..";5,1;"..btnpref.."_makeil;Create Interlocked Track Section]"
		--if tcbs.section_free then
			--form = form.."button[0.5,"..(offset+1.5)..";5,1;"..btnpref.."_setlocked;Section is free]"
		--else
			--form = form.."button[0.5,"..(offset+1.5)..";5,1;"..btnpref.."_setfree;Section is blocked]"		
		--end
	end
	if tcbs.signal then
		form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_sigdia;Signalling]"	
	else
		form = form.."button[0.5,"..(offset+2.5)..";5,1;"..btnpref.."_asnsig;Assign a signal]"
	end
	return form
end


function advtrains.interlocking.show_tcb_form(pos, pname)
	if not minetest.check_player_privs(pname, "interlocking") then
		minetest.chat_send_player(pname, "Insufficient privileges to use this!")
		return
	end
	local tcb = ildb.get_tcb(pos)
	if not tcb then return end
	
	local form = "size[6,9] label[0.5,0.5;Track Circuit Break Configuration]"
	form = form .. mktcbformspec(tcb[1], "A", 1, pname)
	form = form .. mktcbformspec(tcb[2], "B", 5, pname)
	
	minetest.show_formspec(pname, "at_il_tcbconfig_"..minetest.pos_to_string(pos), form)
	advtrains.interlocking.show_tcb_marker(pos)
end

--helper: length of nil table is 0
local function nlen(t)
	if not t then return 0 end
	return #t
end


minetest.register_on_player_receive_fields(function(player, formname, fields)
	local pname = player:get_player_name()
	if not minetest.check_player_privs(pname, "interlocking") then
		return
	end
	local pts = string.match(formname, "^at_il_tcbconfig_(.+)$")
	local pos
	if pts then
		pos = minetest.string_to_pos(pts)
	end
	if pos and not fields.quit then
		local tcb = ildb.get_tcb(pos)
		if not tcb then return end
		local f_gotots = {fields.A_gotots, fields.B_gotots}
		local f_update = {fields.A_update, fields.B_update}
		local f_remove = {fields.A_remove, fields.B_remove}
		local f_makeil = {fields.A_makeil, fields.B_makeil}
		local f_setlocked = {fields.A_setlocked, fields.B_setlocked}
		local f_setfree = {fields.A_setfree, fields.B_setfree}
		local f_asnsig = {fields.A_asnsig, fields.B_asnsig}
		local f_sigdia = {fields.A_sigdia, fields.B_sigdia}
		
		for connid=1,2 do
			local tcbs = tcb[connid]
			if tcbs.ts_id then
				if f_gotots[connid] then
					advtrains.interlocking.show_ts_form(tcbs.ts_id, pname)
					return
				end
				if f_update[connid] then
					ildb.sync_tcb_neighbors(pos, connid)
				end
				if f_remove[connid] then
					ildb.remove_from_interlocking({p=pos, s=connid})
				end
			else
				if f_makeil[connid] then
					-- try sinc_tcb_neighbors first
					ildb.sync_tcb_neighbors(pos, connid)
					-- if that didn't work, create new section
					if not tcbs.ts_id then
						ildb.create_ts({p=pos, s=connid})
						ildb.sync_tcb_neighbors(pos, connid)
					end
				end
				-- non-interlocked
				if f_setfree[connid] then
					tcbs.section_free = true
				end
				if f_setlocked[connid] then
					tcbs.section_free = nil
				end
			end
			if f_asnsig[connid] and not tcbs.signal then
				minetest.chat_send_player(pname, "Configuring TCB: Please punch the signal to assign.")
				players_assign_signal[pname] = {p=pos, s=connid}
				minetest.close_formspec(pname, formname)
				return
			end
			if f_sigdia[connid] and tcbs.signal then
				advtrains.interlocking.show_signalling_form({p=pos, s=connid}, pname)
				return
			end

		end
		advtrains.interlocking.show_tcb_form(pos, pname)
	end

end)



-- TS Formspec

-- textlist selection temporary storage
local ts_pselidx = {}

function advtrains.interlocking.show_ts_form(ts_id, pname, sel_tcb)
	if not minetest.check_player_privs(pname, "interlocking") then
		minetest.chat_send_player(pname, "Insufficient privileges to use this!")
		return
	end
	local ts = ildb.get_ts(ts_id)
	if not ts_id then return end
	
	local form = "size[10,10]label[0.5,0.5;Track Section Detail - "..ts_id.."]"
	form = form.."field[0.8,2;5.2,1;name;Section name;"..ts.name.."]"
	form = form.."button[5.5,1.7;1,1;setname;Set]"
	local hint
	
	local strtab = {}
	for idx, sigd in ipairs(ts.tc_breaks) do
		strtab[#strtab+1] = minetest.formspec_escape(sigd_to_string(sigd))
		advtrains.interlocking.show_tcb_marker(sigd.p)
	end
	
	form = form.."textlist[0.5,3;5,3;tcblist;"..table.concat(strtab, ",").."]"
	if players_link_ts[pname] then
		local other_id = players_link_ts[pname]
		local other_ts = ildb.get_ts(other_id)
		if other_ts then
			form = form.."button[5.5,3;3.5,1;mklink;Join with "..other_ts.name.."]"
			form = form.."button[9  ,3;0.5,1;cancellink;X]"
		end
	else
		form = form.."button[5.5,3;4,1;link;Join into other section]"
		hint = 1
	end