Project

Profile

Help

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

github / src / ui / window.py @ a5c338e8

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

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

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

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

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

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

    
68
        # File menu
69
        ## Preferences
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
        ## Quit
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
        # Game menu
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
    # Methods to handle client's events
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
        # Input
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
        # Password
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
        # Add the input field in the sizer
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
        # Ouput
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
        # Add the output fields in the sizer
200
        sizer.Add(s_input)
201
        sizer.Add(t_output, proportion=8)
202

    
203
        # Event handler
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 + "\r\n")
219

    
220
        # Write in the history
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
        # Look for matching macros
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)
(3-3/3)