2010-11-16 16:05:36 +01:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
#
|
|
|
|
|
# wizpym - The Wyzard Python Module, a tool to write Wizard-like apps.
|
|
|
|
|
# Copyright (C) 2010 David Soulayrol <david.soulayrol@gmail.com>
|
|
|
|
|
#
|
|
|
|
|
# wizpym is free software; you can redistribute it and/or modify
|
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
|
|
|
# (at your option) any later version.
|
|
|
|
|
#
|
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
|
#
|
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
|
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
"""A tool to write Wizard-like apps.
|
|
|
|
|
|
|
|
|
|
This module provides classes to easily build wizard-type applications
|
|
|
|
|
with GTK. This kind of application is composed of a succession of
|
|
|
|
|
panes which asks the user for inputs, and processes them to achieve
|
|
|
|
|
some result. It is a quite natural way to propose a graphical view for
|
|
|
|
|
short and simple tasks, or batch processes.
|
|
|
|
|
|
|
|
|
|
Wizard applications thus are successions of short tasks which
|
|
|
|
|
cooperate to work on some subject, each one adding a value or choosing
|
|
|
|
|
the next step in the whole process. wizpym provides a framework for
|
|
|
|
|
the developers to focus on these tasks by providing the tools to plug
|
|
|
|
|
them together and to handle background activities.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import gobject
|
|
|
|
|
import gtk
|
|
|
|
|
import threading
|
|
|
|
|
|
|
|
|
|
__all__ = ['InvalidPane', 'Pane', 'ActivePane', 'Frame']
|
2010-11-22 12:10:25 +01:00
|
|
|
|
__version_info__ = (0, 2, 0)
|
2010-11-16 16:05:36 +01:00
|
|
|
|
__version__ = '.'.join((str(i) for i in __version_info__))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class InvalidPane(Exception):
|
|
|
|
|
"""Raised when a wrong argument is given in pane manipulating methods."""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Pane(gtk.VBox):
|
|
|
|
|
"""A single pane from a Wizard.
|
|
|
|
|
|
|
|
|
|
Each pane of a wizard has zero or one predecessor and one or many
|
|
|
|
|
successors. The successor is chosen upon an switch, which can be
|
|
|
|
|
altered when the user interacts with the pane.
|
|
|
|
|
|
|
|
|
|
All panes from one wizard share a subject. The subject is the
|
|
|
|
|
object that forwards data from one pane to the other and can be a
|
|
|
|
|
simple string as well as a complex structure. It is initially set
|
|
|
|
|
by the wizard Frame when the first pane is created.
|
2010-11-17 13:25:48 +01:00
|
|
|
|
|
|
|
|
|
A pane can also state that it is not ready to fulfill the next
|
|
|
|
|
pane requirements. The frame will grey the Next button as long as
|
|
|
|
|
its ready attribute is set to False.
|
2010-11-16 16:05:36 +01:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, frame):
|
|
|
|
|
"""Constructor.
|
|
|
|
|
|
2010-11-17 13:25:48 +01:00
|
|
|
|
A fresh pane is in a ready state and has no successors nor
|
|
|
|
|
predecessor. The constructor doesn't either set the subject:
|
|
|
|
|
the wizard frame takes care of setting it, and passing it from
|
|
|
|
|
one pane to the other.
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
Note that all wizard panes constructors should take no
|
|
|
|
|
parameter since they are called automatically by the wizard
|
|
|
|
|
frame. The developer should consider using the wizard subject
|
|
|
|
|
or environment variables to configure the panes if necessary.
|
|
|
|
|
"""
|
|
|
|
|
gtk.VBox.__init__(self, False, 5)
|
|
|
|
|
|
|
|
|
|
self._frame = frame
|
2010-11-18 11:11:28 +01:00
|
|
|
|
|
|
|
|
|
self._header = gtk.VBox()
|
|
|
|
|
self.pack_start(self._header, False)
|
|
|
|
|
|
|
|
|
|
self._error = gtk.VBox()
|
|
|
|
|
self.pack_end(self._error, False)
|
|
|
|
|
|
2010-11-16 16:05:36 +01:00
|
|
|
|
self._subject = None
|
|
|
|
|
self._switch = None
|
|
|
|
|
self._successors = {}
|
2010-11-17 13:25:48 +01:00
|
|
|
|
self._ready = True
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
def link_to(self, klass):
|
|
|
|
|
"""Add a successor to this pane.
|
|
|
|
|
|
|
|
|
|
This method is intended to be called by the pane itself at
|
|
|
|
|
construction time to set its successors, but nothing forbids
|
|
|
|
|
to do it a different way.
|
|
|
|
|
|
|
|
|
|
The switch is set to the first successor set. If the provided
|
|
|
|
|
pane is already a successor of this pane, the InvalidPane
|
|
|
|
|
exception is raised.
|
|
|
|
|
"""
|
|
|
|
|
if klass is None or klass in self._successors:
|
|
|
|
|
raise InvalidPane()
|
|
|
|
|
self._successors[klass] = klass(self._frame)
|
|
|
|
|
if len(self._successors) == 1:
|
|
|
|
|
self._switch = klass
|
|
|
|
|
|
2010-11-17 19:06:16 +01:00
|
|
|
|
def set_header(self, msg):
|
|
|
|
|
"""Add a help header to the pane with the given message.
|
|
|
|
|
|
|
|
|
|
Since it is quite common that panes begin with a little guide
|
|
|
|
|
explaining the current step, calling this method will ensure
|
|
|
|
|
such panes all present the same header style.
|
|
|
|
|
"""
|
2010-11-18 11:11:28 +01:00
|
|
|
|
self._header.foreach(lambda w: self._header.remove(w))
|
|
|
|
|
if msg:
|
|
|
|
|
help_label = gtk.Label(msg)
|
|
|
|
|
help_label.set_justify(gtk.JUSTIFY_FILL)
|
|
|
|
|
help_label.set_line_wrap(True)
|
|
|
|
|
self._header.pack_start(help_label, False, False, 10)
|
|
|
|
|
self._header.pack_start(gtk.HSeparator(), False)
|
|
|
|
|
self._header.show_all()
|
|
|
|
|
|
|
|
|
|
def set_error(self, msg):
|
2011-09-03 22:34:48 +02:00
|
|
|
|
"""Display a error in the pane's footer.
|
2010-11-18 11:11:28 +01:00
|
|
|
|
|
2011-09-03 22:34:48 +02:00
|
|
|
|
Display the given message in red in a special label attached
|
|
|
|
|
to the footer of the pane. The pane is hidden if the message
|
|
|
|
|
is empty.
|
2010-11-18 11:11:28 +01:00
|
|
|
|
"""
|
|
|
|
|
self._error.foreach(lambda w: self._error.remove(w))
|
|
|
|
|
if msg:
|
|
|
|
|
error_label = gtk.Label()
|
|
|
|
|
error_label.set_markup(
|
|
|
|
|
'<span foreground="red" weight="bold">' + msg + '</span>')
|
|
|
|
|
error_label.set_justify(gtk.JUSTIFY_FILL)
|
|
|
|
|
error_label.set_line_wrap(True)
|
|
|
|
|
self._error.pack_start(error_label, False, False, 10)
|
|
|
|
|
self._error.show_all()
|
2010-11-17 19:06:16 +01:00
|
|
|
|
|
2010-11-22 11:48:29 +01:00
|
|
|
|
def clear(self, safe=None):
|
|
|
|
|
"""Clear all the pane children but internal widgets.
|
|
|
|
|
|
|
|
|
|
If the safe parameter is a list, then its content should be
|
|
|
|
|
widgets that should not be removed either. Invalid parameters
|
|
|
|
|
are ignored.
|
|
|
|
|
|
|
|
|
|
This method leaves the header and the error labels untouched
|
|
|
|
|
(eg. if they were displayed, they remain displayed).
|
|
|
|
|
"""
|
|
|
|
|
if safe is None or not isinstance(safe, list):
|
|
|
|
|
safe = []
|
|
|
|
|
safe.extend([self._header, self._error])
|
|
|
|
|
for w in filter(lambda x: x not in safe, self.get_children()):
|
|
|
|
|
self.remove(w)
|
|
|
|
|
|
2010-11-16 16:05:36 +01:00
|
|
|
|
def switch(self, klass):
|
|
|
|
|
"""Update the switch using the given class.
|
|
|
|
|
|
|
|
|
|
If the provided class is None or is not a successor of this
|
|
|
|
|
pane, the InvalidPane exception is raised.
|
|
|
|
|
"""
|
|
|
|
|
if klass is None or klass in [o.__class__ for o in self._successors]:
|
|
|
|
|
raise InvalidPane()
|
|
|
|
|
self._switch = klass
|
|
|
|
|
|
|
|
|
|
def is_terminal(self):
|
|
|
|
|
"""Whether this pane has no successor."""
|
|
|
|
|
return len(self._successors) == 0
|
|
|
|
|
|
|
|
|
|
def enter(self):
|
|
|
|
|
"""Proceed to this pane initialization.
|
|
|
|
|
|
|
|
|
|
This method is called by the wizard main frame when this pane
|
|
|
|
|
is entered or reentered from its predecessor. It should be
|
|
|
|
|
overriden if this pane needs to update itself with the received
|
|
|
|
|
subject.
|
|
|
|
|
"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def leave(self):
|
|
|
|
|
"""Proceed to this pane finalization.
|
|
|
|
|
|
|
|
|
|
This method is called when this pane is replaced by a
|
|
|
|
|
successor. It should be overriden if this pane needs to
|
|
|
|
|
compute or to retrieve some data to update the subject before
|
|
|
|
|
it is passed to the successor.
|
|
|
|
|
"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def next(self):
|
|
|
|
|
"""Return the next pane as designated by the switch."""
|
|
|
|
|
return self._successors.get(self._switch)
|
|
|
|
|
|
|
|
|
|
def _get_subject(self):
|
|
|
|
|
return self._subject
|
|
|
|
|
|
|
|
|
|
def _set_subject(self, s):
|
|
|
|
|
self._subject = s
|
|
|
|
|
|
2010-11-17 13:25:48 +01:00
|
|
|
|
def _is_ready(self):
|
|
|
|
|
return self._ready
|
|
|
|
|
|
|
|
|
|
def _set_ready(self, v):
|
|
|
|
|
self._ready = v
|
|
|
|
|
self._frame.update()
|
|
|
|
|
|
2010-11-16 16:05:36 +01:00
|
|
|
|
subject = property(_get_subject, _set_subject, None, 'This pane subject')
|
2010-11-17 13:25:48 +01:00
|
|
|
|
ready = property(_is_ready, _set_ready, None, 'Whether the next pane can be called')
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ActivePane(Pane):
|
|
|
|
|
"""An active pane from a Wizard.
|
|
|
|
|
|
|
|
|
|
ActivePane specializes the Pane class by providing a mean of
|
|
|
|
|
running long tasks in the background. Applications using this kind
|
|
|
|
|
of Pane should not forget to call gobject.threads_init before
|
|
|
|
|
anything else.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, frame):
|
|
|
|
|
"""Constructor."""
|
|
|
|
|
Pane.__init__(self, frame)
|
|
|
|
|
|
2010-11-21 11:20:49 +01:00
|
|
|
|
self._progress_box = gtk.VBox()
|
|
|
|
|
self.pack_end(self._progress_box, False)
|
|
|
|
|
|
2010-11-16 16:05:36 +01:00
|
|
|
|
self._thread = None
|
|
|
|
|
|
2010-11-21 11:20:49 +01:00
|
|
|
|
def update_progress_bar(self, fraction=None, text=None):
|
|
|
|
|
"""Update this pane's progress bar.
|
|
|
|
|
|
|
|
|
|
The progress bar, when used, shows up at the bottom of the
|
|
|
|
|
pane. When this method is called, it is first displayed is
|
|
|
|
|
absent, and the updated with the given fraction, between 0.0
|
|
|
|
|
and 1.0, and the given text if any. In the special case where
|
|
|
|
|
the fraction is not provided, the ProgressBar.pulse method is
|
|
|
|
|
invoked.
|
|
|
|
|
|
|
|
|
|
Returns True, so that it can be used in a GObject timer.
|
|
|
|
|
|
|
|
|
|
This method is non-GTK thread safe, which means that any
|
|
|
|
|
thread can call it.
|
|
|
|
|
"""
|
|
|
|
|
gobject.idle_add(self._do_update_progress_bar, fraction, text)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def hide_progress_bar(self):
|
|
|
|
|
"""Hide this pane's progress bar.
|
|
|
|
|
|
|
|
|
|
The progress bar is in fact destroyed, so its state is lost.
|
|
|
|
|
|
|
|
|
|
This method is non-GTK thread safe, which means that any
|
|
|
|
|
thread can call it. If no progress bar is currently displayed,
|
|
|
|
|
it does nothing.
|
|
|
|
|
"""
|
|
|
|
|
if len(self._progress_box.get_children()):
|
|
|
|
|
gobject.idle_add(self._progress_box.remove,
|
|
|
|
|
self._progress_box.get_children()[0])
|
|
|
|
|
|
2010-11-22 11:48:29 +01:00
|
|
|
|
def clear(self, safe=None):
|
|
|
|
|
"""Clear all the pane children but internal widgets.
|
|
|
|
|
|
|
|
|
|
This method specializes Pane.clear so that the progress box is
|
|
|
|
|
also left untouched.
|
|
|
|
|
"""
|
|
|
|
|
if safe is None or not isinstance(safe, list):
|
|
|
|
|
safe = []
|
|
|
|
|
safe.append(self._progress_box)
|
|
|
|
|
Pane.clear(self, safe)
|
|
|
|
|
|
2010-11-16 16:05:36 +01:00
|
|
|
|
def enter(self):
|
|
|
|
|
"""Proceed to this pane initialization.
|
|
|
|
|
|
|
|
|
|
See Pane.enter. All ActivePane instances that override this
|
|
|
|
|
method should call thir parent one, since the background task
|
|
|
|
|
start is handled here.
|
|
|
|
|
"""
|
|
|
|
|
self._thread = threading.Thread(
|
|
|
|
|
name=self.__class__.__name__, target=self._wrapper)
|
|
|
|
|
self._thread.start()
|
|
|
|
|
|
|
|
|
|
def is_running(self):
|
|
|
|
|
"""Whether this pane background task is running."""
|
|
|
|
|
return not self._thread is None and self._thread.is_alive()
|
|
|
|
|
|
|
|
|
|
def _runner(self):
|
|
|
|
|
"""The background task.
|
|
|
|
|
|
|
|
|
|
All ActivePane instances should override this method to put
|
|
|
|
|
their behaviour. It is up to the developer to handle
|
|
|
|
|
synchronisation problems. Note in particular that all pane
|
|
|
|
|
drawing invoked from there should be done through some helper
|
|
|
|
|
like gobject.idle_add.
|
|
|
|
|
"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def _wrapper(self):
|
|
|
|
|
"""A internal wrapper around the pane task."""
|
|
|
|
|
gobject.idle_add(self._frame.lock)
|
|
|
|
|
self._runner()
|
|
|
|
|
gobject.idle_add(self._frame.unlock)
|
|
|
|
|
|
2010-11-21 11:20:49 +01:00
|
|
|
|
def _do_update_progress_bar(self, fraction, text):
|
|
|
|
|
"""An internal facility to update the progress bar.
|
|
|
|
|
|
|
|
|
|
This private method is intended to be called in the GTK main
|
|
|
|
|
thread.
|
|
|
|
|
"""
|
|
|
|
|
if not len(self._progress_box.get_children()):
|
|
|
|
|
self._progress_box.pack_start(gtk.ProgressBar())
|
|
|
|
|
self._progress_box.show_all()
|
|
|
|
|
bar = self._progress_box.get_children()[0]
|
|
|
|
|
if text:
|
|
|
|
|
bar.set_text(text)
|
|
|
|
|
bar.pulse() if fraction is None else bar.set_fraction(fraction)
|
|
|
|
|
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
class Frame(gtk.Dialog):
|
|
|
|
|
"""The Wizard window.
|
|
|
|
|
|
|
|
|
|
Each wizard application is built around exactly one Frame
|
|
|
|
|
instance. The frame is responsible to show the panes and to
|
|
|
|
|
forward the subject between them.
|
|
|
|
|
"""
|
|
|
|
|
|
2011-08-28 15:14:19 +02:00
|
|
|
|
def __init__(self, start_klass, subject=None, title='Wizard', min_size=(400, 200)):
|
2010-11-16 16:05:36 +01:00
|
|
|
|
"""Constructor.
|
|
|
|
|
|
|
|
|
|
A frame must be provided at least the initial pane class. If
|
|
|
|
|
no subject is set, then a (initially empty) string is provided
|
|
|
|
|
to the first pane.
|
|
|
|
|
"""
|
|
|
|
|
gtk.Dialog.__init__(self, title)
|
|
|
|
|
|
2010-11-17 13:25:48 +01:00
|
|
|
|
# Members are all defined before calling the First pane so as
|
|
|
|
|
# to be ready if the start pane constructor invokes the frame
|
|
|
|
|
# (such as the ready attribute setting)
|
|
|
|
|
self._locked = False
|
|
|
|
|
self._track = []
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
self._prev_button = gtk.Button('Previous')
|
|
|
|
|
self._prev_button.set_sensitive(False)
|
|
|
|
|
self._next_button = gtk.Button('Next')
|
|
|
|
|
self._next_button.set_sensitive(False)
|
2010-11-22 11:17:35 +01:00
|
|
|
|
self._quit_button = gtk.Button('Quit')
|
|
|
|
|
self._quit_button.set_sensitive(True)
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
self.set_default_size(min_size[0], min_size[1])
|
|
|
|
|
self.set_position(gtk.WIN_POS_CENTER)
|
|
|
|
|
self.has_separator = True
|
|
|
|
|
self.add_action_widget(self._prev_button, gtk.RESPONSE_REJECT)
|
|
|
|
|
self.add_action_widget(self._next_button, gtk.RESPONSE_ACCEPT)
|
2010-11-22 11:17:35 +01:00
|
|
|
|
self.add_action_widget(self._quit_button, gtk.RESPONSE_CLOSE)
|
2010-11-16 16:05:36 +01:00
|
|
|
|
self.connect('close', gtk.main_quit)
|
|
|
|
|
self.connect('destroy', gtk.main_quit)
|
|
|
|
|
self.connect("response", self._on_button)
|
|
|
|
|
|
2010-11-17 13:25:48 +01:00
|
|
|
|
start_pane = start_klass(self)
|
2011-08-28 15:14:19 +02:00
|
|
|
|
if not subject is None:
|
|
|
|
|
start_pane.subject = subject
|
2010-11-17 13:25:48 +01:00
|
|
|
|
self._track = [start_pane]
|
|
|
|
|
|
2010-11-16 16:05:36 +01:00
|
|
|
|
self._enter_pane(start_pane, True)
|
|
|
|
|
|
|
|
|
|
def lock(self):
|
|
|
|
|
"""Freeze the previous and next buttons.
|
|
|
|
|
|
|
|
|
|
This method is called by ActivePane instances when their
|
|
|
|
|
background task is started.
|
|
|
|
|
"""
|
2010-11-22 11:17:35 +01:00
|
|
|
|
self._locked = True
|
2010-11-17 13:25:48 +01:00
|
|
|
|
self.update()
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
def unlock(self):
|
|
|
|
|
"""Unfreeze the previous and next buttons.
|
|
|
|
|
|
|
|
|
|
This method is called by ActivePane instances when their
|
|
|
|
|
background task is terminated.
|
|
|
|
|
"""
|
2010-11-22 11:17:35 +01:00
|
|
|
|
self._locked = False
|
2010-11-17 13:25:48 +01:00
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
|
|
def update(self):
|
|
|
|
|
"""Update the frame buttons display."""
|
2010-11-22 11:17:35 +01:00
|
|
|
|
# Beware that this method can be called early, before the track
|
2010-11-17 13:25:48 +01:00
|
|
|
|
# contains the first pane
|
|
|
|
|
if len(self._track):
|
|
|
|
|
pane = self._track[-1]
|
|
|
|
|
self._prev_button.set_sensitive(
|
|
|
|
|
not self._locked and len(self._track) > 1)
|
|
|
|
|
self._next_button.set_sensitive(
|
|
|
|
|
not self._locked and pane.ready and not pane.is_terminal())
|
2010-11-22 11:17:35 +01:00
|
|
|
|
self._quit_button.set_sensitive(not self._locked)
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
def _on_button(self, w, response_id):
|
|
|
|
|
"""The frame buttons handler."""
|
|
|
|
|
if response_id == gtk.RESPONSE_REJECT:
|
|
|
|
|
if len(self._track) > 1:
|
|
|
|
|
self._exit_pane(self._track.pop(), False)
|
|
|
|
|
self._enter_pane(self._track[-1], False)
|
|
|
|
|
elif response_id == gtk.RESPONSE_ACCEPT:
|
|
|
|
|
p = self._track[-1].next()
|
|
|
|
|
if p:
|
|
|
|
|
self._exit_pane(self._track[-1], True)
|
|
|
|
|
p.subject = self._track[-1].subject
|
|
|
|
|
self._track.append(p)
|
|
|
|
|
self._enter_pane(p, True)
|
|
|
|
|
elif response_id == gtk.RESPONSE_CLOSE:
|
|
|
|
|
gobject.idle_add(gtk.main_quit)
|
|
|
|
|
|
|
|
|
|
def _enter_pane(self, pane, forward):
|
|
|
|
|
"""Handle a new pane display."""
|
|
|
|
|
if forward:
|
|
|
|
|
pane.enter()
|
2010-11-17 13:25:48 +01:00
|
|
|
|
self.update()
|
2010-11-16 16:05:36 +01:00
|
|
|
|
self.vbox.pack_start(self._track[-1], True, True)
|
|
|
|
|
self.vbox.show_all()
|
|
|
|
|
|
|
|
|
|
def _exit_pane(self, pane, forward):
|
|
|
|
|
"""Handle a pane exit."""
|
|
|
|
|
if forward:
|
|
|
|
|
pane.leave()
|
|
|
|
|
self.vbox.remove(pane)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AboutTestPane(Pane):
|
|
|
|
|
def __init__(self, frame):
|
|
|
|
|
Pane.__init__(self, frame)
|
|
|
|
|
|
|
|
|
|
self.link_to(InputTestPane)
|
|
|
|
|
|
2010-11-21 19:11:23 +01:00
|
|
|
|
self.pack_start(gtk.Label('A simple search tool.'), True, True, 10)
|
2010-11-16 16:05:36 +01:00
|
|
|
|
self.pack_start(
|
2010-11-21 19:11:23 +01:00
|
|
|
|
gtk.Label(
|
|
|
|
|
'Copyright 2010 David Soulayrol <david.soulayrol@gmail.com>'), False)
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
comment = gtk.Label()
|
|
|
|
|
comment.set_markup('This wizard tests the main features of wizpym, '
|
|
|
|
|
'which are:\n\n'
|
|
|
|
|
'• The handling of a flow of panes, each one having '
|
|
|
|
|
'any number of successors.\n'
|
|
|
|
|
'• A full history to go back the full track down to '
|
|
|
|
|
'the beginning and to keep the status of the reverted panes.\n'
|
|
|
|
|
'• Data transfer from one pane to the next one.\n'
|
|
|
|
|
'• The possiblity to do heavy stuff in background on '
|
|
|
|
|
'each pane.\n\n\n'
|
|
|
|
|
'This program comes with ABSOLUTELY NO WARRANTY. '
|
|
|
|
|
'This is free software and you are welcome to '
|
|
|
|
|
'redistribute it under certain conditions. See '
|
|
|
|
|
'<a href="http://www.gnu.org/licenses">GNU licenses</a> '
|
|
|
|
|
'for details.')
|
|
|
|
|
comment.set_line_wrap(True)
|
|
|
|
|
self.pack_start(comment, True, True, 10)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class InputTestPane(Pane):
|
|
|
|
|
def __init__(self, frame):
|
|
|
|
|
Pane.__init__(self, frame)
|
|
|
|
|
|
2010-11-21 19:11:23 +01:00
|
|
|
|
self.set_header('Please select your kind of search and fill in criteria fields.')
|
|
|
|
|
self.ready = False
|
|
|
|
|
|
|
|
|
|
self.link_to(GoogleSearchTestPane)
|
|
|
|
|
self.link_to(LocalSearchTestPane)
|
|
|
|
|
|
|
|
|
|
frame = gtk.Frame('Kind of search:')
|
|
|
|
|
frame.set_border_width(10)
|
|
|
|
|
self.pack_start(frame, padding=10)
|
|
|
|
|
vbox = gtk.VBox()
|
|
|
|
|
frame.add(vbox)
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
button = gtk.RadioButton(None, 'Search Google Code')
|
2010-11-21 19:11:23 +01:00
|
|
|
|
button.connect("toggled", self._on_button, GoogleSearchTestPane)
|
2010-11-16 16:05:36 +01:00
|
|
|
|
button.set_active(True)
|
2010-11-21 19:11:23 +01:00
|
|
|
|
vbox.pack_start(button)
|
|
|
|
|
|
|
|
|
|
button = gtk.RadioButton(button, "Search Local Files")
|
|
|
|
|
button.connect("toggled", self._on_button, LocalSearchTestPane)
|
|
|
|
|
vbox.pack_start(button)
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
box = gtk.HBox(False, 10)
|
2010-11-21 19:11:23 +01:00
|
|
|
|
box.set_border_width(10)
|
|
|
|
|
self._folder_entry = gtk.Entry()
|
|
|
|
|
self._folder_entry.connect('changed', self._on_entry_update)
|
|
|
|
|
box.pack_start(gtk.Label('Directory:'), False)
|
|
|
|
|
box.pack_start(self._folder_entry)
|
|
|
|
|
vbox.pack_start(box)
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
2010-11-21 19:11:23 +01:00
|
|
|
|
box = gtk.HBox(False, 10)
|
|
|
|
|
box.set_border_width(10)
|
|
|
|
|
self._keywords_entry = gtk.Entry()
|
|
|
|
|
self._keywords_entry.connect('changed', self._on_entry_update)
|
|
|
|
|
box.pack_start(gtk.Label('Keywords:'), False)
|
|
|
|
|
box.pack_start(self._keywords_entry)
|
|
|
|
|
self.pack_start(box)
|
|
|
|
|
|
|
|
|
|
self._update_status()
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
def leave(self):
|
2010-11-21 19:11:23 +01:00
|
|
|
|
self.subject[0] = self._keywords_entry.get_text()
|
|
|
|
|
self.subject[1] = self._folder_entry.get_text()
|
|
|
|
|
|
|
|
|
|
def _on_entry_update(self, w):
|
|
|
|
|
self._update_status()
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
def _on_button(self, w, klass):
|
|
|
|
|
if w.get_active():
|
|
|
|
|
self.switch(klass)
|
2010-11-21 19:11:23 +01:00
|
|
|
|
self._update_status()
|
|
|
|
|
|
|
|
|
|
def _update_status(self):
|
|
|
|
|
r = len(self._keywords_entry.get_text())
|
|
|
|
|
e = None if r else 'Please fill in the keywords field'
|
|
|
|
|
|
|
|
|
|
if isinstance(self.next(), LocalSearchTestPane):
|
|
|
|
|
if not len(self._folder_entry.get_text()):
|
|
|
|
|
r = False
|
|
|
|
|
e = 'Local search needs a root folder'
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
os.stat(self._folder_entry.get_text())
|
|
|
|
|
except OSError:
|
|
|
|
|
r = False
|
|
|
|
|
e = self._folder_entry.get_text() + ' is not a valid path'
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
2010-11-21 19:11:23 +01:00
|
|
|
|
self.set_error(e)
|
|
|
|
|
self.ready = r
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
2010-11-21 19:11:23 +01:00
|
|
|
|
|
|
|
|
|
class GoogleSearchTestPane(ActivePane):
|
2010-11-16 16:05:36 +01:00
|
|
|
|
PATH = 'https://www.google.com/codesearch/feeds/search?'
|
|
|
|
|
XMLNS_ATOM = '{http://www.w3.org/2005/Atom}'
|
|
|
|
|
XMLNS_GCS = '{http://schemas.google.com/codesearch/2006}'
|
|
|
|
|
|
|
|
|
|
def __init__(self, frame):
|
|
|
|
|
ActivePane.__init__(self, frame)
|
|
|
|
|
|
|
|
|
|
def enter(self):
|
2010-11-21 19:11:23 +01:00
|
|
|
|
self.set_header('Google Code results for: "' + self.subject[0] + '"')
|
2010-11-22 11:48:29 +01:00
|
|
|
|
self.clear()
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
# Start thread
|
|
|
|
|
ActivePane.enter(self)
|
|
|
|
|
|
|
|
|
|
def _runner(self):
|
2010-11-21 11:20:49 +01:00
|
|
|
|
timer = gobject.timeout_add(100, lambda: self.update_progress_bar())
|
|
|
|
|
self.update_progress_bar(text='Searching...')
|
2010-11-21 19:11:23 +01:00
|
|
|
|
c = httplib.HTTPSConnection('www.google.com')
|
|
|
|
|
c.request('GET', self.PATH + urllib.urlencode({'q': self.subject[0]}),
|
|
|
|
|
None, {'GData-Version': '2'})
|
2010-11-21 11:20:49 +01:00
|
|
|
|
self.hide_progress_bar()
|
2010-11-21 19:11:23 +01:00
|
|
|
|
gobject.source_remove(timer)
|
2010-11-16 16:05:36 +01:00
|
|
|
|
gobject.idle_add(self._show_response, c.getresponse())
|
|
|
|
|
|
|
|
|
|
def _show_response(self, r):
|
2010-11-22 11:48:29 +01:00
|
|
|
|
lbl = None
|
2010-11-16 16:05:36 +01:00
|
|
|
|
if r.status != 200:
|
2010-11-22 11:48:29 +01:00
|
|
|
|
lbl = gtk.Label('Got error (' + r.reason + ') from google')
|
|
|
|
|
lbl.show()
|
|
|
|
|
self.pack_start(lbl)
|
2010-11-16 16:05:36 +01:00
|
|
|
|
else:
|
|
|
|
|
tree = etree.fromstring(r.read())
|
|
|
|
|
for e in tree.findall(self.XMLNS_ATOM + 'entry'):
|
2010-11-22 11:48:29 +01:00
|
|
|
|
lbl = gtk.Label(e.find(self.XMLNS_GCS + 'package').get('name'))
|
|
|
|
|
lbl.show()
|
|
|
|
|
self.pack_start(lbl)
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
|
2010-11-21 19:11:23 +01:00
|
|
|
|
class LocalSearchTestPane(ActivePane):
|
2010-11-16 16:05:36 +01:00
|
|
|
|
def __init__(self, frame):
|
2010-11-21 19:11:23 +01:00
|
|
|
|
ActivePane.__init__(self, frame)
|
|
|
|
|
|
|
|
|
|
sw = gtk.ScrolledWindow()
|
|
|
|
|
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
|
|
|
|
textview = gtk.TextView()
|
|
|
|
|
textview.set_editable(False)
|
|
|
|
|
sw.add(textview)
|
|
|
|
|
self.pack_start(sw)
|
|
|
|
|
|
|
|
|
|
self._buffer = textview.get_buffer()
|
|
|
|
|
|
|
|
|
|
def enter(self):
|
|
|
|
|
self.set_header(
|
|
|
|
|
'Grep results for: "' + self.subject[0].split()[0] + '" in ' + self.subject[1])
|
|
|
|
|
|
|
|
|
|
# Start thread
|
|
|
|
|
ActivePane.enter(self)
|
|
|
|
|
|
|
|
|
|
def _runner(self):
|
|
|
|
|
timer = gobject.timeout_add(100, lambda: self.update_progress_bar())
|
|
|
|
|
self.update_progress_bar(0, 'Counting files...')
|
|
|
|
|
nb_files = 0
|
2010-11-22 12:03:00 +01:00
|
|
|
|
for root, dirs, files in os.walk(self.subject[1]):
|
2010-11-21 19:11:23 +01:00
|
|
|
|
self._filter_directories(dirs)
|
|
|
|
|
nb_files += len(files)
|
|
|
|
|
gobject.source_remove(timer)
|
|
|
|
|
|
|
|
|
|
# Tweak so that the division below operates on floats.
|
|
|
|
|
count = float(0)
|
|
|
|
|
item = self.subject[0].split()[0]
|
2010-11-22 12:03:00 +01:00
|
|
|
|
for root, dirs, files in os.walk(self.subject[1]):
|
2010-11-21 19:11:23 +01:00
|
|
|
|
self._filter_directories(dirs)
|
|
|
|
|
for f in files:
|
|
|
|
|
name = os.path.join(root, f)
|
2010-11-22 12:03:00 +01:00
|
|
|
|
p = subprocess.Popen(['grep', '-iHn', item, name],
|
2010-11-21 19:11:23 +01:00
|
|
|
|
stdout=subprocess.PIPE)
|
2010-11-22 12:03:00 +01:00
|
|
|
|
gobject.idle_add(self._buffer.insert_at_cursor, p.communicate()[0])
|
2010-11-21 19:11:23 +01:00
|
|
|
|
self.update_progress_bar(
|
|
|
|
|
count / nb_files,
|
|
|
|
|
name + '(' + str(int(count)) + '/' + str(nb_files) + ')')
|
|
|
|
|
count += 1
|
|
|
|
|
self.hide_progress_bar()
|
|
|
|
|
|
|
|
|
|
def _filter_directories(self, dirs):
|
|
|
|
|
for f in ['CVS', '.svn', '.git']:
|
|
|
|
|
if f in dirs:
|
|
|
|
|
dirs.remove(f)
|
2010-11-16 16:05:36 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2010-11-21 19:11:23 +01:00
|
|
|
|
import httplib, os, subprocess, urllib
|
2010-11-16 16:05:36 +01:00
|
|
|
|
import xml.etree.ElementTree as etree
|
|
|
|
|
|
|
|
|
|
gobject.threads_init()
|
2010-11-21 19:11:23 +01:00
|
|
|
|
Frame(AboutTestPane, ['', '']).run()
|
2010-11-16 16:05:36 +01:00
|
|
|
|
gtk.main()
|
|
|
|
|
|