├── README.rst ├── R_outline.py ├── add-settings-menu.leo ├── buttons ├── NotWS.py ├── changes.py └── tpassin_setting_tricks.leo ├── ctrl_b_i_u.py ├── examples ├── change-fonts-example.leo ├── change-fonts-example.png ├── demo_widget.py ├── layout │ └── qtdock.py ├── rst-tutorial │ ├── leo-rst-tutorial-example.png │ ├── myDocument.html │ ├── myDocument.html.txt │ └── rst-tutorial-example.leo ├── tests │ ├── make_icons.py │ └── string_lists.py └── workflow │ └── latex-pdf │ ├── Client │ ├── Client Variables.txi │ ├── SGC-H120 Reusable Content.tex │ └── SGC-M110 Training Manual.tex │ ├── Latex-PDF Workflow.leo │ └── Resources │ └── Reusable Content.txi ├── experiments ├── cache-testing.leo └── line-numbering.leo ├── git_node_versioning.py ├── introspect.py ├── issue_attach └── 32 │ ├── MindjetScript.py │ ├── PLE_(Personal_Learning_Environment).mm │ ├── PLE_(Personal_Learning_Environment).mm.html │ ├── TED - 10 Most Popular Talks.csv │ ├── TED - 10 Most Popular Talks.mmap │ ├── freemindScript.py │ └── freemind_mindjet_import.txt ├── live_code.py ├── node_backup.py ├── on_typing_idle.py ├── past-due-report.py ├── rich_text_editor.py ├── rst2html_node.py ├── scripts ├── misc │ └── get_tips.py └── output │ └── outline2text.py └── utils ├── diff_trees.py ├── led.py ├── led.sh ├── pytest_switch.py └── x11_copy_paste_for_windows.py /README.rst: -------------------------------------------------------------------------------- 1 | GitHub organizations can't have Gists, so here's a repo. for Leo's 2 | "Gists". 3 | -------------------------------------------------------------------------------- /R_outline.py: -------------------------------------------------------------------------------- 1 | # insert / update headlines as comments in @nosent R code 2 | headlines = [] 3 | for nd in p.self_and_subtree_iter(): 4 | 5 | if nd.h and nd.h[0] == '@' or nd.b and nd.b[0] == '@': 6 | continue 7 | 8 | headlines.append(nd.h) 9 | 10 | lines = nd.b.split('\n') 11 | if lines and lines[0].startswith('### '): 12 | del lines[0] 13 | if lines and lines[0].strip(): 14 | lines[0:0] = [""] 15 | lines[0:0] = [ 16 | "### %s %s" % (nd.h, "#"*(80-len(nd.h)-5)), 17 | ] 18 | if '.coffee' in p.h: 19 | lines[0:0] = [""] 20 | if lines[-1].strip(): 21 | lines.append("") 22 | if lines[-2].strip(): 23 | lines.append("") 24 | 25 | b = '\n'.join(lines) 26 | if nd.b != b: 27 | nd.b = b 28 | 29 | g.es('\n'.join(headlines)) 30 | 31 | c.redraw() 32 | -------------------------------------------------------------------------------- /add-settings-menu.leo: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | @settings 8 | @menuat /help before 9 | @item open-leo12pt-leo 10 | 11 | @data qt-gui-plugin-style-sheet 12 | 13 | 14 | 15 | /* Disable this to continually test leoSettings.leo */ 16 | 17 | /* Documentation of Qt stylesheets at http://doc.trolltech.com/4.2/stylesheet.html */ 18 | 19 | /* constants - *parsed from this comment* 20 | 21 | needed for zoom in / out 22 | 23 | @font-size-body = 10pt 24 | 25 | *color names* 26 | @MistyRose1 = #FFE4E1 27 | @LightSteelBlue1 = #CAE1FF 28 | @LightBlue = #ADD8E6 29 | 30 | Buttons may be styled by name: 31 | QPushButton#<button text>-button { <style> } 32 | or by kind: 33 | QPushButton[button_kind="<button kind>"] { <style> } 34 | Button kinds are: 35 | 'run-script' (the singleton run-script button), 36 | 'debug-script', 37 | 'script-button-button' (the singleton script-button button), 38 | 'script-button' (buttons created by the script-button button), 39 | 'generic-button' (default), 40 | 'quick-move' from the quickMove plugin, 41 | 'interact' from the interact plugin, 42 | 'at-button' (created from @button nodes) 43 | Search 'button_kind' below for button styling examples. 44 | 45 | *button background colors* 46 | @run-script-btn-bg = @MistyRose1 47 | @debug-script-btn-bg = @MistyRose1 48 | @script-button-button-btn-bg = #ffffcc 49 | @script-button-btn-bg = @MistyRose1 50 | @generic-button-btn-bg = @LightSteelBlue1 51 | @quick-move-btn-bg = @LightSteelBlue1 52 | @interact-btn-bg = @LightBlue 53 | @at-button-btn-bg = @LightSteelBlue1 54 | 55 | */ 56 | 57 | /* Valid color names: http://www.w3.org/TR/SVG/types.html#ColorKeywords */ 58 | 59 | /* 60 | Important: this stylesheets is responsible for most, but *not* all, of Leos appearance. 61 | See also settings in "Colorizer and colors" 62 | */ 63 | 64 | QScrollArea { 65 | background-color: white; 66 | } 67 | 68 | /* Components of the Find Tab */ 69 | 70 | QWidget#findTab { 71 | background-color: white; 72 | } 73 | 74 | QLabel#findHeading { 75 | font-family: DejaVu Sans Mono; 76 | font-size: 10pt; 77 | font-weight: normal; /* normal,bold,100,..,900 */ 78 | font-style: normal; /* normal,italic,oblique */ 79 | } 80 | 81 | QLabel#findLabel { 82 | font-family: DejaVu Sans Mono; 83 | font-size: 10pt; 84 | font-weight: normal; /* normal,bold,100,..,900 */ 85 | font-style: normal; /* normal,italic,oblique */ 86 | } 87 | 88 | QLabel#changeLabel { 89 | font-family: DejaVu Sans Mono; 90 | font-size: 10pt; 91 | font-weight: normal; /* normal,bold,100,..,900 */ 92 | font-style: normal; /* normal,italic,oblique */ 93 | } 94 | 95 | QLabel#findHelp { 96 | font-family: DejaVu Sans Mono; 97 | font-size: 10pt; 98 | font-weight: normal; /* normal,bold,100,..,900 */ 99 | font-style: normal; /* normal,italic,oblique */ 100 | } 101 | 102 | QLineEdit#findPattern { 103 | font-family: DejaVu Sans Mono; 104 | font-size: 10pt; 105 | font-weight: normal; /* normal,bold,100,..,900 */ 106 | font-style: normal; /* normal,italic,oblique */ 107 | } 108 | 109 | QLineEdit#findChange { 110 | font-family: DejaVu Sans Mono; 111 | font-size: 10pt; 112 | font-weight: normal; /* normal,bold,100,..,900 */ 113 | font-style: normal; /* normal,italic,oblique */ 114 | } 115 | 116 | /* A QWidget: supports only background attributes.*/ 117 | 118 | QSplitter::handle { 119 | background-color: #CAE1FF; /* lightSteelBlue1 */ 120 | } 121 | 122 | QStackedWidget { 123 | /* background-color:lightpink; */ 124 | border-color: red; 125 | padding: 0px; 126 | /* border-width: 0px; */ 127 | /* background-color: yellow; */ 128 | } 129 | 130 | QSplitter { 131 | border-color: white; 132 | background-color: white; 133 | border-style: solid; 134 | } 135 | 136 | QTreeWidget { 137 | /* These apply to the selected item, but not to editing items.*/ 138 | background-color: #ffffec; /* Leo's traditional tree color */ 139 | selection-color: black; /* was white */ 140 | selection-background-color: lightgrey; 141 | /* font-family: SansSerif; */ 142 | font-family: DejaVu Sans Mono; 143 | font-size: 10pt; 144 | font-weight: normal; /* normal,bold,100,..,900 */ 145 | font-style: normal; /* normal, italic,oblique */ 146 | show-decoration-selected: 1 /* 1: select entire row */ 147 | } 148 | 149 | /* Headline edit widgets */ 150 | QTreeWidget QLineEdit { 151 | background-color: cornsilk; 152 | selection-color: white; 153 | selection-background-color: blue; 154 | font-family: DejaVu Sans Mono; 155 | font-size: 10pt; 156 | font-weight: normal; /* normal,bold,100,..,900 */ 157 | font-style: normal; /* normal, italic,oblique */ 158 | } 159 | 160 | /* The log panes */ 161 | QTextEdit { 162 | background-color: white; /* #f2fdff; */ 163 | selection-color: white; 164 | selection-background-color: blue; 165 | /* font-family: Courier New; */ 166 | font-family: DejaVu Sans Mono; 167 | font-size: 10pt; 168 | font-weight: normal; /* normal,bold,100,..,900 */ 169 | font-style: normal; /* normal, italic,oblique */ 170 | } 171 | 172 | /* The body pane */ 173 | QTextEdit#richTextEdit { 174 | background-color: white; /* #fdf5f5; A kind of pink. */ 175 | selection-color: white; 176 | selection-background-color: lightgrey; 177 | font-family: DejaVu Sans Mono; 178 | /* font-family: Courier New; */ 179 | font-size: @font-size-body; 180 | font-weight: normal; /* normal,bold,100,..,900 */ 181 | font-style: normal; /* normal,italic,oblique */ 182 | } 183 | 184 | /* Editor labels */ 185 | QLineEdit#editorLabel { 186 | background-color: #ffffec; 187 | font-family: DejaVu Sans Mono; 188 | font-size: 10pt; 189 | font-weight: normal; /* normal,bold,100,..,900 */ 190 | font-style: normal; /* normal,italic,oblique */ 191 | border: 2px; 192 | margin: 2px; 193 | } 194 | 195 | /* The text "Minibuffer" in the minibuffer aread. 196 | 197 | Do not overide QLabel directly. It is used for dialog text. 198 | */ 199 | 200 | QLabel#minibufferLabel { 201 | font-family: DejaVu Sans Mono; 202 | font-size: 10pt; 203 | font-weight: normal; 204 | font-style: normal; 205 | } 206 | 207 | /* 208 | QLabel { 209 | font-family: DejaVu Sans Mono; 210 | font-size: 10pt; 211 | font-weight: normal; 212 | font-style: normal; 213 | border: 2px; 214 | margin: 2px; 215 | } 216 | */ 217 | 218 | 219 | /* The mini-buffer 220 | 221 | **Important**: Because Leo changes the color of the minibuffer dynamically, 222 | stylesheets can not be used. Instead, set the desired colors using one of the 223 | following settings, with defaults as shown:: 224 | 225 | @color minibuffer-background-color = lightblue 226 | @color minibuffer-error-color = red 227 | @color minibuffer-foreground-color = black 228 | @color minibuffer-warning-color = lightgrey 229 | */ 230 | QLineEdit#lineEdit { 231 | selection-color: white; 232 | selection-background-color: lightgrey; 233 | font-family: DejaVu Sans Mono; 234 | font-size: 10pt; 235 | font-weight: normal; /* normal,bold,100,..,900 */ 236 | font-style: normal; /* normal,italic,oblique */ 237 | } 238 | 239 | QLineEdit#status1 { 240 | background-color: lightgrey; 241 | border-width: 1px; 242 | border-style: solid; 243 | border-color: darkgrey; 244 | font-size: 10pt; 245 | } 246 | 247 | QLineEdit#status2 { 248 | background-color: lightgrey; 249 | border-width: 1px; 250 | border-style: solid; 251 | border-color: darkgrey; 252 | font-size: 10pt; 253 | } 254 | 255 | /* button_kind based button coloring */ 256 | QPushButton[button_kind="run-script"] { 257 | background-color: @run-script-btn-bg; } 258 | QPushButton[button_kind="debug-script"] { 259 | background-color: @debug-script-btn-bg; } 260 | QPushButton[button_kind="generic-button"] { 261 | background-color: @generic-button-btn-bg; } 262 | QPushButton[button_kind="quick-move"] { 263 | background-color: @quick-move-btn-bg; } 264 | QPushButton[button_kind="interact"] { 265 | background-color: @interact-btn-bg; } 266 | QPushButton[button_kind="at-button"] { 267 | background-color: @at-button-btn-bg; } 268 | QPushButton[button_kind="script-button"] { 269 | background-color: @script-button-btn-bg; } 270 | /* example of name based button coloring. Coincidentally, the 271 | name and button_kind of this button are the same */ 272 | QPushButton#script-button-button { 273 | background-color: @script-button-button-btn-bg; } 274 | 275 | QPlainTextEdit#screencastcaption { 276 | background-color: yellow; 277 | font-family: DejaVu Sans Mono; /* Times New Roman; */ 278 | font-size: 18pt; 279 | font-weight: normal; /* normal,bold,100,..,900 */ 280 | font-style: normal; /* normal,italic,oblique */ 281 | } 282 | 283 | /* focused pane border highlight */ 284 | QTextEdit#log-widget, LeoQTreeWidget#treeWidget, QTextEdit#richTextEdit { 285 | border-style: @focused-border-style; 286 | border-width: @focused-border-width; 287 | border-color: @focused-border-unfocus-color; 288 | } 289 | QTextEdit:focus#log-widget, LeoQTreeWidget:focus#treeWidget, QTextEdit:focus#richTextEdit { 290 | border-style: @focused-border-style; 291 | border-width: @focused-border-width; 292 | border-color: @focused-border-focus-color; 293 | } 294 | 295 | 296 | 297 | Open &leo12pt.leo 298 | 299 | 300 | -------------------------------------------------------------------------------- /buttons/NotWS.py: -------------------------------------------------------------------------------- 1 | """Clean up whitespace in a file""" 2 | 3 | import re 4 | tws = re.compile(' +\n') 5 | hit = False 6 | for nd in p.self_and_subtree_iter(): 7 | t = tws.sub('\n', nd.b.rstrip(' \t\n'))+'\n\n' 8 | if t != nd.b: 9 | nd.b = t 10 | nd.setDirty() 11 | hit = True 12 | if hit: 13 | c.setChanged(True) 14 | c.redraw() 15 | -------------------------------------------------------------------------------- /buttons/changes.py: -------------------------------------------------------------------------------- 1 | from subprocess import Popen, PIPE 2 | 3 | # START: stuff to replace with core functions 4 | def atfile(p): 5 | """Find @ containing p""" 6 | word0 = p.h.split()[0] 7 | return ( 8 | word0 in g.app.atFileNames|set(['@auto']) or 9 | word0.startswith('@auto-') 10 | ) 11 | 12 | aList = g.get_directives_dict_list(p) 13 | path = c.scanAtPathDirectives(aList) 14 | while c.positionExists(p): 15 | if atfile(p): # see if it's a @ node of some sort 16 | break 17 | p.moveToParent() 18 | # END: stuff to replace with core functions 19 | 20 | if c.positionExists(p): 21 | filename = g.os_path_basename(p.h.split(None, 1)[-1]) 22 | g.es(g.os_path_join(path, filename)) 23 | diff, err = Popen(['git', '-C', path, 'diff', filename], 24 | stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate() 25 | if err: 26 | g.es(err.decode('utf-8')) 27 | else: 28 | diff = diff.decode('utf-8') 29 | # parsing a line like `@@ -564,27 +564,27 @@ class NNNModel:` 30 | base = p.get_UNL(with_count=True, with_proto=True) 31 | for line in diff.split('\n'): 32 | if line.startswith('@@ '): 33 | # line.split()[2] would be +564,27 in the above, the modified lines 34 | line, lines = map(int, line.split()[2].split(',')) 35 | g.es("%3d lines @ %d" % (lines, line), 36 | nodeLink="%s,%s" % (base, -(line+int(lines/2)))) 37 | # offset to the middle of the block, -ve indicates global line num. 38 | else: 39 | g.es("Didn't find file") 40 | 41 | -------------------------------------------------------------------------------- /buttons/tpassin_setting_tricks.leo: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Readme 10 | @buttons 11 | @button Show Current Dir 12 | @button Cmd Window Here 13 | 14 | Menus 15 | @menuat /file/openwith before 16 | @openwith Editplus = Alt+Shift+E 17 | @openwith Pyzo = Alt+Shift+P 18 | 19 | 20 | Line Numbers 21 | @bool use_gutter = True 22 | @int gutter-y-adjust = 5 23 | 24 | @shortcuts 25 | @bool create_nonexistent_directories = True 26 | 27 | 28 | 29 | from subprocess import Popen 30 | cmd = 'explorer.exe .' 31 | Popen(cmd) 32 | from subprocess import Popen 33 | cmd = 'cmd.exe cd .' 34 | Popen(cmd) 35 | Note that these paths are specific to my system. Change them for another system. 36 | 37 | ext: 38 | kind: subprocess.Popen 39 | arg: "C:\Program Files\EditPlus\editplus.exe" 40 | 41 | 42 | ext: .exe 43 | kind: subprocess.Popen 44 | arg: "c:\Program Files\pyzo\pyzo.exe" 45 | 46 | 47 | Change the gutter-y-adjust value to make the line numbers line up right. 48 | 49 | 50 | 51 | goto-next-marked = F4 52 | mark = F9 53 | unmark-all = F10 54 | 55 | match-brackets = Ctrl+] 56 | add-comments = Ctrl+' 57 | delete-comments = Ctrl+; 58 | 59 | vr3-toggle = Alt+0 60 | 61 | Contributed by Thomas Passin 62 | https://groups.google.com/d/msg/leo-editor/kgwfUHJdM1s/QU_1HA3vDQAJ 63 | 64 | To use this example, all of the nodes in this file go under the @settings node in MyLeoSettings.Leo. 65 | 66 | QQQ 67 | One of the more useful little things I did was to create two buttons that appear above every outline: 68 | 69 | 1. Show Current Directory 70 | 2. Cmd Window Here 71 | 72 | They both open in the currently effective directory, which is usually the directory of the outline itself. That can be changed using @path directives. So, for example, if you changed an HTML file and wanted to load it into the browser, you could open e.g., Windows Explorer on its directory and then double click it. 73 | 74 | Another thing that is easy to do is to add a menu item that opens the selected file in your favorite editor (or it could be any program, such as a browser). 75 | 76 | Another very useful thing I have done is to show line numbers for files. It takes a bit of tinkering to get them aligned right, but I think it's worth it. 77 | 78 | [...] 79 | 80 | Note that the two @button commands are specific to Windows. They could be done in Linux, of course, but the exact commands would be specific to each linux distro. Maybe someone would like to suggest example Linux commands. For example, launching a GUI program like the file manager can be a little tricky, so help us get it set up right. 81 | 82 | The actual paths to the programs in the @menuat items would have to be adjusted for some else's system. 83 | 84 | I have included some keyboard shortcuts that I find very useful. They don't conflict with any existing shortcuts so far as I know. There is a group to make it easy to set, jump to, and remove node markers. There is a pair that make it easy to comment out or uncomment a selection of lines. And there is my favored ALT-0 for opening viewrendered3. 85 | 86 | Actually, I would find it hard to operate for any period of time without these settings. I use them all the time. 87 | QQQ 88 | 89 | 90 | -------------------------------------------------------------------------------- /ctrl_b_i_u.py: -------------------------------------------------------------------------------- 1 | """ 2 | Commands to go with keybindings in @settings-->@keys-->@shortcuts 3 | to implement Ctrl-B,I,U Bold Italic Underline markup in plain text. 4 | RST flavored, could be made language aware. 5 | 6 | Key bindings would be something like: 7 | 8 | markup_inline_bold ! body = Ctrl-B 9 | markup_inline_italic ! body = Ctrl-I 10 | markup_inline_underline ! body = Ctrl-U 11 | 12 | """ 13 | 14 | def markup_inline(kw, kind='unknown'): 15 | 16 | c = kw['c'] 17 | 18 | # find out if last action was open or close, creates entry if needed 19 | last_type = c.user_dict.setdefault( 20 | 'markup_inline', {'last': 'close'})['last'] 21 | 22 | p = c.p 23 | 24 | delim = { 25 | 'bold': ('**','**'), 26 | 'italic': ('*','*'), 27 | 'underline': (':ul:`','`'), 28 | }[kind] 29 | 30 | if c.frame.body.bodyCtrl.hasSelection(): 31 | c.user_dict['markup_inline']['last'] = 'close' 32 | i,j = c.frame.body.bodyCtrl.getSelectionRange() 33 | txt = c.frame.body.bodyCtrl.getAllText() 34 | p.b = "".join([ 35 | txt[:i], 36 | delim[0], 37 | txt[i:j], 38 | delim[1], 39 | txt[j:], 40 | ]) 41 | c.frame.body.bodyCtrl.setAllText(p.b) 42 | c.frame.body.bodyCtrl.setInsertPoint(j+len(delim[0])+len(delim[1])) 43 | else: 44 | i = c.frame.body.bodyCtrl.getInsertPoint() 45 | txt = c.frame.body.bodyCtrl.getAllText() 46 | if last_type == 'close': 47 | delim = delim[0] 48 | c.user_dict['markup_inline']['last'] = 'open' 49 | else: 50 | delim = delim[1] 51 | c.user_dict['markup_inline']['last'] = 'close' 52 | p.b = "".join([ 53 | txt[:i], 54 | delim, 55 | txt[i:] 56 | ]) 57 | c.frame.body.bodyCtrl.setAllText(p.b) 58 | c.frame.body.bodyCtrl.setInsertPoint(i+len(delim)) 59 | c.setChanged(True) 60 | p.setDirty(True) 61 | c.redraw() 62 | c.bodyWantsFocusNow() 63 | 64 | @g.command('markup_inline_bold') 65 | def markup_inline_bold(kw): 66 | markup_inline(kw, kind='bold') 67 | 68 | @g.command('markup_inline_italic') 69 | def markup_inline_italic(kw): 70 | markup_inline(kw, kind='italic') 71 | 72 | @g.command('markup_inline_underline') 73 | def markup_inline_underline(kw): 74 | markup_inline(kw, kind='underline') 75 | 76 | -------------------------------------------------------------------------------- /examples/change-fonts-example.leo: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | @settings 14 | @data qt-gui-plugin-style-sheet 15 | 16 | 17 | 18 | A stripped down example of changing Leo's default fonts. 19 | 20 | For a more extensive treatment see Help > "LeoSettings.leo --> @settings --> 21 | Appearance" and "LeoSettings.leo --> Themes". 22 | /* The body pane */ 23 | QTextEdit#richTextEdit { 24 | font-family: Garamond, Times New Roman; 25 | /* font-family: Courier New; */ 26 | font-size: 18px; 27 | font-weight: normal; /* normal,bold,100,..,900 */ 28 | font-style: normal; /* normal,italic,oblique */ 29 | } 30 | 31 | /* The log panes */ 32 | QTextEdit#log-widget { 33 | font-family: Courier New; 34 | font-size: 12px; 35 | } 36 | 37 | QTreeWidget { 38 | /* These apply to the selected item, but not to editing items.*/ 39 | font-family: Garamond, Times New Roman; 40 | font-size: 16px; 41 | } 42 | 43 | /* Headline edit widgets */ 44 | QTreeWidget QLineEdit View{ 45 | font-family: Garamond, Times New Roman; 46 | font-size: 16px; 47 | } 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /examples/change-fonts-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leo-editor/snippets/622f9eba4f40c976a4bfd10fdecc0404e15c3206/examples/change-fonts-example.png -------------------------------------------------------------------------------- /examples/demo_widget.py: -------------------------------------------------------------------------------- 1 | # full and minimal examples for adding widgets in panes in Leo 2 | # Note: to add the widget, right click a pane divider, and select 3 | # Insert. Then click the Action button that appears, and select 4 | # your widget. 5 | 6 | # Copy all this text (use Raw view) to a node, and run it (Ctrl-B) once 7 | # After doing that, the pane divider right click context menu open window 8 | # submenu will have "Demo * slider" options. 9 | 10 | from leo.core.leoQt import QtCore, QtWidgets 11 | class DemoProvider: 12 | def ns_provides(self): 13 | """should return a list of ('Item name', '__item_id') strings, 14 | 'Item name' is displayed in the Action button menu, and 15 | '__item_id' is used in ns_provide().""" 16 | return [ 17 | ("Demo horizontal slider", "__demo_provider_hs"), 18 | ("Demo vertical slider", "__demo_provider_vs"), 19 | ] 20 | def ns_provide(self, id_): 21 | """should return the widget to replace the Action button based on 22 | id_, or None if the called thing is not the provider for this id_""" 23 | if id_ == "__demo_provider_hs": 24 | w = QtWidgets.QSlider(QtCore.Qt.Horizontal) 25 | return w 26 | if id_ == "__demo_provider_vs": 27 | w = QtWidgets.QSlider() 28 | return w 29 | return None 30 | def ns_context(self): 31 | """should return a list of ('Item name', '__item_id') strings, 32 | 'Item name' is displayed in the splitter handle context-menu, and 33 | '__item_id' is used in ns_do_context(). May also return a dict, 34 | in which case each key is used as a sub-menu title, whose menu 35 | items are the corresponding dict value, a list of tuples as above. 36 | dicts and tuples may be interspersed in lists.""" 37 | return [ 38 | ("Put 'text' in log", "__demo_provider_til"), 39 | { 40 | 'More texts': [ 41 | ("Put 'frog' in log", "__demo_provider_txt_frog"), 42 | ("Put 'otters' in log", "__demo_provider_txt_otters"), 43 | ], 44 | 'Numbers': [ 45 | ("Put 7 in log", "__demo_provider_txt_7"), 46 | ("Put 77 in log", "__demo_provider_txt_77"), 47 | ], 48 | } 49 | ] 50 | def ns_do_context(self,id_, splitter, index): 51 | """should do something based on id_ and return True, or return False 52 | if the called thing is not the provider for this id_ 53 | 54 | splitter and index as per QSplitter""" 55 | if id_ == '__demo_provider_til': 56 | g.es("'text'") 57 | return True 58 | if id_.startswith('__demo_provider_txt_'): 59 | # silly example, but storing info in id_ is legitimate 60 | g.es(id_.replace('__demo_provider_txt_', '')) 61 | return True 62 | return False 63 | def ns_provider_id(self): 64 | """return a string identifying the provider (at class or instance level), 65 | any providers with the same id will be removed before a new one is 66 | added""" 67 | return "__demo_provider" 68 | 69 | class MinimalDemoProvider: 70 | def ns_provides(self): 71 | return [("Demo minimal slider", "__demo_provider_minimal_slider")] 72 | def ns_provide(self, id_): 73 | if id_ == "__demo_provider_minimal_slider": 74 | w = QtWidgets.QSlider() 75 | return w 76 | return None 77 | def ns_provider_id(self): 78 | return "__demo_provider_minimal" 79 | 80 | c.free_layout.get_top_splitter().register_provider(DemoProvider()) 81 | c.free_layout.get_top_splitter().register_provider(MinimalDemoProvider()) 82 | 83 | -------------------------------------------------------------------------------- /examples/layout/qtdock.py: -------------------------------------------------------------------------------- 1 | """ 2 | Testing Qt Dock widgets as a replacement for NestedSplitter 3 | 4 | See https://github.com/leo-editor/leo-editor/issues/314 5 | 6 | Things learned 7 | 8 | - "close" control just hides the widget 9 | - don't use visibilityChanged to manage close / vs. hide, it's 10 | called during dragging etc. 11 | - instead override .closeEvent() to manage close / vs. hide 12 | - the central widget is a nuisance, easiest just to not have one, 13 | rather than manage moving dock widgets in and out of that role 14 | - allowing nested docks comes at the price of more complicated 15 | behavior, in return for more freedom in layouts 16 | - my regular workbook.leo layout requires nested docks 17 | - floating windows cannot contain docks, which is a major loss 18 | vs. NestedSplitter, might be able to address that by adding 19 | another main window, but (a) doesn't work out of the box, would 20 | need widget transfer code, and (b) would require more 21 | save/restore code 22 | 23 | The no docks in floating windows issue and the pointless central widget 24 | issue may have resulted in NestedSplitter being developed, but at this 25 | point it seems it would be better to use Qt docking. 26 | 27 | Thoughts 28 | 29 | - save / restore state needs to be handled 30 | - the Leo "log pane" can be simulated by using the pane 31 | containing the actual log widget 32 | - rather than (safer than?) maintaining our own dock_widgets 33 | list, could just search main window for QDockWidget children 34 | track_widgets controls this choice 35 | 36 | """ 37 | 38 | import sys 39 | from PyQt4 import QtGui, QtCore, Qt 40 | from PyQt4.QtCore import Qt as QtConst 41 | 42 | app = Qt.QApplication(sys.argv) 43 | main = QtGui.QMainWindow() 44 | main.resize(800,800) 45 | main.move(40,40) 46 | qtextedit = QtGui.QPushButton("Click to close central widget") 47 | main.setCentralWidget(qtextedit) 48 | def close(clicked, qtextedit=qtextedit): 49 | qtextedit.hide() 50 | qtextedit.clicked.connect(close) 51 | 52 | main.setDockOptions(QtGui.QMainWindow.AllowNestedDocks | 53 | QtGui.QMainWindow.AllowTabbedDocks) 54 | 55 | # start - extra dock window 56 | main2 = QtGui.QMainWindow() 57 | main2.resize(200,200) 58 | main2.move(20,140) 59 | qtextedit = QtGui.QPushButton("Click to close central widget") 60 | main2.setCentralWidget(qtextedit) 61 | def close(clicked, qtextedit=qtextedit): 62 | qtextedit.hide() 63 | qtextedit.clicked.connect(close) 64 | main2.setDockOptions(QtGui.QMainWindow.AllowNestedDocks | 65 | QtGui.QMainWindow.AllowTabbedDocks) 66 | main2.show() 67 | # end - extra dock window 68 | 69 | areas = { 70 | 'Left': QtConst.LeftDockWidgetArea, 71 | 'Right': QtConst.RightDockWidgetArea, 72 | 'Top': QtConst.TopDockWidgetArea, 73 | 'Bottom': QtConst.BottomDockWidgetArea, 74 | } 75 | 76 | track_widgets = False 77 | 78 | dock_widgets = [] 79 | 80 | def get_dock_widgets(): 81 | if track_widgets: 82 | return dock_widgets 83 | else: 84 | return main.findChildren(QtGui.QDockWidget) 85 | 86 | def add_widget(name, deleteable=False, moveable=True, 87 | area=QtConst.RightDockWidgetArea): 88 | widget = QtGui.QDockWidget(name) 89 | widget._is_deleteable = deleteable 90 | if track_widgets: 91 | dock_widgets.append(widget) 92 | qtextedit = QtGui.QTextEdit() 93 | qtextedit.setPlainText(name) 94 | qtextedit.setReadOnly(True) 95 | widget.setWidget(qtextedit) 96 | main.addDockWidget(area, widget) 97 | 98 | if not moveable: 99 | widget.setFeatures(QtGui.QDockWidget.DockWidgetClosable) 100 | if deleteable: 101 | # fixme should make LeoQDockWidget subclass, maybe 102 | def closeEvent(event, widget=widget): 103 | if track_widgets: 104 | dock_widgets.remove(widget) 105 | widget.deleteLater() 106 | widget.closeEvent = closeEvent 107 | 108 | return widget 109 | 110 | add_widget("Tree", area=QtConst.LeftDockWidgetArea) 111 | add_widget("Log", area=QtConst.LeftDockWidgetArea) 112 | add_widget("Body") 113 | 114 | def show_dock_placeholders(): 115 | clear_placeholders() 116 | docks_used = set() 117 | for widget in get_dock_widgets(): 118 | if widget.isVisible() and not widget.isFloating(): 119 | docks_used.add(main.dockWidgetArea(widget)) 120 | for name, area in areas.items(): 121 | if area not in docks_used: 122 | text = "%s Dock Placeholder" % name 123 | widget = add_widget( 124 | text, deleteable=True, moveable=False, area=area) 125 | 126 | widget._is_placeholder = True 127 | widget.setStyleSheet("background: #fdd;") 128 | 129 | def clear_placeholders(): 130 | cull = [i for i in get_dock_widgets() 131 | if getattr(i, '_is_placeholder', False)] 132 | for widget in cull: 133 | widget.close() 134 | 135 | def hide_titles(): 136 | for widget in get_dock_widgets(): 137 | widget.setTitleBarWidget(QtGui.QWidget()) 138 | 139 | def show_titles(): 140 | for widget in get_dock_widgets(): 141 | widget.setTitleBarWidget(None) 142 | 143 | menu = main.menuBar().addMenu("Windows") 144 | menu = menu.addMenu("Docks") 145 | menu.addAction("Show placeholders", show_dock_placeholders) 146 | menu.addAction("Hide placeholders", clear_placeholders) 147 | menu.addAction("Show titles / handles", show_titles) 148 | menu.addAction("Hide titles / handles", hide_titles) 149 | 150 | # demo specific code, no place in real app. 151 | widget_count = [0] 152 | 153 | def add_new_widget(**kwargs): 154 | widget_count[0] += 1 155 | n = widget_count[0] 156 | add_widget("Widget %d" % n, deleteable=True, **kwargs) 157 | 158 | for n in range(3): 159 | add_new_widget(area=QtConst.BottomDockWidgetArea) 160 | 161 | menu.addAction("Add new widget", add_new_widget) 162 | # end demo specific code 163 | 164 | main.show() 165 | sys.exit(app.exec_()) 166 | 167 | -------------------------------------------------------------------------------- /examples/rst-tutorial/leo-rst-tutorial-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leo-editor/snippets/622f9eba4f40c976a4bfd10fdecc0404e15c3206/examples/rst-tutorial/leo-rst-tutorial-example.png -------------------------------------------------------------------------------- /examples/rst-tutorial/myDocument.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | War and Peace 8 | 315 | 316 | 317 |
318 |

War and Peace

319 | 320 | 321 |
322 |

by Leo Tolstoy/Tolstoi

323 |

BOOK ONE: 1805

324 |
325 |
326 |

Chapter I

327 |

"Well, Prince, so Genoa and Lucca are now just family estates of the 328 | Buonapartes. But I warn you, if you don't tell me that this means war, 329 | if you still try to defend the infamies and horrors perpetrated by 330 | that Antichrist--I really believe he is Antichrist--I will have 331 | nothing more to do with you and you are no longer my friend, no longer 332 | my 'faithful slave,' as you call yourself! But how do you do? I see 333 | I have frightened you--sit down and tell me all the news."

334 |

...stuff happens...

335 |
336 |
337 |

Chapter XII

338 |

From the time the law of Copernicus was discovered and proved, the 339 | mere recognition of the fact that it was not the sun but the earth 340 | that moves sufficed to destroy the whole cosmography of the 341 | ancients.

342 |

...

343 | 347 |

In the first case it was necessary to renounce the consciousness 348 | of an unreal immobility in space and to recognize a motion we did 349 | not feel; in the present case it is similarly necessary to renounce 350 | a freedom that does not exist, and to recognize a dependence of 351 | which we are not conscious.

352 |

--- The End ---

353 |
354 |
355 | 356 | 357 | -------------------------------------------------------------------------------- /examples/rst-tutorial/myDocument.html.txt: -------------------------------------------------------------------------------- 1 | .. rst3: filename: ~/myDocument.html 2 | 3 | ############# 4 | War and Peace 5 | ############# 6 | 7 | by Leo Tolstoy/Tolstoi 8 | 9 | BOOK ONE: 1805 10 | 11 | Chapter I 12 | +++++++++ 13 | 14 | "Well, Prince, so Genoa and Lucca are now just family estates of the 15 | Buonapartes. But I warn you, if you don't tell me that this means war, 16 | if you still try to defend the infamies and horrors perpetrated by 17 | that Antichrist--I really believe he is Antichrist--I will have 18 | nothing more to do with you and you are no longer my friend, no longer 19 | my 'faithful slave,' as you call yourself! But how do you do? I see 20 | I have frightened you--sit down and tell me all the news." 21 | 22 | ...stuff happens... 23 | 24 | Chapter XII 25 | +++++++++++ 26 | 27 | From the time the law of Copernicus was discovered and proved, the 28 | mere recognition of the fact that it was not the sun but the earth 29 | that moves sufficed to destroy the whole cosmography of the 30 | ancients. 31 | 32 | ... 33 | 34 | .. and now we skip to the end of last chapter, while demonstrating use of an 35 | organizer node (a node whose name does not appear in the output document), 36 | and that rst comments appear as html comments (try 'View Source' on 37 | output.html) 38 | 39 | In the first case it was necessary to renounce the consciousness 40 | of an unreal immobility in space and to recognize a motion we did 41 | not feel; in the present case it is similarly necessary to renounce 42 | a freedom that does not exist, and to recognize a dependence of 43 | which we are not conscious. 44 | 45 | --- The End --- 46 | 47 | -------------------------------------------------------------------------------- /examples/rst-tutorial/rst-tutorial-example.leo: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | @settings 14 | @rst ~/myDocument.html 15 | Chapter I 16 | @rst-ignore 17 | 18 | @rst-ignore-tree Chapter 2+ 19 | Chapter II 20 | translation 21 | 22 | 23 | Chapter XII 24 | @rst-no-head The End 25 | 26 | 27 | 28 | ############# 29 | War and Peace 30 | ############# 31 | 32 | by Leo Tolstoy/Tolstoi 33 | 34 | BOOK ONE: 1805 35 | 36 | You can make whatever notes 37 | to yourself you like here, 38 | as these lines won't appear 39 | in the output files. :) 40 | 41 | 42 | # these rst settings will apply to all @rst nodes in this .leo file, unless overwritten locally. 43 | 44 | @bool rst3_call_docutils = True 45 | # Use False when using Sphinx 46 | @bool rst3_write_intermediate_file = True 47 | @string rst3_write_intermediate_extension = .txt 48 | 49 | "Well, Prince, so Genoa and Lucca are now just family estates of the 50 | Buonapartes. But I warn you, if you don't tell me that this means war, 51 | if you still try to defend the infamies and horrors perpetrated by 52 | that Antichrist--I really believe he is Antichrist--I will have 53 | nothing more to do with you and you are no longer my friend, no longer 54 | my 'faithful slave,' as you call yourself! But how do you do? I see 55 | I have frightened you--sit down and tell me all the news." 56 | 57 | ...stuff happens... 58 | .. and now we skip to the end of last chapter, while demonstrating use of an 59 | organizer node (a node whose name does not appear in the output document), 60 | and that rst comments appear as html comments (try 'View Source' on 61 | output.html) 62 | 63 | In the first case it was necessary to renounce the consciousness 64 | of an unreal immobility in space and to recognize a motion we did 65 | not feel; in the present case it is similarly necessary to renounce 66 | a freedom that does not exist, and to recognize a dependence of 67 | which we are not conscious. 68 | 69 | --- The End --- 70 | 71 | 72 | From the time the law of Copernicus was discovered and proved, the 73 | mere recognition of the fact that it was not the sun but the earth 74 | that moves sufficed to destroy the whole cosmography of the 75 | ancients. 76 | 77 | ... 78 | this node and all child nodes will not appear in the output files. 79 | TL;DR 80 | 81 | ...la femme la plus seduisante de Petersbourg,* ... 82 | *The most fascinating woman in Petersburg. 83 | 84 | 85 | -------------------------------------------------------------------------------- /examples/tests/make_icons.py: -------------------------------------------------------------------------------- 1 | """ 2 | make_icons.py - make simple 4 color icons for the box00.png...box15.png 3 | 4 | Could be adapted for other icon styles, iterates the 16 states in the 5 | correct order. 6 | 7 | WARNING: saves box00.png...box15.png in current directory 8 | 9 | Terry Brown, terrynbrown@gmail.com, Tue Mar 27 12:01:11 2018 10 | """ 11 | from itertools import product 12 | 13 | from PIL import Image 14 | 15 | # ordered list of staes 16 | states = ['clean', 'dirty'], ['no_clone', 'clone'], ['unmarked', 'marked'], ['empty', 'content'] 17 | # solarized colors 18 | sol = {'base03': '002b36', 'base02': '073642', 'base01': '586e75', 'base00': '657b83', 19 | 'base0': '839496', 'base1': '93a1a1', 'base2': 'eee8d5', 'base3': 'fdf6e3', 20 | 'yellow': 'b58900', 'orange': 'cb4b16', 'red': 'dc322f', 'magenta': 'd33682', 21 | 'violet': '6c71c4', 'blue': '268bd2', 'cyan': '2aa198', 'green': '859900'} 22 | for k in sol: 23 | sol[k] = tuple([int(sol[k][i*2:i*2+2], base=16) for i in range(3)] + [255]) 24 | 25 | # states for which marks are made on the transparent icon 26 | # pos (0,0) top left, (1,1) bottom right quarter 27 | marks = { 28 | 'dirty': {'color': sol['yellow'], 'pos': (0,0)}, 29 | 'clone': {'color': sol['magenta'], 'pos': (1,1)}, 30 | 'marked': {'color': sol['cyan'], 'pos': (1,0)}, 31 | 'empty': {'color': sol['base00'], 'pos': (0,1)}, 32 | } 33 | 34 | width, height = 10, 10 35 | 36 | for n, state in enumerate(product(*states)): 37 | print(n, state) 38 | img = Image.new('RGBA', (width, height), (0, 0, 0, 0)) 39 | pix = img.load() 40 | for mark, opt in marks.items(): 41 | if mark in state: 42 | for row in range(height / 2 * opt['pos'][0], height / 2 * (opt['pos'][0]+1)): 43 | for col in range(width / 2 * opt['pos'][1], width / 2 * (opt['pos'][1]+1)): 44 | pix[col, row] = opt['color'] 45 | img.save('box%02d.png' % n) 46 | -------------------------------------------------------------------------------- /examples/tests/string_lists.py: -------------------------------------------------------------------------------- 1 | import timeit 2 | 3 | bees = [ 4 | "[' '] * 10 + [''] + ['\\n']", 5 | "['', '']", 6 | "[' ']", 7 | "['']", 8 | ] 9 | 10 | codes = [ 11 | "bool(''.join(b))", 12 | "all(not i.strip() for i in b)", 13 | "all([not i.strip() for i in b])", 14 | "all(map(lambda i: not i.strip(), b))", 15 | 16 | "''.join(b).isspace()", 17 | "not all(i.isspace() for i in b)", 18 | "not all([i.isspace() for i in b])", 19 | "not all(map(lambda i: i.isspace(), b))", 20 | ] 21 | 22 | for b in bees: 23 | 24 | g.es("\n%s" % b) 25 | setup = b 26 | b = eval(setup) 27 | setup = "b = " + setup 28 | 29 | for n, code in enumerate(codes): 30 | time = timeit.timeit(code, setup, number=100000) 31 | g.es("%s %5s %s" % (n, eval(code), time)) 32 | -------------------------------------------------------------------------------- /examples/workflow/latex-pdf/Client/Client Variables.txi: -------------------------------------------------------------------------------- 1 | %@+leo-ver=5-thin 2 | %@+node:largo84.20190218170416.7: * @file Client Variables.txi 3 | \newcommand{\theClient}{Smith Grocery Company} %Insert full company name. 4 | \newcommand{\SGC}{\theClient} 5 | \newcommand{\theClientName}{Sally Smith} %Insert client agent's name. If unknown, use \hrulefill 6 | \newcommand{\theClientTitle}{President} %Insert client agent's title. If unknown, use \hrulefill 7 | \newcommand{\DSR}{DSR} %Default definition for a company's sales rep. 8 | \newcommand{\DSRs}{DSRs} %Default definition for a company's sales reps (plural). 9 | %@-leo 10 | -------------------------------------------------------------------------------- /examples/workflow/latex-pdf/Client/SGC-H120 Reusable Content.tex: -------------------------------------------------------------------------------- 1 | %@+leo-ver=5-thin 2 | %@+node:largo84.20170410130109.2: * @file SGC-H120 Reusable Content.tex 3 | %@@language tex 4 | \documentclass[addpoints,12pt]{exam} %Use `,answers` for Facilitator and delete for Student version. 5 | %@+<< Paths >> 6 | %@+node:largo84.20170410130109.3: ** << Paths >> 7 | \newcommand{\MainPath}{C:/Main\space Document\space Path/} 8 | 9 | \newcommand{\TemplatePath}{\MainPath Templates/LaTex\space Documents/} 10 | \newcommand{\AgreementPath}{\MainPath Templates/Agreements/} 11 | \newcommand{\ResourcePath}{\MainPath Resources/} 12 | %@-<< Paths >> 13 | %@+<< Project Variables >> 14 | %@+node:largo84.20170410130109.4: ** << Project Variables >> 15 | \input{"Client\space Variables.txi"} % Sets the client name and other related client-specific commands. 16 | \newcommand{\theSchool}{The School Name} 17 | \newcommand{\theSchoolShort}{XYZ} % The short name of the class. Only change this line to reflect the correct school name. 18 | \newcommand{\theCourseShort}{ABC} % The short name of the online course. Only change this line to reflect the correct course name. 19 | %@-<< Project Variables >> 20 | %@+<< Document Variables >> 21 | %@+node:largo84.20170410130109.5: ** << Document Variables >> 22 | \newcommand{\UDI}{SGC-H120} % Unique Document Identifier (F=Form, H=Handout, Q=Test, Quiz or Exam, M=Manual, W=Worksheet, A=Article, C=Contract, S=Script) 23 | \newcommand{\docTitle}{Reusable Content} % ...to show up in the header 24 | \newcommand{\REV}{17.04.10} % Revision number (date), used in combination w/ the UDI as in: FSS-Q101(15.06.08) 25 | 26 | \newcommand{\keywords}{sales training handout \theSchoolShort} %This information will fill in the meta info for PDF creation. 27 | 28 | %@+<< Fonts >> 29 | %@+node:largo84.20170410130109.6: *3* << Fonts >> 30 | %@+at 31 | % Use this section to define document fonts. 32 | %@@c 33 | 34 | \usepackage[T1]{fontenc} 35 | \usepackage{gentium} %Gentium 36 | 37 | %\usepackage{quattrocento} %Quattrocento 38 | %\usepackage{mathpazo} %Palatino 39 | %\usepackage{lmodern} %Latin Modern 40 | %\usepackage{cm-super} %Computer Modern Super (don't use, it may not be installed.) 41 | 42 | %@-<< Fonts >> 43 | %@+<< Packages >> 44 | %@+node:largo84.20170410130109.7: *3* << Packages >> 45 | \usepackage{color} 46 | \usepackage{amsmath} 47 | \usepackage{amssymb} 48 | \usepackage{stmaryrd} 49 | \usepackage{ifsym} 50 | \usepackage{MnSymbol} 51 | \usepackage{textcomp} 52 | \usepackage{verbatim} 53 | \usepackage{wasysym} 54 | \usepackage{ifpdf} %added to support checking to see if pdflatex is used 55 | \usepackage{hyperref} 56 | \usepackage{graphicx,color} % Necessary for adding images and other graphics. 57 | \usepackage{wrapfig} % This allows for wrapping text around images and figures. 58 | \graphicspath{C:/Images/} %Define path(s) to image files. 59 | \usepackage{array} %Extends the tabular environment 60 | \usepackage{xfrac} %Split level fractions \sfrac{}{} 61 | \usepackage[utf8]{inputenc} %Allows direct input of unicode characters, such as café. 62 | 63 | %@-<< Packages >> 64 | %@+<< Definitions >> 65 | %@+node:largo84.20170410130109.8: *3* << Definitions >> 66 | %@+others 67 | %@+node:largo84.20170410130109.9: *4* Sizes 68 | \setlength\answerlinelength{3in} 69 | \setlength\answerskip{0.1in} 70 | %\setlength\parindent{0in} 71 | %\setlength\parskip{1.5ex} 72 | %\setlength\parskip{6pt} 73 | \newcommand{\tinysbh}{.5in} %\sbh = solution box height (.5)=2 lines 74 | \newcommand{\smallsbh}{.75in} %\sbh = solution box height (.75)=3 lines 75 | \newcommand{\mediumsbh}{1in} %\sbh = solution box height (1)=4 lines 76 | \newcommand{\largesbh}{1.25in} %\sbh = solution box height (1.25)=5 lines 77 | \newcommand{\hugesbh}{1.5in} %\sbh = solution box height (1.5)=6 lines 78 | %@+node:largo84.20170410130109.10: *4* Exam Class Definitions 79 | \renewcommand{\solutiontitle}{\noindent{Facilitation Notes: }} 80 | 81 | \CorrectChoiceEmphasis{\color{red} \itshape} 82 | \SolutionEmphasis{\color{red} \itshape} 83 | \unframedsolutions % prevents solutions from being displayed inside an \fbox 84 | 85 | \newcommand\resetcheckboxchar{\checkboxchar{$\bigcirc$}} 86 | \resetcheckboxchar %Sets default display characters for check boxes. 87 | 88 | \newcommand\resetcheckedchar{\checkedchar{$\varoast$}} %varoast is a circle surroundeing an asterisk. 89 | \resetcheckedchar %Sets default display characters for correct choices on answer keys. 90 | 91 | \newcommand\truefalsecheckboxchar{\checkboxchar{\textbf{T or F}}} %Sets the default characters for True/False questions. 92 | \newcommand\truecheckedchar{\checkedchar{\textbf{T}}} %Sets the default character for True statements on answer keys. 93 | \newcommand\falsecheckboxchar{\checkboxchar{\textbf{F}}} %Sets the default character for False statements on answer keys. 94 | 95 | \checkboxchar{$\bigcirc$} %This displays an empty circle. 96 | \checkedchar{$\varoast$} %This displays a circle filled with an asterisk. 97 | %@-others 98 | 99 | %@-<< Definitions >> 100 | %@+<< Meta Info >> 101 | %@+node:largo84.20170410130109.11: *3* << Meta Info >> 102 | %@+at 103 | % Include at the beginning of all documents after document variables and global definitions. 104 | %@@c 105 | \hypersetup{ 106 | pdftitle={\theSchool : \docTitle}, 107 | pdfauthor={Author Name}, 108 | pdfsubject={\theSchool : \docTitle}, 109 | pdfkeywords={\keywords }, 110 | pdfstartview={Fit}, 111 | pdfpagelayout={SinglePage}, 112 | pdfdisplaydoctitle={True}, 113 | pdfpagemode={UseNone} %UseNone for no sidebar, UseOutlines to show bookmarks 114 | } 115 | 116 | \title{\theSchool : \docTitle} 117 | \author{Author Name} 118 | \date{Revised: \today} 119 | %@-<< Meta Info >> 120 | %@+<< Page Headers and Footers >> 121 | %@+node:largo84.20170410130109.12: *3* << Page Headers and Footers >> 122 | %@+at 123 | % Include this section as the last preamble before \begin{document} for all documents. 124 | % If there's a mismatch between this version and the 'global' version, use: 125 | % \input{"\TemplatePath Page\space Headers-Footers.txi"} 126 | %@@c 127 | %@+<< Page Style >> 128 | %@+node:largo84.20170410130109.13: *4* << Page Style >> 129 | \pagestyle{headandfoot} 130 | % \runningheadrule % Use this one to skip a rule on the first page. 131 | \headrule 132 | \footrule 133 | %@-<< Page Style >> 134 | %@+<< Header >> 135 | %@+node:largo84.20170410130109.14: *4* << Header >> 136 | \header 137 | {} %Left side header 138 | {\bfseries \large \docTitle} % Center header 139 | {} %Right side header 140 | %@-<< Header >> 141 | %@+<< Footer >> 142 | %@+node:largo84.20170410130109.15: *4* << Footer >> 143 | \footer 144 | %Left 145 | {\UDI (\REV )\\ \ifprintanswers \textcolor{red}{Facilitator Notes} \else \fi} % Left side includes the document identifier and revision date. 146 | %Center 147 | {\bfseries Footer Text \ifthenelse{\equal{\theSchoolShort}{XYZ}}{}{\\ \theSchool}} % Center includes `Footer Text` and the specific course name (if other than just XYZ). 148 | %Right 149 | {Page \thepage{} of \numpages} % Right side includes the paging information. 150 | %@-<< Footer >> 151 | %@-<< Page Headers and Footers >> 152 | %@-<< Document Variables >> 153 | 154 | \begin{document} 155 | \input{"\ResourcePath Reusable\space Content.txi"} 156 | \end{document} 157 | %@-leo 158 | -------------------------------------------------------------------------------- /examples/workflow/latex-pdf/Client/SGC-M110 Training Manual.tex: -------------------------------------------------------------------------------- 1 | %@+leo-ver=5-thin 2 | %@+node:largo84.20170410131222.2: * @file SGC-M110 Training Manual.tex 3 | %@@language tex 4 | \documentclass[addpoints,12pt,answers]{exam} %Use `,answers` for Facilitator and delete for Student version. 5 | %@+<< Paths >> 6 | %@+node:largo84.20170410131222.3: ** << Paths >> 7 | \newcommand{\MainPath}{C:/Main\space Document\space Path/} 8 | 9 | \newcommand{\TemplatePath}{\MainPath Templates/LaTex\space Documents/} 10 | \newcommand{\AgreementPath}{\MainPath Templates/Agreements/} 11 | \newcommand{\ResourcePath}{\MainPath Resources/} 12 | %@-<< Paths >> 13 | %@+<< Project Variables >> 14 | %@+node:largo84.20170410131222.4: ** << Project Variables >> 15 | \input{"Client\space Variables.txi"} % Sets the client name and other related client-specific commands. 16 | \newcommand{\theSchool}{The School Name} 17 | \newcommand{\theSchoolShort}{XYZ} % The short name of the class. Only change this line to reflect the correct school name. 18 | \newcommand{\theCourseShort}{ABC} % The short name of the online course. Only change this line to reflect the correct course name. 19 | %@-<< Project Variables >> 20 | %@+<< Document Variables >> 21 | %@+node:largo84.20170410131222.5: ** << Document Variables >> 22 | \newcommand{\UDI}{SGC-M110} % Unique Document Identifier (F=Form, H=Handout, Q=Test, Quiz or Exam, M=Manual, W=Worksheet, A=Article, C=Contract, S=Script) 23 | \newcommand{\docTitle}{Smith Grocery Training Manual} % ...to show up in the header 24 | \newcommand{\REV}{17.04.10} % Revision number (date), used in combination w/ the UDI as in: FSS-Q101(15.06.08) 25 | 26 | \newcommand{\keywords}{sales training manual \theSchoolShort} %This information will fill in the meta info for PDF creation. 27 | 28 | %@+<< Fonts >> 29 | %@+node:largo84.20170410131222.6: *3* << Fonts >> 30 | %@+at 31 | % Use this section to define document fonts. 32 | %@@c 33 | 34 | \usepackage[T1]{fontenc} 35 | \usepackage{gentium} %Gentium 36 | 37 | %\usepackage{quattrocento} %Quattrocento 38 | %\usepackage{mathpazo} %Palatino 39 | %\usepackage{lmodern} %Latin Modern 40 | %\usepackage{cm-super} %Computer Modern Super (don't use, it may not be installed.) 41 | 42 | %@-<< Fonts >> 43 | %@+<< Packages >> 44 | %@+node:largo84.20170410131222.7: *3* << Packages >> 45 | \usepackage{color} 46 | \usepackage{amsmath} 47 | \usepackage{amssymb} 48 | \usepackage{stmaryrd} 49 | \usepackage{ifsym} 50 | \usepackage{MnSymbol} 51 | \usepackage{textcomp} 52 | \usepackage{verbatim} 53 | \usepackage{wasysym} 54 | \usepackage{ifpdf} %added to support checking to see if pdflatex is used 55 | \usepackage{hyperref} 56 | \usepackage{graphicx,color} % Necessary for adding images and other graphics. 57 | \usepackage{wrapfig} % This allows for wrapping text around images and figures. 58 | \graphicspath{C:/Images/} %Define path(s) to image files. 59 | \usepackage{array} %Extends the tabular environment 60 | \usepackage{xfrac} %Split level fractions \sfrac{}{} 61 | \usepackage[utf8]{inputenc} %Allows direct input of unicode characters, such as café. 62 | 63 | %@-<< Packages >> 64 | %@+<< Definitions >> 65 | %@+node:largo84.20170410131222.8: *3* << Definitions >> 66 | %@+others 67 | %@+node:largo84.20170410131222.9: *4* Sizes 68 | \setlength\answerlinelength{3in} 69 | \setlength\answerskip{0.1in} 70 | %\setlength\parindent{0in} 71 | %\setlength\parskip{1.5ex} 72 | %\setlength\parskip{6pt} 73 | \newcommand{\tinysbh}{.5in} %\sbh = solution box height (.5)=2 lines 74 | \newcommand{\smallsbh}{.75in} %\sbh = solution box height (.75)=3 lines 75 | \newcommand{\mediumsbh}{1in} %\sbh = solution box height (1)=4 lines 76 | \newcommand{\largesbh}{1.25in} %\sbh = solution box height (1.25)=5 lines 77 | \newcommand{\hugesbh}{1.5in} %\sbh = solution box height (1.5)=6 lines 78 | %@+node:largo84.20170410131222.10: *4* Exam Class Definitions 79 | \renewcommand{\solutiontitle}{\noindent{Facilitation Notes: }} 80 | 81 | \CorrectChoiceEmphasis{\color{red} \itshape} 82 | \SolutionEmphasis{\color{red} \itshape} 83 | \unframedsolutions % prevents solutions from being displayed inside an \fbox 84 | 85 | \newcommand\resetcheckboxchar{\checkboxchar{$\bigcirc$}} 86 | \resetcheckboxchar %Sets default display characters for check boxes. 87 | 88 | \newcommand\resetcheckedchar{\checkedchar{$\varoast$}} %varoast is a circle surroundeing an asterisk. 89 | \resetcheckedchar %Sets default display characters for correct choices on answer keys. 90 | 91 | \newcommand\truefalsecheckboxchar{\checkboxchar{\textbf{T or F}}} %Sets the default characters for True/False questions. 92 | \newcommand\truecheckedchar{\checkedchar{\textbf{T}}} %Sets the default character for True statements on answer keys. 93 | \newcommand\falsecheckboxchar{\checkboxchar{\textbf{F}}} %Sets the default character for False statements on answer keys. 94 | 95 | \checkboxchar{$\bigcirc$} %This displays an empty circle. 96 | \checkedchar{$\varoast$} %This displays a circle filled with an asterisk. 97 | %@-others 98 | 99 | %@-<< Definitions >> 100 | %@+<< Meta Info >> 101 | %@+node:largo84.20170410131222.11: *3* << Meta Info >> 102 | %@+at 103 | % Include at the beginning of all 'Grafton' and DDI documents after document variables and global definitions. 104 | %@@c 105 | \hypersetup{ 106 | pdftitle={\theSchool : \docTitle}, 107 | pdfauthor={Rob Keeney}, 108 | pdfsubject={\theSchool : \docTitle}, 109 | pdfkeywords={\keywords }, 110 | pdfstartview={Fit}, 111 | pdfpagelayout={SinglePage}, 112 | pdfdisplaydoctitle={True}, 113 | pdfpagemode={UseNone} %UseNone for no sidebar, UseOutlines to show bookmarks 114 | } 115 | 116 | \title{\theSchool : \docTitle} 117 | \author{Rob Keeney} 118 | \date{Revised: \today} 119 | %@-<< Meta Info >> 120 | %@+<< Page Headers and Footers >> 121 | %@+node:largo84.20170410131222.12: *3* << Page Headers and Footers >> 122 | %@+at 123 | % Include this section as the last preamble before \begin{document} for all 'Grafton' and DDI documents. 124 | % If there's a mismatch between this version and the 'global' version, use: 125 | % \input{"\TemplatePath Grafton\space Page\space Headers-Footers.txi"} 126 | %@@c 127 | %@+<< Page Style >> 128 | %@+node:largo84.20170410131222.13: *4* << Page Style >> 129 | \pagestyle{headandfoot} 130 | % \runningheadrule % Use this one to skip a rule on the first page. 131 | \headrule 132 | \footrule 133 | %@-<< Page Style >> 134 | %@+<< Header >> 135 | %@+node:largo84.20170410131222.14: *4* << Header >> 136 | \header 137 | {} %Left side header 138 | {\bfseries \large \docTitle} % Center header 139 | {} %Right side header 140 | %@-<< Header >> 141 | %@+<< Footer >> 142 | %@+node:largo84.20170410131222.15: *4* << Footer >> 143 | \footer 144 | %Left 145 | {\UDI (\REV )\\ \ifprintanswers \textcolor{red}{Facilitator Notes} \else \fi} % Left side includes the document identifier and revision date. 146 | %Center 147 | {\bfseries Footer Text \ifthenelse{\equal{\theSchoolShort}{XYZ}}{}{\\ \theSchool}} % Center includes `Footer Text` and the specific course name (if other than just XYZ). 148 | %Right 149 | {Page \thepage{} of \numpages} % Right side includes the paging information. 150 | %@-<< Footer >> 151 | %@-<< Page Headers and Footers >> 152 | %@-<< Document Variables >> 153 | 154 | \begin{document} 155 | %@+<< Title Page >> 156 | %@+node:largo84.20170410131222.16: ** << Title Page >> 157 | %@+at 158 | % Add the following title page if desired. Otherwise, comment it out. 159 | %@@c 160 | 161 | \makeatletter 162 | \@addtoreset{section}{part} % This should reset the section counter when the part counter changes. 163 | \makeatother 164 | 165 | \pointsinrightmargin 166 | \maketitle 167 | \begin{center} %Add company logo. 168 | \includegraphics[width=0.5\textwidth]{logo.png} 169 | \end{center} 170 | \clearpage 171 | \tableofcontents 172 | \clearpage 173 | 174 | 175 | %@-<< Title Page >> 176 | %@+<< Document Body >> 177 | %@+node:largo84.20170410131222.17: ** << Document Body >> 178 | %@+others 179 | %@+node:largo84.20170410131404.2: *3* Reusable Content 180 | \section[Handout 1]{Content 1 \textcolor{red}{(SGC-H120)}} 181 | \label{sec:content1} 182 | 183 | \input{"\ResourcePath Reusable\space Content.txi"} %Inserts external content file. 184 | %@+node:largo84.20170410131504.2: *3* More Content 185 | \section[Handout 2]{More Content \textcolor{red}{(SGC-H130)}} 186 | \label{sec:content2} 187 | 188 | \input{"\ResourcePath More\space Content.txi"} %Inserts external content file. 189 | %@-others 190 | %@-<< Document Body >> 191 | \end{document} 192 | %@-leo 193 | -------------------------------------------------------------------------------- /examples/workflow/latex-pdf/Latex-PDF Workflow.leo: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | @settings 10 | @data abbreviations 11 | LaTex (General) 12 | Lists 13 | Text formatting 14 | Others 15 | 16 | LaTex (Exam Class) 17 | Questions and Parts 18 | Solution blocks 19 | Others 20 | 21 | 22 | @outline-data tree-abbreviations 23 | sec;; 24 | <|Section Name|> 25 | 26 | ssec;; 27 | <|SubSection Name|> 28 | 29 | sssec;; 30 | <|SubSubSection Name|> 31 | 32 | par;; 33 | <|Paragraph Title|> 34 | 35 | part;; 36 | <|Part Name|> 37 | 38 | texdoc;; 39 | @@file <|UDI|> <|Document|>.tex 40 | << Paths >> 41 | << Project Variables >> 42 | << Document Variables >> 43 | << Fonts >> 44 | << Packages >> 45 | << Definitions >> 46 | Sizes 47 | Exam Class Definitions 48 | 49 | << Meta Info >> 50 | << Page Headers and Footers >> 51 | << Page Style >> 52 | << Header >> 53 | << Footer >> 54 | 55 | 56 | 57 | 58 | texmanual;; 59 | @@file <|UDI|> <|Training Manual|>.tex 60 | 61 | 62 | 63 | << Title Page >> 64 | << Document Body >> 65 | 66 | 67 | 68 | 69 | Use Cases 70 | Workflow Overview 71 | Workflow Notes and Shortcuts 72 | Software Needed 73 | Document Example 74 | Resources 75 | (XXX-H120) Reusable Content 76 | @file Reusable Content.txi 77 | 78 | 79 | Smith Grocery Company (the client) 80 | @file SGC-H120 Reusable Content.tex 81 | @file SGC-M110 Training Manual.tex 82 | @file Client Variables.txi 83 | 84 | 85 | 86 | 87 | 88 | # Comments lines (lines starting with '#') are ignored. 89 | # Non-comment lines should have the form:: 90 | # 91 | # name=definition 92 | 93 | # Definitions in @data abbreviation nodes override definitions in @data 94 | # global-abbreviation nodes. Typically you would define @data abbreviation nodes 95 | # in myLeoSettings.leo 96 | 97 | 98 | 99 | enum;;=\begin{enumerate} 100 | \: \item <|Item1|> 101 | \: \item <|Item2|> 102 | \:\end{enumerate} 103 | \: 104 | 105 | itz;;=\begin{itemize} 106 | \: \item <|Item1|> 107 | \: \item <|Item|> 108 | \:\end{itemize} 109 | \: 110 | 111 | desc;;=\begin{quote} %Indents description block (optional). 112 | \: \begin{description} 113 | \: \item[<|Title1|>] <|Description1|> 114 | \: \item[<|Title2|>] <|Description2|> 115 | \: \end{description} 116 | \:\end{quote} 117 | \: 118 | 119 | item;;=\item <|Item|> 120 | itemd;;=\item[<|Title|>] <|Description|> 121 | 122 | qqs;;=\begin{questions} 123 | \: \question[<|Points|>] \label{<|Label-Text|>} <|Question-Text|> 124 | \:\end{questions} 125 | 126 | # The following abbreviations create a note-taking block filled with horizontal lines on the student version and facilitator notes for the teacher version. 127 | # The number of lines is determined by the size of the block ranging from tiny (2 lines) to huge (6 lines). 128 | # These are defined using the following global commands: 129 | # \newcommand{\tinysbh}{.5in} %\sbh = solution box height (.5)=2 lines 130 | # \newcommand{\smallsbh}{.75in} %\sbh = solution box height (.75)=3 lines 131 | # \newcommand{\mediumsbh}{1in} %\sbh = solution box height (1)=4 lines 132 | # \newcommand{\largesbh}{1.25in} %\sbh = solution box height (1.25)=5 lines 133 | # \newcommand{\hugesbh}{1.5in} %\sbh = solution box height (1.5)=6 lines 134 | 135 | sbht;;=\begin{solutionorlines}[\tinysbh] 136 | \: 137 | \: <|Facilitator Notes|> 138 | \:\end{solutionorlines} 139 | 140 | sbhs;;=\begin{solutionorlines}[\smallsbh] 141 | \: 142 | \: <|Facilitator Notes|> 143 | \:\end{solutionorlines} 144 | 145 | sbhm;;=\begin{solutionorlines}[\mediumsbh] 146 | \: 147 | \: <|Facilitator Notes|> 148 | \:\end{solutionorlines} 149 | 150 | sbhl;;=\begin{solutionorlines}[\largesbh] 151 | \: 152 | \: <|Facilitator Notes|> 153 | \:\end{solutionorlines} 154 | 155 | sbhh;;=\begin{solutionorlines}[\hugesbh] 156 | \: 157 | \: <|Facilitator Notes|> 158 | \:\end{solutionorlines} 159 | 160 | supermac;;=M\textsuperscript{<|ac|>}<|Name|> %Useful for names like MacDonald or McGinnis. 161 | 162 | super;;=\textsuperscript{<|th|>} %Useful for constructs such as 7th or 2nd. 163 | 164 | red;;=\textcolor{red}{<|Red-Text|>} 165 | blue;;=\textcolor{blue}{<|Blue-Text|>} 166 | 167 | bld;;=\textbf{<|BoldText|>} 168 | emph;;=\emph{<|EmphasizedText|>} 169 | 170 | cp;;=\clearpage 171 | \: 172 | 173 | box;;=\begin{center} 174 | \: \fbox{\fbox{\parbox{\textwidth}{<|Instructions placed inside a framed box.|>}}} 175 | \:\end{center} 176 | 177 | par;;=\paragraph{<|Paragraph-Title|>} <|Paragraph-Text|> 178 | # These LaTex constructs are unique to the exam document class and will not work in other document classes. 179 | 180 | # The following construct makes it easy to create different content depending on which output version is selected (facilitator or student). 181 | # This is easily determined in the document preamble as in: 182 | # \documentclass[addpoints,12pt,answers]{exam} for facilitator version 183 | # \documentclass[addpoints,12pt]{exam} for student version 184 | 185 | ifp;;=\ifprintanswers %Print for Facilitator only. 186 | \: <|Facilitator-Text|> 187 | \:\else %Print for Students only. 188 | \: <|Student-Text|> 189 | \:\fi 190 | \: 191 | sec;; 192 | ssec;; 193 | sssec;; 194 | part;; 195 | texdoc;; 196 | texmanual;; 197 | 198 | # Creates a child node for a LaTex section. 199 | \section[<|Index-Title|>]{<|Section-Name|>} 200 | \label{sec:<|Label-Text|>} 201 | 202 | \input{"<|Section File Name|>.txi"} %Inserts external content file. 203 | # Creates a child node for a LaTex subsection. 204 | \subsection<|Index-Title|>{<|Subsection-Name|>} 205 | \label{ssec:<|Label-Text|>} 206 | 207 | \input{"<|SubSection File Name|>.txi"} %Inserts external content file. 208 | 209 | # Creates a child node for a LaTex subsubsection. 210 | \subsubsection<|Index-Title|>{<|Subsubsection Name|>} 211 | \label{sssec:<|Label-Text|>} 212 | 213 | \input{"<|SubSubSection File Name|>.txi"} %Inserts external content file. 214 | 215 | # Creates a child node for LaTex part (doesn't work for every document class). 216 | \part<|Index-Title|>{<|Part-Name|>} 217 | \label{pt:<|Label-Text|>} 218 | 219 | \input{"<|Part File Name|>.txi"} %Inserts external content file. 220 | 221 | # Creates a new document 'shell' with variables. 222 | # Use this mostly for student versions only needing one (or a few) content files. 223 | @language tex 224 | \documentclass[addpoints,12pt<|,answers|>]{exam} %Use `,answers` for Facilitator and delete for Student version. 225 | << Paths >> 226 | << Project Variables >> 227 | << Document Variables >> 228 | 229 | \begin{document} 230 | \input{"\ResourcePath <|somefile|>.txi"} 231 | \end{document} 232 | 233 | # Creates a new document 'shell' with variables (eg. UDI is the Unique Document Identifier assigned to the specific version of this document). 234 | # Use this mostly for facilitatir versions that will contain multiple content files. 235 | @language tex 236 | \documentclass[addpoints,12pt<|,answers|>]{exam} %Use `,answers` for Facilitator and delete for Student version. 237 | << Paths >> 238 | << Project Variables >> 239 | << Document Variables >> 240 | 241 | \begin{document} 242 | << Title Page >> 243 | << Document Body >> 244 | \end{document} 245 | 246 | \newcommand{\MainPath}{C:/Main\space Document\space Path/} 247 | 248 | \newcommand{\TemplatePath}{\MainPath Templates/LaTex\space Documents/} 249 | \newcommand{\AgreementPath}{\MainPath Templates/Agreements/} 250 | \newcommand{\ResourcePath}{\MainPath Resources/} 251 | 252 | \input{"Client\space Variables.txi"} % Sets the client name and other related client-specific commands. 253 | \newcommand{\theSchoolShort}{XYZ} % The short name of the class. Only change this line to reflect the correct school name. 254 | \newcommand{\theCourseShort}{ABC} % The short name of the online course. Only change this line to reflect the correct course name. 255 | 256 | \newcommand{\UDI}{<|Prefix|>-<|M|><|123|>} % Unique Document Identifier (F=Form, H=Handout, Q=Test, Quiz or Exam, M=Manual, W=Worksheet, A=Article, C=Contract, S=Script) 257 | \newcommand{\docTitle}{<|Document Title|>} % ...to show up in the header 258 | \newcommand{\REV}{<|17.04.10|>} % Revision number (date), used in combination w/ the UDI as in: FSS-Q101(15.06.08) 259 | 260 | \newcommand{\keywords}{<|sales|> <|training|> <|handout|> \theSchoolShort} %This information will fill in the meta info for PDF creation. 261 | 262 | << Fonts >> 263 | << Packages >> 264 | << Definitions >> 265 | << Meta Info >> 266 | << Page Headers and Footers >> 267 | 268 | @ 269 | Use this section to define document fonts. 270 | @c 271 | 272 | \usepackage[T1]{fontenc} 273 | \usepackage{gentium} %Gentium 274 | 275 | %\usepackage{quattrocento} %Quattrocento 276 | %\usepackage{mathpazo} %Palatino 277 | %\usepackage{lmodern} %Latin Modern 278 | %\usepackage{cm-super} %Computer Modern Super (don't use, it may not be installed.) 279 | 280 | 281 | \usepackage{color} 282 | \usepackage{amsmath} 283 | \usepackage{amssymb} 284 | \usepackage{stmaryrd} 285 | \usepackage{ifsym} 286 | \usepackage{MnSymbol} 287 | \usepackage{textcomp} 288 | \usepackage{verbatim} 289 | \usepackage{wasysym} 290 | \usepackage{ifpdf} %added to support checking to see if pdflatex is used 291 | \usepackage{hyperref} 292 | \usepackage{graphicx,color} % Necessary for adding images and other graphics. 293 | \usepackage{wrapfig} % This allows for wrapping text around images and figures. 294 | \graphicspath{C:/Images/} %Define path(s) to image files. 295 | \usepackage{array} %Extends the tabular environment 296 | \usepackage{xfrac} %Split level fractions \sfrac{<num>}{<den>} 297 | \usepackage[utf8]{inputenc} %Allows direct input of unicode characters, such as café. 298 | 299 | 300 | @others 301 | 302 | 303 | \setlength\answerlinelength{3in} 304 | \setlength\answerskip{0.1in} 305 | %\setlength\parindent{0in} 306 | %\setlength\parskip{1.5ex} 307 | %\setlength\parskip{6pt} 308 | \newcommand{\tinysbh}{.5in} %\sbh = solution box height (.5)=2 lines 309 | \newcommand{\smallsbh}{.75in} %\sbh = solution box height (.75)=3 lines 310 | \newcommand{\mediumsbh}{1in} %\sbh = solution box height (1)=4 lines 311 | \newcommand{\largesbh}{1.25in} %\sbh = solution box height (1.25)=5 lines 312 | \newcommand{\hugesbh}{1.5in} %\sbh = solution box height (1.5)=6 lines 313 | 314 | \renewcommand{\solutiontitle}{\noindent{Facilitation Notes: }} 315 | 316 | \CorrectChoiceEmphasis{\color{red} \itshape} 317 | \SolutionEmphasis{\color{red} \itshape} 318 | \unframedsolutions % prevents solutions from being displayed inside an \fbox 319 | 320 | \newcommand\resetcheckboxchar{\checkboxchar{$\bigcirc$}} 321 | \resetcheckboxchar %Sets default display characters for check boxes. 322 | 323 | \newcommand\resetcheckedchar{\checkedchar{$\varoast$}} %varoast is a circle surroundeing an asterisk. 324 | \resetcheckedchar %Sets default display characters for correct choices on answer keys. 325 | 326 | \newcommand\truefalsecheckboxchar{\checkboxchar{\textbf{T or F}}} %Sets the default characters for True/False questions. 327 | \newcommand\truecheckedchar{\checkedchar{\textbf{T}}} %Sets the default character for True statements on answer keys. 328 | \newcommand\falsecheckboxchar{\checkboxchar{\textbf{F}}} %Sets the default character for False statements on answer keys. 329 | 330 | \checkboxchar{$\bigcirc$} %This displays an empty circle. 331 | \checkedchar{$\varoast$} %This displays a circle filled with an asterisk. 332 | 333 | @ 334 | Include at the beginning of all 'Grafton' and DDI documents after document variables and global definitions. 335 | @c 336 | \hypersetup{ 337 | pdftitle={\theSchool : \docTitle}, 338 | pdfauthor={Author Name}, 339 | pdfsubject={\theSchool : \docTitle}, 340 | pdfkeywords={\keywords }, 341 | pdfstartview={Fit}, 342 | pdfpagelayout={SinglePage}, 343 | pdfdisplaydoctitle={True}, 344 | pdfpagemode={UseNone} %UseNone for no sidebar, UseOutlines to show bookmarks 345 | } 346 | 347 | \title{\theSchool : \docTitle} 348 | \author{Author Name} 349 | \date{Revised: \today} 350 | 351 | @ 352 | Include this section as the last preamble before \begin{document} for all documents. 353 | If there's a mismatch between this version and the 'global' version, use: 354 | \input{"\TemplatePath Page\space Headers-Footers.txi"} 355 | @c 356 | << Page Style >> 357 | << Header >> 358 | << Footer >> 359 | 360 | \pagestyle{headandfoot} 361 | % \runningheadrule % Use this one to skip a rule on the first page. 362 | \headrule 363 | \footrule 364 | 365 | \header 366 | {} %Left side header 367 | {\bfseries \large \docTitle} % Center header 368 | {} %Right side header 369 | 370 | \footer 371 | %Left 372 | {\UDI (\REV )\\ \ifprintanswers \textcolor{red}{Facilitator Notes} \else \fi} % Left side includes the document identifier and revision date. 373 | %Center 374 | {\bfseries Footer Text \ifthenelse{\equal{\theSchoolShort}{XYZ}}{}{\\ \theSchool}} % Center includes `Footer Text` and the specific course name (if other than just XYZ). 375 | %Right 376 | {Page \thepage{} of \numpages} % Right side includes the paging information. 377 | 378 | @ 379 | Add the following title page if desired. Otherwise, comment it out. 380 | @c 381 | 382 | \makeatletter 383 | \@addtoreset{section}{part} % This should reset the section counter when the part counter changes. 384 | \makeatother 385 | 386 | \pointsinrightmargin 387 | \maketitle 388 | \begin{center} %Add company logo. 389 | \includegraphics[width=0.5\textwidth]{logo.png} 390 | \end{center} 391 | \clearpage 392 | \tableofcontents 393 | \clearpage 394 | 395 | 396 | 397 | \input{"\ResourcePath <|somefile|>.txi"} 398 | # Creates a child node for a LaTex paragraph. 399 | \paragraph{<|Paragraph-Title|>} <|Paragraph-Text|> 400 | 401 | * I teach a variety of classes in a business environment and need handouts, teaching aids, worksheets and training manuals that are specifically customized for each client. 402 | * These documents are easier to manage, print and protect using standard PDFs. 403 | * Content files (content.txi) are easily reusable for multiple clients. 404 | * Both student-specific and teacher-specific content can be included in the content files, differentiated by the output shell version. 405 | 1. Document content comes from a primary resource directory arranged by topic (not client specific). 406 | 2. Create a `Resources.leo` file to help keep that directory organized. 407 | 3. All of the content files are written in LaTex (I use a .txi file extension of my own invention to indicate the file is an 'input' file only, not the main output file which uses .tex). 408 | 4. Create a `Client.leo` file for each client in their own directory to organize work specific to each client. 409 | 5. For each document needed for a client project, create a `Document.tex` file from a standard template and change the document properties as needed for the specific client, project and document. 410 | 6. The `Document.tex` file acts as the presentation 'shell' for the document. 411 | 1. Simply add \input{"\ResourcePath Content.txi"} after the \begin{document} statement (\ResourcePath is a shortcut command to the location of the content resource). 412 | 2. This shell determines such things as the document title, document type, client name, header/footer information and revision date. 413 | 7. In Windows, use TeXNicCenter to process (typeset) the `Document.tex` file to create PDF output. (Don't use TeXNicCenter for editing, only file processing). 414 | 415 | 1. Years ago, I discovered the incredible exam class for LaTex and now use it almost exclusively. 416 | * It makes it much easier to create student and teacher versions of the same content (eg. handout for students and training manual with speaking notes for the teacher). 417 | * http://www-math.mit.edu/~psh/exam/examdoc.pdf 418 | 2. I use the new @outline-data tree-abbreviations in Leo to create each new `Document.tex` file from a template with variables (very cool!) 419 | * unl://#@settings:0-->@outline-data%20tree-abbreviations:1-->texdoc``:5 420 | 3. I created many @data abbreviations in Leo to speed up typing of standard LaTex structures. 421 | * unl://#@settings:0-->@data%20abbreviations:0 422 | 4. All document content stays in the Resources directory and only 'shell' documents are in the client directories. 423 | * These shell documents allow for client-specific information to be added to the headers, footers and in some cases as variables inside the content area itself (using \theClient variable that I define). 424 | 425 | * Leo (of course, and its dependencies). 426 | * MiKTex for the LaTeX distribution and package management (I have it set to auto-update as needed). 427 | * TeXNicCenter for processing (typesetting) to PDF output. 428 | @language tex 429 | 430 | This example shows a content file (Resources) and two document 'shells' (a student handout and a training manual) in the client directory. 431 | 432 | @path Resources/ 433 | 434 | This content may be used for a variety of clients. For example, Smith Grocery Company: 435 | Client Abbreviation: SGC 436 | \newcommand{\theClient}{Smith Grocery Company} %Defined in the document preamble. 437 | The document prepared for SGC will have a Unique Document Identifier (UDI) of SGC-H120 and a revision date in the footer as in: 438 | SGC-H120 (17.04.10) 439 | 440 | @path Client/ 441 | The following files were created with the @outline-data tree-abbreviations `texdoc` and `texmanual` abbreviations. 442 | 443 | 444 | 445 | -------------------------------------------------------------------------------- /examples/workflow/latex-pdf/Resources/Reusable Content.txi: -------------------------------------------------------------------------------- 1 | %@+leo-ver=5-thin 2 | %@+node:largo84.20170410124935.1: * @file Reusable Content.txi 3 | %@+others 4 | %@+node:largo84.20170410125206.1: ** First Paragraph 5 | \paragraph{First Paragraph} This is my first paragraph of content. I can add a reference to my client by using \theClient{} variable defined in the document preamble. 6 | 7 | \begin{solutionorlines}[\tinysbh] %\tinysbh is a prefined unit of length I create in the preamble. There are also \smallsbh, \medium...\large... and \huge... 8 | 9 | Facilitator notes for the first paragraph. Students only see a few blank lines they may use to take notes. 10 | \end{solutionorlines} 11 | %@+node:largo84.20170410125334.1: ** Second Paragraph 12 | \paragraph{Second Paragraph} This is my second paragraph of content. 13 | 14 | \begin{solutionorlines}[\hugesbh] 15 | 16 | Facilitator notes for my second paragraph. Students will only see blank lines for note taking. 17 | \end{solutionorlines} 18 | %@-others 19 | %@-leo 20 | -------------------------------------------------------------------------------- /experiments/cache-testing.leo: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | About this file 13 | test createOutlineFromCacheList @key=Alt-4 14 | test_node 15 | tuplifyCacheList 16 | make_outline_clone_1 17 | add_extra_node_to_cache_list 18 | make_outline_clone_2 19 | make_outline_clone_3 20 | make_outline_clone_4 21 | newgnx 22 | clear gnxDict 23 | recreate_node 24 | test1 25 | test2 26 | test3 27 | test4 28 | 29 | 30 | 31 | This file is meant to be a workbook for developing tests for 32 | leoCache module. 33 | 34 | I suspect that not all cases are considered when recreating clones 35 | from cache. There are following cases that need to be tested: 36 | 37 | 1. clone in cache has children with the same gnxes as clone in outline 38 | 2. clone in cache has at least one child that clone in outline hasn't 39 | 3. clone in outline has at least one child than clone in cache hasn't 40 | 4. clone in cache has at least one child that clone in outline hasn't and 41 | clone in outline has at least one child that clone in cache hasn't 42 | from contextlib import contextmanager 43 | import leo.core.leoNodes as leoNodes 44 | import pprint 45 | @others 46 | c.frame.log.selectTab('Log') 47 | c.frame.log.clearLog() 48 | test1(False) 49 | test2(False) 50 | test3(False) 51 | test4(True) 52 | @contextmanager 53 | def test_node(h, keep_node=False): 54 | p2 = c.p.copy() 55 | p1 = c.lastTopLevel().insertAfter() 56 | p1.h = h 57 | try: 58 | c.redraw() 59 | yield p1.copy() 60 | except Exception as e: 61 | g.es_error(e) 62 | finally: 63 | clearGnxDict() 64 | if not keep_node: 65 | p1.doDelete(p2) 66 | c.selectPosition(p2) 67 | c.redraw() 68 | def make_outline_clone_2(p): 69 | p1, cList = make_outline_clone_1(p) 70 | add_extra_node_to_cache_list(cList, newgnx(), 'extra', 'extra node in cache') 71 | return p1, cList 72 | def add_extra_node_to_cache_list(aList, gnx, h, b): 73 | aList2 = aList[-1] 74 | aList2.append([h, b, gnx, []]) 75 | def make_outline_clone_1(p): 76 | '''creates subtree like this: 77 | --C 78 | --A 79 | --B 80 | and returns (C-position, cache list) 81 | ''' 82 | p1 = p.insertAsLastChild() 83 | p1.h = 'C' 84 | p1.b = 'outline clone' 85 | cgnx = p1.gnx 86 | p2 = p1.insertAsLastChild() 87 | p2.h = 'A' 88 | p2.b = 'node A' 89 | p3 = p1.insertAsLastChild() 90 | p3.h = 'B' 91 | p3.b = 'node B' 92 | return p1, c.cacher.makeCacheList(p1) 93 | def make_outline_clone_3(p): 94 | p1, cList = make_outline_clone_1(p) 95 | aList = cList[-1] 96 | aList.pop() 97 | return p1, cList 98 | 99 | def make_outline_clone_4(p): 100 | '''creates subtree like this: 101 | --C 102 | --A 103 | --B 104 | --E 105 | and returns (C-position, cache list adjusted) 106 | ''' 107 | p1 = p.insertAsLastChild() 108 | p1.h = 'C' 109 | p1.b = 'outline clone' 110 | cgnx = p1.gnx 111 | p2 = p1.insertAsLastChild() 112 | p2.h = 'A' 113 | p2.b = 'node A' 114 | p3 = p1.insertAsLastChild() 115 | p3.h = 'B' 116 | p3.b = 'node B' 117 | cList = c.cacher.makeCacheList(p1) 118 | cList[-1].append(['D', 'extra node in cache', newgnx(), []]) 119 | p4 = p1.insertAsLastChild() 120 | p4.h = 'E' 121 | p4.b = 'extra node in outline' 122 | return p1, cList 123 | def tuplifyCacheList(aList): 124 | h, b, gnx, childList = aList 125 | children = tuple(tuplifyCacheList(x) for x in childList) 126 | return (h, b, gnx, children) 127 | def recreate_node(p, aList): 128 | aList1 = ['...', '', p.gnx, [aList]] 129 | c.cacher.createOutlineFromCacheList(p.v, aList1, 'dummy-file') 130 | aList2 = c.cacher.makeCacheList(p.firstChild()) 131 | return p.firstChild(), tuplifyCacheList(aList2) 132 | def test1(keep_node): 133 | with test_node('Case 1', keep_node) as p: 134 | p1, cList = make_outline_clone_1(p) 135 | v1_data = tuplifyCacheList(cList) 136 | p2, v2_data = recreate_node(p.insertAsLastChild(), cList) 137 | if v1_data != v2_data: 138 | g.es(pprint.pformat(v1_data) + '\n!=\n' + pprint.pformat(v2_data)) 139 | assert False, 'case_1 failed' 140 | else: 141 | g.es('case 1: ok') 142 | def test2(keep_node): 143 | with test_node('Case 2', keep_node) as p: 144 | p1, cList = make_outline_clone_2(p) 145 | v1_data = tuplifyCacheList(cList) 146 | p2, v2_data = recreate_node(p.insertAsLastChild(), cList) 147 | if v1_data != v2_data: 148 | g.es(pprint.pformat(v1_data) + '\n!=\n' + pprint.pformat(v2_data)) 149 | assert False, 'case_2 failed' 150 | else: 151 | g.es('case 2: ok') 152 | def clearGnxDict(): 153 | a = set(v.gnx for v in c.all_unique_nodes()) 154 | b = set(c.fileCommands.gnxDict.keys()) 155 | n = len(b - a) 156 | for gnx in (b - a): 157 | del c.fileCommands.gnxDict[gnx] 158 | if n > 0: 159 | g.es('cleared %d nodes'%n) 160 | NI = g.app.nodeIndices 161 | def newgnx(): 162 | t_s = NI.update() 163 | return g.toUnicode("%s.%s.%d" % (NI.userId, t_s, NI.lastIndex)) 164 | def test3(keep_node): 165 | with test_node('Case 3', keep_node) as p: 166 | p1, cList = make_outline_clone_3(p) 167 | v1_data = tuplifyCacheList(cList) 168 | p2, v2_data = recreate_node(p.insertAsLastChild(), cList) 169 | if v1_data != v2_data: 170 | g.es(pprint.pformat(v1_data) + '\n!=\n' + pprint.pformat(v2_data)) 171 | assert False, 'case_3 failed' 172 | else: 173 | g.es('case 3: ok') 174 | def test4(keep_node): 175 | with test_node('Case 4', keep_node) as p: 176 | p1, cList = make_outline_clone_4(p) 177 | v1_data = tuplifyCacheList(cList) 178 | p2, v2_data = recreate_node(p.insertAsLastChild(), cList) 179 | if v1_data != v2_data: 180 | g.es(pprint.pformat(v1_data) + '\n!=\n' + pprint.pformat(v2_data)) 181 | assert False, 'case_4 failed' 182 | else: 183 | g.es('case 4: ok') 184 | 185 | 186 | -------------------------------------------------------------------------------- /git_node_versioning.py: -------------------------------------------------------------------------------- 1 | from leo.extensions import sh 2 | from leo.extensions.sh import git 3 | import binascii 4 | 5 | GIT_DIR = "/home/tbrown/r/gittest/.git" 6 | 7 | git = git.bake(git_dir=GIT_DIR) 8 | 9 | hooks = 'select2', 'unselect2', 'save1', 'end1', 'after-create-leo-frame' 10 | 11 | if hasattr(c, 'node_change'): 12 | for i in hooks: 13 | try: 14 | g.unregisterHandler(i, c.node_change) 15 | except TypeError: 16 | pass # happens if it wasn't registered 17 | 18 | def node_change(tag, kwargs): 19 | 20 | if tag in ('select2', 'after-create-leo-frame'): 21 | v = kwargs['c'].p.v 22 | v._init_content = (v.h, v.b) 23 | return 24 | 25 | if tag == 'end1': 26 | p = c.p 27 | elif tag == 'save1': 28 | p = kwargs['p'] 29 | elif tag == 'unselect2': 30 | p = kwargs['old_p'] 31 | else: 32 | raise Exception("Unknown hook") 33 | 34 | v = p.v 35 | 36 | if not hasattr(v, '_init_content'): 37 | return 38 | 39 | if v._init_content != (v.h, v.b): 40 | 41 | node_data = "%s\n%s" % (v.h, v.b) 42 | 43 | # get previous HEAD 44 | try: 45 | head = git('rev-parse', 'HEAD').strip() 46 | except sh.ErrorReturnCode: 47 | head = None 48 | 49 | # get hash for data, and save data in db 50 | obj = git('hash-object', '-w', '--stdin', _in=node_data).strip() 51 | 52 | # add path using gnx as a path 53 | git('update-index', '--add', '--cacheinfo', '100755', obj, v.gnx) 54 | 55 | # add path using UNL as a path 56 | # have to use hex to make path safe for git 57 | safe_path = binascii.b2a_hex(p.get_UNL(with_file=False)) 58 | git('update-index', '--add', '--cacheinfo', '100755', obj, safe_path) 59 | 60 | # make a tree object from index 61 | tree = git("write-tree").strip() 62 | 63 | # commit tree object 64 | cmd = ['commit-tree', tree] 65 | if head: 66 | cmd += ['-p', head] 67 | commit = git(cmd, _in=p.get_UNL()).strip() 68 | 69 | # update HEAD 70 | git('reset', commit) 71 | 72 | # in case we were triggered by save1 or a explicit commit command 73 | v._init_content = (v.h, v.b) 74 | 75 | c.node_change = node_change 76 | for i in hooks: 77 | g.registerHandler(i, c.node_change) 78 | c.node_change('select2', dict(c=c, new_p=c.p, old_p=c.p, new_v=c.p.v, old_v=c.p.v)) 79 | -------------------------------------------------------------------------------- /introspect.py: -------------------------------------------------------------------------------- 1 | @language python 2 | """Introspect""" 3 | 4 | import types 5 | 6 | sub_mode = 'instance' 7 | # 'instance' or 'class' - controls which, instance or class names, 8 | # are put it a subnode. 'instance class' sub-nodes both. 9 | # '' appends classes after names, not useful. 10 | 11 | def classname(thing): 12 | if hasattr(thing, '__class__'): 13 | return thing.__class__.__name__ 14 | else: 15 | return thing.__name__ 16 | 17 | if not hasattr(c.p.v, '_introspection_target'): 18 | txt = g.app.gui.runAskOkCancelStringDialog( 19 | c, "Introspect what", "Introspect what") 20 | if txt is not None: 21 | o = eval(txt) 22 | c.p.v._introspection_target = o 23 | c.p.h = "%s %s" % (txt, classname(o)) 24 | 25 | # c.p.deletePositionsInList([i.copy() for i in p.children()]) 26 | 27 | obj = c.p.v._introspection_target 28 | g.es(classname(obj)) 29 | 30 | def show_obj(c, obj): 31 | 32 | inames = sorted(dir(obj)) 33 | 34 | things = {} 35 | instances = [] 36 | for iname in inames: 37 | 38 | if iname.startswith('__'): 39 | continue 40 | 41 | o = getattr(obj, iname) 42 | cname = classname(o) 43 | instances.append((iname, o)) 44 | things.setdefault(cname, []).append(instances[-1]) 45 | 46 | if 'instance' in sub_mode: 47 | tnd = c.p.v.insertAsNthChild(0) 48 | tnd.h = "" 49 | else: 50 | tnd = c.p.v 51 | 52 | instances.sort() 53 | for iname, o in instances: 54 | 55 | if classname(o) == 'position': 56 | # apparently this collapses the space-time continuum? 57 | continue 58 | 59 | nd = tnd.insertAsLastChild() 60 | 61 | if not seen_already(tnd, nd, iname, o): 62 | nd.h = "%s %s" % (iname, format_type(nd, o)) 63 | nd._introspection_target = o 64 | 65 | if 'class' in sub_mode: 66 | ttnd = c.p.v.insertAsNthChild(0) 67 | ttnd.h = "" 68 | else: 69 | ttnd = c.p.v 70 | 71 | for cname in sorted(things): 72 | 73 | if len(things[cname]) == 1: 74 | tnd = ttnd 75 | else: 76 | tnd = ttnd.insertAsLastChild() 77 | tnd.h = "<%s>"%cname 78 | 79 | for iname, o in sorted(things[cname]): 80 | 81 | if cname == 'position': 82 | # apparently this collapses the space-time continuum? 83 | continue 84 | 85 | nd = tnd.insertAsLastChild() 86 | if not seen_already(tnd, nd, iname, o): 87 | show_child(nd, iname, o) 88 | nd._introspection_target = o 89 | 90 | def seen_already(tnd, nd, iname, o): 91 | 92 | up = tnd.parents 93 | while up: 94 | if (hasattr(up[0], '_introspection_target') and 95 | up[0]._introspection_target is o): 96 | break 97 | up = up[0].parents 98 | else: 99 | return False 100 | 101 | nd.h = "[%s %s]" % (classname(o), iname) 102 | pos = c.vnode2position(up[0]) 103 | nd.b = pos.get_UNL(with_file=True, with_proto=True) 104 | 105 | return True 106 | 107 | def show_child(nd, iname, o): 108 | 109 | nd._introspection_target = o 110 | nd.h = "%s %s" % (format_type(nd, o), iname) 111 | 112 | docable = ( 113 | types.ClassType, types.MethodType, types.UnboundMethodType, 114 | types.BuiltinFunctionType, types.BuiltinMethodType, 115 | ) 116 | 117 | def format_type(nd, o): 118 | 119 | if isinstance(o, docable): 120 | if hasattr(o, '__doc__'): 121 | nd.b = o.__doc__ 122 | 123 | if isinstance(o, (str, unicode)): 124 | nd.b = o 125 | return "%s '%s'" % (classname(o), o[:20]) 126 | elif isinstance(o, bool): 127 | return "%s %s" % (classname(o), 'T' if o else 'F') 128 | elif isinstance(o, (int, float)): 129 | return "%s %s" % (classname(o), o) 130 | elif isinstance(o, (tuple, list, dict)): 131 | return "%s %s" % (classname(o), len(o)) 132 | else: 133 | return classname(o) 134 | 135 | def show_list(c, list_): 136 | 137 | if len(list_) > 100: 138 | nd = c.p.v.insertAsLastChild() 139 | nd.h = "<%s of %d items truncated>" % len(list_.__class__.__name__, list_) 140 | 141 | if len(list_) == 0: 142 | nd = c.p.v.insertAsLastChild() 143 | nd.h = "<%s of 0 items>" % list_.__class__.__name__ 144 | 145 | for n, i in enumerate(list_[:100]): 146 | nd = c.p.v.insertAsLastChild() 147 | show_child(nd, '', i) 148 | nd.h = "%d: %s" % (n, nd.h) 149 | nd._introspection_target = i 150 | 151 | def show_dict(c, dict_): 152 | 153 | if len(dict_) > 100: 154 | nd = c.p.v.insertAsLastChild() 155 | nd.h = "" % len(dict_) 156 | 157 | if len(dict_) == 0: 158 | nd = c.p.v.insertAsLastChild() 159 | nd.h = "" 160 | 161 | keys = dict_.keys() 162 | keys.sort() 163 | 164 | for k in keys[:100]: 165 | nd = c.p.v.insertAsLastChild() 166 | i = dict_[k] 167 | show_child(nd, '', i) 168 | nd.h = "%s: %s" % (k, nd.h) 169 | nd._introspection_target = i 170 | 171 | dispatch = { 172 | list: show_list, 173 | tuple: show_list, 174 | dict: show_dict, 175 | } 176 | 177 | func = dispatch.get(type(obj), show_obj) 178 | 179 | func(c, obj) 180 | 181 | c.p.expand() 182 | c.redraw() 183 | -------------------------------------------------------------------------------- /issue_attach/32/MindjetScript.py: -------------------------------------------------------------------------------- 1 | #Your currently selected node will be replaced with the outline you are importing. 2 | #Replace the path with the path of your file: 3 | 4 | file_path = 'D:/exmample/mymindmap.csv' 5 | 6 | max_chars_in_header = 80 #Nodes with more characters than that (or with line jumps) will be inserted as bodies instead of as header. 7 | 8 | #INSTRUCTIONS FOR EXPORTING FROM MINDJET: You need a csv file exported from MindJet. When exporting, make sure you: 9 | # - Use the layout "outline" 10 | # - Tick all the options but "inlcude topic properties" 11 | 12 | 13 | import csv 14 | 15 | def get_row_level(row): 16 | count = 0 17 | while count<=len(row): 18 | if row[count]: 19 | return count+1 20 | else: 21 | count = count+1 22 | return -1 23 | 24 | def get_row_string(row): 25 | count = 0 26 | while count<=len(row): 27 | if row[count]: 28 | return row[count] 29 | else: 30 | count = count+1 31 | return None 32 | 33 | f = open(file_path) 34 | 35 | reader = csv.reader(f) 36 | initial_level = c.p.level() 37 | last_created_level = c.p.level() 38 | last_created_node = c.p.copy() 39 | 40 | 41 | for row in list(reader)[1:]: 42 | new_level = get_row_level(row) + initial_level 43 | get_row_string(row) 44 | if new_level > last_created_level: 45 | last_created_node = last_created_node.insertAsLastChild().copy() 46 | last_created_node.b = get_row_string(row) 47 | last_created_level = last_created_level+1 48 | elif new_level == last_created_level: 49 | last_created_node = last_created_node.insertAfter().copy() 50 | last_created_node.b = get_row_string(row) 51 | elif new_level < last_created_level: 52 | 53 | for item in last_created_node.parents(): 54 | if item.level() == new_level-1: 55 | last_created_node = item.copy() 56 | break 57 | 58 | last_created_node = last_created_node.insertAsLastChild().copy() 59 | last_created_node.b = get_row_string(row) 60 | last_created_level = last_created_node.level() 61 | 62 | for p in c.p.unique_subtree(): 63 | if len(p.b.splitlines())==1: 64 | if len(p.b.splitlines()[0]) 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /issue_attach/32/PLE_(Personal_Learning_Environment).mm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PLE (Personal Learning Environment) 5 | 17 | 18 | 19 | 20 |

PLE (Personal Learning Environment) 21 |

  • Outside university 22 |
    • Listening 23 |
      • Foreign friends 24 | 25 |
      • 26 |
      • Travelling 27 | 28 |
      • 29 |
      • Podcasts 30 | 31 |
      • 32 |
      • BBC news 33 | 34 |
      • 35 |
      • Movies 36 | 37 |
      • 38 |
      • Songs 39 | 40 |
      • 41 | 42 |
      43 |
    • 44 |
    • Reading 45 |
      • Newspaper articles 46 | 47 |
      • 48 |
      • Books (novels, plays) 49 | 50 |
      • 51 |
      • Blogs 52 | 53 |
      • 54 |
      • Websites 55 | 56 |
      • 57 |
      • E-mails from foreign friends 58 | 59 |
      • 60 | 61 |
      62 |
    • 63 |
    • Speaking 64 |
      • Foreign friends (face to face, Skype) 65 | 66 |
      • 67 |
      • Travelling 68 | 69 |
      • 70 | 71 |
      72 |
    • 73 |
    • Writing 74 |
      • E-mails to friends 75 | 76 |
      • 77 |
      • Chatting (MSN) 78 | 79 |
      • 80 |
      • Comments to blogs 81 | 82 |
      • 83 | 84 |
      85 |
    • 86 | 87 |
    88 |
  • 89 |
  • At university 90 |
    • Listening   91 |
      • Podcasts 92 | 93 |
      • 94 |
      • YouTube videos (tutorial videos about the "e-tivities") 95 | 96 |
      • 97 |
      • Native speakers during class (Sarah) 98 | 99 |
      • 100 |
      • My peers during class 101 | 102 |
      • 103 | 104 |
      105 |
    • 106 |
    • Reading 107 |
      • YouTube forum 108 | 109 |
      • 110 |
      • Padova-Albany forum 111 | 112 |
      • 113 |
      • My peers' blogs 114 | 115 |
      • 116 |
      • My peers' comments 117 | 118 |
      • 119 |
      • Other blogs on the Internet 120 | 121 |
      • 122 |
      • Websites 123 | 124 |
      • 125 |
      • Newspaper articles 126 | 127 |
      • 128 | 129 |
      130 |
    • 131 |
    • Speaking 132 |
      • Class: discussions in small groups, debates 133 | 134 |
      • 135 | 136 |
      137 |
    • 138 |
    • Writing 139 |
      • YouTube forum 140 | 141 |
      • 142 |
      • Posts on my blog 143 | 144 |
      • 145 |
      • Comments to my peers and to other blogs I subscribed to 146 | 147 |
      • 148 |
      • Padova-Albany forum 149 | 150 |
      • 151 | 152 |
      153 |
    • 154 | 155 |
    156 |
  • 157 |
  • TOOLS 158 |
    • Storing and sharing material 159 |
      • Del.icio.us (Social Bookmarking) 160 | 161 |
      • 162 |
      • Bloglines (Feed aggregators) 163 | 164 |
      • 165 | 166 |
      167 |
    • 168 |
    • Checking 169 |
      • Dictionaries (Monolingual and Bilingual) 170 | 171 |
      • 172 |
      • Google (advanced search) 173 | 174 |
      • 175 |
      • Grammar books 176 | 177 |
      • 178 |
      • Spell-checker 179 | 180 |
      • 181 | 182 |
      183 |
    • 184 | 185 |
    186 |
  • 187 | 188 |
189 | 190 | -------------------------------------------------------------------------------- /issue_attach/32/TED - 10 Most Popular Talks.csv: -------------------------------------------------------------------------------- 1 | "Level 1","Level 2","Level 3","Level 4","Priority","Progress","Start","Due","Duration (Days)","Milestone","Resources", 2 | "#1 Schools kill creativity",,,,,,,,,,, 3 | ,"We must educate for an unpredictable future.",,,,,,,,,, 4 | ,"Ken Robinson",,,,,,,,,, 5 | ,"February 2006",,,,,,,,,, 6 | ,"Creativity = the new literacy",,,,,,,,,, 7 | ,"Be prepared to be wrong",,,,,,,,,, 8 | ,"Intelligence",,,,,,,,,, 9 | ,,"Diverse",,,,,,,,, 10 | ,,"Dynamic",,,,,,,,, 11 | ,,"Distinct",,,,,,,,, 12 | ,"Albert Einstein",,,,,,,,,, 13 | ,,"""Imagination is more important than knowledge.""",,,,,,,,, 14 | "#2 A stroke of insight",,,,,,,,,,, 15 | ,"Brain researcher examines her own brain function.",,,,,,,,,, 16 | ,"Jill Bolte Taylor ",,,,,,,,,, 17 | ,"February 2008",,,,,,,,,, 18 | ,"Brain hemispheres",,,,,,,,,, 19 | ,,"Right",,,,,,,,, 20 | ,,,"Parallel processor",,,,,,,, 21 | ,,,"Present",,,,,,,, 22 | ,,,"Right here",,,,,,,, 23 | ,,,"Right now",,,,,,,, 24 | ,,"Left",,,,,,,,, 25 | ,,,"Serial processor",,,,,,,, 26 | ,,,"Past/Future",,,,,,,, 27 | ,,,"Linear",,,,,,,, 28 | ,,,"Methodical",,,,,,,, 29 | ,"Nirvana = La La Land ",,,,,,,,,, 30 | "#3 The thrilling potential of SixthSense technology",,,,,,,,,,, 31 | ,"Shrinking the gap between 32 | the physical and digital worlds.",,,,,,,,,, 33 | ,"Pranav Mistry ",,,,,,,,,, 34 | ,"November 2009",,,,,,,,,, 35 | ,"Helps us to become more human",,,,,,,,,, 36 | ,"Interfaces of the future",,,,,,,,,, 37 | ,"For the masses",,,,,,,,,, 38 | "#4 How to live before you die ",,,,,,,,,,, 39 | ,"Pursue your dreams and see opportunities in life's setbacks.",,,,,,,,,, 40 | ,"Steve Jobs",,,,,,,,,, 41 | ,"June 2005",,,,,,,,,, 42 | ,"Connecting the dots",,,,,,,,,, 43 | ,,"Can't by looking forward",,,,,,,,, 44 | ,,"Only by looking back",,,,,,,,, 45 | ,"Love & Loss",,,,,,,,,, 46 | ,,"Love what you do",,,,,,,,, 47 | ,,"Don't lose faith",,,,,,,,, 48 | ,,"Don't settle",,,,,,,,, 49 | ,"Death",,,,,,,,,, 50 | ,,"Leaves only what is truly important",,,,,,,,, 51 | ,,"A destination we all share",,,,,,,,, 52 | ,,"Makes way for the new",,,,,,,,, 53 | ,"Your time is limited so don't waste it living someone else's life.",,,,,,,,,, 54 | ,"Stay hungry, stay foolish",,,,,,,,,, 55 | "#5 Underwater astonishments",,,,,,,,,,, 56 | ,"Only 3% of the oceans have been explored.",,,,,,,,,, 57 | ,"David Gallo ",,,,,,,,,, 58 | ,"March 2007",,,,,,,,,, 59 | ,"Artistic creatures",,,,,,,,,, 60 | ,,"Bioluminescence",,,,,,,,, 61 | ,,"Cephalopod",,,,,,,,, 62 | ,,,"Pattern",,,,,,,, 63 | ,,,"Color",,,,,,,, 64 | ,,,"Brightness",,,,,,,, 65 | ,,,"Texture",,,,,,,, 66 | "#6 SixthSense demonstrated",,,,,,,,,,, 67 | ,"Demonstration of interaction between physical and digital worlds.",,,,,,,,,, 68 | ,"Patti Maes ",,,,,,,,,, 69 | ,"February 2009",,,,,,,,,, 70 | ,"Off-the-shelf components",,,,,,,,,, 71 | ,"Natural gestures",,,,,,,,,, 72 | ,"Personal preferences",,,,,,,,,, 73 | "#7 How great leaders inspire action",,,,,,,,,,, 74 | ,"""People don't buy what you do, 75 | they buy why you do it.""",,,,,,,,,, 76 | ,"September 2009",,,,,,,,,, 77 | ,"Simon Sinek ",,,,,,,,,, 78 | ,"Inside out thinking",,,,,,,,,, 79 | ,,"Why",,,,,,,,, 80 | ,,,"Purpose",,,,,,,, 81 | ,,,"Cause",,,,,,,, 82 | ,,,"Belief",,,,,,,, 83 | ,,"How",,,,,,,,, 84 | ,,"What",,,,,,,,, 85 | ,"Outside in thinking",,,,,,,,,, 86 | ,,"What",,,,,,,,, 87 | ,,"How",,,,,,,,, 88 | ,,"Why",,,,,,,,, 89 | ,"Mark Twain",,,,,,,,,, 90 | ,,"""The two most important days in your life are the day you were born and the day you find out why.""",,,,,,,,, 91 | "#8 The power of vulnerability",,,,,,,,,,, 92 | ,"Connection is what gives purpose and meaning to our lives.",,,,,,,,,, 93 | ,"Brene Brown",,,,,,,,,, 94 | ,"June 2010",,,,,,,,,, 95 | ,"Stories",,,,,,,,,, 96 | ,,"""Data with a soul""",,,,,,,,, 97 | ,"""If you can't measure it, it doesn't exist""",,,,,,,,,, 98 | ,"Whole-hearted",,,,,,,,,, 99 | ,,"Courage",,,,,,,,, 100 | ,,"Compassion",,,,,,,,, 101 | ,,"Connection",,,,,,,,, 102 | ,,,"Purpose",,,,,,,, 103 | ,,,"Meaning",,,,,,,, 104 | ,,,"Sense of worthiness",,,,,,,, 105 | ,"Shame",,,,,,,,,, 106 | ,,"Fear of disconnection",,,,,,,,, 107 | ,"Vulnerability",,,,,,,,,, 108 | ,,"Birthplace of",,,,,,,,, 109 | ,,,"Joy",,,,,,,, 110 | ,,,"Creativity",,,,,,,, 111 | ,,,"Belonging",,,,,,,, 112 | ,,,"Love",,,,,,,, 113 | ,,"Numb with",,,,,,,,, 114 | ,,,"Indebtedness",,,,,,,, 115 | ,,,"Obesity",,,,,,,, 116 | ,,,"Addiction",,,,,,,, 117 | ,,,"Medication",,,,,,,, 118 | ,,"Let our true selves be seen",,,,,,,,, 119 | " #9 ""Mathemagic"" ",,,,,,,,,,, 120 | ,"Mathematical wizardry",,,,,,,,,, 121 | ,"Art Benjamin ",,,,,,,,,, 122 | ,"February 2005",,,,,,,,,, 123 | " #10 The best stats you have ever seen",,,,,,,,,,, 124 | ,"Analyzing world statistics",,,,,,,,,, 125 | ,"Hans Rosling ",,,,,,,,,, 126 | ,"February 2006",,,,,,,,,, 127 | ,"Gapminder.org",,,,,,,,,, 128 | ,"Link design to data",,,,,,,,,, 129 | ,,"Animate",,,,,,,,, 130 | ,,"Liberate",,,,,,,,, 131 | ,,"Search",,,,,,,,, 132 | "Author: Jim Lauria Date: June 30, 2012 Contact: www.jimlauria.com",,,,,,,,,,, 133 | "TOP 10 TEDTalks",,,,,,,,,,, 134 | -------------------------------------------------------------------------------- /issue_attach/32/TED - 10 Most Popular Talks.mmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leo-editor/snippets/622f9eba4f40c976a4bfd10fdecc0404e15c3206/issue_attach/32/TED - 10 Most Popular Talks.mmap -------------------------------------------------------------------------------- /issue_attach/32/freemindScript.py: -------------------------------------------------------------------------------- 1 | @language python 2 | #Your currently selected node will be replaced with the outline you are importing. 3 | #Replace the path with the path of your file: 4 | 5 | file_path = 'D:/exmample/mymindmap.mm.html' 6 | 7 | max_chars_in_header = 80 #Nodes with more characters than that (or with line jumps) will be inserted as bodies instead of as header. 8 | 9 | #INSTRUCTIONS FOR EXPORTING FROM FREEMIND: Export your outline into html, then you can use this script. 10 | 11 | import lxml.html 12 | 13 | global outline_dict 14 | 15 | outline_dict = {} 16 | 17 | global count 18 | count = 0 19 | 20 | def element_to_node(parent_node, element ): 21 | global count 22 | global outline_dict 23 | count = count+1 24 | my_actual_count = int(count) 25 | 26 | if len(list(element.iterchildren())): 27 | # if len(list(list(element.iterchildren())[0].iterchildren())): 28 | 29 | outline_dict[str(my_actual_count)] = {} 30 | outline_dict[parent_node]['children'].append(str(my_actual_count)) 31 | if len(list(list(element.iterchildren())[0].iterchildren())): 32 | outline_dict[str(my_actual_count)]['string'] = list(list(element.iterchildren())[0].iterchildren())[0].text 33 | else: 34 | outline_dict[str(my_actual_count)]['string'] = list(element.iterchildren())[0].text 35 | outline_dict[str(my_actual_count)]['children'] = list() 36 | if len(list(element.iterchildren()))>1: 37 | for child in list(element.iterchildren())[1].iterchildren(): 38 | element_to_node(str(my_actual_count),child) 39 | 40 | htmltree = lxml.html.parse(file_path) 41 | 42 | root = htmltree.getroot() 43 | 44 | body = root.findall('body')[0] 45 | outline_dict[str(0)] = {} 46 | outline_dict[str(0)]['children'] = list() 47 | outline_dict[str(count)]['string'] = list(list(body.iterchildren())[0].iterchildren())[0].text 48 | outline_dict[str(count)]['children'] = list() 49 | 50 | for item in list(list(body.iterchildren())[1].iterchildren()): 51 | 52 | element_to_node(str(0), item ) 53 | 54 | 55 | 56 | 57 | #Up to here, the script was for importing into a dict. Now we export that dict into nodes: 58 | 59 | def add_children_as_nodes(node_id_to_add, parent_id): 60 | 61 | global outline_dict 62 | if c.p.h == parent_id: 63 | my_parent = c.p.copy() 64 | else: 65 | for node in c.p.unique_subtree(): 66 | if node.h == parent_id: 67 | my_parent = node.copy() 68 | 69 | 70 | 71 | newchild = my_parent.insertAsLastChild().copy() 72 | newchild.h = node_id_to_add 73 | newchild.b = outline_dict[node_id_to_add]['string'] 74 | 75 | for child_id in outline_dict[node_id_to_add]['children']: 76 | add_children_as_nodes(child_id, node_id_to_add) 77 | 78 | 79 | c.p.h = str(0) 80 | c.p.b = outline_dict['0']['string'] 81 | for child_id in outline_dict['0']['children']: 82 | add_children_as_nodes(child_id, '0') 83 | 84 | for p in c.p.unique_subtree(): 85 | if len(p.b.splitlines())==1: 86 | if len(p.b.splitlines()[0]) import ->" 12 | Because when a new user comes to Leo, he most likely comes from any other 13 | of the most famous outliners (IE freemind or mindjet). 14 | So it will feel very good to import their mindmaps with two clicks. I know 15 | for sure I would have loved it. 16 | 17 | For importing your MindJet mindmap, first you have to export it to CSV, 18 | using the following options: 19 | - Select the 'outline' layout when exporting 20 | - Tick all the options but the 'inlcude topic properties' 21 | 22 | Now you are ready to import your mindmap to Leo with this script. Copy it 23 | and paste it into a node, replace the path with your mindmap path, and 24 | execute the script (with the hotkey control + B): 25 | 26 | #Your currently selected node will be replaced with the outline you are 27 | importing. 28 | #Replace the path with the path of your file: 29 | 30 | 31 | file_path = 'D:/exmample/mymindmap.csv' 32 | 33 | 34 | max_chars_in_header = 80 #Nodes with more characters than that (or with 35 | line jumps) will be inserted as bodies instead of as header. 36 | 37 | 38 | #INSTRUCTIONS FOR EXPORTING FROM MINDJET: You need a csv file exported from 39 | MindJet. When exporting, make sure you: 40 | # - Use the layout "outline" 41 | # - Tick all the options but "inlcude topic properties" 42 | 43 | 44 | 45 | 46 | import csv 47 | 48 | 49 | def get_row_level(row): 50 | count = 0 51 | while count<=len(row): 52 | if row[count]: 53 | return count+1 54 | else: 55 | count = count+1 56 | return -1 57 | 58 | 59 | def get_row_string(row): 60 | count = 0 61 | while count<=len(row): 62 | if row[count]: 63 | return row[count] 64 | else: 65 | count = count+1 66 | return None 67 | 68 | 69 | reader = csv.reader(open(file_path)) 70 | 71 | 72 | initial_level = c.p.level() 73 | last_created_level = c.p.level() 74 | last_created_node = c.p.copy() 75 | 76 | 77 | 78 | 79 | for row in list(reader)[1:]: 80 | new_level = get_row_level(row) + initial_level 81 | get_row_string(row) 82 | if new_level > last_created_level: 83 | last_created_node = last_created_node.insertAsLastChild().copy() 84 | last_created_node.b = get_row_string(row) 85 | last_created_level = last_created_level+1 86 | elif new_level == last_created_level: 87 | last_created_node = last_created_node.insertAfter().copy() 88 | last_created_node.b = get_row_string(row) 89 | elif new_level < last_created_level: 90 | 91 | for item in last_created_node.parents(): 92 | if item.level() == new_level-1: 93 | last_created_node = item.copy() 94 | break 95 | 96 | 97 | last_created_node = last_created_node.insertAsLastChild().copy() 98 | last_created_node.b = get_row_string(row) 99 | last_created_level = last_created_node.level() 100 | 101 | for p in c.p.unique_subtree(): 102 | if len(p.b.splitlines())==1: 103 | if len(p.b.splitlines()[0])1: 170 | for child in list(element.iterchildren())[1].iterchildren(): 171 | element_to_node(str(my_actual_count),child) 172 | 173 | 174 | htmltree = lxml.html.parse(file_path) 175 | 176 | 177 | root = htmltree.getroot() 178 | 179 | 180 | body = root.findall('body')[0] 181 | outline_dict[str(0)] = {} 182 | outline_dict[str(0)]['children'] = list() 183 | outline_dict[str(count)]['string'] = list(list(body.iterchildren())[0]. 184 | iterchildren())[0].text 185 | outline_dict[str(count)]['children'] = list() 186 | 187 | 188 | for item in list(list(body.iterchildren())[1].iterchildren()): 189 | 190 | element_to_node(str(0), item ) 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | #Up to here, the script was for importing into a dict. Now we export that 199 | dict into nodes: 200 | 201 | def add_children_as_nodes(node_id_to_add, parent_id): 202 | 203 | 204 | global outline_dict 205 | 206 | 207 | for node in c.p.unique_subtree(): 208 | if node.h == parent_id: 209 | my_parent = node.copy() 210 | 211 | newchild = my_parent.insertAsLastChild().copy() 212 | newchild.h = node_id_to_add 213 | newchild.b = outline_dict[node_id_to_add]['string'] 214 | 215 | for child_id in outline_dict[node_id_to_add]['children']: 216 | add_children_as_nodes(child_id, node_id_to_add) 217 | 218 | 219 | 220 | c.p.h = str(0) 221 | c.p.b = outline_dict['0']['string'] 222 | for child_id in outline_dict['0']['children']: 223 | add_children_as_nodes(child_id, '0') 224 | 225 | 226 | for p in c.p.unique_subtree(): 227 | if len(p.b.splitlines())==1: 228 | if len(p.b.splitlines()[0])= c._typing_idle['next']: 32 | # wait for another keystroke 33 | c._typing_idle['next'] = None 34 | 35 | # code to do stuff here 36 | g.es('do stuff') 37 | 38 | # keep a reference to the handler function 39 | c._typing_idle['handler'] = handler 40 | g.registerHandler(hooks, c._typing_idle['handler']) 41 | -------------------------------------------------------------------------------- /past-due-report.py: -------------------------------------------------------------------------------- 1 | @language python 2 | 3 | ''' 4 | prints a report to the log pane about tasks that are past due and not marked as 'done' 5 | ''' 6 | 7 | import datetime 8 | 9 | today = datetime.date.today() 10 | n = [] 11 | for p in c.all_positions(): 12 | duedate = c.cleo.getat(p.v, 'duedate') 13 | priority = c.cleo.getat(p.v, 'priority') 14 | if priority: priority = int(priority) 15 | if (duedate and duedate < today 16 | and priority != 100 and p.v not in n): 17 | n.append(p.v) 18 | 19 | if len(n) == 0: 20 | g.es('No tasks past due.',color='blue') 21 | else: 22 | g.es('The following tasks are past due:',color='red') 23 | for v in n: 24 | g.es(c.vnode2position(v).get_UNL(with_file=False, with_proto=False),color='red') 25 | -------------------------------------------------------------------------------- /rich_text_editor.py: -------------------------------------------------------------------------------- 1 | from PyQt4 import QtGui, QtWebKit, QtCore, Qt 2 | from PyQt4.QtCore import Qt as QtConst 3 | from collections import OrderedDict 4 | import sys 5 | 6 | if sys.version_info.major == 3: 7 | toUtf8 = lambda x: x 8 | else: 9 | toUtf8 = lambda x: x.toUtf8() 10 | 11 | 12 | # create Rich Text layout if it doesn't exist 13 | if 'ns_layouts' not in g.app.db: 14 | g.app.db['ns_layouts'] = {} 15 | if 'Rich Text' not in g.app.db['ns_layouts']: 16 | g.app.db['ns_layouts']['Rich Text'] = { 17 | 'content': [ 18 | {'content': ['_leo_pane:outlineFrame', '_leo_pane:logFrame'], 19 | 'orientation': 2, 20 | 'sizes': [538, 221] 21 | }, 22 | '_add_rte_pane'], 23 | 'orientation': 1, 24 | 'sizes': [339, 1033] 25 | } 26 | 27 | # create Default layout if it doesn't exist 28 | if 'Default' not in g.app.db['ns_layouts']: 29 | g.app.db['ns_layouts']['Default'] = { 30 | 'content': [ 31 | {'content': ['_leo_pane:outlineFrame', '_leo_pane:logFrame'], 32 | 'orientation': 2, 33 | 'sizes': [573, 186] 34 | }, 35 | '_leo_pane:bodyFrame'], 36 | 'orientation': 1, 37 | 'sizes': [390, 982] 38 | } 39 | 40 | @g.command('rich-text-open') 41 | def rich_text_open(kwargs): 42 | c.free_layout.get_top_splitter().load_layout( 43 | g.app.db['ns_layouts']['Rich Text']) 44 | 45 | @g.command('rich-text-close') 46 | def rich_text_close(kwargs): 47 | c.free_layout.get_top_splitter().load_layout( 48 | g.app.db['ns_layouts']['Default']) 49 | 50 | 51 | class RTEEditor(QtGui.QWidget): 52 | 53 | def __init__(self, *args, **kwargs): 54 | self.c = kwargs['c'] 55 | del kwargs['c'] 56 | QtGui.QWidget.__init__(self, *args, **kwargs) 57 | self.setLayout(QtGui.QVBoxLayout()) 58 | self.layout().setSpacing(0) 59 | self.layout().setContentsMargins(0,0,0,0) 60 | buttons = QtGui.QWidget() 61 | buttons.setLayout(QtGui.QHBoxLayout()) 62 | buttons.layout().setSpacing(0) 63 | buttons.layout().setContentsMargins(0,0,0,0) 64 | self.layout().addWidget(buttons) 65 | 66 | actions = [ 67 | # text, name, Ctrl- 68 | ['Clr', 'clear', QtConst.Key_Space], 69 | ['B', 'bold', QtConst.Key_B], 70 | ['I', 'italic', QtConst.Key_I], 71 | ['U', 'underline', QtConst.Key_U], 72 | ['---', 'strikeout', QtConst.Key_Underscore], 73 | ['FG', 'foreground', None], 74 | ['BG', 'background', None], 75 | ['Font', 'font', None], 76 | ['+', 'larger', QtConst.Key_Plus], 77 | ['-', 'smaller', QtConst.Key_Minus], 78 | ] 79 | self.key_to_style = {} 80 | 81 | for action in actions: 82 | 83 | # record these for use in key stroke event 84 | key = action[2] 85 | if key: 86 | self.key_to_style[key] = action[1] 87 | 88 | btn = QtGui.QPushButton(action[0]) 89 | buttons.layout().addWidget(btn) 90 | btn.clicked.connect( 91 | lambda checked, style=action[1]: self.set_style(style)) 92 | 93 | self.styles = OrderedDict([ 94 | ("Paragraph", ['clear']), 95 | ("Heading 1", ['bold', ('larger', 20)]), 96 | ("Heading 2", ['bold', 'italic', ('larger', 16)]), 97 | ("Heading 3", ['bold', 'italic', ('larger', 12), ('foreground', '#444')]), 98 | ("Warning", ['bold', ('foreground', '#f00')]), 99 | ("Latin", ['italic']), 100 | ("Literal", [('background', '#ccc')]), 101 | ]) 102 | 103 | # style picker 104 | style_menu = QtGui.QComboBox() 105 | buttons.layout().addWidget(style_menu) 106 | style_menu.addItems([i for i in self.styles.keys()]) 107 | style_menu.currentIndexChanged.connect(self.apply_style) 108 | 109 | # auto save checkbox 110 | self.auto = QtGui.QCheckBox("Auto") 111 | buttons.layout().addWidget(self.auto) 112 | 113 | # close button 114 | btn = QtGui.QPushButton('X') 115 | btn.clicked.connect(self.close) 116 | buttons.layout().addWidget(btn) 117 | 118 | buttons.layout().addStretch(1) 119 | 120 | self.te = QtGui.QTextEdit() 121 | self.layout().addWidget(self.te) 122 | 123 | g.registerHandler('select3', self.select_node) 124 | g.registerHandler('unselect1', self.unselect_node) 125 | self.init_format = self.te.currentCharFormat() 126 | 127 | def handle_keys(event, te=self.te, rte=self): 128 | 129 | if (event.key() == QtConst.Key_Return and 130 | event.modifiers() & QtConst.ControlModifier): 131 | # Ctrl-Return - save edits 132 | self.c.p.b = toUtf8(self.te.toHtml()) 133 | self.init_html = self.c.p.b 134 | elif (event.key() == QtConst.Key_Down and 135 | event.modifiers() & QtConst.AltModifier): 136 | # Alt-Down - select next node 137 | self.c.selectVisNext() 138 | elif (event.key() == QtConst.Key_Up and 139 | event.modifiers() & QtConst.AltModifier): 140 | # Alt-Down - select prev node 141 | self.c.selectVisBack() 142 | elif (event.key() in rte.key_to_style and 143 | event.modifiers() & QtConst.ControlModifier): 144 | self.set_style(rte.key_to_style[event.key()]) 145 | else: 146 | return QtGui.QTextEdit.keyReleaseEvent(te, event) 147 | 148 | event.accept() 149 | 150 | self.te.keyReleaseEvent = handle_keys 151 | 152 | self.te.setFocus(QtConst.OtherFocusReason) 153 | 154 | # Call select_node hook to record initial content so we know 155 | # if it's edited and needs saving when another node's selected. 156 | # Why the QTimer? When the event loop unfolds the HTML content 157 | # changes very slightly ('16px' -> '10px'), perhaps due to 158 | # style being applied, so it appears as if we edited the content. 159 | # QTimer lets us capture the final HTML and only detect real edits. 160 | QtCore.QTimer.singleShot(0, 161 | lambda: self.select_node('', {'c': self.c, 'new_p': self.c.p})) 162 | 163 | def close(self): 164 | if self.c: 165 | # save changes? 166 | self.unselect_node('', {'c': self.c, 'old_p': self.c.p}) 167 | # restore Default layout with regular body pane 168 | QtCore.QTimer.singleShot(0, lambda c=self.c: 169 | c.free_layout.get_top_splitter().load_layout( 170 | g.app.db['ns_layouts']['Default'])) 171 | self.c = None 172 | g.unregisterHandler('select3', self.select_node) 173 | g.unregisterHandler('unselect1', self.unselect_node) 174 | return QtGui.QWidget.close(self) 175 | 176 | def select_node(self, tag, kwargs): 177 | c = kwargs['c'] 178 | if c != self.c: 179 | return 180 | p = kwargs['new_p'] 181 | 182 | if p.b.startswith('%s"%p.b) 186 | self.init_html = toUtf8(self.te.toHtml()) 187 | 188 | def unselect_node(self, tag, kwargs): 189 | c = kwargs['c'] 190 | if c != self.c: 191 | return 192 | if toUtf8(self.te.toHtml()) != self.init_html: 193 | 194 | if self.auto.isChecked(): 195 | ans = 'yes' 196 | else: 197 | ans = g.app.gui.runAskYesNoCancelDialog( 198 | self.c, 199 | "Save edits?", 200 | "Save edits?" 201 | ) 202 | if ans == 'yes': 203 | kwargs['old_p'].b = toUtf8(self.te.toHtml()) 204 | elif ans == 'cancel': 205 | return 'STOP' 206 | else: 207 | pass # discard edits 208 | 209 | def set_style(self, style, value=None): 210 | format = QtGui.QTextCharFormat() 211 | 212 | if style == 'bold': 213 | format.setFontWeight(QtGui.QFont.Bold) 214 | elif style == 'italic': 215 | format.setFontItalic(True) 216 | elif style == 'underline': 217 | format.setFontUnderline(True) 218 | elif style == 'strikeout': 219 | format.setFontStrikeOut(True) 220 | elif style in ('foreground', 'background'): 221 | if not value: 222 | cp = QtGui.QColorDialog() 223 | if cp.exec_() != QtGui.QDialog.Rejected: 224 | color = cp.selectedColor() 225 | else: 226 | color = None 227 | else: 228 | color = QtGui.QColor(value) 229 | if color and style == 'foreground': 230 | format.setForeground(color) 231 | elif color: 232 | format.setBackground(color) 233 | elif style == 'font': 234 | fd = QtGui.QFontDialog() 235 | if fd.exec_() != QtGui.QDialog.Rejected: 236 | font = fd.selectedFont() 237 | else: 238 | font = None 239 | if font: 240 | format.setFont(font) 241 | elif style in ('larger', 'smaller'): 242 | 243 | if not value: 244 | size = self.te.textCursor().charFormat().fontPointSize() 245 | if style == 'larger': 246 | size += 1 247 | else: 248 | size -= 1 249 | else: 250 | size = value 251 | size = max(2, min(100, size)) 252 | format.setFontPointSize(size) 253 | 254 | if style == 'clear': 255 | self.te.textCursor().setCharFormat(self.init_format) 256 | self.te.setCurrentCharFormat(self.init_format) 257 | else: 258 | self.te.textCursor().mergeCharFormat(format) 259 | self.te.mergeCurrentCharFormat(format) 260 | 261 | def apply_style(self, n): 262 | style = self.styles[[i for i in self.styles.keys()][n]] 263 | for attr in style: 264 | if isinstance(attr, tuple): 265 | self.set_style(attr[0], attr[1]) 266 | else: 267 | self.set_style(attr) 268 | 269 | 270 | class RTEPaneProvider: 271 | ns_id = '_add_rte_pane' 272 | def __init__(self, c): 273 | self.c = c 274 | # Careful: we may be unit testing. 275 | if hasattr(c, 'free_layout'): 276 | splitter = c.free_layout.get_top_splitter() 277 | if splitter: 278 | splitter.register_provider(self) 279 | def ns_provides(self): 280 | return[('Rich text editor', self.ns_id)] 281 | def ns_provide(self, id_): 282 | if id_ == self.ns_id: 283 | w = RTEEditor(c=self.c) 284 | return w 285 | def ns_provider_id(self): 286 | # used by register_provider() to unregister previously registered 287 | # providers of the same service 288 | return self.ns_id 289 | 290 | RTEPaneProvider(c) 291 | 292 | # if hasattr(c, 'free_layout'): 293 | # splitter = c.free_layout.get_top_splitter() 294 | # if splitter: 295 | # splitter.open_window(action=RTEPaneProvider.ns_id) 296 | -------------------------------------------------------------------------------- /rst2html_node.py: -------------------------------------------------------------------------------- 1 | from docutils.core import publish_string, publish_parts 2 | 3 | # make a new node 4 | nd = p.insertAfter() 5 | 6 | # include '@rich' in the node headline so it's viewed in the HTML 7 | # editor if richtext plugin is enabled, so we need to say "@norich" 8 | # here to avoid the button's code being shown in HTML mode 9 | 10 | nd.h = 'HTML: %s @rich' % (p.h) 11 | 12 | # either of these works, the first is complete HTML, the second just 13 | # body parts (ewww) 14 | 15 | # nd.b = publish_string(p.b, writer_name='html') 16 | 17 | nd.b = publish_parts(p.b, writer_name='html')['html_body'] 18 | 19 | c.selectPosition(nd) 20 | c.redraw() 21 | -------------------------------------------------------------------------------- /scripts/misc/get_tips.py: -------------------------------------------------------------------------------- 1 | """ 2 | get_tips.py - download current tips from GitHub 3 | 4 | NOTE: if the list of tips gets long enough, GitHub may stop serving them 5 | all in a single request, and serve them page by page - not hard to 6 | handle if it happens. 7 | 8 | Terry Brown, terrynbrown@gmail.com, Thu Dec 7 09:10:54 2017 9 | """ 10 | 11 | from collections import namedtuple 12 | 13 | import requests 14 | 15 | Tip = namedtuple('Tip', 'title tip tags') 16 | 17 | URL = "https://api.github.com/repos/leo-editor/leo-editor/issues?labels=Tip&state=closed" 18 | 19 | # for dev., use open tips 20 | URL = URL.replace('closed', 'open') 21 | 22 | def get_tips(data): 23 | """get_tips - get tips from GitHub issues 24 | 25 | :param dict data: GitHub API issues list 26 | :return: list of Tips 27 | """ 28 | tips = [] 29 | for issue in data: 30 | if '\n' in issue['body']: 31 | tip, tags = issue['body'].strip().rsplit('\n', 1) 32 | else: 33 | tip, tags = issue['body'].strip(), '' 34 | if tags.lower().startswith('tags:'): 35 | tags = [i.strip().strip('.') for i in tags[5:].split(',')] 36 | else: 37 | tags = [] 38 | tip = "%s\n%s" % (tip, tags) 39 | tips.append(Tip(issue['title'], tip, tags)) 40 | return tips 41 | 42 | 43 | def main(): 44 | data = requests.get(URL).json() 45 | for tip in get_tips(data): 46 | print('-'*40) 47 | print("%s\n%s\n%s\n" % ( 48 | tip.title, tip.tags or "**NO TAGS FOR TIP**", tip.tip)) 49 | 50 | if __name__ == '__main__': 51 | main() 52 | -------------------------------------------------------------------------------- /scripts/output/outline2text.py: -------------------------------------------------------------------------------- 1 | # template is everything between r""" and second """ 2 | # placeholders are H heading B body C children, T ToDo priority 3 | # use \n in B and C lines for conditional blank lines 4 | from leo.core.leoQt import QtGui 5 | 6 | try: # Python 2/3 compatibility 7 | unicode 8 | except NameError: 9 | unicode = str 10 | 11 | template = r"""T H 12 | B 13 | * C""" 14 | 15 | lines=[] 16 | exp_only = g.app.gui.runAskYesNoCancelDialog( 17 | c, 'Expanded nodes only?', 'Expanded nodes only?') 18 | if exp_only == 'no': 19 | exp_only = False 20 | 21 | def export_text(p, indent=''): 22 | 23 | spaces = u' '*(len(indent) - len(indent.lstrip(' '))) 24 | 25 | for line in template.split('\n'): 26 | line = unicode(line) 27 | if 'T' in line: 28 | pri = int(c.cleo.getat(p.v, 'priority') or 0) 29 | if pri: 30 | if pri <= 19: 31 | if pri == 19: 32 | pri = '( )' 33 | else: 34 | pri = '(%d)' % pri 35 | elif pri == 21: 36 | pri = '(X)' 37 | elif pri == 100: 38 | pri = '(/)' 39 | else: 40 | pri = '*' 41 | 42 | char = pri # .decode('utf-8') 43 | line = line.replace('T', char) 44 | 45 | if 'H' in line: 46 | lines.append(indent + line.replace('H', p.h)) 47 | elif 'B' in line and p.script.strip(): 48 | text = p.script.strip() 49 | prefix = line[:line.find('B')].replace('\\n', '\n') 50 | for i in text.split('\n'): 51 | lines.append(spaces + prefix + i) 52 | prefix = line[:line.find('B')].replace('\\n', '') 53 | if line.endswith('\\n'): 54 | lines.append('') 55 | elif 'C' in line and (not exp_only or p.isExpanded()): 56 | prefix = line[:line.find('C')].replace('\\n', '\n') 57 | for child in p.children(): 58 | export_text(child, indent=spaces + prefix) 59 | if line.endswith('\\n'): 60 | lines.append('') 61 | elif 'C' not in line and 'B' not in line: 62 | lines.append(line) 63 | 64 | if exp_only != 'cancel': 65 | for i in c.getSelectedPositions(): 66 | export_text(i) 67 | # c.leo_screen.show('\n'.join(lines), 'text', plain=True) 68 | QtGui.QApplication.clipboard().setText('\n'.join(lines)) 69 | g.es("Text copied to clipboard") 70 | 71 | filename = g.app.gui.runSaveFileDialog(c, 'Save to file') 72 | 73 | if filename: 74 | txt = '\n'.join(lines) # .encode('utf-8') 75 | open(filename,'w').write(txt) 76 | -------------------------------------------------------------------------------- /utils/diff_trees.py: -------------------------------------------------------------------------------- 1 | """ 2 | Notes 2017-6-15: this is old code and may not be inserting the output 3 | into the outline in quite the right way. 4 | 5 | Select two nodes - click the first the ctrl-click the second. Then run 6 | this code. It ignores differences in the two top nodes, but recursively 7 | lists differences in their descendant trees. 8 | 9 | The result tree is tagged with @bookmarks and has UNL links to the different 10 | nodes so double clicking nodes in the result tree jumps you to the original 11 | nodes. 12 | 13 | A bug seems to be %20 escaping the text of differing nodes. 14 | """ 15 | 16 | from leo.core.leoNodes import vnode 17 | if not hasattr(vnode, 'insertAsLastChild'): 18 | # add insertAsLastChild method to vnodes 19 | def ialc(self): 20 | vnode(self.context)._linkAsNthChild(self, len(self.children)) 21 | return self.children[-1] 22 | vnode.insertAsLastChild = ialc 23 | 24 | pos0, pos1 = c.getSelectedPositions()[:2] 25 | 26 | vf = pos0.v 27 | vt = pos1.v 28 | 29 | nd = c.rootPosition().insertAfter() 30 | nd.copy().back().moveAfter(nd) 31 | nd.h = 'diff @bookmarks' 32 | 33 | def text_match(a, b): 34 | return (a.h == b.h, 35 | a.h == b.h and a.b == b.b) 36 | def text_match(a, b): 37 | return (a.h == b.h, 38 | a.h == b.h and ' '.join(a.b.split()) == ' '.join(b.b.split())) 39 | def gnx_match(a, b): 40 | return (a.h == b.h and a.gnx == b.gnx, 41 | a.h == b.h and a.b == b.b and a.gnx == b.gnx) 42 | 43 | def diff_trees(vf, vt, path): 44 | 45 | fonly = [] # nodes only in from tree 46 | tonly = [] # nodes only in to tree 47 | diffs = [] # nodes which occur in both but have different descendants 48 | 49 | # count number of times each headline occurs as a child of 50 | # each node being compared 51 | count_f = {} 52 | for cf in vf.children: 53 | count_f[cf.h] = count_f.get(cf.h, 0) + 1 54 | count_t = {} 55 | for ct in vt.children: 56 | count_t[ct.h] = count_t.get(ct.h, 0) + 1 57 | 58 | for cf in vf.children: 59 | 60 | for ct in vt.children: 61 | 62 | if count_f[cf.h] == 1 and count_t[ct.h] == 1: 63 | equal = text_match 64 | else: 65 | equal = gnx_match 66 | 67 | head_eq, body_eq = equal(cf, ct) 68 | 69 | if body_eq: 70 | diffs.append(diff_trees(cf, ct, path+[vf.h])) 71 | 72 | break 73 | elif head_eq: 74 | d = diff_trees(cf, ct, path+[vf.h]) 75 | if d: 76 | d.h = '!v '+d.h 77 | else: 78 | d = vnode(nd.v.context) 79 | d.h = '!v '+cf.h 80 | d.b = "#%s\n\n%s" % ( 81 | '-->'.join((path+[vf.h]+[cf.h])[1:]), 82 | cf.b 83 | ) 84 | diffs.append(d) 85 | d = vnode(nd.v.context) 86 | d.h = '!^ '+cf.h 87 | d.b = "#%s\n\n%s" % ( 88 | '-->'.join((path+[vt.h]+[ct.h])[1:]), 89 | ct.b 90 | ) 91 | d.b = d.b.replace(' ', '%20') 92 | diffs.append(d) 93 | break 94 | else: 95 | fonly.append(cf) 96 | 97 | for ct in vt.children: 98 | 99 | for cf in vf.children: 100 | 101 | if count_f[cf.h] == 1 and count_t[ct.h] == 1: 102 | equal = text_match 103 | else: 104 | equal = gnx_match 105 | 106 | head_eq, body_eq = equal(cf, ct) 107 | if head_eq or body_eq: 108 | # no need to recurse matches again 109 | break 110 | 111 | else: 112 | tonly.append(ct) 113 | 114 | if not any(diffs) and not fonly and not tonly: 115 | return None 116 | 117 | vd = vnode(nd.v.context) 118 | vd.h = vf.h 119 | for d in diffs: 120 | if d: 121 | vd.children.append(d) 122 | for f in fonly: 123 | n = vd.insertAsLastChild() 124 | n.h = '- '+f.h 125 | n.b = "#%s" % ('-->'.join((path+[vf.h]+[f.h])[1:])) 126 | n.b = n.b.replace(' ', '%20') 127 | for t in tonly: 128 | n = vd.insertAsLastChild() 129 | n.h = '+ '+t.h 130 | n.b = "#%s" % ('-->'.join((path+[vf.h]+[t.h])[1:])) 131 | n.b = n.b.replace(' ', '%20') 132 | 133 | return vd 134 | 135 | parent = c.vnode2position(vt).parent() 136 | path = [] 137 | while parent: 138 | path = [parent.h] + path 139 | parent = parent.parent() 140 | path = ['TOP'] + path 141 | 142 | v = diff_trees(vf, vt, path) 143 | if v: 144 | nd.v.children.extend(v.children) # snip off 145 | 146 | c.bringToFront() 147 | c.redraw() 148 | -------------------------------------------------------------------------------- /utils/led.py: -------------------------------------------------------------------------------- 1 | # NOTE: this is obsolete, see led.sh instead (leoremote / lproto is deprecated) 2 | 3 | #!/usr/bin/python 4 | """ 5 | Load a file in a running Leo session from the command line. 6 | 7 | The folder containing the "leo" folder has to be on the Python 8 | path, and you need the leoremote.py plugin enabled in Leo. 9 | 10 | usage: led [-h] [--ask] [--use USE] filename 11 | 12 | positional arguments: 13 | filename File to load 14 | 15 | optional arguments: 16 | -h, --help show this help message and exit 17 | --ask Ask which Leo outline to use (default: False) 18 | --use USE edit, auto, shadow, etc. for @edit etc. node (default: None) 19 | """ 20 | 21 | import argparse 22 | import sys 23 | from leo.external import lproto 24 | import os 25 | import time 26 | 27 | def make_parser(): 28 | 29 | parser = argparse.ArgumentParser( 30 | description="""Load file from command line into Leo""", 31 | formatter_class=argparse.ArgumentDefaultsHelpFormatter 32 | ) 33 | 34 | parser.add_argument("--ask", action='store_true', 35 | help="Ask which Leo outline to use" 36 | ) 37 | parser.add_argument('--use', type=str, 38 | help="edit, auto, shadow, etc. for @edit etc. node" 39 | ) 40 | parser.add_argument('filename', type=str, 41 | help="File to load" 42 | ) 43 | 44 | return parser 45 | 46 | opt = make_parser().parse_args() 47 | if not opt.use: 48 | if opt.filename.endswith(".py"): 49 | opt.use = 'auto' 50 | else: 51 | opt.use = 'edit' 52 | 53 | addr = open(os.path.expanduser('~/.leo/leoserv_sockname')).read() 54 | pc = lproto.LProtoClient(addr) 55 | 56 | c_n = 0 # use first commander 57 | if opt.ask: 58 | pc.send(""" 59 | out = open("/tmp/clist", 'w') 60 | for n,c in enumerate(g.app.commanders()): 61 | # print("%d: %s\\n"%(n,c.fileName())) 62 | out.write("%d: %s\\n"%(n,c.fileName())) 63 | out.close() 64 | """) 65 | time.sleep(1) # wait for file to close - presumably pc.send() 66 | # is asynchronous 67 | print open("/tmp/clist").read() 68 | 69 | print 'Which outline:', 70 | c_n = input() 71 | 72 | pc.send(""" 73 | import os 74 | fn = {filename!r} 75 | c = g.app.commanders()[{which}] 76 | h = "@{type} "+fn 77 | n = g.findNodeAnywhere(c, h) 78 | if not n: 79 | e = g.findTopLevelNode(c, 'Edits') 80 | if not e: 81 | e = c.rootPosition().insertAfter() 82 | e.h = 'Edits' 83 | n = e.insertAsNthChild(0) 84 | c.selectPosition(n) 85 | n.h = h 86 | c.k.simulateCommand("refresh-from-disk") 87 | else: 88 | c.selectPosition(n) 89 | c.redraw() 90 | c.bringToFront() 91 | """.format( 92 | filename=os.path.join(os.getcwd(), opt.filename), 93 | which=c_n, 94 | type=opt.use, 95 | )) 96 | -------------------------------------------------------------------------------- /utils/led.sh: -------------------------------------------------------------------------------- 1 | # Load a file in a running Leo session from the command line. 2 | 3 | TYPE=@auto 4 | 5 | if [ "$1" == "--ask" ]; then 6 | curl --silent --show-error http://localhost:8130/_/exec/commanders/ | cat -n 7 | echo 8 | shift 9 | read -p "Select outline: " WHICH 10 | WHICH=$[WHICH-1] 11 | else 12 | WHICH=0 13 | fi 14 | 15 | if [ "$1" == "--use" ]; then 16 | shift 17 | TYPE="$1" 18 | shift 19 | fi 20 | 21 | FILE="$1" 22 | 23 | docmd () { 24 | CMD="curl --silent --show-error --get --data-urlencode c=$WHICH" 25 | for i in "$@"; do 26 | CMD="$CMD --data-urlencode \"cmd=$i\"" 27 | done 28 | CMD="$CMD http://localhost:8130/_/exec/" 29 | eval $CMD 30 | } 31 | 32 | HEAD="$TYPE $FILE" 33 | 34 | nd=$(docmd "nd = g.findNodeAnywhere(c, '$HEAD')" "bool(nd)") 35 | if [ "$nd" == 'False' -o "$nd" == 'None' ]; then 36 | nd=$(docmd "nd = g.findTopLevelNode(c, 'Edits')" "bool(nd)") 37 | if [ "$nd" == 'False' -o "$nd" == 'None' ]; then 38 | docmd "nd = c.rootPosition().insertAfter()" "nd.h = 'Edits'" 39 | fi; 40 | docmd "nd = nd.insertAsNthChild(0)" \ 41 | "nd.h = '$HEAD'" \ 42 | "c.selectPosition(nd)" \ 43 | "c.k.simulateCommand('refresh-from-disk')" >/dev/null 44 | fi; 45 | docmd "c.selectPosition(nd)" "c.redraw()" >/dev/null 46 | -------------------------------------------------------------------------------- /utils/pytest_switch.py: -------------------------------------------------------------------------------- 1 | """ 2 | switch between code and test 3 | 4 | If you're using `pytest`[1] and use the layout described below, this @button code 5 | will jump you between a function and its test, creating the (test)function 6 | if it doesn't already exist. Also the tests folder and `test_foo.py` file. 7 | It assumes use of the `active_path` plugin which headlines folders as 8 | `/myFolder/` with body text `@path myFolder`. 9 | 10 | This code is very heavy on assumptions, but a lot of those are driven 11 | by pytest default behavior. 12 | 13 | To run tests, use `python -m pytest`, as anything involving py.test is 14 | deprecated, and for some reason `pytest` finds files but runs no tests. 15 | Tested with pytest 3.x, note Ubuntu 16.04 seems to still be on 2.x 16 | 17 | Assumed layout: 18 | 19 | /tests/ 20 | test_utils.py 21 | def test_add_one()... 22 | def test_sub_one()... 23 | test_gui.py 24 | def test_load_buttons()... 25 | utils.py 26 | def add_one()... 27 | def sub_one()... 28 | gui.py 29 | def load_buttons()... 30 | 31 | So running this code from a button will jump you from 32 | test_sub_one() back to sub_one() and visa versa creating any 33 | missing parts of the hierarchy in the process. 34 | 35 | [1] https://docs.pytest.org/en/latest/ 36 | """ 37 | 38 | import re 39 | tests_path = c.config.getString("pytest-path") or "tests" 40 | info = {} 41 | # climb up node tree collecting info. 42 | for nd in p.self_and_parents_iter(): 43 | definition = re.match(r'def ([^( ]*)\(', p.b) 44 | if definition and not info.get('func'): 45 | info['func'] = definition.group(1) 46 | if nd.h.endswith('.py') and not info.get('file'): 47 | info['file'] = nd.h.split()[-1].split('/')[-1] 48 | if nd.h.strip('/') == tests_path.strip('/'): 49 | info['test'] = True 50 | 51 | nd = p.copy() 52 | 53 | if info.get('test'): # we started in these tests folder 54 | while nd.h.strip('/') != tests_path.strip('/'): 55 | nd = nd.parent() # climb up to code folder 56 | if info.get('file'): # find or create code file 57 | target = info['file'][5:] 58 | for sib in nd.self_and_siblings(): 59 | if sib.h.endswith(target): 60 | nd = sib 61 | break 62 | else: 63 | nd = nd.insertAfter() 64 | nd.h = '@auto ' + target 65 | if info.get('func'): # find or create code function 66 | target = info['func'][5:] 67 | for child in nd.children(): 68 | if child.h == target: 69 | nd = child 70 | break 71 | else: 72 | nd = nd.insertAsLastChild() 73 | nd.h = target 74 | nd.b = 'def' # let abbreviation build the rest 75 | else: # we stared in the code folder 76 | if info.get('func'): # get up to file level (weak, could be deeper) 77 | nd.moveToParent() 78 | for sib in nd.self_and_siblings(): # find or create tests folder 79 | if sib.h.strip('/') == tests_path.strip('/'): 80 | nd = sib 81 | break 82 | else: 83 | nd = nd.insertBefore() 84 | nd.h = "/%s/" % tests_path.strip('/') 85 | nd.b = "@path %s" % tests_path 86 | if info.get('file'): # find or create test file 87 | target = 'test_' + info['file'] 88 | for child in nd.children(): 89 | if child.h.endswith(target): 90 | nd = child 91 | break 92 | else: 93 | nd = nd.insertAsLastChild() 94 | nd.h = "@auto %s" % target 95 | nd.b = "import %s\n\n@others\n" % info['file'].replace('.py', '') 96 | if info.get('func'): # find or create test function 97 | target = 'test_' + info['func'] 98 | for child in nd.children(): 99 | if child.h == target: 100 | nd = child 101 | break 102 | else: 103 | nd = nd.insertAsLastChild() 104 | nd.h = target 105 | nd.b = "def %s():\n assert %s. == 'otters'\n" % ( 106 | target, info['file'].replace('.py', '')) 107 | 108 | c.selectPosition(nd) 109 | c.redraw() 110 | -------------------------------------------------------------------------------- /utils/x11_copy_paste_for_windows.py: -------------------------------------------------------------------------------- 1 | # monkey patch to give Leo body widget X11 like select->copy and 2 | # middle-button->paste behaviors in Windows, run it from a @script 3 | # node with @bool scripting-at-script-nodes = True 4 | 5 | from leo.core.leoQt import isQt5, QtCore, QtGui, Qsci, QtWidgets 6 | from leo.plugins.qt_text import QTextEditWrapper 7 | 8 | old = QTextEditWrapper.set_signals 9 | 10 | def set_signals(self, old=old): 11 | 12 | old(self) 13 | 14 | def new_selection(w=self.widget): 15 | sel = w.textCursor().selectedText() 16 | but = QtGui.QApplication.mouseButtons() & QtCore.Qt.LeftButton 17 | if sel and but: 18 | QtGui.QApplication.clipboard().setText(sel) 19 | 20 | def middle_click(event, w=self.widget, old=self.widget.mouseReleaseEvent): 21 | if not event.button() == QtCore.Qt.MiddleButton: 22 | return old(event) 23 | cursor = w.cursorForPosition(event.pos()) 24 | w.copy() # copy selection to clipboard if not there already 25 | text = QtGui.QApplication.clipboard().text() 26 | cursor.insertText(text) 27 | w.setTextCursor(cursor) 28 | 29 | self.widget.selectionChanged.connect(new_selection) 30 | self.widget.mouseReleaseEvent = middle_click 31 | 32 | QTextEditWrapper.set_signals = set_signals 33 | c.frame.body.wrapper.set_signals() 34 | --------------------------------------------------------------------------------