/* Copyright (C) 2009 Dale Whittaker This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. See the file COPYING for the full license text. */ #include "feed-panel.h" #include #include #include #if HAVE_CONFIG_H #include #endif #define STOCK_FEED_PANEL "feed-panel" struct _FeedPanel { GtkVBox parent_instance; GtkWidget* toolbar; GtkWidget* treeview; GtkWidget* webview; GtkWidget* delete; GdkPixbuf* pixbuf; KatzeNet* net; }; struct _FeedPanelClass { GtkVBoxClass parent_class; }; static void feed_panel_viewable_iface_init (MidoriViewableIface* iface); static void feed_panel_insert_item (FeedPanel* panel, GtkTreeStore* treestore, GtkTreeIter* parent, KatzeItem* item); static void feed_panel_disconnect_feed (FeedPanel* panel, KatzeArray* feed); G_DEFINE_TYPE_WITH_CODE (FeedPanel, feed_panel, GTK_TYPE_VBOX, G_IMPLEMENT_INTERFACE (MIDORI_TYPE_VIEWABLE, feed_panel_viewable_iface_init)); enum { ADD_FEED, REMOVE_FEED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; static void feed_panel_treeview_render_icon_cb (GtkTreeViewColumn* column, GtkCellRenderer* renderer, GtkTreeModel* model, GtkTreeIter* iter, FeedPanel* panel) { GdkPixbuf* pixbuf; KatzeItem* item; KatzeItem* pitem; const gchar* uri; gtk_tree_model_get (model, iter, 0, &item, -1); g_assert (KATZE_IS_ITEM (item)); if (!KATZE_IS_ARRAY (item)) { pitem = katze_item_get_parent (item); g_assert (KATZE_IS_ITEM (pitem)); } else pitem = item; uri = katze_item_get_uri (pitem); if (uri) { pixbuf = katze_net_load_icon (panel->net, uri, NULL, NULL, NULL); if (!pixbuf) pixbuf = panel->pixbuf; } else { pixbuf = gtk_widget_render_icon (panel->treeview, GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_MENU, NULL); } g_object_set (renderer, "pixbuf", pixbuf, NULL); if (pixbuf != panel->pixbuf) g_object_unref (pixbuf); } static void feed_panel_treeview_render_text_cb (GtkTreeViewColumn* column, GtkCellRenderer* renderer, GtkTreeModel* model, GtkTreeIter* iter, GtkWidget* treeview) { KatzeItem* item; const gchar* title; gtk_tree_model_get (model, iter, 0, &item, -1); g_assert (KATZE_IS_ITEM (item)); title = katze_item_get_name (item); if (!title) title = katze_item_get_text (item); if (!title) title = katze_item_get_uri (item); g_object_set (renderer, "text", title, NULL); g_object_unref (item); } static void feed_panel_add_item_cb (KatzeArray* parent, KatzeItem* child, FeedPanel* panel) { GtkTreeModel* model; GtkTreeIter iter; GtkTreeIter child_iter; KatzeItem* item; gint i; g_return_if_fail (FEED_IS_PANEL (panel)); g_return_if_fail (KATZE_IS_ARRAY (parent)); g_return_if_fail (KATZE_IS_ITEM (child)); model = gtk_tree_view_get_model (GTK_TREE_VIEW (panel->treeview)); if (katze_item_get_parent (KATZE_ITEM (parent))) { if (KATZE_IS_ARRAY (child)) { gtk_tree_store_insert_with_values (GTK_TREE_STORE (model), &child_iter, NULL, G_MAXINT, 0, child, -1); } else { i = 0; while (gtk_tree_model_iter_nth_child (model, &iter, NULL, i++)) { gtk_tree_model_get (model, &iter, 0, &item, -1); if (item == KATZE_ITEM (parent)) { gtk_tree_store_insert_with_values (GTK_TREE_STORE (model), &child_iter, &iter, 0, 0, child, -1); g_object_unref (child); g_object_unref (item); break; } g_object_unref (item); } } } feed_panel_insert_item (panel, GTK_TREE_STORE (model), &child_iter, child); } static void feed_panel_remove_iter (GtkTreeModel* model, KatzeItem* removed_item) { guint i; GtkTreeIter iter; i = 0; while (gtk_tree_model_iter_nth_child (model, &iter, NULL, i)) { KatzeItem* item; gtk_tree_model_get (model, &iter, 0, &item, -1); if (item == removed_item) { gtk_tree_store_remove (GTK_TREE_STORE (model), &iter); g_object_unref (item); break; } g_object_unref (item); i++; } } static void feed_panel_remove_item_cb (KatzeArray* item, KatzeItem* child, FeedPanel* panel) { GtkTreeModel* model; KatzeItem* pitem; g_return_if_fail (FEED_IS_PANEL (panel)); g_return_if_fail (KATZE_IS_ARRAY (item)); g_return_if_fail (KATZE_IS_ITEM (child)); if (KATZE_IS_ARRAY (child)) feed_panel_disconnect_feed (panel, KATZE_ARRAY (child)); if (!katze_item_get_parent (KATZE_ITEM (item))) { gint n; n = katze_array_get_length (KATZE_ARRAY (child)); g_assert (n == 1); pitem = katze_array_get_nth_item (KATZE_ARRAY (child), 0); } else pitem = child; model = gtk_tree_view_get_model (GTK_TREE_VIEW (panel->treeview)); feed_panel_remove_iter (model, pitem); g_object_unref (pitem); } static void feed_panel_move_item_cb (KatzeArray* feed, KatzeItem* child, gint position, FeedPanel* panel) { GtkTreeModel* model; GtkTreeIter iter; guint i; g_return_if_fail (FEED_IS_PANEL (panel)); g_return_if_fail (KATZE_IS_ARRAY (feed)); g_return_if_fail (KATZE_IS_ITEM (child)); model = gtk_tree_view_get_model (GTK_TREE_VIEW (panel->treeview)); i = 0; while (gtk_tree_model_iter_nth_child (model, &iter, NULL, i)) { KatzeItem* item; gtk_tree_model_get (model, &iter, 0, &item, -1); if (item == child) { gtk_tree_store_move_after (GTK_TREE_STORE (model), &iter, NULL); g_object_unref (item); break; } g_object_unref (item); i++; } } static void feed_panel_disconnect_feed (FeedPanel* panel, KatzeArray* feed) { KatzeItem* item; guint i; g_return_if_fail (KATZE_IS_ARRAY (feed)); g_signal_handlers_disconnect_by_func (feed, feed_panel_add_item_cb, panel); g_signal_handlers_disconnect_by_func (feed, feed_panel_remove_item_cb, panel); g_signal_handlers_disconnect_by_func (feed, feed_panel_move_item_cb, panel); i = 0; while ((item = katze_array_get_nth_item (feed, i++))) { if (KATZE_IS_ARRAY (item)) feed_panel_disconnect_feed (panel, KATZE_ARRAY (item)); g_object_unref (item); } } static void feed_panel_insert_item (FeedPanel* panel, GtkTreeStore* treestore, GtkTreeIter* parent, KatzeItem* item) { g_return_if_fail (KATZE_IS_ITEM (item)); if (KATZE_IS_ARRAY (item)) { g_signal_connect_after (item, "add-item", G_CALLBACK (feed_panel_add_item_cb), panel); g_signal_connect_after (item, "move-item", G_CALLBACK (feed_panel_move_item_cb), panel); if (!parent) { g_signal_connect (item, "remove-item", G_CALLBACK (feed_panel_remove_item_cb), panel); } } } static void feed_panel_row_activated_cb (GtkTreeView* treeview, GtkTreePath* path, GtkTreeViewColumn* column, FeedPanel* panel) { GtkTreeModel* model; GtkTreeIter iter; KatzeItem* item; const gchar* uri; model = gtk_tree_view_get_model (treeview); if (gtk_tree_model_get_iter (model, &iter, path)) { gtk_tree_model_get (model, &iter, 0, &item, -1); uri = katze_item_get_uri (item); if (uri && *uri) { MidoriWebSettings* settings; MidoriBrowser* browser; gint n; browser = midori_browser_get_for_widget (GTK_WIDGET (panel)); n = midori_browser_add_item (browser, item); settings = katze_object_get_object (browser, "settings"); if (!katze_object_get_boolean (settings, "open-tabs-in-the-background")) midori_browser_set_current_page (browser, n); g_object_unref (settings); } g_object_unref (item); } } static void feed_panel_cursor_or_row_changed_cb (GtkTreeView* treeview, FeedPanel* panel) { GtkTreeModel* model; GtkTreeIter iter; KatzeItem* item; gboolean sensitive = FALSE; if (katze_tree_view_get_selected_iter (treeview, &model, &iter)) { const gchar* uri; const gchar* text; gtk_tree_model_get (model, &iter, 0, &item, -1); uri = katze_item_get_uri (item); if (KATZE_IS_ARRAY (item)) { gint64 date; text = NULL; if (!uri) text = g_strdup (katze_item_get_text (KATZE_ITEM (item))); else { date = katze_item_get_added (item); if (date) { time_t date_t; const struct tm* tm; static gchar date_format[512]; const gchar* puri; KatzeItem* parent; gchar* last_updated; date_t = (time_t)date; tm = localtime (&date_t); /* Some gcc versions complain about "%c" for no reason */ strftime (date_format, sizeof (date_format), "%c", tm); parent = katze_item_get_parent (item); g_assert (KATZE_IS_ARRAY (parent)); puri = katze_item_get_uri (parent); /* i18n: The local date a feed was last updated */ last_updated = g_strdup_printf (C_("Feed", "Last updated: %s."), date_format); text = g_strdup_printf ( "feed" "

%s

%s", puri, last_updated); g_free (last_updated); } } webkit_web_view_load_html_string ( WEBKIT_WEB_VIEW (panel->webview), text ? text : "", uri); g_free ((gchar*) text); sensitive = TRUE; } else { text = katze_item_get_text (item); if (text) { webkit_web_view_load_html_string ( WEBKIT_WEB_VIEW (panel->webview), text, uri); } } g_object_unref (item); } if (GTK_IS_WIDGET (panel->delete)) gtk_widget_set_sensitive (panel->delete, sensitive); } static void feed_panel_popup_item (GtkWidget* menu, const gchar* stock_id, const gchar* label, KatzeItem* item, gpointer callback, FeedPanel* panel) { const gchar* uri; GtkWidget* menuitem; uri = katze_item_get_uri (item); menuitem = gtk_image_menu_item_new_from_stock (stock_id, NULL); if (label) gtk_label_set_text_with_mnemonic (GTK_LABEL (gtk_bin_get_child ( GTK_BIN (menuitem))), label); g_object_set_data (G_OBJECT (menuitem), "KatzeItem", item); g_signal_connect (menuitem, "activate", G_CALLBACK (callback), panel); gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); gtk_widget_show (menuitem); } static void feed_panel_open_activate_cb (GtkWidget* menuitem, FeedPanel* panel) { KatzeItem* item; const gchar* uri; item = (KatzeItem*)g_object_get_data (G_OBJECT (menuitem), "KatzeItem"); uri = katze_item_get_uri (item); if (uri && *uri) { MidoriBrowser* browser; browser = midori_browser_get_for_widget (GTK_WIDGET (panel)); midori_browser_set_current_uri (browser, uri); } } static void feed_panel_open_in_tab_activate_cb (GtkWidget* menuitem, FeedPanel* panel) { KatzeItem* item; const gchar* uri; guint n; item = (KatzeItem*)g_object_get_data (G_OBJECT (menuitem), "KatzeItem"); if ((uri = katze_item_get_uri (item)) && *uri) { MidoriWebSettings* settings; MidoriBrowser* browser; browser = midori_browser_get_for_widget (GTK_WIDGET (panel)); n = midori_browser_add_item (browser, item); settings = katze_object_get_object (browser, "settings"); if (!katze_object_get_boolean (settings, "open-tabs-in-the-background")) midori_browser_set_current_page (browser, n); g_object_unref (settings); } } static void feed_panel_open_in_window_activate_cb (GtkWidget* menuitem, FeedPanel* panel) { KatzeItem* item; const gchar* uri; item = (KatzeItem*)g_object_get_data (G_OBJECT (menuitem), "KatzeItem"); uri = katze_item_get_uri (item); if (uri && *uri) { MidoriBrowser* browser; browser = midori_browser_get_for_widget (GTK_WIDGET (panel)); g_signal_emit_by_name (browser, "new-window", uri); } } static void feed_panel_delete_activate_cb (GtkWidget* menuitem, FeedPanel* panel) { KatzeItem* item; g_return_if_fail (FEED_IS_PANEL (panel)); item = (KatzeItem*)g_object_get_data (G_OBJECT (menuitem), "KatzeItem"); g_signal_emit (panel, signals[REMOVE_FEED], 0, item); } static void feed_panel_popup (GtkWidget* widget, GdkEventButton* event, KatzeItem* item, FeedPanel* panel) { GtkWidget* menu; menu = gtk_menu_new (); if (!KATZE_IS_ARRAY (item)) { feed_panel_popup_item (menu, GTK_STOCK_OPEN, NULL, item, feed_panel_open_activate_cb, panel); feed_panel_popup_item (menu, STOCK_TAB_NEW, _("Open in New _Tab"), item, feed_panel_open_in_tab_activate_cb, panel); feed_panel_popup_item (menu, STOCK_WINDOW_NEW, _("Open in New _Window"), item, feed_panel_open_in_window_activate_cb, panel); } else { feed_panel_popup_item (menu, GTK_STOCK_DELETE, NULL, item, feed_panel_delete_activate_cb, panel); } sokoke_widget_popup (widget, GTK_MENU (menu), event, SOKOKE_MENU_POSITION_CURSOR); } static gboolean feed_panel_button_release_event_cb (GtkWidget* widget, GdkEventButton* event, FeedPanel* panel) { GtkTreeModel* model; GtkTreeIter iter; if (event->button != 2 && event->button != 3) return FALSE; if (katze_tree_view_get_selected_iter (GTK_TREE_VIEW (widget), &model, &iter)) { KatzeItem* item; gtk_tree_model_get (model, &iter, 0, &item, -1); if (event->button == 2) { const gchar* uri = katze_item_get_uri (item); if (uri && *uri) { MidoriWebSettings* settings; MidoriBrowser* browser; gint n; browser = midori_browser_get_for_widget (GTK_WIDGET (panel)); n = midori_browser_add_item (browser, item); settings = katze_object_get_object (browser, "settings"); if (!katze_object_get_boolean (settings, "open-tabs-in-the-background")) midori_browser_set_current_page (browser, n); g_object_unref (settings); } } else feed_panel_popup (widget, event, item, panel); g_object_unref (item); return TRUE; } return FALSE; } static void feed_panel_popup_menu_cb (GtkWidget* widget, FeedPanel* panel) { GtkTreeModel* model; GtkTreeIter iter; KatzeItem* item; if (katze_tree_view_get_selected_iter (GTK_TREE_VIEW (widget), &model, &iter)) { gtk_tree_model_get (model, &iter, 0, &item, -1); feed_panel_popup (widget, NULL, item, panel); g_object_unref (item); } } void feed_panel_add_feeds (FeedPanel* panel, KatzeItem* feed) { GtkTreeModel* model; model = gtk_tree_view_get_model (GTK_TREE_VIEW (panel->treeview)); g_assert (GTK_IS_TREE_MODEL (model)); feed_panel_insert_item (panel, GTK_TREE_STORE (model), NULL, feed); } static gboolean webview_button_press_event_cb (GtkWidget* widget, GdkEventButton* event) { /* Disable the popup menu */ return (event->button == 3); } static gboolean webview_navigation_request_cb (WebKitWebView* web_view, WebKitWebFrame* frame, WebKitNetworkRequest* request, WebKitWebNavigationAction* navigation_action, WebKitWebPolicyDecision* policy_decision, FeedPanel* panel) { if (webkit_web_navigation_action_get_reason (navigation_action) == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) { MidoriBrowser* browser; const gchar* uri; gint n; browser = midori_browser_get_for_widget (GTK_WIDGET (panel)); uri = webkit_network_request_get_uri (request); n = midori_browser_add_uri (browser, uri); midori_browser_set_current_page (browser, n); } return TRUE; } static const gchar* feed_panel_get_label (MidoriViewable* viewable) { return _("Feeds"); } static const gchar* feed_panel_get_stock_id (MidoriViewable* viewable) { return STOCK_FEED_PANEL; } static void feed_panel_add_clicked_cb (GtkWidget* toolitem, FeedPanel* panel) { g_return_if_fail (FEED_IS_PANEL (panel)); g_signal_emit (panel, signals[ADD_FEED], 0); } static void feed_panel_delete_clicked_cb (GtkWidget* toolitem, FeedPanel* panel) { GtkTreeModel* model; GtkTreeIter iter; g_return_if_fail (FEED_IS_PANEL (panel)); if (katze_tree_view_get_selected_iter (GTK_TREE_VIEW (panel->treeview), &model, &iter)) { KatzeItem* item; gtk_tree_model_get (model, &iter, 0, &item, -1); g_signal_emit (panel, signals[REMOVE_FEED], 0, item); g_object_unref (item); } } static GtkWidget* feed_panel_get_toolbar (MidoriViewable* viewable) { FeedPanel* panel = FEED_PANEL (viewable); if (!panel->toolbar) { GtkWidget* toolbar; GtkToolItem* toolitem; toolbar = gtk_toolbar_new (); gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_BOTH_HORIZ); gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar), GTK_ICON_SIZE_BUTTON); panel->toolbar = toolbar; toolitem = gtk_tool_button_new_from_stock (GTK_STOCK_ADD); gtk_widget_set_tooltip_text (GTK_WIDGET (toolitem), _("Add new feed")); gtk_tool_item_set_is_important (toolitem, TRUE); g_signal_connect (toolitem, "clicked", G_CALLBACK (feed_panel_add_clicked_cb), panel); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), toolitem, -1); gtk_widget_show (GTK_WIDGET (toolitem)); toolitem = gtk_tool_button_new_from_stock (GTK_STOCK_DELETE); gtk_widget_set_tooltip_text (GTK_WIDGET (toolitem), _("Delete feed")); g_signal_connect (toolitem, "clicked", G_CALLBACK (feed_panel_delete_clicked_cb), panel); gtk_toolbar_insert (GTK_TOOLBAR (toolbar), toolitem, -1); gtk_widget_show (GTK_WIDGET (toolitem)); panel->delete = GTK_WIDGET (toolitem);; feed_panel_cursor_or_row_changed_cb ( GTK_TREE_VIEW (panel->treeview), panel); g_signal_connect (panel->delete, "destroy", G_CALLBACK (gtk_widget_destroyed), &panel->delete); } return panel->toolbar; } static void feed_panel_finalize (GObject* object) { FeedPanel* panel = FEED_PANEL (object); g_object_unref (panel->pixbuf); g_object_unref (panel->net); } static void feed_panel_viewable_iface_init (MidoriViewableIface* iface) { iface->get_stock_id = feed_panel_get_stock_id; iface->get_label = feed_panel_get_label; iface->get_toolbar = feed_panel_get_toolbar; } static void feed_panel_class_init (FeedPanelClass* class) { GObjectClass* gobject_class; signals[ADD_FEED] = g_signal_new ( "add-feed", G_TYPE_FROM_CLASS (class), (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION), 0, 0, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[REMOVE_FEED] = g_signal_new ( "remove-feed", G_TYPE_FROM_CLASS (class), (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION), 0, 0, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); gobject_class = G_OBJECT_CLASS (class); gobject_class->finalize = feed_panel_finalize; } static void feed_panel_init (FeedPanel* panel) { GtkTreeStore* model; GtkWidget* treewin; GtkWidget* treeview; GtkWidget* webview; GtkWidget* paned; GtkTreeViewColumn* column; GtkCellRenderer* renderer_pixbuf; GtkCellRenderer* renderer_text; GtkIconFactory *factory; GtkIconSource *icon_source; GtkIconSet *icon_set; WebKitWebSettings* settings; PangoFontDescription* font_desc; const gchar* family; gint size; GtkStockItem items[] = { { STOCK_FEED_PANEL, N_("_Feeds"), 0, 0, NULL } }; factory = gtk_icon_factory_new (); gtk_stock_add (items, G_N_ELEMENTS (items)); icon_set = gtk_icon_set_new (); icon_source = gtk_icon_source_new (); gtk_icon_source_set_icon_name (icon_source, STOCK_NEWS_FEED); gtk_icon_set_add_source (icon_set, icon_source); gtk_icon_source_free (icon_source); gtk_icon_factory_add (factory, STOCK_FEED_PANEL, icon_set); gtk_icon_set_unref (icon_set); gtk_icon_factory_add_default (factory); g_object_unref (factory); panel->net = katze_net_new (); model = gtk_tree_store_new (1, KATZE_TYPE_ITEM); treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model)); panel->treeview = treeview; gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); column = gtk_tree_view_column_new (); renderer_pixbuf = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_start (column, renderer_pixbuf, FALSE); gtk_tree_view_column_set_cell_data_func (column, renderer_pixbuf, (GtkTreeCellDataFunc)feed_panel_treeview_render_icon_cb, panel, NULL); renderer_text = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (column, renderer_text, FALSE); gtk_tree_view_column_set_cell_data_func (column, renderer_text, (GtkTreeCellDataFunc)feed_panel_treeview_render_text_cb, treeview, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); g_object_unref (model); g_object_connect (treeview, "signal::row-activated", feed_panel_row_activated_cb, panel, "signal::cursor-changed", feed_panel_cursor_or_row_changed_cb, panel, "signal::columns-changed", feed_panel_cursor_or_row_changed_cb, panel, "signal::button-release-event", feed_panel_button_release_event_cb, panel, "signal::popup-menu", feed_panel_popup_menu_cb, panel, NULL); gtk_widget_show (treeview); webview = webkit_web_view_new (); font_desc = treeview->style->font_desc; family = pango_font_description_get_family (font_desc); size = pango_font_description_get_size (font_desc) / PANGO_SCALE; settings = webkit_web_settings_new (); g_object_set (settings, "default-font-family", family, "default-font-size", size, NULL); g_object_set (webview, "settings", settings, NULL); gtk_widget_set_size_request (webview, -1, 50); g_object_connect (webview, "signal::navigation-policy-decision-requested", webview_navigation_request_cb, panel, "signal::button-press-event", webview_button_press_event_cb, NULL, "signal::button-release-event", webview_button_press_event_cb, NULL, NULL); panel->webview = webview; treewin = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (treewin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (treewin), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (treewin), treeview); gtk_widget_show (treewin); paned = gtk_vpaned_new (); gtk_paned_pack1 (GTK_PANED (paned), treewin, TRUE, FALSE); gtk_paned_pack2 (GTK_PANED (paned), webview, TRUE, FALSE); gtk_box_pack_start (GTK_BOX (panel), paned, TRUE, TRUE, 0); gtk_widget_show (webview); gtk_widget_show (paned); panel->pixbuf = gtk_widget_render_icon (treeview, STOCK_NEWS_FEED, GTK_ICON_SIZE_MENU, NULL); } GtkWidget* feed_panel_new (void) { FeedPanel* panel = g_object_new (FEED_TYPE_PANEL, NULL); return GTK_WIDGET (panel); }