source: tags/0.2/uigtk.py @ 335

Revision 215, 55.2 KB checked in by marc, 5 years ago (diff)

Minor UI changes, debug meta option in config, fixed a major bug in screenshooting active windows

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