📄 index.lxp@lxpwrap=x7601_252ehtm.htm
字号:
>self.items</TT>.</P><P>If the end of a line is reached, drawing continues on the next line.</P><P>An <SPAN><ICLASS="EMPHASIS">essential</I></SPAN> step, and one which I tend to forget myself, is to resize the <TTCLASS="CLASSNAME">QCanvas</TT> after having determined what space the items take. You can place items outside the confines of the canvas, and they won't show unless you resize the canvas to include them.</P><P>Finally, you must <TTCLASS="FUNCTION">update()</TT> the <TTCLASS="CLASSNAME">QCanvas</TT> — otherwise you still won't see anything. This method updates all <TTCLASS="CLASSNAME">QCanvasView</TT> objects that show this canvas.</P><P>Setting the font involves drawing the table anew. This is more efficient than applying the font change to each individual <TTCLASS="CLASSNAME">QCanvasText</TT> item — even though that is perfectly possible. The reason is that if the font metrics change, for instance because the new font is a lot larger, you will have to check for collisions and adjust the location of all items anyway. That would take not only a lot of time, it would also demand complex and unmaintainable code. Simple is good, as far as I'm concerned.</P><P>This little table shows almost nothing of the power of <TTCLASS="CLASSNAME">QCanvas</TT> — you can animate the objects, determine if they overlap, and lots more. It offers everything you need, for instance, to write your very own <SPAN><ICLASS="EMPHASIS">Asteroids</I></SPAN> clone...</P></DIV><DIVCLASS="SECT3"><H3CLASS="SECT3">The view on the canvas</A></H3><P>Putting stuff on a canvas is useless, unless you can also see what you've done. You can create one or more <TTCLASS="CLASSNAME">QCanvasView</TT> objects that show the contents of canvas. Each view can show a different part, but every time you call <TTCLASS="FUNCTION">update()</TT> (or <TTCLASS="FUNCTION">advance()</TT>, which advances all animated objects), all views are updated.</P><P>The most important work your <TTCLASS="CLASSNAME">QCanvasView</TT> subclasses have is to react on user input. Here, we draw a cursor rectangle round selected glyphs and emit signals for every mousepress.</P><PRECLASS="PROGRAMLISTING">class CharsetBrowser(QCanvasView): def __init__(self, *args): apply(QCanvasView.__init__,(self,)+args) def setCursor(self, item): self.cursorItem=QCanvasRectangle(self.canvas()) self.cursorItem.setX(item.boundingRect().x() -2) self.cursorItem.setY(item.boundingRect().y() -2) self.cursorItem.setSize(item.boundingRect().width() + 4, item.boundingRect().height() + 4) self.cursorItem.setZ(-1.0) self.cursorItem.setPen(QPen(QColor(Qt.gray), 2, Qt.DashLine)) self.cursorItem.show() self.canvas().update() def contentsMousePressEvent(self, ev): try: items=self.canvas().collisions(ev.pos()) self.setCursor(items[0]) self.emit(PYSIGNAL("sigMousePressedOn"), (items[0].text(),)) except IndexError: pass def setFont(self, font): self.font=font self.canvas().setFont(self.font) </PRE><P>First, the drawing of the cursor. You can see that you don't need to create your canvas items in the <TTCLASS="CLASSNAME">QCanvas</TT> class or its derivatives. Here, it is done in the <TTCLASS="FUNCTION">setCursor()</TT> method. This method is called with the activated <TTCLASS="CLASSNAME">QCanvasText</TT> item as its parameter.</P><P>A new item is created, a <TTCLASS="CLASSNAME">QCanvasRectangle</TT> called <TTCLASS="VARNAME">self.cursorItem</TT>. It's an instance, not a local variable, because otherwise the rectangle would disappear once the item goes out of scope (because the function finishes).</P><P>The location and dimensions of the rectangle are determined. It will be a two-pixel wide, gray, dashed line exactly outside the current glyph. Of course, it must be shown, and the canvas must call <TTCLASS="FUNCTION">update()</TT> in order to notify the view(s). Note that you can retrieve a canvas shown by <TTCLASS="CLASSNAME">QCanvasView</TT> with the <TTCLASS="FUNCTION">canvas()</TT> function.</P><P>If you consult PyQt's documentation (or the C++ Qt documentation) on <TTCLASS="CLASSNAME">QCanvasView</TT>, you will notice that it is not very well endowed with useful functions. <TTCLASS="CLASSNAME">QCanvasView</TT> is a type of specialized <TTCLASS="CLASSNAME">QScrollView</TT>, and this class offers lots of useful methods (for example, event handling methods for mouse events).</P><P>One of these methods, <TTCLASS="FUNCTION">contentsMousePressEvent</TT>, is highly useful. It is called whenever a user clicks somewhere on the canvas view. You can then use the coordinates of the click to determine which <TTCLASS="CLASSNAME">QCanvasItem</TT> objects were hit. The coordinates of the mouse click can be retrieved with the <TTCLASS="FUNCTION">pos()</TT> function of the <TTCLASS="VARNAME">ev</TT> <TTCLASS="CLASSNAME">QMouseEvent</TT>. You then check which <TTCLASS="CLASSNAME">QCanvasItem</TT> objects were hit using the collision detection <TTCLASS="CLASSNAME">QCanvas</TT> provides with the <TTCLASS="FUNCTION">collisions()</TT>.</P><P>The result is a list of items. Because we <SPAN><ICLASS="EMPHASIS">know</I></SPAN> that there are no overlapping items on our canvas, we can simply take the first <TTCLASS="CLASSNAME">QCanvasText</TT> item: that's <TTCLASS="VARNAME">items[0]</TT>. Now we have the selected glyph. The <TTCLASS="FUNCTION">setCursor()</TT> function is called to draw a rectangle around the glyph. Then a signal is emitted, which can be caught by other widgets. This signal is ultimately responsible for getting the selected character in the <SPANCLASS="APPLICATION">Kalam</SPAN> document.</P></DIV><DIVCLASS="SECT3"><H3CLASS="SECT3">Tying the canvas and view together</A></H3><P>The <TTCLASS="CLASSNAME">CharMap</TT> widget is a specialized <TTCLASS="CLASSNAME">QWidget</TT> that contains the three components we developed above.</P><P>A vertical layout manager contains the selection combobox and the <TTCLASS="CLASSNAME">CharsetBrowser</TT> <TTCLASS="CLASSNAME">QCanvasView</TT> widget. Every time a new script is selected, a new <TTCLASS="CLASSNAME">CharsetCanvas</TT> is created — this is easier than erasing the contents of the existing canvas.</P><PRECLASS="PROGRAMLISTING">class CharMap(QWidget): def __init__(self, parent, initialFont = "arial", datadir = "unidata", *args): apply(QWidget.__init__, (self, parent, ) + args) self.parent=parent self.font=initialFont self.box=QVBoxLayout(self) self.comboCharset=CharsetSelector(datadir, FALSE, self) self.box.addWidget(self.comboCharset) self.charsetCanvas=CharsetCanvas(self, self.font, 0, 0, 0) self.charsetBrowser=CharsetBrowser(self.charsetCanvas, self) self.box.addWidget(self.charsetBrowser) self.setCaption("Unicode Character Picker") self.connect(qApp, PYSIGNAL("sigtextfontChanged"), self.setFont) self.connect(self.comboCharset, PYSIGNAL("sigActivated"), self.slotShowCharset) self.connect(self.charsetBrowser, PYSIGNAL("sigMousePressedOn"), self.sigCharacterSelected) self.resize(300,300) self.comboCharset.sigActivated(self.comboCharset.currentItem()) </PRE><P>In the constructor of <TTCLASS="CLASSNAME">CharMap</TT> both the selector combobox and the canvasview are created. We create an initial canvas for the view to display. The <TTCLASS="VARNAME">qApp.sigtextfontChanged</TT> signal is used to redraw the character map when the application font changes. Recall how we synthesized signals for all configuration options in <A href="index.lxp@lxpwrap=c6013_252ehtm.htm">Chapter 18</A>, and used the globally available <TTCLASS="VARNAME">qApp</TT> object to emit those signals.</P><PRECLASS="PROGRAMLISTING"> def setFont(self, font): self.font=font self.charsetBrowser.setFont(font) def sigCharacterSelected(self, text): self.emit(PYSIGNAL("sigCharacterSelected"), (text,)) </PRE><P>Every time a user selects a character, the <TTCLASS="VARNAME">sigCharacterSelected</TT> signal is emitted. In <TTCLASS="CLASSNAME">KalamApp</TT>, this signal is connected to the a slot function that inserts the character in the current view or window.</P><PRECLASS="PROGRAMLISTING"> def slotShowCharset(self, begin, end): self.setCursor(Qt.waitCursor) self.charTable=CharsetCanvas(self, self.font, begin, end, self.width() - 40) self.charsetBrowser.setCanvas(self.charTable) self.setCursor(Qt.arrowCursor) </PRE><P>Drawing a character map can take a while, especially if you select the set of Chinese characters, which has a few tens of thousands of entries. In order to not disquiet the user, we set a waiting cursor—this is a small wristwatch on most versions of Unix/X11, and the familiar sand-timer on Windows. Then a new canvas is created and the canvas view is told to display it.</P><DIVCLASS="WARNING"><P></P><TABLECLASS="WARNING"BORDER="1"WIDTH="100%"><TR><TDALIGN="CENTER"><B>Saving Unicode files</B></TD></TR><TR><TDALIGN="LEFT"><P>Recall <A href="index.lxp@lxpwrap=c2029_252ehtm.htm">Chapter 8</A> on Unicode— if you implement this character map and want to save your carefully created Thai letter, you will be greeted by an encoding error.</P><P>To avoid that, you need to use of the <TTCLASS="FUNCTION">unicode</TT> function instead of <TTCLASS="FUNCTION">str()</TT> when converting the <TTCLASS="CLASSNAME">KalamDoc</TT>.<TTCLASS="VARNAME">text</TT> <TTCLASS="CLASSNAME">QString</TT> variable to Python strings.</P></TD></TR></TABLE></DIV><DIVCLASS="NOTE"><BLOCKQUOTECLASS="NOTE"><P><B>Input methods and foreign keyboards: </B>If you have played around with the version of Kalam that belongs to this chapter, you will no doubt have noticed that writing a letter in, say, Tibetan, is not quite as easy as just banging on the keyboard (to say nothing of writing Chinese, which demands advanced hunting and picking skills).</P><P>A character map like we just made is useful for the occasional phonetic or mathematics character, but not a substitute for the real stuff: specific keyboard layouts for alphabetic scripts, like Cyrillic or Thai, and input method editors for languages like Chinese.</P><P>Properly speaking, it's the job of the Operating System or the GUI system to provide this functionality. Specialized keyboard layouts are fairly easy to come by, at least in the Unix/X11 world. My KDE 2 desktop has lots of keyboard layouts — perhaps you have to buy them in the Windows world. Still, it's not worthwhile to create special keyboard layouts in PyQt.</P><P>It is possible to create your own keyboard layouts in PyQt: re-implement the <TTCLASS="FUNCTION">keyPressEvent()</TT> of the view class and use each pressed key as an index into a dictionary that maps plain keyboard key definitions to, say, Tibetan Unicode characters. This is the same technique we used in <A href="index.lxp@lxpwrap=c5783_252ehtm.htm">Chapter 17</A> to make sure tab characters ended up in the text</P><PRECLASS="PROGRAMLISTING"> keymap={Qt.Key_A: QString(u"\u0270")} def keyPressEvent(self, ev): if keymap.has_key(ev.key()): self.insert(keymap[ev.key()]) else: QMultiLineEdit.keyPressEvent(self, ev) </PRE><P>Input method editors (IME's) are more difficult. Installing the free Chinese or Japanese IME's on Unix/X11 is a serious challenge. Getting your applications to work with them is another challenge. There are, however, special Chinese, Korean and Japanese versions of Qt to deal with these problems. As for Windows, I think you need a special Chinese, Korean or Japanese version of Windows.</P><P>It can be worthwhile to implement a Chinese IME, for instance, yourself:</P><DIVCLASS="MEDIAOBJECT"><P><DIVCLASS="CAPTION"><P>A Chinese input method editor written in Python and PyQt.</P></DIV></P></DIV><P>You can find the code for a stand-alone Pinyin-based Chinese IME at http://www.valdyas.org/python/qt2.html — it's also a nice example of using large Python dictionaries (every Mandarin Chinese syllable is mapped to a list characters with that pronunciation, and Emacs cannot syntax-color the file containing the dictionary).</P></BLOCKQUOTE></DIV></DIV></DIV></DIV><DIVCLASS="NAVFOOTER"><HRALIGN="LEFT"WIDTH="100%"><TABLESUMMARY="Footer navigation table"WIDTH="100%"BORDER="0"CELLPADDING="0"CELLSPACING="0"><TR><TDWIDTH="33%"ALIGN="left"VALIGN="top"><A accesskey="P" href="index.lxp@lxpwrap=c7391_252ehtm.htm">Prev</A></TD><TDWIDTH="34%"ALIGN="center"VALIGN="top"><A accesskey="H" href="index.lxp@lxpwrap=book1_252ehtm">Home</A></TD><TDWIDTH="33%"ALIGN="right"VALIGN="top"><A accesskey="N" href="index.lxp@lxpwrap=x7875_252ehtm.htm">Next</A></TD></TR><TR><TDWIDTH="33%"ALIGN="left"VALIGN="top">Drawing on Painters and Canvases</TD><TDWIDTH="34%"ALIGN="center"VALIGN="top"><A accesskey="U" href="index.lxp@lxpwrap=c7391_252ehtm.htm">Up</A></TD><TDWIDTH="33%"ALIGN="right"VALIGN="top">Conclusion</TD></TR></TABLE></DIV></BODY></HTML> </td> </tr> </table> </td> </tr> </table>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -