Search in sources :

Example 96 with Deck

use of com.ichi2.libanki.Deck in project Anki-Android by ankidroid.

the class NoteEditor method hasUnsavedChanges.

private boolean hasUnsavedChanges() {
    if (!collectionHasLoaded()) {
        return false;
    }
    // changed note type?
    if (!mAddNote && mCurrentEditedCard != null) {
        final JSONObject newModel = getCurrentlySelectedModel();
        final JSONObject oldModel = mCurrentEditedCard.model();
        if (!newModel.equals(oldModel)) {
            return true;
        }
    }
    // changed deck?
    if (!mAddNote && mCurrentEditedCard != null && mCurrentEditedCard.getDid() != mCurrentDid) {
        return true;
    }
    // changed fields?
    if (mFieldEdited) {
        return true;
    }
    // changed tags?
    return mTagsEdited;
}
Also used : JSONObject(com.ichi2.utils.JSONObject)

Example 97 with Deck

use of com.ichi2.libanki.Deck in project Anki-Android by ankidroid.

the class Collection method addNote.

/**
 * Add a note and cards to the collection. If allowEmpty, at least one card is generated.
 * @param note  The note to add to the collection
 * @param allowEmpty Whether we accept to add it even if it should generate no card. Useful to import note even if buggy
 * @return Number of card added
 */
public int addNote(@NonNull Note note, Models.AllowEmpty allowEmpty) {
    // check we have card models available, then save
    ArrayList<JSONObject> cms = findTemplates(note, allowEmpty);
    // Todo: upstream, we accept to add a not even if it generates no card. Should be ported to ankidroid
    if (cms.isEmpty()) {
        return 0;
    }
    note.flush();
    // deck conf governs which of these are used
    int due = nextID("pos");
    // add cards
    int ncards = 0;
    for (JSONObject template : cms) {
        _newCard(note, template, due);
        ncards += 1;
    }
    return ncards;
}
Also used : JSONObject(com.ichi2.utils.JSONObject) SuppressLint(android.annotation.SuppressLint)

Example 98 with Deck

use of com.ichi2.libanki.Deck in project Anki-Android by ankidroid.

the class Collection method _renderQA.

@RustCleanup("#8951 - Remove FrontSide added to the front")
@NonNull
public HashMap<String, String> _renderQA(long cid, Model model, long did, int ord, String tags, String[] flist, int flags, boolean browser, String qfmt, String afmt) {
    // data is [cid, nid, mid, did, ord, tags, flds, cardFlags]
    // unpack fields and create dict
    Map<String, Pair<Integer, JSONObject>> fmap = Models.fieldMap(model);
    Set<Map.Entry<String, Pair<Integer, JSONObject>>> maps = fmap.entrySet();
    Map<String, String> fields = HashUtil.HashMapInit(maps.size() + 8);
    for (Map.Entry<String, Pair<Integer, JSONObject>> entry : maps) {
        fields.put(entry.getKey(), flist[entry.getValue().first]);
    }
    int cardNum = ord + 1;
    fields.put("Tags", tags.trim());
    fields.put("Type", model.getString("name"));
    fields.put("Deck", mDecks.name(did));
    String baseName = Decks.basename(fields.get("Deck"));
    fields.put("Subdeck", baseName);
    fields.put("CardFlag", _flagNameFromCardFlags(flags));
    JSONObject template;
    if (model.isStd()) {
        template = model.getJSONArray("tmpls").getJSONObject(ord);
    } else {
        template = model.getJSONArray("tmpls").getJSONObject(0);
    }
    fields.put("Card", template.getString("name"));
    fields.put(String.format(Locale.US, "c%d", cardNum), "1");
    // render q & a
    HashMap<String, String> d = HashUtil.HashMapInit(2);
    d.put("id", Long.toString(cid));
    qfmt = TextUtils.isEmpty(qfmt) ? template.getString("qfmt") : qfmt;
    afmt = TextUtils.isEmpty(afmt) ? template.getString("afmt") : afmt;
    for (Pair<String, String> p : new Pair[] { new Pair<>("q", qfmt), new Pair<>("a", afmt) }) {
        String type = p.first;
        String format = p.second;
        if ("q".equals(type)) {
            format = fClozePatternQ.matcher(format).replaceAll(String.format(Locale.US, "{{$1cq-%d:", cardNum));
            format = fClozeTagStart.matcher(format).replaceAll(String.format(Locale.US, "<%%cq:%d:", cardNum));
            fields.put("FrontSide", "");
        } else {
            format = fClozePatternA.matcher(format).replaceAll(String.format(Locale.US, "{{$1ca-%d:", cardNum));
            format = fClozeTagStart.matcher(format).replaceAll(String.format(Locale.US, "<%%ca:%d:", cardNum));
            // the following line differs from libanki // TODO: why?
            // fields.put("FrontSide", mMedia.stripAudio(d.get("q")));
            fields.put("FrontSide", d.get("q"));
        }
        String html;
        try {
            html = ParsedNode.parse_inner(format).render(fields, "q".equals(type), getContext());
        } catch (TemplateError er) {
            Timber.w(er);
            html = er.message(getContext());
        }
        html = ChessFilter.fenToChessboard(html, getContext());
        if (!browser) {
            // browser don't show image. So compiling LaTeX actually remove information.
            html = LaTeX.mungeQA(html, this, model);
        }
        d.put(type, html);
        // empty cloze?
        if ("q".equals(type) && model.isCloze()) {
            if (Models._availClozeOrds(model, flist, false).isEmpty()) {
                String link = String.format("<a href=\"%s\">%s</a>", mContext.getResources().getString(R.string.link_ankiweb_docs_cloze_deletion), "help");
                System.out.println(link);
                d.put("q", mContext.getString(R.string.empty_cloze_warning, link));
            }
        }
    }
    return d;
}
Also used : SuppressLint(android.annotation.SuppressLint) JSONObject(com.ichi2.utils.JSONObject) TemplateError(com.ichi2.libanki.template.TemplateError) Map(java.util.Map) HashMap(java.util.HashMap) Pair(android.util.Pair) RustCleanup(net.ankiweb.rsdroid.RustCleanup) NonNull(androidx.annotation.NonNull)

Example 99 with Deck

use of com.ichi2.libanki.Deck in project Anki-Android by ankidroid.

the class Collection method removeDeckOptionsFromDynamicDecks.

private ArrayList<String> removeDeckOptionsFromDynamicDecks(Runnable notifyProgress) {
    Timber.d("removeDeckOptionsFromDynamicDecks()");
    ArrayList<String> problems = new ArrayList<>(1);
    // #5708 - a dynamic deck should not have "Deck Options"
    notifyProgress.run();
    int fixCount = 0;
    for (long id : mDecks.allDynamicDeckIds()) {
        try {
            if (hasDeckOptions(id)) {
                removeDeckOptions(id);
                fixCount++;
            }
        } catch (NoSuchDeckException e) {
            Timber.w(e, "Unable to find dynamic deck %d", id);
        }
    }
    if (fixCount > 0) {
        mDecks.save();
        problems.add(String.format(Locale.US, "%d dynamic deck(s) had deck options.", fixCount));
    }
    return problems;
}
Also used : ArrayList(java.util.ArrayList) NoSuchDeckException(com.ichi2.libanki.exception.NoSuchDeckException) SuppressLint(android.annotation.SuppressLint)

Example 100 with Deck

use of com.ichi2.libanki.Deck in project Anki-Android by ankidroid.

the class CardContentProvider method update.

@Override
public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    if (!hasReadWritePermission() && shouldEnforceUpdateSecurity(uri)) {
        throwSecurityException("update", uri);
    }
    Collection col = CollectionHelper.getInstance().getCol(mContext);
    if (col == null) {
        throw new IllegalStateException(COL_NULL_ERROR_MSG);
    }
    col.log(getLogMessage("update", uri));
    // Find out what data the user is requesting
    int match = sUriMatcher.match(uri);
    // Number of updated entries (return value)
    int updated = 0;
    switch(match) {
        case NOTES_V2:
        case NOTES:
            throw new IllegalArgumentException("Not possible to update notes directly (only through data URI)");
        case NOTES_ID:
            {
                /* Direct access note details
                 */
                Note currentNote = getNoteFromUri(uri, col);
                // the key of the ContentValues contains the column name
                // the value of the ContentValues contains the row value.
                Set<Map.Entry<String, Object>> valueSet = values.valueSet();
                for (Map.Entry<String, Object> entry : valueSet) {
                    String key = entry.getKey();
                    // when the client does not specify FLDS, then don't update the FLDS
                    if (key.equals(FlashCardsContract.Note.FLDS)) {
                        // Update FLDS
                        Timber.d("CardContentProvider: flds update...");
                        String newFldsEncoded = (String) entry.getValue();
                        String[] flds = Utils.splitFields(newFldsEncoded);
                        // Check that correct number of flds specified
                        if (flds.length != currentNote.getFields().length) {
                            throw new IllegalArgumentException("Incorrect flds argument : " + newFldsEncoded);
                        }
                        // Update the note
                        for (int idx = 0; idx < flds.length; idx++) {
                            currentNote.setField(idx, flds[idx]);
                        }
                        updated++;
                    } else if (key.equals(FlashCardsContract.Note.TAGS)) {
                        // Update tags
                        Timber.d("CardContentProvider: tags update...");
                        Object tags = entry.getValue();
                        if (tags != null) {
                            currentNote.setTagsFromStr(String.valueOf(tags));
                        }
                        updated++;
                    } else {
                        // Unsupported column
                        throw new IllegalArgumentException("Unsupported column: " + key);
                    }
                }
                Timber.d("CardContentProvider: Saving note...");
                currentNote.flush();
                break;
            }
        case NOTES_ID_CARDS:
            // TODO: To be implemented
            throw new UnsupportedOperationException("Not yet implemented");
        // break;
        case NOTES_ID_CARDS_ORD:
            {
                Card currentCard = getCardFromUri(uri, col);
                boolean isDeckUpdate = false;
                long did = Decks.NOT_FOUND_DECK_ID;
                // the key of the ContentValues contains the column name
                // the value of the ContentValues contains the row value.
                Set<Map.Entry<String, Object>> valueSet = values.valueSet();
                for (Map.Entry<String, Object> entry : valueSet) {
                    // Only updates on deck id is supported
                    String key = entry.getKey();
                    isDeckUpdate = key.equals(FlashCardsContract.Card.DECK_ID);
                    did = values.getAsLong(key);
                }
                if (col.getDecks().isDyn(did)) {
                    throw new IllegalArgumentException("Cards cannot be moved to a filtered deck");
                }
                /* now update the card
                 */
                if ((isDeckUpdate) && (did >= 0)) {
                    Timber.d("CardContentProvider: Moving card to other deck...");
                    col.getDecks().flush();
                    currentCard.setDid(did);
                    currentCard.flush();
                    col.save();
                    updated++;
                } else {
                    // User tries an operation that is not (yet?) supported.
                    throw new IllegalArgumentException("Currently only updates of decks are supported");
                }
                break;
            }
        case MODELS:
            throw new IllegalArgumentException("Cannot update models in bulk");
        case MODELS_ID:
            // Get the input parameters
            String newModelName = values.getAsString(FlashCardsContract.Model.NAME);
            String newCss = values.getAsString(FlashCardsContract.Model.CSS);
            String newDid = values.getAsString(FlashCardsContract.Model.DECK_ID);
            String newFieldList = values.getAsString(FlashCardsContract.Model.FIELD_NAMES);
            if (newFieldList != null) {
                // Changing the field names would require a full-sync
                throw new IllegalArgumentException("Field names cannot be changed via provider");
            }
            Integer newSortf = values.getAsInteger(FlashCardsContract.Model.SORT_FIELD_INDEX);
            Integer newType = values.getAsInteger(FlashCardsContract.Model.TYPE);
            String newLatexPost = values.getAsString(FlashCardsContract.Model.LATEX_POST);
            String newLatexPre = values.getAsString(FlashCardsContract.Model.LATEX_PRE);
            // Get the original note JSON
            Model model = col.getModels().get(getModelIdFromUri(uri, col));
            try {
                // Update model name and/or css
                if (newModelName != null) {
                    model.put("name", newModelName);
                    updated++;
                }
                if (newCss != null) {
                    model.put("css", newCss);
                    updated++;
                }
                if (newDid != null) {
                    if (col.getDecks().isDyn(Long.parseLong(newDid))) {
                        throw new IllegalArgumentException("Cannot set a filtered deck as default deck for a model");
                    }
                    model.put("did", newDid);
                    updated++;
                }
                if (newSortf != null) {
                    model.put("sortf", newSortf);
                    updated++;
                }
                if (newType != null) {
                    model.put("type", newType);
                    updated++;
                }
                if (newLatexPost != null) {
                    model.put("latexPost", newLatexPost);
                    updated++;
                }
                if (newLatexPre != null) {
                    model.put("latexPre", newLatexPre);
                    updated++;
                }
                col.getModels().save(model);
                col.save();
            } catch (JSONException e) {
                Timber.e(e, "JSONException updating model");
            }
            break;
        case MODELS_ID_TEMPLATES:
            throw new IllegalArgumentException("Cannot update templates in bulk");
        case MODELS_ID_TEMPLATES_ID:
            Long mid = values.getAsLong(CardTemplate.MODEL_ID);
            Integer ord = values.getAsInteger(CardTemplate.ORD);
            String name = values.getAsString(CardTemplate.NAME);
            String qfmt = values.getAsString(CardTemplate.QUESTION_FORMAT);
            String afmt = values.getAsString(CardTemplate.ANSWER_FORMAT);
            String bqfmt = values.getAsString(CardTemplate.BROWSER_QUESTION_FORMAT);
            String bafmt = values.getAsString(CardTemplate.BROWSER_ANSWER_FORMAT);
            // Throw exception if read-only fields are included
            if (mid != null || ord != null) {
                throw new IllegalArgumentException("Updates to mid or ord are not allowed");
            }
            // Update the model
            try {
                int templateOrd = Integer.parseInt(uri.getLastPathSegment());
                Model existingModel = col.getModels().get(getModelIdFromUri(uri, col));
                JSONArray templates = existingModel.getJSONArray("tmpls");
                JSONObject template = templates.getJSONObject(templateOrd);
                if (name != null) {
                    template.put("name", name);
                    updated++;
                }
                if (qfmt != null) {
                    template.put("qfmt", qfmt);
                    updated++;
                }
                if (afmt != null) {
                    template.put("afmt", afmt);
                    updated++;
                }
                if (bqfmt != null) {
                    template.put("bqfmt", bqfmt);
                    updated++;
                }
                if (bafmt != null) {
                    template.put("bafmt", bafmt);
                    updated++;
                }
                // Save the model
                templates.put(templateOrd, template);
                existingModel.put("tmpls", templates);
                col.getModels().save(existingModel, true);
                col.save();
            } catch (JSONException e) {
                throw new IllegalArgumentException("Model is malformed", e);
            }
            break;
        case SCHEDULE:
            {
                Set<Map.Entry<String, Object>> valueSet = values.valueSet();
                int cardOrd = -1;
                long noteID = -1;
                int ease = -1;
                long timeTaken = -1;
                int bury = -1;
                int suspend = -1;
                for (Map.Entry<String, Object> entry : valueSet) {
                    String key = entry.getKey();
                    switch(key) {
                        case FlashCardsContract.ReviewInfo.NOTE_ID:
                            noteID = values.getAsLong(key);
                            break;
                        case FlashCardsContract.ReviewInfo.CARD_ORD:
                            cardOrd = values.getAsInteger(key);
                            break;
                        case FlashCardsContract.ReviewInfo.EASE:
                            ease = values.getAsInteger(key);
                            break;
                        case FlashCardsContract.ReviewInfo.TIME_TAKEN:
                            timeTaken = values.getAsLong(key);
                            break;
                        case FlashCardsContract.ReviewInfo.BURY:
                            bury = values.getAsInteger(key);
                            break;
                        case FlashCardsContract.ReviewInfo.SUSPEND:
                            suspend = values.getAsInteger(key);
                            break;
                    }
                }
                if (cardOrd != -1 && noteID != -1) {
                    Card cardToAnswer = getCard(noteID, cardOrd, col);
                    if (cardToAnswer != null) {
                        if (bury == 1) {
                            // bury card
                            buryOrSuspendCard(col, col.getSched(), cardToAnswer, true);
                        } else if (suspend == 1) {
                            // suspend card
                            buryOrSuspendCard(col, col.getSched(), cardToAnswer, false);
                        } else {
                            answerCard(col, col.getSched(), cardToAnswer, ease, timeTaken);
                        }
                        updated++;
                    } else {
                        Timber.e("Requested card with noteId %d and cardOrd %d was not found. Either the provided " + "noteId/cardOrd were wrong or the card has been deleted in the meantime.", noteID, cardOrd);
                    }
                }
                break;
            }
        case DECKS:
            throw new IllegalArgumentException("Can't update decks in bulk");
        case DECKS_ID:
            // TODO: be sure to throw exception if change to the dyn value of a deck is requested
            throw new UnsupportedOperationException("Not yet implemented");
        case DECK_SELECTED:
            {
                Set<Map.Entry<String, Object>> valueSet = values.valueSet();
                for (Map.Entry<String, Object> entry : valueSet) {
                    String key = entry.getKey();
                    if (key.equals(FlashCardsContract.Deck.DECK_ID)) {
                        long deckId = values.getAsLong(key);
                        if (selectDeckWithCheck(col, deckId)) {
                            updated++;
                        }
                    }
                }
                col.save();
                break;
            }
        default:
            // Unknown URI type
            throw new IllegalArgumentException("uri " + uri + " is not supported");
    }
    return updated;
}
Also used : Set(java.util.Set) JSONArray(com.ichi2.utils.JSONArray) JSONException(com.ichi2.utils.JSONException) Card(com.ichi2.libanki.Card) JSONObject(com.ichi2.utils.JSONObject) Note(com.ichi2.libanki.Note) Model(com.ichi2.libanki.Model) Collection(com.ichi2.libanki.Collection) JSONObject(com.ichi2.utils.JSONObject) Map(java.util.Map) MimeTypeMap(android.webkit.MimeTypeMap)

Aggregations

Deck (com.ichi2.libanki.Deck)100 Collection (com.ichi2.libanki.Collection)97 JSONObject (com.ichi2.utils.JSONObject)88 Test (org.junit.Test)80 JSONArray (com.ichi2.utils.JSONArray)55 Card (com.ichi2.libanki.Card)53 Note (com.ichi2.libanki.Note)50 ArrayList (java.util.ArrayList)47 RobolectricTest (com.ichi2.anki.RobolectricTest)44 DeckConfig (com.ichi2.libanki.DeckConfig)37 JSONException (com.ichi2.utils.JSONException)34 NonNull (androidx.annotation.NonNull)30 HashMap (java.util.HashMap)29 Model (com.ichi2.libanki.Model)23 Map (java.util.Map)22 Intent (android.content.Intent)21 Resources (android.content.res.Resources)18 TextView (android.widget.TextView)18 SharedPreferences (android.content.SharedPreferences)17 Cursor (android.database.Cursor)17