Compare commits

...

346 Commits

Author SHA1 Message Date
f287308986 Cleaned translation files 2015-06-11 16:13:34 -05:00
939523c4f9 Updated translations 2015-06-11 16:11:03 -05:00
aa5977f324 Fixes for the V 9.4 snapshot 2015-06-11 16:09:46 -05:00
4b882d8586 Updated translation templates 2015-06-08 10:04:33 -05:00
b0bbe5719f Fixed a bug when the user unfollows someone 2015-06-08 10:04:16 -05:00
0e3a4372b8 Removed some code 2015-06-08 05:41:48 -05:00
83575b14b9 Opening lists as a buffer is now possible from the list manager 2015-06-05 05:21:49 -05:00
f09ff43672 A couple of bugfixes 2015-06-04 17:15:03 -05:00
0045bd223c Code cleanup and a couple of fixes 2015-06-01 17:52:23 -05:00
f6bf1dcfdc Twishort keys fixed for 64 bits systems 2015-06-01 17:51:49 -05:00
91c5165d12 Interact function refactorized 2015-06-01 11:18:39 -05:00
5976caf736 Code fixes 2015-05-26 20:23:02 -05:00
831bbc2f2f Solved conflicts 2015-05-18 08:42:07 -05:00
Bill Dengler
590e4b0e6b #39: Move new methods. 2015-05-17 13:34:06 -04:00
Bill Dengler
6c690d7a3d Move interact to main controler. 2015-05-17 13:22:11 -04:00
Bill Dengler
b03d832376 String cleanup. 2015-05-17 00:15:10 -04:00
Bill Dengler
d521146562 Fixed #44. 2015-05-16 19:25:02 -04:00
Bill Dengler
66686c786d String cleanup. 2015-05-16 19:09:11 -04:00
Bill Dengler
bb27c08424 #43: Fix keymap conflict. 2015-05-16 19:08:13 -04:00
Bill Dengler
ae9bd087a8 Merge branch 'next-gen' into issue39 2015-05-16 17:43:32 -04:00
Bill Dengler
4bee7991d1 Fix error in base keymap template. 2015-05-16 17:38:24 -04:00
Bill Dengler
c67e8d041b Closed #43. 2015-05-16 17:36:43 -04:00
Bill Dengler
1d7b2c5712 Closed #39. 2015-05-16 14:25:38 -04:00
Bill Dengler
ebe3ab2c14 #39: Update Chicken Nugget keymap. 2015-05-16 13:59:21 -04:00
Bill Dengler
4b2c7a9655 #39: Make keymap changes for secondary_interact. 2015-05-16 13:58:06 -04:00
Bill Dengler
24fa7a1815 #39: Add option to disable new behavior. 2015-05-16 13:56:20 -04:00
Bill Dengler
a60d00d0dc #41: Added Chicken Nugget keymap. 2015-05-16 13:41:17 -04:00
Bill Dengler
b5f1294b82 #41: Implement keymap switching. 2015-05-16 13:28:44 -04:00
Bill Dengler
671e0e0bff String cleanup. 2015-05-16 03:31:53 -04:00
Bill Dengler
44605f1209 String cleanup. 2015-05-16 03:20:58 -04:00
Bill Dengler
cf139292a6 #41: Cleanup. 2015-05-16 03:10:20 -04:00
Bill Dengler
f3e1dc40ea Closed #40. 2015-05-16 03:01:39 -04:00
Bill Dengler
6ba57e83cf #41: Replace references to config.app["keymap"] to config.keymap["keymap"] 2015-05-16 01:23:33 -04:00
Bill Dengler
f68388f700 Clean up default keymap. 2015-05-16 01:15:01 -04:00
Bill Dengler
dc5d11c1c8 #41: Add load_keymap config entry, load a keymap as config.keymap. 2015-05-16 01:13:54 -04:00
Bill Dengler
40adacb32e #41: Added Chicken Nugget keymap. 2015-05-16 00:53:37 -04:00
Bill Dengler
3747f34b69 #40: Created base and default keymaps. 2015-05-16 00:15:10 -04:00
Bill Dengler
938be159a4 Closed #19. 2015-05-15 23:48:12 -04:00
Bill Dengler
7f869f7de1 Merge branch 'next-gen' of https://github.com/manuelcortez/twblue into next-gen 2015-05-15 21:44:13 -04:00
07bb6930d0 Fixed #38 2015-05-15 20:42:59 -05:00
Bill Dengler
48238239c3 Initial support for #29 2015-05-15 21:39:01 -04:00
Bill Dengler
d1fe610d6e Cleaned up copyright section. 2015-05-14 22:17:57 -04:00
Bill Dengler
3028b7d8ca You can now override hardcoded keys by adding attributes to the application module. 2015-05-14 22:12:42 -04:00
Bill Dengler
4a025caa69 String cleanup. 2015-05-14 10:14:42 -04:00
Bill Dengler
aa7145f5b3 Fix keycodes. 2015-05-14 10:12:43 -04:00
Bill Dengler
7fb71e2ec5 Fix filenames, play error sound on unactionable. 2015-05-14 10:09:31 -04:00
Bill Dengler
d02bd91f41 Fix error in sound declaration. 2015-05-14 10:03:38 -04:00
Bill Dengler
432e40bdfa Fix audio playback. 2015-05-14 10:01:37 -04:00
Bill Dengler
f0e1f74d03 Delete audio stream after successful playback. 2015-05-13 23:55:23 -04:00
Bill Dengler
6250e16cfe Fixed #37. 2015-05-13 23:21:47 -04:00
Bill Dengler
0b0da44020 Fixed #36. 2015-05-13 23:20:54 -04:00
Bill Dengler
73a069dbe2 Implement fix for url mishandling, remove print statements for testing, fixed #35 2015-05-13 22:10:37 -04:00
Bill Dengler
81a9a36519 Honor session volume during audio playback from interact method. 2015-05-13 21:59:46 -04:00
Bill Dengler
1b4ebebf25 Add delete flag to method call in interact, so we can stop an audio stream and play another. 2015-05-13 21:35:33 -04:00
Bill Dengler
1d1c07caca Add delete flag to stop_audio, clean duplicate code. 2015-05-13 21:34:26 -04:00
Bill Dengler
5f8760a0a9 Fixed error in settings controler. 2015-05-13 20:43:06 -04:00
Bill Dengler
feda70aea8 String, code cleanups. 2015-05-13 20:40:21 -04:00
Bill Dengler
e92565013b Cleanup, disabled accurate audio algo by default, string cleanup. 2015-05-13 20:26:05 -04:00
Bill Dengler
9ddc74d640 Typo fix. 2015-05-13 17:32:59 -04:00
Bill Dengler
71a061d13c Properly implemented announcements. 2015-05-13 17:32:03 -04:00
Bill Dengler
7f6cf8d30a Added announce flags to play and url methods. 2015-05-13 17:29:58 -04:00
Bill Dengler
58a8e45d56 Bugfix. 2015-05-13 17:25:46 -04:00
Bill Dengler
bb5f79e07b Removed output, time tests. 2015-05-13 14:42:42 -04:00
Bill Dengler
2a8868f096 More code changes. This is a big mess right now. Will clean this up. 2015-05-13 13:36:28 -04:00
Bill Dengler
74f0e8ba50 Attempt to fix merge conflict. 2015-05-13 12:53:14 -04:00
Bill Dengler
1d344275bb #34: just playing around a bit. Really broken right now. 2015-05-13 12:49:22 -04:00
Bill Dengler
ea7f5bacf9 #35: Add is_playable 2015-05-13 10:39:21 -04:00
4c125e4a7a Reverted audio algorithm 2015-05-13 08:55:45 -05:00
Bill Dengler
0845553047 Fixed #34 2015-05-12 22:12:24 -04:00
Bill Dengler
6f88e08811 Remove unneeded lines. 2015-05-12 20:53:28 -04:00
Bill Dengler
2fa17ee51f Clean up interact method. 2015-05-12 20:50:17 -04:00
Bill Dengler
7b2cf28f68 Miscelaneous fixes. 2015-05-12 20:38:40 -04:00
Bill Dengler
e67446b9bf Added exception handlers. 2015-05-12 20:19:39 -04:00
Bill Dengler
c7d157145b Fixed indentation error. 2015-05-12 19:55:32 -04:00
Bill Dengler
ab08d21d95 Made algo option application-wide, setting should actually work now. 2015-05-12 19:52:47 -04:00
Bill Dengler
ee1f95ea3b Clean out memmos when switching algos. 2015-05-12 19:30:44 -04:00
Bill Dengler
8ce5c258d3 Added more hooks, we now call the proper method to cache audio test results, added force flag to skip memmo checks and overwrite invalid memmos (useful when switching algos). 2015-05-12 19:11:24 -04:00
Bill Dengler
9b4fdc8966 #33: Added more hooks. 2015-05-12 18:40:28 -04:00
Bill Dengler
6cb5c4bd5c #33: Removed inefficient method, redesigning. 2015-05-12 18:09:15 -04:00
Bill Dengler
1b2b5913d1 #33: Implement setting for use_modern_audio_algo but not it does nothing right now. 2015-05-12 17:18:30 -04:00
Bill Dengler
92cd16bec9 #33: Add is_audio rebuild method, to eliminate slowness as much as possible. 2015-05-12 15:26:06 -04:00
Bill Dengler
ff9e16285e #33: Performance improvements (Windows). 2015-05-12 14:53:23 -04:00
Bill Dengler
68499ca9e5 #33: Generalize. 2015-05-12 14:21:15 -04:00
Bill Dengler
6fdc5c67e2 #33: Memmoize new audio detection logic for performance, follow HTTP redirects when determining filetype. 2015-05-12 14:13:43 -04:00
Bill Dengler
52bfa82ec5 #33: Add better audio detection support (needs testing, could pose a security risk, users should be able to disable this). 2015-05-12 13:31:51 -04:00
Bill Dengler
1e6a784192 #33: geocode checkers, audio handlers (there's probably a much better way to do this). 2015-05-12 12:01:19 -04:00
Bill Dengler
8eb6d0c4cb #33: Implemented exception on audio links that don't play. 2015-05-12 11:46:08 -04:00
Bill Dengler
e2771023da #33: Generalize audio method. 2015-05-12 11:30:56 -04:00
Bill Dengler
d19baf411a #33: generalize url. 2015-05-12 11:20:50 -04:00
Bill Dengler
0ddc20ea7c #33: Miscelaneous cleanup. 2015-05-12 10:47:24 -04:00
Bill Dengler
2aceab527e Keyboard remaps. 2015-05-12 10:14:26 -04:00
Bill Dengler
be787dc728 #33: redefine methods. 2015-05-12 10:12:09 -04:00
Bill Dengler
0aaec95ac4 #33: Initial interact() method. Very rough. 2015-05-12 09:47:41 -04:00
Bill Dengler
c1ce8cb8e5 Closed #32. 2015-05-11 21:21:27 -04:00
Bill Dengler
3f63046cbb #31: Refactored solution. 2015-05-11 20:25:35 -04:00
Bill Dengler
f241a1fc31 #31: Implement support for invisible interface. GUI implementation needs testing and possibly to be implemented. 2015-05-11 19:54:20 -04:00
Bill Dengler
8d873674ee #31: Initial support. 2015-05-11 19:41:19 -04:00
Bill Dengler
6f4501940e #31: Remove old dict that was to be used for this purpose. 2015-05-11 19:00:25 -04:00
Bill Dengler
69e3205a68 Fix the client, #30 really needs to be closed @manuelcortez 2015-05-10 13:37:05 -04:00
Bill Dengler
5001559c7c Fixed syntax error. 2015-05-10 13:24:56 -04:00
Bill Dengler
e52385317e Error corrections. 2015-05-09 23:19:21 -04:00
Bill Dengler
7003bab110 Fixed capitalization error. 2015-05-09 23:16:40 -04:00
Bill Dengler
f8f9a4ecf2 Fixed syntax error. 2015-05-09 23:13:47 -04:00
Bill Dengler
edae9fb664 Fixed syntax error. 2015-05-09 23:13:17 -04:00
Bill Dengler
a5198b881e #15: Allow for caching to be disabled, delete database when cache is disabled, remove unneeded conditional logic from method calls and move into methods themselves. 2015-05-09 23:12:17 -04:00
Bill Dengler
f394701789 #15: Initial work for persistance size limiting. 2015-05-09 23:04:06 -04:00
Bill Dengler
4d4816a61b Close #29 2015-05-09 16:29:20 -04:00
Bill Dengler
cb94d7f71a More cleanup. 2015-05-09 13:08:37 -04:00
Bill Dengler
c833854097 String cleanup in sounds tutorial. 2015-05-09 13:00:21 -04:00
Bill Dengler
278c1d5024 Closed #28 2015-05-09 12:54:02 -04:00
55d21840ef Included technow in the copyright section 2015-05-08 10:40:05 -05:00
8e27e9720c Closed #26 2015-05-08 09:38:00 -05:00
Bill Dengler
e1479b5c10 Fixed #25 2015-05-08 10:27:24 -04:00
Bill Dengler
e6ce553216 Updated application info, set bug reporting URL to new page for #26. 2015-05-08 10:08:00 -04:00
Bill Dengler
eb9128cb20 Split buffer dialog again. This closes #18 we won't implement this. 2015-05-08 09:59:52 -04:00
Bill Dengler
55683aee96 Fixed #23 2015-05-07 16:59:13 -04:00
e90c70bf02 Fix for #22 2015-05-07 09:16:41 -05:00
71eca7baf2 Fixed #21 2015-05-06 21:11:50 -05:00
Bill Dengler
d777b3a3cb Changes for #21 and #16 but issues still there. Needs work. 2015-05-06 19:06:20 -04:00
Bill Dengler
02486ccae6 Merge branch 'next-gen' of https://github.com/manuelcortez/twblue into next-gen 2015-05-06 16:42:04 -04:00
Bill Dengler
3633513e85 Fix #16. 2015-05-06 16:41:37 -04:00
Bill Dengler
d4932f3d2f Fix issue 13. 2015-05-06 16:36:01 -04:00
971306cd73 Fixed a bug in the buffer order 2015-05-06 13:46:47 -05:00
4b1a4db3db Now buffer list contains translatable strings 2015-05-06 13:33:53 -05:00
472099614a Fix a git conflict 2015-05-06 13:21:09 -05:00
bd6861d179 Move up and down are fully implemented 2015-05-06 12:56:32 -05:00
Bill Dengler
6c5e529129 More fixes. 2015-05-06 12:47:46 -04:00
Bill Dengler
9fbbd29c3b Fix errors in handlers. 2015-05-06 12:43:22 -04:00
Bill Dengler
7206debb30 Fix error in GUI binding declaration. 2015-05-06 12:38:59 -04:00
Bill Dengler
be4ba1608b More handlers. 2015-05-06 12:36:48 -04:00
Bill Dengler
e7628e1f65 Checks for move_up and move_down methods, untested since I broke my Python install and need to rebuild the repo. 2015-05-06 12:05:33 -04:00
Bill Dengler
cdb0a5b5a6 Enable the persistance (cache) by default. 2015-05-06 10:54:58 -04:00
Bill Dengler
1794f497a5 String cleanup, require restart when persistant_session changed. 2015-05-06 10:54:12 -04:00
Bill Dengler
08ce78f97c Speak state upon toggle. 2015-05-06 10:42:39 -04:00
Bill Dengler
797e0d7773 Fixed traceback. 2015-05-06 10:38:34 -04:00
Bill Dengler
ab5f6fd12d Unhook return in buffers dialog. 2015-05-06 10:27:22 -04:00
Bill Dengler
2a3937bf55 Implement show/hide toggle. 2015-05-06 00:26:01 -04:00
ec326ed1c1 Updater commit fixed 2015-05-05 17:54:38 -05:00
501a5cd57e Buffer configuration rewritten (move up and down is not possible yet) 2015-05-05 17:52:54 -05:00
Bill Dengler
780adeb001 Handle exceptions on update, added this because update server is down. 2015-05-05 11:18:40 -04:00
Bill Dengler
1adcd86a39 Merge branch 'next-gen' of https://github.com/manuelcortez/twblue into next-gen 2015-05-05 10:15:01 -04:00
1c4d0cc530 Replaced if with elif for a better performance 2015-05-05 08:39:03 -05:00
Bill Dengler
45d1380f95 Remove unneeded config entries. 2015-05-04 16:40:32 -04:00
Bill Dengler
78355efc45 Removed handling code for old buffer management. 2015-05-04 13:15:44 -04:00
Bill Dengler
0adf06f2a6 Remove options for old buffer management from UI. 2015-05-04 13:10:19 -04:00
Bill Dengler
3dc4c4c685 Buffer order now controlable by editing config file. 2015-05-04 12:51:47 -04:00
Bill Dengler
7b9247eefe Reimplemented long retweet handler. 2015-05-04 11:42:28 -04:00
Bill Dengler
e62039b727 Added message dialogs for handling retweets over 140 characters (I thought I pushed these but perhaps not). 2015-05-04 11:30:21 -04:00
380e05b079 Updated translations, new snapshot 2015-05-04 08:09:34 -05:00
5b6cae0a61 Merge branch 'next-gen' of https://github.com/manuelcortez/TWBlue into next-gen 2015-05-03 14:17:10 -05:00
592952c066 Added persistant session in the general account settings 2015-05-03 14:16:38 -05:00
Bill Dengler
42052f5ee7 Cleaned up user menu. 2015-05-02 18:42:21 -04:00
Bill Dengler
0162ae5739 Initial framework for unified new buffer dialog. 2015-05-02 18:35:11 -04:00
33175cbccd Merge branch 'next-gen' of https://github.com/manuelcortez/TWBlue into next-gen 2015-05-02 17:31:45 -05:00
ce5498ff94 Merge branch 'next-gen' of https://github.com/manuelcortez/TWBlue into next-gen 2015-05-02 17:22:32 -05:00
c50c42b895 Updating some translation strings 2015-05-02 17:22:28 -05:00
Bill Dengler
0f6b3491f8 Generalization. 2015-05-02 18:22:23 -04:00
Bill Dengler
036edb6c28 Cleanup, generalize spell checker. 2015-05-02 18:17:40 -04:00
Bill Dengler
3e8e2312f2 Improved the tweet dialogs. 2015-05-02 18:09:28 -04:00
Bill Dengler
83f9e32fd6 Corrected word choice. 2015-05-02 04:29:41 -04:00
Bill Dengler
6e294ca0da Fixed a typo. 2015-05-02 04:04:29 -04:00
Bill Dengler
3a5eeed372 Announce to user on database shelve, the process can take a while especially with large dictionaries and slow systems and we don't want users thinking the app hung. 2015-05-02 04:00:29 -04:00
Bill Dengler
9d083fb550 Fixed error handling. 2015-05-02 03:53:20 -04:00
Bill Dengler
aca2292637 Removed print statements, these were for my debugging only. 2015-05-02 03:49:47 -04:00
Bill Dengler
c7719545ac All keys will be converted to bitestrings on deshelve and to unicode on shelve. This will probably break i18n majorly, so a better fix should be implemented at some point. 2015-05-02 03:48:36 -04:00
Bill Dengler
f39adb658b Added exception handlers to deshelve and shelve methods. 2015-05-02 03:41:28 -04:00
Bill Dengler
7174984973 String cleanup, improved user experience. What is a timeline? And it's more logical to have the option to create a new buffer in the buffer menu. 2015-05-02 03:04:46 -04:00
Bill Dengler
ffaca56ec5 Cleanup. 2015-05-01 12:54:27 -04:00
Bill Dengler
8207bda74b Consistancy cleanup. 2015-05-01 11:16:41 -04:00
Bill Dengler
3c5529a761 String cleanup. 2015-05-01 11:05:42 -04:00
Bill Dengler
ebcd5720b0 Various changes related to generalization and persistance. 2015-05-01 02:05:25 -04:00
Bill Dengler
12541f3de3 Implement persistance. 2015-05-01 02:03:42 -04:00
Bill Dengler
594985dec4 Initial shelve support for persistance in sessions. 2015-05-01 01:48:42 -04:00
e7943cba50 Updated spanish translation, basic lists support 2015-04-27 16:08:02 -05:00
f51c873324 New snapshot 2015-04-22 15:34:44 -05:00
96671645bb Merge branch 'next-gen' of https://github.com/manuelcortez/TWBlue into next-gen 2015-04-20 17:49:38 -05:00
2880638241 Updated some translations; basic proxy support (needs testing) 2015-04-20 17:49:07 -05:00
Bill Dengler
3f02a0f422 More generalization. 2015-04-19 19:41:24 -04:00
Bill Dengler
7dd383b15e more app name removal. 2015-04-19 19:33:22 -04:00
Bill Dengler
ddf9d29b7d Fixed imports. 2015-04-19 19:28:27 -04:00
Bill Dengler
109635b8f4 More generalization. 2015-04-19 19:23:31 -04:00
Bill Dengler
9820d369c3 Merge branch 'next-gen' into generalize 2015-04-19 19:14:02 -04:00
Bill Dengler
263ce33c41 More string cleanup. 2015-04-19 19:13:57 -04:00
Bill Dengler
0913eac63d Merge branch 'next-gen' into generalize 2015-04-19 19:11:40 -04:00
Bill Dengler
ce0aa2d6a3 String cleanup. 2015-04-19 19:11:23 -04:00
Bill Dengler
00d5b88b8d Remove hardcoded app names. 2015-04-19 19:10:34 -04:00
be0253815f Merge pull request #12 from codeofdusk/next-gen
Ready sound/message toggles, string cleanup, and sounds tutorial menu moving.
2015-04-18 04:35:58 -05:00
Bill Dengler
a9405e32e9 More improvements to the sounds tutorial, reintroduced generalized app name. Why is this implemented as a list of tuples, wouldn't this be better implemented as a dictionary? 2015-04-17 19:32:30 -04:00
Bill Dengler
d0924a6e15 Even more string cleanup. 2015-04-17 19:00:24 -04:00
Bill Dengler
3c46201af9 More string cleanup. 2015-04-17 18:31:27 -04:00
Bill Dengler
2d7c7e8e37 String cleanup. 2015-04-17 18:24:27 -04:00
Bill Dengler
d87929c4c3 Cleaned up strings, added settings for the ready sound and message to the WX UI. 2015-04-17 17:53:24 -04:00
Bill Dengler
d4a4decaed Remove hardcoded app name. 2015-04-17 15:49:35 -04:00
Bill Dengler
61863c38e3 More GUI adjustments for sounds tutorial option move. 2015-04-17 15:39:43 -04:00
Bill Dengler
2d1f7e5ada Moved the option for the sounds tutorial to the "help" menu where a new user of the application is most likely to look. 2015-04-17 15:38:25 -04:00
Bill Dengler
916ca0127d The ready sound and message can now be disabled from the config file. 2015-04-17 15:32:12 -04:00
Bill Dengler
6f4e9e920f Made some app defaults more sane. 2015-04-17 15:07:30 -04:00
c2521f28fc TWBlue allows ask for comment or not in a retweet. Check account settings 2015-04-15 11:09:36 -05:00
7565d2ea57 Merge pull request #11 from codeofdusk/next-gen
More alphabetization.
2015-04-14 11:13:18 -05:00
Bill Dengler
23ba46823c Fixed the soundsTutorial. Really this time. 2015-04-14 11:38:15 -04:00
7facb9b020 Keystroke editor dialog is showed even if config has changed 2015-04-14 09:41:12 -05:00
5490a59eb5 Fixed a typo in the pypubsub package 2015-04-14 09:28:51 -05:00
d90df91aef AttributeError fixed from pull request #10 2015-04-14 09:03:16 -05:00
7cdda6806b Merge branch 'next-gen' of https://github.com/manuelcortez/TWBlue into next-gen 2015-04-14 08:51:33 -05:00
97cff4b598 New snapshot 2015-04-14 08:51:27 -05:00
c45a208cd6 Merge pull request #10 from codeofdusk/next-gen
Sounds tutorial refactor and docstring cleanup
2015-04-14 08:49:14 -05:00
Bill Dengler
8d6d452dfb Fixed docstrings. 2015-04-12 19:43:19 -04:00
Bill Dengler
21c6a999b4 The sounds tutorial is now sorted in alphabetical order. 2015-04-12 18:16:19 -04:00
Bill Dengler
141d8fa105 Improved strings for sounds tutorial. 2015-04-12 17:42:02 -04:00
José Manuel
dec2456655 Merge pull request #9 from codeofdusk/next-gen
English translation cleanup
2015-04-12 21:50:21 +02:00
Bill Dengler
9263aa8507 Fixed readme. 2015-04-12 13:52:55 -04:00
Bill Dengler
df3a32572b Made application description more succinct. 2015-04-12 12:35:54 -04:00
Bill Dengler
8b9209b62a Clean up spelling in help texts, improve clarity. 2015-04-12 12:29:24 -04:00
eef78cb8dc Libraries from keys are copied in setup 2015-04-12 00:03:51 -05:00
7c65a96a2b Fixed translator 2015-04-10 09:36:25 -05:00
5d87ce06b7 Jaws should not speak navigation keystrokes in invisible mode 2015-04-10 09:35:09 -05:00
43b797ca6b Get More Items has been fixed for showing items in the correct order 2015-04-10 09:34:24 -05:00
fa591c200d Merge branch 'next-gen' of https://github.com/manuelcortez/TWBlue into next-gen 2015-04-10 09:33:44 -05:00
1f5b2aaf7c Merge pull request #8 from sukiletxe/next-gen
Fixed small typo in arrow, in Basque locale.
2015-04-08 17:47:18 -05:00
77d04e0297 Longer retweets now includes the username of its owner 2015-04-08 17:46:14 -05:00
Sukil Etxenike
88ffdb4c29 Fixed small typo in arrow, in Basque locale. 2015-04-08 23:47:50 +02:00
324b18e8a4 Merge pull request #7 from sukiletxe/next-gen
Corrected program strings.
2015-04-08 13:44:20 -05:00
e2352ea3f2 Long retweets support. Read a long retweet by showing the tweet 2015-04-08 13:34:50 -05:00
Sukil Etxenike
b272a08ab9 Corrected program strings. 2015-04-05 23:39:55 +02:00
48e918adb0 Focus is handled in Gtk, accelerators and some menu items work 2015-04-04 21:25:52 -06:00
d8581f47d2 Merge pull request #6 from sukiletxe/next-gen
Corrected documentation. Added information for session manager and block...
2015-04-04 09:33:11 -06:00
Sukil Etxenike
17601393de Corrected documentation. Added information for session manager and block /
unblock.
2015-04-04 16:54:57 +02:00
2e32fa7ef2 First great commit for Gtk. It is partially functional now 2015-04-03 16:57:08 -06:00
9356a0544f A gtk view for session manager. Key libraries for linux need to be compiled 2015-03-31 17:46:15 -06:00
c085729096 Mention to all is bound to alt+t 2015-03-30 13:03:22 -06:00
d2f7228653 Config validation, invisible interface fix 2015-03-30 10:55:56 -06:00
a513303a9a Issue reporter module has been refactored in MVC 2015-03-29 23:27:18 -06:00
ffeccbb3b6 user searches and conversation refactored 2015-03-28 05:58:57 -06:00
2a83440c44 Fixes in inverted buffers, fixes in people buffers too 2015-03-28 05:02:17 -06:00
5a1fd9bb75 TWBlue can authorise a dropbox account again 2015-03-26 17:02:58 -06:00
f8c2ac7468 The spell checker now really works 2015-03-26 16:54:20 -06:00
ae6d5b38dd Jaws interrupt fix 2015-03-26 16:38:06 -06:00
1e9d4432f3 Now is possible to delete direct messages 2015-03-26 16:20:21 -06:00
9eec1c7c44 Don't load empty searches 2015-03-26 15:55:31 -06:00
7ad1d55241 Deleted an incorrect line in the session file 2015-03-26 15:17:41 -06:00
c88c37d65b Doc changes 2015-03-25 04:56:48 -06:00
15eb0f62ea TWBlue does not open timelines for protected users when is not possible 2015-03-24 17:28:01 -06:00
2e70cbc298 Searches improvements in inverted buffers 2015-03-24 17:07:14 -06:00
f2bb173ddb A few bugfixes 2015-03-24 03:47:16 -06:00
e046fdd198 TWBlue is closed even if sessions are not started properly; remove the systray icon 2015-03-20 17:19:40 -06:00
a63d6eceb1 Multiline in tweets, searches improvements and visual changes 2015-03-20 17:04:39 -06:00
ee006eeb66 Now searches use the max_tweets_per_api_call and audioboom playback fixed 2015-03-19 12:03:34 -06:00
ac45430839 You can't delete the home timeline 2015-03-19 05:34:18 -06:00
a592f747f8 Now user action events are announced in the events buffer 2015-03-19 05:31:19 -06:00
a0f5a3c134 Ask at exit now is saved in the config file 2015-03-19 05:23:02 -06:00
977ffacd27 A login fix 2015-03-19 04:56:29 -06:00
9256a5f981 Add the conversations option to the menu bar 2015-03-19 04:50:24 -06:00
75dfaec727 Basic conversation support, a jaws fix 2015-03-18 11:45:39 -06:00
89e541be95 Open a search from the trending topics buffer is now possible 2015-03-17 12:24:29 -06:00
c7d007636e Favourites and tweets timelines are available from the menu bar 2015-03-17 11:20:04 -06:00
8c85cc25a0 Basic support for favourites timelines 2015-03-17 05:12:29 -06:00
47d0a3db67 Now user searches and view user details on retweets works better 2015-03-17 03:50:49 -06:00
e15cd86644 Two new events added in the events API 2015-03-17 03:39:59 -06:00
85a7ffc8eb German and croatian are added to language handler, message logs format has changed to partially fix #5 2015-03-16 19:24:27 -06:00
ca9f5b0c30 Some text fixes, fix for turkey locales in arrow 2015-03-15 18:24:15 -06:00
69aa27c33e A few timeline bugfixes 2015-03-14 17:38:26 -06:00
26644da7b4 Changes the text for the logout button when is needed 2015-03-12 17:55:54 -06:00
4465f6e8aa Autocompletion works for the recipient in a direct message 2015-03-12 12:24:34 -06:00
4f4b8c6447 Expand URL button is shown in the view tweet dialogue 2015-03-12 11:45:53 -06:00
5926732c26 Fix for invisible mode 2015-03-12 09:28:58 -06:00
05f75bf5d0 Autoread and mute buffer now are checkable items in the WX interface 2015-03-12 08:31:39 -06:00
e63db7b150 The systray module has been added 2015-03-12 05:17:59 -06:00
d4fc809169 Session mute and buffer mute has been added to their keyboard shorcuts 2015-03-11 17:17:29 -06:00
4613b7ada1 Autoreading is now supported 2015-03-11 16:36:22 -06:00
eb4301aeb5 Play URL now works with all URL 2015-03-11 06:11:42 -06:00
4c56850da6 Changing soundpacks from settings take effect 2015-03-11 05:54:46 -06:00
9d4063b662 Hide buffers frof the account settings dialogue is possible 2015-03-10 09:44:21 -06:00
1a81d1a536 Hide GUI at startup is fixed 2015-03-09 15:55:34 -06:00
e1b3a94b13 It saves config for the logged sessions in the exit function 2015-03-09 13:53:47 -06:00
2cc865b62c view user info is now posible from the menu bar or control+win+shift+n 2015-03-08 14:18:29 -06:00
007da773c9 Two fixes for the last commit 2015-03-08 00:36:41 -06:00
462f6fe395 a minor session bugfix, improvements in the invisible navigation 2015-03-07 20:23:41 -06:00
e65c8ec4a6 Basic update profile function, some minor bugfixes, sound playback improvements 2015-03-06 21:37:08 -06:00
373ca24a96 Session manager is now fully MVC, it can removes an account too 2015-03-05 11:27:48 -06:00
3f1b00af8f Edit keystrokes works from the menu bar, you can add more sessions from application menu 2015-03-04 17:52:27 -06:00
4d1e03fb64 Now a session is saved properly when session manager creates it 2015-03-03 13:48:57 -06:00
632e54a727 Setting up multiple accounts using the session manager 2015-03-03 13:41:41 -06:00
188d95ad6c Another authorisation bug has been fixed 2015-03-02 15:06:47 -06:00
0ba120c3dd An authorisation bugfix 2015-03-01 20:41:02 -06:00
0b12924874 Action dialogue is attached to the right keyboard shorcuts 2015-03-01 20:11:26 -06:00
d6163d6294 Merge branch 'next-gen' of https://github.com/manuelcortez/twblue into next-gen 2015-03-01 20:02:08 -06:00
1b0e8e47fa A bugfix related to muted buffers 2015-03-01 20:02:00 -06:00
ae1df13f4a Merge pull request #5 from zstanecic/next-gen
croatian localization
2015-03-01 19:59:54 -06:00
jmdaweb
1093bea416 Added windows dependencies submodule, added pa.c format files 2015-03-01 23:06:20 +01:00
1f64b80f9d modified setup for compiling the new generation 2015-02-28 08:31:19 -06:00
c571a913c5 TWBlue doesn't play audioboom boos 2015-02-26 16:10:31 -06:00
76d8c292c9 Visit website & about the program work from the help menu 2015-02-26 16:09:26 -06:00
458d607b9a Start session automatically, login and logout in realtime from the account buffer in the GUI 2015-02-26 15:21:26 -06:00
3a9a49d64c Check for updates now works from the menu bar too 2015-02-26 10:26:46 -06:00
b7193d16ee The keystroke editor now works properly 2015-02-26 10:09:13 -06:00
e7852e5e91 Comments in some functions and submodules 2015-02-25 08:39:06 -06:00
35f70e050a Fixed locales in arrow for Catala and Basque (Galician is pending) 2015-02-22 18:45:26 -06:00
zstanecic
db8da293d3 croatian localization 2015-02-22 20:29:45 +01:00
88d7898dba The updater system is executed at the startup 2015-02-22 11:38:22 -06:00
e906d04f18 Now jaws doesn't say the keys pressed in invisible mode 2015-02-22 10:07:56 -06:00
d9d0efab2d Updater has been refactored 2015-02-16 17:21:27 -06:00
10d0b8ba6b Delete search buffers 2015-02-16 16:37:56 -06:00
f13d9113a4 Installer scripts 2015-02-13 10:01:09 -06:00
066814724b A bugfix on buffers deletion 2015-02-12 13:18:24 -06:00
a3e7effb83 Improvements in the tweet and dm dialogue 2015-02-12 13:10:42 -06:00
be9ede43a3 Mention and direct message for people buffers 2015-02-12 12:59:56 -06:00
d4ea961dae A bugfix in deleting a timeline 2015-02-12 10:35:13 -06:00
de7118caf4 Tweet's timelines are possible, a way to restart stream endpoint has been created 2015-02-12 10:29:51 -06:00
5eb4f74242 Delete timelines is now possible 2015-02-11 16:14:25 -06:00
d2cffe65d8 Don't load unallowed tweets 2015-02-08 05:48:40 -06:00
42aea45a41 A bug exiting TWBlue on GUI mode 2015-02-07 13:55:52 -06:00
2dd0cc8fbb Fixes for actions in empty buffers 2015-02-07 10:43:37 -06:00
0ede5bfcb0 Clear buffers is now possible, keys for 64 bits has been added 2015-02-07 10:23:23 -06:00
62b0bc76a8 A new library for storing app keys is added 2015-02-04 17:00:03 -06:00
2a7e09b166 We have a logo 2015-02-03 10:23:43 -06:00
9ad2d6cc69 Get_more_items has been implemented 2015-02-03 09:59:18 -06:00
1fbee07de2 Geo support has been implemented 2015-02-03 05:10:01 -06:00
3b1d8eb48a Trending topic buffers are loaded during init 2015-02-03 04:39:46 -06:00
103b62719e Trending topics support has been added 2015-02-01 21:13:18 -06:00
e93f0f4980 User actions dialog now works, the spell checker module ignores twitter usernames, hashtags, URL and email addresses 2015-02-01 00:49:03 -06:00
ff65b73232 Account settings works properly; auto completion has been implemented and improved, TWBlue loads in a threaded mode 2015-01-27 17:09:28 -06:00
51957125c1 Global settings works, basic support for account settings 2015-01-25 02:38:17 -06:00
af087508b0 Timelines fix and some minor bugfixes too 2015-01-22 08:54:41 -06:00
399de75d64 Invisible navigation fixes, URLPlayer volume fixes, shutdown fix 2015-01-20 22:07:13 -06:00
c25412bd32 Sound object refactored, more invisible shorcuts, global configuration and fixed buffer navigation in invisible mode 2015-01-20 15:40:33 -06:00
7b1f2f9482 Basic invisible interface support 2015-01-19 12:15:57 -06:00
fd70bedc05 Adding log information to events 2015-01-18 17:19:39 -06:00
bd1b0b5e32 Some stream improvements 2015-01-13 12:31:37 -06:00
02bdeae4e6 Changed the default soundpack to adium_glset 2015-01-08 08:32:53 -06:00
4979b2aa66 some useful functions 2015-01-08 08:01:45 -06:00
a84d35c6af Now the buttons in the buffers are connected to their functions; datetime and languageHandler improvements. 2015-01-05 06:05:31 -06:00
32884d3bf2 Delete/view tweets and direct messages has been implemented; some buffer fixes. 2015-01-05 05:33:09 -06:00
a599b37e6d Retweets, add and remove from favourites are supported 2015-01-02 10:53:33 -06:00
5cb4e0fef7 trends_updated and geo are in the sounds tutorial 2015-01-02 10:06:45 -06:00
7d6bf53019 Now it's possible ignore session at strtup 2015-01-02 09:55:27 -06:00
29d434bcd4 Reply and direct message are supported, updated locales and code improvements 2015-01-02 09:38:44 -06:00
186f70afc3 Some code changes to having a more consistent MVC application. 2015-01-01 21:40:57 -06:00
a6dd5d602f Uploading photos is now supported for tweets 2014-12-31 00:07:27 -06:00
2895d02cdc Next-gen: Audio uploader is now MVC, so tweets can include audios. Sounds tutorial is MVC and has been improved. 2014-12-29 20:58:30 -06:00
ca288de515 next-gen: updated widgetUtils 2014-12-29 14:35:08 -06:00
567e612c72 Spell checker is refactored in an MVC pattern and now works for tweets 2014-11-16 22:30:58 -06:00
1b3b684cbc Some documentation added on the wx part on widgetUtils 2014-11-16 21:37:29 -06:00
cd0aab90ef Support for shorten and unshorten URL's on tweets has been added 2014-11-15 19:40:19 -06:00
05dc8e5a31 TWBlue should use direct messages on all accounts #fix #next-gen 2014-11-14 16:02:15 -06:00
606ab6d6fc Post tweets and translate is possible in the next-gen 2014-11-12 22:37:52 -06:00
f54d9394b7 The next generation branch has been added 2014-11-12 20:41:29 -06:00
333 changed files with 60262 additions and 34908 deletions

6
.gitignore vendored
View File

@@ -1,6 +1,5 @@
*.pyc
*~
windows-dependencies/*
src/build/
src/dist/
src/config/
@@ -10,8 +9,11 @@ src/config3/
src/dropbox/
src/logs/
src/documentation/
src/sounds/iOs
src/oggenc2.exe
src/bootstrap.exe
src/Microsoft.VC90.CRT
src/Microsoft.VC90.MFC
src/launcher.bat
src/launcher.bat
src/sounds/iOs
release-snapshot/

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "windows-dependencies"]
path = windows-dependencies
url = https://github.com/jmdaweb/TWBlue_deps_windows.git

103
README.md
View File

@@ -1,9 +1,10 @@
TWBlue
TWBlue -
======
TWBlue, an accessible, open source and multiplatform twitter application.
Copyright (C) 2015. [Technow S.L.](https://www.technow.es)
TW Blue is an app designed to use Twitter in a simple and fast way and avoiding, as far as possible, the consumtion of excessive resources of the machine where its running. With this app youll have access to twitter features such as:
TW Blue is an app designed to use Twitter simply and efficiently while using minimal system resources.
With this app youll have access to twitter features such as:
* Create, reply to, retweet and delete tweets,
* Add and remove tweets from favourites,
@@ -11,8 +12,98 @@ TW Blue is an app designed to use Twitter in a simple and fast way and avoiding,
* See your friends and followers,
* Follow, unfollow, block and report users as spam,
* Open a users timeline, which will allow you to get that users tweets separately,
* Open URL addresses when attached to a tweet or direct message,
* Play various file and URL types which contain audio
* Open URLs when attached to a tweet or direct message,
* Play audio tweets
* and more!
See the [TWBlue's webpage](http://twblue.com.mx) for more details.
See [TWBlue's webpage](http://twblue.es) for more details.
## Using TWBlue from sources
This document describes how to run tw blue from source, and, after that, how to build a binary version, which doesn't need Python and the other dependencies to run.
### Required dependencies.
Although most dependencies can be found in the windows-dependencies directory, we provide links to their official websites. If you are cloning with git, don't forget to initialize and update the submodules to get the windows-dependencies folder. You can use these two commands to perform this task from git bash:
git submodule init
git submodule update
All the dependencies provided in this folder are prebuilt. If you want to build them from source, you will need Microsoft visual Studio 2008.
#### Dependencies packaged in windows installers
* [Python,](http://python.org) version 2.7.9
If you want to build both x86 and x64 binaries, you can install python x86 to C:\python27 and python x64 to C:\python27x64, for example.
* [wxPython](http://www.wxpython.org) for Python 2.7, version 3.0.2
* [Python windows extensions (pywin32)](http://www.sourceforge.net/projects/pywin32/) for python 2.7, build 219
* [Pycurl](http://pycurl.sourceforge.net) 7.19.5 for Python 2.7: [32-bit downloads,](https://pypi.python.org/pypi/pycurl/7.19.3.1) [64-bit downloads](http://www.lfd.uci.edu/~gohlke/pythonlibs/)
* [PyEnchant,](http://pythonhosted.org/pyenchant/) version 1.6.6.
x64 version has been built by TW Blue developers, so you only will find it in windows-dependencies folder
The windows installers are available only in the windows-dependencies folder
To build a binary version:
* [Py2exe](http://www.sourceforge.net/projects/py2exe/) for Python 2.7, version 0.6.9
#### Dependencies that must be installed using easy_install
setuptools install a script, called easy_install. You can find it in the python scripts directory. To install packages using easy_install, you have to navigate to the scripts directory using a command prompt, for example:
cd C:\python27x64\scripts
You can also add the scripts folder to your path environment variable.
After that, run the following command to install a package, replacing packagename with the names listed below:
easy_install -Z package
The -z switch unzips the package, instead of installing it compressed. If you add the --upgrade switch, you can upgrade a package to its latest version. The following packages need to be installed:
* pypubsub
* dropbox
* configobj
* requests-oauthlib
* future
* pygeocoder
* suds
* arrow
* goslate
* markdown
easy_install will automatically get the additional libraries that these packages need to work properly.
#### Other dependencies
These dependencies are located in the windows-dependencies directory. You don't need to install or modify them.
* Bootstrap 1.2.1: included in dependencies directory.
This dependency has been built using pure basic 4.61. Its source can be found at http://hg.q-continuum.net/updater
* [oggenc2.exe,](http://www.rarewares.org/ogg-oggenc.php) version 2.87
* Microsoft Visual c++ 2008 redistributable dlls.
#### Dependencies required to build the installer
* [NSIS unicode,](http://www.scratchpaper.com/) version 2.46.5
### Running TW Blue from source
Now that you have installed all these packages, you can run TW Blue from source using a command prompt. Navigate to the repo's src directory, and type the following command:
python main.py
If necessary, change the first part of the command to reflect the location of your python executable. You can run TW Blue using python x86 and x64
### Building a binary version
A binary version doesn't need python and the other dependencies to run, it's the same version that you will find on the TW Blue website if you download the zip files.
To build it, run the following command from the src folder:
python setup.py py2exe
You will find the binaries in the dist directory.
### How to generate a translation template
Run the gen_pot.bat file, located in the tools directory. Your python installation must be in your path environment variable. The pot file will appear in the tools directory.

View File

@@ -1,4 +1,5 @@
Manuel E. Cortéz
Bill Dengler
Johana Hidrobo
Marcelo Sosa
Isabel del Castillo

7
doc/application.py Normal file
View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
name = 'TWBlue'
snapshot = False
if snapshot == False:
version = "0.80"
else:
version = "7"

View File

@@ -1,18 +1,19 @@
# -*- coding: utf-8 -*-
import application
documentation = []
documentation.append(_(u"""Documentation for TW Blue 0.46"""))
documentation.append(_(u"""Documentation for {0} - {1}""").format(application.name, application.version))
# Translators: This is the new line character, don't change it in the translations.
documentation.append(_(u"""
"""))
documentation.append(_(u"""## Table of contents"""))
# Table of contents for the python markdown extension
documentation.append("""[TOC]""")
#documentation.append(_(u"""# Version 0.46 (alpha)"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""## Warning!"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""You're reading documentation produced for a program still in development. The object of this manual is explaining some details of the operation of the program. Bear in mind that as the software is in the process of active development, parts of this document may change in the near future, so it is advisable to keep an eye on it from time to time to avoid missing too much out."""))
documentation.append(_(u"""You're reading documentation produced for a program still in development. The object of this manual is to explain some details of the operation of the program. Bear in mind that as the software is in the process of active development, parts of this document may change in the near future, so it is advisable to keep an eye on it from time to time to avoid missing too much out."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""If you want to see what has changed from the previous version, [read the list of updates here.](changes.html)"""))
@@ -41,40 +42,40 @@ documentation.append(_(u"""
documentation.append(_(u"""In order to use an application like TW Blue which allows you to manage your Twitter account, you must first be registered on it. It's beyond the scope of this document to explain how to do so. We'll start from the premise that you have an account with its corresponding user name and password."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""## Authorising the application"""))
documentation.append(_(u"""### Authorising the application"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""First off, it's necessary to authorise the program so it can access your Twitter account and act on your behalf. The authorisation process is quite simple, and the program never gets data such as your username and password. In order to authorise the application, you just need to run the main executable file, called TWBlue.exe (on some computers it may appear simply as TWBlue)."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""When executed, if you have not previously configured the program, it will show a dialogue box where it tells you'll be taken to Twitter in order to authorise the application as soon as you press OK. To begin the authorisation process, press the only available button on the box."""))
documentation.append(_(u"""Whether this is the first time you open TWBlue or you don't have any session, you will see the session manager. This dialog allows you to authorise as many accounts as you wish. If you press the "new account" button a dialog will tell you that your default browser will be opened in order to authorise the application. Press "yes" so the process may start."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Your default browser will open on the Twitter page to request authorisation. Enter your user name and password if you're not already logged in, look for the authorise button, and press it."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Read the instructions you will get if the process is successful. In summary, you will be given a numeric code with several digits you must paste on an edit field open by the application on another window."""))
documentation.append(_(u"""Once you've authorised your twitter account, Twitter will redirect you to a web page which will notify you that TWBlue has been authorised successfully. Now you are able to close that window and come back to the session manager. You will see on the session list a new item temporally called "Authorised account x" -where x is a number. The session name will change once you open that session."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Paste the verification code, and press the enter key. """))
documentation.append(_(u"""To start running TWBlue, press the Ok button in the session manager dialog. By default, TWBlue starts all the configured sessions, however, you can change this behavior."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""If all went well, the application will start playing sounds, indicating your data are being updated."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""When the process is finished,the program will play another sound, and the screen reader will say "ready"."""))
documentation.append(_(u"""When the process is finished, the program will play another sound, and the screen reader will say "ready"."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""## The program's interface {#interface}"""))
documentation.append(_(u"""## The program's interface"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""The easiest way to describe the graphical interface of the application is a window with a menu bar with four menus (application, tweet, user and help), a list with several elements, and, in most cases, three buttons: tweet, retweet and reply. The actions available for each element are described below."""))
documentation.append(_(u"""The easiest way to describe the graphical user interface of TWBlue is saying that the application has a window which contains a menu bar with five menus (application, tweet, user, buffer and help); one tree view, one list of items and, mostly in every case, three buttons: Tweet, retweet and reply. The actions that are available for every item will be described later."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Elements on the lists may be tweets, direct messages or users. TW Blue creates different tabs for each list, which can be sent tweets, main timeline tweets, favourites, or direct messages, and each tab contains a single type of tweet. These tabs are called lists or buffers."""))
documentation.append(_(u"""In the tree view are inserted buffers which are lists to manage the processed data. When you configure a new session on TWBlue and start it, your account is the root of the tree view. Inside of it many buffers are created. Each one of them may contain some of the items which TWBlue works with: Tweets, direct messages, users, trends or events. According to the buffer you were, you will be able to make different actions with these items."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""To switch from list to list press control-tab to go forward, and control-shift-tab to go back. Screen readers will announce the list that gains the focus at all times. These are the basic lists of TW Blue, which are configured by default."""))
documentation.append(_(u"""The following is a description for every kind of TWBlue's buffer and the kind of items they work with."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""* Home: it shows all the tweets on the main timeline. These are the tweets by users you follow."""))
@@ -86,39 +87,37 @@ documentation.append(_(u"""* Followers: when users follow you, you'll be able to
documentation.append(_(u"""* Friends: the same as the previous list, but these are the users you follow."""))
documentation.append(_(u"""* User timelines: these are lists you may create. They contain only the tweets by a specific user. They're used so you can see the tweets by a single person and you don't want to look all over your timeline. You may create as many as you like."""))
documentation.append(_(u"""* Events: An event is anything that happens on Twitter, such as when someone follows you, when someone adds or removes one of your tweets from their favorites list, or when you subscribe to a list. There are many more but TW Blue shows the most common ones in the events buffer so that you can easily keep track of what is happening on your account."""))
documentation.append(_(u"""* Lists: A list is similar to a temporary timeline, except that you can configure it to contain tweets from multiple users. This is currently an experimental feature. If you decide to use it, please report any problems you encounter."""))
documentation.append(_(u"""* Lists: A list is similar to a temporary timeline, except that you can configure it to contain tweets from multiple users."""))
documentation.append(_(u"""* Search: A search buffer contains the results of a search operation."""))
documentation.append(_(u"""* User favorites: You can have TW Blue create a buffer containing tweets favorited by a particular user."""))
documentation.append(_(u"""* Trending Topics: a trend buffer shows the top ten most used terms in a geographical region. This region may be a country or a city. Trends are updated every five minutes."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Note: In this version of TW Blue, you will be able to see up to (or around) 400 friends and followers in their respective buffers. In the next version, we will provide a solution for those who have more to be able to see them."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Bear in mind the default configuration only allows getting the last 200 tweets for the home,, mentions, direct messages, and user timeline lists. You can change this on the setup dialogue. For the sent list, the last 200 tweets and the last 200 sent direct messages will be retrieved. Future versions will allow changing this parameter."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""If there's a URL on a tweet TW Blue will try to open it when you press enter on it. If there are several, it will show you a list with all of them so you choose the one you want. If you're on the followers or friends dialogue, the enter key will show you additional information on them."""))
documentation.append(_(u"""If there's a URL on a tweet, TW Blue will try to open it when you press enter on it. If there are several, it will show you a list with all of them so you choose the one you want. If you're on the followers or friends buffer, the enter key will show you additional information about them."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""If you press control-enter, TW Blue will try to play the audio from the focused tweet, as long as it has a URL. If it has the #audio hashtag, you will hear a sound when it is selected, letting you know you can try to play it. However, a tweet can be missing the hashtag and TW Blue will still be able to play it so long as it contains a URL with audio."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""## Controls {#controls}"""))
documentation.append(_(u"""You will also hear a sound when you see any tweet containing geographical information. You can see someone's location by selecting the option "view address" from the tweet menu on the menu bar."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Beginning with the latest version, there's support for an interface which does not require a visible window. It can be activated by pressing control-m, or choosing hide window from the application menu. This interface is entirely driven through shortcut keys. These shortcuts are different from those used to drive the graphical interface. Each interface can use only its own shortcuts, so you may not use the invisible shortcuts if you have the graphical interface opened. This section describes both the graphical and the invisible interface."""))
documentation.append(_(u"""## Controls"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""### The graphical user interface (GUI) {#gui}"""))
documentation.append(_(u"""Beginning with the 0.36 version, there's support for an interface which does not require a visible window. It can be activated by pressing control-m, or choosing hide window from the application menu. This interface is entirely driven through shortcut keys. These shortcuts are different from those used to drive the graphical interface. By default, you can't use the invisible interface shortcuts on the GUI. It has been made this way to keep compatibility with applications like TheQube and Chicken nugget which may use the same shortcuts. If you wish to have available the invisible interface shortcuts even if you are using the GUI, activate this option on the General tab of the preferences dialogue. This section describes both the graphical and the invisible interface."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""### The graphical user interface (GUI)"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Here you have a list divided into two parts. On the one hand, the buttons you will find while tabbing around on the program's interface, and on the other, the different elements present on the menu bar."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""#### Buttons on the application {#buttons}"""))
documentation.append(_(u"""#### Buttons on the application"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""* Tweet: this button opens up a dialogue box to write your tweet. The message must not exceed 140 characters. If you write past this limit, a sound will play to warn you. You may use the shorten and expand URL buttons to comply with the character limit. Press enter to send the tweet. If all goes well, you'll hear a sound confirming it. Otherwise, the screen reader will say an error message in English describing the problem."""))
documentation.append(_(u"""* Tweet: this button opens up a dialogue box to write your tweet. The message must not exceed 140 characters. If you write past this limit, a sound will play to warn you. You may use the shorten and expand URL buttons to comply with the character limit. You can translate your message, upload a picture, check spelling or attach audio by selecting one of the available buttons in the dialogue. Press enter to send the tweet. If all goes well, you'll hear a sound confirming it. Otherwise, the screen reader will say an error message in English describing the problem."""))
documentation.append(_(u"""* Retweet: this button retweets the message you're reading. After you press it, you'll be asked if you want to add a comment or simply send it as written."""))
documentation.append(_(u"""* Reply: when you're viewing a tweet, you can reply to the user who sent it by pressing this button. A dialogue will open up like the one for tweeting, but with the name of the user already filled in (for example @user) so you only need to write your message. If there are more users mentioned on the tweet, you can press shift-tab and press the mention all users button. When you're on the friends or followers lists, the button will be called mention instead."""))
documentation.append(_(u"""* Direct message: exactly like sending a tweet, but it's a private message which can only be read by the user you send it to. Press shift-tab to see the recipient. If there were other users mentioned on the tweet you were reading, you can arrow up or down to choose which one to send it to, or write the username yourself without the at sign."""))
@@ -127,215 +126,86 @@ documentation.append(_(u"""
documentation.append(_(u"""Bear in mind that buttons will appear according to which actions are possible on the list you are browsing. For example, on the home timeline, mentions, sent, favourites and user timelines you will see the four buttons, while on the direct messages list you'll only get the direct message and tweet buttons, and on friends and followers lists you will get the direct message, tweet, and mention buttons."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""#### Menus {#menus}"""))
documentation.append(_(u"""#### Menus"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""On top of the program window there's a menu bar which has the same functions, and some more. To access the menu bar, press alt. You will find four menus: application, tweet, user and help. This section describes the items on each one of them."""))
documentation.append(_(u"""On top of the program window there's a menu bar which has the same functions, and some more. To access the menu bar, press alt. You will find five: application, tweet, user, buffer and help. This section describes the items on each one of them."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""##### Application menu {#app}"""))
documentation.append(_(u"""##### Application menu"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""* Session manager: Opens a window with all the sessions configured in TWBlue, where you can add new sessions or delete the ones you've already created."""))
documentation.append(_(u"""* Update profile: opens a dialogue box where you can update your information on Twitter: name, location, URL and bio. If you have already set this up the fields will be prefilled with the existing information. Also, you can upload a photo to your profile."""))
documentation.append(_(u"""* Hide window: turns off the Graphical User Interface. Read the section on the invisible interface for further details."""))
documentation.append(_(u"""* Search: shows a dialog where you can search for tweets or users on Twitter."""))
documentation.append(_(u"""* View trending topics: It opens a buffer to get the trending topics of a country or a city. You'll be able to select from a dialog if you wish to get countries' trends or cities' trends and choose one from the selected list. The trending topics buffer will be created once pressing "ok" on this dialog. Remember this kind of buffer will be updated every five minutes."""))
documentation.append(_(u"""* Lists Manager: This dialog allows you to manage your Twitter lists. In order to use them, you must first create them. Here, you can view, edit, create, delete or, optionally, open them in buffers similar to temporary timelines."""))
documentation.append(_(u"""* Sound tutorial: Opens a dialog where you can familiarize yourself with the different sounds of the program."""))
documentation.append(_(u"""* Preferences: opens up a preference dialogue box from which you can control some of the program settings. The options need no explanation."""))
documentation.append(_(u"""* Quit: asks whether you want to exit the program. If the answer is yes, it shuts the application down."""))
documentation.append(_(u"""* Sounds tutorial: Opens a dialog where you can familiarize yourself with the different sounds of the program."""))
documentation.append(_(u"""* Edit keystrokes: It opens a dialog where you can see and re edit the invisible interface shortcuts."""))
### Add description for the global settings and accounts settings options.
documentation.append(_(u"""* Quit: asks whether you want to exit the program. If the answer is yes, it shuts the application down. If you wish TWBlue not to ask you for confirmation before exiting, uncheck the checkbox from the preferences dialogue."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""##### Tweet menu {#tweet}"""))
documentation.append(_(u"""##### Tweet menu"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""* You will first find the items to tweet, reply and retweet, which are equivalent to the buttons with the same name."""))
documentation.append(_(u"""* Mark as favourite: marks the tweet you're viewing as a favourite."""))
documentation.append(_(u"""* Remove tweet from favourites: removes the tweet from your favourites, but not from Twitter."""))
documentation.append(_(u"""* Show tweet: opens up a dialogue box where you can read the tweet, direct message, friend or follower under focus. You can read the text with the cursors. It's the same dialogue box used to write tweets on."""))
documentation.append(_(u"""* Show tweet: opens up a dialogue box where you can read the tweet, direct message, friend or follower under focus. You can read the text with the arrow keys. It's the same dialogue box used to write tweets on."""))
documentation.append(_(u"""* View address: If the selected tweet has geographical information, TWBlue may display a dialog where you can read the tweet address. This address is got by sending the geographical coordinates of the tweet to Google maps."""))
### Add description for view conversation feature
documentation.append(_(u"""* Delete: permanently removes the tweet or direct message you're on from Twitter and from your lists. Bear in mind that Twitter only allows you to delete tweets you have posted yourself."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""##### User menu {#user}"""))
documentation.append(_(u"""##### User menu"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Bear in mind the four topmost items on this menu open up the same dialogue box. This box has an edit field where you can choose the user you want to act on, by using up and down arrows or by writing the text in yourself. Afterwards you will find a group with four radio buttons to follow, unfollow, report as spam and block. If you choose the follow menu item, the radio button on the dialogue box will be set to follow, and the same applies to unfollow, report as spam and block. Press OK to try to carry out the action. If it doesn't succeed, you'll hear the error message in English."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""The remaining items on the menu are described below:"""))
documentation.append(_(u"""The available actions you can choose are described below:"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""* Follow: Follows a user. This means you'll see his/her tweets on your main timeline, and if he/she also follows you, you'll be able to interchange direct messages."""))
documentation.append(_(u"""* Unfollow: Stops following a user, which causes you not being able to see his/her tweets on your main timeline neither interchanging direct messages."""))
documentation.append(_(u"""* Mute: While muting someone, TWBlue won't show you nor his/her tweets on your main timeline; neither you'll see that person's mentions. But you both will be able to interchange direct messages. The muted user is not informed of this action."""))
documentation.append(_(u"""* Unmute: It turns the way TWBlue treats this user to its normal way. You will see his/her tweets and mentions again."""))
documentation.append(_(u"""* Report as spam: It suggests twitter this user is performing prohibited practices on the social network."""))
documentation.append(_(u"""* Block: Blocks a user. This forces the user to unfollow you ."""))
documentation.append(_(u"""* Unblock: Stops blocking a user.""")
documentation.append(_(u"""* Direct message: same action as the button."""))
documentation.append(_(u"""* Add to List: In order to see someone's tweets in one or more of your lists, you must add them first. This option will open a dialog where you can select the user you wish to add. Next, you will be asked to select the list you wish to add them to. Afterwards, the list will contain a new member and their tweets will show up there."""))
### add description for remove from list and view lists.
documentation.append(_(u"""* View user profile: opens up a dialogue box to choose the user whose profile you want to browse."""))
documentation.append(_(u"""* Timeline: opens up a dialogue box to choose whose user you want a timeline for. Create it by pressing enter. If you try it with a user that has no tweets, the program will fail. If you try creating an already existing timeline the program will warn you and will not create it again."""))
documentation.append(_(u"""* Timeline: Lets you open a user's timeline by choosing the user in a dialog box. It is created when you press enter. If you try it with a user that has no tweets, the program will fail. If you try creating an already existing timeline the program will warn you and will not create it again."""))
documentation.append(_(u"""* View favourites: Opens a buffer where you can see what tweets have been favorited by a particular user."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""##### Buffer menu{#buffer}"""))
documentation.append(_(u"""##### Buffer menu"""))
documentation.append(_(u"""
"""))
### add description for load previous items.
documentation.append(_(u"""* Mute buffer: Mutes notifications of a particular buffer so you will not hear when new tweets arrive."""))
documentation.append(_(u"""* autoread tweets for this buffer: When enabled, the screen reader or SAPI 5 (if enabled) will read the text of incoming tweets. Please note that this could get rather chatty if there are a lot of incoming tweets."""))
documentation.append(_(u"""* Clear buffer: Deletes all items from the buffer."""))
documentation.append(_(u"""* Remove buffer: dismiss the list you're on."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""##### Help menu {#help}"""))
documentation.append(_(u"""##### Help menu"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""* Documentation: opens up this file, where you can read some useful program concepts."""))
documentation.append(_(u"""* What's new in this version?: opens up a document with the list of changes from the current version down to the first."""))
documentation.append(_(u"""* Check for updates: every time you open the program it automatically checks for new versions. If there are, it will ask you if you want to download it. If you accept, it will do so, after which it will install it and ask you to let it restart itself, which it does automatically. This item checks for new updates without having to restart the application."""))
documentation.append(_(u"""* TW Blue's website: visit our [home page](http://twblue.com.mx) where you can find all relevant information and downloads for TW Blue and become a part of the community."""))
documentation.append(_(u"""* Report a bug: opens up a dialogue box to report a bug by filling a couple of fields: the title and a short description of what happened. Pressing enter will send the report. If the operation doesn't succeed the program will show a warning."""))
documentation.append(_(u"""* Check for updates: every time you open the program it automatically checks for new versions. If there is any, it will ask you if you want to download it. If you accept, it will do so, after which it will install it and ask you to let it restart itself, which it does automatically. This item checks for new updates without having to restart the application."""))
documentation.append(_(u"""* Report a bug: opens up a dialogue box to report a bug by filling a couple of fields. Pressing enter will send the report. If the operation doesn't succeed the program will show a warning."""))
documentation.append(_(u"""* TW Blue's website: visit our [home page](http://twblue.es) where you can find all relevant information and downloads for TW Blue and become a part of the community."""))
documentation.append(_(u"""* About TW Blue: shows the credits of the program."""))
documentation.append(_(u"""
documentation.append(_(u"""...
"""))
documentation.append(_(u"""### Invisible interface {#invisible_interface}"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""If you press control-m, or if you choose hide window from the application menu, you will activate an interface that cannot be used in the usual way, because it is invisible."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Every action on the invisible interface is done through keyboard shortcuts, even browsing lists. Eventually you may open dialogue boxes and these will be visible, but not the application's main window. Read the section on invisible interface shortcuts to know which ones you can use for the time being."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""### Keyboard shortcuts for the graphical interface {#shortcuts}"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Instead of using the buttons and menus, most actions can be carried out by pressing a key combination. The ones available at present are described below:"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""* Enter: open a URL. If there are more than one you will get a list that will allow you to choose the one you want. On the friends or followers lists it will show details on the selected item."""))
documentation.append(_(u"""* Control-enter: attempt to play audio from URL."""))
documentation.append(_(u"""* F5: decrease sounds volume. It affects the program sounds as well as audio played from the program."""))
documentation.append(_(u"""* F6: increase application sounds volume."""))
documentation.append(_(u"""* Control-n: open new tweet dialogue."""))
documentation.append(_(u"""* Control-m: hide window."""))
documentation.append(_(u"""* Control-q: quit."""))
documentation.append(_(u"""* Control-r: open reply tweet dialogue."""))
documentation.append(_(u"""* Control-shift-r: Retweet."""))
documentation.append(_(u"""* Control-d: send direct message."""))
documentation.append(_(u"""* Control-f: mark as favourite."""))
documentation.append(_(u"""* Control-shift-f: remove from favourites."""))
documentation.append(_(u"""* Control-shift-v: view tweet."""))
documentation.append(_(u"""* Control-s: follow a user."""))
documentation.append(_(u"""* Control-shift-s: unfollow a user."""))
documentation.append(_(u"""* Control-k: block a user."""))
documentation.append(_(u"""* Control-shift-k: report as spam."""))
documentation.append(_(u"""* Control-i: open user's timeline."""))
documentation.append(_(u"""* Control-shift-i: remove timeline."""))
documentation.append(_(u"""* Control-p: edit profile."""))
documentation.append(_(u"""* Delete: remove tweet or direct message."""))
documentation.append(_(u"""* Shift-delete: empty the buffer removing all the elements. This doesn't remove them from Twitter itself."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""### Keyboard shortcuts for the invisible interface {#invisible_shortcuts}"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""These are the shortcuts you may use from the invisible interface. Bear in mind that when the graphical user interface is shown you may not use these. By "win" the left windows key is intended."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""* Control+win+up arrow: go up on the current list."""))
documentation.append(_(u"""* Control+win+down arrow: go down on the current list."""))
documentation.append(_(u"""* Control+win+left arrow: go to the previous tab."""))
documentation.append(_(u"""* Control+win+right arrow: go to the next tab."""))
documentation.append(_(u"""* Control+win+home: go to the first element on the list."""))
documentation.append(_(u"""* Control+win+end: go to the last element on the list."""))
documentation.append(_(u"""* Control+win+page down: move 20 elements down on the current list."""))
documentation.append(_(u"""* Control+win+page up: move 20 elements up on the current list."""))
documentation.append(_(u"""* Control+win+alt+up arrow: increase volume by 5%."""))
documentation.append(_(u"""* control+win+alt+down arrow: decrease volume by 5%."""))
documentation.append(_(u"""* Control+win+enter: open URL on the current tweet, or further information for a friend or follower."""))
documentation.append(_(u"""* control+win+alt+enter: attempt to play audio."""))
documentation.append(_(u"""* control+win+m: show the graphical interface, turning the invisible one off."""))
documentation.append(_(u"""* Control+win+n: new tweet."""))
documentation.append(_(u"""* Control+win+r: reply to tweet."""))
documentation.append(_(u"""* Control+win+shift-r: retweet."""))
documentation.append(_(u"""* Control+win+d: send direct message."""))
documentation.append(_(u"""* Control+win+delete: remove a tweet or direct message."""))
documentation.append(_(u"""* Control+win+shift+delete: empty the buffer removing all the elements. This doesn't remove them from Twitter itself."""))
documentation.append(_(u"""* Win+alt+f: mark as favourite."""))
documentation.append(_(u"""* Win+alt+shift+f: remove from favourites."""))
documentation.append(_(u"""* Control+win+s: follow a user."""))
documentation.append(_(u"""* Control+win+shift+s: unfollow a user."""))
documentation.append(_(u"""* Control+win+alt+n: see user details."""))
documentation.append(_(u"""* Control+win+v: show tweet on an edit box."""))
documentation.append(_(u"""* Control+win+i: open user timeline."""))
documentation.append(_(u"""* Control+win+shift+i: remove user timeline."""))
documentation.append(_(u"""* Alt+win+p: edit profile."""))
documentation.append(_(u"""* Control+win+c: Copy to clipboard."""))
documentation.append(_(u"""* Control+win+space: Listen the current message."""))
documentation.append(_(u"""* Control+win+a: Add to list."""))
documentation.append(_(u"""* Control+win+shift+a: Remove from list."""))
documentation.append(_(u"""* Control+Win+Shift+M: Mutes/unmutes the active buffer."""))
documentation.append(_(u"""* Control+Win+E: toggles the automatic reading of incoming tweets in the active buffer."""))
documentation.append(_(u"""* Control+Win+Shift+Up arrow: move up one tweet in the conversation."""))
documentation.append(_(u"""* Control+Win+Shift+Down arrow: move down one tweet in the conversation."""))
documentation.append(_(u"""* Win+Alt+M: Globally mute/unmute TW Blue."""))
documentation.append(_(u"""* control+win+minus: Search on twitter."""))
documentation.append(_(u"""* Control+win+f4: quit."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""## Lists {#lists}"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""One of the most useful features of Twitter is the ability to create lists. Lists allow you to group users whose tweets you wish to see together instead of viewing their individual buffers. A common example of this would be if you follow multiple tech news accounts; it would be more convenient to have, for example, a "Tech News" list in which you can see tweets from these similar accounts together. A temporary buffer, such as what is created when you are viewing an individual person's timeline, is created and you can add/remove people from the list."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""In TW Blue, we have begun working on this feature little by little. It is still experimental but is in working condition. Below, we will explain how to configure lists."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""* First, you will need to open the lists manager which can be found under the application menu."""))
documentation.append(_(u"""* In the lists manager, you will first see the lists you have created followed by those which you are a member. If you see no lists, it means that you have not created any and that you are not a part of any list."""))
documentation.append(_(u"""* You will then see a group of buttons: Create a New List, Edit, Remove and Open in Buffer. Perhaps the last one is a bit less self-explanatory: it will open the list in a buffer similar to when opening someone's timeline. """))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Once you have created a new list, the next step will be to add users to it. If you were to open it in a buffer right now, it would be empty and no tweets would show up in it. To add users, follow these steps:"""))
documentation.append(_(u"""* While browsing your tweets, find a tweet from the user which you wish to add to a list. Next, press ctrl+win+A or select "Add to List" from the User menu."""))
documentation.append(_(u"""* A dialog will then appear asking for the user which you wish to add. The name of the user whose tweet you just selected should already be in the box. Simply confirm that it is correct and press the "OK" button."""))
documentation.append(_(u"""* Another dialog will appear showing all of your lists. Arrow to the one you want and press the "Add" button."""))
documentation.append(_(u"""* To remove a user from a list, repeat the same process but press ctrl+win+shift+A and, from the dialog that appears, choose the list from which you wish to remove the selected user."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""## Reporting bugs from the web {#reporting}"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Note: if you're running the program you can also report a bug from it, by using the help menu item. This process only allows for two edit fields and takes care of the rest. These steps are described for those who can't run the program, don't have it opened at a given moment, or simply want to report from the web instead of using the integrated bug reporting system."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""All things under the sun (yes, this includes computer programs) are very far from being perfect, so often you may find unforeseen bugs in the application. But as our intent is to always improve you're free (what's more, it would be great if you did) to report the bugs you find on the program so they can be reviewed and eventually fixed"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""In order to open the bug tracker's web, [follow this link](http://twblue.com.mx/errores/bug_report_page.php) It's a website with a form where you must fill several fields. Only three of them are really required (those marked with a star), but the more you can fill the better."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Here are the different form fields and what you must enter on each. Remember only fields mark witha star are required:"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""* Category: this field allows to choose what category to assign the bug to. You can choose general, if it's a program error, or documentation, if you have found a mistake in this manual or the changes list. This is a required field."""))
documentation.append(_(u"""* Reproducibility: here you must indicate how easy or hard it is to reproduce the bug. Available options are unknown, not reproducible, not attempted (by default), random, sometimes, or always. Depending on whether you can reproduce the error or not, you should choose the one closest to your situation. If you're making a feature request, this field is irrelevant."""))
documentation.append(_(u"""* Severity: here you choose how much it affects the program. Available options are functionality (choose this for a feature request), trivial, text, setting, minor, major, failure, or crash. Note the options go in increasing order. Choose the one which fits the situation best. If you're not sure which to choose you can leave it as it is."""))
documentation.append(_(u"""* Priority: choose according to the importance of the bug or feature requested. Available options are none, low, normal, high, urgent, and immediate."""))
documentation.append(_(u"""* Choose profile: here you can choose between 32 or 64 bit architecture and OS (Windows 7 for now). If they don't fit, you can fill the edit fields below with your specific information."""))
documentation.append(_(u"""* Product version: choose the version of the program you're running in order to find out when the error was introduced. This field will contain a sorted list of the available versions. Although it's not required, it would help a lot in quickly finding the bug."""))
documentation.append(_(u"""* Summary: a title for the bug, explaining in a few words what the problem is. It's a required text field."""))
documentation.append(_(u"""* Description: this required field asks you to describe in more detail what happened to the program."""))
documentation.append(_(u"""* Steps to reproduce: this field is used if you know how to cause the error. It's not required, but it would help a lot knowing how the program gets to the error in order to track it down."""))
documentation.append(_(u"""* Additional information: if you have a comment or note to add, it can go here. It's not required."""))
documentation.append(_(u"""* File attachment: you can attach the TW Blue.exe.log generated due to the bug. It is not required."""))
documentation.append(_(u"""* Visibility: choose if you want the bug to be publically visible or private. By default it's public, and it's recommended to keep it that way."""))
documentation.append(_(u"""* Send report: press the button to send the report and have it looked into."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""Many thanks for your participation in reporting bugs and trying out new functionality."""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""## Contact {#contact}"""))
documentation.append(_(u"""## Contact"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""If what's explained in this document is not enough, if you want to collaborate in some other way, or if you simply want to get in touch with the application developer, follow the Twitter account [@tw_blue2](https://twitter.com/tw_blue2) or [@manuelcortez00.](https://twitter.com/manuelcortez00) You can also visit [our website](http://twblue.com.mx)"""))
documentation.append(_(u"""
"""))
documentation.append(_(u"""---"""))
documentation.append(_(u"""Copyright © 2013-2014. Manuel Cortéz"""))
documentation.append(_(u"""Copyright © 2013-2015. Manuel Cortéz"""))

23
mysc/keys/api_keys.c Normal file
View File

@@ -0,0 +1,23 @@
#include "api_keys.h"
char *get_api_key(){
return "key\0";
}
char *get_api_secret(){
return "secret_key\0";
}
char *get_dropbox_api_key(){
return "key\0";
}
char *get_dropbox_api_secret(){
return "secret_key\0";
}
char *get_twishort_api_key(){
return "key\0";
}
char *get_bts_user(){
return "user\0";
}
char *get_bts_password(){
return "pass\0";
}

12
mysc/keys/api_keys.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef _API_KEYS_H
#define API_KEYS_H
char *get_api_key();
char *get_api_secret();
char *get_dropbox_api_key();
char *get_dropbox_api_secret();
char *get_twishort_api_key();
char *get_bts_user();
char *get_bts_password();
#endif

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -0,0 +1,8 @@
[Launch]
ProgramExecutable=TWBlue\TWBlue.exe
ProgramExecutable64=TWBlue64\TWBlue.exe
CommandLineArguments=-p -d "%PAL:DataDir%"
SinglePortableAppInstance=true
MinOS=XP
SingleAppInstance=false
DirectoryMoveOK=yes

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

View File

@@ -0,0 +1,28 @@
[Format]
Type=PortableApps.comFormat
Version=3.0
[Details]
Name=TWBlue portable
AppID=TWBluePortable
Publisher=jmdaweb & TWBlue & PortableApps.com
Homepage=PortableApps.com/TWBluePortable
Category=Internet
Description=A portable, fast and accessible Twitter client with many options.
Language=Multilingual
InstallType=Multilingual
[License]
Shareable=true
OpenSource=true
Freeware=true
CommercialUse=true
EULAVersion=2
[Version]
PackageVersion=0.80.0.0
DisplayVersion=0.80
[Control]
Icons=1
Start=TWBluePortable.exe

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -0,0 +1,17 @@
[Languages]
ENGLISH=true
ENGLISHGB=true
ARABIC=true
BASQUE=true
CATALAN=true
FINNISH=true
FRENCH=true
GALICIAN=true
GERMAN=true
HUNGARIAN=true
ITALIAN=true
POLISH=true
PORTUGUESEBR=true
RUSSIAN=true
SPANISHINTERNATIONAL=true
TURKISH=true

View File

@@ -0,0 +1,3 @@
The files in this directory are necessary for the portable application to
function. There is normally no need to directly access or alter any of the
files within these directories.

View File

@@ -0,0 +1,339 @@
 GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -0,0 +1,41 @@
You can get tw blue's source code at https://github.com/manuelcortez/TWBlue
LICENSE
=======
This package's installer and launcher are released under the GPL. The launcher
is the PortableApps.com Launcher, available with full source and documentation
from http://portableapps.com/development. We request that developers using the
PortableApps.com Launcher please leave this directory intact and unchanged.
USER CONFIGURATION
==================
Some configuration in the PortableApps.com Launcher can be overridden by the
user in an INI file next to TWBluePortable.exe called TWBluePortable.ini.
If you are happy with the default options, it is not necessary, though. There
is an example INI included with this package to get you started. To use it,
copy AppNamePortable.ini from this directory to TWBluePortable.ini next to
TWBluePortable.exe. The options in the INI file are as follows:
AdditionalParameters=
DisableSplashScreen=false
RunLocally=false
(There is no need for an INI header in this file; if you have one, though, it
won't damage anything.)
The AdditionalParameters entry allows you to pass additional command-line
parameters to the application.
The DisableSplashScreen entry allows you to run the launcher without the splash
screen showing up. The default is false.
The RunLocally entry allows you to run the portable application from a read-
only medium. This is known as Live mode. It copies what it needs to to a
temporary directory on the host computer, runs the application, and then
deletes it afterwards, leaving nothing behind. This can be useful for running
the application from a CD or if you work on a computer that may have spyware or
viruses and you'd like to keep your device set to read-only. As a consequence
of this technique, any changes you make during the Live mode session aren't
saved back to your device. The default is false.

View File

@@ -0,0 +1,6 @@
AdditionalParameters=
DisableSplashScreen=false
RunLocally=false
# The above options are explained in the included readme.txt
# This INI file is an example only and is not used unless it is placed as described in the included readme.txt

150
mysc/pa.c format/help.html Normal file
View File

@@ -0,0 +1,150 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<title>TWBlue Portable Help</title>
<link rel="alternate" href="http://portableapps.com/feeds/general" type="application/rss+xml" title="PortableApps.com">
<link rel="shortcut icon" href="Other/Help/Images/Favicon.ico">
<style type="text/css">
body {
font-family : Verdana,Arial,Helvetica,sans-serif;
font-size : 76%;
color : black;
margin : 20px;
background : #e6e8ea;
text-align : center;
}
a {
color : #b31616;
font-weight : bold;
}
a:link, a:visited, a:active {}
a:hover {
color : red;
}
h1, h2, h3, h4, h5, h6 {
font-family : Arial, sans-serif;
font-weight : normal;
}
h1 {
color : #b31616;
font-weight : bold;
letter-spacing : -2px;
font-size : 2.2em;
border-bottom : 1px solid silver;
padding-bottom : 5px;
}
h2 {
font-size : 1.5em;
border-bottom : 1px solid silver;
padding-bottom : 3px;
clear : both;
}
h3 { font-size : 1.2em; }
h4 { font-size : 1.1em; }
h5 { font-size : 1.0em; }
h6 { font-size : 0.8em; }
img { border : 0; }
ol, ul, li, p, pre, table, tr, td, th { font-size : 1.0em; }
pre { font-family : monospace; }
strong, b { font-weight : bold; }
td, th {
border : 1px solid #aaaaaa;
border-collapse : collapse;
padding : 3px;
}
th {
background : #3667a8;
color : white;
}
ol ol { list-style-type : lower-alpha; }
.content {
text-align : left;
margin-left : auto;
margin-right : auto;
width : 780px;
background-color : white;
border-left : 1px solid black;
border-right : 1px solid black;
padding : 12px 30px;
line-height : 150%;
}
.logo {
background : white url("Other/Help/Images/Help_Background_Header.png") repeat-x;
width : 840px;
margin-top : 20px;
margin-left : auto;
margin-right : auto;
text-align : left;
border-right : 1px solid black;
border-left : 1px solid black;
}
.footer {
background : white url("Other/Help/Images/Help_Background_Footer.png") repeat-x;
width : 840px;
height : 16px;
margin-left : auto;
margin-right : auto;
text-align : left;
border-right : 1px solid black;
border-left : 1px solid black;
}
.logo img {
padding-left : 0px;
border : none;
position : relative;
top : -4px;
}
* html .content { width : 760px; }
* html .logo, * html .footer { width : 820px; }
.content h1 { margin : 0px; }
h1.hastagline { border : 0; }
h2.tagline {
color : #747673;
clear : none;
margin-top : 0em;
}
/* printer styles */
@media print {
body, .content {
margin : 0;
padding : 0;
}
.navigation, .locator, .footer a, .message, .footer-links { display : none; }
.footer, .content, .header { border : none; }
a {
text-decoration : none;
font-weight : normal;
color : black;
}
}
</style>
</head>
<body>
<div class="logo"><a href="http://portableapps.com/"><img src="Other/Help/Images/Help_Logo_Top.png" alt="PortableApps.com - Your Digital Life, Anywhere"></a></div>
<div class="content">
<h1 class="hastagline">TWBlue Portable Help</h1>
<h2 class="tagline">A powerful and accessible Twitter client</h2>
<p>TWBlue Portable is the TWBlue whatever it is packaged with a PortableApps.com launcher as a <a href="http://portableapps.com/about/what_is_a_portable_app">portable app</a>, so you can view and send tweets on your iPod, USB flash drive, portable hard drive, etc. It has all the same features as TWBlue, plus, it leaves no personal information behind on the machine you run it on, so you can take it with you wherever you go. <a href="http://twblue.es">Learn more about TWBlue...</a></p>
<p><a href="http://portableapps.com/donate"><img src="Other/Help/Images/Donation_Button.png" style="vertical-align:middle" alt="Make a Donation"></a> - Support PortableApps.com's Hosting and Development</p>
<p><a href="http://portableapps.com/node/*Node ID*">Go to the TWBlue Portable Homepage &gt;&gt;</a></p>
<p><a href="http://portableapps.com/">Get more portable apps at PortableApps.com</a></p>
<p>This software is OSI Certified Open Source Software. OSI Certified is a certification mark of the Open Source Initiative.</p>
<h2>Portable App Issues</h2>
<ul>
<li><a href="http://portableapps.com/support/portable_app#downloading">Downloading a Portable App</a></li>
<li><a href="http://portableapps.com/support/portable_app#installing">Installing a Portable App</a></li>
<li><a href="http://portableapps.com/support/portable_app#using">Using a Portable App</a></li>
<li><a href="http://portableapps.com/support/portable_app#upgrading">Upgrading a Portable App</a></li>
</ul>
<p>You can read about advanced configuration options for the PortableApps.com Launcher in its <a href="Other/Source/Readme.txt">readme file</a>.</p>
</div>
<div class="footer"></div>
</body>
</html>

View File

@@ -1,60 +0,0 @@
tw blue build instructions for Windows
Introduction.
This document describes how to run tw blue from source, and, after that, how to build a binary version, which doesn't need Python and the other dependencies to run.
Required dependencies.
The following dependencies need to be installed in your system. If you want to build tw blue for 32-bit versions of Windows, you will find the required software in the x86 folder, inside dependencies directory. If you want to build tw blue for 64-bit windows versions, use the x64 folder.
In this document you will also find links to each dependency website.
If you want to build manually some of the following libraries, you need Microsoft Visual studio 2008 professional.
Dependencies list:
- Python, version 2.7.6: http://www.python.org
- wxPython for Python 2.7, version 3.0.0 (2.9.5 to avoid problems in windows xp): http://www.wxpython.org
-Python windows extensions (pywin32) for python 2.7, build 218: http://www.sourceforge.net/projects/pywin32/
- ConfigObj, version 4.7.2: http://www.voidspace.org.uk/python/configobj.html
- oauthlib 0.6.1: https://pypi.python.org/pypi/oauthlib/0.6.1
- Pycurl 7.19.3.1 for Python 2.7:
Official website: http://pycurl.sourceforge.net/
32-bit downloads: https://pypi.python.org/pypi/pycurl/7.19.3.1
64-bit downloads: http://www.lfd.uci.edu/~gohlke/pythonlibs/
- Requests 2.2.1:
Official website: http://www.python-requests.org/en/latest/
Recommended download site: https://pypi.python.org/pypi/requests/2.2.1
- Requests-oauthlib 0.4.0: https://github.com/requests/requests-oauthlib
- Suds 0.4:
Official website: https://fedorahosted.org/suds
Recommended download site: https://pypi.python.org/pypi/suds/0.4
- Twython 3.1.2: https://pypi.python.org/pypi/twython/3.1.2
- Bootstrap 1.2.1: included in dependencies directory.
Copy the bootstrap.exe file corresponding to the desired platform in the windows folder, inside this repository.
This dependency has been built using pure basic 4.61. Its source can be found at http://hg.q-continuum.net/updater
- oggenc2.exe, version 2.87: http://www.rarewares.org/ogg-oggenc.php
Copy the oggenc2.exe file corresponding to the desired platform in the windows folder, inside this repository.
-Visual C++ 2008 dlls, included in vcredist-x86.7z and vcredist-x64.7z:
Extract the file corresponding to your platform to the windows folder.
To build the binary version:
- Py2exe for Python 2.7, version 0.6.9: http://www.sourceforge.net/projects/py2exe/
- Setuptools 2.1: https://pypi.python.org/pypi/setuptools
- 7-zip: http://7-zip.org
To generate the documentation:
- Pandoc, version 1.12.3:
Official website: http://johnmacfarlane.net/pandoc/
Downloads site: http://code.google.com/p/pandoc/downloads/list
How to run tw blue from source
Run the file main.py located in windows folder. If you have a x64 system, you can install both 32-bit and 64-bit python versions, and test tw blue in these platforms.
How to generate the documentation
To generate quickly the documentation, a bash console is required. You can find bash in git for windows, cygwin, or MSYS, for example.
You must navigate to the tools directory and run the script gen_doc.sh. It will generate and place all html documentation in windows\documentation directory.
How to build a binary version
You must type the following command. In the following example, we will assume that python is the path to the python executable (x86 or x64) and that you have navigated to the windows directory:
python setup.py py2exe
You will find the binary version in the dist directory. You can compress this folder using 7-zip and you will get the zip version.
How to generate a translation template
You must run the gen_pot.bat file, located in the tools directory. Your python installation should be in your path environment variable. The pot file will appear in the tools directory too.

View File

@@ -3,7 +3,7 @@
!include "x64.nsh"
CRCCheck on
XPStyle on
Name "TW Blue"
Name "TWBlue"
OutFile "TWBlue_setup.exe"
InstallDir "$PROGRAMFILES\twblue"
InstallDirRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "InstallLocation"
@@ -11,17 +11,22 @@ RequestExecutionLevel admin
SetCompress auto
SetCompressor /solid lzma
SetDatablockOptimize on
VIAddVersionKey ProductName "TW Blue"
VIAddVersionKey LegalCopyright "Copyright 2014 Manuel Cortez."
VIAddVersionKey ProductVersion "0.48"
VIAddVersionKey FileVersion "0.48"
VIProductVersion "0.48.0.0"
VIAddVersionKey ProductName "TWBlue"
VIAddVersionKey LegalCopyright "Copyright 2015 Manuel Cortéz."
VIAddVersionKey ProductVersion "0.80"
VIAddVersionKey FileVersion "0.80"
VIProductVersion "0.80.0.0"
!insertmacro MUI_PAGE_WELCOME
!define MUI_LICENSEPAGE_RADIOBUTTONS
!insertmacro MUI_PAGE_LICENSE "license.txt"
!insertmacro MUI_PAGE_DIRECTORY
var StartMenuFolder
!insertmacro MUI_PAGE_STARTMENU startmenu $StartMenuFolder
!insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_LINK "Visit TWBlue website"
!define MUI_FINISHPAGE_LINK_LOCATION "http://twblue.es"
!define MUI_FINISHPAGE_RUN "$INSTDIR\TWBlue.exe"
!define MUI_FINISHPAGE_RUN_PARAMETERS "-i"
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
@@ -33,6 +38,7 @@ var StartMenuFolder
!insertmacro MUI_LANGUAGE "Russian"
!insertmacro MUI_LANGUAGE "PortugueseBR"
!insertmacro MUI_LANGUAGE "Polish"
!insertmacro MUI_LANGUAGE "German"
!insertmacro MUI_LANGUAGE "Hungarian"
!insertmacro MUI_LANGUAGE "Turkish"
!insertmacro MUI_LANGUAGE "Arabic"
@@ -48,22 +54,22 @@ File /r TWBlue64\*
${Else}
File /r TWBlue\*
${EndIf}
CreateShortCut "$DESKTOP\TW Blue.lnk" "$INSTDIR\TWBlue.exe" "-i"
CreateShortCut "$DESKTOP\TWBlue.lnk" "$INSTDIR\TWBlue.exe" "-i"
!insertmacro MUI_STARTMENU_WRITE_BEGIN startmenu
CreateDirectory "$SMPROGRAMS\$StartMenuFolder"
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\TW Blue.lnk" "$INSTDIR\TWBlue.exe" "-i"
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\TW Blue on the web.lnk" "http://twblue.com.mx"
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\TWBlue.lnk" "$INSTDIR\TWBlue.exe" "-i"
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\TWBlue on the web.lnk" "http://twblue.es"
CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\Uninstall.exe"
!insertmacro MUI_STARTMENU_WRITE_END
WriteUninstaller "$INSTDIR\Uninstall.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayName" "TW Blue"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayName" "TWBlue"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "InstallLocation" $INSTDIR
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortez"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "0.47"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "URLInfoAbout" "http://twblue.com.mx"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall" "Publisher" "Manuel Cortéz"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "DisplayVersion" "0.80"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "URLInfoAbout" "http://twblue.es"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMajor" 0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMinor" 47
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "VersionMinor" 80
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue" "NoRepair" 1
SectionEnd
@@ -71,7 +77,7 @@ Section "Uninstall"
SetShellVarContext All
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\twblue"
RMDir /r /REBOOTOK $INSTDIR
Delete "$DESKTOP\TW Blue.lnk"
Delete "$DESKTOP\TWBlue.lnk"
!insertmacro MUI_STARTMENU_GETFOLDER startmenu $StartMenuFolder
RMDir /r "$SMPROGRAMS\$StartMenuFolder"
SectionEnd

View File

@@ -5,84 +5,38 @@ user_name = string(default="")
ignored_clients = list(default=list())
[general]
language = string(default="system")
relative_times = boolean(default=True)
hide_gui = boolean(default=False)
voice_enabled = boolean(default=False)
max_api_calls = integer(default=1)
max_tweets_per_call = integer(default=100)
reverse_timelines = boolean(default=False)
time_to_check_streams = integer(default=30)
announce_stream_status = boolean(default=True)
ask_at_exit = boolean(default=True)
retweet_mode = string(default="ask")
persist_size = integer(default=0)
buffer_order = list(default=list('home','mentions','dm','sent_dm','sent_tweets','favorites','followers','friends','blocks','muted','events'))
[sound]
volume = float(default=1.0)
input_device = string(default="Default")
output_device = string(default="Default")
session_mute = boolean(default=False)
current_soundpack = string(default="default")
global_mute = boolean(default=False)
sndup_api_key = string(default="")
[other_buffers]
show_friends = boolean(default=True)
show_followers = boolean(default=True)
show_favourites = boolean(default=True)
show_events = boolean(default=True)
show_blocks = boolean(default=False)
show_muted_users = boolean(default=False)
timelines = list(default=list())
tweet_searches = list(default=list())
lists = list(default=list())
favourites_timelines = list(default=list())
trending_topic_buffers = list(default=list())
muted_buffers = list(default=list())
autoread_buffers = list(default=list())
autoread_buffers = list(default=list(mentions, direct_messages, events))
[mysc]
spelling_language = string(default="")
save_followers_in_autocompletion_db = boolean(default=False)
save_friends_in_autocompletion_db = boolean(default=False)
[services]
dropbox_token=string(default="")
[keymap]
up = string(default="control+win+up")
down = string(default="control+win+down")
left = string(default="control+win+left")
right = string(default="control+win+right")
conversation_up = string(default="control+win+shift+up")
conversation_down = string(default="control+win+shift+down")
show_hide = string(default="control+win+m")
compose = string(default="control+win+n")
reply = string(default="control+win+r")
retweet = string(default="control+win+shift+r")
dm = string(default="control+win+d")
fav = string(default="alt+win+f")
unfav = string(default="alt+shift+win+f")
action = string(default="control+win+s")
details = string(default="control+win+alt+n")
view = string(default="control+win+v")
close = string(default="control+win+f4")
open_timeline = string(default="control+win+i")
delete_buffer = string(default="control+win+shift+i")
url = string(default="control+win+return")
audio = string(default="control+win+alt+return")
volume_up = string(default="control+win+alt+up")
volume_down = string(default="control+win+alt+down")
go_home = string(default="control+win+home")
go_end = string(default="control+win+end")
go_page_up = string(default="control+win+pageup")
go_page_down = string(default="control+win+pagedown")
update_profile = string(default="alt+win+p")
delete = string(default="control+win+delete")
clear_list = string(default="control+win+shift+delete")
repeat_item = string(default="control+win+space")
copy_to_clipboard = string(default="control+win+c")
add_to_list = string(default="control+win+a")
remove_from_list = string(default="control+win+shift+a")
toggle_mute = string(default="control+win+shift+m")
toggle_global_mute = string(default="alt+win+m")
toggle_autoread = string(default="control+win+e")
search = string(default="control+win+-")
edit_keystrokes = string(default="control+win+k")
view_user_lists = string(default="control+win+l")
get_more_items = string(default="alt+win+pageup")

View File

@@ -21,7 +21,7 @@ class SAPI5(Output):
priority = 101
def __init__(self):
if config.main["general"]["voice_enabled"] == False: raise OutputError
if config.app["app-settings"]["voice_enabled"] == False: raise OutputError
try:
self.object = load_com("SAPI.SPVoice")
self._voices = self._available_voices()

View File

@@ -1,9 +1,9 @@
from base import Output, OutputError
import atexit
import application
class SpeechDispatcher(Output):
"""Supports speech dispatcher on Linux.
Note that it will take the configuration from the speech dispatcher, the user will need configure voice, language, punctuation and rate before use this module.
Note that this module will use the configuration of speech dispatcher, the user will need to configure the voice, language, punctuation and rate before using this module.
"""
name = 'SpeechDispatcher'
@@ -11,7 +11,7 @@ class SpeechDispatcher(Output):
super(SpeechDispatcher, self).__init__(*args, **kwargs)
try:
import speechd
self.spd = speechd.SSIPClient("TWBlue")
self.spd = speechd.SSIPClient(application.name)
except ImportError:
raise OutputError
atexit.register(self.on_exit_event)

View File

@@ -0,0 +1,21 @@
[sessions]
current_session = string(default="")
sessions = list(default=list())
ignored_sessions = list(default=list())
[app-settings]
language = string(default="system")
hide_gui = boolean(default=False)
voice_enabled = boolean(default=False)
ask_at_exit = boolean(default=True)
use_invisible_keyboard_shorcuts = boolean(default=True)
play_ready_sound = boolean(default=True)
speak_ready_msg = boolean(default=True)
log_level = string(default="error")
load_keymap = string(default="default.keymap")
[proxy]
server = string(default="")
port = string(default="")
user = string(default="")
password = string(default="")

View File

@@ -1,20 +1,16 @@
# -*- coding: utf-8 -*-
name = 'TW Blue'
snapshot = False
name = 'TWBlue'
snapshot = True
if snapshot == False:
version = "0.48"
update_url = 'http://twblue.com.mx/updates/tw_blue.json'
version = "0.80"
update_url = 'http://twblue.es/updates/twblue_ngen.json'
else:
version = "4"
update_url = 'http://twblue.com.mx/updates/snapshots.json'
author = u"Manuel Cortéz"
authorEmail = "info@twblue.com.mx"
copyright = u"copyright (C) 2013-2014, Manuel cortéz"
description = u"TW Blue is an app designed to use Twitter in a simple and fast way and avoiding, as far as possible, the consumtion of excessive resources of the machine where its running. With this app youll have access to most twitter features."
translators = [u"Bryner Villalobos (English)", u"Mohammed Al Shara (Arabic)", u"Salva Doménech, Juan Carlos Rivilla(Catalan)", u"Manuel cortéz(Spanish)", u"Sukil Etxenike Arizaleta(Basque)", u"Jani Kinnunen(finnish)", u"Javier Currás, José Manuel Delicado, Alba Quinteiro(Galician)", u"Robert Osztolykan(Hungarian)", u"Paweł Masarczyk(Polish)", u"Odenilton Júnior Santos(Portuguese)", u"Alexander Jaszyn(Russian)", u"Burak (Turkish)"]
url = u"http://twblue.com.mx"
#report_bugs_url = "http://twblue.com.mx/errores/api/soap/mantisconnect.php?wsdl"
# Tokens
app_key = '8pDLbyOW3saYnvSZ4uLFg'
app_secret = 'YsgdrzY9B4yyYvYsyee78rKI3GshjHpenVS9LnFJXY'
version = "9.4"
update_url = 'http://twblue.es/updates/snapshots_ngen.json'
author = u"Manuel Cortéz, Bill Dengler"
authorEmail = "manuel@manuelcortez.net"
copyright = u"Copyright (C) 2015, Technow S.L. \nCopyright (C) 2015, Bill Dengler\nCopyright (C) 2013-2015, Manuel cortéz."
description = unicode(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 = [u"Bryner Villalobos, Bill Dengler (English)", u"Mohammed Al Shara (Arabic)", u"Joan Rabat, Juan Carlos Rivilla (Catalan)", u"Manuel cortéz (Spanish)", u"Sukil Etxenike Arizaleta (Basque)", u"Jani Kinnunen (finnish)", u"Rémy Ruiz (French)", u"Alba Quinteiro (Galician)", u"Steffen Schultz (German)", u"Robert Osztolykan (Hungarian)", u"Paweł Masarczyk (Polish)", u"Odenilton Júnior Santos (Portuguese)", u"Alexander Jaszyn (Russian)", u"Burak (Turkish)"]
url = u"http://twblue.es"
report_bugs_url = "http://twblue.es/bugs/api/soap/mantisconnect.php?wsdl"

View File

@@ -10,14 +10,6 @@ def convert_audioboom(url):
audio_id = url.split('.com/')[-1]
return 'https://audioboom.com/%s.mp3' % audio_id
@matches_url('http://q-audio.net')
def convert_q_audio(url):
result = re.match("^https?://q-audio.net/(i|d|download)/(?P<audio_id>[a-z0-9]+/?)$", url, re.I)
if not result or result.group("audio_id") is None:
raise TypeError('%r is not a valid URL' % url)
audio_id = result.group("audio_id")
return 'http://q-audio.net/download/%s' % audio_id
@matches_url ('http://soundcloud.com/')
def convert_soundcloud (url):
client_id = "df8113ca95c157b6c9731f54b105b473"

View File

@@ -1,13 +1,17 @@
# -*- coding: utf-8 -*-
import argparse
import paths
import logging
import application
log = logging.getLogger("commandlineLauncher")
parser = argparse.ArgumentParser(description="TW Blue command line launcher")
parser = argparse.ArgumentParser(description=application.name+" command line launcher")
group = parser.add_mutually_exclusive_group()
group.add_argument("-p", "--portable", help="Use TW Blue as a portable aplication", action="store_true", default=True)
group.add_argument("-i", "--installed", help="Use TW Blue as an installed application. Config files will be saved on the user data directory", action="store_true")
parser.add_argument("-d", "--data-directory", action="store", dest="directory", help="Specifies the directory where TW Blue saves the data files")
group.add_argument("-p", "--portable", help="Use " + application.name + " as a portable application.", action="store_true", default=True)
group.add_argument("-i", "--installed", help="Use " + application.name + " as an installed application. Config files will be saved in the user data directory", action="store_true")
parser.add_argument("-d", "--data-directory", action="store", dest="directory", help="Specifies the directory where " + application.name + " saves userdata.")
args = parser.parse_args()
log.debug("Starting " + application.name + " with the following arguments: installed = %s, portable = %s and directory = %s" % (args.installed, args.portable, args.directory))
if args.installed == True: paths.mode = "installed"
elif args.portable == True:
paths.mode = "portable"

View File

@@ -1,16 +1,19 @@
# -*- coding: cp1252 -*-
from config_utils import Configuration, ConfigurationResetException
import config_utils
import paths
import logging
MAINFILE = "session.conf"
MAINSPEC = "Conf.defaults"
log = logging.getLogger("config")
main = None
MAINFILE = "twblue.conf"
MAINSPEC = "app-configuration.defaults"
app = None
keymap=None
def setup ():
global main
try:
main = Configuration(paths.config_path(MAINFILE), paths.app_path(MAINSPEC))
except ConfigurationResetException:
pass
# return main
global app
log.debug("Loading global app settings...")
app = config_utils.load_config(paths.config_path(MAINFILE), paths.app_path(MAINSPEC))
log.debug("Loading keymap...")
global keymap
keymap = config_utils.load_config(paths.app_path("keymaps/"+app['app-settings']['load_keymap']), paths.app_path('keymaps/base.template'))

View File

@@ -1,50 +1,73 @@
# -*- coding: utf-8 -*-
from UserDict import UserDict
from configobj import ConfigObj, ParseError
from validate import Validator, VdtValueError
from validate import Validator, ValidateError
import os
import string
class ConfigLoadError(Exception): pass
"""We're using the configobj python package
from http://www.voidspace.org.uk/python/configobj.html """
def load_config(config_path, configspec_path=None, *args, **kwargs):
if os.path.exists(config_path):
clean_config(config_path)
spec = ConfigObj(configspec_path, encoding='UTF8', list_values=False, _inspec=True)
try:
config = ConfigObj(infile=config_path, configspec=spec, create_empty=True, encoding='UTF8', *args, **kwargs)
except ParseError:
raise ConfigLoadError("Unable to load %r" % config_path)
validator = Validator()
validated = config.validate(validator, copy=True)
if validated == True:
config.write()
return config
class ConfigurationResetException(Exception):
pass
def is_blank(arg):
"Check if a line is blank."
for c in arg:
if c not in string.whitespace:
return False
return True
def get_keys(path):
"Gets the keys of a configobj config file."
res=[]
fin=open(path)
for line in fin:
if not is_blank(line):
res.append(line[0:line.find('=')].strip())
fin.close()
return res
def hist(keys):
"Generates a histogram of an iterable."
res={}
for k in keys:
res[k]=res.setdefault(k,0)+1
return res
class Configuration (UserDict):
def find_problems(hist):
"Takes a histogram and returns a list of items occurring more than once."
res=[]
for k,v in hist.items():
if v>1:
res.append(k)
return res
def __init__ (self, file=None, spec=None, *args, **kwargs):
self.file = file
self.spec = spec
self.validator = Validator()
self.setup_config(file=file, spec=spec)
self.validated = self.config.validate(self.validator, copy=True)
if self.validated:
self.write()
UserDict.__init__(self, self.config)
def setup_config (self, file, spec):
#The default way -- load from a file
spec = ConfigObj(spec, list_values=False, encoding="utf-8")
try:
self.config = ConfigObj(infile=file, configspec=spec, create_empty=True, stringify=True, encoding="utf-8")
except ParseError:
os.remove(file)
self.config = ConfigObj(infile=file, configspec=spec, create_empty=True, stringify=True)
raise ConfigurationResetException
def __getitem__ (self, *args, **kwargs):
return dict(self.config).__getitem__(*args, **kwargs)
def __setitem__ (self, *args, **kwargs):
self.config.__setitem__(*args, **kwargs)
UserDict.__setitem__(self, *args, **kwargs)
def write (self):
if hasattr(self.config, 'write'):
self.config.write()
class SessionConfiguration (Configuration):
def setup_config (self, file, spec):
#No infile required.
spec = ConfigObj(spec, list_values=False)
self.config = ConfigObj(configspec=spec, stringify=True)
def clean_config(path):
"Cleans a config file. If duplicate values are found, delete all of them and just use the default."
orig=[]
cleaned=[]
fin=open(path)
for line in fin:
orig.append(line)
fin.close()
for p in find_problems(hist(get_keys(path))):
for o in orig:
o.strip()
if p not in o:
cleaned.append(o)
if len(cleaned) != 0:
cam=open(path,'w')
for c in cleaned:
cam.write(c)
cam.close()
return True
else:
return False

View File

@@ -1,3 +1 @@
# -*- coding: utf-8 -*-
import main, dialogs

View File

@@ -0,0 +1,863 @@
# -*- coding: utf-8 -*-
import platform
if platform.system() == "Windows":
import wx
from wxUI import buffers, dialogs, commonMessageDialogs
import user
elif platform.system() == "Linux":
from gi.repository import Gtk
from gtkUI import buffers, dialogs, commonMessageDialogs
import messages
import widgetUtils
import arrow
import webbrowser
import output
import config
import sound
import languageHandler
import logging
from twitter import compose, utils
from mysc.thread_utils import call_threaded
from twython import TwythonError
from pubsub import pub
from 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 bufferController(object):
def __init__(self, parent=None, function=None, session=None, *args, **kwargs):
super(bufferController, self).__init__()
self.function = function
self.compose_function = None
self.args = args
self.kwargs = kwargs
self.buffer = None
self.account = ""
self.needs_init = True
self.invisible = False # False if the buffer will be ignored on the invisible interface.
def clear_list(self): pass
def get_event(self, ev):
if ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown(): event = "url"
elif ev.GetKeyCode() == wx.WXK_RETURN: event = "interact"
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):
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
if hasattr(sound.URLPlayer, "stream"):
sound.URLPlayer.stream.volume = self.session.settings["sound"]["volume"]
self.session.sound.play("volume_changed.ogg")
def volume_up(self):
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
if hasattr(sound.URLPlayer, "stream"):
sound.URLPlayer.stream.volume = self.session.settings["sound"]["volume"]
self.session.sound.play("volume_changed.ogg")
def interact(self):
if hasattr(sound.URLPlayer,'stream'):
return sound.URLPlayer.stop_audio(delete=True)
tweet = self.get_tweet()
url=None
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 != None:
output.speak(_(u"Opening media..."), True)
if sound.URLPlayer.is_playable(url=url, play=True, volume=self.session.settings["sound"]["volume"]) == False:
return webbrowser.open_new_tab(url)
# else:
# output.speak(_(u"Not actionable."), True)
# self.session.sound.play("error.ogg")
def start_stream(self):
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):
self.buffer.list.remove_item(item)
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 direct_message(self):
pass
def retweet(self):
pass
def destroy_status(self):
pass
def post_tweet(self, *args, **kwargs):
title = _(u"Tweet")
caption = _(u"Write the tweet here")
tweet = messages.tweet(self.session, title, caption, "")
if tweet.message.get_response() == widgetUtils.OK:
text = tweet.message.get_text()
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()
class accountPanel(bufferController):
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)
class emptyPanel(bufferController):
def __init__(self, parent, name, account):
super(emptyPanel, self).__init__(parent, None, name)
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
class baseBufferController(bufferController):
def __init__(self, parent, function, name, sessionObject, account, sound=None, bufferType=None, *args, **kwargs):
super(baseBufferController, 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 = compose.compose_tweet
log.debug("Compose_function: %s" % (self.compose_function,))
self.account = account
self.buffer.account = account
self.bind_events()
self.sound = sound
def get_formatted_message(self):
if self.type == "dm" or self.name == "sent_tweets" or self.name == "sent_direct_messages": return self.compose_function(self.get_right_tweet(), self.session.db, self.session.settings["general"]["relative_times"])[1]
return self.get_message()
def get_message(self):
return " ".join(self.compose_function(self.get_right_tweet(), self.session.db, self.session.settings["general"]["relative_times"]))
def get_full_tweet(self):
tweet = self.get_right_tweet()
tweetsList = []
tweet_id = tweet["id"]
uri = None
if tweet.has_key("long_uri"):
uri = tweet["long_uri"]
tweet = self.session.twitter.twitter.show_status(id=tweet_id)
if uri != None:
tweet["text"] = twishort.get_full_text(uri)
l = tweets.is_long(tweet)
while l != False:
tweetsList.append(tweet)
id = tweets.get_id(l)
tweet = self.session.twitter.twitter.show_status(id=id)
l = tweets.is_long(tweet)
if l == False:
tweetsList.append(tweet)
return (tweet, tweetsList)
def start_stream(self):
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))
val = self.session.call_paged(self.function, *self.args, **self.kwargs)
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 self.sound == None: return
if number_of_items > 0 and self.name != "sent_tweets" and self.name != "sent_direct_messages":
self.session.sound.play(self.sound)
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 = self.session.get_more_items(self.function, count=self.session.settings["general"]["max_tweets_per_call"], max_id=last_id, *self.args, **self.kwargs)
except TwythonError as e:
output.speak(e.message, True)
for i in items:
if utils.is_allowed(i, self.session.settings["twitter"]["ignored_clients"]) == True:
elements.append(i)
if self.session.settings["general"]["reverse_timelines"] == False:
self.session.db[self.name].insert(0, i)
else:
self.session.db[self.name].append(i)
selection = self.buffer.list.get_selected()
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.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.buffer.list.insert_item(False, *tweet)
# self.buffer.list.select_item(selection+elements)
# else:
self.buffer.list.select_item(selection)
output.speak(_(u"%s items retrieved") % (str(len(elements))), True)
def remove_buffer(self):
if "-timeline" in self.name:
dlg = commonMessageDialogs.remove_buffer()
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])
return True
elif dlg == widgetUtils.NO:
return False
elif "favorite" in self.name:
dlg = commonMessageDialogs.remove_buffer()
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])
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 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.buffer.list.insert_item(False, *tweet)
self.buffer.set_position(self.session.settings["general"]["reverse_timelines"])
elif self.buffer.list.get_count() > 0:
if self.session.settings["general"]["reverse_timelines"] == False:
for i in self.session.db[self.name][:number_of_items]:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"])
self.buffer.list.insert_item(False, *tweet)
else:
for i in self.session.db[self.name][0:number_of_items]:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"])
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"])
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]))
#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_tweet, self.buffer.tweet)
# if self.type == "baseBuffer":
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.retweet, self.buffer.retweet)
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.direct_message, self.buffer.dm)
widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.reply, self.buffer.reply)
def get_tweet(self):
if self.session.db[self.name][self.buffer.list.get_selected()].has_key("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()
screen_name = tweet["user"]["screen_name"]
id = tweet["id"]
users = utils.get_all_mentioned(tweet, self.session.db)
message = messages.reply(self.session, _(u"Reply"), _(u"Reply to %s") % (screen_name,), "@%s " % (screen_name,), users)
if message.message.get_response() == widgetUtils.OK:
if message.image == None:
call_threaded(self.session.api_call, call_name="update_status", _sound="reply_send.ogg", in_reply_to_status_id=id, status=message.message.get_text())
else:
call_threaded(self.session.api_call, call_name="update_status_with_media", _sound="reply_send.ogg", in_reply_to_status_id=id, status=message.message.get_text(), media=message.file)
if hasattr(message.message, "destroy"): message.message.destroy()
@_tweets_exist
def direct_message(self, *args, **kwargs):
tweet = self.get_right_tweet()
if self.type == "dm":
screen_name = tweet["sender"]["screen_name"]
users = utils.get_all_users(tweet, self.session.db)
elif self.type == "people":
screen_name = tweet["screen_name"]
users = [screen_name]
else:
screen_name = tweet["user"]["screen_name"]
users = utils.get_all_users(tweet, self.session.db)
dm = messages.dm(self.session, _(u"Direct message to %s") % (screen_name,), _(u"New direct message"), users)
if dm.message.get_response() == widgetUtils.OK:
call_threaded(self.session.api_call, call_name="send_direct_message", text=dm.message.get_text(), screen_name=dm.message.get("cb"))
if hasattr(dm.message, "destroy"): dm.message.destroy()
@_tweets_exist
def retweet(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=''):
retweet = messages.tweet(self.session, _(u"Retweet"), _(u"Add your comment to the tweet"), u"“@%s: %s" % (tweet["user"]["screen_name"], tweet["text"]), max=116-len("@%s " % (tweet["user"]["screen_name"],)), messageType="retweet")
if comment != '':
retweet.message.set_text(comment)
if retweet.message.get_response() == widgetUtils.OK:
text = retweet.message.get_text()
comments=text
if len(text+ u"“@%s: %s" % (tweet["user"]["screen_name"], tweet["text"])) < 140:
text = text+u"“@%s: %s" % (tweet["user"]["screen_name"], tweet["text"])
else:
answer = commonMessageDialogs.retweet_as_link(self.buffer)
if answer == widgetUtils.YES:
text = text+" @{2} https://twitter.com/{0}/status/{1}".format(tweet["user"]["screen_name"], id, tweet["user"]["screen_name"])
else:
return self._retweet_with_comment(tweet, id, comment=comments)
if retweet.image == None:
call_threaded(self.session.api_call, call_name="update_status", _sound="retweet_send.ogg", status=text, in_reply_to_status_id=id)
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):
call_threaded(self.session.api_call, call_name="retweet", _sound="retweet_send.ogg", id=id)
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"], "ddd MMM D H:m:s Z YYYY", locale="en")
ts = original_date.humanize(locale=languageHandler.getLanguage())
self.buffer.list.list.SetStringItem(self.buffer.list.get_selected(), 2, ts)
if utils.is_audio(tweet):
self.session.sound.play("audio.ogg")
if utils.is_geocoded(tweet):
self.session.sound.play("geo.ogg")
self.session.db[str(self.name+"_pos")]=self.buffer.list.get_selected()
@_tweets_exist
def audio(self,url=''):
if hasattr(sound.URLPlayer,'stream'):
return sound.URLPlayer.stop_audio(delete=True)
tweet = self.get_tweet()
if tweet == None: return
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 != '':
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):
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:
try:
if self.name == "direct_messages" or self.name == "sent_direct_messages":
self.session.twitter.twitter.destroy_direct_message(id=self.get_right_tweet()["id"])
else:
self.session.twitter.twitter.destroy_status(id=self.get_right_tweet()["id"])
self.session.db[self.name].pop(index)
self.buffer.list.remove_item(index)
# if index > 0:
except TwythonError:
self.session.sound.play("error")
@_tweets_exist
def user_details(self):
tweet = self.get_right_tweet()
if self.type == "dm":
users = utils.get_all_users(tweet, self.session.db)
elif self.type == "people":
users = [tweet["screen_name"]]
else:
users = utils.get_all_users(tweet, self.session.db)
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()
class listBufferController(baseBufferController):
def __init__(self, parent, function, name, sessionObject, account, sound=None, bufferType=None, list_id=None, *args, **kwargs):
super(listBufferController, 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):
self.get_user_ids()
super(listBufferController, self).start_stream()
def get_user_ids(self):
self.users = []
next_cursor = -1
while(next_cursor):
users = self.session.twitter.twitter.get_list_members(list_id=self.list_id, cursor=next_cursor, include_entities=False, skip_status=True)
for i in users['users']:
self.users.append(i["id"])
next_cursor = users["next_cursor"]
def remove_buffer(self):
dlg = commonMessageDialogs.remove_buffer()
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])
return True
elif dlg == widgetUtils.NO:
return False
class eventsBufferController(bufferController):
def __init__(self, parent, name, session, account, *args, **kwargs):
super(eventsBufferController, self).__init__(parent, *args, **kwargs)
log.debug("Initializing buffer %s, account %s" % (name, account,))
self.invisible = True
self.buffer = buffers.eventsPanel(parent, name)
self.name = name
self.account = account
self.buffer.account = self.account
self.compose_function = compose.compose_event
self.session = session
self.type = self.buffer.type
self.get_formatted_message = self.get_message
def get_message(self):
if self.buffer.list.get_count() == 0: return _(u"Empty")
# fix this:
return "%s. %s" % (self.buffer.list.list.GetItemText(self.buffer.list.get_selected()), self.buffer.list.list.GetItemText(self.buffer.list.get_selected(), 1))
def add_new_item(self, item):
tweet = self.compose_function(item, self.session.db["user_name"])
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))
if self.buffer.list.get_count() == 1:
self.buffer.list.select_item(0)
def clear_list(self):
dlg = commonMessageDialogs.clear_list()
if dlg == widgetUtils.YES:
self.buffer.list.clear()
class peopleBufferController(baseBufferController):
def __init__(self, parent, function, name, sessionObject, account, bufferType=None, *args, **kwargs):
super(peopleBufferController, self).__init__(parent, function, name, sessionObject, account, bufferType="peoplePanel")
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
def remove_buffer(self):
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"]))
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 message.image == None:
call_threaded(self.session.api_call, call_name="update_status", _sound="reply_send.ogg", status=message.message.get_text())
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):
log.debug("Starting stream for %s buffer, %s account" % (self.name, self.account,))
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
val = self.session.get_cursored_stream(self.name, self.function, *self.args, **self.kwargs)
# self.session.order_cursored_buffer(self.name, self.session.db[self.name])
# log.debug("Number of items retrieved: %d" % (val,))
self.put_items_on_list(val)
def get_more_items(self):
try:
items = self.session.get_more_items(self.function, users=True, name=self.name, count=self.session.settings["general"]["max_tweets_per_call"], cursor=self.session.db[self.name]["cursor"])
except TwythonError as e:
output.speak(e.message, True)
return
for i in items:
if self.session.settings["general"]["reverse_timelines"] == False:
self.session.db[self.name]["items"].insert(0, i)
else:
self.session.db[self.name]["items"].append(i)
selected = self.buffer.list.get_selected()
# self.put_items_on_list(len(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.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.buffer.list.insert_item(True, *tweet)
# self.buffer.list.select_item(selection)
# else:
# self.buffer.list.select_item(selection-elements)
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]["items"]:
tweet = self.compose_function(i, self.session.db, self.session.settings["general"]["relative_times"])
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]["items"][:number_of_items]:
tweet = self.compose_function(i, self.session.db)
self.buffer.list.insert_item(False, *tweet)
else:
for i in self.session.db[self.name]["items"][0:number_of_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]["items"][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"])
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]["items"] = []
self.session.db[self.name]["cursor"] = -1
self.buffer.list.clear()
def interact(self):
user.profileController(self.session, user=self.get_right_tweet()["screen_name"])
class searchBufferController(baseBufferController):
def start_stream(self):
log.debug("Starting stream for %s buffer, %s account and %s type" % (self.name, self.account, self.type))
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
log.debug("Function: %s" % (self.function,))
# try:
val = self.session.search(self.name, *self.args, **self.kwargs)
# except:
# return None
num = self.session.order_buffer(self.name, val)
self.put_items_on_list(num)
if num > 0:
self.session.sound.play("search_updated.ogg")
def remove_buffer(self):
dlg = commonMessageDialogs.remove_buffer()
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.timer.cancel()
return True
elif dlg == widgetUtils.NO:
return False
class searchPeopleBufferController(peopleBufferController):
def __init__(self, parent, function, name, sessionObject, account, bufferType="peoplePanel", *args, **kwargs):
super(searchPeopleBufferController, 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.args = args
self.kwargs = kwargs
self.function = function
def start_stream(self):
log.debug("starting stream for %s buffer, %s account and %s type" % (self.name, self.account, self.type))
log.debug("args: %s, kwargs: %s" % (self.args, self.kwargs))
log.debug("Function: %s" % (self.function,))
# try:
val = self.session.call_paged(self.function, *self.args, **self.kwargs)
# except:
# return
number_of_items = self.session.order_cursored_buffer(self.name, val)
log.debug("Number of items retrieved: %d" % (number_of_items,))
self.put_items_on_list(number_of_items)
if number_of_items > 0:
self.session.sound.play("search_updated.ogg")
def remove_buffer(self):
dlg = commonMessageDialogs.remove_buffer()
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.timer.cancel()
return True
elif dlg == widgetUtils.NO:
return False
class trendsBufferController(bufferController):
def __init__(self, parent, name, session, account, trendsFor, *args, **kwargs):
super(trendsBufferController, self).__init__(parent=parent, session=session)
self.trendsFor = trendsFor
self.session = session
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
def start_stream(self):
try:
data = self.session.call_paged("get_place_trends", id=self.trendsFor)
except:
return
if not hasattr(self, "name_"):
self.name_ = data[0]["locations"][0]["name"]
self.trends = data[0]["trends"]
self.put_items_on_the_list()
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.post_tweet, self.buffer.tweet)
# widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.retweet, self.buffer.retweet)
# widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.direct_message, self.buffer.dm)
# widgetUtils.connect_event(self.buffer, widgetUtils.BUTTON_PRESSED, self.reply, self.buffer.reply)
def get_message(self):
return self.compose_function(self.trends[self.buffer.list.get_selected()])[0]
def remove_buffer(self):
dlg = commonMessageDialogs.remove_buffer()
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.timer.cancel()
return True
elif dlg == widgetUtils.NO:
return False
def interact(self, *args, **kwargs):
self.searchfunction(value=self.get_message())
class conversationBufferController(searchBufferController):
def start_stream(self, start=False):
if start == True:
self.statuses = []
self.ids = []
self.statuses.append(self.tweet)
self.ids.append(self.tweet["id"])
tweet = self.tweet
while tweet["in_reply_to_status_id"] != None:
tweet = self.session.twitter.twitter.show_status(id=tweet["in_reply_to_status_id"])
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, *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:
self.session.sound.play("search_updated.ogg")

View File

@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
import widgetUtils
import output
from wxUI.dialogs import lists
from twython import TwythonError
from twitter import compose, utils
from pubsub import pub
class listsController(object):
def __init__(self, session, user=None):
super(listsController, self).__init__()
self.session = session
if user == None:
self.dialog = lists.listViewer()
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.editBtn, widgetUtils.BUTTON_PRESSED, self.edit_list)
widgetUtils.connect_event(self.dialog.view, widgetUtils.BUTTON_PRESSED, self.open_list_as_buffer)
self.dialog.get_response()
def get_all_lists(self):
return [compose.compose_list(item) for item in self.session.db["lists"]]
def create_list(self, *args, **kwargs):
dialog = lists.createListDialog()
if dialog.get_response() == widgetUtils.OK:
name = dialog.get("name")
description = dialog.get("description")
p = dialog.get("public")
if public == True:
mode = "public"
else:
mode = "private"
try:
new_list = self.session.twitter.twitter.create_list(name=name, description=description, mode=mode)
self.session.db["lists"].append(new_list)
self.dialog.lista.insert_item(False, *compose.compose_list(new_list))
except TwythonError as e:
output.speak("error %s: %s" % (e.status_code, e.msg))
dialog.destroy()
def edit_list(self, *args, **kwargs):
if self.dialog.lista.get_count() == 0: return
list = self.session.db["lists"][self.dialog.get_item()]
dialog = lists.editListDialog(list)
if dialog.get_response() == widgetUtils.OK:
name = dialog.get("name")
description = dialog.get("description")
p = dialog.get("public")
if p == True:
mode = "public"
else:
mode = "private"
try:
self.session.twitter.twitter.update_list(list_id=list["id"], name=name, description=description, mode=mode)
self.session.get_lists()
self.dialog.populate_list(self.get_all_lists(), True)
except TwythonError as e:
output.speak("error %s: %s" % (e.error_code, e.msg))
dialog.destroy()
def remove_list(self, *args, **kwargs):
if self.dialog.lista.get_count() == 0: return
list = self.session.db["lists"][self.dialog.get_item()]["id"]
if lists.remove_list() == widgetUtils.YES:
try:
self.session.twitter.twitter.delete_list(list_id=list)
self.session.db["lists"].pop(self.dialog.get_item())
self.dialog.lista.remove_item(self.dialog.get_item())
except TwythonError as e:
output.speak("error %s: %s" % (e.error_code, e.msg))
def open_list_as_buffer(self, *args, **kwargs):
if self.dialog.lista.get_count() == 0: return
list = self.session.db["lists"][self.dialog.get_item()]
pub.sendMessage("create-new-buffer", buffer="list", account=self.session.db["user_name"], create=list["slug"])

File diff suppressed because it is too large Load Diff

186
src/controller/messages.py Normal file
View File

@@ -0,0 +1,186 @@
# -*- coding: utf-8 -*-
import platform
system = platform.system()
import widgetUtils
import output
import url_shortener
import sound
from pubsub import pub
if system == "Windows":
from wxUI.dialogs import message, urlList
from extra import translator, SpellChecker, autocompletionUsers
from extra.AudioUploader import audioUploader
elif system == "Linux":
from gtkUI.dialogs import message
from twitter import utils
class basicTweet(object):
""" This class handles the tweet main features. Other classes should derive from this class."""
def __init__(self, session, title, caption, text, messageType="tweet", max=140):
super(basicTweet, self).__init__()
self.max = max
self.title = title
self.session = session
self.message = getattr(message, messageType)(title, caption, text)
widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck)
widgetUtils.connect_event(self.message.attach, widgetUtils.BUTTON_PRESSED, self.attach)
# if system == "Windows":
widgetUtils.connect_event(self.message.text, widgetUtils.ENTERED_TEXT, self.text_processor)
self.text_processor()
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.translateButton, widgetUtils.BUTTON_PRESSED, self.translate)
def translate(self, event=None):
dlg = translator.gui.translateDialog()
if dlg.get_response() == widgetUtils.OK:
text_to_translate = self.message.get_text().encode("utf-8")
source = [x[0] for x in translator.translator.available_languages()][dlg.get("source_lang")]
dest = [x[0] for x in translator.translator.available_languages()][dlg.get("dest_lang")]
msg = translator.translator.translate(text_to_translate, source, dest)
self.message.set_text(msg)
self.message.text_focus()
output.speak(_(u"Translated"))
else:
return
def shorten(self, event=None):
urls = utils.find_urls_in_text(self.message.get_text())
if len(urls) == 0:
output.speak(_(u"There's no URL to be shortened"))
self.message.text_focus()
elif len(urls) == 1:
self.message.set_text(self.message.get_text().replace(urls[0], url_shortener.shorten(urls[0])))
output.speak(_(u"URL shortened"))
self.message.text_focus()
elif len(urls) > 1:
list_urls = urlList.urlList()
list_urls.populate_list(urls)
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())))
output.speak(_(u"URL shortened"))
self.message.text_focus()
def unshorten(self, event=None):
urls = utils.find_urls_in_text(self.message.get_text())
if len(urls) == 0:
output.speak(_(u"There's no URL to be expanded"))
self.message.text_focus()
elif len(urls) == 1:
self.message.set_text(self.message.get_text().replace(urls[0], url_shortener.unshorten(urls[0])))
output.speak(_(u"URL expanded"))
self.message.text_focus()
elif len(urls) > 1:
list_urls = urlList.urlList()
list_urls.populate_list(urls)
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())))
output.speak(_(u"URL expanded"))
self.message.text_focus()
def text_processor(self, *args, **kwargs):
self.message.set_title(_(u"%s - %s of %d characters") % (self.title, len(self.message.get_text()), self.max))
if len(self.message.get_text()) > 1:
self.message.enable_button("shortenButton")
self.message.enable_button("unshortenButton")
else:
self.message.disable_button("shortenButton")
self.message.disable_button("unshortenButton")
if len(self.message.get_text()) > self.max:
self.session.sound.play("max_length.ogg")
def spellcheck(self, event=None):
text = self.message.get_text()
checker = SpellChecker.spellchecker.spellChecker(text, "")
if hasattr(checker, "fixed_text"):
self.message.set_text(checker.fixed_text)
def attach(self, *args, **kwargs):
def completed_callback():
url = dlg.uploaderFunction.get_url()
pub.unsubscribe(dlg.uploaderDialog.update, "uploading")
dlg.uploaderDialog.destroy()
if url != 0:
self.message.set_text(self.message.get_text()+url+" #audio")
else:
output.speak(_(u"Unable to upload the audio"))
dlg.cleanup()
dlg = audioUploader.audioUploader(self.session.settings, completed_callback)
class tweet(basicTweet):
def __init__(self, session, title, caption, text, messageType="tweet", max=140):
super(tweet, self).__init__(session, title, caption, text, messageType, max)
self.image = None
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)
def upload_image(self, *args, **kwargs):
if self.message.get("upload_image") == _(u"Discard image"):
del self.image
self.image = None
output.speak(_(u"Discarded"))
self.message.set("upload_image", _(u"Upload a picture"))
else:
self.image = self.message.get_image()
if self.image != None:
self.message.set("upload_image", _(u"Discard image"))
def autocomplete_users(self, *args, **kwargs):
c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id)
c.show_menu()
class reply(tweet):
def __init__(self, session, title, caption, text, users=None):
super(reply, self).__init__(session, title, caption, text, messageType="reply")
self.users = users
if self.users != None and len(self.users) > 1:
widgetUtils.connect_event(self.message.mentionAll, widgetUtils.BUTTON_PRESSED, self.mention_all)
self.message.enable_button("mentionAll")
self.message.set_cursor_at_end()
def mention_all(self, *args, **kwargs):
self.message.set_text(self.message.get_text()+self.users)
self.message.set_cursor_at_end()
self.message.text_focus()
class dm(basicTweet):
def __init__(self, session, title, caption, text):
super(dm, self).__init__(session, title, caption, text, messageType="dm")
widgetUtils.connect_event(self.message.autocompletionButton, widgetUtils.BUTTON_PRESSED, self.autocomplete_users)
def autocomplete_users(self, *args, **kwargs):
c = autocompletionUsers.completion.autocompletionUsers(self.message, self.session.session_id)
c.show_menu("dm")
class viewTweet(basicTweet):
def __init__(self, tweet, tweetList, is_tweet=True):
if is_tweet == True:
text = ""
for i in xrange(0, len(tweetList)):
if tweetList[i].has_key("retweeted_status"):
text = text + "rt @%s: %s\n\n" % (tweetList[i]["retweeted_status"]["user"]["screen_name"], tweetList[i]["retweeted_status"]["text"])
else:
text = text + "@%s: %s\n\n" % (tweetList[i]["user"]["screen_name"], tweetList[i]["text"])
rt_count = str(tweet["retweet_count"])
favs_count = str(tweet["favorite_count"])
if text == "":
if tweet.has_key("retweeted_status"):
text = "rt @%s: %s" % (tweet["retweeted_status"]["user"]["screen_name"], tweet["retweeted_status"]["text"])
else:
text = tweet["text"]
self.message = message.viewTweet(text, rt_count, favs_count)
self.message.set_title(len(text))
else:
text = tweet
self.message = message.viewNonTweet(text)
widgetUtils.connect_event(self.message.spellcheck, widgetUtils.BUTTON_PRESSED, self.spellcheck)
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):
if len(utils.find_urls_in_text(self.message.get_text())) > 0:
return True
return False

281
src/controller/settings.py Normal file
View File

@@ -0,0 +1,281 @@
# -*- coding: utf-8 -*-
import os
import webbrowser
import sound_lib
import paths
import widgetUtils
import config
import languageHandler
import output
import application
from wxUI.dialogs import configuration
from wxUI import commonMessageDialogs
from extra.autocompletionUsers import settings
from extra.AudioUploader import dropbox_transfer
from pubsub import pub
import logging
import config_utils
log = logging.getLogger("Settings")
class globalSettingsController(object):
def __init__(self):
super(globalSettingsController, self).__init__()
self.dialog = configuration.configurationDialog()
self.create_config()
self.needs_restart = False
self.is_started = True
def make_kmmap(self):
res={}
for i in os.listdir(paths.app_path('keymaps')):
if ".keymap" not in i:
continue
try:
res[config_utils.load_config(paths.app_path('keymaps/'+i))['info']['name']]=i
except:
log.exception("Exception while loading keymap " + i)
return res
def create_config(self):
self.kmmap=self.make_kmmap()
self.langs = languageHandler.getAvailableLanguages()
langs = []
[langs.append(i[1]) for i in self.langs]
self.codes = []
[self.codes.append(i[0]) for i in self.langs]
id = self.codes.index(config.app["app-settings"]["language"])
self.kmfriendlies=[]
self.kmnames=[]
for k,v in self.kmmap.items():
self.kmfriendlies.append(k)
self.kmnames.append(v)
self.kmid=self.kmnames.index(config.app['app-settings']['load_keymap'])
self.dialog.create_general(langs,self.kmfriendlies)
self.dialog.general.language.SetSelection(id)
self.dialog.general.km.SetSelection(self.kmid)
self.dialog.set_value("general", "ask_at_exit", config.app["app-settings"]["ask_at_exit"])
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", "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", "hide_gui", config.app["app-settings"]["hide_gui"])
self.dialog.create_proxy()
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", "user", config.app["proxy"]["user"])
self.dialog.set_value("proxy", "password", config.app["proxy"]["password"])
self.dialog.realize()
self.response = self.dialog.get_response()
def save_configuration(self):
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()]
languageHandler.setLanguage(config.app["app-settings"]["language"])
self.needs_restart = True
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()]
self.needs_restart = True
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")
pub.sendMessage("invisible-shorcuts-changed", registered=self.dialog.get_value("general", "use_invisible_shorcuts"))
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"]["ask_at_exit"] = self.dialog.get_value("general", "ask_at_exit")
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")
if 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:
self.needs_restart = True
config.app["proxy"]["server"] = self.dialog.get_value("proxy", "server")
config.app["proxy"]["port"] = self.dialog.get_value("proxy", "port")
config.app["proxy"]["user"] = self.dialog.get_value("proxy", "user")
config.app["proxy"]["password"] = self.dialog.get_value("proxy", "password")
config.app.write()
class accountSettingsController(globalSettingsController):
def __init__(self, buffer, window):
self.user = buffer.session.db["user_name"]
self.buffer = buffer
self.window = window
self.config = buffer.session.settings
super(accountSettingsController, self).__init__()
def create_config(self):
self.dialog.create_general_account()
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", "apiCalls", self.config["general"]["max_api_calls"])
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"])
rt = self.config["general"]["retweet_mode"]
if rt == "ask":
self.dialog.set_value("general", "retweet_mode", _(u"Ask"))
elif rt == "direct":
self.dialog.set_value("general", "retweet_mode", _(u"Retweet without comments"))
else:
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.create_other_buffers()
buffer_values = self.get_buffers_list()
self.dialog.buffers.insert_buffers(buffer_values)
self.dialog.buffers.connect_hook_func(self.toggle_buffer_active)
widgetUtils.connect_event(self.dialog.buffers.toggle_state, widgetUtils.BUTTON_PRESSED, self.toggle_state)
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"])
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)
self.input_devices = sound_lib.input.Input.get_device_names()
self.output_devices = sound_lib.output.Output.get_device_names()
self.soundpacks = []
[self.soundpacks.append(i) for i in os.listdir(paths.sound_path()) if os.path.isdir(paths.sound_path(i)) == True ]
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", "input", self.config["sound"]["input_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", "soundpack", self.config["sound"]["current_soundpack"])
self.dialog.create_audio_services()
if self.config["services"]["dropbox_token"] == "":
self.dialog.services.set_dropbox(False)
else:
self.dialog.services.set_dropbox(True)
widgetUtils.connect_event(self.dialog.services.dropbox, widgetUtils.BUTTON_PRESSED, self.manage_dropbox)
self.dialog.set_value("services", "apiKey", self.config["sound"]["sndup_api_key"])
self.dialog.realize()
self.dialog.set_title(_(u"Account settings for %s") % (self.user,))
self.response = self.dialog.get_response()
def save_configuration(self):
if self.config["general"]["relative_times"] != self.dialog.get_value("general", "relative_time"):
self.needs_restart = True
self.config["general"]["relative_times"] = self.dialog.get_value("general", "relative_time")
self.config["general"]["max_api_calls"] = self.dialog.get_value("general", "apiCalls")
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.dialog.get_value("general", "persist_size") == '':
self.config["general"]["persist_size"] =-1
else:
try:
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"):
self.needs_restart = True
self.config["general"]["reverse_timelines"] = self.dialog.get_value("general", "reverse_timelines")
rt = self.dialog.get_value("general", "retweet_mode")
if rt == _(u"Ask"):
self.config["general"]["retweet_mode"] = "ask"
elif rt == _(u"Retweet without comments"):
self.config["general"]["retweet_mode"] = "direct"
else:
self.config["general"]["retweet_mode"] = "comment"
buffers_list = self.dialog.buffers.get_list()
if set(self.config["general"]["buffer_order"]) != set(buffers_list) or buffers_list != self.config["general"]["buffer_order"]:
self.needs_restart = True
self.config["general"]["buffer_order"] = buffers_list
# 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")
# pub.sendMessage("create-new-buffer", buffer="followers", account=self.user, create=self.config["other_buffers"]["show_followers"])
# if self.config["other_buffers"]["show_friends"] != self.dialog.get_value("buffers", "friends"):
# self.config["other_buffers"]["show_friends"] = self.dialog.get_value("buffers", "friends")
# pub.sendMessage("create-new-buffer", buffer="friends", account=self.user, create=self.config["other_buffers"]["show_friends"])
# if self.config["other_buffers"]["show_favourites"] != self.dialog.get_value("buffers", "favs"):
# self.config["other_buffers"]["show_favourites"] = self.dialog.get_value("buffers", "favs")
# pub.sendMessage("create-new-buffer", buffer="favourites", account=self.user, create=self.config["other_buffers"]["show_favourites"])
# if self.config["other_buffers"]["show_blocks"] != self.dialog.get_value("buffers", "blocks"):
# self.config["other_buffers"]["show_blocks"] = self.dialog.get_value("buffers", "blocks")
# pub.sendMessage("create-new-buffer", buffer="blocked", account=self.user, create=self.config["other_buffers"]["show_blocks"])
# if self.config["other_buffers"]["show_muted_users"] != self.dialog.get_value("buffers", "mutes"):
# self.config["other_buffers"]["show_muted_users"] = self.dialog.get_value("buffers", "mutes")
# pub.sendMessage("create-new-buffer", buffer="muted", account=self.user, create=self.config["other_buffers"]["show_muted_users"])
# 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")
# 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"):
self.config["sound"]["input_device"] = self.dialog.sound.get("input")
try:
self.buffer.session.sound.input.set_device(self.buffer.session.sound.input.find_device_by_name(self.config["sound"]["input_device"]))
except:
self.config["sound"]["input_device"] = "default"
if self.config["sound"]["output_device"] != self.dialog.sound.get("output"):
self.config["sound"]["output_device"] = self.dialog.sound.get("output")
try:
self.buffer.session.sound.output.set_device(self.buffer.session.sound.output.find_device_by_name(self.config["sound"]["output_device"]))
except:
self.config["sound"]["output_device"] = "default"
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"]["current_soundpack"] = self.dialog.sound.get("soundpack")
self.buffer.session.sound.config = self.config["sound"]
self.buffer.session.sound.check_soundpack()
self.config["sound"]["sndup_api_key"] = self.dialog.get_value("services", "apiKey")
def toggle_state(self,*args,**kwargs):
return self.dialog.buffers.change_selected_item()
def manage_autocomplete(self, *args, **kwargs):
configuration = settings.autocompletionSettings(self.buffer.session.settings, self.buffer, self.window)
def add_ignored_client(self, *args, **kwargs):
client = commonMessageDialogs.get_ignored_client()
if client == None: return
if client not in self.config["twitter"]["ignored_clients"]:
self.config["twitter"]["ignored_clients"].append(client)
self.dialog.ignored_clients.append(client)
def remove_ignored_client(self, *args, **kwargs):
if self.dialog.ignored_clients.get_clients() == 0: return
id = self.dialog.ignored_clients.get_client_id()
self.config["twitter"]["ignored_clients"].pop(id)
self.dialog.ignored_clients.remove_(id)
def manage_dropbox(self, *args, **kwargs):
if self.dialog.services.get_dropbox() == _(u"Link your Dropbox account"):
self.connect_dropbox()
else:
self.disconnect_dropbox()
def connect_dropbox(self):
auth = dropbox_transfer.dropboxLogin(self.config)
url = auth.get_url()
self.dialog.services.show_dialog()
webbrowser.open(url)
resp = self.dialog.services.get_response()
if resp == "":
self.dialog.services.set_dropbox(False)
else:
try:
auth.authorise(resp)
self.dialog.services.set_dropbox()
except:
self.dialog.services.show_error()
self.dialog.services.set_dropbox(False)
def disconnect_dropbox(self):
self.config["services"]["dropbox_token"] = ""
self.dialog.services.set_dropbox(False)
def get_buffers_list(self):
all_buffers = ['home','mentions','dm','sent_dm','sent_tweets','favorites','followers','friends','blocks','muted','events']
list_buffers = []
hidden_buffers=[]
for i in all_buffers:
if i in self.config["general"]["buffer_order"]:
list_buffers.append((i, True))
else:
hidden_buffers.append((i, False))
list_buffers.extend(hidden_buffers)
return list_buffers
def toggle_buffer_active(self, ev):
change = self.dialog.buffers.get_event(ev)
if change == True:
self.dialog.buffers.change_selected_item()

View File

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

102
src/controller/user.py Normal file
View File

@@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
import webbrowser
import widgetUtils
import output
from wxUI.dialogs import update_profile, show_user
from twython import TwythonError
class profileController(object):
def __init__(self, session, user=None):
super(profileController, self).__init__()
self.file = None
self.session = session
self.user = user
if user == None:
self.dialog = update_profile.updateProfileDialog()
self.get_data(screen_name=self.session.db["user_name"])
self.fill_profile_fields()
self.uploaded = False
widgetUtils.connect_event(self.dialog.upload_image, widgetUtils.BUTTON_PRESSED, self.upload_image)
else:
self.dialog = show_user.showUserProfile()
self.get_data(screen_name=self.user)
string = self.get_user_info()
self.dialog.set("text", string)
self.dialog.set_title(_(u"Information for %s") % (self.data["screen_name"]))
if self.data["url"] != None:
self.dialog.enable_url()
widgetUtils.connect_event(self.dialog.url, widgetUtils.BUTTON_PRESSED, self.visit_url)
if self.dialog.get_response() == widgetUtils.OK and self.user == None:
self.do_update()
def get_data(self, screen_name):
self.data = self.session.twitter.twitter.show_user(screen_name=screen_name)
def fill_profile_fields(self):
self.dialog.set_name(self.data["name"])
if self.data["url"] != None:
self.dialog.set_url(self.data["url"])
if len(self.data["location"]) > 0:
self.dialog.set_location(self.data["location"])
if len(self.data["description"]) > 0:
self.dialog.set_description(self.data["description"])
def get_image(self):
file = self.dialog.upload_picture()
if file != None:
self.file = open(file, "rb")
self.uploaded = True
self.dialog.change_upload_button(self.uploaded)
def discard_image(self):
self.file = None
output.speak(_(u"Discarded"))
self.uploaded = False
self.dialog.change_upload_button(self.uploaded)
def upload_image(self, *args, **kwargs):
if self.uploaded == False:
self.get_image()
elif self.uploaded == True:
self.discard_image()
def do_update(self):
if self.user != None: return
name = self.dialog.get("name")
description = self.dialog.get("description")
location = self.dialog.get("location")
url = self.dialog.get("url")
if self.file != None:
try:
self.session.twitter.twitter.update_profile_image(image=self.file)
except TwythonError as e:
output.speak(u"Error %s. %s" % (e.error_code, e.msg))
try:
self.session.twitter.twitter.update_profile(name=name, description=description, location=location, url=url)
except TwythonError as e:
output.speak(u"Error %s. %s" % (e.error_code, e.msg))
def get_user_info(self):
string = u""
string = string + _(u"Username: @%s\n") % (self.data["screen_name"])
string = string + _(u"Name: %s\n") % (self.data["name"])
if self.data["location"] != "":
string = string + _(u"Location: %s\n") % (self.data["location"])
if self.data["url"] != None:
string = string+ _(u"URL: %s\n") % (self.data["url"])
if self.data["description"] != "":
string = string+ _(u"Bio: %s\n") % (self.data["description"])
if self.data["protected"] == True: protected = _(u"Yes")
else: protected = _(u"No")
string = string+ _(u"Protected: %s\n") % (protected)
string = string+_(u"Followers: %s\n Friends: %s\n") % (self.data["followers_count"], self.data["friends_count"])
if self.data["verified"] == True: verified = _(u"Yes")
else: verified = _(u"No")
string = string+ _(u"Verified: %s\n") % (verified)
string = string+ _(u"Tweets: %s\n") % (self.data["statuses_count"])
string = string+ _(u"Favourites: %s") % (self.data["favourites_count"])
return string
def visit_url(self, *args, **kwargs):
webbrowser.open_new_tab(self.data["url"])

View File

@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
from wxUI.dialogs import userActions
from pubsub import pub
import re
import widgetUtils
import output
from twython import TwythonError
class userActionsController(object):
def __init__(self, buffer, users=[], default="follow"):
super(userActionsController, self).__init__()
self.buffer = buffer
self.session = buffer.session
self.dialog = userActions.UserActionsDialog(users, default)
if self.dialog.get_response() == widgetUtils.OK:
self.process_action()
def process_action(self):
action = self.dialog.get_action()
user = self.dialog.get_user()
if user == "": return
getattr(self, action)(user)
def follow(self, user):
try:
self.session.twitter.twitter.create_friendship(screen_name=user )
except TwythonError as err:
output.speak("Error %s: %s" % (err.error_code, err.msg), True)
def unfollow(self, user):
try:
id = self.session.twitter.twitter.destroy_friendship(screen_name=user )
except TwythonError as err:
output.speak("Error %s: %s" % (err.error_code, err.msg), True)
def mute(self, user):
try:
id = self.session.twitter.twitter.create_mute(screen_name=user )
except TwythonError as err:
output.speak("Error %s: %s" % (err.error_code, err.msg), True)
def unmute(self, user):
try:
id = self.session.twitter.twitter.destroy_mute(screen_name=user )
except TwythonError as err:
output.speak("Error %s: %s" % (err.error_code, err.msg), True)
def report(self, user):
try:
id = self.session.twitter.twitter.report_spam(screen_name=user )
except TwythonError as err:
output.speak("Error %s: %s" % (err.error_code, err.msg), True)
def block(self, user):
try:
id = self.session.twitter.twitter.create_block(screen_name=user )
except TwythonError as err:
output.speak("Error %s: %s" % (err.error_code, err.msg), True)
def unblock(self, user):
try:
id = self.session.twitter.twitter.destroy_block(screen_name=user )
except TwythonError as err:
output.speak("Error %s: %s" % (err.error_code, err.msg), True)
def ignore_client(self, user):
tweet = self.buffer.get_right_tweet()
if tweet.has_key("sender"):
output.speak(_(u"You can't ignore direct messages"))
return
client = re.sub(r"(?s)<.*?>", "", tweet["source"])
if client not in self.session.settings["twitter"]["ignored_clients"]:
self.session.settings["twitter"]["ignored_clients"].append(client)

View File

@@ -1,5 +0,0 @@
# -*- coding: utf-8 -*-
""" Handles storage from a durus database """
class db(object):
def __init__(self):
self.settings = {}

View File

@@ -1,3 +0,0 @@
import gui, transfer, transfer_dialogs, platform
if platform.system() != "Darwin":
import dropbox

View File

@@ -0,0 +1,189 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
############################################################
import widgetUtils
import wx_ui
import wx_transfer_dialogs
import dropbox_transfer, transfer
import output
import tempfile
import sound
import os
import config
from pubsub import pub
from mysc.thread_utils import call_threaded
import sound_lib
import logging
log = logging.getLogger("extra.AudioUploader.audioUploader")
class audioUploader(object):
def __init__(self, configFile, completed_callback):
self.config = configFile
super(audioUploader, self).__init__()
self.dialog = wx_ui.audioDialog(services=self.get_available_services())
self.file = None
self.recorded = False
self.recording = None
self.playing = None
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.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.discard, widgetUtils.BUTTON_PRESSED, self.on_discard)
if self.dialog.get_response() == widgetUtils.OK:
self.postprocess()
log.debug("Uploading file %s to %s..." % (self.file, self.dialog.get("services")))
self.uploaderDialog = wx_transfer_dialogs.UploadDialog(self.file)
output.speak(_(u"Attaching..."))
if self.dialog.get("services") == "Dropbox":
self.uploaderFunction = dropbox_transfer.dropboxUploader(filename=self.file, completed_callback=completed_callback, config=self.config)
elif self.dialog.get("services") == "SNDUp":
base_url = "http://sndup.net/post.php"
if len(self.config["sound"]["sndup_api_key"]) > 0:
url = base_url + '?apikey=' + self.config['sound']['sndup_api_key']
else:
url = base_url
self.uploaderFunction = transfer.Upload(field='file', url=url, filename=self.file, completed_callback=completed_callback)
elif self.dialog.get("services") == "TwUp":
url = "http://api.twup.me/post.json"
self.uploaderFunction = transfer.Upload(field='file', url=url, filename=self.file, completed_callback=completed_callback)
pub.subscribe(self.uploaderDialog.update, "uploading")
self.uploaderDialog.get_response()
self.uploaderFunction.perform_threaded()
def get_available_services(self):
services = []
services.append("TwUp")
if self.config["services"]["dropbox_token"] != "":
services.append("Dropbox")
services.append("SNDUp")
return services
def on_pause(self, *args, **kwargs):
if self.dialog.get("pause") == _(u"Pause"):
self.recording.pause()
self.dialog.set("pause", _(u"Resume"))
elif self.dialog.get("pause") == _(u"Resume"):
self.recording.play()
self.dialog.set("pause", _(U"Pause"))
def on_record(self, *args, **kwargs):
if self.recording != None:
self.stop_recording()
self.dialog.disable_control("pause")
else:
self.start_recording()
self.dialog.enable_control("pause")
def start_recording(self):
self.dialog.disable_control("attach_exists")
self.file = tempfile.mktemp(suffix='.wav')
self.recording = sound.recording(self.file)
self.recording.play()
self.dialog.set("record", _(u"Stop"))
output.speak(_(u"Recording"))
def stop_recording(self):
self.recording.stop()
self.recording.free()
output.speak(_(u"Stopped"))
self.recorded = True
self.dialog.set("record", _(u"Record"))
self.file_attached()
def file_attached(self):
self.dialog.set("pause", _(u"Pause"))
self.dialog.disable_control("record")
self.dialog.enable_control("play")
self.dialog.enable_control("discard")
self.dialog.disable_control("attach_exists")
self.dialog.enable_control("attach")
self.dialog.play.SetFocus()
def on_discard(self, *args, **kwargs):
if self.playing:
self._stop()
if self.recording != None:
self.dialog.disable_control("attach")
self.dialog.disable_control("play")
self.file = None
self.dialog.enable_control("record")
self.dialog.enable_control("attach_exists")
self.dialog.record.SetFocus()
self.dialog.disable_control("discard")
self.recording = None
output.speak(_(u"Discarded"))
def on_play(self, *args, **kwargs):
if not self.playing:
call_threaded(self._play)
else:
self._stop()
def _play(self):
output.speak(_(u"Playing..."))
# try:
self.playing = sound_lib.stream.FileStream(file=unicode(self.file), flags=sound_lib.stream.BASS_UNICODE)
self.playing.play()
self.dialog.set("play", _(u"Stop"))
try:
while self.playing.is_playing:
pass
self.dialog.set("play", _(u"Play"))
self.playing.free()
self.playing = None
except:
pass
def _stop(self):
output.speak(_(u"Stopped"))
self.playing.stop()
self.playing.free()
self.dialog.set("play", _(u"Play"))
self.playing = None
def postprocess(self):
if self.file.lower().endswith('.wav'):
output.speak(_(u"Recoding audio..."))
sound.recode_audio(self.file)
self.wav_file = self.file
self.file = '%s.ogg' % self.file[:-4]
def cleanup(self):
if self.playing and self.playing.is_playing:
self.playing.stop()
if self.recording != None:
if self.recording.is_playing:
self.recording.stop()
try:
self.recording.free()
except:
pass
os.remove(self.file)
if hasattr(self, 'wav_file'):
os.remove(self.wav_file)
del(self.wav_file)
if hasattr(self, 'wav_file') and os.path.exists(self.file):
os.remove(self.file)
def on_attach_exists(self, *args, **kwargs):
self.file = self.dialog.get_file()
if self.file != False:
self.file_attached()

View File

@@ -3,14 +3,16 @@ import threading
import time
import os
import exceptions
import wx
import dropbox
import config
from mysc import event
import logging
import application
from keys import keyring
from utils import *
from dropbox.rest import ErrorResponse
from StringIO import StringIO
from pubsub import pub
log = logging.getLogger("extra.AudioUploader.dropbox_transfer")
class UnauthorisedError(exceptions.Exception):
def __init__(self, *args, **kwargs):
super(UnauthorisedError, self).__init__(*args, **kwargs)
@@ -39,29 +41,33 @@ class newChunkedUploader(dropbox.client.ChunkedUploader):
self.offset = reply['offset']
class dropboxLogin(object):
def __init__(self):
def __init__(self, config):
log.debug("Trying to login in Dropbox...")
self.logged = False
self.app_key = "c8ikm0gexqvovol"
self.app_secret = "gvvi6fzfecooast"
self.config = config
def get_url(self):
self.flow = dropbox.client.DropboxOAuth2FlowNoRedirect(self.app_key, self.app_secret)
log.debug("Getting autorisation URL...")
self.flow = dropbox.client.DropboxOAuth2FlowNoRedirect(keyring.get("dropbox_api_key"), keyring.get("dropbox_api_secret"))
return self.flow.start()
def authorise(self, code):
log.debug("Authorising " + application.name + " to Dropbox...")
access_token, user_id = self.flow.finish(code)
config.main["services"]["dropbox_token"] = access_token
log.debug("Saving tokens...")
self.config["services"]["dropbox_token"] = access_token
self.logged = True
class dropboxUploader(object):
def __init__(self, filename, completed_callback, wxDialog, short_url=False):
if config.main["services"]["dropbox_token"] != "":
self.client = dropbox.client.DropboxClient(config.main["services"]["dropbox_token"])
def __init__(self, config, filename, completed_callback, short_url=False):
if config["services"]["dropbox_token"] != "":
log.debug("logging in Dropbox...")
self.client = dropbox.client.DropboxClient(config["services"]["dropbox_token"])
else:
raise UnauthorisedError("You need authorise TWBlue")
log.error("Dropbox is not authorised for this session.")
raise UnauthorisedError("You need to authorise " + application.name)
self.filename = filename
self.short_url = short_url
self.wxDialog = wxDialog
self.file = open(self.filename, "rb")
self.file_size = os.path.getsize(self.filename)
self.uploader = newChunkedUploader(client=self.client, file_obj=self.file, length=self.file_size, callback=self.process)
@@ -70,6 +76,7 @@ class dropboxUploader(object):
self.background_thread = None
self.current = 0
self.transfer_rate = 0
log.debug("File Size: %d " % (self.file_size,))
def elapsed_time(self):
if not self.start_time:
@@ -77,6 +84,7 @@ class dropboxUploader(object):
return time.time() - self.start_time
def perform_transfer(self):
log.debug("Starting transfer...")
self.start_time = time.time()
while self.uploader.offset < self.file_size:
self.uploader.upload_chunked(self.file_size/100)
@@ -94,9 +102,7 @@ class dropboxUploader(object):
progress["eta"] = (progress["total"] - progress["current"]) / self.transfer_rate
else:
progress["eta"] = 0
info = event.event(event.EVT_OBJECT, 1)
info.SetItem(progress)
wx.PostEvent(self.wxDialog, info)
pub.sendMessage("uploading", data=progress)
def perform_threaded(self):
self.background_thread = threading.Thread(target=self.perform_transfer)
@@ -104,10 +110,12 @@ class dropboxUploader(object):
self.background_thread.start()
def transfer_completed(self):
log.debug("Transfer completed")
self.uploader.finish(os.path.basename(self.filename))
if callable(self.completed_callback):
self.completed_callback()
def get_url(self):
original = "%s" % (self.client.share(os.path.basename(self.filename), False)["url"])
return original.replace("dl=0", "dl=1")
original = "%s" % (self.client.media(os.path.basename(self.filename))["url"])
return original
# .replace("dl=0", "dl=1")

View File

@@ -1,191 +0,0 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
############################################################
import wx
import output
import tempfile
import sound
import os
import config
from mysc.thread_utils import call_threaded
import sound_lib
class audioDialog(wx.Dialog):
def __init__(self, parent):
self.parent = parent
wx.Dialog.__init__(self, None, -1, _(u"Attach audio"))
self.file = None
self.recorded = False
self.recording = None
self.playing = None
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.play = wx.Button(panel, -1, _(u"Play"))
self.play.Bind(wx.EVT_BUTTON, self.onPlay)
self.play.Disable()
self.pause = wx.Button(panel, -1, _(u"Pause"))
self.pause.Bind(wx.EVT_BUTTON, self.onPause)
self.pause.Disable()
self.record = wx.Button(panel, -1, _(u"Record"))
self.record.Bind(wx.EVT_BUTTON, self.onRecord)
self.record.SetFocus()
self.attach_exists = wx.Button(panel, -1, _(u"Add an existing file"))
self.attach_exists.Bind(wx.EVT_BUTTON, self.onAttach)
self.discard = wx.Button(panel, -1, _(u"Discard"))
self.discard.Bind(wx.EVT_BUTTON, self.onDiscard)
self.discard.Disable()
label = wx.StaticText(panel, -1, _(u"Upload to"))
self.services = wx.ComboBox(panel, -1, choices=self.get_available_services(), value=self.get_available_services()[0], style=wx.CB_READONLY)
servicesBox = wx.BoxSizer(wx.HORIZONTAL)
servicesBox.Add(label)
servicesBox.Add(self.services)
self.attach = wx.Button(panel, wx.ID_OK, _(u"Attach"))
self.attach.Disable()
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
sizer.Add(self.play)
sizer.Add(self.pause)
sizer.Add(self.record)
sizer.Add(self.attach_exists)
sizer.Add(self.discard)
sizer.Add(self.attach)
def get_available_services(self):
services = []
if config.main["services"]["dropbox_token"] != "":
services.append("Dropbox")
services.append("TwUp")
services.append("SNDUp")
return services
def onPause(self, ev):
if self.pause.GetLabel() == _(u"Pause"):
self.recording.pause()
self.pause.SetLabel(_(u"Resume"))
elif self.pause.GetLabel() == _(u"Resume"):
self.recording.play()
self.pause.SetLabel(_(U"Pause"))
def onRecord(self, ev):
if self.recording != None:
self.stop_recording()
self.pause.Disable()
else:
self.start_recording()
self.pause.Enable()
def start_recording(self):
self.attach_exists.Disable()
self.file = tempfile.mktemp(suffix='.wav')
self.recording = sound.recording(self.file)
self.recording.play()
self.record.SetLabel(_(u"Stop recording"))
output.speak(_(u"Recording"))
def stop_recording(self):
self.recording.stop()
self.recording.free()
output.speak(_(u"Stopped"))
self.recorded = True
self.record.SetLabel(_(u"Record"))
self.file_attached()
def file_attached(self):
self.pause.SetLabel(_(u"Pause"))
self.record.Disable()
self.play.Enable()
self.discard.Enable()
self.attach_exists.Disable()
self.attach.Enable()
self.play.SetFocus()
def onDiscard(self, evt):
evt.Skip()
if self.playing:
self._stop()
if self.recording != None:
self.attach.Disable()
self.play.Disable()
self.file = None
self.record.Enable()
self.attach_exists.Enable()
self.record.SetFocus()
self.discard.Disable()
self.recording = None
output.speak(_(u"Discarded"))
def onPlay(self, evt):
evt.Skip()
if not self.playing:
call_threaded(self._play)
else:
self._stop()
def _play(self):
output.speak(_(u"Playing..."))
# try:
self.playing = sound_lib.stream.FileStream(file=unicode(self.file), flags=sound_lib.stream.BASS_UNICODE)
self.playing.play()
self.play.SetLabel(_(u"Stop"))
try:
while self.playing.is_playing:
pass
self.play.SetLabel(_(u"Play"))
self.playing.free()
self.playing = None
except:
pass
def _stop(self):
output.speak(_(u"Stopped"))
self.playing.stop()
self.playing.free()
self.play.SetLabel(_(u"Play"))
self.playing = None
def postprocess(self):
if self.file.lower().endswith('.wav'):
output.speak(_(u"Recoding audio..."))
sound.recode_audio(self.file)
self.wav_file = self.file
self.file = '%s.ogg' % self.file[:-4]
def cleanup(self):
if self.playing and self.playing.is_playing:
self.playing.stop()
if self.recording != None:
if self.recording.is_playing:
self.recording.stop()
try:
self.recording.free()
except:
pass
os.remove(self.file)
if hasattr(self, 'wav_file'):
os.remove(self.wav_file)
del(self.wav_file)
if hasattr(self, 'wav_file') and os.path.exists(self.file):
os.remove(self.file)
def onAttach(self, ev):
openFileDialog = wx.FileDialog(self, _(u"Select the audio file to be uploaded"), "", "", _("Audio Files (*.mp3, *.ogg, *.wav)|*.mp3; *.ogg; *.wav"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
if openFileDialog.ShowModal() == wx.ID_CANCEL:
return
self.file = openFileDialog.GetPath()
self.file_attached()
ev.Skip()

View File

@@ -4,17 +4,17 @@ import sys
import threading
import time
import json
import wx
from mysc import event
import logging
from utils import *
from pubsub import pub
#__all__ = ['TransferDialog', 'DownloadDialog', 'UploadDialog']
log = logging.getLogger("extra.AudioUploader.transfer")
class Transfer(object):
def __init__(self, url=None, filename=None, follow_location=True, completed_callback=None, verbose=False, wxDialog=None, *args, **kwargs):
def __init__(self, url=None, filename=None, follow_location=True, completed_callback=None, verbose=False, *args, **kwargs):
self.url = url
self.filename = filename
log.debug("Uploading audio to %s, filename %s" % (url, filename))
self.curl = pycurl.Curl()
self.start_time = None
self.completed_callback = completed_callback
@@ -26,7 +26,6 @@ class Transfer(object):
self.curl.setopt(self.curl.HTTP_VERSION, self.curl.CURL_HTTP_VERSION_1_0)
self.curl.setopt(self.curl.FOLLOWLOCATION, int(follow_location))
self.curl.setopt(self.curl.VERBOSE, int(verbose))
self.wxDialog = wxDialog
super(Transfer, self).__init__(*args, **kwargs)
def elapsed_time(self):
@@ -52,15 +51,15 @@ class Transfer(object):
progress["eta"] = (progress["total"] - progress["current"]) / self.transfer_rate
else:
progress["eta"] = 0
info = event.event(event.EVT_OBJECT, 1)
info.SetItem(progress)
wx.PostEvent(self.wxDialog, info)
pub.sendMessage("uploading", data=progress)
def perform_transfer(self):
log.debug("starting upload...")
self.start_time = time.time()
self.curl.perform()
self.curl.close()
wx.CallAfter(self.complete_transfer)
log.debug("Upload finished.")
self.complete_transfer()
def perform_threaded(self):
self.background_thread = threading.Thread(target=self.perform_transfer)

View File

@@ -1,14 +1,12 @@
# -*- coding: utf-8 -*-
import wx
from mysc import event
from utils import *
import widgetUtils
__all__ = ['TransferDialog', 'DownloadDialog', 'UploadDialog']
class TransferDialog(wx.Dialog):
class TransferDialog(widgetUtils.BaseDialog):
def __init__(self, filename, *args, **kwargs):
super(TransferDialog, self).__init__(*args, **kwargs)
super(TransferDialog, self).__init__(parent=None, id=wx.NewId(), *args, **kwargs)
self.pane = wx.Panel(self)
self.progress_bar = wx.Gauge(parent=self.pane)
fileBox = wx.BoxSizer(wx.HORIZONTAL)
@@ -37,7 +35,7 @@ class TransferDialog(wx.Dialog):
self.eta = wx.TextCtrl(self.pane, -1, style=wx.TE_READONLY|wx.TE_MULTILINE, value="Unknown", size=(200, 100))
etaBox.Add(etaLabel)
etaBox.Add(self.eta)
# self.create_buttons()
self.create_buttons()
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(fileBox)
sizer.Add(currentAmountBox)
@@ -46,10 +44,8 @@ class TransferDialog(wx.Dialog):
sizer.Add(etaBox)
sizer.Add(self.progress_bar)
self.pane.SetSizerAndFit(sizer)
self.Bind(event.MyEVT_OBJECT, self.update)
def update(self, ev):
data = ev.GetItem()
def update(self, data):
wx.CallAfter(self.progress_bar.SetValue, data["percent"])
wx.CallAfter(self.current_amount.SetValue, '%s (%d%%)' % (convert_bytes(data["current"]), data["percent"]))
wx.CallAfter(self.total_size.SetValue, convert_bytes(data["total"]))
@@ -59,7 +55,12 @@ class TransferDialog(wx.Dialog):
def create_buttons(self):
self.cancel_button = wx.Button(parent=self.pane, id=wx.ID_CANCEL)
self.cancel_button.Bind(wx.EVT_BUTTON, self.on_cancel)
def get_response(self):
self.Show()
def destroy(self):
self.Destroy()
class UploadDialog(TransferDialog):

View File

@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
############################################################
import wx
import widgetUtils
import output
import logging
log = logging.getLogger("extra.AudioUploader.wx_UI")
class audioDialog(widgetUtils.BaseDialog):
def __init__(self, services):
log.debug("creating audio dialog.")
super(audioDialog, self).__init__(None, -1, _(u"Attach audio"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer2 = wx.BoxSizer(wx.HORIZONTAL)
self.play = wx.Button(panel, -1, _(u"Play"))
self.play.Disable()
self.pause = wx.Button(panel, -1, _(u"Pause"))
self.pause.Disable()
self.record = wx.Button(panel, -1, _(u"Record"))
self.record.SetFocus()
self.attach_exists = wx.Button(panel, -1, _(u"Add an existing file"))
self.discard = wx.Button(panel, -1, _(u"Discard"))
self.discard.Disable()
label = wx.StaticText(panel, -1, _(u"Upload to"))
self.services = wx.ComboBox(panel, -1, choices=services, value=services[0], style=wx.CB_READONLY)
servicesBox = wx.BoxSizer(wx.HORIZONTAL)
servicesBox.Add(label, 0, wx.ALL, 5)
servicesBox.Add(self.services, 0, wx.ALL, 5)
self.attach = wx.Button(panel, wx.ID_OK, _(u"Attach"))
self.attach.Disable()
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Cancel"))
btnSizer.Add(self.play, 0, wx.ALL, 5)
btnSizer.Add(self.pause, 0, wx.ALL, 5)
btnSizer.Add(self.record, 0, wx.ALL, 5)
btnSizer2.Add(self.attach_exists, 0, wx.ALL, 5)
btnSizer2.Add(self.discard, 0, wx.ALL, 5)
btnSizer2.Add(self.attach, 0, wx.ALL, 5)
btnSizer2.Add(cancel, 0, wx.ALL, 5)
sizer.Add(servicesBox, 0, wx.ALL, 5)
sizer.Add(btnSizer, 0, wx.ALL, 5)
sizer.Add(btnSizer2, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def enable_control(self, control):
log.debug("Enabling control %s" % (control,))
if hasattr(self, control):
getattr(self, control).Enable()
def disable_control(self, control):
log.debug("Disabling control %s" % (control,))
if hasattr(self, control):
getattr(self, control).Disable()
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)
if openFileDialog.ShowModal() == wx.ID_CANCEL:
return False
return openFileDialog.GetPath()

View File

@@ -1 +1 @@
import gui
from soundsTutorial import soundsTutorial

View File

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

View File

@@ -1,59 +0,0 @@
# -*- coding: utf-8 -*-
import wx
import config
import os
import paths
import sound
class soundsTutorial(wx.Dialog):
def __init__(self):
self.actions = [
_(u"The tweet may contain a playable audio"),
_(u"A timeline has been created"),
_(u"A timeline has been deleted"),
_(u"You've received a direct message"),
_(u"You've sent a direct message"),
_(u"A bug has happened"),
_(u"You've added a tweet to your favourites"),
_(u"Someone's favourites have been updated"),
_(u"There are no more tweets to read"),
_(u"A list has a new tweet"),
_(u"You can't add any more characters on the tweet"),
_(u"You've been mentioned "),
_(u"A new event has happened"),
_(u"TW Blue is ready "),
_(u"You've replied"),
_(u"You've retweeted"),
_(u"A search has been updated"),
_(u"There's a new tweet in the main buffer"),
_(u"You've sent a tweet"),
_(u"There's a new tweet in a timeline"),
_(u"You have a new follower"),
_(u"You've turned the volume up or down")]
self.files = os.listdir(paths.sound_path("default"))
super(soundsTutorial, self).__init__(None, -1)
if len(self.actions) > len(self.files):
wx.MessageDialog(None, _(u"It seems as though the currently used sound pack needs an update. %i fails are still be required to use this function. Make sure to obtain the needed lacking sounds or to contact with the sound pack developer.") % (len(self.actions) - len(self.files)), _(u"Error"), wx.ICON_ERROR).ShowModal()
self.Destroy()
self.SetTitle(_(u"Sounds tutorial"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
label = wx.StaticText(panel, -1, _(u"Press enter to listen to the sound for the selected event"))
self.items = wx.ListBox(panel, 1, choices=self.actions, style=wx.LB_SINGLE)
self.items.SetSelection(0)
listBox = wx.BoxSizer(wx.HORIZONTAL)
listBox.Add(label)
listBox.Add(self.items)
play = wx.Button(panel, 1, (u"Play"))
play.SetDefault()
self.Bind(wx.EVT_BUTTON, self.onPlay, play)
close = wx.Button(panel, wx.ID_CANCEL)
btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(play)
btnBox.Add(close)
sizer.Add(listBox)
sizer.Add(btnBox)
panel.SetSizer(sizer)
def onPlay(self, ev):
sound.player.play(self.files[self.items.GetSelection()])

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
#-*- coding: utf-8 -*-
import reverse_sort
import application
actions = reverse_sort.reverse_sort([ ("audio", _(u"Audio tweet.")),
("create_timeline", _(u"User timeline buffer created.")),
("delete_timeline", _(u"Buffer destroied.")),
("dm_received", _(u"Direct message received.")),
("dm_sent", _(u"Direct message sent.")),
("error", _(u"Error.")),
("favourite", _(u"Tweet favorited.")),
("favourites_timeline_updated", _(u"Favourites buffer updated.")),
("geo", _(u"Geotweet.")),
("limit", _(u"Boundary reached.")),
("list_tweet", _(u"List updated.")),
("max_length", _(u"Too many characters.")),
("mention_received", _(u"Mention received.")),
("new_event", _(u"New event.")),
("ready", _(unicode(application.name+" is ready."))),
("reply_send", _(u"Mention sent.")),
("retweet_send", _(u"Tweet retweeted.")),
("search_updated", _(u"Search buffer updated.")),
("tweet_received", _(u"Tweet received.")),
("tweet_send", _(u"Tweet sent.")),
("trends_updated", _(u"Trending topics buffer updated.")),
("tweet_timeline", _(u"New tweet in user timeline buffer.")),
("update_followers", _(u"New follower.")),
("volume_changed", _(u"Volume changed."))])

View File

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

View File

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

View File

@@ -1,105 +0,0 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
############################################################
import wx
import output
import config
import languageHandler
from enchant.checker import SpellChecker
from enchant.errors import DictNotFoundError
class spellCheckerDialog(wx.Dialog):
def __init__(self, text, dictionary):
super(spellCheckerDialog, self).__init__(None, 1)
try:
if config.main["general"]["language"] == "system": self.checker = SpellChecker()
else: self.checker = SpellChecker(languageHandler.getLanguage())
self.checker.set_text(text)
except DictNotFoundError:
wx.MessageDialog(None, _(u"A bug has happened. There are no dictionaries available for the selected language in TW Blue"), _(u"Error"), wx.ICON_ERROR).ShowModal()
self.Destroy()
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
word = wx.StaticText(panel, -1, _(u"Mis-spelled word"))
self.word = wx.TextCtrl(panel, -1)
wordBox = wx.BoxSizer(wx.HORIZONTAL)
wordBox.Add(word)
wordBox.Add(self.word)
context = wx.StaticText(panel, -1, _(u"Context"))
self.context = wx.TextCtrl(panel, -1)
contextBox = wx.BoxSizer(wx.HORIZONTAL)
contextBox.Add(context)
contextBox.Add(self.context)
suggest = wx.StaticText(panel, -1, _(u"Suggestions"))
self.suggestions = wx.ListBox(panel, -1, choices=[], style=wx.LB_SINGLE)
suggestionsBox = wx.BoxSizer(wx.HORIZONTAL)
suggestionsBox.Add(suggest)
suggestionsBox.Add(self.suggestions)
ignore = wx.Button(panel, -1, _(u"Ignore"))
self.Bind(wx.EVT_BUTTON, self.onIgnore, ignore)
ignoreAll = wx.Button(panel, -1, _(u"Ignore all"))
self.Bind(wx.EVT_BUTTON, self.onIgnoreAll, ignoreAll)
replace = wx.Button(panel, -1, _(u"Replace"))
self.Bind(wx.EVT_BUTTON, self.onReplace, replace)
replaceAll = wx.Button(panel, -1, _(u"Replace all"))
self.Bind(wx.EVT_BUTTON, self.onReplaceAll, replaceAll)
close = wx.Button(panel, wx.ID_CANCEL)
btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(ignore)
btnBox.Add(ignoreAll)
btnBox.Add(replace)
btnBox.Add(replaceAll)
btnBox.Add(close)
sizer.Add(wordBox)
sizer.Add(contextBox)
sizer.Add(suggestionsBox)
sizer.Add(btnBox)
panel.SetSizerAndFit(sizer)
self.check()
def check(self):
try:
self.checker.next()
textToSay = _(u"Mis-spelled word: %s") % (self.checker.word,)
context = u"... %s %s %s" % (self.checker.leading_context(10), self.checker.word, self.checker.trailing_context(10))
self.SetTitle(textToSay)
output.speak(textToSay)
self.word.SetValue(self.checker.word)
self.context.ChangeValue(context)
self.suggestions.Set(self.checker.suggest())
self.suggestions.SetFocus()
except StopIteration:
wx.MessageDialog(self, _(u"The spelling review has finished."), _("Finished"), style=wx.OK).ShowModal()
self.EndModal(wx.ID_OK)
except AttributeError:
pass
def onIgnore(self, ev):
self.check()
def onIgnoreAll(self, ev):
self.checker.ignore_always(word=self.checker.word)
self.check()
def onReplace(self, ev):
self.checker.replace(self.suggestions.GetStringSelection())
self.check()
def onReplaceAll(self, ev):
self.checker.replace_always(self.suggestions.GetStringSelection())
self.check()

View File

@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
import logging
log = logging.getLogger("extra.SpellChecker.spellChecker")
import wx_ui
import widgetUtils
import output
import config
import languageHandler
from enchant.checker import SpellChecker
from enchant.errors import DictNotFoundError
from enchant import tokenize
import twitterFilter
class spellChecker(object):
def __init__(self, text, dictionary):
super(spellChecker, self).__init__()
log.debug("Creating the SpellChecker object. Dictionary: %s" % (dictionary,))
self.active = True
try:
if config.app["app-settings"]["language"] == "system":
log.debug("Using the system language")
self.checker = SpellChecker(filters=[twitterFilter.TwitterFilter, tokenize.EmailFilter, tokenize.URLFilter])
else:
log.debug("Using language: %s" % (languageHandler.getLanguage(),))
self.checker = SpellChecker(languageHandler.getLanguage(), filters=[twitterFilter.TwitterFilter, tokenize.EmailFilter, tokenize.URLFilter])
self.checker.set_text(text)
except DictNotFoundError:
log.exception("Dictionary for language %s not found." % (dictionary,))
wx_ui.dict_not_found_error()
self.active = False
if self.active == True:
log.debug("Creating dialog...")
self.dialog = wx_ui.spellCheckerDialog()
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.replace, widgetUtils.BUTTON_PRESSED, self.replace)
widgetUtils.connect_event(self.dialog.replaceAll, widgetUtils.BUTTON_PRESSED, self.replaceAll)
self.check()
self.dialog.get_response()
self.fixed_text = self.checker.get_text()
def check(self):
try:
self.checker.next()
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))
self.dialog.set_title(textToSay)
output.speak(textToSay)
self.dialog.set_word_and_suggestions(word=self.checker.word, context=context, suggestions=self.checker.suggest())
except StopIteration:
log.debug("Process finished.")
wx_ui.finished()
self.dialog.Destroy()
# except AttributeError:
# pass
def ignore(self, ev):
self.check()
def ignoreAll(self, ev):
self.checker.ignore_always(word=self.checker.word)
self.check()
def replace(self, ev):
self.checker.replace(self.dialog.get_selected_suggestion())
self.check()
def replaceAll(self, ev):
self.checker.replace_always(self.dialog.get_selected_suggestion())
self.check()

View File

@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
import re
from enchant.tokenize import Filter
class TwitterFilter(Filter):
"""Filter skipping over twitter usernames and hashtags.
This filter skips any words matching the following regular expression:
^[#@](\S){1, }$
That is, any words that resemble users and hashtags.
"""
_pattern = re.compile(r"^[#@](\S){1,}$")
def _skip(self,word):
if self._pattern.match(word):
return True
return False

View File

@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
############################################################
# Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo <manuel@manuelcortez.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
############################################################
import wx
import application
class spellCheckerDialog(wx.Dialog):
def __init__(self):
super(spellCheckerDialog, self).__init__(None, 1)
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
word = wx.StaticText(panel, -1, _(u"Misspelled word"))
self.word = wx.TextCtrl(panel, -1)
wordBox = wx.BoxSizer(wx.HORIZONTAL)
wordBox.Add(word, 0, wx.ALL, 5)
wordBox.Add(self.word, 0, wx.ALL, 5)
context = wx.StaticText(panel, -1, _(u"Context"))
self.context = wx.TextCtrl(panel, -1)
contextBox = wx.BoxSizer(wx.HORIZONTAL)
contextBox.Add(context, 0, wx.ALL, 5)
contextBox.Add(self.context, 0, wx.ALL, 5)
suggest = wx.StaticText(panel, -1, _(u"Suggestions"))
self.suggestions = wx.ListBox(panel, -1, choices=[], style=wx.LB_SINGLE)
suggestionsBox = wx.BoxSizer(wx.HORIZONTAL)
suggestionsBox.Add(suggest, 0, wx.ALL, 5)
suggestionsBox.Add(self.suggestions, 0, wx.ALL, 5)
self.ignore = wx.Button(panel, -1, _(u"Ignore"))
self.ignoreAll = wx.Button(panel, -1, _(u"Ignore all"))
self.replace = wx.Button(panel, -1, _(u"Replace"))
self.replaceAll = wx.Button(panel, -1, _(u"Replace all"))
close = wx.Button(panel, wx.ID_CANCEL)
btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(self.ignore, 0, wx.ALL, 5)
btnBox.Add(self.ignoreAll, 0, wx.ALL, 5)
btnBox.Add(self.replace, 0, wx.ALL, 5)
btnBox.Add(self.replaceAll, 0, wx.ALL, 5)
btnBox.Add(close, 0, wx.ALL, 5)
sizer.Add(wordBox, 0, wx.ALL, 5)
sizer.Add(contextBox, 0, wx.ALL, 5)
sizer.Add(suggestionsBox, 0, wx.ALL, 5)
sizer.Add(btnBox, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def get_response(self):
return self.ShowModal()
def set_title(self, title):
return self.SetTitle(title)
def set_word_and_suggestions(self, word, context, suggestions):
self.word.SetValue(word)
self.context.ChangeValue(context)
self.suggestions.Set(suggestions)
self.suggestions.SetFocus()
def get_selected_suggestion(self):
return self.suggestions.GetStringSelection()
def dict_not_found_error():
wx.MessageDialog(None, _(u"An error has occurred. There are no dictionaries available for the selected language in " + application.name), _(u"Error"), wx.ICON_ERROR).ShowModal()
def finished():
wx.MessageDialog(None, _(u"Spell check complete."), _(application.name), style=wx.OK).ShowModal()

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
import completion, settings

View File

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

View File

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

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
import storage
import widgetUtils
import wx_settings
import manage
import output
from mysc.thread_utils import call_threaded
class autocompletionSettings(object):
def __init__(self, config, buffer, window):
super(autocompletionSettings, self).__init__()
self.config = config
self.buffer = buffer
self.window = window
self.dialog = wx_settings.autocompletionSettingsDialog()
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"])
widgetUtils.connect_event(self.dialog.viewList, widgetUtils.BUTTON_PRESSED, self.view_list)
if self.dialog.get_response() == widgetUtils.OK:
call_threaded(self.add_users_to_database)
def add_users_to_database(self):
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")
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)
if self.dialog.get("followers_buffer") == True:
buffer = self.window.search_buffer("followers", self.config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]["items"]:
database.set_user(i["screen_name"], i["name"], 1)
else:
database.remove_by_buffer(1)
if self.dialog.get("friends_buffer") == True:
buffer = self.window.search_buffer("friends", self.config["twitter"]["user_name"])
for i in buffer.session.db[buffer.name]["items"]:
database.set_user(i["screen_name"], i["name"], 2)
else:
database.remove_by_buffer(2)
wx_settings.show_success_dialog()
self.dialog.destroy()
def view_list(self, ev):
q = manage.autocompletionManage(self.buffer.session)
def execute_at_startup(window, buffer, config):
database = storage.storage(buffer.session.session_id)
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"])
for i in buffer.session.db[buffer.name]:
database.set_user(i["screen_name"], i["name"], 1)
else:
database.remove_by_buffer(1)
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"])
for i in buffer.session.db[buffer.name]:
database.set_user(i["screen_name"], i["name"], 2)
else:
database.remove_by_buffer(2)

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
import sqlite3, paths
class storage(object):
def __init__(self, session_id):
self.connection = sqlite3.connect(paths.config_path("%s/autocompletionUsers.dat" % (session_id)))
self.cursor = self.connection.cursor()
if self.table_exist("users") == False:
self.create_table()
def table_exist(self, table):
ask = self.cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='%s'" % (table))
answer = ask.fetchone()
if answer == None:
return False
else:
return True
def get_all_users(self):
self.cursor.execute("""select * from users""")
return self.cursor.fetchall()
def get_users(self, term):
self.cursor.execute("""SELECT * FROM users WHERE user LIKE ?""", ('{}%'.format(term),))
return self.cursor.fetchall()
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.connection.commit()
def remove_user(self, user):
self.cursor.execute("""DELETE FROM users WHERE user = ?""", (user,))
self.connection.commit()
return self.cursor.fetchone()
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"""
self.cursor.execute("""DELETE FROM users WHERE from_a_buffer = ?""", (bufferType,))
self.connection.commit()
return self.cursor.fetchone()
def create_table(self):
self.cursor.execute("""
create table users(
user TEXT UNIQUE,
name TEXT,
from_a_buffer INTEGER
)""")
def __del__(self):
self.cursor.close()
self.connection.close()

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
import wx
import widgetUtils
import application
class autocompletionSettingsDialog(widgetUtils.BaseDialog):
def __init__(self):
super(autocompletionSettingsDialog, self).__init__(parent=None, id=-1, title=_(u"Autocomplete users settings"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
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"))
sizer.Add(self.followers_buffer, 0, wx.ALL, 5)
sizer.Add(self.friends_buffer, 0, wx.ALL, 5)
self.viewList = wx.Button(panel, -1, _(u"Manage database..."))
sizer.Add(self.viewList, 0, wx.ALL, 5)
ok = wx.Button(panel, wx.ID_OK)
cancel = wx.Button(panel, wx.ID_CANCEL)
sizerBtn = wx.BoxSizer(wx.HORIZONTAL)
sizerBtn.Add(ok, 0, wx.ALL, 5)
sizer.Add(cancel, 0, wx.ALL, 5)
sizer.Add(sizerBtn, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def show_success_dialog():
wx.MessageDialog(None, _(uapplication.name+"'s database of users has been updated."), _(u"Done"), wx.OK).ShowModal()

View File

@@ -1,3 +1,6 @@
# -*- coding: utf-8 -*-
from translator import *
import gui
import translator
import platform
if platform.system() == "Windows":
import wx_ui as gui

View File

@@ -1,53 +1,9 @@
# -*- coding: utf-8 -*-
import re
try:
import urllib2 as request
from urllib import quote
except:
from urllib import request
from urllib.parse import quote
#!/usr/bin/env python
import goslate
class Translator:
string_pattern = r"\"(([^\"\\]|\\.)*)\""
match_string =re.compile(
r"\,?\["
+ string_pattern + r"\,"
+ string_pattern + r"\,"
+ string_pattern + r"\,"
+ string_pattern
+r"\]")
def __init__(self):
self.from_lang = ""
self.to_lang = ""
def translate(self, source):
json5 = self._get_json5_from_google(source)
return self._unescape(self._get_translation_from_json5(json5))
def _get_translation_from_json5(self, content):
result = ""
pos = 2
while True:
m = self.match_string.match(content, pos)
if not m:
break
result += m.group(1)
pos = m.end()
return result
def _get_json5_from_google(self, source):
escaped_source = quote(source, '')
headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.168 Safari/535.19'}
req = request.Request(
url="http://translate.google.com/translate_a/t?client=t&ie=UTF-8&oe=UTF-8"
+"&sl=%s&tl=%s&text=%s" % (self.from_lang, self.to_lang, escaped_source)
, headers = headers)
r = request.urlopen(req)
return r.read().decode('utf-8')
def _unescape(self, text):
return re.sub(r"\\.?", lambda x:eval('"%s"'%x.group(0)), text)
def translate(text, source_lang, target_lang):
gs = goslate.Goslate()
return gs.translate(text, target_lang, source_lang)
languages = {
"af": _(u"Afrikaans"),

View File

@@ -16,12 +16,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
############################################################
import wx
import translator
import wx
from wxUI.dialogs import baseDialog
class translateDialog(wx.Dialog):
class translateDialog(baseDialog.BaseWXDialog):
def __init__(self):
wx.Dialog.__init__(self, None, -1, title=_(u"Translate message"))
super(translateDialog, self).__init__(None, -1, title=_(u"Translate message"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
staticSource = wx.StaticText(panel, -1, _(u"Source language"))
@@ -40,5 +41,5 @@ class translateDialog(wx.Dialog):
cancel = wx.Button(panel, wx.ID_CANCEL)
self.SetEscapeId(wx.ID_CANCEL)
def onOk(self, ev):
self.EndModal(wx.ID_OK)
def get(self, control):
return getattr(self, control).GetSelection()

6
src/fixes/__init__.py Normal file
View File

@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
""" This module contains some bugfixes for packages used in TWBlue."""
import fix_arrow # A few new locales for Three languages in arrow.
def setup():
fix_arrow.fix()

91
src/fixes/fix_arrow.py Normal file
View File

@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
from arrow import locales
from arrow.locales import Locale
def fix():
''' This function adds the Catala, Basque and galician locales to the list of locales supported in Arrow.
it also fixes capitalizations in names from turkish and arabian locales.
see https://github.com/crsmithdev/arrow/pull/207 for following the pull request.'''
locales.CatalaLocale = CatalaLocale
locales.GalicianLocale = GalicianLocale
locales.BasqueLocale = BasqueLocale
locales.TurkishLocale.names[-1] = "tr_tr"
locales.ArabicLocale.names[-1] = "ar_eg"
# We need to reassign the locales list for updating the list with our new contents.
locales._locales = locales._map_locales()
class CatalaLocale(Locale):
names = ['ca', 'ca_ca']
past = 'Fa {0}'
future = '{0}' # I don't know what's the right phrase in catala for the future.
timeframes = {
'now': 'Ara mateix',
'seconds': 'segons',
'minute': '1 minut',
'minutes': '{0} minuts',
'hour': 'una hora',
'hours': '{0} hores',
'day': 'un dia',
'days': '{0} dies',
'month': 'un mes',
'months': '{0} messos',
'year': 'un any',
'years': '{0} anys',
}
month_names = ['', 'Jener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Decembre']
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):
names = ['gl', 'gl_gl']
past = 'Fai {0}'
future = '{0}' # I don't know what's the right phrase in Galician for the future.
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_eu']
past = 'duela {0}'
future = '{0}' # I don't know what's the right phrase in Basque for the future.
timeframes = {
'now': 'Orain',
'seconds': 'segundu',
'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

@@ -1,105 +0,0 @@
# Copyright (c) 2006, 2007, 2010 Alexander Belchenko
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""Helper for standard gettext.py on Windows.
Module obtains user language code on Windows to use with standard
Python gettext.py library.
The module provides 2 functions: setup_env and get_language.
You may use setup_env before initializing gettext functions.
Or you can use get_language to get the list of language codes suitable
to pass them to gettext.find or gettext.translation function.
Usage example #1:
import gettext, gettext_windows
gettext_windows.setup_env()
gettext.install('myapp')
Usage example #2:
import gettext, gettext_windows
lang = gettext_windows.get_language()
translation = gettext.translation('myapp', languages=lang)
_ = translation.gettext
"""
import locale
import os
import sys
OS_WINDOWS = (sys.platform == 'win32')
def setup_env_windows(system_lang=True):
"""Check environment variables used by gettext
and setup LANG if there is none.
"""
if _get_lang_env_var() is not None:
return
lang = get_language_windows(system_lang)
if lang:
os.environ['LANGUAGE'] = ':'.join(lang)
def get_language_windows(system_lang=True):
"""Get language code based on current Windows settings.
@return: list of languages.
"""
try:
import ctypes
except ImportError:
return [locale.getdefaultlocale()[0]]
# get all locales using windows API
lcid_user = ctypes.windll.kernel32.GetUserDefaultLCID()
lcid_system = ctypes.windll.kernel32.GetSystemDefaultLCID()
if system_lang and lcid_user != lcid_system:
lcids = [lcid_user, lcid_system]
else:
lcids = [lcid_user]
return filter(None, [locale.windows_locale.get(i) for i in lcids]) or None
def setup_env_other(system_lang=True):
pass
def get_language_other(system_lang=True):
lang = _get_lang_env_var()
if lang is not None:
return lang.split(':')
return None
def _get_lang_env_var():
for i in ('LANGUAGE','LC_ALL','LC_MESSAGES','LANG'):
lang = os.environ.get(i)
if lang:
return lang
return None
if OS_WINDOWS:
setup_env = setup_env_windows
get_language = get_language_windows
else:
setup_env = setup_env_other
get_language = get_language_other

11
src/gtkUI/__init__.py Normal file
View File

@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
""" This is the GTK view module for TWBlue.
As of April 3 2015, there are the things that have been implemented:
* the main view (partially implemented)
* All buffers.
* Three of the most common message dialogs.
* Dialogs for tweet, reply, retweet, send a direct message and view a tweet.
And we need to implement:
* All the other dialogs.
"""

View File

@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
from base import basePanel
from dm import dmPanel
from events import eventsPanel
from favourites import favsPanel
from lists import listPanel
from panels import accountPanel, emptyPanel
from people import peoplePanel
from trends import trendsPanel
from tweet_searches import searchPanel
from user_searches import searchUsersPanel

35
src/gtkUI/buffers/base.py Normal file
View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
import widgetUtils
from gi.repository import Gtk
class basePanel(Gtk.VBox):
def create_list(self):
self.list = widgetUtils.list(_(u"User"), _(u"Text"), _(u"Date"), _(u"Client"))
def __init__(self, parent, name):
super(basePanel, self).__init__(spacing=6)
self.name = name
self.type = "baseBuffer"
self.create_list()
self.tweet = Gtk.Button(_(u"Tweet"))
self.retweet = Gtk.Button(_(u"Retweet"))
self.reply = Gtk.Button(_(u"Reply"))
self.dm = Gtk.Button(_(u"Direct message"))
btnSizer = Gtk.Box(spacing=6)
btnSizer.add(self.tweet)
btnSizer.add(self.retweet)
btnSizer.add(self.reply)
btnSizer.add(self.dm)
self.add(self.list.list)
self.add(btnSizer)
def set_position(self, reversed=False):
if reversed == False:
self.list.select_item(self.list.get_count()-1)
else:
self.list.select_item(0)
def set_focus_function(self, f):
tree_selection = self.list.list.get_selection()
tree_selection.connect("changed", f)

14
src/gtkUI/buffers/dm.py Normal file
View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from gi.repository import Gtk
import widgetUtils
from base import basePanel
class dmPanel(basePanel):
def __init__(self, parent, name):
""" Class to DM'S. Reply and retweet buttons are not showed and they have your delete method for dm's."""
super(dmPanel, self).__init__(parent, name)
self.retweet.hide()
self.retweet.set_no_show_all(True)
self.reply.hide()
self.reply.set_no_show_all(True)
self.type = "dm"

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
import widgetUtils
from gi.repository import Gtk
class eventsPanel(Gtk.VBox):
""" Buffer to show events. Different than tweets or people."""
def __init__(self, parent, name):
self.type = "event"
super(eventsPanel, self).__init__(spacing=6)
self.name = name
self.list = widgetUtils.list(_(u"Date"), _(u"Event"))
self.add(self.list.list)
self.tweet = Gtk.Button(_(u"Tweet"))
self.delete_event = Gtk.Button(_(u"Remove event"))
btnBox = Gtk.Box(spacing=6)
btnBox.add(self.tweet)
btnBox.add(self.delete_event)
self.add(btnBox)
def set_position(self, reversed=False):
if reversed == False:
self.list.select_item(self.list.get_count()-1)
else:
self.list.select_item(0)

View File

@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
from gi.repository import Gtk
from base import basePanel
class favsPanel(basePanel):
def __init__(self, parent, name):
super(favsPanel, self).__init__(parent, name)
self.type = "favourites_timeline"

View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
from gi.repository import Gtk
from base import basePanel
class listPanel(basePanel):
def __init__(self, parent, name):
super(listPanel, self).__init__(parent, name)
self.type = "list"
self.users = []

View File

@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
from gi.repository import Gtk
import widgetUtils
class accountPanel(Gtk.VBox):
def __init__(self, parent, name=None):
super(accountPanel, self).__init__(spacing=5)
self.name = name
self.type = "account"
self.login = Gtk.Button(_(u"Login"))
self.add(self.login)
self.autostart_account = Gtk.ToggleButton(_(u"Log in automatically"))
self.add(self.autostart_account)
def change_login(self, login=True):
if login == True:
self.login.set_label(_(u"Login"))
else:
self.login.set_label(_(u"Logout"))
def change_autostart(self, autostart=True):
self.autostart_account.set_active(autostart)
def get_autostart(self):
print "actived"
print self.autostart_account.get_active()
return self.autostart_account.get_active()
class emptyPanel(Gtk.VBox):
def __init__(self, parent, name):
super(emptyPanel, self).__init__(spacing=6)
self.name = name
self.type = "account"

View File

@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
from gi.repository import Gtk
import widgetUtils
from base import basePanel
class peoplePanel(basePanel):
""" Buffer used to show people."""
def create_list(self):
self.list = widgetUtils.list(_(u"User"))
def __init__(self, parent, name):
super(peoplePanel, self).__init__(parent, name)
self.type = "people"
self.reply.set_label(_(u"Mention"))
self.retweet.hide()
self.retweet.set_no_show_all(True)

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from gi.repository import Gtk
import widgetUtils
class trendsPanel(Gtk.VBox):
def create_list(self):
""" Returns the list for put the tweets here."""
self.list = widgetUtils.list(_(u"Trending topic"))
def __init__(self, parent, name):
super(trendsPanel, self).__init__(spacing=6)
self.type = "trends"
self.create_list()
self.tweet = Gtk.Button(_(u"Tweet"))
self.tweetTrendBtn = Gtk.Button(_(u"Tweet about this trend"))
btnSizer = Gtk.Box(spacing=3)
btnSizer.add(self.tweet)
btnSizer.add(self.tweetTrendBtn)
self.add(btnSizer)
self.Add(self.list.list)
def set_position(self, reversed=False):
if reversed == False:
self.list.select_item(self.list.get_count()-1)
else:
self.list.select_item(0)

View File

@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
from gi.repository import Gtk
from base import basePanel
class searchPanel(basePanel):
def __init__(self, parent, name):
super(searchPanel, self).__init__(parent, name)
self.type = "search"

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from gi.repository import Gtk
from tweet_searches import searchPanel
import widgetUtils
class searchUsersPanel(searchPanel):
def create_list(self):
""" Returns the list for put the tweets here."""
self.list = widgetUtils.list(_(u"User"))
def __init__(self, parent, name):
self.create_list()
super(searchUsersPanel, self).__init__(parent, name)
self.type = "user_searches"

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
from gi.repository import Gtk
import application
def retweet_as_link(parent):
dialog = Gtk.MessageDialog(None, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, unicode(application.name))
dialog.format_secondary_text(_(u"This retweet is over 140 characters. Would you like to post it as a mention to the poster with your comments and a link to the original tweet?"))
answer = dialog.run()
dialog.destroy()
return answer
def retweet_question(parent):
dialog = Gtk.MessageDialog(None, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, _(u"Retweet"))
dialog.format_secondary_text(_(u"Would you like to add a comment to this tweet?"))
answer = dialog.run()
dialog.destroy()
return answer
def delete_tweet_dialog(parent):
dialog = Gtk.MessageDialog(None, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, _(u"Delete"))
dialog.format_secondary_text(_(u"Do you really want to delete this message? It will be deleted from Twitter as well."))
answer = dialog.run()
dialog.destroy()
return answer
def exit_dialog(parent):
dialog = Gtk.MessageDialog(parent, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, _(u"Exit"))
dialog.format_secondary_text(_(u"Do you really want to close " + application.name + "?"))
answer = dialog.run()
dialog.destroy()
return answer
def needs_restart():
wx.MessageDialog(None, _(unicode(application.name+" must be restarted to save these changes. Press OK to restart now.")), _("Restart " + application.name), wx.OK).ShowModal()
def delete_user_from_db():
return wx.MessageDialog(None, _(u"Are you sure you want to delete this user from the database? This user will not appear on the autocomplete results anymore."), _(u"Confirm"), wx.YES_NO|wx.ICON_QUESTION).ShowModal()
def get_ignored_client():
entry = wx.TextEntryDialog(None, _(u"Enter the name of the client here"), _(u"Add a new ignored client"))
if entry.ShowModal() == wx.ID_OK:
return entry.GetValue()
return None
def clear_list():
dlg = wx.MessageDialog(None, _(u"Do you really want to empty this buffer? It's items will be removed from the list but not from Twitter"), _(u"Empty buffer"), wx.ICON_QUESTION|wx.YES_NO)
return dlg.ShowModal()
def remove_buffer():
return wx.MessageDialog(None, _(u"Do you really want to delete this timeline?"), _(u"Attention"), style=wx.ICON_QUESTION|wx.YES_NO).ShowModal()
def user_not_exist():
return wx.MessageDialog(None, _(u"The user does not exist"), _(u"Error"), wx.ICON_ERROR).ShowModal()
def timeline_exist():
return wx.MessageDialog(None, _(u"There's currently a timeline for this user. You are not able to open another"), _(u"Existing timeline"), wx.ICON_ERROR).ShowModal()
def no_tweets():
return wx.MessageDialog(None, _(u"This user has no tweets. You can't open a timeline for this user"), _(u"Error!"), wx.ICON_ERROR).ShowModal()
def protected_user():
return wx.MessageDialog(None, _(u"This is a protected Twitter user. It means you can not open a timeline using the Streaming API. The user's tweets will not update due to a twitter policy. Do you want to continue?"), _(u"Warning"), wx.ICON_WARNING|wx.YES_NO).ShowModal()
def no_following():
return wx.MessageDialog(None, _(u"This is a protected user account, you need follow to this user for viewing your tweets or favourites."), _(u"Error"), wx.ICON_ERROR).ShowModal()

View File

@@ -0,0 +1 @@
#import trends, configuration, lists, message, search, show_user, update_profile, urlList, userSelection, utils

View File

@@ -0,0 +1,228 @@
# -*- coding: utf-8 -*-
import baseDialog
import wx
import logging as original_logger
import application
class general(wx.Panel, baseDialog.BaseWXDialog):
def __init__(self, parent, languages):
super(general, self).__init__(parent)
sizer = wx.BoxSizer(wx.VERTICAL)
language = wx.StaticText(self, -1, _(u"Language"))
self.language = wx.ListBox(self, -1, choices=languages)
self.language.SetSize(self.language.GetBestSize())
langBox = wx.BoxSizer(wx.HORIZONTAL)
langBox.Add(language, 0, wx.ALL, 5)
langBox.Add(self.language, 0, wx.ALL, 5)
sizer.Add(langBox, 0, wx.ALL, 5)
self.ask_at_exit = wx.CheckBox(self, -1, _(U"ask before exiting " + application.name))
sizer.Add(self.ask_at_exit, 0, wx.ALL, 5)
self.use_invisible_shorcuts = wx.CheckBox(self, -1, _(u"Use invisible interface's keyboard shortcuts while GUI is visible"))
sizer.Add(self.use_invisible_shorcuts, 0, wx.ALL, 5)
self.disable_sapi5 = wx.CheckBox(self, -1, _(u"Activate Sapi5 when any other screen reader is not being run"))
sizer.Add(self.disable_sapi5, 0, wx.ALL, 5)
self.hide_gui = wx.CheckBox(self, -1, _(u"Hide GUI on launch"))
sizer.Add(self.hide_gui, 0, wx.ALL, 5)
self.SetSizer(sizer)
class generalAccount(wx.Panel, baseDialog.BaseWXDialog):
def __init__(self, parent):
super(generalAccount, self).__init__(parent)
sizer = wx.BoxSizer(wx.VERTICAL)
self.au = wx.Button(self, wx.NewId(), _(u"Set the autocomplete function"))
sizer.Add(self.au, 0, wx.ALL, 5)
self.relative_time = wx.CheckBox(self, wx.NewId(), _(U"Relative times"))
sizer.Add(self.relative_time, 0, wx.ALL, 5)
apiCallsBox = wx.BoxSizer(wx.HORIZONTAL)
apiCallsBox.Add(wx.StaticText(self, -1, _(u"API calls when the stream is started (One API call equals to 200 tweetts, two API calls equals 400 tweets, etc):")), 0, wx.ALL, 5)
self.apiCalls = wx.SpinCtrl(self, wx.NewId())
self.apiCalls.SetRange(1, 10)
self.apiCalls.SetSize(self.apiCalls.GetBestSize())
apiCallsBox.Add(self.apiCalls, 0, wx.ALL, 5)
sizer.Add(apiCallsBox, 0, wx.ALL, 5)
tweetsPerCallBox = wx.BoxSizer(wx.HORIZONTAL)
tweetsPerCallBox.Add(wx.StaticText(self, -1, _(u"Items on each API call")), 0, wx.ALL, 5)
self.itemsPerApiCall = wx.SpinCtrl(self, wx.NewId())
self.itemsPerApiCall.SetRange(0, 200)
self.itemsPerApiCall.SetSize(self.itemsPerApiCall.GetBestSize())
tweetsPerCallBox.Add(self.itemsPerApiCall, 0, wx.ALL, 5)
sizer.Add(tweetsPerCallBox, 0, wx.ALL, 5)
self.reverse_timelines = wx.CheckBox(self, wx.NewId(), _(u"Inverted buffers: The newest tweets will be shown at the beginning of the lists while the oldest at the end"))
sizer.Add(self.reverse_timelines, 0, wx.ALL, 5)
self.SetSizer(sizer)
class other_buffers(wx.Panel):
def __init__(self, parent):
super(other_buffers, self).__init__(parent)
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
class ignoredClients(wx.Panel):
def __init__(self, parent, choices):
super(ignoredClients, self).__init__(parent=parent)
sizer = wx.BoxSizer(wx.VERTICAL)
label = wx.StaticText(self, -1, _(u"Ignored clients"))
self.clients = wx.ListBox(self, -1, choices=choices)
self.clients.SetSize(self.clients.GetBestSize())
clientsBox = wx.BoxSizer(wx.HORIZONTAL)
clientsBox.Add(label, 0, wx.ALL, 5)
clientsBox.Add(self.clients, 0, wx.ALL, 5)
self.add = wx.Button(self, -1, _(u"Add client"))
self.remove = wx.Button(self, -1, _(u"Remove client"))
btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(self.add, 0, wx.ALL, 5)
btnBox.Add(self.remove, 0, wx.ALL, 5)
sizer.Add(clientsBox, 0, wx.ALL, 5)
sizer.Add(btnBox, 0, wx.ALL, 5)
self.SetSizer(sizer)
def append(self, client):
self.clients.Append(client)
def get_clients(self):
return self.clients.GetCount()
def get_client_id(self):
return self.clients.GetSelection()
def remove_(self, id):
self.clients.Delete(id)
class sound(wx.Panel):
def __init__(self, parent, input_devices, output_devices, soundpacks):
wx.Panel.__init__(self, parent)
sizer = wx.BoxSizer(wx.VERTICAL)
volume = wx.StaticText(self, -1, _(u"Volume"))
self.volumeCtrl = wx.Slider(self)
self.volumeCtrl.SetRange(0, 100)
self.volumeCtrl.SetSize(self.volumeCtrl.GetBestSize())
volumeBox = wx.BoxSizer(wx.HORIZONTAL)
volumeBox.Add(volume, 0, wx.ALL, 5)
volumeBox.Add(self.volumeCtrl, 0, wx.ALL, 5)
sizer.Add(volumeBox, 0, wx.ALL, 5)
self.session_mute = wx.CheckBox(self, -1, _(u"Session mute"))
sizer.Add(self.session_mute, 0, wx.ALL, 5)
output_label = wx.StaticText(self, -1, _(u"Output device"))
self.output = wx.ComboBox(self, -1, choices=output_devices, style=wx.CB_READONLY)
self.output.SetSize(self.output.GetBestSize())
outputBox = wx.BoxSizer(wx.HORIZONTAL)
outputBox.Add(output_label, 0, wx.ALL, 5)
outputBox.Add(self.output, 0, wx.ALL, 5)
sizer.Add(outputBox, 0, wx.ALL, 5)
input_label = wx.StaticText(self, -1, _(u"Input device"))
self.input = wx.ComboBox(self, -1, choices=input_devices, style=wx.CB_READONLY)
self.input.SetSize(self.input.GetBestSize())
inputBox = wx.BoxSizer(wx.HORIZONTAL)
inputBox.Add(input_label, 0, wx.ALL, 5)
inputBox.Add(self.input, 0, wx.ALL, 5)
sizer.Add(inputBox, 0, wx.ALL, 5)
soundBox = wx.BoxSizer(wx.VERTICAL)
soundpack_label = wx.StaticText(self, -1, _(u"Sound pack"))
self.soundpack = wx.ComboBox(self, -1, choices=soundpacks, style=wx.CB_READONLY)
self.soundpack.SetSize(self.soundpack.GetBestSize())
soundBox.Add(soundpack_label, 0, wx.ALL, 5)
soundBox.Add(self.soundpack, 0, wx.ALL, 5)
sizer.Add(soundBox, 0, wx.ALL, 5)
self.SetSizer(sizer)
def get(self, control):
return getattr(self, control).GetStringSelection()
class audioServicesPanel(wx.Panel):
def __init__(self, parent):
super(audioServicesPanel, self).__init__(parent)
mainSizer = wx.BoxSizer(wx.VERTICAL)
apiKeyLabel = wx.StaticText(self, -1, _(u"If you've got a SndUp account, enter your API Key here. Whether the API Key is wrong, the App will fail to upload anything to the server. Whether there's no API Key here, then the audio files will be uploaded anonimously"))
self.apiKey = wx.TextCtrl(self, -1)
dc = wx.WindowDC(self.apiKey)
dc.SetFont(self.apiKey.GetFont())
self.apiKey.SetSize(dc.GetTextExtent("0"*100))
apiKeyBox = wx.BoxSizer(wx.HORIZONTAL)
apiKeyBox.Add(apiKeyLabel, 0, wx.ALL, 5)
apiKeyBox.Add(self.apiKey, 0, wx.ALL, 5)
mainSizer.Add(apiKeyBox, 0, wx.ALL, 5)
first_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.dropbox = wx.Button(self, -1)
first_sizer.Add(self.dropbox, 0, wx.ALL, 5)
mainSizer.Add(first_sizer, 0, wx.ALL, 5)
self.SetSizer(mainSizer)
def set_dropbox(self, active=True):
if active == True:
self.dropbox.SetLabel(_(u"Unlink your Dropbox account"))
else:
self.dropbox.SetLabel(_(u"Link your Dropbox account"))
def show_dialog(self):
wx.MessageDialog(self, _(u"The authorization request will be opened in your browser. Copy the code from Dropbox and paste it into the text box which will appear. You only need to do this once."), _(u"Authorization"), wx.OK).ShowModal()
def get_response(self):
dlg = wx.TextEntryDialog(self, _(u"Enter the code here."), _(u"Verification code"))
if dlg.ShowModal() == wx.ID_CANCEL:
return False
return dlg.GetValue()
def show_error(self):
wx.MessageDialog(self, _(u"Error during authorisation. Try again later."), _(u"Error!"), wx.ICON_ERROR).ShowModal()
def get_dropbox(self):
return self.dropbox.GetLabel()
class configurationDialog(baseDialog.BaseWXDialog):
def set_title(self, title):
self.SetTitle(title)
def __init__(self):
super(configurationDialog, self).__init__(None, -1)
self.panel = wx.Panel(self)
self.SetTitle(_(u"TW Blue preferences"))
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.notebook = wx.Notebook(self.panel)
def create_general(self, languageList):
self.general = general(self.notebook, languageList)
self.notebook.AddPage(self.general, _(u"General"))
self.general.SetFocus()
def create_general_account(self):
self.general = generalAccount(self.notebook)
self.notebook.AddPage(self.general, _(u"General"))
self.general.SetFocus()
def create_other_buffers(self):
self.buffers = other_buffers(self.notebook)
self.notebook.AddPage(self.buffers, _(u"Show other buffers"))
def create_ignored_clients(self, ignored_clients_list):
self.ignored_clients = ignoredClients(self.notebook, ignored_clients_list)
self.notebook.AddPage(self.ignored_clients, _(u"Ignored clients"))
def create_sound(self, output_devices, input_devices, soundpacks):
self.sound = sound(self.notebook, output_devices, input_devices, soundpacks)
self.notebook.AddPage(self.sound, _(u"Sound"))
def create_audio_services(self):
self.services = audioServicesPanel(self.notebook)
self.notebook.AddPage(self.services, _(u"Audio Services"))
def realize(self):
self.sizer.Add(self.notebook, 0, wx.ALL, 5)
ok_cancel_box = wx.BoxSizer(wx.HORIZONTAL)
ok = wx.Button(self.panel, wx.ID_OK, _(u"Save"))
ok.SetDefault()
cancel = wx.Button(self.panel, wx.ID_CANCEL, _(u"Close"))
self.SetEscapeId(cancel.GetId())
ok_cancel_box.Add(ok, 0, wx.ALL, 5)
ok_cancel_box.Add(cancel, 0, wx.ALL, 5)
self.sizer.Add(ok_cancel_box, 0, wx.ALL, 5)
self.panel.SetSizer(self.sizer)
self.SetClientSize(self.sizer.CalcMin())
def get_value(self, panel, key):
p = getattr(self, panel)
return getattr(p, key).GetValue()
def set_value(self, panel, key, value):
p = getattr(self, panel)
control = getattr(p, key)
getattr(control, "SetValue")(value)

123
src/gtkUI/dialogs/lists.py Normal file
View File

@@ -0,0 +1,123 @@
# -*- coding: utf-8 -*-
import wx
from multiplatform_widgets import widgets
class listViewer(wx.Dialog):
def __init__(self, *args, **kwargs):
super(listViewer, self).__init__(parent=None, *args, **kwargs)
self.SetTitle(_(u"Lists manager"))
panel = wx.Panel(self)
label = wx.StaticText(panel, -1, _(u"Lists"))
self.lista = widgets.list(panel, _(u"List"), _(u"Description"), _(u"Owner"), _(u"Members"), _(u"mode"), size=(800, 800), style=wx.LC_REPORT|wx.LC_SINGLE_SEL)
self.lista.list.SetFocus()
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(label)
sizer.Add(self.lista.list)
self.createBtn = wx.Button(panel, wx.NewId(), _(u"Create a new list"))
self.editBtn = wx.Button(panel, -1, _(u"Edit"))
self.deleteBtn = wx.Button(panel, -1, _(u"Remove"))
self.view = wx.Button(panel, -1, _(u"Open in buffer"))
# self.members = wx.Button(panel, -1, _(u"View members"))
# self.members.Disable()
# self.subscriptors = wx.Button(panel, -1, _(u"View subscribers"))
# self.subscriptors.Disable()
# self.get_linkBtn = wx.Button(panel, -1, _(u"Get link for the list"))
# self.get_linkBtn.Bind(wx.EVT_BUTTON, self.onGetLink)
self.cancelBtn = wx.Button(panel, wx.ID_CANCEL)
btnSizer = wx.BoxSizer()
btnSizer.Add(self.createBtn)
btnSizer.Add(self.editBtn)
btnSizer.Add(self.cancelBtn)
panel.SetSizer(sizer)
def populate_list(self, lists):
for item in lists:
self.lista.insert_item(False, *item)
def get_item(self):
return self.lista.get_selected()
class userListViewer(listViewer):
def __init__(self, username, *args, **kwargs):
self.username = username
super(userListViewer, self).__init__(*args, **kwargs)
self.SetTitle(_(u"Viewing lists for %s") % (self.username))
self.createBtn.SetLabel(_(u"Subscribe"))
self.deleteBtn.SetLabel(_(u"Unsubscribe"))
self.editBtn.Disable()
self.view.Disable()
class createListDialog(wx.Dialog):
def __init__(self, *args, **kwargs):
super(createListDialog, self).__init__(*args, **kwargs)
self.SetTitle(_(u"Create a new list"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
name = wx.StaticText(panel, -1, _(u"Name (20 characters maximun)"))
self.name = wx.TextCtrl(panel, -1)
nameSizer = wx.BoxSizer(wx.HORIZONTAL)
nameSizer.Add(name)
nameSizer.Add(self.name)
description = wx.StaticText(panel, -1, _(u"Description"))
self.description = wx.TextCtrl(panel, -1)
descriptionSizer = wx.BoxSizer(wx.HORIZONTAL)
descriptionSizer.Add(description)
descriptionSizer.Add(self.description)
mode = wx.StaticText(panel, -1, _(u"Mode"))
self.public = wx.RadioButton(panel, -1, _(u"Public"), style=wx.RB_GROUP)
self.private = wx.RadioButton(panel, -1, _(u"Private"))
modeBox = wx.BoxSizer(wx.HORIZONTAL)
modeBox.Add(mode)
modeBox.Add(self.public)
modeBox.Add(self.private)
ok = wx.Button(panel, wx.ID_OK)
ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL)
btnBox = wx.BoxSizer(wx.HORIZONTAL)
btnBox.Add(ok)
btnBox.Add(cancel)
sizer.Add(nameSizer)
sizer.Add(descriptionSizer)
sizer.Add(modeBox)
sizer.Add(btnBox)
def get(self, field):
return getattr(self, field).GetValue()
class editListDialog(createListDialog):
def __init__(self, list, *args, **kwargs):
super(editListDialog, self).__init__(*args, **kwargs)
self.SetTitle(_(u"Editing the list %s") % (list["name"]))
self.name.ChangeValue(list["name"])
self.description.ChangeValue(list["description"])
if list["mode"] == "public":
self.public.SetValue(True)
else:
self.private.SetValue(True)
class addUserListDialog(listViewer):
def __init__(self, *args, **kwargs):
super(addUserListDialog, self).__init__(*args, **kwargs)
self.SetTitle(_(u"Select a list to add the user"))
self.createBtn.SetLabel(_(u"Add"))
self.createBtn.SetDefault()
self.editBtn.Disable()
self.view.Disable()
# self.subscriptors.Disable()
# self.members.Disable()
self.deleteBtn.Disable()
class removeUserListDialog(listViewer):
def __init__(self, *args, **kwargs):
super(removeUserListDialog, self).__init__(*args, **kwargs)
self.SetTitle(_(u"Select a list to remove the user"))
self.createBtn.SetLabel(_(u"Remove"))
self.createBtn.SetDefault()
self.editBtn.Disable()
self.view.Disable()
# self.subscriptors.Disable()
# self.members.Disable()
self.deleteBtn.Disable()

View File

@@ -0,0 +1,275 @@
# -*- coding: utf-8 -*-
from gi.repository import Gtk
import widgetUtils
class textLimited(widgetUtils.baseDialog):
def __init__(self, *args, **kwargs):
super(textLimited, self).__init__(buttons=(Gtk.STOCK_OK, widgetUtils.OK, Gtk.STOCK_CANCEL, widgetUtils.CANCEL), *args, **kwargs)
def createTextArea(self, message="", text=""):
self.label = Gtk.Label(message)
self.set_title(message, titleWindow=True)
self.text = Gtk.Entry()
self.text.set_max_length(140)
self.text.set_text(text)
self.text.set_placeholder_text(message)
self.set_title(str(len(text)))
self.textBox = Gtk.Box(spacing=10)
self.textBox.add(self.label)
self.textBox.add(self.text)
def get(self, control):
if control == "upload_image":
return self.upload_image.get_label()
elif control == "cb":
return self.cb.get_active_text()
def set(self, control, val):
if control == "upload_image":
self.upload_image.set_label(val)
elif control == "cb":
self.cb.set_active_text(val)
def text_focus(self):
self.text.grab_focus()
def get_text(self):
return self.text.get_text()
def set_text(self, text):
self.text.set_text(text)
def set_title(self, new_title, titleWindow=False):
if titleWindow == False:
self.text.set_placeholder_text(new_title)
else:
super(textLimited, self).set_title(new_title)
# self.set_title(new_title)
def enable_button(self, buttonName):
if getattr(self, buttonName):
return getattr(self, buttonName).show()
def disable_button(self, buttonName):
if getattr(self, buttonName):
return getattr(self, buttonName).hide()
def set_cursor_at_end(self):
self.text.set_position(-1)
def set_cursor_at_position(self, position):
self.text.set_position()
def get_position(self):
return self.text.get_position()
class tweet(textLimited):
def createControls(self, title, message, text):
self.createTextArea(message, text)
self.box.add(self.textBox)
self.upload_image = Gtk.Button(_(u"Upload image..."))
self.spellcheck = Gtk.Button(_("Check spelling..."))
self.attach = Gtk.Button(_(u"Attach audio..."))
self.shortenButton = Gtk.Button(_(u"Shorten URL"))
self.unshortenButton = Gtk.Button(_(u"Expand URL"))
self.shortenButton.hide()
self.shortenButton.set_no_show_all(True)
self.unshortenButton.hide()
self.unshortenButton.set_no_show_all(True)
self.translateButton = Gtk.Button(_(u"Translate..."))
self.autocompletionButton = Gtk.Button(_(u"&Autocomplete users"))
self.buttonsBox1 = Gtk.Box(spacing=6)
self.buttonsBox1.add(self.upload_image)
self.buttonsBox1.add(self.spellcheck)
self.buttonsBox1.add(self.attach)
self.box.add(self.buttonsBox1)
self.buttonsBox2 = Gtk.Box(spacing=6)
self.buttonsBox2.add(self.shortenButton)
self.buttonsBox2.add(self.unshortenButton)
self.buttonsBox2.add(self.translateButton)
self.box.add(self.buttonsBox2)
def __init__(self, title, message, text):
super(tweet, self).__init__()
self.createControls(message, title, text)
self.show_all()
def get_image(self):
dialog = Gtk.FileChooserDialog(_(u"Select the picture to be uploaded"), self, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
filter_jpg = Gtk.FileFilter()
filter_jpg.set_name(_(u"JPG images"))
filter_jpg.add_mime_type("image/jpeg")
dialog.add_filter(filter_jpg)
filter_gif = Gtk.FileFilter()
filter_gif.set_name(_(u"GIF images"))
filter_gif.add_mime_type("image/gif")
dialog.add_filter(filter_gif)
filter_png = Gtk.FileFilter()
filter_png.set_name(_(u"PNG Images"))
filter_png.add_mime_type("image/png")
dialog.add_filter(filter_png)
answer = dialog.run()
if answer == widgetUtils.OK:
image = dialog.get_filename()
dialog.destroy()
return open(image, "rb")
else:
dialog.destroy()
return None
class dm(textLimited):
def createControls(self, title, message, users):
label = Gtk.Label(_(u"Recipient"))
self.cb = Gtk.ComboBoxText.new_with_entry()
self.cb.set_entry_text_column(0)
for user in users:
self.cb.append_text(user)
self.cb.get_child().set_placeholder_text(_(u"Recipient"))
self.cb.get_child().set_text(users[0])
self.autocompletionButton = Gtk.Button(_(u"&Autocomplete users"))
self.createTextArea(message, text="")
userBox = Gtk.Box(spacing=8)
userBox.add(label)
userBox.add(self.cb)
userBox.add(self.autocompletionButton)
self.box.add(userBox)
# self.mainBox.Add(self.cb, 0, wx.ALL, 5)
self.box.add(self.textBox)
self.spellcheck = Gtk.Button(_("Spelling correction"))
self.attach = Gtk.Button(_(u"Attach audio"))
self.shortenButton = Gtk.Button(_(u"Shorten URL"))
self.unshortenButton = Gtk.Button(_(u"Expand URL"))
self.shortenButton.hide()
self.shortenButton.set_no_show_all(True)
self.unshortenButton.hide()
self.unshortenButton.set_no_show_all(True)
self.translateButton = Gtk.Button(_(u"Translate message"))
self.buttonsBox = Gtk.Box(spacing=6)
self.buttonsBox.add(self.spellcheck)
self.buttonsBox.add(self.attach)
self.box.add(self.buttonsBox)
self.buttonsBox1 = Gtk.Box(spacing=6)
self.buttonsBox1.add(self.shortenButton)
self.buttonsBox1.add(self.unshortenButton)
self.buttonsBox1.add(self.translateButton)
self.box.add(self.buttonsBox1)
self.text.grab_focus()
def __init__(self, title, message, users):
super(dm, self).__init__()
self.createControls(message, title, users)
# self.onTimer(wx.EVT_CHAR_HOOK)
self.show_all()
def get_user(self):
return self.cb.get_text()
def set_user(self, user):
return self.cb.set_value()
class reply(tweet):
def __init__(self, title, message, text):
super(reply, self).__init__(message, title, text)
self.text.set_position(-1)
self.mentionAll = Gtk.Button(_(u"Men&tion to all"))
self.mentionAll.set_no_show_all(True)
self.mentionAll.hide()
self.buttonsBox1.add(self.mentionAll)
class viewTweet(widgetUtils.baseDialog):
def set_title(self, lenght):
pass
self.set_title(_(u"Tweet - %i characters ") % (lenght,))
def __init__(self, text, rt_count, favs_count):
super(viewTweet, self).__init__(buttons=(Gtk.STOCK_OK, widgetUtils.OK, Gtk.STOCK_CANCEL, widgetUtils.CANCEL))
label = Gtk.Label(_(u"Tweet"))
self.text = Gtk.TextView()
self.textBuffer = self.text.get_buffer()
self.textBuffer.set_text(text)
self.text.set_editable(False)
# self.textBuffer.set_placeholder_text(message)
textBox = Gtk.Box(spacing=6)
textBox.add(label)
textBox.add(self.text)
self.box.add(textBox)
rtCountLabel = Gtk.Label(_(u"Retweets: "))
rtCount = Gtk.Entry()
rtCount.set_text(rt_count)
rtCount.set_editable(False)
rtBox = Gtk.Box(spacing=2)
rtBox.add(rtCountLabel)
rtBox.add(rtCount)
favsCountLabel = Gtk.Label(_(u"Favourites: "))
favsCount = Gtk.Entry()
favsCount.set_text(favs_count)
favsCount.set_editable(False)
favsBox = Gtk.Box(spacing=2)
favsBox.add(favsCountLabel)
favsBox.add(favsCount)
infoBox = Gtk.Box(spacing=4)
infoBox.add(rtBox)
infoBox.add(favsBox)
self.box.add(infoBox)
self.spellcheck = Gtk.Button(_("Spelling correction"))
self.unshortenButton = Gtk.Button(_(u"Expand URL"))
self.unshortenButton.hide()
self.unshortenButton.set_no_show_all(True)
self.translateButton = Gtk.Button(_(u"Translate message"))
buttonsBox = Gtk.Box(spacing=6)
buttonsBox.add(self.spellcheck)
buttonsBox.add(self.unshortenButton)
buttonsBox.add(self.translateButton)
self.box.add(buttonsBox)
self.show_all()
def set_text(self, text):
self.textBuffer.set_text(text)
def get_text(self):
return self.textBuffer.get_text(self.textBuffer.get_start_iter(), self.textBuffer.get_end_iter(), False)
def text_focus(self):
self.text.grab_focus()
def enable_button(self, buttonName):
if getattr(self, buttonName):
return getattr(self, buttonName).show()
class viewNonTweet(widgetUtils.baseDialog):
def __init__(self, text):
super(viewNonTweet, self).__init__(buttons=(Gtk.STOCK_OK, widgetUtils.OK, Gtk.STOCK_CANCEL, widgetUtils.CANCEL))
self.set_title(_(u"View"))
label = Gtk.Label(_(u"Item"))
self.text = Gtk.TextView()
self.text.set_editable(False)
self.text.get_buffer().set_text(text)
textBox = Gtk.Box(spacing=5)
textBox.add(label)
textBox.add(self.text)
self.box.Add(textBox)
self.spellcheck = Gtk.Button(_("Spelling correction"))
self.unshortenButton = Gtk.Button(_(u"Expand URL"))
self.unshortenButton.hide()
self.unshortenButton.set_no_show_all(True)
self.translateButton = Gtk.Button(_(u"Translate message"))
buttonsBox = Gtk.Box(spacing=6)
buttonsBox.add(self.spellcheck)
buttonsBox.add(self.unshortenButton)
buttonsBox.add(self.translateButton)
self.box.Add(buttonsBox)
self.show_all()
def set_text(self, text):
self.text.get_buffer().set_text()
def get_text(self):
return self.text.get_buffer().get_text()
def text_focus(self):
self.text.grab_focus()
def enable_button(self, buttonName):
if getattr(self, buttonName):
return getattr(self, buttonName).show()

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
import baseDialog
import wx
class searchDialog(baseDialog.BaseWXDialog):
def __init__(self, value=""):
super(searchDialog, self).__init__(None, -1)
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetTitle(_(u"Search on Twitter"))
label = wx.StaticText(panel, -1, _(u"Search"))
self.term = wx.TextCtrl(panel, -1, value)
dc = wx.WindowDC(self.term)
dc.SetFont(self.term.GetFont())
self.term.SetSize(dc.GetTextExtent("0"*40))
sizer.Add(label, 0, wx.ALL, 5)
sizer.Add(self.term, 0, wx.ALL, 5)
self.tweets = wx.RadioButton(panel, -1, _(u"Tweets"), style=wx.RB_GROUP)
self.users = wx.RadioButton(panel, -1, _(u"Users"))
radioSizer = wx.BoxSizer(wx.HORIZONTAL)
radioSizer.Add(self.tweets, 0, wx.ALL, 5)
radioSizer.Add(self.users, 0, wx.ALL, 5)
sizer.Add(radioSizer, 0, wx.ALL, 5)
ok = wx.Button(panel, wx.ID_OK, _(u"OK"))
ok.SetDefault()
cancel = wx.Button(panel, wx.ID_CANCEL, _(u"Close"))
btnsizer = wx.BoxSizer()
btnsizer.Add(ok, 0, wx.ALL, 5)
btnsizer.Add(cancel, 0, wx.ALL, 5)
sizer.Add(btnsizer, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())

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