-/* Called when the cancel search button is clicked */
-static void clearsearch_clicked(GtkButton attribute((unused)) *button,
- gpointer attribute((unused)) userdata) {
- gtk_entry_set_text(GTK_ENTRY(searchentry), "");
-}
-
-/* Display functions ------------------------------------------------------- */
-
-/* Delete all the widgets in the tree */
-static void delete_widgets(struct choosenode *cn) {
- int n;
-
- if(cn->container) {
- gtk_widget_destroy(cn->container);
- cn->container = 0;
- }
- for(n = 0; n < cn->children.nvec; ++n)
- delete_widgets(cn->children.vec[n]);
- cn->flags &= ~(CN_DISPLAYED|CN_SELECTED);
- files_selected = 0;
-}
-
-/* Update the display */
-static void redisplay_tree(void) {
- struct displaydata d;
- guint oldwidth, oldheight;
-
- D(("redisplay_tree"));
- /* We'll count these up empirically each time */
- files_selected = 0;
- files_visible = 0;
- /* Correct the layout and find out how much space it uses */
- d = display_tree(root, 0, 0);
- /* We must set the total size or scrolling will not work (it wouldn't be hard
- * for GtkLayout to figure it out for itself but presumably you're supposed
- * to be able to have widgets off the edge of the layuot.)
- *
- * There is a problem: if we shrink the size then the part of the screen that
- * is outside the new size but inside the old one is not updated. I think
- * this is arguably bug in GTK+ but it's easy to force a redraw if this
- * region is nonempty.
- */
- gtk_layout_get_size(GTK_LAYOUT(chooselayout), &oldwidth, &oldheight);
- if(oldwidth > d.width || oldheight > d.height)
- gtk_widget_queue_draw(chooselayout);
- gtk_layout_set_size(GTK_LAYOUT(chooselayout), d.width, d.height);
- /* Notify the main menu of any recent changes */
- menu_update(-1);
-}
-
-/* Make sure all displayed widgets from CN down exist and are in their proper
- * place and return the vertical space used. */
-static struct displaydata display_tree(struct choosenode *cn, int x, int y) {
- int n, aw;
- GtkRequisition req;
- struct displaydata d, cd;
- GdkPixbuf *pb;
-
- 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) {
- /* Widgets need to be created */
- cn->hbox = gtk_hbox_new(FALSE, 1);
- if(cn->flags & CN_EXPANDABLE) {
- 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")))
- cn->marker = gtk_image_new_from_pixbuf(pb);
- }
- 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);
- 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);
- gtk_widget_set_name(cn->label, "choose");
- gtk_widget_set_name(cn->container, "choose");
- /* Show everything by default */
- gtk_widget_show_all(cn->container);
- }
- assert(cn->container);
- /* Make sure the icon is right */
- 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);
- /* Put the widget in the right place */
- 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;
- }
- /* Set the widget's selection status */
- if(!(cn->flags & CN_EXPANDABLE))
- display_selection(cn);
- /* 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;
- 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 {
- for(n = 0; n < cn->children.nvec; ++n)
- undisplay_tree(cn->children.vec[n]);
- }
- if(!(cn->flags & CN_EXPANDABLE)) {
- ++files_visible;
- if(cn->flags & CN_SELECTED)
- ++files_selected;
- }
- /* 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;
-}
-
-/* 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 --------------------------------------------------------------- */
-
-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));
-}
-
-/* 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);
- }
-}
-
-/* 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 ------------------------------------------------------------ */
-
-/* Clicked on something */
-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);
- 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;
- }
- }
- 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) {
- /* 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; n < NMENUITEMS; ++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);
- }
- /* Pop up the menu */
- gtk_widget_show_all(menu);
- gtk_menu_popup(GTK_MENU(menu), 0, 0, 0, 0,
- event->button, event->time);