Browse Source
pygtk file; created a update_box object too, to make it easier to set the avatar and other things.master
Julio Biason
15 years ago
3 changed files with 305 additions and 264 deletions
@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
# ---------------------------------------------------------------------- |
||||
# Threading related objects. |
||||
# These classes are based on the code available at http://gist.github.com/51686 |
||||
# (c) 2008, John Stowers <john.stowers@gmail.com> |
||||
# ---------------------------------------------------------------------- |
||||
|
||||
import gobject |
||||
import threading |
||||
import logging |
||||
|
||||
_log = logging.getLogger('helper.gtk_threads') |
||||
|
||||
# ---------------------------------------------------------------------- |
||||
class _IdleObject(gobject.GObject): |
||||
""" |
||||
Override gobject.GObject to always emit signals in the main thread |
||||
by emmitting on an idle handler |
||||
""" |
||||
|
||||
def __init__(self): |
||||
gobject.GObject.__init__(self) |
||||
|
||||
def emit(self, *args): |
||||
gobject.idle_add(gobject.GObject.emit, self, *args) |
||||
|
||||
# ---------------------------------------------------------------------- |
||||
class _WorkerThread(threading.Thread, _IdleObject): |
||||
""" |
||||
A single working thread. |
||||
""" |
||||
__gsignals__ = { |
||||
"completed": ( |
||||
gobject.SIGNAL_RUN_LAST, |
||||
gobject.TYPE_NONE, |
||||
(gobject.TYPE_PYOBJECT, )), # list/networkdata |
||||
"exception": ( |
||||
gobject.SIGNAL_RUN_LAST, |
||||
gobject.TYPE_NONE, |
||||
(gobject.TYPE_PYOBJECT, ))} # The exception |
||||
|
||||
def __init__(self, function, *args, **kwargs): |
||||
threading.Thread.__init__(self) |
||||
_IdleObject.__init__(self) |
||||
self._function = function |
||||
self._args = args |
||||
self._kwargs = kwargs |
||||
|
||||
def run(self): |
||||
# call the function |
||||
_log.debug('Thread d %s calling %s', self.getName(), |
||||
str(self._function.__name__)) |
||||
|
||||
args = self._args |
||||
kwargs = self._kwargs |
||||
|
||||
try: |
||||
result = self._function(*args, **kwargs) |
||||
except Exception, exc: # Catch ALL exceptions |
||||
# TODO: Check if this catch all warnins too! |
||||
_log.debug('Exception inside thread: %s', str(exc)) |
||||
self.emit("exception", exc) |
||||
return |
||||
|
||||
_log.debug('Thread id %s completed', self.getName()) |
||||
|
||||
self.emit("completed", result) |
||||
return |
||||
|
||||
# ---------------------------------------------------------------------- |
||||
class ThreadManager(object): |
||||
"""Manages the threads.""" |
||||
|
||||
def __init__(self, max_threads=2): |
||||
"""Start the thread pool. The number of threads in the pool is defined |
||||
by `pool_size`, defaults to 2.""" |
||||
self._max_threads = max_threads |
||||
self._thread_pool = [] |
||||
self._running = [] |
||||
self._thread_id = 0 |
||||
|
||||
return |
||||
|
||||
def _remove_thread(self, widget, arg=None): |
||||
"""Called when the thread completes. We remove it from the thread list |
||||
(dictionary, actually) and start the next thread (if there is one).""" |
||||
|
||||
# not actually a widget. It's the object that emitted the signal, in |
||||
# this case, the _WorkerThread object. |
||||
thread_id = widget.getName() |
||||
self._running.remove(thread_id) |
||||
|
||||
_log.debug('Thread id %s completed, %d threads in the queue, ' \ |
||||
'%d still running', thread_id, len(self._thread_pool), |
||||
len(self._running)) |
||||
|
||||
if self._thread_pool: |
||||
if len(self._running) < self._max_threads: |
||||
next = self._thread_pool.pop() |
||||
_log.debug('Dequeuing thread %s', next.getName()) |
||||
self._running.append(next.getName()) |
||||
next.start() |
||||
|
||||
return |
||||
|
||||
def add_work(self, complete_cb, exception_cb, func, *args, **kwargs): |
||||
"""Add a work to the thread list. `complete_cb` is the function to be |
||||
called with the result of the work. `exception_cb` is the function to |
||||
be called if there are any exceptions raised. Note that, once the |
||||
work is complete, one of those will be called, not both. `func` is the |
||||
function to be called in the secondary threads. `args` and `kwargs` |
||||
are parameters passed to the function.""" |
||||
|
||||
thread = _WorkerThread(func, *args, **kwargs) |
||||
thread_id = '%s-%s' % (self._thread_id, func.__name__) |
||||
|
||||
thread.connect('completed', complete_cb) |
||||
thread.connect('completed', self._remove_thread) |
||||
thread.connect('exception', exception_cb) |
||||
thread.connect('exception', self._remove_thread) |
||||
thread.setName(thread_id) |
||||
|
||||
if len(self._running) < self._max_threads: |
||||
# immediatelly start the thread |
||||
self._running.append(thread_id) |
||||
thread.start() |
||||
else: |
||||
# add the thread to the queue |
||||
running_names = ', '.join(self._running) |
||||
_log.debug('Threads %s running, adding %s to the queue', |
||||
running_names, thread_id) |
||||
self._thread_pool.append(thread) |
||||
|
||||
self._thread_id += 1 |
||||
return |
||||
|
||||
def clear(self): |
||||
"""Clear the thread pool list. This will cause the manager to stop |
||||
working after the threads finish.""" |
||||
self._thread_pool = [] |
||||
self._running = [] |
||||
return |
||||
|
@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
# Mitter, a multiple-interface client for microblogging services. |
||||
# Copyright (C) 2007-2010 Mitter Contributors |
||||
# |
||||
# This program 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/>. |
||||
|
||||
import gtk |
||||
import logging |
||||
|
||||
_log = logging.getLogger('ui.pygtk.updatebox') |
||||
|
||||
class UpdateBox(gtk.VBox): |
||||
"""Custom update box widget.""" |
||||
def __init__(self, avatar, spell_check=True): |
||||
super(UpdateBox, self).__init__(False, 0) |
||||
|
||||
self._reply_to = None |
||||
|
||||
self._text = gtk.TextView() |
||||
self._text.set_property('wrap-mode', gtk.WRAP_WORD) |
||||
|
||||
self.avatar = gtk.Image() |
||||
self.avatar.set_from_pixbuf(avatar) |
||||
|
||||
status_bar = gtk.HBox(False, 0) |
||||
self._update_status = gtk.Label() |
||||
|
||||
close_button = gtk.Button(stock=gtk.STOCK_CLOSE) |
||||
close_button.set_relief(gtk.RELIEF_NONE) |
||||
close_button.connect('clicked', self.hide) |
||||
|
||||
status_bar = gtk.HBox(False, 0) |
||||
status_bar.pack_start(self._update_status, expand=True, fill=True, |
||||
padding=0) |
||||
status_bar.pack_start(close_button, expand=False, fill=False, |
||||
padding=0) |
||||
|
||||
update_bar = gtk.HBox(False, 3) |
||||
update_bar.pack_start(self._text, expand=True, fill=True, |
||||
padding=0) |
||||
update_bar.pack_start(self.avatar, expand=False, fill=False, |
||||
padding=0) |
||||
|
||||
self.pack_start(status_bar, expand=False, fill=True, padding=0) |
||||
self.pack_start(update_bar, expand=False, fill=True, padding=0) |
||||
|
||||
# Spell checking the update box |
||||
if spell_check: |
||||
try: |
||||
import gtkspell |
||||
import locale |
||||
language = locale.getlocale()[0] |
||||
self.spell_check = gtkspell.Spell(self._update_text, language) |
||||
_log.debug('Spell checking turned on with language: %s' \ |
||||
% (language)) |
||||
except: |
||||
_log.debug('Error initializing spell checking: ' \ |
||||
'spell checking disabled') |
||||
|
||||
def show(self, reply_to=None): |
||||
"""Show the update box and all related widgets.""" |
||||
super(UpdateBox, self).show() |
||||
self._reply_to = reply_to |
||||
self.show_all() |
||||
_log.debug('show') |
||||
return |
||||
|
||||
def hide(self, caller=None): |
||||
"""Hide the update box and related widgets.""" |
||||
super(UpdateBox, self).hide() |
||||
self.text = '' |
||||
self.hide_all() |
||||
return |
||||
|
||||
def _count_chars(self, text_buffer): |
||||
"""Count the number of chars in the edit field and update the |
||||
label that shows the available space.""" |
||||
count = len(self.text) |
||||
|
||||
if self._reply_to: |
||||
suffix = _('(replying to %s)') % ( |
||||
self._reply_to) |
||||
else: |
||||
suffix = '' |
||||
|
||||
# TODO: gettext to properly use "characters"/"character" |
||||
text = N_('%d character %s', '%d characters %s', count) % (count, |
||||
suffix) |
||||
self._update_status.set_text(text) |
||||
return True |
||||
|
||||
@property |
||||
def text(self): |
||||
"""Return the text in the update box.""" |
||||
text_buffer = self._text.get_buffer() |
||||
|
||||
start = text_buffer.get_start_iter() |
||||
end = text_buffer.get_end_iter() |
||||
|
||||
return text_buffer.get_text(start, end, include_hidden_chars=False) |
||||
|
||||
@text.setter |
||||
def text(self, text): |
||||
"""Set the textarea text.""" |
||||
self._text.get_buffer().set_text(text) |
||||
return |
||||
|
||||
def _set_pixbuf(self, pixbuf): |
||||
"""Update the avatar pixbuf.""" |
||||
self.avatar.set_from_pixbuf(pixbuf) |
||||
# pixbuf can only be set, not get. |
||||
pixbuf = property(None, _set_pixbuf) |
Loading…
Reference in new issue