예제 #1
0
class CalObj(gtk.VBox, CustomizableCalObj):
	_name = "pluginsText"
	desc = _("Plugins Text")

	def __init__(self):
		from scal3.ui_gtk.mywidgets.text_widgets import ReadOnlyTextView
		gtk.VBox.__init__(self)
		self.initVars()
		####
		self.textview = ReadOnlyTextView()
		self.textview.set_wrap_mode(gtk.WrapMode.WORD)
		self.textview.set_justification(gtk.Justification.CENTER)
		self.textbuff = self.textview.get_buffer()
		##
		self.expander = gtk.Expander()
		self.expander.connect("activate", self.expanderExpanded)
		if ui.pluginsTextInsideExpander:
			self.expander.add(self.textview)
			pack(self, self.expander)
			self.expander.set_expanded(ui.pluginsTextIsExpanded)
			self.textview.show()
		else:
			pack(self, self.textview)

	def optionsWidgetCreate(self):
		if self.optionsWidget:
			return
		self.optionsWidget = gtk.HBox()
		self.enableExpanderCheckb = gtk.CheckButton(_("Inside Expander"))
		self.enableExpanderCheckb.set_active(ui.pluginsTextInsideExpander)
		self.enableExpanderCheckb.connect(
			"clicked",
			lambda check: self.setEnableExpander(check.get_active()),
		)
		self.setEnableExpander(ui.pluginsTextInsideExpander)
		pack(self.optionsWidget, self.enableExpanderCheckb)
		####
		self.optionsWidget.show_all()

	def expanderExpanded(self, exp):
		ui.pluginsTextIsExpanded = not exp.get_expanded()
		ui.saveLiveConf()

	def getWidget(self):
		return (
			self.expander if ui.pluginsTextInsideExpander
			else self.textview
		)

	def setText(self, text):
		if text:
			self.textbuff.set_text(text)
			self.getWidget().show()
		else:## elif self.get_property("visible")
			self.textbuff.set_text("")## forethought
			self.getWidget().hide()

	def setEnableExpander(self, enable):
		#print("setEnableExpander", enable)
		if enable:
			if not ui.pluginsTextInsideExpander:
				self.remove(self.textview)
				self.expander.add(self.textview)
				pack(self, self.expander)
				self.expander.show_all()
		else:
			if ui.pluginsTextInsideExpander:
				self.expander.remove(self.textview)
				self.remove(self.expander)
				pack(self, self.textview)
				self.textview.show()
		ui.pluginsTextInsideExpander = enable
		self.onDateChange()

	def onDateChange(self, *a, **kw):
		CustomizableCalObj.onDateChange(self, *a, **kw)
		self.setText(ui.cell.pluginsText)
예제 #2
0
파일: history.py 프로젝트: ilius/starcal
class EventHistoryDialog(gtk.Dialog):
	textViewTypes = [
		"Change Table",
		"Full Table",
		"After change (Text)",
		"After change (JSON)",
		"After change (Plain JSON)",
		"Before change (Text)",
		"Before change (JSON)",
		"Before change (Plain JSON)",
		"Change (JSON Diff)",
	]

	def onResponse(self, w, e):
		self.hide()
		if ui.eventManDialog:
			ui.eventManDialog.onConfigChange()

	def __init__(
		self,
		event,
		**kwargs
	):
		checkEventsReadOnly()
		gtk.Dialog.__init__(self, **kwargs)
		self.set_title(_("History") + ": " + event.summary)
		self.event = event
		self.objectCache = {} # hash(str) -> data(dict)

		dialog_add_button(
			self,
			gtk.STOCK_CLOSE,
			_("_Close"),
			gtk.ResponseType.OK,
		)

		self.connect("response", self.onResponse)

		treev = gtk.TreeView()
		treev.set_headers_clickable(True)
		trees = gtk.ListStore(
			str, # hashBefore (hidden)
			str, # hashAfter (hidden)
			str, # formatted date & time
			str, # change msg (names or the number of changed params)
		)
		treev.set_model(trees)
		treev.connect("cursor-changed", self.treeviewCursorChanged)
		# treev.connect("button-press-event", self.treeviewCursorChanged)
		# FIXME: what is the signal for deselecting / unselecting a row?
		self.trees = trees
		self.treev = treev

		treevSwin = gtk.ScrolledWindow()
		treevSwin.add(treev)
		treevSwin.set_policy(
			gtk.PolicyType.AUTOMATIC,
			gtk.PolicyType.AUTOMATIC,
		)

		hpan = gtk.HPaned()
		hpan.add1(treevSwin)
		leftVbox = gtk.VBox()
		hpan.add2(leftVbox)
		hpan.set_position(600)
		pack(self.vbox, hpan, expand=True, fill=True)

		actionBox = gtk.VBox(spacing=5)
		pack(leftVbox, actionBox, padding=30)

		# revertButton = gtk.Button()
		# revertButton.set_label(_("Revert this vhange"))
		# revertButton.set_image(gtk.Image.new_from_stock(
		# 	gtk.STOCK_UNDO,
		# 	gtk.IconSize.BUTTON,
		# ))
		# revertButton.connect("clicked", self.revertClicked)
		# pack(actionBox, revertButton, padding=1)
		# self.revertButton = revertButton

		checkoutAfterButton = gtk.Button()
		checkoutAfterButton.set_label(_("Select revision after this change"))
		checkoutAfterButton.set_image(gtk.Image.new_from_stock(
			gtk.STOCK_UNDO,
			gtk.IconSize.BUTTON,
		))
		checkoutAfterButton.connect("clicked", self.checkoutAfterClicked)
		pack(actionBox, checkoutAfterButton, padding=1)
		self.checkoutAfterButton = checkoutAfterButton

		checkoutBeforeButton = gtk.Button()
		checkoutBeforeButton.set_label(_("Select revision before this change"))
		checkoutBeforeButton.set_image(gtk.Image.new_from_stock(
			gtk.STOCK_UNDO,
			gtk.IconSize.BUTTON,
		))
		checkoutBeforeButton.connect("clicked", self.checkoutBeforeClicked)
		pack(actionBox, checkoutBeforeButton, padding=1)
		self.checkoutBeforeButton = checkoutBeforeButton

		self.setButtonsSensitive(False)

		combo = gtk.ComboBoxText()
		for text in self.textViewTypes:
			combo.append_text(_(text))
		combo.set_active(1)
		combo.connect("changed", self.viewTypeComboChanged)
		self.viewTypeCombo = combo

		textTypeHbox = gtk.HBox()
		pack(textTypeHbox, gtk.Label(_("View type")+": "))
		pack(textTypeHbox, self.viewTypeCombo)
		pack(leftVbox, textTypeHbox)

		self.textview = ReadOnlyTextView()
		self.textview.set_wrap_mode(gtk.WrapMode.NONE)
		self.textview.set_justification(gtk.Justification.LEFT)
		self.textbuff = self.textview.get_buffer()

		cmpTreev = gtk.TreeView()
		cmpTreev.set_headers_clickable(True)
		cmpTrees = gtk.ListStore(
			str, # change symbol ("M", "+", "-", "")
			str, # key
			str, # old value
			str, # new value
		)
		cmpTreev.set_model(cmpTrees)
		# cmpTreev.connect("cursor-changed", self.cmpTreeviewCursorChanged)
		self.cmpTrees = cmpTrees
		self.cmpTreev = cmpTreev

		cell = gtk.CellRendererText()
		col = gtk.TreeViewColumn("", cell, text=0)
		col.set_resizable(True)
		cmpTreev.append_column(col)

		cell = gtk.CellRendererText()
		col = gtk.TreeViewColumn(_("Key"), cell, text=1)
		col.set_resizable(True)
		cmpTreev.append_column(col)

		cell = gtk.CellRendererText()
		col = gtk.TreeViewColumn(_("Old Value"), cell, text=2)
		col.set_resizable(True)
		cmpTreev.append_column(col)

		cell = gtk.CellRendererText()
		col = gtk.TreeViewColumn(_("New Value"), cell, text=3)
		col.set_resizable(True)
		cmpTreev.append_column(col)

		leftSwin = gtk.ScrolledWindow()
		leftSwin.set_policy(
			gtk.PolicyType.AUTOMATIC,
			gtk.PolicyType.AUTOMATIC,
		)
		pack(leftVbox, leftSwin, expand=True, fill=True)
		self.leftSwin = leftSwin

		cell = gtk.CellRendererText()
		col = gtk.TreeViewColumn(_("Time"), cell, text=2)
		treev.append_column(col)

		cell = gtk.CellRendererText()
		col = gtk.TreeViewColumn(_("Change Summary"), cell, text=3)
		treev.append_column(col)
		col.set_property("expand", True)

		self.load()
		self.vbox.show_all()
		self.resize(ud.screenW, ud.screenH*0.9) # FIXME

	def treeviewCursorChanged(self, treev, e=None):
		self.updateViewType()

	def viewTypeComboChanged(self, combo):
		self.updateViewType()

	def setButtonsSensitive(self, sensitive: bool):
		# self.revertButton.set_sensitive(sensitive)
		self.checkoutAfterButton.set_sensitive(sensitive)
		self.checkoutBeforeButton.set_sensitive(sensitive)

	def updateViewType(self):
		path = self.treev.get_cursor()[0]
		if not path:
			self.setButtonsSensitive(False)
			return
		assert len(path) == 1
		self.setButtonsSensitive(True)
		index = path[0]
		row = self.trees[index]
		hashBefore = row[0]
		hashAfter = row[1]

		viewTypeIndex = self.viewTypeCombo.get_active()
		if viewTypeIndex == -1:
			return
		viewType = self.textViewTypes[viewTypeIndex]

		if viewType in ("Change Table", "Full Table"):
			self.updateTableViewType(viewType, hashBefore, hashAfter)
		else:
			self.updateTextViewType(viewType, hashBefore, hashAfter)

	def updateTableViewType(self, viewType, hashBefore, hashAfter):
		event = self.event
		trees = self.cmpTrees
		trees.clear()

		if viewType == "Change Table":
			if hashBefore != "":
				diff = self.extractChangeDiff(hashBefore, hashAfter)
				for key in sorted(diff.keys()):
					(valueBefore, valueAfter) = diff[key]
					trees.append(["M", key, str(valueBefore), str(valueAfter)])
		elif viewType == "Full Table":
			for row in self.extractFullTable(hashBefore, hashAfter):
				trees.append(row)
		else:
			raise ValueError("Unexpected viewType=%r" % viewType)

		self.setScrolledWinChild(self.cmpTreev)

	def updateTextViewType(self, viewType, hashBefore, hashAfter):
		event = self.event
		text = ""
		if viewType == "After change (Text)":
			if hashAfter != "":
				text = event.getRevision(hashAfter).getInfo()
		elif viewType == "Before change (Text)":
			if hashBefore != "":
				text = event.getRevision(hashBefore).getInfo()
		elif viewType == "After change (JSON)":
			if hashAfter != "":
				text = dataToPrettyJson(event.getRevision(hashAfter).getData())
		elif viewType == "After change (Plain JSON)":
			if hashAfter != "":
				text = dataToPrettyJson(self.getObjectData(hashAfter))
		elif viewType == "Before change (JSON)":
			if hashBefore != "":
				text = dataToPrettyJson(event.getRevision(hashBefore).getData())
		elif viewType == "Before change (Plain JSON)":
			if hashBefore != "":
				text = dataToPrettyJson(self.getObjectData(hashBefore))
		elif viewType == "Change (JSON Diff)":
			if hashBefore != "" and hashAfter != "":
				diff = self.extractChangeDiff(hashBefore, hashAfter)
				text = dataToPrettyJson(diff)
		else:
			raise ValueError("Unexpected viewType=%r" % viewType)
		
		self.textbuff.set_text(text)
		self.setScrolledWinChild(self.textview)

	def setScrolledWinChild(self, new_child):
		old_child = self.leftSwin.get_child()
		if old_child != new_child:
			if old_child is not None:
				print("removing old child")
				self.leftSwin.remove(old_child)
			print("adding new child")
			self.leftSwin.add(new_child)
			new_child.show()

	def switchToRevision(self, revHash):
		newEvent = self.event.getRevision(revHash)
		self.event.parent.removeFromCache(self.event.id)
		# newEvent.id is set
		newEvent.parent = self.event.parent
		newEvent.save()
		self.event = newEvent
		self.load()
		ui.eventDiff.add("e", newEvent)

	def checkoutAfterClicked(self, button):
		path = self.treev.get_cursor()[0]
		if not path:
			return
		assert len(path) == 1
		index = path[0]
		row = self.trees[index]
		hashAfter = row[1]
		self.switchToRevision(hashAfter)

	def checkoutBeforeClicked(self, button):
		path = self.treev.get_cursor()[0]
		if not path:
			return
		assert len(path) == 1
		index = path[0]
		row = self.trees[index]
		hashBefore = row[0]
		self.switchToRevision(hashBefore)
		
	# def revertClicked(self, button):
	# 	path = self.treev.get_cursor()[0]
	# 	if not path:
	# 		return
	# 	assert len(path) == 1
	# 	index = path[0]
	# 	row = self.trees[index]
	# 	hashBefore = row[0]
	# 	hashAfter = row[1]
	# 	# TODO

	def formatEpoch(self, epoch):
		jd, h, m, s = getJhmsFromEpoch(epoch)
		cell = ui.cellCache.getCell(jd)
		return cell.format(historyTimeBinFmt, tm=(h, m, s))

	def normalizeObjectData(self, data):
		if "rules" in data:
			rulesOd = OrderedDict()
			for name, value in data["rules"]:
				rulesOd[name] = value
			data["rules"] = rulesOd
		return unnest(data)

	# returns normalized data ("rules.RULE_NAME" keys)
	def getObjectData(self, _hash):
		if not _hash:
			return {}
		if _hash in self.objectCache:
			return self.objectCache[_hash]
		data = loadBsonObject(_hash)
		data = self.normalizeObjectData(data)
		if len(self.objectCache) > 100:
			self.objectCache.popitem()
		self.objectCache[_hash] = data
		return data

	def extractChangeDiff(self, hashBefore, hashAfter):
		"""
		returns: dict: param -> (valueBefore, valueAfter)
		"""
		dataBefore = self.getObjectData(hashBefore)
		dataAfter = self.getObjectData(hashAfter)
		diff = {}
		for key, valueBefore in dataBefore.items():
			valueAfter = dataAfter.get(key, None)
			if valueAfter == valueBefore:
				continue
			diff[key] = (valueBefore, valueAfter)
		for key, valueAfter in dataAfter.items():
			if key in diff:
				continue
			valueBefore = dataBefore.get(key, None)
			if valueAfter == valueBefore:
				continue
			diff[key] = (valueBefore, valueAfter)
		
		return diff

	def extractFullTable(self, hashBefore, hashAfter):
		dataBefore = self.getObjectData(hashBefore)
		dataAfter = self.getObjectData(hashAfter)
		dataFull = [] # (symbol, key, valueBefore, valueAfter)
		keys = sorted(set(dataBefore.keys()).union(dataAfter.keys()))
		for key in keys:
			valueBefore = dataBefore.get(key, "")
			valueAfter = dataAfter.get(key, "")
			symbol = ""
			if valueBefore == valueAfter:
				pass
			elif key not in dataBefore:
				symbol = "+"
			elif key not in dataAfter:
				symbol = "-"
			else:
				symbol = "M"
			dataFull.append([
				symbol,
				key,
				"%s" % (valueBefore,),
				"%s" % (valueAfter,),
			])
		return dataFull

	def extractChangeSummary(self, diff):
		"""
		diff: dict: param -> (valueBefore, valueAfter)
		"""

		if len(diff) < 3:
			return ", ".join(diff.keys())
		
		return _("%s parameters") % _(len(diff))

	def load(self):
		trees = self.trees
		trees.clear()
		hist = self.event.loadHistory()
		count = len(hist)
		for index, (epoch, hashAfter) in enumerate(hist):
			if index == count-1:
				trees.append([
					"",
					hashAfter,
					self.formatEpoch(epoch),
					_("(Added Event)"),
				])
				continue				
			
			hashBefore = hist[index+1][1]
			diff = self.extractChangeDiff(hashBefore, hashAfter)
			changeSummary = self.extractChangeSummary(diff)

			trees.append([
				hashBefore,
				hashAfter,
				self.formatEpoch(epoch),
				changeSummary,
			])
예제 #3
0
class CalObj(gtk.VBox, CustomizableCalObj):
    _name = 'pluginsText'
    desc = _('Plugins Text')
    def __init__(self):
        from scal3.ui_gtk.mywidgets.text_widgets import ReadOnlyTextView
        gtk.VBox.__init__(self)
        self.initVars()
        ####
        self.textview = ReadOnlyTextView()
        self.textview.set_wrap_mode(gtk.WrapMode.WORD)
        self.textview.set_justification(gtk.Justification.CENTER)
        self.textbuff = self.textview.get_buffer()
        ##
        self.expander = gtk.Expander()
        self.expander.connect('activate', self.expanderExpanded)
        if ui.pluginsTextInsideExpander:
            self.expander.add(self.textview)
            pack(self, self.expander)
            self.expander.set_expanded(ui.pluginsTextIsExpanded)
            self.textview.show()
        else:
            pack(self, self.textview)
    def optionsWidgetCreate(self):
        if self.optionsWidget:
            return
        self.optionsWidget = gtk.HBox()
        self.enableExpanderCheckb = gtk.CheckButton(_('Inside Expander'))
        self.enableExpanderCheckb.set_active(ui.pluginsTextInsideExpander)
        self.enableExpanderCheckb.connect('clicked', lambda check: self.setEnableExpander(check.get_active()))
        self.setEnableExpander(ui.pluginsTextInsideExpander)
        pack(self.optionsWidget, self.enableExpanderCheckb)
        ####
        self.optionsWidget.show_all()
    def expanderExpanded(self, exp):
        ui.pluginsTextIsExpanded = not exp.get_expanded()
        ui.saveLiveConf()
    getWidget = lambda self: self.expander if ui.pluginsTextInsideExpander else self.textview
    def setText(self, text):
        if text:
            self.textbuff.set_text(text)
            self.getWidget().show()
        else:## elif self.get_property('visible')
            self.textbuff.set_text('')## forethought
            self.getWidget().hide()
    def setEnableExpander(self, enable):
        #print('setEnableExpander', enable)
        if enable:
            if not ui.pluginsTextInsideExpander:
                self.remove(self.textview)
                self.expander.add(self.textview)
                pack(self, self.expander)
                self.expander.show_all()
        else:
            if ui.pluginsTextInsideExpander:
                self.expander.remove(self.textview)
                self.remove(self.expander)
                pack(self, self.textview)
                self.textview.show()
        ui.pluginsTextInsideExpander = enable
        self.onDateChange()
    def onDateChange(self, *a, **kw):
        CustomizableCalObj.onDateChange(self, *a, **kw)
        self.setText(ui.cell.pluginsText)
예제 #4
0
파일: history.py 프로젝트: ilius/starcal
class EventHistoryDialog(gtk.Dialog):
    textViewTypes = [
        "Change Table",
        "Full Table",
        "After change (Text)",
        "After change (JSON)",
        "After change (Plain JSON)",
        "Before change (Text)",
        "Before change (JSON)",
        "Before change (Plain JSON)",
        "Change (JSON Diff)",
    ]

    def onResponse(self, w, e):
        self.hide()
        if ui.eventManDialog:
            ui.eventManDialog.onConfigChange()

    def __init__(self, event, **kwargs):
        checkEventsReadOnly()
        gtk.Dialog.__init__(self, **kwargs)
        self.set_title(_("History") + ": " + event.summary)
        self.event = event
        self.objectCache = {}  # hash(str) -> data(dict)

        dialog_add_button(
            self,
            gtk.STOCK_CLOSE,
            _("_Close"),
            gtk.ResponseType.OK,
        )

        self.connect("response", self.onResponse)

        treev = gtk.TreeView()
        treev.set_headers_clickable(True)
        trees = gtk.ListStore(
            str,  # hashBefore (hidden)
            str,  # hashAfter (hidden)
            str,  # formatted date & time
            str,  # change msg (names or the number of changed params)
        )
        treev.set_model(trees)
        treev.connect("cursor-changed", self.treeviewCursorChanged)
        # treev.connect("button-press-event", self.treeviewCursorChanged)
        # FIXME: what is the signal for deselecting / unselecting a row?
        self.trees = trees
        self.treev = treev

        treevSwin = gtk.ScrolledWindow()
        treevSwin.add(treev)
        treevSwin.set_policy(
            gtk.PolicyType.AUTOMATIC,
            gtk.PolicyType.AUTOMATIC,
        )

        hpan = gtk.HPaned()
        hpan.add1(treevSwin)
        leftVbox = gtk.VBox()
        hpan.add2(leftVbox)
        hpan.set_position(600)
        pack(self.vbox, hpan, expand=True, fill=True)

        actionBox = gtk.VBox(spacing=5)
        pack(leftVbox, actionBox, padding=30)

        # revertButton = gtk.Button()
        # revertButton.set_label(_("Revert this vhange"))
        # revertButton.set_image(gtk.Image.new_from_stock(
        # 	gtk.STOCK_UNDO,
        # 	gtk.IconSize.BUTTON,
        # ))
        # revertButton.connect("clicked", self.revertClicked)
        # pack(actionBox, revertButton, padding=1)
        # self.revertButton = revertButton

        checkoutAfterButton = gtk.Button()
        checkoutAfterButton.set_label(_("Select revision after this change"))
        checkoutAfterButton.set_image(
            gtk.Image.new_from_stock(
                gtk.STOCK_UNDO,
                gtk.IconSize.BUTTON,
            ))
        checkoutAfterButton.connect("clicked", self.checkoutAfterClicked)
        pack(actionBox, checkoutAfterButton, padding=1)
        self.checkoutAfterButton = checkoutAfterButton

        checkoutBeforeButton = gtk.Button()
        checkoutBeforeButton.set_label(_("Select revision before this change"))
        checkoutBeforeButton.set_image(
            gtk.Image.new_from_stock(
                gtk.STOCK_UNDO,
                gtk.IconSize.BUTTON,
            ))
        checkoutBeforeButton.connect("clicked", self.checkoutBeforeClicked)
        pack(actionBox, checkoutBeforeButton, padding=1)
        self.checkoutBeforeButton = checkoutBeforeButton

        self.setButtonsSensitive(False)

        combo = gtk.ComboBoxText()
        for text in self.textViewTypes:
            combo.append_text(_(text))
        combo.set_active(1)
        combo.connect("changed", self.viewTypeComboChanged)
        self.viewTypeCombo = combo

        textTypeHbox = gtk.HBox()
        pack(textTypeHbox, gtk.Label(_("View type") + ": "))
        pack(textTypeHbox, self.viewTypeCombo)
        pack(leftVbox, textTypeHbox)

        self.textview = ReadOnlyTextView()
        self.textview.set_wrap_mode(gtk.WrapMode.NONE)
        self.textview.set_justification(gtk.Justification.LEFT)
        self.textbuff = self.textview.get_buffer()

        cmpTreev = gtk.TreeView()
        cmpTreev.set_headers_clickable(True)
        cmpTrees = gtk.ListStore(
            str,  # change symbol ("M", "+", "-", "")
            str,  # key
            str,  # old value
            str,  # new value
        )
        cmpTreev.set_model(cmpTrees)
        # cmpTreev.connect("cursor-changed", self.cmpTreeviewCursorChanged)
        self.cmpTrees = cmpTrees
        self.cmpTreev = cmpTreev

        cell = gtk.CellRendererText()
        col = gtk.TreeViewColumn("", cell, text=0)
        col.set_resizable(True)
        cmpTreev.append_column(col)

        cell = gtk.CellRendererText()
        col = gtk.TreeViewColumn(_("Key"), cell, text=1)
        col.set_resizable(True)
        cmpTreev.append_column(col)

        cell = gtk.CellRendererText()
        col = gtk.TreeViewColumn(_("Old Value"), cell, text=2)
        col.set_resizable(True)
        cmpTreev.append_column(col)

        cell = gtk.CellRendererText()
        col = gtk.TreeViewColumn(_("New Value"), cell, text=3)
        col.set_resizable(True)
        cmpTreev.append_column(col)

        leftSwin = gtk.ScrolledWindow()
        leftSwin.set_policy(
            gtk.PolicyType.AUTOMATIC,
            gtk.PolicyType.AUTOMATIC,
        )
        pack(leftVbox, leftSwin, expand=True, fill=True)
        self.leftSwin = leftSwin

        cell = gtk.CellRendererText()
        col = gtk.TreeViewColumn(_("Time"), cell, text=2)
        treev.append_column(col)

        cell = gtk.CellRendererText()
        col = gtk.TreeViewColumn(_("Change Summary"), cell, text=3)
        treev.append_column(col)
        col.set_property("expand", True)

        self.load()
        self.vbox.show_all()
        self.resize(ud.screenW, ud.screenH * 0.9)  # FIXME

    def treeviewCursorChanged(self, treev, e=None):
        self.updateViewType()

    def viewTypeComboChanged(self, combo):
        self.updateViewType()

    def setButtonsSensitive(self, sensitive: bool):
        # self.revertButton.set_sensitive(sensitive)
        self.checkoutAfterButton.set_sensitive(sensitive)
        self.checkoutBeforeButton.set_sensitive(sensitive)

    def updateViewType(self):
        path = self.treev.get_cursor()[0]
        if not path:
            self.setButtonsSensitive(False)
            return
        assert len(path) == 1
        self.setButtonsSensitive(True)
        index = path[0]
        row = self.trees[index]
        hashBefore = row[0]
        hashAfter = row[1]

        viewTypeIndex = self.viewTypeCombo.get_active()
        if viewTypeIndex == -1:
            return
        viewType = self.textViewTypes[viewTypeIndex]

        if viewType in ("Change Table", "Full Table"):
            self.updateTableViewType(viewType, hashBefore, hashAfter)
        else:
            self.updateTextViewType(viewType, hashBefore, hashAfter)

    def updateTableViewType(self, viewType, hashBefore, hashAfter):
        event = self.event
        trees = self.cmpTrees
        trees.clear()

        if viewType == "Change Table":
            if hashBefore != "":
                diff = self.extractChangeDiff(hashBefore, hashAfter)
                for key in sorted(diff.keys()):
                    (valueBefore, valueAfter) = diff[key]
                    trees.append(["M", key, str(valueBefore), str(valueAfter)])
        elif viewType == "Full Table":
            for row in self.extractFullTable(hashBefore, hashAfter):
                trees.append(row)
        else:
            raise ValueError("Unexpected viewType=%r" % viewType)

        self.setScrolledWinChild(self.cmpTreev)

    def updateTextViewType(self, viewType, hashBefore, hashAfter):
        event = self.event
        text = ""
        if viewType == "After change (Text)":
            if hashAfter != "":
                text = event.getRevision(hashAfter).getInfo()
        elif viewType == "Before change (Text)":
            if hashBefore != "":
                text = event.getRevision(hashBefore).getInfo()
        elif viewType == "After change (JSON)":
            if hashAfter != "":
                text = dataToPrettyJson(event.getRevision(hashAfter).getData())
        elif viewType == "After change (Plain JSON)":
            if hashAfter != "":
                text = dataToPrettyJson(self.getObjectData(hashAfter))
        elif viewType == "Before change (JSON)":
            if hashBefore != "":
                text = dataToPrettyJson(
                    event.getRevision(hashBefore).getData())
        elif viewType == "Before change (Plain JSON)":
            if hashBefore != "":
                text = dataToPrettyJson(self.getObjectData(hashBefore))
        elif viewType == "Change (JSON Diff)":
            if hashBefore != "" and hashAfter != "":
                diff = self.extractChangeDiff(hashBefore, hashAfter)
                text = dataToPrettyJson(diff)
        else:
            raise ValueError("Unexpected viewType=%r" % viewType)

        self.textbuff.set_text(text)
        self.setScrolledWinChild(self.textview)

    def setScrolledWinChild(self, new_child):
        old_child = self.leftSwin.get_child()
        if old_child != new_child:
            if old_child is not None:
                print("removing old child")
                self.leftSwin.remove(old_child)
            print("adding new child")
            self.leftSwin.add(new_child)
            new_child.show()

    def switchToRevision(self, revHash):
        newEvent = self.event.getRevision(revHash)
        self.event.parent.removeFromCache(self.event.id)
        # newEvent.id is set
        newEvent.parent = self.event.parent
        newEvent.save()
        self.event = newEvent
        self.load()
        ui.eventDiff.add("e", newEvent)

    def checkoutAfterClicked(self, button):
        path = self.treev.get_cursor()[0]
        if not path:
            return
        assert len(path) == 1
        index = path[0]
        row = self.trees[index]
        hashAfter = row[1]
        self.switchToRevision(hashAfter)

    def checkoutBeforeClicked(self, button):
        path = self.treev.get_cursor()[0]
        if not path:
            return
        assert len(path) == 1
        index = path[0]
        row = self.trees[index]
        hashBefore = row[0]
        self.switchToRevision(hashBefore)

    # def revertClicked(self, button):
    # 	path = self.treev.get_cursor()[0]
    # 	if not path:
    # 		return
    # 	assert len(path) == 1
    # 	index = path[0]
    # 	row = self.trees[index]
    # 	hashBefore = row[0]
    # 	hashAfter = row[1]
    # 	# TODO

    def formatEpoch(self, epoch):
        jd, h, m, s = getJhmsFromEpoch(epoch)
        cell = ui.cellCache.getCell(jd)
        return cell.format(historyTimeBinFmt, tm=(h, m, s))

    def normalizeObjectData(self, data):
        if "rules" in data:
            rulesOd = OrderedDict()
            for name, value in data["rules"]:
                rulesOd[name] = value
            data["rules"] = rulesOd
        return unnest(data)

    # returns normalized data ("rules.RULE_NAME" keys)
    def getObjectData(self, _hash):
        if not _hash:
            return {}
        if _hash in self.objectCache:
            return self.objectCache[_hash]
        data = loadBsonObject(_hash)
        data = self.normalizeObjectData(data)
        if len(self.objectCache) > 100:
            self.objectCache.popitem()
        self.objectCache[_hash] = data
        return data

    def extractChangeDiff(self, hashBefore, hashAfter):
        """
		returns: dict: param -> (valueBefore, valueAfter)
		"""
        dataBefore = self.getObjectData(hashBefore)
        dataAfter = self.getObjectData(hashAfter)
        diff = {}
        for key, valueBefore in dataBefore.items():
            valueAfter = dataAfter.get(key, None)
            if valueAfter == valueBefore:
                continue
            diff[key] = (valueBefore, valueAfter)
        for key, valueAfter in dataAfter.items():
            if key in diff:
                continue
            valueBefore = dataBefore.get(key, None)
            if valueAfter == valueBefore:
                continue
            diff[key] = (valueBefore, valueAfter)

        return diff

    def extractFullTable(self, hashBefore, hashAfter):
        dataBefore = self.getObjectData(hashBefore)
        dataAfter = self.getObjectData(hashAfter)
        dataFull = []  # (symbol, key, valueBefore, valueAfter)
        keys = sorted(set(dataBefore.keys()).union(dataAfter.keys()))
        for key in keys:
            valueBefore = dataBefore.get(key, "")
            valueAfter = dataAfter.get(key, "")
            symbol = ""
            if valueBefore == valueAfter:
                pass
            elif key not in dataBefore:
                symbol = "+"
            elif key not in dataAfter:
                symbol = "-"
            else:
                symbol = "M"
            dataFull.append([
                symbol,
                key,
                "%s" % (valueBefore, ),
                "%s" % (valueAfter, ),
            ])
        return dataFull

    def extractChangeSummary(self, diff):
        """
		diff: dict: param -> (valueBefore, valueAfter)
		"""

        if len(diff) < 3:
            return ", ".join(diff.keys())

        return _("%s parameters") % _(len(diff))

    def load(self):
        trees = self.trees
        trees.clear()
        hist = self.event.loadHistory()
        count = len(hist)
        for index, (epoch, hashAfter) in enumerate(hist):
            if index == count - 1:
                trees.append([
                    "",
                    hashAfter,
                    self.formatEpoch(epoch),
                    _("(Added Event)"),
                ])
                continue

            hashBefore = hist[index + 1][1]
            diff = self.extractChangeDiff(hashBefore, hashAfter)
            changeSummary = self.extractChangeSummary(diff)

            trees.append([
                hashBefore,
                hashAfter,
                self.formatEpoch(epoch),
                changeSummary,
            ])