45 Commits
v0.20 ... next

Author SHA1 Message Date
ead54fce3f Fixed error with some posts in the home timeline that were making socializer to not load anything else 2019-07-04 08:38:47 -05:00
ae93efb17a Display version type in about dialog between alpha or stable 2019-07-02 09:26:07 -05:00
d95dfb18fd Removed some settings no longer needed 2019-07-02 08:55:55 -05:00
4e3c397ce5 Refactored some functions to call wx's threads properly. 2019-06-10 09:26:10 -05:00
4e6126405f Send links when posting a topic comment 2019-06-06 13:42:22 -05:00
8cdec543e1 Modified auth info for 2 factor auth 2019-06-05 10:38:59 -05:00
4810cbe138 Added timeline creation from the context menu in people buffers 2019-05-29 17:51:37 -05:00
8ec7fbb49e Added support for displaying and opening wall posts as attachments in conversations 2019-05-29 09:17:02 -05:00
7d52ed8802 Fixed error retrieving group information of objects not present in the db. 2019-05-29 09:16:16 -05:00
9d44d063eb Modified timeline dialog for a future change 2019-05-24 17:42:59 -05:00
d5eb9ed478 Merge branch 'master' of code.manuelcortez.net:manuelcortez/socializer 2019-05-24 16:33:20 -05:00
40afb6cdc2 Fixed error when attenpting to upload photos to wall posts. Now it should work normally 2019-05-21 16:20:38 -05:00
74234476ab Updated button label in group feed buffers 2019-05-20 21:32:51 -05:00
82c71a3bd8 Merge branch 'master' of code.manuelcortez.net:manuelcortez/socializer 2019-05-20 00:42:19 -05:00
5161e1c045 Updated changelog 2019-05-19 12:35:19 -05:00
40052dbf3f Fixed audio player on walls 2019-05-15 17:19:02 -05:00
e73d92754e Fixed a typo in documentation translation 2019-05-07 11:38:04 -05:00
6df1b80941 Test 4 2019-05-07 10:37:31 -05:00
a9e5963b74 Test 3 2019-05-07 10:35:12 -05:00
95671966cd Test 2 2019-05-07 10:32:12 -05:00
a8ce7216e1 Test 2019-05-07 10:29:15 -05:00
5c6829165b Fixed a typo 2019-05-07 03:46:14 -05:00
91eebfd895 Added topic creation in groups (experimental) 2019-05-06 17:57:34 -05:00
72cc04342f Restructured group info storage for a wider usage 2019-05-06 15:50:45 -05:00
75267294f9 Now it is possible to post as group or username in group walls 2019-05-06 14:58:45 -05:00
f81d302c9a Updated player 2019-05-06 12:09:53 -05:00
c3ab0406e4 Updated documentation translations 2019-05-06 11:45:58 -05:00
fdcaf4e596 Fixed two factor auth in socializer! 2019-05-05 01:01:31 -05:00
ca7b3eff29 Added some debug info 2019-05-04 14:25:39 -05:00
f1eb640564 Select songs with spacebar and play audio from selected tracks has been implemented 2019-05-02 05:57:49 -05:00
194ca2d380 Implemented a selection control in audio buffers (not working yet) 2019-04-30 17:36:53 -05:00
976e90f0a0 Fixed audio methods 2019-04-30 15:32:38 -05:00
2c836f473d Fixed a strange issue with play_all function 2019-04-30 15:31:54 -05:00
f834b6046e Added the full implementation, pending usage into an experiment 2019-04-26 17:53:55 -05:00
e6087f3818 Fixed a typo 2019-04-26 17:51:18 -05:00
20c3df6be2 Added experiment of a selectableList (not implemented, et) 2019-04-26 17:49:54 -05:00
b914e4b548 Merge branch 'next' 2019-04-25 17:42:16 -05:00
f336489609 Released v0.20 2019-04-25 12:10:35 -05:00
4ce2e88568 Updated changelog 2019-04-25 11:38:38 -05:00
a917a6a9cd Ducking when voice messages are being played 2019-04-25 11:38:00 -05:00
a01eabea91 Separate playback of voice messages from regular audio playback 2019-04-25 10:18:13 -05:00
605f0da751 Modified worker in player module so it will set stopped to True after finishing playback 2019-04-25 09:58:53 -05:00
6bd0c10ef2 Removed some old keystrokes no longer needed 2019-04-25 09:17:48 -05:00
a9032602bf Added play and pause functionality in the audios displayer 2019-04-25 08:56:12 -05:00
8c03601bd2 Change lable to 'pause' in the play button when something is playing 2019-04-25 08:48:19 -05:00
29 changed files with 1183 additions and 848 deletions

View File

@@ -21,6 +21,27 @@ test_py3:
- '%PYTHON3% -m coverage report --omit="test*"' - '%PYTHON3% -m coverage report --omit="test*"'
coverage: '/TOTAL.+ ([0-9]{1,3}%)/' coverage: '/TOTAL.+ ([0-9]{1,3}%)/'
documentation:
type: deploy
tags:
- windows10
before_script:
- '%PYTHON3% -v'
script:
- copy changelog.md doc\changelog.md
- cd doc
- '%PYTHON2% documentation_importer.py'
- cd ..\src
- '%PYTHON2% ..\doc\generator.py'
- 'move documentation ..\'
only:
- master
artifacts:
paths:
- documentation
name: socializer_documentation
expire_in: 1 day
alpha_python3: alpha_python3:
type: deploy type: deploy
tags: tags:

View File

@@ -4,6 +4,34 @@
### New additions ### New additions
* Added "post in groups" feature. In order to do so, you need to load the posts for the group where you want to send something. Once loaded, go to the post button in the group's wall and select whether you want to post in the group's behalf or as yourself.
* In all audio buffers, it is possible to select individual tracks to be played together. In order to do so, you need to press space to start the selection of items. When selected, the item will emit a sound to indicate the change. Press space in all items you want to select/unselect. When you're focusing an already selected item it will play a sound to indicate that it is already selected. Once you're done with your selection, pressing enter in the list of tracks will start the playback of the list of items you have selected. This is a very experimental feature. More actions can be supported based in this selection method if it proves to be useful.
* In conversation buffers, it is possible to display and open wall posts sent as attachments in messages.
* In people buffers, it is possible to create a new timeline by using the context menu while focusing an user. This method will create the buffer for the selected user, as opposed to creating the buffer from the menu bar, where you have to type the username or find it in a list.
### bugfixes
* Fixed an error with two factor authentication in the recent socializer version. Now it works reliably again.
* Fixed an error when trying to attach a photo to a wall post. The error was fixed in the [vk_api](https://github.com/python273/vk_api) module and the fix was sent to the developer of the library, so he will be able to merge it in the next version. In the meantime, socializer already includes the fix for this method, so you can upload photos to wall posts normally.
* Fixed an error retrieving some group information for the current session.
* When posting in a topic, links will be posted properly.
### Changes
* the audio player module has received some improvements:
- When there is something being played, the label of the "play" button, located in all audio buffers, will change automatically to "pause". When pressed, it will pause the song instead of starting the playback again since the beginning.
- The last change will work also in the dialog for displaying audio information. Now it's possible to play and pause an audio from such dialog.
- When playing a voice message, if a song is playing already socializer will decrease the volume so you can hear the voice message well enough. Some seconds after the message has finished, the song's volume will be restored.
* In audio buffers, you will play the focused audio when pressing enter in the item, instead of opening the audio information dialog.
* Removed some old keystrokes in socializer buffers due to better alternatives. The reason for this change is that currently you don't need to be focusing an item in a buffer for being able to use the alternative keystrokes, which makes those a better implementation.
- Control+Enter and control+Shift+Enter: superseeded by Control+P for playing (and pausing) the currently focused audio.
- F5 and F6: superseeded by Alt+down and up arrows for modifying volume.
## changes in version 0.20 (25.04.2019)
### New additions
* For users with multiple soundcards, there is a new tab in the preferences dialogue of Socializer, called sound. From there, you can define which soundcard will be used for input and output. [#25,](https://code.manuelcortez.net/manuelcortez/socializer/issues/25) * For users with multiple soundcards, there is a new tab in the preferences dialogue of Socializer, called sound. From there, you can define which soundcard will be used for input and output. [#25,](https://code.manuelcortez.net/manuelcortez/socializer/issues/25)
* the audio player can seek positions in the currently playing track. You can use the menu items (located in the main menu) or use the new available keystrokes dedicated to the actions. Seeking will be made in 5 second intervals. * the audio player can seek positions in the currently playing track. You can use the menu items (located in the main menu) or use the new available keystrokes dedicated to the actions. Seeking will be made in 5 second intervals.
* Alt+Shift+Left arrow: Seek 5 seconds backwards. * Alt+Shift+Left arrow: Seek 5 seconds backwards.

View File

@@ -3,7 +3,10 @@ wxpython==4.0.3
pywin32 pywin32
pyenchant pyenchant
markdown markdown
vk_api # For the moment we will use the modified vk_api version that does not try to include enum34 and
# already fixed the caption for wall uploaded photos.
# Hopefully I can uncomment this in the near future when my changes will get merged upstream.
#vk_api
bs4 bs4
configobj configobj
pypubsub pypubsub
@@ -19,3 +22,5 @@ git+https://code.manuelcortez.net/manuelcortez/libloader
git+https://code.manuelcortez.net/manuelcortez/platform_utils git+https://code.manuelcortez.net/manuelcortez/platform_utils
git+https://github.com/chrisnorman7/sound_lib git+https://github.com/chrisnorman7/sound_lib
git+https://code.manuelcortez.net/manuelcortez/accessible_output2 git+https://code.manuelcortez.net/manuelcortez/accessible_output2
# Modified vk_api.
git+https://github.com/manuelcortez/vk_api

View File

@@ -55,11 +55,12 @@ def get_non_refreshed(login, password, scope=scope):
url = "https://oauth.vk.com/token" url = "https://oauth.vk.com/token"
params = dict(grant_type="password", params = dict(grant_type="password",
client_id=client_id, client_secret=client_secret, username=login, client_id=client_id, client_secret=client_secret, username=login,
password=password, v=api_ver, scope=scope, lang="en", device_id=device_id) password=password, v=api_ver, scope=scope, lang="en", device_id=device_id, force_sms=1)
# Add two factor auth later due to python's syntax. # Add two factor auth later due to python's syntax.
params["2fa_supported"] = 1 params["2fa_supported"] = 1
headers = {'User-Agent': user_agent} headers = {'User-Agent': user_agent}
r = requests.get(url, params=params, headers=headers) r = requests.get(url, params=params, headers=headers)
log.exception(r.json())
# If a 401 error is raised, we need to use 2FA here. # If a 401 error is raised, we need to use 2FA here.
# see https://vk.com/dev/auth_direct (switch lang to russian, english docs are very incomplete in the matter) # see https://vk.com/dev/auth_direct (switch lang to russian, english docs are very incomplete in the matter)
# ToDo: this needs testing after implemented official VK tokens. # ToDo: this needs testing after implemented official VK tokens.

View File

@@ -4,24 +4,14 @@ import time
import wx import wx
import widgetUtils import widgetUtils
code = None
remember = True
def two_factor_auth(): def two_factor_auth():
global code, remember code = None
wx.CallAfter(get_code)
while code == None:
time.sleep(0.5)
return (code, remember)
def get_code():
global code, remember
dlg = wx.TextEntryDialog(None, _("Please provide the authentication code you have received from VK."), _("Two factor authentication code")) dlg = wx.TextEntryDialog(None, _("Please provide the authentication code you have received from VK."), _("Two factor authentication code"))
response = dlg.ShowModal() response = dlg.ShowModal()
if response == widgetUtils.OK: if response == widgetUtils.OK:
code = dlg.GetValue() code = dlg.GetValue()
dlg.Destroy() dlg.Destroy()
dlg.Destroy() return (code, True)
def bad_password(): def bad_password():
return wx.MessageDialog(None, _("Your password or email address are incorrect. Please fix the mistakes and try it again."), _("Wrong data"), wx.ICON_ERROR).ShowModal() return wx.MessageDialog(None, _("Your password or email address are incorrect. Please fix the mistakes and try it again."), _("Wrong data"), wx.ICON_ERROR).ShowModal()

View File

@@ -19,6 +19,7 @@ from requests.exceptions import ReadTimeout, ConnectionError
from mutagen.id3 import ID3 from mutagen.id3 import ID3
from presenters import player from presenters import player
from wxUI.tabs import home from wxUI.tabs import home
from wxUI.dialogs import timeline
from sessionmanager import session, renderers, utils from sessionmanager import session, renderers, utils
from mysc.thread_utils import call_threaded from mysc.thread_utils import call_threaded
from wxUI import commonMessages, menus from wxUI import commonMessages, menus
@@ -79,7 +80,7 @@ class baseBuffer(object):
def insert(self, item, reversed=False): def insert(self, item, reversed=False):
""" Add a new item to the list. Uses renderers.composefunc for parsing the dictionary and create a valid result for putting it in the list.""" """ Add a new item to the list. Uses renderers.composefunc for parsing the dictionary and create a valid result for putting it in the list."""
item_ = getattr(renderers, self.compose_function)(item, self.session) item_ = getattr(renderers, self.compose_function)(item, self.session)
self.tab.list.insert_item(reversed, *item_) wx.CallAfter(self.tab.list.insert_item, reversed, *item_)
def get_items(self, show_nextpage=False): def get_items(self, show_nextpage=False):
""" Retrieve items from the VK API. This function is called repeatedly by the main controller and users could call it implicitly as well with the update buffer option. """ Retrieve items from the VK API. This function is called repeatedly by the main controller and users could call it implicitly as well with the update buffer option.
@@ -304,11 +305,7 @@ class baseBuffer(object):
def get_event(self, ev): def get_event(self, ev):
""" Parses keyboard input in the ListCtrl and executes the event associated with user keypresses.""" """ Parses keyboard input in the ListCtrl and executes the event associated with user keypresses."""
if ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown() and ev.ShiftDown(): event = "pause_audio" if ev.GetKeyCode() == wx.WXK_RETURN: event = "open_post"
elif ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown(): event = "play_audio"
elif ev.GetKeyCode() == wx.WXK_RETURN: event = "open_post"
elif ev.GetKeyCode() == wx.WXK_F5: event = "volume_down"
elif ev.GetKeyCode() == wx.WXK_F6: event = "volume_up"
else: else:
event = None event = None
ev.Skip() ev.Skip()
@@ -377,7 +374,7 @@ class baseBuffer(object):
else: else:
return [post["source_id"]] return [post["source_id"]]
def onFocus(self, *args,**kwargs): def onFocus(self, event, *args,**kwargs):
""" Function executed when the item in a list is selected. """ Function executed when the item in a list is selected.
For this buffer it updates the date of posts in the list.""" For this buffer it updates the date of posts in the list."""
post = self.get_post() post = self.get_post()
@@ -386,6 +383,7 @@ class baseBuffer(object):
original_date = arrow.get(post["date"]) original_date = arrow.get(post["date"])
created_at = original_date.humanize(locale=languageHandler.curLang[:2]) created_at = original_date.humanize(locale=languageHandler.curLang[:2])
self.tab.list.list.SetItem(self.tab.list.get_selected(), 2, created_at) self.tab.list.list.SetItem(self.tab.list.get_selected(), 2, created_at)
event.Skip()
def open_in_browser(self, *args, **kwargs): def open_in_browser(self, *args, **kwargs):
post = self.get_post() post = self.get_post()
@@ -478,11 +476,14 @@ class feedBuffer(baseBuffer):
class communityBuffer(feedBuffer): class communityBuffer(feedBuffer):
def __init__(self, *args, **kwargs):
super(communityBuffer, self).__init__(*args, **kwargs)
self.group_id = self.kwargs["owner_id"]
def create_tab(self, parent): def create_tab(self, parent):
self.tab = home.communityTab(parent) self.tab = home.communityTab(parent)
self.connect_events() self.connect_events()
self.tab.name = self.name self.tab.name = self.name
if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"):
self.tab.post.Enable(False) self.tab.post.Enable(False)
def connect_events(self): def connect_events(self):
@@ -499,21 +500,41 @@ class communityBuffer(feedBuffer):
""" This method retrieves community information, useful to show different parts of the community itself.""" """ This method retrieves community information, useful to show different parts of the community itself."""
if self.can_get_items: if self.can_get_items:
# Strangely, groups.get does not return counters so we need those to show options for loading specific posts for communities. # Strangely, groups.get does not return counters so we need those to show options for loading specific posts for communities.
self.group_info = self.session.vk.client.groups.getById(group_ids=-1*self.kwargs["owner_id"], fields="counters")[0] group_info = self.session.vk.client.groups.getById(group_ids=-1*self.kwargs["owner_id"], fields="counters")[0]
# print(self.group_info["counters"]) self.session.db["group_info"][self.group_id].update(group_info)
if "can_post" in self.session.db["group_info"][self.group_id] and self.session.db["group_info"][self.group_id]["can_post"] == True:
self.tab.post.Enable(True)
super(communityBuffer, self).get_items(*args, **kwargs) super(communityBuffer, self).get_items(*args, **kwargs)
def post(self, *args, **kwargs):
menu = wx.Menu()
user1 = self.session.get_user(self.session.user_id)
user2 = self.session.get_user(self.kwargs["owner_id"])
user = menu.Append(wx.NewId(), _("Post as {user1_nom}").format(**user1))
group = menu.Append(wx.NewId(), _("Post as {user1_nom}").format(**user2))
menu.Bind(widgetUtils.MENU, lambda evt: self._post(evt, 1), group)
menu.Bind(widgetUtils.MENU, lambda evt: self._post(evt, 0), user)
self.tab.post.PopupMenu(menu, self.tab.post.GetPosition())
def _post(self, event, from_group):
owner_id = self.kwargs["owner_id"]
user = self.session.get_user(owner_id, key="user1")
title = _("Post to {user1_nom}'s wall").format(**user)
p = presenters.createPostPresenter(session=self.session, interactor=interactors.createPostInteractor(), view=views.createPostDialog(title=title, message="", text=""))
if hasattr(p, "text") or hasattr(p, "privacy"):
call_threaded(self.do_last, p=p, owner_id=owner_id, from_group=from_group)
class topicBuffer(feedBuffer): class topicBuffer(feedBuffer):
def create_tab(self, parent): def create_tab(self, parent):
self.tab = home.topicTab(parent) self.tab = home.topicTab(parent)
self.connect_events() self.connect_events()
self.tab.name = self.name self.tab.name = self.name
if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): if "can_create_topic" not in self.session.db["group_info"][self.kwargs["group_id"]*-1] or ("can_create_topic" in self.session.db["group_info"][self.kwargs["group_id"]*-1] and self.session.db["group_info"][self.kwargs["group_id"]*-1]["can_create_topic"] != True):
self.tab.post.Enable(False) self.tab.post.Enable(False)
def onFocus(self, *args, **kwargs): def onFocus(self, event, *args, **kwargs):
pass event.Skip()
def open_post(self, *args, **kwargs): def open_post(self, *args, **kwargs):
""" Opens the currently focused post.""" """ Opens the currently focused post."""
@@ -524,7 +545,6 @@ class topicBuffer(feedBuffer):
def open_in_browser(self, *args, **kwargs): def open_in_browser(self, *args, **kwargs):
post = self.get_post() post = self.get_post()
print(post)
if post == None: if post == None:
return return
# In order to load the selected topic we firstly have to catch the group_id, which is present in self.kwargs # In order to load the selected topic we firstly have to catch the group_id, which is present in self.kwargs
@@ -533,6 +553,43 @@ class topicBuffer(feedBuffer):
url = "https://vk.com/topic{group_id}_{topic_id}".format(group_id=group_id, topic_id=post["id"]) url = "https://vk.com/topic{group_id}_{topic_id}".format(group_id=group_id, topic_id=post["id"])
webbrowser.open_new_tab(url) webbrowser.open_new_tab(url)
def post(self, *args, **kwargs):
menu = wx.Menu()
user1 = self.session.get_user(self.session.user_id)
user2 = self.session.get_user(-1*self.kwargs["group_id"])
user = menu.Append(wx.NewId(), _("Post as {user1_nom}").format(**user1))
group = menu.Append(wx.NewId(), _("Post as {user1_nom}").format(**user2))
menu.Bind(widgetUtils.MENU, lambda evt: self._post(evt, 1), group)
menu.Bind(widgetUtils.MENU, lambda evt: self._post(evt, 0), user)
self.tab.post.PopupMenu(menu, self.tab.post.GetPosition())
def _post(self, event, from_group):
owner_id = self.kwargs["group_id"]
user = self.session.get_user(-1*owner_id, key="user1")
title = _("Create topic in {user1_nom}").format(**user)
p = presenters.createPostPresenter(session=self.session, interactor=interactors.createPostInteractor(), view=views.createTopicDialog(title=title, message="", text=""))
if hasattr(p, "text") or hasattr(p, "privacy"):
call_threaded(self.do_last, p=p, group_id=owner_id, from_group=from_group)
def do_last(self, p, *args, **kwargs):
title = p.view.title
msg = p.text
attachments = ""
if hasattr(p, "attachments"):
attachments = self.upload_attachments(p.attachments)
urls = utils.find_urls_in_text(msg)
if len(urls) != 0:
if len(attachments) == 0: attachments = urls[0]
else: attachments += urls[0]
msg = msg.replace(urls[0], "")
if msg != "":
kwargs.update(text=msg, title=title.GetValue())
if attachments != "":
kwargs.update(attachments=attachments)
# Determines the correct functions to call here.
post = self.session.vk.client.board.addTopic(**kwargs)
pub.sendMessage("posted", buffer=self.name)
class documentBuffer(feedBuffer): class documentBuffer(feedBuffer):
can_get_items = False can_get_items = False
@@ -543,13 +600,14 @@ class documentBuffer(feedBuffer):
if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"): if hasattr(self, "can_post") and self.can_post == False and hasattr(self.tab, "post"):
self.tab.post.Enable(False) self.tab.post.Enable(False)
def onFocus(self, *args,**kwargs): def onFocus(self, event, *args,**kwargs):
post = self.get_post() post = self.get_post()
if post == None: if post == None:
return return
original_date = arrow.get(post["date"]) original_date = arrow.get(post["date"])
created_at = original_date.humanize(locale=languageHandler.curLang[:2]) created_at = original_date.humanize(locale=languageHandler.curLang[:2])
self.tab.list.list.SetItem(self.tab.list.get_selected(), 4, created_at) self.tab.list.list.SetItem(self.tab.list.get_selected(), 4, created_at)
event.Skip()
def connect_events(self): def connect_events(self):
super(documentBuffer, self).connect_events() super(documentBuffer, self).connect_events()
@@ -619,9 +677,6 @@ class documentCommunityBuffer(documentBuffer):
self.tab.post.Enable(False) self.tab.post.Enable(False)
class audioBuffer(feedBuffer): class audioBuffer(feedBuffer):
""" this buffer was supposed to be used with audio elements
but is deprecated as VK removed its audio support for third party apps."""
def create_tab(self, parent): def create_tab(self, parent):
self.tab = home.audioTab(parent) self.tab = home.audioTab(parent)
self.tab.name = self.name self.tab.name = self.name
@@ -629,16 +684,38 @@ class audioBuffer(feedBuffer):
if self.name == "me_audio": if self.name == "me_audio":
self.tab.post.Enable(True) self.tab.post.Enable(True)
def get_event(self, ev):
if ev.GetKeyCode() == wx.WXK_RETURN:
if len(self.tab.list.get_multiple_selection()) < 2:
event = "play_all"
else:
event = "play_audio"
else:
event = None
ev.Skip()
if event != None:
try:
getattr(self, event)(skip_pause=True)
except AttributeError:
pass
def connect_events(self): def connect_events(self):
widgetUtils.connect_event(self.tab.play, widgetUtils.BUTTON_PRESSED, self.play_audio) widgetUtils.connect_event(self.tab.play, widgetUtils.BUTTON_PRESSED, self.play_audio)
widgetUtils.connect_event(self.tab.play_all, widgetUtils.BUTTON_PRESSED, self.play_all) widgetUtils.connect_event(self.tab.play_all, widgetUtils.BUTTON_PRESSED, self.play_all)
pub.subscribe(self.change_label, "playback-changed")
super(audioBuffer, self).connect_events() super(audioBuffer, self).connect_events()
def play_audio(self, *args, **kwargs): def play_audio(self, *args, **kwargs):
selected = self.tab.list.get_selected() if player.player.check_is_playing() and not "skip_pause" in kwargs:
if selected == -1: return pub.sendMessage("pause")
selected = 0 selected = self.tab.list.get_multiple_selection()
pub.sendMessage("play", object=self.session.db[self.name]["items"][selected]) if len(selected) == 0:
return
elif len(selected) == 1:
pub.sendMessage("play", object=self.session.db[self.name]["items"][selected[0]])
else:
selected_audios = [self.session.db[self.name]["items"][item] for item in selected]
pub.sendMessage("play-all", list_of_songs=selected_audios)
return True return True
def play_next(self, *args, **kwargs): def play_next(self, *args, **kwargs):
@@ -696,8 +773,8 @@ class audioBuffer(feedBuffer):
# Translators: Some buffers can't use the get previous item feature due to API limitations. # Translators: Some buffers can't use the get previous item feature due to API limitations.
output.speak(_("This buffer doesn't support getting more items.")) output.speak(_("This buffer doesn't support getting more items."))
def onFocus(self, *args, **kwargs): def onFocus(self, event, *args, **kwargs):
pass event.Skip()
def add_to_library(self, *args, **kwargs): def add_to_library(self, *args, **kwargs):
post = self.get_post() post = self.get_post()
@@ -778,6 +855,16 @@ class audioBuffer(feedBuffer):
url = "https://vk.com/audio{user_id}_{post_id}".format(user_id=post["owner_id"], post_id=post["id"]) url = "https://vk.com/audio{user_id}_{post_id}".format(user_id=post["owner_id"], post_id=post["id"])
webbrowser.open_new_tab(url) webbrowser.open_new_tab(url)
def change_label(self, stopped):
if hasattr(self.tab, "play"):
if stopped == False:
self.tab.play.SetLabel(_("P&ause"))
else:
self.tab.play.SetLabel(_("P&lay"))
def __del__(self):
pub.unsubscribe(self.change_label, "playback-changed")
class audioAlbum(audioBuffer): class audioAlbum(audioBuffer):
""" this buffer was supposed to be used with audio albums """ this buffer was supposed to be used with audio albums
but is deprecated as VK removed its audio support for third party apps.""" but is deprecated as VK removed its audio support for third party apps."""
@@ -852,8 +939,8 @@ class videoBuffer(feedBuffer):
# Translators: Some buffers can't use the get previous item feature due to API limitations. # Translators: Some buffers can't use the get previous item feature due to API limitations.
output.speak(_("This buffer doesn't support getting more items.")) output.speak(_("This buffer doesn't support getting more items."))
def onFocus(self, *args, **kwargs): def onFocus(self, event, *args, **kwargs):
pass event.Skip()
def add_to_library(self, *args, **kwargs): def add_to_library(self, *args, **kwargs):
post = self.get_post() post = self.get_post()
@@ -1056,9 +1143,9 @@ class chatBuffer(baseBuffer):
if show_nextpage == False: if show_nextpage == False:
if self.tab.history.GetValue() != "" and num > 0: if self.tab.history.GetValue() != "" and num > 0:
v = [i for i in self.session.db[self.name]["items"][:num]] v = [i for i in self.session.db[self.name]["items"][:num]]
[self.insert(i, False) for i in v] [wx.CallAfter(self.insert, i, False) for i in v]
else: else:
[self.insert(i) for i in self.session.db[self.name]["items"][:num]] [wx.CallAfter(self.insert, i) for i in self.session.db[self.name]["items"][:num]]
else: else:
if num > 0: if num > 0:
# At this point we save more CPU and mathematical work if we just delete everything in the chat history and readd all messages. # At this point we save more CPU and mathematical work if we just delete everything in the chat history and readd all messages.
@@ -1068,7 +1155,7 @@ class chatBuffer(baseBuffer):
self.chats = dict() self.chats = dict()
self.tab.history.SetValue("") self.tab.history.SetValue("")
v = [i for i in self.session.db[self.name]["items"]] v = [i for i in self.session.db[self.name]["items"]]
[self.insert(i) for i in v] [wx.CallAfter(self.insert, i) for i in v]
# Now it's time to set back the focus in the post. # Now it's time to set back the focus in the post.
for i in self.chats.keys(): for i in self.chats.keys():
if self.chats[i] == focused_post["id"]: if self.chats[i] == focused_post["id"]:
@@ -1186,8 +1273,7 @@ class chatBuffer(baseBuffer):
a = presenters.displayAudioPresenter(session=self.session, postObject=[attachment["audio"]], interactor=interactors.displayAudioInteractor(), view=views.displayAudio()) a = presenters.displayAudioPresenter(session=self.session, postObject=[attachment["audio"]], interactor=interactors.displayAudioInteractor(), view=views.displayAudio())
elif attachment["type"] == "audio_message": elif attachment["type"] == "audio_message":
link = attachment["audio_message"]["link_mp3"] link = attachment["audio_message"]["link_mp3"]
output.speak(_("Playing...")) pub.sendMessage("play-message", message_url=link)
pub.sendMessage("play", object=dict(url=link), set_info=False)
elif attachment["type"] == "link": elif attachment["type"] == "link":
output.speak(_("Opening URL..."), True) output.speak(_("Opening URL..."), True)
webbrowser.open_new_tab(attachment["link"]["url"]) webbrowser.open_new_tab(attachment["link"]["url"])
@@ -1217,6 +1303,8 @@ class chatBuffer(baseBuffer):
break break
if url != "": if url != "":
webbrowser.open_new_tab(url) webbrowser.open_new_tab(url)
if attachment["type"] == "wall":
pub.sendMessage("open-post", post_object=attachment["wall"], controller_="displayPost")
else: else:
log.debug("Unhandled attachment: %r" % (attachment,)) log.debug("Unhandled attachment: %r" % (attachment,))
@@ -1279,7 +1367,14 @@ class peopleBuffer(feedBuffer):
self.tab.list.list.SetItem(self.tab.list.get_selected(), 1, online_status) self.tab.list.list.SetItem(self.tab.list.get_selected(), 1, online_status)
def open_timeline(self, *args, **kwargs): def open_timeline(self, *args, **kwargs):
pass user = self.get_post()
if user == None:
return
a = timeline.timelineDialog([self.session.get_user(user["id"])["user1_gen"]], show_selector=False)
if a.get_response() == widgetUtils.OK:
buffer_type = a.get_buffer_type()
user_id = user["id"]
pub.sendMessage("create-timeline", user_id=user_id, buffer_type=buffer_type)
def get_menu(self, *args, **kwargs): def get_menu(self, *args, **kwargs):
""" display menu for people buffers (friends and requests)""" """ display menu for people buffers (friends and requests)"""

View File

@@ -200,13 +200,14 @@ class Controller(object):
if self.session.settings["load_at_startup"]["communities"] == False and force_action == False: if self.session.settings["load_at_startup"]["communities"] == False and force_action == False:
return return
log.debug("Create community buffers...") log.debug("Create community buffers...")
groups= self.session.vk.client.groups.get(user_id=user_id, extended=1, count=1000) groups= self.session.vk.client.groups.get(user_id=user_id, extended=1, count=1000, fields="can_post,can_create_topic")
self.session.groups=groups["items"] self.session.groups=groups["items"]
# Let's feed the local database cache with new groups coming from here. # Let's feed the local database cache with new groups coming from here.
data= dict(profiles=[], groups=self.session.groups) data= dict(profiles=[], groups=self.session.groups)
self.session.process_usernames(data) self.session.process_usernames(data)
if create_buffers: if create_buffers:
for i in self.session.groups: for i in self.session.groups:
self.session.db["group_info"][i["id"]*-1] = i
wx.CallAfter(pub.sendMessage, "create_buffer", buffer_type="communityBuffer", buffer_title=i["name"], parent_tab="communities", loadable=True, get_items=True, kwargs=dict(parent=self.window.tb, name="{0}_community".format(i["id"],), composefunc="render_status", session=self.session, endpoint="get", parent_endpoint="wall", extended=1, count=self.session.settings["buffers"]["count_for_wall_buffers"], owner_id=-1*i["id"])) wx.CallAfter(pub.sendMessage, "create_buffer", buffer_type="communityBuffer", buffer_title=i["name"], parent_tab="communities", loadable=True, get_items=True, kwargs=dict(parent=self.window.tb, name="{0}_community".format(i["id"],), composefunc="render_status", session=self.session, endpoint="get", parent_endpoint="wall", extended=1, count=self.session.settings["buffers"]["count_for_wall_buffers"], owner_id=-1*i["id"]))
time.sleep(0.15) time.sleep(0.15)
@@ -296,6 +297,7 @@ class Controller(object):
pub.subscribe(self.create_buffer, "create_buffer") pub.subscribe(self.create_buffer, "create_buffer")
pub.subscribe(self.user_typing, "user-typing") pub.subscribe(self.user_typing, "user-typing")
pub.subscribe(self.get_chat, "order-sent-message") pub.subscribe(self.get_chat, "order-sent-message")
pub.subscribe(self.create_timeline, "create-timeline")
def disconnect_events(self): def disconnect_events(self):
log.debug("Disconnecting some events...") log.debug("Disconnecting some events...")
@@ -307,6 +309,7 @@ class Controller(object):
pub.unsubscribe(self.user_online, "user-online") pub.unsubscribe(self.user_online, "user-online")
pub.unsubscribe(self.user_offline, "user-offline") pub.unsubscribe(self.user_offline, "user-offline")
pub.unsubscribe(self.notify, "notify") pub.unsubscribe(self.notify, "notify")
pub.subscribe(self.create_timeline, "create-timeline")
def in_post(self, buffer): def in_post(self, buffer):
""" This event is triggered whenever an user requires an update in their buffers. For example after sending a post successfully. """ This event is triggered whenever an user requires an update in their buffers. For example after sending a post successfully.
@@ -408,7 +411,7 @@ class Controller(object):
if user == None: if user == None:
log.exception("Getting user manually...") log.exception("Getting user manually...")
user = self.session.vk.client.users.get(user_ids=event.user_id, fields="last_seen")[0] user = self.session.vk.client.users.get(user_ids=event.user_id, fields="last_seen")[0]
online_buffer.add_person(user) wx.CallAfter(online_buffer.add_person, user)
def user_offline(self, event): def user_offline(self, event):
""" Sends a notification of an user logging off in VK. """ Sends a notification of an user logging off in VK.
@@ -421,7 +424,7 @@ class Controller(object):
sound = "friend_offline.ogg" sound = "friend_offline.ogg"
self.notify(msg, sound, self.session.settings["chat"]["notifications"]) self.notify(msg, sound, self.session.settings["chat"]["notifications"])
online_friends = self.search("online_friends") online_friends = self.search("online_friends")
online_friends.remove_person(event.user_id) wx.CallAfter(online_friends.remove_person, event.user_id)
def notify(self, message="", sound="", type="native"): def notify(self, message="", sound="", type="native"):
""" display a notification in Socializer. """ display a notification in Socializer.
@@ -509,7 +512,7 @@ class Controller(object):
# Let's add this to the buffer. # Let's add this to the buffer.
# ToDo: Clean this code and test how is the database working with this set to True. # ToDo: Clean this code and test how is the database working with this set to True.
buffer.session.db[buffer.name]["items"].append(message) buffer.session.db[buffer.name]["items"].append(message)
buffer.insert(self.session.db[buffer.name]["items"][-1], False) wx.CallAfter(buffer.insert, self.session.db[buffer.name]["items"][-1], False)
self.session.soundplayer.play("message_received.ogg") self.session.soundplayer.play("message_received.ogg")
wx.CallAfter(self.reorder_buffer, buffer) wx.CallAfter(self.reorder_buffer, buffer)
# Check if we have to read the message aloud # Check if we have to read the message aloud
@@ -517,13 +520,34 @@ class Controller(object):
rendered_message = renderers.render_message(message, self.session) rendered_message = renderers.render_message(message, self.session)
output.speak(rendered_message[0]) output.speak(rendered_message[0])
def create_timeline(self, user_id, buffer_type, user=""):
if user_id == "":
user_data = self.session.vk.client.utils.resolveScreenName(screen_name=user)
if type(user_data) == list:
commonMessages.no_user_exist()
return
user_id = user_data["object_id"]
if buffer_type == "audio":
buffer = buffers.audioBuffer(parent=self.window.tb, name="{0}_audio".format(user_id,), composefunc="render_audio", session=self.session, create_tab=False, endpoint="get", parent_endpoint="audio", owner_id=user_id)
user = self.session.get_user(user_id, key="user1")
name_ = _("{user1_nom}'s audios").format(**user)
elif buffer_type == "wall":
buffer = buffers.feedBuffer(parent=self.window.tb, name="{0}_feed".format(user_id,), composefunc="render_status", session=self.session, create_tab=False, endpoint="get", parent_endpoint="wall", extended=1, count=self.session.settings["buffers"]["count_for_wall_buffers"], owner_id=user_id)
user = self.session.get_user(user_id, key="user1")
name_ = _("{user1_nom}'s posts").format(**user)
elif buffer_type == "friends":
buffer = buffers.peopleBuffer(parent=self.window.tb, name="friends_{0}".format(user_id,), composefunc="render_person", session=self.session, create_tab=False, endpoint="get", parent_endpoint="friends", count=5000, fields="uid, first_name, last_name, last_seen", user_id=user_id)
user = self.session.get_user(user_id, key="user1")
name_ = _("{user1_nom}'s friends").format(**user)
wx.CallAfter(self.complete_buffer_creation, buffer=buffer, name_=name_, position=self.window.search("timelines"))
### GUI events ### GUI events
# These functions are connected to GUI elements such as menus, buttons and so on. # These functions are connected to GUI elements such as menus, buttons and so on.
def connect_gui_events(self): def connect_gui_events(self):
widgetUtils.connect_event(self.window, widgetUtils.CLOSE_EVENT, self.exit) widgetUtils.connect_event(self.window, widgetUtils.CLOSE_EVENT, self.exit)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.update_buffer, menuitem=self.window.update_buffer) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.update_buffer, menuitem=self.window.update_buffer)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.check_for_updates, menuitem=self.window.check_for_updates) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.check_for_updates, menuitem=self.window.check_for_updates)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.window.about_dialog, menuitem=self.window.about) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.on_about, menuitem=self.window.about)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.search_audios, menuitem=self.window.search_audios) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.search_audios, menuitem=self.window.search_audios)
widgetUtils.connect_event(self.window, widgetUtils.MENU, self.search_videos, menuitem=self.window.search_videos) widgetUtils.connect_event(self.window, widgetUtils.MENU, self.search_videos, menuitem=self.window.search_videos)
widgetUtils.connect_event(self.window, widgetUtils.MENU,self.remove_buffer, menuitem=self.window.remove_buffer_) widgetUtils.connect_event(self.window, widgetUtils.MENU,self.remove_buffer, menuitem=self.window.remove_buffer_)
@@ -574,6 +598,10 @@ class Controller(object):
if update == False: if update == False:
commonMessages.no_update_available() commonMessages.no_update_available()
def on_about(self, *args, **kwargs):
channel = self.session.settings["general"]["update_channel"]
self.window.about_dialog(channel)
def search_audios(self, *args, **kwargs): def search_audios(self, *args, **kwargs):
dlg = searchDialogs.searchAudioDialog() dlg = searchDialogs.searchAudioDialog()
if dlg.get_response() == widgetUtils.OK: if dlg.get_response() == widgetUtils.OK:
@@ -656,25 +684,7 @@ class Controller(object):
for i in d: for i in d:
if i[1] == user: if i[1] == user:
user_id = i[0] user_id = i[0]
if user_id == "": pub.sendMessage("create-timeline", user_id=user_id, buffer_type=buffertype)
user_data = self.session.vk.client.utils.resolveScreenName(screen_name=user)
if type(user_data) == list:
commonMessages.no_user_exist()
return
user_id = user_data["object_id"]
if buffertype == "audio":
buffer = buffers.audioBuffer(parent=self.window.tb, name="{0}_audio".format(user_id,), composefunc="render_audio", session=self.session, create_tab=False, endpoint="get", parent_endpoint="audio", owner_id=user_id)
user = self.session.get_user(user_id, key="user1")
name_ = _("{user1_nom}'s audios").format(**user)
elif buffertype == "wall":
buffer = buffers.feedBuffer(parent=self.window.tb, name="{0}_feed".format(user_id,), composefunc="render_status", session=self.session, create_tab=False, endpoint="get", parent_endpoint="wall", extended=1, count=self.session.settings["buffers"]["count_for_wall_buffers"], owner_id=user_id)
user = self.session.get_user(user_id, key="user1")
name_ = _("{user1_nom}'s posts").format(**user)
elif buffertype == "friends":
buffer = buffers.peopleBuffer(parent=self.window.tb, name="friends_{0}".format(user_id,), composefunc="render_person", session=self.session, create_tab=False, endpoint="get", parent_endpoint="friends", count=5000, fields="uid, first_name, last_name, last_seen", user_id=user_id)
user = self.session.get_user(user_id, key="user1")
name_ = _("{user1_nom}'s friends").format(**user)
wx.CallAfter(self.complete_buffer_creation, buffer=buffer, name_=name_, position=self.window.search("timelines"))
def create_audio_album(self, *args, **kwargs): def create_audio_album(self, *args, **kwargs):
d = creation.audio_album() d = creation.audio_album()
@@ -821,19 +831,19 @@ class Controller(object):
# 2. If the group_info does not have counters for such items, which would indicate there are no items posted yet. # 2. If the group_info does not have counters for such items, which would indicate there are no items posted yet.
if self.search(current_buffer.name+"_audios") != False: if self.search(current_buffer.name+"_audios") != False:
menu.load_audios.Enable(False) menu.load_audios.Enable(False)
elif hasattr(current_buffer, "group_info") and "audios" not in current_buffer.group_info["counters"]: elif "counters" in self.session.db["group_info"][current_buffer.group_id] and "audios" not in self.session.db["group_info"][current_buffer.group_id]["counters"]:
menu.load_audios.Enable(False) menu.load_audios.Enable(False)
if self.search(current_buffer.name+"_videos") != False: if self.search(current_buffer.name+"_videos") != False:
menu.load_videos.Enable(False) menu.load_videos.Enable(False)
elif hasattr(current_buffer, "group_info") and "videos" not in current_buffer.group_info["counters"]: elif "counters" in self.session.db["group_info"][current_buffer.group_id] and "videos" not in self.session.db["group_info"][current_buffer.group_id]["counters"]:
menu.load_videos.Enable(False) menu.load_videos.Enable(False)
if self.search(current_buffer.name+"_topics") != False: if self.search(current_buffer.name+"_topics") != False:
menu.load_topics.Enable(False) menu.load_topics.Enable(False)
elif hasattr(current_buffer, "group_info") and "topics" not in current_buffer.group_info["counters"]: elif "counters" in self.session.db["group_info"][current_buffer.group_id] and "topics" not in self.session.db["group_info"][current_buffer.group_id]["counters"]:
menu.load_topics.Enable(False) menu.load_topics.Enable(False)
if self.search(current_buffer.name+"_documents") != False: if self.search(current_buffer.name+"_documents") != False:
menu.load_documents.Enable(False) menu.load_documents.Enable(False)
elif hasattr(current_buffer, "group_info") and "docs" not in current_buffer.group_info["counters"]: elif "counters" in self.session.db["group_info"][current_buffer.group_id] and "docs" not in self.session.db["group_info"][current_buffer.group_id]["counters"]:
menu.load_documents.Enable(False) menu.load_documents.Enable(False)
# Connect the rest of the functions. # Connect the rest of the functions.
widgetUtils.connect_event(menu, widgetUtils.MENU, self.load_community_posts, menuitem=menu.load_posts) widgetUtils.connect_event(menu, widgetUtils.MENU, self.load_community_posts, menuitem=menu.load_posts)
@@ -888,10 +898,10 @@ class Controller(object):
""" Load community audios if they are not loaded already.""" """ Load community audios if they are not loaded already."""
current_buffer = self.get_current_buffer() current_buffer = self.get_current_buffer()
# Get group_info if the community buffer does not have it already, so future menus will be able to use it. # Get group_info if the community buffer does not have it already, so future menus will be able to use it.
if not hasattr(current_buffer, "group_info"): if current_buffer.group_id not in self.session.db["group_info"] or "counters" not in self.session.db["group_info"][current_buffer.group_id]:
group_info = self.session.vk.client.groups.getById(group_ids=-1*current_buffer.kwargs["owner_id"], fields="counters")[0] group_info = self.session.vk.client.groups.getById(group_ids=-1*current_buffer.kwargs["owner_id"], fields="counters,can_create_topics,can_post")[0]
current_buffer.group_info = group_info self.session.db["group_info"][current_buffer.kwargs["owner_id"]].update(group_info)
if "audios" not in current_buffer.group_info["counters"]: if "audios" not in self.session.db["group_info"][current_buffer.group_id]["counters"]:
commonMessages.community_no_items() commonMessages.community_no_items()
return return
new_name = current_buffer.name+"_audios" new_name = current_buffer.name+"_audios"
@@ -901,10 +911,10 @@ class Controller(object):
""" Load community videos if they are not loaded already.""" """ Load community videos if they are not loaded already."""
current_buffer = self.get_current_buffer() current_buffer = self.get_current_buffer()
# Get group_info if the community buffer does not have it already, so future menus will be able to use it. # Get group_info if the community buffer does not have it already, so future menus will be able to use it.
if not hasattr(current_buffer, "group_info"): if current_buffer.group_id not in self.session.db["group_info"] or "counters" not in self.session.db["group_info"][current_buffer.group_id]:
group_info = self.session.vk.client.groups.getById(group_ids=-1*current_buffer.kwargs["owner_id"], fields="counters")[0] group_info = self.session.vk.client.groups.getById(group_ids=-1*current_buffer.kwargs["owner_id"], fields="counters,can_create_topics,can_post")[0]
current_buffer.group_info = group_info self.session.db["group_info"][current_buffer.kwargs["owner_id"]].update(group_info)
if "videos" not in current_buffer.group_info["counters"]: if "videos" not in self.session.db["group_info"][current_buffer.group_id]["counters"]:
commonMessages.community_no_items() commonMessages.community_no_items()
return return
new_name = current_buffer.name+"_videos" new_name = current_buffer.name+"_videos"
@@ -914,10 +924,10 @@ class Controller(object):
""" Load community topics.""" """ Load community topics."""
current_buffer = self.get_current_buffer() current_buffer = self.get_current_buffer()
# Get group_info if the community buffer does not have it already, so future menus will be able to use it. # Get group_info if the community buffer does not have it already, so future menus will be able to use it.
if not hasattr(current_buffer, "group_info"): if current_buffer.group_id not in self.session.db["group_info"] or "counters" not in self.session.db["group_info"][current_buffer.group_id]:
group_info = self.session.vk.client.groups.getById(group_ids=-1*current_buffer.kwargs["owner_id"], fields="counters")[0] group_info = self.session.vk.client.groups.getById(group_ids=-1*current_buffer.kwargs["owner_id"], fields="counters,can_create_topic,can_post")[0]
current_buffer.group_info = group_info self.session.db["group_info"][current_buffer.kwargs["owner_id"]].update(group_info)
if "topics" not in current_buffer.group_info["counters"]: if "topics" not in self.session.db["group_info"][current_buffer.group_id]["counters"]:
commonMessages.community_no_items() commonMessages.community_no_items()
return return
new_name = current_buffer.name+"_topics" new_name = current_buffer.name+"_topics"
@@ -926,10 +936,10 @@ class Controller(object):
def load_community_documents(self, *args, **kwargs): def load_community_documents(self, *args, **kwargs):
current_buffer = self.get_current_buffer() current_buffer = self.get_current_buffer()
# Get group_info if the community buffer does not have it already, so future menus will be able to use it. # Get group_info if the community buffer does not have it already, so future menus will be able to use it.
if not hasattr(current_buffer, "group_info"): if current_buffer.group_id not in self.session.db["group_info"] or "counters" not in self.session.db["group_info"][current_buffer.group_id]:
group_info = self.session.vk.client.groups.getById(group_ids=-1*current_buffer.kwargs["owner_id"], fields="counters")[0] group_info = self.session.vk.client.groups.getById(group_ids=-1*current_buffer.kwargs["owner_id"], fields="counters,can_create_topics,can_post")[0]
current_buffer.group_info = group_info self.session.db["group_info"][current_buffer.kwargs["owner_id"]].update(group_info)
if "docs" not in current_buffer.group_info["counters"]: if "docs" not in self.session.db["group_info"][current_buffer.group_id]["counters"]:
commonMessages.community_no_items() commonMessages.community_no_items()
return return
new_name = current_buffer.name+"_documents" new_name = current_buffer.name+"_documents"

View File

@@ -59,8 +59,6 @@ class configurationInteractor(base.baseInteractor):
self.presenter.update_setting(section="general", setting="update_channel", value=update_channel) self.presenter.update_setting(section="general", setting="update_channel", value=update_channel)
self.presenter.update_setting(section="chat", setting="notify_online", value=self.view.get_value("chat", "notify_online")) self.presenter.update_setting(section="chat", setting="notify_online", value=self.view.get_value("chat", "notify_online"))
self.presenter.update_setting(section="chat", setting="notify_offline", value=self.view.get_value("chat", "notify_offline")) self.presenter.update_setting(section="chat", setting="notify_offline", value=self.view.get_value("chat", "notify_offline"))
self.presenter.update_setting(section="chat", setting="open_unread_conversations", value=self.view.get_value("chat", "open_unread_conversations"))
self.presenter.update_setting(section="chat", setting="automove_to_conversations", value=self.view.get_value("chat", "automove_to_conversations"))
self.presenter.update_setting(section="chat", setting="notifications", value=self.presenter.get_notification_type(self.view.get_value("chat", "notifications"))) self.presenter.update_setting(section="chat", setting="notifications", value=self.presenter.get_notification_type(self.view.get_value("chat", "notifications")))
self.presenter.update_setting(section="load_at_startup", setting="audio_albums", value=self.view.get_value("startup", "audio_albums")) self.presenter.update_setting(section="load_at_startup", setting="audio_albums", value=self.view.get_value("startup", "audio_albums"))
self.presenter.update_setting(section="load_at_startup", setting="video_albums", value=self.view.get_value("startup", "video_albums")) self.presenter.update_setting(section="load_at_startup", setting="video_albums", value=self.view.get_value("startup", "video_albums"))

View File

@@ -171,6 +171,13 @@ class displayAudioInteractor(base.baseInteractor):
getattr(self.view, control).Append(i) getattr(self.view, control).Append(i)
getattr(self.view, control).SetSelection(0) getattr(self.view, control).SetSelection(0)
def change_label(self, stopped):
if stopped == False:
self.view.play.SetLabel(_("P&ause"))
else:
self.view.play.SetLabel(_("P&lay"))
def install(self, *args, **kwargs): def install(self, *args, **kwargs):
super(displayAudioInteractor, self).install(*args, **kwargs) super(displayAudioInteractor, self).install(*args, **kwargs)
widgetUtils.connect_event(self.view.list, widgetUtils.LISTBOX_CHANGED, self.on_change) widgetUtils.connect_event(self.view.list, widgetUtils.LISTBOX_CHANGED, self.on_change)
@@ -180,11 +187,13 @@ class displayAudioInteractor(base.baseInteractor):
widgetUtils.connect_event(self.view.remove, widgetUtils.BUTTON_PRESSED, self.on_remove_from_library) widgetUtils.connect_event(self.view.remove, widgetUtils.BUTTON_PRESSED, self.on_remove_from_library)
pub.subscribe(self.set, self.modulename+"_set") pub.subscribe(self.set, self.modulename+"_set")
pub.subscribe(self.add_items, self.modulename+"_add_items") pub.subscribe(self.add_items, self.modulename+"_add_items")
pub.subscribe(self.change_label, "playback-changed")
def uninstall(self): def uninstall(self):
super(displayAudioInteractor, self).uninstall() super(displayAudioInteractor, self).uninstall()
pub.unsubscribe(self.set, self.modulename+"_set") pub.unsubscribe(self.set, self.modulename+"_set")
pub.unsubscribe(self.add_items, self.modulename+"_add_items") pub.unsubscribe(self.add_items, self.modulename+"_add_items")
pub.unsubscribe(self.change_label, "playback-changed")
def on_change(self, *args, **kwargs): def on_change(self, *args, **kwargs):
post = self.view.get_audio() post = self.view.get_audio()

File diff suppressed because it is too large Load Diff

View File

@@ -59,8 +59,6 @@ class configurationPresenter(base.basePresenter):
self.send_message("create_tab", tab="chat") self.send_message("create_tab", tab="chat")
self.send_message("set", tab="chat", setting="notify_online", value=self.session.settings["chat"]["notify_online"]) self.send_message("set", tab="chat", setting="notify_online", value=self.session.settings["chat"]["notify_online"])
self.send_message("set", tab="chat", setting="notify_offline", value=self.session.settings["chat"]["notify_offline"]) self.send_message("set", tab="chat", setting="notify_offline", value=self.session.settings["chat"]["notify_offline"])
self.send_message("set", tab="chat", setting="open_unread_conversations", value=self.session.settings["chat"]["open_unread_conversations"])
self.send_message("set", tab="chat", setting="automove_to_conversations", value=self.session.settings["chat"]["automove_to_conversations"])
self.send_message("set", tab="chat", setting="notifications", value=self.get_notification_label(self.session.settings["chat"]["notifications"])) self.send_message("set", tab="chat", setting="notifications", value=self.get_notification_label(self.session.settings["chat"]["notifications"]))
self.send_message("create_tab", tab="startup_options") self.send_message("create_tab", tab="startup_options")
self.send_message("set", tab="startup", setting="audio_albums", value=self.session.settings["load_at_startup"]["audio_albums"]) self.send_message("set", tab="startup", setting="audio_albums", value=self.session.settings["load_at_startup"]["audio_albums"])

View File

@@ -3,7 +3,7 @@ import logging
from sessionmanager import utils from sessionmanager import utils
from pubsub import pub from pubsub import pub
from mysc.thread_utils import call_threaded from mysc.thread_utils import call_threaded
from presenters import base from presenters import base, player
log = logging.getLogger(__file__) log = logging.getLogger(__file__)
@@ -82,6 +82,8 @@ class displayAudioPresenter(base.basePresenter):
def play(self, audio_index): def play(self, audio_index):
post = self.post[audio_index] post = self.post[audio_index]
if player.player.check_is_playing() == True:
return pub.sendMessage("stop")
pub.sendMessage("play", object=post) pub.sendMessage("play", object=post)
def load_audios(self): def load_audios(self):

View File

@@ -115,11 +115,6 @@ class displayTopicPresenter(basePost.displayPostPresenter):
attachments = "" attachments = ""
if hasattr(comment, "attachments"): if hasattr(comment, "attachments"):
attachments = self.upload_attachments(comment.attachments) attachments = self.upload_attachments(comment.attachments)
urls = utils.find_urls_in_text(msg)
if len(urls) != 0:
if len(attachments) == 0: attachments = urls[0]
else: attachments += urls[0]
msg = msg.replace(urls[0], "")
if msg != "": if msg != "":
kwargs.update(message=msg) kwargs.update(message=msg)
if attachments != "": if attachments != "":

View File

@@ -3,6 +3,7 @@
As this player does not have (still) an associated GUI, I have decided to place here the code for the interactor, which connects a bunch of pubsub events, and the presenter itself. As this player does not have (still) an associated GUI, I have decided to place here the code for the interactor, which connects a bunch of pubsub events, and the presenter itself.
""" """
import sys import sys
import time
import random import random
import logging import logging
import sound_lib import sound_lib
@@ -19,7 +20,6 @@ from sessionmanager import utils
player = None player = None
log = logging.getLogger("player") log = logging.getLogger("player")
# This function will be deprecated when the player works with pubsub events, as will no longer be needed to instantiate and import the player directly.
def setup(): def setup():
global player global player
if player == None: if player == None:
@@ -34,6 +34,7 @@ class audioPlayer(object):
self.is_playing = False self.is_playing = False
# This will be the URLStream handler # This will be the URLStream handler
self.stream = None self.stream = None
self.message = None
self.vol = config.app["sound"]["volume"] self.vol = config.app["sound"]["volume"]
# this variable is set to true when the URLPlayer is decoding something, thus it will block other calls to the play method. # this variable is set to true when the URLPlayer is decoding something, thus it will block other calls to the play method.
self.is_working = False self.is_working = False
@@ -41,13 +42,18 @@ class audioPlayer(object):
self.queue = [] self.queue = []
# Index of the currently playing track. # Index of the currently playing track.
self.playing_track = 0 self.playing_track = 0
self.playing_all = False
self.worker = RepeatingTimer(5, self.player_function)
self.worker.start()
# Status of the player. # Status of the player.
self.stopped = True self.stopped = True
# Modify some default settings present in Bass so it will increase timeout connection, thus causing less "connection timed out" errors when playing. # Modify some default settings present in Bass so it will increase timeout connection, thus causing less "connection timed out" errors when playing.
bassconfig = BassConfig() bassconfig = BassConfig()
# Set timeout connection to 30 seconds. # Set timeout connection to 30 seconds.
bassconfig["net_timeout"] = 30000 bassconfig["net_timeout"] = 30000
# subscribe all pubsub events.
pub.subscribe(self.play, "play") pub.subscribe(self.play, "play")
pub.subscribe(self.play_message, "play-message")
pub.subscribe(self.play_all, "play-all") pub.subscribe(self.play_all, "play-all")
pub.subscribe(self.pause, "pause") pub.subscribe(self.pause, "pause")
pub.subscribe(self.stop, "stop") pub.subscribe(self.stop, "stop")
@@ -55,6 +61,18 @@ class audioPlayer(object):
pub.subscribe(self.play_previous, "play-previous") pub.subscribe(self.play_previous, "play-previous")
pub.subscribe(self.seek, "seek") pub.subscribe(self.seek, "seek")
# Stopped has a special function here, hence the decorator
# when stopped will be set to True, it will send a pubsub event to inform other parts of the application about the status change.
# this is useful for changing labels between play and pause, and so on, in buttons.
@property
def stopped(self):
return self._stopped
@stopped.setter
def stopped(self, value):
self._stopped = value
pub.sendMessage("playback-changed", stopped=value)
def play(self, object, set_info=True, fresh=False): def play(self, object, set_info=True, fresh=False):
""" Play an URl Stream. """ Play an URl Stream.
@object dict: typically an audio object as returned by VK, with a "url" component which must be a valid URL to a media file. @object dict: typically an audio object as returned by VK, with a "url" component which must be a valid URL to a media file.
@@ -70,9 +88,7 @@ class audioPlayer(object):
log.exception("error when stopping the file") log.exception("error when stopping the file")
self.stream = None self.stream = None
self.stopped = True self.stopped = True
if fresh == True and hasattr(self, "worker") and self.worker != None: if fresh == True:
self.worker.cancel()
self.worker = None
self.queue = [] self.queue = []
# Make sure that there are no other sounds trying to be played. # Make sure that there are no other sounds trying to be played.
if self.is_working == False: if self.is_working == False:
@@ -94,16 +110,41 @@ class audioPlayer(object):
self.stopped = False self.stopped = False
self.is_working = False self.is_working = False
def play_message(self, message_url):
if self.message != None and (self.message.is_playing == True or self.message.is_stalled == True):
return self.stop_message()
output.speak(_("Playing..."))
url_ = utils.transform_audio_url(message_url)
url_ = bytes(url_, "utf-8")
try:
self.message = URLStream(url=url_)
except:
log.error("Unable to play URL %s" % (url_))
return
self.message.volume = self.vol/100.0
self.message.play()
volume_percent = self.volume*0.25
volume_step = self.volume*0.15
while self.stream.volume*100 > volume_percent:
self.stream.volume = self.stream.volume-(volume_step/100)
time.sleep(0.1)
def stop(self): def stop(self):
""" Stop audio playback. """ """ Stop audio playback. """
if self.stream != None and self.stream.is_playing == True: if self.stream != None and self.stream.is_playing == True:
self.stream.stop() self.stream.stop()
self.stopped = True self.stopped = True
if hasattr(self, "worker") and self.worker != None:
self.worker.cancel()
self.worker = None
self.queue = [] self.queue = []
def stop_message(self):
if hasattr(self, "message") and self.message != None and self.message.is_playing == True:
self.message.stop()
volume_step = self.volume*0.15
while self.stream.volume*100 < self.volume:
self.stream.volume = self.stream.volume+(volume_step/100)
time.sleep(0.1)
self.message = None
def pause(self): def pause(self):
""" pause the current playback, without destroying the queue or the current stream. If the stream is already paused this function will resume the playback. """ """ pause the current playback, without destroying the queue or the current stream. If the stream is already paused this function will resume the playback. """
if self.stream != None: if self.stream != None:
@@ -116,6 +157,8 @@ class audioPlayer(object):
self.stopped = False self.stopped = False
except BassError: except BassError:
pass pass
if self.playing_all == False and len(self.queue) > 0:
self.playing_all = True
@property @property
def volume(self): def volume(self):
@@ -130,6 +173,10 @@ class audioPlayer(object):
elif vol > 100: elif vol > 100:
self.vol = 100 self.vol = 100
if self.stream != None: if self.stream != None:
if self.message != None and self.message.is_playing:
self.stream.volume = (self.vol*0.25)/100.0
self.message.volume = self.vol/100.0
else:
self.stream.volume = self.vol/100.0 self.stream.volume = self.vol/100.0
def play_all(self, list_of_songs, shuffle=False): def play_all(self, list_of_songs, shuffle=False):
@@ -145,16 +192,24 @@ class audioPlayer(object):
if shuffle: if shuffle:
random.shuffle(self.queue) random.shuffle(self.queue)
call_threaded(self.play, self.queue[self.playing_track]) call_threaded(self.play, self.queue[self.playing_track])
self.worker = RepeatingTimer(5, self.player_function) self.playing_all = True
self.worker.start()
def player_function(self): def player_function(self):
""" Check if the stream has reached the end of the file so it will play the next song. """ """ Check if the stream has reached the end of the file so it will play the next song. """
if self.message != None and self.message.is_playing == False and len(self.message) == self.message.position:
volume_step = self.volume*0.15
while self.stream.volume*100 < self.volume:
self.stream.volume = self.stream.volume+(volume_step/100)
time.sleep(0.1)
if self.stream != None and self.stream.is_playing == False and self.stopped == False and len(self.stream) == self.stream.position: if self.stream != None and self.stream.is_playing == False and self.stopped == False and len(self.stream) == self.stream.position:
if len(self.queue) == 0 or self.playing_track >= len(self.queue): if self.playing_track >= len(self.queue):
self.worker.cancel() self.stopped = True
self.playing_all = False
return return
if self.playing_track < len(self.queue): elif self.playing_all == False:
self.stopped = True
return
elif self.playing_track < len(self.queue):
self.playing_track += 1 self.playing_track += 1
self.play(self.queue[self.playing_track]) self.play(self.queue[self.playing_track])

View File

@@ -20,8 +20,6 @@ count_for_audio_buffers = integer(default=1000)
[chat] [chat]
notify_online = boolean(default=True) notify_online = boolean(default=True)
notify_offline = boolean(default=True) notify_offline = boolean(default=True)
open_unread_conversations = boolean(default=True)
automove_to_conversations = boolean(default=False)
notifications = string(default="custom") notifications = string(default="custom")
[load_at_startup] [load_at_startup]

View File

@@ -75,6 +75,14 @@ def add_attachment(attachment):
elif attachment["type"] == "poll": elif attachment["type"] == "poll":
tpe = _("Poll") tpe = _("Poll")
msg = attachment["poll"]["question"] msg = attachment["poll"]["question"]
elif attachment["type"] == "wall":
tpe = _("Post")
user = attachment["wall"]["from"]["name"]
if len(attachment["wall"]["text"]) > 140:
text = attachment["wall"]["text"][:145]+"..."
else:
text = attachment["wall"]["text"]
msg = _("{user}: {post}").format(user=user, post=text)
else: else:
print(attachment) print(attachment)
return [tpe, msg] return [tpe, msg]

View File

@@ -40,7 +40,8 @@ def find_item(list, item):
break break
if identifier == None: if identifier == None:
# if there are objects that can't be processed by lack of identifier, let's print keys for finding one. # if there are objects that can't be processed by lack of identifier, let's print keys for finding one.
log.exception("Can't find an identifier for the following object: %r" % (list(item.keys()),)) log.exception("Can't find an identifier for the following object: %r" % (item.keys(),))
return False
for i in list: for i in list:
if identifier in i and i[identifier] == item[identifier]: if identifier in i and i[identifier] == item[identifier]:
return True return True
@@ -53,7 +54,7 @@ class vkSession(object):
""" Put new items on the local cache database. """ Put new items on the local cache database.
@name str: The name for the buffer stored in the dictionary. @name str: The name for the buffer stored in the dictionary.
@data list: A list with items and some information about cursors. @data list: A list with items and some information about cursors.
returns the number of items that has been added in this execution""" returns the number of items that have been added in this execution"""
global post_types global post_types
# When this method is called by friends.getOnlyne, it gives only friend IDS so we need to retrieve full objects from VK. # When this method is called by friends.getOnlyne, it gives only friend IDS so we need to retrieve full objects from VK.
# ToDo: It would be nice to investigate whether reusing some existing objects would be a good idea, whenever possible. # ToDo: It would be nice to investigate whether reusing some existing objects would be a good idea, whenever possible.
@@ -101,6 +102,7 @@ class vkSession(object):
self.db = {} self.db = {}
self.db["users"] = {} self.db["users"] = {}
self.db["groups"] = {} self.db["groups"] = {}
self.db["group_info"] = {}
@property @property
def is_logged(self): def is_logged(self):
@@ -115,9 +117,13 @@ class vkSession(object):
log.debug("Creating config file %s" % (file_,)) log.debug("Creating config file %s" % (file_,))
self.settings = Configuration(os.path.join(paths.config_path(), file_), os.path.join(paths.app_path(), "session.defaults")) self.settings = Configuration(os.path.join(paths.config_path(), file_), os.path.join(paths.app_path(), "session.defaults"))
self.soundplayer = sound.soundSystem(config.app["sound"]) self.soundplayer = sound.soundSystem(config.app["sound"])
pub.subscribe(self.play_sound, "play-sound")
# except: # except:
# log.exception("The session configuration has failed.") # log.exception("The session configuration has failed.")
def play_sound(self, sound):
self.soundplayer.play(sound)
def login(self): def login(self):
""" Logging in VK.com. This is basically the first method interacting with VK. """ """ Logging in VK.com. This is basically the first method interacting with VK. """
# If user is already logged in, we should skip this method. # If user is already logged in, we should skip this method.
@@ -258,6 +264,10 @@ class vkSession(object):
k = "{key}_{case}".format(key=key, case=i) k = "{key}_{case}".format(key=key, case=i)
v = self.db["groups"][abs(user_id)][i] v = self.db["groups"][abs(user_id)][i]
user_data[k] = v user_data[k] = v
else:
group = self.vk.client.groups.getById(group_ids=-1*user_id)[0]
self.process_usernames(data=dict(profiles=[], groups=[group]))
return self.get_user(user_id=user_id, key=key)
return user_data return user_data
def process_usernames(self, data): def process_usernames(self, data):

View File

@@ -78,19 +78,16 @@ def transform_audio_url(url):
""" Transforms the URL offered by VK to the unencrypted stream so we can still play it. """ Transforms the URL offered by VK to the unencrypted stream so we can still play it.
This function will be updated every time VK decides to change something in their Audio API'S. This function will be updated every time VK decides to change something in their Audio API'S.
Changelog: Changelog:
16/04/2019: Implemented this function. For now it replaces /index.m3u8 by .mp3, also removes the path component before "/audios" if the URL contains the word /audios, or the last path component before the filename if doesn't. 30/04/2019: Re-enabled old methods as VK changed everything as how it was working on 16.04.2019.
17/04/2019: Updated function. Now it is not required to strip anything, just replacing /index.m3u8 with .mp3 should be enough. 17.04.2019: Updated function. Now it is not required to strip anything, just replacing /index.m3u8 with .mp3 should be enough.
16.04.2019: Implemented this function. For now it replaces /index.m3u8 by .mp3, also removes the path component before "/audios" if the URL contains the word /audios, or the last path component before the filename if doesn't.
""" """
if "vkuseraudio.net" not in url and "index.m3u8" not in url: if "vkuseraudio.net" not in url and "index.m3u8" not in url:
return url return url
url = url.replace("/index.m3u8", ".mp3") url = url.replace("/index.m3u8", ".mp3")
parts = url.split("/")
if "/audios" not in url:
url = url.replace("/"+parts[-2], "")
else:
url = url.replace("/"+parts[-3], "")
return url return url
### The following code was useful for VK audio methods prior to 17/04/2019.
# I just left this here because they may enable such change any time soon.
### basically this method was requiring us to strip a part of the full URL.
# parts = url.split("/")
# if "/audio" not in url:
# url = url.replace("/"+parts[-2], "")
# else:
# url = url.replace("/"+parts[-3], "")
# return url

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -46,10 +46,6 @@ class chat(wx.Panel, widgetUtils.BaseDialog):
sizer.Add(self.notify_online, 0, wx.ALL, 5) sizer.Add(self.notify_online, 0, wx.ALL, 5)
self.notify_offline = wx.CheckBox(self, wx.NewId(), _("Show notifications when users are offline")) self.notify_offline = wx.CheckBox(self, wx.NewId(), _("Show notifications when users are offline"))
sizer.Add(self.notify_offline, 0, wx.ALL, 5) sizer.Add(self.notify_offline, 0, wx.ALL, 5)
self.open_unread_conversations = wx.CheckBox(self, wx.NewId(), _("Open unread conversations at startup"))
sizer.Add(self.open_unread_conversations, 0, wx.ALL, 5)
self.automove_to_conversations = wx.CheckBox(self, wx.NewId(), _("Move focus to new conversations"))
sizer.Add(self.automove_to_conversations, 0, wx.ALL, 5)
lbl = wx.StaticText(self, wx.NewId(), _("Notification type")) lbl = wx.StaticText(self, wx.NewId(), _("Notification type"))
self.notifications = wx.ComboBox(self, wx.NewId(), choices=[_("Native"), _("Custom"),], value=_("Native"), style=wx.CB_READONLY) self.notifications = wx.ComboBox(self, wx.NewId(), choices=[_("Native"), _("Custom"),], value=_("Native"), style=wx.CB_READONLY)
nbox = wx.BoxSizer(wx.HORIZONTAL) nbox = wx.BoxSizer(wx.HORIZONTAL)

View File

@@ -121,3 +121,51 @@ class createCommentDialog(createTextMessage):
super(createCommentDialog, self).__init__() super(createCommentDialog, self).__init__()
self.createControls(message, title, text) self.createControls(message, title, text)
self.SetClientSize(self.mainBox.CalcMin()) self.SetClientSize(self.mainBox.CalcMin())
self.SetTitle(title)
class createTopicDialog(createCommentDialog):
def createTextArea(self, message="", text=""):
self.panel = wx.Panel(self)
label = wx.StaticText(self.panel, -1, _("Title"))
self.title = wx.TextCtrl(self.panel, wx.NewId())
label2 = wx.StaticText(self.panel, -1, _("Message"))
self.text = wx.TextCtrl(self.panel, -1, text, size=(439, -1), style=wx.TE_MULTILINE)
self.title.SetFocus()
self.textBox = wx.BoxSizer(wx.VERTICAL)
titleb = wx.BoxSizer(wx.HORIZONTAL)
titleb.Add(label, 0, wx.ALL, 5)
titleb.Add(self.title, 0, wx.ALL, 5)
self.textBox.Add(titleb, 0, wx.ALL, 5)
textb = wx.BoxSizer(wx.HORIZONTAL)
textb.Add(label2, 0, wx.ALL, 5)
textb.Add(self.text, 0, wx.ALL, 5)
self.textBox.Add(textb, 0, wx.ALL, 5)
def createControls(self, title, message, text):
self.mainBox = wx.BoxSizer(wx.VERTICAL)
self.createTextArea(message, text)
self.mainBox.Add(self.textBox, 0, wx.ALL, 5)
self.attach = wx.Button(self.panel, -1, _("Attach"), size=wx.DefaultSize)
self.mention = wx.Button(self.panel, wx.NewId(), _("Tag a friend"))
self.spellcheck = wx.Button(self.panel, -1, _("Spelling &correction"), size=wx.DefaultSize)
self.translateButton = wx.Button(self.panel, -1, _("&Translate message"), size=wx.DefaultSize)
self.okButton = wx.Button(self.panel, wx.ID_OK, _("Send"), size=wx.DefaultSize)
self.okButton.SetDefault()
cancelButton = wx.Button(self.panel, wx.ID_CANCEL, _("Close"), size=wx.DefaultSize)
self.buttonsBox1 = wx.BoxSizer(wx.HORIZONTAL)
self.buttonsBox1.Add(self.attach, 0, wx.ALL, 10)
self.buttonsBox1.Add(self.mention, 0, wx.ALL, 10)
self.buttonsBox1.Add(self.spellcheck, 0, wx.ALL, 10)
self.buttonsBox1.Add(self.translateButton, 0, wx.ALL, 10)
self.mainBox.Add(self.buttonsBox1, 0, wx.ALL, 10)
self.ok_cancelSizer = wx.BoxSizer(wx.HORIZONTAL)
self.ok_cancelSizer.Add(self.okButton, 0, wx.ALL, 10)
self.ok_cancelSizer.Add(cancelButton, 0, wx.ALL, 10)
self.mainBox.Add(self.ok_cancelSizer)
selectId = wx.NewId()
self.Bind(wx.EVT_MENU, self.onSelect, id=selectId)
self.accel_tbl = wx.AcceleratorTable([
(wx.ACCEL_CTRL, ord('A'), selectId),])
self.SetAcceleratorTable(self.accel_tbl)
self.panel.SetSizer(self.mainBox)

View File

@@ -4,7 +4,9 @@ from __future__ import unicode_literals
import languageHandler import languageHandler
import paths import paths
import wx import wx
import wx.lib.mixins.listctrl as listmix
from builtins import range from builtins import range
from pubsub import pub
toolkit = "wx" toolkit = "wx"
@@ -134,6 +136,30 @@ class mainLoopObject(wx.App):
def run(self): def run(self):
self.app.MainLoop() self.app.MainLoop()
class multiselectionBaseList(wx.ListCtrl, listmix.CheckListCtrlMixin):
def __init__(self, *args, **kwargs):
wx.ListCtrl.__init__(self, *args, **kwargs)
listmix.CheckListCtrlMixin.__init__(self)
self.Bind(wx.EVT_CHAR_HOOK, self.on_keydown)
self.Bind(wx.EVT_LIST_ITEM_FOCUSED, self.on_focus)
def on_focus(self, event):
currentItem = self.GetFocusedItem()
if self.IsChecked(currentItem):
pub.sendMessage("play-sound", sound="selected.ogg")
event.Skip()
def OnCheckItem(self, index, flag):
if flag == True:
pub.sendMessage("play-sound", sound="checked.ogg")
else:
pub.sendMessage("play-sound", sound="unchecked.ogg")
def on_keydown(self, event):
if event.GetKeyCode() == wx.WXK_SPACE:
self.ToggleItem(self.GetFocusedItem())
event.Skip()
class list(object): class list(object):
def __init__(self, parent, *columns, **listArguments): def __init__(self, parent, *columns, **listArguments):
self.columns = columns self.columns = columns
@@ -182,3 +208,19 @@ class list(object):
def Enable(self, value): def Enable(self, value):
return self.list.Enable(value) return self.list.Enable(value)
class multiselectionList(list):
def create_list(self, parent):
self.list = multiselectionBaseList(parent, -1, **self.listArguments)
for i in range(0, len(self.columns)):
self.list.InsertColumn(i, "%s" % (self.columns[i]))
def get_multiple_selection(self):
selected = []
for item in range(0, self.list.GetItemCount()):
if self.list.IsChecked(item):
selected.append(item)
if len(selected) == 0 and self.list.GetFocusedItem() != -1:
selected.append(self.list.GetFocusedItem())
return selected

View File

@@ -5,16 +5,18 @@ import widgetUtils
class timelineDialog(widgetUtils.BaseDialog): class timelineDialog(widgetUtils.BaseDialog):
def __init__(self, users=[]): def __init__(self, users=[], show_selector=True):
super(timelineDialog, self).__init__(parent=None, title=_("New timeline for {0}").format(users[0],)) super(timelineDialog, self).__init__(parent=None, title=_("New timeline for {0}").format(users[0],))
panel = wx.Panel(self) panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.HORIZONTAL) sizer = wx.BoxSizer(wx.HORIZONTAL)
if show_selector:
userLabel = wx.StaticText(panel, -1, _("User")) userLabel = wx.StaticText(panel, -1, _("User"))
self.cb = wx.ComboBox(panel, -1, choices=users, value=users[0]) self.cb = wx.ComboBox(panel, -1, choices=users, value=users[0])
self.cb.SetFocus() self.cb.SetFocus()
userSizer = wx.BoxSizer() userSizer = wx.BoxSizer()
userSizer.Add(userLabel, 0, wx.ALL, 5) userSizer.Add(userLabel, 0, wx.ALL, 5)
userSizer.Add(self.cb, 0, wx.ALL, 5) userSizer.Add(self.cb, 0, wx.ALL, 5)
sizer.Add(userSizer, 0, wx.ALL, 5)
actionsSizer = wx.StaticBoxSizer(parent=panel, orient=wx.VERTICAL, label=_("Buffer type")) actionsSizer = wx.StaticBoxSizer(parent=panel, orient=wx.VERTICAL, label=_("Buffer type"))
self.wall = wx.RadioButton(actionsSizer.GetStaticBox(), wx.NewId(), _("&Wall posts"), style=wx.RB_GROUP) self.wall = wx.RadioButton(actionsSizer.GetStaticBox(), wx.NewId(), _("&Wall posts"), style=wx.RB_GROUP)
self.audio = wx.RadioButton(actionsSizer.GetStaticBox(), wx.NewId(), _("Audio")) self.audio = wx.RadioButton(actionsSizer.GetStaticBox(), wx.NewId(), _("Audio"))

View File

@@ -133,10 +133,14 @@ class mainWindow(wx.Frame):
def advance_selection(self, forward): def advance_selection(self, forward):
self.tb.AdvanceSelection(forward) self.tb.AdvanceSelection(forward)
def about_dialog(self, *args, **kwargs): def about_dialog(self, channel="stable", *args, **kwargs):
if channel == "stable":
version = _("{version} (stable)").format(version=application.version)
else:
version = _("{version} (alpha)").format(version=application.update_next_version)
info = wx.adv.AboutDialogInfo() info = wx.adv.AboutDialogInfo()
info.SetName(application.name) info.SetName(application.name)
info.SetVersion(application.version) info.SetVersion(version)
info.SetDescription(application.description) info.SetDescription(application.description)
info.SetCopyright(application.copyright) info.SetCopyright(application.copyright)
info.SetTranslators(application.translators) info.SetTranslators(application.translators)

View File

@@ -57,14 +57,14 @@ class communityTab(feedTab):
def create_post_buttons(self): def create_post_buttons(self):
self.postBox = wx.StaticBoxSizer(parent=self, orient=wx.HORIZONTAL, label=_("Actions")) self.postBox = wx.StaticBoxSizer(parent=self, orient=wx.HORIZONTAL, label=_("Actions"))
self.load = wx.Button(self.postBox.GetStaticBox(), wx.NewId(), _("Load buffer")) self.load = wx.Button(self.postBox.GetStaticBox(), wx.NewId(), _("Load buffer"))
self.post = wx.Button(self.postBox.GetStaticBox(), -1, _("&Post")) self.post = wx.Button(self.postBox.GetStaticBox(), -1, _("&Post in group"))
self.postBox.Add(self.load, 0, wx.ALL, 5) self.postBox.Add(self.load, 0, wx.ALL, 5)
self.postBox.Add(self.post, 0, wx.ALL, 5) self.postBox.Add(self.post, 0, wx.ALL, 5)
class audioTab(homeTab): class audioTab(homeTab):
def create_list(self): def create_list(self):
self.lbl = wx.StaticText(self, wx.NewId(), _("Mu&sic")) self.lbl = wx.StaticText(self, wx.NewId(), _("Mu&sic"))
self.list = widgetUtils.list(self, *[_("Title"), _("Artist"), _("Duration")], style=wx.LC_REPORT) self.list = widgetUtils.multiselectionList(self, *[_("Title"), _("Artist"), _("Duration")], style=wx.LC_REPORT)
self.list.set_windows_size(0, 160) self.list.set_windows_size(0, 160)
self.list.set_windows_size(1, 380) self.list.set_windows_size(1, 380)
self.list.set_windows_size(2, 80) self.list.set_windows_size(2, 80)

View File

@@ -1,4 +1,4 @@
{"current_version": "0.19", {"current_version": "0.20",
"description": ".", "description": ".",
"downloads": "downloads":
{"Windows32": "https://code.manuelcortez.net/manuelcortez/socializer/-/jobs/artifacts/v0.19/raw/socializer.zip?job=stable"}} {"Windows32": "https://code.manuelcortez.net/manuelcortez/socializer/-/jobs/artifacts/v0.20/raw/socializer.zip?job=stable"}}