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