示例#1
0
void
item_read_state_changed (itemPtr item, gboolean newState)
{
    nodePtr node;

    debug_start_measurement (DEBUG_GUI);

    /* 1. set values in memory */
    item->readStatus = newState;
    item->updateStatus = FALSE;

    /* 2. propagate to vfolders */
    vfolder_foreach_data (vfolder_merge_item, item);
    vfolder_foreach (node_update_counters);

    /* 3. apply to DB */
    db_item_state_update (item);

    /* 4. update item list GUI state */
    itemlist_update_item (item);

    /* 5. updated feed list unread counters */
    node = node_from_id (item->nodeId);
    node_update_counters (node);

    /* 6. update notification statistics */
    feedlist_reset_new_item_count ();

    /* 7. duplicate state propagation */
    if (item->validGuid) {
        GSList *duplicates, *iter;

        duplicates = iter = db_item_get_duplicates (item->sourceId);
        while (iter) {
            itemPtr duplicate = item_load (GPOINTER_TO_UINT (iter->data));

            /* The check on node_from_id() is an evil workaround
               to handle "lost" items in the DB that have no
               associated node in the feed list. This should be
               fixed by having the feed list in the DB too, so
               we can clean up correctly after crashes. */
            if (duplicate && duplicate->id != item->id && node_from_id (duplicate->nodeId)) {
                item_set_read_state (duplicate, newState);
            }
            if (duplicate) item_unload (duplicate);
            iter = g_slist_next (iter);
        }
        g_slist_free (duplicates);
    }

    debug_end_measurement (DEBUG_GUI, "set read status");
}
示例#2
0
文件: htmlview.c 项目: asl97/liferea
static gchar *
htmlview_render_item (itemPtr item, 
                      guint viewMode,
                      gboolean summaryMode) 
{
	renderParamPtr	params;
	gchar		*output = NULL, *baseUrl = NULL;
	nodePtr		node;
	xmlDocPtr 	doc;
	xmlNodePtr 	xmlNode;
	const gchar     *text_direction = NULL;
	gboolean	isMergedItemset;

	debug_enter ("htmlview_render_item");

	/* don't use node from htmlView_priv as this would be
	   wrong for folders and other merged item sets */
	node = node_from_id (item->nodeId);
	
	isMergedItemset = (node != htmlView_priv.node);

	/* do the XML serialization */
	doc = xmlNewDoc ("1.0");
	xmlNode = xmlNewDocNode (doc, NULL, "itemset", NULL);
	xmlDocSetRootElement (doc, xmlNode);
				
	item_to_xml(item, xmlDocGetRootElement (doc));

	text_direction = htmlview_get_item_direction (item);
			
	if (IS_FEED (node)) {
		xmlNodePtr feed;
		feed = xmlNewChild (xmlDocGetRootElement (doc), NULL, "feed", NULL);
		feed_to_xml (node, feed);
	}
	
	/* do the XSLT rendering */
	params = render_parameter_new ();
	
	if (NULL != node_get_base_url (node)) {
		baseUrl = common_uri_escape (node_get_base_url (node));
		render_parameter_add (params, "baseUrl='%s'", baseUrl);
	}

	render_parameter_add (params, "summary='%d'", summaryMode?1:0);
	render_parameter_add (params, "showFeedName='%d'", isMergedItemset?1:0);
	render_parameter_add (params, "single='%d'", (viewMode == ITEMVIEW_SINGLE_ITEM)?1:0);
	render_parameter_add (params, "txtDirection='%s'", text_direction);
	render_parameter_add (params, "appDirection='%s'", common_get_app_direction ());
	output = render_xml (doc, "item", params);
	
	/* For debugging use: xmlSaveFormatFile("/tmp/test.xml", doc, 1); */
	xmlFreeDoc (doc);
	g_free (baseUrl);
	
	debug_exit ("htmlview_render_item");

	return output;
}
示例#3
0
void
item_set_flag_state (itemPtr item, gboolean newState)
{
    if (newState == item->flagStatus)
        return;

    node_source_item_set_flag (node_from_id (item->nodeId), item, newState);
}
示例#4
0
/**
 * In difference to all the other item state handling methods
 * item_state_set_all_read does not immediately apply the
 * changes to the GUI because it is usually called recursively
 * and would be to slow. Instead the node structure flag for
 * recounting is set. By calling feedlist_update() afterwards
 * those recounts are executed and applied to the GUI.
 */
void
itemset_mark_read (nodePtr node)
{
    itemSetPtr	itemSet;

    if (!node->unreadCount)
        return;

    itemSet = node_get_itemset (node);
    GList *iter = itemSet->ids;
    while (iter) {
        gulong id = GPOINTER_TO_UINT (iter->data);
        itemPtr item = item_load (id);
        if (item) {
            if (!item->readStatus) {
                nodePtr node;

                node = node_from_id (item->nodeId);
                if (node) {
                    item_state_set_recount_flag (node);
                    node_source_item_mark_read (node, item, TRUE);
                } else {
                    g_warning ("itemset_mark_read() on lost item (id=%lu, node id=%s)!", item->id, item->nodeId);
                }

                debug_start_measurement (DEBUG_GUI);

                GSList *duplicates = db_item_get_duplicate_nodes (item->sourceId);
                GSList *duplicate = duplicates;
                while (duplicate) {
                    gchar *nodeId = (gchar *)duplicate->data;
                    nodePtr affectedNode = node_from_id (nodeId);
                    if (affectedNode)
                        item_state_set_recount_flag (affectedNode);
                    g_free (nodeId);
                    duplicate = g_slist_next (duplicate);
                }
                g_slist_free(duplicates);

                debug_end_measurement (DEBUG_GUI, "mark read of duplicates");
            }
            item_unload (item);
        }
        iter = g_list_next (iter);
    }
}
示例#5
0
// FIXME: this ought to be a subscription property!
static guint
itemset_get_max_item_count (itemSetPtr itemSet)
{
	nodePtr node = node_from_id (itemSet->nodeId);

	if (node && IS_FEED (node))
		return feed_get_max_item_count (node);

	return G_MAXUINT;
}
示例#6
0
void
liferea_auth_info_from_store (const gchar *id, const gchar *username, const gchar *password)
{
	nodePtr	node = node_from_id (id);

	g_assert (NULL != node->subscription);

	node->subscription->updateOptions->username = g_strdup (username);
	node->subscription->updateOptions->password = g_strdup (password);
}
示例#7
0
/**
 * In difference to all the other item state handling methods
 * item_state_set_all_read does not immediately apply the 
 * changes to the GUI because it is usually called recursively
 * and would be to slow. Instead the node structure flag for
 * recounting is set. By calling feedlist_update() afterwards
 * those recounts are executed and applied to the GUI.
 */
void
itemset_mark_read (nodePtr node)
{
	itemSetPtr	itemSet;

	itemSet = node_get_itemset (node);
	GList *iter = itemSet->ids;
	while (iter) {
		gulong id = GPOINTER_TO_UINT (iter->data);
		itemPtr item = item_load (id);
		if (item) {
			if (!item->readStatus) {
				nodePtr node = node_from_id (item->nodeId);
				if (node) {
					item_state_set_recount_flag (node);
					node_source_item_mark_read (node, item, TRUE);
				}

				debug_start_measurement (DEBUG_GUI);

				GSList *duplicates = db_item_get_duplicate_nodes (item->sourceId);
				GSList *duplicate = duplicates;
				while (duplicate) {
					gchar *nodeId = (gchar *)duplicate->data;
					nodePtr affectedNode = node_from_id (nodeId);
					if (affectedNode)
						item_state_set_recount_flag (affectedNode);
					g_free (nodeId);
					duplicate = g_slist_next (duplicate);
				}
				g_slist_free(duplicates);

				debug_end_measurement (DEBUG_GUI, "mark read of duplicates");
			}
			item_unload (item);
		}
		iter = g_list_next (iter);
	}

	// FIXME: why not call itemset_free (itemSet); here? Crashes!
}
示例#8
0
void
item_set_read_state (itemPtr item, gboolean newState)
{
    /* Read and update state are coupled insofar as they
       are changed by the same user actions. So we do something
       here if either the read state has changed or the
       updated flag is set (which is always just reset). */

    if (newState == item->readStatus && !item->updateStatus)
        return;

    node_source_item_mark_read (node_from_id (item->nodeId), item, newState);
}
示例#9
0
/**
 * To be called whenever an itemset was updated. If it is the
 * displayed itemset it will be merged against the item list
 * tree view.
 */
void
itemlist_merge_itemset (itemSetPtr itemSet) 
{
	gint	folder_display_mode;

	debug_enter ("itemlist_merge_itemset");
	
	debug_start_measurement (DEBUG_GUI);
	
	/* No node check when loading search results directly */
	if (!itemlist_priv.isSearchResult) {
		nodePtr node = node_from_id (itemSet->nodeId);

		if (!itemlist_priv.currentNode)
			return; /* Nothing to do if nothing is displayed */
		
		if (!IS_VFOLDER (itemlist_priv.currentNode) &&
		    (itemlist_priv.currentNode != node) && 
		    !node_is_ancestor (itemlist_priv.currentNode, node))
			return; /* Nothing to do if the item set does not belong to this node, or this is a search folder */

		conf_get_int_value (FOLDER_DISPLAY_MODE, &folder_display_mode);
		if (IS_FOLDER (itemlist_priv.currentNode) && !folder_display_mode)
			return; /* Bail out if it is a folder without the recursive display preference set */
			
		debug1 (DEBUG_GUI, "reloading item list with node \"%s\"", node_get_title (node));
	} else {
		/* If we are loading a search result we must never merge 
		   anything besides the search items. In fact if we already
		   have items we just return. */
		if (itemlist_priv.searchResultComplete)
			return;
			
		itemlist_priv.searchResultComplete = TRUE;
	}

	/* merge items into item view */
	itemset_foreach (itemSet, itemlist_merge_item);
	
	itemview_update ();
	
	debug_end_measurement (DEBUG_GUI, "itemlist merge");

	debug_exit ("itemlist_merge_itemset");
}
示例#10
0
static void
notif_libnotify_callback_open (NotifyNotification *n, gchar *action, gpointer user_data)
{
	nodePtr node_p;

	g_assert(action != NULL);
	g_assert(strcmp(action, "open") == 0);

	node_p = node_from_id (user_data);

	if (node_p)
		feed_list_view_select (node_p);
	else
		ui_show_error_box (_("This feed does not exist anymore!"));

	notify_notification_close (n, NULL);

	liferea_shell_present ();
}
示例#11
0
static void
notif_libnotify_callback_mark_read (NotifyNotification *n, gchar *action, gpointer user_data)
{
	nodePtr node;

	g_assert (action != NULL);
	g_assert (strcmp (action, "mark_read") == 0);

	node = node_from_id (user_data);

	if (node) {
		feedlist_mark_all_read (node);
		feedlist_reset_new_item_count ();
		item_state_set_all_popup (node->id);
	} else {
		ui_show_error_box (_("This feed does not exist anymore!"));
	}

	notify_notification_close (n, NULL);
}
示例#12
0
static void
item_list_view_add_item_to_tree_store (ItemListView *ilv, GtkTreeStore *itemstore, itemPtr item)
{
	gint		state = 0;
	nodePtr		node;
	GtkTreeIter	*iter;
	GtkTreeIter	old_iter;
	gboolean	exists;
		
	if (item->flagStatus)
		state += 2;
	if (!item->readStatus)
		state += 1;
		
	node = node_from_id (item->nodeId);
	if(!node)
		return;	/* comment items do cause this... maybe filtering them earlier would be a good idea... */
		
	exists = item_list_view_id_to_iter (ilv, item->id, &old_iter);
	iter = &old_iter;
	
	if (!exists) 
	{
		iter = g_new0 (GtkTreeIter, 1);
		gtk_tree_store_prepend (itemstore, iter, NULL);
		g_hash_table_insert (ilv->priv->item_id_to_iter, GUINT_TO_POINTER (item->id), (gpointer)iter);
	}

	gtk_tree_store_set (itemstore, iter,
		                       IS_TIME, (guint64)item->time,
		                       IS_NR, item->id,
				       IS_PARENT, node,
		                       IS_FAVICON, node->icon,
		                       IS_ENCICON, item->hasEnclosure?icon_get (ICON_ENCLOSURE):NULL,
				       IS_ENCLOSURE, item->hasEnclosure,
				       IS_SOURCE, node,
				       IS_STATE, state,
		                       -1);		                       
}
示例#13
0
/* Helper method checking if the passed item set is relevant
   for the currently item list content. */
static gboolean
itemlist_itemset_is_valid (itemSetPtr itemSet)
{
	gint	folder_display_mode;
	nodePtr node;

	node = node_from_id (itemSet->nodeId);

	if (!itemlist->priv->currentNode)
		return FALSE; /* Nothing to do if nothing is displayed */
		
	if (!IS_VFOLDER (itemlist->priv->currentNode) &&
	    (itemlist->priv->currentNode != node) && 
	    !node_is_ancestor (itemlist->priv->currentNode, node))
		return FALSE; /* Nothing to do if the item set does not belong to this node, or this is a search folder */

	conf_get_int_value (FOLDER_DISPLAY_MODE, &folder_display_mode);
	if (IS_FOLDER (itemlist->priv->currentNode) && !folder_display_mode)
		return FALSE; /* Bail out if it is a folder without the recursive display preference set */
		
	debug1 (DEBUG_GUI, "reloading item list with node \"%s\"", node_get_title (node));

	return TRUE;
}
示例#14
0
static gboolean
itemset_merge_item (itemSetPtr itemSet, GList *items, itemPtr item, gint maxChecks, gboolean allowUpdates)
{
	gboolean	allowStateChanges = FALSE;
	gboolean	merge;
	nodePtr		node;

	debug2 (DEBUG_UPDATE, "trying to merge \"%s\" to node id \"%s\"", item_get_title (item), itemSet->nodeId);

	g_assert (itemSet->nodeId);
	node = node_from_id (itemSet->nodeId);
	if (node)
		allowStateChanges = NODE_SOURCE_TYPE (node)->capabilities & NODE_SOURCE_CAPABILITY_ITEM_STATE_SYNC;

	/* first try to merge with existing item */
	merge = itemset_generic_merge_check (items, item, maxChecks, allowUpdates, allowStateChanges);

	/* if it is a new item add it to the item set */
	if (merge) {
		g_assert (!item->nodeId);
		g_assert (!item->id);
		item->nodeId = g_strdup (itemSet->nodeId);
		if (!item->parentNodeId)
			item->parentNodeId = g_strdup (itemSet->nodeId);

		/* step 1: write item to DB */
		db_item_update (item);

		/* step 2: add to itemset */
		itemSet->ids = g_list_prepend (itemSet->ids, GUINT_TO_POINTER (item->id));

		/* step 3: trigger async enrichment of item description */
		if (node && IS_FEED (node) && ((feedPtr)node->data)->html5Extract)
			feed_enrich_item (node->subscription, item);

		debug3 (DEBUG_UPDATE, "-> added \"%s\" (id=%d) to item set %p...", item_get_title (item), item->id, itemSet);

		/* step 4: duplicate detection, mark read if it is a duplicate */
		if (item->validGuid) {
			GSList	*iter, *duplicates;

			duplicates = iter = db_item_get_duplicates (item->sourceId);
			while (iter) {
				debug1 (DEBUG_UPDATE, "-> duplicate guid exists: #%lu", GPOINTER_TO_UINT (iter->data));
				iter = g_slist_next (iter);
			}

			if (g_slist_length (duplicates) > 1) {
				item->readStatus = TRUE;	/* no unread counting... */
				item->popupStatus = FALSE;	/* no notification... */
			}

			g_slist_free (duplicates);
		}

		/* step 5: Check item for new enclosures to download */
		if (node && (((feedPtr)node->data)->encAutoDownload)) {
			GSList *iter = metadata_list_get_values (item->metadata, "enclosure");
			while (iter) {
				enclosurePtr enc = enclosure_from_string (iter->data);
				debug1 (DEBUG_UPDATE, "download enclosure (%s)", (gchar *)iter->data);
				enclosure_download (NULL, enc->url, FALSE /* non interactive */);
				iter = g_slist_next (iter);
				enclosure_free (enc);
			}
		}
	} else {
		debug2 (DEBUG_UPDATE, "-> not adding \"%s\" to node id \"%s\"...", item_get_title (item), itemSet->nodeId);
		item_unload (item);
	}

	return merge;
}
示例#15
0
文件: htmlview.c 项目: asl97/liferea
void
htmlview_update (LifereaHtmlView *htmlview, itemViewMode mode) 
{
	GSList		*iter;
	GString		*output;
	itemPtr		item = NULL;
	gchar		*baseURL = NULL;
	gboolean	summaryMode;

	/* determine base URL */
	switch (mode) {
		case ITEMVIEW_SINGLE_ITEM:
			item = itemlist_get_selected ();
			if(item) {
				baseURL = (gchar *)node_get_base_url (node_from_id (item->nodeId));
				item_unload (item);
			}
			break;
		default:
			if (htmlView_priv.node)
				baseURL = (gchar *) node_get_base_url (htmlView_priv.node);
			break;
	}

	if (baseURL)
		baseURL = g_markup_escape_text (baseURL, -1);
		
	output = g_string_new (NULL);
	htmlview_start_output (output, baseURL, TRUE, TRUE);

	/* HTML view updating means checking which items
	   need to be updated, render them and then 
	   concatenate everything from cache and output it */
	switch (mode) {
		case ITEMVIEW_SINGLE_ITEM:
			item = itemlist_get_selected ();
			if (item) {
				gchar *html = htmlview_render_item (item, mode, FALSE);
				if (html) {
					g_string_append (output, html);
					g_free (html);
				}
				
				item_unload (item);
			}
			break;
		case ITEMVIEW_ALL_ITEMS:
			/* Output optimization for feeds without item content. This
			   is not done for folders, because we only support all items
			   in summary mode or all in detailed mode. With folder item 
			   sets displaying everything in summary because of only a
			   single feed without item descriptions would make no sense. */

			summaryMode = (NULL != htmlView_priv.node) &&
			              !IS_FOLDER (htmlView_priv.node) && 
	        		      !IS_VFOLDER (htmlView_priv.node) && 
	        		      (htmlView_priv.missingContent > 3);

			/* concatenate all items */
			iter = htmlView_priv.orderedChunks;
			while (iter) {
				/* try to retrieve item HTML chunk from cache */
				htmlChunkPtr chunk = (htmlChunkPtr)iter->data;
				
				/* if not found: render new item now and add to cache */
				if (!chunk->html) {
					item = item_load (chunk->id);
					if (item) {
						debug1 (DEBUG_HTML, "rendering item to HTML view: >>>%s<<<", item_get_title (item));
						chunk->html = htmlview_render_item (item, mode, summaryMode);
						item_unload (item);
					}
				}
				
				if (chunk->html)
					g_string_append (output, chunk->html);
					
				iter = g_slist_next (iter);
			}
			break;
		case ITEMVIEW_NODE_INFO:
			{
				gchar *html;
				
				if (htmlView_priv.node) {
					html = node_render (htmlView_priv.node);	
					if (html) {
						g_string_append (output, html);
						g_free (html);
					}
				}
			}
			break;
		default:
			g_warning ("HTML view: invalid viewing mode!!!");
			break;
	}
	
	htmlview_finish_output (output);

	debug1 (DEBUG_HTML, "writing %d bytes to HTML view", strlen (output->str));
	liferea_htmlview_write (htmlview, output->str, baseURL);
	
	g_string_free (output, TRUE);
	g_free (baseURL);
}
示例#16
0
void
feed_list_node_update (const gchar *nodeId)
{
	GtkTreeIter	*iter;
	gchar		*label, *count = NULL;
	guint		labeltype;
	nodePtr		node;

	static gchar	*countColor = NULL;

	node = node_from_id (nodeId);
	iter = feed_list_node_to_iter (nodeId);
	if (!iter)
		return;

	/* Initialize unread item color Pango CSS */
	if (!countColor) {
		const gchar *bg = NULL, *fg = NULL;

		bg = render_get_theme_color ("FEEDLIST_UNREAD_BG");
		fg = render_get_theme_color ("FEEDLIST_UNREAD_FG");
		if (fg && bg) {
			countColor = g_strdup_printf ("foreground='#%s' background='#%s'", fg, bg);
			debug1 (DEBUG_HTML, "Feed list unread CSS: %s\n", countColor);
		}
	}

	labeltype = NODE_TYPE (node)->capabilities;
	labeltype &= (NODE_CAPABILITY_SHOW_UNREAD_COUNT |
        	      NODE_CAPABILITY_SHOW_ITEM_COUNT);

	if (node->unreadCount == 0 && (labeltype & NODE_CAPABILITY_SHOW_UNREAD_COUNT))
		labeltype -= NODE_CAPABILITY_SHOW_UNREAD_COUNT;

	label = g_markup_escape_text (node_get_title (node), -1);
	switch (labeltype) {
		case NODE_CAPABILITY_SHOW_UNREAD_COUNT |
		     NODE_CAPABILITY_SHOW_ITEM_COUNT:
	     		/* treat like show unread count */
		case NODE_CAPABILITY_SHOW_UNREAD_COUNT:
			count = g_strdup_printf ("<span weight='bold' %s> %u </span>", countColor?countColor:"", node->unreadCount);
			break;
		case NODE_CAPABILITY_SHOW_ITEM_COUNT:
			count = g_strdup_printf ("<span weight='bold' %s> %u </span>", countColor?countColor:"", node->itemCount);
		     	break;
		default:
			break;
	}

	/* Extra message for search folder rebuilds */
	if (IS_VFOLDER (node) && node->data) {
		if (((vfolderPtr)node->data)->reloading) {
			gchar *tmp = label;
			label = g_strdup_printf (_("%s\n<i>Rebuilding</i>"), label);
			g_free (tmp);
		}
	}

	gtk_tree_store_set (feedstore, iter,
	                    FS_LABEL, label,
	                    FS_UNREAD, node->unreadCount,
	                    FS_ICON, node->available?node_get_icon (node):icon_get (ICON_UNAVAILABLE),
	                    FS_COUNT, count,
	                    -1);
	g_free (label);

	if (node->parent)
		feed_list_node_update (node->parent->id);
}
示例#17
0
void
liferea_shell_create (GtkApplication *app, const gchar *overrideWindowState)
{
	GtkUIManager	*ui_manager;
	GtkAccelGroup	*accel_group;
	GError		*error = NULL;	
	gboolean	toggle;
	gchar		*id;
	
	debug_enter ("liferea_shell_create");

	g_object_new (LIFEREA_SHELL_TYPE, NULL);

	shell->priv->window = GTK_WINDOW (liferea_shell_lookup ("mainwindow"));

	gtk_window_set_application (GTK_WINDOW (shell->priv->window), app);
	
	/* 1.) menu creation */
	
	debug0 (DEBUG_GUI, "Setting up menues");

	shell->priv->itemlist = itemlist_create ();

	/* Prepare some toggle button states */	
	conf_get_bool_value (REDUCED_FEEDLIST, &toggle);
	liferea_shell_feedlist_toggle_entries[0].is_active = toggle;

	ui_manager = gtk_ui_manager_new ();

	shell->priv->generalActions = gtk_action_group_new ("GeneralActions");
	gtk_action_group_set_translation_domain (shell->priv->generalActions, PACKAGE);
	gtk_action_group_add_actions (shell->priv->generalActions, liferea_shell_action_entries, G_N_ELEMENTS (liferea_shell_action_entries), shell->priv);
	gtk_action_group_add_toggle_actions (shell->priv->generalActions, liferea_shell_action_toggle_entries, G_N_ELEMENTS (liferea_shell_action_toggle_entries), shell->priv);
	gtk_action_group_add_radio_actions (shell->priv->generalActions, liferea_shell_view_radio_entries, G_N_ELEMENTS (liferea_shell_view_radio_entries), itemlist_get_view_mode (), (GCallback)on_view_activate, (gpointer)TRUE);
	gtk_action_group_add_toggle_actions (shell->priv->generalActions, liferea_shell_feedlist_toggle_entries, G_N_ELEMENTS (liferea_shell_feedlist_toggle_entries), shell->priv);
	gtk_ui_manager_insert_action_group (ui_manager, shell->priv->generalActions, 0);

	shell->priv->addActions = gtk_action_group_new ("AddActions");
	gtk_action_group_set_translation_domain (shell->priv->addActions, PACKAGE);
	gtk_action_group_add_actions (shell->priv->addActions, liferea_shell_add_action_entries, G_N_ELEMENTS (liferea_shell_add_action_entries), shell->priv);
	gtk_ui_manager_insert_action_group (ui_manager, shell->priv->addActions, 0);

	shell->priv->feedActions = gtk_action_group_new ("FeedActions");
	gtk_action_group_set_translation_domain (shell->priv->feedActions, PACKAGE);
	gtk_action_group_add_actions (shell->priv->feedActions, liferea_shell_feed_action_entries, G_N_ELEMENTS (liferea_shell_feed_action_entries), shell->priv);
	gtk_ui_manager_insert_action_group (ui_manager, shell->priv->feedActions, 0);

	shell->priv->readWriteActions = gtk_action_group_new("ReadWriteActions");
	gtk_action_group_set_translation_domain (shell->priv->readWriteActions, PACKAGE);
	gtk_action_group_add_actions (shell->priv->readWriteActions, liferea_shell_read_write_action_entries, G_N_ELEMENTS (liferea_shell_read_write_action_entries), shell->priv);
	gtk_ui_manager_insert_action_group (ui_manager, shell->priv->readWriteActions, 0);

	shell->priv->itemActions = gtk_action_group_new ("ItemActions");
	gtk_action_group_set_translation_domain (shell->priv->itemActions, PACKAGE);
	gtk_action_group_add_actions (shell->priv->itemActions, liferea_shell_item_action_entries, G_N_ELEMENTS (liferea_shell_item_action_entries), shell->priv);
	gtk_ui_manager_insert_action_group (ui_manager, shell->priv->itemActions, 0);

	accel_group = gtk_ui_manager_get_accel_group (ui_manager);
	gtk_window_add_accel_group (GTK_WINDOW (shell->priv->window), accel_group);
	g_object_unref (accel_group);

	g_signal_connect (gtk_accel_map_get (), "changed", G_CALLBACK (on_accel_change), NULL);

	if (!gtk_ui_manager_add_ui_from_string (ui_manager, liferea_shell_ui_desc, -1, &error))
		g_error ("building menus failed: %s", error->message);

	shell->priv->menubar = gtk_ui_manager_get_widget (ui_manager, "/MainwindowMenubar");
	shell->priv->toolbar = gtk_ui_manager_get_widget (ui_manager, "/maintoolbar");

	/* Ensure GTK3 toolbar shadows... */
	gtk_style_context_add_class (gtk_widget_get_style_context (shell->priv->toolbar), "primary-toolbar");

	/* what a pain, why is there no markup for this option? */
	g_object_set (G_OBJECT (gtk_ui_manager_get_widget (ui_manager, "/maintoolbar/newFeedButton")), "is_important", TRUE, NULL);
	g_object_set (G_OBJECT (gtk_ui_manager_get_widget (ui_manager, "/maintoolbar/nextUnreadButton")), "is_important", TRUE, NULL);
	g_object_set (G_OBJECT (gtk_ui_manager_get_widget (ui_manager, "/maintoolbar/MarkAsReadButton")), "is_important", TRUE, NULL);
	g_object_set (G_OBJECT (gtk_ui_manager_get_widget (ui_manager, "/maintoolbar/UpdateAllButton")), "is_important", TRUE, NULL);
	g_object_set (G_OBJECT (gtk_ui_manager_get_widget (ui_manager, "/maintoolbar/SearchButton")), "is_important", TRUE, NULL);

	/* 2.) setup containers */
	
	debug0 (DEBUG_GUI, "Setting up widget containers");

	gtk_box_pack_start (GTK_BOX (liferea_shell_lookup ("vbox1")), shell->priv->toolbar, FALSE, FALSE, 0);
	gtk_box_reorder_child (GTK_BOX (liferea_shell_lookup ("vbox1")), shell->priv->toolbar, 0);
	gtk_box_pack_start (GTK_BOX (liferea_shell_lookup ("vbox1")), shell->priv->menubar, FALSE, FALSE, 0);
	gtk_box_reorder_child (GTK_BOX (liferea_shell_lookup ("vbox1")), shell->priv->menubar, 0);

	gtk_widget_show_all(GTK_WIDGET(shell->priv->toolbar));

	g_signal_connect ((gpointer) liferea_shell_lookup ("itemtabs"), "key_press_event",
	                  G_CALLBACK (on_key_press_event_null_cb), NULL);

	g_signal_connect ((gpointer) liferea_shell_lookup ("itemtabs"), "key_release_event",
	                  G_CALLBACK (on_key_press_event_null_cb), NULL);
	
	g_signal_connect ((gpointer) liferea_shell_lookup ("itemtabs"), "scroll_event",
	                  G_CALLBACK (on_notebook_scroll_event_null_cb), NULL);
	
	g_signal_connect (G_OBJECT (shell->priv->window), "delete_event", G_CALLBACK(on_close), shell->priv);
	g_signal_connect (G_OBJECT (shell->priv->window), "window_state_event", G_CALLBACK(on_window_state_event), shell->priv);
	g_signal_connect (G_OBJECT (shell->priv->window), "key_press_event", G_CALLBACK(on_key_press_event), shell->priv);
	
	/* 3.) setup status bar */
	
	debug0 (DEBUG_GUI, "Setting up status bar");
	
	shell->priv->statusbar = GTK_STATUSBAR (liferea_shell_lookup ("statusbar"));
	shell->priv->statusbarLocked = FALSE;
	shell->priv->statusbarLockTimer = 0;
	shell->priv->statusbar_feedsinfo = gtk_label_new("");
	gtk_widget_show(shell->priv->statusbar_feedsinfo);
	gtk_box_pack_start (GTK_BOX (shell->priv->statusbar), shell->priv->statusbar_feedsinfo, FALSE, FALSE, 5);

	/* 4.) setup tabs */
	
	debug0 (DEBUG_GUI, "Setting up tabbed browsing");	
	shell->priv->tabs = browser_tabs_create (GTK_NOTEBOOK (liferea_shell_lookup ("browsertabs")));
	
	/* 5.) setup feed list */

	debug0 (DEBUG_GUI, "Setting up feed list");
	shell->priv->feedlistView = GTK_TREE_VIEW (liferea_shell_lookup ("feedlist"));
	feed_list_view_init (shell->priv->feedlistView);

	/* 6.) setup menu sensivity */
	
	debug0 (DEBUG_GUI, "Initialising menues");
		
	/* On start, no item or feed is selected, so Item menu should be insensitive: */
	liferea_shell_update_item_menu (FALSE);

	/* necessary to prevent selection signals when filling the feed list
	   and setting the 2/3 pane mode view */
	gtk_widget_set_sensitive (GTK_WIDGET (shell->priv->feedlistView), FALSE);
	
	/* 7.) setup item view */
	
	debug0 (DEBUG_GUI, "Setting up item view");

	shell->priv->itemview = itemview_create (GTK_WIDGET (shell->priv->window));

        /* 8.) load icons as required */
        
        debug0 (DEBUG_GUI, "Loading icons");
        
        icons_load ();
	
	/* 9.) update and restore all menu elements */

	liferea_shell_update_toolbar ();
	liferea_shell_update_history_actions ();
	liferea_shell_setup_URL_receiver ();
	liferea_shell_restore_state (overrideWindowState);
	
	gtk_widget_set_sensitive (GTK_WIDGET (shell->priv->feedlistView), TRUE);

	/* 10.) After main window is realized get theme colors and set up feed
 	        list and tray icon */
	render_init_theme_colors (GTK_WIDGET (shell->priv->window));

	shell->priv->feedlist = feedlist_create ();
	g_signal_connect (shell->priv->feedlist, "new-items",
	                  G_CALLBACK (liferea_shell_update_unread_stats), shell->priv->feedlist);

	/* 11.) Restore latest selection */

	// FIXME: Move to feed list code
	if (conf_get_str_value (LAST_NODE_SELECTED, &id)) {
		feed_list_view_select (node_from_id (id));
		g_free (id);
	}

	/* 12. Setup shell plugins */

	shell->priv->extensions = peas_extension_set_new (PEAS_ENGINE (liferea_plugins_engine_get_default ()),
		                             LIFEREA_TYPE_SHELL_ACTIVATABLE, "shell", shell, NULL);

	g_signal_connect (shell->priv->extensions, "extension-added", G_CALLBACK (on_extension_added), shell);
	g_signal_connect (shell->priv->extensions, "extension-removed",	G_CALLBACK (on_extension_removed), shell);

	peas_extension_set_call (shell->priv->extensions, "activate");

	/* 14. Rebuild search folders if needed */
	if (searchFolderRebuild)
		vfolder_foreach (vfolder_rebuild);

	debug_exit ("liferea_shell_create");
}
示例#18
0
static void
notif_libnotify_callback_show_details (NotifyNotification *n, gchar *action, gpointer user_data)
{
	nodePtr node_p;

	GList *list_p;
	itemPtr item_p;

	gchar *labelText_p;
	gchar *labelText_now_p = NULL;
	gchar *labelText_prev_p;

	gchar *labelHeadline_p;
	const gchar *labelURL_p;

	gint item_count = 0;

	g_assert (action != NULL);
	g_assert (strcmp(action, "show_details") == 0);
	node_p = node_from_id (user_data);

	if (node_p) {
		itemSetPtr itemSet = node_get_itemset (node_p);

		labelText_now_p = g_strdup ("");

		/* Gather the feed's headlines */
		list_p = itemSet->ids;
		while (list_p) {
			item_p = item_load (GPOINTER_TO_UINT (list_p->data));
			if (item_p->popupStatus && !item_p->readStatus) {
				item_p->popupStatus = FALSE;
				item_count += 1;

				labelHeadline_p = g_strdup (item_get_title (item_p));
				if (labelHeadline_p == NULL ) {
					labelHeadline_p = g_strdup_printf (_("This news entry has no headline"));
				}

				labelURL_p = item_get_base_url (item_p);
				if (labelURL_p) {
					labelText_p = g_strdup_printf ("%s <a href='%s'>%s</a>\n", labelHeadline_p, labelURL_p, _("Visit"));
				} else {
					labelText_p = g_strdup_printf ("%s\n", labelHeadline_p);
				}

				labelText_prev_p = labelText_now_p;
				labelText_now_p = g_strconcat(labelText_now_p, labelText_p, NULL);

				g_free(labelHeadline_p);
				g_free(labelText_p);
				g_free(labelText_prev_p);
			}
			item_unload (item_p);
			list_p = g_list_next (list_p);
		}
		itemset_free (itemSet);

		if (item_count == 0) {
			g_free (labelText_now_p);
			return;
		}
	} else {
		ui_show_error_box(_("This feed does not exist anymore!"));
	}

	notify_notification_close (n, NULL);

	if (node_p) {
//		notify_notification_update ( n, node_get_title(node_p), labelText_now_p, NULL);
//		notify_notification_clear_actions(n);

		n = notify_notification_new (node_get_title (node_p), labelText_now_p, NULL, NULL);

		notify_notification_set_icon_from_pixbuf (n, node_get_icon (node_p));
		notify_notification_set_category (n, "feed");
		notify_notification_set_timeout (n, NOTIFY_EXPIRES_NEVER);

		if (supports_actions) {
			notify_notification_add_action (n, "open", _("Open feed"),
							(NotifyActionCallback)notif_libnotify_callback_open,
							node_p->id, NULL);
			notify_notification_add_action (n, "mark_read", _("Mark all as read"),
							(NotifyActionCallback)notif_libnotify_callback_mark_read,
							node_p->id, NULL);
		}

		notify_notification_attach_to_status_icon (n, ui_tray_get_status_icon ());

		if (!notify_notification_show (n, NULL)) {
			g_warning ("libnotify.c - failed to update notification via libnotify\n");
		}

		g_free (labelText_now_p);
	}
}
示例#19
0
guint
itemset_merge_items (itemSetPtr itemSet, GList *list, gboolean allowUpdates, gboolean markAsRead)
{
	GList	*iter, *droppedItems = NULL, *items = NULL;
	guint	i, max, length, toBeDropped, newCount = 0, flagCount = 0;
	nodePtr	node;

	debug_start_measurement (DEBUG_UPDATE);

	debug2 (DEBUG_UPDATE, "old item set %p of (node id=%s):", itemSet, itemSet->nodeId);

	/* 1. Preparation: determine effective maximum cache size

	   The problem here is that the configured maximum cache
	   size might not always be sufficient. We need to check
	   border use cases in the following. */

	length = g_list_length (list);
	max = itemset_get_max_item_count (itemSet);

	/* Preload all items for flag counting and later merging comparison */
	iter = itemSet->ids;
	while (iter) {
		itemPtr item = item_load (GPOINTER_TO_UINT (iter->data));
		if (item) {
			items = g_list_append (items, item);
			if (item->flagStatus)
				flagCount++;
		}
		iter = g_list_next (iter);
	}
	debug1(DEBUG_UPDATE, "current cache size: %d", g_list_length(itemSet->ids));
	debug1(DEBUG_UPDATE, "current cache limit: %d", max);
	debug1(DEBUG_UPDATE, "downloaded feed size: %d", g_list_length(list));
	debug1(DEBUG_UPDATE, "flag count: %d", flagCount);

	/* Case #1: Avoid having too many flagged items. We count the
	   flagged items and check if they are fewer than

	      <cache limit> - <downloaded items>

	   to ensure that all new items fit in a feed cache full of
	   flagged items.

	   This handling MUST NOT be invoked when the number of items
	   is larger then the cache size, otherwise we would never
	   remove any items for large feeds. */
	if ((length < max) && (max < length + flagCount)) {
		max = flagCount + length;
		debug2 (DEBUG_UPDATE, "too many flagged items -> increasing cache limit to %u (node id=%s)", max, itemSet->nodeId);
	}

	/* 2. Avoid cache wrapping (if feed size > cache size)

	   Truncate the new itemset if it is longer than
	   the maximum cache size which could cause items
	   to be dropped and added again on subsequent
	   merges with the same feed content */
	if (length > max) {
		debug2 (DEBUG_UPDATE, "item list too long (%u, max=%u) for merging!", length, max);

		/* reach max element */
		for(i = 0, iter = list; (i < max) && iter; ++i)
			iter = g_list_next (iter);

		/* and remove all following elements */
		while (iter) {
			itemPtr item = (itemPtr) iter->data;
			debug2 (DEBUG_UPDATE, "ignoring item nr %u (%s)...", ++i, item_get_title (item));
			item_unload (item);
			iter = g_list_next (iter);
			list = g_list_remove (list, item);
		}
	}

	/* 3. Merge received items to existing item set

	   Items are given in top to bottom display order.
	   Adding them in this order would mean to reverse
	   their order in the merged list, so merging needs
	   to be done bottom to top. During this step the
	   item list (items) may exceed the cache limit. */
	iter = g_list_last (list);
	while (iter) {
		itemPtr item = (itemPtr)iter->data;

		if (markAsRead)
			item->readStatus = TRUE;

		if (itemset_merge_item (itemSet, items, item, length, allowUpdates)) {
			newCount++;
			items = g_list_prepend (items, iter->data);
		}
		iter = g_list_previous (iter);
	}
	g_list_free (list);

	vfolder_foreach (node_update_counters);

	node = node_from_id (itemSet->nodeId);
	if (node && (NODE_SOURCE_TYPE (node)->capabilities & NODE_SOURCE_CAPABILITY_ITEM_STATE_SYNC))
		node_update_counters (node);

	debug1(DEBUG_UPDATE, "added %d new items", newCount);

	/* 4. Apply cache limit for effective item set size
	      and unload older items as necessary. In this step
	      it is important never to drop flagged items and
	      to drop the oldest items only. */

	if (g_list_length (items) > max)
		toBeDropped = g_list_length (items) - max;
	else
		toBeDropped = 0;

	debug3 (DEBUG_UPDATE, "%u new items, cache limit is %u -> dropping %u items", newCount, max, toBeDropped);
	items = g_list_sort (items, itemset_sort_by_date);
	iter = g_list_last (items);
	while (iter) {
		itemPtr item = (itemPtr) iter->data;
		if (toBeDropped > 0 && !item->flagStatus) {
			debug2 (DEBUG_UPDATE, "dropping item nr %u (%s)....", item->id, item_get_title (item));
			droppedItems = g_list_append (droppedItems, item);
			/* no unloading here, it's done in itemlist_remove_items() */
			toBeDropped--;
		} else {
			item_unload (item);
		}
		iter = g_list_previous (iter);
	}

	if (droppedItems) {
		itemlist_remove_items (itemSet, droppedItems);
		g_list_free (droppedItems);
	}

	/* 5. Sanity check to detect merging bugs */
	if (g_list_length (items) > itemset_get_max_item_count (itemSet) + flagCount)
		debug0 (DEBUG_CACHE, "Fatal: Item merging bug! Resulting item list is too long! Cache limit does not work. This is a severe program bug!");

	g_list_free (items);

	debug_end_measurement (DEBUG_UPDATE, "merge itemset");

	return newCount;
}