.'
- );
- }
- }
- addAttr(el, name, JSON.stringify(value));
- // #6887 firefox doesn't update muted state if set via attribute
- // even immediately after element creation
- if (!el.component &&
- name === 'muted' &&
- platformMustUseProp(el.tag, el.attrsMap.type, name)) {
- addProp(el, name, 'true');
- }
- }
- }
-}
-
-function checkInFor (el) {
- var parent = el;
- while (parent) {
- if (parent.for !== undefined) {
- return true
- }
- parent = parent.parent;
- }
- return false
-}
-
-function parseModifiers (name) {
- var match = name.match(modifierRE);
- if (match) {
- var ret = {};
- match.forEach(function (m) { ret[m.slice(1)] = true; });
- return ret
- }
-}
-
-function makeAttrsMap (attrs) {
- var map = {};
- for (var i = 0, l = attrs.length; i < l; i++) {
- if (
- "development" !== 'production' &&
- map[attrs[i].name] && !isIE && !isEdge
- ) {
- warn$2('duplicate attribute: ' + attrs[i].name);
- }
- map[attrs[i].name] = attrs[i].value;
- }
- return map
-}
-
-// for script (e.g. type="x/template") or style, do not decode content
-function isTextTag (el) {
- return el.tag === 'script' || el.tag === 'style'
-}
-
-function isForbiddenTag (el) {
- return (
- el.tag === 'style' ||
- (el.tag === 'script' && (
- !el.attrsMap.type ||
- el.attrsMap.type === 'text/javascript'
- ))
- )
-}
-
-var ieNSBug = /^xmlns:NS\d+/;
-var ieNSPrefix = /^NS\d+:/;
-
-/* istanbul ignore next */
-function guardIESVGBug (attrs) {
- var res = [];
- for (var i = 0; i < attrs.length; i++) {
- var attr = attrs[i];
- if (!ieNSBug.test(attr.name)) {
- attr.name = attr.name.replace(ieNSPrefix, '');
- res.push(attr);
- }
- }
- return res
-}
-
-function checkForAliasModel (el, value) {
- var _el = el;
- while (_el) {
- if (_el.for && _el.alias === value) {
- warn$2(
- "<" + (el.tag) + " v-model=\"" + value + "\">: " +
- "You are binding v-model directly to a v-for iteration alias. " +
- "This will not be able to modify the v-for source array because " +
- "writing to the alias is like modifying a function local variable. " +
- "Consider using an array of objects and use v-model on an object property instead."
- );
- }
- _el = _el.parent;
- }
-}
-
-/* */
-
-/**
- * Expand input[v-model] with dyanmic type bindings into v-if-else chains
- * Turn this:
- *
- * into this:
- *
- *
- *
- */
-
-function preTransformNode (el, options) {
- if (el.tag === 'input') {
- var map = el.attrsMap;
- if (!map['v-model']) {
- return
- }
-
- var typeBinding;
- if (map[':type'] || map['v-bind:type']) {
- typeBinding = getBindingAttr(el, 'type');
- }
- if (!map.type && !typeBinding && map['v-bind']) {
- typeBinding = "(" + (map['v-bind']) + ").type";
- }
-
- if (typeBinding) {
- var ifCondition = getAndRemoveAttr(el, 'v-if', true);
- var ifConditionExtra = ifCondition ? ("&&(" + ifCondition + ")") : "";
- var hasElse = getAndRemoveAttr(el, 'v-else', true) != null;
- var elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true);
- // 1. checkbox
- var branch0 = cloneASTElement(el);
- // process for on the main node
- processFor(branch0);
- addRawAttr(branch0, 'type', 'checkbox');
- processElement(branch0, options);
- branch0.processed = true; // prevent it from double-processed
- branch0.if = "(" + typeBinding + ")==='checkbox'" + ifConditionExtra;
- addIfCondition(branch0, {
- exp: branch0.if,
- block: branch0
- });
- // 2. add radio else-if condition
- var branch1 = cloneASTElement(el);
- getAndRemoveAttr(branch1, 'v-for', true);
- addRawAttr(branch1, 'type', 'radio');
- processElement(branch1, options);
- addIfCondition(branch0, {
- exp: "(" + typeBinding + ")==='radio'" + ifConditionExtra,
- block: branch1
- });
- // 3. other
- var branch2 = cloneASTElement(el);
- getAndRemoveAttr(branch2, 'v-for', true);
- addRawAttr(branch2, ':type', typeBinding);
- processElement(branch2, options);
- addIfCondition(branch0, {
- exp: ifCondition,
- block: branch2
- });
-
- if (hasElse) {
- branch0.else = true;
- } else if (elseIfCondition) {
- branch0.elseif = elseIfCondition;
- }
-
- return branch0
- }
- }
-}
-
-function cloneASTElement (el) {
- return createASTElement(el.tag, el.attrsList.slice(), el.parent)
-}
-
-var model$2 = {
- preTransformNode: preTransformNode
-}
-
-var modules$1 = [
- klass$1,
- style$1,
- model$2
-]
-
-/* */
-
-function text (el, dir) {
- if (dir.value) {
- addProp(el, 'textContent', ("_s(" + (dir.value) + ")"));
- }
-}
-
-/* */
-
-function html (el, dir) {
- if (dir.value) {
- addProp(el, 'innerHTML', ("_s(" + (dir.value) + ")"));
- }
-}
-
-var directives$1 = {
- model: model,
- text: text,
- html: html
-}
-
-/* */
-
-var baseOptions = {
- expectHTML: true,
- modules: modules$1,
- directives: directives$1,
- isPreTag: isPreTag,
- isUnaryTag: isUnaryTag,
- mustUseProp: mustUseProp,
- canBeLeftOpenTag: canBeLeftOpenTag,
- isReservedTag: isReservedTag,
- getTagNamespace: getTagNamespace,
- staticKeys: genStaticKeys(modules$1)
-};
-
-/* */
-
-var isStaticKey;
-var isPlatformReservedTag;
-
-var genStaticKeysCached = cached(genStaticKeys$1);
-
-/**
- * Goal of the optimizer: walk the generated template AST tree
- * and detect sub-trees that are purely static, i.e. parts of
- * the DOM that never needs to change.
- *
- * Once we detect these sub-trees, we can:
- *
- * 1. Hoist them into constants, so that we no longer need to
- * create fresh nodes for them on each re-render;
- * 2. Completely skip them in the patching process.
- */
-function optimize (root, options) {
- if (!root) { return }
- isStaticKey = genStaticKeysCached(options.staticKeys || '');
- isPlatformReservedTag = options.isReservedTag || no;
- // first pass: mark all non-static nodes.
- markStatic$1(root);
- // second pass: mark static roots.
- markStaticRoots(root, false);
-}
-
-function genStaticKeys$1 (keys) {
- return makeMap(
- 'type,tag,attrsList,attrsMap,plain,parent,children,attrs' +
- (keys ? ',' + keys : '')
- )
-}
-
-function markStatic$1 (node) {
- node.static = isStatic(node);
- if (node.type === 1) {
- // do not make component slot content static. this avoids
- // 1. components not able to mutate slot nodes
- // 2. static slot content fails for hot-reloading
- if (
- !isPlatformReservedTag(node.tag) &&
- node.tag !== 'slot' &&
- node.attrsMap['inline-template'] == null
- ) {
- return
- }
- for (var i = 0, l = node.children.length; i < l; i++) {
- var child = node.children[i];
- markStatic$1(child);
- if (!child.static) {
- node.static = false;
- }
- }
- if (node.ifConditions) {
- for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
- var block = node.ifConditions[i$1].block;
- markStatic$1(block);
- if (!block.static) {
- node.static = false;
- }
- }
- }
- }
-}
-
-function markStaticRoots (node, isInFor) {
- if (node.type === 1) {
- if (node.static || node.once) {
- node.staticInFor = isInFor;
- }
- // For a node to qualify as a static root, it should have children that
- // are not just static text. Otherwise the cost of hoisting out will
- // outweigh the benefits and it's better off to just always render it fresh.
- if (node.static && node.children.length && !(
- node.children.length === 1 &&
- node.children[0].type === 3
- )) {
- node.staticRoot = true;
- return
- } else {
- node.staticRoot = false;
- }
- if (node.children) {
- for (var i = 0, l = node.children.length; i < l; i++) {
- markStaticRoots(node.children[i], isInFor || !!node.for);
- }
- }
- if (node.ifConditions) {
- for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
- markStaticRoots(node.ifConditions[i$1].block, isInFor);
- }
- }
- }
-}
-
-function isStatic (node) {
- if (node.type === 2) { // expression
- return false
- }
- if (node.type === 3) { // text
- return true
- }
- return !!(node.pre || (
- !node.hasBindings && // no dynamic bindings
- !node.if && !node.for && // not v-if or v-for or v-else
- !isBuiltInTag(node.tag) && // not a built-in
- isPlatformReservedTag(node.tag) && // not a component
- !isDirectChildOfTemplateFor(node) &&
- Object.keys(node).every(isStaticKey)
- ))
-}
-
-function isDirectChildOfTemplateFor (node) {
- while (node.parent) {
- node = node.parent;
- if (node.tag !== 'template') {
- return false
- }
- if (node.for) {
- return true
- }
- }
- return false
-}
-
-/* */
-
-var fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/;
-var simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/;
-
-// KeyboardEvent.keyCode aliases
-var keyCodes = {
- esc: 27,
- tab: 9,
- enter: 13,
- space: 32,
- up: 38,
- left: 37,
- right: 39,
- down: 40,
- 'delete': [8, 46]
-};
-
-// KeyboardEvent.key aliases
-var keyNames = {
- esc: 'Escape',
- tab: 'Tab',
- enter: 'Enter',
- space: ' ',
- // #7806: IE11 uses key names without `Arrow` prefix for arrow keys.
- up: ['Up', 'ArrowUp'],
- left: ['Left', 'ArrowLeft'],
- right: ['Right', 'ArrowRight'],
- down: ['Down', 'ArrowDown'],
- 'delete': ['Backspace', 'Delete']
-};
-
-// #4868: modifiers that prevent the execution of the listener
-// need to explicitly return null so that we can determine whether to remove
-// the listener for .once
-var genGuard = function (condition) { return ("if(" + condition + ")return null;"); };
-
-var modifierCode = {
- stop: '$event.stopPropagation();',
- prevent: '$event.preventDefault();',
- self: genGuard("$event.target !== $event.currentTarget"),
- ctrl: genGuard("!$event.ctrlKey"),
- shift: genGuard("!$event.shiftKey"),
- alt: genGuard("!$event.altKey"),
- meta: genGuard("!$event.metaKey"),
- left: genGuard("'button' in $event && $event.button !== 0"),
- middle: genGuard("'button' in $event && $event.button !== 1"),
- right: genGuard("'button' in $event && $event.button !== 2")
-};
-
-function genHandlers (
- events,
- isNative,
- warn
-) {
- var res = isNative ? 'nativeOn:{' : 'on:{';
- for (var name in events) {
- res += "\"" + name + "\":" + (genHandler(name, events[name])) + ",";
- }
- return res.slice(0, -1) + '}'
-}
-
-function genHandler (
- name,
- handler
-) {
- if (!handler) {
- return 'function(){}'
- }
-
- if (Array.isArray(handler)) {
- return ("[" + (handler.map(function (handler) { return genHandler(name, handler); }).join(',')) + "]")
- }
-
- var isMethodPath = simplePathRE.test(handler.value);
- var isFunctionExpression = fnExpRE.test(handler.value);
-
- if (!handler.modifiers) {
- if (isMethodPath || isFunctionExpression) {
- return handler.value
- }
- /* istanbul ignore if */
- return ("function($event){" + (handler.value) + "}") // inline statement
- } else {
- var code = '';
- var genModifierCode = '';
- var keys = [];
- for (var key in handler.modifiers) {
- if (modifierCode[key]) {
- genModifierCode += modifierCode[key];
- // left/right
- if (keyCodes[key]) {
- keys.push(key);
- }
- } else if (key === 'exact') {
- var modifiers = (handler.modifiers);
- genModifierCode += genGuard(
- ['ctrl', 'shift', 'alt', 'meta']
- .filter(function (keyModifier) { return !modifiers[keyModifier]; })
- .map(function (keyModifier) { return ("$event." + keyModifier + "Key"); })
- .join('||')
- );
- } else {
- keys.push(key);
- }
- }
- if (keys.length) {
- code += genKeyFilter(keys);
- }
- // Make sure modifiers like prevent and stop get executed after key filtering
- if (genModifierCode) {
- code += genModifierCode;
- }
- var handlerCode = isMethodPath
- ? ("return " + (handler.value) + "($event)")
- : isFunctionExpression
- ? ("return (" + (handler.value) + ")($event)")
- : handler.value;
- /* istanbul ignore if */
- return ("function($event){" + code + handlerCode + "}")
- }
-}
-
-function genKeyFilter (keys) {
- return ("if(!('button' in $event)&&" + (keys.map(genFilterCode).join('&&')) + ")return null;")
-}
-
-function genFilterCode (key) {
- var keyVal = parseInt(key, 10);
- if (keyVal) {
- return ("$event.keyCode!==" + keyVal)
- }
- var keyCode = keyCodes[key];
- var keyName = keyNames[key];
- return (
- "_k($event.keyCode," +
- (JSON.stringify(key)) + "," +
- (JSON.stringify(keyCode)) + "," +
- "$event.key," +
- "" + (JSON.stringify(keyName)) +
- ")"
- )
-}
-
-/* */
-
-function on (el, dir) {
- if ("development" !== 'production' && dir.modifiers) {
- warn("v-on without argument does not support modifiers.");
- }
- el.wrapListeners = function (code) { return ("_g(" + code + "," + (dir.value) + ")"); };
-}
-
-/* */
-
-function bind$1 (el, dir) {
- el.wrapData = function (code) {
- return ("_b(" + code + ",'" + (el.tag) + "'," + (dir.value) + "," + (dir.modifiers && dir.modifiers.prop ? 'true' : 'false') + (dir.modifiers && dir.modifiers.sync ? ',true' : '') + ")")
- };
-}
-
-/* */
-
-var baseDirectives = {
- on: on,
- bind: bind$1,
- cloak: noop
-}
-
-/* */
-
-var CodegenState = function CodegenState (options) {
- this.options = options;
- this.warn = options.warn || baseWarn;
- this.transforms = pluckModuleFunction(options.modules, 'transformCode');
- this.dataGenFns = pluckModuleFunction(options.modules, 'genData');
- this.directives = extend(extend({}, baseDirectives), options.directives);
- var isReservedTag = options.isReservedTag || no;
- this.maybeComponent = function (el) { return !isReservedTag(el.tag); };
- this.onceId = 0;
- this.staticRenderFns = [];
-};
-
-
-
-function generate (
- ast,
- options
-) {
- var state = new CodegenState(options);
- var code = ast ? genElement(ast, state) : '_c("div")';
- return {
- render: ("with(this){return " + code + "}"),
- staticRenderFns: state.staticRenderFns
- }
-}
-
-function genElement (el, state) {
- if (el.staticRoot && !el.staticProcessed) {
- return genStatic(el, state)
- } else if (el.once && !el.onceProcessed) {
- return genOnce(el, state)
- } else if (el.for && !el.forProcessed) {
- return genFor(el, state)
- } else if (el.if && !el.ifProcessed) {
- return genIf(el, state)
- } else if (el.tag === 'template' && !el.slotTarget) {
- return genChildren(el, state) || 'void 0'
- } else if (el.tag === 'slot') {
- return genSlot(el, state)
- } else {
- // component or element
- var code;
- if (el.component) {
- code = genComponent(el.component, el, state);
- } else {
- var data = el.plain ? undefined : genData$2(el, state);
-
- var children = el.inlineTemplate ? null : genChildren(el, state, true);
- code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
- }
- // module transforms
- for (var i = 0; i < state.transforms.length; i++) {
- code = state.transforms[i](el, code);
- }
- return code
- }
-}
-
-// hoist static sub-trees out
-function genStatic (el, state) {
- el.staticProcessed = true;
- state.staticRenderFns.push(("with(this){return " + (genElement(el, state)) + "}"));
- return ("_m(" + (state.staticRenderFns.length - 1) + (el.staticInFor ? ',true' : '') + ")")
-}
-
-// v-once
-function genOnce (el, state) {
- el.onceProcessed = true;
- if (el.if && !el.ifProcessed) {
- return genIf(el, state)
- } else if (el.staticInFor) {
- var key = '';
- var parent = el.parent;
- while (parent) {
- if (parent.for) {
- key = parent.key;
- break
- }
- parent = parent.parent;
- }
- if (!key) {
- "development" !== 'production' && state.warn(
- "v-once can only be used inside v-for that is keyed. "
- );
- return genElement(el, state)
- }
- return ("_o(" + (genElement(el, state)) + "," + (state.onceId++) + "," + key + ")")
- } else {
- return genStatic(el, state)
- }
-}
-
-function genIf (
- el,
- state,
- altGen,
- altEmpty
-) {
- el.ifProcessed = true; // avoid recursion
- return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
-}
-
-function genIfConditions (
- conditions,
- state,
- altGen,
- altEmpty
-) {
- if (!conditions.length) {
- return altEmpty || '_e()'
- }
-
- var condition = conditions.shift();
- if (condition.exp) {
- return ("(" + (condition.exp) + ")?" + (genTernaryExp(condition.block)) + ":" + (genIfConditions(conditions, state, altGen, altEmpty)))
- } else {
- return ("" + (genTernaryExp(condition.block)))
- }
-
- // v-if with v-once should generate code like (a)?_m(0):_m(1)
- function genTernaryExp (el) {
- return altGen
- ? altGen(el, state)
- : el.once
- ? genOnce(el, state)
- : genElement(el, state)
- }
-}
-
-function genFor (
- el,
- state,
- altGen,
- altHelper
-) {
- var exp = el.for;
- var alias = el.alias;
- var iterator1 = el.iterator1 ? ("," + (el.iterator1)) : '';
- var iterator2 = el.iterator2 ? ("," + (el.iterator2)) : '';
-
- if ("development" !== 'production' &&
- state.maybeComponent(el) &&
- el.tag !== 'slot' &&
- el.tag !== 'template' &&
- !el.key
- ) {
- state.warn(
- "<" + (el.tag) + " v-for=\"" + alias + " in " + exp + "\">: component lists rendered with " +
- "v-for should have explicit keys. " +
- "See https://vuejs.org/guide/list.html#key for more info.",
- true /* tip */
- );
- }
-
- el.forProcessed = true; // avoid recursion
- return (altHelper || '_l') + "((" + exp + ")," +
- "function(" + alias + iterator1 + iterator2 + "){" +
- "return " + ((altGen || genElement)(el, state)) +
- '})'
-}
-
-function genData$2 (el, state) {
- var data = '{';
-
- // directives first.
- // directives may mutate the el's other properties before they are generated.
- var dirs = genDirectives(el, state);
- if (dirs) { data += dirs + ','; }
-
- // key
- if (el.key) {
- data += "key:" + (el.key) + ",";
- }
- // ref
- if (el.ref) {
- data += "ref:" + (el.ref) + ",";
- }
- if (el.refInFor) {
- data += "refInFor:true,";
- }
- // pre
- if (el.pre) {
- data += "pre:true,";
- }
- // record original tag name for components using "is" attribute
- if (el.component) {
- data += "tag:\"" + (el.tag) + "\",";
- }
- // module data generation functions
- for (var i = 0; i < state.dataGenFns.length; i++) {
- data += state.dataGenFns[i](el);
- }
- // attributes
- if (el.attrs) {
- data += "attrs:{" + (genProps(el.attrs)) + "},";
- }
- // DOM props
- if (el.props) {
- data += "domProps:{" + (genProps(el.props)) + "},";
- }
- // event handlers
- if (el.events) {
- data += (genHandlers(el.events, false, state.warn)) + ",";
- }
- if (el.nativeEvents) {
- data += (genHandlers(el.nativeEvents, true, state.warn)) + ",";
- }
- // slot target
- // only for non-scoped slots
- if (el.slotTarget && !el.slotScope) {
- data += "slot:" + (el.slotTarget) + ",";
- }
- // scoped slots
- if (el.scopedSlots) {
- data += (genScopedSlots(el.scopedSlots, state)) + ",";
- }
- // component v-model
- if (el.model) {
- data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
- }
- // inline-template
- if (el.inlineTemplate) {
- var inlineTemplate = genInlineTemplate(el, state);
- if (inlineTemplate) {
- data += inlineTemplate + ",";
- }
- }
- data = data.replace(/,$/, '') + '}';
- // v-bind data wrap
- if (el.wrapData) {
- data = el.wrapData(data);
- }
- // v-on data wrap
- if (el.wrapListeners) {
- data = el.wrapListeners(data);
- }
- return data
-}
-
-function genDirectives (el, state) {
- var dirs = el.directives;
- if (!dirs) { return }
- var res = 'directives:[';
- var hasRuntime = false;
- var i, l, dir, needRuntime;
- for (i = 0, l = dirs.length; i < l; i++) {
- dir = dirs[i];
- needRuntime = true;
- var gen = state.directives[dir.name];
- if (gen) {
- // compile-time directive that manipulates AST.
- // returns true if it also needs a runtime counterpart.
- needRuntime = !!gen(el, dir, state.warn);
- }
- if (needRuntime) {
- hasRuntime = true;
- res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:\"" + (dir.arg) + "\"") : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},";
- }
- }
- if (hasRuntime) {
- return res.slice(0, -1) + ']'
- }
-}
-
-function genInlineTemplate (el, state) {
- var ast = el.children[0];
- if ("development" !== 'production' && (
- el.children.length !== 1 || ast.type !== 1
- )) {
- state.warn('Inline-template components must have exactly one child element.');
- }
- if (ast.type === 1) {
- var inlineRenderFns = generate(ast, state.options);
- return ("inlineTemplate:{render:function(){" + (inlineRenderFns.render) + "},staticRenderFns:[" + (inlineRenderFns.staticRenderFns.map(function (code) { return ("function(){" + code + "}"); }).join(',')) + "]}")
- }
-}
-
-function genScopedSlots (
- slots,
- state
-) {
- return ("scopedSlots:_u([" + (Object.keys(slots).map(function (key) {
- return genScopedSlot(key, slots[key], state)
- }).join(',')) + "])")
-}
-
-function genScopedSlot (
- key,
- el,
- state
-) {
- if (el.for && !el.forProcessed) {
- return genForScopedSlot(key, el, state)
- }
- var fn = "function(" + (String(el.slotScope)) + "){" +
- "return " + (el.tag === 'template'
- ? el.if
- ? ((el.if) + "?" + (genChildren(el, state) || 'undefined') + ":undefined")
- : genChildren(el, state) || 'undefined'
- : genElement(el, state)) + "}";
- return ("{key:" + key + ",fn:" + fn + "}")
-}
-
-function genForScopedSlot (
- key,
- el,
- state
-) {
- var exp = el.for;
- var alias = el.alias;
- var iterator1 = el.iterator1 ? ("," + (el.iterator1)) : '';
- var iterator2 = el.iterator2 ? ("," + (el.iterator2)) : '';
- el.forProcessed = true; // avoid recursion
- return "_l((" + exp + ")," +
- "function(" + alias + iterator1 + iterator2 + "){" +
- "return " + (genScopedSlot(key, el, state)) +
- '})'
-}
-
-function genChildren (
- el,
- state,
- checkSkip,
- altGenElement,
- altGenNode
-) {
- var children = el.children;
- if (children.length) {
- var el$1 = children[0];
- // optimize single v-for
- if (children.length === 1 &&
- el$1.for &&
- el$1.tag !== 'template' &&
- el$1.tag !== 'slot'
- ) {
- return (altGenElement || genElement)(el$1, state)
- }
- var normalizationType = checkSkip
- ? getNormalizationType(children, state.maybeComponent)
- : 0;
- var gen = altGenNode || genNode;
- return ("[" + (children.map(function (c) { return gen(c, state); }).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))
- }
-}
-
-// determine the normalization needed for the children array.
-// 0: no normalization needed
-// 1: simple normalization needed (possible 1-level deep nested array)
-// 2: full normalization needed
-function getNormalizationType (
- children,
- maybeComponent
-) {
- var res = 0;
- for (var i = 0; i < children.length; i++) {
- var el = children[i];
- if (el.type !== 1) {
- continue
- }
- if (needsNormalization(el) ||
- (el.ifConditions && el.ifConditions.some(function (c) { return needsNormalization(c.block); }))) {
- res = 2;
- break
- }
- if (maybeComponent(el) ||
- (el.ifConditions && el.ifConditions.some(function (c) { return maybeComponent(c.block); }))) {
- res = 1;
- }
- }
- return res
-}
-
-function needsNormalization (el) {
- return el.for !== undefined || el.tag === 'template' || el.tag === 'slot'
-}
-
-function genNode (node, state) {
- if (node.type === 1) {
- return genElement(node, state)
- } if (node.type === 3 && node.isComment) {
- return genComment(node)
- } else {
- return genText(node)
- }
-}
-
-function genText (text) {
- return ("_v(" + (text.type === 2
- ? text.expression // no need for () because already wrapped in _s()
- : transformSpecialNewlines(JSON.stringify(text.text))) + ")")
-}
-
-function genComment (comment) {
- return ("_e(" + (JSON.stringify(comment.text)) + ")")
-}
-
-function genSlot (el, state) {
- var slotName = el.slotName || '"default"';
- var children = genChildren(el, state);
- var res = "_t(" + slotName + (children ? ("," + children) : '');
- var attrs = el.attrs && ("{" + (el.attrs.map(function (a) { return ((camelize(a.name)) + ":" + (a.value)); }).join(',')) + "}");
- var bind$$1 = el.attrsMap['v-bind'];
- if ((attrs || bind$$1) && !children) {
- res += ",null";
- }
- if (attrs) {
- res += "," + attrs;
- }
- if (bind$$1) {
- res += (attrs ? '' : ',null') + "," + bind$$1;
- }
- return res + ')'
-}
-
-// componentName is el.component, take it as argument to shun flow's pessimistic refinement
-function genComponent (
- componentName,
- el,
- state
-) {
- var children = el.inlineTemplate ? null : genChildren(el, state, true);
- return ("_c(" + componentName + "," + (genData$2(el, state)) + (children ? ("," + children) : '') + ")")
-}
-
-function genProps (props) {
- var res = '';
- for (var i = 0; i < props.length; i++) {
- var prop = props[i];
- /* istanbul ignore if */
- {
- res += "\"" + (prop.name) + "\":" + (transformSpecialNewlines(prop.value)) + ",";
- }
- }
- return res.slice(0, -1)
-}
-
-// #3895, #4268
-function transformSpecialNewlines (text) {
- return text
- .replace(/\u2028/g, '\\u2028')
- .replace(/\u2029/g, '\\u2029')
-}
-
-/* */
-
-// these keywords should not appear inside expressions, but operators like
-// typeof, instanceof and in are allowed
-var prohibitedKeywordRE = new RegExp('\\b' + (
- 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
- 'super,throw,while,yield,delete,export,import,return,switch,default,' +
- 'extends,finally,continue,debugger,function,arguments'
-).split(',').join('\\b|\\b') + '\\b');
-
-// these unary operators should not be used as property/method names
-var unaryOperatorsRE = new RegExp('\\b' + (
- 'delete,typeof,void'
-).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)');
-
-// strip strings in expressions
-var stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g;
-
-// detect problematic expressions in a template
-function detectErrors (ast) {
- var errors = [];
- if (ast) {
- checkNode(ast, errors);
- }
- return errors
-}
-
-function checkNode (node, errors) {
- if (node.type === 1) {
- for (var name in node.attrsMap) {
- if (dirRE.test(name)) {
- var value = node.attrsMap[name];
- if (value) {
- if (name === 'v-for') {
- checkFor(node, ("v-for=\"" + value + "\""), errors);
- } else if (onRE.test(name)) {
- checkEvent(value, (name + "=\"" + value + "\""), errors);
- } else {
- checkExpression(value, (name + "=\"" + value + "\""), errors);
- }
- }
- }
- }
- if (node.children) {
- for (var i = 0; i < node.children.length; i++) {
- checkNode(node.children[i], errors);
- }
- }
- } else if (node.type === 2) {
- checkExpression(node.expression, node.text, errors);
- }
-}
-
-function checkEvent (exp, text, errors) {
- var stipped = exp.replace(stripStringRE, '');
- var keywordMatch = stipped.match(unaryOperatorsRE);
- if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') {
- errors.push(
- "avoid using JavaScript unary operator as property name: " +
- "\"" + (keywordMatch[0]) + "\" in expression " + (text.trim())
- );
- }
- checkExpression(exp, text, errors);
-}
-
-function checkFor (node, text, errors) {
- checkExpression(node.for || '', text, errors);
- checkIdentifier(node.alias, 'v-for alias', text, errors);
- checkIdentifier(node.iterator1, 'v-for iterator', text, errors);
- checkIdentifier(node.iterator2, 'v-for iterator', text, errors);
-}
-
-function checkIdentifier (
- ident,
- type,
- text,
- errors
-) {
- if (typeof ident === 'string') {
- try {
- new Function(("var " + ident + "=_"));
- } catch (e) {
- errors.push(("invalid " + type + " \"" + ident + "\" in expression: " + (text.trim())));
- }
- }
-}
-
-function checkExpression (exp, text, errors) {
- try {
- new Function(("return " + exp));
- } catch (e) {
- var keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE);
- if (keywordMatch) {
- errors.push(
- "avoid using JavaScript keyword as property name: " +
- "\"" + (keywordMatch[0]) + "\"\n Raw expression: " + (text.trim())
- );
- } else {
- errors.push(
- "invalid expression: " + (e.message) + " in\n\n" +
- " " + exp + "\n\n" +
- " Raw expression: " + (text.trim()) + "\n"
- );
- }
- }
-}
-
-/* */
-
-function createFunction (code, errors) {
- try {
- return new Function(code)
- } catch (err) {
- errors.push({ err: err, code: code });
- return noop
- }
-}
-
-function createCompileToFunctionFn (compile) {
- var cache = Object.create(null);
-
- return function compileToFunctions (
- template,
- options,
- vm
- ) {
- options = extend({}, options);
- var warn$$1 = options.warn || warn;
- delete options.warn;
-
- /* istanbul ignore if */
- {
- // detect possible CSP restriction
- try {
- new Function('return 1');
- } catch (e) {
- if (e.toString().match(/unsafe-eval|CSP/)) {
- warn$$1(
- 'It seems you are using the standalone build of Vue.js in an ' +
- 'environment with Content Security Policy that prohibits unsafe-eval. ' +
- 'The template compiler cannot work in this environment. Consider ' +
- 'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
- 'templates into render functions.'
- );
- }
- }
- }
-
- // check cache
- var key = options.delimiters
- ? String(options.delimiters) + template
- : template;
- if (cache[key]) {
- return cache[key]
- }
-
- // compile
- var compiled = compile(template, options);
-
- // check compilation errors/tips
- {
- if (compiled.errors && compiled.errors.length) {
- warn$$1(
- "Error compiling template:\n\n" + template + "\n\n" +
- compiled.errors.map(function (e) { return ("- " + e); }).join('\n') + '\n',
- vm
- );
- }
- if (compiled.tips && compiled.tips.length) {
- compiled.tips.forEach(function (msg) { return tip(msg, vm); });
- }
- }
-
- // turn code into functions
- var res = {};
- var fnGenErrors = [];
- res.render = createFunction(compiled.render, fnGenErrors);
- res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
- return createFunction(code, fnGenErrors)
- });
-
- // check function generation errors.
- // this should only happen if there is a bug in the compiler itself.
- // mostly for codegen development use
- /* istanbul ignore if */
- {
- if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
- warn$$1(
- "Failed to generate render function:\n\n" +
- fnGenErrors.map(function (ref) {
- var err = ref.err;
- var code = ref.code;
-
- return ((err.toString()) + " in\n\n" + code + "\n");
- }).join('\n'),
- vm
- );
- }
- }
-
- return (cache[key] = res)
- }
-}
-
-/* */
-
-function createCompilerCreator (baseCompile) {
- return function createCompiler (baseOptions) {
- function compile (
- template,
- options
- ) {
- var finalOptions = Object.create(baseOptions);
- var errors = [];
- var tips = [];
- finalOptions.warn = function (msg, tip) {
- (tip ? tips : errors).push(msg);
- };
-
- if (options) {
- // merge custom modules
- if (options.modules) {
- finalOptions.modules =
- (baseOptions.modules || []).concat(options.modules);
- }
- // merge custom directives
- if (options.directives) {
- finalOptions.directives = extend(
- Object.create(baseOptions.directives || null),
- options.directives
- );
- }
- // copy other options
- for (var key in options) {
- if (key !== 'modules' && key !== 'directives') {
- finalOptions[key] = options[key];
- }
- }
- }
-
- var compiled = baseCompile(template, finalOptions);
- {
- errors.push.apply(errors, detectErrors(compiled.ast));
- }
- compiled.errors = errors;
- compiled.tips = tips;
- return compiled
- }
-
- return {
- compile: compile,
- compileToFunctions: createCompileToFunctionFn(compile)
- }
- }
-}
-
-/* */
-
-// `createCompilerCreator` allows creating compilers that use alternative
-// parser/optimizer/codegen, e.g the SSR optimizing compiler.
-// Here we just export a default compiler using the default parts.
-var createCompiler = createCompilerCreator(function baseCompile (
- template,
- options
-) {
- var ast = parse(template.trim(), options);
- if (options.optimize !== false) {
- optimize(ast, options);
- }
- var code = generate(ast, options);
- return {
- ast: ast,
- render: code.render,
- staticRenderFns: code.staticRenderFns
- }
-});
-
-/* */
-
-var ref$1 = createCompiler(baseOptions);
-var compileToFunctions = ref$1.compileToFunctions;
-
-/* */
-
-// check whether current browser encodes a char inside attribute values
-var div;
-function getShouldDecode (href) {
- div = div || document.createElement('div');
- div.innerHTML = href ? "
" : "";
- return div.innerHTML.indexOf('
') > 0
-}
-
-// #3663: IE encodes newlines inside attribute values while other browsers don't
-var shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false;
-// #6828: chrome encodes content in a[href]
-var shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false;
-
-/* */
-
-var idToTemplate = cached(function (id) {
- var el = query(id);
- return el && el.innerHTML
-});
-
-var mount = Vue.prototype.$mount;
-Vue.prototype.$mount = function (
- el,
- hydrating
-) {
- el = el && query(el);
-
- /* istanbul ignore if */
- if (el === document.body || el === document.documentElement) {
- "development" !== 'production' && warn(
- "Do not mount Vue to or - mount to normal elements instead."
- );
- return this
- }
-
- var options = this.$options;
- // resolve template/el and convert to render function
- if (!options.render) {
- var template = options.template;
- if (template) {
- if (typeof template === 'string') {
- if (template.charAt(0) === '#') {
- template = idToTemplate(template);
- /* istanbul ignore if */
- if ("development" !== 'production' && !template) {
- warn(
- ("Template element not found or is empty: " + (options.template)),
- this
- );
- }
- }
- } else if (template.nodeType) {
- template = template.innerHTML;
- } else {
- {
- warn('invalid template option:' + template, this);
- }
- return this
- }
- } else if (el) {
- template = getOuterHTML(el);
- }
- if (template) {
- /* istanbul ignore if */
- if ("development" !== 'production' && config.performance && mark) {
- mark('compile');
- }
-
- var ref = compileToFunctions(template, {
- shouldDecodeNewlines: shouldDecodeNewlines,
- shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
- delimiters: options.delimiters,
- comments: options.comments
- }, this);
- var render = ref.render;
- var staticRenderFns = ref.staticRenderFns;
- options.render = render;
- options.staticRenderFns = staticRenderFns;
-
- /* istanbul ignore if */
- if ("development" !== 'production' && config.performance && mark) {
- mark('compile end');
- measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
- }
- }
- }
- return mount.call(this, el, hydrating)
-};
-
-/**
- * Get outerHTML of elements, taking care
- * of SVG elements in IE as well.
- */
-function getOuterHTML (el) {
- if (el.outerHTML) {
- return el.outerHTML
- } else {
- var container = document.createElement('div');
- container.appendChild(el.cloneNode(true));
- return container.innerHTML
- }
-}
-
-Vue.compile = compileToFunctions;
-
-return Vue;
-
-})));
diff --git a/resources/kiwix.qrc b/resources/kiwix.qrc
index 5d65a97..6b48a41 100644
--- a/resources/kiwix.qrc
+++ b/resources/kiwix.qrc
@@ -56,6 +56,5 @@
icons/new-tab-icon.svg
icons/library-icon.svg
icons/open-file.svg
-
js/tools.js
diff --git a/resources/texts/_contentManager.html b/resources/texts/_contentManager.html
deleted file mode 100644
index d02e8d2..0000000
--- a/resources/texts/_contentManager.html
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ book.title }}
-
-
- {{ niceBytes(book.size) }}
-
-
- {{ book.date }}
-
-
- {{ book.tags }}
-
-
-
-
- {{ niceBytes(downloads[book.id].completedLength) }} / {{ niceBytes(downloads[book.id].totalLength) }}
-
-
-
-
-
-
-
-
-
-
-
-
-

-

-
-
-
-
-
-
-
-
-
-
- {{ book.description }}
-
-
-
-
-
-
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index b3bf9c6..b0af6ed 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -21,8 +21,7 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
// mp_view will be passed to the tab who will take ownership,
// so, we don't need to delete it.
mp_view = new ContentManagerView();
- mp_view->registerObject("contentManager", this);
- mp_view->setHtml();
+ mp_view->show();
setCurrentLanguage(QLocale().name().split("_").at(0));
connect(mp_library, &Library::booksChanged, this, [=]() {emit(this->booksChanged());});
connect(this, &ContentManager::filterParamsChanged, this, &ContentManager::updateLibrary);
diff --git a/src/contentmanagerview.cpp b/src/contentmanagerview.cpp
index 4e14a97..72601c5 100644
--- a/src/contentmanagerview.cpp
+++ b/src/contentmanagerview.cpp
@@ -1,29 +1,9 @@
#include "contentmanagerview.h"
#include
-#include
#include "kiwixapp.h"
ContentManagerView::ContentManagerView(QWidget *parent)
- : QWebEngineView(parent)
+ : QTreeView(parent)
{
- QWebEnginePage* page = new QWebEnginePage(KiwixApp::instance()->getProfile(), this);
- setPage(page);
- page->setWebChannel(&m_webChannel);
- setContextMenuPolicy( Qt::NoContextMenu );
-}
-
-
-void ContentManagerView::registerObject(const QString& id, QObject* object)
-{
- m_webChannel.registerObject(id, object);
-}
-
-
-void ContentManagerView::setHtml()
-{
- QFile contentFile(":texts/_contentManager.html");
- contentFile.open(QIODevice::ReadOnly);
- auto byteContent = contentFile.readAll();
- contentFile.close();
- QWebEngineView::setHtml(byteContent);
+ setSortingEnabled(true);
}
diff --git a/src/contentmanagerview.h b/src/contentmanagerview.h
index b7e551c..1ee61dd 100644
--- a/src/contentmanagerview.h
+++ b/src/contentmanagerview.h
@@ -2,17 +2,13 @@
#define CONTENTMANAGERVIEW_H
#include
-#include
+#include
-class ContentManagerView : public QWebEngineView
+class ContentManagerView : public QTreeView
{
Q_OBJECT
public:
ContentManagerView(QWidget *parent = Q_NULLPTR);
- void registerObject(const QString &id, QObject *object);
- void setHtml();
-private:
- QWebChannel m_webChannel;
};
#endif // CONTENTMANAGERVIEW_H
From 3c69c1a5c1397994cd6e97cac7966240b81476cf Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Thu, 22 Jun 2023 10:57:35 +0530
Subject: [PATCH 02/29] getBookInfos now returns a map
getBookInfos will now return a QMap
This will be used to get book data based on key in the TreeView model
---
src/contentmanager.cpp | 16 ++++++++--------
src/contentmanager.h | 2 +-
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index b0af6ed..ae22154 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -46,10 +46,10 @@ QStringList ContentManager::getTranslations(const QStringList &keys)
return translations;
}
-#define ADD_V(KEY, METH) {if(key==KEY) values.append(QString::fromStdString((b->METH())));}
-QStringList ContentManager::getBookInfos(QString id, const QStringList &keys)
+#define ADD_V(KEY, METH) {if(key==KEY) values.insert(key, QString::fromStdString((b->METH())));}
+QMap ContentManager::getBookInfos(QString id, const QStringList &keys)
{
- QStringList values;
+ QMap values;
const kiwix::Book* b = [=]()->const kiwix::Book* {
try {
return &mp_library->getBookById(id);
@@ -63,7 +63,7 @@ QStringList ContentManager::getBookInfos(QString id, const QStringList &keys)
if (nullptr == b){
for(auto& key:keys) {
(void) key;
- values.append("");
+ values.insert(key, "");
}
return values;
}
@@ -86,7 +86,7 @@ QStringList ContentManager::getBookInfos(QString id, const QStringList &keys)
const kiwix::Book::Illustration tempIllustration;
mimeType = tempIllustration.mimeType;
}
- values.append(QString::fromStdString(mimeType));
+ values.insert(key, QString::fromStdString(mimeType));
}
if (key == "faviconUrl") {
std::string url;
@@ -97,10 +97,10 @@ QStringList ContentManager::getBookInfos(QString id, const QStringList &keys)
const kiwix::Book::Illustration tempIllustration;
url = tempIllustration.url;
}
- values.append(QString::fromStdString(url));
+ values.insert(key, QString::fromStdString(url));
}
if (key == "size") {
- values.append(QString::number(b->getSize()));
+ values.insert(key, QString::number(b->getSize()));
}
if (key == "tags") {
QStringList tagList = QString::fromStdString(b->getTags()).split(';');
@@ -116,7 +116,7 @@ QStringList ContentManager::getBookInfos(QString id, const QStringList &keys)
if (displayTagMap["_pictures"]) displayTagList << tr("Pictures");
if (!displayTagMap["_details"]) displayTagList << tr("Introduction only");
QString s = displayTagList.join(", ");
- values.append(s);
+ values.insert(key, s);
}
}
return values;
diff --git a/src/contentmanager.h b/src/contentmanager.h
index d6abb09..0852291 100644
--- a/src/contentmanager.h
+++ b/src/contentmanager.h
@@ -55,7 +55,7 @@ signals:
public slots:
QStringList getTranslations(const QStringList &keys);
- QStringList getBookInfos(QString id, const QStringList &keys);
+ QMap getBookInfos(QString id, const QStringList &keys);
void openBook(const QString& id);
QStringList updateDownloadInfos(QString id, const QStringList& keys);
QString downloadBook(const QString& id);
From e5c8a31ec037d657e46a82f0f9f0758c607546f3 Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Thu, 22 Jun 2023 11:17:46 +0530
Subject: [PATCH 03/29] Basic model implemented for TreeView
This implements a basic model for QTreeModel
Currently, it only shows 1 row with some random data.
In future commits, data from ContentManager::getBookInfos will be taken
---
kiwix-desktop.pro | 4 ++
src/contentmanager.cpp | 3 ++
src/contentmanagermodel.cpp | 103 ++++++++++++++++++++++++++++++++++++
src/contentmanagermodel.h | 32 +++++++++++
src/node.cpp | 52 ++++++++++++++++++
src/node.h | 27 ++++++++++
6 files changed, 221 insertions(+)
create mode 100644 src/contentmanagermodel.cpp
create mode 100644 src/contentmanagermodel.h
create mode 100644 src/node.cpp
create mode 100644 src/node.h
diff --git a/kiwix-desktop.pro b/kiwix-desktop.pro
index 34db554..00579d6 100644
--- a/kiwix-desktop.pro
+++ b/kiwix-desktop.pro
@@ -34,8 +34,10 @@ DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
+ src/contentmanagermodel.cpp \
src/contenttypefilter.cpp \
src/findinpagebar.cpp \
+ src/node.cpp \
src/suggestionlistworker.cpp \
src/translation.cpp \
src/main.cpp \
@@ -67,8 +69,10 @@ SOURCES += \
src/static_content.cpp
HEADERS += \
+ src/contentmanagermodel.h \
src/contenttypefilter.h \
src/findinpagebar.h \
+ src/node.h \
src/suggestionlistworker.h \
src/translation.h \
src/mainwindow.h \
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index ae22154..f24d029 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -11,6 +11,7 @@
#include
#include
#include
+#include "contentmanagermodel.h"
ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader, QObject *parent)
: QObject(parent),
@@ -21,6 +22,8 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
// mp_view will be passed to the tab who will take ownership,
// so, we don't need to delete it.
mp_view = new ContentManagerView();
+ auto managerModel = new ContentManagerModel();
+ mp_view->setModel(managerModel);
mp_view->show();
setCurrentLanguage(QLocale().name().split("_").at(0));
connect(mp_library, &Library::booksChanged, this, [=]() {emit(this->booksChanged());});
diff --git a/src/contentmanagermodel.cpp b/src/contentmanagermodel.cpp
new file mode 100644
index 0000000..ec5bfee
--- /dev/null
+++ b/src/contentmanagermodel.cpp
@@ -0,0 +1,103 @@
+#include "contentmanagermodel.h"
+#include "node.h"
+#include
+#include
+#include
+#include
+#include
+
+
+ContentManagerModel::ContentManagerModel(QObject *parent)
+ : QAbstractItemModel(parent)
+{
+ rootNode = new Node({tr("Icon"), tr("Name"), tr("Date"), tr("Size"), tr("Content Type"), tr("Download")});
+ auto childNode = new Node({"someIcon", "test name", "test date", "test size", "test content", "download link"}, rootNode);
+ rootNode->appendChild(childNode);
+}
+
+ContentManagerModel::~ContentManagerModel()
+{
+ delete rootNode;
+}
+
+int ContentManagerModel::columnCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return static_cast(parent.internalPointer())->columnCount();
+ return rootNode->columnCount();
+}
+
+QVariant ContentManagerModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ Node *item = static_cast(index.internalPointer());
+ const auto displayRole = role == Qt::DisplayRole;
+ if (displayRole)
+ return item->data(index.column());
+
+ return QVariant();
+}
+
+Qt::ItemFlags ContentManagerModel::flags(const QModelIndex &index) const
+{
+ Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);
+ if (index.isValid() && index.parent().isValid()) {
+ return defaultFlags & ~Qt::ItemIsDropEnabled & ~Qt::ItemIsDragEnabled & ~Qt::ItemIsSelectable & ~Qt::ItemIsEditable & ~Qt::ItemIsUserCheckable;
+ }
+ return defaultFlags;
+}
+
+QModelIndex ContentManagerModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (!hasIndex(row, column, parent))
+ return QModelIndex();
+
+ Node *parentItem;
+
+ if (!parent.isValid())
+ parentItem = rootNode;
+ else
+ parentItem = static_cast(parent.internalPointer());
+
+ Node *childItem = parentItem->child(row);
+ if (childItem)
+ return createIndex(row, column, childItem);
+ return QModelIndex();
+}
+
+QModelIndex ContentManagerModel::parent(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return QModelIndex();
+
+ Node *childItem = static_cast(index.internalPointer());
+ Node *parentItem = childItem->parentItem();
+
+ if (parentItem == rootNode)
+ return QModelIndex();
+
+ return createIndex(parentItem->row(), 0, parentItem);
+}
+
+int ContentManagerModel::rowCount(const QModelIndex &parent) const
+{
+ return 5;
+}
+
+QVariant ContentManagerModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
+ return QVariant();
+
+ switch (section)
+ {
+ case 0: return QVariant();
+ case 1: return "Name";
+ case 2: return "Date";
+ case 3: return "Size";
+ case 4: return "Content Type";
+ default: return QVariant();
+ }
+}
diff --git a/src/contentmanagermodel.h b/src/contentmanagermodel.h
new file mode 100644
index 0000000..da45b63
--- /dev/null
+++ b/src/contentmanagermodel.h
@@ -0,0 +1,32 @@
+#ifndef CONTENTMANAGERMODEL_H
+#define CONTENTMANAGERMODEL_H
+
+#include
+#include
+#include
+
+class Node;
+
+class ContentManagerModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ explicit ContentManagerModel(QObject *parent = nullptr);
+ ~ContentManagerModel();
+
+ QVariant data(const QModelIndex &index, int role) const override;
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const override;
+ QModelIndex index(int row, int column,
+ const QModelIndex &parent = QModelIndex()) const override;
+ QModelIndex parent(const QModelIndex &index) const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+
+private:
+ Node *rootNode;
+};
+
+#endif // CONTENTMANAGERMODEL_H
diff --git a/src/node.cpp b/src/node.cpp
new file mode 100644
index 0000000..47b1cb5
--- /dev/null
+++ b/src/node.cpp
@@ -0,0 +1,52 @@
+#include "node.h"
+
+Node::Node(const QList &data, Node *parent)
+ : m_itemData(data), m_parentItem(parent)
+{}
+
+Node::~Node()
+{
+ qDeleteAll(m_childItems);
+}
+
+void Node::appendChild(Node *item)
+{
+ m_childItems.append(item);
+}
+
+Node *Node::child(int row)
+{
+ if (row < 0 || row >= m_childItems.size())
+ return nullptr;
+ return m_childItems.at(row);
+}
+
+int Node::childCount() const
+{
+ return m_childItems.count();
+}
+
+int Node::columnCount() const
+{
+ return m_itemData.count();
+}
+
+QVariant Node::data(int column) const
+{
+ if (column < 0 || column >= m_itemData.size())
+ return QVariant();
+ return m_itemData.at(column);
+}
+
+Node *Node::parentItem()
+{
+ return m_parentItem;
+}
+
+int Node::row() const
+{
+ if (m_parentItem)
+ return m_parentItem->m_childItems.indexOf(const_cast(this));
+
+ return 0;
+}
diff --git a/src/node.h b/src/node.h
new file mode 100644
index 0000000..91676e1
--- /dev/null
+++ b/src/node.h
@@ -0,0 +1,27 @@
+#ifndef NODE_H
+#define NODE_H
+
+#include
+#include
+
+class Node
+{
+public:
+ explicit Node(const QList &data, Node *parentNode = nullptr);
+ ~Node();
+ void appendChild(Node *child);
+ Node *child(int row);
+ int childCount() const;
+ int columnCount() const;
+ QVariant data(int column) const;
+ int row() const;
+ Node *parentItem();
+
+private:
+ QList m_itemData;
+ Node *m_parentItem;
+ QList m_childItems;
+};
+
+
+#endif // NODE_H
From f968a24adcc86e8585044e4e828b2f865a1c7959 Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Thu, 22 Jun 2023 12:02:54 +0530
Subject: [PATCH 04/29] Real book data in library
Adds the book data from library
---
src/contentmanager.cpp | 34 +++++++++++++++++++++++++++
src/contentmanager.h | 1 +
src/contentmanagermodel.cpp | 46 +++++++++++++++++++++++++++++++++----
src/contentmanagermodel.h | 3 +++
4 files changed, 80 insertions(+), 4 deletions(-)
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index f24d029..8109294 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -12,6 +12,8 @@
#include
#include
#include "contentmanagermodel.h"
+#include
+#include
ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader, QObject *parent)
: QObject(parent),
@@ -23,14 +25,46 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
// so, we don't need to delete it.
mp_view = new ContentManagerView();
auto managerModel = new ContentManagerModel();
+ const auto booksList = getBooksList();
+ managerModel->setBooksData(booksList);
mp_view->setModel(managerModel);
mp_view->show();
setCurrentLanguage(QLocale().name().split("_").at(0));
connect(mp_library, &Library::booksChanged, this, [=]() {emit(this->booksChanged());});
connect(this, &ContentManager::filterParamsChanged, this, &ContentManager::updateLibrary);
+ connect(this, &ContentManager::booksChanged, this, [=]() {
+ const auto nBookList = getBooksList();
+ managerModel->setBooksData(nBookList);
+ });
connect(&m_remoteLibraryManager, &OpdsRequestManager::requestReceived, this, &ContentManager::updateRemoteLibrary);
}
+QList> ContentManager::getBooksList()
+{
+ const auto bookIds = getBookIds();
+ QList> bookList;
+ QStringList keys = {"title", "tags", "date", "id", "size"};
+ auto app = KiwixApp::instance();
+ std::shared_ptr archive;
+ QIcon bookIcon;
+ for (auto bookId : bookIds) {
+ try {
+ archive = app->getLibrary()->getArchive(bookId);
+ std::string favicon, _mimetype;
+ auto item = archive->getIllustrationItem(48);
+ favicon = item.getData();
+ _mimetype = item.getMimetype();
+ QPixmap pixmap;
+ pixmap.loadFromData((const uchar*)favicon.data(), favicon.size());
+ bookIcon = QIcon(pixmap);
+ } catch (std::out_of_range& e) {}
+ auto mp = getBookInfos(bookId, keys);
+ mp["icon"] = bookIcon;
+ bookList.append(mp);
+ }
+ return bookList;
+}
+
void ContentManager::setLocal(bool local) {
if (local == m_local) {
return;
diff --git a/src/contentmanager.h b/src/contentmanager.h
index 0852291..b3b58fd 100644
--- a/src/contentmanager.h
+++ b/src/contentmanager.h
@@ -43,6 +43,7 @@ private:
QStringList getBookIds();
void eraseBookFilesFromComputer(const QString dirPath, const QString filename);
+ QList> getBooksList();
signals:
void filterParamsChanged();
diff --git a/src/contentmanagermodel.cpp b/src/contentmanagermodel.cpp
index ec5bfee..8f4df44 100644
--- a/src/contentmanagermodel.cpp
+++ b/src/contentmanagermodel.cpp
@@ -10,9 +10,6 @@
ContentManagerModel::ContentManagerModel(QObject *parent)
: QAbstractItemModel(parent)
{
- rootNode = new Node({tr("Icon"), tr("Name"), tr("Date"), tr("Size"), tr("Content Type"), tr("Download")});
- auto childNode = new Node({"someIcon", "test name", "test date", "test size", "test content", "download link"}, rootNode);
- rootNode->appendChild(childNode);
}
ContentManagerModel::~ContentManagerModel()
@@ -83,7 +80,8 @@ QModelIndex ContentManagerModel::parent(const QModelIndex &index) const
int ContentManagerModel::rowCount(const QModelIndex &parent) const
{
- return 5;
+ Q_UNUSED(parent);
+ return m_data.size();
}
QVariant ContentManagerModel::headerData(int section, Qt::Orientation orientation, int role) const
@@ -101,3 +99,43 @@ QVariant ContentManagerModel::headerData(int section, Qt::Orientation orientatio
default: return QVariant();
}
}
+
+void ContentManagerModel::setBooksData(const QList>& data)
+{
+ m_data = data;
+ rootNode = new Node({tr("Icon"), tr("Name"), tr("Date"), tr("Size"), tr("Content Type"), tr("Download")});
+ setupNodes();
+ emit dataChanged(QModelIndex(), QModelIndex());
+}
+
+QString convertToUnits(QString size)
+{
+ QStringList units = {"bytes", "KB", "MB", "GB", "TB", "PB", "EB"};
+ int unitIndex = 0;
+ auto bytes = size.toDouble();
+ while (bytes >= 1024 && unitIndex < units.size()) {
+ bytes /= 1024;
+ unitIndex++;
+ }
+
+ const auto preciseBytes = QString::number(bytes, 'g', 3);
+ return preciseBytes + units[unitIndex];
+}
+
+void ContentManagerModel::setupNodes()
+{
+ beginResetModel();
+ for (auto bookItem : m_data) {
+ auto name = bookItem["title"].toString();
+ auto date = bookItem["date"].toString();
+ auto size = convertToUnits(bookItem["size"].toString());
+ auto content = bookItem["tags"].toString();
+ auto id = bookItem["id"].toString();
+ auto description = bookItem["description"].toString();
+ auto icon = bookItem["icon"];
+ const auto temp = new Node({icon, name, date, size, content, id}, rootNode);
+ rootNode->appendChild(temp);
+ }
+ endResetModel();
+}
+
diff --git a/src/contentmanagermodel.h b/src/contentmanagermodel.h
index da45b63..9241c82 100644
--- a/src/contentmanagermodel.h
+++ b/src/contentmanagermodel.h
@@ -24,8 +24,11 @@ public:
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+ void setBooksData(const QList>& data);
+ void setupNodes();
private:
+ QList> m_data;
Node *rootNode;
};
From bddda5cb2b0210484ba66954b5e8d05bc89f6ecc Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Thu, 22 Jun 2023 12:23:27 +0530
Subject: [PATCH 05/29] Description field in view
Added the description field in the view. It is shown when the expand button is pressed.
Fixed widths for all columns except Name, which takes up the remaining space.
---
src/contentmanager.cpp | 15 ++++++++++++++-
src/contentmanagermodel.cpp | 10 ++++++++++
src/contentmanagermodel.h | 1 +
src/node.cpp | 4 ++--
src/node.h | 4 +++-
5 files changed, 30 insertions(+), 4 deletions(-)
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index 8109294..2f49c3b 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -14,6 +14,7 @@
#include "contentmanagermodel.h"
#include
#include
+#include
ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader, QObject *parent)
: QObject(parent),
@@ -29,6 +30,18 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
managerModel->setBooksData(booksList);
mp_view->setModel(managerModel);
mp_view->show();
+
+ auto header = mp_view->header();
+ header->setSectionResizeMode(0, QHeaderView::Fixed);
+ header->setSectionResizeMode(1, QHeaderView::Stretch);
+ header->setSectionResizeMode(2, QHeaderView::Fixed);
+ header->setSectionResizeMode(3, QHeaderView::Fixed);
+ header->setSectionResizeMode(4, QHeaderView::Fixed);
+ header->setStretchLastSection(false);
+ header->setSectionsClickable(true);
+ header->setHighlightSections(true);
+ mp_view->setWordWrap(true);
+
setCurrentLanguage(QLocale().name().split("_").at(0));
connect(mp_library, &Library::booksChanged, this, [=]() {emit(this->booksChanged());});
connect(this, &ContentManager::filterParamsChanged, this, &ContentManager::updateLibrary);
@@ -43,7 +56,7 @@ QList> ContentManager::getBooksList()
{
const auto bookIds = getBookIds();
QList> bookList;
- QStringList keys = {"title", "tags", "date", "id", "size"};
+ QStringList keys = {"title", "tags", "date", "id", "size", "description"};
auto app = KiwixApp::instance();
std::shared_ptr archive;
QIcon bookIcon;
diff --git a/src/contentmanagermodel.cpp b/src/contentmanagermodel.cpp
index 8f4df44..9a1a84e 100644
--- a/src/contentmanagermodel.cpp
+++ b/src/contentmanagermodel.cpp
@@ -134,8 +134,18 @@ void ContentManagerModel::setupNodes()
auto description = bookItem["description"].toString();
auto icon = bookItem["icon"];
const auto temp = new Node({icon, name, date, size, content, id}, rootNode);
+ const auto tempsTemp = new Node({"", description, "", "", "", ""}, temp, true);
+ temp->appendChild(tempsTemp);
rootNode->appendChild(temp);
}
endResetModel();
}
+bool ContentManagerModel::hasChildren(const QModelIndex &parent) const
+{
+ Node *item = static_cast(parent.internalPointer());
+ if (item)
+ return !item->isAdditonal();
+ return true;
+}
+
diff --git a/src/contentmanagermodel.h b/src/contentmanagermodel.h
index 9241c82..994f456 100644
--- a/src/contentmanagermodel.h
+++ b/src/contentmanagermodel.h
@@ -26,6 +26,7 @@ public:
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
void setBooksData(const QList>& data);
void setupNodes();
+ bool hasChildren(const QModelIndex &parent) const override;
private:
QList> m_data;
diff --git a/src/node.cpp b/src/node.cpp
index 47b1cb5..c409fd0 100644
--- a/src/node.cpp
+++ b/src/node.cpp
@@ -1,7 +1,7 @@
#include "node.h"
-Node::Node(const QList &data, Node *parent)
- : m_itemData(data), m_parentItem(parent)
+Node::Node(const QList &data, Node *parent, bool isAdditional)
+ : m_itemData(data), m_parentItem(parent), m_isAdditonal(isAdditional)
{}
Node::~Node()
diff --git a/src/node.h b/src/node.h
index 91676e1..cf03ea4 100644
--- a/src/node.h
+++ b/src/node.h
@@ -7,7 +7,7 @@
class Node
{
public:
- explicit Node(const QList &data, Node *parentNode = nullptr);
+ explicit Node(const QList &data, Node *parentItem = nullptr, bool isAdditional = false);
~Node();
void appendChild(Node *child);
Node *child(int row);
@@ -16,11 +16,13 @@ public:
QVariant data(int column) const;
int row() const;
Node *parentItem();
+ bool isAdditonal() const { return m_isAdditonal; }
private:
QList m_itemData;
Node *m_parentItem;
QList m_childItems;
+ bool m_isAdditonal;
};
From 1f33d86340266531ebbce25261a105e4c7945265 Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Thu, 22 Jun 2023 12:37:20 +0530
Subject: [PATCH 06/29] Delegate for ContentManagerView
Added a new delegate to style the view.
Added the following things:
1. Button styling and click to open book
2. Increased size of rows
---
kiwix-desktop.pro | 2 +
src/contentmanager.cpp | 1 +
src/contentmanagerdelegate.cpp | 73 ++++++++++++++++++++++++++++++++++
src/contentmanagerdelegate.h | 21 ++++++++++
src/contentmanagermodel.cpp | 3 +-
src/contentmanagerview.cpp | 3 ++
6 files changed, 102 insertions(+), 1 deletion(-)
create mode 100644 src/contentmanagerdelegate.cpp
create mode 100644 src/contentmanagerdelegate.h
diff --git a/kiwix-desktop.pro b/kiwix-desktop.pro
index 00579d6..e672486 100644
--- a/kiwix-desktop.pro
+++ b/kiwix-desktop.pro
@@ -34,6 +34,7 @@ DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
+ src/contentmanagerdelegate.cpp \
src/contentmanagermodel.cpp \
src/contenttypefilter.cpp \
src/findinpagebar.cpp \
@@ -69,6 +70,7 @@ SOURCES += \
src/static_content.cpp
HEADERS += \
+ src/contentmanagerdelegate.h \
src/contentmanagermodel.h \
src/contenttypefilter.h \
src/findinpagebar.h \
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index 2f49c3b..21fbe59 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -15,6 +15,7 @@
#include
#include
#include
+#include "contentmanagerdelegate.h"
ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader, QObject *parent)
: QObject(parent),
diff --git a/src/contentmanagerdelegate.cpp b/src/contentmanagerdelegate.cpp
new file mode 100644
index 0000000..49d8f65
--- /dev/null
+++ b/src/contentmanagerdelegate.cpp
@@ -0,0 +1,73 @@
+#include
+#include "contentmanagerdelegate.h"
+#include
+#include
+#include
+#include "kiwixapp.h"
+
+ContentManagerDelegate::ContentManagerDelegate(QObject *parent)
+ : QStyledItemDelegate(parent), baseButton(new QPushButton)
+{
+ baseButton->setStyleSheet("background-color: white;"
+ "border: 0;"
+ "font-weight: bold;"
+ "font-family: Selawik;"
+ "color: blue;"
+ "margin: 4px;");
+}
+
+
+void ContentManagerDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ QStyleOptionButton button;
+ QRect r = option.rect;
+ int x,y,w,h;
+ x = r.left();
+ y = r.top();
+ w = r.width();
+ h = r.height();
+ button.rect = QRect(x,y,w,h);
+ button.text = "Open";
+ button.state = QStyle::State_Enabled;
+ if (index.column() == 5 && index.data(Qt::UserRole+1) == QVariant()) {
+ baseButton->style()->drawControl( QStyle::CE_PushButton, &button, painter, baseButton.data());
+ return;
+ }
+ QStyledItemDelegate::paint(painter, option, index);
+}
+
+bool ContentManagerDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
+{
+ if(event->type() == QEvent::MouseButtonRelease )
+ {
+ QMouseEvent * e = (QMouseEvent *)event;
+ int clickX = e->x();
+ int clickY = e->y();
+
+ QRect r = option.rect;//getting the rect of the cell
+ int x,y,w,h;
+ x = r.left();//the X coordinate
+ y = r.top();//the Y coordinate
+ w = r.width();//button width
+ h = r.height();//button height
+
+
+ if(index.column() == 5 && clickX > x && clickX < x + w )
+ if( clickY > y && clickY < y + h )
+ {
+ const auto modeel = index.model();
+ const auto id = modeel->data(index).toString();
+ KiwixApp::instance()->getContentManager()->openBook(id);
+ }
+ }
+
+ return true;
+}
+
+QSize ContentManagerDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ if (index.data(Qt::UserRole+1) != QVariant()) {
+ return QSize(300, 70);
+ }
+ return QSize(50, 70);
+}
diff --git a/src/contentmanagerdelegate.h b/src/contentmanagerdelegate.h
new file mode 100644
index 0000000..0e9f474
--- /dev/null
+++ b/src/contentmanagerdelegate.h
@@ -0,0 +1,21 @@
+#ifndef CONTENTMANAGERDELEGATE_H
+#define CONTENTMANAGERDELEGATE_H
+
+#include
+#include
+
+class ContentManagerDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+public:
+
+ ContentManagerDelegate(QObject *parent=0);
+ void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+ bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override;
+ QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+
+private:
+ QScopedPointer baseButton;
+};
+
+#endif // CONTENTMANAGERDELEGATE_H
diff --git a/src/contentmanagermodel.cpp b/src/contentmanagermodel.cpp
index 9a1a84e..ddfa54f 100644
--- a/src/contentmanagermodel.cpp
+++ b/src/contentmanagermodel.cpp
@@ -31,7 +31,8 @@ QVariant ContentManagerModel::data(const QModelIndex& index, int role) const
Node *item = static_cast(index.internalPointer());
const auto displayRole = role == Qt::DisplayRole;
- if (displayRole)
+ const auto additionalInfoRole = role == Qt::UserRole+1 && item->isAdditonal();
+ if (displayRole || additionalInfoRole)
return item->data(index.column());
return QVariant();
diff --git a/src/contentmanagerview.cpp b/src/contentmanagerview.cpp
index 72601c5..dacfd2b 100644
--- a/src/contentmanagerview.cpp
+++ b/src/contentmanagerview.cpp
@@ -1,9 +1,12 @@
#include "contentmanagerview.h"
#include
#include "kiwixapp.h"
+#include "contentmanagerdelegate.h"
ContentManagerView::ContentManagerView(QWidget *parent)
: QTreeView(parent)
{
setSortingEnabled(true);
+ auto managerDelegate = new ContentManagerDelegate();
+ setItemDelegate(managerDelegate);
}
From b0e3bf8daf99eb7ba8927248ae7a463a28d15c3d Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Thu, 22 Jun 2023 12:49:13 +0530
Subject: [PATCH 07/29] Icon is now displayed
Used delegate to display the icon
If the icon is not available, icons/placeholder-icon.png is displayed
---
resources/icons/placeholder-icon.png | Bin 0 -> 4004 bytes
resources/kiwix.qrc | 1 +
src/contentmanager.cpp | 6 +++++-
src/contentmanagerdelegate.cpp | 21 +++++++++++++++++++--
4 files changed, 25 insertions(+), 3 deletions(-)
create mode 100644 resources/icons/placeholder-icon.png
diff --git a/resources/icons/placeholder-icon.png b/resources/icons/placeholder-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..bfd988c53fa92d0321bd0af6500e7da33aa84422
GIT binary patch
literal 4004
zcmZ`+XHe78v;Bb}0#Xe`ItWPU5NZh0t0282geFC*5|SV-bQDmj(gK3?-fJKTDkwr!
zM5GrLL3#~Jd-;EOGw;2bckkSL=I+kf4?B1E>^?R#(PyCJqyqqe!O%d*f{d8|oGX{f
zH_Suv3mK@qH1B8vK+`k&Q&(#8o8R5Q;tl{jx(NW-H~{!ZHepu*AVdZL{9U)Vz7BBlpZ&78A_D*zD-CrttuWJ@IC`_J
zdz?cEsrt$;2h>ze+vb>ET}#%?R{K)^kA!CygrXM_svABy%nJSh#izt#6YCS&>nb4cjI-x2e0
zcOlltIyce`{s(Z1rWQfpnvE4GjD&B^cDb(*ivsWMujF52#~*%s^EMf*-iciMBDy_r
zOr&R*$nkb}mvf!RhuXAw5!(x3!R1r6_AQmHS)~&-wsMJWzM~r(8@!E9O>SZze&cWj
zE1?*S^6~L8)$!jG{Xp8QvwYm#;SdRljTZlzb~a842?=Q-AtAVojLcgiQJS^r5EUHk
z=oT1w@g=Oi-L&>@*B*tJzJ7}WMtzMYJ2{!&vD+`VDD`x
zf;y`Y27y4-)zr>eU7m2W(tLmLw;5Sip>N?tkn;KZ$93wkPpzt_#;7~!9FJ_SzQ1UO
zY8O+Js+ST{#?auyvnwioAG3{(`Qh@pC^$p~Jeu2lCmn7asL`4_u`n|~X9I&LvtHzf
z<%hXUW{Iael%=@@^=a&CeN5cPVv+$O*=>f!T5eBvi6N;N)1BL@v}e*n9q+Agm`sSwsYW
z)*rp#74=V4lk2IFp_~3Q?xmyfkdVXN!WvsP3R3=N$oN#t*>GP=Fe6XiIRc%dsKo=wvoLe%Y8u3ZCY2oX;Y^gYMV=4_2~qKTvZq_Jn#_@piD&FbBC0J^
zh&b;yhq)BeT%THV;oX=$B?!m49;Gr!T5(z{ZcUAmofpIPd>WAA0A8$xbo)H&jf^pA
z=s4c{CW+c#84c@W!sfpGob$S7h+?k=GpVT22aTDZjYXej3<~#agS1)&jH8?_Eou0B
zv7Gez4Ea{P*lnN|{xJq%G7s(QQvJ+{K2MhBUW!f*Ts_P<
z30&|j@8THEw)+pI6Yg$I|e_>9P3cLiF{wjOr^@^a=1Mpap
z*YTdQsuO~5eE!?FKrxroh@yd;tS;2F_A2lP%WoEZO>uvgGiVgwv-55sykG}#0f>H5z&C~TgyUb|+0e6;BS1B&w6oQufT9mFRz8?@W7AP_{
zak^Bn+>IX|9E|Pg@tdf&wv3I5*)xea?-G4K@dD_v2T>VO)-nr7wCE{m9{*Pt#}`)JaKUb9q+5_ghMEaL8O{US2iVtc;YLJesohkOB8J
zjU=X_TGia%Bzx&+N6D*KaH;n8+Yg6uEO~(x_nG~;Ut1@IM}%X%&x5_az5Sj1>qPBa
z;)23;KyQa=>nTAT9Ph~*r93O5^!FdH%7fF+s?C5)i+#1)-z1*)FRQ21F8}y=+9@0s
zc9N*%_njGs%L*~t`eUfwQ!|00{NrX`(Zb(J=Y#h5_a7?h=@Ed58jep)iAF&+U$Foa
z^or4oVj6|vtsh=Ti&)c_ptk!4ItV$R7S32V{pD5Bbg!+k_}<Mx2IjH#U_lOfaaRAdHl%Dn6R;0YpTeOKv3@
z>svRzJ_?Z#*BQ?4qA}HY;r-hyG!$(G)oPJ-{{~cx`$>?K2TaH?Bps33Rx2x$!?~Um
zNU;>I>AyQ=>9=@SE42Gd357zb>$
z#7|z&n*e&WM6Rq**}3NKpDN8?|MBY=8@AonrX#6ia%cW3HX?;wOz6^kdf*F)gPP5y
zrL0pcG;E1uekL{Gb2y`oQBYRdnW(6$4k0Z)y(C@kaiCuq6R@M*3f1-+?#_oxU|hVI1wgv%oHKAsT$yix(@Hm%i
zY(9Rb#O-l8tfsWH({$`--|e#7z^oyaU9r~52r6RdHQ^bU9d#H%#4%i;qXrktGQ}oD
z8b!q@YmM)fRs^5;T3T8PFN6ZVe9b=cPOH3|(Ik^%MZ4lPL>9C>_@QU2u1;`rZ1*!{_g!+!Jh1u-^qXY#lkW^VZ1^(QbD!IetEs6
z&*#*tM|upGK}S#jHsbyJM0FG6@V_pe>$26K9PTgM-2n654_wUN&R#nh?
z$NNb`EUK~N_ixpijkz9@7(sP9Q+$zzaDCI?A4kseh2F`>>6w|T5`Clad`P#J`XOom
zq><9Td3Mmc?-zUE+I=)BK*;slqeqV_OZ2r*pHlM{+BP_!A(6-f(Kwq7Jjt<^lJ^Wg
zC`|0_wOUed{lw-rqow^i5X}%>XB?T!hF1cx&A7r_D3s|7A1GlcT
zx9a(VKwZHirp*$|KbBRi#l*L1T+U6D>iF$Gu@;(}-M!0=SPdwA{=Bigx%uH6ZTWdL
z{d~{uPxnW?1x_IlQPIc0cyE13&q!a%evuVzxSd3qKsvgwkk=JnBtyYVUB;)}50!)3
z{W(7)W@cu7-PL@WM(Kx-YGlL~72^_KRTdO*XQ!pfR+V;QwhfMAQ%OuW2X2n*?U-A+
zx(d`Z)VN>yOh2ZeL`t#3kOR}jG@3E%c1H!ZT3cEUR@c;2$SWz7@f$PcaF(YIU9NZ>
zvvURatc;oBs&8-5k~DBvow7I=(Ynhj8EN|*$aeDcS0H>Fm*eA|^%#&8lf<4N%q!$7
z2KCu)4wh;lii9b!p{5tOtbKd2&^n!xwShN1Diu>whTqz~BDc{*HQbSS>~C%@tJJ)pI&s}TxFvb9YM
z?tC`@oUT6m+gd>uaV^Wb{JH9?N|8?!7PH&;ulm9at
z-bjm7K4YSv)tQ=~EC!LMSYf2D9n!-Uc~8at;XN_{P#LJ4qzqINDr+UHtfBx@k%fuN
m$f(H3KrpH!|BK)s=;7lT`u`K`h2zj<0$`|XqEoNs67ydlWO>g3
literal 0
HcmV?d00001
diff --git a/resources/kiwix.qrc b/resources/kiwix.qrc
index 6b48a41..6ec4783 100644
--- a/resources/kiwix.qrc
+++ b/resources/kiwix.qrc
@@ -56,5 +56,6 @@
icons/new-tab-icon.svg
icons/library-icon.svg
icons/open-file.svg
+ icons/placeholder-icon.png
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index 21fbe59..6f6309f 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -71,7 +71,11 @@ QList> ContentManager::getBooksList()
QPixmap pixmap;
pixmap.loadFromData((const uchar*)favicon.data(), favicon.size());
bookIcon = QIcon(pixmap);
- } catch (std::out_of_range& e) {}
+ } catch (zim::EntryNotFound &e) {
+ bookIcon = QIcon(":/icons/placeholder-icon.png");
+ } catch (std::out_of_range &e) {
+ bookIcon = QIcon(":/icons/placeholder-icon.png");
+ }
auto mp = getBookInfos(bookId, keys);
mp["icon"] = bookIcon;
bookList.append(mp);
diff --git a/src/contentmanagerdelegate.cpp b/src/contentmanagerdelegate.cpp
index 49d8f65..87e0094 100644
--- a/src/contentmanagerdelegate.cpp
+++ b/src/contentmanagerdelegate.cpp
@@ -4,6 +4,7 @@
#include
#include
#include "kiwixapp.h"
+#include
ContentManagerDelegate::ContentManagerDelegate(QObject *parent)
: QStyledItemDelegate(parent), baseButton(new QPushButton)
@@ -29,11 +30,27 @@ void ContentManagerDelegate::paint(QPainter *painter, const QStyleOptionViewItem
button.rect = QRect(x,y,w,h);
button.text = "Open";
button.state = QStyle::State_Enabled;
- if (index.column() == 5 && index.data(Qt::UserRole+1) == QVariant()) {
+ QStyleOptionViewItem eOpt = option;
+ if (index.data(Qt::UserRole+1) != QVariant()) {
+ // additional info role
+ QStyledItemDelegate::paint(painter, option, index);
+ return;
+ }
+ if (index.column() == 5) {
baseButton->style()->drawControl( QStyle::CE_PushButton, &button, painter, baseButton.data());
return;
}
- QStyledItemDelegate::paint(painter, option, index);
+ if (index.column() == 0) {
+ const auto icon = index.data().value();
+ icon.paint(painter, QRect(x+10, y+10, 30, 50));
+ return;
+ }
+ if (index.column() == 1) {
+ auto bFont = painter->font();
+ bFont.setBold(true);
+ eOpt.font = bFont;
+ }
+ QStyledItemDelegate::paint(painter, eOpt, index);
}
bool ContentManagerDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
From 9e5f187f9eecdf3ae217361dfb01f56a6917ecd8 Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Thu, 22 Jun 2023 12:54:12 +0530
Subject: [PATCH 08/29] Implemented dynamic row adding
The view only adds as many rows as they can be displayed now.
Only scrolling, it adds more
---
src/contentmanagermodel.cpp | 20 +++++++++++++++++++-
src/contentmanagermodel.h | 5 +++++
2 files changed, 24 insertions(+), 1 deletion(-)
diff --git a/src/contentmanagermodel.cpp b/src/contentmanagermodel.cpp
index ddfa54f..06e5632 100644
--- a/src/contentmanagermodel.cpp
+++ b/src/contentmanagermodel.cpp
@@ -82,7 +82,7 @@ QModelIndex ContentManagerModel::parent(const QModelIndex &index) const
int ContentManagerModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
- return m_data.size();
+ return zimCount;
}
QVariant ContentManagerModel::headerData(int section, Qt::Orientation orientation, int role) const
@@ -150,3 +150,21 @@ bool ContentManagerModel::hasChildren(const QModelIndex &parent) const
return true;
}
+bool ContentManagerModel::canFetchMore(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return false;
+ return (zimCount < m_data.size());
+}
+
+void ContentManagerModel::fetchMore(const QModelIndex &parent)
+{
+ if (parent.isValid())
+ return;
+ int remainder = m_data.size() - zimCount;
+ int zimsToFetch = qMin(5, remainder);
+ beginInsertRows(QModelIndex(), zimCount, zimCount + zimsToFetch - 1);
+ zimCount += zimsToFetch;
+ endInsertRows();
+}
+
diff --git a/src/contentmanagermodel.h b/src/contentmanagermodel.h
index 994f456..6d127b4 100644
--- a/src/contentmanagermodel.h
+++ b/src/contentmanagermodel.h
@@ -28,9 +28,14 @@ public:
void setupNodes();
bool hasChildren(const QModelIndex &parent) const override;
+protected:
+ bool canFetchMore(const QModelIndex &parent) const override;
+ void fetchMore(const QModelIndex &parent) override;
+
private:
QList> m_data;
Node *rootNode;
+ int zimCount = 0;
};
#endif // CONTENTMANAGERMODEL_H
From 5f4e7f95b860b1334cf7f7021a3c45175a652767 Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Thu, 22 Jun 2023 13:03:24 +0530
Subject: [PATCH 09/29] Implement sorting
The books can now be sorted by clicking the headers.
---
src/contentmanagermodel.cpp | 23 +++++++++++++++++++++++
src/contentmanagermodel.h | 1 +
src/node.h | 1 +
3 files changed, 25 insertions(+)
diff --git a/src/contentmanagermodel.cpp b/src/contentmanagermodel.cpp
index 06e5632..d12674d 100644
--- a/src/contentmanagermodel.cpp
+++ b/src/contentmanagermodel.cpp
@@ -5,6 +5,7 @@
#include
#include
#include
+#include "kiwixapp.h"
ContentManagerModel::ContentManagerModel(QObject *parent)
@@ -168,3 +169,25 @@ void ContentManagerModel::fetchMore(const QModelIndex &parent)
endInsertRows();
}
+void ContentManagerModel::sort(int column, Qt::SortOrder order)
+{
+ if (column == 0 || column == 4 || column == 5)
+ return;
+
+ QString sortBy = "";
+ switch(column) {
+ case 1:
+ sortBy = "title";
+ break;
+ case 2:
+ sortBy = "date";
+ break;
+ case 3:
+ sortBy = "size";
+ break;
+ default:
+ sortBy = "unsorted";
+ }
+ KiwixApp::instance()->getContentManager()->setSortBy(sortBy, order == Qt::AscendingOrder);
+}
+
diff --git a/src/contentmanagermodel.h b/src/contentmanagermodel.h
index 6d127b4..ec0b058 100644
--- a/src/contentmanagermodel.h
+++ b/src/contentmanagermodel.h
@@ -27,6 +27,7 @@ public:
void setBooksData(const QList>& data);
void setupNodes();
bool hasChildren(const QModelIndex &parent) const override;
+ void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
protected:
bool canFetchMore(const QModelIndex &parent) const override;
diff --git a/src/node.h b/src/node.h
index cf03ea4..a656795 100644
--- a/src/node.h
+++ b/src/node.h
@@ -3,6 +3,7 @@
#include
#include
+#include "contentmanagermodel.h"
class Node
{
From d95347fc882d1c81fb8cd842913417c246f1fa56 Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Thu, 22 Jun 2023 13:10:00 +0530
Subject: [PATCH 10/29] Styles for headers
Added new styles for headers
---
resources/css/_contentManager.css | 39 +++++++++++++++++++++++++++
resources/icons/caret-down-solid.svg | 1 +
resources/icons/caret-right-solid.svg | 1 +
resources/icons/caret-up-solid.svg | 1 +
resources/kiwix.qrc | 3 +++
src/contentmanager.cpp | 3 +++
src/contentmanagerview.cpp | 4 +++
7 files changed, 52 insertions(+)
create mode 100644 resources/icons/caret-down-solid.svg
create mode 100644 resources/icons/caret-right-solid.svg
create mode 100644 resources/icons/caret-up-solid.svg
diff --git a/resources/css/_contentManager.css b/resources/css/_contentManager.css
index e69de29..96f1569 100644
--- a/resources/css/_contentManager.css
+++ b/resources/css/_contentManager.css
@@ -0,0 +1,39 @@
+QTreeView::branch:open:has-children {
+ image: url(:/icons/caret-down-solid.svg);
+ margin: 7px;
+}
+
+QTreeView::branch:closed:has-children {
+ image: url(:/icons/caret-right-solid.svg);
+ margin: 7px;
+}
+
+QTreeView::item:has-children {
+ border-bottom: 1px solid #b7bec9;
+}
+
+QTreeView {
+ font-family: 'Selawik';
+ padding: 4px;
+}
+
+QHeaderView::section {
+ color: grey;
+ background-color: #fff;
+ border-width: 0px 0px 2px;
+ border-color: black;
+ border-style: plain;
+ font-size: 16px;
+ font-family: 'Selawik';
+ padding: 4px;
+}
+
+QHeaderView::down-arrow {
+ image: url(:/icons/caret-down-solid.svg);
+ margin: 5px;
+}
+
+QHeaderView::up-arrow {
+ image: url(:/icons/caret-up-solid.svg);
+ margin: 5px;
+}
diff --git a/resources/icons/caret-down-solid.svg b/resources/icons/caret-down-solid.svg
new file mode 100644
index 0000000..874a1e7
--- /dev/null
+++ b/resources/icons/caret-down-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/icons/caret-right-solid.svg b/resources/icons/caret-right-solid.svg
new file mode 100644
index 0000000..55be097
--- /dev/null
+++ b/resources/icons/caret-right-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/icons/caret-up-solid.svg b/resources/icons/caret-up-solid.svg
new file mode 100644
index 0000000..f8ca589
--- /dev/null
+++ b/resources/icons/caret-up-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/kiwix.qrc b/resources/kiwix.qrc
index 6ec4783..db01fa1 100644
--- a/resources/kiwix.qrc
+++ b/resources/kiwix.qrc
@@ -57,5 +57,8 @@
icons/library-icon.svg
icons/open-file.svg
icons/placeholder-icon.png
+ icons/caret-down-solid.svg
+ icons/caret-right-solid.svg
+ icons/caret-up-solid.svg
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index 6f6309f..d4469c3 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -42,6 +42,9 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
header->setSectionsClickable(true);
header->setHighlightSections(true);
mp_view->setWordWrap(true);
+ mp_view->resizeColumnToContents(4);
+ mp_view->setColumnWidth(0, 80);
+ // TODO: set width for all columns based on viewport
setCurrentLanguage(QLocale().name().split("_").at(0));
connect(mp_library, &Library::booksChanged, this, [=]() {emit(this->booksChanged());});
diff --git a/src/contentmanagerview.cpp b/src/contentmanagerview.cpp
index dacfd2b..c7150af 100644
--- a/src/contentmanagerview.cpp
+++ b/src/contentmanagerview.cpp
@@ -9,4 +9,8 @@ ContentManagerView::ContentManagerView(QWidget *parent)
setSortingEnabled(true);
auto managerDelegate = new ContentManagerDelegate();
setItemDelegate(managerDelegate);
+ QFile file(QString::fromUtf8(":/css/_contentManager.css"));
+ file.open(QFile::ReadOnly);
+ QString styleSheet = QString(file.readAll());
+ setStyleSheet(styleSheet);
}
From 24117ea581e753f5200c347846fc3f2220c5f8e2 Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Thu, 22 Jun 2023 23:52:00 +0530
Subject: [PATCH 11/29] Context menu for book rows on right click
Added a menu on right click to show options.
Current actions supported:
1. Open Book (if the library is showing local files)
2. Download Book (if the library is showing remote files)
3. Delete Book
---
resources/css/_contentManager.css | 20 ++++++++++++++++--
src/contentmanager.cpp | 35 +++++++++++++++++++++++++++++++
src/contentmanager.h | 1 +
src/contentmanagerdelegate.cpp | 2 +-
src/contentmanagermodel.cpp | 5 ++---
src/contentmanagerview.cpp | 1 +
src/node.cpp | 4 ++--
src/node.h | 4 +++-
8 files changed, 63 insertions(+), 9 deletions(-)
diff --git a/resources/css/_contentManager.css b/resources/css/_contentManager.css
index 96f1569..a04c016 100644
--- a/resources/css/_contentManager.css
+++ b/resources/css/_contentManager.css
@@ -9,7 +9,7 @@ QTreeView::branch:closed:has-children {
}
QTreeView::item:has-children {
- border-bottom: 1px solid #b7bec9;
+ border-bottom: 1px solid #cccccc;
}
QTreeView {
@@ -18,7 +18,7 @@ QTreeView {
}
QHeaderView::section {
- color: grey;
+ color: #666666;
background-color: #fff;
border-width: 0px 0px 2px;
border-color: black;
@@ -37,3 +37,19 @@ QHeaderView::up-arrow {
image: url(:/icons/caret-up-solid.svg);
margin: 5px;
}
+
+QMenu {
+ background-color: white;
+ margin: 2px;
+ font-family: 'Selawik';
+}
+
+QMenu::item {
+ padding: 2px 25px 2px 20px;
+ border-bottom: 1px solid #cccccc;
+}
+
+QMenu::item:selected {
+ background-color: #cccccc;
+}
+
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index d4469c3..061b88a 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -16,6 +16,7 @@
#include
#include
#include "contentmanagerdelegate.h"
+#include "node.h"
ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader, QObject *parent)
: QObject(parent),
@@ -54,6 +55,7 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
managerModel->setBooksData(nBookList);
});
connect(&m_remoteLibraryManager, &OpdsRequestManager::requestReceived, this, &ContentManager::updateRemoteLibrary);
+ connect(mp_view, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onCustomContextMenu(const QPoint &)));
}
QList> ContentManager::getBooksList()
@@ -86,6 +88,39 @@ QList> ContentManager::getBooksList()
return bookList;
}
+void ContentManager::onCustomContextMenu(const QPoint &point)
+{
+ QModelIndex index = mp_view->indexAt(point);
+ QMenu contextMenu("optionsMenu", mp_view);
+ Node* bookNode = static_cast(index.internalPointer());
+ const auto id = bookNode->getBookId();
+
+ QAction menuDeleteBook("Delete book", this);
+ QAction menuOpenBook("Open book", this);
+ QAction menuDownloadBook("Download book", this);
+
+ connect(&menuDeleteBook, &QAction::triggered, [=]() {
+ eraseBook(id);
+ emit(booksChanged());
+ });
+ connect(&menuOpenBook, &QAction::triggered, [=]() {
+ openBook(id);
+ });
+ connect(&menuDownloadBook, &QAction::triggered, [=]() {
+ downloadBook(id);
+ });
+
+ if (m_local)
+ contextMenu.addAction(&menuOpenBook);
+ else
+ contextMenu.addAction(&menuDownloadBook);
+ contextMenu.addAction(&menuDeleteBook);
+
+ if (index.isValid()) {
+ contextMenu.exec(mp_view->viewport()->mapToGlobal(point));
+ }
+}
+
void ContentManager::setLocal(bool local) {
if (local == m_local) {
return;
diff --git a/src/contentmanager.h b/src/contentmanager.h
index b3b58fd..8250ace 100644
--- a/src/contentmanager.h
+++ b/src/contentmanager.h
@@ -68,6 +68,7 @@ public slots:
void pauseBook(const QString& id);
void resumeBook(const QString& id);
void cancelBook(const QString& id);
+ void onCustomContextMenu(const QPoint &point);
};
#endif // CONTENTMANAGER_H
diff --git a/src/contentmanagerdelegate.cpp b/src/contentmanagerdelegate.cpp
index 87e0094..24bcb9c 100644
--- a/src/contentmanagerdelegate.cpp
+++ b/src/contentmanagerdelegate.cpp
@@ -47,7 +47,7 @@ void ContentManagerDelegate::paint(QPainter *painter, const QStyleOptionViewItem
}
if (index.column() == 1) {
auto bFont = painter->font();
- bFont.setBold(true);
+ bFont.setWeight(60);
eOpt.font = bFont;
}
QStyledItemDelegate::paint(painter, eOpt, index);
diff --git a/src/contentmanagermodel.cpp b/src/contentmanagermodel.cpp
index d12674d..88ae9da 100644
--- a/src/contentmanagermodel.cpp
+++ b/src/contentmanagermodel.cpp
@@ -135,8 +135,8 @@ void ContentManagerModel::setupNodes()
auto id = bookItem["id"].toString();
auto description = bookItem["description"].toString();
auto icon = bookItem["icon"];
- const auto temp = new Node({icon, name, date, size, content, id}, rootNode);
- const auto tempsTemp = new Node({"", description, "", "", "", ""}, temp, true);
+ const auto temp = new Node({icon, name, date, size, content, id}, rootNode, id);
+ const auto tempsTemp = new Node({"", description, "", "", "", ""}, temp, "", true);
temp->appendChild(tempsTemp);
rootNode->appendChild(temp);
}
@@ -190,4 +190,3 @@ void ContentManagerModel::sort(int column, Qt::SortOrder order)
}
KiwixApp::instance()->getContentManager()->setSortBy(sortBy, order == Qt::AscendingOrder);
}
-
diff --git a/src/contentmanagerview.cpp b/src/contentmanagerview.cpp
index c7150af..c227c51 100644
--- a/src/contentmanagerview.cpp
+++ b/src/contentmanagerview.cpp
@@ -13,4 +13,5 @@ ContentManagerView::ContentManagerView(QWidget *parent)
file.open(QFile::ReadOnly);
QString styleSheet = QString(file.readAll());
setStyleSheet(styleSheet);
+ setContextMenuPolicy(Qt::CustomContextMenu);
}
diff --git a/src/node.cpp b/src/node.cpp
index c409fd0..dbdbfcf 100644
--- a/src/node.cpp
+++ b/src/node.cpp
@@ -1,7 +1,7 @@
#include "node.h"
-Node::Node(const QList &data, Node *parent, bool isAdditional)
- : m_itemData(data), m_parentItem(parent), m_isAdditonal(isAdditional)
+Node::Node(const QList &data, Node *parent, QString bookId, bool isAdditional)
+ : m_itemData(data), m_parentItem(parent), m_isAdditonal(isAdditional), m_bookId(bookId)
{}
Node::~Node()
diff --git a/src/node.h b/src/node.h
index a656795..00ee70b 100644
--- a/src/node.h
+++ b/src/node.h
@@ -8,7 +8,7 @@
class Node
{
public:
- explicit Node(const QList &data, Node *parentItem = nullptr, bool isAdditional = false);
+ explicit Node(const QList &data, Node *parentItem = nullptr, QString bookId = "", bool isAdditional = false);
~Node();
void appendChild(Node *child);
Node *child(int row);
@@ -18,12 +18,14 @@ public:
int row() const;
Node *parentItem();
bool isAdditonal() const { return m_isAdditonal; }
+ QString getBookId() const { return m_bookId; }
private:
QList m_itemData;
Node *m_parentItem;
QList m_childItems;
bool m_isAdditonal;
+ QString m_bookId;
};
From 20f082f73b5f9faf86adbbd40f34a79e361b87e6 Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Sat, 24 Jun 2023 00:19:54 +0530
Subject: [PATCH 12/29] Search books function
Added a search book function in the library
---
kiwix-desktop.pro | 3 ++-
resources/css/_contentManager.css | 10 ++++++++++
src/contentmanager.cpp | 21 +++++++++++----------
src/contentmanagerview.cpp | 31 +++++++++++++++++++++++++------
src/contentmanagerview.h | 19 +++++++++++++++----
src/contentmanagerview.ui | 31 +++++++++++++++++++++++++++++++
src/mainwindow.h | 1 +
7 files changed, 95 insertions(+), 21 deletions(-)
create mode 100644 src/contentmanagerview.ui
diff --git a/kiwix-desktop.pro b/kiwix-desktop.pro
index e672486..a461787 100644
--- a/kiwix-desktop.pro
+++ b/kiwix-desktop.pro
@@ -72,6 +72,7 @@ SOURCES += \
HEADERS += \
src/contentmanagerdelegate.h \
src/contentmanagermodel.h \
+ src/contentmanagerview.h \
src/contenttypefilter.h \
src/findinpagebar.h \
src/node.h \
@@ -93,7 +94,6 @@ HEADERS += \
src/webpage.h \
src/about.h \
src/contentmanager.h \
- src/contentmanagerview.h \
src/tabbar.h \
src/contentmanagerside.h \
src/readinglistbar.h \
@@ -107,6 +107,7 @@ HEADERS += \
src/static_content.h
FORMS += \
+ src/contentmanagerview.ui \
src/findinpagebar.ui \
ui/mainwindow.ui \
ui/about.ui \
diff --git a/resources/css/_contentManager.css b/resources/css/_contentManager.css
index a04c016..ad3d371 100644
--- a/resources/css/_contentManager.css
+++ b/resources/css/_contentManager.css
@@ -15,6 +15,7 @@ QTreeView::item:has-children {
QTreeView {
font-family: 'Selawik';
padding: 4px;
+ border: none;
}
QHeaderView::section {
@@ -53,3 +54,12 @@ QMenu::item:selected {
background-color: #cccccc;
}
+QLineEdit {
+ font-family: 'Selawik';
+ padding: 4px;
+ border: 1px solid #cccccc;
+ color: #666666;
+ font-size: 16px;
+ height: 32px;
+ line-height: 24px;
+}
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index 061b88a..8181eb9 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -30,10 +30,11 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
auto managerModel = new ContentManagerModel();
const auto booksList = getBooksList();
managerModel->setBooksData(booksList);
- mp_view->setModel(managerModel);
- mp_view->show();
+ auto treeView = mp_view->getView();
+ treeView->setModel(managerModel);
+ treeView->show();
- auto header = mp_view->header();
+ auto header = treeView->header();
header->setSectionResizeMode(0, QHeaderView::Fixed);
header->setSectionResizeMode(1, QHeaderView::Stretch);
header->setSectionResizeMode(2, QHeaderView::Fixed);
@@ -42,9 +43,9 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
header->setStretchLastSection(false);
header->setSectionsClickable(true);
header->setHighlightSections(true);
- mp_view->setWordWrap(true);
- mp_view->resizeColumnToContents(4);
- mp_view->setColumnWidth(0, 80);
+ treeView->setWordWrap(true);
+ treeView->resizeColumnToContents(4);
+ treeView->setColumnWidth(0, 80);
// TODO: set width for all columns based on viewport
setCurrentLanguage(QLocale().name().split("_").at(0));
@@ -55,7 +56,7 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
managerModel->setBooksData(nBookList);
});
connect(&m_remoteLibraryManager, &OpdsRequestManager::requestReceived, this, &ContentManager::updateRemoteLibrary);
- connect(mp_view, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onCustomContextMenu(const QPoint &)));
+ connect(mp_view->getView(), SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onCustomContextMenu(const QPoint &)));
}
QList> ContentManager::getBooksList()
@@ -90,8 +91,8 @@ QList> ContentManager::getBooksList()
void ContentManager::onCustomContextMenu(const QPoint &point)
{
- QModelIndex index = mp_view->indexAt(point);
- QMenu contextMenu("optionsMenu", mp_view);
+ QModelIndex index = mp_view->getView()->indexAt(point);
+ QMenu contextMenu("optionsMenu", mp_view->getView());
Node* bookNode = static_cast(index.internalPointer());
const auto id = bookNode->getBookId();
@@ -117,7 +118,7 @@ void ContentManager::onCustomContextMenu(const QPoint &point)
contextMenu.addAction(&menuDeleteBook);
if (index.isValid()) {
- contextMenu.exec(mp_view->viewport()->mapToGlobal(point));
+ contextMenu.exec(mp_view->getView()->viewport()->mapToGlobal(point));
}
}
diff --git a/src/contentmanagerview.cpp b/src/contentmanagerview.cpp
index c227c51..71e2d7b 100644
--- a/src/contentmanagerview.cpp
+++ b/src/contentmanagerview.cpp
@@ -2,16 +2,35 @@
#include
#include "kiwixapp.h"
#include "contentmanagerdelegate.h"
+#include
+#include "ui_contentmanagerview.h"
ContentManagerView::ContentManagerView(QWidget *parent)
- : QTreeView(parent)
+ : QWidget(parent), mp_ui(new Ui::contentmanagerview)
{
- setSortingEnabled(true);
- auto managerDelegate = new ContentManagerDelegate();
- setItemDelegate(managerDelegate);
+ mp_ui->setupUi(this);
+ mp_ui->m_view->setSortingEnabled(true);
QFile file(QString::fromUtf8(":/css/_contentManager.css"));
file.open(QFile::ReadOnly);
QString styleSheet = QString(file.readAll());
- setStyleSheet(styleSheet);
- setContextMenuPolicy(Qt::CustomContextMenu);
+ mp_ui->m_view->setStyleSheet(styleSheet);
+ mp_ui->m_view->setContextMenuPolicy(Qt::CustomContextMenu);
+ auto managerDelegate = new ContentManagerDelegate();
+ mp_ui->m_view->setItemDelegate(managerDelegate);
+
+ auto searcher = mp_ui->searcher;
+ searcher->setPlaceholderText(gt("search-files"));
+ searcher->setStyleSheet(styleSheet);
+
+ QIcon searchIcon = QIcon(":/icons/search.svg");
+ searcher->addAction(searchIcon, QLineEdit::LeadingPosition);
+
+ connect(searcher, &QLineEdit::textChanged, [searcher](){
+ KiwixApp::instance()->getContentManager()->setSearch(searcher->text());
+ });
+}
+
+ContentManagerView::~ContentManagerView()
+{
+
}
diff --git a/src/contentmanagerview.h b/src/contentmanagerview.h
index 1ee61dd..2ecbd6f 100644
--- a/src/contentmanagerview.h
+++ b/src/contentmanagerview.h
@@ -1,14 +1,25 @@
#ifndef CONTENTMANAGERVIEW_H
#define CONTENTMANAGERVIEW_H
-#include
-#include
+#include
+#include "ui_contentmanagerview.h"
-class ContentManagerView : public QTreeView
+namespace Ui {
+class contentmanagerview;
+}
+
+class ContentManagerView : public QWidget
{
Q_OBJECT
+
public:
- ContentManagerView(QWidget *parent = Q_NULLPTR);
+ explicit ContentManagerView(QWidget *parent = nullptr);
+ ~ContentManagerView();
+ QTreeView* getView() { return mp_ui->m_view; }
+ QLineEdit* &getSearcher() { return mp_ui->searcher; }
+
+private:
+ Ui::contentmanagerview *mp_ui;
};
#endif // CONTENTMANAGERVIEW_H
diff --git a/src/contentmanagerview.ui b/src/contentmanagerview.ui
new file mode 100644
index 0000000..013c6e9
--- /dev/null
+++ b/src/contentmanagerview.ui
@@ -0,0 +1,31 @@
+
+
+ contentmanagerview
+
+
+
+ 0
+ 0
+ 400
+ 300
+
+
+
+ Form
+
+
+ -
+
+
-
+
+
+ -
+
+
+
+
+
+
+
+
+
diff --git a/src/mainwindow.h b/src/mainwindow.h
index dba035f..5c34705 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -24,6 +24,7 @@ public:
TabBar* getTabBar();
TopWidget* getTopWidget();
+ QWidget getMainView();
protected:
void keyPressEvent(QKeyEvent *event);
From 3f72af494eccc291a80745891a5dd348fc8700e8 Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Sat, 24 Jun 2023 18:03:15 +0530
Subject: [PATCH 13/29] updateDownloadInfos now returns a map
updateDownloadInfos will now return a map - QMap - same as getBookInfos.
This helps to easier get the required fields
---
src/contentmanager.cpp | 30 +++++++++++++++---------------
src/contentmanager.h | 2 +-
2 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index 8181eb9..427903b 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -236,14 +236,14 @@ void ContentManager::openBook(const QString &id)
}
}
-#define ADD_V(KEY, METH) {if(key==KEY) {values.append(QString::fromStdString((d->METH()))); continue;}}
-QStringList ContentManager::updateDownloadInfos(QString id, const QStringList &keys)
+#define ADD_V(KEY, METH) {if(key==KEY) {values.insert(key, QString::fromStdString((d->METH()))); continue;}}
+QMap ContentManager::updateDownloadInfos(QString id, const QStringList &keys)
{
- QStringList values;
+ QMap values;
if (!mp_downloader) {
for(auto& key: keys) {
(void) key;
- values.append("");
+ values.insert(key, "");
}
return values;
}
@@ -283,41 +283,41 @@ QStringList ContentManager::updateDownloadInfos(QString id, const QStringList &k
if(key == "status") {
switch(d->getStatus()){
case kiwix::Download::K_ACTIVE:
- values.append("active");
+ values.insert(key, "active");
break;
case kiwix::Download::K_WAITING:
- values.append("waiting");
+ values.insert(key, "waiting");
break;
case kiwix::Download::K_PAUSED:
- values.append("paused");
+ values.insert(key, "paused");
break;
case kiwix::Download::K_ERROR:
- values.append("error");
+ values.insert(key, "error");
break;
case kiwix::Download::K_COMPLETE:
- values.append("completed");
+ values.insert(key, "completed");
break;
case kiwix::Download::K_REMOVED:
- values.append("removed");
+ values.insert(key, "removed");
break;
default:
- values.append("unknown");
+ values.insert(key, "unknown");
}
continue;
}
ADD_V("followedBy", getFollowedBy);
ADD_V("path", getPath);
if(key == "totalLength") {
- values.append(QString::number(d->getTotalLength()));
+ values.insert(key, QString::number(d->getTotalLength()));
}
if(key == "completedLength") {
- values.append(QString::number(d->getCompletedLength()));
+ values.insert(key, QString::number(d->getCompletedLength()));
}
if(key == "downloadSpeed") {
- values.append(QString::number(d->getDownloadSpeed()));
+ values.insert(key, QString::number(d->getDownloadSpeed()));
}
if(key == "verifiedLength") {
- values.append(QString::number(d->getVerifiedLength()));
+ values.insert(key, QString::number(d->getVerifiedLength()));
}
}
return values;
diff --git a/src/contentmanager.h b/src/contentmanager.h
index 8250ace..58d7219 100644
--- a/src/contentmanager.h
+++ b/src/contentmanager.h
@@ -58,7 +58,7 @@ public slots:
QStringList getTranslations(const QStringList &keys);
QMap getBookInfos(QString id, const QStringList &keys);
void openBook(const QString& id);
- QStringList updateDownloadInfos(QString id, const QStringList& keys);
+ QMap updateDownloadInfos(QString id, const QStringList& keys);
QString downloadBook(const QString& id);
void updateLibrary();
void setSearch(const QString& search);
From ef0227b08c73ae76dc8afb2573f4b701f1e2640f Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Sat, 24 Jun 2023 23:29:58 +0530
Subject: [PATCH 14/29] Downloading indicator in book list
If a book is downloading, it now shows "Downloading"
---
src/contentmanager.cpp | 8 +++++++
src/contentmanagerdelegate.cpp | 39 ++++++++++++++++++++++++----------
2 files changed, 36 insertions(+), 11 deletions(-)
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index 427903b..4801508 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -357,6 +357,14 @@ QString ContentManager::downloadBook(const QString &id)
bookCopy.setDownloadId(download->getDid());
mp_library->addBookToLibrary(bookCopy);
mp_library->save();
+ QTimer *timer = new QTimer(this);
+ connect(timer, &QTimer::timeout, this, [=](){
+ auto downloadInfos = updateDownloadInfos(id, {"status"});
+ if (!downloadInfos["status"].isValid()) {
+ timer->stop();
+ }
+ });
+ timer->start(1000);
emit(oneBookChanged(id));
return QString::fromStdString(download->getDid());
}
diff --git a/src/contentmanagerdelegate.cpp b/src/contentmanagerdelegate.cpp
index 24bcb9c..3007db6 100644
--- a/src/contentmanagerdelegate.cpp
+++ b/src/contentmanagerdelegate.cpp
@@ -5,6 +5,7 @@
#include
#include "kiwixapp.h"
#include
+#include "node.h"
ContentManagerDelegate::ContentManagerDelegate(QObject *parent)
: QStyledItemDelegate(parent), baseButton(new QPushButton)
@@ -28,8 +29,20 @@ void ContentManagerDelegate::paint(QPainter *painter, const QStyleOptionViewItem
w = r.width();
h = r.height();
button.rect = QRect(x,y,w,h);
- button.text = "Open";
button.state = QStyle::State_Enabled;
+ auto node = static_cast(index.internalPointer());
+ try {
+ const auto id = node->getBookId();
+ const auto book = KiwixApp::instance()->getLibrary()->getBookById(id);
+ if(KiwixApp::instance()->getContentManager()->getBookInfos(id, {"downloadId"})["downloadId"] != "") {
+ button.text = "Downloading";
+ button.state = QStyle::State_ReadOnly;
+ } else {
+ button.text = gt("open");
+ }
+ } catch (std::out_of_range& e) {
+ button.text = gt("download");
+ }
QStyleOptionViewItem eOpt = option;
if (index.data(Qt::UserRole+1) != QVariant()) {
// additional info role
@@ -61,20 +74,24 @@ bool ContentManagerDelegate::editorEvent(QEvent *event, QAbstractItemModel *mode
int clickX = e->x();
int clickY = e->y();
- QRect r = option.rect;//getting the rect of the cell
+ QRect r = option.rect;
int x,y,w,h;
- x = r.left();//the X coordinate
- y = r.top();//the Y coordinate
- w = r.width();//button width
- h = r.height();//button height
-
+ x = r.left();
+ y = r.top();
+ w = r.width();
+ h = r.height();
+ const auto node = static_cast(index.internalPointer());
+ const auto id = node->getBookId();
if(index.column() == 5 && clickX > x && clickX < x + w )
- if( clickY > y && clickY < y + h )
+ if( clickY > y && clickY < y + h && KiwixApp::instance()->getContentManager()->getBookInfos(id, {"downloadId"})["downloadId"] == "")
{
- const auto modeel = index.model();
- const auto id = modeel->data(index).toString();
- KiwixApp::instance()->getContentManager()->openBook(id);
+ try {
+ const auto book = KiwixApp::instance()->getLibrary()->getBookById(id);
+ KiwixApp::instance()->getContentManager()->openBook(id);
+ } catch (std::out_of_range& e) {
+ KiwixApp::instance()->getContentManager()->downloadBook(id);
+ }
}
}
From ff5f022cd65dbb672f1b1a454d9c4b49f049f533 Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Mon, 26 Jun 2023 00:11:11 +0530
Subject: [PATCH 15/29] Icon downloader util for all files view
Added an icon downloader to display icons for remote library.
While the icons are being downloaded, a placeholder icon is shown.
---
kiwix-desktop.pro | 2 +
src/contentmanager.cpp | 22 ++------
src/contentmanager.h | 3 ++
src/contentmanagerdelegate.cpp | 11 +++-
src/contentmanagerdelegate.h | 2 +
src/contentmanagermodel.cpp | 52 +++++++++++++++++--
src/contentmanagermodel.h | 8 +++
src/node.h | 2 +
src/rownode.cpp | 92 ++++++++++++++++++++++++++++++++++
src/thumbnaildownloader.cpp | 52 +++++++++++++++++++
src/thumbnaildownloader.h | 37 ++++++++++++++
11 files changed, 260 insertions(+), 23 deletions(-)
create mode 100644 src/rownode.cpp
create mode 100644 src/thumbnaildownloader.cpp
create mode 100644 src/thumbnaildownloader.h
diff --git a/kiwix-desktop.pro b/kiwix-desktop.pro
index a461787..0f351cc 100644
--- a/kiwix-desktop.pro
+++ b/kiwix-desktop.pro
@@ -40,6 +40,7 @@ SOURCES += \
src/findinpagebar.cpp \
src/node.cpp \
src/suggestionlistworker.cpp \
+ src/thumbnaildownloader.cpp \
src/translation.cpp \
src/main.cpp \
src/mainwindow.cpp \
@@ -77,6 +78,7 @@ HEADERS += \
src/findinpagebar.h \
src/node.h \
src/suggestionlistworker.h \
+ src/thumbnaildownloader.h \
src/translation.h \
src/mainwindow.h \
src/kiwixapp.h \
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index 4801508..09d2d5c 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -27,7 +27,7 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
// mp_view will be passed to the tab who will take ownership,
// so, we don't need to delete it.
mp_view = new ContentManagerView();
- auto managerModel = new ContentManagerModel();
+ managerModel = new ContentManagerModel(this);
const auto booksList = getBooksList();
managerModel->setBooksData(booksList);
auto treeView = mp_view->getView();
@@ -54,6 +54,7 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
connect(this, &ContentManager::booksChanged, this, [=]() {
const auto nBookList = getBooksList();
managerModel->setBooksData(nBookList);
+ managerModel->refreshIcons();
});
connect(&m_remoteLibraryManager, &OpdsRequestManager::requestReceived, this, &ContentManager::updateRemoteLibrary);
connect(mp_view->getView(), SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onCustomContextMenu(const QPoint &)));
@@ -63,27 +64,10 @@ QList> ContentManager::getBooksList()
{
const auto bookIds = getBookIds();
QList> bookList;
- QStringList keys = {"title", "tags", "date", "id", "size", "description"};
- auto app = KiwixApp::instance();
- std::shared_ptr archive;
+ QStringList keys = {"title", "tags", "date", "id", "size", "description", "faviconUrl"};
QIcon bookIcon;
for (auto bookId : bookIds) {
- try {
- archive = app->getLibrary()->getArchive(bookId);
- std::string favicon, _mimetype;
- auto item = archive->getIllustrationItem(48);
- favicon = item.getData();
- _mimetype = item.getMimetype();
- QPixmap pixmap;
- pixmap.loadFromData((const uchar*)favicon.data(), favicon.size());
- bookIcon = QIcon(pixmap);
- } catch (zim::EntryNotFound &e) {
- bookIcon = QIcon(":/icons/placeholder-icon.png");
- } catch (std::out_of_range &e) {
- bookIcon = QIcon(":/icons/placeholder-icon.png");
- }
auto mp = getBookInfos(bookId, keys);
- mp["icon"] = bookIcon;
bookList.append(mp);
}
return bookList;
diff --git a/src/contentmanager.h b/src/contentmanager.h
index 58d7219..c35ae03 100644
--- a/src/contentmanager.h
+++ b/src/contentmanager.h
@@ -8,6 +8,7 @@
#include
#include "opdsrequestmanager.h"
#include "contenttypefilter.h"
+#include "contentmanagermodel.h"
class ContentManager : public QObject
{
@@ -26,6 +27,7 @@ public:
void setCurrentLanguage(QString language);
void setCurrentCategoryFilter(QString category);
void setCurrentContentTypeFilter(QList& contentTypeFilter);
+ bool isLocal() const { return m_local; }
private:
Library* mp_library;
@@ -44,6 +46,7 @@ private:
QStringList getBookIds();
void eraseBookFilesFromComputer(const QString dirPath, const QString filename);
QList> getBooksList();
+ ContentManagerModel *managerModel;
signals:
void filterParamsChanged();
diff --git a/src/contentmanagerdelegate.cpp b/src/contentmanagerdelegate.cpp
index 3007db6..ef3fe0b 100644
--- a/src/contentmanagerdelegate.cpp
+++ b/src/contentmanagerdelegate.cpp
@@ -16,6 +16,10 @@ ContentManagerDelegate::ContentManagerDelegate(QObject *parent)
"font-family: Selawik;"
"color: blue;"
"margin: 4px;");
+ QImage placeholderIconFile(":/icons/placeholder-icon.png");
+ QBuffer buffer(&placeholderIcon);
+ buffer.open(QIODevice::WriteOnly);
+ placeholderIconFile.save(&buffer, "png");
}
@@ -54,7 +58,12 @@ void ContentManagerDelegate::paint(QPainter *painter, const QStyleOptionViewItem
return;
}
if (index.column() == 0) {
- const auto icon = index.data().value();
+ auto iconData = index.data().value();
+ if (iconData.isNull())
+ iconData = placeholderIcon;
+ QPixmap pix;
+ pix.loadFromData(iconData);
+ QIcon icon(pix);
icon.paint(painter, QRect(x+10, y+10, 30, 50));
return;
}
diff --git a/src/contentmanagerdelegate.h b/src/contentmanagerdelegate.h
index 0e9f474..0d820ba 100644
--- a/src/contentmanagerdelegate.h
+++ b/src/contentmanagerdelegate.h
@@ -3,6 +3,7 @@
#include
#include
+#include
class ContentManagerDelegate : public QStyledItemDelegate
{
@@ -16,6 +17,7 @@ public:
private:
QScopedPointer baseButton;
+ QByteArray placeholderIcon;
};
#endif // CONTENTMANAGERDELEGATE_H
diff --git a/src/contentmanagermodel.cpp b/src/contentmanagermodel.cpp
index 88ae9da..fb975cd 100644
--- a/src/contentmanagermodel.cpp
+++ b/src/contentmanagermodel.cpp
@@ -5,12 +5,15 @@
#include
#include
#include
+#include
+#include
+#include
#include "kiwixapp.h"
-
ContentManagerModel::ContentManagerModel(QObject *parent)
: QAbstractItemModel(parent)
{
+ connect(&td, &ThumbnailDownloader::oneThumbnailDownloaded, this, &ContentManagerModel::updateImage);
}
ContentManagerModel::~ContentManagerModel()
@@ -126,6 +129,7 @@ QString convertToUnits(QString size)
void ContentManagerModel::setupNodes()
{
+ QByteArray bookIcon;
beginResetModel();
for (auto bookItem : m_data) {
auto name = bookItem["title"].toString();
@@ -134,8 +138,20 @@ void ContentManagerModel::setupNodes()
auto content = bookItem["tags"].toString();
auto id = bookItem["id"].toString();
auto description = bookItem["description"].toString();
- auto icon = bookItem["icon"];
- const auto temp = new Node({icon, name, date, size, content, id}, rootNode, id);
+ auto faviconUrl = "https://" + bookItem["faviconUrl"].toString();
+ try {
+ auto book = KiwixApp::instance()->getLibrary()->getBookById(id);
+ std::string favicon;
+ auto item = book.getIllustration(48);
+ favicon = item->getData();
+ bookIcon = QByteArray::fromRawData(reinterpret_cast(favicon.data()), favicon.size());
+ bookIcon.detach(); // deep copy
+ } catch (std::out_of_range &e) {
+ if (iconMap.contains(faviconUrl)) {
+ bookIcon = iconMap[faviconUrl];
+ }
+ }
+ const auto temp = new Node({bookIcon, name, date, size, content, id}, rootNode, id);
const auto tempsTemp = new Node({"", description, "", "", "", ""}, temp, "", true);
temp->appendChild(tempsTemp);
rootNode->appendChild(temp);
@@ -143,6 +159,27 @@ void ContentManagerModel::setupNodes()
endResetModel();
}
+void ContentManagerModel::refreshIcons()
+{
+ if (KiwixApp::instance()->getContentManager()->isLocal())
+ return;
+ td.clearQueue();
+ for (auto i = 0; i < rowCount() && i < m_data.size(); i++) {
+ auto bookItem = m_data[i];
+ auto id = bookItem["id"].toString();
+ auto faviconUrl = "https://" + bookItem["faviconUrl"].toString();
+ auto app = KiwixApp::instance();
+ try {
+ auto book = app->getLibrary()->getBookById(id);
+ auto item = book.getIllustration(48);
+ } catch (std::out_of_range &e) {
+ if (faviconUrl != "" && !iconMap.contains(faviconUrl)) {
+ td.addDownload(faviconUrl, index(i, 0));
+ }
+ }
+ }
+}
+
bool ContentManagerModel::hasChildren(const QModelIndex &parent) const
{
Node *item = static_cast(parent.internalPointer());
@@ -167,6 +204,7 @@ void ContentManagerModel::fetchMore(const QModelIndex &parent)
beginInsertRows(QModelIndex(), zimCount, zimCount + zimsToFetch - 1);
zimCount += zimsToFetch;
endInsertRows();
+ refreshIcons();
}
void ContentManagerModel::sort(int column, Qt::SortOrder order)
@@ -190,3 +228,11 @@ void ContentManagerModel::sort(int column, Qt::SortOrder order)
}
KiwixApp::instance()->getContentManager()->setSortBy(sortBy, order == Qt::AscendingOrder);
}
+
+void ContentManagerModel::updateImage(QModelIndex index, QString url, QByteArray imageData)
+{
+ Node *item = static_cast(index.internalPointer());
+ item->setIconData(imageData);
+ iconMap[url] = imageData;
+ emit dataChanged(index, index);
+}
diff --git a/src/contentmanagermodel.h b/src/contentmanagermodel.h
index ec0b058..1bf191a 100644
--- a/src/contentmanagermodel.h
+++ b/src/contentmanagermodel.h
@@ -4,6 +4,8 @@
#include
#include
#include
+#include
+#include "thumbnaildownloader.h"
class Node;
@@ -28,6 +30,10 @@ public:
void setupNodes();
bool hasChildren(const QModelIndex &parent) const override;
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
+ void refreshIcons();
+
+public slots:
+ void updateImage(QModelIndex index, QString url, QByteArray imageData);
protected:
bool canFetchMore(const QModelIndex &parent) const override;
@@ -37,6 +43,8 @@ private:
QList> m_data;
Node *rootNode;
int zimCount = 0;
+ ThumbnailDownloader td;
+ QMap iconMap;
};
#endif // CONTENTMANAGERMODEL_H
diff --git a/src/node.h b/src/node.h
index 00ee70b..27cb966 100644
--- a/src/node.h
+++ b/src/node.h
@@ -4,6 +4,7 @@
#include
#include
#include "contentmanagermodel.h"
+#include
class Node
{
@@ -19,6 +20,7 @@ public:
Node *parentItem();
bool isAdditonal() const { return m_isAdditonal; }
QString getBookId() const { return m_bookId; }
+ void setIconData(QByteArray iconData) { m_itemData[0] = iconData; }
private:
QList m_itemData;
diff --git a/src/rownode.cpp b/src/rownode.cpp
new file mode 100644
index 0000000..957c5cd
--- /dev/null
+++ b/src/rownode.cpp
@@ -0,0 +1,92 @@
+#include "rownode.h"
+#include
+#include "kiwixapp.h"
+#include "descriptionnode.h"
+#include "kiwix/tools.h"
+
+RowNode::RowNode(QList itemData, QString bookId, std::weak_ptr parent)
+ : m_itemData(itemData), m_parentItem(parent), m_bookId(bookId)
+{
+ m_downloadInfo = {0, "", "", false};
+}
+
+RowNode::~RowNode()
+{}
+
+void RowNode::appendChild(std::shared_ptr item)
+{
+ m_childItems.append(item);
+}
+
+std::shared_ptr RowNode::child(int row)
+{
+ if (row < 0 || row >= m_childItems.size())
+ return nullptr;
+ return m_childItems.at(row);
+}
+
+int RowNode::childCount() const
+{
+ return m_childItems.count();
+}
+
+int RowNode::columnCount() const
+{
+ return 6;
+}
+
+std::shared_ptr RowNode::parentItem()
+{
+ std::shared_ptr temp = m_parentItem.lock();
+ return temp;
+}
+
+QVariant RowNode::data(int column)
+{
+ if (column < 0 || column >= m_itemData.size())
+ return QVariant();
+ return m_itemData.at(column);
+}
+
+int RowNode::row() const
+{
+ try {
+ std::shared_ptr temp = m_parentItem.lock();
+ return temp->m_childItems.indexOf(std::const_pointer_cast(shared_from_this()));
+ } catch(...) {
+ return 0;
+ }
+
+ return 0;
+}
+
+std::shared_ptr RowNode::createNode(QMap bookItem, QMap iconMap, std::shared_ptr rootNode)
+{
+ auto faviconUrl = "https://" + bookItem["faviconUrl"].toString();
+ QString id = bookItem["id"].toString();
+ QByteArray bookIcon;
+ try {
+ auto book = KiwixApp::instance()->getLibrary()->getBookById(id);
+ std::string favicon;
+ auto item = book.getIllustration(48);
+ favicon = item->getData();
+ bookIcon = QByteArray::fromRawData(reinterpret_cast(favicon.data()), favicon.size());
+ bookIcon.detach(); // deep copy
+ } catch (...) {
+ bookIcon = QByteArray();
+ if (iconMap.contains(faviconUrl)) {
+ bookIcon = iconMap[faviconUrl];
+ }
+ }
+ std::weak_ptr weakRoot = rootNode;
+ auto rowNodePtr = std::make_shared(
+ RowNode({bookIcon, bookItem["title"],
+ bookItem["date"],
+ QString::fromStdString(kiwix::beautifyFileSize(bookItem["size"].toULongLong())),
+ bookItem["tags"]
+ }, id, weakRoot));
+ std::weak_ptr weakRowNodePtr = rowNodePtr;
+ const auto descNodePtr = std::make_shared(DescriptionNode(bookItem["description"].toString(), weakRowNodePtr));
+ rowNodePtr->appendChild(descNodePtr);
+ return rowNodePtr;
+}
diff --git a/src/thumbnaildownloader.cpp b/src/thumbnaildownloader.cpp
new file mode 100644
index 0000000..c496a14
--- /dev/null
+++ b/src/thumbnaildownloader.cpp
@@ -0,0 +1,52 @@
+#include "thumbnaildownloader.h"
+#include
+#include
+#include
+#include
+
+ThumbnailDownloader::ThumbnailDownloader(QObject *parent)
+{
+ connect(this, &ThumbnailDownloader::oneThumbnailDownloaded, [=]() {
+ if (m_urlPairList.size() != 0)
+ downloadOnePair(m_urlPairList.takeFirst());
+ else
+ m_isDownloading = false;
+ });
+}
+
+ThumbnailDownloader::~ThumbnailDownloader()
+{
+}
+
+void ThumbnailDownloader::addDownload(QString url, QModelIndex index)
+{
+ m_urlPairList.append({index, url});
+ if (!m_isDownloading)
+ startDownload();
+}
+
+void ThumbnailDownloader::startDownload()
+{
+ if (m_urlPairList.size() == 0) {
+ m_isDownloading = false;
+ return;
+ }
+ m_isDownloading = true;
+ downloadOnePair(m_urlPairList.takeFirst());
+}
+
+void ThumbnailDownloader::downloadOnePair(QPair urlPair)
+{
+ QNetworkRequest req(urlPair.second);
+ auto reply = manager.get(req);
+ connect(reply, &QNetworkReply::finished, this, [=](){
+ fileDownloaded(reply, urlPair);
+ });
+}
+
+void ThumbnailDownloader::fileDownloaded(QNetworkReply *pReply, QPair urlPair)
+{
+ auto downloadedData = pReply->readAll();
+ emit oneThumbnailDownloaded(urlPair.first, urlPair.second, downloadedData);
+ pReply->deleteLater();
+}
diff --git a/src/thumbnaildownloader.h b/src/thumbnaildownloader.h
new file mode 100644
index 0000000..cce710c
--- /dev/null
+++ b/src/thumbnaildownloader.h
@@ -0,0 +1,37 @@
+#ifndef THUMBNAILDOWNLOADER_H
+#define THUMBNAILDOWNLOADER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+class ThumbnailDownloader : public QObject
+{
+ Q_OBJECT
+
+public:
+ ThumbnailDownloader(QObject *parent = 0);
+ ~ThumbnailDownloader();
+
+ void addDownload(QString url, QModelIndex index);
+ void startDownload();
+ void downloadOnePair(QPair urlPair);
+ void clearQueue() { m_urlPairList.clear(); }
+
+signals:
+ void oneThumbnailDownloaded(QModelIndex, QString, QByteArray);
+
+private:
+ QQueue> m_urlPairList;
+ QNetworkAccessManager manager;
+ bool m_isDownloading = false;
+
+private slots:
+ void fileDownloaded(QNetworkReply *pReply, QPair urlPair);
+
+};
+
+#endif // THUMBNAILDOWNLOADER_H
From d2d0c18929fa95eb37db7b3be55795ccd38ae615 Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Tue, 27 Jun 2023 03:57:50 +0530
Subject: [PATCH 16/29] Download stats and pause, resume functions
Revamped download function as per design guidelines.
While downloading, it shows download speed and completed length along with button to pause
When paused, it shows the button to resume download or cancel it.
---
src/contentmanager.cpp | 70 ++++++++++---
src/contentmanager.h | 4 +
src/contentmanagerdelegate.cpp | 178 ++++++++++++++++++++++++++++++---
src/contentmanagerdelegate.h | 1 +
src/contentmanagermodel.cpp | 61 ++++++++++-
src/contentmanagermodel.h | 5 +
src/node.cpp | 4 +-
src/node.h | 14 +++
8 files changed, 305 insertions(+), 32 deletions(-)
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index 09d2d5c..f713ddf 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -46,6 +46,7 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader,
treeView->setWordWrap(true);
treeView->resizeColumnToContents(4);
treeView->setColumnWidth(0, 80);
+ treeView->setColumnWidth(5, 120);
// TODO: set width for all columns based on viewport
setCurrentLanguage(QLocale().name().split("_").at(0));
@@ -83,6 +84,25 @@ void ContentManager::onCustomContextMenu(const QPoint &point)
QAction menuDeleteBook("Delete book", this);
QAction menuOpenBook("Open book", this);
QAction menuDownloadBook("Download book", this);
+ QAction menuPauseBook("Pause download", this);
+ QAction menuResumeBook("Resume download", this);
+ QAction menuCancelBook("Cancel download", this);
+
+ if (bookNode->isDownloading()) {
+ if (bookNode->getDownloadInfo().paused) {
+ contextMenu.addAction(&menuResumeBook);
+ } else {
+ contextMenu.addAction(&menuPauseBook);
+ }
+ contextMenu.addAction(&menuCancelBook);
+ } else {
+ if (m_local) {
+ contextMenu.addAction(&menuOpenBook);
+ contextMenu.addAction(&menuDeleteBook);
+ }
+ else
+ contextMenu.addAction(&menuDownloadBook);
+ }
connect(&menuDeleteBook, &QAction::triggered, [=]() {
eraseBook(id);
@@ -92,14 +112,17 @@ void ContentManager::onCustomContextMenu(const QPoint &point)
openBook(id);
});
connect(&menuDownloadBook, &QAction::triggered, [=]() {
- downloadBook(id);
+ downloadBook(id, index);
+ });
+ connect(&menuPauseBook, &QAction::triggered, [=]() {
+ pauseBook(id, index);
+ });
+ connect(&menuCancelBook, &QAction::triggered, [=]() {
+ cancelBook(id, index);
+ });
+ connect(&menuResumeBook, &QAction::triggered, [=]() {
+ resumeBook(id, index);
});
-
- if (m_local)
- contextMenu.addAction(&menuOpenBook);
- else
- contextMenu.addAction(&menuDownloadBook);
- contextMenu.addAction(&menuDeleteBook);
if (index.isValid()) {
contextMenu.exec(mp_view->getView()->viewport()->mapToGlobal(point));
@@ -308,6 +331,13 @@ QMap ContentManager::updateDownloadInfos(QString id, const QS
}
#undef ADD_V
+QString ContentManager::downloadBook(const QString &id, QModelIndex index)
+{
+ emit managerModel->startDownload(index);
+ return downloadBook(id);
+}
+
+
QString ContentManager::downloadBook(const QString &id)
{
if (!mp_downloader)
@@ -341,14 +371,6 @@ QString ContentManager::downloadBook(const QString &id)
bookCopy.setDownloadId(download->getDid());
mp_library->addBookToLibrary(bookCopy);
mp_library->save();
- QTimer *timer = new QTimer(this);
- connect(timer, &QTimer::timeout, this, [=](){
- auto downloadInfos = updateDownloadInfos(id, {"status"});
- if (!downloadInfos["status"].isValid()) {
- timer->stop();
- }
- });
- timer->start(1000);
emit(oneBookChanged(id));
return QString::fromStdString(download->getDid());
}
@@ -383,6 +405,12 @@ void ContentManager::eraseBook(const QString& id)
KiwixApp::instance()->getSettingsManager()->deleteSettings(id);
}
+void ContentManager::pauseBook(const QString& id, QModelIndex index)
+{
+ pauseBook(id);
+ emit managerModel->pauseDownload(index);
+}
+
void ContentManager::pauseBook(const QString& id)
{
if (!mp_downloader) {
@@ -394,6 +422,12 @@ void ContentManager::pauseBook(const QString& id)
download->pauseDownload();
}
+void ContentManager::resumeBook(const QString& id, QModelIndex index)
+{
+ resumeBook(id);
+ emit managerModel->resumeDownload(index);
+}
+
void ContentManager::resumeBook(const QString& id)
{
if (!mp_downloader) {
@@ -405,6 +439,12 @@ void ContentManager::resumeBook(const QString& id)
download->resumeDownload();
}
+void ContentManager::cancelBook(const QString& id, QModelIndex index)
+{
+ cancelBook(id);
+ emit managerModel->cancelDownload(index);
+}
+
void ContentManager::cancelBook(const QString& id)
{
if (!mp_downloader) {
diff --git a/src/contentmanager.h b/src/contentmanager.h
index c35ae03..e351b41 100644
--- a/src/contentmanager.h
+++ b/src/contentmanager.h
@@ -63,6 +63,7 @@ public slots:
void openBook(const QString& id);
QMap updateDownloadInfos(QString id, const QStringList& keys);
QString downloadBook(const QString& id);
+ QString downloadBook(const QString& id, QModelIndex index);
void updateLibrary();
void setSearch(const QString& search);
void setSortBy(const QString& sortBy, const bool sortOrderAsc);
@@ -71,6 +72,9 @@ public slots:
void pauseBook(const QString& id);
void resumeBook(const QString& id);
void cancelBook(const QString& id);
+ void pauseBook(const QString& id, QModelIndex index);
+ void resumeBook(const QString& id, QModelIndex index);
+ void cancelBook(const QString& id, QModelIndex index);
void onCustomContextMenu(const QPoint &point);
};
diff --git a/src/contentmanagerdelegate.cpp b/src/contentmanagerdelegate.cpp
index ef3fe0b..87cc99b 100644
--- a/src/contentmanagerdelegate.cpp
+++ b/src/contentmanagerdelegate.cpp
@@ -22,6 +22,127 @@ ContentManagerDelegate::ContentManagerDelegate(QObject *parent)
placeholderIconFile.save(&buffer, "png");
}
+void createPauseSymbol(QPainter *painter, int x, int y)
+{
+ QPen pen;
+ pen.setWidth(3);
+ QPainterPath path;
+ x += 12.5;
+ y += 10;
+ pen.setColor("#3366cc");
+ path.moveTo(x, y);
+ path.lineTo(x, y + 10);
+ painter->strokePath(path, pen);
+ path.moveTo(x + 5, y);
+ path.lineTo(x + 5, y + 10);
+ painter->strokePath(path, pen);
+}
+
+void createResumeSymbol(QPainter *painter, int x, int y)
+{
+ QPen pen;
+ pen.setWidth(3);
+ QPainterPath path;
+ x += 12.5;
+ y += 8;
+ pen.setColor("#3366cc");
+ path.moveTo(x, y);
+ path.lineTo(x, y + 15);
+ path.lineTo(x + 10, y + 8);
+ path.lineTo(x, y);
+ painter->setRenderHint(QPainter::Antialiasing);
+ painter->strokePath(path, pen);
+}
+
+void createArc(QPainter *painter, int startAngle, int spanAngle, QRect rectangle, QPen pen)
+{
+ painter->setRenderHint(QPainter::Antialiasing);
+ int arcX = rectangle.x();
+ int arcY = rectangle.y();
+ int arcW = rectangle.width();
+ int arcH = rectangle.height();
+ QPainterPath path;
+ path.moveTo(arcX + arcW, arcY + arcH/2);
+ path.arcTo(rectangle, startAngle, spanAngle);
+ painter->strokePath(path, pen);
+}
+
+void createCancelSymbol(QPainter *painter, int x, int y, int w, int h)
+{
+ QPen p;
+ p.setWidth(3);
+ p.setColor("#dd3333");
+ QRect r(x, y, w, h);
+ createArc(painter, 0, 360, r, p);
+ painter->setPen(p);
+ QRect nRect(x, y, w, h);
+ auto oldFont = painter->font();
+ auto bFont = oldFont;
+ bFont.setBold(true);
+ painter->setFont(bFont);
+ painter->drawText(nRect, Qt::AlignCenter | Qt::AlignJustify, "X");
+ painter->setFont(oldFont);
+}
+
+void createDownloadStats(QPainter *painter, QRect box, QString downloadSpeed, QString completedLength)
+{
+ QPen pen;
+ int x = box.x();
+ int y = box.y();
+ int w = box.width();
+ int h = box.height();
+ pen.setColor("#666666");
+ painter->setPen(pen);
+ auto oldFont = painter->font();
+ painter->setFont(QFont("Selawik", 8));
+ QRect nRect(x - 10, y - 10, w, h);
+ painter->drawText(nRect,Qt::AlignCenter | Qt::AlignJustify, downloadSpeed);
+ QRect fRect(x - 10, y + 10, w, h);
+ painter->drawText(fRect,Qt::AlignCenter | Qt::AlignJustify, completedLength);
+ painter->setFont(oldFont);
+}
+
+void showDownloadProgress(QPainter *painter, QRect box, DownloadInfo downloadInfo)
+{
+ int x,y,w,h;
+ x = box.left();
+ y = box.top();
+ w = box.width();
+ h = box.height();
+
+ int arcX = x + w/2 + 20;
+ int arcY = y + 20;
+ int arcW = w - 90;
+ int arcH = h - 40;
+
+ double progress = (double) (downloadInfo.progress) / 100;
+ progress = -progress;
+ auto completedLength = downloadInfo.completedLength;
+ auto downloadSpeed = downloadInfo.downloadSpeed;
+
+ if (downloadInfo.paused) {
+ createResumeSymbol(painter, arcX, arcY);
+ createCancelSymbol(painter, x + w/2 - 20, arcY, arcW, arcH);
+ } else {
+ createPauseSymbol(painter, arcX, arcY);
+ createDownloadStats(painter, box, downloadSpeed, completedLength);
+ }
+
+ QPen pen;
+ pen.setWidth(3);
+ painter->setPen(pen);
+ painter->setRenderHint(QPainter::Antialiasing);
+
+ QRect rectangle(arcX, arcY, arcW, arcH);
+
+ pen.setColor("#eaecf0");
+ createArc(painter, 0, 360, rectangle, pen);
+
+ int startAngle = 0;
+ int spanAngle = progress * 360;
+ pen.setColor("#3366cc");
+ createArc(painter, startAngle, spanAngle, rectangle, pen);
+}
void ContentManagerDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
@@ -39,8 +160,6 @@ void ContentManagerDelegate::paint(QPainter *painter, const QStyleOptionViewItem
const auto id = node->getBookId();
const auto book = KiwixApp::instance()->getLibrary()->getBookById(id);
if(KiwixApp::instance()->getContentManager()->getBookInfos(id, {"downloadId"})["downloadId"] != "") {
- button.text = "Downloading";
- button.state = QStyle::State_ReadOnly;
} else {
button.text = gt("open");
}
@@ -54,7 +173,13 @@ void ContentManagerDelegate::paint(QPainter *painter, const QStyleOptionViewItem
return;
}
if (index.column() == 5) {
- baseButton->style()->drawControl( QStyle::CE_PushButton, &button, painter, baseButton.data());
+ if (node->isDownloading()) {
+ auto downloadInfo = node->getDownloadInfo();
+ showDownloadProgress(painter, r, downloadInfo);
+ }
+ else {
+ baseButton->style()->drawControl( QStyle::CE_PushButton, &button, painter, baseButton.data());
+ }
return;
}
if (index.column() == 0) {
@@ -89,24 +214,47 @@ bool ContentManagerDelegate::editorEvent(QEvent *event, QAbstractItemModel *mode
y = r.top();
w = r.width();
h = r.height();
- const auto node = static_cast(index.internalPointer());
- const auto id = node->getBookId();
- if(index.column() == 5 && clickX > x && clickX < x + w )
- if( clickY > y && clickY < y + h && KiwixApp::instance()->getContentManager()->getBookInfos(id, {"downloadId"})["downloadId"] == "")
- {
- try {
- const auto book = KiwixApp::instance()->getLibrary()->getBookById(id);
- KiwixApp::instance()->getContentManager()->openBook(id);
- } catch (std::out_of_range& e) {
- KiwixApp::instance()->getContentManager()->downloadBook(id);
- }
- }
+ const auto lastColumnClicked = ((index.column() == 5) && (clickX > x && clickX < x + w)
+ && (clickY > y && clickY < y + h));
+
+ if (lastColumnClicked)
+ handleLastColumnClicked(index, e, option);
}
return true;
}
+void ContentManagerDelegate::handleLastColumnClicked(const QModelIndex& index, QMouseEvent *mouseEvent, const QStyleOptionViewItem &option)
+{
+ const auto node = static_cast(index.internalPointer());
+ const auto id = node->getBookId();
+ int clickX = mouseEvent->x();
+
+ QRect r = option.rect;
+ int x = r.left();
+ int w = r.width();
+
+ if (node->isDownloading()) {
+ if (node->getDownloadInfo().paused) {
+ if (clickX < (x + w/2)) {
+ KiwixApp::instance()->getContentManager()->cancelBook(id, index);
+ } else {
+ KiwixApp::instance()->getContentManager()->resumeBook(id, index);
+ }
+ } else {
+ KiwixApp::instance()->getContentManager()->pauseBook(id, index);
+ }
+ } else {
+ try {
+ const auto book = KiwixApp::instance()->getLibrary()->getBookById(id);
+ KiwixApp::instance()->getContentManager()->openBook(id);
+ } catch (std::out_of_range& e) {
+ KiwixApp::instance()->getContentManager()->downloadBook(id, index);
+ }
+ }
+}
+
QSize ContentManagerDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (index.data(Qt::UserRole+1) != QVariant()) {
diff --git a/src/contentmanagerdelegate.h b/src/contentmanagerdelegate.h
index 0d820ba..5821f53 100644
--- a/src/contentmanagerdelegate.h
+++ b/src/contentmanagerdelegate.h
@@ -18,6 +18,7 @@ public:
private:
QScopedPointer baseButton;
QByteArray placeholderIcon;
+ void handleLastColumnClicked(const QModelIndex& index, QMouseEvent *event, const QStyleOptionViewItem &option);
};
#endif // CONTENTMANAGERDELEGATE_H
diff --git a/src/contentmanagermodel.cpp b/src/contentmanagermodel.cpp
index fb975cd..146ef31 100644
--- a/src/contentmanagermodel.cpp
+++ b/src/contentmanagermodel.cpp
@@ -124,7 +124,7 @@ QString convertToUnits(QString size)
}
const auto preciseBytes = QString::number(bytes, 'g', 3);
- return preciseBytes + units[unitIndex];
+ return preciseBytes + " " + units[unitIndex];
}
void ContentManagerModel::setupNodes()
@@ -236,3 +236,62 @@ void ContentManagerModel::updateImage(QModelIndex index, QString url, QByteArray
iconMap[url] = imageData;
emit dataChanged(index, index);
}
+
+void ContentManagerModel::startDownload(QModelIndex index)
+{
+ auto node = static_cast(index.internalPointer());
+ node->setIsDownloading(true);
+ auto id = node->getBookId();
+ QTimer *timer = new QTimer(this);
+ connect(timer, &QTimer::timeout, this, [=]() {
+ auto downloadInfos = KiwixApp::instance()->getContentManager()->updateDownloadInfos(id, {"status", "completedLength", "totalLength", "downloadSpeed"});
+ double percent = (double) downloadInfos["completedLength"].toInt() / downloadInfos["totalLength"].toInt();
+ percent *= 100;
+ percent = QString::number(percent, 'g', 3).toDouble();
+ auto completedLength = convertToUnits(downloadInfos["completedLength"].toString());
+ auto downloadSpeed = convertToUnits(downloadInfos["downloadSpeed"].toString()) + "/s";
+ node->setDownloadInfo({percent, completedLength, downloadSpeed});
+ if (!downloadInfos["status"].isValid()) {
+ node->setIsDownloading(false);
+ timer->stop();
+ timer->deleteLater();
+ }
+ emit dataChanged(index, index);
+ });
+ timer->start(1000);
+ timers[id] = timer;
+}
+
+void ContentManagerModel::pauseDownload(QModelIndex index)
+{
+ auto node = static_cast(index.internalPointer());
+ auto id = node->getBookId();
+ auto prevDownloadInfo = node->getDownloadInfo();
+ prevDownloadInfo.paused = true;
+ node->setDownloadInfo(prevDownloadInfo);
+ timers[id]->stop();
+ emit dataChanged(index, index);
+}
+
+void ContentManagerModel::resumeDownload(QModelIndex index)
+{
+ auto node = static_cast(index.internalPointer());
+ auto id = node->getBookId();
+ auto prevDownloadInfo = node->getDownloadInfo();
+ prevDownloadInfo.paused = false;
+ node->setDownloadInfo(prevDownloadInfo);
+ timers[id]->start(1000);
+ emit dataChanged(index, index);
+}
+
+void ContentManagerModel::cancelDownload(QModelIndex index)
+{
+ auto node = static_cast(index.internalPointer());
+ auto id = node->getBookId();
+ node->setIsDownloading(false);
+ node->setDownloadInfo({0, "", "", false});
+ timers[id]->stop();
+ timers[id]->deleteLater();
+ emit dataChanged(index, index);
+}
+
diff --git a/src/contentmanagermodel.h b/src/contentmanagermodel.h
index 1bf191a..678b833 100644
--- a/src/contentmanagermodel.h
+++ b/src/contentmanagermodel.h
@@ -34,6 +34,10 @@ public:
public slots:
void updateImage(QModelIndex index, QString url, QByteArray imageData);
+ void startDownload(QModelIndex index);
+ void pauseDownload(QModelIndex index);
+ void resumeDownload(QModelIndex index);
+ void cancelDownload(QModelIndex index);
protected:
bool canFetchMore(const QModelIndex &parent) const override;
@@ -45,6 +49,7 @@ private:
int zimCount = 0;
ThumbnailDownloader td;
QMap iconMap;
+ QMap timers;
};
#endif // CONTENTMANAGERMODEL_H
diff --git a/src/node.cpp b/src/node.cpp
index dbdbfcf..5cf2dac 100644
--- a/src/node.cpp
+++ b/src/node.cpp
@@ -2,7 +2,9 @@
Node::Node(const QList &data, Node *parent, QString bookId, bool isAdditional)
: m_itemData(data), m_parentItem(parent), m_isAdditonal(isAdditional), m_bookId(bookId)
-{}
+{
+ m_downloadInfo = {0, "", "", false};
+}
Node::~Node()
{
diff --git a/src/node.h b/src/node.h
index 27cb966..a25844b 100644
--- a/src/node.h
+++ b/src/node.h
@@ -6,6 +6,14 @@
#include "contentmanagermodel.h"
#include
+struct DownloadInfo
+{
+ double progress;
+ QString completedLength;
+ QString downloadSpeed;
+ bool paused;
+};
+
class Node
{
public:
@@ -21,6 +29,10 @@ public:
bool isAdditonal() const { return m_isAdditonal; }
QString getBookId() const { return m_bookId; }
void setIconData(QByteArray iconData) { m_itemData[0] = iconData; }
+ bool isDownloading() const { return m_isDownloading; }
+ void setDownloadInfo(DownloadInfo downloadInfo) { m_downloadInfo = downloadInfo; }
+ DownloadInfo getDownloadInfo() const { return m_downloadInfo; }
+ void setIsDownloading(bool val) { m_isDownloading = val; }
private:
QList m_itemData;
@@ -28,6 +40,8 @@ private:
QList m_childItems;
bool m_isAdditonal;
QString m_bookId;
+ bool m_isDownloading = false;
+ DownloadInfo m_downloadInfo;
};
From cb49cb6a1a4759fde0fc5c26983a8d9971675055 Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Tue, 27 Jun 2023 04:07:37 +0530
Subject: [PATCH 17/29] Replace library-icon.svg with kiwix logo
The designs use kiwix logo as library icon
---
resources/icons/kiwix-logo.svg | 20 +
resources/icons/library-icon.svg | 1715 ------------------------------
resources/kiwix.qrc | 2 +-
src/tabbar.cpp | 2 +-
4 files changed, 22 insertions(+), 1717 deletions(-)
create mode 100644 resources/icons/kiwix-logo.svg
delete mode 100644 resources/icons/library-icon.svg
diff --git a/resources/icons/kiwix-logo.svg b/resources/icons/kiwix-logo.svg
new file mode 100644
index 0000000..566b106
--- /dev/null
+++ b/resources/icons/kiwix-logo.svg
@@ -0,0 +1,20 @@
+
+
+
diff --git a/resources/icons/library-icon.svg b/resources/icons/library-icon.svg
deleted file mode 100644
index fdc9d44..0000000
--- a/resources/icons/library-icon.svg
+++ /dev/null
@@ -1,1715 +0,0 @@
-
-
-
diff --git a/resources/kiwix.qrc b/resources/kiwix.qrc
index db01fa1..a9a3c9b 100644
--- a/resources/kiwix.qrc
+++ b/resources/kiwix.qrc
@@ -54,11 +54,11 @@
icons/pause-button.png
icons/cancel-button.png
icons/new-tab-icon.svg
- icons/library-icon.svg
icons/open-file.svg
icons/placeholder-icon.png
icons/caret-down-solid.svg
icons/caret-right-solid.svg
icons/caret-up-solid.svg
+ icons/kiwix-logo.svg
diff --git a/src/tabbar.cpp b/src/tabbar.cpp
index 4f7e4c3..eb97683 100644
--- a/src/tabbar.cpp
+++ b/src/tabbar.cpp
@@ -113,7 +113,7 @@ void TabBar::setContentManagerView(ContentManagerView* view)
qInfo() << "add widget";
mp_stackedWidget->addWidget(view);
mp_stackedWidget->show();
- int idx = addTab(QIcon(":/icons/library-icon.svg"), "");
+ int idx = addTab(QIcon(":/icons/kiwix-logo.svg"), "");
setTabButton(idx, RightSide, nullptr);
}
From d90158b7898e225f10c10f02be8e61223464283e Mon Sep 17 00:00:00 2001
From: Nikhil Tanwar <2002nikhiltanwar@gmail.com>
Date: Fri, 30 Jun 2023 12:42:20 +0530
Subject: [PATCH 18/29] Confirmation dialog box for delete book and cancel
download
Added a new custom dialog box for confirmations.
Currently, it popups when a book is deleted or a download has to be cancelled
---
kiwix-desktop.pro | 3 +
resources/css/confirmBox.css | 32 +++++++++
resources/i18n/en.json | 6 +-
resources/style.qrc | 1 +
src/contentmanager.cpp | 57 +++++++++++-----
src/kiwixconfirmbox.cpp | 32 +++++++++
src/kiwixconfirmbox.h | 28 ++++++++
ui/kiwixconfirmbox.ui | 129 +++++++++++++++++++++++++++++++++++
8 files changed, 269 insertions(+), 19 deletions(-)
create mode 100644 resources/css/confirmBox.css
create mode 100644 src/kiwixconfirmbox.cpp
create mode 100644 src/kiwixconfirmbox.h
create mode 100644 ui/kiwixconfirmbox.ui
diff --git a/kiwix-desktop.pro b/kiwix-desktop.pro
index 0f351cc..ec8c807 100644
--- a/kiwix-desktop.pro
+++ b/kiwix-desktop.pro
@@ -38,6 +38,7 @@ SOURCES += \
src/contentmanagermodel.cpp \
src/contenttypefilter.cpp \
src/findinpagebar.cpp \
+ src/kiwixconfirmbox.cpp \
src/node.cpp \
src/suggestionlistworker.cpp \
src/thumbnaildownloader.cpp \
@@ -76,6 +77,7 @@ HEADERS += \
src/contentmanagerview.h \
src/contenttypefilter.h \
src/findinpagebar.h \
+ src/kiwixconfirmbox.h \
src/node.h \
src/suggestionlistworker.h \
src/thumbnaildownloader.h \
@@ -111,6 +113,7 @@ HEADERS += \
FORMS += \
src/contentmanagerview.ui \
src/findinpagebar.ui \
+ ui/kiwixconfirmbox.ui \
ui/mainwindow.ui \
ui/about.ui \
src/contentmanagerside.ui \
diff --git a/resources/css/confirmBox.css b/resources/css/confirmBox.css
new file mode 100644
index 0000000..85b0aed
--- /dev/null
+++ b/resources/css/confirmBox.css
@@ -0,0 +1,32 @@
+* {
+ font-family: 'Selawik';
+ background-color: white;
+ font-size: 16px;
+}
+
+QDialog {
+ border: 2px solid #cccccc;
+}
+
+#confirmTitle {
+ font-size: 18px;
+ line-height: 44px;
+ font-weight: bold;
+}
+
+QPushButton {
+ opacity: 1;
+ padding: 6px;
+ outline: 0;
+ border-radius: 2px;
+ width: 24px;
+ border: 1px solid #3366cc;
+ color: #3366cc;
+}
+
+QPushButton:hover {
+ background-color: #3366cc;
+ color: white;
+}
+
+
diff --git a/resources/i18n/en.json b/resources/i18n/en.json
index a652119..9c7607e 100644
--- a/resources/i18n/en.json
+++ b/resources/i18n/en.json
@@ -135,5 +135,9 @@
"monitor-clear-dir-dialog-msg":"This will stop checking the monitor directory for new ZIM files.",
"monitor-directory-tooltip":"All ZIM files in this directory will be automatically added to the library.",
"next-tab":"Move to next tab",
- "previous-tab":"Move to previous tab"
+ "previous-tab":"Move to previous tab",
+ "cancel-download": "Cancel Download",
+ "cancel-download-text": "Are you sure you want to cancel the download of {{ZIM}}?",
+ "delete-book": "Delete book",
+ "delete-book-text": "Are you sure you want to delete {{ZIM}}?"
}
diff --git a/resources/style.qrc b/resources/style.qrc
index 3f284ba..565b93f 100644
--- a/resources/style.qrc
+++ b/resources/style.qrc
@@ -3,5 +3,6 @@
css/style.css
css/popup.css
css/localServer.css
+ css/confirmBox.css
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
index f713ddf..22424a3 100644
--- a/src/contentmanager.cpp
+++ b/src/contentmanager.cpp
@@ -17,6 +17,7 @@
#include
#include "contentmanagerdelegate.h"
#include "node.h"
+#include "kiwixconfirmbox.h"
ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader, QObject *parent)
: QObject(parent),
@@ -106,7 +107,6 @@ void ContentManager::onCustomContextMenu(const QPoint &point)
connect(&menuDeleteBook, &QAction::triggered, [=]() {
eraseBook(id);
- emit(booksChanged());
});
connect(&menuOpenBook, &QAction::triggered, [=]() {
openBook(id);
@@ -388,21 +388,32 @@ void ContentManager::eraseBookFilesFromComputer(const QString dirPath, const QSt
void ContentManager::eraseBook(const QString& id)
{
- auto tabBar = KiwixApp::instance()->getTabWidget();
- tabBar->closeTabsByZimId(id);
- kiwix::Book book = mp_library->getBookById(id);
- QString dirPath = QString::fromStdString(kiwix::removeLastPathElement(book.getPath()));
- QString fileName = QString::fromStdString(kiwix::getLastPathElement(book.getPath())) + "*";
- eraseBookFilesFromComputer(dirPath, fileName);
- mp_library->removeBookFromLibraryById(id);
- mp_library->save();
- emit mp_library->bookmarksChanged();
- if (m_local) {
- emit(bookRemoved(id));
- } else {
- emit(oneBookChanged(id));
- }
- KiwixApp::instance()->getSettingsManager()->deleteSettings(id);
+ auto text = gt("delete-book-text");
+ text = text.replace("{{ZIM}}", QString::fromStdString(mp_library->getBookById(id).getTitle()));
+ KiwixConfirmBox *dialog = new KiwixConfirmBox(gt("delete-book"), text, mp_view);
+ dialog->show();
+ connect(dialog, &KiwixConfirmBox::yesClicked, [=]() {
+ auto tabBar = KiwixApp::instance()->getTabWidget();
+ tabBar->closeTabsByZimId(id);
+ kiwix::Book book = mp_library->getBookById(id);
+ QString dirPath = QString::fromStdString(kiwix::removeLastPathElement(book.getPath()));
+ QString fileName = QString::fromStdString(kiwix::getLastPathElement(book.getPath())) + "*";
+ eraseBookFilesFromComputer(dirPath, fileName);
+ mp_library->removeBookFromLibraryById(id);
+ mp_library->save();
+ emit mp_library->bookmarksChanged();
+ if (m_local) {
+ emit(bookRemoved(id));
+ } else {
+ emit(oneBookChanged(id));
+ }
+ KiwixApp::instance()->getSettingsManager()->deleteSettings(id);
+ dialog->deleteLater();
+ emit booksChanged();
+ });
+ connect(dialog, &KiwixConfirmBox::noClicked, [=]() {
+ dialog->deleteLater();
+ });
}
void ContentManager::pauseBook(const QString& id, QModelIndex index)
@@ -441,8 +452,18 @@ void ContentManager::resumeBook(const QString& id)
void ContentManager::cancelBook(const QString& id, QModelIndex index)
{
- cancelBook(id);
- emit managerModel->cancelDownload(index);
+ auto text = gt("cancel-download-text");
+ text = text.replace("{{ZIM}}", QString::fromStdString(mp_library->getBookById(id).getTitle()));
+ KiwixConfirmBox *dialog = new KiwixConfirmBox(gt("cancel-download"), text, mp_view);
+ dialog->show();
+ connect(dialog, &KiwixConfirmBox::yesClicked, [=]() {
+ cancelBook(id);
+ emit managerModel->cancelDownload(index);
+ dialog->deleteLater();
+ });
+ connect(dialog, &KiwixConfirmBox::noClicked, [=]() {
+ dialog->deleteLater();
+ });
}
void ContentManager::cancelBook(const QString& id)
diff --git a/src/kiwixconfirmbox.cpp b/src/kiwixconfirmbox.cpp
new file mode 100644
index 0000000..b0e22a2
--- /dev/null
+++ b/src/kiwixconfirmbox.cpp
@@ -0,0 +1,32 @@
+#include "kiwixconfirmbox.h"
+#include "ui_kiwixconfirmbox.h"
+#include
+#include
+
+KiwixConfirmBox::KiwixConfirmBox(QString confirmTitle, QString confirmText, QWidget *parent) :
+ QDialog(parent), m_confirmTitle(confirmTitle), m_confirmText(confirmText),
+ ui(new Ui::kiwixconfirmbox)
+{
+ ui->setupUi(this);
+ setWindowFlag(Qt::FramelessWindowHint, true);
+
+ QFile styleFile(":/css/confirmBox.css");
+ styleFile.open(QIODevice::ReadOnly);
+ auto byteContent = styleFile.readAll();
+ styleFile.close();
+ QString style(byteContent);
+ setStyleSheet(style);
+ connect(ui->yesButton, &QPushButton::clicked, [=]() {
+ emit yesClicked();
+ });
+ connect(ui->noButton, &QPushButton::clicked, [=]() {
+ emit noClicked();
+ });
+ ui->confirmText->setText(confirmText);
+ ui->confirmTitle->setText(confirmTitle);
+}
+
+KiwixConfirmBox::~KiwixConfirmBox()
+{
+ delete ui;
+}
diff --git a/src/kiwixconfirmbox.h b/src/kiwixconfirmbox.h
new file mode 100644
index 0000000..2479ed5
--- /dev/null
+++ b/src/kiwixconfirmbox.h
@@ -0,0 +1,28 @@
+#ifndef KIWIXCONFIRMBOX_H
+#define KIWIXCONFIRMBOX_H
+
+#include
+
+namespace Ui {
+class kiwixconfirmbox;
+}
+
+class KiwixConfirmBox : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit KiwixConfirmBox(QString confirmTitle, QString confirmText, QWidget *parent = nullptr);
+ ~KiwixConfirmBox();
+
+signals:
+ void yesClicked();
+ void noClicked();
+
+private:
+ QString m_confirmTitle;
+ QString m_confirmText;
+ Ui::kiwixconfirmbox *ui;
+};
+
+#endif // KIWIXCONFIRMBOX_H
diff --git a/ui/kiwixconfirmbox.ui b/ui/kiwixconfirmbox.ui
new file mode 100644
index 0000000..3b5568b
--- /dev/null
+++ b/ui/kiwixconfirmbox.ui
@@ -0,0 +1,129 @@
+
+
+ kiwixconfirmbox
+
+
+
+ 0
+ 0
+ 368
+ 166
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 474
+ 16777215
+
+
+
+ Dialog
+
+
+ -
+
+
+