- D(("display_tree %s %d,%d", cn->path, x, y));
-
- /* An expandable item contains an arrow and a text label. When you press the
- * button it flips its expand state.
- *
- * A non-expandable item has just a text label and no arrow.
- */
- if(!cn->container) {
- BEGIN(new_widgets);
- MTAG_PUSH("make_widgets_1");
- /* Widgets need to be created */
- NW(hbox);
- cn->hbox = gtk_hbox_new(FALSE, 1);
- if(cn->flags & CN_EXPANDABLE) {
- NW(arrow);
- cn->arrow = gtk_arrow_new(cn->flags & CN_EXPANDED ? GTK_ARROW_DOWN
- : GTK_ARROW_RIGHT,
- GTK_SHADOW_NONE);
- cn->marker = 0;
- } else {
- cn->arrow = 0;
- if((pb = find_image("notes.png"))) {
- NW(image);
- cn->marker = gtk_image_new_from_pixbuf(pb);
- }
- }
- MTAG_POP();
- MTAG_PUSH("make_widgets_2");
- NW(label);
- cn->label = gtk_label_new(cn->display);
- if(cn->arrow)
- gtk_container_add(GTK_CONTAINER(cn->hbox), cn->arrow);
- gtk_container_add(GTK_CONTAINER(cn->hbox), cn->label);
- if(cn->marker)
- gtk_container_add(GTK_CONTAINER(cn->hbox), cn->marker);
- MTAG_POP();
- MTAG_PUSH("make_widgets_3");
- NW(event_box);
- cn->container = gtk_event_box_new();
- gtk_container_add(GTK_CONTAINER(cn->container), cn->hbox);
- g_signal_connect(cn->container, "button-release-event",
- G_CALLBACK(clicked_choosenode), cn);
- g_signal_connect(cn->container, "button-press-event",
- G_CALLBACK(clicked_choosenode), cn);
- g_object_ref(cn->container);
- /* Show everything by default */
- gtk_widget_show_all(cn->container);
- MTAG_POP();
- END(new_widgets);
- }
- assert(cn->container);
- /* Set colors */
- BEGIN(colors);
- /* This section turns out to reliably take >50% of the elapsed time when
- * displaying the tree, both when it's largely unexpanded and when it's
- * heavily expanded. */
- if(search_result)
- gtk_widget_modify_bg(cn->container, GTK_STATE_NORMAL, &search_bg);
- else
- gtk_widget_modify_bg(cn->container, GTK_STATE_NORMAL, &layout_bg);
- gtk_widget_modify_bg(cn->container, GTK_STATE_SELECTED, &selected_bg);
- gtk_widget_modify_bg(cn->container, GTK_STATE_PRELIGHT, &selected_bg);
- gtk_widget_modify_fg(cn->label, GTK_STATE_NORMAL, &item_fg);
- gtk_widget_modify_fg(cn->label, GTK_STATE_SELECTED, &selected_fg);
- gtk_widget_modify_fg(cn->label, GTK_STATE_PRELIGHT, &selected_fg);
- END(colors);
- /* Make sure the icon is right */
- BEGIN(markers);
- if(cn->flags & CN_EXPANDABLE)
- gtk_arrow_set(GTK_ARROW(cn->arrow),
- cn->flags & CN_EXPANDED ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
- GTK_SHADOW_NONE);
- else if(cn->marker)
- /* Make sure the queued marker is right */
- /* TODO: doesn't always work */
- (queued(cn->path) ? gtk_widget_show : gtk_widget_hide)(cn->marker);
- END(markers);
- /* Put the widget in the right place */
- BEGIN(location);
- if(cn->flags & CN_DISPLAYED)
- gtk_layout_move(GTK_LAYOUT(chooselayout), cn->container, x, y);
- else {
- gtk_layout_put(GTK_LAYOUT(chooselayout), cn->container, x, y);
- cn->flags |= CN_DISPLAYED;
- /* Now chooselayout has a ref to the container */
- g_object_unref(cn->container);
- }
- END(location);
- /* Set the widget's selection status */
- BEGIN(selection);
- if(!(cn->flags & CN_EXPANDABLE))
- display_selection(cn);
- END(selection);
- /* Find the size used so we can get vertical positioning right. */
- gtk_widget_size_request(cn->container, &req);
- d.width = x + req.width;
- d.height = y + req.height;
- cn->ymin = y;
- cn->ymax = d.height;
- if(cn->flags & CN_EXPANDED) {
- /* We'll offset children by the size of the arrow whatever it might be. */
- assert(cn->arrow);
- gtk_widget_size_request(cn->arrow, &req);
- aw = req.width;
- for(n = 0; n < cn->children.nvec; ++n) {
- cd = display_tree(cn->children.vec[n], x + aw, d.height);
- if(cd.width > d.width)
- d.width = cd.width;
- d.height = cd.height;
- }
- } else {
- BEGIN(undisplay);
- for(n = 0; n < cn->children.nvec; ++n)
- undisplay_tree(cn->children.vec[n]);
- END(undisplay);
- }
- if(!(cn->flags & CN_EXPANDABLE)) {
- ++files_visible;
- if(cn->flags & CN_SELECTED)
- ++files_selected;
- }
- /* update the search results array */
- if(search_result)
- searchnodes[search_result - 1] = cn;
- /* report back how much space we used */
- D(("display_tree %s %d,%d total size %dx%d", cn->path, x, y,
- d.width, d.height));
- return d;
-}
-
-/** @brief Remove widgets for newly hidden nodes */
-static void undisplay_tree(struct choosenode *cn) {
- int n;
-
- D(("undisplay_tree %s", cn->path));
- /* Remove this widget from the display */
- if(cn->flags & CN_DISPLAYED) {
- gtk_container_remove(GTK_CONTAINER(chooselayout), cn->container);
- cn->flags ^= CN_DISPLAYED;
- }
- /* Remove children too */
- for(n = 0; n < cn->children.nvec; ++n)
- undisplay_tree(cn->children.vec[n]);
-}
-
-/* Selection --------------------------------------------------------------- */
-
-/** @brief Mark the widget @p cn according to its selection state */
-static void display_selection(struct choosenode *cn) {
- /* Need foreground and background colors */
- gtk_widget_set_state(cn->label, (cn->flags & CN_SELECTED
- ? GTK_STATE_SELECTED : GTK_STATE_NORMAL));
- gtk_widget_set_state(cn->container, (cn->flags & CN_SELECTED
- ? GTK_STATE_SELECTED : GTK_STATE_NORMAL));
-}
-
-/** @brief Set the selection state of a widget
- *
- * Directories can never be selected, we just ignore attempts to do so. */
-static void set_selection(struct choosenode *cn, int selected) {
- unsigned f = selected ? CN_SELECTED : 0;
-
- D(("set_selection %d %s", selected, cn->path));
- if(!(cn->flags & CN_EXPANDABLE) && (cn->flags & CN_SELECTED) != f) {
- cn->flags ^= CN_SELECTED;
- /* Maintain selection count */
- if(selected)
- ++files_selected;
- else
- --files_selected;
- display_selection(cn);
- /* Update main menu sensitivity */
- menu_update(-1);
- }
-}
-
-/** @brief Recursively clear all selection bits from CN down */
-static void clear_selection(struct choosenode *cn) {
- int n;
-
- set_selection(cn, 0);
- for(n = 0; n < cn->children.nvec; ++n)
- clear_selection(cn->children.vec[n]);
-}
-
-/* User actions ------------------------------------------------------------ */
-
-/** @brief Clicked on something
- *
- * This implements playing, all the modifiers for selection, etc.
- */
-static void clicked_choosenode(GtkWidget attribute((unused)) *widget,
- GdkEventButton *event,
- gpointer user_data) {
- struct choosenode *cn = user_data;
- int ind, last_ind, n;
-
- D(("clicked_choosenode %s", cn->path));
- if(event->type == GDK_BUTTON_RELEASE
- && event->button == 1) {
- /* Left click */
- if(cn->flags & CN_EXPANDABLE) {
- /* This is a directory. Flip its expansion status. */
- if(cn->flags & CN_EXPANDED)
- contract_node(cn);
- else
- expand_node(cn, 0/*!contingent*/);
- last_click = 0;
- } else {
- /* This is a file. Adjust selection status */
- /* TODO the basic logic here is essentially the same as that in queue.c.
- * Can we share code at all? */
- switch(event->state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)) {
- case 0:
- clear_selection(root);
- set_selection(cn, 1);
- last_click = cn;
- break;
- case GDK_CONTROL_MASK:
- set_selection(cn, !(cn->flags & CN_SELECTED));
- last_click = cn;
- break;
- case GDK_SHIFT_MASK:
- case GDK_SHIFT_MASK|GDK_CONTROL_MASK:
- if(last_click && last_click->parent == cn->parent) {
- /* Figure out where the current and last clicks are in the list */
- ind = last_ind = -1;
- for(n = 0; n < cn->parent->children.nvec; ++n) {
- if(cn->parent->children.vec[n] == cn)
- ind = n;
- if(cn->parent->children.vec[n] == last_click)
- last_ind = n;
- }
- /* Test shouldn't ever fail, but still */
- if(ind >= 0 && last_ind >= 0) {
- if(!(event->state & GDK_CONTROL_MASK)) {
- for(n = 0; n < cn->parent->children.nvec; ++n)
- set_selection(cn->parent->children.vec[n], 0);
- }
- if(ind > last_ind)
- for(n = last_ind; n <= ind; ++n)
- set_selection(cn->parent->children.vec[n], 1);
- else
- for(n = ind; n <= last_ind; ++n)
- set_selection(cn->parent->children.vec[n], 1);
- if(event->state & GDK_CONTROL_MASK)
- last_click = cn;
- }
- }
- /* TODO trying to select a range that doesn't share a single parent
- * currently does not work, but it ought to. */
- break;
- }
- }
- } else if(event->type == GDK_BUTTON_RELEASE
- && event->button == 2) {
- /* Middle click - play the pointed track */
- if(!(cn->flags & CN_EXPANDABLE)) {
- clear_selection(root);
- set_selection(cn, 1);
- gtk_label_set_text(GTK_LABEL(report_label), "adding track to queue");
- disorder_eclient_play(client, cn->path, 0, 0);
- last_click = 0;
- }
- } else if(event->type == GDK_BUTTON_PRESS
- && event->button == 3) {
- struct choose_menuitem *const menuitems =
- (cn->flags & CN_EXPANDABLE ? dir_menuitems : track_menuitems);
- GtkWidget *const menu =
- (cn->flags & CN_EXPANDABLE ? dir_menu : track_menu);
- /* Right click. Pop up a menu. */
- /* If the current file isn't selected, switch the selection to just that.
- * (If we're looking at a directory then leave the selection alone.) */
- if(!(cn->flags & CN_EXPANDABLE) && !(cn->flags & CN_SELECTED)) {
- clear_selection(root);
- set_selection(cn, 1);
- last_click = cn;
- }
- /* Set the item sensitivity and callbacks */
- for(n = 0; menuitems[n].name; ++n) {
- if(menuitems[n].handlerid)
- g_signal_handler_disconnect(menuitems[n].w,
- menuitems[n].handlerid);
- gtk_widget_set_sensitive(menuitems[n].w,
- menuitems[n].sensitive(cn));
- menuitems[n].handlerid = g_signal_connect
- (menuitems[n].w, "activate", G_CALLBACK(menuitems[n].activate), cn);
- }
- set_tool_colors(menu);
- /* Pop up the menu */
- gtk_widget_show_all(menu);
- gtk_menu_popup(GTK_MENU(menu), 0, 0, 0, 0,
- event->button, event->time);
- }