Changeset 84

Show
Ignore:
Timestamp:
Sat Jul 22 16:15:14 2006
Author:
mbutscher
Message:

* Bug fixed: Timestamp after each line from stderr

in ExceptionLogger?.py
* Wiki-wide search, exporters and printer now
respect open pages in editors, not only the database.
* Working support for multiple main frames in same
process and shared editing between frames.
Missing: Interprocess communication, some menu
points and optical details.
* Internal: DocPages? and WikiDataManager? are now
completely independend from PersonalWikiFrame
* Internal: some misc events renamed, properties and
message flow changed.
* Bug fixed: Opening help wiki failed

Files:

Legend:

Unmodified
Added
Removed
Modified
  • branches/mbutscher/TodoList.txt

    r81 r84  
    1   +++ Installation in non-Admin mode  
      1 I am using Wikipad on a network drive (that is located on my laptop)  
      2 and then use the data on different clients.  
      3 This works quite fine - but when the network connection drops (for  
      4 example WLAN outage) while Wikipad is running this causes Wikipad to  
      5 stay in an endless error loop. (A Modal Dialg telling me about the  
      6 Error and if you press OK the dialog comes up again and again...)  
    2 7  
      8 There is no other way than to kick the process off the task manager  
      9 and restart it.  
    3 10  
    4   +++ Handle all possible copy commands for preview  
    5   Ctrl-Insert and "Wiki Words" -> "Copy" do not work in preview of 1.6rc (check for 1.7beta)  
      11 *Details*:  
      12 Maybe option to internally restart the WikiData connection, see  
      13 "NetworkExcept.log"  
    6 14  
    7 15  
     
    18 27  
    19 28  
    20    
    21   +++ Quick search on toolbar  
    22   I would have liked a "quick search" field on the toolbar also...  
    23    
    24    
    25 29 +++ Bug: wiki-wide "Replace All" doesn't update page  
    26 30 > 5. just one I realized: I made a new WikiWord 'GranDolina' and replaced  
     
    33 37  
    34 38  
    35    
    36    
    37 39 +++ Internal: Reorganize handling of functional keys  
    38 40 Especially in the editor. The large if/else construction in OnKeyDown is bad.  
     
    40 42  
    41 43  
    42   +++ File dialog to create URLs  
    43   > 8. Misc: A dialog to easily create a link to a file would be handy. A  
    44   > window would pop up and the user could browse files (like when opening  
    45   > an existing wiki database). The user selects a file and Wikid Pad  
    46   > would insert a link to it at the current position of the cursor.  
      44 I wonder if it would be a small addition to have an option to minimize  
      45 Wikipad to tray instead of closing it?  
      46 There is already a "Minimize to Tray" option, but I like to have the  
      47 same behaviour on the WinClose Button.  
    47 48  
    48 49  
    49   +++ History of searches and opened wiki words  
    50   > * drop-down history of typed-in searches in Search Wiki? Same for Open  
    51   > Wiki? (I know there is Save Search)  
      50 It would also be cool it WikiPad would support a hotkey to expand it.  
    52 51  
    53 52  
      53 Or if there'd be an option to maximize it \[already open instance] when I launch it 2nd time (I could create a hot-key to launch it). \[low prio.]  
    54 54  
    55   +++ Remove brackets around noncamelcase wikiwords on preview/export  
    56   > By the way, I thought [these wikis] were  
    57   > exported as 'these wikis' in one of the previous versions, but I just  
    58   > realized it doesn't now...  
      55  
      56 > * drop-down history of typed-in searches in Search Wiki? Same for Open  
      57 > Wiki? (I know there is Save Search)  
    59 58  
    60 59  
     
    226 229  
    227 230  
      231 List nodes of words which are linked to but do not exist similar to list of parentless nodes  
      232  
      233  
    228 234 +++ icon bar button for bullets  
    229 235 > How about an icon bar button for bullets, like the ones for italics and bold?  
     
    259 266  
    260 267  
      268 In "open wiki word" and the multiple wiki word list dialogs  
      269  
      270  
      271 > * Could maybe the wikis in the Open Wiki, View Parents/Bookmarks etc  
      272 > windows appear like in the tree (with formatting and possibly with icons)?  
      273  
      274  
    261 275 +++ Selective escaping blocks  
    262 276 One small request: on the block escaping is there is way to block  
  • branches/mbutscher/work/ExceptionLogger.py

    r83 r84  
    11 11             f = open(os.path.join(EL._exceptionDestDir, "WikidPad_Error.log"), "a")  
    12 12             try:  
    13                   if not EL._exceptionOccurred:  
      13                 if not EL._timestampPrinted:  
    13 13                     # (Only write for first exception in session) This isn't an exception  
    14 14                     f.write(EL._exceptionSessionTimeStamp)  
    15                       ## _exceptionOccurred = True  
      15                     EL._timestampPrinted = True  
    15 15                 sys.stdout.write(data)  
    16 16                 f.write(data)  
     
    52 52         f = open(os.path.join(EL._exceptionDestDir, "WikidPad_Error.log"), "a")  
    53 53         try:  
    54               if not EL._exceptionOccurred:  
      54             if not EL._timestampPrinted:  
    54 54                 # Only write for first exception in session  
    55                   f.write(EL._exceptionSessionTimeStamp)  
    56                   EL._exceptionOccurred = True  
      55                 f.write(EL._exceptionSessionTimeStamp)  
      56                 EL._timestampPrinted = True  
      57              
      58             EL._exceptionOccurred = True  
    57 59             EL.traceback.print_exception(typ, value, trace, file=f)  
    58 60             EL.traceback.print_exception(typ, value, trace, file=sys.stdout)  
     
    76 78                     "' Session start: %Y-%m-%d %H:%M:%S\n")  
    77 79     EL._exceptionOccurred = False  
      80     EL._timestampPrinted = False  
    78 81      
    79 82      
  • branches/mbutscher/work/WikidPadStarter.py

    r83 r84  
    4 4 os.stat_float_times(True)  
    5 5  
    6   VERSION_STRING = "wikidPad 1.7beta7"  
      6 VERSION_STRING = "wikidPad 1.7beta8"  
    6 6  
    7 7 if not hasattr(sys, 'frozen'):  
  • branches/mbutscher/work/lib/pwiki/WikiTxtCtrl.py

    r83 r84  
    154 154  
    155 155         self.wikiPageListener = KeyFunctionSink((  
    156                   ("wiki page updated", self.onWikiPageUpdated),   # fired by a WikiPage  
      156                 ("updated wiki page", self.onWikiPageUpdated),   # fired by a WikiPage  
    156 156         ))  
    157 157  
     
    399 399             miscevt.removeListener(self.wikiPageListener)  
    400 400              
      401             self.SetDocPointer(None)  
      402             self.SetCodePage(wxSTC_CP_UTF8)  
      403  
    401 404             self.loadedDocPage.removeTxtEditor(self)  
    402 405             self.loadedDocPage = None  
     
    411 414          
    412 415         self.loadedDocPage = funcPage  
      416          
    413 417         if self.loadedDocPage is None:  
    414               self.SetText(u"")  
    415 418             return  # TODO How to handle?  
    416 419  
    417           miscevt = self.loadedDocPage.getMiscEvent()  
    418           miscevt.addListener(self.wikiPageListener)  
    419           self.loadedDocPage.addTxtEditor(self)  
    420    
    421           try:  
    422               content = self.loadedDocPage.getContent()  
    423           except WikiFileNotFoundException, e:  
    424               assert 0   # TODO  
    425    
    426 420         globalProps = wikiDataManager.getWikiData().getGlobalProperties()  
    427 421         # get the font that should be used in the editor  
     
    439 433 #         self.pWiki.fireMiscEventProps(p2)  # TODO Remove this hack  
    440 434  
    441           # now fill the text into the editor  
    442           self.SetText(content)  
      435         miscevt = self.loadedDocPage.getMiscEvent()  
      436         miscevt.addListener(self.wikiPageListener)  
      437  
      438         otherEditor = self.loadedDocPage.getTxtEditor()  
      439         if otherEditor is not None:  
      440             # Another editor contains already this page, so share its  
      441             # Scintilla document object for synchronized editing  
      442             self.SetDocPointer(otherEditor.GetDocPointer())  
      443             self.SetCodePage(wxSTC_CP_UTF8)  
      444         else:  
      445             # Load content  
      446             try:  
      447                 content = self.loadedDocPage.getLiveText()  
      448             except WikiFileNotFoundException, e:  
      449                 assert 0   # TODO  
      450  
      451             # now fill the text into the editor  
      452             self.SetText(content)  
      453  
      454         self.loadedDocPage.addTxtEditor(self)  
    443 455  
    444 456  
     
    454 466  
    455 467         if self.loadedDocPage is None:  
    456               self.SetText(u"")  
    457 468             return  # TODO How to handle?  
    458 469  
    459           miscevt = self.loadedDocPage.getMiscEvent()  
    460           miscevt.addListener(self.wikiPageListener)  
    461           self.loadedDocPage.addTxtEditor(self)  
    462    
    463           content = self.loadedDocPage.getContent()  
    464    
    465 470         # get the font that should be used in the editor  
    466 471         font = self.loadedDocPage.getPropertyOrGlobal("font",  
     
    473 478             self.SetStyles(faces)  
    474 479             self.lastEditorFont = font  
    475                
      480  
      481         miscevt = self.loadedDocPage.getMiscEvent()  
      482         miscevt.addListener(self.wikiPageListener)  
      483  
      484  
      485         otherEditor = self.loadedDocPage.getTxtEditor()  
      486         if otherEditor is not None:  
      487             # Another editor contains already this page, so share its  
      488             # Scintilla document object for synchronized editing  
      489             self.SetDocPointer(otherEditor.GetDocPointer())  
      490             self.SetCodePage(wxSTC_CP_UTF8)  
      491         else:  
      492             # Load content  
      493             try:  
      494                 content = self.loadedDocPage.getLiveText()  
      495             except WikiFileNotFoundException, e:  
      496                 assert 0   # TODO  
      497  
      498             # now fill the text into the editor  
      499             self.setTextAgaUpdated(content)  
      500  
      501         self.loadedDocPage.addTxtEditor(self)  
      502  
    476 503         if evtprops is None:  
    477 504             evtprops = {}  
     
    480 507         self.pWiki.fireMiscEventProps(p2)  # TODO Remove this hack  
    481 508  
    482           # now fill the text into the editor  
    483           self.setTextAgaUpdated(content)  
    484    
    485 509         self.pageType = self.loadedDocPage.getProperties().get(u"pagetype",  
    486 510                 [u"normal"])[-1]  
     
    975 999                             importPage = self.pWiki.getWikiDataManager().\  
    976 1000                                     getWikiPage(script)  
    977                               content = importPage.getContent()  
      1001                             content = importPage.getLiveText()  
    977 1001                             text += "\n" + content  
    978 1002                         except:  
     
    988 1012                         importPage = self.pWiki.getWikiDataManager().\  
    989 1013                                 getWikiPage(globscript)  
    990                           content = importPage.getContent()  
      1014                         content = importPage.getLiveText()  
    990 1014                         text += "\n" + content  
    991 1015                     except:  
  • branches/mbutscher/work/lib/pwiki/Exporters.py

    r83 r84  
    242 242  
    243 243             try:  
    244                   content = wikiPage.getContent()  
      244                 content = wikiPage.getLiveText()  
    244 244                 formatDetails = wikiPage.getFormatDetails()  
    245 245                 links = {}  # TODO Why links to all (even not exported) children?  
     
    331 331  
    332 332             try:  
    333                   content = wikiPage.getContent()  
      333                 content = wikiPage.getLiveText()  
    333 333                 formatDetails = wikiPage.getFormatDetails()  
    334 334                 links = {}  
     
    374 374              
    375 375             wikiPage = self.wikiDataManager.getWikiPage(word)  
    376               content = wikiPage.getContent()  
      376             content = wikiPage.getLiveText()  
    376 376             formatDetails = wikiPage.getFormatDetails()         
    377 377             fp.write(self.exportContentToHtmlString(word, content,  
     
    1159 1159         for word in self.wordList:  
    1160 1160             try:  
    1161                   content = self.wikiDataManager.getWikiData().getContent(word)  
    1162                   modified = self.wikiDataManager.getWikiData().getTimestamps(word)[0]  
      1161                 wikiPage = self.wikiDataManager.getWikiPage(word)  
      1162                 content = wikiPage.getLiveText()  
      1163                 modified = wikiPage.getTimestamps()[0]  
      1164 #                 content = self.wikiDataManager.getWikiData().getContent(word)  
      1165 #                 modified = self.wikiDataManager.getWikiData().getTimestamps(word)[0]  
    1163 1166             except:  
    1164 1167                 traceback.print_exc()  
     
    1274 1277         searchOp.listWikiPagesOp = wpo  
    1275 1278          
    1276           foundPages = self.mainControl.getWikiData().search(searchOp)  
      1279         foundPages = self.mainControl.getWikiDocument().searchWiki(searchOp)  
    1276 1279          
    1277 1280         return len(foundPages) == 0  
     
    1360 1363                 self.exportFile.write("%s\n" % word)  
    1361 1364                 page = self.wikiDataManager.getWikiPage(word)  
    1362                   self.exportFile.write(page.getContent())  
    1363                    
      1365                 self.exportFile.write(page.getLiveText())  
      1366  
    1364 1367                 if sepCount > 0:  
    1365 1368                     self.exportFile.write("\n%s\n" % self.separator)  
  • branches/mbutscher/work/lib/pwiki/PageHistory.py

    r83 r84  
    14 14         self.pWiki.getMiscEvent().addListener(KeyFunctionSink((  
    15 15                 ("loading current page", self.onLoadingCurrentWikiPage),  
    16                   ("deleted page", self.onDeletedWikiPage),  
    17                   ("renamed page", self.onRenamedWikiPage),  
      16                 ("deleted wiki page", self.onDeletedWikiPage),  
      17                 ("renamed wiki page", self.onRenamedWikiPage),  
    18 18                 ("opened wiki", self.onOpenedWiki)  
    19 19         )), False)  
     
    54 54         """  
    55 55         newhist = []  
    56           word = miscevt.get("wikiWord") # self.pWiki.getCurrentWikiWord()  
      56         word = miscevt.get("wikiPage").getWikiWord() # self.pWiki.getCurrentWikiWord()  
    56 56          
    57 57         # print "onDeletedWikiPage1",  self.pos, repr(self.history)  
     
    73 73         Rename word in history  
    74 74         """  
    75           oldWord = miscevt.get("oldWord")  
      75         oldWord = miscevt.get("wikiPage").getWikiWord()  
    75 75         newWord = miscevt.get("newWord")  
    76 76          
  • branches/mbutscher/work/lib/pwiki/wikidata/WikiDataManager.py

    r83 r84  
    1 1 from weakref import WeakValueDictionary  
    2   import os, os.path  
      2 import os, os.path, sets  
    2 2 from threading import RLock  
    3 3  
     
    20 20 _openDocuments = {}  # Dictionary {<path to data dir>: <WikiDataManager>}  
    21 21  
      22  
      23 _globalFuncPages = WeakValueDictionary()  # weak dictionary  
      24         # {<funcTag starting with "global/">: <funcPage>}  
      25  
    22 26 def isDbHandlerAvailable(dbtype):  
    23 27     wikiDataFactory, createWikiDbFunc = DbBackendUtils.getHandler(dbtype)  
     
    47 51  
    48 52  
    49   # def openWikiDocument(pWiki, dbtype, dataDir, fileStorDir, wikiSyntax):   # createWikiDataManager  
    50   def openWikiDocument(pWiki, wikiConfigFilename, wikiSyntax, dbtype=None):  
      53 def openWikiDocument(wikiConfigFilename, wikiSyntax, dbtype=None):  
    51 54     """  
    52 55     Create a new instance of the WikiDataManager or return an already existing  
    53 56     one  
    54       pWiki -- instance of PersonalWikiFrame  
    55 57     dbtype -- internal name of database type  
    56 58     wikiName -- Name of the wiki to create  
     
    62 64     wdm = _openDocuments.get(wikiConfigFilename)  
    63 65     if wdm is not None:  
    64           if dbtype != wdm.getDbtype():  
      66         if dbtype is not None and dbtype != wdm.getDbtype():  
    64 66             # Same database can't be opened twice with different db handlers  
    65 67             raise WrongDbHandlerException(("Database is already in use "  
     
    71 73         return wdm  
    72 74  
    73       wdm = WikiDataManager(pWiki, wikiConfigFilename, wikiSyntax, dbtype)  
      75     wdm = WikiDataManager(wikiConfigFilename, wikiSyntax, dbtype)  
    73 75      
    74 76     _openDocuments[wikiConfigFilename] = wdm  
     
    134 136     """  
    135 137  
    136       # TODO Remove dependency to mainControl !!!  
    137    
    138       def __init__(self, mainControl, wikiConfigFilename, wikiSyntax, dbtype):  #  dataDir, fileStorDir, dbtype, ):  
    139           self.mainControl = mainControl  
    140    
      138     def __init__(self, wikiConfigFilename, wikiSyntax, dbtype):  #  dataDir, fileStorDir, dbtype, ):  
    141 139         wikiConfig = createWikiConfiguration()  
    142 140          
     
    261 259         self.wikiData = WikiDataSynchronizedProxy(self.baseWikiData)  
    262 260         self.wikiPageDict = WeakValueDictionary()  
      261         self.funcPageDict = WeakValueDictionary()  
    263 262  
    264 263 #         self.fileStorage = FileStorage.FileStorage(self,  
     
    326 325  
    327 326     def getFormatting(self):  
    328           return self.formatting  # self.mainControl.getFormatting()  
      327         return self.formatting  
    328 327          
    329 328     def getWikiName(self):  
     
    359 358             if wikiWord == realWikiWord:  
    360 359                 # no alias  
    361                   value = WikiPage(self, self.mainControl, wikiWord)  
      360                 value = WikiPage(self, wikiWord)  
    361 360             else:  
    362                   realpage = WikiPage(self, self.mainControl, realWikiWord)  
      361                 realpage = WikiPage(self, realWikiWord)  
    362 361                 value = AliasWikiPage(self, wikiWord, realpage)  
    363 362  
    364 363             self.wikiPageDict[wikiWord] = value  
    365 364              
    366           value.getMiscEvent().addListener(self)  
      365             value.getMiscEvent().addListener(self)  
    366 365  
    367 366         return value  
     
    383 382         Retrieve a functional page  
    384 383         """  
    385           # TODO Ensure uniqueness as for wiki pages  
    386           value = FunctionalPage(self.mainControl, self, funcTag)  
    387           value.getMiscEvent().addListener(self)  
      384         global _globalFuncPages  
      385         if funcTag.startswith("global/"):  
      386             cacheDict = _globalFuncPages  
      387         else:  
      388             cacheDict = self.funcPageDict  
      389  
      390         value = cacheDict.get(funcTag)  
      391         if value is None:  
      392             value = FunctionalPage(self, funcTag)  
      393             if not value.getMiscEvent().hasListener(self):  
      394                 value.getMiscEvent().addListener(self)  
      395             cacheDict[funcTag] = value  
      396  
    388 397         return value  
    389 398  
     
    429 438         """  
    430 439         global _openDocuments  
      440          
      441         oldWikiPage = self.getWikiPage(wikiWord)  
    431 442  
    432 443         self.getWikiData().renameWord(wikiWord, toWikiWord)  
     
    455 466             searchOp.searchStr = wikiWord  
    456 467      
    457               for resultWord in self.getWikiData().search(searchOp):  
      468             for resultWord in self.searchWiki(searchOp):  
    457 468                 page = self.getWikiPage(resultWord)  
    458                   content = page.getContent()  
      469                 content = page.getLiveText()  
    458 469                 content = content.replace(wikiWord, toWikiWord)  
    459                   page.save(content)  
    460                   page.update(content, False)  # TODO AGA processing  
      470 #                 page.save(content)  
      471 #                 page.update(content, False)  # TODO AGA processing  
      472                 page.replaceLiveText(content)  
    461 473  
    462 474         # if the root was renamed we have a little more to do  
     
    487 499             _openDocuments[renamedConfigPath] = self  
    488 500  
      501         oldWikiPage.informRenamedWikiPage(toWikiWord)  
      502  
      503  
      504     def searchWiki(self, sarOp, applyOrdering=True):  # TODO Threadholder  
      505         """  
      506         Search all wiki pages using the SearchAndReplaceOperation sarOp and  
      507         return list of all page names that match the search criteria.  
      508         If applyOrdering is True, the ordering of the sarOp is applied before  
      509         returning the list.  
      510         """  
      511         wikiData = self.getWikiData()  
      512         sarOp.beginWikiSearch(wikiData)  
      513         try:  
      514             # First search currently cached pages  
      515             exclusionSet = sets.Set()  
      516             preResultSet = sets.Set()  
      517              
      518             for k in self.wikiPageDict.keys():  
      519                 wikiPage = self.wikiPageDict.get(k)  
      520                 if wikiPage is None:  
      521                     continue  
      522                      
      523                 text = wikiPage.getLiveText()  
      524                 if sarOp.testWikiPage(k, text) == True:  
      525                     preResultSet.add(k)  
      526                  
      527                 exclusionSet.add(k)  
      528  
      529             # Now search database  
      530             resultSet = self.getWikiData().search(sarOp, exclusionSet)  
      531             resultSet |= preResultSet  
      532             if applyOrdering:  
      533                 result = sarOp.applyOrdering(resultSet)  
      534             else:  
      535                 result = list(resultSet)  
      536  
      537         finally:  
      538             sarOp.endWikiSearch()  
      539              
      540         return result  
      541  
    489 542  
    490 543     def miscEventHappened(self, miscevt):  
     
    496 549                 self.getFormatting().rebuildFormatting(miscevt)  
    497 550         else:  
    498               if miscevt.has_key("wiki page updated"):  
    499                   # This was send from a WikiPage object, send it again  
      551             # These messages come from (classes derived from) DocPages,  
      552             # they are mainly relayed  
      553  
      554             if miscevt.has_key_in(("updated wiki page", "deleted wiki page",  
      555                     "renamed wiki page")):  
    500 556                 props = miscevt.getProps().copy()  
    501 557                 props["wikiPage"] = miscevt.getSource()  
    502 558                 self.fireMiscEventProps(props)  
      559             elif miscevt.has_key("updated func page"):  
      560                 # This was send from a FuncPage object, send it again  
      561                 # The event also contains more specific information  
      562                 # handled by PesonalWikiFrame  
      563  
      564                 props = miscevt.getProps().copy()  
      565                 props["funcPage"] = miscevt.getSource()  
      566                 self.fireMiscEventProps(props)  
      567  
      568                  
    503 569              
    504 570          
  • branches/mbutscher/work/lib/pwiki/wikidata/original_sqlite/WikiData.py

    r83 r84  
    814 814     # ---------- Searching pages ----------  
    815 815  
    816    
    817    
    818       def search(self, sarOp, applyOrdering=True):  # TODO Threadholder for all !!!!!  
      816     def search(self, sarOp, exclusionSet):  # TODO Threadholder for all !!!!!  
    819 817         """  
    820 818         Search all content using the SearchAndReplaceOperation sarOp and  
    821           return list of all page names match the search criteria.  
    822           This version uses sqlite user-defined functions.  
      819         return set of all page names that match the search criteria.  
      820         sarOp.beginWikiSearch() must be called before calling this function,  
      821         sarOp.endWikiSearch() must be called after calling this function.  
      822          
      823         exclusionSet -- set of wiki words for which their pages shouldn't be  
      824         searched here and which must not be part of the result set  
    823 825         """  
    824           results = []  
    825           sarOp.beginWikiSearch(self)  
    826           try:  
    827               for word in self.getAllDefinedWikiPageNames():  #glob.glob(join(self.dataDir, '*.wiki')):  
    828                   try:  
    829                       fileContents = self.getContent(word)  
    830                   except WikiFileNotFoundException:  
    831                       # some error in cache (should not happen)  
    832                       continue  
    833    
    834                   if sarOp.testWikiPage(word, fileContents) == True:  
    835                       results.append(word)  
    836               if applyOrdering:  
    837                   results = sarOp.applyOrdering(results)  
      826         result = sets.Set()  
      827         for word in self.getAllDefinedWikiPageNames():  #glob.glob(join(self.dataDir, '*.wiki')):  
      828             if word in exclusionSet:  
      829                 continue  
      830             try:  
      831                 fileContents = self.getContent(word)  
      832             except WikiFileNotFoundException:  
      833                 # some error in cache (should not happen)  
      834                 continue  
    838 835  
    839           finally:  
    840               sarOp.endWikiSearch()  
      836             if sarOp.testWikiPage(word, fileContents) == True:  
      837                 result.add(word)  
    841 838  
    842           return results  
      839         return result  
    842 839  
    843 840  
  • branches/mbutscher/work/lib/pwiki/wikidata/compact_sqlite/WikiData.py

    r83 r84  
    874 874  
    875 875  
    876   &nbs