· 6 years ago · Nov 22, 2019, 06:02 PM
1#!/usr/bin/python
2# encoding: utf-8
3
4# ePad - a simple text editor written in Elementary and Python
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19from __future__ import print_function # May as well bite the bullet
20
21__author__ = "Jeff Hoogland"
22__contributors__ = ["Jeff Hoogland", "Robert Wiley", "Kai Huuhko", "Scimmia22"]
23__copyright__ = "Copyright (C) 2015 Bodhi Linux"
24__appname__ = 'epad'
25__version__ = "0.9.6"
26__description__ = 'A simple text editor for the Enlightenment Desktop.'
27__github__ = 'http://jeffhoogland.github.io/ePad/'
28__source__ = 'Source code and bug reports: {0}'.format(__github__)
29PY_EFL = "https://git.enlightenment.org/bindings/python/python-efl.git/"
30
31AUTHORS = """
32<br>
33<align=center>
34<hilight>Jeff Hoogland (Jef91)</hilight><br>
35<link><a href=http://www.jeffhoogland.com>Contact</a></link><br><br>
36
37<hilight>Robert Wiley (ylee)</hilight><br><br>
38
39<hilight>Kai Huuhko (kuuko)</hilight><br><br>
40</align>
41"""
42
43LICENSE = """<br>
44<align=center>
45<hilight>
46GNU GENERAL PUBLIC LICENSE<br>
47Version 3, 29 June 2007<br><br>
48</hilight>
49
50This program is free software: you can redistribute it and/or modify
51it under the terms of the GNU General Public License as published by
52the Free Software Foundation, either version 3 of the License, or
53(at your option) any later version.<br><br>
54
55This program is distributed in the hope that it will be useful,
56but WITHOUT ANY WARRANTY; without even the implied warranty of
57MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
58GNU General Public License for more details.<br><br>
59
60You should have received a copy of the GNU General Public License
61along with this program. If not, see<br>
62<link><a href=http://www.gnu.org/licenses>http://www.gnu.org/licenses/</a></link>
63</align>
64<br>
65"""
66
67INFO = """
68<align=center>
69<hilight>ePad</hilight> is a simple text editor written in Elementary and Python.<br>
70<br>
71<br>
72</align>
73"""
74
75
76import errno
77import sys
78import os
79import urllib
80import io
81import json
82from collections import Mapping
83import tempfile
84from subprocess import Popen
85import shlex
86import threading
87
88try:
89 # Python3
90 import urllib.request
91except ImportError:
92 pass
93import re
94
95from efl import ecore
96from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL, \
97 EVAS_CALLBACK_KEY_UP, EVAS_EVENT_FLAG_ON_HOLD
98from efl import elementary
99from efl.elementary.window import StandardWindow
100from efl.elementary.box import Box
101from efl.elementary.button import Button
102from efl.elementary.label import Label, ELM_WRAP_WORD
103from efl.elementary.icon import Icon
104from efl.elementary.notify import Notify, ELM_NOTIFY_ALIGN_FILL
105from efl.elementary.separator import Separator
106from efl.elementary.scroller import Scroller
107from efl.elementary.frame import Frame
108from efl.elementary.entry import Entry, ELM_TEXT_FORMAT_PLAIN_UTF8, \
109 markup_to_utf8, utf8_to_markup, ELM_WRAP_NONE, ELM_WRAP_MIXED
110from efl.elementary.popup import Popup
111from efl.elementary.toolbar import Toolbar, ELM_OBJECT_SELECT_MODE_DEFAULT
112from efl.elementary.flip import Flip, ELM_FLIP_ROTATE_XZ_CENTER_AXIS, \
113 ELM_FLIP_ROTATE_YZ_CENTER_AXIS, ELM_FLIP_INTERACTION_ROTATE
114from efl.elementary.table import Table
115from efl.elementary.check import Check
116
117# Imported here to stop class resolver complaining when an input event
118# applies to an internal layout object
119from efl.elementary.layout import Layout
120from efl.elementary.theme import Theme
121from elmextensions import AboutWindow, InstanceError
122from elmextensions import FileSelector
123from elmextensions import FontSelector
124from elmextensions import TabbedBox
125
126EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND
127EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0
128FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL
129FILL_HORIZ = EVAS_HINT_FILL, 0.5
130EXPAND_NONE = 0.0, 0.0
131ALIGN_CENTER = 0.5, 0.5
132ALIGN_RIGHT = 1.0, 0.5
133ALIGN_LEFT = 0.0, 0.5
134PADDING = 15, 0
135PROC = None
136TMP = None
137
138def print_err(*args, **kwargs):
139 """
140 error message to stderr
141 """
142 print('[ePad]:', *args, file=sys.stderr, **kwargs)
143
144def errorPopup(window, errorMsg):
145 errorPopup = Popup(window, size_hint_weight=EXPAND_BOTH)
146 errorPopup.callback_block_clicked_add(lambda obj: errorPopup.delete())
147
148 # Add a table to hold dialog image and text to Popup
149 tb = Table(errorPopup, size_hint_weight=EXPAND_BOTH)
150 errorPopup.part_content_set("default", tb)
151 tb.show()
152
153 # Add dialog-error Image to table
154 icon = Icon(errorPopup, resizable=(True, True),
155 size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH)
156 icon.standard_set('dialog-warning')
157 tb.pack(icon, 0, 0, 1, 1)
158 icon.show()
159
160 # Add dialog text to table
161 dialogLabel = Label(errorPopup, line_wrap=ELM_WRAP_WORD,
162 size_hint_weight=EXPAND_HORIZ,
163 size_hint_align=FILL_BOTH)
164 dialogLabel.text = errorMsg
165 tb.pack(dialogLabel, 1, 0, 1, 1)
166 dialogLabel.show()
167
168 # Ok Button
169 ok_btt = Button(errorPopup)
170 ok_btt.text = "Ok"
171 ok_btt.callback_clicked_add(lambda obj: errorPopup.delete())
172 ok_btt.show()
173
174 # add button to popup
175 errorPopup.part_content_set("button3", ok_btt)
176 errorPopup.show()
177
178
179def closeMenu(obj, label):
180 if not hasattr(closeMenu, 'count'):
181 closeMenu.count = 0
182 if not hasattr(closeMenu, 'name'):
183 closeMenu.lastItem = label
184 if closeMenu.lastItem != label:
185 closeMenu.count = 0
186 if closeMenu.count:
187 obj.selected_set(False)
188 obj.menu_get().close()
189 closeMenu.count = (closeMenu.count + 1) % 2
190
191
192def resetCloseMenuCount(obj):
193 global closeMenu
194 if hasattr(closeMenu, 'count'):
195 closeMenu.count = 0
196
197# Code taken from:
198# https://stackoverflow.com/questions/2581817/python-subprocess-callback-when-cmd-exits
199def popenAndCall(onExit, popenArgs):
200 """
201 Runs the given args in a subprocess.Popen, and then calls the function
202 onExit when the subprocess completes.
203 onExit is a callable object, and popenArgs is a list/tuple of args that
204 would give to subprocess.Popen.
205 """
206 def runInThread(onExit, popenArgs):
207 global PROC
208 proc = Popen(popenArgs)
209 # print(proc)
210 PROC = proc
211 proc.wait()
212 onExit()
213 return
214 popenAndCall.proc = None
215 thread = threading.Thread(target=runInThread, args=(onExit, popenArgs))
216 thread.start()
217 # returns immediately after the thread starts
218 return thread
219
220class Interface(object):
221 def __init__(self):
222 self.confirmPopup = None
223 self.config = ePadConf()
224
225 self.mainWindow = StandardWindow("epad", "Untitled - ePad",
226 size=(600, 400))
227 self.mainWindow.callback_delete_request_add(self.closeChecks)
228 self.mainWindow.elm_event_callback_add(self.eventsCb)
229 #self.mainWindow.repeat_events_set(False)
230
231 icon = Icon(self.mainWindow,
232 size_hint_weight=EXPAND_BOTH,
233 size_hint_align=FILL_BOTH)
234 icon.standard_set('accessories-text-editor')
235 self.mainWindow.icon_object_set(icon)
236
237 self.mainBox = Box(self.mainWindow,
238 size_hint_weight=EXPAND_BOTH,
239 size_hint_align=FILL_BOTH)
240 self.mainBox.show()
241
242 self.mainTb = ePadToolbar(self, self.mainWindow)
243 self.mainTb.focus_allow = False
244 self.mainTb.show()
245
246 self.mainBox.pack_end(self.mainTb)
247
248 # Root User Notification
249 if os.geteuid() == 0:
250 # print_err("Caution: Root User")
251 if self.config["notify_root"]:
252 notifyBox = Box(self.mainWindow, horizontal=True)
253 notifyLabel = Label(self.mainWindow, size_hint_align=FILL_HORIZ)
254 notifyLabel.text = "<b><i>Root User</i></b>"
255 notifyBox.pack_end(notifyLabel)
256 notifyLabel.show()
257 self.mainBox.pack_end(notifyBox)
258 notifyBox.show()
259
260 self.findBox = ePadFindBox(self, self.mainWindow)
261 self.findVisible = False
262
263 self.tabbs = TabbedBox(self.mainWindow, size_hint_weight=EXPAND_BOTH,
264 size_hint_align=FILL_BOTH)
265 self.tabbs.closeCallback = self.closeFile
266 self.tabbs.emptyCallback = self.baseFile
267 self.tabbs.tabChangedCallback = self.tabChanged
268 self.tabbs.show()
269
270 self.mainBox.pack_end(self.tabbs)
271
272 # Build our file selector for saving/loading files
273 self.fileBox = Box(self.mainWindow,
274 size_hint_weight=EXPAND_BOTH,
275 size_hint_align=FILL_BOTH)
276 self.fileBox.show()
277
278 self.fileLabel = Label(self.mainWindow,
279 size_hint_weight=EXPAND_HORIZ,
280 size_hint_align=FILL_BOTH, text="")
281 self.fileLabel.show()
282 self.lastDir = os.getenv("HOME")
283 self.fileSelector = FileSelector(self.mainWindow,
284 defaultPath=self.lastDir,
285 defaultPopulate=False,
286 size_hint_weight=EXPAND_BOTH,
287 size_hint_align=FILL_BOTH)
288 self.fileSelector.callback_activated_add(self.fileSelected)
289 self.fileSelector.callback_directory_open_add(self.updateLastDir)
290 self.fileSelector.callback_cancel_add(self.fileSelCancelPressed)
291 self.fileSelector.setMode("Open")
292 self.fileSelector.show()
293
294 self.fileBox.pack_end(self.fileLabel)
295 self.fileBox.pack_end(self.fileSelector)
296
297 # Flip object has the file selector on one side
298 # and the GUI on the other
299 self.flip = Flip(self.mainWindow, size_hint_weight=EXPAND_BOTH,
300 size_hint_align=FILL_BOTH)
301 self.flip.part_content_set("front", self.mainBox)
302 self.flip.part_content_set("back", self.fileBox)
303 self.mainWindow.resize_object_add(self.flip)
304 self.flip.show()
305
306 self.fontBox = FontSelector(self.mainWindow,
307 size_hint_weight=EXPAND_BOTH,
308 size_hint_align=FILL_BOTH)
309 # FIXME: these two and more should be handled by fs.set_font function
310 if self.config['use_theme']:
311 self.fontBox.use_theme = True
312 else:
313 self.fontBox.set_font(self.config['font'], self.config['font_style'], self.config['font_size'])
314 # FIXME: WTF should all be one function in fontselector obj
315 self.fontBox.default_font = self.config['font']
316 self.fontBox.default_font_style = self.config['font_style']
317 self.fontBox.default_font_size = self.config['font_size']
318 self.fontBox.override_theme_font_size = not self.config['use_theme_font_size']
319 self.fontBox.override_font_size = ePadEntry.default_font_size
320
321 self.fontBox.callback_cancel_add(self.fontSelCancelPressed)
322 self.fontBox.callback_activated_add(self.fontSelected)
323 # We will show when needed
324
325 def tabChanged(self, tabbs, widget):
326 self.mainWindow.title = widget.data["button"].text
327 self.mainTb.savebtn.disabled = not widget.dirty
328
329 def addFile(self, filePath):
330 entryBox = ePadEntry(self, self.tabbs)
331 entryBox.show()
332
333
334 if filePath != "Untitled":
335 entryBox.openFile(filePath)
336 tabName = filePath.split("/")[-1]
337 else:
338 tabName = "Untitled"
339 entryBox.curChanged(entryBox.mainEn, entryBox.line_label)
340 entryBox.checkLineNumbers()
341 self.tabbs.addTab(entryBox, tabName)
342
343 def baseFile(self, tabbs):
344 #This function gets called when all files are closed
345 self.addFile("Untitled")
346
347 def closeFile(self, tabbs, widget):
348 widget.closeChecks()
349
350 def showFile(self, btn):
351 if self.tabbs.currentTab != btn.data["entry"]:
352 self.setFile(btn.data["entry"], btn.text)
353
354 def newFile(self, obj=None, ignoreSave=False):
355 if self.config["new_instance"]:
356 print("Launching new instance")
357 ecore.Exe('epad', ecore.ECORE_EXE_PIPE_READ|ecore.ECORE_EXE_PIPE_ERROR|ecore.ECORE_EXE_PIPE_WRITE)
358 return
359 self.addFile("Untitled")
360
361 def openFile(self, obj=None, ignoreSave=False):
362 self.fileSelector.setMode("Open")
363 self.fileLabel.text = "<b>Select a text file to open:</b>"
364 if self.fileSelector.filepathEntry.text != self.lastDir:
365 self.fileSelector.populateFiles(self.lastDir)
366
367 self.fileBox.show()
368 self.flip.go(ELM_FLIP_ROTATE_YZ_CENTER_AXIS)
369
370
371 def fileSelCancelPressed(self, fs):
372 self.flip.go(ELM_FLIP_ROTATE_XZ_CENTER_AXIS)
373
374 def fontSelCancelPressed(self, fs):
375 self.fontBox.hide()
376 self.flip.part_content_unset("back")
377 self.flip.part_content_set("back", self.fileBox)
378 self.fileBox.show()
379
380 self.flip.go(ELM_FLIP_ROTATE_XZ_CENTER_AXIS)
381
382 def showFind(self, obj=None):
383 if not self.findVisible:
384 self.mainBox.pack_before(self.findBox, self.tabbs)
385 self.findBox.findEntry.text = self.tabbs.currentTab.mainEn.selection_get()
386 self.findBox.findEntry.focus_set(True)
387 self.findBox.findEntry.cursor_end_set()
388 self.findBox.show()
389 self.findVisible = True
390 else:
391 self.hideFind()
392
393 def hideFind(self, obj=None):
394 if self.findVisible:
395 self.mainBox.unpack(self.findBox)
396 self.findBox.hide()
397 self.findVisible = False
398
399 def saveAs(self):
400
401 self.fileSelector.setMode("Save")
402 self.fileLabel.text = "<b>Save new file to where:</b>"
403 if self.fileSelector.filepathEntry.text != self.lastDir:
404 self.fileSelector.populateFiles(self.lastDir)
405 self.flip.go(ELM_FLIP_ROTATE_XZ_CENTER_AXIS)
406
407 def saveFile(self, obj=False):
408 if self.tabbs.currentTab.mainEn.file_get()[0] is None or self.tabbs.currentTab.isNewFile:
409 self.saveAs()
410 else:
411 if not self.tabbs.currentTab.isSaved:
412 file_selected = self.tabbs.currentTab.mainEn.file_get()[0]
413 # Detect save errors as entry.file_save currently returns no errors
414 # even in the case where the file fails to save :(
415 try:
416 newfile = io.open(file_selected, 'w')
417 except IOError as err:
418 if err.errno == errno.EACCES:
419 errorMsg = ("Permision denied: <b>'%s'</b>."
420 "<br><br>Operation failed !!!"
421 % (file_selected))
422 errorPopup(self.mainWindow, errorMsg)
423 else:
424 errorMsg = ("ERROR: %s: '%s'"
425 "<br><br>Operation failed !!!"
426 % (err.strerror, file_selected))
427 errorPopup(self.mainWindow, errorMsg)
428 return
429 newfile.close()
430 # if entry is empty and the file does not exists then
431 # entry.file_save will destroy the file created about by the
432 # open statement above for some odd reason ...
433 if not self.tabbs.currentTab.mainEn.is_empty:
434 self.tabbs.currentTab.mainEn.file_save()
435 self.tabbs.currentTab.setDirty(False)
436 self.tabbs.currentTab.isSaved = True
437
438 def fontSelected(self, fs):
439
440 self.fontBox.hide()
441 self.flip.part_content_unset("back")
442 self.flip.part_content_set("back", self.fileBox)
443 self.fileBox.show()
444 self.flip.go(ELM_FLIP_ROTATE_XZ_CENTER_AXIS)
445 if fs.use_theme:
446 self.config['use_theme'] = True
447 for tab in self.tabbs.tabs:
448 while tab.mainEn.text_style_user_peek():
449 tab.mainEn.text_style_user_pop();
450 while tab.lineList.text_style_user_peek():
451 tab.lineList.text_style_user_pop();
452 if not self.config['use_theme_font_size']:
453 tab.mainEn.text_style_user_push("DEFAULT='font_size={0}'".format(ePadEntry.default_font_size))
454 tab.lineList.text_style_user_push("DEFAULT='font_size={0}'".format(ePadEntry.default_font_size))
455
456 else:
457 for tab in self.tabbs.tabs:
458 tab.mainEn.text_style_user_push(fs.font_style_str)
459 tab.lineList.text_style_user_push(self.fontBox.get_text_style(self.fontBox.selected_font, None, self.fontBox.selected_font_size))
460 self.config['use_theme'] = False
461 self.config['font'] = fs.selected_font
462 self.config['font_style'] = fs.selected_font_style
463 self.config['font_size'] = fs.selected_font_size
464
465 def fileSelected(self, fs, file_selected, onStartup=False):
466 if not onStartup:
467 self.flip.go(ELM_FLIP_ROTATE_XZ_CENTER_AXIS)
468 # Markup can end up in file names because file_selector name_entry
469 # is an elementary entry. So lets sanitize file_selected.
470 file_selected = markup_to_utf8(file_selected)
471 if file_selected:
472 #print("File Selected: {0}".format(file_selected))
473 self.lastDir = os.path.dirname(file_selected)
474 # This fails if file_selected does not exist yet
475
476 fs.fileEntry.text = file_selected.split("/")[-1]
477
478 IsSave = fs.mode
479
480 if file_selected:
481 if IsSave == "save":
482 if os.path.isdir(file_selected):
483 current_file = os.path.basename(file_selected)
484 errorMsg = ("<b>'%s'</b> is a folder."
485 "<br><br>Operation failed !!!"
486 % (current_file))
487 errorPopup(self.mainWindow, errorMsg)
488 return
489 elif os.path.exists(file_selected):
490 self.tabbs.currentTab.fileExists(file_selected)
491 return
492 else:
493 self.tabbs.currentTab.doSelected(file_selected)
494 return
495 else:
496 self.addFile(file_selected)
497
498 def updateLastDir(self, path):
499 self.lastDir = path
500
501 def showAbout(self):
502 self.about.launch()
503
504 def closeApp(self, obj=False, trash=False):
505 global PROC
506 if PROC: PROC.terminate()
507 PROC = None
508 elementary.exit()
509
510 def closeChecks(self, obj=False):
511 allSaved = True
512
513 for en in self.tabbs.tabs:
514 if not en.isSaved:
515 allSaved = False
516
517 if allSaved:
518 self.closeApp()
519 else:
520 self.unsavedWorkPopup()
521
522 def closePopup(self, bt, confirmPopup):
523 self.confirmPopup.delete()
524 self.confirmPopup = None
525
526 def unsavedWorkPopup(self):
527 if self.confirmPopup:
528 return
529 self.confirmPopup = Popup(self.mainWindow,
530 size_hint_weight=EXPAND_BOTH)
531
532 # Add a table to hold dialog image and text to Popup
533 tb = Table(self.confirmPopup, size_hint_weight=EXPAND_BOTH)
534 self.confirmPopup.part_content_set("default", tb)
535 tb.show()
536
537 # Add dialog-error Image to table
538
539 icon = Icon(self.confirmPopup, resizable=(True, True),
540 size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH)
541 icon.standard_set('dialog-question')
542 tb.pack(icon, 0, 0, 1, 1)
543 icon.show()
544
545 # Add dialog text to table
546 dialogLabel = Label(self.confirmPopup, line_wrap=ELM_WRAP_WORD,
547 size_hint_weight=EXPAND_HORIZ,
548 size_hint_align=FILL_BOTH)
549 dialogLabel.text = "You have unsaved work. Close anyways?<br><br>"
550 tb.pack(dialogLabel, 1, 0, 1, 1)
551 dialogLabel.show()
552
553 # Close without saving button
554 no_btt = Button(self.mainWindow)
555 no_btt.text = "No"
556 no_btt.callback_clicked_add(self.closePopup, self.confirmPopup)
557 no_btt.show()
558 # Save the file and then close button
559 sav_btt = Button(self.mainWindow)
560 sav_btt.text = "Yes"
561 sav_btt.callback_clicked_add(self.closeApp)
562 sav_btt.show()
563
564 # add buttons to popup
565 self.confirmPopup.part_content_set("button1", no_btt)
566 self.confirmPopup.part_content_set("button3", sav_btt)
567 self.confirmPopup.show()
568
569 def eventsCb(self, obj, src, event_type, event):
570 # Ignore if FontSelector is open
571 if not event_type == EVAS_CALLBACK_KEY_UP or \
572 type(self.flip.part_content_get("back")) is FontSelector:
573 return False
574
575 if event.modifier_is_set("Control"):
576 if event.keyname == "n":
577 #newFile(self.newFile)
578 self.newFile()
579 elif event.keyname == "s" and event.modifier_is_set("Shift"):
580 self.saveAs()
581 elif event.keyname == "s":
582 self.saveFile()
583 elif event.keyname == "z" and event.modifier_is_set("Shift"):
584 self.tabbs.currentTab.reDo()
585 elif event.keyname == "z":
586 self.tabbs.currentTab.unDo()
587 elif event.keyname == "o":
588 self.openFile()
589 elif event.keyname == "h":
590 if not self.flip.front_visible_get():
591 self.fileSelector.toggleHidden()
592 elif event.keyname == "q":
593 self.closeChecks()
594 elif event.keyname == "f":
595 self.showFind()
596 elif isinstance(src, Entry) and event.key in ["space", "BackSpace", "Return"]:
597 self.tabbs.currentTab.takeSnapShot()
598
599 event.event_flags = event.event_flags | EVAS_EVENT_FLAG_ON_HOLD
600 return True
601
602 def launch(self, start=[]):
603 if start[0]:
604 for count, ourFile in enumerate(start[0]):
605 if os.path.dirname(ourFile) == '':
606 start[0][count] = os.getcwd() + '/' + ourFile
607
608 if start and start[0]:
609 for ourFile in start[0]:
610 if ourFile[:7] == "file://":
611 try:
612 ourFile = urllib.url2pathname(ourFile[7:])
613 except AttributeError:
614 # Python3
615 ourFile = urllib.request.url2pathname(ourFile[7:])
616 if os.path.isdir(os.path.dirname(ourFile)):
617 if os.path.isfile(ourFile):
618 ##print(ourFile)
619 self.addFile(ourFile)
620 else:
621 errorMsg = ("<b>'%s'</b> is an Invalid path."
622 "<br><br>Open failed !!!" % (ourFile))
623 errorPopup(self.mainWindow, errorMsg)
624 if start and start[1]:
625 if os.path.isdir(start[1]):
626 self.lastDir = start[1]
627 else:
628 pass
629
630 if not len(self.tabbs.tabs):
631 self.addFile("Untitled")
632
633 self.mainWindow.show()
634
635class ePadEntry(Box):
636
637 default_font_size = 14
638
639 def __init__(self, parent, canvas):
640 Box.__init__(self, canvas)
641 self._parent = parent
642 self._canvas = canvas
643 self._config = self._parent.config
644
645 self.size_hint_weight = EXPAND_BOTH
646 self.size_hint_align = FILL_BOTH
647 # py-efl doesn't work correctly with fonts that have spaces in names
648 # Oddly enough remove spaces and works
649 # versions up to 1.18 (inclusive)
650
651 self.font_style = self._parent.fontBox.get_text_style(self._config['font'], self._config['font_style'], str(self._config['font_size']))
652 self.entryBox = Box(self._canvas,
653 size_hint_weight=EXPAND_BOTH,
654 size_hint_align=FILL_BOTH)
655 self.entryBox.horizontal = True
656 self.entryBox.show()
657
658 # Initialize Text entry box and line label
659 self.lineList = Entry(self._canvas,
660 scrollable=False, editable=False,
661 size_hint_weight=(0.0, EVAS_HINT_EXPAND),
662 size_hint_align=(0.0, 0.0),
663 line_wrap=ELM_WRAP_NONE)
664
665 self.currentLinesShown = 1
666 self.lineList.text_set("1<br>")
667 self.lineNums = self._parent.config['line_numbers']
668
669 self.mainEn = Entry(self._canvas, scrollable=False,
670 line_wrap=self._parent.config['word_wrap'],
671 autosave=self._parent.config['autosave'],
672 size_hint_weight=(0.85, EVAS_HINT_EXPAND),
673 size_hint_align=FILL_BOTH)
674 self.mainEn.callback_changed_user_add(self.textEdited)
675 self.mainEn.callback_clicked_add(resetCloseMenuCount)
676 self.mainEn.callback_selection_cut_add(self.takeSnapShot)
677 self.mainEn.callback_selection_paste_add(self.takeSnapShot)
678
679 # Set user Font styles
680 if self._config['use_theme'] and not self._config['use_theme_font_size']:
681 self.lineList.text_style_user_push("DEFAULT='font_size={0}'".format(ePadEntry.default_font_size))
682 self.mainEn.text_style_user_push("DEFAULT='font_size={0}'".format(ePadEntry.default_font_size))
683 elif not self._config['use_theme']:
684 self.lineList.text_style_user_push(self._parent.fontBox.get_text_style(self._config['font'], None, str(self._config['font_size'])))
685 self.mainEn.text_style_user_push(self.font_style)
686
687 self.totalLines = 0
688 self.mainEn.show()
689 self.mainEn.focus_set(True)
690 self.sep = Separator(self._canvas)
691 self.sep.horizontal_set(False)
692 self.sep.show()
693
694 if self.lineNums:
695 self.lineList.show()
696 self.entryBox.pack_end(self.lineList)
697 self.entryBox.pack_end(self.sep)
698 self.entryBox.pack_end(self.mainEn)
699
700 self.scr = Scroller(self._canvas,
701 size_hint_weight=EXPAND_BOTH,
702 size_hint_align=FILL_BOTH)
703 self.scr.content = self.entryBox
704 self.scr.show()
705
706 self.pack_end(self.scr)
707
708 # Add label to show current cursor position
709 if self._parent.config['show_pos']:
710 self.line_label = Label(self._canvas,
711 size_hint_weight=EXPAND_HORIZ,
712 size_hint_align=ALIGN_RIGHT)
713
714 self.mainEn.callback_cursor_changed_add(self.curChanged,
715 self.line_label)
716 self.curChanged(self.mainEn, self.line_label)
717 self.line_label.show()
718 self.pack_end(self.line_label)
719
720 self.isNewFile = True
721 self.isSaved = True
722 self.doArchive = []
723 self.doSpot = 0
724 self.takeSnapShot()
725 self.setDirty(False)
726 #self.printDialog = None
727 #self.printTmp = None
728
729 def takeSnapShot(self, obj=None):
730 if self.doSpot != len(self.doArchive)-1:
731 for i in range(self.doSpot+1, len(self.doArchive)):
732 self.doArchive.pop(self.doSpot+1)
733
734 curPos = self.mainEn.cursor_pos_get()
735 entryGet = self.mainEn.entry_get()
736
737 if self.doSpot == 0:
738 self.saveSnapShot(curPos, entryGet)
739 elif entryGet != self.doArchive[self.doSpot][1]:
740 self.saveSnapShot(curPos, entryGet)
741
742 def saveSnapShot(self, curPos, entryGet):
743 self.doArchive.append([curPos, entryGet])
744
745 if len(self.doArchive) > 30:
746 self.doArchive.pop(0)
747
748 self.doSpot = len(self.doArchive) - 1
749
750
751 def unDo(self):
752 if self.doSpot > 0:
753 # A check if this is the first time we are undoing that we store the latest data
754 if self.doSpot == len(self.doArchive) - 1:
755 if self.doArchive[self.doSpot][1] != self.mainEn.entry_get():
756 self.takeSnapShot()
757 self.doSpot -= 1
758 self.mainEn.entry_set(self.doArchive[self.doSpot][1])
759 self.mainEn.cursor_pos_set(self.doArchive[self.doSpot][0])
760
761 def reDo(self):
762 if self.doSpot + 1 < len(self.doArchive):
763 self.doSpot += 1
764 self.mainEn.entry_set(self.doArchive[self.doSpot][1])
765 self.mainEn.cursor_pos_set(self.doArchive[self.doSpot][0])
766
767 def checkLineNumbers(self):
768 if self.currentLinesShown < self.totalLines:
769 lines = ""
770 for i in range(self.currentLinesShown+1, self.totalLines+1):
771 lines += "%s<br>"%(i)
772 self.lineList.entry_append(lines)
773 self.currentLinesShown = self.totalLines
774 elif self.currentLinesShown > self.totalLines:
775 lines = "<i>"
776
777 for i in range(1, self.totalLines+1):
778 lines = "%s%s<br>"%(lines, i)
779
780 self.lineList.entry_set(lines)
781 '''for i in range(self.totalLines+1, self.currentLinesShown+1):
782 ll = self.lineList
783 ll.cursor_end_set()
784 ll.cursor_prev()
785 ll.cursor_selection_begin()
786 ll.cursor_line_begin_set()
787 ll.cursor_prev()
788 ll.cursor_selection_end()
789 ll.selection_cut()'''
790
791 self.currentLinesShown = self.totalLines
792
793 def curChanged(self, entry, label):
794 # get linear index into current text
795 index = entry.cursor_pos_get()
796 # Replace <br /> tag with single char
797 # to simplify (line, col) calculation
798 tmp_text = markup_to_utf8(entry.entry_get())
799 self.totalLines = tmp_text.count("\n")+1
800 if self.lineNums:
801 self.checkLineNumbers()
802 text_slice = tmp_text[:index]
803 split_line = text_slice.split("\n")
804 line_n = len(split_line)
805 col = len(split_line[-1]) + 1
806 # Update label text with line, col
807 label.text = "Ln {0} Col {1} ".format(line_n, col)
808
809 def setDirty(self, dirty):
810 if dirty:
811 self.dirty = True
812 self._parent.mainWindow.title = "*" + self._parent.mainWindow.title
813 try:
814 self.data["button"].text = "*" + self.data["button"].text
815 except:
816 pass
817 self._parent.mainTb.savebtn.disabled = False
818 else:
819 self.dirty = False
820 self._parent.mainWindow.title = self._parent.mainWindow.title[1:]
821 try:
822 self.data["button"].text = self.data["button"].text[1:]
823 except:
824 pass
825 self._parent.mainTb.savebtn.disabled = True
826
827 def textEdited(self, obj=None):
828 global PROC
829 if not self.dirty:
830 self.setDirty(True)
831 self.isSaved = False
832 if PROC:
833 PROC.terminate()
834 PROC = None
835
836 def openFile(self, filePath):
837 try:
838 self.mainEn.file_set(filePath, ELM_TEXT_FORMAT_PLAIN_UTF8)
839 except (RuntimeWarning, RuntimeError) as msg:
840 print("ePad: Loading Empty File")
841 pass
842 self.mainEn.focus_set(True)
843 self.lineList.cursor_end_set()
844 self.checkLineNumbers()
845 self.isNewFile = False
846 #Reset undo/redo tracks when we open a file
847 self.doArchive = []
848 self.doSpot = 0
849 self.takeSnapShot()
850
851 def closeChecks(self, ourCallback=None):
852 if not self.isSaved:
853 self.confirmSave()
854 else:
855 self._parent.tabbs.deleteTab(self)
856
857 def confirmSave(self):
858 self.confirmPopup = Popup(self._parent.mainWindow,
859 size_hint_weight=EXPAND_BOTH)
860 self.confirmPopup.part_text_set("title,text", "File Unsaved")
861 current_file = self.mainEn.file[0]
862 current_file = \
863 os.path.basename(current_file) if current_file else "Untitled"
864 self.confirmPopup.text = "Save changes to '%s'?" % (current_file)
865 # Close without saving button
866 no_btt = Button(self._parent.mainWindow)
867 no_btt.text = "No"
868 no_btt.callback_clicked_add(self.closePopup, self.confirmPopup)
869 no_btt.callback_clicked_add(lambda o: self._parent.tabbs.deleteTab(self))
870 no_btt.show()
871 # cancel close request
872 cancel_btt = Button(self._parent.mainWindow)
873 cancel_btt.text = "Cancel"
874 cancel_btt.callback_clicked_add(self.closePopup, self.confirmPopup)
875 cancel_btt.show()
876 # Save the file and then close button
877 sav_btt = Button(self._parent.mainWindow)
878 sav_btt.text = "Yes"
879 sav_btt.callback_clicked_add(self._parent.saveFile)
880 sav_btt.callback_clicked_add(self.closePopup, self.confirmPopup)
881 sav_btt.show()
882
883 # add buttons to popup
884 self.confirmPopup.part_content_set("button1", no_btt)
885 self.confirmPopup.part_content_set("button2", cancel_btt)
886 self.confirmPopup.part_content_set("button3", sav_btt)
887 self.confirmPopup.show()
888
889 def closePopup(self, bt, confirmPopup):
890 self.confirmPopup.delete()
891 self.confirmPopup = None
892
893 def fileExists(self, filePath):
894 self.confirmPopup = Popup(self._parent.mainWindow,
895 size_hint_weight=EXPAND_BOTH)
896
897 # Add a table to hold dialog image and text to Popup
898 tb = Table(self.confirmPopup, size_hint_weight=EXPAND_BOTH)
899 self.confirmPopup.part_content_set("default", tb)
900 tb.show()
901
902 # Add dialog-error Image to table
903 icon = Icon(self.confirmPopup, resizable=(True, True),
904 size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH)
905 icon.standard_set('dialog-question')
906 tb.pack(icon, 0, 0, 1, 1)
907 icon.show()
908
909 # Add dialog text to table
910 dialogLabel = Label(self.confirmPopup, line_wrap=ELM_WRAP_WORD,
911 size_hint_weight=EXPAND_HORIZ,
912 size_hint_align=FILL_BOTH)
913 current_file = os.path.basename(filePath)
914 dialogLabel.text = "'%s' already exists. Overwrite?<br><br>" \
915 % (current_file)
916 tb.pack(dialogLabel, 1, 0, 1, 1)
917 dialogLabel.show()
918
919 # Close without saving button
920 no_btt = Button(self._parent.mainWindow)
921 no_btt.text = "No"
922 no_btt.callback_clicked_add(self.closePopup, self.confirmPopup)
923 no_btt.show()
924 # Save the file and then close button
925 sav_btt = Button(self._parent.mainWindow)
926 sav_btt.text = "Yes"
927 sav_btt.callback_clicked_add(self.doSelected)
928 sav_btt.callback_clicked_add(self.closePopup, self.confirmPopup)
929 sav_btt.show()
930
931 # add buttons to popup
932 self.confirmPopup.part_content_set("button1", no_btt)
933 self.confirmPopup.part_content_set("button3", sav_btt)
934 self.confirmPopup.show()
935
936 def doSelected(self, obj):
937 # Something I should avoid but here I prefer a polymorphic function
938 if isinstance(obj, Button):
939 file_selected = self._parent.fileSelector.selected_get()
940 else:
941 file_selected = obj
942
943 if file_selected:
944 try:
945 newfile = io.open(file_selected, 'w')
946 except IOError as err:
947 pass
948 #print("ERROR: {0}: '{1}'".format(err.strerror,
949 # file_selected))
950 if err.errno == errno.EACCES:
951 errorMsg = ("Permision denied: <b>'%s'</b>."
952 "<br><br>Operation failed !!!</br>"
953 % (file_selected))
954 errorPopup(self._parent.mainWindow, errorMsg)
955 else:
956 errorMsg = ("ERROR: %s: '%s'"
957 "<br><br>Operation failed !!!</br>"
958 % (err.strerror, file_selected))
959 errorPopup(self._parent.mainWindow, errorMsg)
960 return
961 tmp_text = self.mainEn.entry_get()
962 # FIXME: Why save twice?
963 newfile.write(tmp_text)
964 newfile.close()
965 # Suppress error message when empty file is saved
966 try:
967 self.mainEn.file_set(file_selected,
968 ELM_TEXT_FORMAT_PLAIN_UTF8)
969 except RuntimeError:
970 pass
971 #print("Empty file saved:{0}".format(file_selected))
972 self.mainEn.entry_set(tmp_text)
973 # if empty file entry.file_save destroys file :(
974 if len(tmp_text):
975 self.mainEn.file_save()
976 self._parent.mainWindow.title_set(" %s - ePad" % os.path.basename(file_selected))
977 self.data["button"].text = " %s" % os.path.basename(file_selected)
978
979 self.isSaved = True
980 self.isNewFile = False
981 self.setDirty(False)
982
983class ePadFindBox(Box):
984 def __init__(self, parent, canvas):
985 Box.__init__(self, canvas)
986 self._parent = parent
987 self._canvas = canvas
988
989 self.size_hint_weight = EXPAND_HORIZ
990 self.size_hint_align = FILL_HORIZ
991
992 self.currentFind = None
993 self.lastSearch = None
994
995 frameBox = Box(self._canvas, size_hint_weight=EXPAND_HORIZ, size_hint_align=FILL_HORIZ)
996 frameBox.horizontal = True
997 frameBox.show()
998
999 findBox = Frame(self._canvas, size_hint_weight=EXPAND_HORIZ, size_hint_align=FILL_HORIZ)
1000 findBox.text = "Find Text:"
1001 findBox.show()
1002
1003 self.findEntry = Entry(self._canvas, size_hint_weight=EXPAND_HORIZ, size_hint_align=FILL_HORIZ)
1004 self.findEntry.single_line_set(True)
1005 self.findEntry.scrollable_set(True)
1006 self.findEntry.callback_activated_add(self.findPressed)
1007 self.findEntry.show()
1008
1009 findBox.content = self.findEntry
1010
1011 replaceBox = Frame(self._canvas, size_hint_weight=EXPAND_HORIZ, size_hint_align=FILL_HORIZ)
1012 replaceBox.text = "Replace Text:"
1013 replaceBox.show()
1014
1015 self.replaceEntry = Entry(self._canvas, size_hint_weight=EXPAND_HORIZ, size_hint_align=FILL_HORIZ)
1016 self.replaceEntry.single_line_set(True)
1017 self.replaceEntry.scrollable_set(True)
1018 self.replaceEntry.show()
1019
1020 replaceBox.content = self.replaceEntry
1021
1022 frameBox.pack_end(findBox)
1023 frameBox.pack_end(replaceBox)
1024
1025 buttonBox = Box(self._canvas, size_hint_weight=EXPAND_HORIZ, size_hint_align=FILL_HORIZ)
1026 buttonBox.horizontal = True
1027 buttonBox.show()
1028
1029 findButton = Button(self._canvas)
1030 findButton.text = "Find Next"
1031 findButton.callback_pressed_add(self.findPressed)
1032 findButton.show()
1033
1034 replaceButton = Button(self._canvas)
1035 replaceButton.text = "Replace All"
1036 replaceButton.callback_pressed_add(self.replacePressed)
1037 replaceButton.show()
1038
1039 closeButton = Button(self._canvas)
1040 closeButton.text = "Done"
1041 closeButton.callback_pressed_add(self._parent.showFind)
1042 closeButton.show()
1043
1044 self.caseCheck = Check(self._canvas, text = "Case Sensitive")
1045 self.caseCheck.state_set(self._parent.config['case_sensitive'])
1046 self.caseCheck.callback_changed_add(self.caseUpdate)
1047 self.caseCheck.show()
1048
1049 buttonBox.pack_end(self.caseCheck)
1050 buttonBox.pack_end(findButton)
1051 buttonBox.pack_end(replaceButton)
1052 buttonBox.pack_end(closeButton)
1053
1054 self.pack_end(frameBox)
1055 self.pack_end(buttonBox)
1056
1057 def replacePressed(self, obj):
1058 tmp_text = markup_to_utf8(self._parent.tabbs.currentTab.mainEn.entry_get())
1059 if not self.caseCheck.state_get():
1060 search_string = self.findEntry.text.lower()
1061 locations = list(self.findAll(tmp_text.lower(), search_string))
1062 else:
1063 search_string = self.findEntry.text
1064 locations = list(self.findAll(tmp_text, search_string))
1065 search_length = len(search_string)
1066 if search_length:
1067 replace_string = self.replaceEntry.text
1068 #if replace_string:
1069 if len(locations):
1070 if not self._parent.config['case_sensitive']:
1071 ourRe = re.compile(search_string, re.IGNORECASE)
1072 else:
1073 ourRe = re.compile(search_string)
1074 tmp_text = ourRe.sub(replace_string, tmp_text).encode('utf-8').strip()
1075 tmp_text = utf8_to_markup(tmp_text)
1076 curPos = self._parent.tabbs.currentTab.mainEn.cursor_pos_get()
1077 self._parent.tabbs.currentTab.mainEn.text_set(tmp_text)
1078 try:
1079 self._parent.tabbs.currentTab.mainEn.cursor_pos_set(curPos)
1080 except:
1081 pass
1082 #print("Error: Can't set cursor position")
1083 self._parent.tabbs.currentTab.textEdited()
1084 self._parent.tabbs.currentTab.takeSnapShot()
1085 else:
1086 errorPopup(self._parent.mainWindow, "Text %s not found. Nothing replaced."%search_string)
1087 #else:
1088 # errorPopup(self._parent.mainWindow, "No replacement string entered.")
1089 else:
1090 errorPopup(self._parent.mainWindow, "No find string entered.")
1091
1092 def findPressed(self, obj):
1093 if not self.caseCheck.state_get():
1094 search_string = self.findEntry.text.lower()
1095 tmp_text = markup_to_utf8(self._parent.tabbs.currentTab.mainEn.entry_get()).lower()
1096 else:
1097 search_string = self.findEntry.text
1098 tmp_text = markup_to_utf8(self._parent.tabbs.currentTab.mainEn.entry_get())
1099 search_length = len(search_string)
1100 if search_length:
1101 locations = list(self.findAll(tmp_text, search_string))
1102 if len(locations):
1103 if self.currentFind == None or search_string != self.lastSearch:
1104 self.lastSearch = search_string
1105 self.currentFind = locations[0]
1106 else:
1107 lastFind = locations.index(self.currentFind)
1108 if lastFind < len(locations)-1:
1109 self.currentFind = locations[lastFind+1]
1110 else:
1111 self.currentFind = locations[0]
1112 self._parent.tabbs.currentTab.mainEn.select_region_set(self.currentFind, self.currentFind+search_length)
1113 else:
1114 errorPopup(self._parent.mainWindow, "Text %s not found."%search_string)
1115 else:
1116 errorPopup(self._parent.mainWindow, "No find string entered.")
1117
1118 def findAll(self, a_str, sub):
1119 start = 0
1120 while True:
1121 start = a_str.find(sub, start)
1122 if start == -1: return
1123 yield start
1124 start += len(sub) + 1
1125
1126 def caseUpdate(self, ck):
1127 self._parent.config['case_sensitive'] = ck.state_get()
1128
1129
1130class ePadToolbar(Toolbar):
1131 def __init__(self, parent, canvas):
1132 Toolbar.__init__(self, canvas)
1133 self._parent = parent
1134 self._canvas = canvas
1135
1136 self.homogeneous = False
1137 self.size_hint_weight = (0.0, 0.0)
1138 self.size_hint_align = (EVAS_HINT_FILL, 0.0)
1139 self.select_mode = ELM_OBJECT_SELECT_MODE_DEFAULT
1140 self.icon_size_set(16)
1141 self.callback_selected_add(self.itemClicked)
1142
1143 self.menu_parent = canvas
1144
1145 self.item_append("document-new", "New",
1146 lambda self, obj: self._parent.newFile())
1147 self.item_append("document-open", "Open",
1148 lambda self, obj: self._parent.openFile())
1149 self.savebtn = self.item_append("document-save", "Save",
1150 lambda self, obj: self._parent.saveFile())
1151 self.savebtn.disabled = True
1152 self.item_append("document-save-as", "Save As",
1153 lambda self, obj: self._parent.saveAs())
1154 self.printbtn = self.item_append("printer", "Print", self.printFile)
1155 # -- Edit Dropdown Menu --
1156 tb_it = self.item_append("edit-copy", "Edit")
1157 tb_it.menu = True
1158 menu = tb_it.menu
1159 menu.item_add(None, "Undo", "edit-undo", self.unDoPress)
1160 menu.item_add(None, "Redo", "edit-redo", self.reDoPress)
1161 menu.item_separator_add()
1162 menu.item_add(None, "Copy", "edit-copy", self.copyPress)
1163 menu.item_add(None, "Paste", "edit-paste", self.pastePress)
1164 menu.item_add(None, "Cut", "edit-cut", self.cutPress)
1165 menu.item_separator_add()
1166 menu.item_add(None, "Select All", "edit-select-all",
1167 self.selectAllPress)
1168
1169 self.item_append("edit-find-replace", "Find",
1170 lambda self, obj: self._parent.showFind())
1171 # -----------------------
1172 #
1173 # -- Options Dropdown Menu --
1174 #
1175 # self.item_append("settings", "Options", self.optionsPress)
1176 tb_it = self.item_append("preferences-desktop", "Options")
1177 tb_it.menu = True
1178 menu = tb_it.menu
1179 self._parent.wordwrap = self._parent.config['word_wrap']
1180 it = menu.item_add(None, "Wordwrap", None, self.optionsToggle)
1181 chk = Check(canvas)
1182 it.content = chk
1183 it.content.state = (self._parent.config['word_wrap'] == ELM_WRAP_MIXED)
1184 self.menu_item_ww = it
1185
1186 it = menu.item_add(None, "Line Numbers", None, self.optionsToggle)
1187 chk = Check(canvas)
1188 it.content = chk
1189 it.content.state = self._parent.config['line_numbers']
1190 self.menu_item_ln = it
1191
1192 it = menu.item_separator_add()
1193 it = menu.item_add(None, "Font", "preferences-desktop-font", self.optionsFont)
1194
1195 '''it = menu.item_add(None, "New Instance", None, self.optionsNew)
1196 chk = Check(canvas, disabled=True)
1197 it.content = chk
1198 if self._parent.newInstance:
1199 it.content.state = True
1200 else:
1201 it.content.state = False'''
1202
1203 # ---------------------------
1204
1205 self.item_append("dialog-information", "About",
1206 self.showAbout)
1207
1208 def showAbout(self, obj, it):
1209 try:
1210 AboutWindow(self, title="ePad", standardicon="accessories-text-editor", \
1211 version=__version__, authors=AUTHORS, \
1212 licen=LICENSE, webaddress=__github__, \
1213 info=INFO)
1214 except InstanceError:
1215 pass
1216
1217
1218 #############################################################
1219 # Hack to toogle word wrap state with line numbers state ##
1220 #############################################################
1221 def optionsToggle(self, obj, it):
1222 wordwrap = self._parent.config['word_wrap']
1223 linenum = self._parent.config['line_numbers']
1224
1225 if it.text == "Wordwrap":
1226 if wordwrap == ELM_WRAP_MIXED:
1227 wordwrap = ELM_WRAP_NONE
1228 it.content.state = False
1229 linenum = self.menu_item_ln.content.state = True
1230 else:
1231 wordwrap = ELM_WRAP_MIXED
1232 it.content.state = True
1233 linenum = self.menu_item_ln.content.state = False
1234 else:
1235 if linenum:
1236 linenum = it.content.state = False
1237 wordwrap = ELM_WRAP_MIXED
1238 self.menu_item_ww.content.state = True
1239 else:
1240 linenum = it.content.state = True
1241 wordwrap = ELM_WRAP_NONE
1242 self.menu_item_ww.content.state = False
1243 # Change word wrap state of all open tabs
1244 for tab in self._parent.tabbs.tabs:
1245 tab.mainEn.line_wrap_set(wordwrap)
1246 # for some reason have to reset font style
1247 while tab.mainEn.text_style_user_peek():
1248 tab.mainEn.text_style_user_pop();
1249 #FIXME SHOULD CHECK THEME
1250 # Set user Font styles
1251 if self._parent.config['use_theme'] and not self._parent.config['use_theme_font_size']:
1252 tab.lineList.text_style_user_push("DEFAULT='font_size={0}'".format(ePadEntry.default_font_size))
1253 tab.mainEn.text_style_user_push("DEFAULT='font_size={0}'".format(ePadEntry.default_font_size))
1254 elif not self._parent.config['use_theme']:
1255 tab.lineList.text_style_user_push(self._parent.fontBox.get_text_style(self._parent.config['font'], None, str(self._parent.config['font_size'])))
1256 tab.mainEn.text_style_user_push(self._parent.tabbs.currentTab.font_style)
1257 #tab.mainEn.text_style_user_push(self._parent.tabbs.currentTab.font_style)
1258 # Change line number state of all open tabs
1259 if linenum:
1260 for tab in self._parent.tabbs.tabs:
1261 tab.entryBox.pack_before(tab.sep, tab.mainEn)
1262 tab.sep.show()
1263 tab.entryBox.pack_before(tab.lineList, tab.sep)
1264 tab.checkLineNumbers()
1265 tab.lineList.show()
1266 else:
1267 for tab in self._parent.tabbs.tabs:
1268 tab.entryBox.unpack(tab.lineList)
1269 tab.entryBox.unpack(tab.sep)
1270 tab.lineList.hide()
1271 tab.sep.hide()
1272 # Update Config file
1273 self._parent.config['word_wrap'] = wordwrap
1274 self._parent.config['line_numbers'] = linenum
1275 resetCloseMenuCount(None)
1276
1277 def unDoPress(self, obj, it):
1278 self._parent.tabbs.currentTab.unDo()
1279 resetCloseMenuCount(None)
1280
1281 def reDoPress(self, obj, it):
1282 self._parent.tabbs.currentTab.reDo()
1283 resetCloseMenuCount(None)
1284
1285 def copyPress(self, obj, it):
1286 self._parent.tabbs.currentTab.mainEn.selection_copy()
1287 resetCloseMenuCount(None)
1288
1289 def itemClicked(self, obj, item):
1290 if item.menu_get() is None and item.selected_get():
1291 item.selected_set(False)
1292 elif item.menu_get():
1293 closeMenu(item, item.text_get())
1294
1295 def pastePress(self, obj, it):
1296 self._parent.tabbs.currentTab.mainEn.selection_paste()
1297 resetCloseMenuCount(None)
1298
1299 def cutPress(self, obj, it):
1300 self._parent.tabbs.currentTab.mainEn.selection_cut()
1301 resetCloseMenuCount(None)
1302
1303 def selectAllPress(self, obj, it):
1304 self._parent.tabbs.currentTab.mainEn.select_all()
1305 resetCloseMenuCount(None)
1306
1307 def optionsNew(self, obj, it):
1308 self._parent.newInstance = not self._parent.newInstance
1309 if self._parent.newInstance:
1310 it.content.state = True
1311 else:
1312 it.content.state = False
1313 resetCloseMenuCount(None)
1314
1315 def optionsFont(self, obj, it):
1316 self._parent.fileBox.hide()
1317 self._parent.flip.part_content_unset("back")
1318 self._parent.flip.part_content_set("back", self._parent.fontBox)
1319 # FIXME: WTF should all be one function in fontselector obj
1320 self._parent.fontBox.default_font = self._parent.config['font']
1321 self._parent.fontBox.default_font_style = self._parent.config['font_style']
1322 self._parent.fontBox.default_font_size = self._parent.config['font_size']
1323 self._parent.fontBox.override_theme_font_size = not self._parent.config['use_theme_font_size']
1324 ######
1325 self._parent.fontBox.show()
1326 if True:
1327 self._parent.flip.go(ELM_FLIP_ROTATE_XZ_CENTER_AXIS)
1328 resetCloseMenuCount(None)
1329
1330 def printFile(self, obj, it):
1331 global TMP
1332 if PROC: return
1333 #self.printbtn.disabled = True
1334 file_selected = markup_to_utf8(self._parent.tabbs.currentTab.mainEn.entry_get())
1335 tmp = tempfile.NamedTemporaryFile( prefix='ePad-', delete=False)
1336 with open(tmp.name, "w") as f:
1337 f.write(file_selected)
1338
1339 #self._parent.tabbs.currentTab.printTmp=tmp.name
1340 TMP = tmp.name
1341 #FIXME: Should not had code path here
1342 #window-icon not working does not seem to work in tad pribt dialog :(
1343 cmd = '/usr/bin/yad --print --type=TEXT --fontname="{}" "{}" {} --filename="{}" --window-icon=gnome-devel'.format(
1344 self._parent.config['font'], self._parent.config['font_style'], self._parent.config['font_size'], tmp.name)
1345 #call([cmd], shell=True)
1346 #args = shlex.split(cmd)
1347 #process = Popen(shlex.split(cmd))
1348 process = popenAndCall(self.onPrintExit, shlex.split(cmd))
1349
1350 #print(process.pid)
1351 #os.remove(tmp.name)
1352
1353 def onPrintExit(self):
1354 global TMP
1355 global PROC
1356 PROC = None
1357 os.remove(TMP)
1358 TMP=None
1359 #print(self.printbtn)
1360 #print(self.printbtn.disabled)
1361 self.printbtn.disabled = False
1362
1363
1364class ePadConf(object):
1365 """
1366 config file object
1367 """
1368
1369 # Config version follows major:minor format
1370 # an increase in major version number indicates incompatible
1371 # config file format change
1372 # an increase in minor for compatible config file format change
1373 __config_version_major = 0
1374 __config_version_minor = 1
1375 __config_version = '{0}.{1}'.format(__config_version_major,
1376 __config_version_minor)
1377 default = {"version": __config_version,
1378 "word_wrap": ELM_WRAP_NONE,
1379 "font": "Sans",
1380 "font_size": ePadEntry.default_font_size,
1381 "font_style": "Regular",
1382 "line_numbers": True,
1383 "show_hidden": False,
1384 "show_pos": True,
1385 "max_undo": 100,
1386 "case_sensitive": False,
1387 "notify_root": True,
1388 "new_instance": False,
1389 "show_hidden": False,
1390 "autosave": False,
1391 "use_theme": False,
1392 "use_theme_font_size": False}
1393
1394 def __init__(self):
1395 # sys.platform returns 'linux2' in py2 and linux in py3
1396 # hence hackish way to ensure compatibility
1397 if sys.platform[:5] != 'linux':
1398 # only support for linux is implemented
1399 raise RuntimeError("Unsupported OS: patches accepted.")
1400
1401 self.__file_name = "{0}.json".format(__appname__)
1402 # we follow the XDG spec and support $XDG_CONFIG_HOME.
1403 # Also, traditionally, Linux apps store their data in
1404 # "~/.config/<appname>" instead of "~/.local/share/<appname>".
1405 self.__dir_path = os.getenv('XDG_CONFIG_HOME',
1406 os.path.expanduser('~/.config'))
1407 self.__dir_path = os.path.join(self.__dir_path,
1408 '{0}'.format(__appname__))
1409 # Better way to do the below in py3 but ...
1410 try:
1411 os.makedirs(self.__dir_path)
1412 except OSError:
1413 if not os.path.isdir(self.__dir_path):
1414 raise
1415 self.path = os.path.join(self.__dir_path, self.__file_name)
1416 # Initialize config data
1417 self.get_config()
1418
1419 def get_config(self):
1420 """
1421 Initializes the config data
1422 """
1423 if os.path.exists(self.path):
1424 try:
1425 with open(self.path) as config_file:
1426 self.data = json.load(config_file)
1427 except ValueError:
1428 print_err('Config file corruption!')
1429 self.default_config()
1430 else:
1431 self.default_config()
1432
1433 if not self.__validate():
1434 print_err('Config file corruption!')
1435 self.default_config()
1436
1437 version = self.data["version"]
1438 if version.split('.')[0] != str(self.__config_version_major):
1439 print_err('Config version mismatch')
1440 self.default_config()
1441
1442 def __validate(self):
1443 """
1444 Return true on Valid config keys. No checks on config values
1445 """
1446 return set(ePadConf.default) == set(self.data)
1447
1448 def write(self):
1449 with open(self.path, "w") as config_file:
1450 json.dump(self.data, config_file, sort_keys=True, indent=4, separators=(',', ': '))
1451
1452 def default_config(self):
1453 """
1454 Create a config file
1455 """
1456 self.data = ePadConf.default
1457 self.write()
1458
1459 def __getitem__(self, setting):
1460 """
1461 Returns a setting.
1462 """
1463 try:
1464 value = self.data[setting]
1465 except KeyError:
1466 print_err('Invalid settings: "{0}"'.format(setting))
1467 value = None
1468 return value
1469
1470 def __setitem__(self, setting, value):
1471 """
1472 Update a setting. No validation on Value. Save file after update.
1473 """
1474 self.__safe_set(setting, value)
1475 self.write()
1476
1477 def __safe_set(self, setting, value):
1478 """
1479 Update a setting. No validation on Value.
1480 """
1481 try:
1482 if self.data[setting] == value:
1483 return
1484 except KeyError:
1485 print_err('Invalid settings: "{0}"'.format(setting))
1486 return
1487 self.data[setting] = value
1488
1489 def update(self, settings=None, **kwargs):
1490 """
1491 Update Multiple settings. No validation on Value.
1492 Saves file after update.
1493 Usage:
1494 config = ePadConf()
1495 config.update(font='Mono',font_size=24)
1496 config.update({'font': 'Mono', 'font_size': 24})
1497 """
1498 if settings is not None:
1499 for setting, value in settings.items() if isinstance(settings, Mapping) else settings:
1500 self.__safe_set(setting, value)
1501 for setting, value in kwargs.items():
1502 self.__safe_set(setting, value)
1503 self.write()
1504
1505
1506if __name__ == "__main__":
1507
1508 ourFiles = sys.argv
1509
1510 #Remove ePad.py from the arguments
1511 del ourFiles[0]
1512
1513 # Start App
1514 elementary.init()
1515 GUI = Interface()
1516 if ourFiles:
1517 GUI.launch([ourFiles, None])
1518 else:
1519 GUI.launch([None, os.getcwd()])
1520 elementary.run()
1521 GUI.fileSelector.shutdown()
1522 # Shutdown App
1523 elementary.shutdown()