mirror of
https://github.com/MCV-Software/TWBlue.git
synced 2024-11-22 11:18:08 -06:00
Putting all the code from the current master branch of TWBlue
This commit is contained in:
parent
58c82e5486
commit
1af4a8b291
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
*.pyc
|
||||||
|
*~
|
||||||
|
src/build/
|
||||||
|
src/dist/
|
||||||
|
src/config/
|
||||||
|
src/config1/
|
||||||
|
src/config2/
|
||||||
|
src/config3/
|
||||||
|
src/dropbox/
|
||||||
|
src/logs/
|
||||||
|
src/documentation/
|
||||||
|
src/oggenc2.exe
|
||||||
|
src/bootstrap.exe
|
||||||
|
src/Microsoft.VC90.CRT
|
||||||
|
src/Microsoft.VC90.MFC
|
||||||
|
src/launcher.bat
|
33
contributors.txt
Normal file
33
contributors.txt
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
Manuel E. Cortéz
|
||||||
|
Johana Hidrobo
|
||||||
|
Marcelo Sosa
|
||||||
|
Isabel del Castillo
|
||||||
|
José Manuel Delicado
|
||||||
|
David Picón
|
||||||
|
Vicente Llopis
|
||||||
|
Javier Dorado
|
||||||
|
Guillem León
|
||||||
|
Marco Serrano
|
||||||
|
Bryner Villalobos
|
||||||
|
Robert Spangler
|
||||||
|
Paweł Masarczyk
|
||||||
|
Odenilton Júnior Santos
|
||||||
|
Burak
|
||||||
|
Alexander Jaszyn
|
||||||
|
Salva Doménech
|
||||||
|
Juan Carlos Rivilla
|
||||||
|
Sukil Etxenike
|
||||||
|
Javier Currás
|
||||||
|
Alba Quinteiro
|
||||||
|
Jani Kinnunen
|
||||||
|
Mohammed Al Shara
|
||||||
|
Robert Osztolykan
|
||||||
|
Rémy Ruiz
|
||||||
|
Jorge Almeida
|
||||||
|
Jose Maria ortiz silva
|
||||||
|
Pablo Perello
|
||||||
|
Michael Lau
|
||||||
|
Paris-Apps
|
||||||
|
Salvadora Melguizo
|
||||||
|
Holly Scott-Gardner
|
||||||
|
Anibal Hernández
|
88
src/Conf.defaults
Normal file
88
src/Conf.defaults
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
[twitter]
|
||||||
|
user_key = string(default="")
|
||||||
|
user_secret = string(default="")
|
||||||
|
user_name = string(default="")
|
||||||
|
ignored_clients = list(default=list())
|
||||||
|
|
||||||
|
[general]
|
||||||
|
language = string(default="system")
|
||||||
|
relative_times = boolean(default=True)
|
||||||
|
hide_gui = boolean(default=False)
|
||||||
|
voice_enabled = boolean(default=False)
|
||||||
|
max_api_calls = integer(default=1)
|
||||||
|
max_tweets_per_call = integer(default=100)
|
||||||
|
reverse_timelines = boolean(default=False)
|
||||||
|
time_to_check_streams = integer(default=30)
|
||||||
|
announce_stream_status = boolean(default=True)
|
||||||
|
|
||||||
|
[sound]
|
||||||
|
volume = float(default=1.0)
|
||||||
|
input_device = string(default="Default")
|
||||||
|
output_device = string(default="Default")
|
||||||
|
current_soundpack = string(default="default")
|
||||||
|
global_mute = boolean(default=False)
|
||||||
|
sndup_api_key = string(default="")
|
||||||
|
|
||||||
|
[other_buffers]
|
||||||
|
show_friends = boolean(default=True)
|
||||||
|
show_followers = boolean(default=True)
|
||||||
|
show_favourites = boolean(default=True)
|
||||||
|
show_events = boolean(default=True)
|
||||||
|
show_blocks = boolean(default=False)
|
||||||
|
show_muted_users = boolean(default=False)
|
||||||
|
timelines = list(default=list())
|
||||||
|
tweet_searches = list(default=list())
|
||||||
|
lists = list(default=list())
|
||||||
|
favourites_timelines = list(default=list())
|
||||||
|
muted_buffers = list(default=list())
|
||||||
|
autoread_buffers = list(default=list())
|
||||||
|
|
||||||
|
[mysc]
|
||||||
|
spelling_language = string(default="")
|
||||||
|
|
||||||
|
[services]
|
||||||
|
dropbox_token=string(default="")
|
||||||
|
|
||||||
|
[keymap]
|
||||||
|
up = string(default="control+win+up")
|
||||||
|
down = string(default="control+win+down")
|
||||||
|
left = string(default="control+win+left")
|
||||||
|
right = string(default="control+win+right")
|
||||||
|
conversation_up = string(default="control+win+shift+up")
|
||||||
|
conversation_down = string(default="control+win+shift+down")
|
||||||
|
show_hide = string(default="control+win+m")
|
||||||
|
compose = string(default="control+win+n")
|
||||||
|
reply = string(default="control+win+r")
|
||||||
|
retweet = string(default="control+win+shift+r")
|
||||||
|
dm = string(default="control+win+d")
|
||||||
|
fav = string(default="alt+win+f")
|
||||||
|
unfav = string(default="alt+shift+win+f")
|
||||||
|
action = string(default="control+win+s")
|
||||||
|
details = string(default="control+win+alt+n")
|
||||||
|
view = string(default="control+win+v")
|
||||||
|
close = string(default="control+win+f4")
|
||||||
|
open_timeline = string(default="control+win+i")
|
||||||
|
delete_buffer = string(default="control+win+shift+i")
|
||||||
|
url = string(default="control+win+return")
|
||||||
|
audio = string(default="control+win+alt+return")
|
||||||
|
volume_up = string(default="control+win+alt+up")
|
||||||
|
volume_down = string(default="control+win+alt+down")
|
||||||
|
go_home = string(default="control+win+home")
|
||||||
|
go_end = string(default="control+win+end")
|
||||||
|
go_page_up = string(default="control+win+pageup")
|
||||||
|
go_page_down = string(default="control+win+pagedown")
|
||||||
|
update_profile = string(default="alt+win+p")
|
||||||
|
delete = string(default="control+win+delete")
|
||||||
|
clear_list = string(default="control+win+shift+delete")
|
||||||
|
repeat_item = string(default="control+win+space")
|
||||||
|
copy_to_clipboard = string(default="control+win+c")
|
||||||
|
add_to_list = string(default="control+win+a")
|
||||||
|
remove_from_list = string(default="control+win+shift+a")
|
||||||
|
toggle_mute = string(default="control+win+shift+m")
|
||||||
|
toggle_global_mute = string(default="alt+win+m")
|
||||||
|
toggle_autoread = string(default="control+win+e")
|
||||||
|
search = string(default="control+win+-")
|
||||||
|
edit_keystrokes = string(default="control+win+k")
|
||||||
|
view_user_lists = string(default="control+win+l")
|
||||||
|
get_more_items = string(default="alt+win+pageup")
|
||||||
|
connect_streams = string(default="win+alt+c")
|
29
src/accessible_output2/__init__.py
Normal file
29
src/accessible_output2/__init__.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import ctypes
|
||||||
|
import os
|
||||||
|
import types
|
||||||
|
from platform_utils import paths
|
||||||
|
|
||||||
|
def load_library(libname):
|
||||||
|
if paths.is_frozen():
|
||||||
|
libfile = os.path.join(paths.embedded_data_path(), 'accessible_output2', 'lib', libname)
|
||||||
|
else:
|
||||||
|
libfile = os.path.join(paths.module_path(), 'lib', libname)
|
||||||
|
return ctypes.windll[libfile]
|
||||||
|
|
||||||
|
def get_output_classes():
|
||||||
|
import outputs
|
||||||
|
module_type = types.ModuleType
|
||||||
|
classes = [m.output_class for m in outputs.__dict__.itervalues() if type(m) == module_type and hasattr(m, 'output_class')]
|
||||||
|
return sorted(classes, key=lambda c: c.priority)
|
||||||
|
|
||||||
|
def find_datafiles():
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
from glob import glob
|
||||||
|
import accessible_output2
|
||||||
|
if platform.system() != 'Windows':
|
||||||
|
return []
|
||||||
|
path = os.path.join(accessible_output2.__path__[0], 'lib', '*.dll')
|
||||||
|
results = glob(path)
|
||||||
|
dest_dir = os.path.join('accessible_output2', 'lib')
|
||||||
|
return [(dest_dir, results)]
|
BIN
src/accessible_output2/lib/PCTKUSR.dll
Normal file
BIN
src/accessible_output2/lib/PCTKUSR.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/PCTKUSR64.dll
Normal file
BIN
src/accessible_output2/lib/PCTKUSR64.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/SAAPI32.dll
Normal file
BIN
src/accessible_output2/lib/SAAPI32.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/dolapi.dll
Normal file
BIN
src/accessible_output2/lib/dolapi.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/jfwapi.dll
Normal file
BIN
src/accessible_output2/lib/jfwapi.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/nvdaControllerClient32.dll
Normal file
BIN
src/accessible_output2/lib/nvdaControllerClient32.dll
Normal file
Binary file not shown.
BIN
src/accessible_output2/lib/nvdaControllerClient64.dll
Normal file
BIN
src/accessible_output2/lib/nvdaControllerClient64.dll
Normal file
Binary file not shown.
16
src/accessible_output2/outputs/__init__.py
Normal file
16
src/accessible_output2/outputs/__init__.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import platform
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
import nvda
|
||||||
|
import jaws
|
||||||
|
import sapi5
|
||||||
|
import window_eyes
|
||||||
|
import system_access
|
||||||
|
import dolphin
|
||||||
|
import pc_talker
|
||||||
|
#import sapi4
|
||||||
|
elif platform.system() == "Darwin":
|
||||||
|
import voiceover
|
||||||
|
elif platform.system() == "Linux":
|
||||||
|
import speechDispatcher
|
||||||
|
|
||||||
|
import auto
|
42
src/accessible_output2/outputs/auto.py
Normal file
42
src/accessible_output2/outputs/auto.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import platform
|
||||||
|
import accessible_output2
|
||||||
|
from base import Output, OutputError
|
||||||
|
|
||||||
|
class Auto(Output):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if platform.system() == "Darwin":
|
||||||
|
import voiceover
|
||||||
|
self.outputs = [voiceover.VoiceOver()]
|
||||||
|
elif platform.system() == "Linux":
|
||||||
|
import speechDispatcher
|
||||||
|
self.outputs = [speechDispatcher.SpeechDispatcher()]
|
||||||
|
elif platform.system() == "Windows":
|
||||||
|
output_classes = accessible_output2.get_output_classes()
|
||||||
|
self.outputs = []
|
||||||
|
for output in output_classes:
|
||||||
|
try:
|
||||||
|
self.outputs.append(output())
|
||||||
|
except OutputError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_first_available_output(self):
|
||||||
|
for output in self.outputs:
|
||||||
|
if output.is_active():
|
||||||
|
return output
|
||||||
|
return None
|
||||||
|
|
||||||
|
def speak(self, *args, **kwargs):
|
||||||
|
output = self.get_first_available_output()
|
||||||
|
if output:
|
||||||
|
output.speak(*args, **kwargs)
|
||||||
|
|
||||||
|
def braille(self, *args, **kwargs):
|
||||||
|
output = self.get_first_available_output()
|
||||||
|
if output:
|
||||||
|
output.braille(*args, **kwargs)
|
||||||
|
|
||||||
|
def output(self, *args, **kwargs):
|
||||||
|
output = self.get_first_available_output()
|
||||||
|
if output:
|
||||||
|
output.speak(*args, **kwargs)
|
31
src/accessible_output2/outputs/base.py
Normal file
31
src/accessible_output2/outputs/base.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from accessible_output2 import load_library
|
||||||
|
import platform
|
||||||
|
|
||||||
|
class OutputError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Output(object):
|
||||||
|
name = "Unnamed Output" #The name of this output
|
||||||
|
lib32 = None #name of 32-bit lib
|
||||||
|
lib64 = None #name of 64-bit lib
|
||||||
|
priority = 100 #Where to sort in the list of available outputs for automaticly speaking
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
is_32bit = platform.architecture()[0] == "32bit"
|
||||||
|
if self.lib32 and is_32bit:
|
||||||
|
self.lib = load_library(self.lib32)
|
||||||
|
elif self.lib64:
|
||||||
|
self.lib = load_library(self.lib64)
|
||||||
|
|
||||||
|
def output(self, text, **options):
|
||||||
|
output = False
|
||||||
|
if hasattr(self, 'speak') and callable(self.speak):
|
||||||
|
self.speak(text, **options)
|
||||||
|
output = True
|
||||||
|
if hasattr(self, 'braille') and callable(self.braille):
|
||||||
|
self.braille(text, **options)
|
||||||
|
output = True
|
||||||
|
if not output:
|
||||||
|
raise RuntimeError("Output %r does not have any method defined to output" % self)
|
||||||
|
|
||||||
|
|
27
src/accessible_output2/outputs/dolphin.py
Normal file
27
src/accessible_output2/outputs/dolphin.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from base import Output
|
||||||
|
|
||||||
|
class Dolphin (Output):
|
||||||
|
"""Supports dolphin products."""
|
||||||
|
|
||||||
|
name = 'Dolphin'
|
||||||
|
lib32 = 'dolapi.dll'
|
||||||
|
|
||||||
|
def speak(self, text, interrupt=0):
|
||||||
|
if interrupt:
|
||||||
|
self.silence()
|
||||||
|
#If we don't call this, the API won't let us speak.
|
||||||
|
if self.is_active():
|
||||||
|
self.lib.DolAccess_Command(unicode(text), (len(text)*2)+2, 1)
|
||||||
|
|
||||||
|
def silence(self):
|
||||||
|
self.lib.DolAccess_Action(141)
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
try:
|
||||||
|
return self.lib.DolAccess_GetSystem() in (1, 4, 8)
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
output_class = Dolphin
|
33
src/accessible_output2/outputs/jaws.py
Normal file
33
src/accessible_output2/outputs/jaws.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import win32gui
|
||||||
|
from libloader.com import load_com
|
||||||
|
import pywintypes
|
||||||
|
|
||||||
|
from base import Output, OutputError
|
||||||
|
|
||||||
|
class Jaws (Output):
|
||||||
|
"""Output supporting the Jaws for Windows screen reader."""
|
||||||
|
|
||||||
|
name = 'jaws'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super (Jaws, self).__init__(*args, **kwargs)
|
||||||
|
try:
|
||||||
|
self.object = load_com("FreedomSci.JawsApi", "jfwapi")
|
||||||
|
except pywintypes.com_error:
|
||||||
|
raise OutputError
|
||||||
|
|
||||||
|
def braille(self, text, **options):
|
||||||
|
# HACK: replace " with ', Jaws doesn't seem to understand escaping them with \
|
||||||
|
text = text.replace('"', "'")
|
||||||
|
self.object.RunFunction("BrailleString(\"%s\")" % text)
|
||||||
|
|
||||||
|
def speak(self, text, interrupt=False):
|
||||||
|
self.object.SayString(' %s' % text, interrupt)
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
try:
|
||||||
|
return self.object.SayString('',0) == True or win32gui.FindWindow("JFWUI2", "JAWS") != 0
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
output_class = Jaws
|
31
src/accessible_output2/outputs/nvda.py
Normal file
31
src/accessible_output2/outputs/nvda.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import os
|
||||||
|
import platform
|
||||||
|
|
||||||
|
from platform_utils import paths
|
||||||
|
from libloader import load_library
|
||||||
|
from base import Output
|
||||||
|
|
||||||
|
class NVDA(Output):
|
||||||
|
"""Supports The NVDA screen reader"""
|
||||||
|
name = "NVDA"
|
||||||
|
lib32 = 'nvdaControllerClient32.dll'
|
||||||
|
lib64 = 'nvdaControllerClient64.dll'
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
try:
|
||||||
|
return self.lib.nvdaController_testIfRunning() == 0
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def braille(self, text, **options):
|
||||||
|
self.lib.nvdaController_brailleMessage(unicode(text))
|
||||||
|
|
||||||
|
def speak(self, text, interrupt=False):
|
||||||
|
if interrupt:
|
||||||
|
self.silence()
|
||||||
|
self.lib.nvdaController_speakText(unicode(text))
|
||||||
|
|
||||||
|
def silence(self):
|
||||||
|
self.lib.nvdaController_cancelSpeech()
|
||||||
|
|
||||||
|
output_class = NVDA
|
19
src/accessible_output2/outputs/pc_talker.py
Normal file
19
src/accessible_output2/outputs/pc_talker.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import ctypes
|
||||||
|
from base import Output
|
||||||
|
|
||||||
|
class PCTalker(Output):
|
||||||
|
lib32 = 'pctkusr.dll'
|
||||||
|
lib64 = 'pctkusr64.dll'
|
||||||
|
|
||||||
|
def speak(self, text, interrupt=False):
|
||||||
|
if interrupt:
|
||||||
|
self.silence()
|
||||||
|
self.lib.PCTKPRead(text.encode('cp932', 'replace'))
|
||||||
|
|
||||||
|
def silence(self):
|
||||||
|
self.lib.PCTKVReset()
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
return self.lib.PCTKStatus() != 0
|
||||||
|
|
||||||
|
output_class = PCTalker
|
141
src/accessible_output2/outputs/sapi4.py
Normal file
141
src/accessible_output2/outputs/sapi4.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
from libloader.com import load_com
|
||||||
|
from base import Output
|
||||||
|
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class Sapi4(Output):
|
||||||
|
|
||||||
|
name = 'sapi4'
|
||||||
|
priority = 102
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
sapi4 = load_com("{EEE78591-FE22-11D0-8BEF-0060081841DE}")
|
||||||
|
self._voiceNo = sapi4.Find(0)
|
||||||
|
sapi4.Select(self._voiceNo)
|
||||||
|
sapi4.Speak(" ")
|
||||||
|
self.__object = sapi4
|
||||||
|
self._voice_list = self._available_voices()
|
||||||
|
|
||||||
|
def _set_capabilities(self):
|
||||||
|
sapi4 = self.__object
|
||||||
|
try:
|
||||||
|
sapi4.Pitch = sapi4.Pitch
|
||||||
|
self._min_pitch = sapi4.MinPitch
|
||||||
|
self._max_pitch = sapi4.MaxPitch
|
||||||
|
self._has_pitch = True
|
||||||
|
except:
|
||||||
|
self._min_pitch = 0
|
||||||
|
self._max_pitch = 0
|
||||||
|
self._has_pitch = False
|
||||||
|
try:
|
||||||
|
sapi4.Speed = sapi4.Speed
|
||||||
|
self._min_rate = sapi4.MinSpeed
|
||||||
|
self._max_rate = sapi4.MaxSpeed
|
||||||
|
self._has_rate = True
|
||||||
|
except:
|
||||||
|
self._min_rate = 0
|
||||||
|
self._max_rate = 0
|
||||||
|
self._has_rate = False
|
||||||
|
try:
|
||||||
|
sapi4.VolumeLeft = sapi4.VolumeLeft
|
||||||
|
self._min_volume = sapi4.MinVolumeLeft
|
||||||
|
self._max_volume = sapi4.MaxVolumeLeft
|
||||||
|
self._has_volume = True
|
||||||
|
except:
|
||||||
|
self._min_volume = 0
|
||||||
|
self._max_volume = 0
|
||||||
|
self._has_volume = False
|
||||||
|
|
||||||
|
def _available_voices(self):
|
||||||
|
voice_list = []
|
||||||
|
for voice_no in range(1, self.__object.CountEngines):
|
||||||
|
voice_list.append(self.__object.ModeName(voice_no))
|
||||||
|
return voice_list
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available_voices(self):
|
||||||
|
return self._voice_list
|
||||||
|
|
||||||
|
def list_voices(self):
|
||||||
|
return self.available_voices
|
||||||
|
|
||||||
|
def get_voice(self):
|
||||||
|
return self.__object.ModeName(self._voice_no)
|
||||||
|
|
||||||
|
def set_voice(self, value):
|
||||||
|
self._voice_no = self.list_voices().index(value) + 1
|
||||||
|
self.__object.Select(self._voice_no)
|
||||||
|
self.silence()
|
||||||
|
self.__object.Speak(" ")
|
||||||
|
self._set_capabilities()
|
||||||
|
|
||||||
|
def get_pitch(self):
|
||||||
|
if self.has_pitch:
|
||||||
|
return self.__object.Pitch
|
||||||
|
|
||||||
|
def set_pitch(self, value):
|
||||||
|
if self.has_pitch:
|
||||||
|
self.__object.Pitch = value
|
||||||
|
|
||||||
|
def get_rate(self):
|
||||||
|
if self.has_rate:
|
||||||
|
return self.__object.Speed
|
||||||
|
|
||||||
|
def set_rate(self, value):
|
||||||
|
if self.has_rate:
|
||||||
|
self.__object.Speed = value
|
||||||
|
|
||||||
|
def get_volume(self):
|
||||||
|
if self.has_volume:
|
||||||
|
return self.__object.VolumeLeft
|
||||||
|
|
||||||
|
def set_volume(self, value):
|
||||||
|
if self.has_volume:
|
||||||
|
self.__object.VolumeLeft = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_pitch(self):
|
||||||
|
return self._has_pitch
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_rate(self):
|
||||||
|
return self._has_rate
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_volume(self):
|
||||||
|
return self._has_volume
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_pitch(self):
|
||||||
|
return self._min_pitch
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_pitch(self):
|
||||||
|
return self._max_pitch
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_rate(self):
|
||||||
|
return self._min_rate
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_rate(self):
|
||||||
|
return self._max_rate
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_volume(self):
|
||||||
|
return self._min_volume
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_volume(self):
|
||||||
|
return self._max_volume
|
||||||
|
|
||||||
|
def speak(self, text, interrupt=False):
|
||||||
|
if interrupt:
|
||||||
|
self.silence()
|
||||||
|
self.__object.Speak(text)
|
||||||
|
|
||||||
|
def silence(self):
|
||||||
|
self.__object.AudioReset()
|
||||||
|
|
||||||
|
output_class = Sapi4
|
88
src/accessible_output2/outputs/sapi5.py
Normal file
88
src/accessible_output2/outputs/sapi5.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import config
|
||||||
|
from collections import OrderedDict
|
||||||
|
from libloader.com import load_com
|
||||||
|
from base import Output, OutputError
|
||||||
|
import pywintypes
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class SAPI5(Output):
|
||||||
|
has_volume = True
|
||||||
|
has_rate = True
|
||||||
|
has_pitch = True
|
||||||
|
min_pitch = -10
|
||||||
|
max_pitch = 10
|
||||||
|
min_rate = -10
|
||||||
|
max_rate = 10
|
||||||
|
min_volume = 0
|
||||||
|
max_volume = 100
|
||||||
|
name = "sapi5"
|
||||||
|
priority = 101
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if config.main["general"]["voice_enabled"] == False: raise OutputError
|
||||||
|
try:
|
||||||
|
self.object = load_com("SAPI.SPVoice")
|
||||||
|
self._voices = self._available_voices()
|
||||||
|
except pywintypes.com_error:
|
||||||
|
raise OutputError
|
||||||
|
self._pitch = 0
|
||||||
|
|
||||||
|
def _available_voices(self):
|
||||||
|
_voices = OrderedDict()
|
||||||
|
for v in self.object.GetVoices():
|
||||||
|
_voices[v.GetDescription()] = v
|
||||||
|
return _voices
|
||||||
|
|
||||||
|
def list_voices(self):
|
||||||
|
return self.available_voices.keys()
|
||||||
|
|
||||||
|
def get_voice(self):
|
||||||
|
return self.object.Voice.GetDescription()
|
||||||
|
|
||||||
|
def set_voice(self, value):
|
||||||
|
log.debug("Setting SAPI5 voice to \"%s\"" % value)
|
||||||
|
self.object.Voice = self.available_voices[value]
|
||||||
|
# For some reason SAPI5 does not reset audio after changing the voice
|
||||||
|
# By setting the audio device after changing voices seems to fix this
|
||||||
|
# This was noted from information at:
|
||||||
|
# http://lists.nvaccess.org/pipermail/nvda-dev/2011-November/022464.html
|
||||||
|
self.object.AudioOutput = self.object.AudioOutput
|
||||||
|
|
||||||
|
def get_pitch(self):
|
||||||
|
return self._pitch
|
||||||
|
|
||||||
|
def set_pitch(self, value):
|
||||||
|
log.debug("Setting pitch to %d" % value)
|
||||||
|
self._pitch = value
|
||||||
|
|
||||||
|
def get_rate(self):
|
||||||
|
return self.object.Rate
|
||||||
|
|
||||||
|
def set_rate(self, value):
|
||||||
|
log.debug("Setting rate to %d" % value)
|
||||||
|
self.object.Rate = value
|
||||||
|
|
||||||
|
def get_volume(self):
|
||||||
|
return self.object.Volume
|
||||||
|
|
||||||
|
def set_volume(self, value):
|
||||||
|
self.object.Volume = value
|
||||||
|
|
||||||
|
def speak(self, text, interrupt=False):
|
||||||
|
if interrupt:
|
||||||
|
self.silence()
|
||||||
|
# We need to do the pitch in XML here
|
||||||
|
textOutput = "<pitch absmiddle=\"%d\">%s</pitch>" % (round(self._pitch), text.replace("<", "<"))
|
||||||
|
self.object.Speak(textOutput, 1|8)
|
||||||
|
|
||||||
|
def silence(self):
|
||||||
|
self.object.Speak("", 3)
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
if self.object:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
output_class = SAPI5
|
29
src/accessible_output2/outputs/speechDispatcher.py
Normal file
29
src/accessible_output2/outputs/speechDispatcher.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from base import Output, OutputError
|
||||||
|
import atexit
|
||||||
|
|
||||||
|
class SpeechDispatcher(Output):
|
||||||
|
"""Supports speech dispatcher on Linux.
|
||||||
|
Note that it will take the configuration from the speech dispatcher, the user will need configure voice, language, punctuation and rate before use this module.
|
||||||
|
"""
|
||||||
|
name = 'SpeechDispatcher'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(SpeechDispatcher, self).__init__(*args, **kwargs)
|
||||||
|
try:
|
||||||
|
import speechd
|
||||||
|
self.spd = speechd.SSIPClient("TWBlue")
|
||||||
|
except ImportError:
|
||||||
|
raise OutputError
|
||||||
|
atexit.register(self.on_exit_event)
|
||||||
|
|
||||||
|
def speak(self, text, interupt=False):
|
||||||
|
if interupt == True:
|
||||||
|
self.spd.cancel()
|
||||||
|
self.spd.speak(text)
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def on_exit_event(self):
|
||||||
|
self.spd.close()
|
||||||
|
del self.spd
|
18
src/accessible_output2/outputs/speechd/__init__.py
Normal file
18
src/accessible_output2/outputs/speechd/__init__.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Copyright (C) 2001, 2002 Brailcom, o.p.s.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
from .client import *
|
||||||
|
|
1125
src/accessible_output2/outputs/speechd/client.py
Normal file
1125
src/accessible_output2/outputs/speechd/client.py
Normal file
File diff suppressed because it is too large
Load Diff
1
src/accessible_output2/outputs/speechd/paths.py
Normal file
1
src/accessible_output2/outputs/speechd/paths.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
SPD_SPAWN_CMD = "/usr/bin/speech-dispatcher"
|
23
src/accessible_output2/outputs/system_access.py
Normal file
23
src/accessible_output2/outputs/system_access.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from base import Output
|
||||||
|
|
||||||
|
class SystemAccess (Output):
|
||||||
|
"""Supports System Access and System Access Mobile"""
|
||||||
|
|
||||||
|
name = "System Access"
|
||||||
|
lib32 = 'saapi32.dll'
|
||||||
|
priority = 99
|
||||||
|
|
||||||
|
def braille(self, text, **options):
|
||||||
|
self.lib.SA_BrlShowTextW(unicode(text))
|
||||||
|
|
||||||
|
def speak(self, text, interrupt=False):
|
||||||
|
if self.is_active():
|
||||||
|
self.dll.SA_SayW(unicode(text))
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
try:
|
||||||
|
return self.dll.SA_IsRunning()
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
output_class = SystemAccess
|
23
src/accessible_output2/outputs/voiceover.py
Normal file
23
src/accessible_output2/outputs/voiceover.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from base import Output, OutputError
|
||||||
|
|
||||||
|
class VoiceOver (Output):
|
||||||
|
"""Supports the VoiceOver screenreader on the Mac.
|
||||||
|
|
||||||
|
Note that this will also output as a message to the braille display if VoiceOver is used with braille.
|
||||||
|
Calling this module could cause VoiceOver to be started.
|
||||||
|
"""
|
||||||
|
name = 'VoiceOver'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(VoiceOver, self).__init__(*args, **kwargs)
|
||||||
|
try:
|
||||||
|
from appscript import app
|
||||||
|
self.app = app('VoiceOver')
|
||||||
|
except ImportError:
|
||||||
|
raise OutputError
|
||||||
|
|
||||||
|
def speak(self, text, interupt=False):
|
||||||
|
self.app.output(text)
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
return True
|
32
src/accessible_output2/outputs/window_eyes.py
Normal file
32
src/accessible_output2/outputs/window_eyes.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import win32gui
|
||||||
|
from libloader.com import load_com
|
||||||
|
from base import Output, OutputError
|
||||||
|
import pywintypes
|
||||||
|
|
||||||
|
class WindowEyes (Output):
|
||||||
|
"""Speech output supporting the WindowEyes screen reader"""
|
||||||
|
|
||||||
|
name = 'Window-Eyes'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(WindowEyes, self).__init__(*args, **kwargs)
|
||||||
|
try:
|
||||||
|
self.object = load_com("gwspeak.speak")
|
||||||
|
except pywintypes.com_error:
|
||||||
|
raise OutputError
|
||||||
|
|
||||||
|
def speak(self, text, interrupt=0):
|
||||||
|
if interrupt:
|
||||||
|
self.silence()
|
||||||
|
self.object.SpeakString(text)
|
||||||
|
|
||||||
|
def silence (self):
|
||||||
|
self.object.Silence()
|
||||||
|
|
||||||
|
def is_active(self):
|
||||||
|
try:
|
||||||
|
return win32gui.FindWindow("GWMExternalControl", "External Control") != 0
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
output_class = WindowEyes
|
20
src/application.py
Normal file
20
src/application.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
name = 'TW Blue'
|
||||||
|
snapshot = False
|
||||||
|
if snapshot == False:
|
||||||
|
version = "0.47"
|
||||||
|
update_url = 'http://twblue.com.mx/updates/tw_blue.json'
|
||||||
|
else:
|
||||||
|
version = "4"
|
||||||
|
update_url = 'http://twblue.com.mx/updates/snapshots.json'
|
||||||
|
author = u"Manuel Cortéz"
|
||||||
|
authorEmail = "info@twblue.com.mx"
|
||||||
|
copyright = u"copyright (C) 2013-2014, Manuel cortéz"
|
||||||
|
description = u"TW Blue is an app designed to use Twitter in a simple and fast way and avoiding, as far as possible, the consumtion of excessive resources of the machine where it’s running. With this app you’ll have access to most twitter features."
|
||||||
|
translators = [u"Bryner Villalobos (English)", u"Mohammed Al Shara (Arabic)", u"Salva Doménech, Juan Carlos Rivilla(Catalan)", u"Manuel cortéz(Spanish)", u"Sukil Etxenike Arizaleta(Basque)", u"Jani Kinnunen(finnish)", u"Javier Currás, José Manuel Delicado, Alba Quinteiro(Galician)", u"Robert Osztolykan(Hungarian)", u"Paweł Masarczyk(Polish)", u"Odenilton Júnior Santos(Portuguese)", u"Alexander Jaszyn(Russian)", u"Burak (Turkish)"]
|
||||||
|
url = u"http://twblue.com.mx"
|
||||||
|
report_bugs_url = "http://twblue.com.mx/errores/api/soap/mantisconnect.php?wsdl"
|
||||||
|
|
||||||
|
# Tokens
|
||||||
|
app_key = '8pDLbyOW3saYnvSZ4uLFg'
|
||||||
|
app_secret = 'YsgdrzY9B4yyYvYsyee78rKI3GshjHpenVS9LnFJXY'
|
22
src/audio_services/__init__.py
Normal file
22
src/audio_services/__init__.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
def matches_url(url):
|
||||||
|
def url_setter(func):
|
||||||
|
@wraps(func)
|
||||||
|
def internal_url_setter(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
internal_url_setter.url = url
|
||||||
|
return internal_url_setter
|
||||||
|
return url_setter
|
||||||
|
|
||||||
|
def find_url_transformer(url):
|
||||||
|
from audio_services import services
|
||||||
|
funcs = []
|
||||||
|
for i in dir(services):
|
||||||
|
possible = getattr(services, i)
|
||||||
|
if callable(possible) and hasattr(possible, 'url'):
|
||||||
|
funcs.append(possible)
|
||||||
|
for f in funcs:
|
||||||
|
if url.lower().startswith(f.url.lower()):
|
||||||
|
return f
|
||||||
|
return services.convert_generic_audio
|
56
src/audio_services/services.py
Normal file
56
src/audio_services/services.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from audio_services import matches_url
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
@matches_url('https://audioboom.com')
|
||||||
|
def convert_audioboom(url):
|
||||||
|
if "audioboom.com" not in url.lower():
|
||||||
|
raise TypeError('%r is not a valid URL' % url)
|
||||||
|
audio_id = url.split('.com/')[-1]
|
||||||
|
return 'https://audioboom.com/%s.mp3' % audio_id
|
||||||
|
|
||||||
|
@matches_url('http://q-audio.net')
|
||||||
|
def convert_q_audio(url):
|
||||||
|
result = re.match("^https?://q-audio.net/(i|d|download)/(?P<audio_id>[a-z0-9]+/?)$", url, re.I)
|
||||||
|
if not result or result.group("audio_id") is None:
|
||||||
|
raise TypeError('%r is not a valid URL' % url)
|
||||||
|
audio_id = result.group("audio_id")
|
||||||
|
return 'http://q-audio.net/download/%s' % audio_id
|
||||||
|
|
||||||
|
@matches_url ('http://soundcloud.com/')
|
||||||
|
def convert_soundcloud (url):
|
||||||
|
client_id = "df8113ca95c157b6c9731f54b105b473"
|
||||||
|
permalink = urllib.urlopen ('http://api.soundcloud.com/resolve.json?client_id=%s&url=%s' %(client_id, url))
|
||||||
|
if permalink.getcode () == 404:
|
||||||
|
permalink.close ()
|
||||||
|
raise TypeError('%r is not a valid URL' % url)
|
||||||
|
else:
|
||||||
|
resolved_url = permalink.geturl ()
|
||||||
|
permalink.close ()
|
||||||
|
track_url = urllib.urlopen (resolved_url)
|
||||||
|
track_data = json.loads (track_url.read ())
|
||||||
|
track_url.close ()
|
||||||
|
if track_data ['streamable']:
|
||||||
|
return track_data ['stream_url'] + "?client_id=%s" %client_id
|
||||||
|
else:
|
||||||
|
raise TypeError('%r is not streamable' % url)
|
||||||
|
|
||||||
|
@matches_url('http://twup.me')
|
||||||
|
def convert_twup(url):
|
||||||
|
result = re.match("^http://twup.me/(?P<audio_id>[A-Za-z0-9]+/?)$", url, re.I)
|
||||||
|
if not result or result.group("audio_id") is None:
|
||||||
|
raise TypeError('%r is not a valid URL' % url)
|
||||||
|
audio_id = result.group("audio_id")
|
||||||
|
return 'http://twup.me/%s' % audio_id
|
||||||
|
|
||||||
|
@matches_url('http://sndup.net')
|
||||||
|
def convert_sndup(url):
|
||||||
|
result = re.match("^http://sndup.net/(?P<audio_id>[a-z0-9]+/?)(|d|l|a)/?$", url, re.I)
|
||||||
|
if not result or result.group("audio_id") is None:
|
||||||
|
raise TypeError('%r is not a valid URL' % url)
|
||||||
|
audio_id = result.group("audio_id")
|
||||||
|
return 'http://sndup.net/%s/a' % audio_id
|
||||||
|
|
||||||
|
def convert_generic_audio(url):
|
||||||
|
return url
|
4966
src/cacert.pem
Normal file
4966
src/cacert.pem
Normal file
File diff suppressed because it is too large
Load Diff
14
src/commandline.py
Normal file
14
src/commandline.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import argparse
|
||||||
|
import paths
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="TW Blue command line launcher")
|
||||||
|
group = parser.add_mutually_exclusive_group()
|
||||||
|
group.add_argument("-p", "--portable", help="Use TW Blue as a portable aplication", action="store_true", default=True)
|
||||||
|
group.add_argument("-i", "--installed", help="Use TW Blue as an installed application. Config files will be saved on the user data directory", action="store_true")
|
||||||
|
parser.add_argument("-d", "--data-directory", action="store", dest="directory", help="Specifies the directory where TW Blue saves the data files")
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.installed == True: paths.mode = "installed"
|
||||||
|
elif args.portable == True:
|
||||||
|
paths.mode = "portable"
|
||||||
|
if args.directory != None: paths.directory = args.directory
|
16
src/config.py
Normal file
16
src/config.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# -*- coding: cp1252 -*-
|
||||||
|
from config_utils import Configuration, ConfigurationResetException
|
||||||
|
import paths
|
||||||
|
|
||||||
|
MAINFILE = "session.conf"
|
||||||
|
MAINSPEC = "Conf.defaults"
|
||||||
|
|
||||||
|
main = None
|
||||||
|
|
||||||
|
def setup ():
|
||||||
|
global main
|
||||||
|
try:
|
||||||
|
main = Configuration(paths.config_path(MAINFILE), paths.app_path(MAINSPEC))
|
||||||
|
except ConfigurationResetException:
|
||||||
|
pass
|
||||||
|
# return main
|
50
src/config_utils.py
Normal file
50
src/config_utils.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from UserDict import UserDict
|
||||||
|
from configobj import ConfigObj, ParseError
|
||||||
|
from validate import Validator, VdtValueError
|
||||||
|
import os
|
||||||
|
|
||||||
|
"""We're using the configobj python package
|
||||||
|
from http://www.voidspace.org.uk/python/configobj.html """
|
||||||
|
|
||||||
|
class ConfigurationResetException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Configuration (UserDict):
|
||||||
|
|
||||||
|
def __init__ (self, file=None, spec=None, *args, **kwargs):
|
||||||
|
self.file = file
|
||||||
|
self.spec = spec
|
||||||
|
self.validator = Validator()
|
||||||
|
self.setup_config(file=file, spec=spec)
|
||||||
|
self.validated = self.config.validate(self.validator, copy=True)
|
||||||
|
if self.validated:
|
||||||
|
self.write()
|
||||||
|
UserDict.__init__(self, self.config)
|
||||||
|
|
||||||
|
def setup_config (self, file, spec):
|
||||||
|
#The default way -- load from a file
|
||||||
|
spec = ConfigObj(spec, list_values=False, encoding="utf-8")
|
||||||
|
try:
|
||||||
|
self.config = ConfigObj(infile=file, configspec=spec, create_empty=True, stringify=True, encoding="utf-8")
|
||||||
|
except ParseError:
|
||||||
|
os.remove(file)
|
||||||
|
self.config = ConfigObj(infile=file, configspec=spec, create_empty=True, stringify=True)
|
||||||
|
raise ConfigurationResetException
|
||||||
|
def __getitem__ (self, *args, **kwargs):
|
||||||
|
return dict(self.config).__getitem__(*args, **kwargs)
|
||||||
|
|
||||||
|
def __setitem__ (self, *args, **kwargs):
|
||||||
|
self.config.__setitem__(*args, **kwargs)
|
||||||
|
UserDict.__setitem__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def write (self):
|
||||||
|
if hasattr(self.config, 'write'):
|
||||||
|
self.config.write()
|
||||||
|
|
||||||
|
class SessionConfiguration (Configuration):
|
||||||
|
def setup_config (self, file, spec):
|
||||||
|
#No infile required.
|
||||||
|
spec = ConfigObj(spec, list_values=False)
|
||||||
|
self.config = ConfigObj(configspec=spec, stringify=True)
|
5
src/db.py
Normal file
5
src/db.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
""" Handles storage from a durus database """
|
||||||
|
class db(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.settings = {}
|
3
src/extra/AudioUploader/__init__.py
Normal file
3
src/extra/AudioUploader/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import gui, transfer, transfer_dialogs, platform
|
||||||
|
if platform.system() != "Darwin":
|
||||||
|
import dropbox
|
113
src/extra/AudioUploader/dropbox_transfer.py
Normal file
113
src/extra/AudioUploader/dropbox_transfer.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
import exceptions
|
||||||
|
import wx
|
||||||
|
import dropbox
|
||||||
|
import config
|
||||||
|
from mysc import event
|
||||||
|
from utils import *
|
||||||
|
from dropbox.rest import ErrorResponse
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
class UnauthorisedError(exceptions.Exception):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(UnauthorisedError, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
class newChunkedUploader(dropbox.client.ChunkedUploader):
|
||||||
|
def __init__(self, client, file_obj, length, callback):
|
||||||
|
super(newChunkedUploader, self).__init__(client, file_obj, length)
|
||||||
|
self.progress_callback = callback
|
||||||
|
|
||||||
|
def upload_chunked(self, chunk_size = 4 * 1024 * 1024):
|
||||||
|
while self.offset < self.target_length:
|
||||||
|
next_chunk_size = min(chunk_size, self.target_length - self.offset)
|
||||||
|
if self.last_block == None:
|
||||||
|
self.last_block = self.file_obj.read(next_chunk_size)
|
||||||
|
|
||||||
|
try:
|
||||||
|
(self.offset, self.upload_id) = self.client.upload_chunk(
|
||||||
|
StringIO(self.last_block), next_chunk_size, self.offset, self.upload_id)
|
||||||
|
self.last_block = None
|
||||||
|
if callable(self.progress_callback): self.progress_callback(self.offset)
|
||||||
|
except ErrorResponse as e:
|
||||||
|
reply = e.body
|
||||||
|
if "offset" in reply and reply['offset'] != 0:
|
||||||
|
if reply['offset'] > self.offset:
|
||||||
|
self.last_block = None
|
||||||
|
self.offset = reply['offset']
|
||||||
|
|
||||||
|
class dropboxLogin(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.logged = False
|
||||||
|
self.app_key = "c8ikm0gexqvovol"
|
||||||
|
self.app_secret = "gvvi6fzfecooast"
|
||||||
|
|
||||||
|
def get_url(self):
|
||||||
|
self.flow = dropbox.client.DropboxOAuth2FlowNoRedirect(self.app_key, self.app_secret)
|
||||||
|
return self.flow.start()
|
||||||
|
|
||||||
|
def authorise(self, code):
|
||||||
|
access_token, user_id = self.flow.finish(code)
|
||||||
|
config.main["services"]["dropbox_token"] = access_token
|
||||||
|
self.logged = True
|
||||||
|
|
||||||
|
class dropboxUploader(object):
|
||||||
|
def __init__(self, filename, completed_callback, wxDialog, short_url=False):
|
||||||
|
if config.main["services"]["dropbox_token"] != "":
|
||||||
|
self.client = dropbox.client.DropboxClient(config.main["services"]["dropbox_token"])
|
||||||
|
else:
|
||||||
|
raise UnauthorisedError("You need authorise TWBlue")
|
||||||
|
self.filename = filename
|
||||||
|
self.short_url = short_url
|
||||||
|
self.wxDialog = wxDialog
|
||||||
|
self.file = open(self.filename, "rb")
|
||||||
|
self.file_size = os.path.getsize(self.filename)
|
||||||
|
self.uploader = newChunkedUploader(client=self.client, file_obj=self.file, length=self.file_size, callback=self.process)
|
||||||
|
self.start_time = None
|
||||||
|
self.completed_callback = completed_callback
|
||||||
|
self.background_thread = None
|
||||||
|
self.current = 0
|
||||||
|
self.transfer_rate = 0
|
||||||
|
|
||||||
|
def elapsed_time(self):
|
||||||
|
if not self.start_time:
|
||||||
|
return 0
|
||||||
|
return time.time() - self.start_time
|
||||||
|
|
||||||
|
def perform_transfer(self):
|
||||||
|
self.start_time = time.time()
|
||||||
|
while self.uploader.offset < self.file_size:
|
||||||
|
self.uploader.upload_chunked(self.file_size/100)
|
||||||
|
self.transfer_completed()
|
||||||
|
|
||||||
|
def process(self, offset):
|
||||||
|
progress = {}
|
||||||
|
self.current = offset
|
||||||
|
progress["total"] = self.file_size
|
||||||
|
progress["current"] = self.current
|
||||||
|
progress["percent"] = int((float(progress["current"]) / progress["total"]) * 100)
|
||||||
|
self.transfer_rate = progress["current"] / self.elapsed_time()
|
||||||
|
progress["speed"] = '%s/s' % convert_bytes(self.transfer_rate)
|
||||||
|
if self.transfer_rate:
|
||||||
|
progress["eta"] = (progress["total"] - progress["current"]) / self.transfer_rate
|
||||||
|
else:
|
||||||
|
progress["eta"] = 0
|
||||||
|
info = event.event(event.EVT_OBJECT, 1)
|
||||||
|
info.SetItem(progress)
|
||||||
|
wx.PostEvent(self.wxDialog, info)
|
||||||
|
|
||||||
|
def perform_threaded(self):
|
||||||
|
self.background_thread = threading.Thread(target=self.perform_transfer)
|
||||||
|
self.background_thread.daemon = True
|
||||||
|
self.background_thread.start()
|
||||||
|
|
||||||
|
def transfer_completed(self):
|
||||||
|
self.uploader.finish(os.path.basename(self.filename))
|
||||||
|
if callable(self.completed_callback):
|
||||||
|
self.completed_callback()
|
||||||
|
|
||||||
|
def get_url(self):
|
||||||
|
original = "%s" % (self.client.share(os.path.basename(self.filename), False)["url"])
|
||||||
|
return original.replace("dl=0", "dl=1")
|
190
src/extra/AudioUploader/gui.py
Normal file
190
src/extra/AudioUploader/gui.py
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import output
|
||||||
|
import tempfile
|
||||||
|
import sound
|
||||||
|
import os
|
||||||
|
import config
|
||||||
|
from mysc.thread_utils import call_threaded
|
||||||
|
import sound_lib
|
||||||
|
|
||||||
|
class audioDialog(wx.Dialog):
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.parent = parent
|
||||||
|
wx.Dialog.__init__(self, None, -1, _(u"Attach audio"))
|
||||||
|
self.file = None
|
||||||
|
self.recorded = False
|
||||||
|
self.recording = None
|
||||||
|
self.playing = None
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
self.play = wx.Button(panel, -1, _(u"Play"))
|
||||||
|
self.play.Bind(wx.EVT_BUTTON, self.onPlay)
|
||||||
|
self.play.Disable()
|
||||||
|
self.pause = wx.Button(panel, -1, _(u"Pause"))
|
||||||
|
self.pause.Bind(wx.EVT_BUTTON, self.onPause)
|
||||||
|
self.pause.Disable()
|
||||||
|
self.record = wx.Button(panel, -1, _(u"Record"))
|
||||||
|
self.record.Bind(wx.EVT_BUTTON, self.onRecord)
|
||||||
|
self.record.SetFocus()
|
||||||
|
self.attach_exists = wx.Button(panel, -1, _(u"Add an existing file"))
|
||||||
|
self.attach_exists.Bind(wx.EVT_BUTTON, self.onAttach)
|
||||||
|
self.discard = wx.Button(panel, -1, _(u"Discard"))
|
||||||
|
self.discard.Bind(wx.EVT_BUTTON, self.onDiscard)
|
||||||
|
self.discard.Disable()
|
||||||
|
label = wx.StaticText(panel, -1, _(u"Upload to"))
|
||||||
|
self.services = wx.ComboBox(panel, -1, choices=self.get_available_services(), value=self.get_available_services()[0], style=wx.CB_READONLY)
|
||||||
|
servicesBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
servicesBox.Add(label)
|
||||||
|
servicesBox.Add(self.services)
|
||||||
|
self.attach = wx.Button(panel, wx.ID_OK, _(u"Attach"))
|
||||||
|
self.attach.Disable()
|
||||||
|
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
|
||||||
|
sizer.Add(self.play)
|
||||||
|
sizer.Add(self.pause)
|
||||||
|
sizer.Add(self.record)
|
||||||
|
sizer.Add(self.attach_exists)
|
||||||
|
sizer.Add(self.discard)
|
||||||
|
sizer.Add(self.attach)
|
||||||
|
|
||||||
|
def get_available_services(self):
|
||||||
|
services = []
|
||||||
|
if config.main["services"]["dropbox_token"] != "":
|
||||||
|
services.append("Dropbox")
|
||||||
|
services.append("SNDUp")
|
||||||
|
return services
|
||||||
|
|
||||||
|
def onPause(self, ev):
|
||||||
|
if self.pause.GetLabel() == _(u"Pause"):
|
||||||
|
self.recording.pause()
|
||||||
|
self.pause.SetLabel(_(u"Resume"))
|
||||||
|
elif self.pause.GetLabel() == _(u"Resume"):
|
||||||
|
self.recording.play()
|
||||||
|
self.pause.SetLabel(_(U"Pause"))
|
||||||
|
|
||||||
|
def onRecord(self, ev):
|
||||||
|
if self.recording != None:
|
||||||
|
self.stop_recording()
|
||||||
|
self.pause.Disable()
|
||||||
|
else:
|
||||||
|
self.start_recording()
|
||||||
|
self.pause.Enable()
|
||||||
|
|
||||||
|
def start_recording(self):
|
||||||
|
self.attach_exists.Disable()
|
||||||
|
self.file = tempfile.mktemp(suffix='.wav')
|
||||||
|
self.recording = sound.recording(self.file)
|
||||||
|
self.recording.play()
|
||||||
|
self.record.SetLabel(_(u"Stop recording"))
|
||||||
|
output.speak(_(u"Recording"))
|
||||||
|
|
||||||
|
def stop_recording(self):
|
||||||
|
self.recording.stop()
|
||||||
|
self.recording.free()
|
||||||
|
output.speak(_(u"Stopped"))
|
||||||
|
self.recorded = True
|
||||||
|
self.record.SetLabel(_(u"Record"))
|
||||||
|
self.file_attached()
|
||||||
|
|
||||||
|
def file_attached(self):
|
||||||
|
self.pause.SetLabel(_(u"Pause"))
|
||||||
|
self.record.Disable()
|
||||||
|
self.play.Enable()
|
||||||
|
self.discard.Enable()
|
||||||
|
self.attach_exists.Disable()
|
||||||
|
self.attach.Enable()
|
||||||
|
self.play.SetFocus()
|
||||||
|
|
||||||
|
def onDiscard(self, evt):
|
||||||
|
evt.Skip()
|
||||||
|
if self.playing:
|
||||||
|
self._stop()
|
||||||
|
if self.recording != None:
|
||||||
|
self.attach.Disable()
|
||||||
|
self.play.Disable()
|
||||||
|
self.file = None
|
||||||
|
self.record.Enable()
|
||||||
|
self.attach_exists.Enable()
|
||||||
|
self.record.SetFocus()
|
||||||
|
self.discard.Disable()
|
||||||
|
self.recording = None
|
||||||
|
output.speak(_(u"Discarded"))
|
||||||
|
|
||||||
|
def onPlay(self, evt):
|
||||||
|
evt.Skip()
|
||||||
|
if not self.playing:
|
||||||
|
call_threaded(self._play)
|
||||||
|
else:
|
||||||
|
self._stop()
|
||||||
|
|
||||||
|
def _play(self):
|
||||||
|
output.speak(_(u"Playing..."))
|
||||||
|
# try:
|
||||||
|
self.playing = sound_lib.stream.FileStream(file=unicode(self.file), flags=sound_lib.stream.BASS_UNICODE)
|
||||||
|
self.playing.play()
|
||||||
|
self.play.SetLabel(_(u"Stop"))
|
||||||
|
try:
|
||||||
|
while self.playing.is_playing:
|
||||||
|
pass
|
||||||
|
self.play.SetLabel(_(u"Play"))
|
||||||
|
self.playing.free()
|
||||||
|
self.playing = None
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _stop(self):
|
||||||
|
output.speak(_(u"Stopped"))
|
||||||
|
self.playing.stop()
|
||||||
|
self.playing.free()
|
||||||
|
self.play.SetLabel(_(u"Play"))
|
||||||
|
self.playing = None
|
||||||
|
|
||||||
|
def postprocess(self):
|
||||||
|
if self.file.lower().endswith('.wav'):
|
||||||
|
output.speak(_(u"Recoding audio..."))
|
||||||
|
sound.recode_audio(self.file)
|
||||||
|
self.wav_file = self.file
|
||||||
|
self.file = '%s.ogg' % self.file[:-4]
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
if self.playing and self.playing.is_playing:
|
||||||
|
self.playing.stop()
|
||||||
|
if self.recording != None:
|
||||||
|
if self.recording.is_playing:
|
||||||
|
self.recording.stop()
|
||||||
|
try:
|
||||||
|
self.recording.free()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
os.remove(self.file)
|
||||||
|
if hasattr(self, 'wav_file'):
|
||||||
|
os.remove(self.wav_file)
|
||||||
|
del(self.wav_file)
|
||||||
|
if hasattr(self, 'wav_file') and os.path.exists(self.file):
|
||||||
|
os.remove(self.file)
|
||||||
|
|
||||||
|
|
||||||
|
def onAttach(self, ev):
|
||||||
|
openFileDialog = wx.FileDialog(self, _(u"Select the audio file to be uploaded"), "", "", _("Audio Files (*.mp3, *.ogg, *.wav)|*.mp3; *.ogg; *.wav"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
|
||||||
|
if openFileDialog.ShowModal() == wx.ID_CANCEL:
|
||||||
|
return
|
||||||
|
self.file = openFileDialog.GetPath()
|
||||||
|
self.file_attached()
|
||||||
|
ev.Skip()
|
107
src/extra/AudioUploader/transfer.py
Normal file
107
src/extra/AudioUploader/transfer.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import pycurl
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import wx
|
||||||
|
from mysc import event
|
||||||
|
from utils import *
|
||||||
|
|
||||||
|
#__all__ = ['TransferDialog', 'DownloadDialog', 'UploadDialog']
|
||||||
|
|
||||||
|
class Transfer(object):
|
||||||
|
|
||||||
|
def __init__(self, url=None, filename=None, follow_location=True, completed_callback=None, verbose=False, wxDialog=None, *args, **kwargs):
|
||||||
|
self.url = url
|
||||||
|
self.filename = filename
|
||||||
|
self.curl = pycurl.Curl()
|
||||||
|
self.start_time = None
|
||||||
|
self.completed_callback = completed_callback
|
||||||
|
self.background_thread = None
|
||||||
|
self.transfer_rate = 0
|
||||||
|
self.curl.setopt(self.curl.PROGRESSFUNCTION, self.progress_callback)
|
||||||
|
self.curl.setopt(self.curl.URL, url)
|
||||||
|
self.curl.setopt(self.curl.NOPROGRESS, 0)
|
||||||
|
self.curl.setopt(self.curl.HTTP_VERSION, self.curl.CURL_HTTP_VERSION_1_0)
|
||||||
|
self.curl.setopt(self.curl.FOLLOWLOCATION, int(follow_location))
|
||||||
|
self.curl.setopt(self.curl.VERBOSE, int(verbose))
|
||||||
|
self.wxDialog = wxDialog
|
||||||
|
super(Transfer, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def elapsed_time(self):
|
||||||
|
if not self.start_time:
|
||||||
|
return 0
|
||||||
|
return time.time() - self.start_time
|
||||||
|
|
||||||
|
def progress_callback(self, down_total, down_current, up_total, up_current):
|
||||||
|
progress = {}
|
||||||
|
progress["total"] = up_total
|
||||||
|
progress["current"] = up_current
|
||||||
|
# else:
|
||||||
|
# print "Killed function"
|
||||||
|
# return
|
||||||
|
if progress["current"] == 0:
|
||||||
|
progress["percent"] = 0
|
||||||
|
self.transfer_rate = 0
|
||||||
|
else:
|
||||||
|
progress["percent"] = int((float(progress["current"]) / progress["total"]) * 100)
|
||||||
|
self.transfer_rate = progress["current"] / self.elapsed_time()
|
||||||
|
progress["speed"] = '%s/s' % convert_bytes(self.transfer_rate)
|
||||||
|
if self.transfer_rate:
|
||||||
|
progress["eta"] = (progress["total"] - progress["current"]) / self.transfer_rate
|
||||||
|
else:
|
||||||
|
progress["eta"] = 0
|
||||||
|
info = event.event(event.EVT_OBJECT, 1)
|
||||||
|
info.SetItem(progress)
|
||||||
|
wx.PostEvent(self.wxDialog, info)
|
||||||
|
|
||||||
|
def perform_transfer(self):
|
||||||
|
self.start_time = time.time()
|
||||||
|
self.curl.perform()
|
||||||
|
self.curl.close()
|
||||||
|
wx.CallAfter(self.complete_transfer)
|
||||||
|
|
||||||
|
def perform_threaded(self):
|
||||||
|
self.background_thread = threading.Thread(target=self.perform_transfer)
|
||||||
|
self.background_thread.daemon = True
|
||||||
|
self.background_thread.start()
|
||||||
|
|
||||||
|
def complete_transfer(self):
|
||||||
|
if callable(self.completed_callback):
|
||||||
|
self.curl.close()
|
||||||
|
self.completed_callback()
|
||||||
|
|
||||||
|
class Upload(Transfer):
|
||||||
|
|
||||||
|
def __init__(self, field=None, filename=None, *args, **kwargs):
|
||||||
|
super(Upload, self).__init__(filename=filename, *args, **kwargs)
|
||||||
|
self.response = dict()
|
||||||
|
self.curl.setopt(self.curl.POST, 1)
|
||||||
|
if isinstance(filename, unicode):
|
||||||
|
local_filename = filename.encode(sys.getfilesystemencoding())
|
||||||
|
else:
|
||||||
|
local_filename = filename
|
||||||
|
self.curl.setopt(self.curl.HTTPPOST, [(field, (self.curl.FORM_FILE, local_filename, self.curl.FORM_FILENAME, filename.encode("utf-8")))])
|
||||||
|
self.curl.setopt(self.curl.HEADERFUNCTION, self.header_callback)
|
||||||
|
self.curl.setopt(self.curl.WRITEFUNCTION, self.body_callback)
|
||||||
|
|
||||||
|
def header_callback(self, content):
|
||||||
|
self.response['header'] = content
|
||||||
|
|
||||||
|
def body_callback(self, content):
|
||||||
|
self.response['body'] = content
|
||||||
|
|
||||||
|
def get_url(self):
|
||||||
|
return json.loads(self.response['body'])['url']
|
||||||
|
|
||||||
|
class Download(Transfer):
|
||||||
|
|
||||||
|
def __init__(self, follow_location=True, *args, **kwargs):
|
||||||
|
super(Download, self).__init__(*args, **kwargs)
|
||||||
|
self.download_file = open(self.filename, 'wb')
|
||||||
|
self.curl.setopt(self.curl.WRITEFUNCTION, self.download_file.write)
|
||||||
|
|
||||||
|
def complete_transfer(self):
|
||||||
|
self.download_file.close()
|
||||||
|
super(DownloadDialog, self).complete_transfer()
|
72
src/extra/AudioUploader/transfer_dialogs.py
Normal file
72
src/extra/AudioUploader/transfer_dialogs.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import wx
|
||||||
|
from mysc import event
|
||||||
|
from utils import *
|
||||||
|
|
||||||
|
__all__ = ['TransferDialog', 'DownloadDialog', 'UploadDialog']
|
||||||
|
|
||||||
|
class TransferDialog(wx.Dialog):
|
||||||
|
|
||||||
|
def __init__(self, filename, *args, **kwargs):
|
||||||
|
super(TransferDialog, self).__init__(*args, **kwargs)
|
||||||
|
self.pane = wx.Panel(self)
|
||||||
|
self.progress_bar = wx.Gauge(parent=self.pane)
|
||||||
|
fileBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
fileLabel = wx.StaticText(self.pane, -1, _(u"File"))
|
||||||
|
self.file = wx.TextCtrl(self.pane, -1, value=filename, style=wx.TE_READONLY|wx.TE_MULTILINE, size=(200, 100))
|
||||||
|
self.file.SetFocus()
|
||||||
|
fileBox.Add(fileLabel)
|
||||||
|
fileBox.Add(self.file)
|
||||||
|
currentAmountBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
current_amount_label = wx.StaticText(self.pane, -1, _(u"Transferred"))
|
||||||
|
self.current_amount = wx.TextCtrl(self.pane, -1, value='0', style=wx.TE_READONLY|wx.TE_MULTILINE)
|
||||||
|
currentAmountBox.Add(current_amount_label)
|
||||||
|
currentAmountBox.Add(self.current_amount)
|
||||||
|
totalSizeBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
total_size_label = wx.StaticText(self.pane, -1, _(u"Total file size"))
|
||||||
|
self.total_size = wx.TextCtrl(self.pane, -1, value='0', style=wx.TE_READONLY|wx.TE_MULTILINE)
|
||||||
|
totalSizeBox.Add(total_size_label)
|
||||||
|
totalSizeBox.Add(self.total_size)
|
||||||
|
speedBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
speedLabel = wx.StaticText(self.pane, -1, _(u"Transfer rate"))
|
||||||
|
self.speed = wx.TextCtrl(self.pane, -1, style=wx.TE_READONLY|wx.TE_MULTILINE, value="0 Kb/s")
|
||||||
|
speedBox.Add(speedLabel)
|
||||||
|
speedBox.Add(self.speed)
|
||||||
|
etaBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
etaLabel = wx.StaticText(self.pane, -1, _(u"Time left"))
|
||||||
|
self.eta = wx.TextCtrl(self.pane, -1, style=wx.TE_READONLY|wx.TE_MULTILINE, value="Unknown", size=(200, 100))
|
||||||
|
etaBox.Add(etaLabel)
|
||||||
|
etaBox.Add(self.eta)
|
||||||
|
# self.create_buttons()
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
sizer.Add(fileBox)
|
||||||
|
sizer.Add(currentAmountBox)
|
||||||
|
sizer.Add(totalSizeBox)
|
||||||
|
sizer.Add(speedBox)
|
||||||
|
sizer.Add(etaBox)
|
||||||
|
sizer.Add(self.progress_bar)
|
||||||
|
self.pane.SetSizerAndFit(sizer)
|
||||||
|
self.Bind(event.MyEVT_OBJECT, self.update)
|
||||||
|
|
||||||
|
def update(self, ev):
|
||||||
|
data = ev.GetItem()
|
||||||
|
wx.CallAfter(self.progress_bar.SetValue, data["percent"])
|
||||||
|
wx.CallAfter(self.current_amount.SetValue, '%s (%d%%)' % (convert_bytes(data["current"]), data["percent"]))
|
||||||
|
wx.CallAfter(self.total_size.SetValue, convert_bytes(data["total"]))
|
||||||
|
wx.CallAfter(self.speed.SetValue, data["speed"])
|
||||||
|
if data["eta"]:
|
||||||
|
wx.CallAfter(self.eta.SetValue, seconds_to_string(data["eta"]))
|
||||||
|
|
||||||
|
def create_buttons(self):
|
||||||
|
self.cancel_button = wx.Button(parent=self.pane, id=wx.ID_CANCEL)
|
||||||
|
self.cancel_button.Bind(wx.EVT_BUTTON, self.on_cancel)
|
||||||
|
|
||||||
|
class UploadDialog(TransferDialog):
|
||||||
|
|
||||||
|
def __init__(self, filename=None, *args, **kwargs):
|
||||||
|
super(UploadDialog, self).__init__(filename=filename, *args, **kwargs)
|
||||||
|
|
||||||
|
class DownloadDialog(TransferDialog):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(Download, self).__init__(*args, **kwargs)
|
42
src/extra/AudioUploader/utils.py
Normal file
42
src/extra/AudioUploader/utils.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
def convert_bytes(n):
|
||||||
|
K, M, G, T, P = 1 << 10, 1 << 20, 1 << 30, 1 << 40, 1 << 50
|
||||||
|
if n >= P:
|
||||||
|
return '%.2fPb' % (float(n) / T)
|
||||||
|
elif n >= T:
|
||||||
|
return '%.2fTb' % (float(n) / T)
|
||||||
|
elif n >= G:
|
||||||
|
return '%.2fGb' % (float(n) / G)
|
||||||
|
elif n >= M:
|
||||||
|
return '%.2fMb' % (float(n) / M)
|
||||||
|
elif n >= K:
|
||||||
|
return '%.2fKb' % (float(n) / K)
|
||||||
|
else:
|
||||||
|
return '%d' % n
|
||||||
|
|
||||||
|
def seconds_to_string(seconds, precision=0):
|
||||||
|
day = seconds // 86400
|
||||||
|
hour = seconds // 3600
|
||||||
|
min = (seconds // 60) % 60
|
||||||
|
sec = seconds - (hour * 3600) - (min * 60)
|
||||||
|
sec_spec = "." + str(precision) + "f"
|
||||||
|
sec_string = sec.__format__(sec_spec)
|
||||||
|
string = ""
|
||||||
|
if day == 1:
|
||||||
|
string += _(u"%d day, ") % day
|
||||||
|
elif day >= 2:
|
||||||
|
string += _(u"%d days, ") % day
|
||||||
|
if (hour == 1):
|
||||||
|
string += _(u"%d hour, ") % hour
|
||||||
|
elif (hour >= 2):
|
||||||
|
string += _("%d hours, ") % hour
|
||||||
|
if (min == 1):
|
||||||
|
string += _(u"%d minute, ") % min
|
||||||
|
elif (min >= 2):
|
||||||
|
string += _(u"%d minutes, ") % min
|
||||||
|
if sec >= 0 and sec <= 2:
|
||||||
|
string += _(u"%s second") % sec_string
|
||||||
|
else:
|
||||||
|
string += _(u"%s seconds") % sec_string
|
||||||
|
return string
|
1
src/extra/SoundsTutorial/__init__.py
Normal file
1
src/extra/SoundsTutorial/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
import gui
|
59
src/extra/SoundsTutorial/gui.py
Normal file
59
src/extra/SoundsTutorial/gui.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import wx
|
||||||
|
import config
|
||||||
|
import os
|
||||||
|
import paths
|
||||||
|
import sound
|
||||||
|
|
||||||
|
class soundsTutorial(wx.Dialog):
|
||||||
|
def __init__(self):
|
||||||
|
self.actions = [
|
||||||
|
_(u"The tweet may contain a playable audio"),
|
||||||
|
_(u"A timeline has been created"),
|
||||||
|
_(u"A timeline has been deleted"),
|
||||||
|
_(u"You've received a direct message"),
|
||||||
|
_(u"You've sent a direct message"),
|
||||||
|
_(u"A bug has happened"),
|
||||||
|
_(u"You've added a tweet to your favourites"),
|
||||||
|
_(u"Someone's favourites have been updated"),
|
||||||
|
_(u"There are no more tweets to read"),
|
||||||
|
_(u"A list has a new tweet"),
|
||||||
|
_(u"You can't add any more characters on the tweet"),
|
||||||
|
_(u"You've been mentioned "),
|
||||||
|
_(u"A new event has happened"),
|
||||||
|
_(u"TW Blue is ready "),
|
||||||
|
_(u"You've replied"),
|
||||||
|
_(u"You've retweeted"),
|
||||||
|
_(u"A search has been updated"),
|
||||||
|
_(u"There's a new tweet in the main buffer"),
|
||||||
|
_(u"You've sent a tweet"),
|
||||||
|
_(u"There's a new tweet in a timeline"),
|
||||||
|
_(u"You have a new follower"),
|
||||||
|
_(u"You've turned the volume up or down")]
|
||||||
|
self.files = os.listdir(paths.sound_path("default"))
|
||||||
|
super(soundsTutorial, self).__init__(None, -1)
|
||||||
|
if len(self.actions) > len(self.files):
|
||||||
|
wx.MessageDialog(None, _(u"It seems as though the currently used sound pack needs an update. %i fails are still be required to use this function. Make sure to obtain the needed lacking sounds or to contact with the sound pack developer.") % (len(self.actions) - len(self.files)), _(u"Error"), wx.ICON_ERROR).ShowModal()
|
||||||
|
self.Destroy()
|
||||||
|
self.SetTitle(_(u"Sounds tutorial"))
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
label = wx.StaticText(panel, -1, _(u"Press enter to listen to the sound for the selected event"))
|
||||||
|
self.items = wx.ListBox(panel, 1, choices=self.actions, style=wx.LB_SINGLE)
|
||||||
|
self.items.SetSelection(0)
|
||||||
|
listBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
listBox.Add(label)
|
||||||
|
listBox.Add(self.items)
|
||||||
|
play = wx.Button(panel, 1, (u"Play"))
|
||||||
|
play.SetDefault()
|
||||||
|
self.Bind(wx.EVT_BUTTON, self.onPlay, play)
|
||||||
|
close = wx.Button(panel, wx.ID_CANCEL)
|
||||||
|
btnBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
btnBox.Add(play)
|
||||||
|
btnBox.Add(close)
|
||||||
|
sizer.Add(listBox)
|
||||||
|
sizer.Add(btnBox)
|
||||||
|
panel.SetSizer(sizer)
|
||||||
|
|
||||||
|
def onPlay(self, ev):
|
||||||
|
sound.player.play(self.files[self.items.GetSelection()])
|
0
src/extra/SpellChecker/__init__.py
Normal file
0
src/extra/SpellChecker/__init__.py
Normal file
105
src/extra/SpellChecker/gui.py
Normal file
105
src/extra/SpellChecker/gui.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import output
|
||||||
|
import config
|
||||||
|
import languageHandler
|
||||||
|
from enchant.checker import SpellChecker
|
||||||
|
from enchant.errors import DictNotFoundError
|
||||||
|
|
||||||
|
class spellCheckerDialog(wx.Dialog):
|
||||||
|
def __init__(self, text, dictionary):
|
||||||
|
super(spellCheckerDialog, self).__init__(None, 1)
|
||||||
|
try:
|
||||||
|
if config.main["general"]["language"] == "system": self.checker = SpellChecker()
|
||||||
|
else: self.checker = SpellChecker(languageHandler.getLanguage())
|
||||||
|
self.checker.set_text(text)
|
||||||
|
except DictNotFoundError:
|
||||||
|
wx.MessageDialog(None, _(u"A bug has happened. There are no dictionaries available for the selected language in TW Blue"), _(u"Error"), wx.ICON_ERROR).ShowModal()
|
||||||
|
self.Destroy()
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
word = wx.StaticText(panel, -1, _(u"Mis-spelled word"))
|
||||||
|
self.word = wx.TextCtrl(panel, -1)
|
||||||
|
wordBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
wordBox.Add(word)
|
||||||
|
wordBox.Add(self.word)
|
||||||
|
context = wx.StaticText(panel, -1, _(u"Context"))
|
||||||
|
self.context = wx.TextCtrl(panel, -1)
|
||||||
|
contextBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
contextBox.Add(context)
|
||||||
|
contextBox.Add(self.context)
|
||||||
|
suggest = wx.StaticText(panel, -1, _(u"Suggestions"))
|
||||||
|
self.suggestions = wx.ListBox(panel, -1, choices=[], style=wx.LB_SINGLE)
|
||||||
|
suggestionsBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
suggestionsBox.Add(suggest)
|
||||||
|
suggestionsBox.Add(self.suggestions)
|
||||||
|
ignore = wx.Button(panel, -1, _(u"Ignore"))
|
||||||
|
self.Bind(wx.EVT_BUTTON, self.onIgnore, ignore)
|
||||||
|
ignoreAll = wx.Button(panel, -1, _(u"Ignore all"))
|
||||||
|
self.Bind(wx.EVT_BUTTON, self.onIgnoreAll, ignoreAll)
|
||||||
|
replace = wx.Button(panel, -1, _(u"Replace"))
|
||||||
|
self.Bind(wx.EVT_BUTTON, self.onReplace, replace)
|
||||||
|
replaceAll = wx.Button(panel, -1, _(u"Replace all"))
|
||||||
|
self.Bind(wx.EVT_BUTTON, self.onReplaceAll, replaceAll)
|
||||||
|
close = wx.Button(panel, wx.ID_CANCEL)
|
||||||
|
btnBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
btnBox.Add(ignore)
|
||||||
|
btnBox.Add(ignoreAll)
|
||||||
|
btnBox.Add(replace)
|
||||||
|
btnBox.Add(replaceAll)
|
||||||
|
btnBox.Add(close)
|
||||||
|
sizer.Add(wordBox)
|
||||||
|
sizer.Add(contextBox)
|
||||||
|
sizer.Add(suggestionsBox)
|
||||||
|
sizer.Add(btnBox)
|
||||||
|
panel.SetSizerAndFit(sizer)
|
||||||
|
self.check()
|
||||||
|
|
||||||
|
def check(self):
|
||||||
|
try:
|
||||||
|
self.checker.next()
|
||||||
|
textToSay = _(u"Mis-spelled word: %s") % (self.checker.word,)
|
||||||
|
context = u"... %s %s %s" % (self.checker.leading_context(10), self.checker.word, self.checker.trailing_context(10))
|
||||||
|
self.SetTitle(textToSay)
|
||||||
|
output.speak(textToSay)
|
||||||
|
self.word.SetValue(self.checker.word)
|
||||||
|
self.context.ChangeValue(context)
|
||||||
|
self.suggestions.Set(self.checker.suggest())
|
||||||
|
self.suggestions.SetFocus()
|
||||||
|
except StopIteration:
|
||||||
|
wx.MessageDialog(self, _(u"The spelling review has finished."), _("Finished"), style=wx.OK).ShowModal()
|
||||||
|
self.EndModal(wx.ID_OK)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def onIgnore(self, ev):
|
||||||
|
self.check()
|
||||||
|
|
||||||
|
def onIgnoreAll(self, ev):
|
||||||
|
self.checker.ignore_always(word=self.checker.word)
|
||||||
|
self.check()
|
||||||
|
|
||||||
|
def onReplace(self, ev):
|
||||||
|
self.checker.replace(self.suggestions.GetStringSelection())
|
||||||
|
self.check()
|
||||||
|
|
||||||
|
def onReplaceAll(self, ev):
|
||||||
|
self.checker.replace_always(self.suggestions.GetStringSelection())
|
||||||
|
self.check()
|
0
src/extra/__init__.py
Normal file
0
src/extra/__init__.py
Normal file
3
src/extra/translator/__init__.py
Normal file
3
src/extra/translator/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from translator import *
|
||||||
|
import gui
|
44
src/extra/translator/gui.py
Normal file
44
src/extra/translator/gui.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import translator
|
||||||
|
|
||||||
|
class translateDialog(wx.Dialog):
|
||||||
|
def __init__(self):
|
||||||
|
wx.Dialog.__init__(self, None, -1, title=_(u"Translate message"))
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
staticSource = wx.StaticText(panel, -1, _(u"Source language"))
|
||||||
|
self.source_lang = wx.ComboBox(panel, -1, choices=[x[1] for x in translator.available_languages()], style = wx.CB_READONLY)
|
||||||
|
self.source_lang.SetFocus()
|
||||||
|
staticDest = wx.StaticText(panel, -1, _(u"Target language"))
|
||||||
|
self.source_lang.SetSelection(0)
|
||||||
|
self.dest_lang = wx.ComboBox(panel, -1, choices=[x[1] for x in translator.available_languages()], style = wx.CB_READONLY)
|
||||||
|
listSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
listSizer.Add(staticSource)
|
||||||
|
listSizer.Add(self.source_lang)
|
||||||
|
listSizer.Add(staticDest)
|
||||||
|
listSizer.Add(self.dest_lang)
|
||||||
|
ok = wx.Button(panel, wx.ID_OK)
|
||||||
|
ok.SetDefault()
|
||||||
|
cancel = wx.Button(panel, wx.ID_CANCEL)
|
||||||
|
self.SetEscapeId(wx.ID_CANCEL)
|
||||||
|
|
||||||
|
def onOk(self, ev):
|
||||||
|
self.EndModal(wx.ID_OK)
|
151
src/extra/translator/translator.py
Normal file
151
src/extra/translator/translator.py
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
try:
|
||||||
|
import urllib2 as request
|
||||||
|
from urllib import quote
|
||||||
|
except:
|
||||||
|
from urllib import request
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
class Translator:
|
||||||
|
string_pattern = r"\"(([^\"\\]|\\.)*)\""
|
||||||
|
match_string =re.compile(
|
||||||
|
r"\,?\["
|
||||||
|
+ string_pattern + r"\,"
|
||||||
|
+ string_pattern + r"\,"
|
||||||
|
+ string_pattern + r"\,"
|
||||||
|
+ string_pattern
|
||||||
|
+r"\]")
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.from_lang = ""
|
||||||
|
self.to_lang = ""
|
||||||
|
|
||||||
|
def translate(self, source):
|
||||||
|
json5 = self._get_json5_from_google(source)
|
||||||
|
return self._unescape(self._get_translation_from_json5(json5))
|
||||||
|
|
||||||
|
def _get_translation_from_json5(self, content):
|
||||||
|
result = ""
|
||||||
|
pos = 2
|
||||||
|
while True:
|
||||||
|
m = self.match_string.match(content, pos)
|
||||||
|
if not m:
|
||||||
|
break
|
||||||
|
result += m.group(1)
|
||||||
|
pos = m.end()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _get_json5_from_google(self, source):
|
||||||
|
escaped_source = quote(source, '')
|
||||||
|
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.168 Safari/535.19'}
|
||||||
|
req = request.Request(
|
||||||
|
url="http://translate.google.com/translate_a/t?client=t&ie=UTF-8&oe=UTF-8"
|
||||||
|
+"&sl=%s&tl=%s&text=%s" % (self.from_lang, self.to_lang, escaped_source)
|
||||||
|
, headers = headers)
|
||||||
|
r = request.urlopen(req)
|
||||||
|
return r.read().decode('utf-8')
|
||||||
|
|
||||||
|
def _unescape(self, text):
|
||||||
|
return re.sub(r"\\.?", lambda x:eval('"%s"'%x.group(0)), text)
|
||||||
|
|
||||||
|
languages = {
|
||||||
|
"af": _(u"Afrikaans"),
|
||||||
|
"sq": _(u"Albanian"),
|
||||||
|
"am": _(u"Amharic"),
|
||||||
|
"ar": _(u"Arabic"),
|
||||||
|
"hy": _(u"Armenian"),
|
||||||
|
"az": _(u"Azerbaijani"),
|
||||||
|
"eu": _(u"Basque"),
|
||||||
|
"be": _(u"Belarusian"),
|
||||||
|
"bn": _(u"Bengali"),
|
||||||
|
"bh": _(u"Bihari"),
|
||||||
|
"bg": _(u"Bulgarian"),
|
||||||
|
"my": _(u"Burmese"),
|
||||||
|
"ca": _(u"Catalan"),
|
||||||
|
"chr": _(u"Cherokee"),
|
||||||
|
"zh": _(u"Chinese"),
|
||||||
|
"zh-CN": _(u"Chinese_simplified"),
|
||||||
|
"zh-TW": _(u"Chinese_traditional"),
|
||||||
|
"hr": _(u"Croatian"),
|
||||||
|
"cs": _(u"Czech"),
|
||||||
|
"da": _(u"Danish"),
|
||||||
|
"dv": _(u"Dhivehi"),
|
||||||
|
"nl": _(u"Dutch"),
|
||||||
|
"en": _(u"English"),
|
||||||
|
"eo": _(u"Esperanto"),
|
||||||
|
"et": _(u"Estonian"),
|
||||||
|
"tl": _(u"Filipino"),
|
||||||
|
"fi": _(u"Finnish"),
|
||||||
|
"fr": _(u"French"),
|
||||||
|
"gl": _(u"Galician"),
|
||||||
|
"ka": _(u"Georgian"),
|
||||||
|
"de": _(u"German"),
|
||||||
|
"el": _(u"Greek"),
|
||||||
|
"gn": _(u"Guarani"),
|
||||||
|
"gu": _(u"Gujarati"),
|
||||||
|
"iw": _(u"Hebrew"),
|
||||||
|
"hi": _(u"Hindi"),
|
||||||
|
"hu": _(u"Hungarian"),
|
||||||
|
"is": _(u"Icelandic"),
|
||||||
|
"id": _(u"Indonesian"),
|
||||||
|
"iu": _(u"Inuktitut"),
|
||||||
|
"ga": _(u"Irish"),
|
||||||
|
"it": _(u"Italian"),
|
||||||
|
"ja": _(u"Japanese"),
|
||||||
|
"kn": _(u"Kannada"),
|
||||||
|
"kk": _(u"Kazakh"),
|
||||||
|
"km": _(u"Khmer"),
|
||||||
|
"ko": _(u"Korean"),
|
||||||
|
"ku": _(u"Kurdish"),
|
||||||
|
"ky": _(u"Kyrgyz"),
|
||||||
|
"lo": _(u"Laothian"),
|
||||||
|
"lv": _(u"Latvian"),
|
||||||
|
"lt": _(u"Lithuanian"),
|
||||||
|
"mk": _(u"Macedonian"),
|
||||||
|
"ms": _(u"Malay"),
|
||||||
|
"ml": _(u"Malayalam"),
|
||||||
|
"mt": _(u"Maltese"),
|
||||||
|
"mr": _(u"Marathi"),
|
||||||
|
"mn": _(u"Mongolian"),
|
||||||
|
"ne": _(u"Nepali"),
|
||||||
|
"no": _(u"Norwegian"),
|
||||||
|
"or": _(u"Oriya"),
|
||||||
|
"ps": _(u"Pashto"),
|
||||||
|
"fa": _(u"Persian"),
|
||||||
|
"pl": _(u"Polish"),
|
||||||
|
"pt-PT": _(u"Portuguese"),
|
||||||
|
"pa": _(u"Punjabi"),
|
||||||
|
"ro": _(u"Romanian"),
|
||||||
|
"ru": _(u"Russian"),
|
||||||
|
"sa": _(u"Sanskrit"),
|
||||||
|
"sr": _(u"Serbian"),
|
||||||
|
"sd": _(u"Sindhi"),
|
||||||
|
"si": _(u"Sinhalese"),
|
||||||
|
"sk": _(u"Slovak"),
|
||||||
|
"sl": _(u"Slovenian"),
|
||||||
|
"es": _(u"Spanish"),
|
||||||
|
"sw": _(u"Swahili"),
|
||||||
|
"sv": _(u"Swedish"),
|
||||||
|
"tg": _(u"Tajik"),
|
||||||
|
"ta": _(u"Tamil"),
|
||||||
|
"tl": _(u"Tagalog"),
|
||||||
|
"te": _(u"Telugu"),
|
||||||
|
"th": _(u"Thai"),
|
||||||
|
"bo": _(u"Tibetan"),
|
||||||
|
"tr": _(u"Turkish"),
|
||||||
|
"uk": _(u"Ukrainian"),
|
||||||
|
"ur": _(u"Urdu"),
|
||||||
|
"uz": _(u"Uzbek"),
|
||||||
|
"ug": _(u"Uighur"),
|
||||||
|
"vi": _(u"Vietnamese"),
|
||||||
|
"cy": _(u"Welsh"),
|
||||||
|
"yi": _(u"Yiddish")
|
||||||
|
}
|
||||||
|
|
||||||
|
def available_languages():
|
||||||
|
l = languages.keys()
|
||||||
|
d = languages.values()
|
||||||
|
l.insert(0, '')
|
||||||
|
d.insert(0, _(u"autodetect"))
|
||||||
|
return sorted(zip(l, d))
|
105
src/gettext_windows.py
Normal file
105
src/gettext_windows.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# Copyright (c) 2006, 2007, 2010 Alexander Belchenko
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Helper for standard gettext.py on Windows.
|
||||||
|
|
||||||
|
Module obtains user language code on Windows to use with standard
|
||||||
|
Python gettext.py library.
|
||||||
|
|
||||||
|
The module provides 2 functions: setup_env and get_language.
|
||||||
|
|
||||||
|
You may use setup_env before initializing gettext functions.
|
||||||
|
|
||||||
|
Or you can use get_language to get the list of language codes suitable
|
||||||
|
to pass them to gettext.find or gettext.translation function.
|
||||||
|
|
||||||
|
Usage example #1:
|
||||||
|
|
||||||
|
import gettext, gettext_windows
|
||||||
|
gettext_windows.setup_env()
|
||||||
|
gettext.install('myapp')
|
||||||
|
|
||||||
|
Usage example #2:
|
||||||
|
|
||||||
|
import gettext, gettext_windows
|
||||||
|
lang = gettext_windows.get_language()
|
||||||
|
translation = gettext.translation('myapp', languages=lang)
|
||||||
|
_ = translation.gettext
|
||||||
|
"""
|
||||||
|
|
||||||
|
import locale
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
OS_WINDOWS = (sys.platform == 'win32')
|
||||||
|
|
||||||
|
|
||||||
|
def setup_env_windows(system_lang=True):
|
||||||
|
"""Check environment variables used by gettext
|
||||||
|
and setup LANG if there is none.
|
||||||
|
"""
|
||||||
|
if _get_lang_env_var() is not None:
|
||||||
|
return
|
||||||
|
lang = get_language_windows(system_lang)
|
||||||
|
if lang:
|
||||||
|
os.environ['LANGUAGE'] = ':'.join(lang)
|
||||||
|
|
||||||
|
def get_language_windows(system_lang=True):
|
||||||
|
"""Get language code based on current Windows settings.
|
||||||
|
@return: list of languages.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
except ImportError:
|
||||||
|
return [locale.getdefaultlocale()[0]]
|
||||||
|
# get all locales using windows API
|
||||||
|
lcid_user = ctypes.windll.kernel32.GetUserDefaultLCID()
|
||||||
|
lcid_system = ctypes.windll.kernel32.GetSystemDefaultLCID()
|
||||||
|
if system_lang and lcid_user != lcid_system:
|
||||||
|
lcids = [lcid_user, lcid_system]
|
||||||
|
else:
|
||||||
|
lcids = [lcid_user]
|
||||||
|
return filter(None, [locale.windows_locale.get(i) for i in lcids]) or None
|
||||||
|
|
||||||
|
|
||||||
|
def setup_env_other(system_lang=True):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_language_other(system_lang=True):
|
||||||
|
lang = _get_lang_env_var()
|
||||||
|
if lang is not None:
|
||||||
|
return lang.split(':')
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _get_lang_env_var():
|
||||||
|
for i in ('LANGUAGE','LC_ALL','LC_MESSAGES','LANG'):
|
||||||
|
lang = os.environ.get(i)
|
||||||
|
if lang:
|
||||||
|
return lang
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
if OS_WINDOWS:
|
||||||
|
setup_env = setup_env_windows
|
||||||
|
get_language = get_language_windows
|
||||||
|
else:
|
||||||
|
setup_env = setup_env_other
|
||||||
|
get_language = get_language_other
|
3
src/gui/__init__.py
Normal file
3
src/gui/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import main, dialogs
|
||||||
|
|
10
src/gui/buffers/__init__.py
Normal file
10
src/gui/buffers/__init__.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from panels import accountPanel, emptyPanel
|
||||||
|
from base import *
|
||||||
|
from dm import *
|
||||||
|
from events import *
|
||||||
|
from favourites import *
|
||||||
|
from lists import *
|
||||||
|
from people import *
|
||||||
|
from tweet_searches import *
|
||||||
|
from user_searches import *
|
364
src/gui/buffers/base.py
Normal file
364
src/gui/buffers/base.py
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import gui.dialogs
|
||||||
|
import twitter
|
||||||
|
import webbrowser
|
||||||
|
import config
|
||||||
|
import sound
|
||||||
|
import url_shortener
|
||||||
|
import logging as original_logger
|
||||||
|
import output
|
||||||
|
import platform
|
||||||
|
import datetime
|
||||||
|
from twitter import prettydate
|
||||||
|
from multiplatform_widgets import widgets
|
||||||
|
from mysc import event
|
||||||
|
from mysc.thread_utils import call_threaded
|
||||||
|
from twython import TwythonError
|
||||||
|
log = original_logger.getLogger("buffers.base")
|
||||||
|
|
||||||
|
class basePanel(wx.Panel):
|
||||||
|
|
||||||
|
def bind_events(self):
|
||||||
|
self.Bind(event.MyEVT_OBJECT, self.update)
|
||||||
|
self.Bind(event.MyEVT_DELETED, self.Remove)
|
||||||
|
self.list.list.Bind(wx.EVT_CHAR_HOOK, self.interact)
|
||||||
|
if self.system == "Windows":
|
||||||
|
self.list.list.Bind(wx.EVT_LIST_ITEM_FOCUSED, self.onFocus)
|
||||||
|
else:
|
||||||
|
self.list.list.Bind(wx.EVT_LISTBOX, self.onFocus)
|
||||||
|
|
||||||
|
def get_message(self, dialog=False):
|
||||||
|
if dialog == False: return " ".join(self.compose_function(self.db.settings[self.name_buffer][self.list.get_selected()], self.db))
|
||||||
|
else:
|
||||||
|
list = self.compose_function(self.db.settings[self.name_buffer][self.list.get_selected()], self.db)
|
||||||
|
return " ".join(list[1:-2])
|
||||||
|
|
||||||
|
def create_list(self):
|
||||||
|
self.list = widgets.list(self, _(u"User"), _(u"Text"), _(u"Date"), _(u"Client"), style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VRULES)
|
||||||
|
if self.system == "Windows":
|
||||||
|
self.list.set_windows_size(0, 30)
|
||||||
|
self.list.set_windows_size(1, 160)
|
||||||
|
self.list.set_windows_size(2, 55)
|
||||||
|
self.list.set_windows_size(3, 42)
|
||||||
|
self.list.set_size()
|
||||||
|
|
||||||
|
def __init__(self, parent, window, name_buffer, function=None, argumento=None, sound="", timeline=False):
|
||||||
|
if timeline == False:
|
||||||
|
self.type = "buffer"
|
||||||
|
elif timeline == True:
|
||||||
|
self.type = "timeline"
|
||||||
|
self.db = window.db
|
||||||
|
self.twitter = window.twitter
|
||||||
|
self.name_buffer = name_buffer
|
||||||
|
self.function = function
|
||||||
|
self.argumento = argumento
|
||||||
|
self.sound = sound
|
||||||
|
self.parent = window
|
||||||
|
self.compose_function = twitter.compose.compose_tweet
|
||||||
|
self.system = platform.system()
|
||||||
|
wx.Panel.__init__(self, parent)
|
||||||
|
self.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
self.create_list()
|
||||||
|
self.btn = wx.Button(self, -1, _(u"Tweet"))
|
||||||
|
self.btn.Bind(wx.EVT_BUTTON, self.post_status)
|
||||||
|
self.retweetBtn = wx.Button(self, -1, _(u"Retweet"))
|
||||||
|
self.retweetBtn.Bind(wx.EVT_BUTTON, self.onRetweet)
|
||||||
|
self.responseBtn = wx.Button(self, -1, _(u"Reply"))
|
||||||
|
self.responseBtn.Bind(wx.EVT_BUTTON, self.onResponse)
|
||||||
|
self.dmBtn = wx.Button(self, -1, _(u"Direct message"))
|
||||||
|
self.dmBtn.Bind(wx.EVT_BUTTON, self.onDm)
|
||||||
|
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
btnSizer.Add(self.btn, 0, wx.ALL, 5)
|
||||||
|
btnSizer.Add(self.retweetBtn, 0, wx.ALL, 5)
|
||||||
|
btnSizer.Add(self.responseBtn, 0, wx.ALL, 5)
|
||||||
|
btnSizer.Add(self.dmBtn, 0, wx.ALL, 5)
|
||||||
|
self.sizer.Add(btnSizer, 0, wx.ALL, 5)
|
||||||
|
self.sizer.Add(self.list.list, 0, wx.ALL, 5)
|
||||||
|
self.bind_events()
|
||||||
|
self.SetSizer(self.sizer)
|
||||||
|
|
||||||
|
def remove_buffer(self):
|
||||||
|
if self.type == "timeline":
|
||||||
|
dlg = wx.MessageDialog(self, _(u"Do you really want to delete this timeline?"), _(u"Attention"), style=wx.ICON_QUESTION|wx.YES_NO)
|
||||||
|
if dlg.ShowModal() == wx.ID_YES:
|
||||||
|
names = config.main["other_buffers"]["timelines"]
|
||||||
|
user = self.name_buffer
|
||||||
|
log.info(u"Deleting %s's timeline" % user)
|
||||||
|
if user in names:
|
||||||
|
names.remove(user)
|
||||||
|
self.db.settings.pop(user)
|
||||||
|
pos = self.db.settings["buffers"].index(user)
|
||||||
|
self.db.settings["buffers"].remove(user)
|
||||||
|
return pos
|
||||||
|
elif self.type == "buffer":
|
||||||
|
output.speak(_(u"This buffer is not a timeline; it can't be deleted."))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def remove_invalid_buffer(self):
|
||||||
|
if self.type == "timeline":
|
||||||
|
names = config.main["other_buffers"]["timelines"]
|
||||||
|
user = self.name_buffer
|
||||||
|
log.info(u"Deleting %s's timeline" % user)
|
||||||
|
if user in names:
|
||||||
|
names.remove(user)
|
||||||
|
self.db.settings.pop(user)
|
||||||
|
pos = self.db.settings["buffers"].index(user)
|
||||||
|
self.db.settings["buffers"].remove(user)
|
||||||
|
return pos
|
||||||
|
|
||||||
|
def Remove(self, ev):
|
||||||
|
# try:
|
||||||
|
self.list.remove_item(ev.GetItem())
|
||||||
|
# except:
|
||||||
|
# log.error(u"Cannot delete the %s item from list " % str(ev.GetItem()))
|
||||||
|
|
||||||
|
def destroy_status(self, ev):
|
||||||
|
index = self.list.get_selected()
|
||||||
|
try:
|
||||||
|
self.twitter.twitter.destroy_status(id=self.db.settings[self.name_buffer][index]["id"])
|
||||||
|
self.db.settings[self.name_buffer].pop(index)
|
||||||
|
self.list.remove_item(index)
|
||||||
|
if index > 0:
|
||||||
|
self.list.select_item(index-1)
|
||||||
|
except:
|
||||||
|
sound.player.play("error.ogg")
|
||||||
|
|
||||||
|
def onFocus(self, ev):
|
||||||
|
if self.db.settings[self.name_buffer][self.list.get_selected()].has_key("retweeted_status"): tweet = self.db.settings[self.name_buffer][self.list.get_selected()]["retweeted_status"]
|
||||||
|
else: tweet = self.db.settings[self.name_buffer][self.list.get_selected()]
|
||||||
|
if config.main["general"]["relative_times"] == True:
|
||||||
|
# On windows we need only put the new date on the column, but under linux and mac it isn't possible.
|
||||||
|
if self.system == "Windows":
|
||||||
|
original_date = datetime.datetime.strptime(tweet["created_at"], "%a %b %d %H:%M:%S +0000 %Y")
|
||||||
|
date = original_date-datetime.timedelta(seconds=-self.db.settings["utc_offset"])
|
||||||
|
ts = prettydate(original_date)
|
||||||
|
self.list.list.SetStringItem(self.list.get_selected(), 2, ts)
|
||||||
|
else:
|
||||||
|
self.list.list.SetString(self.list.get_selected(), " ".join(self.compose_function(self.db.settings[self.name_buffer][self.list.get_selected()], self.db)))
|
||||||
|
if twitter.utils.is_audio(tweet):
|
||||||
|
sound.player.play("audio.ogg", False)
|
||||||
|
|
||||||
|
def start_streams(self):
|
||||||
|
if self.name_buffer == "sent":
|
||||||
|
num = twitter.starting.start_sent(self.db, self.twitter, self.name_buffer, self.function, param=self.argumento)
|
||||||
|
else:
|
||||||
|
# try:
|
||||||
|
if self.argumento != None:
|
||||||
|
num = twitter.starting.start_stream(self.db, self.twitter, self.name_buffer, self.function, param=self.argumento)
|
||||||
|
else:
|
||||||
|
num = twitter.starting.start_stream(self.db, self.twitter, self.name_buffer, self.function)
|
||||||
|
# except TwythonError:
|
||||||
|
# raise TwythonError
|
||||||
|
# self.parent.delete_invalid_timeline()
|
||||||
|
if self.sound != "" and num > 0 and self.name_buffer != "home_timeline" and self.name_buffer != "sent": sound.player.play(self.sound)
|
||||||
|
return num
|
||||||
|
|
||||||
|
def get_more_items(self):
|
||||||
|
if config.main["general"]["reverse_timelines"] == False:
|
||||||
|
last_id = self.db.settings[self.name_buffer][0]["id"]
|
||||||
|
else:
|
||||||
|
last_id = self.db.settings[self.name_buffer][-1]["id"]
|
||||||
|
try:
|
||||||
|
items = twitter.starting.get_more_items(self.function, self.twitter, count=config.main["general"]["max_tweets_per_call"], max_id=last_id)
|
||||||
|
except TwythonError as e:
|
||||||
|
output.speak(e.message)
|
||||||
|
for i in items:
|
||||||
|
if config.main["general"]["reverse_timelines"] == False:
|
||||||
|
self.db.settings[self.name_buffer].insert(0, i)
|
||||||
|
else:
|
||||||
|
self.db.settings[self.name_buffer].append(i)
|
||||||
|
if config.main["general"]["reverse_timelines"] == False:
|
||||||
|
for i in items:
|
||||||
|
tweet = self.compose_function(i, self.db)
|
||||||
|
self.list.insert_item(True, *tweet)
|
||||||
|
else:
|
||||||
|
for i in items:
|
||||||
|
tweet = self.compose_function(i, self.db)
|
||||||
|
self.list.insert_item(False, *tweet)
|
||||||
|
output.speak(_(u"%s items retrieved") % (len(items)))
|
||||||
|
|
||||||
|
def put_items(self, num):
|
||||||
|
if self.list.get_count() == 0:
|
||||||
|
for i in self.db.settings[self.name_buffer]:
|
||||||
|
tweet = self.compose_function(i, self.db)
|
||||||
|
self.list.insert_item(False, *tweet)
|
||||||
|
self.set_list_position()
|
||||||
|
elif self.list.get_count() > 0:
|
||||||
|
if config.main["general"]["reverse_timelines"] == False:
|
||||||
|
for i in self.db.settings[self.name_buffer][:num]:
|
||||||
|
tweet = self.compose_function(i, self.db)
|
||||||
|
self.list.insert_item(False, *tweet)
|
||||||
|
else:
|
||||||
|
for i in self.db.settings[self.name_buffer][0:num]:
|
||||||
|
tweet = self.compose_function(i, self.db)
|
||||||
|
self.list.insert_item(True, *tweet)
|
||||||
|
|
||||||
|
def onDm(self, ev):
|
||||||
|
if self.name_buffer == "sent": return
|
||||||
|
if self.name_buffer == "direct_messages":
|
||||||
|
self.onResponse(ev)
|
||||||
|
else:
|
||||||
|
user = self.db.settings[self.name_buffer][self.list.get_selected()]["user"]["screen_name"]
|
||||||
|
dlg = gui.dialogs.message.dm(_("Direct message to %s") % (user,), "", "", self)
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
call_threaded(self.twitter.api_call, call_name="send_direct_message", _sound="dm_sent.ogg", text=dlg.text.GetValue(), screen_name=dlg.cb.GetValue())
|
||||||
|
# dlg.Destroy()
|
||||||
|
if ev != None:
|
||||||
|
self.list.list.SetFocus()
|
||||||
|
|
||||||
|
def post_status(self, ev=None):
|
||||||
|
text = gui.dialogs.message.tweet(_(u"Write the tweet here"), _(u"Tweet"), "", self)
|
||||||
|
if text.ShowModal() == wx.ID_OK:
|
||||||
|
if text.image == None:
|
||||||
|
call_threaded(self.twitter.api_call, call_name="update_status", _sound="tweet_send.ogg", status=text.text.GetValue())
|
||||||
|
else:
|
||||||
|
call_threaded(self.twitter.api_call, call_name="update_status_with_media", _sound="tweet_send.ogg", status=text.text.GetValue(), media=text.file)
|
||||||
|
# text.Destroy()
|
||||||
|
if ev != None: self.list.list.SetFocus()
|
||||||
|
|
||||||
|
def onRetweet(self, ev):
|
||||||
|
if self.name_buffer != "direct_messages":
|
||||||
|
id=self.db.settings[self.name_buffer][self.list.get_selected()]["id"]
|
||||||
|
ask = wx.MessageDialog(self.parent, _(u"Would you like to add a comment to this tweet?"), _("Retweet"), wx.YES_NO|wx.CANCEL|wx.ICON_QUESTION)
|
||||||
|
response = ask.ShowModal()
|
||||||
|
if response == wx.ID_YES:
|
||||||
|
dlg = gui.dialogs.message.retweet(_(u"Add your comment to the tweet"), "", u"“@%s: %s ”" % (self.db.settings[self.name_buffer][self.list.get_selected()]["user"]["screen_name"], self.db.settings[self.name_buffer][self.list.get_selected()]["text"]), self)
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
if dlg.image == None:
|
||||||
|
call_threaded(self.twitter.api_call, call_name="update_status", _sound="retweet_send.ogg", status=dlg.text.GetValue(), in_reply_to_status_id=dlg.in_reply_to)
|
||||||
|
else:
|
||||||
|
call_threaded(self.twitter.call_api, call_name="update_status_with_media", _sound="retweet_send.ogg", status=dlg.text.GetValue(), in_reply_to_status_id=text.in_reply_to, media=dlg.file)
|
||||||
|
# dlg.Destroy()
|
||||||
|
if ev != None:
|
||||||
|
self.list.list.SetFocus()
|
||||||
|
elif response == wx.ID_NO:
|
||||||
|
call_threaded(self.twitter.api_call, call_name="retweet", _sound="retweet_send.ogg", id=id)
|
||||||
|
if ev != None: self.list.list.SetFocus()
|
||||||
|
ask.Destroy()
|
||||||
|
|
||||||
|
def onResponse(self, ev):
|
||||||
|
if self.name_buffer == "sent": return
|
||||||
|
dlg = gui.dialogs.message.reply(_(u"Reply to %s") % (self.db.settings[self.name_buffer][self.list.get_selected()]["user"]["screen_name"]), "", u"@%s " % (self.db.settings[self.name_buffer][self.list.get_selected()]["user"]["screen_name"]), self)
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
if dlg.image == None:
|
||||||
|
call_threaded(self.twitter.api_call, call_name="update_status", _sound="reply_send.ogg", in_reply_to_status_id=dlg.in_reply_to, status=dlg.text.GetValue())
|
||||||
|
else:
|
||||||
|
call_threaded(self.twitter.api_call, call_name="update_status_with_media", _sound="reply_send.ogg", in_reply_to_status_id=dlg.in_reply_to, status=dlg.text.GetValue(), media=dlg.file)
|
||||||
|
# dlg.Destroy()
|
||||||
|
if ev != None: self.list.list.SetFocus()
|
||||||
|
|
||||||
|
def update(self, ev):
|
||||||
|
data = ev.GetItem()
|
||||||
|
announce = ev.GetAnnounce()
|
||||||
|
if config.main["general"]["reverse_timelines"] == False: self.db.settings[self.name_buffer].append(data)
|
||||||
|
else: self.db.settings[self.name_buffer].insert(0, data)
|
||||||
|
tweet = self.compose_function(data, self.db)
|
||||||
|
self.list.insert_item(config.main["general"]["reverse_timelines"], *tweet)
|
||||||
|
if self.name_buffer not in config.main["other_buffers"]["muted_buffers"]:
|
||||||
|
if self.sound != "": sound.player.play(self.sound)
|
||||||
|
if announce != "": output.speak(announce)
|
||||||
|
if self.name_buffer in config.main["other_buffers"]["autoread_buffers"]:
|
||||||
|
output.speak(" ".join(tweet[:2]))
|
||||||
|
|
||||||
|
def interact(self, ev):
|
||||||
|
try:
|
||||||
|
if self.db.settings[self.name_buffer][self.list.get_selected()].has_key("retweeted_status"): tweet = self.db.settings[self.name_buffer][self.list.get_selected()]["retweeted_status"]
|
||||||
|
else: tweet = self.db.settings[self.name_buffer][self.list.get_selected()]
|
||||||
|
urls = twitter.utils.find_urls_in_text(tweet["text"])
|
||||||
|
except:
|
||||||
|
urls = []
|
||||||
|
if type(ev) is str: event = ev
|
||||||
|
else:
|
||||||
|
if ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown(): event = "audio"
|
||||||
|
elif ev.GetKeyCode() == wx.WXK_RETURN: event = "url"
|
||||||
|
elif ev.GetKeyCode() == wx.WXK_F5: event = "volume_down"
|
||||||
|
elif ev.GetKeyCode() == wx.WXK_F6: event = "volume_up"
|
||||||
|
elif ev.GetKeyCode() == wx.WXK_DELETE and ev.ShiftDown(): event = "clear_list"
|
||||||
|
elif ev.GetKeyCode() == wx.WXK_DELETE: event = "delete_item"
|
||||||
|
else:
|
||||||
|
ev.Skip()
|
||||||
|
return
|
||||||
|
if event == "audio" and len(urls) > 0:
|
||||||
|
self.streamer(urls[0])
|
||||||
|
elif event == "url":
|
||||||
|
if len(urls) == 0: return
|
||||||
|
elif len(urls) == 1:
|
||||||
|
output.speak(_(u"Opening URL..."), True)
|
||||||
|
webbrowser.open(urls[0])
|
||||||
|
elif len(urls) > 1:
|
||||||
|
gui.dialogs.urlList.urlList(urls).ShowModal()
|
||||||
|
elif event == "volume_down":
|
||||||
|
if config.main["sound"]["volume"] > 0.05:
|
||||||
|
config.main["sound"]["volume"] = config.main["sound"]["volume"]-0.05
|
||||||
|
sound.player.play("volume_changed.ogg", False)
|
||||||
|
if hasattr(self.parent, "audioStream"):
|
||||||
|
self.parent.audioStream.stream.volume = config.main["sound"]["volume"]
|
||||||
|
elif event == "volume_up":
|
||||||
|
if config.main["sound"]["volume"] < 0.95:
|
||||||
|
config.main["sound"]["volume"] = config.main["sound"]["volume"]+0.05
|
||||||
|
sound.player.play("volume_changed.ogg", False)
|
||||||
|
if hasattr(self.parent, "audioStream"):
|
||||||
|
self.parent.audioStream.stream.volume = config.main["sound"]["volume"]
|
||||||
|
elif event == "clear_list" and self.list.get_count() > 0:
|
||||||
|
dlg = wx.MessageDialog(self, _(u"Do you really want to empty this buffer? It's tweets will be removed from the list but not from Twitter"), _(u"Empty buffer"), wx.ICON_QUESTION|wx.YES_NO)
|
||||||
|
if dlg.ShowModal() == wx.ID_YES:
|
||||||
|
self.db.settings[self.name_buffer] = []
|
||||||
|
self.list.clear()
|
||||||
|
elif event == "delete_item":
|
||||||
|
dlg = wx.MessageDialog(self, _(u"Do you really want to delete this message?"), _(u"Delete"), wx.ICON_QUESTION|wx.YES_NO)
|
||||||
|
if dlg.ShowModal() == wx.ID_YES:
|
||||||
|
self.destroy_status(wx.EVT_MENU)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
ev.Skip()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def streamer(self, url):
|
||||||
|
if hasattr(self.parent, "audioStream"):
|
||||||
|
if self.parent.audioStream.stream.is_active() == 0:
|
||||||
|
output.speak(_(u"Playing..."))
|
||||||
|
self.parent.audioStream = sound.urlStream(url)
|
||||||
|
try:
|
||||||
|
self.parent.audioStream.prepare()
|
||||||
|
self.parent.audioStream.play()
|
||||||
|
except:
|
||||||
|
del self.parent.audioStream
|
||||||
|
output.speak(_(u"Unable to play audio."))
|
||||||
|
else:
|
||||||
|
output.speak(_(u"Audio stopped."))
|
||||||
|
self.parent.audioStream.stream.stop()
|
||||||
|
else:
|
||||||
|
output.speak(_(u"Playing..."))
|
||||||
|
self.parent.audioStream = sound.urlStream(url)
|
||||||
|
try:
|
||||||
|
self.parent.audioStream.prepare()
|
||||||
|
self.parent.audioStream.play()
|
||||||
|
except:
|
||||||
|
output.speak(_(u"Unable to play audio."))
|
||||||
|
del self.parent.audioStream
|
||||||
|
|
||||||
|
def set_list_position(self):
|
||||||
|
if config.main["general"]["reverse_timelines"] == False:
|
||||||
|
self.list.select_item(len(self.db.settings[self.name_buffer])-1)
|
||||||
|
else:
|
||||||
|
self.list.select_item(0)
|
48
src/gui/buffers/dm.py
Normal file
48
src/gui/buffers/dm.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import sound
|
||||||
|
import gui.dialogs
|
||||||
|
import logging as original_logger
|
||||||
|
from base import basePanel
|
||||||
|
from mysc.thread_utils import call_threaded
|
||||||
|
log = original_logger.getLogger("buffers.base")
|
||||||
|
|
||||||
|
class dmPanel(basePanel):
|
||||||
|
def __init__(self, parent, window, name_buffer, function, argumento=None, sound=""):
|
||||||
|
""" Class to DM'S. Reply and retweet buttons are not showed and they have your delete method for dm's."""
|
||||||
|
super(dmPanel, self).__init__(parent, window, name_buffer, function, argumento=argumento, sound=sound)
|
||||||
|
self.retweetBtn.Disable()
|
||||||
|
self.responseBtn.Disable()
|
||||||
|
|
||||||
|
def destroy_status(self, ev):
|
||||||
|
index = self.list.get_selected()
|
||||||
|
try:
|
||||||
|
self.twitter.twitter.destroy_direct_message(id=self.db.settings[self.name_buffer][index]["id"])
|
||||||
|
self.db.settings[self.name_buffer].pop(index)
|
||||||
|
self.remove_item(index)
|
||||||
|
except:
|
||||||
|
sound.player.play("error.ogg")
|
||||||
|
|
||||||
|
def onResponse(self, ev):
|
||||||
|
dlg = gui.dialogs.message.dm(_("Direct message to %s") % (self.db.settings[self.name_buffer][self.list.get_selected()]["sender"]["screen_name"]), "", "", self)
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
call_threaded(self.twitter.api_call, call_name="send_direct_message", _sound="dm_sent.ogg", text=dlg.text.GetValue(), screen_name=dlg.cb.GetValue())
|
||||||
|
if ev != None:
|
||||||
|
self.list.list.SetFocus()
|
134
src/gui/buffers/events.py
Normal file
134
src/gui/buffers/events.py
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import sound
|
||||||
|
import config
|
||||||
|
import platform
|
||||||
|
import gui.dialogs
|
||||||
|
import output
|
||||||
|
import logging as original_logger
|
||||||
|
from multiplatform_widgets import widgets
|
||||||
|
from mysc import event
|
||||||
|
from mysc.thread_utils import call_threaded
|
||||||
|
log = original_logger.getLogger("buffers.base")
|
||||||
|
|
||||||
|
class eventsPanel(wx.Panel):
|
||||||
|
""" Buffer to show events. Different than tweets or people."""
|
||||||
|
|
||||||
|
def get_more_items(self):
|
||||||
|
output.speak(_(u"This action is not supported for this buffer"))
|
||||||
|
|
||||||
|
def bind_events(self):
|
||||||
|
self.Bind(event.MyEVT_OBJECT, self.update)
|
||||||
|
|
||||||
|
def put_items(self, items):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_selected_text(self):
|
||||||
|
if self.list.get_count() == 0: return _(u"Empty")
|
||||||
|
if self.system == "Windows":
|
||||||
|
return "%s. %s" % (self.list.list.GetItemText(self.list.get_selected()), self.list.list.GetItemText(self.list.get_selected(), 1))
|
||||||
|
else:
|
||||||
|
return self.list.list.GetStringSelection()
|
||||||
|
|
||||||
|
def get_message(self, dialog=False):
|
||||||
|
return self.get_selected_text()
|
||||||
|
|
||||||
|
def __init__(self, parent, window, sound=""):
|
||||||
|
self.type = "event"
|
||||||
|
self.system = platform.system()
|
||||||
|
self.name_buffer = "events"
|
||||||
|
self.parent = window
|
||||||
|
self.sound = "new_event.ogg"
|
||||||
|
wx.Panel.__init__(self, parent)
|
||||||
|
sizer = wx.BoxSizer()
|
||||||
|
self.list = widgets.list(self, _(u"Date"), _(u"Event"), size=(600,600), style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VRULES)
|
||||||
|
self.tweet = wx.Button(self, -1, _(u"Tweet"))
|
||||||
|
self.tweet.Bind(wx.EVT_BUTTON, self.post_status)
|
||||||
|
self.delete_event = wx.Button(self, -1, _(u"Remove event"))
|
||||||
|
self.delete_event.Bind(wx.EVT_BUTTON, self.on_delete_event)
|
||||||
|
self.bind_events()
|
||||||
|
|
||||||
|
def on_delete_event(self, ev):
|
||||||
|
self.list.remove_item(self.get_selected())
|
||||||
|
|
||||||
|
def remove_buffer(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def start_streams(self):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def post_status(self, ev=None):
|
||||||
|
text = gui.dialogs.message.tweet(_(u"Write the tweet here"), _(u"Tweet"), "", self.parent)
|
||||||
|
if text.ShowModal() == wx.ID_OK:
|
||||||
|
if text.image == None:
|
||||||
|
call_threaded(self.parent.twitter.api_call, call_name="update_status", _sound="tweet_send.ogg", status=text.text.GetValue())
|
||||||
|
else:
|
||||||
|
call_threaded(self.parent.twitter.api_call, call_name="update_status_with_media", _sound="tweet_send.ogg", status=text.text.GetValue(), media=text.file)
|
||||||
|
# text.Destroy()
|
||||||
|
if ev != None: self.list.list.SetFocus()
|
||||||
|
|
||||||
|
def update(self, ev):
|
||||||
|
tweet = ev.GetItem()
|
||||||
|
announce = ev.GetAnnounce()
|
||||||
|
self.list.insert_item(config.main["general"]["reverse_timelines"], *tweet)
|
||||||
|
if self.list.get_count() == 1:
|
||||||
|
self.list.select_item(0)
|
||||||
|
if self.name_buffer not in config.main["other_buffers"]["muted_buffers"]:
|
||||||
|
if self.sound != "": sound.player.play(self.sound)
|
||||||
|
# if announce != "": output.speak(announce)
|
||||||
|
if self.name_buffer in config.main["other_buffers"]["autoread_buffers"]:
|
||||||
|
output.speak(" ".join(tweet))
|
||||||
|
|
||||||
|
def interact(self, ev):
|
||||||
|
if type(ev) is str: event = ev
|
||||||
|
else:
|
||||||
|
if ev.GetKeyCode() == wx.WXK_F5: event = "volume_down"
|
||||||
|
elif ev.GetKeyCode() == wx.WXK_F6: event = "volume_up"
|
||||||
|
elif ev.GetKeyCode() == wx.WXK_DELETE and ev.ShiftDown(): event = "clear_list"
|
||||||
|
elif ev.GetKeyCode() == wx.WXK_DELETE: event = "delete_item"
|
||||||
|
else:
|
||||||
|
ev.Skip()
|
||||||
|
return
|
||||||
|
if event == "volume_down":
|
||||||
|
if config.main["sound"]["volume"] > 0.05:
|
||||||
|
config.main["sound"]["volume"] = config.main["sound"]["volume"]-0.05
|
||||||
|
sound.player.play("volume_changed.ogg", False)
|
||||||
|
if hasattr(self.parent, "audioStream"):
|
||||||
|
self.parent.audioStream.stream.volume = config.main["sound"]["volume"]
|
||||||
|
elif event == "volume_up":
|
||||||
|
if config.main["sound"]["volume"] < 0.95:
|
||||||
|
config.main["sound"]["volume"] = config.main["sound"]["volume"]+0.05
|
||||||
|
sound.player.play("volume_changed.ogg", False)
|
||||||
|
if hasattr(self.parent, "audioStream"):
|
||||||
|
self.parent.audioStream.stream.volume = config.main["sound"]["volume"]
|
||||||
|
elif event == "clear_list" and self.get_count() > 0:
|
||||||
|
dlg = wx.MessageDialog(self, _(u"Do you really want to empty this buffer? It's tweets will be removed from the list but not from Twitter"), _(u"Empty buffer"), wx.ICON_QUESTION|wx.YES_NO)
|
||||||
|
if dlg.ShowModal() == wx.ID_YES:
|
||||||
|
self.list.clear()
|
||||||
|
elif event == "delete_item":
|
||||||
|
dlg = wx.MessageDialog(self, _(u"Do you really want to delete this message?"), _(u"Delete"), wx.ICON_QUESTION|wx.YES_NO)
|
||||||
|
if dlg.ShowModal() == wx.ID_YES:
|
||||||
|
self.list.remove_item(self.list.get_selected())
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
ev.Skip()
|
||||||
|
except:
|
||||||
|
pass
|
62
src/gui/buffers/favourites.py
Normal file
62
src/gui/buffers/favourites.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import twitter
|
||||||
|
import config
|
||||||
|
import sound
|
||||||
|
import logging as original_logger
|
||||||
|
from base import basePanel
|
||||||
|
log = original_logger.getLogger("buffers.base")
|
||||||
|
|
||||||
|
class favsPanel(basePanel):
|
||||||
|
def __init__(self, parent, window, name_buffer, argumento=None, sound=""):
|
||||||
|
super(favsPanel, self).__init__(parent, window, name_buffer, function="", argumento=argumento, sound=sound)
|
||||||
|
self.type = "favourites_timeline"
|
||||||
|
|
||||||
|
def start_streams(self):
|
||||||
|
num = twitter.starting.get_favourites_timeline(self.db, self.twitter, self.name_buffer, param=self.argumento, count=200)
|
||||||
|
if self.sound != "" and num > 0:
|
||||||
|
sound.player.play(self.sound)
|
||||||
|
if self.list.get_count() > 0: self.put_items(num)
|
||||||
|
return num
|
||||||
|
return num
|
||||||
|
|
||||||
|
def remove_buffer(self):
|
||||||
|
dlg = wx.MessageDialog(self, _(u"Do you really want to delete this favourites timeline?"), _(u"Attention"), style=wx.ICON_QUESTION|wx.YES_NO)
|
||||||
|
if dlg.ShowModal() == wx.ID_YES:
|
||||||
|
names = config.main["other_buffers"]["favourites_timelines"]
|
||||||
|
user = self.argumento
|
||||||
|
log.info(u"Deleting %s's timeline" % user)
|
||||||
|
if user in names:
|
||||||
|
names.remove(user)
|
||||||
|
self.db.settings.pop(self.name_buffer)
|
||||||
|
pos = self.db.settings["buffers"].index(self.name_buffer)
|
||||||
|
self.db.settings["buffers"].remove(self.name_buffer)
|
||||||
|
return pos
|
||||||
|
|
||||||
|
def remove_invalid_buffer(self):
|
||||||
|
names = config.main["other_buffers"]["favourites_timelines"]
|
||||||
|
user = self.name_buffer[:-5]
|
||||||
|
log.info(u"Deleting %s's timeline" % user)
|
||||||
|
if user in names:
|
||||||
|
names.remove(user)
|
||||||
|
self.db.settings.pop(self.name_buffer)
|
||||||
|
pos = self.db.settings["buffers"].index(self.name_buffer)
|
||||||
|
self.db.settings["buffers"].remove(self.name_buffer)
|
||||||
|
return pos
|
68
src/gui/buffers/lists.py
Normal file
68
src/gui/buffers/lists.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import sound
|
||||||
|
import config
|
||||||
|
import twitter
|
||||||
|
import gui.dialogs
|
||||||
|
import logging as original_logger
|
||||||
|
from base import basePanel
|
||||||
|
from mysc.thread_utils import call_threaded
|
||||||
|
log = original_logger.getLogger("buffers.base")
|
||||||
|
|
||||||
|
class listPanel(basePanel):
|
||||||
|
def __init__(self, parent, window, name_buffer, argumento="", sound=""):
|
||||||
|
super(listPanel, self).__init__(parent, window, name_buffer, argumento=argumento, sound=sound)
|
||||||
|
self.type = "list"
|
||||||
|
self.users = []
|
||||||
|
self.sound = "list_tweet.ogg"
|
||||||
|
|
||||||
|
def start_streams(self):
|
||||||
|
self.retrieve_ids()
|
||||||
|
num = twitter.starting.start_list(self.db, self.twitter, self.name_buffer, list_id=self.argumento)
|
||||||
|
return num
|
||||||
|
|
||||||
|
def retrieve_ids(self):
|
||||||
|
self.users = twitter.starting.get_users_list(self.twitter, self.argumento)
|
||||||
|
|
||||||
|
def remove_buffer(self):
|
||||||
|
if self.type == "list":
|
||||||
|
dlg = wx.MessageDialog(self, _(u"Do you really want to delete this list?"), _(u"Attention"), style=wx.ICON_QUESTION|wx.YES_NO)
|
||||||
|
if dlg.ShowModal() == wx.ID_YES:
|
||||||
|
names = config.main["other_buffers"]["lists"]
|
||||||
|
user = self.name_buffer[:-5]
|
||||||
|
log.info(u"Deleting %s's list" % user)
|
||||||
|
if user in names:
|
||||||
|
names.remove(user)
|
||||||
|
self.db.settings.pop(self.name_buffer)
|
||||||
|
pos = self.db.settings["buffers"].index(self.name_buffer)
|
||||||
|
self.db.settings["buffers"].remove(self.name_buffer)
|
||||||
|
return pos
|
||||||
|
|
||||||
|
def remove_invalid_buffer(self):
|
||||||
|
if self.type == "list":
|
||||||
|
names = config.main["other_buffers"]["lists"]
|
||||||
|
user = self.name_buffer[:-5]
|
||||||
|
log.info(u"Deleting %s's list" % user)
|
||||||
|
if user in names:
|
||||||
|
names.remove(user)
|
||||||
|
self.db.settings.pop(self.name_buffer)
|
||||||
|
pos = self.db.settings["buffers"].index(self.name_buffer)
|
||||||
|
self.db.settings["buffers"].remove(self.name_buffer)
|
||||||
|
return pos
|
37
src/gui/buffers/panels.py
Normal file
37
src/gui/buffers/panels.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
from multiplatform_widgets import widgets
|
||||||
|
|
||||||
|
class accountPanel(wx.Panel):
|
||||||
|
def __init__(self, parent):
|
||||||
|
super(accountPanel, self).__init__(parent=parent)
|
||||||
|
self.type = "account"
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
self.list = widgets.list(self, _(u"Announce"))
|
||||||
|
sizer.Add(self.list.list, 0, wx.ALL, 5)
|
||||||
|
self.SetSizer(sizer)
|
||||||
|
|
||||||
|
class emptyPanel(accountPanel):
|
||||||
|
def __init__(self, parent):
|
||||||
|
super(emptyPanel, self).__init__(parent=parent)
|
||||||
|
self.type = "empty"
|
||||||
|
|
||||||
|
def get_more_items(self):
|
||||||
|
output.speak(_(u"This action is not supported for this buffer"))
|
142
src/gui/buffers/people.py
Normal file
142
src/gui/buffers/people.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import sound
|
||||||
|
import config
|
||||||
|
import twitter
|
||||||
|
import gui.dialogs
|
||||||
|
import logging as original_logger
|
||||||
|
import output
|
||||||
|
from multiplatform_widgets import widgets
|
||||||
|
from mysc import event
|
||||||
|
from base import basePanel
|
||||||
|
from mysc.thread_utils import call_threaded
|
||||||
|
from twython import TwythonError
|
||||||
|
log = original_logger.getLogger("buffers.base")
|
||||||
|
|
||||||
|
class peoplePanel(basePanel):
|
||||||
|
""" Buffer used to show people."""
|
||||||
|
def bind_events(self):
|
||||||
|
self.Bind(event.MyEVT_OBJECT, self.update)
|
||||||
|
self.Bind(event.MyEVT_DELETED, self.Remove)
|
||||||
|
self.list.list.Bind(wx.EVT_CHAR_HOOK, self.interact)
|
||||||
|
|
||||||
|
def create_list(self):
|
||||||
|
self.list = widgets.list(self, _(u"User"), style=wx.LC_REPORT|wx.LC_SINGLE_SEL, size=(800, 800))
|
||||||
|
|
||||||
|
def __init__(self, parent, window, name_buffer, function, argumento=None, sound="", timeline=False):
|
||||||
|
self.type = "people"
|
||||||
|
super(peoplePanel, self).__init__(parent, window, name_buffer, function, argumento=argumento, sound=sound)
|
||||||
|
self.responseBtn.SetLabel(_(u"Mention"))
|
||||||
|
self.retweetBtn.Disable()
|
||||||
|
self.compose_function = twitter.compose.compose_followers_list
|
||||||
|
|
||||||
|
def onDm(self, ev):
|
||||||
|
if self.name_buffer == "sent": return
|
||||||
|
if self.name_buffer == "direct_messages":
|
||||||
|
self.onResponse(ev)
|
||||||
|
else:
|
||||||
|
user = self.db.settings[self.name_buffer][self.list.get_selected()]["screen_name"]
|
||||||
|
dlg = gui.dialogs.message.dm(_("Direct message to %s") % (user,), "", "", self)
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
call_threaded(self.twitter.api_call, call_name="send_direct_message", _sound="dm_sent.ogg", text=dlg.text.GetValue(), screen_name=dlg.cb.GetValue())
|
||||||
|
# dlg.Destroy()
|
||||||
|
if ev != None:
|
||||||
|
self.list.list.SetFocus()
|
||||||
|
|
||||||
|
def onResponse(self, ev):
|
||||||
|
dlg = gui.dialogs.message.reply(_(u"Reply to %s") % (self.db.settings[self.name_buffer][self.list.get_selected()]["screen_name"]), "", u"@%s " % (self.db.settings[self.name_buffer][self.list.get_selected()]["screen_name"]), self)
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
if dlg.image == None:
|
||||||
|
call_threaded(self.twitter.api_call, call_name="update_status", _sound="reply_send.ogg", in_reply_to_status_id=dlg.in_reply_to, status=dlg.text.GetValue())
|
||||||
|
else:
|
||||||
|
call_threaded(self.twitter.api_call, call_name="update_status_with_media", _sound="reply_send.ogg", in_reply_to_status_id=dlg.in_reply_to, status=dlg.text.GetValue(), media=dlg.file)
|
||||||
|
# dlg.Destroy()
|
||||||
|
if ev != None: self.list.list.SetFocus()
|
||||||
|
|
||||||
|
def Remove(self, ev):
|
||||||
|
try:
|
||||||
|
index = self.list.get_selected()
|
||||||
|
self.list.remove_item(ev.GetItem())
|
||||||
|
except:
|
||||||
|
log.error("Unable to delete element %s from the list %s" % (str(ev.GetItem(), self.name_buffer)))
|
||||||
|
|
||||||
|
def start_streams(self):
|
||||||
|
num = twitter.starting.start_followers(self.db, self.twitter, self.name_buffer, self.function, param=self.argumento)
|
||||||
|
# sound.player.play(self.sound)
|
||||||
|
return num
|
||||||
|
|
||||||
|
def put_items(self, num):
|
||||||
|
if self.list.get_count() > 0:
|
||||||
|
self.list.clear()
|
||||||
|
for i in self.db.settings[self.name_buffer]:
|
||||||
|
f = self.compose_function(i, self.db)
|
||||||
|
self.list.insert_item(False, *f)
|
||||||
|
self.set_list_position()
|
||||||
|
|
||||||
|
def get_more_items(self):
|
||||||
|
if self.name_buffer == "followers": cursor = twitter.starting.followers_cursor
|
||||||
|
elif self.name_buffer == "friends": cursor = twitter.starting.friends_cursor
|
||||||
|
try:
|
||||||
|
items = twitter.starting.get_more_items(self.function, self.twitter, users=True, name=self.name_buffer, count=config.main["general"]["max_tweets_per_call"], cursor=cursor)
|
||||||
|
except TwythonError as e:
|
||||||
|
output.speak(e.message)
|
||||||
|
return
|
||||||
|
for i in items:
|
||||||
|
if config.main["general"]["reverse_timelines"] == False:
|
||||||
|
self.db.settings[self.name_buffer].insert(0, i)
|
||||||
|
else:
|
||||||
|
self.db.settings[self.name_buffer].append(i)
|
||||||
|
if config.main["general"]["reverse_timelines"] == False:
|
||||||
|
for i in items:
|
||||||
|
tweet = self.compose_function(i, self.db)
|
||||||
|
self.list.insert_item(True, *tweet)
|
||||||
|
else:
|
||||||
|
for i in items:
|
||||||
|
tweet = self.compose_function(i, self.db)
|
||||||
|
self.list.insert_item(False, *tweet)
|
||||||
|
output.speak(_(u"%s items retrieved") % (len(items)))
|
||||||
|
|
||||||
|
def interact(self, ev):
|
||||||
|
if type(ev) is str: event = ev
|
||||||
|
else:
|
||||||
|
if ev.GetKeyCode() == wx.WXK_RETURN:
|
||||||
|
event = "url"
|
||||||
|
elif ev.GetKeyCode() == wx.WXK_F5:
|
||||||
|
event = "volume_down"
|
||||||
|
elif ev.GetKeyCode() == wx.WXK_F6:
|
||||||
|
event = "volume_down"
|
||||||
|
else:
|
||||||
|
ev.Skip()
|
||||||
|
return
|
||||||
|
if event == "url":
|
||||||
|
gui.dialogs.show_user.showUserProfile(self.parent.twitter, self.db.settings[self.name_buffer][self.list.get_selected()]["screen_name"]).ShowModal()
|
||||||
|
elif event == "volume_down":
|
||||||
|
if config.main["sound"]["volume"] > 0.05:
|
||||||
|
config.main["sound"]["volume"] = config.main["sound"]["volume"]-0.05
|
||||||
|
sound.player.play("volume_changed.ogg")
|
||||||
|
elif event == "volume_up":
|
||||||
|
if config.main["sound"]["volume"] < 0.95:
|
||||||
|
config.main["sound"]["volume"] = config.main["sound"]["volume"]+0.05
|
||||||
|
sound.player.play("volume_changed.ogg")
|
||||||
|
if type(ev) is not str: ev.Skip()
|
||||||
|
|
||||||
|
def remove_buffer(self):
|
||||||
|
pos = None
|
||||||
|
return pos
|
51
src/gui/buffers/trends.py
Normal file
51
src/gui/buffers/trends.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import sound
|
||||||
|
import config
|
||||||
|
import twitter
|
||||||
|
import gui.dialogs
|
||||||
|
import logging as original_logger
|
||||||
|
from base import basePanel
|
||||||
|
log = original_logger.getLogger("buffers.base")
|
||||||
|
|
||||||
|
class trendPanel(basePanel):
|
||||||
|
def __init__(self, parent, window, name_buffer, *args, **kwargs):
|
||||||
|
super(searchPanel, self).__init__(parent, window, name_buffer, sound)
|
||||||
|
self.type = "trend"
|
||||||
|
self.args = kwargs
|
||||||
|
|
||||||
|
def start_streams(self):
|
||||||
|
num = twitter.starting.search(self.db, self.twitter, self.name_buffer, **self.args)
|
||||||
|
if num > 0: sound.player.play("search_updated.ogg")
|
||||||
|
self.put_items(num)
|
||||||
|
return num
|
||||||
|
|
||||||
|
def remove_buffer(self):
|
||||||
|
dlg = wx.MessageDialog(self, _(u"Do you really want to delete this search term?"), _(u"Attention"), style=wx.ICON_QUESTION|wx.YES_NO)
|
||||||
|
if dlg.ShowModal() == wx.ID_YES:
|
||||||
|
names = config.main["other_buffers"]["tweet_searches"]
|
||||||
|
user = self.name_buffer[:-7]
|
||||||
|
log.info(u"Deleting %s's search term" % user)
|
||||||
|
if user in names:
|
||||||
|
names.remove(user)
|
||||||
|
self.db.settings.pop(self.name_buffer)
|
||||||
|
pos = self.db.settings["buffers"].index(self.name_buffer)
|
||||||
|
self.db.settings["buffers"].remove(self.name_buffer)
|
||||||
|
return pos
|
58
src/gui/buffers/tweet_searches.py
Normal file
58
src/gui/buffers/tweet_searches.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import sound
|
||||||
|
import config
|
||||||
|
import twitter
|
||||||
|
import gui.dialogs
|
||||||
|
import output
|
||||||
|
import logging as original_logger
|
||||||
|
from base import basePanel
|
||||||
|
log = original_logger.getLogger("buffers.base")
|
||||||
|
|
||||||
|
class searchPanel(basePanel):
|
||||||
|
def __init__(self, parent, window, name_buffer, *args, **kwargs):
|
||||||
|
super(searchPanel, self).__init__(parent, window, name_buffer, sound)
|
||||||
|
self.type = "search"
|
||||||
|
self.args = kwargs
|
||||||
|
|
||||||
|
def load_search(self):
|
||||||
|
num = self.start_streams()
|
||||||
|
self.put_items(num)
|
||||||
|
|
||||||
|
def start_streams(self):
|
||||||
|
num = twitter.starting.search(self.db, self.twitter, self.name_buffer, **self.args)
|
||||||
|
if num > 0: sound.player.play("search_updated.ogg")
|
||||||
|
return num
|
||||||
|
|
||||||
|
def remove_buffer(self):
|
||||||
|
dlg = wx.MessageDialog(self, _(u"Do you really want to delete this search term?"), _(u"Attention"), style=wx.ICON_QUESTION|wx.YES_NO)
|
||||||
|
if dlg.ShowModal() == wx.ID_YES:
|
||||||
|
names = config.main["other_buffers"]["tweet_searches"]
|
||||||
|
user = self.name_buffer[:-7]
|
||||||
|
log.info(u"Deleting %s's search term" % user)
|
||||||
|
if user in names:
|
||||||
|
names.remove(user)
|
||||||
|
self.db.settings.pop(self.name_buffer)
|
||||||
|
pos = self.db.settings["buffers"].index(self.name_buffer)
|
||||||
|
self.db.settings["buffers"].remove(self.name_buffer)
|
||||||
|
return pos
|
||||||
|
|
||||||
|
def get_more_items(self):
|
||||||
|
output.speak(_(u"This action is not supported for this buffer"))
|
61
src/gui/buffers/user_searches.py
Normal file
61
src/gui/buffers/user_searches.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import twitter
|
||||||
|
import logging as original_logger
|
||||||
|
import sound
|
||||||
|
from people import peoplePanel
|
||||||
|
from mysc import event
|
||||||
|
from multiplatform_widgets import widgets
|
||||||
|
log = original_logger.getLogger("buffers.base")
|
||||||
|
|
||||||
|
class searchUsersPanel(peoplePanel):
|
||||||
|
def create_list(self):
|
||||||
|
""" Returns the list for put the tweets here."""
|
||||||
|
self.list = widgets.list(self, _(u"User"), style=wx.LC_REPORT|wx.LC_SINGLE_SEL, size=(800, 800))
|
||||||
|
|
||||||
|
# def bind_events(self):
|
||||||
|
# self.Bind(event.MyEVT_OBJECT, self.update)
|
||||||
|
# self.list.list.Bind(wx.EVT_CHAR_HOOK, self.interact)
|
||||||
|
|
||||||
|
def __init__(self, parent, window, name_buffer, *args, **kwargs):
|
||||||
|
super(searchUsersPanel, self).__init__(parent, window, name_buffer, function=None)
|
||||||
|
self.compose_function = twitter.compose.compose_followers_list
|
||||||
|
self.create_list()
|
||||||
|
self.args = args
|
||||||
|
self.kwargs = kwargs
|
||||||
|
self.type = "timeline"
|
||||||
|
|
||||||
|
def start_streams(self):
|
||||||
|
num = twitter.starting.search_users(self.db, self.twitter, self.name_buffer, **self.kwargs)
|
||||||
|
if num > 0: sound.player.play("search_updated.ogg")
|
||||||
|
# self.put_items(num)
|
||||||
|
return num
|
||||||
|
|
||||||
|
def load_search(self):
|
||||||
|
num = self.start_streams()
|
||||||
|
self.put_items(num)
|
||||||
|
|
||||||
|
def remove_buffer(self):
|
||||||
|
dlg = wx.MessageDialog(self, _(u"Do you really want to delete this search term?"), _(u"Attention"), style=wx.ICON_QUESTION|wx.YES_NO)
|
||||||
|
if dlg.ShowModal() == wx.ID_YES:
|
||||||
|
self.db.settings.pop(self.name_buffer)
|
||||||
|
pos = self.db.settings["buffers"].index(self.name_buffer)
|
||||||
|
self.db.settings["buffers"].remove(self.name_buffer)
|
||||||
|
return pos
|
1
src/gui/dialogs/__init__.py
Normal file
1
src/gui/dialogs/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
import message, urlList, follow, utils, show_user, update_profile, configuration, lists, search
|
420
src/gui/dialogs/configuration.py
Normal file
420
src/gui/dialogs/configuration.py
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import config
|
||||||
|
#import gui
|
||||||
|
from gui import buffers
|
||||||
|
import sound as snd
|
||||||
|
import sound_lib
|
||||||
|
import languageHandler
|
||||||
|
import logging as original_logger
|
||||||
|
import os
|
||||||
|
import webbrowser
|
||||||
|
import paths
|
||||||
|
import platform
|
||||||
|
from mysc import restart
|
||||||
|
log = original_logger.getLogger("configuration")
|
||||||
|
|
||||||
|
system = platform.system()
|
||||||
|
class general(wx.Panel):
|
||||||
|
def __init__(self, parent):
|
||||||
|
wx.Panel.__init__(self, parent)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
language = wx.StaticText(self, -1, _(u"Language"))
|
||||||
|
self.langs = languageHandler.getAvailableLanguages()
|
||||||
|
langs = []
|
||||||
|
[langs.append(i[1]) for i in self.langs]
|
||||||
|
self.codes = []
|
||||||
|
[self.codes.append(i[0]) for i in self.langs]
|
||||||
|
self.language = wx.ListBox(self, -1, choices=langs)
|
||||||
|
id = self.codes.index(config.main["general"]["language"])
|
||||||
|
self.language.SetSelection(id)
|
||||||
|
self.language.SetSize(self.language.GetBestSize())
|
||||||
|
langBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
langBox.Add(language, 0, wx.ALL, 5)
|
||||||
|
langBox.Add(self.language, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(langBox, 0, wx.ALL, 5)
|
||||||
|
self.relative_time = wx.CheckBox(self, -1, _(U"Relative times"))
|
||||||
|
self.relative_time.SetValue(config.main["general"]["relative_times"])
|
||||||
|
sizer.Add(self.relative_time, 0, wx.ALL, 5)
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
self.disable_sapi5 = wx.CheckBox(self, -1, _(u"Activate Sapi5 when any other screen reader is not being run"))
|
||||||
|
self.disable_sapi5.SetValue(config.main["general"]["voice_enabled"])
|
||||||
|
sizer.Add(self.disable_sapi5, 0, wx.ALL, 5)
|
||||||
|
self.show_gui = wx.CheckBox(self, -1, _(u"Activate the auto-start of the invisible interface"))
|
||||||
|
self.show_gui.SetValue(config.main["general"]["hide_gui"])
|
||||||
|
sizer.Add(self.show_gui, 0, wx.ALL, 5)
|
||||||
|
apiCallsBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
apiCallsBox.Add(wx.StaticText(self, -1, _(u"API calls when the stream is started (One API call equals to 200 tweetts, two API calls equals 400 tweets, etc):")), 0, wx.ALL, 5)
|
||||||
|
self.apiCalls = wx.SpinCtrl(self, -1)
|
||||||
|
self.apiCalls.SetRange(1, 10)
|
||||||
|
self.apiCalls.SetValue(config.main["general"]["max_api_calls"])
|
||||||
|
self.apiCalls.SetSize(self.apiCalls.GetBestSize())
|
||||||
|
apiCallsBox.Add(self.apiCalls, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(apiCallsBox, 0, wx.ALL, 5)
|
||||||
|
tweetsPerCallBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
tweetsPerCallBox.Add(wx.StaticText(self, -1, _(u"Items on each API call")), 0, wx.ALL, 5)
|
||||||
|
self.itemsPerApiCall = wx.SpinCtrl(self, -1)
|
||||||
|
self.itemsPerApiCall.SetRange(0, 200)
|
||||||
|
self.itemsPerApiCall.SetValue(config.main["general"]["max_tweets_per_call"])
|
||||||
|
self.itemsPerApiCall.SetSize(self.itemsPerApiCall.GetBestSize())
|
||||||
|
tweetsPerCallBox.Add(self.itemsPerApiCall, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(tweetsPerCallBox, 0, wx.ALL, 5)
|
||||||
|
self.reverse_timelines = wx.CheckBox(self, -1, _(u"Inverted buffers: The newest tweets will be shown at the beginning of the lists while the oldest at the end"))
|
||||||
|
self.reverse_timelines.SetValue(config.main["general"]["reverse_timelines"])
|
||||||
|
sizer.Add(self.reverse_timelines, 0, wx.ALL, 5)
|
||||||
|
self.SetSizer(sizer)
|
||||||
|
|
||||||
|
class other_buffers(wx.Panel):
|
||||||
|
def __init__(self, parent):
|
||||||
|
wx.Panel.__init__(self, parent)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
self.followers_value = config.main["other_buffers"]["show_followers"]
|
||||||
|
self.friends_value = config.main["other_buffers"]["show_friends"]
|
||||||
|
self.favs_value = config.main["other_buffers"]["show_favourites"]
|
||||||
|
self.events_value = config.main["other_buffers"]["show_events"]
|
||||||
|
self.blocks_value = config.main["other_buffers"]["show_blocks"]
|
||||||
|
self.mutes_value = config.main["other_buffers"]["show_muted_users"]
|
||||||
|
self.followers = wx.CheckBox(self, -1, _(u"Show followers"))
|
||||||
|
self.followers.SetValue(config.main["other_buffers"]["show_followers"])
|
||||||
|
sizer.Add(self.followers, 0, wx.ALL, 5)
|
||||||
|
self.friends = wx.CheckBox(self, -1, _(u"Show friends"))
|
||||||
|
self.friends.SetValue(config.main["other_buffers"]["show_friends"])
|
||||||
|
sizer.Add(self.friends, 0, wx.ALL, 5)
|
||||||
|
self.favs = wx.CheckBox(self, -1, _(u"Show favourites"))
|
||||||
|
self.favs.SetValue(config.main["other_buffers"]["show_favourites"])
|
||||||
|
sizer.Add(self.favs, 0, wx.ALL, 5)
|
||||||
|
self.blocks = wx.CheckBox(self, -1, _(u"Show blocked users"))
|
||||||
|
self.blocks.SetValue(config.main["other_buffers"]["show_blocks"])
|
||||||
|
sizer.Add(self.blocks, 0, wx.ALL, 5)
|
||||||
|
self.mutes = wx.CheckBox(self, -1, _(u"Show muted users"))
|
||||||
|
self.mutes.SetValue(config.main["other_buffers"]["show_muted_users"])
|
||||||
|
sizer.Add(self.mutes, 0, wx.ALL, 5)
|
||||||
|
self.events = wx.CheckBox(self, -1, _(u"Show events"))
|
||||||
|
self.events.SetValue(config.main["other_buffers"]["show_events"])
|
||||||
|
sizer.Add(self.events, 0, wx.ALL, 5)
|
||||||
|
self.SetSizer(sizer)
|
||||||
|
|
||||||
|
class ignoredClients(wx.Panel):
|
||||||
|
def __init__(self, parent):
|
||||||
|
super(ignoredClients, self).__init__(parent=parent)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
choices = config.main["twitter"]["ignored_clients"]
|
||||||
|
label = wx.StaticText(self, -1, _(u"Ignored clients"))
|
||||||
|
self.clients = wx.ListBox(self, -1, choices=choices)
|
||||||
|
self.clients.SetSize(self.clients.GetBestSize())
|
||||||
|
clientsBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
clientsBox.Add(label, 0, wx.ALL, 5)
|
||||||
|
clientsBox.Add(self.clients, 0, wx.ALL, 5)
|
||||||
|
add = wx.Button(self, -1, _(u"Add client"))
|
||||||
|
remove = wx.Button(self, -1, _(u"Remove client"))
|
||||||
|
self.Bind(wx.EVT_BUTTON, self.add, add)
|
||||||
|
self.Bind(wx.EVT_BUTTON, self.remove, remove)
|
||||||
|
btnBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
btnBox.Add(add, 0, wx.ALL, 5)
|
||||||
|
btnBox.Add(remove, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(clientsBox, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(btnBox, 0, wx.ALL, 5)
|
||||||
|
self.SetSizer(sizer)
|
||||||
|
|
||||||
|
def add(self, ev):
|
||||||
|
entry = wx.TextEntryDialog(self, _(u"Enter the name of the client here"), _(u"Add a new ignored client"))
|
||||||
|
if entry.ShowModal() == wx.ID_OK:
|
||||||
|
client = entry.GetValue()
|
||||||
|
if client not in config.main["twitter"]["ignored_clients"]:
|
||||||
|
config.main["twitter"]["ignored_clients"].append(client)
|
||||||
|
self.clients.Append(client)
|
||||||
|
|
||||||
|
def remove(self, ev):
|
||||||
|
if self.clients.GetCount() == 0: return
|
||||||
|
id = self.clients.GetSelection()
|
||||||
|
config.main["twitter"]["ignored_clients"].pop(id)
|
||||||
|
self.clients.Delete(id)
|
||||||
|
|
||||||
|
class sound(wx.Panel):
|
||||||
|
def __init__(self, parent):
|
||||||
|
wx.Panel.__init__(self, parent)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
volume = wx.StaticText(self, -1, _(u"Volume"))
|
||||||
|
self.volumeCtrl = wx.Slider(self)
|
||||||
|
self.volumeCtrl.SetRange(0, 100)
|
||||||
|
self.volumeCtrl.SetValue(config.main["sound"]["volume"]*100)
|
||||||
|
self.volumeCtrl.SetSize(self.volumeCtrl.GetBestSize())
|
||||||
|
volumeBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
volumeBox.Add(volume, 0, wx.ALL, 5)
|
||||||
|
volumeBox.Add(self.volumeCtrl, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(volumeBox, 0, wx.ALL, 5)
|
||||||
|
self.global_mute = wx.CheckBox(self, -1, _(u"Global mute"))
|
||||||
|
self.global_mute.SetValue(config.main["sound"]["global_mute"])
|
||||||
|
sizer.Add(self.global_mute, 0, wx.ALL, 5)
|
||||||
|
self.output_devices = sound_lib.output.Output.get_device_names()
|
||||||
|
output_label = wx.StaticText(self, -1, _(u"Output device"))
|
||||||
|
self.output = wx.ComboBox(self, -1, choices=self.output_devices, style=wx.CB_READONLY)
|
||||||
|
self.output.SetValue(config.main["sound"]["output_device"])
|
||||||
|
self.output.SetSize(self.output.GetBestSize())
|
||||||
|
outputBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
outputBox.Add(output_label, 0, wx.ALL, 5)
|
||||||
|
outputBox.Add(self.output, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(outputBox, 0, wx.ALL, 5)
|
||||||
|
self.input_devices = sound_lib.input.Input.get_device_names()
|
||||||
|
input_label = wx.StaticText(self, -1, _(u"Input device"))
|
||||||
|
self.input = wx.ComboBox(self, -1, choices=self.input_devices, style=wx.CB_READONLY)
|
||||||
|
self.input.SetValue(config.main["sound"]["input_device"])
|
||||||
|
self.input.SetSize(self.input.GetBestSize())
|
||||||
|
inputBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
inputBox.Add(input_label, 0, wx.ALL, 5)
|
||||||
|
inputBox.Add(self.input, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(inputBox, 0, wx.ALL, 5)
|
||||||
|
soundBox = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
self.soundpacks = []
|
||||||
|
[self.soundpacks.append(i) for i in os.listdir(paths.sound_path()) if os.path.isdir(paths.sound_path(i)) == True ]
|
||||||
|
soundpack_label = wx.StaticText(self, -1, _(u"Sound pack"))
|
||||||
|
self.soundpack = wx.ComboBox(self, -1, choices=self.soundpacks, style=wx.CB_READONLY)
|
||||||
|
self.soundpack.SetValue(config.main["sound"]["current_soundpack"])
|
||||||
|
self.soundpack.SetSize(self.soundpack.GetBestSize())
|
||||||
|
soundBox.Add(soundpack_label, 0, wx.ALL, 5)
|
||||||
|
soundBox.Add(self.soundpack, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(soundBox, 0, wx.ALL, 5)
|
||||||
|
self.SetSizer(sizer)
|
||||||
|
|
||||||
|
class audioServicesPanel(wx.Panel):
|
||||||
|
def __init__(self, parent):
|
||||||
|
super(audioServicesPanel, self).__init__(parent)
|
||||||
|
mainSizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
apiKeyLabel = wx.StaticText(self, -1, _(u"If you've got a SndUp account, enter your API Key here. Whether the API Key is wrong, the App will fail to upload anything to the server. Whether there's no API Key here, then the audio files will be uploaded anonimously"))
|
||||||
|
self.apiKey = wx.TextCtrl(self, -1)
|
||||||
|
self.apiKey.SetValue(config.main["sound"]["sndup_api_key"])
|
||||||
|
dc = wx.WindowDC(self.apiKey)
|
||||||
|
dc.SetFont(self.apiKey.GetFont())
|
||||||
|
self.apiKey.SetSize(dc.GetTextExtent("0"*100))
|
||||||
|
apiKeyBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
apiKeyBox.Add(apiKeyLabel, 0, wx.ALL, 5)
|
||||||
|
apiKeyBox.Add(self.apiKey, 0, wx.ALL, 5)
|
||||||
|
mainSizer.Add(apiKeyBox, 0, wx.ALL, 5)
|
||||||
|
first_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
self.dropbox = wx.Button(self, -1)
|
||||||
|
if len(config.main["services"]["dropbox_token"]) > 0:
|
||||||
|
self.dropbox.SetLabel(_(u"Unlink your Dropbox account"))
|
||||||
|
else:
|
||||||
|
self.dropbox.SetLabel(_(u"Link your Dropbox account"))
|
||||||
|
self.dropbox.Bind(wx.EVT_BUTTON, self.onLink_unlink)
|
||||||
|
first_sizer.Add(self.dropbox, 0, wx.ALL, 5)
|
||||||
|
mainSizer.Add(first_sizer, 0, wx.ALL, 5)
|
||||||
|
self.SetSizer(mainSizer)
|
||||||
|
|
||||||
|
def setup_dropbox(self):
|
||||||
|
from extra.AudioUploader import dropbox_transfer
|
||||||
|
auth = dropbox_transfer.dropboxLogin()
|
||||||
|
url = auth.get_url()
|
||||||
|
wx.MessageDialog(self, _(u"The authorisation request will be shown on your browser. Copy the code tat Dropbox will provide and, in the text box that will appear on TW Blue, paste it. This code is necessary to continue. You only need to do it once."), _(u"Authorisation"), wx.OK).ShowModal()
|
||||||
|
webbrowser.open(url)
|
||||||
|
dlg = wx.TextEntryDialog(self, _(u"Enter the code here."), _(u"Verification code"))
|
||||||
|
if dlg.ShowModal() == wx.ID_CANCEL:
|
||||||
|
return False
|
||||||
|
resp = dlg.GetValue()
|
||||||
|
if resp == "":
|
||||||
|
self.dropbox.SetLabel(_(u"Link your Dropbox account"))
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
auth.authorise(resp)
|
||||||
|
self.dropbox.SetLabel(_(u"Unlink your Dropbox account"))
|
||||||
|
except:
|
||||||
|
wx.MessageDialog(self, _(u"Error during authorisation. Try again later."), _(u"Error!"), wx.ICON_ERROR).ShowModal()
|
||||||
|
self.dropbox.SetLabel(_(u"Link your Dropbox account"))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def onLink_unlink(self, ev):
|
||||||
|
if self.dropbox.GetLabel() == _(u"Link your Dropbox account"):
|
||||||
|
self.setup_dropbox()
|
||||||
|
else:
|
||||||
|
self.disconnect_dropbox()
|
||||||
|
|
||||||
|
def disconnect_dropbox(self):
|
||||||
|
config.main["services"]["dropbox_token"] = ""
|
||||||
|
self.dropbox.SetLabel(_(u"Link your Dropbox account"))
|
||||||
|
|
||||||
|
class configurationDialog(wx.Dialog):
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.parent = parent
|
||||||
|
wx.Dialog.__init__(self, None, -1)
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
self.SetTitle(_(u"TW Blue preferences"))
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
notebook = wx.Notebook(panel)
|
||||||
|
self.general = general(notebook)
|
||||||
|
notebook.AddPage(self.general, _(u"General"))
|
||||||
|
self.general.SetFocus()
|
||||||
|
self.buffers = other_buffers(notebook)
|
||||||
|
notebook.AddPage(self.buffers, _(u"Show other buffers"))
|
||||||
|
self.ignored_clients = ignoredClients(notebook)
|
||||||
|
notebook.AddPage(self.ignored_clients, _(u"Ignored clients"))
|
||||||
|
self.sound = sound(notebook)
|
||||||
|
notebook.AddPage(self.sound, _(u"Sound"))
|
||||||
|
self.services = audioServicesPanel(notebook)
|
||||||
|
notebook.AddPage(self.services, _(u"Audio Services"))
|
||||||
|
sizer.Add(notebook, 0, wx.ALL, 5)
|
||||||
|
ok_cancel_box = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
ok = wx.Button(panel, wx.ID_OK, _(u"Save"))
|
||||||
|
ok.Bind(wx.EVT_BUTTON, self.onSave)
|
||||||
|
ok.SetDefault()
|
||||||
|
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Close"))
|
||||||
|
self.SetEscapeId(cancel.GetId())
|
||||||
|
ok_cancel_box.Add(ok, 0, wx.ALL, 5)
|
||||||
|
ok_cancel_box.Add(cancel, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(ok_cancel_box, 0, wx.ALL, 5)
|
||||||
|
panel.SetSizer(sizer)
|
||||||
|
self.SetClientSize(sizer.CalcMin())
|
||||||
|
|
||||||
|
def check_followers_change(self):
|
||||||
|
if self.buffers.followers.GetValue() != self.buffers.followers_value:
|
||||||
|
if self.buffers.followers.GetValue() == True:
|
||||||
|
log.debug("Creating followers list...")
|
||||||
|
followers = buffers.peoplePanel(self.parent.nb, self.parent, "followers", self.parent.twitter.twitter.get_followers_list, argumento=self.parent.db.settings["user_name"])
|
||||||
|
self.parent.nb.InsertSubPage(self.parent.db.settings["buffers"].index(self.parent.db.settings["user_name"]), followers, _(u"Followers"))
|
||||||
|
num = followers.start_streams()
|
||||||
|
followers.put_items(num)
|
||||||
|
self.parent.db.settings["buffers"].append("followers")
|
||||||
|
elif self.buffers.followers.GetValue() == False:
|
||||||
|
self.parent.nb.DeletePage(self.parent.db.settings["buffers"].index("followers"))
|
||||||
|
self.parent.db.settings.pop("followers")
|
||||||
|
self.parent.db.settings["buffers"].remove("followers")
|
||||||
|
|
||||||
|
def check_friends_change(self):
|
||||||
|
if self.buffers.friends.GetValue() != self.buffers.friends_value:
|
||||||
|
if self.buffers.friends.GetValue() == True:
|
||||||
|
log.debug("Creating friends list...")
|
||||||
|
friends = buffers.peoplePanel(self.parent.nb, self.parent, "friends", self.parent.twitter.twitter.get_friends_list, argumento=self.parent.db.settings["user_name"])
|
||||||
|
self.parent.nb.InsertSubPage(self.parent.db.settings["buffers"].index(self.parent.db.settings["user_name"]), friends, _(u"friends"))
|
||||||
|
num = friends.start_streams()
|
||||||
|
friends.put_items(num)
|
||||||
|
self.parent.db.settings["buffers"].append("friends")
|
||||||
|
elif self.buffers.friends.GetValue() == False:
|
||||||
|
self.parent.nb.DeletePage(self.parent.db.settings["buffers"].index("friends"))
|
||||||
|
self.parent.db.settings.pop("friends")
|
||||||
|
self.parent.db.settings["buffers"].remove("friends")
|
||||||
|
|
||||||
|
def check_favs_change(self):
|
||||||
|
if self.buffers.favs.GetValue() != self.buffers.favs_value:
|
||||||
|
if self.buffers.favs.GetValue() == True:
|
||||||
|
log.debug("Creating favorites...")
|
||||||
|
favs = buffers.basePanel(self.parent.nb, self.parent, "favs", self.parent.twitter.twitter.get_favorites)
|
||||||
|
self.parent.nb.InsertSubPage(self.parent.db.settings["buffers"].index(self.parent.db.settings["user_name"]), favs, _(u"Favorites"))
|
||||||
|
num = favs.start_streams()
|
||||||
|
favs.put_items(num)
|
||||||
|
self.parent.db.settings["buffers"].append("favs")
|
||||||
|
elif self.buffers.favs.GetValue() == False:
|
||||||
|
self.parent.nb.DeletePage(self.parent.db.settings["buffers"].index("favs"))
|
||||||
|
self.parent.db.settings.pop("favs")
|
||||||
|
self.parent.db.settings["buffers"].remove("favs")
|
||||||
|
|
||||||
|
def check_events_change(self):
|
||||||
|
if self.buffers.events.GetValue() != self.buffers.events_value:
|
||||||
|
if self.buffers.events.GetValue() == True:
|
||||||
|
log.debug("Creating events...")
|
||||||
|
events = buffers.eventsPanel(self.parent.nb, self.parent)
|
||||||
|
self.parent.nb.InsertSubPage(self.parent.db.settings["buffers"].index(self.parent.db.settings["user_name"]), events, _(u"Events"))
|
||||||
|
self.parent.db.settings["buffers"].append("events")
|
||||||
|
elif self.buffers.events.GetValue() == False:
|
||||||
|
self.parent.nb.DeletePage(self.parent.db.settings["buffers"].index("events"))
|
||||||
|
self.parent.db.settings["buffers"].remove("events")
|
||||||
|
|
||||||
|
def check_blocks_change(self):
|
||||||
|
if self.buffers.blocks.GetValue() != self.buffers.blocks_value:
|
||||||
|
if self.buffers.blocks.GetValue() == True:
|
||||||
|
log.debug("Creating blocked users list...")
|
||||||
|
blocks = buffers.peoplePanel(self.parent.nb, self.parent, "blocks", self.parent.twitter.twitter.list_blocks)
|
||||||
|
self.parent.nb.InsertSubPage(self.parent.db.settings["buffers"].index(self.parent.db.settings["user_name"]), blocks, _(u"Blocked users"))
|
||||||
|
num = blocks.start_streams()
|
||||||
|
blocks.put_items(num)
|
||||||
|
self.parent.db.settings["buffers"].append("blocks")
|
||||||
|
elif self.buffers.blocks.GetValue() == False:
|
||||||
|
self.parent.nb.DeletePage(self.parent.db.settings["buffers"].index("blocks"))
|
||||||
|
self.parent.db.settings.pop("blocks")
|
||||||
|
self.parent.db.settings["buffers"].remove("blocks")
|
||||||
|
|
||||||
|
def check_mutes_change(self):
|
||||||
|
if self.buffers.mutes.GetValue() != self.buffers.mutes_value:
|
||||||
|
if self.buffers.mutes.GetValue() == True:
|
||||||
|
log.debug("Creating muted users list...")
|
||||||
|
mutes = buffers.peoplePanel(self.parent.nb, self.parent, "muteds", self.parent.twitter.twitter.get_muted_users_list)
|
||||||
|
self.parent.nb.InsertSubPage(self.parent.db.settings["buffers"].index(self.parent.db.settings["user_name"]), mutes, _(u"Muted users"))
|
||||||
|
num = mutes.start_streams()
|
||||||
|
mutes.put_items(num)
|
||||||
|
self.parent.db.settings["buffers"].append("muteds")
|
||||||
|
elif self.buffers.mutes.GetValue() == False:
|
||||||
|
self.parent.nb.DeletePage(self.parent.db.settings["buffers"].index("muteds"))
|
||||||
|
self.parent.db.settings.pop("muteds")
|
||||||
|
self.parent.db.settings["buffers"].remove("muteds")
|
||||||
|
|
||||||
|
def onSave(self, ev):
|
||||||
|
need_restart = False
|
||||||
|
# Check general settings
|
||||||
|
if config.main["general"]["language"] != self.general.langs[self.general.language.GetSelection()][0]:
|
||||||
|
if self.general.langs[self.general.language.GetSelection()][0] in self.general.codes: config.main["general"]["language"] = self.general.langs[self.general.language.GetSelection()][0]
|
||||||
|
languageHandler.setLanguage(config.main["general"]["language"])
|
||||||
|
need_restart = True
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
config.main["general"]["voice_enabled"] = self.general.disable_sapi5.GetValue()
|
||||||
|
config.main["general"]["hide_gui"] = self.general.show_gui.GetValue()
|
||||||
|
config.main["general"]["max_api_calls"] = self.general.apiCalls.GetValue()
|
||||||
|
config.main["general"]["max_tweets_per_call"] = self.general.itemsPerApiCall.GetValue()
|
||||||
|
if config.main["general"]["relative_times"] != self.general.relative_time.GetValue():
|
||||||
|
config.main["general"]["relative_times"] = self.general.relative_time.GetValue()
|
||||||
|
need_restart = True
|
||||||
|
if config.main["general"]["reverse_timelines"] != self.general.reverse_timelines.GetValue():
|
||||||
|
config.main["general"]["reverse_timelines"] = self.general.reverse_timelines.GetValue()
|
||||||
|
need_restart = True
|
||||||
|
|
||||||
|
## Check buffers settings
|
||||||
|
config.main["other_buffers"]["show_followers"] = self.buffers.followers.GetValue()
|
||||||
|
self.check_followers_change()
|
||||||
|
config.main["other_buffers"]["show_friends"] = self.buffers.friends.GetValue()
|
||||||
|
self.check_friends_change()
|
||||||
|
config.main["other_buffers"]["show_favourites"] = self.buffers.favs.GetValue()
|
||||||
|
self.check_favs_change()
|
||||||
|
config.main["other_buffers"]["show_events"] = self.buffers.events.GetValue()
|
||||||
|
self.check_events_change()
|
||||||
|
config.main["other_buffers"]["show_blocks"] = self.buffers.blocks.GetValue()
|
||||||
|
self.check_blocks_change()
|
||||||
|
config.main["other_buffers"]["show_muted_users"] = self.buffers.mutes.GetValue()
|
||||||
|
self.check_mutes_change()
|
||||||
|
|
||||||
|
## Check sound settings
|
||||||
|
config.main["sound"]["volume"] = self.sound.volumeCtrl.GetValue()/100.0
|
||||||
|
config.main["sound"]["global_mute"] = self.sound.global_mute.GetValue()
|
||||||
|
if system == "Windows":
|
||||||
|
config.main["sound"]["output_device"] = self.sound.output.GetStringSelection()
|
||||||
|
config.main["sound"]["input_device"] = self.sound.input.GetValue()
|
||||||
|
try:
|
||||||
|
snd.player.input.set_device(snd.player.input.find_device_by_name(config.main["sound"]["input_device"]))
|
||||||
|
snd.player.output.set_device(snd.player.output.find_device_by_name(config.main["sound"]["output_device"]))
|
||||||
|
except:
|
||||||
|
config.main["sound"]["output_device"] = "Default"
|
||||||
|
config.main["sound"]["input_device"] = "Default"
|
||||||
|
config.main["sound"]["sndup_api_key"] = self.services.apiKey.GetValue()
|
||||||
|
config.main["sound"]["current_soundpack"] = self.sound.soundpack.GetStringSelection()
|
||||||
|
snd.player.check_soundpack()
|
||||||
|
if need_restart == True:
|
||||||
|
config.main.write()
|
||||||
|
wx.MessageDialog(None, _(u"The application requires to be restarted to save these changes. Press OK to do it now."), _("Restart TW Blue"), wx.OK).ShowModal()
|
||||||
|
restart.restart_program()
|
||||||
|
|
||||||
|
config.main.write()
|
||||||
|
self.EndModal(wx.ID_OK)
|
151
src/gui/dialogs/follow.py
Normal file
151
src/gui/dialogs/follow.py
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import config
|
||||||
|
from mysc import event
|
||||||
|
import twitter
|
||||||
|
from twitter import utils
|
||||||
|
from twython import TwythonError
|
||||||
|
import output
|
||||||
|
|
||||||
|
class follow(wx.Dialog):
|
||||||
|
def __init__(self, parent, default="follow"):
|
||||||
|
self.parent = parent
|
||||||
|
wx.Dialog.__init__(self, None, -1)
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
userSizer = wx.BoxSizer()
|
||||||
|
self.SetTitle(_(u"Action"))
|
||||||
|
if self.parent.name_buffer == "followers" or self.parent.name_buffer == "friends":
|
||||||
|
list = [self.parent.db.settings[self.parent.name_buffer][self.parent.list.get_selected()]["screen_name"]]
|
||||||
|
else:
|
||||||
|
try: list =twitter.utils.get_all_users(self.parent.db.settings[self.parent.name_buffer][self.parent.list.get_selected()], self.parent.db)
|
||||||
|
except KeyError: list = [self.parent.db.settings[self.parent.name_buffer][self.parent.list.get_selected()]["screen_name"]]
|
||||||
|
self.cb = wx.ComboBox(panel, -1, choices=list, value=list[0])
|
||||||
|
self.cb.SetFocus()
|
||||||
|
userSizer.Add(self.cb)
|
||||||
|
actionSizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
label2 = wx.StaticText(panel, -1, _(u"Action"))
|
||||||
|
self.follow = wx.RadioButton(panel, -1, _(u"Follow"), style=wx.RB_GROUP)
|
||||||
|
self.unfollow = wx.RadioButton(panel, -1, _(u"Unfollow"))
|
||||||
|
self.mute = wx.RadioButton(panel, -1, _(u"Mute"))
|
||||||
|
self.unmute = wx.RadioButton(panel, -1, _(u"Unmute"))
|
||||||
|
self.block = wx.RadioButton(panel, -1, _(u"Block"))
|
||||||
|
self.unblock = wx.RadioButton(panel, -1, _(u"Unblock"))
|
||||||
|
self.reportSpam = wx.RadioButton(panel, -1, _(u"Report as spam"))
|
||||||
|
self.setup_default(default)
|
||||||
|
actionSizer.Add(label2)
|
||||||
|
actionSizer.Add(self.follow)
|
||||||
|
actionSizer.Add(self.unfollow)
|
||||||
|
actionSizer.Add(self.mute)
|
||||||
|
actionSizer.Add(self.unmute)
|
||||||
|
actionSizer.Add(self.block)
|
||||||
|
actionSizer.Add(self.unblock)
|
||||||
|
actionSizer.Add(self.reportSpam)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
ok = wx.Button(panel, wx.ID_OK, _(u"OK"))
|
||||||
|
ok.Bind(wx.EVT_BUTTON, self.onok)
|
||||||
|
ok.SetDefault()
|
||||||
|
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Close"))
|
||||||
|
btnsizer = wx.BoxSizer()
|
||||||
|
btnsizer.Add(ok)
|
||||||
|
btnsizer.Add(cancel)
|
||||||
|
sizer.Add(userSizer)
|
||||||
|
sizer.Add(actionSizer)
|
||||||
|
sizer.Add(btnsizer)
|
||||||
|
panel.SetSizer(sizer)
|
||||||
|
self.Bind(wx.EVT_CHAR_HOOK, self.onEscape, self.cb)
|
||||||
|
|
||||||
|
def onEscape(self, ev):
|
||||||
|
if ev.GetKeyCode() == wx.WXK_RETURN:
|
||||||
|
self.onok(wx.EVT_BUTTON)
|
||||||
|
ev.Skip()
|
||||||
|
|
||||||
|
def onok(self, ev):
|
||||||
|
if self.follow.GetValue() == True:
|
||||||
|
try:
|
||||||
|
self.parent.twitter.twitter.create_friendship(screen_name=self.cb.GetValue())
|
||||||
|
self.Destroy()
|
||||||
|
except TwythonError as err:
|
||||||
|
output.speak("Error %s: %s" % (err.error_code, err.msg), True)
|
||||||
|
elif self.unfollow.GetValue() == True:
|
||||||
|
try:
|
||||||
|
id = self.parent.twitter.twitter.destroy_friendship(screen_name=self.cb.GetValue())
|
||||||
|
self.Destroy()
|
||||||
|
except TwythonError as err:
|
||||||
|
output.speak("Error %s: %s" % (err.error_code, err.msg), True)
|
||||||
|
elif self.mute.GetValue() == True:
|
||||||
|
try:
|
||||||
|
id = self.parent.twitter.twitter.create_mute(screen_name=self.cb.GetValue())
|
||||||
|
if config.main["other_buffers"]["show_muted_users"] == True:
|
||||||
|
tweet_event = event.event(event.EVT_OBJECT, 1)
|
||||||
|
tweet_event.SetItem(id)
|
||||||
|
wx.PostEvent(self.parent.parent.nb.GetPage(self.parent.db.settings["buffers"].index("muteds")), tweet_event)
|
||||||
|
self.parent.db.settings["muted_users"].append(id["id"])
|
||||||
|
self.Destroy()
|
||||||
|
output.speak(_(u"You've muted to %s") % (id["screen_name"]))
|
||||||
|
except TwythonError as err:
|
||||||
|
output.speak("Error %s: %s" % (err.error_code, err.msg), True)
|
||||||
|
elif self.unmute.GetValue() == True:
|
||||||
|
try:
|
||||||
|
id = self.parent.twitter.twitter.destroy_mute(screen_name=self.cb.GetValue())
|
||||||
|
if config.main["other_buffers"]["show_muted_users"] == True:
|
||||||
|
item = utils.find_item(id, self.parent.db.settings["muteds"])
|
||||||
|
if item > 0:
|
||||||
|
deleted_event = event.event(event.EVT_DELETED, 1)
|
||||||
|
deleted_event.SetItem(item)
|
||||||
|
wx.PostEvent(self.parent.parent.nb.GetPage(self.parent.db.settings["buffers"].index("muteds")), deleted_event)
|
||||||
|
if id["id"] in self.parent.db.settings["muted_users"]: self.parent.db.settings["muted_users"].remove(id["id"])
|
||||||
|
self.Destroy()
|
||||||
|
output.speak(_(u"You've unmuted to %s") % (id["screen_name"]))
|
||||||
|
except TwythonError as err:
|
||||||
|
output.speak("Error %s: %s" % (err.error_code, err.msg), True)
|
||||||
|
elif self.reportSpam.GetValue() == True:
|
||||||
|
try:
|
||||||
|
self.parent.twitter.twitter.report_spam(screen_name=self.cb.GetValue())
|
||||||
|
self.Destroy()
|
||||||
|
except TwythonError as err:
|
||||||
|
output.speak("Error %s: %s" % (err.error_code, err.msg), True)
|
||||||
|
elif self.block.GetValue() == True:
|
||||||
|
try:
|
||||||
|
self.parent.twitter.twitter.create_block(screen_name=self.cb.GetValue())
|
||||||
|
self.Destroy()
|
||||||
|
except TwythonError as err:
|
||||||
|
output.speak("Error %s: %s" % (err.error_code, err.msg), True)
|
||||||
|
elif self.unblock.GetValue() == True:
|
||||||
|
try:
|
||||||
|
self.parent.twitter.twitter.destroy_block(screen_name=self.cb.GetValue())
|
||||||
|
self.Destroy()
|
||||||
|
except TwythonError as err:
|
||||||
|
output.speak("Error %s: %s" % (err.error_code, err.msg), True)
|
||||||
|
|
||||||
|
def setup_default(self, default):
|
||||||
|
if default == "follow":
|
||||||
|
self.follow.SetValue(True)
|
||||||
|
elif default == "unfollow":
|
||||||
|
self.unfollow.SetValue(True)
|
||||||
|
elif default == "mute":
|
||||||
|
self.mute.SetValue(True)
|
||||||
|
elif default == "unmute":
|
||||||
|
self.unmute.SetValue(True)
|
||||||
|
elif default == "report":
|
||||||
|
self.reportSpam.SetValue(True)
|
||||||
|
elif default == "block":
|
||||||
|
self.block.SetValue(True)
|
||||||
|
elif default == "unblock":
|
||||||
|
self.unblock.SetValue(True)
|
244
src/gui/dialogs/lists.py
Normal file
244
src/gui/dialogs/lists.py
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import platform
|
||||||
|
import output
|
||||||
|
import config
|
||||||
|
import gui
|
||||||
|
from multiplatform_widgets import widgets
|
||||||
|
from twython import TwythonError
|
||||||
|
from twitter import compose, utils
|
||||||
|
|
||||||
|
class listViewer(wx.Dialog):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.twitter = parent.twitter
|
||||||
|
self.db = parent.db
|
||||||
|
self.nb = parent.nb
|
||||||
|
self.parent = parent
|
||||||
|
wx.Dialog.__init__(self, None)
|
||||||
|
self.SetTitle(_(u"Lists manager"))
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
label = wx.StaticText(panel, -1, _(u"Lists"))
|
||||||
|
self.lista = widgets.list(panel, _(u"List"), _(u"Description"), _(u"Owner"), _(u"Members"), _(u"mode"), size=(800, 800), style=wx.LC_REPORT|wx.LC_SINGLE_SEL)
|
||||||
|
self.lista.list.SetFocus()
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
sizer.Add(label)
|
||||||
|
sizer.Add(self.lista.list)
|
||||||
|
self.createBtn = wx.Button(panel, wx.NewId(), _(u"Create a new list"))
|
||||||
|
self.createBtn.Bind(wx.EVT_BUTTON, self.onGo)
|
||||||
|
self.editBtn = wx.Button(panel, -1, _(u"Edit"))
|
||||||
|
self.Bind(wx.EVT_BUTTON, self.onEdit, self.editBtn)
|
||||||
|
self.deleteBtn = wx.Button(panel, -1, _(u"Remove"))
|
||||||
|
self.Bind(wx.EVT_BUTTON, self.onDelete, self.deleteBtn)
|
||||||
|
self.view = wx.Button(panel, -1, _(u"Open in buffer"))
|
||||||
|
self.Bind(wx.EVT_BUTTON, self.onView, self.view)
|
||||||
|
# self.members = wx.Button(panel, -1, _(u"View members"))
|
||||||
|
# self.members.Disable()
|
||||||
|
# self.subscriptors = wx.Button(panel, -1, _(u"View subscribers"))
|
||||||
|
# self.subscriptors.Disable()
|
||||||
|
# self.get_linkBtn = wx.Button(panel, -1, _(u"Get link for the list"))
|
||||||
|
# self.get_linkBtn.Bind(wx.EVT_BUTTON, self.onGetLink)
|
||||||
|
self.cancelBtn = wx.Button(panel, wx.ID_CANCEL)
|
||||||
|
btnSizer = wx.BoxSizer()
|
||||||
|
btnSizer.Add(self.createBtn)
|
||||||
|
btnSizer.Add(self.editBtn)
|
||||||
|
btnSizer.Add(self.cancelBtn)
|
||||||
|
panel.SetSizer(sizer)
|
||||||
|
self.populate_list()
|
||||||
|
self.lista.select_item(0)
|
||||||
|
|
||||||
|
def onGo(self, ev):
|
||||||
|
ev.Skip()
|
||||||
|
dlg = createListDialog()
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
name = dlg.name.GetValue()
|
||||||
|
description = dlg.description.GetValue()
|
||||||
|
if dlg.public.GetValue() == True: mode = "public"
|
||||||
|
else: mode = "private"
|
||||||
|
try:
|
||||||
|
new_list = self.twitter.twitter.create_list(name=name, description=description, mode=mode)
|
||||||
|
self.db.settings["lists"].append(new_list)
|
||||||
|
self.lista.insert_item(False, *compose.compose_list(new_list))
|
||||||
|
except TwythonError as e:
|
||||||
|
output.speak("error %s: %s" % (e.status_code, e.msg))
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def onEdit(self, ev):
|
||||||
|
ev.Skip()
|
||||||
|
if self.lista.get_count() == 0: return
|
||||||
|
list = self.db.settings["lists"][self.lista.get_selected()]
|
||||||
|
dlg = editListDialog(list)
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
name = dlg.name.GetValue()
|
||||||
|
description = dlg.description.GetValue()
|
||||||
|
if dlg.public.GetValue() == True: mode = "public"
|
||||||
|
else: mode = "private"
|
||||||
|
try:
|
||||||
|
self.twitter.twitter.update_list(list_id=self.lists[self.get_selected()]["id"], name=name, description=description, mode=mode)
|
||||||
|
except TwythonError as e:
|
||||||
|
output.speak("error %s: %s" % (e.error_code, e.msg))
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def onDelete(self, ev):
|
||||||
|
ev.Skip()
|
||||||
|
if self.lista.get_count() == 0: return
|
||||||
|
list = self.db.settings["lists"][self.lista.get_selected()]["id"]
|
||||||
|
dlg = wx.MessageDialog(self, _("Do you really want to delete this list?"), _("Delete"), wx.YES_NO)
|
||||||
|
if dlg.ShowModal() == wx.ID_YES:
|
||||||
|
try:
|
||||||
|
self.twitter.twitter.delete_list(list_id=list)
|
||||||
|
self.db.settings["lists"].pop(self.lista.get_selected())
|
||||||
|
self.remove_item(self.lista.get_selected())
|
||||||
|
except TwythonError as e:
|
||||||
|
output.speak("error %s: %s" % (e.error_code, e.msg))
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def onView(self, ev):
|
||||||
|
ev.Skip()
|
||||||
|
if self.lista.get_count() == 0: return
|
||||||
|
list_id = self.db.settings["lists"][self.lista.get_selected()]["id"]
|
||||||
|
list_updated = self.twitter.twitter.get_specific_list(list_id=list_id)
|
||||||
|
self.db.settings["lists"][self.lista.get_selected()] = list_updated
|
||||||
|
if list_updated["slug"] not in config.main["other_buffers"]["lists"]:
|
||||||
|
config.main["other_buffers"]["lists"].append(list_updated["slug"])
|
||||||
|
output.speak(_(u"List opened"))
|
||||||
|
else:
|
||||||
|
output.speak(_(u"This list is arready opened."))
|
||||||
|
return
|
||||||
|
listUI = gui.buffers.lists.listPanel(self.nb, self.parent, list_updated["slug"]+"-list", argumento=utils.find_list(list_updated["slug"], self.db.settings["lists"]))
|
||||||
|
self.nb.AddPage(listUI, _(u"List for %s") % (list_updated["slug"],))
|
||||||
|
self.db.settings["buffers"].append(list_updated["slug"]+"-list")
|
||||||
|
num = listUI.start_streams()
|
||||||
|
listUI.put_items(num)
|
||||||
|
listUI.sound = "tweet_timeline.wav"
|
||||||
|
self.parent.stream2.disconnect()
|
||||||
|
del self.parent.stream2
|
||||||
|
self.parent.get_tls()
|
||||||
|
|
||||||
|
def populate_list(self):
|
||||||
|
for i in self.db.settings["lists"]:
|
||||||
|
item = compose.compose_list(i)
|
||||||
|
self.lista.insert_item(False, *item)
|
||||||
|
|
||||||
|
class userListViewer(listViewer):
|
||||||
|
def __init__(self, parent, username):
|
||||||
|
self.username = username
|
||||||
|
super(userListViewer, self).__init__(parent)
|
||||||
|
self.SetTitle(_(u"Viewing lists for %s") % (self.username))
|
||||||
|
self.createBtn.SetLabel(_(u"Subscribe"))
|
||||||
|
self.deleteBtn.SetLabel(_(u"Unsubscribe"))
|
||||||
|
self.editBtn.Disable()
|
||||||
|
self.view.Disable()
|
||||||
|
|
||||||
|
def populate_list(self):
|
||||||
|
self.lists = self.twitter.twitter.show_owned_lists(screen_name=self.username, count=200)["lists"]
|
||||||
|
for i in self.lists:
|
||||||
|
item = compose.compose_list(i)
|
||||||
|
self.lista.insert_item(False, *item)
|
||||||
|
|
||||||
|
def onGo(self, ev):
|
||||||
|
list_id = self.lists[self.lista.get_selected()]["id"]
|
||||||
|
try:
|
||||||
|
list = self.twitter.twitter.subscribe_to_list(list_id=list_id)
|
||||||
|
item = utils.find_item(list["id"], self.db.settings["lists"])
|
||||||
|
self.db.settings["lists"].append(list)
|
||||||
|
except TwythonError as e:
|
||||||
|
output.speak("error %s: %s" % (e.status_code, e.msg))
|
||||||
|
|
||||||
|
class createListDialog(wx.Dialog):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
wx.Dialog.__init__(self, None, size=(450, 400))
|
||||||
|
self.SetTitle(_(u"Create a new list"))
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
name = wx.StaticText(panel, -1, _(u"Name (20 characters maximun)"))
|
||||||
|
self.name = wx.TextCtrl(panel, -1)
|
||||||
|
nameSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
nameSizer.Add(name)
|
||||||
|
nameSizer.Add(self.name)
|
||||||
|
description = wx.StaticText(panel, -1, _(u"Description"))
|
||||||
|
self.description = wx.TextCtrl(panel, -1)
|
||||||
|
descriptionSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
descriptionSizer.Add(description)
|
||||||
|
descriptionSizer.Add(self.description)
|
||||||
|
mode = wx.StaticText(panel, -1, _(u"Mode"))
|
||||||
|
self.public = wx.RadioButton(panel, -1, _(u"Public"), style=wx.RB_GROUP)
|
||||||
|
self.private = wx.RadioButton(panel, -1, _(u"Private"))
|
||||||
|
modeBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
modeBox.Add(mode)
|
||||||
|
modeBox.Add(self.public)
|
||||||
|
modeBox.Add(self.private)
|
||||||
|
ok = wx.Button(panel, wx.ID_OK)
|
||||||
|
ok.SetDefault()
|
||||||
|
cancel = wx.Button(panel, wx.ID_CANCEL)
|
||||||
|
btnBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
btnBox.Add(ok)
|
||||||
|
btnBox.Add(cancel)
|
||||||
|
sizer.Add(nameSizer)
|
||||||
|
sizer.Add(descriptionSizer)
|
||||||
|
sizer.Add(modeBox)
|
||||||
|
sizer.Add(btnBox)
|
||||||
|
|
||||||
|
class editListDialog(createListDialog):
|
||||||
|
|
||||||
|
def __init__(self, list):
|
||||||
|
createListDialog.__init__(self)
|
||||||
|
self.SetTitle(_(u"Editing the list %s") % (list["name"]))
|
||||||
|
self.name.ChangeValue(list["name"])
|
||||||
|
self.description.ChangeValue(list["description"])
|
||||||
|
if list["mode"] == "public":
|
||||||
|
self.public.SetValue(True)
|
||||||
|
else:
|
||||||
|
self.private.SetValue(True)
|
||||||
|
|
||||||
|
class addUserListDialog(listViewer):
|
||||||
|
def __init__(self, parent):
|
||||||
|
listViewer.__init__(self, parent)
|
||||||
|
self.SetTitle(_(u"Select a list to add the user"))
|
||||||
|
self.createBtn.SetLabel(_(u"Add"))
|
||||||
|
self.createBtn.SetDefault()
|
||||||
|
self.editBtn.Disable()
|
||||||
|
self.view.Disable()
|
||||||
|
# self.subscriptors.Disable()
|
||||||
|
# self.members.Disable()
|
||||||
|
self.deleteBtn.Disable()
|
||||||
|
|
||||||
|
def onGo(self, ev):
|
||||||
|
self.EndModal(wx.ID_OK)
|
||||||
|
|
||||||
|
class removeUserListDialog(listViewer):
|
||||||
|
def __init__(self, parent):
|
||||||
|
listViewer.__init__(self, parent)
|
||||||
|
self.SetTitle(_(u"Select a list to remove the user"))
|
||||||
|
self.createBtn.SetLabel(_(u"Remove"))
|
||||||
|
self.createBtn.SetDefault()
|
||||||
|
self.editBtn.Disable()
|
||||||
|
self.view.Disable()
|
||||||
|
# self.subscriptors.Disable()
|
||||||
|
# self.members.Disable()
|
||||||
|
self.deleteBtn.Disable()
|
||||||
|
|
||||||
|
def onGo(self, ev):
|
||||||
|
self.EndModal(wx.ID_OK)
|
415
src/gui/dialogs/message.py
Normal file
415
src/gui/dialogs/message.py
Normal file
@ -0,0 +1,415 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import twitter
|
||||||
|
import config
|
||||||
|
import output
|
||||||
|
import sound
|
||||||
|
import urlList
|
||||||
|
import url_shortener
|
||||||
|
import json
|
||||||
|
from mysc.thread_utils import call_threaded
|
||||||
|
from mysc.repeating_timer import RepeatingTimer
|
||||||
|
from twython import TwythonError
|
||||||
|
from extra import translator, AudioUploader
|
||||||
|
import platform
|
||||||
|
from extra.AudioUploader import transfer
|
||||||
|
if platform.system() != "Darwin":
|
||||||
|
from extra.AudioUploader import dropbox_transfer
|
||||||
|
from extra.SpellChecker import gui as spellCheckerGUI
|
||||||
|
|
||||||
|
class textLimited(wx.Dialog):
|
||||||
|
def __init__(self, message, title, text, parent):
|
||||||
|
wx.Dialog.__init__(self, parent)
|
||||||
|
self.twitter = parent.twitter
|
||||||
|
self.parent = parent
|
||||||
|
self.SetTitle(_(u"New tweet"))
|
||||||
|
self.panel = wx.Panel(self)
|
||||||
|
|
||||||
|
def createTextArea(self, message, text):
|
||||||
|
self.label = wx.StaticText(self.panel, -1, str(len(text)))
|
||||||
|
self.text = wx.TextCtrl(self.panel, -1, text)
|
||||||
|
font = self.text.GetFont()
|
||||||
|
dc = wx.WindowDC(self.text)
|
||||||
|
dc.SetFont(font)
|
||||||
|
x, y = dc.GetTextExtent("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||||
|
self.text.SetSize((x, y))
|
||||||
|
self.text.SetFocus()
|
||||||
|
if platform.system() != "Darwin":
|
||||||
|
self.text.Bind(wx.EVT_TEXT, self.onTimer)
|
||||||
|
self.textBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
self.textBox.Add(self.label, 0, wx.ALL, 5)
|
||||||
|
self.textBox.Add(self.text, 0, wx.ALL, 5)
|
||||||
|
|
||||||
|
def onCheck(self, ev):
|
||||||
|
if platform.system() == "Darwin":
|
||||||
|
return
|
||||||
|
text = self.text.GetValue()
|
||||||
|
dlg = spellCheckerGUI.spellCheckerDialog(text, "")
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
self.text.ChangeValue(dlg.checker.get_text())
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def onAttach(self, ev):
|
||||||
|
ev.Skip()
|
||||||
|
self.recording = AudioUploader.gui.audioDialog(self.parent)
|
||||||
|
if self.recording.ShowModal() != wx.ID_OK:
|
||||||
|
self.recording.cleanup()
|
||||||
|
return
|
||||||
|
self.recording.postprocess()
|
||||||
|
output.speak(_(u"Attaching..."))
|
||||||
|
self.uploaderDialog = AudioUploader.transfer_dialogs.UploadDialog(parent=self.parent, title=_(u"Uploading..."), filename=self.recording.file)
|
||||||
|
if self.recording.services.GetValue() == "Dropbox" and platform.system() != "Darwin":
|
||||||
|
self.uploader = dropbox_transfer.dropboxUploader(filename=self.recording.file, completed_callback=self.upload_completed, wxDialog=self.uploaderDialog)
|
||||||
|
elif self.recording.services.GetValue() == "SNDUp":
|
||||||
|
base_url = 'http://sndup.net/post.php'
|
||||||
|
if len(config.main["sound"]["sndup_api_key"]) > 0:
|
||||||
|
url = base_url + '?apikey=' + config.main['sound']['sndup_api_key']
|
||||||
|
else:
|
||||||
|
url = base_url
|
||||||
|
self.uploader = transfer.Upload(field='file', url=url, filename=self.recording.file, completed_callback=self.upload_completed, wxDialog=self.uploaderDialog)
|
||||||
|
self.uploaderDialog.Show()
|
||||||
|
self.uploader.perform_threaded()
|
||||||
|
|
||||||
|
def upload_completed(self):
|
||||||
|
url = self.uploader.get_url()
|
||||||
|
self.uploaderDialog.Destroy()
|
||||||
|
if url != 0:
|
||||||
|
self.text.SetValue(self.text.GetValue()+url+" #audio")
|
||||||
|
else:
|
||||||
|
output.speak(_(u"Unable to upload the audio"))
|
||||||
|
|
||||||
|
def onTranslate(self, ev):
|
||||||
|
dlg = translator.gui.translateDialog()
|
||||||
|
selection = dlg.ShowModal()
|
||||||
|
if selection != wx.ID_CANCEL:
|
||||||
|
text_to_translate = self.text.GetValue().encode("utf-8")
|
||||||
|
source = [x[0] for x in translator.available_languages()][dlg.source_lang.GetSelection()]
|
||||||
|
dest = [x[0] for x in translator.available_languages()][dlg.dest_lang.GetSelection()]
|
||||||
|
t = translator.translator.Translator()
|
||||||
|
t.from_lang = source
|
||||||
|
t.to_lang = dest
|
||||||
|
msg = t.translate(text_to_translate)
|
||||||
|
self.text.ChangeValue(msg)
|
||||||
|
output.speak(_(u"Translated"))
|
||||||
|
self.text.SetFocus()
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def onSelect(self, ev):
|
||||||
|
self.text.SelectAll()
|
||||||
|
|
||||||
|
def onShorten(self, ev):
|
||||||
|
urls = twitter.utils.find_urls_in_text(self.text.GetValue())
|
||||||
|
if len(urls) == 0:
|
||||||
|
output.speak(_(u"There's no URL to be shortened"))
|
||||||
|
elif len(urls) == 1:
|
||||||
|
self.text.SetValue(self.text.GetValue().replace(urls[0], url_shortener.shorten(urls[0])))
|
||||||
|
output.speak(_(u"URL shortened"))
|
||||||
|
elif len(urls) > 1:
|
||||||
|
urlList.shorten(urls, self).ShowModal()
|
||||||
|
self.text.SetFocus()
|
||||||
|
|
||||||
|
def onUnshorten(self, ev):
|
||||||
|
urls = twitter.utils.find_urls_in_text(self.text.GetValue())
|
||||||
|
if len(urls) == 0:
|
||||||
|
output.speak(_(u"There's no URL to be expanded"))
|
||||||
|
elif len(urls) == 1:
|
||||||
|
self.text.SetValue(self.text.GetValue().replace(urls[0], url_shortener.unshorten(urls[0])))
|
||||||
|
output.speak(_(u"URL expanded"))
|
||||||
|
elif len(urls) > 1:
|
||||||
|
urlList.unshorten(urls, self).ShowModal()
|
||||||
|
self.text.SetFocus()
|
||||||
|
|
||||||
|
def onTimer(self, ev):
|
||||||
|
self.label.SetLabel(str(len(self.text.GetValue())))
|
||||||
|
if len(self.text.GetValue()) > 1:
|
||||||
|
self.shortenButton.Enable()
|
||||||
|
self.unshortenButton.Enable()
|
||||||
|
else:
|
||||||
|
self.shortenButton.Disable()
|
||||||
|
self.unshortenButton.Disable()
|
||||||
|
if len(self.text.GetValue()) > 140:
|
||||||
|
sound.player.play("max_length.ogg")
|
||||||
|
self.okButton.Disable()
|
||||||
|
elif len(self.text.GetValue()) <= 140:
|
||||||
|
self.okButton.Enable()
|
||||||
|
|
||||||
|
def onCancel(self, ev):
|
||||||
|
self.Destroy()
|
||||||
|
|
||||||
|
|
||||||
|
class tweet(textLimited):
|
||||||
|
def createControls(self, message, title, text):
|
||||||
|
self.mainBox = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
self.createTextArea(message, text)
|
||||||
|
self.mainBox.Add(self.textBox, 0, wx.ALL, 5)
|
||||||
|
self.upload_image = wx.Button(self.panel, -1, _(u"Upload a picture"), size=wx.DefaultSize)
|
||||||
|
self.upload_image.Bind(wx.EVT_BUTTON, self.onUpload_image)
|
||||||
|
if platform.system() != "Darwin":
|
||||||
|
self.spellcheck = wx.Button(self.panel, -1, _("Spelling correction"), size=wx.DefaultSize)
|
||||||
|
self.spellcheck.Bind(wx.EVT_BUTTON, self.onCheck)
|
||||||
|
self.attach = wx.Button(self.panel, -1, _(u"Attach audio"), size=wx.DefaultSize)
|
||||||
|
self.attach.Bind(wx.EVT_BUTTON, self.onAttach)
|
||||||
|
self.shortenButton = wx.Button(self.panel, -1, _(u"Shorten URL"), size=wx.DefaultSize)
|
||||||
|
self.shortenButton.Bind(wx.EVT_BUTTON, self.onShorten)
|
||||||
|
self.unshortenButton = wx.Button(self.panel, -1, _(u"Expand URL"), size=wx.DefaultSize)
|
||||||
|
self.unshortenButton.Bind(wx.EVT_BUTTON, self.onUnshorten)
|
||||||
|
self.shortenButton.Disable()
|
||||||
|
self.unshortenButton.Disable()
|
||||||
|
self.translateButton = wx.Button(self.panel, -1, _(u"Translate message"), size=wx.DefaultSize)
|
||||||
|
self.translateButton.Bind(wx.EVT_BUTTON, self.onTranslate)
|
||||||
|
self.okButton = wx.Button(self.panel, wx.ID_OK, _(u"Send"), size=wx.DefaultSize)
|
||||||
|
self.okButton.Bind(wx.EVT_BUTTON, self.onSend)
|
||||||
|
self.okButton.SetDefault()
|
||||||
|
cancelButton = wx.Button(self.panel, wx.ID_CANCEL, _(u"Close"), size=wx.DefaultSize)
|
||||||
|
cancelButton.Bind(wx.EVT_BUTTON, self.onCancel)
|
||||||
|
self.buttonsBox1 = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
self.buttonsBox1.Add(self.upload_image, 0, wx.ALL, 5)
|
||||||
|
if platform.system() != "Darwin":
|
||||||
|
self.buttonsBox1.Add(self.spellcheck, 0, wx.ALL, 5)
|
||||||
|
self.buttonsBox1.Add(self.attach, 0, wx.ALL, 5)
|
||||||
|
self.mainBox.Add(self.buttonsBox1, 0, wx.ALL, 5)
|
||||||
|
self.buttonsBox2 = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
self.buttonsBox2.Add(self.shortenButton, 0, wx.ALL, 5)
|
||||||
|
self.buttonsBox2.Add(self.unshortenButton, 0, wx.ALL, 5)
|
||||||
|
self.buttonsBox2.Add(self.translateButton, 0, wx.ALL, 5)
|
||||||
|
self.mainBox.Add(self.buttonsBox2, 0, wx.ALL, 5)
|
||||||
|
self.ok_cancelSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
self.ok_cancelSizer.Add(self.okButton, 0, wx.ALL, 5)
|
||||||
|
self.ok_cancelSizer.Add(cancelButton, 0, wx.ALL, 5)
|
||||||
|
self.mainBox.Add(self.ok_cancelSizer)
|
||||||
|
selectId = wx.NewId()
|
||||||
|
self.Bind(wx.EVT_MENU, self.onSelect, id=selectId)
|
||||||
|
self.accel_tbl = wx.AcceleratorTable([
|
||||||
|
(wx.ACCEL_CTRL, ord('A'), selectId),
|
||||||
|
])
|
||||||
|
self.SetAcceleratorTable(self.accel_tbl)
|
||||||
|
self.panel.SetSizer(self.mainBox)
|
||||||
|
|
||||||
|
def __init__(self, message, title, text, parent):
|
||||||
|
super(tweet, self).__init__(message, title, text, parent)
|
||||||
|
self.image = None
|
||||||
|
self.createControls(message, title, text)
|
||||||
|
self.onTimer(wx.EVT_CHAR_HOOK)
|
||||||
|
self.SetClientSize(self.mainBox.CalcMin())
|
||||||
|
|
||||||
|
def onUpload_image(self, ev):
|
||||||
|
if self.upload_image.GetLabel() == _(u"Discard image"):
|
||||||
|
self.image = None
|
||||||
|
del self.file
|
||||||
|
output.speak(_(u"Discarded"))
|
||||||
|
self.upload_image.SetLabel(_(u"Upload a picture"))
|
||||||
|
else:
|
||||||
|
openFileDialog = wx.FileDialog(self, _(u"Select the picture to be uploaded"), "", "", _("Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
|
||||||
|
if openFileDialog.ShowModal() == wx.ID_CANCEL:
|
||||||
|
return
|
||||||
|
self.file = open(openFileDialog.GetPath(), "rb")
|
||||||
|
self.image = True
|
||||||
|
self.upload_image.SetLabel(_(u"Discard image"))
|
||||||
|
ev.Skip()
|
||||||
|
|
||||||
|
def onSend(self, ev):
|
||||||
|
self.EndModal(wx.ID_OK)
|
||||||
|
|
||||||
|
class retweet(tweet):
|
||||||
|
def __init__(self, message, title, text, parent):
|
||||||
|
super(retweet, self).__init__(message, title, text, parent)
|
||||||
|
# self.createControls(message, title, text)
|
||||||
|
self.in_reply_to = parent.db.settings[parent.name_buffer][parent.list.get_selected()]["id"]
|
||||||
|
self.text.SetInsertionPoint(0)
|
||||||
|
|
||||||
|
def onSend(self, ev):
|
||||||
|
self.EndModal(wx.ID_OK)
|
||||||
|
|
||||||
|
class dm(textLimited):
|
||||||
|
def createControls(self, message, title, text):
|
||||||
|
self.mainBox = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
if self.parent.name_buffer == "followers" or self.parent.name_buffer == "friends":
|
||||||
|
list = [self.parent.db.settings[self.parent.name_buffer][self.parent.list.get_selected()]["screen_name"]]
|
||||||
|
else:
|
||||||
|
list =twitter.utils.get_all_users(self.parent.db.settings[self.parent.name_buffer][self.parent.list.get_selected()], self.parent.db)
|
||||||
|
label = wx.StaticText(self.panel, -1, _(u"Recipient"))
|
||||||
|
self.cb = wx.ComboBox(self.panel, -1, choices=list, value=list[0], size=wx.DefaultSize)
|
||||||
|
self.createTextArea(message, text)
|
||||||
|
self.mainBox.Add(self.cb, 0, wx.ALL, 5)
|
||||||
|
self.mainBox.Add(self.textBox, 0, wx.ALL, 5)
|
||||||
|
if platform.system() != "Darwin":
|
||||||
|
self.spellcheck = wx.Button(self.panel, -1, _("Spelling correction"), size=wx.DefaultSize)
|
||||||
|
self.spellcheck.Bind(wx.EVT_BUTTON, self.onCheck)
|
||||||
|
self.attach = wx.Button(self.panel, -1, _(u"Attach audio"), size=wx.DefaultSize)
|
||||||
|
self.attach.Bind(wx.EVT_BUTTON, self.onAttach)
|
||||||
|
self.shortenButton = wx.Button(self.panel, -1, _(u"Shorten URL"), size=wx.DefaultSize)
|
||||||
|
self.shortenButton.Bind(wx.EVT_BUTTON, self.onShorten)
|
||||||
|
self.unshortenButton = wx.Button(self.panel, -1, _(u"Expand URL"), size=wx.DefaultSize)
|
||||||
|
self.unshortenButton.Bind(wx.EVT_BUTTON, self.onUnshorten)
|
||||||
|
self.shortenButton.Disable()
|
||||||
|
self.unshortenButton.Disable()
|
||||||
|
self.translateButton = wx.Button(self.panel, -1, _(u"Translate message"), size=wx.DefaultSize)
|
||||||
|
self.translateButton.Bind(wx.EVT_BUTTON, self.onTranslate)
|
||||||
|
self.okButton = wx.Button(self.panel, wx.ID_OK, _(u"Send"), size=wx.DefaultSize)
|
||||||
|
self.okButton.Bind(wx.EVT_BUTTON, self.onSend)
|
||||||
|
self.okButton.SetDefault()
|
||||||
|
cancelButton = wx.Button(self.panel, wx.ID_CANCEL, _(u"Close"), size=wx.DefaultSize)
|
||||||
|
cancelButton.Bind(wx.EVT_BUTTON, self.onCancel)
|
||||||
|
self.buttonsBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
if platform.system() != "Darwin":
|
||||||
|
self.buttonsBox.Add(self.spellcheck, 0, wx.ALL, 5)
|
||||||
|
self.buttonsBox.Add(self.attach, 0, wx.ALL, 5)
|
||||||
|
self.mainBox.Add(self.buttonsBox, 0, wx.ALL, 5)
|
||||||
|
self.buttonsBox1 = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
self.buttonsBox1.Add(self.shortenButton, 0, wx.ALL, 5)
|
||||||
|
self.buttonsBox1.Add(self.unshortenButton, 0, wx.ALL, 5)
|
||||||
|
self.buttonsBox1.Add(self.translateButton, 0, wx.ALL, 5)
|
||||||
|
self.mainBox.Add(self.buttonsBox1, 0, wx.ALL, 5)
|
||||||
|
self.buttonsBox3 = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
self.buttonsBox3.Add(self.okButton, 0, wx.ALL, 5)
|
||||||
|
self.buttonsBox3.Add(cancelButton, 0, wx.ALL, 5)
|
||||||
|
self.mainBox.Add(self.buttonsBox3, 0, wx.ALL, 5)
|
||||||
|
self.panel.SetSizer(self.mainBox)
|
||||||
|
|
||||||
|
def __init__(self, message, title, text, parent):
|
||||||
|
super(dm, self).__init__(message, title, text, parent)
|
||||||
|
self.parent = parent
|
||||||
|
self.image = None
|
||||||
|
self.createControls(message, title, text)
|
||||||
|
self.onTimer(wx.EVT_CHAR_HOOK)
|
||||||
|
self.SetClientSize(self.mainBox.CalcMin())
|
||||||
|
|
||||||
|
def onSend(self, ev):
|
||||||
|
self.EndModal(wx.ID_OK)
|
||||||
|
|
||||||
|
class reply(tweet):
|
||||||
|
def __init__(self, message, title, text, parent):
|
||||||
|
super(reply, self).__init__(message, title, text, parent)
|
||||||
|
self.in_reply_to = parent.db.settings[parent.name_buffer][parent.list.get_selected()]["id"]
|
||||||
|
self.text.SetInsertionPoint(len(self.text.GetValue()))
|
||||||
|
self.mentionAll = wx.Button(self, -1, _(u"Mention to all"), size=wx.DefaultSize)
|
||||||
|
self.mentionAll.Disable()
|
||||||
|
self.mentionAll.Bind(wx.EVT_BUTTON, self.mentionAllUsers)
|
||||||
|
self.buttonsBox1.Add(self.mentionAll, 0, wx.ALL, 5)
|
||||||
|
self.buttonsBox1.Layout()
|
||||||
|
self.mainBox.Layout()
|
||||||
|
self.check_if_users()
|
||||||
|
self.SetClientSize(self.mainBox.CalcMin())
|
||||||
|
|
||||||
|
def check_if_users(self):
|
||||||
|
try:
|
||||||
|
if len(self.parent.db.settings[self.parent.name_buffer][self.parent.list.get_selected()]["entities"]["user_mentions"]) > 0:
|
||||||
|
self.mentionAll.Enable()
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def mentionAllUsers(self, ev):
|
||||||
|
self.text.SetValue(self.text.GetValue()+twitter.utils.get_all_mentioned(self.parent.db.settings[self.parent.name_buffer][self.parent.list.get_selected()], self.parent.db))
|
||||||
|
self.text.SetInsertionPoint(len(self.text.GetValue()))
|
||||||
|
self.text.SetFocus()
|
||||||
|
|
||||||
|
def onSend(self, ev):
|
||||||
|
self.EndModal(wx.ID_OK)
|
||||||
|
|
||||||
|
class viewTweet(wx.Dialog):
|
||||||
|
def __init__(self, tweet):
|
||||||
|
super(viewTweet, self).__init__(None, size=(850,850))
|
||||||
|
self.SetTitle(_(u"Tweet - %i characters ") % (len(tweet)))
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
label = wx.StaticText(panel, -1, _(u"Tweet"))
|
||||||
|
self.text = wx.TextCtrl(panel, -1, tweet, style=wx.TE_READONLY|wx.TE_MULTILINE, size=(250, 180))
|
||||||
|
dc = wx.WindowDC(self.text)
|
||||||
|
dc.SetFont(self.text.GetFont())
|
||||||
|
(x, y, z) = dc.GetMultiLineTextExtent("0"*140)
|
||||||
|
self.text.SetSize((x, y))
|
||||||
|
self.text.SetFocus()
|
||||||
|
textBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
textBox.Add(label, 0, wx.ALL, 5)
|
||||||
|
textBox.Add(self.text, 1, wx.EXPAND, 5)
|
||||||
|
mainBox = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
mainBox.Add(textBox, 0, wx.ALL, 5)
|
||||||
|
if platform.system() != "Darwin":
|
||||||
|
spellcheck = wx.Button(panel, -1, _("Spelling correction"), size=wx.DefaultSize)
|
||||||
|
spellcheck.Bind(wx.EVT_BUTTON, self.onCheck)
|
||||||
|
self.unshortenButton = wx.Button(panel, -1, _(u"Expand URL"), size=wx.DefaultSize)
|
||||||
|
self.unshortenButton.Bind(wx.EVT_BUTTON, self.onUnshorten)
|
||||||
|
self.unshortenButton.Disable()
|
||||||
|
translateButton = wx.Button(panel, -1, _(u"Translate message"), size=wx.DefaultSize)
|
||||||
|
translateButton.Bind(wx.EVT_BUTTON, self.onTranslate)
|
||||||
|
cancelButton = wx.Button(panel, wx.ID_CANCEL, _(u"Close"), size=wx.DefaultSize)
|
||||||
|
cancelButton.SetDefault()
|
||||||
|
buttonsBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
if platform.system() != "Darwin":
|
||||||
|
buttonsBox.Add(spellcheck, 0, wx.ALL, 5)
|
||||||
|
buttonsBox.Add(self.unshortenButton, 0, wx.ALL, 5)
|
||||||
|
buttonsBox.Add(translateButton, 0, wx.ALL, 5)
|
||||||
|
buttonsBox.Add(cancelButton, 0, wx.ALL, 5)
|
||||||
|
mainBox.Add(buttonsBox, 0, wx.ALL, 5)
|
||||||
|
selectId = wx.NewId()
|
||||||
|
self.Bind(wx.EVT_MENU, self.onSelect, id=selectId)
|
||||||
|
self.accel_tbl = wx.AcceleratorTable([
|
||||||
|
(wx.ACCEL_CTRL, ord('A'), selectId),
|
||||||
|
])
|
||||||
|
self.SetAcceleratorTable(self.accel_tbl)
|
||||||
|
panel.SetSizer(mainBox)
|
||||||
|
self.check_urls()
|
||||||
|
self.SetClientSize(mainBox.CalcMin())
|
||||||
|
|
||||||
|
def check_urls(self):
|
||||||
|
if len(twitter.utils.find_urls_in_text(self.text.GetValue())) > 0:
|
||||||
|
self.unshortenButton.Enable()
|
||||||
|
|
||||||
|
def onCheck(self, ev):
|
||||||
|
if platform.system() != "Darwin": return
|
||||||
|
text = self.text.GetValue()
|
||||||
|
dlg = spellCheckerGUI.spellCheckerDialog(text, "")
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
self.text.ChangeValue(dlg.checker.get_text())
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def onTranslate(self, ev):
|
||||||
|
dlg = translator.gui.translateDialog()
|
||||||
|
selection = dlg.ShowModal()
|
||||||
|
if selection != wx.ID_CANCEL:
|
||||||
|
text_to_translate = self.text.GetValue().encode("utf-8")
|
||||||
|
source = [x[0] for x in translator.available_languages()][dlg.source_lang.GetSelection()]
|
||||||
|
dest = [x[0] for x in translator.available_languages()][dlg.dest_lang.GetSelection()]
|
||||||
|
t = translator.translator.Translator()
|
||||||
|
t.from_lang = source
|
||||||
|
t.to_lang = dest
|
||||||
|
msg = t.translate(text_to_translate)
|
||||||
|
self.text.ChangeValue(msg)
|
||||||
|
output.speak(_(u"Translated"))
|
||||||
|
self.text.SetFocus()
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def onSelect(self, ev):
|
||||||
|
self.text.SelectAll()
|
||||||
|
|
||||||
|
def onUnshorten(self, ev):
|
||||||
|
urls = twitter.utils.find_urls_in_text(self.text.GetValue())
|
||||||
|
if len(urls) == 0:
|
||||||
|
output.speak(_(u"There's no URL to be expanded"))
|
||||||
|
elif len(urls) == 1:
|
||||||
|
self.text.SetValue(self.text.GetValue().replace(urls[0], url_shortener.unshorten(urls[0])))
|
||||||
|
output.speak(_(u"URL expanded"))
|
||||||
|
elif len(urls) > 1:
|
||||||
|
urlList.unshorten(urls, self).ShowModal()
|
||||||
|
self.text.SetFocus()
|
||||||
|
|
48
src/gui/dialogs/search.py
Normal file
48
src/gui/dialogs/search.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
|
||||||
|
class searchDialog(wx.Dialog):
|
||||||
|
def __init__(self):
|
||||||
|
super(searchDialog, self).__init__(None, -1)
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
self.SetTitle(_(u"Search on Twitter"))
|
||||||
|
label = wx.StaticText(panel, -1, _(u"Search"))
|
||||||
|
self.term = wx.TextCtrl(panel, -1,)
|
||||||
|
dc = wx.WindowDC(self.term)
|
||||||
|
dc.SetFont(self.term.GetFont())
|
||||||
|
self.term.SetSize(dc.GetTextExtent("0"*40))
|
||||||
|
sizer.Add(label, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(self.term, 0, wx.ALL, 5)
|
||||||
|
self.tweets = wx.RadioButton(panel, -1, _(u"Tweets"), style=wx.RB_GROUP)
|
||||||
|
self.users = wx.RadioButton(panel, -1, _(u"Users"))
|
||||||
|
radioSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
radioSizer.Add(self.tweets, 0, wx.ALL, 5)
|
||||||
|
radioSizer.Add(self.users, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(radioSizer, 0, wx.ALL, 5)
|
||||||
|
ok = wx.Button(panel, wx.ID_OK, _(u"OK"))
|
||||||
|
ok.SetDefault()
|
||||||
|
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Close"))
|
||||||
|
btnsizer = wx.BoxSizer()
|
||||||
|
btnsizer.Add(ok, 0, wx.ALL, 5)
|
||||||
|
btnsizer.Add(cancel, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(btnsizer, 0, wx.ALL, 5)
|
||||||
|
panel.SetSizer(sizer)
|
||||||
|
self.SetClientSize(sizer.CalcMin())
|
83
src/gui/dialogs/show_user.py
Normal file
83
src/gui/dialogs/show_user.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx, twitter, config, gui.dialogs, sound, webbrowser
|
||||||
|
|
||||||
|
class showUserProfile(wx.Dialog):
|
||||||
|
def __init__(self, twitter, screen_name):
|
||||||
|
self.twitter = twitter
|
||||||
|
self.screen_name = screen_name
|
||||||
|
wx.Dialog.__init__(self, None, -1)
|
||||||
|
self.SetTitle(_(u"Information for %s") % (screen_name))
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
self.get_data()
|
||||||
|
static = wx.StaticText(panel, -1, _(u"Details"))
|
||||||
|
sizer.Add(static, 0, wx.ALL, 5)
|
||||||
|
text = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE|wx.TE_READONLY)
|
||||||
|
# dc = wx.WindowDC(text)
|
||||||
|
# dc.SetFont(text.GetFont())
|
||||||
|
# (x, y, z) = dc.GetMultiLineTextExtent("0"*10000)
|
||||||
|
# text.SetSize((x, y))
|
||||||
|
text.SetFocus()
|
||||||
|
sizer.Add(text, 0, wx.ALL|wx.EXPAND, 5)
|
||||||
|
self.url = wx.Button(panel, -1, _(u"Go to URL"), size=wx.DefaultSize)
|
||||||
|
self.url.Bind(wx.EVT_BUTTON, self.onUrl)
|
||||||
|
self.url.Disable()
|
||||||
|
close = wx.Button(panel, wx.ID_CANCEL, _(u"Close"))
|
||||||
|
close.Bind(wx.EVT_BUTTON, self.onClose)
|
||||||
|
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
btnSizer.Add(self.url, 0, wx.ALL, 5)
|
||||||
|
btnSizer.Add(close, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(btnSizer, 0, wx.ALL, 5)
|
||||||
|
text.ChangeValue(self.compose_string())
|
||||||
|
text.SetSize(text.GetBestSize())
|
||||||
|
panel.SetSizer(sizer)
|
||||||
|
self.SetClientSize(sizer.CalcMin())
|
||||||
|
|
||||||
|
def onUrl(self, ev):
|
||||||
|
webbrowser.open(self.data["url"])
|
||||||
|
|
||||||
|
def onClose(self, ev):
|
||||||
|
self.Destroy()
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
try:
|
||||||
|
self.data = self.twitter.twitter.show_user(screen_name=self.screen_name)
|
||||||
|
except:
|
||||||
|
wx.MessageDialog(self, _(u"This user does not exist on Twitter"), _(u"Error"), wx.ICON_ERROR).ShowModal()
|
||||||
|
self.EndModal()
|
||||||
|
|
||||||
|
def compose_string(self):
|
||||||
|
string = u""
|
||||||
|
string = string + _(u"Username: @%s\n") % (self.data["screen_name"])
|
||||||
|
string = string + _(u"Name: %s\n") % (self.data["name"])
|
||||||
|
if self.data["location"] != "":
|
||||||
|
string = string + _(u"Location: %s\n") % (self.data["location"])
|
||||||
|
if self.data["url"] != None:
|
||||||
|
string = string+ _(u"URL: %s\n") % (self.data["url"])
|
||||||
|
self.url.Enable()
|
||||||
|
if self.data["description"] != "":
|
||||||
|
string = string+ _(u"Bio: %s\n") % (self.data["description"])
|
||||||
|
if self.data["protected"] == True: protected = _(u"Yes")
|
||||||
|
else: protected = _(u"No")
|
||||||
|
string = string+ _(u"Protected: %s\n") % (protected)
|
||||||
|
string = string+_(u"Followers: %s\n Friends: %s\n") % (self.data["followers_count"], self.data["friends_count"])
|
||||||
|
string = string+ _(u"Tweets: %s\n") % (self.data["statuses_count"])
|
||||||
|
string = string+ _(u"Favourites: %s") % (self.data["favourites_count"])
|
||||||
|
return string
|
119
src/gui/dialogs/update_profile.py
Normal file
119
src/gui/dialogs/update_profile.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
from twython import TwythonError
|
||||||
|
|
||||||
|
class updateProfile(wx.Dialog):
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.twitter = parent.twitter
|
||||||
|
self.parent = parent
|
||||||
|
super(updateProfile, self).__init__(parent=None, id=-1)
|
||||||
|
self.SetTitle(_(u"Update your profile"))
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
labelName = wx.StaticText(panel, -1, _(u"Name (20 characters maximum)"))
|
||||||
|
self.name = wx.TextCtrl(panel, -1)
|
||||||
|
self.name.SetFocus()
|
||||||
|
dc = wx.WindowDC(self.name)
|
||||||
|
dc.SetFont(self.name.GetFont())
|
||||||
|
self.name.SetSize(dc.GetTextExtent("0"*20))
|
||||||
|
labelLocation = wx.StaticText(panel, -1, _(u"Location"))
|
||||||
|
self.location = wx.TextCtrl(panel, -1)
|
||||||
|
dc = wx.WindowDC(self.location)
|
||||||
|
dc.SetFont(self.location.GetFont())
|
||||||
|
self.location.SetSize(dc.GetTextExtent("0"*35))
|
||||||
|
labelUrl = wx.StaticText(panel, -1, _(u"Website"))
|
||||||
|
self.url = wx.TextCtrl(panel, -1)
|
||||||
|
dc = wx.WindowDC(self.url)
|
||||||
|
dc.SetFont(self.url.GetFont())
|
||||||
|
self.url.SetSize(dc.GetTextExtent("0"*22))
|
||||||
|
labelDescription = wx.StaticText(panel, -1, _(u"Bio (160 characters maximum)"))
|
||||||
|
self.description = wx.TextCtrl(panel, -1, size=(400, 400))
|
||||||
|
dc = wx.WindowDC(self.description)
|
||||||
|
dc.SetFont(self.description.GetFont())
|
||||||
|
self.description.SetSize(dc.GetTextExtent("0"*160))
|
||||||
|
self.image = None
|
||||||
|
self.upload_image = wx.Button(panel, -1, _(u"Upload a picture"))
|
||||||
|
self.upload_image.Bind(wx.EVT_BUTTON, self.onUpload_picture)
|
||||||
|
ok = wx.Button(panel, wx.ID_OK, _(u"Update profile"))
|
||||||
|
ok.Bind(wx.EVT_BUTTON, self.onUpdateProfile)
|
||||||
|
ok.SetDefault()
|
||||||
|
close = wx.Button(panel, wx.ID_CANCEL, _("Close"))
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
nameBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
nameBox.Add(labelName, 0, wx.ALL, 5)
|
||||||
|
nameBox.Add(self.name, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(nameBox, 0, wx.ALL, 5)
|
||||||
|
locationBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
locationBox.Add(labelLocation, 0, wx.ALL, 5)
|
||||||
|
locationBox.Add(self.location, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(locationBox, 0, wx.ALL, 5)
|
||||||
|
urlBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
urlBox.Add(labelUrl, 0, wx.ALL, 5)
|
||||||
|
urlBox.Add(self.url, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(urlBox, 0, wx.ALL, 5)
|
||||||
|
descriptionBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
descriptionBox.Add(labelDescription, 0, wx.ALL, 5)
|
||||||
|
descriptionBox.Add(self.description, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(descriptionBox, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(self.upload_image, 5, wx.CENTER, 5)
|
||||||
|
btnBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
btnBox.Add(ok, 0, wx.ALL, 5)
|
||||||
|
btnBox.Add(close, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(btnBox, 0, wx.ALL, 5)
|
||||||
|
panel.SetSizer(sizer)
|
||||||
|
self.SetClientSize(sizer.CalcMin())
|
||||||
|
self.get_data()
|
||||||
|
|
||||||
|
def onUpload_picture(self, ev):
|
||||||
|
if self.upload_image.GetLabel() == _(u"Discard image"):
|
||||||
|
self.image = None
|
||||||
|
del self.file
|
||||||
|
output.speak(_(u"Discarded"))
|
||||||
|
self.upload_image.SetLabel(_(u"Upload a picture"))
|
||||||
|
else:
|
||||||
|
openFileDialog = wx.FileDialog(self, _(u"Select the picture to be uploaded"), "", "", _("Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
|
||||||
|
if openFileDialog.ShowModal() == wx.ID_CANCEL:
|
||||||
|
return
|
||||||
|
self.file = open(openFileDialog.GetPath(), "rb")
|
||||||
|
self.image = True
|
||||||
|
self.upload_image.SetLabel(_(u"Discard image"))
|
||||||
|
ev.Skip()
|
||||||
|
|
||||||
|
def onUpdateProfile(self, ev):
|
||||||
|
try:
|
||||||
|
if self.image != None:
|
||||||
|
self.twitter.twitter.update_profile_image(image=self.file)
|
||||||
|
except TwythonError as e:
|
||||||
|
output.speak(u"Error %s. %s" % (e.error_code, e.msg))
|
||||||
|
try:
|
||||||
|
f = self.twitter.twitter.update_profile(name=self.name.GetValue(), location=self.location.GetValue(), url=self.url.GetValue(), description=self.description.GetValue())
|
||||||
|
self.EndModal(wx.ID_OK)
|
||||||
|
except TwythonError as e:
|
||||||
|
output.speak(u"Error %s. %s" % (e.error_code, e.msg))
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
data = self.twitter.twitter.show_user(screen_name=self.parent.db.settings["user_name"])
|
||||||
|
self.name.ChangeValue(data["name"])
|
||||||
|
if data["url"] != None:
|
||||||
|
self.url.ChangeValue(data["url"])
|
||||||
|
if len(data["location"]) > 0:
|
||||||
|
self.location.ChangeValue(data["location"])
|
||||||
|
if len(data["description"]) > 0:
|
||||||
|
self.description.ChangeValue(data["description"])
|
71
src/gui/dialogs/urlList.py
Normal file
71
src/gui/dialogs/urlList.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import webbrowser
|
||||||
|
import url_shortener
|
||||||
|
|
||||||
|
class urlList(wx.Dialog):
|
||||||
|
def __init__(self, urls):
|
||||||
|
self.urls = urls
|
||||||
|
super(urlList, self).__init__(parent=None, title=_(u"Select an URL"))
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
# label = wx.StaticText(panel, -1, _(u"Select a URL"))
|
||||||
|
self.lista = wx.ListBox(panel, -1)
|
||||||
|
self.lista.SetFocus()
|
||||||
|
self.populate_list()
|
||||||
|
self.lista.SetSelection(0)
|
||||||
|
self.lista.SetSize(self.lista.GetBestSize())
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
# sizer.Add(label, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(self.lista, 0, wx.ALL, 5)
|
||||||
|
goBtn = wx.Button(panel, wx.ID_OK)
|
||||||
|
goBtn.SetDefault()
|
||||||
|
goBtn.Bind(wx.EVT_BUTTON, self.onGo)
|
||||||
|
cancelBtn = wx.Button(panel, wx.ID_CANCEL)
|
||||||
|
btnSizer = wx.BoxSizer()
|
||||||
|
btnSizer.Add(goBtn, 0, wx.ALL, 5)
|
||||||
|
btnSizer.Add(cancelBtn, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(btnSizer, 0, wx.ALL, 5)
|
||||||
|
panel.SetSizer(sizer)
|
||||||
|
self.SetClientSize(sizer.CalcMin())
|
||||||
|
|
||||||
|
def onGo(self, ev):
|
||||||
|
webbrowser.open(self.lista.GetStringSelection())
|
||||||
|
self.Destroy()
|
||||||
|
|
||||||
|
def populate_list(self):
|
||||||
|
for i in self.urls:
|
||||||
|
self.lista.Append(i)
|
||||||
|
|
||||||
|
class shorten(urlList):
|
||||||
|
def __init__(self, urls, parent):
|
||||||
|
urlList.__init__(self, urls)
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
def onGo(self, ev):
|
||||||
|
self.parent.text.SetValue(self.parent.text.GetValue().replace(self.lista.GetStringSelection(), url_shortener.shorten(self.lista.GetStringSelection())))
|
||||||
|
self.Destroy()
|
||||||
|
|
||||||
|
class unshorten(shorten):
|
||||||
|
def __init__(self, urls, parent):
|
||||||
|
shorten.__init__(self, urls, parent)
|
||||||
|
|
||||||
|
def onGo(self, ev):
|
||||||
|
self.parent.text.SetValue(self.parent.text.GetValue().replace(self.lista.GetStringSelection(), url_shortener.unshorten(self.lista.GetStringSelection())))
|
||||||
|
self.Destroy()
|
49
src/gui/dialogs/utils.py
Normal file
49
src/gui/dialogs/utils.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx, twitter, gui.dialogs, sound, config
|
||||||
|
from mysc.repeating_timer import RepeatingTimer
|
||||||
|
|
||||||
|
class selectUserDialog(wx.Dialog):
|
||||||
|
def __init__(self, parent, title):
|
||||||
|
self.parent = parent
|
||||||
|
wx.Dialog.__init__(self, None, -1)
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
userSizer = wx.BoxSizer()
|
||||||
|
self.SetTitle(title)
|
||||||
|
if self.parent.nb.GetCurrentPage().name_buffer == "followers" or self.parent.nb.GetCurrentPage().name_buffer == "friends":
|
||||||
|
list = [self.parent.db.settings[self.parent.nb.GetCurrentPage().name_buffer][self.parent.nb.GetCurrentPage().list.get_selected()]["screen_name"]]
|
||||||
|
else:
|
||||||
|
try: list =twitter.utils.get_all_users(self.parent.db.settings[self.parent.nb.GetCurrentPage().name_buffer][self.parent.nb.GetCurrentPage().list.get_selected()], self.parent.db)
|
||||||
|
except KeyError: list = [self.parent.db.settings[self.parent.nb.GetCurrentPage().name_buffer][self.parent.nb.GetCurrentPage().list.get_selected()]["screen_name"]]
|
||||||
|
self.cb = wx.ComboBox(panel, -1, choices=list, value=list[0], size=wx.DefaultSize)
|
||||||
|
self.cb.SetFocus()
|
||||||
|
userSizer.Add(wx.StaticText(panel, -1, _(u"User")), 0, wx.ALL, 5)
|
||||||
|
userSizer.Add(self.cb)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
ok = wx.Button(panel, wx.ID_OK, _(u"OK"))
|
||||||
|
ok.SetDefault()
|
||||||
|
# ok.Bind(wx.EVT_BUTTON, self.onok)
|
||||||
|
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Close"))
|
||||||
|
btnsizer = wx.BoxSizer()
|
||||||
|
btnsizer.Add(ok, 0, wx.ALL, 5)
|
||||||
|
btnsizer.Add(cancel, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(userSizer, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(btnsizer, 0, wx.ALL, 5)
|
||||||
|
panel.SetSizer(sizer)
|
||||||
|
self.SetClientSize(sizer.CalcMin())
|
969
src/gui/main.py
Normal file
969
src/gui/main.py
Normal file
@ -0,0 +1,969 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import dialogs
|
||||||
|
import buffers
|
||||||
|
import config
|
||||||
|
import twitter
|
||||||
|
import db
|
||||||
|
import webbrowser
|
||||||
|
import sound
|
||||||
|
import updater
|
||||||
|
import application
|
||||||
|
import os
|
||||||
|
import logging as original_logger
|
||||||
|
import output
|
||||||
|
import platform
|
||||||
|
import urllib2
|
||||||
|
import sysTrayIcon
|
||||||
|
import languageHandler
|
||||||
|
from issueReporter import gui as issueReporterGUI
|
||||||
|
from sessionmanager import manager
|
||||||
|
from mysc import event
|
||||||
|
from mysc.thread_utils import call_threaded
|
||||||
|
from twython import TwythonError
|
||||||
|
from urllib2 import URLError
|
||||||
|
from mysc.repeating_timer import RepeatingTimer
|
||||||
|
from mysc import localization
|
||||||
|
if platform.system() == "Windows" or platform.system() == "Linux":
|
||||||
|
from keyboard_handler.wx_handler import WXKeyboardHandler
|
||||||
|
from extra import SoundsTutorial
|
||||||
|
from keystrokeEditor import gui as keystrokeEditorGUI
|
||||||
|
log = original_logger.getLogger("gui.main")
|
||||||
|
|
||||||
|
class mainFrame(wx.Frame):
|
||||||
|
""" Main class of the Frame. This is the Main Window."""
|
||||||
|
|
||||||
|
### MENU
|
||||||
|
def makeMenus(self):
|
||||||
|
""" Creates, bind and returns the menu bar for the application. Also in this function, the accel table is created."""
|
||||||
|
menuBar = wx.MenuBar()
|
||||||
|
|
||||||
|
# Application menu
|
||||||
|
app = wx.Menu()
|
||||||
|
updateProfile = app.Append(wx.NewId(), _(u"&Update profile"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.update_profile, updateProfile)
|
||||||
|
show_hide = app.Append(wx.NewId(), _(u"&Hide window"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.show_hide, show_hide)
|
||||||
|
search = app.Append(wx.NewId(), _(u"&Search"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.search, search)
|
||||||
|
lists = app.Append(wx.NewId(), _(u"&Lists manager"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.list_manager, lists)
|
||||||
|
sounds_tutorial = app.Append(wx.NewId(), _(u"Sounds &tutorial"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.learn_sounds, sounds_tutorial)
|
||||||
|
keystroke_editor = app.Append(wx.NewId(), _(u"&Edit keystrokes"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.edit_keystrokes, keystroke_editor)
|
||||||
|
prefs = app.Append(wx.ID_PREFERENCES, _(u"&Preferences"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.preferences, prefs)
|
||||||
|
close = app.Append(wx.ID_EXIT, _(u"E&xit"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.close, close)
|
||||||
|
|
||||||
|
# Tweet menu
|
||||||
|
tweet = wx.Menu()
|
||||||
|
compose = tweet.Append(wx.NewId(), _(u"&Tweet"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.compose, compose)
|
||||||
|
response = tweet.Append(wx.NewId(), _(u"Re&ply"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.reply, response)
|
||||||
|
retweet = tweet.Append(wx.NewId(), _(u"&Retweet"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.retweet, retweet)
|
||||||
|
fav = tweet.Append(wx.NewId(), _(u"Add to &favourites"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.fav, fav)
|
||||||
|
unfav = tweet.Append(wx.NewId(), _(u"Remove from favo&urites"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.unfav, unfav)
|
||||||
|
view = tweet.Append(wx.NewId(), _(u"&Show tweet"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.view, view)
|
||||||
|
delete = tweet.Append(wx.NewId(), _(u"&Delete"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.delete, delete)
|
||||||
|
|
||||||
|
# User menu
|
||||||
|
user = wx.Menu()
|
||||||
|
follow = user.Append(wx.NewId(), _(u"&Follow"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.onFollow, follow)
|
||||||
|
unfollow = user.Append(wx.NewId(), _(u"&Unfollow"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.onUnfollow, unfollow)
|
||||||
|
mute = user.Append(wx.NewId(), _(u"&Mute"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.onMute, mute)
|
||||||
|
unmute = user.Append(wx.NewId(), _(u"U&nmute"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.onUnmute, unmute)
|
||||||
|
report = user.Append(wx.NewId(), _(u"&Report as spam"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.onReport, report)
|
||||||
|
block = user.Append(wx.NewId(), _(u"&Block"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.onBlock, block)
|
||||||
|
unblock = user.Append(wx.NewId(), _(u"Unb&lock"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.onUnblock, unblock)
|
||||||
|
dm = user.Append(wx.NewId(), _(u"Direct me&ssage"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.dm, dm)
|
||||||
|
addToList = user.Append(wx.NewId(), _(u"&Add to list"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.add_to_list, addToList)
|
||||||
|
removeFromList = user.Append(wx.NewId(), _(u"R&emove from list"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.remove_from_list, removeFromList)
|
||||||
|
viewLists = user.Append(wx.NewId(), _(u"&View lists"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.view_user_lists, viewLists)
|
||||||
|
details = user.Append(wx.NewId(), _(u"Show user &profile"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.details, details)
|
||||||
|
timeline = user.Append(wx.NewId(), _(u"&Timeline"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.open_timeline, timeline)
|
||||||
|
favs = user.Append(wx.NewId(), _(u"V&iew favourites"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.favs_timeline, favs)
|
||||||
|
|
||||||
|
# buffer menu
|
||||||
|
buffer = wx.Menu()
|
||||||
|
mute = buffer.Append(wx.NewId(), _(u"&Mute"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.toggle_mute, mute)
|
||||||
|
autoread = buffer.Append(wx.NewId(), _(u"&Autoread tweets for this buffer"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.toggle_autoread, autoread)
|
||||||
|
clear = buffer.Append(wx.NewId(), _(u"&Clear buffer"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.clear_list, clear)
|
||||||
|
deleteTl = buffer.Append(wx.NewId(), _(u"&Remove buffer"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.delete_buffer, deleteTl)
|
||||||
|
|
||||||
|
# Help Menu
|
||||||
|
help = wx.Menu()
|
||||||
|
doc = help.Append(-1, _(u"&Documentation"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.onManual, doc)
|
||||||
|
changelog = help.Append(wx.NewId(), _(u"&What's new in this version?"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.onChangelog, changelog)
|
||||||
|
check_for_updates = help.Append(wx.NewId(), _(u"&Check for updates"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.onCheckForUpdates, check_for_updates)
|
||||||
|
reportError = help.Append(wx.NewId(), _(u"&Report an error"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.onReportBug, reportError)
|
||||||
|
visit_website = help.Append(-1, _(u"TW Blue &website"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.onVisit_website, visit_website)
|
||||||
|
about = help.Append(-1, _(u"About &TW Blue"))
|
||||||
|
self.Bind(wx.EVT_MENU, self.onAbout, about)
|
||||||
|
|
||||||
|
# Add all to the menu Bar
|
||||||
|
menuBar.Append(app, _(u"&Application"))
|
||||||
|
menuBar.Append(tweet, _(u"&Tweet"))
|
||||||
|
menuBar.Append(user, _(u"&User"))
|
||||||
|
menuBar.Append(buffer, _(u"&Buffer"))
|
||||||
|
menuBar.Append(help, _(u"&Help"))
|
||||||
|
|
||||||
|
downID = wx.NewId()
|
||||||
|
upID = wx.NewId()
|
||||||
|
leftID = wx.NewId()
|
||||||
|
rightID = wx.NewId()
|
||||||
|
if platform.system() == "Darwin":
|
||||||
|
self.Bind(wx.EVT_MENU, self.down, id=downID)
|
||||||
|
self.Bind(wx.EVT_MENU, self.up, id=upID)
|
||||||
|
self.Bind(wx.EVT_MENU, self.left, id=leftID)
|
||||||
|
self.Bind(wx.EVT_MENU, self.right, id=rightID)
|
||||||
|
|
||||||
|
# Creates the acceleration table.
|
||||||
|
self.accel_tbl = wx.AcceleratorTable([
|
||||||
|
(wx.ACCEL_CTRL, ord('N'), compose.GetId()),
|
||||||
|
(wx.ACCEL_CTRL, ord('R'), response.GetId()),
|
||||||
|
(wx.ACCEL_CTRL|wx.ACCEL_SHIFT, ord('R'), retweet.GetId()),
|
||||||
|
(wx.ACCEL_CTRL, ord('F'), fav.GetId()),
|
||||||
|
(wx.ACCEL_CTRL|wx.ACCEL_SHIFT, ord('F'), unfav.GetId()),
|
||||||
|
(wx.ACCEL_CTRL|wx.ACCEL_SHIFT, ord('V'), view.GetId()),
|
||||||
|
(wx.ACCEL_CTRL, ord('D'), dm.GetId()),
|
||||||
|
|
||||||
|
(wx.ACCEL_CTRL, ord('Q'), close.GetId()),
|
||||||
|
(wx.ACCEL_CTRL, ord('S'), follow.GetId()),
|
||||||
|
(wx.ACCEL_CTRL|wx.ACCEL_SHIFT, ord('S'), unfollow.GetId()),
|
||||||
|
(wx.ACCEL_CTRL, ord('K'), block.GetId()),
|
||||||
|
(wx.ACCEL_CTRL|wx.ACCEL_SHIFT, ord('K'), report.GetId()),
|
||||||
|
(wx.ACCEL_CTRL, ord('I'), timeline.GetId()),
|
||||||
|
(wx.ACCEL_CTRL|wx.ACCEL_SHIFT, ord('I'), deleteTl.GetId()),
|
||||||
|
(wx.ACCEL_CTRL, ord('M'), show_hide.GetId()),
|
||||||
|
(wx.ACCEL_CTRL, ord('P'), updateProfile.GetId()),
|
||||||
|
(wx.ACCEL_CTRL, wx.WXK_DOWN, downID),
|
||||||
|
(wx.ACCEL_CTRL, wx.WXK_UP, upID),
|
||||||
|
(wx.ACCEL_CTRL, wx.WXK_LEFT, leftID),
|
||||||
|
(wx.ACCEL_CTRL, wx.WXK_RIGHT, rightID),
|
||||||
|
])
|
||||||
|
|
||||||
|
self.SetAcceleratorTable(self.accel_tbl)
|
||||||
|
return menuBar
|
||||||
|
|
||||||
|
### MAIN
|
||||||
|
def __init__(self, authorised=True, user_key=None, user_secret=None):
|
||||||
|
""" Main function of this class."""
|
||||||
|
if authorised == False:
|
||||||
|
self.user_key = user_key
|
||||||
|
self.user_secret = user_secret
|
||||||
|
else:
|
||||||
|
self.user_key = self.user_secret = None
|
||||||
|
log.debug("Loading temporal database...")
|
||||||
|
self.db = db.db()
|
||||||
|
# Gets the twitter object for future calls to the twitter Rest API.
|
||||||
|
log.debug("Getting Twitter's Rest API...")
|
||||||
|
self.twitter = twitter.twitter.twitter()
|
||||||
|
super(mainFrame, self).__init__(None, -1, "TW Blue", size=(1600, 1600))
|
||||||
|
self.Bind(wx.EVT_QUERY_END_SESSION, self.exit)
|
||||||
|
self.Bind(wx.EVT_END_SESSION, self.exit)
|
||||||
|
log.debug(u"Creating the system tray icon... ")
|
||||||
|
sysTray=sysTrayIcon.SysTrayIcon(self)
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
self.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
self.SetTitle("TW Blue")
|
||||||
|
try:
|
||||||
|
updater.update_manager.check_for_update()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self.SetMenuBar(self.makeMenus())
|
||||||
|
self.setup_twitter(panel)
|
||||||
|
|
||||||
|
def logging_in_twblue(self, panel):
|
||||||
|
log.debug("Retrieving username...")
|
||||||
|
twitter.starting.start_user_info(config=self.db, twitter=self.twitter)
|
||||||
|
config.main["twitter"]["user_name"] = self.db.settings["user_name"]
|
||||||
|
self.SetTitle(u"@%s. - TW Blue" % (self.db.settings["user_name"]))
|
||||||
|
self.nb = wx.Treebook(panel, -1)
|
||||||
|
self.Bind(wx.EVT_CLOSE, self.close)
|
||||||
|
self.nb.Bind(wx.EVT_TREEBOOK_PAGE_CHANGED, self.onPageChanged)
|
||||||
|
# Gets the tabs for home, mentions, send and direct messages.
|
||||||
|
log.debug("Creating buffers...")
|
||||||
|
self.db.settings["buffers"] = []
|
||||||
|
account = buffers.accountPanel(self.nb)
|
||||||
|
self.nb.AddPage(account, self.db.settings["user_name"])
|
||||||
|
self.db.settings["buffers"].append(self.db.settings["user_name"])
|
||||||
|
account_index = self.db.settings["buffers"].index(self.db.settings["user_name"])
|
||||||
|
home = buffers.basePanel(self.nb, self, "home_timeline", self.twitter.twitter.get_home_timeline, sound="tweet_received.ogg")
|
||||||
|
self.nb.InsertSubPage(account_index, home, _(u"Home"))
|
||||||
|
self.db.settings["buffers"].append("home_timeline")
|
||||||
|
self.nb.SetSelection(1)
|
||||||
|
self.nb.GetPage(1).list.list.SetFocus()
|
||||||
|
mentionsP = buffers.basePanel(self.nb, self, "mentions", self.twitter.twitter.get_mentions_timeline, sound="mention_received.ogg")
|
||||||
|
self.nb.InsertSubPage(account_index, mentionsP, _("Mentions"))
|
||||||
|
self.db.settings["buffers"].append("mentions")
|
||||||
|
dms = buffers.dmPanel(self.nb, self, "direct_messages", self.twitter.twitter.get_direct_messages, sound="dm_received.ogg")
|
||||||
|
self.nb.InsertSubPage(account_index, dms, _(u"Direct messages"))
|
||||||
|
self.db.settings["buffers"].append("direct_messages")
|
||||||
|
sent = buffers.basePanel(self.nb, self, "sent", self.twitter.twitter.get_user_timeline, argumento=self.db.settings["user_name"])
|
||||||
|
self.nb.InsertSubPage(account_index, sent, _(u"Sent"))
|
||||||
|
self.db.settings["buffers"].append("sent")
|
||||||
|
# If the user has enabled favs from config.
|
||||||
|
if config.main["other_buffers"]["show_favourites"] == True:
|
||||||
|
log.debug("Getting Favorited tweets...")
|
||||||
|
favs = buffers.basePanel(self.nb, self, "favs", self.twitter.twitter.get_favorites)
|
||||||
|
self.nb.InsertSubPage(account_index, favs, _(u"Favourites"))
|
||||||
|
self.db.settings["buffers"].append("favs")
|
||||||
|
# If followers are enabled from config.
|
||||||
|
if config.main["other_buffers"]["show_followers"] == True:
|
||||||
|
log.debug("Getting followers...")
|
||||||
|
followers = buffers.peoplePanel(self.nb, self, "followers", self.twitter.twitter.get_followers_list, argumento=self.db.settings["user_name"], sound="update_followers.ogg")
|
||||||
|
self.nb.InsertSubPage(account_index, followers, _(u"Followers"))
|
||||||
|
self.db.settings["buffers"].append("followers")
|
||||||
|
# Same here but for friends.
|
||||||
|
if config.main["other_buffers"]["show_friends"] == True:
|
||||||
|
log.debug("Getting friends...")
|
||||||
|
friends = buffers.peoplePanel(self.nb, self, "friends", self.twitter.twitter.get_friends_list, argumento=self.db.settings["user_name"])
|
||||||
|
self.nb.InsertSubPage(account_index, friends, _(u"Friends"))
|
||||||
|
self.db.settings["buffers"].append("friends")
|
||||||
|
if config.main["other_buffers"]["show_blocks"] == True:
|
||||||
|
blocked = buffers.peoplePanel(self.nb, self, "blocks", self.twitter.twitter.list_blocks)
|
||||||
|
self.nb.InsertSubPage(account_index, blocked, _(u"Blocked users"))
|
||||||
|
self.db.settings["buffers"].append("blocks")
|
||||||
|
if config.main["other_buffers"]["show_muted_users"] == True:
|
||||||
|
muteds = buffers.peoplePanel(self.nb, self, "muteds", self.twitter.twitter.get_muted_users_list)
|
||||||
|
self.nb.InsertSubPage(account_index, muteds, _(u"Muted users"))
|
||||||
|
self.db.settings["buffers"].append("muteds")
|
||||||
|
if config.main["other_buffers"]["show_events"] == True:
|
||||||
|
evt = buffers.eventsPanel(self.nb, self, sound="new_event.ogg")
|
||||||
|
self.nb.InsertSubPage(account_index, evt, _(u"Events"))
|
||||||
|
self.db.settings["buffers"].append("events")
|
||||||
|
searches = buffers.emptyPanel(self.nb)
|
||||||
|
self.nb.InsertSubPage(account_index, searches, _(u"Searches"))
|
||||||
|
self.db.settings["buffers"].append("searches")
|
||||||
|
|
||||||
|
for i in config.main["other_buffers"]["tweet_searches"]:
|
||||||
|
self.nb.InsertSubPage(self.db.settings["buffers"].index("searches"), buffers.searchPanel(self.nb, self, "%s-search" % (i,), q=i, count=100), _(u"Search for %s" % (i,)))
|
||||||
|
self.db.settings["buffers"].append("%s-search" % (i,))
|
||||||
|
timelines = buffers.emptyPanel(self.nb)
|
||||||
|
self.nb.InsertSubPage(account_index, timelines, _(u"Timelines"))
|
||||||
|
self.db.settings["buffers"].append("timelines")
|
||||||
|
for i in config.main["other_buffers"]["timelines"]:
|
||||||
|
self.nb.InsertSubPage(self.db.settings["buffers"].index("timelines"), buffers.basePanel(self.nb, self, i, self.twitter.twitter.get_user_timeline, argumento=i, timeline=True, sound="tweet_timeline.ogg"), _(u"Timeline for %s") % i)
|
||||||
|
self.db.settings["buffers"].append(i)
|
||||||
|
lists = buffers.emptyPanel(self.nb)
|
||||||
|
self.nb.InsertSubPage(account_index, lists, _(u"Lists"))
|
||||||
|
self.db.settings["buffers"].append("lists")
|
||||||
|
for i in config.main["other_buffers"]["lists"]:
|
||||||
|
self.nb.InsertSubPage(self.db.settings["buffers"].index("lists"), buffers.listPanel(self.nb, self, i+"-list", argumento=twitter.utils.find_list(i, self.db.settings["lists"])), _(u"List for %s") % i)
|
||||||
|
self.db.settings["buffers"].append(i+"-list")
|
||||||
|
|
||||||
|
## favourites timelines
|
||||||
|
favs_timelines = buffers.emptyPanel(self.nb)
|
||||||
|
self.nb.InsertSubPage(account_index, favs_timelines, _(U"Favourites timelines"))
|
||||||
|
self.db.settings["buffers"].append("favourites_timelines")
|
||||||
|
for i in config.main["other_buffers"]["favourites_timelines"]:
|
||||||
|
self.nb.InsertSubPage(self.db.settings["buffers"].index("favourites_timelines"), buffers.favsPanel(self.nb, self, i+"favs", argumento=i, sound="favourites_timeline_updated.ogg"), _(u"Favourites for %s") % i,)
|
||||||
|
self.db.settings["buffers"].append(i+"favs")
|
||||||
|
self.fav_stream = RepeatingTimer(180, self.get_fav_buffers)
|
||||||
|
self.fav_stream.start()
|
||||||
|
self.sizer.Add(self.nb, 0, wx.ALL, 5)
|
||||||
|
panel.SetSizer(self.sizer)
|
||||||
|
self.SetClientSize(self.sizer.CalcMin())
|
||||||
|
self.Bind(event.MyEVT_STARTED, self.onInit)
|
||||||
|
self.Bind(event.EVT_RESULT, self.onMemberAdded)
|
||||||
|
call_threaded(self.init, run_streams=True)
|
||||||
|
|
||||||
|
def init(self, run_streams=False):
|
||||||
|
""" Calls the start_stream function for each stream tab."""
|
||||||
|
deleted = 0
|
||||||
|
for i in range(0, self.nb.GetPageCount()):
|
||||||
|
if self.nb.GetPage(i).type == "account" or self.nb.GetPage(i).type == "empty": continue
|
||||||
|
if i == self.nb.GetPageCount() and deleted > 0:
|
||||||
|
i = i-1
|
||||||
|
deleted = deleted-1
|
||||||
|
log.debug("Starting stream for %s..." % self.nb.GetPage(i).name_buffer)
|
||||||
|
info_event = event.infoEvent(event.EVT_STARTED, 1)
|
||||||
|
try:
|
||||||
|
if self.nb.GetPage(i).type == "search":
|
||||||
|
self.nb.GetPage(i).timer = RepeatingTimer(180, self.nb.GetPage(i).load_search)
|
||||||
|
self.nb.GetPage(i).timer.start()
|
||||||
|
num = self.nb.GetPage(i).start_streams()
|
||||||
|
info_event.SetItem(i, num)
|
||||||
|
wx.PostEvent(self, info_event)
|
||||||
|
except TwythonError as e:
|
||||||
|
continue
|
||||||
|
except UnicodeDecodeError: # This happens when there is a bad internet connection
|
||||||
|
continue
|
||||||
|
output.speak(_(u"Ready"))
|
||||||
|
|
||||||
|
if run_streams == True:
|
||||||
|
self.get_home()
|
||||||
|
self.get_tls()
|
||||||
|
self.check_streams = RepeatingTimer(config.main["general"]["time_to_check_streams"], self.check_stream_up)
|
||||||
|
self.check_streams.start()
|
||||||
|
# If all it's done, then play a nice sound saying that all it's OK.
|
||||||
|
sound.player.play("ready.ogg")
|
||||||
|
|
||||||
|
def remove_list(self, id):
|
||||||
|
for i in range(0, self.nb.GetPageCount()):
|
||||||
|
if self.nb.GetPage(i).type == "list":
|
||||||
|
if self.nb.GetPage(i).argumento == id:
|
||||||
|
pos = self.nb.GetCurrentPage().remove_invalid_buffer()
|
||||||
|
if pos != None:
|
||||||
|
self.nb.DeletePage(pos)
|
||||||
|
self.onMemberAdded()
|
||||||
|
|
||||||
|
def onMemberAdded(self, ev):
|
||||||
|
self.stream2.disconnect()
|
||||||
|
del self.stream2
|
||||||
|
self.get_tls()
|
||||||
|
|
||||||
|
def get_fav_buffers(self):
|
||||||
|
for i in config.main["other_buffers"]["favourites_timelines"]:
|
||||||
|
num = self.nb.GetPage(self.db.settings["buffers"].index(i+"favs")).start_streams()
|
||||||
|
if num > 0: output.speak(_(u"%s favourites from %s") % (nun, i))
|
||||||
|
|
||||||
|
def setup_twitter(self, panel):
|
||||||
|
""" Setting up the connection for twitter, or authenticate if the config file has valid credentials."""
|
||||||
|
# try:
|
||||||
|
self.twitter.login(self.user_key, self.user_secret)
|
||||||
|
self.logging_in_twblue(panel)
|
||||||
|
log.info("Authorized in Twitter.")
|
||||||
|
del self.user_key; del self.user_secret
|
||||||
|
# except:
|
||||||
|
# dlg1 = wx.MessageDialog(panel, _(u"Connection error. Try again later."), _(u"Error!"), wx.ICON_ERROR)
|
||||||
|
# dlg1.ShowModal()
|
||||||
|
# self.Close(True)
|
||||||
|
|
||||||
|
def get_home(self):
|
||||||
|
""" Gets the home stream, that manages home timeline, mentions, direct messages and sent."""
|
||||||
|
try:
|
||||||
|
self.stream = twitter.buffers.stream.streamer(application.app_key, application.app_secret, config.main["twitter"]["user_key"], config.main["twitter"]["user_secret"], parent=self)
|
||||||
|
call_threaded(self.stream.user)
|
||||||
|
except:
|
||||||
|
self.stream.disconnect()
|
||||||
|
|
||||||
|
def start_lists(self):
|
||||||
|
for i in range(0, self.nb.GetPageCount()):
|
||||||
|
if self.nb.GetPage(i).type == "list": self.nb.GetPage(i).retrieve_ids()
|
||||||
|
|
||||||
|
def get_tls(self):
|
||||||
|
""" Setting the stream for individual user timelines."""
|
||||||
|
try:
|
||||||
|
self.stream2 = twitter.buffers.indibidual.streamer(application.app_key, application.app_secret, config.main["twitter"]["user_key"], config.main["twitter"]["user_secret"], parent=self)
|
||||||
|
# The self.ids contains all IDS for the follow argument of the stream.
|
||||||
|
ids = ""
|
||||||
|
# If we have more than 0 items on a list, then.
|
||||||
|
for i in config.main["other_buffers"]["timelines"]:
|
||||||
|
ids = ids+self.db.settings[i][0]["user"]["id_str"]+", "
|
||||||
|
for i in range(0, self.nb.GetPageCount()):
|
||||||
|
if self.nb.GetPage(i).type == "list":
|
||||||
|
for z in self.nb.GetPage(i).users:
|
||||||
|
ids+= str(z)+", "
|
||||||
|
if ids != "":
|
||||||
|
# try:
|
||||||
|
call_threaded(self.stream2.statuses.filter, follow=ids)
|
||||||
|
# except:
|
||||||
|
# pass
|
||||||
|
except:
|
||||||
|
self.stream2.disconnect()
|
||||||
|
|
||||||
|
def check_stream_up(self):
|
||||||
|
try:
|
||||||
|
urllib2.urlopen("http://74.125.228.231", timeout=5)
|
||||||
|
except urllib2.URLError:
|
||||||
|
if self.stream.connected == True: self.stream.disconnect()
|
||||||
|
if hasattr(self, "stream2") and self.stream2.connected: self.stream2.disconnect()
|
||||||
|
if config.main["general"]["announce_stream_status"] == True: output.speak(_(u"Streams disconnected. TW Blue will try to reconnect in a minute."))
|
||||||
|
return
|
||||||
|
if self.stream.connected == False:
|
||||||
|
del self.stream
|
||||||
|
if config.main["general"]["announce_stream_status"] == True: output.speak(_(u"Reconnecting streams..."))
|
||||||
|
call_threaded(self.init)
|
||||||
|
self.get_home()
|
||||||
|
if hasattr(self, "stream2") and self.stream2.connected == False:
|
||||||
|
log.debug("Trying reconnects the timelines stream...")
|
||||||
|
del self.stream2
|
||||||
|
self.get_tls()
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
def edit_keystrokes(self, ev=None):
|
||||||
|
if hasattr(self, "keyboard_handler"):
|
||||||
|
dlg = keystrokeEditorGUI.keystrokeEditor(parent=self, keyboard_handler=self.keyboard_handler)
|
||||||
|
else:
|
||||||
|
dlg = keystrokeEditorGUI.keystrokeEditor(parent=self)
|
||||||
|
dlg.ShowModal()
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def search(self, ev=None):
|
||||||
|
dlg = dialogs.search.searchDialog()
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
term = dlg.term.GetValue()
|
||||||
|
if dlg.tweets.GetValue() == True:
|
||||||
|
search =buffers.searchPanel(self.nb, self, "%s-search" % (term,), q=term, count=100)
|
||||||
|
self.nb.InsertSubPage(self.db.settings["buffers"].index("searches"), search, _(u"search for %s") % (term,))
|
||||||
|
self.db.settings["buffers"].append("%s-search" % (term,))
|
||||||
|
config.main["other_buffers"]["tweet_searches"].append(term)
|
||||||
|
elif dlg.users.GetValue() == True:
|
||||||
|
search =buffers.searchUsersPanel(self.nb, self, "%s_search" % (term,), q=term, count=20)
|
||||||
|
self.nb.InsertSubPage(self.db.settings["buffers"].index("searches"), search, _(u"search users for %s") % (term,))
|
||||||
|
self.db.settings["buffers"].append("%s_search" % (term,))
|
||||||
|
timer = RepeatingTimer(180, search.load_search)
|
||||||
|
timer.start()
|
||||||
|
num = search.start_streams()
|
||||||
|
search.put_items(num)
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def learn_sounds(self, ev):
|
||||||
|
SoundsTutorial.gui.soundsTutorial().ShowModal()
|
||||||
|
|
||||||
|
def view_user_lists(self, ev=None):
|
||||||
|
userDlg = dialogs.utils.selectUserDialog(parent=self, title=_(u"Select the user"))
|
||||||
|
if userDlg.ShowModal() == wx.ID_OK:
|
||||||
|
user = userDlg.cb.GetValue()
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
dlg = dialogs.lists.userListViewer(self, user)
|
||||||
|
dlg.ShowModal()
|
||||||
|
userDlg.Destroy()
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def add_to_list(self, ev=None):
|
||||||
|
userDlg = dialogs.utils.selectUserDialog(parent=self, title=_(u"Select the user"))
|
||||||
|
if userDlg.ShowModal() == wx.ID_OK:
|
||||||
|
user = userDlg.cb.GetValue()
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
dlg = dialogs.lists.addUserListDialog(self)
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
try:
|
||||||
|
list = self.twitter.twitter.add_list_member(list_id=self.db.settings["lists"][dlg.lista.get_selected()]["id"], screen_name=user)
|
||||||
|
older_list = twitter.utils.find_item(self.db.settings["lists"][dlg.lista.get_selected()]["id"], self.db.settings["lists"])
|
||||||
|
if list["mode"] == "private":
|
||||||
|
self.db.settings["lists"].pop(older_list)
|
||||||
|
self.db.settings["lists"].append(list)
|
||||||
|
except TwythonError as e:
|
||||||
|
output.speak("error %s: %s" % (e.error_code, e.msg))
|
||||||
|
userDlg.Destroy()
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def remove_from_list(self, ev=None):
|
||||||
|
userDlg = dialogs.utils.selectUserDialog(parent=self, title=_(u"Select the user"))
|
||||||
|
if userDlg.ShowModal() == wx.ID_OK:
|
||||||
|
user = userDlg.cb.GetValue()
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
dlg = dialogs.lists.removeUserListDialog(self)
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
try:
|
||||||
|
list = self.twitter.twitter.delete_list_member(list_id=self.db.settings["lists"][dlg.get_selected()]["id"], screen_name=user)
|
||||||
|
older_list = twitter.utils.find_item(self.db.settings["lists"][dlg.get_selected()]["id"], self.db.settings["lists"])
|
||||||
|
if list["mode"] == "private":
|
||||||
|
self.db.settings["lists"].pop(older_list)
|
||||||
|
self.db.settings["lists"].append(list)
|
||||||
|
except TwythonError as e:
|
||||||
|
output.speak("error %s: %s" % (e.error_code, e.msg))
|
||||||
|
userDlg.Destroy()
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def list_manager(self, ev):
|
||||||
|
dlg = dialogs.lists.listViewer(self)
|
||||||
|
dlg.ShowModal()
|
||||||
|
self.stream2.disconnect()
|
||||||
|
del self.stream2
|
||||||
|
self.get_tls()
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def onInit(self, ev):
|
||||||
|
if self.nb.GetPage(ev.GetItem()[0]).type != "search" or self.nb.GetPage(ev.GetItem()[0]).type != "favourites_timeline": self.nb.GetPage(ev.GetItem()[0]).put_items(ev.GetItem()[1])
|
||||||
|
|
||||||
|
def preferences(self, ev=None):
|
||||||
|
dlg = dialogs.configuration.configurationDialog(self)
|
||||||
|
dlg.ShowModal()
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def update_profile(self, ev=None):
|
||||||
|
dialogs.update_profile.updateProfile(self).ShowModal()
|
||||||
|
|
||||||
|
def onManual(self, ev):
|
||||||
|
lang = localization.get("documentation")
|
||||||
|
os.chdir("documentation/%s" % (lang,))
|
||||||
|
webbrowser.open("manual.html")
|
||||||
|
os.chdir("../../")
|
||||||
|
|
||||||
|
def onChangelog(self, ev):
|
||||||
|
lang = localization.get("documentation")
|
||||||
|
os.chdir("documentation/%s" % (lang,))
|
||||||
|
webbrowser.open("changes.html")
|
||||||
|
os.chdir("../../")
|
||||||
|
|
||||||
|
def onVisit_website(self, ev):
|
||||||
|
webbrowser.open("http://twblue.com.mx")
|
||||||
|
|
||||||
|
def onReportBug(self, ev):
|
||||||
|
issueReporterGUI.reportBug(self.db.settings["user_name"]).ShowModal()
|
||||||
|
|
||||||
|
def onCheckForUpdates(self, ev):
|
||||||
|
updater.update_manager.check_for_update(msg=True)
|
||||||
|
|
||||||
|
def details(self, ev=None):
|
||||||
|
""" This function shows details for the selected user."""
|
||||||
|
dlg = dialogs.utils.selectUserDialog(parent=self, title=_(u"User details"))
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
dialogs.show_user.showUserProfile(self.twitter, dlg.cb.GetValue()).ShowModal()
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def delete(self, ev=None):
|
||||||
|
""" Deleting a tweet or direct message."""
|
||||||
|
if self.nb.GetCurrentPage().name_buffer != "followers" and self.nb.GetCurrentPage() != "friends" and self.nb.GetCurrentPage().name_buffer != "events":
|
||||||
|
dlg = wx.MessageDialog(self, _(u"Do you really want to delete this message? It will be eliminated from Twitter as well."), _(u"Delete"), wx.ICON_QUESTION|wx.YES_NO)
|
||||||
|
if dlg.ShowModal() == wx.ID_YES:
|
||||||
|
self.nb.GetCurrentPage().destroy_status(wx.EVT_MENU)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
def onPageChanged(self, ev):
|
||||||
|
""" Announces the new title for the tab."""
|
||||||
|
if platform.system() == "Darwin":
|
||||||
|
output.speak(self.nb.GetPageText(self.nb.GetSelection())+",", True)
|
||||||
|
|
||||||
|
def skip_blank_pages(self, forward=True):
|
||||||
|
if self.nb.GetCurrentPage().type == "account" or self.nb.GetCurrentPage().type == "empty" and (self.showing == False or platform.system() == "Darwin"):
|
||||||
|
self.nb.AdvanceSelection(forward)
|
||||||
|
|
||||||
|
def close(self, ev=None):
|
||||||
|
# if ev == None or hasattr(ev, "GetLoggingOff") == False:
|
||||||
|
dlg = wx.MessageDialog(self, _(u"Do you really want to close TW Blue?"), _(u"Exit"), wx.YES_NO|wx.ICON_QUESTION)
|
||||||
|
if dlg.ShowModal() == wx.ID_YES:
|
||||||
|
self.exit()
|
||||||
|
dlg.Destroy()
|
||||||
|
# elif hasattr(ev, "GetLoggingOff") == True:
|
||||||
|
# self.exit()
|
||||||
|
|
||||||
|
def exit(self, event=None):
|
||||||
|
config.main.write()
|
||||||
|
log.debug("Exiting...")
|
||||||
|
try:
|
||||||
|
self.check_streams.cancel()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
sound.player.cleaner.cancel()
|
||||||
|
try:
|
||||||
|
self.stream.disconnect()
|
||||||
|
log.debug("Stream disconnected.")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self.stream2.disconnect()
|
||||||
|
log.debug(u"Timelines stream disconnected.")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
wx.GetApp().ExitMainLoop()
|
||||||
|
|
||||||
|
def onFollow(self, ev=None):
|
||||||
|
""" Opens the follow dialog."""
|
||||||
|
dialogs.follow.follow(self.nb.GetCurrentPage(), "follow").ShowModal()
|
||||||
|
|
||||||
|
def onUnfollow(self, ev=None):
|
||||||
|
""" Opens the unfollow dialog."""
|
||||||
|
dialogs.follow.follow(self.nb.GetCurrentPage(), "unfollow").ShowModal()
|
||||||
|
|
||||||
|
def onMute(self, ev=None):
|
||||||
|
""" Opens the unfollow dialog."""
|
||||||
|
dialogs.follow.follow(self.nb.GetCurrentPage(), "mute").ShowModal()
|
||||||
|
|
||||||
|
def onUnmute(self, ev=None):
|
||||||
|
""" Opens the unfollow dialog."""
|
||||||
|
dialogs.follow.follow(self.nb.GetCurrentPage(), "unmute").ShowModal()
|
||||||
|
|
||||||
|
def onReport(self, ev=None):
|
||||||
|
""" Opens the report dialog, to report as spam to the specified user."""
|
||||||
|
dialogs.follow.follow(self.nb.GetCurrentPage(), "report").ShowModal()
|
||||||
|
|
||||||
|
def onBlock(self, ev=None):
|
||||||
|
""" Opens the "block" dialog, to block the user that you want."""
|
||||||
|
dialogs.follow.follow(self.nb.GetCurrentPage(), "block").ShowModal()
|
||||||
|
|
||||||
|
def onUnblock(self, ev=None):
|
||||||
|
""" Opens the "block" dialog, to block the user that you want."""
|
||||||
|
dialogs.follow.follow(self.nb.GetCurrentPage(), "unblock").ShowModal()
|
||||||
|
|
||||||
|
def action(self, ev=None):
|
||||||
|
dialogs.follow.follow(self.nb.GetCurrentPage()).ShowModal()
|
||||||
|
|
||||||
|
def compose(self, ev=None):
|
||||||
|
""" Opens the new tweet dialog."""
|
||||||
|
self.nb.GetCurrentPage().post_status(ev)
|
||||||
|
|
||||||
|
def reply(self, ev=None):
|
||||||
|
""" Opens the response dialog."""
|
||||||
|
self.nb.GetCurrentPage().onResponse(ev)
|
||||||
|
|
||||||
|
def dm(self, ev=None):
|
||||||
|
""" Opens the DM Dialog."""
|
||||||
|
# The direct_messages buffer has a method to post a diret messages while the other tabs does has not it.
|
||||||
|
if self.nb.GetCurrentPage().name_buffer == "direct_messages":
|
||||||
|
self.nb.GetCurrentPage().onResponse(ev)
|
||||||
|
elif self.nb.GetCurrentPage().name_buffer == "events": return
|
||||||
|
else:
|
||||||
|
# dialogs.message.dm(_(u"Direct message to %s ") % (self.db.settings[self.nb.GetCurrentPage().name_buffer][self.nb.GetCurrentPage().get_selected()]["user"]["screen_name"]), "", "", self.nb.GetCurrentPage()).ShowModal()
|
||||||
|
self.nb.GetCurrentPage().onDm(ev)
|
||||||
|
|
||||||
|
def retweet(self, ev=None):
|
||||||
|
if self.nb.GetCurrentPage().name_buffer != "direct_messages" and self.nb.GetCurrentPage().name_buffer != "followers" and self.nb.GetCurrentPage().name_buffer != "friends" and self.nb.GetCurrentPage().name_buffer != "events":
|
||||||
|
self.nb.GetCurrentPage().onRetweet(ev)
|
||||||
|
|
||||||
|
def view(self, ev=None):
|
||||||
|
tweet = self.nb.GetCurrentPage().get_message(dialog=True)
|
||||||
|
dialogs.message.viewTweet(tweet).ShowModal()
|
||||||
|
|
||||||
|
def fav(self, ev=None):
|
||||||
|
if self.nb.GetCurrentPage().name_buffer != "direct_messages" and self.nb.GetCurrentPage().name_buffer != "followers" and self.nb.GetCurrentPage().name_buffer != "friends":
|
||||||
|
try:
|
||||||
|
self.twitter.twitter.create_favorite(id=self.db.settings[self.nb.GetCurrentPage().name_buffer][self.nb.GetCurrentPage().list.get_selected()]["id"])
|
||||||
|
sound.player.play("favourite.ogg")
|
||||||
|
except TwythonError as e:
|
||||||
|
output.speak(_(u"Error while adding to favourites."), True)
|
||||||
|
sound.player.play("error.ogg")
|
||||||
|
|
||||||
|
def unfav(self, ev=None):
|
||||||
|
if self.nb.GetCurrentPage().name_buffer != "direct_messages" and self.nb.GetCurrentPage().name_buffer != "followers" and self.nb.GetCurrentPage().name_buffer != "friends":
|
||||||
|
try:
|
||||||
|
self.twitter.twitter.destroy_favorite(id=self.db.settings[self.nb.GetCurrentPage().name_buffer][self.nb.GetCurrentPage().list.get_selected()]["id"])
|
||||||
|
except TwythonError as e:
|
||||||
|
output.speak(_(u"Error while removing from favourites."), True)
|
||||||
|
sound.player.play("error.ogg")
|
||||||
|
|
||||||
|
def open_timeline(self, ev=None):
|
||||||
|
dlg = dialogs.utils.selectUserDialog(self, _(u"Individual timeline"))
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
user = twitter.utils.if_user_exists(self.twitter.twitter, dlg.cb.GetValue())
|
||||||
|
if user == None:
|
||||||
|
wx.MessageDialog(None, _(u"The user does not exist"), _(u"Error"), wx.ICON_ERROR).ShowModal()
|
||||||
|
dlg.Destroy()
|
||||||
|
return
|
||||||
|
if user not in config.main["other_buffers"]["timelines"]:
|
||||||
|
config.main["other_buffers"]["timelines"].append(user)
|
||||||
|
else:
|
||||||
|
wx.MessageDialog(None, _(u"There's currently a timeline for this user. You are not able to open another"), _(u"Existing timeline"), wx.ICON_ERROR).ShowModal()
|
||||||
|
dlg.Destroy()
|
||||||
|
return
|
||||||
|
sound.player.play("create_timeline.ogg")
|
||||||
|
st = buffers.basePanel(self.nb, self, user, self.twitter.twitter.get_user_timeline, argumento=user, sound="ready.ogg", timeline=True)
|
||||||
|
num = st.start_streams()
|
||||||
|
self.db.settings["buffers"].append(user)
|
||||||
|
if num == 0:
|
||||||
|
wx.MessageDialog(None, _(u"This user has no tweets. You can't open a timeline for this user"), _(u"Error!"), wx.ICON_ERROR).ShowModal()
|
||||||
|
self.db.settings.pop(user)
|
||||||
|
# self.nb.DeletePage(self.db.settings["buffers"].index(user))
|
||||||
|
self.db.settings["buffers"].remove(user)
|
||||||
|
else:
|
||||||
|
self.nb.InsertSubPage(self.db.settings["buffers"].index("timelines"), st, _(u"Timeline for %s") % (user))
|
||||||
|
st.put_items(num)
|
||||||
|
st.sound = "tweet_timeline.ogg"
|
||||||
|
self.stream2.disconnect()
|
||||||
|
del self.stream2
|
||||||
|
self.get_tls()
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def favs_timeline(self, ev=None):
|
||||||
|
dlg = dialogs.utils.selectUserDialog(self, _(u"List of favourites"))
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
user = twitter.utils.if_user_exists(self.twitter.twitter, dlg.cb.GetValue())
|
||||||
|
if user == None:
|
||||||
|
wx.MessageDialog(None, _(u"The user does not exist"), _(u"Error"), wx.ICON_ERROR).ShowModal()
|
||||||
|
dlg.Destroy()
|
||||||
|
return
|
||||||
|
if user not in config.main["other_buffers"]["favourites_timelines"]:
|
||||||
|
config.main["other_buffers"]["favourites_timelines"].append(user)
|
||||||
|
else:
|
||||||
|
wx.MessageDialog(None, _(u"There's already a list of favourites for this user. You can't create another."), _(u"Existing list"), wx.ICON_ERROR).ShowModal()
|
||||||
|
dlg.Destroy()
|
||||||
|
return
|
||||||
|
sound.player.play("create_timeline.ogg")
|
||||||
|
st = buffers.favsPanel(self.nb, self, user+"-favs", argumento=user, sound="favourites_timeline_updated.ogg")
|
||||||
|
self.nb.InsertSubPage(self.db.settings["buffers"].index("favourites_timelines"), st, _(u"Favourites for %s") % (user))
|
||||||
|
num = st.start_streams()
|
||||||
|
self.db.settings["buffers"].append(user+"-favs")
|
||||||
|
if num == 0:
|
||||||
|
wx.MessageDialog(None, _(u"This user has no favourites. You can't create a list of favourites for this user."), _(u"Error!"), wx.ICON_ERROR).ShowModal()
|
||||||
|
self.db.settings.pop(user+"-favs")
|
||||||
|
self.nb.DeletePage(self.db.settings["buffers"].index(user+"-favs"))
|
||||||
|
self.db.settings["buffers"].remove(user+"-favs")
|
||||||
|
st.put_items(num)
|
||||||
|
dlg.Destroy()
|
||||||
|
|
||||||
|
def onAbout(self, ev=None):
|
||||||
|
info = wx.AboutDialogInfo()
|
||||||
|
info.SetName(application.name)
|
||||||
|
info.SetVersion(application.version)
|
||||||
|
info.SetDescription(application.description)
|
||||||
|
info.SetCopyright(application.copyright)
|
||||||
|
info.SetTranslators(application.translators)
|
||||||
|
# info.SetLicence(application.licence)
|
||||||
|
info.AddDeveloper(application.author)
|
||||||
|
wx.AboutBox(info)
|
||||||
|
|
||||||
|
def delete_buffer(self, ev=None):
|
||||||
|
pos = self.nb.GetCurrentPage().remove_buffer()
|
||||||
|
if pos != None:
|
||||||
|
self.stream2.disconnect()
|
||||||
|
del self.stream2
|
||||||
|
self.nb.DeletePage(self.nb.GetSelection())
|
||||||
|
sound.player.play("delete_timeline.ogg")
|
||||||
|
self.get_tls()
|
||||||
|
|
||||||
|
def delete_invalid_timeline(self):
|
||||||
|
pos = self.nb.GetCurrentPage().remove_invalid_buffer()
|
||||||
|
if pos != None:
|
||||||
|
self.nb.DeletePage(self.nb.GetSelection())
|
||||||
|
|
||||||
|
### Hidden Window
|
||||||
|
def left(self, event=None):
|
||||||
|
num = self.nb.GetSelection()
|
||||||
|
if num == 0:
|
||||||
|
self.nb.ChangeSelection(self.nb.GetPageCount()-1)
|
||||||
|
else:
|
||||||
|
self.nb.SetSelection(num-1)
|
||||||
|
while self.nb.GetCurrentPage().type == "account" or self.nb.GetCurrentPage().type == "empty": self.skip_blank_pages(False)
|
||||||
|
try:
|
||||||
|
msg = _(u"%s, %s of %s") % (self.nb.GetPageText(self.nb.GetSelection()), self.nb.GetCurrentPage().list.get_selected()+1, self.nb.GetCurrentPage().list.get_count())
|
||||||
|
except:
|
||||||
|
msg = _(u"%s. Empty") % (self.nb.GetPageText(self.nb.GetSelection()))
|
||||||
|
output.speak(msg, 1)
|
||||||
|
|
||||||
|
def right(self, event=None):
|
||||||
|
num = self.nb.GetSelection()
|
||||||
|
if num+1 == self.nb.GetPageCount():
|
||||||
|
self.nb.SetSelection(0)
|
||||||
|
else:
|
||||||
|
self.nb.SetSelection(num+1)
|
||||||
|
while self.nb.GetCurrentPage().type == "account" or self.nb.GetCurrentPage().type == "empty": self.skip_blank_pages(True)
|
||||||
|
try:
|
||||||
|
msg = _(u"%s, %s of %s") % (self.nb.GetPageText(self.nb.GetSelection()), self.nb.GetCurrentPage().list.get_selected()+1, self.nb.GetCurrentPage().list.get_count())
|
||||||
|
except:
|
||||||
|
msg = _(u"%s. Empty") % (self.nb.GetPageText(self.nb.GetSelection()))
|
||||||
|
output.speak(msg, 1)
|
||||||
|
|
||||||
|
def show_hide(self, ev=None):
|
||||||
|
# if platform.system() == "Linux" or platform.system() == "Darwin": return
|
||||||
|
keymap = {}
|
||||||
|
for i in config.main["keymap"]:
|
||||||
|
if hasattr(self, i):
|
||||||
|
keymap[config.main["keymap"][i]] = getattr(self, i)
|
||||||
|
if self.showing == True:
|
||||||
|
self.keyboard_handler = WXKeyboardHandler(self)
|
||||||
|
self.keyboard_handler.register_keys(keymap)
|
||||||
|
self.Hide()
|
||||||
|
self.showing = False
|
||||||
|
else:
|
||||||
|
self.keyboard_handler.unregister_keys(keymap)
|
||||||
|
del self.keyboard_handler
|
||||||
|
self.Show()
|
||||||
|
self.showing = True
|
||||||
|
|
||||||
|
def toggle_global_mute(self, event=None):
|
||||||
|
if config.main["sound"]["global_mute"] == False:
|
||||||
|
config.main["sound"]["global_mute"] = True
|
||||||
|
output.speak(_(u"Global mute on"))
|
||||||
|
elif config.main["sound"]["global_mute"] == True:
|
||||||
|
config.main["sound"]["global_mute"] = False
|
||||||
|
output.speak(_(u"Global mute off"))
|
||||||
|
|
||||||
|
def toggle_mute(self, event=None):
|
||||||
|
buffer = self.nb.GetCurrentPage().name_buffer
|
||||||
|
if buffer not in config.main["other_buffers"]["muted_buffers"]:
|
||||||
|
config.main["other_buffers"]["muted_buffers"].append(buffer)
|
||||||
|
output.speak(_(u"Buffer mute on"))
|
||||||
|
elif buffer in config.main["other_buffers"]["muted_buffers"]:
|
||||||
|
config.main["other_buffers"]["muted_buffers"].remove(buffer)
|
||||||
|
output.speak(_(u"Buffer mute off"))
|
||||||
|
|
||||||
|
def toggle_autoread(self, event=None):
|
||||||
|
buffer = self.nb.GetCurrentPage().name_buffer
|
||||||
|
if buffer not in config.main["other_buffers"]["autoread_buffers"]:
|
||||||
|
config.main["other_buffers"]["autoread_buffers"].append(buffer)
|
||||||
|
output.speak(_(u"The auto-reading of new tweets is enabled for this buffer"))
|
||||||
|
elif buffer in config.main["other_buffers"]["autoread_buffers"]:
|
||||||
|
config.main["other_buffers"]["autoread_buffers"].remove(buffer)
|
||||||
|
output.speak(_(u"The auto-reading of new tweets is disabled for this buffer"))
|
||||||
|
|
||||||
|
def repeat_item(self):
|
||||||
|
output.speak(self.nb.GetCurrentPage().get_message(), 1)
|
||||||
|
|
||||||
|
def copy_to_clipboard(self, event=None):
|
||||||
|
output.Copy(self.nb.GetCurrentPage().get_message())
|
||||||
|
output.speak(_(u"Copied"))
|
||||||
|
|
||||||
|
def clear_list(self, event=None):
|
||||||
|
self.nb.GetCurrentPage().interact("clear_list")
|
||||||
|
|
||||||
|
def conversation_up(self, evt=None):
|
||||||
|
if config.main["general"]["reverse_timelines"] == True and evt == None:
|
||||||
|
self.conversation_down("down")
|
||||||
|
return
|
||||||
|
id = self.db.settings[self.nb.GetCurrentPage().name_buffer][self.nb.GetCurrentPage().list.get_selected()]["in_reply_to_status_id_str"]
|
||||||
|
pos = twitter.utils.find_previous_reply(id, self.db.settings["home_timeline"])
|
||||||
|
if pos != None:
|
||||||
|
self.nb.ChangeSelection(1)
|
||||||
|
self.nb.GetCurrentPage().list.select_item(pos)
|
||||||
|
msg = _(u"%s") % (self.nb.GetCurrentPage().get_message())
|
||||||
|
output.speak(msg)
|
||||||
|
|
||||||
|
def conversation_down(self, evt=None):
|
||||||
|
if config.main["general"]["reverse_timelines"] == True and evt == None:
|
||||||
|
self.conversation_up("up")
|
||||||
|
return
|
||||||
|
id = self.db.settings[self.nb.GetCurrentPage().name_buffer][self.nb.GetCurrentPage().list.get_selected()]["id_str"]
|
||||||
|
# pos = twitter.utils.find_next_reply(id, self.db.settings["home_timeline"])
|
||||||
|
pos = twitter.utils.find_next_reply(id, self.db.settings["home_timeline"])
|
||||||
|
if pos != None:
|
||||||
|
self.nb.ChangeSelection(1)
|
||||||
|
self.nb.GetCurrentPage().list.select_item(pos)
|
||||||
|
msg = _(u"%s") % (self.nb.GetCurrentPage().get_message())
|
||||||
|
output.speak(msg)
|
||||||
|
|
||||||
|
def go_home(self):
|
||||||
|
self.nb.GetCurrentPage().list.select_item(0)
|
||||||
|
try:
|
||||||
|
output.speak(self.nb.GetCurrentPage().get_message(), 1)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def go_end(self):
|
||||||
|
self.nb.GetCurrentPage().list.select_item(self.nb.GetCurrentPage().list.get_count()-1)
|
||||||
|
try:
|
||||||
|
output.speak(self.nb.GetCurrentPage().get_message(), 1)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def go_page_up(self):
|
||||||
|
if self.nb.GetCurrentPage().list.get_selected <= 20:
|
||||||
|
index = 0
|
||||||
|
else:
|
||||||
|
index = self.nb.GetCurrentPage().list.get_selected() - 20
|
||||||
|
self.nb.GetCurrentPage().list.select_item(index)
|
||||||
|
try:
|
||||||
|
output.speak(self.nb.GetCurrentPage().get_message(), 1)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def go_page_down(self):
|
||||||
|
if self.nb.GetCurrentPage().list.get_selected() >= self.nb.GetCurrentPage().list.get_count() - 20:
|
||||||
|
index = self.nb.GetCurrentPage().list.get_count()-1
|
||||||
|
else:
|
||||||
|
index = self.nb.GetCurrentPage().list.get_selected() + 20
|
||||||
|
self.nb.GetCurrentPage().list.select_item(index)
|
||||||
|
try:
|
||||||
|
output.speak(self.nb.GetCurrentPage().get_message(), 1)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def volume_up(self):
|
||||||
|
self.nb.GetCurrentPage().interact("volume_up")
|
||||||
|
|
||||||
|
def volume_down(self):
|
||||||
|
self.nb.GetCurrentPage().interact("volume_down")
|
||||||
|
|
||||||
|
def url(self):
|
||||||
|
self.nb.GetCurrentPage().interact("url")
|
||||||
|
|
||||||
|
def audio(self):
|
||||||
|
self.nb.GetCurrentPage().interact("audio")
|
||||||
|
|
||||||
|
def up(self, event=None):
|
||||||
|
pos = self.nb.GetCurrentPage().list.get_selected()
|
||||||
|
index = self.nb.GetCurrentPage().list.get_selected()-1
|
||||||
|
try:
|
||||||
|
self.nb.GetCurrentPage().list.select_item(index)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if pos == self.nb.GetCurrentPage().list.get_selected():
|
||||||
|
sound.player.play("limit.ogg", False)
|
||||||
|
try:
|
||||||
|
output.speak(self.nb.GetCurrentPage().get_message(), 1)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def down(self, event=None):
|
||||||
|
index = self.nb.GetCurrentPage().list.get_selected()+1
|
||||||
|
pos = self.nb.GetCurrentPage().list.get_selected()
|
||||||
|
try:
|
||||||
|
self.nb.GetCurrentPage().list.select_item(index)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if pos == self.nb.GetCurrentPage().list.get_selected():
|
||||||
|
sound.player.play("limit.ogg", False)
|
||||||
|
try:
|
||||||
|
output.speak(self.nb.GetCurrentPage().get_message(), 1)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_more_items(self):
|
||||||
|
self.nb.GetCurrentPage().get_more_items()
|
||||||
|
|
||||||
|
def connect_streams(self):
|
||||||
|
disconnect = False
|
||||||
|
if self.stream.connected == False:
|
||||||
|
output.speak(_(u"Trying to reconnect the main stream"))
|
||||||
|
disconnect = True
|
||||||
|
del self.stream
|
||||||
|
call_threaded(self.init)
|
||||||
|
self.get_home()
|
||||||
|
if hasattr(self, "stream2") and self.stream2.connected == False:
|
||||||
|
output.speak(_(u"Trying to reconnect the buffers stream"))
|
||||||
|
disconnect = True
|
||||||
|
log.debug("Trying reconnects the timelines stream...")
|
||||||
|
del self.stream2
|
||||||
|
self.get_tls()
|
||||||
|
if disconnect == False:
|
||||||
|
output.speak(_(u"All streams are working properly"))
|
||||||
|
|
||||||
|
### Close App
|
||||||
|
def Destroy(self):
|
||||||
|
self.sysTray.Destroy()
|
||||||
|
super(mainFrame, self).Destroy()
|
62
src/gui/sysTrayIcon.py
Normal file
62
src/gui/sysTrayIcon.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
""" A systray for TW Blue """
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2014 José Manuel Delicado Alcolea <jmdaweb@gmail.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
import wx
|
||||||
|
import application
|
||||||
|
import paths
|
||||||
|
import os
|
||||||
|
|
||||||
|
class SysTrayIcon(wx.TaskBarIcon):
|
||||||
|
|
||||||
|
def __init__(self, frame):
|
||||||
|
super(SysTrayIcon, self).__init__()
|
||||||
|
self.frame=frame
|
||||||
|
icon=wx.Icon(os.path.join(paths.app_path(), "icon.ico"), wx.BITMAP_TYPE_ICO)
|
||||||
|
self.SetIcon(icon, application.name)
|
||||||
|
self.menu=wx.Menu()
|
||||||
|
item=self.menu.Append(wx.ID_ANY, _(u"Tweet"))
|
||||||
|
self.Bind(wx.EVT_MENU, frame.compose, item)
|
||||||
|
item=self.menu.Append(wx.ID_ANY, _(u"Preferences"))
|
||||||
|
self.Bind(wx.EVT_MENU, frame.preferences, item)
|
||||||
|
item=self.menu.Append(wx.ID_ANY, _(u"Update profile"))
|
||||||
|
self.Bind(wx.EVT_MENU, frame.update_profile, item)
|
||||||
|
item=self.menu.Append(wx.ID_ANY, _(u"Show / hide"))
|
||||||
|
self.Bind(wx.EVT_MENU, frame.show_hide, item)
|
||||||
|
item=self.menu.Append(wx.ID_ANY, _(u"Documentation"))
|
||||||
|
self.Bind(wx.EVT_MENU, frame.onManual, item)
|
||||||
|
item=self.menu.Append(wx.ID_ANY, _(u"Check for updates"))
|
||||||
|
self.Bind(wx.EVT_MENU, frame.onCheckForUpdates, item)
|
||||||
|
item=self.menu.Append(wx.ID_ANY, _(u"Exit"))
|
||||||
|
self.Bind(wx.EVT_MENU, frame.close, item)
|
||||||
|
self.Bind(wx.EVT_TASKBAR_RIGHT_DOWN, self.onRightClick)
|
||||||
|
self.Bind(wx.EVT_TASKBAR_LEFT_DOWN, self.onLeftClick)
|
||||||
|
|
||||||
|
def onRightClick(self, evt):
|
||||||
|
self.PopupMenu(self.menu)
|
||||||
|
|
||||||
|
def onLeftClick(self, evt):
|
||||||
|
if (self.frame.showing):
|
||||||
|
self.frame.SetFocus()
|
||||||
|
else:
|
||||||
|
self.frame.onShow_hide()
|
||||||
|
|
||||||
|
def Destroy(self):
|
||||||
|
self.menu.Destroy()
|
||||||
|
super(SysTrayIcon, self).Destroy()
|
BIN
src/icon.ico
Normal file
BIN
src/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 182 KiB |
0
src/issueReporter/__init__.py
Normal file
0
src/issueReporter/__init__.py
Normal file
21
src/issueReporter/constants.py
Normal file
21
src/issueReporter/constants.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
categories = ["General"]
|
||||||
|
reproducibilities = ["always", "sometimes", "random", "have not tried", "unable to duplicate"]
|
||||||
|
severities = ["block", "crash", "major", "minor", "tweak", "text", "trivial", "feature"]
|
32
src/issueReporter/get_logs.py
Normal file
32
src/issueReporter/get_logs.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import paths
|
||||||
|
import os
|
||||||
|
|
||||||
|
def get_logs_files():
|
||||||
|
files = {}
|
||||||
|
for i in os.listdir(paths.logs_path()):
|
||||||
|
if i == "debug.log": continue
|
||||||
|
f = open(paths.logs_path(i), "r")
|
||||||
|
files[i] = f.readlines()
|
||||||
|
f.close()
|
||||||
|
try: os.remove(paths.logs_path("tracebacks.log"))
|
||||||
|
except: pass
|
||||||
|
return files
|
||||||
|
|
124
src/issueReporter/gui.py
Normal file
124
src/issueReporter/gui.py
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
############################################################
|
||||||
|
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
import wx
|
||||||
|
import application
|
||||||
|
from suds.client import Client
|
||||||
|
import constants
|
||||||
|
|
||||||
|
class reportBug(wx.Dialog):
|
||||||
|
def __init__(self, user_name):
|
||||||
|
self.user = "informador"
|
||||||
|
self.user_name = user_name
|
||||||
|
self.password = "contrasena"
|
||||||
|
self.url = application.report_bugs_url
|
||||||
|
self.categories = [_(u"General")]
|
||||||
|
self.reproducibilities = [_(u"always"), _(u"sometimes"), _(u"random"), _(u"have not tried"), _(u"unable to duplicate")]
|
||||||
|
self.severities = [_(u"block"), _(u"crash"), _(u"major"), _(u"minor"), _(u"tweak"), _(u"text"), _(u"trivial"), _(u"feature")]
|
||||||
|
wx.Dialog.__init__(self, None, -1)
|
||||||
|
self.SetTitle(_(u"Report an error"))
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
categoryLabel = wx.StaticText(panel, -1, _(u"Select a category"), size=wx.DefaultSize)
|
||||||
|
self.category = wx.ComboBox(panel, -1, choices=self.categories, style=wx.CB_READONLY)
|
||||||
|
self.category.SetSize(self.category.GetBestSize())
|
||||||
|
self.category.SetSelection(0)
|
||||||
|
categoryB = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
categoryB.Add(categoryLabel, 0, wx.ALL, 5)
|
||||||
|
categoryB.Add(self.category, 0, wx.ALL, 5)
|
||||||
|
self.category.SetFocus()
|
||||||
|
sizer.Add(categoryB, 0, wx.ALL, 5)
|
||||||
|
summaryLabel = wx.StaticText(panel, -1, _(u"Briefly describe what happened. You will be able to thoroughly explain it later"), size=wx.DefaultSize)
|
||||||
|
self.summary = wx.TextCtrl(panel, -1)
|
||||||
|
dc = wx.WindowDC(self.summary)
|
||||||
|
dc.SetFont(self.summary.GetFont())
|
||||||
|
self.summary.SetSize(dc.GetTextExtent("a"*80))
|
||||||
|
# self.summary.SetFocus()
|
||||||
|
summaryB = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
summaryB.Add(summaryLabel, 0, wx.ALL, 5)
|
||||||
|
summaryB.Add(self.summary, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(summaryB, 0, wx.ALL, 5)
|
||||||
|
descriptionLabel = wx.StaticText(panel, -1, _(u"Here, you can describe the bug in detail"), size=wx.DefaultSize)
|
||||||
|
self.description = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE)
|
||||||
|
dc = wx.WindowDC(self.description)
|
||||||
|
dc.SetFont(self.description.GetFont())
|
||||||
|
(x, y, z) = dc.GetMultiLineTextExtent("0"*2000)
|
||||||
|
self.description.SetSize((x, y))
|
||||||
|
descBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
descBox.Add(descriptionLabel, 0, wx.ALL, 5)
|
||||||
|
descBox.Add(self.description, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(descBox, 0, wx.ALL, 5)
|
||||||
|
reproducibilityLabel = wx.StaticText(panel, -1, _(u"how often does this bug happen?"), size=wx.DefaultSize)
|
||||||
|
self.reproducibility = wx.ComboBox(panel, -1, choices=self.reproducibilities, style=wx.CB_READONLY)
|
||||||
|
self.reproducibility.SetSelection(3)
|
||||||
|
self.reproducibility.SetSize(self.reproducibility.GetBestSize())
|
||||||
|
reprB = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
reprB.Add(reproducibilityLabel, 0, wx.ALL, 5)
|
||||||
|
reprB.Add(self.reproducibility, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(reprB, 0, wx.ALL, 5)
|
||||||
|
severityLabel = wx.StaticText(panel, -1, _(u"Select the importance that you think this bug has"))
|
||||||
|
self.severity = wx.ComboBox(panel, -1, choices=self.severities, style=wx.CB_READONLY)
|
||||||
|
self.severity.SetSize(self.severity.GetBestSize())
|
||||||
|
self.severity.SetSelection(3)
|
||||||
|
severityB = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
severityB.Add(severityLabel, 0, wx.ALL, 5)
|
||||||
|
severityB.Add(self.severity, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(severityB, 0, wx.ALL, 5)
|
||||||
|
self.agree = wx.CheckBox(panel, -1, _(u"I know that the TW Blue bug system will get my Twitter username to contact me and fix the bug quickly"))
|
||||||
|
self.agree.SetValue(False)
|
||||||
|
sizer.Add(self.agree, 0, wx.ALL, 5)
|
||||||
|
ok = wx.Button(panel, wx.ID_OK, _(u"Send report"))
|
||||||
|
ok.Bind(wx.EVT_BUTTON, self.onSend)
|
||||||
|
ok.SetDefault()
|
||||||
|
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
|
||||||
|
btnBox = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
btnBox.Add(ok, 0, wx.ALL, 5)
|
||||||
|
btnBox.Add(cancel, 0, wx.ALL, 5)
|
||||||
|
sizer.Add(btnBox, 0, wx.ALL, 5)
|
||||||
|
panel.SetSizer(sizer)
|
||||||
|
self.SetClientSize(sizer.CalcMin())
|
||||||
|
|
||||||
|
def onSend(self, ev):
|
||||||
|
if self.summary.GetValue() == "" or self.description.GetValue() == "":
|
||||||
|
wx.MessageDialog(self, _(u"You must fill out both fields"), _(u"Error"), wx.OK|wx.ICON_ERROR).ShowModal()
|
||||||
|
return
|
||||||
|
if self.agree.GetValue() == False:
|
||||||
|
wx.MessageDialog(self, _(u"You need to mark the checkbox to provide us your twitter username to contact to you if is necessary."), _(u"Error"), wx.ICON_ERROR).ShowModal()
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
client = Client(self.url)
|
||||||
|
issue = client.factory.create('IssueData')
|
||||||
|
issue.project.name = "TW Blue"
|
||||||
|
issue.project.id = 0
|
||||||
|
issue.summary = self.summary.GetValue(),
|
||||||
|
issue.description = "Reported by @%s\n\n" % (self.user_name) + self.description.GetValue()
|
||||||
|
issue.category = constants.categories[self.category.GetSelection()]
|
||||||
|
issue.reproducibility.name = constants.reproducibilities[self.reproducibility.GetSelection()]
|
||||||
|
issue.severity.name = constants.severities[self.severity.GetSelection()]
|
||||||
|
issue.priority.name = "normal"
|
||||||
|
issue.view_state.name = "public"
|
||||||
|
issue.resolution.name = "open"
|
||||||
|
issue.projection.name = "none"
|
||||||
|
issue.eta.name = "eta"
|
||||||
|
issue.status.name = "new"
|
||||||
|
id = client.service.mc_issue_add(self.user, self.password, issue)
|
||||||
|
wx.MessageDialog(self, _(u"Thanks for reporting this bug! In future versions, you may be able to find it in the changes list. You've reported the bug number %i") % (id), _(u"reported"), wx.OK).ShowModal()
|
||||||
|
self.EndModal(wx.ID_OK)
|
||||||
|
except:
|
||||||
|
wx.MessageDialog(self, _(u"Something unexpected occurred while trying to report the bug. Please, try again later"), _(u"Error while reporting"), wx.ICON_ERROR|wx.OK).ShowModal()
|
||||||
|
self.EndModal(wx.ID_CANCEL)
|
3
src/keyboard_handler/__init__.py
Normal file
3
src/keyboard_handler/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from main import KeyboardHandler, KeyboardHandlerError
|
||||||
|
#from wx_handler import WXKeyboardHandler
|
||||||
|
__all__ = ["KeyboardHandler", "KeyboardHandlerError", "WXKeyboardHandler", "WXPanelKeyboardHandler"]
|
7
src/keyboard_handler/global_handler.py
Normal file
7
src/keyboard_handler/global_handler.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import platform
|
||||||
|
if platform.system() == 'Linux':
|
||||||
|
from linux import LinuxKeyboardHandler as GlobalKeyboardHandler
|
||||||
|
elif platform.system() == 'Windows':
|
||||||
|
from wx_handler import WXKeyboardHandler as GlobalKeyboardHandler
|
||||||
|
elif platform.system() == 'Darwin':
|
||||||
|
from osx import OSXKeyboardHandler as GlobalKeyboardHandler
|
58
src/keyboard_handler/linux.py
Normal file
58
src/keyboard_handler/linux.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from main import KeyboardHandler
|
||||||
|
import threading
|
||||||
|
import thread
|
||||||
|
import pyatspi
|
||||||
|
def parse(s):
|
||||||
|
"""parse a string like control+f into (modifier, key).
|
||||||
|
Unknown modifiers will return ValueError."""
|
||||||
|
m = 0
|
||||||
|
lst = s.split('+')
|
||||||
|
if not len(lst): return (0, s)
|
||||||
|
#Are these right?
|
||||||
|
d = {
|
||||||
|
"shift": 1<<pyatspi.MODIFIER_SHIFT,
|
||||||
|
"control": 1<<pyatspi.MODIFIER_CONTROL,
|
||||||
|
"alt": 1<<pyatspi.MODIFIER_ALT,
|
||||||
|
"win":1<<pyatspi.MODIFIER_META3,
|
||||||
|
}
|
||||||
|
for item in lst:
|
||||||
|
if item in d:
|
||||||
|
m|=d[item]
|
||||||
|
lst.remove(item)
|
||||||
|
#end if
|
||||||
|
if len(lst) > 1: #more than one key, parse error
|
||||||
|
raise ValueError, 'unknown modifier %s' % lst[0]
|
||||||
|
return (m, lst[0].lower())
|
||||||
|
class AtspiThread(threading.Thread):
|
||||||
|
def run(self):
|
||||||
|
pyatspi.Registry.registerKeystrokeListener(handler, kind=(pyatspi.KEY_PRESSED_EVENT,),
|
||||||
|
mask=pyatspi.allModifiers())
|
||||||
|
pyatspi.Registry.start()
|
||||||
|
#the keys we registered
|
||||||
|
keys = {}
|
||||||
|
def handler(e):
|
||||||
|
m,k = e.modifiers,e.event_string.lower()
|
||||||
|
#not sure why we can't catch control+f. Try to fix it.
|
||||||
|
if (not e.is_text) and e.id >= 97 <= 126:
|
||||||
|
k = chr(e.id)
|
||||||
|
if (m,k) not in keys: return False
|
||||||
|
thread.start_new(keys[(m,k)], ())
|
||||||
|
return True #don't pass it on
|
||||||
|
class LinuxKeyboardHandler(KeyboardHandler):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
KeyboardHandler.__init__(self, *args, **kwargs)
|
||||||
|
t = AtspiThread()
|
||||||
|
t.start()
|
||||||
|
def register_key(self, key, function):
|
||||||
|
"""key will be a string, such as control+shift+f.
|
||||||
|
We need to convert that, using parse_key,
|
||||||
|
into modifier and key to put into our dictionary."""
|
||||||
|
#register key so we know if we have it on event receive.
|
||||||
|
t = parse(key)
|
||||||
|
keys[t] = function
|
||||||
|
#if we got this far, the key is valid.
|
||||||
|
KeyboardHandler.register_key(self, key, function)
|
||||||
|
|
||||||
|
def unregister_key (self, key, function):
|
||||||
|
KeyboardHandler.unregister_key(self, key, function)
|
||||||
|
del keys[parse(key)]
|
88
src/keyboard_handler/main.py
Normal file
88
src/keyboard_handler/main.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import platform
|
||||||
|
import time
|
||||||
|
|
||||||
|
class KeyboardHandlerError (Exception): pass
|
||||||
|
|
||||||
|
class KeyboardHandler(object):
|
||||||
|
|
||||||
|
def __init__(self, repeat_rate=0.0, *args, **kwargs):
|
||||||
|
self.repeat_rate = repeat_rate #How long between accepting the same keystroke?
|
||||||
|
self._last_key = None
|
||||||
|
self._last_keypress_time = 0
|
||||||
|
super(KeyboardHandler, self).__init__(*args, **kwargs)
|
||||||
|
self.active_keys = {}
|
||||||
|
if not hasattr(self, 'replacement_mods'):
|
||||||
|
self.replacement_mods = {}
|
||||||
|
if not hasattr(self, 'replacement_keys'):
|
||||||
|
self.replacement_keys = {}
|
||||||
|
|
||||||
|
def register_key (self, key, function):
|
||||||
|
if key in self.active_keys:
|
||||||
|
raise KeyboardHandlerError, "Key %s is already registered to a function" % key
|
||||||
|
if not callable(function):
|
||||||
|
raise TypeError, "Must provide a callable to be invoked upon keypress"
|
||||||
|
self.active_keys[key] = function
|
||||||
|
|
||||||
|
def unregister_key (self, key, function):
|
||||||
|
try:
|
||||||
|
if self.active_keys[key] != function:
|
||||||
|
raise KeyboardHandlerError, "key %s is not registered to that function" % key
|
||||||
|
except KeyError:
|
||||||
|
raise KeyboardHandlerError, "Key %s not currently registered"
|
||||||
|
del(self.active_keys[key])
|
||||||
|
|
||||||
|
def unregister_all_keys(self):
|
||||||
|
for key in list(self.active_keys):
|
||||||
|
self.unregister_key(key, self.active_keys[key])
|
||||||
|
|
||||||
|
def handle_key (self, key):
|
||||||
|
if self.repeat_rate and key == self._last_key and time.time() - self._last_keypress_time < self.repeat_rate:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
function = self.active_keys[key]
|
||||||
|
except KeyError:
|
||||||
|
return
|
||||||
|
self._last_key = key
|
||||||
|
self._last_keypress_time = time.time()
|
||||||
|
return function()
|
||||||
|
|
||||||
|
def register_keys(self, keys):
|
||||||
|
"""Given a mapping of keystrokes to functions, registers all keystrokes"""
|
||||||
|
for k in keys:
|
||||||
|
self.register_key(k, keys[k])
|
||||||
|
|
||||||
|
def unregister_keys(self, keys):
|
||||||
|
"""Given a mapping of keys to their functions, unregisters all provided keys."""
|
||||||
|
for k in keys:
|
||||||
|
self.unregister_key(k, keys[k])
|
||||||
|
|
||||||
|
def standardize_key(self, key):
|
||||||
|
"""Takes a keystroke and places it in a standard case and order in a list."""
|
||||||
|
working = key.split('+')
|
||||||
|
working = [i.lower() for i in working]
|
||||||
|
answer = []
|
||||||
|
if "control" in working:
|
||||||
|
answer.append("control")
|
||||||
|
if "win" in working:
|
||||||
|
answer.append("win")
|
||||||
|
if "alt" in working:
|
||||||
|
answer.append("alt")
|
||||||
|
if "shift" in working:
|
||||||
|
answer.append("shift")
|
||||||
|
if working[-1] not in answer:
|
||||||
|
answer.append(working[-1])
|
||||||
|
return answer
|
||||||
|
|
||||||
|
def standardize_keymap(self, keymap):
|
||||||
|
"""Given a keymap, returns the keymap standardized."""
|
||||||
|
full = {}
|
||||||
|
for i in keymap:
|
||||||
|
answer = ""
|
||||||
|
new = self.standardize_key(keymap[i])
|
||||||
|
for (c, j) in enumerate(new):
|
||||||
|
if c < len(new)-1:
|
||||||
|
answer = "%s%s+" % (answer, j)
|
||||||
|
else:
|
||||||
|
answer = "%s%s" % (answer, j)
|
||||||
|
full[i] = answer
|
||||||
|
return full
|
56
src/keyboard_handler/osx.py
Normal file
56
src/keyboard_handler/osx.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from AppKit import *
|
||||||
|
from PyObjCTools import AppHelper
|
||||||
|
from Carbon.CarbonEvt import RegisterEventHotKey, GetApplicationEventTarget
|
||||||
|
from Carbon.Events import cmdKey, controlKey
|
||||||
|
import struct
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
from main import KeyboardHandler
|
||||||
|
|
||||||
|
kEventHotKeyPressedSubtype = 6
|
||||||
|
kEventHotKeyReleasedSubtype = 9
|
||||||
|
|
||||||
|
class OSXKeyboardHandler(KeyboardHandler):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(OSXKeyboardHandler, self).__init__()
|
||||||
|
self.replacement_keys = dict()
|
||||||
|
self.app = KeyboardCapturingNSApplication.alloc().init()
|
||||||
|
self._event_thread = Thread(target=AppHelper.runEventLoop)
|
||||||
|
self._event_thread.start()
|
||||||
|
|
||||||
|
def register_key (self, key, function):
|
||||||
|
super(OSXKeyboardHandler, self).register_key(key, function)
|
||||||
|
k, m = self.parse_key(key)
|
||||||
|
key_id = RegisterEventHotKey(k, m, (0, 0), GetApplicationEventTarget(), 0)
|
||||||
|
self.key_ids[key] = key_id
|
||||||
|
|
||||||
|
def unregister_key (self, key, function):
|
||||||
|
super(OSXKeyboardHandler, self).unregister_key(key, function)
|
||||||
|
key_id = self.key_ids[key]
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def parse_key (self, key):
|
||||||
|
key=key.split("+")
|
||||||
|
#replacements
|
||||||
|
#Modifier keys:
|
||||||
|
for index, item in enumerate(key[0:-1]):
|
||||||
|
if self.replacement_mods.has_key(item):
|
||||||
|
key[index] = self.replacement_mods[item]
|
||||||
|
if self.replacement_keys.has_key(key[-1]):
|
||||||
|
key[-1] = self.replacement_keys[key[-1]]
|
||||||
|
elif len(key[-1])==1:
|
||||||
|
key[-1] = ord(str(key[-1]))-36
|
||||||
|
mods = 0
|
||||||
|
for i in key[:-1]:
|
||||||
|
mods = mods|i
|
||||||
|
return [key[-1], mods]
|
||||||
|
|
||||||
|
class KeyboardCapturingNSApplication(NSApplication):
|
||||||
|
|
||||||
|
def sendEvent_(self, theEvent):
|
||||||
|
if theEvent.type() == NSSystemDefined and theEvent.subtype() == kEventHotKeyPressedSubtype:
|
||||||
|
self.activateIgnoringOtherApps_(True)
|
||||||
|
NSRunAlertPanel(u'Hot Key Pressed', u'Hot Key Pressed', None, None, None)
|
||||||
|
super(NSApplication, self).sendEvent_(theEvent)
|
||||||
|
|
40
src/keyboard_handler/windows.py
Normal file
40
src/keyboard_handler/windows.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import win32api
|
||||||
|
import win32con
|
||||||
|
|
||||||
|
from main import KeyboardHandler
|
||||||
|
|
||||||
|
class WindowsKeyboardHandler(KeyboardHandler):
|
||||||
|
|
||||||
|
def __init__ (self, *args, **kwargs):
|
||||||
|
super(WindowsKeyboardHandler, self).__init__(*args, **kwargs)
|
||||||
|
#Setup the replacement dictionaries.
|
||||||
|
for i in dir(win32con):
|
||||||
|
if i.startswith("VK_"):
|
||||||
|
key = i[3:].lower()
|
||||||
|
self.replacement_keys[key] = getattr(win32con, i)
|
||||||
|
elif i.startswith("MOD_"):
|
||||||
|
key = i[4:].lower()
|
||||||
|
self.replacement_mods[key] = getattr(win32con, i)
|
||||||
|
self.replacement_keys .update(dict(pageup=win32con.VK_PRIOR, pagedown=win32con.VK_NEXT))
|
||||||
|
|
||||||
|
def parse_key (self, keystroke, separator="+"):
|
||||||
|
keystroke = str(keystroke) #We don't want unicode
|
||||||
|
keystroke = [self.keycode_from_key(i) for i in keystroke.split(separator)]
|
||||||
|
mods = 0
|
||||||
|
for i in keystroke[:-1]:
|
||||||
|
mods = mods | i #or everything together
|
||||||
|
return (mods, keystroke[-1])
|
||||||
|
|
||||||
|
def keycode_from_key(self, key):
|
||||||
|
if key in self.replacement_mods:
|
||||||
|
return self.replacement_mods[key]
|
||||||
|
if key in self.replacement_keys:
|
||||||
|
return self.replacement_keys[key]
|
||||||
|
if len(key) == 1:
|
||||||
|
return win32api.VkKeyScanEx(key, win32api.GetKeyboardLayout())
|
||||||
|
|
||||||
|
def is_key_pressed(self, key):
|
||||||
|
"""Returns if the given key was pressed. Requires an active message loop or will simply give if the key was pressed recently."""
|
||||||
|
key = self.keycode_from_key(key)
|
||||||
|
return win32api.GetAsyncKeyState(key)
|
||||||
|
|
119
src/keyboard_handler/wx_handler.py
Normal file
119
src/keyboard_handler/wx_handler.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import functools
|
||||||
|
import wx
|
||||||
|
import platform
|
||||||
|
from main import KeyboardHandler
|
||||||
|
|
||||||
|
__all__ = ['WXKeyboardHandler', 'WXControlKeyboardHandler']
|
||||||
|
|
||||||
|
|
||||||
|
def call_after(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
wx.CallAfter(func, *args, **kwargs)
|
||||||
|
functools.update_wrapper(wrapper, func)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class BaseWXKeyboardHandler(KeyboardHandler):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(BaseWXKeyboardHandler, self).__init__(*args, **kwargs)
|
||||||
|
#Setup the replacement dictionaries.
|
||||||
|
for i in dir(wx):
|
||||||
|
if i.startswith('WXK_'):
|
||||||
|
key = i[4:].lower()
|
||||||
|
self.replacement_keys[key] = getattr(wx, i)
|
||||||
|
elif i.startswith('MOD_'):
|
||||||
|
key = i[4:].lower()
|
||||||
|
self.replacement_mods[key] = getattr(wx, i)
|
||||||
|
|
||||||
|
def parse_key (self, keystroke, separator="+"):
|
||||||
|
keystroke = [self.keycode_from_key(i) for i in keystroke.split(separator)]
|
||||||
|
mods = 0
|
||||||
|
for i in keystroke[:-1]:
|
||||||
|
mods = mods | i #or everything together
|
||||||
|
return (mods, keystroke[-1])
|
||||||
|
|
||||||
|
def keycode_from_key(self, key):
|
||||||
|
if key in self.replacement_mods:
|
||||||
|
result = self.replacement_mods[key]
|
||||||
|
elif key in self.replacement_keys:
|
||||||
|
result = self.replacement_keys[key]
|
||||||
|
if result >= 277:
|
||||||
|
result -= 277
|
||||||
|
elif len(key) == 1:
|
||||||
|
result = ord(key.upper())
|
||||||
|
print "result: ", result
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#try:
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
from windows import WindowsKeyboardHandler as keyboard_handler
|
||||||
|
elif platform.system() == "Linux":
|
||||||
|
from linux import LinuxKeyboardHandler as keyboard_handler
|
||||||
|
elif platform.system() == "Darwin":
|
||||||
|
from osx import OSXKeyboardHandler as keyboard_handler
|
||||||
|
|
||||||
|
class WXKeyboardHandler(keyboard_handler):
|
||||||
|
def __init__ (self, parent, *args, **kwargs):
|
||||||
|
super(WXKeyboardHandler, self).__init__(*args, **kwargs)
|
||||||
|
self.parent = parent
|
||||||
|
self.key_ids = {}
|
||||||
|
|
||||||
|
@call_after
|
||||||
|
def register_key(self, key, function):
|
||||||
|
super(WXKeyboardHandler, self).register_key(key, function)
|
||||||
|
key_id = wx.NewId()
|
||||||
|
parsed = self.parse_key(key)
|
||||||
|
self.parent.RegisterHotKey(key_id, *parsed)
|
||||||
|
self.parent.Bind(wx.EVT_HOTKEY, lambda evt: self.process_key(evt, key_id), id=key_id)
|
||||||
|
self.key_ids[key] = key_id
|
||||||
|
|
||||||
|
@call_after
|
||||||
|
def unregister_key (self, key, function):
|
||||||
|
super(WXKeyboardHandler, self).unregister_key(key, function)
|
||||||
|
if key not in self.key_ids:
|
||||||
|
return #there's nothing we can do.
|
||||||
|
key_id = self.key_ids[key]
|
||||||
|
self.parent.UnregisterHotKey(key_id)
|
||||||
|
self.parent.Unbind( wx.EVT_HOTKEY, id=key_id)
|
||||||
|
self.key_ids.pop(key)
|
||||||
|
|
||||||
|
def process_key (self, evt, id):
|
||||||
|
evt.Skip()
|
||||||
|
key_ids = self.key_ids.keys()
|
||||||
|
for i in key_ids:
|
||||||
|
if self.key_ids.get(i) == id:
|
||||||
|
self.handle_key(i)
|
||||||
|
|
||||||
|
class WXControlKeyboardHandler(wx.StaticText, KeyboardHandler):
|
||||||
|
|
||||||
|
def __init__(self, parent=None, *a, **k):
|
||||||
|
wx.StaticText.__init__(self, parent=parent)
|
||||||
|
KeyboardHandler.__init__(self, *a, **k)
|
||||||
|
self.wx_replacements = {}
|
||||||
|
for i in [d for d in dir(wx) if d.startswith('WXK_')]:
|
||||||
|
self.wx_replacements[getattr(wx, i)] = i[4:].lower()
|
||||||
|
self.Bind(wx.EVT_KEY_DOWN, self.process_key, self)
|
||||||
|
self.SetFocus()
|
||||||
|
|
||||||
|
def process_key(self, evt):
|
||||||
|
keycode = evt.GetKeyCode()
|
||||||
|
keyname = self.wx_replacements.get(keycode, None)
|
||||||
|
modifiers = ""
|
||||||
|
replacements = ( (evt.ControlDown(), 'control+'),
|
||||||
|
(evt.AltDown(), 'alt+'),
|
||||||
|
(evt.ShiftDown(), 'shift+'),
|
||||||
|
(evt.MetaDown(), 'win+')
|
||||||
|
)
|
||||||
|
for mod, ch in (replacements):
|
||||||
|
if mod:
|
||||||
|
modifiers += ch
|
||||||
|
if keyname is None:
|
||||||
|
if 27 < keycode < 256:
|
||||||
|
keyname = chr(keycode).lower()
|
||||||
|
else:
|
||||||
|
keyname = "(%s)unknown" % keycode
|
||||||
|
key = modifiers + keyname
|
||||||
|
self.handle_key(key)
|
0
src/keystrokeEditor/__init__.py
Normal file
0
src/keystrokeEditor/__init__.py
Normal file
44
src/keystrokeEditor/constants.py
Normal file
44
src/keystrokeEditor/constants.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
actions = {
|
||||||
|
"up": _(u"Go up up on the current list"),
|
||||||
|
"down": _(u"Go down up on the current list"),
|
||||||
|
"left": _(u"Go to the previous tab"),
|
||||||
|
"right": _(u"Go to the next tab"),
|
||||||
|
"conversation_up": _(u"Move up one tweet in the conversation"),
|
||||||
|
"conversation_down": _(u"Move down one tweet in the conversation"),
|
||||||
|
"show_hide": _(u"Show the graphical interface"),
|
||||||
|
"compose": _(u"New tweet"),
|
||||||
|
"reply": _(u"Reply to a tweet"),
|
||||||
|
"retweet": _(u"Retweet"),
|
||||||
|
"dm": _(u"Send direct message"),
|
||||||
|
"fav": _(u"Mark as favourite"),
|
||||||
|
"unfav": _(u"Remove from favourites"),
|
||||||
|
"action": _(u"Open the actions dialogue"),
|
||||||
|
"details": _(u"See user details"),
|
||||||
|
"view": _(u"Show tweet"),
|
||||||
|
"close": _(u"Quit"),
|
||||||
|
"open_timeline": _(u"Open user timeline"),
|
||||||
|
"delete_buffer": _(u"Remove buffer"),
|
||||||
|
"url": _(u"Open URL on the current tweet, or further information for a friend or follower"),
|
||||||
|
"audio": _(u"Attempt to play audio"),
|
||||||
|
"volume_up": _(u"Increase volume by 5%"),
|
||||||
|
"volume_down": _(u"Decrease volume by 5%"),
|
||||||
|
"go_home": _(u"Go to the first element on the list"),
|
||||||
|
"go_end": _(u"Go to the last element on the list"),
|
||||||
|
"go_page_up": _(u"Move 20 elements up on the current list"),
|
||||||
|
"go_page_down": _(u"Move 20 elements down on the current list"),
|
||||||
|
"update_profile": _(u"Edit profile"),
|
||||||
|
"delete": _(u"Remove a tweet or direct message"),
|
||||||
|
"clear_list": _(u"Empty the buffer removing all the elements"),
|
||||||
|
"repeat_item": _(u"Listen the current message"),
|
||||||
|
"copy_to_clipboard": _(u"Copy to clipboard"),
|
||||||
|
"add_to_list": _(u"Add to list"),
|
||||||
|
"remove_from_list": _(u"Remove from list"),
|
||||||
|
"toggle_mute": _(u"Mutes/unmutes the active buffer"),
|
||||||
|
"toggle_global_mute": _(u"Globally mute/unmute TW Blue"),
|
||||||
|
"toggle_autoread": _(u"toggles the automatic reading of incoming tweets in the active buffer"),
|
||||||
|
"search": _(u"Search on twitter"),
|
||||||
|
"edit_keystrokes": _(u"Shows the keystroke editor"),
|
||||||
|
"view_user_lists": _(u"Show lists for a specified user"),
|
||||||
|
}
|
119
src/keystrokeEditor/gui.py
Normal file
119
src/keystrokeEditor/gui.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import config
|
||||||
|
import wx
|
||||||
|
import constants
|
||||||
|
from multiplatform_widgets import widgets
|
||||||
|
from constants import actions
|
||||||
|
|
||||||
|
class keystrokeEditor(wx.Dialog):
|
||||||
|
def __init__(self, parent=None, keyboard_handler=None):
|
||||||
|
super(keystrokeEditor, self).__init__(parent=parent, id=-1, title=_(u"Keystroke editor"))
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
self.parent = parent
|
||||||
|
self.keyboard_handler = keyboard_handler or None
|
||||||
|
self.actions = []
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
keysText = wx.StaticText(panel, -1, _(u"Select a keystroke to edit"))
|
||||||
|
self.keys = widgets.list(self, _(u"Action"), _(u"Keystroke"), style=wx.LC_REPORT|wx.LC_SINGLE_SEL, size=(400, 450))
|
||||||
|
self.keys.list.SetFocus()
|
||||||
|
firstSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
firstSizer.Add(keysText)
|
||||||
|
firstSizer.Add(self.keys.list)
|
||||||
|
edit = wx.Button(panel, -1, _(u"Edit"))
|
||||||
|
self.Bind(wx.EVT_BUTTON, self.edit, edit)
|
||||||
|
edit.SetDefault()
|
||||||
|
|
||||||
|
close = wx.Button(panel, wx.ID_CANCEL, _(u"Close"))
|
||||||
|
secondSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
secondSizer.Add(edit)
|
||||||
|
secondSizer.Add(close)
|
||||||
|
sizer.Add(firstSizer)
|
||||||
|
sizer.Add(secondSizer)
|
||||||
|
panel.SetSizerAndFit(sizer)
|
||||||
|
self.put_keystrokes()
|
||||||
|
|
||||||
|
def put_keystrokes(self):
|
||||||
|
for i in config.main["keymap"]:
|
||||||
|
action = actions[i]
|
||||||
|
self.actions.append(i)
|
||||||
|
keystroke = config.main["keymap"][i]
|
||||||
|
self.keys.insert_item(False, *[action, keystroke])
|
||||||
|
|
||||||
|
def edit(self, ev):
|
||||||
|
action = self.actions[self.keys.get_selected()]
|
||||||
|
dlg = editKeystroke(self.parent, action, config.main["keymap"][action], self.keyboard_handler)
|
||||||
|
if dlg.ShowModal() == wx.ID_OK:
|
||||||
|
pos = self.keys.get_selected()
|
||||||
|
self.keys.clear()
|
||||||
|
self.put_keystrokes()
|
||||||
|
self.keys.select_item(pos)
|
||||||
|
# dlg.Destroy()
|
||||||
|
|
||||||
|
class editKeystroke(wx.Dialog):
|
||||||
|
def __init__(self, parent, action, keystroke, keyboard_handler):
|
||||||
|
super(editKeystroke, self).__init__(parent=None, id=-1, title=_(u"Editing keystroke"))
|
||||||
|
self.parent = parent
|
||||||
|
self.keyboard_handler = keyboard_handler
|
||||||
|
self.action = action
|
||||||
|
self.keystroke = keystroke
|
||||||
|
panel = wx.Panel(self)
|
||||||
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||||
|
self.control = wx.CheckBox(panel, -1, _(u"Control"))
|
||||||
|
self.alt = wx.CheckBox(panel, -1, _(u"Alt"))
|
||||||
|
self.shift = wx.CheckBox(panel, -1, _(u"Shift"))
|
||||||
|
self.win = wx.CheckBox(panel, -1, _(u"Windows"))
|
||||||
|
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
sizer1.Add(self.control)
|
||||||
|
sizer1.Add(self.alt)
|
||||||
|
sizer1.Add(self.shift)
|
||||||
|
sizer1.Add(self.win)
|
||||||
|
charLabel = wx.StaticText(panel, -1, _(u"Key"))
|
||||||
|
self.key = wx.TextCtrl(panel, -1)
|
||||||
|
# self.key.SetMaxLength(1)
|
||||||
|
sizer2 = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
sizer2.Add(charLabel)
|
||||||
|
sizer2.Add(self.key)
|
||||||
|
ok = wx.Button(panel, wx.ID_OK, _(u"OK"))
|
||||||
|
ok.SetDefault()
|
||||||
|
self.Bind(wx.EVT_BUTTON, self.ok, ok)
|
||||||
|
cancel = wx.Button(panel, wx.ID_CANCEL)
|
||||||
|
sizer3 = wx.BoxSizer(wx.HORIZONTAL)
|
||||||
|
sizer3.Add(ok)
|
||||||
|
sizer3.Add(cancel)
|
||||||
|
sizer.Add(sizer1)
|
||||||
|
sizer.Add(sizer2)
|
||||||
|
sizer.Add(sizer3)
|
||||||
|
panel.SetSizerAndFit(sizer)
|
||||||
|
self.set_default()
|
||||||
|
|
||||||
|
def set_default(self):
|
||||||
|
for i in self.keystroke.split("+"):
|
||||||
|
if hasattr(self, i):
|
||||||
|
key = getattr(self, i)
|
||||||
|
key.SetValue(True)
|
||||||
|
self.key.SetValue(self.keystroke.split("+")[-1])
|
||||||
|
|
||||||
|
def ok(self, ev):
|
||||||
|
keys = []
|
||||||
|
if self.win.GetValue() == False:
|
||||||
|
wx.MessageDialog(self, _(u"You need to use the Windows key"), _(u"Invalid keystroke"), wx.OK|wx.ICON_ERROR).ShowModal()
|
||||||
|
return
|
||||||
|
if self.control.GetValue() == True:
|
||||||
|
keys.append("control")
|
||||||
|
if self.win.GetValue() == True:
|
||||||
|
keys.append("win")
|
||||||
|
if self.alt.GetValue() == True:
|
||||||
|
keys.append("alt")
|
||||||
|
if self.shift.GetValue() == True:
|
||||||
|
keys.append("shift")
|
||||||
|
if self.key.GetValue() != "":
|
||||||
|
keys.append(self.key.GetValue())
|
||||||
|
else:
|
||||||
|
wx.MessageDialog(self, _(u"You must provide a character for the keystroke"), _(u"Invalid keystroke"), wx.ICON_ERROR).ShowModal()
|
||||||
|
return
|
||||||
|
config.main["keymap"][self.action] = "+".join(keys)
|
||||||
|
if self.keyboard_handler != None:
|
||||||
|
self.keyboard_handler.unregister_key(self.keystroke, getattr(self.parent, self.action))
|
||||||
|
self.keyboard_handler.register_key(config.main["keymap"][self.action], getattr(self.parent, self.action))
|
||||||
|
|
||||||
|
self.EndModal(wx.ID_OK)
|
180
src/languageHandler.py
Normal file
180
src/languageHandler.py
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import __builtin__
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import ctypes
|
||||||
|
import locale
|
||||||
|
import gettext
|
||||||
|
import paths
|
||||||
|
import platform
|
||||||
|
|
||||||
|
# A fix for the mac locales
|
||||||
|
#if platform.system() == 'Darwin':
|
||||||
|
|
||||||
|
#a few Windows locale constants
|
||||||
|
LOCALE_SLANGUAGE=0x2
|
||||||
|
LOCALE_SLANGDISPLAYNAME=0x6f
|
||||||
|
|
||||||
|
curLang="en"
|
||||||
|
|
||||||
|
def localeNameToWindowsLCID(localeName):
|
||||||
|
"""Retreave the Windows locale identifier (LCID) for the given locale name
|
||||||
|
@param localeName: a string of 2letterLanguage_2letterCountry or or just 2letterLanguage
|
||||||
|
@type localeName: string
|
||||||
|
@returns: a Windows LCID
|
||||||
|
@rtype: integer
|
||||||
|
"""
|
||||||
|
#Windows Vista is able to convert locale names to LCIDs
|
||||||
|
func_LocaleNameToLCID=getattr(ctypes.windll.kernel32,'LocaleNameToLCID',None)
|
||||||
|
if func_LocaleNameToLCID is not None:
|
||||||
|
localeName=localeName.replace('_','-')
|
||||||
|
LCID=func_LocaleNameToLCID(unicode(localeName),0)
|
||||||
|
else: #Windows doesn't have this functionality, manually search Python's windows_locale dictionary for the LCID
|
||||||
|
localeName=locale.normalize(localeName)
|
||||||
|
if '.' in localeName:
|
||||||
|
localeName=localeName.split('.')[0]
|
||||||
|
LCList=[x[0] for x in locale.windows_locale.iteritems() if x[1]==localeName]
|
||||||
|
if len(LCList)>0:
|
||||||
|
LCID=LCList[0]
|
||||||
|
else:
|
||||||
|
LCID=0
|
||||||
|
return LCID
|
||||||
|
|
||||||
|
def getLanguageDescription(language):
|
||||||
|
"""Finds out the description (localized full name) of a given local name"""
|
||||||
|
desc=None
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
LCID=localeNameToWindowsLCID(language)
|
||||||
|
if LCID!=0:
|
||||||
|
buf=ctypes.create_unicode_buffer(1024)
|
||||||
|
if '_' not in language:
|
||||||
|
res=ctypes.windll.kernel32.GetLocaleInfoW(LCID,LOCALE_SLANGDISPLAYNAME,buf,1024)
|
||||||
|
else:
|
||||||
|
res=0
|
||||||
|
if res==0:
|
||||||
|
res=ctypes.windll.kernel32.GetLocaleInfoW(LCID,LOCALE_SLANGUAGE,buf,1024)
|
||||||
|
desc=buf.value
|
||||||
|
elif platform.system() == "Linux" or not desc:
|
||||||
|
desc={
|
||||||
|
"am":pgettext("languageName","Amharic"),
|
||||||
|
"an":pgettext("languageName","Aragonese"),
|
||||||
|
"es":pgettext("languageName","Spanish"),
|
||||||
|
"pt":pgettext("languageName","Portuguese"),
|
||||||
|
"ru":pgettext("languageName","Russian"),
|
||||||
|
"it":pgettext("languageName","italian"),
|
||||||
|
"tr":pgettext("languageName","Turkey"),
|
||||||
|
"gl":pgettext("languageName","Galician"),
|
||||||
|
"ca":pgettext("languageName","Catala"),
|
||||||
|
"eu":pgettext("languageName","Vasque"),
|
||||||
|
"pl":pgettext("languageName","polish"),
|
||||||
|
"ar":pgettext("languageName","Arabic"),
|
||||||
|
"ne":pgettext("languageName","Nepali"),
|
||||||
|
"sr":pgettext("languageName","Serbian (Latin)"),
|
||||||
|
}.get(language,None)
|
||||||
|
return desc
|
||||||
|
|
||||||
|
def getAvailableLanguages():
|
||||||
|
"""generates a list of locale names, plus their full localized language and country names.
|
||||||
|
@rtype: list of tuples
|
||||||
|
"""
|
||||||
|
#Make a list of all the locales found in NVDA's locale dir
|
||||||
|
l=[x for x in os.listdir(paths.locale_path()) if not x.startswith('.')]
|
||||||
|
l=[x for x in l if os.path.isfile(paths.locale_path('%s/LC_MESSAGES/twblue.mo' % x))]
|
||||||
|
#Make sure that en (english) is in the list as it may not have any locale files, but is default
|
||||||
|
if 'en' not in l:
|
||||||
|
l.append('en')
|
||||||
|
l.sort()
|
||||||
|
#For each locale, ask Windows for its human readable display name
|
||||||
|
d=[]
|
||||||
|
for i in l:
|
||||||
|
desc=getLanguageDescription(i)
|
||||||
|
label="%s, %s"%(desc,i) if desc else i
|
||||||
|
d.append(label)
|
||||||
|
#include a 'user default, windows' language, which just represents the default language for this user account
|
||||||
|
l.append("system")
|
||||||
|
# Translators: the label for the Windows default NVDA interface language.
|
||||||
|
d.append(_("User default"))
|
||||||
|
#return a zipped up version of both the lists (a list with tuples of locale,label)
|
||||||
|
return zip(l,d)
|
||||||
|
|
||||||
|
def makePgettext(translations):
|
||||||
|
"""Obtaina pgettext function for use with a gettext translations instance.
|
||||||
|
pgettext is used to support message contexts,
|
||||||
|
but Python 2.7's gettext module doesn't support this,
|
||||||
|
so NVDA must provide its own implementation.
|
||||||
|
"""
|
||||||
|
if isinstance(translations, gettext.GNUTranslations):
|
||||||
|
def pgettext(context, message):
|
||||||
|
message = unicode(message)
|
||||||
|
try:
|
||||||
|
# Look up the message with its context.
|
||||||
|
return translations._catalog[u"%s\x04%s" % (context, message)]
|
||||||
|
except KeyError:
|
||||||
|
return message
|
||||||
|
else:
|
||||||
|
def pgettext(context, message):
|
||||||
|
return unicode(message)
|
||||||
|
return pgettext
|
||||||
|
|
||||||
|
def setLanguage(lang):
|
||||||
|
system = platform.system()
|
||||||
|
global curLang
|
||||||
|
try:
|
||||||
|
if lang=="system":
|
||||||
|
if system == "Windows":
|
||||||
|
windowsLCID=ctypes.windll.kernel32.GetUserDefaultUILanguage()
|
||||||
|
localeName=locale.windows_locale[windowsLCID]
|
||||||
|
elif system == "Darwin":
|
||||||
|
import Foundation
|
||||||
|
localeName = Foundation.NSLocale.currentLocale().identifier()
|
||||||
|
# trans=gettext.translation('twblue', localedir=paths.locale_path(), languages=[localeName])
|
||||||
|
# curLang=localeName
|
||||||
|
else:
|
||||||
|
localeName=locale.getdefaultlocale()[0]
|
||||||
|
trans=gettext.translation('twblue', localedir=paths.locale_path(), languages=[localeName])
|
||||||
|
curLang=localeName
|
||||||
|
|
||||||
|
else:
|
||||||
|
trans=gettext.translation("twblue", localedir=paths.locale_path(), languages=[lang])
|
||||||
|
curLang=lang
|
||||||
|
localeChanged=False
|
||||||
|
#Try setting Python's locale to lang
|
||||||
|
try:
|
||||||
|
locale.setlocale(locale.LC_ALL, lang)
|
||||||
|
localeChanged=True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if not localeChanged and '_' in lang:
|
||||||
|
#Python couldn'tsupport the language_country locale, just try language.
|
||||||
|
try:
|
||||||
|
locale.setlocale(locale.LC_ALL, lang.split('_')[0])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
#Set the windows locale for this thread (NVDA core) to this locale.
|
||||||
|
if system == "Windows":
|
||||||
|
LCID=localeNameToWindowsLCID(lang)
|
||||||
|
ctypes.windll.kernel32.SetThreadLocale(LCID)
|
||||||
|
except IOError:
|
||||||
|
trans=gettext.translation("twblue",fallback=True)
|
||||||
|
curLang="en"
|
||||||
|
trans.install(unicode=True)
|
||||||
|
# Install our pgettext function.
|
||||||
|
__builtin__.__dict__["pgettext"] = makePgettext(trans)
|
||||||
|
|
||||||
|
def getLanguage():
|
||||||
|
return curLang
|
||||||
|
|
||||||
|
def normalizeLanguage(lang):
|
||||||
|
"""
|
||||||
|
Normalizes a language-dialect string in to a standard form we can deal with.
|
||||||
|
Converts any dash to underline, and makes sure that language is lowercase and dialect is upercase.
|
||||||
|
"""
|
||||||
|
lang=lang.replace('-','_')
|
||||||
|
ld=lang.split('_')
|
||||||
|
ld[0]=ld[0].lower()
|
||||||
|
#Filter out meta languages such as x-western
|
||||||
|
if ld[0]=='x':
|
||||||
|
return None
|
||||||
|
if len(ld)>=2:
|
||||||
|
ld[1]=ld[1].upper()
|
||||||
|
return "_".join(ld)
|
||||||
|
|
7
src/libloader/__init__.py
Normal file
7
src/libloader/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from .libloader import *
|
||||||
|
|
||||||
|
__version__ = 0.1
|
||||||
|
__author__ = 'Christopher Toth <q@q-continuum.net>'
|
||||||
|
__doc__ = """
|
||||||
|
Quickly and easily load shared libraries from various platforms. Also includes a libloader.com module for loading com modules on Windows.
|
||||||
|
"""
|
21
src/libloader/com.py
Normal file
21
src/libloader/com.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
from pywintypes import com_error
|
||||||
|
from win32com.client import gencache
|
||||||
|
|
||||||
|
def prepare_gencache():
|
||||||
|
gencache.is_readonly = False
|
||||||
|
gencache.GetGeneratePath()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def load_com(*names):
|
||||||
|
result = None
|
||||||
|
for name in names:
|
||||||
|
try:
|
||||||
|
result = gencache.EnsureDispatch(name)
|
||||||
|
break
|
||||||
|
except com_error:
|
||||||
|
continue
|
||||||
|
if result is None:
|
||||||
|
raise com_error("Unable to load any of the provided com objects.")
|
||||||
|
return result
|
||||||
|
|
56
src/libloader/libloader.py
Normal file
56
src/libloader/libloader.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import ctypes
|
||||||
|
import collections
|
||||||
|
import platform
|
||||||
|
import os
|
||||||
|
|
||||||
|
TYPES = {
|
||||||
|
'Linux': {
|
||||||
|
'loader': ctypes.CDLL,
|
||||||
|
'functype': ctypes.CFUNCTYPE,
|
||||||
|
'prefix': 'lib',
|
||||||
|
'extension': '.so'
|
||||||
|
},
|
||||||
|
'Darwin': {
|
||||||
|
'loader': ctypes.CDLL,
|
||||||
|
'functype': ctypes.CFUNCTYPE,
|
||||||
|
'prefix': 'lib',
|
||||||
|
'extension': '.dylib'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
TYPES['Windows'] = {
|
||||||
|
'loader': ctypes.WinDLL,
|
||||||
|
'functype': ctypes.WINFUNCTYPE,
|
||||||
|
'prefix': "",
|
||||||
|
'extension': '.dll'
|
||||||
|
}
|
||||||
|
|
||||||
|
class LibraryLoadError(OSError): pass
|
||||||
|
|
||||||
|
def load_library(library, x86_path='.', x64_path='.', *args, **kwargs):
|
||||||
|
lib = find_library_path(library, x86_path=x86_path, x64_path=x64_path)
|
||||||
|
loaded = _do_load(lib, *args, **kwargs)
|
||||||
|
if loaded is not None:
|
||||||
|
return loaded
|
||||||
|
raise LibraryLoadError('unable to load %r. Provided library path: %r' % (library, path))
|
||||||
|
|
||||||
|
def _do_load(file, *args, **kwargs):
|
||||||
|
loader = TYPES[platform.system()]['loader']
|
||||||
|
return loader(file, *args, **kwargs)
|
||||||
|
|
||||||
|
def find_library_path(libname, x86_path='.', x64_path='.'):
|
||||||
|
libname = '%s%s' % (TYPES[platform.system()]['prefix'], libname)
|
||||||
|
if platform.architecture()[0] == '64bit':
|
||||||
|
path = os.path.join(x64_path, libname)
|
||||||
|
else:
|
||||||
|
path = os.path.join(x86_path, libname)
|
||||||
|
ext = get_library_extension()
|
||||||
|
path = '%s%s' % (path, ext)
|
||||||
|
return os.path.abspath(path)
|
||||||
|
|
||||||
|
|
||||||
|
def get_functype():
|
||||||
|
return TYPES[platform.system()]['functype']
|
||||||
|
|
||||||
|
def get_library_extension():
|
||||||
|
return TYPES[platform.system()]['extension']
|
BIN
src/locales/EU/LC_MESSAGES/twblue.mo
Normal file
BIN
src/locales/EU/LC_MESSAGES/twblue.mo
Normal file
Binary file not shown.
2460
src/locales/EU/LC_MESSAGES/twblue.po
Normal file
2460
src/locales/EU/LC_MESSAGES/twblue.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/locales/ar/lc_messages/twblue.mo
Normal file
BIN
src/locales/ar/lc_messages/twblue.mo
Normal file
Binary file not shown.
2438
src/locales/ar/lc_messages/twblue.po
Normal file
2438
src/locales/ar/lc_messages/twblue.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/locales/ca/LC_MESSAGES/twblue.mo
Normal file
BIN
src/locales/ca/LC_MESSAGES/twblue.mo
Normal file
Binary file not shown.
2462
src/locales/ca/LC_MESSAGES/twblue.po
Normal file
2462
src/locales/ca/LC_MESSAGES/twblue.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/locales/es/LC_MESSAGES/twblue.mo
Normal file
BIN
src/locales/es/LC_MESSAGES/twblue.mo
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user