"Fossies" - the Fresh Open Source Software Archive 
Member "revelation-0.5.4/src/lib/ui.py" (4 Oct 2020, 48295 Bytes) of package /linux/privat/revelation-0.5.4.tar.xz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style:
standard) with prefixed line numbers.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "ui.py" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
0.5.3_vs_0.5.4.
1 #
2 # Revelation - a password manager for GNOME 2
3 # http://oss.codepoet.no/revelation/
4 # $Id$
5 #
6 # Module for UI functionality
7 #
8 #
9 # Copyright (c) 2003-2006 Erik Grinaker
10 #
11 # This program is free software; you can redistribute it and/or
12 # modify it under the terms of the GNU General Public License
13 # as published by the Free Software Foundation; either version 2
14 # of the License, or (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with this program; if not, write to the Free Software
23 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #
25
26 from . import config, data, dialog, entry, io, util
27
28 import gi
29 gi.require_version('Gtk', '3.0')
30 from gi.repository import GObject, Gtk, Gdk, Gio, Pango
31 import gettext, time
32
33 _ = gettext.gettext
34
35
36 STOCK_CONTINUE = _("_Continue") # "revelation-continue"
37 STOCK_DISCARD = "revelation-discard"
38 STOCK_EDIT = "revelation-edit"
39 STOCK_EXPORT = _("_Export") # "revelation-export"
40 STOCK_FOLDER = "revelation-folder"
41 STOCK_GENERATE = _("_Generate") # "revelation-generate"
42 STOCK_IMPORT = _("_Import") # "revelation-import"
43 STOCK_GOTO = "revelation-goto"
44 STOCK_LOCK = "revelation-lock"
45 STOCK_NEW_ENTRY = _("_Add Entry") # "revelation-new-entry"
46 STOCK_NEW_FOLDER = _("_Add Folder") # "revelation-new-folder"
47 STOCK_NEXT = "go-down" # "revelation-next"
48 STOCK_PASSWORD_CHANGE = _("_Change") # "revelation-password-change"
49 STOCK_PASSWORD_CHECK = "revelation-password-check"
50 STOCK_PASSWORD_STRONG = "security-high" # "revelation-password-strong"
51 STOCK_PASSWORD_WEAK = "security-low" # "revelation-password-weak"
52 STOCK_PREVIOUS = "go-up" # "revelation-previous"
53 STOCK_RELOAD = _("_Reload") # "revelation-reload"
54 STOCK_REMOVE = "revelation-remove"
55 STOCK_REPLACE = _("_Replace") # "revelation-replace"
56 STOCK_UNKNOWN = "dialog-question" # "revelation-unknown"
57 STOCK_UNLOCK = _("_Unlock") # "revelation-unlock"
58 STOCK_UPDATE = _("_Update") # "revelation-update"
59
60
61 STOCK_ENTRY_FOLDER = "folder" # "revelation-account-folder"
62 STOCK_ENTRY_FOLDER_OPEN = "folder-open" # "revelation-account-folder-open"
63 STOCK_ENTRY_CREDITCARD = "x-office-contact" # "revelation-account-creditcard"
64 STOCK_ENTRY_CRYPTOKEY = "dialog-password" # "revelation-account-cryptokey"
65 STOCK_ENTRY_DATABASE = "server-database" # "revelation-account-database"
66 STOCK_ENTRY_DOOR = "changes-allow" # "revelation-account-door"
67 STOCK_ENTRY_EMAIL = "emblem-mail" # "revelation-account-email"
68 STOCK_ENTRY_FTP = "system-file-manager" # "revelation-account-ftp"
69 STOCK_ENTRY_GENERIC = "document-new" # "revelation-account-generic"
70 STOCK_ENTRY_PHONE = "phone" # "revelation-account-phone"
71 STOCK_ENTRY_SHELL = "utilities-terminal" # "revelation-account-shell"
72 STOCK_ENTRY_REMOTEDESKTOP = "preferences-desktop-remote-desktop" # "revelation-account-remotedesktop"
73 STOCK_ENTRY_WEBSITE = "web-browser" # "revelation-account-website"
74
75
76 ICON_SIZE_APPLET = Gtk.IconSize.LARGE_TOOLBAR
77 ICON_SIZE_DATAVIEW = Gtk.IconSize.LARGE_TOOLBAR
78 ICON_SIZE_DROPDOWN = Gtk.IconSize.SMALL_TOOLBAR
79 ICON_SIZE_ENTRY = Gtk.IconSize.MENU
80 ICON_SIZE_FALLBACK = Gtk.IconSize.LARGE_TOOLBAR
81 ICON_SIZE_HEADLINE = Gtk.IconSize.LARGE_TOOLBAR
82 ICON_SIZE_LABEL = Gtk.IconSize.MENU
83 ICON_SIZE_LOGO = Gtk.IconSize.DND
84 ICON_SIZE_TREEVIEW = Gtk.IconSize.MENU
85
86 STOCK_ICONS = (
87 ( STOCK_ENTRY_CREDITCARD, "contact-new", ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
88 ( STOCK_ENTRY_CRYPTOKEY, "dialog-password", ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
89 ( STOCK_ENTRY_DATABASE, "package_system", ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
90 ( STOCK_ENTRY_DOOR, "changes-allow", ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
91 ( STOCK_ENTRY_EMAIL, "emblem-mail", ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
92 ( STOCK_ENTRY_FTP, "system-file-manager", ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
93 ( STOCK_ENTRY_GENERIC, "document-new", ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
94 ( STOCK_ENTRY_PHONE, "phone", ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
95 ( STOCK_ENTRY_SHELL, "utilities-terminal", ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
96 ( STOCK_ENTRY_REMOTEDESKTOP,"preferences-desktop-remote-desktop",( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
97 ( STOCK_ENTRY_WEBSITE, "web-browser", ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
98 ( STOCK_ENTRY_FOLDER, "folder", ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
99 ( STOCK_ENTRY_FOLDER_OPEN, "folder-open", ( ICON_SIZE_DATAVIEW, ICON_SIZE_DROPDOWN, ICON_SIZE_ENTRY, ICON_SIZE_TREEVIEW )),
100 )
101
102 STOCK_ITEMS = (
103 ( STOCK_CONTINUE, _('_Continue'), "stock_test-mode" ),
104 ( STOCK_DISCARD, _('_Discard'), Gtk.STOCK_DELETE ),
105 ( STOCK_EDIT, _('_Edit'), Gtk.STOCK_EDIT ),
106 ( STOCK_EXPORT, _('_Export'), Gtk.STOCK_EXECUTE ),
107 ( STOCK_FOLDER, '', "stock_folder" ),
108 ( STOCK_GENERATE, _('_Generate'), Gtk.STOCK_EXECUTE ),
109 ( STOCK_GOTO, _('_Go to'), Gtk.STOCK_JUMP_TO ),
110 ( STOCK_IMPORT, _('_Import'), Gtk.STOCK_CONVERT ),
111 ( STOCK_LOCK, _('_Lock'), "stock_lock" ),
112 ( STOCK_NEW_ENTRY, _('_Add Entry'), Gtk.STOCK_ADD ),
113 ( STOCK_NEW_FOLDER, _('_Add Folder'), "stock_folder" ),
114 ( STOCK_NEXT, _('Next'), Gtk.STOCK_GO_DOWN ),
115 ( STOCK_PASSWORD_CHANGE,_('_Change'), "stock_lock-ok" ),
116 ( STOCK_PASSWORD_CHECK, _('_Check'), "stock_lock-ok" ),
117 ( STOCK_PASSWORD_STRONG,'', "stock_lock-ok" ),
118 ( STOCK_PASSWORD_WEAK, '', "stock_lock-broken" ),
119 ( STOCK_PREVIOUS, _('Previous'), Gtk.STOCK_GO_UP ),
120 ( STOCK_RELOAD, _('_Reload'), Gtk.STOCK_REFRESH ),
121 ( STOCK_REMOVE, _('Re_move'), Gtk.STOCK_DELETE ),
122 ( STOCK_REPLACE, _('_Replace'), Gtk.STOCK_SAVE_AS ),
123 ( STOCK_UNKNOWN, _('Unknown'), "dialog-question" ),
124 ( STOCK_UNLOCK, _('_Unlock'), "stock_lock-open" ),
125 ( STOCK_UPDATE, _('_Update'), "stock_edit" ),
126 )
127
128
129
130 ##### EXCEPTIONS #####
131
132 class DataError(Exception):
133 "Exception for invalid data"
134 pass
135
136
137
138 ##### FUNCTIONS #####
139
140 def generate_field_display_widget(field, cfg = None, userdata = None):
141 "Generates a widget for displaying a field value"
142
143 if field.datatype == entry.DATATYPE_EMAIL:
144 widget = LinkButton("mailto:%s" % field.value, util.escape_markup(field.value))
145
146 elif field.datatype == entry.DATATYPE_PASSWORD:
147 widget = PasswordLabel(util.escape_markup(field.value), cfg, userdata)
148
149 elif field.datatype == entry.DATATYPE_URL:
150 widget = LinkButton(field.value, util.escape_markup(field.value))
151
152 else:
153 widget = Label(util.escape_markup(field.value))
154 widget.set_selectable(True)
155
156 return widget
157
158
159 def generate_field_edit_widget(field, cfg = None, userdata = None):
160 "Generates a widget for editing a field"
161
162 if type(field) == entry.PasswordField:
163 widget = PasswordEntryGenerate(None, cfg, userdata)
164
165 elif type(field) == entry.UsernameField:
166 widget = Gtk.ComboBox.new_with_entry()
167 setup_comboboxentry(widget, userdata)
168
169 elif field.datatype == entry.DATATYPE_FILE:
170 widget = FileEntry()
171
172 elif field.datatype == entry.DATATYPE_PASSWORD:
173 widget = PasswordEntry(None, cfg, userdata)
174
175 else:
176 widget = Entry()
177
178 widget.set_text(field.value or "")
179
180 return widget
181
182 def setup_comboboxentry(widget, userdata=None):
183 widget.entry = widget.get_child()
184 widget.entry.set_activates_default(True)
185
186 widget.set_text = widget.entry.set_text
187 widget.get_text = widget.entry.get_text
188
189 widget.model = Gtk.ListStore(GObject.TYPE_STRING)
190 widget.set_model(widget.model)
191 widget.set_entry_text_column(0)
192
193 widget.completion = Gtk.EntryCompletion()
194 widget.completion.set_model(widget.model)
195 widget.completion.set_text_column(0)
196 widget.completion.set_minimum_key_length(1)
197 widget.entry.set_completion(widget.completion)
198
199 def set_values(vlist):
200 "Sets the values for the dropdown"
201
202 widget.model.clear()
203
204 for item in vlist:
205 widget.model.append((item,))
206
207 widget.set_values = set_values
208
209 if userdata is not None:
210 widget.set_values(userdata)
211
212
213 ##### CONTAINERS #####
214
215 class HBox(Gtk.HBox):
216 "A horizontal container"
217
218 def __init__(self, *args):
219 Gtk.HBox.__init__(self)
220
221 self.set_spacing(6)
222 self.set_border_width(0)
223
224 for widget in args:
225 self.pack_start(widget, True, True, 0)
226
227
228
229 class HButtonBox(Gtk.HButtonBox):
230 "A horizontal button box"
231
232 def __init__(self, *args):
233 Gtk.HButtonBox.__init__(self)
234
235 self.set_layout(Gtk.ButtonBoxStyle.SPREAD)
236 self.set_spacing(12)
237
238 for button in args:
239 self.pack_start(button, True, True, 0)
240
241
242
243 class VBox(Gtk.VBox):
244 "A vertical container"
245
246 def __init__(self, *args):
247 Gtk.VBox.__init__(self)
248
249 self.set_spacing(6)
250 self.set_border_width(0)
251
252 for widget in args:
253 self.pack_start(widget, True, True, 0)
254
255
256
257 class Notebook(Gtk.Notebook):
258 "A notebook (tabbed view)"
259
260 def __init__(self):
261 Gtk.Notebook.__init__(self)
262
263
264 def create_page(self, title):
265 "Creates a notebook page"
266
267 page = NotebookPage()
268 self.append_page(page, Label(title))
269
270 return page
271
272
273
274 class NotebookPage(VBox):
275 "A notebook page"
276
277 def __init__(self):
278 VBox.__init__(self)
279
280 self.sizegroup = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
281 self.set_border_width(12)
282 self.set_spacing(18)
283
284
285 def add_section(self, title, description = None):
286 "Adds an input section to the notebook"
287
288 section = InputSection(title, description, self.sizegroup)
289 self.pack_start(section, False, False, 0)
290
291 return section
292
293
294
295 class ScrolledWindow(Gtk.ScrolledWindow):
296 "A scrolled window for partially displaying a child widget"
297
298 def __init__(self, contents = None):
299 Gtk.ScrolledWindow.__init__(self)
300
301 self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
302
303 if contents is not None:
304 self.add(contents)
305
306
307
308 class Toolbar(Gtk.Toolbar):
309 "A Toolbar subclass"
310
311 def append_space(self):
312 "Appends a space to the toolbar"
313
314 space = Gtk.SeparatorToolItem()
315 space.set_draw(False)
316
317 self.insert(space, -1)
318
319
320 def append_widget(self, widget, tooltip = None):
321 "Appends a widget to the toolbar"
322
323 toolitem = Gtk.ToolItem()
324 toolitem.add(widget)
325
326 if tooltip != None:
327 toolitem.set_tooltip_text(tooltip)
328
329 self.insert(toolitem, -1)
330
331
332
333 class InputSection(VBox):
334 "A section of input fields"
335
336 def __init__(self, title = None, description = None, sizegroup = None):
337 VBox.__init__(self)
338
339 self.title = None
340 self.desc = None
341 self.sizegroup = sizegroup
342
343 if title is not None:
344 self.title = Label("<span weight=\"bold\">%s</span>" % util.escape_markup(title))
345 self.pack_start(self.title, False, True, 0)
346
347 if description is not None:
348 self.desc = Label(util.escape_markup(description))
349 self.pack_start(self.desc, False, True, 0)
350
351 if sizegroup is None:
352 self.sizegroup = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
353
354
355 def append_widget(self, title, widget, indent = True):
356 "Adds a widget to the section"
357
358 row = HBox()
359 row.set_spacing(12)
360 self.pack_start(row, False, False, 0)
361
362 if self.title is not None and indent == True:
363 row.pack_start(Label(""), False, False, 0)
364
365 if title is not None:
366 label = Label("%s:" % util.escape_markup(title))
367 self.sizegroup.add_widget(label)
368 row.pack_start(label, False, False, 0)
369
370 row.pack_start(widget, True, True, 0)
371
372
373 def clear(self):
374 "Removes all widgets"
375
376 for child in self.get_children():
377 if child not in ( self.title, self.desc ):
378 child.destroy()
379
380
381
382 ##### DISPLAY WIDGETS #####
383
384 class EventBox(Gtk.EventBox):
385 "A container which handles events for a widget (for tooltips etc)"
386
387 def __init__(self, widget = None):
388 Gtk.EventBox.__init__(self)
389
390 if widget is not None:
391 self.add(widget)
392
393
394
395 class Image(Gtk.Image):
396 "A widget for displaying an image"
397
398 def __init__(self, stock = None, size = None):
399 Gtk.Image.__init__(self)
400
401 if stock is not None:
402 self.set_from_icon_name(stock, size)
403
404
405
406 class ImageLabel(HBox):
407 "A label with an image"
408
409 def __init__(self, text = None, stock = None, size = ICON_SIZE_LABEL):
410 HBox.__init__(self)
411
412 self.image = Image()
413 self.pack_start(self.image, False, True, 0)
414
415 self.label = Label(text)
416 self.pack_start(self.label, True, True, 0)
417
418 if text != None:
419 self.set_text(text)
420
421 if stock != None:
422 self.set_stock(stock, size)
423
424
425 def set_ellipsize(self, ellipsize):
426 "Sets label ellisization"
427
428 self.label.set_ellipsize(ellipsize)
429
430
431 def set_stock(self, stock, size):
432 "Sets the image"
433
434 self.image.set_from_icon_name(stock, size)
435
436
437 def set_text(self, text):
438 "Sets the label text"
439
440 self.label.set_text(text)
441
442
443
444 class Label(Gtk.Label):
445 "A text label"
446
447 def __init__(self, text = None, justify = Gtk.Justification.LEFT):
448 Gtk.Label.__init__(self)
449
450 self.set_text(text)
451 self.set_justify(justify)
452 self.set_use_markup(True)
453 self.set_line_wrap(True)
454
455 self.set_valign(Gtk.Align.CENTER)
456 if justify == Gtk.Justification.LEFT:
457 self.set_halign(Gtk.Align.START)
458
459 elif justify == Gtk.Justification.CENTER:
460 self.set_halign(Gtk.Align.CENTER)
461
462 elif justify == Gtk.Justification.RIGHT:
463 self.set_halign(Gtk.Align.END)
464
465
466 def set_text(self, text):
467 "Sets the text of the label"
468
469 if text is None:
470 Gtk.Label.set_text(self, "")
471
472 else:
473 Gtk.Label.set_markup(self, text)
474
475
476
477 class PasswordLabel(EventBox):
478 "A label for displaying passwords"
479
480 def __init__(self, password = "", cfg = None, clipboard = None, justify = Gtk.Justification.LEFT):
481 EventBox.__init__(self)
482
483 self.password = util.unescape_markup(password)
484 self.config = cfg
485 self.clipboard = clipboard
486
487 self.label = Label(util.escape_markup(self.password), justify)
488 self.label.set_selectable(True)
489 self.add(self.label)
490
491 self.show_password(cfg.get_boolean("view-passwords"))
492 self.config.connect('changed::view-passwords', lambda w, k: self.show_password(w.get_boolean(k)))
493
494 self.connect("button-press-event", self.__cb_button_press)
495 self.connect("drag-data-get", self.__cb_drag_data_get)
496
497
498 def __cb_drag_data_get(self, widget, context, selection, info, timestamp, data = None):
499 "Provides data for a drag operation"
500
501 selection.set_text(self.password, -1)
502
503
504 def __cb_button_press(self, widget, data = None):
505 "Populates the popup menu"
506
507 if self.label.get_selectable() == True:
508 return False
509
510 elif data.button == 3:
511 menu = Menu()
512
513 menuitem = ImageMenuItem(Gtk.STOCK_COPY, _('Copy password'))
514 menuitem.connect("activate", lambda w: self.clipboard.set([self.password], True))
515 menu.append(menuitem)
516
517 menu.show_all()
518 menu.popup_at_pointer(data)
519
520 return True
521
522
523 def set_ellipsize(self, ellipsize):
524 "Sets ellipsize for the label"
525
526 self.label.set_ellipsize(ellipsize)
527
528
529 def show_password(self, show = True):
530 "Sets whether to display the password"
531
532 if show == True:
533 self.label.set_text(util.escape_markup(self.password))
534 self.label.set_selectable(True)
535 self.drag_source_unset()
536
537 else:
538 self.label.set_text(Gtk.Entry().get_invisible_char()*6)
539 self.label.set_selectable(False)
540
541 self.drag_source_set(
542 Gdk.ModifierType.BUTTON1_MASK,
543 [
544 Gtk.TargetEntry.new("text/plain", 0, 0),
545 Gtk.TargetEntry.new("TEXT", 0, 1),
546 Gtk.TargetEntry.new("STRING", 0, 2),
547 Gtk.TargetEntry.new("COMPOUND TEXT", 0, 3),
548 Gtk.TargetEntry.new("UTF8_STRING", 0, 4)
549 ],
550 Gdk.DragAction.COPY
551 )
552
553
554
555 class EditableTextView(Gtk.ScrolledWindow):
556 "An editable text view"
557
558 def __init__(self, buffer = None, text = None):
559
560 Gtk.ScrolledWindow.__init__(self)
561 self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
562 self.set_shadow_type(Gtk.ShadowType.ETCHED_OUT)
563 self.textview = Gtk.TextView(buffer=buffer)
564 self.textbuffer = self.textview.get_buffer()
565 self.add(self.textview)
566
567 if text is not None:
568 self.textview.get_buffer().set_text(text)
569
570 def set_text(self, text):
571 "Sets the entry contents"
572
573 if text is None:
574 self.textbuffer.set_text("")
575
576 self.textbuffer.set_text(text)
577
578 def get_text(self):
579 "Returns the text of the entry"
580
581 return self.textbuffer.get_text(self.textbuffer.get_start_iter(), self.textbuffer.get_end_iter(), False)
582
583 class TextView(Gtk.TextView):
584 "A text view"
585
586 def __init__(self, buffer = None, text = None):
587 Gtk.TextView.__init__(self)
588 self.set_buffer(buffer)
589
590 self.set_editable(False)
591 self.set_wrap_mode(Gtk.WrapMode.NONE)
592 self.set_cursor_visible(False)
593 self.modify_font(Pango.FontDescription("Monospace"))
594
595 if text is not None:
596 self.get_buffer().set_text(text)
597
598
599
600 ##### TEXT ENTRIES #####
601
602 class Entry(Gtk.Entry):
603 "A normal text entry"
604
605 def __init__(self, text = None):
606 Gtk.Entry.__init__(self)
607
608 self.set_activates_default(True)
609 self.set_text(text)
610
611
612 def set_text(self, text):
613 "Sets the entry contents"
614
615 if text is None:
616 text = ""
617
618 Gtk.Entry.set_text(self, text)
619
620
621
622 class FileEntry(HBox):
623 "A file entry"
624
625 def __init__(self, title = None, file = None, type = Gtk.FileChooserAction.OPEN):
626 HBox.__init__(self)
627
628 self.title = title is not None and title or _('Select File')
629 self.type = type
630
631 self.entry = Entry()
632 self.entry.connect("changed", lambda w: self.emit("changed"))
633 self.pack_start(self.entry, True, True, 0)
634
635 self.button = Button(_('Browse...'), self.__cb_filesel)
636 self.pack_start(self.button, False, False, 0)
637
638 if file is not None:
639 self.set_filename(file)
640
641
642 def __cb_filesel(self, widget, data = None):
643 "Displays a file selector when Browse is pressed"
644
645 try:
646 fsel = dialog.FileSelector(None, self.title, self.type)
647 file = self.get_filename()
648
649 if file != None:
650 fsel.set_filename(file)
651
652 self.set_filename(fsel.run())
653
654 except dialog.CancelError:
655 pass
656
657
658 def get_filename(self):
659 "Gets the current filename"
660
661 return io.file_normpath(self.entry.get_text())
662
663
664 def get_text(self):
665 "Wrapper to emulate Entry"
666
667 return self.entry.get_text()
668
669
670 def set_filename(self, filename):
671 "Sets the current filename"
672
673 self.entry.set_text(io.file_normpath(filename))
674 self.entry.set_position(-1)
675
676
677 def set_text(self, text):
678 "Wrapper to emulate Entry"
679
680 self.entry.set_text(text)
681
682
683 GObject.type_register(FileEntry)
684 GObject.signal_new("changed", FileEntry, GObject.SignalFlags.ACTION,
685 GObject.TYPE_BOOLEAN, ())
686
687
688
689 class PasswordEntry(Gtk.Entry):
690 "An entry for editing a password (follows the 'show passwords' preference)"
691
692 def __init__(self, password = None, cfg = None, clipboard = None):
693 Gtk.Entry.__init__(self)
694 self.set_visibility(False)
695 if password:
696 self.set_text(password)
697
698 self.autocheck = True
699 self.config = cfg
700 self.clipboard = clipboard
701
702 self.connect("changed", self.__cb_check_password)
703 self.connect("populate-popup", self.__cb_popup)
704
705 if cfg != None:
706 self.config.bind('view-passwords', self, "visibility", Gio.SettingsBindFlags.DEFAULT)
707
708
709 def __cb_check_password(self, widget, data = None):
710 "Callback for changed, checks the password"
711
712 if self.autocheck == False:
713 return
714
715 password = self.get_text()
716
717 if len(password) == 0:
718 self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,None)
719
720 else:
721 try:
722 util.check_password(password)
723
724 except ValueError as reason:
725 self.set_password_strong(False, _('The password %s') % str(reason))
726
727 else:
728 self.set_password_strong(True, _('The password seems good'))
729
730
731 def __cb_popup(self, widget, menu):
732 "Populates the popup menu"
733
734 if self.clipboard != None:
735 menuitem = ImageMenuItem(Gtk.STOCK_COPY, _('Copy password'))
736 menuitem.connect("activate", lambda w: self.clipboard.set([self.get_text()], True))
737
738 menu.insert(menuitem, 2)
739
740 menu.show_all()
741
742
743 def set_password_strong(self, strong, reason = ""):
744 "Sets whether the password is strong or not"
745
746 self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, strong and STOCK_PASSWORD_STRONG or STOCK_PASSWORD_WEAK)
747 self.set_icon_tooltip_text(Gtk.EntryIconPosition.SECONDARY, reason)
748
749
750
751 class PasswordEntryGenerate(HBox):
752 "A password entry with a generator button"
753
754 def __init__(self, password = None, cfg = None, clipboard = None):
755 HBox.__init__(self)
756 self.config = cfg
757
758 self.pwentry = PasswordEntry(password, cfg, clipboard)
759 self.pack_start(self.pwentry, True, True, 0)
760
761 self.button = Button(_('Generate'), lambda w: self.generate())
762 self.pack_start(self.button, False, False, 0)
763
764 self.entry = self.pwentry
765
766
767 def generate(self):
768 "Generates a password for the entry"
769
770 password = util.generate_password(self.config.get_int("passwordgen-length"), self.config.get_boolean("passwordgen-punctuation"))
771 self.pwentry.set_text(password)
772
773
774 def get_text(self):
775 "Wrapper for the entry"
776
777 return self.pwentry.get_text()
778
779
780 def set_text(self, text):
781 "Wrapper for the entry"
782
783 self.pwentry.set_text(text)
784
785
786
787 class SpinEntry(Gtk.SpinButton):
788 "An entry for numbers"
789
790 def __init__(self, adjustment = None, climb_rate = 0.0, digits = 0):
791 Gtk.SpinButton.__init__(self)
792 self.configure(adjustment, climb_rate, digits)
793
794 self.set_increments(1, 5)
795 self.set_range(0, 100000)
796 self.set_numeric(True)
797
798
799
800 ##### BUTTONS #####
801
802 class Button(Gtk.Button):
803 "A normal button"
804
805 def __init__(self, label, callback = None):
806 Gtk.Button.__init__(self, label)
807
808 self.set_use_stock(True)
809
810 if callback is not None:
811 self.connect("clicked", callback)
812
813
814
815 class CheckButton(Gtk.CheckButton):
816 "A checkbutton"
817
818 def __init__(self, label = None):
819 Gtk.CheckButton.__init__(self, label)
820
821
822
823 class DropDown(Gtk.ComboBox):
824 "A dropdown button"
825
826 def __init__(self, icons = False):
827 Gtk.ComboBox.__init__(self)
828
829 self.model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_PYOBJECT)
830 self.set_model(self.model)
831
832 if icons == True:
833 cr = Gtk.CellRendererPixbuf()
834 cr.set_fixed_size(Gtk.icon_size_lookup(ICON_SIZE_DROPDOWN)[1] + 5, -1)
835 self.pack_start(cr, False)
836 self.add_attribute(cr, "icon-name", 1)
837
838 cr = Gtk.CellRendererText()
839 self.pack_start(cr, True)
840 self.add_attribute(cr, "text", 0)
841
842 self.connect("realize", self.__cb_show)
843
844
845 def __cb_show(self, widget, data = None):
846 "Callback for when widget is shown"
847
848 if self.get_active() == -1:
849 self.set_active(0)
850
851
852 def append_item(self, text, stock = None, data = None):
853 "Appends an item to the dropdown"
854
855 self.model.append( ( text, stock, data ) )
856
857
858 def delete_item(self, index):
859 "Removes an item from the dropdown"
860
861 if self.model.iter_n_children(None) > index:
862 iter = self.model.iter_nth_child(None, index)
863 self.model.remove(iter)
864
865
866 def get_active_item(self):
867 "Returns a tuple with data for the current item"
868
869 iter = self.model.iter_nth_child(None, self.get_active())
870 return self.model.get(iter, 0, 1, 2)
871
872
873 def get_item(self, index):
874 "Returns data for an item"
875
876 return self.model.get(self.model.iter_nth_child(None, index), 0, 1, 2)
877
878
879 def get_num_items(self):
880 "Returns the number of items in the dropdown"
881
882 return self.model.iter_n_children(None)
883
884
885 def insert_item(self, index, text, stock = None, data = None):
886 "Inserts an item in the dropdown"
887
888 self.model.insert(index, ( text, stock, data ) )
889
890
891
892 class EntryDropDown(DropDown):
893 "An entry type dropdown"
894
895 def __init__(self):
896 DropDown.__init__(self, True)
897
898 for e in entry.ENTRYLIST:
899 if e != entry.FolderEntry:
900 self.append_item(e().typename, e().icon, e)
901
902
903 def get_active_type(self):
904 "Get the currently active type"
905
906 item = self.get_active_item()
907
908 if item is not None:
909 return item[2]
910
911
912 def set_active_type(self, entrytype):
913 "Set the active type"
914
915 for i in range(self.model.iter_n_children(None)):
916 iter = self.model.iter_nth_child(None, i)
917
918 if self.model.get_value(iter, 2) == entrytype:
919 self.set_active(i)
920
921
922 class FileButton(Gtk.FileChooserButton):
923 "A file chooser button"
924
925 def __init__(self, title = None, file = None, type = Gtk.FileChooserAction.OPEN):
926 Gtk.FileChooserButton.__init__(self, title)
927 self.set_action(type)
928 self.set_local_only(False)
929
930 if file != None:
931 self.set_filename(file)
932
933
934 def get_filename(self):
935 "Gets the filename"
936
937 return io.file_normpath(self.get_uri())
938
939
940 def set_filename(self, filename):
941 "Sets the filename"
942
943 filename = io.file_normpath(filename)
944
945 if filename != io.file_normpath(self.get_filename()):
946 Gtk.FileChooserButton.set_filename(self, filename)
947
948
949 class LinkButton(Gtk.LinkButton):
950 "A link button"
951
952 def __init__(self, url, label):
953 Gtk.LinkButton.__init__(self, url, label)
954 self.set_alignment(0, 0.5)
955
956 self.label = self.get_children()[0]
957
958 "If URI is too long reduce it for the label"
959 if len(label) > 60:
960 self.label.set_text(label[0:59] + " (...)")
961
962 def set_ellipsize(self, ellipsize):
963 "Sets ellipsize for label"
964
965 self.label.set_ellipsize(ellipsize)
966
967
968 def set_justify(self, justify):
969 "Sets justify for label"
970
971 self.label.set_justify(justify)
972
973
974 class RadioButton(Gtk.RadioButton):
975 "A radio button"
976
977 def __init__(self, group, label):
978 Gtk.RadioButton.__init__(self, group, label)
979
980
981
982 ##### MENUS AND MENU ITEMS #####
983
984 class ImageMenuItem(Gtk.ImageMenuItem):
985 "A menuitem with a stock icon"
986
987 def __init__(self, stock, text = None):
988 Gtk.ImageMenuItem.__init__(self, stock)
989
990 self.label = self.get_children()[0]
991 self.image = self.get_image()
992
993 if text is not None:
994 self.set_text(text)
995
996
997 def set_stock(self, stock):
998 "Set the stock item to use as icon"
999
1000 self.image.set_from_icon_name(stock, Gtk.IconSize.MENU)
1001
1002
1003 def set_text(self, text):
1004 "Set the item text"
1005
1006 self.label.set_text(text)
1007
1008
1009
1010 class Menu(Gtk.Menu):
1011 "A menu"
1012
1013 def __init__(self):
1014 Gtk.Menu.__init__(self)
1015
1016
1017
1018 ##### MISCELLANEOUS WIDGETS #####
1019
1020 class TreeView(Gtk.TreeView):
1021 "A tree display"
1022
1023 def __init__(self, model):
1024 Gtk.TreeView.__init__(self, model=model)
1025 self.set_headers_visible(False)
1026 self.model = model
1027
1028 self.__cbid_drag_motion = None
1029 self.__cbid_drag_end = None
1030
1031 self.selection = self.get_selection()
1032 self.selection.set_mode(Gtk.SelectionMode.MULTIPLE)
1033
1034 self.connect("button-press-event", self.__cb_buttonpress)
1035 self.connect("key-press-event", self.__cb_keypress)
1036
1037
1038 def __cb_buttonpress(self, widget, data):
1039 "Callback for handling mouse clicks"
1040
1041 path = self.get_path_at_pos(int(data.x), int(data.y))
1042
1043 # handle click outside entry
1044 if path is None:
1045 self.unselect_all()
1046
1047 # handle doubleclick
1048 if data.button == 1 and data.type == Gdk.EventType._2BUTTON_PRESS and path != None:
1049 iter = self.model.get_iter(path[0])
1050 self.toggle_expanded(iter)
1051
1052 if iter != None:
1053 self.emit("doubleclick", iter)
1054
1055 # display popup on right-click
1056 elif data.button == 3:
1057 if path != None and self.selection.iter_is_selected(self.model.get_iter(path[0])) == False:
1058 self.set_cursor(path[0], path[1], False)
1059
1060 self.emit("popup", data)
1061
1062 return True
1063
1064 # handle drag-and-drop of multiple rows
1065 elif self.__cbid_drag_motion is None and data.button in ( 1, 2 ) and data.type == Gdk.EventType.BUTTON_PRESS and path != None and self.selection.iter_is_selected(self.model.get_iter(path[0])) == True and len(self.get_selected()) > 1:
1066 self.__cbid_drag_motion = self.connect("motion-notify-event", self.__cb_drag_motion, data.copy() )
1067 self.__cbid_drag_end = self.connect("button-release-event", self.__cb_button_release, data.copy() )
1068
1069 return True
1070
1071
1072 def __cb_button_release(self, widget, data, userdata = None):
1073 "Ends a drag"
1074
1075 self.emit("button-press-event", userdata)
1076 self.__drag_check_end()
1077
1078
1079 def __cb_drag_motion(self, widget, data, userdata = None):
1080 "Monitors drag motion"
1081
1082 if self.drag_check_threshold(int(userdata.x), int(userdata.y), int(data.x), int(data.y)) == True:
1083 self.__drag_check_end()
1084 uritarget = Gtk.TargetEntry.new( "revelation/treerow", Gtk.TargetFlags.SAME_APP | Gtk.TargetFlags.SAME_WIDGET, 0)
1085 self.drag_begin_with_coordinates( Gtk.TargetList([uritarget]), Gdk.DragAction.MOVE, userdata.button.button, userdata, userdata.x, userdata.y)
1086
1087
1088 def __cb_keypress(self, widget, data = None):
1089 "Callback for handling key presses"
1090
1091 # expand/collapse node on space
1092 if data.keyval == Gdk.KEY_space:
1093 self.toggle_expanded(self.get_active())
1094
1095
1096 def __drag_check_end(self):
1097 "Ends a drag check"
1098
1099 self.disconnect(self.__cbid_drag_motion)
1100 self.disconnect(self.__cbid_drag_end)
1101
1102 self.__cbid_drag_motion = None
1103 self.__cbid_drag_end = None
1104
1105
1106 def collapse_row(self, iter):
1107 "Collapse a tree row"
1108
1109 Gtk.TreeView.collapse_row(self, self.model.get_path(iter))
1110
1111
1112 def expand_row(self, iter):
1113 "Expand a tree row"
1114
1115 if iter is not None and self.model.iter_n_children(iter) > 0:
1116 Gtk.TreeView.expand_row(self, self.model.get_path(iter), False)
1117
1118
1119 def expand_to_iter(self, iter):
1120 "Expand all items up to and including a given iter"
1121
1122 path = self.model.get_path(iter)
1123
1124 for i in range(len(path)):
1125 iter = self.model.get_iter(path[0:i])
1126 self.expand_row(iter)
1127
1128
1129 def get_active(self):
1130 "Get the currently active row"
1131
1132 if self.model is None:
1133 return None
1134
1135 iter = self.model.get_iter(self.get_cursor()[0])
1136
1137 if iter is None or self.selection.iter_is_selected(iter) == False:
1138 return None
1139
1140 return iter
1141
1142 def get_selected(self):
1143 "Get a list of currently selected rows"
1144
1145 list = []
1146 self.selection.selected_foreach(lambda model, path, iter: list.append(iter))
1147
1148 return list
1149
1150
1151 def select(self, iter):
1152 "Select a particular row"
1153
1154 if iter is None:
1155 self.unselect_all()
1156
1157 else:
1158 self.expand_to_iter(iter)
1159 self.set_cursor(self.model.get_path(iter))
1160
1161
1162 def select_all(self):
1163 "Select all rows in the tree"
1164
1165 self.selection.select_all()
1166 self.selection.emit("changed")
1167 self.emit("cursor_changed")
1168
1169
1170 def set_model(self, model):
1171 "Change the tree model which is being displayed"
1172
1173 Gtk.TreeView.set_model(self, model)
1174 self.model = model
1175
1176
1177 def toggle_expanded(self, iter):
1178 "Toggle the expanded state of a row"
1179
1180 if iter is None:
1181 return
1182
1183 elif self.row_expanded(self.model.get_path(iter)):
1184 self.collapse_row(iter)
1185
1186 else:
1187 self.expand_row(iter)
1188
1189
1190 def unselect_all(self):
1191 "Unselect all rows in the tree"
1192
1193 self.selection.unselect_all()
1194 self.selection.emit("changed")
1195 self.emit("cursor_changed")
1196 self.emit("unselect_all")
1197
1198
1199 GObject.signal_new("doubleclick", TreeView, GObject.SignalFlags.ACTION,
1200 GObject.TYPE_BOOLEAN, (GObject.TYPE_PYOBJECT, ))
1201 GObject.signal_new("popup", TreeView, GObject.SignalFlags.ACTION,
1202 GObject.TYPE_BOOLEAN, (GObject.TYPE_PYOBJECT, ))
1203
1204
1205
1206 class EntryTree(TreeView):
1207 "An entry tree"
1208
1209 def __init__(self, entrystore):
1210 TreeView.__init__(self, entrystore)
1211
1212 column = Gtk.TreeViewColumn()
1213 self.append_column(column)
1214
1215 cr = Gtk.CellRendererPixbuf()
1216 column.pack_start(cr, False)
1217 column.add_attribute(cr, "icon-name", data.COLUMN_ICON)
1218 cr.set_property("stock-size", ICON_SIZE_TREEVIEW)
1219
1220 cr = Gtk.CellRendererText()
1221 column.pack_start(cr, True)
1222 column.add_attribute(cr, "text", data.COLUMN_NAME)
1223
1224 self.connect("doubleclick", self.__cb_doubleclick)
1225 self.connect("row-expanded", self.__cb_row_expanded)
1226 self.connect("row-collapsed", self.__cb_row_collapsed)
1227
1228
1229 def __cb_doubleclick(self, widget, iter):
1230 "Stop doubleclick emission on folder"
1231
1232 if type(self.model.get_entry(iter)) == entry.FolderEntry:
1233 self.stop_emission("doubleclick")
1234
1235
1236 def __cb_row_collapsed(self, object, iter, extra):
1237 "Updates folder icons when collapsed"
1238
1239 self.model.folder_expanded(iter, False)
1240
1241
1242 def __cb_row_expanded(self, object, iter, extra):
1243 "Updates folder icons when expanded"
1244
1245 # make sure all children are collapsed (some may have lingering expand icons)
1246 for i in range(self.model.iter_n_children(iter)):
1247 child = self.model.iter_nth_child(iter, i)
1248
1249 if self.row_expanded(self.model.get_path(child)) == False:
1250 self.model.folder_expanded(child, False)
1251
1252 self.model.folder_expanded(iter, True)
1253
1254
1255 def set_model(self, model):
1256 "Sets the model displayed by the tree view"
1257
1258 TreeView.set_model(self, model)
1259
1260 if model is None:
1261 return
1262
1263 for i in range(model.iter_n_children(None)):
1264 model.folder_expanded(model.iter_nth_child(None, i), False)
1265
1266
1267
1268 class Statusbar(Gtk.Statusbar):
1269 "An application statusbar"
1270
1271 def __init__(self):
1272 Gtk.Statusbar.__init__(self)
1273 self.contextid = self.get_context_id("statusbar")
1274
1275
1276 def clear(self):
1277 "Clears the statusbar"
1278
1279 self.pop(self.contextid)
1280
1281
1282 def set_status(self, text):
1283 "Displays a text in the statusbar"
1284
1285 self.clear()
1286 self.push(self.contextid, text or "")
1287
1288
1289
1290 ##### ACTION HANDLING #####
1291
1292 class Action(Gtk.Action):
1293 "UI Manager Action"
1294
1295 def __init__(self, name, label = None, tooltip = None, stock = "", important = False):
1296 Gtk.Action.__init__(self, name, label, tooltip, stock)
1297
1298 if important == True:
1299 self.set_property("is-important", True)
1300
1301
1302
1303 class ActionGroup(Gtk.ActionGroup):
1304 "UI Manager Actiongroup"
1305
1306 def add_action(self, action, accel = None):
1307 "Adds an action to the actiongroup"
1308
1309 if accel is None:
1310 Gtk.ActionGroup.add_action(self, action)
1311
1312 else:
1313 self.add_action_with_accel(action, accel)
1314
1315
1316
1317 class ToggleAction(Gtk.ToggleAction):
1318 "A toggle action item"
1319
1320 def __init__(self, name, label, tooltip = None, stock = None):
1321 Gtk.ToggleAction.__init__(self, name, label, tooltip, stock)
1322
1323
1324
1325 class UIManager(Gtk.UIManager):
1326 "UI item manager"
1327
1328 def __init__(self):
1329 Gtk.UIManager.__init__(self)
1330
1331 self.connect("connect-proxy", self.__cb_connect_proxy)
1332
1333
1334 def __cb_connect_proxy(self, uimanager, action, widget):
1335 "Callback for connecting proxies to an action"
1336
1337 if type(widget) in ( Gtk.MenuItem, Gtk.ImageMenuItem, Gtk.CheckMenuItem ):
1338 widget.tooltip = action.get_property("tooltip")
1339
1340 else:
1341 widget.set_property("label", widget.get_property("label").replace("...", ""))
1342
1343
1344 def add_ui_from_file(self, file):
1345 "Loads ui from a file"
1346
1347 try:
1348 Gtk.UIManager.add_ui_from_file(self, file)
1349
1350 except GObject.GError:
1351 raise IOError
1352
1353
1354 def append_action_group(self, actiongroup):
1355 "Appends an action group"
1356
1357 Gtk.UIManager.insert_action_group(self, actiongroup, len(self.get_action_groups()))
1358
1359
1360 def get_action(self, name):
1361 "Looks up an action in the managers actiongroups"
1362
1363 for actiongroup in self.get_action_groups():
1364 action = actiongroup.get_action(name)
1365
1366 if action is not None:
1367 return action
1368
1369
1370 def get_action_group(self, name):
1371 "Returns the named action group"
1372
1373 for actiongroup in self.get_action_groups():
1374 if actiongroup.get_name() == name:
1375 return actiongroup
1376
1377
1378
1379 ##### APPLICATION COMPONENTS #####
1380
1381 class AppWindow(Gtk.ApplicationWindow):
1382 "An application window"
1383
1384 def __init__(self, *args, **kwargs):
1385 super().__init__(*args, **kwargs)
1386
1387 class App(Gtk.Application):
1388 "An application"
1389
1390 def __init__(self, appname):
1391 Gtk.Application.__init__(self,
1392 application_id='info.olasagasti.revelation')
1393
1394 self.toolbars = {}
1395
1396
1397 def __connect_menu_statusbar(self, menu):
1398 "Connects a menus items to the statusbar"
1399
1400 for item in menu.get_children():
1401 if isinstance(item, Gtk.MenuItem) == True:
1402 item.connect("select", self.cb_menudesc, True)
1403 item.connect("deselect", self.cb_menudesc, False)
1404
1405
1406 def cb_menudesc(self, item, show):
1407 "Displays menu descriptions in the statusbar"
1408
1409 if show == True:
1410 self.statusbar.set_status(item.get_label())
1411
1412
1413 else:
1414 self.statusbar.clear()
1415
1416
1417 def __cb_toolbar_hide(self, widget, name):
1418 "Hides the toolbar dock when the toolbar is hidden"
1419
1420 if name in self.toolbars:
1421 self.toolbars[name].hide()
1422
1423
1424 def __cb_toolbar_show(self, widget, name):
1425 "Shows the toolbar dock when the toolbar is shown"
1426
1427 if name in self.toolbars:
1428 self.toolbars[name].show()
1429
1430
1431 def add_toolbar(self, toolbar, name, band):
1432 "Adds a toolbar"
1433
1434 self.toolbars[name] = toolbar
1435 self.main_vbox.pack_start(toolbar, False, True, 0)
1436
1437 toolbar.connect("show", self.__cb_toolbar_show, name)
1438 toolbar.connect("hide", self.__cb_toolbar_hide, name)
1439
1440 toolbar.show_all()
1441
1442
1443 def get_title(self):
1444 "Returns the app title"
1445
1446 title = Gtk.Window.get_title(self.window)
1447
1448 return title.replace(" - " + config.APPNAME, "")
1449
1450
1451 def popup(self, menu, button, time):
1452 "Displays a popup menu"
1453
1454 # get Gtk.Menu
1455 gmenu = Gtk.Menu.new_from_model(menu)
1456 gmenu.attach_to_widget(self.window,None)
1457
1458 # transfer the tooltips from Gio.Menu to Gtk.Menu
1459 menu_item_index = 0
1460 menu_items=gmenu.get_children()
1461 for sect in range(menu.get_n_items()):
1462 for item in range(menu.get_item_link(sect,'section').get_n_items()):
1463 tooltip_text = menu.get_item_link(sect,'section').get_item_attribute_value(item,'tooltip',None)
1464 if tooltip_text:
1465 tooltip_text = tooltip_text.unpack()
1466 menu_items[menu_item_index].set_tooltip_text(tooltip_text)
1467 menu_item_index += 1
1468 # skip section separator
1469 menu_item_index += 1
1470
1471 self.__connect_menu_statusbar(gmenu)
1472 gmenu.popup(None, None, None, None, button, time)
1473
1474
1475 def set_menus(self, menubar):
1476 "Sets the menubar for the application"
1477
1478 for item in menubar.get_children():
1479 self.__connect_menu_statusbar(item.get_submenu())
1480
1481 self.main_vbox.pack_start(menubar, False, True, 0)
1482
1483
1484 def set_title(self, title):
1485 "Sets the window title"
1486
1487 Gtk.Window.set_title(self.window, title + " - " + config.APPNAME)
1488
1489
1490 def set_toolbar(self, toolbar):
1491 "Sets the application toolbar"
1492
1493 self.main_vbox.pack_start(toolbar, False, True, 0)
1494 toolbar.connect("show", self.__cb_toolbar_show, "Toolbar")
1495 toolbar.connect("hide", self.__cb_toolbar_hide, "Toolbar")
1496
1497 def set_contents(self, widget):
1498 self.main_vbox.pack_start(widget, True, True, 0)
1499
1500
1501
1502 class EntryView(VBox):
1503 "A component for displaying an entry"
1504
1505 def __init__(self, cfg = None, clipboard = None):
1506 VBox.__init__(self)
1507 self.set_spacing(12)
1508 self.set_border_width(12)
1509
1510 self.config = cfg
1511 self.clipboard = clipboard
1512 self.entry = None
1513
1514
1515 def clear(self, force = False):
1516 "Clears the data view"
1517
1518 self.entry = None
1519
1520 for child in self.get_children():
1521 child.destroy()
1522
1523
1524 def display_entry(self, e):
1525 "Displays info about an entry"
1526
1527 self.clear()
1528 self.entry = e
1529
1530 if self.entry is None:
1531 return
1532
1533 # set up metadata display
1534 metabox = VBox()
1535 self.pack_start(metabox)
1536
1537 label = ImageLabel(
1538 "<span size=\"large\" weight=\"bold\">%s</span>" % util.escape_markup(e.name),
1539 e.icon, ICON_SIZE_DATAVIEW
1540 )
1541 label.set_halign(Gtk.Align.CENTER)
1542 label.set_valign(Gtk.Align.CENTER)
1543 metabox.pack_start(label, True, True, 0)
1544
1545 label = Label("<span weight=\"bold\">%s</span>%s" % ( e.typename + (e.description != "" and ": " or ""), util.escape_markup(e.description) ), Gtk.Justification.CENTER)
1546 metabox.pack_start(label, True, True, 0)
1547
1548 # set up field list
1549 fields = [ field for field in e.fields if field.value != "" ]
1550
1551 if len(fields) > 0:
1552 table = Gtk.Grid()
1553 self.pack_start(table)
1554 table.set_column_spacing(10)
1555 table.set_row_spacing(5)
1556
1557 for rowindex, field in zip(range(len(fields)), fields):
1558 label = Label("<span weight=\"bold\">%s: </span>" % util.escape_markup(field.name))
1559 label.set_hexpand(True)
1560 table.attach(label, 0, rowindex, 1, 1)
1561
1562 widget = generate_field_display_widget(field, self.config, self.clipboard)
1563 widget.set_hexpand(True)
1564 table.attach(widget, 1, rowindex, 1, 1)
1565
1566 # notes
1567 label = Label("<span weight=\"bold\">%s</span>%s" % ((e.notes != "" and _("Notes: ") or ""),
1568 util.escape_markup(e.notes) ), Gtk.Justification.LEFT)
1569 self.pack_start(label)
1570
1571 # display updatetime
1572 if type(e) != entry.FolderEntry:
1573 label = Label((_('Updated %s ago') + "\n%s") % ( util.time_period_rough(e.updated, time.time()), time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(e.updated)) ), Gtk.Justification.CENTER)
1574 self.pack_start(label)
1575
1576 self.show_all()
1577
1578
1579 def pack_start(self, widget):
1580 "Adds a widget to the data view"
1581
1582 widget.set_halign(Gtk.Align.CENTER)
1583 widget.set_valign(Gtk.Align.CENTER)
1584 VBox.pack_start(self, widget, False, False, 0)
1585
1586
1587
1588 class Searchbar(Toolbar):
1589 "A toolbar for easy searching"
1590
1591 def __init__(self):
1592 Toolbar.__init__(self)
1593
1594 self.entry = Gtk.SearchEntry()
1595 self.entry.set_tooltip_text(_('Text to search for'))
1596 self.dropdown = EntryDropDown()
1597 self.dropdown.insert_item(0, _('Any type'), "help-about")
1598
1599 box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
1600 Gtk.StyleContext.add_class(box.get_style_context(), "linked")
1601 self.button_prev = Gtk.Button.new_from_icon_name(STOCK_PREVIOUS,
1602 Gtk.IconSize.BUTTON)
1603 self.button_prev.set_tooltip_text(_('Find the previous match'))
1604 self.button_next = Gtk.Button.new_from_icon_name(STOCK_NEXT,
1605 Gtk.IconSize.BUTTON)
1606 self.button_next.set_tooltip_text(_('Find the next match'))
1607
1608 box.add(self.entry)
1609 box.add(self.button_prev)
1610 box.add(self.button_next)
1611 box.add(self.dropdown)
1612
1613 self.append_widget(box)
1614
1615 self.connect("show", self.__cb_show)
1616
1617 self.entry.connect("changed", self.__cb_entry_changed)
1618 self.entry.connect("key-press-event", self.__cb_key_press)
1619
1620 self.button_next.set_sensitive(False)
1621 self.button_prev.set_sensitive(False)
1622
1623
1624 def __cb_entry_changed(self, widget, data = None):
1625 "Callback for entry changes"
1626
1627 s = self.entry.get_text() != ""
1628
1629 self.button_next.set_sensitive(s)
1630 self.button_prev.set_sensitive(s)
1631
1632
1633 def __cb_key_press(self, widget, data = None):
1634 "Callback for key presses"
1635
1636 # return
1637 if data.keyval == Gdk.KEY_Return and widget.get_text() != "":
1638 if (data.state & Gdk.ModifierType.SHIFT_MASK) == Gdk.ModifierType.SHIFT_MASK:
1639 self.button_prev.activate()
1640
1641 else:
1642 self.button_next.activate()
1643
1644 return True
1645
1646
1647 def __cb_show(self, widget, data = None):
1648 "Callback for widget display"
1649
1650 self.entry.select_region(0, -1)
1651 self.entry.grab_focus()
1652