Completed
Push — master ( 600d74...425f9b )
by Jace
07:38 queued 05:57
created

dtb.Application.separator()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1
Metric Value
cc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
#!/usr/bin/env python
2
3 1
"""Graphical interface for DropTheBeat."""
4
5 1
import sys
6 1
from unittest.mock import Mock
7 1
try:
8 1
    import tkinter as tk
9 1
    from tkinter import ttk
10 1
    from tkinter import messagebox, simpledialog, filedialog
11
except ImportError as err:
12
    sys.stderr.write("WARNING: {}\n".format(err))
13
    tk = Mock()
14
    ttk = Mock()
15
16 1
import os
17 1
import argparse
18 1
from itertools import chain
19 1
import logging
20
21 1
from dtb import GUI, __version__
22 1
from dtb import share, user
23 1
from dtb.common import SHARED, WarningFormatter
24 1
from dtb import settings
25
26 1
_LAUNCH = True
27
28
29 1
class Application(ttk.Frame):  # pylint: disable=too-many-instance-attributes
30
    """Tkinter application for DropTheBeat."""
31
32 1
    def __init__(self, master=None, root=None, home=None, name=None):
33 1
        ttk.Frame.__init__(self, master)
34
35
        # Load the root sharing directory
36 1
        self.root = root or share.find(home)
37
38
        # Load the user
39 1
        self.user = user.User(os.path.join(self.root, name)) if name else None
40 1
        try:
41 1
            self.user = self.user or user.get_current(self.root)
42
        except EnvironmentError:
43
44
            while True:
45
46
                msg = "Enter your name in the form 'First Last':"
47
                text = simpledialog.askstring("Create a User", msg)
48
                logging.debug("text: {}".format(repr(text)))
49
                name = text.strip(" '") if text else None
50
                if not name:
51
                    raise KeyboardInterrupt("no user specified")
52
                try:
53
                    self.user = user.User.new(self.root, name)
54
                except EnvironmentError:
55
                    existing = user.User(os.path.join(self.root, name))
56
                    msg = "Is this you:"
57
                    for info in existing.info:
58
                        msg += "\n\n'{}' on '{}'".format(info[1], info[0])
59
                    if not existing.info or \
60
                            messagebox.askyesno("Add to Existing User", msg):
61
                        self.user = user.User.add(self.root, name)
62
                        break
63
                else:
64
                    break
65
66
        # Create variables
67 1
        self.path_root = tk.StringVar(value=self.root)
68 1
        self.path_downloads = tk.StringVar(value=self.user.path_downloads)
69 1
        self.outgoing = []
70 1
        self.incoming = []
71
72
        # Initialize the GUI
73 1
        self.listbox_outgoing = None
74 1
        self.listbox_incoming = None
75 1
        frame = self.init(master)
76 1
        frame.pack(fill=tk.BOTH, expand=1)
77
78
        # Show the GUI
79 1
        master.deiconify()
80 1
        self.update()
81
82 1
    def init(self, root):
83
        """Initialize frames and widgets."""
84
85
        # pylint: disable=line-too-long
86
87 1
        mac = sys.platform == 'darwin'
88
89
        # Shared keyword arguments
90 1
        kw_f = {'padding': 5}  # constructor arguments for frames
91 1
        kw_gp = {'padx': 5, 'pady': 5}  # grid arguments for padded widgets
92 1
        kw_gs = {'sticky': tk.NSEW}  # grid arguments for sticky widgets
93 1
        kw_gsp = dict(chain(kw_gs.items(), kw_gp.items()))  # grid arguments for sticky padded widgets
94
95
        # Configure grid
96 1
        frame = ttk.Frame(root, **kw_f)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
97 1
        frame.rowconfigure(0, weight=0)
98 1
        frame.rowconfigure(2, weight=1)
99 1
        frame.rowconfigure(4, weight=1)
100 1
        frame.columnconfigure(0, weight=1)
101
102
        # Create widgets
103 1
        def frame_settings(master):
104
            """Frame for the settings."""
105 1
            frame = ttk.Frame(master, **kw_f)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
106
107
            # Configure grid
108 1
            frame.rowconfigure(0, weight=1)
109 1
            frame.rowconfigure(1, weight=1)
110 1
            frame.columnconfigure(0, weight=0)
111 1
            frame.columnconfigure(1, weight=1)
112 1
            frame.columnconfigure(2, weight=0)
113
114
            # Place widgets
115 1
            ttk.Label(frame, text="Shared:").grid(row=0, column=0, sticky=tk.W, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
116 1
            ttk.Entry(frame, state='readonly', textvariable=self.path_root).grid(row=0, column=1, columnspan=2, **kw_gsp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
117 1
            ttk.Label(frame, text="Downloads:").grid(row=1, column=0, sticky=tk.W, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
118 1
            ttk.Entry(frame, state='readonly', textvariable=self.path_downloads).grid(row=1, column=1, **kw_gsp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
119 1
            ttk.Button(frame, text="...", width=0, command=self.browse_downloads).grid(row=1, column=2, ipadx=5, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
120
121 1
            return frame
122
123 1
        def frame_incoming(master):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
124
            """Frame for incoming songs."""
125 1
            frame = ttk.Frame(master, **kw_f)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
126
127
            # Configure grid
128 1
            frame.rowconfigure(0, weight=1)
129 1
            frame.rowconfigure(1, weight=0)
130 1
            frame.columnconfigure(0, weight=0)
131 1
            frame.columnconfigure(1, weight=1)
132 1
            frame.columnconfigure(2, weight=1)
133
134
            # Place widgets
135 1
            self.listbox_incoming = tk.Listbox(frame, selectmode=tk.EXTENDED if mac else tk.MULTIPLE)
136 1
            self.listbox_incoming.grid(row=0, column=0, columnspan=3, **kw_gsp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
137 1
            scroll_incoming = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=self.listbox_incoming.yview)
138 1
            self.listbox_incoming.configure(yscrollcommand=scroll_incoming.set)
139 1
            scroll_incoming.grid(row=0, column=2, sticky=(tk.N, tk.E, tk.S))
140 1
            ttk.Button(frame, text="\u21BB", width=0, command=self.update).grid(row=1, column=0, sticky=tk.SW, ipadx=5, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
141 1
            ttk.Button(frame, text="Ignore Selected", command=self.do_ignore).grid(row=1, column=1, sticky=tk.SW, ipadx=5, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
142 1
            ttk.Button(frame, text="Download Selected", command=self.do_download).grid(row=1, column=2, sticky=tk.SE, ipadx=5, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
143 1
            return frame
144
145 1
        def frame_outgoing(master):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146
            """Frame for outgoing songs."""
147 1
            frame = ttk.Frame(master, **kw_f)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
148
149
            # Configure grid
150 1
            frame.rowconfigure(0, weight=1)
151 1
            frame.rowconfigure(1, weight=0)
152 1
            frame.columnconfigure(0, weight=0)
153 1
            frame.columnconfigure(1, weight=1)
154 1
            frame.columnconfigure(2, weight=1)
155
156
            # Place widgets
157 1
            self.listbox_outgoing = tk.Listbox(frame, selectmode=tk.EXTENDED if mac else tk.MULTIPLE)
158 1
            self.listbox_outgoing.grid(row=0, column=0, columnspan=3, **kw_gsp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
159 1
            scroll_outgoing = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=self.listbox_outgoing.yview)
160 1
            self.listbox_outgoing.configure(yscrollcommand=scroll_outgoing.set)
161 1
            scroll_outgoing.grid(row=0, column=2, sticky=(tk.N, tk.E, tk.S))
162 1
            ttk.Button(frame, text="\u21BB", width=0, command=self.update).grid(row=1, column=0, sticky=tk.SW, ipadx=5, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
163 1
            ttk.Button(frame, text="Remove Selected", command=self.do_remove).grid(row=1, column=1, sticky=tk.SW, ipadx=5, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
164 1
            ttk.Button(frame, text="Share Songs...", command=self.do_share).grid(row=1, column=2, sticky=tk.SE, ipadx=5, **kw_gp)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
165
166 1
            return frame
167
168 1
        def separator(master):
169
            """Widget to separate frames."""
170 1
            return ttk.Separator(master)
171
172
        # Place widgets
173 1
        frame_settings(frame).grid(row=0, **kw_gs)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
174 1
        separator(frame).grid(row=1, padx=10, pady=5, **kw_gs)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
175 1
        frame_outgoing(frame).grid(row=2, **kw_gs)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
176 1
        separator(frame).grid(row=3, padx=10, pady=5, **kw_gs)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
177 1
        frame_incoming(frame).grid(row=4, **kw_gs)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
178
179 1
        return frame
180
181 1
    def browse_downloads(self):
182
        """Browser for a new downloads directory."""
183
        path = filedialog.askdirectory()
184
        logging.debug("path: {}".format(path))
185
        if path:
186
            self.user.path_downloads = path
187
            self.path_downloads.set(self.user.path_downloads)
188
189 1
    def do_remove(self):
190
        """Remove selected songs."""
191
        for index in (int(s) for s in self.listbox_outgoing.curselection()):
192
            self.outgoing[index].ignore()
193
        self.update()
194
195 1
    def do_share(self):
196
        """Share songs."""
197
        paths = filedialog.askopenfilenames()
198
        if isinstance(paths, str):  # http://bugs.python.org/issue5712
199
            paths = self.master.tk.splitlist(paths)
200
        logging.debug("paths: {}".format(paths))
201
        for path in paths:
202
            self.user.recommend(path)
203
        self.update()
204
205 1
    def do_ignore(self):
206
        """Ignore selected songs."""
207
        for index in (int(s) for s in self.listbox_incoming.curselection()):
208
            song = self.incoming[index]
209
            song.ignore()
210
        self.update()
211
212 1
    def do_download(self):
213
        """Download selected songs."""
214
        indicies = (int(s) for s in self.listbox_incoming.curselection())
215
        try:
216
            for index in indicies:
217
                song = self.incoming[index]
218
                song.download(catch=False)
219
        except IOError as exc:
220
            self.show_error_from_exception(exc, "Download Error")
221
        self.update()
222
223 1
    def update(self):
224
        """Update the list of outgoing and incoming songs."""
225
226
        # Cleanup outgoing songs
227 1
        self.user.cleanup()
228
229
        # Update outgoing songs list
230 1
        logging.info("updating outgoing songs...")
231 1
        self.outgoing = list(self.user.outgoing)
232 1
        self.listbox_outgoing.delete(0, tk.END)
233 1
        for song in self.outgoing:
234
            self.listbox_outgoing.insert(tk.END, song.out_string)
235
        # Update incoming songs list
236
237 1
        logging.info("updating incoming songs...")
238 1
        self.incoming = list(self.user.incoming)
239 1
        self.listbox_incoming.delete(0, tk.END)
240 1
        for song in self.incoming:
241
            self.listbox_incoming.insert(tk.END, song.in_string)
242
243 1
    @staticmethod
244 1
    def show_error_from_exception(exception, title="Error"):
245
        """Convert an exception to an error dialog."""
246
        message = str(exception).capitalize().replace(": ", ":\n\n")
247
        messagebox.showerror(title, message)
248
249
250 1
def main(args=None):
251
    """Process command-line arguments and run the program."""
252
253
    # Main parser
254 1
    parser = argparse.ArgumentParser(prog=GUI, description=__doc__, **SHARED)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
255 1
    parser.add_argument('--home', metavar='PATH', help="path to home directory")
256
    # Hidden argument to override the root sharing directory path
257 1
    parser.add_argument('--root', metavar="PATH", help=argparse.SUPPRESS)
258
    # Hidden argument to run the program as a different user
259 1
    parser.add_argument('--test', metavar='"First Last"',
260
                        help=argparse.SUPPRESS)
261
262
    # Parse arguments
263 1
    args = parser.parse_args(args=args)
264
265
    # Configure logging
266 1
    _configure_logging(args.verbose)
267
268
    # Run the program
269 1
    try:
270 1
        success = run(args)
271 1
    except KeyboardInterrupt:
272 1
        logging.debug("program manually closed")
273
    else:
274 1
        if success:
275 1
            logging.debug("program exited")
276
        else:
277 1
            logging.debug("program exited with error")
278 1
            sys.exit(1)
279
280
281 1
def _configure_logging(verbosity=0):
282
    """Configure logging using the provided verbosity level (0+)."""
283
284
    # Configure the logging level and format
285 1
    if verbosity == 0:
286 1
        level = settings.VERBOSE_LOGGING_LEVEL
287 1
        default_format = settings.DEFAULT_LOGGING_FORMAT
288 1
        verbose_format = settings.VERBOSE_LOGGING_FORMAT
289
    else:
290 1
        level = settings.VERBOSE2_LOGGING_LEVEL
291 1
        default_format = verbose_format = settings.VERBOSE_LOGGING_FORMAT
292
293
    # Set a custom formatter
294 1
    logging.basicConfig(level=level)
295 1
    formatter = WarningFormatter(default_format, verbose_format)
296 1
    logging.root.handlers[0].setFormatter(formatter)
297
298
299 1
def run(args):
300
    """Start the GUI."""
301
302
    # Exit if tkinter is not available
303 1
    if isinstance(tk, Mock) or isinstance(ttk, Mock):
304 1
        logging.error("tkinter is not available")
305 1
        return False
306
307
    else:
308
309 1
        root = tk.Tk()
310 1
        root.title("{} (v{})".format(GUI, __version__))
311 1
        root.minsize(500, 500)
312
313
        # Map the Mac 'command' key to 'control'
314 1
        if sys.platform == 'darwin':
315
            root.bind_class('Listbox', '<Command-Button-1>',
316
                            root.bind_class('Listbox', '<Control-Button-1>'))
317
318
        # Temporarily hide the window for other dialogs
319 1
        root.withdraw()
320
321
        # Start the application
322 1
        app = Application(master=root, home=args.home,
323
                          root=args.root, name=args.test)
324 1
        if _LAUNCH:
325
            app.mainloop()
326
327 1
        return True
328
329
330
if __name__ == '__main__':  # pragma: no cover (manual test)
331
    main()
332