updated on Thu Jan 19 00:16:31 UTC 2012
[aur-mirror.git] / fontmanager / fontmanager.py
blob765b632fdc029d938941326f68f9145a9ff70c0d
1 #!/usr/bin/env python2
2 import gtk
3 import gobject
4 import os
5 import pango
6 import subprocess
7 import libxml2
8 from os.path import exists
10 # Font Manager
11 # (C) 2008 Karl Pickett
12 # License: GPLv3
14 # for future I18n
15 def _(str):
16 return unicode(str)
18 # To install ourselves, we add an include line in the
19 # user font config file (default ~/.fonts.conf)
20 USER_FONT_CONF = "~/.fonts.conf"
21 USER_FONT_CONF_BACKUP = "~/.fonts.conf.fontmanager.save"
23 VERSION = "0.5"
24 PRODUCT_TITLE = _("Font Manager %s") % VERSION
26 FM_DIR = "~/.fontmanager"
27 FM_BLOCK_CONF = os.path.join(FM_DIR, "fontmanager.conf")
28 FM_BLOCK_CONF_TMP = FM_BLOCK_CONF + ".tmp"
29 FM_GROUP_CONF = os.path.join(FM_DIR, "groups.xml")
31 FC_INCLUDE_LINE = "<include ignore_missing=\"yes\">%s</include>" % \
32 FM_BLOCK_CONF
34 TEST_TEXT = _("The Hungry Penguin Ate A Big Fish")
35 TEST_TEXT += _("\nABCDEFGHIJKLMNOPQRSTUVWXYZ")
36 TEST_TEXT += _("\n1234567890")
37 TEST_TEXT += _("\nabcdefghijklmnopqrstuvwxyz")
39 DEFAULT_CUSTOM_TEXT = _("Enter Your Text Here")
41 SCALABLE_SIZES = (200, 150, 100, 72, 48, 36, 24, 18, 14, 12, 10)
43 # What style gets shown first
44 DEFAULT_STYLES = ["Regular", "Roman", "Medium", "Normal", "Book"]
47 UI_XML = """
48 <ui>
49 <menubar name="MenuBar">
50 <menu action="File">
51 <menuitem action="Save"/>
52 <separator/>
53 <menuitem action="Quit"/>
54 </menu>
55 <menu action="Collection">
56 <menuitem action="NewCollection"/>
57 <menuitem action="RenameCollection"/>
58 <separator/>
59 <menuitem action="DeleteCollection"/>
60 <separator/>
61 <menuitem action="TurnOnCollection"/>
62 <menuitem action="TurnOffCollection"/>
63 </menu>
64 <menu action="Font">
65 <menuitem action="Copy"/>
66 <menuitem action="Cut"/>
67 <menuitem action="Paste"/>
68 <menuitem action="Remove"/>
69 <separator/>
70 <menuitem action="TurnOn"/>
71 <menuitem action="TurnOff"/>
72 </menu>
73 <menu action="View">
74 <menuitem action="ViewSample"/>
75 <menuitem action="ViewCustom"/>
76 <separator/>
77 <menuitem action="ViewDetails"/>
78 </menu>
79 <menu action="Help">
80 <menuitem action="About"/>
81 </menu>
82 </menubar>
83 </ui>
84 """
86 # Some globals
87 # Names of system font families as reported by fc-list
88 g_system_families = {}
89 # map of family to list of filenames
90 g_font_files = {}
91 # map of namily name to Family object
92 g_fonts = {}
95 class Pattern(object):
96 __slots__ = ("family", "style")
98 def __init__(self):
99 self.family = self.style = None
101 class Collection (object):
102 __slots__ = ("name", "fonts", "builtin", "enabled")
104 def __init__(self, name):
105 self.name = name
106 self.fonts = []
107 self.builtin = True
108 self.enabled = True
110 def get_label(self):
111 if self.enabled:
112 return on(self.name)
113 else:
114 return off(self.name)
116 def obj_exists(self, obj):
117 for f in self.fonts:
118 if f is obj:
119 return True
120 return False
122 def add(self, obj):
123 # check duplicate reference
124 if self.obj_exists(obj):
125 return
126 self.fonts.append(obj)
128 def get_text(self):
129 return self.name
131 def num_fonts_enabled(self):
132 ret = 0
133 for f in self.fonts:
134 if f.enabled:
135 ret += 1
136 return ret
138 def set_enabled(self, enabled):
139 for f in self.fonts:
140 f.enabled = enabled
142 def set_enabled_from_fonts(self):
143 self.enabled = (self.num_fonts_enabled() > 0)
145 def remove(self, font):
146 self.fonts.remove(font)
149 class Family(object):
150 __slots__ = ("family", "user", "enabled", "pango_family")
152 def __init__(self, family):
153 self.family = family
154 self.user = False
155 self.enabled = True
156 self.pango_family = None
158 def get_label(self):
159 if self.enabled:
160 return on(self.family)
161 else:
162 return off(self.family)
164 def cmp_family(lhs, rhs):
165 return cmp(lhs.family, rhs.family)
168 # XML Helpers
169 def add_patelt_node(parent, type, val):
170 pi = parent.newChild(None, "patelt", None)
171 pi.setProp("name", type)
172 str = pi.newChild(None, "string", val)
174 def get_fontconfig_patterns(node, patterns):
175 for n in node.xpathEval('pattern'):
176 p = Pattern()
177 for c in n.xpathEval('patelt'):
178 name = c.prop("name")
179 if name == "family":
180 p.family = c.xpathEval('string')[0].content
181 if p.family:
182 patterns.append(p)
186 def gtk_markup_escape(str):
187 str = str.replace("&", "&amp;")
188 str = str.replace("<", "&lt;")
189 str = str.replace(">", "&gt;")
190 return str
192 def on(str):
193 str = gtk_markup_escape(str)
194 return "<span weight='heavy'>%s</span>" % str
196 def off(str):
197 str = gtk_markup_escape(str)
198 return "<span weight='ultralight'>%s Off</span>" % str
202 # Font loading
203 def strip_fontconfig_family(family):
204 # remove alt name
205 n = family.find(',')
206 if n > 0:
207 family = family[:n]
208 family = family.replace("\\-", "-")
209 family = family.strip()
210 return family
212 def load_fontconfig_files():
213 cmd = "fc-list : file family"
214 for l in os.popen(cmd).readlines():
215 l = l.strip()
216 if l.find(":") < 0:
217 continue
218 file, family = l.split(":")
219 family = strip_fontconfig_family(family)
220 list = g_font_files.get(family, None)
221 if not list:
222 list = []
223 g_font_files[family] = list
224 list.append(file)
226 def load_fontconfig_system_families():
227 cmd = "HOME= fc-list : family"
228 print "Executing %s..." % cmd
229 for l in os.popen(cmd).readlines():
230 l = l.strip()
231 family = strip_fontconfig_family(l)
232 g_system_families[family] = 1
235 def load_fonts(widget):
236 ctx = widget.get_pango_context()
237 families = ctx.list_families()
238 for f in families:
239 obj = Family(f.get_name())
240 obj.pango_family = f
241 if not g_system_families.has_key(f.get_name()):
242 obj.user = True
243 g_fonts[f.get_name()] = obj
245 def find_font(family):
246 return g_fonts.get(family, None)
249 # Blacklist Code
250 def save_blacklist():
251 doc = libxml2.newDoc("1.0")
252 root = doc.newChild(None, "fontconfig", None)
253 n = root.newChild(None, "selectfont", None)
254 n = n.newChild(None, "rejectfont", None)
256 for font in g_fonts.itervalues():
257 if not font.enabled:
258 p = n.newChild(None, "pattern", None)
259 add_patelt_node(p, "family", font.family)
261 print "Writing to %s" % FM_BLOCK_CONF
262 doc.saveFormatFile(FM_BLOCK_CONF, format=1)
265 def load_blacklist(filename):
266 if not exists(filename):
267 return
269 patterns = []
270 doc = libxml2.parseFile(filename)
271 rejects = doc.xpathEval('//rejectfont')
272 for a in rejects:
273 get_fontconfig_patterns(a, patterns)
274 doc.freeDoc()
276 for p in patterns:
277 set_blacklist(p)
279 def set_blacklist(pattern):
280 font = find_font(pattern.family)
281 if font:
282 font.enabled = False
284 def enable_blacklist():
285 if exists(FM_BLOCK_CONF_TMP):
286 if exists(FM_BLOCK_CONF):
287 os.unlink(FM_BLOCK_CONF)
288 os.rename(FM_BLOCK_CONF_TMP, FM_BLOCK_CONF)
290 def disable_blacklist():
291 if exists(FM_BLOCK_CONF):
292 if exists(FM_BLOCK_CONF_TMP):
293 os.unlink(FM_BLOCK_CONF_TMP)
294 os.rename(FM_BLOCK_CONF, FM_BLOCK_CONF_TMP)
296 def get_filenames(family):
297 ret = []
298 try:
299 pipe = subprocess.Popen(["fc-list", family, "file"],
300 stdout=subprocess.PIPE).stdout
301 for line in pipe:
302 ret.append(line.split(':')[0].strip())
303 except Exception, e:
304 print e
305 return ret
307 # Details dialog
308 def get_font_details_text(family):
309 filenames = g_font_files.get(family, None)
310 str = "%s\n\n" % family
312 if not filenames:
313 str += "No Files Found"
314 else:
315 for f in filenames:
316 st = os.stat(f)
317 str += "%s %d KB\n" % (f, st.st_size / 1024)
319 return str
324 # Gui Code
326 class FontBook(gtk.Window):
327 def __init__(self, parent=None):
328 gtk.Window.__init__(self)
329 self.connect('destroy', lambda *w: self.action_quit(None))
331 self.uimanager = gtk.UIManager()
332 accelgroup = self.uimanager.get_accel_group()
333 self.add_accel_group(accelgroup)
335 self.create_actions()
337 vb = gtk.VBox(False, 0)
338 vb.pack_start(self.menu_bar, False, False, 0)
340 self.DRAG_TARGETS = [("test", gtk.TARGET_SAME_APP, 0)]
341 self.DRAG_ACTIONS = gtk.gdk.ACTION_LINK
343 self.set_title(PRODUCT_TITLE)
344 self.set_default_size(700, 450)
345 #self.set_border_width(8)
347 hbox = gtk.HBox(False, 3)
348 hbox.set_homogeneous(False)
350 w = self.init_collections()
351 hbox.pack_start(w, False)
352 w = self.init_families()
353 hbox.pack_start(w, False)
354 w = self.init_text_view()
355 hbox.pack_start(w)
357 self.copy_buffer = []
358 self.font_tags = []
360 self.custom_text = DEFAULT_CUSTOM_TEXT
361 self.preview_mode = 0
363 load_fontconfig_system_families()
364 load_fontconfig_files()
365 load_fonts(self)
366 load_blacklist(FM_BLOCK_CONF_TMP)
368 vb.pack_start(hbox)
369 self.add(vb)
370 self.create_collections()
371 #self.show_collection(self.collections[0])
372 self.collection_tv.get_selection().select_path(0)
373 self.family_tv.get_selection().select_path(0)
374 self.show_all()
377 def init_collections(self):
378 sw = gtk.ScrolledWindow()
379 sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
380 sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
382 model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,
383 gobject.TYPE_STRING)
385 treeview = gtk.TreeView(model)
386 treeview.set_search_column(2)
387 treeview.get_selection().connect("changed", self.collection_changed)
389 #dnd
390 treeview.connect("drag-data-received", self.drag_data_received)
391 treeview.enable_model_drag_dest(self.DRAG_TARGETS, self.DRAG_ACTIONS)
392 treeview.connect("row-activated", self.collection_activated)
393 treeview.set_row_separator_func(self.is_row_separator_collection)
395 r = gtk.CellRendererText()
396 column = gtk.TreeViewColumn(_('Collection'), r, markup=0)
398 #column.set_sort_column_id(0)
399 treeview.append_column(column)
401 self.collection_tv = treeview
402 sw.add(treeview)
403 return sw
406 def init_families(self):
407 sw = gtk.ScrolledWindow()
408 sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
409 sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
411 model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT,
412 gobject.TYPE_STRING)
414 treeview = gtk.TreeView(model)
415 treeview.set_search_column(2)
416 treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
417 treeview.get_selection().connect("changed", self.font_changed)
418 treeview.connect("row-activated", self.font_activated)
420 # dnd
421 #treeview.connect("drag-data-get", self.drag_data_get)
422 treeview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
423 self.DRAG_TARGETS, self.DRAG_ACTIONS)
425 column = gtk.TreeViewColumn(_('Font'), gtk.CellRendererText(),
426 markup=0)
427 column.set_sort_column_id(2)
428 treeview.append_column(column)
430 self.family_tv = treeview
431 sw.add(treeview)
432 return sw
435 def init_text_view(self):
436 #self.notebook = gtk.Notebook()
438 view = gtk.TextView()
439 sw = gtk.ScrolledWindow()
440 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
441 self.text_view = view
442 self.text_view.set_left_margin(8)
443 self.text_view.set_right_margin(8)
444 self.text_view.set_wrap_mode(gtk.WRAP_WORD_CHAR)
445 sw.add(view)
448 #self.font_label = gtk.Label(_("Preview"))
449 self.style_combo = gtk.combo_box_new_text()
450 self.size_combo = gtk.combo_box_new_text()
451 vb = gtk.VBox()
452 hb = gtk.HBox()
453 vb.pack_start(hb, False)
454 vb.pack_start(sw)
455 #hb.pack_start(self.font_label, False)
456 hb.pack_end(self.style_combo, False)
457 hb.pack_end(self.size_combo, False)
458 self.set_scalable_sizes()
459 self.style_combo.connect("changed", self.style_changed)
460 self.size_combo.connect("changed", self.size_changed)
462 return vb
465 def create_actions(self):
466 g = gtk.ActionGroup('global')
468 add_action(g, "File", None, "_File", None)
469 add_action(g, 'Collection', None, "_Collection", None)
470 add_action(g, 'Font', None, "Font", None)
471 add_action(g, 'View', None, "_View", None)
472 add_action(g, 'Help', None, "_Help", None)
474 add_action(g, 'Quit', gtk.STOCK_QUIT, None,
475 self.action_quit)
476 add_action(g, 'Save', gtk.STOCK_SAVE, None,
477 self.action_save)
479 add_action(g, 'NewCollection', gtk.STOCK_NEW, "_New Collection",
480 self.action_new_collection)
482 add_action(g, 'About', gtk.STOCK_ABOUT, None,
483 self.action_about)
484 self.uimanager.insert_action_group(g, 0)
486 # view
487 g.add_radio_actions([
488 ('ViewSample', None, "_Sample Text", "<Control>1", None, 0),
489 ('ViewCustom', None, "_Custom Text", "<Control>2", None, 1),
490 ('ViewDetails', None, "_Font Information", "<Control>3", None, 2),
491 ], 0, self.preview_mode_changed)
494 # any collection selected
495 g = gtk.ActionGroup('collection_selected')
496 g.set_sensitive(False)
497 add_action(g, 'TurnOnCollection', None, "_Enable Collection",
498 self.action_turn_on_collection)
499 add_action(g, 'TurnOffCollection', None, "_Disable Collection",
500 self.action_turn_off_collection)
501 self.uimanager.insert_action_group(g, 0)
502 self.ag_collection_selected = g
505 # user collection selected
506 g = gtk.ActionGroup('user-collection_selected')
507 g.set_sensitive(False)
508 add_action(g, 'DeleteCollection', None, "Delete Collection",
509 self.action_delete_collection, "<Ctrl>d")
510 add_action(g, 'RenameCollection', None, "Rename Collection",
511 self.action_rename_collection, "<Ctrl>r")
512 self.uimanager.insert_action_group(g, 0)
513 self.ag_user_collection_selected = g
515 g = gtk.ActionGroup('ag-paste')
516 g.set_sensitive(False)
517 add_action(g, 'Paste', gtk.STOCK_PASTE, None,
518 self.action_paste)
519 self.ag_paste = g
520 self.uimanager.insert_action_group(g, 0)
522 g = gtk.ActionGroup('ag-cut')
523 g.set_sensitive(False)
524 add_action(g, 'Cut', gtk.STOCK_CUT, None,
525 self.action_cut)
526 add_action(g, 'Remove', gtk.STOCK_DELETE, "Remove",
527 self.action_remove)
528 self.ag_cut = g
529 self.uimanager.insert_action_group(g, 0)
531 # font selected
532 g = gtk.ActionGroup('font_selected')
533 g.set_sensitive(False)
534 add_action(g, 'TurnOn', None, "_Enable Font(s)",
535 self.action_turn_on)
536 add_action(g, 'TurnOff', None, "_Disable Font(s)",
537 self.action_turn_off)
538 add_action(g, 'Copy', gtk.STOCK_COPY, None,
539 self.action_copy)
540 self.ag_font_selected = g
541 self.uimanager.insert_action_group(g, 0)
543 self.uimanager.add_ui_from_string(UI_XML)
544 self.menu_bar = self.uimanager.get_widget('/MenuBar')
549 # Actions
551 def action_save(self, a):
552 self.save_config()
554 def action_quit(self, a):
555 self.save_config()
556 gtk.main_quit()
558 def collection_name_exists(self, name):
559 for c in self.collections:
560 if c.name == name:
561 return True
562 return False
564 def action_new_collection(self, a):
565 str = _("New Collection")
566 while True:
567 str = self.get_new_collection_name(str)
568 if not str:
569 return
570 if not self.collection_name_exists(str):
571 break
572 c = Collection(str)
573 c.builtin = False
574 self.add_collection(c)
576 def action_delete_collection(self, a):
577 c = self.get_current_collection()
578 self.ag_paste.set_sensitive(False)
579 self.ag_collection_selected.set_sensitive(False)
580 self.collections.remove(c)
581 self.update_views()
583 def action_rename_collection(self, a):
584 c = self.get_current_collection()
585 str = c.name
586 while True:
587 str = self.get_new_collection_name(str)
588 if not str or c.name == str:
589 return
590 if not self.collection_name_exists(str):
591 c.name = str
592 self.update_collection_view()
593 return
595 def action_about(self, a):
596 d = gtk.AboutDialog()
597 d.set_name(PRODUCT_TITLE)
598 d.set_copyright("2008 Karl Pickett/penguindev")
599 d.set_license("GPL3")
600 d.set_website("http://fontmanager.blogspot.com/")
601 d.run()
602 d.destroy()
604 def action_turn_on_collection(self, a):
605 self.enable_collection(True)
607 def action_turn_off_collection(self, a):
608 self.enable_collection(False)
610 def collection_activated(self, tv, path, col):
611 c = self.get_current_collection()
612 self.enable_collection(not c.enabled)
614 def enable_collection(self, enabled):
615 c = self.get_current_collection()
616 if c.builtin and not self.confirm_enable_collection(enabled):
617 return
618 c.set_enabled(enabled)
619 self.update_views()
621 def confirm_enable_collection(self, enabled):
622 d = gtk.Dialog(_("Confirm Action"),
623 self, gtk.DIALOG_MODAL,
624 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
625 gtk.STOCK_OK, gtk.RESPONSE_OK))
626 d.set_default_response(gtk.RESPONSE_CANCEL)
628 c = self.get_current_collection()
629 if enabled:
630 str = _("Are you sure you want to enable the \"%s\" built in collection?") % c.name
631 else:
632 str = _("Are you sure you want to disable the \"%s\" built in collection?") % c.name
634 text = gtk.Label()
635 text.set_text(str)
636 d.vbox.pack_start(text, padding=10)
637 text.show()
639 ret = d.run()
640 d.destroy()
641 return (ret == gtk.RESPONSE_OK)
644 def is_row_separator_collection(self, model, iter):
645 obj = model.get(iter, 1)[0]
646 #print "is_row_separator_collection", obj, iter
647 return (obj is None)
650 # Cut and paste
651 def action_remove(self, a):
652 c = self.get_current_collection()
653 for f in self.iter_selected_fonts():
654 c.remove(f)
655 self.update_views()
657 def action_cut(self, a):
658 self.do_copy(True)
660 def action_copy(self, a):
661 self.do_copy(False)
663 def action_paste(self, a):
664 c = self.get_current_collection()
665 for f in self.copy_buffer:
666 if not c.obj_exists(f):
667 c.add(f)
668 self.add_font_to_view(f)
669 self.update_views()
671 def do_copy(self, cut):
672 c = self.get_current_collection()
673 self.copy_buffer = []
675 for f in self.iter_selected_fonts():
676 self.copy_buffer.append(f)
677 if cut:
678 c.remove(f)
680 self.update_views()
682 if not c.builtin:
683 self.ag_paste.set_sensitive(True)
686 def iter_selected_fonts(self):
687 sel = self.family_tv.get_selection()
688 m, path_list = sel.get_selected_rows()
689 for p in path_list:
690 obj = m[p][1]
691 yield obj
693 def action_turn_on(self, a):
694 for f in self.iter_selected_fonts():
695 f.enabled = True
696 self.update_views()
698 def action_turn_off(self, a):
699 for f in self.iter_selected_fonts():
700 f.enabled = False
701 self.update_views()
703 def font_activated(self, tv, path, col):
704 for f in self.iter_selected_fonts():
705 f.enabled = (not f.enabled)
706 self.update_views()
709 # View Updating
711 def update_views(self):
712 self.update_collection_view()
713 self.update_font_view()
715 def update_collection_view(self):
716 for c in self.collections:
717 c.set_enabled_from_fonts()
719 model = self.collection_tv.get_model()
720 iter = model.get_iter_first()
721 while iter:
722 label, obj = model.get(iter, 0, 1)
723 if not obj:
724 iter = model.iter_next(iter)
725 continue
726 if obj in self.collections:
727 new_label = obj.get_label()
728 if label != new_label:
729 model.set(iter, 0, new_label)
730 iter = model.iter_next(iter)
731 else:
732 if not model.remove(iter):
733 return
735 def update_font_view(self):
736 c = self.get_current_collection()
737 model = self.family_tv.get_model()
738 iter = model.get_iter_first()
739 while iter:
740 label, obj = model.get(iter, 0, 1)
741 if obj in c.fonts:
742 new_label = obj.get_label()
743 if label != new_label:
744 model.set(iter, 0, new_label)
745 iter = model.iter_next(iter)
746 else:
747 if not model.remove(iter):
748 return
750 def preview_mode_changed(self, a, b):
751 if self.preview_mode == 1:
752 self.custom_text = self.get_current_text()
754 self.preview_mode = a.get_current_value()
755 combos_visible = (self.preview_mode != 2)
756 self.size_combo.set_property("visible", combos_visible)
757 self.style_combo.set_property("visible", combos_visible)
758 self.set_preview_text(self.current_descr, False)
760 def get_current_text(self):
761 print "get_current_text"
762 b = self.text_view.get_buffer()
763 return b.get_text(b.get_start_iter(), b.get_end_iter())
766 def get_current_collection(self):
767 sel = self.collection_tv.get_selection()
768 m, iter = sel.get_selected()
769 if not iter:
770 return
771 return m.get(iter, 1)[0]
773 def collection_changed(self, sel):
774 c = self.get_current_collection()
775 if c:
776 print "collection_changed", c.name
777 self.ag_user_collection_selected.set_sensitive(not c.builtin)
778 self.ag_collection_selected.set_sensitive(True)
779 if c.builtin:
780 self.ag_paste.set_sensitive(False)
781 elif self.copy_buffer:
782 self.ag_paste.set_sensitive(True)
783 self.show_collection(c)
784 else:
785 self.ag_user_collection_selected.set_sensitive(False)
786 self.ag_collection_selected.set_sensitive(False)
788 self.show_collection(None)
790 def font_changed(self, sel):
791 tv = self.family_tv
792 m, path_list = tv.get_selection().get_selected_rows()
793 rows = len(path_list)
794 if rows == 0:
795 self.ag_font_selected.set_sensitive(False)
796 self.ag_cut.set_sensitive(False)
797 return
799 if not self.get_current_collection().builtin:
800 self.ag_cut.set_sensitive(True)
802 self.ag_font_selected.set_sensitive(True)
803 if rows > 1:
804 return
806 obj = m[path_list[0]][1]
807 if isinstance(obj, Family):
808 #print "family changed", f.family
809 self.change_font(obj)
811 def get_new_collection_name(self, old_name):
812 d = gtk.Dialog(_("Enter Collection Name"),
813 self, gtk.DIALOG_MODAL,
814 (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
815 gtk.STOCK_OK, gtk.RESPONSE_OK))
816 d.set_default_response(gtk.RESPONSE_OK)
818 text = gtk.Entry()
819 if old_name:
820 text.set_text(old_name)
821 text.set_property("activates-default", True)
822 d.vbox.pack_start(text)
823 text.show()
825 ret = d.run()
826 d.destroy()
827 if ret == gtk.RESPONSE_OK:
828 return text.get_text().strip()
829 return None
832 # DND Stuff
833 def drag_data_received(self, treeview, context, x, y,
834 selection, info, timestamp):
835 #print "drag_data_received"
836 drop_info = treeview.get_dest_row_at_pos(x, y)
837 #print drop_info
838 if drop_info:
839 model = treeview.get_model()
840 path, position = drop_info
842 collection = model[path][1]
843 collection.add(self.get_dragged_font())
845 self.update_views()
848 # GTK only supports dragging a single row? :(
849 def get_dragged_font(self):
850 for f in self.iter_selected_fonts():
851 return f
854 def save_config(self):
855 if not is_installed():
856 install()
857 save_blacklist()
858 self.save_collection()
860 def save_collection(self):
861 doc = libxml2.newDoc("1.0")
862 root = doc.newChild(None, "fontmanager", None)
863 for c in self.collections:
864 if c.builtin:
865 continue
866 cn = root.newChild(None, "fontcollection", None)
867 cn.setProp("name", c.name)
868 for f in c.fonts:
869 p = cn.newChild(None, "pattern", None)
870 add_patelt_node(p, "family", f.family)
872 doc.saveFormatFile(FM_GROUP_CONF, format=1)
875 def create_collections(self):
876 self.collections = []
878 c = Collection(_("All Fonts"))
879 for f in sorted(g_fonts.itervalues(), Family.cmp_family):
880 c.fonts.append(f)
881 self.add_collection(c)
883 c = Collection(_("System"))
884 for f in sorted(g_fonts.itervalues(), Family.cmp_family):
885 if not f.user:
886 c.fonts.append(f)
887 self.add_collection(c)
889 c = Collection(_("User"))
890 for f in sorted(g_fonts.itervalues(), Family.cmp_family):
891 if f.user:
892 c.fonts.append(f)
893 self.add_collection(c)
895 # add separator - hack
896 lstore = self.collection_tv.get_model()
897 iter = lstore.append()
898 lstore.set(iter, 1, None)
900 self.load_user_collections()
903 def add_collection(self, c):
904 c.set_enabled_from_fonts()
905 lstore = self.collection_tv.get_model()
906 iter = lstore.append()
907 lstore.set(iter, 0, c.get_label())
908 lstore.set(iter, 1, c)
909 lstore.set(iter, 2, c.get_text())
910 self.collections.append(c)
912 def load_user_collections(self):
913 if not exists(FM_GROUP_CONF):
914 return
915 doc = libxml2.parseFile(FM_GROUP_CONF)
916 nodes = doc.xpathEval('//fontcollection')
917 for a in nodes:
918 patterns = []
919 name = a.prop("name")
920 get_fontconfig_patterns(a, patterns)
922 c = Collection(name)
923 c.builtin = False
924 for p in patterns:
925 font = find_font(p.family)
926 if font:
927 c.fonts.append(font)
929 self.add_collection(c)
930 print "Loaded user collection %s" % name
932 doc.freeDoc()
934 def size_changed(self, combo):
935 if combo.get_active() < 0:
936 return
937 self.change_font(self.current_font)
940 def style_changed(self, combo):
941 if combo.get_active() < 0:
942 return
943 style = combo.get_model()[combo.get_active()][0]
944 faces = self.current_font.pango_family.list_faces()
945 for face in faces:
946 if face.get_face_name() == style:
947 descr = face.describe()
948 self.set_preview_text(descr)
949 return
952 def change_font(self, font):
953 self.current_font = font
954 self.style_combo.get_model().clear()
955 faces = font.pango_family.list_faces()
957 selected_face = None
958 active = -1
960 i = 0
961 for face in faces:
962 name = face.get_face_name()
963 self.style_combo.append_text(name)
964 if name in DEFAULT_STYLES or not selected_face:
965 selected_face = face
966 active = i
967 i += 1
969 self.style_combo.set_active(active)
970 self.set_preview_text(selected_face.describe())
972 def get_current_size(self):
973 i = self.size_combo.get_active()
974 if i < 0:
975 return 14
976 model = self.size_combo.get_model()
977 str = model[i][0]
978 return int(str)
980 def set_scalable_sizes(self):
981 for size in SCALABLE_SIZES:
982 self.size_combo.append_text(str(size))
983 self.size_combo.set_active(6)
985 def set_preview_text(self, descr, update_custom=True):
986 if update_custom and self.preview_mode == 1:
987 self.custom_text = self.get_current_text()
989 self.text_view.set_editable(self.preview_mode == 1)
991 b = self.text_view.get_buffer()
992 b.set_text("", 0)
994 for tag in self.font_tags:
995 b.get_tag_table().remove(tag)
996 self.font_tags = []
998 # create font
999 if self.preview_mode == 2:
1000 size = 14
1001 tag = b.create_tag(None, size_points=size)
1002 else:
1003 size = self.get_current_size()
1004 tag = b.create_tag(None, font_desc=descr, size_points=size)
1005 self.font_tags.append(tag)
1007 if self.preview_mode == 0:
1008 b.insert_with_tags(b.get_end_iter(), descr.to_string() + "\n", tag)
1009 b.insert_with_tags(b.get_end_iter(), TEST_TEXT + "\n", tag)
1010 elif self.preview_mode == 1:
1011 b.insert_with_tags(b.get_end_iter(), self.custom_text, tag)
1012 else:
1013 text = get_font_details_text(self.current_font.family)
1014 b.insert_with_tags(b.get_end_iter(), text, tag)
1016 self.current_descr = descr
1021 def show_collection(self, c):
1022 lstore = self.family_tv.get_model()
1023 lstore.clear()
1025 if not c:
1026 return
1028 for f in c.fonts:
1029 self.add_font_to_view(f)
1031 def add_font_to_view(self, f):
1032 lstore = self.family_tv.get_model()
1033 iter = lstore.append(None)
1034 lstore.set(iter, 0, f.get_label())
1035 lstore.set(iter, 1, f)
1036 lstore.set(iter, 2, f.family)
1039 def add_action(g, action, stock, label, cb, accel=None):
1040 g.add_actions([(action, stock, label, accel, None, cb)])
1043 def is_installed():
1044 if not exists(USER_FONT_CONF):
1045 print "User conf file %s does not exist" % USER_FONT_CONF
1046 return False
1048 for l in open(USER_FONT_CONF):
1049 if l.strip() == FC_INCLUDE_LINE:
1050 print "Include exists in %s" % USER_FONT_CONF
1051 return True
1052 print "Include does not exist in %s" % USER_FONT_CONF
1053 return False
1055 # put an include into ~/.fonts.conf
1056 def install():
1057 if not exists(USER_FONT_CONF):
1058 print "Making empty user conf file %s" % USER_FONT_CONF
1059 f = open(USER_FONT_CONF, "w")
1060 f.write("<fontconfig>\n</fontconfig>\n")
1061 f.close()
1063 tmpname = USER_FONT_CONF + ".fontmanager.tmp"
1064 print "Starting install, adding %s to %s" % (FC_INCLUDE_LINE, USER_FONT_CONF)
1065 print "Backup will be saved as %s" % USER_FONT_CONF_BACKUP
1066 tmp = open(tmpname, "w")
1067 for l in open(USER_FONT_CONF):
1068 if l.strip() == "</fontconfig>":
1069 tmp.write(FC_INCLUDE_LINE + "\n")
1070 tmp.write(l)
1072 print "Saving backup %s" % USER_FONT_CONF_BACKUP
1073 os.rename(USER_FONT_CONF, USER_FONT_CONF_BACKUP)
1074 print "Overwriting %s" % USER_FONT_CONF
1075 os.rename(tmpname, USER_FONT_CONF)
1079 def update_home(path):
1080 return path.replace("~", os.getenv("HOME"))
1082 def main():
1083 if not exists(FM_DIR):
1084 print "Creating %s" % (FM_DIR)
1085 os.mkdir(FM_DIR)
1087 print "Disabling blacklist temporarily..."
1088 disable_blacklist()
1090 f = FontBook()
1092 print "Reenabling blacklist"
1093 enable_blacklist()
1095 gtk.main()
1097 if __name__ == '__main__':
1098 FM_DIR = update_home(FM_DIR)
1099 FM_BLOCK_CONF = update_home(FM_BLOCK_CONF)
1100 FM_BLOCK_CONF_TMP = update_home(FM_BLOCK_CONF_TMP)
1101 FM_GROUP_CONF = update_home(FM_GROUP_CONF)
1103 USER_FONT_CONF = update_home(USER_FONT_CONF)
1104 USER_FONT_CONF_BACKUP = update_home(USER_FONT_CONF_BACKUP)
1105 main()