commit/readFeeds: norrumar: Added dialogs to copy and restore feeds based on the portable creater dialog of NVDA, and reviewed placeMarkers add-on. Used skipTranslation to get translated messages of NVDA core, and restructured folders to avoid issues with modules external to the plugin.

  • From: commits-noreply@xxxxxxxxxxxxx
  • To: nvda-addons-commits@xxxxxxxxxxxxx
  • Date: Mon, 05 Dec 2016 11:30:44 -0000

1 new commit in readFeeds:

https://bitbucket.org/nvdaaddonteam/readfeeds/commits/7fd97679f551/
Changeset:   7fd97679f551
Branch:      enhancedgui
User:        norrumar
Date:        2016-12-05 11:28:07+00:00
Summary:     Added dialogs to copy and restore feeds based on the portable 
creater dialog of NVDA, and reviewed placeMarkers add-on. Used skipTranslation 
to get translated messages of NVDA core, and restructured folders to avoid 
issues with modules external to the plugin.

Affected #:  69 files

diff --git a/addon/globalPlugins/personalFeeds/Blog SpazioAusili.txt 
b/addon/globalPlugins/personalFeeds/Blog SpazioAusili.txt
deleted file mode 100644
index 4e93538..0000000
--- a/addon/globalPlugins/personalFeeds/Blog SpazioAusili.txt   
+++ /dev/null
@@ -1 +0,0 @@
-http://www.spazioausili.net/rss/blog
\ No newline at end of file

diff --git a/addon/globalPlugins/personalFeeds/Forum SpazioAusili.txt 
b/addon/globalPlugins/personalFeeds/Forum SpazioAusili.txt
deleted file mode 100644
index 467e223..0000000
--- a/addon/globalPlugins/personalFeeds/Forum SpazioAusili.txt  
+++ /dev/null
@@ -1 +0,0 @@
-http://www.spazioausili.net/rss/forum
\ No newline at end of file

diff --git a/addon/globalPlugins/personalFeeds/W3C-news.txt 
b/addon/globalPlugins/personalFeeds/W3C-news.txt
deleted file mode 100644
index d6d78cd..0000000
--- a/addon/globalPlugins/personalFeeds/W3C-news.txt
+++ /dev/null
@@ -1 +0,0 @@
-http://www.w3.org/News/atom.xml
\ No newline at end of file

diff --git a/addon/globalPlugins/personalFeeds/addons NVDA-project IT RSS.txt 
b/addon/globalPlugins/personalFeeds/addons NVDA-project IT RSS.txt
deleted file mode 100644
index ce662ed..0000000
--- a/addon/globalPlugins/personalFeeds/addons NVDA-project IT RSS.txt  
+++ /dev/null
@@ -1 +0,0 @@
-http://addons.nvda-project.org/index.it.rss
\ No newline at end of file

diff --git a/addon/globalPlugins/personalFeeds/addressFile.txt 
b/addon/globalPlugins/personalFeeds/addressFile.txt
deleted file mode 100644
index 467e223..0000000
--- a/addon/globalPlugins/personalFeeds/addressFile.txt
+++ /dev/null
@@ -1 +0,0 @@
-http://www.spazioausili.net/rss/forum
\ No newline at end of file

diff --git a/addon/globalPlugins/readFeeds.py b/addon/globalPlugins/readFeeds.py
deleted file mode 100644
index 7746d5c..0000000
--- a/addon/globalPlugins/readFeeds.py
+++ /dev/null
@@ -1,413 +0,0 @@
-# -*- coding: UTF-8 -*-
-
-# Read feeds: A simple plugin for reading feeds with NVDA
-#Copyright (C) 2012-2016 Noelia Ruiz Martínez, Mesar Hameed
-# Released under GPL 2
-
-# Version: 6.0
-# Used globalVars to get config path, for better results with temporary 
copies, as instantTranslate or SaveLog add-ons
-# Version: 5.0
-# Control instead of alt in gesture. Added installTask to update the add-on
-# Version: 4.2
-# Channel title and number of articles in News list dialog
-# Date: 23/06/2012
-
-import addonHandler
-import globalPluginHandler
-import os
-import sys
-import shutil
-import globalVars
-import config
-import urllib
-import scriptHandler
-import api
-import gui
-from gui import guiHelper
-import wx
-import ui
-from logHandler import log
-import re
-
-sys.path.append(os.path.dirname(__file__))
-from xml2.dom import minidom
-del sys.path[-1]
-
-addonHandler.initTranslation()
-
-### Constants
-
-ADDON_DIR = os.path.join(os.path.dirname(__file__), "..")  # The root of the 
addon folder
-ADDON_INSTANCE = addonHandler.Addon(ADDON_DIR)
-ADDON_SUMMARY = ADDON_INSTANCE.manifest['summary']
-FEEDS_PATH = os.path.join(ADDON_DIR, "globalPlugins", "personalFeeds")
-CONFIG_PATH = globalVars.appArgs.configPath
-# Translators: message presented when feeds cannot be reported.
-CAN_NOT_REPORT = _("Unable to refresh feed. Check your Internet conectivity or 
that the specified feed address is correct.")
-
-### Configuration
-
-confspec = {
-       "addressFile": "string(default="")",
-}
-config.conf.spec["readFeeds"] = confspec
-
-### Dialogs
-
-class FeedsDialog(wx.Dialog):
-
-       _instance = None
-       def __new__(cls, *args, **kwargs):
-               # Make this a singleton.
-               if FeedsDialog._instance is None:
-                       return super(FeedsDialog, cls).__new__(cls, *args, 
**kwargs)
-               return FeedsDialog._instance
-
-       def __init__(self, parent):
-               if FeedsDialog._instance is not None:
-                       return
-               FeedsDialog._instance = self
-               # Translators: The title of a dialog.
-               super(FeedsDialog, self).__init__(parent, title=_("Feeds"))
-
-               mainSizer = wx.BoxSizer(wx.VERTICAL)
-               sHelper = guiHelper.BoxSizerHelper(self,orientation=wx.VERTICAL)
-               feedsListGroupSizer = wx.StaticBoxSizer(wx.StaticBox(self), 
wx.HORIZONTAL)
-               feedsListGroupContents = wx.BoxSizer(wx.HORIZONTAL)
-               changeFeedsSizer = wx.BoxSizer(wx.VERTICAL)
-
-               # Translators: The label of an edit box to search feeds.
-               searchTextLabel = _("&Text to search:")
-               searchLabeledCtrl = gui.guiHelper.LabeledControlHelper(self, 
searchTextLabel, wx.TextCtrl)
-               self.searchTextEdit = searchLabeledCtrl.control
-               self.searchTextEdit.Bind(wx.EVT_TEXT, 
self.onSearchEditTextChange)
-
-               self.choices = [os.path.splitext(filename)[0] for filename in 
os.listdir(FEEDS_PATH)]
-               self.feedsList = wx.ListBox(self,
-                       choices=self.choices)
-               self.feedsList.Selection = 0
-               self.feedsList.Bind(wx.EVT_LISTBOX, self.onFeedsListChoice)
-               changeFeedsSizer.Add(self.feedsList, proportion=1.0)
-               
changeFeedsSizer.AddSpacer(guiHelper.SPACE_BETWEEN_BUTTONS_VERTICAL)
-
-               # Translators: The label of a button to open the list of 
articles of a feed.
-               self.articlesButton = wx.Button(self, label=_("List of 
&articles..."))
-               self.articlesButton.Bind(wx.EVT_BUTTON, self.onArticles)
-               self.AffirmativeId = self.articlesButton.Id
-               self.articlesButton.SetDefault()
-               changeFeedsSizer.Add(self.articlesButton)
-
-               feedsListGroupContents.Add(changeFeedsSizer, flag = wx.EXPAND)
-               
feedsListGroupContents.AddSpacer(guiHelper.SPACE_BETWEEN_ASSOCIATED_CONTROL_HORIZONTAL)
-
-               buttonHelper = guiHelper.ButtonHelper(wx.VERTICAL)
-               # Translators: The label of a button to add a new feed.
-               newButton = buttonHelper.addButton(self, label=_("&New..."))
-               newButton.Bind(wx.EVT_BUTTON, self.onNew)
-
-               # Translators: The label of a button to rename a feed.
-               self.renameButton = buttonHelper.addButton(self, 
label=_("&Rename..."))
-               self.renameButton.Bind(wx.EVT_BUTTON, self.onRename)
-
-               # Translators: The label of a button to delete a feed.
-               self.deleteButton = buttonHelper.addButton(self, 
label=_("&Delete..."))
-               self.deleteButton.Bind(wx.EVT_BUTTON, self.onDelete)
-
-# Translators: The label of a button to set a feed as default.
-               self.defaultButton = buttonHelper.addButton(self, label=_("S&et 
default"))
-               self.defaultButton.Bind(wx.EVT_BUTTON, self.onDefault)
-
-               feedsListGroupContents.Add(buttonHelper.sizer)
-               feedsListGroupSizer.Add(feedsListGroupContents, 
border=guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL)
-               sHelper.addItem(feedsListGroupSizer)
-
-               # Translators: The label of a button to close a dialog.
-               closeButton = wx.Button(self, wx.ID_CLOSE, label=_("&Close"))
-               closeButton.Bind(wx.EVT_BUTTON, lambda evt: self.Close())
-               sHelper.addDialogDismissButtons(closeButton)
-               self.Bind(wx.EVT_CLOSE, self.onClose)
-               self.EscapeId = wx.ID_CLOSE
-
-               self.onFeedsListChoice(None)
-               mainSizer.Add(sHelper.sizer, flag=wx.ALL, 
border=guiHelper.BORDER_FOR_DIALOGS)
-               mainSizer.Fit(self)
-               self.Sizer = mainSizer
-               self.searchTextEdit.SetFocus()
-               self.Center(wx.BOTH | wx.CENTER_ON_SCREEN)
-
-       def __del__(self):
-               FeedsDialog._instance = None
-
-       def createFeed(self, address):
-               feed = Feed(address)
-               feedName = api.filterFileName(feed.getFeedName())
-               if os.path.isfile(os.path.join(FEEDS_PATH, "%s.txt" % 
feedName)):
-                       feedName = "tempFeed"
-               with open(os.path.join(FEEDS_PATH, "%s.txt" % feedName), "w") 
as f:
-                       f.write(address)
-                       f.close()
-               return feedName
-
-       def onSearchEditTextChange(self, evt):
-               self.feedsList.Clear()
-               # Based on the filter of the Input gestures dialog of NVDA's 
core.
-               filter = self.searchTextEdit.Value
-               filter = re.escape(filter)
-               filterReg = re.compile(r'(?=.*?' + 
r')(?=.*?'.join(filter.split('\ ')) + r')', re.U|re.IGNORECASE)
-               for choice in self.choices:
-                       if filter and not filterReg.match(choice):
-                               continue
-                       self.feedsList.Append(choice)
-               self.feedsList.Selection = 0
-               self.onFeedsListChoice(None)
-
-       def onFeedsListChoice(self, evt):
-               self.sel = self.feedsList.Selection
-               self.stringSel = self.feedsList.StringSelection
-               self.articlesButton.Enabled = self.sel>= 0
-               self.deleteButton.Enabled = self.sel >= 0
-               self.renameButton.Enabled = self.sel >= 0
-               self.defaultButton.Enabled = (self.sel >= 0 and
-                       self.stringSel != 
config.conf["readFeeds"]["addressFile"])
-
-       def onArticles(self, evt):
-               with open(os.path.join(FEEDS_PATH, "%s.txt" % self.stringSel), 
"r") as f:
-                       address = f.read()
-                       f.close()
-               feed = Feed(address)
-               with wx.SingleChoiceDialog(self,
-                       # Translators: the label of a single choice dialog.
-                       _("Open web page of selected article."),
-                       # Translators: Title of a dialog.
-                       u"{feedTitle} 
({feedNumber})".format(feedTitle=self.stringSel, 
feedNumber=feed.getNumberOfArticles()),
-                       [feed.getArticleTitle(index) for index in 
xrange(feed.getNumberOfArticles())]) as d:
-                       if d.ShowModal() == wx.ID_CANCEL:
-                               return
-                       os.startfile(feed.getArticleLink(d.Selection))
-
-       def onNew(self, evt):
-               # Translators: The label of a field to enter an address for a 
new feed.
-               with wx.TextEntryDialog(self, _("Address of a new feed:"),
-                       # Translators: The title of a dialog to create a new 
feed.
-                               _("New feed")) as d:
-                       if d.ShowModal() == wx.ID_CANCEL:
-                               return
-                       name = self.createFeed(d.Value)
-               self.feedsList.Append(name)
-
-       def onDelete(self, evt):
-               if gui.messageBox(
-                       # Translators: The confirmation prompt displayed when 
the user requests to delete a feed.
-                       _("Are you sure you want to delete this feed? This 
cannot be undone."),
-                       # Translators: The title of the confirmation dialog for 
deletion of a feed.
-                       _("Confirm Deletion"),
-                       wx.YES | wx.NO | wx.ICON_QUESTION, self
-               ) == wx.NO:
-                       return
-               os.remove(os.path.join(FEEDS_PATH, "%s.txt" % self.stringSel))
-               self.feedsList.Delete(self.sel)
-               self.feedsList.Selection = 0
-               self.onFeedsListChoice(None)
-
-       def onDefault(self, evt):
-               config.conf["readFeeds"]["addressFile"] = self.stringSel
-               self.onFeedsListChoice(None)
-               self.searchTextEdit.SetFocus()
-
-       def onRename(self, evt):
-               # Translators: The label of a field to enter a new name for a 
feed.
-               with wx.TextEntryDialog(self, _("New name:"),
-                               # Translators: The title of a dialog to rename 
a feed.
-                               _("Rename feed"), defaultValue=self.stringSel) 
as d:
-                       if d.ShowModal() == wx.ID_CANCEL or not d.Value:
-                               return
-                       curName = "%s.txt" % self.stringSel
-                       newName = "%s.txt" % api.filterFileName(d.Value)
-                       os.rename(os.path.join(FEEDS_PATH, curName),
-                               os.path.join(FEEDS_PATH, newName))
-               self.feedsList.SetString(self.sel, os.path.splitext(newName)[0])
-
-       def onClose(self, evt):
-               self.Destroy()
-
-class Feed(object):
-
-       def __init__(self, url):
-               super(Feed, self).__init__()
-               self._url = url
-               self._document = None
-               self._articles = []
-               self.refresh()
-
-       def refresh(self):
-               try:
-                       self._document = 
minidom.parse(urllib.urlopen(self._url))
-               except Exception as e:
-                       raise e
-               # Check if we are dealing with an rss or atom feed.
-               rssFeed = self._document.getElementsByTagName('channel')
-               if len(rssFeed):
-                       self._feedType = 'rss'
-                       self._articles = 
self._document.getElementsByTagName('item')
-               else:
-                       atomFeed = self._document.getElementsByTagName('feed')
-                       if len(atomFeed):
-                               self._feedType = 'atom'
-                               self._articles = 
self._document.getElementsByTagName('entry')
-                       else:
-                               log.debugWarning("Unknown type of current 
feed", exc_info=True)
-                               raise
-               self._index = 0
-
-       def getFeedUrl(self):
-               return self._url
-
-       def getFeedType(self):
-               return self._feedType
-
-       def getFeedName(self):
-               try:
-                       return 
self._document.getElementsByTagName('title')[0].firstChild.data
-               except:
-                       return ""
-
-       def getArticleTitle(self, index=None):
-               if index is None: index = self._index
-               try:
-                       return 
self._articles[index].getElementsByTagName('title')[0].firstChild.data
-               except:
-                       # Translators: Presented when the current article does 
not have an associated title.
-                       return _("Unable to locate article title.")
-
-       def getArticleLink(self, index=None):
-               if index is None: index = self._index
-               try:
-                       if self.getFeedType() == u'rss':
-                               return 
self._articles[index].getElementsByTagName('link')[0].firstChild.data
-                       elif self.getFeedType() == u'atom':
-                               return 
self._articles[index].getElementsByTagName('link')[0].getAttribute('href')
-               except:
-                       # Translators: Presented when the current article does 
not have an associated link.
-                       return _("Unable to locate article link.")
-
-       def next(self):
-               self._index += 1
-               if self._index == self.getNumberOfArticles():
-                       self._index = 0
-
-       def previous(self):
-               self._index -= 1
-               if self._index == -1:
-                       self._index = self.getNumberOfArticles() - 1
-
-       def getNumberOfArticles(self):
-               return len(self._articles)
-
-
-class GlobalPlugin(globalPluginHandler.GlobalPlugin):
-
-       scriptCategory = unicode(ADDON_SUMMARY)
-
-       def __init__(self):
-               super(globalPluginHandler.GlobalPlugin, self).__init__()
-               self.menu = gui.mainFrame.sysTrayIcon.toolsMenu
-               self.readFeedsMenu = wx.Menu()
-               self.mainItem = self.menu.AppendSubMenu(self.readFeedsMenu,
-               # Translators: the name of a submenu.
-               _("&Read Feeds"),
-               # Translators: the tooltip for a submenu.
-               _("Manage feeds."))
-               self.feedsListItem = self.readFeedsMenu.Append(wx.ID_ANY,
-               # Translators: the name of a menu item.
-               _("&Feeds..."),
-               # Translators: the tooltip for a menu item.
-               _("View and manage feeds"))
-               gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.onFeeds, 
self.feedsListItem)
-               self.feed = None
-
-       def terminate(self):
-               try:
-                       self.menu.RemoveItem(self.mainItem)
-               except wx.PyDeadObjectError:
-                       pass
-
-       def onFeeds(self, evt):
-               gui.mainFrame.prePopup()
-               d = FeedsDialog(gui.mainFrame)
-               d.Show()
-               gui.mainFrame.postPopup()
-
-       def script_feedsDialog(self, gesture):
-               wx.CallAfter(self.onFeeds, None)
-       # Translators: message presented in input mode.
-       script_feedsDialog.__doc__ = _("Shows the Feeds dialog.")
-
-       def getFirstFeed(self):
-               if not self.feed:
-                       try:
-                               addressFile = "%s.txt" % 
config.conf["readFeeds"]["addressFile"]
-                               with open(os.path.join(FEEDS_PATH, 
addressFile), "r") as f:
-                                       address = f.read()
-                                       f.close()
-                               self.feed = Feed(address)
-                       except Exception as e:
-                               ui.message(CAN_NOT_REPORT)
-
-       def script_readFirstFeed(self, gesture):
-               self.getFirstFeed()
-               if self.feed:
-                       ui.message(self.feed.getArticleTitle())
-       # Translators: message presented in input mode.
-       script_readFirstFeed.__doc__ = _("Refreshes the current feed and 
announces the most recent article title.")
-
-       def script_readCurrentFeed(self, gesture):
-               self.getFirstFeed()
-               feedInfo = 
u"{title}\r\n\r\n{address}".format(title=self.feed.getArticleTitle(), 
address=self.feed.getArticleLink())
-               if scriptHandler.getLastScriptRepeatCount()==1 and 
api.copyToClip(feedInfo):
-                       # Translators: message presented when the information 
about an article of a feed is copied to the clipboard.
-                       ui.message(_("Copied to clipboard %s") % feedInfo)
-               else:
-                       ui.message(feedInfo)
-       # Translators: message presented in input mode.
-       script_readCurrentFeed.__doc__ = _("Announces the title of the current 
article. Pressed two times, copies title and related link to the clipboard.")
-
-       def script_readNextFeed(self, gesture):
-               self.getFirstFeed()
-               self.feed.next()
-               ui.message(self.feed.getArticleTitle())
-       # Translators: message presented in input mode.
-       script_readNextFeed.__doc__ = _("Announces the title of the next 
article.")
-
-       def script_readPriorFeed(self, gesture):
-               self.getFirstFeed()
-               self.feed.previous()
-               ui.message(self.feed.getArticleTitle())
-       # Translators: message presented in input mode.
-       script_readPriorFeed.__doc__ = _("Announces the title of the previous 
article.")
-
-       def script_reportLink(self, gesture):
-               self.getFirstFeed()
-               feedLink = self.feed.getArticleLink()
-               if scriptHandler.getLastScriptRepeatCount()==1:
-                       os.startfile(feedLink)
-               else:
-                       ui.message(feedLink)
-       # Translators: message presented in input mode.
-       script_reportLink.__doc__ = _("Announces article link, when pressed two 
times, opens related web page.")
-
-       def script_copyFeedInfo(self, gesture):
-               self.getFirstFeed()
-               feedInfo = 
u"{title}\r\n\r\n{address}".format(title=self.feed.getArticleTitle(), 
address=self.feed.getArticleLink())
-               if api.copyToClip(feedInfo):
-                       # Translators: message presented when the information 
about an article of a feed is copied to the clipboard.
-                       ui.message(_("Copied to clipboard %s") % feedInfo)
-
-       __gestures = {
-               "kb:control+shift+NVDA+8": "readFirstFeed",
-               "kb:control+shift+NVDA+i": "readCurrentFeed",
-               "kb:control+shift+NVDA+o": "readNextFeed",
-               "kb:control+shift+NVDA+u": "readPriorFeed",
-               "kb:control+shift+NVDA+space": "reportLink",
-               "kb:shift+NVDA+enter": "copyFeedInfo",
-       }
-

diff --git a/addon/globalPlugins/readFeeds/__init__.py 
b/addon/globalPlugins/readFeeds/__init__.py
new file mode 100644
index 0000000..9bfba46
--- /dev/null
+++ b/addon/globalPlugins/readFeeds/__init__.py
@@ -0,0 +1,611 @@
+# -*- coding: UTF-8 -*-
+
+# Read feeds: A simple plugin for reading feeds with NVDA
+#Copyright (C) 2012-2016 Noelia Ruiz Martínez, Mesar Hameed
+# Released under GPL 2
+
+# Version: 6.0
+# Used globalVars to get config path, for better results with temporary 
copies, as instantTranslate or SaveLog add-ons
+# Version: 5.0
+# Control instead of alt in gesture. Added installTask to update the add-on
+# Version: 4.2
+# Channel title and number of articles in News list dialog
+# Date: 23/06/2012
+
+import addonHandler
+import globalPluginHandler
+import os
+import sys
+import shutil
+import globalVars
+import config
+import urllib
+import scriptHandler
+import api
+import gui
+from gui import guiHelper
+import wx
+import ui
+from logHandler import log
+import re
+from skipTranslation import translate
+
+sys.path.append(os.path.join(os.path.dirname(__file__), "..", ".."))
+from xml2.dom import minidom
+del sys.path[-1]
+
+addonHandler.initTranslation()
+
+### Constants
+
+ADDON_DIR = os.path.join(os.path.dirname(__file__), "..", "..")  # The root of 
the addon folder
+ADDON_INSTANCE = addonHandler.Addon(ADDON_DIR)
+ADDON_SUMMARY = ADDON_INSTANCE.manifest['summary']
+FEEDS_PATH = os.path.join(os.path.dirname(__file__), 
"personalFeeds").decode("mbcs")
+CONFIG_PATH = globalVars.appArgs.configPath
+# Translators: message presented when feeds cannot be reported.
+CAN_NOT_REPORT = _("Unable to refresh feed. Check your Internet conectivity or 
that the specified feed address is correct.")
+
+### Configuration
+
+confspec = {
+       "addressFile": "string(default="")",
+}
+config.conf.spec["readFeeds"] = confspec
+
+### Dialogs
+
+def doCopy(copyDirectory):
+       try:
+               shutil.rmtree(copyDirectory, ignore_errors=True)
+               shutil.copytree(FEEDS_PATH, copyDirectory)
+               wx.CallLater(100, ui.message,
+                       # Translators: Message presented when feeds have been 
copied.
+                       _("Feeds copied"))
+       except Exception as e:
+               wx.CallAfter(gui.messageBox,
+                       # Translators: label of error dialog shown when cannot 
copy feeds folder.
+                       _("Folder not copied"),
+                       # Translators: title of error dialog shown when cannot 
copy feeds folder.
+                       _("Copy Error"),
+                       wx.OK|wx.ICON_ERROR)
+               raise e
+
+def doRestore(restoreDirectory):
+       try:
+               shutil.rmtree(FEEDS_PATH, ignore_errors=True)
+               shutil.copytree(restoreDirectory, FEEDS_PATH)
+               wx.CallLater(100, ui.message,
+                       # Translators: Message presented when feeds have been 
restored.
+                       _("Feeds restored"))
+       except Exception as e:
+               wx.CallAfter(gui.messageBox,
+                       # Translators: label of error dialog shown when cannot 
copy feeds folder.
+                       _("Folder not copied"),
+                       # Translators: title of error dialog shown when cannot 
copy feeds folder.
+                       _("Copy Error"),
+                       wx.OK|wx.ICON_ERROR)
+               raise e
+
+class FeedsDialog(wx.Dialog):
+
+       _instance = None
+       def __new__(cls, *args, **kwargs):
+               # Make this a singleton.
+               if FeedsDialog._instance is None:
+                       return super(FeedsDialog, cls).__new__(cls, *args, 
**kwargs)
+               return FeedsDialog._instance
+
+       def __init__(self, parent):
+               if FeedsDialog._instance is not None:
+                       return
+               FeedsDialog._instance = self
+               # Translators: The title of a dialog.
+               super(FeedsDialog, self).__init__(parent, title=_("Feeds"))
+
+               mainSizer = wx.BoxSizer(wx.VERTICAL)
+               sHelper = guiHelper.BoxSizerHelper(self,orientation=wx.VERTICAL)
+               feedsListGroupSizer = wx.StaticBoxSizer(wx.StaticBox(self), 
wx.HORIZONTAL)
+               feedsListGroupContents = wx.BoxSizer(wx.HORIZONTAL)
+               changeFeedsSizer = wx.BoxSizer(wx.VERTICAL)
+
+               # Translators: The label of an edit box to search feeds.
+               searchTextLabel = _("&Text to search:")
+               searchLabeledCtrl = gui.guiHelper.LabeledControlHelper(self, 
searchTextLabel, wx.TextCtrl)
+               self.searchTextEdit = searchLabeledCtrl.control
+               self.searchTextEdit.Bind(wx.EVT_TEXT, 
self.onSearchEditTextChange)
+
+               self.choices = [os.path.splitext(filename)[0] for filename in 
os.listdir(FEEDS_PATH)]
+               self.feedsList = wx.ListBox(self,
+                       choices=self.choices)
+               self.feedsList.Selection = 0
+               self.feedsList.Bind(wx.EVT_LISTBOX, self.onFeedsListChoice)
+               changeFeedsSizer.Add(self.feedsList, proportion=1.0)
+               
changeFeedsSizer.AddSpacer(guiHelper.SPACE_BETWEEN_BUTTONS_VERTICAL)
+
+               # Translators: The label of a button to open the list of 
articles of a feed.
+               self.articlesButton = wx.Button(self, label=_("List of 
&articles..."))
+               self.articlesButton.Bind(wx.EVT_BUTTON, self.onArticles)
+               self.AffirmativeId = self.articlesButton.Id
+               self.articlesButton.SetDefault()
+               changeFeedsSizer.Add(self.articlesButton)
+
+               feedsListGroupContents.Add(changeFeedsSizer, flag = wx.EXPAND)
+               
feedsListGroupContents.AddSpacer(guiHelper.SPACE_BETWEEN_ASSOCIATED_CONTROL_HORIZONTAL)
+
+               buttonHelper = guiHelper.ButtonHelper(wx.VERTICAL)
+               # Translators: The label of a button to add a new feed.
+               newButton = buttonHelper.addButton(self, label=_("&New..."))
+               newButton.Bind(wx.EVT_BUTTON, self.onNew)
+
+               # Translators: The label of a button to rename a feed.
+               self.renameButton = buttonHelper.addButton(self, 
label=_("&Rename..."))
+               self.renameButton.Bind(wx.EVT_BUTTON, self.onRename)
+
+               # Translators: The label of a button to delete a feed.
+               self.deleteButton = buttonHelper.addButton(self, 
label=_("&Delete..."))
+               self.deleteButton.Bind(wx.EVT_BUTTON, self.onDelete)
+
+# Translators: The label of a button to set a feed as default.
+               self.defaultButton = buttonHelper.addButton(self, label=_("S&et 
default"))
+               self.defaultButton.Bind(wx.EVT_BUTTON, self.onDefault)
+
+               feedsListGroupContents.Add(buttonHelper.sizer)
+               feedsListGroupSizer.Add(feedsListGroupContents, 
border=guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL)
+               sHelper.addItem(feedsListGroupSizer)
+
+               # Translators: The label of a button to close a dialog.
+               closeButton = wx.Button(self, wx.ID_CLOSE, label=_("&Close"))
+               closeButton.Bind(wx.EVT_BUTTON, lambda evt: self.Close())
+               sHelper.addDialogDismissButtons(closeButton)
+               self.Bind(wx.EVT_CLOSE, self.onClose)
+               self.EscapeId = wx.ID_CLOSE
+
+               self.onFeedsListChoice(None)
+               mainSizer.Add(sHelper.sizer, flag=wx.ALL, 
border=guiHelper.BORDER_FOR_DIALOGS)
+               mainSizer.Fit(self)
+               self.Sizer = mainSizer
+               self.searchTextEdit.SetFocus()
+               self.Center(wx.BOTH | wx.CENTER_ON_SCREEN)
+
+       def __del__(self):
+               FeedsDialog._instance = None
+
+       def createFeed(self, address):
+               feed = Feed(address)
+               feedName = api.filterFileName(feed.getFeedName())
+               if os.path.isfile(os.path.join(FEEDS_PATH, "%s.txt" % 
feedName)):
+                       feedName = "tempFeed"
+               with open(os.path.join(FEEDS_PATH, "%s.txt" % feedName), "w") 
as f:
+                       f.write(address)
+                       f.close()
+               return feedName
+
+       def onSearchEditTextChange(self, evt):
+               self.feedsList.Clear()
+               # Based on the filter of the Input gestures dialog of NVDA's 
core.
+               filter = self.searchTextEdit.Value
+               filter = re.escape(filter)
+               filterReg = re.compile(r'(?=.*?' + 
r')(?=.*?'.join(filter.split('\ ')) + r')', re.U|re.IGNORECASE)
+               for choice in self.choices:
+                       if filter and not filterReg.match(choice):
+                               continue
+                       self.feedsList.Append(choice)
+               self.feedsList.Selection = 0
+               self.onFeedsListChoice(None)
+
+       def onFeedsListChoice(self, evt):
+               self.sel = self.feedsList.Selection
+               self.stringSel = self.feedsList.StringSelection
+               self.articlesButton.Enabled = self.sel>= 0
+               self.deleteButton.Enabled = self.sel >= 0
+               self.renameButton.Enabled = self.sel >= 0
+               self.defaultButton.Enabled = (self.sel >= 0 and
+                       self.stringSel != 
config.conf["readFeeds"]["addressFile"])
+
+       def onArticles(self, evt):
+               with open(os.path.join(FEEDS_PATH, "%s.txt" % self.stringSel), 
"r") as f:
+                       address = f.read()
+                       f.close()
+               feed = Feed(address)
+               with wx.SingleChoiceDialog(self,
+                       # Translators: the label of a single choice dialog.
+                       _("Open web page of selected article."),
+                       # Translators: Title of a dialog.
+                       u"{feedTitle} 
({feedNumber})".format(feedTitle=self.stringSel, 
feedNumber=feed.getNumberOfArticles()),
+                       [feed.getArticleTitle(index) for index in 
xrange(feed.getNumberOfArticles())]) as d:
+                       if d.ShowModal() == wx.ID_CANCEL:
+                               return
+                       os.startfile(feed.getArticleLink(d.Selection))
+
+       def onNew(self, evt):
+               # Translators: The label of a field to enter an address for a 
new feed.
+               with wx.TextEntryDialog(self, _("Address of a new feed:"),
+                       # Translators: The title of a dialog to create a new 
feed.
+                               _("New feed")) as d:
+                       if d.ShowModal() == wx.ID_CANCEL:
+                               return
+                       name = self.createFeed(d.Value)
+               self.feedsList.Append(name)
+
+       def onDelete(self, evt):
+               if gui.messageBox(
+                       # Translators: The confirmation prompt displayed when 
the user requests to delete a feed.
+                       _("Are you sure you want to delete this feed? This 
cannot be undone."),
+                       # Translators: The title of the confirmation dialog for 
deletion of a feed.
+                       _("Confirm Deletion"),
+                       wx.YES | wx.NO | wx.ICON_QUESTION, self
+               ) == wx.NO:
+                       return
+               os.remove(os.path.join(FEEDS_PATH, "%s.txt" % self.stringSel))
+               self.feedsList.Delete(self.sel)
+               self.feedsList.Selection = 0
+               self.onFeedsListChoice(None)
+
+       def onDefault(self, evt):
+               config.conf["readFeeds"]["addressFile"] = self.stringSel
+               self.onFeedsListChoice(None)
+               self.searchTextEdit.SetFocus()
+
+       def onRename(self, evt):
+               # Translators: The label of a field to enter a new name for a 
feed.
+               with wx.TextEntryDialog(self, _("New name:"),
+                               # Translators: The title of a dialog to rename 
a feed.
+                               _("Rename feed"), defaultValue=self.stringSel) 
as d:
+                       if d.ShowModal() == wx.ID_CANCEL or not d.Value:
+                               return
+                       curName = "%s.txt" % self.stringSel
+                       newName = "%s.txt" % api.filterFileName(d.Value)
+                       os.rename(os.path.join(FEEDS_PATH, curName),
+                               os.path.join(FEEDS_PATH, newName))
+               self.feedsList.SetString(self.sel, os.path.splitext(newName)[0])
+
+       def onClose(self, evt):
+               self.Destroy()
+
+class CopyDialog(wx.Dialog):
+
+       def __init__(self, parent):
+               # Translators: The title of the Copy dialog.
+               super(CopyDialog, self).__init__(parent, title=_("Copy feeds"))
+
+               mainSizer = wx.BoxSizer(wx.VERTICAL)
+               sHelper = gui.guiHelper.BoxSizerHelper(self, 
orientation=wx.VERTICAL)
+
+               # Translators: An informational message displayed in the Copy 
dialog.
+               dialogCaption=_("""Select a folder to save a backup of your 
current feeds.\n
+               They will be copied from %s.""" % FEEDS_PATH)
+               sHelper.addItem(wx.StaticText(self, label=dialogCaption))
+
+               # Translators: The label of a grouping containing controls to 
select the destination directory in the Copy dialog.
+               directoryGroupText = _("directory for backup:")
+               groupHelper = 
sHelper.addItem(gui.guiHelper.BoxSizerHelper(self, 
sizer=wx.StaticBoxSizer(wx.StaticBox(self, label=directoryGroupText), 
wx.VERTICAL)))
+               # Message translated in NVDA core.
+               browseText = translate("Browse...")
+               # Translators: The title of the dialog presented when browsing 
for the destination directory when copying feeds.
+               dirDialogTitle = _("Select directory to copy")
+               directoryEntryControl = 
groupHelper.addItem(gui.guiHelper.PathSelectionHelper(self, browseText, 
dirDialogTitle))
+               self.copyDirectoryEdit = directoryEntryControl.pathControl
+               self.copyDirectoryEdit.Value = os.path.join(CONFIG_PATH, 
"personalFeeds")
+               bHelper = 
sHelper.addDialogDismissButtons(gui.guiHelper.ButtonHelper(wx.HORIZONTAL))
+               # Message translated in NVDA core.
+               continueButton = bHelper.addButton(self, 
label=translate("&Continue"), id=wx.ID_OK)
+               continueButton.SetDefault()
+               continueButton.Bind(wx.EVT_BUTTON, self.onCopy)
+               bHelper.addButton(self, id=wx.ID_CANCEL)
+               mainSizer.Add(sHelper.sizer, 
border=gui.guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL)
+               self.Sizer = mainSizer
+               mainSizer.Fit(self)
+               self.Center(wx.BOTH | wx.CENTER_ON_SCREEN)
+
+       def onCopy(self, evt):
+               if not self.copyDirectoryEdit.Value:
+                       # Message translated in NVDA core.
+                       gui.messageBox(translate("Please specify a directory."),
+                               # Message translated in NVDA core.
+                               translate("Error"),
+                               wx.OK | wx.ICON_ERROR)
+                       return
+               drv=os.path.splitdrive(self.copyDirectoryEdit.Value)[0]
+               if drv and not os.path.isdir(drv):
+                       # Message translated in NVDA core.
+                       gui.messageBox(translate("Invalid drive %s")%drv,
+                               # Message translated in NVDA core.
+                               translate("Error"),
+                               wx.OK | wx.ICON_ERROR)
+                       return
+               self.Hide()
+               doCopy(self.copyDirectoryEdit.Value)
+               self.Destroy()
+
+       def onCancel(self, evt):
+               self.Destroy()
+
+class PathSelectionWithoutNewDir(gui.guiHelper.PathSelectionHelper):
+
+       def __init__(self, parent, buttonText, browseForDirectoryTitle):
+               super(PathSelectionWithoutNewDir, self).__init__(parent, 
buttonText, browseForDirectoryTitle)
+
+       def onBrowseForDirectory(self, evt):
+               startPath = self.getDefaultBrowseForDirectoryPath()
+               with wx.DirDialog(self._parent, self._browseForDirectoryTitle, 
defaultPath=startPath, style=wx.DD_DIR_MUST_EXIST | wx.DD_DEFAULT_STYLE) as d:
+                       if d.ShowModal() == wx.ID_OK:
+                               self._textCtrl.Value = d.Path
+
+class RestoreDialog(wx.Dialog):
+
+       def __init__(self, parent):
+               # Translators: The title of the Restore dialog.
+               super(RestoreDialog, self).__init__(parent, title=_("Restore 
feeds"))
+
+               mainSizer = wx.BoxSizer(wx.VERTICAL)
+               sHelper = gui.guiHelper.BoxSizerHelper(self, 
orientation=wx.VERTICAL)
+
+               # Translators: An informational message displayed in the 
Restore dialog.
+               dialogCaption=_("""Select a folder to restore a backup of your 
previous copied feeds.\n
+               They will be copied to %s.""" % FEEDS_PATH)
+               sHelper.addItem(wx.StaticText(self, label=dialogCaption))
+
+               # Translators: The label of a grouping containing controls to 
select the destination directory in the Restore dialog.
+               directoryGroupText = _("directory containing backup:")
+               groupHelper = 
sHelper.addItem(gui.guiHelper.BoxSizerHelper(self, 
sizer=wx.StaticBoxSizer(wx.StaticBox(self, label=directoryGroupText), 
wx.VERTICAL)))
+               # Message translated in NVDA core.
+               browseText = translate("Browse...")
+               # Translators: The title of the dialog presented when browsing 
for the destination directory when restoring feeds.
+               dirDialogTitle = _("Select directory to restore")
+               directoryEntryControl = 
groupHelper.addItem(PathSelectionWithoutNewDir(self, browseText, 
dirDialogTitle))
+               self.restoreDirectoryEdit = directoryEntryControl.pathControl
+               backupDirectory = os.path.join(CONFIG_PATH, "personalFeeds")
+               if os.path.isdir(backupDirectory):
+                       self.restoreDirectoryEdit.Value = backupDirectory
+               bHelper = 
sHelper.addDialogDismissButtons(gui.guiHelper.ButtonHelper(wx.HORIZONTAL))
+               # Message translated in NVDA core.
+               continueButton = bHelper.addButton(self, 
label=translate("&Continue"), id=wx.ID_OK)
+               continueButton.SetDefault()
+               continueButton.Bind(wx.EVT_BUTTON, self.onRestore)
+               bHelper.addButton(self, id=wx.ID_CANCEL)
+               mainSizer.Add(sHelper.sizer, 
border=gui.guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL)
+               self.Sizer = mainSizer
+               mainSizer.Fit(self)
+               self.Center(wx.BOTH | wx.CENTER_ON_SCREEN)
+
+       def onRestore(self, evt):
+               if not self.restoreDirectoryEdit.Value:
+                       # Message translated in NVDA core.
+                       gui.messageBox(translate("Please specify a directory."),
+                               # Message translated in NVDA core.
+                               translate("Error"),
+                               wx.OK | wx.ICON_ERROR)
+                       return
+               drv=os.path.splitdrive(self.restoreDirectoryEdit.Value)[0]
+               if drv and not os.path.isdir(drv):
+                       # Message translated in NVDA core.
+                       gui.messageBox(translate("Invalid drive %s")%drv,
+                               # Message translated in NVDA core.
+                               translate("Error"),
+                               wx.OK | wx.ICON_ERROR)
+                       return
+               self.Hide()
+               doRestore(self.restoreDirectoryEdit.Value)
+               self.Destroy()
+
+       def onCancel(self, evt):
+               self.Destroy()
+
+### Feed object 
+
+class Feed(object):
+
+       def __init__(self, url):
+               super(Feed, self).__init__()
+               self._url = url
+               self._document = None
+               self._articles = []
+               self.refresh()
+
+       def refresh(self):
+               try:
+                       self._document = 
minidom.parse(urllib.urlopen(self._url))
+               except Exception as e:
+                       raise e
+               # Check if we are dealing with an rss or atom feed.
+               rssFeed = self._document.getElementsByTagName('channel')
+               if len(rssFeed):
+                       self._feedType = 'rss'
+                       self._articles = 
self._document.getElementsByTagName('item')
+               else:
+                       atomFeed = self._document.getElementsByTagName('feed')
+                       if len(atomFeed):
+                               self._feedType = 'atom'
+                               self._articles = 
self._document.getElementsByTagName('entry')
+                       else:
+                               log.debugWarning("Unknown type of current 
feed", exc_info=True)
+                               raise
+               self._index = 0
+
+       def getFeedUrl(self):
+               return self._url
+
+       def getFeedType(self):
+               return self._feedType
+
+       def getFeedName(self):
+               try:
+                       return 
self._document.getElementsByTagName('title')[0].firstChild.data
+               except:
+                       return ""
+
+       def getArticleTitle(self, index=None):
+               if index is None: index = self._index
+               try:
+                       return 
self._articles[index].getElementsByTagName('title')[0].firstChild.data
+               except:
+                       # Translators: Presented when the current article does 
not have an associated title.
+                       return _("Unable to locate article title.")
+
+       def getArticleLink(self, index=None):
+               if index is None: index = self._index
+               try:
+                       if self.getFeedType() == u'rss':
+                               return 
self._articles[index].getElementsByTagName('link')[0].firstChild.data
+                       elif self.getFeedType() == u'atom':
+                               return 
self._articles[index].getElementsByTagName('link')[0].getAttribute('href')
+               except:
+                       # Translators: Presented when the current article does 
not have an associated link.
+                       return _("Unable to locate article link.")
+
+       def next(self):
+               self._index += 1
+               if self._index == self.getNumberOfArticles():
+                       self._index = 0
+
+       def previous(self):
+               self._index -= 1
+               if self._index == -1:
+                       self._index = self.getNumberOfArticles() - 1
+
+       def getNumberOfArticles(self):
+               return len(self._articles)
+
+class GlobalPlugin(globalPluginHandler.GlobalPlugin):
+
+       scriptCategory = unicode(ADDON_SUMMARY)
+
+       def __init__(self):
+               super(globalPluginHandler.GlobalPlugin, self).__init__()
+               self.menu = gui.mainFrame.sysTrayIcon.toolsMenu
+               self.readFeedsMenu = wx.Menu()
+               self.mainItem = self.menu.AppendSubMenu(self.readFeedsMenu,
+               # Translators: the name of a submenu.
+               _("&Read Feeds"),
+               # Translators: the tooltip for a submenu.
+               _("Manage feeds."))
+               self.feedsListItem = self.readFeedsMenu.Append(wx.ID_ANY,
+               # Translators: the name of a menu item.
+               _("&Feeds..."),
+               # Translators: the tooltip for a menu item.
+               _("View and manage feeds"))
+               gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.onFeeds, 
self.feedsListItem)
+               self.copyItem = self.readFeedsMenu.Append(wx.ID_ANY,
+                       # Translators: the name for an item of addon submenu.
+                       _("&Copy feeds folder..."),
+                       # Translators: the tooltip text for an item of addon 
submenu.
+                       _("Backup of feeds"))
+               gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.onCopy, 
self.copyItem)
+               self.restoreItem = self.readFeedsMenu.Append(wx.ID_ANY,
+                       # Translators: the name for an item of addon submenu.
+                       _("R&estore feeds..."),
+                       # Translators: the tooltip text for an item of addon 
submenu.
+                       _("Restore previously saved feeds"))
+               gui.mainFrame.sysTrayIcon.Bind(wx.EVT_MENU, self.onRestore, 
self.restoreItem)
+
+               self.feed = None
+
+       def terminate(self):
+               try:
+                       self.menu.RemoveItem(self.mainItem)
+               except wx.PyDeadObjectError:
+                       pass
+
+       def onFeeds(self, evt):
+               gui.mainFrame.prePopup()
+               d = FeedsDialog(gui.mainFrame)
+               d.Show()
+               gui.mainFrame.postPopup()
+
+       def script_activateFeedsDialog(self, gesture):
+               wx.CallAfter(self.onFeeds, None)
+       # Translators: message presented in input mode.
+       script_activateFeedsDialog.__doc__ = _("Shows the Feeds dialog of %s." 
% ADDON_SUMMARY)
+
+       def onCopy(self, evt):
+               gui.mainFrame.prePopup()
+               d = CopyDialog(gui.mainFrame)
+               d.Show()
+               gui.mainFrame.postPopup()
+
+       def script_activateCopyDialog(self, gesture):
+               wx.CallAfter(self.onCopy, None)
+       # Translators: message presented in input mode.
+       script_activateCopyDialog.__doc__ = _("Activates the Copy dialog of 
%s." % ADDON_SUMMARY)
+
+       def onRestore(self, evt):
+               gui.mainFrame.prePopup()
+               d = RestoreDialog(gui.mainFrame)
+               d.Show()
+               gui.mainFrame.postPopup()
+
+       def script_activateRestoreDialog(self, gesture):
+               wx.CallAfter(self.onRestore, None)
+       # Translators: message presented in input mode.
+       script_activateRestoreDialog.__doc__ = _("Activates the Restore dialog 
of %s." % ADDON_SUMMARY)
+
+       def getFirstFeed(self):
+               if not self.feed:
+                       try:
+                               addressFile = "%s.txt" % 
config.conf["readFeeds"]["addressFile"]
+                               with open(os.path.join(FEEDS_PATH, 
addressFile), "r") as f:
+                                       address = f.read()
+                                       f.close()
+                               self.feed = Feed(address)
+                       except Exception as e:
+                               ui.message(CAN_NOT_REPORT)
+
+       def script_readFirstFeed(self, gesture):
+               self.getFirstFeed()
+               if self.feed:
+                       ui.message(self.feed.getArticleTitle())
+       # Translators: message presented in input mode.
+       script_readFirstFeed.__doc__ = _("Refreshes the current feed and 
announces the most recent article title.")
+
+       def script_readCurrentFeed(self, gesture):
+               self.getFirstFeed()
+               feedInfo = 
u"{title}\r\n\r\n{address}".format(title=self.feed.getArticleTitle(), 
address=self.feed.getArticleLink())
+               if scriptHandler.getLastScriptRepeatCount()==1 and 
api.copyToClip(feedInfo):
+                       # Translators: message presented when the information 
about an article of a feed is copied to the clipboard.
+                       ui.message(_("Copied to clipboard %s") % feedInfo)
+               else:
+                       ui.message(feedInfo)
+       # Translators: message presented in input mode.
+       script_readCurrentFeed.__doc__ = _("Announces the title of the current 
article. Pressed two times, copies title and related link to the clipboard.")
+
+       def script_readNextFeed(self, gesture):
+               self.getFirstFeed()
+               self.feed.next()
+               ui.message(self.feed.getArticleTitle())
+       # Translators: message presented in input mode.
+       script_readNextFeed.__doc__ = _("Announces the title of the next 
article.")
+
+       def script_readPriorFeed(self, gesture):
+               self.getFirstFeed()
+               self.feed.previous()
+               ui.message(self.feed.getArticleTitle())
+       # Translators: message presented in input mode.
+       script_readPriorFeed.__doc__ = _("Announces the title of the previous 
article.")
+
+       def script_reportLink(self, gesture):
+               self.getFirstFeed()
+               feedLink = self.feed.getArticleLink()
+               if scriptHandler.getLastScriptRepeatCount()==1:
+                       os.startfile(feedLink)
+               else:
+                       ui.message(feedLink)
+       # Translators: message presented in input mode.
+       script_reportLink.__doc__ = _("Announces article link, when pressed two 
times, opens related web page.")
+
+       def script_copyFeedInfo(self, gesture):
+               self.getFirstFeed()
+               feedInfo = 
u"{title}\r\n\r\n{address}".format(title=self.feed.getArticleTitle(), 
address=self.feed.getArticleLink())
+               if api.copyToClip(feedInfo):
+                       # Translators: message presented when the information 
about an article of a feed is copied to the clipboard.
+                       ui.message(_("Copied to clipboard %s") % feedInfo)
+
+       __gestures = {
+               "kb:control+shift+NVDA+8": "readFirstFeed",
+               "kb:control+shift+NVDA+i": "readCurrentFeed",
+               "kb:control+shift+NVDA+o": "readNextFeed",
+               "kb:control+shift+NVDA+u": "readPriorFeed",
+               "kb:control+shift+NVDA+space": "reportLink",
+               "kb:shift+NVDA+enter": "copyFeedInfo",
+       }
+

diff --git a/addon/globalPlugins/readFeeds/__init__.pyo 
b/addon/globalPlugins/readFeeds/__init__.pyo
new file mode 100644
index 0000000..5ee0829
Binary files /dev/null and b/addon/globalPlugins/readFeeds/__init__.pyo differ

diff --git a/addon/globalPlugins/readFeeds/personalFeeds/Blog SpazioAusili.txt 
b/addon/globalPlugins/readFeeds/personalFeeds/Blog SpazioAusili.txt
new file mode 100644
index 0000000..4e93538
--- /dev/null
+++ b/addon/globalPlugins/readFeeds/personalFeeds/Blog SpazioAusili.txt 
@@ -0,0 +1 @@
+http://www.spazioausili.net/rss/blog
\ No newline at end of file

diff --git a/addon/globalPlugins/readFeeds/personalFeeds/Forum SpazioAusili.txt 
b/addon/globalPlugins/readFeeds/personalFeeds/Forum SpazioAusili.txt
new file mode 100644
index 0000000..467e223
--- /dev/null
+++ b/addon/globalPlugins/readFeeds/personalFeeds/Forum SpazioAusili.txt        
@@ -0,0 +1 @@
+http://www.spazioausili.net/rss/forum
\ No newline at end of file

diff --git a/addon/globalPlugins/readFeeds/personalFeeds/W3C-news.txt 
b/addon/globalPlugins/readFeeds/personalFeeds/W3C-news.txt
new file mode 100644
index 0000000..d6d78cd
--- /dev/null
+++ b/addon/globalPlugins/readFeeds/personalFeeds/W3C-news.txt
@@ -0,0 +1 @@
+http://www.w3.org/News/atom.xml
\ No newline at end of file

diff --git a/addon/globalPlugins/readFeeds/personalFeeds/addons NVDA-project IT 
RSS.txt b/addon/globalPlugins/readFeeds/personalFeeds/addons NVDA-project IT 
RSS.txt
new file mode 100644
index 0000000..ce662ed
--- /dev/null
+++ b/addon/globalPlugins/readFeeds/personalFeeds/addons NVDA-project IT 
RSS.txt        
@@ -0,0 +1 @@
+http://addons.nvda-project.org/index.it.rss
\ No newline at end of file

diff --git a/addon/globalPlugins/readFeeds/personalFeeds/addressFile.txt 
b/addon/globalPlugins/readFeeds/personalFeeds/addressFile.txt
new file mode 100644
index 0000000..467e223
--- /dev/null
+++ b/addon/globalPlugins/readFeeds/personalFeeds/addressFile.txt
@@ -0,0 +1 @@
+http://www.spazioausili.net/rss/forum
\ No newline at end of file

diff --git a/addon/globalPlugins/readFeeds/skipTranslation.py 
b/addon/globalPlugins/readFeeds/skipTranslation.py
new file mode 100644
index 0000000..ea074a6
--- /dev/null
+++ b/addon/globalPlugins/readFeeds/skipTranslation.py
@@ -0,0 +1,9 @@
+# -*- coding: UTF-8 -*-
+# skipTranslation: Module to get translated texts from NVDA
+# Based on implementation made by Alberto Buffolino
+# https://github.com/nvaccess/nvda/issues/4652
+#Copyright (C) 2016 Noelia Ruiz Martínez
+# Released under GPL 2
+
+def translate(text):
+       return _(text)

diff --git a/addon/globalPlugins/readFeeds/skipTranslation.pyo 
b/addon/globalPlugins/readFeeds/skipTranslation.pyo
new file mode 100644
index 0000000..6bbe7c0
Binary files /dev/null and b/addon/globalPlugins/readFeeds/skipTranslation.pyo 
differ

diff --git a/addon/globalPlugins/xml2/__init__.py 
b/addon/globalPlugins/xml2/__init__.py
deleted file mode 100644
index f7dcb04..0000000
--- a/addon/globalPlugins/xml2/__init__.py
+++ /dev/null
@@ -1,48 +0,0 @@
-"""Core XML support for Python.
-
-This package contains four sub-packages:
-
-dom -- The W3C Document Object Model.  This supports DOM Level 1 +
-       Namespaces.
-
-parsers -- Python wrappers for XML parsers (currently only supports Expat).
-
-sax -- The Simple API for XML, developed by XML-Dev, led by David
-       Megginson and ported to Python by Lars Marius Garshol.  This
-       supports the SAX 2 API.
-
-etree -- The ElementTree XML library.  This is a subset of the full
-       ElementTree XML release.
-
-"""
-
-
-__all__ = ["dom", "parsers", "sax", "etree"]
-
-_MINIMUM_XMLPLUS_VERSION = (0, 8, 4)
-
-
-try:
-    import _xmlplus
-except ImportError:
-    pass
-else:
-    try:
-        v = _xmlplus.version_info
-    except AttributeError:
-        # _xmlplus is too old; ignore it
-        pass
-    else:
-        if v >= _MINIMUM_XMLPLUS_VERSION:
-            import sys
-            _xmlplus.__path__.extend(__path__)
-            sys.modules[__name__] = _xmlplus
-        else:
-            del v
-
-import globalPluginHandler
-
-class GlobalPlugin(globalPluginHandler.GlobalPlugin):
-
-       """Plugin class"""
-

diff --git a/addon/globalPlugins/xml2/dom/NodeFilter.py 
b/addon/globalPlugins/xml2/dom/NodeFilter.py
deleted file mode 100644
index fc05245..0000000
--- a/addon/globalPlugins/xml2/dom/NodeFilter.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# This is the Python mapping for interface NodeFilter from
-# DOM2-Traversal-Range. It contains only constants.
-
-class NodeFilter:
-    """
-    This is the DOM2 NodeFilter interface. It contains only constants.
-    """
-    FILTER_ACCEPT = 1
-    FILTER_REJECT = 2
-    FILTER_SKIP   = 3
-
-    SHOW_ALL                    = 0xFFFFFFFFL
-    SHOW_ELEMENT                = 0x00000001
-    SHOW_ATTRIBUTE              = 0x00000002
-    SHOW_TEXT                   = 0x00000004
-    SHOW_CDATA_SECTION          = 0x00000008
-    SHOW_ENTITY_REFERENCE       = 0x00000010
-    SHOW_ENTITY                 = 0x00000020
-    SHOW_PROCESSING_INSTRUCTION = 0x00000040
-    SHOW_COMMENT                = 0x00000080
-    SHOW_DOCUMENT               = 0x00000100
-    SHOW_DOCUMENT_TYPE          = 0x00000200
-    SHOW_DOCUMENT_FRAGMENT      = 0x00000400
-    SHOW_NOTATION               = 0x00000800
-
-    def acceptNode(self, node):
-        raise NotImplementedError

diff --git a/addon/globalPlugins/xml2/dom/__init__.py 
b/addon/globalPlugins/xml2/dom/__init__.py
deleted file mode 100644
index 6363d00..0000000
--- a/addon/globalPlugins/xml2/dom/__init__.py
+++ /dev/null
@@ -1,139 +0,0 @@
-"""W3C Document Object Model implementation for Python.
-
-The Python mapping of the Document Object Model is documented in the
-Python Library Reference in the section on the xml.dom package.
-
-This package contains the following modules:
-
-minidom -- A simple implementation of the Level 1 DOM with namespace
-           support added (based on the Level 2 specification) and other
-           minor Level 2 functionality.
-
-pulldom -- DOM builder supporting on-demand tree-building for selected
-           subtrees of the document.
-
-"""
-
-
-class Node:
-    """Class giving the NodeType constants."""
-
-    # DOM implementations may use this as a base class for their own
-    # Node implementations.  If they don't, the constants defined here
-    # should still be used as the canonical definitions as they match
-    # the values given in the W3C recommendation.  Client code can
-    # safely refer to these values in all tests of Node.nodeType
-    # values.
-
-    ELEMENT_NODE                = 1
-    ATTRIBUTE_NODE              = 2
-    TEXT_NODE                   = 3
-    CDATA_SECTION_NODE          = 4
-    ENTITY_REFERENCE_NODE       = 5
-    ENTITY_NODE                 = 6
-    PROCESSING_INSTRUCTION_NODE = 7
-    COMMENT_NODE                = 8
-    DOCUMENT_NODE               = 9
-    DOCUMENT_TYPE_NODE          = 10
-    DOCUMENT_FRAGMENT_NODE      = 11
-    NOTATION_NODE               = 12
-
-
-#ExceptionCode
-INDEX_SIZE_ERR                 = 1
-DOMSTRING_SIZE_ERR             = 2
-HIERARCHY_REQUEST_ERR          = 3
-WRONG_DOCUMENT_ERR             = 4
-INVALID_CHARACTER_ERR          = 5
-NO_DATA_ALLOWED_ERR            = 6
-NO_MODIFICATION_ALLOWED_ERR    = 7
-NOT_FOUND_ERR                  = 8
-NOT_SUPPORTED_ERR              = 9
-INUSE_ATTRIBUTE_ERR            = 10
-INVALID_STATE_ERR              = 11
-SYNTAX_ERR                     = 12
-INVALID_MODIFICATION_ERR       = 13
-NAMESPACE_ERR                  = 14
-INVALID_ACCESS_ERR             = 15
-VALIDATION_ERR                 = 16
-
-
-class DOMException(Exception):
-    """Abstract base class for DOM exceptions.
-    Exceptions with specific codes are specializations of this class."""
-
-    def __init__(self, *args, **kw):
-        if self.__class__ is DOMException:
-            raise RuntimeError(
-                "DOMException should not be instantiated directly")
-        Exception.__init__(self, *args, **kw)
-
-    def _get_code(self):
-        return self.code
-
-
-class IndexSizeErr(DOMException):
-    code = INDEX_SIZE_ERR
-
-class DomstringSizeErr(DOMException):
-    code = DOMSTRING_SIZE_ERR
-
-class HierarchyRequestErr(DOMException):
-    code = HIERARCHY_REQUEST_ERR
-
-class WrongDocumentErr(DOMException):
-    code = WRONG_DOCUMENT_ERR
-
-class InvalidCharacterErr(DOMException):
-    code = INVALID_CHARACTER_ERR
-
-class NoDataAllowedErr(DOMException):
-    code = NO_DATA_ALLOWED_ERR
-
-class NoModificationAllowedErr(DOMException):
-    code = NO_MODIFICATION_ALLOWED_ERR
-
-class NotFoundErr(DOMException):
-    code = NOT_FOUND_ERR
-
-class NotSupportedErr(DOMException):
-    code = NOT_SUPPORTED_ERR
-
-class InuseAttributeErr(DOMException):
-    code = INUSE_ATTRIBUTE_ERR
-
-class InvalidStateErr(DOMException):
-    code = INVALID_STATE_ERR
-
-class SyntaxErr(DOMException):
-    code = SYNTAX_ERR
-
-class InvalidModificationErr(DOMException):
-    code = INVALID_MODIFICATION_ERR
-
-class NamespaceErr(DOMException):
-    code = NAMESPACE_ERR
-
-class InvalidAccessErr(DOMException):
-    code = INVALID_ACCESS_ERR
-
-class ValidationErr(DOMException):
-    code = VALIDATION_ERR
-
-class UserDataHandler:
-    """Class giving the operation constants for UserDataHandler.handle()."""
-
-    # Based on DOM Level 3 (WD 9 April 2002)
-
-    NODE_CLONED   = 1
-    NODE_IMPORTED = 2
-    NODE_DELETED  = 3
-    NODE_RENAMED  = 4
-
-XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace";
-XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/";
-XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
-EMPTY_NAMESPACE = None
-EMPTY_PREFIX = None
-
-from domreg import getDOMImplementation,registerDOMImplementation

diff --git a/addon/globalPlugins/xml2/dom/domreg.py 
b/addon/globalPlugins/xml2/dom/domreg.py
deleted file mode 100644
index ea8b7c9..0000000
--- a/addon/globalPlugins/xml2/dom/domreg.py
+++ /dev/null
@@ -1,99 +0,0 @@
-"""Registration facilities for DOM. This module should not be used
-directly. Instead, the functions getDOMImplementation and
-registerDOMImplementation should be imported from xml.dom."""
-
-from xml2.dom.minicompat import *  # isinstance, StringTypes
-
-# This is a list of well-known implementations.  Well-known names
-# should be published by posting to xml-sig@xxxxxxxxxx, and are
-# subsequently recorded in this file.
-
-well_known_implementations = {
-    'minidom':'xml.dom.minidom',
-    '4DOM': 'xml.dom.DOMImplementation',
-    }
-
-# DOM implementations not officially registered should register
-# themselves with their
-
-registered = {}
-
-def registerDOMImplementation(name, factory):
-    """registerDOMImplementation(name, factory)
-
-    Register the factory function with the name. The factory function
-    should return an object which implements the DOMImplementation
-    interface. The factory function can either return the same object,
-    or a new one (e.g. if that implementation supports some
-    customization)."""
-
-    registered[name] = factory
-
-def _good_enough(dom, features):
-    "_good_enough(dom, features) -> Return 1 if the dom offers the features"
-    for f,v in features:
-        if not dom.hasFeature(f,v):
-            return 0
-    return 1
-
-def getDOMImplementation(name = None, features = ()):
-    """getDOMImplementation(name = None, features = ()) -> DOM implementation.
-
-    Return a suitable DOM implementation. The name is either
-    well-known, the module name of a DOM implementation, or None. If
-    it is not None, imports the corresponding module and returns
-    DOMImplementation object if the import succeeds.
-
-    If name is not given, consider the available implementations to
-    find one with the required feature set. If no implementation can
-    be found, raise an ImportError. The features list must be a sequence
-    of (feature, version) pairs which are passed to hasFeature."""
-
-    import os
-    creator = None
-    mod = well_known_implementations.get(name)
-    if mod:
-        mod = __import__(mod, {}, {}, ['getDOMImplementation'])
-        return mod.getDOMImplementation()
-    elif name:
-        return registered[name]()
-    elif "PYTHON_DOM" in os.environ:
-        return getDOMImplementation(name = os.environ["PYTHON_DOM"])
-
-    # User did not specify a name, try implementations in arbitrary
-    # order, returning the one that has the required features
-    if isinstance(features, StringTypes):
-        features = _parse_feature_string(features)
-    for creator in registered.values():
-        dom = creator()
-        if _good_enough(dom, features):
-            return dom
-
-    for creator in well_known_implementations.keys():
-        try:
-            dom = getDOMImplementation(name = creator)
-        except StandardError: # typically ImportError, or AttributeError
-            continue
-        if _good_enough(dom, features):
-            return dom
-
-    raise ImportError,"no suitable DOM implementation found"
-
-def _parse_feature_string(s):
-    features = []
-    parts = s.split()
-    i = 0
-    length = len(parts)
-    while i < length:
-        feature = parts[i]
-        if feature[0] in "0123456789":
-            raise ValueError, "bad feature name: %r" % (feature,)
-        i = i + 1
-        version = None
-        if i < length:
-            v = parts[i]
-            if v[0] in "0123456789":
-                i = i + 1
-                version = v
-        features.append((feature, version))
-    return tuple(features)

This diff is so big that we needed to truncate the remainder.

Repository URL: https://bitbucket.org/nvdaaddonteam/readfeeds/

--

This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.

Other related posts:

  • » commit/readFeeds: norrumar: Added dialogs to copy and restore feeds based on the portable creater dialog of NVDA, and reviewed placeMarkers add-on. Used skipTranslation to get translated messages of NVDA core, and restructured folders to avoid issues with modules external to the plugin. - commits-noreply