def _create_menu(self): BaseWidget._create_menu(self) self._sort_order = StringVar( self, LATESTS.get(self.name, 'sort_order', fallback='A-Z')) add_trace(self._sort_order, 'write', self._order_trace) self.menu_sort.add_radiobutton( label='A-Z', variable=self._sort_order, value='A-Z', command=lambda: self._sort_by_name(reverse=False)) self.menu_sort.add_radiobutton( label='Z-A', variable=self._sort_order, value='Z-A', command=lambda: self._sort_by_name(reverse=True)) self.menu_sort.add_radiobutton( label=_('Oldest first'), variable=self._sort_order, value='oldest', command=lambda: self._sort_by_date(reverse=False)) self.menu_sort.add_radiobutton( label=_('Most recent first'), variable=self._sort_order, value='latest', command=lambda: self._sort_by_date(reverse=True)) if self.name != 'All': self.menu.add_command(label=_('Remove category'), command=self.remove_cat)
def feed_change_cat(self, title, old_cat, new_cat): if old_cat != new_cat: FEEDS.set(title, 'category', new_cat) if old_cat != '': self.cat_widgets[old_cat].remove_feed(title) if new_cat != '': if new_cat not in LATESTS.sections(): LATESTS.add_section(new_cat) LATESTS.set(new_cat, 'visible', 'True') LATESTS.set(new_cat, 'geometry', '') LATESTS.set(new_cat, 'position', 'normal') LATESTS.set(new_cat, 'sort_order', 'A-Z') self.cat_widgets[new_cat] = CatWidget(self, new_cat) self.cat_widgets[new_cat].event_generate('<Configure>') self.menu_categories.add_checkbutton( label=new_cat, command=lambda: self.toggle_category_widget(new_cat)) cst.add_trace(self.cat_widgets[new_cat].variable, 'write', lambda *args: self.cat_widget_trace(new_cat)) self.cat_widgets[new_cat].variable.set(True) else: try: filename = FEEDS.get(title, 'data') latest = cst.feed_get_latest(filename) except (configparser.NoOptionError, pickle.UnpicklingError): latest = '' self.cat_widgets[new_cat].entry_add( title, FEEDS.get(title, 'updated'), latest, FEEDS.get(title, 'url'))
def feed_rename(self, old_name, new_name): options = { opt: FEEDS.get(old_name, opt) for opt in FEEDS.options(old_name) } FEEDS.remove_section(old_name) try: # check if feed's title already exists FEEDS.add_section(new_name) except configparser.DuplicateSectionError: i = 2 duplicate = True while duplicate: # increment i until new_name~#i does not already exist try: FEEDS.add_section("{}~#{}".format(new_name, i)) except configparser.DuplicateSectionError: i += 1 else: duplicate = False name = "{}~#{}".format(new_name, i) else: name = new_name logging.info("Renamed feed '%s' to '%s'", old_name, name) for opt, val in options.items(): FEEDS.set(name, opt, val) self._check_result_init_id[name] = self._check_result_init_id.pop( old_name, '') self._check_result_update_id[name] = self._check_result_update_id.pop( old_name, '') self.threads[name] = self.threads.pop(old_name, None) self.queues[name] = self.queues.pop(old_name) self.feed_widgets[name] = self.feed_widgets.pop(old_name) self.feed_widgets[name].rename_feed(name) self.cat_widgets['All'].rename_feed(old_name, name) category = FEEDS.get(name, 'category', fallback='') if category != '': self.cat_widgets[category].rename_feed(old_name, name) self.menu_feeds.delete(old_name) self.menu_feeds.add_checkbutton( label=name, command=lambda: self.toggle_feed_widget(name)) trace_info = cst.info_trace(self.feed_widgets[name].variable) if trace_info: cst.remove_trace(self.feed_widgets[name].variable, 'write', trace_info[0][1]) cst.add_trace(self.feed_widgets[name].variable, 'write', lambda *args: self.feed_widget_trace(name)) self.menu_feeds.set_item_value(name, self.feed_widgets[name].variable.get()) cst.save_feeds() return name
def __init__(self, master, font, style=False, sample_text=_("Sample text")): ttk.Frame.__init__(self, master) # entry validation self._validate = self.register(self._validate_entry_nb) self._validate_size = self.register(self._validate_font_size) # chooser self.font = tkfont.Font(self, font=font) sample = ttk.Label(self, text=sample_text, anchor="center", font=self.font, style="white.TLabel", relief="groove") sample.grid(row=2, columnspan=2, padx=4, pady=6, ipadx=4, ipady=4, sticky="eswn") self.fonts = list(set(tkfont.families())) self.fonts.append("TkDefaultFont") self.fonts.sort() prop = self.font.actual() self.font_family = tk.StringVar(self, value=prop['family']) add_trace( self.font_family, 'write', lambda *args: self.font.configure(family=self.font_family.get())) self.font_size = tk.StringVar(self, value=prop['size']) add_trace(self.font_size, 'write', lambda *args: self._config_size(self.font_size, self.font)) w = max([len(f) for f in self.fonts]) sizes = list(range(6, 17)) + list(range(18, 32, 2)) if not prop['size'] in sizes: sizes.append(prop['size']) sizes.sort() self.sizes = ["%i" % i for i in sizes] self.choose_family = AutoCompleteCombobox( self, values=self.fonts, width=(w * 2) // 3, textvariable=self.font_family, exportselection=False) self.choose_family.current(self.fonts.index(prop['family'])) self.choose_family.grid(row=0, column=0, padx=4, pady=4) self.choose_size = ttk.Combobox(self, values=self.sizes, width=5, exportselection=False, textvariable=self.font_size, validate="key", validatecommand=(self._validate_size, "%d", "%P", "%V")) self.choose_size.current(self.sizes.index(str(prop['size']))) self.choose_size.grid(row=0, column=1, padx=4, pady=4) if style: frame_style = ttk.Frame(self) frame_style.grid(row=1, columnspan=2, pady=6) self.bold = tk.StringVar(self, value=prop['weight']) add_trace( self.bold, 'write', lambda *args: self.font.configure(weight=self.bold.get())) self.italic = tk.StringVar(self, value=prop['slant']) add_trace( self.italic, 'write', lambda *args: self.font.configure(slant=self.italic.get())) self.underline = tk.BooleanVar(self, value=prop['underline']) add_trace( self.underline, 'write', lambda *args: self.font.configure(underline=self.underline.get( ))) ttk.Checkbutton(frame_style, text=_("Bold"), onvalue='bold', offvalue='normal', variable=self.bold).pack(side='left', padx=4) ttk.Checkbutton(frame_style, text=_("Italic"), onvalue='italic', offvalue='roman', variable=self.italic).pack(side='left', padx=4) ttk.Checkbutton(frame_style, text=_("Underline"), variable=self.underline).pack(side='left', padx=4)
def __init__(self, master, name, config, save_config): """Create base desktop widget.""" Toplevel.__init__(self, master, class_=APP_NAME) self.rowconfigure(2, weight=1) self.columnconfigure(0, weight=1) self.minsize(50, 50) self.protocol('WM_DELETE_WINDOW', self.withdraw) self.ewmh = EWMH() self.name = name self.config = config # configparser self.save_config = save_config # save config method # get splash window type compatibility if CONFIG.getboolean('General', 'splash_supported', fallback=True): self.attributes('-type', 'splash') else: self.attributes('-type', 'toolbar') # control main menu checkbutton self.variable = BooleanVar(self, False) # save widget's position self._position = StringVar( self, self.config.get(name, 'position', fallback='normal')) add_trace(self._position, 'write', self._position_trace) self.title('feedagregator.widget.{}'.format(name.replace(' ', '_'))) self.withdraw() # window dragging self.x = None self.y = None # --- menu self._create_menu() # --- elements # --- --- title bar frame = Frame(self, style='widget.TFrame') Button(frame, style='widget.close.TButton', command=self.withdraw).pack(side='left') self.label = Label(frame, text=name, style='widget.title.TLabel', anchor='center') self.label.pack(side='left', fill='x', expand=True) frame.grid(row=0, columnspan=2, padx=4, pady=4, sticky='ew') sep = Separator(self, style='widget.Horizontal.TSeparator') sep.grid(row=1, columnspan=2, sticky='ew') # --- --- widget body self.canvas = Canvas(self, highlightthickness=0) self.canvas.grid(row=2, column=0, sticky='ewsn', padx=(2, 8), pady=(2, 4)) scroll = AutoScrollbar(self, orient='vertical', style='widget.Vertical.TScrollbar', command=self.canvas.yview) scroll.grid(row=2, column=1, sticky='ns', pady=(2, 14)) self.canvas.configure(yscrollcommand=scroll.set) self.display = Frame(self.canvas, style='widget.TFrame') self.canvas.create_window(0, 0, anchor='nw', window=self.display, tags=('display', )) self.display.columnconfigure(0, weight=1) # --- style self.style = Style(self) self._font_size = 10 self.update_style() # --- resizing and geometry corner = Sizegrip(self, style="widget.TSizegrip") corner.place(relx=1, rely=1, anchor='se', bordermode='outside') geometry = self.config.get(self.name, 'geometry') if geometry: self.geometry(geometry) self.update_idletasks() if self.config.getboolean(self.name, 'visible', fallback=True): self.deiconify() # --- bindings self.bind('<3>', lambda e: self.menu.tk_popup(e.x_root, e.y_root)) for widget in [self.label, self.canvas, sep]: widget.bind('<ButtonPress-1>', self._start_move) widget.bind('<ButtonRelease-1>', self._stop_move) widget.bind('<B1-Motion>', self._move) self.label.bind('<Map>', self._change_position) self.bind('<Configure>', self._on_configure) self.bind('<4>', lambda e: self._scroll(-1)) self.bind('<5>', lambda e: self._scroll(1)) self.update_idletasks() self.canvas.configure(scrollregion=self.canvas.bbox('all')) self.populate_widget() if not CONFIG.getboolean('General', 'splash_supported', fallback=True) and self.config.getboolean( self.name, 'visible', fallback=True): Toplevel.withdraw(self) Toplevel.deiconify(self)
def _check_result_add(self, thread, queue, url, manager_queue=None): if thread.is_alive(): self._check_add_id = self.after(1000, self._check_result_add, thread, queue, url, manager_queue) else: title, latest, date, data = queue.get(False) if title: try: # check if feed's title already exists FEEDS.add_section(title) except configparser.DuplicateSectionError: i = 2 duplicate = True while duplicate: # increment i until title~#i does not already exist try: FEEDS.add_section("{}~#{}".format(title, i)) except configparser.DuplicateSectionError: i += 1 else: duplicate = False name = "{}~#{}".format(title, i) else: name = title if manager_queue is not None: manager_queue.put(name) logging.info("Added feed '%s' %s", name, url) if CONFIG.getboolean("General", "notifications", fallback=True): run([ "notify-send", "-i", cst.IM_ICON_SVG, name, cst.html2text(latest) ]) self.cat_widgets['All'].entry_add(name, date, latest, url) filename = cst.new_data_file() cst.save_data(filename, latest, data) FEEDS.set(name, 'url', url) FEEDS.set(name, 'updated', date) FEEDS.set(name, 'data', filename) FEEDS.set(name, 'visible', 'True') FEEDS.set(name, 'geometry', '') FEEDS.set(name, 'position', 'normal') FEEDS.set(name, 'category', '') FEEDS.set(name, 'sort_is_reversed', 'False') FEEDS.set(name, 'active', 'True') cst.save_feeds() self.queues[name] = queue self.feed_widgets[name] = FeedWidget(self, name) self.menu_feeds.add_checkbutton( label=name, command=lambda: self.toggle_feed_widget(name)) cst.add_trace(self.feed_widgets[name].variable, 'write', lambda *args: self.feed_widget_trace(name)) self.feed_widgets[name].variable.set(True) for entry_title, date, summary, link in data: self.feed_widgets[name].entry_add(entry_title, date, summary, link, -1) else: if manager_queue is not None: manager_queue.put('') if cst.internet_on(): logging.error('%s is not a valid feed.', url) showerror(_('Error'), _('{url} is not a valid feed.').format(url=url)) else: logging.warning('No Internet connection.') showerror(_('Error'), _('No Internet connection.'))
def __init__(self): Tk.__init__(self, className=cst.APP_NAME) self.protocol("WM_DELETE_WINDOW", self.quit) self.withdraw() logging.info('Starting %s', cst.APP_NAME) self.im_icon = PhotoImage(master=self, file=cst.IM_ICON_48) self.iconphoto(True, self.im_icon) # --- style self.style = Style(self) self.style.theme_use("clam") self.style.configure("TScale", sliderlength=20) self.style.map("TCombobox", fieldbackground=[('readonly', 'white')], selectbackground=[('readonly', 'white')], selectforeground=[('readonly', 'black')]) self.style.configure("title.TLabel", font="TkDefaultFont 9 bold") self.style.configure("white.TLabel", background="white") self.style.map("white.TLabel", background=[("active", "white")]) self.style.configure('heading.TLabel', relief='ridge', borderwidth=1, padding=(10, 4)) self.style.configure('manager.TButton', padding=0) self.style.map('manager.Treeview', background=[], foreground=[]) self.style.layout( 'no_edit.TEntry', [('Entry.padding', { 'children': [('Entry.textarea', { 'sticky': 'nswe' })], 'sticky': 'nswe' })]) self.style.configure('no_edit.TEntry', background='white', padding=[4, 0]) self.style.configure('manager.TEntry', padding=[2, 1]) self.style.layout('manager.Treeview.Row', [('Treeitem.row', { 'sticky': 'nswe' }), ('Treeitem.image', { 'side': 'right', 'sticky': 'e' })]) self.style.layout('manager.Treeview.Item', [('Treeitem.padding', { 'children': [('Checkbutton.indicator', { 'side': 'left', 'sticky': '' }), ('Treeitem.text', { 'side': 'left', 'sticky': '' })], 'sticky': 'nswe' })]) self._im_trough = tkPhotoImage(name='trough-scrollbar-vert', width=15, height=15, master=self) bg = CONFIG.get("Widget", 'background', fallback='gray10') widget_bg = (0, 0, 0) widget_fg = (255, 255, 255) vmax = self.winfo_rgb('white')[0] color = tuple(int(val / vmax * 255) for val in widget_bg) active_bg = cst.active_color(color) active_bg2 = cst.active_color(cst.active_color(color, 'RGB')) slider_vert_insens = Image.new('RGBA', (13, 28), widget_bg) slider_vert = Image.new('RGBA', (13, 28), active_bg) slider_vert_active = Image.new('RGBA', (13, 28), widget_fg) slider_vert_prelight = Image.new('RGBA', (13, 28), active_bg2) self._im_trough.put(" ".join(["{" + " ".join([bg] * 15) + "}"] * 15)) self._im_slider_vert_active = PhotoImage(slider_vert_active, name='slider-vert-active', master=self) self._im_slider_vert = PhotoImage(slider_vert, name='slider-vert', master=self) self._im_slider_vert_prelight = PhotoImage(slider_vert_prelight, name='slider-vert-prelight', master=self) self._im_slider_vert_insens = PhotoImage(slider_vert_insens, name='slider-vert-insens', master=self) self.style.element_create('widget.Vertical.Scrollbar.trough', 'image', 'trough-scrollbar-vert') self.style.element_create( 'widget.Vertical.Scrollbar.thumb', 'image', 'slider-vert', ('pressed', '!disabled', 'slider-vert-active'), ('active', '!disabled', 'slider-vert-prelight'), ('disabled', 'slider-vert-insens'), border=6, sticky='ns') self.style.layout('widget.Vertical.TScrollbar', [ ('widget.Vertical.Scrollbar.trough', { 'children': [('widget.Vertical.Scrollbar.thumb', { 'expand': '1' })], 'sticky': 'ns' }) ]) hide = Image.new('RGBA', (12, 12), active_bg2) hide_active = Image.new('RGBA', (12, 12), widget_fg) hide_pressed = Image.new('RGBA', (12, 12), (150, 0, 0)) toggle_open = Image.new('RGBA', (9, 9), widget_fg) toggle_open_active = Image.new('RGBA', (9, 9), active_bg2) toggle_close = Image.new('RGBA', (9, 9), widget_fg) toggle_close_active = Image.new('RGBA', (9, 9), active_bg2) self._im_hide = PhotoImage(hide, master=self) self._im_hide_active = PhotoImage(hide_active, master=self) self._im_hide_pressed = PhotoImage(hide_pressed, master=self) self._im_open = PhotoImage(toggle_open, master=self) self._im_open_active = PhotoImage(toggle_open_active, master=self) self._im_close = PhotoImage(toggle_close, master=self) self._im_close_active = PhotoImage(toggle_close_active, master=self) self.style.element_create( "toggle", "image", self._im_close, ("!hover", "selected", "!disabled", self._im_open), ("hover", "!selected", "!disabled", self._im_close_active), ("hover", "selected", "!disabled", self._im_open_active), border=2, sticky='') self.style.layout('Toggle', [('Toggle.border', { 'children': [('Toggle.padding', { 'children': [('Toggle.toggle', { 'sticky': 'nswe' })], 'sticky': 'nswe' })], 'sticky': 'nswe' })]) self.style.configure('widget.close.TButton', background=bg, relief='flat', image=self._im_hide, padding=0) self.style.map('widget.close.TButton', background=[], relief=[], image=[('active', '!pressed', self._im_hide_active), ('active', 'pressed', self._im_hide_pressed)]) self.option_add('*Toplevel.background', self.style.lookup('TFrame', 'background')) self.option_add('*{app_name}.background'.format(app_name=cst.APP_NAME), self.style.lookup('TFrame', 'background')) self.widget_style_init() # --- tray icon menu self.icon = TrayIcon(cst.ICON) self.menu_widgets = SubMenu(parent=self.icon.menu) self.menu_categories = SubMenu(parent=self.menu_widgets) self.menu_categories.add_command(label=_('Hide all'), command=self.hide_all_cats) self.menu_categories.add_command(label=_('Show all'), command=self.hide_all_cats) self.menu_categories.add_separator() self.menu_feeds = SubMenu(parent=self.menu_widgets) self.menu_feeds.add_command(label=_('Hide all'), command=self.hide_all_feeds) self.menu_feeds.add_command(label=_('Show all'), command=self.show_all_feeds) self.menu_feeds.add_separator() self.menu_widgets.add_command(label=_('Hide all'), command=self.hide_all) self.menu_widgets.add_command(label=_('Show all'), command=self.show_all) self.menu_widgets.add_separator() self.menu_widgets.add_cascade(label=_('Categories'), menu=self.menu_categories) self.menu_widgets.add_cascade(label=_('Feeds'), menu=self.menu_feeds) self.icon.menu.add_cascade(label=_('Widgets'), menu=self.menu_widgets) self.icon.menu.add_command(label=_('Add feed'), command=self.add) self.icon.menu.add_command(label=_('Update feeds'), command=self.feed_update) self.icon.menu.add_command(label=_('Manage feeds'), command=self.feed_manage) self.icon.menu.add_command(label=_("Suspend"), command=self.start_stop) self.icon.menu.add_separator() self.icon.menu.add_command(label=_('Settings'), command=self.settings) self.icon.menu.add_command(label=_("Check for updates"), command=lambda: UpdateChecker(self, True)) self.icon.menu.add_command(label=_("Help"), command=lambda: Help(self)) self.icon.menu.add_command(label=_("About"), command=lambda: About(self)) self.icon.menu.add_command(label=_('Quit'), command=self.quit) self.icon.loop(self) self._notify_no_internet = True self._internet_id = "" self._update_id = "" self._check_add_id = "" self._check_end_update_id = "" self._check_result_update_id = {} self._check_result_init_id = {} self.queues = {} self.threads = {} # --- category widgets self.cat_widgets = {} self.cat_widgets['All'] = CatWidget(self, 'All') self.cat_widgets['All'].event_generate('<Configure>') self.menu_widgets.add_checkbutton(label=_('Latests'), command=self.toggle_latests_widget) cst.add_trace(self.cat_widgets['All'].variable, 'write', self.latests_widget_trace) self.cat_widgets['All'].variable.set( LATESTS.getboolean('All', 'visible')) cats = LATESTS.sections() cats.remove('All') for category in cats: self.cat_widgets[category] = CatWidget(self, category) self.cat_widgets[category].event_generate('<Configure>') self.menu_categories.add_checkbutton( label=category, command=lambda c=category: self.toggle_category_widget(c)) cst.add_trace(self.cat_widgets[category].variable, 'write', lambda *args, c=category: self.cat_widget_trace(c)) self.cat_widgets[category].variable.set( LATESTS.getboolean(category, 'visible')) # --- feed widgets self.feed_widgets = {} for title in FEEDS.sections(): self._check_result_update_id[title] = '' self._check_result_init_id[title] = '' self.queues[title] = Queue(1) self.threads[title] = None self.menu_feeds.add_checkbutton( label=title, command=lambda t=title: self.toggle_feed_widget(t)) self.feed_widgets[title] = FeedWidget(self, title) cst.add_trace(self.feed_widgets[title].variable, 'write', lambda *args, t=title: self.feed_widget_trace(t)) self.feed_widgets[title].variable.set( FEEDS.getboolean(title, 'visible', fallback=True)) self.feed_init() # --- check for updates if CONFIG.getboolean("General", "check_update"): UpdateChecker(self) self.bind_class('TEntry', '<Control-a>', self.entry_select_all)