File: modes.rst

package info (click to toggle)
mu-editor 1.0.2%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 9,048 kB
  • sloc: python: 16,322; makefile: 129; xml: 29; sh: 11
file content (342 lines) | stat: -rw-r--r-- 15,057 bytes parent folder | download | duplicates (3)
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
Modes in Mu
-----------

Mu is a modal editor: it behaves differently depending on the currently
selected mode. The name of the current mode is always displayed in the bottom
right hand corner of Mu's window. Clicking on the mode button opens up a dialog
box to allow users to select a new mode.

What Are Modes?
===============

Modes are a way to customise how Mu should behave. This simplifies Mu: rather
than trying to provide every possible feature at once (and thus become a
nightmare of complexity for the user), modes bring related features together in
a simple and easy to use manner.

Modes are able to add buttons to the user interface, handle certain events
(such as when one of the mode's buttons is clicked) and provide contextual
information for Mu (such as where files should be saved or what API metadata
is available). It's also possible for one mode to transition to another and
some modes are only available as transitional modes (i.e. they may not be
selected by the user). A good example of such a "transitional" mode is the
Python 3 debugger, which can only be accessed from the standard Python 3 mode.

Mu contains the following modes, although it is very easy to add more (the
images below are used with permission, see :doc:`copyright`).

Adafruit Mode
+++++++++++++

`Adafruit <http://adafruit.com/>`_ make extraordinarily awesome boards for
embedded development. Many of these boards run Adafruit's own flavour of 
`MicroPython <http://micropython.org/>`_ called
`CircuitPython <https://www.adafruit.com/circuitpython>`_.

The Adafruit mode
inherits from a base MicroPython mode that provides USB/serial connectivity to
the board. Because source code is stored directly on the Adafruit boards,
this mode ensures that filesystem based operations actually happen on the
connected device. If no such device is found, the mode will warn you. If the
mode detects a device in "bootloader mode" (tap the reset button three times
to enable this), then Mu can also flash the latest version of CircuitPython
onto your device.

BBC micro:bit Mode
++++++++++++++++++

.. image:: microbit.png 

The `BBC micro:bit <http://microbit.org/>`_ is a small computing device for
young coders that is capable of running MicroPython. Mu was originally created
as volunteer led effort as part of the Python Software Foundation's
contribution to the project.

Just like the Adafruit mode, micro:bit mode inherits from a base MicroPython
mode so there's a REPL based interface to the device. It also provides
functionality to "flash" (i.e. copy) your code onto the device and a simple
user interface to the simple file system on the device.

Pygame Zero / PyGame Mode
+++++++++++++++++++++++++

.. image:: pygame.png

`PyGame <http://pygame.org/>`_ (or, more correctly: "pygame") is a cross
platform set of Python libraries for writing games.
`Pygame Zero <https://pygame-zero.readthedocs.io/en/stable/>`_ is a wrapper for
pygame that makes it easy for beginners to make games. If both pygame and
Pygame Zero are installed (as they are if you used the official Windows
installer), Mu's Pygame Zero mode makes it easy for beginner programmers to
create games.

This mode provides a "Play" button that uses Pygame Zero's game-runner to
launch the user's games. Two further buttons open the operating system's file
system explorer for the directories containing images and sounds used in the
user's games. This makes it easy for the user to copy and paste new game assets
into the right place.

The standard Python3 mode (see below) is probably a better environment for more
advanced pygame-only development. Mu ensures that all the game assets required
by the `Pygame Zero introductory tutorial <https://pygame-zero.readthedocs.io/en/stable/introduction.html>`_
are available by default.

Standard Python3 Mode
+++++++++++++++++++++

This mode is for creating simple Python 3 programs. As with the other modes,
there is a REPL for live programming, but in this case it is an iPython based
REPL that uses `project Jupyter <http://jupyter.org/>`_. As with other Jupyter
notebooks, it's possible to embed graphics and charts into the REPL so it
becomes a interesting to read and work with.

There are two ways to run your script in this mode:

1. Click the "Run" button: will launch the script using Python's interactive
   mode (so you'll be dropped into a basic interactive Python shell upon the
   script's completion).
2. Click the "Debug" button: Python mode transitions to the debug mode - a
   graphical way to inspect and watch your code execute.
   
Because of the overhead needed to start the graphical debugger it takes longer
to start running your script. This is especially noticable on the Raspberry Pi.

Python 2 isn't supported by Mu and never will be.

Debug Mode
++++++++++

It's only possible to enter debug mode from standard Python mode. It's purpose
is to manage the execution and inspection of your code.

Clicking the margin of the editor toggles "break points" that tell the debugger
where to pause. Once paused it's possible to inspect the state of various
objects at that moment in the code's execution and step over, into and out of
lines of code. You're able to watch Python execute your code, allowing you to
discover where there may be bugs.

Once the code has finished the debug mode transitions back to standard
Python mode.

Create a New Mode
=================

It's very easy to create new mode for Mu. The following tutorial explains how
we created the Pygame Zero mode.

Create a Class
++++++++++++++

The most important aspects of a mode are encapsulated in a class that
represents the mode. These classes live in the ``mu.modes`` namespace and
**must** inherit from the ``mu.modes.base.BaseMode`` class. If your new mode
is for a MicroPython based device, you should inherit from the
``mu.modes.base.MicroPythonMode`` class, since this includes various helpful
utilities for such things as finding a connected device and running a REPL
over a USB-serial connection.

The naming convention is to create a new module in which is found the class
representing the mode. For example, for Pygame Zero, the new module is
``mu.modes.pygamezero`` in which is found the ``PyGameZeroMode`` class that
inherits form ``BaseMode``.

Integrate the Mode
++++++++++++++++++

Mu needs to know that the new mode is available to use. This is fulfilled by
a couple of relatively simple steps:

* Add the mode's class to the ``__all__`` list in the ``__init__.py`` file for
  the ``mu.modes`` namespace.
* In ``mu.app.py`` import the new mode from ``mu.modes`` and add an instance of
  the mode's class to the dictionary returned by the ``setup_modes`` function.
  (All modes are instantiated with the available ``editor`` and ``view``
  objects that represnt the editor's logic and UI layer respectively.)

Update the Class's Behaviour
++++++++++++++++++++++++++++

The core elements of your new mode's class that need updating include some
attributes and three methods.

The attributes that must be changed are:

* ``name`` -- the full name of the mode, for example, "PyGame Zero".
* ``description`` -- a short description of the mode to be displayed in the
  mode picker. For example, "Make games with Pygame Zero".
* ``icon`` -- an icon used to represent the mode in the mode picker. This must
  be a ``.png`` image file found in the ``mu/resources/images`` directory.

Additional attritbutes with safe default values set in the ``BaseMode`` class
which may be of value for you to change are:

* ``save_timeout`` -- the number of seconds to wait before auto-saving work. If
  this value is 0 (zero) Mu will not auto-save changed files when in this mode.
* ``builtins`` -- a list of strings defining symbols that Mu's code checker
  must assume are builtins (above and beyond Python's standard builtins).

.. note::

    When creating strings that will be seen by users please remember to use
    the conventions for internationalization (i18n). Put simply, enclose your
    strings in a call to ``_`` like this::
    
    _('This string will be translated automatically')
    
    Please see :doc:`translations` for more details.

You should pay attention to three methods of your class: ``actions``,
``api`` and ``workspace_dir``. You must override ``actions`` and ``api`` (see
below) and *may* want to override ``workspace_dir``.

The purpose of the ``workspace_dir`` method is to return a string
representation of the path to the directory containing the code created with
this mode. The default implementation in ``BaseMode`` is generally safe to use
although some CircuitPython based boards may want to use this method to point
to a connected device (if attached) or a safe default on the user's filesystem
(if no device is attached). See how it's done in the ``AdafruitMode`` class.
If in doubt, just use the method inherited from ``BaseMode``.

However, you **must** override the ``actions`` method. It must return a list
of dictionaries that describe the buttons to be added to Mu's user interface.
Each dictionary must contain the following key/value pairs:

* ``name`` -- the name of the button which doubles as the name of the icon
  found in ``mu/resources/images`` used as the visual representation of the
  button. To create a new button start with the blank ``button.png`` image
  and use either an icon from the
  `FontAwesome <https://fontawesome.bootstrapcheatsheets.com/>`_ set of icons,
  or some other graphical device that looks visually similar. Make sure that
  the colour of the image is correct blue of (hex value) #336699. Please
  remember to centre it within the button and make sure it has the same sort
  of scale as the existing buttons.
* ``display_name`` -- the string displayed immediately underneath the button
  in Mu's user interface.
* ``description`` -- the string displayed as a tool-top when the mouse
  pointer hovers over the button, but the button remains unclicked.
* ``handler`` -- a reference to a method you have created in your mode's class
  that is called, with an event object, when the button is clicked.
* ``shortcut`` -- a string representation of the keyboard shortcut for the
  button. Valid examples include, ``'F5'`` (for function key 5) or,
  ``'Ctrl+Shift+I'`` (for control-shift-I).

By way of illustration, here's the list of dictionaries returned in the
Pygame Zero mode::

    [
        {
            'name': 'play', 
            'display_name': _('Play'),
            'description': _('Play your PyGame Zero game.'),
            'handler': self.play_toggle,
            'shortcut': 'F5',
        },
        {
            'name': 'images',
            'display_name': _('Images'),
            'description': _('Show the images used by PyGame Zero.'),
            'handler': self.show_images,
            'shortcut': 'Ctrl+Shift+I',
        },
        {
            'name': 'sounds',
            'display_name': _('Sounds'),
            'description': _('Show the sounds used by PyGame Zero.'),
            'handler': self.show_sounds,
            'shortcut': 'Ctrl+Shift+S',
        },
    ]

Notice how the handlers are references to methods of the ``PyGameZeroMode``
class, the details of which are left to the creator of the mode. Mu simply
calls the handler and expects the author of the mode to know what they're
doing.

Interactions with the Mu editor are via two objects referenced within the
class:

* ``self.editor`` -- represents an object containing the core logic of the
  editor (an instance of ``mu.logic.Editor``).
* ``self.view`` -- references the main GUI object through which all display
  and user interface related operations should pass (an instance of
  ``mu.interface.main.Window``).

Please see the :doc:`api` for specific details of what these two objects
offer.

Finally, you **must** also override the ``api`` method, whose role is to
provide a list of strings that conform to Scintilla's protocol for defining
and documenting API's to be used with autocomplete and call-tips. The protocol
is::

    'foo.bar(arg1, args2="baz") \nMulti line\n\nEnglish description.`

Happily, various scripts in the ``utils`` directory can be used, cloned and
modified to autogenerate this documentation from source code. The reason the
extraction of such API related information is automated is so it makes it
very quick and easy to revise such data as APIs change in the future.

Take a look at the ``pgzero_api.py`` file and you'll find a simple recipe for
extracting such information from Python modules. Three modules for Python's
standard library (``json``, ``inspect`` and ``importlib``) are used to import
the modules we're interested in, inspect the signatures of the callable objects
found therein and emit a JSON based output (called ``pgzero_api.json``).

The resulting JSON is a list of JSON objects containing three attributes:

* ``name`` -- the module name + object name.
* ``args`` -- a list of the arguments taken by the callable Python object
  being described.
* ``description`` -- the docstring associated with the Python object.

Here's an example of such an object from the emitted ``pgzero_api.json``
file::

    {
        "description": "Interface to the screen.",
        "name": "screen.Screen",
        "args": [
            "surface"
        ]
    }

Given such JSON serialised data, the ``mkapi.py`` command will take such a file
as input and emit to stdout a list of strings for the API that conform to
Scintilla's protocol to be used by autocomplete and call-tips.

In the case of the Pygame Zero mode, the output from the ``mkapi.py`` command
ended up in ``mu.modes.api.PYGAMEZERO_APIS``. The list itself is in the
``pygamezero.py`` file in the ``mu/modes/api`` directory, and the
``__init__.py`` found therein exposes it via the ``__all__`` list.

Back in the ``PyGameZeroMode`` class the ``api`` method simply returns a
concatenated list of the APIs that a user of the mode may use::

    from mu.modes.api import (PYTHON3_APIS, SHARED_APIS, PI_APIS,
                              PYGAMEZERO_APIS)

    ... later in the PyGameZeroMode class ...

    def api(self):
        return SHARED_APIS + PYTHON3_APIS + PI_APIS + PYGAMEZERO_APIS

With these relatively simple steps, it's possible to create quite powerful
modes. Most importantly, taking a look at the existing modes in the
``mu.modes`` namespace will reveal how to do most of the things you'll need.

However, there is one final aspect of creating a mode that we need to address.

Unit Test the Mode
++++++++++++++++++

**We will not accept any new modes without 100% unit test coverage.**

Please read the guide about :doc:`tests` for how Mu is tested and the various
expectations we have when it comes to writing tests.

If you are unsure about the best way to go about testing your mode please feel
free to ask for help. We would much rather get a pull request for a "spike"
(draft) version of a new mode and work with the original author on testing the
code, than have no pull request at all.

If in doubt, ask. We're a friendly bunch and :doc:`contributing` is easy.