Application.display_item()   F
last analyzed

Complexity

Conditions 24

Size

Total Lines 109
Code Lines 76

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 76
dl 0
loc 109
rs 0
c 0
b 0
f 0
cc 24
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like doorstop.gui.application.Application.display_item() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python
2
# SPDX-License-Identifier: LGPL-3.0-only
3
# type: ignore
4
5
"""Graphical interface for Doorstop."""
6
7
import functools
8
import logging
9
import sys
10
from itertools import chain
11
from unittest.mock import Mock
12
13
from doorstop import common
14
from doorstop.common import DoorstopError
15
from doorstop.core import builder, vcs
16
from doorstop.gui import utilTkinter, widget
17
18
try:
19
    import tkinter as tk
20
    from tkinter import ttk
21
    from tkinter import filedialog
22
except ImportError as _exc:
23
    sys.stderr.write("WARNING: {}\n".format(_exc))
24
    tk = Mock()
25
    ttk = Mock()
26
27
28
log = common.logger(__name__)
29
30
31 View Code Duplication
def _log(func):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
32
    """Log name and arguments."""
33
34
    @functools.wraps(func)
35
    def wrapped(self, *args, **kwargs):
36
        sargs = "{}, {}".format(
37
            ', '.join(repr(a) for a in args),
38
            ', '.join("{}={}".format(k, repr(v)) for k, v in kwargs.items()),
39
        )
40
        msg = "log: {}: {}".format(func.__name__, sargs.strip(", "))
41
        if not isinstance(self, ttk.Frame) or not self.ignore:
42
            log.debug(msg.strip())
43
        return func(self, *args, **kwargs)
44
45
    return wrapped
46
47
48
class Application(ttk.Frame):
49
    """Graphical application for Doorstop."""
50
51
    def __init__(self, root, cwd, project):
52
        ttk.Frame.__init__(self, root)
53
54
        # Create Doorstop variables
55
        self.cwd = cwd
56
        self.tree = None
57
        self.document = None
58
        self.item = None
59
60
        # Create string variables
61
        self.stringvar_project = tk.StringVar(value=project or '')
62
        self.stringvar_project.trace('w', self.display_tree)
63
        self.stringvar_document = tk.StringVar()
64
        self.stringvar_document.trace('w', self.display_document)
65
66
        # The stringvar_item holds the uid of the main selected item (or empty string if nothing is selected).
67
        self.stringvar_item = tk.StringVar()
68
        self.stringvar_item.trace('w', self.display_item)
69
70
        self.stringvar_text = tk.StringVar()
71
        self.stringvar_text.trace('w', self.update_item)
72
        self.intvar_active = tk.IntVar()
73
        self.intvar_active.trace('w', self.update_item)
74
        self.intvar_derived = tk.IntVar()
75
        self.intvar_derived.trace('w', self.update_item)
76
        self.intvar_normative = tk.IntVar()
77
        self.intvar_normative.trace('w', self.update_item)
78
        self.intvar_heading = tk.IntVar()
79
        self.intvar_heading.trace('w', self.update_item)
80
        self.stringvar_link = tk.StringVar()  # no trace event
81
        self.stringvar_ref = tk.StringVar()
82
        self.stringvar_ref.trace('w', self.update_item)
83
        self.stringvar_extendedkey = tk.StringVar()
84
        self.stringvar_extendedkey.trace('w', self.display_extended)
85
        self.stringvar_extendedvalue = tk.StringVar()
86
        self.stringvar_extendedvalue.trace('w', self.update_item)
87
88
        # Create widget variables
89
        self.combobox_documents = None
90
        self.text_items = None
91
        self.text_item = None
92
        self.listbox_links = None
93
        self.combobox_extended = None
94
        self.text_extendedvalue = None
95
        self.text_parents = None
96
        self.text_children = None
97
98
        # Initialize the GUI
99
        self.ignore = False  # flag to ignore internal events
100
        frame = self.init(root)
101
        frame.pack(fill=tk.BOTH, expand=1)
102
103
        # Start the application
104
        root.after(500, self.find)
105
106
    def init(self, root):
107
        """Initialize and return the main frame."""
108
        # pylint: disable=attribute-defined-outside-init
109
110
        # Shared arguments
111
        width_text = 30
112
        height_text = 10
113
        height_ext = 5
114
115
        # Shared keyword arguments
116
        kw_f = {'padding': 5}  # constructor arguments for frames
117
        kw_gp = {'padx': 2, 'pady': 2}  # grid arguments for padded widgets
118
        kw_gs = {'sticky': tk.NSEW}  # grid arguments for sticky widgets
119
        kw_gsp = dict(
120
            chain(kw_gs.items(), kw_gp.items())
121
        )  # grid arguments for sticky padded widgets
122
123
        root.bind_all("<Control-minus>", lambda arg: widget.adjustFontSize(-1))
124
        root.bind_all("<Control-equal>", lambda arg: widget.adjustFontSize(1))
125
        root.bind_all("<Control-0>", lambda arg: widget.resetFontSize())
126
127
        # Configure grid
128
        frame = ttk.Frame(root, **kw_f)
129
        frame.rowconfigure(0, weight=0)
130
        frame.rowconfigure(1, weight=1)
131
        frame.columnconfigure(0, weight=2)
132
        frame.columnconfigure(1, weight=1)
133
        frame.columnconfigure(2, weight=1)
134
        frame.columnconfigure(3, weight=2)
135
136
        # Create widgets
137
        def frame_project(root):
138
            """Frame for the current project."""
139
            # Configure grid
140
            frame = ttk.Frame(root, **kw_f)
141
            frame.rowconfigure(0, weight=1)
142
            frame.columnconfigure(0, weight=0)
143
            frame.columnconfigure(1, weight=1)
144
145
            # Place widgets
146
            widget.Label(frame, text="Project:").grid(row=0, column=0, **kw_gp)
147
            widget.Entry(frame, textvariable=self.stringvar_project).grid(
148
                row=0, column=1, **kw_gsp
149
            )
150
151
            return frame
152
153
        def frame_tree(root):
154
            """Frame for the current document."""
155
            # Configure grid
156
            frame = ttk.Frame(root, **kw_f)
157
            frame.rowconfigure(0, weight=1)
158
            frame.columnconfigure(0, weight=0)
159
            frame.columnconfigure(1, weight=1)
160
161
            # Place widgets
162
            widget.Label(frame, text="Document:").grid(row=0, column=0, **kw_gp)
163
            self.combobox_documents = widget.Combobox(
164
                frame, textvariable=self.stringvar_document, state="readonly"
165
            )
166
            self.combobox_documents.grid(row=0, column=1, **kw_gsp)
167
168
            return frame
169
170
        def frame_document(root):
171
            """Frame for current document's outline and items."""
172
            # Configure grid
173
            frame = ttk.Frame(root, **kw_f)
174
            frame.rowconfigure(0, weight=0)
175
            frame.rowconfigure(1, weight=5)
176
            frame.rowconfigure(2, weight=0)
177
            frame.rowconfigure(3, weight=0)
178
            frame.columnconfigure(0, weight=0)
179
            frame.columnconfigure(1, weight=0)
180
            frame.columnconfigure(2, weight=0)
181
            frame.columnconfigure(3, weight=0)
182
            frame.columnconfigure(4, weight=1)
183
            frame.columnconfigure(5, weight=1)
184
185
            @_log
186
            def treeview_outline_treeviewselect(event):
187
                """Handle selecting an item in the tree view."""
188
                if self.ignore:
189
                    return
190
                thewidget = event.widget
191
                curselection = thewidget.selection()
192
                if curselection:
193
                    uid = curselection[0]
194
                    self.stringvar_item.set(uid)
195
196
            @_log
197
            def treeview_outline_delete(event):  # pylint: disable=W0613
198
                """Handle deleting an item in the tree view."""
199
                if self.ignore:
200
                    return
201
                self.remove()
202
203
            # Place widgets
204
            widget.Label(frame, text="Outline:").grid(
205
                row=0, column=0, columnspan=4, sticky=tk.W, **kw_gp
206
            )
207
            widget.Label(frame, text="Items:").grid(
208
                row=0, column=4, columnspan=2, sticky=tk.W, **kw_gp
209
            )
210
            c_columnId = ("Id",)
211
            self.treeview_outline = widget.TreeView(frame, columns=c_columnId)
212
            for col in c_columnId:
213
                self.treeview_outline.heading(col, text=col)
214
215
            # Add a Vertical scrollbar to the Treeview Outline
216
            treeview_outline_verticalScrollBar = widget.ScrollbarV(
217
                frame, command=self.treeview_outline.yview
218
            )
219
            treeview_outline_verticalScrollBar.grid(
220
                row=1, column=0, columnspan=1, **kw_gs
221
            )
222
            self.treeview_outline.configure(
223
                yscrollcommand=treeview_outline_verticalScrollBar.set
224
            )
225
226
            self.treeview_outline.bind(
227
                "<<TreeviewSelect>>", treeview_outline_treeviewselect
228
            )
229
            self.treeview_outline.bind("<Delete>", treeview_outline_delete)
230
            self.treeview_outline.grid(row=1, column=1, columnspan=3, **kw_gsp)
231
            self.text_items = widget.noUserInput_init(
232
                widget.Text(frame, width=width_text, wrap=tk.WORD)
233
            )
234
            self.text_items.grid(row=1, column=4, columnspan=2, **kw_gsp)
235
            self.text_items_hyperlink = utilTkinter.HyperlinkManager(self.text_items)
236
            widget.Button(frame, text="<", width=0, command=self.left).grid(
237
                row=2, column=0, sticky=tk.EW, padx=(2, 0)
238
            )
239
            widget.Button(frame, text="v", width=0, command=self.down).grid(
240
                row=2, column=1, sticky=tk.EW
241
            )
242
            widget.Button(frame, text="^", width=0, command=self.up).grid(
243
                row=2, column=2, sticky=tk.EW
244
            )
245
            widget.Button(frame, text=">", width=0, command=self.right).grid(
246
                row=2, column=3, sticky=tk.EW, padx=(0, 2)
247
            )
248
            widget.Button(frame, text="Add Item", command=self.add).grid(
249
                row=2, column=4, sticky=tk.W, **kw_gp
250
            )
251
            widget.Button(frame, text="Remove Selected Item", command=self.remove).grid(
252
                row=2, column=5, sticky=tk.E, **kw_gp
253
            )
254
255
            return frame
256
257
        def frame_item(root):
258
            """Frame for the currently selected item."""
259
            # Configure grid
260
            frame = ttk.Frame(root, **kw_f)
261
            frame.rowconfigure(0, weight=0)
262
            frame.rowconfigure(1, weight=4)
263
            frame.rowconfigure(2, weight=0)
264
            frame.rowconfigure(3, weight=1)
265
            frame.rowconfigure(4, weight=1)
266
            frame.rowconfigure(5, weight=1)
267
            frame.rowconfigure(6, weight=1)
268
            frame.rowconfigure(7, weight=0)
269
            frame.rowconfigure(8, weight=0)
270
            frame.rowconfigure(9, weight=0)
271
            frame.rowconfigure(10, weight=0)
272
            frame.rowconfigure(11, weight=4)
273
            frame.columnconfigure(0, weight=1, pad=kw_f['padding'] * 2)
274
            frame.columnconfigure(1, weight=1)
275
276
            @_log
277
            def text_focusin(_):
278
                """Handle entering a text field."""
279
                self.ignore = True
280
281
            @_log
282
            def text_item_focusout(event):
283
                """Handle updated text text."""
284
                self.ignore = False
285
                thewidget = event.widget
286
                value = thewidget.get('1.0', tk.END)
287
                self.stringvar_text.set(value)
288
289
            @_log
290
            def text_extendedvalue_focusout(event):
291
                """Handle updated extended attributes."""
292
                self.ignore = False
293
                thewidget = event.widget
294
                value = thewidget.get('1.0', tk.END)
295
                self.stringvar_extendedvalue.set(value)
296
297
            # Selected Item
298
            widget.Label(frame, text="Selected Item:").grid(
299
                row=0, column=0, columnspan=3, sticky=tk.W, **kw_gp
300
            )
301
            self.text_item = widget.Text(
302
                frame, width=width_text, height=height_text, wrap=tk.WORD
303
            )
304
            self.text_item.bind('<FocusIn>', text_focusin)
305
            self.text_item.bind('<FocusOut>', text_item_focusout)
306
            self.text_item.grid(row=1, column=0, columnspan=3, **kw_gsp)
307
308
            # Column: Properties
309
            self.create_properties_widget(frame).grid(
310
                row=2, rowspan=2, column=0, columnspan=2, sticky=tk.NSEW, **kw_gp
311
            )
312
313
            # Column: Links
314
            self.create_links_widget(frame).grid(
315
                row=4, rowspan=3, column=0, columnspan=2, sticky=tk.NSEW, **kw_gp
316
            )
317
318
            # External Reference
319
            self.create_reference_widget(frame).grid(
320
                row=7, rowspan=2, column=0, columnspan=2, sticky=tk.NSEW, **kw_gp
321
            )
322
323
            widget.Label(frame, text="Extended Attributes:").grid(
324
                row=9, column=0, columnspan=3, sticky=tk.W, **kw_gp
325
            )
326
            self.combobox_extended = widget.Combobox(
327
                frame, textvariable=self.stringvar_extendedkey
328
            )
329
            self.combobox_extended.grid(row=10, column=0, columnspan=3, **kw_gsp)
330
            self.text_extendedvalue = widget.Text(
331
                frame, width=width_text, height=height_ext, wrap=tk.WORD
332
            )
333
            self.text_extendedvalue.bind('<FocusIn>', text_focusin)
334
            self.text_extendedvalue.bind('<FocusOut>', text_extendedvalue_focusout)
335
            self.text_extendedvalue.grid(row=11, column=0, columnspan=3, **kw_gsp)
336
337
            return frame
338
339
        def frame_family(root):
340
            """Frame for the parent and child document items."""
341
            # Configure grid
342
            frame = ttk.Frame(root, **kw_f)
343
            frame.rowconfigure(0, weight=0)
344
            frame.rowconfigure(1, weight=1)
345
            frame.rowconfigure(2, weight=0)
346
            frame.rowconfigure(3, weight=1)
347
            frame.columnconfigure(0, weight=1)
348
349
            # Place widgets
350
            widget.Label(frame, text="Linked To:").grid(
351
                row=0, column=0, sticky=tk.W, **kw_gp
352
            )
353
            self.text_parents = widget.noUserInput_init(
354
                widget.Text(frame, width=width_text, wrap=tk.WORD)
355
            )
356
            self.text_parents_hyperlink = utilTkinter.HyperlinkManager(
357
                self.text_parents
358
            )
359
            self.text_parents.grid(row=1, column=0, **kw_gsp)
360
            widget.Label(frame, text="Linked From:").grid(
361
                row=2, column=0, sticky=tk.W, **kw_gp
362
            )
363
            self.text_children = widget.noUserInput_init(
364
                widget.Text(frame, width=width_text, wrap=tk.WORD)
365
            )
366
            self.text_children_hyperlink = utilTkinter.HyperlinkManager(
367
                self.text_children
368
            )
369
            self.text_children.grid(row=3, column=0, **kw_gsp)
370
371
            return frame
372
373
        # Place widgets
374
        frame_project(frame).grid(row=0, column=0, columnspan=2, **kw_gs)
375
        frame_tree(frame).grid(row=0, column=2, columnspan=2, **kw_gs)
376
        frame_document(frame).grid(row=1, column=0, **kw_gs)
377
        frame_item(frame).grid(row=1, column=1, columnspan=2, **kw_gs)
378
        frame_family(frame).grid(row=1, column=3, **kw_gs)
379
380
        return frame
381
382
    @_log
383
    def find(self):
384
        """Find the root of the project."""
385
        if not self.stringvar_project.get():
386
            try:
387
                path = vcs.find_root(self.cwd)
388
            except DoorstopError as exc:
389
                log.error(exc)
390
            else:
391
                self.stringvar_project.set(path)
392
393
    @_log
394
    def browse(self):
395
        """Browse for the root of a project."""
396
        path = filedialog.askdirectory()
397
        log.debug("path: {}".format(path))
398
        if path:
399
            self.stringvar_project.set(path)
400
401
    @_log
402
    def display_tree(self, *_):
403
        """Display the currently selected tree."""
404
        # Set the current tree
405
        self.tree = builder.build(root=self.stringvar_project.get())
406
        log.info("displaying tree...")
407
408
        # Display the documents in the tree
409
        values = [
410
            "{} ({})".format(document.prefix, document.relpath)
411
            for document in self.tree
412
        ]
413
        self.combobox_documents['values'] = values
414
415
        # Select the first document
416
        if len(self.tree):  # pylint: disable=len-as-condition
417
            self.combobox_documents.current(0)
418
        else:
419
            logging.warning("no documents to display")
420
421
    @_log
422
    def display_document(self, *_):
423
        """Display the currently selected document."""
424
        # Set the current document
425
        index = self.combobox_documents.current()
426
        self.document = list(self.tree)[index]
427
        log.info("displaying document {}...".format(self.document))
428
429
        # Record the currently opened items.
430
        c_openItem = []
431
        for c_currUID in utilTkinter.getAllChildren(self.treeview_outline):
432
            if self.treeview_outline.item(c_currUID)["open"]:
433
                c_openItem.append(c_currUID)
434
435
        # Record the currently selected items.
436
        c_selectedItem = self.treeview_outline.selection()
437
438
        # Clear the widgets
439
        self.treeview_outline.delete(*self.treeview_outline.get_children())
440
        widget.noUserInput_delete(self.text_items, '1.0', tk.END)
441
        self.text_items_hyperlink.reset()
442
443
        # Display the items in the document
444
        c_levelsItem = [""]
445
        for item in self.document.items:
446
            theParent = next(
447
                iter(reversed([x for x in c_levelsItem[: item.depth]])), ""
448
            )
449
450
            while len(c_levelsItem) < item.depth:
451
                c_levelsItem.append(item.uid)
452
            c_levelsItem = c_levelsItem[: item.depth]
453
            for x in range(item.depth):
454
                c_levelsItem.append(item.uid)
455
456
            # Add the item to the document outline
457
            self.treeview_outline.insert(
458
                theParent,
459
                tk.END,
460
                item.uid,
461
                text=item.level,
462
                values=(item.uid,),
463
                open=item.uid in c_openItem,
464
            )
465
466
            # Add the item to the document text
467
            widget.noUserInput_insert(
468
                self.text_items, tk.END, "{t}".format(t=item.text or item.ref or '???')
469
            )
470
            widget.noUserInput_insert(self.text_items, tk.END, " [")
471
            widget.noUserInput_insert(
472
                self.text_items,
473
                tk.END,
474
                item.uid,
475
                self.text_items_hyperlink.add(
476
                    # pylint: disable=unnecessary-lambda
477
                    lambda c_theURL: self.followlink(c_theURL),
478
                    item.uid,
479
                    ["refLink"],
480
                ),
481
            )
482
            widget.noUserInput_insert(self.text_items, tk.END, "]\n\n")
483
484
        # Set tree view selection
485
        c_selectedItem = [
486
            x
487
            for x in c_selectedItem
488
            if x in utilTkinter.getAllChildren(self.treeview_outline)
489
        ]
490
        if c_selectedItem:
491
            # Restore selection
492
            self.treeview_outline.selection_set(c_selectedItem)
493
        else:
494
            # Select the first item
495
            for uid in utilTkinter.getAllChildren(self.treeview_outline):
496
                self.stringvar_item.set(uid)
497
                break
498
            else:
499
                logging.warning("no items to display")
500
                self.stringvar_item.set("")
501
502
    @_log
503
    def display_item(self, *_):
504
        """Display the currently selected item."""
505
        try:
506
            self.ignore = True
507
508
            # Fetch the current item
509
            uid = self.stringvar_item.get()
510
            if uid == "":
511
                self.item = None
512
            else:
513
                try:
514
                    self.item = self.tree.find_item(uid)
515
                except DoorstopError:
516
                    pass
517
            log.info("displaying item {}...".format(self.item))
518
519
            if uid != "":
520
                if uid not in self.treeview_outline.selection():
521
                    self.treeview_outline.selection_set((uid,))
522
                self.treeview_outline.see(uid)
523
524
            # Display the item's text
525
            self.text_item.replace(
526
                '1.0', tk.END, "" if self.item is None else self.item.text
527
            )
528
529
            # Display the item's properties
530
            self.stringvar_text.set("" if self.item is None else self.item.text)
531
            self.intvar_active.set(False if self.item is None else self.item.active)
532
            self.intvar_derived.set(False if self.item is None else self.item.derived)
533
            self.intvar_normative.set(
534
                False if self.item is None else self.item.normative
535
            )
536
            self.intvar_heading.set(False if self.item is None else self.item.heading)
537
538
            # Display the item's links
539
            self.listbox_links.delete(0, tk.END)
540
            if self.item is not None:
541
                for uid in self.item.links:
542
                    self.listbox_links.insert(tk.END, uid)
543
            self.stringvar_link.set('')
544
545
            # Display the item's external reference
546
            self.stringvar_ref.set("" if self.item is None else self.item.ref)
547
548
            # Display the item's extended attributes
549
            values = None if self.item is None else self.item.extended
550
            self.combobox_extended['values'] = values or ['']
551
            if self.item is not None:
552
                self.combobox_extended.current(0)
553
554
            # Display the items this item links to
555
            widget.noUserInput_delete(self.text_parents, '1.0', tk.END)
556
            self.text_parents_hyperlink.reset()
557
            if self.item is not None:
558
                for uid in self.item.links:
559
                    try:
560
                        item = self.tree.find_item(uid)
561
                    except DoorstopError:
562
                        text = "???"
563
                    else:
564
                        text = item.text or item.ref or '???'
565
                        uid = item.uid
566
567
                    widget.noUserInput_insert(
568
                        self.text_parents, tk.END, "{t}".format(t=text)
569
                    )
570
                    widget.noUserInput_insert(self.text_parents, tk.END, " [")
571
                    widget.noUserInput_insert(
572
                        self.text_parents,
573
                        tk.END,
574
                        uid,
575
                        self.text_parents_hyperlink.add(
576
                            # pylint: disable=unnecessary-lambda
577
                            lambda c_theURL: self.followlink(c_theURL),
578
                            uid,
579
                            ["refLink"],
580
                        ),
581
                    )
582
                    widget.noUserInput_insert(self.text_parents, tk.END, "]\n\n")
583
584
            # Display the items this item has links from
585
            widget.noUserInput_delete(self.text_children, '1.0', 'end')
586
            self.text_children_hyperlink.reset()
587
            if self.item is not None:
588
                for uid in self.item.find_child_links():
589
                    item = self.tree.find_item(uid)
590
                    text = item.text or item.ref or '???'
591
                    uid = item.uid
592
593
                    widget.noUserInput_insert(
594
                        self.text_children, tk.END, "{t}".format(t=text)
595
                    )
596
                    widget.noUserInput_insert(self.text_children, tk.END, " [")
597
                    widget.noUserInput_insert(
598
                        self.text_children,
599
                        tk.END,
600
                        uid,
601
                        self.text_children_hyperlink.add(
602
                            # pylint: disable=unnecessary-lambda
603
                            lambda c_theURL: self.followlink(c_theURL),
604
                            uid,
605
                            ["refLink"],
606
                        ),
607
                    )
608
                    widget.noUserInput_insert(self.text_children, tk.END, "]\n\n")
609
        finally:
610
            self.ignore = False
611
612
    @_log
613
    def display_extended(self, *_):
614
        """Display the currently selected extended attribute."""
615
        try:
616
            self.ignore = True
617
618
            name = self.stringvar_extendedkey.get()
619
            log.debug("displaying extended attribute '{}'...".format(name))
620
            self.text_extendedvalue.replace('1.0', tk.END, self.item.get(name, ""))
621
        finally:
622
            self.ignore = False
623
624
    @_log
625
    def update_item(self, *_):
626
        """Update the current item from the fields."""
627
        if self.ignore:
628
            return
629
        if not self.item:
630
            logging.warning("no item selected")
631
            return
632
633
        # Update the current item
634
        log.info("updating {}...".format(self.item))
635
        self.item.auto = False
636
        self.item.text = self.stringvar_text.get()
637
        self.item.active = self.intvar_active.get()
638
        self.item.derived = self.intvar_derived.get()
639
        self.item.normative = self.intvar_normative.get()
640
        self.item.heading = self.intvar_heading.get()
641
        self.item.links = self.listbox_links.get(0, tk.END)
642
        self.item.ref = self.stringvar_ref.get()
643
        name = self.stringvar_extendedkey.get()
644
        if name:
645
            self.item.set(name, self.stringvar_extendedvalue.get())
646
        self.item.save()
647
648
        # Re-select this item
649
        self.display_document()
650
651
    @_log
652
    def left(self):
653
        """Dedent the current item's level."""
654
        self.item.level <<= 1
655
        self.document.reorder(keep=self.item)
656
        self.display_document()
657
658
    @_log
659
    def down(self):
660
        """Increment the current item's level."""
661
        self.item.level += 1
662
        self.document.reorder(keep=self.item)
663
        self.display_document()
664
665
    @_log
666
    def up(self):
667
        """Decrement the current item's level."""
668
        self.item.level -= 1
669
        self.document.reorder(keep=self.item)
670
        self.display_document()
671
672
    @_log
673
    def right(self):
674
        """Indent the current item's level."""
675
        self.item.level >>= 1
676
        self.document.reorder(keep=self.item)
677
        self.display_document()
678
679
    @_log
680
    def add(self):
681
        """Add a new item to the document."""
682
        logging.info("adding item to {}...".format(self.document))
683
        if self.item:
684
            level = self.item.level + 1
685
        else:
686
            level = None
687
        item = self.document.add_item(level=level)
688
        logging.info("added item: {}".format(item))
689
        # Refresh the document view
690
        self.display_document()
691
        # Set the new selection
692
        self.stringvar_item.set(item.uid)
693
694
    @_log
695
    def remove(self):
696
        """Remove the selected item from the document."""
697
        newSelection = ""
698
        for c_currUID in self.treeview_outline.selection():
699
            # Find the item which should be selected once the current selection is removed.
700
            for currNeighbourStrategy in (
701
                self.treeview_outline.next,
702
                self.treeview_outline.prev,
703
                self.treeview_outline.parent,
704
            ):
705
                newSelection = currNeighbourStrategy(c_currUID)
706
                if newSelection != "":
707
                    break
708
            # Remove the item
709
            item = self.tree.find_item(c_currUID)
710
            logging.info("removing item {}...".format(item))
711
            item = self.tree.remove_item(item)
712
            logging.info("removed item: {}".format(item))
713
            # Set the new selection
714
            self.stringvar_item.set(newSelection)
715
            # Refresh the document view
716
            self.display_document()
717
718
    @_log
719
    def link(self):
720
        """Add the specified link to the current item."""
721
        # Add the specified link to the list
722
        uid = self.stringvar_link.get()
723
        if uid:
724
            self.listbox_links.insert(tk.END, uid)
725
            self.stringvar_link.set('')
726
727
            # Update the current item
728
            self.update_item()
729
730
    @_log
731
    def unlink(self):
732
        """Remove the currently selected link from the current item."""
733
        # Remove the selected link from the list (if selected)
734
        index = self.listbox_links.curselection()
735
        if not index:
736
            return
737
        self.listbox_links.delete(index)
738
739
        # Update the current item
740
        self.update_item()
741
742
    @_log
743
    def followlink(self, uid):
744
        """Display a given uid."""
745
        # Update the current item
746
        self.ignore = False
747
        self.update_item()
748
749
        # Load the good document.
750
        document = self.tree.find_document(uid.prefix)
751
        index = list(self.tree).index(document)
752
        self.combobox_documents.current(index)
753
        self.display_document()
754
755
        # load the good Item
756
        self.stringvar_item.set(uid)
757
758
    def create_properties_widget(self, parent):
759
        frame = ttk.Frame(parent)
760
761
        frame.columnconfigure(0, weight=1)
762
        frame.rowconfigure(0, weight=1)
763
        frame.rowconfigure(1, weight=1)
764
        frame.rowconfigure(2, weight=1)
765
        frame.rowconfigure(3, weight=1)
766
        frame.rowconfigure(4, weight=1)
767
768
        widget.Label(frame, text="Properties:").grid(row=0, column=0, sticky=tk.NW)
769
        widget.Checkbutton(frame, text="Active", variable=self.intvar_active).grid(
770
            row=1, column=0, sticky=tk.NW
771
        )
772
        widget.Checkbutton(frame, text="Derived", variable=self.intvar_derived).grid(
773
            row=2, column=0, sticky=tk.NW
774
        )
775
        widget.Checkbutton(
776
            frame, text="Normative", variable=self.intvar_normative
777
        ).grid(row=3, column=0, sticky=tk.NW)
778
        widget.Checkbutton(frame, text="Heading", variable=self.intvar_heading).grid(
779
            row=4, column=0, sticky=tk.NW
780
        )
781
782
        return frame
783
784
    def create_links_widget(self, parent):
785
        frame = ttk.Frame(parent)
786
787
        frame.columnconfigure(0, weight=1)
788
        frame.columnconfigure(1, weight=1)
789
        frame.columnconfigure(2, weight=0)
790
        frame.columnconfigure(3, weight=0)
791
        frame.rowconfigure(0, weight=1)
792
        frame.rowconfigure(1, weight=1)
793
        frame.rowconfigure(2, weight=1)
794
795
        width_uid = 10
796
        widget.Label(frame, text="Links:").grid(
797
            row=0, column=0, columnspan=1, sticky=tk.NW
798
        )
799
        widget.Entry(frame, textvariable=self.stringvar_link).grid(
800
            row=1, column=0, columnspan=2, sticky=tk.EW + tk.N
801
        )
802
        widget.Button(frame, text="+", command=self.link).grid(
803
            row=1, column=2, columnspan=1, sticky=tk.EW + tk.N
804
        )
805
        widget.Button(frame, text="-", command=self.unlink).grid(
806
            row=1, column=3, columnspan=1, sticky=tk.EW + tk.N
807
        )
808
        self.listbox_links = widget.Listbox(frame, width=width_uid)
809
        self.listbox_links.grid(
810
            row=2,
811
            column=0,
812
            rowspan=2,
813
            columnspan=4,
814
            padx=(3, 0),
815
            pady=(3, 0),
816
            sticky=tk.NSEW,
817
        )
818
819
        return frame
820
821
    def create_reference_widget(self, parent):
822
        frame = ttk.Frame(parent)
823
824
        frame.columnconfigure(0, weight=1)
825
        frame.rowconfigure(0, weight=1)
826
        frame.rowconfigure(1, weight=1)
827
828
        widget.Label(frame, text="External Reference:").grid(
829
            row=0, column=0, sticky=tk.W
830
        )
831
        widget.Entry(frame, textvariable=self.stringvar_ref).grid(
832
            row=1, column=0, sticky=tk.NSEW
833
        )
834
835
        return frame
836