source: tags/0.2.2/uigtk.py @ 326

Revision 326, 56.4 KB checked in by marc, 3 years ago (diff)

No itaka verbosity conf

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