eg/Classes/MainFrame/LogCtrl.py
# -*- coding: utf-8 -*-
#
# This file is part of EventGhost.
# Copyright © 2005-2020 EventGhost Project <http://www.eventghost.net/>
#
# EventGhost is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# EventGhost is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with EventGhost. If not, see <http://www.gnu.org/licenses/>.
import collections
import wx
from ast import literal_eval
from time import localtime, strftime
# Local imports
import eg
EVENT_ICON = eg.EventItem.icon
ERROR_ICON = eg.Icons.ERROR_ICON
DEBUG_ICON = eg.Icons.DEBUG_ICON
WARNING_ICON = eg.Icons.WARNING_ICON
def _create_colour_attributes(foreground, background):
attr1 = wx.ListItemAttr()
attr1.BackgroundColour = eg.colour.GetOddLogColour(background)
attr1.TextColour = foreground
attr2 = wx.ListItemAttr()
attr2.BackgroundColour = background
attr2.TextColour = foreground
return attr1, attr2
class LogCtrl(wx.ListCtrl):
"""
Implementation of a ListCtrl with a circular buffer.
"""
def __init__(self, parent):
self.maxlength = 2000
self.removeOnMax = 200
self.indent = ""
self.OnGetItemText = self.OnGetItemTextWithDT
wx.ListCtrl.__init__(
self,
parent,
style=(
wx.LC_REPORT |
wx.LC_VIRTUAL |
wx.NO_FULL_REPAINT_ON_RESIZE |
wx.HSCROLL |
wx.CLIP_CHILDREN |
wx.LC_NO_HEADER
)
)
if eg.config.useFixedFont:
df = self.GetFont()
font = wx.Font(df.GetPointSize(), wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, "Courier New")
self.SetFont(font)
self.SetImageList(eg.Icons.gImageList, wx.IMAGE_LIST_SMALL)
self.logColours = _create_colour_attributes(
eg.colour.windowText,
eg.colour.windowBackground
)
self.errorColours = _create_colour_attributes(
eg.colour.errorText,
eg.colour.errorBackground
)
self.debugColours = _create_colour_attributes(
eg.colour.debugText,
eg.colour.debugBackground
)
self.warningColours = _create_colour_attributes(
eg.colour.warningText,
eg.colour.warningBackground
)
self.InsertColumn(0, "")
# logger popup menu
menu = wx.Menu()
menu.Append(wx.ID_SELECTALL, eg.text.MainFrame.Menu.SelectAll)
self.Bind(wx.EVT_MENU, self.OnCmdSelectAll, id=wx.ID_SELECTALL)
menu.Append(wx.ID_COPY, eg.text.MainFrame.Menu.Copy)
self.Bind(wx.EVT_MENU, self.OnCmdCopy, id=wx.ID_COPY)
menu.AppendSeparator()
menuId = wx.NewId()
menu.Append(menuId, eg.text.MainFrame.Menu.Replay)
self.Bind(wx.EVT_MENU, self.OnCmdReplay, id=menuId)
menu.AppendSeparator()
menuId = wx.NewId()
menu.Append(menuId, eg.text.MainFrame.Menu.ClearLog)
self.Bind(wx.EVT_MENU, self.OnCmdClearLog, id=menuId)
self.contextMenu = menu
self.Bind(wx.EVT_CONTEXT_MENU, self.OnRightUp)
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.OnStartDrag)
self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
accel_entries = [
wx.AcceleratorEntry(wx.ACCEL_CTRL, ord('A'), wx.ID_SELECTALL)
]
accel = wx.AcceleratorTable(accel_entries)
self.SetAcceleratorTable(accel)
self.logDates = True
self.logTimes = True
self._datetime_fmt = " {0} %X ".format(eg.config.datestamp)
self.__inSelection = False
self.isOdd = False
self.data = collections.deque()
eg.log.SetCtrl(self)
wx.CallAfter(self.SetData, eg.log.GetData())
if eg.debugLevel:
@eg.LogIt
def __del__(self):
pass
def CanCut(self):
return False
def CanCopy(self):
return self.GetSelectedItemCount() > 0
def CanPaste(self):
return False
@eg.LogIt
def Destroy(self):
eg.log.SetCtrl(None)
def FocusLastItem(self):
if self.GetFocusedItem() == -1:
item = len(self.data) - 1
self.Focus(item)
self.SetItemState(item, 0, wx.LIST_STATE_SELECTED)
def GetItemData(self, item):
return self.data[item]
def OnCmdClearLog(self, dummyEvent=None):
self.SetItemCount(0)
self.DeleteAllItems()
self.data.clear()
eg.log.data.clear()
eg.Print(eg.text.MainFrame.Logger.welcomeText)
self.FocusLastItem()
self.Scroll()
self.Refresh()
def OnCmdCopy(self, dummyEvent=None):
text = ""
lines = 1
firstItem = item = self.GetNextItem(
-1,
wx.LIST_NEXT_ALL,
wx.LIST_STATE_SELECTED
)
if item != -1:
text = self.OnGetItemText(item, 0)[1:]
item = self.GetNextItem(
item,
wx.LIST_NEXT_ALL,
wx.LIST_STATE_SELECTED
)
while item != -1:
lines += 1
text += "\r\n" + self.OnGetItemText(item, 0)[1:]
item = self.GetNextItem(
item,
wx.LIST_NEXT_ALL,
wx.LIST_STATE_SELECTED
)
if text != "" and wx.TheClipboard.Open():
textDataObject = wx.TextDataObject(text)
dataObjectComposite = wx.DataObjectComposite()
dataObjectComposite.Add(textDataObject)
if lines == 1:
eventstring, icon = self.GetItemData(firstItem)[:2]
if icon == EVENT_ICON:
customDataObject = wx.CustomDataObject("DragEventItem")
customDataObject.SetData(eventstring.encode("UTF-8"))
dataObjectComposite.Add(customDataObject)
wx.TheClipboard.SetData(dataObjectComposite)
wx.TheClipboard.Close()
wx.TheClipboard.Flush()
def OnCmdReplay(self, dummyEvent=None):
item = self.GetFirstSelected()
while item != -1:
text, icon = self.GetItemData(item)[:2]
if icon == eg.Icons.EVENT_ICON:
parts = text.split(" ", 1)
e = parts[0]
prefix, suffix = e.split(".", 1) if "." in e else [e, ""]
payload = literal_eval(parts[1]) if len(parts) == 2 else None
eg.TriggerEvent(suffix, payload, prefix)
item = self.GetNextSelected(item)
def OnCmdSelectAll(self, dummyEvent=None):
for idx in range(self.GetItemCount()):
self.Select(idx)
def OnDoubleClick(self, event):
item, flags = self.HitTest(event.GetPosition())
if flags & wx.LIST_HITTEST_ONITEM:
icon, wref = self.GetItemData(item)[1:3]
if icon != eg.EventItem.icon and wref is not None:
node = wref()
if node is not None and not node.isDeleted:
node.Select()
def OnGetItemAttr(self, item):
colours = {
ERROR_ICON: self.errorColours,
DEBUG_ICON: self.debugColours,
WARNING_ICON: self.warningColours,
}
return colours.get(
self.data[item][1],
self.logColours
)[item % 2]
def OnGetItemImage(self, item):
return self.data[item][1].index
def OnGetItemText(self, item, column):
return ""
def OnGetItemTextNormal(self, item, dummyColumn):
line, _, _, _, indent = self.data[item]
return " " + indent * self.indent + line
def OnGetItemTextWithDT(self, item, dummyColumn):
line, _, _, when, indent = self.data[item]
return (
strftime(self._datetime_fmt, localtime(when)) +
indent * self.indent +
line
)
def OnKillFocus(self, event):
eg.Notify("FocusChange", None)
event.Skip()
def OnRightUp(self, dummyEvent):
self.PopupMenu(self.contextMenu)
def OnSetFocus(self, event):
self.FocusLastItem()
eg.Notify("FocusChange", self)
event.Skip()
def OnStartDrag(self, event):
idx = event.GetIndex()
itemData = self.GetItemData(idx)
if itemData[1] != EVENT_ICON:
return
text = itemData[2]
# create our own data format and use it in a
# custom data object
customData = wx.CustomDataObject(wx.CustomDataFormat("DragItem"))
customData.SetData(text.encode("utf-8"))
# And finally, create the drop source and begin the drag
# and drop operation
dropSource = wx.DropSource(self)
dropSource.SetData(customData)
result = dropSource.DoDragDrop(wx.Drag_AllowMove)
if result == wx.DragMove:
self.Refresh()
def Scroll(self):
if self.IsAutoscroll():
self.ScrollList(0, 1000000)
def IsAutoscroll(self):
val = self.GetTopItem() + self.GetCountPerPage() + 2
return len(self.data) <= val
@eg.AssertInMainThread
def SetData(self, data):
# self.Freeze()
self.data = collections.deque(data)
self.SetItemCount(len(data))
if eg.document.visibleLogItem:
self.EnsureVisible(eg.document.visibleLogItem)
else:
self.EnsureVisible(len(self.data) - 1)
# self.Thaw()
def SetIndent(self, shouldIndent):
if shouldIndent:
self.indent = " "
else:
self.indent = ""
self.Refresh()
def SetDateLogging(self, flag):
self.logDates = flag
self.SetDTLogging()
def SetTimeLogging(self, flag):
self.logTimes = flag
self.SetDTLogging()
def SetDTLogging(self):
if self.logDates and self.logTimes:
self._datetime_fmt = " {0} %X ".format(eg.config.datestamp)
elif self.logDates:
self._datetime_fmt = " {0} ".format(eg.config.datestamp)
elif self.logTimes:
self._datetime_fmt = " %X "
if self.logDates or self.logTimes:
self.OnGetItemText = self.OnGetItemTextWithDT
else:
self.OnGetItemText = self.OnGetItemTextNormal
self.Refresh()
@eg.AssertInMainThread
def WriteLine(self, line, icon, wRef, when, indent):
data = self.data
if len(data) >= self.maxlength:
self.Freeze()
for _ in range(self.removeOnMax):
self.DeleteItem(0)
data.popleft()
self.Thaw()
data.append((line, icon, wRef, when, indent))
self.SetItemCount(len(data))
self.Scroll()
self.Update()