source: trunk/uigtk.py @ 324

Revision 324, 62.9 KB checked in by marc, 3 years ago (diff)

First try at backport

  • 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        if self.itaka_globals.notify_available:
774            self.notify_value = self.check_preferences_notifications.get_active()
775
776            if self.notify_value:
777                self.notify_value = True
778                self.menu_item_notifications.set_active(True)
779                self.configuration['server']['notify'] = True
780            else:
781                self.notify_value = False
782                self.menu_item_notifications.set_active(False)
783                self.configuration['server']['notify'] = False
784        else:
785            self.notify_value = False
786            self.configuration['server']['notify'] = False
787
788        if not self.itaka_globals.system == 'nt':
789            if self.combo_preferences_screenshot.get_active_text() == _('Active window'):
790                self.configuration['screenshot']['currentwindow'] = True
791                self.screenshot_value = True
792            else:
793                self.configuration['screenshot']['currentwindow'] = False
794                self.screenshot_value = False
795        else:
796            self.screenshot_value = False
797            self.configuration['screenshot']['currentwindow'] = False
798
799        self.scale_value = [self.spin_preferences_scale.get_value_as_int()]
800
801        if self.scale_value[0] == 100:
802            self.configuration['screenshot']['scale'] = False
803            self.scale_value.append(False)
804        else:
805            self.configuration['screenshot']['scale'] = True
806            self.scale_value.append(True)
807
808        if self.configuration['screenshot']['scalepercent'] != self.scale_value[0]:
809            self.configuration['screenshot']['scalepercent'] = self.scale_value[0]
810       
811        # Build a configuration dictionary to send to the configuration engine's
812        # save method. Redundant values must be included for the comparison
813        self.configuration_dict = {
814            'html':
815                {'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.">',
816                'authfailure': '<p><strong>Sorry, but you cannot access this resource without authorization.</strong></p>'},
817               
818            'screenshot':
819                {'path': self.configuration['screenshot']['path'],
820                'format': self.format_value,
821                'quality': self.spin_preferences_quality.get_value_as_int(),
822                'currentwindow': self.screenshot_value,
823                'scale': self.scale_value[1],
824                'scalepercent': self.scale_value[0]},
825
826            'log':
827                {'logtimeformat': '[%d/%b/%Y %H:%M:%S]',
828                'logfile': '~/.itaka/access.log'},
829
830            'server':
831                {'username': self.entry_preferences_user.get_text(),
832                'authentication': self.check_preferences_auth.get_active(),
833                'notify': self.notify_value,
834                'password': self.entry_preferences_pass.get_text(),
835                'port': self.spin_preferences_port.get_value_as_int()}
836            }
837
838        # Set them for local use now
839        if self.configuration['screenshot']['quality'] != self.spin_preferences_quality.get_value_as_int():
840            self.configuration['screenshot']['quality'] = self.spin_preferences_quality.get_value_as_int()
841
842        if self.configuration['server']['port'] !=  self.spin_preferences_port.get_value_as_int():
843            self.configuration['server']['port'] =  self.spin_preferences_port.get_value_as_int()
844            self.restart_server()
845
846        if self.configuration['server']['authentication'] is not self.check_preferences_auth.get_active():
847            self.configuration['server']['authentication'] = self.check_preferences_auth.get_active()
848
849        if self.configuration['server']['username'] != self.entry_preferences_user.get_text():
850            self.configuration['server']['username'] = self.entry_preferences_user.get_text()
851
852        if self.configuration['server']['password'] != self.entry_preferences_pass.get_text():
853            self.configuration['server']['password'] = self.entry_preferences_pass.get_text()
854
855        # Check if the configuration changed
856        if (self.configuration_dict != self.current_configuration):
857
858            # Update the needed keys
859            try:
860                # self.config_instance.save(self.configuration_dict)
861                for section in self.configuration_dict:
862                    [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]
863            except:
864                self.log.failure(('Gui', '_save_preferences'), _('Could not save preferences'), 'ERROR')
865
866    def _expand_preferences(self, *args):
867        """
868        Expands the window for preferences
869        """
870
871        # We have a race condition here. If GTK cant resize fast enough, then it gets very sluggish
872        # See configure-event signal of gtk.Widget
873        # 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
874        if not self.preferences_expanded:
875            # Close log in small screen displays
876            if ((gtk.gdk.screen_height() < self.itaka_globals.min_screen_height) and self.expander.get_property("expanded")):
877                self.expander.set_expanded(False)
878
879            if self.timeout_expand is not None:
880                """NOTE: GTK+ GtkWidget.size_request() method can give you the amount of size a widget will take
881                however, it has to be show()ned before. For our little hack, we show the vbox_preferences widgets
882                but not itself, which should yield a close enough calculation."""
883                self.frame_preference_settings.show_all()
884                self.hbox_preferences_10.show_all()
885
886                """If the logger is expanded, use that as the initial size.
887                _expander_size is set by our GtkWindow resize callback
888                but we also set a expander_size_finalized variable here
889                so that _window_size_changed doesnt set the new expanded_size over
890                again as our window is expanding here."""
891               
892                self.expander_size_finalized = False
893                if self.expander.get_expanded():
894                    self.window.normal_size = self.expander_size
895                    self.expander_size_finalized = True
896                else:
897                    self.window.normal_size = self.window.initial_size
898
899                self.increment = 33
900                if self.window.current_size[1] < self.window.normal_size[1]+self.vbox_preferences.size_request()[1]:
901                    # Avoid overexpanding our calculation
902                    if self.window.current_size[1]+self.increment > self.window.normal_size[1]+self.vbox_preferences.size_request()[1]:
903                        self.increment = (self.window.normal_size[1]+self.vbox_preferences.size_request()[1] - self.window.current_size[1])
904
905                    self.window.resize(self.window.current_size[0], self.window.current_size[1]+self.increment)
906                    return True
907                else:
908                    # Its done expanding, add our widgets or display it if it has been done already
909                    self.button_preferences.set_sensitive(False)
910                    self.preferences_expanded = True
911
912                    # Reload our configuration and show the preferences
913                    self.configuration = self.config_instance.load()
914                    if self.preferences_hidden:
915                        self.vbox_preferences.show_all()
916                    else:
917                        self.vbox.pack_start(self.vbox_preferences, False, False, 0)
918                        self.vbox_preferences.show_all()
919                   
920                    self.timeout_expand = None
921                    return False
922            else:
923                self.timeout_expand = gobject.timeout_add(30, self._expand_preferences)
924
925    def _contract_preferences(self, save):
926        """
927        Contracts the window of preferences
928
929        @type args: unknown
930        @param args: gtk.Widget or "False" if you don't want to save preferences, just hide the pane
931        """
932
933        if self.timeout_contract is not None:
934            # If you dont use the normal_size proxy to our window sizes,
935            # it generates a nice effect of doing the animation when closing the expander also.
936            # While sexy, it's inconsistent, and most definately a resource hungry bug
937            if self.expander.get_expanded():
938                self.window.normal_size = self.expander_size
939                self.expander_size_finalized = True
940            else:
941                self.window.normal_size = self.window.initial_size
942           
943            if self.vbox_preferences.get_property("visible"):
944                self.vbox_preferences.hide_all()
945
946            if self.window.current_size[1] > self.window.normal_size[1]:
947                self.window.resize(self.window.current_size[0], self.window.current_size[1]-self.increment)
948                return True
949            else:
950                # Done, set some variables and stop our timer
951                self.preferences_expanded = False
952                self.preferences_hidden = True
953                self.expander.size_finalized = False
954                self.button_preferences.set_sensitive(True)
955               
956                # Save our settings (but not if we are on small displays and we just want to hide the pane)
957                if save is not False:
958                    self._save_preferences()
959
960                self.timeout_contract = None
961                return False
962        else:
963            self.timeout_contract = gobject.timeout_add(30, self._contract_preferences, save)
964
965    def _window_size_changed(self, widget=None, data=None):
966        """
967        Report the window size on change
968       
969        @type widget: instance
970        @param widget: gtk.Widget
971
972        @type data: unknown
973        @param data: Unknown
974        """
975       
976        self.window.current_size = self.window.get_size()
977       
978        # If the logger is expanded, give them a new size unless our preferences expander is working
979        if self.expander.get_expanded() and not self.expander_size_finalized:
980            self.expander_size = self.window.current_size
981            # If the preferences were expanded before the logger
982            if self.preferences_expanded:
983                # Cant assign tuple items
984                self.expander_size = [self.expander_size[0], self.expander_size[1] - self.vbox_preferences.size_request()[1]]
985
986    def status_icon_menu(self, widget, button, time, menu):
987        """
988        Display the menu on the status icon
989       
990        @type widget: instance
991        @param widget: gtk.Widget
992
993        @type button: int
994        @param button: The button pressed.
995
996        @type time: unknown
997        @param time: Unknown
998
999        @type menu: instance
1000        @param menu: A gtk.Menu instance
1001        """
1002
1003        if button == 3:
1004            if menu:
1005                menu.show_all()
1006                menu.popup(None, None, None, 3, time)
1007
1008    def status_icon_timeout_blink(self, time=3000):
1009        """
1010        Sets the timeout in miliseconds to blink and stop blinking the status icon
1011       
1012        @type time: int
1013        @param time: Time in milliseconds to blink the status icon
1014        """
1015
1016        if self.timeout_blink is None:
1017            self.status_icon.set_blinking(True)
1018            self.timeout_blink = gobject.timeout_add(time, self.status_icon_timeout_blink)
1019        else:
1020            self.status_icon.set_blinking(False)
1021            self.timeout_blink = None
1022            return False
1023 
1024    def status_icon_activate(self, widget):
1025        """
1026        Toggle the window visibility from the status icon when clicked
1027       
1028        @type widget: instance
1029        @param widget: gtk.Widget
1030        """
1031
1032        if self.window.get_property("visible"):
1033            # Save it for when we undock because of errors
1034            self.window_position = self.window.get_position()
1035            self.window.hide()
1036        else:
1037            self.window.show()
1038
1039    def status_icon_notify(self, widget):
1040        """
1041        Disable or enable notifications on the fly from the status icon
1042       
1043        @type widget: instance
1044        @param widget: gtk.Widget
1045        """
1046
1047        if self.check_widget(self.menu_item_notifications):
1048            self.configuration['server']['notify'] = True
1049        else:
1050            self.configuration['server']['notify'] = False
1051
1052    def about(self, *args):
1053        """
1054        Creates the about dialog
1055        """
1056
1057        self.about_dialog = gtk.AboutDialog()
1058        self.about_dialog.set_transient_for(self.window)
1059        self.about_dialog.set_name('Itaka')
1060        self.about_dialog.set_version(self.itaka_globals.__version__)
1061        self.about_dialog.set_copyright(u'© 2003-2009 Marc E.')
1062        self.about_dialog.set_comments('Screenshooting de mercado.')
1063        self.about_dialog.set_authors(['Marc E. <santusmarc@users.sourceforge.net>', 'Kurt Erickson <psychogenicshk@users.sourceforge.net>', 'Nicoleau Fabien <nicoleau.fabien@gmail.com> (Fedora mantainer)'])
1064        self.about_dialog.set_artists(['Marc E. <santusmarc@users.sourceforge.net>', 'Tango Project (http://tango.freedesktop.org)'])
1065        self.about_dialog.set_license('''Itaka is free software; you can redistribute it and/or modify
1066it under the terms of the GNU General Public License as published by
1067the Free Software Foundation; either version 3 of the License, or
1068any later version.
1069
1070Itaka is distributed in the hope that it will be useful,
1071but WITHOUT ANY WARRANTY; without even the implied warranty of
1072MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1073GNU General Public License for more details.
1074
1075You should have received a copy of the GNU General Public License
1076along with Itaka; if not, write to the Free Software
1077Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA''')
1078        self.about_dialog.set_website('http://itaka.jardinpresente.com.ar')
1079        self.about_dialog.set_website_label('Itaka website')
1080        gtk.about_dialog_set_url_hook(None)
1081        self.about_dialog.set_logo(gtk.gdk.pixbuf_new_from_file(os.path.join(self.itaka_globals.image_dir, "itaka64x64.png")))
1082        self.about_dialog.set_icon(self.icon_pixbuf)
1083        self.about_dialog.run()
1084        self.about_dialog.destroy()
1085
1086    def _expand_logger(self, expander, params):
1087        """
1088        Expand or contract the logger
1089       
1090        @type expander: instance
1091        @param expander: gtk.Expander instance
1092
1093        @type params: unknown
1094        @param params: Unknown
1095        """
1096
1097        if self.expander.get_expanded():
1098            # Close preferences pane in small screen displays
1099            if ((gtk.gdk.screen_height() < self.itaka_globals.min_screen_height) and self.preferences_expanded):
1100                # Tell it to not save
1101                self._contract_preferences(False)
1102
1103            # Show the debugvbox() and it's subwidgets
1104            self.vbox_log.show_all()
1105
1106            self.expander.add(self.vbox_log)
1107        else:
1108            self.expander.remove(self.expander.child)
1109            self.window.resize(self.window.initial_size[0], self.window.initial_size[1])
1110        return
1111
1112    def _clear_logger(self, *args):
1113        """
1114        Clear the log
1115        """
1116
1117        self.log_events_store.clear()
1118        self.log_details_buffer.set_text("")
1119        self.log_server_buffer.set_text("")
1120
1121    def button_pause_log(self, widget):
1122        """
1123        Interface to pause or unpause the Gui logger by checking the status of a gtk.ToggleButton
1124       
1125        @type widget: instance
1126        @param widget: gtk.Widget
1127        """
1128
1129        if self.check_widget(widget):
1130            if not self.log_paused():
1131                self.pause_log()
1132        else:
1133            if self.log_paused():
1134                self.unpause_log()
1135
1136    def pause_log(self):
1137        """
1138        Pause Gui log output
1139        """
1140
1141        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'])))])
1142       
1143        self.log_events_tree_view.set_sensitive(False)
1144        self.log_details_text_view.set_sensitive(False)
1145
1146        self.server.remove_log_observer()
1147        self.log_is_paused = True
1148
1149    def unpause_log(self, foreign=False):
1150        """
1151        Unpause Gui log output
1152
1153        @type foreign: bool
1154        @param foreign: Whether the caller of this method is not the Gui gtk.ToggleButton
1155        """
1156
1157        self.server.add_log_observer(self.log.twisted_observer)
1158        if (foreign):
1159            self.button_log_pause.set_active(False)
1160           
1161        self.log_events_tree_view.set_sensitive(True)
1162        self.log_details_text_view.set_sensitive(True)
1163        self.log_server_text_view.set_sensitive(True)
1164
1165        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'])))])
1166
1167        self.log_is_paused = False
1168
1169    def log_paused(self):
1170        """
1171        Whether the Gui log is paused
1172
1173        @rtype: bool
1174        @return: True if the Gui log is paused
1175        """
1176       
1177        return self.log_is_paused
1178
1179    def main(self):
1180        """
1181        Main initiation function. Starts the Twisted GUI reactors
1182        """
1183
1184        # Run the gtk.main() loop and start a reactor for our server.
1185        # False can be passed for it to not register to it's SIGINT handler.
1186        # Register our own handler to send to _destroy
1187        signal.signal(signal.SIGINT, self._destroy)
1188        reactor.run()
1189       
1190    def _preferences_combo_changed(self, widget):
1191        """
1192        Callback for preferenes gtk.ComboBox widget
1193
1194        @type widget: instance
1195        @param widget: gtk.Widget
1196        """
1197       
1198        if self.combo_preferences_format.get_active_text() == 'PNG':
1199            self.hbox_preferences_6.set_sensitive(False)
1200        else:
1201            self.hbox_preferences_6.set_sensitive(True)
1202
1203    def _preferences_authentication_toggled(self, widget):
1204        """
1205        Callback for preferences gtk.CheckButton widget
1206
1207        @type widget: instance
1208        @param widget: gtk.Widget
1209        """
1210
1211        if self.check_widget(widget):
1212            self.entry_preferences_user.set_sensitive(True)
1213            self.entry_preferences_pass.set_sensitive(True)
1214            self.itaka_logo.set_from_file(os.path.join(self.itaka_globals.image_dir, "itaka-secure.png"))
1215            self.status_icon.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file(os.path.join(self.itaka_globals.image_dir, "itaka-secure.png")))
1216        else:
1217            self.entry_preferences_user.set_sensitive(False)
1218            self.entry_preferences_pass.set_sensitive(False)
1219            self.itaka_logo.set_from_file(os.path.join(self.itaka_globals.image_dir, "itaka.png"))
1220            self.status_icon.set_from_pixbuf(self.icon_pixbuf)
1221
1222
1223    def check_widget(self, widget):
1224        """
1225        Checks if a gtk.Widget is active
1226
1227        @type widget: instance
1228        @param widget: gtk.Widget
1229        """
1230
1231        if hasattr(widget, 'get_active') and callable(getattr(widget, 'get_active')):
1232            return widget.get_active()
1233        else:
1234            return False
1235
1236    def button_start_server(self, widget):
1237        """
1238        Interface to start or stop the server by checking the status of a gtk.ToggleButton
1239       
1240        @type widget: instance
1241        @param widget: gtk.Widget
1242        """
1243
1244        if self.check_widget(widget):
1245            self.start_server()
1246        else:
1247            self.stop_server()
1248
1249    def start_server(self, widget=None, foreign=False):
1250        """
1251        Starts the Twisted server
1252
1253        @type widget: instance
1254        @param widget: gtk.Widget
1255
1256        @type foreign: bool
1257        @param foreign: Whether the caller of this method is not self.button_start_stop
1258
1259        """
1260
1261        if self.server.listening(): return
1262
1263        try:
1264            self.server.start_server(reactor, self.configuration['server']['port'])
1265        except error.ItakaServerCannotListenError, e:
1266            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')
1267            self.button_start_stop.set_active(False)
1268            return
1269
1270        self.server.add_log_observer(self.log.twisted_observer)
1271
1272        if self.configuration['server']['authentication']:
1273            serverstock = 'STOCK_DIALOG_AUTHENTICATION'
1274            serverstring = _('Secure server')
1275        else:
1276            serverstock = 'STOCK_CONNECT'
1277            serverstring = _('Server')
1278
1279        if self.configuration['screenshot']['format'] == "jpeg":
1280            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])
1281        else:
1282            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])
1283
1284        # Change buttons
1285        if foreign:
1286            self.button_start_stop.set_active(True)
1287        self.button_start_stop.set_label(_('Stop'))
1288        self.start_stop_image.set_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_BUTTON)
1289        self.button_start_stop.set_image(self.start_stop_image)
1290
1291        self.status_icon.set_tooltip(_('Itaka - Server running'))
1292        self.menu_item_start.set_sensitive(False)
1293        self.menu_item_stop.set_sensitive(True)
1294
1295        if not self.expander.get_property("sensitive"):
1296            self.expander.set_sensitive(True)
1297        if not self.expander.get_property("expanded"):
1298            self.expander.set_expanded(True)
1299
1300    def stop_server(self, widget=None, foreign=False):
1301        """
1302        Stops the Twisted server
1303
1304        @type widget: instance
1305        @param widget: gtk.Widget
1306
1307        @type foreign: bool
1308        @param foreign: Whether the caller of this method is not self.button_start_stop
1309        """
1310
1311        if self.server.listening():
1312            self.log.message(_('Server stopped'), ['stock', 'STOCK_DISCONNECT'])
1313
1314            self.server.stop_server()
1315            self.server.remove_log_observer()
1316
1317            # Stop the g_timeout
1318            if hasattr(self, 'iagotimer'):
1319                gobject.source_remove(self.iagotimer)
1320
1321            # Change GUI elements
1322            if foreign:
1323                self.button_start_stop.set_active(False)
1324
1325            self.status_icon.set_tooltip('Itaka')
1326            self.start_stop_image.set_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON)
1327            self.button_start_stop.set_image(self.start_stop_image)
1328            self.button_start_stop.set_label(_('Start'))
1329            if not foreign:
1330                self.label_last_ip.set_text('')
1331                self.label_time.set_text('')
1332                self.label_served.set_text('')
1333            self.menu_item_start.set_sensitive(True)
1334            self.menu_item_stop.set_sensitive(False)
1335
1336    def restart_server(self):
1337        """
1338        Restarts the Twisted server
1339        """
1340
1341        if self.server.listening():
1342            self.log.message(_('Restarting the server to listen on port %d') % (self.configuration['server']['port']), ['stock', 'STOCK_REFRESH'])
1343            self.stop_server(None, True)
1344            self.start_server(None, True)
1345
1346    def _destroy(self, *args):
1347        """
1348        Main window destroy event
1349        """
1350
1351        if self.server.listening():
1352            self.server.stop_server()
1353
1354        # Remove stale screenshot and quit
1355        if os.path.exists(os.path.join(self.configuration['screenshot']['path'], 'itakashot.%s' % (self.configuration['screenshot']['format']))):
1356            os.remove(os.path.join(self.configuration['screenshot']['path'], 'itakashot.%s' % (self.configuration['screenshot']['format'])))
1357
1358        # Windows fix
1359        del self.status_icon
1360
1361        self.console.message(_('Itaka shutting down'))
1362
1363        reactor.stop()
1364
1365    def _literal_time_difference(self, dtime):
1366        """
1367        Calculates the time difference from the last server request to
1368        the current time. Expresses a datetime.timedelta using a
1369        string such as "1 hour, 20 minutes"
1370       
1371        @type dtime: datetime.datetime
1372        @param dtime: A starting datetime.datetime object
1373        """
1374
1375        # Create a timedelta from the datetime.datetime and the current time
1376        # (you can create your own timedeltas with datetime.timedelta(5, (650 *
1377        # 60) * 2, 12) for testing
1378        self.td = datetime.datetime.now() - dtime
1379
1380        self.pieces = []
1381        if self.td.days:
1382                self.pieces.append(self.plural(self.td.days, _('day')))
1383
1384        self.minutes, self.seconds = divmod(self.td.seconds, 60)
1385        self.hours, self.minutes = divmod(self.minutes, 60)
1386        if self.hours:
1387            self.pieces.append(self.plural(self.hours, _('hour')))
1388        if self.minutes or len(self.pieces) == 0:
1389            self.pieces.append(self.plural(self.minutes, _('minute')))
1390
1391        self.label_time.set_text(_('<b>When</b>: ') + ', '.join(self.pieces) + _(' ago'))
1392        self.label_time.set_use_markup(True)
1393
1394        # Need this so it runs more than once
1395        return True
1396
1397    def plural(self, count, singular):
1398        """
1399        Helper method to handle simple english plural translations
1400       
1401        @type count: int
1402        @param count: Number
1403
1404        @type singular: str
1405        @param singular: Singular version of the word to pluralize
1406        """
1407
1408        # This is the simplest version; a more general version
1409        # should handle -y -> -ies, child -> children, etc
1410        return '%d %s%s' % (count, singular, ("", 's')[count != 1])
1411
1412    def set_standard_images(self):
1413        """
1414        Changes the logo on the main window
1415        """
1416       
1417        if self.configuration['server']['authentication']:
1418            self.itaka_logo.set_from_file(os.path.join(self.itaka_globals.image_dir, "itaka-secure.png"))           
1419            self.status_icon.set_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka-secure.png'))
1420        else:
1421            self.itaka_logo.set_from_file(os.path.join(self.itaka_globals.image_dir, "itaka.png"))
1422            self.status_icon.set_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka.png'))
1423
1424        # Only run this event once
1425        return False
1426
1427    def update_gui(self, counter, ip, time):
1428        """
1429        Updates the GUI on request from the server
1430       
1431        @type counter: int
1432        @param counter: Total number of server hits
1433
1434        @type ip: str
1435        @param ip: IP address of the client
1436
1437        @type time: datetime.datetime
1438        @param time: Time of the request
1439        """
1440
1441        if self.configuration['server']['authentication']:
1442            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"))])
1443        else:
1444            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"))])
1445
1446        self.label_served.set_text(_('<b>Served</b>: %d') % (counter))
1447        self.label_served.set_use_markup(True)
1448        self.label_last_ip.set_text(_('<b>Client</b>: %s') % (ip))
1449        self.label_last_ip.set_use_markup(True)
1450        self.status_icon.set_tooltip(_('Itaka - %s served') % (self.plural(counter, _('screenshot'))))
1451
1452        # Show the camera image on tray and interface for 1.5 seconds
1453        if self.configuration['server']['authentication']:
1454            self.itaka_logo.set_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka-secure-take.png'))
1455            self.status_icon.set_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka-secure-take.png'))
1456        else:
1457            self.itaka_logo.set_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka-take.png'))
1458            self.status_icon.set_from_file(os.path.join(self.itaka_globals.image_dir, 'itaka-take.png'))
1459
1460        gobject.timeout_add(1500, self.set_standard_images)
1461
1462        # Add a timer to update the interface
1463        self._literal_time_difference(time)
1464
1465        if hasattr(self, 'iagotimer'):
1466            gobject.source_remove(self.iagotimer)
1467
1468        self.iagotimer = gobject.timeout_add(60000, self._literal_time_difference, time)
1469
Note: See TracBrowser for help on using the repository browser.