From a93e6b663ff714625c3ce203d9593fb076906e9d Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 13 Apr 2022 21:23:56 +0100 Subject: [PATCH 1/3] Enhancement #1968810: provide an interface ot mark books with arbitrary text. --- resources/images/marked-text.png | Bin 0 -> 4915 bytes src/calibre/db/view.py | 5 +- src/calibre/gui2/actions/mark_books.py | 110 ++++++++++++++++++++++++- src/calibre/gui2/library/models.py | 8 +- src/calibre/gui2/library/views.py | 2 + 5 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 resources/images/marked-text.png diff --git a/resources/images/marked-text.png b/resources/images/marked-text.png new file mode 100644 index 0000000000000000000000000000000000000000..cf25d4528d0f645ca3be51f85fd496183c7683a0 GIT binary patch literal 4915 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 0: + layout.addWidget(QLabel(_('Enter a value:')), button_rows+1, 0, 1, 2) + label = QLabel('&' + str(button_rows+1)) + else: + label = QLabel('') + label.setBuddy(textbox) + layout.addWidget(label, button_rows+2, 0, 1, 1) + layout.addWidget(textbox, button_rows+2, 1) + textbox.setFocus() + button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | + QDialogButtonBox.StandardButton.Cancel) + button_box.accepted.connect(self.accept) + button_box.rejected.connect(self.reject) + layout.addWidget(button_box, button_rows+3, 0, 1, 2) + + def text(self): + return self.text_box.text() + + def button_pushed(self, checked, text=''): + self.text_box.setText(text) + self.text_box.save_history() + self.accept() + + def accept(self): + if not self.text_box.text(): + d = error_dialog(self.gui, _('Value cannot be empty'), _('You must provide a value')) + d.exec_() + else: + super().accept() class MarkBooksAction(InterfaceAction): @@ -49,12 +118,18 @@ class MarkBooksAction(InterfaceAction): self.toggle_ids(book_ids) def genesis(self): + self.search_icon = QIcon.ic('search.png') self.qaction.triggered.connect(self.toggle_selected) self.menu = m = self.qaction.menu() m.aboutToShow.connect(self.about_to_show_menu) ma = partial(self.create_menu_action, m) + self.show_marked_action = a = ma('mark_with_text', _('Mark books with text label'), icon='marked-text.png') + a.triggered.connect(self.mark_with_text) self.show_marked_action = a = ma('show-marked', _('Show marked books'), icon='search.png', shortcut='Shift+Ctrl+M') a.triggered.connect(self.show_marked) + self.show_marked_with_text = QMenu(_('Show marked books with text label')) + self.show_marked_with_text.setIcon(self.search_icon) + m.addMenu(self.show_marked_with_text) self.clear_marked_action = a = ma('clear-all-marked', _('Clear all marked books'), icon='clear_left.png') a.triggered.connect(self.clear_all_marked) m.addSeparator() @@ -86,9 +161,22 @@ class MarkBooksAction(InterfaceAction): def about_to_show_menu(self): db = self.gui.current_db - num = len(frozenset(db.data.marked_ids).intersection(db.new_api.all_book_ids())) + marked_ids = db.data.marked_ids + num = len(frozenset(marked_ids).intersection(db.new_api.all_book_ids())) text = _('Show marked book') if num == 1 else (_('Show marked books') + (' (%d)' % num)) self.show_marked_action.setText(text) + counts = dict() + for v in marked_ids.values(): + counts[v] = counts.get(v, 0) + 1 + labels = sorted(counts.keys(), key=sort_key) + self.show_marked_with_text.clear() + if len(labels): + self.show_marked_with_text.setEnabled(True) + for t in labels: + ac = self.show_marked_with_text.addAction(self.search_icon, f'{t} ({counts[t]})') + ac.triggered.connect(partial(self.show_marked_text, txt=t)) + else: + self.show_marked_with_text.setEnabled(False) def location_selected(self, loc): enabled = loc == 'library' @@ -116,6 +204,9 @@ class MarkBooksAction(InterfaceAction): def show_marked(self): self.gui.search.set_search_string('marked:true') + def show_marked_text(self, txt=None): + self.gui.search.set_search_string(f'marked:"={txt}"') + def clear_all_marked(self): self.gui.current_db.data.set_marked_ids(()) if str(self.gui.search.text()).startswith('marked:'): @@ -139,3 +230,18 @@ class MarkBooksAction(InterfaceAction): else: mids.pop(book_id, None) db.data.set_marked_ids(mids) + + def mark_with_text(self): + book_ids = self._get_selected_ids() + if not book_ids: + return + dialog = MarkWithTextDialog(self.gui) + if dialog.exec_() != QDialog.DialogCode.Accepted: + return + txt = dialog.text() + txt = txt if txt else 'true' + db = self.gui.current_db + mids = db.data.marked_ids.copy() + for book_id in book_ids: + mids[book_id] = txt + db.data.set_marked_ids(mids) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 22e512ea47..325f11926c 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -232,6 +232,7 @@ class BooksModel(QAbstractTableModel): # {{{ # remember that the cover grid view needs a larger version of the icon, # anyway) self.marked_icon = QIcon(I('marked.png')) + self.marked_text_icon = QIcon(I('marked-text.png')) self.bool_blank_icon_as_icon = QIcon(self.bool_blank_icon) self.row_decoration = None self.device_connected = False @@ -1072,7 +1073,12 @@ class BooksModel(QAbstractTableModel): # {{{ return (section+1) if role == Qt.ItemDataRole.DecorationRole: try: - return self.marked_icon if self.db.data.get_marked(self.db.data.index_to_id(section)) else self.row_decoration + m = self.db.data.get_marked(self.db.data.index_to_id(section)) + if m: + i = self.marked_icon if m == 'true' else self.marked_text_icon + else: + i = self.row_decoration + return i except (ValueError, IndexError): pass return None diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index e2a67929cf..5253c61acd 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -946,6 +946,8 @@ class BooksView(QTableView): # {{{ # This is needed otherwise Qt does not always update the # viewport correctly. See https://bugs.launchpad.net/bugs/1404697 self.row_header.viewport().update() + # refresh the rows because there might be a composite that uses marked_books() + self.model().refresh_rows(changed) else: # Marked items have either appeared or all been removed self.model().set_row_decoration(current_marked) From 84284c3fe178af0e7669a433c837d44db7e8e68a Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Thu, 14 Apr 2022 13:48:44 +0100 Subject: [PATCH 2/3] This icon is obviously different from the other one because it points the other direction. --- resources/images/marked-text.png | Bin 4915 -> 4875 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/images/marked-text.png b/resources/images/marked-text.png index cf25d4528d0f645ca3be51f85fd496183c7683a0..d60fc1f38410055d0eeac061743abb03c9283510 100644 GIT binary patch delta 4865 zcmV+c6aMV8CW|JJHh;iLL_t(|ob6qEa9q`S|DAL0y}Oc3ER1<)Y=e2*!2v5N0YYQi zhR{Sf9nv%d#MDD$Kn((COb3Q(>mdx#42(fwf*B8;PBMmZN$k{sZ5(40k_w0b^N>jm zA|hZSgJfjw-uHR*k8}57JyyHgr`G%X2idIlocsHo@B7a8oqz9K0}V9LKm!dl&_DwX zG|)f;4K&ceG{wh#J$-$B)t*IRpw*s0DdE$E@w@8}PAd)SN`O;NS;EcP_6t`ZJUDGM zs4D>g$dmpi{A5q?^fl8(gL)I-BYCQ?TKs|6<+IDCg$DH|z>+0P{A0i7hX@Y5O1E8m z@ZiB}&u9|Vn|}ZR+!MZP2g(Z(9OyBgyQ0Q5s6zp~mPL+eb^_&dga>-8U)Qt-btu4+ zB};5bkx;d( zL469aeED*D+y!o^>>zSPvQ{;yPXPeX6E6w`yZ}z1ynhf}LZKEls9OOB_~!*}_PKIH z_#pxgE^1MOx)osg^5x{XFNXpFhZ`!NBNVl$LEQ=ffSvGFsSt1k0zS`ZP*d@so&^|C zpBJ>5OT`63fl$Eb2n4vSAr0zTfaS}Vv*RyN3POSlghB-zUI;HlAZlfATF(LiFy~%E zDF^^SNPmPvd7*LwI05_+yasZ#dKch?6D_mFWMCA8Kq~kgwy%<&vVC|ppaH4MsFlR; z*L~akcx%iA0pU{l93h7SECVxQWJU~e69$EGLYx0r|1)dWtO`%B&Mjcqu3b$tW;R*G zV#J_e6a)|wZh#RNha8pgvh1K5byK6l<5d!W*njzuc>PAh?Kc@HfmEQ1P@)KdP%7ln zc1XK{xCtDHd{HIspgvvDd#`Mkuih<&-e80n43q$XRNw-EP>zp;4>wR!A!cA^6KQQG zN5!k-i|Wk+`rmj#y#0XK@eh;`#t2DKFC`QJfKiwREDoO_6mUZz`adZ<7^tWn)Q?_< zHd4C^DZwQ|p|qo?1O^3RiV|WlSO%ITgf5X*RfB0Szy~j`3!lGIB>D{+?dg;Pmk78D z1-OJ#AOKi~U`B{#D5;eb9wi_tpQ9@c0Y@kyC8W}e9hAbL zFc=IPu}=snU@#KPEcvS0!88*fJJPZ&cOOa>aD+mKTmYbH>M0ln%VHk@Az%O)L4UP1 zm^K3BjFg*mAB+?2<6}fv#!7>YFcJx_f=2>jk8GPoP_dX2T z+S(@ist^8mefa8qNc5+v*ME^#`y4Lu<*id^Un92$NK#2s?aeK8Bo7UCu`84L6KSA%*8*hz5=zQwcS{K@Mt>{=F#{$mnFgf^ z!q5NLZSw6kO$^O3G+V^dQ=}NR4>|nVqGy(|W4ZmQ&YqMy%om3eLX6(z6pyhQq@7Q? zdSNia2ndO+P)Mb8DVq}0fW@*jC@vn9CJ(su+IzP>w(`*1Ykf`vuCysw1{jSz5>f^( zRYnE{NC_E!sw)RlL4PVGB)9}ml@ox&ENMdYT%Y;5ZC#=B*X(lvE&)I%dfJGk0#)Ft z%C?|NDiYZWU{LVl&e2M_4c+ocr|f++W}(F*%`r4vh#5p{pgR(Tgp3@k(u-X`iAV{c zWX2C@>!CEK>(AzWEGAYW+X&KmfQ~eTAZ8$L5YvbbkiAg(Jbwzvlt5RrbF%o=T>ATf%`JYsXa1 z^h?|ovGm3V*nc@I`~Xg%93O6oP%5cFNaT*9*bo(j;dA(01zd5V^zn#3Ink>uW?<2l z36_5zizyigg@UYp41yik`d zquRYLVU9mWp6sVTJb^FD-f`;i%ScqXCInnSG|eHWod@9J_GrYd8}8!+-Qhqt_Xm9r zFC+n1K1Yj1;wG$t9&7s*2M_kmnl(Sq*_EXj7irM@7$J&LVo+r;V$x{aU;!}$agzkm z@{jh~8Gj8(rjAxts3`nK{V4>{NhPC^YckHe`K$%s&Hk0kuUe&l*t+4qaG+ZZbcgPs z&xy~~j2N0s!awe@wq2XQ2IZ=TrZq?s#b}fq7!0~oQ?GH$fW?VUI7RB*oPb1mTpo3_ zVi0Y4rQ)fI9;2{~XbYqi=y8irzx0O_{dz`;jeie%122kCySXzMSgOS$;r^c3)64SI zpuB5r%Ojm?@1xBz%xosQo)t5YuFk8dxP~wl0QC+v&3m?|&Y&Rwlg4;n=$*go3^;ra zAwdZ&1I-pO82|_g?12q z^zPaa3_dSE-obZY-PhMQKc@y|Kagqf==#-9Z%OX`DF8%iCSoQSKuG#X1ON!pIcF3P zDxd)uNN>GN3NzKnC15n2y9(GewXa>UJ z47)eo?G6rvry6d0X3yTcI-7NfsXt~&9P6uvW%mSL8UKf6sp1a zD98&<@L|3D4GNPHeFP*G*m)}#ELc#`6BUUn+>j|qgFk(5f-|fn;Vo0nyoy1UZ%O9qi$1v~ul}lh@PE=}ofwmO zv9qq8x9BfRHAF$E{x#J~YVgW8I;);}jY9PW0MOpi6<@N}_EA<1dj7Vv7Ba6A;B(LQ~AArHmWSZ zi!c7#aRSZ0hVqB!_TSYKn|yDYu3ZB-^}jP>q+3q(k+1GM*YE8cwfxoW#i#w?cU5@o zEL2l~?c28l+;O}N{jTFUt|6R-2Ope~M}Cm4!PxTyslcVmb>?;1#(z^5Us2hI9*wF6 zVWkux#`}gs?9#CD8P;#TeCe+nthG^gZ5VE%3?H- zsRMT*qK=1AnHO7lOEsQ*H1bR|ON?#XwjIXz;q1Gv>p1SvaI()k?Tn^AebHLC80n@8 zGG5z6bfGKTO-Tg$O@E0B21Cq*WkiJsdiJtYzcyjVLnToK3wZ9iEv{?lW?$p|{e$cG z{Y9a607QMhTt=t9fbRDq`lz|Onb#;(+5&d$*kRjlKJ04@3=Y3JbYk1_gK0a^7l3ov zf%g7fAP|vYj38!2g-@kmp8unH7k+obDOMI0wSd*DS0@r9u7B&~$-e%Zy?y)tedo!^ ztTTN-oZJrdD{6#-l$p*{0Qg*`&%AP@q5^#Ji|2ZtTM+vid*1mY&8ipW)(M@j)O5ZyIdNu4Ba|1M_!A-IH7IsS0?EbsW2)e zz~;@HUDwNSTYn(?8toljYC%UbRkNE&1ATcYdv8i87$Jt~09&?fahzn4 z>}%xJhLfWk6h8ByMwc|{EYMr!U~ z7Av~|fBy6SLfLm5XCn4BCeZ-VwceN+wVOx22%txr0Dl`c{H##+JwSPlFzbIHJ9Fe%ZmPKYp4TVALv0;IM*le2Hzj&0k6gM-BkH(CQ9 z9*F8)QAFGk)i%|n(5r59lR`t=2Qp3gq}toT94=E>1NU8OQEU{Bg(e@?`&`k3Q<}?=Ru#NNM2s zIOM4j4@qAIJoA=>NQmM0N;_zgD4783)~$2hWd7`zYTQwXH1IIu>i1PBFI4(vAko=< zm~cL-AtNZ60GxY{lULL&ANv~Z9bI(64Q_yhr;;9=5I&Dy;jJ~0&i|El0tKNo0<2y8 zkbmQhrhDAAzkmP!l8l%e?HyfY!3zBjO52AMK))tR4v8fVg*>ccCF`4!oK6U`}S2K{Yh(J`^pL6g^?rT zbt6DmSC``?au5CN-zFV~ijx-2i2Y<>55QGpCa=3<);i(Itl=8euqHY+5@4Z*$ zZQs(4hsgr%i{OST5a7uJLqoM<1XCB_{`>!9Y>dtRlWjZiyz@>8=Q?F9?Rc0h_-8Lr zZUFsaI5qTPwI>x}iUR!PC->O4Hxc`u=e+U8TUB5C+4$xSYq0PpH&AY%f)H}(4}aC3 zQG}@oaPPhMx~`wq-g6lHw(acM^H#|xT0Fjf-Th?IkAe_BSN`C%bwO*^tnqz!0`@)I zcHVlc7TMRh6ehsNjT;k*;fXv?B9W+N_BGnR^JDg%H{S%e}q5Wq+DwY4L^je`9C=)5eV(t345*pc^uG-nlw^*O_MD z^W5(4SIRfV`bNIp(RbeYui3X*ve*y0yI(Hr6zdy#xuCo6zTI*Bv3}q4g6{6_a!;|o zF}VP%SFau!5u@1mJa_i&V;k%jkI7%w;b)e9x^vCly?b~0=XOM3k{EE?ZGX3pV&C_j zUAtbW%;b&)Ah|P3g6tOxoMGZ?F8c zjvS1R0iB(lY4*dAH`p&X4tjGzD_5?x?S$t47r&^&8|;oiWGrCSs#Uff^^`yLRD=Br zA!7j}Bf~oCHr9S65e4Dvum6GL*vX{3X;Xv!ih=-GvEn+{^*;P?f1|=v1zdOCb>IK~ nRn?xw5sYQamQ{NSM>PH)tZA`kkP>LC00000NkvXXu0mjfM~pd~ delta 4906 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 From e0fe581d1d9bcb8897a61017bf75395ea00b922c Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Thu, 14 Apr 2022 18:14:30 +0100 Subject: [PATCH 3/3] Add id_from_url() to the Google metadata source --- src/calibre/ebooks/metadata/sources/google.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/calibre/ebooks/metadata/sources/google.py b/src/calibre/ebooks/metadata/sources/google.py index af380836f4..e2f0991e1a 100644 --- a/src/calibre/ebooks/metadata/sources/google.py +++ b/src/calibre/ebooks/metadata/sources/google.py @@ -198,7 +198,16 @@ class GoogleBooks(Source): goog = identifiers.get('google', None) if goog is not None: return ('google', goog, 'https://books.google.com/books?id=%s' % goog) + # }}} + def id_from_url(self, url): # {{{ + url_pattern = '(?:http|https)://books\.google\.com/books\?id=(?P.+)' + url_pattern = re.compile(url_pattern) + id_ = url_pattern.search(url).group('id_') + if id_: + return ('google', id_) + else: + return None # }}} def create_query(self, title=None, authors=None, identifiers={}, capitalize_isbn=False): # {{{