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

Revision 295, 55.3 KB checked in by marc, 9 years ago (diff)

Updated debian build, copyrights, names, etc

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