Project

Profile

Help

How to connect?
Download (25.3 KB) Statistics View on GitHub Reload from mirrored respository
| Branch: | Tag: | Revision:

github / src / ui / window.py @ f78b61ef

1
# Copyright (c) 2016, LE GOFF Vincent
2
# Copyright (c) 2016, LE GOFF Vincent
3
# All rights reserved.
4

    
5
# Redistribution and use in source and binary forms, with or without
6
# modification, are permitted provided that the following conditions are met:
7

    
8
# * Redistributions of source code must retain the above copyright notice, this
9
#   list of conditions and the following disclaimer.
10

    
11
# * Redistributions in binary form must reproduce the above copyright notice,
12
#   this list of conditions and the following disclaimer in the documentation
13
#   and/or other materials provided with the distribution.
14

    
15
# * Neither the name of ytranslate nor the names of its
16
#   contributors may be used to endorse or promote products derived from
17
#   this software without specific prior written permission.
18

    
19
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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
## Constants
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
            # The LoadingDialog needs to be destroyed to avoid confirmation
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
        # Differemtn menus
119
        fileMenu = wx.Menu()
120
        gameMenu = wx.Menu()
121
        connectionMenu = wx.Menu()
122
        toolsMenu = wx.Menu()
123
        helpMenu = wx.Menu()
124

    
125
        ## File menu
126
        # New
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
        # Open
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
        # Close
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
        # Import
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
        # Export
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
        # Preferences
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
        # Quit
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
        ## Game menu
168
        # Aliases
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
        # Macros
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
        # Triggers
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
        # Channels
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
        # Notepad
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
        # Character
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
        # Play sounds
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
        # Clear
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
        ## Connection menu
216
        # Disconnect
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
        # Reconnect
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
        ## Tools menu
227
        # Python console
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
        ## Help menu
233
        # Basics
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
        # News
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
        # Check for updates
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
            # Try to install the world from the archive
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
        # Set all tabs has unfocused
473
        for tab in self.tabs.GetChildren():
474
            tab.focus = False
475
            tab.inside = self.focus
476

    
477
        # Change the focus for the currently selected tab
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
            # Reset the window's title
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
        # Font setup, conditional to screen reader support
573
        if not engine.settings["options.general.screenreader"]:
574
            size = 12
575
            # modern is a monotype font
576
            family = wx.FONTFAMILY_MODERN
577
            font = wx.Font(size, family, wx.NORMAL, wx.NORMAL)
578
            # only sets the output window font
579
            self.output.SetFont(font)
580

    
581
        # Event binding
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
            # Send these commands
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
    # Methods to handle client's events
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
        # If there's a mark, move the cursor to it
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
        # Change the window title if not focused
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
            # Test the different macros
713
            if self.client and self.client.test_macros(key, modifiers):
714
                self.output.SetInsertionPoint(self.editing_pos)
715
                return
716

    
717
            # Test auto-completion
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
                        # Remove the word to be modified
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)
(4-4/4)