├── .gitignore ├── COPYING.txt ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.md ├── ldoce5viewer.desktop ├── ldoce5viewer.py ├── ldoce5viewer ├── __init__.py ├── fulltext.py ├── incremental.py ├── ldoce5 │ ├── __init__.py │ ├── advtree.py │ ├── extract.py │ ├── filemap.py │ ├── idmreader.py │ ├── transform.py │ ├── transform_body.py │ └── utils.py ├── qtgui │ ├── __init__.py │ ├── access.py │ ├── advanced.py │ ├── async_.py │ ├── config.py │ ├── indexer.py │ ├── main.py │ ├── resources │ │ ├── Makefile │ │ ├── __init__.py │ │ ├── icons │ │ │ ├── LICENSE.txt │ │ │ ├── Makefile │ │ │ ├── application-exit-16.png │ │ │ ├── application-exit-22.png │ │ │ ├── application-exit-24.png │ │ │ ├── application-exit.svg │ │ │ ├── document-print-16.png │ │ │ ├── document-print-22.png │ │ │ ├── document-print-24.png │ │ │ ├── document-print-preview-16.png │ │ │ ├── document-print-preview-22.png │ │ │ ├── document-print-preview-24.png │ │ │ ├── document-print-preview.svg │ │ │ ├── document-print.svg │ │ │ ├── document-properties-16.png │ │ │ ├── document-properties-22.png │ │ │ ├── document-properties-24.png │ │ │ ├── document-properties.svg │ │ │ ├── edit-clear-16.png │ │ │ ├── edit-clear-22.png │ │ │ ├── edit-clear-24.png │ │ │ ├── edit-clear-48-src.png │ │ │ ├── edit-clear-48.png │ │ │ ├── edit-clear.svg │ │ │ ├── edit-copy-16.png │ │ │ ├── edit-copy-22.png │ │ │ ├── edit-copy-24.png │ │ │ ├── edit-copy.svg │ │ │ ├── edit-find-16.png │ │ │ ├── edit-find-22.png │ │ │ ├── edit-find-24.png │ │ │ ├── edit-find.svg │ │ │ ├── go-down-16.png │ │ │ ├── go-down-22.png │ │ │ ├── go-down-24.png │ │ │ ├── go-down.svg │ │ │ ├── go-next-16.png │ │ │ ├── go-next-22.png │ │ │ ├── go-next-24.png │ │ │ ├── go-next.svg │ │ │ ├── go-previous-16.png │ │ │ ├── go-previous-22.png │ │ │ ├── go-previous-24.png │ │ │ ├── go-previous.svg │ │ │ ├── go-up-16.png │ │ │ ├── go-up-22.png │ │ │ ├── go-up-24.png │ │ │ ├── go-up.svg │ │ │ ├── help-about-16.png │ │ │ ├── help-about-22.png │ │ │ ├── help-about-24.png │ │ │ ├── help-about.svg │ │ │ ├── help-contents-16.png │ │ │ ├── help-contents-22.png │ │ │ ├── help-contents-24.png │ │ │ ├── help-contents.svg │ │ │ ├── iconblock-16.png │ │ │ ├── iconblock-22.png │ │ │ ├── iconblock-24.png │ │ │ ├── iconblock.svg │ │ │ ├── icongen.py │ │ │ ├── reload-16.png │ │ │ ├── reload-22.png │ │ │ ├── reload-24.png │ │ │ ├── reload.svg │ │ │ ├── star-16.png │ │ │ ├── star-22.png │ │ │ ├── star-24.png │ │ │ ├── star.svg │ │ │ ├── window-close-16.png │ │ │ ├── window-close-22.png │ │ │ ├── window-close-24.png │ │ │ ├── window-close.svg │ │ │ ├── zoom-in-16.png │ │ │ ├── zoom-in-22.png │ │ │ ├── zoom-in-24.png │ │ │ ├── zoom-in.svg │ │ │ ├── zoom-original-16.png │ │ │ ├── zoom-original-22.png │ │ │ ├── zoom-original-24.png │ │ │ ├── zoom-original.svg │ │ │ ├── zoom-out-16.png │ │ │ ├── zoom-out-22.png │ │ │ ├── zoom-out-24.png │ │ │ └── zoom-out.svg │ │ ├── ldoce5viewer.icns │ │ ├── ldoce5viewer.ico │ │ ├── ldoce5viewer.png │ │ ├── ldoce5viewer.svg │ │ ├── next-mac.png │ │ ├── prev-mac.png │ │ └── resource.qrc │ ├── ui │ │ ├── Makefile │ │ ├── __init__.py │ │ ├── advanced.ui │ │ ├── custom.py │ │ ├── indexer.ui │ │ └── main.ui │ └── utils │ │ ├── __init__.py │ │ ├── error.py │ │ ├── fontfallback.py │ │ ├── mp3play │ │ ├── __init__.py │ │ └── windows.py │ │ ├── singleapp.py │ │ └── soundplayer.py ├── static │ ├── documents │ │ └── about.html │ ├── images │ │ ├── external-hover.png │ │ ├── external-link.png │ │ ├── external.png │ │ ├── sp.png │ │ ├── speaker_am.png │ │ ├── speaker_br.png │ │ └── speaker_eg.png │ ├── scripts │ │ ├── activator.js │ │ ├── body.js │ │ ├── collocations.js │ │ ├── colorbox │ │ │ ├── colorbox.css │ │ │ ├── images │ │ │ │ ├── close.png │ │ │ │ └── loading.gif │ │ │ └── jquery.colorbox.js │ │ ├── common.js │ │ ├── entry.js │ │ ├── etymologies.js │ │ ├── examples.js │ │ ├── jquery.js │ │ ├── phrases.js │ │ ├── search.js │ │ ├── thesaurus.js │ │ ├── word_families.js │ │ └── word_sets.js │ └── styles │ │ ├── about.css │ │ ├── activator.css │ │ ├── body.css │ │ ├── collocations.css │ │ ├── colorbox.css │ │ ├── common.css │ │ ├── entry.css │ │ ├── etymologies.css │ │ ├── examples.css │ │ ├── list.css │ │ ├── phrases.css │ │ ├── search.css │ │ ├── thesaurus.css │ │ ├── word_families.css │ │ └── word_sets.css └── utils │ ├── __init__.py │ ├── cdb.py │ ├── compat.py │ └── text.py ├── scripts └── ldoce5viewer └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dist/ 3 | build/ 4 | *egg-info/ 5 | exedist/ 6 | buildexe.bat 7 | innosetup.iss 8 | run.bat 9 | msvcx90/ 10 | wininst/ 11 | ldoce5viewer/qtgui/ui/main.py 12 | ldoce5viewer/qtgui/ui/advanced.py 13 | ldoce5viewer/qtgui/ui/main.py 14 | ldoce5viewer/qtgui/ui/indexer.py 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | LDOCE5 Viewer: 2 | by Taku Fukada 3 | GNU General Public License version 3 or later 4 | 5 | An implementaiton of cdb (./ldoce5viewer/utils/cdb.py): 6 | Public Domain 7 | 8 | Whoosh (./ldoce5viewer/whoosh/): 9 | by Matt Chaput 10 | (two-clause) BSD License 11 | 12 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.txt 2 | include COPYING.txt 3 | include LICENSE.txt 4 | include whoosh/LICENSE.txt 5 | include ldoce5viewer.py 6 | include ldoce5viewer.desktop 7 | include scripts/ldoce5viewer 8 | include ldoce5viewer/qtgui/resources/ldoce5viewer.svg 9 | recursive-include ldoce5viewer/static * 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PKG := ldoce5viewer 2 | PYTHON := python3 3 | 4 | build: precompile 5 | $(PYTHON) ./setup.py build 6 | 7 | install: build 8 | $(PYTHON) ./setup.py install 9 | cp ./ldoce5viewer.desktop /usr/share/applications/ 10 | cp ./ldoce5viewer/qtgui/resources/ldoce5viewer.svg /usr/share/pixmaps/ 11 | [ -x /usr/bin/update-desktop-database ] && sudo update-desktop-database -q 12 | 13 | sdist: precompile 14 | $(PYTHON) ./setup.py sdist 15 | 16 | precompile: qtui qtresource 17 | 18 | qtui: 19 | cd $(PKG)/qtgui/ui/; $(MAKE) 20 | 21 | qtresource: 22 | cd $(PKG)/qtgui/resources/; $(MAKE) 23 | 24 | .PHONY: clean clean-build 25 | clean: clean-build 26 | cd $(PKG)/qtgui/ui/; $(MAKE) clean 27 | cd $(PKG)/qtgui/resources/; $(MAKE) clean 28 | 29 | clean-build: 30 | rm -rf build 31 | rm -rf dist 32 | rm -f MANIFEST 33 | 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #LDOCE5 Viewer (PyQt5) 2 | 3 | This project is ported to PyQt5 which supports retina (HiDPI) display. 4 | 5 | The LDOCE5 Viewer is an alternative dictionary viewer for the Longman Dictionary of Contemporary English 5th Edition (LDOCE 5). 6 | 7 | Website: http://hakidame.net/ldoce5viewer/ 8 | 9 | It runs on Linux, Mac OS X and Microsoft Windows. 10 | 11 | This software is free and open source software licensed under the terms of GPLv3. 12 | 13 | 14 | -------------------------------------------------------------------------------- /ldoce5viewer.desktop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env xdg-open 2 | 3 | [Desktop Entry] 4 | Name=LDOCE5 Viewer 5 | Comment=A dictionary viewer for LDOCE5 6 | Categories=Education;Dictionary 7 | Exec=ldoce5viewer 8 | Icon=ldoce5viewer 9 | Terminal=false 10 | Type=Application 11 | Version=1.0 12 | 13 | -------------------------------------------------------------------------------- /ldoce5viewer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | from ldoce5viewer import qtgui 6 | 7 | 8 | if __name__ == '__main__': 9 | sys.exit(qtgui.run(sys.argv)) 10 | sys.exit() 11 | 12 | -------------------------------------------------------------------------------- /ldoce5viewer/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from __future__ import unicode_literals, print_function 5 | 6 | __version__ = '2013.04.24' 7 | __author__ = 'Taku Fukada' 8 | 9 | 10 | -------------------------------------------------------------------------------- /ldoce5viewer/fulltext.py: -------------------------------------------------------------------------------- 1 | '''Full-text searcher for headwords/phrases/examples/definitions''' 2 | 3 | from __future__ import absolute_import 4 | 5 | import re 6 | import os.path 7 | from operator import itemgetter 8 | import fnmatch 9 | 10 | from whoosh import index as wh_index 11 | from whoosh.fields import Schema, STORED, IDLIST, ID, TEXT 12 | from whoosh.analysis import StandardAnalyzer, Filter 13 | from whoosh.query import Variations, Term, Or, And 14 | from whoosh.qparser import QueryParser, \ 15 | RangePlugin, BoostPlugin, WildcardPlugin, OperatorsPlugin 16 | from whoosh.highlight import WholeFragmenter, HtmlFormatter 17 | from whoosh.collectors import WrappingCollector, \ 18 | UnlimitedCollector, TopCollector 19 | 20 | from .utils.cdb import CDBReader, CDBMaker, CDBError 21 | from .utils.text import normalize_token, normalize_index_key,\ 22 | enc_utf8, dec_utf8 23 | 24 | 25 | class IndexError(Exception): 26 | pass 27 | 28 | 29 | class AbortableCollector(WrappingCollector): 30 | def __init__(self, child): 31 | WrappingCollector.__init__(self, child) 32 | self._aborted = False 33 | 34 | def collect_matches(self): 35 | collect = self.collect 36 | for sub_docnum in self.matches(): 37 | if self._aborted: 38 | break 39 | collect(sub_docnum) 40 | 41 | @property 42 | def aborted(self): 43 | return self._aborted 44 | 45 | def abort(self): 46 | self._aborted = True 47 | 48 | 49 | #----------------- 50 | # Word Vatiations 51 | #----------------- 52 | 53 | class VariationsReader(object): 54 | def __init__(self, path): 55 | self._path = path 56 | self._reader = None 57 | self._reader = CDBReader(path) 58 | 59 | def __del__(self): 60 | try: 61 | self.close() 62 | except: 63 | pass 64 | 65 | def close(self): 66 | if self._reader: 67 | self._reader.close() 68 | self._reader = None 69 | 70 | def get_variations(self, word): 71 | r = set((word, )) 72 | try: 73 | s = self._reader[enc_utf8(word)] 74 | except KeyError: 75 | return r 76 | 77 | r.update(dec_utf8(w) for w in s.split(b'\0')) 78 | return r 79 | 80 | 81 | class VariationsWriter(object): 82 | def __init__(self, f): 83 | self._writer = CDBMaker(f) 84 | 85 | def add(self, word, variations): 86 | self._writer.add( 87 | enc_utf8(word), 88 | b'\0'.join(enc_utf8(v) for v in variations)) 89 | 90 | def finalize(self): 91 | self._writer.finalize() 92 | 93 | 94 | def my_variations(var_reader): 95 | if var_reader: 96 | def f(fieldname, text, boost=1.0): 97 | return MyVariations(var_reader, fieldname, text, boost) 98 | return f 99 | else: 100 | return Term 101 | 102 | 103 | class MyVariations(Variations): 104 | def __init__(self, var_reader, fieldname, text, boost=1.0): 105 | super(MyVariations, self).__init__(fieldname, text, boost) 106 | self.__var_reader = var_reader 107 | self.__fieldname = fieldname 108 | self.__text = text 109 | self.__boost = boost 110 | self.__cache = {} 111 | 112 | def _words(self, ixreader): 113 | cache = self.__cache 114 | text = self.text 115 | if text in cache: 116 | return cache[text] 117 | else: 118 | fieldname = self.fieldname 119 | words = [word for word in self.__var_reader.get_variations(text) 120 | if (fieldname, word) in ixreader] 121 | cache[text] = words 122 | return words 123 | 124 | def __deepcopy__(self, x): 125 | return MyVariations(self.__var_reader, 126 | self.__fieldname, self.__text, self.__boost) 127 | 128 | 129 | #----------------- 130 | # Index Schema 131 | #----------------- 132 | 133 | class _AccentFilter(Filter): 134 | def __call__(self, tokens): 135 | for t in tokens: 136 | t.text = normalize_token(t.text) 137 | yield t 138 | 139 | _stopwords = frozenset(('a', 'an')) 140 | _analyzer = (StandardAnalyzer(stoplist=_stopwords) | _AccentFilter()) 141 | _schema = Schema( 142 | content=TEXT( 143 | stored=True, 144 | spelling=True, 145 | analyzer=_analyzer), 146 | data=STORED, # tuple (label, path, prio, sortkey) 147 | itemtype=ID, 148 | asfilter=IDLIST 149 | ) 150 | _schema['content'].scorable = False 151 | 152 | 153 | #----------------- 154 | # Maker 155 | #----------------- 156 | 157 | class Maker(object): 158 | def __init__(self, index_dir): 159 | if os.path.exists(index_dir) and os.path.isfile(index_dir): 160 | os.unlink(index_dir) 161 | 162 | if not os.path.exists(index_dir): 163 | os.makedirs(index_dir) 164 | 165 | index = wh_index.create_in(index_dir, _schema) 166 | self._index = index 167 | self._writer = index.writer() 168 | self._committed = False 169 | 170 | def add_item(self, 171 | itemtype, content, asfilter, label, path, prio, sortkey): 172 | self._writer.add_document( 173 | itemtype=itemtype, 174 | content=content, 175 | asfilter=asfilter, 176 | data=(label, path, prio, normalize_index_key(sortkey)) 177 | ) 178 | 179 | def commit(self): 180 | self._committed = True 181 | self._writer.commit() 182 | 183 | def close(self): 184 | if not self._committed: 185 | self._writer.cancel() 186 | 187 | self._index.close() 188 | self._index = None 189 | self._writer = None 190 | 191 | 192 | #----------------- 193 | # Searcher 194 | #----------------- 195 | 196 | class Searcher(object): 197 | def __init__(self, index_dir, var_path): 198 | self._index = None 199 | try: 200 | self._index = wh_index.open_dir(index_dir) 201 | except wh_index.IndexError: 202 | raise IndexError 203 | 204 | self._var_reader = self._make_var_reader(var_path) 205 | 206 | op = OperatorsPlugin( 207 | And=r"\bAND\b|&", Or=None, # r"\bOR\b|\|", 208 | Not=r"\bNOT\b|\s+-", AndMaybe=None, Require=None) 209 | parser = QueryParser('content', _schema, 210 | termclass=my_variations(self._var_reader)) 211 | parser.remove_plugin_class(RangePlugin) 212 | parser.remove_plugin_class(BoostPlugin) 213 | parser.remove_plugin_class(WildcardPlugin) 214 | parser.replace_plugin(op) 215 | self._parser = parser 216 | 217 | parser_wild = QueryParser('content', _schema, 218 | termclass=my_variations(self._var_reader)) 219 | parser_wild.remove_plugin_class(RangePlugin) 220 | parser_wild.remove_plugin_class(BoostPlugin) 221 | parser_wild.replace_plugin(op) 222 | self._parser_wild = parser_wild 223 | 224 | op_filter = OperatorsPlugin(And=r"\bAND\b", Or=r"\bOR\b", 225 | Not=None, AndMaybe=None, Require=None) 226 | asf_parser = QueryParser('asfilter', _schema) 227 | asf_parser.replace_plugin(op_filter) 228 | self._asf_parser = asf_parser 229 | 230 | def __del__(self): 231 | try: 232 | self.close() 233 | except: 234 | pass 235 | 236 | def close(self): 237 | if self._index: 238 | self._index.close() 239 | self._index = None 240 | if self._var_reader: 241 | self._var_reader.close() 242 | 243 | def _make_var_reader(self, var_path): 244 | try: 245 | return VariationsReader(var_path) 246 | except (EnvironmentError, CDBError): 247 | return None 248 | 249 | def correct(self, misspelled, limit=5): 250 | with self._index.searcher() as searcher: 251 | corrector = searcher.corrector("content") 252 | return corrector.suggest(misspelled, limit) 253 | 254 | def make_collector(self, limit=None): 255 | if limit is None: 256 | return AbortableCollector(UnlimitedCollector()) 257 | else: 258 | return AbortableCollector(TopCollector(limit)) 259 | 260 | def search(self, collector, query_str1=None, query_str2=None, 261 | itemtypes=(), highlight=False): 262 | 263 | # rejects '*' and '?' 264 | if query_str1: 265 | for kw in (s.strip() for s in query_str1.split()): 266 | if not kw.replace("*", "").replace("?", "").strip(): 267 | return [] 268 | 269 | wildcard = (query_str1 and any(c in query_str1 for c in "*?")) 270 | 271 | parser = self._parser_wild if wildcard else self._parser 272 | asf_parser = self._asf_parser 273 | 274 | with self._index.searcher() as searcher: 275 | andlist = [] 276 | try: 277 | if query_str1: 278 | andlist.append(parser.parse(query_str1)) 279 | if query_str2: 280 | andlist.append(asf_parser.parse(query_str2)) 281 | except: 282 | return [] 283 | 284 | if itemtypes: 285 | if len(itemtypes) > 1: 286 | andlist.append( 287 | Or([Term('itemtype', t) for t in itemtypes])) 288 | else: 289 | andlist.append(Term('itemtype', itemtypes[0])) 290 | 291 | query = And(andlist) 292 | 293 | searcher.search_with_collector(query, collector) 294 | hits = collector.results() 295 | 296 | if highlight: 297 | hits.fragmenter = WholeFragmenter() 298 | hits.formatter = HtmlFormatter( 299 | tagname='span', classname='s_match', termclass='s_term') 300 | 301 | if wildcard and query_str1: 302 | pat = query_str1.replace("-", "").replace(" ", "") 303 | wildmatch = re.compile(fnmatch.translate(pat)) 304 | 305 | # Construct a result list 306 | results = [] 307 | for hit in hits: 308 | if collector.aborted: 309 | return [] 310 | (label, path, prio, sortkey) = hit['data'] 311 | 312 | if wildcard and query_str1: 313 | if not wildmatch.match(sortkey): 314 | continue 315 | 316 | if highlight: 317 | if query_str1: 318 | text = hit.highlights('content') 319 | else: 320 | text = hit['content'] 321 | else: 322 | text = None 323 | 324 | results.append((label, path, sortkey, prio, text)) 325 | 326 | sortkey_prio_getter = itemgetter(2, 3) 327 | results.sort(key=sortkey_prio_getter) 328 | 329 | # Return 330 | return results 331 | 332 | -------------------------------------------------------------------------------- /ldoce5viewer/incremental.py: -------------------------------------------------------------------------------- 1 | '''Incremental searcher for headwords and phrases''' 2 | 3 | 4 | from __future__ import absolute_import 5 | from __future__ import unicode_literals 6 | from __future__ import division 7 | 8 | import os 9 | import mmap 10 | from struct import Struct 11 | from operator import itemgetter 12 | 13 | from .utils.text import normalize_index_key, enc_utf8, dec_utf8 14 | from .utils.compat import range 15 | 16 | 17 | _MAGIC = 0x28061691 18 | _DB_VERSION = 1 19 | 20 | 21 | _struct_I = Struct(b' plain: 98 | a = c + 1 99 | else: 100 | b = c 101 | return a 102 | 103 | def bisect_end(key, start): 104 | (a, b) = (start, num) 105 | while a != b: 106 | c = (a + b) // 2 107 | p = first + 4 * c 108 | (p,) = _unpack_I(mm[p:p+4]) 109 | (lenp,) = _unpack_H(mm[p:p+2]) 110 | p += 8 111 | plain = dec_utf8(mm[p:p+lenp]) 112 | if key < plain and not plain.startswith(key): 113 | b = c 114 | else: 115 | a = c + 1 116 | return a 117 | 118 | start = bisect_start(key) 119 | end = bisect_end(key, start) 120 | 121 | if start == num: 122 | return [] 123 | 124 | (seek, read) = (mm.seek, mm.read) 125 | ret = [None] * min(limit, end - start) 126 | p = first + 4 * start 127 | seek(_unpack_I(mm[p:p+4])[0]) 128 | for i in range(len(ret)): 129 | (lenplain, lentypecode, lenlabel, lenpath, prio) = \ 130 | _unpack_HBHHB(read(8)) 131 | data = read(lenplain + lentypecode + lenlabel + lenpath) 132 | plain = dec_utf8(data[:lenplain]) 133 | x1 = lenplain + lentypecode 134 | x2 = x1 + lenlabel 135 | #typecode = data[lenplain:x1] 136 | label = dec_utf8(data[x1:x2]) 137 | path = dec_utf8(data[-lenpath:]) 138 | ret[i] = (label, path, plain, prio, None) 139 | 140 | return ret 141 | 142 | 143 | class Maker(object): 144 | def __init__(self, path, tmp_path): 145 | self._items = [] 146 | self._path = path 147 | self._tmp_path = tmp_path 148 | self._tmpf = open(tmp_path, "wb") 149 | 150 | def add_item(self, plain, typecode, label, path, prio): 151 | plain_n = normalize_index_key(plain) 152 | plain_e = enc_utf8(plain_n) 153 | typecode_e = enc_utf8(typecode) 154 | label_e = enc_utf8(label) 155 | path_e = path.encode('ascii') 156 | data = b''.join( 157 | (_pack_HBHHB( 158 | len(plain_e), len(typecode_e), 159 | len(label_e), len(path_e), prio 160 | ), plain_e, typecode_e, label_e, path_e)) 161 | tmpf = self._tmpf 162 | pos = tmpf.tell() 163 | tmpf.write(data) 164 | self._items.append((pos, plain_n, prio)) 165 | 166 | def abort(self): 167 | if self._tmpf: 168 | self._tmpf.close() 169 | self._tmpf = None 170 | os.remove(self._tmp_path) 171 | 172 | def finalize(self): 173 | tmpf = self._tmpf 174 | num = len(self._items) 175 | first = tmpf.tell() 176 | 177 | # Sort by (plain_n, prio) 178 | self._items.sort(key=itemgetter(1, 2)) 179 | 180 | for item in self._items: 181 | tmpf.write(_pack_I(item[0])) 182 | del self._items 183 | 184 | self._tmpf.close() 185 | self._tmpf = None 186 | try: 187 | self._generate(num, first) 188 | except: 189 | raise 190 | finally: 191 | os.remove(self._tmp_path) 192 | 193 | def _generate(self, num, first): 194 | try: 195 | with open(self._tmp_path, 'rb') as f: 196 | mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) 197 | except ValueError: 198 | raise IndexError('index is broken') 199 | 200 | if len(mm) != first + num * 4: 201 | raise IndexError('index is broken') 202 | 203 | dstf = open(self._path, "wb") 204 | 205 | write = dstf.write 206 | write(_pack_I(_MAGIC)) 207 | write(_pack_I(_DB_VERSION)) 208 | write(_pack_I(num)) 209 | write(_pack_I(first + 4 * 4)) 210 | 211 | new_xlist = [] 212 | p = first 213 | newx = 4 * 4 214 | for i in range(num): 215 | new_xlist.append(newx) 216 | x = _unpack_I(mm[p:p+4])[0] 217 | sizes = mm[x:(x + 8)] 218 | (lenplain, lentypecode, lenlabel, lenpath, prio) = \ 219 | _unpack_HBHHB(sizes) 220 | datasize = lenplain + lentypecode + lenlabel + lenpath 221 | data = mm[x:(x + 8 + datasize)] 222 | write(data) 223 | p += 4 224 | newx += 8 + datasize 225 | 226 | for x in new_xlist: 227 | write(_pack_I(x)) 228 | 229 | dstf.close() 230 | mm.close() 231 | -------------------------------------------------------------------------------- /ldoce5viewer/ldoce5/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import zlib 4 | import os.path 5 | import traceback 6 | 7 | from .filemap import FilemapReader 8 | from .idmreader import ArchiveReader 9 | from . import transform 10 | from ..utils.cdb import CDBError, CDBReader 11 | 12 | 13 | class NotFoundError(Exception): 14 | pass 15 | 16 | class FilemapError(Exception): 17 | pass 18 | 19 | class ArchiveError(Exception): 20 | pass 21 | 22 | 23 | def load_from_cdb_archive(data_dir, archive_name, name): 24 | path = os.path.join(data_dir, 'cdb_archives', archive_name + '.cdb') 25 | with CDBReader(path) as db: 26 | data = db[name.encode('utf-8')] 27 | if data[0] == 'c': 28 | data = zlib.decompress(data[1:]) 29 | else: 30 | data = data[1:] 31 | return data 32 | 33 | 34 | class LDOCE5(object): 35 | def __init__(self, data_dir, filemap_path): 36 | self._data_dir = data_dir 37 | self._filemap_path = filemap_path 38 | 39 | def get_content(self, path): 40 | try: 41 | archive, name = path.lstrip('/').split('/', 1) 42 | except ValueError: 43 | raise NotFoundError(u'invalid path') 44 | 45 | def load_content(archive_name, name): 46 | #try: 47 | # return load_from_cdb_archive( 48 | # self._data_dir, archive_name, name) 49 | #except IOError: 50 | # pass 51 | 52 | try: 53 | with FilemapReader(self._filemap_path) as fmr: 54 | location = fmr.lookup(archive_name, name) 55 | except (IOError, CDBError): 56 | raise FilemapError 57 | except KeyError: 58 | raise NotFoundError(u'content not found in filemap') 59 | try: 60 | with ArchiveReader(self._data_dir, archive_name) as reader: 61 | return reader.read(location) 62 | except IOError: 63 | raise ArchiveError 64 | 65 | def transform_exc(tf, *data): 66 | try: 67 | return tf(*data) 68 | except: 69 | exc = traceback.format_exc() 70 | if isinstance(exc, bytes): 71 | exc = traceback.format_exc().decode('utf-8', 'replace') 72 | s = u"

Error

{0}
".format( 73 | u'
'.join(exc.splitlines())) 74 | return s.encode('utf-8') 75 | 76 | ret_data = None 77 | mime_type = None 78 | 79 | if archive == 'fs': 80 | data = load_content(archive, name) 81 | ret_data = transform_exc(transform.trans_entry, data) 82 | mime_type = 'text/html;charset=utf-8' 83 | 84 | elif archive == 'collocations': 85 | data = load_content(archive, name) 86 | ret_data = transform_exc(transform.trans_collocations, data) 87 | mime_type = 'text/html;charset=utf-8' 88 | 89 | elif archive == 'examples': 90 | data = load_content(archive, name) 91 | ret_data = transform_exc(transform.trans_examples, data) 92 | mime_type = 'text/html;charset=utf-8' 93 | 94 | elif archive == 'word_families': 95 | data = load_content(archive, name) 96 | ret_data = transform_exc(transform.trans_word_families, data) 97 | mime_type = 'text/html;charset=utf-8' 98 | 99 | elif archive == 'etymologies': 100 | data = load_content(archive, name) 101 | ret_data = transform_exc(transform.trans_etymologies, data) 102 | mime_type = 'text/html;charset=utf-8' 103 | 104 | elif archive == 'activator': 105 | try: 106 | cid, sid = name.split('/', 1) 107 | except ValueError: 108 | raise NotFoundError('invalid path') 109 | data_c = load_content('activator_concept', cid) 110 | data_s = load_content('activator_section', sid) 111 | ret_data = transform_exc(transform.trans_activator, 112 | data_c, data_s, sid) 113 | mime_type = 'text/html;charset=utf-8' 114 | 115 | elif archive == 'phrases': 116 | data = load_content(archive, name) 117 | ret_data = transform_exc(transform.trans_phrases, data) 118 | mime_type = 'text/html;charset=utf-8' 119 | 120 | elif archive == 'thesaurus': 121 | data_set = [load_content('thesaurus', n) 122 | for n in name.split('_')] 123 | ret_data = transform_exc(transform.trans_thesaurus, data_set) 124 | mime_type = 'text/html;charset=utf-8' 125 | 126 | elif archive == 'word_sets': 127 | data_set = [load_content('word_sets', n) 128 | for n in name.split('_')] 129 | ret_data = transform_exc(transform.trans_word_sets,data_set) 130 | mime_type = 'text/html;charset=utf-8' 131 | 132 | elif archive == 'picture': 133 | ret_data = load_content('picture', name) 134 | mime_type = 'image/jpeg' 135 | 136 | elif archive in ('us_hwd_pron', 'gb_hwd_pron', 'exa_pron', 'sfx'): 137 | ret_data = load_content(archive, name) 138 | mime_type = 'audio/mpeg' 139 | 140 | return (ret_data, mime_type) 141 | 142 | -------------------------------------------------------------------------------- /ldoce5viewer/ldoce5/filemap.py: -------------------------------------------------------------------------------- 1 | '''File-location map''' 2 | 3 | from __future__ import absolute_import 4 | 5 | from struct import Struct 6 | from hashlib import md5 7 | 8 | import lxml.etree as et 9 | 10 | from ..utils import cdb 11 | from .utils import shorten_id 12 | from . import idmreader 13 | 14 | 15 | _struct_IIII = Struct('= len(dirs): 102 | # what's happening? 103 | return ('', ) 104 | (name, parent) = dirs[i] 105 | if parent == 0: 106 | return (name, ) 107 | return build_dirpath(parent) + (name, ) 108 | 109 | def _bytes2int(s): 110 | r = 0 111 | for c in reversed(bytearray(s)): 112 | r = r * 256 + c 113 | return r 114 | 115 | def _load_dirlist(target_base, t_info): 116 | dirsbase = os.path.join(target_base, 'dirs.skn') 117 | d_rsize, d_parent = t_info['d_rsize'], t_info['d_parent'] 118 | 119 | # name 120 | with open(os.path.join(dirsbase, 'NAME.tda'), 'rb') as f: 121 | namelist = [b.decode('utf-8') for b in f.read().split(b'\0')[:-1]] 122 | 123 | # parent 124 | parentlist = [] 125 | with open(os.path.join(dirsbase, 'dirs.dat'), 'rb') as f: 126 | while True: 127 | record = f.read(d_rsize) 128 | if len(record) != d_rsize: 129 | break 130 | parentlist.append(_bytes2int(record[d_parent])) 131 | 132 | return list(zip(namelist, parentlist)) 133 | 134 | def _load_filelist(target_base, t_info): 135 | filesbase = os.path.join(target_base, 'files.skn') 136 | f_rsize = t_info['f_rsize'] 137 | f_offset = t_info['f_offset'] 138 | f_parent = t_info['f_parent'] 139 | 140 | # name 141 | with open(os.path.join(filesbase, 'NAME.tda'), 'rb') as f: 142 | namelist = [b.decode('utf-8') for b in f.read().split(b'\0')[:-1]] 143 | 144 | # dat 145 | parentlist = [] 146 | offsetlist = [] 147 | with open(os.path.join(filesbase, 'files.dat'), 'rb') as f: 148 | while True: 149 | record = f.read(f_rsize) 150 | if len(record) != f_rsize: 151 | break 152 | offsetlist.append(_bytes2int(record[f_offset])) 153 | parentlist.append(_bytes2int(record[f_parent])) 154 | 155 | # size 156 | sizelist = [offsetlist[i + 1] - offsetlist[i] - 1 157 | for i in range(len(offsetlist) - 1)] 158 | sizelist.append(-1) 159 | return list(zip(namelist, parentlist, offsetlist, sizelist)) 160 | 161 | def _load_catalog(target_base): 162 | filesbase = os.path.join(target_base, 'files.skn') 163 | catpath = os.path.join(filesbase, 'CONTENT.tda.tdz') 164 | origsizes, cmpsizes = [], [] 165 | 166 | with open(catpath, 'rb') as f: 167 | while True: 168 | catrec = f.read(8) 169 | if len(catrec) != 8: 170 | break 171 | (origsize, cmpsize, ) = unpack('= origoffsets[ci + 1]: 205 | ci += 1 206 | (cmporig, cmpsize) = cmpoffsets[ci], cmpsizes[ci] 207 | if size < 0: 208 | size = origsizes[ci] - (offset - origoffsets[ci]) - 1 209 | (origorig, origsize) = (offset - origoffsets[ci], size) 210 | location = (cmporig, cmpsize, origorig, origsize) 211 | yield (build_dirpath(parent), name, location) 212 | 213 | 214 | class ArchiveReader(object): 215 | def __init__(self, data_dir, archive_name): 216 | self._f = None 217 | content_path = os.path.join(data_dir, os.path.join( 218 | _ARCHIVE_DIRS[archive_name], 'files.skn', 'CONTENT.tda')) 219 | self._f = open(content_path, 'rb') 220 | self._cache = '' 221 | self._cache_offset = -1 222 | self._cache_size = -1 223 | 224 | def read(self, location): 225 | (cmpoffset, cmpsize, origoffset, origsize) = location 226 | f = self._f 227 | f.seek(cmpoffset) 228 | if (self._cache_offset != cmpoffset) or (self._cache_size != cmpsize): 229 | self._cache = decompress(f.read(cmpsize)) 230 | self._cache_offset = cmpoffset 231 | self._cache_size = cmpsize 232 | return self._cache[origoffset:(origoffset+origsize)] 233 | 234 | def __del__(self): 235 | self.close() 236 | 237 | def __enter__(self): 238 | return self 239 | 240 | def __exit__(self, *args): 241 | self.close() 242 | 243 | def close(self): 244 | if self._f: 245 | self._f.close() 246 | 247 | -------------------------------------------------------------------------------- /ldoce5viewer/ldoce5/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | def shorten_id(_id): 4 | ids = _id.split('.') 5 | if len(ids) == 4: 6 | return '.'.join(ids[2:4]) 7 | else: 8 | return _id 9 | 10 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/__init__.py: -------------------------------------------------------------------------------- 1 | '''Entry point for the application''' 2 | 3 | from __future__ import unicode_literals, absolute_import, print_function 4 | 5 | _SINGLEAPP_KEY = 'ed437af1-0388-4e13-90e9-486bdc88c77a' 6 | 7 | from .. import __author__ 8 | 9 | # use PyQt API v2 10 | import sip 11 | sip.setapi('QString', 2) 12 | sip.setapi('QVariant', 2) 13 | 14 | import sys 15 | from optparse import OptionParser 16 | import logging 17 | import codecs 18 | import os.path 19 | 20 | from PyQt5.QtGui import QIcon 21 | 22 | from .utils.singleapp import SingleApplication 23 | from .utils.error import StdErrWrapper, MyStreamHandler 24 | from .config import get_config 25 | 26 | 27 | # set a dummy function if QLineEdit doesn't have setPlaceholderText 28 | from PyQt5.QtWidgets import QLineEdit 29 | if not hasattr(QLineEdit, 'setPlaceholderText'): 30 | def _dummySetPlaceholderText(self, *args, **kwargs): 31 | pass 32 | setattr(QLineEdit, 'setPlaceholderText', _dummySetPlaceholderText) 33 | 34 | 35 | try: 36 | from . import ui 37 | from . import resources 38 | except ImportError: 39 | print("need to run '$ make' in order for the program to work") 40 | exit() 41 | 42 | 43 | def _setup_py2exe(config): 44 | # suspend py2exe's logging facility 45 | log_path = os.path.join(config._config_dir, 'log.txt') 46 | try: 47 | f = codecs.open(log_path, 'w', encoding='utf-8') 48 | except: 49 | pass 50 | else: 51 | sys.stderr = f 52 | 53 | 54 | def run(argv): 55 | '''start the application''' 56 | 57 | config = get_config() 58 | 59 | # py2exe 60 | if sys.platform == 'win32' and \ 61 | (hasattr(sys, 'frozen') or hasattr(sys, 'importers')): 62 | _setup_py2exe(config) 63 | 64 | # Parse arguments 65 | optparser = OptionParser() 66 | optparser.set_defaults(debug=False) 67 | optparser.add_option( 68 | '--debug', action='store_true', help='Enable debug mode') 69 | (options, args) = optparser.parse_args(argv) 70 | 71 | # stderr wrapper 72 | sys.stderr = StdErrWrapper(sys.stderr) 73 | 74 | # logging 75 | logger = logging.getLogger() 76 | handler = MyStreamHandler() 77 | handler.setFormatter( 78 | logging.Formatter('%(levelname)s:%(name)s:%(message)s')) 79 | logger.addHandler(handler) 80 | logger.setLevel(logging.DEBUG if options.debug else logging.ERROR) 81 | 82 | # Create an application instance 83 | app = SingleApplication(argv, _SINGLEAPP_KEY) 84 | if app.isRunning(): 85 | app.sendMessage('activate') 86 | return 1 87 | 88 | # Load the configuration file 89 | config.debug = options.debug 90 | config.load() 91 | 92 | # Set the application's information 93 | app.setApplicationName(config.app_name) 94 | app.setOrganizationName(__author__) 95 | app.setWindowIcon(QIcon(':/icons/icon.png')) 96 | 97 | # Setup MainWindow 98 | from .main import MainWindow 99 | main_window = MainWindow() 100 | 101 | def messageHandler(msg): 102 | if msg == 'activate': 103 | main_window.activateWindow() 104 | main_window.setVisible(True) 105 | app.messageAvailable.connect(messageHandler) 106 | 107 | # On Windows-ja 108 | if app.font().family() == u'MS UI Gothic': 109 | cand = (('Segoe UI', None), ('Meiryo UI', None), ('Tahoma', 8)) 110 | from PyQt5.QtGui import QFont 111 | for name, point in cand: 112 | ps = app.font().pointSize() 113 | if point is None: 114 | point = ps if ps != -1 else 9 115 | font = QFont(name, point) 116 | if font.exactMatch(): 117 | app.setFont(font) 118 | break 119 | 120 | # Redirect stderr to the Error Console 121 | if not options.debug: 122 | sys.stderr.setApplication(app) 123 | 124 | # Start the application 125 | r = app.exec_() 126 | 127 | # Quit 128 | config.save() 129 | return r 130 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/access.py: -------------------------------------------------------------------------------- 1 | '''application-specific URI scheme handler for QtWebKit''' 2 | 3 | from __future__ import absolute_import 4 | from __future__ import print_function 5 | 6 | import sys 7 | import imp 8 | import os.path 9 | import traceback 10 | 11 | from PyQt5.QtCore import ( 12 | Qt, Q_ARG, QMetaObject, QIODevice, QTimer,) 13 | from PyQt5.QtNetwork import ( 14 | QNetworkAccessManager, QNetworkReply, QNetworkRequest,) 15 | 16 | from .. import __version__ 17 | from .. import __name__ as basepkgname 18 | from ..ldoce5 import LDOCE5, NotFoundError, FilemapError, ArchiveError 19 | from ..utils.text import enc_utf8 20 | 21 | from .advanced import search_and_render 22 | from .utils import fontfallback 23 | from .config import get_config 24 | 25 | STATIC_REL_PATH = 'static' 26 | 27 | 28 | def _load_static_data(filename): 29 | """Load a static file from the 'static' directory""" 30 | 31 | is_frozen = (hasattr(sys, 'frozen') # new py2exe 32 | or imp.is_frozen('__main__')) # tools/freeze 33 | 34 | if is_frozen: 35 | if sys.platform.startswith("darwin"): 36 | path = os.path.join(os.path.dirname(sys.executable), 37 | "../Resources", 38 | STATIC_REL_PATH, filename) 39 | else: 40 | path = os.path.join(os.path.dirname(sys.executable), 41 | STATIC_REL_PATH, filename) 42 | with open(path, 'rb') as f: 43 | data = f.read() 44 | else: 45 | try: 46 | from pkgutil import get_data as _get 47 | except ImportError: 48 | from pkg_resources import resource_string as _get 49 | 50 | data = _get(basepkgname, os.path.join(STATIC_REL_PATH, filename)) 51 | 52 | if filename.endswith('.css'): 53 | s = data.decode('utf-8') 54 | s = fontfallback.css_replace_fontfamily(s) 55 | data = s.encode('utf-8') 56 | elif filename.endswith('.html'): 57 | s = data.decode('utf-8') 58 | s = s.replace('{% current_version %}', __version__) 59 | data = s.encode('utf-8') 60 | 61 | return data 62 | 63 | 64 | class MyNetworkAccessManager(QNetworkAccessManager): 65 | '''Customized NetworkAccessManager''' 66 | 67 | def __init__(self, parent, searcher_hp, searcher_de): 68 | QNetworkAccessManager.__init__(self, parent) 69 | self._searcher_hp = searcher_hp 70 | self._searcher_de = searcher_de 71 | 72 | def createRequest(self, operation, request, data): 73 | if (operation == self.GetOperation and 74 | request.url().scheme() in ('dict', 'static', 'search')): 75 | return MyNetworkReply( 76 | self, operation, request, 77 | self._searcher_hp, self._searcher_de) 78 | else: 79 | return super(MyNetworkAccessManager, self).createRequest( 80 | operation, request, data) 81 | 82 | 83 | class MyNetworkReply(QNetworkReply): 84 | '''Customized NetworkReply 85 | 86 | It handles the 'dict' and 'static' schemes. 87 | ''' 88 | 89 | def __init__(self, parent, operation, request, 90 | searcher_hp, searcher_de): 91 | QNetworkReply.__init__(self, parent) 92 | 93 | url = request.url() 94 | self.setRequest(request) 95 | self.setUrl(url) 96 | self.setOperation(operation) 97 | self.open(QIODevice.ReadOnly) 98 | 99 | self._finished = False 100 | self._data = None 101 | self._offset = 0 102 | 103 | self._url = url 104 | self._searcher_hp = searcher_hp 105 | self._searcher_de = searcher_de 106 | QTimer.singleShot(0, self._load) # don't disturb the UI thread 107 | 108 | def _load(self): 109 | url = self._url 110 | config = get_config() 111 | searcher_hp = self._searcher_hp 112 | searcher_de = self._searcher_de 113 | mime = None 114 | error = False 115 | 116 | if url.scheme() == 'static': 117 | try: 118 | self._data = _load_static_data(url.path().lstrip('/')) 119 | except EnvironmentError: 120 | self._data = '

Static File Not Found

' 121 | mime = 'text/html' 122 | error = True 123 | 124 | elif url.scheme() == 'dict': 125 | try: 126 | path = url.path().split('#', 1)[0] 127 | ldoce5 = LDOCE5(config.get('dataDir', ''), config.filemap_path) 128 | (self._data, mime) = ldoce5.get_content(path) 129 | except NotFoundError: 130 | self._data = '

Content Not Found

' 131 | mime = 'text/html' 132 | error = True 133 | except FilemapError: 134 | self._data = '

File-Location Map Not Available

' 135 | mime = 'text/html' 136 | error = True 137 | except ArchiveError: 138 | self._data = '

Dictionary Data Not Available

' 139 | mime = 'text/html' 140 | error = True 141 | 142 | elif url.scheme() == 'search': 143 | if searcher_hp and searcher_de: 144 | try: 145 | self._data = enc_utf8(search_and_render( 146 | url, searcher_hp, searcher_de)) 147 | mime = 'text/html' 148 | except: 149 | s = u"

Error

{0}
".format( 150 | '
'.join(traceback.format_exc().splitlines())) 151 | self._data = enc_utf8(s) 152 | mime = 'text/html' 153 | error = True 154 | else: 155 | mime = 'text/html' 156 | self._data = ("""

The full-text search index """ 157 | """has not been created yet or broken.

""") 158 | error = True 159 | 160 | if mime: 161 | self.setHeader(QNetworkRequest.ContentTypeHeader, mime) 162 | self.setHeader(QNetworkRequest.ContentLengthHeader, len(self._data)) 163 | self.setOpenMode(self.ReadOnly | self.Unbuffered) 164 | 165 | if error: 166 | nwerror = QNetworkReply.ContentNotFoundError 167 | error_msg = u'Content Not Found' 168 | self.setError(nwerror, error_msg) 169 | QMetaObject.invokeMethod( 170 | self, 'error', Qt.QueuedConnection, 171 | Q_ARG(QNetworkReply.NetworkError, nwerror)) 172 | 173 | QMetaObject.invokeMethod(self, 'metaDataChanged', Qt.QueuedConnection) 174 | QMetaObject.invokeMethod(self, 'downloadProgress', Qt.QueuedConnection, 175 | Q_ARG('qint64', len(self._data)), 176 | Q_ARG('qint64', len(self._data))) 177 | QMetaObject.invokeMethod(self, 'readyRead', Qt.QueuedConnection) 178 | QMetaObject.invokeMethod(self, 'finished', Qt.QueuedConnection) 179 | 180 | self._finished = True 181 | 182 | def isFinished(self): 183 | return self._finished 184 | 185 | def isSequential(self): 186 | return True 187 | 188 | def abort(self): 189 | self.close() 190 | 191 | def size(self): 192 | return len(self._data) 193 | 194 | def bytesAvailable(self): 195 | return (super(MyNetworkReply, self).bytesAvailable() 196 | + len(self._data) - self._offset) 197 | 198 | def readData(self, maxSize): 199 | size = min(maxSize, len(self._data) - self._offset) 200 | data = self._data[self._offset:self._offset + size] 201 | self._offset += size 202 | return data 203 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/async_.py: -------------------------------------------------------------------------------- 1 | """Asynchlonous full-text search facility for phrase search""" 2 | 3 | from __future__ import absolute_import 4 | from __future__ import unicode_literals 5 | 6 | import logging 7 | 8 | from PyQt5.QtCore import ( 9 | QObject, QThread, QMutex, QWaitCondition, pyqtSignal) 10 | 11 | _logger = logging.getLogger(__name__) 12 | 13 | 14 | class _FTSearchThread(QThread): 15 | '''This thread performs full text search in the background''' 16 | 17 | searchFinished = pyqtSignal() 18 | searchError = pyqtSignal() 19 | 20 | def __init__(self, searcher, parent): 21 | QThread.__init__(self, parent) 22 | self._searcher = searcher 23 | self._quit = False 24 | self._mutex = QMutex() 25 | self._pending = QWaitCondition() 26 | self._collector = None 27 | self._query = None 28 | self._result = None 29 | 30 | def run(self): 31 | while not self._quit: 32 | self._mutex.lock() 33 | if not self._query: 34 | self._pending.wait(self._mutex) 35 | query = self._query 36 | self._query = None 37 | self._mutex.unlock() 38 | 39 | # search 40 | if query: 41 | (query_str1, query_str2, itemtypes, 42 | limit, highlight, merge) = query 43 | 44 | self._mutex.lock() 45 | collector = self._searcher.make_collector(limit) 46 | self._collector = collector 47 | self._mutex.unlock() 48 | 49 | try: 50 | result = self._searcher.search( 51 | collector, 52 | query_str1, query_str2, 53 | itemtypes, highlight) 54 | except: 55 | self._mutex.lock() 56 | self._result = None 57 | self._mutex.unlock() 58 | self.searchError.emit() 59 | else: 60 | if collector.aborted: 61 | pass 62 | else: 63 | self._mutex.lock() 64 | self._result = (merge, result) 65 | self._mutex.unlock() 66 | self.searchFinished.emit() 67 | 68 | self._mutex.lock() 69 | self._collector = None 70 | self._mutex.unlock() 71 | 72 | def cancel(self): 73 | self._mutex.lock() 74 | self._query = None 75 | if self._collector: 76 | self._collector.abort() 77 | self._mutex.unlock() 78 | 79 | def quit(self): 80 | self._mutex.lock() 81 | if self._collector: 82 | self._collector.abort() 83 | self._query = None 84 | self._quit = True 85 | self._mutex.unlock() 86 | self._pending.wakeAll() 87 | 88 | def update_query(self, query_str1=None, query_str2=None, itemtypes=(), 89 | limit=1000, highlight=False, merge=False): 90 | self._mutex.lock() 91 | if self._collector: 92 | self._collector.abort() 93 | self._query = (query_str1, query_str2, 94 | itemtypes, limit, highlight, merge) 95 | self._mutex.unlock() 96 | self._pending.wakeAll() 97 | 98 | def take_result(self): 99 | self._mutex.lock() 100 | r = self._result 101 | self._result = None 102 | self._mutex.unlock() 103 | return r 104 | 105 | 106 | class AsyncFTSearcher(QObject): 107 | finished = pyqtSignal() 108 | error = pyqtSignal() 109 | 110 | def __init__(self, parent, searcher): 111 | QObject.__init__(self, parent) 112 | 113 | self._result = None 114 | self._thread = _FTSearchThread(searcher, self) 115 | self._thread.searchFinished.connect(self._onFinished) 116 | self._thread.searchError.connect(self._onError) 117 | self._thread.start() 118 | 119 | def update_query(self, query_str1=None, query_str2=None, itemtypes=(), 120 | limit=1000, highlight=False, merge=False): 121 | self._thread.update_query(query_str1, query_str2, itemtypes, 122 | limit, highlight, merge) 123 | 124 | def shutdown(self): 125 | self._thread.quit() 126 | self._thread.wait() 127 | self._thread = None 128 | 129 | def cancel(self): 130 | self._thread.cancel() 131 | 132 | def _onError(self): 133 | self.error.emit() 134 | 135 | def _onFinished(self): 136 | if self._thread: 137 | self._result = self._thread.take_result() 138 | self.finished.emit() 139 | 140 | def take_result(self): 141 | r = self._result 142 | self._result = None 143 | return r 144 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/config.py: -------------------------------------------------------------------------------- 1 | '''Global configurations''' 2 | 3 | from __future__ import absolute_import, unicode_literals, print_function 4 | 5 | import sys 6 | import os 7 | import os.path 8 | import tempfile 9 | import shutil 10 | try: 11 | import cPickle as pickle 12 | except ImportError: 13 | import pickle 14 | 15 | from PyQt5.QtCore import QReadWriteLock 16 | 17 | 18 | __config = None 19 | 20 | 21 | def get_config(): 22 | global __config 23 | if not __config: 24 | __config = __Config() 25 | return __config 26 | 27 | 28 | class __Config(object): 29 | def __init__(self, debug=False): 30 | self.debug = debug 31 | self._dict = dict() 32 | self._prepare_dir() 33 | self._remove_tmps() 34 | self._lock = QReadWriteLock() 35 | 36 | def __getitem__(self, key): 37 | self._lock.lockForRead() 38 | r = self._dict[key] 39 | self._lock.unlock() 40 | return r 41 | 42 | def __setitem__(self, key, value): 43 | self._lock.lockForWrite() 44 | self._dict[key] = value 45 | self._lock.unlock() 46 | 47 | def get(self, key, default=None): 48 | self._lock.lockForRead() 49 | r = self._dict.get(key, default) 50 | self._lock.unlock() 51 | return r 52 | 53 | def pop(self, key, default=None): 54 | self._lock.lockForWrite() 55 | r = self._dict.pop(key, default) 56 | self._lock.unlock() 57 | return r 58 | 59 | def __contains__(self, key): 60 | self._lock.lockForRead() 61 | r = self._dict.__contains__(key) 62 | self._lock.unlock() 63 | return r 64 | 65 | def __str__(self): 66 | self._lock.lockForRead() 67 | s = ''.join([ 68 | 'Config(', 69 | ', '.join("{0}: {1}".format(k, v) for (k, v) in self._dict), 70 | ')']) 71 | self._lock.unlock() 72 | return s 73 | 74 | @property 75 | def _config_dir(self): 76 | # Windows 77 | if sys.platform.startswith('win'): 78 | if 'LOCALAPPDATA' in os.environ: 79 | return os.path.join( 80 | os.environ['LOCALAPPDATA'], 'LDOCE5Viewer') 81 | else: 82 | return os.path.join( 83 | os.environ['APPDATA'], 'LDOCE5Viewer') 84 | # Mac OS X 85 | elif sys.platform.startswith('darwin'): 86 | return os.path.expanduser( 87 | '~/Library/Application Support/LDOCE5Viewer') 88 | # Linux 89 | else: 90 | base = os.path.join(os.path.expanduser('~'), '.config') 91 | # XDG 92 | try: 93 | import xdg.BaseDirectory 94 | base = xdg.BaseDirectory.xdg_config_home 95 | except ImportError: 96 | if 'XDG_CONFIG_HOME' in os.environ: 97 | base = os.environ['XDG_CONFIG_HOME'] 98 | return os.path.join(base, 'ldoce5viewer') 99 | 100 | @property 101 | def _data_dir(self): 102 | # Windows 103 | if sys.platform.startswith('win'): 104 | return self._config_dir 105 | # Mac OS X 106 | elif sys.platform.startswith('darwin'): 107 | return self._config_dir 108 | # Linux 109 | else: 110 | base = os.path.join(os.path.expanduser('~'), '.local/share/') 111 | # XDG 112 | try: 113 | import xdg.BaseDirectory 114 | base = xdg.BaseDirectory.xdg_data_home 115 | except ImportError: 116 | if 'XDG_DATA_HOME' in os.environ: 117 | base = os.environ['XDG_DATA_HOME'] 118 | return os.path.join(base, 'ldoce5viewer') 119 | 120 | @property 121 | def app_name(self): 122 | return 'LDOCE5 Viewer' 123 | 124 | @property 125 | def _config_path(self): 126 | return os.path.join(self._config_dir, 'config.pickle') 127 | 128 | @property 129 | def filemap_path(self): 130 | return os.path.join(self._data_dir, 'filemap.cdb') 131 | 132 | @property 133 | def variations_path(self): 134 | return os.path.join(self._data_dir, 'variations.cdb') 135 | 136 | @property 137 | def incremental_path(self): 138 | return os.path.join(self._data_dir, 'incremental.db') 139 | 140 | @property 141 | def fulltext_hwdphr_path(self): 142 | return os.path.join(self._data_dir, 'fulltext_hp') 143 | 144 | @property 145 | def fulltext_defexa_path(self): 146 | return os.path.join(self._data_dir, 'fulltext_de') 147 | 148 | @property 149 | def scan_tmp_path(self): 150 | return os.path.join(self._data_dir, 'scan' + self.tmp_suffix) 151 | 152 | @property 153 | def tmp_suffix(self): 154 | return '.tmp' 155 | 156 | def _remove_tmps(self): 157 | for name in os.listdir(self._config_dir) + os.listdir(self._data_dir): 158 | if name.endswith(self.tmp_suffix): 159 | path = os.path.join(self._config_dir, name) 160 | try: 161 | if os.path.isfile(path): 162 | os.remove(path) 163 | elif os.path.isdir(path): 164 | shutil.rmtree(path) 165 | except OSError: 166 | pass 167 | 168 | def _prepare_dir(self): 169 | if not os.path.exists(self._config_dir): 170 | os.makedirs(self._config_dir) 171 | if not os.path.exists(self._data_dir): 172 | os.makedirs(self._data_dir) 173 | 174 | def load(self): 175 | self._lock.lockForWrite() 176 | try: 177 | with open(self._config_path, 'rb') as f: 178 | self._dict.clear() 179 | try: 180 | data = pickle.load(f) 181 | except: 182 | pass 183 | else: 184 | self._dict.update(data) 185 | except IOError: 186 | self._dict.clear() 187 | self._lock.unlock() 188 | 189 | def save(self): 190 | self._lock.lockForRead() 191 | 192 | if sys.platform == 'win32': 193 | with open(self._config_path, 'wb') as f: 194 | pickle.dump(self._dict, f) 195 | else: 196 | f = tempfile.NamedTemporaryFile( 197 | dir=self._config_dir, delete=False, suffix=self.tmp_suffix) 198 | pickle.dump(self._dict, f, protocol=0) 199 | f.close() 200 | os.rename(f.name, self._config_path) 201 | 202 | self._lock.unlock() 203 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | FILES := $(wildcard *.png) $(wildcard *.svg) $(wildcard */*.png) 4 | 5 | __init__.py: resource.qrc $(FILES) 6 | pyrcc5 $< -o $@ 7 | 8 | .PHONY: clean 9 | clean: 10 | rm -f __init__.py 11 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Poor Icons 2 | ---------- 3 | 4 | by: Taku Fukada 5 | license: CC0 (Public Domain) 6 | 7 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | PNG_SRC := $(patsubst %.svg, %-src.png, $(wildcard *.svg)) 4 | PNG_24 := $(patsubst %-src.png, %-24.png, $(PNG_SRC)) 5 | PNG_22 := $(patsubst %-src.png, %-22.png, $(PNG_SRC)) 6 | PNG_16 := $(patsubst %-src.png, %-16.png, $(PNG_SRC)) 7 | 8 | all: $(PNG_24) $(PNG_22) $(PNG_16) 9 | 10 | %-48-src.png: %.svg icongen.py 11 | inkscape -w 96 -h 96 -e $@ $< 12 | 13 | edit-clear-48.png: edit-clear-48-src.png 14 | cp $< $@ 15 | 16 | %-48.png: %-48-src.png 17 | python2 icongen.py $< $@ 18 | 19 | %-24.png: %-48.png 20 | convert $< -filter Lanczos -resize 24x24 $@ 21 | 22 | %-22.png: %-48.png 23 | convert $< -filter Lanczos -resize 22x22 $@ 24 | 25 | %-16.png: %-48.png 26 | convert $< -filter Lanczos -resize 16x16 $@ 27 | 28 | .PHONY: clean 29 | clean: 30 | rm -f *.png 31 | 32 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/application-exit-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/application-exit-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/application-exit-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/application-exit-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/application-exit-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/application-exit-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/application-exit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/document-print-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/document-print-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/document-print-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/document-print-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/document-print-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/document-print-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/document-print-preview-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/document-print-preview-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/document-print-preview-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/document-print-preview-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/document-print-preview-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/document-print-preview-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/document-print-preview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/document-print.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/document-properties-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/document-properties-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/document-properties-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/document-properties-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/document-properties-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/document-properties-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/document-properties.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/edit-clear-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/edit-clear-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/edit-clear-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/edit-clear-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/edit-clear-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/edit-clear-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/edit-clear-48-src.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/edit-clear-48-src.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/edit-clear-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/edit-clear-48.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/edit-clear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/edit-copy-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/edit-copy-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/edit-copy-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/edit-copy-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/edit-copy-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/edit-copy-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/edit-copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/edit-find-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/edit-find-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/edit-find-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/edit-find-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/edit-find-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/edit-find-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/edit-find.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-down-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/go-down-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-down-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/go-down-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-down-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/go-down-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-next-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/go-next-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-next-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/go-next-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-next-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/go-next-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-previous-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/go-previous-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-previous-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/go-previous-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-previous-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/go-previous-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-previous.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-up-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/go-up-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-up-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/go-up-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-up-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/go-up-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/go-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/help-about-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/help-about-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/help-about-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/help-about-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/help-about-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/help-about-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/help-about.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/help-contents-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/help-contents-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/help-contents-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/help-contents-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/help-contents-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/help-contents-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/help-contents.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/iconblock-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/iconblock-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/iconblock-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/iconblock-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/iconblock-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/iconblock-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/iconblock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/icongen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | import Image, ImageFilter, ImageChops, ImageEnhance, ImageDraw 6 | 7 | 8 | OFFSET_S = 4 9 | OFFSET_H = 4 10 | 11 | 12 | def cast_gradation(img, b1, b2): 13 | (w, h) = img.size 14 | pix = img.load() 15 | for y in xrange(h): 16 | c = b1 + (b2 - b1) * (float(y) / (h - 1)) 17 | for x in xrange(w): 18 | pix[x, y] += int(c + 0.5) 19 | return img 20 | 21 | 22 | def make_inset_shadow(alpha): 23 | mc = alpha.copy() 24 | for i in xrange(6): 25 | mc = mc.filter(ImageFilter.SMOOTH_MORE) 26 | mc = ImageChops.subtract(alpha, mc) 27 | mcb = ImageEnhance.Brightness(mc).enhance(0.35) 28 | 29 | m1 = alpha.copy() 30 | for i in xrange(6): 31 | m1 = m1.filter(ImageFilter.SMOOTH_MORE) 32 | m1 = m1.offset(0, OFFSET_S) 33 | m1 = ImageChops.subtract(alpha, m1) 34 | m1b = ImageEnhance.Brightness(m1).enhance(0.35) 35 | 36 | m = ImageChops.lighter(mc, m1) 37 | mb = ImageChops.lighter(mcb, m1b) 38 | return (m, mb) 39 | 40 | 41 | def make_highlight(alpha): 42 | mc = alpha.copy() 43 | for i in xrange(3): 44 | mc = mc.filter(ImageFilter.SMOOTH_MORE) 45 | mc = ImageChops.subtract(mc, alpha) 46 | mcb = ImageEnhance.Brightness(mc).enhance(0.35) 47 | 48 | m1 = alpha.copy() 49 | for i in xrange(2): 50 | m1 = m1.filter(ImageFilter.SMOOTH_MORE) 51 | m1 = m1.offset(0, OFFSET_H) 52 | m1 = ImageChops.subtract(m1, alpha) 53 | m1b = ImageEnhance.Brightness(m1).enhance(0.35) 54 | 55 | m = ImageChops.lighter(mc, m1) 56 | mb = ImageChops.lighter(mcb, m1b) 57 | 58 | return (m, mb) 59 | 60 | 61 | def make(src_path, out_path): 62 | src = Image.open(src_path) 63 | src = src.copy() 64 | (srcr, srcg, srcb, srca) = src.split() 65 | white = ImageChops.constant(src, 255) 66 | 67 | outr = cast_gradation(srcr, 0, 90) 68 | outg = cast_gradation(srcg, 0, 90) 69 | outb = cast_gradation(srcb, 0, 90) 70 | outa = srca.copy() 71 | 72 | outr = ImageChops.composite(srcr, white, srca) 73 | outg = ImageChops.composite(srcg, white, srca) 74 | outb = ImageChops.composite(srcb, white, srca) 75 | 76 | (shadow_a, shadow) = make_inset_shadow(srca) 77 | outr = ImageChops.subtract(outr, shadow, 1, 0) 78 | outg = ImageChops.subtract(outg, shadow, 1, 0) 79 | outb = ImageChops.subtract(outb, shadow, 1, 0) 80 | outa = ImageChops.lighter(outa, shadow_a) 81 | 82 | (highlight_a, highlight) = make_highlight(srca) 83 | outa = ImageChops.lighter(outa, highlight) 84 | 85 | outa = ImageChops.subtract(outa, ImageChops.constant(outa, 25), 1, 0) 86 | 87 | out = Image.merge('RGBA', (outr, outg, outb, outa)) 88 | out.save(out_path) 89 | 90 | 91 | if __name__ == "__main__": 92 | src_path = sys.argv[1] 93 | out_path = sys.argv[2] 94 | make(src_path, out_path) 95 | 96 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/reload-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/reload-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/reload-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/reload-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/reload-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/reload-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/reload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/star-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/star-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/star-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/star-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/star-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/star-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/window-close-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/window-close-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/window-close-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/window-close-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/window-close-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/window-close-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/window-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/zoom-in-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/zoom-in-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/zoom-in-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/zoom-in-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/zoom-in-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/zoom-in-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/zoom-in.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/zoom-original-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/zoom-original-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/zoom-original-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/zoom-original-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/zoom-original-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/zoom-original-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/zoom-original.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/zoom-out-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/zoom-out-16.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/zoom-out-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/zoom-out-22.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/zoom-out-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/icons/zoom-out-24.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/icons/zoom-out.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | image/svg+xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/ldoce5viewer.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/ldoce5viewer.icns -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/ldoce5viewer.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/ldoce5viewer.ico -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/ldoce5viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/ldoce5viewer.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/next-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/next-mac.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/prev-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/resources/prev-mac.png -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/resources/resource.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/application-exit-16.png 4 | icons/document-properties-16.png 5 | icons/document-print-16.png 6 | icons/document-print-preview-16.png 7 | icons/edit-copy-16.png 8 | icons/edit-clear-16.png 9 | icons/edit-clear-22.png 10 | icons/edit-find-16.png 11 | icons/edit-find-22.png 12 | icons/edit-find-24.png 13 | icons/go-down-16.png 14 | next-mac.png 15 | prev-mac.png 16 | icons/go-next-24.png 17 | icons/go-previous-24.png 18 | icons/go-up-16.png 19 | icons/help-about-16.png 20 | icons/help-contents-16.png 21 | ldoce5viewer.svg 22 | ldoce5viewer.png 23 | icons/reload-16.png 24 | icons/window-close-16.png 25 | icons/zoom-in-16.png 26 | icons/zoom-original-16.png 27 | icons/zoom-out-16.png 28 | icons/iconblock-16.png 29 | 30 | 31 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/ui/Makefile: -------------------------------------------------------------------------------- 1 | PYFILES := $(patsubst %.ui, %.py, $(wildcard *.ui)) 2 | 3 | all: __init__.py $(PYFILES) 4 | 5 | __init__.py: 6 | touch $@ 7 | 8 | %.py: %.ui 9 | pyuic5 $< -o $@ 10 | 11 | .PYONY: clean 12 | clean: 13 | rm -f $(PYFILES) 14 | 15 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/ui/__init__.py: -------------------------------------------------------------------------------- 1 | from . import main 2 | from . import indexer 3 | from . import advanced 4 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/ui/advanced.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 361 10 | 468 11 | 12 | 13 | 14 | Advanced Search 15 | 16 | 17 | 18 | 5 19 | 20 | 21 | 5 22 | 23 | 24 | 25 | 26 | 5 27 | 28 | 29 | 0 30 | 31 | 32 | 33 | 34 | Qt::ImhDigitsOnly|Qt::ImhLowercaseOnly|Qt::ImhUppercaseOnly 35 | 36 | 37 | Search phrase (optional) 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 0 46 | 0 47 | 48 | 49 | 50 | Search 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 16 60 | 16 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | true 71 | 72 | 73 | 74 | 1 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | Reset 83 | 84 | 85 | 86 | 16 87 | 16 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | LineEdit 97 | QLineEdit 98 |
.custom
99 |
100 |
101 | 102 | treeWidget 103 | buttonSearch 104 | lineEditPhrase 105 | 106 | 107 | 108 |
109 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/ui/indexer.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 473 10 | 395 11 | 12 | 13 | 14 | Create Index 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Data Location: 23 | 24 | 25 | 26 | 27 | 28 | 29 | true 30 | 31 | 32 | 33 | 34 | 35 | 36 | Browse... 37 | 38 | 39 | 40 | 16 41 | 16 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | true 52 | 53 | 54 | false 55 | 56 | 57 | true 58 | 59 | 60 | Qt::TextBrowserInteraction 61 | 62 | 63 | false 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Qt::Horizontal 73 | 74 | 75 | 76 | 40 77 | 20 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | Cancel 86 | 87 | 88 | 89 | 90 | 91 | 92 | Start Indexing 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/qtgui/utils/__init__.py -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/utils/error.py: -------------------------------------------------------------------------------- 1 | """Error console""" 2 | 3 | from logging import StreamHandler 4 | 5 | from PyQt5.QtCore import QMutex, QObject, pyqtSignal 6 | from PyQt5.QtWidgets import QPlainTextEdit 7 | 8 | 9 | class MyStreamHandler(StreamHandler): 10 | def __init__(self): 11 | StreamHandler.__init__(self) 12 | 13 | def createLock(self): 14 | # must be Recursive (= reentrant) 15 | self._mutex = QMutex(QMutex.Recursive) 16 | 17 | def acquire(self): 18 | self._mutex.lock() 19 | 20 | def release(self): 21 | self._mutex.unlock() 22 | 23 | 24 | class StdErrWrapper(QObject): 25 | _write = pyqtSignal(type(u'')) 26 | _flush = pyqtSignal() 27 | 28 | def __init__(self, old_stderr): 29 | QObject.__init__(self) 30 | self._old_stderr = old_stderr 31 | self._widget = None 32 | self._mutex = QMutex(QMutex.Recursive) 33 | 34 | def setApplication(self, app): 35 | assert(self._widget is None) 36 | 37 | widget = QPlainTextEdit() 38 | widget.setWindowTitle(u"Error Console") 39 | widget.resize(486, 300) 40 | widget.appendHtml( 41 | u'' 42 | u'An unhandled error occurred.
' 43 | u'Sorry for the inconvinience.
' 44 | u'Please copy the following text into a bug report:

' 45 | u'
') 46 | app.aboutToQuit.connect(self.restoreStdErr) 47 | self._write.connect(self._write_handler) 48 | self._flush.connect(self._flush_handler) 49 | self._widget = widget 50 | 51 | def _write_handler(self, data): 52 | self._mutex.lock() 53 | if self._widget: 54 | self._widget.show() 55 | self._widget.insertPlainText(data) 56 | self._mutex.unlock() 57 | 58 | def _flush_handler(self): 59 | self._mutex.lock() 60 | if self._widget: 61 | self._widget.show() 62 | self._mutex.unlock() 63 | 64 | def restoreStdErr(self): 65 | self._mutex.lock() 66 | if self._widget: 67 | self._widget.close() 68 | self._widget = None 69 | self._mutex.unlock() 70 | 71 | @property 72 | def encoding(self): 73 | return 'utf-8' 74 | 75 | def write(self, s): 76 | self._mutex.lock() 77 | if self._widget: 78 | if isinstance(s, bytes): 79 | s = s.decode('utf-8', 'replace') 80 | self._write.emit(s) 81 | else: 82 | self._old_stderr.write(s) 83 | self._mutex.unlock() 84 | 85 | def flush(self): 86 | self._mutex.lock() 87 | if self._widget: 88 | self._flush.emit() 89 | else: 90 | self._old_stderr.flush() 91 | self._mutex.unlock() 92 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/utils/fontfallback.py: -------------------------------------------------------------------------------- 1 | '''My very own font fallback facility 2 | 3 | (QtWebkit 2.2 doesn't perform the font fallback properly because of a bug) 4 | ''' 5 | 6 | import re 7 | 8 | from PyQt5.QtGui import QFont 9 | 10 | 11 | _DEFAULT_FONT_NAMES = frozenset((b'sans-serif', b'serif', b'monospace')) 12 | 13 | 14 | def _fallback(fontnames): 15 | for name in fontnames: 16 | if name in _DEFAULT_FONT_NAMES: 17 | return name 18 | elif QFont(name).exactMatch(): 19 | return name 20 | return 'serif' 21 | 22 | 23 | def css_replace_fontfamily(text): 24 | def replace_func(m): 25 | families = (s.strip().strip('"\'') for s in m.group(2).split(',')) 26 | return ''.join(( 27 | m.group(1), 28 | 'font-family: "', 29 | _fallback(families), 30 | '"', 31 | m.group(3))) 32 | 33 | return re.sub(r"({|;)\s*font-family\s*:\s*(.*?)(;|})", 34 | replace_func, text, flags=re.MULTILINE | re.DOTALL) 35 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/utils/mp3play/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | if os.name == 'nt': 4 | from .windows import AudioClip as _PlatformSpecificAudioClip 5 | else: 6 | raise Exception("mp3play can't run on your operating system.") 7 | 8 | def load(filename): 9 | """Return an AudioClip for the given filename.""" 10 | return AudioClip(filename) 11 | 12 | class AudioClip(object): 13 | __slots__ = ['_clip'] 14 | 15 | def __init__(self, filename): 16 | """Create an AudioClip for the given filename.""" 17 | self._clip = _PlatformSpecificAudioClip(filename) 18 | 19 | def play(self, start_ms=None, end_ms=None): 20 | """ 21 | Start playing the audio clip, and return immediately. Play from 22 | start_ms to end_ms if either is specified; defaults to beginning 23 | and end of the clip. Returns immediately. If end_ms is specified 24 | as smaller than start_ms, nothing happens. 25 | """ 26 | if end_ms != None and end_ms < start_ms: 27 | return 28 | else: 29 | return self._clip.play(start_ms, end_ms) 30 | 31 | def volume(self, level): 32 | """Sets the volume between 0 and 100.""" 33 | assert level >=0 and level <= 100 34 | return self._clip.volume(level) 35 | 36 | def isplaying(self): 37 | """Returns True if the clip is currently playing. Note that if a 38 | clip is paused, or if you called play() on a clip and playing has 39 | completed, this returns False.""" 40 | return self._clip.isplaying() 41 | 42 | def pause(self): 43 | """Pause the clip if it is currently playing.""" 44 | return self._clip.pause() 45 | 46 | def unpause(self): 47 | """Unpause the clip if it is currently paused.""" 48 | return self._clip.unpause() 49 | 50 | def ispaused(self): 51 | """Returns True if the clip is currently paused.""" 52 | return self._clip.ispaused() 53 | 54 | def stop(self): 55 | """Stop the audio clip if it is playing.""" 56 | return self._clip.stop() 57 | 58 | def close(self): 59 | """Stop the audio clip if it is playing.""" 60 | return self._clip.close() 61 | 62 | def seconds(self): 63 | """ 64 | Returns the length in seconds of the audio clip, rounded to the 65 | nearest second. 66 | """ 67 | return int(round(float(self.milliseconds()) / 1000)) 68 | 69 | def milliseconds(self): 70 | """Returns the length in milliseconds of the audio clip.""" 71 | return self._clip.milliseconds() 72 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/utils/mp3play/windows.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals, absolute_import, print_function 2 | 3 | import random 4 | from ctypes import windll, c_buffer 5 | 6 | 7 | 8 | class _MCI: 9 | def __init__(self): 10 | self.w32mci = windll.winmm.mciSendStringA 11 | self.w32mcierror = windll.winmm.mciGetErrorStringA 12 | 13 | def send(self, command): 14 | buffer = c_buffer(255) 15 | errorcode = self.w32mci(str(command), buffer, 254, 0) 16 | if errorcode: 17 | return errorcode, self.get_error(errorcode) 18 | else: 19 | return errorcode, buffer.value 20 | 21 | def get_error(self, error): 22 | error = int(error) 23 | buffer = c_buffer(255) 24 | self.w32mcierror(error, buffer, 254) 25 | return buffer.value 26 | 27 | def directsend(self, txt): 28 | (err, buf) = self.send(txt) 29 | if err != 0: 30 | print('Error %s for "%s": %s' % (str(err), txt, buf)) 31 | return (err, buf) 32 | 33 | 34 | # TODO: detect errors in all mci calls 35 | class AudioClip(object): 36 | def __init__(self, filename): 37 | filename = filename.replace('/', '\\') 38 | self.filename = filename 39 | self._alias = 'mp3_%s' % repr(random.random()) 40 | 41 | self._mci = _MCI() 42 | 43 | (err, buf) = self._mci.directsend( 44 | r'open "%s" alias %s' % (filename, self._alias )) 45 | if err: 46 | raise Exception(buf) 47 | 48 | (err, buf) = self._mci.directsend( 49 | 'set %s time format milliseconds' % self._alias) 50 | if err: 51 | raise Exception(buf) 52 | 53 | (err, buf) = self._mci.directsend( 54 | 'status %s length' % self._alias) 55 | if err: 56 | raise Exception(buf) 57 | 58 | self._length_ms = int(buf) 59 | 60 | def volume(self, level): 61 | """Sets the volume between 0 and 100.""" 62 | self._mci.directsend('setaudio %s volume to %d' % 63 | (self._alias, level * 10) ) 64 | 65 | def play(self, start_ms=None, end_ms=None): 66 | start_ms = 0 if not start_ms else start_ms 67 | end_ms = self.milliseconds() if not end_ms else end_ms 68 | err,buf=self._mci.directsend('play %s from %d to %d' 69 | % (self._alias, start_ms, end_ms) ) 70 | 71 | def isplaying(self): 72 | return self._mode() == 'playing' 73 | 74 | def _mode(self): 75 | err, buf = self._mci.directsend('status %s mode' % self._alias) 76 | return buf 77 | 78 | def pause(self): 79 | self._mci.directsend('pause %s' % self._alias) 80 | 81 | def unpause(self): 82 | self._mci.directsend('resume %s' % self._alias) 83 | 84 | def ispaused(self): 85 | return self._mode() == 'paused' 86 | 87 | def stop(self): 88 | self._mci.directsend('stop %s' % self._alias) 89 | self._mci.directsend('seek %s to start' % self._alias) 90 | 91 | def close(self): 92 | if self._alias is not None: 93 | self._mci.directsend('close %s' % self._alias) 94 | self._alias = None 95 | 96 | def milliseconds(self): 97 | return self._length_ms 98 | 99 | # TODO: this closes the file even if we're still playing. 100 | # no good. detect isplaying(), and don't die till then! 101 | def __del__(self): 102 | self.close() 103 | 104 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/utils/singleapp.py: -------------------------------------------------------------------------------- 1 | '''This module prevent you from running two instances of the app''' 2 | 3 | 4 | from PyQt5.QtCore import pyqtSignal, QIODevice 5 | from PyQt5.QtWidgets import QApplication 6 | from PyQt5.QtNetwork import QLocalSocket, QLocalServer 7 | 8 | 9 | class SingleApplication(QApplication): 10 | messageAvailable = pyqtSignal(type(u'')) 11 | 12 | def __init__(self, argv, key): 13 | QApplication.__init__(self, argv) 14 | 15 | self._key = key 16 | self._timeout = 1000 17 | 18 | socket = QLocalSocket(self) 19 | socket.connectToServer(self._key) 20 | if socket.waitForConnected(self._timeout): 21 | self._isRunning = True 22 | socket.abort() 23 | return 24 | socket.abort() 25 | 26 | self._isRunning = False 27 | self._server = QLocalServer(self) 28 | self._server.newConnection.connect(self.__onNewConnection) 29 | self._server.listen(self._key) 30 | 31 | self.aboutToQuit.connect(self.__onAboutToQuit) 32 | 33 | def __onAboutToQuit(self): 34 | if self._server: 35 | self._server.close() 36 | self._server = None 37 | 38 | def __onNewConnection(self): 39 | socket = self._server.nextPendingConnection() 40 | if socket.waitForReadyRead(self._timeout): 41 | self.messageAvailable.emit(socket.readAll().data().decode('utf-8')) 42 | socket.disconnectFromServer() 43 | else: 44 | pass 45 | 46 | def isRunning(self): 47 | return self._isRunning 48 | 49 | def sendMessage(self, message): 50 | assert(self._isRunning) 51 | 52 | if self.isRunning(): 53 | socket = QLocalSocket(self) 54 | socket.connectToServer(self._key, QIODevice.WriteOnly) 55 | if not socket.waitForConnected(self._timeout): 56 | return False 57 | socket.write(message.encode('utf-8')) 58 | if not socket.waitForBytesWritten(self._timeout): 59 | return False 60 | socket.disconnectFromServer() 61 | return True 62 | return False 63 | -------------------------------------------------------------------------------- /ldoce5viewer/qtgui/utils/soundplayer.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import abc 4 | from tempfile import NamedTemporaryFile, mkdtemp 5 | import logging 6 | 7 | from PyQt5.QtCore import * 8 | from ...utils.compat import range 9 | 10 | _logger = logging.getLogger(__name__) 11 | 12 | # Gstreamer 1.0 13 | try: 14 | import gi 15 | gi.require_version('Gst', '1.0') 16 | from gi.repository import GObject, Gst 17 | GObject.threads_init() 18 | Gst.init(None) 19 | except (ImportError, ValueError): 20 | Gst = None 21 | GObject = None 22 | 23 | # Gstreamer 0.10 24 | try: 25 | if Gst is not None: 26 | raise ImportError() 27 | import gst 28 | import gobject 29 | gobject.threads_init() 30 | except ImportError: 31 | gst = None 32 | gobject = None 33 | 34 | # Cocoa via PyObjC 35 | try: 36 | import AppKit 37 | except: 38 | AppKit = None 39 | 40 | 41 | # WinMCI 42 | if sys.platform == 'win32': 43 | try: 44 | import mp3play 45 | except: 46 | mp3play = None 47 | else: 48 | mp3play = None 49 | 50 | 51 | # Qt-Phonon 52 | try: 53 | from PyQt5.phonon import Phonon 54 | except ImportError: 55 | Phonon = None 56 | 57 | 58 | # Qt-Multimedia 59 | try: 60 | import PyQt5.QtMultimedia as QtMultimedia 61 | except ImportError: 62 | QtMultimedia = None 63 | 64 | 65 | class Backend(object): 66 | __metaclass__ = abc.ABCMeta 67 | def __init__(self, parent, temp_dir): 68 | pass 69 | @abc.abstractmethod 70 | def play(self, data): 71 | pass 72 | @abc.abstractmethod 73 | def close(self): 74 | pass 75 | 76 | 77 | class NullBackend(Backend): 78 | def __init__(self, parent, temp_dir): 79 | pass 80 | def play(self, data): 81 | pass 82 | def close(self): 83 | pass 84 | 85 | 86 | class GstreamerBackend(Backend): 87 | """Backend for Gstreamer 1.0""" 88 | 89 | def __init__(self, parent, temp_dir): 90 | self._player = None 91 | self._data = None 92 | 93 | def play(self, data): 94 | if self._player: 95 | self._player.set_state(Gst.State.NULL) 96 | 97 | try: 98 | self._player = Gst.parse_launch( 99 | 'appsrc name=src ! decodebin ! autoaudiosink') 100 | except: 101 | _logger.error( 102 | "Gstreamer's good-plugins package is needed to play sound") 103 | return 104 | 105 | self._player.set_state(Gst.State.NULL) 106 | bus = self._player.get_bus() 107 | bus.add_signal_watch() 108 | bus.connect("message", self._on_message) 109 | 110 | def need_data(appsrc, size): 111 | if not self._data: 112 | appsrc.emit('end-of-stream') 113 | return 114 | appsrc.emit('push-buffer', Gst.Buffer.new_wrapped(self._data[:size])) 115 | self._data = self._data[size:] 116 | 117 | self._data = data 118 | self._player.get_by_name('src').connect('need-data', need_data) 119 | self._player.set_state(Gst.State.PLAYING) 120 | 121 | def _on_message(self, bus, message): 122 | if message.type == Gst.MessageType.EOS: 123 | self._player.set_state(Gst.State.NULL) 124 | self._player = None 125 | 126 | def close(self): 127 | if self._player: 128 | self._player.set_state(Gst.State.NULL) 129 | self._player = None 130 | 131 | 132 | class GstreamerOldBackend(Backend): 133 | """Backend for Gstreamer 0.10""" 134 | 135 | def __init__(self, parent, temp_dir): 136 | self._player = None 137 | self._data = None 138 | 139 | def play(self, data): 140 | if self._player: 141 | self._player.set_state(gst.STATE_NULL) 142 | 143 | try: 144 | self._player = gst.parse_launch( 145 | 'appsrc name=src ! decodebin2 ! autoaudiosink') 146 | except: 147 | _logger.error( 148 | "Gstreamer's good-plugins package is needed to play sound") 149 | return 150 | 151 | self._player.set_state(gst.STATE_NULL) 152 | bus = self._player.get_bus() 153 | bus.add_signal_watch() 154 | bus.connect("message", self._on_message) 155 | 156 | def need_data(appsrc, size): 157 | if not self._data: 158 | appsrc.emit('end-of-stream') 159 | return 160 | appsrc.emit('push-buffer', gst.Buffer(self._data[:size])) 161 | self._data = self._data[size:] 162 | 163 | self._data = data 164 | self._player.get_by_name('src').connect('need-data', need_data) 165 | self._player.set_state(gst.STATE_PLAYING) 166 | 167 | def _on_message(self, bus, message): 168 | if message.type == gst.MESSAGE_EOS: 169 | self._player.set_state(gst.STATE_NULL) 170 | self._player = None 171 | 172 | def close(self): 173 | if self._player: 174 | self._player.set_state(gst.STATE_NULL) 175 | self._player = None 176 | 177 | 178 | class WinMCIBackend(Backend): 179 | def __init__(self, parent, temp_dir): 180 | self._mp3 = None 181 | self._NUM_TRY = 30 182 | self._temp_dir = temp_dir 183 | 184 | def _get_f(self): 185 | for i in range(self._NUM_TRY): 186 | path = os.path.join(self._temp_dir, 187 | "sound.tmp{0}.mp3".format(i)) 188 | try: 189 | os.unlink(path) 190 | except: 191 | pass 192 | try: 193 | f = open(path, "wb") 194 | except IOError: 195 | continue 196 | else: 197 | return f 198 | return None 199 | 200 | def play(self, data): 201 | if self._mp3: 202 | self._mp3.stop() 203 | self._mp3.close() 204 | self._mp3 = None 205 | f = self._get_f() 206 | if f is None: 207 | return 208 | f.write(data) 209 | path = f.name 210 | f.close() 211 | self._mp3 = mp3play.load(path) 212 | self._mp3.play() 213 | 214 | def close(self): 215 | for i in range(self._NUM_TRY): 216 | path = os.path.join(self._temp_dir, 217 | "sound.tmp{0}.mp3".format(i)) 218 | try: 219 | os.unlink(path) 220 | except: 221 | pass 222 | 223 | 224 | class PhononBackend(Backend): 225 | def __init__(self, parent, temp_dir): 226 | self._player = Phonon.createPlayer(Phonon.NoCategory) 227 | self._player.finished.connect(self._onFinished) 228 | self._alive = set() 229 | 230 | def _onFinished(self): 231 | self._clean_tmp() 232 | 233 | def _play(self): 234 | source = Phonon.MediaSource(self._path) 235 | self._player.setCurrentSource(source) 236 | self._player.play() 237 | 238 | def play(self, data): 239 | self._player.stop() 240 | self._clean_tmp() 241 | with NamedTemporaryFile(mode='w+b', prefix='', 242 | suffix='.tmp.mp3', delete=False) as f: 243 | f.write(data) 244 | self._path = f.name 245 | self._alive.add(f.name) 246 | QTimer.singleShot(0, self._play) 247 | 248 | def _clean_tmp(self): 249 | removed = [] 250 | for path in self._alive: 251 | try: 252 | os.unlnk(path) 253 | except: 254 | pass 255 | else: 256 | removed.append(path) 257 | self._alive.difference_update(removed) 258 | 259 | def close(self): 260 | self._player.stop() 261 | self._clean_tmp() 262 | 263 | 264 | class QtMultimediaBackend(Backend): 265 | def __init__(self, parent, temp_dir): 266 | self._player = QtMultimedia.QMediaPlayer() 267 | self._tmpdir = mkdtemp() 268 | 269 | def _play(self): 270 | url = QUrl.fromLocalFile(self._path) 271 | content = QtMultimedia.QMediaContent(url) 272 | self._player.setMedia(content) 273 | self._player.play() 274 | 275 | def play(self, data): 276 | self._player.stop() 277 | with NamedTemporaryFile(mode='w+b', prefix='', 278 | suffix='.tmp.mp3', dir=self._tmpdir, delete=False) as f: 279 | f.write(data) 280 | self._path = f.name 281 | QTimer.singleShot(0, self._play) 282 | 283 | def close(self): 284 | self._player.stop() 285 | 286 | 287 | class AppKitBackend(Backend): 288 | def __init__(self, parent, temp_dir): 289 | self._sound = None 290 | 291 | def stop(self): 292 | if self._sound: 293 | self._sound.stop() 294 | 295 | def play(self, data): 296 | if self._sound: 297 | self._sound.stop() 298 | 299 | self._sound = AppKit.NSSound.alloc().initWithData_(data) 300 | self._sound.play() 301 | 302 | def close(self): 303 | self.stop() 304 | 305 | 306 | def create_soundplayer(parent, temp_dir): 307 | backends = [] 308 | if AppKit: 309 | backends.append(AppKitBackend) 310 | if mp3play: 311 | backends.append(WinMCIBackend) 312 | if Phonon: 313 | backends.append(PhononBackend) 314 | if QtMultimedia: 315 | backends.append(QtMultimediaBackend) 316 | if Gst: 317 | backends.append(GstreamerBackend) 318 | if gst: 319 | backends.append(GstreamerOldBackend) 320 | backends.append(NullBackend) 321 | 322 | return backends[0](parent, temp_dir) 323 | 324 | 325 | 326 | -------------------------------------------------------------------------------- /ldoce5viewer/static/documents/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | About LDOCE5 Viewer 6 | 7 | 8 | 9 |

About LDOCE5 Viewer

10 |
11 | 12 |
13 |

LDOCE5 Viewer is an alternative dictionary viewer for “Longman Dictionary of Contemporary English 5th Edition.”

14 |

Version: {% current_version %}

15 | 16 |

Author

17 | This software is written by Taku Fukada. 18 | 19 |

Acknowledgements

20 |

Tomasz P. Szynalski made some improvements on stylesheets. He is a founder of Antimoon — a site which tells people how to learn English effectively. He also provided many suggestions and bug reports.

21 | 22 |

This application utilizes the following libraries and software:

23 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /ldoce5viewer/static/images/external-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/images/external-hover.png -------------------------------------------------------------------------------- /ldoce5viewer/static/images/external-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/images/external-link.png -------------------------------------------------------------------------------- /ldoce5viewer/static/images/external.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/images/external.png -------------------------------------------------------------------------------- /ldoce5viewer/static/images/sp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/images/sp.png -------------------------------------------------------------------------------- /ldoce5viewer/static/images/speaker_am.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/images/speaker_am.png -------------------------------------------------------------------------------- /ldoce5viewer/static/images/speaker_br.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/images/speaker_br.png -------------------------------------------------------------------------------- /ldoce5viewer/static/images/speaker_eg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/images/speaker_eg.png -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/activator.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/scripts/activator.js -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/body.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/scripts/body.js -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/collocations.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/scripts/collocations.js -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/colorbox/colorbox.css: -------------------------------------------------------------------------------- 1 | #colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:visible;} 2 | #cboxOverlay{position:fixed; width:100%; height:100%;} 3 | #cboxMiddleLeft, #cboxBottomLeft{clear:left;} 4 | #cboxContent{position:relative; } 5 | #cboxLoadedContent{overflow:auto;} 6 | #cboxTitle{margin:0;} 7 | #cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%; height:100%;} 8 | #cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;} 9 | .cboxPhoto{float:left; margin:auto; border:0; display:block;} 10 | .cboxIframe{width:100%; height:100%; display:block; border:0;} 11 | 12 | 13 | #cboxOverlay{background:#fff;} 14 | #colorbox{} 15 | #cboxContent{overflow:visible; } 16 | .cboxIframe{background:#fff;} 17 | #cboxError{padding:50px; border:1px solid #ccc;} 18 | #cboxLoadedContent{background:#777; padding:1px; } 19 | #cboxLoadingGraphic{background:url(images/loading.gif) no-repeat center center;} 20 | #cboxLoadingOverlay{background:#000;} 21 | #cboxTitle{position:absolute; top:-16px; left:0; color:#000;} 22 | #cboxCurrent {position:absolute; top:-18px; right:205px; text-indent:-9999px; } 23 | #cboxClose{text-indent:-9999px; width:16px; height:16px; position:absolute; top:-18px; background:url(images/close.png) no-repeat 0 0;} 24 | #cboxClose { background-position:0px 0px; right:0; } 25 | #cboxClose:hover { background-position:0px -16px; } 26 | -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/colorbox/images/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/scripts/colorbox/images/close.png -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/colorbox/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/scripts/colorbox/images/loading.gif -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/colorbox/jquery.colorbox.js: -------------------------------------------------------------------------------- 1 | /* 2 | jQuery ColorBox v1.3.32 - 2013-01-31 3 | (c) 2013 Jack Moore - jacklmoore.com/colorbox 4 | license: http://www.opensource.org/licenses/mit-license.php 5 | */ 6 | (function(e,t,i){function o(i,o,n){var h=t.createElement(i);return o&&(h.id=U+o),n&&(h.style.cssText=n),e(h)}function n(e){var t=b.length,i=(A+e)%t;return 0>i?t+i:i}function h(e,t){return Math.round((/%/.test(e)?("x"===t?T.width():T.height())/100:1)*parseInt(e,10))}function l(e){return _.photo||/\.(gif|png|jp(e|g|eg)|bmp|ico)((#|\?).*)?$/i.test(e)}function r(){var t,i=e.data(N,J);null==i?(_=e.extend({},$),console&&console.log&&console.log("Error: cboxElement missing settings object")):_=e.extend({},i);for(t in _)e.isFunction(_[t])&&"on"!==t.slice(0,2)&&(_[t]=_[t].call(N));_.rel=_.rel||N.rel||e(N).data("rel")||"nofollow",_.href=_.href||e(N).attr("href"),_.title=_.title||N.title,"string"==typeof _.href&&(_.href=e.trim(_.href))}function s(i,o){e(t).trigger(i),rt.trigger(i),e.isFunction(o)&&o.call(N)}function a(){var e,t,i,o,n,h=U+"Slideshow_",l="click."+U;_.slideshow&&b[1]?(t=function(){clearTimeout(e)},i=function(){(_.loop||b[A+1])&&(e=setTimeout(G.next,_.slideshowSpeed))},o=function(){I.html(_.slideshowStop).unbind(l).one(l,n),rt.bind(et,i).bind(Z,t).bind(tt,n),p.removeClass(h+"off").addClass(h+"on")},n=function(){t(),rt.unbind(et,i).unbind(Z,t).unbind(tt,n),I.html(_.slideshowStart).unbind(l).one(l,function(){G.next(),o()}),p.removeClass(h+"on").addClass(h+"off")},_.slideshowAuto?o():n()):p.removeClass(h+"off "+h+"on")}function d(t){j||(N=t,r(),b=e(N),A=0,"nofollow"!==_.rel&&(b=e("."+V).filter(function(){var t,i=e.data(this,J);return i&&(t=e(this).data("rel")||i.rel||this.rel),t===_.rel}),A=b.index(N),-1===A&&(b=b.add(N),A=b.length-1)),P||(P=R=!0,p.css({visibility:"hidden",display:"block"}),C=o(st,"LoadedContent","width:0; height:0; overflow:hidden").appendTo(w),K=g.height()+x.height()+w.outerHeight(!0)-w.height(),z=y.width()+v.width()+w.outerWidth(!0)-w.width(),D=C.outerHeight(!0),B=C.outerWidth(!0),_.returnFocus&&(e(N).blur(),rt.one(it,function(){e(N).focus()})),f.css({opacity:parseFloat(_.opacity),cursor:_.overlayClose?"pointer":"auto",visibility:"visible"}).show(),_.w=h(_.initialWidth,"x"),_.h=h(_.initialHeight,"y"),G.position(),ht&&T.bind("resize."+lt+" scroll."+lt,function(){f.css({width:T.width(),height:T.height(),top:T.scrollTop(),left:T.scrollLeft()})}).trigger("resize."+lt),a(),s(Y,_.onOpen),S.add(H).hide(),F.html(_.close).show()),G.load(!0))}function c(){!p&&t.body&&(X=!1,T=e(i),p=o(st).attr({id:J,"class":nt?U+(ht?"IE6":"IE"):""}).hide(),f=o(st,"Overlay",ht?"position:absolute":"").hide(),W=o(st,"LoadingOverlay").add(o(st,"LoadingGraphic")),m=o(st,"Wrapper"),w=o(st,"Content").append(H=o(st,"Title"),E=o(st,"Current"),L=o(st,"Next"),M=o(st,"Previous"),I=o(st,"Slideshow"),F=o(st,"Close")),m.append(o(st).append(o(st,"TopLeft"),g=o(st,"TopCenter"),o(st,"TopRight")),o(st,!1,"clear:left").append(y=o(st,"MiddleLeft"),w,v=o(st,"MiddleRight")),o(st,!1,"clear:left").append(o(st,"BottomLeft"),x=o(st,"BottomCenter"),o(st,"BottomRight"))).find("div div").css({"float":"left"}),k=o(st,!1,"position:absolute; width:9999px; visibility:hidden; display:none"),S=L.add(M).add(E).add(I),e(t.body).append(f,p.append(m,k)))}function u(){function i(e){e.which>1||e.shiftKey||e.altKey||e.metaKey||(e.preventDefault(),d(this))}return p?(X||(X=!0,L.click(function(){G.next()}),M.click(function(){G.prev()}),F.click(function(){G.close()}),f.click(function(){_.overlayClose&&G.close()}),e(t).bind("keydown."+U,function(e){var t=e.keyCode;P&&_.escKey&&27===t&&(e.preventDefault(),G.close()),P&&_.arrowKey&&b[1]&&(37===t?(e.preventDefault(),M.click()):39===t&&(e.preventDefault(),L.click()))}),e.isFunction(e.fn.on)?e(t).on("click."+U,"."+V,i):e("."+V).live("click."+U,i)),!0):!1}var f,p,m,w,g,y,v,x,b,T,C,k,W,H,E,I,L,M,F,S,_,K,z,D,B,N,A,O,P,R,j,q,G,Q,X,$={transition:"elastic",speed:300,width:!1,initialWidth:"600",innerWidth:!1,maxWidth:!1,height:!1,initialHeight:"450",innerHeight:!1,maxHeight:!1,scalePhotos:!0,scrolling:!0,inline:!1,html:!1,iframe:!1,fastIframe:!0,photo:!1,href:!1,title:!1,rel:!1,opacity:.9,preloading:!0,className:!1,current:"image {current} of {total}",previous:"previous",next:"next",close:"close",xhrError:"This content failed to load.",imgError:"This image failed to load.",open:!1,returnFocus:!0,reposition:!0,loop:!0,slideshow:!1,slideshowAuto:!0,slideshowSpeed:2500,slideshowStart:"start slideshow",slideshowStop:"stop slideshow",onOpen:!1,onLoad:!1,onComplete:!1,onCleanup:!1,onClosed:!1,overlayClose:!0,escKey:!0,arrowKey:!0,top:!1,bottom:!1,left:!1,right:!1,fixed:!1,data:void 0},J="colorbox",U="cbox",V=U+"Element",Y=U+"_open",Z=U+"_load",et=U+"_complete",tt=U+"_cleanup",it=U+"_closed",ot=U+"_purge",nt=!e.support.leadingWhitespace,ht=nt&&!i.XMLHttpRequest,lt=U+"_IE6",rt=e({}),st="div";e.colorbox||(e(c),G=e.fn[J]=e[J]=function(t,i){var o=this;if(t=t||{},c(),u()){if(e.isFunction(o))o=e(""),t.open=!0;else if(!o[0])return o;i&&(t.onComplete=i),o.each(function(){e.data(this,J,e.extend({},e.data(this,J)||$,t))}).addClass(V),(e.isFunction(t.open)&&t.open.call(o)||t.open)&&d(o[0])}return o},G.position=function(e,t){function i(e){g[0].style.width=x[0].style.width=w[0].style.width=parseInt(e.style.width,10)-z+"px",w[0].style.height=y[0].style.height=v[0].style.height=parseInt(e.style.height,10)-K+"px"}var o,n,l,r=0,s=0,a=p.offset();T.unbind("resize."+U),p.css({top:-9e4,left:-9e4}),n=T.scrollTop(),l=T.scrollLeft(),_.fixed&&!ht?(a.top-=n,a.left-=l,p.css({position:"fixed"})):(r=n,s=l,p.css({position:"absolute"})),s+=_.right!==!1?Math.max(T.width()-_.w-B-z-h(_.right,"x"),0):_.left!==!1?h(_.left,"x"):Math.round(Math.max(T.width()-_.w-B-z,0)/2),r+=_.bottom!==!1?Math.max(T.height()-_.h-D-K-h(_.bottom,"y"),0):_.top!==!1?h(_.top,"y"):Math.round(Math.max(T.height()-_.h-D-K,0)/2),p.css({top:a.top,left:a.left,visibility:"visible"}),e=p.width()===_.w+B&&p.height()===_.h+D?0:e||0,m[0].style.width=m[0].style.height="9999px",o={width:_.w+B+z,height:_.h+D+K,top:r,left:s},0===e&&p.css(o),p.dequeue().animate(o,{duration:e,complete:function(){i(this),R=!1,m[0].style.width=_.w+B+z+"px",m[0].style.height=_.h+D+K+"px",_.reposition&&setTimeout(function(){T.bind("resize."+U,G.position)},1),t&&t()},step:function(){i(this)}})},G.resize=function(e){P&&(e=e||{},e.width&&(_.w=h(e.width,"x")-B-z),e.innerWidth&&(_.w=h(e.innerWidth,"x")),C.css({width:_.w}),e.height&&(_.h=h(e.height,"y")-D-K),e.innerHeight&&(_.h=h(e.innerHeight,"y")),e.innerHeight||e.height||(C.css({height:"auto"}),_.h=C.height()),C.css({height:_.h}),G.position("none"===_.transition?0:_.speed))},G.prep=function(t){function i(){return _.w=_.w||C.width(),_.w=_.mw&&_.mw<_.w?_.mw:_.w,_.w}function h(){return _.h=_.h||C.height(),_.h=_.mh&&_.mh<_.h?_.mh:_.h,_.h}if(P){var r,a="none"===_.transition?0:_.speed;C.empty().remove(),C=o(st,"LoadedContent").append(t),C.hide().appendTo(k.show()).css({width:i(),overflow:_.scrolling?"auto":"hidden"}).css({height:h()}).prependTo(w),k.hide(),e(O).css({"float":"none"}),r=function(){function t(){nt&&p[0].style.removeAttribute("filter")}var i,h,r=b.length,d="frameBorder",c="allowTransparency";P&&(h=function(){clearTimeout(q),W.remove(),s(et,_.onComplete)},nt&&O&&C.fadeIn(100),H.html(_.title).add(C).show(),r>1?("string"==typeof _.current&&E.html(_.current.replace("{current}",A+1).replace("{total}",r)).show(),L[_.loop||r-1>A?"show":"hide"]().html(_.next),M[_.loop||A?"show":"hide"]().html(_.previous),_.slideshow&&I.show(),_.preloading&&e.each([n(-1),n(1)],function(){var t,i,o=b[this],n=e.data(o,J);n&&n.href?(t=n.href,e.isFunction(t)&&(t=t.call(o))):t=o.href,l(t)&&(i=new Image,i.src=t)})):S.hide(),_.iframe?(i=o("iframe")[0],d in i&&(i[d]=0),c in i&&(i[c]="true"),_.scrolling||(i.scrolling="no"),e(i).attr({src:_.href,name:(new Date).getTime(),"class":U+"Iframe",allowFullScreen:!0,webkitAllowFullScreen:!0,mozallowfullscreen:!0}).one("load",h).appendTo(C),rt.one(ot,function(){i.src="//about:blank"}),_.fastIframe&&e(i).trigger("load")):h(),"fade"===_.transition?p.fadeTo(a,1,t):t())},"fade"===_.transition?p.fadeTo(a,0,function(){G.position(0,r)}):G.position(a,r)}},G.load=function(t){var i,n,a,d=G.prep;R=!0,O=!1,N=b[A],t||r(),Q&&p.add(f).removeClass(Q),_.className&&p.add(f).addClass(_.className),Q=_.className,s(ot),s(Z,_.onLoad),_.h=_.height?h(_.height,"y")-D-K:_.innerHeight&&h(_.innerHeight,"y"),_.w=_.width?h(_.width,"x")-B-z:_.innerWidth&&h(_.innerWidth,"x"),_.mw=_.w,_.mh=_.h,_.maxWidth&&(_.mw=h(_.maxWidth,"x")-B-z,_.mw=_.w&&_.w<_.mw?_.w:_.mw),_.maxHeight&&(_.mh=h(_.maxHeight,"y")-D-K,_.mh=_.h&&_.h<_.mh?_.h:_.mh),i=_.href,q=setTimeout(function(){W.appendTo(w)},100),_.inline?(a=o(st).hide().insertBefore(e(i)[0]),rt.one(ot,function(){a.replaceWith(C.children())}),d(e(i))):_.iframe?d(" "):_.html?d(_.html):l(i)?(e(O=new Image).addClass(U+"Photo").bind("error",function(){_.title=!1,d(o(st,"Error").html(_.imgError))}).one("load",function(){var e;_.scalePhotos&&(n=function(){O.height-=O.height*e,O.width-=O.width*e},_.mw&&O.width>_.mw&&(e=(O.width-_.mw)/O.width,n()),_.mh&&O.height>_.mh&&(e=(O.height-_.mh)/O.height,n())),_.h&&(O.style.marginTop=Math.max(_.h-O.height,0)/2+"px"),b[1]&&(_.loop||b[A+1])&&(O.style.cursor="pointer",O.onclick=function(){G.next()}),nt&&(O.style.msInterpolationMode="bicubic"),setTimeout(function(){d(O)},1)}),setTimeout(function(){O.src=i},1)):i&&k.load(i,_.data,function(t,i){d("error"===i?o(st,"Error").html(_.xhrError):e(this).contents())})},G.next=function(){!R&&b[1]&&(_.loop||b[A+1])&&(A=n(1),G.load())},G.prev=function(){!R&&b[1]&&(_.loop||A)&&(A=n(-1),G.load())},G.close=function(){P&&!j&&(j=!0,P=!1,s(tt,_.onCleanup),T.unbind("."+U+" ."+lt),f.fadeTo(200,0),p.stop().fadeTo(300,0,function(){p.add(f).css({opacity:1,cursor:"auto"}).hide(),s(ot),C.empty().remove(),setTimeout(function(){j=!1,s(it,_.onClosed)},1)}))},G.remove=function(){e([]).add(p).add(f).remove(),p=null,e("."+V).removeData(J).removeClass(V),e(t).unbind("click."+U)},G.element=function(){return e(N)},G.settings=$)})(jQuery,document,window); -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/common.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/scripts/common.js -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/entry.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | $("a.illust").colorbox({ 3 | transition: 'none', 4 | opacity: 0.75, 5 | }); 6 | 7 | $("a[href*='/etymologies/']").colorbox({ 8 | iframe: true, 9 | width: '70%', 10 | height: '70%', 11 | transition: 'none', 12 | opacity: 0.75, 13 | }); 14 | 15 | $("a[href*='/word_families/']").colorbox({ 16 | iframe: true, 17 | width: '75%', 18 | height: '85%', 19 | transition: 'none', 20 | opacity: 0.75, 21 | }); 22 | }) 23 | 24 | -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/etymologies.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/scripts/etymologies.js -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/examples.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/scripts/examples.js -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/phrases.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/scripts/phrases.js -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/search.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/scripts/search.js -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/thesaurus.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/scripts/thesaurus.js -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/word_families.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/scripts/word_families.js -------------------------------------------------------------------------------- /ldoce5viewer/static/scripts/word_sets.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/static/scripts/word_sets.js -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/about.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Georgia, serif; 3 | line-height: 1.4; 4 | color: #333; 5 | } 6 | 7 | h1 { 8 | font-size: x-large; 9 | font-family: "Trebuchet MS", sans-serif; 10 | border-bottom: 4px double #444; 11 | margin-top: 0; 12 | } 13 | 14 | h2 { 15 | font-size: large; 16 | font-family: "Trebuchet MS", sans-serif; 17 | border-bottom: 1px solid #aaa; 18 | } 19 | 20 | a { 21 | text-decoration: none; 22 | color: #208080; 23 | } 24 | a:hover { color: #951740; } 25 | 26 | a.external { 27 | padding-right: 13px; 28 | background: url("static:///images/external-link.png") no-repeat right center; 29 | background-size: 10px 10px; 30 | } 31 | a.external:hover { 32 | background-image: url("static:///images/external-hover.png"); 33 | } 34 | 35 | -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/activator.css: -------------------------------------------------------------------------------- 1 | @import url('common.css'); 2 | @import url('body.css'); 3 | 4 | 5 | @media screen { 6 | html { height: 100%; } 7 | body { 8 | height: 100%; 9 | margin: 0; 10 | padding: 0; 11 | } 12 | #section_w { 13 | height: 100%; 14 | float: right; 15 | width: 100%; 16 | margin-left: -280px; 17 | } 18 | #section { 19 | height: 100%; 20 | padding: 0 15px; 21 | margin-left: 280px; 22 | overflow: auto; 23 | } 24 | #concept { 25 | height: 100%; 26 | position: relative; 27 | padding: 0 10px; 28 | width: 260px; 29 | overflow: auto; 30 | } 31 | } /* END @media screen */ 32 | 33 | @media print { 34 | #concept { display: none; } 35 | } /* END @media print */ 36 | 37 | 38 | /* CONCEPT */ 39 | 40 | #concept h1 { 41 | margin: 5px 0 10px; 42 | font-size: x-large; 43 | } 44 | #concept h2 { 45 | margin: 15px 0; 46 | font-size: large; 47 | text-transform: capitalize; 48 | } 49 | #concept .secnr { 50 | float: left; 51 | width: 1.4em; 52 | padding: 0.1em 0; 53 | text-align: right; 54 | white-space: nowrap; 55 | overflow: hidden; 56 | font-size: 95%; 57 | font-family: "Trebuchet MS", sans-serif; 58 | } 59 | #concept ul { 60 | padding-left: 1.8em; 61 | font-size: 95%; 62 | } 63 | #concept ul li { 64 | margin: 0.2em 0 0.2em 0; 65 | } 66 | #concept .section { 67 | margin: 0.3em 0 0.3em 1.8em; 68 | } 69 | #concept .section a { 70 | display: block; 71 | padding: 2px 0.35em 3px; 72 | vertical-align: middle; 73 | line-height: 1.3; 74 | background: -webkit-gradient(linear, left top, left bottom, 75 | color-stop(0%,#fcfcfc), color-stop(100%,#f8f8f7)); 76 | border: 1px solid #ddd; 77 | border-radius: 3px; 78 | font-family: "Segoe UI", Arial, sans-serif; 79 | font-size: 14px; 80 | } 81 | #concept .sel a { 82 | background: -webkit-gradient(linear, left top, left bottom, 83 | color-stop(0%,#edf7f7), color-stop(100%,#dfebeb)); 84 | border: 1px solid #a7b2b2; 85 | border-radius: 3px; 86 | color: #202020; 87 | } 88 | 89 | 90 | /* SECTION */ 91 | 92 | #section .exponent { 93 | margin: 0.4em 0; 94 | padding: 4px 5px 4px 10px; 95 | page-break-inside: avoid; 96 | } 97 | #section .exas { display: block; } 98 | #section .exp { margin-left: -10px; } 99 | #section .spelling { font-weight: bold; } 100 | #section .exponent .repeat { font-weight: bold; } 101 | #section .exponent .example { 102 | display: list-item; 103 | color: #333; 104 | margin: 0.1em 0 0.1em 2em; 105 | } 106 | #section .exp { font-size: large; } 107 | #section h1 { 108 | margin-top: 5px; 109 | font-size: x-large; 110 | } 111 | 112 | @media screen { 113 | #section .exponent:target { 114 | margin: 0.3em -7px; 115 | padding: 3px 4px 3px 16px; 116 | border: 1px solid #ba9; 117 | background: -webkit-gradient(linear, center top, center bottom, 118 | from(#fffff7), to(#fffff0)); 119 | box-shadow: 0 1px 5px #ccb; 120 | border-radius: 3px; 121 | } 122 | } /* END @media screen */ 123 | 124 | -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/body.css: -------------------------------------------------------------------------------- 1 | /* BOLD */ 2 | .amequiv, .brequiv, .orthvar, .fullform, .pasttense, .pastpart, .prespart, 3 | .ptandpp, .t3perssing, .pluralform, .comp, .superl, .lexvar, 4 | .abbr, .synopp, .syn, .opp, 5 | .colloc, .exp, 6 | .exponent .var, 7 | .hint .hintbo, .hint .hintbold, 8 | .defbold 9 | { 10 | font-weight: bold; 11 | } 12 | .hintitalic { font-style: italic; } 13 | 14 | /* ITALIC */ 15 | .thesexa, .collexa { font-style: italic; } 16 | 17 | .propform, .propformprep, .collo, .thespropform { 18 | font-weight: bold; 19 | color: #662c00; 20 | } 21 | .hint { 22 | margin: 0.4em 0; 23 | padding: 0.2em 0.3em; 24 | border: 1px solid #777; 25 | border-radius: 3px; 26 | clear: none; 27 | overflow: hidden; 28 | } 29 | .syn, .opp { 30 | display: inline-block; 31 | } 32 | .synopp { 33 | font-family: "Trebuchet MS", sans-serif; 34 | padding: 1px 3px; 35 | font-size: normal; 36 | color: white; 37 | background-color: #730; 38 | border-radius: 2px; 39 | font-size: 12px; 40 | vertical-align: text-bottom; 41 | } 42 | 43 | .pron, .amevarpron { 44 | font-family: 'Lucida Sans Unicode', 'Lucida Grande', 45 | 'Gentium Plus', 'Gentium', 'DejaVu Sans', 'Arial Unicode MS', 46 | 'sans-serif'; 47 | font-size: 90%; 48 | } 49 | 50 | .homnum, .refhomnum { 51 | vertical-align: super; 52 | font-size: medium; 53 | } 54 | 55 | .amequiv, .brequiv { 56 | display: inline-block; 57 | } 58 | -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/collocations.css: -------------------------------------------------------------------------------- 1 | @import url('common.css'); 2 | @import url('body.css'); 3 | 4 | /* HEADING */ 5 | 6 | h2 { 7 | font-size: x-large; 8 | border-bottom: 4px double; 9 | text-transform: uppercase; 10 | } 11 | h3 { 12 | font-size: large; 13 | border-bottom: 1px double; 14 | } 15 | 16 | 17 | /* COLLOCATION */ 18 | 19 | .collocate { 20 | margin: 0.7em 0 0.7em 1em; 21 | page-break-inside: avoid; 22 | } 23 | .colloc:first-child { margin-left: -1em; } 24 | .colloc { font-size: large; } 25 | .collexa { 26 | display: list-item; 27 | margin: 0.2em 0 0.2em 1.5em; 28 | color: #444; 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/colorbox.css: -------------------------------------------------------------------------------- 1 | /* 2 | ColorBox Core Style: 3 | The following CSS is consistent between example themes and should not be altered. 4 | */ 5 | #colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;} 6 | #cboxOverlay{position:fixed; width:100%; height:100%;} 7 | #cboxMiddleLeft, #cboxBottomLeft{clear:left;} 8 | #cboxContent{position:relative;} 9 | #cboxLoadedContent{overflow:auto;} 10 | #cboxTitle{margin:0;} 11 | #cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%; height:100%;} 12 | #cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;} 13 | .cboxPhoto{float:left; margin:auto; border:0; display:block;} 14 | .cboxIframe{width:100%; height:100%; display:block; border:0;} 15 | 16 | /* 17 | User Style: 18 | Change the following styles to modify the appearance of ColorBox. They are 19 | ordered & tabbed in a way that represents the nesting of the generated HTML. 20 | */ 21 | #cboxOverlay{background:#fff;} 22 | #colorbox{} 23 | #cboxContent{margin-top:32px; overflow:visible;} 24 | .cboxIframe{background:#fff;} 25 | #cboxError{padding:50px; border:1px solid #ccc;} 26 | #cboxLoadedContent{background:#000; padding:1px;} 27 | #cboxLoadingGraphic{background:url(images/loading.gif) no-repeat center center;} 28 | #cboxLoadingOverlay{background:#000;} 29 | #cboxTitle{position:absolute; top:-22px; left:0; color:#000;} 30 | #cboxCurrent{position:absolute; top:-22px; right:205px; text-indent:-9999px;} 31 | #cboxSlideshow, #cboxPrevious, #cboxNext, #cboxClose{text-indent:-9999px; width:20px; height:20px; position:absolute; top:-20px; background:url(images/controls.png) no-repeat 0 0;} 32 | #cboxPrevious{background-position:0px 0px; right:44px;} 33 | #cboxPrevious:hover{background-position:0px -25px;} 34 | #cboxNext{background-position:-25px 0px; right:22px;} 35 | #cboxNext:hover{background-position:-25px -25px;} 36 | #cboxClose{background-position:-50px 0px; right:0;} 37 | #cboxClose:hover{background-position:-50px -25px;} 38 | .cboxSlideshow_on #cboxPrevious, .cboxSlideshow_off #cboxPrevious{right:66px;} 39 | .cboxSlideshow_on #cboxSlideshow{background-position:-75px -25px; right:44px;} 40 | .cboxSlideshow_on #cboxSlideshow:hover{background-position:-100px -25px;} 41 | .cboxSlideshow_off #cboxSlideshow{background-position:-100px 0px; right:44px;} 42 | .cboxSlideshow_off #cboxSlideshow:hover{background-position:-75px -25px;} 43 | -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/common.css: -------------------------------------------------------------------------------- 1 | html { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | body { 6 | margin: 10px 10px 15px; 7 | padding: 0; 8 | font-family: 'Georgia', serif; 9 | line-height: 1.4; 10 | color: #202020; 11 | background-color: white; 12 | } 13 | h1, h2, h3 { 14 | font-family: "Trebuchet MS", sans-serif; 15 | } 16 | h1:first-child, h2:first-child, h3:first-child { margin-top: 0; } 17 | a { 18 | text-decoration: none; 19 | color: #208080; 20 | } 21 | a:hover { color: #aa1750; } 22 | ul, ol { padding-left: 2.5em; } 23 | .gram { color: #087b44; } 24 | .geo, .label { 25 | color: #087B44; 26 | font-family: "Trebuchet MS", sans-serif; 27 | font-size: 14px; 28 | } 29 | .registerlab { 30 | color: #087B44; 31 | font-family: "Trebuchet MS", sans-serif; 32 | font-size: 14px; 33 | } 34 | .infllab, .linkword, .varitype { font-size: small; } 35 | .b { font-weight: bold; } 36 | .i, .italic { font-style: italic; } 37 | .italic { font-weight: normal; } 38 | .geo, .example, .pos, .linkword, .varitype, 39 | .label, .registerlab, .infllab { 40 | font-weight: normal; 41 | font-style: italic; 42 | } 43 | 44 | .winxpsym { 45 | font-family: "Lucida Sans Unicode", sans-serif; 46 | } 47 | 48 | @media print { 49 | html { 50 | margin: auto; 51 | padding: auto; 52 | } 53 | body { 54 | color: black; 55 | margin: auto; 56 | padding: auto; 57 | width: auto; 58 | } 59 | } /* end of @media print */ 60 | 61 | 62 | -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/entry.css: -------------------------------------------------------------------------------- 1 | @import url('common.css'); 2 | @import url('body.css'); 3 | 4 | 5 | .audio:focus { 6 | outline: none; 7 | } 8 | 9 | /* HEAD */ 10 | 11 | .entry > .head { 12 | line-height: 1.3; 13 | margin: 0 0 0.6em 2px; 14 | } 15 | .entry > .head .hyphenation { 16 | font-family: "Trebuchet MS", sans-serif; 17 | font-size: 26px; 18 | font-weight: bold; 19 | margin-right: 2px; 20 | } 21 | .entry > .head .homnum { 22 | font-size: 16px; 23 | font-family: "Trebuchet MS", sans-serif; 24 | font-weight: bold; 25 | } 26 | .freq, .ac { 27 | font-family: "Trebuchet MS", sans-serif; 28 | font-size: 12px; 29 | font-weight: bold; 30 | background-color: #009a50; 31 | color: white; 32 | padding: 1px 3px; 33 | margin-left: 0.1em; 34 | border-radius: 2px; 35 | vertical-align: 1px; 36 | } 37 | .entry > .head .audio { 38 | margin: 0 3px 0 3px; 39 | vertical-align: -3px; 40 | } 41 | .entry > .head .audio img { 42 | height: 15px; 43 | } 44 | .entry > .head .infllab { font-size: medium; } 45 | 46 | 47 | /* ASSETS */ 48 | 49 | .assets { 50 | display: block; 51 | float: right; 52 | font-size: 13px; 53 | font-family: 'Trebuchet MS', sans-serif; 54 | margin: 0 0 1.0em 1.0em; 55 | background: #f3f3f3; 56 | } 57 | .assetbox { 58 | margin: 0.5em 0.3em 0.5em 0.3em; 59 | } 60 | .assethead { 61 | color: #333; 62 | font-weight: bold; 63 | font-style: italic; 64 | padding: 1px 0.5em; 65 | } 66 | .assetbody { 67 | line-height: 1.3; 68 | margin: 0; 69 | padding: 0em 0.5em 0.3em 0.5em; 70 | list-style: none; 71 | } 72 | 73 | .assetbody li a { display: block; } 74 | .assethead:after { 75 | content: ''; 76 | width: 11px; 77 | height: 11px; 78 | margin-left: 4px; 79 | display: inline-block; 80 | } 81 | 82 | .assets-word .assethead:after { background-color: #777; } 83 | .assets-collo .assethead:after { background-color: #0a6; } 84 | .assets-thes .assethead:after { background-color: #299; } 85 | .assets-phr .assethead:after { background-color: #a74; } 86 | .assets-exas .assethead:after { background-color: #a48; } 87 | 88 | .assets-link .assethead:after { 89 | background: url("static:///images/external.png") no-repeat center center; 90 | background-size: 11px 11px; 91 | } 92 | 93 | /* SENSE */ 94 | 95 | .sense { 96 | margin: 0.5em 0 0.7em 0; 97 | page-break-inside: avoid; 98 | } 99 | .subsense { margin: 0.3em 0; } 100 | .sensewithnum { margin-left: 1.2em; } 101 | .sensenum { 102 | font-family: Helvetica, Arial, sans-serif; 103 | display: inline-block; 104 | width: 1.0em; 105 | padding-right: 0.4em; 106 | margin-left: -1.4em; 107 | text-align: right; 108 | font-weight: bold; 109 | color: #207070; 110 | } 111 | .signpost { 112 | font-family: "Trebuchet MS", sans-serif; 113 | font-size: 12px; 114 | font-weight: bold; 115 | padding: 1px 4px; 116 | letter-spacing: 1px; 117 | text-transform: uppercase; 118 | white-space: pre; 119 | color: white; 120 | background-color: #3f7373; 121 | } 122 | 123 | span.gram { 124 | font-size: 14px; 125 | font-family: "Trebuchet MS", sans-serif; 126 | } 127 | 128 | .lexunit, .sense .lexvar { 129 | font-weight: bold; 130 | color: #662c00; 131 | } 132 | .sense .audio, .runon .audio { 133 | margin: 0; 134 | padding: 0; 135 | margin-right: 6px; 136 | text-decoration: none; 137 | } 138 | .sense .audio img, .tail .audio img{ 139 | height: 12px; 140 | } 141 | 142 | span.spokensect { 143 | font-weight: bold; 144 | font-size: large; 145 | } 146 | .example { 147 | margin: 0.1em 0 0.1em 0; 148 | color: #333; 149 | } 150 | .gramexa { 151 | margin: 0.1em 0 0.1em 0; 152 | } 153 | .gramexa .example { 154 | margin-left: 1em; 155 | } 156 | .crossref a { 157 | color: #730; 158 | border-bottom: solid 1px #b8a698; 159 | padding-bottom: 1px; 160 | } 161 | .crossref a:hover { color: #cc5700; } 162 | .colloinexa { font-weight: bold } 163 | .reflex, .relatedwd, .expr { font-weight: bold; } 164 | .deriv .base, .runon .lexvar .base { font-weight: bold; font-size: large; } 165 | .refhwd { text-transform: uppercase; font-size: 14px; } 166 | .nondv { text-transform: uppercase; font-size: 14px; } 167 | a.nondv { color: inherit; padding-bottom: 1px; border-bottom: solid 1px #b8a698; } 168 | .goodcollo, .badcollo { font-weight: bold; } 169 | .badcollo, .badexa { text-decoration: line-through; } 170 | .warning { color: #b34d00; } 171 | 172 | 173 | /* PHRASAL VERB */ 174 | 175 | .phrvbentry { margin: 0.7em 0; } 176 | .phrvbhwd { 177 | font-family: 'Trebuchet MS', sans-serif; 178 | font-size: 21px; 179 | font-weight: bold; 180 | margin-right: 0.2em; 181 | color: #662C00; 182 | } 183 | .phrvbentry .sense { margin: 0.2em 0; } 184 | .phrvbentry .sensewithnum { margin-left: 1.2em; } 185 | 186 | 187 | /* BOX (COLLOCATION, THESAURUS, ETC.) */ 188 | 189 | .thesbox, .f2nbox, .grambox, .collobox { 190 | border: 1px solid #666; 191 | margin: 0.8em 0; 192 | padding: 0 0.35em; 193 | clear: both; 194 | } 195 | .grambox, .f2nbox { 196 | clear: none; 197 | overflow: hidden; 198 | } 199 | div.heading { 200 | display: block; 201 | padding: 0.3em 0.35em 0.3em 0.35em; 202 | margin: 0 -0.35em; 203 | font-weight: bold; 204 | color: white; 205 | background-color: #444; 206 | background: -webkit-gradient(linear, center top, center bottom, 207 | from(#707070), to(#404040)); 208 | text-transform: uppercase; 209 | } 210 | .grambox div.heading, .f2nbox div.heading { 211 | padding: 0.15em 0.35em 0.15em 0.35em; 212 | } 213 | span.heading { 214 | font-weight: bold; 215 | font-size: 90%; 216 | display: block; 217 | padding: 0.1em 0.35em; 218 | margin: 0 -0.35em; 219 | text-transform: uppercase; 220 | } 221 | .secheading { 222 | font-size: 90%; 223 | padding: 0.2em 0.35em; 224 | margin: 0 -0.35em; 225 | text-transform: uppercase; 226 | color: white; 227 | background-color: #777; 228 | font-weight: bold; 229 | } 230 | .exponent, .collocate { 231 | margin: 0.2em 0; 232 | page-break-inside: avoid; 233 | } 234 | 235 | .grambox span.heading { 236 | background-color: #888; 237 | color: white; 238 | } 239 | 240 | .thesbox { border: 1px solid #579999; } 241 | .thesbox div.heading { 242 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#579999), color-stop(100%,#3a7070)); 243 | } 244 | .thesbox .secheading { background-color: #6a9999; } 245 | .thesbox .thesexa .base { color: #207070; font-size: 14px; } 246 | 247 | .collobox { border: solid 1px #44ab7a; } 248 | .collobox div.heading { 249 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#44ab7a), color-stop(100%,#3f8c67)); 250 | } 251 | .collobox .secheading { background-color: #68ba94; } 252 | .collobox .collexa .base { color: #207070; font-size: 14px; } 253 | 254 | .exponent, .collocate { padding-left: 0.5em; } 255 | .exp, .colloc { margin-left: -0.5em; } 256 | 257 | .expl { display: block; } 258 | .expl .example { display: inline; margin: 0; } 259 | 260 | .f2nbox .example, .grambox .example { 261 | display: list-item; 262 | list-style: circle; 263 | margin-left: 2em; 264 | } 265 | 266 | 267 | /* TARGET */ 268 | 269 | @media screen { 270 | .lexunit:target, 271 | .lexvar:target, 272 | .orthvar:target, 273 | .propform:target, 274 | .propformprep:target, 275 | .collo:target, 276 | .example:target, 277 | .colloc:target, 278 | .deriv:target, 279 | .phrvbentry:target .phrvbhwd { 280 | border-radius: 4px; 281 | background: -webkit-gradient(linear, center top, center bottom, 282 | from(#ffffcc), to(#ffffbb)); 283 | box-shadow: 0 0 8px #ffffbb; 284 | } 285 | .sense:target, 286 | .collocate:target, 287 | .exponent:target { 288 | border-radius: 4px; 289 | background: -webkit-gradient(linear, center top, center bottom, 290 | from(#ffffdd), to(#ffffcc)); 291 | box-shadow: 0 0 8px #ffffcc; 292 | } 293 | } /* end of @media screen */ 294 | 295 | 296 | /* PRINT */ 297 | 298 | @media print { 299 | .assets, .audio { display: none; } 300 | .example { 301 | display: list-item; 302 | list-style: circle; 303 | margin-left: 2.5em; 304 | } 305 | .sensewithnum { margin-left: 1.6em; } 306 | .sensenum { margin-left: -1.6em; } 307 | } /* end of @media print */ 308 | 309 | -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/etymologies.css: -------------------------------------------------------------------------------- 1 | @import url('common.css'); 2 | @import url('body.css'); 3 | 4 | .head { 5 | font-size: x-large; 6 | font-weight: bold; 7 | } 8 | .head .hwd { 9 | font-size: 26px; 10 | font-family: "Trebuchet MS", sans-serif; 11 | } 12 | .lead { font-weight: bold } 13 | .origin { color: #087b44; } 14 | 15 | .sense { 16 | display: block; 17 | margin: 0.5em 0; 18 | } 19 | 20 | .sense br:first-child { 21 | display: none; 22 | } 23 | 24 | .refhom { 25 | font-size: small; 26 | vertical-align: super; 27 | } 28 | -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/examples.css: -------------------------------------------------------------------------------- 1 | @import url('common.css'); 2 | 3 | h1 { 4 | font-size: x-large; 5 | margin: 0; 6 | } 7 | h1 .pos { font-size: large; } 8 | .example { 9 | font-style: normal; 10 | margin: 0.3em 0; 11 | page-break-inside: avoid; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/list.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #888; 3 | font-size: 13px; 4 | font-family: "Trebuchet MS", sans-serif; 5 | } 6 | 7 | .p { color: #777; font-size: 12px; font-style: italic; } 8 | .s { vertical-align: super; } 9 | 10 | .h { color: #730; } 11 | .n { font-weight: bold; } 12 | .f { font-weight: bold; } 13 | .pv { font-weight: bold; } 14 | .v { } 15 | 16 | .a { } 17 | .a .e { color: #208080; font-weight: bold; } 18 | .a .c { font-weight: bold; font-size: 12px; letter-spacing: 1px; } 19 | 20 | .c { } 21 | .c .o { color: #333; } 22 | .c .p { color: #888; } 23 | 24 | .l { } 25 | .l .o { color: #730; font-weight: bold; } 26 | .l .p { color: #888; } 27 | -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/phrases.css: -------------------------------------------------------------------------------- 1 | @import url('common.css'); 2 | 3 | h2 { 4 | font-size: large; 5 | margin: 0.5em 0; 6 | page-break-after: always; 7 | } 8 | ul { margin: 0.7em 0; } 9 | .example { 10 | font-style: normal; 11 | margin: 0.3em 0; 12 | page-break-inside: avoid; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/search.css: -------------------------------------------------------------------------------- 1 | @import url('common.css'); 2 | 3 | /* HEADINGS */ 4 | 5 | h1 { 6 | font-size: x-large; 7 | border-bottom: 4px double; 8 | text-transform: uppercase; 9 | } 10 | 11 | 12 | /* NAVIGATION */ 13 | 14 | ul.nav { 15 | font-family: 'Segoe UI', 'Tahoma', Verdana, sans-serif; 16 | font-size: 9pt; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | 21 | ul.nav li { 22 | display: inline-block; 23 | margin: 0.2em 0.2em 0.2em 0; 24 | } 25 | 26 | ul.nav a, ul.nav span.sel { 27 | display: block; 28 | padding: 0.4em 0.5em; 29 | border: 1px solid #999; 30 | border-radius: 0.3em; 31 | box-shadow: 0 1px 1px #ddd; 32 | text-shadow: 0 1px 2px #ffffff; 33 | } 34 | 35 | ul.nav a { 36 | color: #111; 37 | background: -webkit-gradient(linear, center top, center bottom, 38 | from(#f7f7f7), to(#d8d8d8)); 39 | } 40 | ul.nav a:hover { 41 | background: -webkit-gradient(linear, center top, center bottom, 42 | from(#fff), to(#e7e7e7)); 43 | } 44 | ul.nav span.sel { 45 | background: -webkit-gradient(linear, center top, center bottom, 46 | from(#d6d7dd), to(#e0e8f0)); 47 | } 48 | 49 | /* COMMAND */ 50 | 51 | ul.excmd { 52 | font-family: "Segoe UI", "Tahoma", "Helvetica", sans-serif; 53 | font-size: small; 54 | float: right; 55 | margin: 0; 56 | padding: 0; 57 | } 58 | 59 | ul.excmd li { 60 | display: block; 61 | padding: 0; 62 | margin: 0; 63 | margin-left: 1em; 64 | } 65 | 66 | ul.excmd li a { 67 | color: #777; 68 | text-decoration: underline; 69 | } 70 | 71 | /* RESULTS */ 72 | 73 | ul.result { 74 | margin: 1em 0; 75 | padding: 0 0 0 1.5em; 76 | } 77 | ul.result li { 78 | margin: 0.2em 0; 79 | padding: 0; 80 | page-break-inside: avoid; 81 | } 82 | ul.result a { 83 | padding: 0.2em 0.2em; 84 | color: #222; 85 | border: 1px solid transparent; 86 | border-radius: 0.2em; 87 | } 88 | ul.result a:hover { 89 | border: 1px solid #ba9; 90 | background-color: #fffae7; 91 | background: -webkit-gradient(linear, center top, center bottom, 92 | from(#fffff2), to(#ffffe7)); 93 | box-shadow: 0 1px 2px #edb; 94 | } 95 | 96 | 97 | ul.r_examples, ul.r_definitions { 98 | margin: 1em 0; 99 | padding: 0; 100 | } 101 | ul.r_examples li, ul.r_definitions li { 102 | margin: 0.4em 0; 103 | display: block; 104 | list-style: none; 105 | } 106 | ul.r_examples a, ul.r_definitions a { 107 | display: block; 108 | color: #222; 109 | padding: 0.2em 0.35em; 110 | border: 1px solid #ba9; 111 | border-radius: 2px; 112 | box-shadow: 0 1px 2px #ccc; 113 | } 114 | ul.r_examples a:hover, ul.r_definitions a:hover { 115 | background-color: #ffffe0; 116 | background: -webkit-gradient(linear, center top, center bottom, 117 | from(#ffffeb), to(#ffffd9)); 118 | box-shadow: 0 1px 2px #ccc; 119 | } 120 | 121 | 122 | .s_match { 123 | font-weight: bold; 124 | } 125 | 126 | .entry { 127 | display: block; 128 | } 129 | 130 | .label_n, .label_f { 131 | font-weight: bold; 132 | color: #3f7373; 133 | } 134 | .label_pv { 135 | font-weight: bold; 136 | color: #730; 137 | } 138 | .label_b { 139 | font-size: small; 140 | font-weight: bold; 141 | color: #555; 142 | } 143 | .label_c, .label_l { color: #777; } 144 | .label_l .label_o { 145 | color: #730; 146 | font-weight: bold; 147 | } 148 | .label_c .label_o { 149 | color: #333; 150 | font-weight: bold; 151 | } 152 | .label_c .label_n, .label_c .label_f, .label_c .label_p, 153 | .label_l .label_n, .label_l .label_f, .label_l .label_p 154 | { 155 | color: #777; 156 | } 157 | .label_p { 158 | color: #060; 159 | font-style: italic; 160 | font-size: small; 161 | } 162 | .label_s { 163 | vertical-align: super; 164 | font-size: 75%; 165 | } 166 | 167 | 168 | /* PRINT */ 169 | 170 | @media print { 171 | ul.nav { 172 | display: none; 173 | } 174 | } /* END of @media print */ 175 | 176 | -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/thesaurus.css: -------------------------------------------------------------------------------- 1 | @import url('common.css'); 2 | @import url('body.css'); 3 | 4 | /* HEADING */ 5 | 6 | h2 { 7 | font-size: x-large; 8 | border-bottom: 1px solid; 9 | } 10 | 11 | 12 | /* EXPONENT */ 13 | 14 | .exponent { 15 | margin: 0.7em 0 0.7em 0.7em; 16 | page-break-inside: avoid; 17 | } 18 | .colloc:first-child { margin-left: -0.7em; } 19 | .colloc { font-size: large; } 20 | .thesexa { 21 | display: list-item; 22 | margin: 0.2em 0 0.2em 1.5em; 23 | } 24 | .propexa { display: block; } 25 | .propexa .thesexa { margin-left: 2.5em; } 26 | 27 | -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/word_families.css: -------------------------------------------------------------------------------- 1 | @import url('common.css'); 2 | 3 | h2 { 4 | font-size: large; 5 | font-style: italic; 6 | font-weight: normal; 7 | } 8 | li { font-weight: bold; } 9 | 10 | -------------------------------------------------------------------------------- /ldoce5viewer/static/styles/word_sets.css: -------------------------------------------------------------------------------- 1 | @import url('common.css'); 2 | 3 | h2 { 4 | text-transform: uppercase; 5 | } 6 | ul { 7 | margin: 0; 8 | padding: 0; 9 | } 10 | li { 11 | display: inline-block; 12 | margin: 0.2em 0.7em 0.2em 0; 13 | } 14 | .hwd { 15 | font-weight: bold; 16 | } 17 | .pos { 18 | color: #777; 19 | font-size: small; 20 | } 21 | -------------------------------------------------------------------------------- /ldoce5viewer/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/purboo/ldoce5viewer-pyqt5/1f8340ea19908c54fad81f1771e34ae18bbf44f2/ldoce5viewer/utils/__init__.py -------------------------------------------------------------------------------- /ldoce5viewer/utils/cdb.py: -------------------------------------------------------------------------------- 1 | """A pure-python implementation of cdb""" 2 | 3 | from struct import Struct 4 | from mmap import mmap, ACCESS_READ 5 | 6 | _struct_2L = Struct(b'') 11 | MATCH_CLOSE_TAG = re.compile(r'\<(\/.+?)\>') 12 | 13 | 14 | def enc_utf8(s): 15 | return _utf8_encoder(s)[0] 16 | 17 | 18 | def dec_utf8(s): 19 | return _utf8_decoder(s)[0] 20 | 21 | 22 | def normalize_token(t): 23 | key = t.replace(u'\u00A9', u'c') 24 | 25 | def is_not_mn(c): 26 | cat = _unicode_category(c) 27 | return cat != 'Mn' 28 | 29 | return u''.join(c for c in _unicode_normalize(u'NFKD', key) 30 | if is_not_mn(c)) 31 | 32 | 33 | def normalize_index_key(key): 34 | key = key.strip().lower() 35 | key = key.replace(u'\u00A9', u'c') 36 | 37 | def is_wd(c): 38 | cat = _unicode_category(c) 39 | return (cat == 'Ll' or cat == 'Nd') 40 | 41 | return u''.join(c for c in _unicode_normalize(u'NFKD', key) 42 | if is_wd(c)) 43 | 44 | 45 | def ellipsis(s, length): 46 | if len(s) >= length: 47 | return s[:length-1] + u'\u2026' 48 | return s 49 | -------------------------------------------------------------------------------- /scripts/ldoce5viewer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | from ldoce5viewer import qtgui 6 | 7 | 8 | if __name__ == '__main__': 9 | sys.exit(qtgui.run(sys.argv)) 10 | 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess 4 | from distutils.core import setup 5 | from ldoce5viewer import __version__ 6 | 7 | 8 | def iter_static(): 9 | import os, os.path 10 | 11 | for root, dirs, files in os.walk("ldoce5viewer/static"): 12 | for filename in files: 13 | yield os.path.relpath(os.path.join(root, filename), "ldoce5viewer") 14 | 15 | for root, dirs, files in os.walk("ldoce5viewer/qtgui/resources"): 16 | for filename in files: 17 | yield os.path.relpath(os.path.join(root, filename), "ldoce5viewer") 18 | 19 | for root, dirs, files in os.walk("ldoce5viewer/qtgui/ui"): 20 | for filename in files: 21 | yield os.path.relpath(os.path.join(root, filename), "ldoce5viewer") 22 | 23 | 24 | extra_options = {} 25 | 26 | 27 | #-------- 28 | # py2exe 29 | #-------- 30 | try: 31 | import py2exe 32 | except ImportError: 33 | pass 34 | else: 35 | extra_options.update(dict( 36 | name='LDOCE5 Viewer', 37 | windows = [{ 38 | 'script': 'ldoce5viewer.py', 39 | 'icon_resources': [(1, 'ldoce5viewer/resources/ldoce5viewer.ico')], 40 | }], 41 | options = {'py2exe': { 42 | 'includes': ['sip'], 43 | 'packages': ['lxml.etree', 'gzip', 'lxml._elementpath'], 44 | #'excludes': ['_ssl', 'ssl', 'bz2', 'sqlite3', 'select', 45 | # 'xml', 'unittest', 'email', 'distutils', 'xmlrpclib', 46 | # 'doctest', 'pdb', 'tarfile'], 47 | 'compressed': True, 48 | 'optimize': 2, 49 | 'bundle_files': 3, 50 | 'dist_dir': 'exedist', 51 | }}, 52 | zipfile=None 53 | )) 54 | 55 | 56 | #-------- 57 | # py2app 58 | #-------- 59 | try: 60 | import py2app 61 | except ImportError: 62 | pass 63 | else: 64 | qt_plugins_path = subprocess.check_output('qmake -query QT_INSTALL_PLUGINS', shell=True) 65 | qt_plugins_path = qt_plugins_path[0:len(qt_plugins_path)-1] # remove "\n" 66 | extra_options.update(dict( 67 | name='LDOCE5 Viewer', 68 | app=['ldoce5viewer.py'], 69 | options={'py2app': { 70 | 'iconfile': './ldoce5viewer/qtgui/resources/ldoce5viewer.icns', 71 | 'argv_emulation': False, 72 | 'optimize': 0, 73 | 'includes': ['sip', 'lxml._elementpath'], 74 | 'packages': [], 75 | 'excludes': [ 76 | 'email', 'sqlite3', 77 | 'PyQt4.QtCLucene', 78 | 'PyQt4.QtHtml', 79 | 'PyQt4.QtHelp', 80 | 'PyQt4.QtTest', 81 | 'PyQt4.QtOpenGL', 82 | 'PyQt4.QtScript', 83 | 'PyQt4.QtScriptTools', 84 | 'PyQt4.QtSql', 85 | 'PyQt4.QtDeclarative', 86 | 'PyQt4.QtMultimedia', 87 | 'PyQt4.QtDesigner', 88 | 'PyQt4.QtXml', 89 | 'PyQt4.QtXmlPatterns', 90 | ], 91 | #'qt_plugins': [ 92 | # 'imageformats/libqjpeg.dylib', 93 | #] 94 | }}, 95 | data_files=[ 96 | ('qt_plugins/imageformats', [qt_plugins_path]), 97 | ('', ['ldoce5viewer/static']), 98 | ], 99 | )) 100 | 101 | 102 | #------------ 103 | # setup(...) 104 | #------------ 105 | 106 | if 'name' not in extra_options: 107 | extra_options['name'] = 'ldoce5viewer' 108 | 109 | setup( 110 | version = __version__, 111 | description = 'LDOCE5 Viewer', 112 | url = 'http://hakidame.net/ldoce5viewer/', 113 | license = 'GPLv3+', 114 | platforms='any', 115 | classifiers=[ 116 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 117 | 'Development Status :: 5 - Production/Stable' 118 | 'Intended Audience :: End Users/Desktop', 119 | 'Intended Audience :: Education', 120 | 'Programming Language :: Python', 121 | 'Operating System :: OS Independent', 122 | 'Topic :: Education', 123 | ], 124 | author = 'Taku Fukada', 125 | author_email = 'naninunenor@gmail.com', 126 | package_dir = {'ldoce5viewer': 'ldoce5viewer'}, 127 | packages = [ 128 | 'ldoce5viewer', 129 | 'ldoce5viewer.qtgui', 130 | 'ldoce5viewer.qtgui.ui', 131 | 'ldoce5viewer.qtgui.resources', 132 | 'ldoce5viewer.qtgui.utils', 133 | 'ldoce5viewer.qtgui.utils.mp3play', 134 | 'ldoce5viewer.utils', 135 | 'ldoce5viewer.ldoce5', 136 | ], 137 | package_data = {'ldoce5viewer': list(iter_static())}, 138 | scripts = ['scripts/ldoce5viewer'], 139 | **extra_options 140 | ) 141 | 142 | --------------------------------------------------------------------------------