RtmFrame.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # @file RtmFrame.py
5 # @brief rtc-link main frame class
6 # @date $Date: 2005-05-27 15:51:31 $
7 # @author Noriaki Ando <n-ando@aist.go.jp>
8 #
9 # Copyright (C) 2003-2005
10 # Task-intelligence Research Group,
11 # Intelligent Systems Research Institute,
12 # National Institute of
13 # Advanced Industrial Science and Technology (AIST), Japan
14 # All rights reserved.
15 #
16 # $Id$
17 #
18 
19 import sys, os, time
20 import wx # This module uses the new wx namespace
21 import wx.html
22 import rtimages
23 from wxPython.wx import *
24 #import ScrolledWindow
25 #import OGL
26 import RtmSystemDraw
27 import wx.ogl as ogl
28 import os
29 import cPickle
30 #from wxPython.wx import *
31 #import wx
32 
33 import threading
34 
35 import RtmAbout
36 
37 from RtmCompData import *
38 
39 import RTM
40 
41 #ID_NEW = 100
42 #ID_OPEN = 101
43 #ID_CONNECT = 102
44 #ID_EXIT = 103
45 #ID_COPY = 104
46 #ID_CUT = 105
47 #ID_PASTE = 106
48 #ID_HELP = 107
49 #ID_ABOUT = 108
50 
51 ID_WINDOW_LEFT = 5000
52 ID_WINDOW_CENTER_TOP = 5001
53 ID_WINDOW_CENTER_BOTTOM = 5002
54 ID_WINDOW_RIGHT = 5003
55 
57  def __init__(self, parent, menulist):
58  self.parent = parent
59  self.menu = wx.Menu()
60  for item_name, handler in menulist:
61  if item_name == "---":
62  self.menu.AppendSeparator()
63  else:
64  new_id = wx.NewId()
65  parent.Bind(wx.EVT_MENU, handler, id=new_id)
66  item = wx.MenuItem(self.menu, new_id, item_name)
67  self.menu.AppendItem(item)
68  def GetMenu(self):
69  return self.menu
70 
71 class RTComponentDropTarget(wx.DropTarget):
72  def __init__(self, window):
73  wx.DropTarget.__init__(self)
74  self.window = window
75 
76  self.df = wx.CustomDataFormat("RTComponent")
77 # print self.df, dir(self.df)
78  print "RTComponentDropTarget", self.df.GetId(), self.df.GetType()
79  self.data = wx.CustomDataObject(self.df)
80  self.SetDataObject(self.data)
81 
82  def OnDragOver(self, x, y, d):
83  print "OnDragOver"
84  self.window.AppendText("OnDragOver\n")
85  return wx.DragLink
86 
87  def OnDrop(self, x, y):
88  print "OnDrop"
89  print x, y
90  return True
91 
92 
93  def OnData(self, x, y, d):
94  print "OnData"
95  if self.GetData():
96  data = self.data.GetData()
97  cdata = cPickle.loads(data)
98  self.window.AppendText(cdata + "\n")
99 # self.window.AppendText(type(cdata[1]) + "\n")
100 
101 # return wx.DragNone
102  # url = self.data.GetURL()
103  return d
104 
105 
107  def __init__(self, parent):
108  menu_list = [("&New System", self.OnNew),
109  ("&Open System", self.OnOpen),
110  ("&Save System", self.OnSave),
111  ("Save System &as", self.OnSaveAs),
112  ("---", None),
113  ("&Import Component", self.OnImport),
114  ("&Export Component", self.OnExport),
115  ("---", None),
116  ("Print Pre&view", self.OnPrintPreview),
117  ("&Print System", self.OnPrint),
118  ("---", None),
119  ("&Connect Naming Server", self.OnConnect),
120  ("---", None),
121  ("E&xit", self.OnExit)]
122  MenuFactory.__init__(self, parent, menu_list)
123 
124  def OnNew(self, event):
125  self.parent.OnNewSystemClick(None)
126  print "OnNew"
127 
128  def OnOpen(self, event):
129  self.parent.drawWin[self.parent.drawCurNum].loadXML()
130  print "OnOpen"
131 
132  def OnSave(self, event):
133  filename = "System%d.xml" % self.parent.drawCurNum
134  self.parent.drawWin[self.parent.drawCurNum].saveXML(filename)
135  print "OnSave"
136 
137  def OnSaveAs(self, event):
138  self.parent.drawWin[self.parent.drawCurNum].saveAsXML()
139  print "OnSaveAs"
140 
141  def OnImport(self, event):
142  self.parent.OnFileOpen(event)
143  print "OnImport"
144 
145  def OnExport(self, event):
146  self.parent.OnFileOpen(event)
147  print "OnExport"
148 
149  def OnPrint(self, event):
150  dlg = wx.PrintDialog(self.parent, None)
151  if dlg.ShowModal() == wx.ID_OK:
152  data = dlg.GetPrintDialogData()
153  print "OnPrint"
154 
155  def OnPrintPreview(self, event):
156  self.parent.OnFileOpen(event)
157  print "OnOpen"
158 
159  def OnConnect(self, event):
160  self.parent.treepanel.OnConnectNSClick(None)
161  print "OnConnect"
162 
163  def OnExit(self, event):
164  print "OnExit"
165  self.parent.treepanel.threadloop = 0
166  self.parent.close_evt.wait()
167  self.parent.Close(true)
168 
169 
171  def __init__(self, parent):
172  menu_list = [("&Copy", self.OnCopy),
173  ("C&ut", self.OnCut),
174  ("&Paste", self.OnPaste)]
175  MenuFactory.__init__(self, parent, menu_list)
176 
177  def OnCopy(self, event):
178  print "OnCopy"
179 
180  def OnCut(self, event):
181  print "OnCut"
182 
183  def OnPaste(self, event):
184  print "OnPaste"
185 
187  def __init__(self, parent):
188  menu_list = [("Long Name &and Alias", self.OnAllDisp),
189  ("&Long Name", self.OnLongDisp),
190  ("&Alias", self.OnAliasDisp)]
191  MenuFactory.__init__(self, parent, menu_list)
192 
193  def OnAllDisp(self, event):
194  self.parent.kindDispMode = 'all'
195  print "OnAllDisp"
196 
197  def OnLongDisp(self, event):
198  self.parent.kindDispMode = 'long'
199  print "OnLongDisp"
200 
201  def OnAliasDisp(self, event):
202  self.parent.kindDispMode = 'alias'
203  print "OnAliasDisp"
204 
205 
207  def __init__(self, parent):
208  menu_list = [("&Help", self.OnHelp),
209  ("---", None),
210  ("&About", self.OnAbout)]
211  MenuFactory.__init__(self, parent, menu_list)
212 
213  def OnHelp(self, event):
214  print "OnHelp"
215 
216  def OnAbout(self, event):
217  print "OnAbout"
218  dlg = RtmAbout.RtdAboutBox(None)
219  dlg.ShowModal()
220  dlg.Destroy()
221 
222 
223 
224 
225 
226 class Log:
227  def WriteText(self, text):
228  if text[-1:] == '\n':
229  text = text[:-1]
230 # wxLogMessage(text)
231  write = WriteText
232 
233 #class RtdLog(wx.PyLog):
234 # def __init__(self, textCtrl, logTime=0):
235 # wx.PyLog.__init__(self)
236 # self.tc = textCtrl
237 # self.logTime = logTime
238 #
239 # def DoLogString(self, message, timeStamp):
240 # if self.logTime:
241 # message = time.strftime("%X", time.localtime(timeStamp)) + \
242 # ": " + message
243 # if self.tc:
244 # self.tc.AppendText(message + '\n')
245 
246 
247 
248 class RtdFrame(wxMDIParentFrame):
249  def __del__(self):
250 # for sys_no in self.drawWin.keys():
251 # del self.drawWin[sys_no]
252 
253  ogl.OGLCleanUp()
254 
255  def __init__(self, parent, ID, title):
256  wxMDIParentFrame.__init__(self, parent, -1, title, size = (800, 600),
257  style=wx.DEFAULT_FRAME_STYLE|wx.HSCROLL | wx.VSCROLL
258 
259  )
260  # wx.Frame.__init__(self, parent, ID, title,
261  # wxDefaultPosition, wxSize(800, 600))
262  self.winCount = 0
263  self.drawWin = {}
264  self.drawWin2 = {}
265  self.drawWinID = {}
266  self.drawCurNum = 0
267  self.cwd = os.getcwd()
268  self.curOverview = ""
269  self.window = None
270  self.log = Log()
271  ogl.OGLInitialize()
272  self.close_evt = threading.Event()
273  self.kindDispMode = 'all'
274  #------------------------------------------------------------
275  # Basic window frame settings
276  #------------------------------------------------------------
277  # Set window icon image
278  self.SetIcon(rtimages.getRT_iconIcon())
279 
280  # Status Bar
281  self.status_bar = self.CreateStatusBar()
282 
283  # Tool Bar
284  self.toolbar = self.CreateToolBar(wxTB_HORIZONTAL
285  | wxTB_DOCKABLE
286  | wx.NO_BORDER
287  | wx.TB_FLAT
288 # | wx.TB_TEXT
289  )
290 
291  self.exitdoorID = wx.NewId()
292  self.toolbar.AddSimpleTool(self.exitdoorID, rtimages.getExitDoorBitmap(),
293  "Exit", "Exit from rtc-link.")
294  self.Bind(wx.EVT_TOOL, self.TimeToQuit, id=self.exitdoorID)
295 
296  self.toolbar.AddSeparator()
297 
298  self.connectID = wx.NewId()
299  self.toolbar.AddSimpleTool(self.connectID,
301  "Connect",
302  "Connect to a Naming Server.")
303 # self.toolbar.EnableTool(self.connectID, False)
304 
305  self.alldispID = wx.NewId()
306  self.toolbar.AddSimpleTool(self.alldispID, rtimages.getMixNameBitmap(),
307  "Long Name and Alias", "Display long name and alias on the naming tree window.")
308  self.Bind(wx.EVT_TOOL, self.OnAllDisp, id=self.alldispID)
309 
310 
311  self.longdispID = wx.NewId()
312  self.toolbar.AddSimpleTool(self.longdispID, rtimages.getLongNameBitmap(),
313  "Long Name", "Display only long name on the naming tree window.")
314  self.Bind(wx.EVT_TOOL, self.OnLongnameDisp, id=self.longdispID)
315 
316  self.aliasdispID = wx.NewId()
317  self.toolbar.AddSimpleTool(self.aliasdispID, rtimages.getShortNameBitmap(),
318  "Alias", "Display only alias on the naming tree window.")
319  self.Bind(wx.EVT_TOOL, self.OnAliasDisp, id=self.aliasdispID)
320  # self.Bind(wx.EVT_TOOL, self.OnToolClick, id=40)
321  # self.Bind(wx.EVT_TOOL_RCLICKED, self.OnToolRClick, id=40)
322 
323  self.toolbar.AddSeparator()
324 
325  self.newsysID = wx.NewId()
326  self.toolbar.AddSimpleTool(self.newsysID,
328  "New",
329  "Open.: Open a new system draw window.")
330  self.Bind(wx.EVT_TOOL, self.OnNewSystemClick, id=self.newsysID)
331 
332 # cbID = wx.NewId()
333 # self.toolbar.AddControl(
334 # wx.ComboBox(
335 # self.toolbar, cbID, "", choices=["", "This", "is a", "wxComboBox"],
336 # size=(150,-1), style=wx.CB_DROPDOWN
337 # ))
338  self.toolbar.Realize()
339 
340  # Menu bar
341  self.SetMenuBar(self.CreateMenu())
342 
343 
344  #------------------------------------------------------------
345  # Menu event binding
346  #------------------------------------------------------------
347  # EVT_MENU(self, ID_ABOUT, self.OnAbout)
348  # EVT_MENU(self, ID_EXIT, self.TimeToQuit)
349  # EVT_MENU(self, ID_OPEN, self.OnFileOpen)
350 
351  #------------------------------------------------------------
352  # Dictionary
353  #------------------------------------------------------------
354  self.myDict = RtmCompData(self)
355 
356  #------------------------------------------------------------
357  # Naming Tree Window
358  #------------------------------------------------------------
359  self.nametreeW_ID = wxNewId()
360  win = wxSashLayoutWindow(self, self.nametreeW_ID,
361  style=wx.NO_BORDER|wx.SW_3D)
362  win.SetDefaultSize((200, 600))
363  win.SetOrientation(wxLAYOUT_VERTICAL)
364  win.SetAlignment(wxLAYOUT_LEFT)
365  win.SetBackgroundColour(wxColour(200, 200, 200))
366  win.SetSashVisible(wxSASH_RIGHT, True)
367 
368  # Tree
369  tID = wxNewId()
370  self.treeMap = {}
371 
372  import RtmTreeCtrl
373  log = Log()
374 # self.treepanel = RtmTreeCtrl.RtmTreeCtrlPanel(win, log)
376  self.Bind(wx.EVT_TOOL,
377  self.treepanel.OnConnectNSClick,
378  id=self.connectID)
379  self.treepanelW = win
380 
381  #------------------------------------------------------------
382  # Profile Window
383  #------------------------------------------------------------
384  self.profileW_ID = wxNewId()
385  win = wxSashLayoutWindow(self, self.profileW_ID,
386  style=wx.NO_BORDER|wx.SW_3D)
387  win.SetDefaultSize((200, 600))
388  win.SetOrientation(wxLAYOUT_VERTICAL)
389  win.SetAlignment(wxLAYOUT_RIGHT)
390  win.SetBackgroundColour(wxColour(200, 200, 200))
391  win.SetSashVisible(wxSASH_LEFT, True)
392 
393  import RtmProfileList
395  self.profilepanelW = win
396 
397  #------------------------------------------------------------
398  # Log Window
399  #------------------------------------------------------------
400  self.logW_ID = wxNewId()
401  win = wxSashLayoutWindow(self, self.logW_ID,
402  style=wx.NO_BORDER|wx.SW_3D)
403  win.SetDefaultSize((400, 100))
404  win.SetOrientation(wx.LAYOUT_HORIZONTAL)
405  win.SetAlignment(wx.LAYOUT_BOTTOM)
406  win.SetBackgroundColour(wx.Colour(200, 200, 200))
407  win.SetSashVisible(wx.SASH_TOP, True)
408 
409  self.textWindow = wx.TextCtrl(win, -1, "",
410  style=wx.TE_MULTILINE|wx.SUNKEN_BORDER)
411  self.textWindow.SetValue("A sub window")
412 
413  self.logW = win
414  self.textWindow.SetDropTarget(RTComponentDropTarget(self.textWindow))
415 
417  self.GetClientWindow().Bind(
418  wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground
419  )
420 
421  self.Bind(wx.EVT_CLOSE, self.OnClose)
422  self.Bind(
423  wx.EVT_SASH_DRAGGED_RANGE, self.OnSashDrag,
424  id=self.nametreeW_ID,
425  id2=self.logW_ID
426  )
427  self.Bind(wx.EVT_SIZE, self.OnSize)
428 
429 
430  def OnAllDisp(self, event):
431  self.kindDispMode = 'all'
432 
433  def OnLongnameDisp(self, event):
434  self.kindDispMode = 'long'
435 
436  def OnAliasDisp(self, event):
437  self.kindDispMode = 'alias'
438 
439  def OnNewSystemClick(self, event):
440  self.winCount = self.winCount + 1
441  new_id = wx.NewId()
442  win = wxMDIChildFrame(self, new_id , "System: %d" % self.winCount)
443  # canvas = ScrolledWindow.MyCanvas(win)
444  self.drawWin[self.winCount] = RtmSystemDraw.RtdSystemDraw(win, self.log, self)
445  self.drawWin2[self.winCount] = win
446  win.Show(True)
447  win.SetIcon(rtimages.getRTIcon())
448  self.drawWinID[self.winCount] = self.drawWin[self.winCount].GetId()
449  self.drawCurNum = self.winCount
450 
451  os_check = sys.platform
452  if os_check == 'win32':
453  self.drawWin[self.winCount].Bind(wx.EVT_SET_FOCUS, self.OnChangeDraw)
454  else:
455  self.drawWin[self.winCount].Bind(wx.EVT_ENTER_WINDOW, self.OnChangeDraw)
456  self.drawWin2[self.winCount].Bind(wx.EVT_CLOSE, self.OnChildClose)
457 
458  def OnChildClose(self, event):
459  curID = event.GetId()
460  for n in self.drawWinID.keys():
461  if self.drawWinID[n] == curID:
462  self.drawCurNum = n
463  break
464  del self.drawWin[self.drawCurNum]
465  del self.drawWinID[self.drawCurNum]
466  event.Skip()
467 
468  def OnChangeDraw(self, event):
469  curID = event.GetId()
470  for n in self.drawWinID.keys():
471  if self.drawWinID[n] == curID:
472  self.drawCurNum = n
473  break
474 
475  def OnSize(self, event):
476  wxLayoutAlgorithm().LayoutMDIFrame(self)
477 
478 
479  def OnSashDrag(self, event):
480  if event.GetDragStatus() == wxSASH_STATUS_OUT_OF_RANGE:
481  return
482 
483  eID = event.GetId()
484  if eID == self.nametreeW_ID:
485  self.treepanelW.SetDefaultSize((event.GetDragRect().width, 200))
486 
487  elif eID == self.profileW_ID:
488  self.profilepanelW.SetDefaultSize((event.GetDragRect().width, 200))
489 
490  elif eID == self.logW_ID:
491  print "logW_ID", self.logW_ID
492  self.logW.SetDefaultSize((1000, event.GetDragRect().height))
493 
494  wx.LayoutAlgorithm().LayoutMDIFrame(self)
495  self.GetClientWindow().Refresh()
496 
497 
498  def OnClose(self, event):
499  if hasattr(self, "treepanel"):
500  print 'OnClose:'
501  self.treepanel.threadloop = 0
502  self.close_evt.wait()
503  event.Skip()
504 
505  def TimeToQuit(self, event):
506  self.Close(true)
507 
508 
509  def CreateMenu(self):
510  menu_file = RtmFileMenu(self).GetMenu()
511  menu_edit = RtmEditMenu(self).GetMenu()
512  menu_disp = RtmDisplayMenu(self).GetMenu()
513  menu_help = RtmHelpMenu(self).GetMenu()
514 
515  menuBar = wxMenuBar()
516  menuBar.Append(menu_file, "&File");
517  menuBar.Append(menu_edit, "&Edit");
518  menuBar.Append(menu_disp, "&Display");
519  menuBar.Append(menu_help, "&Help");
520 
521  # Disable to menu item
522  id = menuBar.FindMenuItem("Edit","Copy")
523  menuBar.FindItemById(id).Enable(false)
524  id = menuBar.FindMenuItem("Edit","Cut")
525  menuBar.FindItemById(id).Enable(false)
526  id = menuBar.FindMenuItem("Edit","Paste")
527  menuBar.FindItemById(id).Enable(false)
528 
529 # assembly enable
530 # id = menuBar.FindMenuItem("File","Open System")
531 # menuBar.FindItemById(id).Enable(false)
532 # id = menuBar.FindMenuItem("File","Save System")
533 # menuBar.FindItemById(id).Enable(false)
534 # id = menuBar.FindMenuItem("File","Save System as")
535 # menuBar.FindItemById(id).Enable(false)
536 #
537  id = menuBar.FindMenuItem("File","Import Component")
538  menuBar.FindItemById(id).Enable(false)
539  id = menuBar.FindMenuItem("File","Export Component")
540  menuBar.FindItemById(id).Enable(false)
541 
542  id = menuBar.FindMenuItem("File","Print Preview")
543  menuBar.FindItemById(id).Enable(false)
544  id = menuBar.FindMenuItem("File","Print System")
545  menuBar.FindItemById(id).Enable(false)
546 
547  return menuBar
548 
549  def OnFileOpen(self, event):
550  dlg = wx.FileDialog(
551  self, message="Choose a file", defaultDir=os.getcwd(),
552  defaultFile="", wildcard="*.*", style=wx.OPEN | wx.MULTIPLE | wx.CHANGE_DIR
553  )
554 
555  if dlg.ShowModal() == wx.ID_OK:
556  # This returns a Python list of files that were selected.
557  self.paths = dlg.GetPaths()
558 
559  # for path in paths:
560  # log.WriteText(' %s\n' % path)
561 
562  # Compare this with the debug above; did we change working dirs?
563 
564  # Destroy the dialog. Don't do this until you are done with it!
565  # BAD things can happen otherwise!
566  dlg.Destroy()
567 
568 
569 
570  def OnEraseBackground(self, evt):
571  dc = evt.GetDC()
572 
573  if not dc:
574  dc = wx.ClientDC(self.GetClientWindow())
575 
576  # tile the background bitmap
577  sz = self.GetClientSize()
578  w = self.bg_bmp.GetWidth()
579  h = self.bg_bmp.GetHeight()
580  x = 0
581 
582  while x < sz.width:
583  y = 0
584 
585  while y < sz.height:
586  dc.DrawBitmap(self.bg_bmp, (x, y))
587  y = y + h
588 
589  x = x + w
def TimeToQuit(self, event)
Definition: RtmFrame.py:505
def OnHelp(self, event)
Definition: RtmFrame.py:213
def OnAllDisp(self, event)
Definition: RtmFrame.py:193
def CreateMenu(self)
Definition: RtmFrame.py:509
def getShortNameBitmap()
Definition: rtimages.py:844
def OnCopy(self, event)
Definition: RtmFrame.py:177
def OnDragOver(self, x, y, d)
Definition: RtmFrame.py:82
def OnAliasDisp(self, event)
Definition: RtmFrame.py:436
def WriteText(self, text)
Definition: RtmFrame.py:227
def OnNew(self, event)
Definition: RtmFrame.py:124
def OnEraseBackground(self, evt)
Definition: RtmFrame.py:570
def getGridBGBitmap()
Definition: rtimages.py:349
def OnLongnameDisp(self, event)
Definition: RtmFrame.py:433
def OnExport(self, event)
Definition: RtmFrame.py:145
def getRTIcon()
Definition: rtimages.py:741
def __init__(self, window)
Definition: RtmFrame.py:72
def OnSashDrag(self, event)
Definition: RtmFrame.py:479
def OnOpen(self, event)
Definition: RtmFrame.py:128
def OnChildClose(self, event)
Definition: RtmFrame.py:458
def getRT_iconIcon()
Definition: rtimages.py:818
def GetMenu(self)
Definition: RtmFrame.py:68
def OnAbout(self, event)
Definition: RtmFrame.py:216
def OnClose(self, event)
Definition: RtmFrame.py:498
def OnChangeDraw(self, event)
Definition: RtmFrame.py:468
def OnSaveAs(self, event)
Definition: RtmFrame.py:137
def OnNewSystemClick(self, event)
Definition: RtmFrame.py:439
def OnAliasDisp(self, event)
Definition: RtmFrame.py:201
def __init__(self, parent)
Definition: RtmFrame.py:187
def OnAllDisp(self, event)
Definition: RtmFrame.py:430
def OnSize(self, event)
Definition: RtmFrame.py:475
def __init__(self, parent)
Definition: RtmFrame.py:207
def OnPrint(self, event)
Definition: RtmFrame.py:149
def __init__(self, parent)
Definition: RtmFrame.py:171
def __init__(self, parent)
Definition: RtmFrame.py:107
winCount
wx.NO_FULL_REPAINT_ON_RESIZE
Definition: RtmFrame.py:262
def OnDrop(self, x, y)
Definition: RtmFrame.py:87
def getLongNameBitmap()
Definition: rtimages.py:435
def __del__(self)
Definition: RtmFrame.py:249
def OnData(self, x, y, d)
Definition: RtmFrame.py:93
def OnPrintPreview(self, event)
Definition: RtmFrame.py:155
def getMixNameBitmap()
Definition: rtimages.py:519
def OnCut(self, event)
Definition: RtmFrame.py:180
def OnConnect(self, event)
Definition: RtmFrame.py:159
def OnPaste(self, event)
Definition: RtmFrame.py:183
def getNewRTSystemBitmap()
Definition: rtimages.py:649
def OnLongDisp(self, event)
Definition: RtmFrame.py:197
def OnExit(self, event)
Definition: RtmFrame.py:163
def getConnect2Bitmap()
Definition: rtimages.py:258
def OnSave(self, event)
Definition: RtmFrame.py:132
def __init__(self, parent, menulist)
Definition: RtmFrame.py:57
def OnImport(self, event)
Definition: RtmFrame.py:141
def getExitDoorBitmap()
Definition: rtimages.py:307
def __init__(self, parent, ID, title)
Definition: RtmFrame.py:255
def OnFileOpen(self, event)
Definition: RtmFrame.py:549


openrtm_aist
Author(s): Noriaki Ando
autogenerated on Mon Feb 28 2022 23:00:44