Compare commits

...

170 Commits

Author SHA1 Message Date
a5ba80feee Final touches 2021-10-26 16:18:53 -05:00
528ecc2a33 Updated NSI file 2021-10-26 16:16:03 -05:00
3519746078 Move installer Nsis file to a new name 2021-10-26 15:47:03 -05:00
ef79e0696e Move updated Nsis script to artifacts in build stage 2021-10-26 15:19:57 -05:00
b9ee0dae5b Fixed a typo in installer script 2021-10-26 14:48:58 -05:00
f31575a733 Merge pull request #408 from manuelcortez/calendar_versioning
Switch to Calendar versioning
2021-10-26 13:52:21 -05:00
e451bbd5e9 Merge pull request #407 from manuelcortez/alias_manager
Alias manager Dialog. Closes #401
2021-10-26 13:51:09 -05:00
f7f303929e Updated CI config 2021-10-26 13:41:22 -05:00
9f48784ce4 Added script to add next version in application.py and installer generation files 2021-10-26 13:39:50 -05:00
cb1312d0c9 Changed version in installer so it will be easier to replace it later by the authomatic script 2021-10-26 13:38:21 -05:00
a82efd4dcc Fixed a typo 2021-10-26 13:16:31 -05:00
72e6d030d5 Updater no longer will try to compare float numbers for updates 2021-10-26 13:10:57 -05:00
d222740887 Updated gitlab CI 2021-10-26 13:04:49 -05:00
2b059ee42e Modified Nsis scripts to remove references to old snapshots code 2021-10-26 13:02:24 -05:00
daac312658 Removes snapshot information from app info 2021-10-26 12:43:03 -05:00
b23be9c896 Started to remove stable and snapshot update information files 2021-10-26 12:40:57 -05:00
61b0dc34b8 Merge branch 'next-gen' into alias_manager 2021-10-26 10:48:43 -05:00
c5d13369eb Merge branch 'next-gen' of github.com:manuelcortez/twblue into next-gen 2021-10-26 10:47:55 -05:00
856ecf5eb9 Merge branch 'next-gen' of github.com:manuelcortez/twblue into alias_manager 2021-10-26 10:46:30 -05:00
e3e0ac9457 Added an alias manager dialog in the application menu 2021-10-26 10:45:11 -05:00
34c1f69ec1 Merge pull request #404 from nidza07/SerbianTranslation
Updated Serbian translation
2021-10-22 12:57:58 -05:00
7326ff88f9 Merge pull request #405 from Oreonan/fr_211021
Update french interface
2021-10-22 12:56:55 -05:00
Oreonan
a8d876a7b7 Fix last-translator e-mail 2021-10-21 17:02:03 +02:00
Oreonan
89fa6435b4 Update french interface 2021-10-21 16:58:32 +02:00
Nikola Jovic
d1bd393be2 Updated Serbian translation 2021-10-21 16:30:46 +02:00
301bd5fd39 Pushed a new snapshot 2021-10-20 15:52:58 -05:00
José Manuel Delicado Alcolea
a2f25bfbb5 Added charset-normalizer to requirements 2021-10-12 17:05:09 +02:00
286e030f40 Handle new Tweepy exceptions properly. #403 2021-10-07 09:20:06 -05:00
d8fca3b31a Initial work to Support Tweepy 4 2021-09-26 03:58:25 -05:00
0c27427843 Merge branch 'next-gen' of github.com:manuelcortez/twblue into next-gen 2021-09-10 15:05:28 -05:00
dfdbe3c5f4 Close VLC window after video playback ends. Close #399 2021-09-10 15:05:05 -05:00
José Manuel Delicado
2222a97451 Merge pull request #394 from Oreonan/fr_020821
Update french interface translation
2021-09-03 11:38:50 +02:00
fbe93ea4be Made FreakyBlue The default soundpack for new twitter sessions 2021-09-02 09:59:13 -05:00
4bcae1aa97 Fixed search filters. Closes #397 2021-09-02 09:38:05 -05:00
4cabf5b9cd Added code to handle user timelines in buffer creator 2021-08-31 10:31:18 -05:00
a9a4189295 Added improvements to tweet condense feature 2021-08-31 10:05:21 -05:00
c7b6d69518 Fixed an error in new buffer creation. Closes #396 2021-08-30 13:19:15 -05:00
65512a9862 Added a pubsub event to create sessions buffers 2021-08-30 10:51:26 -05:00
Oreonan
17ea8af050 Update translation 2021-08-28 08:37:26 +02:00
Oreonan
43578a32eb Merge remote-tracking branch 'origin/next-gen' into fr_020821 2021-08-28 08:33:40 +02:00
7c34204d17 Allow to specify alternative configuration specs for sessions 2021-08-27 13:45:59 -05:00
3a5c1c10d3 Released a new snapshot 2021-08-26 15:13:34 -05:00
f9864a887d Fixed a small traceback that was happening when translating a direct message 2021-08-26 09:16:02 -05:00
3d8519313e Switched Geocoding library to OpenStreetMap's Nominatim API. Closes #390 2021-08-26 08:56:51 -05:00
f4ecf10885 Allow to copy tweet URLS from tweet displayer dialog 2021-08-25 16:30:37 -05:00
c67b415934 Updated changelog 2021-08-25 12:49:53 -05:00
10511d3022 Improvements to reading tweets with many mentions on them 2021-08-25 11:13:12 -05:00
Oreonan
ddc80a29fd Update french interface translation 2021-08-02 19:30:24 +02:00
9ea36a26d2 Updated changelog with windows 11 keymap 2021-07-31 22:33:32 -05:00
97286496fc fixed changelog in diverged commits 2021-07-31 22:28:46 -05:00
José Manuel Delicado
6436af76f5 Merge pull request #393 from jpavonabian/windows11
Added a fix.
2021-07-28 07:50:38 +02:00
Jesus
576b5064c0 Added a fix. 2021-07-28 07:23:57 +02:00
José Manuel Delicado
342265b3c0 Merge pull request #392 from jpavonabian/windows11
Fixed shortkuts
2021-07-26 13:38:32 +02:00
Jesus
54938ecb6c Fixed shortkuts 2021-07-26 12:47:51 +02:00
José Manuel Delicado
7aff8252d2 Merge pull request #391 from jpavonabian/windows11
Keypad for windows 11
2021-07-24 12:46:43 +02:00
Jesus
9c680130f7 Keypad para windows 11 2021-07-24 06:24:08 +02:00
24d1ad093d Streaming API: Ignore retweets if original tweet is present in a buffer 2021-07-16 10:22:51 -05:00
b2b9cd810f Fixed an issue when indefined keystrokes 2021-07-13 17:53:01 -05:00
582be54dea Added add_alias action to keymaps as undefined 2021-07-13 17:25:48 -05:00
ff0fbeafa3 Modified keystrokeEditor to allow undefined keystrokes 2021-07-13 17:22:20 -05:00
e314cf0599 Allow search users by names in autocomplete users 2021-07-10 05:24:56 -05:00
José Manuel Delicado Alcolea
e7b72112cf Merge branch 'next-gen' of github.com:manuelcortez/TWBlue into next-gen 2021-07-07 08:42:14 +02:00
José Manuel Delicado Alcolea
70c095febe setup.py: patch cx_freeze to include our Microsoft Visual C++ runtime files 2021-07-07 08:41:08 +02:00
6119b029f8 Merge pull request #388 from zstanecic/next-gen
Updated russian interface
2021-07-06 17:13:59 -05:00
b74cd9a73d Ignore undefined actions in keymaps 2021-07-06 17:01:42 -05:00
8ff6809f08 Updated changelog 2021-07-06 16:22:52 -05:00
39af9d8623 Merge pull request #389 from manuelcortez/user_alias
User aliases within TWBlue
2021-07-06 15:58:54 -05:00
3688d7548c Check alias before returning any user object 2021-07-06 13:59:34 -05:00
07f9afb14e Added option to user menu in the menu bar 2021-07-06 13:58:34 -05:00
de12dadac2 Added GUI for user alias addition 2021-07-06 13:58:13 -05:00
877c909482 Added user-aliases section on session configs 2021-07-06 13:56:56 -05:00
José Manuel Delicado Alcolea
1206aba83b Added a few more dependencies to requirements.txt 2021-07-06 20:42:03 +02:00
zstanecic
fcd631b2de Merge remote-tracking branch 'upstream/next-gen' into next-gen 2021-07-06 19:16:47 +02:00
Jose Manuel Delicado
86130954d7 Updated Windows dependencies. Latest versions of NSIS and VLC, among others 2021-07-06 12:39:47 +02:00
Jose Manuel Delicado
23a56c637d Add some indirect dependencies to requirements.txt in order to easily keep them updated 2021-07-05 23:03:40 +02:00
zstanecic
d5ac0db67b updated the russian localization. 2021-07-05 16:04:19 +02:00
bb4869b7be Pushed a new snapshot 2021-07-04 17:05:06 -05:00
44b6e82183 Fix reply dialog issue 2021-07-04 11:50:03 -05:00
Jose Manuel Delicado
5268f166f8 Use Python 3.8.10 for automated builds 2021-07-04 18:43:33 +02:00
Jose Manuel Delicado
37ad6b5fbf paths.py: replace socializer by TW Blue in data_path function definition 2021-07-04 18:32:17 +02:00
Jose Manuel Delicado
bcc72c932d Updated Windows dependencies. Python 3.8.10 2021-07-04 18:26:49 +02:00
b9a9bd03c2 Pushed a new snapshot 2021-07-04 09:47:33 -05:00
e6543bcf77 Allow streaming API support to be disabled from global settings dialog 2021-07-04 09:44:48 -05:00
03b61946f8 Fixed user searches 2021-07-04 09:43:53 -05:00
8fe2f4c64d Exclude muted users from Streaming API 2021-07-04 09:15:04 -05:00
37af722556 Fixed alpha version for snapshots 2021-07-04 06:15:40 -05:00
4312ad82e7 Fixed runner config 2021-07-04 05:51:59 -05:00
e9e8a8fba9 Pushed a new snapshot 2021-07-04 05:43:06 -05:00
5cad4ab2a7 Restore get_all_users and get_all_mentionned in nested tweets 2021-07-04 05:31:27 -05:00
01dd93e076 Show all current account replies in streaming API 2021-07-04 05:30:52 -05:00
d301f841e3 TWBlue should stop showing sent retweets as original tweets in Streaming API 2021-07-04 05:02:02 -05:00
81d18d4656 Updated changelog 2021-07-04 04:47:13 -05:00
ccba22cfd2 Show dialog on suspended users again. closes #387 2021-07-03 14:09:52 -05:00
465b550c30 Removed old code to deal with invalid users 2021-07-03 14:05:02 -05:00
788811bf6c Ensure a tweet is reduced when sent by streaming API 2021-07-03 14:04:14 -05:00
c926355048 Hide replies to users the current account doesn't follow in streaming API 2021-07-03 11:52:21 -05:00
84cbf5c497 Fix yet another error due to reduced tweets 2021-07-03 11:51:43 -05:00
7eb2d8930f Fixed traceback happened when attempting to send streaming data to not logged sessions 2021-07-03 11:51:11 -05:00
864ebdf96d Made widgetUtils available to searchBuffers 2021-07-02 17:57:39 -05:00
ee9a92bcb4 Makes time available on conversation buffers 2021-07-02 17:53:05 -05:00
818bc243e4 Handle Twyshort's detection for reduced tweets 2021-07-02 17:34:40 -05:00
062289a977 Merge pull request #385 from manuelcortez/streaming
Streaming API support
2021-07-02 17:29:10 -05:00
56a1c57e04 Merge pull request #386 from manuelcortez/buffer_modularization
Separate all buffers in modules for an easier work with code
2021-07-02 17:27:48 -05:00
3c7063792c Separate all buffers in modules for an easier work with code 2021-07-02 17:22:24 -05:00
77eadb42bb Make sure to disconnect the streams as tweepy implements it 2021-07-02 10:35:20 -05:00
9053fcd5de Send Tweets to mentions properly 2021-07-02 10:11:50 -05:00
5f11467f27 Switched threads to our own facilities and attempts to improve thread management for streaming endpoints 2021-07-02 09:52:21 -05:00
55b1c7bdae Integrates Tweepy's 68e19cc for preventing Urllib3 ProtocolError 2021-07-02 09:50:22 -05:00
ba90842185 Initial work to put tweets in mentions, sent and timelines 2021-06-29 17:55:36 -05:00
8fd3041efd Added some reconnection code and logging 2021-06-29 17:16:53 -05:00
bb5ead80de Parse correctly incoming tweets from Streaming API 2021-06-29 05:05:20 -05:00
168c7e7a5d Initial test for supporting a subset of the Streaming API 2021-06-28 17:03:26 -05:00
a7838bbf7d Next snapshots will have installer and 64 bits version. Switched to Gitlab CI to generate snapshots. 2021-06-28 10:23:39 -05:00
fe8b58a7b9 Include quoted tweets and retweets in audio detection and playback 2021-06-28 10:20:39 -05:00
a9f52b3a94 Added convert_bytes function to show correctly FTP upload progress 2021-06-28 06:01:05 -05:00
13c47f7b9f Fixed branch name 2021-06-28 05:28:15 -05:00
3515df9b15 Upload files to FTP server after generating snapshots 2021-06-28 05:26:42 -05:00
f998fa62a6 Added missed move command 2021-06-28 04:50:36 -05:00
a6032cae46 Added make_archive as a script 2021-06-28 04:30:42 -05:00
7935f79d77 fixed a syntax error 2021-06-28 04:20:54 -05:00
ef443346d1 Try to generate zip versions and installers 2021-06-28 04:16:59 -05:00
a27eee1fa2 Add submodules 2021-06-28 03:40:00 -05:00
b839dc077c Updated a small typo 2021-06-28 03:29:27 -05:00
2b719858c2 Initial implementation of gitlab's CI file 2021-06-28 03:28:43 -05:00
97afc379e8 Pushed a new snapshot 2021-06-28 00:47:23 -05:00
7f401ba789 Removed unneeded locales 2021-06-28 00:39:45 -05:00
ff22ae5653 Merge branch 'next-gen' of https://github.com/manuelcortez/twblue into next-gen 2021-06-28 00:38:54 -05:00
02d94fcea0 Fixed a small issue when loading a conversation buffer 2021-06-28 00:36:53 -05:00
df2015f360 Merge pull request #384 from manuelcortez/twitter_videos
Support for Twitter video playback
2021-06-27 23:40:11 -05:00
64a14c831b Added initial support for playback of Twitter videos (only works in tweets so far) 2021-06-27 21:40:22 -05:00
fbbe7852c2 Added serbian to generate documentation 2021-06-27 20:57:13 -05:00
9dfccd2bd0 Updated documentation generators 2021-06-27 20:55:16 -05:00
969a75e9f3 Merge pull request #380 from manuelcortez/better_memory_management
Replace the cache database for a SQLite backed implementation
2021-06-27 19:10:50 -05:00
a59ba5ef78 Reset cache every time we call to save_persistent_data in tests 2021-06-27 19:09:49 -05:00
3ebfdbc48b Request for a restart when switching memory cache 2021-06-27 19:09:21 -05:00
8db14a95c1 Call reduce.reduce_tweet and session.save_users when retrieving previous items for a buffer 2021-06-27 18:07:56 -05:00
1b9062d86f aHandles deleted users for direct messages without wasting too many API calls 2021-06-27 18:05:35 -05:00
4b60a79e49 Made get_all_mentioned to take into account sometimes tweets might have no entities defined 2021-06-27 18:04:26 -05:00
002e1ccb55 If get_user is called with a full user object as an argument, logs it and changes the object to be only the user_id 2021-06-27 16:03:36 -05:00
0bcdf88290 Merge pull request #382 from nidza07/SerbianTranslation
Added Serbian documentation and changelog.
2021-06-27 02:35:55 -05:00
0612c653b8 Make TWBlue to respect persist_size when loading the cache database. Added tests for both reading from disk and loading in memory caches 2021-06-27 02:32:28 -05:00
nikola nikola
c5dadb063a Added Serbian documentation and changelog. 2021-06-27 04:22:02 +02:00
35d6010298 Save tweets cache by taking into account persist_size 2021-06-26 15:16:50 -05:00
40a63d9e16 Merge pull request #381 from nidza07/SerbianTranslation
Updated Serbian translation.
2021-06-26 02:16:13 -05:00
5712dd735b Added a new setting in account dialog to control whehter we load the cache db into memory or read it from disk 2021-06-26 01:52:07 -05:00
2c75ea5005 Fixed a small issue referencing an user in the old way while retrieving all mentioned users in a tweet 2021-06-26 01:50:47 -05:00
e35f37fcc2 Updated changelog 2021-06-26 01:49:55 -05:00
71358ea74d changed function names and cleaned code a little bit to reflect better the changes to object percistance 2021-06-25 23:35:33 -05:00
b8f822830f Added proper docstrings to reduce twitter objects 2021-06-25 22:47:10 -05:00
74e4fe6357 Ensure direct message buffers are correctly saved in database 2021-06-25 16:49:23 -05:00
77bee64421 Pass a null value to tweepy.models.Status as sometimes database saved objects might not include it 2021-06-25 16:48:44 -05:00
c761230566 Reduce the size of all tweets so it might make easier to handle those in a realtime database 2021-06-25 16:25:51 -05:00
49505fabcd Modify utils so those will take into account that entities might be not present in tweet objects 2021-06-25 13:14:01 -05:00
4ad01d7833 Retrieve user objects from the users list stored in SqlDict as opposed to loading it from tweet objects 2021-06-25 13:13:00 -05:00
ab1a13f886 Improve save_users() and get_user() as those will be used in more places later 2021-06-25 13:11:33 -05:00
nikola nikola
44c25e54f8 Updated Serbian translation. 2021-06-25 18:33:16 +02:00
cdabd6f055 Merge branch 'next-gen' into better_memory_management 2021-06-24 09:54:45 -05:00
60144a6b08 Added initial support to SqliteDict package 2021-06-24 09:52:10 -05:00
382acf7c8c Use slitedict to attempt to reduce memory usage when caching tweets 2021-06-23 13:40:21 -05:00
José Manuel Delicado
03ba59028f Merge pull request #379 from ivnc/next-gen
Fix arrow for Galician
2021-06-23 20:05:24 +02:00
Iván Novegil
50125fc55a Fix arrow for Galician. Modify some existing translations to localize them properly 2021-06-23 18:45:33 +02:00
39e1fb017c Made code indentation to comply with PEP8 2021-06-16 16:18:41 -05:00
2aaa4eced3 Removed Catala and Basque locale as they are in arrow already. Disabled Galician locale cause it's not fully implemented and fails 2021-06-16 16:17:16 -05:00
José Manuel
6d2eac5b1c Merge pull request #332 from Oreonan/fr-04062020
Some changes for french translation
2021-05-29 21:56:33 +02:00
Oreonan
40040d1b17 Merge branch 'next-gen' into fr-04062020 2021-05-14 21:24:46 +02:00
2a791d43bf Fixed an error when parsing a DM sent from an deleted account 2021-05-14 09:52:19 -05:00
b10aeb046d Changed label of direct message's text field so it will not reference any username in the hint. Closes #366 2021-05-07 17:18:21 -05:00
7d6e230fd9 Fix issue that avoids TWBlue to use Shift+F10 as menu key. Fixes #353 2021-05-07 16:52:10 -05:00
9346bba7a0 Fixed a small bug when sending long tweets via twyshort 2021-05-03 10:05:14 -05:00
Oreonan
0111c8aae1 some changes for french translation 2020-06-04 21:20:18 +02:00
202 changed files with 16196 additions and 27606 deletions

123
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,123 @@
variables:
GIT_SUBMODULE_STRATEGY: recursive
PYTHON: "C:\\python38\\python.exe"
NSIS: "C:\\program files (x86)\\nsis\\makensis.exe"
stages:
- build
- make_installer
- upload
twblue32:
tags:
- shared-windows
- windows
- windows-1809
before_script:
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- choco install python --version 3.8.10 -y -ForceX86
- '&$env:PYTHON -V'
- '&$env:PYTHON -m pip install --upgrade pip'
- '&$env:PYTHON -m pip install --upgrade -r requirements.txt'
stage: build
interruptible: true
script:
# Create html documentation firstly.
- cd doc
- '&$env:PYTHON documentation_importer.py'
- cd ..\src
- '&$env:PYTHON ..\doc\generator.py'
- '&$env:PYTHON write_version_data.py'
- '&$env:PYTHON setup.py build'
- cd ..
- mkdir artifacts
- cd scripts
- '&$env:PYTHON make_archive.py'
- cd ..
- mv src/dist artifacts/TWBlue
- move src/twblue.zip artifacts/twblue_x86.zip
# Move the generated script nsis file to artifacts, so we won't need python when generating the installer.
- move scripts/twblue.nsi artifacts/twblue.nsi
only:
- tags
artifacts:
paths:
- artifacts
expire_in: 1 day
twblue64:
tags:
- shared-windows
- windows
- windows-1809
before_script:
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- choco install python --version 3.8.10 -y
- '&$env:PYTHON -V'
- '&$env:PYTHON -m pip install --upgrade pip'
- '&$env:PYTHON -m pip install --upgrade -r requirements.txt'
stage: build
interruptible: true
script:
# Create html documentation firstly.
- cd doc
- '&$env:PYTHON documentation_importer.py'
- cd ..\src
- '&$env:PYTHON ..\doc\generator.py'
- '&$env:PYTHON write_version_data.py'
- '&$env:PYTHON setup.py build'
- cd ..
- mkdir artifacts
- cd scripts
- '&$env:PYTHON make_archive.py'
- cd ..
- mv src/dist artifacts/TWBlue64
- move src/twblue.zip artifacts/twblue_x64.zip
only:
- tags
artifacts:
paths:
- artifacts
expire_in: 1 day
generate_versions:
stage: make_installer
tags:
- shared-windows
- windows
- windows-1809
before_script:
- Set-Variable -Name "time" -Value (date -Format "%H:%m")
- echo ${time}
- echo "started by ${GITLAB_USER_NAME}"
- choco install nsis -y -ForceX86
script:
- move artifacts/TWBlue scripts/
- move artifacts/TWBlue64 scripts/
- move artifacts/twblue.nsi scripts/installer.nsi
- cd scripts
- '&$env:NSIS installer.nsi'
- move twblue_setup.exe ../artifacts
only:
- tags
artifacts:
paths:
- artifacts
expire_in: 1 day
upload:
stage: upload
tags:
- linux
image: python
interruptible: true
script:
- cd artifacts
- python ../scripts/upload.py
only:
- tags
- schedules

View File

@@ -1,84 +0,0 @@
pull_requests:
# Avoid building after pull requests. Shall we disable this option?
do_not_increment_build_number: true
# Only build whenever we add tags to the repo.
skip_non_tags: true
environment:
matrix:
# List of python versions we want to work with.
- PYTHON: "C:\\Python38"
PYTHON_VERSION: "3.8.x" # currently 2.7.9
PYTHON_ARCH: "32"
# perhaps we may enable this one in future?
# - PYTHON: "C:\\Python37-x64"
# PYTHON_VERSION: "3.7.x" # currently 2.7.9
# PYTHON_ARCH: "64"
# This is important so we will retrieve everything in submodules as opposed to default method.
clone_script:
- cmd: >-
git clone -q --branch=%APPVEYOR_REPO_BRANCH% https://github.com/%APPVEYOR_REPO_NAME%.git %APPVEYOR_BUILD_FOLDER%
&& cd %APPVEYOR_BUILD_FOLDER%
&& git checkout -qf %APPVEYOR_REPO_COMMIT%
&& git submodule update --init --recursive
install:
# If there is a newer build queued for the same PR, cancel this one.
# The AppVeyor 'rollout builds' option is supposed to serve the same
# purpose but it is problematic because it tends to cancel builds pushed
# directly to master instead of just PR builds (or the converse).
# credits: JuliaLang developers.
- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
throw "There are newer queued builds for this pull request, failing early." }
# - ECHO "Filesystem root:"
# - ps: "ls \"C:/\""
# Check that we have the expected version and architecture for Python
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
- "python --version"
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
# Upgrade to the latest version of pip to avoid it displaying warnings
# about it being out of date.
- "python -m pip install --upgrade pip setuptools"
# Install the build dependencies of the project. If some dependencies contain
# compiled extensions and are not provided as pre-built wheel packages,
# pip will build them from source using the MSVC compiler matching the
# target Python version and architecture
- "%CMD_IN_ENV% pip install -r requirements.txt"
- "%CMD_IN_ENV% pip install pyenchant"
build_script:
# Build documentation at first, so setup.py won't fail when copying everything.
- "cd doc"
# Import documentation before building, so strings.py will be created.
- "%CMD_IN_ENV% python documentation_importer.py"
# build doc from src folder so it will generate result files right there.
- "cd ..\\src"
- "%CMD_IN_ENV% python ..\\doc\\generator.py"
# Build distributable files.
- "%CMD_IN_ENV% python setup.py build"
- "cd dist"
# Zip it all.
- cmd: 7z a ..\..\snapshot.zip *
artifacts:
- path: snapshot.zip
deploy:
- provider: FTP
host: twblue.es
protocol: ftp
beta: true
username: twblue.es
password:
secure: lQZqpYRnHf4LLVOg0C42NQ==
folder: 'web/pubs'

View File

@@ -39,5 +39,5 @@ florian Ionașcu
Christian Leo Mameli Christian Leo Mameli
Natalia Hedlund (Наталья Хедлунд) Natalia Hedlund (Наталья Хедлунд)
Valeria (Валерия) Valeria (Валерия)
Corentin Bacqué-Cazenave Oreonan
Artem Plaksin (maniyax) Artem Plaksin (maniyax)

View File

@@ -2,6 +2,27 @@
## changes in this version ## changes in this version
* Added an user alias manager, located in the application menu in the menu bar. From this dialog, it is possible to review, add, edit or remove user aliases for the current account. ([#401](https://github.com/manuelcortez/TWBlue/issues/401))
* TWBlue now closes the VLC player window automatically when a video reaches its end. ([#399](https://github.com/manuelcortez/TWBlue/issues/399))
* After a lot of time, TWBlue now uses a new default Soundpack, called FreakyBlue. This soundpack will be set by default in all new sessions created in the application. Thanks to [Andre Louis](https://twitter.com/FreakyFwoof) for the pack. ([#247](https://github.com/manuelcortez/TWBlue/issues/247))
* When reading a tweet, if the tweet contains more than 2 consecutive mentions, TWBlue will announce how many more users the tweet includes, as opposed to read every user in the conversation. You still can display the tweet to read all users.
* In the tweet displayer, It is possible to copy a link to the current tweet or person by pressing a button called "copy link to clipboard".
* Added a keymap capable to work under Windows 11. ([#391](https://github.com/manuelcortez/TWBlue/pull/391))
* Added user aliases to TWBlue. This feature allows you to rename user's display names on Twitter, so the next time you'll read an user it will be announced as you configured. For adding an alias to an user, select the "add alias" option in the user menu, located in the menu bar. This feature works only if you have set display screen names unchecked. Users are displayed with their display name in people buffers only. This action is supported in all keymaps, although it is undefined by default. ([#389](https://github.com/manuelcortez/TWBlue/pull/389))
* There are some changes to the autocomplete users feature:
* Now users can search for twitter screen names or display names in the database.
* It is possible to undefine keystrokes in the current keymap in TWBlue. This allows you, for example, to redefine keystrokes completely.
* We have changed our Geocoding service to the Nominatim API from OpenStreetMap. Addresses present in tweets are going to be determined by this service, as the Google Maps API now requires an API key. ([#390](https://github.com/manuelcortez/TWBlue/issues/390))
* Added a limited version of the Twitter's Streaming API: The Streaming API will work only for tweets, and will receive tweets only by people you follow. Protected users are not possible to be streamed. It is possible that during high tweet traffic, the Stream might get disconnected at times, but TWBlue should be capable of detecting this problem and reconnecting the stream again. ([#385](https://github.com/manuelcortez/TWBlue/pull/385))
* Fixed an issue that made TWBlue to not show a dialog when attempting to show a profile for a suspended user. ([#387](https://github.com/manuelcortez/TWBlue/issues/387))
* Added support for Twitter audio and videos: Tweets which contains audio or videos will be detected as audio items, and you can playback those with the regular command to play audios. ([#384,](https://github.com/manuelcortez/TWBlue/pull/384))
* We just implemented some changes in the way TWBlue handles tweets in order to reduce its RAM memory usage [#380](https://github.com/manuelcortez/TWBlue/pull/380):
* We reduced the tweets size by storing only the tweet fields we currently use. This should reduce tweet's size in memory for every object up to 75%.
* When using the cache database to store your tweets, there is a new setting present in the account settings dialog, in the general tab. This setting allows you to control whether TWBlue will load the whole database into memory (which is the current behaviour) or not.
* Loading the whole database into memory has the advantage of being extremely fast to access any element (for example when moving through tweets in a buffer), but it requires more memory as the tweet buffers grow up. This should, however, use less memory than before thanks to the optimizations performed in tweet objects. If you have a machine with enough memory, this should be a good option for your case.
* If you uncheck this setting, TWBlue will read the whole database from disk. This is significantly slower, but the advantage of this setting is that it will consume almost no extra memory, no matter how big is the tweets dataset. Be ware, though, that TWBlue might start to feel slower when accessing elements (for example when reading tweets) as the buffers grow up. This setting is suggested for computers with low memory or for those people not wanting to keep a really big amount of tweets stored.
* Changed the label in the direct message's text control so it will indicate that the user needs to write the text there, without referring to any username in particular. ([#366,](https://github.com/manuelcortez/TWBlue/issues/366))
* TWBlue will take Shift+F10 again as the contextual menu key in the list of items in a buffer. This stopped working after we have migrated to WX 4.1. ([#353,](https://github.com/manuelcortez/TWBlue/issues/353))
* TWBlue should render correctly retweets of quoted tweets. ([#365,](https://github.com/manuelcortez/TWBlue/issues/365)) * TWBlue should render correctly retweets of quoted tweets. ([#365,](https://github.com/manuelcortez/TWBlue/issues/365))
* Fixed an error that was causing TWBlue to be unable to output to screen readers at times. ([#369,](https://github.com/manuelcortez/TWBlue/issues/369)) * Fixed an error that was causing TWBlue to be unable to output to screen readers at times. ([#369,](https://github.com/manuelcortez/TWBlue/issues/369))
* Fixed autocomplete users feature. ([#367,](https://github.com/manuelcortez/TWBlue/issues/367)) * Fixed autocomplete users feature. ([#367,](https://github.com/manuelcortez/TWBlue/issues/367))

View File

@@ -16,9 +16,9 @@ def prepare_documentation_in_file(fileSource, fileDest):
if "\n" == i: if "\n" == i:
newvar = "\"\"," newvar = "\"\","
elif "\n" == i[-1]: elif "\n" == i[-1]:
newvar = "_(u\"\"\"%s\"\"\"),\n" % (i[:-1]) newvar = "\"\"\"%s\"\"\",\n" % (i[:-1])
else: else:
newvar = "_(u\"\"\"%s\"\"\"),\n" % (i) newvar = "\"\"\"%s\"\"\",\n" % (i)
f2.write(newvar) f2.write(newvar)
f1.close() f1.close()
f2.write("]") f2.write("]")

View File

@@ -8,28 +8,27 @@ import shutil
from codecs import open as _open from codecs import open as _open
from importlib import reload from importlib import reload
def change_language(name, language): def get_translation_function(name, language):
global _ if language == "en":
os.environ["lang"] = language return gettext.NullTranslations()
_ = gettext.install(name, os.path.join(paths.app_path(), "locales")) translation_function = gettext.translation(name, os.path.join(paths.app_path(), "locales"), languages=[language])
return translation_function
# the list of supported language codes of TW Blue # the list of supported language codes of TW Blue
languages = ["en", "es", "fr", "de", "it", "gl", "ja", "ru", "ro", "eu", "ca", "da"] languages = ["en", "es", "fr", "de", "it", "gl", "ja", "ru", "ro", "eu", "ca", "da", "sr"]
def generate_document(language, document_type="documentation"): def generate_document(language, document_type="documentation"):
if document_type == "documentation": if document_type == "documentation":
translation_file = "twblue-documentation" translation_file = "twblue-documentation"
change_language(translation_file, language) translation_function = get_translation_function(translation_file, language)
reload(strings) markdown_file = markdown.markdown("\n".join([translation_function.gettext(s[:-1]) if s != "\n" else s for s in strings.documentation[1:]]), extensions=["markdown.extensions.toc"])
markdown_file = markdown.markdown("\n".join(strings.documentation[1:]), extensions=["markdown.extensions.toc"]) title = translation_function.gettext(strings.documentation[0][:-1])
title = strings.documentation[0]
filename = "manual.html" filename = "manual.html"
elif document_type == "changelog": elif document_type == "changelog":
translation_file = "twblue-changelog" translation_file = "twblue-changelog"
change_language(translation_file, language) translation_function = get_translation_function(translation_file, language)
reload(changelog) markdown_file = markdown.markdown("\n".join([translation_function.gettext(s[:-1]) if s != "\n" else s for s in changelog.documentation[1:]]), extensions=["markdown.extensions.toc"])
markdown_file = markdown.markdown("\n".join(changelog.documentation[1:]), extensions=["markdown.extensions.toc"]) title = translation_function.gettext(changelog.documentation[0][:-1])
title = changelog.documentation[0]
filename = "changelog.html" filename = "changelog.html"
first_html_block = """<!doctype html> first_html_block = """<!doctype html>
<html lang="%s"> <html lang="%s">
@@ -56,11 +55,13 @@ def create_documentation():
shutil.copy(os.path.join("..", "license.txt"), os.path.join("documentation", "license.txt")) shutil.copy(os.path.join("..", "license.txt"), os.path.join("documentation", "license.txt"))
for i in languages: for i in languages:
print("Creating documentation for: %s" % (i,)) print("Creating documentation for: %s" % (i,))
generate_document(i) try:
generate_document(i, "changelog") generate_document(i)
generate_document(i, "changelog")
except:
continue
print("Done") print("Done")
change_language("twblue-documentation", "en")
import strings import strings
import changelog import changelog
create_documentation() create_documentation()

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ oauthlib
requests-oauthlib requests-oauthlib
requests-toolbelt requests-toolbelt
pypubsub pypubsub
pygeocoder geopy
arrow arrow
python-dateutil python-dateutil
futures futures
@@ -25,12 +25,29 @@ urllib3
youtube-dl youtube-dl
python-vlc python-vlc
pypiwin32 pypiwin32
pywin32
certifi certifi
backports.functools_lru_cache backports.functools_lru_cache
cx_freeze cx_freeze
tweepy tweepy
twitter-text-parser twitter-text-parser
pyenchant pyenchant
sqlitedict
cx-Logging
h11
h2
hpack
hstspreload
httpcore
httpx
hyperframe
rfc3986
sniffio
attrs
importlib-metadata
numpy
pillow
charset-normalizer
git+https://github.com/accessibleapps/libloader git+https://github.com/accessibleapps/libloader
git+https://github.com/accessibleapps/platform_utils git+https://github.com/accessibleapps/platform_utils
git+https://github.com/accessibleapps/accessible_output2 git+https://github.com/accessibleapps/accessible_output2

12
scripts/make_archive.py Normal file
View File

@@ -0,0 +1,12 @@
import shutil
import os
import sys
def create_archive():
os.chdir("..\\src")
print("Creating zip archive...")
folder = "dist"
shutil.make_archive("twblue", "zip", folder)
os.chdir("..\\scripts")
create_archive()

View File

@@ -14,11 +14,11 @@ SetCompress auto
SetCompressor /solid lzma SetCompressor /solid lzma
SetDatablockOptimize on SetDatablockOptimize on
VIAddVersionKey ProductName "TWBlue" VIAddVersionKey ProductName "TWBlue"
VIAddVersionKey LegalCopyright "Copyright 2018 Manuel Cortéz." VIAddVersionKey LegalCopyright "Copyright 2014-2021 Manuel Cortéz."
VIAddVersionKey ProductVersion "0.95" VIAddVersionKey ProductVersion "0.95.0"
VIAddVersionKey FileVersion "0.95" VIAddVersionKey FileVersion "0.95.0"
VIProductVersion "0.95.0.0" VIProductVersion "0.95.0"
VIFileVersion "0.95.0.0" VIFileVersion "0.95.0"
!insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_WELCOME
!define MUI_LICENSEPAGE_RADIOBUTTONS !define MUI_LICENSEPAGE_RADIOBUTTONS
!insertmacro MUI_PAGE_LICENSE "license.txt" !insertmacro MUI_PAGE_LICENSE "license.txt"
@@ -27,12 +27,12 @@ var StartMenuFolder
!insertmacro MUI_PAGE_STARTMENU startmenu $StartMenuFolder !insertmacro MUI_PAGE_STARTMENU startmenu $StartMenuFolder
!insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_LINK "Visit TWBlue website" !define MUI_FINISHPAGE_LINK "Visit TWBlue website"
!define MUI_FINISHPAGE_LINK_LOCATION "http://twblue.es" !define MUI_FINISHPAGE_LINK_LOCATION "https://twblue.es"
!define MUI_FINISHPAGE_RUN "$INSTDIR\TWBlue.exe" !define MUI_FINISHPAGE_RUN "$INSTDIR\TWBlue.exe"
!insertmacro MUI_PAGE_FINISH !insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English" !insertmacro MUI_LANGUAGE "English"
!insertmacro MUI_LANGUAGE "French" !insertmacro MUI_LANGUAGE "French"
!insertmacro MUI_LANGUAGE "Spanish" !insertmacro MUI_LANGUAGE "Spanish"
!insertmacro MUI_LANGUAGE "Italian" !insertmacro MUI_LANGUAGE "Italian"
@@ -73,9 +73,9 @@ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "U
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "0.95" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "0.95"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "URLInfoAbout" "http://twblue.es" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "URLInfoAbout" "https://twblue.es"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMajor" 0 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMajor" 0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMinor" 95 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMinor" 0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoModify" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoRepair" 1 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoRepair" 1
SectionEnd SectionEnd

48
scripts/upload.py Normal file
View File

@@ -0,0 +1,48 @@
#! /usr/bin/env python
import sys
import os
import glob
import ftplib
transferred=0
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 callback(progress):
global transferred
transferred = transferred+len(progress)
print("Uploaded {}".format(convert_bytes(transferred),))
ftp_server = os.environ.get("FTP_SERVER") or sys.argv[1]
ftp_username = os.environ.get("FTP_USERNAME") or sys.argv[2]
ftp_password = os.environ.get("FTP_PASSWORD") or sys.argv[3]
print("Uploading files to the TWBlue server...")
print("Connecting to %s" % (ftp_server,))
connection = ftplib.FTP(ftp_server)
print("Connected to FTP server {}".format(ftp_server,))
connection.login(user=ftp_username, passwd=ftp_password)
print("Logged in successfully")
connection.cwd("web/pubs")
files = glob.glob("*.zip")+glob.glob("*.exe")
print("These files will be uploaded into the version folder: {}".format(files,))
for file in files:
transferred = 0
print("Uploading {}".format(file,))
with open(file, "rb") as f:
connection.storbinary('STOR %s' % file, f, callback=callback, blocksize=1024*1024)
print("Upload completed. exiting...")
connection.quit()

View File

@@ -12,6 +12,7 @@ reverse_timelines = boolean(default=False)
announce_stream_status = boolean(default=True) announce_stream_status = boolean(default=True)
retweet_mode = string(default="ask") retweet_mode = string(default="ask")
persist_size = integer(default=0) persist_size = integer(default=0)
load_cache_in_memory=boolean(default=True)
show_screen_names = boolean(default=False) show_screen_names = boolean(default=False)
buffer_order = list(default=list('home','mentions', 'dm', 'sent_dm', 'sent_tweets','favorites','followers','friends','blocks','muted','events')) buffer_order = list(default=list('home','mentions', 'dm', 'sent_dm', 'sent_tweets','favorites','followers','friends','blocks','muted','events'))
@@ -20,7 +21,7 @@ volume = float(default=1.0)
input_device = string(default="Default") input_device = string(default="Default")
output_device = string(default="Default") output_device = string(default="Default")
session_mute = boolean(default=False) session_mute = boolean(default=False)
current_soundpack = string(default="default") current_soundpack = string(default="FreakyBlue")
indicate_audio = boolean(default=True) indicate_audio = boolean(default=True)
indicate_geo = boolean(default=True) indicate_geo = boolean(default=True)
indicate_img = boolean(default=True) indicate_img = boolean(default=True)
@@ -48,3 +49,5 @@ braille_reporting = boolean(default=True)
speech_reporting = boolean(default=True) speech_reporting = boolean(default=True)
[filters] [filters]
[user-aliases]

View File

@@ -3,20 +3,14 @@ import datetime
name = 'TWBlue' name = 'TWBlue'
short_name='twblue' short_name='twblue'
snapshot = True update_url = 'https://twblue.es/updates/updates.php'
if snapshot == False: mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/updates.json'
version = "0.95"
update_url = 'https://twblue.es/updates/stable.php'
mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/stable.json'
else:
version = "5"
update_url = 'https://twblue.es/updates/snapshot.php'
mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/snapshots.json'
authors = ["Manuel Cortéz", "José Manuel Delicado"] authors = ["Manuel Cortéz", "José Manuel Delicado"]
authorEmail = "manuel@manuelcortez.net" authorEmail = "manuel@manuelcortez.net"
copyright = "Copyright (C) 2013-2021, Manuel cortéz." copyright = "Copyright (C) 2013-2021, Manuel cortéz."
description = name+" is an app designed to use Twitter simply and efficiently while using minimal system resources. This app provides access to most Twitter features." description = name+" is an app designed to use Twitter simply and efficiently while using minimal system resources. This app provides access to most Twitter features."
translators = ["Manuel Cortéz (English)", "Mohammed Al Shara, Hatoun Felemban (Arabic)", "Francisco Torres (Catalan)", "Manuel cortéz (Spanish)", "Sukil Etxenike Arizaleta (Basque)", "Jani Kinnunen (finnish)", "Rémy Ruiz (French)", "Juan Buño (Galician)", "Steffen Schultz (German)", "Zvonimir Stanečić (Croatian)", "Robert Osztolykan (Hungarian)", "Christian Leo Mameli (Italian)", "Riku (Japanese)", "Paweł Masarczyk (Polish)", "Odenilton Júnior Santos (Portuguese)", "Florian Ionașcu, Nicușor Untilă (Romanian)", "Natalia Hedlund, Valeria Kuznetsova (Russian)", "Aleksandar Đurić (Serbian)", "Burak Yüksek (Turkish)"] translators = ["Manuel Cortéz (English)", "Mohammed Al Shara, Hatoun Felemban (Arabic)", "Francisco Torres (Catalan)", "Manuel cortéz (Spanish)", "Sukil Etxenike Arizaleta (Basque)", "Jani Kinnunen (finnish)", "Oreonan (Français)", "Juan Buño (Galician)", "Steffen Schultz (German)", "Zvonimir Stanečić (Croatian)", "Robert Osztolykan (Hungarian)", "Christian Leo Mameli (Italian)", "Riku (Japanese)", "Paweł Masarczyk (Polish)", "Odenilton Júnior Santos (Portuguese)", "Florian Ionașcu, Nicușor Untilă (Romanian)", "Natalia Hedlund, Valeria Kuznetsova (Russian)", "Aleksandar Đurić (Serbian)", "Burak Yüksek (Turkish)"]
url = u"https://twblue.es" url = u"https://twblue.es"
report_bugs_url = "https://github.com/manuelcortez/twblue/issues" report_bugs_url = "https://github.com/manuelcortez/twblue/issues"
supported_languages = [] supported_languages = []
version = "11"

View File

@@ -2,22 +2,22 @@ from __future__ import unicode_literals
from functools import wraps from functools import wraps
def matches_url(url): def matches_url(url):
def url_setter(func): def url_setter(func):
@wraps(func) @wraps(func)
def internal_url_setter(*args, **kwargs): def internal_url_setter(*args, **kwargs):
return func(*args, **kwargs) return func(*args, **kwargs)
internal_url_setter.url = url internal_url_setter.url = url
return internal_url_setter return internal_url_setter
return url_setter return url_setter
def find_url_transformer(url): def find_url_transformer(url):
from audio_services import services from audio_services import services
funcs = [] funcs = []
for i in dir(services): for i in dir(services):
possible = getattr(services, i) possible = getattr(services, i)
if callable(possible) and hasattr(possible, 'url'): if callable(possible) and hasattr(possible, 'url'):
funcs.append(possible) funcs.append(possible)
for f in funcs: for f in funcs:
if url.lower().startswith(f.url.lower()): if url.lower().startswith(f.url.lower()):
return f return f
return services.convert_generic_audio return services.convert_generic_audio

View File

@@ -5,37 +5,37 @@ from . import youtube_utils
@matches_url('https://audioboom.com') @matches_url('https://audioboom.com')
def convert_audioboom(url): def convert_audioboom(url):
if "audioboom.com" not in url.lower(): if "audioboom.com" not in url.lower():
raise TypeError('%r is not a valid URL' % url) raise TypeError('%r is not a valid URL' % url)
audio_id = url.split('.com/')[-1] audio_id = url.split('.com/')[-1]
return 'https://audioboom.com/%s.mp3' % audio_id return 'https://audioboom.com/%s.mp3' % audio_id
@matches_url ('https://soundcloud.com/') @matches_url ('https://soundcloud.com/')
def convert_soundcloud (url): def convert_soundcloud (url):
client_id = "df8113ca95c157b6c9731f54b105b473" client_id = "df8113ca95c157b6c9731f54b105b473"
with requests.get('http://api.soundcloud.com/resolve.json', client_id=client_id, url=url) as permalink: with requests.get('http://api.soundcloud.com/resolve.json', client_id=client_id, url=url) as permalink:
if permalink.status_code==404: if permalink.status_code==404:
raise TypeError('%r is not a valid URL' % permalink.url) raise TypeError('%r is not a valid URL' % permalink.url)
else: else:
resolved_url = permalink.url resolved_url = permalink.url
with requests.get(resolved_url) as track_url: with requests.get(resolved_url) as track_url:
track_data = track_url.json() track_data = track_url.json()
if track_data ['streamable']: if track_data ['streamable']:
return track_data ['stream_url'] + "?client_id=%s" %client_id return track_data ['stream_url'] + "?client_id=%s" %client_id
else: else:
raise TypeError('%r is not streamable' % url) raise TypeError('%r is not streamable' % url)
@matches_url ('https://www.youtube.com/watch') @matches_url ('https://www.youtube.com/watch')
def convert_youtube_long (url): def convert_youtube_long (url):
return youtube_utils.get_video_url(url) return youtube_utils.get_video_url(url)
@matches_url ('http://anyaudio.net/listen') @matches_url ('http://anyaudio.net/listen')
def convert_anyaudio(url): def convert_anyaudio(url):
values = url.split("audio=") values = url.split("audio=")
if len(values) != 2: if len(values) != 2:
raise TypeError('%r is not streamable' % url) raise TypeError('%r is not streamable' % url)
return "http://anyaudio.net/audiodownload?audio=%s" % (values[1],) return "http://anyaudio.net/audiodownload?audio=%s" % (values[1],)
def convert_generic_audio(url): def convert_generic_audio(url):
return url return url

View File

@@ -3,11 +3,11 @@ from __future__ import unicode_literals
import youtube_dl import youtube_dl
def get_video_url(url): def get_video_url(url):
ydl = youtube_dl.YoutubeDL({'quiet': True, 'format': 'bestaudio/best', 'outtmpl': u'%(id)s%(ext)s'}) ydl = youtube_dl.YoutubeDL({'quiet': True, 'format': 'bestaudio/best', 'outtmpl': u'%(id)s%(ext)s'})
with ydl: with ydl:
result = ydl.extract_info(url, download=False) result = ydl.extract_info(url, download=False)
if 'entries' in result: if 'entries' in result:
video = result['entries'][0] video = result['entries'][0]
else: else:
video = result video = result
return video["formats"][0]["url"] return video["formats"][0]["url"]

View File

@@ -15,14 +15,14 @@ keymap=None
changed_keymap = False changed_keymap = False
def setup (): def setup ():
global app global app
log.debug("Loading global app settings...") log.debug("Loading global app settings...")
app = config_utils.load_config(os.path.join(paths.config_path(), MAINFILE), os.path.join(paths.app_path(), MAINSPEC)) app = config_utils.load_config(os.path.join(paths.config_path(), MAINFILE), os.path.join(paths.app_path(), MAINSPEC))
log.debug("Loading keymap...") log.debug("Loading keymap...")
global keymap global keymap
if float(platform.version()[:2]) >= 10 and app["app-settings"]["load_keymap"] == "default.keymap": if float(platform.version()[:2]) >= 10 and app["app-settings"]["load_keymap"] == "default.keymap":
app["app-settings"]["load_keymap"] = "Windows 10.keymap" app["app-settings"]["load_keymap"] = "Windows 10.keymap"
app.write() app.write()
global changed_keymap global changed_keymap
changed_keymap = True changed_keymap = True
keymap = config_utils.load_config(os.path.join(paths.config_path(), "keymap.keymap"), os.path.join(paths.app_path(), "keymaps/"+app['app-settings']['load_keymap']), copy=False) keymap = config_utils.load_config(os.path.join(paths.config_path(), "keymap.keymap"), os.path.join(paths.app_path(), "keymaps/"+app['app-settings']['load_keymap']), copy=False)

View File

@@ -9,69 +9,69 @@ log = getLogger("config_utils")
class ConfigLoadError(Exception): pass class ConfigLoadError(Exception): pass
def load_config(config_path, configspec_path=None, copy=True, *args, **kwargs): def load_config(config_path, configspec_path=None, copy=True, *args, **kwargs):
spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True) spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True)
try: try:
config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs) config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs)
except ParseError: except ParseError:
raise ConfigLoadError("Unable to load %r" % config_path) raise ConfigLoadError("Unable to load %r" % config_path)
validator = Validator() validator = Validator()
validated = config.validate(validator, preserve_errors=False, copy=copy) validated = config.validate(validator, preserve_errors=False, copy=copy)
if validated == True: if validated == True:
config.write() config.write()
return config return config
else: else:
log.exception("Error in config file: {0}".format(validated,)) log.exception("Error in config file: {0}".format(validated,))
def is_blank(arg): def is_blank(arg):
"Check if a line is blank." "Check if a line is blank."
for c in arg: for c in arg:
if c not in string.whitespace: if c not in string.whitespace:
return False return False
return True return True
def get_keys(path): def get_keys(path):
"Gets the keys of a configobj config file." "Gets the keys of a configobj config file."
res=[] res=[]
fin=open(path) fin=open(path)
for line in fin: for line in fin:
if not is_blank(line): if not is_blank(line):
res.append(line[0:line.find('=')].strip()) res.append(line[0:line.find('=')].strip())
fin.close() fin.close()
return res return res
def hist(keys): def hist(keys):
"Generates a histogram of an iterable." "Generates a histogram of an iterable."
res={} res={}
for k in keys: for k in keys:
res[k]=res.setdefault(k,0)+1 res[k]=res.setdefault(k,0)+1
return res return res
def find_problems(hist): def find_problems(hist):
"Takes a histogram and returns a list of items occurring more than once." "Takes a histogram and returns a list of items occurring more than once."
res=[] res=[]
for k,v in hist.items(): for k,v in hist.items():
if v>1: if v>1:
res.append(k) res.append(k)
return res return res
def clean_config(path): def clean_config(path):
"Cleans a config file. If duplicate values are found, delete all of them and just use the default." "Cleans a config file. If duplicate values are found, delete all of them and just use the default."
orig=[] orig=[]
cleaned=[] cleaned=[]
fin=open(path) fin=open(path)
for line in fin: for line in fin:
orig.append(line) orig.append(line)
fin.close() fin.close()
for p in find_problems(hist(get_keys(path))): for p in find_problems(hist(get_keys(path))):
for o in orig: for o in orig:
o.strip() o.strip()
if p not in o: if p not in o:
cleaned.append(o) cleaned.append(o)
if len(cleaned) != 0: if len(cleaned) != 0:
cam=open(path,'w') cam=open(path,'w')
for c in cleaned: for c in cleaned:
cam.write(c) cam.write(c)
cam.close() cam.close()
return True return True
else: else:
return False return False

View File

@@ -8,33 +8,33 @@ from wxUI.dialogs import attach as gui
log = logging.getLogger("controller.attach") log = logging.getLogger("controller.attach")
class attach(object): class attach(object):
def __init__(self): def __init__(self):
self.attachments = list() self.attachments = list()
self.dialog = gui.attachDialog() self.dialog = gui.attachDialog()
widgetUtils.connect_event(self.dialog.photo, widgetUtils.BUTTON_PRESSED, self.upload_image) widgetUtils.connect_event(self.dialog.photo, widgetUtils.BUTTON_PRESSED, self.upload_image)
widgetUtils.connect_event(self.dialog.remove, widgetUtils.BUTTON_PRESSED, self.remove_attachment) widgetUtils.connect_event(self.dialog.remove, widgetUtils.BUTTON_PRESSED, self.remove_attachment)
self.dialog.get_response() self.dialog.get_response()
log.debug("Attachments controller started.") log.debug("Attachments controller started.")
def upload_image(self, *args, **kwargs): def upload_image(self, *args, **kwargs):
image, description = self.dialog.get_image() image, description = self.dialog.get_image()
if image != None: if image != None:
imageInfo = {"type": "photo", "file": image, "description": description} imageInfo = {"type": "photo", "file": image, "description": description}
log.debug("Image data to upload: %r" % (imageInfo,)) log.debug("Image data to upload: %r" % (imageInfo,))
self.attachments.append(imageInfo) self.attachments.append(imageInfo)
info = [_(u"Photo"), description] info = [_(u"Photo"), description]
self.dialog.attachments.insert_item(False, *info) self.dialog.attachments.insert_item(False, *info)
self.dialog.remove.Enable(True) self.dialog.remove.Enable(True)
def remove_attachment(self, *args, **kwargs): def remove_attachment(self, *args, **kwargs):
current_item = self.dialog.attachments.get_selected() current_item = self.dialog.attachments.get_selected()
log.debug("Removing item %d" % (current_item,)) log.debug("Removing item %d" % (current_item,))
if current_item == -1: current_item = 0 if current_item == -1: current_item = 0
self.attachments.pop(current_item) self.attachments.pop(current_item)
self.dialog.attachments.remove_item(current_item) self.dialog.attachments.remove_item(current_item)
self.check_remove_status() self.check_remove_status()
log.debug("Removed") log.debug("Removed")
def check_remove_status(self): def check_remove_status(self):
if len(self.attachments) == 0 and self.dialog.attachments.get_count() == 0: if len(self.attachments) == 0 and self.dialog.attachments.get_count() == 0:
self.dialog.remove.Enable(False) self.dialog.remove.Enable(False)

View File

@@ -1,8 +1,3 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" this package contains logic related to buffers. A buffer is a virtual representation of a group of items retrieved through the Social network API'S. from . import base as base
Ideally, new social networks added to TWBlue will have its own "buffers", and these buffers should be defined within this package, following the Twitter example. from . import twitter as twitter
Currently, the package contains the following modules:
* baseBuffers: Define a set of functions and structure to be expected in all buffers. New buffers should inherit its classes from one of the classes present here.
* twitterBuffers: All other code, specific to Twitter.
"""
from __future__ import unicode_literals

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from .account import AccountBuffer
from .base import Buffer
from .empty import EmptyBuffer

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
""" Common logic to all buffers in TWBlue."""
import logging
import config
import widgetUtils
from pubsub import pub
from wxUI import buffers
from . import base
log = logging.getLogger("controller.buffers.base.account")
class AccountBuffer(base.Buffer):
def __init__(self, parent, name, account, account_id):
super(AccountBuffer, self).__init__(parent, None, name)
log.debug("Initializing buffer %s, account %s" % (name, account,))
self.buffer = buffers.accountPanel(parent, name)
self.type = self.buffer.type
self.compose_function = None
self.session = None
self.needs_init = False
self.account = account
self.buffer.account = account
self.name = name
self.account_id = account_id
def setup_account(self):
widgetUtils.connect_event(self.buffer, widgetUtils.CHECKBOX, self.autostart, menuitem=self.buffer.autostart_account)
if self.account_id in config.app["sessions"]["ignored_sessions"]:
self.buffer.change_autostart(False)
else:
self.buffer.change_autostart(True)
if not hasattr(self, "logged"):
self.buffer.change_login(login=False)
widgetUtils.connect_event(self.buffer.login, widgetUtils.BUTTON_PRESSED, self.logout)
else:
self.buffer.change_login(login=True)
widgetUtils.connect_event(self.buffer.login, widgetUtils.BUTTON_PRESSED, self.login)
def login(self, *args, **kwargs):
del self.logged
self.setup_account()
pub.sendMessage("login", session_id=self.account_id)
def logout(self, *args, **kwargs):
self.logged = False
self.setup_account()
pub.sendMessage("logout", session_id=self.account_id)
def autostart(self, *args, **kwargs):
if self.account_id in config.app["sessions"]["ignored_sessions"]:
self.buffer.change_autostart(True)
config.app["sessions"]["ignored_sessions"].remove(self.account_id)
else:
self.buffer.change_autostart(False)
config.app["sessions"]["ignored_sessions"].append(self.account_id)
config.app.write()

View File

@@ -0,0 +1,138 @@
# -*- coding: utf-8 -*-
""" Common logic to all buffers in TWBlue."""
import logging
import wx
import output
import sound
import widgetUtils
log = logging.getLogger("controller.buffers.base.base")
class Buffer(object):
""" A basic buffer object. This should be the base class for all other derived buffers."""
def __init__(self, parent=None, function=None, session=None, *args, **kwargs):
"""Inits the main controller for this buffer:
@ parent wx.Treebook object: Container where we will put this buffer.
@ function str or None: function to be called periodically and update items on this buffer.
@ session sessionmanager.session object or None: Session handler for settings, database and data access.
"""
super(Buffer, self).__init__()
self.function = function
# Compose_function will be used to render an object on this buffer. Normally, signature is as follows:
# compose_function(item, db, relative_times, show_screen_names=False, session=None)
# Read more about compose functions in sessions/twitter/compose.py.
self.compose_function = None
self.args = args
self.kwargs = kwargs
# This will be used as a reference to the wx.Panel object wich stores the buffer GUI.
self.buffer = None
# This should countains the account associated to this buffer.
self.account = ""
# This controls whether the start_stream function should be called when starting the program.
self.needs_init = True
# if this is set to False, the buffer will be ignored on the invisible interface.
self.invisible = False
# Control variable, used to track time of execution for calls to start_stream.
self.execution_time = 0
def clear_list(self):
pass
def get_event(self, ev):
""" Catch key presses in the WX interface and generate the corresponding event names."""
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 = "destroy_status"
# Raise a Special event when pressed Shift+F10 because Wx==4.1.x does not seems to trigger this by itself.
# See https://github.com/manuelcortez/TWBlue/issues/353
elif ev.GetKeyCode() == wx.WXK_F10 and ev.ShiftDown(): event = "show_menu"
else:
event = None
ev.Skip()
if event != None:
try:
### ToDo: Remove after WX fixes issue #353 in the widgets.
if event == "show_menu":
return self.show_menu(widgetUtils.MENU, pos=self.buffer.list.list.GetPosition())
getattr(self, event)()
except AttributeError:
pass
def volume_down(self):
""" Decreases volume by 5%"""
if self.session.settings["sound"]["volume"] > 0.0:
if self.session.settings["sound"]["volume"] <= 0.05:
self.session.settings["sound"]["volume"] = 0.0
else:
self.session.settings["sound"]["volume"] -=0.05
sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100.0))
self.session.sound.play("volume_changed.ogg")
self.session.settings.write()
def volume_up(self):
""" Increases volume by 5%."""
if self.session.settings["sound"]["volume"] < 1.0:
if self.session.settings["sound"]["volume"] >= 0.95:
self.session.settings["sound"]["volume"] = 1.0
else:
self.session.settings["sound"]["volume"] +=0.05
sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100))
self.session.sound.play("volume_changed.ogg")
self.session.settings.write()
def start_stream(self, mandatory=False, play_sound=True):
pass
def get_more_items(self):
output.speak(_(u"This action is not supported for this buffer"), True)
def put_items_on_list(self, items):
pass
def remove_buffer(self):
return False
def remove_item(self, item):
f = self.buffer.list.get_selected()
self.buffer.list.remove_item(item)
self.buffer.list.select_item(f)
def bind_events(self):
pass
def get_object(self):
return self.buffer
def get_message(self):
pass
def set_list_position(self, reversed=False):
if reversed == False:
self.buffer.list.select_item(-1)
else:
self.buffer.list.select_item(0)
def reply(self):
pass
def send_message(self):
pass
def share_item(self):
pass
def destroy_status(self):
pass
def post_status(self, *args, **kwargs):
pass
def save_positions(self):
try:
self.session.db[self.name+"_pos"]=self.buffer.list.get_selected()
except AttributeError:
pass

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
import logging
from wxUI import buffers
from . import base
log = logging.getLogger("controller.buffers.base.empty")
class EmptyBuffer(base.Buffer):
def __init__(self, parent, name, account):
super(EmptyBuffer, self).__init__(parent=parent)
log.debug("Initializing buffer %s, account %s" % (name, account,))
self.buffer = buffers.emptyPanel(parent, name)
self.type = self.buffer.type
self.compose_function = None
self.account = account
self.buffer.account = account
self.name = name
self.session = None
self.needs_init = True

View File

@@ -1,203 +0,0 @@
# -*- coding: utf-8 -*-
""" Common logic to all buffers in TWBlue."""
from __future__ import unicode_literals
from builtins import object
import logging
import wx
import output
import config
import sound
import widgetUtils
from pubsub import pub
from wxUI import buffers
log = logging.getLogger("controller.buffers.baseBuffers")
def _items_exist(function):
""" A decorator to execute a function only if the selected buffer contains at least one item."""
def function_(self, *args, **kwargs):
if self.buffer.list.get_count() > 0:
function(self, *args, **kwargs)
return function_
class buffer(object):
""" A basic buffer object. This should be the base class for all other derived buffers."""
def __init__(self, parent=None, function=None, session=None, *args, **kwargs):
"""Inits the main controller for this buffer:
@ parent wx.Treebook object: Container where we will put this buffer.
@ function str or None: function to be called periodically and update items on this buffer.
@ session sessionmanager.session object or None: Session handler for settings, database and data access.
"""
super(buffer, self).__init__()
self.function = function
# Compose_function will be used to render an object on this buffer. Normally, signature is as follows:
# compose_function(item, db, relative_times, show_screen_names=False, session=None)
# Read more about compose functions in twitter/compose.py.
self.compose_function = None
self.args = args
self.kwargs = kwargs
# This will be used as a reference to the wx.Panel object wich stores the buffer GUI.
self.buffer = None
# This should countains the account associated to this buffer.
self.account = ""
# This controls whether the start_stream function should be called when starting the program.
self.needs_init = True
# if this is set to False, the buffer will be ignored on the invisible interface.
self.invisible = False
# Control variable, used to track time of execution for calls to start_stream.
self.execution_time = 0
def clear_list(self):
pass
def get_event(self, ev):
""" Catch key presses in the WX interface and generate the corresponding event names."""
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 = "destroy_status"
else:
event = None
ev.Skip()
if event != None:
try:
getattr(self, event)()
except AttributeError:
pass
def volume_down(self):
""" Decreases volume by 5%"""
if self.session.settings["sound"]["volume"] > 0.0:
if self.session.settings["sound"]["volume"] <= 0.05:
self.session.settings["sound"]["volume"] = 0.0
else:
self.session.settings["sound"]["volume"] -=0.05
sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100.0))
self.session.sound.play("volume_changed.ogg")
self.session.settings.write()
def volume_up(self):
""" Increases volume by 5%."""
if self.session.settings["sound"]["volume"] < 1.0:
if self.session.settings["sound"]["volume"] >= 0.95:
self.session.settings["sound"]["volume"] = 1.0
else:
self.session.settings["sound"]["volume"] +=0.05
sound.URLPlayer.player.audio_set_volume(int(self.session.settings["sound"]["volume"]*100))
self.session.sound.play("volume_changed.ogg")
self.session.settings.write()
def start_stream(self, mandatory=False, play_sound=True):
pass
def get_more_items(self):
output.speak(_(u"This action is not supported for this buffer"), True)
def put_items_on_list(self, items):
pass
def remove_buffer(self):
return False
def remove_item(self, item):
f = self.buffer.list.get_selected()
self.buffer.list.remove_item(item)
self.buffer.list.select_item(f)
def bind_events(self):
pass
def get_object(self):
return self.buffer
def get_message(self):
pass
def set_list_position(self, reversed=False):
if reversed == False:
self.buffer.list.select_item(-1)
else:
self.buffer.list.select_item(0)
def reply(self):
pass
def send_message(self):
pass
def share_item(self):
pass
def destroy_status(self):
pass
def post_status(self, *args, **kwargs):
pass
def save_positions(self):
try:
self.session.db[self.name+"_pos"]=self.buffer.list.get_selected()
except AttributeError:
pass
class accountPanel(buffer):
def __init__(self, parent, name, account, account_id):
super(accountPanel, self).__init__(parent, None, name)
log.debug("Initializing buffer %s, account %s" % (name, account,))
self.buffer = buffers.accountPanel(parent, name)
self.type = self.buffer.type
self.compose_function = None
self.session = None
self.needs_init = False
self.account = account
self.buffer.account = account
self.name = name
self.account_id = account_id
def setup_account(self):
widgetUtils.connect_event(self.buffer, widgetUtils.CHECKBOX, self.autostart, menuitem=self.buffer.autostart_account)
if self.account_id in config.app["sessions"]["ignored_sessions"]:
self.buffer.change_autostart(False)
else:
self.buffer.change_autostart(True)
if not hasattr(self, "logged"):
self.buffer.change_login(login=False)
widgetUtils.connect_event(self.buffer.login, widgetUtils.BUTTON_PRESSED, self.logout)
else:
self.buffer.change_login(login=True)
widgetUtils.connect_event(self.buffer.login, widgetUtils.BUTTON_PRESSED, self.login)
def login(self, *args, **kwargs):
del self.logged
self.setup_account()
pub.sendMessage("login", session_id=self.account_id)
def logout(self, *args, **kwargs):
self.logged = False
self.setup_account()
pub.sendMessage("logout", session_id=self.account_id)
def autostart(self, *args, **kwargs):
if self.account_id in config.app["sessions"]["ignored_sessions"]:
self.buffer.change_autostart(True)
config.app["sessions"]["ignored_sessions"].remove(self.account_id)
else:
self.buffer.change_autostart(False)
config.app["sessions"]["ignored_sessions"].append(self.account_id)
config.app.write()
class emptyPanel(buffer):
def __init__(self, parent, name, account):
super(emptyPanel, self).__init__(parent=parent)
log.debug("Initializing buffer %s, account %s" % (name, account,))
self.buffer = buffers.emptyPanel(parent, name)
self.type = self.buffer.type
self.compose_function = None
self.account = account
self.buffer.account = account
self.name = name
self.session = None
self.needs_init = True

View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from .base import BaseBuffer
from .directMessages import DirectMessagesBuffer, SentDirectMessagesBuffer
from .list import ListBuffer
from .people import PeopleBuffer
from .trends import TrendsBuffer
from .search import SearchBuffer, SearchPeopleBuffer, ConversationBuffer

View File

@@ -0,0 +1,655 @@
# -*- coding: utf-8 -*-
import time
import platform
if platform.system() == "Windows":
import wx
from wxUI import buffers, dialogs, commonMessageDialogs, menus
from controller import user
elif platform.system() == "Linux":
from gi.repository import Gtk
from gtkUI import buffers, dialogs, commonMessageDialogs
from controller import messages
import widgetUtils
import arrow
import webbrowser
import output
import config
import sound
import languageHandler
import logging
from audio_services import youtube_utils
from controller.buffers.base import base
from sessions.twitter import compose, utils, reduce
from mysc.thread_utils import call_threaded
from tweepy.errors import TweepyException
from tweepy.cursor import Cursor
from pubsub import pub
from sessions.twitter.long_tweets import twishort, tweets
log = logging.getLogger("controller.buffers")
def _tweets_exist(function):
""" A decorator to execute a function only if the selected buffer contains at least one item."""
def function_(self, *args, **kwargs):
if self.buffer.list.get_count() > 0:
function(self, *args, **kwargs)
return function_
class BaseBuffer(base.Buffer):
def __init__(self, parent, function, name, sessionObject, account, sound=None, bufferType=None, compose_func="compose_tweet", *args, **kwargs):
super(BaseBuffer, self).__init__(parent, function, *args, **kwargs)
log.debug("Initializing buffer %s, account %s" % (name, account,))
if bufferType != None:
self.buffer = getattr(buffers, bufferType)(parent, name)
else:
self.buffer = buffers.basePanel(parent, name)
self.invisible = True
self.name = name
self.type = self.buffer.type
self.session = sessionObject
self.compose_function = getattr(compose, compose_func)
log.debug("Compose_function: %s" % (self.compose_function,))
self.account = account
self.buffer.account = account
self.bind_events()
self.sound = sound
if "-timeline" in self.name or "-favorite" in self.name:
self.finished_timeline = False
# Add a compatibility layer for username based timelines from config.
# ToDo: Remove this in some new versions of the client, when user ID timelines become mandatory.
try:
int(self.kwargs["user_id"])
except ValueError:
self.is_screen_name = True
self.kwargs["screen_name"] = self.kwargs["user_id"]
self.kwargs.pop("user_id")
def get_buffer_name(self):
""" Get buffer name from a set of different techniques."""
# firstly let's take the easier buffers.
basic_buffers = dict(home_timeline=_(u"Home"), mentions=_(u"Mentions"), direct_messages=_(u"Direct messages"), sent_direct_messages=_(u"Sent direct messages"), sent_tweets=_(u"Sent tweets"), favourites=_(u"Likes"), followers=_(u"Followers"), friends=_(u"Friends"), blocked=_(u"Blocked users"), muted=_(u"Muted users"))
if self.name in list(basic_buffers.keys()):
return basic_buffers[self.name]
# Check user timelines
elif hasattr(self, "username"):
if "-timeline" in self.name:
return _(u"{username}'s timeline").format(username=self.username,)
elif "-favorite" in self.name:
return _(u"{username}'s likes").format(username=self.username,)
elif "-followers" in self.name:
return _(u"{username}'s followers").format(username=self.username,)
elif "-friends" in self.name:
return _(u"{username}'s friends").format(username=self.username,)
log.error("Error getting name for buffer %s" % (self.name,))
return _(u"Unknown buffer")
def post_status(self, *args, **kwargs):
item = None
title = _(u"Tweet")
caption = _(u"Write the tweet here")
tweet = messages.tweet(self.session, title, caption, "")
if tweet.message.get_response() == widgetUtils.OK:
if config.app["app-settings"]["remember_mention_and_longtweet"]:
config.app["app-settings"]["longtweet"] = tweet.message.long_tweet.GetValue()
config.app.write()
text = tweet.message.get_text()
if len(text) > 280 and tweet.message.get("long_tweet") == True:
if not hasattr(tweet, "attachments"):
text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text)
else:
text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text, 1)
if not hasattr(tweet, "attachments") or len(tweet.attachments) == 0:
item = self.session.api_call(call_name="update_status", status=text, _sound="tweet_send.ogg", tweet_mode="extended")
else:
call_threaded(self.post_with_media, text=text, attachments=tweet.attachments)
# We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item.
# but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly.
# if item != None:
# pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"])
if hasattr(tweet.message, "destroy"): tweet.message.destroy()
self.session.settings.write()
def post_with_media(self, text, attachments):
media_ids = []
for i in attachments:
img = self.session.twitter.media_upload(i["file"])
self.session.twitter.create_media_metadata(media_id=img.media_id, alt_text=i["description"])
media_ids.append(img.media_id)
item = self.session.twitter.update_status(status=text, media_ids=media_ids)
# We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item.
# but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly.
# if item != None:
# pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"])
def get_formatted_message(self):
if self.type == "dm" or self.name == "direct_messages":
return self.compose_function(self.get_right_tweet(), self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)[1]
return self.get_message()
def get_message(self):
tweet = self.get_right_tweet()
return " ".join(self.compose_function(tweet, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session))
def get_full_tweet(self):
tweet = self.get_right_tweet()
tweetsList = []
tweet_id = tweet.id
message = None
if hasattr(tweet, "message"):
message = tweet.message
try:
tweet = self.session.twitter.get_status(id=tweet_id, include_ext_alt_text=True, tweet_mode="extended")
tweet.full_text = utils.expand_urls(tweet.full_text, tweet.entities)
except TweepyException as e:
utils.twitter_error(e)
return
if message != None:
tweet.message = message
l = tweets.is_long(tweet)
while l != False:
tweetsList.append(tweet)
try:
tweet = self.session.twitter.get_status(id=l, include_ext_alt_text=True, tweet_mode="extended")
tweet.full_text = utils.expand_urls(tweet.full_text, tweet.entities)
except TweepyException as e:
utils.twitter_error(e)
return
l = tweets.is_long(tweet)
if l == False:
tweetsList.append(tweet)
return (tweet, tweetsList)
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
# starts stream every 3 minutes.
current_time = time.time()
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
self.execution_time = current_time
log.debug("Starting stream for buffer %s, account %s and type %s" % (self.name, self.account, self.type))
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
if self.name != "direct_messages":
val = self.session.call_paged(self.function, *self.args, **self.kwargs)
else:
# 50 results are allowed per API call, so let's assume max value can be 50.
# reference: https://developer.twitter.com/en/docs/twitter-api/v1/direct-messages/sending-and-receiving/api-reference/list-events
if self.session.settings["general"]["max_tweets_per_call"] > 50:
count = 50
else:
count = self.session.settings["general"]["max_tweets_per_call"]
# try to retrieve the cursor for the current buffer.
try:
val = getattr(self.session.twitter, self.function)(return_cursors=True, count=count, *self.args, **self.kwargs)
if type(val) == tuple:
val, cursor = val
if type(cursor) == tuple:
cursor = cursor[1]
cursors = self.session.db["cursors"]
cursors[self.name] = cursor
self.session.db["cursors"] = cursors
results = [i for i in val]
val = results
val.reverse()
log.debug("Retrieved %d items from the cursored search on function %s." %(len(val), self.function))
user_ids = [item.message_create["sender_id"] for item in val]
self.session.save_users(user_ids)
except TweepyException as e:
log.exception("Error %s" % (str(e)))
return
number_of_items = self.session.order_buffer(self.name, val)
log.debug("Number of items retrieved: %d" % (number_of_items,))
self.put_items_on_list(number_of_items)
if hasattr(self, "finished_timeline") and self.finished_timeline == False:
if "-timeline" in self.name:
self.username = val[0].user.screen_name
elif "-favorite" in self.name:
self.username = self.session.api_call("get_user", **self.kwargs).screen_name
self.finished_timeline = True
if number_of_items > 0 and self.name != "sent_tweets" and self.name != "sent_direct_messages" and self.sound != None and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and play_sound == True:
self.session.sound.play(self.sound)
# Autoread settings
if avoid_autoreading == False and mandatory == True and number_of_items > 0 and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
self.auto_read(number_of_items)
return number_of_items
def auto_read(self, number_of_items):
if number_of_items == 1 and self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
if self.session.settings["general"]["reverse_timelines"] == False:
tweet = self.session.db[self.name][-1]
else:
tweet = self.session.db[self.name][0]
output.speak(_(u"New tweet in {0}").format(self.get_buffer_name()))
output.speak(" ".join(self.compose_function(tweet, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)))
elif number_of_items > 1 and self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
output.speak(_(u"{0} new tweets in {1}.").format(number_of_items, self.get_buffer_name()))
def get_more_items(self):
elements = []
if self.session.settings["general"]["reverse_timelines"] == False:
last_id = self.session.db[self.name][0].id
else:
last_id = self.session.db[self.name][-1].id
try:
items = getattr(self.session.twitter, self.function)(max_id=last_id, count=self.session.settings["general"]["max_tweets_per_call"], *self.args, **self.kwargs)
except TweepyException as e:
log.exception("Error %s" % (str(e)))
return
if items == None:
return
items_db = self.session.db[self.name]
self.session.add_users_from_results(items)
for i in items:
if utils.is_allowed(i, self.session.settings, self.name) == True and utils.find_item(i, self.session.db[self.name]) == None:
i = reduce.reduce_tweet(i)
i = self.session.check_quoted_status(i)
i = self.session.check_long_tweet(i)
elements.append(i)
if self.session.settings["general"]["reverse_timelines"] == False:
items_db.insert(0, i)
else:
items_db.append(i)
self.session.db[self.name] = items_db
selection = self.buffer.list.get_selected()
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
if self.session.settings["general"]["reverse_timelines"] == False:
for i in elements:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(True, *tweet)
else:
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(False, *tweet)
self.buffer.list.select_item(selection)
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
def remove_buffer(self, force=False):
if "-timeline" in self.name:
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-9] in self.session.settings["other_buffers"]["timelines"]:
self.session.settings["other_buffers"]["timelines"].remove(self.name[:-9])
self.session.settings.write()
if self.name in self.session.db:
self.session.db.pop(self.name)
return True
elif dlg == widgetUtils.NO:
return False
elif "favorite" in self.name:
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-9] in self.session.settings["other_buffers"]["favourites_timelines"]:
self.session.settings["other_buffers"]["favourites_timelines"].remove(self.name[:-9])
if self.name in self.session.db:
self.session.db.pop(self.name)
self.session.settings.write()
return True
elif dlg == widgetUtils.NO:
return False
else:
output.speak(_(u"This buffer is not a timeline; it can't be deleted."), True)
return False
def remove_tweet(self, id):
if type(self.session.db[self.name]) == dict: return
items = self.session.db[self.name]
for i in range(0, len(items)):
if items[i].id == id:
items.pop(i)
self.remove_item(i)
self.session.db[self.name] = items
def put_items_on_list(self, number_of_items):
list_to_use = self.session.db[self.name]
if number_of_items == 0 and self.session.settings["general"]["persist_size"] == 0: return
log.debug("The list contains %d items " % (self.buffer.list.get_count(),))
log.debug("Putting %d items on the list" % (number_of_items,))
if self.buffer.list.get_count() == 0:
for i in list_to_use:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(False, *tweet)
self.buffer.set_position(self.session.settings["general"]["reverse_timelines"])
elif self.buffer.list.get_count() > 0 and number_of_items > 0:
if self.session.settings["general"]["reverse_timelines"] == False:
items = list_to_use[len(list_to_use)-number_of_items:]
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(False, *tweet)
else:
items = list_to_use[0:number_of_items]
items.reverse()
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(True, *tweet)
log.debug("Now the list contains %d items " % (self.buffer.list.get_count(),))
def add_new_item(self, item):
tweet = self.compose_function(item, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
if self.session.settings["general"]["reverse_timelines"] == False:
self.buffer.list.insert_item(False, *tweet)
else:
self.buffer.list.insert_item(True, *tweet)
if self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
output.speak(" ".join(tweet[:2]), speech=self.session.settings["reporting"]["speech_reporting"], braille=self.session.settings["reporting"]["braille_reporting"])
#Improve performance on Windows
# if platform.system() == "Windows":
# call_threaded(utils.is_audio,item)
def bind_events(self):
log.debug("Binding events...")
self.buffer.set_focus_function(self.onFocus)
widgetUtils.connect_event(self.buffer.list.list, widgetUtils.KEYPRESS, self.get_event)
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.post_status, self.buffer.tweet)
# if self.type == "baseBuffer":
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.share_item, self.buffer.retweet)
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.send_message, self.buffer.dm)
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.reply, self.buffer.reply)
# Replace for the correct way in other platforms.
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_ITEM_RIGHT_CLICK, self.show_menu)
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_KEY_DOWN, self.show_menu_by_key)
def show_menu(self, ev, pos=0, *args, **kwargs):
if self.buffer.list.get_count() == 0: return
if self.name == "sent_tweets" or self.name == "direct_messages":
menu = menus.sentPanelMenu()
elif self.name == "direct_messages":
menu = menus.dmPanelMenu()
widgetUtils.connect_event(menu, widgetUtils.MENU, self.send_message, menuitem=menu.reply)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.user_actions, menuitem=menu.userActions)
else:
menu = menus.basePanelMenu()
widgetUtils.connect_event(menu, widgetUtils.MENU, self.reply, menuitem=menu.reply)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.user_actions, menuitem=menu.userActions)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.share_item, menuitem=menu.retweet)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.fav, menuitem=menu.fav)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.unfav, menuitem=menu.unfav)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.url_, menuitem=menu.openUrl)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.audio, menuitem=menu.play)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.view, menuitem=menu.view)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.copy, menuitem=menu.copy)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.destroy_status, menuitem=menu.remove)
if hasattr(menu, "openInBrowser"):
widgetUtils.connect_event(menu, widgetUtils.MENU, self.open_in_browser, menuitem=menu.openInBrowser)
if pos != 0:
self.buffer.PopupMenu(menu, pos)
else:
self.buffer.PopupMenu(menu, ev.GetPosition())
def view(self, *args, **kwargs):
pub.sendMessage("execute-action", action="view_item")
def copy(self, *args, **kwargs):
pub.sendMessage("execute-action", action="copy_to_clipboard")
def user_actions(self, *args, **kwargs):
pub.sendMessage("execute-action", action="follow")
def fav(self, *args, **kwargs):
pub.sendMessage("execute-action", action="add_to_favourites")
def unfav(self, *args, **kwargs):
pub.sendMessage("execute-action", action="remove_from_favourites")
def delete_item_(self, *args, **kwargs):
pub.sendMessage("execute-action", action="delete_item")
def url_(self, *args, **kwargs):
self.url()
def show_menu_by_key(self, ev):
if self.buffer.list.get_count() == 0:
return
if ev.GetKeyCode() == wx.WXK_WINDOWS_MENU:
self.show_menu(widgetUtils.MENU, pos=self.buffer.list.list.GetPosition())
def get_tweet(self):
if hasattr(self.session.db[self.name][self.buffer.list.get_selected()], "retweeted_status"):
tweet = self.session.db[self.name][self.buffer.list.get_selected()].retweeted_status
else:
tweet = self.session.db[self.name][self.buffer.list.get_selected()]
return tweet
def get_right_tweet(self):
tweet = self.session.db[self.name][self.buffer.list.get_selected()]
return tweet
@_tweets_exist
def reply(self, *args, **kwargs):
tweet = self.get_right_tweet()
user = self.session.get_user(tweet.user)
screen_name = user.screen_name
id = tweet.id
twishort_enabled = hasattr(tweet, "twishort")
users = utils.get_all_mentioned(tweet, self.session.db, field="screen_name")
ids = utils.get_all_mentioned(tweet, self.session.db, field="id")
# Build the window title
if len(users) < 1:
title=_("Reply to {arg0}").format(arg0=screen_name)
else:
title=_("Reply")
message = messages.reply(self.session, title, _(u"Reply to %s") % (screen_name,), "", users=users, ids=ids)
if message.message.get_response() == widgetUtils.OK:
if config.app["app-settings"]["remember_mention_and_longtweet"]:
config.app["app-settings"]["longtweet"] = message.message.long_tweet.GetValue()
if len(users) > 0:
config.app["app-settings"]["mention_all"] = message.message.mentionAll.GetValue()
config.app.write()
params = {"_sound": "reply_send.ogg", "in_reply_to_status_id": id, "tweet_mode": "extended"}
text = message.message.get_text()
if twishort_enabled == False:
excluded_ids = message.get_ids()
params["exclude_reply_user_ids"] =excluded_ids
params["auto_populate_reply_metadata"] =True
else:
mentioned_people = message.get_people()
text = "@"+screen_name+" "+mentioned_people+u" "+text
if len(text) > 280 and message.message.get("long_tweet") == True:
if message.image == None:
text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text)
else:
text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text, 1)
params["status"] = text
if message.image == None:
params["call_name"] = "update_status"
else:
params["call_name"] = "update_status_with_media"
params["media"] = message.file
item = self.session.api_call(**params)
# We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item.
# but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly.
# if item != None:
# pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"])
if hasattr(message.message, "destroy"): message.message.destroy()
self.session.settings.write()
@_tweets_exist
def send_message(self, *args, **kwargs):
tweet = self.get_right_tweet()
if self.type == "dm":
screen_name = self.session.get_user(tweet.message_create["sender_id"]).screen_name
users = [screen_name]
elif self.type == "people":
screen_name = tweet.screen_name
users = [screen_name]
else:
screen_name = self.session.get_user(tweet.user).screen_name
users = utils.get_all_users(tweet, self.session)
dm = messages.dm(self.session, _(u"Direct message to %s") % (screen_name,), _(u"New direct message"), users)
if dm.message.get_response() == widgetUtils.OK:
screen_name = dm.message.get("cb")
user = self.session.get_user_by_screen_name(screen_name)
recipient_id = user
text = dm.message.get_text()
val = self.session.api_call(call_name="send_direct_message", recipient_id=recipient_id, text=text)
if val != None:
sent_dms = self.session.db["sent_direct_messages"]
if self.session.settings["general"]["reverse_timelines"] == False:
sent_dms.append(val)
else:
sent_dms.insert(0, val)
self.session.db["sent_direct_messages"] = sent_dms
pub.sendMessage("sent-dm", data=val, user=self.session.db["user_name"])
if hasattr(dm.message, "destroy"): dm.message.destroy()
@_tweets_exist
def share_item(self, *args, **kwargs):
tweet = self.get_right_tweet()
id = tweet.id
if self.session.settings["general"]["retweet_mode"] == "ask":
answer = commonMessageDialogs.retweet_question(self.buffer)
if answer == widgetUtils.YES:
self._retweet_with_comment(tweet, id)
elif answer == widgetUtils.NO:
self._direct_retweet(id)
elif self.session.settings["general"]["retweet_mode"] == "direct":
self._direct_retweet(id)
else:
self._retweet_with_comment(tweet, id)
def _retweet_with_comment(self, tweet, id, comment=''):
# If quoting a retweet, let's quote the original tweet instead the retweet.
if hasattr(tweet, "retweeted_status"):
tweet = tweet.retweeted_status
if hasattr(tweet, "full_text"):
comments = tweet.full_text
else:
comments = tweet.text
retweet = messages.tweet(self.session, _(u"Quote"), _(u"Add your comment to the tweet"), u"“@%s: %s" % (self.session.get_user(tweet.user).screen_name, comments), max=256, messageType="retweet")
if comment != '':
retweet.message.set_text(comment)
if retweet.message.get_response() == widgetUtils.OK:
text = retweet.message.get_text()
text = text+" https://twitter.com/{0}/status/{1}".format(self.session.get_user(tweet.user).screen_name, id)
if retweet.image == None:
# We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item.
# but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly.
item = self.session.api_call(call_name="update_status", _sound="retweet_send.ogg", status=text, in_reply_to_status_id=id, tweet_mode="extended")
# if item != None:
# new_item = self.session.twitter.get_status(id=item.id, include_ext_alt_text=True, tweet_mode="extended")
# pub.sendMessage("sent-tweet", data=new_item, user=self.session.db["user_name"])
else:
call_threaded(self.session.api_call, call_name="update_status", _sound="retweet_send.ogg", status=text, media=retweet.image)
if hasattr(retweet.message, "destroy"): retweet.message.destroy()
def _direct_retweet(self, id):
item = self.session.api_call(call_name="retweet", _sound="retweet_send.ogg", id=id)
# We will no longer will reuse the sent item from here as Streaming API should give us the new and correct item.
# but in case we'd need it, just uncomment the following couple of lines and make sure we reduce the item correctly.
# if item != None:
# Retweets are returned as non-extended tweets, so let's get the object as extended
# just before sending the event message. See https://github.com/manuelcortez/TWBlue/issues/253
# item = self.session.twitter.get_status(id=item.id, include_ext_alt_text=True, tweet_mode="extended")
# pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"])
def onFocus(self, *args, **kwargs):
tweet = self.get_tweet()
if platform.system() == "Windows" and self.session.settings["general"]["relative_times"] == True:
# fix this:
original_date = arrow.get(self.session.db[self.name][self.buffer.list.get_selected()].created_at, locale="en")
ts = original_date.humanize(locale=languageHandler.getLanguage())
self.buffer.list.list.SetItem(self.buffer.list.get_selected(), 2, ts)
if self.session.settings['sound']['indicate_audio'] and utils.is_audio(tweet):
self.session.sound.play("audio.ogg")
if self.session.settings['sound']['indicate_geo'] and utils.is_geocoded(tweet):
self.session.sound.play("geo.ogg")
if self.session.settings['sound']['indicate_img'] and utils.is_media(tweet):
self.session.sound.play("image.ogg")
def audio(self, url='', *args, **kwargs):
if sound.URLPlayer.player.is_playing():
return sound.URLPlayer.stop_audio()
tweet = self.get_tweet()
if tweet == None: return
urls = utils.find_urls(tweet, twitter_media=True)
if len(urls) == 1:
url=urls[0]
elif len(urls) > 1:
urls_list = dialogs.urlList.urlList()
urls_list.populate_list(urls)
if urls_list.get_response() == widgetUtils.OK:
url=urls_list.get_string()
if hasattr(urls_list, "destroy"): urls_list.destroy()
if url != '':
# try:
sound.URLPlayer.play(url, self.session.settings["sound"]["volume"])
# except:
# log.error("Exception while executing audio method.")
# @_tweets_exist
def url(self, url='', announce=True, *args, **kwargs):
if url == '':
tweet = self.get_tweet()
urls = utils.find_urls(tweet)
if len(urls) == 1:
url=urls[0]
elif len(urls) > 1:
urls_list = dialogs.urlList.urlList()
urls_list.populate_list(urls)
if urls_list.get_response() == widgetUtils.OK:
url=urls_list.get_string()
if hasattr(urls_list, "destroy"): urls_list.destroy()
if url != '':
if announce:
output.speak(_(u"Opening URL..."), True)
webbrowser.open_new_tab(url)
def clear_list(self):
dlg = commonMessageDialogs.clear_list()
if dlg == widgetUtils.YES:
self.session.db[self.name] = []
self.buffer.list.clear()
@_tweets_exist
def destroy_status(self, *args, **kwargs):
index = self.buffer.list.get_selected()
if self.type == "events" or self.type == "people" or self.type == "empty" or self.type == "account": return
answer = commonMessageDialogs.delete_tweet_dialog(None)
if answer == widgetUtils.YES:
items = self.session.db[self.name]
try:
if self.name == "direct_messages" or self.name == "sent_direct_messages":
self.session.twitter.delete_direct_message(id=self.get_right_tweet().id)
items.pop(index)
else:
self.session.twitter.destroy_status(id=self.get_right_tweet().id)
items.pop(index)
self.buffer.list.remove_item(index)
except TweepyException:
self.session.sound.play("error.ogg")
self.session.db[self.name] = items
@_tweets_exist
def user_details(self):
tweet = self.get_right_tweet()
if self.type == "dm":
users = [self.session.get_user(tweet.message_create["sender_id"]).screen_name]
elif self.type == "people":
users = [tweet.screen_name]
else:
users = utils.get_all_users(tweet, self.session)
dlg = dialogs.utils.selectUserDialog(title=_(u"User details"), users=users)
if dlg.get_response() == widgetUtils.OK:
user.profileController(session=self.session, user=dlg.get_user())
if hasattr(dlg, "destroy"): dlg.destroy()
def get_quoted_tweet(self, tweet):
quoted_tweet = self.session.twitter.get_status(id=tweet.id)
quoted_tweet.text = utils.find_urls_in_text(quoted_tweet.text, quoted_tweet.entities)
l = tweets.is_long(quoted_tweet)
id = tweets.get_id(l)
original_tweet = self.session.twitter.get_status(id=id)
original_tweet.text = utils.find_urls_in_text(original_tweet.text, original_tweet.entities)
return compose.compose_quoted_tweet(quoted_tweet, original_tweet, self.session.db, self.session.settings["general"]["relative_times"])
def get_item_url(self):
tweet = self.get_tweet()
url = "https://twitter.com/{screen_name}/status/{tweet_id}".format(screen_name=self.session.get_user(tweet.user).screen_name, tweet_id=tweet.id)
return url
def open_in_browser(self, *args, **kwargs):
url = self.get_item_url()
output.speak(_(u"Opening item in web browser..."))
webbrowser.open(url)

View File

@@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
import platform
import widgetUtils
import arrow
import webbrowser
import output
import config
import languageHandler
import logging
from controller import messages
from sessions.twitter import compose, utils
from mysc.thread_utils import call_threaded
from tweepy.errors import TweepyException
from pubsub import pub
from . import base
log = logging.getLogger("controller.buffers.twitter.dmBuffer")
class DirectMessagesBuffer(base.BaseBuffer):
def get_more_items(self):
# 50 results are allowed per API call, so let's assume max value can be 50.
# reference: https://developer.twitter.com/en/docs/twitter-api/v1/direct-messages/sending-and-receiving/api-reference/list-events
if self.session.settings["general"]["max_tweets_per_call"] > 50:
count = 50
else:
count = self.session.settings["general"]["max_tweets_per_call"]
total = 0
# try to retrieve the cursor for the current buffer.
cursor = self.session.db["cursors"].get(self.name)
try:
items = getattr(self.session.twitter, self.function)(return_cursors=True, cursor=cursor, count=count, *self.args, **self.kwargs)
if type(items) == tuple:
items, cursor = items
if type(cursor) == tuple:
cursor = cursor[1]
cursors = self.session.db["cursors"]
cursors[self.name] = cursor
self.session.db["cursors"] = cursors
results = [i for i in items]
items = results
log.debug("Retrieved %d items for cursored search in function %s" % (len(items), self.function))
except TweepyException as e:
log.exception("Error %s" % (str(e)))
return
if items == None:
return
sent = []
received = []
sent_dms = self.session.db["sent_direct_messages"]
received_dms = self.session.db["direct_messages"]
for i in items:
if int(i.message_create["sender_id"]) == self.session.db["user_id"]:
if self.session.settings["general"]["reverse_timelines"] == False:
sent_dms.insert(0, i)
sent.append(i)
else:
sent_dms.append(i)
sent.insert(0, i)
else:
if self.session.settings["general"]["reverse_timelines"] == False:
received_dms.insert(0, i)
received.append(i)
else:
received_dms.append(i)
received.insert(0, i)
total = total+1
self.session.db["direct_messages"] = received_dms
self.session.db["sent_direct_messages"] = sent_dms
user_ids = [item.message_create["sender_id"] for item in items]
self.session.save_users(user_ids)
pub.sendMessage("more-sent-dms", data=sent, account=self.session.db["user_name"])
selected = self.buffer.list.get_selected()
if self.session.settings["general"]["reverse_timelines"] == True:
for i in received:
if int(i.message_create["sender_id"]) == self.session.db["user_id"]:
continue
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(True, *tweet)
self.buffer.list.select_item(selected)
else:
for i in received:
if int(i.message_create["sender_id"]) == self.session.db["user_id"]:
continue
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(True, *tweet)
output.speak(_(u"%s items retrieved") % (total), True)
def reply(self, *args, **kwargs):
tweet = self.get_right_tweet()
screen_name = self.session.get_user(tweet.message_create["sender_id"]).screen_name
message = messages.reply(self.session, _(u"Mention"), _(u"Mention to %s") % (screen_name,), "@%s " % (screen_name,), [screen_name,])
if message.message.get_response() == widgetUtils.OK:
if config.app["app-settings"]["remember_mention_and_longtweet"]:
config.app["app-settings"]["longtweet"] = message.message.long_tweet.GetValue()
config.app.write()
if message.image == None:
item = self.session.api_call(call_name="update_status", _sound="reply_send.ogg", status=message.message.get_text(), tweet_mode="extended")
if item != None:
pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"])
else:
call_threaded(self.session.api_call, call_name="update_status_with_media", _sound="reply_send.ogg", status=message.message.get_text(), media=message.file)
if hasattr(message.message, "destroy"): message.message.destroy()
def onFocus(self, *args, **kwargs):
tweet = self.get_tweet()
if platform.system() == "Windows" and self.session.settings["general"]["relative_times"] == True:
# fix this:
original_date = arrow.get(int(tweet.created_timestamp))
ts = original_date.humanize(locale=languageHandler.getLanguage())
self.buffer.list.list.SetItem(self.buffer.list.get_selected(), 2, ts)
if self.session.settings['sound']['indicate_audio'] and utils.is_audio(tweet):
self.session.sound.play("audio.ogg")
if self.session.settings['sound']['indicate_img'] and utils.is_media(tweet):
self.session.sound.play("image.ogg")
def clear_list(self):
dlg = commonMessageDialogs.clear_list()
if dlg == widgetUtils.YES:
self.session.db[self.name] = []
self.buffer.list.clear()
def auto_read(self, number_of_items):
if number_of_items == 1 and self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
if self.session.settings["general"]["reverse_timelines"] == False:
tweet = self.session.db[self.name][-1]
else:
tweet = self.session.db[self.name][0]
output.speak(_(u"New direct message"))
output.speak(" ".join(self.compose_function(tweet, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)))
elif number_of_items > 1 and self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
output.speak(_(u"{0} new direct messages.").format(number_of_items,))
def open_in_browser(self, *args, **kwargs):
output.speak(_(u"This action is not supported in the buffer yet."))
class SentDirectMessagesBuffer(DirectMessagesBuffer):
def __init__(self, *args, **kwargs):
super(SentDirectMessagesBuffer, self).__init__(*args, **kwargs)
if ("sent_direct_messages" in self.session.db) == False:
self.session.db["sent_direct_messages"] = []
def get_more_items(self):
output.speak(_(u"Getting more items cannot be done in this buffer. Use the direct messages buffer instead."))
def start_stream(self, *args, **kwargs):
pass
def put_more_items(self, items):
if self.session.settings["general"]["reverse_timelines"] == True:
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(False, *tweet)
else:
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)
self.buffer.list.insert_item(False, *tweet)

View File

@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
import platform
if platform.system() == "Windows":
from wxUI import dialogs, commonMessageDialogs
elif platform.system() == "Linux":
from gi.repository import Gtk
from gtkUI import dialogs, commonMessageDialogs
import widgetUtils
import logging
from tweepy.cursor import Cursor
from . import base
log = logging.getLogger("controller.buffers.twitter.listBuffer")
class ListBuffer(base.BaseBuffer):
def __init__(self, parent, function, name, sessionObject, account, sound=None, bufferType=None, list_id=None, *args, **kwargs):
super(ListBuffer, self).__init__(parent, function, name, sessionObject, account, sound=None, bufferType=None, *args, **kwargs)
self.users = []
self.list_id = list_id
self.kwargs["list_id"] = list_id
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
self.get_user_ids()
super(ListBuffer, self).start_stream(mandatory, play_sound, avoid_autoreading)
def get_user_ids(self):
for i in Cursor(self.session.twitter.get_list_members, list_id=self.list_id, include_entities=False, skip_status=True, count=5000).items():
if i.id not in self.users:
self.users.append(i.id)
def remove_buffer(self, force=False):
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-5] in self.session.settings["other_buffers"]["lists"]:
self.session.settings["other_buffers"]["lists"].remove(self.name[:-5])
if self.name in self.session.db:
self.session.db.pop(self.name)
self.session.settings.write()
return True
elif dlg == widgetUtils.NO:
return False

View File

@@ -0,0 +1,258 @@
# -*- coding: utf-8 -*-
import time
import platform
if platform.system() == "Windows":
from wxUI import commonMessageDialogs, menus
from controller import user
elif platform.system() == "Linux":
from gi.repository import Gtk
from gtkUI import dialogs, commonMessageDialogs
from controller import messages
import widgetUtils
import webbrowser
import output
import config
import logging
from mysc.thread_utils import call_threaded
from tweepy.errors import TweepyException
from pubsub import pub
from sessions.twitter import compose
from . import base
log = logging.getLogger("controller.buffers.twitter.peopleBuffer")
def _tweets_exist(function):
""" A decorator to execute a function only if the selected buffer contains at least one item."""
def function_(self, *args, **kwargs):
if self.buffer.list.get_count() > 0:
function(self, *args, **kwargs)
return function_
class PeopleBuffer(base.BaseBuffer):
def __init__(self, parent, function, name, sessionObject, account, bufferType=None, *args, **kwargs):
super(PeopleBuffer, self).__init__(parent, function, name, sessionObject, account, bufferType="peoplePanel", *args, **kwargs)
log.debug("Initializing buffer %s, account %s" % (name, account,))
self.compose_function = compose.compose_followers_list
log.debug("Compose_function: %s" % (self.compose_function,))
self.get_tweet = self.get_right_tweet
self.url = self.interact
if "-followers" in self.name or "-friends" in self.name:
self.finished_timeline = False
# Add a compatibility layer for username based timelines from config.
# ToDo: Remove this in some new versions of the client, when user ID timelines become mandatory.
try:
int(self.kwargs["user_id"])
except ValueError:
self.is_screen_name = True
self.kwargs["screen_name"] = self.kwargs["user_id"]
self.kwargs.pop("user_id")
def remove_buffer(self, force=True):
if "-followers" in self.name:
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-10] in self.session.settings["other_buffers"]["followers_timelines"]:
self.session.settings["other_buffers"]["followers_timelines"].remove(self.name[:-10])
if self.name in self.session.db:
self.session.db.pop(self.name)
self.session.settings.write()
return True
elif dlg == widgetUtils.NO:
return False
elif "-friends" in self.name:
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-8] in self.session.settings["other_buffers"]["friends_timelines"]:
self.session.settings["other_buffers"]["friends_timelines"].remove(self.name[:-8])
if self.name in self.session.db:
self.session.db.pop(self.name)
self.session.settings.write()
return True
elif dlg == widgetUtils.NO:
return False
else:
output.speak(_(u"This buffer is not a timeline; it can't be deleted."), True)
return False
def onFocus(self, ev):
pass
def get_message(self):
return " ".join(self.compose_function(self.get_tweet(), self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session))
def delete_item(self): pass
@_tweets_exist
def reply(self, *args, **kwargs):
tweet = self.get_right_tweet()
screen_name = tweet.screen_name
message = messages.reply(self.session, _(u"Mention"), _(u"Mention to %s") % (screen_name,), "@%s " % (screen_name,), [screen_name,])
if message.message.get_response() == widgetUtils.OK:
if config.app["app-settings"]["remember_mention_and_longtweet"]:
config.app["app-settings"]["longtweet"] = message.message.long_tweet.GetValue()
config.app.write()
if message.image == None:
item = self.session.api_call(call_name="update_status", _sound="reply_send.ogg", status=message.message.get_text(), tweet_mode="extended")
if item != None:
pub.sendMessage("sent-tweet", data=item, user=self.session.db["user_name"])
else:
call_threaded(self.session.api_call, call_name="update_status_with_media", _sound="reply_send.ogg", status=message.message.get_text(), media=message.file)
if hasattr(message.message, "destroy"): message.message.destroy()
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
# starts stream every 3 minutes.
current_time = time.time()
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory==True:
self.execution_time = current_time
log.debug("Starting stream for %s buffer, %s account" % (self.name, self.account,))
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
try:
val = getattr(self.session.twitter, self.function)(return_cursors=True, count=self.session.settings["general"]["max_tweets_per_call"], *self.args, **self.kwargs)
if type(val) == tuple:
val, cursor = val
if type(cursor) == tuple:
cursor = cursor[1]
cursors = self.session.db["cursors"]
cursors[self.name] = cursor
self.session.db["cursors"] = cursors
results = [i for i in val]
val = results
val.reverse()
log.debug("Retrieved %d items from cursored search in function %s" % (len(val), self.function))
except TweepyException as e:
log.exception("Error %s" % (str(e)))
return
number_of_items = self.session.order_people(self.name, val)
log.debug("Number of items retrieved: %d" % (number_of_items,))
self.put_items_on_list(number_of_items)
if hasattr(self, "finished_timeline") and self.finished_timeline == False:
self.username = self.session.api_call("get_user", **self.kwargs).screen_name
self.finished_timeline = True
if number_of_items > 0 and self.sound != None and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and play_sound == True:
self.session.sound.play(self.sound)
# Autoread settings
if avoid_autoreading == False and mandatory == True and number_of_items > 0 and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
self.auto_read(number_of_items)
return number_of_items
def get_more_items(self):
try:
cursor = self.session.db["cursors"].get(self.name)
items = getattr(self.session.twitter, self.function)(return_cursors=True, users=True, cursor=cursor, count=self.session.settings["general"]["max_tweets_per_call"], *self.args, **self.kwargs)
if type(items) == tuple:
items, cursor = items
if type(cursor) == tuple:
cursor = cursor[1]
cursors = self.session.db["cursors"]
cursors[self.name] = cursor
self.session.db["cursors"] = cursors
results = [i for i in items]
items = results
log.debug("Retrieved %d items from cursored search in function %s" % (len(items), self.function))
except TweepyException as e:
log.exception("Error %s" % (str(e)))
return
if items == None:
return
items_db = self.session.db[self.name]
for i in items:
if self.session.settings["general"]["reverse_timelines"] == False:
items_db.insert(0, i)
else:
items_db.append(i)
self.session.db[self.name] = items_db
selected = self.buffer.list.get_selected()
if self.session.settings["general"]["reverse_timelines"] == True:
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session)
self.buffer.list.insert_item(True, *tweet)
self.buffer.list.select_item(selected)
else:
for i in items:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session)
self.buffer.list.insert_item(True, *tweet)
output.speak(_(u"%s items retrieved") % (len(items)), True)
def put_items_on_list(self, number_of_items):
log.debug("The list contains %d items" % (self.buffer.list.get_count(),))
# log.debug("Putting %d items on the list..." % (number_of_items,))
if self.buffer.list.get_count() == 0:
for i in self.session.db[self.name]:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"], self.session)
self.buffer.list.insert_item(False, *tweet)
self.buffer.set_position(self.session.settings["general"]["reverse_timelines"])
# self.buffer.set_list_position()
elif self.buffer.list.get_count() > 0:
if self.session.settings["general"]["reverse_timelines"] == False:
for i in self.session.db[self.name][len(self.session.db[self.name])-number_of_items:]:
tweet = self.compose_function(i, self.session.db)
self.buffer.list.insert_item(False, *tweet)
else:
items = self.session.db[self.name][0:number_of_items]
items.reverse()
for i in items:
tweet = self.compose_function(i, self.session.db)
self.buffer.list.insert_item(True, *tweet)
log.debug("now the list contains %d items" % (self.buffer.list.get_count(),))
def get_right_tweet(self):
tweet = self.session.db[self.name][self.buffer.list.get_selected()]
return tweet
def add_new_item(self, item):
tweet = self.compose_function(item, self.session.db, self.session.settings["general"]["relative_times"], self.session)
if self.session.settings["general"]["reverse_timelines"] == False:
self.buffer.list.insert_item(False, *tweet)
else:
self.buffer.list.insert_item(True, *tweet)
if self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
output.speak(" ".join(tweet))
def clear_list(self):
dlg = commonMessageDialogs.clear_list()
if dlg == widgetUtils.YES:
self.session.db[self.name] = []
self.session.db["cursors"][self.name] = -1
self.buffer.list.clear()
def interact(self):
user.profileController(self.session, user=self.get_right_tweet().screen_name)
def show_menu(self, ev, pos=0, *args, **kwargs):
menu = menus.peoplePanelMenu()
widgetUtils.connect_event(menu, widgetUtils.MENU, self.send_message, menuitem=menu.reply)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.user_actions, menuitem=menu.userActions)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.details, menuitem=menu.details)
# widgetUtils.connect_event(menu, widgetUtils.MENU, self.lists, menuitem=menu.lists)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.view, menuitem=menu.view)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.copy, menuitem=menu.copy)
if hasattr(menu, "openInBrowser"):
widgetUtils.connect_event(menu, widgetUtils.MENU, self.open_in_browser, menuitem=menu.openInBrowser)
if pos != 0:
self.buffer.PopupMenu(menu, pos)
else:
self.buffer.PopupMenu(menu, ev.GetPosition())
def details(self, *args, **kwargs):
pub.sendMessage("execute-action", action="user_details")
def auto_read(self, number_of_items):
if number_of_items == 1 and self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
if self.session.settings["general"]["reverse_timelines"] == False:
tweet = self.session.db[self.name][-1]
else:
tweet = self.session.db[self.name][0]
output.speak(" ".join(self.compose_function(tweet, self.session.db, self.session.settings["general"]["relative_times"], self.session.settings["general"]["show_screen_names"], self.session)))
elif number_of_items > 1 and self.name in self.session.settings["other_buffers"]["autoread_buffers"] and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and self.session.settings["sound"]["session_mute"] == False:
output.speak(_(u"{0} new followers.").format(number_of_items))
def get_item_url(self, *args, **kwargs):
tweet = self.get_tweet()
url = "https://twitter.com/{screen_name}".format(screen_name=tweet.screen_name)
return url

View File

@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
import time
import platform
if platform.system() == "Windows":
from wxUI import commonMessageDialogs
elif platform.system() == "Linux":
from gi.repository import Gtk
from gtkUI import commonMessageDialogs
import widgetUtils
import logging
from . import base, people
log = logging.getLogger("controller.buffers.twitter.searchBuffer")
class SearchBuffer(base.BaseBuffer):
def remove_buffer(self, force=False):
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-11] in self.session.settings["other_buffers"]["tweet_searches"]:
self.session.settings["other_buffers"]["tweet_searches"].remove(self.name[:-11])
self.session.settings.write()
if self.name in self.session.db:
self.session.db.pop(self.name)
return True
elif dlg == widgetUtils.NO:
return False
class SearchPeopleBuffer(people.PeopleBuffer):
""" This is identical to a normal peopleBufferController, except that uses the page parameter instead of a cursor."""
def __init__(self, parent, function, name, sessionObject, account, bufferType="peoplePanel", *args, **kwargs):
super(SearchPeopleBuffer, self).__init__(parent, function, name, sessionObject, account, bufferType="peoplePanel", *args, **kwargs)
if ("page" in self.kwargs) == False:
self.page = 1
else:
self.page = self.kwargs.pop("page")
def get_more_items(self, *args, **kwargs):
# Add 1 to the page parameter, put it in kwargs and calls to get_more_items in the parent buffer.
self.page = self.page +1
self.kwargs["page"] = self.page
super(SearchPeopleBuffer, self).get_more_items(*args, **kwargs)
# remove the parameter again to make sure start_stream won't fetch items for this page indefinitely.
self.kwargs.pop("page")
def remove_buffer(self, force=False):
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-11] in self.session.settings["other_buffers"]["tweet_searches"]:
self.session.settings["other_buffers"]["tweet_searches"].remove(self.name[:-11])
self.session.settings.write()
if self.name in self.session.db:
self.session.db.pop(self.name)
return True
elif dlg == widgetUtils.NO:
return False
class ConversationBuffer(SearchBuffer):
def start_stream(self, start=False, mandatory=False, play_sound=True, avoid_autoreading=False):
# starts stream every 3 minutes.
current_time = time.time()
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory == True:
self.execution_time = current_time
if start == True:
self.statuses = []
self.ids = []
self.statuses.append(self.tweet)
self.ids.append(self.tweet.id)
tweet = self.tweet
if not hasattr(tweet, "in_reply_to_status_id"):
tweet.in_reply_to_status_id = None
while tweet.in_reply_to_status_id != None:
try:
tweet = self.session.twitter.get_status(id=tweet.in_reply_to_status_id, tweet_mode="extended")
except TweepError as err:
break
self.statuses.insert(0, tweet)
self.ids.append(tweet.id)
if tweet.in_reply_to_status_id == None:
self.kwargs["since_id"] = tweet.id
self.ids.append(tweet.id)
val2 = self.session.search(self.name, tweet_mode="extended", *self.args, **self.kwargs)
for i in val2:
if i.in_reply_to_status_id in self.ids:
self.statuses.append(i)
self.ids.append(i.id)
tweet = i
number_of_items = self.session.order_buffer(self.name, self.statuses)
log.debug("Number of items retrieved: %d" % (number_of_items,))
self.put_items_on_list(number_of_items)
if number_of_items > 0 and self.sound != None and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and play_sound == True:
self.session.sound.play(self.sound)
# Autoread settings
if avoid_autoreading == False and mandatory == True and number_of_items > 0 and self.name in self.session.settings["other_buffers"]["autoread_buffers"]:
self.auto_read(number_of_items)
return number_of_items
def remove_buffer(self, force=False):
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name in self.session.db:
self.session.db.pop(self.name)
return True
elif dlg == widgetUtils.NO:
return False

View File

@@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
import time
import platform
if platform.system() == "Windows":
import wx
from wxUI import buffers, commonMessageDialogs, menus
from controller import user
elif platform.system() == "Linux":
from gi.repository import Gtk
from gtkUI import buffers, commonMessageDialogs
from controller import messages
import widgetUtils
import output
import logging
from mysc.thread_utils import call_threaded
from tweepy.errors import TweepyException
from pubsub import pub
from controller.buffers import base
log = logging.getLogger("controller.buffers.twitter.trends")
class TrendsBuffer(base.Buffer):
def __init__(self, parent, name, sessionObject, account, trendsFor, *args, **kwargs):
super(TrendsBuffer, self).__init__(parent=parent, sessionObject=sessionObject)
self.trendsFor = trendsFor
self.session = sessionObject
self.account = account
self.invisible = True
self.buffer = buffers.trendsPanel(parent, name)
self.buffer.account = account
self.type = self.buffer.type
self.bind_events()
self.sound = "trends_updated.ogg"
self.trends = []
self.name = name
self.buffer.name = name
self.compose_function = self.compose_function_
self.get_formatted_message = self.get_message
self.reply = self.search_topic
def start_stream(self, mandatory=False, play_sound=True, avoid_autoreading=False):
# starts stream every 3 minutes.
current_time = time.time()
if self.execution_time == 0 or current_time-self.execution_time >= 180 or mandatory == True:
self.execution_time = current_time
try:
data = self.session.twitter.get_place_trends(id=self.trendsFor)
except TweepyException as err:
log.exception("Error %s" % (str(err)))
if not hasattr(self, "name_"):
self.name_ = data[0]["locations"][0]["name"]
self.trends = data[0]["trends"]
self.put_items_on_the_list()
if self.sound != None and self.session.settings["sound"]["session_mute"] == False and self.name not in self.session.settings["other_buffers"]["muted_buffers"] and play_sound == True:
self.session.sound.play(self.sound)
def put_items_on_the_list(self):
selected_item = self.buffer.list.get_selected()
self.buffer.list.clear()
for i in self.trends:
tweet = self.compose_function(i)
self.buffer.list.insert_item(False, *tweet)
self.buffer.set_position(self.session.settings["general"]["reverse_timelines"])
def compose_function_(self, trend):
return [trend["name"]]
def bind_events(self):
log.debug("Binding events...")
self.buffer.list.list.Bind(wx.EVT_CHAR_HOOK, self.get_event)
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.tweet_about_this_trend, self.buffer.tweetTrendBtn)
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.post_status, self.buffer.tweet)
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_ITEM_RIGHT_CLICK, self.show_menu)
widgetUtils.connect_event(self.buffer.list.list, wx.EVT_LIST_KEY_DOWN, self.show_menu_by_key)
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.search_topic, self.buffer.search_topic)
def get_message(self):
return self.compose_function(self.trends[self.buffer.list.get_selected()])[0]
def remove_buffer(self, force=False):
if force == False:
dlg = commonMessageDialogs.remove_buffer()
else:
dlg = widgetUtils.YES
if dlg == widgetUtils.YES:
if self.name[:-3] in self.session.settings["other_buffers"]["trending_topic_buffers"]:
self.session.settings["other_buffers"]["trending_topic_buffers"].remove(self.name[:-3])
self.session.settings.write()
if self.name in self.session.db:
self.session.db.pop(self.name)
return True
elif dlg == widgetUtils.NO:
return False
def url(self, *args, **kwargs):
self.tweet_about_this_trend()
def search_topic(self, *args, **kwargs):
topic = self.trends[self.buffer.list.get_selected()]["name"]
pub.sendMessage("search", term=topic)
def show_menu(self, ev, pos=0, *args, **kwargs):
menu = menus.trendsPanelMenu()
widgetUtils.connect_event(menu, widgetUtils.MENU, self.search_topic, menuitem=menu.search_topic)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.tweet_about_this_trend, menuitem=menu.tweetThisTrend)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.view, menuitem=menu.view)
widgetUtils.connect_event(menu, widgetUtils.MENU, self.copy, menuitem=menu.copy)
if pos != 0:
self.buffer.PopupMenu(menu, pos)
else:
self.buffer.PopupMenu(menu, ev.GetPosition())
def view(self, *args, **kwargs):
pub.sendMessage("execute-action", action="view_item")
def copy(self, *args, **kwargs):
pub.sendMessage("execute-action", action="copy_to_clipboard")
def tweet_about_this_trend(self, *args, **kwargs):
if self.buffer.list.get_count() == 0: return
title = _(u"Tweet")
caption = _(u"Write the tweet here")
tweet = messages.tweet(self.session, title, caption, self.get_message()+ " ")
tweet.message.set_cursor_at_end()
if tweet.message.get_response() == widgetUtils.OK:
text = tweet.message.get_text()
if len(text) > 280 and tweet.message.get("long_tweet") == True:
if tweet.image == None:
text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text)
else:
text = twishort.create_tweet(self.session.settings["twitter"]["user_key"], self.session.settings["twitter"]["user_secret"], text, 1)
if tweet.image == None:
call_threaded(self.session.api_call, call_name="update_status", status=text)
else:
call_threaded(self.session.api_call, call_name="update_status_with_media", status=text, media=tweet.image)
if hasattr(tweet.message, "destroy"): tweet.message.destroy()
def show_menu_by_key(self, ev):
if self.buffer.list.get_count() == 0:
return
if ev.GetKeyCode() == wx.WXK_WINDOWS_MENU:
self.show_menu(widgetUtils.MENU, pos=self.buffer.list.list.GetPosition())
def open_in_browser(self, *args, **kwargs):
output.speak(_(u"This action is not supported in the buffer, yet."))

File diff suppressed because it is too large Load Diff

View File

@@ -8,71 +8,71 @@ from wxUI.dialogs import filterDialogs
from wxUI import commonMessageDialogs from wxUI import commonMessageDialogs
class filter(object): class filter(object):
def __init__(self, buffer, filter_title=None, if_word_exists=None, in_lang=None, regexp=None, word=None, in_buffer=None): def __init__(self, buffer, filter_title=None, if_word_exists=None, in_lang=None, regexp=None, word=None, in_buffer=None):
self.buffer = buffer self.buffer = buffer
self.dialog = filterDialogs.filterDialog(languages=[i["name"] for i in application.supported_languages]) self.dialog = filterDialogs.filterDialog(languages=[i["name"] for i in application.supported_languages])
if self.dialog.get_response() == widgetUtils.OK: if self.dialog.get_response() == widgetUtils.OK:
title = self.dialog.get("title") title = self.dialog.get("title")
contains = self.dialog.get("contains") contains = self.dialog.get("contains")
term = self.dialog.get("term") term = self.dialog.get("term")
regexp = self.dialog.get("regexp") regexp = self.dialog.get("regexp")
allow_rts = self.dialog.get("allow_rts") allow_rts = self.dialog.get("allow_rts")
allow_quotes = self.dialog.get("allow_quotes") allow_quotes = self.dialog.get("allow_quotes")
allow_replies = self.dialog.get("allow_replies") allow_replies = self.dialog.get("allow_replies")
load_language = self.dialog.get("load_language") load_language = self.dialog.get("load_language")
ignore_language = self.dialog.get("ignore_language") ignore_language = self.dialog.get("ignore_language")
lang_option = None lang_option = None
if ignore_language: if ignore_language:
lang_option = False lang_option = False
elif load_language: elif load_language:
lang_option = True lang_option = True
langs = self.dialog.get_selected_langs() langs = self.dialog.get_selected_langs()
langcodes = [] langcodes = []
for i in application.supported_languages: for i in application.supported_languages:
if i["name"] in langs: if i["name"] in langs:
langcodes.append(i["code"]) langcodes.append(i["code"])
d = dict(in_buffer=self.buffer.name, word=term, regexp=regexp, in_lang=lang_option, languages=langcodes, if_word_exists=contains, allow_rts=allow_rts, allow_quotes=allow_quotes, allow_replies=allow_replies) d = dict(in_buffer=self.buffer.name, word=term, regexp=regexp, in_lang=lang_option, languages=langcodes, if_word_exists=contains, allow_rts=allow_rts, allow_quotes=allow_quotes, allow_replies=allow_replies)
if title in self.buffer.session.settings["filters"]: if title in self.buffer.session.settings["filters"]:
return commonMessageDialogs.existing_filter() return commonMessageDialogs.existing_filter()
self.buffer.session.settings["filters"][title] = d self.buffer.session.settings["filters"][title] = d
self.buffer.session.settings.write() self.buffer.session.settings.write()
class filterManager(object): class filterManager(object):
def __init__(self, session): def __init__(self, session):
self.session = session self.session = session
self.dialog = filterDialogs.filterManagerDialog() self.dialog = filterDialogs.filterManagerDialog()
self.insert_filters(self.session.settings["filters"]) self.insert_filters(self.session.settings["filters"])
if self.dialog.filters.get_count() == 0: if self.dialog.filters.get_count() == 0:
self.dialog.edit.Enable(False) self.dialog.edit.Enable(False)
self.dialog.delete.Enable(False) self.dialog.delete.Enable(False)
else: else:
widgetUtils.connect_event(self.dialog.edit, widgetUtils.BUTTON_PRESSED, self.edit_filter) widgetUtils.connect_event(self.dialog.edit, widgetUtils.BUTTON_PRESSED, self.edit_filter)
widgetUtils.connect_event(self.dialog.delete, widgetUtils.BUTTON_PRESSED, self.delete_filter) widgetUtils.connect_event(self.dialog.delete, widgetUtils.BUTTON_PRESSED, self.delete_filter)
response = self.dialog.get_response() response = self.dialog.get_response()
def insert_filters(self, filters): def insert_filters(self, filters):
self.dialog.filters.clear() self.dialog.filters.clear()
for f in list(filters.keys()): for f in list(filters.keys()):
filterName = f filterName = f
buffer = filters[f]["in_buffer"] buffer = filters[f]["in_buffer"]
if filters[f]["if_word_exists"] == "True" and filters[f]["word"] != "": if filters[f]["if_word_exists"] == "True" and filters[f]["word"] != "":
filter_by_word = "True" filter_by_word = "True"
else: else:
filter_by_word = "False" filter_by_word = "False"
filter_by_lang = "" filter_by_lang = ""
if filters[f]["in_lang"] != "None": if filters[f]["in_lang"] != "None":
filter_by_lang = "True" filter_by_lang = "True"
b = [f, buffer, filter_by_word, filter_by_lang] b = [f, buffer, filter_by_word, filter_by_lang]
self.dialog.filters.insert_item(False, *b) self.dialog.filters.insert_item(False, *b)
def edit_filter(self, *args, **kwargs): def edit_filter(self, *args, **kwargs):
pass pass
def delete_filter(self, *args, **kwargs): def delete_filter(self, *args, **kwargs):
filter_title = self.dialog.filters.get_text_column(self.dialog.filters.get_selected(), 0) filter_title = self.dialog.filters.get_text_column(self.dialog.filters.get_selected(), 0)
response = commonMessageDialogs.delete_filter() response = commonMessageDialogs.delete_filter()
if response == widgetUtils.YES: if response == widgetUtils.YES:
self.session.settings["filters"].pop(filter_title) self.session.settings["filters"].pop(filter_title)
self.session.settings.write() self.session.settings.write()
self.insert_filters(self.session.settings["filters"]) self.insert_filters(self.session.settings["filters"])

View File

@@ -3,108 +3,112 @@ import widgetUtils
import output import output
import logging import logging
from wxUI.dialogs import lists from wxUI.dialogs import lists
from tweepy.error import TweepError from tweepy.errors import TweepyException
from sessions.twitter import compose, utils from sessions.twitter import compose, utils
from pubsub import pub from pubsub import pub
log = logging.getLogger("controller.listsController") log = logging.getLogger("controller.listsController")
class listsController(object): class listsController(object):
def __init__(self, session, user=None): def __init__(self, session, user=None):
super(listsController, self).__init__() super(listsController, self).__init__()
self.session = session self.session = session
if user == None: if user == None:
self.dialog = lists.listViewer() self.dialog = lists.listViewer()
self.dialog.populate_list(self.get_all_lists()) self.dialog.populate_list(self.get_all_lists())
widgetUtils.connect_event(self.dialog.createBtn, widgetUtils.BUTTON_PRESSED, self.create_list) widgetUtils.connect_event(self.dialog.createBtn, widgetUtils.BUTTON_PRESSED, self.create_list)
widgetUtils.connect_event(self.dialog.editBtn, widgetUtils.BUTTON_PRESSED, self.edit_list) widgetUtils.connect_event(self.dialog.editBtn, widgetUtils.BUTTON_PRESSED, self.edit_list)
widgetUtils.connect_event(self.dialog.deleteBtn, widgetUtils.BUTTON_PRESSED, self.remove_list) widgetUtils.connect_event(self.dialog.deleteBtn, widgetUtils.BUTTON_PRESSED, self.remove_list)
widgetUtils.connect_event(self.dialog.view, widgetUtils.BUTTON_PRESSED, self.open_list_as_buffer) widgetUtils.connect_event(self.dialog.view, widgetUtils.BUTTON_PRESSED, self.open_list_as_buffer)
widgetUtils.connect_event(self.dialog.deleteBtn, widgetUtils.BUTTON_PRESSED, self.remove_list) widgetUtils.connect_event(self.dialog.deleteBtn, widgetUtils.BUTTON_PRESSED, self.remove_list)
else: else:
self.dialog = lists.userListViewer(user) self.dialog = lists.userListViewer(user)
self.dialog.populate_list(self.get_user_lists(user)) self.dialog.populate_list(self.get_user_lists(user))
widgetUtils.connect_event(self.dialog.createBtn, widgetUtils.BUTTON_PRESSED, self.subscribe) widgetUtils.connect_event(self.dialog.createBtn, widgetUtils.BUTTON_PRESSED, self.subscribe)
widgetUtils.connect_event(self.dialog.deleteBtn, widgetUtils.BUTTON_PRESSED, self.unsubscribe) widgetUtils.connect_event(self.dialog.deleteBtn, widgetUtils.BUTTON_PRESSED, self.unsubscribe)
self.dialog.get_response() self.dialog.get_response()
def get_all_lists(self): def get_all_lists(self):
return [compose.compose_list(item) for item in self.session.db["lists"]] return [compose.compose_list(item) for item in self.session.db["lists"]]
def get_user_lists(self, user): def get_user_lists(self, user):
self.lists = self.session.twitter.lists_all(reverse=True, screen_name=user) self.lists = self.session.twitter.lists_all(reverse=True, screen_name=user)
return [compose.compose_list(item) for item in self.lists] return [compose.compose_list(item) for item in self.lists]
def create_list(self, *args, **kwargs): def create_list(self, *args, **kwargs):
dialog = lists.createListDialog() dialog = lists.createListDialog()
if dialog.get_response() == widgetUtils.OK: if dialog.get_response() == widgetUtils.OK:
name = dialog.get("name") name = dialog.get("name")
description = dialog.get("description") description = dialog.get("description")
p = dialog.get("public") p = dialog.get("public")
if p == True: if p == True:
mode = "public" mode = "public"
else: else:
mode = "private" mode = "private"
try: try:
new_list = self.session.twitter.create_list(name=name, description=description, mode=mode) new_list = self.session.twitter.create_list(name=name, description=description, mode=mode)
self.session.db["lists"].append(new_list) self.session.db["lists"].append(new_list)
self.dialog.lista.insert_item(False, *compose.compose_list(new_list)) self.dialog.lista.insert_item(False, *compose.compose_list(new_list))
except TweepError as e: except TweepyException as e:
output.speak("error %s: %s" % (e.api_code, e.reason)) output.speak("error %s" % (str(e)))
log.exception("error %s: %s" % (e.api_code, e.reason)) log.exception("error %s" % (str(e)))
dialog.destroy() dialog.destroy()
def edit_list(self, *args, **kwargs): def edit_list(self, *args, **kwargs):
if self.dialog.lista.get_count() == 0: return if self.dialog.lista.get_count() == 0: return
list = self.session.db["lists"][self.dialog.get_item()] list = self.session.db["lists"][self.dialog.get_item()]
dialog = lists.editListDialog(list) dialog = lists.editListDialog(list)
if dialog.get_response() == widgetUtils.OK: if dialog.get_response() == widgetUtils.OK:
name = dialog.get("name") name = dialog.get("name")
description = dialog.get("description") description = dialog.get("description")
p = dialog.get("public") p = dialog.get("public")
if p == True: if p == True:
mode = "public" mode = "public"
else: else:
mode = "private" mode = "private"
try: try:
self.session.twitter.update_list(list_id=list.id, name=name, description=description, mode=mode) self.session.twitter.update_list(list_id=list.id, name=name, description=description, mode=mode)
self.session.get_lists() self.session.get_lists()
self.dialog.populate_list(self.get_all_lists(), True) self.dialog.populate_list(self.get_all_lists(), True)
except TweepError as e: except TweepyException as e:
output.speak("error %s: %s" % (e.api_code, e.reason)) output.speak("error %s" % (str(e)))
dialog.destroy() log.exception("error %s" % (str(e)))
dialog.destroy()
def remove_list(self, *args, **kwargs): def remove_list(self, *args, **kwargs):
if self.dialog.lista.get_count() == 0: return if self.dialog.lista.get_count() == 0: return
list = self.session.db["lists"][self.dialog.get_item()].id list = self.session.db["lists"][self.dialog.get_item()].id
if lists.remove_list() == widgetUtils.YES: if lists.remove_list() == widgetUtils.YES:
try: try:
self.session.twitter.destroy_list(list_id=list) self.session.twitter.destroy_list(list_id=list)
self.session.db["lists"].pop(self.dialog.get_item()) self.session.db["lists"].pop(self.dialog.get_item())
self.dialog.lista.remove_item(self.dialog.get_item()) self.dialog.lista.remove_item(self.dialog.get_item())
except TweepError as e: except TweepyException as e:
output.speak("error %s: %s" % (e.api_code, e.reason)) output.speak("error %s" % (str(e)))
log.exception("error %s" % (str(e)))
def open_list_as_buffer(self, *args, **kwargs): def open_list_as_buffer(self, *args, **kwargs):
if self.dialog.lista.get_count() == 0: return if self.dialog.lista.get_count() == 0: return
list = self.session.db["lists"][self.dialog.get_item()] list = self.session.db["lists"][self.dialog.get_item()]
pub.sendMessage("create-new-buffer", buffer="list", account=self.session.db["user_name"], create=list.name) pub.sendMessage("create-new-buffer", buffer="list", account=self.session.db["user_name"], create=list.name)
def subscribe(self, *args, **kwargs): def subscribe(self, *args, **kwargs):
if self.dialog.lista.get_count() == 0: return if self.dialog.lista.get_count() == 0: return
list_id = self.lists[self.dialog.get_item()].id list_id = self.lists[self.dialog.get_item()].id
try: try:
list = self.session.twitter.subscribe_list(list_id=list_id) list = self.session.twitter.subscribe_list(list_id=list_id)
item = utils.find_item(list.id, self.session.db["lists"]) item = utils.find_item(list.id, self.session.db["lists"])
self.session.db["lists"].append(list) self.session.db["lists"].append(list)
except TweepError as e: except TweepyException as e:
output.speak("error %s: %s" % (e.api_code, e.reason)) output.speak("error %s" % (str(e)))
log.exception("error %s" % (str(e)))
def unsubscribe(self, *args, **kwargs): def unsubscribe(self, *args, **kwargs):
if self.dialog.lista.get_count() == 0: return if self.dialog.lista.get_count() == 0: return
list_id = self.lists[self.dialog.get_item()].id list_id = self.lists[self.dialog.get_item()].id
try: try:
list = self.session.twitter.unsubscribe_list(list_id=list_id) list = self.session.twitter.unsubscribe_list(list_id=list_id)
self.session.db["lists"].remove(list) self.session.db["lists"].remove(list)
except TweepError as e: except TweepyException as e:
output.speak("error %s: %s" % (e.api_code, e.reason)) output.speak("error %s" % (str(e)))
log.exception("error %s" % (str(e)))

File diff suppressed because it is too large Load Diff

View File

@@ -12,281 +12,290 @@ import config
from pubsub import pub from pubsub import pub
from twitter_text import parse_tweet from twitter_text import parse_tweet
if system == "Windows": if system == "Windows":
from wxUI.dialogs import message, urlList from wxUI.dialogs import message, urlList
from wxUI import commonMessageDialogs from wxUI import commonMessageDialogs
from extra import translator, SpellChecker, autocompletionUsers from extra import translator, SpellChecker, autocompletionUsers
from extra.AudioUploader import audioUploader from extra.AudioUploader import audioUploader
elif system == "Linux": elif system == "Linux":
from gtkUI.dialogs import message from gtkUI.dialogs import message
from sessions.twitter import utils from sessions.twitter import utils
from . import attach from . import attach
class basicTweet(object): class basicTweet(object):
""" This class handles the tweet main features. Other classes should derive from this class.""" """ This class handles the tweet main features. Other classes should derive from this class."""
def __init__(self, session, title, caption, text, messageType="tweet", max=280, *args, **kwargs): def __init__(self, session, title, caption, text, messageType="tweet", max=280, *args, **kwargs):
super(basicTweet, self).__init__() super(basicTweet, self).__init__()
self.max = max self.max = max
self.title = title self.title = title
self.session = session self.session = session
self.message = getattr(message, messageType)(title, caption, text, *args, **kwargs) self.message = getattr(message, messageType)(title, caption, text, *args, **kwargs)
widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck) widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck)
widgetUtils.connect_event(self.message.attach, widgetUtils.BUTTON_PRESSED, self.attach) widgetUtils.connect_event(self.message.attach, widgetUtils.BUTTON_PRESSED, self.attach)
widgetUtils.connect_event(self.message.text, widgetUtils.ENTERED_TEXT, self.text_processor) widgetUtils.connect_event(self.message.text, widgetUtils.ENTERED_TEXT, self.text_processor)
widgetUtils.connect_event(self.message.shortenButton, widgetUtils.BUTTON_PRESSED, self.shorten) widgetUtils.connect_event(self.message.shortenButton, widgetUtils.BUTTON_PRESSED, self.shorten)
widgetUtils.connect_event(self.message.unshortenButton, widgetUtils.BUTTON_PRESSED, self.unshorten) widgetUtils.connect_event(self.message.unshortenButton, widgetUtils.BUTTON_PRESSED, self.unshorten)
widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate) widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate)
if hasattr(self.message, "long_tweet"): if hasattr(self.message, "long_tweet"):
widgetUtils.connect_event(self.message.long_tweet, widgetUtils.CHECKBOX, self.text_processor) widgetUtils.connect_event(self.message.long_tweet, widgetUtils.CHECKBOX, self.text_processor)
if config.app["app-settings"]["remember_mention_and_longtweet"]: if config.app["app-settings"]["remember_mention_and_longtweet"]:
self.message.long_tweet.SetValue(config.app["app-settings"]["longtweet"]) self.message.long_tweet.SetValue(config.app["app-settings"]["longtweet"])
self.attachments = [] self.attachments = []
def translate(self, event=None): def translate(self, event=None):
dlg = translator.gui.translateDialog() dlg = translator.gui.translateDialog()
if dlg.get_response() == widgetUtils.OK: if dlg.get_response() == widgetUtils.OK:
text_to_translate = self.message.get_text() text_to_translate = self.message.get_text()
language_dict = translator.translator.available_languages() language_dict = translator.translator.available_languages()
for k in language_dict: for k in language_dict:
if language_dict[k] == dlg.dest_lang.GetStringSelection(): if language_dict[k] == dlg.dest_lang.GetStringSelection():
dst = k dst = k
msg = translator.translator.translate(text=text_to_translate, target=dst) msg = translator.translator.translate(text=text_to_translate, target=dst)
self.message.set_text(msg) self.message.set_text(msg)
self.text_processor() self.text_processor()
self.message.text_focus() self.message.text_focus()
output.speak(_(u"Translated")) output.speak(_(u"Translated"))
else: else:
return return
def shorten(self, event=None): def shorten(self, event=None):
urls = utils.find_urls_in_text(self.message.get_text()) urls = utils.find_urls_in_text(self.message.get_text())
if len(urls) == 0: if len(urls) == 0:
output.speak(_(u"There's no URL to be shortened")) output.speak(_(u"There's no URL to be shortened"))
self.message.text_focus() self.message.text_focus()
elif len(urls) == 1: elif len(urls) == 1:
self.message.set_text(self.message.get_text().replace(urls[0], url_shortener.shorten(urls[0]))) self.message.set_text(self.message.get_text().replace(urls[0], url_shortener.shorten(urls[0])))
output.speak(_(u"URL shortened")) output.speak(_(u"URL shortened"))
self.text_processor() self.text_processor()
self.message.text_focus() self.message.text_focus()
elif len(urls) > 1: elif len(urls) > 1:
list_urls = urlList.urlList() list_urls = urlList.urlList()
list_urls.populate_list(urls) list_urls.populate_list(urls)
if list_urls.get_response() == widgetUtils.OK: if list_urls.get_response() == widgetUtils.OK:
self.message.set_text(self.message.get_text().replace(urls[list_urls.get_item()], url_shortener.shorten(list_urls.get_string()))) self.message.set_text(self.message.get_text().replace(urls[list_urls.get_item()], url_shortener.shorten(list_urls.get_string())))
output.speak(_(u"URL shortened")) output.speak(_(u"URL shortened"))
self.text_processor() self.text_processor()
self.message.text_focus() self.message.text_focus()
def unshorten(self, event=None): def unshorten(self, event=None):
urls = utils.find_urls_in_text(self.message.get_text()) urls = utils.find_urls_in_text(self.message.get_text())
if len(urls) == 0: if len(urls) == 0:
output.speak(_(u"There's no URL to be expanded")) output.speak(_(u"There's no URL to be expanded"))
self.message.text_focus() self.message.text_focus()
elif len(urls) == 1: elif len(urls) == 1:
self.message.set_text(self.message.get_text().replace(urls[0], url_shortener.unshorten(urls[0]))) self.message.set_text(self.message.get_text().replace(urls[0], url_shortener.unshorten(urls[0])))
output.speak(_(u"URL expanded")) output.speak(_(u"URL expanded"))
self.text_processor() self.text_processor()
self.message.text_focus() self.message.text_focus()
elif len(urls) > 1: elif len(urls) > 1:
list_urls = urlList.urlList() list_urls = urlList.urlList()
list_urls.populate_list(urls) list_urls.populate_list(urls)
if list_urls.get_response() == widgetUtils.OK: if list_urls.get_response() == widgetUtils.OK:
self.message.set_text(self.message.get_text().replace(urls[list_urls.get_item()], url_shortener.unshorten(list_urls.get_string()))) self.message.set_text(self.message.get_text().replace(urls[list_urls.get_item()], url_shortener.unshorten(list_urls.get_string())))
output.speak(_(u"URL expanded")) output.speak(_(u"URL expanded"))
self.text_processor() self.text_processor()
self.message.text_focus() self.message.text_focus()
def text_processor(self, *args, **kwargs): def text_processor(self, *args, **kwargs):
if len(self.message.get_text()) > 1: if len(self.message.get_text()) > 1:
self.message.enable_button("shortenButton") self.message.enable_button("shortenButton")
self.message.enable_button("unshortenButton") self.message.enable_button("unshortenButton")
else: else:
self.message.disable_button("shortenButton") self.message.disable_button("shortenButton")
self.message.disable_button("unshortenButton") self.message.disable_button("unshortenButton")
if self.message.get("long_tweet") == False: if self.message.get("long_tweet") == False and hasattr(self, "max"):
text = self.message.get_text() text = self.message.get_text()
results = parse_tweet(text) results = parse_tweet(text)
self.message.set_title(_(u"%s - %s of %d characters") % (self.title, results.weightedLength, self.max)) self.message.set_title(_(u"%s - %s of %d characters") % (self.title, results.weightedLength, self.max))
if results.weightedLength > self.max: if results.weightedLength > self.max:
self.session.sound.play("max_length.ogg") self.session.sound.play("max_length.ogg")
else: else:
self.message.set_title(_(u"%s - %s characters") % (self.title, results.weightedLength)) self.message.set_title(_(u"%s - %s characters") % (self.title, len(self.message.get_text())))
def spellcheck(self, event=None): def spellcheck(self, event=None):
text = self.message.get_text() text = self.message.get_text()
checker = SpellChecker.spellchecker.spellChecker(text, "") checker = SpellChecker.spellchecker.spellChecker(text, "")
if hasattr(checker, "fixed_text"): if hasattr(checker, "fixed_text"):
self.message.set_text(checker.fixed_text) self.message.set_text(checker.fixed_text)
self.text_processor() self.text_processor()
self.message.text_focus() self.message.text_focus()
def attach(self, *args, **kwargs): def attach(self, *args, **kwargs):
def completed_callback(dlg): def completed_callback(dlg):
url = dlg.uploaderFunction.get_url() url = dlg.uploaderFunction.get_url()
pub.unsubscribe(dlg.uploaderDialog.update, "uploading") pub.unsubscribe(dlg.uploaderDialog.update, "uploading")
dlg.uploaderDialog.destroy() dlg.uploaderDialog.destroy()
if "sndup.net/" in url: if "sndup.net/" in url:
self.message.set_text(self.message.get_text()+url+" #audio") self.message.set_text(self.message.get_text()+url+" #audio")
self.text_processor() self.text_processor()
else: else:
commonMessageDialogs.common_error(url) commonMessageDialogs.common_error(url)
dlg.cleanup() dlg.cleanup()
dlg = audioUploader.audioUploader(self.session.settings, completed_callback) dlg = audioUploader.audioUploader(self.session.settings, completed_callback)
self.message.text_focus() self.message.text_focus()
class tweet(basicTweet): class tweet(basicTweet):
def __init__(self, session, title, caption, text, max=280, messageType="tweet", *args, **kwargs): def __init__(self, session, title, caption, text, max=280, messageType="tweet", *args, **kwargs):
super(tweet, self).__init__(session, title, caption, text, messageType, max, *args, **kwargs) super(tweet, self).__init__(session, title, caption, text, messageType, max, *args, **kwargs)
self.image = None self.image = None
widgetUtils.connect_event(self.message.upload_image, widgetUtils.BUTTON_PRESSED, self.upload_image) widgetUtils.connect_event(self.message.upload_image, widgetUtils.BUTTON_PRESSED, self.upload_image)
widgetUtils.connect_event(self.message.autocompletionButton, widgetUtils.BUTTON_PRESSED, self.autocomplete_users) widgetUtils.connect_event(self.message.autocompletionButton, widgetUtils.BUTTON_PRESSED, self.autocomplete_users)
self.text_processor() self.text_processor()
def upload_image(self, *args, **kwargs): def upload_image(self, *args, **kwargs):
a = attach.attach() a = attach.attach()
if len(a.attachments) != 0: if len(a.attachments) != 0:
self.attachments = a.attachments self.attachments = a.attachments
def autocomplete_users(self, *args, **kwargs): def autocomplete_users(self, *args, **kwargs):
c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id) c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id)
c.show_menu() c.show_menu()
class reply(tweet): class reply(tweet):
def __init__(self, session, title, caption, text, users=[], ids=[]): def __init__(self, session, title, caption, text, users=[], ids=[]):
super(reply, self).__init__(session, title, caption, text, messageType="reply", users=users) super(reply, self).__init__(session, title, caption, text, messageType="reply", users=users)
self.ids = ids self.ids = ids
self.users = users self.users = users
if len(users) > 0: if len(users) > 0:
widgetUtils.connect_event(self.message.mentionAll, widgetUtils.CHECKBOX, self.mention_all) widgetUtils.connect_event(self.message.mentionAll, widgetUtils.CHECKBOX, self.mention_all)
self.message.enable_button("mentionAll") self.message.enable_button("mentionAll")
if config.app["app-settings"]["remember_mention_and_longtweet"]: if config.app["app-settings"]["remember_mention_and_longtweet"]:
self.message.mentionAll.SetValue(config.app["app-settings"]["mention_all"]) self.message.mentionAll.SetValue(config.app["app-settings"]["mention_all"])
self.mention_all() self.mention_all()
self.message.set_cursor_at_end() self.message.set_cursor_at_end()
self.text_processor() self.text_processor()
def mention_all(self, *args, **kwargs): def mention_all(self, *args, **kwargs):
if self.message.mentionAll.GetValue() == True: if self.message.mentionAll.GetValue() == True:
for i in self.message.checkboxes: for i in self.message.checkboxes:
i.SetValue(True) i.SetValue(True)
i.Hide() i.Hide()
else: else:
for i in self.message.checkboxes: for i in self.message.checkboxes:
i.SetValue(False) i.SetValue(False)
i.Show() i.Show()
def get_ids(self): def get_ids(self):
excluded_ids = "" excluded_ids = ""
for i in range(0, len(self.message.checkboxes)): for i in range(0, len(self.message.checkboxes)):
if self.message.checkboxes[i].GetValue() == False: if self.message.checkboxes[i].GetValue() == False:
excluded_ids = excluded_ids + "{0},".format(self.ids[i],) excluded_ids = excluded_ids + "{0},".format(self.ids[i],)
return excluded_ids return excluded_ids
def get_people(self): def get_people(self):
people = "" people = ""
for i in range(0, len(self.message.checkboxes)): for i in range(0, len(self.message.checkboxes)):
if self.message.checkboxes[i].GetValue() == True: if self.message.checkboxes[i].GetValue() == True:
people = people + "{0} ".format(self.message.checkboxes[i].GetLabel(),) people = people + "{0} ".format(self.message.checkboxes[i].GetLabel(),)
return people return people
class dm(basicTweet): class dm(basicTweet):
def __init__(self, session, title, caption, text): def __init__(self, session, title, caption, text):
super(dm, self).__init__(session, title, caption, text, messageType="dm", max=10000) super(dm, self).__init__(session, title, caption, text, messageType="dm", max=10000)
widgetUtils.connect_event(self.message.autocompletionButton, widgetUtils.BUTTON_PRESSED, self.autocomplete_users) widgetUtils.connect_event(self.message.autocompletionButton, widgetUtils.BUTTON_PRESSED, self.autocomplete_users)
self.text_processor() self.text_processor()
widgetUtils.connect_event(self.message.cb, widgetUtils.ENTERED_TEXT, self.user_changed) widgetUtils.connect_event(self.message.cb, widgetUtils.ENTERED_TEXT, self.user_changed)
def user_changed(self, *args, **kwargs): def user_changed(self, *args, **kwargs):
self.title = _("Direct message to %s") % (self.message.get_user()) self.title = _("Direct message to %s") % (self.message.get_user())
self.text_processor() self.text_processor()
def autocomplete_users(self, *args, **kwargs): def autocomplete_users(self, *args, **kwargs):
c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id) c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id)
c.show_menu("dm") c.show_menu("dm")
class viewTweet(basicTweet): class viewTweet(basicTweet):
def __init__(self, tweet, tweetList, is_tweet=True, utc_offset=0, date=""): def __init__(self, tweet, tweetList, is_tweet=True, utc_offset=0, date="", item_url=""):
""" This represents a tweet displayer. However it could be used for showing something wich is not a tweet, like a direct message or an event. """ This represents a tweet displayer. However it could be used for showing something wich is not a tweet, like a direct message or an event.
param tweet: A dictionary that represents a full tweet or a string for non-tweets. param tweet: A dictionary that represents a full tweet or a string for non-tweets.
param tweetList: If is_tweet is set to True, this could be a list of quoted tweets. param tweetList: If is_tweet is set to True, this could be a list of quoted tweets.
param is_tweet: True or false, depending wether the passed object is a tweet or not.""" param is_tweet: True or false, depending wether the passed object is a tweet or not."""
if is_tweet == True: if is_tweet == True:
self.title = _(u"Tweet") self.title = _(u"Tweet")
image_description = [] image_description = []
text = "" text = ""
for i in range(0, len(tweetList)): for i in range(0, len(tweetList)):
# tweets with message keys are longer tweets, the message value is the full messaje taken from twishort. # tweets with message keys are longer tweets, the message value is the full messaje taken from twishort.
if hasattr(tweetList[i], "message") and tweetList[i].is_quote_status == False: if hasattr(tweetList[i], "message") and tweetList[i].is_quote_status == False:
value = "message" value = "message"
else: else:
value = "full_text" value = "full_text"
if hasattr(tweetList[i], "retweeted_status") and tweetList[i].is_quote_status == False: if hasattr(tweetList[i], "retweeted_status") and tweetList[i].is_quote_status == False:
if not hasattr(tweetList[i], "message"): if not hasattr(tweetList[i], "message"):
text = text + "rt @%s: %s\n" % (tweetList[i].retweeted_status.user.screen_name, tweetList[i].retweeted_status.full_text) text = text + "rt @%s: %s\n" % (tweetList[i].retweeted_status.user.screen_name, tweetList[i].retweeted_status.full_text)
else: else:
text = text + "rt @%s: %s\n" % (tweetList[i].retweeted_status.user.screen_name, getattr(tweetList[i], value)) text = text + "rt @%s: %s\n" % (tweetList[i].retweeted_status.user.screen_name, getattr(tweetList[i], value))
else: else:
text = text + " @%s: %s\n" % (tweetList[i].user.screen_name, getattr(tweetList[i], value)) text = text + " @%s: %s\n" % (tweetList[i].user.screen_name, getattr(tweetList[i], value))
# tweets with extended_entities could include image descriptions. # tweets with extended_entities could include image descriptions.
if hasattr(tweetList[i], "extended_entities") and "media" in tweetList[i].extended_entities: if hasattr(tweetList[i], "extended_entities") and "media" in tweetList[i].extended_entities:
for z in tweetList[i].extended_entities["media"]: for z in tweetList[i].extended_entities["media"]:
if "ext_alt_text" in z and z["ext_alt_text"] != None: if "ext_alt_text" in z and z["ext_alt_text"] != None:
image_description.append(z["ext_alt_text"]) image_description.append(z["ext_alt_text"])
if hasattr(tweetList[i], "retweeted_status") and hasattr(tweetList[i].retweeted_status, "extended_entities") and "media" in tweetList[i].retweeted_status["extended_entities"]: if hasattr(tweetList[i], "retweeted_status") and hasattr(tweetList[i].retweeted_status, "extended_entities") and "media" in tweetList[i].retweeted_status["extended_entities"]:
for z in tweetList[i].retweeted_status.extended_entities["media"]: for z in tweetList[i].retweeted_status.extended_entities["media"]:
if "ext_alt_text" in z and z["ext_alt_text"] != None: if "ext_alt_text" in z and z["ext_alt_text"] != None:
image_description.append(z["ext_alt_text"]) image_description.append(z["ext_alt_text"])
# set rt and likes counters. # set rt and likes counters.
rt_count = str(tweet.retweet_count) rt_count = str(tweet.retweet_count)
favs_count = str(tweet.favorite_count) favs_count = str(tweet.favorite_count)
# Gets the client from where this tweet was made. # Gets the client from where this tweet was made.
source = tweet.source source = tweet.source
original_date = arrow.get(tweet.created_at, locale="en") original_date = arrow.get(tweet.created_at, locale="en")
date = original_date.shift(seconds=utc_offset).format(_(u"MMM D, YYYY. H:m"), locale=languageHandler.getLanguage()) date = original_date.shift(seconds=utc_offset).format(_(u"MMM D, YYYY. H:m"), locale=languageHandler.getLanguage())
if text == "": if text == "":
if hasattr(tweet, "message"): if hasattr(tweet, "message"):
value = "message" value = "message"
else: else:
value = "full_text" value = "full_text"
if hasattr(tweet, "retweeted_status"): if hasattr(tweet, "retweeted_status"):
if not hasattr(tweet, "message"): if not hasattr(tweet, "message"):
text = "rt @%s: %s" % (tweet.retweeted_status.user.screen_name, tweet.retweeted_status.full_text) text = "rt @%s: %s" % (tweet.retweeted_status.user.screen_name, tweet.retweeted_status.full_text)
else: else:
text = "rt @%s: %s" % (tweet.retweeted_status.user.screen_name, getattr(tweet, value)) text = "rt @%s: %s" % (tweet.retweeted_status.user.screen_name, getattr(tweet, value))
else: else:
text = getattr(tweet, value) text = getattr(tweet, value)
text = self.clear_text(text) text = self.clear_text(text)
if hasattr(tweet, "extended_entities") and "media" in tweet.extended_entities: if hasattr(tweet, "extended_entities") and "media" in tweet.extended_entities:
for z in tweet.extended_entities["media"]: for z in tweet.extended_entities["media"]:
if "ext_alt_text" in z and z["ext_alt_text"] != None: if "ext_alt_text" in z and z["ext_alt_text"] != None:
image_description.append(z["ext_alt_text"]) image_description.append(z["ext_alt_text"])
if hasattr(tweet, "retweeted_status") and hasattr(tweet.retweeted_status, "extended_entities") and "media" in tweet.retweeted_status.extended_entities: if hasattr(tweet, "retweeted_status") and hasattr(tweet.retweeted_status, "extended_entities") and "media" in tweet.retweeted_status.extended_entities:
for z in tweet.retweeted_status.extended_entities["media"]: for z in tweet.retweeted_status.extended_entities["media"]:
if "ext_alt_text" in z and z["ext_alt_text"] != None: if "ext_alt_text" in z and z["ext_alt_text"] != None:
image_description.append(z["ext_alt_text"]) image_description.append(z["ext_alt_text"])
self.message = message.viewTweet(text, rt_count, favs_count, source, date) self.message = message.viewTweet(text, rt_count, favs_count, source, date)
results = parse_tweet(text) results = parse_tweet(text)
self.message.set_title(results.weightedLength) self.message.set_title(results.weightedLength)
[self.message.set_image_description(i) for i in image_description] [self.message.set_image_description(i) for i in image_description]
else: else:
self.title = _(u"View item") self.title = _(u"View item")
text = tweet text = tweet
self.message = message.viewNonTweet(text, date) self.message = message.viewNonTweet(text, date)
widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck) widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck)
widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate) if item_url != "":
if self.contain_urls() == True: self.message.enable_button("share")
self.message.enable_button("unshortenButton") widgetUtils.connect_event(self.message.share, widgetUtils.BUTTON_PRESSED, self.share)
widgetUtils.connect_event(self.message.unshortenButton, widgetUtils.BUTTON_PRESSED, self.unshorten) self.item_url = item_url
self.message.get_response() widgetUtils.connect_event(self.message.translateButton, widgetUtils.BUTTON_PRESSED, self.translate)
if self.contain_urls() == True:
self.message.enable_button("unshortenButton")
widgetUtils.connect_event(self.message.unshortenButton, widgetUtils.BUTTON_PRESSED, self.unshorten)
self.message.get_response()
def contain_urls(self): def contain_urls(self):
if len(utils.find_urls_in_text(self.message.get_text())) > 0: if len(utils.find_urls_in_text(self.message.get_text())) > 0:
return True return True
return False return False
def clear_text(self, text): def clear_text(self, text):
urls = utils.find_urls_in_text(text) urls = utils.find_urls_in_text(text)
for i in urls: for i in urls:
if "https://twitter.com/" in i: if "https://twitter.com/" in i:
text = text.replace(i, "\n") text = text.replace(i, "\n")
return text return text
def share(self, *args, **kwargs):
if hasattr(self, "item_url"):
output.copy(self.item_url)
output.speak(_("Link copied to clipboard."))

View File

@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from builtins import str
from builtins import object
import os import os
import webbrowser import webbrowser
import sound_lib import sound_lib
@@ -24,202 +21,206 @@ from collections import OrderedDict
from mysc import autostart as autostart_windows from mysc import autostart as autostart_windows
class globalSettingsController(object): class globalSettingsController(object):
def __init__(self): def __init__(self):
super(globalSettingsController, self).__init__() super(globalSettingsController, self).__init__()
self.dialog = configuration.configurationDialog() self.dialog = configuration.configurationDialog()
self.create_config() self.create_config()
self.needs_restart = False self.needs_restart = False
self.is_started = True self.is_started = True
def make_kmmap(self): def make_kmmap(self):
res={} res={}
for i in os.listdir(os.path.join(paths.app_path(), 'keymaps')): for i in os.listdir(os.path.join(paths.app_path(), 'keymaps')):
if ".keymap" not in i: if ".keymap" not in i:
continue continue
try: try:
res[i[:-7]] =i res[i[:-7]] =i
except: except:
log.exception("Exception while loading keymap " + i) log.exception("Exception while loading keymap " + i)
return res return res
def create_config(self): def create_config(self):
self.kmmap=self.make_kmmap() self.kmmap=self.make_kmmap()
self.langs = languageHandler.getAvailableLanguages() self.langs = languageHandler.getAvailableLanguages()
langs = [] langs = []
[langs.append(i[1]) for i in self.langs] [langs.append(i[1]) for i in self.langs]
self.codes = [] self.codes = []
[self.codes.append(i[0]) for i in self.langs] [self.codes.append(i[0]) for i in self.langs]
id = self.codes.index(config.app["app-settings"]["language"]) id = self.codes.index(config.app["app-settings"]["language"])
self.kmfriendlies=[] self.kmfriendlies=[]
self.kmnames=[] self.kmnames=[]
for k,v in list(self.kmmap.items()): for k,v in list(self.kmmap.items()):
self.kmfriendlies.append(k) self.kmfriendlies.append(k)
self.kmnames.append(v) self.kmnames.append(v)
self.kmid=self.kmnames.index(config.app['app-settings']['load_keymap']) self.kmid=self.kmnames.index(config.app['app-settings']['load_keymap'])
self.dialog.create_general(langs,self.kmfriendlies) self.dialog.create_general(langs,self.kmfriendlies)
self.dialog.general.language.SetSelection(id) self.dialog.general.language.SetSelection(id)
self.dialog.general.km.SetSelection(self.kmid) self.dialog.general.km.SetSelection(self.kmid)
if paths.mode == "installed": if paths.mode == "installed":
self.dialog.set_value("general", "autostart", config.app["app-settings"]["autostart"]) self.dialog.set_value("general", "autostart", config.app["app-settings"]["autostart"])
else: else:
self.dialog.general.autostart.Enable(False) self.dialog.general.autostart.Enable(False)
self.dialog.set_value("general", "ask_at_exit", config.app["app-settings"]["ask_at_exit"]) self.dialog.set_value("general", "ask_at_exit", config.app["app-settings"]["ask_at_exit"])
self.dialog.set_value("general", "no_streaming", config.app["app-settings"]["no_streaming"]) self.dialog.set_value("general", "no_streaming", config.app["app-settings"]["no_streaming"])
self.dialog.set_value("general", "play_ready_sound", config.app["app-settings"]["play_ready_sound"]) self.dialog.set_value("general", "play_ready_sound", config.app["app-settings"]["play_ready_sound"])
self.dialog.set_value("general", "speak_ready_msg", config.app["app-settings"]["speak_ready_msg"]) self.dialog.set_value("general", "speak_ready_msg", config.app["app-settings"]["speak_ready_msg"])
self.dialog.set_value("general", "handle_longtweets", config.app["app-settings"]["handle_longtweets"]) self.dialog.set_value("general", "handle_longtweets", config.app["app-settings"]["handle_longtweets"])
self.dialog.set_value("general", "use_invisible_shorcuts", config.app["app-settings"]["use_invisible_keyboard_shorcuts"]) self.dialog.set_value("general", "use_invisible_shorcuts", config.app["app-settings"]["use_invisible_keyboard_shorcuts"])
self.dialog.set_value("general", "disable_sapi5", config.app["app-settings"]["voice_enabled"]) self.dialog.set_value("general", "disable_sapi5", config.app["app-settings"]["voice_enabled"])
self.dialog.set_value("general", "hide_gui", config.app["app-settings"]["hide_gui"]) self.dialog.set_value("general", "hide_gui", config.app["app-settings"]["hide_gui"])
self.dialog.set_value("general", "update_period", config.app["app-settings"]["update_period"]) self.dialog.set_value("general", "update_period", config.app["app-settings"]["update_period"])
self.dialog.set_value("general", "check_for_updates", config.app["app-settings"]["check_for_updates"]) self.dialog.set_value("general", "check_for_updates", config.app["app-settings"]["check_for_updates"])
self.dialog.set_value("general", "remember_mention_and_longtweet", config.app["app-settings"]["remember_mention_and_longtweet"]) self.dialog.set_value("general", "remember_mention_and_longtweet", config.app["app-settings"]["remember_mention_and_longtweet"])
proxyTypes = [_("System default"), _("HTTP"), _("SOCKS v4"), _("SOCKS v4 with DNS support"), _("SOCKS v5"), _("SOCKS v5 with DNS support")] proxyTypes = [_("System default"), _("HTTP"), _("SOCKS v4"), _("SOCKS v4 with DNS support"), _("SOCKS v5"), _("SOCKS v5 with DNS support")]
self.dialog.create_proxy(proxyTypes) self.dialog.create_proxy(proxyTypes)
try: try:
self.dialog.proxy.type.SetSelection(config.app["proxy"]["type"]) self.dialog.proxy.type.SetSelection(config.app["proxy"]["type"])
except: except:
self.dialog.proxy.type.SetSelection(0) self.dialog.proxy.type.SetSelection(0)
self.dialog.set_value("proxy", "server", config.app["proxy"]["server"]) self.dialog.set_value("proxy", "server", config.app["proxy"]["server"])
self.dialog.set_value("proxy", "port", config.app["proxy"]["port"]) self.dialog.set_value("proxy", "port", config.app["proxy"]["port"])
self.dialog.set_value("proxy", "user", config.app["proxy"]["user"]) self.dialog.set_value("proxy", "user", config.app["proxy"]["user"])
self.dialog.set_value("proxy", "password", config.app["proxy"]["password"]) self.dialog.set_value("proxy", "password", config.app["proxy"]["password"])
self.dialog.realize() self.dialog.realize()
self.response = self.dialog.get_response() self.response = self.dialog.get_response()
def save_configuration(self): def save_configuration(self):
if self.codes[self.dialog.general.language.GetSelection()] != config.app["app-settings"]["language"]: if self.codes[self.dialog.general.language.GetSelection()] != config.app["app-settings"]["language"]:
config.app["app-settings"]["language"] = self.codes[self.dialog.general.language.GetSelection()] config.app["app-settings"]["language"] = self.codes[self.dialog.general.language.GetSelection()]
languageHandler.setLanguage(config.app["app-settings"]["language"]) languageHandler.setLanguage(config.app["app-settings"]["language"])
self.needs_restart = True self.needs_restart = True
if self.kmnames[self.dialog.general.km.GetSelection()] != config.app["app-settings"]["load_keymap"]: if self.kmnames[self.dialog.general.km.GetSelection()] != config.app["app-settings"]["load_keymap"]:
config.app["app-settings"]["load_keymap"] =self.kmnames[self.dialog.general.km.GetSelection()] config.app["app-settings"]["load_keymap"] =self.kmnames[self.dialog.general.km.GetSelection()]
kmFile = open(os.path.join(paths.config_path(), "keymap.keymap"), "w") kmFile = open(os.path.join(paths.config_path(), "keymap.keymap"), "w")
kmFile.close() kmFile.close()
self.needs_restart = True self.needs_restart = True
if config.app["app-settings"]["autostart"] != self.dialog.get_value("general", "autostart") and paths.mode == "installed": if config.app["app-settings"]["autostart"] != self.dialog.get_value("general", "autostart") and paths.mode == "installed":
config.app["app-settings"]["autostart"] = self.dialog.get_value("general", "autostart") config.app["app-settings"]["autostart"] = self.dialog.get_value("general", "autostart")
autostart_windows.setAutoStart(application.name, enable=self.dialog.get_value("general", "autostart")) autostart_windows.setAutoStart(application.name, enable=self.dialog.get_value("general", "autostart"))
if config.app["app-settings"]["use_invisible_keyboard_shorcuts"] != self.dialog.get_value("general", "use_invisible_shorcuts"): if config.app["app-settings"]["use_invisible_keyboard_shorcuts"] != self.dialog.get_value("general", "use_invisible_shorcuts"):
config.app["app-settings"]["use_invisible_keyboard_shorcuts"] = self.dialog.get_value("general", "use_invisible_shorcuts") config.app["app-settings"]["use_invisible_keyboard_shorcuts"] = self.dialog.get_value("general", "use_invisible_shorcuts")
pub.sendMessage("invisible-shorcuts-changed", registered=self.dialog.get_value("general", "use_invisible_shorcuts")) pub.sendMessage("invisible-shorcuts-changed", registered=self.dialog.get_value("general", "use_invisible_shorcuts"))
if config.app["app-settings"]["no_streaming"] != self.dialog.get_value("general", "no_streaming"): if config.app["app-settings"]["no_streaming"] != self.dialog.get_value("general", "no_streaming"):
config.app["app-settings"]["no_streaming"] = self.dialog.get_value("general", "no_streaming") config.app["app-settings"]["no_streaming"] = self.dialog.get_value("general", "no_streaming")
self.needs_restart = True self.needs_restart = True
if config.app["app-settings"]["update_period"] != self.dialog.get_value("general", "update_period"): if config.app["app-settings"]["update_period"] != self.dialog.get_value("general", "update_period"):
config.app["app-settings"]["update_period"] = self.dialog.get_value("general", "update_period") config.app["app-settings"]["update_period"] = self.dialog.get_value("general", "update_period")
self.needs_restart = True self.needs_restart = True
config.app["app-settings"]["voice_enabled"] = self.dialog.get_value("general", "disable_sapi5") config.app["app-settings"]["voice_enabled"] = self.dialog.get_value("general", "disable_sapi5")
config.app["app-settings"]["hide_gui"] = self.dialog.get_value("general", "hide_gui") config.app["app-settings"]["hide_gui"] = self.dialog.get_value("general", "hide_gui")
config.app["app-settings"]["ask_at_exit"] = self.dialog.get_value("general", "ask_at_exit") config.app["app-settings"]["ask_at_exit"] = self.dialog.get_value("general", "ask_at_exit")
config.app["app-settings"]["handle_longtweets"] = self.dialog.get_value("general", "handle_longtweets") config.app["app-settings"]["handle_longtweets"] = self.dialog.get_value("general", "handle_longtweets")
config.app["app-settings"]["play_ready_sound"] = self.dialog.get_value("general", "play_ready_sound") config.app["app-settings"]["play_ready_sound"] = self.dialog.get_value("general", "play_ready_sound")
config.app["app-settings"]["speak_ready_msg"] = self.dialog.get_value("general", "speak_ready_msg") config.app["app-settings"]["speak_ready_msg"] = self.dialog.get_value("general", "speak_ready_msg")
config.app["app-settings"]["check_for_updates"] = self.dialog.get_value("general", "check_for_updates") config.app["app-settings"]["check_for_updates"] = self.dialog.get_value("general", "check_for_updates")
config.app["app-settings"]["remember_mention_and_longtweet"] = self.dialog.get_value("general", "remember_mention_and_longtweet") config.app["app-settings"]["remember_mention_and_longtweet"] = self.dialog.get_value("general", "remember_mention_and_longtweet")
if config.app["proxy"]["type"]!=self.dialog.get_value("proxy", "type") or config.app["proxy"]["server"] != self.dialog.get_value("proxy", "server") or config.app["proxy"]["port"] != self.dialog.get_value("proxy", "port") or config.app["proxy"]["user"] != self.dialog.get_value("proxy", "user") or config.app["proxy"]["password"] != self.dialog.get_value("proxy", "password"): if config.app["proxy"]["type"]!=self.dialog.get_value("proxy", "type") or config.app["proxy"]["server"] != self.dialog.get_value("proxy", "server") or config.app["proxy"]["port"] != self.dialog.get_value("proxy", "port") or config.app["proxy"]["user"] != self.dialog.get_value("proxy", "user") or config.app["proxy"]["password"] != self.dialog.get_value("proxy", "password"):
if self.is_started == True: if self.is_started == True:
self.needs_restart = True self.needs_restart = True
config.app["proxy"]["type"] = self.dialog.proxy.type.Selection config.app["proxy"]["type"] = self.dialog.proxy.type.Selection
config.app["proxy"]["server"] = self.dialog.get_value("proxy", "server") config.app["proxy"]["server"] = self.dialog.get_value("proxy", "server")
config.app["proxy"]["port"] = self.dialog.get_value("proxy", "port") config.app["proxy"]["port"] = self.dialog.get_value("proxy", "port")
config.app["proxy"]["user"] = self.dialog.get_value("proxy", "user") config.app["proxy"]["user"] = self.dialog.get_value("proxy", "user")
config.app["proxy"]["password"] = self.dialog.get_value("proxy", "password") config.app["proxy"]["password"] = self.dialog.get_value("proxy", "password")
config.app.write() config.app.write()
class accountSettingsController(globalSettingsController): class accountSettingsController(globalSettingsController):
def __init__(self, buffer, window): def __init__(self, buffer, window):
self.user = buffer.session.db["user_name"] self.user = buffer.session.db["user_name"]
self.buffer = buffer self.buffer = buffer
self.window = window self.window = window
self.config = buffer.session.settings self.config = buffer.session.settings
super(accountSettingsController, self).__init__() super(accountSettingsController, self).__init__()
def create_config(self): def create_config(self):
self.dialog.create_general_account() self.dialog.create_general_account()
widgetUtils.connect_event(self.dialog.general.au, widgetUtils.BUTTON_PRESSED, self.manage_autocomplete) widgetUtils.connect_event(self.dialog.general.au, widgetUtils.BUTTON_PRESSED, self.manage_autocomplete)
self.dialog.set_value("general", "relative_time", self.config["general"]["relative_times"]) self.dialog.set_value("general", "relative_time", self.config["general"]["relative_times"])
self.dialog.set_value("general", "show_screen_names", self.config["general"]["show_screen_names"]) self.dialog.set_value("general", "show_screen_names", self.config["general"]["show_screen_names"])
self.dialog.set_value("general", "itemsPerApiCall", self.config["general"]["max_tweets_per_call"]) self.dialog.set_value("general", "itemsPerApiCall", self.config["general"]["max_tweets_per_call"])
self.dialog.set_value("general", "reverse_timelines", self.config["general"]["reverse_timelines"]) self.dialog.set_value("general", "reverse_timelines", self.config["general"]["reverse_timelines"])
rt = self.config["general"]["retweet_mode"] rt = self.config["general"]["retweet_mode"]
if rt == "ask": if rt == "ask":
self.dialog.set_value("general", "retweet_mode", _(u"Ask")) self.dialog.set_value("general", "retweet_mode", _(u"Ask"))
elif rt == "direct": elif rt == "direct":
self.dialog.set_value("general", "retweet_mode", _(u"Retweet without comments")) self.dialog.set_value("general", "retweet_mode", _(u"Retweet without comments"))
else: else:
self.dialog.set_value("general", "retweet_mode", _(u"Retweet with comments")) self.dialog.set_value("general", "retweet_mode", _(u"Retweet with comments"))
self.dialog.set_value("general", "persist_size", str(self.config["general"]["persist_size"])) self.dialog.set_value("general", "persist_size", str(self.config["general"]["persist_size"]))
self.dialog.create_reporting() self.dialog.set_value("general", "load_cache_in_memory", self.config["general"]["load_cache_in_memory"])
self.dialog.set_value("reporting", "speech_reporting", self.config["reporting"]["speech_reporting"]) self.dialog.create_reporting()
self.dialog.set_value("reporting", "braille_reporting", self.config["reporting"]["braille_reporting"]) self.dialog.set_value("reporting", "speech_reporting", self.config["reporting"]["speech_reporting"])
self.dialog.create_other_buffers() self.dialog.set_value("reporting", "braille_reporting", self.config["reporting"]["braille_reporting"])
buffer_values = self.get_buffers_list() self.dialog.create_other_buffers()
self.dialog.buffers.insert_buffers(buffer_values) buffer_values = self.get_buffers_list()
self.dialog.buffers.connect_hook_func(self.toggle_buffer_active) self.dialog.buffers.insert_buffers(buffer_values)
widgetUtils.connect_event(self.dialog.buffers.toggle_state, widgetUtils.BUTTON_PRESSED, self.toggle_state) self.dialog.buffers.connect_hook_func(self.toggle_buffer_active)
widgetUtils.connect_event(self.dialog.buffers.up, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_up) widgetUtils.connect_event(self.dialog.buffers.toggle_state, widgetUtils.BUTTON_PRESSED, self.toggle_state)
widgetUtils.connect_event(self.dialog.buffers.down, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_down) widgetUtils.connect_event(self.dialog.buffers.up, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_up)
widgetUtils.connect_event(self.dialog.buffers.down, widgetUtils.BUTTON_PRESSED, self.dialog.buffers.move_down)
self.dialog.create_ignored_clients(self.config["twitter"]["ignored_clients"]) self.dialog.create_ignored_clients(self.config["twitter"]["ignored_clients"])
widgetUtils.connect_event(self.dialog.ignored_clients.add, widgetUtils.BUTTON_PRESSED, self.add_ignored_client) widgetUtils.connect_event(self.dialog.ignored_clients.add, widgetUtils.BUTTON_PRESSED, self.add_ignored_client)
widgetUtils.connect_event(self.dialog.ignored_clients.remove, widgetUtils.BUTTON_PRESSED, self.remove_ignored_client) widgetUtils.connect_event(self.dialog.ignored_clients.remove, widgetUtils.BUTTON_PRESSED, self.remove_ignored_client)
self.input_devices = sound_lib.input.Input.get_device_names() self.input_devices = sound_lib.input.Input.get_device_names()
self.output_devices = sound_lib.output.Output.get_device_names() self.output_devices = sound_lib.output.Output.get_device_names()
self.soundpacks = [] self.soundpacks = []
[self.soundpacks.append(i) for i in os.listdir(paths.sound_path()) if os.path.isdir(os.path.join(paths.sound_path(), i)) == True ] [self.soundpacks.append(i) for i in os.listdir(paths.sound_path()) if os.path.isdir(os.path.join(paths.sound_path(), i)) == True ]
self.dialog.create_sound(self.input_devices, self.output_devices, self.soundpacks) self.dialog.create_sound(self.input_devices, self.output_devices, self.soundpacks)
self.dialog.set_value("sound", "volumeCtrl", self.config["sound"]["volume"]*100) self.dialog.set_value("sound", "volumeCtrl", self.config["sound"]["volume"]*100)
self.dialog.set_value("sound", "input", self.config["sound"]["input_device"]) self.dialog.set_value("sound", "input", self.config["sound"]["input_device"])
self.dialog.set_value("sound", "output", self.config["sound"]["output_device"]) self.dialog.set_value("sound", "output", self.config["sound"]["output_device"])
self.dialog.set_value("sound", "session_mute", self.config["sound"]["session_mute"]) self.dialog.set_value("sound", "session_mute", self.config["sound"]["session_mute"])
self.dialog.set_value("sound", "soundpack", self.config["sound"]["current_soundpack"]) self.dialog.set_value("sound", "soundpack", self.config["sound"]["current_soundpack"])
self.dialog.set_value("sound", "indicate_audio", self.config["sound"]["indicate_audio"]) self.dialog.set_value("sound", "indicate_audio", self.config["sound"]["indicate_audio"])
self.dialog.set_value("sound", "indicate_geo", self.config["sound"]["indicate_geo"]) self.dialog.set_value("sound", "indicate_geo", self.config["sound"]["indicate_geo"])
self.dialog.set_value("sound", "indicate_img", self.config["sound"]["indicate_img"]) self.dialog.set_value("sound", "indicate_img", self.config["sound"]["indicate_img"])
self.dialog.create_extras(OCRSpace.translatable_langs) self.dialog.create_extras(OCRSpace.translatable_langs)
self.dialog.set_value("extras", "sndup_apiKey", self.config["sound"]["sndup_api_key"]) self.dialog.set_value("extras", "sndup_apiKey", self.config["sound"]["sndup_api_key"])
language_index = OCRSpace.OcrLangs.index(self.config["mysc"]["ocr_language"]) language_index = OCRSpace.OcrLangs.index(self.config["mysc"]["ocr_language"])
self.dialog.extras.ocr_lang.SetSelection(language_index) self.dialog.extras.ocr_lang.SetSelection(language_index)
self.dialog.realize() self.dialog.realize()
self.dialog.set_title(_(u"Account settings for %s") % (self.user,)) self.dialog.set_title(_(u"Account settings for %s") % (self.user,))
self.response = self.dialog.get_response() self.response = self.dialog.get_response()
def save_configuration(self): def save_configuration(self):
if self.config["general"]["relative_times"] != self.dialog.get_value("general", "relative_time"): if self.config["general"]["relative_times"] != self.dialog.get_value("general", "relative_time"):
self.needs_restart = True self.needs_restart = True
self.config["general"]["relative_times"] = self.dialog.get_value("general", "relative_time") self.config["general"]["relative_times"] = self.dialog.get_value("general", "relative_time")
self.config["general"]["show_screen_names"] = self.dialog.get_value("general", "show_screen_names") self.config["general"]["show_screen_names"] = self.dialog.get_value("general", "show_screen_names")
self.config["general"]["max_tweets_per_call"] = self.dialog.get_value("general", "itemsPerApiCall") self.config["general"]["max_tweets_per_call"] = self.dialog.get_value("general", "itemsPerApiCall")
if self.config["general"]["persist_size"] != self.dialog.get_value("general", "persist_size"): if self.config["general"]["load_cache_in_memory"] != self.dialog.get_value("general", "load_cache_in_memory"):
if self.dialog.get_value("general", "persist_size") == '': self.config["general"]["load_cache_in_memory"] = self.dialog.get_value("general", "load_cache_in_memory")
self.config["general"]["persist_size"] =-1 self.needs_restart = True
else: if self.config["general"]["persist_size"] != self.dialog.get_value("general", "persist_size"):
try: if self.dialog.get_value("general", "persist_size") == '':
self.config["general"]["persist_size"] = int(self.dialog.get_value("general", "persist_size")) self.config["general"]["persist_size"] =-1
except ValueError: else:
output.speak("Invalid cache size, setting to default.",True) try:
self.config["general"]["persist_size"] =1764 self.config["general"]["persist_size"] = int(self.dialog.get_value("general", "persist_size"))
except ValueError:
output.speak("Invalid cache size, setting to default.",True)
self.config["general"]["persist_size"] =1764
if self.config["general"]["reverse_timelines"] != self.dialog.get_value("general", "reverse_timelines"): if self.config["general"]["reverse_timelines"] != self.dialog.get_value("general", "reverse_timelines"):
self.needs_restart = True self.needs_restart = True
self.config["general"]["reverse_timelines"] = self.dialog.get_value("general", "reverse_timelines") self.config["general"]["reverse_timelines"] = self.dialog.get_value("general", "reverse_timelines")
rt = self.dialog.get_value("general", "retweet_mode") rt = self.dialog.get_value("general", "retweet_mode")
if rt == _(u"Ask"): if rt == _(u"Ask"):
self.config["general"]["retweet_mode"] = "ask" self.config["general"]["retweet_mode"] = "ask"
elif rt == _(u"Retweet without comments"): elif rt == _(u"Retweet without comments"):
self.config["general"]["retweet_mode"] = "direct" self.config["general"]["retweet_mode"] = "direct"
else: else:
self.config["general"]["retweet_mode"] = "comment" self.config["general"]["retweet_mode"] = "comment"
buffers_list = self.dialog.buffers.get_list() buffers_list = self.dialog.buffers.get_list()
if buffers_list != self.config["general"]["buffer_order"]: if buffers_list != self.config["general"]["buffer_order"]:
self.needs_restart = True self.needs_restart = True
self.config["general"]["buffer_order"] = buffers_list self.config["general"]["buffer_order"] = buffers_list
self.config["reporting"]["speech_reporting"] = self.dialog.get_value("reporting", "speech_reporting") self.config["reporting"]["speech_reporting"] = self.dialog.get_value("reporting", "speech_reporting")
self.config["reporting"]["braille_reporting"] = self.dialog.get_value("reporting", "braille_reporting") self.config["reporting"]["braille_reporting"] = self.dialog.get_value("reporting", "braille_reporting")
self.config["mysc"]["ocr_language"] = OCRSpace.OcrLangs[self.dialog.extras.ocr_lang.GetSelection()] self.config["mysc"]["ocr_language"] = OCRSpace.OcrLangs[self.dialog.extras.ocr_lang.GetSelection()]
# if self.config["other_buffers"]["show_followers"] != self.dialog.get_value("buffers", "followers"): # if self.config["other_buffers"]["show_followers"] != self.dialog.get_value("buffers", "followers"):
# self.config["other_buffers"]["show_followers"] = self.dialog.get_value("buffers", "followers") # self.config["other_buffers"]["show_followers"] = self.dialog.get_value("buffers", "followers")
# pub.sendMessage("create-new-buffer", buffer="followers", account=self.user, create=self.config["other_buffers"]["show_followers"]) # pub.sendMessage("create-new-buffer", buffer="followers", account=self.user, create=self.config["other_buffers"]["show_followers"])
@@ -238,75 +239,75 @@ class accountSettingsController(globalSettingsController):
# if self.config["other_buffers"]["show_events"] != self.dialog.get_value("buffers", "events"): # if self.config["other_buffers"]["show_events"] != self.dialog.get_value("buffers", "events"):
# self.config["other_buffers"]["show_events"] = self.dialog.get_value("buffers", "events") # self.config["other_buffers"]["show_events"] = self.dialog.get_value("buffers", "events")
# pub.sendMessage("create-new-buffer", buffer="events", account=self.user, create=self.config["other_buffers"]["show_events"]) # pub.sendMessage("create-new-buffer", buffer="events", account=self.user, create=self.config["other_buffers"]["show_events"])
if self.config["sound"]["input_device"] != self.dialog.sound.get("input"): if self.config["sound"]["input_device"] != self.dialog.sound.get("input"):
self.config["sound"]["input_device"] = self.dialog.sound.get("input") self.config["sound"]["input_device"] = self.dialog.sound.get("input")
try: try:
self.buffer.session.sound.input.set_device(self.buffer.session.sound.input.find_device_by_name(self.config["sound"]["input_device"])) self.buffer.session.sound.input.set_device(self.buffer.session.sound.input.find_device_by_name(self.config["sound"]["input_device"]))
except: except:
self.config["sound"]["input_device"] = "default" self.config["sound"]["input_device"] = "default"
if self.config["sound"]["output_device"] != self.dialog.sound.get("output"): if self.config["sound"]["output_device"] != self.dialog.sound.get("output"):
self.config["sound"]["output_device"] = self.dialog.sound.get("output") self.config["sound"]["output_device"] = self.dialog.sound.get("output")
try: try:
self.buffer.session.sound.output.set_device(self.buffer.session.sound.output.find_device_by_name(self.config["sound"]["output_device"])) self.buffer.session.sound.output.set_device(self.buffer.session.sound.output.find_device_by_name(self.config["sound"]["output_device"]))
except: except:
self.config["sound"]["output_device"] = "default" self.config["sound"]["output_device"] = "default"
self.config["sound"]["volume"] = self.dialog.get_value("sound", "volumeCtrl")/100.0 self.config["sound"]["volume"] = self.dialog.get_value("sound", "volumeCtrl")/100.0
self.config["sound"]["session_mute"] = self.dialog.get_value("sound", "session_mute") self.config["sound"]["session_mute"] = self.dialog.get_value("sound", "session_mute")
self.config["sound"]["current_soundpack"] = self.dialog.sound.get("soundpack") self.config["sound"]["current_soundpack"] = self.dialog.sound.get("soundpack")
self.config["sound"]["indicate_audio"] = self.dialog.get_value("sound", "indicate_audio") self.config["sound"]["indicate_audio"] = self.dialog.get_value("sound", "indicate_audio")
self.config["sound"]["indicate_geo"] = self.dialog.get_value("sound", "indicate_geo") self.config["sound"]["indicate_geo"] = self.dialog.get_value("sound", "indicate_geo")
self.config["sound"]["indicate_img"] = self.dialog.get_value("sound", "indicate_img") self.config["sound"]["indicate_img"] = self.dialog.get_value("sound", "indicate_img")
self.config["sound"]["sndup_api_key"] = self.dialog.get_value("extras", "sndup_apiKey") self.config["sound"]["sndup_api_key"] = self.dialog.get_value("extras", "sndup_apiKey")
self.buffer.session.sound.config = self.config["sound"] self.buffer.session.sound.config = self.config["sound"]
self.buffer.session.sound.check_soundpack() self.buffer.session.sound.check_soundpack()
self.config.write() self.config.write()
def toggle_state(self,*args,**kwargs): def toggle_state(self,*args,**kwargs):
return self.dialog.buffers.change_selected_item() return self.dialog.buffers.change_selected_item()
def manage_autocomplete(self, *args, **kwargs): def manage_autocomplete(self, *args, **kwargs):
configuration = settings.autocompletionSettings(self.buffer.session.settings, self.buffer, self.window) configuration = settings.autocompletionSettings(self.buffer.session.settings, self.buffer, self.window)
def add_ignored_client(self, *args, **kwargs): def add_ignored_client(self, *args, **kwargs):
client = commonMessageDialogs.get_ignored_client() client = commonMessageDialogs.get_ignored_client()
if client == None: return if client == None: return
if client not in self.config["twitter"]["ignored_clients"]: if client not in self.config["twitter"]["ignored_clients"]:
self.config["twitter"]["ignored_clients"].append(client) self.config["twitter"]["ignored_clients"].append(client)
self.dialog.ignored_clients.append(client) self.dialog.ignored_clients.append(client)
def remove_ignored_client(self, *args, **kwargs): def remove_ignored_client(self, *args, **kwargs):
if self.dialog.ignored_clients.get_clients() == 0: return if self.dialog.ignored_clients.get_clients() == 0: return
id = self.dialog.ignored_clients.get_client_id() id = self.dialog.ignored_clients.get_client_id()
self.config["twitter"]["ignored_clients"].pop(id) self.config["twitter"]["ignored_clients"].pop(id)
self.dialog.ignored_clients.remove_(id) self.dialog.ignored_clients.remove_(id)
def get_buffers_list(self): def get_buffers_list(self):
all_buffers=OrderedDict() all_buffers=OrderedDict()
all_buffers['home']=_(u"Home") all_buffers['home']=_(u"Home")
all_buffers['mentions']=_(u"Mentions") all_buffers['mentions']=_(u"Mentions")
all_buffers['dm']=_(u"Direct Messages") all_buffers['dm']=_(u"Direct Messages")
all_buffers['sent_dm']=_(u"Sent direct messages") all_buffers['sent_dm']=_(u"Sent direct messages")
all_buffers['sent_tweets']=_(u"Sent tweets") all_buffers['sent_tweets']=_(u"Sent tweets")
all_buffers['favorites']=_(u"Likes") all_buffers['favorites']=_(u"Likes")
all_buffers['followers']=_(u"Followers") all_buffers['followers']=_(u"Followers")
all_buffers['friends']=_(u"Friends") all_buffers['friends']=_(u"Friends")
all_buffers['blocks']=_(u"Blocked users") all_buffers['blocks']=_(u"Blocked users")
all_buffers['muted']=_(u"Muted users") all_buffers['muted']=_(u"Muted users")
list_buffers = [] list_buffers = []
hidden_buffers=[] hidden_buffers=[]
all_buffers_keys = list(all_buffers.keys()) all_buffers_keys = list(all_buffers.keys())
# Check buffers shown first. # Check buffers shown first.
for i in self.config["general"]["buffer_order"]: for i in self.config["general"]["buffer_order"]:
if i in all_buffers_keys: if i in all_buffers_keys:
list_buffers.append((i, all_buffers[i], True)) list_buffers.append((i, all_buffers[i], True))
# This second pass will retrieve all hidden buffers. # This second pass will retrieve all hidden buffers.
for i in all_buffers_keys: for i in all_buffers_keys:
if i not in self.config["general"]["buffer_order"]: if i not in self.config["general"]["buffer_order"]:
hidden_buffers.append((i, all_buffers[i], False)) hidden_buffers.append((i, all_buffers[i], False))
list_buffers.extend(hidden_buffers) list_buffers.extend(hidden_buffers)
return list_buffers return list_buffers
def toggle_buffer_active(self, ev): def toggle_buffer_active(self, ev):
change = self.dialog.buffers.get_event(ev) change = self.dialog.buffers.get_event(ev)
if change == True: if change == True:
self.dialog.buffers.change_selected_item() self.dialog.buffers.change_selected_item()

View File

@@ -3,43 +3,43 @@ from wxUI.dialogs import trends
import widgetUtils import widgetUtils
class trendingTopicsController(object): class trendingTopicsController(object):
def __init__(self, session): def __init__(self, session):
super(trendingTopicsController, self).__init__() super(trendingTopicsController, self).__init__()
self.countries = {} self.countries = {}
self.cities = {} self.cities = {}
self.dialog = trends.trendingTopicsDialog() self.dialog = trends.trendingTopicsDialog()
self.information = session.twitter.trends_available() self.information = session.twitter.available_trends()
self.split_information() self.split_information()
widgetUtils.connect_event(self.dialog.country, widgetUtils.RADIOBUTTON, self.get_places) widgetUtils.connect_event(self.dialog.country, widgetUtils.RADIOBUTTON, self.get_places)
widgetUtils.connect_event(self.dialog.city, widgetUtils.RADIOBUTTON, self.get_places) widgetUtils.connect_event(self.dialog.city, widgetUtils.RADIOBUTTON, self.get_places)
self.get_places() self.get_places()
def split_information(self): def split_information(self):
for i in self.information: for i in self.information:
if i["placeType"]["name"] == "Country": if i["placeType"]["name"] == "Country":
self.countries[i["name"]] = i["woeid"] self.countries[i["name"]] = i["woeid"]
else: else:
self.cities[i["name"]] = i["woeid"] self.cities[i["name"]] = i["woeid"]
def get_places(self, event=None): def get_places(self, event=None):
values = [] values = []
if self.dialog.get_active() == "country": if self.dialog.get_active() == "country":
for i in self.information: for i in self.information:
if i["placeType"]["name"] == "Country": if i["placeType"]["name"] == "Country":
values.append(i["name"]) values.append(i["name"])
elif self.dialog.get_active() == "city": elif self.dialog.get_active() == "city":
for i in self.information: for i in self.information:
if i["placeType"]["name"] != "Country": if i["placeType"]["name"] != "Country":
values.append(i["name"]) values.append(i["name"])
self.dialog.set(values) self.dialog.set(values)
def get_woeid(self): def get_woeid(self):
selected = self.dialog.get_item() selected = self.dialog.get_item()
if self.dialog.get_active() == "country": if self.dialog.get_active() == "country":
woeid = self.countries[selected] woeid = self.countries[selected]
else: else:
woeid = self.cities[selected] woeid = self.cities[selected]
return woeid return woeid
def get_string(self): def get_string(self):
return self.dialog.get_item() return self.dialog.get_item()

View File

@@ -6,123 +6,123 @@ import output
from wxUI.dialogs import update_profile, show_user from wxUI.dialogs import update_profile, show_user
import logging import logging
log = logging.getLogger("controller.user") log = logging.getLogger("controller.user")
from tweepy.error import TweepError from tweepy.errors import TweepyException, Forbidden, NotFound
from sessions.twitter import utils from sessions.twitter import utils
class profileController(object): class profileController(object):
def __init__(self, session, user=None): def __init__(self, session, user=None):
super(profileController, self).__init__() super(profileController, self).__init__()
self.file = None self.file = None
self.session = session self.session = session
self.user = user self.user = user
if user == None: if user == None:
self.get_data(screen_name=self.session.db["user_name"]) self.get_data(screen_name=self.session.db["user_name"])
self.dialog = update_profile.updateProfileDialog() self.dialog = update_profile.updateProfileDialog()
self.fill_profile_fields() self.fill_profile_fields()
self.uploaded = False self.uploaded = False
widgetUtils.connect_event(self.dialog.upload_image, widgetUtils.BUTTON_PRESSED, self.upload_image) widgetUtils.connect_event(self.dialog.upload_image, widgetUtils.BUTTON_PRESSED, self.upload_image)
else: else:
try: try:
self.get_data(screen_name=self.user) self.get_data(screen_name=self.user)
except TweepError as err: except TweepyException as err:
if err.api_code == 50: if type(err) == NotFound:
wx.MessageDialog(None, _(u"That user does not exist"), _(u"Error"), wx.ICON_ERROR).ShowModal() wx.MessageDialog(None, _(u"That user does not exist"), _(u"Error"), wx.ICON_ERROR).ShowModal()
if err.api_code == 403: if type(err) == Forbidden:
wx.MessageDialog(None, _(u"User has been suspended"), _(u"Error"), wx.ICON_ERROR).ShowModal() wx.MessageDialog(None, _(u"User has been suspended"), _(u"Error"), wx.ICON_ERROR).ShowModal()
log.error("error %d: %s" % (err.api_code, err.reason)) log.error("error %s" % (str(err)))
return return
self.dialog = show_user.showUserProfile() self.dialog = show_user.showUserProfile()
string = self.get_user_info() string = self.get_user_info()
self.dialog.set("text", string) self.dialog.set("text", string)
self.dialog.set_title(_(u"Information for %s") % (self.data.screen_name)) self.dialog.set_title(_(u"Information for %s") % (self.data.screen_name))
if self.data.url != None: if self.data.url != None:
self.dialog.enable_url() self.dialog.enable_url()
widgetUtils.connect_event(self.dialog.url, widgetUtils.BUTTON_PRESSED, self.visit_url) widgetUtils.connect_event(self.dialog.url, widgetUtils.BUTTON_PRESSED, self.visit_url)
if self.dialog.get_response() == widgetUtils.OK and self.user == None: if self.dialog.get_response() == widgetUtils.OK and self.user == None:
self.do_update() self.do_update()
def get_data(self, screen_name): def get_data(self, screen_name):
self.data = self.session.twitter.get_user(screen_name=screen_name) self.data = self.session.twitter.get_user(screen_name=screen_name)
if screen_name != self.session.db["user_name"]: if screen_name != self.session.db["user_name"]:
self.friendship_status = self.session.twitter.show_friendship(source_screen_name=self.session.db["user_name"], target_screen_name=screen_name) self.friendship_status = self.session.twitter.get_friendship(source_screen_name=self.session.db["user_name"], target_screen_name=screen_name)
def fill_profile_fields(self): def fill_profile_fields(self):
self.dialog.set_name(self.data.name) self.dialog.set_name(self.data.name)
if self.data.url != None: if self.data.url != None:
self.dialog.set_url(self.data.url) self.dialog.set_url(self.data.url)
if len(self.data.location) > 0: if len(self.data.location) > 0:
self.dialog.set_location(self.data.location) self.dialog.set_location(self.data.location)
if len(self.data.description) > 0: if len(self.data.description) > 0:
self.dialog.set_description(self.data.description) self.dialog.set_description(self.data.description)
def get_image(self): def get_image(self):
file = self.dialog.upload_picture() file = self.dialog.upload_picture()
if file != None: if file != None:
self.file = open(file, "rb") self.file = open(file, "rb")
self.uploaded = True self.uploaded = True
self.dialog.change_upload_button(self.uploaded) self.dialog.change_upload_button(self.uploaded)
def discard_image(self): def discard_image(self):
self.file = None self.file = None
output.speak(_(u"Discarded")) output.speak(_(u"Discarded"))
self.uploaded = False self.uploaded = False
self.dialog.change_upload_button(self.uploaded) self.dialog.change_upload_button(self.uploaded)
def upload_image(self, *args, **kwargs): def upload_image(self, *args, **kwargs):
if self.uploaded == False: if self.uploaded == False:
self.get_image() self.get_image()
elif self.uploaded == True: elif self.uploaded == True:
self.discard_image() self.discard_image()
def do_update(self): def do_update(self):
if self.user != None: return if self.user != None: return
name = self.dialog.get("name") name = self.dialog.get("name")
description = self.dialog.get("description") description = self.dialog.get("description")
location = self.dialog.get("location") location = self.dialog.get("location")
url = self.dialog.get("url") url = self.dialog.get("url")
if self.file != None: if self.file != None:
try: try:
self.session.twitter.update_profile_image(image=self.file) self.session.twitter.update_profile_image(image=self.file)
except TweepError as e: except TweepyException as e:
output.speak(u"Error %s. %s" % (e.api_code, e.reason)) output.speak(u"Error %s" % (str(e)))
try: try:
self.session.twitter.update_profile(name=name, description=description, location=location, url=url) self.session.twitter.update_profile(name=name, description=description, location=location, url=url)
except TweepError as e: except TweepyException as e:
output.speak(u"Error %s. %s" % (e.api_code, e.reason)) output.speak(u"Error %s." % (str(e)))
def get_user_info(self): def get_user_info(self):
string = u"" string = u""
string = string + _(u"Username: @%s\n") % (self.data.screen_name) string = string + _(u"Username: @%s\n") % (self.data.screen_name)
string = string + _(u"Name: %s\n") % (self.data.name) string = string + _(u"Name: %s\n") % (self.data.name)
if self.data.location != "": if self.data.location != "":
string = string + _(u"Location: %s\n") % (self.data.location) string = string + _(u"Location: %s\n") % (self.data.location)
if self.data.url != None: if self.data.url != None:
string = string+ _(u"URL: %s\n") % (self.data.entities["url"]["urls"][0]["expanded_url"]) string = string+ _(u"URL: %s\n") % (self.data.entities["url"]["urls"][0]["expanded_url"])
if self.data.description != "": if self.data.description != "":
if self.data.entities.get("description") != None and self.data.entities["description"].get("urls"): if self.data.entities.get("description") != None and self.data.entities["description"].get("urls"):
self.data.description = utils.expand_urls(self.data.description, self.data.entities["description"]) self.data.description = utils.expand_urls(self.data.description, self.data.entities["description"])
string = string+ _(u"Bio: %s\n") % (self.data.description) string = string+ _(u"Bio: %s\n") % (self.data.description)
if self.data.protected == True: protected = _(u"Yes") if self.data.protected == True: protected = _(u"Yes")
else: protected = _(u"No") else: protected = _(u"No")
string = string+ _(u"Protected: %s\n") % (protected) string = string+ _(u"Protected: %s\n") % (protected)
if hasattr(self, "friendship_status"): if hasattr(self, "friendship_status"):
relation = False relation = False
friendship = "Relationship: " friendship = "Relationship: "
if self.friendship_status[0].following: if self.friendship_status[0].following:
friendship += _(u"You follow {0}. ").format(self.data.name,) friendship += _(u"You follow {0}. ").format(self.data.name,)
relation = True relation = True
if self.friendship_status[1].following: if self.friendship_status[1].following:
friendship += _(u"{0} is following you.").format(self.data.name,) friendship += _(u"{0} is following you.").format(self.data.name,)
relation = True relation = True
if relation == True: if relation == True:
string = string+friendship+"\n" string = string+friendship+"\n"
string = string+_(u"Followers: %s\n Friends: %s\n") % (self.data.followers_count, self.data.friends_count) string = string+_(u"Followers: %s\n Friends: %s\n") % (self.data.followers_count, self.data.friends_count)
if self.data.verified == True: verified = _(u"Yes") if self.data.verified == True: verified = _(u"Yes")
else: verified = _(u"No") else: verified = _(u"No")
string = string+ _(u"Verified: %s\n") % (verified) string = string+ _(u"Verified: %s\n") % (verified)
string = string+ _(u"Tweets: %s\n") % (self.data.statuses_count) string = string+ _(u"Tweets: %s\n") % (self.data.statuses_count)
string = string+ _(u"Likes: %s") % (self.data.favourites_count) string = string+ _(u"Likes: %s") % (self.data.favourites_count)
return string return string
def visit_url(self, *args, **kwargs): def visit_url(self, *args, **kwargs):
webbrowser.open_new_tab(self.data.url) webbrowser.open_new_tab(self.data.url)

View File

@@ -3,77 +3,77 @@ import widgetUtils
import output import output
from wxUI.dialogs import userActions from wxUI.dialogs import userActions
from pubsub import pub from pubsub import pub
from tweepy.error import TweepError from tweepy.errors import TweepyException
from extra import autocompletionUsers from extra import autocompletionUsers
class userActionsController(object): class userActionsController(object):
def __init__(self, buffer, users=[], default="follow"): def __init__(self, buffer, users=[], default="follow"):
super(userActionsController, self).__init__() super(userActionsController, self).__init__()
self.buffer = buffer self.buffer = buffer
self.session = buffer.session self.session = buffer.session
self.dialog = userActions.UserActionsDialog(users, default) self.dialog = userActions.UserActionsDialog(users, default)
widgetUtils.connect_event(self.dialog.autocompletion, widgetUtils.BUTTON_PRESSED, self.autocomplete_users) widgetUtils.connect_event(self.dialog.autocompletion, widgetUtils.BUTTON_PRESSED, self.autocomplete_users)
if self.dialog.get_response() == widgetUtils.OK: if self.dialog.get_response() == widgetUtils.OK:
self.process_action() self.process_action()
def autocomplete_users(self, *args, **kwargs): def autocomplete_users(self, *args, **kwargs):
c = autocompletionUsers.completion.autocompletionUsers(self.dialog, self.session.session_id) c = autocompletionUsers.completion.autocompletionUsers(self.dialog, self.session.session_id)
c.show_menu("dm") c.show_menu("dm")
def process_action(self): def process_action(self):
action = self.dialog.get_action() action = self.dialog.get_action()
user = self.dialog.get_user() user = self.dialog.get_user()
if user == "": return if user == "": return
getattr(self, action)(user) getattr(self, action)(user)
def follow(self, user): def follow(self, user):
try: try:
self.session.twitter.create_friendship(screen_name=user ) self.session.twitter.create_friendship(screen_name=user )
except TweepError as err: except TweepyException as err:
output.speak("Error %s: %s" % (err.api_code, err.reason), True) output.speak("Error %s" % (str(err)), True)
def unfollow(self, user): def unfollow(self, user):
try: try:
id = self.session.twitter.destroy_friendship(screen_name=user ) id = self.session.twitter.destroy_friendship(screen_name=user )
except TweepError as err: except TweepyException as err:
output.speak("Error %s: %s" % (err.api_code, err.reason), True) output.speak("Error %s" % (str(err)), True)
def mute(self, user): def mute(self, user):
try: try:
id = self.session.twitter.create_mute(screen_name=user ) id = self.session.twitter.create_mute(screen_name=user )
except TweepError as err: except TweepyException as err:
output.speak("Error %s: %s" % (err.api_code, err.reason), True) output.speak("Error %s" % (str(err)), True)
def unmute(self, user): def unmute(self, user):
try: try:
id = self.session.twitter.destroy_mute(screen_name=user ) id = self.session.twitter.destroy_mute(screen_name=user )
except TweepError as err: except TweepyException as err:
output.speak("Error %s: %s" % (err.api_code, err.reason), True) output.speak("Error %s" % (str(err)), True)
def report(self, user): def report(self, user):
try: try:
id = self.session.twitter.report_spam(screen_name=user ) id = self.session.twitter.report_spam(screen_name=user )
except TweepError as err: except TweepyException as err:
output.speak("Error %s: %s" % (err.api_code, err.reason), True) output.speak("Error %s" % (str(err)), True)
def block(self, user): def block(self, user):
try: try:
id = self.session.twitter.create_block(screen_name=user ) id = self.session.twitter.create_block(screen_name=user )
except TweepError as err: except TweepyException as err:
output.speak("Error %s: %s" % (err.api_code, err.reason), True) output.speak("Error %s" % (str(err)), True)
def unblock(self, user): def unblock(self, user):
try: try:
id = self.session.twitter.destroy_block(screen_name=user ) id = self.session.twitter.destroy_block(screen_name=user )
except TweepError as err: except TweepyException as err:
output.speak("Error %s: %s" % (err.api_code, err.reason), True) output.speak("Error %s" % (str(err)), True)
def ignore_client(self, user): def ignore_client(self, user):
tweet = self.buffer.get_right_tweet() tweet = self.buffer.get_right_tweet()
if hasattr(tweet, "sender"): if hasattr(tweet, "sender"):
output.speak(_(u"You can't ignore direct messages")) output.speak(_(u"You can't ignore direct messages"))
return return
client = tweet.source client = tweet.source
if client not in self.session.settings["twitter"]["ignored_clients"]: if client not in self.session.settings["twitter"]["ignored_clients"]:
self.session.settings["twitter"]["ignored_clients"].append(client) self.session.settings["twitter"]["ignored_clients"].append(client)
self.session.settings.write() self.session.settings.write()

View File

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
import widgetUtils
from pubsub import pub
from wxUI.dialogs import userAliasDialogs
from extra import autocompletionUsers
class userAliasController(object):
def __init__(self, settings):
super(userAliasController, self).__init__()
self.settings = settings
self.dialog = userAliasDialogs.userAliasEditorDialog()
self.update_aliases_manager()
widgetUtils.connect_event(self.dialog.add, widgetUtils.BUTTON_PRESSED, self.on_add)
widgetUtils.connect_event(self.dialog.edit, widgetUtils.BUTTON_PRESSED, self.on_edit)
widgetUtils.connect_event(self.dialog.remove, widgetUtils.BUTTON_PRESSED, self.on_remove)
pub.subscribe(self.update_aliases_manager, "alias-added")
self.dialog.ShowModal()
def update_aliases_manager(self):
self.dialog.users.Clear()
aliases = [self.settings["user-aliases"].get(k) for k in self.settings["user-aliases"].keys()]
if len(aliases) > 0:
self.dialog.users.InsertItems(aliases, 0)
self.dialog.on_selection_changes()
def on_add(self, *args, **kwargs):
pub.sendMessage("execute-action", action="add_alias")
def on_edit(self, *args, **kwargs):
selection = self.dialog.get_selected_user()
if selection != "":
edited = self.dialog.edit_alias_dialog(_("Edit alias for {}").format(selection))
if edited == None or edited == "":
return
for user_key in self.settings["user-aliases"].keys():
if self.settings["user-aliases"][user_key] == selection:
self.settings["user-aliases"][user_key] = edited
self.settings.write()
self.update_aliases_manager()
break
def on_remove(self, *args, **kwargs):
selection = self.dialog.get_selected_user()
if selection == None or selection == "":
return
should_remove = self.dialog.remove_alias_dialog()
if should_remove:
for user_key in self.settings["user-aliases"].keys():
if self.settings["user-aliases"][user_key] == selection:
self.settings["user-aliases"].pop(user_key)
self.settings.write()
self.update_aliases_manager()
break

View File

@@ -37,146 +37,146 @@ import logging
log = logging.getLogger("extra.AudioUploader.audioUploader") log = logging.getLogger("extra.AudioUploader.audioUploader")
class audioUploader(object): class audioUploader(object):
def __init__(self, configFile, completed_callback): def __init__(self, configFile, completed_callback):
self.config = configFile self.config = configFile
super(audioUploader, self).__init__() super(audioUploader, self).__init__()
self.dialog = wx_ui.audioDialog(services=self.get_available_services()) self.dialog = wx_ui.audioDialog(services=self.get_available_services())
self.file = None self.file = None
self.recorded = False self.recorded = False
self.recording = None self.recording = None
self.playing = None self.playing = None
widgetUtils.connect_event(self.dialog.play, widgetUtils.BUTTON_PRESSED, self.on_play) widgetUtils.connect_event(self.dialog.play, widgetUtils.BUTTON_PRESSED, self.on_play)
widgetUtils.connect_event(self.dialog.pause, widgetUtils.BUTTON_PRESSED, self.on_pause) widgetUtils.connect_event(self.dialog.pause, widgetUtils.BUTTON_PRESSED, self.on_pause)
widgetUtils.connect_event(self.dialog.record, widgetUtils.BUTTON_PRESSED, self.on_record) widgetUtils.connect_event(self.dialog.record, widgetUtils.BUTTON_PRESSED, self.on_record)
widgetUtils.connect_event(self.dialog.attach_exists, widgetUtils.BUTTON_PRESSED, self.on_attach_exists) widgetUtils.connect_event(self.dialog.attach_exists, widgetUtils.BUTTON_PRESSED, self.on_attach_exists)
widgetUtils.connect_event(self.dialog.discard, widgetUtils.BUTTON_PRESSED, self.on_discard) widgetUtils.connect_event(self.dialog.discard, widgetUtils.BUTTON_PRESSED, self.on_discard)
if self.dialog.get_response() == widgetUtils.OK: if self.dialog.get_response() == widgetUtils.OK:
self.postprocess() self.postprocess()
log.debug("Uploading file %s to %s..." % (self.file, self.dialog.get("services"))) log.debug("Uploading file %s to %s..." % (self.file, self.dialog.get("services")))
self.uploaderDialog = wx_transfer_dialogs.UploadDialog(self.file) self.uploaderDialog = wx_transfer_dialogs.UploadDialog(self.file)
output.speak(_(u"Attaching...")) output.speak(_(u"Attaching..."))
if self.dialog.get("services") == "SNDUp": if self.dialog.get("services") == "SNDUp":
base_url = "https://sndup.net/post.php" base_url = "https://sndup.net/post.php"
if len(self.config["sound"]["sndup_api_key"]) > 0: if len(self.config["sound"]["sndup_api_key"]) > 0:
url = base_url + '?apikey=' + self.config['sound']['sndup_api_key'] url = base_url + '?apikey=' + self.config['sound']['sndup_api_key']
else: else:
url = base_url url = base_url
self.uploaderFunction = transfer.Upload(obj=self, field='file', url=url, filename=self.file, completed_callback=completed_callback) self.uploaderFunction = transfer.Upload(obj=self, field='file', url=url, filename=self.file, completed_callback=completed_callback)
pub.subscribe(self.uploaderDialog.update, "uploading") pub.subscribe(self.uploaderDialog.update, "uploading")
self.uploaderDialog.get_response(self.uploaderFunction.perform_threaded) self.uploaderDialog.get_response(self.uploaderFunction.perform_threaded)
def get_available_services(self): def get_available_services(self):
services = [] services = []
services.append("SNDUp") services.append("SNDUp")
return services return services
def on_pause(self, *args, **kwargs): def on_pause(self, *args, **kwargs):
if self.dialog.get("pause") == _(u"Pause"): if self.dialog.get("pause") == _(u"Pause"):
self.recording.pause() self.recording.pause()
self.dialog.set("pause", _(u"&Resume")) self.dialog.set("pause", _(u"&Resume"))
elif self.dialog.get("pause") == _(u"Resume"): elif self.dialog.get("pause") == _(u"Resume"):
self.recording.play() self.recording.play()
self.dialog.set("pause", _(U"&Pause")) self.dialog.set("pause", _(U"&Pause"))
def on_record(self, *args, **kwargs): def on_record(self, *args, **kwargs):
if self.recording != None: if self.recording != None:
self.stop_recording() self.stop_recording()
self.dialog.disable_control("pause") self.dialog.disable_control("pause")
else: else:
self.start_recording() self.start_recording()
self.dialog.enable_control("pause") self.dialog.enable_control("pause")
def start_recording(self): def start_recording(self):
self.dialog.disable_control("attach_exists") self.dialog.disable_control("attach_exists")
self.file = tempfile.mktemp(suffix='.wav') self.file = tempfile.mktemp(suffix='.wav')
self.recording = sound.recording(self.file) self.recording = sound.recording(self.file)
self.recording.play() self.recording.play()
self.dialog.set("record", _(u"&Stop")) self.dialog.set("record", _(u"&Stop"))
output.speak(_(u"Recording")) output.speak(_(u"Recording"))
def stop_recording(self): def stop_recording(self):
self.recording.stop() self.recording.stop()
self.recording.free() self.recording.free()
output.speak(_(u"Stopped")) output.speak(_(u"Stopped"))
self.recorded = True self.recorded = True
self.dialog.set("record", _(u"&Record")) self.dialog.set("record", _(u"&Record"))
self.file_attached() self.file_attached()
def file_attached(self): def file_attached(self):
self.dialog.set("pause", _(u"&Pause")) self.dialog.set("pause", _(u"&Pause"))
self.dialog.disable_control("record") self.dialog.disable_control("record")
self.dialog.enable_control("play") self.dialog.enable_control("play")
self.dialog.enable_control("discard") self.dialog.enable_control("discard")
self.dialog.disable_control("attach_exists") self.dialog.disable_control("attach_exists")
self.dialog.enable_control("attach") self.dialog.enable_control("attach")
self.dialog.play.SetFocus() self.dialog.play.SetFocus()
def on_discard(self, *args, **kwargs): def on_discard(self, *args, **kwargs):
if self.playing: if self.playing:
self._stop() self._stop()
if self.recording != None: if self.recording != None:
self.cleanup() self.cleanup()
self.dialog.disable_control("attach") self.dialog.disable_control("attach")
self.dialog.disable_control("play") self.dialog.disable_control("play")
self.file = None self.file = None
self.dialog.enable_control("record") self.dialog.enable_control("record")
self.dialog.enable_control("attach_exists") self.dialog.enable_control("attach_exists")
self.dialog.record.SetFocus() self.dialog.record.SetFocus()
self.dialog.disable_control("discard") self.dialog.disable_control("discard")
self.recording = None self.recording = None
output.speak(_(u"Discarded")) output.speak(_(u"Discarded"))
def on_play(self, *args, **kwargs): def on_play(self, *args, **kwargs):
if not self.playing: if not self.playing:
call_threaded(self._play) call_threaded(self._play)
else: else:
self._stop() self._stop()
def _play(self): def _play(self):
output.speak(_(u"Playing...")) output.speak(_(u"Playing..."))
# try: # try:
self.playing = sound_lib.stream.FileStream(file=str(self.file), flags=sound_lib.stream.BASS_UNICODE) self.playing = sound_lib.stream.FileStream(file=str(self.file), flags=sound_lib.stream.BASS_UNICODE)
self.playing.play() self.playing.play()
self.dialog.set("play", _(u"&Stop")) self.dialog.set("play", _(u"&Stop"))
try: try:
while self.playing.is_playing: while self.playing.is_playing:
pass pass
self.dialog.set("play", _(u"&Play")) self.dialog.set("play", _(u"&Play"))
self.playing.free() self.playing.free()
self.playing = None self.playing = None
except: except:
pass pass
def _stop(self): def _stop(self):
output.speak(_(u"Stopped")) output.speak(_(u"Stopped"))
self.playing.stop() self.playing.stop()
self.playing.free() self.playing.free()
self.dialog.set("play", _(u"&Play")) self.dialog.set("play", _(u"&Play"))
self.playing = None self.playing = None
def postprocess(self): def postprocess(self):
if self.file.lower().endswith('.wav'): if self.file.lower().endswith('.wav'):
output.speak(_(u"Recoding audio...")) output.speak(_(u"Recoding audio..."))
sound.recode_audio(self.file) sound.recode_audio(self.file)
self.wav_file = self.file self.wav_file = self.file
self.file = '%s.ogg' % self.file[:-4] self.file = '%s.ogg' % self.file[:-4]
def cleanup(self): def cleanup(self):
if self.playing and self.playing.is_playing: if self.playing and self.playing.is_playing:
self.playing.stop() self.playing.stop()
if self.recording != None: if self.recording != None:
if self.recording.is_playing: if self.recording.is_playing:
self.recording.stop() self.recording.stop()
try: try:
self.recording.free() self.recording.free()
except: except:
pass pass
os.remove(self.file) os.remove(self.file)
if hasattr(self, 'wav_file'): if hasattr(self, 'wav_file'):
os.remove(self.wav_file) os.remove(self.wav_file)
def on_attach_exists(self, *args, **kwargs): def on_attach_exists(self, *args, **kwargs):
self.file = self.dialog.get_file() self.file = self.dialog.get_file()
if self.file != False: if self.file != False:
self.file_attached() self.file_attached()

View File

@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import division from __future__ import division
from __future__ import unicode_literals from __future__ import unicode_literals
from builtins import object from builtins import object
from past.utils import old_div from past.utils import old_div
import sys import sys
import threading import threading
import time import time
@@ -15,74 +15,74 @@ from requests_toolbelt.multipart.encoder import MultipartEncoder, MultipartEncod
import requests import requests
import os import os
class Upload(object): class Upload(object):
def __init__(self, field=None, obj=None, url=None, filename=None, follow_location=True, completed_callback=None, verbose=False, *args, **kwargs): def __init__(self, field=None, obj=None, url=None, filename=None, follow_location=True, completed_callback=None, verbose=False, *args, **kwargs):
super(Upload, self).__init__(*args, **kwargs) super(Upload, self).__init__(*args, **kwargs)
self.url=url self.url=url
self.filename=filename self.filename=filename
log.debug("Uploading audio to %s, filename %s" % (url, filename)) log.debug("Uploading audio to %s, filename %s" % (url, filename))
self.start_time = None self.start_time = None
self.completed_callback = completed_callback self.completed_callback = completed_callback
self.background_thread = None self.background_thread = None
self.transfer_rate = 0 self.transfer_rate = 0
self.local_filename=os.path.basename(self.filename) self.local_filename=os.path.basename(self.filename)
if isinstance(self.local_filename, str): if isinstance(self.local_filename, str):
self.local_filename=self.local_filename.encode(sys.getfilesystemencoding()) self.local_filename=self.local_filename.encode(sys.getfilesystemencoding())
self.fin=open(self.filename, 'rb') self.fin=open(self.filename, 'rb')
self.m = MultipartEncoder(fields={field:(self.local_filename, self.fin, "application/octet-stream")}) self.m = MultipartEncoder(fields={field:(self.local_filename, self.fin, "application/octet-stream")})
self.monitor = MultipartEncoderMonitor(self.m, self.progress_callback) self.monitor = MultipartEncoderMonitor(self.m, self.progress_callback)
self.response=None self.response=None
self.obj=obj self.obj=obj
self.follow_location=follow_location self.follow_location=follow_location
#the verbose parameter is deprecated and will be removed soon #the verbose parameter is deprecated and will be removed soon
def elapsed_time(self): def elapsed_time(self):
if not self.start_time: if not self.start_time:
return 0 return 0
return time.time() - self.start_time return time.time() - self.start_time
def progress_callback(self, monitor): def progress_callback(self, monitor):
progress = {} progress = {}
progress["total"] = monitor.len progress["total"] = monitor.len
progress["current"] = monitor.bytes_read progress["current"] = monitor.bytes_read
if progress["current"] == 0: if progress["current"] == 0:
progress["percent"] = 0 progress["percent"] = 0
self.transfer_rate = 0 self.transfer_rate = 0
else: else:
progress["percent"] = int((float(progress["current"]) / progress["total"]) * 100) progress["percent"] = int((float(progress["current"]) / progress["total"]) * 100)
self.transfer_rate = old_div(progress["current"], self.elapsed_time()) self.transfer_rate = old_div(progress["current"], self.elapsed_time())
progress["speed"] = '%s/s' % convert_bytes(self.transfer_rate) progress["speed"] = '%s/s' % convert_bytes(self.transfer_rate)
if self.transfer_rate: if self.transfer_rate:
progress["eta"] = old_div((progress["total"] - progress["current"]), self.transfer_rate) progress["eta"] = old_div((progress["total"] - progress["current"]), self.transfer_rate)
else: else:
progress["eta"] = 0 progress["eta"] = 0
pub.sendMessage("uploading", data=progress) pub.sendMessage("uploading", data=progress)
def perform_transfer(self): def perform_transfer(self):
log.debug("starting upload...") log.debug("starting upload...")
self.start_time = time.time() self.start_time = time.time()
self.response=requests.post(url=self.url, data=self.monitor, headers={"Content-Type":self.m.content_type}, allow_redirects=self.follow_location, stream=True) self.response=requests.post(url=self.url, data=self.monitor, headers={"Content-Type":self.m.content_type}, allow_redirects=self.follow_location, stream=True)
log.debug("Upload finished.") log.debug("Upload finished.")
self.complete_transfer() self.complete_transfer()
def perform_threaded(self, *args, **kwargs): def perform_threaded(self, *args, **kwargs):
self.background_thread = threading.Thread(target=self.perform_transfer) self.background_thread = threading.Thread(target=self.perform_transfer)
self.background_thread.daemon = True self.background_thread.daemon = True
self.background_thread.start() self.background_thread.start()
def complete_transfer(self): def complete_transfer(self):
if callable(self.completed_callback): if callable(self.completed_callback):
self.completed_callback(self.obj) self.completed_callback(self.obj)
if hasattr(self,'fin') and callable(self.fin.close): if hasattr(self,'fin') and callable(self.fin.close):
self.fin.close() self.fin.close()
def get_url(self): def get_url(self):
try: try:
data = self.response.json() data = self.response.json()
except: except:
return _("Error in file upload: {0}").format(self.data.content,) return _("Error in file upload: {0}").format(self.data.content,)
if "url" in data and data["url"] != "0": if "url" in data and data["url"] != "0":
return data["url"] return data["url"]
elif "error" in data and data["error"] != "0": elif "error" in data and data["error"] != "0":
return data["error"] return data["error"]
else: else:
return _("Error in file upload: {0}").format(self.data.content,) return _("Error in file upload: {0}").format(self.data.content,)

View File

@@ -3,42 +3,42 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from builtins import str from builtins import str
def convert_bytes(n): def convert_bytes(n):
K, M, G, T, P = 1 << 10, 1 << 20, 1 << 30, 1 << 40, 1 << 50 K, M, G, T, P = 1 << 10, 1 << 20, 1 << 30, 1 << 40, 1 << 50
if n >= P: if n >= P:
return '%.2fPb' % (float(n) / T) return '%.2fPb' % (float(n) / T)
elif n >= T: elif n >= T:
return '%.2fTb' % (float(n) / T) return '%.2fTb' % (float(n) / T)
elif n >= G: elif n >= G:
return '%.2fGb' % (float(n) / G) return '%.2fGb' % (float(n) / G)
elif n >= M: elif n >= M:
return '%.2fMb' % (float(n) / M) return '%.2fMb' % (float(n) / M)
elif n >= K: elif n >= K:
return '%.2fKb' % (float(n) / K) return '%.2fKb' % (float(n) / K)
else: else:
return '%d' % n return '%d' % n
def seconds_to_string(seconds, precision=0): def seconds_to_string(seconds, precision=0):
day = seconds // 86400 day = seconds // 86400
hour = seconds // 3600 hour = seconds // 3600
min = (seconds // 60) % 60 min = (seconds // 60) % 60
sec = seconds - (hour * 3600) - (min * 60) sec = seconds - (hour * 3600) - (min * 60)
sec_spec = "." + str(precision) + "f" sec_spec = "." + str(precision) + "f"
sec_string = sec.__format__(sec_spec) sec_string = sec.__format__(sec_spec)
string = "" string = ""
if day == 1: if day == 1:
string += _(u"%d day, ") % day string += _(u"%d day, ") % day
elif day >= 2: elif day >= 2:
string += _(u"%d days, ") % day string += _(u"%d days, ") % day
if (hour == 1): if (hour == 1):
string += _(u"%d hour, ") % hour string += _(u"%d hour, ") % hour
elif (hour >= 2): elif (hour >= 2):
string += _("%d hours, ") % hour string += _("%d hours, ") % hour
if (min == 1): if (min == 1):
string += _(u"%d minute, ") % min string += _(u"%d minute, ") % min
elif (min >= 2): elif (min >= 2):
string += _(u"%d minutes, ") % min string += _(u"%d minutes, ") % min
if sec >= 0 and sec <= 2: if sec >= 0 and sec <= 2:
string += _(u"%s second") % sec_string string += _(u"%s second") % sec_string
else: else:
string += _(u"%s seconds") % sec_string string += _(u"%s seconds") % sec_string
return string return string

View File

@@ -7,57 +7,57 @@ import widgetUtils
class UploadDialog(widgetUtils.BaseDialog): class UploadDialog(widgetUtils.BaseDialog):
def __init__(self, filename, *args, **kwargs): def __init__(self, filename, *args, **kwargs):
super(UploadDialog, self).__init__(parent=None, id=wx.ID_ANY, *args, **kwargs) super(UploadDialog, self).__init__(parent=None, id=wx.ID_ANY, *args, **kwargs)
self.pane = wx.Panel(self) self.pane = wx.Panel(self)
self.progress_bar = wx.Gauge(parent=self.pane) self.progress_bar = wx.Gauge(parent=self.pane)
fileBox = wx.BoxSizer(wx.HORIZONTAL) fileBox = wx.BoxSizer(wx.HORIZONTAL)
fileLabel = wx.StaticText(self.pane, -1, _(u"File")) 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 = wx.TextCtrl(self.pane, -1, value=filename, style=wx.TE_READONLY|wx.TE_MULTILINE, size=(200, 100))
self.file.SetFocus() self.file.SetFocus()
fileBox.Add(fileLabel) fileBox.Add(fileLabel)
fileBox.Add(self.file) fileBox.Add(self.file)
currentAmountBox = wx.BoxSizer(wx.HORIZONTAL) currentAmountBox = wx.BoxSizer(wx.HORIZONTAL)
current_amount_label = wx.StaticText(self.pane, -1, _(u"Transferred")) 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) self.current_amount = wx.TextCtrl(self.pane, -1, value='0', style=wx.TE_READONLY|wx.TE_MULTILINE)
currentAmountBox.Add(current_amount_label) currentAmountBox.Add(current_amount_label)
currentAmountBox.Add(self.current_amount) currentAmountBox.Add(self.current_amount)
totalSizeBox = wx.BoxSizer(wx.HORIZONTAL) totalSizeBox = wx.BoxSizer(wx.HORIZONTAL)
total_size_label = wx.StaticText(self.pane, -1, _(u"Total file size")) 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) self.total_size = wx.TextCtrl(self.pane, -1, value='0', style=wx.TE_READONLY|wx.TE_MULTILINE)
totalSizeBox.Add(total_size_label) totalSizeBox.Add(total_size_label)
totalSizeBox.Add(self.total_size) totalSizeBox.Add(self.total_size)
speedBox = wx.BoxSizer(wx.HORIZONTAL) speedBox = wx.BoxSizer(wx.HORIZONTAL)
speedLabel = wx.StaticText(self.pane, -1, _(u"Transfer rate")) 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") self.speed = wx.TextCtrl(self.pane, -1, style=wx.TE_READONLY|wx.TE_MULTILINE, value="0 Kb/s")
speedBox.Add(speedLabel) speedBox.Add(speedLabel)
speedBox.Add(self.speed) speedBox.Add(self.speed)
etaBox = wx.BoxSizer(wx.HORIZONTAL) etaBox = wx.BoxSizer(wx.HORIZONTAL)
etaLabel = wx.StaticText(self.pane, -1, _(u"Time left")) 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)) self.eta = wx.TextCtrl(self.pane, -1, style=wx.TE_READONLY|wx.TE_MULTILINE, value="Unknown", size=(200, 100))
etaBox.Add(etaLabel) etaBox.Add(etaLabel)
etaBox.Add(self.eta) etaBox.Add(self.eta)
self.create_buttons() self.create_buttons()
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(fileBox) sizer.Add(fileBox)
sizer.Add(currentAmountBox) sizer.Add(currentAmountBox)
sizer.Add(totalSizeBox) sizer.Add(totalSizeBox)
sizer.Add(speedBox) sizer.Add(speedBox)
sizer.Add(etaBox) sizer.Add(etaBox)
sizer.Add(self.progress_bar) sizer.Add(self.progress_bar)
self.pane.SetSizerAndFit(sizer) self.pane.SetSizerAndFit(sizer)
def update(self, data): def update(self, data):
wx.CallAfter(self.progress_bar.SetValue, data["percent"]) 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.current_amount.SetValue, '%s (%d%%)' % (convert_bytes(data["current"]), data["percent"]))
wx.CallAfter(self.total_size.SetValue, convert_bytes(data["total"])) wx.CallAfter(self.total_size.SetValue, convert_bytes(data["total"]))
wx.CallAfter(self.speed.SetValue, data["speed"]) wx.CallAfter(self.speed.SetValue, data["speed"])
if data["eta"]: if data["eta"]:
wx.CallAfter(self.eta.SetValue, seconds_to_string(data["eta"])) wx.CallAfter(self.eta.SetValue, seconds_to_string(data["eta"]))
def create_buttons(self): def create_buttons(self):
self.cancel_button = wx.Button(parent=self.pane, id=wx.ID_CANCEL) self.cancel_button = wx.Button(parent=self.pane, id=wx.ID_CANCEL)
def get_response(self, fn): def get_response(self, fn):
wx.CallAfter(fn, 0.01) wx.CallAfter(fn, 0.01)
self.ShowModal() self.ShowModal()

View File

@@ -24,56 +24,56 @@ import logging
log = logging.getLogger("extra.AudioUploader.wx_UI") log = logging.getLogger("extra.AudioUploader.wx_UI")
class audioDialog(widgetUtils.BaseDialog): class audioDialog(widgetUtils.BaseDialog):
def __init__(self, services): def __init__(self, services):
log.debug("creating audio dialog.") log.debug("creating audio dialog.")
super(audioDialog, self).__init__(None, -1, _(u"Attach audio")) super(audioDialog, self).__init__(None, -1, _(u"Attach audio"))
panel = wx.Panel(self) panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
btnSizer = wx.BoxSizer(wx.HORIZONTAL) btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer2 = wx.BoxSizer(wx.HORIZONTAL) btnSizer2 = wx.BoxSizer(wx.HORIZONTAL)
self.play = wx.Button(panel, -1, _(u"&Play")) self.play = wx.Button(panel, -1, _(u"&Play"))
self.play.Disable() self.play.Disable()
self.pause = wx.Button(panel, -1, _(u"&Pause")) self.pause = wx.Button(panel, -1, _(u"&Pause"))
self.pause.Disable() self.pause.Disable()
self.record = wx.Button(panel, -1, _(u"&Record")) self.record = wx.Button(panel, -1, _(u"&Record"))
self.record.SetFocus() self.record.SetFocus()
self.attach_exists = wx.Button(panel, -1, _(u"&Add an existing file")) self.attach_exists = wx.Button(panel, -1, _(u"&Add an existing file"))
self.discard = wx.Button(panel, -1, _(u"&Discard")) self.discard = wx.Button(panel, -1, _(u"&Discard"))
self.discard.Disable() self.discard.Disable()
label = wx.StaticText(panel, -1, _(u"Upload to")) label = wx.StaticText(panel, -1, _(u"Upload to"))
self.services = wx.ComboBox(panel, -1, choices=services, value=services[0], style=wx.CB_READONLY) self.services = wx.ComboBox(panel, -1, choices=services, value=services[0], style=wx.CB_READONLY)
servicesBox = wx.BoxSizer(wx.HORIZONTAL) servicesBox = wx.BoxSizer(wx.HORIZONTAL)
servicesBox.Add(label, 0, wx.ALL, 5) servicesBox.Add(label, 0, wx.ALL, 5)
servicesBox.Add(self.services, 0, wx.ALL, 5) servicesBox.Add(self.services, 0, wx.ALL, 5)
self.attach = wx.Button(panel, wx.ID_OK, _(u"Attach")) self.attach = wx.Button(panel, wx.ID_OK, _(u"Attach"))
self.attach.Disable() self.attach.Disable()
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"&Cancel")) cancel = wx.Button(panel, wx.ID_CANCEL, _(u"&Cancel"))
btnSizer.Add(self.play, 0, wx.ALL, 5) btnSizer.Add(self.play, 0, wx.ALL, 5)
btnSizer.Add(self.pause, 0, wx.ALL, 5) btnSizer.Add(self.pause, 0, wx.ALL, 5)
btnSizer.Add(self.record, 0, wx.ALL, 5) btnSizer.Add(self.record, 0, wx.ALL, 5)
btnSizer2.Add(self.attach_exists, 0, wx.ALL, 5) btnSizer2.Add(self.attach_exists, 0, wx.ALL, 5)
btnSizer2.Add(self.discard, 0, wx.ALL, 5) btnSizer2.Add(self.discard, 0, wx.ALL, 5)
btnSizer2.Add(self.attach, 0, wx.ALL, 5) btnSizer2.Add(self.attach, 0, wx.ALL, 5)
btnSizer2.Add(cancel, 0, wx.ALL, 5) btnSizer2.Add(cancel, 0, wx.ALL, 5)
sizer.Add(servicesBox, 0, wx.ALL, 5) sizer.Add(servicesBox, 0, wx.ALL, 5)
sizer.Add(btnSizer, 0, wx.ALL, 5) sizer.Add(btnSizer, 0, wx.ALL, 5)
sizer.Add(btnSizer2, 0, wx.ALL, 5) sizer.Add(btnSizer2, 0, wx.ALL, 5)
panel.SetSizer(sizer) panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin()) self.SetClientSize(sizer.CalcMin())
def enable_control(self, control): def enable_control(self, control):
log.debug("Enabling control %s" % (control,)) log.debug("Enabling control %s" % (control,))
if hasattr(self, control): if hasattr(self, control):
getattr(self, control).Enable() getattr(self, control).Enable()
def disable_control(self, control): def disable_control(self, control):
log.debug("Disabling control %s" % (control,)) log.debug("Disabling control %s" % (control,))
if hasattr(self, control): if hasattr(self, control):
getattr(self, control).Disable() getattr(self, control).Disable()
def get_file(self): def get_file(self):
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) 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: if openFileDialog.ShowModal() == wx.ID_CANCEL:
return False return False
return openFileDialog.GetPath() return openFileDialog.GetPath()

View File

@@ -4,23 +4,23 @@ from gi.repository import Gtk
import widgetUtils import widgetUtils
class soundsTutorialDialog(Gtk.Dialog): class soundsTutorialDialog(Gtk.Dialog):
def __init__(self, actions): def __init__(self, actions):
super(soundsTutorialDialog, self).__init__("Sounds tutorial", None, 0, (Gtk.STOCK_CANCEL, widgetUtils.CANCEL)) super(soundsTutorialDialog, self).__init__("Sounds tutorial", None, 0, (Gtk.STOCK_CANCEL, widgetUtils.CANCEL))
box = self.get_content_area() box = self.get_content_area()
label = Gtk.Label("Press enter for listen the sound") label = Gtk.Label("Press enter for listen the sound")
self.list = widgetUtils.list("Action") self.list = widgetUtils.list("Action")
self.populate_actions(actions) self.populate_actions(actions)
lBox = Gtk.Box(spacing=6) lBox = Gtk.Box(spacing=6)
lBox.add(label) lBox.add(label)
lBox.add(self.list.list) lBox.add(self.list.list)
box.add(lBox) box.add(lBox)
self.play = Gtk.Button("Play") self.play = Gtk.Button("Play")
box.add(self.play) box.add(self.play)
self.show_all() self.show_all()
def populate_actions(self, actions): def populate_actions(self, actions):
for i in actions: for i in actions:
self.list.insert_item(i) self.list.insert_item(i)
def get_selected(self): def get_selected(self):
return self.list.get_selected() return self.list.get_selected()

View File

@@ -1,4 +1,4 @@
from __future__ import unicode_literals from __future__ import unicode_literals
#Reverse sort, by Bill Dengler <codeofdusk@gmail.com> for use in TWBlue http://twblue.es #Reverse sort, by Bill Dengler <codeofdusk@gmail.com> for use in TWBlue http://twblue.es
def invert_tuples(t): def invert_tuples(t):
"Invert a list of tuples, so that the 0th element becomes the -1th, and the -1th becomes the 0th." "Invert a list of tuples, so that the 0th element becomes the -1th, and the -1th becomes the 0th."
@@ -9,4 +9,4 @@ def invert_tuples(t):
def reverse_sort(t): def reverse_sort(t):
"Sorts a list of tuples/lists by their last elements, not their first." "Sorts a list of tuples/lists by their last elements, not their first."
return invert_tuples(sorted(invert_tuples(t))) return invert_tuples(sorted(invert_tuples(t)))

View File

@@ -10,28 +10,28 @@ import logging
log = logging.getLogger("extra.SoundsTutorial.soundsTutorial") log = logging.getLogger("extra.SoundsTutorial.soundsTutorial")
from . import soundsTutorial_constants from . import soundsTutorial_constants
if platform.system() == "Windows": if platform.system() == "Windows":
from . import wx_ui as UI from . import wx_ui as UI
elif platform.system() == "Linux": elif platform.system() == "Linux":
from . import gtk_ui as UI from . import gtk_ui as UI
class soundsTutorial(object): class soundsTutorial(object):
def __init__(self, sessionObject): def __init__(self, sessionObject):
log.debug("Creating sounds tutorial object...") log.debug("Creating sounds tutorial object...")
super(soundsTutorial, self).__init__() super(soundsTutorial, self).__init__()
self.session = sessionObject self.session = sessionObject
self.actions = [] self.actions = []
log.debug("Loading actions for sounds tutorial...") log.debug("Loading actions for sounds tutorial...")
[self.actions.append(i[1]) for i in soundsTutorial_constants.actions] [self.actions.append(i[1]) for i in soundsTutorial_constants.actions]
self.files = [] self.files = []
log.debug("Searching sound files...") log.debug("Searching sound files...")
[self.files.append(i[0]) for i in soundsTutorial_constants.actions] [self.files.append(i[0]) for i in soundsTutorial_constants.actions]
log.debug("Creating dialog...") log.debug("Creating dialog...")
self.dialog = UI.soundsTutorialDialog(self.actions) self.dialog = UI.soundsTutorialDialog(self.actions)
widgetUtils.connect_event(self.dialog.play, widgetUtils.BUTTON_PRESSED, self.on_play) widgetUtils.connect_event(self.dialog.play, widgetUtils.BUTTON_PRESSED, self.on_play)
self.dialog.get_response() self.dialog.get_response()
def on_play(self, *args, **kwargs): def on_play(self, *args, **kwargs):
try: try:
self.session.sound.play(self.files[self.dialog.get_selection()]+".ogg") self.session.sound.play(self.files[self.dialog.get_selection()]+".ogg")
except: except:
log.exception("Error playing the %s sound" % (self.files[self.dialog.items.GetSelection()],)) log.exception("Error playing the %s sound" % (self.files[self.dialog.items.GetSelection()],))

View File

@@ -5,27 +5,27 @@ from __future__ import unicode_literals
from . import reverse_sort from . import reverse_sort
import application import application
actions = reverse_sort.reverse_sort([ ("audio", _(u"Audio tweet.")), actions = reverse_sort.reverse_sort([ ("audio", _(u"Audio tweet.")),
("create_timeline", _(u"User timeline buffer created.")), ("create_timeline", _(u"User timeline buffer created.")),
("delete_timeline", _(u"Buffer destroied.")), ("delete_timeline", _(u"Buffer destroied.")),
("dm_received", _(u"Direct message received.")), ("dm_received", _(u"Direct message received.")),
("dm_sent", _(u"Direct message sent.")), ("dm_sent", _(u"Direct message sent.")),
("error", _(u"Error.")), ("error", _(u"Error.")),
("favourite", _(u"Tweet liked.")), ("favourite", _(u"Tweet liked.")),
("favourites_timeline_updated", _(u"Likes buffer updated.")), ("favourites_timeline_updated", _(u"Likes buffer updated.")),
("geo", _(u"Geotweet.")), ("geo", _(u"Geotweet.")),
("image", _("Tweet contains one or more images")), ("image", _("Tweet contains one or more images")),
("limit", _(u"Boundary reached.")), ("limit", _(u"Boundary reached.")),
("list_tweet", _(u"List updated.")), ("list_tweet", _(u"List updated.")),
("max_length", _(u"Too many characters.")), ("max_length", _(u"Too many characters.")),
("mention_received", _(u"Mention received.")), ("mention_received", _(u"Mention received.")),
("new_event", _(u"New event.")), ("new_event", _(u"New event.")),
("ready", _(u"{0} is ready.").format(application.name,)), ("ready", _(u"{0} is ready.").format(application.name,)),
("reply_send", _(u"Mention sent.")), ("reply_send", _(u"Mention sent.")),
("retweet_send", _(u"Tweet retweeted.")), ("retweet_send", _(u"Tweet retweeted.")),
("search_updated", _(u"Search buffer updated.")), ("search_updated", _(u"Search buffer updated.")),
("tweet_received", _(u"Tweet received.")), ("tweet_received", _(u"Tweet received.")),
("tweet_send", _(u"Tweet sent.")), ("tweet_send", _(u"Tweet sent.")),
("trends_updated", _(u"Trending topics buffer updated.")), ("trends_updated", _(u"Trending topics buffer updated.")),
("tweet_timeline", _(u"New tweet in user timeline buffer.")), ("tweet_timeline", _(u"New tweet in user timeline buffer.")),
("update_followers", _(u"New follower.")), ("update_followers", _(u"New follower.")),
("volume_changed", _(u"Volume changed."))]) ("volume_changed", _(u"Volume changed."))])

View File

@@ -4,27 +4,27 @@ import wx
import widgetUtils import widgetUtils
class soundsTutorialDialog(widgetUtils.BaseDialog): class soundsTutorialDialog(widgetUtils.BaseDialog):
def __init__(self, actions): def __init__(self, actions):
super(soundsTutorialDialog, self).__init__(None, -1) super(soundsTutorialDialog, self).__init__(None, -1)
self.SetTitle(_(u"Sounds tutorial")) self.SetTitle(_(u"Sounds tutorial"))
panel = wx.Panel(self) panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
label = wx.StaticText(panel, -1, _(u"Press enter to listen to the sound for the selected event")) label = wx.StaticText(panel, -1, _(u"Press enter to listen to the sound for the selected event"))
self.items = wx.ListBox(panel, 1, choices=actions, style=wx.LB_SINGLE) self.items = wx.ListBox(panel, 1, choices=actions, style=wx.LB_SINGLE)
self.items.SetSelection(0) self.items.SetSelection(0)
listBox = wx.BoxSizer(wx.HORIZONTAL) listBox = wx.BoxSizer(wx.HORIZONTAL)
listBox.Add(label) listBox.Add(label)
listBox.Add(self.items) listBox.Add(self.items)
self.play = wx.Button(panel, 1, (u"Play")) self.play = wx.Button(panel, 1, (u"Play"))
self.play.SetDefault() self.play.SetDefault()
close = wx.Button(panel, wx.ID_CANCEL) close = wx.Button(panel, wx.ID_CANCEL)
btnBox = wx.BoxSizer(wx.HORIZONTAL) btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(self.play) btnBox.Add(self.play)
btnBox.Add(close) btnBox.Add(close)
sizer.Add(listBox) sizer.Add(listBox)
sizer.Add(btnBox) sizer.Add(btnBox)
panel.SetSizer(sizer) panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin()) self.SetClientSize(sizer.CalcMin())
def get_selection(self): def get_selection(self):
return self.items.GetSelection() return self.items.GetSelection()

View File

@@ -3,4 +3,4 @@ from __future__ import unicode_literals
from . import spellchecker from . import spellchecker
import platform import platform
if platform.system() == "Windows": if platform.system() == "Windows":
from .wx_ui import * from .wx_ui import *

View File

@@ -19,69 +19,69 @@ from enchant import tokenize
log = logging.getLogger("extra.SpellChecker.spellChecker") log = logging.getLogger("extra.SpellChecker.spellChecker")
class spellChecker(object): class spellChecker(object):
def __init__(self, text, dictionary): def __init__(self, text, dictionary):
super(spellChecker, self).__init__() super(spellChecker, self).__init__()
# Set Dictionary path if not set in a previous call to this method. # Set Dictionary path if not set in a previous call to this method.
# Dictionary path will be located in user config, see https://github.com/manuelcortez/twblue/issues/208 # Dictionary path will be located in user config, see https://github.com/manuelcortez/twblue/issues/208
# dict_path = enchant.get_param("enchant.myspell.dictionary.path") # dict_path = enchant.get_param("enchant.myspell.dictionary.path")
# if dict_path == None: # if dict_path == None:
# enchant.set_param("enchant.myspell.dictionary.path", os.path.join(paths.config_path(), "dicts")) # enchant.set_param("enchant.myspell.dictionary.path", os.path.join(paths.config_path(), "dicts"))
# log.debug("Dictionary path set to %s" % (os.path.join(paths.config_path(), "dicts"),)) # log.debug("Dictionary path set to %s" % (os.path.join(paths.config_path(), "dicts"),))
log.debug("Creating the SpellChecker object. Dictionary: %s" % (dictionary,)) log.debug("Creating the SpellChecker object. Dictionary: %s" % (dictionary,))
self.active = True self.active = True
try: try:
if config.app["app-settings"]["language"] == "system": if config.app["app-settings"]["language"] == "system":
log.debug("Using the system language") log.debug("Using the system language")
self.dict = enchant.DictWithPWL(languageHandler.curLang[:2], os.path.join(paths.config_path(), "wordlist.dict")) self.dict = enchant.DictWithPWL(languageHandler.curLang[:2], os.path.join(paths.config_path(), "wordlist.dict"))
else: else:
log.debug("Using language: %s" % (languageHandler.getLanguage(),)) log.debug("Using language: %s" % (languageHandler.getLanguage(),))
self.dict = enchant.DictWithPWL(languageHandler.getLanguage()[:2], os.path.join(paths.config_path(), "wordlist.dict")) self.dict = enchant.DictWithPWL(languageHandler.getLanguage()[:2], os.path.join(paths.config_path(), "wordlist.dict"))
except DictNotFoundError: except DictNotFoundError:
log.exception("Dictionary for language %s not found." % (dictionary,)) log.exception("Dictionary for language %s not found." % (dictionary,))
wx_ui.dict_not_found_error() wx_ui.dict_not_found_error()
self.active = False self.active = False
self.checker = SpellChecker(self.dict, filters=[twitterFilter.TwitterFilter, tokenize.EmailFilter, tokenize.URLFilter]) self.checker = SpellChecker(self.dict, filters=[twitterFilter.TwitterFilter, tokenize.EmailFilter, tokenize.URLFilter])
self.checker.set_text(text) self.checker.set_text(text)
if self.active == True: if self.active == True:
log.debug("Creating dialog...") log.debug("Creating dialog...")
self.dialog = wx_ui.spellCheckerDialog() self.dialog = wx_ui.spellCheckerDialog()
widgetUtils.connect_event(self.dialog.ignore, widgetUtils.BUTTON_PRESSED, self.ignore) widgetUtils.connect_event(self.dialog.ignore, widgetUtils.BUTTON_PRESSED, self.ignore)
widgetUtils.connect_event(self.dialog.ignoreAll, widgetUtils.BUTTON_PRESSED, self.ignoreAll) widgetUtils.connect_event(self.dialog.ignoreAll, widgetUtils.BUTTON_PRESSED, self.ignoreAll)
widgetUtils.connect_event(self.dialog.replace, widgetUtils.BUTTON_PRESSED, self.replace) widgetUtils.connect_event(self.dialog.replace, widgetUtils.BUTTON_PRESSED, self.replace)
widgetUtils.connect_event(self.dialog.replaceAll, widgetUtils.BUTTON_PRESSED, self.replaceAll) widgetUtils.connect_event(self.dialog.replaceAll, widgetUtils.BUTTON_PRESSED, self.replaceAll)
widgetUtils.connect_event(self.dialog.add, widgetUtils.BUTTON_PRESSED, self.add) widgetUtils.connect_event(self.dialog.add, widgetUtils.BUTTON_PRESSED, self.add)
self.check() self.check()
self.dialog.get_response() self.dialog.get_response()
self.fixed_text = self.checker.get_text() self.fixed_text = self.checker.get_text()
def check(self): def check(self):
try: try:
next(self.checker) next(self.checker)
textToSay = _(u"Misspelled word: %s") % (self.checker.word,) textToSay = _(u"Misspelled word: %s") % (self.checker.word,)
context = u"... %s %s %s" % (self.checker.leading_context(10), self.checker.word, self.checker.trailing_context(10)) context = u"... %s %s %s" % (self.checker.leading_context(10), self.checker.word, self.checker.trailing_context(10))
self.dialog.set_title(textToSay) self.dialog.set_title(textToSay)
output.speak(textToSay) output.speak(textToSay)
self.dialog.set_word_and_suggestions(word=self.checker.word, context=context, suggestions=self.checker.suggest()) self.dialog.set_word_and_suggestions(word=self.checker.word, context=context, suggestions=self.checker.suggest())
except StopIteration: except StopIteration:
log.debug("Process finished.") log.debug("Process finished.")
wx_ui.finished() wx_ui.finished()
self.dialog.Destroy() self.dialog.Destroy()
def ignore(self, ev): def ignore(self, ev):
self.check() self.check()
def ignoreAll(self, ev): def ignoreAll(self, ev):
self.checker.ignore_always(word=self.checker.word) self.checker.ignore_always(word=self.checker.word)
self.check() self.check()
def replace(self, ev): def replace(self, ev):
self.checker.replace(self.dialog.get_selected_suggestion()) self.checker.replace(self.dialog.get_selected_suggestion())
self.check() self.check()
def replaceAll(self, ev): def replaceAll(self, ev):
self.checker.replace_always(self.dialog.get_selected_suggestion()) self.checker.replace_always(self.dialog.get_selected_suggestion())
self.check() self.check()
def add(self, ev): def add(self, ev):
self.checker.add() self.checker.add()
self.check() self.check()

View File

@@ -21,63 +21,63 @@ import wx
import application import application
class spellCheckerDialog(wx.Dialog): class spellCheckerDialog(wx.Dialog):
def __init__(self): def __init__(self):
super(spellCheckerDialog, self).__init__(None, 1) super(spellCheckerDialog, self).__init__(None, 1)
panel = wx.Panel(self) panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
word = wx.StaticText(panel, -1, _(u"Misspelled word")) word = wx.StaticText(panel, -1, _(u"Misspelled word"))
self.word = wx.TextCtrl(panel, -1) self.word = wx.TextCtrl(panel, -1)
wordBox = wx.BoxSizer(wx.HORIZONTAL) wordBox = wx.BoxSizer(wx.HORIZONTAL)
wordBox.Add(word, 0, wx.ALL, 5) wordBox.Add(word, 0, wx.ALL, 5)
wordBox.Add(self.word, 0, wx.ALL, 5) wordBox.Add(self.word, 0, wx.ALL, 5)
context = wx.StaticText(panel, -1, _(u"Context")) context = wx.StaticText(panel, -1, _(u"Context"))
self.context = wx.TextCtrl(panel, -1) self.context = wx.TextCtrl(panel, -1)
contextBox = wx.BoxSizer(wx.HORIZONTAL) contextBox = wx.BoxSizer(wx.HORIZONTAL)
contextBox.Add(context, 0, wx.ALL, 5) contextBox.Add(context, 0, wx.ALL, 5)
contextBox.Add(self.context, 0, wx.ALL, 5) contextBox.Add(self.context, 0, wx.ALL, 5)
suggest = wx.StaticText(panel, -1, _(u"Suggestions")) suggest = wx.StaticText(panel, -1, _(u"Suggestions"))
self.suggestions = wx.ListBox(panel, -1, choices=[], style=wx.LB_SINGLE) self.suggestions = wx.ListBox(panel, -1, choices=[], style=wx.LB_SINGLE)
suggestionsBox = wx.BoxSizer(wx.HORIZONTAL) suggestionsBox = wx.BoxSizer(wx.HORIZONTAL)
suggestionsBox.Add(suggest, 0, wx.ALL, 5) suggestionsBox.Add(suggest, 0, wx.ALL, 5)
suggestionsBox.Add(self.suggestions, 0, wx.ALL, 5) suggestionsBox.Add(self.suggestions, 0, wx.ALL, 5)
self.ignore = wx.Button(panel, -1, _(u"&Ignore")) self.ignore = wx.Button(panel, -1, _(u"&Ignore"))
self.ignoreAll = wx.Button(panel, -1, _(u"I&gnore all")) self.ignoreAll = wx.Button(panel, -1, _(u"I&gnore all"))
self.replace = wx.Button(panel, -1, _(u"&Replace")) self.replace = wx.Button(panel, -1, _(u"&Replace"))
self.replaceAll = wx.Button(panel, -1, _(u"R&eplace all")) self.replaceAll = wx.Button(panel, -1, _(u"R&eplace all"))
self.add = wx.Button(panel, -1, _(u"&Add to personal dictionary")) self.add = wx.Button(panel, -1, _(u"&Add to personal dictionary"))
close = wx.Button(panel, wx.ID_CANCEL) close = wx.Button(panel, wx.ID_CANCEL)
btnBox = wx.BoxSizer(wx.HORIZONTAL) btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(self.ignore, 0, wx.ALL, 5) btnBox.Add(self.ignore, 0, wx.ALL, 5)
btnBox.Add(self.ignoreAll, 0, wx.ALL, 5) btnBox.Add(self.ignoreAll, 0, wx.ALL, 5)
btnBox.Add(self.replace, 0, wx.ALL, 5) btnBox.Add(self.replace, 0, wx.ALL, 5)
btnBox.Add(self.replaceAll, 0, wx.ALL, 5) btnBox.Add(self.replaceAll, 0, wx.ALL, 5)
btnBox.Add(self.add, 0, wx.ALL, 5) btnBox.Add(self.add, 0, wx.ALL, 5)
btnBox.Add(close, 0, wx.ALL, 5) btnBox.Add(close, 0, wx.ALL, 5)
sizer.Add(wordBox, 0, wx.ALL, 5) sizer.Add(wordBox, 0, wx.ALL, 5)
sizer.Add(contextBox, 0, wx.ALL, 5) sizer.Add(contextBox, 0, wx.ALL, 5)
sizer.Add(suggestionsBox, 0, wx.ALL, 5) sizer.Add(suggestionsBox, 0, wx.ALL, 5)
sizer.Add(btnBox, 0, wx.ALL, 5) sizer.Add(btnBox, 0, wx.ALL, 5)
panel.SetSizer(sizer) panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin()) self.SetClientSize(sizer.CalcMin())
def get_response(self): def get_response(self):
return self.ShowModal() return self.ShowModal()
def set_title(self, title): def set_title(self, title):
return self.SetTitle(title) return self.SetTitle(title)
def set_word_and_suggestions(self, word, context, suggestions): def set_word_and_suggestions(self, word, context, suggestions):
self.word.SetValue(word) self.word.SetValue(word)
self.context.ChangeValue(context) self.context.ChangeValue(context)
self.suggestions.Set(suggestions) self.suggestions.Set(suggestions)
self.suggestions.SetFocus() self.suggestions.SetFocus()
def get_selected_suggestion(self): def get_selected_suggestion(self):
return self.suggestions.GetStringSelection() return self.suggestions.GetStringSelection()
def dict_not_found_error(): def dict_not_found_error():
wx.MessageDialog(None, _(u"An error has occurred. There are no dictionaries available for the selected language in {0}").format(application.name,), _(u"Error"), wx.ICON_ERROR).ShowModal() wx.MessageDialog(None, _(u"An error has occurred. There are no dictionaries available for the selected language in {0}").format(application.name,), _(u"Error"), wx.ICON_ERROR).ShowModal()
def finished(): def finished():
wx.MessageDialog(None, _(u"Spell check complete."), application.name, style=wx.OK).ShowModal() wx.MessageDialog(None, _(u"Spell check complete."), application.name, style=wx.OK).ShowModal()

View File

@@ -2,4 +2,4 @@
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import completion, settings from . import completion, settings

View File

@@ -4,44 +4,44 @@ from . import storage
from . import wx_menu from . import wx_menu
class autocompletionUsers(object): class autocompletionUsers(object):
def __init__(self, window, session_id): def __init__(self, window, session_id):
super(autocompletionUsers, self).__init__() super(autocompletionUsers, self).__init__()
self.window = window self.window = window
self.db = storage.storage(session_id) self.db = storage.storage(session_id)
def show_menu(self, mode="tweet"): def show_menu(self, mode="tweet"):
position = self.window.get_position() position = self.window.get_position()
if mode == "tweet": if mode == "tweet":
text = self.window.get_text() text = self.window.get_text()
text = text[:position] text = text[:position]
try: try:
pattern = text.split()[-1] pattern = text.split()[-1]
except IndexError: except IndexError:
output.speak(_(u"You have to start writing")) output.speak(_(u"You have to start writing"))
return return
if pattern.startswith("@") == True: if pattern.startswith("@") == True:
menu = wx_menu.menu(self.window.text, pattern[1:], mode=mode) menu = wx_menu.menu(self.window.text, pattern[1:], mode=mode)
users = self.db.get_users(pattern[1:]) users = self.db.get_users(pattern[1:])
if len(users) > 0: if len(users) > 0:
menu.append_options(users) menu.append_options(users)
self.window.popup_menu(menu) self.window.popup_menu(menu)
menu.destroy() menu.destroy()
else: else:
output.speak(_(u"There are no results in your users database")) output.speak(_(u"There are no results in your users database"))
else: else:
output.speak(_(u"Autocompletion only works for users.")) output.speak(_(u"Autocompletion only works for users."))
elif mode == "dm": elif mode == "dm":
text = self.window.get_user() text = self.window.get_user()
try: try:
pattern = text.split()[-1] pattern = text.split()[-1]
except IndexError: except IndexError:
output.speak(_(u"You have to start writing")) output.speak(_(u"You have to start writing"))
return return
menu = wx_menu.menu(self.window.cb, pattern, mode=mode) menu = wx_menu.menu(self.window.cb, pattern, mode=mode)
users = self.db.get_users(pattern) users = self.db.get_users(pattern)
if len(users) > 0: if len(users) > 0:
menu.append_options(users) menu.append_options(users)
self.window.popup_menu(menu) self.window.popup_menu(menu)
menu.destroy() menu.destroy()
else: else:
output.speak(_(u"There are no results in your users database")) output.speak(_(u"There are no results in your users database"))

View File

@@ -4,39 +4,39 @@ from . import storage, wx_manage
from wxUI import commonMessageDialogs from wxUI import commonMessageDialogs
class autocompletionManage(object): class autocompletionManage(object):
def __init__(self, session): def __init__(self, session):
super(autocompletionManage, self).__init__() super(autocompletionManage, self).__init__()
self.session = session self.session = session
self.dialog = wx_manage.autocompletionManageDialog() self.dialog = wx_manage.autocompletionManageDialog()
self.database = storage.storage(self.session.session_id) self.database = storage.storage(self.session.session_id)
self.users = self.database.get_all_users() self.users = self.database.get_all_users()
self.dialog.put_users(self.users) self.dialog.put_users(self.users)
widgetUtils.connect_event(self.dialog.add, widgetUtils.BUTTON_PRESSED, self.add_user) widgetUtils.connect_event(self.dialog.add, widgetUtils.BUTTON_PRESSED, self.add_user)
widgetUtils.connect_event(self.dialog.remove, widgetUtils.BUTTON_PRESSED, self.remove_user) widgetUtils.connect_event(self.dialog.remove, widgetUtils.BUTTON_PRESSED, self.remove_user)
self.dialog.get_response() self.dialog.get_response()
def update_list(self): def update_list(self):
item = self.dialog.users.get_selected() item = self.dialog.users.get_selected()
self.dialog.users.clear() self.dialog.users.clear()
self.users = self.database.get_all_users() self.users = self.database.get_all_users()
self.dialog.put_users(self.users) self.dialog.put_users(self.users)
self.dialog.users.select_item(item) self.dialog.users.select_item(item)
def add_user(self, *args, **kwargs): def add_user(self, *args, **kwargs):
usr = self.dialog.get_user() usr = self.dialog.get_user()
if usr == False: if usr == False:
return return
try: try:
data = self.session.twitter.twitter.get_user(screen_name=usr) data = self.session.twitter.twitter.get_user(screen_name=usr)
except: except:
self.dialog.show_invalid_user_error() self.dialog.show_invalid_user_error()
return return
self.database.set_user(data.screen_name, data.name, 0) self.database.set_user(data.screen_name, data.name, 0)
self.update_list() self.update_list()
def remove_user(self, ev): def remove_user(self, ev):
if commonMessageDialogs.delete_user_from_db() == widgetUtils.YES: if commonMessageDialogs.delete_user_from_db() == widgetUtils.YES:
item = self.dialog.users.get_selected() item = self.dialog.users.get_selected()
user = self.users[item] user = self.users[item]
self.database.remove_user(user[0]) self.database.remove_user(user[0])
self.update_list() self.update_list()

View File

@@ -7,53 +7,53 @@ from . import storage
from mysc.thread_utils import call_threaded from mysc.thread_utils import call_threaded
class autocompletionSettings(object): class autocompletionSettings(object):
def __init__(self, config, buffer, window): def __init__(self, config, buffer, window):
super(autocompletionSettings, self).__init__() super(autocompletionSettings, self).__init__()
self.config = config self.config = config
self.buffer = buffer self.buffer = buffer
self.window = window self.window = window
self.dialog = wx_settings.autocompletionSettingsDialog() self.dialog = wx_settings.autocompletionSettingsDialog()
self.dialog.set("friends_buffer", self.config["mysc"]["save_friends_in_autocompletion_db"]) self.dialog.set("friends_buffer", self.config["mysc"]["save_friends_in_autocompletion_db"])
self.dialog.set("followers_buffer", self.config["mysc"]["save_followers_in_autocompletion_db"]) self.dialog.set("followers_buffer", self.config["mysc"]["save_followers_in_autocompletion_db"])
widgetUtils.connect_event(self.dialog.viewList, widgetUtils.BUTTON_PRESSED, self.view_list) widgetUtils.connect_event(self.dialog.viewList, widgetUtils.BUTTON_PRESSED, self.view_list)
if self.dialog.get_response() == widgetUtils.OK: if self.dialog.get_response() == widgetUtils.OK:
call_threaded(self.add_users_to_database) call_threaded(self.add_users_to_database)
def add_users_to_database(self): def add_users_to_database(self):
self.config["mysc"]["save_friends_in_autocompletion_db"] = self.dialog.get("friends_buffer") self.config["mysc"]["save_friends_in_autocompletion_db"] = self.dialog.get("friends_buffer")
self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers_buffer") self.config["mysc"]["save_followers_in_autocompletion_db"] = self.dialog.get("followers_buffer")
output.speak(_(u"Updating database... You can close this window now. A message will tell you when the process finishes.")) output.speak(_(u"Updating database... You can close this window now. A message will tell you when the process finishes."))
database = storage.storage(self.buffer.session.session_id) database = storage.storage(self.buffer.session.session_id)
if self.dialog.get("followers_buffer") == True: if self.dialog.get("followers_buffer") == True:
buffer = self.window.search_buffer("followers", self.config["twitter"]["user_name"]) buffer = self.window.search_buffer("followers", self.config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]: for i in buffer.session.db[buffer.name]:
database.set_user(i.screen_name, i.name, 1) database.set_user(i.screen_name, i.name, 1)
else: else:
database.remove_by_buffer(1) database.remove_by_buffer(1)
if self.dialog.get("friends_buffer") == True: if self.dialog.get("friends_buffer") == True:
buffer = self.window.search_buffer("friends", self.config["twitter"]["user_name"]) buffer = self.window.search_buffer("friends", self.config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]: for i in buffer.session.db[buffer.name]:
database.set_user(i.screen_name, i.name, 2) database.set_user(i.screen_name, i.name, 2)
else: else:
database.remove_by_buffer(2) database.remove_by_buffer(2)
wx_settings.show_success_dialog() wx_settings.show_success_dialog()
self.dialog.destroy() self.dialog.destroy()
def view_list(self, ev): def view_list(self, ev):
q = manage.autocompletionManage(self.buffer.session) q = manage.autocompletionManage(self.buffer.session)
def execute_at_startup(window, buffer, config): def execute_at_startup(window, buffer, config):
database = storage.storage(buffer.session.session_id) database = storage.storage(buffer.session.session_id)
if config["mysc"]["save_followers_in_autocompletion_db"] == True and config["other_buffers"]["show_followers"] == True: if config["mysc"]["save_followers_in_autocompletion_db"] == True and config["other_buffers"]["show_followers"] == True:
buffer = window.search_buffer("followers", config["twitter"]["user_name"]) buffer = window.search_buffer("followers", config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]: for i in buffer.session.db[buffer.name]:
database.set_user(i.screen_name, i.name, 1) database.set_user(i.screen_name, i.name, 1)
else: else:
database.remove_by_buffer(1) database.remove_by_buffer(1)
if config["mysc"]["save_friends_in_autocompletion_db"] == True and config["other_buffers"]["show_friends"] == True: if config["mysc"]["save_friends_in_autocompletion_db"] == True and config["other_buffers"]["show_friends"] == True:
buffer = window.search_buffer("friends", config["twitter"]["user_name"]) buffer = window.search_buffer("friends", config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]: for i in buffer.session.db[buffer.name]:
database.set_user(i.screen_name, i.name, 2) database.set_user(i.screen_name, i.name, 2)
else: else:
database.remove_by_buffer(2) database.remove_by_buffer(2)

View File

@@ -2,51 +2,51 @@
import os, sqlite3, paths import os, sqlite3, paths
class storage(object): class storage(object):
def __init__(self, session_id): def __init__(self, session_id):
self.connection = sqlite3.connect(os.path.join(paths.config_path(), "%s/autocompletionUsers.dat" % (session_id))) self.connection = sqlite3.connect(os.path.join(paths.config_path(), "%s/autocompletionUsers.dat" % (session_id)))
self.cursor = self.connection.cursor() self.cursor = self.connection.cursor()
if self.table_exist("users") == False: if self.table_exist("users") == False:
self.create_table() self.create_table()
def table_exist(self, table): def table_exist(self, table):
ask = self.cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='%s'" % (table)) ask = self.cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='%s'" % (table))
answer = ask.fetchone() answer = ask.fetchone()
if answer == None: if answer == None:
return False return False
else: else:
return True return True
def get_all_users(self): def get_all_users(self):
self.cursor.execute("""select * from users""") self.cursor.execute("""select * from users""")
return self.cursor.fetchall() return self.cursor.fetchall()
def get_users(self, term): def get_users(self, term):
self.cursor.execute("""SELECT * FROM users WHERE user LIKE ?""", ('{}%'.format(term),)) self.cursor.execute("""SELECT * FROM users WHERE UPPER(user) LIKE :term OR UPPER(name) LIKE :term""", {"term": "%{}%".format(term.upper())})
return self.cursor.fetchall() return self.cursor.fetchall()
def set_user(self, screen_name, user_name, from_a_buffer): def set_user(self, screen_name, user_name, from_a_buffer):
self.cursor.execute("""insert or ignore into users values(?, ?, ?)""", (screen_name, user_name, from_a_buffer)) self.cursor.execute("""insert or ignore into users values(?, ?, ?)""", (screen_name, user_name, from_a_buffer))
self.connection.commit() self.connection.commit()
def remove_user(self, user): def remove_user(self, user):
self.cursor.execute("""DELETE FROM users WHERE user = ?""", (user,)) self.cursor.execute("""DELETE FROM users WHERE user = ?""", (user,))
self.connection.commit() self.connection.commit()
return self.cursor.fetchone() return self.cursor.fetchone()
def remove_by_buffer(self, bufferType): def remove_by_buffer(self, bufferType):
""" Removes all users saved on a buffer. BufferType is 0 for no buffer, 1 for friends and 2 for followers""" """ Removes all users saved on a buffer. BufferType is 0 for no buffer, 1 for friends and 2 for followers"""
self.cursor.execute("""DELETE FROM users WHERE from_a_buffer = ?""", (bufferType,)) self.cursor.execute("""DELETE FROM users WHERE from_a_buffer = ?""", (bufferType,))
self.connection.commit() self.connection.commit()
return self.cursor.fetchone() return self.cursor.fetchone()
def create_table(self): def create_table(self):
self.cursor.execute(""" self.cursor.execute("""
create table users( create table users(
user TEXT UNIQUE, user TEXT UNIQUE,
name TEXT, name TEXT,
from_a_buffer INTEGER from_a_buffer INTEGER
)""") )""")
def __del__(self): def __del__(self):
self.cursor.close() self.cursor.close()
self.connection.close() self.connection.close()

View File

@@ -5,40 +5,40 @@ from multiplatform_widgets import widgets
import application import application
class autocompletionManageDialog(widgetUtils.BaseDialog): class autocompletionManageDialog(widgetUtils.BaseDialog):
def __init__(self): def __init__(self):
super(autocompletionManageDialog, self).__init__(parent=None, id=-1, title=_(u"Manage Autocompletion database")) super(autocompletionManageDialog, self).__init__(parent=None, id=-1, title=_(u"Manage Autocompletion database"))
panel = wx.Panel(self) panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
label = wx.StaticText(panel, -1, _(u"Editing {0} users database").format(application.name,)) label = wx.StaticText(panel, -1, _(u"Editing {0} users database").format(application.name,))
self.users = widgets.list(panel, _(u"Username"), _(u"Name"), style=wx.LC_REPORT) self.users = widgets.list(panel, _(u"Username"), _(u"Name"), style=wx.LC_REPORT)
sizer.Add(label, 0, wx.ALL, 5) sizer.Add(label, 0, wx.ALL, 5)
sizer.Add(self.users.list, 0, wx.ALL, 5) sizer.Add(self.users.list, 0, wx.ALL, 5)
self.add = wx.Button(panel, -1, _(u"Add user")) self.add = wx.Button(panel, -1, _(u"Add user"))
self.remove = wx.Button(panel, -1, _(u"Remove user")) self.remove = wx.Button(panel, -1, _(u"Remove user"))
optionsBox = wx.BoxSizer(wx.HORIZONTAL) optionsBox = wx.BoxSizer(wx.HORIZONTAL)
optionsBox.Add(self.add, 0, wx.ALL, 5) optionsBox.Add(self.add, 0, wx.ALL, 5)
optionsBox.Add(self.remove, 0, wx.ALL, 5) optionsBox.Add(self.remove, 0, wx.ALL, 5)
sizer.Add(optionsBox, 0, wx.ALL, 5) sizer.Add(optionsBox, 0, wx.ALL, 5)
ok = wx.Button(panel, wx.ID_OK) ok = wx.Button(panel, wx.ID_OK)
cancel = wx.Button(panel, wx.ID_CANCEL) cancel = wx.Button(panel, wx.ID_CANCEL)
sizerBtn = wx.BoxSizer(wx.HORIZONTAL) sizerBtn = wx.BoxSizer(wx.HORIZONTAL)
sizerBtn.Add(ok, 0, wx.ALL, 5) sizerBtn.Add(ok, 0, wx.ALL, 5)
sizer.Add(cancel, 0, wx.ALL, 5) sizer.Add(cancel, 0, wx.ALL, 5)
sizer.Add(sizerBtn, 0, wx.ALL, 5) sizer.Add(sizerBtn, 0, wx.ALL, 5)
panel.SetSizer(sizer) panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin()) self.SetClientSize(sizer.CalcMin())
def put_users(self, users): def put_users(self, users):
for i in users: for i in users:
j = [i[0], i[1]] j = [i[0], i[1]]
self.users.insert_item(False, *j) self.users.insert_item(False, *j)
def get_user(self): def get_user(self):
usr = False usr = False
userDlg = wx.TextEntryDialog(None, _(u"Twitter username"), _(u"Add user to database")) userDlg = wx.TextEntryDialog(None, _(u"Twitter username"), _(u"Add user to database"))
if userDlg.ShowModal() == wx.ID_OK: if userDlg.ShowModal() == wx.ID_OK:
usr = userDlg.GetValue() usr = userDlg.GetValue()
return usr return usr
def show_invalid_user_error(self): def show_invalid_user_error(self):
wx.MessageDialog(None, _(u"The user does not exist"), _(u"Error!"), wx.ICON_ERROR).ShowModal() wx.MessageDialog(None, _(u"The user does not exist"), _(u"Error!"), wx.ICON_ERROR).ShowModal()

View File

@@ -2,24 +2,24 @@
import wx import wx
class menu(wx.Menu): class menu(wx.Menu):
def __init__(self, window, pattern, mode): def __init__(self, window, pattern, mode):
super(menu, self).__init__() super(menu, self).__init__()
self.window = window self.window = window
self.pattern = pattern self.pattern = pattern
self.mode = mode self.mode = mode
def append_options(self, options): def append_options(self, options):
for i in options: for i in options:
item = wx.MenuItem(self, wx.ID_ANY, "%s (@%s)" % (i[1], i[0])) item = wx.MenuItem(self, wx.ID_ANY, "%s (@%s)" % (i[1], i[0]))
self.AppendItem(item) self.AppendItem(item)
self.Bind(wx.EVT_MENU, lambda evt, temp=i[0]: self.select_text(evt, temp), item) self.Bind(wx.EVT_MENU, lambda evt, temp=i[0]: self.select_text(evt, temp), item)
def select_text(self, ev, text): def select_text(self, ev, text):
if self.mode == "tweet": if self.mode == "tweet":
self.window.ChangeValue(self.window.GetValue().replace("@"+self.pattern, "@"+text+" ")) self.window.ChangeValue(self.window.GetValue().replace("@"+self.pattern, "@"+text+" "))
elif self.mode == "dm": elif self.mode == "dm":
self.window.SetValue(self.window.GetValue().replace(self.pattern, text)) self.window.SetValue(self.window.GetValue().replace(self.pattern, text))
self.window.SetInsertionPointEnd() self.window.SetInsertionPointEnd()
def destroy(self): def destroy(self):
self.Destroy() self.Destroy()

View File

@@ -4,24 +4,24 @@ import widgetUtils
import application import application
class autocompletionSettingsDialog(widgetUtils.BaseDialog): class autocompletionSettingsDialog(widgetUtils.BaseDialog):
def __init__(self): def __init__(self):
super(autocompletionSettingsDialog, self).__init__(parent=None, id=-1, title=_(u"Autocomplete users' settings")) super(autocompletionSettingsDialog, self).__init__(parent=None, id=-1, title=_(u"Autocomplete users' settings"))
panel = wx.Panel(self) panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
self.followers_buffer = wx.CheckBox(panel, -1, _(u"Add users from followers buffer")) self.followers_buffer = wx.CheckBox(panel, -1, _(u"Add users from followers buffer"))
self.friends_buffer = wx.CheckBox(panel, -1, _(u"Add users from friends buffer")) self.friends_buffer = wx.CheckBox(panel, -1, _(u"Add users from friends buffer"))
sizer.Add(self.followers_buffer, 0, wx.ALL, 5) sizer.Add(self.followers_buffer, 0, wx.ALL, 5)
sizer.Add(self.friends_buffer, 0, wx.ALL, 5) sizer.Add(self.friends_buffer, 0, wx.ALL, 5)
self.viewList = wx.Button(panel, -1, _(u"Manage database...")) self.viewList = wx.Button(panel, -1, _(u"Manage database..."))
sizer.Add(self.viewList, 0, wx.ALL, 5) sizer.Add(self.viewList, 0, wx.ALL, 5)
ok = wx.Button(panel, wx.ID_OK) ok = wx.Button(panel, wx.ID_OK)
cancel = wx.Button(panel, wx.ID_CANCEL) cancel = wx.Button(panel, wx.ID_CANCEL)
sizerBtn = wx.BoxSizer(wx.HORIZONTAL) sizerBtn = wx.BoxSizer(wx.HORIZONTAL)
sizerBtn.Add(ok, 0, wx.ALL, 5) sizerBtn.Add(ok, 0, wx.ALL, 5)
sizer.Add(cancel, 0, wx.ALL, 5) sizer.Add(cancel, 0, wx.ALL, 5)
sizer.Add(sizerBtn, 0, wx.ALL, 5) sizer.Add(sizerBtn, 0, wx.ALL, 5)
panel.SetSizer(sizer) panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin()) self.SetClientSize(sizer.CalcMin())
def show_success_dialog(): def show_success_dialog():
wx.MessageDialog(None, _(u"{0}'s database of users has been updated.").format(application.name,), _(u"Done"), wx.OK).ShowModal() wx.MessageDialog(None, _(u"{0}'s database of users has been updated.").format(application.name,), _(u"Done"), wx.OK).ShowModal()

View File

@@ -9,37 +9,37 @@ short_langs = ["", "da", "du", "en", "fi", "fr", "de", "hu", "ko", "it", "ja", "
OcrLangs = ["", "dan", "dut", "eng", "fin", "fre", "ger", "hun", "kor", "ita", "jpn", "pol", "por", "rus", "spa", "tur"] OcrLangs = ["", "dan", "dut", "eng", "fin", "fre", "ger", "hun", "kor", "ita", "jpn", "pol", "por", "rus", "spa", "tur"]
class APIError(Exception): class APIError(Exception):
pass pass
class OCRSpaceAPI(object): class OCRSpaceAPI(object):
def __init__(self, key="4e72ae996f88957", url='https://api.ocr.space/parse/image'): def __init__(self, key="4e72ae996f88957", url='https://api.ocr.space/parse/image'):
self.key = key self.key = key
self.url = url self.url = url
def OCR_URL(self, url, overlay=False, lang=None): def OCR_URL(self, url, overlay=False, lang=None):
payload = { payload = {
'url': url, 'url': url,
'isOverlayRequired': overlay, 'isOverlayRequired': overlay,
'apikey': self.key, 'apikey': self.key,
} }
if lang != None: if lang != None:
payload.update(language=lang) payload.update(language=lang)
r = requests.post(self.url, data=payload) r = requests.post(self.url, data=payload)
result = r.json()['ParsedResults'][0] result = r.json()['ParsedResults'][0]
if result['ErrorMessage']: if result['ErrorMessage']:
raise APIError(result['ErrorMessage']) raise APIError(result['ErrorMessage'])
return result return result
def OCR_file(self, fileobj, overlay=False): def OCR_file(self, fileobj, overlay=False):
payload = { payload = {
'isOverlayRequired': overlay, 'isOverlayRequired': overlay,
'apikey': self.key, 'apikey': self.key,
'lang': 'es', 'lang': 'es',
} }
r = requests.post(self.url, data=payload, files={'file': fileobj}) r = requests.post(self.url, data=payload, files={'file': fileobj})
results = r.json()['ParsedResults'] results = r.json()['ParsedResults']
if results[0]['ErrorMessage']: if results[0]['ErrorMessage']:
raise APIError(results[0]['ErrorMessage']) raise APIError(results[0]['ErrorMessage'])
return results return results

View File

@@ -2,4 +2,4 @@
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import OCRSpace from . import OCRSpace

View File

@@ -4,5 +4,5 @@ from __future__ import unicode_literals
from . import translator from . import translator
import platform import platform
if platform.system() == "Windows": if platform.system() == "Windows":
from . import wx_ui as gui from . import wx_ui as gui

View File

@@ -9,108 +9,108 @@ log = logging.getLogger("extras.translator")
t = None t = None
def translate(text="", target="en"): def translate(text="", target="en"):
global t global t
log.debug("Received translation request for language %s, text=%s" % (target, text)) log.debug("Received translation request for language %s, text=%s" % (target, text))
if t == None: if t == None:
t = Translator() t = Translator()
vars = dict(text=text, dest=target) vars = dict(text=text, dest=target)
return t.translate(**vars).text return t.translate(**vars).text
supported_langs = None supported_langs = None
languages = { languages = {
"af": _(u"Afrikaans"), "af": _(u"Afrikaans"),
"sq": _(u"Albanian"), "sq": _(u"Albanian"),
"am": _(u"Amharic"), "am": _(u"Amharic"),
"ar": _(u"Arabic"), "ar": _(u"Arabic"),
"hy": _(u"Armenian"), "hy": _(u"Armenian"),
"az": _(u"Azerbaijani"), "az": _(u"Azerbaijani"),
"eu": _(u"Basque"), "eu": _(u"Basque"),
"be": _(u"Belarusian"), "be": _(u"Belarusian"),
"bn": _(u"Bengali"), "bn": _(u"Bengali"),
"bh": _(u"Bihari"), "bh": _(u"Bihari"),
"bg": _(u"Bulgarian"), "bg": _(u"Bulgarian"),
"my": _(u"Burmese"), "my": _(u"Burmese"),
"ca": _(u"Catalan"), "ca": _(u"Catalan"),
"chr": _(u"Cherokee"), "chr": _(u"Cherokee"),
"zh": _(u"Chinese"), "zh": _(u"Chinese"),
"zh-CN": _(u"Chinese_simplified"), "zh-CN": _(u"Chinese_simplified"),
"zh-TW": _(u"Chinese_traditional"), "zh-TW": _(u"Chinese_traditional"),
"hr": _(u"Croatian"), "hr": _(u"Croatian"),
"cs": _(u"Czech"), "cs": _(u"Czech"),
"da": _(u"Danish"), "da": _(u"Danish"),
"dv": _(u"Dhivehi"), "dv": _(u"Dhivehi"),
"nl": _(u"Dutch"), "nl": _(u"Dutch"),
"en": _(u"English"), "en": _(u"English"),
"eo": _(u"Esperanto"), "eo": _(u"Esperanto"),
"et": _(u"Estonian"), "et": _(u"Estonian"),
"tl": _(u"Filipino"), "tl": _(u"Filipino"),
"fi": _(u"Finnish"), "fi": _(u"Finnish"),
"fr": _(u"French"), "fr": _(u"French"),
"gl": _(u"Galician"), "gl": _(u"Galician"),
"ka": _(u"Georgian"), "ka": _(u"Georgian"),
"de": _(u"German"), "de": _(u"German"),
"el": _(u"Greek"), "el": _(u"Greek"),
"gn": _(u"Guarani"), "gn": _(u"Guarani"),
"gu": _(u"Gujarati"), "gu": _(u"Gujarati"),
"iw": _(u"Hebrew"), "iw": _(u"Hebrew"),
"hi": _(u"Hindi"), "hi": _(u"Hindi"),
"hu": _(u"Hungarian"), "hu": _(u"Hungarian"),
"is": _(u"Icelandic"), "is": _(u"Icelandic"),
"id": _(u"Indonesian"), "id": _(u"Indonesian"),
"iu": _(u"Inuktitut"), "iu": _(u"Inuktitut"),
"ga": _(u"Irish"), "ga": _(u"Irish"),
"it": _(u"Italian"), "it": _(u"Italian"),
"ja": _(u"Japanese"), "ja": _(u"Japanese"),
"kn": _(u"Kannada"), "kn": _(u"Kannada"),
"kk": _(u"Kazakh"), "kk": _(u"Kazakh"),
"km": _(u"Khmer"), "km": _(u"Khmer"),
"ko": _(u"Korean"), "ko": _(u"Korean"),
"ku": _(u"Kurdish"), "ku": _(u"Kurdish"),
"ky": _(u"Kyrgyz"), "ky": _(u"Kyrgyz"),
"lo": _(u"Laothian"), "lo": _(u"Laothian"),
"lv": _(u"Latvian"), "lv": _(u"Latvian"),
"lt": _(u"Lithuanian"), "lt": _(u"Lithuanian"),
"mk": _(u"Macedonian"), "mk": _(u"Macedonian"),
"ms": _(u"Malay"), "ms": _(u"Malay"),
"ml": _(u"Malayalam"), "ml": _(u"Malayalam"),
"mt": _(u"Maltese"), "mt": _(u"Maltese"),
"mr": _(u"Marathi"), "mr": _(u"Marathi"),
"mn": _(u"Mongolian"), "mn": _(u"Mongolian"),
"ne": _(u"Nepali"), "ne": _(u"Nepali"),
"no": _(u"Norwegian"), "no": _(u"Norwegian"),
"or": _(u"Oriya"), "or": _(u"Oriya"),
"ps": _(u"Pashto"), "ps": _(u"Pashto"),
"fa": _(u"Persian"), "fa": _(u"Persian"),
"pl": _(u"Polish"), "pl": _(u"Polish"),
"pt": _(u"Portuguese"), "pt": _(u"Portuguese"),
"pa": _(u"Punjabi"), "pa": _(u"Punjabi"),
"ro": _(u"Romanian"), "ro": _(u"Romanian"),
"ru": _(u"Russian"), "ru": _(u"Russian"),
"sa": _(u"Sanskrit"), "sa": _(u"Sanskrit"),
"sr": _(u"Serbian"), "sr": _(u"Serbian"),
"sd": _(u"Sindhi"), "sd": _(u"Sindhi"),
"si": _(u"Sinhalese"), "si": _(u"Sinhalese"),
"sk": _(u"Slovak"), "sk": _(u"Slovak"),
"sl": _(u"Slovenian"), "sl": _(u"Slovenian"),
"es": _(u"Spanish"), "es": _(u"Spanish"),
"sw": _(u"Swahili"), "sw": _(u"Swahili"),
"sv": _(u"Swedish"), "sv": _(u"Swedish"),
"tg": _(u"Tajik"), "tg": _(u"Tajik"),
"ta": _(u"Tamil"), "ta": _(u"Tamil"),
"tl": _(u"Tagalog"), "tl": _(u"Tagalog"),
"te": _(u"Telugu"), "te": _(u"Telugu"),
"th": _(u"Thai"), "th": _(u"Thai"),
"bo": _(u"Tibetan"), "bo": _(u"Tibetan"),
"tr": _(u"Turkish"), "tr": _(u"Turkish"),
"uk": _(u"Ukrainian"), "uk": _(u"Ukrainian"),
"ur": _(u"Urdu"), "ur": _(u"Urdu"),
"uz": _(u"Uzbek"), "uz": _(u"Uzbek"),
"ug": _(u"Uighur"), "ug": _(u"Uighur"),
"vi": _(u"Vietnamese"), "vi": _(u"Vietnamese"),
"cy": _(u"Welsh"), "cy": _(u"Welsh"),
"yi": _(u"Yiddish") "yi": _(u"Yiddish")
} }
def available_languages(): def available_languages():
return dict(sorted(languages.items(), key=lambda x: x[1])) return dict(sorted(languages.items(), key=lambda x: x[1]))

View File

@@ -21,25 +21,25 @@ import wx
from wxUI.dialogs import baseDialog from wxUI.dialogs import baseDialog
class translateDialog(baseDialog.BaseWXDialog): class translateDialog(baseDialog.BaseWXDialog):
def __init__(self): def __init__(self):
languages = [] languages = []
language_dict = translator.available_languages() language_dict = translator.available_languages()
for k in language_dict: for k in language_dict:
languages.append(language_dict[k]) languages.append(language_dict[k])
super(translateDialog, self).__init__(None, -1, title=_(u"Translate message")) super(translateDialog, self).__init__(None, -1, title=_(u"Translate message"))
panel = wx.Panel(self) panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
staticDest = wx.StaticText(panel, -1, _(u"Target language")) staticDest = wx.StaticText(panel, -1, _(u"Target language"))
self.dest_lang = wx.ComboBox(panel, -1, choices=languages, style = wx.CB_READONLY) self.dest_lang = wx.ComboBox(panel, -1, choices=languages, style = wx.CB_READONLY)
self.dest_lang.SetFocus() self.dest_lang.SetFocus()
self.dest_lang.SetSelection(0) self.dest_lang.SetSelection(0)
listSizer = wx.BoxSizer(wx.HORIZONTAL) listSizer = wx.BoxSizer(wx.HORIZONTAL)
listSizer.Add(staticDest) listSizer.Add(staticDest)
listSizer.Add(self.dest_lang) listSizer.Add(self.dest_lang)
ok = wx.Button(panel, wx.ID_OK) ok = wx.Button(panel, wx.ID_OK)
ok.SetDefault() ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL) cancel = wx.Button(panel, wx.ID_CANCEL)
self.SetEscapeId(wx.ID_CANCEL) self.SetEscapeId(wx.ID_CANCEL)
def get(self, control): def get(self, control):
return getattr(self, control).GetSelection() return getattr(self, control).GetSelection()

View File

@@ -9,11 +9,11 @@ from . import fix_urllib3_warnings # Avoiding some SSL warnings related to Twyth
from . import fix_win32com from . import fix_win32com
#from . import fix_requests #fix cacert.pem location for TWBlue binary copies #from . import fix_requests #fix cacert.pem location for TWBlue binary copies
def setup(): def setup():
fix_arrow.fix() fix_arrow.fix()
if hasattr(sys, "frozen"): if hasattr(sys, "frozen"):
fix_libloader.fix() fix_libloader.fix()
fix_win32com.fix() fix_win32com.fix()
# fix_requests.fix() # fix_requests.fix()
# else: # else:
# fix_requests.fix(False) # fix_requests.fix(False)
fix_urllib3_warnings.fix() fix_urllib3_warnings.fix()

View File

@@ -1,95 +1,49 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
from arrow import locales from arrow import locales
from arrow.locales import Locale from arrow.locales import Locale
def fix(): def fix():
# insert a modified function so if there is no language available in arrow, returns English locale. # insert a modified function so if there is no language available in arrow, returns English locale.
locales.get_locale = get_locale locales.get_locale = get_locale
def get_locale(name): def get_locale(name):
locale_cls = locales._locales.get(name.lower()) locale_cls = locales._locale_map.get(name.lower())
if locale_cls is None: if locale_cls is None:
name = name[:2] name = name[:2]
locale_cls = locales._locales.get(name.lower()) locale_cls = locales._locale_map.get(name.lower())
if locale_cls == None: if locale_cls == None:
return locales.EnglishLocale() return locales.EnglishLocale()
return locale_cls() return locale_cls()
class CatalaLocale(Locale): class GalicianLocale(object):
names = ['ca', 'ca_es', 'ca_ca'] names = ['gl', 'gl_es', 'gl_gl']
past = 'Fa {0}' past = 'Hai {0}'
future = '{0}' # I don't know what's the right phrase in catala for the future. future = 'En {0}'
and_word = "e"
timeframes = { timeframes = {
'now': 'Ara mateix', 'now': 'Agora',
'seconds': 'segons', "second": "un segundo",
'minute': '1 minut', 'seconds': '{0} segundos',
'minutes': '{0} minuts', 'minute': 'un minuto',
'hour': 'una hora', 'minutes': '{0} minutos',
'hours': '{0} hores', 'hour': 'unha hora',
'day': 'un dia', 'hours': '{0} horas',
'days': '{0} dies', 'day': 'un día',
'month': 'un mes', 'days': '{0} días',
'months': '{0} messos', "week": "unha semana",
'year': 'un any', "weeks": "{0} semanas",
'years': '{0} anys', 'month': 'un mes',
} 'months': '{0} meses',
'year': 'un ano',
'years': '{0} anos',
}
month_names = ['', 'Jener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Decembre'] meridians = {"am": "am", "pm": "pm", "AM": "AM", "PM": "PM"}
month_abbreviations = ['', 'Jener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Decembre']
day_names = ['', 'Dilluns', 'Dimars', 'Dimecres', 'Dijous', 'Divendres', 'Disabte', 'Diumenge']
day_abbreviations = ['', 'Dilluns', 'Dimars', 'Dimecres', 'Dijous', 'Divendres', 'Disabte', 'Diumenge']
class GalicianLocale(Locale): month_names = ['', 'xaneiro', 'febreiro', 'marzo', 'abril', 'maio', 'xuño', 'xullo', 'agosto', 'setembro', 'outubro', 'novembro', 'decembro']
names = ['gl', 'gl_es', 'gl_gl'] month_abbreviations = ['', 'xan', 'feb', 'mar', 'abr', 'mai', 'xun', 'xul', 'ago', 'set', 'out', 'nov', 'dec']
past = 'Fai {0}' day_names = ['', 'luns', 'martes', 'mércores', 'xoves', 'venres', 'sábado', 'domingo']
future = 'En {0}' day_abbreviations = ['', 'lun', 'mar', 'mer', 'xov', 'ven', 'sab', 'dom']
ordinal_day_re = r"((?P<value>[1-3]?[0-9](?=[ºª]))[ºª])"
timeframes = {
'now': 'Agora mesmo',
'seconds': 'segundos',
'minute': 'un minuto',
'minutes': '{0} minutos',
'hour': 'una hora',
'hours': '{0} horas',
'day': 'un día',
'days': '{0} días',
'month': 'un mes',
'months': '{0} meses',
'year': 'un ano',
'years': '{0} anos',
}
month_names = ['', 'Xaneiro', 'Febreiro', 'Marzo', 'Abril', 'Maio', 'Xuño', 'Xullo', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Decembro']
month_abbreviations = ['', 'Xan', 'Feb', 'Mar', 'Abr', 'Mai', 'Xun', 'Xul', 'Ago', 'Set', 'Out', 'Nov', 'Dec']
day_names = ['', 'Luns', 'Martes', 'Mércores', 'Xoves', 'Venres', 'Sábado', 'Domingo']
day_abbreviations = ['', 'Lun', 'Mar', 'Mer', 'xov', 'Ven' 'Sab', 'Dom']
class BasqueLocale(Locale):
names = ['eu', 'eu_es', 'eu_eu']
past = 'duela {0}'
future = '{0} igarota'
timeframes = {
'now': 'Orain',
# 'second': 'segundu bat',
'seconds': 'segundu batzuk', # without specifying a number.
#'seconds': '{0} segundu', # specifying a number
'minute': 'minutu bat',
'minutes': '{0} minutu',
'hour': 'ordu bat',
'hours': '{0} ordu',
'day': 'egun bat',
'days': '{0} egun',
'month': 'hilabete bat',
'months': '{0} hilabete',
'year': 'urte bat',
'years': '{0} urte',
}
month_names = ['', 'Urtarrilak', 'Otsailak', 'Martxoak', 'Apirilak', 'Maiatzak', 'Ekainak', 'Uztailak', 'Abuztuak', 'Irailak', 'Urriak', 'Azaroak', 'Abenduak']
month_abbreviations = ['', 'urt', 'ots', 'mar', 'api', 'mai', 'eka', 'uzt', 'abu', 'ira', 'urr', 'aza', 'abe']
day_names = ['', 'Asteleehna', 'Asteartea', 'Asteazkena', 'Osteguna', 'Ostirala', 'Larunbata', 'Igandea']
day_abbreviations = ['', 'al', 'ar', 'az', 'og', 'ol', 'lr', 'ig']

View File

@@ -16,27 +16,27 @@ log = logging.getLogger("fixes.fix_libloader")
fixed=False fixed=False
def patched_getmodule(modname): def patched_getmodule(modname):
mod=__import__(modname) mod=__import__(modname)
return sys.modules[modname] return sys.modules[modname]
def load_com(*names): def load_com(*names):
global fixed global fixed
if fixed==False: if fixed==False:
gencache._GetModule=patched_getmodule gencache._GetModule=patched_getmodule
com.prepare_gencache() com.prepare_gencache()
fixed=True fixed=True
result = None result = None
for name in names: for name in names:
try: try:
result = gencache.EnsureDispatch(name) result = gencache.EnsureDispatch(name)
break break
except com_error: except com_error:
continue continue
if result is None: if result is None:
raise com_error("Unable to load any of the provided com objects.") raise com_error("Unable to load any of the provided com objects.")
return result return result
def fix(): def fix():
log.debug("Applying fix for Libloader...") log.debug("Applying fix for Libloader...")
com.load_com = load_com com.load_com = load_com
log.debug("Load_com has been mapped correctly.") log.debug("Load_com has been mapped correctly.")

View File

@@ -7,6 +7,6 @@ import logging
log = logging.getLogger("fixes.fix_requests") log = logging.getLogger("fixes.fix_requests")
def fix(): def fix():
log.debug("Applying fix for requests...") log.debug("Applying fix for requests...")
os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(paths.app_path(), "certifi", "cacert.pem")#.encode(paths.fsencoding) os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(paths.app_path(), "certifi", "cacert.pem")#.encode(paths.fsencoding)
# log.debug("Changed CA path to %s" % (os.environ["REQUESTS_CA_BUNDLE"]))#.decode(paths.fsencoding))) # log.debug("Changed CA path to %s" % (os.environ["REQUESTS_CA_BUNDLE"]))#.decode(paths.fsencoding)))

View File

@@ -8,8 +8,8 @@ import six
import urllib.request, urllib.parse, urllib.error import urllib.request, urllib.parse, urllib.error
def fix(): def fix():
urllib3.disable_warnings() urllib3.disable_warnings()
fields.format_header_param=patched_format_header_param fields.format_header_param=patched_format_header_param
def patched_format_header_param(name, value): def patched_format_header_param(name, value):
if not any(ch in value for ch in '"\\\r\n'): if not any(ch in value for ch in '"\\\r\n'):

View File

@@ -1,6 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import win32com.client import win32com.client
def fix(): def fix():
if win32com.client.gencache.is_readonly == True: if win32com.client.gencache.is_readonly == True:
win32com.client.gencache.is_readonly = False win32com.client.gencache.is_readonly = False
win32com.client.gencache.Rebuild() win32com.client.gencache.Rebuild()

View File

@@ -27,40 +27,40 @@ from suds.client import Client
import constants import constants
class reportBug(object): class reportBug(object):
def __init__(self, user_name): def __init__(self, user_name):
self.user_name = user_name self.user_name = user_name
self.categories = [_(u"General")] self.categories = [_(u"General")]
self.reproducibilities = [_(u"always"), _(u"sometimes"), _(u"random"), _(u"have not tried"), _(u"unable to duplicate")] 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")] self.severities = [_(u"block"), _(u"crash"), _(u"major"), _(u"minor"), _(u"tweak"), _(u"text"), _(u"trivial"), _(u"feature")]
self.dialog = wx_ui.reportBugDialog(self.categories, self.reproducibilities, self.severities) self.dialog = wx_ui.reportBugDialog(self.categories, self.reproducibilities, self.severities)
widgetUtils.connect_event(self.dialog.ok, widgetUtils.BUTTON_PRESSED, self.send) widgetUtils.connect_event(self.dialog.ok, widgetUtils.BUTTON_PRESSED, self.send)
self.dialog.get_response() self.dialog.get_response()
def send(self, *args, **kwargs): def send(self, *args, **kwargs):
if self.dialog.get("summary") == "" or self.dialog.get("description") == "": if self.dialog.get("summary") == "" or self.dialog.get("description") == "":
self.dialog.no_filled() self.dialog.no_filled()
return return
if self.dialog.get("agree") == False: if self.dialog.get("agree") == False:
self.dialog.no_checkbox() self.dialog.no_checkbox()
return return
try: try:
client = Client(application.report_bugs_url) client = Client(application.report_bugs_url)
issue = client.factory.create('IssueData') issue = client.factory.create('IssueData')
issue.project.name = application.name issue.project.name = application.name
issue.project.id = 0 issue.project.id = 0
issue.summary = self.dialog.get("summary"), issue.summary = self.dialog.get("summary"),
issue.description = "Reported by @%s on version %s (snapshot = %s)\n\n" % (self.user_name, application.version, application.snapshot) + self.dialog.get("description") issue.description = "Reported by @%s on version %s (snapshot = %s)\n\n" % (self.user_name, application.version, application.snapshot) + self.dialog.get("description")
# to do: Create getters for category, severity and reproducibility in wx_UI. # to do: Create getters for category, severity and reproducibility in wx_UI.
issue.category = constants.categories[self.dialog.category.GetSelection()] issue.category = constants.categories[self.dialog.category.GetSelection()]
issue.reproducibility.name = constants.reproducibilities[self.dialog.reproducibility.GetSelection()] issue.reproducibility.name = constants.reproducibilities[self.dialog.reproducibility.GetSelection()]
issue.severity.name = constants.severities[self.dialog.severity.GetSelection()] issue.severity.name = constants.severities[self.dialog.severity.GetSelection()]
issue.priority.name = "normal" issue.priority.name = "normal"
issue.view_state.name = "public" issue.view_state.name = "public"
issue.resolution.name = "open" issue.resolution.name = "open"
issue.projection.name = "none" issue.projection.name = "none"
issue.eta.name = "eta" issue.eta.name = "eta"
issue.status.name = "new" issue.status.name = "new"
id = client.service.mc_issue_add(keys.keyring.get("bts_user"), keys.keyring.get("bts_password"), issue) id = client.service.mc_issue_add(keys.keyring.get("bts_user"), keys.keyring.get("bts_password"), issue)
self.dialog.success(id) self.dialog.success(id)
except: except:
self.dialog.error() self.dialog.error()

View File

@@ -21,75 +21,75 @@ import wx
import widgetUtils import widgetUtils
import application import application
class reportBugDialog(widgetUtils.BaseDialog): class reportBugDialog(widgetUtils.BaseDialog):
def __init__(self, categories, reproducibilities, severities): def __init__(self, categories, reproducibilities, severities):
super(reportBugDialog, self).__init__(parent=None, id=wx.NewId()) super(reportBugDialog, self).__init__(parent=None, id=wx.NewId())
self.SetTitle(_(u"Report an error")) self.SetTitle(_(u"Report an error"))
panel = wx.Panel(self) panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
categoryLabel = wx.StaticText(panel, -1, _(u"Select a category"), size=wx.DefaultSize) categoryLabel = wx.StaticText(panel, -1, _(u"Select a category"), size=wx.DefaultSize)
self.category = wx.ComboBox(panel, -1, choices=categories, style=wx.CB_READONLY) self.category = wx.ComboBox(panel, -1, choices=categories, style=wx.CB_READONLY)
self.category.SetSelection(0) self.category.SetSelection(0)
categoryB = wx.BoxSizer(wx.HORIZONTAL) categoryB = wx.BoxSizer(wx.HORIZONTAL)
categoryB.Add(categoryLabel, 0, wx.ALL, 5) categoryB.Add(categoryLabel, 0, wx.ALL, 5)
categoryB.Add(self.category, 0, wx.ALL, 5) categoryB.Add(self.category, 0, wx.ALL, 5)
self.category.SetFocus() self.category.SetFocus()
sizer.Add(categoryB, 0, wx.ALL, 5) 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) 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) self.summary = wx.TextCtrl(panel, -1)
dc = wx.WindowDC(self.summary) dc = wx.WindowDC(self.summary)
dc.SetFont(self.summary.GetFont()) dc.SetFont(self.summary.GetFont())
self.summary.SetSize(dc.GetTextExtent("a"*80)) self.summary.SetSize(dc.GetTextExtent("a"*80))
summaryB = wx.BoxSizer(wx.HORIZONTAL) summaryB = wx.BoxSizer(wx.HORIZONTAL)
summaryB.Add(summaryLabel, 0, wx.ALL, 5) summaryB.Add(summaryLabel, 0, wx.ALL, 5)
summaryB.Add(self.summary, 0, wx.ALL, 5) summaryB.Add(self.summary, 0, wx.ALL, 5)
sizer.Add(summaryB, 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) 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) self.description = wx.TextCtrl(panel, -1, style=wx.TE_MULTILINE)
dc = wx.WindowDC(self.description) dc = wx.WindowDC(self.description)
dc.SetFont(self.description.GetFont()) dc.SetFont(self.description.GetFont())
(x, y, z) = dc.GetMultiLineTextExtent("0"*2000) (x, y, z) = dc.GetMultiLineTextExtent("0"*2000)
self.description.SetSize((x, y)) self.description.SetSize((x, y))
descBox = wx.BoxSizer(wx.HORIZONTAL) descBox = wx.BoxSizer(wx.HORIZONTAL)
descBox.Add(descriptionLabel, 0, wx.ALL, 5) descBox.Add(descriptionLabel, 0, wx.ALL, 5)
descBox.Add(self.description, 0, wx.ALL, 5) descBox.Add(self.description, 0, wx.ALL, 5)
sizer.Add(descBox, 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) reproducibilityLabel = wx.StaticText(panel, -1, _(u"how often does this bug happen?"), size=wx.DefaultSize)
self.reproducibility = wx.ComboBox(panel, -1, choices=reproducibilities, style=wx.CB_READONLY) self.reproducibility = wx.ComboBox(panel, -1, choices=reproducibilities, style=wx.CB_READONLY)
self.reproducibility.SetSelection(3) self.reproducibility.SetSelection(3)
reprB = wx.BoxSizer(wx.HORIZONTAL) reprB = wx.BoxSizer(wx.HORIZONTAL)
reprB.Add(reproducibilityLabel, 0, wx.ALL, 5) reprB.Add(reproducibilityLabel, 0, wx.ALL, 5)
reprB.Add(self.reproducibility, 0, wx.ALL, 5) reprB.Add(self.reproducibility, 0, wx.ALL, 5)
sizer.Add(reprB, 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")) severityLabel = wx.StaticText(panel, -1, _(u"Select the importance that you think this bug has"))
self.severity = wx.ComboBox(panel, -1, choices=severities, style=wx.CB_READONLY) self.severity = wx.ComboBox(panel, -1, choices=severities, style=wx.CB_READONLY)
self.severity.SetSelection(3) self.severity.SetSelection(3)
severityB = wx.BoxSizer(wx.HORIZONTAL) severityB = wx.BoxSizer(wx.HORIZONTAL)
severityB.Add(severityLabel, 0, wx.ALL, 5) severityB.Add(severityLabel, 0, wx.ALL, 5)
severityB.Add(self.severity, 0, wx.ALL, 5) severityB.Add(self.severity, 0, wx.ALL, 5)
sizer.Add(severityB, 0, wx.ALL, 5) sizer.Add(severityB, 0, wx.ALL, 5)
self.agree = wx.CheckBox(panel, -1, _(u"I know that the {0} bug system will get my Twitter username to contact me and fix the bug quickly").format(application.name,)) self.agree = wx.CheckBox(panel, -1, _(u"I know that the {0} bug system will get my Twitter username to contact me and fix the bug quickly").format(application.name,))
self.agree.SetValue(False) self.agree.SetValue(False)
sizer.Add(self.agree, 0, wx.ALL, 5) sizer.Add(self.agree, 0, wx.ALL, 5)
self.ok = wx.Button(panel, wx.ID_OK, _(u"Send report")) self.ok = wx.Button(panel, wx.ID_OK, _(u"Send report"))
self.ok.SetDefault() self.ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel")) cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
btnBox = wx.BoxSizer(wx.HORIZONTAL) btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(self.ok, 0, wx.ALL, 5) btnBox.Add(self.ok, 0, wx.ALL, 5)
btnBox.Add(cancel, 0, wx.ALL, 5) btnBox.Add(cancel, 0, wx.ALL, 5)
sizer.Add(btnBox, 0, wx.ALL, 5) sizer.Add(btnBox, 0, wx.ALL, 5)
panel.SetSizer(sizer) panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin()) self.SetClientSize(sizer.CalcMin())
def no_filled(self): def no_filled(self):
wx.MessageDialog(self, _(u"You must fill out both fields"), _(u"Error"), wx.OK|wx.ICON_ERROR).ShowModal() wx.MessageDialog(self, _(u"You must fill out both fields"), _(u"Error"), wx.OK|wx.ICON_ERROR).ShowModal()
def no_checkbox(self): def no_checkbox(self):
wx.MessageDialog(self, _(u"You need to mark the checkbox to provide us your twitter username to contact you if it is necessary."), _(u"Error"), wx.ICON_ERROR).ShowModal() wx.MessageDialog(self, _(u"You need to mark the checkbox to provide us your twitter username to contact you if it is necessary."), _(u"Error"), wx.ICON_ERROR).ShowModal()
def success(self, id): def success(self, id):
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() 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) self.EndModal(wx.ID_OK)
def error(self): def error(self):
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() 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) self.EndModal(wx.ID_CANCEL)

View File

@@ -1,8 +1,8 @@
from __future__ import absolute_import from __future__ import absolute_import
import platform import platform
if platform.system() == 'Linux': if platform.system() == 'Linux':
from .linux import LinuxKeyboardHandler as GlobalKeyboardHandler from .linux import LinuxKeyboardHandler as GlobalKeyboardHandler
else: else:
from .wx_handler import WXKeyboardHandler as GlobalKeyboardHandler from .wx_handler import WXKeyboardHandler as GlobalKeyboardHandler
#elif platform.system() == 'Darwin': #elif platform.system() == 'Darwin':
#from osx import OSXKeyboardHandler as GlobalKeyboardHandler #from osx import OSXKeyboardHandler as GlobalKeyboardHandler

View File

@@ -1,127 +1,127 @@
keys = { keys = {
'accept': 30, 'accept': 30,
'add': 107, 'add': 107,
'apps': 93, 'apps': 93,
'attn': 246, 'attn': 246,
'back': 8, 'back': 8,
'browser_back': 166, 'browser_back': 166,
'browser_forward': 167, 'browser_forward': 167,
'cancel': 3, 'cancel': 3,
'capital': 20, 'capital': 20,
'clear': 12, 'clear': 12,
'control': 17, 'control': 17,
'convert': 28, 'convert': 28,
'crsel': 247, 'crsel': 247,
'decimal': 110, 'decimal': 110,
'delete': 46, 'delete': 46,
'divide': 111, 'divide': 111,
'down': 40, 'down': 40,
'end': 35, 'end': 35,
'ereof': 249, 'ereof': 249,
'escape': 27, 'escape': 27,
'execute': 43, 'execute': 43,
'exsel': 248, 'exsel': 248,
'f1': 112, 'f1': 112,
'f10': 121, 'f10': 121,
'f11': 122, 'f11': 122,
'f12': 123, 'f12': 123,
'f13': 124, 'f13': 124,
'f14': 125, 'f14': 125,
'f15': 126, 'f15': 126,
'f16': 127, 'f16': 127,
'f17': 128, 'f17': 128,
'f18': 129, 'f18': 129,
'f19': 130, 'f19': 130,
'f2': 113, 'f2': 113,
'f20': 131, 'f20': 131,
'f21': 132, 'f21': 132,
'f22': 133, 'f22': 133,
'f23': 134, 'f23': 134,
'f24': 135, 'f24': 135,
'f3': 114, 'f3': 114,
'f4': 115, 'f4': 115,
'f5': 116, 'f5': 116,
'f6': 117, 'f6': 117,
'f7': 118, 'f7': 118,
'f8': 119, 'f8': 119,
'f9': 120, 'f9': 120,
'final': 24, 'final': 24,
'hangeul': 21, 'hangeul': 21,
'hangul': 21, 'hangul': 21,
'hanja': 25, 'hanja': 25,
'help': 47, 'help': 47,
'home': 36, 'home': 36,
'insert': 45, 'insert': 45,
'junja': 23, 'junja': 23,
'kana': 21, 'kana': 21,
'kanji': 25, 'kanji': 25,
'lbutton': 1, 'lbutton': 1,
'lcontrol': 162, 'lcontrol': 162,
'left': 37, 'left': 37,
'lmenu': 164, 'lmenu': 164,
'lshift': 160, 'lshift': 160,
'lwin': 91, 'lwin': 91,
'mbutton': 4, 'mbutton': 4,
'media_next_track': 176, 'media_next_track': 176,
'media_play_pause': 179, 'media_play_pause': 179,
'media_prev_track': 177, 'media_prev_track': 177,
'menu': 18, 'menu': 18,
'modechange': 31, 'modechange': 31,
'multiply': 106, 'multiply': 106,
'next': 34, 'next': 34,
'noname': 252, 'noname': 252,
'nonconvert': 29, 'nonconvert': 29,
'numlock': 144, 'numlock': 144,
'numpad0': 96, 'numpad0': 96,
'numpad1': 97, 'numpad1': 97,
'numpad2': 98, 'numpad2': 98,
'numpad3': 99, 'numpad3': 99,
'numpad4': 100, 'numpad4': 100,
'numpad5': 101, 'numpad5': 101,
'numpad6': 102, 'numpad6': 102,
'numpad7': 103, 'numpad7': 103,
'numpad8': 104, 'numpad8': 104,
'numpad9': 105, 'numpad9': 105,
'oem_clear': 254, 'oem_clear': 254,
'pa1': 253, 'pa1': 253,
'pagedown': 34, 'pagedown': 34,
'pageup': 33, 'pageup': 33,
'pause': 19, 'pause': 19,
'play': 250, 'play': 250,
'print': 42, 'print': 42,
'prior': 33, 'prior': 33,
'processkey': 229, 'processkey': 229,
'rbutton': 2, 'rbutton': 2,
'rcontrol': 163, 'rcontrol': 163,
'return': 13, 'return': 13,
'right': 39, 'right': 39,
'rmenu': 165, 'rmenu': 165,
'rshift': 161, 'rshift': 161,
'rwin': 92, 'rwin': 92,
'scroll': 145, 'scroll': 145,
'select': 41, 'select': 41,
'separator': 108, 'separator': 108,
'shift': 16, 'shift': 16,
'snapshot': 44, 'snapshot': 44,
'space': 32, 'space': 32,
'subtract': 109, 'subtract': 109,
'tab': 9, 'tab': 9,
'up': 38, 'up': 38,
'volume_down': 174, 'volume_down': 174,
'volume_mute': 173, 'volume_mute': 173,
'volume_up': 175, 'volume_up': 175,
'xbutton1': 5, 'xbutton1': 5,
'xbutton2': 6, 'xbutton2': 6,
'zoom': 251, 'zoom': 251,
'/': 191, '/': 191,
';': 218, ';': 218,
'[': 219, '[': 219,
'\\': 220, '\\': 220,
']': 221, ']': 221,
'\'': 222, '\'': 222,
'=': 187, '=': 187,
'-': 189, '-': 189,
';': 186, ';': 186,
} }
modifiers = {'alt': 1, 'control': 2, 'shift': 4, 'win': 8} modifiers = {'alt': 1, 'control': 2, 'shift': 4, 'win': 8}

View File

@@ -3,56 +3,56 @@ import threading
import thread import thread
import pyatspi import pyatspi
def parse(s): def parse(s):
"""parse a string like control+f into (modifier, key). """parse a string like control+f into (modifier, key).
Unknown modifiers will return ValueError.""" Unknown modifiers will return ValueError."""
m = 0 m = 0
lst = s.split('+') lst = s.split('+')
if not len(lst): return (0, s) if not len(lst): return (0, s)
#Are these right? #Are these right?
d = { d = {
"shift": 1<<pyatspi.MODIFIER_SHIFT, "shift": 1<<pyatspi.MODIFIER_SHIFT,
"control": 1<<pyatspi.MODIFIER_CONTROL, "control": 1<<pyatspi.MODIFIER_CONTROL,
"alt": 1<<pyatspi.MODIFIER_ALT, "alt": 1<<pyatspi.MODIFIER_ALT,
"win":1<<pyatspi.MODIFIER_META3, "win":1<<pyatspi.MODIFIER_META3,
} }
for item in lst: for item in lst:
if item in d: if item in d:
m|=d[item] m|=d[item]
lst.remove(item) lst.remove(item)
#end if #end if
if len(lst) > 1: #more than one key, parse error if len(lst) > 1: #more than one key, parse error
raise ValueError, 'unknown modifier %s' % lst[0] raise ValueError, 'unknown modifier %s' % lst[0]
return (m, lst[0].lower()) return (m, lst[0].lower())
class AtspiThread(threading.Thread): class AtspiThread(threading.Thread):
def run(self): def run(self):
pyatspi.Registry.registerKeystrokeListener(handler, kind=(pyatspi.KEY_PRESSED_EVENT,), pyatspi.Registry.registerKeystrokeListener(handler, kind=(pyatspi.KEY_PRESSED_EVENT,),
mask=pyatspi.allModifiers()) mask=pyatspi.allModifiers())
pyatspi.Registry.start() pyatspi.Registry.start()
#the keys we registered #the keys we registered
keys = {} keys = {}
def handler(e): def handler(e):
m,k = e.modifiers,e.event_string.lower() m,k = e.modifiers,e.event_string.lower()
#not sure why we can't catch control+f. Try to fix it. #not sure why we can't catch control+f. Try to fix it.
if (not e.is_text) and e.id >= 97 <= 126: if (not e.is_text) and e.id >= 97 <= 126:
k = chr(e.id) k = chr(e.id)
if (m,k) not in keys: return False if (m,k) not in keys: return False
thread.start_new(keys[(m,k)], ()) thread.start_new(keys[(m,k)], ())
return True #don't pass it on return True #don't pass it on
class LinuxKeyboardHandler(KeyboardHandler): class LinuxKeyboardHandler(KeyboardHandler):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
KeyboardHandler.__init__(self, *args, **kwargs) KeyboardHandler.__init__(self, *args, **kwargs)
t = AtspiThread() t = AtspiThread()
t.start() t.start()
def register_key(self, key, function): def register_key(self, key, function):
"""key will be a string, such as control+shift+f. """key will be a string, such as control+shift+f.
We need to convert that, using parse_key, We need to convert that, using parse_key,
into modifier and key to put into our dictionary.""" into modifier and key to put into our dictionary."""
#register key so we know if we have it on event receive. #register key so we know if we have it on event receive.
t = parse(key) t = parse(key)
keys[t] = function keys[t] = function
#if we got this far, the key is valid. #if we got this far, the key is valid.
KeyboardHandler.register_key(self, key, function) KeyboardHandler.register_key(self, key, function)
def unregister_key (self, key, function): def unregister_key (self, key, function):
KeyboardHandler.unregister_key(self, key, function) KeyboardHandler.unregister_key(self, key, function)
del keys[parse(key)] del keys[parse(key)]

View File

@@ -5,84 +5,84 @@ class KeyboardHandlerError (Exception): pass
class KeyboardHandler(object): class KeyboardHandler(object):
def __init__(self, repeat_rate=0.0, *args, **kwargs): def __init__(self, repeat_rate=0.0, *args, **kwargs):
self.repeat_rate = repeat_rate #How long between accepting the same keystroke? self.repeat_rate = repeat_rate #How long between accepting the same keystroke?
self._last_key = None self._last_key = None
self._last_keypress_time = 0 self._last_keypress_time = 0
super(KeyboardHandler, self).__init__(*args, **kwargs) super(KeyboardHandler, self).__init__(*args, **kwargs)
self.active_keys = {} self.active_keys = {}
if not hasattr(self, 'replacement_mods'): if not hasattr(self, 'replacement_mods'):
self.replacement_mods = {} self.replacement_mods = {}
if not hasattr(self, 'replacement_keys'): if not hasattr(self, 'replacement_keys'):
self.replacement_keys = {} self.replacement_keys = {}
def register_key (self, key, function): def register_key (self, key, function):
if key in self.active_keys: if key in self.active_keys:
raise KeyboardHandlerError("Key %s is already registered to a function" % key) raise KeyboardHandlerError("Key %s is already registered to a function" % key)
if not callable(function): if not callable(function):
raise TypeError("Must provide a callable to be invoked upon keypress") raise TypeError("Must provide a callable to be invoked upon keypress")
self.active_keys[key] = function self.active_keys[key] = function
def unregister_key (self, key, function): def unregister_key (self, key, function):
try: try:
if self.active_keys[key] != function: if self.active_keys[key] != function:
raise KeyboardHandlerError("key %s is not registered to that function" % key) raise KeyboardHandlerError("key %s is not registered to that function" % key)
except KeyError: except KeyError:
raise KeyboardHandlerError("Key %s not currently registered" % key) raise KeyboardHandlerError("Key %s not currently registered" % key)
del(self.active_keys[key]) del(self.active_keys[key])
def unregister_all_keys(self): def unregister_all_keys(self):
for key in list(self.active_keys): for key in list(self.active_keys):
self.unregister_key(key, self.active_keys[key]) self.unregister_key(key, self.active_keys[key])
def handle_key (self, 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: if self.repeat_rate and key == self._last_key and time.time() - self._last_keypress_time < self.repeat_rate:
return return
try: try:
function = self.active_keys[key] function = self.active_keys[key]
except KeyError: except KeyError:
return return
self._last_key = key self._last_key = key
self._last_keypress_time = time.time() self._last_keypress_time = time.time()
return function() return function()
def register_keys(self, keys): def register_keys(self, keys):
"""Given a mapping of keystrokes to functions, registers all keystrokes""" """Given a mapping of keystrokes to functions, registers all keystrokes"""
for k in keys: for k in keys:
self.register_key(k, keys[k]) self.register_key(k, keys[k])
def unregister_keys(self, keys): def unregister_keys(self, keys):
"""Given a mapping of keys to their functions, unregisters all provided keys.""" """Given a mapping of keys to their functions, unregisters all provided keys."""
for k in keys: for k in keys:
self.unregister_key(k, keys[k]) self.unregister_key(k, keys[k])
def standardize_key(self, key): def standardize_key(self, key):
"""Takes a keystroke and places it in a standard case and order in a list.""" """Takes a keystroke and places it in a standard case and order in a list."""
working = key.split('+') working = key.split('+')
working = [i.lower() for i in working] working = [i.lower() for i in working]
answer = [] answer = []
if "control" in working: if "control" in working:
answer.append("control") answer.append("control")
if "win" in working: if "win" in working:
answer.append("win") answer.append("win")
if "alt" in working: if "alt" in working:
answer.append("alt") answer.append("alt")
if "shift" in working: if "shift" in working:
answer.append("shift") answer.append("shift")
if working[-1] not in answer: if working[-1] not in answer:
answer.append(working[-1]) answer.append(working[-1])
return answer return answer
def standardize_keymap(self, keymap): def standardize_keymap(self, keymap):
"""Given a keymap, returns the keymap standardized.""" """Given a keymap, returns the keymap standardized."""
full = {} full = {}
for i in keymap: for i in keymap:
answer = "" answer = ""
new = self.standardize_key(keymap[i]) new = self.standardize_key(keymap[i])
for (c, j) in enumerate(new): for (c, j) in enumerate(new):
if c < len(new)-1: if c < len(new)-1:
answer = "%s%s+" % (answer, j) answer = "%s%s+" % (answer, j)
else: else:
answer = "%s%s" % (answer, j) answer = "%s%s" % (answer, j)
full[i] = answer full[i] = answer
return full return full

View File

@@ -12,45 +12,45 @@ kEventHotKeyReleasedSubtype = 9
class OSXKeyboardHandler(KeyboardHandler): class OSXKeyboardHandler(KeyboardHandler):
def __init__(self): def __init__(self):
super(OSXKeyboardHandler, self).__init__() super(OSXKeyboardHandler, self).__init__()
self.replacement_keys = dict() self.replacement_keys = dict()
self.app = KeyboardCapturingNSApplication.alloc().init() self.app = KeyboardCapturingNSApplication.alloc().init()
self._event_thread = Thread(target=AppHelper.runEventLoop) self._event_thread = Thread(target=AppHelper.runEventLoop)
self._event_thread.start() self._event_thread.start()
def register_key (self, key, function): def register_key (self, key, function):
super(OSXKeyboardHandler, self).register_key(key, function) super(OSXKeyboardHandler, self).register_key(key, function)
k, m = self.parse_key(key) k, m = self.parse_key(key)
key_id = RegisterEventHotKey(k, m, (0, 0), GetApplicationEventTarget(), 0) key_id = RegisterEventHotKey(k, m, (0, 0), GetApplicationEventTarget(), 0)
self.key_ids[key] = key_id self.key_ids[key] = key_id
def unregister_key (self, key, function): def unregister_key (self, key, function):
super(OSXKeyboardHandler, self).unregister_key(key, function) super(OSXKeyboardHandler, self).unregister_key(key, function)
key_id = self.key_ids[key] key_id = self.key_ids[key]
raise NotImplementedError raise NotImplementedError
def parse_key (self, key): def parse_key (self, key):
key=key.split("+") key=key.split("+")
#replacements #replacements
#Modifier keys: #Modifier keys:
for index, item in enumerate(key[0:-1]): for index, item in enumerate(key[0:-1]):
if self.replacement_mods.has_key(item): if self.replacement_mods.has_key(item):
key[index] = self.replacement_mods[item] key[index] = self.replacement_mods[item]
if self.replacement_keys.has_key(key[-1]): if self.replacement_keys.has_key(key[-1]):
key[-1] = self.replacement_keys[key[-1]] key[-1] = self.replacement_keys[key[-1]]
elif len(key[-1])==1: elif len(key[-1])==1:
key[-1] = ord(str(key[-1]))-36 key[-1] = ord(str(key[-1]))-36
mods = 0 mods = 0
for i in key[:-1]: for i in key[:-1]:
mods = mods|i mods = mods|i
return [key[-1], mods] return [key[-1], mods]
class KeyboardCapturingNSApplication(NSApplication): class KeyboardCapturingNSApplication(NSApplication):
def sendEvent_(self, theEvent): def sendEvent_(self, theEvent):
if theEvent.type() == NSSystemDefined and theEvent.subtype() == kEventHotKeyPressedSubtype: if theEvent.type() == NSSystemDefined and theEvent.subtype() == kEventHotKeyPressedSubtype:
self.activateIgnoringOtherApps_(True) self.activateIgnoringOtherApps_(True)
NSRunAlertPanel(u'Hot Key Pressed', u'Hot Key Pressed', None, None, None) NSRunAlertPanel(u'Hot Key Pressed', u'Hot Key Pressed', None, None, None)
super(NSApplication, self).sendEvent_(theEvent) super(NSApplication, self).sendEvent_(theEvent)

View File

@@ -5,36 +5,36 @@ from main import KeyboardHandler
class WindowsKeyboardHandler(KeyboardHandler): class WindowsKeyboardHandler(KeyboardHandler):
def __init__ (self, *args, **kwargs): def __init__ (self, *args, **kwargs):
super(WindowsKeyboardHandler, self).__init__(*args, **kwargs) super(WindowsKeyboardHandler, self).__init__(*args, **kwargs)
#Setup the replacement dictionaries. #Setup the replacement dictionaries.
for i in dir(win32con): for i in dir(win32con):
if i.startswith("VK_"): if i.startswith("VK_"):
key = i[3:].lower() key = i[3:].lower()
self.replacement_keys[key] = getattr(win32con, i) self.replacement_keys[key] = getattr(win32con, i)
elif i.startswith("MOD_"): elif i.startswith("MOD_"):
key = i[4:].lower() key = i[4:].lower()
self.replacement_mods[key] = getattr(win32con, i) self.replacement_mods[key] = getattr(win32con, i)
self.replacement_keys .update(dict(pageup=win32con.VK_PRIOR, pagedown=win32con.VK_NEXT)) self.replacement_keys .update(dict(pageup=win32con.VK_PRIOR, pagedown=win32con.VK_NEXT))
def parse_key (self, keystroke, separator="+"): def parse_key (self, keystroke, separator="+"):
keystroke = str(keystroke) #We don't want unicode keystroke = str(keystroke) #We don't want unicode
keystroke = [self.keycode_from_key(i) for i in keystroke.split(separator)] keystroke = [self.keycode_from_key(i) for i in keystroke.split(separator)]
mods = 0 mods = 0
for i in keystroke[:-1]: for i in keystroke[:-1]:
mods = mods | i #or everything together mods = mods | i #or everything together
return (mods, keystroke[-1]) return (mods, keystroke[-1])
def keycode_from_key(self, key): def keycode_from_key(self, key):
if key in self.replacement_mods: if key in self.replacement_mods:
return self.replacement_mods[key] return self.replacement_mods[key]
if key in self.replacement_keys: if key in self.replacement_keys:
return self.replacement_keys[key] return self.replacement_keys[key]
if len(key) == 1: if len(key) == 1:
return win32api.VkKeyScanEx(key, win32api.GetKeyboardLayout()) return win32api.VkKeyScanEx(key, win32api.GetKeyboardLayout())
def is_key_pressed(self, key): 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.""" """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) key = self.keycode_from_key(key)
return win32api.GetAsyncKeyState(key) return win32api.GetAsyncKeyState(key)

View File

@@ -10,121 +10,121 @@ from . import key_constants
__all__ = ['WXKeyboardHandler', 'WXControlKeyboardHandler'] __all__ = ['WXKeyboardHandler', 'WXControlKeyboardHandler']
def call_after(func): def call_after(func):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
wx.CallAfter(func, *args, **kwargs) wx.CallAfter(func, *args, **kwargs)
functools.update_wrapper(wrapper, func) functools.update_wrapper(wrapper, func)
return wrapper return wrapper
class BaseWXKeyboardHandler(KeyboardHandler): class BaseWXKeyboardHandler(KeyboardHandler):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(BaseWXKeyboardHandler, self).__init__(*args, **kwargs) super(BaseWXKeyboardHandler, self).__init__(*args, **kwargs)
#Setup the replacement dictionaries. #Setup the replacement dictionaries.
for i in dir(wx): for i in dir(wx):
if i.startswith('WXK_'): if i.startswith('WXK_'):
key = i[4:].lower() key = i[4:].lower()
self.replacement_keys[key] = getattr(wx, i) self.replacement_keys[key] = getattr(wx, i)
elif i.startswith('MOD_'): elif i.startswith('MOD_'):
key = i[4:].lower() key = i[4:].lower()
self.replacement_mods[key] = getattr(wx, i) self.replacement_mods[key] = getattr(wx, i)
def parse_key (self, keystroke, separator="+"): def parse_key (self, keystroke, separator="+"):
keystroke = [self.keycode_from_key(i) for i in keystroke.split(separator)] keystroke = [self.keycode_from_key(i) for i in keystroke.split(separator)]
mods = 0 mods = 0
for i in keystroke[:-1]: for i in keystroke[:-1]:
mods = mods | i #or everything together mods = mods | i #or everything together
return (mods, keystroke[-1]) return (mods, keystroke[-1])
def keycode_from_key(self, key):
result = None
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())
if result is None:
raise KeyboardHandlerError("Could not translate key %r into a valid keycode." % key)
return result
def keycode_from_key(self, key):
result = None
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())
if result is None:
raise KeyboardHandlerError("Could not translate key %r into a valid keycode." % key)
return result
class WXKeyboardHandler(BaseWXKeyboardHandler): class WXKeyboardHandler(BaseWXKeyboardHandler):
def __init__ (self, parent, *args, **kwargs): def __init__ (self, parent, *args, **kwargs):
super(WXKeyboardHandler, self).__init__(*args, **kwargs) super(WXKeyboardHandler, self).__init__(*args, **kwargs)
self.parent = parent self.parent = parent
self.key_ids = {} self.key_ids = {}
self.replacement_keys = key_constants.keys self.replacement_keys = key_constants.keys
self.replacement_mods = key_constants.modifiers self.replacement_mods = key_constants.modifiers
@call_after @call_after
def register_key(self, key, function): def register_key(self, key, function):
super(WXKeyboardHandler, self).register_key(key, function) super(WXKeyboardHandler, self).register_key(key, function)
key_id = wx.NewId() key_id = wx.NewId()
parsed = self.parse_key(key) parsed = self.parse_key(key)
res = self.parent.RegisterHotKey(key_id, *parsed) res = self.parent.RegisterHotKey(key_id, *parsed)
if not res: if not res:
logger.warn("Failed to register hotkey: %s for function %r", key, function) logger.warn("Failed to register hotkey: %s for function %r", key, function)
self.parent.Bind(wx.EVT_HOTKEY, lambda evt: self.process_key(evt, key_id), id=key_id) self.parent.Bind(wx.EVT_HOTKEY, lambda evt: self.process_key(evt, key_id), id=key_id)
self.key_ids[key] = key_id self.key_ids[key] = key_id
return res return res
def parse_key (self, keystroke, separator="+"): def parse_key (self, keystroke, separator="+"):
keystroke = str(keystroke) #We don't want unicode keystroke = str(keystroke) #We don't want unicode
keystroke = [self.keycode_from_key(i) for i in keystroke.split(separator)] keystroke = [self.keycode_from_key(i) for i in keystroke.split(separator)]
mods = 0 mods = 0
for i in keystroke[:-1]: for i in keystroke[:-1]:
mods = mods | i #or everything together mods = mods | i #or everything together
return (mods, keystroke[-1]) return (mods, keystroke[-1])
@call_after @call_after
def unregister_key (self, key, function): def unregister_key (self, key, function):
super(WXKeyboardHandler, self).unregister_key(key, function) super(WXKeyboardHandler, self).unregister_key(key, function)
if key not in self.key_ids: if key not in self.key_ids:
return #there's nothing we can do. return #there's nothing we can do.
key_id = self.key_ids[key] key_id = self.key_ids[key]
self.parent.UnregisterHotKey(key_id) self.parent.UnregisterHotKey(key_id)
self.parent.Unbind( wx.EVT_HOTKEY, id=key_id) self.parent.Unbind( wx.EVT_HOTKEY, id=key_id)
self.key_ids.pop(key) self.key_ids.pop(key)
def process_key (self, evt, id): def process_key (self, evt, id):
evt.Skip() evt.Skip()
key_ids = self.key_ids.keys() key_ids = self.key_ids.keys()
for i in key_ids: for i in key_ids:
if self.key_ids.get(i) == id: if self.key_ids.get(i) == id:
self.handle_key(i) self.handle_key(i)
class WXControlKeyboardHandler(wx.StaticText, KeyboardHandler): class WXControlKeyboardHandler(wx.StaticText, KeyboardHandler):
def __init__(self, parent=None, *a, **k): def __init__(self, parent=None, *a, **k):
wx.StaticText.__init__(self, parent=parent) wx.StaticText.__init__(self, parent=parent)
KeyboardHandler.__init__(self, *a, **k) KeyboardHandler.__init__(self, *a, **k)
self.wx_replacements = {} self.wx_replacements = {}
for i in [d for d in dir(wx) if d.startswith('WXK_')]: for i in [d for d in dir(wx) if d.startswith('WXK_')]:
self.wx_replacements[getattr(wx, i)] = i[4:].lower() self.wx_replacements[getattr(wx, i)] = i[4:].lower()
self.Bind(wx.EVT_KEY_DOWN, self.process_key, self) self.Bind(wx.EVT_KEY_DOWN, self.process_key, self)
self.SetFocus() self.SetFocus()
def process_key(self, evt): def process_key(self, evt):
keycode = evt.GetKeyCode() keycode = evt.GetKeyCode()
keyname = self.wx_replacements.get(keycode, None) keyname = self.wx_replacements.get(keycode, None)
modifiers = "" modifiers = ""
replacements = ( (evt.ControlDown(), 'control+'), replacements = ( (evt.ControlDown(), 'control+'),
(evt.AltDown(), 'alt+'), (evt.AltDown(), 'alt+'),
(evt.ShiftDown(), 'shift+'), (evt.ShiftDown(), 'shift+'),
(evt.MetaDown(), 'win+') (evt.MetaDown(), 'win+')
) )
for mod, ch in (replacements): for mod, ch in (replacements):
if mod: if mod:
modifiers += ch modifiers += ch
if keyname is None: if keyname is None:
if 27 < keycode < 256: if 27 < keycode < 256:
keyname = chr(keycode).lower() keyname = chr(keycode).lower()
else: else:
keyname = "(%s)unknown" % keycode keyname = "(%s)unknown" % keycode
key = modifiers + keyname key = modifiers + keyname
self.handle_key(key) self.handle_key(key)

View File

@@ -34,4 +34,5 @@ configuration = string(default="control+win+o")
accountConfiguration = string(default="control+win+shift+o") accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+win+shift+u") update_buffer = string(default="control+win+shift+u")
ocr_image = string(default="win+alt+o") ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="")

View File

@@ -53,4 +53,5 @@ configuration = string(default="control+win+o")
accountConfiguration = string(default="control+win+shift+o") accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+win+shift+u") update_buffer = string(default="control+win+shift+u")
ocr_image = string(default="win+alt+o") ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="")

View File

@@ -54,4 +54,5 @@ configuration = string(default="control+win+alt+o")
accountConfiguration = string(default="control+win+shift+o") accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+alt+shift+u") update_buffer = string(default="control+alt+shift+u")
ocr_image = string(default="win+alt+o") ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="")

View File

@@ -0,0 +1,58 @@
[info]
name = string(default="Windows 11")
desc = string(default="A keymap with remapped modifiers for Windows 11 compatibility.")
author = string(default="Bill Jesús <galorasd@gmail.com>")
[keymap]
up = string(default="control+alt+win+up")
down = string(default="control+alt+win+down")
left = string(default="control+alt+win+left")
right = string(default="control+alt+win+right")
next_account = string(default="control+alt+win+shift+right")
previous_account = string(default="control+alt+win+shift+left")
open_conversation = string(default="control+alt+win+c")
show_hide = string(default="control+win+w")
post_tweet = string(default="alt+win+n")
post_reply = string(default="control+win+r")
post_retweet = string(default="alt+win+shift+r")
send_dm = string(default="alt+win+shift+d")
toggle_like = string(default="control+alt+win+f")
follow = string(default="alt+win+shift+s")
user_details = string(default="alt+win+shift+n")
view_item = string(default="alt+win+v")
exit = string(default="alt+win+f4")
open_timeline = string(default="alt+win+i")
remove_buffer = string(default="alt+win+shift+i")
url = string(default="alt+win+return")
audio = string(default="alt+shift+win+return")
volume_up = string(default="control+alt+win+shift+up")
go_home = string(default="control+alt+win+home")
volume_down = string(default="control+alt+win+shift+down")
go_end = string(default="control+alt+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="alt+win+delete")
clear_buffer = string(default="alt+win+shift+delete")
repeat_item = string(default="control+alt+win+space")
copy_to_clipboard = string(default="alt+win+shift+c")
add_to_list = string(default="alt+win+a")
remove_from_list = string(default="alt+win+shift+a")
toggle_buffer_mute = string(default="alt+win+shift+m")
toggle_session_mute = string(default="control+alt+win+m")
toggle_autoread = string(default="alt+win+e")
search = string(default="alt+win+-")
edit_keystrokes = string(default="alt+win+k")
view_user_lists = string(default="alt+win+l")
get_more_items = string(default="alt+win+pageup")
reverse_geocode = string(default="control+win+g")
view_reverse_geocode = string(default="alt+win+shift+g")
get_trending_topics = string(default="control+win+t")
check_for_updates = string(default="alt+win+u")
list_manager = string(default="alt+win+shift+l")
configuration = string(default="control+win+alt+o")
accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+alt+shift+u")
ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="")

View File

@@ -55,4 +55,5 @@ list_manager = string(default="control+win+shift+l")
configuration = string(default="control+win+o") configuration = string(default="control+win+o")
accountConfiguration = string(default="control+win+shift+o") accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+win+shift+u") update_buffer = string(default="control+win+shift+u")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="")

View File

@@ -56,4 +56,5 @@ configuration = string(default="control+win+o")
accountConfiguration = string(default="control+win+shift+o") accountConfiguration = string(default="control+win+shift+o")
update_buffer = string(default="control+win+shift+u") update_buffer = string(default="control+win+shift+u")
ocr_image = string(default="win+alt+o") ocr_image = string(default="win+alt+o")
open_in_browser = string(default="alt+control+win+return") open_in_browser = string(default="alt+control+win+return")
add_alias=string(default="")

View File

@@ -14,9 +14,9 @@ import paths
# lib = load_library("snapshot_api_keys64", x64_path=paths.app_path("keys/lib")) # lib = load_library("snapshot_api_keys64", x64_path=paths.app_path("keys/lib"))
#else: #else:
if platform.architecture()[0][:2] == "32": if platform.architecture()[0][:2] == "32":
lib = load_library("stable_api_keys32", x86_path=os.path.join(paths.app_path(), "keys", "lib")) lib = load_library("stable_api_keys32", x86_path=os.path.join(paths.app_path(), "keys", "lib"))
else: else:
lib = load_library("stable_api_keys64", x64_path=os.path.join(paths.app_path(), "keys", "lib")) lib = load_library("stable_api_keys64", x64_path=os.path.join(paths.app_path(), "keys", "lib"))
# import linuxKeys # import linuxKeys
# lib = linuxKeys # lib = linuxKeys
@@ -24,20 +24,20 @@ else:
keyring = None keyring = None
def setup(): def setup():
global keyring global keyring
if keyring == None: if keyring == None:
keyring = Keyring() keyring = Keyring()
class Keyring(object): class Keyring(object):
def __init__(self): def __init__(self):
super(Keyring, self).__init__() super(Keyring, self).__init__()
def _call_method(self, function): def _call_method(self, function):
result = getattr(lib, function) result = getattr(lib, function)
result = c_char_p(result.__call__()) result = c_char_p(result.__call__())
return result.value return result.value
def get(self, func): def get(self, func):
if hasattr(application,func+"_override"): if hasattr(application,func+"_override"):
return getattr(application,func+'_override') return getattr(application,func+'_override')
return getattr(self, "_call_method")("get_"+func) return getattr(self, "_call_method")("get_"+func)

View File

@@ -1,9 +1,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
def get_api_key(): def get_api_key():
return "8pDLbyOW3saYnvSZ4uLFg\0" return "8pDLbyOW3saYnvSZ4uLFg\0"
def get_api_secret(): def get_api_secret():
return "YsgdrzY9B4yyYvYsyee78rKI3GshjHpenVS9LnFJXY\0"; return "YsgdrzY9B4yyYvYsyee78rKI3GshjHpenVS9LnFJXY\0";
#char *get_dropbox_api_key(){ #char *get_dropbox_api_key(){
#return "key\0"; #return "key\0";

View File

@@ -1,3 +1,3 @@
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
from .keystrokeEditor import KeystrokeEditor from .keystrokeEditor import KeystrokeEditor

View File

@@ -1,60 +1,61 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
actions = { actions = {
"up": _(u"Go up in the current buffer"), "up": _(u"Go up in the current buffer"),
"down": _(u"Go down in the current buffer"), "down": _(u"Go down in the current buffer"),
"left": _(u"Go to the previous buffer"), "left": _(u"Go to the previous buffer"),
"right": _(u"Go to the next buffer"), "right": _(u"Go to the next buffer"),
"next_account": _(u"Focus the next session"), "next_account": _(u"Focus the next session"),
"previous_account": _(u"Focus the previous session"), "previous_account": _(u"Focus the previous session"),
"show_hide": _(u"Show or hide the GUI"), "show_hide": _(u"Show or hide the GUI"),
"post_tweet": _(u"New tweet"), "post_tweet": _(u"New tweet"),
"post_reply": _(u"Reply"), "post_reply": _(u"Reply"),
"post_retweet": _(u"Retweet"), "post_retweet": _(u"Retweet"),
"send_dm": _(u"Send direct message"), "send_dm": _(u"Send direct message"),
"add_to_favourites": _(u"Like a tweet"), "add_to_favourites": _(u"Like a tweet"),
"toggle_like": _(u"Like/unlike a tweet"), "toggle_like": _(u"Like/unlike a tweet"),
"remove_from_favourites": _(u"Unlike a tweet"), "remove_from_favourites": _(u"Unlike a tweet"),
"follow": _(u"Open the user actions dialogue"), "follow": _(u"Open the user actions dialogue"),
"user_details": _(u"See user details"), "user_details": _(u"See user details"),
"view_item": _(u"Show tweet"), "view_item": _(u"Show tweet"),
"exit": _(u"Quit"), "exit": _(u"Quit"),
"open_timeline": _(u"Open user timeline"), "open_timeline": _(u"Open user timeline"),
"remove_buffer": _(u"Destroy buffer"), "remove_buffer": _(u"Destroy buffer"),
"interact": _(u"Interact with the currently focused tweet."), "interact": _(u"Interact with the currently focused tweet."),
"url": _(u"Open URL"), "url": _(u"Open URL"),
"open_in_browser": _(u"View in Twitter"), "open_in_browser": _(u"View in Twitter"),
"volume_up": _(u"Increase volume by 5%"), "volume_up": _(u"Increase volume by 5%"),
"volume_down": _(u"Decrease volume by 5%"), "volume_down": _(u"Decrease volume by 5%"),
"go_home": _(u"Jump to the first element of a buffer"), "go_home": _(u"Jump to the first element of a buffer"),
"go_end": _(u"Jump to the last element of the current buffer"), "go_end": _(u"Jump to the last element of the current buffer"),
"go_page_up": _(u"Jump 20 elements up in the current buffer"), "go_page_up": _(u"Jump 20 elements up in the current buffer"),
"go_page_down": _(u"Jump 20 elements down in the current buffer"), "go_page_down": _(u"Jump 20 elements down in the current buffer"),
"update_profile": _(u"Edit profile"), "update_profile": _(u"Edit profile"),
"delete": _(u"Delete a tweet or direct message"), "delete": _(u"Delete a tweet or direct message"),
"clear_buffer": _(u"Empty the current buffer"), "clear_buffer": _(u"Empty the current buffer"),
"repeat_item": _(u"Repeat last item"), "repeat_item": _(u"Repeat last item"),
"copy_to_clipboard": _(u"Copy to clipboard"), "copy_to_clipboard": _(u"Copy to clipboard"),
"add_to_list": _(u"Add to list"), "add_to_list": _(u"Add to list"),
"remove_from_list": _(u"Remove from list"), "remove_from_list": _(u"Remove from list"),
"toggle_buffer_mute": _(u"Mute/unmute the active buffer"), "toggle_buffer_mute": _(u"Mute/unmute the active buffer"),
"toggle_session_mute": _(u"Mute/unmute the current session"), "toggle_session_mute": _(u"Mute/unmute the current session"),
"toggle_autoread": _(u"toggle the automatic reading of incoming tweets in the active buffer"), "toggle_autoread": _(u"toggle the automatic reading of incoming tweets in the active buffer"),
"search": _(u"Search on twitter"), "search": _(u"Search on twitter"),
"find": _(u"Find a string in the currently focused buffer"), "find": _(u"Find a string in the currently focused buffer"),
"edit_keystrokes": _(u"Show the keystroke editor"), "edit_keystrokes": _(u"Show the keystroke editor"),
"view_user_lists": _(u"Show lists for a specified user"), "view_user_lists": _(u"Show lists for a specified user"),
"get_more_items": _(u"load previous items"), "get_more_items": _(u"load previous items"),
"reverse_geocode": _(u"Get geolocation"), "reverse_geocode": _(u"Get geolocation"),
"view_reverse_geocode": _(u"Display the tweet's geolocation in a dialog"), "view_reverse_geocode": _(u"Display the tweet's geolocation in a dialog"),
"get_trending_topics": _(u"Create a trending topics buffer"), "get_trending_topics": _(u"Create a trending topics buffer"),
"open_conversation": _(u"View conversation"), "open_conversation": _(u"View conversation"),
"check_for_updates": _(u"Check and download updates"), "check_for_updates": _(u"Check and download updates"),
"lists_manager": _(u"Opens the list manager, which allows you to create, edit, delete and open lists in buffers."), "lists_manager": _(u"Opens the list manager, which allows you to create, edit, delete and open lists in buffers."),
"configuration": _(u"Opens the global settings dialogue"), "configuration": _(u"Opens the global settings dialogue"),
"list_manager": _(u"Opens the list manager"), "list_manager": _(u"Opens the list manager"),
"accountConfiguration": _(u"Opens the account settings dialogue"), "accountConfiguration": _(u"Opens the account settings dialogue"),
"audio": _(u"Try to play an audio file"), "audio": _(u"Try to play an audio file"),
"update_buffer": _(u"Updates the buffer and retrieves possible lost items there."), "update_buffer": _(u"Updates the buffer and retrieves possible lost items there."),
"ocr_image": _(u"Extracts the text from a picture and displays the result in a dialog."), "ocr_image": _(u"Extracts the text from a picture and displays the result in a dialog."),
} "add_alias": _("Adds an alias to an user"),
}

View File

@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
from builtins import object
import widgetUtils import widgetUtils
import config import config
from . import wx_ui from . import wx_ui
@@ -9,53 +6,65 @@ from . import constants
from pubsub import pub from pubsub import pub
class KeystrokeEditor(object): class KeystrokeEditor(object):
def __init__(self): def __init__(self):
super(KeystrokeEditor, self).__init__() super(KeystrokeEditor, self).__init__()
self.changed = False # Change it if the keyboard shorcuts are reassigned. self.changed = False # Change it if the keyboard shorcuts are reassigned.
self.dialog = wx_ui.keystrokeEditorDialog() self.dialog = wx_ui.keystrokeEditorDialog()
self.map = config.keymap["keymap"] self.map = config.keymap["keymap"]
# we need to copy the keymap before modify it, for unregistering the old keystrokes if is needed. # we need to copy the keymap before modify it, for unregistering the old keystrokes if is needed.
self.hold_map = self.map.copy() self.hold_map = self.map.copy()
self.dialog.put_keystrokes(constants.actions, self.map) self.dialog.put_keystrokes(constants.actions, self.map)
widgetUtils.connect_event(self.dialog.edit, widgetUtils.BUTTON_PRESSED, self.edit_keystroke) widgetUtils.connect_event(self.dialog.edit, widgetUtils.BUTTON_PRESSED, self.edit_keystroke)
widgetUtils.connect_event(self.dialog.execute, widgetUtils.BUTTON_PRESSED, self.execute_action) widgetUtils.connect_event(self.dialog.undefine, widgetUtils.BUTTON_PRESSED, self.undefine_keystroke)
self.dialog.get_response() widgetUtils.connect_event(self.dialog.execute, widgetUtils.BUTTON_PRESSED, self.execute_action)
self.dialog.get_response()
def edit_keystroke(self, *args, **kwargs): def edit_keystroke(self, *args, **kwargs):
action = self.dialog.actions[self.dialog.get_action()] action = self.dialog.actions[self.dialog.get_action()]
edit_dialog = wx_ui.editKeystrokeDialog() edit_dialog = wx_ui.editKeystrokeDialog()
self.set_keystroke(self.map[action], edit_dialog) self.set_keystroke(self.map[action], edit_dialog)
answer = edit_dialog.get_response() answer = edit_dialog.get_response()
if answer == widgetUtils.OK: if answer == widgetUtils.OK:
new_keystroke = self.get_edited_keystroke(edit_dialog) new_keystroke = self.get_edited_keystroke(edit_dialog)
if new_keystroke != self.map[action]: if new_keystroke != self.map[action]:
self.changed = True self.changed = True
self.map[action] = new_keystroke self.map[action] = new_keystroke
self.dialog.put_keystrokes(constants.actions, self.map) self.dialog.put_keystrokes(constants.actions, self.map)
def set_keystroke(self, keystroke, dialog): def undefine_keystroke(self, *args, **kwargs):
for i in keystroke.split("+"): action = self.dialog.actions[self.dialog.get_action()]
if hasattr(dialog, i): keystroke = self.map.get(action)
dialog.set(i, True) if keystroke == None:
dialog.set("key", keystroke.split("+")[-1]) return
answer = self.dialog.undefine_keystroke_confirmation()
if answer == widgetUtils.YES:
self.map[action] = ""
self.changed = True
self.dialog.put_keystrokes(constants.actions, self.map)
def get_edited_keystroke(self, dialog): def set_keystroke(self, keystroke, dialog):
keys = [] for i in keystroke.split("+"):
if dialog.get("control") == True: if hasattr(dialog, i):
keys.append("control") dialog.set(i, True)
if dialog.get("win") == True: dialog.set("key", keystroke.split("+")[-1])
keys.append("win")
if dialog.get("alt") == True:
keys.append("alt")
if dialog.get("shift") == True:
keys.append("shift")
if dialog.get("key") != "":
keys.append(dialog.get("key"))
else:
wx_ui.no_key()
return
return "+".join(keys)
def execute_action(self, *args, **kwargs): def get_edited_keystroke(self, dialog):
action = self.dialog.actions[self.dialog.get_action()] keys = []
pub.sendMessage("execute-action", action=action) if dialog.get("control") == True:
keys.append("control")
if dialog.get("win") == True:
keys.append("win")
if dialog.get("alt") == True:
keys.append("alt")
if dialog.get("shift") == True:
keys.append("shift")
if dialog.get("key") != "":
keys.append(dialog.get("key"))
else:
wx_ui.no_key()
return
return "+".join(keys)
def execute_action(self, *args, **kwargs):
action = self.dialog.actions[self.dialog.get_action()]
pub.sendMessage("execute-action", action=action)

View File

@@ -1,82 +1,87 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import wx import wx
from multiplatform_widgets import widgets from multiplatform_widgets import widgets
from wxUI.dialogs import baseDialog from wxUI.dialogs import baseDialog
class keystrokeEditorDialog(baseDialog.BaseWXDialog): class keystrokeEditorDialog(baseDialog.BaseWXDialog):
def __init__(self): def __init__(self):
super(keystrokeEditorDialog, self).__init__(parent=None, id=-1, title=_(u"Keystroke editor")) super(keystrokeEditorDialog, self).__init__(parent=None, id=-1, title=_(u"Keystroke editor"))
panel = wx.Panel(self) panel = wx.Panel(self)
self.actions = [] self.actions = []
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
keysText = wx.StaticText(panel, -1, _(u"Select a keystroke to edit")) 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 = widgets.list(self, _(u"Action"), _(u"Keystroke"), style=wx.LC_REPORT|wx.LC_SINGLE_SEL, size=(400, 450))
self.keys.list.SetFocus() self.keys.list.SetFocus()
firstSizer = wx.BoxSizer(wx.HORIZONTAL) firstSizer = wx.BoxSizer(wx.HORIZONTAL)
firstSizer.Add(keysText, 0, wx.ALL, 5) firstSizer.Add(keysText, 0, wx.ALL, 5)
firstSizer.Add(self.keys.list, 0, wx.ALL, 5) firstSizer.Add(self.keys.list, 0, wx.ALL, 5)
self.edit = wx.Button(panel, -1, _(u"Edit")) self.edit = wx.Button(panel, -1, _(u"Edit"))
self.edit.SetDefault() self.edit.SetDefault()
self.execute = wx.Button(panel, -1, _(u"Execute action")) self.undefine = wx.Button(panel, -1, _("Undefine keystroke"))
close = wx.Button(panel, wx.ID_CANCEL, _(u"Close")) self.execute = wx.Button(panel, -1, _(u"Execute action"))
secondSizer = wx.BoxSizer(wx.HORIZONTAL) close = wx.Button(panel, wx.ID_CANCEL, _(u"Close"))
secondSizer.Add(self.edit, 0, wx.ALL, 5) secondSizer = wx.BoxSizer(wx.HORIZONTAL)
secondSizer.Add(self.execute, 0, wx.ALL, 5) secondSizer.Add(self.edit, 0, wx.ALL, 5)
secondSizer.Add(close, 0, wx.ALL, 5) secondSizer.Add(self.execute, 0, wx.ALL, 5)
sizer.Add(firstSizer, 0, wx.ALL, 5) secondSizer.Add(close, 0, wx.ALL, 5)
sizer.Add(secondSizer, 0, wx.ALL, 5) sizer.Add(firstSizer, 0, wx.ALL, 5)
panel.SetSizer(sizer) sizer.Add(secondSizer, 0, wx.ALL, 5)
self.SetClientSize(sizer.CalcMin()) panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def put_keystrokes(self, actions, keystrokes): def put_keystrokes(self, actions, keystrokes):
selection = self.keys.get_selected() selection = self.keys.get_selected()
self.keys.clear() self.keys.clear()
for i in keystrokes: for i in keystrokes:
if (i in actions) == False: if (i in actions) == False:
continue continue
action = actions[i] action = actions[i]
self.actions.append(i) self.actions.append(i)
keystroke = keystrokes[i] keystroke = keystrokes.get(i)
self.keys.insert_item(False, *[action, keystroke]) if keystroke == "":
self.keys.select_item(selection) keystroke = _("Undefined")
self.keys.insert_item(False, *[action, keystroke])
self.keys.select_item(selection)
def get_action(self): def get_action(self):
return self.keys.get_selected() return self.keys.get_selected()
def undefine_keystroke_confirmation(self):
return wx.MessageDialog(self, _("Are you sure you want to undefine this keystroke?"), _("Undefine keystroke"), wx.YES_NO|wx.CANCEL|wx.ICON_QUESTION).ShowModal()
class editKeystrokeDialog(baseDialog.BaseWXDialog): class editKeystrokeDialog(baseDialog.BaseWXDialog):
def __init__(self): def __init__(self):
super(editKeystrokeDialog, self).__init__(parent=None, id=-1, title=_(u"Editing keystroke")) super(editKeystrokeDialog, self).__init__(parent=None, id=-1, title=_(u"Editing keystroke"))
panel = wx.Panel(self) panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
self.control = wx.CheckBox(panel, -1, _(u"Control")) self.control = wx.CheckBox(panel, -1, _(u"Control"))
self.alt = wx.CheckBox(panel, -1, _(u"Alt")) self.alt = wx.CheckBox(panel, -1, _(u"Alt"))
self.shift = wx.CheckBox(panel, -1, _(u"Shift")) self.shift = wx.CheckBox(panel, -1, _(u"Shift"))
self.win = wx.CheckBox(panel, -1, _(u"Windows")) self.win = wx.CheckBox(panel, -1, _(u"Windows"))
sizer1 = wx.BoxSizer(wx.HORIZONTAL) sizer1 = wx.BoxSizer(wx.HORIZONTAL)
sizer1.Add(self.control) sizer1.Add(self.control)
sizer1.Add(self.alt) sizer1.Add(self.alt)
sizer1.Add(self.shift) sizer1.Add(self.shift)
sizer1.Add(self.win) sizer1.Add(self.win)
charLabel = wx.StaticText(panel, -1, _(u"Key")) charLabel = wx.StaticText(panel, -1, _(u"Key"))
self.key = wx.TextCtrl(panel, -1) self.key = wx.TextCtrl(panel, -1)
sizer2 = wx.BoxSizer(wx.HORIZONTAL) sizer2 = wx.BoxSizer(wx.HORIZONTAL)
sizer2.Add(charLabel) sizer2.Add(charLabel)
sizer2.Add(self.key) sizer2.Add(self.key)
ok = wx.Button(panel, wx.ID_OK, _(u"OK")) ok = wx.Button(panel, wx.ID_OK, _(u"OK"))
ok.SetDefault() ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL) cancel = wx.Button(panel, wx.ID_CANCEL)
sizer3 = wx.BoxSizer(wx.HORIZONTAL) sizer3 = wx.BoxSizer(wx.HORIZONTAL)
sizer3.Add(ok) sizer3.Add(ok)
sizer3.Add(cancel) sizer3.Add(cancel)
sizer.Add(sizer1) sizer.Add(sizer1)
sizer.Add(sizer2) sizer.Add(sizer2)
sizer.Add(sizer3) sizer.Add(sizer3)
panel.SetSizerAndFit(sizer) panel.SetSizerAndFit(sizer)
def no_win_message(): def no_win_message():
return wx.MessageDialog(None, _(u"You need to use the Windows key"), _(u"Invalid keystroke"), wx.OK|wx.ICON_ERROR).ShowModal() return wx.MessageDialog(None, _(u"You need to use the Windows key"), _(u"Invalid keystroke"), wx.OK|wx.ICON_ERROR).ShowModal()
def no_key(): def no_key():
return wx.MessageDialog(None, _(u"You must provide a character for the keystroke"), _(u"Invalid keystroke"), wx.ICON_ERROR).ShowModal() return wx.MessageDialog(None, _(u"You must provide a character for the keystroke"), _(u"Invalid keystroke"), wx.ICON_ERROR).ShowModal()

View File

@@ -12,7 +12,7 @@ import gettext
import paths import paths
import platform import platform
import application import application
#a few Windows locale constants #a few Windows locale constants
LOCALE_SLANGUAGE=0x2 LOCALE_SLANGUAGE=0x2
LOCALE_SLANGDISPLAYNAME=0x6f LOCALE_SLANGDISPLAYNAME=0x6f
@@ -20,195 +20,195 @@ LOCALE_SLANGDISPLAYNAME=0x6f
curLang="en" curLang="en"
def localeNameToWindowsLCID(localeName): def localeNameToWindowsLCID(localeName):
"""Retreave the Windows locale identifier (LCID) for the given locale name """Retreave the Windows locale identifier (LCID) for the given locale name
@param localeName: a string of 2letterLanguage_2letterCountry or or just 2letterLanguage @param localeName: a string of 2letterLanguage_2letterCountry or or just 2letterLanguage
@type localeName: string @type localeName: string
@returns: a Windows LCID @returns: a Windows LCID
@rtype: integer @rtype: integer
""" """
#Windows Vista is able to convert locale names to LCIDs #Windows Vista is able to convert locale names to LCIDs
func_LocaleNameToLCID=getattr(ctypes.windll.kernel32,'LocaleNameToLCID',None) func_LocaleNameToLCID=getattr(ctypes.windll.kernel32,'LocaleNameToLCID',None)
if func_LocaleNameToLCID is not None: if func_LocaleNameToLCID is not None:
localeName=localeName.replace('_','-') localeName=localeName.replace('_','-')
LCID=func_LocaleNameToLCID(str(localeName),0) LCID=func_LocaleNameToLCID(str(localeName),0)
else: #Windows doesn't have this functionality, manually search Python's windows_locale dictionary for the LCID else: #Windows doesn't have this functionality, manually search Python's windows_locale dictionary for the LCID
localeName=locale.normalize(localeName) localeName=locale.normalize(localeName)
if '.' in localeName: if '.' in localeName:
localeName=localeName.split('.')[0] localeName=localeName.split('.')[0]
LCList=[x[0] for x in locale.windows_locale.items() if x[1]==localeName] LCList=[x[0] for x in locale.windows_locale.items() if x[1]==localeName]
if len(LCList)>0: if len(LCList)>0:
LCID=LCList[0] LCID=LCList[0]
else: else:
LCID=0 LCID=0
return LCID return LCID
def getLanguageDescription(language): def getLanguageDescription(language):
"""Finds out the description (localized full name) of a given local name""" """Finds out the description (localized full name) of a given local name"""
desc=None desc=None
if platform.system() == "Windows": if platform.system() == "Windows":
LCID=localeNameToWindowsLCID(language) LCID=localeNameToWindowsLCID(language)
if LCID!=0: if LCID!=0:
buf=ctypes.create_unicode_buffer(1024) buf=ctypes.create_unicode_buffer(1024)
if '_' not in language: if '_' not in language:
res=ctypes.windll.kernel32.GetLocaleInfoW(LCID,LOCALE_SLANGDISPLAYNAME,buf,1024) res=ctypes.windll.kernel32.GetLocaleInfoW(LCID,LOCALE_SLANGDISPLAYNAME,buf,1024)
else: else:
res=0 res=0
if res==0: if res==0:
res=ctypes.windll.kernel32.GetLocaleInfoW(LCID,LOCALE_SLANGUAGE,buf,1024) res=ctypes.windll.kernel32.GetLocaleInfoW(LCID,LOCALE_SLANGUAGE,buf,1024)
desc=buf.value desc=buf.value
elif platform.system() == "Linux" or not desc: elif platform.system() == "Linux" or not desc:
desc={ desc={
"am":pgettext("languageName","Amharic"), "am":pgettext("languageName","Amharic"),
"an":pgettext("languageName","Aragonese"), "an":pgettext("languageName","Aragonese"),
"es":pgettext("languageName","Spanish"), "es":pgettext("languageName","Spanish"),
"pt":pgettext("languageName","Portuguese"), "pt":pgettext("languageName","Portuguese"),
"ru":pgettext("languageName","Russian"), "ru":pgettext("languageName","Russian"),
"it":pgettext("languageName","italian"), "it":pgettext("languageName","italian"),
"tr":pgettext("languageName","Turkey"), "tr":pgettext("languageName","Turkey"),
"gl":pgettext("languageName","Galician"), "gl":pgettext("languageName","Galician"),
"ca":pgettext("languageName","Catala"), "ca":pgettext("languageName","Catala"),
"eu":pgettext("languageName","Vasque"), "eu":pgettext("languageName","Vasque"),
"pl":pgettext("languageName","polish"), "pl":pgettext("languageName","polish"),
"ar":pgettext("languageName","Arabic"), "ar":pgettext("languageName","Arabic"),
"ne":pgettext("languageName","Nepali"), "ne":pgettext("languageName","Nepali"),
"sr":pgettext("languageName","Serbian (Latin)"), "sr":pgettext("languageName","Serbian (Latin)"),
"ja":pgettext("languageName","Japanese"), "ja":pgettext("languageName","Japanese"),
}.get(language,None) }.get(language,None)
return desc return desc
def getAvailableLanguages(): def getAvailableLanguages():
"""generates a list of locale names, plus their full localized language and country names. """generates a list of locale names, plus their full localized language and country names.
@rtype: list of tuples @rtype: list of tuples
""" """
#Make a list of all the locales found in NVDA's locale dir #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 os.listdir(paths.locale_path()) if not x.startswith('.')]
l=[x for x in l if os.path.isfile(os.path.join(paths.locale_path(), '%s/LC_MESSAGES/%s.po' % (x, application.short_name)))] l=[x for x in l if os.path.isfile(os.path.join(paths.locale_path(), '%s/LC_MESSAGES/%s.po' % (x, application.short_name)))]
#Make sure that en (english) is in the list as it may not have any locale files, but is default #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: if 'en' not in l:
l.append('en') l.append('en')
l.sort() l.sort()
#For each locale, ask Windows for its human readable display name #For each locale, ask Windows for its human readable display name
d=[] d=[]
for i in l: for i in l:
desc=getLanguageDescription(i) desc=getLanguageDescription(i)
label="%s, %s"%(desc,i) if desc else i label="%s, %s"%(desc,i) if desc else i
d.append(label) d.append(label)
#include a 'user default, windows' language, which just represents the default language for this user account #include a 'user default, windows' language, which just represents the default language for this user account
l.append("system") l.append("system")
# Translators: the label for the Windows default NVDA interface language. # Translators: the label for the Windows default NVDA interface language.
d.append(_("User default")) d.append(_("User default"))
#return a zipped up version of both the lists (a list with tuples of locale,label) #return a zipped up version of both the lists (a list with tuples of locale,label)
return list(zip(l,d)) return list(zip(l,d))
def makePgettext(translations): def makePgettext(translations):
"""Obtaina pgettext function for use with a gettext translations instance. """Obtaina pgettext function for use with a gettext translations instance.
pgettext is used to support message contexts, pgettext is used to support message contexts,
but Python 2.7's gettext module doesn't support this, but Python 2.7's gettext module doesn't support this,
so NVDA must provide its own implementation. so NVDA must provide its own implementation.
""" """
if isinstance(translations, gettext.GNUTranslations): if isinstance(translations, gettext.GNUTranslations):
def pgettext(context, message): def pgettext(context, message):
message = str(message) message = str(message)
try: try:
# Look up the message with its context. # Look up the message with its context.
return translations._catalog["%s\x04%s" % (context, message)] return translations._catalog["%s\x04%s" % (context, message)]
except KeyError: except KeyError:
return message return message
else: else:
def pgettext(context, message): def pgettext(context, message):
return str(message) return str(message)
return pgettext return pgettext
def setLanguage(lang): def setLanguage(lang):
system = platform.system() system = platform.system()
global curLang global curLang
try: try:
if lang=="system": if lang=="system":
if system == "Windows": if system == "Windows":
windowsLCID=ctypes.windll.kernel32.GetUserDefaultUILanguage() windowsLCID=ctypes.windll.kernel32.GetUserDefaultUILanguage()
localeName=locale.windows_locale[windowsLCID] localeName=locale.windows_locale[windowsLCID]
elif system == "Darwin": elif system == "Darwin":
import Foundation import Foundation
localeName = Foundation.NSLocale.currentLocale().identifier() localeName = Foundation.NSLocale.currentLocale().identifier()
elif system == "Linux": elif system == "Linux":
localeName = locale.getdefaultlocale()[0] localeName = locale.getdefaultlocale()[0]
trans=gettext.translation(application.short_name, localedir=paths.locale_path(), languages=[localeName]) trans=gettext.translation(application.short_name, localedir=paths.locale_path(), languages=[localeName])
curLang=localeName curLang=localeName
# else: # else:
# localeName=locale.getdefaultlocale()[0] # localeName=locale.getdefaultlocale()[0]
# trans=gettext.translation('twblue', localedir=paths.locale_path(), languages=[localeName]) # trans=gettext.translation('twblue', localedir=paths.locale_path(), languages=[localeName])
# curLang=localeName # curLang=localeName
else: else:
trans=gettext.translation(application.short_name, localedir=paths.locale_path(), languages=[lang]) trans=gettext.translation(application.short_name, localedir=paths.locale_path(), languages=[lang])
curLang=lang curLang=lang
localeChanged=False localeChanged=False
#Try setting Python's locale to lang #Try setting Python's locale to lang
# try: # try:
if system == "Windows": if system == "Windows":
locale.setlocale(locale.LC_ALL, langToWindowsLocale(lang)) locale.setlocale(locale.LC_ALL, langToWindowsLocale(lang))
localeChanged=True localeChanged=True
else: else:
locale.setlocale(locale.LC_ALL, lang) locale.setlocale(locale.LC_ALL, lang)
localeChanged=True localeChanged=True
# except: # except:
# pass # pass
if not localeChanged and '_' in lang: if not localeChanged and '_' in lang:
#Python couldn'tsupport the language_country locale, just try language. #Python couldn'tsupport the language_country locale, just try language.
try: try:
locale.setlocale(locale.LC_ALL, lang.split('_')[0]) locale.setlocale(locale.LC_ALL, lang.split('_')[0])
except: except:
pass pass
#Set the windows locale for this thread (NVDA core) to this locale. #Set the windows locale for this thread (NVDA core) to this locale.
if system == "Windows": if system == "Windows":
LCID=localeNameToWindowsLCID(lang) LCID=localeNameToWindowsLCID(lang)
ctypes.windll.kernel32.SetThreadLocale(LCID) ctypes.windll.kernel32.SetThreadLocale(LCID)
except IOError: except IOError:
trans=gettext.translation(application.short_name, fallback=True) trans=gettext.translation(application.short_name, fallback=True)
curLang="en" curLang="en"
if sys.version[0] == "3": if sys.version[0] == "3":
trans.install() trans.install()
else: else:
trans.install(unicode=True) trans.install(unicode=True)
# Install our pgettext function. # Install our pgettext function.
# __builtin__.__dict__["pgettext"] = makePgettext(trans) # __builtin__.__dict__["pgettext"] = makePgettext(trans)
def getLanguage(): def getLanguage():
return curLang return curLang
def normalizeLanguage(lang): def normalizeLanguage(lang):
""" """
Normalizes a language-dialect string in to a standard form we can deal with. 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. Converts any dash to underline, and makes sure that language is lowercase and dialect is upercase.
""" """
lang=lang.replace('-','_') lang=lang.replace('-','_')
ld=lang.split('_') ld=lang.split('_')
ld[0]=ld[0].lower() ld[0]=ld[0].lower()
#Filter out meta languages such as x-western #Filter out meta languages such as x-western
if ld[0]=='x': if ld[0]=='x':
return None return None
if len(ld)>=2: if len(ld)>=2:
ld[1]=ld[1].upper() ld[1]=ld[1].upper()
return "_".join(ld) return "_".join(ld)
def langToWindowsLocale(lang): def langToWindowsLocale(lang):
languages = {"en": "eng", languages = {"en": "eng",
"ar": "ara", "ar": "ara",
"ca": "cat", "ca": "cat",
"de": "deu", "de": "deu",
"es": "esp", "es": "esp",
"fi": "fin", "fi": "fin",
"fr": "fre_FRA", "fr": "fre_FRA",
"gl": "glc", "gl": "glc",
"eu": "euq", "eu": "euq",
"hu": "hun", "hu": "hun",
"hr": "hrv", "hr": "hrv",
"it": "ita", "it": "ita",
"ja": "jpn", "ja": "jpn",
"pl": "plk", "pl": "plk",
"pt": "ptb", "pt": "ptb",
"ru": "rus", "ru": "rus",
"tr": "trk", "tr": "trk",
"sr": "eng", "sr": "eng",
} }
return languages[lang] return languages[lang]

Some files were not shown because too many files have changed in this diff Show More