source: branches/release/0.2/uigtk.py @ 309

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