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
|
|
30
|
"""This file contains the ClientWindow class."""
|
31
|
|
32
|
from __future__ import absolute_import
|
33
|
import os
|
34
|
import re
|
35
|
import sys
|
36
|
from threading import RLock
|
37
|
from zipfile import ZipFile
|
38
|
|
39
|
from accesspanel import AccessPanel
|
40
|
import wx
|
41
|
from wx.lib.pubsub import pub
|
42
|
from ytranslate.tools import t
|
43
|
|
44
|
from autoupdate import AutoUpdate
|
45
|
from log import logger
|
46
|
from screenreader import ScreenReader
|
47
|
from scripting.key import key_name
|
48
|
from session import Session
|
49
|
from task.import_worlds import ImportWorlds
|
50
|
from ui.dialogs.alias import AliasDialog
|
51
|
from ui.dialogs.channel import ChannelsDialog
|
52
|
from ui.dialogs.character import CharacterDialog
|
53
|
from ui.dialogs.connection import ConnectionDialog, EditWorldDialog
|
54
|
from ui.dialogs.console import ConsoleDialog
|
55
|
from ui.dialogs.loading import LoadingDialog
|
56
|
from ui.dialogs.macro import MacroDialog
|
57
|
from ui.dialogs.notepad import NotepadDialog
|
58
|
from ui.dialogs.preferences import PreferencesDialog
|
59
|
from ui.dialogs.trigger import TriggerDialog
|
60
|
from ui.dialogs.worlds import WorldsDialog, ExportWorldDialog
|
61
|
from ui.event import EVT_FOCUS, FocusEvent, myEVT_FOCUS
|
62
|
from wizard.install_world import InstallWorld
|
63
|
from world import World
|
64
|
from updater import *
|
65
|
from version import BUILD
|
66
|
|
67
|
|
68
|
LAST_WORD = re.compile(r"^.*?(\w+)$", re.UNICODE | re.DOTALL)
|
69
|
|
70
|
class ClientWindow(DummyUpdater):
|
71
|
|
72
|
def __init__(self, engine, world=None):
|
73
|
super(ClientWindow, self).__init__(None)
|
74
|
self.lock = RLock()
|
75
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
76
|
self.sizer = sizer
|
77
|
self.main_panel = wx.Panel(self)
|
78
|
self.tabs = wx.Notebook(self.main_panel)
|
79
|
sizer.Add(self.tabs, 1, wx.EXPAND)
|
80
|
self.main_panel.SetSizer(sizer)
|
81
|
self.engine = engine
|
82
|
self.focus = True
|
83
|
self.interrupt = False
|
84
|
self.loading = None
|
85
|
self.connection = None
|
86
|
self.CreateMenuBar()
|
87
|
self.InitUI(world)
|
88
|
|
89
|
@property
|
90
|
def world(self):
|
91
|
return self.panel and self.panel.world or None
|
92
|
|
93
|
@property
|
94
|
def panel(self):
|
95
|
"""Return the currently selected tab (a MUDPanel0."""
|
96
|
return self.tabs.GetCurrentPage()
|
97
|
|
98
|
def _get_client(self):
|
99
|
return self.panel.client
|
100
|
def _set_client(self, client):
|
101
|
self.panel.client = client
|
102
|
client = property(_get_client, _set_client)
|
103
|
|
104
|
def CloseAll(self):
|
105
|
"""Close ALL windows (counting dialogs)."""
|
106
|
windows = wx.GetTopLevelWindows()
|
107
|
for window in windows:
|
108
|
|
109
|
if isinstance(window, LoadingDialog):
|
110
|
window.Destroy()
|
111
|
else:
|
112
|
window.Close()
|
113
|
|
114
|
def CreateMenuBar(self):
|
115
|
"""Create the GUI menu bar and hierarchy of menus."""
|
116
|
menubar = wx.MenuBar()
|
117
|
|
118
|
|
119
|
fileMenu = wx.Menu()
|
120
|
gameMenu = wx.Menu()
|
121
|
connectionMenu = wx.Menu()
|
122
|
toolsMenu = wx.Menu()
|
123
|
helpMenu = wx.Menu()
|
124
|
|
125
|
|
126
|
|
127
|
create = wx.MenuItem(fileMenu, -1, t("ui.menu.create"))
|
128
|
self.Bind(wx.EVT_MENU, self.OnCreate, create)
|
129
|
fileMenu.AppendItem(create)
|
130
|
|
131
|
|
132
|
open = wx.MenuItem(fileMenu, -1, t("ui.menu.open"))
|
133
|
self.Bind(wx.EVT_MENU, self.OnOpen, open)
|
134
|
fileMenu.AppendItem(open)
|
135
|
|
136
|
|
137
|
close_tab = wx.MenuItem(fileMenu, -1, t("ui.menu.close_tab"))
|
138
|
self.Bind(wx.EVT_MENU, self.OnCloseTab, close_tab)
|
139
|
fileMenu.AppendItem(close_tab)
|
140
|
|
141
|
|
142
|
import_world = wx.Menu()
|
143
|
import_ondisk = import_world.Append(wx.ID_ANY,
|
144
|
t("ui.menu.import_on_disk"))
|
145
|
import_online = import_world.Append(wx.ID_ANY,
|
146
|
t("ui.menu.import_online"))
|
147
|
wx.MenuItem(fileMenu, -1, t("ui.menu.import"))
|
148
|
self.Bind(wx.EVT_MENU, self.OnImportOndisk, import_ondisk)
|
149
|
self.Bind(wx.EVT_MENU, self.OnImportOnline, import_online)
|
150
|
fileMenu.AppendMenu(wx.ID_ANY, t("ui.menu.import"), import_world)
|
151
|
|
152
|
|
153
|
export = wx.MenuItem(fileMenu, -1, t("ui.menu.export"))
|
154
|
self.Bind(wx.EVT_MENU, self.OnExportWorld, export)
|
155
|
fileMenu.AppendItem(export)
|
156
|
|
157
|
|
158
|
preferences = wx.MenuItem(fileMenu, -1, t("ui.menu.preferences"))
|
159
|
self.Bind(wx.EVT_MENU, self.OnPreferences, preferences)
|
160
|
fileMenu.AppendItem(preferences)
|
161
|
|
162
|
|
163
|
quit = wx.MenuItem(fileMenu, -1, t("ui.menu.quit"))
|
164
|
self.Bind(wx.EVT_MENU, self.OnQuit, quit)
|
165
|
fileMenu.AppendItem(quit)
|
166
|
|
167
|
|
168
|
|
169
|
alias = wx.MenuItem(gameMenu, -1, t("ui.menu.aliases"))
|
170
|
self.Bind(wx.EVT_MENU, self.OnAlias, alias)
|
171
|
gameMenu.AppendItem(alias)
|
172
|
|
173
|
|
174
|
macro = wx.MenuItem(gameMenu, -1, t("ui.menu.macro"))
|
175
|
self.Bind(wx.EVT_MENU, self.OnMacro, macro)
|
176
|
gameMenu.AppendItem(macro)
|
177
|
|
178
|
|
179
|
triggers = wx.MenuItem(gameMenu, -1, t("ui.menu.triggers"))
|
180
|
self.Bind(wx.EVT_MENU, self.OnTriggers, triggers)
|
181
|
gameMenu.AppendItem(triggers)
|
182
|
|
183
|
|
184
|
channels = wx.MenuItem(gameMenu, -1, t("ui.menu.channels"))
|
185
|
self.Bind(wx.EVT_MENU, self.OnChannels, channels)
|
186
|
gameMenu.AppendItem(channels)
|
187
|
|
188
|
|
189
|
notepad = wx.Menu()
|
190
|
notepad_world = notepad.Append(wx.ID_ANY,
|
191
|
t("ui.menu.notepad_world"))
|
192
|
notepad_character = notepad.Append(wx.ID_ANY,
|
193
|
t("ui.menu.notepad_character"))
|
194
|
wx.MenuItem(gameMenu, -1, t("ui.menu.notepad"))
|
195
|
self.Bind(wx.EVT_MENU, self.OnNotepadWorld, notepad_world)
|
196
|
self.Bind(wx.EVT_MENU, self.OnNotepadCharacter, notepad_character)
|
197
|
gameMenu.AppendMenu(wx.ID_ANY, t("ui.menu.notepad"), notepad)
|
198
|
|
199
|
|
200
|
character = wx.MenuItem(gameMenu, -1, t("ui.menu.character"))
|
201
|
self.Bind(wx.EVT_MENU, self.OnCharacter, character)
|
202
|
gameMenu.AppendItem(character)
|
203
|
|
204
|
|
205
|
self.chk_sounds = gameMenu.Append(wx.ID_ANY, t("ui.menu.sounds"),
|
206
|
t("ui.menu.sounds"), kind=wx.ITEM_CHECK)
|
207
|
gameMenu.Check(self.chk_sounds.GetId(), True)
|
208
|
self.Bind(wx.EVT_MENU, self.ToggleSounds, self.chk_sounds)
|
209
|
|
210
|
|
211
|
clear = wx.MenuItem(gameMenu, -1, t("ui.menu.clear_output"))
|
212
|
self.Bind(wx.EVT_MENU, self.OnClear, clear)
|
213
|
gameMenu.AppendItem(clear)
|
214
|
|
215
|
|
216
|
|
217
|
disconnect = wx.MenuItem(connectionMenu, -1, t("ui.menu.disconnect"))
|
218
|
self.Bind(wx.EVT_MENU, self.OnDisconnect, disconnect)
|
219
|
connectionMenu.AppendItem(disconnect)
|
220
|
|
221
|
|
222
|
reconnect = wx.MenuItem(connectionMenu, -1, t("ui.menu.reconnect"))
|
223
|
self.Bind(wx.EVT_MENU, self.OnReconnect, reconnect)
|
224
|
connectionMenu.AppendItem(reconnect)
|
225
|
|
226
|
|
227
|
|
228
|
pyconsole = wx.MenuItem(toolsMenu, -1, t("ui.menu.python_console"))
|
229
|
self.Bind(wx.EVT_MENU, self.OnPythonConsole, pyconsole)
|
230
|
toolsMenu.AppendItem(pyconsole)
|
231
|
|
232
|
|
233
|
|
234
|
basics = wx.MenuItem(helpMenu, -1, t("ui.menu.help_index"))
|
235
|
self.Bind(wx.EVT_MENU, self.OnBasics, basics)
|
236
|
helpMenu.AppendItem(basics)
|
237
|
|
238
|
|
239
|
new = wx.MenuItem(helpMenu, -1, t("ui.menu.new"))
|
240
|
self.Bind(wx.EVT_MENU, self.OnNew, new)
|
241
|
helpMenu.AppendItem(new)
|
242
|
|
243
|
|
244
|
updates = wx.MenuItem(helpMenu, -1, t("ui.menu.updates"))
|
245
|
self.Bind(wx.EVT_MENU, self.OnCheckForUpdates, updates)
|
246
|
helpMenu.AppendItem(updates)
|
247
|
|
248
|
menubar.Append(fileMenu, t("ui.menu.file"))
|
249
|
menubar.Append(gameMenu, t("ui.menu.game"))
|
250
|
menubar.Append(connectionMenu, t("ui.menu.connection"))
|
251
|
menubar.Append(toolsMenu, t("ui.menu.tools"))
|
252
|
menubar.Append(helpMenu, t("ui.menu.help"))
|
253
|
|
254
|
self.SetMenuBar(menubar)
|
255
|
|
256
|
def InitUI(self, world=None):
|
257
|
self.create_updater(just_checking=True)
|
258
|
session = Session(None, None)
|
259
|
if world is None:
|
260
|
dialog = ConnectionDialog(self.engine, session)
|
261
|
self.connection = dialog
|
262
|
value = dialog.ShowModal()
|
263
|
if value == wx.ID_CANCEL:
|
264
|
self.Close()
|
265
|
return
|
266
|
|
267
|
world = session.world
|
268
|
character = session.character
|
269
|
|
270
|
self.connection = None
|
271
|
self.tabs.AddPage(MUDPanel(self.tabs, self, self.engine, world,
|
272
|
session), world.name)
|
273
|
self.SetTitle("{} [CocoMUD]".format(world.name))
|
274
|
self.Maximize()
|
275
|
self.Show()
|
276
|
self.Bind(wx.EVT_CLOSE, self.OnClose)
|
277
|
self.Bind(wx.EVT_ACTIVATE, self.OnActivate)
|
278
|
self.tabs.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnTabChanged)
|
279
|
pub.subscribe(self.disconnectClient, "disconnect")
|
280
|
pub.subscribe(self.messageClient, "message")
|
281
|
|
282
|
def OnCreate(self, e):
|
283
|
"""Open the dialog to add a new world."""
|
284
|
session = Session(None, None)
|
285
|
world = World("")
|
286
|
dialog = EditWorldDialog(self.engine, world)
|
287
|
dialog.ShowModal()
|
288
|
if not world.name:
|
289
|
return
|
290
|
|
291
|
self.SetTitle("{} [CocoMUD]".format(world.name))
|
292
|
panel = MUDPanel(self.tabs, self, self.engine, world, session)
|
293
|
panel.CreateClient()
|
294
|
self.tabs.AddPage(panel, world.name, select=True)
|
295
|
panel.SetFocus()
|
296
|
self.sizer.Fit(self)
|
297
|
|
298
|
def OnOpen(self, e):
|
299
|
"""Open the ConnectionDialog for an additional world."""
|
300
|
session = Session(None, None)
|
301
|
dialog = ConnectionDialog(self.engine, session)
|
302
|
value = dialog.ShowModal()
|
303
|
if value == wx.ID_CANCEL:
|
304
|
return
|
305
|
|
306
|
world = session.world
|
307
|
self.SetTitle("{} [CocoMUD]".format(world.name))
|
308
|
panel = MUDPanel(self.tabs, self, self.engine, world, session)
|
309
|
panel.CreateClient()
|
310
|
self.tabs.AddPage(panel, world.name, select=True)
|
311
|
panel.SetFocus()
|
312
|
self.sizer.Fit(self)
|
313
|
|
314
|
def OnCloseTab(self, e):
|
315
|
"""Close the current tab."""
|
316
|
panel = self.panel
|
317
|
if panel:
|
318
|
if panel.client:
|
319
|
panel.client.disconnect()
|
320
|
|
321
|
for i, tab in enumerate(self.tabs.GetChildren()):
|
322
|
if tab is panel:
|
323
|
self.tabs.DeletePage(i)
|
324
|
break
|
325
|
|
326
|
def OnImportOndisk(self, e):
|
327
|
"""Import a world on disk."""
|
328
|
choose_file = t("ui.button.choose_file")
|
329
|
extensions = "Zip archive (*.zip)|*.zip"
|
330
|
dialog = wx.FileDialog(None, choose_file,
|
331
|
"", "", extensions, wx.OPEN)
|
332
|
result = dialog.ShowModal()
|
333
|
if result == wx.ID_OK:
|
334
|
filename = dialog.GetPath()
|
335
|
|
336
|
|
337
|
archive = ZipFile(filename)
|
338
|
files = {name: archive.read(name) for name in archive.namelist()}
|
339
|
options = files.get("world/options.conf")
|
340
|
if options:
|
341
|
infos = World.get_infos(options)
|
342
|
name = infos.get("connection", {}).get("name")
|
343
|
wizard = InstallWorld(self.engine, name, files)
|
344
|
wizard.start()
|
345
|
|
346
|
def OnImportOnline(self, e):
|
347
|
"""Import a world online."""
|
348
|
task = ImportWorlds()
|
349
|
task.start()
|
350
|
dialog = WorldsDialog(self.engine, task.worlds)
|
351
|
dialog.ShowModal()
|
352
|
|
353
|
def OnExportWorld(self, e):
|
354
|
"""Open the export world dialog box."""
|
355
|
dialog = ExportWorldDialog(self, self.engine, self.world)
|
356
|
dialog.ShowModal()
|
357
|
dialog.Destroy()
|
358
|
|
359
|
def OnPreferences(self, e):
|
360
|
"""Open the preferences dialog box."""
|
361
|
dialog = PreferencesDialog(self, self.engine)
|
362
|
dialog.ShowModal()
|
363
|
dialog.Destroy()
|
364
|
|
365
|
def OnPythonConsole(self, e):
|
366
|
"""Open the Python console dialog box."""
|
367
|
dialog = ConsoleDialog(self.engine, self.world, self.panel)
|
368
|
dialog.ShowModal()
|
369
|
|
370
|
def OnAlias(self, e):
|
371
|
"""Open the alias dialog box."""
|
372
|
dialog = AliasDialog(self.engine, self.world)
|
373
|
dialog.ShowModal()
|
374
|
dialog.Destroy()
|
375
|
|
376
|
def OnMacro(self, e):
|
377
|
"""Open the macro dialog box."""
|
378
|
dialog = MacroDialog(self.engine, self.world)
|
379
|
dialog.ShowModal()
|
380
|
dialog.Destroy()
|
381
|
|
382
|
def OnChannels(self, e):
|
383
|
"""Open the channels dialog box."""
|
384
|
dialog = ChannelsDialog(self.engine, self.world, self.world.channels)
|
385
|
dialog.ShowModal()
|
386
|
|
387
|
def OnTriggers(self, e):
|
388
|
"""Open the triggers dialog box."""
|
389
|
dialog = TriggerDialog(self.engine, self.world)
|
390
|
dialog.ShowModal()
|
391
|
dialog.Destroy()
|
392
|
|
393
|
def OnNotepadWorld(self, e):
|
394
|
"""The user selected the Notepad -> World... menu."""
|
395
|
panel = self.panel
|
396
|
world = panel.world
|
397
|
notepad = world.open_notepad()
|
398
|
dialog = NotepadDialog(notepad)
|
399
|
dialog.ShowModal()
|
400
|
|
401
|
def OnNotepadCharacter(self, e):
|
402
|
"""The user selected the Notepad -> Character... menu."""
|
403
|
panel = self.panel
|
404
|
character = panel.session.character
|
405
|
if character is None:
|
406
|
wx.MessageBox(t("ui.message.notepad.no_character"),
|
407
|
t("ui.alert.error"), wx.OK | wx.ICON_ERROR)
|
408
|
else:
|
409
|
notepad = character.open_notepad()
|
410
|
dialog = NotepadDialog(notepad)
|
411
|
dialog.ShowModal()
|
412
|
|
413
|
def OnCharacter(self, e):
|
414
|
"""Open the character dialog box."""
|
415
|
panel = self.panel
|
416
|
session = panel.session
|
417
|
dialog = CharacterDialog(self.engine, session)
|
418
|
dialog.ShowModal()
|
419
|
|
420
|
def ToggleSounds(self, e):
|
421
|
"""Toggle the "play sounds" checkbox."""
|
422
|
self.engine.sounds = self.chk_sounds.IsChecked()
|
423
|
|
424
|
def OnClear(self, e):
|
425
|
"""Force clear the output."""
|
426
|
if self.panel:
|
427
|
self.panel.ClearOutput()
|
428
|
|
429
|
def OnDisconnect(self, e):
|
430
|
"""Disconnect the current client."""
|
431
|
panel = self.panel
|
432
|
if panel and panel.client:
|
433
|
panel.client.disconnect()
|
434
|
|
435
|
def OnReconnect(self, e):
|
436
|
"""Reconnect the current client."""
|
437
|
panel = self.panel
|
438
|
if panel:
|
439
|
panel.CreateClient()
|
440
|
|
441
|
def OnBasics(self, e):
|
442
|
"""Open the Basics help file."""
|
443
|
self.engine.open_help("Basics")
|
444
|
|
445
|
def OnNew(self, e):
|
446
|
"""Open the Builds help file."""
|
447
|
self.engine.open_help("Builds")
|
448
|
|
449
|
def OnCheckForUpdates(self, e):
|
450
|
"""Open the 'check for updates' dialog box."""
|
451
|
self.create_updater(just_checking=True)
|
452
|
dialog = LoadingDialog(t("ui.message.update.loading"))
|
453
|
self.loading = dialog
|
454
|
dialog.ShowModal()
|
455
|
|
456
|
def OnQuit(self, e):
|
457
|
self.OnClose(e)
|
458
|
|
459
|
def OnClose(self, e):
|
460
|
"""Properly close the interface."""
|
461
|
self.Destroy()
|
462
|
if self.engine:
|
463
|
self.engine.stop()
|
464
|
|
465
|
def OnActivate(self, e):
|
466
|
"""The window gains or loses focus."""
|
467
|
if not self or not self.tabs:
|
468
|
return
|
469
|
|
470
|
self.focus = e.GetActive()
|
471
|
|
472
|
|
473
|
for tab in self.tabs.GetChildren():
|
474
|
tab.focus = False
|
475
|
tab.inside = self.focus
|
476
|
|
477
|
|
478
|
panel = self.panel
|
479
|
if panel:
|
480
|
panel.focus = True
|
481
|
if self.focus:
|
482
|
panel.output.SetFocus()
|
483
|
|
484
|
if self.focus:
|
485
|
|
486
|
if panel:
|
487
|
world = self.world
|
488
|
panel.nb_unread = 0
|
489
|
self.SetTitle("{} [CocoMUD]".format(world.name))
|
490
|
else:
|
491
|
self.SetTitle("CocoMUD")
|
492
|
|
493
|
e.Skip()
|
494
|
|
495
|
def OnTabChanged(self, e):
|
496
|
"""The current tab has changed."""
|
497
|
for page in self.tabs.GetChildren():
|
498
|
page.focus = False
|
499
|
|
500
|
tab = self.tabs.GetCurrentPage()
|
501
|
tab.focus = True
|
502
|
pos = tab.output.GetInsertionPoint()
|
503
|
tab.output.SetInsertionPoint(pos)
|
504
|
world = tab.world
|
505
|
self.SetTitle("{} [CocoMUD]".format(world.name))
|
506
|
e.Skip()
|
507
|
|
508
|
def disconnectClient(self, client=None, reason=None):
|
509
|
"""A client disconnects."""
|
510
|
if not self:
|
511
|
return
|
512
|
|
513
|
panel = client.factory.panel
|
514
|
if panel:
|
515
|
with self.lock:
|
516
|
panel.handle_disconnection(reason)
|
517
|
|
518
|
def messageClient(self, client, message, mark=None):
|
519
|
"""A client receives a message."""
|
520
|
if not self:
|
521
|
return
|
522
|
|
523
|
panel = client.factory.panel
|
524
|
if panel:
|
525
|
with self.lock:
|
526
|
panel.handle_message(message, mark=mark)
|
527
|
|
528
|
def OnResponseUpdate(self, build=None):
|
529
|
"""The check for updates has returned."""
|
530
|
if self.loading:
|
531
|
self.loading.Destroy()
|
532
|
if build is None:
|
533
|
message = t("ui.message.update.noupdate")
|
534
|
wx.MessageBox(message, t("ui.alert.information"),
|
535
|
wx.OK | wx.ICON_INFORMATION)
|
536
|
|
537
|
if build is not None:
|
538
|
message = t("ui.message.update.available", build=build)
|
539
|
value = wx.MessageBox(message, t("ui.message.update.title"),
|
540
|
wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
|
541
|
|
542
|
if value == wx.YES:
|
543
|
self.CloseAll()
|
544
|
os.startfile("updater.exe")
|
545
|
|
546
|
|
547
|
class MUDPanel(AccessPanel):
|
548
|
|
549
|
def __init__(self, parent, window, engine, world, session):
|
550
|
self.rich = engine.settings["options.output.richtext"]
|
551
|
AccessPanel.__init__(self, parent, history=True, lock_input=True,
|
552
|
ansi=self.rich, rich=self.rich)
|
553
|
self.screenreader_support = engine.settings[
|
554
|
"options.general.screenreader"]
|
555
|
if self.rich:
|
556
|
self.output.SetForegroundColour(wx.WHITE)
|
557
|
self.output.SetBackgroundColour(wx.BLACK)
|
558
|
ansi = self.extensions["ANSI"]
|
559
|
ansi.default_foreground = wx.WHITE
|
560
|
ansi.default_background = wx.BLACK
|
561
|
self.window = window
|
562
|
self.engine = engine
|
563
|
self.client = None
|
564
|
self.world = world
|
565
|
self.session = session
|
566
|
self.focus = True
|
567
|
self.inside = True
|
568
|
self.last_ac = None
|
569
|
self.output.SetFocus()
|
570
|
self.nb_unread = 0
|
571
|
|
572
|
|
573
|
if not engine.settings["options.general.screenreader"]:
|
574
|
size = 12
|
575
|
|
576
|
family = wx.FONTFAMILY_MODERN
|
577
|
font = wx.Font(size, family, wx.NORMAL, wx.NORMAL)
|
578
|
|
579
|
self.output.SetFont(font)
|
580
|
|
581
|
|
582
|
self.output.Bind(wx.EVT_TEXT_PASTE, self.OnPaste)
|
583
|
|
584
|
def CreateClient(self):
|
585
|
"""Connect the MUDPanel."""
|
586
|
log = logger("client")
|
587
|
session = self.session
|
588
|
name = session.world and session.world.name or "unknown"
|
589
|
character = session.character and session.character.name or "any"
|
590
|
log.info("Selecting world {}, character {}".format(name, character))
|
591
|
|
592
|
if self.client:
|
593
|
self.client.disconnect()
|
594
|
|
595
|
engine = self.engine
|
596
|
world = self.world
|
597
|
hostname = world.hostname
|
598
|
port = world.port
|
599
|
client = engine.open(hostname, port, world, self)
|
600
|
client.strip_ansi = not self.rich
|
601
|
world.load()
|
602
|
client.commands = self.login()
|
603
|
self.session.client = client
|
604
|
return client
|
605
|
|
606
|
def login(self):
|
607
|
"""Return the commands to login if a character has been selected."""
|
608
|
if self.session.character:
|
609
|
character = self.session.character
|
610
|
username = character.username
|
611
|
password = character.password
|
612
|
post_login = character.other_commands
|
613
|
|
614
|
|
615
|
commands = []
|
616
|
|
617
|
if username:
|
618
|
commands.extend(username.splitlines())
|
619
|
if password:
|
620
|
commands.extend(password.splitlines())
|
621
|
if post_login:
|
622
|
commands.extend(post_login.splitlines())
|
623
|
return commands
|
624
|
|
625
|
return []
|
626
|
|
627
|
|
628
|
def handle_disconnection(self, reason=None):
|
629
|
"""The client has been disconnected for any reason."""
|
630
|
message = "--- {} ---".format(t("ui.client.disconnected"))
|
631
|
if self:
|
632
|
self.Send(message)
|
633
|
ScreenReader.talk(message, interrupt=False)
|
634
|
|
635
|
def handle_message(self, message="", mark=None):
|
636
|
"""The client has just received a message."""
|
637
|
point = self.editing_pos
|
638
|
lines = message.splitlines()
|
639
|
lines = [line for line in lines if line]
|
640
|
message = "\n".join(lines)
|
641
|
world = self.world
|
642
|
if world:
|
643
|
world.feed_words(message)
|
644
|
|
645
|
if not self:
|
646
|
return
|
647
|
|
648
|
self.Send(message, pos=mark)
|
649
|
|
650
|
|
651
|
if mark is not None:
|
652
|
log = logger("ui")
|
653
|
word = self.output.GetRange(point + mark, point + mark + 15)
|
654
|
log.debug("A mark has been detected, move to {} : {}".format(
|
655
|
mark, repr(word)))
|
656
|
self.output.SetInsertionPoint(point + mark)
|
657
|
|
658
|
|
659
|
if self.focus and not self.inside:
|
660
|
self.nb_unread += 1
|
661
|
self.window.SetTitle("({}) {} [CocoMUD]".format(
|
662
|
self.nb_unread, world.name))
|
663
|
|
664
|
def OnInput(self, message):
|
665
|
"""Some text has been sent from the input."""
|
666
|
if self.world:
|
667
|
self.world.reset_autocompletion()
|
668
|
|
669
|
with self.window.lock:
|
670
|
try:
|
671
|
self.client.write(message)
|
672
|
except Exception:
|
673
|
log = logger("client")
|
674
|
log.exception("An error occurred when sending a message")
|
675
|
|
676
|
def OnPaste(self, e):
|
677
|
"""Paste several lines in the input field.
|
678
|
|
679
|
This event simply sends this text to be processed.
|
680
|
|
681
|
"""
|
682
|
with self.window.lock:
|
683
|
clipboard = wx.TextDataObject()
|
684
|
if not wx.TheClipboard.IsOpened():
|
685
|
wx.TheClipboard.Open()
|
686
|
success = wx.TheClipboard.GetData(clipboard)
|
687
|
wx.TheClipboard.Close()
|
688
|
if success:
|
689
|
clipboard = clipboard.GetText()
|
690
|
if self.IsEditing():
|
691
|
print("no edit")
|
692
|
self.output.SetInsertionPoint(self.output.GetLastPosition())
|
693
|
|
694
|
input = self.input + clipboard
|
695
|
if input.endswith("\n") and self.engine.settings[
|
696
|
"options.input.auto_send_paste"]:
|
697
|
lines = input.splitlines()
|
698
|
for line in lines:
|
699
|
self.OnInput(line)
|
700
|
self.ClearInput()
|
701
|
else:
|
702
|
e.Skip()
|
703
|
|
704
|
def OnKeyDown(self, e):
|
705
|
"""A key is pressed in the window."""
|
706
|
modifiers = e.GetModifiers()
|
707
|
key = e.GetUnicodeKey()
|
708
|
if not key:
|
709
|
key = e.GetKeyCode()
|
710
|
|
711
|
if self.world:
|
712
|
|
713
|
if self.client and self.client.test_macros(key, modifiers):
|
714
|
self.output.SetInsertionPoint(self.editing_pos)
|
715
|
return
|
716
|
|
717
|
|
718
|
if key == wx.WXK_TAB and modifiers == wx.MOD_NONE:
|
719
|
input = self.input
|
720
|
last_word = LAST_WORD.search(input)
|
721
|
if last_word:
|
722
|
last_word = last_word.groups()[0]
|
723
|
if self.last_ac and last_word.startswith(self.last_ac):
|
724
|
|
725
|
self.output.Remove(
|
726
|
self.output.GetLastPosition() + len(
|
727
|
self.last_ac) - len(last_word),
|
728
|
self.output.GetLastPosition())
|
729
|
last_word = self.last_ac
|
730
|
else:
|
731
|
self.last_ac = last_word
|
732
|
|
733
|
complete = self.world.find_word(last_word, TTS=True)
|
734
|
if complete:
|
735
|
end = complete[len(last_word):]
|
736
|
self.output.AppendText(end)
|
737
|
|
738
|
AccessPanel.OnKeyDown(self, e)
|