source: trunk/uigtk.py @ 309

Revision 309, 62.9 KB checked in by marc, 9 years ago (diff)

Improved spec file, removed unused images from installation, fixed a bug with secure images not being shown in ntotifications, bumped changelog, and improved debian/rules build on converting images

  • Property svn:keywords set to Id Rev
Line 
1#! /usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Itaka is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3 of the License, or
7# any later version.
8#
9# Itaka is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with Itaka; if not, write to the Free Software
16# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17#
18# Copyright 2003-2009 Marc E.
19# http://itaka.jardinpresente.com.ar
20#
21# $Id$
22
23""" Itaka GTK+ GUI """
24
25import copy
26import datetime
27import os
28import sys
29import signal
30import traceback
31
32try:
33    from twisted.internet import gtk2reactor
34    try:
35        gtk2reactor.install()
36    except Exception, e:
37        print_e(_('Could not initiate GTK modules: %s' % (e)))
38        sys.exit(1)
39    from twisted.internet import reactor
40except ImportError:
41    print_e(_('Could not import Twisted Network Framework'))
42    traceback.print_exc()
43    sys.exit(1)
44
45try:
46    import server as iserver
47    import error
48except ImportError:
49    print_e(_('Failed to import Itaka modules'))
50    traceback.print_exc()
51    sys.exit(1)
52
53try:
54    import pygtk
55    pygtk.require("2.0")
56except ImportError:
57    print_w(_('Pygtk module is missing'))
58    pass
59try:
60    import gtk, gobject, pango
61except ImportError:
62    print_e(_('GTK+ bindings are missing'))
63    sys.exit(1)
64
65if gtk.gtk_version[1] < 10:
66    print_e(_('Itaka requires GTK+ 2.10, you have %s installed' % (".".join(str(x) for x in gtk.gtk_version))))
67    sys.exit(1)
68
69class GuiLog:
70    """
71    GTK+ GUI logging handler
72    """
73
74    def __init__(self, gui_instance, console, configuration):
75        """
76        Constructor
77
78        @type gui_instance: instance
79        @param gui_instance: Instance of L{Gui}
80
81        @type console: instance
82        @param console: Instance of L{Console}
83
84        @type configuration: dict
85        @param configuration: Configuration values dictionary from L{ConfigParser}
86        """
87
88        self.gui = gui_instance
89        self.console = console
90        self.configuration = configuration
91
92    def twisted_observer(self, args):
93        """
94        A log observer for our Twisted server
95
96        Interestingly it carries a time timestamp.
97        datetime.datetime.fromtimestamp(args['time']).strftime("%b %d %H:%M:%S")
98
99        @type args: dict
100        @param args: dict {'key': [str(message)]}
101        """
102       
103        # Handle twisted errors
104        # 'isError': 1, 'failure': <twisted.python.failure.Failure <type 'exceptions.AttributeError'>>
105        if args.has_key('isError') and args['isError'] == 1:
106            self.msg = str(args['failure'])
107        else:
108            self.msg = args['message'][0]
109
110        self._write_server_log(self.msg, False)
111
112    def message(self, message, icon=None):
113        """
114        Write normal message on Gui log widgets
115
116        @type message: str
117        @param message: Message to be inserted
118
119        @type icon: tuple
120        @param icon: The first argument is a string of either 'stock' or 'pixbuf', and the second is a string of gtk.STOCK_ICON or a gtk.gdk.pixbuf object (without the 'gtk.' prefix)
121        """
122     
123        if self.configuration['log']['logtimeformat']:
124            message = "%s %s" % (datetime.datetime.now().strftime(self.configuration['log']['logtimeformat']), message)
125
126        self.console.message(message)
127        self._write_gui_log(message, None, icon, False)
128
129    def verbose_message(self, message, detailed_message, icon=None):
130        """
131        Write detailed message on Gui log widgets
132
133        @type message: str
134        @param message: Message to be inserted in the events log
135
136        @type detailed_message: str
137        @param detailed_message: Message to be inserted in the detailed log
138
139        @type icon: tuple
140        @param icon: The first argument is a string of either 'stock' or 'pixbuf', and the second is a string of gtk.STOCK_ICON or a gtk.gdk.pixbuf object (without the 'gtk.' prefix)
141        """
142
143        if self.configuration['log']['logtimeformat']:
144            message = "%s %s" % (datetime.datetime.now().strftime(self.configuration['log']['logtimeformat']), message)
145            detailed_message = "%s %s" % (datetime.datetime.now().strftime(self.configuration['log']['logtimeformat']), detailed_message)
146
147        self.console.message(detailed_message)
148        self._write_gui_log(message, detailed_message, icon, False, False)
149
150    def failure(self, caller, message, failure_type='ERROR'):
151        """
152        Write failure message on Gui log widgets
153
154        @type caller: tuple
155        @param caller: Specifies the class and method were the warning ocurred
156
157        @type message: tuple
158        @param message: A tuple containing first the simple message to the events log, and then the detailed message for the detailed log
159
160        @type failure_type: str
161        @param failure_type: What kind of failure it is, either 'ERROR' (default), 'WARNING' or 'DEBUG'
162        """
163       
164        self.simple_message = message[0]
165        self.detailed_message = message[1]
166
167        if self.configuration['log']['logtimeformat']:
168            self.simple_message = "%s %s" % (datetime.datetime.now().strftime(self.configuration['log']['logtimeformat']), self.simple_message)
169            self.detailed_message = "%s %s" % (datetime.datetime.now().strftime(self.configuration['log']['logtimeformat']), self.detailed_message)
170
171        self.console.failure(caller, self.detailed_message, failure_type)
172
173        # Errors require some more actions
174        if failure_type == 'ERROR':
175            # Show the window and its widgets, set the status icon blinking timeout
176            if not self.gui.window.get_property("visible"):
177                self.gui.window.present()
178                self.gui.status_icon_timeout_blink()
179                self.gui.window.move(self.gui.window_position[0], self.gui.window_position[1])
180
181            if not self.gui.expander.get_property("sensitive"):
182                self.gui.expander.set_sensitive(True)
183            if not self.gui.expander.get_property("expanded"):
184                self.gui.expander.set_expanded(True)
185           
186            # Stop the server
187            if self.gui.server.listening():
188                self.gui.stop_server(None, True)
189
190        self._write_gui_log(self.simple_message, self.detailed_message, self._get_failure_icon(failure_type), True, True)
191
192    def _get_failure_icon(self, failure_type):
193        """
194        Return a default stock icon for a failure type
195
196        @type failure_type: str
197        @param failure_type: What kind of failure it is, either 'ERROR' (default), 'WARNING' or 'DEBUG'
198
199        @rtype: tuple
200        @return: An GTK+ stock icon definition. ['stock', 'STOCK_ICON']
201        """
202        # Default icon is always STOCK_DIALOG_ERROR
203        icon = ['stock', 'STOCK_DIALOG_ERROR']
204       
205        if failure_type == "WARNING":
206            icon = ['stock', 'STOCK_DIALOG_WARNING']
207        elif failure_type == "DEBUG":
208            icon = ['stock', 'STOCK_DIALOG_INFO']
209
210        return icon
211
212    def _write_gui_log(self, message, detailed_message=None, icon=None, unpause_logger=True, failure=False):
213        """
214        Private method to write to both Gui logs
215
216        @type message: str
217        @param message: Message to be inserted
218
219        @type detailed_message: str
220        @param detailed_message: Optional detailed message if the event log and detailed log messages differ
221
222        @type icon: tuple
223        @param icon: The first argument is a string of either 'stock' or 'pixbuf', and the second is a string of gtk.STOCK_ICON or a gtk.gdk.pixbuf object (without the 'gtk.' prefix)
224
225        @type unpause_logger: bool
226        @param unpause_logger: Whether to unpause the GUI Logger
227
228        @type failure: bool
229        @param failure: Whether the message is a failure or not
230        """
231
232        if detailed_message is None:
233            detailed_message = message
234
235        # Only write messages when the logging is unpaused. Unless we are told otherwise
236        if self.gui.log_paused():
237            if unpause_logger:
238                self.gui.unpause_log(True)
239                self._write_events_log(message, icon, failure)
240                self._write_detailed_log(detailed_message)
241        else:
242            self._write_events_log(message, icon, failure)
243            self._write_detailed_log(detailed_message)
244
245    def _write_events_log(self, message, icon=None, failure=False):
246        """
247        Private method to write to the events log Gui widget
248
249        @type message: str
250        @param message: Message to be inserted
251
252        @type icon: tuple
253        @param icon: The first argument is a string of either 'stock' or 'pixbuf', and the second is a string of gtk.STOCK_ICON or a gtk.gdk.pixbuf object (without the 'gtk.' prefix) if its stock, or a pixbuf
254
255        @type failure: bool
256        @param failure: Whether the message is a failure or not
257        """
258
259        if icon is not None:
260            if icon[0] == "stock":
261                self.inserted_iter = self.gui.log_events_store.append([self.gui.log_events_tree_view.render_icon(stock_id=getattr(gtk, icon[1]), size=gtk.ICON_SIZE_MENU, detail=None), message])
262                # Select the iter if it's a failure
263                if failure:
264                    self.selection = self.gui.log_events_tree_view.get_selection()
265                    self.selection.select_iter(self.inserted_iter)
266            else:
267                self.inserted_iter = self.gui.log_events_store.append([icon[1], message])
268        else:
269            self.inserted_iter = self.gui.log_events_store.append([icon, message])
270
271        # Scroll
272        self.gui.log_events_tree_view.scroll_to_cell(self.gui.log_events_tree_view.get_model().get_path(self.inserted_iter))
273
274    def _write_detailed_log(self, message, bold=True):
275        """
276        Private method to write to the detailed log Gui widget
277
278        @type message: str
279        @param message: Message to be inserted
280
281        @type bold: bool
282        @param bold: Whether the text will be inserted as bold
283        """
284
285        message = message + '\r'
286
287        if bold:
288            self.gui.log_details_buffer.insert_with_tags_by_name(self.gui.log_details_buffer.get_end_iter(), message, 'bold-text')
289        else:
290            self.gui.log_details_buffer.insert_at_cursor(message, len(message))
291
292        # Automatically scroll. Use wrap until fix
293        self.gui.log_details_text_view.scroll_mark_onscreen(self.gui.log_details_buffer.get_insert())
294
295    def _write_server_log(self, message, bold=True):
296        """
297        Private method to write to the server log Gui widget
298
299        @type message: str
300        @param message: Message to be inserted
301
302        @type bold: bool
303        @param bold: Whether the text will be inserted as bold
304        """
305
306        message = message + '\r'
307
308        if bold:
309            self.gui.log_server_buffer.insert_with_tags_by_name(self.gui.log_server_buffer.get_end_iter(), message, 'bold-text')
310        else:
311            self.gui.log_server_buffer.insert_at_cursor(message, len(message))
312
313        # Automatically scroll. Use wrap until fix
314        self.gui.log_server_text_view.scroll_mark_onscreen(self.gui.log_server_buffer.get_insert())
315
316class Gui:
317    """
318    GTK+ GUI
319    """
320
321    def __init__(self, console_instance, configuration):
322        """
323        Constructor
324
325        @type console_instance: instance
326        @param console_instance: An instance of the L{Console} class
327
328        @type configuration: tuple
329        @param configuration: A tuple of configuration globals and an instance of L{ConfigParser}
330        """
331
332        # Load our configuration and console instances and values
333        self.console = console_instance
334        self.itaka_globals = configuration[0]
335
336        # The configuration instance has the user's preferences already loaded
337        self.config_instance = configuration[1]
338        self.configuration = self.itaka_globals.configuration_values
339
340        # Instances of our Gui Logging class and Screenshot Server
341        self.server = iserver.ScreenshotServer(self)
342        self.log = GuiLog(self, self.console, self.configuration)
343        self.log_is_paused = False
344
345        # Start defining widgets
346        self.icon_pixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka.png'))
347        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
348        self.window.connect('destroy', self._destroy)
349        self.window.connect('size-allocate', self._window_size_changed)
350        self.window.set_title('Itaka')
351        self.window.set_icon(self.icon_pixbuf)
352        self.window.set_border_width(6)
353        self.window.set_default_size(425, 1)
354        self.window.set_position(gtk.WIN_POS_CENTER)
355        self.window_position = self.window.get_position()
356
357        # Create our tray icon
358        self.status_icon = gtk.StatusIcon()
359        self.status_menu = gtk.Menu()
360
361        if self.configuration['server']['authentication']:
362            self.status_icon.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka-secure.png')))
363        else:
364            self.status_icon.set_from_pixbuf(self.icon_pixbuf)
365
366        self.status_icon.set_tooltip('Itaka')
367        self.status_icon.set_visible(True)
368        self.status_icon.connect('activate', self.status_icon_activate)
369        self.status_icon.connect('popup-menu', self.status_icon_menu, self.status_menu)
370
371        self.start_image = gtk.Image()
372        self.start_image.set_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
373
374        self.stop_image = gtk.Image()
375        self.stop_image.set_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU)
376        self.menu_item_start = gtk.ImageMenuItem(_('St_art'))
377        self.menu_item_start.set_image(self.start_image)
378        self.menu_item_start.connect('activate', self.start_server, True)
379        self.menu_item_stop = gtk.ImageMenuItem(_('St_op'))
380        self.menu_item_stop.set_image(self.stop_image)
381        self.menu_item_stop.connect('activate', self.stop_server, True)
382        self.menu_item_stop.set_sensitive(False)
383
384        if self.itaka_globals.notify_available:
385            self.menu_item_notifications = gtk.CheckMenuItem(_('Show _Notifications'))
386            if self.configuration['server']['notify']:
387                self.menu_item_notifications.set_active(True)
388            self.menu_item_notifications.connect('toggled', self.status_icon_notify)
389
390        self.menu_item_separator = gtk.SeparatorMenuItem()
391        self.menu_item_separator1 = gtk.SeparatorMenuItem()
392        self.menu_item_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT)
393        self.menu_item_quit.connect('activate', self._destroy)
394
395        self.status_menu.append(self.menu_item_start)
396        self.status_menu.append(self.menu_item_stop)
397
398        if self.itaka_globals.notify_available:
399            self.status_menu.append(self.menu_item_separator)
400            self.status_menu.append(self.menu_item_notifications)
401
402        self.status_menu.append(self.menu_item_separator1)
403        self.status_menu.append(self.menu_item_quit)
404
405        self.vbox = gtk.VBox(False, 6)
406        self.box = gtk.HBox(False, 0)
407
408        self.itaka_logo = gtk.Image()
409
410        if self.configuration['server']['authentication']:
411            self.itaka_logo.set_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka-secure.png'))
412        else:
413            self.itaka_logo.set_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka.png'))
414
415        self.itaka_logo.show()
416
417        self.box.pack_start(self.itaka_logo, False, False, 35)
418
419        self.button_start_stop = gtk.ToggleButton(_('Start'))
420        self.start_stop_image = gtk.Image()
421
422        self.start_stop_image.set_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON)
423        self.button_start_stop.set_image(self.start_stop_image)
424        self.button_start_stop.connect('toggled', self.button_start_server)
425
426        self.button_preferences = gtk.Button(_('Preferences'), gtk.STOCK_PREFERENCES)
427        self.button_preferences.connect('clicked', self._expand_preferences)
428
429        # Set up some variables for our timeouts/animations
430        self.preferences_hidden = False
431        self.preferences_expanded = False
432        self.timeout_contract = None
433        self.timeout_expand = None
434        self.timeout_blink = None
435
436        self.box.pack_start(self.button_start_stop, True, True, 5)
437        self.box.pack_start(self.button_preferences, True, True, 8)
438
439        self.vbox.pack_start(self.box, False, False, 0)
440
441        self.hbox_status = gtk.HBox(False, 0)
442        self.label_served = gtk.Label()
443        self.label_last_ip = gtk.Label()
444        self.label_time = gtk.Label()
445
446        self.hbox_status.pack_start(self.label_last_ip, True, False, 0)
447        self.hbox_status.pack_start(self.label_time, True, False, 0)
448        self.hbox_status.pack_start(self.label_served, True, False, 0)
449
450        # Logger widget (displayed when expanded)
451        self.vbox_log = gtk.VBox(False, 0)
452        self.notebook_log = gtk.Notebook()
453        self.notebook_log.set_tab_pos(gtk.POS_BOTTOM)
454
455        self.label_log_events = gtk.Label(_('Events'))
456        self.label_log_details = gtk.Label(_('Details'))
457        self.label_log_server = gtk.Label(_('Server'))
458
459        self.scroll_log_events = gtk.ScrolledWindow()
460        self.scroll_log_events.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
461        self.scroll_log_events.set_shadow_type(gtk.SHADOW_NONE)
462
463        self.log_events_store = gtk.ListStore(gtk.gdk.Pixbuf, str)
464        self.log_events_tree_view = gtk.TreeView(self.log_events_store)
465        self.log_events_tree_view.set_property('headers-visible', False)
466        self.log_events_tree_view.set_property('rules-hint', True)
467
468        self.column_log_events_icon = gtk.TreeViewColumn()
469        self.column_log_events = gtk.TreeViewColumn()
470        self.log_events_tree_view.append_column(self.column_log_events_icon)
471        self.log_events_tree_view.append_column(self.column_log_events)
472
473        self.cell_pixbuf_log_events = gtk.CellRendererPixbuf()
474        self.column_log_events_icon.pack_start(self.cell_pixbuf_log_events)
475        self.column_log_events_icon.add_attribute(self.cell_pixbuf_log_events, 'pixbuf', 0)
476
477        self.cell_text_log_events = gtk.CellRendererText()
478        self.column_log_events.pack_start(self.cell_text_log_events, True)
479        self.column_log_events.add_attribute(self.cell_text_log_events, 'text', 1)
480        self.scroll_log_events.add(self.log_events_tree_view)
481
482        self.scroll_log_details = gtk.ScrolledWindow()
483        self.scroll_log_details.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
484        self.scroll_log_details.set_shadow_type(gtk.SHADOW_NONE)
485        self.log_details_text_view = gtk.TextView()
486        self.log_details_text_view.set_wrap_mode(gtk.WRAP_WORD)
487        self.log_details_text_view.set_editable(False)
488        self.log_details_text_view.set_size_request(-1, 160)
489        self.log_details_buffer = self.log_details_text_view.get_buffer()
490        self.log_details_buffer.create_tag ('bold-text', weight = pango.WEIGHT_BOLD)
491        self.scroll_log_details.add(self.log_details_text_view)
492
493        self.scroll_log_server = gtk.ScrolledWindow()
494        self.scroll_log_server.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
495        self.scroll_log_server.set_shadow_type(gtk.SHADOW_NONE)
496        self.log_server_text_view = gtk.TextView()
497        self.log_server_text_view.set_wrap_mode(gtk.WRAP_WORD)
498        self.log_server_text_view.set_editable(False)
499        self.log_server_text_view.set_size_request(-1, 160)
500        self.log_server_buffer = self.log_server_text_view.get_buffer()
501        self.log_server_buffer.create_tag ('bold-text', weight = pango.WEIGHT_BOLD)
502        self.scroll_log_server.add(self.log_server_text_view)
503
504        self.notebook_log.append_page(self.scroll_log_events, self.label_log_events)
505        self.notebook_log.append_page(self.scroll_log_details, self.label_log_details)
506        self.notebook_log.append_page(self.scroll_log_server, self.label_log_server)
507
508        self.hbox_log = gtk.HBox(False, 0)
509        self.button_log_clear = gtk.Button(_('Clear'))
510        self.button_log_clear_image = gtk.Image()
511        self.button_log_clear_image.set_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_BUTTON)
512        self.button_log_clear.set_image(self.button_log_clear_image)
513        self.button_log_clear.connect('clicked', self._clear_logger)
514
515        self.button_log_pause = gtk.ToggleButton(_('Pause'))
516        self.button_log_pause_image = gtk.Image()
517        self.button_log_pause_image.set_from_stock(gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_BUTTON)
518        self.button_log_pause.set_image(self.button_log_pause_image)
519        self.button_log_pause.connect('toggled', self.button_pause_log)
520
521        self.button_log_save = gtk.Button(_('Save'), gtk.STOCK_SAVE)
522        #self.button_log_save.connect('toggled', self._save_log)
523
524        self.hbox_log.pack_start(self.button_log_clear, False, False, 4)
525        self.hbox_log.pack_start(self.button_log_pause, False, False, 4)
526        self.hbox_log.pack_end(self.button_log_save, False, False, 4)
527
528        self.vbox_log.pack_start(self.notebook_log, False, False, 4)
529        self.vbox_log.pack_start(self.hbox_log, False, False, 4)
530
531        self.label_log_box = gtk.Label(_('<b>Server log</b>'))
532        self.label_log_box.set_use_markup(True)
533
534        self.expander_size_finalized = False
535        self.expander = gtk.Expander(None)
536        self.expander.set_label_widget(self.label_log_box)
537        self.expander.connect('notify::expanded', self._expand_logger)
538
539        self.vbox.pack_start(self.hbox_status, False, False, 4)
540        self.vbox.pack_start(self.expander, False, False, 0)
541        self.expander.set_sensitive(False)
542
543        # This is are the preference widgets that are going to be added and shown later
544        self.vbox_preferences = gtk.VBox(False, 7)
545        self.vbox_preferences_items = gtk.VBox(False, 5)
546        self.vbox_preferences_items.set_border_width(2)
547       
548        # Create our Hboxes
549        for n in xrange(1, 12+1):
550            setattr(self, 'hbox_preferences_%d' % (n), gtk.HBox(False, 0))
551
552        self.frame_preference_settings = gtk.Frame()
553        self.label_preferences_settings = gtk.Label(_('<b>Preferences</b>'))
554        self.label_preferences_settings.set_use_markup(True)
555        self.frame_preference_settings.set_label_widget(self.label_preferences_settings)
556        self.frame_preference_settings.set_label_align(0.5, 0.5)
557
558        self.label_preferences_port = gtk.Label(_('Port'))
559        self.label_preferences_port.set_justify(gtk.JUSTIFY_LEFT)
560        self.label_preferences_port.set_alignment(0, 0.60)
561
562        self.label_preferences_auth = gtk.Label(_('Authentication'))
563        self.label_preferences_auth.set_justify(gtk.JUSTIFY_LEFT)
564        self.label_preferences_auth.set_alignment(0, 0.60)
565
566        self.label_preferences_user = gtk.Label(_('Username'))
567        self.label_preferences_user.set_justify(gtk.JUSTIFY_LEFT)
568        self.label_preferences_user.set_alignment(0, 0.60)
569
570        self.label_preferences_pass = gtk.Label(_('Password'))
571        self.label_preferences_pass.set_justify(gtk.JUSTIFY_LEFT)
572        self.label_preferences_pass.set_alignment(0, 0.60)
573
574        self.label_preferences_format = gtk.Label(_('Format'))
575        self.label_preferences_format.set_justify(gtk.JUSTIFY_LEFT)
576        self.label_preferences_format.set_alignment(0, 0.50)
577
578        self.label_preferences_quality = gtk.Label(_('Quality'))
579        self.label_preferences_quality.set_justify(gtk.JUSTIFY_LEFT)
580        self.label_preferences_quality.set_alignment(0, 0.50)
581
582        self.label_preferences_scale = gtk.Label(_('Scale'))
583        self.label_preferences_scale.set_justify(gtk.JUSTIFY_LEFT)
584        self.label_preferences_scale.set_alignment(0, 0.50)
585
586        if not self.itaka_globals.system == 'nt':
587            self.label_preferences_screenshot = gtk.Label(_('Window'))
588            self.label_preferences_screenshot.set_justify(gtk.JUSTIFY_LEFT)
589            self.label_preferences_screenshot.set_alignment(0, 0.50)
590
591        if self.itaka_globals.notify_available:
592            self.label_preferences_notifications = gtk.Label(_('Desktop notifications'))
593            self.label_preferences_notifications.set_justify(gtk.JUSTIFY_LEFT)
594            self.label_preferences_notifications.set_alignment(0, 0.50)
595           
596        self.label_preferences_notifications_sound = gtk.Label(_('Audio notifications'))
597        self.label_preferences_notifications_sound.set_justify(gtk.JUSTIFY_LEFT)
598        self.label_preferences_notifications_sound.set_alignment(0, 0.50)
599
600        self.label_preferences_timestamp = gtk.Label(_('Show timestamp in log'))
601        self.label_preferences_timestamp.set_justify(gtk.JUSTIFY_LEFT)
602        self.label_preferences_timestamp.set_alignment(0, 0.50)
603           
604        self.adjustment_preferences_port = gtk.Adjustment(float(self.configuration['server']['port']), 1024, 65535, 1, 0, 0)
605        self.spin_preferences_port = gtk.SpinButton(self.adjustment_preferences_port)
606        self.spin_preferences_port.set_numeric(True)
607
608        self.entry_preferences_user = gtk.Entry()
609        self.entry_preferences_user.set_width_chars(11)
610        self.entry_preferences_user.set_text(self.configuration['server']['username'])
611
612        self.entry_preferences_pass = gtk.Entry()
613        self.entry_preferences_pass.set_width_chars(11)
614
615        char = u'\u25cf'
616        if self.itaka_globals.system == 'nt':
617            char = '*'
618
619        self.entry_preferences_pass.set_invisible_char(char)
620        self.entry_preferences_pass.set_visibility(False)
621        self.entry_preferences_pass.set_text(self.configuration['server']['password'])
622
623        self.check_preferences_auth = gtk.CheckButton()
624        self.check_preferences_auth.connect('toggled', self._preferences_authentication_toggled)
625
626        if self.configuration['server']['authentication']:
627            self.check_preferences_auth.set_active(1)
628        else:
629            self.check_preferences_auth.set_active(0)
630
631        if not self.configuration['server']['authentication']:
632            self.entry_preferences_user.set_sensitive(False)
633            self.entry_preferences_pass.set_sensitive(False)
634
635        self.adjustment_preferences_quality = gtk.Adjustment(float(self.configuration['screenshot']['quality']), 0, 100, 1, 0, 0)
636        self.spin_preferences_quality = gtk.SpinButton(self.adjustment_preferences_quality)
637        self.spin_preferences_quality.set_numeric(True)
638
639        self.adjustment_preferences_scale = gtk.Adjustment(float(self.configuration['screenshot']['scalepercent']), 1, 100, 1, 0, 0)
640        self.spin_preferences_scale = gtk.SpinButton(self.adjustment_preferences_scale)
641        self.spin_preferences_scale.set_numeric(True)
642
643        self.combo_preferences_format = gtk.combo_box_new_text()
644        self.combo_preferences_format.connect('changed', self._preferences_combo_changed)
645        self.combo_preferences_format.append_text('JPG')
646        self.combo_preferences_format.append_text('PNG')
647
648        if self.configuration['screenshot']['format'] == 'jpeg':
649            self.combo_preferences_format.set_active(0)
650        else:
651            self.combo_preferences_format.set_active(1)
652            self.hbox_preferences_6.set_sensitive(False)
653
654        if self.itaka_globals.notify_available:
655            self.check_preferences_notifications = gtk.CheckButton()
656            if self.configuration['server']['notify']:
657                self.check_preferences_notifications.set_active(1)
658            else:
659                self.check_preferences_notifications.set_active(0)
660
661        self.check_preferences_timestamp = gtk.CheckButton()
662##        if self.configuration['log']['timestamp']:
663##            self.check_preferences_notifications.set_active(1)
664##        else:
665##            self.check_preferences_notifications.set_active(0)
666
667        self.check_preferences_notifications_sound = gtk.CheckButton()
668##        if self.configuration['log']['timestamp']:
669##            self.check_preferences_notifications.set_active(1)
670##        else:
671##            self.check_preferences_notifications.set_active(0)
672
673        if not self.itaka_globals.system == 'nt':
674            self.combo_preferences_screenshot = gtk.combo_box_new_text()
675            self.combo_preferences_screenshot.append_text(_('Fullscreen'))
676            self.combo_preferences_screenshot.append_text(_('Active window'))
677            if self.configuration['screenshot']['currentwindow']:
678                self.combo_preferences_screenshot.set_active(1)
679            else:
680                self.combo_preferences_screenshot.set_active(0)
681
682        self.button_preferences_close = gtk.Button('Close', gtk.STOCK_SAVE)
683        self.button_preferences_close.connect('clicked', self._contract_preferences)
684       
685        self.button_preferences_about = gtk.Button('About', gtk.STOCK_ABOUT)
686        self.button_preferences_about.connect('clicked', self.about)
687
688        self.hbox_preferences_1.pack_start(self.label_preferences_port, False, False, 12)
689        self.hbox_preferences_1.pack_end(self.spin_preferences_port, False, False, 7)
690        self.hbox_preferences_2.pack_start(self.label_preferences_auth, False, False, 12)
691        self.hbox_preferences_3.pack_start(self.label_preferences_user, False, False, 12)
692        self.hbox_preferences_4.pack_end(self.entry_preferences_pass, False, False, 7)
693        self.hbox_preferences_4.pack_start(self.label_preferences_pass, False, False, 12)
694        self.hbox_preferences_3.pack_end(self.entry_preferences_user, False, False, 7)
695        self.hbox_preferences_2.pack_end(self.check_preferences_auth, False, False, 7)
696        self.hbox_preferences_5.pack_start(self.label_preferences_format, False, False, 12)
697        self.hbox_preferences_5.pack_end(self.combo_preferences_format, False, False, 7)
698        self.hbox_preferences_6.pack_start(self.label_preferences_quality, False, False, 12)
699        self.hbox_preferences_6.pack_end(self.spin_preferences_quality, False, False, 7)
700
701        if not self.itaka_globals.system == 'nt':
702            self.hbox_preferences_7.pack_start(self.label_preferences_screenshot, False, False, 12)
703            self.hbox_preferences_7.pack_end(self.combo_preferences_screenshot, False, False, 7)
704
705        self.hbox_preferences_8.pack_start(self.label_preferences_scale, False, False, 12)
706        self.hbox_preferences_8.pack_end(self.spin_preferences_scale, False, False, 7)
707
708        if self.itaka_globals.notify_available:
709            self.hbox_preferences_9.pack_start(self.label_preferences_notifications, False, False, 12)
710            self.hbox_preferences_9.pack_end(self.check_preferences_notifications, False, False, 7)
711
712        self.hbox_preferences_10.pack_start(self.label_preferences_notifications_sound, False, False, 12)
713        self.hbox_preferences_10.pack_end(self.check_preferences_notifications_sound, False, False, 7)
714
715        self.hbox_preferences_11.pack_start(self.label_preferences_timestamp, False, False, 12)
716        self.hbox_preferences_11.pack_end(self.check_preferences_timestamp, False, False, 7)
717
718        self.hbox_preferences_12.pack_start(self.button_preferences_about, False, False, 7)
719        self.hbox_preferences_12.pack_end(self.button_preferences_close, False, False, 7)
720
721        self.vbox_preferences_items.pack_start(self.hbox_preferences_1, False, False, 0)
722        self.vbox_preferences_items.pack_start(self.hbox_preferences_2, False, False, 0)
723        self.vbox_preferences_items.pack_start(self.hbox_preferences_3, False, False, 0)
724        self.vbox_preferences_items.pack_start(self.hbox_preferences_4, False, False, 0)
725        self.vbox_preferences_items.pack_start(self.hbox_preferences_5, False, False, 0)
726        self.vbox_preferences_items.pack_start(self.hbox_preferences_6, False, False, 0)
727
728        if not self.itaka_globals.system == 'nt':
729            self.vbox_preferences_items.pack_start(self.hbox_preferences_7, False, False, 0)
730
731        self.vbox_preferences_items.pack_start(self.hbox_preferences_8, False, False, 0)
732
733        if self.itaka_globals.notify_available:
734            self.vbox_preferences_items.pack_start(self.hbox_preferences_9, False, False, 0)
735
736        self.vbox_preferences_items.pack_start(self.hbox_preferences_10, False, False, 0)
737        self.vbox_preferences_items.pack_start(self.hbox_preferences_11, False, False, 0)
738
739        self.frame_preference_settings.add(self.vbox_preferences_items)
740        self.vbox_preferences.pack_start(self.frame_preference_settings, False, False, 0)
741        self.vbox_preferences.pack_start(self.hbox_preferences_12, False, False, 4)
742
743        self.window.add(self.vbox)
744        self.window.show_all()
745
746        # Once we have all our widgets shown, get the 'initial' real size, for expanding/contracting
747        self.window.initial_size = self.window.get_size()
748
749    def _save_preferences(self):
750        """
751        Saves and hides the preferences dialog
752        """
753       
754        # So we can mess with the values in the running one and not mess up our comparison
755        self.current_configuration = copy.deepcopy(self.configuration)
756
757        # Switch to the proper values
758        self.format_value = str(self.combo_preferences_format.get_active_text())
759
760        if self.format_value == 'PNG':
761            self.format_value = 'png'
762            self.configuration['screenshot']['format'] = 'png'
763        else:
764            self.format_value = 'jpeg'
765            self.configuration['screenshot']['format'] = 'jpeg'
766
767        # Delete stale old screenshot
768        if (self.current_configuration['screenshot']['format'] != self.configuration['screenshot']['format']):
769            if os.path.exists(os.path.join(self.current_configuration['screenshot']['path'], 'itakashot.%s' % (self.current_configuration['screenshot']['format']))):
770                os.remove(os.path.join(self.current_configuration['screenshot']['path'], 'itakashot.%s' % (self.current_configuration['screenshot']['format'])))
771                if self.itaka_globals.console_verbosity['debug']: print_m(_("Deleting stale screenshot file '%s'" % ((os.path.join(self.current_configuration['screenshot']['path'], 'itakashot.%s' % (self.current_configuration['screenshot']['format']))))))
772
773
774        if self.itaka_globals.notify_available:
775            self.notify_value = self.check_preferences_notifications.get_active()
776
777            if self.notify_value:
778                self.notify_value = True
779                self.menu_item_notifications.set_active(True)
780                self.configuration['server']['notify'] = True
781            else:
782                self.notify_value = False
783                self.menu_item_notifications.set_active(False)
784                self.configuration['server']['notify'] = False
785        else:
786            self.notify_value = False
787            self.configuration['server']['notify'] = False
788
789        if not self.itaka_globals.system == 'nt':
790            if self.combo_preferences_screenshot.get_active_text() == _('Active window'):
791                self.configuration['screenshot']['currentwindow'] = True
792                self.screenshot_value = True
793            else:
794                self.configuration['screenshot']['currentwindow'] = False
795                self.screenshot_value = False
796        else:
797            self.screenshot_value = False
798            self.configuration['screenshot']['currentwindow'] = False
799
800        self.scale_value = [self.spin_preferences_scale.get_value_as_int()]
801
802        if self.scale_value[0] == 100:
803            self.configuration['screenshot']['scale'] = False
804            self.scale_value.append(False)
805        else:
806            self.configuration['screenshot']['scale'] = True
807            self.scale_value.append(True)
808
809        if self.configuration['screenshot']['scalepercent'] != self.scale_value[0]:
810            self.configuration['screenshot']['scalepercent'] = self.scale_value[0]
811       
812        # Build a configuration dictionary to send to the configuration engine's
813        # save method. Redundant values must be included for the comparison
814        self.configuration_dict = {
815            'html':
816                {'html': '<img src="screenshot" alt="If you are seeing this message it means there was an error in Itaka or you are using a text-only browser.">',
817                'authfailure': '<p><strong>Sorry, but you cannot access this resource without authorization.</strong></p>'},
818               
819            'screenshot':
820                {'path': self.configuration['screenshot']['path'],
821                'format': self.format_value,
822                'quality': self.spin_preferences_quality.get_value_as_int(),
823                'currentwindow': self.screenshot_value,
824                'scale': self.scale_value[1],
825                'scalepercent': self.scale_value[0]},
826
827            'log':
828                {'logtimeformat': '[%d/%b/%Y %H:%M:%S]',
829                'logfile': '~/.itaka/access.log'},
830
831            'server':
832                {'username': self.entry_preferences_user.get_text(),
833                'authentication': self.check_preferences_auth.get_active(),
834                'notify': self.notify_value,
835                'password': self.entry_preferences_pass.get_text(),
836                'port': self.spin_preferences_port.get_value_as_int()}
837            }
838
839        # Set them for local use now
840        if self.configuration['screenshot']['quality'] != self.spin_preferences_quality.get_value_as_int():
841            self.configuration['screenshot']['quality'] = self.spin_preferences_quality.get_value_as_int()
842
843        if self.configuration['server']['port'] !=  self.spin_preferences_port.get_value_as_int():
844            self.configuration['server']['port'] =  self.spin_preferences_port.get_value_as_int()
845            self.restart_server()
846
847        if self.configuration['server']['authentication'] is not self.check_preferences_auth.get_active():
848            self.configuration['server']['authentication'] = self.check_preferences_auth.get_active()
849
850        if self.configuration['server']['username'] != self.entry_preferences_user.get_text():
851            self.configuration['server']['username'] = self.entry_preferences_user.get_text()
852
853        if self.configuration['server']['password'] != self.entry_preferences_pass.get_text():
854            self.configuration['server']['password'] = self.entry_preferences_pass.get_text()
855
856        # Check if the configuration changed
857        if (self.configuration_dict != self.current_configuration):
858
859            # Update the needed keys
860            try:
861                # self.config_instance.save(self.configuration_dict)
862                for section in self.configuration_dict:
863                    [self.config_instance.update(section, key, value) for key, value in self.configuration_dict[section].iteritems() if key not in self.current_configuration[section] or self.current_configuration[section][key] != value]
864            except:
865                self.log.failure(('Gui', '_save_preferences'), _('Could not save preferences'), 'ERROR')
866
867    def _expand_preferences(self, *args):
868        """
869        Expands the window for preferences
870        """
871
872        # We have a race condition here. If GTK cant resize fast enough, then it gets very sluggish
873        # See configure-event signal of gtk.Widget
874        # start timer, resize, catch configure-notify, set up idle handler, when idle resize to what the size should be at this point of time, repeat
875        if not self.preferences_expanded:
876            # Close log in small screen displays
877            if ((gtk.gdk.screen_height() < self.itaka_globals.min_screen_height) and self.expander.get_property("expanded")):
878                self.expander.set_expanded(False)
879
880            if self.timeout_expand is not None:
881                """NOTE: GTK+ GtkWidget.size_request() method can give you the amount of size a widget will take
882                however, it has to be show()ned before. For our little hack, we show the vbox_preferences widgets
883                but not itself, which should yield a close enough calculation."""
884                self.frame_preference_settings.show_all()
885                self.hbox_preferences_10.show_all()
886
887                """If the logger is expanded, use that as the initial size.
888                _expander_size is set by our GtkWindow resize callback
889                but we also set a expander_size_finalized variable here
890                so that _window_size_changed doesnt set the new expanded_size over
891                again as our window is expanding here."""
892               
893                self.expander_size_finalized = False
894                if self.expander.get_expanded():
895                    self.window.normal_size = self.expander_size
896                    self.expander_size_finalized = True
897                else:
898                    self.window.normal_size = self.window.initial_size
899
900                self.increment = 33
901                if self.window.current_size[1] < self.window.normal_size[1]+self.vbox_preferences.size_request()[1]:
902                    # Avoid overexpanding our calculation
903                    if self.window.current_size[1]+self.increment > self.window.normal_size[1]+self.vbox_preferences.size_request()[1]:
904                        self.increment = (self.window.normal_size[1]+self.vbox_preferences.size_request()[1] - self.window.current_size[1])
905
906                    self.window.resize(self.window.current_size[0], self.window.current_size[1]+self.increment)
907                    return True
908                else:
909                    # Its done expanding, add our widgets or display it if it has been done already
910                    self.button_preferences.set_sensitive(False)
911                    self.preferences_expanded = True
912
913                    # Reload our configuration and show the preferences
914                    self.configuration = self.config_instance.load()
915                    if self.preferences_hidden:
916                        self.vbox_preferences.show_all()
917                    else:
918                        self.vbox.pack_start(self.vbox_preferences, False, False, 0)
919                        self.vbox_preferences.show_all()
920                   
921                    self.timeout_expand = None
922                    return False
923            else:
924                self.timeout_expand = gobject.timeout_add(30, self._expand_preferences)
925
926    def _contract_preferences(self, save):
927        """
928        Contracts the window of preferences
929
930        @type args: unknown
931        @param args: gtk.Widget or "False" if you don't want to save preferences, just hide the pane
932        """
933
934        if self.timeout_contract is not None:
935            # If you dont use the normal_size proxy to our window sizes,
936            # it generates a nice effect of doing the animation when closing the expander also.
937            # While sexy, it's inconsistent, and most definately a resource hungry bug
938            if self.expander.get_expanded():
939                self.window.normal_size = self.expander_size
940                self.expander_size_finalized = True
941            else:
942                self.window.normal_size = self.window.initial_size
943           
944            if self.vbox_preferences.get_property("visible"):
945                self.vbox_preferences.hide_all()
946
947            if self.window.current_size[1] > self.window.normal_size[1]:
948                self.window.resize(self.window.current_size[0], self.window.current_size[1]-self.increment)
949                return True
950            else:
951                # Done, set some variables and stop our timer
952                self.preferences_expanded = False
953                self.preferences_hidden = True
954                self.expander.size_finalized = False
955                self.button_preferences.set_sensitive(True)
956               
957                # Save our settings (but not if we are on small displays and we just want to hide the pane)
958                if save is not False:
959                    self._save_preferences()
960
961                self.timeout_contract = None
962                return False
963        else:
964            self.timeout_contract = gobject.timeout_add(30, self._contract_preferences, save)
965
966    def _window_size_changed(self, widget=None, data=None):
967        """
968        Report the window size on change
969       
970        @type widget: instance
971        @param widget: gtk.Widget
972
973        @type data: unknown
974        @param data: Unknown
975        """
976       
977        self.window.current_size = self.window.get_size()
978       
979        # If the logger is expanded, give them a new size unless our preferences expander is working
980        if self.expander.get_expanded() and not self.expander_size_finalized:
981            self.expander_size = self.window.current_size
982            # If the preferences were expanded before the logger
983            if self.preferences_expanded:
984                # Cant assign tuple items
985                self.expander_size = [self.expander_size[0], self.expander_size[1] - self.vbox_preferences.size_request()[1]]
986
987    def status_icon_menu(self, widget, button, time, menu):
988        """
989        Display the menu on the status icon
990       
991        @type widget: instance
992        @param widget: gtk.Widget
993
994        @type button: int
995        @param button: The button pressed.
996
997        @type time: unknown
998        @param time: Unknown
999
1000        @type menu: instance
1001        @param menu: A gtk.Menu instance
1002        """
1003
1004        if button == 3:
1005            if menu:
1006                menu.show_all()
1007                menu.popup(None, None, None, 3, time)
1008
1009    def status_icon_timeout_blink(self, time=3000):
1010        """
1011        Sets the timeout in miliseconds to blink and stop blinking the status icon
1012       
1013        @type time: int
1014        @param time: Time in milliseconds to blink the status icon
1015        """
1016
1017        if self.timeout_blink is None:
1018            self.status_icon.set_blinking(True)
1019            self.timeout_blink = gobject.timeout_add(time, self.status_icon_timeout_blink)
1020        else:
1021            self.status_icon.set_blinking(False)
1022            self.timeout_blink = None
1023            return False
1024 
1025    def status_icon_activate(self, widget):
1026        """
1027        Toggle the window visibility from the status icon when clicked
1028       
1029        @type widget: instance
1030        @param widget: gtk.Widget
1031        """
1032
1033        if self.window.get_property("visible"):
1034            # Save it for when we undock because of errors
1035            self.window_position = self.window.get_position()
1036            self.window.hide()
1037        else:
1038            self.window.show()
1039
1040    def status_icon_notify(self, widget):
1041        """
1042        Disable or enable notifications on the fly from the status icon
1043       
1044        @type widget: instance
1045        @param widget: gtk.Widget
1046        """
1047
1048        if self.check_widget(self.menu_item_notifications):
1049            self.configuration['server']['notify'] = True
1050        else:
1051            self.configuration['server']['notify'] = False
1052
1053    def about(self, *args):
1054        """
1055        Creates the about dialog
1056        """
1057
1058        self.about_dialog = gtk.AboutDialog()
1059        self.about_dialog.set_transient_for(self.window)
1060        self.about_dialog.set_name('Itaka')
1061        self.about_dialog.set_version(self.itaka_globals.__version__)
1062        self.about_dialog.set_copyright(u'© 2003-2009 Marc E.')
1063        self.about_dialog.set_comments('Screenshooting de mercado.')
1064        self.about_dialog.set_authors(['Marc E. <santusmarc@users.sourceforge.net>', 'Kurt Erickson <psychogenicshk@users.sourceforge.net>'])
1065        self.about_dialog.set_artists(['Marc E. <santusmarc@users.sourceforge.net>', 'Tango Project (http://tango.freedesktop.org)'])
1066        self.about_dialog.set_license('''Itaka is free software; you can redistribute it and/or modify
1067it under the terms of the GNU General Public License as published by
1068the Free Software Foundation; either version 3 of the License, or
1069any later version.
1070
1071Itaka is distributed in the hope that it will be useful,
1072but WITHOUT ANY WARRANTY; without even the implied warranty of
1073MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1074GNU General Public License for more details.
1075
1076You should have received a copy of the GNU General Public License
1077along with Itaka; if not, write to the Free Software
1078Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA''')
1079        self.about_dialog.set_website('http://itaka.jardinpresente.com.ar')
1080        self.about_dialog.set_website_label('Itaka website')
1081        gtk.about_dialog_set_url_hook(None)
1082        self.about_dialog.set_logo(gtk.gdk.pixbuf_new_from_file(os.path.join(self.itaka_globals.image_dir, "itaka64x64.png")))
1083        self.about_dialog.set_icon(self.icon_pixbuf)
1084        self.about_dialog.run()
1085        self.about_dialog.destroy()
1086
1087    def _expand_logger(self, expander, params):
1088        """
1089        Expand or contract the logger
1090       
1091        @type expander: instance
1092        @param expander: gtk.Expander instance
1093
1094        @type params: unknown
1095        @param params: Unknown
1096        """
1097
1098        if self.expander.get_expanded():
1099            # Close preferences pane in small screen displays
1100            if ((gtk.gdk.screen_height() < self.itaka_globals.min_screen_height) and self.preferences_expanded):
1101                # Tell it to not save
1102                self._contract_preferences(False)
1103
1104            # Show the debugvbox() and it's subwidgets
1105            self.vbox_log.show_all()
1106
1107            self.expander.add(self.vbox_log)
1108        else:
1109            self.expander.remove(self.expander.child)
1110            self.window.resize(self.window.initial_size[0], self.window.initial_size[1])
1111        return
1112
1113    def _clear_logger(self, *args):
1114        """
1115        Clear the log
1116        """
1117
1118        self.log_events_store.clear()
1119        self.log_details_buffer.set_text("")
1120        self.log_server_buffer.set_text("")
1121
1122    def button_pause_log(self, widget):
1123        """
1124        Interface to pause or unpause the Gui logger by checking the status of a gtk.ToggleButton
1125       
1126        @type widget: instance
1127        @param widget: gtk.Widget
1128        """
1129
1130        if self.check_widget(widget):
1131            if not self.log_paused():
1132                self.pause_log()
1133        else:
1134            if self.log_paused():
1135                self.unpause_log()
1136
1137    def pause_log(self):
1138        """
1139        Pause Gui log output
1140        """
1141
1142        self.log_events_store.append([self.log_events_tree_view.render_icon(stock_id=gtk.STOCK_MEDIA_PAUSE, size=gtk.ICON_SIZE_MENU, detail=None), _('%s Logging paused' %(datetime.datetime.now().strftime(self.configuration['log']['logtimeformat'])))])
1143       
1144        self.log_events_tree_view.set_sensitive(False)
1145        self.log_details_text_view.set_sensitive(False)
1146
1147        self.server.remove_log_observer()
1148        self.log_is_paused = True
1149
1150    def unpause_log(self, foreign=False):
1151        """
1152        Unpause Gui log output
1153
1154        @type foreign: bool
1155        @param foreign: Whether the caller of this method is not the Gui gtk.ToggleButton
1156        """
1157
1158        self.server.add_log_observer(self.log.twisted_observer)
1159        if (foreign):
1160            self.button_log_pause.set_active(False)
1161           
1162        self.log_events_tree_view.set_sensitive(True)
1163        self.log_details_text_view.set_sensitive(True)
1164        self.log_server_text_view.set_sensitive(True)
1165
1166        self.log_events_store.append([self.log_events_tree_view.render_icon(stock_id=gtk.STOCK_MEDIA_PLAY, size=gtk.ICON_SIZE_MENU, detail=None), _('%s Logging resumed' %(datetime.datetime.now().strftime(self.configuration['log']['logtimeformat'])))])
1167
1168        self.log_is_paused = False
1169
1170    def log_paused(self):
1171        """
1172        Whether the Gui log is paused
1173
1174        @rtype: bool
1175        @return: True if the Gui log is paused
1176        """
1177       
1178        return self.log_is_paused
1179
1180    def main(self):
1181        """
1182        Main initiation function. Starts the Twisted GUI reactors
1183        """
1184
1185        # Run the gtk.main() loop and start a reactor for our server.
1186        # False can be passed for it to not register to it's SIGINT handler.
1187        # Register our own handler to send to _destroy
1188        signal.signal(signal.SIGINT, self._destroy)
1189        reactor.run()
1190       
1191    def _preferences_combo_changed(self, widget):
1192        """
1193        Callback for preferenes gtk.ComboBox widget
1194
1195        @type widget: instance
1196        @param widget: gtk.Widget
1197        """
1198       
1199        if self.combo_preferences_format.get_active_text() == 'PNG':
1200            self.hbox_preferences_6.set_sensitive(False)
1201        else:
1202            self.hbox_preferences_6.set_sensitive(True)
1203
1204    def _preferences_authentication_toggled(self, widget):
1205        """
1206        Callback for preferences gtk.CheckButton widget
1207
1208        @type widget: instance
1209        @param widget: gtk.Widget
1210        """
1211
1212        if self.check_widget(widget):
1213            self.entry_preferences_user.set_sensitive(True)
1214            self.entry_preferences_pass.set_sensitive(True)
1215            self.itaka_logo.set_from_file(os.path.join(self.itaka_globals.image_dir, "itaka-secure.png"))
1216            self.status_icon.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file(os.path.join(self.itaka_globals.image_dir, "itaka-secure.png")))
1217        else:
1218            self.entry_preferences_user.set_sensitive(False)
1219            self.entry_preferences_pass.set_sensitive(False)
1220            self.itaka_logo.set_from_file(os.path.join(self.itaka_globals.image_dir, "itaka.png"))
1221            self.status_icon.set_from_pixbuf(self.icon_pixbuf)
1222
1223
1224    def check_widget(self, widget):
1225        """
1226        Checks if a gtk.Widget is active
1227
1228        @type widget: instance
1229        @param widget: gtk.Widget
1230        """
1231
1232        if hasattr(widget, 'get_active') and callable(getattr(widget, 'get_active')):
1233            return widget.get_active()
1234        else:
1235            return False
1236
1237    def button_start_server(self, widget):
1238        """
1239        Interface to start or stop the server by checking the status of a gtk.ToggleButton
1240       
1241        @type widget: instance
1242        @param widget: gtk.Widget
1243        """
1244
1245        if self.check_widget(widget):
1246            self.start_server()
1247        else:
1248            self.stop_server()
1249
1250    def start_server(self, widget=None, foreign=False):
1251        """
1252        Starts the Twisted server
1253
1254        @type widget: instance
1255        @param widget: gtk.Widget
1256
1257        @type foreign: bool
1258        @param foreign: Whether the caller of this method is not self.button_start_stop
1259
1260        """
1261
1262        if self.server.listening(): return
1263
1264        try:
1265            self.server.start_server(reactor, self.configuration['server']['port'])
1266        except error.ItakaServerCannotListenError, e:
1267            self.log.failure(('Gui', 'start_server'), (_('Failed to start server on port %(port)s: %(errorstring)s' % {'port': e.value.port, 'errorstring': e.value.args[-1][-1]}), _('Failed to start server: %s') % e.value), 'ERROR')
1268            self.button_start_stop.set_active(False)
1269            return
1270
1271        self.server.add_log_observer(self.log.twisted_observer)
1272
1273        if self.configuration['server']['authentication']:
1274            serverstock = 'STOCK_DIALOG_AUTHENTICATION'
1275            serverstring = _('Secure server')
1276        else:
1277            serverstock = 'STOCK_CONNECT'
1278            serverstring = _('Server')
1279
1280        if self.configuration['screenshot']['format'] == "jpeg":
1281            self.log.verbose_message(_('%(serverstring)s started on port %(port)d') % {'serverstring': serverstring, 'port': self.configuration['server']['port']}, _('%(serverstring)s started on port %(port)s TCP. Serving %(format)s images with %(quality)d%% quality') % {'serverstring': serverstring, 'port': self.configuration['server']['port'], 'format': self.configuration['screenshot']['format'].upper(), 'quality': self.configuration['screenshot']['quality']}, ['stock', serverstock])
1282        else:
1283            self.log.verbose_message(_('%(serverstring)s started on port %(port)d') % {'serverstring': serverstring, 'port': self.configuration['server']['port']}, _('%(serverstring)s started on port %(port)s TCP. Serving %(format)s images') % {'serverstring': serverstring, 'port': self.configuration['server']['port'], 'format': self.configuration['screenshot']['format'].upper()}, ['stock', serverstock])
1284
1285        # Change buttons
1286        if foreign:
1287            self.button_start_stop.set_active(True)
1288        self.button_start_stop.set_label(_('Stop'))
1289        self.start_stop_image.set_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_BUTTON)
1290        self.button_start_stop.set_image(self.start_stop_image)
1291
1292        self.status_icon.set_tooltip(_('Itaka - Server running'))
1293        self.menu_item_start.set_sensitive(False)
1294        self.menu_item_stop.set_sensitive(True)
1295
1296        if not self.expander.get_property("sensitive"):
1297            self.expander.set_sensitive(True)
1298        if not self.expander.get_property("expanded"):
1299            self.expander.set_expanded(True)
1300
1301    def stop_server(self, widget=None, foreign=False):
1302        """
1303        Stops the Twisted server
1304
1305        @type widget: instance
1306        @param widget: gtk.Widget
1307
1308        @type foreign: bool
1309        @param foreign: Whether the caller of this method is not self.button_start_stop
1310        """
1311
1312        if self.server.listening():
1313            self.log.message(_('Server stopped'), ['stock', 'STOCK_DISCONNECT'])
1314
1315            self.server.stop_server()
1316            self.server.remove_log_observer()
1317
1318            # Stop the g_timeout
1319            if hasattr(self, 'iagotimer'):
1320                gobject.source_remove(self.iagotimer)
1321
1322            # Change GUI elements
1323            if foreign:
1324                self.button_start_stop.set_active(False)
1325
1326            self.status_icon.set_tooltip('Itaka')
1327            self.start_stop_image.set_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON)
1328            self.button_start_stop.set_image(self.start_stop_image)
1329            self.button_start_stop.set_label(_('Start'))
1330            if not foreign:
1331                self.label_last_ip.set_text('')
1332                self.label_time.set_text('')
1333                self.label_served.set_text('')
1334            self.menu_item_start.set_sensitive(True)
1335            self.menu_item_stop.set_sensitive(False)
1336
1337    def restart_server(self):
1338        """
1339        Restarts the Twisted server
1340        """
1341
1342        if self.server.listening():
1343            self.log.message(_('Restarting the server to listen on port %d') % (self.configuration['server']['port']), ['stock', 'STOCK_REFRESH'])
1344            self.stop_server(None, True)
1345            self.start_server(None, True)
1346
1347    def _destroy(self, *args):
1348        """
1349        Main window destroy event
1350        """
1351
1352        if self.server.listening():
1353            self.server.stop_server()
1354
1355        # Remove stale screenshot and quit
1356        if os.path.exists(os.path.join(self.configuration['screenshot']['path'], 'itakashot.%s' % (self.configuration['screenshot']['format']))):
1357            os.remove(os.path.join(self.configuration['screenshot']['path'], 'itakashot.%s' % (self.configuration['screenshot']['format'])))
1358
1359        # Windows fix
1360        del self.status_icon
1361
1362        self.console.message(_('Itaka shutting down'))
1363
1364        reactor.stop()
1365
1366    def _literal_time_difference(self, dtime):
1367        """
1368        Calculates the time difference from the last server request to
1369        the current time. Expresses a datetime.timedelta using a
1370        string such as "1 hour, 20 minutes"
1371       
1372        @type dtime: datetime.datetime
1373        @param dtime: A starting datetime.datetime object
1374        """
1375
1376        # Create a timedelta from the datetime.datetime and the current time
1377        # (you can create your own timedeltas with datetime.timedelta(5, (650 *
1378        # 60) * 2, 12) for testing
1379        self.td = datetime.datetime.now() - dtime
1380
1381        self.pieces = []
1382        if self.td.days:
1383                self.pieces.append(self.plural(self.td.days, _('day')))
1384
1385        self.minutes, self.seconds = divmod(self.td.seconds, 60)
1386        self.hours, self.minutes = divmod(self.minutes, 60)
1387        if self.hours:
1388            self.pieces.append(self.plural(self.hours, _('hour')))
1389        if self.minutes or len(self.pieces) == 0:
1390            self.pieces.append(self.plural(self.minutes, _('minute')))
1391
1392        self.label_time.set_text(_('<b>When</b>: ') + ', '.join(self.pieces) + _(' ago'))
1393        self.label_time.set_use_markup(True)
1394
1395        # Need this so it runs more than once
1396        return True
1397
1398    def plural(self, count, singular):
1399        """
1400        Helper method to handle simple english plural translations
1401       
1402        @type count: int
1403        @param count: Number
1404
1405        @type singular: str
1406        @param singular: Singular version of the word to pluralize
1407        """
1408
1409        # This is the simplest version; a more general version
1410        # should handle -y -> -ies, child -> children, etc
1411        return '%d %s%s' % (count, singular, ("", 's')[count != 1])
1412
1413    def set_standard_images(self):
1414        """
1415        Changes the logo on the main window
1416        """
1417       
1418        if self.configuration['server']['authentication']:
1419            self.itaka_logo.set_from_file(os.path.join(self.itaka_globals.image_dir, "itaka-secure.png"))           
1420            self.status_icon.set_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka-secure.png'))
1421        else:
1422            self.itaka_logo.set_from_file(os.path.join(self.itaka_globals.image_dir, "itaka.png"))
1423            self.status_icon.set_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka.png'))
1424
1425        # Only run this event once
1426        return False
1427
1428    def update_gui(self, counter, ip, time):
1429        """
1430        Updates the GUI on request from the server
1431       
1432        @type counter: int
1433        @param counter: Total number of server hits
1434
1435        @type ip: str
1436        @param ip: IP address of the client
1437
1438        @type time: datetime.datetime
1439        @param time: Time of the request
1440        """
1441
1442        if self.configuration['server']['authentication']:
1443            self.log.verbose_message(_('Screenshot served to %s') % ip, _('Screenshot number %(number)d served to %(ip)s') % {'number': counter, 'ip':ip}, ['pixbuf', gtk.gdk.pixbuf_new_from_file(os.path.join(self.itaka_globals.image_dir, "itaka16x16-secure-take.png"))])
1444        else:
1445            self.log.verbose_message(_('Screenshot served to %s') % ip, _('Screenshot number %(number)d served to %(ip)s') % {'number': counter, 'ip':ip}, ['pixbuf', gtk.gdk.pixbuf_new_from_file(os.path.join(self.itaka_globals.image_dir, "itaka16x16-take.png"))])
1446
1447        self.label_served.set_text(_('<b>Served</b>: %d') % (counter))
1448        self.label_served.set_use_markup(True)
1449        self.label_last_ip.set_text(_('<b>Client</b>: %s') % (ip))
1450        self.label_last_ip.set_use_markup(True)
1451        self.status_icon.set_tooltip(_('Itaka - %s served') % (self.plural(counter, _('screenshot'))))
1452
1453        # Show the camera image on tray and interface for 1.5 seconds
1454        if self.configuration['server']['authentication']:
1455            self.itaka_logo.set_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka-secure-take.png'))
1456            self.status_icon.set_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka-secure-take.png'))
1457        else:
1458            self.itaka_logo.set_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka-take.png'))
1459            self.status_icon.set_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka-take.png'))
1460
1461        gobject.timeout_add(1500, self.set_standard_images)
1462
1463        # Add a timer to update the interface
1464        self._literal_time_difference(time)
1465
1466        if hasattr(self, 'iagotimer'):
1467            gobject.source_remove(self.iagotimer)
1468
1469        self.iagotimer = gobject.timeout_add(60000, self._literal_time_difference, time)
1470
Note: See TracBrowser for help on using the repository browser.