This file is indexed.

/usr/share/stackapplet/stackapplet.py is in stackapplet 1.5.2-1.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
#! /usr/bin/python

#===================================
#		   stackapplet
#  Copyright 2011 - Nathan Osman
#
#	 Contains the main class
#	  used by StackApplet
#
#   StackApplet is released under
#		the MIT license
#===================================

# Standard modules
import os
import platform
import signal
import sys
import time
import urllib2
import webbrowser

# The following modules are required
# for supporting translations
import locale
import gettext

# We create a shortcut for the gettext function
# which will later point to the function that can
# be used to retrieve text in the appropriate language.
_ = None

# Global definitions
__application__ = "StackApplet"
__authors__	    = ["Nathan Osman", "Isaiah Heyer"]
__artists__	    = ["Marco Ceppi"]
__translators__ = "translator-credits"
__copyright__   = "Copyright (c) 2014, Nathan Osman"
__license__	    = '''Copyright (c) 2014 Nathan Osman

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.'''
__version__	 = "1.5.2"

# Before we do much else, we are going to
# suppress any warnings - these wreak havok
# on Windows.

if platform.system() == 'Windows':
	import warnings
	warnings.simplefilter('ignore')

# modules required for using GTK
import pygtk
pygtk.require('2.0')
import gtk
import gobject

# Now for our own modules
# =======================

# First try to import the native app
# indicator module. Failing that, use our
# fallback module instead.
try:
	import appindicator
except ImportError:
	import appindicator_replacement as appindicator

# Do the exact same thing with pynotify.
# Use our fallback module if it's not available
try:
	import pynotify
except ImportError:
	import pynotify_replacement as pynotify

# Initialize PyNotify
pynotify.init('StackApplet')

# ... the rest of them.
import config_store    # stores configuration settings / data
import defaults        # list of default settings
import network_thread  # for making anychronous network requests to the API
import preferences     # a web server that allows preferences to be changed

# Messaging menu support requires
try:
	import pymm
	CAN_USE_MM = 1
	
except ImportError:
	CAN_USE_MM = 0

#=======================================
# This is the root class that contains
# all of the core logic for StackApplet
#=======================================
class stackapplet:
	
	# Our constructor
	def __init__ (self):
		
		# Before doing anything, we set the current language
		self.set_language()
		
		# Begin by creating the application indicator which we
		# use for displaying the icon.
		self.indicator = appindicator.Indicator ("stackapplet-desktop-client",
		                                         "stackapplet_grey",
		                                         appindicator.CATEGORY_APPLICATION_STATUS)
		
		self.indicator.set_status(appindicator.STATUS_ACTIVE)
		self.indicator.set_attention_icon("stackapplet")
		
		# Create the settings object so that we
		# can retrieve the list of accounts, preferences,
		# and so on.
		self.prefs = config_store.config_store()
		self.prefs.load_settings()
		
		self.refresh_rate = self.prefs.get_val("refresh_rate", defaults.__default_refresh_rate__)
		language          = self.prefs.get_val("language",     defaults.__default_language__)
		
		if not language == "":
			self.set_language(language)
		
		# Determine which icon set to use.
		if self.prefs.get_val("theme", defaults.__default_theme__) == 'dark':
			icon_to_use = 'stackapplet_grey'
		else:
			icon_to_use = 'stackapplet_light'
		
		self.indicator.set_icon(icon_to_use)
		
		# Create the menus for the accounts
		self.initialize_menu()
		
		# Create the messaging menu entry as well.
		if CAN_USE_MM:
			self.mm = pymm.pymm("/usr/share/applications/stackapplet.desktop")
		
		# Create the threaded network manager
		self.network = network_thread.network_thread(self.process_error)
		
		# Immediately get it fetching the list of sites.
		self.stackauth = None
		self.network.issue_request("http://stackauth.com/1.0/sites?key=" + network_thread.API_KEY,
		                           self.process_stackauth,
		                           None,
		                           True,
		                           1)
		
		# Register one of our member functions with
		# the preference server so that we can supply
		# the requested details as necessary.
		preferences.register_callback(self.preference_callback)
		
		# Finally, begin updating the accounts.
		self.timer = None
		self.update()
	
	# If language == "", then the system default is used
	def set_language(self, language=""):
		
		# Bind the domain
		if not platform.system() == 'Windows':
			gettext.bindtextdomain("stackapplet", "/usr/share/locale")
		
		gettext.textdomain("stackapplet")
		
		if language == None:
			
			# Figure out the system locale
			(lang, enc) = locale.getdefaultlocale()
			
			# Set it to a sensible default if we couldn't
			# figure it out otherwise
			if lang == "":
				lang = "en_US"
		
		else:
			lang = language
		
		# Now create the translation object and
		# register the translation function
		if platform.system() == 'Windows':
			tr = gettext.translation("stackapplet",
									 "po",
									 [lang],
									 fallback=True)
		else:
			tr = gettext.translation("stackapplet",
									 "/usr/share/locale",
									 [lang],
									 fallback=True)
		
		# Now apply tr to our _ shortcut
		global _
		_ = tr.gettext
	
	def initialize_menu(self):
		
		# Create the root GTK menu
		self.root_menu = gtk.Menu()
		
		# Create the Accounts and Tools
		# menu headers
		menu_item_accounts_header = gtk.MenuItem(_("Accounts"))
		menu_item_accounts_header.set_sensitive(False)
		menu_item_accounts_header.show()
		self.root_menu.append(menu_item_accounts_header)
		
		# Now add the accounts to the list.
		# The return value tells us if any accounts
		# were added, and if so, we add a separator
		if self.populate_menu():
			separator = gtk.SeparatorMenuItem()
			separator.show()
			self.root_menu.append(separator)
			
		else:
			menu_item_accounts_header.set_label(_("No Accounts"))
		
		# Now we add the remaining menu items
		menu_item_tools_header = gtk.MenuItem(_("Tools"))
		menu_item_tools_header.set_sensitive(False)
		menu_item_tools_header.show()
		self.root_menu.append(menu_item_tools_header)
		
		# For these items, we can use the stock
		# GTK menuitems
		menu_item_refresh = gtk.ImageMenuItem(stock_id=gtk.STOCK_REFRESH)
		menu_item_prefs   = gtk.ImageMenuItem(stock_id=gtk.STOCK_PREFERENCES)
		menu_item_about   = gtk.ImageMenuItem(stock_id=gtk.STOCK_ABOUT)
		menu_item_quit    = gtk.ImageMenuItem(stock_id=gtk.STOCK_QUIT)
		
		menu_item_refresh.connect("activate", self.menu_refresh_activate)
		menu_item_prefs  .connect("activate", self.menu_prefs_activate)
		menu_item_about  .connect("activate", self.menu_about_activate)
		menu_item_quit   .connect("activate", self.menu_quit_activate)
		
		menu_item_refresh.show()
		menu_item_prefs  .show()
		menu_item_about  .show()
		menu_item_quit   .show()
		
		# Add them all to the menu
		self.root_menu.append(menu_item_refresh)
		self.root_menu.append(menu_item_prefs)
		self.root_menu.append(menu_item_about)
		self.root_menu.append(menu_item_quit)
		
		self.indicator.set_menu(self.root_menu)
	
	def populate_menu(self):
		
		# Reset our accounts container
		self.accounts = []
		
		# Loop through each of the accounts in the list
		entries = self.prefs.get_val("accounts", defaults.__default_accounts__)
		accounts_processed = 0
		
		for entry in entries:
			
			self.create_menu_item(entry)
			accounts_processed += 1
		
		return accounts_processed
	
	def create_menu_item(self, entry):
		
		# Create the menu item
		menu_entry = gtk.MenuItem()
		menu_entry.set_label(_("Loading..."))
		
		# Add it to the menu
		menu_entry.show()
		self.root_menu.insert(menu_entry, 1 + len(self.accounts))
		
		# Create the messaging menu entry
		if CAN_USE_MM:
			mm_source = pymm.pymm_source(entry["site_name"])
			mm_source.set_count(0)
		else:
			mm_source = None
		
		# Now we need to check and see what values
		# are present in the configuration file.
		reputation_on_last_poll = entry['reputation_on_last_poll'] if 'reputation_on_last_poll' in entry else 0
		last_comment_ts         = entry['last_comment_ts']         if 'last_comment_ts'         in entry else 0
		last_answer_ts          = entry['last_answer_ts']          if 'last_answer_ts'          in entry else 0
		unread_reputation       = entry['unread_reputation']       if 'unread_reputation'       in entry else 0
		unread_comments         = entry['unread_comments']         if 'unread_comments'         in entry else 0
		unread_answers          = entry['unread_answers']          if 'unread_answers'          in entry else 0
		notifications           = entry['notifications']           if 'notifications'           in entry else 0
		
		account = { "menu"      : menu_entry,           # the Gtk menu
		            "mm_source" : mm_source,            # the Messaging menu source
		            "site"      : entry['site'],        # the URL of the SE site with this account
		            "site_name" : entry['site_name'],   # the name of the SE site
		            "user_id"   : entry['user_id'],     # the user ID for the site
		            
		            "reputation_on_last_poll"   : reputation_on_last_poll, # the API will never return 0, so this is safe
		            "last_comment_ts"           : last_comment_ts,         # the last timestamp for comments to user
		            "last_answer_ts"	        : last_answer_ts,          # the last timestamp for answers to questions
		            "unread_reputation"         : unread_reputation,       # the amount of rep. earned that hasn't been seen
		            "unread_comments"           : unread_comments,         # the num. of comments that haven't been seen
		            "unread_answers"	        : unread_answers,          # the num. of answers that haven't been seen
		            "notifications"             : notifications }          # whether this item has notifications or not
		
		# If there are any notifications, display
		# the indicator.
		if notifications:
			self.activate_notification(account)
		
		# Set the handlers
		menu_entry.connect("activate", self.menu_item_activate, account)
		
		if CAN_USE_MM:
			mm_source.source.connect("user-display", self.messaging_item_activate, account)
		
		# Add this item to our account list
		self.accounts.append(account)
	
	def process_stackauth(self, data, ignored):
		
		self.stackauth = data
	
	def display_notification(self, title, message, image):
		
		# The image in this case is expected to either
		# be an icon name (in which case we display it)
		# or a complete URL (in which case we fetch it)
		if image.startswith("/"):
			
			# Local path, so display it.
			n = pynotify.Notification(title, message, image)
			n.show()
				
		else:
		
			# Check to see if this image is in the cache
			img_path = os.path.join(os.path.expanduser("~"),".stackapplet")
			img_path = os.path.join(img_path, 'logos')
			img_fn = os.path.join(img_path, image + ".png")
			
			if os.path.isfile(img_fn):
			
				# It's in the cache, so simply display
				# the image.
				n = pynotify.Notification(title, message, img_fn)
				n.show()
			
			else:
			
				# Make sure that the dir exists
				if not os.path.isdir(img_path):
					os.makedirs(img_path)
				
				# We need to determine which site is equivalent to this image.
				for site in self.stackauth['api_sites']:
					
					if site['site_url'] == 'http://' + image + '.com':
						
						self.network.issue_request(site['icon_url'],
						                           self.save_and_display_notification,
						                           { 'title': title,
						                             'message': message,
						                             'image_location': img_fn},
						                           False)
					
	def save_and_display_notification(self, data, info):
	
		# Dump data to the file.
		f = open(info['image_location'], "wb")
		f.write(data)
		f.close()
		
		self.display_notification(info['title'],
		                          info['message'],
		                          info['image_location'])
	
	def activate_notification(self, account):
		
		# Set the color of our indicator
		self.indicator.set_status(appindicator.STATUS_ATTENTION)
		
		# Also set the active state of the messaging menu
		if CAN_USE_MM:
			account['mm_source'].set_notify(True)
	
	# Check to see if there are any pending notifications
	# for any of the accounts.
	def test_notifications(self):
		
		any_notifications = 0
		
		for account in self.accounts:
			
			if account['notifications']:
			
				if CAN_USE_MM:
					account['mm_source'].set_notify(True)
				
				any_notifications = 1
			
			else:
				
				if CAN_USE_MM:
					account['mm_source'].set_notify(False)
		
		if any_notifications:
			self.indicator.set_status(appindicator.STATUS_ATTENTION)
		else:
			self.indicator.set_status(appindicator.STATUS_ACTIVE)
	
	# Process an error that we receive from the
	# network thread
	def process_error(self, data, error_str):
	
		# If it's an account, then at least
		# display a notification in that account
		# that there was an error retrieving the data.
		if data in self.accounts:
			
			data['menu'].set_label(error_str)
	
	# This function is responsible for dispatching
	# the API requests and making sure that when the
	# requests are completed that they are processed
	# by the right functions.
	def update(self):
		
		# Remove the current timer
		if(not self.timer == None):
			gobject.source_remove(self.timer)
		
		for account in self.accounts:
			
			self.update_account(account)
		
		# Now run the update again after the specified
		# timeout has been reached
		self.timer = gobject.timeout_add(self.refresh_rate, self.update)
	
	def update_account(self, account):
		
		# Start by dispatching a request to get the
		# current reputation for this account.
		self.network.issue_api_request(account['site'],
		                               '/users/' + account['user_id'],
		                               self.update_reputation,
		                               account)
		
		self.network.issue_api_request(account['site'],
		                               '/users/' + account['user_id'] + '/mentioned',
		                               self.update_comments,
		                               account,
		                               'fromdate=' + str(account["last_comment_ts"]))
		
		self.network.issue_api_request(account['site'],
		                               '/users/' + account['user_id'] + '/questions',
		                               self.update_answers_1,
		                               account)
	
	def update_reputation(self, data, account):
		
		# First make sure that the account still exists
		if not account in self.accounts:
			return
		
		# First check to see if there is any new reputation
		current_rep = data['items'][0]['reputation']
		
		# Check to see if there has been a change
		if not current_rep == account['reputation_on_last_poll'] and not account['reputation_on_last_poll'] == 0:
			
			# Calculate the change
			change = current_rep - account['reputation_on_last_poll']
			
			# Add this new reputation to the amount of reputation
			# that the user has not seen yet.
			account['unread_reputation'] += change
			
			# Display a notification
			if change > 0:
				message = _("You have gained %s reputation.") % (change)
			else:
				message = _("You have lost %s reputation.") % (abs(change))
			
			self.display_notification(account["site_name"] + ":", message, account['site'])
			
			# Last but not least, we need to set that this item
			# has a pending unread notification
			account['notifications'] = 1
			
			self.activate_notification(account)
			
		# Update the menu captions
		self.set_menu_label(account, current_rep)
			
		# Set this as the new reputation.
		account['reputation_on_last_poll'] = current_rep
	
	def update_comments(self, data, account):
		
		# First make sure that the account still exists
		if not account in self.accounts:
			return
		
		if len(data['items']) and account["last_comment_ts"]:
			
			# We know there is one or more comments
			
			if len(data['items']) == 1:
				message = _("%s has posted a comment to you.") % (data['items'][0]["owner"]["display_name"])
			else:
				message = _("You have received %s new comments.") % (len(data['items']))
			
			self.display_notification(account["site_name"] + ":", message, account['site'])
			
			account['unread_comments'] += len(data['items'])
			account['notifications'] = 1
			
			self.set_menu_label(account, account['reputation_on_last_poll'])
			
			self.activate_notification(account)
		
		# Set the timestamp
		account["last_comment_ts"] = int(time.time())
	
	def update_answers_1(self, data, account):
		
		# First make sure that the account still exists
		if not account in self.accounts:
			return
		
		# We are only half of the way there. We have the users'
		# questions... now get the answers to those questions.
		
		# Create a vectorized list of question ids
		id_array = []
		
		for question in data['items']:
			id_array.append(str(question["question_id"]))
		
		if len(id_array) and account["last_answer_ts"]:
			
			# Join the IDs
			id_str = ";".join(id_array)
			
			# Issue the second part of the request
			self.network.issue_api_request(account['site'],
				                           '/questions/' + id_str + '/answers',
				                           self.update_answers_2,
				                           account,
				                           'fromdate=' + str(account["last_answer_ts"]))
				                           
			# ...and the third part - which will get us a list
			# of comments to the question list we have
			self.network.issue_api_request(account['site'],
				                           '/posts/' + id_str + '/comments',
				                           self.update_post_comments,
				                           account,
				                           'fromdate=' + str(account["last_answer_ts"]))
		
		# Set the timestamp
		account["last_answer_ts"] = int(time.time())
	
	def update_answers_2(self, data, account):
		
		# First make sure that the account still exists
		if not account in self.accounts:
			return
		
		# See if there are any new answers
		if len(data['items']):
			
			# Display a notification
			message = _("Your have received %s new answer(s) to your questions.") % (len(data['items']))
			self.display_notification(account["site_name"] + ":", message, account['site'])
			
			account['unread_answers'] += len(data['items'])
			account['notifications'] = 1
			
			self.set_menu_label(account, account['reputation_on_last_poll'])
			
			self.activate_notification(account)
	
	def update_post_comments(self, data, account):
		
		# See if there are any comments that matched the criterion
		
		# First make sure that the account still exists
		if not account in self.accounts:
			return
		
		# We know there is one or more comments but are they
		# comments that were made by that user?
		for comment in data['items']:
			if int(account['user_id']) == comment["owner"]["user_id"]:
				data['items'].remove(comment)
		
		if len(data['items']):
			
			if len(data['items']) == 1:
				message = _("%s has posted a comment on your question.") % (data['items'][0]["owner"]["display_name"])
			else:
				message = _("You have received %s new comments to your question.") % (len(data['items']))
			
			self.display_notification(account["site_name"] + ":", message, account['site'])
			
			account['unread_comments'] += len(data['items'])
			account['notifications'] = 1
			
			self.set_menu_label(account, account['reputation_on_last_poll'])
			
			self.activate_notification(account)
		
		# Either way, save the account information
		self.save_account_info()
	
	def set_menu_label(self, account, reputation):
		
		# Construct the label we will be using for the account in the menu
		label = account['site_name'] + ' / ' + str(reputation)
		
		# Check for unread reputation
		if account['unread_reputation']:
			label += _(' [%s reputation]') % (account['unread_reputation'])
		
		# Check for comments
		if account['unread_comments']:
			label += _(' [%s comments]') % (account['unread_comments'])
		
		# Check for new answers
		if account['unread_answers']:
			label += _(' [%s answers]') % (account['unread_answers'])
		
		if account['notifications']:
			label = "* " + label
		
		account['menu'].set_label(label)
		
		# Don't forget about the MM
		if CAN_USE_MM:
			account['mm_source'].set_count(account['unread_comments'] + account['unread_answers'])
	
	def preference_callback(self, command, event, data):
		
		# Process the request that was made
		if command == 'get_settings':
		
			data['accounts']     = self.accounts[:]
			data['theme']        = str(self.prefs.get_val('theme', defaults.__default_theme__))
			data['refresh_rate'] = int(self.refresh_rate)
			data['language']     = str(self.prefs.get_val('language', defaults.__default_language__))
		
		elif command == 'get_translation':
			
			global _
			data['translation'] = _
			
		elif command == 'add_account':
			
			# Add the account
			self.create_menu_item(data)
			
			self.update_account(self.accounts[len(self.accounts) - 1])
			
		elif command == 'remove_account':
			
			index = int(data)
			
			# Remove the menu item and MM entry
			self.root_menu.remove(self.accounts[index]['menu'])
			
			if CAN_USE_MM:
				del self.accounts[index]['mm_source']
			
			# Delete the account
			del self.accounts[index]
			
			self.test_notifications()
		
		elif command == 'reorder':
			
			# We need to grab a copy of the item
			account = self.accounts[int(data['old_index'])]
			
			# Now remove it from the list
			self.accounts.remove(account)
			
			# Insert it into its new location
			self.accounts.insert(int(data['new_index']), account)
			
			# We also need to switch the menu items around too.
			self.root_menu.remove(account['menu'])
			self.root_menu.insert(account['menu'], 1 + int(data['new_index']))
		
		elif command == 'set_rate':
			
			self.refresh_rate = int(data)
			
			self.prefs.set_val('refresh_rate', int(data))
		
		elif command == 'set_theme':
			
			if data == 'dark':
				icon_to_use = 'stackapplet_grey'
			else:
				icon_to_use = 'stackapplet_light'
			
			self.indicator.set_icon(icon_to_use)
			self.prefs.set_val('theme', str(data))
		
		elif command == 'set_language':
			
			self.set_language(str(data))
			self.prefs.set_val('language', str(data))
		
		# Set the event to allow the other thread
		# to continue along.
		event.set()
	
	def menu_item_activate(self, widget, account):
		
		# We need to either reset any unread stuff
		# or if there isn't any, then launch the user's
		# profile page.
		if account['notifications']:
			
			# Reset everything.
			account['notifications']     = 0
			account['unread_reputation'] = 0
			account['unread_comments']   = 0
			account['unread_answers']    = 0
			
			# Reset the menu caption
			self.set_menu_label(account, account['reputation_on_last_poll'])
			
			# Recheck for global notifications
			self.test_notifications()
		
		# Otherwise, display their profile page
		else:
			
			webbrowser.open("http://" + account["site"] + ".com/users/" + str(account["user_id"]))
	
	# Note that I have no clue what the third parameter is since
	# it is not documented ANYWHERE.
	def messaging_item_activate(self, indicator, unknown_data, account):
		
		# Just turn around and call menu_item_activate
		self.menu_item_activate(None, account)
	
	def menu_refresh_activate(self, widget):
		
		self.update()
	
	def menu_prefs_activate(self, widget):
		
		preferences.trigger()
	
	def menu_about_activate(self, widget):
		
		global _,__application__,__author__,__copyright__,__license__,__version__
		
		# Display the about dialog box
		dialog = gtk.AboutDialog()
		
		dialog.set_name(_(__application__))
		dialog.set_authors(__authors__)
		dialog.set_artists(__artists__)
		dialog.set_translator_credits(_(__translators__))
		dialog.set_copyright(_(__copyright__))
		dialog.set_license(_(__license__))
		dialog.set_version(__version__)
		
		if platform.system() == 'Windows':
			dialog.set_logo(gtk.gdk.pixbuf_new_from_file(os.path.join(os.path.dirname(sys.executable), "images/stackapplet.png")))
		else:
			dialog.set_logo_icon_name("stackapplet")
		
		dialog.connect("response",lambda x,y: dialog.destroy())
		
		dialog.run()
	
	def menu_quit_activate(self, widget):
		
		self.shutdown()
		
	# Saves all account information
	def save_account_info(self):
		
		# Save the accounts
		accounts_data = []
		
		for account in self.accounts:
			
			# Take a copy
			copy_of_account = dict(account)
			
			# Remove two items
			del copy_of_account['menu']
			del copy_of_account['mm_source']
			
			accounts_data.append(copy_of_account)
		
		self.prefs.set_val('accounts', accounts_data)
		self.prefs.save_settings()
	
	# This is the safe shutdown function that
	# ensures all accounts' notification data
	# are saved with the accounts' information.
	def shutdown(self):
		
		# We need the network thread to shutdown.
		self.network.request_queue.put([0, None])
		self.network.join()
		
		# ...and we need the preferences thread to
		# shutdown.
		preferences.httpd.shutdown()
		preferences.thread.join()
		
		self.save_account_info();
		
		gtk.main_quit()

stackapplet_instance = None

# Do a quick check for old config files
if os.path.isfile(config_store.CONFIG_FILE_PATH):
	
	dialog = gtk.MessageDialog(None,
		                       gtk.DIALOG_MODAL,
		                       gtk.MESSAGE_INFO,gtk.BUTTONS_OK,
		                       _("StackApplet 1.5.1 has found some old configuration files from a previous version of StackApplet.\n\nThese files will be removed so that StackApplet can continue loading. If you had any accounts registered with StackApplet, you will need to add them again.\n\nNote: StackApplet is no longer a Gnome panel applet. It is an indicator and can be located in your system's notification or tray area."))
	dialog.set_title(_("StackApplet has been upgraded:"))
	dialog.connect("response", lambda x,y: dialog.destroy())
	dialog.run()

	# Delete the old files
	os.remove(config_store.CONFIG_FILE_PATH)

# SIGTERM handler
def signal_handler(a, b):
	
	global stackapplet_instance
	stackapplet_instance.shutdown()

# Create the instance of stackapplet and
# run the main GTK thread
if __name__ == "__main__":

	stackapplet_instance = stackapplet()

	# For Windows, we need our helper module
	if platform.system() == 'Windows':
		import w32_notifications
		w32_notifications.register_main_instance(stackapplet_instance)
	
	signal.signal(signal.SIGTERM, signal_handler)
		
	gtk.main()