25 February
2008
Digg digg it  |  Slashdot slashdot.org  |  Reddit reddit  |  del.icio.us del.icio.us  |  Technorati Technorati

What happens when Python remembers me that programming is fun?

Looking back, last month was a really productive and fun one; I've designed and developed "from scratch" a fairly complete and extensible IDE and a quite powerful web application server as plugins for the Devil Framework. The two required really different sets of functionalities but where equally fast and fun to develop, thanks to the Python language itself, to the libraries available for it and also for the Devil Framework itself (that recursively uses Python,etc.).

Today I want to talk about the ScriptIDE plugin and how great are the two libraries that made it possible: PyQt and Qscintilla.

The plugin augments the IceBridge smart client with an integrated development environment designed (and shamelessly inspired by WingIDE and Eric) to enable developers to access all scripts (and more generally source code) made available by the different extensions available for the Devil Framework, like:

Two scripts in two different contexts: an SQL Method script in the Database Manager and a Signal Handler in a View.

Every extension provides its own ad-hoc user interface for managing its functionalities: this is something required, as scripts are just a single element of the whole configuration. As scripts are used everywhere, this also makes the script development experience less than excellent (at least).

So came the decision to develop the ScriptIDE plugin and with the following requirements:

And in great astonishment and disbelief, I achieved all the above requirements in a couple of weeks! And in a mere 6000 lines of code!! And having some fun doing it!!! BAH!!!!

The editor part was e breeze as the QScintilla library/widget is really complete and simple to use and offers also some high-level functionalities (like the markers and the search and replace stuff). With a ~2000 LOC wrapper (really basic, no rocket science, just simple utilities) the editor come out fairly complete (no macros and scripts, by now).

GUI stuff such menus, toolbars and popup menus were easily managed thanks to the Devil Framework wrappers we have developed. You can add/remove/manage actions as in the code below:

# actions definition
actions = [
    {
        Action.NAME: 'close_current',
        Action.ACTION: self.on_close_current,
        Action.MENUBAR_PATH: '/g10/&File/fg10/15_close_current',
        Action.TOOLBAR_PATH: '/g10/file/fg10/15_close_current',
        Action.POPUP_PATH: '/g10/15_close_current',
        Action.LABEL: tr ('&Close', context='ScriptIDE'),
        Action.ACCEL: [Cons.GUI.MENU_ACCELERATOR_CTRL, 'd'], 
        Action.EXCLUSIVE: False,
        Action.TARGETS: Cons.GUI.TARGET_MENUBAR | Cons.GUI.TARGET_TOOLBAR | Cons.GUI.TARGET_POPUP_MENU,
        Action.ICON: 'images/file_close',
        Action.TEXT: tr ('Closes current editor.', context='ScriptIDE'),
        Action.WHATIS: tr ('', context='ScriptIDE'),
    },
]
# initialize actions displaying menubars and toolbars
self.init_actions (actions)
# get an action instance
a = self.get_action_by_name ('close_current')

# enable/disable and action
a.enabled = True
# show/hide an action
a.set_visible (True)
# set icon
a.set_icon ('images/devil')

# show a popup menu populated with given actions
self.show_popup ([a])

The same can be said about windows and dock windows: our wrappers automatically keep track of all the configurations, making state management mostly transparent. And as everything is data-driven,implementing pluggable tools ended up "implementing" a data structure like the one below:

DEFAULT_TOOL_WINDOWS = [
    {
        'name': 'right_toolbox',
        'label': 'Toolbox 1',
        'dock_config': { # dock window specific config
            'index': 0,
            'orientation': 'vertical',
            'position': 'right',
            'movable': True,
        },
        'show_hide_key': qt.Qt.Key_F10, # key sequence used to show or hide the toot window
        'sections': [
            {
                'style': 'tabs',
                'tabs_position': 'top',
                'tools': [
                    {
                        'name': 'script_browser',
                    },
                ],
            },
        ]
    },
]

Most of the common dialogs used in the IDE are "commodity" dialogs made available by the IceBridge console, so:

# to select a file for open (main_loop is the "historically misnamed" core singleton object)
self.main_loop.api.dialog.get_open_file_name (
    path = self.preferences.get ('insert_from_file_path', self.main_loop.platform.home_dir ()),
    filter = 'Python files (*.py);;Text files (*.txt);;All files (*.*)',
    caption = tr ('Select file to insert', context='ScriptIDE')
)

# to get an integer between 1 and last line in editor
i = self.main_loop.api.dialog.get_integer (
    caption = tr ('Goto Line', context='ScriptIDE'), 
    message = tr ('&Line Number:', context='ScriptIDE'),
    min = 1, 
    max = editor.lines (), 
    step = 1,
    data = 1,
)

Other common stuff was equally easy, like the management of hierarchical lists (QListView) so commonly used in modern GUIs:

# define the listview columns
columns = [
    ListView.Column ('name', tr ('Name', context='ScriptIDE'), width=80),
    ListView.Column ('source', tr ('Source', context='ScriptIDE'), stretchable=True),
    ListView.Column ('line', tr ('Line', context='ScriptIDE'), width=50),
]

self.bookmarks_list = ListView (
    self['bookmarks_list'], # Windows and Dialogs can find child widgets by name
    columns, 
    decorated_root = False
)

# populate the view
for b in bookmarks:
    name = b['name']
    label = b['label']
    line = b['line']

    # fill the row fields with proper values. Fields can be skipped if needed.
    f = {
        'name': ListView.Field ('name', name),
        'source': ListView.Field ('source', label),
        'line': ListView.Field ('line', str (line + 1)),
    }

    # define a row, with custom named attributes
    r = ListView.Row (
        None, self.bookmarks_list, f, 
        # custom named fields
        rtype = 'bookmark', 
        name = name,
        label = label,
        line = line,
        bookmark = b, 
        # a context menu action is associated to this row,
        # different actions can be specified for each row
        context_menu = self.on_context_menu 
    )

    if rows:
        rows.append (r)
    else:
        rows = r
			
# rows are updated: if an “equal” row is present, the old row is
# updated with the values of the new one, but the state is preserved:
# open, selected, etc.
self.bookmarks_list.update_rows (rows)

# state management; the state can be dumped and saved for later reuse
# state management is intelligent enough to skip obsolete data.
state = self.bookmarks_list.dump_state ()
self.bookmarks_list.load_state (state)

# rows can be directly manipulated
row.set_checked (True)
row.set_enabled (True, recursive=False)
row.select (True)
row.flash = True
# update 2 fields
row.field['name'].data = 'Foo'
row.field['source'].data = 'Bar'
row.update ()

And as you may have noted everything comes with internationalization included. And multi-platform!

I always wanted to develop my own IDE, but never gone too far has the task was too big. Not anymore! Python and PyQt (and our "little" framework) made it easy, fast and fun, removing all the tedious and infinitely prolonged stuff developers have to deal with. The IDE is not finished (next to be added: macros, scripts, templates and more providers) but it's fully working and it looks and behaves professionally.

Happy coding......


Posted by alex at 19:19 | Comments (0) | Trackbacks (0)
<< DLNetSNMP added to Pypi | Main | HyperCard, Python, Views and a little sip of Pan Galactic Gargle Blaster (be careful...) >>
Comments
There is no comment.
Trackbacks
Please send trackback to:http://www.dlevel.com/blogs/alex/25/tbping
There is no trackback.