#include #include #include static GtkWidget *main_window = NULL; struct private_data { GtkWidget *tree_view; GtkWidget *scrolled_window; /* ドラッグ側データ */ gboolean in_drag_operation; gint drag_button; gint drag_x; gint drag_y; /* ドロップ側データ */ guint scroll_timer; }; #define INFO_MY_DRAG_DATA 1 static GtkTargetEntry drag_targets[] = { { "my-drag-data", GTK_TARGET_SAME_APP, INFO_MY_DRAG_DATA } }; enum { COLUMN_TITLE = 0, COLUMN_AUTHOR, COLUMN_PRICE, COLUMN_PUBLISHER, COLUMN_ISBN }; struct book_info_type { char *title; char *author; int price; char *publisher; char *isbn; }; static const struct book_info_type book_info[] = { { "暗黒神ダゴン", "フレッド・チャペル", 540, "東京創元社", "ISBN4-488-52312-9" }, { "アーカム計画", "ロバート・ブロック", 680, "東京創元社", "ISBN4-488-53102-4" }, { "黒の碑", "ロバート・E・ハワード", 720, "東京創元社", "ISBN4-488-51408-1" }, { "死者の書", "ジョナサン・キャロル", 760, "東京創元社", "ISBN4-488-54701-X" }, { "少女は巨人と踊る", "雨木シュウスケ", 580, "富士見書房", "ISBN4-8291-1548-3" }, { "少女は精霊と歌う", "雨木シュウスケ", 580, "富士見書房", "ISBN4-8291-1583-1" }, { "少女は蒼剣と語る", "雨木シュウスケ", 560, "富士見書房", "ISBN4-8291-1617-X" }, { "少女は世界と歩む", "雨木シュウスケ", 560, "富士見書房", "ISBN4-8291-1647-1" }, { "そして少女は慈しむ", "雨木シュウスケ", 560, "富士見書房", "ISBN4-8291-1686-2" }, { "鋼殻のレギオス ドラゴンマガジン7月号ふろく1", "雨木シュウスケ", 0, "富士見書房", "" }, { "CAVE IN", "都築由浩", 950, "リーフ", "ISBN4-434-08525-5" }, { "風の十二方位", "アーシュラ・K・ル・グィン", 880, "早川書房", "ISBN4-15-010399-2" }, { "OS概論", "久保秀士", 2670, "共立出版", "ISBN4-320-02395-1" }, { "OS雑学", "前田英明", 2505, "共立出版", "ISBN4-320-02396-X" }, { "OS-9/68000", "高澤嘉光", 2670, "共立出版", "ISBN4-320-02401-X" }, { "楽園通信社綺談", "佐藤明機", 952, "コスミック出版", "ISBN4-7747-3008-4" }, { "ビブリオテーク・リヴ", "佐藤明機", 952, "コスミック出版", "ISBN4-7747-3009-2" }, { "ネコノクラスム", "浅木柚乃", 886, "フランス書院", "ISBN4-8296-7885-2" }, { "プリント基盤CAD EAGLE活用入門", "今野邦彦", 2800, "CQ出版社", "ISBN4-7898-3630-4" }, { "電子回路シミュレータPSpice入門編", "棚木義則", 1800, "CQ出版社", "ISBN4-7898-3627-4" } }; gboolean scroll_tree_view(gpointer user_data) { struct private_data *private_data = (struct private_data *)user_data; gint width; gint height; GtkAdjustment *adjustment; gdouble value; gint x; gint y; gboolean repeat = FALSE; gdk_threads_enter(); if (gdk_window_get_pointer(private_data->tree_view->window, &x, &y, NULL) == NULL) { return FALSE; } /* カーソルがウィジェットの端にある場合、スクロールさせる */ gdk_window_get_geometry(private_data->tree_view->window, NULL, NULL, &width, &height, NULL); if (y < 32) { /* 下スクロール */ adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(private_data->scrolled_window)); if (adjustment->lower < adjustment->value) { value = adjustment->value - adjustment->step_increment; if (value < adjustment->lower) { value = adjustment->lower; } gtk_adjustment_set_value(adjustment, value); repeat = TRUE; } } else if (height - 32 < y) { /* 上スクロール */ adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(private_data->scrolled_window)); if (adjustment->value < adjustment->upper - adjustment->page_increment - adjustment->step_increment) { value = adjustment->value + adjustment->step_increment; if (adjustment->upper - adjustment->page_increment - adjustment->step_increment < value) { value = adjustment->upper - adjustment->page_increment - adjustment->step_increment; } gtk_adjustment_set_value(adjustment, value); repeat = TRUE; } } if (x < 32) { /* 左スクロール */ adjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(private_data->scrolled_window)); if (adjustment->lower < adjustment->value) { value = adjustment->value - adjustment->step_increment; if (value < adjustment->lower) { value = adjustment->lower; } gtk_adjustment_set_value(adjustment, value); repeat = TRUE; } } else if (width - 32 < x) { /* 右スクロール */ adjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(private_data->scrolled_window)); if (adjustment->value < adjustment->upper - adjustment->page_increment - adjustment->step_increment) { value = adjustment->value + adjustment->step_increment; if (adjustment->upper - adjustment->page_increment - adjustment->step_increment < value) { value = adjustment->upper - adjustment->page_increment - adjustment->step_increment; } gtk_adjustment_set_value(adjustment, value); repeat = TRUE; } } if (repeat == FALSE) { private_data->scroll_timer = 0; } gdk_threads_leave(); return repeat; } GdkDragAction select_action(GdkDragAction actions) { GtkWidget *dialog; gint value; dialog = gtk_dialog_new(); gtk_window_set_title(GTK_WINDOW(dialog), "どうしますか?"); gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(main_window)); gtk_window_set_destroy_with_parent(GTK_WINDOW(dialog), TRUE); gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE); gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE); if (actions & GDK_ACTION_COPY) { gtk_dialog_add_button(GTK_DIALOG(dialog), "コピー", GDK_ACTION_COPY); } if (actions & GDK_ACTION_MOVE) { gtk_dialog_add_button(GTK_DIALOG(dialog), "移動", GDK_ACTION_MOVE); } if (actions & GDK_ACTION_LINK) { gtk_dialog_add_button(GTK_DIALOG(dialog), "リンク", GDK_ACTION_LINK); } gtk_dialog_add_button(GTK_DIALOG(dialog), "キャンセル", 0); value = gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); if (value != 0 && value != GDK_ACTION_COPY && value != GDK_ACTION_MOVE && value != GDK_ACTION_LINK) { value = 0; } return value; } gboolean button_press_event(GtkWidget *widget, GdkEventButton *event, gpointer user_data) { struct private_data *private_data = (struct private_data *)user_data; gboolean stop_other_handlers = FALSE; GtkTreePath *tree_path; if (private_data->in_drag_operation) { return FALSE; } if (event == NULL) { return FALSE; } if (event->button != 1 && event->button != 2 && event->button != 3) { return FALSE; } /* マウスカーソルの位置がGtkTreeViewの描画ウィンドウでない(ヘッダ部分でボタンを押した)場合は何もしない */ if (event->window != gtk_tree_view_get_bin_window(GTK_TREE_VIEW(widget))) { return FALSE; } /* カーソルの下にアイテムが存在する場合のみドラッグ&ドロップの開始チェックを行う */ if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), (gint)event->x, (gint)event->y, &tree_path, NULL, NULL, NULL) == FALSE) { return FALSE; } /* ドラッグ&ドロップの開始ボタン、カーソル位置を保存する */ if (private_data->drag_button == 0) { private_data->drag_button = event->button; private_data->drag_x = (gint)event->x; private_data->drag_y = (gint)event->y; } else if (event->button < private_data->drag_button) { private_data->drag_button = event->button; } /* ボタン2、3が押された場合は選択変更を自分で行う */ if (event->button == 2 || event->button == 3) { GtkTreeSelection *selection; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)); if (gtk_tree_selection_path_is_selected(selection, tree_path) == FALSE) { gtk_tree_selection_unselect_all(selection); gtk_tree_selection_select_path(selection, tree_path); gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), tree_path, NULL, FALSE); } gtk_widget_grab_focus(widget); stop_other_handlers = TRUE; } gtk_tree_path_free(tree_path); return stop_other_handlers; } gboolean button_release_event(GtkWidget *widget, GdkEventButton *event, gpointer user_data) { struct private_data *private_data = (struct private_data *)user_data; gboolean stop_other_handlers = FALSE; GtkTreePath *tree_path; /* 自動スクロール解除 */ if (private_data->scroll_timer != 0) { g_source_remove(private_data->scroll_timer); private_data->scroll_timer = 0; } if (private_data->in_drag_operation) { return FALSE; } if (event == NULL) { return FALSE; } if (event->button != 1 && event->button != 2 && event->button != 3) { return FALSE; } /* ドラッグ&ドロップの開始ボタンをクリアする */ private_data->drag_button = 0; #if 0 /* マウスカーソルの位置にあるアイテムを調べる */ if (event->window == gtk_tree_view_get_bin_window(GTK_TREE_VIEW(widget)) && gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), (gint)event->x, (gint)event->y, &tree_path, NULL, NULL, NULL) == FALSE) { tree_path = NULL; } /* ボタン2を離したときに何か実行する */ if (event->button == 2) { button2_action(widget, tree_path); stop_other_handlers = TRUE; } /* ボタン3を離したときにポップアップメニューを表示する */ if (event->button == 3) { popup_menu(widget, event); stop_other_handlers = TRUE; } if (tree_path != NULL) { gtk_tree_path_free(tree_path); } #endif return stop_other_handlers; } gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event, gpointer user_data) { struct private_data *private_data = (struct private_data *)user_data; if (private_data->in_drag_operation) { return FALSE; } if (event == NULL) { return FALSE; } if (private_data->drag_button == 0) { return FALSE; } /* ボタン押下からのカーソル移動量をチェックし、閾値を越えていたらドラッグ&ドロップを開始する */ if (gtk_drag_check_threshold(widget, private_data->drag_x, private_data->drag_y, (gint)event->x, (gint)event->y)) { GtkTargetList *list; private_data->in_drag_operation = TRUE; list = gtk_target_list_new(drag_targets,sizeof(drag_targets) / sizeof(drag_targets[0])); gtk_drag_begin(widget, list, GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK, private_data->drag_button, (GdkEvent *)event); gtk_target_list_unref(list); } return FALSE; } gboolean leave_notify_event(GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) { struct private_data *private_data = (struct private_data *)user_data; if (!private_data->in_drag_operation) { private_data->drag_button = 0; } return FALSE; } void drag_begin(GtkWidget *widget, GdkDragContext *context, gpointer user_data) { /* デフォルトハンドラの停止 */ g_signal_stop_emission_by_name(G_OBJECT(widget), "drag-begin"); } void drag_data_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *data, guint info, guint time_, gpointer user_data) { /* デフォルトハンドラの停止 */ g_signal_stop_emission_by_name(G_OBJECT(widget), "drag-data-get"); /* ドラッグデータ作成 */ if (info == INFO_MY_DRAG_DATA) { GtkTreeModel *model; GtkTreeIter iter; GList *list; GList *cur; guint i; struct book_info_type *books; gint size; gchar *drag_data; gint offset; list = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)), &model); i = g_list_length(list); books = (struct book_info_type *)g_new0(struct book_info_type, i + 1); for (i = 0, cur = g_list_first(list); cur != NULL; i++, cur = g_list_next(cur)) { gtk_tree_model_get_iter(model, &iter, (GtkTreePath *)cur->data); gtk_tree_model_get(model, &iter, COLUMN_TITLE, &books[i].title, COLUMN_AUTHOR, &books[i].author, COLUMN_PRICE, &books[i].price, COLUMN_PUBLISHER, &books[i].publisher, COLUMN_ISBN, &books[i].isbn, -1); } size = 0; for (i = 0; books[i].title != NULL; i++) { size += strlen(books[i].title) + 1; size += strlen(books[i].author) + 1; size += 10 + 1; size += strlen(books[i].publisher) + 1; size += strlen(books[i].isbn) + 1; } size += 1; drag_data = (gchar *)g_new(gchar *, size); offset = 0; for (i = 0; books[i].title != NULL; i++) { offset += g_sprintf(drag_data + offset, "%s\t%s\t%d\t%s\t%s\n", books[i].title, books[i].author, books[i].price, books[i].publisher, books[i].isbn); g_free(books[i].title); g_free(books[i].author); g_free(books[i].publisher); g_free(books[i].isbn); } *(drag_data + offset) = '\0'; g_free(books); gtk_selection_data_set(data, gdk_atom_intern("my-drag-data", TRUE), 8, drag_data, strlen(drag_data) + 1); g_free(drag_data); } } void drag_data_delete(GtkWidget *widget, GdkDragContext *context, gpointer user_data) { GtkTreeModel *model; GtkTreePath *tree_path; GtkTreeIter iter; GList *list; GList *cur; GList *ref_list; /* デフォルトハンドラの停止 */ g_signal_stop_emission_by_name(G_OBJECT(widget), "drag-data-delete"); list = gtk_tree_selection_get_selected_rows(gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)), &model); ref_list = NULL; /* GtkTreePathからGtkTreeRowReferenceを作成して */ for (cur = g_list_first(list); cur != NULL; cur = g_list_next(cur)) { ref_list = g_list_append(ref_list, gtk_tree_row_reference_new(model, (GtkTreePath *)cur->data)); gtk_tree_path_free((GtkTreePath *)cur->data); } g_list_free(list); /* GtkTreeRowReferenceをもとに削除する */ for (cur = g_list_first(ref_list); cur != NULL; cur = g_list_next(cur)) { tree_path = gtk_tree_row_reference_get_path((GtkTreeRowReference *)cur->data); gtk_tree_model_get_iter(model, &iter, tree_path); gtk_list_store_remove(GTK_LIST_STORE(model), &iter); gtk_tree_path_free(tree_path); gtk_tree_row_reference_free((GtkTreeRowReference *)cur->data); } g_list_free(ref_list); } void drag_end(GtkWidget *widget, GdkDragContext *context, gpointer user_data) { struct private_data *private_data = (struct private_data *)user_data; /* デフォルトハンドラの停止 */ g_signal_stop_emission_by_name(G_OBJECT(widget), "drag-end"); private_data->in_drag_operation = FALSE; private_data->drag_button = 0; } gboolean drag_motion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time_, gpointer user_data) { struct private_data *private_data = (struct private_data *)user_data; GtkTargetList *target_list; GdkAtom target; GtkTreePath *tree_path; /* デフォルトハンドラの停止 */ g_signal_stop_emission_by_name(G_OBJECT(widget), "drag-motion"); /* ドロップ可否チェック */ target_list = gtk_drag_dest_get_target_list(widget); target = gtk_drag_dest_find_target(widget, context, target_list); if (target == GDK_NONE) { gdk_drag_status(context, 0, time_); return FALSE; } /* スクロールタイマー設定 */ if (private_data->scroll_timer == 0) { private_data->scroll_timer = g_timeout_add(150, scroll_tree_view, private_data); } /* ドロップ場所のチェック */ /* 同じウィジェット内のドラッグ&ドロップかつ選択中のアイテム上であればドロップ不可にする */ if (gtk_drag_get_source_widget(context) == widget && gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget), x, y, &tree_path, NULL) == TRUE) { if (gtk_tree_selection_path_is_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)), tree_path) == TRUE) { gtk_tree_path_free(tree_path); gdk_drag_status(context, 0, time_); return FALSE; } gtk_tree_path_free(tree_path); } gdk_drag_status(context, context->suggested_action, time_); return TRUE; } void drag_leave(GtkWidget *widget, GdkDragContext *context, guint time_, gpointer user_data) { struct private_data *private_data = (struct private_data *)user_data; /* デフォルトハンドラの停止 */ g_signal_stop_emission_by_name(G_OBJECT(widget), "drag-leave"); /* 自動スクロール解除 */ if (private_data->scroll_timer != 0) { g_source_remove(private_data->scroll_timer); private_data->scroll_timer = 0; } } gboolean drag_drop(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time_, gpointer user_data) { struct private_data *private_data = (struct private_data *)user_data; GtkTargetList *target_list; GdkAtom target; GtkTreePath *tree_path; /* デフォルトハンドラの停止 */ g_signal_stop_emission_by_name(G_OBJECT(widget), "drag-drop"); /* 自動スクロール解除 */ if (private_data->scroll_timer != 0) { g_source_remove(private_data->scroll_timer); private_data->scroll_timer = 0; } /* ドロップ可否チェック */ target_list = gtk_drag_dest_get_target_list(widget); target = gtk_drag_dest_find_target(widget, context, target_list); if (target == GDK_NONE) { gtk_drag_finish(context, FALSE, FALSE, time_); return FALSE; } /* ドロップ場所のチェック */ /* 同じウィジェット内のドラッグ&ドロップかつ選択中のアイテム上であればドロップ不可にする */ if (gtk_drag_get_source_widget(context) == widget && gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget), x, y, &tree_path, NULL) == TRUE) { if (gtk_tree_selection_path_is_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)), tree_path) == TRUE) { gtk_tree_path_free(tree_path); gtk_drag_finish(context, FALSE, FALSE, time_); return FALSE; } gtk_tree_path_free(tree_path); } /* ドラッグデータを取得 */ gtk_drag_get_data(widget, context, target, time_); return TRUE; } void drag_data_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint info, guint time_, gpointer user_data) { gboolean result = FALSE; gboolean delete = FALSE; /* デフォルトハンドラの停止 */ g_signal_stop_emission_by_name(G_OBJECT(widget), "drag-data-received"); if (info == INFO_MY_DRAG_DATA) { if (context->suggested_action == GDK_ACTION_ASK) { GdkDragAction action; if (gtk_drag_get_source_widget(context) == widget) { /* 同一ウィジェットのときは移動不可 */ action = select_action(context->actions & GDK_ACTION_COPY); } else { action = select_action(context->actions & (GDK_ACTION_COPY | GDK_ACTION_MOVE)); } if (action == 0) { /* キャンセル */ goto ERROR_EXIT; } context->action = action; } if (context->action == GDK_ACTION_MOVE && gtk_drag_get_source_widget(context) == widget) { /* 同じウィジェット内の移動はエラー */ goto ERROR_EXIT; } if (context->action == GDK_ACTION_COPY || context->action == GDK_ACTION_MOVE) { GtkTreeSelection *selection; GtkTreeModel *model; GtkTreeIter iter; gchar **books; gchar **items; gint i; GList *list; if (data->format == 8 && data->length != 0 && data->data != NULL) { books = g_strsplit(data->data, "\n", -1); model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget)); selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget)); gtk_tree_selection_unselect_all(selection); for (i = 0; books[i] != NULL && *(books[i]) != '\0'; i++) { items = g_strsplit(books[i], "\t", 5); gtk_list_store_append(GTK_LIST_STORE(model), &iter); gtk_list_store_set(GTK_LIST_STORE(model), &iter, COLUMN_TITLE, items[0], COLUMN_AUTHOR, items[1], COLUMN_PRICE, atoi(items[2]), COLUMN_PUBLISHER, items[3], COLUMN_ISBN, items[4], -1); g_strfreev(items); /* コピー/移動したアイテムを選択する */ gtk_tree_selection_select_iter(selection, &iter); /* 最初にコピー/移動したアイテムにカーソルを移動する */ if (i == 0) { GtkTreePath *tree_path; tree_path = gtk_tree_model_get_path(model, &iter); gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), tree_path, NULL, FALSE); gtk_tree_path_free(tree_path); } } /* コピー/移動したアイテムにスクロールする */ list = gtk_tree_selection_get_selected_rows(selection, NULL); if (list != NULL) { gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(widget), (GtkTreePath *)list->data, NULL, TRUE, 0.5, 0); g_list_foreach(list, (GFunc)gtk_tree_path_free, NULL); g_list_free(list); } g_strfreev(books); result = TRUE; } } } if (result == TRUE) { if (context->action == GDK_ACTION_MOVE) { delete = TRUE; } gtk_widget_grab_focus(widget); } ERROR_EXIT: gtk_drag_finish(context, result, delete, time_); } void column_clicked(GtkTreeViewColumn *column, gpointer user_data) { GtkTreeView *tree_view = user_data; GtkTreeModel *model; gint column_id; gint cur_id; GtkSortType order; GtkTreeViewColumn *cur_column; if (gtk_widget_is_focus(GTK_WIDGET(tree_view)) == FALSE) { gtk_widget_grab_focus(GTK_WIDGET(tree_view)); } column_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "column_id")); model = gtk_tree_view_get_model(tree_view); /* 現在のソート列と同じときは昇順/降順を反転する、違うときはクリックした列で昇順ソートする */ if (gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(model), &cur_id, &order) == TRUE) { if (cur_id == column_id) { order = (order == GTK_SORT_ASCENDING) ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING; } else { order = GTK_SORT_ASCENDING; } cur_column = gtk_tree_view_get_column(tree_view, cur_id); gtk_tree_view_column_set_sort_indicator(cur_column, FALSE); } else { order = GTK_SORT_ASCENDING; } gtk_tree_view_column_set_sort_order(column, order); gtk_tree_view_column_set_sort_indicator(column, TRUE); gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), column_id, order); } void render_price(GtkTreeViewColumn *column, GtkCellRenderer *renderer, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data) { gint price; gchar buf[32]; gchar tmp[32]; gint len; gint i; gchar *src = tmp; gchar *des = buf; gtk_tree_model_get(model, iter, COLUMN_PRICE, &price, -1); if (price <= 0) { /* 中央揃え表示 */ g_object_set(renderer, "text", "非売品", "xalign", (gfloat)0.5, NULL); } else { /* カンマ区切り、右揃え表示 */ g_snprintf(tmp, sizeof(tmp), "%d", price); len = strlen(tmp); i = len % 3; if (i) { while (i--) { *des++ = *src++; } if (*src) { *des++ = ','; } } while (*src) { i = 3; while (i--) { *des++ = *src++; } if (*src) { *des++ = ','; } } *des = 0; g_object_set(renderer, "text", buf, "xalign", (gfloat)1.0, NULL); } } gint compare_string(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) { gint column_id = GPOINTER_TO_INT(user_data); gint result; gchar *str1; gchar *str2; gtk_tree_model_get(model, a, column_id, &str1, -1); gtk_tree_model_get(model, b, column_id, &str2, -1); result = g_utf8_collate(str1, str2); g_free(str1); g_free(str2); /* 比較結果が同じかつISBN以外のとき、ISBNで比較する */ if (result == 0 && column_id != COLUMN_ISBN) { gchar *isbn1; gchar *isbn2; gtk_tree_model_get(model, a, COLUMN_ISBN, &isbn1, -1); gtk_tree_model_get(model, b, COLUMN_ISBN, &isbn2, -1); result = g_utf8_collate(isbn1, isbn2); g_free(isbn1); g_free(isbn2); } return result; } gint compare_price(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) { gint result; gint price1; gint price2; gtk_tree_model_get(model, a, COLUMN_PRICE, &price1, -1); gtk_tree_model_get(model, b, COLUMN_PRICE, &price2, -1); result = price1 - price2; /* 価格での比較結果が同じときはISBNで比較する */ if (result == 0) { gchar *isbn1; gchar *isbn2; gtk_tree_model_get(model, a, COLUMN_ISBN, &isbn1, -1); gtk_tree_model_get(model, b, COLUMN_ISBN, &isbn2, -1); result = g_utf8_collate(isbn1, isbn2); g_free(isbn1); g_free(isbn2); } return result; } GtkListStore *create_list_store(void) { static int offset = 0; GtkListStore *list_store; GtkTreeIter iter; gint i; list_store = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING); for (i = offset; i < offset + 10 && i < (sizeof(book_info) / sizeof(book_info[0])); i++) { gtk_list_store_append(list_store, &iter); gtk_list_store_set(list_store, &iter, COLUMN_TITLE, book_info[i].title, COLUMN_AUTHOR, book_info[i].author, COLUMN_PRICE, book_info[i].price, COLUMN_PUBLISHER, book_info[i].publisher, COLUMN_ISBN, book_info[i].isbn, -1); } offset = i; /* ソート設定 */ gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(list_store), COLUMN_TITLE, compare_string, GINT_TO_POINTER(COLUMN_TITLE), NULL); gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(list_store), COLUMN_AUTHOR, compare_string, GINT_TO_POINTER(COLUMN_AUTHOR), NULL); gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(list_store), COLUMN_PRICE, compare_price, NULL, NULL); gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(list_store), COLUMN_PUBLISHER, compare_string, GINT_TO_POINTER(COLUMN_PUBLISHER), NULL); gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(list_store), COLUMN_ISBN, compare_string, GINT_TO_POINTER(COLUMN_ISBN), NULL); return list_store; } GtkWidget *create_tree_view(GtkWidget *scrolled_window) { GtkWidget *tree_view; GtkCellRenderer *renderer; GtkTreeViewColumn *column; GtkTreeSelection *selection; GtkListStore *list_store; struct private_data *private_data; private_data = (struct private_data *)g_new(struct private_data, 1); private_data->in_drag_operation = 0; private_data->drag_button = 0; private_data->drag_x = 0; private_data->drag_y = 0; private_data->scroll_timer = 0; private_data->tree_view = tree_view = gtk_tree_view_new(); private_data->scrolled_window = scrolled_window; /* 列作成:題名 */ column = gtk_tree_view_column_new(); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column); gtk_tree_view_column_set_title(column, "題名"); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_column_pack_start(column, renderer, TRUE); gtk_tree_view_column_set_attributes(column, renderer, "text", COLUMN_TITLE, NULL); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_clickable(column, TRUE); g_object_set(renderer, "width", (gint)250, NULL); g_object_set_data(G_OBJECT(column), "column_id", GINT_TO_POINTER(COLUMN_TITLE)); g_signal_connect(G_OBJECT(column), "clicked", G_CALLBACK(column_clicked), tree_view); /* 列作成:著者 */ column = gtk_tree_view_column_new(); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column); gtk_tree_view_column_set_title(column, "著者"); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_column_pack_start(column, renderer, TRUE); gtk_tree_view_column_set_attributes(column, renderer, "text", COLUMN_AUTHOR, NULL); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_clickable(column, TRUE); g_object_set(renderer, "width", (gint)250, NULL); g_object_set_data(G_OBJECT(column), "column_id", GINT_TO_POINTER(COLUMN_AUTHOR)); g_signal_connect(G_OBJECT(column), "clicked", G_CALLBACK(column_clicked), tree_view); /* 列作成:価格 */ column = gtk_tree_view_column_new(); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column); gtk_tree_view_column_set_title(column, "価格"); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_column_pack_start(column, renderer, TRUE); gtk_tree_view_column_set_cell_data_func(column, renderer, render_price, NULL, NULL); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_clickable(column, TRUE); g_object_set(renderer, "width", (gint)90, NULL); g_object_set_data(G_OBJECT(column), "column_id", GINT_TO_POINTER(COLUMN_PRICE)); g_signal_connect(G_OBJECT(column), "clicked", G_CALLBACK(column_clicked), tree_view); /* 列作成:発行 */ column = gtk_tree_view_column_new(); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column); gtk_tree_view_column_set_title(column, "発行"); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_column_pack_start(column, renderer, TRUE); gtk_tree_view_column_set_attributes(column, renderer, "text", COLUMN_PUBLISHER, NULL); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_clickable(column, TRUE); g_object_set(renderer, "width", (gint)150, NULL); g_object_set_data(G_OBJECT(column), "column_id", GINT_TO_POINTER(COLUMN_PUBLISHER)); g_signal_connect(G_OBJECT(column), "clicked", G_CALLBACK(column_clicked), tree_view); /* 列作成:ISBN */ column = gtk_tree_view_column_new(); gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column); gtk_tree_view_column_set_title(column, "ISBN"); renderer = gtk_cell_renderer_text_new(); gtk_tree_view_column_pack_start(column, renderer, TRUE); gtk_tree_view_column_set_attributes(column, renderer, "text", COLUMN_ISBN, NULL); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_clickable(column, TRUE); g_object_set(renderer, "width", (gint)180, NULL); g_object_set_data(G_OBJECT(column), "column_id", GINT_TO_POINTER(COLUMN_ISBN)); g_signal_connect(G_OBJECT(column), "clicked", G_CALLBACK(column_clicked), tree_view); /* 選択モード、スタイル設定 */ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view)); gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree_view), TRUE); /* シグナルハンドラ設定 */ g_signal_connect(G_OBJECT(tree_view), "button-press-event", G_CALLBACK(button_press_event), private_data); g_signal_connect(G_OBJECT(tree_view), "button-release-event", G_CALLBACK(button_release_event), private_data); g_signal_connect(G_OBJECT(tree_view), "motion-notify-event", G_CALLBACK(motion_notify_event), private_data); g_signal_connect(G_OBJECT(tree_view), "leave-notify-event", G_CALLBACK(leave_notify_event), private_data); g_signal_connect(G_OBJECT(tree_view), "drag-begin", G_CALLBACK(drag_begin), private_data); g_signal_connect(G_OBJECT(tree_view), "drag-data-get", G_CALLBACK(drag_data_get), private_data); g_signal_connect(G_OBJECT(tree_view), "drag-data-delete", G_CALLBACK(drag_data_delete), private_data); g_signal_connect(G_OBJECT(tree_view), "drag-end", G_CALLBACK(drag_end), private_data); g_signal_connect(G_OBJECT(tree_view), "drag-motion", G_CALLBACK(drag_motion), private_data); g_signal_connect(G_OBJECT(tree_view), "drag-leave", G_CALLBACK(drag_leave), private_data); g_signal_connect(G_OBJECT(tree_view), "drag-drop", G_CALLBACK(drag_drop), private_data); g_signal_connect(G_OBJECT(tree_view), "drag-data-received", G_CALLBACK(drag_data_received), private_data); gtk_drag_dest_set(tree_view, GTK_DEST_DEFAULT_HIGHLIGHT, drag_targets, sizeof(drag_targets) / sizeof(drag_targets[0]), GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_ASK); /* データ作成 */ list_store = create_list_store(); /* ソート設定 */ gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(list_store), COLUMN_TITLE, GTK_SORT_ASCENDING); column = gtk_tree_view_get_column(GTK_TREE_VIEW(tree_view), COLUMN_TITLE); gtk_tree_view_column_set_sort_order(column, GTK_SORT_ASCENDING); gtk_tree_view_column_set_sort_indicator(column, TRUE); /* データセット */ gtk_tree_view_set_model(GTK_TREE_VIEW(tree_view), GTK_TREE_MODEL(list_store)); g_object_unref(G_OBJECT(list_store)); return tree_view; } int main(int argc, char **argv) { GtkWidget *paned; GtkWidget *scrolled_window; GtkWidget *tree_view; gtk_init(&argc, &argv); main_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(main_window), "GtkTreeView - Drag & Drop"); g_signal_connect(G_OBJECT(main_window), "destroy", G_CALLBACK(gtk_main_quit), NULL); paned = gtk_vpaned_new(); gtk_container_add(GTK_CONTAINER(main_window), paned); gtk_widget_set_size_request(paned, 960, 500); scrolled_window = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_widget_set_size_request(scrolled_window, -1, 250); gtk_paned_add1(GTK_PANED(paned), scrolled_window); tree_view = create_tree_view(scrolled_window); gtk_container_add(GTK_CONTAINER(scrolled_window), tree_view); scrolled_window = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_widget_set_size_request(scrolled_window, -1, 250); gtk_paned_add2(GTK_PANED(paned), scrolled_window); tree_view = create_tree_view(scrolled_window); gtk_container_add(GTK_CONTAINER(scrolled_window), tree_view); gtk_widget_show_all(main_window); gtk_main(); return 0; }