| 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. <santusmarc_at_gmail.com>. |
|---|
| 19 | # http://itaka.jardinpresente.com.ar |
|---|
| 20 | # |
|---|
| 21 | # $Id$ |
|---|
| 22 | |
|---|
| 23 | """ Itaka GTK+ GUI """ |
|---|
| 24 | |
|---|
| 25 | import sys, os, datetime, traceback, copy |
|---|
| 26 | |
|---|
| 27 | try: |
|---|
| 28 | from twisted.internet import gtk2reactor |
|---|
| 29 | gtk2reactor.install() |
|---|
| 30 | |
|---|
| 31 | from twisted.python import log |
|---|
| 32 | from twisted.web import server, static |
|---|
| 33 | from twisted.internet import reactor |
|---|
| 34 | import twisted.internet.error |
|---|
| 35 | except ImportError: |
|---|
| 36 | print "[*] Warning: Twisted Network Framework is missing, quitting" |
|---|
| 37 | sys.exit(1) |
|---|
| 38 | |
|---|
| 39 | try: |
|---|
| 40 | import server as iserver |
|---|
| 41 | except ImportError: |
|---|
| 42 | print "[*] ERROR: Failed to import Itaka server module" |
|---|
| 43 | traceback.print_exc() |
|---|
| 44 | sys.exit(1) |
|---|
| 45 | |
|---|
| 46 | try: |
|---|
| 47 | import pygtk |
|---|
| 48 | pygtk.require("2.0") |
|---|
| 49 | except ImportError: |
|---|
| 50 | print "[*] WARNING: Pygtk module is missing" |
|---|
| 51 | pass |
|---|
| 52 | try: |
|---|
| 53 | import gtk, gobject |
|---|
| 54 | except ImportError: |
|---|
| 55 | print "[*] ERROR: GTK+ bindings are missing" |
|---|
| 56 | sys.exit(1) |
|---|
| 57 | |
|---|
| 58 | if gtk.gtk_version[1] < 10: |
|---|
| 59 | print "[*] ERROR: Itaka requires GTK+ 2.10, you have %s installed" % ".".join(str(x) for x in gtk.gtk_version) |
|---|
| 60 | sys.exit(1) |
|---|
| 61 | |
|---|
| 62 | class Gui: |
|---|
| 63 | """ GTK GUI """ |
|---|
| 64 | def __init__(self, consoleinstance, configuration): |
|---|
| 65 | """ 'consoleinstance' is an instance of the Console class. 'configuration' is a tuple of configuration globals and an instance of the user's configuration values """ |
|---|
| 66 | |
|---|
| 67 | # Set up some variables |
|---|
| 68 | self.server_listening = False |
|---|
| 69 | |
|---|
| 70 | # Load our configuration and console instances and values |
|---|
| 71 | self.console = consoleinstance |
|---|
| 72 | self.itakaglobals = configuration[0] |
|---|
| 73 | # The configuration instance has the user's preferences already loaded. |
|---|
| 74 | self.configinstance = configuration[1] |
|---|
| 75 | self.configuration = self.itakaglobals.values |
|---|
| 76 | |
|---|
| 77 | # Start defining widgets |
|---|
| 78 | self.icon_pixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(self.itakaglobals.image_dir, "itaka.png")) |
|---|
| 79 | self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) |
|---|
| 80 | self.window.connect("destroy", self.destroy) |
|---|
| 81 | self.window.connect("size-allocate", self.__windowsizechanged) |
|---|
| 82 | self.window.set_title("Itaka") |
|---|
| 83 | self.window.set_icon(self.icon_pixbuf) |
|---|
| 84 | self.window.set_border_width(6) |
|---|
| 85 | self.window.set_default_size(400, 1) |
|---|
| 86 | self.window.set_position(gtk.WIN_POS_CENTER) |
|---|
| 87 | |
|---|
| 88 | # Create our tray icon |
|---|
| 89 | self.statusIcon = gtk.StatusIcon() |
|---|
| 90 | self.statusmenu = gtk.Menu() |
|---|
| 91 | self.statusIcon.set_from_pixbuf(self.icon_pixbuf) |
|---|
| 92 | self.statusIcon.set_tooltip("Itaka") |
|---|
| 93 | self.statusIcon.set_visible(True) |
|---|
| 94 | self.statusIcon.connect('activate', self.__statusicon_activate) |
|---|
| 95 | self.statusIcon.connect('popup-menu', self.__statusicon_menu, self.statusmenu) |
|---|
| 96 | |
|---|
| 97 | self.startimage = gtk.Image() |
|---|
| 98 | self.startimage.set_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) |
|---|
| 99 | |
|---|
| 100 | self.stopimage = gtk.Image() |
|---|
| 101 | self.stopimage.set_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) |
|---|
| 102 | |
|---|
| 103 | self.menuitemstart = gtk.ImageMenuItem("Start") |
|---|
| 104 | self.menuitemstart.set_image(self.startimage) |
|---|
| 105 | self.menuitemstart.connect('activate', self.startstop, "start") |
|---|
| 106 | self.menuitemstop = gtk.ImageMenuItem("Stop") |
|---|
| 107 | self.menuitemstop.set_image(self.stopimage) |
|---|
| 108 | self.menuitemstop.connect('activate', self.startstop, "stop") |
|---|
| 109 | self.menuitemstop.set_sensitive(False) |
|---|
| 110 | |
|---|
| 111 | if self.itakaglobals.notifyavailable: |
|---|
| 112 | self.menuitemnotifications = gtk.CheckMenuItem("Show Notifications") |
|---|
| 113 | if (self.configuration['server']['notify'] == "True"): |
|---|
| 114 | self.menuitemnotifications.set_active(True) |
|---|
| 115 | self.menuitemnotifications.connect('toggled', self.__statusicon_notify) |
|---|
| 116 | |
|---|
| 117 | self.menuitemseparator = gtk.SeparatorMenuItem() |
|---|
| 118 | self.menuitemseparator1 = gtk.SeparatorMenuItem() |
|---|
| 119 | self.menuitemabout = gtk.ImageMenuItem(gtk.STOCK_ABOUT) |
|---|
| 120 | self.menuitemabout.connect('activate', self.about) |
|---|
| 121 | self.menuitemquit = gtk.ImageMenuItem(gtk.STOCK_QUIT) |
|---|
| 122 | self.menuitemquit.connect('activate', self.destroy) |
|---|
| 123 | |
|---|
| 124 | self.statusmenu.append(self.menuitemstart) |
|---|
| 125 | self.statusmenu.append(self.menuitemstop) |
|---|
| 126 | if self.itakaglobals.notifyavailable: |
|---|
| 127 | self.statusmenu.append(self.menuitemseparator) |
|---|
| 128 | self.statusmenu.append(self.menuitemnotifications) |
|---|
| 129 | self.statusmenu.append(self.menuitemseparator1) |
|---|
| 130 | self.statusmenu.append(self.menuitemabout) |
|---|
| 131 | self.statusmenu.append(self.menuitemquit) |
|---|
| 132 | |
|---|
| 133 | self.vbox = gtk.VBox(False, 6) |
|---|
| 134 | self.box = gtk.HBox(False, 0) |
|---|
| 135 | |
|---|
| 136 | self.itakaLogo = gtk.Image() |
|---|
| 137 | self.itakaLogo.set_from_file(os.path.join(self.itakaglobals.image_dir, "itaka.png")) |
|---|
| 138 | self.itakaLogo.show() |
|---|
| 139 | |
|---|
| 140 | self.box.pack_start(self.itakaLogo, True, True, 4) |
|---|
| 141 | |
|---|
| 142 | self.ibox = gtk.HBox(False, 0) |
|---|
| 143 | self.buttonStartstop = gtk.ToggleButton("Start", gtk.STOCK_PREFERENCES) |
|---|
| 144 | self.startstopimage = gtk.Image() |
|---|
| 145 | |
|---|
| 146 | self.startstopimage.set_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON) |
|---|
| 147 | self.buttonStartstop.set_image(self.startstopimage) |
|---|
| 148 | self.buttonStartstop.connect("toggled", self.startstop, "Start/Stop button") |
|---|
| 149 | self.ibox.pack_start(self.buttonStartstop, True, True, 8) |
|---|
| 150 | |
|---|
| 151 | self.preferencesButton = gtk.Button("Preferences", gtk.STOCK_PREFERENCES) |
|---|
| 152 | self.preferencesButton.connect("clicked", self.__expandpreferences) |
|---|
| 153 | #self.preferencesButton.connect("clicked", ipreferences.Preferences().prefwindow, [config, self.configinstance], self, self.icon_pixbuf) |
|---|
| 154 | |
|---|
| 155 | # Set up some variables for our timeouts/animations |
|---|
| 156 | self.preferenceshidden = False |
|---|
| 157 | self.preferencesexpanded = False |
|---|
| 158 | self.contracttimeout = None |
|---|
| 159 | self.expandtimeout = None |
|---|
| 160 | |
|---|
| 161 | self.ibox.pack_start(self.preferencesButton, True, True, 4) |
|---|
| 162 | |
|---|
| 163 | self.box.pack_start(self.ibox, True, True, 0) |
|---|
| 164 | self.vbox.pack_start(self.box, False, False, 0) |
|---|
| 165 | |
|---|
| 166 | self.statusBox = gtk.HBox(False, 0) |
|---|
| 167 | self.labelServed = gtk.Label() |
|---|
| 168 | self.labelLastip = gtk.Label() |
|---|
| 169 | self.labelTime = gtk.Label() |
|---|
| 170 | |
|---|
| 171 | self.statusBox.pack_start(self.labelLastip, True, False, 0) |
|---|
| 172 | self.statusBox.pack_start(self.labelTime, True, False, 0) |
|---|
| 173 | self.statusBox.pack_start(self.labelServed, True, False, 0) |
|---|
| 174 | |
|---|
| 175 | # Logger widget (displayed when expanded) |
|---|
| 176 | self.logvbox = gtk.VBox(False, 0) |
|---|
| 177 | self.lognotebook = gtk.Notebook() |
|---|
| 178 | self.lognotebook.set_tab_pos(gtk.POS_BOTTOM) |
|---|
| 179 | |
|---|
| 180 | self.logeventslabel = gtk.Label("Events") |
|---|
| 181 | self.logdetailslabel = gtk.Label("Details") |
|---|
| 182 | |
|---|
| 183 | self.logeventsscroll = gtk.ScrolledWindow() |
|---|
| 184 | self.logeventsscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) |
|---|
| 185 | self.logeventsscroll.set_shadow_type(gtk.SHADOW_NONE) |
|---|
| 186 | |
|---|
| 187 | self.logeventsstore = gtk.ListStore(gtk.gdk.Pixbuf, str) |
|---|
| 188 | self.logeventstreeview = gtk.TreeView(self.logeventsstore) |
|---|
| 189 | self.logeventstreeview.set_property("headers-visible", False) |
|---|
| 190 | self.logeventstreeview.set_property("rules-hint", True) |
|---|
| 191 | |
|---|
| 192 | self.logeventscolumnicon = gtk.TreeViewColumn() |
|---|
| 193 | self.logeventscolumntext = gtk.TreeViewColumn() |
|---|
| 194 | self.logeventstreeview.append_column(self.logeventscolumnicon) |
|---|
| 195 | self.logeventstreeview.append_column(self.logeventscolumntext) |
|---|
| 196 | |
|---|
| 197 | self.logeventscellpixbuf = gtk.CellRendererPixbuf() |
|---|
| 198 | self.logeventscolumnicon.pack_start(self.logeventscellpixbuf) |
|---|
| 199 | self.logeventscolumnicon.add_attribute(self.logeventscellpixbuf, 'pixbuf', 0) |
|---|
| 200 | |
|---|
| 201 | self.logeventscelltext = gtk.CellRendererText() |
|---|
| 202 | self.logeventscolumntext.pack_start(self.logeventscelltext, True) |
|---|
| 203 | self.logeventscolumntext.add_attribute(self.logeventscelltext, 'text', 1) |
|---|
| 204 | self.logeventsscroll.add(self.logeventstreeview) |
|---|
| 205 | |
|---|
| 206 | self.logdetailsscroll = gtk.ScrolledWindow() |
|---|
| 207 | self.logdetailsscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) |
|---|
| 208 | self.logdetailsscroll.set_shadow_type(gtk.SHADOW_NONE) |
|---|
| 209 | self.logdetailstextview = gtk.TextView() |
|---|
| 210 | self.logdetailstextview.set_wrap_mode(gtk.WRAP_WORD) |
|---|
| 211 | self.logdetailstextview.set_editable(False) |
|---|
| 212 | self.logdetailstextview.set_size_request(-1, 160) |
|---|
| 213 | self.logdetailsbuffer = self.logdetailstextview.get_buffer() |
|---|
| 214 | self.logdetailsscroll.add(self.logdetailstextview) |
|---|
| 215 | |
|---|
| 216 | self.lognotebook.append_page(self.logeventsscroll, self.logeventslabel) |
|---|
| 217 | self.lognotebook.append_page(self.logdetailsscroll, self.logdetailslabel) |
|---|
| 218 | |
|---|
| 219 | self.loghbox = gtk.HBox(False, 0) |
|---|
| 220 | self.logclearbutton = gtk.Button("Clear") |
|---|
| 221 | self.logclearbuttonimage = gtk.Image() |
|---|
| 222 | self.logclearbuttonimage.set_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_BUTTON) |
|---|
| 223 | self.logclearbutton.set_image(self.logclearbuttonimage) |
|---|
| 224 | self.logclearbutton.connect("clicked", self.clearlogger) |
|---|
| 225 | |
|---|
| 226 | self.logpausebutton = gtk.ToggleButton("Pause") |
|---|
| 227 | self.logpausebuttonimage = gtk.Image() |
|---|
| 228 | self.logpausebuttonimage.set_from_stock(gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_BUTTON) |
|---|
| 229 | self.logpausebutton.set_image(self.logpausebuttonimage) |
|---|
| 230 | self.logpausebutton.connect("toggled", self.pauselogger) |
|---|
| 231 | |
|---|
| 232 | self.loghbox.pack_end(self.logclearbutton, False, False, 4) |
|---|
| 233 | self.loghbox.pack_end(self.logpausebutton, False, False, 4) |
|---|
| 234 | |
|---|
| 235 | self.logvbox.pack_start(self.lognotebook, False, False, 4) |
|---|
| 236 | self.logvbox.pack_start(self.loghbox, False, False, 4) |
|---|
| 237 | |
|---|
| 238 | self.logboxLabel = gtk.Label("<b>Event log</b>") |
|---|
| 239 | self.logboxLabel.set_use_markup(True) |
|---|
| 240 | |
|---|
| 241 | # Expander |
|---|
| 242 | self.expander_size_finalized = False |
|---|
| 243 | self.expander = gtk.Expander(None) |
|---|
| 244 | self.expander.set_label_widget(self.logboxLabel) |
|---|
| 245 | self.expander.connect('notify::expanded', self.__expandlogger) |
|---|
| 246 | |
|---|
| 247 | # Log to the self.logger function, which sets the buffer for self.debubuffer |
|---|
| 248 | log.addObserver(self.logger) |
|---|
| 249 | |
|---|
| 250 | self.vbox.pack_start(self.statusBox, False, False, 4) |
|---|
| 251 | self.vbox.pack_start(self.expander, False, False, 0) |
|---|
| 252 | self.expander.set_sensitive(False) |
|---|
| 253 | |
|---|
| 254 | # This is are the preference widgets that are going to be added and shown later |
|---|
| 255 | self.preferencesVBox = gtk.VBox(False, 7) |
|---|
| 256 | self.preferencesVBoxitems = gtk.VBox(False, 5) |
|---|
| 257 | self.preferencesVBoxitems.set_border_width(2) |
|---|
| 258 | self.preferencesHBox1 = gtk.HBox(False, 0) |
|---|
| 259 | self.preferencesHBox2 = gtk.HBox(False, 0) |
|---|
| 260 | self.preferencesHBox3 = gtk.HBox(False, 0) |
|---|
| 261 | self.preferencesHBox4 = gtk.HBox(False, 0) |
|---|
| 262 | self.preferencesHBox5 = gtk.HBox(False, 0) |
|---|
| 263 | |
|---|
| 264 | self.preferencesFramesettings = gtk.Frame() |
|---|
| 265 | self.preferencesSettingslabel = gtk.Label("<b>Preferences</b>") |
|---|
| 266 | self.preferencesSettingslabel.set_use_markup(True) |
|---|
| 267 | self.preferencesFramesettings.set_label_widget(self.preferencesSettingslabel) |
|---|
| 268 | |
|---|
| 269 | self.preferencesLabelport = gtk.Label("Port:") |
|---|
| 270 | self.preferencesLabelport.set_justify(gtk.JUSTIFY_LEFT) |
|---|
| 271 | self.preferencesLabelport.set_alignment(0, 0.50) |
|---|
| 272 | |
|---|
| 273 | self.preferencesLabelquality = gtk.Label("Quality:") |
|---|
| 274 | self.preferencesLabelquality.set_justify(gtk.JUSTIFY_LEFT) |
|---|
| 275 | self.preferencesLabelquality.set_alignment(0, 0.50) |
|---|
| 276 | |
|---|
| 277 | |
|---|
| 278 | self.preferencesLabelformat = gtk.Label("Format:") |
|---|
| 279 | self.preferencesLabelformat.set_justify(gtk.JUSTIFY_LEFT) |
|---|
| 280 | self.preferencesLabelformat.set_alignment(0, 0.50) |
|---|
| 281 | |
|---|
| 282 | self.preferencesLabelnotifications = gtk.Label("Notifications:") |
|---|
| 283 | self.preferencesLabelnotifications.set_justify(gtk.JUSTIFY_LEFT) |
|---|
| 284 | self.preferencesLabelnotifications.set_alignment(0, 0.50) |
|---|
| 285 | |
|---|
| 286 | # Spinbuttons |
|---|
| 287 | self.adjustmentport = gtk.Adjustment(float(self.configuration['server']['port']), 1024, 65535, 1, 0, 0) |
|---|
| 288 | self.preferencesSpinport = gtk.SpinButton(self.adjustmentport) |
|---|
| 289 | self.preferencesSpinport.set_numeric(True) |
|---|
| 290 | |
|---|
| 291 | self.adjustmentquality = gtk.Adjustment(float(self.configuration['screenshot']['quality']), 0, 100, 1, 0, 0) |
|---|
| 292 | self.preferencesSpinquality = gtk.SpinButton(self.adjustmentquality) |
|---|
| 293 | self.preferencesSpinquality.set_numeric(True) |
|---|
| 294 | |
|---|
| 295 | # Combos |
|---|
| 296 | self.preferencesComboformat = gtk.combo_box_new_text() |
|---|
| 297 | self.preferencesComboformat.connect('changed', self.__preferencesComboChanged) |
|---|
| 298 | self.preferencesComboformat.append_text("JPG") |
|---|
| 299 | self.preferencesComboformat.append_text("PNG") |
|---|
| 300 | if (self.configuration['screenshot']['format'] == "jpeg"): |
|---|
| 301 | self.preferencesComboformat.set_active(0) |
|---|
| 302 | else: |
|---|
| 303 | self.preferencesComboformat.set_active(1) |
|---|
| 304 | self.preferencesHBox3.set_sensitive(False) |
|---|
| 305 | |
|---|
| 306 | self.preferencesChecknotifications = gtk.CheckButton() |
|---|
| 307 | if (self.configuration['server']['notify'] == "True"): |
|---|
| 308 | self.preferencesChecknotifications.set_active(1) |
|---|
| 309 | else: |
|---|
| 310 | self.preferencesChecknotifications.set_active(0) |
|---|
| 311 | |
|---|
| 312 | self.preferencesButtonClose = gtk.Button("Close", gtk.STOCK_CLOSE) |
|---|
| 313 | self.preferencesButtonClose.connect("clicked", lambda wid: self.__contractpreferences()) |
|---|
| 314 | |
|---|
| 315 | self.preferencesButtonAbout = gtk.Button("About", gtk.STOCK_ABOUT) |
|---|
| 316 | self.preferencesButtonAbout.connect("clicked", lambda wid: self.about()) |
|---|
| 317 | |
|---|
| 318 | self.preferencesHBox1.pack_start(self.preferencesLabelport, False, False, 12) |
|---|
| 319 | self.preferencesHBox2.pack_start(self.preferencesLabelformat, False, False, 12) |
|---|
| 320 | self.preferencesHBox3.pack_start(self.preferencesLabelquality, False, False, 12) |
|---|
| 321 | self.preferencesHBox4.pack_start(self.preferencesLabelnotifications, False, False, 12) |
|---|
| 322 | |
|---|
| 323 | self.preferencesHBox1.pack_end(self.preferencesSpinport, False, False, 7) |
|---|
| 324 | self.preferencesHBox2.pack_end(self.preferencesComboformat, False, False, 7) |
|---|
| 325 | self.preferencesHBox3.pack_end(self.preferencesSpinquality, False, False, 7) |
|---|
| 326 | self.preferencesHBox4.pack_end(self.preferencesChecknotifications, False, False, 7) |
|---|
| 327 | self.preferencesHBox5.pack_start(self.preferencesButtonAbout, False, False, 7) |
|---|
| 328 | self.preferencesHBox5.pack_end(self.preferencesButtonClose, False, False, 7) |
|---|
| 329 | |
|---|
| 330 | # Hbox4 contains notifications which is only available in some systems |
|---|
| 331 | if not self.itakaglobals.notifyavailable: |
|---|
| 332 | self.preferencesHBox4.set_sensitive(False) |
|---|
| 333 | |
|---|
| 334 | # Add Hboxes to the Vbox |
|---|
| 335 | self.preferencesVBoxitems.pack_start(self.preferencesHBox1, False, False, 0) |
|---|
| 336 | self.preferencesVBoxitems.pack_start(self.preferencesHBox2, False, False, 0) |
|---|
| 337 | self.preferencesVBoxitems.pack_start(self.preferencesHBox3, False, False, 0) |
|---|
| 338 | self.preferencesVBoxitems.pack_start(self.preferencesHBox4, False, False, 0) |
|---|
| 339 | |
|---|
| 340 | self.preferencesFramesettings.add(self.preferencesVBoxitems) |
|---|
| 341 | self.preferencesVBox.pack_start(self.preferencesFramesettings, False, False, 0) |
|---|
| 342 | self.preferencesVBox.pack_start(self.preferencesHBox5, False, False, 4) |
|---|
| 343 | |
|---|
| 344 | self.window.add(self.vbox) |
|---|
| 345 | self.window.show_all() |
|---|
| 346 | |
|---|
| 347 | # Once we have all our widgets shown, get the 'initial' real size, for expanding/contracting |
|---|
| 348 | self.window.initial_size = self.window.get_size() |
|---|
| 349 | |
|---|
| 350 | def save_preferences(self): |
|---|
| 351 | """ Save and hide the preferences dialog """ |
|---|
| 352 | # So we can mess with the values in the running one and not mess up our comparison |
|---|
| 353 | self.currentconfiguration = copy.deepcopy(self.configuration) |
|---|
| 354 | |
|---|
| 355 | # Switch to the proper values |
|---|
| 356 | formatvalue = str(self.preferencesComboformat.get_active_text()) |
|---|
| 357 | if formatvalue == "PNG": |
|---|
| 358 | formatvalue = "png" |
|---|
| 359 | self.configuration['screenshot']['format'] = 'png' |
|---|
| 360 | else: |
|---|
| 361 | formatvalue = "jpeg" |
|---|
| 362 | self.configuration['screenshot']['format'] = 'jpeg' |
|---|
| 363 | |
|---|
| 364 | if self.itakaglobals.notifyavailable: |
|---|
| 365 | notifyvalue = self.preferencesChecknotifications.get_active() |
|---|
| 366 | if notifyvalue: |
|---|
| 367 | notifyvalue = 'True' |
|---|
| 368 | self.menuitemnotifications.set_active(True) |
|---|
| 369 | self.configuration['server']['notify'] = 'True' |
|---|
| 370 | else: |
|---|
| 371 | notifyvalue = 'False' |
|---|
| 372 | self.menuitemnotifications.set_active(False) |
|---|
| 373 | self.configuration['server']['notify'] = 'False' |
|---|
| 374 | else: |
|---|
| 375 | notifyvalue = 'False' |
|---|
| 376 | self.configuration['server']['notify'] = 'False' |
|---|
| 377 | |
|---|
| 378 | # Build a configuration dictionary to send to the configuration engine's |
|---|
| 379 | # save method. Redundant values must be included for the comparison |
|---|
| 380 | self.configurationdict = { |
|---|
| 381 | 'html': |
|---|
| 382 | {'html': '<html><body><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." border="0"></a></body</html>'}, |
|---|
| 383 | |
|---|
| 384 | 'screenshot': |
|---|
| 385 | {'path': '/tmp', |
|---|
| 386 | 'format': formatvalue, |
|---|
| 387 | 'quality': str(self.preferencesSpinquality.get_value_as_int())}, |
|---|
| 388 | |
|---|
| 389 | 'server': |
|---|
| 390 | {'port': str(self.preferencesSpinport.get_value_as_int()), |
|---|
| 391 | 'notify': notifyvalue} |
|---|
| 392 | } |
|---|
| 393 | |
|---|
| 394 | # Set them for local use now |
|---|
| 395 | if self.configuration['screenshot']['quality'] != str(self.preferencesSpinquality.get_value_as_int()): |
|---|
| 396 | self.configuration['screenshot']['quality'] = str(self.preferencesSpinquality.get_value_as_int()) |
|---|
| 397 | |
|---|
| 398 | if self.configuration['server']['port'] != str(self.preferencesSpinport.get_value_as_int()): |
|---|
| 399 | self.configuration['server']['port'] = str(self.preferencesSpinport.get_value_as_int()) |
|---|
| 400 | self.restart_server() |
|---|
| 401 | |
|---|
| 402 | # Check if the configuration changed |
|---|
| 403 | if (self.configurationdict != self.currentconfiguration): |
|---|
| 404 | try: |
|---|
| 405 | self.configinstance.save(self.configurationdict) |
|---|
| 406 | except: |
|---|
| 407 | self.console.error(['Gui', 'save'], "Could not save preferences") |
|---|
| 408 | |
|---|
| 409 | def __expandpreferences(self, params=None): |
|---|
| 410 | """ Callback to expand the window for preferences. """ |
|---|
| 411 | # We have a race condition here. If GTK cant resize fast enough, then it gets very sluggish |
|---|
| 412 | # See configure-event signal of gtk.Widget |
|---|
| 413 | # 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 |
|---|
| 414 | if self.preferencesexpanded != True: |
|---|
| 415 | if self.expandtimeout is not None: |
|---|
| 416 | """NOTE: GTK+ GtkWidget.size_request() method can give you the amount of size a widget will take. |
|---|
| 417 | however, it has to be show()ned before. For our little hack, we show the preferencesVBox widgets |
|---|
| 418 | but not itself, which should yield a close enough calculation.""" |
|---|
| 419 | self.preferencesFramesettings.show_all() |
|---|
| 420 | self.preferencesHBox5.show_all() |
|---|
| 421 | |
|---|
| 422 | """If the logger is expanded, use that as the initial size. |
|---|
| 423 | _expander_size is set by our GtkWindow resize callback |
|---|
| 424 | but we also set a expander_size_finalized variable here |
|---|
| 425 | so that __windowsizechanged doesnt set the new expanded_size over |
|---|
| 426 | again as our window is expanding here.""" |
|---|
| 427 | |
|---|
| 428 | self.expander_size_finalized = False |
|---|
| 429 | if self.expander.get_expanded(): |
|---|
| 430 | self.window.normal_size = self.expander_size |
|---|
| 431 | self.expander_size_finalized = True |
|---|
| 432 | else: |
|---|
| 433 | self.window.normal_size = self.window.initial_size |
|---|
| 434 | |
|---|
| 435 | self.increment = 33 |
|---|
| 436 | if self.window.current_size[1] < self.window.normal_size[1]+self.preferencesVBox.size_request()[1]: |
|---|
| 437 | # Avoid overexpanding our calculation |
|---|
| 438 | if self.window.current_size[1]+self.increment > self.window.normal_size[1]+self.preferencesVBox.size_request()[1]: |
|---|
| 439 | self.increment = (self.window.normal_size[1]+self.preferencesVBox.size_request()[1] - self.window.current_size[1]) |
|---|
| 440 | |
|---|
| 441 | self.window.resize(self.window.current_size[0], self.window.current_size[1]+self.increment) |
|---|
| 442 | return True |
|---|
| 443 | else: |
|---|
| 444 | # Its done expanding, add our widgets or display it if it has been done already |
|---|
| 445 | self.preferencesButton.set_sensitive(False) |
|---|
| 446 | self.preferencesexpanded = True |
|---|
| 447 | |
|---|
| 448 | # Reload our configuration and show the preferences |
|---|
| 449 | self.configuration = self.configinstance.load(False) |
|---|
| 450 | if self.preferenceshidden: |
|---|
| 451 | self.preferencesVBox.show_all() |
|---|
| 452 | else: |
|---|
| 453 | self.vbox.pack_start(self.preferencesVBox, False, False, 0) |
|---|
| 454 | self.preferencesVBox.show_all() |
|---|
| 455 | |
|---|
| 456 | self.expandtimeout = None |
|---|
| 457 | return False |
|---|
| 458 | else: |
|---|
| 459 | self.expandtimeout = gobject.timeout_add(30, self.__expandpreferences) |
|---|
| 460 | |
|---|
| 461 | def __contractpreferences(self, params=None): |
|---|
| 462 | """ Callback to contract the window of preferences. """ |
|---|
| 463 | # TODO: Add save |
|---|
| 464 | |
|---|
| 465 | if self.contracttimeout is not None: |
|---|
| 466 | # If you dont use the normal_size proxy to our window sizes, |
|---|
| 467 | # it generates a nice effect of doing the animation when closing the expander also. |
|---|
| 468 | # While sexy, it's inconsistent, and most definately a resource hungry bug. |
|---|
| 469 | if self.expander.get_expanded(): |
|---|
| 470 | self.window.normal_size = self.expander_size |
|---|
| 471 | self.expander_size_finalized = True |
|---|
| 472 | else: |
|---|
| 473 | self.window.normal_size = self.window.initial_size |
|---|
| 474 | |
|---|
| 475 | if self.preferencesVBox.get_property("visible"): |
|---|
| 476 | self.preferencesVBox.hide_all() |
|---|
| 477 | |
|---|
| 478 | if self.window.current_size[1] > self.window.normal_size[1]: |
|---|
| 479 | self.window.resize(self.window.current_size[0], self.window.current_size[1]-self.increment) |
|---|
| 480 | return True |
|---|
| 481 | else: |
|---|
| 482 | # Done, set some variables and stop our timer |
|---|
| 483 | self.preferencesexpanded = False |
|---|
| 484 | self.preferenceshidden = True |
|---|
| 485 | self.expander.size_finalized = False |
|---|
| 486 | self.preferencesButton.set_sensitive(True) |
|---|
| 487 | |
|---|
| 488 | # Save our settings |
|---|
| 489 | self.save_preferences() |
|---|
| 490 | |
|---|
| 491 | self.contracttimeout = None |
|---|
| 492 | return False |
|---|
| 493 | else: |
|---|
| 494 | self.contracttimeout = gobject.timeout_add(30, self.__contractpreferences) |
|---|
| 495 | |
|---|
| 496 | def __windowsizechanged(self, widget=None, data=None): |
|---|
| 497 | """ This is a callback to always have the latest window size """ |
|---|
| 498 | self.window.current_size = self.window.get_size() |
|---|
| 499 | |
|---|
| 500 | # If the logger is expanded, give them a new size unless our preferences expander is working |
|---|
| 501 | if self.expander.get_expanded() and not self.expander_size_finalized: |
|---|
| 502 | self.expander_size = self.window.current_size |
|---|
| 503 | # If the preferences were expanded before the logger |
|---|
| 504 | if self.preferencesexpanded: |
|---|
| 505 | # Cant assign tuple items |
|---|
| 506 | self.expander_size = [self.expander_size[0], self.expander_size[1] - self.preferencesVBox.size_request()[1]] |
|---|
| 507 | |
|---|
| 508 | def __statusicon_menu(self, widget, button, time, menu): |
|---|
| 509 | """ Callback to display the menu on the status icon """ |
|---|
| 510 | if button == 3: |
|---|
| 511 | if menu: |
|---|
| 512 | menu.show_all() |
|---|
| 513 | menu.popup(None, None, None, 3, time) |
|---|
| 514 | pass |
|---|
| 515 | |
|---|
| 516 | def __statusicon_activate(self, widget): |
|---|
| 517 | """ Callback to toggle the window visibility from the status icon """ |
|---|
| 518 | if (self.window.get_property("visible")): |
|---|
| 519 | self.window.hide() |
|---|
| 520 | else: |
|---|
| 521 | self.window.show() |
|---|
| 522 | |
|---|
| 523 | def __statusicon_notify(self, widget): |
|---|
| 524 | """ Callback to disable or enable notifications on the fly from the status icon. 'active' is a boolean for the checkbox """ |
|---|
| 525 | if self.menuitemnotifications.get_active(): |
|---|
| 526 | self.configuration['server']['notify'] = 'True' |
|---|
| 527 | else: |
|---|
| 528 | self.configuration['server']['notify'] = 'False' |
|---|
| 529 | |
|---|
| 530 | def about(self, data=None): |
|---|
| 531 | """ Create the About dialog. """ |
|---|
| 532 | self.aboutdialog = gtk.AboutDialog() |
|---|
| 533 | self.aboutdialog.set_transient_for(self.window) |
|---|
| 534 | self.aboutdialog.set_name('Itaka') |
|---|
| 535 | self.aboutdialog.set_version(self.itakaglobals.version) |
|---|
| 536 | self.aboutdialog.set_copyright(u'© 2003-2007 Marc E.') |
|---|
| 537 | self.aboutdialog.set_comments('Screenshooting de mercado.') |
|---|
| 538 | self.aboutdialog.set_authors(['Marc E. <santusmarc@gmail.com>']) |
|---|
| 539 | self.aboutdialog.set_artists(['Marc E. <santusmarc@gmail.com>', 'Tango Project (http://tango.freedesktop.org)']) |
|---|
| 540 | self.aboutdialog.set_license('''Itaka is free software; you can redistribute it and/or modify |
|---|
| 541 | it under the terms of the GNU General Public License as published by |
|---|
| 542 | the Free Software Foundation; either version 2 of the License, or |
|---|
| 543 | any later version. |
|---|
| 544 | |
|---|
| 545 | Itaka is distributed in the hope that it will be useful, |
|---|
| 546 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 547 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 548 | GNU General Public License for more details. |
|---|
| 549 | |
|---|
| 550 | You should have received a copy of the GNU General Public License |
|---|
| 551 | along with Itaka; if not, write to the Free Software |
|---|
| 552 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA''') |
|---|
| 553 | self.aboutdialog.set_website('http://itaka.jardinpresente.com.ar') |
|---|
| 554 | self.aboutdialog.set_logo(gtk.gdk.pixbuf_new_from_file(os.path.join(self.itakaglobals.image_dir, "itaka64x64.png"))) |
|---|
| 555 | self.aboutdialog.set_icon(self.icon_pixbuf) |
|---|
| 556 | self.aboutdialog.run() |
|---|
| 557 | self.aboutdialog.destroy() |
|---|
| 558 | |
|---|
| 559 | def __expandlogger(self, expander, params): |
|---|
| 560 | """ Callback for the expander widget. """ |
|---|
| 561 | if self.expander.get_expanded(): |
|---|
| 562 | # Show the debugvbox() and it's subwidgets |
|---|
| 563 | self.logvbox.show_all() |
|---|
| 564 | |
|---|
| 565 | self.expander.add(self.logvbox) |
|---|
| 566 | else: |
|---|
| 567 | self.expander.remove(self.expander.child) |
|---|
| 568 | self.window.resize(self.window.initial_size[0], self.window.initial_size[1]) |
|---|
| 569 | return |
|---|
| 570 | |
|---|
| 571 | def logger(self, args, failure=False, failuretype=None, eventsonly=False, icon=None): |
|---|
| 572 | """ Handle logging in the GUI. Arguments: 'args' is a dict { 'key': [str(msg)]], however, when handling failures the message tuple contains an extra item containing a detailed message for separation in the GUI log viewers. 'failure' is a boolean specifying whether we are logging an error. 'failuretype' is a string specyfing what kind of failure it is, either 'error', 'warning' or 'debug'. 'eventsonly' is a boolean to spcecify if the log message will only go to the events log. 'icon' is tuple, 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. It is used for the event log in the GUI """ |
|---|
| 573 | |
|---|
| 574 | self.message = args['message'][0] |
|---|
| 575 | # The detailed log gets the detailed mesage on failures |
|---|
| 576 | if failure: |
|---|
| 577 | self.message = args['message'][1] |
|---|
| 578 | |
|---|
| 579 | # Write out the message to the GUI |
|---|
| 580 | self.logdetailsbuffer.insert_at_cursor("\r" +self.message,len("\r" + self.message)) |
|---|
| 581 | # Automatically scroll. Use wrap until fix. |
|---|
| 582 | self.logdetailstextview.scroll_mark_onscreen(self.logdetailsbuffer.get_insert()) |
|---|
| 583 | |
|---|
| 584 | if eventsonly or failure: |
|---|
| 585 | # The event log |
|---|
| 586 | if failure: |
|---|
| 587 | # Use the non-detailed mesage |
|---|
| 588 | self.message = args['message'][0] |
|---|
| 589 | # Failures can not set icons, we set them. |
|---|
| 590 | if failuretype == "error": |
|---|
| 591 | icon = ['stock', 'STOCK_DIALOG_ERROR'] |
|---|
| 592 | elif failuretype == "warning": |
|---|
| 593 | icon = ['stock', 'STOCK_DIALOG_WARNING'] |
|---|
| 594 | elif failuretype == "debug": |
|---|
| 595 | icon = ['stock', 'STOCK_DIALOG_INFO'] |
|---|
| 596 | |
|---|
| 597 | if icon is not None: |
|---|
| 598 | if icon[0] == "stock": |
|---|
| 599 | self.insertediter = self.logeventsstore.append([self.logeventstreeview.render_icon(stock_id=getattr(gtk, icon[1]), size=gtk.ICON_SIZE_MENU, detail=None), self.message]) |
|---|
| 600 | # Select the item if its a failure |
|---|
| 601 | if failure: |
|---|
| 602 | self.selection = self.logeventstreeview.get_selection() |
|---|
| 603 | self.selection.select_iter(self.insertediter) |
|---|
| 604 | |
|---|
| 605 | else: |
|---|
| 606 | self.logeventsstore.append([icon[1], self.message]) |
|---|
| 607 | else: |
|---|
| 608 | self.logeventsstore.append([None, self.message]) |
|---|
| 609 | |
|---|
| 610 | def clearlogger(self, args): |
|---|
| 611 | """ Callback to clear the log """ |
|---|
| 612 | self.logeventsstore.clear() |
|---|
| 613 | self.logdetailsbuffer.set_text("") |
|---|
| 614 | |
|---|
| 615 | def pauselogger(self, widget, data=None): |
|---|
| 616 | """ Callback to pause log output. """ |
|---|
| 617 | if widget.get_active(): |
|---|
| 618 | # It would be nice if we could set a center background image to our textview. |
|---|
| 619 | # However, GTK makes that very hard. |
|---|
| 620 | """ |
|---|
| 621 | self.logprepausetext = self.logdetailsbuffer.get_text(self.logdetailsbuffer.get_start_iter(), self.logdetailsbuffer.get_end_iter()) |
|---|
| 622 | self.logdetailsbuffer.set_text("") |
|---|
| 623 | |
|---|
| 624 | self.logdetailsbuffer.create_tag ('center-image', justification = gtk.JUSTIFY_CENTER) |
|---|
| 625 | self.logdetailsimageiter = self.logdetailsbuffer.get_iter_at_offset(0) |
|---|
| 626 | self.logdetailsbuffer.insert_pixbuf(self.logdetailsimageiter, self.logdetailstextview.render_icon(stock_id=getattr(gtk, 'STOCK_MEDIA_PAUSE'), size=gtk.ICON_SIZE_DIALOG, detail=None)) |
|---|
| 627 | self.logdetailsbuffer.apply_tag_by_name('center-image', self.logdetailsbuffer.get_iter_at_offset(0), self.logdetailsimageiter) |
|---|
| 628 | |
|---|
| 629 | """ |
|---|
| 630 | self.logeventsstore.append([self.logeventstreeview.render_icon(stock_id=getattr(gtk, 'STOCK_MEDIA_PAUSE'), size=gtk.ICON_SIZE_MENU, detail=None), "Logging paused"]) |
|---|
| 631 | |
|---|
| 632 | self.logeventstreeview.set_sensitive(False) |
|---|
| 633 | self.logdetailstextview.set_sensitive(False) |
|---|
| 634 | |
|---|
| 635 | log.removeObserver(self.logger) |
|---|
| 636 | else: |
|---|
| 637 | log.addObserver(self.logger) |
|---|
| 638 | self.logdetailstextview.set_sensitive(True) |
|---|
| 639 | self.logeventstreeview.set_sensitive(True) |
|---|
| 640 | |
|---|
| 641 | self.logeventsstore.append([self.logeventstreeview.render_icon(stock_id=getattr(gtk, 'STOCK_MEDIA_PLAY'), size=gtk.ICON_SIZE_MENU, detail=None), "Logging resumed"]) |
|---|
| 642 | |
|---|
| 643 | def main(self): |
|---|
| 644 | """ Main init function. Starts the GUI reactors.""" |
|---|
| 645 | |
|---|
| 646 | # Server reactor (interacts with the Twisted reactor) |
|---|
| 647 | self.sreact = reactor.run() |
|---|
| 648 | |
|---|
| 649 | def __preferencesComboChanged(self, widget): |
|---|
| 650 | """ Callback for when a preferenes gtk.combo_box is changed """ |
|---|
| 651 | if self.preferencesComboformat.get_active_text() == "PNG": |
|---|
| 652 | self.preferencesHBox3.set_sensitive(False) |
|---|
| 653 | else: |
|---|
| 654 | self.preferencesHBox3.set_sensitive(True) |
|---|
| 655 | |
|---|
| 656 | def __checkwidget(self, widget): |
|---|
| 657 | """ Check the status of the toggle button """ |
|---|
| 658 | if hasattr(widget, 'get_active'): |
|---|
| 659 | return widget.get_active() |
|---|
| 660 | else: |
|---|
| 661 | return False |
|---|
| 662 | |
|---|
| 663 | def startstop(self, widget, traydata=None, dontexpandlogger = False): |
|---|
| 664 | """ Start or stop the screenshooting server. 'traydata' is a string either 'start' or 'stop' to be used from the Status tray icon or error handling. 'dontexpandlogger' handles whether the logger is expanded or not by default when changing status. """ |
|---|
| 665 | if (self.__checkwidget(widget) or traydata == "start"): |
|---|
| 666 | if self.server_listening: return |
|---|
| 667 | # NOTE: Twisted doesnt support hot-restarting as stopListening()/startListening(), just use the old one again |
|---|
| 668 | |
|---|
| 669 | # Set up the twisted site |
|---|
| 670 | # Pass a reference of GUI and Console instance to Screenshot module for its notification handling. |
|---|
| 671 | self.sinstance = iserver.ImageResource(self, self.console) |
|---|
| 672 | self.root = static.Data(self.configuration['html']['html'], 'text/html; charset=UTF-8') |
|---|
| 673 | self.root.putChild('screenshot', self.sinstance) |
|---|
| 674 | self.root.putChild('', self.root) |
|---|
| 675 | self.site = server.Site(self.root) |
|---|
| 676 | |
|---|
| 677 | # Start the server. Make an instance to distinguish from self.sreactor(). |
|---|
| 678 | try: |
|---|
| 679 | self.ilistener = reactor.listenTCP(int(self.configuration['server']['port']), self.site) |
|---|
| 680 | self.server_listening = True |
|---|
| 681 | except twisted.internet.error.CannotListenError: |
|---|
| 682 | self.console.error(('Gui', 'startstop'), 'Failed to start server, port %s is already in use' % (self.configuration['server']['port']), self) |
|---|
| 683 | # NOTE: This actually calls startstop to stop the server again, acts as a click |
|---|
| 684 | self.buttonStartstop.set_active(False) |
|---|
| 685 | return |
|---|
| 686 | |
|---|
| 687 | # Announce on log & console stdout |
|---|
| 688 | if self.configuration['screenshot']['format'] == "jpeg": |
|---|
| 689 | self.console.msg('Server started on port %s' % (self.configuration['server']['port']), self, True, ['stock', 'STOCK_CONNECT']) |
|---|
| 690 | self.console.msg('Server started on port %s TCP. Serving %s images with %s%% quality.' % (self.configuration['server']['port'], self.configuration['screenshot']['format'].upper(), self.configuration['screenshot']['quality']), self, False, ['stock', 'STOCK_CONNECT']) |
|---|
| 691 | else: |
|---|
| 692 | self.console.msg('Server started on port %s.' % (self.configuration['server']['port']), self, True, ['stock', 'STOCK_CONNECT']) |
|---|
| 693 | self.console.msg('Server started on port %s TCP. Serving %s images.' % (self.configuration['server']['port'], self.configuration['screenshot']['format'].upper()), self, False, ['stock', 'STOCK_CONNECT']) |
|---|
| 694 | |
|---|
| 695 | # Change buttons |
|---|
| 696 | self.buttonStartstop.set_active(True) |
|---|
| 697 | self.buttonStartstop.set_label("Stop") |
|---|
| 698 | self.startstopimage.set_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_BUTTON) |
|---|
| 699 | self.buttonStartstop.set_image(self.startstopimage) |
|---|
| 700 | |
|---|
| 701 | self.statusIcon.set_tooltip("Itaka - Server running") |
|---|
| 702 | self.menuitemstart.set_sensitive(False) |
|---|
| 703 | self.menuitemstop.set_sensitive(True) |
|---|
| 704 | |
|---|
| 705 | # Close the expander |
|---|
| 706 | self.expander.set_sensitive(True) |
|---|
| 707 | |
|---|
| 708 | # I am not sure about this, notification |
|---|
| 709 | # if (self.configuration['server']['notify'] == "True"): |
|---|
| 710 | # self.itakaLogo.set_from_file(os.path.join(self.itakaglobals.image_dir, "itaka-take.png")) |
|---|
| 711 | |
|---|
| 712 | else: |
|---|
| 713 | if self.server_listening: |
|---|
| 714 | self.console.msg("Server stopped", self, True, ['stock', 'STOCK_DISCONNECT']) |
|---|
| 715 | |
|---|
| 716 | self.ilistener.stopListening() |
|---|
| 717 | self.server_listening = False |
|---|
| 718 | # Stop the g_timeout |
|---|
| 719 | if hasattr(self, 'iagotimer'): |
|---|
| 720 | gobject.source_remove(self.iagotimer) |
|---|
| 721 | |
|---|
| 722 | # Change GUI elements |
|---|
| 723 | if (traydata): |
|---|
| 724 | self.buttonStartstop.set_active(False) |
|---|
| 725 | |
|---|
| 726 | self.statusIcon.set_tooltip("Itaka") |
|---|
| 727 | self.startstopimage.set_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON) |
|---|
| 728 | self.buttonStartstop.set_image(self.startstopimage) |
|---|
| 729 | self.buttonStartstop.set_label("Start") |
|---|
| 730 | self.labelLastip.set_text('') |
|---|
| 731 | self.labelTime.set_text('') |
|---|
| 732 | self.labelServed.set_text('') |
|---|
| 733 | if not dontexpandlogger: |
|---|
| 734 | self.expander.set_expanded(False) |
|---|
| 735 | self.expander.set_sensitive(False) |
|---|
| 736 | self.itakaLogo.set_from_file(os.path.join(self.itakaglobals.image_dir, "itaka.png")) |
|---|
| 737 | self.menuitemstart.set_sensitive(True) |
|---|
| 738 | self.menuitemstop.set_sensitive(False) |
|---|
| 739 | |
|---|
| 740 | def restart_server(self): |
|---|
| 741 | if self.server_listening: |
|---|
| 742 | self.console.msg("Restarting the server to listen on port %s" % (self.configuration['server']['port']), self, True, ['stock', 'STOCK_REFRESH']) |
|---|
| 743 | self.startstop(None, "stop") |
|---|
| 744 | self.startstop(None, "start") |
|---|
| 745 | |
|---|
| 746 | def destroy(self, *args): |
|---|
| 747 | """ Callback for the main window's destroy. """ |
|---|
| 748 | # Stop server. |
|---|
| 749 | if self.server_listening: |
|---|
| 750 | self.console.msg("Shutting down server...") |
|---|
| 751 | self.ilistener.stopListening() |
|---|
| 752 | del self.console |
|---|
| 753 | else: |
|---|
| 754 | # Console goodbye! |
|---|
| 755 | if hasattr(self, 'console'): |
|---|
| 756 | del self.console |
|---|
| 757 | |
|---|
| 758 | # Remove stale screenshot and quit |
|---|
| 759 | if os.path.exists(os.path.join(self.configuration['screenshot']['path'], 'itakashot.%s' % (self.configuration['screenshot']['format']))): |
|---|
| 760 | os.remove(os.path.join(self.configuration['screenshot']['path'], 'itakashot.%s' % (self.configuration['screenshot']['format']))) |
|---|
| 761 | |
|---|
| 762 | # Windows needs this... |
|---|
| 763 | del self.statusIcon |
|---|
| 764 | |
|---|
| 765 | gtk.main_quit() |
|---|
| 766 | |
|---|
| 767 | def __calcsince(self, dtime): |
|---|
| 768 | """ Function to calculate the time difference from the last request to |
|---|
| 769 | the current time. Express a datetime.timedelta using a |
|---|
| 770 | phrase such as "1 hour, 20 minutes". """ |
|---|
| 771 | |
|---|
| 772 | # Create a timedelta from the datetime.datetime and the current time |
|---|
| 773 | # (you can create your own timedeltas with datetime.timedelta(5, (650 * |
|---|
| 774 | # 60) * 2, 12) for testing. |
|---|
| 775 | self.td = datetime.datetime.now() - dtime |
|---|
| 776 | |
|---|
| 777 | self.pieces = [] |
|---|
| 778 | if self.td.days: |
|---|
| 779 | self.pieces.append(self.__plural(self.td.days, 'day')) |
|---|
| 780 | |
|---|
| 781 | self.minutes, self.seconds = divmod(self.td.seconds, 60) |
|---|
| 782 | self.hours, self.minutes = divmod(self.minutes, 60) |
|---|
| 783 | if self.hours: |
|---|
| 784 | self.pieces.append(self.__plural(self.hours, 'hour')) |
|---|
| 785 | if self.minutes or len(self.pieces) == 0: |
|---|
| 786 | self.pieces.append(self.__plural(self.minutes, 'minute')) |
|---|
| 787 | |
|---|
| 788 | # "Time: " + ", ".join(self.pieces[:-1]) + "and" + self.pieces[-1] + " ago" |
|---|
| 789 | |
|---|
| 790 | self.labelTime.set_text("<b>Time</b>: " + ", ".join(self.pieces) + " ago") |
|---|
| 791 | self.labelTime.set_use_markup(True) |
|---|
| 792 | |
|---|
| 793 | # Need this so it runs more than once. Weird. |
|---|
| 794 | return True |
|---|
| 795 | |
|---|
| 796 | def __plural(self, count, singular): |
|---|
| 797 | """ This is a helper function that handles english plural translations """ |
|---|
| 798 | |
|---|
| 799 | # This is the simplest version; a more general version |
|---|
| 800 | # should handle -y -> -ies, child -> children, etc. |
|---|
| 801 | return '%d %s%s' % (count, singular, ("", 's')[count != 1]) |
|---|
| 802 | |
|---|
| 803 | def notify(self): |
|---|
| 804 | """ Change the image on the main screen, for notification purpose. """ |
|---|
| 805 | self.itakaLogo.set_from_file(os.path.join(self.itakaglobals.image_dir, "itaka.png")) |
|---|
| 806 | self.statusIcon.set_from_pixbuf(self.icon_pixbuf) |
|---|
| 807 | # Only run this event once |
|---|
| 808 | return False |
|---|
| 809 | |
|---|
| 810 | def talk(self, action, number=False, ip=False, time=False): |
|---|
| 811 | """ Handler for communcations between the server backend, and the GUI. """ |
|---|
| 812 | if (action == "updateGuiStatus" ): |
|---|
| 813 | self.console.msg("Screenshot number " + str(number) + " served to " + str(ip), self, True, ['pixbuf', gtk.gdk.pixbuf_new_from_file(os.path.join(self.itakaglobals.image_dir, "itaka16x16-take.png"))]) |
|---|
| 814 | |
|---|
| 815 | self.labelServed.set_text("<b>Served</b>: " + str(number)) |
|---|
| 816 | self.labelServed.set_use_markup(True) |
|---|
| 817 | self.labelLastip.set_text("<b>IP</b>: " + str(ip)) |
|---|
| 818 | self.labelLastip.set_use_markup(True) |
|---|
| 819 | self.statusIcon.set_tooltip("Itaka - %s served" % (self.__plural(int(number), 'screenshot'))) |
|---|
| 820 | # if (self.configuration['server']['notify'] == "True"): |
|---|
| 821 | # Show the camera image on tray and interface for 1.5 seconds |
|---|
| 822 | self.itakaLogo.set_from_file(os.path.join(self.itakaglobals.image_dir, "itaka-take.png")) |
|---|
| 823 | self.statusIcon.set_from_file(os.path.join(self.itakaglobals.image_dir, "itaka-take.png")) |
|---|
| 824 | self.itakaLogo.set_from_file(os.path.join(self.itakaglobals.image_dir, "itaka-take.png")) |
|---|
| 825 | self.notifyimg = gobject.timeout_add(1500, self.notify) |
|---|
| 826 | |
|---|
| 827 | # Call the update timer function, and add a timer to update the GUI of its |
|---|
| 828 | # "Last screenshot taken" time |
|---|
| 829 | self.__calcsince(time) |
|---|
| 830 | if hasattr(self, 'iagotimer'): gobject.source_remove(self.iagotimer) |
|---|
| 831 | self.iagotimer = gobject.timeout_add(60000, self.__calcsince, time) |
|---|