diff --git a/src/controller/buffers.py b/src/controller/buffers.py index a7b1fcb..ff07339 100644 --- a/src/controller/buffers.py +++ b/src/controller/buffers.py @@ -6,6 +6,9 @@ import logging import webbrowser import arrow import wx +import presenters +import views +import interactors import languageHandler import widgetUtils from . import messages @@ -13,7 +16,6 @@ from presenters import player import output from . import selector from . import posts -from . import attach from pubsub import pub from vk_api.exceptions import VkApiError from vk_api import upload @@ -840,7 +842,7 @@ class chatBuffer(baseBuffer): return retrieved def add_attachment(self, *args, **kwargs): - a = attach.attach(self.session, True) + a = presenters.attachPresenter(session=self.session, view=views.attachDialog(voice_messages=True), interactor=interactors.attachInteractor()) if len(a.attachments) != 0: self.attachments_to_be_sent = a.attachments diff --git a/src/controller/messages.py b/src/controller/messages.py index 8384f76..26def37 100644 --- a/src/controller/messages.py +++ b/src/controller/messages.py @@ -2,9 +2,11 @@ from __future__ import unicode_literals import time import widgetUtils +import presenters +import views +import interactors import output from pubsub import pub -from . import attach from wxUI.dialogs import message, selector from extra import SpellChecker, translator from logging import getLogger @@ -71,7 +73,7 @@ class post(object): checker.clean() def show_attach_dialog(self, *args, **kwargs): - a = attach.attach(self.session) + a = presenters.attachPresenter(session=self.session, view=views.attachDialog(), interactor=interactors.attachInteractor()) if len(a.attachments) != 0: self.attachments = a.attachments diff --git a/src/interactors/__init__.py b/src/interactors/__init__.py index 40a8280..789ef95 100644 --- a/src/interactors/__init__.py +++ b/src/interactors/__init__.py @@ -1,3 +1,4 @@ +from .attach import * from . audioRecorder import * from .configuration import * from .profiles import * \ No newline at end of file diff --git a/src/interactors/attach.py b/src/interactors/attach.py new file mode 100644 index 0000000..7bbbaf8 --- /dev/null +++ b/src/interactors/attach.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +import widgetUtils +from pubsub import pub +from wxUI.dialogs import selector +from wxUI.menus import attachMenu +from . import base + +class attachInteractor(base.baseInteractor): + + def insert_attachment(self, attachment): + self.view.attachments.insert_item(False, *attachment) + + def remove_attachment(self, attachment): + self.view.attachments.remove_item(attachment) + + def install(self, *args, **kwargs): + super(attachInteractor, self).install(*args, **kwargs) + widgetUtils.connect_event(self.view.photo, widgetUtils.BUTTON_PRESSED, self.on_image) + widgetUtils.connect_event(self.view.audio, widgetUtils.BUTTON_PRESSED, self.on_audio) + if hasattr(self.view, "voice_message"): + widgetUtils.connect_event(self.view.voice_message, widgetUtils.BUTTON_PRESSED, self.on_upload_voice_message) + widgetUtils.connect_event(self.view.remove, widgetUtils.BUTTON_PRESSED, self.on_remove_attachment) + pub.subscribe(self.insert_attachment, self.modulename+"_insert_attachment") + pub.subscribe(self.remove_attachment, self.modulename+"_remove_attachment") + + def uninstall(self): + super(attachInteractor, self).uninstall() + pub.unsubscribe(self.insert_attachment, self.modulename+"_insert_attachment") + pub.unsubscribe(self.remove_attachment, self.modulename+"_remove_attachment") + + def on_image(self, *args, **kwargs): + """ display menu for adding image attachments. """ + m = attachMenu() + # disable add from VK as it is not supported in images, yet. + m.add.Enable(False) + widgetUtils.connect_event(m, widgetUtils.MENU, self.on_upload_image, menuitem=m.upload) + self.view.PopupMenu(m, self.view.photo.GetPosition()) + + def on_audio(self, *args, **kwargs): + """ display menu to add audio attachments.""" + m = attachMenu() + widgetUtils.connect_event(m, widgetUtils.MENU, self.on_upload_audio, menuitem=m.upload) + widgetUtils.connect_event(m, widgetUtils.MENU, self.on_add_audio, menuitem=m.add) + self.view.PopupMenu(m, self.view.audio.GetPosition()) + + def on_upload_image(self, *args, **kwargs): + """ allows uploading an image from the computer. + """ + image, description = self.view.get_image() + if image != None: + self.presenter.upload_image(image, description) + + def on_upload_audio(self, *args, **kwargs): + """ Allows uploading an audio file from the computer. Only mp3 files are supported. """ + audio = self.view.get_audio() + if audio != None: + self.presenter.upload_audio(audio) + + def on_upload_voice_message(self, *args, **kwargs): + self.presenter.upload_voice_message() + + def on_add_audio(self, *args, **kwargs): + """ Allow adding an audio directly from the user's audio library.""" + audios = self.presenter.get_available_audios() + select = selector.selectAttachment(_("Select the audio files you want to send"), audios) + if select.get_response() == widgetUtils.OK and select.attachments.GetCount() > 0: + attachments = select.get_all_attachments() + self.presenter.take_audios(attachments) + + def on_remove_attachment(self, *args, **kwargs): + """ Remove the currently focused item from the attachments list.""" + current_item = self.view.attachments.get_selected() + self.presenter.remove_attachment(current_item) \ No newline at end of file diff --git a/src/presenters/__init__.py b/src/presenters/__init__.py index 4985bc8..88bb123 100644 --- a/src/presenters/__init__.py +++ b/src/presenters/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from .attach import * from .audioRecorder import * from .configuration import * from .profiles import * \ No newline at end of file diff --git a/src/presenters/attach.py b/src/presenters/attach.py new file mode 100644 index 0000000..1ea2e93 --- /dev/null +++ b/src/presenters/attach.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +""" Attachment controller for different kind of posts in VK. +this controller will take care of preparing data structures to be uploaded later, when the user decides to start the upload process by sending the post. +""" +from __future__ import unicode_literals +import os +import logging +import interactors +import views +from mutagen.id3 import ID3 +from sessionmanager.utils import seconds_to_string +from . import audioRecorder, base + +log = logging.getLogger(__file__) + +class attachPresenter(base.basePresenter): + """ Controller used in some sections of the application, it can do the following: + + * Handle all user input related to adding local or online files (online files are those already uploaded in vk). + * Prepare local files to be uploaded once a post will be sent (no uploading work is done here, but structured dicts will be generated). + * Parse online files and allow addition of them as attachments, so this controller will add both local and online files in the same dialog. +""" + + def __init__(self, session, view, interactor, voice_messages=False): + """ Constructor. + @ session sessionmanager.session object: an object capable of calling all VK methods and accessing the session database. + @voice_messages bool: If True, will add a button for sending voice messages. Functionality for this button has not been added yet. + """ + super(attachPresenter, self).__init__(view=view, interactor=interactor, modulename="attach") + self.session = session + # Self.attachments will hold a reference to all attachments added to the dialog. + self.attachments = list() + self.run() + + def upload_image(self, image, description): + """ allows uploading an image from the computer. + """ + imageInfo = {"type": "photo", "file": image, "description": description, "from": "local"} + self.attachments.append(imageInfo) + # Translators: This is the text displayed in the attachments dialog, when the user adds a photo. + info = [_("Photo"), description] + self.send_message("insert_attachment", attachment=info) + self.send_message("enable_control", control="remove") + + def upload_audio(self, audio): + """ Allows uploading an audio file from the computer. Only mp3 files are supported. """ + if audio != None: + # Define data structure for this attachment, as will be required by VK API later. + # Let's extract the ID3 tags to show them in the list and send them to VK, too. + audio_tags = ID3(audio) + if "TIT2" in audio_tags: + title = audio_tags["TIT2"].text[0] + else: + title = _("Untitled") + if "TPE1" in audio_tags: + artist = audio_tags["TPE1"].text[0] + else: + artist = _("Unknown artist") + audioInfo = {"type": "audio", "file": audio, "from": "local", "title": title, "artist": artist} + self.attachments.append(audioInfo) + # Translators: This is the text displayed in the attachments dialog, when the user adds an audio file. + info = [_("Audio file"), "{title} - {artist}".format(title=title, artist=artist)] + self.send_message("insert_attachment", attachment=info) + self.send_message("enable_control", control="remove") + + def upload_voice_message(self): + a = audioRecorder.audioRecorderPresenter(view=views.audioRecorderDialog(), interactor=interactors.audioRecorderInteractor()) + if a.file != None and a.duration != 0: + audioInfo = {"type": "voice_message", "file": a.file, "from": "local"} + self.attachments.append(audioInfo) + # Translators: This is the text displayed in the attachments dialog, when the user adds an audio file. + info = [_("Voice message"), seconds_to_string(a.duration,)] + self.send_message("insert_attachment", attachment=info) + self.send_message("enable_control", control="remove") + + ####### ToDo: replace this with selector presenter when finished. + def get_available_audios(self): + # Let's reuse the already downloaded audios. + list_of_audios = self.session.db["me_audio"]["items"] + audios = [] + for i in list_of_audios: + audios.append("{0}, {1}".format(i["title"], i["artist"])) + return audios + + def take_audios(self, audios_list): + list_of_audios = self.session.db["me_audio"]["items"] + for i in audios_list: + info = dict(type="audio", id=list_of_audios[i]["id"], owner_id=list_of_audios[i]["owner_id"]) + info["from"] = "online" + self.attachments.append(info) + # Translators: This is the text displayed in the attachments dialog, when the user adds an audio file. + info2 = [_("Audio file"), "{0} - {1}".format(list_of_audios[i]["title"], list_of_audios[i]["artist"])] + self.send_message("insert_attachment", attachment=info2) + self.check_remove_status() + + def remove_attachment(self, item_index): + """ Remove the currently focused item from the attachments list.""" + log.debug("Removing item %d" % (item_index,)) + if item_index == -1: item_index = 0 + self.attachments.pop(item_index) + self.send_message("remove_attachment", attachment=item_index) + self.check_remove_status() + log.debug("Removed") + + def check_remove_status(self): + """ Checks whether the remove button should remain enabled.""" + if len(self.attachments) == 0: + self.send_message("disable_control", control="remove") diff --git a/src/presenters/audioRecorder.py b/src/presenters/audioRecorder.py index 7de9d1d..04673c9 100644 --- a/src/presenters/audioRecorder.py +++ b/src/presenters/audioRecorder.py @@ -16,6 +16,7 @@ class audioRecorderPresenter(base.basePresenter): self.recording = None self.duration = 0 self.playing = None + self.file = None self.run() def toggle_recording(self, *args, **kwargs): diff --git a/src/views/__init__.py b/src/views/__init__.py index b1da70e..cfdf13c 100644 --- a/src/views/__init__.py +++ b/src/views/__init__.py @@ -1,3 +1,9 @@ +""" Views package for socializer. A view is the Graphical user interface in the application. This package includes everything needed to render a window. + No other functionality is assumed in this package. + All modules from here should be used via an interactor (read description of the interactors package). + Interactors will be routing user events (like buttons pressed, menus activated and so on) to presenter functions. +""" +from .dialogs.attach import * from .dialogs.audioRecorder import * from .dialogs.configuration import * from .dialogs.profiles import * \ No newline at end of file diff --git a/src/views/dialogs/attach.py b/src/views/dialogs/attach.py new file mode 100644 index 0000000..3344ec9 --- /dev/null +++ b/src/views/dialogs/attach.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +import wx +import widgetUtils + +class attachDialog(widgetUtils.BaseDialog): + def __init__(self, voice_messages=False): + super(attachDialog, self).__init__(None, title=_("Add an attachment")) + panel = wx.Panel(self) + sizer = wx.BoxSizer(wx.VERTICAL) + lbl1 = wx.StaticText(panel, wx.NewId(), _("Attachments")) + self.attachments = widgetUtils.list(panel, _("Type"), _("Title"), style=wx.LC_REPORT) + box = wx.BoxSizer(wx.HORIZONTAL) + box.Add(lbl1, 0, wx.ALL, 5) + box.Add(self.attachments.list, 0, wx.ALL, 5) + sizer.Add(box, 0, wx.ALL, 5) + static = wx.StaticBox(panel, label=_("Add attachments")) + self.photo = wx.Button(panel, wx.NewId(), _("&Photo")) + self.audio = wx.Button(panel, wx.NewId(), _("Audio file")) + if voice_messages: + self.voice_message = wx.Button(panel, wx.NewId(), _("Voice message")) + self.remove = wx.Button(panel, wx.NewId(), _("Remove attachment")) + self.remove.Enable(False) + btnsizer = wx.StaticBoxSizer(static, wx.HORIZONTAL) + btnsizer.Add(self.photo, 0, wx.ALL, 5) + btnsizer.Add(self.audio, 0, wx.ALL, 5) + if voice_messages: + btnsizer.Add(self.voice_message, 0, wx.ALL, 5) + sizer.Add(btnsizer, 0, wx.ALL, 5) + ok = wx.Button(panel, wx.ID_OK) + ok.SetDefault() + cancelBtn = wx.Button(panel, wx.ID_CANCEL) + btnSizer = wx.BoxSizer() + btnSizer.Add(ok, 0, wx.ALL, 5) + btnSizer.Add(cancelBtn, 0, wx.ALL, 5) + sizer.Add(btnSizer, 0, wx.ALL, 5) + panel.SetSizer(sizer) + self.SetClientSize(sizer.CalcMin()) + + def get_image(self): + openFileDialog = wx.FileDialog(self, _("Select the picture to be uploaded"), "", "", _("Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + if openFileDialog.ShowModal() == wx.ID_CANCEL: + return None + dsc = self.ask_description() + return (openFileDialog.GetPath(), dsc) + + def ask_description(self): + dlg = wx.TextEntryDialog(self, _("please provide a description"), _("Description")) + dlg.ShowModal() + result = dlg.GetValue() + dlg.Destroy() + return result + + def get_audio(self): + openFileDialog = wx.FileDialog(self, _("Select the audio file to be uploaded"), "", "", _("Audio files (*.mp3)|*.mp3"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) + if openFileDialog.ShowModal() == wx.ID_CANCEL: + return None + return openFileDialog.GetPath()