1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
9
|
|
10
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
|
17
|
|
18
|
|
19
|
|
20
|
|
21
|
|
22
|
|
23
|
|
24
|
|
25
|
|
26
|
|
27
|
|
28
|
|
29
|
"""This file contains the ClientWindow class."""
|
30
|
|
31
|
import wx
|
32
|
|
33
|
from ytranslate.tools import t
|
34
|
|
35
|
from scripting.key import key_name
|
36
|
from ui.dialogs.connection import ConnectionDialog
|
37
|
from ui.dialogs.macro import MacroDialog
|
38
|
from ui.dialogs.preferences import PreferencesDialog
|
39
|
from ui.event import EVT_FOCUS, FocusEvent, myEVT_FOCUS
|
40
|
|
41
|
class ClientWindow(wx.Frame):
|
42
|
|
43
|
def __init__(self, engine, world=None):
|
44
|
super(ClientWindow, self).__init__(None)
|
45
|
self.engine = engine
|
46
|
self.focus = True
|
47
|
self.CreateMenuBar()
|
48
|
self.InitUI(world)
|
49
|
|
50
|
@property
|
51
|
def world(self):
|
52
|
return self.panel and self.panel.world or None
|
53
|
|
54
|
def _get_client(self):
|
55
|
return self.panel.client
|
56
|
def _set_client(self, client):
|
57
|
self.panel.client = client
|
58
|
client = property(_get_client, _set_client)
|
59
|
|
60
|
def CreateMenuBar(self):
|
61
|
"""Create the GUI menu bar and hierarchy of menus."""
|
62
|
menubar = wx.MenuBar()
|
63
|
|
64
|
|
65
|
fileMenu = wx.Menu()
|
66
|
gameMenu = wx.Menu()
|
67
|
|
68
|
|
69
|
|
70
|
preferences = wx.MenuItem(fileMenu, -1, t("ui.menu.preferences"))
|
71
|
self.Bind(wx.EVT_MENU, self.OnPreferences, preferences)
|
72
|
fileMenu.AppendItem(preferences)
|
73
|
|
74
|
|
75
|
quit = wx.MenuItem(fileMenu, -1, t("ui.menu.quit"))
|
76
|
self.Bind(wx.EVT_MENU, self.OnQuit, quit)
|
77
|
fileMenu.AppendItem(quit)
|
78
|
|
79
|
|
80
|
macro = wx.MenuItem(fileMenu, -1, t("ui.menu.macro"))
|
81
|
self.Bind(wx.EVT_MENU, self.OnMacro, macro)
|
82
|
gameMenu.AppendItem(macro)
|
83
|
|
84
|
menubar.Append(fileMenu, t("ui.menu.file"))
|
85
|
menubar.Append(gameMenu, t("ui.menu.game"))
|
86
|
|
87
|
self.SetMenuBar(menubar)
|
88
|
|
89
|
def InitUI(self, world=None):
|
90
|
if world is None:
|
91
|
dialog = ConnectionDialog(self.engine)
|
92
|
dialog.ShowModal()
|
93
|
world = self.engine.default_world
|
94
|
|
95
|
self.panel = MUDPanel(self, self.engine, world)
|
96
|
self.SetTitle("{} [CocoMUD]".format(world.name))
|
97
|
self.Maximize()
|
98
|
self.Show()
|
99
|
self.Bind(wx.EVT_CLOSE, self.OnClose)
|
100
|
self.Bind(wx.EVT_ACTIVATE, self.OnActivate)
|
101
|
|
102
|
def OnPreferences(self, e):
|
103
|
"""Open the preferences dialog box."""
|
104
|
dialog = PreferencesDialog(self.engine)
|
105
|
dialog.ShowModal()
|
106
|
dialog.Destroy()
|
107
|
|
108
|
def OnMacro(self, e):
|
109
|
"""Open the macro dialog box."""
|
110
|
dialog = MacroDialog(self.engine, self.world)
|
111
|
dialog.ShowModal()
|
112
|
dialog.Destroy()
|
113
|
|
114
|
def OnQuit(self, e):
|
115
|
self.OnClose(e)
|
116
|
|
117
|
def OnClose(self, e):
|
118
|
if self.panel.client:
|
119
|
self.panel.client.running = False
|
120
|
self.Destroy()
|
121
|
|
122
|
def OnActivate(self, e):
|
123
|
"""The window gains focus."""
|
124
|
self.focus = e.GetActive()
|
125
|
e.Skip()
|
126
|
|
127
|
|
128
|
def handle_message(self, message):
|
129
|
"""The client has just received a message."""
|
130
|
pos = self.panel.output.GetInsertionPoint()
|
131
|
lines = message.splitlines()
|
132
|
lines = [line for line in lines if line]
|
133
|
message = "\n".join(lines)
|
134
|
output = self.panel.output.GetValue()
|
135
|
if output and not output.endswith("\n"):
|
136
|
message = "\n" + message
|
137
|
|
138
|
if self.engine.settings["options.accessibility.nl_end"]:
|
139
|
message += "\n"
|
140
|
|
141
|
self.panel.output.write(message)
|
142
|
self.panel.output.SetInsertionPoint(pos)
|
143
|
|
144
|
def handle_option(self, command):
|
145
|
"""Handle the specified option.
|
146
|
|
147
|
The command is a string representing the received option.
|
148
|
The following options are supported:
|
149
|
"hide": the input should be hidden
|
150
|
"show": the input should be shown
|
151
|
|
152
|
"""
|
153
|
if command == "hide":
|
154
|
evt = FocusEvent(myEVT_FOCUS, -1, "password")
|
155
|
wx.PostEvent(self.panel, evt)
|
156
|
elif command == "show":
|
157
|
evt = FocusEvent(myEVT_FOCUS, -1, "input")
|
158
|
wx.PostEvent(self.panel, evt)
|
159
|
|
160
|
class MUDPanel(wx.Panel):
|
161
|
|
162
|
def __init__(self, parent, engine, world):
|
163
|
wx.Panel.__init__(self, parent)
|
164
|
self.engine = engine
|
165
|
self.client = None
|
166
|
self.world = world
|
167
|
self.index = -1
|
168
|
self.history = []
|
169
|
self.focused = True
|
170
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
171
|
self.SetSizer(sizer)
|
172
|
|
173
|
|
174
|
s_input = wx.BoxSizer(wx.HORIZONTAL)
|
175
|
l_input = wx.StaticText(self, -1, t("ui.client.input"))
|
176
|
t_input = wx.TextCtrl(self, -1, "", size=(125, -1),
|
177
|
style=wx.TE_PROCESS_ENTER)
|
178
|
self.input = t_input
|
179
|
|
180
|
|
181
|
l_password = wx.StaticText(self, -1, t("ui.client.password"))
|
182
|
t_password = wx.TextCtrl(self, -1, "", size=(20, -1),
|
183
|
style=wx.TE_PROCESS_ENTER | wx.TE_PASSWORD)
|
184
|
self.password = t_password
|
185
|
t_password.Hide()
|
186
|
|
187
|
|
188
|
s_input.Add(l_input)
|
189
|
s_input.Add(t_input, proportion=4)
|
190
|
s_input.Add(l_password)
|
191
|
s_input.Add(t_password, proportion=2)
|
192
|
|
193
|
|
194
|
l_output = wx.StaticText(self, -1, t("ui.client.output"))
|
195
|
t_output = wx.TextCtrl(self, -1, "",
|
196
|
size=(600, 400), style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_PROCESS_TAB)
|
197
|
self.output = t_output
|
198
|
|
199
|
|
200
|
sizer.Add(s_input)
|
201
|
sizer.Add(t_output, proportion=8)
|
202
|
|
203
|
|
204
|
t_input.Bind(wx.EVT_TEXT_ENTER, self.EvtText)
|
205
|
t_password.Bind(wx.EVT_TEXT_ENTER, self.EvtText)
|
206
|
self.Bind(EVT_FOCUS, self.OnFocus)
|
207
|
t_input.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
|
208
|
t_input.Bind(wx.EVT_SET_FOCUS, self.OnInputFocused)
|
209
|
t_password.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
|
210
|
t_output.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
|
211
|
|
212
|
def EvtText(self, event):
|
213
|
"""One of the input fields is sending text."""
|
214
|
self.input.Clear()
|
215
|
self.password.Clear()
|
216
|
encoding = self.engine.settings["options.general.encoding"]
|
217
|
msg = event.GetString().encode(encoding, "replace")
|
218
|
self.client.write(msg)
|
219
|
|
220
|
|
221
|
if event.GetEventObject() == self.input:
|
222
|
if self.index == -1 and msg:
|
223
|
self.history.append(msg)
|
224
|
|
225
|
def OnFocus(self, evt):
|
226
|
"""The GUIClient requires a change of focus.
|
227
|
|
228
|
This event is triggered when the GUIClient asks a change of
|
229
|
focus in the input field (hiding the password field) or in
|
230
|
the password field (hiding the input field).
|
231
|
"""
|
232
|
val = evt.GetValue()
|
233
|
if val == "input":
|
234
|
self.input.Show()
|
235
|
self.input.SetFocus()
|
236
|
self.password.Hide()
|
237
|
elif val == "password":
|
238
|
self.password.Show()
|
239
|
self.password.SetFocus()
|
240
|
self.input.Hide()
|
241
|
|
242
|
def OnInputFocused(self, e):
|
243
|
"""Input gains the focus."""
|
244
|
message = self.input.GetValue()
|
245
|
if message:
|
246
|
self.input.SetInsertionPoint(len(message) + 1)
|
247
|
e.Skip()
|
248
|
|
249
|
def OnKeyDown(self, e):
|
250
|
"""A key is pressed in the window."""
|
251
|
skip = True
|
252
|
modifiers = e.GetModifiers()
|
253
|
key = e.GetUnicodeKey()
|
254
|
if not key:
|
255
|
key = e.GetKeyCode()
|
256
|
|
257
|
if e.GetEventObject() == self.input:
|
258
|
skip = self.HandleHistory(modifiers, key)
|
259
|
|
260
|
|
261
|
if self.world:
|
262
|
for macro in self.world.macros:
|
263
|
code = (macro.key, macro.modifiers)
|
264
|
if code == (key, modifiers):
|
265
|
macro.execute(self.engine, self.client)
|
266
|
|
267
|
if e.GetEventObject() is self.output:
|
268
|
shortcut = key_name(key, modifiers)
|
269
|
if shortcut:
|
270
|
if shortcut == "Backspace" or len(shortcut) == 1 or (
|
271
|
shortcut.startswith("Shift +") and len(shortcut) == 9):
|
272
|
self.input.EmulateKeyPress(e)
|
273
|
elif e.GetEventObject() == self.input:
|
274
|
if key == wx.WXK_TAB:
|
275
|
if self.engine.settings["options.accessibility.tab_end"]:
|
276
|
message = self.output.GetValue()
|
277
|
self.output.SetInsertionPoint(-1)
|
278
|
|
279
|
if skip:
|
280
|
e.Skip()
|
281
|
|
282
|
def HandleHistory(self, modifiers, key):
|
283
|
"""Handle the history commands."""
|
284
|
if modifiers == 0:
|
285
|
if key == wx.WXK_UP:
|
286
|
self.HistoryGoUp()
|
287
|
return False
|
288
|
elif key == wx.WXK_DOWN:
|
289
|
self.HistoryGoDown()
|
290
|
return False
|
291
|
|
292
|
return True
|
293
|
|
294
|
def HistoryGoUp(self):
|
295
|
"""Go up in the history."""
|
296
|
if self.index == -1:
|
297
|
self.index = len(self.history) - 1
|
298
|
elif self.index > 0:
|
299
|
self.index -= 1
|
300
|
else:
|
301
|
return
|
302
|
|
303
|
message = self.history[self.index]
|
304
|
encoding = self.engine.settings["options.general.encoding"]
|
305
|
message = message.decode(encoding, "replace")
|
306
|
self.input.Clear()
|
307
|
self.input.SetValue(message)
|
308
|
self.input.SetInsertionPoint(-1)
|
309
|
|
310
|
def HistoryGoDown(self):
|
311
|
"""Go down in the history."""
|
312
|
if self.index == -1:
|
313
|
return
|
314
|
elif self.index >= len(self.history) - 1:
|
315
|
self.index = -1
|
316
|
message = ""
|
317
|
else:
|
318
|
self.index += 1
|
319
|
message = self.history[self.index]
|
320
|
|
321
|
encoding = self.engine.settings["options.general.encoding"]
|
322
|
message = message.decode(encoding, "replace")
|
323
|
self.input.Clear()
|
324
|
self.input.SetValue(message)
|
325
|
self.input.SetInsertionPoint(-1)
|