| Line | Revision | Contents |
| 1 | 185 | /* Calliope Music Player |
| 2 | * Copyright 2005-09 Sam Thursfield <ssssam gmail.com> | |
| 3 | * | |
| 4 | * This program is free software: you can redistribute it and/or modify | |
| 5 | * it under the terms of the GNU General Public License as published by | |
| 6 | * the Free Software Foundation, either version 3 of the License, or | |
| 7 | * (at your option) any later version. | |
| 8 | * | |
| 9 | * This program is distributed in the hope that it will be useful, | |
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 12 | * GNU General Public License for more details. | |
| 13 | * | |
| 14 | * You should have received a copy of the GNU General Public License | |
| 15 | 253 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 16 | 185 | */ |
| 17 | 253 | |
| 18 | 185 | #include <string.h> |
| 19 | #include "gtk/gtktreemodel.h" | |
| 20 | #include "gtk/gtktreednd.h" | |
| 21 | #include "main.h" | |
| 22 | #include "musicsource.h" | |
| 23 | #include "musicsourceview.h" | |
| 24 | #include "musicsourceview-private.h" | |
| 25 | ||
| 26 | /* MusicSourceView: | |
| 27 | * this file defines a basic view for the MusicSource model. It is possible to extend | |
| 28 | * it partially, as in LibraryView, but it's not intended for there to be a complete | |
| 29 | 253 | * reimplementation in some subclass. I don't see there being any dramatic improvements |
| 30 | 185 | * that couldn't be implemented directly in this base class. |
| 31 | * | |
| 32 | * Its architecture is such that only a tiny portion of the source needs to be in memory | |
| 33 | * in any situation. The view is divided up into pages, of MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE | |
| 34 | * rows. On creation, the first & last pages are queried from the source, and the total | |
| 35 | * row count is *estimated*. This value can be used for the tree view scrollbar - which needs | |
| 36 | * to be visually approximate but is by no means an exact selector of position, except for at | |
| 37 | * the top and the bottom. This is less true with small numbers of rows, but in this case | |
| 38 | * of course we can just query the whole source. The scrollbar can then select a position | |
| 39 | * 0 to 100% through the view - and we can estimate what page this will be on and give | |
| 40 | * hopefully sensible results! Due to estimates being based on averages (obviously) the bigger | |
| 41 | * the page size the more realistic the estimates will probably be. Even in the worst case, | |
| 42 | * the only issue is a goofy vertical scroll bar, **provided MIN_PAGE_SIZE is bigger than the | |
| 43 | * visible area**. | |
| 44 | * | |
| 45 | 253 | * FIXME: there are a couple of major hacks in place at the moment to work around the |
| 46 | 185 | * limitations of the current GtkTreeView. What we need is a GtkTreeView which doesn't |
| 47 | * start its day by querying the entire model .. until then, the strategy is this. | |
| 48 | * 1. iter.stamp stores the first row in the current node this iter represented, so we | |
| 49 | * can tell how far it has travelled easily. (Which I know is a misuse of stamp :) | |
| 50 | 253 | * 2. if one iterator goes through more than PAGE_SIZE rows, we stop querying the source, |
| 51 | 185 | * set user_data2 to NULL and instead let it go through the estimated rows. This is |
| 52 | * called 'freefall'. | |
| 53 | * 3. has_child always returns true except for leaves | |
| 54 | * 4. calls to iter_nth_child are treated as using the estimated row number, unless it's | |
| 55 | * actually known - so we return what's near that row rather than its actual contents. | |
| 56 | * 5. I presume this only works in fixed height mode at the moment. | |
| 57 | * 6. if an estimated n is found to be wrong, row-added, row-changed and row-removed are | |
| 58 | * emitted until the treeview's internal data is correct. | |
| 59 | 253 | * 7. to help test for overlaps, each page can have one row more than its -> size suggests, |
| 60 | 185 | * except: the last page, and any page with chain_next=TRUE. This is used by libraryview. |
| 61 | * 8. therefore, in fixed height mode, tree_view_set_model should involve reading the first, | |
| 62 | * last and middle pages only. (Middle page because gtktreeview reads the root of its | |
| 63 | * internal tree). In non fixed height mode I don't really know what happens. | |
| 64 | * 9. row-inserted etc. signals are unreliable and only for the benefit of the treeview. If you | |
| 65 | * want to find out about entries being added and removed, you should connect to the signals | |
| 66 | * of the underlying musicsourceview. | |
| 67 | 253 | * |
| 68 | 185 | * FIXME: I'm sure there are a lot of other possible improvements to this implementation. |
| 69 | * | |
| 70 | * Variables such as n_children have an e_ or _e counterpart. This is the estimated value. | |
| 71 | * _e is always set; _n is -1 if unknown. If _n is known, _e=_n. | |
| 72 | */ | |
| 73 | 253 | |
| 74 | 185 | /* IMPORTANT POINTS: |
| 75 | GtkTreePaths are no longer reliable, because I could return one with an estimated n, | |
| 76 | 253 | and then some other query could make us find the real n and suddenly the path would |
| 77 | 185 | point to a different row with no indication of the change. How to solve this conundrum?? |
| 78 | Well, sometimes it doesn't matter, if the path is only used briefly (ie. nothing else | |
| 79 | given a chance to query the model, and no queries which would cause us to load more | |
| 80 | pages. | |
| 81 | Solutions: | |
| 82 | -> give GtkTreePaths an 'approximate' flag? | |
| 83 | ow do you refer to a row consistently? Well gtktreerowreference would still work b/c | |
| 84 | we emit row-inserted and row-deleted when we renumber rows. I guess that's the only | |
| 85 | way. | |
| 86 | 253 | |
| 87 | 185 | The point of this rambling anyway is, don't rely on GtkTreePath once you call something |
| 88 | that could result in estimates being revised - ie. iter_next, iter_nth_child or get_iter | |
| 89 | I think. | |
| 90 | */ | |
| 91 | ||
| 92 | /* GTKTREEITER STRUCTURE: | |
| 93 | * stamp: first row n within this *node* that this iter represented. | |
| 94 | * FIXME: i know this is a misuse of stamp :( | |
| 95 | * user_data: ViewNode * | |
| 96 | * user_data2: ViewPage *, or NULL if has been through more than a page already | |
| 97 | * user_data3: index under current page, or under node | |
| 98 | * | |
| 99 | * If user_data3+(ViewPage *)user_data2->start_e > iter.stamp+MIN_PAGE_SIZE, | |
| 100 | * iter_next does no more page querying and instead returns TRUE up until it falls off the | |
| 101 | * bottom of the last page. I decided to call this freefall for some reason. | |
| 102 | */ | |
| 103 | ||
| 104 | /* ORPHANS: | |
| 105 | * All the stuff about orphans looks pretty confusing. The basic idea is that one view config | |
| 106 | * node, where the next node down is a many:1 reference instead of the usual 1:many, can have | |
| 107 | * some entries with more than one parent, and some entries with no parents at all. The entries | |
| 108 | * with more than one parent just appear more than once, but the entries with no parent are | |
| 109 | * treated specially with all this orphanage nonsense. | |
| 110 | * | |
| 111 | 253 | * At the moment, this is only possible with the track->recording relation. Hence only one |
| 112 | * orphan property in a config is possible at the moment. I don't see any point in the near future | |
| 113 | 215 | * when more than one is likely. Some work is needed to make more than one orphaning join work. |
| 114 | 185 | * |
| 115 | 217 | * To accomodate orphans the config can branch at a certain point. If a branch isn't specified |
| 116 | * explicitly, it's assumed to be at the first node. Take this example config: | |
| 117 | 185 | * |
| 118 | 217 | * artist > (album > release > track:recording) / recording |
| 119 | 185 | * |
| 120 | 253 | * The orphan join is track:recording, on config level 3. Any recordings which are not |
| 121 | 185 | * referenced in any track.recording_id are instead found on level 1, after all of the albums |
| 122 | 253 | * - the connecting property (in this case recording.artist_id) is found in the same way as |
| 123 | 217 | * all the other connections. The branch point is often referred to as the orphanage. |
| 124 | 185 | * |
| 125 | * Orphan info is stored as follows: | |
| 126 | 217 | * - the branching node has its 'orphan_branch' member set to the head node of the branch |
| 127 | 185 | */ |
| 128 | 253 | |
| 129 | 185 | /* LEVELS: |
| 130 | * Level numbering works as follows. Using the above example config we get a tree like this: | |
| 131 | * | |
| 132 | * root | |
| 133 | * | - artist 1 | |
| 134 | * | - album 1 | |
| 135 | * | - release 1 | |
| 136 | * | track 1 & recording 1 | |
| 137 | * | - recording 2 (an orphan) | |
| 138 | * | - recording 3 (..) | |
| 139 | * | - artist 2 | |
| 140 | * .... | |
| 141 | * | |
| 142 | 253 | * In this tree, the root node has depth 0. The artists have depth 1. The album and the |
| 143 | 185 | * orphan recording nodes are at depth 2. The release is at depth 3, and the track is at 4. |
| 144 | * The *config depth* is the position in the view config. For both the orphan recordings and | |
| 145 | * the normal recordings, for example, this will be 3. For the artists, the config depth is 1. | |
| 146 | * | |
| 147 | * The rec_level and rec_orphan_level members store the depth that recordings and orphan | |
| 148 | * recordings are at, respectively. | |
| 149 | */ | |
| 150 | ||
| 151 | Column column[MUSIC_SOURCE_VIEW_COLUMN_COUNT]={ | |
| 152 | 310 | // Smart columns |
| 153 | { "Artist", TRUE, -1, -1 }, | |
| 154 | ||
| 155 | // Dumb columns | |
| 156 | { "Original Title", TRUE, _COMPOSITION_NAME, -1, 14 }, | |
| 157 | 185 | { "Length", TRUE, _RECORDING_LENGTH, -1, -1 }, |
| 158 | { "Title", TRUE, _RECORDING_NAME, -1, 1}, | |
| 159 | 310 | { "Composer", FALSE, _ARTIST_NAME, _COMPOSITION_ARTIST, 15 }, |
| 160 | { "Recording Artist", FALSE, _ARTIST_NAME, _RECORDING_ARTIST, 2}, | |
| 161 | 185 | { "Path", FALSE, _FILE_PATH, -1, -1}, |
| 162 | { "Path Encoding", FALSE, _FILE_PATH_ENCODING, -1, -1}, | |
| 163 | { "Type", FALSE, _FILE_TYPE, -1, -1}, | |
| 164 | { "Size", FALSE, _FILE_SIZE, -1, -1}, | |
| 165 | 310 | { "Bitrate", TRUE, _FILE_BITRATE, -1, -1}, |
| 166 | 185 | { "Sample Rate", FALSE, _FILE_SAMPLERATE, -1, -1}, |
| 167 | { "Channels", FALSE, _FILE_CHANNELS, -1, -1}, | |
| 168 | { "Date Added", TRUE, _FILE_DATE_ADDED, -1, -1}, | |
| 169 | { "Album Title", TRUE, _ALBUM_NAME, -1, -1}, | |
| 170 | 310 | { "Album Artist", FALSE, _ARTIST_NAME, _ALBUM_ARTIST, -1 }, |
| 171 | 185 | { "Release Date", TRUE, _RELEASE_DATE, -1, -1 }, |
| 172 | { "Track Number", TRUE, _TRACK_NUMBER, -1, -1 } | |
| 173 | }; | |
| 174 | ||
| 175 | #define COLUMN_COUNT MUSIC_SOURCE_VIEW_COLUMN_COUNT | |
| 176 | 268 | static gboolean columns_initialised = FALSE; |
| 177 | 185 | |
| 178 | // MusicSourceViewPrivate and some related stuff is defined in musicsourceview-private.h | |
| 179 | ||
| 180 | ||
| 181 | ||
| 182 | static void tree_model_init(GtkTreeModelIface *iface); | |
| 183 | static GObject *constructor(GType type, guint n_construct_properties, GObjectConstructParam *construct_params); | |
| 184 | static void finalize(GObject *self); | |
| 185 | static void set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); | |
| 186 | ||
| 187 | // GtkTreeModel methods | |
| 188 | static GtkTreeModelFlags get_flags(GtkTreeModel *tree_model); | |
| 189 | static gint get_n_columns(GtkTreeModel *tree_model); | |
| 190 | static GType get_column_type(GtkTreeModel *tree_model, gint index); | |
| 191 | ||
| 192 | static gboolean get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path); | |
| 193 | static GtkTreePath *get_path(GtkTreeModel *tree_model, GtkTreeIter *iter); | |
| 194 | static void get_value(GtkTreeModel *tree_model, GtkTreeIter *iter, gint column, GValue *value); | |
| 195 | static gboolean iter_children(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent); | |
| 196 | ||
| 197 | // The four above methods are implemented in terms of these five | |
| 198 | static gboolean iter_next(GtkTreeModel *tree_model, GtkTreeIter *iter); | |
| 199 | static gboolean iter_has_child(GtkTreeModel *tree_model, GtkTreeIter *iter); | |
| 200 | 216 | static gint iter_n_children (GtkTreeModel *tree_model, GtkTreeIter *iter); |
| 201 | 185 | static gboolean iter_nth_child(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, gint n); |
| 202 | static gboolean iter_parent(GtkTreeModel *tree_model,GtkTreeIter *iter, GtkTreeIter *child); | |
| 203 | ||
| 204 | 228 | static void _init_node(ViewNode *node, GList *parent_tail, GList *head, GList *tail); |
| 205 | 185 | static gboolean _find_iter_for_row(MusicSourceView *self, ViewNode *node, int n, GtkTreeIter *iter); |
| 206 | 261 | static gboolean defloat(MusicSourceView *self, GtkTreeIter *iter); |
| 207 | 268 | static int _get_id_at_iter (MusicSourceView *self, EntryType type, GtkTreeIter *iter_param, |
| 208 | int entry_map[ENTRY_TYPE_COUNT]); | |
| 209 | 185 | |
| 210 | static void interface_track_printf(const char *text, ...); | |
| 211 | void _music_source_view_dump_node(MusicSourceView *self, ViewNode *node); | |
| 212 | //static void dump_iter(GtkTreeIter *iter); | |
| 213 | //static void dump_page(ViewPage *page); | |
| 214 | ||
| 215 | enum { | |
| 216 | PROP_0, PROP_SOURCE, PROP_CONFIG | |
| 217 | }; | |
| 218 | ||
| 219 | G_DEFINE_TYPE_WITH_CODE (MusicSourceView, music_source_view, G_TYPE_OBJECT, | |
| 220 | G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, tree_model_init)) | |
| 221 | ||
| 222 | ||
| 223 | 215 | static void music_source_view_class_init (MusicSourceViewClass *cl) { |
| 224 | GObjectClass *object_class = (GObjectClass *) cl; | |
| 225 | object_class->constructor = constructor; | |
| 226 | object_class->finalize = finalize; | |
| 227 | object_class->set_property = set_property; | |
| 228 | 253 | |
| 229 | g_object_class_install_property (object_class, PROP_SOURCE, | |
| 230 | g_param_spec_object("source", "source", "Source", | |
| 231 | MUSIC_SOURCE_TYPE, STATICSTRS | | |
| 232 | 215 | G_PARAM_CONSTRUCT_ONLY|G_PARAM_WRITABLE)); |
| 233 | g_object_class_install_property (object_class, PROP_CONFIG, | |
| 234 | 253 | g_param_spec_pointer("config", "config", "View configuration", |
| 235 | STATICSTRS | G_PARAM_CONSTRUCT_ONLY | | |
| 236 | 215 | G_PARAM_WRITABLE)); |
| 237 | 253 | |
| 238 | 185 | g_type_class_add_private(object_class, sizeof(MusicSourceViewPrivate)); |
| 239 | } | |
| 240 | ||
| 241 | static void tree_model_init(GtkTreeModelIface *iface) { | |
| 242 | iface->get_flags=get_flags; iface->get_n_columns=get_n_columns; | |
| 243 | iface->get_column_type=get_column_type; iface->get_iter=get_iter; | |
| 244 | iface->get_path=get_path; iface->get_value=get_value; | |
| 245 | iface->iter_next=iter_next; iface->iter_children=iter_children; | |
| 246 | 216 | iface->iter_has_child=iter_has_child; iface->iter_n_children = iter_n_children; |
| 247 | 185 | iface->iter_nth_child=iter_nth_child; iface->iter_parent=iter_parent; |
| 248 | } | |
| 249 | ||
| 250 | static GObject *constructor(GType type, guint n_construct_properties, GObjectConstructParam *construct_params) { | |
| 251 | GObject *object=((GObjectClass *)music_source_view_parent_class)->constructor(type, n_construct_properties, construct_params); | |
| 252 | MusicSourceView *self=MUSIC_SOURCE_VIEW(object); | |
| 253 | 253 | |
| 254 | 185 | // FIXME: should be done on program init really. |
| 255 | if (!columns_initialised) { | |
| 256 | 268 | for (int i=0; i<MUSIC_SOURCE_VIEW_COLUMN_COUNT; i++) |
| 257 | entry_property_map (APID_GET_TYPE(column[i].apid), column[i].route_apid, | |
| 258 | column[i].connection); | |
| 259 | columns_initialised = TRUE; | |
| 260 | 185 | }; |
| 261 | ||
| 262 | g_object_ref(SP->source); | |
| 263 | ||
| 264 | 216 | if (SP->config==NULL || SP->config->head==NULL) { |
| 265 | g_message("musicsourceview: empty configuration\n"); | |
| 266 | 185 | return object; |
| 267 | }; | |
| 268 | 253 | |
| 269 | 185 | SP->row_markers = NULL; |
| 270 | //char *dump = view_config_to_string(SP->config, TRUE); | |
| 271 | //printf ("musicsourceview: config: %s\n", dump); fflush(stdout); | |
| 272 | //g_free (dump); | |
| 273 | ||
| 274 | // Initialise tree | |
| 275 | 253 | // |
| 276 | 228 | _init_node(&SP->root, NULL, SP->config->head, NULL); |
| 277 | 420 | SP->root.has_child_toggled_emitted = TRUE; |
| 278 | 253 | |
| 279 | 185 | // FIXME: is a hash table the best way to maintain an index of largely consecutive id's ? |
| 280 | // The pain with a GArray comes inserting and removing entries, but then again that shouldn't | |
| 281 | // happen so often, whereas lookups are pretty frequent ... what about a Btree? | |
| 282 | //SP->index=g_hash_table_new_full(g_int_hash, g_int_equal, g_free, (GDestroyNotify)gtk_tree_path_free); | |
| 283 | ||
| 284 | 253 | return object; |
| 285 | 185 | }; |
| 286 | ||
| 287 | static void music_source_view_init (MusicSourceView *self) { | |
| 288 | SP = G_TYPE_INSTANCE_GET_PRIVATE(self, MUSIC_SOURCE_VIEW_TYPE, MusicSourceViewPrivate); | |
| 289 | //SP->config_n_levels=0; | |
| 290 | 253 | //SP->rec_level=0; SP->rec_orphan_level=0; |
| 291 | 185 | //SP->index=NULL; |
| 292 | } | |
| 293 | ||
| 294 | static void finalize(GObject *object) { | |
| 295 | MusicSourceView *self=MUSIC_SOURCE_VIEW(object); | |
| 296 | 253 | |
| 297 | 453 | //printf ("Freeing msv: %x.\n", object); fflush (stdout); |
| 298 | 185 | //if (SP->index!=NULL) |
| 299 | // g_hash_table_destroy(SP->index); | |
| 300 | 253 | for (GSList *ref_node=SP->row_markers; ref_node; ref_node=ref_node->next) |
| 301 | 185 | g_slice_free (_MusicSourceViewRowMarker, ref_node->data); |
| 302 | 253 | g_slist_free (SP->row_markers); |
| 303 | ||
| 304 | 185 | _music_source_view_free_node(&self->priv->root); |
| 305 | 253 | |
| 306 | 216 | if (SP->config!=NULL) |
| 307 | view_config_free(SP->config); | |
| 308 | 185 | |
| 309 | g_object_unref(SP->source); | |
| 310 | G_OBJECT_CLASS(music_source_view_parent_class)->finalize(object); | |
| 311 | }; | |
| 312 | ||
| 313 | static void set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { | |
| 314 | MusicSourceView *self=MUSIC_SOURCE_VIEW(object); | |
| 315 | 253 | |
| 316 | switch(prop_id) { | |
| 317 | 216 | case PROP_SOURCE: SP->source = g_value_get_object(value); break; |
| 318 | case PROP_CONFIG: SP->config = g_value_get_pointer(value); break; | |
| 319 | 185 | default: |
| 320 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); | |
| 321 | } | |
| 322 | }; | |
| 323 | ||
| 324 | //////////////////////////////// | |
| 325 | // Internal GtkTreeModel utilities | |
| 326 | // | |
| 327 | ||
| 328 | // Get estimated position within its parent node of an iter. | |
| 329 | static gint iter_get_e(GtkTreeIter *iter) { | |
| 330 | ViewPage *page=iter->user_data2; | |
| 331 | if (page==NULL) | |
| 332 | return GPOINTER_TO_INT(iter->user_data3); | |
| 333 | return page->start_n!=-1? | |
| 334 | 253 | page->start_n + GPOINTER_TO_INT(iter->user_data3): |
| 335 | 185 | page->start_e + GPOINTER_TO_INT(iter->user_data3); |
| 336 | }; | |
| 337 | ||
| 338 | // Get the node this iter is pointing to. | |
| 339 | static ViewNode *iter_get_node(MusicSourceView *self, GtkTreeIter *iter) { | |
| 340 | if (iter==NULL) | |
| 341 | return &self->priv->root; | |
| 342 | else { | |
| 343 | 216 | // iter never points directly to leaf nodes so this should be unneccessary |
| 344 | 253 | //if (!gtk_tree_model_iter_has_child(GTK_TREE_MODEL(self), iter)) |
| 345 | 216 | // return NULL; |
| 346 | 253 | |
| 347 | 221 | ViewPage *page = iter->user_data2; |
| 348 | int n = GPOINTER_TO_INT(iter->user_data3); | |
| 349 | 185 | if (page==NULL) { |
| 350 | // FIXME: We will need to query the page, since this iter is in freefall. This | |
| 351 | // is possible, however. | |
| 352 | g_warning("iter_get_node: node %x is in freefall, pointing at row %i, so I " | |
| 353 | "should really have done some work here ..\n", (guint)iter, n); | |
| 354 | return NULL; | |
| 355 | }; | |
| 356 | 253 | |
| 357 | //printf ("iter_get_node: n %i, page->size %i ... n children %i.\n", n, page->size, | |
| 358 | 233 | // page->parent_node->e_children); fflush (stdout); |
| 359 | 185 | g_return_val_if_fail(n>=0 && n<page->size, NULL); |
| 360 | 253 | |
| 361 | 185 | // Take the iter out of freefall again. |
| 362 | // If we don't change the stamp (origin), it will go straight back into freefall on | |
| 363 | // iter_next .. | |
| 364 | 221 | iter->stamp = n; |
| 365 | iter->user_data2 = page; | |
| 366 | iter->user_data3 = GINT_TO_POINTER(n-page->start_e); | |
| 367 | 253 | |
| 368 | 235 | //printf (">>Returning child %i at depth %i.\n", n, page->parent_node->depth); fflush (stdout); |
| 369 | 185 | return g_array_index(page->nodes, ViewNode *, n); |
| 370 | }; | |
| 371 | return NULL; | |
| 372 | }; | |
| 373 | ||
| 374 | //////////////////////////////// | |
| 375 | // GtkTreeModel interface | |
| 376 | // | |
| 377 | ||
| 378 | 235 | /* FIXME: since it seems set that all descendents of musicsourceview will store internal data |
| 379 | * in the same way (and that seems fair, since any descendent will be doing the same job so | |
| 380 | * it seems unlikely it should need radically different internal storage - any better methods | |
| 381 | * should be implemented in this base class instead). Anyway since this is true we should maybe | |
| 382 | * rewrite these not in terms of gtktreemodel methods but directly acting on the internal | |
| 383 | * representation since that would be faster. Although make sure there are no radically better | |
| 384 | * internal representations you are thinking of implementing first. */ | |
| 385 | 185 | |
| 386 | static GtkTreeModelFlags get_flags(GtkTreeModel *tree_model) { | |
| 387 | return /*GTK_TREE_MODEL_ITERS_PERSIST*/ 0; // (I don't think they do) | |
| 388 | } | |
| 389 | ||
| 390 | static gint get_n_columns(GtkTreeModel *tree_model) { | |
| 391 | interface_track_printf("music_source_view_get_n_columns\n"); | |
| 392 | return MUSIC_SOURCE_VIEW_COLUMN_COUNT; | |
| 393 | } | |
| 394 | ||
| 395 | static GType get_column_type(GtkTreeModel *tree_model, gint index) { | |
| 396 | interface_track_printf("music_source_view_get_column_type [%i]\n", index); | |
| 397 | 253 | |
| 398 | if (index>=MUSIC_SOURCE_VIEW_COLUMN_COUNT) return G_TYPE_INVALID; | |
| 399 | 185 | return G_TYPE_STRING; |
| 400 | } | |
| 401 | ||
| 402 | // Return iter for a given path. | |
| 403 | static gboolean get_iter(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path) { | |
| 404 | 241 | MusicSourceView *self = MUSIC_SOURCE_VIEW(tree_model); |
| 405 | 259 | //interface_track_printf ("music_source_view_get_iter (path %X - %s) ..", path, |
| 406 | // gtk_tree_path_to_string(path)); | |
| 407 | 185 | |
| 408 | 241 | g_return_val_if_fail (SP->config!=NULL, FALSE); |
| 409 | 253 | |
| 410 | 185 | int depth = gtk_tree_path_get_depth(path); |
| 411 | 216 | if (depth<=0 || depth > SP->config->n_levels) { |
| 412 | 253 | interface_track_printf ("false - depth is %i, config_n_levels in %i\n", depth, |
| 413 | 216 | SP->config->n_levels); |
| 414 | 185 | return FALSE; |
| 415 | }; | |
| 416 | 253 | |
| 417 | 259 | int *indices = gtk_tree_path_get_indices(path); |
| 418 | 185 | GtkTreeIter parent; |
| 419 | if (!gtk_tree_model_iter_nth_child (tree_model, iter, NULL, indices[0])) { | |
| 420 | 259 | interface_track_printf ("false - no %ith root child\n", indices[0]); |
| 421 | 185 | return FALSE; |
| 422 | }; | |
| 423 | ||
| 424 | 259 | for (int i=1; i<depth; i++) { |
| 425 | parent = *iter; | |
| 426 | 185 | if (!gtk_tree_model_iter_nth_child (tree_model, iter, &parent, indices[i])) { |
| 427 | 259 | interface_track_printf ("false - no child at depth %i\n", i); |
| 428 | 185 | return FALSE; |
| 429 | }; | |
| 430 | } | |
| 431 | ||
| 432 | 259 | interface_track_printf ("got iter\n"); |
| 433 | 185 | return TRUE; |
| 434 | } | |
| 435 | ||
| 436 | // Return path for a given iter. | |
| 437 | // This may be an estimated path!!!!!!! | |
| 438 | static GtkTreePath *get_path(GtkTreeModel *tree_model, GtkTreeIter *iter) { | |
| 439 | interface_track_printf ("music_source_view_get_path\n"); | |
| 440 | if (iter==NULL) | |
| 441 | return gtk_tree_path_new_first(); | |
| 442 | ||
| 443 | //g_message("music_source_view_get_path: might be returning an estimated path!\n"); | |
| 444 | 253 | |
| 445 | 185 | int e = iter_get_e(iter); |
| 446 | //printf ("get_path: got e %i\n", e); fflush (stdout); | |
| 447 | 253 | GtkTreePath *path = gtk_tree_path_new_from_indices(e, -1); |
| 448 | 185 | GtkTreeIter new_iter = *iter, parent; |
| 449 | while (gtk_tree_model_iter_parent(tree_model, &parent, &new_iter)) { | |
| 450 | gtk_tree_path_prepend_index (path, iter_get_e(&parent)); | |
| 451 | new_iter = parent; | |
| 452 | 253 | }; |
| 453 | 185 | return path; |
| 454 | } | |
| 455 | ||
| 456 | 310 | /* get_column_value: returns TRUE if a valid value could be found, and sets p_value. */ |
| 457 | static gboolean get_column_value (MusicSourceView *self, GtkTreeIter *iter, gint column_index, | |
| 458 | void **p_value) { | |
| 459 | const EntryType type = APID_GET_TYPE(column[column_index].apid); | |
| 460 | const int property_id = APID_GET_PROPERTY_ID(column[column_index].apid); | |
| 461 | ||
| 462 | int id = _get_id_at_iter(self, type, iter, column[column_index].connection); | |
| 463 | if (id==0) | |
| 464 | return FALSE; | |
| 465 | ||
| 466 | Entry *entry = music_source_query_entry(SP->source, type, id, "music_source_view::get_value"); | |
| 467 | if (!entry) { | |
| 468 | g_message ("Unable to find %s %i\n", entry_type_name[type], id); | |
| 469 | return FALSE; | |
| 470 | }; | |
| 471 | ||
| 472 | *(p_value) = entry_get_property(entry, property_id); | |
| 473 | entry_unref (entry, "music_source_view::get_value"); | |
| 474 | return TRUE; | |
| 475 | }; | |
| 476 | ||
| 477 | 185 | // Fill value with specified column of given iter. |
| 478 | // FIXME: make this really efficient! It's called a *lot* it seems ! | |
| 479 | // (in fact, is it right that gtktreeview should be calling this on every mouse movement?) | |
| 480 | // | |
| 481 | 268 | static void get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, gint column_index, |
| 482 | 310 | GValue *g_value) { |
| 483 | 268 | MusicSourceView *self = MUSIC_SOURCE_VIEW(tree_model); |
| 484 | 185 | //printf("music_source_view_get_value .. "); dump_iter(iter); printf("\n"); fflush(stdout); |
| 485 | 268 | // FIXME: it seems wrong that we should return a blank string for columns we have no value for - |
| 486 | // surely the type should be unset? That breaks everything though. | |
| 487 | 310 | g_value_init (g_value, G_TYPE_STRING); |
| 488 | ||
| 489 | // Process smart columns | |
| 490 | // FIXME: implement these more automatically | |
| 491 | // FIXME: and more cleverly - instead of get_id_at_iter, we have a lookup table of what levels | |
| 492 | // have what entry types available and use that. It's important because this value gets called a | |
| 493 | // lot! | |
| 494 | if (column_index == COLUMN_ARTIST) { | |
| 495 | void *recording_artist_value; | |
| 496 | gboolean has_recording_artist = get_column_value(self, iter, COLUMN_RECORDING_ARTIST_NAME, | |
| 497 | &recording_artist_value); | |
| 498 | if (has_recording_artist && recording_artist_value!=NULL) { | |
| 499 | g_value_set_string (g_value, recording_artist_value); | |
| 500 | return; | |
| 501 | } | |
| 502 | ||
| 503 | void *album_artist_value; | |
| 504 | gboolean has_album_artist = get_column_value(self, iter, COLUMN_ALBUM_ARTIST_NAME, | |
| 505 | &album_artist_value); | |
| 506 | if (has_album_artist && album_artist_value!=NULL) { | |
| 507 | g_value_set_string (g_value, album_artist_value); | |
| 508 | return; | |
| 509 | } | |
| 510 | ||
| 511 | return; | |
| 512 | }; | |
| 513 | 268 | |
| 514 | if (column_index < COLUMN_COUNT) { | |
| 515 | 310 | void *value; |
| 516 | if (!get_column_value(self, iter, column_index, &value)) return; | |
| 517 | ||
| 518 | 309 | const EntryProperty *property = &APID_GET_PROPERTY(column[column_index].apid); |
| 519 | ||
| 520 | switch (property->type) { | |
| 521 | case PROPERTY_TYPE_INT: { | |
| 522 | // -1 means unknown for length. | |
| 523 | 310 | if (column[column_index].apid == _RECORDING_LENGTH && (int)value==-1) |
| 524 | 309 | break; |
| 525 | ||
| 526 | 350 | g_value_take_string (g_value, format_int64(property->unit, (guint)value)); |
| 527 | 310 | return; |
| 528 | 309 | }; |
| 529 | ||
| 530 | 310 | case PROPERTY_TYPE_STRING: case PROPERTY_TYPE_NAME: |
| 531 | g_value_set_string (g_value, value); | |
| 532 | return; | |
| 533 | ||
| 534 | 309 | default: |
| 535 | 310 | g_warning ("Invalid property for column %s\n", column[column_index].name); |
| 536 | case PROPERTY_TYPE_DATE: case PROPERTY_TYPE_DATE_TIME: | |
| 537 | g_value_take_string (g_value, entry_value_to_string (property->type, value)); | |
| 538 | return; | |
| 539 | 309 | }; |
| 540 | 185 | } |
| 541 | } | |
| 542 | ||
| 543 | static gboolean iter_children(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent) { | |
| 544 | //MusicSourceView *self=MUSIC_SOURCE_VIEW(tree_model); | |
| 545 | interface_track_printf("music_source_view_iter_children\n"); | |
| 546 | 253 | |
| 547 | 185 | if (gtk_tree_model_iter_nth_child(tree_model, iter, parent, 0)) { |
| 548 | return TRUE; | |
| 549 | }; | |
| 550 | return FALSE; | |
| 551 | } | |
| 552 | ||
| 553 | ||
| 554 | /////////////////////////////////////////////////////////////////////////// | |
| 555 | // These five methods are the basic treemodel interface. The rest of it | |
| 556 | // can be implemented in terms of these with pretty much no loss of | |
| 557 | // efficiency as far as i can see .. has_child could be iter_nth_child(0), but GtkTreeView | |
| 558 | 253 | // calls it so much that it needs to be really fast. |
| 559 | 185 | // |
| 560 | ||
| 561 | 221 | static gboolean iter_next (GtkTreeModel *tree_model, GtkTreeIter *iter) { |
| 562 | 185 | //interface_track_printf("musicsourceview::iter-next\n"); |
| 563 | 221 | g_return_val_if_fail (iter!=NULL, FALSE); |
| 564 | 253 | |
| 565 | 221 | const int n = GPOINTER_TO_INT(iter->user_data3), |
| 566 | origin = GPOINTER_TO_INT(iter->stamp); | |
| 567 | ViewNode *node = iter->user_data; | |
| 568 | ViewPage *page = iter->user_data2; | |
| 569 | g_return_val_if_fail (node!=NULL, FALSE); | |
| 570 | 253 | |
| 571 | 185 | if (page==NULL) { |
| 572 | // This iter is in freefall (gone through more than 1 page worth of rows, so we don't | |
| 573 | // want to do any more querying for it because it's probably going to iterate through | |
| 574 | // the rest of the tree model). | |
| 575 | 253 | //printf("Freefall: n %i/%i\n", n, node->e_children); |
| 576 | 221 | if (n >= node->e_children-1) |
| 577 | 185 | return FALSE; |
| 578 | 221 | iter->user_data3 = GINT_TO_POINTER(n+1); |
| 579 | 185 | return TRUE; |
| 580 | }; | |
| 581 | ||
| 582 | // Check if this iter needs to go into freefall. Or if this is the last page, don't bother. | |
| 583 | // The -2 is so we avoid changing pages at all in the most common case (starting from 0). | |
| 584 | 368 | // If origin==-1, we never go into freefall (for internal hacks :) |
| 585 | if (origin!=-1 && n+page->start_e >= origin+MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE | |
| 586 | && page->next!=NULL) { | |
| 587 | 185 | // n is now node-relative not page-relative because the page may not be in memory. |
| 588 | // Of course it also still needs incrementing! | |
| 589 | 221 | iter->user_data2 = NULL; |
| 590 | iter->user_data3 = GINT_TO_POINTER(n+page->start_e)+1; | |
| 591 | 185 | return TRUE; |
| 592 | }; | |
| 593 | 253 | |
| 594 | 185 | //printf("n+1 %i, page->size-1 %i\n", n+1, page->size-1);fflush(stdout); |
| 595 | 221 | if (n >= page->size-1) { |
| 596 | 185 | if (page->next==NULL) { |
| 597 | // This iter is at the bottom of the last page. We query the last page on | |
| 598 | // load, so there is no possibility of extra unqueried data. | |
| 599 | ||
| 600 | // (The hard way to find out the same thing) | |
| 601 | //printf("page start e %i, page size %i, c %i\n", page->start_e, page->size, page->parent_node->e_children); fflush(stdout); | |
| 602 | 221 | g_warn_if_fail (page->start_e+page->size==page->parent_node->e_children); |
| 603 | 185 | return FALSE; |
| 604 | }; | |
| 605 | 253 | |
| 606 | 185 | if (!page->chain_next) |
| 607 | // The next page is probably unqueried. | |
| 608 | 221 | _music_source_view_read_next_page (MUSIC_SOURCE_VIEW(tree_model), page); |
| 609 | 253 | |
| 610 | 185 | // We should definitely have the next page now. |
| 611 | 221 | g_warn_if_fail (page->chain_next); |
| 612 | 253 | |
| 613 | 221 | iter->user_data2 = page->next; |
| 614 | iter->user_data3 = GINT_TO_POINTER(0); | |
| 615 | 185 | return TRUE; |
| 616 | } | |
| 617 | 253 | |
| 618 | 221 | //printf ("setting n: %i.\n", GINT_TO_POINTER(n+1)); fflush (stdout); |
| 619 | iter->user_data3 = GINT_TO_POINTER(n+1); | |
| 620 | 185 | return TRUE; |
| 621 | }; | |
| 622 | ||
| 623 | 253 | /* iter_has_child needs to be able to execute without taking the iter out of freefall. If the iter |
| 624 | 221 | * is in freefall (ie. it points to an as yet unqueried region of the node) has_child has to guess, |
| 625 | * which it does by returning TRUE unless depth==n_levels. For this reason, when reading bits of | |
| 626 | * partially queried pages, leaf nodes of depth < n_levels need to have has_child_toggled emitted so | |
| 627 | * they display correctly. | |
| 628 | */ | |
| 629 | 216 | static gboolean iter_has_child (GtkTreeModel *tree_model, GtkTreeIter *iter) { |
| 630 | MusicSourceView *self = MUSIC_SOURCE_VIEW(tree_model); | |
| 631 | interface_track_printf ("musicsourceview::iter-has-child\n"); | |
| 632 | g_warn_if_fail (iter!=NULL); | |
| 633 | 253 | |
| 634 | 221 | ViewNode *node; |
| 635 | if (iter==NULL) | |
| 636 | node = &self->priv->root; | |
| 637 | else { | |
| 638 | ViewPage *page = iter->user_data2; | |
| 639 | int n = GPOINTER_TO_INT(iter->user_data3); | |
| 640 | if (page==NULL) { | |
| 641 | /* Iter is in freefall - guess. */ | |
| 642 | ViewNode *parent_node = iter->user_data; | |
| 643 | 441 | g_return_val_if_fail (parent_node != NULL, FALSE); |
| 644 | ||
| 645 | 221 | if (parent_node->depth+1 == SP->config->n_levels) |
| 646 | return FALSE; | |
| 647 | return TRUE; | |
| 648 | }; | |
| 649 | 253 | |
| 650 | 221 | g_return_val_if_fail(n>=0 && n<page->size, FALSE); |
| 651 | node = g_array_index(page->nodes, ViewNode *, n); | |
| 652 | }; | |
| 653 | 253 | |
| 654 | 216 | /* Leaf nodes can't have any children. */ |
| 655 | if (node->head==NULL) | |
| 656 | 185 | return FALSE; |
| 657 | return TRUE; | |
| 658 | }; | |
| 659 | ||
| 660 | // FIXME: this is iter_e_children | |
| 661 | 216 | static int iter_n_children (GtkTreeModel *tree_model, GtkTreeIter *iter) { |
| 662 | MusicSourceView *self = MUSIC_SOURCE_VIEW(tree_model); | |
| 663 | interface_track_printf ("musicsourceview::iter-n-children\n"); | |
| 664 | 253 | |
| 665 | ViewNode *node = iter_get_node(self, iter); | |
| 666 | 235 | /*ViewNode *node; |
| 667 | 253 | if (iter==NULL) |
| 668 | 235 | node = &SP->root; |
| 669 | else node = iter->user_data;*/ | |
| 670 | ||
| 671 | 237 | //printf ("Got node: depth %i, kids %i. Head %x - parent tail %s\n", node->depth, node->e_children, |
| 672 | // node->head, CONFIG_TYPE_NAME(node->parent_tail)); fflush (stdout); | |
| 673 | 253 | if (node==NULL) |
| 674 | 185 | return 0; |
| 675 | 216 | if (node->head==NULL) // Is a leaf node. |
| 676 | return 0; | |
| 677 | 185 | |
| 678 | 235 | if (node->e_children==-1) |
| 679 | 185 | _music_source_view_initial_read(self, node); |
| 680 | 235 | |
| 681 | 218 | //_music_source_view_dump_node (self, node); fflush (stdout); |
| 682 | 185 | return node->e_children; |
| 683 | }; | |
| 684 | ||
| 685 | // FIXME: this is iter_eth_child | |
| 686 | static gboolean iter_nth_child(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, int n) { | |
| 687 | MusicSourceView *self=MUSIC_SOURCE_VIEW(tree_model); | |
| 688 | interface_track_printf("musicsourceview::iter-nth-child (%i)\n", n); | |
| 689 | g_return_val_if_fail(iter!=NULL, FALSE); | |
| 690 | 253 | |
| 691 | 185 | ViewNode *node=iter_get_node(self, parent); |
| 692 | //printf("\tgot node %x\n", node);fflush(stdout); | |
| 693 | if (node==NULL) return 0; | |
| 694 | ||
| 695 | if (node->e_children<0) { | |
| 696 | // Start off the query for this node. | |
| 697 | _music_source_view_initial_read(self, node); | |
| 698 | 253 | |
| 699 | 185 | // FIXME: Maybe the source knows something we don't? I'm not sure how to handle this |
| 700 | // situation .. so at the moment we warn and return. And leak some memory. | |
| 701 | if (n>=node->e_children) { | |
| 702 | if (node->n_children<0) { | |
| 703 | char *path_string="<root>"; | |
| 704 | if (parent!=NULL) { | |
| 705 | GtkTreePath *path=gtk_tree_model_get_path(tree_model, parent); | |
| 706 | path_string=gtk_tree_path_to_string(path); | |
| 707 | gtk_tree_path_free(path); | |
| 708 | } | |
| 709 | //printf(str); fflush(stdout); | |
| 710 | g_warning("iter_nth_child called at %s %i - but this node was only estimated " | |
| 711 | "to have %i children :(\n", path_string, n, node->e_children); | |
| 712 | if (parent!=NULL) g_free(path_string); | |
| 713 | }; | |
| 714 | return FALSE; | |
| 715 | }; | |
| 716 | }; | |
| 717 | //printf("\tafter read: %i , n%i kids\n", node->e_children, node->n_children);fflush(stdout); | |
| 718 | 253 | |
| 719 | 185 | if (node->e_children==0 || n>=node->e_children) |
| 720 | return FALSE; | |
| 721 | ||
| 722 | //printf("iter-nth-child: %i/%i[%i]\n", n, node->e_children, node->n_children); | |
| 723 | 253 | |
| 724 | 185 | ViewPage *page=node->children; |
| 725 | g_return_val_if_fail(page!=NULL, FALSE); | |
| 726 | 253 | |
| 727 | 185 | return _find_iter_for_row(self, node, n, iter); |
| 728 | }; | |
| 729 | ||
| 730 | 218 | static gboolean iter_parent (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child) { |
| 731 | 185 | //MusicSourceView *self=MUSIC_SOURCE_VIEW(tree_model); |
| 732 | 218 | interface_track_printf ("musicsourceview::iter-parent\n"); |
| 733 | g_return_val_if_fail (child!=NULL, FALSE); | |
| 734 | 253 | |
| 735 | 218 | ViewNode *child_node = child->user_data; |
| 736 | ViewPage *page = child->user_data2; | |
| 737 | 253 | g_return_val_if_fail (child_node!=NULL, FALSE); |
| 738 | ||
| 739 | if (child_node->depth==0) | |
| 740 | 185 | return FALSE; |
| 741 | 253 | |
| 742 | 185 | if (iter!=NULL) { |
| 743 | 218 | iter->stamp = page->start_e + GPOINTER_TO_INT(child->user_data3); |
| 744 | iter->user_data = child_node->parent_page->parent_node; | |
| 745 | iter->user_data2 = child_node->parent_page; | |
| 746 | iter->user_data3 = GINT_TO_POINTER(child_node->n); | |
| 747 | 185 | }; |
| 748 | 218 | g_return_val_if_fail (iter->user_data!=NULL, FALSE); |
| 749 | 185 | return TRUE; |
| 750 | }; | |
| 751 | ||
| 752 | ||
| 753 | ///////////////////////////////////////////// | |
| 754 | // Our own methods - virtual | |
| 755 | // | |
| 756 | ||
| 757 | GtkTreePath *music_source_view_get_path_for_id(MusicSourceView *self, EntryType type, int id) { | |
| 758 | return MUSIC_SOURCE_VIEW_GET_CLASS(self)->get_path_for_id (self, type, id); | |
| 759 | }; | |
| 760 | ||
| 761 | 253 | MusicSourceViewSearch *music_source_view_get_path_for_id_begin (MusicSourceView *self, |
| 762 | 185 | EntryType type, int id) { |
| 763 | 253 | return MUSIC_SOURCE_VIEW_GET_CLASS(self)->get_path_for_id_begin (self, type, id); |
| 764 | 185 | }; |
| 765 | ||
| 766 | 253 | gboolean music_source_view_get_path_for_id_step (MusicSourceView *self, |
| 767 | 185 | MusicSourceViewSearch *state) { |
| 768 | return MUSIC_SOURCE_VIEW_GET_CLASS(self)->get_path_for_id_step (self, state); | |
| 769 | }; | |
| 770 | 253 | |
| 771 | GtkTreePath *music_source_view_get_path_for_id_end (MusicSourceView *self, | |
| 772 | 185 | MusicSourceViewSearch *state) { |
| 773 | return MUSIC_SOURCE_VIEW_GET_CLASS(self)->get_path_for_id_end (self, state); | |
| 774 | }; | |
| 775 | ||
| 776 | ||
| 777 | /////////////////////////// | |
| 778 | // Own methods - non virtual | |
| 779 | // | |
| 780 | ||
| 781 | MusicSource *music_source_view_get_source(MusicSourceView *self) { | |
| 782 | return SP->source; | |
| 783 | }; | |
| 784 | ||
| 785 | 241 | const ViewConfig *music_source_view_get_config(MusicSourceView *self) { |
| 786 | 185 | //printf ("self -> priv: %s\n", self->priv); fflush (stdout); |
| 787 | 241 | return SP->config; |
| 788 | 185 | }; |
| 789 | ||
| 790 | int music_source_view_get_n_levels(MusicSourceView *self) { | |
| 791 | 216 | if (SP->config==NULL) |
| 792 | 185 | return 0; |
| 793 | 216 | return SP->config->n_levels; |
| 794 | 185 | }; |
| 795 | ||
| 796 | // At the moment this function has a shortcoming of assuming that there are no empty nodes, so | |
| 797 | // if there is one, it will be very confused .. eventually, there should not be any empty | |
| 798 | // join nodes in the model, so there is no point adding lots of code to deal with this problem. | |
| 799 | // Nobody wants to see artists listed who they don't have any songs by etc. | |
| 800 | GtkTreePath *music_source_view_get_first (MusicSourceView *self, EntryType type) { | |
| 801 | ViewNode *node = &SP->root; | |
| 802 | 216 | GList *clnode = SP->config->head; |
| 803 | 185 | GtkTreePath *path = gtk_tree_path_new(); |
| 804 | 216 | for (int i=0; i<SP->config->n_levels; i++) { |
| 805 | 185 | if (node->e_children==-1) |
| 806 | 253 | _music_source_view_initial_read (self, node); |
| 807 | if (node->e_children==0) | |
| 808 | 185 | break; |
| 809 | ||
| 810 | // FIXME: is append or prepend faster? Our index will always be 0 here so I guess it | |
| 811 | // doesn't matter which we use .. | |
| 812 | gtk_tree_path_append_index (path, 0); | |
| 813 | ||
| 814 | ViewPage *page = node->children; | |
| 815 | g_return_val_if_fail (page!=NULL, NULL); | |
| 816 | g_return_val_if_fail (page->size>0, NULL); | |
| 817 | 253 | |
| 818 | 185 | node = g_array_index (page->nodes, ViewNode *, 0); |
| 819 | g_return_val_if_fail (node!=NULL, NULL); | |
| 820 | ||
| 821 | // Now, see if 'type' is on this level. | |
| 822 | while (CONFIG_NODE(clnode)->flat) { | |
| 823 | if (CONFIG_NODE(clnode)->type==type) | |
| 824 | return path; | |
| 825 | clnode = clnode->next; | |
| 826 | g_return_val_if_fail (clnode!=NULL, NULL); | |
| 827 | }; | |
| 828 | 253 | |
| 829 | 185 | // If we're at the orphanage and there are only orphans, return here. |
| 830 | 218 | // FIXME: reimplement this, unit test for it too since it's clearly not tested. |
| 831 | 253 | /*if (SP->config->orphaning_join_level>0 |
| 832 | 216 | && node->tree_depth==SP->config->orphanage_level && node->orphan_break==0) |
| 833 | 218 | return path;*/ |
| 834 | 185 | }; |
| 835 | 253 | |
| 836 | 185 | gtk_tree_path_free (path); |
| 837 | return NULL; | |
| 838 | }; | |
| 839 | ||
| 840 | ||
| 841 | 261 | |
| 842 | // FIXME: optimise this bad boy! get_value depends on it at the moment and that | |
| 843 | // is called all the time! | |
| 844 | // | |
| 845 | // FIXME: is it possible to go from node->id and node->intermediate_ids to just node->ids? | |
| 846 | // It would be easier for this func, but what about everyone else !! | |
| 847 | // This is when it's a good time to write your "what bits of code does changing this bit affect" | |
| 848 | // tool. | |
| 849 | // | |
| 850 | // FIXME: how about, rewriting in terms of _walk_config .. | |
| 851 | 310 | // FIXME: could we speed this up with some sort of lookup table?? |
| 852 | 268 | /* entry_map: Used to find which entry types can be connected to other types. There is a global |
| 853 | * 'entry_map' that's normally used, but when finding a column value we want to use | |
| 854 | * a more limited map - for example, the 'Album Artist' column's map doesn't connect | |
| 855 | * recording or composition to artist, whereas the normal entry map does - | |
| 856 | * otherwise, the album artist column might return the recording artist or something | |
| 857 | * instead. | |
| 858 | */ | |
| 859 | static int _get_id_at_iter (MusicSourceView *self, EntryType type, GtkTreeIter *iter_param, | |
| 860 | int entry_map[ENTRY_TYPE_COUNT]) { | |
| 861 | 261 | if (iter_param->user_data2==NULL) |
| 862 | if (!defloat(self, iter_param)) { | |
| 863 | g_warning ("Defloat of iter failed.\n"); return 0; | |
| 864 | }; | |
| 865 | //printf ("\n\nget_id_at_iter: %s\n", entry_type_name[type]); fflush (stdout); | |
| 866 | ||
| 867 | 268 | /*printf ("Map: (to %s)\n", entry_type_name[type]); |
| 868 | for (int i=0; i<ENTRY_TYPE_COUNT; i++) { | |
| 869 | if (entry_map[i] < 0) printf ("\t<%s>\n", entry_type_name[i]); | |
| 870 | else printf ("\t->%s: %s.\n", entry_type_name[i], schema[i][entry_map[i]].name); | |
| 871 | }; fflush (stdout);*/ | |
| 872 | ||
| 873 | 261 | GtkTreeIter iter = *iter_param; |
| 874 | ||
| 875 | ViewPage *page = iter.user_data2; | |
| 876 | int n = GPOINTER_TO_INT(iter.user_data3); | |
| 877 | g_return_val_if_fail (page!=NULL, 0); | |
| 878 | g_return_val_if_fail (n < page->size, 0); | |
| 879 | ||
| 880 | // First, see if the tail join at each level is what we want, because that's really easy | |
| 881 | // to find. | |
| 882 | 268 | ViewNode *node = g_array_index(page->nodes, ViewNode *, n); |
| 883 | 261 | do { |
| 884 | 410 | //printf ("Tail type: %s. prev %x\n", CONFIG_TYPE_NAME(node->parent_tail), node->parent_tail->prev); fflush (stdout); |
| 885 | 261 | //printf ("--head type: %s. next %x\n", entry_type_name[CONFIG_NODE(parent_node->head)->type], parent_node->head->next); fflush (stdout); |
| 886 | 268 | if (CONFIG_NODE(node->parent_tail)->type == type) { |
| 887 | //printf ("Got type: %s. id %i. At depth %i\n", entry_type_name[type], node->id, | |
| 888 | // node->depth); fflush (stdout); | |
| 889 | 261 | return node->id; |
| 890 | }; | |
| 891 | ||
| 892 | 268 | g_return_val_if_fail (node->parent_page!=NULL, 0); |
| 893 | node = node->parent_page->parent_node; | |
| 894 | g_return_val_if_fail (node!=NULL, 0); | |
| 895 | 410 | } while (node->depth > 0); |
| 896 | 261 | |
| 897 | // Loop through the levels, going up a level each time we don't find what we want, and | |
| 898 | // looking at each entry type that we can reach along the way. | |
| 899 | gboolean tree_result = TRUE; | |
| 900 | while (tree_result!=0) { | |
| 901 | 411 | ViewNode *parent_node = iter.user_data; g_return_val_if_fail (parent_node != NULL, 0); |
| 902 | ViewPage *page = iter.user_data2; g_return_val_if_fail (page != NULL, 0); | |
| 903 | 261 | int n = GPOINTER_TO_INT(iter.user_data3); |
| 904 | 411 | |
| 905 | 261 | ViewNode *node = g_array_index(page->nodes, ViewNode *, n); |
| 906 | g_return_val_if_fail (node!=NULL, 0); | |
| 907 | ||
| 908 | if (parent_node->head!=NULL /* has_child */ && parent_node->e_children==-1) { | |
| 909 | 411 | // node should be read if we have an iter pointing to it, so this code is an error if |
| 910 | // called. | |
| 911 | 261 | g_warn_if_reached(); |
| 912 | _music_source_view_initial_read (self, parent_node); | |
| 913 | }; | |
| 914 | ||
| 915 | // Loop through the joins on this level. We don't skip the tail join node, because we didn't | |
| 916 | // look at the type map for it before. | |
| 917 | GList *clnode = node->parent_tail; | |
| 918 | 385 | g_return_val_if_fail (node->parent_tail!=NULL, 0); |
| 919 | 261 | |
| 920 | /* The *tail* level_break_count takes into account any orphan branching along the | |
| 921 | * way that would have reduced the number of intermediate ids. */ | |
| 922 | int join_n = CONFIG_NODE(parent_node->head)->level_break_count - 1, | |
| 923 | intermediate_n = CONFIG_NODE(parent_node->tail)->level_break_count - 1; | |
| 924 | ||
| 925 | while(1) { | |
| 926 | ViewConfigNode *cnode = CONFIG_NODE(clnode); | |
| 927 | EntryType current_type = cnode->type; | |
| 928 | int current_id = 0; | |
| 929 | if (node->intermediate_ids==NULL || node->intermediate_ids[intermediate_n]==0) | |
| 930 | current_id = node->id; | |
| 931 | else current_id = node->intermediate_ids[intermediate_n]; | |
| 932 | ||
| 933 | /*printf ("get_id_at_iter[%s]: Got %s %i - depth %i join n %i/%i intermediate_n %i/%i " | |
| 934 | "%x - map %i\n", entry_type_name[type], entry_type_name[current_type], | |
| 935 | current_id, node->depth, join_n, | |
| 936 | CONFIG_NODE(parent_node->head)->level_break_count, intermediate_n, | |
| 937 | CONFIG_NODE(parent_node->tail)->level_break_count, | |
| 938 | 268 | (guint)node->intermediate_ids, entry_map[current_type]); fflush(stdout);*/ |
| 939 | 261 | |
| 940 | // Got the right entry straight away - return | |
| 941 | if (type==current_type) return current_id; | |
| 942 | ||
| 943 | 268 | if (entry_map[current_type]!=-1) { |
| 944 | 261 | // FIXME: I'm not sure if the entry map is the right/only way to do things. |
| 945 | // Why can't we use the view config to work out what properties link what? | |
| 946 | ||
| 947 | // Let's try to make our way to the desired entry type. | |
| 948 | Entry *base_entry = music_source_query_entry(SP->source, current_type, | |
| 949 | current_id, "msv::get-id-at-path"), | |
| 950 | *entry = base_entry; | |
| 951 | ||
| 952 | // When this can happen. | |
| 953 | if (base_entry==NULL) { | |
| 954 | GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL(self), iter_param); | |
| 955 | g_warning ("get_id_at_iter: %s %i was NULL, at path %s :(", | |
| 956 | entry_type_name[current_type], current_id, | |
| 957 | gtk_tree_path_to_string(path)); | |
| 958 | break; | |
| 959 | }; | |
| 960 | ||
| 961 | while (current_type!=type) { | |
| 962 | // FIXME: here recording.default-file works as a connection, even though that | |
| 963 | // defeats the point of info listing all the files if it doesn't have a | |
| 964 | // specific one .. not a big issue now since only one file per rec anyway. But | |
| 965 | // my tentative solution is to flag default rels as a default-only somehow, so | |
| 966 | // they can be ignored here. | |
| 967 | 268 | int conn_id = entry_map[current_type]; |
| 968 | 261 | if (conn_id==-1) { |
| 969 | current_id = 0; break; | |
| 970 | }; | |
| 971 | ||
| 972 | entry = entry_get_property(entry, conn_id); | |
| 973 | if (entry==NULL) | |
| 974 | break; | |
| 975 | current_id = entry->id; current_type = entry->type; | |
| 976 | }; | |
| 977 | entry_unref (base_entry, "msv::get-id-at-path"); | |
| 978 | ||
| 979 | //if (current_id!=0) { | |
| 980 | // printf("Connected to %s %i\n\n", entry_type_name[current_type], current_id); | |
| 981 | //} else printf("No connection found\n\n"); | |
| 982 | fflush (stdout); | |
| 983 | ||
| 984 | if (current_id!=0) { | |
| 985 | //interface_track_printf("\tgot id %s %i\n", entry_type_name[type], current_id); | |
| 986 | return current_id; | |
| 987 | } | |
| 988 | }; | |
| 989 | ||
| 990 | 411 | // Walk backwards to next break - branch or many:1 join. |
| 991 | 261 | // |
| 992 | do { | |
| 993 | if (cnode->flags & VIEW_CONFIG_NODE_MANY_TO_ONE_JOIN) { | |
| 994 | 411 | clnode = clnode->prev; g_return_val_if_fail (clnode!=NULL, 0); |
| 995 | cnode = clnode->data; g_return_val_if_fail (cnode->flat, 0); | |
| 996 | 261 | break; |
| 997 | }; | |
| 998 | 411 | |
| 999 | clnode = clnode->prev; if (clnode==NULL) break; | |
| 1000 | cnode = clnode->data; | |
| 1001 | ||
| 1002 | if (!cnode->flat) break; | |
| 1003 | 261 | } while (clnode); |
| 1004 | ||
| 1005 | 411 | // At end of config |
| 1006 | if (clnode==NULL) break; | |
| 1007 | ||
| 1008 | // At end of level | |
| 1009 | 261 | if (join_n==0 || intermediate_n==0) { |
| 1010 | 411 | g_warn_if_fail (cnode->flat==0); |
| 1011 | 261 | break; |
| 1012 | } | |
| 1013 | ||
| 1014 | 411 | join_n--; intermediate_n--; |
| 1015 | ||
| 1016 | 261 | g_warn_if_fail (clnode->next != NULL); |
| 1017 | g_warn_if_fail (CONFIG_NODE(clnode->next)->flags && VIEW_CONFIG_NODE_MANY_TO_ONE_JOIN); | |
| 1018 | }; | |
| 1019 | ||
| 1020 | // Try again at the level above, if possible | |
| 1021 | //next_level:; | |
| 1022 | GtkTreeIter old = iter; | |
| 1023 | tree_result = gtk_tree_model_iter_parent(GTK_TREE_MODEL(self), &iter, &old); | |
| 1024 | ||
| 1025 | if (!tree_result) | |
| 1026 | 269 | // Make sure we worked all the way through the config, and didn't give up early. |
| 1027 | 261 | g_warn_if_fail (clnode==NULL); |
| 1028 | else | |
| 1029 | g_warn_if_fail (clnode!=NULL); | |
| 1030 | }; | |
| 1031 | //printf ("\tgot no id for %s\n", entry_type_name[type]); | |
| 1032 | return 0; | |
| 1033 | }; | |
| 1034 | ||
| 1035 | 268 | |
| 1036 | int music_source_view_get_id_at_iter (MusicSourceView *self, EntryType type, | |
| 1037 | GtkTreeIter *iter_param) { | |
| 1038 | 304 | return _get_id_at_iter(self, type, iter_param, entry_type_info[type].map); |
| 1039 | 268 | }; |
| 1040 | ||
| 1041 | 185 | // Finds entry id at a given entry, or 0 if uncertain. |
| 1042 | // | |
| 1043 | 261 | // FIXME: make sure this is used only when necessary, and we aren't converting iters to paths and |
| 1044 | // back to iters . | |
| 1045 | 185 | int music_source_view_get_id_at_path(MusicSourceView *self, EntryType type, const GtkTreePath *path) { |
| 1046 | ///*interface_track_*/printf("music-source-view::get-id-at-path %s at %s\n", entry_type_name[type], gtk_tree_path_to_string((GtkTreePath *)path)); | |
| 1047 | 253 | |
| 1048 | // The method here is to try to map the entry type we have to the join node of this | |
| 1049 | 185 | // level .. if that isn't possible, move up a level and try again. |
| 1050 | // | |
| 1051 | GtkTreeIter iter; | |
| 1052 | if (gtk_tree_model_get_iter(GTK_TREE_MODEL(self), &iter, (GtkTreePath *)path)) | |
| 1053 | 261 | return music_source_view_get_id_at_iter(self, type, &iter); |
| 1054 | 185 | return 0; |
| 1055 | }; | |
| 1056 | ||
| 1057 | 261 | |
| 1058 | 185 | // These functions are a bit slow, given how often _locate_entry and _locate_entry_range |
| 1059 | // call them, but much faster than the alternative of connecting to the signals! | |
| 1060 | // | |
| 1061 | // 'maximise' specifies what to do if the exact row pointed to by 'row' moves - if 'maximise' | |
| 1062 | 253 | // is false, 'row' points to before any added rows, and if 'maximise' is true 'row' points |
| 1063 | 185 | // to after the new rows. When 'row' is removed, a minimised reference moves to the row |
| 1064 | // before while a maximised reference jumps to the row after. | |
| 1065 | // | |
| 1066 | 253 | void music_source_view_add_row_marker (MusicSourceView *self, int *row, ViewNode *parent_node, |
| 1067 | 185 | gboolean maximise) { |
| 1068 | 237 | // Allowed to be 1 node off the end, to allow exclusive row ranges like in locate_entry_range. |
| 1069 | g_return_if_fail (*row >=0 || *row <= parent_node->e_children); | |
| 1070 | ||
| 1071 | 185 | _MusicSourceViewRowMarker *ref = g_slice_new(_MusicSourceViewRowMarker); |
| 1072 | ref->parent_node = parent_node; ref->row = row; ref->maximise = maximise; | |
| 1073 | SP->row_markers = g_slist_prepend(SP->row_markers, ref); | |
| 1074 | }; | |
| 1075 | ||
| 1076 | 253 | void music_source_view_remove_row_marker (MusicSourceView *self, int *row) { |
| 1077 | 185 | for (GSList *ref_node=SP->row_markers; ref_node; ref_node=ref_node->next) { |
| 1078 | _MusicSourceViewRowMarker *ref = ref_node->data; | |
| 1079 | if (ref->row==row) { | |
| 1080 | g_slice_free (_MusicSourceViewRowMarker, ref); | |
| 1081 | SP->row_markers = g_slist_remove_link(SP->row_markers, ref_node); | |
| 1082 | return; | |
| 1083 | }; | |
| 1084 | }; | |
| 1085 | 253 | |
| 1086 | 185 | g_warning ("music_source_view_remove_marker: Unable to find given marker!!\n"); |
| 1087 | }; | |
| 1088 | ||
| 1089 | 366 | |
| 1090 | 185 | //////////////////////////////////////// |
| 1091 | // Internal non-virtual methods | |
| 1092 | // | |
| 1093 | ||
| 1094 | 228 | static void _init_node(ViewNode *node, GList *parent_tail, GList *head, GList *tail) { |
| 1095 | 253 | g_warn_if_fail (parent_tail!=NULL || head!=NULL); |
| 1096 | ||
| 1097 | 228 | if (head==NULL && parent_tail!=NULL) |
| 1098 | head = parent_tail->next; | |
| 1099 | 218 | if (tail==NULL && head!=NULL) { |
| 1100 | tail = head; | |
| 1101 | 253 | for (; CONFIG_NODE(tail)->flat; tail=tail->next) |
| 1102 | 218 | g_warn_if_fail (tail!=NULL); |
| 1103 | }; | |
| 1104 | 253 | |
| 1105 | 235 | //printf ("New node - head %s, tail %s.\n", head==NULL?"<none>":CONFIG_TYPE_NAME(head), |
| 1106 | // tail==NULL?"<none>":CONFIG_TYPE_NAME(tail)); fflush (stdout); | |
| 1107 | ||
| 1108 | 253 | node->parent_page = NULL; |
| 1109 | ||
| 1110 | 215 | node->depth = 0; |
| 1111 | 253 | node->head = head; |
| 1112 | 218 | node->tail = tail; |
| 1113 | 228 | node->parent_tail = parent_tail; |
| 1114 | 253 | |
| 1115 | 259 | if (head==NULL && tail==NULL) |
| 1116 | // Leaf node. | |
| 1117 | node->e_children = node->n_children = 0; | |
| 1118 | else node->e_children = node->n_children = -1; | |
| 1119 | 261 | |
| 1120 | 215 | node->children = NULL; |
| 1121 | node->intermediate_ids = NULL; | |
| 1122 | ||
| 1123 | /*node->orphan_break = 0; | |
| 1124 | 253 | |
| 1125 | 215 | node->root_orphan_break = -1;*/ |
| 1126 | 185 | node->root_to_size_ratio=1.0; |
| 1127 | 420 | |
| 1128 | node->has_child_toggled_emitted = FALSE; | |
| 1129 | 185 | }; |
| 1130 | ||
| 1131 | 228 | ViewNode *_music_source_view_new_node (int depth, GList *parent_tail, GList *head, GList *tail) { |
| 1132 | 185 | ViewNode *node = g_slice_new(ViewNode); |
| 1133 | 228 | _init_node (node, parent_tail, head, tail); |
| 1134 | 216 | node->depth = depth; |
| 1135 | 420 | |
| 1136 | 185 | return node; |
| 1137 | }; | |
| 1138 | ||
| 1139 | ViewPage *_music_source_view_new_page (ViewNode *node, ViewPage *prev, ViewPage *next) { | |
| 1140 | ViewPage *page = g_slice_new0(ViewPage); | |
| 1141 | if (prev!=NULL) { | |
| 1142 | g_warn_if_fail (prev->next==next); | |
| 1143 | prev->next = page; page->prev = prev; | |
| 1144 | }; | |
| 1145 | if (next!=NULL) { | |
| 1146 | g_warn_if_fail (next->prev==prev); | |
| 1147 | page->next = next; next->prev = page; | |
| 1148 | }; | |
| 1149 | 253 | |
| 1150 | 185 | page->parent_node = node; |
| 1151 | ||
| 1152 | page->start_e = -1; page->start_n = -1; | |
| 1153 | page->size = 0; | |
| 1154 | page->root_overhang = 0; | |
| 1155 | ||
| 1156 | page->nodes = g_array_new(FALSE, FALSE, sizeof(ViewNode *)); | |
| 1157 | 253 | |
| 1158 | 185 | page->chain_next = FALSE; |
| 1159 | return page; | |
| 1160 | }; | |
| 1161 | ||
| 1162 | // FIXME: this should really be virtual, because of the user_data in | |
| 1163 | // Library ... | |
| 1164 | // FIXME: yes this is really bad! The user_data is leaked !!!! and that's lots of memory !!!! | |
| 1165 | 253 | // This doesn't free the actual node structure, which needs freeing with g_free - |
| 1166 | 185 | // this is because the root is statically allocated. |
| 1167 | void _music_source_view_free_node (ViewNode *node) { | |
| 1168 | ViewPage *page = node->children; | |
| 1169 | while (page!=NULL) { | |
| 1170 | for (int i=0; i<page->size; i++) { | |
| 1171 | 253 | if (page->nodes!=NULL) { |
| 1172 | 185 | ViewNode *child = g_array_index(page->nodes, ViewNode *, i); |
| 1173 | 253 | _music_source_view_free_node (child); |
| 1174 | 185 | g_slice_free (ViewNode, child); |
| 1175 | }; | |
| 1176 | 253 | } |
| 1177 | ||
| 1178 | 185 | if (page->nodes!=NULL) |
| 1179 | g_array_free (page->nodes, TRUE); | |
| 1180 | ||
| 1181 | if (node->intermediate_ids!=NULL) | |
| 1182 | g_free (node->intermediate_ids); | |
| 1183 | ||
| 1184 | ViewPage *old_page = page; | |
| 1185 | page = page->next; | |
| 1186 | g_slice_free (ViewPage, old_page); | |
| 1187 | 253 | }; |
| 1188 | 185 | }; |
| 1189 | ||
| 1190 | 366 | /* _music_source_view_match_node: Used by the views in places. */ |
| 1191 | gboolean _music_source_view_match_node (ViewNode *node, int entry_id, int *entry_intermediate_ids) { | |
| 1192 | if (node->id != entry_id) | |
| 1193 | return FALSE; | |
| 1194 | ||
| 1195 | // We could still be different rows - check each intermediate id! The int. id arrays are all | |
| 1196 | // 0-terminated for safety reasons. | |
| 1197 | // | |
| 1198 | int *i = entry_intermediate_ids, | |
| 1199 | *j = node->intermediate_ids, | |
| 1200 | count = 0; | |
| 1201 | if (i != 0) { | |
| 1202 | g_return_val_if_fail (j!=0, TRUE); | |
| 1203 | while (*i!=0 || *j!=0) { | |
| 1204 | // ID arrays hould be the same length, because they represent the same level. | |
| 1205 | g_return_val_if_fail (*i!=0 && *j!=0, FALSE); | |
| 1206 | ||
| 1207 | if (*i != *j) | |
| 1208 | return FALSE; | |
| 1209 | i++; j++; | |
| 1210 | count++; | |
| 1211 | }; | |
| 1212 | } | |
| 1213 | ||
| 1214 | return TRUE; | |
| 1215 | }; | |
| 1216 | ||
| 1217 | 185 | // Alters n based on row-deleted and row-inserted signals. |
| 1218 | ViewPage *_music_source_view_find_page_for_row (MusicSourceView *self, ViewNode *node, int *n, | |
| 1219 | ViewPage *start) { | |
| 1220 | //printf ("find_page_for_row: %i (/%i)\n", *n, node->e_children); fflush (stdout); | |
| 1221 | ViewPage *page = start!=NULL? start: node->children; | |
| 1222 | g_return_val_if_fail (page!=NULL, NULL); | |
| 1223 | 438 | g_return_val_if_fail (*n >= 0, NULL); |
| 1224 | 253 | |
| 1225 | 185 | // Step 1. search page tree (FIXME: still a list at the moment). |
| 1226 | // We traverse the list (which of course is ordered) and stop on the last page | |
| 1227 | // before 'n'. | |
| 1228 | while (page->next!=NULL) { | |
| 1229 | if (page->next->start_e > *n) | |
| 1230 | break; | |
| 1231 | g_warn_if_fail (page->start_n < *n); | |
| 1232 | 253 | page = page->next; |
| 1233 | 185 | }; |
| 1234 | 253 | |
| 1235 | 185 | // Page has not been added properly. |
| 1236 | g_warn_if_fail (page->start_e > -1); | |
| 1237 | 253 | |
| 1238 | 185 | //printf ("start_e: %i, size %i\n", page->start_e, page->size); fflush(stdout); |
| 1239 | // Step 2. If page->size+start_e<n, we are here! | |
| 1240 | 253 | if (page->start_e<=*n && *n<page->start_e+page->size) |
| 1241 | 185 | return page; |
| 1242 | 253 | |
| 1243 | 185 | // If this is the last page and n is past the last row, return this page - it's the |
| 1244 | 253 | // caller's problem that the row it wants doesn't actually exist (and it may be planning |
| 1245 | 185 | // to add it). |
| 1246 | if (*n >= node->e_children) { | |
| 1247 | g_warn_if_fail (page->next==NULL); // Should have come to last page. | |
| 1248 | return page; | |
| 1249 | }; | |
| 1250 | 253 | |
| 1251 | // Else, query the page at row n. This is where it gets complicated - rows may be | |
| 1252 | 185 | // removed or inserted if estimations are changed. 'n' is updated appropriately in this |
| 1253 | // case. | |
| 1254 | // | |
| 1255 | //printf ("calling _read_page_at:\n"); fflush (stdout); | |
| 1256 | page = _music_source_view_read_page_at (self, page, page->next, n); | |
| 1257 | #ifdef MUSIC_SOURCE_VIEW_CHECK_TREE | |
| 1258 | _music_source_view_check_tree (self, NULL); | |
| 1259 | #endif | |
| 1260 | return page; | |
| 1261 | }; | |
| 1262 | ||
| 1263 | // Sets iter to point to row n in node. | |
| 1264 | // | |
| 1265 | static gboolean _find_iter_for_row(MusicSourceView *self, ViewNode *node, int n, GtkTreeIter *iter) { | |
| 1266 | ViewPage *page = _music_source_view_find_page_for_row (self, node, &n, NULL); | |
| 1267 | g_return_val_if_fail(page!=NULL, FALSE); | |
| 1268 | 253 | |
| 1269 | 185 | iter->stamp=n; |
| 1270 | iter->user_data=page->parent_node; | |
| 1271 | iter->user_data2=page; | |
| 1272 | iter->user_data3=GINT_TO_POINTER(n-page->start_e); | |
| 1273 | return TRUE; | |
| 1274 | }; | |
| 1275 | ||
| 1276 | // FIXME: warn when we have to do this - either GtkTreeView has changed, or someone else is | |
| 1277 | // querying using the default methods .. when I guess really we should have some "i'm sure | |
| 1278 | // i really want to iterate through every node" interface ... | |
| 1279 | static gboolean defloat(MusicSourceView *self, GtkTreeIter *iter) { | |
| 1280 | g_return_val_if_fail(iter->user_data2==NULL, FALSE); | |
| 1281 | 253 | |
| 1282 | 185 | void row_inserted(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, void *data) { |
| 1283 | }; | |
| 1284 | ||
| 1285 | void row_deleted(GtkTreeModel *model, GtkTreePath *path, void *data) { | |
| 1286 | }; | |
| 1287 | 253 | |
| 1288 | 185 | int h1=g_signal_connect(GTK_TREE_MODEL(self), "row-inserted", G_CALLBACK(row_inserted), NULL); |
| 1289 | int h2=g_signal_connect(GTK_TREE_MODEL(self), "row-deleted", G_CALLBACK(row_deleted), NULL); | |
| 1290 | 253 | |
| 1291 | 185 | ViewNode *node=iter->user_data; |
| 1292 | g_return_val_if_fail(node!=NULL, FALSE); | |
| 1293 | 253 | |
| 1294 | 185 | gboolean result=_find_iter_for_row(self, node, GPOINTER_TO_INT(iter->user_data3), iter); |
| 1295 | 253 | |
| 1296 | 185 | g_signal_handler_disconnect(GTK_TREE_MODEL(self), h1); |
| 1297 | g_signal_handler_disconnect(GTK_TREE_MODEL(self), h2); | |
| 1298 | 253 | |
| 1299 | 185 | return result; |
| 1300 | }; | |
| 1301 | ||
| 1302 | ||
| 1303 | 389 | void _music_source_view_start_initial_read_watch (MusicSourceView *self) { |
| 1304 | if (SP->initial_read_watch) | |
| 1305 | g_warning ("music_source_view_start_initial_read_watch: watch already in progress.\n"); | |
| 1306 | if (SP->initial_read_watch_list != NULL) | |
| 1307 | g_warning ("music_source_view_start_initial_read_watch: watch list not empty.\n"); | |
| 1308 | SP->initial_read_watch = TRUE; | |
| 1309 | }; | |
| 1310 | ||
| 1311 | GSList *_music_source_view_get_initial_read_watch_list (MusicSourceView *self) { | |
| 1312 | if (!SP->initial_read_watch) | |
| 1313 | g_warning ("music_source_view_get_initial_read_watch_list: watch not in progress.\n"); | |
| 1314 | ||
| 1315 | return SP->initial_read_watch_list; | |
| 1316 | }; | |
| 1317 | ||
| 1318 | void _music_source_view_end_initial_read_watch (MusicSourceView *self) { | |
| 1319 | if (!SP->initial_read_watch) | |
| 1320 | g_warning ("music_source_view_end_initial_read_watch: watch not in progress.\n"); | |
| 1321 | g_slist_free (SP->initial_read_watch_list); | |
| 1322 | SP->initial_read_watch_list = NULL; | |
| 1323 | SP->initial_read_watch = FALSE; | |
| 1324 | }; | |
| 1325 | ||
| 1326 | 185 | ///////////////////////////////////////////////////// |
| 1327 | // Internal interface | |
| 1328 | // | |
| 1329 | ||
| 1330 | 216 | void _music_source_view_initial_read (MusicSourceView *self, ViewNode *node) { |
| 1331 | 389 | interface_track_printf ("music_source_view_initial_read: node %x\n", node); |
| 1332 | if (SP->initial_read_watch) | |
| 1333 | SP->initial_read_watch_list = g_slist_prepend (SP->initial_read_watch_list, node); | |
| 1334 | 216 | MUSIC_SOURCE_VIEW_GET_CLASS(self)->initial_read (self, node); |
| 1335 | 185 | }; |
| 1336 | ||
| 1337 | 253 | ViewPage *_music_source_view_read_page_at (MusicSourceView *self, ViewPage *head, |
| 1338 | 185 | ViewPage *tail, int *e) { |
| 1339 | return MUSIC_SOURCE_VIEW_GET_CLASS(self)->read_page_at(self, head, tail, e); | |
| 1340 | } | |
| 1341 | ||
| 1342 | void _music_source_view_read_next_page(MusicSourceView *self, ViewPage *page) { | |
| 1343 | return MUSIC_SOURCE_VIEW_GET_CLASS(self)->read_next_page(self, page); | |
| 1344 | } | |
| 1345 | ||
| 1346 | ||
| 1347 | ||
| 1348 | //////////////////////////// | |
| 1349 | // Debugging methods | |
| 1350 | // | |
| 1351 | ||
| 1352 | ||
| 1353 | static void interface_track_printf(const char *text, ...) { | |
| 1354 | #ifdef MUSIC_SOURCE_VIEW_TRACE_INTERFACE | |
| 1355 | va_list ap; va_start(ap, text); vprintf(text, ap); va_end(ap); fflush(stdout); | |
| 1356 | #endif | |
| 1357 | }; | |
| 1358 | ||
| 1359 | /*static void dump_iter(GtkTreeIter *iter) { | |
| 1360 | ViewPage *page=iter->user_data2; | |
| 1361 | if (page==NULL) { | |
| 1362 | printf("iter %x: no page :(", (guint)iter); | |
| 1363 | return; | |
| 1364 | }; | |
| 1365 | printf("PAGE %x (e%in%i)/(e%in%i) - root_rows- %i size %i n %i\n", (guint)page, page->start_e, page->start_n, page->parent_node->e_children, page->parent_node->n_children, | |
| 1366 | page->root_rows, page->size, GPOINTER_TO_INT(iter->user_data3)); | |
| 1367 | // printf("\nIter: tree depth %i, config depth %i, id %i, n %i - index %i\n", node->tree_depth, node->config_depth, node->id, node->n, iter->user_data3); fflush(stdout); | |
| 1368 | 253 | }; |
| 1369 | 185 | |
| 1370 | static void dump_page(ViewPage *page) { | |
| 1371 | g_return_if_fail(page!=NULL); | |
| 1372 | printf("%x: prev %x, next %x - parent node %x\n", (guint)page, (guint)page->prev, (guint)page->next, (guint)page->parent_node); | |
| 1373 | //printf("At: Tree %i Config %i\n", page->tree_depth, page->config_depth); | |
| 1374 | printf("Start: e%i/n%i. chain_next: %i", page->start_e, page->start_n, page->chain_next); | |
| 1375 | printf("root rows %i, size %i.\n", page->root_rows, page->size); | |
| 1376 | printf("nodes %x\n\n", (guint)page->nodes); | |
| 1377 | };*/ | |
| 1378 | ||
| 1379 | 208 | void _music_source_view_dump_node (MusicSourceView *self, ViewNode *node) { |
| 1380 | 368 | if (node==NULL) |
| 1381 | node = &SP->root; | |
| 1382 | printf ("NODE: depth %i - type %s ", node->depth, | |
| 1383 | node->head==NULL? "<leaf>": entry_type_name[CONFIG_NODE(node->head)->type]); | |
| 1384 | ||
| 1385 | // Dump intermediate ids, and then entry id | |
| 1386 | printf ("["); int *ii = node->intermediate_ids; | |
| 1387 | while (*ii != 0) | |
| 1388 | printf ("%i, ", *(ii++)); | |
| 1389 | printf ("%i] ", node->id); fflush (stdout); | |
| 1390 | ||
| 1391 | 208 | int i = 1; |
| 1392 | for (ViewPage *page=node->children; page; page=page->next) { | |
| 1393 | 253 | printf ("\t%i:%x| [%x %x]\t%i-%i <%i> [%i-%i]\n", i++, (guint)page, (guint)page->prev, |
| 1394 | (guint)page->next, page->start_e, page->start_e+page->size, page->start_n, | |
| 1395 | 208 | page->root_start, page->root_start+page->root_rows-1); |
| 1396 | 185 | }; |
| 1397 | 216 | if (node->children!=NULL) |
| 1398 | 368 | printf ("\nNode thinks there are %i children \n", node->e_children); |
| 1399 | else printf ("..no children\n"); | |
| 1400 | 185 | fflush(stdout); |
| 1401 | }; | |
| 1402 | ||
| 1403 | 259 | /* A couple of utils for _check_tree. 'Running' differs from 'walking' in that we don't care about |
| 1404 | * the entries along the way. | |
| 1405 | * FIXME: put in viewconfig.c? | |
| 1406 | */ | |
| 1407 | static GList *_run_to_level_head (GList *start_node) { | |
| 1408 | GList *level_head; | |
| 1409 | for (level_head=start_node; level_head; level_head=level_head->prev) { | |
| 1410 | 261 | while (level_head->prev==NULL && CONFIG_NODE(level_head)->parent!=NULL) |
| 1411 | 259 | level_head = CONFIG_NODE(level_head)->parent; |
| 1412 | if (level_head->prev==NULL || !CONFIG_NODE(level_head->prev)->flat) break; | |
| 1413 | }; | |
| 1414 | return level_head; | |
| 1415 | }; | |
| 1416 | ||
| 1417 | 431 | /* _music_source_view_check_tree: assert 'node' and its children are in a consistent state. |
| 1418 | * | |
| 1419 | * This uses g_assert rather than g_[warn|return]_if_fail because it is not intended to be used | |
| 1420 | * outside of debugging builds and unit tests. | |
| 1421 | 218 | */ |
| 1422 | 261 | void _music_source_view_check_tree (MusicSourceView *self, ViewNode *node) { |
| 1423 | 185 | #ifdef MUSIC_SOURCE_VIEW_CHECK_TREE |
| 1424 | 218 | if (SP->config==NULL) return; |
| 1425 | 253 | |
| 1426 | if (node==NULL) | |
| 1427 | 218 | node = &SP->root; |
| 1428 | 185 | //_music_source_view_dump_node(self, node); |
| 1429 | ||
| 1430 | 218 | g_assert_cmpint (node->depth, <=, SP->config->n_levels); |
| 1431 | ||
| 1432 | 431 | // Test 'n' is correct. This should be the index to 'node' within its parent page. (So it should |
| 1433 | // really be called 'page_i', being a relative index). | |
| 1434 | // | |
| 1435 | if (node != &SP->root) { | |
| 1436 | ViewNode *n_node = g_array_index (node->parent_page->nodes, ViewNode *, node->n); | |
| 1437 | g_assert (node == n_node); | |
| 1438 | } | |
| 1439 | ||
| 1440 | // Test 'parent_tail'. Normally, parent_tail==parent->tail, but we could also be on an orphan | |
| 1441 | // branch. In this case we walk to the parent_tail's head to check it matches with parent->head. | |
| 1442 | 259 | // |
| 1443 | if (node==&SP->root) { | |
| 1444 | g_assert (node->parent_tail==NULL); | |
| 1445 | g_assert (node->parent_page==NULL); | |
| 1446 | } else { | |
| 1447 | ViewNode *parent_node = node->parent_page->parent_node; | |
| 1448 | g_assert (parent_node!=NULL); | |
| 1449 | g_assert (node->parent_tail!=NULL); | |
| 1450 | 261 | |
| 1451 | 259 | if (node->parent_tail!=parent_node->tail) { |
| 1452 | 261 | g_assert (CONFIG_NODE(node->parent_tail)->flat == FALSE); |
| 1453 | 259 | g_assert (_run_to_level_head(node->parent_tail) == parent_node->head); |
| 1454 | }; | |
| 1455 | }; | |
| 1456 | ||
| 1457 | 431 | // Check 'head' and 'tail' are correct. |
| 1458 | // | |
| 1459 | 218 | if (node->head==NULL && node->tail==NULL) { |
| 1460 | 259 | // Must be a leaf node if there is no config level associated. -1 (not yet read) isn't |
| 1461 | 261 | // acceptable either. |
| 1462 | 259 | g_assert (node->e_children==0); |
| 1463 | 218 | } else { |
| 1464 | 259 | //printf ("Checking: %i, %s.\n", node->depth, entry_type_name[CONFIG_NODE(node->head)->type]); |
| 1465 | g_assert (node->head!=NULL); g_assert (node->tail!=NULL); | |
| 1466 | ||
| 1467 | // First, run from tail to head. | |
| 1468 | GList *head, *tail; | |
| 1469 | g_assert (node->head->prev==NULL || CONFIG_NODE(node->head->prev)->flat==FALSE); | |
| 1470 | 261 | |
| 1471 | 259 | g_assert (CONFIG_NODE(node->tail)->flat == FALSE); |
| 1472 | g_assert (_run_to_level_head(node->tail) == node->head); | |
| 1473 | //printf ("FROM %s %i TO %s %i == %s %i\n", CONFIG_TYPE_NAME(node->tail), CONFIG_TYPE_NAME(_run_to_level_head(node->tail)), | |
| 1474 | // CONFIG_TYPE_NAME(node->head)); fflush (stdout); | |
| 1475 | ||
| 1476 | head = node->head; | |
| 1477 | 218 | int level = node->depth; |
| 1478 | 259 | fflush (stdout); |
| 1479 | 218 | // Given node's 'head' and 'depth', seek the tail node of the first level. |
| 1480 | while (level > 0) { | |
| 1481 | g_assert (head!=NULL); | |
| 1482 | if (head->prev) head = head->prev; | |
| 1483 | else if (CONFIG_NODE(head)->parent) head = CONFIG_NODE(head)->parent; | |
| 1484 | else g_assert_not_reached (); | |
| 1485 | if (!CONFIG_NODE(head)->flat) level--; | |
| 1486 | }; | |
| 1487 | ||
| 1488 | // Seek head node of first level. | |
| 1489 | do { | |
| 1490 | if (head->prev==NULL) { | |
| 1491 | if (CONFIG_NODE(head)->parent!=NULL) | |
| 1492 | head = CONFIG_NODE(head)->parent; | |
| 1493 | else break; | |
| 1494 | } else head = head->prev; | |
| 1495 | g_assert (head!=NULL); | |
| 1496 | } while (CONFIG_NODE(head)->flat); | |
| 1497 | 253 | |
| 1498 | 218 | // Check we got to config->head - if not, node's 'head' or 'depth' is wrong. |
| 1499 | g_assert (level==0); | |
| 1500 | g_assert (head==SP->config->head); | |
| 1501 | 253 | |
| 1502 | 218 | // Check tail is the end of this level. |
| 1503 | for (tail=node->head; CONFIG_NODE(tail)->flat; tail=tail->next) | |
| 1504 | g_assert (tail); | |
| 1505 | g_assert (tail==node->tail); | |
| 1506 | }; | |
| 1507 | ||
| 1508 | 431 | // Investigate the children. |
| 1509 | // | |
| 1510 | 218 | ViewPage *page = node->children; |
| 1511 | 185 | g_assert (node->e_children<=0 || node->children!=NULL); |
| 1512 | 253 | |
| 1513 | 185 | if (node->e_children==-1) { |
| 1514 | g_assert (node->children==NULL); | |
| 1515 | return; | |
| 1516 | }; | |
| 1517 | 431 | |
| 1518 | 185 | if (node->n_children==0) |
| 1519 | return; | |
| 1520 | ||
| 1521 | ViewPage *previous_page=NULL; | |
| 1522 | 259 | while (page!=NULL) { |
| 1523 | 185 | g_assert (page->prev==previous_page); |
| 1524 | 253 | |
| 1525 | 185 | if (previous_page!=NULL) |
| 1526 | g_assert_cmpint (page->start_e, >=, previous_page->start_e+previous_page->size); | |
| 1527 | ||
| 1528 | // Test all of the children now! | |
| 1529 | if (page->next==NULL || page->chain_next) | |
| 1530 | g_warn_if_fail(page->size==page->nodes->len); | |
| 1531 | else g_warn_if_fail(page->size==page->nodes->len || page->size==page->nodes->len-1); | |
| 1532 | 253 | |
| 1533 | 185 | for (int i=0;i<page->size;i++) { |
| 1534 | ViewNode *child = g_array_index(page->nodes, ViewNode *, i); | |
| 1535 | //printf("[page start %i] Testing node %i [id %i] - n %i i %i\n", page->start_e, i, child->id, child->n, i);fflush(stdout); | |
| 1536 | 253 | |
| 1537 | 185 | g_assert_cmpint (child->n, ==, i); |
| 1538 | g_assert_cmphex ((guint32)child->parent_page, ==, (guint32)page); | |
| 1539 | _music_source_view_check_tree(self, child); | |
| 1540 | }; | |
| 1541 | 253 | |
| 1542 | 185 | previous_page=page; |
| 1543 | page=page->next; | |
| 1544 | }; | |
| 1545 | 253 | |
| 1546 | 185 | g_assert (previous_page->size+previous_page->start_e == node->e_children); |
| 1547 | #endif | |
| 1548 | }; |
Loggerhead is a web-based interface for Bazaar branches