Codebase list anki / 8da38a8
New upstream version 2.1.0+dfsg~rc2 Julian Gilbey 5 years ago
84 changed file(s) with 404 addition(s) and 744 deletion(s). Raw diff Collapse all Expand all
2020
2121 $ pip3 install -r requirements.txt
2222
23 You will also need PyQt development tools (specifically pyrcc5 and pyuic5).
24 These are often contained in a separate package on Linux, such as
25 'pyqt5-dev-tools' on Debian/Ubuntu.
23 If you're on a Linux distribution that packages PyQt 5.9 then you can use the
24 distro's packages. Make sure you install the development tools (eg
25 pyqt5-dev-tools) as well.
26
27 If you're on another platform or your distro has the wrong Qt version, you
28 can install PyQt with pip:
29
30 $ pip3 install sip pyqt5==5.9
2631
2732 To use the development version:
2833
5156
5257 If you get any errors, please make sure you don't have an older version of
5358 Anki installed in a system location.
59
60 To run the unit tests, you will need to install nose from your distro, or
61 with pip:
62
63 $ pip3 install nose
5464
5565 Before contributing code, please read README.contributing.
5666
99 if sys.getfilesystemencoding().lower() in ("ascii", "ansi_x3.4-1968"):
1010 raise Exception("Anki requires a UTF-8 locale.")
1111
12 version="2.1.0beta43" # build scripts grep this line, so preserve formatting
12 version="2.1.0rc2" # build scripts grep this line, so preserve formatting
1313 from anki.storage import Collection
1414 __all__ = ["Collection"]
4949 SCHEMA_VERSION = 11
5050 SYNC_ZIP_SIZE = int(2.5*1024*1024)
5151 SYNC_ZIP_COUNT = 25
52 SYNC_BASE = "https://sync.ankiweb.net/"
53 SYNC_MEDIA_BASE = "https://sync.ankiweb.net/msync/"
52 SYNC_BASE = "https://sync%s.ankiweb.net/"
5453 SYNC_VER = 9
5554
5655 HELP_SITE="http://ankisrs.net/docs/manual.html"
227227 parts = parts[:-1]
228228 return "::".join(parts)
229229 for deck in decks:
230 # if we've already seen the exact same deck name, remove the
230 # if we've already seen the exact same deck name, rename the
231231 # invalid duplicate and reload
232232 if deck['name'] in lims:
233 self.col.decks.rem(deck['id'], cardsToo=False, childrenToo=True)
233 deck['name'] += "1"
234 self.col.decks.save(deck)
234235 return self.deckDueList()
235236 p = parent(deck['name'])
236237 # new
237238 nlim = self._deckNewLimitSingle(deck)
238239 if p:
239240 if p not in lims:
240 # if parent was missing, this deck is invalid, and we
241 # need to reload the deck list
242 self.col.decks.rem(deck['id'], cardsToo=False, childrenToo=True)
241 # if parent was missing, this deck is invalid
242 deck['name'] = "recovered"
243 self.col.decks.save(deck)
243244 return self.deckDueList()
244245 nlim = min(nlim, lims[p][0])
245246 new = self._newForDeck(deck['id'], nlim)
217217 return "::".join(parts)
218218 childMap = self.col.decks.childMap()
219219 for deck in decks:
220 # if we've already seen the exact same deck name, remove the
220 # if we've already seen the exact same deck name, rename the
221221 # invalid duplicate and reload
222222 if deck['name'] in lims:
223 self.col.decks.rem(deck['id'], cardsToo=False, childrenToo=True)
223 deck['name'] += "1"
224 self.col.decks.save(deck)
224225 return self.deckDueList()
225226 p = parent(deck['name'])
226227 # new
227228 nlim = self._deckNewLimitSingle(deck)
228229 if p:
229230 if p not in lims:
230 # if parent was missing, this deck is invalid, and we
231 # need to reload the deck list
232 self.col.decks.rem(deck['id'], cardsToo=False, childrenToo=True)
231 # if parent was missing, this deck is invalid
232 deck['name'] = "recovered"
233 self.col.decks.save(deck)
233234 return self.deckDueList()
234235 nlim = min(nlim, lims[p][0])
235236 new = self._newForDeck(deck['id'], nlim)
583584 if delay is None:
584585 delay = self._delayForGrade(conf, card.left)
585586
586 if card.due < time.time():
587 # not collapsed; add some randomness
588 delay *= random.uniform(1, 1.25)
589587 card.due = int(time.time() + delay)
590588 # due today?
591589 if card.due < self.dayCutoff:
590 # add some randomness, up to 5 minutes or 25%
591 maxExtra = min(300, int(delay*0.25))
592 fuzz = random.randrange(0, maxExtra)
593 card.due = min(self.dayCutoff-1, card.due + fuzz)
592594 card.queue = 1
593595 if card.due < (intTime() + self.col.conf['collapseTime']):
594596 self.lrnCount += 1
854856 return delay
855857
856858 def _lapseIvl(self, card, conf):
857 due = card.odue or card.due
858 elapsed = card.ivl - (due - self.today)
859 ivl = min(elapsed, card.ivl)
860 ivl = max(1, conf['minInt'], ivl*conf['mult'])
859 ivl = max(1, conf['minInt'], card.ivl*conf['mult'])
861860 return ivl
862861
863862 def _rescheduleRev(self, card, ease, early):
15861585 # remove review cards from relearning
15871586 self.col.db.execute("""
15881587 update cards set
1589 due = odue, queue = 2, mod = %d, usn = %d, odue = 0
1588 due = odue, queue = 2, type = 2, mod = %d, usn = %d, odue = 0
15901589 where queue in (1,3) and type in (2, 3)
15911590 """ % (intTime(), self.col.usn()))
15921591 # remove new cards from learning
101101 super().__init__(window_id=None, debug=False)
102102
103103 def queueFile(self, file):
104 runHook("mpvWillPlay")
104 runHook("mpvWillPlay", file)
105105
106106 path = os.path.join(os.getcwd(), file)
107107 self.command("loadfile", path, "append-play")
5353 rts = meta['ts']
5454 self.rmod = meta['mod']
5555 self.maxUsn = meta['usn']
56 # this is a temporary measure to address the problem of users
57 # forgetting which email address they've used - it will be removed
58 # when enough time has passed
5956 self.uname = meta.get("uname", "")
57 self.hostNum = meta.get("hostNum")
6058 meta = self.meta()
6159 self.col.log("lmeta", meta)
6260 self.lmod = meta['mod']
7775 if not self.col.basicCheck():
7876 self.col.log("basic check")
7977 return "basicCheckFailed"
80 # step 2: deletions
78 # step 2: startup and deletions
8179 runHook("sync", "meta")
82 lrem = self.removed()
83 rrem = self.server.start(
84 minUsn=self.minUsn, lnewer=self.lnewer, graves=lrem)
80 rrem = self.server.start(minUsn=self.minUsn, lnewer=self.lnewer)
81
82 # apply deletions to server
83 lgraves = self.removed()
84 while lgraves:
85 gchunk, lgraves = self._gravesChunk(lgraves)
86 self.server.applyGraves(chunk=gchunk)
87
88 # then apply server deletions here
8589 self.remove(rrem)
90
8691 # ...and small objects
8792 lchg = self.changes()
8893 rchg = self.server.applyChanges(changes=lchg)
121126 self.finish(mod)
122127 return "success"
123128
129 def _gravesChunk(self, graves):
130 lim = 250
131 chunk = dict(notes=[], cards=[], decks=[])
132 for cat in "notes", "cards", "decks":
133 if lim and graves[cat]:
134 chunk[cat] = graves[cat][:lim]
135 graves[cat] = graves[cat][lim:]
136 lim -= len(chunk[cat])
137
138 # anything remaining?
139 if graves['notes'] or graves['cards'] or graves['decks']:
140 return chunk, graves
141 return chunk, None
142
124143 def meta(self):
125144 return dict(
126145 mod=self.col.mod,
141160 d['conf'] = self.getConf()
142161 d['crt'] = self.col.crt
143162 return d
144
145 def applyChanges(self, changes):
146 self.rchg = changes
147 lchg = self.changes()
148 # merge our side before returning
149 self.mergeChanges(lchg, self.rchg)
150 return lchg
151163
152164 def mergeChanges(self, lchg, rchg):
153165 # then the other objects
176188 return "tag had usn = -1"
177189 found = False
178190 for m in self.col.models.all():
179 if self.col.server:
180 # the web upgrade was mistakenly setting usn
181 if m['usn'] < 0:
182 m['usn'] = 0
183 found = True
184 else:
185 if m['usn'] == -1:
186 return "model had usn = -1"
191 if m['usn'] == -1:
192 return "model had usn = -1"
187193 if found:
188194 self.col.models.save()
189195 self.col.sched.reset()
201207 len(self.col.decks.allConf()),
202208 ]
203209
204 def sanityCheck2(self, client):
205 server = self.sanityCheck()
206 if client != server:
207 return dict(status="bad", c=client, s=server)
208 return dict(status="ok")
209
210210 def usnLim(self):
211 if self.col.server:
212 return "usn >= %d" % self.minUsn
213 else:
214 return "usn = -1"
211 return "usn = -1"
215212
216213 def finish(self, mod=None):
217 if not mod:
218 # server side; we decide new mod time
219 mod = intTime(1000)
220214 self.col.ls = mod
221215 self.col._usn = self.maxUsn + 1
222216 # ensure we save the mod time even if no changes made
261255 # table is empty
262256 self.tablesLeft.pop(0)
263257 self.cursor = None
264 # if we're the client, mark the objects as having been sent
265 if not self.col.server:
266 self.col.db.execute(
267 "update %s set usn=? where usn=-1"%curTable,
268 self.maxUsn)
258 # mark the objects as having been sent
259 self.col.db.execute(
260 "update %s set usn=? where usn=-1"%curTable,
261 self.maxUsn)
269262 buf[curTable] = rows
270263 lim -= fetched
271264 if not self.tablesLeft:
287280 cards = []
288281 notes = []
289282 decks = []
290 if self.col.server:
291 curs = self.col.db.execute(
292 "select oid, type from graves where usn >= ?", self.minUsn)
293 else:
294 curs = self.col.db.execute(
295 "select oid, type from graves where usn = -1")
283
284 curs = self.col.db.execute(
285 "select oid, type from graves where usn = -1")
286
296287 for oid, type in curs:
297288 if type == REM_CARD:
298289 cards.append(oid)
300291 notes.append(oid)
301292 else:
302293 decks.append(oid)
303 if not self.col.server:
304 self.col.db.execute("update graves set usn=? where usn=-1",
305 self.maxUsn)
294
295 self.col.db.execute("update graves set usn=? where usn=-1",
296 self.maxUsn)
297
306298 return dict(cards=cards, notes=notes, decks=decks)
307
308 def start(self, minUsn, lnewer, graves):
309 self.maxUsn = self.col._usn
310 self.minUsn = minUsn
311 self.lnewer = not lnewer
312 lgraves = self.removed()
313 self.remove(graves)
314 return lgraves
315299
316300 def remove(self, graves):
317301 # pretend to be the server so we don't set usn = -1
318 wasServer = self.col.server
319302 self.col.server = True
303
320304 # notes first, so we don't end up with duplicate graves
321305 self.col._remNotes(graves['notes'])
322306 # then cards
324308 # and decks
325309 for oid in graves['decks']:
326310 self.col.decks.rem(oid, childrenToo=False)
327 self.col.server = wasServer
311
312 self.col.server = False
328313
329314 # Models
330315 ##########################################################################
331316
332317 def getModels(self):
333 if self.col.server:
334 return [m for m in self.col.models.all() if m['usn'] >= self.minUsn]
335 else:
336 mods = [m for m in self.col.models.all() if m['usn'] == -1]
337 for m in mods:
338 m['usn'] = self.maxUsn
339 self.col.models.save()
340 return mods
318 mods = [m for m in self.col.models.all() if m['usn'] == -1]
319 for m in mods:
320 m['usn'] = self.maxUsn
321 self.col.models.save()
322 return mods
341323
342324 def mergeModels(self, rchg):
343325 for r in rchg:
350332 ##########################################################################
351333
352334 def getDecks(self):
353 if self.col.server:
354 return [
355 [g for g in self.col.decks.all() if g['usn'] >= self.minUsn],
356 [g for g in self.col.decks.allConf() if g['usn'] >= self.minUsn]
357 ]
358 else:
359 decks = [g for g in self.col.decks.all() if g['usn'] == -1]
360 for g in decks:
361 g['usn'] = self.maxUsn
362 dconf = [g for g in self.col.decks.allConf() if g['usn'] == -1]
363 for g in dconf:
364 g['usn'] = self.maxUsn
365 self.col.decks.save()
366 return [decks, dconf]
335 decks = [g for g in self.col.decks.all() if g['usn'] == -1]
336 for g in decks:
337 g['usn'] = self.maxUsn
338 dconf = [g for g in self.col.decks.allConf() if g['usn'] == -1]
339 for g in dconf:
340 g['usn'] = self.maxUsn
341 self.col.decks.save()
342 return [decks, dconf]
367343
368344 def mergeDecks(self, rchg):
369345 for r in rchg[0]:
388364 ##########################################################################
389365
390366 def getTags(self):
391 if self.col.server:
392 return [t for t, usn in self.col.tags.allItems()
393 if usn >= self.minUsn]
394 else:
395 tags = []
396 for t, usn in self.col.tags.allItems():
397 if usn == -1:
398 self.col.tags.tags[t] = self.maxUsn
399 tags.append(t)
400 self.col.tags.save()
401 return tags
367 tags = []
368 for t, usn in self.col.tags.allItems():
369 if usn == -1:
370 self.col.tags.tags[t] = self.maxUsn
371 tags.append(t)
372 self.col.tags.save()
373 return tags
402374
403375 def mergeTags(self, tags):
404376 self.col.tags.register(tags, usn=self.maxUsn)
447419 def mergeConf(self, conf):
448420 self.col.conf = conf
449421
450 # Local syncing for unit tests
451 ##########################################################################
452
453 class LocalServer(Syncer):
454
455 # serialize/deserialize payload, so we don't end up sharing objects
456 # between cols
457 def applyChanges(self, changes):
458 l = json.loads; d = json.dumps
459 return l(d(Syncer.applyChanges(self, l(d(changes)))))
460
461422 # Wrapper for requests that tracks upload/download progress
462423 ##########################################################################
463424
511472
512473 class HttpSyncer:
513474
514 def __init__(self, hkey=None, client=None):
475 def __init__(self, hkey=None, client=None, hostNum=None):
515476 self.hkey = hkey
516477 self.skey = checksum(str(random.random()))[:8]
517478 self.client = client or AnkiRequestsClient()
518479 self.postVars = {}
480 self.hostNum = hostNum
481 self.prefix = "sync/"
482
483 def syncURL(self):
484 if devMode:
485 url = "https://l1sync.ankiweb.net/"
486 else:
487 url = SYNC_BASE % (self.hostNum or "")
488 return url + self.prefix
519489
520490 def assertOk(self, resp):
521491 # not using raise_for_status() as aqt expects this error msg
591561
592562 class RemoteServer(HttpSyncer):
593563
594 def __init__(self, hkey):
595 HttpSyncer.__init__(self, hkey)
596
597 def syncURL(self):
598 if devMode:
599 return "https://l1sync.ankiweb.net/sync/"
600 return SYNC_BASE + "sync/"
564 def __init__(self, hkey, hostNum):
565 HttpSyncer.__init__(self, hkey, hostNum=hostNum)
601566
602567 def hostKey(self, user, pw):
603568 "Returns hkey or none if user/pw incorrect."
625590 return
626591 return json.loads(ret.decode("utf8"))
627592
593 def applyGraves(self, **kw):
594 return self._run("applyGraves", kw)
595
628596 def applyChanges(self, **kw):
629597 return self._run("applyChanges", kw)
630598
655623
656624 class FullSyncer(HttpSyncer):
657625
658 def __init__(self, col, hkey, client):
659 HttpSyncer.__init__(self, hkey, client)
626 def __init__(self, col, hkey, client, hostNum):
627 HttpSyncer.__init__(self, hkey, client, hostNum=hostNum)
660628 self.postVars = dict(
661629 k=self.hkey,
662630 v="ankidesktop,%s,%s"%(anki.version, platDesc()),
663631 )
664632 self.col = col
665
666 def syncURL(self):
667 if devMode:
668 return "https://l1.ankiweb.net/sync/"
669 return SYNC_BASE + "sync/"
670633
671634 def download(self):
672635 runHook("sync", "download")
846809
847810 class RemoteMediaServer(HttpSyncer):
848811
849 def __init__(self, col, hkey, client):
812 def __init__(self, col, hkey, client, hostNum):
850813 self.col = col
851 HttpSyncer.__init__(self, hkey, client)
852
853 def syncURL(self):
854 if devMode:
855 return "https://l1.ankiweb.net/msync/"
856 return SYNC_MEDIA_BASE
814 HttpSyncer.__init__(self, hkey, client, hostNum=hostNum)
815 self.prefix = "msync/"
857816
858817 def begin(self):
859818 self.postVars = dict(
222222 parser.add_option("-b", "--base", help="path to base folder")
223223 parser.add_option("-p", "--profile", help="profile name to load")
224224 parser.add_option("-l", "--lang", help="interface language (en, de, etc)")
225 if not isMac:
226 parser.add_option("--hwaccel", action="store_true", help="enable hardware acceleration")
225227 return parser.parse_args(argv[1:])
226228
227229 def run():
251253 opts, args = parseArgs(argv)
252254 opts.base = opts.base or ""
253255 opts.profile = opts.profile or ""
256
257 if not isMac and not opts.hwaccel:
258 print("Hardware acceleration disabled.")
259 if isWin:
260 os.environ["QT_OPENGL"] = "software"
261 else:
262 os.environ["QT_XCB_FORCE_SOFTWARE_OPENGL"] = "1"
254263
255264 # work around pyqt loading wrong GL library
256265 if isLin:
292301 # remaining pm init
293302 pm.ensureProfile()
294303
295 print("This is an BETA build - please do not package it up for Linux distributions")
296
297304 # load the main window
298305 import aqt.main
299306 mw = aqt.main.AnkiQt(app, pm, opts, args)
203203 ######################################################################
204204
205205 _configButtonActions = {}
206 _configUpdatedActions = {}
206207
207208 def addonConfigDefaults(self, dir):
208209 path = os.path.join(self.addonsFolder(dir), "config.json")
225226
226227 def configAction(self, addon):
227228 return self._configButtonActions.get(addon)
229
230 def configUpdatedAction(self, addon):
231 return self._configUpdatedActions.get(addon)
228232
229233 # Add-on Config API
230234 ######################################################################
244248 def setConfigAction(self, module, fn):
245249 addon = self.addonFromModule(module)
246250 self._configButtonActions[addon] = fn
251
252 def setConfigUpdatedAction(self, module, fn):
253 addon = self.addonFromModule(module)
254 self._configUpdatedActions[addon] = fn
247255
248256 def writeConfig(self, module, conf):
249257 addon = self.addonFromModule(module)
456464 restore = self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults)
457465 restore.clicked.connect(self.onRestoreDefaults)
458466 self.updateHelp()
459 self.updateText()
467 self.updateText(self.conf)
460468 self.show()
461469
462470 def onRestoreDefaults(self):
463 self.conf = self.mgr.addonConfigDefaults(self.addon)
464 self.updateText()
471 default_conf = self.mgr.addonConfigDefaults(self.addon)
472 self.updateText(default_conf)
465473
466474 def updateHelp(self):
467475 txt = self.mgr.addonConfigHelp(self.addon)
470478 else:
471479 self.form.scrollArea.setVisible(False)
472480
473 def updateText(self):
481 def updateText(self, conf):
474482 self.form.editor.setPlainText(
475 json.dumps(self.conf,sort_keys=True,indent=4, separators=(',', ': ')))
483 json.dumps(conf,sort_keys=True,indent=4, separators=(',', ': ')))
476484
477485 def accept(self):
478486 txt = self.form.editor.toPlainText()
479487 try:
480 self.conf = json.loads(txt)
488 new_conf = json.loads(txt)
481489 except Exception as e:
482490 showInfo(_("Invalid configuration: ") + repr(e))
483491 return
484492
485 self.mgr.writeConfig(self.addon, self.conf)
493 if new_conf != self.conf:
494 self.mgr.writeConfig(self.addon, new_conf)
495 # does the add-on define an action to be fired?
496 act = self.mgr.configUpdatedAction(self.addon)
497 if act:
498 act(new_conf)
499
486500 super().accept()
460460 m.addSeparator()
461461 for act in self.form.menu_Notes.actions():
462462 m.addAction(act)
463 runHook("browser.onContextMenu", self, m)
463464 m.exec_(QCursor.pos())
464465
465466 def updateFont(self):
555556 self.form.searchEdit.lineEdit().setText("deck:current ")
556557
557558 # update history
558 txt = str(self.form.searchEdit.lineEdit().text()).strip()
559 txt = str(self.form.searchEdit.lineEdit().text())
559560 sh = self.mw.pm.profile['searchHistory']
560561 if txt in sh:
561562 sh.remove(txt)
575576 if "is:current" in self._lastSearchTxt:
576577 # show current card if there is one
577578 c = self.mw.reviewer.card
579 self.card = self.mw.reviewer.card
578580 nid = c and c.nid or 0
579581 self.model.search("nid:%d"%nid)
580582 else:
733735 self.model.beginReset()
734736 if type in self.model.activeCols:
735737 if len(self.model.activeCols) < 2:
738 self.model.endReset()
736739 return showInfo(_("You must have at least one column."))
737740 self.model.activeCols.remove(type)
738741 adding=False
17071710 "%(a)d of %(b)d notes updated", len(sf)) % {
17081711 'a': changed,
17091712 'b': len(sf),
1710 })
1713 }, parent=self)
17111714
17121715 def onFindReplaceHelp(self):
17131716 openHelp("findreplace")
2121 self.form.buttonBox.button(QDialogButtonBox.Close).setShortcut(
2222 QKeySequence("Ctrl+Return"))
2323 self.editor = aqt.editor.Editor(self.mw, self.form.fieldsArea, self)
24 self.editor.card = self.mw.reviewer.card
2425 self.editor.setNote(self.mw.reviewer.card.note(), focusTo=0)
2526 restoreGeom(self, "editcurrent")
2627 addHook("reset", self.onReset)
66 from aqt.qt import *
77 import aqt
88 from aqt.utils import getSaveFile, tooltip, showWarning, askUser, \
9 checkInvalidFilename
9 checkInvalidFilename, showInfo
1010 from anki.exporting import exporters
1111 from anki.hooks import addHook, remHook
1212 from anki.lang import ngettext
8484 deck_name = self.decks[self.frm.deck.currentIndex()]
8585 deck_name = re.sub('[\\\\/?<>:*|"^]', '_', deck_name)
8686
87 if not self.isVerbatim and self.isApkg and self.exporter.includeSched:
88 showInfo("Please switch to the regular scheduler before exporting a single deck .apkg with scheduling.")
89 return
90
8791 filename = '{0}{1}'.format(deck_name, self.exporter.ext)
8892 while 1:
8993 file = getSaveFile(self, _("Export"), "export",
264264 self.menuFlag.setTitle(_("Flag"))
265265 self.menu_Notes.setTitle(_("&Notes"))
266266 self.actionReschedule.setText(_("&Reschedule..."))
267 self.actionReschedule.setShortcut(_("Ctrl+Alt+R"))
267268 self.actionSelectAll.setText(_("Select &All"))
268269 self.actionSelectAll.setShortcut(_("Ctrl+Alt+A"))
269270 self.actionUndo.setText(_("&Undo"))
270271 self.actionUndo.setShortcut(_("Ctrl+Z"))
271272 self.actionInvertSelection.setText(_("&Invert Selection"))
273 self.actionInvertSelection.setShortcut(_("Ctrl+Alt+S"))
272274 self.actionFind.setText(_("&Find"))
273275 self.actionFind.setShortcut(_("Ctrl+F"))
274276 self.actionNote.setText(_("N&ote"))
88 from PyQt5 import QtCore
99
1010 qt_resource_data = b"\
11 \x00\x00\x02\xd7\
12 \x89\
13 \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
14 \x00\x00\x3c\x00\x00\x00\x3c\x08\x06\x00\x00\x00\x3a\xfc\xd9\x72\
15 \x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\
16 \x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\
17 \x79\x71\xc9\x65\x3c\x00\x00\x02\x79\x49\x44\x41\x54\x78\xda\xec\
18 \x9a\xd1\x6d\xc2\x30\x10\x40\x0d\x62\x80\x6c\x40\x3a\x01\xde\xa0\
19 \x19\x21\x23\xa4\x13\xc0\x06\xf5\x06\xa8\x13\xa4\x1b\xa4\x1b\x44\
20 \x4c\x10\x98\x20\x6c\x10\x36\x68\x8d\xe4\x48\xc8\x75\x82\xef\xb0\
21 \x0f\x87\xf8\xa4\xfb\x31\x89\xc3\xf3\x9d\xed\xbb\xb3\x17\x8c\x56\
22 \xb8\xd4\x5c\xea\x46\x6a\xa2\xda\x2e\x52\x4f\x52\x7f\xa4\x1e\xd9\
23 \x8b\xc8\x15\xb2\x91\xfa\x7b\x47\x5b\xf5\xec\x64\xe5\x6a\xc5\xca\
24 \x02\x54\xd7\xea\xc6\x03\x26\x05\xdb\x20\x60\x7b\x6d\xd4\x14\x98\
25 \x05\x6c\xaf\xdd\x54\x2c\xed\x02\xf6\xd6\xd2\x41\x8b\x70\x08\xdb\
26 \xab\x08\x15\x36\xf5\x00\xdb\x6b\x1a\x22\x70\xe9\x11\xb8\x9e\x93\
27 \x75\x9d\x59\x79\xe9\x10\x78\x4b\x30\xa8\xdb\x90\x2c\xdc\x12\x58\
28 \xb8\x0b\x05\x96\x13\xc0\xf6\xca\x43\x70\xe9\x8c\x38\x2e\x47\xcb\
29 \xea\x0e\x04\x57\x19\xcc\x59\xe9\x90\x6c\x08\x81\x37\x3e\x81\x3f\
30 \xb5\xb6\x2b\xfc\x97\xd4\x6f\xc3\x0a\x4d\xb9\x1b\xe8\x61\x6c\xae\
31 \xda\xfb\xb4\x93\xab\xb4\xf3\x0d\x02\x3c\x34\x57\xd3\x27\xbb\x34\
32 \x1f\xd8\xff\x4d\xf1\x3c\x68\x0e\xbf\x0f\xb4\x87\x96\xa4\x5f\x7c\
33 \x2f\x5a\x17\x36\x61\x59\xb2\x99\xc9\x12\x61\xc9\xf4\x55\x81\x4f\
34 \x13\x01\x4e\x7c\xbb\xf4\xfa\xc9\x0b\xd9\xd1\x62\xd5\x1e\xfc\x4f\
35 \x4b\x40\xc7\x63\x1f\x38\x3f\x71\x55\xe6\x90\x29\x89\x05\x4e\x2c\
36 \xdd\xdf\x87\x1c\x2c\x23\xaf\x03\xa6\xf3\x6e\x20\x80\xcf\x0d\x81\
37 \x07\x55\xf2\x90\x21\xff\xa3\x95\x0c\xd5\x94\x4b\xc0\xe0\xf8\x4c\
38 \x0f\xf3\x91\x67\x51\xd5\xce\x02\xd0\x61\x49\x00\x5c\x5a\x7e\x13\
39 \x5d\xe9\x4c\x98\x7d\x25\x91\x13\xbb\xf3\x58\x49\x69\xf7\xc8\x22\
40 \x51\x31\xfb\x22\x79\x4d\x58\xc4\x2b\x7d\xd5\xbe\x32\x80\x95\xb3\
41 \x00\xac\x5b\xfa\xae\x57\x71\x82\xb9\x5c\x01\x3c\xc9\x49\xaa\x3a\
42 \xb6\x1a\x36\x9a\x6b\x27\x8e\x0b\x7a\xad\xd6\xbf\x60\x44\xb5\xeb\
43 \x1a\xe0\x46\xdc\xd1\x36\xd5\x69\x1e\x94\xfb\x2c\xf0\x41\x2b\x93\
44 \xae\xa1\x75\xd8\x7b\xfd\xed\x7d\x84\x75\x02\x01\x8d\x39\x49\x6c\
45 \x80\xb0\x2d\xf3\x78\xac\x5a\x33\xd8\xe9\x7d\xa2\x06\xaa\xb3\xb4\
46 \xaa\xd0\xde\x2f\x98\xe7\x5a\xb5\x4d\x30\xd2\x58\x2c\x34\x99\xe1\
47 \xbd\x42\x0d\x48\xab\x3d\x5b\xa9\xdf\xf4\x81\xb2\xb9\x2e\x51\x50\
48 \x55\x0e\x3b\xcb\xed\x04\x13\x04\xec\x2c\xfb\x17\x94\x95\x06\xc8\
49 \xa2\x54\x1b\x2c\x68\xea\x6f\x0f\xe8\x13\x15\x60\x2c\x1e\x84\x4e\
50 \x95\x15\x21\x73\xe8\x6c\x28\x18\x40\x83\x85\x0f\xf6\xff\x30\x80\
51 \xb4\xa6\x84\xb9\x9a\x84\xdd\xaa\x82\xb9\xc7\xb5\x63\x7e\x73\xe2\
52 \x9a\x05\x78\xa3\x27\xf1\x10\x4b\x4f\xe2\x76\x5e\xaa\xc0\x1f\xb1\
53 \x78\x43\xb5\xe5\xb8\xb6\x78\xa1\xe0\x5b\x4b\x6b\x0a\x36\xa1\x1b\
54 \x78\xd8\xbc\x9a\x6c\x5f\x8d\x67\x4b\x11\x38\x02\x47\xe0\x08\x1c\
55 \x81\x03\x92\x15\xf2\x3d\x8e\x88\x6b\x6d\x82\x89\x35\x22\x73\x32\
56 \x65\x5f\xe4\x65\x1e\x4a\x15\xd1\xa5\x23\x70\x04\x9e\x8f\xfc\x09\
57 \x30\x00\xa0\x1c\x74\x67\x26\xea\x15\x76\x00\x00\x00\x00\x49\x45\
58 \x4e\x44\xae\x42\x60\x82\
5911 \x00\x00\x06\xb8\
6012 \x89\
6113 \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
166118 \xf0\x09\xd0\xde\xc7\xe9\xb6\x11\x7f\x4b\x80\x25\xa0\xdc\xca\x39\
167119 \xc0\xff\x00\x27\xf2\xcd\xbe\x4f\x7b\xc5\xe3\x00\x00\x00\x00\x49\
168120 \x45\x4e\x44\xae\x42\x60\x82\
169 \x00\x00\x04\x30\
121 \x00\x00\x04\x06\
170122 \x3c\
171123 \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
172124 \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\
196148 \x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\
197149 \x6c\x69\x6d\x69\x74\x3a\x31\x2e\x35\x3b\x22\x3e\x0a\x20\x20\x20\
198150 \x20\x3c\x67\x20\x74\x72\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x6d\
199 \x61\x74\x72\x69\x78\x28\x31\x2c\x30\x2c\x30\x2c\x31\x2c\x2d\x31\
200 \x34\x30\x2c\x30\x29\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\
201 \x3c\x67\x20\x69\x64\x3d\x22\x74\x61\x67\x22\x20\x74\x72\x61\x6e\
202 \x73\x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\x31\x2c\
203 \x30\x2c\x30\x2c\x31\x2c\x2d\x37\x34\x36\x2c\x30\x29\x22\x3e\x0a\
204 \x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x72\x65\x63\
205 \x74\x20\x78\x3d\x22\x38\x38\x36\x22\x20\x79\x3d\x22\x30\x22\x20\
206 \x77\x69\x64\x74\x68\x3d\x22\x36\x30\x22\x20\x68\x65\x69\x67\x68\
207 \x74\x3d\x22\x36\x30\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\
208 \x6c\x6c\x3a\x6e\x6f\x6e\x65\x3b\x22\x2f\x3e\x0a\x20\x20\x20\x20\
209 \x20\x20\x20\x20\x20\x20\x20\x20\x3c\x67\x20\x74\x72\x61\x6e\x73\
210 \x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\x31\x2e\x30\
211 \x32\x36\x31\x33\x2c\x30\x2c\x30\x2c\x31\x2e\x32\x35\x39\x32\x36\
212 \x2c\x35\x32\x36\x2e\x35\x38\x33\x2c\x2d\x38\x2e\x34\x30\x37\x34\
213 \x31\x29\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
214 \x20\x20\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x33\
215 \x35\x34\x2c\x31\x37\x4c\x33\x38\x38\x2c\x31\x37\x4c\x34\x30\x35\
216 \x2e\x30\x30\x31\x2c\x32\x33\x2e\x37\x35\x4c\x34\x30\x35\x2e\x30\
217 \x30\x31\x2c\x33\x37\x2e\x32\x35\x31\x4c\x33\x38\x38\x2c\x34\x34\
218 \x4c\x33\x35\x34\x2c\x34\x34\x4c\x33\x35\x34\x2c\x31\x37\x5a\x22\
151 \x61\x74\x72\x69\x78\x28\x31\x2c\x30\x2c\x30\x2c\x31\x2c\x2d\x32\
152 \x38\x30\x2c\x30\x29\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\
153 \x3c\x67\x20\x69\x64\x3d\x22\x68\x65\x61\x72\x74\x22\x20\x74\x72\
154 \x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\
155 \x31\x2c\x30\x2c\x30\x2c\x31\x2c\x2d\x31\x34\x31\x31\x2c\x30\x29\
156 \x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\
157 \x72\x65\x63\x74\x20\x78\x3d\x22\x31\x36\x39\x31\x22\x20\x79\x3d\
158 \x22\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x36\x30\x22\x20\x68\
159 \x65\x69\x67\x68\x74\x3d\x22\x36\x30\x22\x20\x73\x74\x79\x6c\x65\
160 \x3d\x22\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x3b\x22\x2f\x3e\x0a\
161 \x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x67\x20\x74\
162 \x72\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\
163 \x28\x30\x2e\x39\x36\x30\x32\x34\x31\x2c\x30\x2c\x30\x2c\x30\x2e\
164 \x39\x36\x30\x32\x34\x31\x2c\x31\x34\x33\x31\x2e\x30\x31\x2c\x33\
165 \x2e\x31\x34\x37\x30\x31\x29\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\
166 \x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x70\x61\x74\x68\x20\
167 \x64\x3d\x22\x4d\x33\x30\x32\x2c\x31\x30\x2e\x38\x43\x33\x30\x37\
168 \x2e\x36\x38\x34\x2c\x30\x20\x33\x31\x39\x2e\x30\x35\x33\x2c\x30\
169 \x20\x33\x32\x34\x2e\x37\x33\x37\x2c\x35\x2e\x34\x43\x33\x33\x30\
170 \x2e\x34\x32\x31\x2c\x31\x30\x2e\x38\x20\x33\x33\x30\x2e\x34\x32\
171 \x31\x2c\x32\x31\x2e\x36\x20\x33\x32\x34\x2e\x37\x33\x37\x2c\x33\
172 \x32\x2e\x34\x43\x33\x32\x30\x2e\x37\x35\x38\x2c\x34\x30\x2e\x35\
173 \x20\x33\x31\x30\x2e\x35\x32\x36\x2c\x34\x38\x2e\x36\x20\x33\x30\
174 \x32\x2c\x35\x34\x43\x32\x39\x33\x2e\x34\x37\x34\x2c\x34\x38\x2e\
175 \x36\x20\x32\x38\x33\x2e\x32\x34\x32\x2c\x34\x30\x2e\x35\x20\x32\
176 \x37\x39\x2e\x32\x36\x33\x2c\x33\x32\x2e\x34\x43\x32\x37\x33\x2e\
177 \x35\x37\x39\x2c\x32\x31\x2e\x36\x20\x32\x37\x33\x2e\x35\x37\x39\
178 \x2c\x31\x30\x2e\x38\x20\x32\x37\x39\x2e\x32\x36\x33\x2c\x35\x2e\
179 \x34\x43\x32\x38\x34\x2e\x39\x34\x37\x2c\x30\x20\x32\x39\x36\x2e\
180 \x33\x31\x36\x2c\x30\x20\x33\x30\x32\x2c\x31\x30\x2e\x38\x5a\x22\
219181 \x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\
220182 \x65\x3b\x73\x74\x72\x6f\x6b\x65\x3a\x62\x6c\x61\x63\x6b\x3b\x73\
221 \x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x32\x2e\x31\x38\
183 \x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x32\x2e\x38\x32\
222184 \x70\x78\x3b\x22\x2f\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\
223 \x20\x20\x20\x20\x20\x20\x20\x3c\x67\x20\x74\x72\x61\x6e\x73\x66\
224 \x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\x30\x2e\x38\x39\
225 \x36\x31\x32\x38\x2c\x30\x2c\x30\x2c\x30\x2e\x38\x39\x36\x31\x32\
226 \x38\x2c\x33\x39\x2e\x33\x30\x39\x32\x2c\x33\x2e\x31\x36\x37\x34\
227 \x38\x29\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
228 \x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x63\x69\x72\x63\x6c\x65\
229 \x20\x63\x78\x3d\x22\x34\x30\x30\x22\x20\x63\x79\x3d\x22\x33\x30\
230 \x2e\x35\x30\x31\x22\x20\x72\x3d\x22\x32\x2e\x34\x39\x39\x22\x20\
231 \x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x72\x6f\x6b\x65\x3a\x62\x6c\
232 \x61\x63\x6b\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
233 \x3a\x32\x2e\x36\x33\x70\x78\x3b\x22\x2f\x3e\x0a\x20\x20\x20\x20\
234 \x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x67\x3e\
235 \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x67\
236 \x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x67\x3e\x0a\x20\
237 \x20\x20\x20\x3c\x2f\x67\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\x0a\
185 \x20\x20\x20\x3c\x2f\x67\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\
186 \x3c\x2f\x67\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x67\x3e\x0a\x3c\x2f\
187 \x73\x76\x67\x3e\x0a\
238188 \x00\x00\x05\x69\
239189 \x3c\
240190 \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
324274 \x20\x20\x20\x20\x20\x20\x3c\x2f\x67\x3e\x0a\x20\x20\x20\x20\x20\
325275 \x20\x20\x20\x3c\x2f\x67\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x67\x3e\
326276 \x0a\x3c\x2f\x73\x76\x67\x3e\x0a\
327 \x00\x00\x02\x68\
328 \x00\
329 \x00\x10\x25\x78\x9c\xed\x97\x5b\x6f\xd3\x30\x14\xc7\xdf\xf7\x29\
330 \x8c\x25\x24\x90\x52\x5f\x13\x3b\xce\x9a\x4d\xda\x85\x09\x69\xc0\
331 \x24\x36\x10\xbc\x85\xc4\x6b\xcd\xd2\x24\x4a\xb2\xb6\xfb\xf6\x9c\
332 \x64\xed\xb4\x6e\xd5\x40\x88\xf1\xb2\xb4\x55\xeb\x73\x72\x7c\x2e\
333 \x3f\xfb\xff\xd0\xf1\xfe\x72\x96\xa3\xb9\xad\x1b\x57\x16\x31\xe6\
334 \x84\x61\x64\x8b\xb4\xcc\x5c\x31\x89\xf1\xc5\xf9\xbb\x51\x88\x51\
335 \xd3\x26\x45\x96\xe4\x65\x61\x63\x5c\x94\x78\x7f\x6f\x67\xfc\xea\
336 \xe8\xd3\xe1\xf9\xb7\xb3\x63\xd4\xcc\x27\xe8\xec\xe2\xe0\xf4\xfd\
337 \x21\xc2\x23\x4a\xbf\xca\x43\x4a\x8f\xce\x8f\xd0\xe7\x2f\x27\x88\
338 \x13\x4e\xe9\xf1\x47\x8c\xf0\xb4\x6d\xab\x88\xd2\xc5\x62\x41\x16\
339 \x92\x94\xf5\x84\x9e\xd4\x49\x35\x75\x69\x43\x21\x90\x76\x81\xb0\
340 \x89\x42\x32\xce\x49\xd6\x66\x18\x4a\x74\x99\x17\x2e\x6b\xa7\xd0\
341 \x16\x63\xaf\x31\x9a\x5a\x37\x99\xb6\x6b\x6b\xee\xec\xe2\xa0\x5c\
342 \xc6\x98\x21\x86\x54\xf7\xc1\xf7\xe7\xe0\x18\xc1\x64\x45\x13\x6f\
343 \xa9\x2d\x18\x63\x5d\xad\x55\x48\xb4\xcc\x5d\x71\xb5\x2d\x90\x1b\
344 \x63\x68\xff\xb4\x0f\x8d\x9a\x2a\x49\x81\x41\x55\xdb\xc6\xd6\x73\
345 \xdb\x91\xb9\xc9\xc1\x71\xe9\xf2\x7c\x54\x5f\xe7\x36\xb2\x73\x5b\
346 \x94\x59\xb6\x9b\xe6\xae\xda\xf4\x34\x6d\x5d\x5e\xd9\x11\x24\xb3\
347 \x69\x52\x45\x75\x79\x5d\x6c\x38\x7f\x96\xae\xd8\xf4\xce\x5c\x6b\
348 \xeb\xdc\xc1\x4f\xc4\x49\xb0\x0b\x4c\x10\xbc\xc6\x13\xd4\xd6\x49\
349 \xd1\x5c\x96\xf5\x2c\xc6\xb3\xa4\xad\xdd\xf2\x0d\xf7\x18\xbc\xb9\
350 \x37\xf2\xb9\xf6\xd8\xdb\x55\xe8\x2a\xdc\x65\x31\x4e\xcb\x3c\xb7\
351 \x69\x0b\x70\xf0\x53\xdb\xb9\xd0\xfe\xe6\xfe\x3e\x47\x0d\x5b\x11\
352 \xa0\xe6\xca\x00\xd7\x1b\x60\x8e\xd7\x47\xd3\x61\x5f\x1f\x4c\xb7\
353 \xbe\x47\x24\x2a\xe0\xc6\xec\x62\xfa\x20\xd9\xb6\xfe\x19\x31\x7e\
354 \xe0\x4b\xd9\xf7\x71\x67\x18\x41\x94\x62\xdc\x13\x84\x2b\xa5\xcc\
355 \xc3\xb6\x9e\xc8\xa6\x82\x80\x73\xc8\x46\x18\xe3\x4a\xfa\x42\x78\
356 \xa3\x7e\x6d\x84\x66\x06\xdc\x5a\x8b\x00\x48\x71\x61\x0c\x31\xd2\
357 \x13\x21\x91\xa1\x0e\xb7\x15\xe8\x8b\x54\x49\x3b\x45\x40\xf1\x83\
358 \xd2\xd2\xd3\xa7\x2a\x10\xab\x6f\x0e\xbd\xf2\x53\xa5\x42\x22\xa0\
359 \x4f\xb8\xbb\x8c\xcb\x5b\xd3\x97\x9e\x14\xc4\x30\x30\xf5\xc6\x52\
360 \x7f\xdf\x02\xe9\xf6\xc4\xa3\x1f\x79\x92\x5e\xad\x8f\xbf\x07\x1c\
361 \xf9\xd5\xf2\x11\xc2\xbe\x27\x3a\xf9\xe7\x34\x24\x93\xc0\xc1\x13\
362 \x01\x11\x81\x0a\x5e\x3a\x0d\x61\x14\xd1\xa1\x27\x41\x7b\xdc\xff\
363 \x0b\x1a\xff\x77\xe0\xc7\x8e\x3f\xd7\x99\x52\x83\xce\x06\x9d\x0d\
364 \x3a\x7b\x6e\x9d\xc1\xa0\x4c\x19\xe5\x8d\x84\x26\x42\x85\x6a\x10\
365 \xda\x33\x5f\xad\x41\x68\x2f\x53\x68\x1a\xe6\xf7\x75\x30\x08\x6d\
366 \x10\xda\x20\xb4\xdf\x0d\xbc\xe1\xb8\x33\xfa\xc5\xb8\xfb\x97\xbc\
367 \xb7\xf3\x0b\x46\x33\xee\x37\
368 \x00\x00\x04\x06\
277 \x00\x00\x02\xd7\
278 \x89\
279 \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
280 \x00\x00\x3c\x00\x00\x00\x3c\x08\x06\x00\x00\x00\x3a\xfc\xd9\x72\
281 \x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\
282 \x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\
283 \x79\x71\xc9\x65\x3c\x00\x00\x02\x79\x49\x44\x41\x54\x78\xda\xec\
284 \x9a\xd1\x6d\xc2\x30\x10\x40\x0d\x62\x80\x6c\x40\x3a\x01\xde\xa0\
285 \x19\x21\x23\xa4\x13\xc0\x06\xf5\x06\xa8\x13\xa4\x1b\xa4\x1b\x44\
286 \x4c\x10\x98\x20\x6c\x10\x36\x68\x8d\xe4\x48\xc8\x75\x82\xef\xb0\
287 \x0f\x87\xf8\xa4\xfb\x31\x89\xc3\xf3\x9d\xed\xbb\xb3\x17\x8c\x56\
288 \xb8\xd4\x5c\xea\x46\x6a\xa2\xda\x2e\x52\x4f\x52\x7f\xa4\x1e\xd9\
289 \x8b\xc8\x15\xb2\x91\xfa\x7b\x47\x5b\xf5\xec\x64\xe5\x6a\xc5\xca\
290 \x02\x54\xd7\xea\xc6\x03\x26\x05\xdb\x20\x60\x7b\x6d\xd4\x14\x98\
291 \x05\x6c\xaf\xdd\x54\x2c\xed\x02\xf6\xd6\xd2\x41\x8b\x70\x08\xdb\
292 \xab\x08\x15\x36\xf5\x00\xdb\x6b\x1a\x22\x70\xe9\x11\xb8\x9e\x93\
293 \x75\x9d\x59\x79\xe9\x10\x78\x4b\x30\xa8\xdb\x90\x2c\xdc\x12\x58\
294 \xb8\x0b\x05\x96\x13\xc0\xf6\xca\x43\x70\xe9\x8c\x38\x2e\x47\xcb\
295 \xea\x0e\x04\x57\x19\xcc\x59\xe9\x90\x6c\x08\x81\x37\x3e\x81\x3f\
296 \xb5\xb6\x2b\xfc\x97\xd4\x6f\xc3\x0a\x4d\xb9\x1b\xe8\x61\x6c\xae\
297 \xda\xfb\xb4\x93\xab\xb4\xf3\x0d\x02\x3c\x34\x57\xd3\x27\xbb\x34\
298 \x1f\xd8\xff\x4d\xf1\x3c\x68\x0e\xbf\x0f\xb4\x87\x96\xa4\x5f\x7c\
299 \x2f\x5a\x17\x36\x61\x59\xb2\x99\xc9\x12\x61\xc9\xf4\x55\x81\x4f\
300 \x13\x01\x4e\x7c\xbb\xf4\xfa\xc9\x0b\xd9\xd1\x62\xd5\x1e\xfc\x4f\
301 \x4b\x40\xc7\x63\x1f\x38\x3f\x71\x55\xe6\x90\x29\x89\x05\x4e\x2c\
302 \xdd\xdf\x87\x1c\x2c\x23\xaf\x03\xa6\xf3\x6e\x20\x80\xcf\x0d\x81\
303 \x07\x55\xf2\x90\x21\xff\xa3\x95\x0c\xd5\x94\x4b\xc0\xe0\xf8\x4c\
304 \x0f\xf3\x91\x67\x51\xd5\xce\x02\xd0\x61\x49\x00\x5c\x5a\x7e\x13\
305 \x5d\xe9\x4c\x98\x7d\x25\x91\x13\xbb\xf3\x58\x49\x69\xf7\xc8\x22\
306 \x51\x31\xfb\x22\x79\x4d\x58\xc4\x2b\x7d\xd5\xbe\x32\x80\x95\xb3\
307 \x00\xac\x5b\xfa\xae\x57\x71\x82\xb9\x5c\x01\x3c\xc9\x49\xaa\x3a\
308 \xb6\x1a\x36\x9a\x6b\x27\x8e\x0b\x7a\xad\xd6\xbf\x60\x44\xb5\xeb\
309 \x1a\xe0\x46\xdc\xd1\x36\xd5\x69\x1e\x94\xfb\x2c\xf0\x41\x2b\x93\
310 \xae\xa1\x75\xd8\x7b\xfd\xed\x7d\x84\x75\x02\x01\x8d\x39\x49\x6c\
311 \x80\xb0\x2d\xf3\x78\xac\x5a\x33\xd8\xe9\x7d\xa2\x06\xaa\xb3\xb4\
312 \xaa\xd0\xde\x2f\x98\xe7\x5a\xb5\x4d\x30\xd2\x58\x2c\x34\x99\xe1\
313 \xbd\x42\x0d\x48\xab\x3d\x5b\xa9\xdf\xf4\x81\xb2\xb9\x2e\x51\x50\
314 \x55\x0e\x3b\xcb\xed\x04\x13\x04\xec\x2c\xfb\x17\x94\x95\x06\xc8\
315 \xa2\x54\x1b\x2c\x68\xea\x6f\x0f\xe8\x13\x15\x60\x2c\x1e\x84\x4e\
316 \x95\x15\x21\x73\xe8\x6c\x28\x18\x40\x83\x85\x0f\xf6\xff\x30\x80\
317 \xb4\xa6\x84\xb9\x9a\x84\xdd\xaa\x82\xb9\xc7\xb5\x63\x7e\x73\xe2\
318 \x9a\x05\x78\xa3\x27\xf1\x10\x4b\x4f\xe2\x76\x5e\xaa\xc0\x1f\xb1\
319 \x78\x43\xb5\xe5\xb8\xb6\x78\xa1\xe0\x5b\x4b\x6b\x0a\x36\xa1\x1b\
320 \x78\xd8\xbc\x9a\x6c\x5f\x8d\x67\x4b\x11\x38\x02\x47\xe0\x08\x1c\
321 \x81\x03\x92\x15\xf2\x3d\x8e\x88\x6b\x6d\x82\x89\x35\x22\x73\x32\
322 \x65\x5f\xe4\x65\x1e\x4a\x15\xd1\xa5\x23\x70\x04\x9e\x8f\xfc\x09\
323 \x30\x00\xa0\x1c\x74\x67\x26\xea\x15\x76\x00\x00\x00\x00\x49\x45\
324 \x4e\x44\xae\x42\x60\x82\
325 \x00\x00\x04\x30\
369326 \x3c\
370327 \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
371328 \x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\
395352 \x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\
396353 \x6c\x69\x6d\x69\x74\x3a\x31\x2e\x35\x3b\x22\x3e\x0a\x20\x20\x20\
397354 \x20\x3c\x67\x20\x74\x72\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x6d\
398 \x61\x74\x72\x69\x78\x28\x31\x2c\x30\x2c\x30\x2c\x31\x2c\x2d\x32\
399 \x38\x30\x2c\x30\x29\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\
400 \x3c\x67\x20\x69\x64\x3d\x22\x68\x65\x61\x72\x74\x22\x20\x74\x72\
401 \x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\
402 \x31\x2c\x30\x2c\x30\x2c\x31\x2c\x2d\x31\x34\x31\x31\x2c\x30\x29\
403 \x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\
404 \x72\x65\x63\x74\x20\x78\x3d\x22\x31\x36\x39\x31\x22\x20\x79\x3d\
405 \x22\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x36\x30\x22\x20\x68\
406 \x65\x69\x67\x68\x74\x3d\x22\x36\x30\x22\x20\x73\x74\x79\x6c\x65\
407 \x3d\x22\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x3b\x22\x2f\x3e\x0a\
408 \x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x67\x20\x74\
409 \x72\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\
410 \x28\x30\x2e\x39\x36\x30\x32\x34\x31\x2c\x30\x2c\x30\x2c\x30\x2e\
411 \x39\x36\x30\x32\x34\x31\x2c\x31\x34\x33\x31\x2e\x30\x31\x2c\x33\
412 \x2e\x31\x34\x37\x30\x31\x29\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\
413 \x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x70\x61\x74\x68\x20\
414 \x64\x3d\x22\x4d\x33\x30\x32\x2c\x31\x30\x2e\x38\x43\x33\x30\x37\
415 \x2e\x36\x38\x34\x2c\x30\x20\x33\x31\x39\x2e\x30\x35\x33\x2c\x30\
416 \x20\x33\x32\x34\x2e\x37\x33\x37\x2c\x35\x2e\x34\x43\x33\x33\x30\
417 \x2e\x34\x32\x31\x2c\x31\x30\x2e\x38\x20\x33\x33\x30\x2e\x34\x32\
418 \x31\x2c\x32\x31\x2e\x36\x20\x33\x32\x34\x2e\x37\x33\x37\x2c\x33\
419 \x32\x2e\x34\x43\x33\x32\x30\x2e\x37\x35\x38\x2c\x34\x30\x2e\x35\
420 \x20\x33\x31\x30\x2e\x35\x32\x36\x2c\x34\x38\x2e\x36\x20\x33\x30\
421 \x32\x2c\x35\x34\x43\x32\x39\x33\x2e\x34\x37\x34\x2c\x34\x38\x2e\
422 \x36\x20\x32\x38\x33\x2e\x32\x34\x32\x2c\x34\x30\x2e\x35\x20\x32\
423 \x37\x39\x2e\x32\x36\x33\x2c\x33\x32\x2e\x34\x43\x32\x37\x33\x2e\
424 \x35\x37\x39\x2c\x32\x31\x2e\x36\x20\x32\x37\x33\x2e\x35\x37\x39\
425 \x2c\x31\x30\x2e\x38\x20\x32\x37\x39\x2e\x32\x36\x33\x2c\x35\x2e\
426 \x34\x43\x32\x38\x34\x2e\x39\x34\x37\x2c\x30\x20\x32\x39\x36\x2e\
427 \x33\x31\x36\x2c\x30\x20\x33\x30\x32\x2c\x31\x30\x2e\x38\x5a\x22\
355 \x61\x74\x72\x69\x78\x28\x31\x2c\x30\x2c\x30\x2c\x31\x2c\x2d\x31\
356 \x34\x30\x2c\x30\x29\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\
357 \x3c\x67\x20\x69\x64\x3d\x22\x74\x61\x67\x22\x20\x74\x72\x61\x6e\
358 \x73\x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\x31\x2c\
359 \x30\x2c\x30\x2c\x31\x2c\x2d\x37\x34\x36\x2c\x30\x29\x22\x3e\x0a\
360 \x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x72\x65\x63\
361 \x74\x20\x78\x3d\x22\x38\x38\x36\x22\x20\x79\x3d\x22\x30\x22\x20\
362 \x77\x69\x64\x74\x68\x3d\x22\x36\x30\x22\x20\x68\x65\x69\x67\x68\
363 \x74\x3d\x22\x36\x30\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\
364 \x6c\x6c\x3a\x6e\x6f\x6e\x65\x3b\x22\x2f\x3e\x0a\x20\x20\x20\x20\
365 \x20\x20\x20\x20\x20\x20\x20\x20\x3c\x67\x20\x74\x72\x61\x6e\x73\
366 \x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\x31\x2e\x30\
367 \x32\x36\x31\x33\x2c\x30\x2c\x30\x2c\x31\x2e\x32\x35\x39\x32\x36\
368 \x2c\x35\x32\x36\x2e\x35\x38\x33\x2c\x2d\x38\x2e\x34\x30\x37\x34\
369 \x31\x29\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
370 \x20\x20\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x33\
371 \x35\x34\x2c\x31\x37\x4c\x33\x38\x38\x2c\x31\x37\x4c\x34\x30\x35\
372 \x2e\x30\x30\x31\x2c\x32\x33\x2e\x37\x35\x4c\x34\x30\x35\x2e\x30\
373 \x30\x31\x2c\x33\x37\x2e\x32\x35\x31\x4c\x33\x38\x38\x2c\x34\x34\
374 \x4c\x33\x35\x34\x2c\x34\x34\x4c\x33\x35\x34\x2c\x31\x37\x5a\x22\
428375 \x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\
429376 \x65\x3b\x73\x74\x72\x6f\x6b\x65\x3a\x62\x6c\x61\x63\x6b\x3b\x73\
430 \x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x32\x2e\x38\x32\
377 \x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x32\x2e\x31\x38\
431378 \x70\x78\x3b\x22\x2f\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\
432 \x20\x20\x20\x3c\x2f\x67\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\
433 \x3c\x2f\x67\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x67\x3e\x0a\x3c\x2f\
434 \x73\x76\x67\x3e\x0a\
379 \x20\x20\x20\x20\x20\x20\x20\x3c\x67\x20\x74\x72\x61\x6e\x73\x66\
380 \x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\x30\x2e\x38\x39\
381 \x36\x31\x32\x38\x2c\x30\x2c\x30\x2c\x30\x2e\x38\x39\x36\x31\x32\
382 \x38\x2c\x33\x39\x2e\x33\x30\x39\x32\x2c\x33\x2e\x31\x36\x37\x34\
383 \x38\x29\x22\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\
384 \x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x63\x69\x72\x63\x6c\x65\
385 \x20\x63\x78\x3d\x22\x34\x30\x30\x22\x20\x63\x79\x3d\x22\x33\x30\
386 \x2e\x35\x30\x31\x22\x20\x72\x3d\x22\x32\x2e\x34\x39\x39\x22\x20\
387 \x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x72\x6f\x6b\x65\x3a\x62\x6c\
388 \x61\x63\x6b\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
389 \x3a\x32\x2e\x36\x33\x70\x78\x3b\x22\x2f\x3e\x0a\x20\x20\x20\x20\
390 \x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x67\x3e\
391 \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x67\
392 \x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x2f\x67\x3e\x0a\x20\
393 \x20\x20\x20\x3c\x2f\x67\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\x0a\
435394 \x00\x00\x05\x55\
436395 \x3c\
437396 \x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
520479 \x20\x20\x3c\x2f\x67\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x3c\
521480 \x2f\x67\x3e\x0a\x20\x20\x20\x20\x3c\x2f\x67\x3e\x0a\x3c\x2f\x73\
522481 \x76\x67\x3e\x0a\
482 \x00\x00\x02\x68\
483 \x00\
484 \x00\x10\x25\x78\x9c\xed\x97\x5b\x6f\xd3\x30\x14\xc7\xdf\xf7\x29\
485 \x8c\x25\x24\x90\x52\x5f\x13\x3b\xce\x9a\x4d\xda\x85\x09\x69\xc0\
486 \x24\x36\x10\xbc\x85\xc4\x6b\xcd\xd2\x24\x4a\xb2\xb6\xfb\xf6\x9c\
487 \x64\xed\xb4\x6e\xd5\x40\x88\xf1\xb2\xb4\x55\xeb\x73\x72\x7c\x2e\
488 \x3f\xfb\xff\xd0\xf1\xfe\x72\x96\xa3\xb9\xad\x1b\x57\x16\x31\xe6\
489 \x84\x61\x64\x8b\xb4\xcc\x5c\x31\x89\xf1\xc5\xf9\xbb\x51\x88\x51\
490 \xd3\x26\x45\x96\xe4\x65\x61\x63\x5c\x94\x78\x7f\x6f\x67\xfc\xea\
491 \xe8\xd3\xe1\xf9\xb7\xb3\x63\xd4\xcc\x27\xe8\xec\xe2\xe0\xf4\xfd\
492 \x21\xc2\x23\x4a\xbf\xca\x43\x4a\x8f\xce\x8f\xd0\xe7\x2f\x27\x88\
493 \x13\x4e\xe9\xf1\x47\x8c\xf0\xb4\x6d\xab\x88\xd2\xc5\x62\x41\x16\
494 \x92\x94\xf5\x84\x9e\xd4\x49\x35\x75\x69\x43\x21\x90\x76\x81\xb0\
495 \x89\x42\x32\xce\x49\xd6\x66\x18\x4a\x74\x99\x17\x2e\x6b\xa7\xd0\
496 \x16\x63\xaf\x31\x9a\x5a\x37\x99\xb6\x6b\x6b\xee\xec\xe2\xa0\x5c\
497 \xc6\x98\x21\x86\x54\xf7\xc1\xf7\xe7\xe0\x18\xc1\x64\x45\x13\x6f\
498 \xa9\x2d\x18\x63\x5d\xad\x55\x48\xb4\xcc\x5d\x71\xb5\x2d\x90\x1b\
499 \x63\x68\xff\xb4\x0f\x8d\x9a\x2a\x49\x81\x41\x55\xdb\xc6\xd6\x73\
500 \xdb\x91\xb9\xc9\xc1\x71\xe9\xf2\x7c\x54\x5f\xe7\x36\xb2\x73\x5b\
501 \x94\x59\xb6\x9b\xe6\xae\xda\xf4\x34\x6d\x5d\x5e\xd9\x11\x24\xb3\
502 \x69\x52\x45\x75\x79\x5d\x6c\x38\x7f\x96\xae\xd8\xf4\xce\x5c\x6b\
503 \xeb\xdc\xc1\x4f\xc4\x49\xb0\x0b\x4c\x10\xbc\xc6\x13\xd4\xd6\x49\
504 \xd1\x5c\x96\xf5\x2c\xc6\xb3\xa4\xad\xdd\xf2\x0d\xf7\x18\xbc\xb9\
505 \x37\xf2\xb9\xf6\xd8\xdb\x55\xe8\x2a\xdc\x65\x31\x4e\xcb\x3c\xb7\
506 \x69\x0b\x70\xf0\x53\xdb\xb9\xd0\xfe\xe6\xfe\x3e\x47\x0d\x5b\x11\
507 \xa0\xe6\xca\x00\xd7\x1b\x60\x8e\xd7\x47\xd3\x61\x5f\x1f\x4c\xb7\
508 \xbe\x47\x24\x2a\xe0\xc6\xec\x62\xfa\x20\xd9\xb6\xfe\x19\x31\x7e\
509 \xe0\x4b\xd9\xf7\x71\x67\x18\x41\x94\x62\xdc\x13\x84\x2b\xa5\xcc\
510 \xc3\xb6\x9e\xc8\xa6\x82\x80\x73\xc8\x46\x18\xe3\x4a\xfa\x42\x78\
511 \xa3\x7e\x6d\x84\x66\x06\xdc\x5a\x8b\x00\x48\x71\x61\x0c\x31\xd2\
512 \x13\x21\x91\xa1\x0e\xb7\x15\xe8\x8b\x54\x49\x3b\x45\x40\xf1\x83\
513 \xd2\xd2\xd3\xa7\x2a\x10\xab\x6f\x0e\xbd\xf2\x53\xa5\x42\x22\xa0\
514 \x4f\xb8\xbb\x8c\xcb\x5b\xd3\x97\x9e\x14\xc4\x30\x30\xf5\xc6\x52\
515 \x7f\xdf\x02\xe9\xf6\xc4\xa3\x1f\x79\x92\x5e\xad\x8f\xbf\x07\x1c\
516 \xf9\xd5\xf2\x11\xc2\xbe\x27\x3a\xf9\xe7\x34\x24\x93\xc0\xc1\x13\
517 \x01\x11\x81\x0a\x5e\x3a\x0d\x61\x14\xd1\xa1\x27\x41\x7b\xdc\xff\
518 \x0b\x1a\xff\x77\xe0\xc7\x8e\x3f\xd7\x99\x52\x83\xce\x06\x9d\x0d\
519 \x3a\x7b\x6e\x9d\xc1\xa0\x4c\x19\xe5\x8d\x84\x26\x42\x85\x6a\x10\
520 \xda\x33\x5f\xad\x41\x68\x2f\x53\x68\x1a\xe6\xf7\x75\x30\x08\x6d\
521 \x10\xda\x20\xb4\xdf\x0d\xbc\xe1\xb8\x33\xfa\xc5\xb8\xfb\x97\xbc\
522 \xb7\xf3\x0b\x46\x33\xee\x37\
523523 "
524524
525525 qt_resource_name = b"\
527527 \x00\x6f\xa6\x53\
528528 \x00\x69\
529529 \x00\x63\x00\x6f\x00\x6e\x00\x73\
530 \x00\x08\
531 \x05\x1c\x5a\x47\
532 \x00\x61\
533 \x00\x6e\x00\x6b\x00\x69\x00\x2e\x00\x70\x00\x6e\x00\x67\
534 \x00\x09\
535 \x08\x97\x87\xa7\
536 \x00\x68\
537 \x00\x65\x00\x61\x00\x72\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\
538 \x00\x08\
539 \x0b\x9e\x57\x87\
540 \x00\x64\
541 \x00\x65\x00\x63\x00\x6b\x00\x2e\x00\x73\x00\x76\x00\x67\
530542 \x00\x10\
531543 \x08\x12\xae\xa7\
532544 \x00\x6d\
533545 \x00\x65\x00\x64\x00\x69\x00\x61\x00\x2d\x00\x72\x00\x65\x00\x63\x00\x6f\x00\x72\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
534 \x00\x08\
535 \x05\x1c\x5a\x47\
536 \x00\x61\
537 \x00\x6e\x00\x6b\x00\x69\x00\x2e\x00\x70\x00\x6e\x00\x67\
538546 \x00\x07\
539547 \x0a\x7a\x5a\x27\
540548 \x00\x74\
541549 \x00\x61\x00\x67\x00\x2e\x00\x73\x00\x76\x00\x67\
542 \x00\x08\
543 \x0b\x9e\x57\x87\
544 \x00\x64\
545 \x00\x65\x00\x63\x00\x6b\x00\x2e\x00\x73\x00\x76\x00\x67\
550 \x00\x0c\
551 \x0e\xcd\x03\x47\
552 \x00\x6e\
553 \x00\x6f\x00\x74\x00\x65\x00\x74\x00\x79\x00\x70\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\
546554 \x00\x0e\
547555 \x04\x44\x35\x07\
548556 \x00\x63\
549557 \x00\x6f\x00\x6c\x00\x6c\x00\x65\x00\x63\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\
550 \x00\x09\
551 \x08\x97\x87\xa7\
552 \x00\x68\
553 \x00\x65\x00\x61\x00\x72\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\
554 \x00\x0c\
555 \x0e\xcd\x03\x47\
556 \x00\x6e\
557 \x00\x6f\x00\x74\x00\x65\x00\x74\x00\x79\x00\x70\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\
558558 "
559559
560560 qt_resource_struct_v1 = b"\
561561 \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
562562 \x00\x00\x00\x00\x00\x02\x00\x00\x00\x07\x00\x00\x00\x02\
563 \x00\x00\x00\x76\x00\x01\x00\x00\x00\x01\x00\x00\x13\x38\
564 \x00\x00\x00\x36\x00\x00\x00\x00\x00\x01\x00\x00\x02\xdb\
563 \x00\x00\x00\xac\x00\x01\x00\x00\x00\x01\x00\x00\x1c\x9b\
565564 \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
566 \x00\x00\x00\x98\x00\x00\x00\x00\x00\x01\x00\x00\x15\xa4\
567 \x00\x00\x00\x4c\x00\x00\x00\x00\x00\x01\x00\x00\x09\x97\
568 \x00\x00\x00\x60\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xcb\
569 \x00\x00\x00\xb0\x00\x00\x00\x00\x00\x01\x00\x00\x19\xae\
565 \x00\x00\x00\x54\x00\x00\x00\x00\x00\x01\x00\x00\x10\x33\
566 \x00\x00\x00\x26\x00\x00\x00\x00\x00\x01\x00\x00\x06\xbc\
567 \x00\x00\x00\x7a\x00\x00\x00\x00\x00\x01\x00\x00\x13\x0e\
568 \x00\x00\x00\x3e\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xc6\
569 \x00\x00\x00\x8e\x00\x00\x00\x00\x00\x01\x00\x00\x17\x42\
570570 "
571571
572572 qt_resource_struct_v2 = b"\
574574 \x00\x00\x00\x00\x00\x00\x00\x00\
575575 \x00\x00\x00\x00\x00\x02\x00\x00\x00\x07\x00\x00\x00\x02\
576576 \x00\x00\x00\x00\x00\x00\x00\x00\
577 \x00\x00\x00\x76\x00\x01\x00\x00\x00\x01\x00\x00\x13\x38\
577 \x00\x00\x00\xac\x00\x01\x00\x00\x00\x01\x00\x00\x1c\x9b\
578578 \x00\x00\x01\x5f\xb2\xe6\x4b\xb0\
579 \x00\x00\x00\x36\x00\x00\x00\x00\x00\x01\x00\x00\x02\xdb\
579 \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
580580 \x00\x00\x01\x37\x58\x6b\x1d\xa0\
581 \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
581 \x00\x00\x00\x54\x00\x00\x00\x00\x00\x01\x00\x00\x10\x33\
582582 \x00\x00\x01\x5f\xb2\xe6\x4b\xb0\
583 \x00\x00\x00\x98\x00\x00\x00\x00\x00\x01\x00\x00\x15\xa4\
583 \x00\x00\x00\x26\x00\x00\x00\x00\x00\x01\x00\x00\x06\xbc\
584584 \x00\x00\x01\x5f\xb2\xe6\x4b\xb0\
585 \x00\x00\x00\x4c\x00\x00\x00\x00\x00\x01\x00\x00\x09\x97\
585 \x00\x00\x00\x7a\x00\x00\x00\x00\x00\x01\x00\x00\x13\x0e\
586586 \x00\x00\x01\x5f\xb2\xe6\x4b\xb0\
587 \x00\x00\x00\x60\x00\x00\x00\x00\x00\x01\x00\x00\x0d\xcb\
587 \x00\x00\x00\x3e\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xc6\
588588 \x00\x00\x01\x5f\xb2\xe6\x4b\xb0\
589 \x00\x00\x00\xb0\x00\x00\x00\x00\x00\x01\x00\x00\x19\xae\
589 \x00\x00\x00\x8e\x00\x00\x00\x00\x00\x01\x00\x00\x17\x42\
590590 \x00\x00\x01\x5f\xb2\xe6\x4b\xb0\
591591 "
592592
969969 Invalid property found on card. Please use Tools>Check Database, \
970970 and if the problem comes up again, please ask on the support site."""))
971971
972 def onMpvWillPlay(self):
973 if not self._activeWindowOnPlay:
974 self._activeWindowOnPlay = self.app.activeWindow()
972 def _isVideo(self, file):
973 head, ext = os.path.splitext(file.lower())
974 return ext in (".mp4", ".mov", ".mpg", ".mpeg", ".mkv", ".avi")
975
976 def onMpvWillPlay(self, file):
977 if not self._isVideo(file):
978 return
979
980 self._activeWindowOnPlay = self.app.activeWindow() or self._activeWindowOnPlay
975981
976982 def onMpvIdle(self):
977983 w = self._activeWindowOnPlay
978 if w and not sip.isdeleted(w) and w.isVisible():
984 if not self.app.activeWindow() and w and not sip.isdeleted(w) and w.isVisible():
979985 w.activateWindow()
980986 w.raise_()
981987 self._activeWindowOnPlay = None
4242 # create the thread, setup signals and start running
4343 t = self.thread = SyncThread(
4444 self.pm.collectionPath(), self.pm.profile['syncKey'],
45 auth=auth, media=self.pm.profile['syncMedia'])
45 auth=auth, media=self.pm.profile['syncMedia'],
46 hostNum=self.pm.profile.get("hostNum"),
47 )
4648 t.event.connect(self.onEvent)
4749 self.label = _("Connecting...")
4850 prog = self.mw.progress.start(immediate=True, label=self.label)
6365 showText(self.thread.syncMsg)
6466 if self.thread.uname:
6567 self.pm.profile['syncUser'] = self.thread.uname
68 self.pm.profile['hostNum'] = self.thread.hostNum
6669 def delayedInfo():
6770 if self._didFullUp and not self._didError:
6871 showInfo(_("""\
289292
290293 event = pyqtSignal(str, str)
291294
292 def __init__(self, path, hkey, auth=None, media=True):
295 def __init__(self, path, hkey, auth=None, media=True, hostNum=None):
293296 QThread.__init__(self)
294297 self.path = path
295298 self.hkey = hkey
296299 self.auth = auth
297300 self.media = media
301 self.hostNum = hostNum
298302 self._abort = 0 # 1=flagged, 2=aborting
299303
300304 def flagAbort(self):
310314 except:
311315 self.fireEvent("corrupt")
312316 return
313 self.server = RemoteServer(self.hkey)
317 self.server = RemoteServer(self.hkey, hostNum=self.hostNum)
314318 self.client = Syncer(self.col, self.server)
315319 self.sentTotal = 0
316320 self.recvTotal = 0
404408 self.fireEvent("error", "Unknown sync return code.")
405409 self.syncMsg = self.client.syncMsg
406410 self.uname = self.client.uname
411 self.hostNum = self.client.hostNum
407412 # then move on to media sync
408413 self._syncMedia()
409414
418423 f = self.fullSyncChoice
419424 if f == "cancel":
420425 return
421 self.client = FullSyncer(self.col, self.hkey, self.server.client)
426 self.client = FullSyncer(self.col, self.hkey, self.server.client,
427 hostNum=self.hostNum)
422428 try:
423429 if f == "upload":
424430 if not self.client.upload():
436442 def _syncMedia(self):
437443 if not self.media:
438444 return
439 self.server = RemoteMediaServer(self.col, self.hkey, self.server.client)
445 self.server = RemoteMediaServer(self.col, self.hkey, self.server.client,
446 hostNum=self.hostNum)
440447 self.client = MediaSyncer(self.col, self.server)
441448 try:
442449 ret = self.client.sync()
271271 cb(file)
272272 ret.append(file)
273273 d.accepted.connect(accept)
274 if key:
275 restoreState(d, key)
274276 d.exec_()
277 if key:
278 saveState(d, key)
275279 return ret and ret[0]
276280
277281 def getSaveFile(parent, title, dir_description, key, ext, fname=None):
323323 <property name="text">
324324 <string>&amp;Reschedule...</string>
325325 </property>
326 <property name="shortcut">
327 <string>Ctrl+Alt+R</string>
328 </property>
326329 </action>
327330 <action name="actionSelectAll">
328331 <property name="text">
343346 <action name="actionInvertSelection">
344347 <property name="text">
345348 <string>&amp;Invert Selection</string>
349 </property>
350 <property name="shortcut">
351 <string>Ctrl+Alt+S</string>
346352 </property>
347353 </action>
348354 <action name="actionFind">
135135 # pass it once
136136 d.sched.answerCard(c, 3)
137137 # it should by due in 3 minutes
138 assert round(c.due - time.time()) in (179, 180)
138 dueIn = c.due - time.time()
139 assert 179 <= dueIn <= 180*1.25
139140 assert c.left%1000 == 2
140141 assert c.left//1000 == 2
141142 # check log is accurate
146147 # pass again
147148 d.sched.answerCard(c, 3)
148149 # it should by due in 10 minutes
149 assert round(c.due - time.time()) in (599, 600)
150 dueIn = c.due - time.time()
151 assert 599 <= dueIn <= 600*1.25
150152 assert c.left%1000 == 1
151153 assert c.left//1000 == 1
152154 # the next pass should graduate the card
11061108 c = d.sched.getCard()
11071109 d.sched.answerCard(c, 1)
11081110 assert c.ivl == 50
1109 # failing again, the actual elapsed interval is 0,
1110 # so the card is reset to new
1111 d.sched.answerCard(c, 1)
1112 assert c.ivl == 1
1111 d.sched.answerCard(c, 1)
1112 assert c.ivl == 25
11131113
11141114 def test_moveVersions():
11151115 col = _getEmptyCol(schedVer=1)
+0
-370
tests/test_sync.py less more
0 # coding: utf-8
1
2 import nose, os, shutil, time
3
4 from anki import Collection as aopen, Collection
5 from anki.utils import intTime
6 from anki.sync import Syncer, LocalServer
7 from anki.consts import STARTING_FACTOR
8 from tests.shared import getEmptyCol, getEmptyDeckWith
9 import anki.stdmodels
10
11 # Local tests
12 ##########################################################################
13
14 deck1=None
15 deck2=None
16 client=None
17 server=None
18 server2=None
19
20 def setup_basic():
21 global deck1, deck2, client, server
22 deck1 = getEmptyCol()
23 # add a note to deck 1
24 f = deck1.newNote()
25 f['Front'] = "foo"; f['Back'] = "bar"; f.tags = ["foo"]
26 deck1.addNote(f)
27 # answer it
28 deck1.reset(); deck1.sched.answerCard(deck1.sched.getCard(), 4)
29 # repeat for deck2
30 deck2 = getEmptyDeckWith(server=True)
31 f = deck2.newNote()
32 f['Front'] = "bar"; f['Back'] = "bar"; f.tags = ["bar"]
33 deck2.addNote(f)
34 deck2.reset(); deck2.sched.answerCard(deck2.sched.getCard(), 4)
35 # start with same schema and sync time
36 deck1.scm = deck2.scm = 0
37 # and same mod time, so sync does nothing
38 t = intTime(1000)
39 deck1.save(mod=t); deck2.save(mod=t)
40 server = LocalServer(deck2)
41 client = Syncer(deck1, server)
42
43 def setup_modified():
44 setup_basic()
45 # mark deck1 as changed
46 time.sleep(0.1)
47 deck1.setMod()
48 deck1.save()
49
50 @nose.with_setup(setup_basic)
51 def test_nochange():
52 assert client.sync() == "noChanges"
53
54 @nose.with_setup(setup_modified)
55 def test_changedSchema():
56 deck1.scm += 1
57 deck1.setMod()
58 assert client.sync() == "fullSync"
59
60 @nose.with_setup(setup_modified)
61 def test_sync():
62 def check(num):
63 for d in deck1, deck2:
64 for t in ("revlog", "notes", "cards"):
65 assert d.db.scalar("select count() from %s" % t) == num
66 assert len(d.models.all()) == num*len(anki.stdmodels.models)
67 # the default deck and config have an id of 1, so always 1
68 assert len(d.decks.all()) == 1
69 assert len(d.decks.dconf) == 1
70 assert len(d.tags.all()) == num
71 check(1)
72 origUsn = deck1.usn()
73 assert client.sync() == "success"
74 # last sync times and mod times should agree
75 assert deck1.mod == deck2.mod
76 assert deck1._usn == deck2._usn
77 assert deck1.mod == deck1.ls
78 assert deck1._usn != origUsn
79 # because everything was created separately it will be merged in. in
80 # actual use, we use a full sync to ensure a common starting point.
81 check(2)
82 # repeating it does nothing
83 assert client.sync() == "noChanges"
84 # if we bump mod time, the decks will sync but should remain the same.
85 deck1.setMod()
86 deck1.save(mod=deck1.mod+1)
87 assert client.sync() == "success"
88 check(2)
89 # crt should be synced
90 deck1.crt = 123
91 deck1.setMod()
92 deck1.save(mod=deck1.mod+1)
93 ret = client.sync(); assert ret == "success"
94 assert deck1.crt == deck2.crt
95
96 @nose.with_setup(setup_modified)
97 def test_models():
98 test_sync()
99 # update model one
100 cm = deck1.models.current()
101 cm['name'] = "new"
102 time.sleep(1)
103 deck1.models.save(cm)
104 deck1.save()
105 assert deck2.models.get(cm['id'])['name'].startswith("Basic")
106 assert client.sync() == "success"
107 assert deck2.models.get(cm['id'])['name'] == "new"
108 # deleting triggers a full sync
109 deck1.scm = deck2.scm = 0
110 deck1.models.rem(cm)
111 deck1.save()
112 assert client.sync() == "fullSync"
113
114 @nose.with_setup(setup_modified)
115 def test_notes():
116 test_sync()
117 # modifications should be synced
118 nid = deck1.db.scalar("select id from notes")
119 note = deck1.getNote(nid)
120 assert note['Front'] != "abc"
121 note['Front'] = "abc"
122 note.flush()
123 deck1.save()
124 assert client.sync() == "success"
125 assert deck2.getNote(nid)['Front'] == "abc"
126 # deletions too
127 assert deck1.db.scalar("select 1 from notes where id = ?", nid)
128 deck1.remNotes([nid])
129 deck1.save()
130 assert client.sync() == "success"
131 assert not deck1.db.scalar("select 1 from notes where id = ?", nid)
132 assert not deck2.db.scalar("select 1 from notes where id = ?", nid)
133
134 @nose.with_setup(setup_modified)
135 def test_cards():
136 test_sync()
137 nid = deck1.db.scalar("select id from notes")
138 note = deck1.getNote(nid)
139 card = note.cards()[0]
140 # answer the card locally
141 card.startTimer()
142 deck1.sched.answerCard(card, 4)
143 assert card.reps == 2
144 deck1.save()
145 assert deck2.getCard(card.id).reps == 1
146 assert client.sync() == "success"
147 assert deck2.getCard(card.id).reps == 2
148 # if it's modified on both sides , later mod time should win
149 for test in ((deck1, deck2), (deck2, deck1)):
150 time.sleep(1)
151 c = test[0].getCard(card.id)
152 c.reps = 5; c.flush()
153 test[0].save()
154 time.sleep(1)
155 c = test[1].getCard(card.id)
156 c.reps = 3; c.flush()
157 test[1].save()
158 assert client.sync() == "success"
159 assert test[1].getCard(card.id).reps == 3
160 assert test[0].getCard(card.id).reps == 3
161 # removals should work too
162 deck1.remCards([card.id])
163 deck1.save()
164 assert deck2.db.scalar("select 1 from cards where id = ?", card.id)
165 assert client.sync() == "success"
166 assert not deck2.db.scalar("select 1 from cards where id = ?", card.id)
167
168 @nose.with_setup(setup_modified)
169 def test_tags():
170 test_sync()
171 def sortedTags(deck):
172 return sorted(deck.tags.all())
173 assert sortedTags(deck1) == sortedTags(deck2)
174 deck1.tags.register(["abc"])
175 deck2.tags.register(["xyz"])
176 assert sortedTags(deck1) != sortedTags(deck2)
177 deck1.save()
178 time.sleep(0.1)
179 deck2.save()
180 assert client.sync() == "success"
181 assert sortedTags(deck1) == sortedTags(deck2)
182
183 @nose.with_setup(setup_modified)
184 def test_decks():
185 test_sync()
186 assert len(deck1.decks.all()) == 1
187 assert len(deck1.decks.all()) == len(deck2.decks.all())
188 deck1.decks.id("new")
189 assert len(deck1.decks.all()) != len(deck2.decks.all())
190 time.sleep(0.1)
191 deck2.decks.id("new2")
192 deck1.save()
193 time.sleep(0.1)
194 deck2.save()
195 assert client.sync() == "success"
196 assert sorted(deck1.tags.all()) == sorted(deck2.tags.all())
197 assert len(deck1.decks.all()) == len(deck2.decks.all())
198 assert len(deck1.decks.all()) == 3
199 assert deck1.decks.confForDid(1)['maxTaken'] == 60
200 deck2.decks.confForDid(1)['maxTaken'] = 30
201 deck2.decks.save(deck2.decks.confForDid(1))
202 deck2.save()
203 assert client.sync() == "success"
204 assert deck1.decks.confForDid(1)['maxTaken'] == 30
205
206 @nose.with_setup(setup_modified)
207 def test_conf():
208 test_sync()
209 assert deck2.conf['curDeck'] == 1
210 deck1.conf['curDeck'] = 2
211 time.sleep(0.1)
212 deck1.setMod()
213 deck1.save()
214 assert client.sync() == "success"
215 assert deck2.conf['curDeck'] == 2
216
217 @nose.with_setup(setup_modified)
218 def test_threeway():
219 test_sync()
220 deck1.close(save=False)
221 d3path = deck1.path.replace(".anki", "2.anki")
222 shutil.copy2(deck1.path, d3path)
223 deck1.reopen()
224 deck3 = aopen(d3path)
225 client2 = Syncer(deck3, server)
226 assert client2.sync() == "noChanges"
227 # client 1 adds a card at time 1
228 time.sleep(1)
229 f = deck1.newNote()
230 f['Front'] = "1";
231 deck1.addNote(f)
232 deck1.save()
233 # at time 2, client 2 syncs to server
234 time.sleep(1)
235 deck3.setMod()
236 deck3.save()
237 assert client2.sync() == "success"
238 # at time 3, client 1 syncs, adding the older note
239 time.sleep(1)
240 assert client.sync() == "success"
241 assert deck1.noteCount() == deck2.noteCount()
242 # syncing client2 should pick it up
243 assert client2.sync() == "success"
244 assert deck1.noteCount() == deck2.noteCount() == deck3.noteCount()
245
246 def test_threeway2():
247 # for this test we want ms precision of notes so we don't have to
248 # sleep a lot
249 import anki.notes
250 intTime = anki.notes.intTime
251 anki.notes.intTime = lambda x=1: intTime(1000)
252 def setup():
253 # create collection 1 with a single note
254 c1 = getEmptyCol()
255 f = c1.newNote()
256 f['Front'] = "startingpoint"
257 nid = f.id
258 c1.addNote(f)
259 cid = f.cards()[0].id
260 c1.beforeUpload()
261 # start both clients and server off in this state
262 s1path = c1.path.replace(".anki2", "-s1.anki2")
263 c2path = c1.path.replace(".anki2", "-c2.anki2")
264 shutil.copy2(c1.path, s1path)
265 shutil.copy2(c1.path, c2path)
266 # open them
267 c1 = Collection(c1.path)
268 c2 = Collection(c2path)
269 s1 = Collection(s1path, server=True)
270 return c1, c2, s1, nid, cid
271 c1, c2, s1, nid, cid = setup()
272 # modify c1 then sync c1->s1
273 n = c1.getNote(nid)
274 t = "firstmod"
275 n['Front'] = t
276 n.flush()
277 c1.db.execute("update cards set mod=1, usn=-1")
278 srv = LocalServer(s1)
279 clnt1 = Syncer(c1, srv)
280 clnt1.sync()
281 n.load()
282 assert n['Front'] == t
283 assert s1.getNote(nid)['Front'] == t
284 assert s1.db.scalar("select mod from cards") == 1
285 # sync s1->c2
286 clnt2 = Syncer(c2, srv)
287 clnt2.sync()
288 assert c2.getNote(nid)['Front'] == t
289 assert c2.db.scalar("select mod from cards") == 1
290 # modify c1 and sync
291 time.sleep(0.001)
292 t = "secondmod"
293 n = c1.getNote(nid)
294 n['Front'] = t
295 n.flush()
296 c1.db.execute("update cards set mod=2, usn=-1")
297 clnt1.sync()
298 # modify c2 and sync - both c2 and server should be the same
299 time.sleep(0.001)
300 t2 = "thirdmod"
301 n = c2.getNote(nid)
302 n['Front'] = t2
303 n.flush()
304 c2.db.execute("update cards set mod=3, usn=-1")
305 clnt2.sync()
306 n.load()
307 assert n['Front'] == t2
308 assert c2.db.scalar("select mod from cards") == 3
309 n = s1.getNote(nid)
310 assert n['Front'] == t2
311 assert s1.db.scalar("select mod from cards") == 3
312 # and syncing c1 again should yield the updated note as well
313 clnt1.sync()
314 n = s1.getNote(nid)
315 assert n['Front'] == t2
316 assert s1.db.scalar("select mod from cards") == 3
317 n = c1.getNote(nid)
318 assert n['Front'] == t2
319 assert c1.db.scalar("select mod from cards") == 3
320
321 def _test_speed():
322 t = time.time()
323 deck1 = aopen(os.path.expanduser("~/rapid.anki"))
324 for tbl in "revlog", "cards", "notes", "graves":
325 deck1.db.execute("update %s set usn = -1 where usn != -1"%tbl)
326 for m in deck1.models.all():
327 m['usn'] = -1
328 for tx in deck1.tags.all():
329 deck1.tags.tags[tx] = -1
330 deck1._usn = -1
331 deck1.save()
332 deck2 = getEmptyDeckWith(server=True)
333 deck1.scm = deck2.scm = 0
334 server = LocalServer(deck2)
335 client = Syncer(deck1, server)
336 print("load %d" % ((time.time() - t)*1000)); t = time.time()
337 assert client.sync() == "success"
338 print("sync %d" % ((time.time() - t)*1000)); t = time.time()
339
340 @nose.with_setup(setup_modified)
341 def test_filtered_delete():
342 test_sync()
343 nid = deck1.db.scalar("select id from notes")
344 note = deck1.getNote(nid)
345 card = note.cards()[0]
346 card.queue = 2
347 card.type = 2
348 card.ivl = 10
349 card.factor = STARTING_FACTOR
350 card.due = deck1.sched.today
351 card.flush()
352 # put cards into a filtered deck
353 did = deck1.decks.newDyn("dyn")
354 deck1.sched.rebuildDyn(did)
355 # sync the filtered deck
356 assert client.sync() == "success"
357 # answer the card locally
358 time.sleep(1)
359 card.load()
360 card.startTimer()
361 deck1.sched.answerCard(card, 4)
362 assert card.ivl > 10
363 # delete the filtered deck
364 deck1.decks.rem(did)
365 # sync again
366 assert client.sync() == "success"
367 card.load()
368 assert card.ivl > 10
369 return
2727 }
2828 }
2929
30 function triggerKeyTimer() {
31 clearChangeTimer();
32 changeTimer = setTimeout(function () {
33 updateButtonState();
34 saveField("key");
35 }, 600);
36 }
37
3038 function onKey() {
3139 // esc clears focus, allowing dialog to close
3240 if (window.event.which === 27) {
4048 focusPrevious();
4149 return;
4250 }
43 clearChangeTimer();
44 changeTimer = setTimeout(function () {
45 updateButtonState();
46 saveField("key");
47 }, 600);
51 triggerKeyTimer();
4852 }
4953
5054 function insertNewline() {
8185 return window.getComputedStyle(n).whiteSpace.startsWith("pre");
8286 }
8387
84 function checkForEmptyField() {
88 function onInput() {
89 // empty field?
8590 if (currentField.innerHTML === "") {
8691 currentField.innerHTML = "<br>";
8792 }
93
94 // make sure IME changes get saved
95 triggerKeyTimer();
8896 }
8997
9098 function updateButtonState() {
287295 f = "<br>";
288296 }
289297 txt += "<tr><td class=fname>{0}</td></tr><tr><td width=100%>".format(n);
290 txt += "<div id=f{0} onkeydown='onKey();' oninput='checkForEmptyField()' onmouseup='onKey();'".format(i);
298 txt += "<div id=f{0} onkeydown='onKey();' oninput='onInput()' onmouseup='onKey();'".format(i);
291299 txt += " onfocus='onFocus(this);' onblur='onBlur();' class=field ";
292300 txt += "ondragover='onDragOver(this);' onpaste='onPaste(this);' ";
293301 txt += "oncopy='onCutOrCopy(this);' oncut='onCutOrCopy(this);' ";