package com.orgzly.android.provider.actions; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import com.orgzly.android.NotePosition; import com.orgzly.android.provider.GenericDatabaseUtils; import com.orgzly.android.provider.DatabaseUtils; import com.orgzly.android.provider.ProviderContract; import com.orgzly.android.provider.models.DbNote; import com.orgzly.android.provider.models.DbNoteAncestor; import com.orgzly.android.ui.Place; public class PasteNotesAction implements Action { private static final String TAG = PasteNotesAction.class.getName(); private Place place; private long targetNoteId; private long batchId; public PasteNotesAction(ContentValues values) { place = Place.valueOf(values.getAsString(ProviderContract.Paste.Param.SPOT)); targetNoteId = values.getAsLong(ProviderContract.Paste.Param.NOTE_ID); batchId = values.getAsLong(ProviderContract.Paste.Param.BATCH_ID); } @Override public int run(SQLiteDatabase db) { long batchMinLft; long batchMaxRgt; long batchMinLevel; long foldedUnder = 0; Cursor cursor = db.query( DbNote.TABLE, new String[] { "min(" + DbNote.Column.LFT + ")", "max(" + DbNote.Column.RGT + ")", "min(" + DbNote.Column.LEVEL + ")" }, DbNote.Column.IS_CUT + " = " + batchId, null, null, null, null); try { if (cursor.moveToFirst()) { batchMinLft = cursor.getLong(0); batchMaxRgt = cursor.getLong(1); batchMinLevel = cursor.getLong(2); } else { return 0; } } finally { cursor.close(); } NotePosition targetNotePosition = DbNote.getPosition(db, targetNoteId); long pastedLft, pastedLevel, pastedParentId; /* If target note is hidden, hide pasted under the same note. */ if (targetNotePosition.getFoldedUnderId() != 0) { foldedUnder = targetNotePosition.getFoldedUnderId(); } switch (place) { case ABOVE: pastedLft = targetNotePosition.getLft(); pastedLevel = targetNotePosition.getLevel(); pastedParentId = targetNotePosition.getParentId(); break; case UNDER: NotePosition lastHighestLevelDescendant = getLastHighestLevelDescendant(db, targetNotePosition); if (lastHighestLevelDescendant != null) { /* Insert batch after last descendant with highest level. */ pastedLft = lastHighestLevelDescendant.getRgt() + 1; pastedLevel = lastHighestLevelDescendant.getLevel(); } else { /* Insert batch just under the target note. */ pastedLft = targetNotePosition.getLft() + 1; pastedLevel = targetNotePosition.getLevel() + 1; } if (targetNotePosition.isFolded()) { foldedUnder = targetNoteId; } pastedParentId = targetNoteId; break; case BELOW: pastedLft = targetNotePosition.getRgt() + 1; pastedLevel = targetNotePosition.getLevel(); pastedParentId = targetNotePosition.getParentId(); break; default: throw new IllegalArgumentException("Unsupported place for paste: " + place); } int positionsRequired = (int) (batchMaxRgt - batchMinLft + 1); long positionOffset = pastedLft - batchMinLft; long levelOffset = pastedLevel - batchMinLevel; /* * Make space for new notes incrementing lft and rgt. * FIXME: This could be slow. */ String bookSelection = DatabaseUtils.whereUncutBookNotes(targetNotePosition.getBookId()); GenericDatabaseUtils.incrementFields( db, DbNote.TABLE, bookSelection + " AND " + DbNote.Column.LFT + " >= " + pastedLft, positionsRequired, ProviderContract.Notes.UpdateParam.LFT); GenericDatabaseUtils.incrementFields( db, DbNote.TABLE, "(" + bookSelection + " AND " + DbNote.Column.RGT + " >= " + pastedLft + ") OR " + DbNote.Column.LEVEL + " = 0", positionsRequired, ProviderContract.Notes.UpdateParam.RGT); /* Make sure batch has no no FOLDED_UNDER_ID IDs which do not belong to the batch itself. */ db.execSQL("UPDATE " + DbNote.TABLE + " SET " + DbNote.Column.FOLDED_UNDER_ID + " = 0 WHERE " + DbNote.Column.IS_CUT + " = " + batchId + " AND " + DbNote.Column.FOLDED_UNDER_ID + " NOT IN (SELECT " + DbNote.Column._ID + " FROM " + DbNote.TABLE + " WHERE " + DbNote.Column.IS_CUT + " = " + batchId + ")"); /* Mark batch as folded. */ if (foldedUnder != 0) { ContentValues values = new ContentValues(); values.put(DbNote.Column.FOLDED_UNDER_ID, foldedUnder); String where = DbNote.Column.IS_CUT + " = " + batchId + " AND " + DbNote.Column.FOLDED_UNDER_ID + " = 0"; db.update(DbNote.TABLE, values, where, null); } /* Update parent of the root of the batch. */ ContentValues values = new ContentValues(); values.put(DbNote.Column.PARENT_ID, pastedParentId); db.update(DbNote.TABLE, values, DbNote.Column.IS_CUT + " = " + batchId + " AND " + DbNote.Column.LFT + " = " + batchMinLft, null); /* Move batch to the new position. */ String set = DbNote.Column.LFT + " = " + DbNote.Column.LFT + " + " + positionOffset + ", " + DbNote.Column.RGT + " = " + DbNote.Column.RGT + " + " + positionOffset + ", " + DbNote.Column.LEVEL + " = " + DbNote.Column.LEVEL + " + " + levelOffset + ", " + DbNote.Column.BOOK_ID + "= " + targetNotePosition.getBookId(); String sql = "UPDATE " + DbNote.TABLE + " SET " + set + " WHERE " + DbNote.Column.IS_CUT + " = " + batchId; db.execSQL(sql); /* Insert ancestors for all notes of the batch. */ db.execSQL("INSERT INTO " + DbNoteAncestor.TABLE + " (" + DbNoteAncestor.Column.BOOK_ID + ", " + DbNoteAncestor.Column.NOTE_ID + ", " + DbNoteAncestor.Column.ANCESTOR_NOTE_ID + ") " + "SELECT n." + DbNote.Column.BOOK_ID + ", n." + DbNote.Column._ID + ", a." + DbNote.Column._ID + " FROM " + DbNote.TABLE + " n " + " JOIN " + DbNote.TABLE + " a ON (n." + DbNote.Column.BOOK_ID + " = a." + DbNote.Column.BOOK_ID + " AND a." + DbNote.Column.LFT + " < n." + DbNote.Column.LFT + " AND n." + DbNote.Column.RGT + " < a." + DbNote.Column.RGT + ") " + "WHERE n." + DbNote.Column.IS_CUT + " = " + batchId + " AND " + "a." + DbNote.Columns.LEVEL + " > 0"); /* Make the batch visible. */ db.execSQL("UPDATE " + DbNote.TABLE + " SET " + DbNote.Column.IS_CUT + " = 0 WHERE " + DbNote.Column.IS_CUT + " = " + batchId); /* Update number of descendants for ancestors and the note itself. */ String where = DatabaseUtils.whereAncestorsAndNote(targetNotePosition.getBookId(), targetNoteId); DatabaseUtils.updateDescendantsCount(db, where); /* Delete other batches. */ db.execSQL("DELETE FROM " + DbNote.TABLE + " WHERE " + DbNote.Column.IS_CUT + " != 0"); DatabaseUtils.updateBookMtime(db, targetNotePosition.getBookId()); return 0; } private NotePosition getLastHighestLevelDescendant(SQLiteDatabase db, NotePosition note) { NotePosition position = null; Cursor cursor = db.query( DbNote.TABLE, DbNote.POSITION_PROJECTION, DatabaseUtils.whereDescendants(note.getBookId(), note.getLft(), note.getRgt()), null, null, null, DbNote.Column.LEVEL + ", " + DbNote.Column.LFT + " DESC"); try { if (cursor.moveToFirst()) { position = DbNote.positionFromCursor(cursor); } } finally { cursor.close(); } return position; } @Override public void undo() { } }